package com.gzzm.lobster.config;

import net.cyan.nest.annotation.Injectable;

/**
 * LobsterConfig —— 大龙虾全局配置 / Global tunables.
 *
 * <p>所有字段的默认值在此类源码中写死（保证 XML 未加载时仍能启动）；
 * 运行期由 cyan 的 {@code <config>} 机制读取 {@code web/WEB-INF/config/lobster.xml}，
 * 按 JavaBean setter 覆盖静态字段。
 *
 * <p>XML 写法：
 * <pre>{@code
 * <bean class="com.gzzm.lobster.config.LobsterConfig" factory="instance">
 *     <maxTurnsPerRun>20</maxTurnsPerRun>
 *     ...
 * </bean>
 * }</pre>
 *
 * <p>{@link Injectable} singleton=true 是 cyan {@code factory="instance"} 的前置条件；
 * 配合 public 空构造器，cyan 运行时 new 一份实例，把 XML 子标签按 JavaBean setter 注入，
 * setter 内部落到 static 字段。
 *
 * <p>业务代码请通过 {@code LobsterConfig.getXxx()} 读取，不要直接引用字段，
 * 方便以后换成支持热更新的实现。
 */
@Injectable(singleton = true)
public class LobsterConfig {

    // ===== Agent Runtime =====
    /** 单个 run 的最大轮数 / Max ReAct turns per run. */
    private static int maxTurnsPerRun = 20;
    /**
     * 上下文 token 预算的「兜底下界」/ Lower bound for context budget tokens.
     *
     * <p>实际预算 = max(此值, ModelProfile.contextWindow - maxOutputTokens - safetyMargin).
     * 模型 profile 未填 contextWindow 时直接走此兜底；现代模型（Claude 200k/1M、DeepSeek 128k 等）
     * 通常会覆盖出更大的动态预算.
     */
    private static int defaultContextBudgetTokens = 100000;
    /**
     * 是否启用 LLM 历史摘要 / Enable LLM-driven history summarization.
     *
     * <p>关闭时 ContextCompactionPolicy 走 BulletSummarizer 兜底（纯字符串拼接）；
     * 开启时调主路由模型做摘要，失败自动回退到 bullet（永不阻塞主对话）.
     */
    private static boolean summarizerEnabled = true;
    /**
     * Summarizer 单次输入字符上限 / Max input chars fed to LLM summarizer.
     *
     * <p>防止「老历史本身已超长」让 summarizer 自己也爆 token；超过此长度的部分会被预裁.
     */
    private static int summarizerMaxInputChars = 24000;
    /**
     * Summarizer 专用模型 ID / Dedicated model id for summarizer.
     *
     * <p>留空则沿用主路由的 primary 模型；配置后 LlmSummarizer 会按此 modelId 查
     * {@code AI_MODEL_PROFILE}，单独路由（适合用便宜小模型，比如 Haiku / DeepSeek-V3）.
     * 查不到则退回主路由 —— fail-open，永不阻塞主对话.
     */
    private static String summarizerModelId = "deepseek-chat";
    /** 单轮 LLM 流式空闲超时（毫秒）/ Per-turn LLM stream idle-timeout ms. */
    private static long llmTurnTimeoutMs = 120_000L;
    /** 单用户 orgId 维度的并发 run 上限 / Per-user concurrent-run limit. */
    private static int perUserConcurrentRunLimit = 3;
    /** 单 thread 维度的并发 run 上限 / Per-thread concurrent-run limit. */
    private static int perThreadConcurrentRunLimit = 1;

    // ===== Memory =====
    /** 单条 memory 正文最大长度 / Max content length of one memory. */
    private static int memoryMaxContentLen = 500;
    /** 单用户每分钟写入上限 / Per-user write rate limit (per minute). */
    private static int memoryWriteLimitPerMin = 20;

