Repository navigation
Releases: MCviseron/dsh-eda-mcp
Releases · MCviseron/dsh-eda-mcp
Release list
v0.5.1 - v0.1.0
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 脚本的可执行文件:DSH_EDA_MCP_NODE显式覆盖;DSH_NODE_EXECUTABLE/DSH_DESKTOP_NODE_EXECUTABLE(+ELECTRON_RUN_AS_NODE=1);- 宿主是 Node →
process.execPath; - Electron 宿主 → 桌面负载自带
resources/runtime/primary-runtime/dependencies/node/bin/node.exe; PATH里的node;- 兜底
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.ts14 例全过(含「有get但没有协议 Symbol 的普通对象不得误拆」「booleanField/numberField对不可读包装退回默认值」);test/node-runtime.test.ts7 例全过(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.2configure({ auto: false })→ 0.1.5installSection→ 0.1.1register依次探测;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.5dsh-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...