☰
dsh-pet 工作状态状态机:DSH会话事件如何压缩成6档动画+“真收尾才庆祝“的goal续跑判定
2026/10/14 18:35:52 网站建设 项目流程
  • AI 插件
  • dsh-plugin
  • 交互助手
  • 桌面应用

【免费下载链接】dsh-pet

DSH 桌面宠物:一行命令装好即用的透明动画小桌宠,支持多开、大小位置随心配置;还内置 DIY 素材链,能用 AI 视频自造专属宠物

项目地址:https://gitcode.com/gh_mirrors/ds/dsh-pet
点击查看免费下载

dsh-pet 是一款一行命令装好即用的透明动画桌宠,它最"贴心"的功能之一是工作状态联动:桌宠会实时监听 DSH 的会话事件流,把 agent 的思考、调工具、等确认、完成、出错等过程,压缩成 6 档动画档位 + 气泡文案。本文拆解这套工作状态状态机的设计——事件如何映射到档位、多会话如何聚合,以及"goal 自动续跑"时如何做到"真收尾才庆祝"。

为什么桌宠要"跟着会话干活"

平时你让 agent 跑一个长任务时,界面滚动、日志刷屏,很难一眼看出"它现在是在想、在干、还是在等我确认"。dsh-pet 的思路是:不做日志解析,也不调模型,只监听 DSH 的session/event事件流,用最轻的方式把"当前活动工作状态"聚合成一个快照,前端按档位切换预置动画。

开关就藏在宠物实例配置里:pets[].workStatusEnabled = true时,host 侧开始聚合会话事件,浏览器/桌面端轮询/state后按 counter 变化切动画。整个链路纯监听、零模型调用,配置见 config.jsonc。

6 档状态机:6 类会话事件 → 6 个动画档位

工作状态只有 6 个档位,顺序固定,与配置里animations.events.workStatus动画数组的索引严格一一对应(注释里专门叮嘱"勿在中间插入新档"):

档位索引状态触发事件默认动画
0thinkingturn/start回合开始工作状态-思考冒泡
1workingtool/call工具调用工作状态-忙碌点按
2resulttool/result工具返回工作状态-清点归档
3waitingapproval/asked/ 问用户选择题工作状态-原地踱步张望
4successturn/endreason=completed工作状态-雀跃庆祝
5errorturn/endreason=error/max-tokens/timeout工作状态-垂头叹气冒汗

核心压缩逻辑就一个纯函数 reduceWorkStatus,几行 switch 把所有事件收敛成档位或null:

  • tool/call有个特例:工具名是ask_user_question(agent 弹选择题问你)时归为waiting——"问你的题"不是"在干活";
  • turn/end的blocked(回合被阻塞等确认)归waiting;
  • 其余 reason(如aborted)返回null,含义是该会话回合已结束,清空回空闲。这是当年"这一步正在进行中哦"永久挂死的根因修复——回合被打断后绝不能残留上一档。

档位与索引常量在 shared/work-status.ts 中单一来源定义,气泡文案则完全由配置驱动:workStatusTexts 是一个二维数组,每档放多个备选句,播放时随机抽一句,想换文案只改配置不用碰代码。

goal 续跑判定:"真收尾才庆祝"

上面 6 档里有个隐藏矛盾:turn/end completed只代表"这一轮"结束,不等于"整个任务"完成。DSH 的 goal 机制会自动续跑:一轮跑完如果目标没达成,会带着source.kind === 'goal'的消息再开新一轮。如果每轮完成都播"雀跃庆祝",就会出现"任务还没干完,桌宠先撒花"的尴尬。

解法是给每个 turn 维护一对轻量标志(WorkStatusTurnContext),由 index.ts 在事件流里置位、在turn/start清零:

  1. goalRound:收到user/message且source.kind === 'goal'时置true——本轮属于自动续跑;
  2. closing:本轮出现过update_goal工具调用且action为complete/blocked时记录收尾动作(解析函数 goalUpdateAction 对非法 JSON 一律按"未收尾"容错)。

终局判定收敛到 completedState 一个函数,规则非常干净:

场景turn/end = completed 时的档位语义
非 goal 轮(普通问答)success一轮答完即成功(原行为,向后兼容)
goal 续跑·中间轮(未收尾)result本轮完成 ≠ 任务完成,只播"清点归档",不庆祝
收尾轮action: completesuccess整个目标达成,才雀跃庆祝
收尾轮action: blockederror目标被阻塞,诚实表达沮丧而不是假装成功

这套判定被单测逐条钉死,见 work-status.test.ts:"中间轮 completed → result(不庆祝);收尾轮 complete → success / blocked → error"。

多会话聚合:等待和错误优先浮出

桌宠常同时挂着多个会话(子代理、委派任务),展示时不能"先到先得"。WorkStatusStore 按会话各记一条state + task,对外用 pickDisplayed 挑出优先级最高的一条展示:

waiting(60) > error(50) > working(40) > thinking(30) > result(25) > success(20)

这个优先级表里有两处刻意的取舍:

  • waiting永远最优先——只要有会话在等你确认,其他一切动画都该让位,你该去点那个"批准"按钮了;
  • result高于success——任何一个会话的进行中过渡态,都不允许被别的会话的"已完成"盖过去,这正是"防中途庆祝"在多会话下的兜底。

另外两个细节让状态"不会赖着不走":

  • 同档去重:同会话同状态不重复更新(签名比对防刷屏),状态或任务文案任一变化才刷新时间戳;
  • 终态 60 秒窗口:success/error展示约 60s 后自动清掉会话条目,陈旧完成态不会在后续无关活动里反复浮上来;若会话期间回到非终态,到期的清理定时器会被取消。

任务详情气泡("正在做第几步")也挂在会话条目上而非全局单值——条目一清,文案随之消失,不会再出现"A 会话的状态配 B 会话的文案",或任务结束后文案永久残留(issue #59 的根治点,详见 currentTaskFromTodo 的注释)。

渲染端:轮询 + counter 触发切档

host 侧聚合结果写进/state的sections.workStatus,浏览器与桌面端共用同一份纯逻辑 shared/work-status.ts(toWorkStatus把不认识的状态安全归一成"空闲",形状非法直接跳过这一拍、不抛错),前端按 counter 变化切到对应workStatus动画并弹出该档文案;终态气泡 10 秒自动收起,非终态常驻。

小结:把"状态机"做轻的三条经验

dsh-pet 的工作状态联动给自研状态展示功能提供了不错的参考:

  1. 压缩而不是解析:不碰日志和模型输出,只靠 6 类事件 → 6 档的固定映射,纯函数可单测,天然稳定;
  2. "完成"要分级:区分"本轮完成"与"整个任务完成"(goal 续跑的goalRound + closing双标志),只在真收尾时庆祝,失败就诚实表达;
  3. 终态要有生命周期:去重防刷屏、终态定时过期、文案与状态同生共死——状态机的下半身和上半身同样重要。

想深入源码,从 dsh-pet/src/host/work-status.ts 入手即可:6 档压缩、goal 收尾判定、多会话聚合全在这个文件里,配套的 work-status.test.ts 把每条边界行为都写成了用例。

  • AI 插件
  • dsh-plugin
  • 交互助手
  • 桌面应用

【免费下载链接】dsh-pet

DSH 桌面宠物:一行命令装好即用的透明动画小桌宠,支持多开、大小位置随心配置;还内置 DIY 素材链,能用 AI 视频自造专属宠物

项目地址:https://gitcode.com/gh_mirrors/ds/dsh-pet
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询