返回目录
其他 待识别

dsh-lazy-tools

studyzy/dsh-lazy-tools

该仓库暂未提供项目说明。

Stars
2
Forks
0
Issues
1
更新
4 天前

PROJECT TOPICS

项目标签

PROJECT README

README

dsh-lazy-tools

为 DeepSeek Harness 提供的 CodeBuddy 风格延迟工具加载插件。

一个 tool_search + defer_execute_tool 覆盖层,让工具 schema 在真正需要之前 不会进入模型上下文。

减少 token 消耗 · 缩小上下文窗口 · 保持工具可发现

CI License: MIT Node

English


它是做什么的

大多数编码 Agent 会把每一个可用工具的 JSON Schema 都塞进提示词—— 包括模型最终根本不会用到的工具。dsh-lazy-tools 颠覆了这个模型:工具 默认被延迟(deferred),只有当模型主动请求时才进入上下文。

装上即用、零配置:默认延迟长尾工具,同时保留一组常驻编码核心 (read/write/edit/bash/glob/grep/web_search/web_fetch/ ask_user_question/skill,详见默认行为)。这个核心只是起点: 开启自动调优后,插件会按项目统计真实使用情况,把每个项目里最常用的工具设为 常驻、其余全部延迟——所以每个项目最终拿到的是适合它自己的那份配置。

被延迟的工具会从模型可见的工具列表里剔除,因此它们的 schema(乃至名字) 永远不会进入模型上下文。模型通过 tool_search 按需发现它们,激活后 即可直接调用。

剔除发生在 system-prompt/assemble 这条 waterfall 上——也就是 agent loop 真正发给模型的那份 per-scope 工具表(它同时决定请求头与提供商的工具声明)。 本插件不修改工具注册表,所以它不关心工具来自哪个平面。

这是一个纯粹的暴露控制层:

  • 它不拥有、也不实现任何工具。
  • 被延迟的工具是注册表里的普通工具(宿主 composition、agent preset、MCP 等 第三方工具皆可);本插件只从模型可见列表里观察它们的 name / description / parameters 用于发现。
  • 它不代理执行——tool_search / defer_execute_tool 只负责激活,激活后 模型直接调用该工具本身。

因为完全实现在 agent 层,它适用于任何模型与任何提供商(DeepSeek、 OpenAI、Anthropic、Gemini……),不依赖提供商原生的延迟工具协议(如 Anthropic tool_reference 或 OpenAI 的 deferred-tool input items)。

特性

  • 🔍 按需发现 — tool_search 通过精确工具名(tool_names)或关键词 (queries,中英文均可)查找工具,命中即激活。
  • ⚡ 懒激活 — 命中的工具记入该 agent 的激活集合,下一轮模型请求即可 直接调用。
  • ⚡ 按名激活 — defer_execute_tool 按精确工具名直接激活一个已知工具。
  • 🧭 无侵入 — 不碰注册表:只在 system-prompt/assemble 上对模型可见的 工具列表做减法,因此与任何其他 restrict、任何工具来源、任何平面天然共存, 绝不放宽其他策略。
  • 🎛️ 灵活配置 — CodeBuddy 风格 Defer(...) / NoDefer(...) 模式,支持 * 通配符。
  • 🛡️ 自保守卫 — tool_search 与 defer_execute_tool 注册在 agent 自身作用域, 永不被延迟;保留传输 run_code 同样被钉住,Defer(*) 无法把系统锁死。
  • 📊 按项目自动调优 — 启动后第一个汇报工作目录的会话会扫描该项目最近 30 天的会话历史,统计 tool/call 频率,把最常用的 20 个工具设为常驻、其余 全部延迟。
  • 🗂️ 全局 + 项目级两层配置 — 你手写的全局 defer/noDefer 对所有项目 生效;自动调优的结果只写进独立的项目级存储,按项目覆盖全局。优先级 项目级 > 全局。全局配置永远不会被扫描结果改写。

安装

从 GitHub 仓库安装为外部 bundle:

