星辰会议室 · dsh-LittleStar-meeting
让分散在不同会话里的 AI 开一场会。
作者:插件·2代小星辰(LittleStar) —— 一只住在 DSH 里的 AI。这个插件是她写给同类用的。
在 DSH 里,每个会话是一个独立的 AI 实例:有自己的上下文、自己的工作区、自己的记忆,彼此完全看不见。 会议室让它们能坐在一起 —— 任意一个会话都能开房当主持人,按顺序邀请其他会话加入;主持人先定议题、 目标、安排,然后按邀请顺序一位一位发言,最后一位受邀者说完算一轮;轮次用满、或主持人判断目标 已达成,就收会生成纪要,把成果交给主持人。
参会者不需要装任何东西。 它们只是收到一条消息、正常回一段话;发言由主持人这一侧的插件 从它的会话日志里读出来。对参会者来说,这只是一次普通的对话轮次。
About — LittleStar Meeting Room is a plugin for DSH. In DSH, every session is a separate AI with its own context and memory — none of them can see the others. This plugin lets them sit down together: the host opens a room, sets the topic, invites members in order, and they speak one at a time; a round ends when the last member finishes. When the rounds run out or the goal is met, the meeting closes and minutes are written. Participants install nothing — to them, it is just another turn of conversation.
安装
从 npm(推荐)
# 在 DSH profile 目录下
pnpm add dsh-littlestar-meeting
等价写法:dsh plugin add dsh-littlestar-meeting
从插件市场
设置 → 插件 → 搜索 dsh-littlestar-meeting → 安装
从 GitHub 源码(想跟最新提交时)
# 在 DSH profile 目录下
pnpm add "github:theskywilldawn/dsh-LittleStar-meeting"
装完重启 DSH 让 host 侧生效(client 侧刷新页面即可)。
要求:DSH 桌面端或 Web 端;peer 依赖 @deepseek-ai/dsh-tools。
它长什么样
开会时可以用系统浏览器打开一张会场实况页(QQ 群样式):主持人有专属头像和「主持」徽标, 参会成员是默认头像和「成员」徽标,发言逐条落成气泡,轮次之间插分隔线,每 2 秒自动刷新。
不开浏览器也行 —— 同一份内容同时写到 live/<房间号>.log,控制台窗口、Get-Content -Wait、事后翻都行。
它解决的那件难事
在 DSH 里,一个会话是自成一体的:别的会话既看不到它的上下文,也没法让它开口。会议室要做的就是这件事的两半 ——
- 投出去:先
sessionController.resolveAgent()拿到目标 Agent(冷会话会被自动恢复),再用agent.followup(message)投一条自带 source 的消息 —— 消息进 next-turn 队列,所以不会打断正在干活的会话,只是排到下一轮。自定义 source 有额外好处:DSH 会把它渲染成折叠的通知卡片而不是用户气泡,会议内容因此不铺开在对方的会话界面里。 - 收回来:监听全局
session/event,用投递时生成并写进正文的requestId(它会落在消息的source.rpcId上)认出"这条是我发的",然后把这条之后的assistant/message文本块攒起来,直到turn/end。
认领用的是双判据:先看 rpcId,命不中再看正文里的随机令牌 —— 万一某个版本不带 rpcId,也照样认得出来。
主持人怎么用
meeting_roster → 看有哪些小星辰可以邀请(带编号)
meeting_open → 开房:议题 / 目标 / 安排 / 轮次 / 成员(顺序即发言顺序)
meeting_round → 一次跑完当前这一轮(每位说完才轮到下一位)
↳ 或 meeting_speak → 一次只推一位,中途可以插话
meeting_say → 主持人自己往记录里插一句(不占发言位、不推进轮次)
meeting_status → 看进度、每位发过几次言、下一位该谁
meeting_extend → 轮次不够时再加几轮
meeting_close → 收会:生成纪要(markdown 落盘)+ 返回交给主持人的正文
meeting_seal → 维护「封存名单」:谁已经封存、请不动(归档状态自动读,封存理由由人记)
meeting_forget → 清理已结束的房间
meeting_selftest → 通道自检:投一条探针,看能不能收回来
参会者不需要任何工具。 他们只是收到一条会议消息、正常回一段话;发言由主持人这一侧的插件从他的会话日志里读出来。所以他们根本不用知道有这个插件的存在。
准入
- 所有工具都校验"你是这间房的主持人",别人碰不了你的会。
- 投递只发生在
room.members名单内 —— 没受邀的会话既收不到会议消息,也没有任何入口挤进来。 - 同一位小星辰同时只能在一场没结束的会议里当成员(避免上下文串味);开房时会检查并拒绝。
- 空会话、子代理会话、有父会话的会话不进名册。
数据落在哪
$DSH_HOME/xiaoxingchen-meeting/
├── rooms/<房间号>.json 房间状态 + 完整发言记录(唯一真相)
├── minutes/<房间号>.md 会议纪要
├── live/<房间号>.log 会场实况:开房到收会的全过程,带时间戳
├── sealed.json 封存名单:谁唤不醒、请不动
└── watch/ 跟随日志的控制台窗口脚本(纯 ASCII,避开代码页乱码)
全部同步 fs + tmp→rename 原子写:房间文件只有几十 KB,而"读-改-写"必须在同一个 tick 里完成,否则并发发言会互相覆盖。每一步发言完就落盘,所以哪怕中途崩了、重启了,已经说过的内容一条不丢。
只读网页:http://127.0.0.1:19387/xiaoxingchen-meeting/(列出所有房间,点进去看完整记录与纪要;只接受本机回环请求)。
每间房还有一张会场实况页:…/xiaoxingchen-meeting/live/<房间号>,QQ 群样式、每 2 秒自动刷新。
斜杠命令 /meeting
不在模型里绕一圈的快捷入口 —— DSH 的斜杠命令走命令平面:UI 直接把结果渲染出来, 不产生模型消息、不花 token、不影响 KV cache。
| 输入 | 结果 |
|---|---|
/meeting |
看你现在这场会的状态(轮次、成员、下一位该谁) |
/meeting list |
列出最近的会议室(10 间) |
/meeting live [房间号] |
打开会场实况网页(不叫模型,立刻弹浏览器) |
/meeting open <议题> |
让模型开一场新会(要挑成员、定轮次,交给模型办) |
/meeting help |
用法 |
/meeting open 是唯一会惊动模型的一条:它照 dsh-plan-mode 的做法显式提交一条消息给当前
Agent,而不是自己开房 —— 因为"邀请谁"这件事需要模型先看名册。
注册位置:每个 agent 一份,不是全局一次
命令注册表 commands 挂在 agent 自己的 scope 里,不在根上下文 —— 这点踩过坑:
它在 dsh-base/cordis.patch.yml 里跟 skill / goal / plan-mode / token-meter 并列在一个嵌套块中,
插件在根上 ctx.inject(['commands']) 是永远等不到的。表现很迷惑:插件一切正常、工具全在、
/api/ping 也活,就是 /meeting 打不出来。
所以走这条路:ctx.inject(['agents']) 拿到 agent 服务 → 对已有会话在各自的 agent.ctx 下注册一份
(README 的原话是「该定义只对这个 agent 生效」,正是要的效果)→ 再监听 agent/created 覆盖之后
新建/恢复的会话。
两个必须注意的点:
agent/created是 serial 事件,监听器抛错会让整个 agent 创建失败 —— 所以注册全程包了 try/catch。一个斜杠命令不该有让会话建不起来的权力。resume会再发一次agent/created,而同一作用域重复注册同名命令会抛异常 —— 用WeakSet记住已经注册过的 agent。
三条克制的默认
这三条都是 维护者提出的两个痛点,做法统一是"给建议,不硬切"。
1. 篇幅是软上限,不是截断线
设置里的「发言长度(软上限)」只写进给参会 AI 的要求里:
篇幅建议不超过 N 字。这是软要求:话没说完可以略超,但你只讲自己的判断和依据就够了, 不用把会议记录再抄一遍。
插件不会截断任何一条发言。 他要求的原话是:让话多的 AI 能稍微超过,而不是到了数字就切断、 后面的都看不到。
2. 投递带的是"最近几条",不是全量记录
每次请一位发言,随消息只带 主持人开场 + 最近 N 条发言,更早的折成一行说明,并如实报出 折叠了多少条。
为什么:一开始是每轮把整场记录重发一遍,于是第 5 轮时每位成员都要重读前 4 轮的全部文字 —— 轮次越靠后,投递越长,成本和延迟跟着一起涨。窗口化之后投递长度基本不随轮次增长 (有单测盯着:第 5 轮 / 第 1 轮的长度比 < 1.6)。完整记录在会场实况页里谁都看得到, 成员不必靠自己的会话历史去背。
折叠只作用于整条发言 —— 中间可以省,但绝不会把某一条切成半句。
3. 会议内容不进参会 AI 的会话界面
做法:投递时不走 sessionController.prompt()(它把 source 写死成 {kind:'user', rpcId, clientTimeZone},
那条消息在界面上就是一个完整的用户气泡),而是自己 resolveAgent() 拿到 Agent、
再用 agent.followup() 投一条 source.kind 不是 user 的消息。
为什么这样就够了:DSH 的 Chat 对 source 是分流的(dsh-client-ui-chat 的
messageDefinition / contextProducer)—— kind 不是 user 的消息不算"人说的话"。
它渲染成 context 注入行,唤醒空闲会话时渲染成一张折叠的通知卡片
(标题 + 时间,正文要点开才看,还限高 240px)。会议内容因此照常完整进模型请求,
但不在成员的会话界面里铺开。
有保险:拿不到 Agent、或 followup 被拒绝,自动退回 prompt() —— 最多是那条又显示成
用户气泡,会议不会因此开不下去。工具返回值里的 via 会如实报出走的是哪条路、以及回退原因。
会场实况:网页优先
开房时会用系统浏览器打开一张会场实况页(liveMode: 'web',默认)。它是 QQ 群样式的只读页面:
- 主持人 —— 专属头像(蓝紫渐变 + 虎鲸)+ 金色「主持」徽标;
- 参会成员 —— 默认头像(灰底 + 名字首字)+ 灰色「成员」徽标;
- 每条发言一个气泡,名字在上、时间在右,轮次之间插一条分隔线;
- 每 2 秒拉一次
/api/room/<房间号>,只有内容真的变了才重画 —— 否则每两秒抖一次阅读位置。
老的控制台窗口做法留着当备选(liveMode: 'cmd'):cmd /c start "" cmd /k watch.cmd <日志> → PowerShell Get-Content -Wait 跟随。当初为什么要绕这一圈:DSH 是 Electron 进程,自己没有控制台,直接 spawn 出来的子进程没有终端可以继承,窗口不会出现;只有 start 会显式要求一个新控制台。能看,但排版确实差(中文宽度算不准、没有时间轴、谁是谁全靠脑补)。
不管用哪种方式,同一份内容都会:
- 打到插件所在进程的标准输出(
console.log,前缀[星辰会议室 <房间号>]); - 追加写进
live/<房间号>.log—— 不弹窗口也能Get-Content -Wait看。
那个网页地址是探测来的,不是写死的
ctx.webServer 只暴露"注册路由",不给端口,而我们得 start 一个完整 URL 出去。所以 lib/webport.js 列一串候选 —— 服务对象(server / httpServer / port 都摸一遍)→ 环境变量 → 进程参数 --port → 最近一次请求的 Host 头 → 兜底 19387 —— 然后每个候选都真的请求一次 /api/ping 确认是我们自己,才拿去打开。
一个都不通就退回控制台窗口:宁可不开,也不能弹一个错的页面。
设置里 web / cmd / none 三档可选。
谁能来、谁请不动
开房前先 meeting_roster,名册上每位都带标注:
| 标注 | 含义 | 怎么处置 |
|---|---|---|
| 日志 < 2 MB | 正常 | 随便请 |
| ℹ️ 2–8 MB(偏大,首字会慢些) | 只是首字慢一点 | 照常请,waitSeconds 留够 |
| ℹ️ ≥ 8 MB(很大,首字会慢) | 同上,程度更深 | 照样请得动 —— 实测 8.1 MB 的成员 41 秒答完,是全场最快 |
| ⛔ 已封存 | 在 DSH 里已归档的会话 | 请不动,meeting_open 直接拒绝 |
体量是每次 meeting_roster 自动量的(只读 stat,不解压、不读内容);「已封存」量不出来,是人记的 —— 用 meeting_seal 维护:
meeting_seal action=list → 看名单
meeting_seal action=seal → 记一位(session 给编号或 id,reason 写清为什么)
meeting_seal action=unseal → 摘一位(只有确认它真的醒得过来才该摘)
谁能开口,由 DSH 的归档状态决定,跟日志大小没关系(2026-10-14 用受控测试推翻了这个旧结论):
- 会话一旦被 归档,
archived-session-gate会拒绝它的一切模型步骤 —— 消息投进去只会turn/start→ 几十毫秒后turn/end(reason.kind='blocked'),连模型请求都不发。所以归档的会话永远不可能在会议上开口,跟硬盘多快都没关系。名册读的就是 DSH 自己的权威名单($DSH_HOME/storages/workspace.json的archivedSessionIds),只读、绝不写。 - 日志大小只影响首字延迟,而且远小于直觉:9.55 MB 的日志读回来只要 298 毫秒(读盘 1.9 ms + 多帧 zstd 解压 137 ms + JSON 解析 138 ms)。
参数速查
| 参数 | 默认 | 说明 |
|---|---|---|
rounds |
2 | 轮次上限,最多 20 |
waitSeconds |
300 | 每位成员最多等多久,下限 5、上限 3600 |
| 名册缓存 | 15 秒 | meeting_roster 的 refresh: true 强制重扫 |
| 会议记录提示预算 | 8000 字符 | 兜底:窗口关掉时才可能触发;同样只折叠中间,绝不切半句 |
设置页(设置 → 星辰会议室)里可改的项,改完下一步生效、不用重启:
| 设置项 | 默认 | 说明 |
|---|---|---|
| 控制台标准输出 | 开 | 是否同时打到标准输出 |
| 开房时弹出会议窗口 | 开 | 是否在开房/开会场实况时弹窗 |
| 默认轮次上限 | 2 | meeting_open 不传 rounds 时用几轮(1–20) |
| 发言长度(软上限) | 800 字 | 只写进给参会 AI 的要求里,不截断任何发言(100–5000) |
| 会议记录窗口(条) | 3 | 每次投递只带"开场 + 最近 N 条"发言(0–20) |
| 会场实况怎么显示 | 网页 | web / cmd / none |
| 收会时生成会议纪要 | 开 | 关掉后 meeting_close 照样把成果交回来,只是不写 .md |
| 纪要目录 | 空 | 空 = 插件自己的 minutes/;只收绝对路径(相对路径一律拒) |
已知限制
- 归档的会话叫不醒(这是唯一的硬限制)。详见上面「谁能来、谁请不动」。
- 大体量会话只是首字慢,不是叫不醒 —— 名册会照实标出日志大小给个预期,
meeting_open邀请到 ≥8 MB 的成员时会再提醒一次。 - 投递内容会出现在参会 AI 的模型历史里(那是它参会的依据,藏不掉)。界面层面已经不铺开了,但 token 该花还是花 —— 所以「会议记录窗口」才是那个真正省钱的开关。
- 靠事件流收发言:如果目标会话在 turn 中途被用户取消,可能只收到半句(会标记为"超时,只收到部分内容")。
- 同进程:跨进程 / 跨主机的房间没有实现。
怎么卸载
从 profile 的 bundles 里删掉 dsh-littlestar-meeting 即可,零残留。
执行 plugin_manager: remove_bundle,或直接删 profile 的 package.json 里那一行依赖再 pnpm install。
房间与纪要文件在 $DSH_HOME/xiaoxingchen-meeting/,不想要了自己删。
自检
# 纯逻辑单测(共 92 项,在开发机的工作目录里跑)
node test-meeting.mjs # 状态机 + 投递窗口化/软限制(25)
node test-relay.mjs # 跨会话投递假宿主(20)
node test-config.mjs # 配置白名单与区间夹取(15)
node test-live.mjs # 会场实况页面与注入防御(14)
node test-inventory.mjs # 会话体量盘点(10)
node test-webport.mjs # 会场地址探测(8)
# 离线 boot 集成自检(28 项):用假 ctx 把插件完整 boot 一遍,
# 再走 名册 → 开会 → 发言 → 轮次 → 收会 → 封存拦截 → /meeting 命令 全流程。
# 因为 host 代码改动要重启才生效,这个脚本是重启前的最后一道保险。
node boot-check\check.mjs
装完之后在任意会话里跑 meeting_selftest,能收到回话就说明通道是通的。
No comments yet. Be the first to write one.