/*
 * 文件定位：Agent 执行引擎 / 基础工具原语层 / 基础工具集合 / Shell 基础工具 / Shell 生成。
 * 核心目的：提供 Shell 基础工具 / Shell 生成 中的“生成脚本”基础能力原语。
 * 能力要求1：需要定义该能力的输入、输出、错误、权限需求和可观测事件。
 * 能力要求2：这些基础工具是 Agent 成立的底层能力，不是 TAP 的最终高级工具库。
 * 能力要求3：后续 TAP 可以基于这些原语构建更强的工具编排、审批、替换和专业能力库。
 * 边界：保留 Agent 基础工具原语，不替代 TAP 的高级工具系统。
 * 对接：需要被 runtime.execEngine 拉起，并和 mainLoop、stateEngine、事件暴露、工具调用策略接通。
 * 实现提示：先补稳定类型契约、最小可测行为和清晰错误边界，再接入真实执行逻辑。
 */
export const shellScriptGenerationDescriptor = {
    toolId: "shell.scriptGeneration",
    capability: "shell-script-generation",
    route: "agent_executionEngine.basic_toolLayer.baseTools.shellBase.shellGeneration",
    defaultDryRun: true,
    requiredPermission: "shell:script:generate",
    tapOwnsApproval: true,
    unsafeSideEffects: false,
};
const scriptNamePattern = /^[a-zA-Z0-9._-]+$/;
const shellOperatorPattern = /(?:&&|\|\||;|\|(?!=)|>|<|`|\$\()/;
function cleanStringList(values) {
    if (values === undefined) {
        return [];
    }
    if (!Array.isArray(values)) {
        return [];
    }
    return [...new Set(values.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean))];
}
function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function dryRunEnabled(context) {
    return context?.dryRun !== false;
}
function invocationId(context) {
    return typeof context?.invocationId === "string" && context.invocationId.trim().length > 0
        ? context.invocationId.trim()
        : "shell.scriptGeneration:dry-run";
}
function normalizeDirectory(directory) {
    const trimmed = directory.trim();
    return trimmed === "/" ? trimmed : trimmed.replace(/\/+$/, "");
}
function auditEvent(type, context, metadata) {
    return {
        type,
        toolId: shellScriptGenerationDescriptor.toolId,
        invocationId: invocationId(context),
        dryRun: dryRunEnabled(context),
        metadata: {
            ...(context?.auditMetadata ?? {}),
            ...(metadata ?? {}),
        },
    };
}
function failure(code, message, boundary, context) {
    return {
        ok: false,
        toolId: shellScriptGenerationDescriptor.toolId,
        error: { code, message, boundary, publicSafe: true, internalDetailExposed: false },
        audit: [auditEvent("agentCore.basicTool.shell.scriptGeneration.rejected", context, { code })],
        events: ["basicTool.shell.scriptGeneration.rejected"],
    };
}
function ensureScope(workingDirectory, context) {
    if (workingDirectory === undefined) {
        return undefined;
    }
    const allowedDirectories = cleanStringList(context?.allowedWorkingDirectories).map(normalizeDirectory);
    if (allowedDirectories.length === 0) {
        return undefined;
    }
    const normalizedDirectory = normalizeDirectory(workingDirectory);
    const allowed = allowedDirectories.some((directory) => normalizedDirectory === directory || normalizedDirectory.startsWith(`${directory}/`));
    if (allowed) {
        return undefined;
    }
    return failure("SCOPE_REJECTED", "shell.scriptGeneration workingDirectory is outside allowed shell generation scope", "scope", context);
}
function ensurePermission(context) {
    if (context?.grantedPermissions === undefined) {
        return undefined;
    }
    if (cleanStringList(context.grantedPermissions).includes(shellScriptGenerationDescriptor.requiredPermission)) {
        return undefined;
    }
    return failure("PERMISSION_DENIED", "shell.scriptGeneration is missing permission: shell:script:generate", "permission", context);
}
function ensureDryRunOnly(context) {
    if (dryRunEnabled(context)) {
        return undefined;
    }
    return failure("REAL_EXECUTION_BLOCKED", "shell.scriptGeneration only creates a guarded dry-run script envelope in the first implementation", "contract", context);
}
function shellQuote(value) {
    return `'${value.replace(/'/g, "'\\''")}'`;
}
function normalizeEnvironment(environment, context) {
    if (environment === undefined) {
        return undefined;
    }
    if (!isRecord(environment)) {
        return failure("INVALID_ENVIRONMENT", "shell.scriptGeneration environment must use safe shell variable names and string values", "input", context);
    }
    const entries = Object.entries(environment);
    for (const [key, value] of entries) {
        if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || typeof value !== "string" || value.includes("\0")) {
            return failure("INVALID_ENVIRONMENT", "shell.scriptGeneration environment must use safe shell variable names and string values", "input", context);
        }
    }
    return Object.fromEntries(entries.sort(([left], [right]) => left.localeCompare(right)));
}
function isShellScriptGenerationResult(value) {
    return (typeof value === "object" &&
        value !== null &&
        value.toolId === shellScriptGenerationDescriptor.toolId &&
        typeof value.ok === "boolean");
}
function normalizeTarget(target, context) {
    if (target !== undefined && !isRecord(target)) {
        return failure("MISSING_COMMANDS", "shell.scriptGeneration requires at least one command", "input", context);
    }
    const shell = target?.shell ?? "bash";
    if (shell !== "sh" && shell !== "bash" && shell !== "zsh") {
        return failure("INVALID_SHELL", "shell.scriptGeneration shell must be sh, bash, or zsh", "input", context);
    }
    if (target?.scriptName !== undefined && typeof target.scriptName !== "string") {
        return failure("INVALID_SCRIPT_NAME", "shell.scriptGeneration scriptName must be a string when provided", "input", context);
    }
    const scriptName = typeof target?.scriptName === "string" ? target.scriptName.trim() || undefined : undefined;
    if (scriptName !== undefined && !scriptNamePattern.test(scriptName)) {
        return failure("INVALID_SCRIPT_NAME", "shell.scriptGeneration scriptName must be a safe file-like label", "input", context);
    }
    if (target?.commands !== undefined &&
        (!Array.isArray(target.commands) || target.commands.some((command) => typeof command !== "string"))) {
        return failure("INVALID_COMMAND", "shell.scriptGeneration commands must be an array of strings", "input", context);
    }
    const commands = cleanStringList(target?.commands);
    if (commands.length === 0) {
        return failure("MISSING_COMMANDS", "shell.scriptGeneration requires at least one command", "input", context);
    }
    if (commands.some((command) => command.includes("\0"))) {
        return failure("INVALID_COMMAND", "shell.scriptGeneration commands must be safe strings", "input", context);
    }
    const environment = normalizeEnvironment(target?.environment, context);
    if (isShellScriptGenerationResult(environment)) {
        return environment;
    }
    if (target?.workingDirectory !== undefined && typeof target.workingDirectory !== "string") {
        return failure("INVALID_WORKING_DIRECTORY", "shell.scriptGeneration workingDirectory must be a string when provided", "input", context);
    }
    const workingDirectory = typeof target?.workingDirectory === "string" ? target.workingDirectory.trim() || undefined : undefined;
    return {
        scriptName,
        shell,
        workingDirectory,
        commands,
        environment,
        notes: cleanStringList(target?.notes),
    };
}
function buildScript(target) {
    const lines = [`#!/usr/bin/env ${target.shell}`, target.shell === "sh" ? "set -eu" : "set -euo pipefail"];
    if (target.workingDirectory !== undefined) {
        lines.push(`cd ${shellQuote(target.workingDirectory)}`);
    }
    for (const [key, value] of Object.entries(target.environment ?? {})) {
        lines.push(`export ${key}=${shellQuote(value)}`);
    }
    for (const note of target.notes ?? []) {
        lines.push(`# ${note}`);
    }
    lines.push(...target.commands);
    return `${lines.join("\n")}\n`;
}
function needsTapApproval(commands) {
    return commands.some((command) => command.trim().startsWith("sudo ") || shellOperatorPattern.test(command));
}
export function generateShellScriptPlan(request = {}) {
    if (!isRecord(request)) {
        return failure("MISSING_COMMANDS", "shell.scriptGeneration requires at least one command", "input", undefined);
    }
    if (request.context?.guard?.accepted === false) {
        return failure("GOVERNANCE_REJECTED", request.context.guard.reason ?? "shell.scriptGeneration was rejected by runtime governance", "governance", request.context);
    }
    const dryRunFailure = ensureDryRunOnly(request.context);
    if (dryRunFailure !== undefined) {
        return dryRunFailure;
    }
    const permissionFailure = ensurePermission(request.context);
    if (permissionFailure !== undefined) {
        return permissionFailure;
    }
    const target = normalizeTarget(request.target, request.context);
    if ("ok" in target) {
        return target;
    }
    const scopeFailure = ensureScope(target.workingDirectory, request.context);
    if (scopeFailure !== undefined) {
        return scopeFailure;
    }
    const script = buildScript(target);
    const requiresTapApproval = needsTapApproval(target.commands);
    return {
        ok: true,
        toolId: shellScriptGenerationDescriptor.toolId,
        output: {
            kind: "agentCore.basicTool.shell.scriptGeneration",
            target,
            script,
            commandPreview: target.commands,
            lineCount: script.trimEnd().split("\n").length,
            requiredPermission: shellScriptGenerationDescriptor.requiredPermission,
            requiresTapApproval,
            dryRun: true,
            providerCalled: false,
            executionBlocked: true,
            unsafeSideEffects: false,
        },
        audit: [
            auditEvent("agentCore.basicTool.shell.scriptGeneration.dryRun", request.context, {
                commandCount: target.commands.length,
                shell: target.shell,
                requiresTapApproval,
            }),
        ],
        events: ["basicTool.shell.scriptGeneration.dryRun"],
    };
}