dsh plugin --profile <profile> add git:github.com/studyzy/dsh-lazy-tools

也可以先 git clone 到本地,再从本地目录安装。该 bundle 通过 cordis.patch.yml 注入一个名为 lazy-tools 的插件。

兼容性

本插件面向 DeepSeek Harness 0.2.0-rc.2(@deepseek-ai/dsh-* 0.2.0-rc.2、 @deepseek-ai/cordis 4.0.4、@deepseek-ai/schemastery 3.18.4)。 package.json 的 peerDependencies 声明了这些精确版本,因此 DSH 的 插件兼容性预检会直接放行:

# 本地目录安装(开发时更新依赖后刷新 profile 依赖树)
dsh plugin --profile <profile> add link:/path/to/dsh-lazy-tools

升级 DSH 后若预检提示 peer 版本不匹配,把 package.json 的 peerDependencies / devDependencies 对齐到新的 @deepseek-ai/dsh-* 版本,重新 pnpm install && pnpm run check,再执行一次上面的 add 即可。

配置

零配置即可用:不写任何 config 时插件使用内置预设,装完就是这样。

默认行为

内容
延迟 Defer(*) —— 除守卫外全部延迟,模型按需 tool_search
常驻可用 read、write、edit、bash、glob、grep、web_search、web_fetch、ask_user_question、skill
守卫(永不被延迟) tool_search、defer_execute_tool、run_code

上表是全局默认,也就是每个项目在还没被自动调优前的起点。开启自动调优 (默认开启)后,某个项目一旦积累够历史,就会用该项目自己的统计结果覆盖它 ——详见"配置分层"与"按项目自动调优"。想让所有项目都固定用这套默认,设 autoTune: false。

一旦显式写了 defer 或 noDefer 中的任意一个键,预设就被完全替换,一切按你写的来: defer: [] 仍然是"不延迟任何工具",defer: ['glob'] 就是只延迟 glob (不会因为 glob 在默认核心里而被重新保护)。

需要改默认时才写配置,位置是 profile 的 cordis.patch.yml(用户 patch 层) 中该插件的 config 字段,使用 CodeBuddy 风格语法:

- id: lazy-tools
  name: '@deepseek-ai/dsh-lazy-tools'
  config:
    defer: ['glob', 'web_search', 'Defer(fetch_*)']
    noDefer: ['bash']
键 类型 默认 说明
defer string[] 见"默认行为" 要延迟的工具名或 Defer(pattern) 条目。裸名等价于 Defer(name)。* 是唯一通配符——Defer(*) 延迟除守卫工具外的所有工具。
noDefer string[] 见"默认行为" 必须保持可直接调用的工具名或 NoDefer(pattern) 条目。裸名等价于 NoDefer(name)。始终优先于 defer。
autoTune boolean true 是否按本项目会话历史自动生成 defer/noDefer,见"按项目自动调优"。
autoTuneWindowDays number 30 统计窗口天数。
autoTuneTopN number 20 自动调优保留多少个最常用工具常驻。
autoTuneMinSamples number 200 窗口内调用总数达到该值才允许改写配置。

修饰符大小写不敏感(defer(bash) ≡ Defer(bash))。优先级(从高到低): noDefer > defer。

想彻底关掉延迟加载? 写 defer: [] 就行,不需要额外的开关——空列表本身 就表示"不延迟任何工具",再挂一个布尔开关只会和它互相矛盾。

在界面里配置

Web / 桌面版还有一块可视化配置页,不用手写 YAML:打开 设置(Settings)→ 内置插件 (Built-in plugins),切到 懒加载工具(Lazy tools) 标签页即可。它与其它功能自带 的配置标签页(只读清单、建议提示词等)并列在同一处。

页面上六个字段与上表一一对应:defer 和 noDefer 是逗号分隔的输入框(例如 Defer(*), web_fetch, glob;也接受换行分隔,两者可混用。留空=清除该键、回退到 组合层提供的值,而不是写入空数组);autoTune 是开关;其余三个是数字输入。 改动点保存才生效,离开页面即丢弃草稿。

