DSH plugin 怎么用 dsh-resource://:useResource 与资源钉住释放

插件开发发布于 2026-10-17作者: DeepSeek Plugin 插件市场
DeepSeek HarnessDSH plugindsh-resourceuseResourceResourceProtocolMap客户端资源
DSH plugin 的客户端资源模型:地址由协议 host 与协议自有路径组成,提供方返回一条帧流,useResource 有四种状态,订阅或 pin 决定资源何时打开与释放。

DSH plugin 的客户端资源模型把「一个地址」变成任何 Web 组件都能读的活数据:拥有内容的包注册协议提供方,组件按地址订阅。 它的关键约束是持有者决定生命周期——一个订阅中的 useResource 或一次 pin 都能让资源存活;第一个持有者打开提供方的流,最后一个释放时中止流、丢弃值,快照回到 loading 或 none。这也解释了为什么切 tab 卸载正文不会关流:右侧栏在每条 tab 存续期内钉住了它的地址。

DSH plugin 的资源模型:地址、提供方与读取方

一句话结论:协议拥有值类型并注册唯一提供方;组件只认地址,不引用拥有者运行时。

  1. ctx.resources:资源服务与 useResource 全局 hook 的提供者。
  2. 协议声明:协议拥有者在 ResourceProtocolMap 上声明值类型。
  3. 唯一提供方:一个协议恰有一个提供方,第二次注册抛错。
  4. 读取方:组件按地址读取当前状态,无需知道谁在产生数据。
  5. 第一个消费方:右侧 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://guidenone:导航地址,不是资源
/home/me/n.mdnone:不是 URL
  1. file 协议的作用域:session/<id> 后接相对路径,或 absolute 后接去掉前导斜杠的绝对路径。
  2. 构造与解析:用 fileAddressFor(sessionId, cwd, path) 与 parseFileAddress(address)。
  3. 只读 scheme 与 host:protocolOf(address) 只返回小写 host。
  4. 字符串比较:大小写变体是另一条记录,别手拼地址。

DSH plugin 的提供方注册:一条帧流,必须响应 signal

一句话结论:open 返回帧流,首帧是当前状态,之后每次变化一帧;中止信号必须让流停下。

ts
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
  },
}))
  1. 首帧即状态:不要等「第一次变化」才发数据。
  2. 失败是帧:ok: false 携带 RemoteFailure;流里抛异常是编程错误。
  3. 必须响应中止:忽略 signal 的提供方会一直跑到下一帧才停。
  4. 注册时机:放在 ctx.effect 里,让协议与插件同寿。
  5. 已被持有的地址:注册时会立刻打开它们的流;提供方 dispose 后地址读作 none。

DSH plugin 的读取:useResource 的四种状态

一句话结论:none、loading、live、failed 四态;failed 保留上一个成功值,便于继续展示旧内容。

status含义valuefailure
none没有提供方,或地址不是资源地址undefinedundefined
loading流已打开、尚未产出undefinedundefined
live最新一帧成功最新值undefined
failed最新一帧失败上一个成功值该帧的失败信息
  1. 订阅即持有:组件挂载时订阅;另一个持有者存在时,你可以立刻读到最新值而不重开流。
  2. 失败展示自负:模型不产生任何用户可见文案。
  3. 类型参数命名协议:useResource<'file'>(address) 让返回值类型跟着协议走。
  4. 多数流只推元数据:例如 file 协议给的是文件 stat,内容由消费方另行读取。

DSH plugin 的持有与释放:订阅与 pin

一句话结论:有持有者就活;最后一个离开时中止流、丢弃值、回到 loading 或 none。

  1. 两种持有:订阅中的 useResource,或一次 ctx.resources.pin(address, signal)。
  2. pin 的语义:不订阅也保持打开,直到 signal 中止;已中止的信号什么也不钉。
  3. Sidebar 的用法:在每条打开 tab 的存续期内钉住地址,因此切 tab 不关流。
  4. 释放后果:中止流、丢弃值、快照回到 loading(有提供方)或 none;之后的帧被丢弃。
  5. 裸 observable:ctx.resources.source(address) 按地址引用稳定,只读快照不算持有。

DSH plugin 排查注意事项

一句话结论:资源问题先分「地址不对」「提供方没注册」「没人持有」三类。

  1. 一直 none → 协议没注册提供方,或地址不是资源地址(拼错也是 none)。
  2. 一直 loading → 提供方没发首帧,或持有者已全部离开。
  3. 失败后空白 → 消费方没处理 failed,应该继续显示旧值加提示。
  4. 切 tab 流断了 → 没有 pin 住地址,正文卸载触发释放。
  5. 内存增长 → 每条地址的记录会保留到页面关闭,读过的不同地址越多占用越大。
  6. 修复状态如实:本文的地址规则、帧流契约、四态与持有语义均取自官方「客户端资源」页面;file 协议与 Remote 命名空间细节以对应包文档为准。

来源:客户端资源、dsh-client-resources README、右侧 Sidebar。

常见问题

DSH plugin 里,dsh-resource:// 地址里的 host 和路径分别归谁管?

DSH plugin 的资源地址里 host 命名协议,必须是 ResourceProtocolMap 的键;路径归协议自己,由其拥有者逐段做百分号编码。新增协议只新增 host,不新增 scheme。

DSH plugin 里,提供方的 open 返回什么,什么时候算失败?

DSH plugin 的 open 返回一条 RemoteResult 帧流:首帧是当前状态,之后每次变化一帧。失败是 ok 为 false 的帧;流里抛异常属于编程错误,不会被捕获。

DSH plugin 里,useResource 返回的 failed 状态下还能显示内容吗?

可以,DSH plugin 的 failed 状态会保留上一个成功值,同时给出失败信息。消费方可以继续展示旧内容并加提示,下一个成功帧会清除失败;文案由消费方决定,模型不产生可见文案。

DSH plugin 里,什么情况下资源会被释放,什么时候会回到 loading?

DSH plugin 的资源靠持有者存活:订阅中的 useResource 或一次 pin。最后一个持有者释放时中止流、丢弃值,快照回到 loading(有提供方)或 none(没有提供方)。

DSH plugin 里,地址拼错了会报错吗,具体要注意什么?

不会,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

来源