🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

Windows 原生校验 ax2/dsh-win-native

DeepSeek Harness
ax219 天前有提交已提供中文文档扫描:含敏感能力

检测编码、校验路径合法性并只读盘点磁盘

30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/12 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add ax2/dsh-win-native
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么
dsh 原生插件 · dev
装得上吗
曾在 dsh 0.2.0-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.1-rc.5,本站尚未在新版下复验
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 19 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件
查看扫描证据(共 1 处,展示前 1 条)
  • 读取本地文件
    src/core/duplicates.ts:133
    const stream = createReadStream(path)
扫描于 2026/11/1(0 天前) · 范围 全包扫描(包内未声明入口) · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-llm@deepseek-ai/dsh-tools@deepseek-ai/dsh-util-values@deepseek-ai/schemastery
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 编码检测与转换
  • 校验路径合法性
  • 只读磁盘检查

DeepSeek Harness 的 Windows 正确性层:编码检测与转换(GBK/GB18030/Big5/Shift-JIS)、Windows 路径合法性、netstat 解读,以及有边界的只读磁盘检查。

README

由 DeepSeek 最新模型翻译生成
dsh-win-native Windows 正确性层:把智能体平时用 PowerShell 文本解析凑合的那些判断,做成有回执、有边界、说实话的工具。 English · MIT · dsh-win-native · 6 个工具 · 无安装期脚本 快速开始 dsh plugin --profile web add dsh-win-native 装完重启应用(bundle 列表是启动时读的)。想一次装齐四个插件(含 deterministic-tools / jvm-doctor / py-doctor)用 @ax2zicode/dsh-dev-toolbox;别和单独安装混用。 家族总览:zicode.com/software 是四个插件、合装包与桌面软件的统一入口。
展开完整文档(README 剩余 1 节)
dsh-win-native

Windows 正确性层:把智能体平时用 PowerShell 文本解析凑合的那些判断,做成有回执、有边界、说实话的工具。

English · MIT · dsh-win-native · 6 个工具 · 无安装期脚本

快速开始

dsh plugin --profile web add dsh-win-native

装完重启应用(bundle 列表是启动时读的)。想一次装齐四个插件(含 deterministic-tools / jvm-doctor / py-doctor)用 @ax2zicode/dsh-dev-toolbox;别和单独安装混用。

家族总览:zicode.com/software 是四个插件、合装包与桌面软件的统一入口。

15 秒看效果 —— 问「C:\temp\CON.txt 这个路径能用吗」,模型调用 path_inspect,拿回(实测输出):

{ "issues": [ { "code": "reserved_name", "severity": "error", "position": 8,
"detail": "segment \"CON.txt\" is a reserved DOS device name, even with an extension or a path prefix" } ],
"safe_for_max_path": false, "canonical_length": 15 }

为什么不让模型自己写 pwsh? 这些判断写成一行 PowerShell 都会漏东西,而工具是 57 个测试盯着的固定实现:
- 编码:GBK / GB18030 / Big5 / Shift_JIS 评分 + high/medium/low 置信度,并列出所有接近的备选;
- 路径:保留设备名、8.3 安全、UNC vs 驱动器相对、逐段长度、260 上限——每条 finding 带 code / severity / 位置;
- 只读:只有 dir_usage 与 file_duplicates 碰磁盘,都有预算,软链接与 junction 跳过并上报、从不跟随。

相邻插件:dsh-deterministic-tools · dsh-jvm-doctor · dsh-py-doctor · dsh-dev-toolbox

这个插件解决什么问题

Windows 上的智能体每天在做同一类事:猜一个字节流是什么编码、判断一个路径为什么会失败、把 netstat 的输出用正则抠出来、统计一个目录多大、找重复文件。这些事用临时 PowerShell 拼出来通常有三个毛病:

