dsh-eda-mcp 0.7.0 → 0.7.1

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,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.1 — 2026-10-20
4
+
5
+ 构建产物修正:**扩展 zip 只保留带版本号的那一个**。
6
+
7
+ - 0.7.0 里为了不打破旧引用,同时产出了 `dsh-eda-bridge-extension-0.4.0.zip` 和同内容的 `dsh-eda-bridge-extension.zip`(SHA256 完全一致)。两个同内容文件摆在一起只会让人分不清哪份是当前版本,属于多余的冗余,删掉。
8
+ - 现在只生成 `dsh-eda-bridge-extension-<线协议版本>.zip`。所有引用改为**按版本解析**:
9
+ - `src/index.ts`:从 `jlc-extension/extension.json` 读版本号拼出文件名(找不到时回退旧的固定名,兼容旧布局);
10
+ - `src/routes.ts`:下载响应头用实际文件名的 `basename`,浏览器另存下来自带版本号;
11
+ - 设置卡片的下载链接去掉固定的 `download` 属性,改由响应头决定文件名;
12
+ - 桥接里所有提示语改成 `dsh-eda-bridge-extension-' + VERSION + '.zip`;
13
+ - README(中/英)、CONTRIBUTING、`docs/release.md`、扩展头部注释同步。
14
+ - `package.json files` 去掉固定名条目,只保留 `dsh-eda-bridge-extension-*.zip`。
15
+
16
+ 线协议仍是 **0.4.0**,扩展内容未变——已导入 0.4.0 的机器**无需重新导入**。
17
+
3
18
  ## 0.7.0 — 2026-10-20
4
19
 
5
20
  主题:把「操作嘉立创EDA」从**画图**扩展到**应用与工程**层面,并把这一路真机联调暴露的 bug 全部修掉。
package/CONTRIBUTING.md CHANGED
@@ -34,7 +34,6 @@ pnpm install
34
34
  pnpm typecheck # tsc --noEmit
35
35
  pnpm test # unit tests + the seam compatibility test
36
36
  pnpm build # lib/*.js, lib/bridge.mjs, lib/client.js, dsh-eda-bridge-extension-<version>.zip
37
- # (+ dsh-eda-bridge-extension.zip as a stable-name alias of the same build)
38
37
  pnpm check # all three, in that order
39
38
  pnpm docs # regenerate docs/tools.md from the built bridge
40
39
  pnpm seam-check # assert the plugin's DSH contracts against another version line
package/README.md CHANGED
@@ -16,7 +16,7 @@ DSH agent
16
16
  └─ mcp__jlceda__eda_* tools
17
17
  └─ @deepseek-ai/dsh-mcp-client (stdio)
18
18
  └─ lib/bridge.mjs (local MCP + WebSocket server, 127.0.0.1:39009)
19
- └─ JLCEDA Pro extension dsh-eda-bridge-extension.zip
19
+ └─ JLCEDA Pro extension dsh-eda-bridge-extension-<version>.zip
20
20
  └─ official EasyEDA Pro extension API (eda.sch_*)
21
21
  ```
22
22
 
@@ -36,9 +36,9 @@ pnpm add dsh-eda-mcp # or use the plugin manager in the Web GUI
36
36
  # restart DSH
37
37
  ```
38
38
 
39
- The published package already contains `lib/` and `dsh-eda-bridge-extension.zip`, so no build
39
+ The published package already contains `lib/` and `dsh-eda-bridge-extension-<version>.zip`, so no build
40
40
  toolchain is needed. The extension still has to be imported into JLCEDA Pro by hand — the zip
