@swoop111/dsh-tool-fs
v0.2.0-rc.2.1
Published
Model-facing filesystem tools (read, write, edit) over the DeepSeek Harness filesystem seam (ctx.fs)
Readme
description: "面向模型的 read、read_image、write 与 edit 工具:供组合或排查 agent 文件系统访问的用户与维护者使用。" kind: "package-reference"
@deepseek-ai/dsh-tool-fs
English | 中文
概述
使用 dsh-tool-fs 可让模型带行号读取 UTF-8 文件、读取受支持的图片、创建或原子地替换文件,以及执行有针对性的字面量编辑。结果都有上限,失败会提供稳定错误码与恢复指令。当写入和编辑必须在成功读取后执行时,请添加 dsh-fs-observation-policy;省略它时,变更仍是原子的,但不受此条件约束。图片读取需要持久附件存储和支持图片输入的路由模型。glob 或 grep 搜索请选择同级的发现工具包。
目录
使用本包
在 ctx.fs 后端之后挂载工具,并在需要先读后写/编辑行为时挂载策略插件。模型随后获得带行号的读取、原子的写入与编辑,以及——挂载附件存储时——图像读取;每个结果都有上限,失败携带稳定错误码与恢复指令。
最小组合
一个后端、策略插件,然后是工具;附件存储为可选,用于启用 read_image。
- name: '@deepseek-ai/dsh-fs-local'
- name: '@deepseek-ai/dsh-fs-observation-policy'
- name: '@deepseek-ai/dsh-tool-fs'策略插件是可选的:省略时,工具直接使用裸提供方(无条件写入、覆盖与编辑,无已观察状态)。加载这些工具的部署也应加载该插件,从而提供写入/编辑前读取行为。read_image 只在持久 ctx.attachments 服务已挂载时注册;执行时还拒绝确切模型未声明图像输入的路由,因此文本路由的持久历史不会出现图像块。
工具
| 工具 | 参数 | 行为 |
|---|---|---|
| read | file_path、offset?、limit?、force? | 带行号的 UTF-8 内容与分页 footer;offset 从 1 开始,limit 默认为配置的 readLimit,上限也为该值;force(readRepair.duplicate 启用时出现)在重复守卫本会拒绝时照常读取该窗口 |
| read_image | file_path | 读取并持久保存 PNG/JPEG/WebP/GIF 源图;无扩展名路径(包括规范化附件对象路径)按文件签名识别格式;规范化可在下一次模型请求前缩小图片,因此模型无需先创建缩略图 |
| write | file_path、content | 创建或完整替换文件;有策略插件时,覆盖要求先在未变版本上执行 read,创建不需要 |
| edit | file_path、old_string、new_string、replace_all? | 字面量替换,除非 replace_all 为 true 否则要求唯一匹配;有策略插件时,要求先执行 read 且文件未变 |
字段名使用 snake_case,与 Claude Code 和现有 harness 工具 schema 一致。成功返回紧凑信封——读取窗口、图像引用或 Created file/Updated file 确认——write/edit 还会派生可回放的 diff 卡片元数据供 UI 展示。
配置
所有键均为可选;默认值是随产品交付的读取上限。
| 键 | 默认值 | 含义 |
|---|---|---|
| readLimit | 2000 | 一次 read 调用返回的默认和最大行数 |
| readMaxLineLength | 2000 | 每行截断前保留的字符数 |
| readMaxBytes | 51200 | 一次 read 调用所选行的字节上限;溢出时以「已达上限」footer 结束窗口 |
| readStreamMinSize | 10485760 | 大于等于该大小或大小未知的文件采用流式读取,而不是整体加载到内存 |
| readRepair.enabled | true | 对 read 的寻址与路径失败运行 read 修复规则;关闭后一律报错 |
| readRepair.duplicate.enabled | true | 拒绝对同版本未变文件、且已被本会话近期窗口完全覆盖的读取,以已交付范围答复而不重复内容 |
| readRepair.duplicate.maxSteps | 4 | 自窗口记录以来提交的步数超过该值即视为模型已遗忘内容,重读照常返回 |
| readRepair.completion.enabled | true | 补齐窗口的结构边界:前置窗口起点所处结构的开启行,追加窗口终点所处结构的闭合行(围栏、括号块、缩进块) |
| readRepair.completion.maxLines | 40 | 为闭合尾部可追加的最大行数,也是头部向上搜索开启行的最大深度 |
| editRepair.enabled | true | 当 edit 的 old_string 无法逐字命中时运行 fs-edit-repair 引擎,并在目标缺失时把 edit 重锚到验证过的匹配路径;关闭后一律报错 |
| editRepair.maxFileChars | 1000000 | 超过该内容长度的文件跳过行窗口修复规则 |
| editRepair.maxWindowLines | 64 | 超过该行数的 old_str 跳过行窗口修复规则 |
| writeRepair.pathHint | true | 创建文件时,以 note 披露验证过的高相似度已存在路径——write 本身绝不被改写 |
生成的配置目录是每个受支持字段及其 JSDoc 的穷尽式真源。
策略与沙箱行为
read 与 read_image 的路径授权完全由 ctx.fs 负责;媒体类型声明和文件签名只决定 read_image 是否接受该后端返回的字节。
挂载策略插件后,write 与 edit 从 fs/* 意图槽位取得防护,因此未读目标或陈旧观察会以 FS_NOT_OBSERVED 或 FS_STALE_VERSION 及恢复指令失败。使用施加沙箱限制的后端(fs-sandbox)时,write/edit 还会公开 sandbox_permissions 与 justification;被拒绝的变更返回 [sandbox: file access denied under <mode> mode] 标记与同轮次升级提示,获批的重试可以在该次调用中加盖严格更宽的模式。理由字段提示模型使用用户当前提问的语言。
失败与恢复
失败被规范化为 Error: <message>,并为调用方保留结构化错误码。稳定消息包括 file_path must be a non-empty string、limit must be less than or equal to <max>、cannot read "<path>": not found、cannot read "<path>": not a regular file,重复读取拒绝 duplicate read blocked: lines <first>-<last> unchanged, delivered <n> steps ago; that content is still above. Use offset=<next>, or force=true.,以及图像路由拒绝 cannot read "<path>" as an image: model "<model>" does not declare image input; switch to an image-capable model to read images。无论拒绝来自策略还是提供方,FS_NOT_OBSERVED 都规范化为 cannot modify "<path>": file has not been read — read the file, then retry;FS_STALE_VERSION 保留提供方原因并追加 — re-read the file, then retry。该次重新读取确认缺失后,edit 报告 FS_NOT_FOUND 而不会重复陈旧恢复指令,write 则使用防护创建。
readRepair 启用(默认)时,read 对实测寻址失败做修复而不是报错:0 起始或其他非法 offset 读作第 1 行;非正数或超上限的 limit 落到配置上限;越过文件末尾的起始行读取末尾同等长度的窗口(空文件返回空窗口);缺失路径经 fs-edit-repair 引擎重锚——先取本会话先前成功路径参数的唯一匹配,再取目录树补全的唯一结果,二者均验证存在。披露取最小必要:两项参数归一每会话只讲一次(footer 之后一行 (repair: …)),因为交付窗口与其 footer 已经给出实际范围;重锚路径每次都说,因为纠正后的路径是新事实;越界尾窗一字不说。
readRepair.duplicate.enabled 启用时,被本会话近期窗口完全覆盖的同版本文件读取会被拒绝:以 duplicate read blocked: … 失败,并说明未变更的行范围、距今多少步,以及两条可行路径(force: true,或从已交付范围之后的偏移继续)。遗忘规则保留合法重读——针对该文件的修改工具已提交、压缩已替换表面、或自窗口记录以来已过去超过 maxSteps 步。readRepair.completion.enabled 启用时,起始于某结构内部的窗口会前置开启该结构的行,终止于未闭合结构的窗口会追加使其闭合的后续行;两侧都不作披露,因为补进来的行自带行号、footer 也已经统计实际交付的窗口。补齐绝不修改、重排或丢弃已交付的行;开启行落在搜索范围之外时不前置;maxLines 内无法证明闭合时不追加。结构一直延伸到文件末尾的窗口原样返回。
当 edit 因目标不存在而失败(经 stat 确认)时,引擎把编辑重锚到验证过的路径——先取本会话先前成功路径参数的唯一匹配,再取目录树补全的唯一结果——经同一防护接缝应用,并以一行 (repair: …) 披露替换;目标存在时的真实过期失败保留其固有恢复指令,重锚路径上的拒绝(未读目标、沙箱拒绝)原样上抛。write 在与既有文件路径相似的路径上创建文件时,会追加一行 (hint: …) 指明该路径——在请求路径上创建是合法行为,因此 write 本身绝不被改写。
理解实现
本节解释工具套件背后的设计决策,并指出实现它们的代码位置;可观察行为已在使用本包中完整说明。
设计理念
工具就是执行器;策略是事件门禁。工具不注入策略服务,也不检查任何缓存——每次变更都通过 ctx.waterfall 向单一意图槽位请求防护,每个操作只在成功后发出 fs/observed。读取恰好执行一次提供方 stat(类型与大小路由加观察到的版本);变更一次也不执行,因为防护来自意图槽位,提供方在锁内重新检查。
源码地图
| 文件 | 职责 |
|---|---|
| src/index.ts | 插件入口:Config、工具组合、read_image 附件门禁 |
| src/read.ts | read 执行器:一次 stat、流式决策、窗口构建、观察 |
| src/read-image.ts | read_image 执行器:路由与媒体类型门禁、有界字节、附件保存 |
| src/write.ts | write 执行器:意图 waterfall、原子写入、观察 |
| src/edit.ts | edit 执行器:意图 waterfall、字面量编辑、观察 |
| src/read-render.ts | 不依赖 Cordis 的窗口构建与信封格式化 |
| src/structure.ts | 窗口边界的围栏、括号与缩进分析 |
| src/completion.ts | 结构补齐:在窗口两侧相邻行上组装头部与尾部 |
| src/sandbox.ts | write/edit 共享的升权 API:策略解析与拒绝标记映射 |
| src/error.ts | 防护变更失败的稳定模型侧诊断 |
各工具流程
四个工具共享同一种流程形态:用调用会话的 cwd 解析路径、运行适用的门禁、恰好执行一次提供方操作,并且只在成功后发出 fs/observed。read 与 read_image 为类型与大小路由付出一次 stat;write 与 edit 不执行 stat,因为防护来自意图槽位,提供方失败以类型化 FsError 结果呈现。各工具执行器位于 src/read.ts、src/read-image.ts、src/write.ts 与 src/edit.ts。
窗口补齐会为窗口两侧的行再次读取文件:窗口起始于第 1 行之后时读取前置行,仅在窗口终止于未闭合结构内部时读取后续行。两次读取都以 readRepair.completion.maxLines 行为上限,且绝不改动窗口自身的行。
观察与并发
fs/observed 在操作成功之后通过普通 ctx.emit 发出;监听器的约定是同步且只有副作用的记录器,因此异步或可能失败的观察不属于该事件。read 允许并发调度,因为它唯一改变状态的操作是同步记录版本;稍后的 write 或 edit 会在目标锁内重新检查版本,因此记录器竞态会安全地失败,两个变更工具仍保持互斥。
进一步探索
当包级约定不够用时阅读以下页面。它们从工具逐步进入它们所组合的约定、后端与策略。
- 文件系统子系统——穷尽式提供方约定、策略事件与错误分类体系。
- dsh-fs——这些工具消费的
ctx.fs约定。 - fs-local——这些工具运行于其上的宿主文件系统后端。
- fs-sandbox——添加升权字段的沙箱强制后端。
- fs-observation-policy——通过
fs/*事件防护变更的策略插件。 - 生成工具目录——本包注册的穷尽式 schema。
模型体验
系统提示词
模型看到的内容
组装时,每个指导段落通过 ctx.tools.get(name, scope) 检查对应工具,仅在该 agent 可见时输出。write 段落仅在 edit 可见时推荐 edit。三个工具都可用时,下方原文保持不变;限制的施加、解除和工具注册变化在下次组装时生效。同一检查适用于直接限制 agent 和 subagent 的 toolFilter,也适用于通过 run_code 暴露的 PTC 能力。 write/edit 中的先读后改句子描述观察策略,并非要求调用名为 read 的工具。隐藏 read 时仍保留这些句子:策略继续保护修改操作,其他产生观察记录的操作(例如 str_replace_editor 的 command: view)也能建立同一文件观察记录。工具可见性不会禁用该前置条件。
Read 指导
Use the read tool — not shell commands like cat — to inspect text files. Use offset and limit to continue reading large files.Write 指导
Read an existing file before overwriting it with write (the default fs-observation-policy requires it) and prefer edit for targeted changes.Edit 指导
Read a file before editing it (the default fs-observation-policy requires it), unless you just created or edited it in this session.Token 影响
指导成本取决于可见工具及其适用的跨工具推荐。
KV Cache 影响
可见工具集合、插件作用域和指导文本不变时,前缀保持稳定。限制或插件生命周期变化可能从首个变化的段落开始使复用失效。
工具 schema
模型看到的内容
模型会看到已生成的 read、read_image、write 和 edit schema,参数使用 snake_case。图片工具只在持久附件存储已挂载时出现;schema 本身与路由无关,严格门禁在执行时拒绝。作用域工具限制可以为某个 agent 移除任一定义。
Token 影响
该工具视图中的每个请求都支付固定 schema 成本。
KV Cache 影响
只要可见工具定义和顺序不变,前缀就保持稳定。注册生命周期或作用域限制可能从首个变化的 schema token 开始使复用失效。
读取结果
模型看到的内容
成功读取结果精确为 <path><displayPath></path>、换行、<type>file</type>、换行、<content>、形如 <lineNumber>: <text> 的编号行、一个空行、一条 footer 和 </content>。footer 精确为 (Output capped. Showing lines <start>-<end>. Use offset=<next> to continue.)、(Showing lines <start>-<end> of <total>. Use offset=<next> to continue.) 或 (End of file - total <total> lines)。长行结尾精确为 ... (line truncated to <max> chars)。read 陈述某项修复时,footer 之后精确追加一行 (repair: <内容>)。读取缺失目标仍返回 FS_NOT_FOUND,但会为调用会话记录确认缺失;外部删除的文件被重新读取后,重试的 write 可以通过提供方的不替换防护安全地重新创建该文件。
Token 影响
读取输出受 readLimit、readMaxLineLength 与 readMaxBytes 限制;保留的调用与结果会反复发送,直到上下文压缩(compaction)。
KV Cache 影响
仅追加;新增可见内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
图像读取结果
模型看到的内容
成功的 read_image 返回 <path><displayPath></path>、<type>image</type> 和写明媒体类型、规范化尺寸与字节数的 <content> 信封,随后是作为原生图像块的图像本身。结果会随持久引用写入会话日志,然后才进入下一次模型请求。
Token 影响
图像在之后每次请求中都会计费,直到压缩。每次调用都独立受附件存储的 maxImageBytes/maxImagePixels/maxImageDimension 约束;重复成功调用会在历史中累积,内容寻址只去重存储的字节,不去重每次请求的 token 成本。
KV Cache 影响
仅追加;新可见内容跟在可复用请求前缀之后,不会使既有 KV 缓存条目失效。
写入与编辑结果
模型看到的内容
写入精确返回五行包络:<path><displayPath></path>、<type>file</type>、<content>、Created file 或 Updated file,以及 </content>。编辑精确返回 The file <displayPath> has been updated successfully.;对于 replace_all,精确返回 The file <displayPath> has been updated. All occurrences were successfully replaced.。完整写入或替换文本仍保留在 assistant 工具调用参数中。
Token 影响
成功文本很少,但大型变更参数和所有结果会反复发送,直到上下文压缩。
KV Cache 影响
仅追加;新增可见内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
工具错误
模型看到的内容
失败会规范化为 Error: <message>。本包稳定的校验和读取消息是 file_path must be a non-empty string、limit must be less than or equal to <max>、old_string must be a non-empty string、old_string and new_string must differ、cannot read "<path>": not found、cannot read "<path>": not a regular file、offset <offset> is out of range for "<path>" (<total> lines)、cannot read "<path>": the <ext> extension does not declare a supported image format; read_image accepts PNG/JPEG/WebP/GIF files, including extension-less files in those formats、cannot read "<path>": the file content is not a supported image format; read_image accepts PNG/JPEG/WebP/GIF、cannot read "<path>": the bytes do not decode as a supported PNG/JPEG/WebP/GIF image; the file may be truncated or corrupt、cannot read "<path>" as an image: model "<model>" does not declare image input; switch to an image-capable model to read images,以及类型不匹配的修复消息 cannot read "<path>": the <ext> extension declares <type>, but the bytes use a different image format; rename the file to match its actual format if it is PNG/JPEG/WebP/GIF, or convert it to one of those formats(无扩展名路径的不匹配报告 cannot read "<path>": the file signature claims <type>, but the bytes decode as a different image format; the file may be corrupt)。16-bit 转换失败会报告 cannot read "<path>": the 16-bit PNG could not be converted to the normalized 8-bit sRGB form; convert it to an 8-bit PNG/JPEG/WebP and retry。提供方和策略模板在各自包的 README 中逐字列出。模型侧错误包装把所有 FS_NOT_OBSERVED 来源规范化为 cannot modify "<path>": file has not been read — read the file, then retry;FS_STALE_VERSION 保留提供方原因并追加 — re-read the file, then retry。两者都保留结构化错误码和原始原因。该次重新读取确认缺失后,edit 会报告 FS_NOT_FOUND,而不会重复陈旧恢复指令;write 则使用带防护的创建。
Token 影响
只有失败调用会添加这些保留 token。
KV Cache 影响
仅追加;新增可见内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
已知限制与延期工作
这些限制说明工具套件何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是通用文件系统对比或任务积压。
- 未交付面向模型的目录列表工具:
ctx.fs.listDir服务于 skill(技能)发现等提供方代码,同级dsh-tool-fs-search包则提供基于 ripgrep 的glob与grep,而不是扩展文件系统 seam。 read只处理 UTF-8 文本文件:图像使用独立的read_image工具;PDF、音频和视频仍延期处理。目录目标为FS_NOT_REGULAR_FILE。- 媒体类型按扩展名声明:扩展名选择声明类型,附件存储的魔数校验保持权威;扩展名错误但格式正确的图像会得到改名修复提示,而不是被嗅探接受。只有没有扩展名的路径按文件签名识别格式。
- 对象路径重新走源准入:对规范化附件对象调用
read_image会把其字节作为新来源重新准入,因此把maxImageBytes/maxMessageImageBytes配置得低于规范化图片字节预算的部署可能拒绝ctx.attachments.readImage仍可读取的对象路径;默认配置下规范化预算(4 MiB)远低于源上限(20 MiB)。 - 内嵌图像预览依赖 UI 组合:工具结果卡片经由浏览器的
tool.call.images槽位渲染图像,由附件呈现插件填充;未组合该插件的 UI 改为显示结果的信封文本。 - 没有附件区域工具:agent 在拥有文件系统路径时可以通过其他可用工具裁剪图片;没有路径的粘贴或拖入图片无法按更高分辨率重新读取。
- 没有超时接口:
read/write/edit不接受超时参数,也不声明超时预算;取消只通过exec.signal传递(见提供方理由)。
开发备注
无。
运行时不变式: 不发布伴生入口。这个模型侧 adapter 没有独立 lifecycle stream;执行关系由它调用的 capability seam 负责。