1. 猜。 编码靠「看起来像 GBK」,路径靠「报错了就加双反斜杠」,端口占用靠「LISTENING 应该就是活着」。猜错时智能体不会说「我不确定」。
2. 不说边界。 目录统计跑了一半被截断、重复文件只哈希了一部分,结果看起来和完整结果一模一样。
3. 不守恒。 一次转换悄悄把 4 个汉字变成 ?,没有任何地方记录这件事。

dsh-win-native 把这六件事做成六个工具,每个都遵守 DSH 插件契约:返回 { result, meta } 回执、结构化输入超预算拒绝而不是截断、不支持的取值连允许列表一起报出、领域失败作为成功结果返回(ENOENT 是数据,不是崩溃)。

只读承诺(重要)

| 工具 | 会不会碰文件系统 | 会不会写 |
| encoding_detect | 不会。只分析你给的 base64 字节 | 不会 |
| encoding_convert | 不会。只转换你给的 base64 字节 | 不会 |
| path_inspect | 不会。纯字符串分析,路径不存在也能分析 | 不会 |
| netstat_parse | 不会。只解析你粘贴的文本 | 不会 |
| dir_usage | 会读。lstat + opendir 遍历你指定的绝对路径 | 不会 |
| file_duplicates | 会读。读取文件内容做流式 SHA-256 | 不会 |

这个插件永远不会删除、移动、重命名或写入任何文件——包括它自己找到的重复文件。file_duplicates 只报告哪些文件重复、能回收多少字节,回收动作永远由人或上层策略决定。两个遍历工具用 lstat 而不是 stat,所以符号链接、junction 和其他 reparse point 一律跳过并上报(skipped_links),绝不跟随——跟随一个 junction 可能走出你要求的目录树,在 Windows 上还可能成环。
两个会读文件的工具拒绝相对路径。相对路径的含义取决于宿主进程是怎么启动的,正是这个插件要消灭的那类不确定性;所以拒绝比「帮你解析」更诚实。

六个工具

1. encoding_detect — 这是什么编码?

按 BOM → 严格 UTF-8 → 打分候选 的顺序判断,返回最佳候选、high/medium/low 置信度,以及所有得分接近的候选。它不会在不确定时假装确定。

// 输入:GBK 字节的 base64("配置文件路径:C:\Users\ax2,服务未启动。")
{ "base64": "..." }

{
"result": {
"encoding": "gb18030",
"confidence": "high",
"bom": "none",
"byte_length": 40,
"alternatives": [
{ "encoding": "gb18030", "score": 1, "replacements": 0, "suspicious": 0, "round_trip": true, "sample": "配置文件路径:C:\\Users\\ax2,服务未启动。" }, { "encoding": "latin1", "score": 0.75, "replacements": 0, "suspicious": 0, "round_trip": true, "sample": "ÅäÖÃÎļþ·¾¶£ºC:\\Users\\ax2£¬·þÎñδÆô¶¯¡£" }, { "encoding": "big5", "score": 0.743,"replacements": 0, "suspicious": 2, "round_trip": true, "sample": "饜离恅璃繚噤ㄩC:\\Users\\ax2ㄛ督昢帤ぎ雄﹝" }, { "encoding": "utf-8", "score": 0.15, "replacements": 22,"suspicious": 0, "round_trip": false, "sample": "�����ļ�·����C:\\Users\\ax2������δ������" } ],
"signals": ["scored:gb18030=1,latin1=0.75,big5=0.743,utf-8=0.15,shift_jis=0"]
},
"meta": { "bytesIn": 56, "bytesOut": 1128, "truncated": false, "notes": [] }
}

短样本会诚实地报 low。 两个汉字(4 字节)在 GB18030 和 Big5 里都能合法解码,得分完全相同:

