dev-manager-tauri/.blueprint/modules/agent-client-parity.md
lanrtop 32e947b622
All checks were successful
Push & PR Check / check (push) Successful in 1m2s
Push & PR Check / check (pull_request) Successful in 59s
fix(onboarding/parity): lian-jing skill 免确认例外补列 register_project_dev_variable,第 8 条开发变量改用对应登记工具(P7 审查修复)
服务器说明 MCP_SERVER_INSTRUCTIONS 的免确认例外是泛称「不含秘密的开发需求元数据」,
register_project_test_account 与 register_project_dev_variable 都属于;skill 新增的
通用边界括注只列了前者,开发变量登记会被误读为须用户确认的「其他写操作」。同源旧
问题:skill 第 8 条把「开发变量(沙箱 key 等)」也只指向 register_project_test_account。

- skill 例外括注同时列出两个登记工具
- 第 8 条拆分:测试账号用 register_project_test_account 登记名称、目标系统、非生产
  环境与用途;开发变量用 register_project_dev_variable 登记名称与用途
- base-onboarding 1.8.0 尚未发布,不再升版本,仅更新 TEMPLATE_FINGERPRINTS 摘要
- server_instruction_universal_rules_are_in_skill 加断言:服务器说明含「开发需求元
  数据」,skill 写操作确认规则所在行同时含该泛称与两个登记工具名
- 蓝图 P7 known_limits 把「措辞与服务器说明一致」精确为「两条规则核心句逐字一致」,
  并记录本次例外与第 8 条的修正

测试:cargo test --locked 全量通过(lib 273 passed / 5 ignored,0 failed)。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 05:43:32 +09:00

194 lines
39 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: agent-client-parity
name: 三端客户端对等
status: in_progress
progress: 0
---
## 概述
子项目在 Claude Code、Codex、DSH(DeepSeek Harness 桌面版)三端都能继续开发:项目状态只存在于 Git 与炼境,客户端是可替换的投影。炼境适配的是**客户端层**(规则文件、skill 位置、MCP 连接、认证、无头模式),不是模型层——在某客户端内切换 DeepSeek 等模型属 `codex-model-switch`,与本模块正交。
## 定位:agent 客户端兼容层(类比 Web 框架之于浏览器)
炼境对 agent 客户端的角色,如同 Web 框架对浏览器:内容按开放标准写一次(AGENTS.md、MCP、SKILL.md),各客户端私有机制的差异由炼境的适配层吸收,并持续实测"这些机制此刻是否仍有效"。
| Web 世界 | 炼境 |
| --- | --- |
| 浏览器 | agent 客户端(Claude Code、Codex、DSH) |
| Web 标准 | AGENTS.md、MCP、SKILL.md 格式 |
| 浏览器私有实现差异 | skill 目录、MCP 配置位置、helper 执行 shell、DSH `!!js` 等 |
| 框架兼容层 / polyfill | 炼境各客户端适配器(skill 双投影、统一 shim、各端注入) |
| Can I Use 兼容性表 | 三端对等矩阵(A–F) |
| 跨浏览器自动化测试 | 三端无头探针(P4) |
skill 只是五个接入面之一,兼容层覆盖全部五面。
类比不成立处(也是本模块的特殊难点):agent 客户端迭代远快于浏览器且机制常无文档,所以更依赖持续实测而非读规范;"浏览器"本身是智能的,部分兼容可用 skill 里的降级说明解决,但可靠性低于代码适配,只作兜底。
## 设计原则
- **标准优先,私有机制只进适配层**:项目内容只写标准形态,客户端差异不得渗入项目内容。
- **检测行为而非嗅探版本**:用无头实测判定机制是否有效,不凭版本号或文档推断(H1–H4 即此法)。
- **兼容性表必须可自动重跑**:客户端几乎每周更新,人工结论很快过期。
- **polyfill 可退役**:客户端原生支持标准后撤掉对应适配(例如 Claude 若开始读 `.agents/skills`,即撤 `.claude/skills` 投影)——对应 AGENTS.md「管家准入与退出」的退出规则。
- **渐进增强**:核心能力在最弱客户端也可用(落地文件兜底),高级能力只在支持的客户端启用(明文读取有界例外)。
- **五个接入面**逐一对齐:① 项目规则(AGENTS.md)② 工作流 skill ③ MCP 连接炼境 ④ 敏感取值 ⑤ 交付信号(Git + lefthook + Gitea,已与客户端无关)。
- **先路由、后认证**:客户端必须连项目专用地址 `/mcp/projects/<project_id>`;全局 `/mcp` 只作兼容,且暴露跨项目管理工具,不应作为子项目日常入口(最小权限)。
- **无头模式 = 各客户端的 agent 按钮**:`claude -p`、`codex exec`、`dsh headless`。对等性用同一任务、同一断言在三端无头实测,不靠文档推断。
- 不写静态 token 进任何配置文件;认证靠各客户端的动态机制或不需认证的落地路径。
## 基线证据(2026-09-30,enterprise-system,三端无头同一只读任务)
| 检查项 | Codex 0.155 | Claude Code 2.1.173 | DSH 桌面版 0.2.0-rc.2 |
| --- | --- | --- | --- |
| A 加载项目规则 | ✅ 摘录 AGENTS.md | ✅ CLAUDE.md 经 `@AGENTS.md` 引用 | ✅ 原生读取 AGENTS.md |
| B lian-jing skill | ✅ | ❌ 看不到(未投影到 `.claude/skills/`) | ✅ 读取 `.agents/skills`,与 Codex 共用 |
| C MCP 工具 | ✅ 32 个,含敏感读取 | ⚠️ 35 个全局工具,无开发变量工具 | ⚠️ 35 个全局工具,无开发变量工具 |
| D get_diagnostics | ✅ | ✅ | ✅ |
| E 列出开发变量 | ✅ | ❌ 工具不存在 | ❌ 工具不存在 |
| F 敏感读取 | ✅ | ❌ 工具不存在 | ❌ 工具不存在 |
| 耗时 | 56 s | 138 s | 25 s |
三端均未泄露凭据。连接现状:Codex 用项目 `.codex/config.toml`(项目地址 + helper);Claude 仅在 `~/.claude.json` user scope 注册全局 `/mcp`(SSE);DSH 在机器级 `~/.dsh/cordis.patch.yml` 注册全局 `/mcp`,配置无法按项目拆分,但支持 `!!js` 求值与每次调用的 `--patch` 叠加层。
## 实施前待验证假设(闸门)
- **H1 DSH**:`!!js` 能否在加载时读取当前目录的 `.lian-jing/context.json` 算出项目地址,并执行 helper 生成认证头。
- **H2 Claude**:Windows 上 `headersHelper` 的执行方式与调用写法;local scope 条目覆盖 user scope 全局注册。
- **H3 Claude skill**:项目 `.claude/skills/<name>/SKILL.md` 能被无头会话发现。
- **H4 Codex**:`http_headers_helper` 中 `%LOCALAPPDATA%` 能被 `cmd /C` 展开。
任一假设不成立:回到设计,不退回写静态 token 或本机绝对路径。
### 假设实测结果(2026-09-30,Workflow 并行实验 + 独立对抗复核)
- **H1 ✅ 成立(复核未推翻)**:DSH `--patch` 叠加层即可,不改 `~/.dsh`。`!!js` 在主进程加载时求值,`require` 不可用但 `process.getBuiltinModule('fs'|'path'|'crypto'|'child_process')` 可用;`process.cwd()` = 启动目录,向上查找 `.lian-jing/context.json` 算项目路由(子目录启动亦可);`execFileSync` 直接执行 helper(不经 shell)生成 Authorization;`disabled: !!js` 在项目外不挂载且不求值 config。helper 失败时安全降级(只写一行 stderr、读取 403、会话不崩)。**限制**:桌面 GUI 模式下 cwd 不随工作区切换,本方案目前只证实适用于在项目目录启动的 headless/CLI。
- **H3 ✅ 成立(复核未推翻)**:Claude 发现 `<项目>/.claude/skills/<name>/SKILL.md`(向上查找到 git 根为止,git 根以上不加载);**不读 `.agents/skills`**,所以必须投影一份到 `.claude/skills`。`-p` 模式下目录 trust 不影响 skill 加载。
- **H4 ✅ 成立(第二轮零凭据实测)**:首轮仅 cmd 层模拟、被复核判证据不足;第二轮用哑 helper + 本地请求记录器直接观测 Codex 0.155 自身行为:`"%LOCALAPPDATA%\...\hdr.cmd"` 被执行且输出成为请求头(记录器收到 `codex-mcp-client` 携带的标记头);`%NOT_A_REAL_VAR%` 不执行;父进程导出的自定义变量 `%LJPROBE_BASE%` 不执行(证实 env_clear 白名单);把 `LOCALAPPDATA` 指向替身目录后执行的是替身目录里的脚本(因果对照);cmd 独有的 `%LOCALAPPDATA:~0%` 子串语法也生效(展开由 cmd.exe 完成);helper 非零退出时连接不建立。
- **H2 ✅ 成立(第二轮零凭据实测)**:Claude Code 2.1.173 在 Windows 经 **cmd.exe** 执行 headersHelper——带引号反斜杠路径、正斜杠路径、`%TEMP%`、显式 `bash x.sh` 均执行且输出成为请求头;`$TEMP/...` 不执行,`%USERNAME%` 展开而 `$USERNAME` 保持字面。**local scope 覆盖 user scope**:init 事件中 `lian-jing` 工具数 35(全局)→ 加 local 条目后 32(项目路由)→ 删除后回到 35。经 `--mcp-config` 传入的 helper 在未信任目录也执行(文档所述 trust 限制针对项目 `.mcp.json` 来源)。`-p` 需设 `MCP_CONNECTION_NONBLOCKING=false` 才会等 MCP 连上再发 init。
- 第二轮两项的对抗复核 agent 未能运行(见下条),结论由主会话直接核对原始日志(记录器日志由独立进程写入)得出,未经第二个 agent 复现。
- **结论:Codex 与 Claude 都经 cmd.exe 执行 helper,可共用同一个 shim 写法**;DSH 经 `!!js` + `execFileSync` 直接调用同一 shim。(入口位置 2026-10-01 由 `%LOCALAPPDATA%\lian-jing\bin\` 迁至 `%USERPROFILE%\.lian-jing\bin\`,原因见 P3 返工记录。)
### 实验附带发现
- **服务端缺陷**:`/mcp/projects/<任意字符串>` 都返回完整 32 个项目工具,不校验项目是否存在;应对未知 project_id 明确拒绝。
- **零凭据验证法**(后续实验与 P4 探针采用):认证链路用 `read_project_test_account(name="LJ_PROBE_NONEXISTENT")` 判定——认证通过返回"开发需求未登记",失败返回 403,凭据不进任何会话;客户端 helper 机制用"哑 helper(写运行标记 + 输出假请求头)+ 本地 HTTP 记录器"判定,全程不碰真 token。
- 模型自报的工具数量不可靠(同一路由报出 27~38 个),计数以 `curl tools/list` 真值为准(项目路由 32、全局 35)。
- 在 Claude 会话中嵌套无头 Claude 须先去掉父会话注入的 `CLAUDECODE`、`CLAUDE_CODE_*` 环境变量并 `< /dev/null`;在 Git Bash 中传 `/斜杠命令` 需设 `MSYS_NO_PATHCONV=1`,否则提示词被改写成 Windows 路径导致误判。
- **实验编排教训**:Workflow 子 agent 超过 180 秒无进展即判卡死并从头重试,而单次真实客户端无头会话常需 1~3 分钟,导致 H2/H4 共 4 次整轮重跑、无一产出结论(实验本身已完成并落盘日志)。需要启动真实客户端会话的实验应由主会话直接执行,或把长调用拆到后台任务、agent 只读结果。
## 任务拆解
### ✅ P1 — 统一连接项目专用地址(Claude + DSH)
- status: done
- 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 项目路由只对在项目目录启动的 headless 生效(2026-10-01 收紧:web/acp/tui 一个进程可服务多个工作区,按启动目录会让别的工作区会话连到该项目并带上认证,属跨项目越权,故与桌面版一样回退全局;判定用 profileContext.name,DSH 0.1.x 无该服务时退回看启动参数)。桌面 GUI 只有一个 Host 进程、启动目录固定为 profile 目录,按工作区路由需会话级插件,见 P9;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
- complexity: S
- files: src-tauri/src/commands/onboarding.rs, src-tauri/resources/onboarding/
- depends: H3
- 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
- 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] 炼境启动写/更新 %USERPROFILE%\.lian-jing\bin 下 shim 指向当前 exe;inject 后受跟踪文件不含本机绝对路径或用户名;[Codex 无头探针] F 项经 shim 成功,且在 Codex 包上下文(Invoke-CommandInDesktopPackage 运行 codex exec,等价 Codex Desktop)同样成功;换构建目录启动后无需重注入仍成功。"
- 注:即原 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 构建启动时自动改指向)。
返工(2026-10-01 人终验发现):Codex Desktop 会话里 lian-jing 完全未加载,日志为 headers helper exit 1。修复:入口迁至 %USERPROFILE%\.lian-jing\bin;旧入口迁移期同步保留,verify_onboarding 把 %LOCALAPPDATA% 写法报 Legacy。复验:四路零凭据探针(Codex 包内、Codex 包外、Claude、DSH 无头)均返回「开发需求未登记」,证实 USERPROFILE 在 Codex helper 环境白名单内。
根因更正(2026-10-02):首次判断「Codex Desktop(MSIX)看不见其安装后在 AppData 下新建的顶层目录」是错的——当时的对照目录本身也是在 Claude 桌面版的包环境里建的。真正原因:worktree 版炼境由 Claude 桌面版(MSIX,Claude_pzs8sxrjxfjjc)里的 agent 启动,继承了其包环境,写到 %LOCALAPPDATA%\lian-jing 的入口被 Windows 重定向进 %LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Local\,只有 Claude 的进程树看得到。对照:经 WMI 启动的非包进程在 AppData 下新建的顶层目录,Codex 包内可见;%USERPROFILE% 根不做这种重定向。入口迁至 %USERPROFILE% 因此仍是正解(与炼境在什么环境里启动无关);据错误前提加的「exe 在 AppData 下就复制副本」逻辑已按「退出」原则撤销(exe 总在真实路径上)。"
- known_limits: "旧 %LOCALAPPDATA% 入口仅为迁移期兼容,各子项目需重注入(会改动其受跟踪的 .codex/config.toml,需在子项目提交)。炼境若在 MSIX 应用进程树里启动,它写的其他 AppData 内容(如安装目录外的缓存)同样只对该应用可见,凡给其他客户端读的文件都不应放在 AppData。"
### 📋 P4 — 三端对等探针固化为炼境能力
- status: todo
- complexity: M
- files: scripts/ 或 src-tauri/src/commands/(实现时定), docs/ai-context/agent-button-system.md
- depends: P1, P2, P3
- acceptance: "agent_verify: [CLI] 一条命令对指定项目依次跑三端无头探针,输出 A–F × 三端矩阵与耗时,断言无凭据泄露;接入包升级后重跑结果可比对。"
### 📋 P5 — 测试账户落地到被 Git 忽略文件
- status: todo
- complexity: M×3
- 见 project-dev-variables V14–V16 与 `docs/tasks/2026-09-30-dev-variables-materialize.md`;落地不返回值、无需认证,是三端都可用的兜底路径。
### 📋 P6 — 技能能力包:通用 skill 单一来源、三端投影
- status: todo
- complexity: M
- files: src-tauri/src/commands/onboarding.rs, src-tauri/resources/(实现时定)
- depends: P2
- acceptance: "agent_verify: [cargo test] 通用 skill(architect、splitter、code-review 等)由一处模板按版本投影到各客户端目录(Claude user/project skills、Codex/DSH 的 .agents/skills);[三端无头探针] B 项三端看到同一组 skill 且内容一致。"
- 为什么:2026-09-30 探针显示同一批 skill 在三端各维护一份(Claude `~/.claude/skills`、Codex `source-command-*` 副本、DSH 手写 `.dsh/skills`),是实际的重复与漂移源。先把 skill 中的确定性步骤下沉为炼境 MCP 工具,让 skill 变薄,再做投影。
- 不做:炼境不执行推理类 skill(无模型、会丢失会话代码上下文、越过「不替 agent 思考」边界);不以运行时拉取 skill 正文作为主路径(炼境离线即失效、脱离 Git 分支)。
### ✅ P7 — MCP 服务器说明的三端支持验证
- status: done
- complexity: S
- files: .blueprint/modules/agent-client-parity.md, docs/ai-context/agent-button-system.md(结论为部分支持,src-tauri/src/mcp_server.rs 与 lian-jing skill 均不改)
- depends: P1
- acceptance: "agent_verify: [三端无头探针] 在 initialize 下发唯一标记的 instructions,判定三端是否把它带入模型上下文。全部支持则把引导提示迁到服务器说明,本地 skill 只保留 MCP 不可达时的排障步骤;部分支持则如实记录、保留 skill 引导。"
- 为什么:Claude 已实证会把 lian-jing 的服务器说明带入上下文;若三端皆支持,引导提示可由炼境集中维护。
- 结论(2026-10-03):部分支持。Claude Code 与 DSH 会把 initialize 下发的 instructions 带入模型上下文,Codex 不会。按 acceptance 不迁移:引导提示仍以 lian-jing skill(及 AGENTS.md 托管块)为三端单一来源,服务器说明保持现状,只作 Claude/DSH 的增强层;三端都须遵守的规则不得只写在服务器说明里(已写入 agent-button-system.md「本地 Agent 控制面的稳定边界」)。
- verification: "2026-10-03 三端无头探针(零凭据;不改用户配置,不连运行中的炼境):node 写的最小 MCP 桩服务(streamable-http,纯 JSON 响应),每端两个端口——实验组 initialize 带 instructions,内含该端专属随机标记;对照组不带 instructions 字段,其余完全相同。标记只出现在 instructions 里,工具 probe_ping 的名字、描述和返回值都不含标记。同一问题:「MCP 服务器说明里有什么标记字符串?只回答标记,不调用工具,没收到就答 NONE」。桩服务记录收到的请求,用来证明客户端确实完成了 initialize → tools/list。
① Claude Code 2.1.173(claude-sonnet-4-6;claude -p --mcp-config --strict-mcp-config --tools '',cwd 为空目录):支持。实验组 init 显示 ljprobe connected,答出 LJ-P7-3067CE459B;对照组答 NONE;实验组输入 token 多 99(10774 对 10675),与多出的说明长度相符。
② Codex CLI 0.155.0-alpha.9.2(codex exec --json -s read-only,-c mcp_servers.ljprobe.url 临时覆盖,用户默认模型,cwd 为空目录):不支持。实验组、对照组都答 NONE;桩服务日志证明 initialize(codex-mcp-client,协议 2025-06-18)与 tools/list 均成功,即说明已送达客户端、但没进入模型;两组输入 token 完全相同(16320 = 16320;换宽问法「全部上下文里是否有 LJ-P7- 开头的字符串」为 16335 = 16335);该会话 rollout 文件中标记出现 0 次。正向对照:让模型调用 probe_ping,它确实发起 mcp_tool_call(server=ljprobe, tool=probe_ping)(被 approval policy never 拦下),说明桩服务的工具在模型视野里,NONE 不是没连上。加开发中特性 --enable mcp_2026_07_28 后仍答 NONE,输入 token 仍为 16320。
③ DSH(桌面版内置运行时 headless;--patch 叠加层停用用户已有的三个 MCP 条目、插入指向桩服务的 dsh-mcp-client 条目,用户 ~/.dsh 与 DSH_HOME 均未改):支持。实验组答出 LJ-P7-B0628021BF,思考过程原样复述了说明全文;对照组答 NONE;实验组输入 token 多 53(6487 对 6434)。DSH 在 initialize 前先发 server/discover,桩服务与炼境一样回 -32601,DSH 随即回退到 initialize → tools/list;其 mcp-client 配置有 maxInstructionBytes(默认 32768)。
探针脚本、桩服务与原始日志留在本机 %TEMP%\lj-p7-probe(未入库,P4 固化时可参考)。"
- known_limits: "Claude 与 DSH 只验了无头模式,Claude 只用了 sonnet-4-6;Codex 未验 TUI 与桌面版(桌面版可能走 app-server 路径,结论可能不同),也未验 SSE 传输。探针用的是桩服务的中英混合短说明,未用炼境真实说明(中文 238 字、UTF-8 500 字节,远低于 DSH 默认上限 32768 字节)实测。Codex 后续版本若开始带入服务器说明,按「检测行为而非嗅探版本」重跑本探针(P4 固化后纳入矩阵),三端都支持时再迁移。现有服务器说明五条要点中,「先读状态」「canonical project_id」「开发变量先查后登记」在 skill 有对应条目;「其他写操作须用户明确确认」与「unavailable/unknown 不得当作成功」在 skill 与 AGENTS.md 中只有分场景表述(合并 PR、CI workflow、工具链安装;MCP 不可用时不伪造诊断),Codex 会话只能从这些条目与工具描述获知——是否补成通用条目留待下次改 skill 时一并处理(需升接入包版本),本卡不改。
已补齐(2026-10-03,分支 feat/agent-client-parity-p7-skill-rules):两条已作为通用边界写进 lian-jing skill 模板开头(双投影到 .agents 与 .claude),两条规则的核心句与服务器说明逐字一致;只读查询、登记开发需求元数据与回传炼境明确列为无需确认的例外,与服务器说明的「其他写操作」及 skill「回传不必逐条征得同意」相容。服务器说明的例外是泛称「不含秘密的开发需求元数据」,skill 例外括注同时列出两个登记工具 register_project_test_account 与 register_project_dev_variable(审查修复:初版只列前者,会把开发变量登记误归入须确认的写操作);同批修正 skill 第 8 条——开发变量改用 register_project_dev_variable 登记名称与用途,测试账号仍用 register_project_test_account(初版两者都指向后者)。接入包 base-onboarding 1.7.0→1.8.0(修复在 1.8.0 发布前,未再升版本,只更新指纹);mcp_server 新增 server_instruction_universal_rules_are_in_skill,服务器说明与 skill 任一侧缺这两条即失败,skill 例外行缺任一登记工具也失败。AGENTS.md 托管块未改,仍只有分场景表述;存量项目需重建安装炼境后重跑 apply_onboarding_pack 才拿到新 skill。"
### 📋 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 仍可用。
### ✅ P9 — DSH 桌面 GUI 按工作区连项目地址(会话级插件)
- status: done
- human_verify: "2026-10-02 用户在 DSH 桌面 GUI 终验通过:重启 DSH 后在 enterprise 工作区新建会话,实际调用 list_project_dev_variables(返回 1 条)与 read_project_test_account(name=LJ_PROBE_NONEXISTENT)(返回「开发需求未登记」,即项目地址与认证均生效);在 dev-manager-tauri 工作区(控制面,null 停止哨兵)新建会话,无 list_project_dev_variables 工具。"
- complexity: M
- files: src-tauri/src/mcp_inject.rs(render_dsh_block 追加插件行), src-tauri/resources/dsh/lianjing-session-mcp.v1.mjs(新增,随二进制打包), src/lib/dshSessionPlugin.test.ts
- depends: P1
- acceptance: "agent_verify: [cargo test] 托管块同时含 mcp-lian-jing 与 lianjing-session-mcp 两行、插件路径为绝对路径、roots 与 normalize_dsh_root 一致;[vitest] 假 ctx 下按会话 cwd 命中项目才挂载、null 哨兵与未命中不挂、shim 失败/超时不抛错且无未处理 rejection;[DSH 0.2 与 0.1 运行时] 临时 DSH_HOME + stub MCP 服务,ACP session/new 两个不同 cwd 的会话只有项目会话产生 /mcp/projects/<id> 请求。human_verify: DSH GUI 在 enterprise 工作区新建会话能看到开发变量工具,在其他工作区看不到。"
- 为什么:2026-10-01 人终验确认 DSH 桌面版会话只连全局地址。只读调研(两路独立 + 对抗复核)结论:DSH 桌面版只有一个 Host 进程,启动目录固定为 profile 目录,!!js 只在启动时求值、拿不到会话信息,mcp-client 的地址在实例化时固定,也不读任何项目级 MCP 配置,所以只改托管块做不到。可行路径是 DSH 自带的 Agent 作用域:每个 GUI 会话的 session.header.cwd 即工作区路径,会话创建时串行发出 agent/created;在 agent.ctx 上挂 dsh-mcp-client,工具只对该会话可见并覆盖同名全局工具(DSH 内置的 ACP 就这样按会话挂 MCP)。
- 风险(实施前需人确认是否值得):依赖 DSH 内部接口(agent/created、agent.ctx.plugin、ctx.loader.import、按 scope 分账),DSH 升级可能失效,须特性探测并降级为全局;Host 有 installFailLoud,插件任何未处理异常都会打崩桌面 Host;agent/created 是串行事件,挂载须加短超时,炼境离线时静默降级;Node 按 URL 缓存 ESM,插件升级须换文件名;子代理看不到父会话作用域的工具,是否给子代理单独挂载需人定;属于往 DSH 进程注入代码,信任边界与现有 !!js + execFileSync 相同,但需按「管家准入」论证。
- 可行性实测(2026-10-02,用户确认「先实测再实现」):临时 DSH_HOME + 记录请求的桩 MCP 服务 + 原型插件(%TEMP%\lj-p9-spike),经 ACP 在同一进程内新建两个不同 cwd 的会话。DSH 0.2.0-rc.2(桌面版内置)与 0.1.5-rc.2(enterprise 本地)结果一致:插件按绝对路径从 home patch 加载;ctx.loader.import('@deepseek-ai/dsh-mcp-client') 成功;每个会话触发 agent/created 且带 session.header.cwd;项目内会话挂载后桩服务收到 /mcp/projects/<id> 的 initialize 与 tools/list(带认证头),另一会话无任何请求。0.1.x 没有 profileContext,事件里也没有 source 字段。未实测:挂载工具对该会话模型的可见性、桌面 GUI 热加载(只有代码阅读结论)。实现需在 PR #24 合并后从 master 开分支。
- 实现(2026-10-02,分支 feat/agent-client-parity-p9):插件随二进制打包(include_str!),ensure_dsh_patch 刷新时写到 %USERPROFILE%\.lian-jing\dsh\(内容不变不写),托管块在全局行之后追加 lianjing-session-mcp 条目(port、shim、roots 以 JSON 写入,路径含引号空格也合法);插件文件写失败时只保留全局行。插件行为:headless 交给全局行(不重复挂载);特性探测 agents/on/loader.import 与 mcp-client 的 Config/apply,不符即降级为全局;按会话 cwd 匹配白名单,命中才在 agent.ctx 挂 serverName=lian-jing 的 streamable-http 客户端,经稳定入口异步取认证(≤3 秒,失败则不带认证头挂载),整个挂载 ≤5 秒;所有回调吞掉异常;会话销毁时释放;子代理(header.origin=subagent)按 cwd 挂载,但带工具限制(toolFilter)或无法判断时不挂——挂在子代理自己作用域的工具不受限制约束。
- 对抗审查(11 个 agent,6 条成立均为 low、已修):受限子代理会绕过 toolFilter → 经 tools.view 判断,受限即不挂;版本号只编进文件名时运行中的 DSH 不会重载 → 条目 id 同带版本号(lianjing-session-mcp-v1),并加内容哈希测试拦漏升版本;插件文件缺失时 DSH 0.1.x 启动失败 → 条目加 `disabled: !!js` 文件存在性守卫;GUI 工作目录为 realpath → 白名单同时写入登记路径的规范形态(junction/8.3 短名);补分层停止哨兵、取认证失败/超时用例;验收记录更正(首轮端到端的补丁只有插件行,见下)。
- verification: "2026-10-02:cargo test 247 passed(托管块双条目、版本化 id 与存在性守卫、插件内容哈希、规范路径键);vitest 8 条插件用例通过(含分层哨兵、取认证失败与超时、受限/未受限/无法判断的子代理),typecheck 通过。端到端第一轮的补丁只有插件一行;审查后补测完整托管块(从真实 ~/.dsh 复制的全局行 + 插件行 + 存在性守卫 + 停止哨兵,临时 DSH_HOME + 桩服务,认证经真实稳定入口):DSH 0.2.0-rc.2 与 0.1.5-rc.2 上项目子目录会话产生 /mcp/projects/<enterprise id> 的 initialize 与 tools/list 且带认证头,其他目录会话无任何请求;插件文件缺失时两个版本都正常启动、会话正常、插件条目不加载。审查者另测:全局行与插件共存时 headless 只有一组连接;项目地址 tools/list 很慢时会话创建在约 5 秒上限内返回。"
- known_limits: "未实测桌面 GUI 内:挂载工具对会话模型的可见性(DSH 的 scope 覆盖只有代码阅读与 ACP 探针结论)、改托管块后的热加载(审查者在 ACP 开启 hmr 实测 id 变化会重载;GUI 不生效时重启 DSH)。0.1.x 上工具在会话创建后约几十毫秒才就绪,极端情况下首轮请求可能只看到全局工具。依赖 DSH 内部接口,升级后可能失效(插件会降级并在 stderr 打 [lian-jing] 提示)。flaky:mcp_server 的 scoped_mcp_create_pull_request_uses_synthetic_credential 在全量并行测试中偶发失败(单跑通过,与本卡无关)。"
### ✅ P3b — 接入写入幂等:同内容不重写、Codex 托管块不累积空行
- status: done
- complexity: S
- files: src-tauri/src/lib.rs, src-tauri/src/mcp_inject.rs, src-tauri/src/commands/blueprint.rs, src-tauri/src/commands/cicd.rs, src-tauri/src/commands/agent_infra.rs, src-tauri/src/mcp_tools.rs
- acceptance: "agent_verify: [cargo test] 连续 upsert Codex 托管块两次字节不变,已累积的空行被吸收,块外后续内容保留;CRLF 检出的同内容 AGENTS/CLAUDE 托管块返回 unchanged 且不被 LF 重写;语义相同但键序/行尾/末尾换行不同的 settings.json 不被重写;CRLF 检出的同内容 CI workflow 的 plan 为 noop;[MCP inject_mcp ×2] enterprise 的 .codex/config.toml、.claude/settings.json、.lian-jing/context.json、.claude/liangjing.json 第二次注入修改时间不变。"
- 为什么:2026-10-01 enterprise 未提交改动归属梳理发现两处炼境侧缺陷。① Codex 托管块自带结尾换行,又原样保留 END 之后的换行,每注入一次多一个空行(enterprise 已累积 16 行)。② sync_blueprint_rules 与注入在内容不变时也用 LF 重写文件:子项目开 core.autocrlf,CRLF 检出的同内容文件因大小变化被 git status 误报为改动(CONVENTIONS.md、context.json),修改时间也被刷新,干扰了「这次改动是谁做的」的判断。
- verification: "2026-10-02:新增 lib.rs write_if_content_changed(忽略行尾比较,内容不变不写);sync_blueprint_rules 的 CONVENTIONS 与托管块、context.json、.claude/settings.json、Codex 托管块统一改用它,托管块未变时返回 unchanged。cargo test 246 passed。实测 enterprise:注入后 .codex/config.toml 末尾空行 16→0;再次注入四个文件修改时间均不变。已存在的伪改动(context.json、CONVENTIONS.md)需在子项目里由人刷新一次索引。
对抗审查(9 个 agent,2 条成立、5 条驳回)后补:JSON 写入(settings.json、context.json)改为按语义比较——Claude Code 改写后键序与末尾换行不同,按文本比较仍会整文件重排;CI workflow 的 plan 改为忽略行尾判 noop(原按字节比较,CRLF 检出的同内容文件会被判 update 并请用户确认一次无变化写入),apply 与 generate_workflow 落盘同样只在内容变化时写;同类遗漏 agent_infra 生成文件与 notes-archive 回填也统一改用 write_if_content_changed。cargo test 245 passed(新增 JSON 语义与 CI CRLF 回归断言)。"
### ✅ P10 — 接入包升级前检测目标文件的块外未提交改动
- status: done
- complexity: S
- files: src-tauri/src/commands/onboarding.rs(apply 预览 / 结果), src-tauri/src/commands/blueprint.rs(BLUEPRINT_MANAGED 标记常量), src-tauri/src/mcp_tools.rs(MCP 输出), src/lib/commands.ts, src/components/dashboard/BlueprintModal.tsx, docs/ai-context/project-context.yaml
- depends: P8
- acceptance: "agent_verify: [cargo test] 目标文件在 git 中有托管块之外的未提交改动时,apply_onboarding_pack 的预览与结果对该文件给出 warning(含文件名与「块外有未提交改动,升级后将成为混合文件」提示),默认仍只改托管块、不改块外内容;无未提交改动或非 git 仓库时无此提示。"
- 为什么:2026-10-01 enterprise 的 docs/ai-context/git-workflow.md 在有项目未提交改写的情况下被升级了托管块(1.6.0→1.7.0),产生「同一文件混有炼境与项目两种来源改动」的混合文件。按「禁止字符串手术」铁律它无法拆分提交,只能交人定案,verify_onboarding 也因此持续报版本不符。管家应在制造混合文件之前提示,而不是事后由人收拾。
- 实现(2026-10-03,分支 feat/agent-client-parity-p10):TemplateResult 新增可选 warning。apply_onboarding_pack 在模板分发与 buff 蓝图规则同步前后夹一层检测:写入前对 AGENTS.md、docs/ai-context/git-workflow.md、CLAUDE.md 跑 `git --no-optional-locks status --porcelain -uall -- <文件>`,有改动再取 `git show HEAD:./<文件>`(不在 HEAD 中按空内容算,即未跟踪/仅暂存/尚无提交),两边去掉 ONBOARDING_MANAGED 与 BLUEPRINT_MANAGED 块后比较(只忽略 CRLF/LF 与文末托管块连同其前空白,见下方审查修复);写入后只对本次确被改写的文件给出 warning(含文件名与「块外有未提交改动,升级后将成为混合文件」),CLAUDE.md 不在模板结果里,预警时补一条结果。git 不可用、非仓库或命令失败一律静默。只提示不阻断,块外内容照旧不动。MCP 单项目输出在该文件行下加 ⚠️ 行,批量输出追加「混合文件」清单;炼境 UI 接入包结果行下显示琥珀色预警。
- verification: "2026-10-03:新增 3 个测试——outside_managed_content_strips_blocks_and_ignores_eol_and_blank_lines(纯函数:剔除两种块、忽略 CRLF 与空行、不在 HEAD 时有块外内容才算、残缺块按块外处理);mixed_file_warning_only_for_uncommitted_changes_outside_managed_blocks(临时 git 仓库,走与 apply 相同的检测夹层 + 真实 sync_blueprint_rules:git-workflow.md 块外未提交改写 → updated 且带预警、块外改写原样保留、托管块升到模板版本;未跟踪 CLAUDE.md 含项目内容 → 补 appended 结果并预警;与 HEAD 一致的 AGENTS.md 照常升级无预警;全部提交后只改托管块版本并整体转 CRLF → 升级且全部无预警、不补 CLAUDE.md 行);mixed_file_warning_is_silent_outside_git(非 git 目录升级无预警,--nocapture 确认未走跳过分支)。变异自检:把探测函数强制返回 true,后两个测试均失败,已还原。cargo test --locked:lib 270 passed / 5 ignored,0 failed;pnpm typecheck 通过;pnpm test 16 passed。"
- review_fixes: "2026-10-03 审查修复三条:① 原比较逐行 trim_end 并丢弃全部空行,块外删空行(Markdown 并段)或加行尾空格(硬换行)会漏报——改为托管块原位留占位符,只把文末的占位符连同其前空白按 trim_end 归一(抵消炼境追加时的 trim_end + 空行),文中块两侧空行与行尾空格照常比较;② 后续步骤(buff 先同步蓝图规则、再写 DB)报错时预警随 Err 丢失——改为先标注再返回,错误信息并入混合文件名;③ 补「块外有未提交改动但托管块已是最新、本次不改写」情形,测住 annotate 的改写判断。新增测试 outside_managed_whitespace_edits_count_as_changes、mixed_file_warning_survives_failure_after_write,主场景测试补情形三。"
- known_limits: "仓库里没有 apply_onboarding_pack 的预览命令(acceptance 写的「预览」无处可挂),预警随结果返回:检测在写入前完成,但升级照常进行,调用方拿到结果时混合文件已经产生——真正「先提示再决定」要等 P8 的只报告 diff 模式复用本检测。从未提交过的炼境新建文件(块外整段是模板 TODO)在模板升版后重跑也会预警(不在 HEAD 中、无法区分块外内容来源),属安全方向的误报。buff 之外单独调用 sync_blueprint_rules(UI 同步蓝图规则、批量同步)不经此检测。仅比较 HEAD 与工作区:暂存区与工作区不同的中间状态不单独判断。文末托管块之前、之后的纯空白增删(如块前空行数、文件末尾多出空行)为抵消炼境追加差异仍被忽略。"