//! `config/launcher.json` 的键表(全局设置)。 //! //! 键名与默认值对齐 `DevDocs/PlanDoc/04-data-and-config.md` §2-§3 与 `05/06/07/08` 各专题; //! AI 可写 + 敏感分级按 `02-ai-agent-design.md` §3.1 的边界落:**密钥类一律 locked**。 use super::catalog::SettingSchema; use super::schema::{SchemaId, SchemaVersion}; use super::spec::{AiPolicy, SettingSpec}; /// `core.data_mode` 允许值。 pub const DATA_MODES: &[&str] = &["portable", "standard", "custom"]; /// `download.region` 允许值。 pub const REGIONS: &[&str] = &["auto", "cn", "global"]; /// `download.mirror_preference` 允许值。 pub const MIRROR_PREFERENCES: &[&str] = &["auto", "official", "mirror"]; /// `download.proxy_mode` 允许值。 pub const PROXY_MODES: &[&str] = &["none", "system", "manual"]; /// `ai.provider` 允许值。 pub const AI_PROVIDERS: &[&str] = &["openai_compatible", "anthropic", "ollama", "disabled"]; /// `ui.language` 允许值。 pub const LANGUAGES: &[&str] = &["zh_cn", "en_us"]; /// `appearance.theme_mode` 允许值(D17 定案:auto/light/dark)。 pub const THEME_MODES: &[&str] = &["auto", "light", "dark"]; /// `ui.theme` 的旧允许值(该键已废弃,仅读兼容,不再落盘)。 pub const LEGACY_THEMES: &[&str] = &["system", "metro_light", "metro_dark"]; /// 旧 `ui.theme` 值 → 新 `appearance.theme_mode` 值(tower 键冲突裁决的读兼容映射)。 pub const THEME_ALIASES: &[(&str, &str)] = &[ ("system", "auto"), ("metro_light", "light"), ("metro_dark", "dark"), ]; /// `log.level` 允许值。 pub const LOG_LEVELS: &[&str] = &["error", "warn", "info", "debug", "trace"]; /// `instances.mode` 允许值(D20 定案:默认隔离,用户可手动切共享)。 pub const INSTANCE_MODES: &[&str] = &["isolated", "shared"]; /// `java.installRoot` 未设置时的哨兵(空串 = 按数据目录模式派生落点)。 pub const JAVA_INSTALL_ROOT_UNSET: &str = ""; /// `java.vendorPriority` 的允许值(D23:四家发行版,顺序即优先级)。 /// /// 与 `crate::game::java::vendor::VENDOR_IDS` 必须是同一份集合——setting 层不依赖 game 层 /// (分层纪律),一致性由 `tests/setting/java_install_keys.rs` 的契约测试固定。 pub const JDK_VENDORS: &[&str] = &["temurin", "oracle", "microsoft", "zulu"]; /// launcher.json 当前 schema 版本。 /// /// `1.1.0` 登记 D20 实例隔离键(`instances.mode` 等)。 /// `1.2.0` 登记 D23 的 JDK 落点与发行版优先级键(`java.installRoot` / `java.vendorPriority`): /// 同样只推 minor、不强制迁移——[`super::document`] 的「默认值不落盘」让老文件缺键即取默认, /// 迁移链(按 major 走)不需要新步骤;`1.1.0 → 1.2.0` 的差异只体现在文件里出现新键之后。 /// `1.3.0` 登记 D17 的「外观(高级)」P1 键(`appearance.bg_image*` / `panel_alpha` / `glass` / /// `accent_color`):同一套纯增量语义——出厂全默认(高级特性全关)即等于老文件缺键。 #[must_use] pub fn launcher_version() -> SchemaVersion { SchemaVersion::new(1, 3, 0) } /// `config/launcher.json` 的 schema。 #[must_use] pub fn launcher_schema() -> SettingSchema { SettingSchema::new(SchemaId::LAUNCHER, launcher_version()) .with(SettingSpec::enumeration( "core.data_mode", "portable", DATA_MODES, AiPolicy::sensitive(), "数据目录模式:portable(exe 旁)/ standard(系统标准目录)/ custom(自定义)", )) .with(SettingSpec::path( "core.data_custom_root", AiPolicy::sensitive(), "custom 模式的根目录(必须是绝对路径)", )) .with(SettingSpec::path( "core.launcher_home", AiPolicy::sensitive(), "显式覆盖启动器数据目录(.ULL 的等价物)", )) .with(SettingSpec::path( "core.game_home", AiPolicy::sensitive(), "游戏目录(.minecraft),与数据目录分离指定", )) // D20(research/04 §5.1):只留一个全局默认档,实例级 override 在 // `instance_keys::instance_schema`(`isolation`),派生逻辑在 `game::isolation`。 .with(SettingSpec::enumeration( "instances.mode", "isolated", INSTANCE_MODES, AiPolicy::sensitive(), "实例默认隔离模式:isolated(运行目录派生 versions/<版本 id>,D20 默认)/ shared(所有实例共用 .minecraft 根)", )) .with(SettingSpec::int( "download.concurrency", 8, (1, 256), AiPolicy::silent(), "下载并发数", )) .with(SettingSpec::int( "download.retry_limit", 3, (0, 10), AiPolicy::silent(), "单个文件重试次数", )) .with(SettingSpec::enumeration( "download.region", "auto", REGIONS, AiPolicy::audited(), "下载地区判定:auto(时区+语言+探测)/ cn / global", )) .with(SettingSpec::enumeration( "download.mirror_preference", "auto", MIRROR_PREFERENCES, AiPolicy::audited(), "镜像偏好:auto(健康度评分)/ official(官方源优先)/ mirror(镜像优先)", )) .with(SettingSpec::enumeration( "download.proxy_mode", "none", PROXY_MODES, AiPolicy::sensitive(), "代理模式:none / system / manual", )) .with(SettingSpec::text( "download.proxy_url", "", 512, AiPolicy::sensitive(), "manual 代理地址(如 http://127.0.0.1:7890)", )) .with(SettingSpec::path( "java.selected_path", AiPolicy::sensitive(), "用户选定的 Java 可执行文件路径", )) .with(SettingSpec::bool( "java.auto_install", true, AiPolicy::audited(), "缺 Java 时自动下载(D13 基准 JDK17)", )) // D23:托管 JDK 的落点三模式。语义在 `game::java::location`: // 未设置(空串)→ 按 `core.data_mode` 派生(Portable 数据根 / Standard 系统目录); // 设置了 → 覆盖前两者,按 `/` 分层。 .with(SettingSpec::text( "java.installRoot", JAVA_INSTALL_ROOT_UNSET, 512, AiPolicy::sensitive(), "托管 JDK 的自定义安装根(留空 = 按数据目录模式:portable 进数据根、standard 进 %APPDATA%\\Java);设置后覆盖两者,JDK 落在 /<发行方>/<版本>/", )) // D23:发行版回退链的用户优先级。空数组 = 按地区默认序 // (国内 Temurin→Oracle→Microsoft→Zulu;国外 Temurin→Microsoft→Oracle→Zulu)。 .with(SettingSpec::enum_list( "java.vendorPriority", JDK_VENDORS, AiPolicy::audited(), "JDK 发行版优先级(temurin/oracle/microsoft/zulu);留空按地区默认序,只写几家则其余按默认序补齐", )) .with(SettingSpec::int( "java.default_memory_mb", 2048, (512, 65536), AiPolicy::silent(), "默认分配给游戏的最大堆内存(MB)", )) .with(SettingSpec::text( "java.extra_args", "", 1024, AiPolicy::sensitive(), "附加 JVM 参数(原样拼到命令行)", )) .with(SettingSpec::bool( "ai.enabled", true, AiPolicy::silent(), "启用内置 AI agent(关闭后规则引擎诊断仍可用)", )) .with(SettingSpec::enumeration( "ai.provider", "openai_compatible", AI_PROVIDERS, AiPolicy::sensitive(), "LLM 提供方:openai_compatible(含本地 llama.cpp/DeepSeek/Qwen)/ anthropic / ollama / disabled", )) .with(SettingSpec::text( "ai.base_url", "", 512, AiPolicy::sensitive(), "LLM 端点 base_url(OpenAI 兼容形态)", )) .with(SettingSpec::text( "ai.model", "", 128, AiPolicy::audited(), "模型名(如 gpt-4o-mini / deepseek-chat)", )) .with(SettingSpec::bool( "ai.yolo", false, AiPolicy::sensitive(), "自动批准全部工具调用(审计照记,白名单不变)", )) .with(SettingSpec::new( "ai.api_key", super::spec::ValueKind::Text { max_len: 4096 }, serde_json::Value::Null, AiPolicy::locked(), "LLM API 密钥——AI 永不读写的凭据(D9)", )) // M39(P3 MCP server,02 §0「传输定论」):对外 agent 工具服务。 // 三个开关默认「关 / 只读」——外部客户端拿到的是与内置客户端**同一注册表**的工具, // 但写权限必须用户显式打开(02 §1「同一注册表、同一安全策略、同一审计日志」)。 .with(SettingSpec::bool( "agent.mcp.enabled", false, AiPolicy::sensitive(), "对外 MCP server 总开关(127.0.0.1 上的 Streamable HTTP;stdio 形态按需显式启动)", )) .with(SettingSpec::int( "agent.mcp.port", 47823, (1024, 65535), AiPolicy::sensitive(), "MCP server 的 Streamable HTTP 端口(只绑 127.0.0.1)", )) .with(SettingSpec::bool( "agent.mcp.allow_write", false, AiPolicy::sensitive(), "允许外部 MCP 客户端调用写工具(默认只读;敏感键仍受分级确认与 YOLO 规则约束)", )) .with(SettingSpec::text( "agent.mcp.token", "", 256, AiPolicy::locked(), "MCP server 本地访问令牌(Authorization: Bearer;空 = 不校验,仅绑 127.0.0.1)", )) .with(SettingSpec::enumeration( "ui.language", "zh_cn", LANGUAGES, AiPolicy::audited(), "界面语言", )) .with(SettingSpec::enumeration( "appearance.theme_mode", "auto", THEME_MODES, AiPolicy::silent(), "主题模式:auto(跟随系统)/ light(Metro 浅色)/ dark(Metro 深色)", )) // D17「外观(高级)」P1 四件(09 §101-104 / §111):**默认全部关闭**(出厂 Metro 纯色)。 // 分级一律 audited(Medium):全部是纯视觉、可一键复原的键,没有安全面(不碰凭据/网络/ // 数据根),谈不上 High 级确认;但 AI 改过什么要留审计痕迹,故不用 silent。 .with(SettingSpec::bool( "appearance.bg_image_enabled", false, AiPolicy::audited(), "自定义窗口背景图开关(默认关;开启时设置页给出对比度提示)", )) .with(SettingSpec::text( "appearance.bg_image", "", 512, AiPolicy::audited(), "背景图路径(PNG;空 = 未设置。Slint 默认解码器只带 PNG)", )) .with(SettingSpec::enumeration( "appearance.bg_fill", "cover", super::appearance::BG_FILLS, AiPolicy::audited(), "背景图填充模式:cover(铺满裁切)/ contain(完整留底色)/ tile(平铺)", )) .with(SettingSpec::float( "appearance.bg_dim", 0.35, ( super::appearance::BG_DIM_MIN, super::appearance::BG_DIM_MAX, ), AiPolicy::audited(), "背景图叠加暗化层强度(越大前景越清晰;09 §101 的对比度保底手法)", )) .with(SettingSpec::float( "appearance.panel_alpha", 1.0, (super::appearance::PANEL_ALPHA_MIN, 1.0), AiPolicy::audited(), "全局面板半透明度(卡片/导航/AI 栏统一透明度;1.0 = 出厂不透明)", )) .with(SettingSpec::bool( "appearance.glass", false, AiPolicy::audited(), "玻璃拟态卡片(背景图预生成模糊层作卡片底;Slint 无 backdrop-filter 的替代)", )) .with(SettingSpec::text( "appearance.accent_color", "", 9, AiPolicy::audited(), "自定义强调色 #RRGGBB(空 = 出厂 #0078D4;accent 令牌全域生效)", )) // 已废弃:D17 定案 appearance.theme_mode 为准(旧值读兼容映射,写永远走新键)。 .with( SettingSpec::new( "ui.theme", super::spec::ValueKind::Enum(LEGACY_THEMES), serde_json::Value::Null, AiPolicy::retired(), "【已废弃】改用 appearance.theme_mode;旧值读兼容:system→auto / metro_light→light / metro_dark→dark", ) .deprecated("appearance.theme_mode", THEME_ALIASES), ) .with(SettingSpec::float( "ui.scale", 1.0, (0.75, 2.0), AiPolicy::silent(), "界面缩放系数", )) .with(SettingSpec::int( "ui.window.width", 1280, (320, 16384), AiPolicy::silent(), "主窗口宽度(逻辑像素)", )) .with(SettingSpec::int( "ui.window.height", 800, (200, 16384), AiPolicy::silent(), "主窗口高度(逻辑像素)", )) .with(SettingSpec::enumeration( "log.level", "info", LOG_LEVELS, AiPolicy::audited(), "启动器日志级别", )) }