dsh-tool-everything
🔀 本仓库是 roxyyn0304/dsh-tool-everything 的 fork, 在上游 的基础上继续维护;本 fork 相对上游改了什么见下文 「🔀 本 fork 相对上游的差异」一节。 代码以 MIT 许可发布,原始版权归原作者 roxyyn0304 所有,上游 LICENSE 原文保留未改。
📖 插件简介
给 DSH Agent 加两个能力:按文件名秒搜全盘(Everything 索引 + koffi),按正文搜 PDF/DOCX 等文档(本机 Anytxt 索引)。共 6 个工具,装好即用,不用开 HTTP 服务,也不用 es.exe。
- ⚡ 极速:直接调用 Everything SDK(koffi FFI),走 IPC 通道——毫秒级响应
- 🔍 完整语法:通配符
*.pdf、path:C:\code、ext:zip、size:>10mb、regex:、|或、!非 - 📄 内容检索:走本机 Anytxt 的 JSON-RPC(仅回环),读到片段或整篇抽取文本
- 🎯 结构化结果:返回路径 + 大小 + 时间 + 是否文件夹,UI 自动渲染搜索卡片
- 📦 零安装负担:自带
Everything64.dll(x64,voidtools 签名),无需额外组件 - 🔥 DSH 原生:Cordis 插件,两条组件行可分别开关,支持 HMR 热重载
🛠️ 安装说明
前提
- Windows 系统
- Everything 正在运行(文件名面)
- Anytxt(
ATGUI.exe)在运行且目标目录已建索引(内容面,可选;没装只影响anytxt_*) - DeepSeek Harness(DSH)≥0.2.0-rc.1(
koffi随包依赖)。版本号只作兜底提示,能不能跑由插件加载时的宿主能力探测决定
安装
dsh plugin --profile web add "github:shuing/dsh-tool-everything"
装完硬刷新浏览器(Ctrl+Shift+R),新开会话即可使用。
⚠️ 请确认安装的是 0.5.1 及以上版本。0.2.0 的
peerDependencies只声明了^0.1.0-rc.6,会被 DSH 0.2.x 的兼容性检查判为不兼容并拒绝安装 (报错incompatible-version)。现行声明为engines.dsh: ">=0.2.0-rc.1 <1.0.0", 各条 DSHpeerDependencies同区间——版本区间只是安装期兜底提示,真正决定能不能跑的是下文的宿主能力探测。
从旧版本升级
dsh plugin --profile web add "github:shuing/dsh-tool-everything"
若之前装过 0.2.0,重新执行上面的 add 即可覆盖。若 profile 里残留了
0.2.0 的版本豁免(compatibility.json),升级后可以删掉,不再需要。
0.4.14 → 0.5.0 的破坏性变更
anytxt_search 的输出行与大模型可见语义变了,所以走次版本号而不是修订号:
- 每个命中行现在以
ref<TAB>路径<TAB>大小<TAB>时间开头,ref是第一列(这是anytxt_read的唯一入口;旧版根本没渲染它); - 引号整句真的生效了:引号留在
pattern里发给服务端,带引号的命中集从「等于裸词」变成「裸词的子集」; nextOffset按服务器原始返回行数前进,不再用过滤后条数——被排除的行落在页内时,相邻两页不再重复;- 新增
exclude参数,默认排除写进结果头(N exclusion rules applied),exclude: ""可关闭; max的 fragments 上限从 32 改为 8(服务端上限就是 8,实测传 3/8/32/300 都回 8 段);path与搜索串里已有的path:撞车改为报参数错误(旧行为静默丢掉path);anytxt_status不再因探针 0 命中回ok: false(一次探针分不出「索引为空」与「没有这个词」)。
everything_search 的行为只有一处变化:path / path: 撞车从静默改为显式失败。不传这两个参数时行为不变。
0.5.1 修复
anytxt_search的modified列改成本地时间。索引回的是 ISO(UTC),旧实现只把T/Z去掉就印出来,同一个文件会比everything_search那一列早整整一个时区(本机 8 小时),且两边都没有时区标记——读的人只会得出「这是两个不同版本」。现在两个面共用lib/shared/format.mjs的同一个本地时间格式化函数。anytxt_read在ref的 fid 不存在时不再静默返回0 of 0 characters(那是「读到一个空文档」的语义),改报ANYTXT_FID_NOT_FOUND。实测服务端对不存在的 fid 回errno 1+text: null,对有效 fid 回errno 0+ 字符串,所以两者可区分;GetFragmentAll两种都回errno 0,fragments侧分不出,只有full能报。此前errno只在anytxt_sync_index那条路径上被读过。
📁 文件树
dsh-tool-everything/
├── lib/
│ ├── index.js 外壳:宿主能力探测 + 组 caps(不注册工具)
│ ├── paths.mjs DLL 路径与 worker 入口的唯一定义处
│ ├── components/
│ │ ├── everything/ 文件名面
│ │ │ ├── index.js everything_search / everything_status 注册 + 参数 schema
│ │ │ ├── sdk.mjs koffi 绑定 + 实测常量(主线程与 worker 共用一份)
│ │ │ ├── query-worker.mjs 阻塞查询所在的 worker 线程
│ │ │ ├── status.mjs everything_status 探活
│ │ │ ├── package.json 组件身份(loader 用)
│ │ │ └── locale/en.json
│ │ └── anytxt/ 内容面
│ │ ├── index.js 四条 anytxt_* 工具注册 + 模型可见文本
│ │ ├── anytxt.mjs JSON-RPC 客户端 + 协议实测
│ │ ├── package.json
│ │ └── locale/en.json
│ └── shared/ 两面共用,禁止各复制一套
│ ├── config.mjs 唯一一份 Config schema
│ ├── limits.mjs 上限与预算校验(offset 钳位、坏预算回退)
│ ├── format.mjs 模型可见文本(含本地时间格式化)
│ ├── prompt-orders.mjs prompt 段 order 的唯一来源
│ └── type-name.mjs 探测报错用的 expected/got
├── native/
│ ├── Everything64.dll 随包的 Everything SDK(x64,voidtools 签名)
│ └── LICENSE SDK 自己的许可
├── test/
│ ├── verify.mjs 文件名面 + 打包 + 文档互校
│ └── anytxt.verify.mjs 内容面,全离线(注入 fetchImpl)
├── docs/architecture.md 为什么这样实现(数据流、只读边界)
├── cordis.patch.yml 三条 loader 行:外壳 + 两个组件
├── dsh.plugin.json 插件元数据(显示名、平台、tags、homepage)
├── package.json 依赖、exports、scripts、engines.dsh
├── package-lock.json
├── .gitignore
├── LICENSE MIT,上游原文保留
└── README.md
🧰 工具说明
六个工具全部无条件注册,没有开关要记。分工:
| 工具 | 干什么 | 依赖 |
|---|---|---|
everything_search |
按文件名全盘搜索(Everything 索引) | Everything 在运行 |
everything_status |
探活:everything_search 现在能不能用 |
同上 |
anytxt_search |
按正文搜文档,返回带 ref 的命中页 |
Anytxt 在运行 + 目录已索引 |
anytxt_read |
按 ref 读片段或整篇抽取文本 |
同上 |
anytxt_status |
探活:内容检索现在能不能用 | 同上 |
anytxt_sync_index |
按目录建库/刷新索引(唯一会改索引状态的一步) | 同上 |
everything_search
| 参数 | 类型 | 说明 |
|---|---|---|
search |
string | 必填,Everything 搜索表达式 |
path |
string | 目录收窄,渲染成 path:<value>;含空格自动加引号。目录不存在默认报错,错误消息给出最近存在的父目录与三条出路(确认目录、改通配、省略 path:,或传 pathMustExist: false)。搜索串里已带 path: 时再传 path 直接报参数错误(两个 path: 会 AND 成 0 条) |
pathMustExist |
boolean | 可选,缺省 true。传 false 时 path 不存在不报错,改为忽略该 scope 整盘搜索,结果头明写 path ignored: does not exist |
maxResults |
number | 单次返回条数,1–500,默认见常量 DEFAULT_MAX_RESULTS,文档不写死数字 |
offset |
number | 分页起点,默认 0;接上一条结果里的 nextOffset 继续翻页。只配翻页用:与 countOnly 互斥,countOnly: true 且 offset > 0 直接报参数错误(只报数就没有「第几页」这回事,旧版会静默返回同一个总数) |
exclude |
string | ; 分隔的排除片段,渲染成 !path:<片段>;默认 $RECYCLE.BIN; System Volume Information; WinSxS,传空串关闭 |
sort |
string | 可选,排序字段:name/path/size/dateModified/dateCreated,其它值报参数错误。白名单在 execute 里校验,不走 schema enum(本宿主 DSL 挑食);不传则保持 Everything 默认序 |
descending |
boolean | 可选,把 sort 反序;sort 缺省时按 name 处理,即 descending: true 就是 name 降序(不是「忽略」——这是 0.3.0 起的既有行为,与「不传新参数行为不变」一致) |
countOnly |
boolean | 可选,只报匹配总数:items 为空数组、total 是全集计数,路径/大小/时间的解码整段跳过——先量规模再翻页就用它。与正的 offset 互斥,同时传报参数错误 |
type |
string | 可选,file 或 dir,渲染成 Everything 自己的 file:/folder:(统一小写);其它值报参数错误。白名单在 execute 里校验,不走 schema enum(理由同 sort)。可与 path/sort/countOnly 共存,它只是一个搜索词;搜索串里已带 file:/folder: 时再传 type 直接报参数错误(两个词会 AND 成 0 条),错误消息给两条出路:去掉 type,或去掉搜索串里的那项 |
输出字段:items(每项 path/folder/size/mtime,mtime 是整数 epoch 毫秒(FILETIME 换算后取整,分数毫秒过不了宿主的无损 JSON 判定),读不到给 -1;文本里每条按 路径<TAB>大小<TAB>本地时间 打印,读不到大小或时间都打 -(三列恒在,不留连着的 TAB),不印 1970)、total(命中总数)、truncated、可选 nextOffset、offset(本页起点,钳后的真实值)、countOnly、dbLoaded(Everything 索引是否已加载)、excluded(本次生效的排除规则条数)、pathIgnored(仅当 pathMustExist: false 丢掉了一个不存在的 scope 时带上原 scope)、typeIgnored(仅当搜索串里已带 file:/folder:、type 因此未追加时带上那个 type 值)。
header 形态照实写,别去找不存在的句子:截断时是 Found N of M results (offset X),全量时是 Found M results——Found N of M 只在截断时出现;countOnly: true 时 header 只有 Found <total> matches (count only),items 为空数组、truncated 恒为 false(也就没有 nextOffset)。非 countOnly 一律在其下固定再输出一行 shown N / total M / nextOffset X(没有下一页时 nextOffset 打 -),把「这只是采样」写进结构;countOnly 不出这一行。pathMustExist: false 丢过 scope 时 header 追加 path ignored: does not exist (…),索引未加载时追加 index is not loaded yet。(type not appended 只可能出现在直接调 buildSearch 的路径上;工具面遇到 type 与串内 file:/folder: 撞车已改在执行期报参数错误,不再走到头行。)
everything_status(探活,无参数)
回答一件事:everything_search 现在能不能用。只读索引加载标志与 SDK 版本,不发起查询、不解码结果。输出仅 ok/code/message/version/path,additionalProperties: false。
判决:EVERYTHING_READY(能用)、EVERYTHING_INDEX_NOT_LOADED(Everything 在但索引未加载)、STATUS_SKIPPED / STATUS_ABORTED(探活自身没答,不代表引擎 DOWN)。不提供修复步骤——索引不可用时正解是退回 grep/glob。探测预算钳在 1500–5000ms(探活是诊断不是检索,预算再小也不能把健康引擎报成 DOWN)。
anytxt_search / anytxt_read / anytxt_status / anytxt_sync_index(内容检索,需 Anytxt)
四条工具与 everything_search 一样无条件注册,装好即用,没有开关要记。它们走本机 HTTP JSON-RPC(默认 http://127.0.0.1:9920/rpc,仅默认端口时才试回退 9921/rpc),不绑定任何 DLL,也不经 Everything 的 worker 单例;Anytxt 没开只影响内容检索,everything_search 与 grep 照旧。
前提:Anytxt(ATGUI.exe)在运行,且目标目录已经加进它的索引。dir 只做查询过滤,不建索引——查一个没被索引的目录返回空,而不是报错;要建库或刷新索引用 anytxt_sync_index(见下)。结果是索引里的副本,不是磁盘上的当前文件,所以每次都按 indexed copy, may be stale 对待。
anytxt_search 的调用参数:
| 参数 | 类型 | 说明 |
|---|---|---|
pattern |
string | 必填,内容查询词;支持与/或/非。双引号包起来的整句按整句匹配——引号原样留在 pattern 里发给服务端(实测服务端完全忽略 st,整句语义只由引号决定;st 仍照发,但无作用)。去掉引号就退化成多个词分别匹配 |
dir |
string | 必填,一个已建索引的目录;只过滤,不建库(建库/刷新用 anytxt_sync_index)。空值直接报参数错误,不猜整盘。分隔符会归一(/ 与 \ 等价)——实测正斜杠写法服务端静默回 0 命中 |
ext |
string | 可选扩展名过滤,原样进 filterExt(缺省 *)。多值只认分号分隔 |
limit |
number | 单次条数 1–300(API v1 的整数区间,越界会被 -32602 拒),默认取 anytxtMaxResults(20),越界在 execute 里钳,下发时传真整数 |
offset |
number | 分页起点,接上一条结果里的 nextOffset;钳到 2^31-1(ANYTXT_OFFSET_CAP)——GetResult 只收有符号 32 位整数,实测 2147483648 起被 -32602 拒,钳位就是为这个 |
order |
number | 排序:0 默认 / 1 修改时间升 / 2 修改时间降 / 3 路径升 / 4 路径降(五值真机实测,同一查询各回不同文件),其它值报参数错误。白名单在 execute 里校验,不走 schema enum(理由同 everything_search 的 sort/type)。与 offset 同用时的翻页稳定性未验证——时间序在索引更新时可能错位,长翻页请固定 order: 0 或改用路径序 |
exclude |
string | ; 分隔的路径片段,大小写不敏感的子串匹配,命中即从这一页的结果里剔除;默认 $RECYCLE.BIN; System Volume Information; WinSxS,传空串关闭。生效条数写进结果头((3 exclusion rules applied)),0 条时不印——排除是显式的,不做静默丢结果 |
输出:items(每项 ref、path,size/modified 读不到就整键不写)、total、truncated、offset、可选 nextOffset。模型可见文本里每行是 ref<TAB>路径<TAB>大小<TAB>本地时间——ref 是第一列,也是拿到 anytxt_read 所需 id 的唯一途径(fid 无法从路径推导);大小与时间读不到打 -,ref 那一列恒在。header 固定带一句 indexed copy, may be stale;默认三条排除见 exclude 参数,生效条数写成 (N exclusion rules applied)。翻页口径与 everything_search 一致,但 nextOffset 按服务器原始返回行数推进(不是过滤后条数):实测 GetResult 的窗口随 offset 1:1 滑动,用过滤后条数起头会覆盖原始页尾、把没被排除的行重复发出来。
anytxt_read 的调用参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ref |
string | 必填,anytxt: 前缀的引用,只能来自 anytxt_search;别家前缀直接报参数错误。索引里没有该 fid 时报 ANYTXT_FID_NOT_FOUND(不是「空文档」) |
mode |
string | fragments 或 full,缺省 full;其它值报参数错误 |
query |
string | 仅 fragments 必填:要高亮片段的关键词 |
offset |
number | 仅 full:字符偏移,不是页码 |
max |
number | fragments 段数 1–8(默认 8,服务端上限就是 8:limit 被忽略,传多少都回 8 段);full 字符数 1–40000(默认 8000) |
互斥在 execute 里显式判,一律 ANYTXT_INVALID_ARGUMENT:非法 mode、fragments 缺 query、fragments 传了 offset、full 传了 query(max 两种模式都收:fragments 是段数,full 是字符数)。不靠 schema 拦——本宿主的值 DSL 不收 oneOf/enum,写进去整条工具会被丢掉。输出:fragments 给 snippets/totalSnippets;full 给 text/totalChars/returned/offset/truncated。
⚠️
full模式的传输量没有上限:GetRawTextByFID忽略offset/limit(回显只有{fid},实测一个文件直接回 1,219,343 字符),整篇抽取文本每次调用都会传过来、max/offset只裁返回窗口。所以大文档翻页是每页重下全文,峰值内存是 O(文档) 而不是 O(max)——先用fragments判断值不值得读,再取全文。
anytxt_status:无参数,输出仅 ok/code/message/url(additionalProperties: false)。只答「内容索引现在能不能用」,内部 state 不外露,也不写 version(Anytxt 的 RPC 没有版本方法)。三态:ANYTXT_READY(服务端对 Search 有应答即判可用——0 命中只作附注,因为一次探针分不出「索引为空」和「索引里没有这个词」)、ANYTXT_MISSING_TOOL、ANYTXT_DOWN,探活永不抛。ANYTXT_INDEX_NOT_LOADED 保留在码表里但不再产出;索引是否真的为空由真正的搜索回答(空结果 + indexed copy, may be stale)。
anytxt_sync_index(按目录建库,插件里唯一会改索引状态的一步)
| 参数 | 类型 | 说明 |
|---|---|---|
folder |
string | 必填,绝对 Windows 目录。相对路径、纯正斜杠路径、不存在的目录一律当场报参数错误——实测这三种服务端都回 errno 0 却什么都不建,所以三条都在插件侧拦,不靠服务端 |
输出:folder(归一成反斜杠后的路径)、ok、code、message。回 ok 只表示「请求已被接受」,建库在后台跑,通常数秒后 anytxt_search 就能搜到——确认方式就是再搜一次。删除、移动、重命名一概没有绑定。
💬 提示词说明
插件在加载时向宿主注入两段 system prompt,各由对应组件行注册——关掉哪条行,哪段提示词随之消失(order 取值集中在 lib/shared/prompt-orders.mjs,一行一个数、全局唯一,不许用 +1 凑顺序)。
第一段:name: tool:everything_search,order: 3254,由文件名面注册(lib/components/everything/index.js):
Before work: one everything_search first — find what the machine already has for this task.
Mid-task: something missing? search first, then guess, ask, or stub.
Use: everything_search for whole-disk discovery by name — contents are grep's, in-workspace paths are glob's. Index up? ask everything_status; an empty result still means fall back to grep/glob.
Argument rules live in the tool and parameter descriptions.
Report in three steps: header, paths verified with read or glob, paging; unverified paths stay "candidate, unverified".
第二段:name: tool:anytxt_search,order: 3255,由内容面注册(lib/components/anytxt/index.js):
Use: anytxt_search for text inside PDF/DOCX/XLSX/PPTX/EPUB files - finding files by name stays everything_search's job, workspace source stays grep's.
The Anytxt index is its own copy: hits describe the indexed copy and may be stale, and dir only filters - it indexes nothing.
Read a hit back with anytxt_read: mode fragments for the passages around a query, mode full for the extracted text.
为什么只写这些:prompt 只承载两件事——「要不要调这个工具」(用途与三者的分工)和「怎么汇报」(跨轮行为纪律,比如未核验路径要标成「候选,未核验」)。语法要点、regex: 的引号形态、header 怎么读、参数之间的互斥,全部归工具 description 与各参数 description:那两份每轮都在上下文里,prompt 再抄一遍就是同一句话占两处字符。栈的边界也写进 prompt——按名找文件归 everything_search,工作区源码归 grep,glob 管工作区内路径匹配。
宿主如果没有 systemPrompt.section(唯一软探测项),只记一条 warn,工具照常注册,只是少了这两段引导。
⚙️ 配置
配置必须写在组件行上,写在外壳行无效:外壳 apply() 只做「探测宿主能力 → 组 caps」,不注册任何工具、也不向组件传 caps(实测外壳行配 maxResults/timeoutMs 得到 { tools: [], promptSections: [] },同一份值写在组件行才进 description)。两个行 id 原文:
dsh-tool-everything-everything——maxResults、timeoutMs、everythingDllPath、autoStartdsh-tool-everything-anytxt——anytxtUrl、anytxtTimeoutMs、anytxtMaxResults、anytxtSyncTimeoutMs
缺键一律取默认,坏值(非整数、越界)回退默认——不会以一条无错误码的裸 RangeError 逃出。
| 字段 | 默认 | 说明 |
|---|---|---|
maxResults |
见 lib/index.js 的 DEFAULT_MAX_RESULTS 常量 |
单次返回结果上限(硬上限 500,超出在 execute 里 clamp 到 1–500)。宽查询的大结果按字节触发 spill 落盘,够判断方向,不够就收窄重搜 |
timeoutMs |
8000 |
工具超时预算(ms);FFI 查询阻塞不可中断,故预算偏短 |
everythingDllPath |
包内 native/ |
自定义 Everything64.dll 路径(一般不用) |
autoStart |
false |
索引未加载时是否替你拉一次 Everything.exe -startup、等 ≤3s 并重试一次;默认关,不替用户启动进程 |
anytxtUrl |
http://127.0.0.1:9920/rpc |
Anytxt JSON-RPC 端点,必须是 loopback。/rpc 是 ATGUI 的 RPC 路径(根路径是帮助网页,POST 过去回 404,实测 POST / → 404、POST /rpc → errno:0);只给 origin 时插件自动补 /rpc。只有地址用默认端口时才回退 9921,且只换端口不换路径 |
anytxtTimeoutMs |
15000 |
正文检索单次请求预算(ms),上限同 timeoutMs 的 MAX_TIMER_DELAY_MS |
anytxtMaxResults |
20 |
anytxt_search 的默认单页条数(硬上限 300,与 API v1 的 limit 整数区间一致) |
anytxtSyncTimeoutMs |
60000 |
anytxt_sync_index 的单次预算(ms)。建库比查询慢(小目录实测 6 秒内完成,大目录未知),故与查询的 15 秒分开 |
🧪 验证
回归检查在仓库里:npm run verify(test/verify.mjs;依赖 Everything 在跑的项在它未启动时打 SKIP,退出码仍为 0)。VERIFY_REQUIRE_LIVE=1 时 SKIP 计为失败(退出码非 0),用于「live 必须真跑」的回归。
内容面另有一整套:npm run anytxt:verify(test/anytxt.verify.mjs,全套离线,靠注入 fetchImpl 走完四态与每个 ANYTXT_* 码)。两套一起跑用 npm run verify:all——旧版没有聚合脚本,只跑 verify 会让那 60+ 条 anytxt 断言永远不跑。
verify.mjs 除了跑行为断言,还把文档钉在代码上:README 必须登记编译出的全部参数、三句 header 形态、实测常量那一批数,以及 lib/ 里出现的每一个错误码字面量——改了代码不更文档会直接红。
❌ 错误码
| code | 含义 |
|---|---|
EVERYTHING_SDK_LOAD_FAILED |
找不到或无法加载 Everything64.dll |
EVERYTHING_NOT_RUNNING |
Everything 未运行 |
EVERYTHING_OUT_OF_MEMORY |
Everything SDK 报内存不足(lastError=1) |
EVERYTHING_REGISTER_CLASS_FAILED |
Everything 无法注册其 IPC 窗口类(lastError=3) |
EVERYTHING_CREATE_WINDOW_FAILED |
Everything 无法创建 IPC 窗口(lastError=4) |
EVERYTHING_CREATE_THREAD_FAILED |
Everything 无法创建 IPC 线程(lastError=5) |
EVERYTHING_INVALID_INDEX |
取结果的索引越界(lastError=6) |
EVERYTHING_INVALID_CALL |
SDK 在非法状态下被调用(lastError=7) |
EVERYTHING_INVALID_REQUEST |
Everything 拒绝了这条 IPC 请求(lastError=8) |
EVERYTHING_INVALID_PARAMETER |
查询参数被 Everything 拒绝(lastError=9) |
EVERYTHING_ERROR_0 |
报了失败却没置错误码,通常索引未就绪(码表从 1 起,0 单独解释,见下表注) |
SEARCH_ABORTED |
工具被超时/用户取消终止 |
EVERYTHING_QUERY_FAILED |
worker 回了失败但没带具体 code 时的兜底 code |
EVERYTHING_WORKER_FAILED |
worker 内部抛错(错误分支与启动失败同码) |
EVERYTHING_WORKER_EXITED |
worker 在回答前退出;下一次调用会新建一个 |
EVERYTHING_WORKER_REPLACED |
apply() 重载换掉旧 worker,被中断的那条请求以此码结束 |
EVERYTHING_READY |
everything_status 的判决:索引已加载,搜索可用(ok: true) |
EVERYTHING_INDEX_NOT_LOADED |
everything_status 的判决:Everything 在应答,但索引尚未加载,搜索答不了 |
STATUS_SKIPPED |
everything_status 的探活自身没在预算内答;不代表引擎 DOWN |
STATUS_ABORTED |
everything_status 的探活被取消;不代表引擎 DOWN |
ANYTXT_READY |
anytxt_status 的判决:Anytxt 索引应答了,内容检索可用(ok: true) |
ANYTXT_INDEX_NOT_LOADED |
保留码,不再产出:一次探针的 0 命中分不出「索引为空」与「索引里没有这个词」,据此判 ok:false 会把可用索引报成不可用。索引是否真的为空由真正的搜索回答(空结果 + indexed copy, may be stale) |
ANYTXT_MISSING_TOOL |
找不到 ATGUI.exe 且端点不应答,看起来没装 Anytxt |
ANYTXT_DOWN |
Anytxt 的 JSON-RPC 端点连不上(消息里带 ECONNREFUSED 这类原因码) |
ANYTXT_TIMEOUT |
Anytxt 请求超过预算没答 |
ANYTXT_ABORTED |
Anytxt 请求被取消(工具超时或调用方取消) |
ANYTXT_HTTP |
Anytxt 端点回了非 2xx |
ANYTXT_RPC |
Anytxt 拒绝了这条 JSON-RPC 调用,或回包里没有 result.data.output |
ANYTXT_BAD_OUTPUT |
Anytxt 回的不是期望的 JSON 对象 |
ANYTXT_ENDPOINT_REMOTE |
配置的 Anytxt 地址不是本机回环地址 |
ANYTXT_INVALID_ARGUMENT |
anytxt 三工具的参数在发出请求前就被拒(mode 非法、fragments 缺 query、dir 为空、ref 的 id 不是数字、exclude 之外的可选值非法等) |
ANYTXT_FID_NOT_FOUND |
anytxt_read 的 ref 指向索引里不存在的 fid(实测服务端回 errno 1 + text: null):ref 过期、来自别的机器,或索引重建过。重搜一次拿新 ref——不要把它读成「空文档」 |
ANYTXT_SYNC_ACCEPTED |
anytxt_sync_index 的写路径回执:请求已被服务端接受,建库在后台跑。刻意不借 ANYTXT_READY——那个码是 anytxt_status 的判决,混用会让一次写路径的结果读起来像一次探活判决 |
INVALID_REGEX |
regex: 子句明显写坏(括号或字符类未闭合) |
EVERYTHING_ERROR_<n> |
映射表外的 SDK 错误码,原样透出数值(例如 EVERYTHING_ERROR_99) |
HOST_CAPABILITY_MISSING |
宿主缺少本插件注册所需的接口(消息点名能力名、使用它的函数名与期望/实际) |
HOST_MISSING_SIGNAL |
工具调用上下文没带可用的 exec.signal(aborted 非布尔),超时/取消判断失效 |
HOST_PROBE |
宿主自检探针:probeHost 里造的探针错误,只验证 HarnessError 可继承且带 code,不外露 |
映射表从
lastError=1起:lastError=0(SDK 定义里的EVERYTHING_OK)本工具不走,它在这种语境下是「报了失败却没置错误码,通常索引未就绪」,单独报成EVERYTHING_ERROR_0。
🔎 Anytxt 实测口径(真机探针,非文档抄录)
四条与官方文档或直觉不符,全部实测(anytxt.v1.*,127.0.0.1:9920):
st(精确/高级模式)被服务端完全忽略:它从不出现在回显的input里,而所有真实生效的参数(pattern/filterDir/filterExt/lastModify*/limit/offset/order)都在。整句语义只由pattern内的双引号决定——同一词组带引号 15 条、裸词 69 条;st传 1/传 2/不传,命中数一模一样。旧实现「剥掉引号 + 设st=1」正好删掉了唯一生效的那个信号。filterExt多值只认分号:pdf;docx命中 7 条;pdf,docx命中 0 条,服务端不报错,静默归零。getFragmentAll的limit/topK无效:传 3 恒返 8 段、回显恒limit: 8,段数只能在客户端切,且只能往下切(本插件按实测把对外上限记作 1–8,不再宣称 32)。filterExt: "md"命中 22720 条、接近全量(txt只有 4467 条),行为异常,未验证,不得据它推断扩展名语义。
同批实测另两条:服务只绑回环(非回环 Host/Origin 直接 403),请求体上限 1 MiB;结果描述的是索引里的副本,不是磁盘当前文件。
📐 调用原则与约束
结构与工具的 description 同构,六段,参数在前、汇报在后:
- 用途:全盘按文件名查找,从精确匹配逐步放宽到通配符。
- 原则:一次查询一个意图;顺序为精确名 →
path:/ext:/size:→ 裸通配符。非 countOnly 结果附shown N / total M / nextOffset X一行,头部为Found M results(全集)或Found N of M results (offset X)(截断);翻页用nextOffset,不靠增大maxResults。 - 约束:0 结果 = 索引中无,不写作「文件不存在」;头部提示索引未加载或
dbLoaded: false时空结果不确定(问everything_status;搜索本身不探活);<DIR>是目录,不读取;countOnly不与正数offset同传;path缺失报最近存在的父目录,pathMustExist: false则降级为忽略该范围。 - 语义:
maxResults的默认值与上限见参数表(由常量决定,不写死数字);type: file→file:、type: dir→folder:;不传sort且descending: true即 name descending,不被忽略;regex:支持双引号形态承载空格(如regex:"final report");mtime为整数 epoch 毫秒。 - 报告:原样引用头部 → 已核验路径 → 翻页方式;未核验或仅见于抽样的路径标为「候选,未核验」,不得以单次抽样的条数断言全盘命中数量。
- 范围:默认排除
$RECYCLE.BIN、System Volume Information、WinSxS;传exclude:""可包含它们。
🧩 技术细节
┌─────────────────────────────────────────────┐
│ DSH Agent (你) │
│ 调用 everything_search("npm", max=10) │
├─────────────────────────────────────────────┤
│ dsh-tool-everything (本插件) │
│ koffi FFI → Everything64.dll │
├─────────────────────────────────────────────┤
│ Everything SDK (IPC 通道) │
├─────────────────────────────────────────────┤
│ Everything.exe (全盘 NTFS 索引引擎) │
└─────────────────────────────────────────────┘
Everything64.dll来自 Everything SDK 1.5;Anytxt 侧只走127.0.0.1:9920的 JSON-RPC,不绑任何 DLL- FFI 通过 koffi 实现(本包
dependencies里的koffi);绑定与实测常量集中在lib/components/everything/sdk.mjs,主线程与 worker 共用一份(两份必漂) - 查询跑在 worker 线程(
lib/components/everything/query-worker.mjs):Everything_QueryW是阻塞调用,搬进 worker 后事件循环不再被它卡住;主线程只保留单在途链条,5 条并发不会同时塞进 DLL(并发是排队不是并行:一次只放一条进 worker,按 request id 的真并发还没做) - 在途原生查询不可取消(实测,不是推断):
worker.terminate()在Everything_QueryW在途时返回只要 6–8 ms,但线程是在原生调用返回之后才真正退出——terminate 之后它还会补发一条结果。所以 abort 的处理是「换新 worker、丢掉旧回复」,不宣称能打断已在 DLL 里的调用 - 插件 ESM 模块(外壳
lib/index.js+ 两条组件行lib/components/everything/、lib/components/anytxt/+ 共享件lib/shared/),Cordis 插件规范 - 兼容性靠能力检测,不靠版本区间:
engines.dsh与各条 DSHpeerDependencies同声明>=0.2.0-rc.1 <1.0.0,只作安装期兜底提示。插件apply()开头调用probeHost(ctx),逐项探测真正用到的宿主接口,缺一项即抛HOST_CAPABILITY_MISSING,消息点名能力名 + 使用它的函数名 + 期望/实际,不写「版本不匹配」, 也不写行号(行号每改一次就漂一次,写死等于埋新坑) - 探测项:
defineTool为函数、HarnessError可继承且带code、MAX_TIMER_DELAY_MS为正有限数、schemastery 默认值填充真的生效、ctx.tools.register为函数、defineTool接受现定义形(parameters+output.schema+output.render,返回对象含execute——实测缺output.render会被defineTool直接拒);ctx.systemPrompt.section是唯一软项——缺了只记一条 warn,工具照注册 - 首次
execute另做一次性探针:exec.signal.aborted必须是布尔,缺则抛HOST_MISSING_SIGNAL(只查一次,此后不再重复)
开发者备注:SDK 常量一律实测,不要按文档手抄
仓库里只有 native/Everything64.dll,没有 SDK 头文件。下面这批绑定——SORT_VALUES(五档)与 SORT_DESCENDING_OFFSET、REQUEST_FLAG_FULL_PATH / REQUEST_FLAG_SIZE / REQUEST_FLAG_DATE_MODIFIED 及其合起来的 84、FILETIME_EPOCH_OFFSET_MS——是在本机用这份 DLL 实测钉死的,test/verify.mjs 有断言兜着;改这些绑定前先跑一遍 verify:
Everything_SetSort降序 = 升序 +1,不是 +100。实测name升序1与降序2:升序尾页与降序首页互为反序;换成101拿回的还是升序。SORT 表为name 1/path 3/size 5/dateModified 13/dateCreated 15,降序走SORT_DESCENDING_OFFSET常量偏移,不手抄成第二张表。Everything_SetRequestFlags:FULL_PATH = 4、SIZE = 16、DATE_MODIFIED = 64。 flags 必须凑齐 84(4|16|64)才读得回路径、size、mtime——只设16|64时Everything_GetResultFullPathNameW全部返回空串,只设其一则另一个读回-1。 每一处读回都与它的 flag 成对,countOnly一个都不设、一条都不解码。SetSort是进程级查询状态:一次带排序的查询会漏给下一次(实测SetSort(5)之后不传排序的查询仍是 size 升序)。所以未传sort时显式设回name升序 (Everything 默认序),「不传新参数时行为与旧版一致」才成立。- FILETIME → epoch 毫秒:
Math.round(Number(koffi.decode(buf,"int64")) / 10000) - 11644473600000(必须取整:一个 tick 是 100 ns,不取整就是分数毫秒,宿主会判 value is not lossless JSON), 对应常量FILETIME_EPOCH_OFFSET_MS;GetResultDateModified与DATE_MODIFIED必须成对,缺则读回全0/-1。
Anytxt 侧的两条实测常量
- 读 fid 不存在的文档回
errno 1(有效 fid 回errno 0),text为null——anytxt_read靠这条区分「没有这个 fid」与「空文档」;GetFragmentAll两种都回errno 0,分不出。 GetResult的offset窗口 1:1 滑动(实测 offset 0/1/3 得到错开一行的三页),所以翻页必须按原始行数前进。
🧭 设计边界
- Everything 侧只读:
everything_search与只读探活everything_status,SDK 侧只绑定Everything_Set*、Everything_QueryW、Everything_Get*,没有写、删、移动、同步、OCR 的入口。内容面另有四条anytxt_*工具,其中唯一会改索引状态的是anytxt_sync_index(对用户显式给出的绝对目录建库,只增不改,不删不移),其余三条只读;工作区路径匹配归glob。 - 不替你起进程:
autoStart默认false。 - 在途查询不可取消(实测):
worker.terminate()在原生调用在途时 6–8ms 返回,但线程要等调用返回才真退,并会补发一条回复;所以 abort 是「换新 worker、丢掉旧回复」。 - 错误码分三层:SDK 层(索引不可用 → 退回
grep/glob)、worker 与取消层(重试)、宿主能力层(装或升级 DSH)。 - 默认排除是显式的:回收站、系统卷信息、WinSxS 三条默认规则会写进结果头,不做静默丢结果。
完整理由、数据流与只读边界见 docs/architecture.md。
🔀 本 fork 相对上游的差异
本仓库 fork 自 roxyyn0304/dsh-tool-everything 的 0.4.14。
上游的架构分层、实测常量(SDK sort/flag/FILETIME 那批)、只读边界与工具分工一律保留,本 fork 只做修复、测试与文档同步:
| 面 | 相对上游的改动 |
|---|---|
内容面(anytxt_*) |
命中行渲染 ref(在此之前 anytxt_read 拿不到入口);引号整句真正生效(上游剥掉引号后整句退化成多词);dir 统一分隔符(上游正斜杠写法静默 0 命中);offset 上限改为 2^31-1(上游沿用了 Everything 的 uint32,越界被服务端拒后报成后端故障);默认排除写进结果头并可用 exclude: "" 关闭;nextOffset 改按服务器原始行数前进(上游用过滤后条数,会重复发未排除的行);anytxt_status 不再把「探针 0 命中」判成索引不可用;anytxt_read 对不存在的 fid 报 ANYTXT_FID_NOT_FOUND(上游静默返回 0 of 0 characters);modified 列改本地时间(上游印 UTC 墙钟,与 Everything 那列差一个时区) |
文件名面(everything_*) |
只有一处:path 与搜索串里已有的 path: 撞车,从静默丢弃改为报参数错误。不传这两个参数时行为与上游一致 |
| 配置 | cordis.patch.yml 的 config: 从外壳行移到两条组件行——外壳行只做能力探测、不注册工具,写在那里不生效 |
| 健壮性 | autoStart 的候选 exe 列表改为按存在性依次尝试(上游实际只试第一个);组件行入口补整数/范围校验,坏预算不再以裸 RangeError 逃出 |
| 测试与文档 | 新增 test/anytxt.verify.mjs(全套离线,注入 fetchImpl 走完四态与每个 ANYTXT_* 码)与 npm run verify:all;补齐 README/docs/architecture.md 里重构后残留的陈旧标识符 |
逐条理由与验收见上文「0.4.14 → 0.5.0 的破坏性变更」与「0.5.1 修复」两节。
📄 License
MIT。原始版权归原作者 roxyyn0304 所有; 上游 LICENSE 原文保留、未做任何改动,本 fork 自身的修改同样以 MIT 发布。
🙏 致谢
- roxyyn0304 — 本插件的原作者,本 fork
- voidtools — Everything 文件搜索引擎
- koffi — Node.js FFI 库
- deepseek-ai/deepseek-harness — DSH Agent 框架
No comments yet. Be the first to write one.