DSH plugin 怎么用 dsh-resource://:useResource 与资源钉住释放
DSH plugin 的客户端资源模型把「一个地址」变成任何 Web 组件都能读的活数据:拥有内容的包注册协议提供方,组件按地址订阅。 它的关键约束是持有者决定生命周期——一个订阅中的 useResource 或一次 pin 都能让资源存活;第一个持有者打开提供方的流,最后一个释放时中止流、丢弃值,快照回到 loading 或 none。这也解释了为什么切 tab 卸载正文不会关流:右侧栏在每条 tab 存续期内钉住了它的地址。
DSH plugin 的资源模型:地址、提供方与读取方
一句话结论:协议拥有值类型并注册唯一提供方;组件只认地址,不引用拥有者运行时。
ctx.resources:资源服务与useResource全局 hook 的提供者。- 协议声明:协议拥有者在
ResourceProtocolMap上声明值类型。 - 唯一提供方:一个协议恰有一个提供方,第二次注册抛错。
- 读取方:组件按地址读取当前状态,无需知道谁在产生数据。
- 第一个消费方:右侧 Sidebar 的 tab 就是这套模型。
DSH plugin 的 dsh-resource:// 地址:协议是 host,路径归协议
一句话结论:dsh-resource://<type>/…,host 必须是协议键;规范小写拼写才被接受。
| 地址 | 读作 |
|---|---|
dsh-resource://file/session/s1/notes/a.md | 会话 s1 工作区下的文件元数据 |
dsh-resource://file/absolute/home/me/n.md | 绝对路径,但无授权会话会失败 |
dsh-resource://subagentchat/session/c1?... | 已寻址的 subagent 会话 |
sidebar://guide | none:导航地址,不是资源 |
/home/me/n.md | none:不是 URL |
- file 协议的作用域:
session/<id>后接相对路径,或absolute后接去掉前导斜杠的绝对路径。 - 构造与解析:用
fileAddressFor(sessionId, cwd, path)与parseFileAddress(address)。 - 只读 scheme 与 host:
protocolOf(address)只返回小写 host。 - 字符串比较:大小写变体是另一条记录,别手拼地址。
DSH plugin 的提供方注册:一条帧流,必须响应 signal
一句话结论:open 返回帧流,首帧是当前状态,之后每次变化一帧;中止信号必须让流停下。
ctx.effect(() => ctx.resources.register<'note'>({
protocol: 'note',
async *open(address, { signal }) {
yield await ctx.remote.notes.read(id, signal)
for await (const change of ctx.remote.notes.follow(id, signal)) yield change
},
}))
- 首帧即状态:不要等「第一次变化」才发数据。
- 失败是帧:
ok: false携带RemoteFailure;流里抛异常是编程错误。 - 必须响应中止:忽略
signal的提供方会一直跑到下一帧才停。 - 注册时机:放在
ctx.effect里,让协议与插件同寿。 - 已被持有的地址:注册时会立刻打开它们的流;提供方 dispose 后地址读作
none。
DSH plugin 的读取:useResource 的四种状态
一句话结论:none、loading、live、failed 四态;failed 保留上一个成功值,便于继续展示旧内容。
| status | 含义 | value | failure |
|---|---|---|---|
none | 没有提供方,或地址不是资源地址 | undefined | undefined |
loading | 流已打开、尚未产出 | undefined | undefined |
live | 最新一帧成功 | 最新值 | undefined |
failed | 最新一帧失败 | 上一个成功值 | 该帧的失败信息 |
- 订阅即持有:组件挂载时订阅;另一个持有者存在时,你可以立刻读到最新值而不重开流。
- 失败展示自负:模型不产生任何用户可见文案。
- 类型参数命名协议:
useResource<'file'>(address)让返回值类型跟着协议走。 - 多数流只推元数据:例如 file 协议给的是文件 stat,内容由消费方另行读取。
DSH plugin 的持有与释放:订阅与 pin
一句话结论:有持有者就活;最后一个离开时中止流、丢弃值、回到 loading 或 none。
- 两种持有:订阅中的
useResource,或一次ctx.resources.pin(address, signal)。 - pin 的语义:不订阅也保持打开,直到 signal 中止;已中止的信号什么也不钉。
- Sidebar 的用法:在每条打开 tab 的存续期内钉住地址,因此切 tab 不关流。
- 释放后果:中止流、丢弃值、快照回到
loading(有提供方)或none;之后的帧被丢弃。 - 裸 observable:
ctx.resources.source(address)按地址引用稳定,只读快照不算持有。
DSH plugin 排查注意事项
一句话结论:资源问题先分「地址不对」「提供方没注册」「没人持有」三类。
- 一直 none → 协议没注册提供方,或地址不是资源地址(拼错也是 none)。
- 一直 loading → 提供方没发首帧,或持有者已全部离开。
- 失败后空白 → 消费方没处理 failed,应该继续显示旧值加提示。
- 切 tab 流断了 → 没有 pin 住地址,正文卸载触发释放。
- 内存增长 → 每条地址的记录会保留到页面关闭,读过的不同地址越多占用越大。
- 修复状态如实:本文的地址规则、帧流契约、四态与持有语义均取自官方「客户端资源」页面;
file协议与 Remote 命名空间细节以对应包文档为准。
常见问题
DSH plugin 的资源地址里 host 命名协议,必须是 ResourceProtocolMap 的键;路径归协议自己,由其拥有者逐段做百分号编码。新增协议只新增 host,不新增 scheme。
DSH plugin 的 open 返回一条 RemoteResult 帧流:首帧是当前状态,之后每次变化一帧。失败是 ok 为 false 的帧;流里抛异常属于编程错误,不会被捕获。
可以,DSH plugin 的 failed 状态会保留上一个成功值,同时给出失败信息。消费方可以继续展示旧内容并加提示,下一个成功帧会清除失败;文案由消费方决定,模型不产生可见文案。
DSH plugin 的资源靠持有者存活:订阅中的 useResource 或一次 pin。最后一个持有者释放时中止流、丢弃值,快照回到 loading(有提供方)或 none(没有提供方)。
不会,DSH plugin 把拼错的协议或畸形地址读作 none,没有额外诊断。只有「没有任何 tab 类型认领该地址」这类接线错误才会抛错。
相关术语
- dsh-resource:// 地址
- dsh-resource:// 是 DSH plugin 的资源地址方案,host 是协议键,其后路径归协议所有。地址按字符串比较,规范小写拼写才被接受。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/client-resources
- ResourceProtocolMap
- ResourceProtocolMap 是协议到值类型的映射表,协议拥有者通过声明合并写入自己的类型,再在 effect 里注册唯一提供方。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/client-resources
- useResource
- useResource 是 DSH plugin 提供的全局标准 hook,按地址返回当前快照,状态为 none、loading、live 或 failed。订阅本身就是持有资源的方式。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/client-resources
- pin(钉住)
- pin 是在不订阅的情况下让资源保持打开,直到传入的 signal 中止。已中止的信号不会钉住任何东西;最后一个持有者离开时资源才真正释放。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/client-resources
来源
- DeepSeek Harness 文档 — 客户端资源(地址、注册提供方、读取、持有与释放、限制)· deepseek-harness.github.io
- dsh-client-resources 包 README(协议提供与读取)· deepseek-ai(GitHub)
- DeepSeek Harness 文档 — 右侧 Sidebar(资源模型的第一个消费方)· deepseek-harness.github.io