Project Room(项目群聊)
一个项目 = 一个持续存在的群聊。用户、主代理、前端、后端、测试、运维在同一条群消息时间线上沟通,同时各自拥有真实独立的 Harness 会话、自己的工作上下文和真实的执行能力。
这不是套壳聊天界面:群里的每条投递都落到真实会话,任务状态只由真实执行与证据驱动,模型按成员独立路由。
- English:
README.md
1. 安装与启用
插件是零运行时依赖的纯 ESM 包:宿主半边只 import Node 内置模块,浏览器半边是一段自注册的 classic script,因此不需要构建工具链,也不需要在 profile 里额外安装任何包。
先克隆本仓库(git clone https://github.com/duxingxiake3/dsh-project-room.git),再在 Harness 的插件管理里安装克隆出的目录 `;之后 profile 会得到一条link:` 依赖和一条 loader 行:
- insert:
- id: project-room
name: 'dsh-project-room'
> 注意写法:- insert: 才会创建 loader 行(行 id 与包名是两件事)。写成 - id: project-room 不会加载任何东西——这一点是在隔离 profile 里实测出来的。
启用成功后会写出两份可复核的记录:
| 文件 | 内容 |
|---|---|
/storages/project-room/boot-report.json |
解析到的核心版本、解析来源文件路径(coreResolvedFrom)、已注册工具名、当前上下文可用的工具名、能力标志、每次激活的历史 |
/storages/project-room/boot-trace.jsonl |
逐阶段的激活轨迹(一行一个 JSON)。进程若在启动中死掉,最后一行就是它走到哪一步 |
boot-report.json 里的版本取自运行进程自己的解析结果,不是读某个路径猜的;同时安装多套运行时,也能看出实际执行的是哪一套。
停用与卸载
- 停用:插件管理里关闭
project-room。 - 卸载:插件管理里移除
dsh-project-room。删除插件不删除项目历史——群、消息、任务、决定、成果都留在该目录的rooms.sqlite里,重装即可继续。 - 彻底清理是单独操作:关掉插件后删除上述目录。
- 插件不会销毁借用的用户会话(见第 6 节)。
2. 建群
左侧栏「项目群」图标 → 新建项目群。
| 字段 | 说明 |
|---|---|
| 群名称 | 任意,例如「验证码登录功能」 |
| 工作区目录 | 成员真实执行任务的工作目录(成员会话的 cwd)。可留空后补 |
| 整体目标 | 交给主代理的目标。提交后主代理会拆分工作、安排成员并推进 |
| 初始成员 | 勾选主代理 / 前端 / 后端 / 测试 / 运维 / 设计 / 文档;也可只建主代理,之后再按需补齐 |
| 每位成员的模型 | 每位成员单独选模型,或留空走角色/项目默认。选项来自宿主真实已接入的提供方与模型 ID |
建群在一个事务里写完群、成员与 room.created 事件,然后才创建成员会话。中断不会留下半个团队;成员身份先落盘,恢复时是核对而不是重建一个人。
3. 每位成员独立选模型
四层配置,优先级从低到高:
| 层级 | 位置 | 说明 |
|---|---|---|
| 项目默认 | 群设置 → 模型 | 没有单独配置的成员使用 |
| 角色默认 | 群设置 → 模型 → 角色默认 | 按 frontend/backend/tester/devops 预设 |
| 成员专属 | 成员详情 → 模型 | 指定到具体 memberId,优先级最高 |
| 允许池自动选型 | 群设置 → 选型模式 | 用户启用后,主代理在允许池内选型并公开理由;不覆盖被锁定的成员 |
默认 fixed:由用户固定每位成员,不会偷偷换成别的模型。
切换语义(安全边界):在成员详情选好模型点「应用模型」后,当前正在进行的请求沿用旧路由,下一次请求使用新模型;成员身份、头像、昵称、@ 名称与任务归属全部不变,也不重建会话、不重复工具副作用。每次切换记录配置版本与原因,旧结果保留旧来源。
「实际路由」一行显示的是真实请求时观测到的提供方/模型,而不是配置里写了什么——只有真正跑过一次才会出现。
模型标识必须写成宿主已接入的 提供方/模型ID。口头品牌名(「用 GLM 吧」)不会被当作模型 ID;目录读取失败或凭据失效的提供方显示为不可用并给出原因,而不是从列表里消失。
4. 三种 @,以及怎么把整体任务交出去
4.1 三种合法输入方式
| 方式 | 操作 | 行为 |
|---|---|---|
| 头像右键「@ 他」 | 右键(触屏长按 / 键盘 Enter)任意头像 | 把该成员插入输入框,不自动发送,原草稿保留。对自己显示「@ 自己」,只作留注记,不触发模型 |
输入 @ 选人 |
输入框输入 @ 或全角 @ |
弹出本群成员列表;按显示名/handle/角色/别名筛选;↑↓ 移动、Enter/Tab 选人、Esc 关闭。第一项是「所有人」,并显示会触达几位 AI 成员 |
直接输入 @名字 |
例如 @后端,接口字段怎么变了? |
服务器用确定性规则解析成与芯片相同的 memberId 列表,不要求重新打开选人框 |
解析规则(shared/mention-resolver.js,前后端是同一份代码,由 scripts/build-client.mjs 内联进浏览器半边):
- 先屏蔽正文结构——代码块、行内代码、
>引用行、链接目标、邮箱地址、URL——这些位置里的@不唤醒任何人。 @前必须是文本开始、空白或允许的标点边界;dev@example.com与@scope/pkg不触发。- 先按唯一 handle 精确匹配(ASCII 大小写不敏感),再按唯一别名、唯一显示名(NFC 规范化)精确匹配。不做 LLM 猜人。
- 同一成员被不同形式点名只生成一份投递;芯片 + 文本重复点名也只投一次。
- 名称含空格时用
@「前端 小林」;@前端 小林只按「前端」这个完整别名解析。 @前端你先看看这种没有名字边界的写法不猜测拆分,界面提示「请在名字后加空格或标点(例如 @前端)」。- 重名、无法识别、成员已退出(旧别名 tombstone)都在发送前提示,可以选人或按普通文本发送——不会静默改投主代理。
@所有人/@all展开为发送时的活跃成员快照;@用户只通知人类成员,不产生模型调用。
中文输入法处于 composition 阶段时 Enter 不会被抢走;Enter 发送、Shift+Enter 换行。
4.2 提交整体任务
建群时填「整体目标」,或直接在群里对主代理说一句整体目标(未点名时默认由主代理接收)。主代理随后:
- 检查现状与约束;
- 通过群工具落盘结构化计划(工作项、依赖、验收条件、假设);
- 校验提交(依赖无环、每项有验收条件、负责人存在);
- 依赖满足的任务才进入
ready并派发,未满足的保持可见的等待状态; - 持续协调、组织验收。
「先规划」模式下计划只落盘、不派发,等用户启动。
4.3 用户直接改需求
@前端 把这个入口改成手机验证码。 这类明确指令走完整链路:保存原话 → 绑定目标任务 → 写成新的需求决定 revision(人类要求直接采纳)→ 标出确定性影响的任务并置 needsSync → 通知相关成员 → 要求每人回报同步状态。旧版本的通过结果不能直接沿用,必须按新版本重验。
@前端 为什么这样设计? 是提问,不会自动变成改代码的任务。
5. 群里能看到什么
- 面板:顶部是项目标题、状态、项目切换以及任务/成员入口。宽屏显示「项目群 / 时间线 / 任务与模型」三栏;空间不足时自动收起左右栏,入口变成抽屉,不会把聊天挤成一条缝。右上角的
Aa提供标准 / 大 / 特大三档字号,选择保存在本机。 - 时间线:按天分隔;自己的消息在右侧气泡里,成员消息在左侧。代码围栏显示为代码块,行内代码、加粗与已投递的点名都有明确样式;悬停消息可以回复,或在输入框里
@对方。 - 输入区:毛玻璃输入卡片;
@弹层支持键盘选择,引用条、接收人芯片与「将触达几位 AI 成员」的提示都在发送前可见。 - 成员栏:头像菜单提供
@ 他、查看成员、查看/切换模型、查看当前任务、暂停与退出;状态点与模型来源(成员固定/角色默认/项目默认/允许池)在行内可见。 - 任务与模型:同一栏分「任务 / 模型」两个标签。任务页有验收进度、任务卡(状态、版本、负责人、验收条件、依赖等待、产物核验状态)、项目决定与同步回执、投递与恢复;模型页是项目默认、角色默认、选型模式与允许池。
- 项目生命周期:左栏分「进行中 / 已归档 / 回收站」;右上角
…提供暂停/恢复、归档、移入回收站、清理预览与永久删除。暂停、归档、回收站都先阻断新执行再请求停止,并重新读取真实成员状态;未确认停止时显示“停止未确认”,可重试核对或取消请求。恢复只会回到暂停态。移入回收站默认提示保留 30 天(trashRetentionDays),到期不会自动清除。永久删除需输入完整项目名并勾选确认,逐项释放插件独占资源,全部成功后只清理插件私有数据;源代码、借用会话、worktree 与宿主日志默认保留,并留下最小审计墓碑与删除审计;部分释放失败会停在“清理受阻”并可重试。 - 深度研究:主代理或用户可提出一次带范围、成员与预算的研究提议;群时间线上出现需要真实用户点击的审批卡。批准只覆盖当前
proposalRevision + scopeDigest,不会自动开始;可以拒绝、取消或调整范围/预算(调整会生成新版本并让旧批准失效),批准过期后不能启动。点击“开始研究”后经 ResearchGate 再核对项目版本、批准与预算,批准后为参与成员建立专用研究 Session(带agent/pre-step门禁、每研究运行锁、普通群工具禁用、写入路径真实解析门禁和 session 级用量归属;受监督原型暂不允许研究 Session 执行 shell,实验仍在受监督模式下由成员执行)。运行中可暂停/停止;归档/回收站会停止研究、撤销批准并释放专用会话,恢复后不能复用旧批准。重启后运行中的研究进入PAUSED_RECONCILE,必须由人核对后才能恢复;多人类项目因无法验证具体审批人而 fail-closed。研究工具为room_research_propose / get / start / hypothesis / experiment / finding / report / flowback,没有代理可调用的批准或删除入口。 - 投递状态是诚实的:
已送达,尚未确认取用与对方本轮已取用是两个不同状态,后者只在宿主真实报告取用后才出现。无法确认时显示无法确认是否送达,正在核对,并停止自动重发。 - 技术细节不塞进群聊:工具执行与自然发言分开呈现,群消息只显示有意义的信息。
成员的正常回答会自动回到群里(每个群驱动轮次的最终回答),因此不会出现「代理明明回答了,群里却看不到」。工作中需要提前说话时用 room_send。
6. 权限与边界
- 作者身份由服务端根据真实调用者绑定;正文里写「我是老板」不会获得任何权限。
- 群成员只能读写自己加入的群;工具不接受可伪造的 senderId。
- 独立根会话显式应用项目的工作区与工具配置;设计/文档这类只做细节的成员会被真正移除写文件与 shell 工具(不是靠提示词请求它别改)。
- 宿主原生权限提示仍归宿主管理;群里的聊天不会被当成原生批准。
- 借用的用户会话:插件不会销毁它,也不会把它与项目无关的轮次发到群里。
- 生命周期操作只接受真实人类成员:代理不能调用暂停、归档、回收站或永久删除,也不能用聊天内容伪造确认;永久删除还需要完整项目名与明确勾选。
- 写入工作:git 仓库里每个写入尝试使用独立 worktree;非 git 或 worktree 创建失败时明确降级为串行写入并说明原因,不会声称已隔离。成员退出或失败后保留未合并变更,不自动删除工作目录。
7. 暂停、恢复、重启
| 操作 | 行为 |
|---|---|
| 暂停成员 | 停止该成员新的自动执行;消息与任务保留,新 @ 显示为待处理 |
| 恢复成员 | 先同步版本与未处理消息,再继续适用的任务 |
| 停止当前工作 | 请求取消当前轮次,保留待处理队列(不承诺已执行的动作被回滚) |
| 成员退出本群 | 终止新分配,保留历史作者身份;旧别名变 tombstone 不再误投新人;未完成任务转为待交接 |
| 暂停项目 | 先阻断新任务准入与成员唤醒,再请求停止运行中的成员;只有重新读取到成员已停止才进入“已暂停”,否则停在“停止未确认”,可重试核对或取消请求 |
| 恢复项目 | 只把已暂停项目恢复为 active,并重新校验/创建成员会话;失败会逐人报告 |
| 归档项目 | 先阻断执行并请求停止;确认停止后归档,保留消息、任务、决定、成果与模型绑定历史;恢复后回到“已暂停”,不会自动启动 |
| 移入回收站 | 立即进入停止阻断态并请求停止;确认停止后才写入可恢复快照并进入回收站;未确认时不会宣称“已删除”;源代码、借用会话、worktree 默认不删;默认提示保留 30 天 |
| 从回收站恢复 | 先校验移入时记录的结构快照与资源摘要,再恢复为“已暂停”;不重放旧副作用、不恢复旧审批 |
| 停止核对 / 取消请求 | 停止结果未知时项目显示“停止未确认”,可重试核对真实成员状态;尚未造成不可逆删除的请求可以取消并回到原状态 |
| 永久删除 | 只允许在回收站中执行;要求完整项目名与明确勾选,拒绝仍有成员运行的项目;逐项释放插件独占资源,全部成功后删除插件私有数据并保留最小审计墓碑;部分释放失败会停在“清理受阻”,保留数据并可重试 |
| 关闭群界面 | 只关闭视图,已启动任务继续 |
| 重启 Harness | 按群数据库恢复:群、成员、消息、任务、决定与投递账本;只恢复 active 项目的 owned 成员会话(暂停/归档/回收站的不恢复),不确定的投递在核对后才重试,并给出简短恢复报告 |
数据库使用 schemaVersion 与迁移前备份;单写者锁保证不会有第二个进程同时改同一个项目——第二个实例会被明确拒绝,而不是并发破坏数据。
8. 已知限制
这里只列最需要先知道的几条:
- 成员会话的文件与命令工具来自
memberAgentPreset:不配置时成员只有群协作类工具。预设是两段契约——
meta.agentPreset只写会话头,真正的工具树由agentPresets.mount()挂到成员上下文上;插件两件都做
(create 与 resume 都挂),两条路径都已在真实宿主验证:成员真跑node --version、真写文件并通过验收。 - 生命周期操作不会假装成员已停止:归档/回收站会先阻断新执行,再请求停止,并重新读取真实 agent 状态;只有确认停止才进入目标状态,否则停在“停止未确认”(
BLOCKED)。真实桌面端已实测:停止请求已发出时confirmed=false,随后核对成功才归档。 - 项目生命周期是软删除闭环,不是“删库”:移入回收站只改变项目状态并保留全部插件数据;永久删除只清理该项目的插件私有记录、资源归属账本与插件拥有的会话,绝不默认删除源代码、用户工作区、借用会话、worktree 或宿主日志。插件无法证明清理的外部副本会如实保留并报告。回收站到期只做日期提示,不会自动清除。部分资源释放失败会进入“清理受阻”(
PURGE_BLOCKED),保留数据并允许幂等重试。 - 可选源码 / 临时文件 / worktree 清理未实现:规范里的高级清理需要独立危险操作、逐路径授权、归属核验和宿主删除回执;本版不提供,清理预览会明确列出被保留的源码、worktree 与宿主日志。任何“清理源码”的操作都不应被当作已支持。
- 删除与研究审批都要求可验证的人类身份:项目里有多个活跃人类成员时,生命周期操作与研究审批会 fail-closed,而不是猜一个审批人。
- 深度研究是受监督原型,不是完整自主研究:已实现审批卡、scopeDigest/revision、取消/过期、预算预留、专用研究 Session(
agent/pre-step门禁、运行锁、按 researchId 精确归属用量、写入路径真实解析与 shell 禁用)、暂停/停止、归档/回收站撤销授权,以及可自动化的权限与恢复测试。尚未实现:共享成员会话的调用前拦截、自动研究编排/阶段循环、经验提示的产品层治理,以及完整真实研究演示。 - 插件必须被选为 bundle 才会生效。
installed: true+enabled: false时,插件的
cordis.patch.yml整个不生效、loader 树里没有它的行,而且没有任何报错。必须把包名加进
profilepackage.json的dsh.profile.bundles。判定看三处:list_bundles.enabled、loader 条目、
boot-trace.jsonl里是否有本进程的activation.ready。 - 跨提供方验收需要宿主真实接入第二个提供方;只有一条路由时,跨提供方兼容性标为未实测
(不用 Mock 替代)。 - 价格与费用只记真实用量,不显示金额:当前环境没有可靠价目来源,插件按"可靠价格缺失时显示未知"处理,
所以留空而不编造。 steer(运行中于步骤边界插入)与queue(排到下一轮)的实际差异需要一次长工具调用来观察;
界面会显示这一次实际用的是哪种。- 群里的「活人感」是行为目标,不是提示词存在即成立:验收看的是真实依据、后续行动与产物,
不按消息条数或情绪词评分。 - 非 git 项目没有文件系统级隔离,串行写入只是协作机制,不能阻止所有 shell 写入。
- 多窗口/多进程同时管理同一项目:单写者锁会拒绝第二个写入者。
- 源码热重载需要配置监听目录:插件若通过 junction 链接到 profile 之外,HMR 默认看不到
src/;把仓库目录加入 profile 的hmr.root即可就地重载,否则改完src/需要重载宿主。
9. 开发与验证
# 生成浏览器半边(从 shared/ 单一来源内联,禁止手改生成区)
node scripts/build-client.mjs
# 语法闸门(含「禁止 import @deepseek-ai 包」检查)+ 生成物是否最新
npm run check
# 单元 + 集成测试
npm run test
客户端半边必须声明 inject
浏览器半边的 exports.inject 不能为空。客户端加载器会给每个条目一个 Cordis fiber,而未声明的服务不是"取不到",是读取就抛错,fiber 直接进入 failed;宿主随后的启动审计只报一句没有原因的:
web boot: 1 entry did not activate
dsh-project-room: failed
这就是桌面端启动不起来的直接原因。当前声明为 exports.inject = ['slots'](apply 需要槽位注册表)。凡是 apply() 中通过 ctx.xxx 直接读取的服务,都必须写进这个数组。
验证方式:npm run harness 会在真实 Chromium 里用一个只提供已声明服务的代理去执行 apply,一旦读了未声明的服务立刻报错——这条规则正是为了让"只报 failed、不给原因"的情况不再发生。
两个测试文件专门锁死曾经真实发生过的加载失败:
tests/unit/activation-contract.test.js——inject声明必须覆盖apply()读到的每个服务(否则 Cordis 直接抛错,插件永远不激活);浏览器半边不得引用任何 chunk。tests/unit/client-materialize.test.js—— 按真实加载器契约在 Node 里物化浏览器半边,断言它只注册sidebar.panellist:project-room与main:project-room,且只依赖react/react/jsx-runtime。
10. 技术文档
- 深度研究技术文档——状态机、审批与范围冻结、预算账本、专用研究会话、证据协议、数据模型、威胁模型、恢复与测试覆盖。