JayYuen666/dsh-zvec-grep ↗★ 0

@jayyuen66/dsh-zvec-grep

为工作区提供语义检索工具、路由规则与安全防护 适合需要在大型工作区中进行混合检索、强制先检索后读取的开发任务。

包名
@jayyuen66/dsh-zvec-grep
兼容性
待验证
版本
0.1.1
许可证
MIT
最近更新
2026年10月2日

安装

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:JayYuen666/dsh-zvec-grep

@jayyuen66/dsh-zvec-grep

中文 · English

中文

它做什么

  • 给 dsh 会话接上工作区语义检索:注册 zg_search / zg_index / zg_status 三个模型工具,底层经宿主 shell 以 --mode direct 调 zg CLI(BM25 + 向量 + RRF 混合检索)。
  • 注入一段 systemPrompt 硬规则(zvec-grep-routing,order 1550)+ 两道 tools.guard:zg_* 安全兜底与 search-first 门禁(已建索引的工作区里,grep/rg 前必须先有一次成功的 zg_search)。
  • 附带 web 端设置卡:默认 embedding、limit、search-first 配额/时效,以及「工作区重建」按钮。

安装

dsh plugin --profile web add @jayyuen66/dsh-zvec-grep
  • 包在公共 npm 上,安装不需要凭据;发布态产物只有 host.js、client.js、cordis.patch.yml(package.json 的 files)。
  • 运行期值 import @jayyuen66/dsh-plugin-shared(lib/http、lib/locale)与 @deepseek-ai/schemastery(宿主 fork:0.1.7 的 .volatile() 解析只有它有实现,公共 schemastery 既没这个方法、解析出的也仍是普通值),两者都在 dependencies 里,装不上就是 ERR_MODULE_NOT_FOUND。