41
- sits at `node_modules/dsh-eda-mcp/dsh-eda-bridge-extension.zip` (or download it from the
41
+ sits at `node_modules/dsh-eda-mcp/dsh-eda-bridge-extension-<version>.zip` (or download it from the
42
42
  settings card).
43
43
 
44
44
  ### 1. Install into the DSH web profile
@@ -71,10 +71,10 @@ The plugin now resolves a real Node executable (`src/node-runtime.ts`), in order
71
71
 
72
72
  ### 2. Import the JLCEDA Pro bridge extension
73
73
 
74
- Build produces `dsh-eda-bridge-extension-<version>.zip` (plus a `dsh-eda-bridge-extension.zip` alias with identical content; **hand out the versioned file** so stale copies are obvious). In JLCEDA Pro:
74
+ Build produces `dsh-eda-bridge-extension-<version>.zip` only — a second copy under the stable name only made it unclear which build you were looking at. In JLCEDA Pro:
75
75
 
76
76
  1. Open **扩展 → 导入** (Extensions → Import).
77
- 2. Select `dsh-eda-bridge-extension.zip`.
77
+ 2. Select `dsh-eda-bridge-extension-<version>.zip`.
78
78
  3. In the extension list find **DSH EDA MCP Bridge**.
79
79
  4. Enable it and, importantly, enable **允许外部交互 / Allow external interactions**.
80
80
 
@@ -188,7 +188,7 @@ Build outputs:
188
188
  - `lib/index.js` — DSH host plugin.
189
189
  - `lib/client.js` — DSH Web GUI settings card.
190
190
  - `lib/bridge.mjs` — standalone MCP/WebSocket bridge child.
191
- - `dsh-eda-bridge-extension.zip` — JLCEDA Pro extension.
191
+ - `dsh-eda-bridge-extension-<version>.zip` — JLCEDA Pro extension (the version is the wire contract).
192
192
 
193
193
  `lib/` and the zip are build outputs and are git-ignored; the host loads the packaged `lib/index.js`, so a change under `src/` needs `pnpm build` plus a Host restart (a plugin reload does not re-import a cached ESM module).
194
194
 
package/README.zh.md CHANGED
@@ -16,7 +16,7 @@ DSH Agent
16
16
  └─ mcp__jlceda__eda_* 工具
17
17
  └─ @deepseek-ai/dsh-mcp-client(stdio)
18
18
  └─ lib/bridge.mjs(本地 MCP + WebSocket 服务,127.0.0.1:39009)
19
- └─ 嘉立创EDA专业版扩展 dsh-eda-bridge-extension.zip
19
+ └─ 嘉立创EDA专业版扩展 dsh-eda-bridge-extension-<版本号>.zip
20
20
  └─ 官方扩展 API(eda.sch_*)
21
21
  ```
22
22
 
@@ -47,8 +47,8 @@ pnpm add dsh-eda-mcp # 或用 Web GUI 的插件管理器安装
47
47
  # 重启 DSH
48
48
  ```
49
49
 
50
- 发布包里已包含 `lib/` 与 `dsh-eda-bridge-extension.zip`,**不需要构建工具链**。扩展仍需手动导入
51
- 嘉立创EDA:zip 位于 `node_modules/dsh-eda-mcp/dsh-eda-bridge-extension.zip`(也可从设置卡片下载)。
50
+ 发布包里已包含 `lib/` 与 `dsh-eda-bridge-extension-<线协议版本>.zip`,**不需要构建工具链**。扩展仍需手动导入
51
+ 嘉立创EDA:zip 位于 `node_modules/dsh-eda-mcp/dsh-eda-bridge-extension-<线协议版本>.zip`(也可从设置卡片下载,下载到的文件名自带版本号)。
52
52
 
53
53
  ### 1. 安装到 DSH Web profile
54
54
 
@@ -65,10 +65,10 @@ cd dsh-eda-mcp && pnpm install && dsh plugin --profile web add "link:$PWD"
65
65
 
66
66
  ### 2. 导入嘉立创EDA桥接扩展
67
67
 
68
- 构建会生成 `dsh-eda-bridge-extension-<版本号>.zip`(同时保留 `dsh-eda-bridge-extension.zip` 别名,内容相同;**分发请用带版本号的那个**,方便区分旧副本)。在嘉立创EDA专业版中:
68
+ 构建只生成 `dsh-eda-bridge-extension-<版本号>.zip`(不再保留同名别名——两个同内容文件只会让人分不清是哪一版)。在嘉立创EDA专业版中:
69
69
 
70
70
  1. 打开 **扩展 → 导入**。
71
- 2. 选择 `dsh-eda-bridge-extension.zip`。
71
+ 2. 选择 `dsh-eda-bridge-extension-<版本号>.zip`。
72
72
  3. 在扩展列表中找到 **DSH EDA MCP Bridge**。
73
73
  4. 启用它,并务必开启 **允许外部交互**。
74
74
 
@@ -354,7 +354,7 @@ pnpm check # 以上三步
354
354
  - `lib/index.js` — DSH Host 插件。
355
355
  - `lib/client.js` — DSH Web GUI 设置卡片。
356
356
  - `lib/bridge.mjs` — 独立 MCP/WebSocket 桥接子进程。
357
- - `dsh-eda-bridge-extension.zip` — 嘉立创EDA专业版扩展。
357
+ - `dsh-eda-bridge-extension-<版本号>.zip` — 嘉立创EDA专业版扩展(版本号 = 线协议版本)。
358
358
 
359
359
  `lib/` 与 zip 都是构建产物(已 gitignore);宿主加载的是打包后的 `lib/index.js`,所以改 `src/` 后必须 `pnpm build` **并重启 Host**(插件重载不会重新 import 已缓存的 ESM 模块)。仓库结构、发布打 tag 流程、bridge/扩展版本规则见 [CONTRIBUTING.md](CONTRIBUTING.md);**发布到 npm** 的完整流程(首次手动发布,之后由 `v*` tag 经 trusted publishing/OIDC 自动发布)见 [docs/release.md](docs/release.md)。
360
360
 
package/docs/release.md CHANGED
@@ -30,7 +30,7 @@ npm pack --dry-run # 看 tarball 内容
30
30
  ```
31
31
 
32
32
  `npm pack --dry-run` 里必须能同时看到 **`lib/index.js`、`lib/client.js`、`lib/bridge.mjs`、
33
- `dsh-eda-bridge-extension.zip`、`cordis.patch.yml`、`docs/`**。`lib/` 与 zip 是构建产物(被 gitignore),
33
+ `dsh-eda-bridge-extension-<线协议版本>.zip`、`cordis.patch.yml`、`docs/`**。`lib/` 与 zip 是构建产物(被 gitignore),
34
34
  由 `prepare` 脚本在打包时生成 —— 所以**打包机必须是 Windows**(zip 用 PowerShell `Compress-Archive`)。
35
35
 
36
36
  ## 2. 首次手动发布(必须手动,顺序不能颠倒)
Binary file
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Connects to the local dsh-eda-mcp bridge process over WebSocket and executes
6
6
  * a strict allow-list of official EasyEDA Pro extension APIs. Import through:
7
- * Extensions → Import → select dsh-eda-bridge-extension.zip
7
+ * Extensions → Import → select dsh-eda-bridge-extension-<version>.zip
8
8
  * then enable "Allow interactive with external" for this extension.
9
9
  */
10
10
  var DSH_EDA_MCP = {
package/lib/bridge.mjs CHANGED
@@ -781,7 +781,7 @@ function sendToClient(client, method, args, timeoutMs) {
781
781
  async function callExtension(method, args, timeoutMs) {
782
782
  const pool = pickClients();
783
783
  if (pool.length === 0) {
784
- throw new Error('JLCEDA bridge extension is not connected. Open JLCEDA Pro, import dsh-eda-bridge-extension.zip, enable "Allow interactive with external", and keep a schematic or PCB page open.');
784
+ throw new Error("JLCEDA bridge extension is not connected. Open JLCEDA Pro, import dsh-eda-bridge-extension-" + VERSION + '.zip, enable "Allow interactive with external", and keep a schematic or PCB page open.');
785
785
  }
786
786
  const deadline = Date.now() + timeoutMs;
787
787
  let lastError;
@@ -1311,7 +1311,7 @@ async function executeTool(name, rawArgs, timeoutMs) {
1311
1311
  versionMismatch: stale.length > 0,
1312
1312
  mcpUnits: "0.01 inch",
1313
1313
  defaultPaper: DEFAULT_PAPER,
1314
- hint: ready.length === 0 ? "No JLCEDA extension is connected. Open JLCEDA Pro and click DSH MCP -> connect, or re-import dsh-eda-bridge-extension.zip." : stale.length > 0 ? "Connected EDA extension " + stale.join(", ") + " does not match bridge " + VERSION + ". In JLCEDA Pro remove the OLD dsh-eda-bridge extension first, import dsh-eda-bridge-extension.zip again, then restart the app; a simple reload keeps the old copy." : "ok"
1314
+ hint: ready.length === 0 ? "No JLCEDA extension is connected. Open JLCEDA Pro and click DSH MCP -> connect, or re-import dsh-eda-bridge-extension-" + VERSION + ".zip." : stale.length > 0 ? "Connected EDA extension " + stale.join(", ") + " does not match bridge " + VERSION + ". In JLCEDA Pro remove the OLD dsh-eda-bridge extension first, import dsh-eda-bridge-extension-" + VERSION + ".zip again, then restart the app; a simple reload keeps the old copy." : "ok"
1315
1315
  };
1316
1316
  }
1317
1317
  case "eda_close_document":
@@ -1652,7 +1652,7 @@ async function executeTool(name, rawArgs, timeoutMs) {
1652
1652
  connected: waited.connected,
1653
1653
  connectedClients: waited.clients,
1654
1654
  waitedMs: waited.waitedMs,
1655
- hint: waited.connected ? "JLCEDA is connected; the eda_* tools can be used now." : "The app was started but no EDA extension connected yet. In JLCEDA Pro: \u6269\u5C55 \u2192 DSH MCP \u2192 \u8FDE\u63A5 (and enable \u201C\u5141\u8BB8\u5916\u90E8\u4EA4\u4E92\u201D); the extension must be imported once from dsh-eda-bridge-extension.zip."
1655
+ hint: waited.connected ? "JLCEDA is connected; the eda_* tools can be used now." : "The app was started but no EDA extension connected yet. In JLCEDA Pro: \u6269\u5C55 \u2192 DSH MCP \u2192 \u8FDE\u63A5 (and enable \u201C\u5141\u8BB8\u5916\u90E8\u4EA4\u4E92\u201D); the extension must be imported once from dsh-eda-bridge-extension-" + VERSION + ".zip."
1656
1656
  };
1657
1657
  }
1658
1658
  case "eda_get_page_info": {
@@ -2215,7 +2215,7 @@ async function executeTool(name, rawArgs, timeoutMs) {
2215
2215
  componentsBefore: pcbComponentBaseline.length,
2216
2216
  componentsAfter: after.length,
2217
2217
  confirmedYet: after.length > pcbComponentBaseline.length,
2218
- hint: "The imported EDA extension has no background-job helper, so the sync was awaited. Re-import dsh-eda-bridge-extension.zip to use the async + polling flow."
2218
+ hint: "The imported EDA extension has no background-job helper, so the sync was awaited. Re-import dsh-eda-bridge-extension-" + VERSION + ".zip to use the async + polling flow."
2219
2219
  };
2220
2220
  }
2221
2221
  return {
@@ -2514,7 +2514,7 @@ async function executeTool(name, rawArgs, timeoutMs) {
2514
2514
  } catch (error) {
2515
2515
  if (!isInternalHelperMissing(error)) throw error;
2516
2516
  const blocking = asRecord(await callExtension("pcb_Document.autoRouting", [props], toolTimeout(args, timeoutMs)));
2517
- return { ...blocking, backgroundUnavailable: true, props, hint: "The imported EDA extension has no background-job helper, so routing was awaited. Re-import dsh-eda-bridge-extension.zip to get the async + polling flow." };
2517
+ return { ...blocking, backgroundUnavailable: true, props, hint: "The imported EDA extension has no background-job helper, so routing was awaited. Re-import dsh-eda-bridge-extension-" + VERSION + ".zip to get the async + polling flow." };
2518
2518
  }
2519
2519
  const running = started.started === true || started.alreadyRunning === true;
2520
2520
  return {
package/lib/client.js CHANGED
@@ -1283,7 +1283,6 @@ function EdaMcpSettingsCard(props) {
1283
1283
  "a",
1284
1284
  {
1285
1285
  href: "/api/dsh-eda-mcp/extension.zip",
1286
- download: "dsh-eda-bridge-extension.zip",
1287
1286
  style: { ...secondaryButtonStyle, textDecoration: "none", display: "inline-flex", width: "fit-content" },
1288
1287
  children: t("settings.downloadExtension")
1289
1288
  }
package/lib/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import * as mcpClient from "@deepseek-ai/dsh-mcp-client";
3
3
  import z from "@deepseek-ai/schemastery";
4
4
  import { fileURLToPath } from "node:url";
5
+ import { existsSync as existsSync2, readFileSync } from "node:fs";
5
6
  import { dirname as dirname2 } from "node:path";
6
7
 
7
8
  // src/guidance.ts
@@ -23,6 +24,7 @@ var EDA_MCP_GUIDANCE = [
23
24
 
24
25
  // src/routes.ts
25
26
  import { createReadStream } from "node:fs";
27
+ import { basename } from "node:path";
26
28
  import { SettingsConflictError } from "@deepseek-ai/dsh-settings";
27
29
 
28
30
  // src/loopback.ts
@@ -196,7 +198,8 @@ function makeEdaMcpRoutes(deps) {
196
198
  if (!isLoopbackRequest(req)) return json(res, 403, { ok: false, error: "forbidden" });
197
199
  res.writeHead(200, {
198
200
  "content-type": "application/zip",
199
- "content-disposition": 'attachment; filename="dsh-eda-bridge-extension.zip"',
201
+ // Keep the versioned file name the browser saves it as.
202
+ "content-disposition": 'attachment; filename="' + basename(deps.extensionZipPath) + '"',
200
203
  "cache-control": "no-store"
201
204
  });
202
205
  const stream = createReadStream(deps.extensionZipPath);
@@ -532,7 +535,19 @@ function installSettingsSection(ctx, scoped, ns, schema, entry, hooks) {
532
535
  }
533
536
  var inject = ["webServer", "systemPrompt"];
534
537
  var BRIDGE_URL = new URL("../lib/bridge.mjs", import.meta.url);
535
- var EXTENSION_ZIP_URL = new URL("../dsh-eda-bridge-extension.zip", import.meta.url);
538
+ function resolveExtensionZipPath() {
539
+ const plain = fileURLToPath(new URL("../dsh-eda-bridge-extension.zip", import.meta.url));
540
+ try {
541
+ const manifest = JSON.parse(readFileSync(fileURLToPath(new URL("../jlc-extension/extension.json", import.meta.url)), "utf8"));
542
+ const version = typeof manifest.version === "string" ? manifest.version : "";
543
+ if (version !== "") {
544
+ const versioned = fileURLToPath(new URL("../dsh-eda-bridge-extension-" + version + ".zip", import.meta.url));
545
+ if (existsSync2(versioned)) return versioned;
546
+ }
547
+ } catch {
548
+ }
549
+ return plain;
550
+ }
536
551
  var BRIDGE_PATH = fileURLToPath(BRIDGE_URL);
537
552
  function bridgeLaunchFor(active) {
538
553
  const runtime = resolveBridgeRuntime();
@@ -614,7 +629,7 @@ function apply(ctx, config) {
614
629
  ctx.effect(() => {
615
630
  const disposers = makeEdaMcpRoutes({
616
631
  getConfig: () => current(),
617
- extensionZipPath: fileURLToPath(EXTENSION_ZIP_URL),
632
+ extensionZipPath: resolveExtensionZipPath(),
618
633
  reconnect: remountClient,
619
634
  bridgeLaunch: describeBridgeLaunch,
620
635
  diagnostics: () => diagnostics.liveSnapshot(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-eda-mcp",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
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": {
@@ -50,7 +50,6 @@
50
50
  "docs",
51
51
  "jlc-extension",
52
52
  "dsh-eda-bridge-extension-*.zip",
53
- "dsh-eda-bridge-extension.zip",
54
53
  "cordis.patch.yml",
55
54
  "CHANGELOG.md",
56
55
  "CONTRIBUTING.md",
@@ -507,7 +507,7 @@ function sendToClient(client, method, args, timeoutMs) {
507
507
  async function callExtension(method, args, timeoutMs) {
508
508
  const pool = pickClients()
509
509
  if (pool.length === 0) {
510
- throw new Error('JLCEDA bridge extension is not connected. Open JLCEDA Pro, import dsh-eda-bridge-extension.zip, enable "Allow interactive with external", and keep a schematic or PCB page open.')
510
+ throw new Error('JLCEDA bridge extension is not connected. Open JLCEDA Pro, import dsh-eda-bridge-extension-' + VERSION + '.zip, enable "Allow interactive with external", and keep a schematic or PCB page open.')
511
511
  }
512
512
  const deadline = Date.now() + timeoutMs
513
513
  let lastError
@@ -1075,10 +1075,10 @@ async function executeTool(name, rawArgs, timeoutMs) {
1075
1075
  versionMismatch: stale.length > 0,
1076
1076
  mcpUnits: '0.01 inch', defaultPaper: DEFAULT_PAPER,
1077
1077
  hint: ready.length === 0
1078
- ? 'No JLCEDA extension is connected. Open JLCEDA Pro and click DSH MCP -> connect, or re-import dsh-eda-bridge-extension.zip.'
1078
+ ? 'No JLCEDA extension is connected. Open JLCEDA Pro and click DSH MCP -> connect, or re-import dsh-eda-bridge-extension-' + VERSION + '.zip.'
1079
1079
  : (stale.length > 0
1080
1080
  ? 'Connected EDA extension ' + stale.join(', ') + ' does not match bridge ' + VERSION +
1081
- '. In JLCEDA Pro remove the OLD dsh-eda-bridge extension first, import dsh-eda-bridge-extension.zip again, then restart the app; a simple reload keeps the old copy.'
1081
+ '. In JLCEDA Pro remove the OLD dsh-eda-bridge extension first, import dsh-eda-bridge-extension-' + VERSION + '.zip again, then restart the app; a simple reload keeps the old copy.'
1082
1082
  : 'ok'),
1083
1083
  }
1084
1084
  }
@@ -1459,7 +1459,7 @@ async function executeTool(name, rawArgs, timeoutMs) {
1459
1459
  waitedMs: waited.waitedMs,
1460
1460
  hint: waited.connected
1461
1461
  ? 'JLCEDA is connected; the eda_* tools can be used now.'
1462
- : 'The app was started but no EDA extension connected yet. In JLCEDA Pro: 扩展 → DSH MCP → 连接 (and enable “允许外部交互”); the extension must be imported once from dsh-eda-bridge-extension.zip.',
1462
+ : 'The app was started but no EDA extension connected yet. In JLCEDA Pro: 扩展 → DSH MCP → 连接 (and enable “允许外部交互”); the extension must be imported once from dsh-eda-bridge-extension-' + VERSION + '.zip.',
1463
1463
  }
1464
1464
  }
1465
1465
 
@@ -1982,7 +1982,7 @@ async function executeTool(name, rawArgs, timeoutMs) {
1982
1982
  triggered: true, backgroundUnavailable: true, importResult: result,
1983
1983
  componentsBefore: pcbComponentBaseline.length, componentsAfter: after.length,
1984
1984
  confirmedYet: after.length > pcbComponentBaseline.length,
1985
- hint: 'The imported EDA extension has no background-job helper, so the sync was awaited. Re-import dsh-eda-bridge-extension.zip to use the async + polling flow.',
1985
+ hint: 'The imported EDA extension has no background-job helper, so the sync was awaited. Re-import dsh-eda-bridge-extension-' + VERSION + '.zip to use the async + polling flow.',
1986
1986
  }
1987
1987
  }
1988
1988
  return {
@@ -2284,7 +2284,7 @@ async function executeTool(name, rawArgs, timeoutMs) {
2284
2284
  // Older EDA extension without the background-job helper: fall back to the
2285
2285
  // blocking call (which may hit the tool timeout for large boards).
2286
2286
  const blocking = asRecord(await callExtension('pcb_Document.autoRouting', [props], toolTimeout(args, timeoutMs)))
2287
- return { ...blocking, backgroundUnavailable: true, props, hint: 'The imported EDA extension has no background-job helper, so routing was awaited. Re-import dsh-eda-bridge-extension.zip to get the async + polling flow.' }
2287
+ return { ...blocking, backgroundUnavailable: true, props, hint: 'The imported EDA extension has no background-job helper, so routing was awaited. Re-import dsh-eda-bridge-extension-' + VERSION + '.zip to get the async + polling flow.' }
2288
2288
  }
2289
2289
  const running = started.started === true || started.alreadyRunning === true
2290
2290
  return {
@@ -456,7 +456,8 @@ export function EdaMcpSettingsCard(props: EdaMcpSettingsCardProps) {
456
456
  <Field label={t('settings.downloadExtension')} hint={t('settings.installHint')}>
457
457
  <a
458
458
  href="/api/dsh-eda-mcp/extension.zip"
459
- download="dsh-eda-bridge-extension.zip"
459
+ // No download name: the server sends the versioned file name, so the
460
+ // saved copy always states which build it is.
460
461
  style={{ ...secondaryButtonStyle, textDecoration: 'none', display: 'inline-flex', width: 'fit-content' }}
461
462
  >
462
463
  {t('settings.downloadExtension')}
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ import type {} from '@deepseek-ai/dsh-settings'
5
5
  import * as mcpClient from '@deepseek-ai/dsh-mcp-client'
6
6
  import z from '@deepseek-ai/schemastery'
7
7
  import { fileURLToPath } from 'node:url'
8
+ import { existsSync, readFileSync } from 'node:fs'
8
9
  import { dirname } from 'node:path'
9
10
  import { EDA_MCP_GUIDANCE, SECTION_ORDER } from './guidance.ts'
10
11
  import { makeEdaMcpRoutes, makeEdaMcpSettingsRoutes, type SettingsBridge } from './routes.ts'
@@ -141,7 +142,27 @@ function installSettingsSection<T>(ctx: Context, scoped: Context, ns: string, sc
141
142
  export const inject = ['webServer', 'systemPrompt']
142
143
 
143
144
  const BRIDGE_URL = new URL('../lib/bridge.mjs', import.meta.url)
144
- const EXTENSION_ZIP_URL = new URL('../dsh-eda-bridge-extension.zip', import.meta.url)
145
+
146
+ /**
147
+ * The extension zip is shipped as `dsh-eda-bridge-extension-<version>.zip` (the
148
+ * version is the wire contract in jlc-extension/extension.json). Resolve it from
149
+ * that manifest instead of hard-coding a file name; older layouts without a
150
+ * versioned file keep working through the plain-name fallback.
151
+ */
152
+ function resolveExtensionZipPath(): string {
153
+ const plain = fileURLToPath(new URL('../dsh-eda-bridge-extension.zip', import.meta.url))
154
+ try {
155
+ const manifest = JSON.parse(readFileSync(fileURLToPath(new URL('../jlc-extension/extension.json', import.meta.url)), 'utf8')) as { version?: string }
156
+ const version = typeof manifest.version === 'string' ? manifest.version : ''
157
+ if (version !== '') {
158
+ const versioned = fileURLToPath(new URL('../dsh-eda-bridge-extension-' + version + '.zip', import.meta.url))
159
+ if (existsSync(versioned)) return versioned
160
+ }
161
+ } catch {
162
+ // Bundle-only installs may lack the manifest; the plain name is the fallback.
163
+ }
164
+ return plain
165
+ }
145
166
 
146
167
  /** Bridge script path, resolved once. */
147
168
  const BRIDGE_PATH = fileURLToPath(BRIDGE_URL)
@@ -252,7 +273,7 @@ export function apply(ctx: Context, config?: EdaMcpConfig): void {
252
273
  ctx.effect(() => {
253
274
  const disposers = makeEdaMcpRoutes({
254
275
  getConfig: () => current(),
255
- extensionZipPath: fileURLToPath(EXTENSION_ZIP_URL),
276
+ extensionZipPath: resolveExtensionZipPath(),
256
277
  reconnect: remountClient,
257
278
  bridgeLaunch: describeBridgeLaunch,
258
279
  diagnostics: () => diagnostics.liveSnapshot(),
package/src/routes.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createReadStream } from 'node:fs'
2
+ import { basename } from 'node:path'
2
3
  import type { ServerResponse } from 'node:http'
3
4
  import type { WebRoute } from '@deepseek-ai/dsh-host-webserver'
4
5
  import { SettingsConflictError, type SettingsPathOp } from '@deepseek-ai/dsh-settings'
@@ -186,7 +187,8 @@ export function makeEdaMcpRoutes(deps: EdaMcpRouteDeps): WebRoute[] {
186
187
  if (!isLoopbackRequest(req)) return json(res, 403, { ok: false, error: 'forbidden' })
187
188
  res.writeHead(200, {
188
189
  'content-type': 'application/zip',
189
- 'content-disposition': 'attachment; filename="dsh-eda-bridge-extension.zip"',
190
+ // Keep the versioned file name the browser saves it as.
191
+ 'content-disposition': 'attachment; filename="' + basename(deps.extensionZipPath) + '"',
190
192
  'cache-control': 'no-store',
191
193
  })
192
194
  const stream = createReadStream(deps.extensionZipPath)
Binary file