DeepSeek Harness 插件

27painter/dsh-nx

Star 数 ★ 1 分类 工具与能力 收录于 2026-11-02

从 DeepSeek Harness 驱动 Siemens NX 2406 与 2506,也支持 NX 12:一个按日志而非退出码判定 journal 成败的执行器(NX 对吞掉异常的 journal 返回 0,干净结束时返回 0xC0000409)、常驻批处理 NX 上的 16 个类型化工具,以及一个能分清三个版本的探测。仅 Windows;以 NX 2406 为基准版本,2506 为预期可用。需本机已安装 Siemens NX 并有一个空闲的 NX 许可席位;先运行一次 `dsh-nx install`;16 个 MCP 工具默认关闭,需设 `NX_ENABLE_MCP=1` 才注册(技能路线不受影响)。

安装

# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)

dsh plugin --profile web add github:27painter/dsh-nx

装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。

README

English | 中文

别的 NX MCP 告诉 agent 该做什么,dsh-nx 告诉它到底做成了没有。

让 DeepSeek Harness 的 agent 驱动 Siemens NX 2406 与 2506——以及 NX 12,那一版上游桥根本够 不着,因为它要求 Python 3.10+,而 NX 12 内嵌 3.6.1。

NXOpen 跑在 NX 进程内,所以自动化的唯一办法是起一个批处理 NX、把 journal 放进去跑。这个仓库就是 这层运行时:一套代码覆盖三个版本;一个能告诉你「这次到底成没成」的执行器;以及一个能把版本分清楚 的探测。

缺的是什么,这里补了什么

能力 之前 现在
知道一次运行成没成 run_journal.exe 对一个自己吞掉异常的 journal 照样返回 0 run-journal:判定来自日志,超时杀进程树,JSON 里带原因
一套运行时覆盖 2406、2506 与 12 上游桥要求 Python 3.10+,NX 12 够不着,而且每个版本都得单独配 五个模块 1,785 行移植到 3.6.1 并向后打垫片;一套代码,三个版本上都是同一组 16 个工具
知道机器上是哪个 NX 查注册表,或者靠猜 probe.py:列出每一份安装、各自的解释器、以及 verified / expected 标签
把它装上 克隆、建 venv、做 junction、设 NX_HOME、配许可目录,全手工 install.ps1 + doctor,后者会指出唯一出错的那一处
让 agent 会写 NXOpen 一份把版本写死在文字里的技能 同一份技能,解释器指引和 API 检查都跟着版本走

前两行是这个仓库存在的理由,后三行是它能在真机器上跑起来、而不只是演示的原因。

安装

NX 本身既不安装也不修改。install.ps1 建一个给 sidecar 用的 venv、取回 pinned 技能、并做好 junction。

git clone https://github.com/27painter/dsh-nx
cd dsh-nx
npm install -g .            # 把 dsh-nx 放进 PATH
dsh-nx install              # [1/5] python  [2/5] venv-nx  [3/5] 技能  [4/5] NX_HOME  [5/5] junction
dsh-nx doctor               # 只读;缺东西时退出码 1

dsh-nx install 是对 install.ps1 的薄包装;想直接看脚本输出就跑脚本:

.\install.ps1
.\install.ps1 -BaseDir "E:\cad" -Force      # 换位置 / 重建

包还没发到 npm,所以 npm install -g dsh-nx 现在解析不到。

开始之前

  • 不需要先打开 NX,打开也没用。 每条路都是通过 run_journal.exe 自己起一个批处理 NX;你已经打开 的那个窗口驱动不了,因为 NXOpen 跑在进程内。那个窗口不会被复用,也不会被看见。 这和 dsh-sw-cad 恰好相反——它走 COM 驱动 AutoCAD 与 SolidWorks,所以确实希望那两个应用已经开着。
  • NX_HOME 要指向你想用的那份安装——dsh-nx doctor 会打印它选了哪个、依据是什么。
  • 一个空闲的许可席位。 批处理 NX 跑多久就占多久。已经开着的 NX 窗口是否也占席位取决于你的许可 类型;如果运行停在 failed to initialize UFUN 948792,先把它关掉重试,再怀疑别的地方。
  • 一个可写的 workdir(-w),因为默认是 journal 所在目录——当插件是从 profile junction 加载时, 那个目录是只读的。

真正说明问题的那一步

一个立方体、一个体积、一个判定。这条命令打出 OK,就说明 NXOpen 在这台机器上真的做出了几何—— 后面所有说法都建立在这件事上:

