From 7c62a1c148b7eafad7c4ceb321fba61eca2c72f8 Mon Sep 17 00:00:00 2001 From: lanrtop Date: Wed, 23 Sep 2026 03:38:12 +0900 Subject: [PATCH] =?UTF-8?q?test(agent/access):=20=E5=9B=BA=E5=8C=96?= =?UTF-8?q?=E8=87=AA=E5=90=AF=E5=8A=A8=E5=B9=82=E7=AD=89=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rules-Applied: R01,R06,R07,R08,R10,R12,R13 --- .../modules/agent-access-reliability.md | 4 +- docs/ai-context/agent-button-system.md | 1 + src-tauri/src/commands/settings.rs | 65 ++++++++++++++++--- 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/.blueprint/modules/agent-access-reliability.md b/.blueprint/modules/agent-access-reliability.md index 9848bea..16e3e22 100644 --- a/.blueprint/modules/agent-access-reliability.md +++ b/.blueprint/modules/agent-access-reliability.md @@ -126,8 +126,8 @@ Codex / Claude 项目 - depends: A2. 分层 Agent runtime readiness 本体 - files: src-tauri/Cargo.toml, src-tauri/Cargo.lock, src-tauri/src/lib.rs, src-tauri/src/commands/settings.rs - acceptance: "[Tauri command] set_agent_autostart(enabled) → 默认 false,可启用、查询并撤销;重复调用幂等;不启动第二个炼境实例,不修改项目文件或 pnpm-lock.yaml。" -- decision_note: "用户于 2026-09-23 明确恢复 A6,但限定正式安装版:默认关闭,debug/target 构建拒绝启用,查询/启停幂等,复用 single-instance;updater 单独建设。" -- done_note: "接入官方 tauri-plugin-autostart,保留既有 single-instance;query/set command 对重复调用幂等,debug 与 target/release 路径拒绝启用,未改 pnpm-lock。路径 fixture、cargo test --lib 178/178 与 cargo check --release 通过;`mise exec -- pnpm build:local` 已生成 NSIS/MSI,NSIS SHA-256=82F05ECB0A68E5B87FCD5D50906A64A4CD07B6E9169A07CB2F20D6130FE36C12。正式安装后的注册表效果留待安装包 human_verify。" +- decision_note: "用户于 2026-09-23 明确恢复 A6,但限定正式安装版:默认关闭,debug/target 构建拒绝启用,查询/启停幂等,复用 single-instance;开发前先退出正式版,不并行运行两个实例;updater 单独建设。" +- done_note: "接入官方 tauri-plugin-autostart,保留既有 single-instance;query/set command 通过纯状态机 fixture 证明重复调用幂等,debug 与 target/release 路径拒绝启用,未改 pnpm-lock。路径 fixture、cargo test --lib 179/179 与 cargo check --release 通过;`mise exec -- pnpm build:local` 已生成 NSIS/MSI,NSIS SHA-256=82F05ECB0A68E5B87FCD5D50906A64A4CD07B6E9169A07CB2F20D6130FE36C12。正式安装后的注册表效果留待安装包 human_verify。" ### ✅ A7. Agent 连接状态与自启动 UI 投影 - status: done diff --git a/docs/ai-context/agent-button-system.md b/docs/ai-context/agent-button-system.md index 8b294ea..341fe3f 100644 --- a/docs/ai-context/agent-button-system.md +++ b/docs/ai-context/agent-button-system.md @@ -133,6 +133,7 @@ acceptance 是 agent 自验的唯一判断依据,必须是可执行断言: - readiness 分为 `configured → reachable → handshake → tools_ready → reload_required`。配置存在不等于当前任务已加载;停在哪层,就只修哪一层。 - `.lian-jing/context.json` 是项目身份唯一来源;Skill 和插件不复制项目身份、Gitea 凭证或 MCP 实现。 - Windows 登录自启动只服务正式安装版,默认关闭且由用户主动启用;debug 和 `target/release` 直接运行明确拒绝注册。single-instance 继续保证同一时间只有一个炼境实例。 +- 开发炼境前先从托盘完全退出正式安装版;不得把正式版与 `pnpm tauri dev` 并行运行。single-instance 是运行时兜底,不替代这一开发约定。 - updater 是独立的发布能力,不属于自启动或 readiness 的完成条件。 这套 scope 是单用户本机的误操作隔离,不是多租户认证。威胁模型变化前,不向项目配置写持久 token。 diff --git a/src-tauri/src/commands/settings.rs b/src-tauri/src/commands/settings.rs index eb29ffd..8daeed1 100644 --- a/src-tauri/src/commands/settings.rs +++ b/src-tauri/src/commands/settings.rs @@ -604,6 +604,28 @@ pub struct AgentAutostartStatus { pub detail: String, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum AutostartAction { + RefuseEnable, + Noop, + Enable, + Disable, +} + +fn plan_autostart_action(eligible: bool, current: bool, requested: bool) -> AutostartAction { + if !eligible && requested { + return AutostartAction::RefuseEnable; + } + if !eligible || current == requested { + return AutostartAction::Noop; + } + if requested { + AutostartAction::Enable + } else { + AutostartAction::Disable + } +} + fn autostart_eligibility(is_debug: bool, executable: &Path) -> (bool, String, String) { let executable_text = executable.to_string_lossy().to_string(); if !cfg!(target_os = "windows") { @@ -688,18 +710,15 @@ pub fn set_agent_autostart( enabled: bool, ) -> Result { let before = get_agent_autostart_status(app.clone())?; - if !before.eligible { - if enabled { - return Err(before.detail); - } - return Ok(before); - } - if before.enabled != enabled { - if enabled { + match plan_autostart_action(before.eligible, before.enabled, enabled) { + AutostartAction::RefuseEnable => return Err(before.detail), + AutostartAction::Noop => return Ok(before), + AutostartAction::Enable => { app.autolaunch() .enable() .map_err(|error| error.to_string())?; - } else { + } + AutostartAction::Disable => { app.autolaunch() .disable() .map_err(|error| error.to_string())?; @@ -1331,4 +1350,32 @@ mod tests { assert_eq!(kind, "installed_release"); assert!(detail.contains("正式安装版")); } + + #[test] + fn autostart_transition_is_idempotent_and_refuses_ineligible_enable() { + assert_eq!( + plan_autostart_action(false, false, true), + AutostartAction::RefuseEnable + ); + assert_eq!( + plan_autostart_action(false, false, false), + AutostartAction::Noop + ); + assert_eq!( + plan_autostart_action(true, false, false), + AutostartAction::Noop + ); + assert_eq!( + plan_autostart_action(true, true, true), + AutostartAction::Noop + ); + assert_eq!( + plan_autostart_action(true, false, true), + AutostartAction::Enable + ); + assert_eq!( + plan_autostart_action(true, true, false), + AutostartAction::Disable + ); + } }