// 输入:GBK 字节的 base64("中文") {
"result": {
"encoding": "gb18030",
"confidence": "low",
"alternatives": [
{ "encoding": "gb18030", "score": 1, "sample": "中文" }, { "encoding": "big5", "score": 1, "sample": "笢恅" }, { "encoding": "latin1", "score": 0.75, "sample": "ÖÐÎÄ" }, { "encoding": "shift_jis","score": 0.65,"sample": "ヨミホト" } ],
"signals": ["scored:gb18030=1,big5=1,latin1=0.75,shift_jis=0.65,utf-8=0.15", "ambiguous-candidates-within-score-margin"]
},
"meta": {
"notes": ["confidence is low: at least two code pages explain these bytes equally well. Pass an explicit from to encoding_convert, or confirm with a larger sample, before trusting the text."]
}
}

这是设计的一部分,不是缺陷。 在 iconv-lite 的码表下,每一个合法的 Big5 双字节对同时也是合法的 GB18030 双字节对(实测交集 16777 对,零分歧);Shift-JIS 与 GB18030 也是同样情况(7326 对,零分歧)。所以短中日文样本的歧义是信息论层面的:不存在能区分它们的字节级证据。工具的职责是把这个事实报出来,而不是掷骰子——gb18030 之所以排在前面,是因为一个显式的、写在 TSDoc 里的码页先验(gb18030 的加分随「低位引导字节占比」衰减,shift_jis 固定小额加分)。score 字段是原始证据,先验只影响排序,绝不改变打印出来的分数。

2. encoding_convert — 转换,并且报告丢了多少

支持 utf-8、utf-16le、utf-16be、gbk、gb18030、big5、shift_jis、latin1、ascii,from: "auto" 先检测。有损转换一定被报出来,绝不静默:

// 输入:"中文测试" 的 UTF-8 字节 → latin1 { "base64": "5Lit5paH5rWL6K+V", "from": "utf-8", "to": "latin1", "bom": "strip" }

{
"result": {
"base64": "Pz8/Pw==",
"source_encoding": "utf-8",
"target_encoding": "latin1",
"source_bom": "none",
"output_bom": "none",
"bytes_in": 12,
"bytes_out": 4,
"lossy_characters": 4,
"lossy_list": [
{ "code_point": "U+4E2D", "char": "中", "count": 1 },jsonc { "code_point": "U+6587", "char": "文", "count": 1 }, { "code_point": "U+6D4B", "char": "测", "count": 1 }, { "code_point": "U+8BD5", "char": "试", "count": 1 } ], "hex_preview": "3f 3f 3f 3f", "text": "中文测试", "changed": true,
"signals": ["source-declared:utf-8", "lossy:4-characters-replaced", "bom:stripped"]
},
"meta": {
"bytesIn": 16,
"bytesOut": 512,
"truncated": false,
"notes": ["LOSSY: 4 character(s) have no representation in latin1 and were replaced; first offenders: \"中\" (U+4E2D), \"文\" (U+6587), \"测\" (U+6D4B), \"试\" (U+8BD5). The output is not a faithful round trip."]
}
}

带 BOM 的源文件会被正确识别并剥离,text 里不会留下 U+FEFF: jsonc // 输入:UTF-8 BOM + "中文测试" { "base64": "77u/5Lit5paH5rWL6K+V", "to": "gbk", "bom": "strip" } // result: { "source_bom": "utf-8", "output_bom": "none", "bytes_out": 8, // "hex_preview": "d6 d0 ce c4 b2 e2 ca d4", "text": "中文测试", // "lossy_characters": 0, "signals": ["auto-detected-source:utf-8:high", "lossless-round-trip", "bom:stripped"] }

3. path_inspect — 这条路径会怎么炸?