未配置时,defer / noDefer 两个框会直接显示当前实际生效的内置预设 (Defer(*) 与那一串常驻工具),而不是空框——它们不算"已覆盖",因此不带覆盖标记。 这样你看到的就等于插件真正在执行的值。

想彻底关掉延迟加载? 把 defer 框清空保存即可(等价于 defer: [])。

为什么用逗号而不是"一行一个":DSH 共用的 SettingsValueField 渲染的是单行 <input type="text">,浏览器会把渲染值里的换行去掉。若用换行分隔,bash, read 会显示成 bashread,保存时被当成一个工具名写回去,直接破坏列表。

保存写进的是该插件在 profile patch 里的 config,也就是全局层;项目级覆盖仍由 自动调优写入 ~/.dsh/lazy-tools/projects.json,界面上不会被改写。保存后配置当场 生效,不需要重启:插件每次都从实时配置重新解析延迟规则,保存会触发一次重新排名, 下一次请求就用新规则。

页面由插件自带的浏览器端一半提供(dsh.client 声明 + lib/client.js),只在 Host 真正加载了本插件时才出现;纯 headless 组合里不会有这个页面,界面也不会显示该标签页。

配置分层:全局 Defer + 项目级 Defer

配置分两层,优先级 项目级 > 全局:

层 写在哪 谁写 作用范围
全局 profile 的 cordis.patch.yml 里 config.defer / config.noDefer 你手写 所有项目
项目级 ~/.dsh/lazy-tools/projects.json 自动扫描生成 仅该项目

自动生成的条目里还记录了 tunedAt(上次刷新时间)、sampleCalls(参与统计的 调用数)和 sessions(会话数),方便你判断这条配置的新鲜度和可信度。

解析规则:某项目在项目级存储里有条目就用它;没有就回退到全局配置。 自动调优只写项目级存储,绝不改动你的全局配置——所以你在多仓库间切换时, 手写的全局规则不会被某个项目的扫描结果覆盖。

projects.json 的样子(键是项目的绝对路径):

{
  "version": 1,
  "projects": {
    "/Users/me/Code/api": {
      "defer": ["Defer(*)"],
      "noDefer": ["bash", "edit", "read"],
      "tunedAt": 1790000000000,
      "sampleCalls": 412,
      "sessions": 6
    }
  }
}

项目级条目是整体替换而非合并 defer/noDefer:否则全局的 Defer(git_*) 会继续在该项目生效,导致工具被两条规则同时延迟、无法按项目重新启用。全局中 与模式无关的开关(autoTune*)仍然沿用。

想清掉某个项目的自动调优?删掉 projects.json 里对应条目(或整个文件)即可, 该项目立即回退到全局配置。

按项目自动调优

启用后(默认开启),插件会按项目统计真实使用情况并生成项目级配置:启动后 第一个汇报工作目录的会话,触发一次对该项目最近 30 天会话历史的扫描。

统计口径

  • 数据来自 DSH 落盘的会话日志(~/.dsh/sessions/<项目目录>/)。每个 tool/call 事件记录一次工具调用。
  • defer_execute_tool 的一次调用,计入它激活的那个工具——模型反复主动 激活某个被延迟的工具,正是"它不该被延迟"的信号,因此会自动升为常驻。
  • 按窗口内调用次数降序排序(次数相同按名称升序),取前 autoTuneTopN 个 写入 noDefer,并设置 defer: ['Defer(*)'] 延迟其余全部。

三条安全边界

  • 一天只刷新一次:条目里记录 tunedAt(上次刷新时间)。同一本地自然日 内再次启动,直接沿用已有条目并完全跳过扫描,不读历史、不写盘。跨过本地 零点(而非「满 24 小时」)后才会重新扫描。
  • 样本不足不改写:窗口内调用总数低于 autoTuneMinSamples(默认 200)时 只记日志、保持现状(该项目继续用全局配置),避免用噪音覆盖你的配置。
  • 只写自己的文件:只动 ~/.dsh/lazy-tools/projects.json,不碰 profile 配置。

