富排版组件 demo
P2 阶段的所有自定义组件 + diagram plugin + Matrix 语义类的可视化样品页。
仅用于开发期肉眼对照现有 HTML 截图;不进 sidebar (content/_meta.ts 不列)。
Badge
普通: draft 2026-05-22
强调: accent ok
状态: warning danger info
带 dot: draft passed
Card · CardGrid
需求 A
任务类型可扩展
OpenSpec 4 阶段是当前唯一形态。但 spec-kit / TDD 循环 / bug fix from issue 都是合理 task type 候选。
需求 B
用户运行中追加指令
30 min 任务中段, 用户想干预: “这里加个测试” / “跳过这步” / “换个方向”。
需求 C
主 API 重启不丢事件流
每次 deploy → in-process streamLogs loop 死 → 任务被 janitor 60min 后误判 failed。
G1 · 产品需求
双向通道
web ↔ 主 API ↔ sandbox-agent 三段全部走 WebSocket, 用户能在任务运行中追加指令。
sunk 样式 (无阴影, bg-sunk)
用于次要的、需要视觉降级的卡片。e.g. 不再活跃的 spike 总结。
Decision
D1通信形态 = WebSocket 双向 (替代 SSE + stdout JSONL)
决定: web ↔ 主 API ↔ sandbox-agent 三段全部走 WebSocket, 由中间的 sandbank Relay 桥接。
理由:
- G1 (用户追加指令) 需要 client → server 双向, SSE 做不到
- Claude Agent SDK 原生支持
query({ prompt: AsyncIterable<SDKUserMessage> }) - Fly.io 官方: “WebSocket implementation is very straightforward; no third-party tools necessary”
代价:
- web 端 EventSource 改 socket.io-client (一次性迁移)
- run.js 改为 streamInput + AsyncIterable 模式
Phase
P1
WebSocket 双向链路接入1 周
- 建立 sandbank Relay fly app
- 主 API 加
@WebSocketGateway - web 端 EventSource → socket.io-client
P2
任务类型插件化2 周
task-spec.json协议定稿- RunnerRegistry 表上线
- openspec / spec-kit / tdd-cycle 三个 plugin 适配 HostContext ABI
P3
多 task 并发隔离3-5 天
- Relay channel = task_id 验证
- ResubscribeBootstrap 接入主 API 启动钩子
Matrix (markdown 表格 + cell 语义类)
| 场景 | 当前行为 | 影响 |
|---|---|---|
| 主 API deploy | streamLogs loop 断 | 每次 deploy 期间 running 的 task 失去事件流 |
| web 标签页切走 | SSE 自动重连 | ✓ 不丢 |
| 用户中途追加指令 | 无通路 | 只能 destroy 整个 sandbox 重新派发 |
| 用户想接受 permission | 容器内 bypassPermissions | N/A (设计上跳过) |
KVList
- web ↔ 主 API
@Sse- 主 API ↔ sandbox
handle.streamLogs()- web 自动重连
- 浏览器 EventSource 内建 (3s retry + Last-Event-ID)
- 主 API ↔ sandbox 重连
- 不存在 — in-memory loop 死即断
Diagram (fenced code block ```diagram, remark-diagram plugin 转换)
┌──────────────────────────────────────────────────────────────────────────┐ │ web (browser) 主 API (NestJS, Fly) sandbox │ │ │ │ │ │ │ │ EventSource (SSE) │ │ │ │ │ ◄────── 单向 ─────────────│ │ │ │ │ │ │ │ │ │ │ adapter.streamLogs() │ │ │ │ │ ReadableStream<Uint8> │ │ │ │ │ ◄──── 单向 ───────────────│ │ │ │ │ (sandbox stdout JSONL) │ │ └──────────────────────────────────────────────────────────────────────────┘ • web→主 API: HTTP POST /tasks/:id/dispatch (一次性,非通道) • 主 API→sandbox: 仅在 dispatch 时 provider.create(env, …) 注入,之后无通路 legend: tag (绿色加粗) · dim (灰色) · new (蓝色加粗) · drop (红色线穿)