Compare commits
No commits in common. "1888f71626851ae48a18e56b8db3f22ce1039672" and "9a1635a22576253c58e7c001c837f8b32a233bd8" have entirely different histories.
1888f71626
...
9a1635a225
@ -6,7 +6,7 @@ description: >
|
||||
项目自身保留业务规则、测试、部署声明与交付决策。炼境本身亦被纳入其中,
|
||||
形成可自我进化的开发智能闭环。
|
||||
iteration: 1
|
||||
updated: 2026-10-01
|
||||
updated: 2026-09-30
|
||||
agent_button_carrier: tauri
|
||||
|
||||
|
||||
@ -511,19 +511,10 @@ modules:
|
||||
name: 三端客户端对等
|
||||
area: infra
|
||||
status: in_progress
|
||||
progress: 38
|
||||
updated: 2026-10-01
|
||||
note: "P1/P2/P3 完成:三端(Claude Code、Codex、DSH)在 Enterprise 经稳定 shim 连项目专用地址,无头验收 B–F 全部通过(基线时 Claude、DSH 缺工具与认证、Claude 缺 skill)。Codex 项目配置不含本机路径;Claude 条目直接编辑 ~/.claude.json;DSH 托管块按登记白名单路由。四轮对抗审查问题均已修复。待做 P4 探针固化(依赖 architecture-evolution A1)、P5–P8。已知限制:DSH 桌面 GUI 模式回退全局。"
|
||||
position: [3500, 950]
|
||||
|
||||
- id: architecture-evolution
|
||||
name: 炼境架构演进
|
||||
area: infra
|
||||
status: planned
|
||||
progress: 0
|
||||
updated: 2026-10-01
|
||||
note: "技术栈评估:Tauri v2 + Rust + React/TS 适合管家方针,不换栈。按收益排序的结构改进:A1 本体与投影分离(core crate + CLI,P4/P8 依赖)、A2 前后端类型契约生成、A3 配置文本模板化、A4 MCP 改用官方 rmcp SDK。"
|
||||
position: [3750, 950]
|
||||
updated: 2026-09-30
|
||||
note: "目标:子项目在 Claude Code、Codex、DSH 三端都能继续开发。2026-09-30 无头基线探针:Codex 全绿;Claude 与 DSH 连全局 /mcp、看不到开发变量工具;Claude 缺 lian-jing skill。先统一连接地址,再处理认证与 skill 投影,并把探针固化为可重跑检查。"
|
||||
position: [3500, 950]
|
||||
|
||||
edges:
|
||||
- from: project-resource-broker
|
||||
@ -863,7 +854,3 @@ edges:
|
||||
- from: project-dev-variables
|
||||
to: agent-client-parity
|
||||
type: dependency
|
||||
# P4 探针固化、P8 一键接入以本体与投影分离(A1 的 CLI 投影)为承载。
|
||||
- from: architecture-evolution
|
||||
to: agent-client-parity
|
||||
type: dependency
|
||||
|
||||
@ -80,16 +80,13 @@ skill 只是五个接入面之一,兼容层覆盖全部五面。
|
||||
|
||||
## 任务拆解
|
||||
|
||||
### ✅ P1 — 统一连接项目专用地址(Claude + DSH)
|
||||
- status: done
|
||||
### 🔵 P1 — 统一连接项目专用地址(Claude + DSH)
|
||||
- status: in_progress
|
||||
- complexity: M
|
||||
- files: src-tauri/src/mcp_inject.rs, docs/ai-context/agent-button-system.md
|
||||
- depends: H1, H2
|
||||
- acceptance: "agent_verify: [三端无头探针] 在 enterprise 目录 Claude 与 DSH 的 C 项均看到开发变量工具(项目工具集),E 项成功;项目目录外的会话不获得他项目工具。"
|
||||
- 注:吸收原 project-dev-variables V17(Claude 以 local scope + headersHelper 取值)。
|
||||
- verification: "2026-10-01 Enterprise 三端无头验收(零凭据:F 项以 read_project_test_account(name=LJ_PROBE_NONEXISTENT) 判定认证,返回「开发需求未登记」即认证通过):Codex 59s、Claude 40~56s、DSH 11~14s,三端 B/C/D/E/F 全部通过(基线时 Claude、DSH 的 C/E/F 失败、Claude 的 B 失败)。Claude 以 init 事件客观确认 lian-jing 32 个项目工具且含开发变量工具;claude mcp list 显示项目地址 ✔ Connected。DSH 托管块 url 表达式在项目根、子目录、带尾分隔符路径解析为项目地址,在上级/无关目录回退全局;控制面 null 哨兵命中即停。未知 project_id 返回 404。"
|
||||
- review: "六轮对抗审查(首轮 9 项、第二轮 13 条、第三轮 10 条、最终 10 条、收尾 13 条,均已处理)。Claude 条目写入方式三度调整:CLI+后台队列(cwd 依赖与竞态)→ 直接编辑 ~/.claude.json(须复刻 Claude 的 git 根/worktree/NFC 键推导、.claude.json.lock、CLAUDE_CONFIG_DIR 等私有机制)→ 只读扫描认领 + CLI 写入(扫描结论不代表本目录实际生效条目,共享键下会误删他项目)→ 最终定为『Claude 自己的回答为准』:快路径只认项目根或其祖先键下的一致托管条目,其余在项目目录执行 claude mcp get 取实际生效条目,按归属(本项目托管/他项目托管/手写/无)决策,写入经 claude CLI、失败回滚;涉及 CLI 的 Tauri 命令改到线程池执行,删组/移出组先删成员关系再清配置。实测(enterprise):稳态注入 174ms 不起 CLI;缺失 → 写入、过期 helper → 刷新、手写条目 → 保留,claude mcp get 均为 Local 且 ✔ Connected;经 shim 零凭据探针认证通过。"
|
||||
- known_limits: "DSH 桌面 GUI 模式的 cwd 不随工作区切换,项目路由目前只对在项目目录启动的 headless/CLI 生效,GUI 回退全局;claude mcp list 会提示 lian-jing 同时存在 user 与 local 两个作用域(user 级全局为有意保留的兜底);项目目录已被移动或删除后,Claude 中的残留托管条目无法经 CLI 定位,只记录 warn 与手工清理命令,不直接改写 ~/.claude.json;共享 Claude 项目键的嵌套项目/worktree 只有键所属项目的条目生效,他项目不改动、只记 warn;非稳态同步依赖 claude mcp get 的人类可读输出(格式无法识别时按未知处理、不写入),Claude 改输出格式会退化为只告警。"
|
||||
|
||||
### ✅ P2 — Claude skill 投影
|
||||
- status: done
|
||||
@ -99,14 +96,13 @@ skill 只是五个接入面之一,兼容层覆盖全部五面。
|
||||
- acceptance: "agent_verify: [cargo test onboarding] 接入包同时写 .claude/skills/lian-jing/SKILL.md,内容与 .agents/skills 单一来源一致;[Claude 无头探针] B 项看到 lian-jing。"
|
||||
- verification: "2026-10-01:同一模板双投影到 .agents/skills 与 .claude/skills;verify_onboarding 新增「Claude 炼境技能已分发」(检查链 12 项);base-onboarding 升至 1.6.0,supported_clients 增 dsh;skill 降级步骤改为三端通用并提示「无开发变量工具即连的是全局地址」。新增测试 skill_is_projected_to_codex_and_claude_from_one_source;cargo test --locked 229 passed / 5 ignored。Enterprise 分发后 verify_onboarding 14/14,两份 skill 逐字节一致;Claude 无头会话 init 事件 skills 与 slash_commands 均含 lian-jing(基线探针时为不存在)。"
|
||||
|
||||
### ✅ P3 — 稳定 helper 入口,项目配置去本机路径
|
||||
- status: done
|
||||
### 🔵 P3 — 稳定 helper 入口,项目配置去本机路径
|
||||
- status: in_progress
|
||||
- complexity: M
|
||||
- files: src-tauri/src/mcp_inject.rs, src-tauri/src/lib.rs, src-tauri/src/commands/onboarding.rs
|
||||
- depends: H4
|
||||
- acceptance: "agent_verify: [cargo test] 炼境启动写/更新 %LOCALAPPDATA% 下 shim 指向当前 exe;inject 后受跟踪文件不含本机绝对路径或用户名;[Codex 无头探针] F 项经 shim 成功;换构建目录启动后无需重注入仍成功。"
|
||||
- 注:即原 project-dev-variables V18;Claude headersHelper 与 DSH 共用同一 shim。
|
||||
- verification: "2026-10-01:shim 写入 %LOCALAPPDATA%\lian-jing\bin\lianjing-mcp-header.cmd(CRLF,exe 路径 % 转义),Enterprise 的 .codex/config.toml 托管块 helper 为 %LOCALAPPDATA% 形式、不含本机路径与用户名;Codex 无头 F 项经 shim 认证通过。H4 实测证实 Codex 经 cmd 展开 %LOCALAPPDATA%(替身目录因果对照)。换构建目录启动后免重注入:shim 每次启动重写指向当前 exe(实测在 worktree 构建启动时自动改指向)。"
|
||||
|
||||
### 📋 P4 — 三端对等探针固化为炼境能力
|
||||
- status: todo
|
||||
@ -136,11 +132,3 @@ skill 只是五个接入面之一,兼容层覆盖全部五面。
|
||||
- depends: P1
|
||||
- acceptance: "agent_verify: [三端无头探针] 在 initialize 下发唯一标记的 instructions,判定三端是否把它带入模型上下文。全部支持则把引导提示迁到服务器说明,本地 skill 只保留 MCP 不可达时的排障步骤;部分支持则如实记录、保留 skill 引导。"
|
||||
- 为什么:Claude 已实证会把 lian-jing 的服务器说明带入上下文;若三端皆支持,引导提示可由炼境集中维护。
|
||||
|
||||
### 📋 P8 — 一键接入:创建/添加子项目即完成三端可用
|
||||
- status: todo
|
||||
- complexity: M
|
||||
- files: src-tauri/src/mcp_inject.rs, src-tauri/src/commands/onboarding.rs, src-tauri/src/commands/projects.rs(实现时定)
|
||||
- depends: P4
|
||||
- acceptance: "agent_verify: [Tauri command / MCP] 对新登记项目执行一次接入 → 依次完成 canonical context、接入包分发(规则、skill 双投影、Codex 托管块)、本机三端注册(Claude local、DSH 白名单、shim、token),并以三端无头探针给出 A–F 兼容矩阵;重复执行幂等;接入包对项目文件的改动只报告 diff、不自动提交。"
|
||||
- 为什么:2026-10-01 Enterprise 验收时,接入散落在 inject_mcp、apply_onboarding_pack、verify_onboarding 与手工探针多个步骤。按「分发 vs 查询」:随仓库走的文件由炼境分发一次、按版本升级;与机器相关的客户端注册由炼境在本机维护、不进仓库;易变信息走 MCP 查询。默认三端都接入(成本小,保留随时换客户端的能力);能用标准的不做专门适配(Codex、DSH 原生读 AGENTS.md 与 .agents/skills,只有 Claude 需要 CLAUDE.md 与 .claude/skills 投影);炼境离线时规则与 skill 仍可用。
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
---
|
||||
id: architecture-evolution
|
||||
name: 炼境架构演进
|
||||
status: planned
|
||||
progress: 0
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
2026-10-01 评估结论:现有技术栈(Tauri v2 + Rust + React/TS + SQLite)适合「管家 / agent 客户端兼容层」方针,不换栈。常驻托盘服务、凭据安全、单文件分发、cargo test 快速回归都是 Rust/Tauri 的强项;真正的难点在 Windows 与各客户端自身机制(cmd 引号与 %VAR% 展开、CRLF、Claude 配置作用域、DSH !!js、Node 拒绝直接执行 .cmd),与语言无关。Electron 全 Node 栈(与三家客户端同生态、JSON/YAML 顺手)因常驻内存大、凭据需原生模块且须整体重写而否决;Go、Python 无明显收益。
|
||||
|
||||
本模块收集比「换语言」更影响实现效率的结构性改进,按收益排序。
|
||||
|
||||
## 任务拆解
|
||||
|
||||
### 📋 A1 — 本体与投影分离:core crate + CLI
|
||||
- status: todo
|
||||
- complexity: L(先 /architect 再拆)
|
||||
- files: src-tauri/Cargo.toml(改为 workspace)、新增 core 与 cli crate(实现时定)
|
||||
- acceptance: "agent_verify: [cargo test] 业务逻辑位于不依赖 Tauri 的 core crate;[CLI] 同一 exe 或独立 cli 在炼境界面未运行时可执行 onboard / probe / diagnostics 等命令并返回结构化结果。"
|
||||
- 为什么:核心逻辑写在 Tauri 应用内,炼境不运行时 agent 与脚本无法调用。业务函数已按「连接由调用方注入」编写,拆出成本可控。拆分后 Tauri 界面、MCP 服务、命令行成为同一本体的三个投影,契合「一个本体 + 多个投影」原则;agent-client-parity 的 P4 探针、P8 一键接入可直接做成命令。shim 已在用 exe 的命令行模式(--lianjing-mcp-header),只是尚未系统化。
|
||||
|
||||
### 📋 A2 — 前后端类型契约自动生成
|
||||
- status: todo
|
||||
- complexity: M
|
||||
- files: src-tauri/src/commands/、src/lib/commands.ts
|
||||
- acceptance: "agent_verify: [pnpm typecheck] commands.ts 的参数与返回类型由 Rust 定义生成(tauri-specta 或 ts-rs),新增 command 不再手抄类型;删改 Rust 字段时 typecheck 报出所有受影响处。"
|
||||
- 为什么:现由规则 R01 要求手动同步 commands.ts,易漏;对应四支柱「可推理」。
|
||||
|
||||
### 📋 A3 — 配置文本生成模板化
|
||||
- status: todo
|
||||
- complexity: M
|
||||
- files: src-tauri/src/mcp_inject.rs、src-tauri/resources/
|
||||
- acceptance: "agent_verify: [cargo test] Codex 托管块、DSH 托管块、shim 等由模板(如 minijinja)渲染,并有「渲染结果是合法 YAML/TOML/JSON」测试。"
|
||||
- 为什么:2026-10-01 实现 P1 时在 Rust 中手拼 YAML/JS,出现缩进被字符串续行吞掉、转义少一个反斜杠两个缺陷(均被测试拦下)。模板能从写法上避免这类问题。
|
||||
|
||||
### 📋 A4 — MCP 协议改用官方 Rust SDK(rmcp)
|
||||
- status: todo
|
||||
- complexity: M
|
||||
- files: src-tauri/src/mcp_server.rs、src-tauri/src/mcp_tools.rs
|
||||
- acceptance: "agent_verify: [cargo test + 三端无头探针] 换用 rmcp 后 scoped 路由、敏感读取认证、SSE 与 streamable HTTP 行为不变,三端 A–F 矩阵不回退。"
|
||||
- 为什么:现为手写 JSON-RPC + SSE。各客户端对 MCP 的支持持续演进(streamable HTTP、认证、服务器说明等),自维护协议细节的成本会上升。
|
||||
|
||||
### 附:调用外部进程的 Tauri command 统一 async
|
||||
- 同步 Tauri command 跑在主线程,内部调用 claude CLI 等外部进程会卡界面;agent-client-parity 审查已两次发现此类问题。新增此类 command 默认 async,存量随相关改动迁移,不单独立卡。
|
||||
@ -1,5 +1,4 @@
|
||||
{
|
||||
"project_id": "dev-manager-tauri",
|
||||
"schema_version": 1,
|
||||
"mcp_scope": "global"
|
||||
"schema_version": 1
|
||||
}
|
||||
@ -6,7 +6,7 @@ contracts:
|
||||
- label: API 接口(Axum)
|
||||
paths:
|
||||
- "src-tauri/src/mcp_server.rs"
|
||||
doc: "MCP 仅监听 127.0.0.1;新项目使用 /mcp/projects/<project_id> scoped route,连接 scope 拒绝跨项目参数和全局管理写工具。scoped route 只服务已登记项目,未知 project_id 返回 404(数据库不可用同样拒绝)。各客户端经 %LOCALAPPDATA%/lian-jing/bin/lianjing-mcp-header.cmd 稳定入口取认证头(炼境每次启动指向当前 exe):Codex 写项目 .codex/config.toml,Claude 写 local 条目(优先于 user 级全局;一律经 claude CLI、cwd=项目目录写入,项目键推导、文件锁与配置路径交给 Claude 自身,不直接编辑 ~/.claude.json;项目根或其祖先键下已有一致的托管条目则不起 CLI,否则以项目目录下 claude mcp get 的实际生效条目为准:本项目托管条目刷新/移除,他项目托管条目与手写条目不碰,无法确认现状一律不写;项目目录已不存在时只记录残留不写入),DSH 写 ~/.dsh/cordis.patch.yml 托管块(内嵌炼境登记的「项目根目录→project_id」白名单,!!js 从启动目录向上精确匹配根目录才选项目地址并取认证,任意目录里的 context.json 不作授权依据;未命中回退全局;项目增删与端口变更时重写)。Claude local 条目与 DSH 块只替换炼境托管的条目,手写同名配置不覆盖;移除项目时一并清理。旧 /mcp 只作兼容迁移。read_project_dev_variable 与 read_project_test_account 是两种有界开发值读取:仅 scoped 路由、匹配项目的本机 bearer 认证后单项读取;认证失败在 dispatch 前拒绝。路由变更需同步 Agent 按钮体系文档。"
|
||||
doc: "MCP 仅监听 127.0.0.1;新项目使用 /mcp/projects/<project_id> scoped route,连接 scope 拒绝跨项目参数和全局管理写工具。scoped route 只服务已登记项目,未知 project_id 返回 404(数据库不可用同样拒绝)。各客户端经 %LOCALAPPDATA%/lian-jing/bin/lianjing-mcp-header.cmd 稳定入口取认证头(炼境每次启动指向当前 exe):Codex 写项目 .codex/config.toml,Claude 写 ~/.claude.json 的 local 条目(优先于 user 级全局),DSH 写 ~/.dsh/cordis.patch.yml 托管块(!!js 按启动目录选项目地址,项目外回退全局)。旧 /mcp 只作兼容迁移。read_project_dev_variable 与 read_project_test_account 是两种有界开发值读取:仅 scoped 路由、匹配项目的本机 bearer 认证后单项读取;认证失败在 dispatch 前拒绝。路由变更需同步 Agent 按钮体系文档。"
|
||||
|
||||
- label: MCP 工具接口
|
||||
paths:
|
||||
@ -25,7 +25,7 @@ contracts:
|
||||
- ".lian-jing/context.json"
|
||||
- ".lian-jing/capability-packs.json"
|
||||
- ".claude/liangjing.json"
|
||||
doc: "`.lian-jing/context.json` 是 project_id 的工具无关单一事实来源;`.claude/liangjing.json` 仅为兼容投影;`.lian-jing/capability-packs.json` 是炼境分发基线的版本描述。lian-jing skill 以同一模板双投影:`.agents/skills/lian-jing/`(Codex、DSH 读取)与 `.claude/skills/lian-jing/`(Claude Code 不读 .agents/skills),两份内容必须一致。context.json 可选字段 mcp_scope=global 声明控制面项目(如炼境自身):Claude 不写 local 条目、DSH 不入白名单,保留全局地址与跨项目工具。迁移必须保留未知字段;验证必须返回显式状态与 upgrade_hint;升级仅覆盖炼境托管文件,绝不覆盖项目定制或手工 MCP 配置,且不得以客户端全局配置或项目 UI 身份替代项目根目录上下文。"
|
||||
doc: "`.lian-jing/context.json` 是 project_id 的工具无关单一事实来源;`.claude/liangjing.json` 仅为兼容投影;`.lian-jing/capability-packs.json` 是炼境分发基线的版本描述。lian-jing skill 以同一模板双投影:`.agents/skills/lian-jing/`(Codex、DSH 读取)与 `.claude/skills/lian-jing/`(Claude Code 不读 .agents/skills),两份内容必须一致。迁移必须保留未知字段;验证必须返回显式状态与 upgrade_hint;升级仅覆盖炼境托管文件,绝不覆盖项目定制或手工 MCP 配置,且不得以客户端全局配置或项目 UI 身份替代项目根目录上下文。"
|
||||
|
||||
- label: Tauri command 接口(前后端契约)
|
||||
paths:
|
||||
|
||||
@ -17,8 +17,7 @@ pub struct BuffStatus {
|
||||
// ── Tauri 命令 ───────────────────────────────────────────────────────────────
|
||||
|
||||
/// 向项目施加蓝图 Buff:注入 CONVENTIONS + CLAUDE.md,写入 DB
|
||||
// 会经 claude CLI 写客户端配置:在线程池执行,不占界面线程
|
||||
#[tauri::command(async)]
|
||||
#[tauri::command]
|
||||
pub fn apply_blueprint_buff(
|
||||
project_path: String,
|
||||
project_id: String,
|
||||
|
||||
@ -72,10 +72,10 @@ pub fn update_group(id: String, input: GroupInput) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 会经 claude CLI 清理客户端配置:在线程池执行,不占界面线程
|
||||
#[tauri::command(async)]
|
||||
#[tauri::command]
|
||||
pub fn delete_group(id: String) -> Result<(), String> {
|
||||
let conn = pool().get().map_err(|e| e.to_string())?;
|
||||
// 先清除所有成员的 MCP 配置(DB cascade 删除之前)
|
||||
let member_ids: Vec<String> = conn
|
||||
.prepare("SELECT project_id FROM project_group_map WHERE group_id = ?1")
|
||||
.map_err(|e| e.to_string())?
|
||||
@ -83,24 +83,18 @@ pub fn delete_group(id: String) -> Result<(), String> {
|
||||
.map_err(|e| e.to_string())?
|
||||
.filter_map(|r| r.ok())
|
||||
.collect();
|
||||
// 单一 lian-jing key 为全项目共用:成员还属于其他组时保留配置。删组前先算出需清理的成员
|
||||
let to_clear: Vec<&String> = member_ids
|
||||
.iter()
|
||||
.filter(|project_id| {
|
||||
conn.query_row(
|
||||
for project_id in &member_ids {
|
||||
// 单一 lian-jing key 为全项目共用:成员还属于其他组时保留配置
|
||||
let in_other_group: i64 = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM project_group_map WHERE project_id = ?1 AND group_id != ?2",
|
||||
params![project_id, id],
|
||||
|row| row.get::<_, i64>(0),
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap_or(0)
|
||||
== 0
|
||||
})
|
||||
.collect();
|
||||
// 先删组(级联删除成员关系)再清 MCP 配置:批量重注入在锁内复查成员关系,先删可避免把它们重新注入
|
||||
conn.execute("DELETE FROM product_groups WHERE id = ?1", params![id])
|
||||
.map_err(|e| e.to_string())?;
|
||||
drop(conn);
|
||||
for project_id in to_clear {
|
||||
.unwrap_or(0);
|
||||
if in_other_group > 0 {
|
||||
continue;
|
||||
}
|
||||
if let Err(e) = mcp_inject::remove_project_mcp(project_id) {
|
||||
let ctx = serde_json::json!({"project_id": project_id, "group_id": id}).to_string();
|
||||
log_event(
|
||||
@ -111,6 +105,8 @@ pub fn delete_group(id: String) -> Result<(), String> {
|
||||
);
|
||||
}
|
||||
}
|
||||
conn.execute("DELETE FROM product_groups WHERE id = ?1", params![id])
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -185,7 +181,6 @@ pub fn ensure_standalone_group(project_id: &str) -> Result<(), String> {
|
||||
params![project_id, group_id],
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
drop(conn);
|
||||
|
||||
if let Err(e) = mcp_inject::inject_project_mcp(project_id) {
|
||||
let ctx = serde_json::json!({"project_id": project_id, "group_id": &group_id}).to_string();
|
||||
@ -200,8 +195,7 @@ pub fn ensure_standalone_group(project_id: &str) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 会经 claude CLI 写客户端配置:在线程池执行,不占界面线程
|
||||
#[tauri::command(async)]
|
||||
#[tauri::command]
|
||||
pub fn add_group_member(
|
||||
project_id: String,
|
||||
group_id: String,
|
||||
@ -213,7 +207,6 @@ pub fn add_group_member(
|
||||
params![project_id, group_id, role],
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
drop(conn);
|
||||
// DB 写入成功后注入 MCP 配置(非致命,注入失败不影响成员关系)
|
||||
let inject_warning = match mcp_inject::inject_project_mcp(&project_id) {
|
||||
Ok(()) => None,
|
||||
@ -227,8 +220,7 @@ pub fn add_group_member(
|
||||
Ok(inject_warning)
|
||||
}
|
||||
|
||||
// 会经 claude CLI 清理客户端配置:在线程池执行,不占界面线程
|
||||
#[tauri::command(async)]
|
||||
#[tauri::command]
|
||||
pub fn remove_group_member(project_id: String, group_id: String) -> Result<(), String> {
|
||||
let conn = pool().get().map_err(|e| e.to_string())?;
|
||||
// 仅当项目退出后不再属于任何组时才清除 MCP 配置(单一 lian-jing key 为全项目共用)
|
||||
@ -239,13 +231,6 @@ pub fn remove_group_member(project_id: String, group_id: String) -> Result<(), S
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap_or(0);
|
||||
// 先删成员关系再清 MCP 配置:批量重注入在锁内复查成员关系,先删可避免把它重新注入
|
||||
conn.execute(
|
||||
"DELETE FROM project_group_map WHERE project_id = ?1 AND group_id = ?2",
|
||||
params![project_id, group_id],
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
drop(conn);
|
||||
if in_other_group == 0 {
|
||||
if let Err(e) = mcp_inject::remove_project_mcp(&project_id) {
|
||||
let ctx =
|
||||
@ -258,5 +243,10 @@ pub fn remove_group_member(project_id: String, group_id: String) -> Result<(), S
|
||||
);
|
||||
}
|
||||
}
|
||||
conn.execute(
|
||||
"DELETE FROM project_group_map WHERE project_id = ?1 AND group_id = ?2",
|
||||
params![project_id, group_id],
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -276,12 +276,7 @@ pub fn update_project(id: String, input: UpdateProjectInput) -> Result<(), Strin
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// 2. 更新 workspace(路径或平台变化时,先记下旧根目录,供客户端侧配置迁移)
|
||||
let root_may_change =
|
||||
input.win_path.is_some() || input.wsl_path.is_some() || input.platform.is_some();
|
||||
let old_root = root_may_change
|
||||
.then(|| crate::mcp_inject::project_root_with_conn(&conn, &id).ok())
|
||||
.flatten();
|
||||
// 2. 更新 workspace
|
||||
conn.execute(
|
||||
"UPDATE project_workspaces SET
|
||||
wsl_path = COALESCE(?1, wsl_path),
|
||||
@ -302,16 +297,6 @@ pub fn update_project(id: String, input: UpdateProjectInput) -> Result<(), Strin
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// 根目录变化:Claude local 条目按目录挂载、DSH 白名单按根目录匹配,都要迁移到新根。
|
||||
// 会调用 claude CLI,放到后台线程,避免同步命令卡住界面。
|
||||
if root_may_change {
|
||||
let new_root = crate::mcp_inject::project_root_with_conn(&conn, &id).ok();
|
||||
if crate::mcp_inject::project_root_changed(old_root.as_ref(), new_root.as_ref()) {
|
||||
drop(conn);
|
||||
std::thread::spawn(move || crate::mcp_inject::on_project_root_changed(&id, old_root));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -327,8 +312,7 @@ pub fn update_project_tags(id: String, tags: String) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 会经 claude CLI 清理客户端配置:在线程池执行,不占界面线程
|
||||
#[tauri::command(async)]
|
||||
#[tauri::command]
|
||||
pub fn delete_project(id: String, delete_local: Option<bool>) -> Result<(), String> {
|
||||
let conn = pool().get().map_err(|e| e.to_string())?;
|
||||
|
||||
|
||||
@ -76,8 +76,7 @@ pub fn get_settings(space_id: Option<String>) -> Result<HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// 更新 settings:全局 key 写入 settings 表;其余写入 space_settings 表
|
||||
// 端口变更会重注入全部项目(含 claude CLI):在线程池执行,不占界面线程
|
||||
#[tauri::command(async)]
|
||||
#[tauri::command]
|
||||
pub fn update_settings(
|
||||
settings: HashMap<String, String>,
|
||||
space_id: Option<String>,
|
||||
|
||||
@ -119,15 +119,11 @@ pub fn run() {
|
||||
// ── 三端共用的稳定 helper 入口与 DSH 托管块(best-effort,失败只记日志)──
|
||||
// 每次启动把 shim 指向当前 exe,换构建目录后各客户端无需逐项目重注入。
|
||||
if let Err(error) = mcp_inject::ensure_header_shim() {
|
||||
db::log_event(
|
||||
"mcp_inject",
|
||||
"warn",
|
||||
&format!("MCP helper 入口写入失败: {error}"),
|
||||
Some(r#"{"client":"shim"}"#),
|
||||
);
|
||||
db::log_event("mcp_inject", "warn", &format!("MCP helper 入口写入失败: {error}"), None);
|
||||
}
|
||||
if let Err(error) = mcp_inject::ensure_dsh_patch(mcp_port) {
|
||||
db::log_event("mcp_inject", "warn", &format!("DSH 托管块写入失败: {error}"), None);
|
||||
}
|
||||
// 刷新 DSH 白名单要读所有项目根目录的 context.json,放到后台线程,不拖慢窗口启动
|
||||
std::thread::spawn(|| mcp_inject::refresh_dsh_patch(None));
|
||||
|
||||
// ── MCP 单一服务器化一次性迁移(settings 标记位防重复)────
|
||||
std::thread::spawn(|| {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1603,19 +1603,6 @@ async fn handle_scoped_sse_message(
|
||||
headers: HeaderMap,
|
||||
Json(req): Json<RpcRequest>,
|
||||
) -> Response {
|
||||
// 握手后项目可能被删除:每条消息复核登记。只移除确实属于该项目的会话,
|
||||
// 不能凭请求里任意给出的 session_id 删掉别的会话。
|
||||
if !scoped_project_registered(&project_id) {
|
||||
let mut guard = sessions.lock().await;
|
||||
let owned = guard
|
||||
.get(&query.session_id)
|
||||
.and_then(|entry| entry.project_scope.as_deref())
|
||||
== Some(project_id.as_str());
|
||||
if owned {
|
||||
guard.remove(&query.session_id);
|
||||
}
|
||||
return unknown_project_response(&project_id, req.id.clone());
|
||||
}
|
||||
if authorize_sensitive_request(
|
||||
&req,
|
||||
Some(&project_id),
|
||||
|
||||
@ -1292,8 +1292,6 @@ fn get_diagnostics() -> Result<String, String> {
|
||||
AND id IN (
|
||||
SELECT MAX(id) FROM runtime_logs
|
||||
WHERE category = 'mcp_inject'
|
||||
AND json_valid(context)
|
||||
AND json_extract(context, '$.project_id') IS NOT NULL
|
||||
GROUP BY json_extract(context, '$.project_id')
|
||||
)
|
||||
ORDER BY ts DESC
|
||||
|
||||
Loading…
Reference in New Issue
Block a user