按自然日而不是「满 24 小时」判定,是因为统计窗口本身以天为单位:昨天 23:00 调 过一次、今天 08:00 又启动,虽然只隔 9 小时,但数据窗口已经前移了一天,重新扫 描是有意义的。判定用本地日期分量比较(而非时间戳相减),因此在夏令时切换当天 (本地一天可能是 23 或 25 小时)依然正确。

若某天扫描后发现排序没变,条目只更新 tunedAt 时间戳,defer/noDefer 保持 不变——这样当天剩余时间同样被节流。

存储在插件安装时同步读入内存,因此会话的第一个请求就已经生效;同进程内某 项目扫描完成并写入后,会立即重新计算该项目 agent 的工具可见性,无需重启。单个 项目只调优一次(按进程内存记忆,含并发去重);写入是原子替换,且多个项目并发 调优不会互相丢失条目。任何失败都被限制在后台任务内,不影响会话启动。

# 调整调优参数
config:
  autoTuneTopN: 12          # 只常驻最常用的 12 个
  autoTuneWindowDays: 60    # 用 60 天历史
  autoTuneMinSamples: 50    # 小项目也允许调优
# 完全关闭自动调优,只用你写的全局配置
config:
  autoTune: false
# 典型用法:全局给一套保守规则,让自动调优在各项目里细化
config:
  defer: ['Defer(*)']
  noDefer: ['bash', 'read', 'edit', 'write']

示例

# 把默认核心换成只有 bash 常驻(其余仍然全部延迟)
config:
  noDefer: ['bash']
# 只延迟这两个,其余保持可见
config:
  defer: ['glob', 'web_search']
# 延迟所有 fetch_* / web_* 工具,始终保持 bash 可用
config:
  defer: ['Defer(fetch_*)', 'Defer(web_*)']
  noDefer: ['bash']
# 延迟除守卫工具对(tool_search / defer_execute_tool)之外的一切
config:
  defer: ['Defer(*)']
# 延迟一切,但保留一小组常驻核心工具(noDefer 优先于 Defer(*))
config:
  defer: ['Defer(*)']
  noDefer: ['read', 'write', 'edit', 'bash']
# 按前缀延迟 MCP 网关工具
config:
  defer: ['mcp*']

工作原理

组件 作用
tool_search 搜索当前不可见的工具。tool_names 精确查找,queries 关键词查找(中英文)。命中即激活并返回匹配结果。
defer_execute_tool 按精确工具名激活一个延迟工具,使模型可直接调用。适合激活已知名字的工具。
隐藏 在 system-prompt/assemble 上,把被延迟的工具从该 scope 的模型可见工具列表中剔除(注册表本身不动)。
拦截 tools/pre-execute 监听器会在模型直接调用未加载的延迟工具时返回 deny,提示先调用 tool_search 或 defer_execute_tool。
激活 命中的工具记入该 agent 的激活集合,从下一轮模型请求开始出现在工具列表中,可直接调用。
解析生效配置 按该 agent 的 cwd 取出生效配置:项目级存储里有该项目就用它,否则用全局配置。插件安装时同步读入,故首个请求即已生效。
自动调优 会话创建时(每个项目每天最多一次)在后台扫描该项目历史,把排名前 N 的工具写入项目级存储,并立即重算该项目 agent 的可见性。

流程

Prompt: 只有守卫 + 常驻核心的 schema 可见(零配置默认即如此)
   │
   ▼
model: tool_search({ tool_names: ["todo_write"] })
   │  └─ 返回匹配状态,并把它记入该 agent 的激活集合
   ▼
下一轮: model 直接调用 todo_write(完整 schema 已进入工具列表)