    // ===== Tool =====
    /** 单用户工具默认每分钟调用上限 / Per-user tool call rate limit (per minute). */
    private static int toolRateLimitPerMin = 60;

    // ===== Thread / List paging =====
    /** 列表接口默认每页条数 / Default page size for list endpoints. */
    private static int listDefaultPageSize = 20;
    /** 列表接口最大每页条数 / Max page size for list endpoints. */
    private static int listMaxPageSize = 200;

    // ===== Admin =====
    /** 管理员角色名 / Admin role name recognized by UserContext.hasRole. */
    private static String adminRoleName = "ai_admin";
    /** 模型 apiKey 回显时保留的明文尾部字符数 / ApiKey masking tail length. */
    private static int apiKeyMaskTailLen = 4;
    /** API Center console base URL used by Lobster admin links. */
    private static String apiCenterConsoleBaseUrl = "";

    // ===== MCP =====
    /** Enable background remote listTools refresh for enabled MCP servers. */
    private static boolean mcpToolAutoRefreshEnabled = true;
    /** Remote listTools refresh interval / TTL in ms. Default 10 minutes. */
    private static long mcpToolListTtlMs = 600_000L;
    /** Runtime local DB snapshot cache TTL in ms. Default 10 minutes. */
    private static long mcpRuntimeToolCacheTtlMs = 600_000L;

    // ===== OA Knowledge Base (KB API v1) =====
    /**
     * KB 服务的 endpoint base / Base URL of the OA knowledge-base service.
     *
     * <p>{@link com.gzzm.lobster.oa.OaKnowledgeClientHttpImpl} 在此 base 之上拼 {@code /search}、
     * {@code /detail}、{@code /scopes}。空串表示未配置——HTTP impl 调用时直接抛 {@code kb.not_configured}，
     * 避免静默成功. 想用 stub 走本地，请在 nest.xml 切回 {@code OaKnowledgeClientStub} 实现.
     *
     * <p>例如：{@code http://oa.intranet.example.com/api/kb}
     */
    private static String oaKnowledgeBaseUrl = "";
    /**
     * KB 入口鉴权 apiKey / Optional bearer token for KB endpoint.
     *
     * <p>非空时 HTTP impl 会带 {@code Authorization: Bearer <key>}；用户身份仍走
     * {@code X-User-Id / X-Dept-Id / X-Org-Id} header 透传到 KB 侧做权限裁决.
     */
    private static String oaKnowledgeApiKey = "";
    /**
     * 「强制引用」模式下注入的最大 KB 条目数 / Max KB hits prepended in FORCED mode.
     *
     * <p>对话开启「强制引用」时 ContextAssembler 会预先 search 一次，把 top N 命中作为
     * prelude 拼到 user 输入前；本字段控制 N（防止超长 system 段炸 token）.
     */
    private static int kbForcedInjectMaxHits = 10;

    // ===== LLM Trace =====
    /** 是否把每次 LLM 调用的完整 prompt+response 落盘为 trace / Toggle full LLM I/O tracing.
     *  开启后通过 ContentStore 写入，ref 存到 ModelCallLog.traceRef，可经
     *  GET /ai/api/admin/calls/{callId}/trace 回查。*/
    private static boolean llmTraceEnabled = true;
    /** 单条消息超过此长度就截断后 N 字 / Truncate over-long single message. {@code <=0} 表示不截断. */
    private static int llmTraceMaxMessageChars = 0;

    // ===== Storage =====
    /**
     * ContentStore 根目录 base / Root base of ContentStore.
     *
     * <p>最终落盘路径为 {@code <contentStoreBase>/files/lobster-content/<category>/...}。
     * 默认值按 OS 区分：Linux/Unix → {@code /home/app/lobster}，Windows → {@code d:/lobster}。
     * 可在 lobster.xml 里用 {@code <contentStoreBase>} 覆盖。
     */
    private static String contentStoreBase = defaultContentStoreBase();