dsh-nx run-journal examples\quickcheck.py --expect QUICKCHECK_OK -w D:\nx-work
CUBE_EDGE=50.0
VOLUME=125000.000
QUICKCHECK_OK
[dsh-nx] OK  10.8s

它建一个 50 mm 的方块,向 MeasureManager 要体积,数字不是 125000 mm³ 就判失败。冷启动约 20 秒 (要等 NX 起来),热启动 11 秒。examples/smoke.py 是同一件事但不要几何,只想确认 NX 能不能起时用它。

到这里技能就能用了。要用 MCP 工具,还得做下面的插件行。

插件行

开发机上实际做的是:往 profile 里做一条 junction、加一条依赖、加一条 bundle 记录。 dsh plugin --profile desktop add link:<路径> 是走 pnpm 的等价做法。

New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-nx" -Target "D:\path\to\dsh-nx"

用 link: 到达的插件,裸导入是按它的真实路径解析的,所以 @deepseek-ai/dsh-mcp-client 必须从 那里够得着,否则加载时直接失败:

$src = "$env:APPDATA\npm\node_modules\@deepseek-ai\dsh\node_modules\@deepseek-ai\dsh-mcp-client"
$dst = "D:\path\to\dsh-nx\node_modules\@deepseek-ai\dsh-mcp-client"
New-Item -ItemType Directory -Force -Path (Split-Path $dst) | Out-Null
New-Item -ItemType Junction -Path $dst -Target $src

然后在 dependencies 里加 "dsh-nx": "link:D:/path/to/dsh-nx",在 dsh.profile.bundles 里加 "dsh-nx"。

状态

下面所有内容都以这张表为前提。

状态
NX 2406(2406.1700,CPython 3.10) 基准版本。 16 个工具、技能、run-journal,以及四个示例全部端到端跑过
NX 2506(CPython 3.12) 从没跑过。 3.12 下能解析、能导入;探测把它标成 expected 而不是 verified
NX 12(12.0.0.27,CPython 3.6.1) 16 个工具、技能、run-journal 在真实零件上跑过;示例没有在这里重跑
16 个 MCP 工具 只覆盖建模与导出——不含 Drafting:没有视图投影、没有尺寸、没有标题栏、没有 GB 图框、没有 PDF
驱动你已经打开的那个 NX 窗口 做不到,这是 NXOpen 的进程模型决定的
npm 上的包 还没发布

要从 NX 出图,无论走哪条路都落在 journal 上——工具做不了,这份 README 也不假装它能。

为什么需要它

两条硬约束决定了这里每个设计:

NXOpen 跑在 NX 里面。 没有 COM 服务器,没有进程外句柄,agent 没有任何办法挂到你打开的那个 NX 上。可选的路只有两条:「自己起一个批处理 NX」或「把 journal 拷进去手工跑」。这里每条路都走第一条, 这也是常驻桥活着时 NX 被占用的原因。

NX 各版本不共用一个 Python。 2406 内嵌 3.10,2506 是 3.12,而仍在服役的 12 是 3.6.1。只按最新 的那个写,最老的就彻底没有自动化路径——上游桥正是这个状态。这里的移植按 3.6.1 写、向后打垫片, 所以 2406 与 2506 跑的是同一套代码、垫片自动失效,NX 12 也能跑。

箱子里有什么

路径 行数 是什么
nxsrc/ 3,030(30 个文件) 跑在 NX 内部的桥——移植版,见建立在什么之上
nxsrc/nx_mcp/nx_bridge.py + bridge.py 766 + 872 移植后的核心:会话、回环 JSON-RPC、16 个工具实现
lib/run-journal.mjs 408 执行器:解析 NX、运行、判定、杀进程
lib/probe.py 243 找出机器上每一份 NX,并标出各自能干什么
bin/dsh-nx.mjs 374 install / doctor / where / serve / run-journal / bridge-start / bridge-stop
lib/capture.mjs 74 用文件描述符而不是管道跑子进程
lib/nx-root.mjs + lib/paths.mjs 77 + 71 「用哪个 NX」只有一处实现,以及布局默认值
install.ps1 155 venv、pinned 技能、junction、NX_HOME
examples/ 4 个 journal 体积自检、冒烟、零件、以及这个零件的 GB A3 工程图

bin/、lib/、install.ps1 里的自有代码合计 1,402 行。

三条入口

路线 机制 什么时候用
技能(默认) agent 写 NXOpen Python,run-journal 执行 凡是需要 agent 现写的:建模、工程图、有限元、运动仿真
run-journal 一个 journal 进,一个判定出 手上已经有 journal,只想知道成没成
16 个 MCP 工具(opt-in) 对着常驻批处理 NX 的类型化调用 同一个零件上要调很多次小工具,冷启动那 60 秒不划算

