Skip to content

Releases: MCviseron/dsh-eda-mcp

Release list

v0.5.1 - v0.1.0

Choose a tag to compare

@MCviseron MCviseron released this 01 Oct 16:15

0.5.1

修复:官方桌面端(DSH 0.2.0-rc.2)里 bridge 子进程根本起不来

  • 根因:桌面端把插件宿主跑在 Electron 里,process.execPath 是 D:\DeepSeek Harness\DeepSeek Harness.exe 而不是 Node。旧代码 command: process.execPath 等于让应用二进制去执行 lib/bridge.mjs——要么启动第二个实例、要么被单实例锁挡掉后立刻退出,于是 39009 从来没有监听:设置卡片在、/test 恒 503、Agent 也拿不到 mcp__jlceda__* 工具。
  • 修复:新增 src/node-runtime.ts,按六级顺序挑真正能跑 Node 脚本的可执行文件:
    1. DSH_EDA_MCP_NODE 显式覆盖;
    2. DSH_NODE_EXECUTABLE / DSH_DESKTOP_NODE_EXECUTABLE(+ ELECTRON_RUN_AS_NODE=1);
    3. 宿主是 Node → process.execPath;
    4. Electron 宿主 → 桌面负载自带 resources/runtime/primary-runtime/dependencies/node/bin/node.exe;
    5. PATH 里的 node;
    6. 兜底 process.execPath + ELECTRON_RUN_AS_NODE=1。
  • 子进程 cwd 从 process.cwd()(GUI 应用里可能是 C:\Windows\System32)改为 bridge 脚本所在目录。
  • /api/dsh-eda-mcp/test 与 /reconnect 的响应新增 launch: { command, args, source },直接暴露「用的哪个可执行文件、依据哪条规则」,不用再靠猜。

修复(第二轮):可执行文件选对之后,MCP 客户端仍然没有 spawn

桌面端重启后 launch 已证明命令正确(desktop runtime Node + lib/bridge.mjs),但 /test 依旧 503;用进程监视盯住整次 /reconnect,没有任何 node.exe 子进程被创建过 —— 问题在挂载,不在命令。

  • 0.2.0-rc.2 的契约(从 app.asar 提取真实 runtime 读出):@deepseek-ai/dsh-mcp-client 的 inject = ["tools"](tools 由根级行 @deepseek-ai/dsh-tools 的 super(ctx, "tools") 提供),所以它只会在「工具注册表可达」的上下文里 apply;此外它给 serverName 加了一把唯一性守卫——同一个 owner 作用域里 jlceda 还没随旧 fiber 释放就被再次占用,就直接抛 serverName "jlceda" is already in use。而 fiber.dispose() 是异步的,插件的 sync() 在启动阶段会跑两次 remountClient(),第二次完全可能撞上守卫;再叠加 failOnStartupError: false,错误只进 host logger(桌面端不落盘),于是表现为「什么都没发生」。
  • 修复:remountClient() 改为串行队列 + await 上一次 fiber 销毁;MCP 客户端改到 ctx.inject(['tools'], …) 内挂载(既等待服务可见,又能记录它是否出现);所有挂载异常写入诊断记录,不再静默。
  • 新增自检路由(这次排查缺的就是它):
    • POST /api/dsh-eda-mcp/diagnostics → { launch, diagnostics, config },其中 diagnostics 记录 toolsServiceSeen / mountAttempts / mountError / clientFiberState / bridgeHealth / probeError / probedAt;
    • POST /api/dsh-eda-mcp/self-test → 从插件宿主进程内部直接 spawn bridge(spawned/exited/exitCode/stderr/stdout/healthz/spawnError/pid),用来区分「宿主不能启动该可执行文件」和「MCP 客户端没去启动它」。

修复(第三轮,真正的根因):0.2 的 volatile 配置字段是「活值句柄」,不是值

打开 failOnStartupError: true 并捕获 fiber 的 reject 之后,终于拿到被吞掉的错误:

