# 路线规划 > v0.10 · 2026-10-28 · 连接管理器 v0.6.0:自动重试白名单、探测退避 45s、设置页主动校验 > 只定方向、边界与判据,不定实现细节。拿不准的地方不写进正文,集中留在末节「待明确」,按提问—批复就地留白,不代负责人拍板。 ## 一、要解决的事 中继的价值不在「多一个工具」,而在消掉两份互相不认账的真相。两个典型场景: **场景 A · 服务启停只有一个合法入口。** 开发服务的启停被统一收在 IDE 的 Run Configuration / Services 面板里。DSH 只能走命令行另起一份,于是端口被占、进程重复、日志散成两摊;更要命的是 **IDE 注入的运行环境与 shell 并不相同**——working directory、SDK、环境变量、启动前置任务都由 IDE 叠加,命令行复现出来的「同一份服务」其实不是同一份。 **场景 B · 报错方与检查方结论相反。** IDE 满屏红线,DSH 检查完说「没有异常」。真实成因(2026-10-27 实测后重排,原「未落盘缓冲区」一条已降级): - IDE 有自己的 **语义分析**(inspection profile、SDK、依赖解析),DSH 只有文本搜索与编译——这是 **主因**; - IDE 的 **索引与刷新有时差**:外部改了磁盘文件,IDE 尚未 refresh,展示的是旧视图; - IDE 展示的构建结果可能来自 **上一次构建**; - 缓冲区未落盘只是 **次要且短暂**的一种:IntelliJ 默认「失焦保存 + 空闲 15 秒保存」(本机无覆盖配置),不像 VS Code 必须显式保存。所以「IDE 看内存、DSH 看磁盘」对 VS Code 成立,对 IDEA 基本不成立。 **共同根因**:DSH 用文本与进程当判据,IDE 用语义模型与自己的执行环境当判据。两边都没说谎,但谁也说服不了对方—— **该做的是让 DSH 用 IDE 的判据、并让写操作经过 IDE**,而不是让 DSH 去模拟 IDE。 ## 二、定位与边界 - 联络中转:把 IDE 的真值源接给 DSH,把 DSH 的动作投递给 IDE 执行。 - **能借则借,不重造**:IDE 已自带 MCP Server(见第三节)。凡它已覆盖的能力,本插件不重复实现,只补缺口。 - 不替代 `terminal` / `lsp`;不接管版本控制。 - 顺序上先读后写。 - **交付形态(2026-10-27 定)**: **不自研 JetBrains 插件**。能力全部取自 IDE 内置 MCP Server;DSH 侧只保留连接管理器(多 IDE、配置存储、健康探测、路由鉴权)。仅当出现 MCP 覆盖不了的能力、且价值足够时,才重新评估是否写插件。 - **第一原则:读不到 ≠ 没问题。** 取不到 IDE 结论时必须显式报「未知/不可用」,绝不降级成「检查通过」——这正是场景 B 的教训。 - **第一原则(续):工具在列表里 ≠ 可调用。** IDE 重启后连接对象仍在、调用全废。故「无结论」至少三种来源——IDE 未连上、会话失效、查询本身失败——三者必须可区分并显式报出。 ## 三、已有能力:IDE 内置 MCP Server(已实测) IntelliJ IDEA 自 **2025.2** 起捆绑 `MCP Server` 插件并默认启用;2026.2 另带 `Debugger MCP toolset`(`debuggerMcp`)。本机基线 2026.2.3 的两个插件均在安装目录 `plugins\` 下。 - 开关:Settings | Tools | MCP Server → Enable MCP Server。 - 传输:SSE、Stdio、HTTP Stream 三种(对应端点 `/sse` 与 `/stream`)。 **DSH 侧 `mcp-client` 只支持 `stdio` 与 `streamable-http`,故取 HTTP Stream。** - 连接参数:仅 URL 加一个头 `IJ_MCP_SERVER_PROJECT_PATH`(指定路由到哪个项目), **不含令牌**。 - 形态:本机默认开着 **Router-only**,60+ 个工具不逐个暴露,只在客户端呈现为一个 `execute_tool` 路由,参数为 `--name value` 的命令行风格(对象/数组传 JSON 字面量,例如 `--files '["a.md"]'`)。资源(resources)为空。 - 实测工具目录共 66 个,按用途归并: - 分析:`analyze_calls`、`build_project`、`get_file_problems`、`lint_files`、`get_project_dependencies`、 `get_project_modules`、`generate_psi_tree`、`run_inspection_kts`、`generate_inspection_kts_api` / `generate_inspection_kts_examples` - 文件与检索:`read_file`、`create_new_file`、`apply_patch`、`search_file`、`search_text`、`search_regex`、`search_symbol`、 `list_directory_tree`、`get_all_open_file_paths`、`open_file_in_editor`、`reformat_file`、`rename_refactoring` - 运行与终端:`get_run_configurations`、`execute_run_configuration`、`execute_terminal_command` - 版本控制:`git_status`、`get_repositories` - 调试器:`xdebug_*` 全套(断点、会话控制、栈、求值) - 数据库与 Notebook:各一套 - **与本规划的关系**:场景 B 的判据与场景 A 的执行入口都已现成,且实测可用(见第九节)。本插件的立足点因此确定从「造中继」改为「补缺口」。 ## 四、路线(缺口驱动) ### 阶段 0 · 接内置 MCP,量缺口(已完成 2026-10-27) 实测记录见第九节。四问结论: 1. `get_file_problems` 覆盖未落盘缓冲区—— **未测**,且先被内容根前提挡住(见下); 2. `build_project` 回传编译错误—— **未测**; 3. `lint_files` 批量语义——已跑通,正常返回 `items`; 4. Run Configuration 列举/启停——`get_run_configurations` 可达(当前项目无配置,返回空数组);`execute_run_configuration` 未试。 **内容根前提(比四问更基础)**:`get_file_problems` 对 `.java` 一律返回 `File is outside project content roots or in an excluded directory`,文件放项目根也一样——原因是模块(`JAVA_MODULE` )没有源码根、没有 SDK。markdown 等文档检查不需要模块,因此不受影响。结论: **场景 B 对文档已可用,对代码需项目结构就位(源码根 + SDK/构建)后才成立**。这不是插件能绕过的,是 IDE 判据本身的前提。 ### 阶段 1 · 只读真值源补齐(场景 B,首个上线) 只做内置 MCP 给不了的部分。按阶段 0 结论排定优先级: - **读写通道(第一缺口,替代原「脏文件全景」)**:写文件与改代码一律走 IDE 的 `apply_patch` / `create_new_file`,不走 DSH 的 write/edit 直落磁盘——这样 IDE 的 PSI 与检查立刻跟上,不会出现「磁盘变了、IDE 还是旧视图」;读判据用 `get_file_problems` / `lint_files` / `build_project`,不自己 grep 硬猜。 读文件同样走 IDE 的 `read_file`(能读 jar 内源码、能反编译 class),不要用 DSH 自己的读文件去猜工程结构。 - 全项目问题聚合:`get_file_problems` 是单文件粒度,缺"项目当前共几个错、按严重级与文件分布"。 - 结论来源标注:每条结论标明来自 IDE 的哪一次分析、是否含未落盘内容。 - 前置条件:目标项目须有源码根与 SDK,否则代码类诊断不可用(须在输出里显式说明,不得当作"无异常")。 判据:制造三类错(语法错、inspection 错、仅存在于未保存缓冲区里的错),DSH 能逐条复述并指出第三类的性质;全过程只读。 ### 阶段 2 · 运行态与启停(场景 A) 内置已有 `get_run_configurations` 与 `execute_run_configuration`,本阶段因此不重造启停,而是做 **约束与呈现**:禁止 DSH 绕开 IDE 用命令行另起服务,并在输出中标明状态来源。 判据:IDE 面板与 DSH 对同一服务的 running 结论逐项一致;DSH 触发的启停在面板上可见。 ### 阶段 3 · 动作回写 原料已具备(`apply_patch`、`create_new_file`、`rename_refactoring`、`reformat_file`、`open_file_in_editor`、 `execute_terminal_command`),缺的是约束。`reformat_file` 已在阶段 0 实测走通。 - 导航与同屏:定位到文件/行、选中范围、弹 diff 或通知。 - 重构与修复:rename、reformat、apply quick fix。 - 全部经审批,且 IDE 侧可见、可撤销。 ### 阶段 4 · 打包与安装(已作废) 不自研 JetBrains 插件,所以没有「把插件装进 IDE」这件事。本阶段只剩 DSH 侧连接管理器的安装:`install_bundle` 指向 `connectors/dsh-mcp-idea`,用户在 IDE 里什么都不用装。 ## 五、已定(负责人 2026-10-27 批复) | 项 | 定论 | |----------|-----------------------------------------------------------| | 文档归属 | 本文归 `文档/软件工程/0.项目管理/` | | 目标产品 | 仅 IntelliJ IDEA;其它 JetBrains 产品后续增量再议 | | 兼容基线 | 以本机安装版本为保守最低版本:2026.2.3(IU-262.10968.63) | | 阶段优先 | 场景 B(只读诊断)先上线 | | 安装方式 | 用户自行在 IDE 的 GUI 从磁盘安装 | | 通信方向 | 优先按需拉取;拉取不成立再考虑 IDE 主动推送 | | 交付形态 | **不自研 JetBrains 插件**;DSH 侧连接管理器 + IDE 内置 MCP | ## 六、风险与坑 - **版本探测不可信**:安装目录名与注册表卸载项都可能是旧值。本机目录名写 `IntelliJ IDEA 2026.1.1`、卸载表写 `261.23567.138` ,而 `build.txt` / `product-info.json` 是 `IU-262.10968.63`(2026.2.3)。一律以 `build.txt` 为准。 - **端口每 IDE 一个,且推不出公式**:实测 IDEA `64342`、CLion `64362`、PyCharm `64462`,三者同时开也互不抢占(内置端口 63342/63343/63344 依次递增,MCP 端口不呈由此派生的线性关系)。因此 **不能靠「内置端口 + 1000」推算**。已验证: **重启后端口不变**(实测重启后仍为 `64342`,新进程 PID 9896),写死端口可行。 - **IDE 重启后 HTTP 会话失效**:连接对象仍在、工具仍列在表里,调用却报 `Streamable HTTP session not found` ;自动重连只管「传输断开」,不会重新协商会话。恢复:`plugin_manager set_plugin` 对 `include:idea-mcp` 关再开( `install_bundle` 会以 `already-installed` 拒绝,重装无效)。这是「工具在 ≠ 可用」的现场证据。 - **内容根前提**:代码文件无源码根/SDK 时诊断不可用;此时工具报的是 `outside content roots`,与"没有错误"必须严格区分。 - **router 参数易错**:`--name value` 是字符串拼接,不是结构化参数。首次实测即因按 JSON 传参而失败。常用工具可考虑取消 router-only,改走结构化参数。 - **未落盘覆盖**:写操作前必须确认缓冲区状态,否则 DSH 改磁盘、IDE 保存旧缓冲区,静默覆盖。 - **brave mode**:免确认执行终端命令与 Run Configuration 会绕开人的复核;默认不开,开启须显式说明风险。 - **长任务**:`build_project` 与调试会话生命周期长,调用须有超时与取消。 - **双真相复发**:只要还留一条绕过 IDE 的路径,分叉就会回来。要么堵掉,要么在工具输出上明标「本结论不来自 IDE」。 ## 七、里程碑判据 1. ~~阶段 0 四问有实测结论~~ → 已完成(四问中两问有结论,两问受阻于内容根前提,见第九节)。 2. 三类错分得清:语法错、inspection 错、仅在缓冲区里的错。 3. 状态一致:IDE 面板与 DSH 对同一服务的结论逐项相同。 4. ~~安装闭环:用户在 GUI 装包后,DSH 侧配置一次即通~~ → 已作废(不自研插件,无需装机)。 ## 八、待明确 > 待明确:内置 `get_file_problems` 是否覆盖未落盘缓冲区?(需在编辑器里造一次未保存改动;文档类文件可直接测,代码类须先有源码根) > 待明确:既然 IDE 自动落盘,「脏文件全景」已降级为次要需求,是否彻底不做,只保留「写入前若发现该文件在 IDE 里打开则提示」这一条轻量规则? > 待明确:是否需要 brave mode?(默认关) > 待明确:设置页配色对比度偏低(说明文字与字段标签在皮肤半透明背景上发灰)。 **延后处理**:当前装的是鲸鱼娘皮肤,主题行为与默认不同,改 > theme token 要等皮肤侧口径定了再做,现不视为缺陷。 > 待明确:多 IDE 并存时的路由——已证实每个 IDE 各自占独立端口、且请求头 `IJ_MCP_SERVER_PROJECT_PATH` 可指定项目,是否按「每个 > IDE 一条 `mcp-client` 行」(`idea` / `clion` / `pycharm` 各一个 `serverName`)注册? > 待明确:DSH 侧的连接 bundle 暂放工作区根 `connectors/idea-mcp`(不在插件 git 仓内),是否迁入插件仓或独立成 DSH 侧插件仓? ## 九、阶段 0 实测记录(2026-10-27) 环境:IntelliJ IDEA 2026.2.3(IU-262.10968.63),Windows;DSH profile `web`。 | # | 动作 | 结果 | |----|--------------------------------------------------|-----------------------------------------------------------------------------| | 1 | IDE 启用 MCP Server,取 HTTP Stream 配置 | 端点 `http://127.0.0.1:64342/stream`;仅一个项目路径头,无令牌 | | 2 | 端点连通性 | `GET /stream` 返回 200 且 `content-type: text/event-stream` | | 3 | 写连接 bundle 并 `install_bundle` | `application: applied`、`enabled: true`、`warnings: []` | | 4 | DSH 侧可见工具 | 仅 `mcp__idea__execute_tool` 加三个 resource 工具(router-only 生效) | | 5 | 探测工具目录 | 66 个工具;参数格式为 `--name value` | | 6 | `get_project_modules` | `[{name: dsh_jetbrains_interaction, type: JAVA_MODULE}]` | | 7 | `get_file_problems` 对 `.md` | 抓到第 89 行「表的格式不正确」(WEAK WARNING),DSH 只看磁盘不会有此结论 | | 8 | `reformat_file` 修表格 | 返回 `ok`;复查 `get_file_problems` → `errors: []` | | 9 | `get_file_problems` 对 `.java`(子目录与项目根) | 均拒绝:`File is outside project content roots or in an excluded directory` | | 10 | `lint_files` | 正常返回 `{"items":[]}` | | 11 | `get_run_configurations` | `{"configurations":[]}`(项目尚无运行配置) | | 12 | MCP resources | 空 | | 13 | 并行启动 CLion 与 PyCharm 的 MCP Server | 三者互不抢占:IDEA `64342` / CLion `64362` / PyCharm `64462` | | 14 | 三 IDE 同开时复验 IDEA 连接 | `get_project_modules` 正常返回,IDEA 端口未被顶掉 | | 15 | 端口派生规律 | 内置端口 63342/63343/63344 递增;MCP 端口无线性公式,不能推算 | 第 7–8 条是场景 B 的现场证据:IDE 报出了 DSH 凭磁盘检查不可能发现的缺陷,且经工具改写后复验通过——读→写→复验闭环成立。探针文件(两个 `.java` 与 `_probe/`)已清理。 ## 十、DSH 侧连接管理器(v0.3.0,2026-10-27) 从「配置型 bundle」升级为 Host + Client 双半插件,落在 `connectors/dsh-mcp-idea`(包 `@local/dsh-mcp-idea`)。 - **架构**:Config 为 `connections[]`,每条连接在 `apply` 里用 `ctx.plugin` 挂一个 `@deepseek-ai/dsh-mcp-client` 子插件; `serverName` 决定工具名 `mcp____*`。已装进 profile `web`,默认三条(idea 启用 64342,clion 64362 与 pycharm 64462 默认关)。 - **设置页机制(关键纠正)**:`ctx.settings` 的表单只收 **volatile** 字段(`packages/settings/settings/src/schema.ts` 的 `volatileForm` 对非 volatile 返回 undefined,`write()` 直接抛错)。我们的 `connections` 不是 volatile,且 volatile 的语义恰是"改值不重挂",与"保存即重挂"相反。 **故设置页走不通,改走 `ctx.configEditor.edit()` + 插件自有 HTTP 路由**,与 `dsh-feiyu-mood` 同款做法。 - **保存语义**:`configEditor.edit` 写 profile patch 后触发 Loader 该条目重启 → `apply` 重跑 → 子插件全部重挂。实测改 `pycharm` 的 id → 3 秒后读回新值 → `mcp__idea__execute_tool get_project_modules` 仍正常。 - **接口**:`GET /dsh-mcp-idea/config`(读连接与挂载态)、`POST /dsh-mcp-idea/config`(校验后保存并重挂)、 `POST /dsh-mcp-idea/reconnect`(按 id 就地重挂单条)。保存采用 **先应答后落盘**:`configEditor.edit` 会重启本插件,路由随之 dispose,后写的响应发不出去。 - **工具面由 IDE 侧的「仅路由模式」开关决定,不是产品差异**:开启时只暴露一个 `mcp____execute_tool`(参数为 `--name value`);关闭时逐个暴露全部工具,并 **同时保留** `execute_tool`。实测把 IDEA 与 CLion 的开关对调后,两边工具名整体改变( `mcp__idea__execute_tool` ↔ `mcp__clion__get_file_problems` 等),历史会话里的旧工具名随之全部失效。故除「一条连接一个子插件」外,还要接受「同一个 IDE 的工具面可被用户随时改掉」。 **工具面是连接时的快照**:在 IDE 里切换开关后,DSH 侧不重连就仍是旧的一套(实测未重连时 IDEA 只有 `execute_tool`,点「重新连接」后逐个工具才出现)。 - **`projectPath` 不可为空**:为空的连接一切调用都会报 `Unable to determine the target project`(并列出该 IDE 里打开的项目)。实测 CLion 指向 `<另一个 Java 项目>/3dPrinterAlign` 后,`get_all_open_file_paths` 正常返回。设置页里新增连接后必须填这一栏。 ### 新增风险(本阶段暴露) - **落在皮肤托管区**:`configEditor` 把配置行追加到 profile patch 末尾,实测落在 `# --- dsh-skin managed (auto-generated; do not edit) ---` 与 `# --- end ... ---` 之间(patch 第 9 行起,我们的行在第 19 行)。若皮肤管理器整段重写该区域,连接配置有被冲掉的风险,需持续观察。 - **本地路由无认证**:`/dsh-mcp-idea/*` 不经 DSH 的浏览器认证即可访问(实测未带 cookie 返回 200),能读连接配置、能触发保存与重连。仅绑回环,但等于给本机任何进程开放了改配置的能力。 - **保存失败不可见**:先应答后落盘,极端情况(写盘被拒、被上层 patch 覆盖)界面只能显示"已保存但没等到重挂",真实原因只在 Host 日志里。 - **linked 包不热更新**:改代码必须 `remove_bundle` + `install_bundle` 才加载新模块世代(已记入 AGENTS.md)。 ## 十一、写读通道与数据库(2026-10-27 定) 本节回答「DSH 到底该用谁的判据干活」,是新阶段的主线,替代原「脏文件全景」那条。 ### 前提修正:IDEA 会自动落盘 IntelliJ 默认开启「失焦保存」与「空闲 15 秒保存」,本机 `options` 下无相关覆盖配置,即默认值生效。 **所以 IDEA 里改的东西基本很快就会进磁盘**;「IDE 看内存、DSH 看磁盘」这套说法对 VS Code 成立(默认必须显式保存),对 IDEA 不成立。 推论: **「未落盘缓冲区」不再是主要分歧来源**,只在「用户正在编辑且尚未触发自动保存」的极短窗口里存在。原「脏文件全景」需求因此降级(见「待明确」)。 ### 写通道:一律走 IDE 写文件与改代码走 IDE 的 `apply_patch` / `create_new_file`,不再用 DSH 的 write/edit 直落磁盘。 理由不是「防覆盖」(IDEA 会自动落盘,覆盖窗口很小),而是 **让 IDE 的 PSI 与检查立刻跟上**:直写磁盘后 IDE 要等 refresh 与重新索引,这段时间里 IDE 的视图与磁盘不一致,恰恰是场景 B 那种「两边说法不一样」。 ### 读通道:用 IDE 的判据,不自己模拟 - 有没有错 → `get_file_problems` / `lint_files`(inspection) - 编译过不过 → `build_project` - 谁调用了谁 → `analyze_calls`(IDE 调用层级,比文本搜索准) - 结构问题 → `search_symbol` / `get_symbol_info` / `get_project_modules` **不要**用 grep 猜语义、也不要用外部编译替代 IDE 的编译结果——两套判据并存就会继续「鸡同鸭讲」。 ### 数据库:借 IDE 已配好的数据源 IDE 侧(DataGrip 组件)已经配好了连接、驱动与凭据,DSH 通过 `list_database_connections` / `execute_sql_query` / `get_database_object_description` 等工具直接用, **不必在 DSH 侧重配一套 JDBC 与口令** 。官方文档给的安全建议:给智能体用一个只读权限的数据库账号,并把数据源配成该账号。 ### 引用规矩:契约与实测分开标注 | 类别 | 内容 | 出处 | |--------------|---------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------| | **契约** | 工具参数、必填项、`1-based` 行列、只分析项目内文件 | 官方文档(本地副本 `文档/参考/mcp-server-2026.2.en.md`) | | **实现观察** | 会话需持 GET SSE 流否则约 15 秒被回收;流上约 6 秒一帧 `: heartbeat`;会话过期回 404 需重握手;切换「仅路由模式」后必须重连才换工具面 | 本仓库实测 | 写文档或写代码时,「契约」可直接引用;「实现观察」必须标明是本机实测,换 IDE 版本要重验。 **两者不许混写**——这是前几轮反复踩到的教训。 ## 十二、实测:空 `problems` ≠ 干净(2026-10-28) 拿 `tty-shop-monorepo` 做对照实验(用户提供的语料:历史上「编辑器报错、编译器通过」较多)。结论分两部分。 ### 一、这批样本上没有分歧 | 判据 | 范围 | 结果 | |--------------------------------------------------|----------------------------------------------------------------------------------------------|----------------------------------| | 编译器(Maven 离线 `-o -q -DskipTests compile`) | `jeecg-system-biz` 整模块 | **通过** | | IDE(`lint_files`,`min_severity=error`) | 16 个文件,跨 `jeecg-system-biz` / `jeecg-boot-base-core` / `jeecg-airag` / `jeecg-merchant` | **0 个 error,且全部被真正分析** | 即:没复现出「编辑器红、编译绿」。用户回忆这类历史告警已在此前工作中修掉,故语料失效,不再追。 ### 二、撞到了同家族的另一种分歧(更重要) 第一次把连接指向 **根项目窗口**(`tty-shop-monorepo`)时,7 个 Java 文件全部返回: ```json { "notAnalyzedReason": "File is outside project content roots or in an excluded directory", "problems": [] } ``` **`problems` 是空数组。** 若把空数组读成「检查通过」,就会得出「这 7 个文件干净」的结论——而 IDE 根本没看它们。同一窗口里 `pom.xml` 却能正常分析(返回 XML 空标签与 http 链接警告),说明不是 IDE 故障,而是那批 Java 文件确实不在该项目的内容根里: **根项目没有把子项目识别为 Java 项目**,子项目要单独开窗口(`tty-shop-boot-admin`)才是 Java 项目。 ### 由此定的两条硬规矩 1. **`problems: []` 与 `notAnalyzedReason` 是两件事。** 前者是「看过、没问题」,后者是「没看」。任何工具结果为空时,必须先看 `notAnalyzedReason` / `timedOut` / `more` 三个字段,再决定能不能说「干净」。 2. **同一项目里可以一部分文件能分析、另一部分不能。** 不能用一个文件的成功推断另一个文件;连接也必须指向 **真正拥有该 Java 项目**的那个 IDE 窗口(靠 `IJ_MCP_SERVER_PROJECT_PATH` 路由),指向容器根项目会得到整片「未分析」。 这两条把第一原则从「设计理念」变成了有现场证据的规矩。 ## 十三、调用失败的自诊断与一次性重试(v0.6.0,2026-10-28) MCP 工具调用失败时不再直接把错误抛给人,而是走一条确定的诊断链。实现挂在 DSH 的 `tools/execute` waterfall(官方定位就是 retry/metrics)。 ### 判定链 1. 只处理 `mcp__<本插件某连接的 serverName>__*` 的调用,别的工具一律不干预。 2. 失败后**先取服务端工具清单**,比对被调用工具的原始名(去掉 `mcp____` 前缀): - **不在清单**→ `TOOL_MISSING`(工具面已变或名字写错)。**不重试**,并把工具数量与相近名字一并返回。 - **在清单**→ 继续第 3 步。 3. **只有"像传输/会话问题"的失败才允许重试**。白名单(大小写不敏感):`session not found`、`ECONNREFUSED`、`ECONNRESET`、`fetch failed`、`socket hang up`、`timed out`、`timeout`、`EPIPE`。其它错误(参数校验、业务错误)**原样返回,不重试**。 4. 重试动作:就地重挂**那一条**连接 → 等工具重新注册(≤10 秒)→ 用**同一份参数**再 dispatch 一次,把第二次结果作为最终结果。 5. 仍失败:保留失败语义,附「已诊断:工具存在、已重挂并重试一次、仍失败」+ 两次错误原文,交上层决定查文档或报告。 6. 同一次调用最多一次;同一连接 60 秒冷却;所有自动动作进 50 条 ring buffer,经 `GET /config` 的 `diagnostics` 暴露。 ### 为什么白名单这么窄 写操作被悄悄执行两次,比报一次错严重得多。因此**宁可少救一次**:语义不明(超时是否已生效、4xx 是否已产生副作用)一律不重试。`timed out`/`timeout` 进白名单是需求明确要求,但**对写操作语义不确定**,属已知取舍,遇到写工具超时应当人工确认。 ### 顺带的探测调优 - 健康退避上限 120 秒 → **45 秒**(30/45/45/45),`surfaceEvery` 仍为 2 ⇒ 工具面变化最坏 **2×45 ≈ 90 秒**发现。代价约 2.7 倍探测流量(IDEA 的 `tools/list` 一次约 138 KB)。 - `GET /config?probe=surface`:打开设置页或点「刷新状态」时**立即**对所有已挂载连接做一次工具面校验;15 秒轮询不带该参数,避免持续拉大响应。 ### 首屏与校验的顺序(2026-10-28 修) 设置页首屏原本带 `?probe=surface`,等于每次打开都要等所有连接跑完一次全量 `tools/list`——实测**卡「正在读取连接配置…」约 10 秒**才出卡片。这会被误读成"插件坏了"。 改成:首屏只取配置(**166ms 出卡片**),渲染完再在后台发一次 `?probe=surface`,回来只更新挂载状态/健康/诊断(绝不动 rows,避免覆盖未保存的编辑)。实测健康文案在约 7 秒内补齐。 原则:**首屏先能看能改,校验晚几秒无所谓**;慢不是坏,但用户分不出来,所以不能让首屏去等校验。 ### 路由模式下的盲区(2026-10-28 实测后补) 「仅路由模式」下所有调用都走 `execute_tool`,真实工具名藏在 `command` 参数字符串里。最初的实现只比对被调用工具名(`execute_tool`)——它当然存在——于是拼错子命令会被判成 `not-transport`,一个提示都不给。 更麻烦的是:路由会话的 `tools/list` **只列 `execute_tool` 一个**,拿它比对子命令必然误判(合法子命令也会被报"不存在")。真正权威的清单只出现在 IDE 的报错文本里:`Tool 'x' not found. Available tools: …`(实测列出 66 个)。 修正后的判定: 1. 被调用工具是 `execute_tool` 时,取 `command` 的首个 token 作为子命令名; 2. 从失败文本里解析 `Available tools:` 清单; 3. **只有解析到清单**、且子命令不在其中时,才判 `tool-missing`,并给出相近名字; 4. 解析不到清单就**不猜**,照常走传输判定。 实测: - 合法子命令 `get_project_modules` → 正常返回,无任何附加提示; - 拼错 `get_problemz` → 报错后附「路由器工具 `mcp__idea__execute_tool` 的子命令「get_problemz」不在 IDE 的工具清单中(现有 66 个;相近名字:get_project_dependencies、get_project_modules、get_all_open_file_paths、get_database_object_description、get_file_problems)」,且不重试; - `diagnostics` 记为 `verdict:"tool-missing", router:true, inner:"get_problemz"`。 ### 实测(子代理夹具 + 本机) - 工具面变化:base 5s 时 9.7–21.8 秒发现,随后自动重挂并注册新工具,全链路 30–37 秒。 - 重试全链路:`Session not found` → 诊断 → 重挂 → 重试成功(156–202 ms,`tools/call` 2 次、`restarts` +1)。 - 反例:参数错误**未**触发重连与重试(`tools/call` 仅 1 次、`restarts` 不变)。 - 失败保留:重试仍失败时 `isError` 与两次错误原文都在。 ## 十四、工程形态:双半 TS 源码 + tsc 产物(2026-10-28) 两个半端都改为 TypeScript 源码,用 `tsc` 编译出包根产物,行为由构建保证而不是靠人工同步。 | 源码 | 产物 | 说明 | |---|---|---| | `src/index.ts` | `index.js` | Host 半。允许裸 `import`(`node:*` 与 `@deepseek-ai/*`),Node ESM 直接加载 | | `src/client.tsx` | `client.js` | 客户端半。浏览器直接加载的工厂脚本,**产物里不许出现任何 `import`** | 关键构建约定: - **JSX 用经典运行时**(`jsx: "react"` + `jsxFactory: "h"`)。宿主通过 `require` 注入 `react`,裸 ESM 解析不了 `react/jsx-runtime`,用自动运行时会直接加载失败。 - `@deepseek-ai/*` 是 peer,工程内没有 node_modules 供解析;在 `src/env.d.ts` 用**类型 shim** 顶上(只声明形状,不伪造运行期实现)。 - `types: ["node"]` + `devDependencies: @types/node` 供 Host 使用 `process`/`Buffer`/`node:*`。 - 产物入库:linked 安装不现场构建,`pnpm build` 只在开发期跑。`pnpm-lock.yaml` 不入库(该目录 `pnpm install` 会因上游包未发布而 404,lock 无法再生成/校验)。 - **四个文件 IDE 静态检查均为 0**:`src/index.ts`、`index.js`、`src/client.tsx`、`client.js`。 ### 迁移时踩到的两个具体点 1. `typescript@5.0.2` 的 `lib.dom` 缺 `AbortSignal.any`,编译直接失败;升到 5.9.3 才通过。 2. 删形参必须同步删 JSDoc,否则 IDE 会立刻报「JSDoc 中描述的形参未出现在函数签名中」——改签名不等改文档,这条正是「用 IDE 检查自己」抓出来的。 ## 十五、下一轮:删除 / 移动 / 符号改名走 IDE(计划,2026-10-28 立项) ### 动机 删除文件现在只能走 shell(`Remove-Item` / `rm`),IDE 完全不知情——既不受「读写走 IDE」那条策略约束,也没有 Local History 可回退。而 IDE 侧其实已经具备这些能力。 ### 可用的 IDE 能力(已核对官方文档副本) | 需求 | IDE 工具 | 说明 | |---|---|---| | 删除文件 | `apply_patch` 的 **Delete** 操作 | 路径必须在项目内 | | 移动/重命名文件 | `apply_patch` 的 update + **move-to** | 同上 | | 符号改名 | `rename_refactoring` | **语义重构**:全项目更新引用,不是文本替换 | `rename_refactoring` 是 DSH 目前完全做不到的:它按符号结构改名并同步所有引用,价值可能高于删除本身。 ### 计划做什么 1. 新增两个显式工具:`ide_delete`(走 `apply_patch` Delete)与 `ide_move`(走 `apply_patch` move-to); 2. 符号改名通过 `rename_refactoring` 暴露; 3. 在 `write` / `edit` 的工具描述里写明「项目内文件优先用 IDE 工具」,让模型有正当路径,而不是默认掏 shell。 ### 必须写明的边界(否则会产生虚假安全感) - **shell 里的删除不受此策略约束**:模型若通过 `pwsh Remove-Item` 或 `bash rm` 删除,IDE 不知情,Local History 也救不了。这条要同时写进工具描述与本文档。 - **不是权限模型升级**:MCP server 用的是 IDE 进程同一个操作系统用户身份。它给的是**范围受限(必须项目内)+ 可撤销**,不是 OS 级隔离。不要宣称"更安全"而不加限定。 ### 前置条件 先让「读写走 IDE」(第十四轮,v0.8.0 起)落地跑稳,再叠这一轮——两条策略共用同一套路由与失败语义,同时改容易互相掩盖问题。 ## 十六、「读写走 IDE」落地(v0.8.0,2026-10-28) 设置页顶部新增全局开关,存在自有存储的 `ideIo.enabled`(默认关)。开启后 Host 在 `tools/execute` 里拦 `read` / `write` / `edit`,全部改走该连接对应的 IDE 工具;关闭则原样走磁盘。**开启要求至少一条 `enabled: true` 的连接**,否则 Host 返 400、界面拦下并提示。 路由规则:按目标文件绝对路径与各启用连接 `projectPath` 做**最长前缀匹配**;并列或无匹配且多条启用即明确失败;无匹配但恰好一条启用则兜底使用(`diagnostics` 记 `fallback:true`)。**任何失败都不回落磁盘**,并给出三种解法。 ### 五条实测发现(都改变了实现决定) **1. `apply_patch` 不做歧义检查——多处相同文本时静默改第一处并报成功。** 本仓库实测:文件里 `same`+`Y` 出现两次,patch 只给了这两行作上下文,返回 `1 out of 1 operations applied`,实际改的是**第一处**,第二处原样。 > 因此 `edit` 必须先 `search_text` 定位做安全闸,**不能**「先盲打 patch、失败再搜」。后者会静默改错地方,属于最危险的一类失败。 这与本文档早期写的「apply_patch 失败 → search_text 自纠」顺序相反:**顺序反了会改错代码**,以本节为准。 **2. 官方文档与本机实现不一致。** 文档副本(2026.2 在线版)写 `read_file` 用 `mode` / `start_line` / `end_line`;本机安装的 **2026.2.3** 里实际签名是 `(file_path, offset, limit)`,传 `mode` 会被丢弃。结论:**契约以本机实测为准,文档只能当参考**;引用时注明"文档 2026.2 / 本机 2026.2.3"。 **3. EOL 会漂移。** 开关开启后,写入的换行由 **IDE 的项目设置**决定(本机实测写出了 CRLF,字节末尾 `13,10`),而原生 `write` 是 LF。与本仓库「代码 LF」的约定冲突:`.gitattributes` 能归一索引,但工作区文件会变成 CRLF。开启该开关时要意识到这点。 **4. 读到的可能是 IDE 缓冲区,不是磁盘。** 开关开启后读的是 IDE 视角:若文件在 IDE 里打开且有未保存修改,读回的是缓冲区内容(实测 640 行 vs 磁盘 763 行)。这是「IDE 是唯一事实」的应有行为,但必须提前说清,否则「读到的和磁盘不一样」会被当 bug。 **5. `write` 仍有一次内部 `read_file`。** 为生成原生 `before` 与判断是新建还是更新,`write` 内部会读 offset 1 / limit 5000;内容不进模型上下文,但 >5000 行的文件其 `before` 会带截断标记。要彻底零读取需牺牲 `before` 的原生等价性,目前不这么做。 ### `edit` 的预算(代码常量) `EDIT_CONTEXT_LINES=3`、`EDIT_CANDIDATE_LIMIT=10`、`EDIT_READ_LINE_BUDGET=200`、`EDIT_PATCH_ATTEMPTS=2`。 实测:双候选且仅一处吻合 → 总读 16 行、patch 1 次即中;双候选上下文完全相同 → 明确失败、0 次 patch、文件未变;候选 12 处超上限 → **0 读 0 patch** 直接失败;单候选但行内子串 → patch#1 失败、读 3 行、patch#2 成功。每步都进 `diagnostics`(`attempt/candidates/linesRead/readRanges/chosen`)。 ### 已知未做 真实 IDEA 未保存缓冲区下的 `edit`、多 IDE 路由、`read_image` 未拦截、空文件与"仅含一个换行"的文件无法区分。 ## 变更记录 | 版本 | 日期 | 变更 | |------|------------|--------------------------------------------------------------------------------| | v0.2 | 2026-10-27 | 迁入软工目录;折入五条批复;改为缺口驱动 | | v0.3 | 2026-10-27 | 补入阶段 0 实测结论与第九节记录;内容根前提与端口风险入档;细化第一缺口 | | v0.4 | 2026-10-27 | 多 IDE 端口实测(互不抢占、无派生公式)入档;路由待明确项改写为按 IDE 分别注册 | | v0.5 | 2026-10-27 | 重启实测:端口稳定(仍 64342)但 HTTP 会话失效;补「工具在 ≠ 可调用」与恢复手法 | | v0.6 | 2026-10-27 | 新增第十节:Host+Client 双半插件、设置页机制纠正、新风险入档 | | v0.7 | 2026-10-27 | 新增第十一节:IDEA 自动落盘的前提修正、写读通道、数据库借道、契约/实测标注 | | v0.8 | 2026-10-28 | 定「不自研 JetBrains 插件」为交付形态;阶段 4 与安装闭环判据作废;补「读文件也走 IDE read_file」 | | v0.9 | 2026-10-28 | 新增第十二节:对照实验结论(样本无分歧)+「空 problems ≠ 干净」的两条硬规矩 | | v0.10 | 2026-10-28 | 新增第十三节:调用失败的自诊断与一次性重试(白名单/冷却/ring buffer)+ 探测退避上限 45s 与打开设置页强制校验 | | v0.11 | 2026-10-28 | 补路由模式下的诊断盲区修正:从 IDE 报错文本解析可用工具清单,解析不到就不猜 | | v0.12 | 2026-10-28 | 新增第十四节(双半 TS 工程形态);修正设置页首屏被工具面校验阻塞的问题(166ms 出卡片) | | v0.13 | 2026-10-28 | 新增第十五节:下一轮把删除/移动/符号改名也走 IDE,并写明「shell 删除不受约束」与「不是权限升级」两条边界 | | v0.14 | 2026-10-28 | 新增第十六节:读写走 IDE 落地;记录五条实测发现(apply_patch 不查歧义、文档与本机签名不一致、EOL 漂移、缓冲区优先、write 内部读取) |