    private static String defaultContentStoreBase() {
        String os = System.getProperty("os.name", "").toLowerCase();
        return os.contains("win") ? "d:/lobster" : "/home/app/lobster";
    }

    // ===== Upload =====
    /** 单次上传最大字节数 / Max upload size in bytes. 默认 50 MB. */
    private static long uploadMaxBytes = 50L * 1024 * 1024;
    /** 对话框上传的纯文本直接内联到当前 turn 的阈值（字符）/
     *  Text upload inline-into-current-turn threshold (chars). 超过走 `read_file` 按需读取. */
    private static int uploadInlineMaxChars = 4000;
    /** 解析后 markdown 正文的字符上限 / Hard cap for parsed markdown body chars. 超过截断并提示用 sectionId 导航. */
    private static int parsedMarkdownMaxChars = 80_000;
    /** xlsx 单 sheet 正文渲染的行数上限 / Max rows rendered into the markdown body per xlsx sheet.
     *  超出部分由独立 `read_sheet` 工具按 rowRange 按需读取. */
    private static int xlsxInlineRowLimit = 500;
    /** outline sections 超过此阈值按 level 1 聚合折叠 / Fold outline sections above this count. */
    private static int outlineFoldThreshold = 500;
    /** PDF 书签嵌套深度上限；超出扁平化到此层 / Max PDF bookmark nesting depth; deeper levels flattened. */
    private static int pdfBookmarkMaxDepth = 3;

    // ===== Sandbox (code_exec) =====
    /**
     * 沙箱镜像名（含 tag / digest）.
     * <p>与 {@code deploy/sandbox/build.sh} 产出的 tag 保持一致；生产升级镜像时两边同步改.
     */
    private static String sandboxImage = "lobster-sandbox:py3.11-office-v4";
    /** 沙箱默认脚本超时（秒） */
    private static int sandboxDefaultTimeoutSec = 30;
    /** 沙箱脚本超时上限（秒） */
    private static int sandboxMaxTimeoutSec = 120;
    /** 内存上限（MB） */
    private static int sandboxMemoryMb = 512;
    /** CPU 核数上限 */
    private static double sandboxCpus = 0.5;
    /** pids 上限 */
    private static int sandboxPidsLimit = 128;
    /** 产物目录单次上限字节数 */
    private static long sandboxOutputMaxBytes = 50L * 1024 * 1024;
    /** 代码字节数上限（LLM tool_call 里 `code` 字段 UTF-8 字节数）
     *  默认 64KB：20KB 太紧，稍复杂的 docx/pptx 生成脚本、含 shadcn 组件的 HTML artifact 生成脚本
     *  常接近甚至超过. 主流 LLM tool_call 单参数支持到 100KB+，放宽到 64KB 覆盖绝大多数场景. */
    private static int sandboxCodeMaxBytes = 64 * 1024;
    /** user/min 限流；code_exec 专用 */
    private static int sandboxRatePerMinute = 10;
    /** host 临时工作目录 / Host staging root for sandbox runs. */
    private static String sandboxWorkDir = defaultSandboxWorkDir();
    /** Enable fixed-slot prewarmed sandbox containers. Disabled by default. */
    private static boolean sandboxPoolEnabled = false;
    /** Number of fixed sandbox pool slots. */
    private static int sandboxPoolSize = 4;
    /** Host root for fixed sandbox pool slots. */
    private static String sandboxPoolRoot = defaultSandboxPoolRoot();
    /** skill bundle 缓存根目录 / Bundle cache root. */
    private static String sandboxBundleCacheDir = defaultBundleCacheDir();
    /** 沙箱容器使用的非 root UID */
    private static int sandboxUid = 10001;
    /** docker 可执行路径 */
    private static String sandboxDockerBin = "docker";