ValidationError: invalid config
  ... but got {"transport":"stdio","serverName":"jlceda","command":"…\\node.exe","args":["…\\bridge.mjs"],
              "env":{"DSH_EDA_MCP_ALLOW_RAW_API":"1","DSH_EDA_MCP_TOOL_TIMEOUT_MS":"[object Object]"},
              "cwd":"…\\lib","toolCallTimeoutMs":{},"failOnStartupError":true,…}
  • 根因:DSH ≥ 0.2 把标了 .volatile() 的配置字段交给 apply() 时不是普通值,而是活值引用。@deepseek-ai/cosmokit 的定义是 const write = Symbol.for("cosmokit.volatile.write")、句柄为 Object.freeze({ get: () => current, [write]: (v) => … }),判定函数是 isVolatile(v) = typeof v === "object" && v !== null && write in v(Symbol 键);@deepseek-ai/dsh-settings 用 isVolatile(v) ? v.get() : v 拆包。插件直接当值用 → 数字字段变成 {}、字符串化成 "[object Object]" → mcp-client 的配置校验失败,连接从头到尾没有发起;failOnStartupError: false 又把这个错误只写进 host logger(桌面端不落盘),于是一直表现为「重连失败: bridge-unreachable」而没有任何提示。这也解释了 /diagnostics 里 config 四个字段全是 {}。
  • 修复:新增 src/config-values.ts —— 标记用 Symbol.for('cosmokit.volatile.write') 从全局符号注册表取(因此不依赖 cosmokit 包本身,也不依赖宿主导入的是哪一份);提供 isLiveValue / readConfigValue / unwrapConfig 以及带默认值的 booleanField / numberField。apply() 的 current() 与 setSource 两条路径统一解包,bridgeLaunchFor() 与 sync() 改用带默认值的读取器——即使将来设置代际再换包装,也只会退回默认值,而不会把一个对象塞进会做类型校验的消费者。/diagnostics 新增 launchConfig,直接暴露交给 mcp-client 的那份配置。
  • 中途踩的坑(已修):第一版判定写成 'write' in value(字符串键),对 Symbol 键恒为 false,于是解包没生效 —— 重启后 /diagnostics.launchConfig 仍是 toolCallTimeoutMs: {},这才定位到 Symbol。test/config-values.test.ts 现在按 cosmokit 的真实构造(Object.freeze + Symbol.for 键)造句柄。
  • failOnStartupError 保持 true:bridge 是本插件自己的脚本,启动失败就该抛出(而不是静默重试到放弃)。

验证

  • 单测:test/config-values.test.ts 14 例全过(含「有 get 但没有协议 Symbol 的普通对象不得误拆」「booleanField/numberField 对不可读包装退回默认值」);test/node-runtime.test.ts 7 例全过(Node 宿主 / Electron 找自带 Node / 覆盖变量 / 启动器变量 / 不存在的覆盖值 / PATH 回退 / Electron 兜底);按真实桌面布局(execPath = D:\DeepSeek Harness\DeepSeek Harness.exe)解析出 …\primary-runtime\dependencies\node\bin\node.exe,source = desktop runtime Node。
  • 真实启动:用该可执行文件跑 lib/bridge.mjs → http://127.0.0.1:39009/healthz 返回 {"status":"ok","version":"0.3.5","clients":0},进程结束后端口关闭。
  • 0.2.0-rc.2 接缝逐条核对(从 app.asar 提取真实运行时确认):SystemPrompt.section({ name, order, text }) 仍是单对象签名;SettingsForms 的 configure({ auto:false }) + describe/mutate 可用(实测 POST /api/dsh-eda-mcp/settings/describe 返回 writable: true,mutate 提交成功);dsh-mcp-client 的子进程环境是 { ...scrubbedParentEnv(), ...config.env }(合并,不是替换);客户端 inject 的 @deepseek-ai/dsh-client-ui-settings@0.2.0-rc.2、dsh-client-locale@0.2.0-rc.2 都在桌面负载里。
  • tsc --noEmit 与 node build.mjs 通过。
  • 桌面端实测(用户重启后):POST /api/dsh-eda-mcp/test → {"ok":true,"health":{"status":"ok","version":"0.3.5","clients":1}};/diagnostics → config 为明文 {enabled:true, announceToAgent:true, toolCallTimeoutMs:60000, allowRawApi:true}、launchConfig.toolCallTimeoutMs=60000、env.DSH_EDA_MCP_TOOL_TIMEOUT_MS="60000"、bridgeHealth:"ok"、clientFiberState:2(active);插件管理器 /failures 为空、dsh-eda-mcp 行 enabled:true @0.5.1;会话内 mcp__jlceda__* 命名空间(81 个工具)已注册。
  • 收尾小修(下次重启自然生效):beginMount() 现在会清掉上一轮的 clientStartupError(否则启动期重挂竞争会留下一条与当前健康连接并存的旧错误,例如 cannot create effect on inactive context);apply() 末尾的兜底 sync() 只在设置接缝没有回调过时才执行,避免启动时多做一次重挂。