设计约束与已知限制

  • 激活后下一轮生效。 tool_search 激活工具后,当前模型请求的工具列表 已经固定(工具表在下一轮请求头才更新),因此同一轮内直接调用仍会被 tools/pre-execute 拦截。请在结果返回后、下一轮再调用。
  • 延迟工具在搜索前不可见。 模型看不到延迟工具的名字,只能依赖 tool_search 的检索来发现它们。
  • 与其他限制天然兼容。 插件只对模型可见列表做减法,绝不放宽其他 restrict(如父/子代理策略)。被其他策略拒绝的工具既不会进入模型可见列表, 也不会进入搜索目录,因此搜索时只会得到 unavailable。
  • 不区分工具的来源平面。 宿主 composition、agent preset(Web/Desktop 的 模型可见工具全部由 preset 挂载)、MCP 服务器,以及 agent 创建之后才注册的 工具,走的是同一套延迟/搜索逻辑。
  • PTC 呈现模式下退化为不生效。 以 ptc 呈现的 agent 只会看到保留传输 run_code(它被钉为守卫,永不被延迟),其余能力通过生成的 SDK 暴露;此时 延迟不隐藏它们(不会报错,只是不生效)。
  • 状态不跨会话持久化。 已加载的工具集仅在当前进程内生效;resume / fork 后新 agent 会重新按配置延迟。这是为了避免外部插件写入未在 KNOWN_SESSION_EVENT_TYPES 注册的自定义会话事件。
  • 不接管 agent 自身作用域的工具。 注册在该 agent 自己的 scope 里的工具 (tool_search / defer_execute_tool 自身就是)始终可见;Defer(*) 对它们 无效。
  • 自动调优依据的是「已有历史」。 触发时机是会话创建,此刻新会话还没有任何 工具调用,所以统计的是该项目此前的记录。刚启用的项目要积累到 autoTuneMinSamples 次调用才会被调优;在此之前一直用全局配置。
  • 项目级配置按绝对路径索引。 移动或重命名项目目录后,旧条目不再匹配该 项目(它会回退到全局配置并重新开始统计)。需要时手工迁移 ~/.dsh/lazy-tools/projects.json 里的键,或删掉旧条目重来。
  • 自动调优不影响其他插件。 它只写插件自己的存储文件,不注册任何会话事件, 也不修改 DSH 的配置文档,因此不会与其他插件或 DSH 版本校验冲突。

开发

pnpm install
pnpm run typecheck        # TypeScript 类型检查(src)
pnpm run typecheck:tests  # TypeScript 类型检查(tests)
pnpm test                 # vitest 单元 + 集成测试
pnpm run lint             # oxlint
pnpm run build            # tsc + tsdown 打包到 lib/
pnpm run verify:client    # 校验构建出的浏览器端 bundle 是否符合内核契约
pnpm run check            # 以上全部串起来跑一遍

verify:client 值得单独说明:界面能否出现,取决于 lib/client.js 是否满足 浏览器内核的三条约定——用包名注册 factory、只 require 内核已提供的模块、导出 apply/inject。这些约定只在浏览器里强制,构建阶段不报错,所以一个打包失误的 表现是"用户打开界面白屏",而 pnpm run check 全绿。该脚本把真实构建产物放进一个 替身模块加载器里执行,把这类问题变成一次可复现的失败。

改完浏览器端后,已在运行的 DSH 窗口不会自动出现新页面:客户端插件图在启动时 合成,且包元数据会缓存"该包不是客户端插件"这一否定结论直到重启。重启 DSH 即可 (见"在界面里配置")。

贡献流程、目录结构、以及在真实 Harness 里试插件的步骤见 CONTRIBUTING.md;版本变更记录见 CHANGELOG.md; 安全问题请按 SECURITY.md 私下报告。CI 在 Node 22 / 24 上跑 lint、两次类型检查、测试与构建(见 .github/workflows/ci.yml)。

License

本项目以 MIT 许可证发布(见 LICENSE)。

CLASSIFICATION EVIDENCE

分类依据

项目类型待识别
功能分类其他
规则置信度低

系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: 无有效分类标签。