纯字符串分析,不碰文件系统,所以对不存在的路径同样有效。逐条检查:绝对/相对、\\?\ 扩展前缀、UNC vs 盘符 vs 根相对 vs 盘符相对(C:foo)、保留设备名(CON、PRN、AUX、NUL、COM1-9、LPT1-9,带扩展名也算,如 CON.txt)、非法字符、控制字符、结尾空格或点、8.3 安全名、单段长度、以及总长度对 260 的 MAX_PATH 判定。
jsonc
// 输入:{ "path": "C:\\temp\\CON.txt" } {
"result": {
"input": "C:\\temp\\CON.txt",
"absolute": true,
"extended": false,
"kind": "drive",
"drive": "C",
"canonical": "C:\\temp\\CON.txt",
"length": 15,
"canonical_length": 15,
"max_path_limit": 260,
"safe_for_max_path": false,
"requires_extended_prefix": false,
"segments": [
{ "value": "temp", "index": 0, "safe_8_3": true, "length": 4 },
{ "value": "CON.txt", "index": 1, "safe_8_3": false, "length": 7 }
],
"issues": [
{ "code": "reserved_name", "severity": "error",
"detail": "segment \"CON.txt\" is a reserved DOS device name, even with an extension or a path prefix",
"position": 8 }
]
},
"meta": { "bytesIn": 15, "bytesOut": 530, "truncated": false, "notes": [] }
}

盘符相对路径会被正确拆段——盘符是根,不是第一段: jsonc // 输入:{ "path": "C:foo\\bar" } // result.kind = "drive" // result.absolute = false // result.canonical = "C:foo\\bar" // result.segments = [{ "value": "foo", "index": 0, ... }, { "value": "bar", "index": 1, ... }] // result.issues = [{ "code": "drive_relative", "severity": "error", // "detail": "\"C:\" is drive-relative: it resolves against the current directory of that drive, which differs per process", // "position": 2 }] // meta.notes = ["the path is not absolute; it would be resolved against the caller working directory"]

超长路径会给出确定的结论和可执行的建议: jsonc // 输入:{"path": "C:\\data\\segment\\...\\file.txt"}(336 字符) // result.requires_extended_prefix = true // result.safe_for_max_path = false // result.issues = [{ "code": "above_max_path", "severity": "warning", // "detail": "the path is 336 characters; without \\\\?\\ or the LongPathsEnabled policy, Win32 APIs fail at 259 characters" }] // meta.notes = ["use the canonical form (or the LongPathsEnabled policy) or Win32 APIs will fail on this path"]

UNC 路径的 canonical 形式保持 \\ 开头(不会被规范化成单反斜杠): jsonc // 输入:{ "path": "\\\\server\\share\\dir\\file.txt" } // result.kind = "unc", result.drive = null // result.canonical = "\\\\server\\share\\dir\\file.txt", result.canonical_length = 27 // result.segments = [{ "value": "dir", "index": 1 }, { "value": "file.txt", "index": 2 }] // server/share 属于根 // result.issues = []

每个 issue 都带 code、severity 和 position(在原始字符串里的下标),所以智能体可以精确定位到出问题的那一段,而不是重读整条路径。

4. netstat_parse — 端口到底谁在听?

解析粘贴的 netstat -ano / netstat -anob 或 PowerShell Get-NetTCPConnection 表格,自动识别方言。返回监听端点(带 PID 和文本里真有的话进程名)、按远端端点聚合的已建立连接、重复绑定(同一端口同时绑在 0.0.0.0 和具体地址上)、以及按状态和协议的计数。
jsonc
{
"dialect": "netstat",
"summary": { "tcp_rows": 5, "udp_rows": 1, "listening_tcp": 3, "listening_udp": 1,
"established": 2, "distinct_pids": 4, "ports": 5 },
"listening": [
{ "protocol": "tcp", "address": "0.0.0.0", "port": 135, "state": "LISTENING", "pid": 1012, "process_name": null },
{ "protocol": "tcp", "address": "0.0.0.0", "port": 445, "state": "LISTENING", "pid": 4, "process_name": null },
{ "protocol": "tcp", "address": "127.0.0.1", "port": 445, "state": "LISTENING", "pid": 4, "process_name": null },
{ "protocol": "udp", "address": "0.0.0.0", "port": 3702, "state": "UDP", "pid": 3140, "process_name": null }
],
"duplicate_binds": [
{ "protocol": "tcp", "port": 445, "wildcard_conflict": true, "multi_pid": false,
"bindings": [{ "address": "0.0.0.0", "pid": 4 }, { "address": "127.0.0.1", "pid": 4 }] }
],
"limitations": [
"whether a listening PID still exists cannot be determined from netstat text; resolve the pid separately",
"process names are only present with netstat -b (often \"Access denied\") or from Get-NetTCPConnection -OwningProcess",
"UDP has no connection state, so a UDP row without a foreign address is reported as a bind rather than a session"
],
"notes": []
}