已知限制

  • 宿主模块热重载不会重新 import 已缓存的 ESM:试过两条路都不行 —— 插件管理器 set-enabled 停用/启用(会写 profile patch 并让宿主重挂载该行,但模块仍走缓存),以及把 package.json 的入口换成新文件名(换 URL 本应触发重新 import,实测宿主仍执行旧模块)。改了 src/ 必须 node build.mjs + 重启桌面应用。
  • 第二轮挂载修复(inject(['tools']) + 串行销毁)尚未在运行中的应用里验证,需要一次重启;重启后 /diagnostics 与 /self-test 会直接给出结论。
  • 本版只改宿主端;bridge(0.3.5)与 EDA 扩展(0.3.5)不变。扩展侧仍缺 clearRouting 后台任务类型的那一版需要重新导入 zip 才能拿到「异步清布线」。

0.5.0

兼容 DSH 0.2.0-rc.x 与官方桌面端(同一份构建继续支持 0.1.1-rc.x / 0.1.5-rc.x)

  • 设置接缝三代表探测:0.2 起 ctx.settings 是 SettingsForms,插件的命名空间就是它在 profile 里的 entry Config,installSection/register 都已移除。现在按 0.2 configure({ auto: false }) → 0.1.5 installSection → 0.1.1 register 依次探测;0.2 下关掉自动生成的设置页,插件自带的卡片仍是唯一入口。
  • Config 字段标记 .volatile():0.2 只把标了 volatile 的字段投影成可编辑表单(SettingsForms.describe() 会跳过其余字段),四个设置项全部标记;该标记在旧接缝上只是 schema 元数据。
  • schema 依赖换成 @deepseek-ai/schemastery:与各代运行时加载插件 Config 所用的包一致(原为不带 scope 的 schemastery)。
  • 桥接路由改为结构类型:@deepseek-ai/dsh-settings 的 SettingsProvider 类型在 0.2 已不存在,src/routes.ts 改用本地 SettingsBridge/SettingsBridgeView(describe/mutate/writable),ctx.settings 在调用点显式转型——一份代码同时通过两代类型检查。
  • 设置冲突判定跨副本安全:插件自带的 @deepseek-ai/dsh-settings 副本与宿主实际抛错的副本不是同一份,instanceof 恒为 false;现在同时检查 error.code === 'SETTINGS_CONFLICT'。
  • 客户端类型本地化:SettingsScope/SettingsScopeSnapshot 在 0.2 已改名 ConfigForm/ConfigFormSnapshot,卡片改用 src/client/settings-scope.ts 内声明的结构类型(成员与两代一致)。
  • 对等依赖范围扩到 ^0.1.1-rc.2 || ^0.1.5-rc.2 || ^0.2.0-rc.2;构建设置新增 zustand@4.4.7、immer@10.2.0(内联快照存储的构建期依赖,不进入运行时外部清单)。

未改动(刻意保持)

  • 工具集(69 个)、MCP/bridge 协议、WebSocket 端口 39009、扩展白名单、设置卡片字段与交互全部与 0.4.1 相同;bridge 与 EDA 扩展版本号仍为 0.3.5,无需重新导入 zip。

