import { loadPiSettings } from '@amaster.ai/pi-shared/settings';
import { getAgentDir } from '@earendil-works/pi-coding-agent';
import { SecurityGate, securityEvaluationDetails, } from './index.js';
import { loadFilePolicies } from './policy-loader.js';
const SETTINGS_KEY = 'pi-security';
const EXTENSION_STATUS_KEY = 'pi-security';
const DEFAULT_PROFILE = 'auto-review';
const DEFAULT_AUDIT_LIMIT = 200;
export default function piSecurityExtension(pi) {
    const state = {
        config: resolvePiSecurityConfig(),
        auditLog: [],
        grants: [],
    };
    pi.on('session_start', async (_event, ctx) => {
        state.config = resolvePiSecurityConfig(loadSettings(ctx.cwd));
        state.auditLog = [];
        state.grants = [];
        ctx.ui.setStatus(EXTENSION_STATUS_KEY, state.config.enabled ? `security: ${state.config.profile}` : 'security: disabled');
    });
    pi.on('session_shutdown', async (_event, ctx) => {
        ctx.ui.setStatus(EXTENSION_STATUS_KEY, undefined);
        state.auditLog = [];
        state.grants = [];
    });
    pi.on('tool_call', async (event, ctx) => authorizePiToolCall(event, ctx, state));
    pi.on('user_bash', async (event, ctx) => authorizeUserBash(event, ctx, state));
    pi.registerCommand('pi-security-status', {
        description: 'Show Pi security policy status.',
        handler: async (_args, ctx) => {
            ctx.ui.notify(formatSecurityStatus(state), 'info');
        },
    });
    pi.registerCommand('pi-security-audit', {
        description: 'Show recent Pi security authorization decisions.',
        handler: async (args, ctx) => {
            ctx.ui.notify(formatAuditLog(state, parseAuditLimit(args)), 'info');
        },
    });
    pi.registerCommand('pi-security-reset', {
        description: 'Clear in-session Pi security approval grants.',
        handler: async (_args, ctx) => {
            state.grants = [];
            ctx.ui.notify('Pi security session grants cleared.', 'info');
        },
    });
}
export async function authorizePiToolCall(event, ctx, state) {
    if (!state.config.enabled) {
        return undefined;
    }
    const toolCall = toolCallFromPiEvent(event);
    const evaluation = await createGate(ctx, state).authorize({
        request: requestFromContext(ctx),
        toolCall,
        workspaceDir: ctx.cwd,
    });
    if (evaluation.decision.kind === 'deny' || evaluation.decision.kind === 'ask') {
        return { block: true, reason: evaluation.decision.reason };
    }
    return undefined;
}
export async function authorizeUserBash(event, ctx, state) {
    if (!state.config.enabled) {
        return undefined;
    }
    const toolCall = {
        id: 'user-bash',
        name: 'bash',
        source: 'builtin',
        args: {
            command: event.command,
            cwd: event.cwd,
            excludeFromContext: event.excludeFromContext,
        },
    };
    const evaluation = await createGate(ctx, state).authorize({
        request: requestFromContext(ctx),
        toolCall,
        workspaceDir: ctx.cwd,
    });
    if (evaluation.decision.kind === 'deny' || evaluation.decision.kind === 'ask') {
        return {
            result: {
                output: evaluation.decision.reason,
                exitCode: 1,
                cancelled: false,
                truncated: false,
            },
        };
    }
    return undefined;
}
export function resolvePiSecurityConfig(config) {
    const configuredAuditLimit = config?.auditLimit;
    const auditLimit = Number.isInteger(configuredAuditLimit) &&
        configuredAuditLimit !== undefined &&
        configuredAuditLimit > 0
        ? Math.min(configuredAuditLimit, 1_000)
        : DEFAULT_AUDIT_LIMIT;
    return {
        enabled: config?.enabled !== false,
        profile: config?.profile?.trim() || DEFAULT_PROFILE,
        ...(config?.security ? { security: config.security } : {}),
        auditLimit,
        allowSessionGrants: config?.approvals?.allowSessionGrants !== false,
    };
}
function createGate(ctx, state) {
    return new SecurityGate({
        profile: state.config.profile,
        ...(state.config.security ? { config: state.config.security } : {}),
        filePolicies: loadFilePolicies(ctx.cwd, getAgentDir()),
        approvalHandler: async ({ toolCall, decision }) => resolveApproval(ctx, state, toolCall, decision),
        auditSink: (event) => {
            state.auditLog.push(event);
            if (state.auditLog.length > state.config.auditLimit) {
                state.auditLog.splice(0, state.auditLog.length - state.config.auditLimit);
            }
        },
    });
}
async function resolveApproval(ctx, state, toolCall, decision) {
    if (hasSessionGrant(state, toolCall, decision)) {
        return { kind: 'allow', reason: `Allowed by in-session grant: ${decision.reason}` };
    }
    if (!ctx.hasUI) {
        return decision;
    }
    if (!state.config.allowSessionGrants) {
        const approved = await ctx.ui.confirm(decision.prompt ?? 'Approve tool call?', approvalMessage(toolCall, decision));
        return approved
            ? { kind: 'allow', reason: `Approved by user: ${decision.reason}` }
            : denyByUser(decision);
    }
    const choice = await ctx.ui.select(`${decision.prompt ?? 'Approve tool call?'}\n${approvalMessage(toolCall, decision)}`, ['Allow once', 'Allow similar for this session', 'Deny']);
    if (choice === 'Allow once') {
        return { kind: 'allow', reason: `Approved by user: ${decision.reason}` };
    }
    if (choice === 'Allow similar for this session') {
        state.grants.push({ toolName: toolCall.name, reason: decision.reason });
        return { kind: 'allow', reason: `Approved by user with session grant: ${decision.reason}` };
    }
    return denyByUser(decision);
}
function loadSettings(cwd) {
    try {
        const config = loadPiSettings(SETTINGS_KEY, {
            cwd,
        });
        return Object.keys(config).length > 0 ? config : undefined;
    }
    catch {
        return undefined;
    }
}
function isPlainObject(value) {
    return Boolean(value && typeof value === 'object' && !Array.isArray(value));
}
function requestFromContext(ctx) {
    const sessionId = ctx.sessionManager.getSessionId();
    const model = ctx.model;
    return {
        sessionId,
        conversationId: sessionId,
        trigger: 'user',
        senderTrust: 'owner',
        interactive: ctx.hasUI,
        model: {
            provider: stringFromRecord(model, 'provider') ?? 'pi',
            model: stringFromRecord(model, 'id') ?? stringFromRecord(model, 'name') ?? 'unknown',
        },
    };
}
function toolCallFromPiEvent(event) {
    return {
        id: event.toolCallId,
        name: event.toolName,
        source: toolSourceForPiTool(event.toolName),
        args: normalizeToolArgs(event.toolName, event.input),
    };
}
function toolSourceForPiTool(toolName) {
    if (toolName.startsWith('mcp_') || toolName.startsWith('mcp__')) {
        return 'mcp';
    }
    if (['bash', 'write', 'edit', 'read', 'ls', 'grep', 'find'].includes(toolName)) {
        return 'builtin';
    }
    return 'runtime';
}
function normalizeToolArgs(toolName, input) {
    const args = toJsonObject(input);
    if (toolName === 'bash' && typeof input.command === 'string') {
        return { ...args, command: input.command };
    }
    return args;
}
function toJsonObject(input) {
    const result = {};
    for (const [key, value] of Object.entries(input)) {
        const jsonValue = toJsonValue(value);
        if (jsonValue !== undefined) {
            result[key] = jsonValue;
        }
    }
    return result;
}
function toJsonValue(value) {
    if (value === null ||
        typeof value === 'string' ||
        typeof value === 'number' ||
        typeof value === 'boolean') {
        return value;
    }
    if (Array.isArray(value)) {
        return value.map(toJsonValue).filter((item) => item !== undefined);
    }
    if (isPlainObject(value)) {
        return toJsonObject(value);
    }
    return undefined;
}
function hasSessionGrant(state, toolCall, decision) {
    return state.grants.some((grant) => grant.toolName === toolCall.name && grant.reason === decision.reason);
}
function denyByUser(decision) {
    return { kind: 'deny', reason: `User denied approval: ${decision.reason}` };
}
function approvalMessage(toolCall, decision) {
    const target = summarizeToolCall(toolCall);
    return target ? `${decision.reason}\n\n${target}` : decision.reason;
}
function summarizeToolCall(toolCall) {
    const command = stringArg(toolCall.args, 'command');
    if (command) {
        return command;
    }
    const filePath = stringArg(toolCall.args, 'path') ?? stringArg(toolCall.args, 'file_path');
    if (filePath) {
        return filePath;
    }
    return toolCall.name;
}
function formatSecurityStatus(state) {
    return [
        `Pi security: ${state.config.enabled ? 'enabled' : 'disabled'}`,
        `Profile: ${state.config.profile}`,
        `Audit entries: ${state.auditLog.length}`,
        `Session grants: ${state.grants.length}`,
    ].join('\n');
}
function formatAuditLog(state, limit) {
    if (state.auditLog.length === 0) {
        return 'Pi security audit log is empty.';
    }
    return state.auditLog
        .slice(-limit)
        .map((event) => {
        const details = securityEvaluationDetails(event);
        return `${event.createdAt} ${event.toolName} -> ${event.decision.kind} (${details.risk})`;
    })
        .join('\n');
}
function parseAuditLimit(args) {
    const parsed = Number.parseInt((args ?? '').trim(), 10);
    return Number.isFinite(parsed) && parsed > 0 ? Math.min(parsed, 50) : 10;
}
function stringArg(args, key) {
    const value = args[key];
    return typeof value === 'string' ? value : undefined;
}
function stringFromRecord(value, key) {
    if (!value || typeof value !== 'object') {
        return undefined;
    }
    const actual = value[key];
    return typeof actual === 'string' ? actual : undefined;
}
//# sourceMappingURL=extension.js.map