From 91d0a7189f17541bd6ce95df7d74c29907f66e4a Mon Sep 17 00:00:00 2001 From: Yaojia Wang Date: Tue, 16 Jun 2026 07:08:54 +0200 Subject: [PATCH] docs: add subagent dispatch kit (per-task prompts + templates) --- docs/DISPATCH.md | 261 +++++++++++++++++++++++++++++++++++++++++++++++ docs/PLAN.md | 2 + 2 files changed, 263 insertions(+) create mode 100644 docs/DISPATCH.md diff --git a/docs/DISPATCH.md b/docs/DISPATCH.md new file mode 100644 index 0000000..8b065bb --- /dev/null +++ b/docs/DISPATCH.md @@ -0,0 +1,261 @@ +# Subagent 派活套件 (Dispatch Kit) + +> orchestrator(主会话)按 [PLAN.md §3 波次](./PLAN.md) 派活时,复制下面对应任务的 prompt。 +> 角色/通用规则已写在 [`.claude/agents/`](../.claude/agents)(`module-builder` / `module-reviewer`)的定义里, +> 此处只给**每个任务的变量部分**。`model` / `isolation` 在 `Agent(...)` 调用参数里设(见 PLAN §4),不写进 prompt。 + +## 使用方法 + +1. 选定本波要派的任务(同波、文件不交叉,单批 ~3–5 个)。 +2. 对每个任务调用:`Agent(subagent_type:"module-builder"|"module-reviewer", model:<§4>, isolation:<§4>, prompt:<下面对应块>)`。 +3. **worktree 隔离的 builder 派出前,先把上一波 commit**(否则隔离工作树里没有 `types.ts`)。 +4. 每个 agent 返回后:orchestrator 把它交回的日志条目**追加进 `PROGRESS_LOG.md`**,更新"当前焦点",再派下一波。 +5. agent 返回 `[!] BLOCKED` → orchestrator 解歧义(必要时问用户)→ 重新派活。 + +--- + +## 模板 A · module-builder(实现任务) + +``` +执行 web-terminal 项目的 PLAN 任务 (<一句话标题>)。 + +先读(你的上下文是空白的):CLAUDE.md 的 "Development Workflow";docs/PLAN.md 里的任务 +(看清 Owns/Depends/Steps/Accept);docs/ARCHITECTURE.md 的 ;并在 ARCHITECTURE.md 内 +搜索这些锚点逐条照做:;背景看 docs/TECH_DOC.md <§>。 +从 src/types.ts import 共享类型,**不要**本地另立类型。 + +硬边界 —— 只能创建/修改这些文件:。其它一律不碰(尤其 src/types.ts 和别的模块)。 + +TDD:先写覆盖每个 Step + Accept 的失败测试 → 实现到全绿 → 运行 确认。 +本任务最易错的点:。 + +文档若留有真歧义 → 停下返回 [!] BLOCKED,**绝不猜**。不要写 docs/PROGRESS_LOG.md。 +最终消息以可粘贴的日志条目块结尾(状态/改动/验证/决策偏离/遗留/阻塞/commit)。 +``` + +## 模板 B · module-reviewer(只读验收/复查) + +``` +复查/验收 web-terminal 项目的 <目标>。只读,不改任何源码。 + +先读:docs/ARCHITECTURE.md 的 <§> + §8 不变量清单 + 锚点 ;docs/PLAN.md 任务 的 +Steps/Accept;docs/TECH_DOC.md <§>。 +按优先级核:不变量 → 相关 M*/L* 修订是否真落地 → Accept 与 Steps 覆盖 → 编码风格(不可变/小文件/ +错误处理);对抗性地找能打破它的输入/时序/边界。只跑只读或测试命令(vitest run / git diff / grep)。 + +最终给 findings 报告:verdict PASS / CHANGES NEEDED;每条 finding 标 severity + file:区域 + +违反了哪条不变量/M*/Accept + **路由给哪个 owning task** 去修。无真问题就判 PASS,不要凑数。 +``` + +--- + +## W0 · 基础(串行,主会话直接做或单个 builder) + +### T1 — 脚手架 + 装齐依赖 · `Agent(module-builder, model:"sonnet")` +``` +执行 PLAN 任务 T1(脚手架 + 一次性装齐依赖)。先读 CLAUDE.md 工作流、PLAN 任务 T1、ARCHITECTURE §6(S1)。 +硬边界 —— 只创建:package.json、tsconfig.json、tsconfig.web.json、.gitignore、vitest.config.ts、空目录骨架。 +要点:**一次性**声明全部依赖(运行时 express ws node-pty @xterm/xterm @xterm/addon-fit;开发 typescript +tsx vitest @types/node @types/ws @types/express),避免后续并发 npm install 冲突;后端 tsconfig(NodeNext/strict) ++ 前端 tsconfig.web(lib DOM);.gitignore 含 node_modules/dist;**验证 node-pty 原生编译通过** +(npm install 无错 + node -e "require('node-pty')")。Accept:npm install 成功、npm test 能跑、node-pty 可 require。 +歧义则 [!] BLOCKED;不写 LOG;结尾给日志条目。 +``` + +### T2 — 冻结共享契约 · `Agent(module-builder, model:"sonnet")` +``` +执行 PLAN 任务 T2(共享类型契约 src/types.ts)。先读 PLAN 任务 T2、ARCHITECTURE §3 全部接口定义。 +硬边界 —— 只创建:src/types.ts(全局唯一契约源,完成即冻结)。 +要点:逐个从 ARCHITECTURE §3 誊写为可编译 TS:Config(含 maxPayloadBytes、allowedOrigins)、 +ClientMessage/ServerMessage/ParseResult、Dims、SessionMeta、Session(含 lastOutputAt/exitedAt/exitCode)、 +RingBuffer、IPty 子集(pid,cols,rows,onData,onExit,write,resize,kill)、前端 mountKeybar(onSend) 类型; +各模块对外函数签名以注释列出做 import 锚点。Accept:tsc --noEmit 通过,纯类型无实现。 +歧义则 [!] BLOCKED;不写 LOG;结尾给日志条目。 +``` + +### T3 — mock IPty · `Agent(module-builder, model:"haiku", isolation:"worktree")` +``` +执行 PLAN 任务 T3(mock IPty 测试替身)。先读 PLAN 任务 T3、ARCHITECTURE §7,从 src/types.ts import IPty。 +硬边界 —— 只创建:test/helpers/mock-pty.ts。 +要点:createMockPty() 实现 IPty,可手动触发 emitData(s)/emitExit(code);记录收到的 write/resize/kill 供断言。 +Accept:自带 smoke 测试(emitData 后监听者收到、kill 被记录)。歧义则 [!] BLOCKED;不写 LOG;结尾给日志条目。 +``` + +> **派 W1 前:`git add -A && git commit` W0 产物**,worktree builder 才能看到 types.ts。 + +--- + +## W1 · 叶子模块(并行,单批 ~3–5 个) + +### T4 — config · `Agent(module-builder, model:"sonnet", isolation:"worktree")` +``` +执行 PLAN 任务 T4(config.ts)。先读 PLAN 任务 T4、ARCHITECTURE §3.1,并在 ARCHITECTURE.md 搜 "M1" 照做; +从 src/types.ts import Config。硬边界 —— 只创建:src/config.ts、test/config.test.ts。 +要点(M1):loadConfig 读 env 给默认值;**allowedOrigins 由 os.networkInterfaces() 各网卡 IPv4 + localhost + +可选主机名推导(绝不从 bindHost,结果不含 0.0.0.0)**,再并入 ALLOWED_ORIGINS;非法值 fail-fast 抛错; +返回对象 Object.freeze。测试须断言 allowedOrigins 不含 0.0.0.0、含注入的假网卡 IP(mock networkInterfaces)。 +Accept:vitest run config 全绿。歧义则 [!] BLOCKED;不写 LOG;结尾给日志条目。 +``` + +### T5 — protocol · `Agent(module-builder, model:"sonnet", isolation:"worktree")` +``` +执行 PLAN 任务 T5(protocol.ts)。先读 PLAN 任务 T5、ARCHITECTURE §3.2,搜 "M7" 照做;背景 TECH_DOC §4; +从 src/types.ts import ClientMessage/ServerMessage/ParseResult。硬边界 —— 只创建:src/protocol.ts、test/protocol.test.ts。 +要点:export SESSION_ID_RE(UUID v4);parseClientMessage **永不抛异常**,错误全走 ParseResult.ok=false; +校验 JSON→type 白名单→resize cols/rows 1–1000 整数→input.data 必为 string 原样→attach.sessionId 为 null 或 +匹配 SESSION_ID_RE(M7:abc123 必须被拒、合法 UUID 通过);serialize 三种 server 消息。 +Accept:vitest run protocol 全绿、模糊输入不抛。歧义则 [!] BLOCKED;不写 LOG;结尾给日志条目。 +``` + +### T6 — ring-buffer · `Agent(module-builder, model:"opus", isolation:"worktree")` +``` +执行 PLAN 任务 T6(ring-buffer.ts)。先读 PLAN 任务 T6、ARCHITECTURE §3.4 的 RingBuffer 段,**重点搜 "M2" 全文照做**; +从 src/types.ts import RingBuffer。硬边界 —— 只创建:src/session/ring-buffer.ts、test/ring-buffer.test.ts。 +要点(M2,最易错):内部用 Buffer('utf8')按**真实字节**计量(非 string.length);超容量时**按 chunk 边界整体淘汰 +最旧块**,绝不在多字节 UTF-8 码点或 ANSI 转义序列中间截断;snapshot() 开头补软复位 \x1b[0m。 +测试须覆盖:容量内全量回放、超容量淘汰、多字节中文不被截断、ANSI 序列不被切断、按字节计量。 +Accept:vitest run ring-buffer 全绿。歧义则 [!] BLOCKED;不写 LOG;结尾给日志条目。 +``` + +### T7 — origin · `Agent(module-builder, model:"sonnet", isolation:"worktree")` +``` +执行 PLAN 任务 T7(http/origin.ts)。先读 PLAN 任务 T7、ARCHITECTURE §3.3、TECH_DOC §7(CSWSH)。 +硬边界 —— 只创建:src/http/origin.ts、test/origin.test.ts。 +要点:isOriginAllowed(origin, allowed) **host+port 双匹配**;undefined Origin 默认拒绝(集中决定,不散落 if)。 +测试:白名单命中放行、域名/端口不符拒绝、undefined 拒绝、evil.com 拒绝。 +Accept:vitest run origin 全绿。歧义则 [!] BLOCKED;不写 LOG;结尾给日志条目。 +``` + +### T8 — index.html · `Agent(module-builder, model:"haiku", isolation:"worktree")` +``` +执行 PLAN 任务 T8(public/index.html)。先读 PLAN 任务 T8、ARCHITECTURE §5。 +硬边界 —— 只创建:public/index.html。要点:终端容器 #term、键栏挂载点 #keybar、引 main.ts/style.css、 +移动端 viewport meta。Accept:浏览器打开无控制台报错。歧义则 [!] BLOCKED;不写 LOG;结尾给日志条目。 +``` + +### T9 — style.css · `Agent(module-builder, model:"haiku", isolation:"worktree")` +``` +执行 PLAN 任务 T9(public/style.css)。先读 PLAN 任务 T9、ARCHITECTURE §5。 +硬边界 —— 只创建:public/style.css。要点:全屏终端布局、键栏固定底部且 >768px 隐藏、深色背景。 +Accept:桌面/窄屏两种宽度布局正确。歧义则 [!] BLOCKED;不写 LOG;结尾给日志条目。 +``` + +### T10 — keybar · `Agent(module-builder, model:"haiku", isolation:"worktree")` +``` +执行 PLAN 任务 T10(public/keybar.ts)。先读 PLAN 任务 T10、ARCHITECTURE §6.3,从 src/types.ts import mountKeybar 类型。 +硬边界 —— 只创建:public/keybar.ts、test/keybar.test.ts。 +要点:按键→字节表(Esc \x1b、Shift+Tab \x1b[Z、↑↓←→、Enter \r(不是 \n)、Ctrl+C \x03、Tab \t); +mountKeybar(onSend) 在 touchstart 直接 onSend(bytes) 并 preventDefault 防软键盘;字节表导出为纯数据便于单测。 +Accept:vitest run keybar 全绿。歧义则 [!] BLOCKED;不写 LOG;结尾给日志条目。 +``` + +### T11 — main.ts · `Agent(module-builder, model:"sonnet", isolation:"worktree")` +``` +执行 PLAN 任务 T11(public/main.ts)。先读 PLAN 任务 T11、ARCHITECTURE §5/§6,搜 "M6" 照做;背景 TECH_DOC §6/§9。 +依赖 src/types.ts 的 mountKeybar 签名(仅签名,可并行)。硬边界 —— 只创建:public/main.ts。 +要点:xterm + FitAddon,fit() 必须在容器有真实尺寸后调用(display:none 得 NaN); +**WS scheme 随页面协议 https→wss(M6)**,URL …/term,首帧发 attach(localStorage 取 sessionId); +term.onData→ws.send {input};onmessage 按 type 处理(attached 存 id、output→term.write、exit→提示重连); +ResizeObserver→fit→发 resize(防抖 100ms);断线指数退避重连 1/2/4…≤30s 携带 sessionId;mountKeybar(d=>ws.send)。 +Accept:tsc -p tsconfig.web.json 通过(行为留 W5 联调)。歧义则 [!] BLOCKED;不写 LOG;结尾给日志条目。 +``` + +> 派 W2 前:commit W1 产物。 + +--- + +## W2 / W3 / W4 · 会话与集成(基本串行) + +### T12 — session · `Agent(module-builder, model:"opus")` +``` +执行 PLAN 任务 T12(session/session.ts)。先读 PLAN 任务 T12、ARCHITECTURE §3.4,逐条搜 "M4" "M5" "L1" "L4" 照做; +从 src/types.ts import Session/SessionMeta/IPty,用 src/session/ring-buffer.ts 的 createRingBuffer、 +test/helpers/mock-pty.ts 测试。硬边界 —— 只创建:src/session/session.ts、test/session.test.ts。 +要点:createSession(cfg,dims,now,onExit) 接 onData(append+刷新 lastOutputAt+转发 attachedWs)、 +接 onExit(置 exitedAt/exitCode→有 ws 则发 exit→调注入 onExit);**spawn 失败不吞,向上抛(M4)**; +attachWs 先重置 attachedWs 指针再回放 snapshot 再续流、返回被踢旧 ws;detachWs 置 detachedAt **不杀 PTY**; +writeInput/resize 在 exitedAt!=null 时直接 return(L4)、resize 幂等;send 前查 readyState(M5)。 +测试(mock IPty):detach 不杀 PTY、attach 回放、后到踢前者、detach 后 emitExit 保留会话、已退出 PTY 的 write/resize 被忽略。 +Accept:vitest run session 全绿。歧义则 [!] BLOCKED;不写 LOG;结尾给日志条目。 +``` + +### T13 — manager · `Agent(module-builder, model:"sonnet")` +``` +执行 PLAN 任务 T13(session/manager.ts)。先读 PLAN 任务 T13、ARCHITECTURE §3.5,逐条搜 "M3" "M4" "L1" "L2" 照做; +依赖 src/session/session.ts。硬边界 —— 只创建:src/session/manager.ts、test/manager.test.ts。 +要点:handleAttach:null→新建;命中存活→attachWs;**命中已退出→回放+补发 exit 后移除(L1)**;查不到→新建返回新 id; +createSession 抛错向上抛(M4)。reapIdle:**now - max(detachedAt,lastOutputAt) > idleTtl 才回收(M3)**。 +shutdown kill 全部。注入给 createSession 的 onExit 从会话表删除(L2)。 +测试(mock IPty):三种 attach 路径、reapIdle 有新输出不回收/超时无输出回收、shutdown 全 kill。 +Accept:vitest run manager 全绿。歧义则 [!] BLOCKED;不写 LOG;结尾给日志条目。 +``` + +### T14 — server 接线 · `Agent(module-builder, model:"sonnet")` +``` +执行 PLAN 任务 T14(server.ts)。先读 PLAN 任务 T14、ARCHITECTURE §3.6,逐条搜 "M4" "M5" "L3" "L5" 照做; +用 config/protocol/origin/manager。硬边界 —— 只创建:src/server.ts。 +要点:Express 托管 public/;**WebSocketServer({noServer:true})**;HTTP upgrade:非 /term→destroy、Origin 不过→ +socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n')+destroy、通过→handleUpgrade→emit connection(L3); +connection 等首帧 attach→try handleAttach 回 attached,**catch→send exit(-1,reason)+close 仅此连接,不 fail-fast(M4)**; +message:parse,ok:false 丢弃记日志,否则路由 writeInput/resize;close→detach;SIGINT/SIGTERM→shutdown; +**ws.send 前查 OPEN(M5)**;ws.maxPayload=cfg.maxPayloadBytes(L5);/term 路径常量入 config。 +Accept:本机浏览器连通拿到 shell、坏 Origin 被 401(自动化留 T15)。歧义则 [!] BLOCKED;不写 LOG;结尾给日志条目。 +``` + +### T15 — 集成/E2E · `Agent(module-builder, model:"sonnet")` +``` +执行 PLAN 任务 T15(集成/E2E 测试)。先读 PLAN 任务 T15、ARCHITECTURE §7,搜 "M4" "M2" "L3" "L5";依赖 T14。 +硬边界 —— 只创建:test/integration/*.test.ts。 +要点(起真实 WS):attach→attached→output 时序;reconnect 回放并断言 ANSI/中文不乱(F5/F6); +spawn 失败→exit(-1)+仅关连接(M4);坏 Origin/非 /term→拒(F9/L3);超 maxPayload 帧被拒(L5)。 +Accept:vitest run integration 全绿。歧义则 [!] BLOCKED;不写 LOG;结尾给日志条目。 +``` + +> 派 W5 前:commit W4 产物。 + +--- + +## W5 · 验收(只读,reviewer)+ 收尾(orchestrator) + +> 验收 agent **不改源码**;发现问题标 severity + owning task,修复**派回该模块的 module-builder**。 + +### T16 — 验收 F1–F3 · `Agent(module-reviewer, model:"sonnet")` +``` +验收 web-terminal 的 F1–F3(终端基础/TUI/resize),对照 TECH_DOC §8。只读不改源码。 +核:ls/补全/Ctrl+C/方向键历史(F1);vim/top/claude 颜色与重绘(F2);拖窗口行列实时调整、TUI 不错位(F3)。 +给 findings 报告:verdict + 每条 severity + 现象 + 违反的 Accept/不变量 + owning task。无问题判 PASS。 +``` + +### T17 — 验收 F4 + F9 · `Agent(module-reviewer, model:"sonnet")` +``` +验收 F4(局域网访问)+ F9(Origin)。先读 ARCHITECTURE §3.1/§3.3 + 搜 "M1"、TECH_DOC §7。只读不改源码。 +核:手机/他机 http://:3000 可用(F4,**重点确认 allowedOrigins 含本机网卡 IP**);跨源 WS 握手被 401、非 /term 被拒(F9/L3)。 +给 findings 报告(severity + owning task)。无问题判 PASS。 +``` + +### T18 — 验收 F5 + F6 · `Agent(module-reviewer, model:"sonnet")` +``` +验收 F5(会话保活)+ F6(重连回放)。先读 ARCHITECTURE §3.4/§3.5 + 搜 "M2" "L1"、TECH_DOC §5.2。只读不改源码。 +核:发长任务→刷新/换设备→仍在跑、内容不丢(F5);离线期输出重连回放、**ANSI/中文不乱码(F6/M2)**;detach 后退出仍见末屏+exit(L1)。 +给 findings 报告(severity + owning task)。无问题判 PASS。 +``` + +### T19 — 验收 F7 · `Agent(module-reviewer, model:"sonnet")` +``` +验收 F7(移动快捷键栏)。先读 ARCHITECTURE §6.3。只读不改源码。 +核:真机按 Esc/Shift+Tab/方向/Enter/Ctrl+C 生效、不弹软键盘。给 findings 报告(severity + owning task)。无问题判 PASS。 +``` + +### T20 — 验收 F8 · `Agent(module-reviewer, model:"sonnet")` +``` +验收 F8(进程退出处理)。先读 ARCHITECTURE §3.4/§3.5 + 搜 "L1"、TECH_DOC §8。只读不改源码。 +核:shell 退出提示重连、回车开新会话;**detach 后退出**回来仍见末屏+exit(L1)。给 findings 报告(severity + owning task)。无问题判 PASS。 +``` + +### T21 — 安全核对 + README + LOG 收尾 · **orchestrator 自做**(涉及写 LOG/README,不派并行 subagent) +- 可先派 `module-reviewer` 做只读安全核对: +``` +对照 TECH_DOC §7 风险表逐条核 web-terminal 的安全实现(Origin 校验、明文嗅探提示、严禁公网、maxPayload/背压)。 +只读不改源码。给 findings 报告(severity + owning task)。无问题判 PASS。 +``` +- orchestrator 据报告写 README(npm install/start/test、查 IP、Tailscale 部署、安全警告)、标 LOG v0.1 完成、确认 npm run build 产物可 npm start。 diff --git a/docs/PLAN.md b/docs/PLAN.md index 2d3ea09..3bc2f19 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -288,6 +288,8 @@ W5 验收与收尾(按 F# 并行) T16–T20 F1–F9 实测 · T21 安全+README ## 3. 建议的并行分派(参考) +> 每个任务**填好的 dispatch prompt**(可直接复制调用 subagent)见 [DISPATCH.md](./DISPATCH.md)。 + | 批次 | 可同时开工的任务 | 说明 | |------|------------------|------| | 第 1 批 | T1 | 串行,装齐依赖 |