dsh-eda-mcp 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.2 — 2026-10-20
4
+
5
+ ### 覆铜调查结案(扩展 0.4.7 / 0.4.8 真机)
6
+
7
+ 结论:**这台构建(EDA 3.2.186 / pro-api 0.3.18.0bc12bb0)无法通过 API 创建铜面图元**。不是参数问题,证据链如下。
8
+
9
+ | 尝试 | 结果 |
10
+ |---|---|
11
+ | `pcb_PrimitivePour.create` 静态入口 × 8 种组合(单/复杂多边形、原始源码数组、空网络、层 1/2/字符串、全参数显式、100×100 与 300×300 mil) | 全部 `无法创建覆铜边框图元` |
12
+ | 实例入口 `new IPCB_PrimitivePour(...).create()`(0.4.7 新增,验证构造器在运行时可取) | 同样报错 |
13
+ | 填充方式 `solid`/`45grid`/`90grid`/`0`/`1`/`2`/`SOLID`/`GRID45`/`45GRID` | 全部报错 |
14
+ | `pcb_PrimitiveFill.create`(文档说明它同样带 `net` 参数)× 4 种(对象/原始数组/复杂源码/显式 fillMode) | 全部 `无法创建填充图元` |
15
+ | **控制组**:同一多边形管线写丝印折线 `pcb_PrimitivePolyline` / `pcb_draw_polyline` | 也写不出来(`getAll` 0 条),而 `pcb_get_polylines` 内部路径能读到既有板框 1 条 |
16
+
17
+ 外部资料也到此为止:`prodocs` 正文是前端渲染(HTML 只有导航),官方配方给的流程就是我们实现的那条(`createPolygon`/`createComplexPolygon` → pour → `rebuildCopperRegion`),搜 "无法创建覆铜边框图元" 无任何社区解法,应用 bundle 内也搜不到该字符串。
18
+
19
+ **因此撤掉 `pcb_create_copper_fill`**:它在这台构建上必然失败,留着就是给调用方挖坑。白名单里的 `pcb_MathPolygon.createComplexPolygon` 与 `pcb_PrimitiveFill.*` 保留(换构建可能有效),但不再包装成工具。
20
+
21
+ **正解**:覆铜在界面手工铺(GND → 顶层+底层 → 重新铺铜);`eda_design_audit` 会持续把缺覆铜记成 `copper-pour` warn,`eda_delivery_checklist` 也会带上它。
22
+
23
+
24
+ ### 通知堆叠 / 粘连(扩展 0.4.9)
25
+
26
+ 屏幕实测:三条提示叠在画布上("未连接…点击右侧按钮"、"扩展已加载 v0.4.8"、"已连接;端口 39009"),且"未连接"那条永不消失。
27
+
28
+ - 根因:`showToastMessage` 的 `timer: 0` 语义是**永不自动隐藏**,而 `SYS_Message` **没有任何隐藏或替换 API**;连接成功时也没有人撤掉它。
29
+ - 修法:未连接提示改成 `showFollowMouseTip(tip, 8000)`,并在 WebSocket `onopen` 里 `removeFollowMouseTip` 显式撤掉;`showToast` 一律传有限时长(默认 4 s);文案改为"菜单 DSH MCP → 连接 DSH MCP 服务器"(跟随提示没有按钮)。
30
+ - 线协议 **0.4.8 → 0.4.9**(纯 UI 层)。
31
+
3
32
  ## 1.0.1 — 2026-10-20
4
33
 
5
34
  1.0.0 的真机回归修掉 4 个 bug(全部由"跑一遍"发现,不是靠读代码):
@@ -146,7 +146,7 @@
146
146
  | eda_launch 带 project 没打开工程 | 命令行路径被应用忽略 | 用 eda_open_project({ name }) 打开 |
147
147
  | 关应用后改动丢失 | eda_close 走了强制路径 | 关前先保存;看返回的 forced |
148
148
  | run_code 读大 JSON 被截断 | 宿主 read 有单行上限 | 用 node:fs 读盘或分片读 |
