重构世界模拟器为模块化配方架构,完善创作编排、会话运行时与 Web UI,并清理过时技能。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-30 00:39:32 +08:00
parent 2b74c30d36
commit e670a5129c
167 changed files with 22955 additions and 5659 deletions

452
docs/ui-design.md Normal file
View File

@@ -0,0 +1,452 @@
# Web UI 心流设计
## 1. 定位
Writing Agent 的 Web 界面是 **全屏创作工作台**,不是即时通讯客户端。
**用户可见文案**阶段名、Worker 名、气泡标题)须服从 **`ui-glossary.md`**:内部仍用 `design` / `design-core` 等 id界面映射为「创作」「创作 · 核心」等中文。实现见 `src/server/display-labels.ts``web/display-labels.js`
| 层 | 角色 |
|----|------|
| **工作面working surface** | 主舞台:用户此刻要看、要改、要验收的对象 |
| **协调层coordination** | 对话与确认:说明意图、回答问题、触发下一步 |
| **项目轨project rail** | 低频:作品切换、快照、模式切换 |
| **系统态ambient** | 后台Agent 执行、调度日志,默认不抢视线 |
**对话是手段,产物与叙事是目的。** 聊天气泡适合承载协调叙事,不适合承载长规格全文或沉浸式阅读。
主场景是 **全屏浏览器、长会话12 小时)**,布局应稳定、可预期,而非频繁切 Tab 或在三块等权面板间跳视线。
相关代码:`web/index.html``web/app.js``web/agent-ui.js``web/styles.css`;视图模型 `src/server/agent-view.ts`
---
## 2. 两种业务阶段 = 两种主布局
`lifecycleStage``design` / `play`)切换时,**主舞台内容应变形**,壳子(顶栏、底栏、左轨)保持同一套肌肉记忆。
### 2.1 创作 `design` — 规格工作台
用户在定义世界、验收 Worker 集、选定开局;核心是 **结构化产物**JSON 规格、表格、规格卡)。
```text
主视线:正在成形 / 正在审阅的产物
辅视线:协调对话(窄列或底栏上方的摘要条)
冷信息:项目轨、快照、设计进度
```
典型路径:
```text
描述意图 → 主区草稿成形 → 回答追问(表单) → 验收全文 → Accept
↑ 主舞台 ↑ 检查器 ↑ 绑在产物上
```
**心流标杆(组合参考)**Notion结构化编辑+ GitHub PR版本验收+ NotebookLM零散想法 → 结构化草稿)。
### 2.2 游玩 `play` — 叙事驾驶舱
用户代入角色、推进回合;核心是 **叙事流 + 世界状态**
```text
主视线:回合输出(阅读优先,视野占比 70%+
辅视线:底部命令输入(短、快、位置固定)
冷信息:角色/世界状态抽屉、多 run 存档
```
典型路径:
```text
读回合输出 → 输入行动 → 等生成 → 继续读
↑ 主舞台 ↑ 固定底栏
```
**心流标杆**SillyTavern / 视觉小说阅读器(沉浸叙事)+ Roguelike 存档抽屉(同一开局多条 run
`world-simulator` 包说明见 `skills/dialogue/world-simulator/README.md`;流程概念见 `creation-playbook.md`
---
## 3. 全屏网页约束
与手机 IM如 QQ不同全屏 Web 有以下硬约束:
| 约束 | 设计含义 |
|------|----------|
| **视口宽** | 可横向分栏:工作面 5565% + 协调区 3545%,不必单列堆叠 |
| **会话长** | 底栏输入/确认位置永远不变;减少布局跳动 |
| **产物大** | Worker 集、开场白等长文进工作面,不进聊天气泡 |
| **双模式** | 同一壳子、两种主画布,不靠一套气泡布局打天下 |
QQ 等 IM 仅保留一条可借鉴点:**底栏作为唯一行动锚点**。其余按工作台设计。
---
## 4. 壳子布局Figma 式分区)
全屏下的推荐骨架:
```text
┌────┬──────────────────────────────┬────────────┐
│项目│ 主画布(随 stage 变) │ 检查器 │
│轨 │ │(按需展开) │
│窄 │ design: 规格 / 表格 / 验收 │ │
│ │ play: 叙事流 │ │
└────┴──────────────────────────────┴────────────┘
│ 命令栏composer固定
└──────────────────────────────────────────────────┘
```
| 区域 | 职责 | 频率 |
|------|------|------|
| **左轨** | 作品列表、路径、快照入口 | 低 |
| **顶栏** | 作品名、创作/游玩切换、状态 pill、`waitingReason` 摘要 | 常瞥 |
| **主画布** | 当前任务的工作面 | 主视线 |
| **检查器** | 产物元数据、验收按钮、进度、可编辑表格 | 按需 |
| **命令栏** | 输入、发送、主确认(与检查器不重复) | 手常驻 |
检查器默认 **收起**;有产物验收、结构化填空、开局 swipe 等任务时 **滑出**(推开主画布,窄屏用全屏 drawer。不是常驻 300px Agent 监控台。
---
## 5. 参考产品(按心流相似度)
| 产品 | 借鉴什么 | 对应本项目的阶段 |
|------|----------|------------------|
| **Cursor `AskQuestion`** | 独立询问卡、字母点选、分页、Skip/Continue、键盘捷径聊天只留索引 | `worker_questions`§8选项可编辑为我们的增强 |
| **ChatGPT 澄清问法** | 题干短、选项 = 完整可采纳句;一次收齐再继续 | 发问侧文案与批大小 |
| **Claude Artifacts / ChatGPT Canvas** | 产物占主视区,对话收窄为协调 | `review_artifact`、Worker 集验收;询问卡放置 |
| **GitHub Pull Request** | 被审对象为主舞台讨论为时间线Merge/Request changes 绑在对象上 | Accept、variant 切换、重 roll |
| **Notion / Coda** | 文档/数据库是「家」AI 辅助不抢主位 | `intake`、变量 schema、Worker 分工 |
| **Figma** | 主画布 + 窄轨 + 按需检查器 | 全屏壳子结构 |
| **SillyTavern / AI Dungeon** | 叙事占满视野;侧栏可收 | `play` 回合阅读 |
| **NotebookLM** | 多源 → 中间协调 → 右侧成型笔记 | 创作开头 intake、草稿预览 |
| **Linear** | 状态一眼可见、操作路径短、装饰极少 | 顶栏状态、长按效率 |
---
## 6. 四条统一原则
`design` / `play` 均遵守:
| 原则 | 含义 | 反面 |
|------|------|------|
| **一屏一事** | 同一时刻只有一个主任务(审产物 / 答提问 / 读叙事) | 右侧同时堆历史 + 验收 + 进度 + Agent focus |
| **行动点唯一** | Accept、发送、swipe 只在一处出现 | composer 与检查器各放一套接受按钮 |
| **对话是索引** | 气泡放摘要 + 跳转;全文在工作面 | Worker 长产出塞进中间气泡 |
| **底栏锚定** | 输入/确认永远在底部同一区域 | 底栏在纯按钮与 textarea 间大幅换位 |
---
## 7. 消息与面板的职责切分
### 7.1 中间协调区(当前 `message-feed`
**应展示:**
- 用户输入
- Worker 提问的 **一行索引**(「提问中 · 1/3」题干与选项在询问卡§8
- 简短结论、错误提示
- 流式 pending执行中摘要
- 长产物的 **摘要 +「已在检查器打开」** 链接
**不应展示:**
- `agent_tool``orchestrator_decision``worker_running`(已在 `FEED_HIDDEN_KINDS`
- 验收中的产物全文(`review_artifact` 时隐藏 `worker_output`
- intake 完整表格(仅进度摘要)
- `worker_questions` 的完整选项列表(进 §8 询问卡)
实现:`web/agent-ui.js``shouldShowInFeed``FEED_HIDDEN_KINDS`
### 7.2 检查器(目标态;当前部分在 `panel-agent`
**应承载:**
- 产物预览 / 编辑 / diff
- intake 结构化表单
- `worker_questions` 的备选容器(主路径见 §8 询问卡)
- 开局 swipe 卡片栈
- 设计进度Worker 集卡片、步骤)
- **固定上下文(独立区)**:纲领 / 常驻 / 黑板 tag 与 Worker 解耦;每张卡标明 **塞进哪些 Worker**(注入正文 / 常驻挂载 / 读入黑板)
- **上下文 / 黑板**(终产物 tag、定稿摘要、归档计数
- 验收主按钮(接受 / 不接受创作删本单位交互消息只留产物run压缩过程 tag
Worker 卡不再嵌套完整 tag 列表,只显示「上下文」摘要;完整挂载关系见上方固定上下文区。
**应降级折叠:**
- Agent focus、tool trace
- 调度时间线现「历史」Tab→ 高级 / 调试模式
### 7.3 命令栏(`composer`
`resolveComposer``web/app.js`)按 `phase` + `waitingReason` 变形,但位置不变:
| 态 | 表现 |
|----|------|
| `running` | 轻量状态 + 允许预输入草稿 |
| `approve` / `accept` | 主按钮在底栏hint 一行 |
| `review_artifact` | 底栏:输入并发送=改产物;旁挂「接受目前产物」。产物卡上不放接受钮 |
| `intake` / `input` | textarea + 发送 |
| `worker_questions` | **不抢主交互**;主回答在询问卡内(见 §8 |
---
## 8. 结构化询问卡Questions Card
`worker_questions` / `ask_user`**首选交互**。壳子与键盘心流对齐 **Cursor `AskQuestion`**;选项语义与文案改写对齐 **ChatGPT / Claude 的「选项 = 可执行草稿」**;放置原则对齐 **Artifacts / Canvas「决策面 ≠ 聊天气泡」**
### 8.0 业内标杆:借鉴什么、刻意改进什么
| 产品 / 模式 | 借鉴(照抄心流) | 不照抄 / 我们的改进 |
|-------------|------------------|---------------------|
| **Cursor `AskQuestion`** | 独立询问卡(非气泡堆选项);`A/B/C` 字母点选;`n of N` 分页;底部 **Skip + Continue**Enter / Esc聊天流只留「在答」索引 | Cursor 选项多为**只读**,改细节只能塞底部通用备注 → 我们默认**选项文案可改写**,点文案编辑、点字母选中(社区对 Cursor 的高频诉求,见 AskQuestion inline-edit 讨论) |
| **ChatGPT** | 澄清问法:题干短、选项是完整可采纳句;多题一次收齐再继续;自由补充不抢主路径 | 不用纯聊天气泡做多选题主交互;长选项不进 feed |
| **Claude Artifacts / ChatGPT Canvas** | 「结构化决策 / 产物」与对话分离:卡/面板是工作面,聊天是协调 | 询问卡不是 Artifact 全文,而是短决策面;验收长产物仍走检查器 |
| **Linear / GitHub PR 决策** | 一屏一事、主按钮唯一、状态一眼可见 | 不引入 Issue 式侧栏评论串 |
**产品结论(写死):**
1. **主路径 = Cursor 式询问卡**(字母选中 + Continue不是 QQ/ChatGPT 气泡里的长问答。
2. **选项 = 可编辑草稿**(相对 Cursor 默认只读的增强):多数一点即答;不满意就改文案再选,不必另开 Other。
3. **对话流只索引**:「提问中 · 1/3」题干与选项只在卡上。
4. **命令栏降级**:本态不当主输入;可选「自由补充」兜底,语义从属于卡上 Continue。
```text
┌─────────────────────────────────────────────┐
│ ? Questions ▲ ▼ 1 of 3 │
├─────────────────────────────────────────────┤
│ 1. Q1: 问题正文…… │
│ [A] 选项文案(可编辑) ← 点文案 = 改写 │
│ [B] 选项文案… ← 点字母 = 选中 │
│ [C] 选项文案… │
│ [+] Other… [_________] │
│ │
│ 2. Q2: …… │
│ … │
├─────────────────────────────────────────────┤
│ Skip Esc [ Continue ↵ ]│
└─────────────────────────────────────────────┘
```
**最关键交互:选项默认可改写;点选项文案是编辑,点字母按钮才是选中。**
Agent 给的选项是「可编辑草稿」,不是只读单选列表。
### 8.1 为什么适合本产品
| 特质 | 对用户心流的作用 | 业内对应 |
|------|------------------|----------|
| **点选优先,改写兜底** | 多数情况一点字母即答;不满意就改文案再选,不必另开 Other | Cursor 点选 + ChatGPT「改一句再说」 |
| **选项即可执行语义** | 提交的是用户确认(或改写后)的完整句子,可直接喂给 worker | ChatGPT / Claude 澄清选项写法 |
| **编辑与选中分离** | 避免「想改一下措辞」却误触提交路径;符合「一屏一事」 | 相对 Cursor 只读选项的增强 |
| **分页批次** | 「1 of 3」管理预期单屏别堆太多题 | Cursor `AskQuestion` |
| **键盘捷径** | Enter = ContinueEsc = Skip字母键可选中可选增强 | Cursor / IDE 习惯 |
| **模态焦点** | 当前这一批问清了再 Continue | Cursor 卡内提交;非边聊边答 |
适用:`design` 里 intake 追问、规格确认;`play` 里「局面 + 可选行动」;凡原先要用户在 composer 里写长段回答的提问,优先改成卡。
### 8.2 放置
| 区域 | 职责 | 业内对应 |
|------|------|----------|
| **主画布消息流下方(推荐)** | 询问卡嵌在协调列底部composer 之上),宽度随列自适应,不遮盖历史消息 | 决策面与对话分离,但占文档流而非浮层 |
| **检查器** | 备选:与产物并排时,卡可嵌在检查器顶部;长产物场景优先主画布居中卡 | Canvas / 侧栏表单 |
| **中间气泡** | 只留一行索引「Worker 提问中 · 1/3」点开回到卡 | Cursor聊天不堆完整选项列表 |
| **命令栏** | 降级:本态不当主输入;仅作兜底自由补充(可选) | Cursor 底部「optional details」——我们刻意弱化主语义在选项改写 |
Continue 提交后:卡收起 → 系统 `running` → 回复摘要进对话流(摘要用**用户最终确认的文案**,含改写)。
### 8.3 卡片结构与命中区
| 元件 | 规则 | 对齐 |
|------|------|------|
| **Header** | 标题「Questions」+ 批次 `n of N` + 上下翻批 | Cursor |
| **题干** | 编号 `1.`;一句说清决策点;避免在选项里复述背景 | ChatGPT 澄清问法 |
| **字母按钮 `[A]`…** | **唯一「选中」命中区**;选中后该行高亮;同题单选 | Cursor |
| **选项文案区** | **默认可编辑**;点击文案(或已聚焦时)进入改写,**不触发选中** | 相对 Cursor 的增强(社区诉求) |
| **Other…** | 每题末可选;短输入;与改写选项二选一语义相同——都是「自拟答案」 | Cursor Other + ChatGPT 自由答 |
| **Skip** | 本批可跳过Esc下游需能处理「未答」 | Cursor |
| **Continue** | 主按钮Enter校验必选题已有选中项提交的是该选项当前文案 | Cursor |
#### 8.3.1 编辑 vs 选中(强制)
```text
一行选项 = [字母按钮] + [文案区域]
点 [A] → 选中 A提交候选变为 A 的当前文案)
点 文案区 → 进入编辑;改写本地草稿;不改变选中态
(若尚未选中,可编辑完再点字母选中;
若已选中 A 又改了文案,提交仍用改写后的 A
```
细则:
1. **默认所有选项文案可改**(含 Agent 预填的 A/B/C不是「只能选、想改去 Other」Cursor 现状的痛点,我们默认解掉)。
2. **点文案 ≠ 选中**。禁止「点整行即选中」的常见单选实现。
3. **只有字母按钮(或等价:键盘 A/B/C负责选中**
4. 编辑中Enter 可结束编辑并保持焦点在卡内;勿与 Continue 的 Enter 冲突(编辑态下 Enter = 收起编辑)。
5. 提交 payload 必须带上 **最终文案**`label` / `text`),不能只交 `optionId`——否则改写丢失ChatGPT/Cursor 都要求「用户说了什么」完整回传)。
6. Other 与「改写某选项」并存Other 是空槽自拟;改写是在 Agent 草稿上微调。二者都合法。
多批Continue 先推进当前页,再进下一批;**全部页完成后再**统一提交并解除 `waiting_user`(对齐 Cursor 一批问清再继续)。
### 8.4 与 runtime 的契约
`askUser` / `ask_user` 已支持结构化 `QuestionItem[]`(兼容旧 `string[]`)。提交走 `POST /api/sessions/:id/answers`
```ts
{
answers: [
{ questionId: "q1", optionId: "A", text: "(用户看到的/改写后的完整句)" },
{ questionId: "q2", optionId: "other", text: "…" },
]
}
```
前端:主画布消息流下方询问卡(点字母选中、点文案编辑、分页 Continue气泡可只显示答句发给 AI 仍含问+答。
Worker / Agent 发问侧(对齐 Cursor AskQuestion询问不阻断主产出
- 总管 `ask_user`**assessment 是主内容**`questions` 挂在其下,用户可 Skip 并请总管基于现有信息继续。
- Worker优先 `outputs` + `askUser` 同时给出;有产物时追问挂在验收态下,用户可直接「接受目前产物」而不作答。仅完全无法产出时才阻塞提问。
- 能推断选项时 **必须**`options`;每项写成用户可直接采用或微调的**建议示范**(可含短场景钩子),禁止空泛「是 / 否」。
- `editable` 默认 `true`;挂载题 `required` 默认 `false`
- 单批题量控制在 Cursor 舒适区:**默认每页 1 题(左右切换),整批不宜超过 5 题**;更细的拆到下一轮 `ask_user`
过渡期:仅有 `string[]`UI 将每题渲染为题干 + Other。询问卡首屏可展示 `waitingReason.message` 中的内容评价。
实现:`web/questions-ui.js``web/styles.css``.qcard*`)、`web/agent-ui.js` 索引气泡。
### 8.5 反模式
- 点整行 / 点文案就选中(应点字母才选中)— 违反 Cursor 式命中区
- 选项只读、想改只能走 Other 或底栏备注Cursor 痛点,禁止照抄)
- 提交只带 `optionId`、丢掉用户改写后的 `text`(业内答案回传完整性要求)
- 把 5+ 道长选项塞进一条聊天气泡ChatGPT/Cursor 都已证明劣于独立卡)
- 选项写「是 / 否」却题干含糊
- Continue 与底栏「发送」并存且语义不同
- 无 Skip/必答标记,用户卡死无法继续
- 编辑态下 Enter 误触 Continue 整卡提交
---
## 9. `waitingReason` × `lifecycleStage` 显示规格
目标态 UI 分配表(实现时对照 `agent-view``buildFocus``actions`)。
| waitingReason / phase | design 主画布 | play 主画布 | 检查器 | 命令栏 | 顶栏状态 |
|------------------------|---------------|-------------|--------|--------|----------|
| `idle` / 未打开作品 | 空状态引导 | — | 关 | 禁用 | — |
| `intake` | 空或首条引导文案 | — | intake 表单(有字段时展开) | 描述需求 + 发送;必要项满则确认 | 描述创作需求 |
| `input`(首句) | 协调摘要 | 叙事历史 | 关 | 发送 | 描述 / 补充 |
| `input`(补充) | 协调摘要 | 叙事历史 | 关 | 发送 | 补充说明 |
| `running` | 流式 pending | 流式 pending | 关 | 等待态(可预输入) | Agent/Skill 执行中 |
| `worker_questions` | **询问卡§8** + 一行索引气泡 | 同左;行动题尤其用选项 | 可选副展示 | 降级;可 Skip | 回答提问 |
| `approve_step` | 决策摘要 | 决策摘要 | 可选:待 invoke 说明 | 确认 / 暂不 / 说明意见 | 建议 invoke |
| `review_artifact` | 「请验收」摘要;若有挂载题则询问卡可选(**Accept 即收起** | 同左 | **产物全文 + 接受/不接受** | 修改意见(可选);接受 = 无需再完善 | 验收产物 |
| `revision` | 修改说明上下文 | 同左 | 相关产物 | 按 instruction 输入 | 修订中 |
| opening swipe业务 | 卡片栈主舞台 | — | 版本 nav + 锁定 | 选定 / 换一版 | 选定开局 |
| `done` | 阶段结束摘要 | 会话结束 | 关 | 空闲提示 | 已完成 |
`skill_selection` 为遗留恢复态UI 引导用户发消息继续即可。
---
## 10. 阶段心流图
```mermaid
flowchart TB
subgraph shell [固定壳子]
Rail[左轨 · 作品]
Top[顶栏 · 模式与状态]
Cmd[底栏 · 命令栏]
end
subgraph design [design 主画布]
Spec[规格 / 表格 / 验收]
CoordD[协调摘要]
QCardD[询问卡 · worker_questions]
end
subgraph play [play 主画布]
Narr[叙事流]
CoordP[回合摘要]
QCardP[询问卡 · 行动选择]
end
subgraph inspector [检查器 · 按需]
Artifact[产物 / 表单]
Progress[设计进度]
Debug[调度日志 · 折叠]
end
Rail --> Spec
Rail --> Narr
Top --> Spec
Top --> Narr
Spec --> Cmd
Narr --> Cmd
Spec -.-> Artifact
Narr -.-> Artifact
CoordD --> Cmd
CoordP --> Cmd
QCardD -->|Continue| Cmd
QCardP -->|Continue| Cmd
```
---
## 11. 现状与目标差距
| 现状(`web/` | 心流问题 | 目标 |
|----------------|----------|------|
| 右侧常驻 `panel-agent`(约 300px | 空跑时也占视线 | 按需检查器,默认收起 |
| focus / tool-trace 在右侧顶部 | 像 IDE 调试台 | 并入折叠日志或顶栏小状态 |
| intake 可在 feed 与 composer 两处 | 信息重复 | 表格进检查器feed 仅摘要 |
| `worker_questions` = 文本列表 + composer | 用户写长段、选项语义易丢 | **§8 询问卡**:文案可改、点字母选中 |
| `askUser: string[]` | 无 options / Other / 分页 | 升级 `QuestionItem` 协议 |
| 验收时 composer 与「验收」Tab 均可操作 | 行动点分散 | 主按钮只在检查器 |
| 移动端隐藏整个 `panel-agent` | 无产物面 | 全屏 drawer |
| 主区标题写「agent-first」 | 与产品定位不符 | 创作 = 产物优先;游玩 = 叙事优先 |
已有正确方向(保持并强化):
- SillyTavern 式气泡与左右对齐(`play` 适用)
- `FEED_HIDDEN_KINDS` 分流内部消息
- `review_artifact` 时产物不进 feed、自动切验收视图
- 底栏 `composer``waitingReason` 变形
---
## 12. 实现备注
- 视图数据:`SessionView` / `buildAgentView``src/server/agent-view.ts`)已区分 `messages``reviewArtifact``waitingReason``actions``intake`;询问卡需扩展 questions 结构,见 §8.4。
- 消息分类:`classifyAgentMessage``EnrichedMessage.kind` 驱动 feed 过滤;`worker_questions` 在 feed 仅留索引。
- 生命周期:`inferLifecycleStage``body[data-lifecycle]``web/agent-ui.js`)控制 accent可扩展为 **主画布布局切换**
- 验收:`renderReviewPanel` → 迁移为检查器主内容,与 composer 去重。
- 设计进度:`renderSkillGuide` / `worker-set-user-panel` → 检查器内按需 Tab非默认主视图。
- 发问侧:`src/worker/executor.ts``askUser`、docs/`worker-skill-format.md` §ask_user → 支持带 `options` 的结构化问题。
改版时建议顺序:
1. 检查器按需展开 + 验收动作收敛
2. **§8 询问卡 UI**(先兼容现有 `string[]`,再补 options 协议)
3. design 主画布产物优先(收窄协调列)
4. play 叙事区拉满 + 状态抽屉
5. 调度日志降级为高级折叠
6. 窄屏 drawer 与 opening swipe 专屏
---
## 13. 相关文档
| 文档 | 关系 |
|------|------|
| `architecture.md` | 总览UI 为 Session 的人机界面 |
| `creation-playbook.md` | design → play 业务流程 |
| `runtime-state-machine.md` | `waitingReason` 定义 |
| `worker-skill-format.md` | `ask_user` / 提问侧契约 |
| `run-snapshot.md` | 快照与分支;左轨/检查器入口 |
| `implementation-guide.md` | `web/*` 文件职责 |
| `book-storage.md` | 作品、存档、资产形态 |