/*
 * 文件定位：Agent 执行引擎 / 基础工具原语层 / 基础工具集合 / Shell 基础工具 / Shell 执行。
 * 核心目的：提供 Shell 基础工具 / Shell 执行 中的“执行命令”基础能力原语。
 * 能力要求1：需要定义该能力的输入、输出、错误、权限需求和可观测事件。
 * 能力要求2：这些基础工具是 Agent 成立的底层能力，不是 TAP 的最终高级工具库。
 * 能力要求3：后续 TAP 可以基于这些原语构建更强的工具编排、审批、替换和专业能力库。
 * 边界：保留 Agent 基础工具原语，不替代 TAP 的高级工具系统。
 * 对接：需要被 runtime.execEngine 拉起，并和 mainLoop、stateEngine、事件暴露、工具调用策略接通。
 * 实现提示：先补稳定类型契约、最小可测行为和清晰错误边界，再接入真实执行逻辑。
 */
import { describeShellWorkspaceWrite, shellWorkspaceWriteGuardMessage, } from "../../_shared/workspaceWriteGuard.js";
export const shellCommandExecutionDescriptor = {
    toolId: "shell.commandExecution",
    capability: "execute-command",
    layer: "agent_executionEngine.basic_toolLayer.baseTools.shellBase.shellExecution",
    defaultDispatch: "dry-run",
    defaultTimeoutMs: 30_000,
    maxTimeoutMs: 600_000,
    permissionsRequired: ["shell:execute"],
    unsafeSideEffects: false,
    requiresTapApproval: true,
    tapOwnsApproval: true,
};
function stringValue(value) {
    return typeof value === "string" ? value : undefined;
}
function isBlank(value) {
    return typeof value !== "string" || value.trim().length === 0;
}
function cleanList(values) {
    if (!Array.isArray(values)) {
        return [];
    }
    return [...new Set(values.map((value) => stringValue(value)?.trim() ?? "").filter(Boolean))];
}
function dryRunEnabled(context) {
    return context?.dryRun !== false;
}
function invocationId(context, runtimeId, command) {
    return stringValue(context?.invocationId)?.trim() || `${runtimeId}:shell.commandExecution:${command}`;
}
function guardRejected(guard) {
    return guard?.accepted === false || guard?.allowed === false;
}
function auditEvent(type, context, metadata) {
    return {
        type,
        toolId: shellCommandExecutionDescriptor.toolId,
        invocationId: stringValue(context?.invocationId)?.trim() || `${shellCommandExecutionDescriptor.toolId}:dry-run`,
        dryRun: dryRunEnabled(context),
        metadata: {
            ...(context?.auditMetadata ?? {}),
            ...(metadata ?? {}),
        },
    };
}
function failure(code, message, boundary, context) {
    return {
        ok: false,
        error: {
            code,
            message,
            boundary,
            safeForRuntimeInspection: true,
            internalDetailExposed: false,
        },
        events: ["basicTool.shell.commandExecution.rejected"],
    };
}
function toolFailure(code, message, boundary, context) {
    return {
        ok: false,
        toolId: shellCommandExecutionDescriptor.toolId,
        error: {
            code,
            message,
            boundary,
            safeForRuntimeInspection: true,
            internalDetailExposed: false,
        },
        audit: [auditEvent("agentCore.basicTool.shell.commandExecution.rejected", context, { code, boundary })],
        events: ["basicTool.shell.commandExecution.rejected"],
    };
}
function publicSafeProviderErrorCode(error) {
    if (error instanceof Error && (error.name === "SANDBOX_UNAVAILABLE" || error.name === "SANDBOX_PROVIDER_UNSUPPORTED")) {
        return error.name;
    }
    return "PROVIDER_REJECTED";
}
function resolveScopes(requestedScopes, allowedScopes) {
    const requested = cleanList(requestedScopes);
    const allowed = cleanList(allowedScopes);
    const denied = requested.filter((scope) => !allowed.includes(scope));
    if (denied.length > 0) {
        return failure("SCOPE_DENIED", `shell.commandExecution scope ${denied[0]} is outside runtime governance`, "scope");
    }
    return requested;
}
function normalizeCommand(value) {
    if (isBlank(value)) {
        return failure("MISSING_COMMAND", "shell.commandExecution requires command", "input");
    }
    const command = stringValue(value)?.trim() ?? "";
    if (command.includes("\0") || /[\r\n]/u.test(command)) {
        return failure("INVALID_COMMAND", "shell.commandExecution command must be a single safe command token", "input");
    }
    return command;
}
function normalizeArgs(values) {
    if (values === undefined) {
        return [];
    }
    if (!Array.isArray(values)) {
        return failure("INVALID_ARGUMENT", "shell.commandExecution args must be safe strings", "input");
    }
    const normalized = [];
    for (const arg of values) {
        if (typeof arg !== "string" || arg.includes("\0")) {
            return failure("INVALID_ARGUMENT", "shell.commandExecution args must be safe strings", "input");
        }
        if (arg.length > 8192) {
            return failure("INVALID_ARGUMENT", "shell.commandExecution args must stay within resource limits", "resource");
        }
        normalized.push(arg);
    }
    return normalized;
}
function normalizeCwd(value) {
    if (value === undefined) {
        return undefined;
    }
    if (typeof value !== "string") {
        return failure("INVALID_CWD", "shell.commandExecution cwd must be a safe path string", "input");
    }
    const cwd = value.trim();
    if (cwd.length === 0 || cwd.includes("\0")) {
        return failure("INVALID_CWD", "shell.commandExecution cwd must be a safe path string", "input");
    }
    return cwd;
}
function normalizeTimeout(value) {
    if (value !== undefined && typeof value !== "number") {
        return failure("INVALID_TIMEOUT", "shell.commandExecution timeoutMs must be between 1 and 600000", "resource");
    }
    const timeoutMs = value ?? shellCommandExecutionDescriptor.defaultTimeoutMs;
    if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > shellCommandExecutionDescriptor.maxTimeoutMs) {
        return failure("INVALID_TIMEOUT", "shell.commandExecution timeoutMs must be between 1 and 600000", "resource");
    }
    return timeoutMs;
}
function quoteShellCommandPart(part) {
    return /\s/u.test(part) ? JSON.stringify(part) : part;
}
function compactShellCommand(command, args) {
    return [command, ...args].map(quoteShellCommandPart).join(" ");
}
function hasBoundedLifetime(commandLine) {
    return /\btimeout\s+(?:--foreground\s+)?\d+(?:\.\d+)?[smhd]?\b/u.test(commandLine)
        || /\b(--help|-h|--version|-v)\b/u.test(commandLine);
}
function longRunningForegroundReason(command, args) {
    const commandLine = compactShellCommand(command, args);
    if (hasBoundedLifetime(commandLine)) {
        return undefined;
    }
    const normalized = commandLine.replace(/\s+/gu, " ").trim();
    if (/\bnode\s+(?:\.\/)?(?:server|app|index)\.[cm]?js\b/u.test(normalized)) {
        return "node server-style commands normally keep a web service running";
    }
    if (/\bnpm\s+(?:run\s+)?(?:dev|start|serve)\b/u.test(normalized)) {
        return "npm dev/start/serve scripts normally keep a web service running";
    }
    if (/\b(?:vite|next\s+dev|webpack-dev-server|python3?\s+-m\s+http\.server)\b/u.test(normalized)) {
        return "development server commands normally keep a web service running";
    }
    return undefined;
}
function normalizeShellCommandExecution(request, options) {
    const runtimeId = stringValue(request.context?.runtimeId)?.trim() ?? "";
    if (isBlank(runtimeId)) {
        return failure("MISSING_RUNTIME_ID", "shell.commandExecution requires context.runtimeId for audit", "input");
    }
    if (!options.allowRealExecution && request.context?.dryRun === false) {
        return failure("REAL_COMMAND_EXECUTION_NOT_ALLOWED", "first-round shell.commandExecution only creates a dry-run command plan", "contract");
    }
    if (guardRejected(request.context?.guard)) {
        return failure("GOVERNANCE_REJECTED", request.context?.guard?.reason ?? "shell.commandExecution was rejected by runtime governance", "governance");
    }
    const acceptedScopes = resolveScopes(request.context?.requestedScopes, request.context?.allowedScopes);
    if (!Array.isArray(acceptedScopes)) {
        return acceptedScopes;
    }
    const command = normalizeCommand(request.command);
    if (typeof command !== "string") {
        return command;
    }
    const args = normalizeArgs(request.args);
    if (!Array.isArray(args)) {
        return args;
    }
    const workspaceWriteReason = describeShellWorkspaceWrite([command, ...args].join(" "));
    if (workspaceWriteReason !== undefined) {
        return failure("WORKSPACE_WRITE_REQUIRES_CODE_TOOL", shellWorkspaceWriteGuardMessage(workspaceWriteReason), "governance");
    }
    const foregroundLongRunningReason = longRunningForegroundReason(command, args);
    if (foregroundLongRunningReason !== undefined) {
        return failure("LONG_RUNNING_FOREGROUND_COMMAND", [
            `shell.commandExecution refused foreground launch because ${foregroundLongRunningReason}.`,
            "Use shell.backgroundExecution or shell.detachedExecution for the service, or wrap a probe in timeout.",
            "When verifying a web app, read the actual listening port from stdout or scan localhost ports 3000-3020 instead of assuming 3000.",
        ].join(" "), "governance");
    }
    const cwd = normalizeCwd(request.cwd);
    if (cwd !== undefined && typeof cwd !== "string") {
        return cwd;
    }
    const timeoutMs = normalizeTimeout(request.timeoutMs);
    if (typeof timeoutMs !== "number") {
        return timeoutMs;
    }
    return {
        runtimeId,
        invocationId: invocationId(request.context, runtimeId, command),
        command,
        args,
        cwd,
        shellType: stringValue(request.shellType)?.trim() || undefined,
        timeoutMs,
        acceptedScopes,
    };
}
function isCommandExecutionResult(value) {
    return "ok" in value;
}
function isCommandExecutionFailure(value) {
    return "ok" in value && !value.ok;
}
export function planShellCommandExecution(request = {}) {
    const normalized = normalizeShellCommandExecution(request, { allowRealExecution: false });
    if (isCommandExecutionResult(normalized)) {
        return normalized;
    }
    return {
        ok: true,
        plan: {
            toolId: "shell.commandExecution",
            capability: "execute-command",
            runtimeId: normalized.runtimeId,
            invocationId: normalized.invocationId,
            command: normalized.command,
            args: normalized.args,
            cwd: normalized.cwd,
            shellType: normalized.shellType,
            timeoutMs: normalized.timeoutMs,
            requiredPermissions: ["shell:execute:dry-run"],
            requiresTapApproval: true,
            dispatch: "dry-run",
            dryRun: true,
            wouldSpawnProcess: true,
            unsafeSideEffects: false,
            acceptedScopes: normalized.acceptedScopes,
            outputEnvelope: {
                stdoutPreview: "",
                stderrPreview: "",
                started: false,
            },
            audit: {
                guard: "shell-command-dry-run-approval",
                event: "basicTool.shell.commandExecution.planned",
                metadata: {
                    ...(request.context?.auditMetadata ?? {}),
                    ...(request.metadata ?? {}),
                },
            },
        },
        events: ["basicTool.shell.commandExecution.planned"],
    };
}
function dryRunOutput(normalized) {
    return {
        kind: "agentCore.basicTool.shell.commandExecution",
        command: normalized.command,
        args: normalized.args,
        cwd: normalized.cwd,
        shellType: normalized.shellType,
        timeoutMs: normalized.timeoutMs,
        dryRun: true,
        providerCalled: false,
        stdout: "",
        stderr: "",
        permissionsRequired: shellCommandExecutionDescriptor.permissionsRequired,
        unsafeSideEffects: false,
    };
}
export async function executeShellCommand(request = {}) {
    const normalized = normalizeShellCommandExecution(request, { allowRealExecution: true });
    if (isCommandExecutionFailure(normalized)) {
        return toolFailure(normalized.error.code, normalized.error.message, normalized.error.boundary, request.context);
    }
    if (dryRunEnabled(request.context)) {
        return {
            ok: true,
            toolId: shellCommandExecutionDescriptor.toolId,
            output: dryRunOutput(normalized),
            audit: [
                auditEvent("agentCore.basicTool.shell.commandExecution.dryRun", request.context, {
                    command: normalized.command,
                    timeoutMs: normalized.timeoutMs,
                }),
            ],
            events: ["basicTool.shell.commandExecution.dryRun"],
        };
    }
    if (request.provider === undefined) {
        return toolFailure("PROVIDER_UNAVAILABLE", "shell.commandExecution requires a runtime-provided shell executor when dryRun is false", "provider", request.context);
    }
    try {
        const providerResult = await request.provider({
            command: normalized.command,
            args: normalized.args,
            cwd: normalized.cwd,
            timeoutMs: normalized.timeoutMs,
            stdin: request.stdin,
        }, request.context ?? {});
        return {
            ok: true,
            toolId: shellCommandExecutionDescriptor.toolId,
            output: {
                kind: "agentCore.basicTool.shell.commandExecution",
                command: normalized.command,
                args: normalized.args,
                cwd: normalized.cwd,
                shellType: normalized.shellType,
                timeoutMs: normalized.timeoutMs,
                dryRun: false,
                providerCalled: true,
                exitCode: providerResult.exitCode,
                stdout: providerResult.stdout,
                stderr: providerResult.stderr,
                permissionsRequired: shellCommandExecutionDescriptor.permissionsRequired,
                unsafeSideEffects: false,
            },
            audit: [
                auditEvent("agentCore.basicTool.shell.commandExecution.provider", request.context, {
                    command: normalized.command,
                    exitCode: providerResult.exitCode,
                }),
            ],
            events: ["basicTool.shell.commandExecution.providerCalled"],
        };
    }
    catch (error) {
        const code = publicSafeProviderErrorCode(error);
        return toolFailure(code, error instanceof Error ? error.message : "shell.commandExecution provider rejected the invocation", code === "SANDBOX_UNAVAILABLE" || code === "SANDBOX_PROVIDER_UNSUPPORTED" ? "governance" : "provider", request.context);
    }
}