149
- | 覆铜创建失败(无法创建覆铜边框图元) | 应用侧限制:polygonStage=ok(轮廓被接受)但 pours 被拒,与网络/填充方式/坐标无关 | 工具会重试一次并给出该诊断;手工在 PCB 页框选覆铜(GND,顶层+底层),design_audit 记为 warn |
149
+ | 覆铜/填充一律被拒(无法创建覆铜边框图元 / 无法创建填充图元) | 应用侧限制:静态+实例入口、单/复杂多边形、原始源码数组、6 种填充枚举、全参数组合全部被拒;同一多边形管线在本构建上连丝印折线都写不出来 | 不要在 API 层反复试:覆铜请在界面手工铺(GND、顶层+底层);design_audit 持续记 copper-pour warn |
150
150
  | pcb_draw_mounting_hole 报“无法创建焊盘图元” | pad 形状必填,且槽孔只能用 ELLIPSE | 已内建:圆孔 ELLIPSE(d,d),槽孔 ELLIPSE(长,宽)+hole[SLOT,宽,长];OBLONG/RECTANGLE 焊盘会被拒 |
151
151
  | pcb_PrimitiveString.getAll / pcb_PrimitivePad.getAll 返回 0 | 这两个 API 按层查询 | 传 layer:丝印 3/4,多层焊盘 12;不传参恒为 0 |
152
152
  | 按坐标算的越界没被发现 | 图框/原点假设与真实页面不一致 | 用 eda_get_page_info 的真实值 + eda_capture 导 PNG 看实际版面 |