在 dsh 里启用

  • add/remove 按包内 cordis.patch.yml 维护注册行 - id: zvec-grep(name 指向本包);该文件由 package.json 的 dsh.bundle.patch 指向。
  • 宿主版本要求 >=0.2.0-rc.2:写在 peerDependencies(0.1.7-rc 起宿主装插件时校验它;alpha.1 还没有这道门)与 engines.dsh(同值、无人读)。
  • dsh.client.platform 为 web 且 immediately,设置卡的 client 半随宿主下发;bundle 安装走 pnpm(>= 12.3.0)。
  • apply 需要 settings/shell/tools/systemPrompt/timer 五个服务面:它们都在 inject 里,缺任一 cordis 就不激活本条目(宿主启动诊断点名 pending (waiting for service: …));apply 里的守卫再兜一层,ctx 形状不对时抛 required services … missing。
    • 三条 /_dsh/zvec-grep/* 端点挂在 inject(["webServer"]) 的子 fiber 上:宿主没有 webServer(如 TUI)时子 fiber 不激活,端点不存在而工具照常。真实宿主上 webServer 比本条目晚到位约 1 秒,所以这里必须是依赖、不能只在 apply 里 ctx.get 读一次。

提供给模型的工具

  • zg_search:query / queries / fts / vector 至少其一(合计 --capability embedding --scope workspace` 才肯发请求,那道按 root 的授权本插件不代劳。
    • requireApprovalForExplicitRoot=false:打开后,三个 zg 工具里显式给了 root、且那个 root 既不是本会话工作区、也不在本会话已登记范围内时,会经官方 tools/pre-execute → {kind:'ask'} → ctx.approval 弹一次用户确认。默认关,且有三个已知边界:danger-full-access 预设把 approval/policy 写成 never(ask 会被确定性拒绝)、委派子代理被钉死 approvalPolicy: 'never'(该开关对子代理无效)、宿主没装 approval 服务时 fail-closed 成拒绝。这道确认只加不减:rootOf 的授权判据一字未改,越界的 root 即便「用户已批准」照旧被拒。
  • 优先级:设置里的运行时值 > 组合包层/用户层行的 config > schema 的 .default();行 config 中显式缺省的字段不覆盖默认。读侧是 config..get()(volatile 引用,卡上改完下一个事件即生效);非 volatile 部署值是普通值形态,不经 .get()。
  • root 校验:必须是以 / 开头的绝对路径,词典归一折叠空段与 .。
    • 拒绝空串、非字符串、含 NUL、相对路径、越出文件系统根的 ..,归一后为 / 也拒。
    • 目录不存在时单报「root 目录不存在」,不冒成 spawn ENOENT。
  • guard 与 execute 用同一套 root 判据,避免「guard 放行、execute 抛错」说两遍。
  • 解锁按「会话 × 索引根」计:每次成功 zg_search 重置为 grepBudgetPerSearch 次、unlockWindowMin 分钟有效;索引根是 root 自身或含索引的最近祖先(最多上溯 8 层)。
    • 分片用的会话是根会话:沿 header.parentSession 一路上溯到最顶层那一跳。故同一棵委派树上的主代理与子代理共享一份额度——主代理搜过一次,子代理随即就能用那几次 grep/rg,不必各自再搜一次(它们搜的往往是同一棵树)。
    • 上溯的降级是三级的,都不抛错:自身即顶层就用自己;某一跳查不到它的父(会话已落定,或宿主没装会话存储)就停在已知的最后一跳;链成环就停在首次重复前。跳数有上限。
  • 「含索引」的判据是 /.zvec-grep/manifest.json 在盘上,只判 .zvec-grep 目录名不够:这个名字同时是 zg 自己的全局 home(ZVEC_GREP_HOME ?? ~/.zvec-grep,装的是 config.json / locks / models),只判目录名会让一个从未建索引、只是祖先目录撞名的普通工作区也被门禁拦下。
  • enforceSearchFirst=false 只关掉门禁,zg_* 的安全兜底(confirm、root 判据)不受影响。

外部依赖(rg)

  • 真正的二进制依赖是 zg(由 npm install -g @zvec/zvec-grep 提供):不在 PATH 时 shell 回 exit 127,工具据此回「zg 未安装或不在 PATH」,与「沙箱 runner 没起来」分开归因。
  • 本地 embedding 由 zg 侧后端加载:qwen3/embeddinggemma 走 llama-cpp(device=auto 时试 Metal、失败回退 CPU),potion-* 走 model2vec,其余走 transformers-js 纯 JS。
    • 权重按 hfEndpoint 注入 HF_ENDPOINT 后下载。
  • 本包不依赖也不调用 ripgrep:rg 只出现在门禁判据里。
    • bash/pwsh 命令串经引号/注释感知切词后,命令位上的 grep/egrep/fgrep/rg 与原生 grep 工具才算检索。
    • --help/--version/-V、echo "grep x"、git commit -m "use rg" 不算。
  • 有沙箱执行器时会区分 runner 失败、策略拒绝与 zg 自身失败,并在结果里显式打「可能不完整」标记。

对外接口

  • 三条路由的 handler 第一句都是 guardTrust(req, res, { servingNonLoopback })(shared/lib/trust),任一判据不过即 403 + { ok:false, error: ... }。
    • 判据次序:Host 权威 → sec-fetch-site 白名单 → origin 与 Host 逐字比对;两句错误文本是 "untrusted host authority" 与 "cross-origin request rejected"。
    • servingNonLoopback 只取 webServer.host === "0.0.0.0";非本方法的 405 带 Allow 与 { ok:false, error:"GET only" | "POST only" },不再是空回显。
  • GET /_dsh/zvec-grep/rebuild-roots:只回本包自己的数据(每次 apply 重新生成的 CSRF token + 已观测工作区列表),允许 GET/HEAD,过不了上面那道闸门即 403。
  • POST /_dsh/zvec-grep/rebuild?root=...:闸门 + 头 x-zvec-grep-csrf + body 0,看不见了);缓冲区溢出= 执行器的读者报 lossy 或干脆抛了(真少了字节,可能有 spill 文件)。超时说明与沙箱事实**不进环**:环的写权归注册表的泵,而它在落定之后丢弃一切写入(实测),所以这两条由端点在投影时合成,永远不会因为"写晚了"而消失。注册表被重载或记录被别人收走时轮询答 **404**(官方get/readAt对未知 id 抛错,而宿主对 handler 抛出的兜法是记一条日志再写一枚没有 body、没有 content-type 的 **400**,只有头已发时才断掉 socket —— installed dsh-host-webserverlib/index.js:246-256` ⇒ 卡片会进终止态并停轮询,那条还在跑的重建就再也看不见)。

数据与隐私

  • 索引的唯一落盘形状是被检索 root 下的 .zvec-grep/ 目录,由 zg 写入(read-only 沙箱下写不了它,会明确报索引可能不完整)。
  • 重建有两条路:模型侧 zg_index(rebuild=true、confirm=true),或设置卡「工作区重建」走上面的 rebuild 路由;删除是 zg_index drop=true(拼成 zg index --drop --yes)。
  • 本包自身不写任何文件(唯一的 fs 调用是索引探测:existsSync + 读一次清单),也不向宿主数据目录 $DSH_HOME(``)落数据。
    • 索引探测不只判「.zvec-grep/manifest.json 在不在」:还要解析清单并确认它的 rootPaths 真的覆盖这个目录。两种实况会被「只判存在」误判成有索引,而实际检索必然失败——文件在但不是一份清单(半截写入/同名占用),以及清单在但根路径指向别处。此时门禁若还按「已建索引」拦着 grep/rg,就等于把检索与门禁双向堵死。
    • 认的结构只有 manifestVersion(严格等于 1)与 rootPaths(非空数组、每项带非空 absolutePath)两处——版本不等时上游读它会直接抛 MANIFEST.INVALID,此时这个目录一次检索都做不了,宽松放行只会把用户卡在「门禁拦着、检索又必失败」的死角里。刻意不校验 embedding / policy 等字段——那些变了索引照样能用,判成「无索引」反而是错的。
    • 覆盖判据是归一后相等;macOS 上清单里写的是未解析路径(/tmp/x)而 path 字段才是解析过的(/private/tmp/x),故两边都再剥一次 /private 前缀再比,两种写法都能对上。
    • 白名单/解锁表都在内存、有界,且淘汰策略与语义相符:命中即触摸(挪到队尾),淘汰时先按有效期丢过期项、再按最近使用丢最冷的一条到上限之内——不是按插入序。纯插入序对一张「每次成功检索都重置同一批键」的表等于不淘汰:真正冷掉的项永远留着,而很久没被碰过的键一直占位。白名单条目另有一条有效期(一天),一个月前顺手开过的一个目录不该今天还占名额。容量上界(白名单 64 条 / 会话分片 256 个)保持不变,故常驻内存依旧有界。
    • 重建作业本体住在宿主注册表里(见上一节),插件卸载时本包侧表随 effect 回收、在跑的作业经注册表 kill 掉。
  • 默认不进索引的:*.pem / *.key / *.p12 / *.pfx / *.keystore / id_rsa* / id_ed25519*(excludeSecrets=false 可关)。这些规则按大小写不敏感匹配,Server.PEM 同样被排除;且排在用户自己的路径规则之后生效,用户写一条宽泛的正向规则也覆盖不掉它们。
    • 残留缺口:无扩展名的密钥文件(如 myprivatekey、rsa_private)扩展名规则覆盖不到,id_rsa* / id_ed25519* 只覆盖 OpenSSH 默认命名;这类文件请走 ignoreFiles(--ignore-file)显式声明。
  • 隐藏文件默认不收,ignore 文件默认生效(noIgnore 可关)。
  • 本包只在本机拼 zg 命令并透传输出,不上传代码。
    • 唯一的外连是 embedding 权重按 hfEndpoint 下载。
    • 结果回显有界:stdout 超限截尾(有 spill 路径会给出)、stderr 尾部 400 字符一并回显,免得「退化成纯词法」被当成完整语义结果。

常见问题

  • grep/rg 被拒且提示「尚未执行过 zg_search」:这是 search-first 门禁,先 zg_search;配额用尽或过期同理,换个更准的 query/fts 重搜即可续上,不必关插件。
  • 检索超时:默认 --refresh wait 会同步刷新既有索引,大工作区请用 globs/fileTypes 缩小范围或调小 limit,也可先手写字面检索。
  • 重建按钮回 403「root 不在本 daemon 观测到的工作区白名单内」:该目录从未在本 daemon 开过会话;先在该目录起会话,或让模型带 confirm=true 调 zg_index。
  • 卡片显示重建失败而 zg 看着像跑完了:成功判据是 completed 且 exit 0——exit 127(zg 没装)也落在 completed,看输出尾部原因。
  • 从未建索引的 root:zg_search 明确报错而不是静默建索引;建索引只在用户明确要求时由 zg_index 做。
  • 换了 embedding 却搜不到语义结果:切换 embedding 必须 rebuild=true 重建后才生效。

English

What it does

  • Adds semantic workspace search to dsh sessions: registers three model tools, zg_search / zg_index / zg_status, which drive the zg CLI (BM25 + vector + RRF hybrid) through the host shell with --mode direct.
  • Injects one systemPrompt rule section (zvec-grep-routing, order 1550) plus two tools.guard layers: safety backstop for zg_* and the search-first gate (in an indexed workspace, grep/rg requires a prior successful zg_search).
  • Ships a web settings card: default embedding, limit, search-first quota/window, and a "rebuild workspace" button.

Installation

dsh plugin --profile web add @jayyuen66/dsh-zvec-grep
  • The packages are on the public npm registry, so no credentials are needed; the published artifact only carries host.js, client.js and cordis.patch.yml (the files field in package.json).
  • Runtime value imports are @jayyuen66/dsh-plugin-shared (lib/http, lib/locale) and @deepseek-ai/schemastery (the host's fork — the only one whose resolve wraps .volatile() fields into Volatile references); both sit in dependencies.
    • Missing either one is ERR_MODULE_NOT_FOUND at loa