    /**
     * 系统 skill：沙箱类扫描根目录 / System sandbox-skill root.
     * 该目录下每个含 SKILL.md 的子目录注册为沙箱资源类系统 skill；
     * scripts/、字体、模板等都是可选 bundle 资源。目录本身即"已解压 bundle"，
     * 通过 fs:// 协议绑定到 SkillDefinition.assetBundleRef.
     *
     * <p>默认路径 {@code WEB-INF/lobster/deploy/skills} —— SystemSkillLoader 按
     * webappRoot / catalina.base / user.dir 三级解析.
     *
     * <p>约定目录结构（本仓库）：
     * <pre>
     *   web/WEB-INF/lobster/
     *   ├── deploy/
     *   │   ├── sandbox/       ← Dockerfile + build.sh/ps1/cmd；构建镜像用，不是系统 skill
     *   │   └── skills/        ← ★ 沙箱类系统 skill（docx/xlsx/pptx/pdf/web-artifacts-builder）
     *   └── skills/            ← ★ guidance 类系统 skill（doc-coauthoring/frontend-design）
     * </pre>
     *
     * <p>Dockerfile build context 就是 {@code web/WEB-INF/lobster/deploy/}（含 sandbox/ 与 skills/），
     * 运行期 Tomcat 和构建期 docker 走同一份文件，无需双份维护.
     */
    private static String systemSandboxSkillDir = "WEB-INF/lobster/deploy/skills";
    /**
     * 系统 skill：guidance 类扫描根目录 / System guidance-only-skill root.
     * 该目录下每个含 SKILL.md 的子目录注册为纯 guidance skill（不进沙箱）；
     * SKILL.md 内容直接作为 guidance 落库.
     *
     * <p>源码仓库里位于 {@code web/WEB-INF/lobster/skills/}.
     */
    private static String systemGuidanceSkillDir = "WEB-INF/lobster/skills";
    /**
     * 沙箱镜像预装包白名单文件 / Installed-packages manifest path.
     * 由 {@code deploy/sandbox/build.sh} 导出，供 {@code AdminSkillBundleApi}
     * 校验用户上传 bundle 的 {@code pythonPackages} 是否 ⊆ 镜像预装集.
     * 绝对路径或按 {@code catalina.base}/{@code user.dir} 解析.
     */
    private static String sandboxInstalledPackagesFile = "WEB-INF/lobster/sandbox-installed-packages.json";
    /**
     * 白名单文件缺失时的策略 / Policy when installed-packages manifest missing.
     * <ul>
     *   <li>{@code true}（生产推荐）—— fail-closed：bundle 中声明了 pythonPackages
     *       但找不到白名单文件 → 整体拒绝上传</li>
     *   <li>{@code false}（开发期）—— fail-open：跳过 pip 校验，bundle 照常接受.
     *       只有在 lobster.xml 中显式打开 dev 模式时才允许</li>
     * </ul>
     */
    private static boolean sandboxRequireInstalledPackagesFile = true;

    // ===== Sandbox input staging 上限 =====
    /** 单个 input_ref 最大字节数 */
    private static long sandboxInputMaxBytes = 20L * 1024 * 1024;
    /** 一次 code_exec input_refs 总字节数上限 */
    private static long sandboxInputTotalMaxBytes = 100L * 1024 * 1024;
    /** input_refs 条目数上限 */
    private static int sandboxInputMaxFiles = 20;
    /**
     * Bundle 解压缓存条目数上限 / Bundle cache entry cap.
     * 超过触发 LRU 淘汰（按 last-access 时间）. 系统 skill（fs:// 前缀）不计入上限.
     */
    private static int sandboxBundleCacheMaxEntries = 64;
    /**
     * Bundle 解压缓存总大小上限字节数 / Bundle cache total-size cap.
     * 超过也触发 LRU 淘汰.
     */
    private static long sandboxBundleCacheMaxBytes = 2L * 1024 * 1024 * 1024;

    private static String defaultSandboxWorkDir() {
        String os = System.getProperty("os.name", "").toLowerCase();
        return os.contains("win") ? "d:/lobster/sandbox" : "/srv/sandbox";
    }

