# 使用手册 README 给最短路径,[上手](getting-started.md) 把整条路径串一遍;本文展开每个动作的完整交互与边界。实现见[架构文档](architecture.md),取舍见[已知限制](limitations.md)。 ## 派工 派工分两步。先调 `spawn_worker`,`name` 必填,可带 `cardId` + `slot`(成对)把卡槽位绑到新会话。`name` 是 worker 的显示名(角色-任务短名,如 `research-sources`),同时钉成它的会话标题:不钉的话宿主按首条消息自动起名,侧栏看到的是任务正文前几个字。 工具立即返回会话 id,但 worker **还没开工**——它不产生模型调用,等 `message_worker` 投递第一条消息才开始跑。分两步是为了让绑定先落盘:第一条任务请求组装时,角色槽位与卡片事实(规格、链接、revision)就已经在上下文里。`cardId` 必须是**本项目**的、状态为 `active` 的卡:跨项目的卡会被拒绝(否则 A 项目能把 worker 绑到 B 项目的卡上,每轮读到 B 的 spec),已关闭(`done`)或归档的卡也会被拒绝(关闭即释放执行者,再绑会让投递门禁看不见它)。绑定失败会销毁刚建的会话;改名在绑定成功之后才做,所以磁盘上不会留下一条空会话。 worker 是**常驻根会话**——它在左栏项目树下有自己的会话行,你能像开别的会话一样打开它、直接输入新指令,也能在 `message_worker` 里给它续聊。worker 那一轮结束时 leader 随即被唤醒,收到一条完成通知——回报由机制侧从会话事件结算,worker 不需要调用任何工具。 通知在会话里渲染成 **worker 的发言**:左侧气泡,气泡上方是头像与 worker 名字,正文里没有「谁在说话」的前缀——和用户自己的输入(右侧气泡)区分得开。气泡里只显示 worker 最后一次回报的内容(产物摘要或失败原因),协议字段(会话、状态、下一步)留给模型读;气泡下方是时间与复制按钮,点 worker 名字可跳到它的会话。通知排进 inbox 的 **next-turn** 边界(下一个 turn 的一次输入),且**父会话正忙时先不投**——它留在落盘队列里,等父会话空闲后再投,因此不会挂在宿主输入框上方的「消息队列」面板上。 ## 续聊 worker 用 `message_worker` 投递(`sessionId` 与 `message` 必填,`sessionId` 就是 `spawn_worker` 返回的会话 id;可选的 `rename` 顺手改名,登记表与会话标题同次改到新名字)。**第一条消息就是开工信号**,之后再用它是续聊。会话还活着就直接投进去(返回 `merged`),进程重启后不在内存就先冷恢复再投(返回 `sent`)——两种形态对调用方是同一步。新的一轮结束时同样唤醒 leader,收到一条格式一致的汇报气泡。 **人自己下钻跟 worker 聊天不经过这条路径**:那种轮次不会登记在飞记录,因此不会被结算,leader 不会被打扰。派工要求组合里有会话持久化与查询后端(`sessionPersistence` / `sessionQuery`):缺任一就拒绝派工,而不是悄悄降级成只能跑一轮的一次性 worker。 ## 回看 worker 的会话 `read_worker_session`(`sessionId` 必填,`turns` 可选,默认 2、最多 20)直读 worker 的会话日志并按回合给出:投进去的任务、worker 说过的正文、调过哪些工具、失败原因与每轮终态。它**只读**——不唤醒 worker、不产生模型调用,worker 正在跑也能读;最后一轮尚未 `turn/end` 时标「进行中」,按已追加的事件展示。 它和完成通知分工:通知说这一轮结束了、产物是什么;回看说它是怎么做的。排查「为什么慢、为什么错」先用它,不要为此让 worker 再跑一轮——需要它解释或接着动手时才用 `message_worker`。 回合里「任务」是 leader 投递的正文,「用户」是人下钻到该会话说的话;宿主注入的上下文(换目录、plan 模式等)只记条数、不渲染正文。正文上限、权限与截断细节见[已知限制](limitations.md#worker-的控制面)。 ## 卡片 调用 `create_card` 建卡(`title` 必填),`update_card` 改卡(`id` 必填,改内容带 `expectedRevision` 做乐观并发),`list_cards` 查卡(默认只列 active,一页 20 张;传 `cardId` 读单卡完整规格与链接)。`list_cards` 与 `create_card` 只给 leader;`update_card` leader 与 worker 都有(worker 用它把产物链接挂到自己的卡),普通会话一件也没有——它不在项目编排里,建出来的卡没有槽位、没有 leader 认领。worker 不建卡(建卡与派工是 leader 的动作链),`update_card` 也只作用于自己挂着的卡,且不能改状态、规格、标题与槽位绑定:完成由 leader 验收后判定(`done` / `archived` 会释放执行者),换执行者同样由它做;规格与标题都是验收锚点(走同一条注入通道),worker 的进展与产物写进回报和 `links`。 链接有两种写法:`links` 整体替换,`addLinks` 增量追加(同 `id` 原位更新字段,无 `id` 按 label+url 去重),两者互斥。worker 建出 PR/Issue 后用 `addLinks` 自己挂,不会覆盖 leader 刚写的条目,也不依赖它先读整卡。 每条链接要有非空 `label`;`url` 可选,但给了就必须是 http(s) 绝对地址——写入时按 URL 标准归一化(例如 `https:/example.com` 会存成 `https://example.com/`),卡面只在 http(s) 时渲染成可点链接。传错时工具直接报错并说明是第几条、哪个标签、收到什么值、该怎么改(例如把 `docs/report.md` 改成完整地址,或删掉 `url`),不会落盘半成品。 卡片标 `done` 或归档会自动释放槽位上的执行者(`slots` 清空):worker 交活后不必手工解绑,leader 把卡关掉它就能接下一张卡。挂着未完成卡的 worker 不能接别的工作,投递只会放行同卡续跑;要复用就先关闭旧卡。同一个会话同时挂多张未完成卡时投递一律被拒——先关卡收敛到一张。重新打开卡片不会恢复绑定。 卡片落盘在 `$DSH_HOME/dsh-leader-agent/cards.json`,编号全局递增不复用。**卡片按项目归属**:宿主按调用方会话反查项目(leader 会话走项目注册表的指针,其它会话按 cwd 与项目目录的 realpath 相同),模型不传项目参数。`list_cards` 默认只列本项目,要看全部项目传 `scope=all`;跨项目改卡会被拒绝。卡片说明在详情浮层里按 Markdown 渲染(标题、列表、行内代码、代码块);worker 回报气泡的正文同样按 Markdown 渲染;旧回报走纯文本的一类是没有单独回报字段的(回退的是整条通知正文),另一类是有回报字段、多行但既没有块级结构也没有空行的正文(早期落盘时多块用单个换行连接,按 Markdown 渲染会被折成一段),失败原因也保留纯文本换行。 ## 看板 浏览器右栏打开「看板」Tab。**看板按项目组织且只对项目会话展示**:面板先按当前会话解析出所属项目、确认它是该项目的 leader 或 worker,再按项目拉卡片(被换下的 leader 已降级为项目里的普通会话,拿不到看板入口)——leader 建的卡与 worker 建的卡在同一处;项目目录里的普通会话打开看板只看到「看板只在项目会话中打开」。 卡片不另存一份,看板直接读宿主的卡片库(不是重放会话事件流),因此跨会话聚合成立,worker 会话被回收后卡也不会消失。leader 会话里建卡后右栏会自动打开(或聚焦到已有的)「看板」Tab,出现新卡才打断你,改卡不会。 卡面只做信息与导航:点卡面开详情(说明 / 链接;链接可在详情里整体编辑,保存后看板立刻回填),点链接 chip 新标签打开,点槽位 chip 打开该槽位绑定的会话。已归档的卡是终态,不给编辑入口;已完成卡仍可补挂链接。信号灯由槽位会话状态实时投影(不落盘),七档为「等你输入 / 需注意 / 执行中 / 未读 / 空闲 / 状态未知 / 无信号」。运行态来自**保留会话的快照**:面板所在的右栏座只注入 tab 信息,不提供宿主的会话状态 hook,因此「执行中」与「上次执行失败」成立,「等你输入」「未读」两档在这条路径上无从表达。「状态未知」表示读不到该会话(例如会话已被删除),不谎称空闲。worker 会话是普通根会话,客户端会话目录里通常有独立条目;宿主给不出条目时(历史 subagent 时代的 worker,或会话刚建还没同步)恢复时会用它所属的建卡会话补一个子代理地址再保留——这也是卡片上 worker 槽位圆点能亮起来的前提。 ## 项目管理 项目是显式注册的实体,不是目录。**左栏的「项目」区块是唯一入口**(模型没有项目注册表工具):一条项目记录有名字、目录并绑定一个 leader 会话;删除项目只删这条记录,dsh 工作区、leader 会话与历史都保留在会话库里。 区块在「自动化任务」之下、工作区之上:点右上角的「加项目」选一个目录,项目**当场建好**并出现在列表里(名字默认取目录名,要改用行上的「重命名」)——不再多一道「起名 + 点注册」的确认手续,建好时也不弹成功提示(新项目行本身就是回执;失败、目录名不合法这类一次性消息走顶部提示条;列表收着时建项会自动展开,保证新行当场可见)。目录名当不了项目名时(选到根目录、目录名超过 200 字)退回一个能改名字的输入框,因为那时直接提交只会换回一条没有出口的报错;同名目录会依次建成 `repo`、`repo-2`、`repo-3`。 点标题「项目」收起或展开整个列表,点项目行打开该项目的 leader 会话(主区域切到该会话),点行首槽位展开或收起该项目的会话(worker 与被换下的 leader;当前 leader 与用户手动开的会话都不在其中)。行的「更多」菜单里是重命名、换新 Leader、删除。 新建项目的 leader 会话还空着时,输入框上方有一条引导条:右侧「了解怎么用」替用户发一句开场请求(真的走一轮对话)——leader 先解释 dsh-leader-agent 怎么工作,再问你要不要初始化 `LEADER.md` 与 `SLOTS.md`,确认后由 `init-leader-agent` skill 写文件。「知道了」只关掉并记在本机(按会话分键)。卡片自己不写转录、不请求模型;发出第一条消息后它自然消失。 明确说不需要初始化时,leader 在本会话里不再追问,但换一个新会话还会再问一次——拒绝不做持久化。想彻底停用就创建 `LEADER.md`,或者让 leader 跑 `init-leader-agent` 后只留少量内容。项目已经有 `LEADER.md`(或旧名 `COORDINATOR.md`)时不会有这段询问。 **排列顺序**可以拖:把项目行拖到另一行的上半,插到它之前;拖到下半,插到它之后;拖到最后一行的下半即排到末尾。拖拽中源行压暗、落点画一条插入线,松手后顺序当场生效并落盘(`projects.json` 里的数组顺序),重启与换窗口都保持。新建项目排在末尾;拖回原处不写盘、也不发请求。 当前会话属于该项目时,左栏的回执分两层:整行选中底色只给现任 leader 会话——leader 在项目树下不单独出行,项目行就是它的入口;被打开的 worker 会话在项目下有自己的行,选中态落在那一行(整行底色与官方工作区树的会话行同款),项目行这时只点亮行首文件夹,表示这一组里有你正在看的会话。换下的 leader 与已归档会话不算——它们打开时两层都不亮,因为点这一行去的是现任 leader。同窗口里新建项目或换 leader 之后名单当场刷新,不用切窗口;打开刚派工的 worker 时归属表会立刻重问一次,文件夹与会话行的选中都不晚一拍。 行首槽位平时是目录图标,活动提示按「行首能不能代表它」分两种:leader 会话正在跑时换成加载圈——它在项目树下没有自己的行,所以项目展开着也照换;项目收起时若名下有 worker 在跑,也换成加载圈,这时行首是唯一的活动提示,展开后 worker 由各自的会话行带加载圈,行首回到目录图标。 打开项目前会先确认项目目录还在:目录被删或被移走时给一条顶部提示,说明可以恢复目录或删掉项目后重新添加(换新 Leader 同样先过这一步)。 项目不注册也不占用 dsh 工作区:项目会话落进官方树的「未分组」,在那里被渲染层排除(官方计数与搜索仍包含它们)。组里只剩被排除的行时,「未分组」组头也会藏起来。旧版本把这些会话挂进过工作区,升级后第一次启动会一次性摘掉;摘掉的正是宿主的「新建会话」会复用的空白草稿,正是它让你在工作区里新建会话时跳到 leader。 **删除**会先弹二次确认,讲清文件夹、会话记录与 dsh 工作区都会保留。删除成功后弹一条顶部提示条(「已删除项目「X」。」,几秒后消失)。区块里没有常驻提示行:持续性状态(项目通道未就绪)常显在列表上方,收起列表也不消失;一次性成败都走提示条。重命名时清空名字或超过 200 字,输入框与原因留在行内就地显示,不弹提示条。 项目名后面不跟路径(悬停可见)。项目的 worker 与 leader 会话(含被换下的 leader,它降级为项目里的普通会话、和 worker 同列)在官方工作区树的「未分组」里被渲染层排除,不在两棵树里重复出现。 **换 leader 之后,退役会话不再指挥这个项目**:`spawn_worker`、`message_worker` 与跨项目的 `list_cards` 都在调用时按当前身份判,退役会话一律被拒。换人时原班 worker 的派工归属与未投递的完成回报一并改投新 leader(派工记录里留 `reassignedFrom` 记着是谁派的),所以新会话接手项目之后照样能 `message_worker` 原班 worker;旧会话里已投出的回报属于它的历史,不搬动。 **归档会话**:会话行悬停时右端露出归档按钮。归档后该会话从项目树消失,卡片上指向它的槽位一并摘掉(死引用会让看板挂着一个点开没有去处的执行者),派工登记也删掉(否则 `message_worker` 会走进一条注定被宿主拒绝的路径);会话正在跑时先问「停止并归档」,因为宿主的归档闸门会拒绝运行中的会话。 行的形态照官方工作区行:行首平时是目录图标,鼠标悬停整行时换成展开箭头(展开时箭头朝下),「更多」在行右端、悬停时出现。 打开 leader 会话时,它的形态是**输入框停靠底部的对话视图,但转录里一条消息都没有**——dsh 对空会话默认渲染居中大标题的欢迎页,本 bundle 注册一个第三方会话视图目标并在 leader 会话打开时激活它(宿主据此把外壳切到 active 形态),不产生任何模型调用,也不往转录里塞任何开场白。 ## 角色槽位(`SLOTS.md`) 在项目目录写 `SLOTS.md`,按 `## <槽位名>` 分节写角色的工作方法与人设;在卡片的 `slots` 字段把槽位名绑到会话 id。被绑会话每次模型请求前都会重新求值该槽位正文,正文变化才追加一份新快照,改动 `SLOTS.md` 在下一个模型请求生效。 槽位名是 ASCII 机器标识(小写字母、数字、下划线、连字符,最长 32 字符),卡片工具与解析器共用同一条规则,非法名在写卡时直接报错。文件顶部的 YAML frontmatter 用 `slots:` 声明卡面上的显示名:`slots:` 下按槽位名缩进一层写 `label:`(块状 YAML,不能写成 `slots.dev.label: 开发` 这种点路径),中文可用,只影响界面、不进入注入正文;没声明时内置 `dev` / `review` 按界面语言显示,其余槽位显示槽位名(`discuss` 保留内置翻译)。 槽位只贡献提示词文本,不授予工具权限。没写 `SLOTS.md` 的项目回落默认两角色 `dev` / `review`(正文为空)。节名不合法(中文、大写、超长)的节整节忽略,宿主日志一次列出全部被忽略的节名;一个合法节都不剩时同样回落。同一槽位在 frontmatter 里出现两个不同 `label` 时告警,后者覆盖。同一问题只告警一次,配置修好后再坏会重新报——沉默的配置错误会让注入静默变空串或显示名静默丢失。 ## leader 手册(`LEADER.md`) 在项目目录写 `LEADER.md`,写这个项目的 leader 该怎么指挥——职责边界、派工要求、验收口径、本仓硬约束。**leader 会话每次模型请求前都会重新求值并注入**,不需要卡片绑定,也不用加载任何 skill 才生效;正文变化才追加新快照,改动在下一个模型请求生效。它只贡献提示词文本,与 `SLOTS.md` 一样不授予工具权限。 插件只读 `LEADER.md`。项目里另放一个只指向它的 `COORDINATOR.md` 是给 Telov 等仍按旧文件名找 leader 手册的外部工具用的兼容入口,不参与注入;没有那个工具就不需要建。