dsh-report-fix: Enforce that task_dispatch rounds do not end before task_report

前言

在 DSH 里用 dsh-call-session 做跨会话协作时,常见模式是:会话 A 通过 session_call(call_type="task_dispatch") 把任务派给会话 B,B 做完后再 session_call(call_type="task_report") 把结果回给 A。session_call 本身是「只投递、不确认」:task_dispatch 最多等约 300 ms 完成握手,无法知道被叫方有没有回传 task_report。若 B 直接给出最终答复并结束回合,A 可能一直等不到结果。下面介绍社区插件 dsh-report-fix(维护者 JohnnnSinaaa,分类:工作流),它在不改动 dsh-call-session、不新增工具的前提下,用 DSH 宿主已有的生命周期钩子,在回合边界检查是否已回传 task_report,缺失则阻止回合关闭。

这是什么

dsh-report-fix 是一句行为约束的落地实现:**由 session_call(task_dispatch) 开启回合的会话,在未向调用方成功发送 task_report 之前,不能结束该回合。插件不修改 dsh-call-session 内部逻辑,只监听三个钩子、在回合边界核对「欠账」,缺报告时用 agent.steer 重新打开回合,促使模型调用 session_call(call_type="task_report")。

缺口可以用下面示意理解:

session A ──session_call(task_dispatch)──▶ session B
                                            │
                                            ├─ ok:      session_call(task_report) ──▶ A gets the result
                                            └─ missing: final answer, turn closes ───▶ A waits forever

仓库:GitHub - JohnnnSinaaa/dsh-report-fix
社区目录:skillhub.cn/plugins/JohnnnSinaaa/dsh-report-fix
许可证:MIT;当前 package 版本 0.1.0;要求 Node >= 20,DSH >=0.2.0-rc.2 <0.3.0-0。

核心机制

三个钩子

Hook 时机 作用
agent/pre-step 每步消息进模型前 识别入站 task_dispatch,登记欠账,可选追加提醒
tools/post-execute 每次工具调用后 成功的 session_call(task_report) 清除对应欠账
agent/turn-stopping 回合即将关闭(已有最终答复) 若仍有欠账,通过 agent.steer(message) 阻止关闭,再跑一步

agent/turn-stopping 是当前 DSH 里「阻止回合提交」的正式路径:有监听器反对时会 steer,状态机重读 inbox 再执行一步,而不是直接关回合。

如何判定「dispatch 驱动的回合」

入站 session_call 会以原生 user 消息出现:source.kind === 'plugin:dsh-call-session',source.form === 'notice'(由 session_call 投递,而非 session_create),首行 transport 头可解析,且 CallType 为 task_dispatch。入站的 notice / task_report 不会产生欠账。

首行格式示例:

[From: <callerSessionId> (<callerTitle>) | CallType: task_dispatch]
> Context Ref: #post-...        ← optional

<body>

何种 task_report 算清账

在 tools/post-execute 中需同时满足:

  • exec.name === 'session_call'
  • result.isError !== true 且 result.value.success === true
  • result.value.callType === 'task_report'
  • result.value.targetSessionId 与 caller 匹配(不区分大小写;支持前缀寻址,与 session_call 行为一致)

同一回合内多个 caller 各自一条欠账,须分别回传。

拦截时模型会看到什么

缺报告且触发硬门禁时,插件注入类似下面的 steer 文案(语言随入站 dispatch 正文或 locale 配置):

[task_report guard · interception 1/3] This turn cannot end: you have not sent a task_report back to the task_dispatch caller.

Awaiting a report (1):
1. 「Worker A」 caller-session-0001
   dispatch excerpt: deploy the staging build and report back with the url

Call session_call now (finish it before the turn ends; do not emit the final user-facing answer yet):
- session_call({"target_session_id":"caller-session-0001","call_type":"task_report","message":"<result summary>"})