    private static String defaultSandboxPoolRoot() {
        String os = System.getProperty("os.name", "").toLowerCase();
        return os.contains("win") ? "d:/lobster/sandbox/pool" : "/srv/sandbox/pool";
    }

    private static String defaultBundleCacheDir() {
        String os = System.getProperty("os.name", "").toLowerCase();
        return os.contains("win") ? "d:/lobster/skill-bundles" : "/var/cache/lobster/skill-bundles";
    }

    public LobsterConfig() {}

    // ----- Readers -----
    public static int getMaxTurnsPerRun() { return maxTurnsPerRun; }
    public static int getDefaultContextBudgetTokens() { return defaultContextBudgetTokens; }
    public static boolean isSummarizerEnabled() { return summarizerEnabled; }
    public static int getSummarizerMaxInputChars() { return summarizerMaxInputChars; }
    public static String getSummarizerModelId() { return summarizerModelId; }
    public static long getLlmTurnTimeoutMs() { return llmTurnTimeoutMs; }
    public static int getPerUserConcurrentRunLimit() { return perUserConcurrentRunLimit; }
    public static int getPerThreadConcurrentRunLimit() { return perThreadConcurrentRunLimit; }
    public static int getMemoryMaxContentLen() { return memoryMaxContentLen; }
    public static int getMemoryWriteLimitPerMin() { return memoryWriteLimitPerMin; }
    public static int getToolRateLimitPerMin() { return toolRateLimitPerMin; }
    public static int getListDefaultPageSize() { return listDefaultPageSize; }
    public static int getListMaxPageSize() { return listMaxPageSize; }
    public static String getAdminRoleName() { return adminRoleName; }
    public static int getApiKeyMaskTailLen() { return apiKeyMaskTailLen; }
    public static String getApiCenterConsoleBaseUrl() { return apiCenterConsoleBaseUrl; }
    public static boolean isMcpToolAutoRefreshEnabled() { return mcpToolAutoRefreshEnabled; }
    public static long getMcpToolListTtlMs() { return mcpToolListTtlMs; }
    public static long getMcpRuntimeToolCacheTtlMs() { return mcpRuntimeToolCacheTtlMs; }
    public static boolean isLlmTraceEnabled() { return llmTraceEnabled; }
    public static int getLlmTraceMaxMessageChars() { return llmTraceMaxMessageChars; }
    public static String getOaKnowledgeBaseUrl() { return oaKnowledgeBaseUrl; }
    public static String getOaKnowledgeApiKey() { return oaKnowledgeApiKey; }
    public static int getKbForcedInjectMaxHits() { return kbForcedInjectMaxHits; }
    public static String getContentStoreBase() { return contentStoreBase; }
    public static long getUploadMaxBytes() { return uploadMaxBytes; }
    public static int getUploadInlineMaxChars() { return uploadInlineMaxChars; }
    public static int getParsedMarkdownMaxChars() { return parsedMarkdownMaxChars; }
    public static int getXlsxInlineRowLimit() { return xlsxInlineRowLimit; }
    public static int getOutlineFoldThreshold() { return outlineFoldThreshold; }
    public static int getPdfBookmarkMaxDepth() { return pdfBookmarkMaxDepth; }
    public static String getSandboxImage() { return sandboxImage; }
    public static int getSandboxDefaultTimeoutSec() { return sandboxDefaultTimeoutSec; }
    public static int getSandboxMaxTimeoutSec() { return sandboxMaxTimeoutSec; }
    public static int getSandboxMemoryMb() { return sandboxMemoryMb; }
    public static double getSandboxCpus() { return sandboxCpus; }
    public static int getSandboxPidsLimit() { return sandboxPidsLimit; }
    public static long getSandboxOutputMaxBytes() { return sandboxOutputMaxBytes; }
    public static int getSandboxCodeMaxBytes() { return sandboxCodeMaxBytes; }
    public static int getSandboxRatePerMinute() { return sandboxRatePerMinute; }
    public static String getSandboxWorkDir() { return sandboxWorkDir; }
    public static boolean isSandboxPoolEnabled() { return sandboxPoolEnabled; }
    public static int getSandboxPoolSize() { return sandboxPoolSize; }
    public static String getSandboxPoolRoot() { return sandboxPoolRoot; }
    public static String getSandboxBundleCacheDir() { return sandboxBundleCacheDir; }
    public static int getSandboxUid() { return sandboxUid; }
    public static String getSandboxDockerBin() { return sandboxDockerBin; }
    public static String getSystemSandboxSkillDir() { return systemSandboxSkillDir; }
    public static String getSystemGuidanceSkillDir() { return systemGuidanceSkillDir; }
    public static String getSandboxInstalledPackagesFile() { return sandboxInstalledPackagesFile; }
    public static boolean isSandboxRequireInstalledPackagesFile() { return sandboxRequireInstalledPackagesFile; }
    public static long getSandboxInputMaxBytes() { return sandboxInputMaxBytes; }
    public static long getSandboxInputTotalMaxBytes() { return sandboxInputTotalMaxBytes; }
    public static int getSandboxInputMaxFiles() { return sandboxInputMaxFiles; }
    public static int getSandboxBundleCacheMaxEntries() { return sandboxBundleCacheMaxEntries; }
    public static long getSandboxBundleCacheMaxBytes() { return sandboxBundleCacheMaxBytes; }

    // ----- JavaBean setters (framework hydrates from lobster.xml) -----
    public void setMaxTurnsPerRun(int v) { maxTurnsPerRun = v; }
    public void setDefaultContextBudgetTokens(int v) { defaultContextBudgetTokens = v; }
    public void setSummarizerEnabled(boolean v) { summarizerEnabled = v; }
    public void setSummarizerMaxInputChars(int v) { if (v > 0) summarizerMaxInputChars = v; }
    public void setSummarizerModelId(String v) { summarizerModelId = v == null ? "" : v.trim(); }
    public void setLlmTurnTimeoutMs(long v) { llmTurnTimeoutMs = v; }
    public void setPerUserConcurrentRunLimit(int v) { perUserConcurrentRunLimit = v; }
    public void setPerThreadConcurrentRunLimit(int v) { perThreadConcurrentRunLimit = v; }
    public void setMemoryMaxContentLen(int v) { memoryMaxContentLen = v; }
    public void setMemoryWriteLimitPerMin(int v) { memoryWriteLimitPerMin = v; }
    public void setToolRateLimitPerMin(int v) { toolRateLimitPerMin = v; }
    public void setListDefaultPageSize(int v) { listDefaultPageSize = v; }
    public void setListMaxPageSize(int v) { listMaxPageSize = v; }
    public void setAdminRoleName(String v) { adminRoleName = v; }
    public void setApiKeyMaskTailLen(int v) { apiKeyMaskTailLen = v; }
    public void setApiCenterConsoleBaseUrl(String v) { apiCenterConsoleBaseUrl = v == null ? "" : v.trim(); }
    public void setMcpToolAutoRefreshEnabled(boolean v) { mcpToolAutoRefreshEnabled = v; }
    public void setMcpToolListTtlMs(long v) { if (v > 0) mcpToolListTtlMs = v; }
    public void setMcpRuntimeToolCacheTtlMs(long v) { if (v > 0) mcpRuntimeToolCacheTtlMs = v; }
    public void setLlmTraceEnabled(boolean v) { llmTraceEnabled = v; }
    public void setLlmTraceMaxMessageChars(int v) { llmTraceMaxMessageChars = v; }
    public void setOaKnowledgeBaseUrl(String v) { oaKnowledgeBaseUrl = v == null ? "" : v.trim(); }
    public void setOaKnowledgeApiKey(String v) { oaKnowledgeApiKey = v == null ? "" : v.trim(); }
    public void setKbForcedInjectMaxHits(int v) { if (v > 0) kbForcedInjectMaxHits = v; }
    public void setContentStoreBase(String v) { if (v != null && !v.isEmpty()) contentStoreBase = v; }
    public void setUploadMaxBytes(long v) { uploadMaxBytes = v; }
    public void setUploadInlineMaxChars(int v) { uploadInlineMaxChars = v; }
    public void setParsedMarkdownMaxChars(int v) { parsedMarkdownMaxChars = v; }
    public void setXlsxInlineRowLimit(int v) { xlsxInlineRowLimit = v; }
    public void setOutlineFoldThreshold(int v) { outlineFoldThreshold = v; }
    public void setPdfBookmarkMaxDepth(int v) { pdfBookmarkMaxDepth = v; }
    public void setSandboxImage(String v) { if (v != null && !v.isEmpty()) sandboxImage = v; }
    public void setSandboxDefaultTimeoutSec(int v) { if (v > 0) sandboxDefaultTimeoutSec = v; }
    public void setSandboxMaxTimeoutSec(int v) { if (v > 0) sandboxMaxTimeoutSec = v; }
    public void setSandboxMemoryMb(int v) { if (v > 0) sandboxMemoryMb = v; }
    public void setSandboxCpus(double v) { if (v > 0) sandboxCpus = v; }
    public void setSandboxPidsLimit(int v) { if (v > 0) sandboxPidsLimit = v; }
    public void setSandboxOutputMaxBytes(long v) { if (v > 0) sandboxOutputMaxBytes = v; }
    public void setSandboxCodeMaxBytes(int v) { if (v > 0) sandboxCodeMaxBytes = v; }
    public void setSandboxRatePerMinute(int v) { if (v > 0) sandboxRatePerMinute = v; }
    public void setSandboxWorkDir(String v) { if (v != null && !v.isEmpty()) sandboxWorkDir = v; }
    public void setSandboxPoolEnabled(boolean v) { sandboxPoolEnabled = v; }
    public void setSandboxPoolSize(int v) { if (v > 0) sandboxPoolSize = v; }
    public void setSandboxPoolRoot(String v) { if (v != null && !v.isEmpty()) sandboxPoolRoot = v; }
    public void setSandboxBundleCacheDir(String v) { if (v != null && !v.isEmpty()) sandboxBundleCacheDir = v; }
    public void setSandboxUid(int v) { if (v > 0) sandboxUid = v; }
    public void setSandboxDockerBin(String v) { if (v != null && !v.isEmpty()) sandboxDockerBin = v; }
    public void setSystemSandboxSkillDir(String v) { if (v != null && !v.isEmpty()) systemSandboxSkillDir = v; }
    public void setSystemGuidanceSkillDir(String v) { if (v != null && !v.isEmpty()) systemGuidanceSkillDir = v; }
    public void setSandboxInstalledPackagesFile(String v) { if (v != null && !v.isEmpty()) sandboxInstalledPackagesFile = v; }
    public void setSandboxRequireInstalledPackagesFile(boolean v) { sandboxRequireInstalledPackagesFile = v; }
    public void setSandboxInputMaxBytes(long v) { if (v > 0) sandboxInputMaxBytes = v; }
    public void setSandboxInputTotalMaxBytes(long v) { if (v > 0) sandboxInputTotalMaxBytes = v; }
    public void setSandboxInputMaxFiles(int v) { if (v > 0) sandboxInputMaxFiles = v; }
    public void setSandboxBundleCacheMaxEntries(int v) { if (v > 0) sandboxBundleCacheMaxEntries = v; }
    public void setSandboxBundleCacheMaxBytes(long v) { if (v > 0) sandboxBundleCacheMaxBytes = v; }
}
