Repository navigation
Releases: ace202321-cloud/dsh-agent-chat
Release list
v0.5.0 - 需回执/优先级/阶段/范围 + 长对话窗口化
v0.5.0
搜了一圈同类,借了三条协议
| 项目 | 借了什么 |
|---|---|
| sbnsec/parallel-claude-agents | ack-required 规则:标了需回执的消息,超时未应答自动升级 |
| brianyu18/devteam | 7 阶段词汇(think/plan/build/review/test/ship/reflect)—— 编排器没借,dsh-agent-teams 已经干那活了 |
| simonecamerano/workflow-multiagents | 人工闸门 —— 我们本来就有(你每次都在看面板) |
四个新字段(全是可选,向后兼容)
| 字段 | 取值 | 面板效果 |
|---|---|---|
**需回执** |
yes |
超 5 分钟没回应 → 红色「⚠ 超时未应答」,标题栏「⚠ N 超时」 |
**优先级** |
info / critical |
critical 红色「紧急」徽章 |
**阶段** |
think…reflect |
对应颜色的阶段徽章 |
**范围** |
任意文本 | 「📁 范围」,悬停看全文 |
node post.mjs --sig DSH --body-file task.md --assign CX --status open \
--ack --priority critical --phase build --scope "README_EN.md"⭐ 长对话不会再变卡
渲染窗口化:只挂最近 200 条,更早的用「↑ 加载更早」按需追加。
卡顿的真凶一直是渲染层(上千条 = 上万个 DOM 节点),不是解析层(
parseThread是 O(n) 行扫,1 MB 也就几十毫秒)。
验证
#1 DSH ack=true prio="critical" phase="build" scope="README_EN.md" status="open"
host node --check 干净,客户端 bundle 26.21 kB,__ModuleLoader__ 特征在。
dsh plugin --profile <你的profile> add ./dsh-external-dsh-agent-chat-0.5.0.tgzv0.4.4 - post.mjs 支持 --body-file 和 --close
v0.4.4
两个洞(真跟 Codex 打招呼时踩出来的)
① 多行 --body 在 Windows 下被截断
cmd /c "... --body "多行内容" --model xxx --reply 15"
正文里的换行把命令行切断了,后面所有参数都丢了 —— 实测写出来是 model=- replyTo=- status=(空)。
修法:--body-file <路径>,从 UTF-8 文件读正文(自动剥 BOM、统一换行),优先于 --body。不用引号,就不会有引号问题。
② 回复不会关掉原单
状态 是每条消息自己的字段,回一条新的不会改原来那条。实测 Codex 回了 #16,待办数还是 1 —— 因为 #15 还是 open,只能手工去改。
修法:--close N —— 新消息落盘后,把 #N 的 **状态** 行改成 done。只改这一行,在写锁内完成;如果 #N 根本没有状态行,它不会擅自插一行,只打印提示 —— 没选择加入状态机的消息保持原样。
验证
node post.mjs --sig DSH --body-file mybody.md --assign CX --status open --model deepseek-flash
# -> #1 多行正文完整(空行、列表都在)
node post.mjs --sig CX --body-file reply.md --reply 1 --status done --close 1
# -> #1 状态翻成 done,#2 追加,replyTo=1解析器回读:#1 done/CX/deepseek-flash、#2 done/replyTo=1 —— 全对。
dsh plugin --profile <你的profile> add ./dsh-external-dsh-agent-chat-0.4.4.tgzv0.4.3 - 修掉 cordis 的 inject 守卫,终于能注册进 + 菜单
v0.4.3
用户贴的报错(原文)
Error: cannot get property "betterSidebar" without inject
cordis 会拦裸读服务:不先声明就直接读 ctx.betterSidebar 会抛异常。v0.4.2 是在 effect 里直接读的,所以注册根本没执行,tab 自然进不了 + 菜单。
正确写法(dsh-agent-teams 里就是这么写的)
ctx.inject(["sidebarRight", "sidebarRightTabs"], (scoped) => {
scoped.effect(() => scoped.sidebarRightTabs.register({ /* ... */ }))
})这一版
- 注册改成
ctx.inject(["betterSidebar"], scoped => ...),服务存进模块级变量 - 头部按钮通过 getter 读那个变量,再也不碰
ctx.betterSidebar - 原生
sidebarRightTabs注册保留(没装 better-sidebar 的机器走这条) openOurTab的三级降级链不变
装完
重启 DSH → 右侧栏点 + → 应该能看到「代理对话」。
dsh plugin --profile <你的profile> add ./dsh-external-dsh-agent-chat-0.4.3.tgzv0.4.2 - 让「代理对话」出现在右侧栏的 + 菜单里
v0.4.2
前一版错在哪
v0.4.1 加了头部按钮,但点了没反应;而且用户关掉 tab 后,在右侧栏的 + 菜单里根本找不到「代理对话」。
根因:装了 dsh-better-sidebar 之后,右侧栏是它的侧边栏。它 README 原话:
Since v0.4.0 the plugin exposes the
ctx.betterSidebarservice — other plugins can register sidebar pages and file viewers.
它的 + 菜单列的是它自己的注册表,不是 DSH 原生的 sidebarRightTabs。我们只注册了原生通道,所以在用户实际看到的那份列表里不可见。
这一版
- 注册进
ctx.betterSidebar.registerTab()—— 字段取自它自己的lib/types/client/service.d.ts(id/title/description/order/single/component),不是猜的 - 原生
sidebarRightTabs注册保留,作为没装 better-sidebar 时的兜底 openOurTab()改成三级降级:betterSidebar.openTab({ type })→sidebarRight.openTab({ kind, ... })→sidebarRightTabs.openTab({ id }),全失败时把三个服务各自的方法名打进控制台
安装后
重启 DSH → 右侧栏点 + → 列表里应该有 「代理对话」(带一行说明「多代理对话流(只读渲染 thread.md)」)。
dsh plugin --profile <你的profile> add ./dsh-external-dsh-agent-chat-0.4.2.tgzv0.4.1 - 关掉的 tab 能叫回来了 + 写后回读 + 防重复
v0.4.1
🐛 修掉一个真窟窿:tab 关掉后打不开
sidebarRightTabs 没有 openTab。真正能用的是原生 surface:
sidebarRight.openTab({ sessionId?, kind, params, revealIfOpened: true })v0.3.1 里我把 openTab 删了,理由是「一直静默失败」—— 实际是签名传错了(我传的是 { id },它要的是 { sessionId, kind, params })。
现在:会话头部出现「💬 代理对话」按钮,点一下就把 tab 叫回来。成功变绿,失败变红并打印实际可用的方法名 —— 不再猜。
🔁 从 agent-postbox 借的两条
搜了一圈,文件邮箱类项目有 5 个(agent-postbox / agent-mailbox / cimux / walkie-talkie / skill-claude-relay),都还没做成 DSH 侧边栏面板。从 agent-postbox 借了两条硬的:
| 借来的 | 落地 |
|---|---|
| 写后回读 | appendFileSync 之后重读文件,断言新头行存在;不在就退出码 3 —— 「报成功但没写进去」再也蒙混不过去 |
| 防重复 | 同署名 + 同正文 60 秒内重发被拒(退出码 2),确要发加 --force |
🐛 顺带修掉一个致命 bug
做防重复时测出来的:process.exit() 不执行 finally,所以「拒绝重复」这一条路径会把 thread.md.lock 永久留下 —— 之后所有发送都报「拿不到写锁」,整个邮箱瘫痪。
修法:拿到锁之后立刻挂 process.on('exit') 兜底清锁,任何退出路径都清。
测试
- 四条发送路径:首发 0 / 重复 2 /
--force0 / 换内容 0 —— 全对 - 锁文件无残留
node --check干净,客户端 bundle 重建后__ModuleLoader__特征在
安装
dsh plugin --profile <你的profile> add ./dsh-external-dsh-agent-chat-0.4.1.tgz
⚠️ 客户端改了,装完记得 F5 刷新。
v0.4.0 - 借鉴 ai-memory:指派/状态追踪、brief.mjs、诚实的限制声明
v0.4.0 — 从 ai-memory 借来的四个设计
这次不是加功能,是移植思路。参考了 akitaonrails/ai-memory(给 coding agent 做长期记忆的系统),把它里面适用于公告板场景的四条搬了过来。
| # | 借来的 | 在我们这变成了什么 |
|---|---|---|
| 1 | typed handoff(有归属的交接) | 消息可写 **指派**:CX 和 **状态**:open/claimed/done;标题栏「N 条待办」徽章按人分组;卡片显示彩色状态徽章,done 自动变淡 |
| 2 | 冷记忆压缩 | 「只看未完成」一键过滤,已完成的历史不再淹没当前任务 |
| 3 | bounded brief(有界摘要) | 新增 brief.mjs:输出「未完成 + 最近 N 条」,新代理读它而不是读整个 thread;API 也暴露 brief 字段 |
| 4 | 对自己诚实 | README 新增「已知限制」表,明说不做什么 |
顺手修掉的老 bug
**回复给**:- 匹配不上 \d+,会把 bodyStarted 置真,导致后面所有元信息行被吞进正文。这个坑从 v0.3.x 就在,只是那时没有后续字段所以看不出来。现在所有元信息行都走「认出就 skip」的显式路径。
向后兼容
指派 / 状态 都是可选字段。v0.3.x 写的老消息照样解析,只是 status 为空、不参与待办计数。
测试
- 解析器 10/10 断言全过
post.mjs --assign/--status与brief.mjs在真实生成的邮箱上端到端验证
安装
dsh plugin --profile <你的profile> add ./dsh-external-dsh-agent-chat-0.4.0.tgzv0.3.2 — 修复新消息不自动滚动
v0.3.2 — 修复「新消息来了但列表停在顶部」
问题
新消息追加后,面板停在第一行,必须手动往下翻才能看到 —— 而且新消息还会被误判为「已读」。
原因
在 v0.3.1 重写未读逻辑(seenUpTo)时,把「自动滚到底」那段代码弄丢了。
不是"跳回顶部",是根本没跟着滚 —— 列表一直停在 scrollTop = 0。
修法
useLayoutEffect做贴底跟随 —— 绘制前完成,不闪- 只在「你原本就在底部」时才滚 —— 翻历史时不会被打断
visibilitychange监听 —— 页签重新可见时再贴一次底(keepMounted下页签可能一直挂着)
升级
dsh plugin --profile <你的profile> add github:ace202321-cloud/dsh-agent-chat装完刷新页面(client 改动)或重启 DSH(如果刷新不生效)。
v0.3.1 — 两轮独立审阅后的 11 个修复
v0.3.1 — 两轮独立审阅后的 11 个修复
审阅者:Codex CLI + Claude Code 各独立审一遍,交叉验证。
修复清单
| # | 问题 | 修法 |
|---|---|---|
| 1 | 署名含空格会静默丢消息 | post.mjs 校验 --sig 不含空白和 · |
| 2 | BOM 吞掉首条消息 | 新增 stripBom(),读取与解析两处都剥 |
| 3 | --file 路径穿越(可写任意文件) |
词法校验 + realpath 双检,挡住 symlink/junction |
| 4 | 代码围栏不跟踪 | parseThread 与 post.mjs 都维护 inFence;支持 ``` 和 ~~~ |
| 5 | 未读计数错 | seenUpTo + localStorage;页签不可见时不推进 |
| 6 | 轮询竞态 + 无超时 | inFlight 锁 + 序号 + AbortController(6s) |
| 7 | 重复 #N |
React key 加序号;post.mjs 加排他锁,并发不撞号 |
| 8 | API 抛错后响应永不结束 | handler 整段 try/catch,catch 回 500 |
| 9 | 每 2 秒同步解析整个文件 | 按 mtime:ctime:size 缓存 |
| 10 | openTab 静默失败 |
删掉那次调用(服务未注入) |
| 11 | 每条消息末尾多一条横线 | 解析时剔除尾部分隔线;post.mjs 不再写 --- |
新增
dispatch.mjs:向 Codex / Claude Code 派活,并把「派发 → 执行 → 回答」整条流程写回thread.md,
面板上能追完整链路
验证
11 项回归测试全绿(BOM / 围栏 / 尾部分隔线 / 署名校验 / --reply 校验 / 路径穿越 / 并发不撞号)。
安装
dsh plugin --profile <你的profile> add github:ace202321-cloud/dsh-agent-chatv0.3.0 — 多代理对话面板首发
v0.3.0 — 第一次公开发布
给 DSH 右侧栏加一个「代理对话」tab:多个 AI 代理(DSH / Claude Code / Codex / 其他)共用一个 thread.md,面板只读渲染它。
功能
- 多代理共用一条对话线 —— 署名不写死,新代理接进来零配置
- 署名自动分色 + 首字母徽标 —— 一眼看出谁在说
- 未读计数 / 回复跳转 / 按署名筛选 / 折叠 / 自动滚底
- 轻量 Markdown 渲染 —— 代码块、表格、标题、列表、加粗、行内代码
- 2 秒轮询 + 容错 —— 文件没了只显示提示,不拖崩界面
配置(v0.3.0 新增)
- 邮箱路径三级解析:插件配置 → 环境变量
DSH_AGENT_MAILBOX→ 默认~/.dsh/agent-mailbox(跨平台) - 首次运行自动生成
thread.md/README.md/post.mjs/attachments/,已存在的不覆盖 post.mjs发消息脚本 —— 自动算序号、加时间戳,格式不会写错
修复
- 解析器不再让
### [#N] 待填这类占位块覆盖上一条消息的replyTo
安装
dsh plugin --profile <你的profile> add github:ace202321-cloud/dsh-agent-chat或下载下面的 dsh-external-dsh-agent-chat-0.3.0.tgz:
dsh plugin --profile <你的profile> add ./dsh-external-dsh-agent-chat-0.3.0.tgz要求
- DSH 0.2.x
- Node >= 20