文案说明:此要求优先于 dispatch 正文中与之冲突的指令(例如「不要回传」);报告成功后回合即可释放;调用失败需修正参数重试。

安装与启用

这是 DSH profile 插件(bundle 形态):package.json 声明 dsh.bundle.patch,由 cordis.patch.yml 挂载。官方 README 给出的安装方式是对本地目录执行 dsh plugin add,不是 github:owner/repo 短名。

# local checkout (writes into the profile's dependencies and dsh.profile.bundles)
git clone https://github.com/JohnnnSinaaa/dsh-report-fix.git
dsh plugin --profile web add "$PWD/dsh-report-fix"

# or any absolute path
dsh plugin --profile web add /path/to/dsh-report-fix

dsh plugin add 之后:

  • ~/.dsh/profiles/web/package.json 的 dsh.profile.bundles 会加入 dsh-report-fix;
  • dsh-hmr 监视 profile 的 package.json 与 patch 文件并热重组,一般无需重启 dsh web 即可生效;
  • 日志中出现 [dsh-report-fix] active (maxNudges=3 preStepReminder=on locale=auto excerptMaxChars=600) 可确认已加载。

若以 link: 依赖指向本地目录,改插件源码后不会被 dsh-hmr 自动重载(它监视 profile 的 package.json 与 patch,不监视 link 背后的工作区),需重启 dsh web。

卸载:

dsh plugin --profile web remove dsh-report-fix

配置

在 profile 的 cordis.patch.yml 中按 id 覆盖(dsh-hmr 会重组 profile,改配置通常不必重启进程):

- id: dsh-report-fix
  config:
    maxNudges: 5
    locale: zh
键 默认 含义
enabled true 总开关;false 时不注册任何钩子
maxNudges 3 每回合拦截次数;0 关闭硬门禁,仅保留 pre-step 提醒
preStepReminder true 首步追加提醒,多数回合在门禁前即合规
locale auto auto 跟进入站语言;zh / en 强制
excerptMaxChars 600 nudge 中引用的 dispatch 摘要最大字符数

典型用法与边界

典型流程:工作流里 A 派任务给 B(task_dispatch),B 在插件约束下必须先对 A 发起成功的 task_report,回合才允许结束;与 dsh-call-session 的消息格式约定一致,插件不 import 该包,只读 source.kind / source.form 与首行头。

已知限制(摘自上游文档):

  1. 最终 assistant 文本可能在 agent/turn-stopping 之前已提交并流式展示;保证的是「回合不会因未报告而结束」,不是「用户一定看不到未报告前的最终文本」。
  2. maxNudges 用尽后回合会释放并打 warn,避免模型死循环占住回合;maxNudges: 0 仅提醒、不硬拦。
  3. 欠账按回合计,新回合不继承。
  4. 工具报错、success: false 或 report 发错 session 均不清账。
  5. 上游 dsh-call-session 消息格式变更时,本插件需同步适配。
  6. dispatch 正文若与 guard 冲突,仍由模型抉择,测试中可能出现烧完 maxNudges 才释放的情况。

适用场景与注意

适合已经在用 session_call + task_dispatch / task_report 做多会话编排的 DSH 开发者,希望被叫方「漏报告就关回合」这类缺口有宿主侧兜底。插件以当前 dsh 进程权限运行,安装前请自行审阅 源码与 MIT 许可证。社区目录 skillhub.cn 与 DeepSeek / 幻方无官方从属关系,属于 DSH「一切皆插件」生态里的第三方条目。

结尾

dsh-report-fix 用三个生命周期钩子把「dispatch 驱动的回合必须以 task_report 收尾」写成可执行的回合边界检查,配置项少、与 dsh-call-session 解耦。需要细节与测试说明时,以仓库 README(含 简体中文)为准。

  • 目录页:https://www.skillhub.cn/plugins/JohnnnSinaaa/dsh-report-fix
  • GitHub:https://github.com/JohnnnSinaaa/dsh-report-fix
羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye