dev-manager-tauri/.blueprint/modules/agent-client-parity.md
lanrtop e13599fbbd
All checks were successful
Push & PR Check / check (push) Successful in 59s
Push & PR Check / check (pull_request) Successful in 1m0s
feat(onboarding/parity): lian-jing skill 补写操作确认与 unavailable/unknown 两条通用边界,接入包升至 1.8.0
P7 实测 Codex 不把 MCP 服务器说明带入模型上下文,服务器说明里的「其他写操作
须用户明确确认」「unavailable/unknown 不得当作成功」在 skill 中只有分场景表述。
现把两条作为通用边界写进 lian-jing skill 模板开头(同一模板双投影到 .agents
与 .claude),措辞与服务器说明一致;只读查询、登记开发需求元数据、回传炼境
列为无需确认的例外,与 skill「回传不必逐条征得同意」相容。

- base-onboarding 1.7.0→1.8.0(BASE_ONBOARDING_PACK_VERSION、模板
  capability-packs.json、TEMPLATE_FINGERPRINTS 指纹)
- mcp_server 新增 server_instruction_universal_rules_are_in_skill:服务器说明与
  skill 任一侧缺这两条即失败
- 蓝图 agent-client-parity P7 known_limits 记录已补齐

Feature-Confirmed: true
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 05:25:18 +09:00

38 KiB
Raw Blame History

id name status progress
agent-client-parity 三端客户端对等 in_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「回传不必逐条征得同意」相容。接入包 base-onboarding 1.7.0→1.8.0;mcp_server 新增 server_instruction_universal_rules_are_in_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/ 请求。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/ 的 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/ 的 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 与工作区:暂存区与工作区不同的中间状态不单独判断。文末托管块之前、之后的纯空白增删(如块前空行数、文件末尾多出空行)为抵消炼境追加差异仍被忽略。"