技能默认开,因为它不占 NX。工具默认关,因为启动 dsh 不该等一个 CAD 应用。

  agent ──写──▶ journal.py ──run-journal──▶ run_journal.exe ──▶ 批处理 NX ──▶ .prt / .pdf / .stp
                    │                            │
                    │                            └── 判定来自日志,不是退出码
                    └── 先过 check_journal.py(3.6 子集,任何版本都能跑)

  agent ──nx_* 工具──▶ dsh-nx 插件 ──stdio──▶ sidecar(venv-nx)──回环──▶ 批处理 NX

示例

journal 证明什么 成功 token
examples/quickcheck.py NXOpen 真的做出了几何:50 mm 方块,量出 125000 mm³ QUICKCHECK_OK
examples/smoke.py 这台机器能不能跑 journal SMOKE_OK
examples/a_model.py 一个完整零件——凸台、通孔、键槽、台阶腔、孔——每步体数断言 MODEL_DONE
examples/b_drawing.py 这个零件的 GB A3 工程图:2:1 三视图、12 个尺寸、图框标题栏、PDF DRAWING_PRODUCTS_OK
dsh-nx run-journal examples\quickcheck.py --expect QUICKCHECK_OK -w D:\nx-work
dsh-nx run-journal examples\a_model.py --expect MODEL_DONE -w D:\nx-work
dsh-nx run-journal examples\b_drawing.py --expect DRAWING_PRODUCTS_OK -w D:\nx-work --allow-traceback

每个示例需要什么——许可席位、可写 workdir、出图那个还要上一步的零件——写在 examples/README.md。

run-journal

dsh-nx run-journal .\part.py                       # workdir 默认是 journal 所在目录
dsh-nx run-journal .\part.py -w .\build -t 900     # 指定 workdir 和超时(默认 600 秒)
dsh-nx run-journal .\part.py --expect DRAWING_OK   # 要求输出里出现某个 token
dsh-nx run-journal .\part.py --allow-traceback     # journal 会捕获并记录异常
dsh-nx run-journal .\part.py --log run1.log        # 换个日志文件
dsh-nx run-journal .\part.py --json                # 机器可读的判定
dsh-nx run-journal .\part.py --no-kill             # 超时不杀,保留现场
dsh-nx run-journal .\part.py --kill-grace 5        # 进程树有几秒可以死(默认 2)
dsh-nx run-journal .\part.py --print-command       # 只打印命令和环境,不跑

为什么判定不是退出码。 run_journal.exe 对一个自己接住异常的 journal 返回 0,对干净结束返回 0xC0000409。这两个数字都不是调用方想要的,所以由日志来判:

信号 判定
General Fault Exception、failed to initialize UFUN、FATAL ERROR、_FAIL、BRIDGE_FAIL fail
有 traceback 且 非干净退出 uncaught exception (exit N) —— --allow-traceback 不豁免
有 traceback 且 干净退出 默认 fail;加 --allow-traceback 降为 warning
--expect TOKEN 没出现 fail
退出码既不是 0 也不是 0xC0000409 fail(除非上面已经判了)

退出码是用来区分「崩了」和「健壮脚本」的:每步 try/except 记日志继续跑的 journal 照样干净结束,而 停在异常上的那个不会。

超时时对子进程树跑 taskkill /T /F——NX 会派生子进程,只杀 shell 会把 ugraf.exe 留在那里占着 许可——等 --kill-grace 秒后复查 pid。JSON 里有 killed 和 kill_ok。

日志是追加的,且只判本次写入的字节,所以上一次失败会留在盘上,但不会决定下一次的结论。

出问题的时候

这些都是在这台机器上真遇到过的,按症状查:

症状 真正的原因 怎么办
NX_HOME 明明对,doctor 却说 nx not found 沙箱读不到安装目录,解释器探测根本没跑起来 看它下面那行,那里写了原因;把下面的路径加进白名单
运行停在 failed to initialize UFUN 948792 NX 够不到许可或配置目录 白名单 C:\ProgramData\Siemens 和 %LOCALAPPDATA%\Siemens\NX<release>,或设 UGII_USER_PROFILE_DIR
判定因为 traceback 失败,但 journal 其实跑完了 分步 try/except 的 journal 本来就会记 traceback 加 --allow-traceback,或用 --expect 把关
tools/list 什么都没有 桥是在 MCP 握手期间才起的,而握手先超时了 先 dsh-nx bridge-start,再启动 dsh
插件加载报 ERR_MODULE_NOT_FOUND link: 插件看不到 @deepseek-ai/dsh-mcp-client 按插件行补那条 junction
doctor 点名了磁盘上另一份副本 工作区和已安装副本是两处 更新它点名的那份
日志只有 100 字节,但 journal 干了不少活 NX 在非控制台环境下缓冲 stdout 用 --expect 判定,或看 journal 自己写的文件日志
超时之后 ugraf.exe 还在 早先的版本只杀 shell 当前版本杀整棵树;--no-kill 是显式选择,不是默认