它绝不编造文本里没有的事实。 监听 PID 是否还活着、UDP「连接」的状态、没带 -b 时的进程名——这三件事这段文本里根本没有,所以它们出现在 limitations 里,而不是被猜出来。无法识别的行被跳过并计数上报(notes),不会被硬塞进某个字段。

5. dir_usage — 这个目录多大?(只读)

lstat + opendir 流式遍历,返回总字节、文件/目录数、最大的若干文件、扩展名直方图。受 max_files(默认 20000)、max_depth(默认 8)、max_bytes(默认 512 MiB)约束;撞到任何一个预算都会把 truncated 置真并说明是哪一个。
jsonc
// 输入:{ "path": "C:\\Users\\ax2\\AppData\\Local\\Temp\\demo", "top_n": 2 } {
"result": {
"root": "C:\\Users\\ax2\\AppData\\Local\\Temp\\demo",
"total_bytes": 5632,
"files": 4,
"directories": 2,
"largest": [
{ "path": "...\\app.log", "size": 4096, "depth": 1 },
{ "path": "...\\logs\\old.log", "size": 1024, "depth": 2 }
],
"extensions": [ { "extension": ".log", "files": 2, "bytes": 5120 },
{ "extension": ".ini", "files": 1, "bytes": 512 },
{ "extension": ".txt", "files": 1, "bytes": 16 } ],
"skipped_links": [],
"unreadable": [],
"truncated": false,
"notes": []
},
"meta": { "bytesIn": 45, "bytesOut": 521, "truncated": false, "notes": [] }
}

预算生效时的样子——truncated 为真,且 note 说清是哪个预算:

// 输入:{ "path": "<上面的目录>", "max_files": 3 } // result.files = 3, result.truncated = true // meta.truncated = true // meta.notes = ["file budget maxFiles=3 reached; the totals are a lower bound"]

根目录打不开时不抛异常——ENOENT 是数据:

// 输入:一个不存在的绝对路径 // result.files = 0 // result.unreadable = [{ "path": "...", "code": "ENOENT", "message": "..." }] // meta.truncated = true // meta.notes = ["the root directory itself could not be opened; the totals describe nothing"]

6. file_duplicates — 哪些文件是重复的?(只读)

先按大小分组,只对同尺寸候选做流式 SHA-256。返回重复组(含哈希、尺寸、数量、可回收字节、全部路径)、reclaimable_bytes、以及完整的守恒计数。

// 输入:{ "path": "C:\\temp\\demo" } {
"result": {
"root": "C:\\temp\\demo",
"groups": [
{ "hash": "…64 hex…", "size": 16, "count": 2, "reclaimable": 16,
"paths": ["C:\\temp\\demo\\copy-a.txt", "C:\\temp\\demo\\copy-b.txt"] }
],
"groups_total": 1,
"reclaimable_bytes": 16,
"files_seen": 5,
"candidates_hashed": 2,
"files_hashed": 2,
"bytes_hashed": 32,
"unreadable": [],
"truncated": false,
"notes": ["3 file(s) had a unique size and needed no hashing"]
},
"meta": { "bytesIn": 45, "bytesOut": 536, "truncated": false, "notes": ["3 file(s) had a unique size and needed no hashing"] }
}