@@ -212,6 +212,9 @@ function dshEdaMcpStatus() {
212
212
  'pcb_PrimitivePad.create',
213
213
  'pcb_PrimitivePad.getAll',
214
214
  'pcb_PrimitivePad.delete',
215
+ 'pcb_PrimitiveFill.create',
216
+ 'pcb_PrimitiveFill.getAll',
217
+ 'pcb_PrimitiveFill.delete',
215
218
  'pcb_PrimitivePour.create',
216
219
  'pcb_PrimitivePour.modify',
217
220
  'pcb_PrimitivePour.delete',
@@ -459,21 +462,54 @@ function dshEdaMcpStatus() {
459
462
  : undefined;
460
463
  if (!pourPolygon) pourPolygon = eda.pcb_MathPolygon.createPolygon(pourSource);
461
464
  if (!pourPolygon) throw new Error('dsh.internal.pourCreate: invalid polygon source ' + JSON.stringify(pourSource));
462
- var pour = await eda.pcb_PrimitivePour.create(
463
- optional(pourArgs[0]), optional(pourArgs[1]), pourPolygon, optional(pourArgs[3]), optional(pourArgs[4]),
464
- optional(pourArgs[5]), optional(pourArgs[6]), optional(pourArgs[7]), optional(pourArgs[8])
465
- );
465
+ // Route 1: the documented static factory.
466
+ var pourRoutes = [];
467
+ var pour = null;
468
+ var usedRoute = 'static';
469
+ try {
470
+ pour = await eda.pcb_PrimitivePour.create(
471
+ optional(pourArgs[0]), optional(pourArgs[1]), pourPolygon, optional(pourArgs[3]), optional(pourArgs[4]),
472
+ optional(pourArgs[5]), optional(pourArgs[6]), optional(pourArgs[7]), optional(pourArgs[8])
473
+ );
474
+ } catch (staticError) {
475
+ pourRoutes.push('static: ' + (staticError && staticError.message ? staticError.message : String(staticError)));
476
+ }
477
+ // Route 2: the instance entry point (@internal in the typings, but the class
478
+ // constructor is exported at runtime on some builds).
466
479
  if (!pour) {
467
- throw new Error('dsh.internal.pourCreate: pcb_PrimitivePour.create returned undefined (net/layer/polygon rejected; copper layers only)');
480
+ var PourCtor = null;
481
+ try {
482
+ if (typeof eda.IPCB_PrimitivePour === 'function') PourCtor = eda.IPCB_PrimitivePour;
483
+ else if (eda.pcb_PrimitivePour && typeof eda.pcb_PrimitivePour.constructor === 'function' && eda.pcb_PrimitivePour.constructor !== Object) PourCtor = eda.pcb_PrimitivePour.constructor;
484
+ } catch (ctorProbeError) { PourCtor = null; }
485
+ if (PourCtor !== null) {
486
+ usedRoute = 'instance';
487
+ try {
488
+ var pourInstance = new PourCtor(
489
+ optional(pourArgs[0]), optional(pourArgs[1]), pourPolygon, optional(pourArgs[3]), optional(pourArgs[4]),
490
+ optional(pourArgs[5]), optional(pourArgs[6]), optional(pourArgs[7]), optional(pourArgs[8])
491
+ );
492
+ pour = await pourInstance.create();
493
+ } catch (instanceError) {
494
+ pourRoutes.push('instance: ' + (instanceError && instanceError.message ? instanceError.message : String(instanceError)));
495
+ usedRoute = 'none';
496
+ }
497
+ } else {
498
+ pourRoutes.push('instance: no exported constructor');
499
+ usedRoute = 'none';
500
+ }
468
501
  }
469
- // A pour border alone leaves no copper: the region has to be rebuilt for the
470
- // fill (and the clearance against other nets) to be computed.
502
+ if (!pour) {
503
+ throw new Error('dsh.internal.pourCreate: every route refused (' + pourRoutes.join(' | ') + ')');
504
+ }
505
+ // A pour border alone leaves no copper: rebuild so the fill (and the clearance
506
+ // against other nets) is actually computed.
471
507
  var rebuilt = false;
472
508
  if (pour && typeof pour.rebuildCopperRegion === 'function') {
473
509
  try { await pour.rebuildCopperRegion(); rebuilt = true; } catch (error) { rebuilt = false; }
474
510
  }
475
511
  var pourJson = toJsonable(pour);
476
- if (pourJson && typeof pourJson === 'object') pourJson.rebuilt = rebuilt;
512
+ if (pourJson && typeof pourJson === 'object') { pourJson.rebuilt = rebuilt; pourJson.route = usedRoute; }
477
513
  return pourJson;
478
514
  }
479
515
  // Internal helper: long-running jobs (auto-routing / auto-layout) are started
@@ -579,6 +615,9 @@ function dshEdaMcpStatus() {
579
615
  function () {
580
616
  wsReady = true;
581
617
  lastPongAt = Date.now();
618
+ // The 'not connected' tip is sticky by design while we are offline; once the
619
+ // socket is up it must go away, otherwise it sits on the canvas forever.
620
+ hideConnectTip();
582
621
  sendObject({ type: 'hello', version: VERSION });
583
622
  }
584
623
  );
@@ -599,10 +638,11 @@ function dshEdaMcpStatus() {
599
638
  registerWebSocket();
600
639
  }
601
640
 
602
- function showToast(message, type) {
641
+ function showToast(message, type, seconds) {
603
642
  try {
604
643
  if (typeof eda !== 'undefined' && eda.sys_Message && typeof eda.sys_Message.showToastMessage === 'function') {
605
- eda.sys_Message.showToastMessage(message, type || 'info');
644
+ // Always pass a finite timer: omitting it (or 0) leaves the toast on screen forever.
645
+ eda.sys_Message.showToastMessage(message, type || 'info', seconds || 4);
606
646
  } else {
607
647
  console.log('[dsh-eda-mcp] ' + message);
608
648
  }
@@ -706,20 +746,30 @@ function dshEdaMcpStatus() {
706
746
  }
707
747
  }
708
748
 
749
+ // One removable tip instead of a stack of toasts: showToastMessage has no hide API
750
+ // (timer:0 meant "never"), so a stack of them used to pile up over the canvas and the
751
+ // '未连接' banner survived even after the connection was established (measured).
752
+ var CONNECT_TIP = 'DSH MCP 未连接:菜单 DSH MCP → 连接 DSH MCP 服务器(ws://127.0.0.1:39009/ws)';
753
+
754
+ function hideConnectTip() {
755
+ try {
756
+ if (typeof eda !== 'undefined' && eda.sys_Message && typeof eda.sys_Message.removeFollowMouseTip === 'function') {
757
+ eda.sys_Message.removeFollowMouseTip(CONNECT_TIP);
758
+ }
759
+ } catch (_) { /* ignore */ }
760
+ }
761
+
709
762
  function showConnectToast() {
710
763
  if (wsReady) return;
711
764
  try {
712
765
  if (typeof eda === 'undefined' || !eda.sys_Message) return;
713
- eda.sys_Message.showToastMessage(
714
- 'DSH MCP 未连接。点击右侧按钮连接本地桥接服务(ws://127.0.0.1:39009/ws)。',
715
- 'warning',
716
- 0,
717
- undefined,
718
- '连接 DSH MCP',
719
- "eda.sys_MessageBus.publishPublic('dsh-eda-mcp/connect', {})"
720
- );
766
+ if (typeof eda.sys_Message.showFollowMouseTip === 'function') {
767
+ eda.sys_Message.showFollowMouseTip(CONNECT_TIP, 8000);
768
+ return;
769
+ }
770
+ eda.sys_Message.showToastMessage(CONNECT_TIP, 'warning', 8);
721
771
  } catch (error) {
722
- console.error('[dsh-eda-mcp] connect toast failed:', error);
772
+ console.error('[dsh-eda-mcp] connect tip failed:', error);
723
773
  }
724
774
  }
725
775
 
@@ -2,7 +2,7 @@
2
2
  "uuid": "d5e0a1b2c3f4456789abcdef01234567",
3
3
  "name": "dsh-eda-bridge",
4
4
  "displayName": "DSH EDA MCP Bridge",
5
- "version": "0.4.6",
5
+ "version": "0.4.9",
6
6
  "description": "Local WebSocket bridge used by the dsh-eda-mcp plugin to let DeepSeek Harness draw schematics and drive PCB operations in JLCEDA Pro through the official extension API.",
7
7
  "entry": "./api-bridge.js",
8
8
  "publisher": "dsh-eda-mcp",
package/lib/bridge.mjs CHANGED
@@ -807,7 +807,7 @@ var WORKFLOW_GUIDE = WORKFLOW_SUMMARY + "\n\n" + [
807
807
  ].join("\n");
808
808
 
809
809
  // src/bridge/index.ts
810
- var VERSION = "0.4.6";
810
+ var VERSION = "0.4.9";
811
811
  var WS_PORT = Number(process.env.DSH_EDA_MCP_WS_PORT ?? 39009);
812
812
  var WS_HOST = "127.0.0.1";
813
813
  var ALLOW_RAW_API = (process.env.DSH_EDA_MCP_ALLOW_RAW_API ?? "1") !== "0";
package/lib/index.js CHANGED
@@ -179,7 +179,7 @@ var SKILL_PLAYBOOK_LINES = [
179
179
  "| eda_launch \u5E26 project \u6CA1\u6253\u5F00\u5DE5\u7A0B | \u547D\u4EE4\u884C\u8DEF\u5F84\u88AB\u5E94\u7528\u5FFD\u7565 | \u7528 eda_open_project({ name }) \u6253\u5F00 |",
180
180
  "| \u5173\u5E94\u7528\u540E\u6539\u52A8\u4E22\u5931 | eda_close \u8D70\u4E86\u5F3A\u5236\u8DEF\u5F84 | \u5173\u524D\u5148\u4FDD\u5B58\uFF1B\u770B\u8FD4\u56DE\u7684 forced |",
181
181
  "| run_code \u8BFB\u5927 JSON \u88AB\u622A\u65AD | \u5BBF\u4E3B read \u6709\u5355\u884C\u4E0A\u9650 | \u7528 node:fs \u8BFB\u76D8\u6216\u5206\u7247\u8BFB |",
182
- "| \u8986\u94DC\u521B\u5EFA\u5931\u8D25\uFF08\u65E0\u6CD5\u521B\u5EFA\u8986\u94DC\u8FB9\u6846\u56FE\u5143\uFF09 | \u5E94\u7528\u4FA7\u9650\u5236\uFF1ApolygonStage=ok\uFF08\u8F6E\u5ED3\u88AB\u63A5\u53D7\uFF09\u4F46 pours \u88AB\u62D2\uFF0C\u4E0E\u7F51\u7EDC/\u586B\u5145\u65B9\u5F0F/\u5750\u6807\u65E0\u5173 | \u5DE5\u5177\u4F1A\u91CD\u8BD5\u4E00\u6B21\u5E76\u7ED9\u51FA\u8BE5\u8BCA\u65AD\uFF1B\u624B\u5DE5\u5728 PCB \u9875\u6846\u9009\u8986\u94DC\uFF08GND\uFF0C\u9876\u5C42+\u5E95\u5C42\uFF09\uFF0Cdesign_audit \u8BB0\u4E3A warn |",
182
+ "| \u8986\u94DC/\u586B\u5145\u4E00\u5F8B\u88AB\u62D2\uFF08\u65E0\u6CD5\u521B\u5EFA\u8986\u94DC\u8FB9\u6846\u56FE\u5143 / \u65E0\u6CD5\u521B\u5EFA\u586B\u5145\u56FE\u5143\uFF09 | \u5E94\u7528\u4FA7\u9650\u5236\uFF1A\u9759\u6001+\u5B9E\u4F8B\u5165\u53E3\u3001\u5355/\u590D\u6742\u591A\u8FB9\u5F62\u3001\u539F\u59CB\u6E90\u7801\u6570\u7EC4\u30016 \u79CD\u586B\u5145\u679A\u4E3E\u3001\u5168\u53C2\u6570\u7EC4\u5408\u5168\u90E8\u88AB\u62D2\uFF1B\u540C\u4E00\u591A\u8FB9\u5F62\u7BA1\u7EBF\u5728\u672C\u6784\u5EFA\u4E0A\u8FDE\u4E1D\u5370\u6298\u7EBF\u90FD\u5199\u4E0D\u51FA\u6765 | \u4E0D\u8981\u5728 API \u5C42\u53CD\u590D\u8BD5\uFF1A\u8986\u94DC\u8BF7\u5728\u754C\u9762\u624B\u5DE5\u94FA\uFF08GND\u3001\u9876\u5C42+\u5E95\u5C42\uFF09\uFF1Bdesign_audit \u6301\u7EED\u8BB0 copper-pour warn |",
183
183
  "| pcb_draw_mounting_hole \u62A5\u201C\u65E0\u6CD5\u521B\u5EFA\u710A\u76D8\u56FE\u5143\u201D | pad \u5F62\u72B6\u5FC5\u586B\uFF0C\u4E14\u69FD\u5B54\u53EA\u80FD\u7528 ELLIPSE | \u5DF2\u5185\u5EFA\uFF1A\u5706\u5B54 ELLIPSE(d,d)\uFF0C\u69FD\u5B54 ELLIPSE(\u957F,\u5BBD)+hole[SLOT,\u5BBD,\u957F]\uFF1BOBLONG/RECTANGLE \u710A\u76D8\u4F1A\u88AB\u62D2 |",
184
184
  "| pcb_PrimitiveString.getAll / pcb_PrimitivePad.getAll \u8FD4\u56DE 0 | \u8FD9\u4E24\u4E2A API \u6309\u5C42\u67E5\u8BE2 | \u4F20 layer\uFF1A\u4E1D\u5370 3/4\uFF0C\u591A\u5C42\u710A\u76D8 12\uFF1B\u4E0D\u4F20\u53C2\u6052\u4E3A 0 |",
185
185
  "| \u6309\u5750\u6807\u7B97\u7684\u8D8A\u754C\u6CA1\u88AB\u53D1\u73B0 | \u56FE\u6846/\u539F\u70B9\u5047\u8BBE\u4E0E\u771F\u5B9E\u9875\u9762\u4E0D\u4E00\u81F4 | \u7528 eda_get_page_info \u7684\u771F\u5B9E\u503C + eda_capture \u5BFC PNG \u770B\u5B9E\u9645\u7248\u9762 |",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-eda-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "DeepSeek Harness (DSH) plugin — connects DSH to JLCEDA Pro (EasyEDA Pro) via a local MCP bridge and draws schematics through the JLCEDA extension API.",
5
5
  "license": "Apache-2.0",
6
6
  "author": {
@@ -21,7 +21,7 @@ import {
21
21
  } from './design-rules.ts'
22
22
  import { WORKFLOW_GUIDE, WORKFLOW_SUMMARY } from './workflow-guide.ts'
23
23
 
24
- const VERSION = '0.4.6'
24
+ const VERSION = '0.4.9'
25
25
  const WS_PORT = Number(process.env.DSH_EDA_MCP_WS_PORT ?? 39009)
26
26
  const WS_HOST = '127.0.0.1'
27
27
  const ALLOW_RAW_API = (process.env.DSH_EDA_MCP_ALLOW_RAW_API ?? '1') !== '0'
@@ -3136,6 +3136,7 @@ async function executeTool(name, rawArgs, timeoutMs) {
3136
3136
  case 'eda_pcb_delete_via':
3137
3137
  return await callExtension('pcb_PrimitiveVia.delete', [String(args.primitiveId)], timeoutMs)
3138
3138
 
3139
+
3139
3140
  case 'eda_pcb_create_pour': {
3140
3141
  const layer = resolveRegionLayer(args.layer, 'eda_pcb_create_pour')
3141
3142
  const points = requireFlatPoints(args.points, 'eda_pcb_create_pour')
@@ -153,7 +153,7 @@ export const SKILL_PLAYBOOK_LINES: readonly string[] = [
153
153
  "| eda_launch 带 project 没打开工程 | 命令行路径被应用忽略 | 用 eda_open_project({ name }) 打开 |",
154
154
  "| 关应用后改动丢失 | eda_close 走了强制路径 | 关前先保存;看返回的 forced |",
155
155
  "| run_code 读大 JSON 被截断 | 宿主 read 有单行上限 | 用 node:fs 读盘或分片读 |",
156
- "| 覆铜创建失败(无法创建覆铜边框图元) | 应用侧限制:polygonStage=ok(轮廓被接受)但 pours 被拒,与网络/填充方式/坐标无关 | 工具会重试一次并给出该诊断;手工在 PCB 页框选覆铜(GND,顶层+底层),design_audit 记为 warn |",
156
+ "| 覆铜/填充一律被拒(无法创建覆铜边框图元 / 无法创建填充图元) | 应用侧限制:静态+实例入口、单/复杂多边形、原始源码数组、6 种填充枚举、全参数组合全部被拒;同一多边形管线在本构建上连丝印折线都写不出来 | 不要在 API 层反复试:覆铜请在界面手工铺(GND、顶层+底层);design_audit 持续记 copper-pour warn |",
157
157
  "| pcb_draw_mounting_hole 报“无法创建焊盘图元” | pad 形状必填,且槽孔只能用 ELLIPSE | 已内建:圆孔 ELLIPSE(d,d),槽孔 ELLIPSE(长,宽)+hole[SLOT,宽,长];OBLONG/RECTANGLE 焊盘会被拒 |",
158
158
  "| pcb_PrimitiveString.getAll / pcb_PrimitivePad.getAll 返回 0 | 这两个 API 按层查询 | 传 layer:丝印 3/4,多层焊盘 12;不传参恒为 0 |",
159
159
  "| 按坐标算的越界没被发现 | 图框/原点假设与真实页面不一致 | 用 eda_get_page_info 的真实值 + eda_capture 导 PNG 看实际版面 |",
Binary file