建立在什么之上

两个上游。哪些是「用」,哪些是「重写」,这里划清楚。

桥 —— DreamEnding/NX_MCP 的 4a7c8811,MIT。 上游要求 Python 3.10+,提供 C# GUI 桥和 Python 批处理桥两个。这里只留 Python 那个,因为 C# 桥要按 NX 12 的程序集重编,而加载未签名 DLL 需要 NX Open .NET 作者许可。五个模块——bridge_journal.py、 nx_bridge.py、bridge.py、runtime.py、workspace.py,共 1,785 行——被移植到 3.6.1:

  • 去掉 from __future__ import annotations,再用按 AST 偏移的改写工具把 256 条注解逐条加引号, 让它们永不被求值(str | None、dict[str, int] 是 3.10/3.9 语法)。正则会把注释和排版一起吃掉, 这个不会。
  • 给 3.6 补了一个 dataclasses 替身,且在 3.7+ 上是直通——Pydantic 会检查 __dataclass_fields__, 只模仿 dataclass 的东西会被它拒掉。
  • str.isascii()、Path.unlink(missing_ok=True) 和 walrus 运算符逐处替换。这三处都是跑工具跑出来 的,读代码一处也看不出来。
  • 拉伸那一段重写:上游的 CreateRuleCurveFeature([sketch], Null) 在 12 和 2406 上都报 NX 错误 650004,现在按顺序试四种建截面的组合并记录是哪一步失败。失败会指出具体那一步,而不是丢回一句 "Bridge execution failed"。

每一处改动的证据都记在 nxsrc/PORT-NOTES.md。

技能 —— EVOL233awa/nxopen-journal 的 4b77d88(v1.3.0),MIT。 作者是另一个人,不是这个账号。安装时以 zip 取回并 junction 进来; 不 vendor,所以你拿到的是他们当前的代码,著作权也归他们。其中两个文件在本地打了补丁,因为它原本 只面向 NX 12:SKILL.md 现在按版本列出解释器,而不是把 3.6.1 当成事实;check_journal.py 接受 目标版本(--python-version、--print-python),而不是无条件把 3.8+ 的 API 判成错误。这两处补丁 只在本机,没有合进上游。

其余部分——执行器、探测、安装器、MCP 行、示例——都是本仓库写的。同样的划分(带 commit 与许可证 路径)也写在 THIRD-PARTY-NOTICES.md。

验证方式

NX 2406.1700 NX 12.0.0.27
五个 in-NX 模块能导入 3.10 3.6.1
桥能起、nx_status 有回应 connected: true,v2406 connected: true,v12
16 个工具 全过 全过
examples/quickcheck.py 125000 mm³,冷 21.6 秒、热 10.8 秒 —
examples/a_model.py 通过,写出 .prt —
examples/b_drawing.py 17.9 秒;.prt 204 KB、.pdf 48 KB、.stl 67 KB —

判定逻辑在 2406 上对着四个 journal 验过。每一个都必须让同一条代码路径给出不同的答案,否则这套逻辑 等于没做事:

journal 期望 实际
examples/smoke.py ok ok,10.1 秒,退出码 0xC0000409 按干净处理
故意抛异常 fail uncaught exception (exit 1)
接住异常但跑完 先 fail,后 ok 默认是 fail 原因;加 --allow-traceback 变 warning
sleep 超过 --timeout 5 fail timeout … killed 4 process(es),7.2 秒返回

复跑第一条:

dsh-nx run-journal examples\smoke.py --expect SMOKE_OK --json

CI 不需要 NX:移植后的源码按 3.6 / 3.10 / 3.12 三套语法解析;五个 in-NX 模块能导入并建出 dataclass; 探测对每种解释器的假安装都能报出正确的支持档位。

许可证

MIT,Copyright (c) 2026 27painter —— 见 LICENSE。两个上游,一个 vendor、一个安装时拉取; 各自是什么、改了什么,记在 THIRD-PARTY-NOTICES.md。

内容来自项目 README(GitHub)↗

链接

同类插件

查看整个分类 →

社区评论

评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。