哈希预算用完时,剩余候选被报为"未哈希",绝不被当成"不重复":

// 输入:{ "path": "...", "max_bytes_hashed": 50 }(两个 100 字节的同内容文件) // result.groups_total = 0, result.files_hashed = 0, result.truncated = true // meta.notes = ["byte budget maxBytesHashed=50 reached; files that were not hashed may or may not be duplicates"]

同尺寸但内容不同的文件一定不会被合并;无法读取的路径带 stage(list / stat / read)和错误码出现在 unreadable 里。空文件被排除并附上说明——它技术上"重复",但回收它没有任何意义。

预算与配置

所有预算都能在插件配置里覆盖(宿主会用 schemastery 校验),非法值(非正数、NaN、非整数)回落到默认值:

| 配置项 | 默认值 | 管什么 |
| maxInputBytes | 262144(256 KiB) | 结构化输入的硬上限;超过就拒绝,不截断 |
| maxOutputBytes | 262144(256 KiB) | 渲染结果的软上限;文本按 code point 边界截断并标 truncated |
| maxRawBytes | 2097152(2 MiB) | 编码工具解码后的原始字节上限 |
| maxEntries | 2000 | socket 条目 / issue 列表的上限(计数仍然完整) |
| maxTextChars | 200 | encoding_convert 回显的解码字符数 |
| maxFiles | 20000 | dir_usage / file_duplicates 的默认文件预算 |
| maxBytes | 536870912(512 MiB) | dir_usage 的默认记账字节预算 |
| maxBytesHashed | 268435456(256 MiB) | file_duplicates 的默认哈希预算 |
| maxErrors | 50 | 上报的不可读路径条数上限 |
| toolPrefix | "" | 工具名前缀,win → win__path_inspect |
| only | [] | 只注册这些工具 |

cordis 配置示例 plugins: win-native: maxBytesHashed: 1073741824 # 1 GiB
only: [path_inspect, encoding_detect]

为什么结构化输入超预算要拒绝而不是截断? 因为截断一个 base64 编码流或者一段 netstat 文本,得到的不是「小一点的结果」,而是错误的结果——一半的字节能解码出完全不同的字符,一半的 netstat 表格会漏掉真正的占用者。宁可失败得明确,也不要成功得错误。

为什么只依赖 iconv-lite

运行期只有一个依赖:iconv-lite(^0.6.3)。

- Windows 的码页转换没有 Node 内置替代。 TextDecoder 只支持 UTF 系列和少量 windows-1252 之类的标签,不支持 GBK / GB18030 / Big5 / Shift-JIS——而这恰好是中文 Windows 上智能体最常撞到的四种编码。Buffer 也只支持 utf8/utf16le/latin1/ascii/base64/hex。
- 它没有原生编译。 纯 JS + 码表数据,不需要 node-gyp、不需要 Visual Studio Build Tools、不需要在安装期联网。这直接满足契约里「不写安装期脚本」的要求——如果引入需要编译的模块,就必然要 postinstall,而 pnpm 的 allowBuilds 会拦截它。
- 它本来就是 DSH 生态的既成事实。 很多工具链已经在传递依赖它,所以引入它几乎不增加实际的供应链面。
- 码表行为可验证。 本插件的编码测试断言了真实字节(GBK C4E3 BAC3 = "你好"、GBK C5E4 D6C3 = "配置"),所以如果 iconv-lite 哪天改了码表,测试会先炸,而不是检测逻辑悄悄漂移。

不引入的替代方案都更差:自己维护 GBK/Big5 码表(几 MB 的数据,必然过时)、调 chcp + PowerShell(每个进程状态不同,正是本插件要消灭的不确定性)、要求用户预转码(把问题推回给用户)。

安装与构建

npm(推荐):

