CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent), MCP server 接入外部 Agent 工具,GUI 开发中。
353 lines
14 KiB
Rust
353 lines
14 KiB
Rust
//! `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 系统目录);
|
||
// 设置了 → 覆盖前两者,按 `<vendor>/<version>` 分层。
|
||
.with(SettingSpec::text(
|
||
"java.installRoot",
|
||
JAVA_INSTALL_ROOT_UNSET,
|
||
512,
|
||
AiPolicy::sensitive(),
|
||
"托管 JDK 的自定义安装根(留空 = 按数据目录模式:portable 进数据根、standard 进 %APPDATA%\\Java);设置后覆盖两者,JDK 落在 <root>/<发行方>/<版本>/",
|
||
))
|
||
// 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(),
|
||
"启动器日志级别",
|
||
))
|
||
}
|