验证

  • 0.1.5-rc.2 与 0.2.0-rc.2 两套 tsc --noEmit 与构建全部通过;客户端 bundle 的运行时依赖仍只有 react/react/jsx-runtime。
  • 在隔离 DSH_HOME 里用 0.2 运行时启动 web profile:插件加载无任何报错,POST /api/dsh-eda-mcp/settings/describe 正确返回 schema/value/base/user/secrets/writable;/api/dsh-eda-mcp/test 正常响应。
  • 跨代 mcp-client 兼容性已核对:0.1.5 与 0.2 的 ctx.tools.register(definition) 签名、ToolDefinition 形状一致(0.2 仅新增可选 projectContent),因此插件自带的 0.1.5 dsh-mcp-client 副本在 0.2 宿主上可用。

升级注意

  • 0.2 上本插件设置由 profile entry Config 承载;旧 ~/.dsh/settings.yaml 会在 0.2 首次启动时被迁移/改名(settings.yaml.imported),eda-mcp 一节会迁进当时运行的那个 profile。

0.4.1

bridge 与 EDA 扩展版本:0.3.4 → 0.3.5(扩展白名单与内部助手有新增,需要重新导入 zip 并重启嘉立创EDA专业版)。

自动布线/自动布局:改为「启动 + 轮询」,不再用超时硬等

  • 之前 eda_pcb_auto_route 会一直等到布线结束,整板或大网络(GND 35+ 引脚)必然撞上 MCP 工具超时——而 EDA 侧其实还在正常布线并显示进度条,只是调用方已经超时放弃了。
  • 现在默认异步启动:扩展内部用 dsh.internal.jobStart 发起 pcb_Document.autoRouting 但不 await,把 promise 状态存起来立刻返回;新增 eda_pcb_job_status(dsh.internal.jobStatus)轮询 {running, elapsedMs, result, error}。
  • eda_pcb_auto_route:默认异步(返回 started/running/requestedNets/props 与提示),wait: true 才阻塞;eda_pcb_auto_place 默认阻塞、wait: false 异步。
  • 因此不需要再加大超时;长任务用轮询。timeoutMs 参数保留给 DRC/Gerber 等同步调用。
  • 任务结束时 hint 会给出下一步(success=false/duration=0 的解释、未布通网络列表、建议跑 DRC 与加宽电源网络)。

网络标签:createNetLabel + 文本回退

  • 新增 eda_place_net_label(sch_PrimitiveAttribute.createNetLabel(x, y, net))与 eda_get_net_labels;eda_place_net_label_at_pin 改为优先创建真正的网络标签图元,返回 method: 'netLabel' | 'text'。
  • createNetLabel 官方标注「ADD since EDA v4」,当前 EDA 3.2.186 上可能不存在;不存在时自动回退到文本标签,并在结果里明确警告「文本标签不建立电气连接,请改用 eda_draw_wire 的 net 参数」。

eda_get_nets 返回空的定位与修复

  • 实测:sch_Net.getCurrentProjectAllNets / sch_Net.getAllNets / sch_Net.getAllNetsName 在当前活动文档不是原理图时全部返回 [](sch_ManufactureData.getNetlistFile 同时返回 null)。这是「查询作用于当前活动文档」造成的,不是网表坏了。
  • 新增 eda_get_active_document(dmt_SelectControl.getCurrentDocumentInfo,返回 documentTypeName)与 eda_open_document(dmt_EditorControl.openDocument),可以在调用前把原理图/PCB 切到前台。
  • eda_get_nets 现在按 getCurrentProjectAllNets → getAllNets → 解析 sch_Netlist.getNetlist 依次降级,并返回 source 与提示;eda_export_netlist 为 null 的说明也写进了工具描述。

覆铜 / PCB DRC / 电源线宽

  • eda_pcb_create_pour / eda_pcb_get_pours / eda_pcb_delete_pour:pcb_MathPolygon.createPolygon + pcb_PrimitivePour.create(内部助手 dsh.internal.pourCreate),pourFillMethod 支持 45grid / 90grid / solid,铜层 1/2/15-44。
  • eda_pcb_run_drc(pcb_Drc.check,strict / userInterface / includeVerboseError)、eda_pcb_get_drc_rules(当前规则配置名与内容、实时 DRC 状态)。
  • `eda_pcb_set_n...
Read more