dsh plugin --profile web add dsh-win-native

预构建 tarball(与 npm 上是同一份产物,无需构建步骤):

dsh plugin --profile web add https://github.com/ax2/dsh-win-native/releases/latest/download/dsh-win-native.tgz

要固定版本就把 latest/download/dsh-win-native.tgz 换成 download/v<version>/dsh-win-native-<version>.tgz。

dsh.bundle.patch 指向 ./cordis.patch.yml,它在配置里插入 id: win-native。没有 postinstall / prepare:发布的是预构建的 lib/(lib/ 不进 git,由 prepublishOnly 在打包前重新构建),所以安装时不需要编译器。

从源码构建与验证:

pnpm install
pnpm typecheck
node --test test/
pnpm build

开发说明

- 源码用 Node 的 strip-only TypeScript:相对导入必须写 .ts 后缀,不能用构造函数参数属性、enum、namespace、装饰器。
- defineTool 的 const 泛型会在宽 ParameterSchemaSpec 联合上递归爆掉(TS2321),所以只有那一处泛型被擦除(src/tools.ts 里的单一 cast),参数 DSL 仍然被编译和测试。
- 每个文件系统工具在遍历时逐条检查 exec.signal.throwIfAborted(),长遍历每 64 条让出事件循环,所以调用方能取消一个跑在大目录上的统计。

ROADMAP

以下能力明确不在 v0.1,因为它们在纯 Node 里无法诚实实现。

| 能力 | 为什么需要真实 Win32 / Node-API | 为什么不在 v0.1 |
| 剪贴板读写 | 需要 OpenClipboard/GetClipboardData 等 Win32 调用,或用 Electron 的 clipboard(本插件不是 Electron 应用) | 没有任何纯 Node 的剪贴板 API。用 PowerShell Get-Clipboard 绕路会引入「文本解析 + 每个进程状态不同」,正是本插件反对的做法 |
| 截图 | 需要 GDI / BitBlt / PrintWindow 或 desktopCapturer;PrintWindow 还需要处理 DWM 与不同 DPI | 同上。用 PowerShell + .NET 拼可以出图,但依赖宿主装了 .NET 桌面运行时,且无法可靠处理多显示器 / 高 DPI |
| Windows 服务查询与控制 | 需要 OpenSCManager/EnumServicesStatusEx/ControlService;sc.exe 的输出是本地化的 | 控制服务是写操作,与「只读承诺」直接冲突。查询部分可以用 sc query 但输出随系统语言变化,解析它就是在编造事实 |
| 进程控制(结束/挂起/优先级) | 需要 OpenProcess/TerminateProcess/SetPriorityClass | v0.1 的全部工具都是只读的,这是本插件相对「ad-hoc PowerShell」的核心优势。破坏性操作应该有独立的、显式的权限模型,不该混进来 |
| 长路径文件操作(\\?\ 下的创建/枚举/删除) | 需要 CreateFileW 等能接受扩展前缀的 API,或 Node 的 libuv 在足够新的版本上支持 | v0.1 提供了 path_inspect 的 canonical 和 toExtendedPath,把「该怎么写」算清楚了;真正执行长路径操作需要引入 Node-API 原生模块,会破坏「无安装期脚本」 |
| 真实进程/端口归属(哪个进程占着 445) | 需要 GetExtendedTcpTable + QueryFullProcessImageName | netstat_parse 只解析你给的文本,并且明确说「PID 是否还活着无法从这段文本判断」。要给出可信答案必须有真实的表 API |

v0.2 会以独立的一个原生模块(Node-API / 预编译二进制,按平台分发)来实现这些,让 dsh-win-native 的纯 JS 部分保持零编译依赖。任何新工具仍然遵守同一套契约和同一条「如实报告边界」的规则。

License

MIT © 2026 ax2 — 见 LICENSE。

Windows 正确性层:智能体平时靠临时拼 PowerShell 文本解析糊弄过去的那些判断,这里做成工具,带凭据、带预算、也带诚实的边界。

它是什么

六个工具。其中三个(path_inspect、netstat_parse、encoding_detect)加上 encoding_convert 分析的是你交给它们的输入,从不碰文件系统。另外两个(dir_usage、file_duplicates)只读地读取本地文件系统。这个插件里没有任何东西会写入、移动、重命名或删除文件——包括它找出的重复文件;它只报告能回收多少空间。

| 工具 | 会读文件系统吗? | 会写入吗? | 它回答什么 |
| encoding_detect | 否 | 否 | 这些字节是什么编码?BOM → 严格 UTF-8 → 打分后的代码页,附带 high/medium/low 的置信度,以及每一个接近的备选。 |
| encoding_convert | 否 | 否 | 在 utf-8、utf-16le、utf-16be、gbk、gb18030、big5、shift_jis、latin1、ascii 之间转换。报告目标编码无法表示的字符的确切数量和身份。 |
| path_inspect | 否(纯文本) | 否 | 所有会让 Windows 路径出问题的地方:扩展前缀,UNC 与盘符、根路径、盘符相对路径的区分,保留设备名(CON.txt 也算),非法字符与控制字符,结尾的空格或点,8.3 安全性,每一段的长度,以及 260 字符的 MAX_PATH。每条发现都带一个代码、一个严重级别,以及它在原字符串中的位置。 |
| netstat_parse | 否 | 否 | 解析 netstat -ano/-anob 或 Get-NetTCPConnection 的文本:带 PID 的监听项,按远端端点分组的已建立连接,重复绑定,按状态和协议统计的数量——并且拒绝凭空编造文本里没有的存活状态、UDP 状态或进程名。 |
| dir_usage | 是,只读 | 否 | 文件/目录数量,总字节数,最大的文件,扩展名直方图。用 lstat,所以符号链接和 junction 会被跳过并报告,绝不跟随。 |
| file_duplicates | 是,只读 | 否 | 先按大小分组,然后只对大小相同的候选流式计算 SHA-256。报告可回收的字节数,以及预算耗尽时具体哪些文件没有做哈希。 |

塑造一切的两条规则

1. 超出预算的结构化输入一律拒绝,绝不截断。 半截 base64 流解出来的字符是错的;半张 netstat 表会藏起端口真正的归属者。干脆的失败胜过自信的错误成功。
2. 不做任何假设。 预算耗尽导致遍历中断时,会设置 truncated 并标明是哪个预算。有损转换会统计并列出受损项。目录打不开时,返回的是带 ENOENT 的成功结果,而不是抛异常。无法读取的路径会出现在 unreadable 里,并附上所属阶段和错误码。短 CJK 载荷会以 low 置信度上报,并给出备选结果,因为有了真实的代码页表之后,短的 GBK、Big5 和 Shift-JIS 载荷确实无法区分。

安装

dsh plugin --profile web add dsh-win-native # npm

或者用预构建的 tarball(同一个产物,供离线安装使用;把 latest/download/dsh-win-native.tgz 换成 download/v<version>/dsh-win-native-<version>.tgz 即可固定版本):

dsh plugin --profile web add https://github.com/ax2/dsh-win-native/releases/latest/download/dsh-win-native.tgz

端到端验证通过:把已发布的包从 npm 安装到一个临时配置档,然后让真实模型跑一轮,对 C:\temp\CON.txt 调用 path_inspect,返回了 reserved_name 错误发现(session-b5681b93)。

从源码构建并验证:

pnpm install
pnpm typecheck
node --test test/
pnpm build

许可证

MIT © 2026 ax2
预览分享卡片长什么样 ↓ax2/dsh-win-native
分享 · DeepSeek Harness Hub
ax2/dsh-win-native

检测编码、校验路径合法性并只读盘点磁盘

回到顶部