重构世界模拟器为模块化配方架构,完善创作编排、会话运行时与 Web UI,并清理过时技能。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
32
THIRD_PARTY_NOTICES.md
Normal file
32
THIRD_PARTY_NOTICES.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Third-Party Notices
|
||||
|
||||
本仓库以自研运行内核与文档为主。下列项目**仅作架构与实现参考**(见 [`docs/references.md`](docs/references.md)),默认**不**将它们的源码整仓纳入本仓库。
|
||||
|
||||
在实际引入 npm/依赖、复制示例代码、或嵌入前端素材之前,必须逐项确认:
|
||||
|
||||
- LICENSE 是否允许修改与再分发
|
||||
- 是否要求保留版权声明
|
||||
- 前端素材是否使用不同许可证
|
||||
- 示例代码与主仓库许可证是否一致
|
||||
- 模型 Provider 是否另有使用限制
|
||||
- 角色卡等格式是事实标准还是含特定项目代码
|
||||
|
||||
## Reference projects (not vendored by default)
|
||||
|
||||
| Project | URL | Intended use in this repo |
|
||||
|---------|-----|---------------------------|
|
||||
| Mastra | https://github.com/mastra-ai/mastra | Reference only (agent/tool patterns); not the runtime kernel |
|
||||
| assistant-ui | https://github.com/assistant-ui/assistant-ui | Optional UI pattern reference |
|
||||
| LobeChat | https://github.com/lobehub/lobe-chat | Product/IA reference |
|
||||
| SillyTavern | https://github.com/SillyTavern/SillyTavern | UX/concept reference for cards & lorebooks |
|
||||
| LangGraph.js | https://github.com/langchain-ai/langgraphjs | Checkpoint / recovery ideas |
|
||||
| AutoGen | https://github.com/microsoft/autogen | Multi-agent runtime ideas |
|
||||
| Letta | https://github.com/letta-ai/letta | Memory / context window ideas |
|
||||
| OpenHands | https://github.com/All-Hands-AI/OpenHands | Event stream / observability ideas |
|
||||
| CopilotKit | https://github.com/CopilotKit/CopilotKit | Agent–UI connection ideas |
|
||||
|
||||
When a dependency is added to `package.json`, record its license here (or under `licenses/`) and keep versions pinned via the lockfile.
|
||||
|
||||
## Direct dependencies
|
||||
|
||||
See `package.json` / lockfile for runtime and dev dependencies and their respective licenses.
|
||||
@@ -1,150 +1,301 @@
|
||||
# 整体架构
|
||||
# 本地多 Agent 文本创作系统架构
|
||||
|
||||
## 1. 方向
|
||||
## 1. 文档定位
|
||||
|
||||
**标签驱动黑板** + **agent tool loop** + **5 相位阶段机** + **skill 能力库**。
|
||||
本文档描述**系统级运行内核**:模块边界、创作定义与运行实例如何分离、总管 / Worker / Skill / 上下文如何协作、数据如何持久化,以及外部项目按层借鉴的原则。
|
||||
|
||||
```text
|
||||
Skill 包(orchestrator manifest + workers/*/SKILL.md) 静态能力定义
|
||||
Session + 黑板 tag 一次运行的实参
|
||||
Book 跨 Session 的项目与资产
|
||||
|
||||
Agent(总管) running 相位内 tool loop,调度 invoke 哪个 skill
|
||||
Runtime 拼接上下文、校验 tool、写黑板、驱动阶段机边界
|
||||
阶段机 谁可动、何时等用户、产物生命周期——不是流水线剧本
|
||||
```
|
||||
|
||||
核心文档:
|
||||
|
||||
```text
|
||||
docs/tag-blackboard.md 黑板、标签、分工
|
||||
docs/context-assembly.md ★ 上下文拼接(上半固定、下半动态)
|
||||
docs/tool-contracts.md 总管 / worker tool
|
||||
docs/runtime-state-machine.md 5 相位、tool 边界
|
||||
docs/book-storage.md 长期存储(过程 / 资产 / 游玩)
|
||||
docs/skill-design-guide.md 新 skill 包设计方法
|
||||
docs/implementation-guide.md 代码文件职责
|
||||
```
|
||||
本文档**不**设计具体剧本 Skill 的提示词、步骤或字段。剧本与创作方法见独立文档(如 `design-orchestrator-guide.md`);Skill 包格式见 `skill-format.md` 系列。
|
||||
|
||||
---
|
||||
|
||||
## 2. 术语
|
||||
## 2. 产品目标
|
||||
|
||||
本地运行的多 Agent 文本生成平台,兼容多种创作模式(角色扮演、长短篇小说、仿写扩写、思想实验、信息隔离模拟、世界观/角色卡/模板创作、动态表与长期状态、存档分支等)。
|
||||
|
||||
这些模式共享同一运行内核,区别来自:
|
||||
|
||||
- 装载哪些 Skill / 能力包
|
||||
- 实例规格(Worker 声明)如何配置
|
||||
- 上下文如何按契约编译
|
||||
- 对状态读写实施什么权限
|
||||
|
||||
不为每一种创作模式各实现一套固定工作流。
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心架构原则
|
||||
|
||||
### 3.1 创作定义与运行实例分离
|
||||
|
||||
| 层 | 当前实现对应 | 含义 |
|
||||
|----|--------------|------|
|
||||
| **创作定义 / 规格** | accept 后的 `设计.worker集`、静态设定 tag;可存为 `instance` / `opening` 快照 | 可复用的「函数声明」:Worker、表、常驻上下文、权限与能力边界 |
|
||||
| **运行实例** | Book + Session + 黑板 + `run` 快照 | 一次具体创作/游玩过程:对话、变量、轮次、中间产物 |
|
||||
|
||||
同一规格可多次开档;实例应**钉住**某次规格截面。规格更新后,已有实例不自动跟随;需显式加载/迁移到新截面。
|
||||
|
||||
语义上:`instance` 快照 ≈ 已发布的规格版本;`run` 快照 ≈ 运行过程存档。正式 `DefinitionVersion` 发布与迁移协议在现有快照之上增量补齐,不另起一套推倒重写。
|
||||
|
||||
### 3.2 总管只负责任务调配
|
||||
|
||||
总管 Agent(Main Agent)负责:判断目标是否完成、决定下一项任务、选择 Worker、必要时询问用户或结束。
|
||||
|
||||
总管**不**负责:直接创作最终正文、临场拼接完整上下文、绕过权限读私有信息、直接改正式状态、同时兼创作与评测、把隐藏推理写入长期状态。
|
||||
|
||||
任务所需信息由总管**声明意图**(如 invoke 哪个 worker);真正发给 Worker 的上下文由 Runtime 的上下文编译器按 Skill / 声明契约生成。
|
||||
|
||||
### 3.3 Agent 决策与程序控制分离
|
||||
|
||||
| Agent(非确定) | Runtime(确定) |
|
||||
|-----------------|-----------------|
|
||||
| 下一步做什么 | 权限与声明校验 |
|
||||
| 调用哪个能力 | 上下文装载与 Token 裁剪 |
|
||||
| 是否返工 / 问用户 | 状态提交、表 `rev` 合并 |
|
||||
| | 相位边界、burst、失败处理 |
|
||||
|
||||
不能依赖提示词保证权限与信息隔离;访问限制在 Tool 与 Context Compiler 层执行。
|
||||
|
||||
### 3.4 动态调度,不用固定创作 DAG
|
||||
|
||||
「下一步 invoke 哪个 skill」由 agent tool loop 决定。
|
||||
|
||||
**5 相位阶段机**(`idle` / `running` / `waiting_user` / `done` / `error`)是并发与权限模型——谁可动、何时等用户、产物何时算事实——**不是**流水线剧本。详见 `runtime-state-machine.md`、`tool-contracts.md`。
|
||||
|
||||
确定性后台流程(导入导出、索引、迁移、批量评测)可用固定步骤;整场 AIRP / 小说创作流程不固化为完整 DAG。
|
||||
|
||||
---
|
||||
|
||||
## 4. 总体架构
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Web / Electron Frontend │
|
||||
│ 创作 / 游玩 / 存档 / 检查器 / 调试 │
|
||||
└─────────────────────┬───────────────────────┘
|
||||
│ HTTP(+ 流式事件)
|
||||
┌─────────────────────▼───────────────────────┐
|
||||
│ Application API │
|
||||
│ Book、Session、快照、资源、模型配置 │
|
||||
└─────────────────────┬───────────────────────┘
|
||||
│
|
||||
┌─────────────────────▼───────────────────────┐
|
||||
│ Agent Runtime │
|
||||
│ Main Agent(tool loop) │
|
||||
│ Phase Machine + Phase Runtime │
|
||||
│ Skill Registry / Worker 声明校验 │
|
||||
│ Context Compiler(assembleWorkerContext) │
|
||||
│ Commit:黑板写入、表 rev、acceptance │
|
||||
└─────────────────────┬───────────────────────┘
|
||||
│
|
||||
┌─────────────────────▼───────────────────────┐
|
||||
│ Domain / Storage │
|
||||
│ Book · Session · Blackboard · Snapshots │
|
||||
│ Artifacts · RuntimeEvent history │
|
||||
│ 文件资源(books/…);向量检索可插拔 │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
合成边界:
|
||||
|
||||
> Book/规格描述可以运行什么,Session/快照保存实际发生了什么,相位机决定谁可动,总管决定下一步 invoke 谁,Context Compiler 决定 Worker 看见什么,Runtime 决定哪些修改正式生效。剧本 Skill 是可装载能力,不是系统固定流水线。
|
||||
|
||||
---
|
||||
|
||||
## 5. 系统核心模块
|
||||
|
||||
### 5.1 规格与 Book(Definition 侧)
|
||||
|
||||
- Book:长期项目容器(过程、资产、游玩状态)
|
||||
- `设计.worker集` accept 后 → 本实例 Worker 声明
|
||||
- `instance` / `opening` 快照:规格与可选开局截面
|
||||
- 导入导出、复制、从截面新开 play 线
|
||||
|
||||
细节:`book-storage.md`、`run-snapshot.md`。
|
||||
|
||||
### 5.2 Session 与 Instance Manager(运行侧)
|
||||
|
||||
- 按规格创建 / 加载 Session
|
||||
- 自动续作 `session.json` 与手动 `run-snapshots/`
|
||||
- 消息 swipe / branch checkpoint
|
||||
- 从 earlier 快照恢复后重 roll
|
||||
|
||||
### 5.3 Main Agent(Supervisor)
|
||||
|
||||
接收压缩后的任务视图(目标、相位、可调用能力、最近产物摘要等),经 tool loop 输出调度意图;Runtime 校验后执行。
|
||||
|
||||
### 5.4 Phase Runtime(Dynamic Scheduler 的具体形态)
|
||||
|
||||
执行总管决定并维护相位边界:
|
||||
|
||||
1. 校验 Worker id ∈ 声明
|
||||
2. 编译上下文
|
||||
3. 短生命周期执行 Worker
|
||||
4. 收集写入请求 → 校验 / 合并 / 提交
|
||||
5. 按 acceptance 可能进入 `waiting_user`
|
||||
6. 预算:`maxBurst`;后续补齐嵌套深度、重复任务检测、Token/调用预算
|
||||
|
||||
实现:`src/runtime/phase-machine.ts`、`phase-runtime.ts`。
|
||||
|
||||
### 5.5 Worker
|
||||
|
||||
按任务实例化的短生命周期调用。保留的是事件、产物与状态修改,不是 Agent 对象。
|
||||
|
||||
### 5.6 Skill Registry
|
||||
|
||||
架构层两种能力层级:
|
||||
|
||||
- **包 / Orchestrator manifest**:能力发现、验收与进 play 门槛
|
||||
- **Worker 契约**:声明驱动的输入输出、上下文段、工具边界
|
||||
|
||||
注册、加载、版本与磁盘格式见 Skill 系列文档;**具体剧本步骤不在本文**。
|
||||
|
||||
### 5.7 Context Compiler
|
||||
|
||||
确定性模块:按任务、Worker 权限与实例状态构建 Context View。
|
||||
|
||||
当前实现:`inputTags` / `contextSegments` + `assembleWorkerContext()`(`context-assembly.md`)。
|
||||
|
||||
演进契约:
|
||||
|
||||
- **Context Trace**:记录装载了什么、来源、权限依据、因预算/权限排除了什么
|
||||
- 逻辑分区语义(public / narrative / characters / tables / artifacts / runtime)可映射到 tag 与可见范围;实现仍以 tag 黑板为主
|
||||
|
||||
### 5.8 Blackboard
|
||||
|
||||
带类型与来源的运行状态(tag 条目),不是全体 Agent 共享的大字符串。表字段使用 `value + rev + source`;正式写入经 Runtime。见 `tag-blackboard.md`。
|
||||
|
||||
### 5.9 Artifact 与验收
|
||||
|
||||
较长或需反复编辑的内容以黑板 tag / 产物记录保存;`drafted → under_review → accepted|rejected|revision_requested`;accepted 前不得当下游事实。
|
||||
|
||||
### 5.10 Commit and Validation
|
||||
|
||||
Worker 不直接改「当前事实」。路径:输出 → 声明/schema 校验 → 权限与 rev 合并 → 提交事件 → 更新黑板。结构化状态用受控写入;正文类产物仍记录来源与版本。
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据持久化
|
||||
|
||||
当前以**状态快照为主**,会话内保留 `RuntimeEvent` history;完整领域事件溯源(投影 + 事件日志 + 周期快照)按分支/迁移/审计痛点再加深。
|
||||
|
||||
| 需求 | 机制 |
|
||||
|------|------|
|
||||
| 续作 | `session.json` |
|
||||
| 规格截面 | `instance` / `opening` 快照 |
|
||||
| 游玩存档 / 重 roll | `run` 快照;load = 恢复到该档 |
|
||||
| 同开局多线 | 保留 opening/instance 层,清空或分支 run 层 |
|
||||
| 调试回放 | `RuntimeSession.history` |
|
||||
|
||||
分支不要求复制整库;记录父档 / 基准快照即可。
|
||||
|
||||
---
|
||||
|
||||
## 7. 信息隔离
|
||||
|
||||
隔离由 Runtime 强制:Worker 只看到契约允许的 tag / 段。多角色模拟时,角色 Worker 不得看到他角私有记忆、未公开事件、总管隐藏调度信息。
|
||||
|
||||
Context Trace 是排查泄漏的主工具(契约已定,实现渐进)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 技术栈(现状与边界)
|
||||
|
||||
| 层 | 现状 | 边界 |
|
||||
|----|------|------|
|
||||
| 语言 | TypeScript / Node.js | — |
|
||||
| Agent Runtime | 自研(main-agent、phase-runtime、llm) | 领域对象不绑定外部 Agent 框架内部类型;Mastra 等仅可作按层参考或远期适配 |
|
||||
| 前端 | `web/` + Electron | 不强制 Next.js / assistant-ui;可借鉴交互模式 |
|
||||
| 存储 | 文件系统 Book JSON | SQLite/Drizzle 非近端前提 |
|
||||
| Skill | `skills/` + registry | 包内剧本与系统内核分离 |
|
||||
|
||||
外部仓库按模块借鉴,见 `references.md`;许可证见仓库根 `THIRD_PARTY_NOTICES.md`。
|
||||
|
||||
---
|
||||
|
||||
## 9. 术语对照
|
||||
|
||||
| 规划用语(抽象) | 本仓库用语(实现) |
|
||||
|------------------|-------------------|
|
||||
| CreationDefinition / DefinitionVersion | `设计.worker集` 截面;`instance` / `opening` 快照 |
|
||||
| RunInstance | Book + Session + 黑板 + `run` 快照 |
|
||||
| 导演 Skill / 能力包 | `orchestrator.md` 包(UI 可选;registry 一项) |
|
||||
| 剧本层(动态) | 对话谈成的 Worker 集 + play 期 tool loop 调度(非死板选单) |
|
||||
| Supervisor | Main Agent(tool loop) |
|
||||
| Dynamic Scheduler | Phase Machine + Phase Runtime |
|
||||
| Worker Factory | `run_worker` → 声明校验 → executor |
|
||||
| Context Compiler | `assembleWorkerContext` / contextSegments |
|
||||
| Blackboard | tag 黑板(`BlackboardItem`) |
|
||||
| Commit Layer | 写入校验、表 rev 合并、acceptance |
|
||||
| Skill Registry | `skills/` loader + orchestrator manifest |
|
||||
|
||||
用户可见中文口径(禁止裸露内部 id)见 `ui-glossary.md`。
|
||||
|
||||
| 词 | 含义 |
|
||||
|----|------|
|
||||
| **skill** | `SKILL.md` 定义的能力(设计期能力库中的一条) |
|
||||
| **worker** | 某 skill 被 invoke 的一次执行 |
|
||||
| **stage** | 业务阶段:`design`(实例化)→ `play`(运行)→ `done` |
|
||||
| **phase** | 运行相位:`idle` \| `running` \| `waiting_user` \| `done` \| `error` |
|
||||
|
||||
不使用 **step** 指代设计步骤编号,避免与管道混淆。
|
||||
| **Worker 声明** | accept 后的 `设计.worker集` |
|
||||
| **worker** | 一次 `run_worker` invoke |
|
||||
| **stage** | `design` → `play` → `done`(创作 → 游玩 → 已完成) |
|
||||
| **phase** | `idle` \| `running` \| `waiting_user` \| `done` \| `error` |
|
||||
|
||||
---
|
||||
|
||||
## 3. Agent tool loop
|
||||
## 10. 文档地图
|
||||
|
||||
```text
|
||||
用户硬事件(输入 / 确认 / 验收)
|
||||
→ phase = running,toolLoopBurst 计数归零
|
||||
→ while running && burst < N:
|
||||
LLM(messages, tools)
|
||||
→ 循环 tool(read_blackboard …)→ 结果 append 到 messages
|
||||
→ 边界 tool(run_worker / ask_user / finish)→ 退出 burst
|
||||
→ 若需用户 → waiting_user
|
||||
```
|
||||
|
||||
- **burst 上限 N**:两次用户操作之间的最大推理轮数,非 Session 终身额度。
|
||||
- **循环 tool**:不改 phase,只追加 agent 对话。
|
||||
- **边界 tool**:触发阶段机转移(等用户、跑 worker、结束)。
|
||||
|
||||
代码:`src/main-agent/tool-loop.ts`、`src/runtime/phase-runtime.ts`。
|
||||
|
||||
目标态:worker 执行也用 tool loop(`submit` / `ask_user`),上下文仍由 Runtime 拼接,见 `docs/tool-contracts.md`。
|
||||
| 层级 | 文档 |
|
||||
|------|------|
|
||||
| **系统架构(本文)** | `architecture.md` |
|
||||
| 运行内核 | `runtime-state-machine.md`、`tool-contracts.md`、`context-assembly.md`、`tag-blackboard.md` |
|
||||
| 持久化 | `book-storage.md`、`run-snapshot.md` |
|
||||
| UI | `ui-design.md`、`ui-glossary.md` |
|
||||
| 实现顺序 | `implementation-guide.md` |
|
||||
| **产品体验路线** | **`px-roadmap.md`**(PX0–PX5 交付与 DoD) |
|
||||
| **日常场景 → P0** | **`daily-use-p0.md`**(场景、功能表、P0 工作包) |
|
||||
| 外部参考 | `references.md` |
|
||||
| **剧本 / 创作方法(非系统架构)** | `design-orchestrator-guide.md`、`creation-playbook.md` |
|
||||
| **Skill 包格式(非系统架构)** | `skill-format.md`、`orchestrator-skill-format.md`、`worker-skill-format.md`、`skill-design-guide.md`、`preset-format.md` |
|
||||
|
||||
---
|
||||
|
||||
## 4. 阶段机做什么
|
||||
## 11. 在现有内核上补齐(路线)
|
||||
|
||||
阶段机 **不是** 编排表执行器,而是:
|
||||
已具备:相位机、Agent tool loop、Skill loader、design-intake、Worker 声明、上下文拼装、表 rev、Web UI、快照 API。
|
||||
|
||||
```text
|
||||
1. Actor 门禁 此刻用户 / agent / worker 谁在场
|
||||
2. Tool 边界 当前态允许哪些 tool
|
||||
3. 事实生命周期 draft → accepted;用户才能 accept
|
||||
4. 等待原因 waitingReason 细分等什么
|
||||
```
|
||||
1. **PX0**:导演选择 UI;AIRP + 长文/爽文主路径;**存档/续作硬稳定**;剧本层保持动态
|
||||
2. **PX1–PX2**:创作/游玩体验(可抄 UI);副作用与多 run 线
|
||||
3. **PX3**:Context Trace、调度预算、规格钉版本、E2E
|
||||
4. **PX4–PX5**:长文加深;隔离模拟 + 调试(新方法边界可用新导演包)
|
||||
5. SQLite / 框架适配仅当痛点单开,不插入 PX0 关键路径
|
||||
|
||||
业务「先跑哪个 skill」由 **agent 在 tool loop 里决定**,不由 orchestrator 逐步剧本写死。
|
||||
权威表:[`px-roadmap.md`](./px-roadmap.md)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 实例化:skill 能力库
|
||||
## 12. 主要技术风险
|
||||
|
||||
实例化 = agent 在 `design` stage 按需 invoke **instantiate skill**(原「设计步骤」),不是固定 1→14 管道。
|
||||
|
||||
```text
|
||||
交互范式 skill → 产出 设计.run_skill清单(run 阶段需要哪些 skill)
|
||||
每个 run skill 倒推 → 缺什么 instantiate skill → agent invoke
|
||||
declare_instance_ready → 进入 play stage
|
||||
```
|
||||
|
||||
orchestrator.md = **manifest**(有哪些 skill、约束、验收策略),不是逐步思维链。
|
||||
| 风险 | 解决边界 |
|
||||
|------|----------|
|
||||
| 上下文污染 | 全部经 Context Compiler;补 Trace |
|
||||
| 自然语言误改状态 | 结构化状态只走受校验写入 / Patch |
|
||||
| 调度死循环 | burst、后续嵌套/重复/预算限制 |
|
||||
| 规格更新污染旧档 | 实例钉规格截面;显式迁移 |
|
||||
| 框架耦合 | 领域与持久化不保存外部 Agent 框架内部对象 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 上下文拼接
|
||||
|
||||
**上半固定、下半动态**。规则在 skill 定义 + 实例 contextProfile;Runtime 执行。
|
||||
见 `docs/context-assembly.md`。
|
||||
|
||||
---
|
||||
|
||||
## 7. 模块
|
||||
|
||||
```text
|
||||
phase-machine.ts 纯函数:event → phase(边界规则)
|
||||
phase-runtime.ts Session IO、tool loop 驱动、worker 执行
|
||||
main-agent/ tool loop、tool 定义
|
||||
worker/executor.ts assembleWorkerContext、run skill
|
||||
blackboard.ts tag 池
|
||||
skills/loader.ts 解析 orchestrator + SKILL.md
|
||||
book/ Book、快照、持久化
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 数据流(目标态)
|
||||
|
||||
```text
|
||||
选 orchestrator 包
|
||||
→ design:agent burst → invoke instantiate skills → 设计.* tag
|
||||
→ declare ready → play
|
||||
→ play:用户输入 → agent burst → invoke run skills → 运行.* tag
|
||||
→ 过程/资产/游玩 归档 Book(见 book-storage.md)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 边界
|
||||
|
||||
```text
|
||||
Agent 调度 skill;read_blackboard;不传 inputTags;不写黑板(边界 tool 除外)
|
||||
Runtime 拼接上下文;校验 tool;写黑板;执行 worker
|
||||
Worker LLM 在拼接后的 prompt 内产出;submit 写 declared outputTags
|
||||
用户 approve、accept、输入
|
||||
阶段机 只响应 event,不调 LLM
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 实现进度(摘要)
|
||||
## 13. 实现进度(摘要)
|
||||
|
||||
```text
|
||||
✅ phase-machine、phase-runtime、skills loader
|
||||
✅ 总管 tool loop(read_blackboard、run_worker、…)
|
||||
⬜ toolLoopBurst 按用户事件归零
|
||||
⬜ assembleWorkerContext(contextSegments)
|
||||
⬜ worker tool loop
|
||||
⬜ Book:designTrace / CardAsset / PlayBook
|
||||
⬜ orchestrator 从编排表迁为 manifest
|
||||
✅ design-intake + Worker 声明校验
|
||||
✅ 声明驱动 worker 执行 + acceptance
|
||||
✅ 表字段格 rev 合并
|
||||
✅ Book session / run-snapshots / message branch
|
||||
✅ 导演选择 UI(新建作品);instance/run 存档人话 kind
|
||||
✅ 长文模板 outline / chapter-writer;引导覆盖爽文/分段
|
||||
✅ 失败/revision/error 出口;黑板用户手改 API
|
||||
⬜ Context Trace
|
||||
⬜ 规格版本发布与迁移协议
|
||||
⬜ 调度嵌套 / 重复任务 / Token 预算补齐
|
||||
⬜ 边沿副作用完整调度器
|
||||
⬜ 标准 fixture E2E(双线黄金路径自动化)
|
||||
```
|
||||
|
||||
435
docs/briefs/capability-authoring-brief.md
Normal file
435
docs/briefs/capability-authoring-brief.md
Normal file
@@ -0,0 +1,435 @@
|
||||
# 能力撰写交接简报(泛用)
|
||||
|
||||
> **用途**:把本文**整份**交给另一 AI / 作者,用于撰写任意【能力】的 `modules/{id}/prompt.md`。
|
||||
> **本文是自洽规范**:不依赖读者已熟悉本仓库。
|
||||
> **不要**让撰写方改程序代码;**不要**只写某一能力的特例而不遵守通用格式。
|
||||
|
||||
范例(深度与结构对齐):`skills/dialogue/world-simulator/modules/aesthetics-interaction/prompt.md`
|
||||
能力池清单(选题用):`docs/world-simulator-modules.md`
|
||||
术语权威:`docs/ui-glossary.md` §0
|
||||
|
||||
---
|
||||
|
||||
# 第一部分:项目是什么
|
||||
|
||||
## 1.1 一句话
|
||||
|
||||
**writing-agent** 是本地运行的多 Agent **文本创作 / 游玩**系统:用户选一个【导演】,在【创作】期谈成【剧本】,再在【游玩】期让【演员】按剧本上场,产出可读文本(角色扮演、长文/爽文、扩写、思想实验等共用同一内核)。
|
||||
|
||||
## 1.2 要解决什么问题
|
||||
|
||||
| 要 | 不要 |
|
||||
|----|------|
|
||||
| 用程序控制「每一步注入什么上下文」 | 把整本长说明书一次塞进 LLM |
|
||||
| 用 Agent 按用户体验**编排**用哪些能力、什么顺序 | 死板选单 DAG,或全程让 LLM 自由发明工序 |
|
||||
| 产物可解析、可渲染、对人可读 | 产物里堆满英文变量名让用户难受 |
|
||||
| 规格(能复用的声明)与一局游玩过程分离 | 每种玩法重写一套运行时 |
|
||||
|
||||
## 1.3 两段生命周期
|
||||
|
||||
```text
|
||||
【创作 design】选导演 → 编排剧本(步骤=能力)→ 逐步执行能力 → 谈成可运行规格
|
||||
│
|
||||
▼ 用户手动进入
|
||||
【游玩 play】用户输入 → 演员按规格上场 → 可见终稿;可存档 / 重 roll
|
||||
```
|
||||
|
||||
- **创作**:谈清「这局要什么体验、用哪些能力钉成什么产物」。
|
||||
- **游玩**:按已验收规格跑;用户新输入通常视为认可上一轮展示(否则重 roll)。
|
||||
|
||||
## 1.4 运行时怎么分工(撰写能力时必须懂)
|
||||
|
||||
| 角色 | 做什么 | 不做什么 |
|
||||
|------|--------|----------|
|
||||
| **导演(Agent)** | 决定下一步调哪个演员;编排剧本步骤 | 不直接写小说正文;不私自改相位 |
|
||||
| **程序(Runtime)** | 校验权限;按契约拼上下文;切步;发 opening;验收门控 | 不替用户发明体验 |
|
||||
| **能力(modules)** | 某一步「怎么和用户谈、产出什么形状」的方法正文 | 不是整场流水线 |
|
||||
| **演员(Worker)** | 一次 LLM 执行(创作期跑能力,或游玩期跑声明内 ref) | 不决定全局调度 |
|
||||
|
||||
关键路径:
|
||||
|
||||
```text
|
||||
用户选【导演】(如世界模拟器)
|
||||
→ design-flow:从【能力】排出**近期**步骤,写出 设计.创作流程
|
||||
(可变增量 DAG:每步 id + 固定中文名 + depends_on;status=open|closed)
|
||||
→ 用户验收近期流程
|
||||
→ 反复 design-step:程序注入「当前能力」的 prompt 切片 + 依赖产物
|
||||
→ 步骤做完仍 open → 再 design-flow(可追加同能力多次,如生成规则 / 具体实例)
|
||||
→ closed 后收成可进游玩的规格,如 设计.worker集
|
||||
→ 用户手动进游玩
|
||||
```
|
||||
|
||||
## 1.5 磁盘上两层内容(作者视角)
|
||||
|
||||
```text
|
||||
【导演】recipes/{导演id}/recipe.yaml 该方法的建议近期起点 / 调味说明
|
||||
【能力】modules/{能力id}/prompt.md 共用工序;多导演可选用同一能力
|
||||
modules/catalog.yaml 能力目录:中文名 + 短声明 + 产物 tag(+ 可选 repeatable)
|
||||
```
|
||||
|
||||
- 新建作品**只选导演一层**,不再叠「能力包 + 配方」。
|
||||
- 导演给出的步骤是**近期起点**;本局可增量追加、可反复调用标了 `repeatable` 的能力;验收的是本局【剧本】,不是磁盘死菜谱。
|
||||
|
||||
---
|
||||
|
||||
# 第二部分:称呼定义(必须统一)
|
||||
|
||||
对用户与能力正文,优先用**拍摄隐喻**。括号内为内部/旧称,撰写时勿当用户主词。
|
||||
|
||||
## 2.1 四个核心词
|
||||
|
||||
| 称呼 | 含义 | 内部大致对应 | 禁止对用户说 |
|
||||
|------|------|--------------|--------------|
|
||||
| **导演** | ① 新建时选一次的方法起点(世界模拟器、扩写助手…);② 会话里负责调度的 Agent | `recipes/`、Main Agent / orchestrator | 总管、配方(作选型主词)、能力包(作第二层选项) |
|
||||
| **剧本** | 本局谈成的流程与规格(活的) | `设计.创作流程` + 各能力产物 + 终稿 `设计.worker集` 等 | 「剧本 Skill 菜单」、死板流程卡 |
|
||||
| **演员** | 上场执行的一次单元 | Worker / `run_worker` | 对用户堆 `Worker · english-id` |
|
||||
| **能力** | 共用工序模块;导演从池里选型编排 | `modules/{id}/` | 组件池、模块(作者文档可用;用户文案用「能力」) |
|
||||
|
||||
关系口诀:
|
||||
|
||||
```text
|
||||
选导演 → 用能力编排 → 谈成剧本 → 演员按剧本上场
|
||||
```
|
||||
|
||||
## 2.2 阶段与界面
|
||||
|
||||
| 称呼 | 含义 |
|
||||
|------|------|
|
||||
| **创作** | lifecycle `design`:谈剧本 |
|
||||
| **游玩** | lifecycle `play`:演员上场 |
|
||||
| **产物** | 某步待验收输出(写入黑板 tag) |
|
||||
| **验收 / 接受** | 用户确认产物算数 |
|
||||
| **黑板** | 按 tag 存正文的上下文板 |
|
||||
| **询问卡** | 结构化提问(选项可改写) |
|
||||
|
||||
## 2.3 创作调度相关(可出现在作者文档,少直接甩给用户)
|
||||
|
||||
| 内部 id | 用户可见 | 含义 |
|
||||
|---------|----------|------|
|
||||
| `design-flow` | 创作 · 流程编排 | 编排/增量修订可变 DAG → `设计.创作流程` |
|
||||
| `design-step` | 创作 · 执行步骤 | 执行剧本中当前那一个能力 |
|
||||
| `opening-generator` | 开局 · 开场白 | 可选;规格收成后的开场白 |
|
||||
|
||||
## 2.4 剧本流程 JSON(编排产物:增量 DAG)
|
||||
|
||||
```json
|
||||
{
|
||||
"brief": "可选:一句话体验复述",
|
||||
"status": "open",
|
||||
"steps": [
|
||||
{ "id": "美学纲领与交互范式", "name": "美学纲领与交互范式", "depends_on": [] },
|
||||
{ "id": "生成规则", "name": "生成规则", "depends_on": ["美学纲领与交互范式"] },
|
||||
{ "id": "生成规则#2", "name": "生成规则", "depends_on": ["生成规则"] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 规则 |
|
||||
|------|------|
|
||||
| `status` | `open` = 还可追加;`closed` = 不再扩步。缺省兼容旧稿视为已收口 |
|
||||
| `steps` 顺序 | 建议执行顺序 |
|
||||
| `id` | 本局步骤唯一键;同能力多次必须不同 |
|
||||
| `name` | 必须与能力目录中的**固定中文名**完全一致(可重复) |
|
||||
| `depends_on` | 依赖的其它步骤 **id**(若某 name 在本流程唯一,也可写 name) |
|
||||
|
||||
程序用中文 `name` 映射到 `artifact` tag;流程 JSON **不写**英文模块 id / tag。
|
||||
**禁止**一次排死全程固定长链;「生成规则」「具体实例」等标了 `repeatable` 的能力应允许多次编入。
|
||||
|
||||
## 2.5 已合并能力(勿拆回)
|
||||
|
||||
| 错误拆法 | 正确能力 |
|
||||
|----------|----------|
|
||||
| 「交互范式」+「美学纲领」两步 | **美学纲领与交互范式**(`aesthetics-interaction`)一步 |
|
||||
|
||||
询问相近则合并为一步;不要在新能力里建议再拆开。
|
||||
|
||||
## 2.6 命名纪律
|
||||
|
||||
| 种类 | 规则 | 例 |
|
||||
|------|------|-----|
|
||||
| 能力中文名 | 稳定、给人看、进流程 JSON | `实现机制` |
|
||||
| 能力 id | 英文 kebab,= 文件夹名 | `mechanism` |
|
||||
| 产物 tag | 通常 `设计.{中文名或约定名}` | `设计.实现机制` |
|
||||
| 游玩演员 ref | 英文 kebab | `narrator` |
|
||||
| 游玩演员展示名 | 中文 `name` | `叙事转述` |
|
||||
| UI | 禁止裸露内部英文 id | 不要写 `design-step` 给用户 |
|
||||
|
||||
---
|
||||
|
||||
# 第三部分:什么是「能力」(泛用定义)
|
||||
|
||||
## 3.1 定义
|
||||
|
||||
**能力** = 创作期可被编排进剧本的**一步工序**:
|
||||
|
||||
1. 有固定中文名与产物 tag;
|
||||
2. 有一份程序可切割的方法文档(`prompt.md`);
|
||||
3. 执行时只注入**本能力**方法 + **依赖产物** + 用户表述;
|
||||
4. 与用户对话后产出**可验收**的结构化结果。
|
||||
|
||||
能力**不是**:整场游戏引擎、游玩期每轮演员、也不是导演本身。
|
||||
|
||||
## 3.2 能力在系统中的生命周期
|
||||
|
||||
```text
|
||||
catalog 登记(短声明给编排看;可选 repeatable)
|
||||
→ 导演/编排增量选入 设计.创作流程(可同能力多次)
|
||||
→ 用户验收流程(或后续扩步后再验)
|
||||
→ 轮到该步:若有 opening → 程序先发出
|
||||
→ 用户首答 → LLM 按能力方法谈/写
|
||||
→ 写入 artifact tag → 用户验收
|
||||
→ 下游能力可读该产物(若 depends_on 声明了)
|
||||
→ 不够则再 design-flow 追加同能力新 id
|
||||
```
|
||||
## 3.3 能力作者要交付的两样东西
|
||||
|
||||
| 交付物 | 路径 | 给谁看 |
|
||||
|--------|------|--------|
|
||||
| 目录行 | `modules/catalog.yaml` 一行 | 编排:只看短 `declaration` |
|
||||
| 方法全文 | `modules/{id}/prompt.md` | 执行该步的 LLM + 程序切割 |
|
||||
|
||||
编排时**禁止**把全文 prompt 塞进导演上下文;执行时才注入切割后的方法块。
|
||||
|
||||
## 3.4 能力之间如何相处
|
||||
|
||||
- **正推**:需要 [体验/产物] → 才纳入某能力;勿默认全选池内能力。
|
||||
- **边界**:每个能力在 `meta.boundary` 写清「本步定什么 / 不定什么 / 交给谁」。
|
||||
- **依赖**:由剧本 `depends_on` 声明;执行期程序注入对应产物。
|
||||
- **合并**:询问主题高度重叠 → 合成一个能力(如美学+交互)。
|
||||
- **缩减**:每增必问能否删、能否常驻替代、能否合并。
|
||||
|
||||
## 3.5 方法层硬禁止(写任何能力都适用)
|
||||
|
||||
- 题材 → 固定演员清单。
|
||||
- 否定式路由(「因为是 X 所以不需要 Y」);应写「需要 A 体验 → 用 B 手段」。
|
||||
- 把「世界模拟」当成一切开场的默认答案。
|
||||
- 为排版/格式单独发明演员;能程序拼则程序拼。
|
||||
- 临时发明 tool。
|
||||
- 一次 burst 写齐全部能力产物。
|
||||
- 在能力产物里堆大量用户看不懂的英文变量名(机器 id 可有,但须有中文展示字段)。
|
||||
|
||||
---
|
||||
|
||||
# 第四部分:能力文档格式规范(程序契约)
|
||||
|
||||
每个能力 = `modules/{id}/prompt.md` + `catalog.yaml` 一行。
|
||||
|
||||
程序**只认 fence 的语言标签**切割,**不认**仅靠 `##` 标题。
|
||||
|
||||
## 4.1 块一览
|
||||
|
||||
| fence 标签 | 必填 | 用途 |
|
||||
|------------|------|------|
|
||||
| `meta` | 强烈建议 | YAML 元数据:选型与边界 |
|
||||
| `opening` | 可选 | **默认问题**;程序发给用户,**不经**本步 LLM |
|
||||
| `task` | **必填** | 本步任务与验收边界 |
|
||||
| `principles` | 强烈建议 | 原则 |
|
||||
| `probe` | 强烈建议 | 追问策略 |
|
||||
| `output` | **必填** | 产物形状(多为 JSON) |
|
||||
| `checklist` | 强烈建议 | 自检 |
|
||||
| `examples` | 可选 | 好/坏对照 |
|
||||
|
||||
未列出的 fence 名:**不要发明**(除非先改程序切割器)。
|
||||
|
||||
## 4.2 标准骨架
|
||||
|
||||
````markdown
|
||||
# {能力中文名}
|
||||
|
||||
> 可选说明。
|
||||
|
||||
## meta
|
||||
|
||||
```meta
|
||||
name: {与 catalog 完全一致的中文名}
|
||||
id: {文件夹 id}
|
||||
artifact: 设计.{…}
|
||||
declaration: >
|
||||
{一行选型话术;与 catalog 对齐}
|
||||
when: |
|
||||
{什么情况下应被编排进来}
|
||||
when_not: |
|
||||
{什么情况下不要进来}
|
||||
boundary: |
|
||||
本能力:…
|
||||
邻接能力:…(点名其它能力中文名,说明分工)
|
||||
```
|
||||
|
||||
## opening
|
||||
|
||||
```opening
|
||||
{用户第一眼看到的引导;整块可省略或留空 = 本步直接 LLM}
|
||||
```
|
||||
|
||||
## task
|
||||
|
||||
```task
|
||||
{只做本步;写清产物 tag;如何对待 opening 首答与依赖产物}
|
||||
```
|
||||
|
||||
## principles
|
||||
|
||||
```principles
|
||||
{正推、缩减、禁止项、与体验的关系}
|
||||
```
|
||||
|
||||
## probe
|
||||
|
||||
```probe
|
||||
{缺什么问什么;一轮 1~2 点;优先具体选项/短场景;勿重复已答清}
|
||||
```
|
||||
|
||||
## output
|
||||
|
||||
```output
|
||||
{推荐产物 JSON 形状:中文键、可渲染}
|
||||
```
|
||||
|
||||
## checklist
|
||||
|
||||
```checklist
|
||||
- [ ] …
|
||||
```
|
||||
|
||||
## examples
|
||||
|
||||
```examples
|
||||
{可选}
|
||||
```
|
||||
````
|
||||
|
||||
## 4.3 `meta` 字段
|
||||
|
||||
| 字段 | 含义 |
|
||||
|------|------|
|
||||
| `name` | 固定中文名(= 流程 `steps[].name`) |
|
||||
| `id` | 英文 id(= 目录名) |
|
||||
| `artifact` | 本步写入的黑板 tag |
|
||||
| `declaration` | 给编排看的短声明(不是全文) |
|
||||
| `when` / `when_not` | 何时该/不该入选剧本 |
|
||||
| `boundary` | 与邻接能力的分工 |
|
||||
|
||||
## 4.4 `catalog.yaml` 一行
|
||||
|
||||
```yaml
|
||||
- id: example-id
|
||||
name: 示例能力中文名
|
||||
declaration: >-
|
||||
一句话说明何时选用、解决什么
|
||||
artifact: 设计.示例能力中文名
|
||||
```
|
||||
|
||||
| 字段 | 谁用 |
|
||||
|------|------|
|
||||
| `declaration` | 插入导演/编排提示,选型 |
|
||||
| `name` / `artifact` | 流程与执行映射 |
|
||||
| `opening` | 可选覆盖;一般只写在 prompt 的 `opening` 块 |
|
||||
|
||||
改 `name` 会破坏已有剧本 JSON,尽量不改。
|
||||
|
||||
## 4.5 opening 节奏(通用)
|
||||
|
||||
```text
|
||||
若 opening 非空:
|
||||
程序发 opening → 用户首答 → 再调 LLM
|
||||
LLM 上下文含:方法块(通常不含再贴一遍 opening 策略以产品为准)
|
||||
+ 首答 + 依赖产物 + 用户需求
|
||||
task 必须写:禁止重复同一开场;在首答上补洞。
|
||||
```
|
||||
|
||||
## 4.6 注入 LLM 时包含哪些块
|
||||
|
||||
按顺序拼接切割结果,**默认不含** `opening`(开场已由程序发出)。
|
||||
`meta` 是否注入以运行实现为准;撰写时把给人/模型的方法写在 `task`/`principles`/`probe`/`output`/`checklist`。
|
||||
|
||||
## 4.7 产物(`output`)通用要求
|
||||
|
||||
- 形状稳定,便于 UI 按 JSON 渲染。
|
||||
- **键名对人友好**(中文或稳定中文标签)。
|
||||
- 机器 id(如 `ref`)若需要,与中文名成对出现。
|
||||
- 写清「本步完成的定义」;未决放 `开放问题`,不要假完备。
|
||||
- 不要在本能力产物里偷偷交下游能力该交的终稿(除非本能力就是收成步)。
|
||||
|
||||
## 4.8 追问(`probe`)通用要求
|
||||
|
||||
- 一轮 askUser **1~2** 点。
|
||||
- 优先:可直接采用或微调的完整句选项 / 短场景。
|
||||
- 能推断则先写入再复述;只问影响本步核心且不能瞎填的点。
|
||||
- 依赖产物已钉死的内容禁止重问。
|
||||
|
||||
---
|
||||
|
||||
# 第五部分:撰写任一能力时的工作步骤
|
||||
|
||||
1. **定身份**:中文名、id、artifact;写清 when / when_not / boundary。
|
||||
2. **看邻接**:上/下游能力各定什么;禁止重叠问卷。
|
||||
3. **定产物形状**:先定 `output` JSON,再写 task/probe 如何填满它。
|
||||
4. **定 opening**:需要程序先问再 LLM → 写 opening;否则留空。
|
||||
5. **写 principles / probe / checklist**:正推、缩减、禁止套件。
|
||||
6. **对照范例**:深度不低于 `aesthetics-interaction`。
|
||||
7. **自检**:§4 块齐全;name 与 catalog 一致;用户文案无裸英文 id。
|
||||
|
||||
---
|
||||
|
||||
# 第六部分:给撰写 AI 的通用任务指令(粘贴用)
|
||||
|
||||
请撰写(或重写)一个【能力】文档:
|
||||
`skills/dialogue/world-simulator/modules/{id}/prompt.md`
|
||||
(具体中文名 / id / 边界由出题方在指令末行指定。)
|
||||
|
||||
必须遵守:
|
||||
|
||||
1. 本文**第一部分~第四部分**的项目理解、称呼与格式契约。
|
||||
2. 只使用规定的 fence 块;不要发明新 fence 名。
|
||||
3. 对用户话术使用导演/剧本/演员/能力;不要写「总管调度 design-xxx」。
|
||||
4. 正推:体验 → 手段;禁止题材套件与否定式路由。
|
||||
5. 交互与美学已合并为「美学纲领与交互范式」;禁止建议拆回两步。
|
||||
6. 不要修改程序代码;默认只交付 `prompt.md`(除非出题方要求改 catalog)。
|
||||
7. 输出完整 Markdown 文件正文。
|
||||
|
||||
出题方填写:
|
||||
|
||||
```text
|
||||
能力中文名:
|
||||
id:
|
||||
artifact:
|
||||
上游依赖(常见):
|
||||
明确不做(交给谁):
|
||||
特殊产物要求(可选):
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 第七部分:当前能力池速查(选题,非本文重点)
|
||||
|
||||
世界模拟器常用(编排按需,勿默认全选):
|
||||
|
||||
| 能力 | id | 状态(以仓库为准) |
|
||||
|------|-----|-------------------|
|
||||
| 美学纲领与交互范式 | `aesthetics-interaction` | 范例 |
|
||||
| 实现机制 | `mechanism` | 待细写 |
|
||||
| 世界蓝图与人文地理 | `world-blueprint` | 已写 |
|
||||
| 生成规则 | `generation-rules` | 待细写 |
|
||||
| 具体实例 | `concrete-instances` | 待细写 |
|
||||
| 拓扑图谱 | `topology` | 待细写 |
|
||||
| 叙事指南 | `narrative` | 待细写 |
|
||||
| 变量设计与更新规则 | `variable-design` | 待细写 |
|
||||
| 变量控制上下文 | `variable-context` | 待细写 |
|
||||
| 设计状态栏 | `status-bar` | 待细写 |
|
||||
| 设计回复格式 | `reply-format` | 待细写 |
|
||||
| Worker 规格 | `worker-spec` | 待细写 |
|
||||
| 细化终稿 | `refine` | 待细写 |
|
||||
|
||||
导演示例:世界模拟器、扩写助手(见 `recipes/`)。
|
||||
|
||||
---
|
||||
|
||||
# 附录:与旧文档关系
|
||||
|
||||
| 文档 | 关系 |
|
||||
|------|------|
|
||||
| 本文 | **泛用**能力撰写 + 项目/称呼;给外部 AI 的主交接 |
|
||||
| `world-simulator-modules.md` | 仓库内清单与格式摘要 |
|
||||
| `ui-glossary.md` | 用户可见文案权威 |
|
||||
| `architecture.md` | 运行内核;写能力时不必复述实现细节 |
|
||||
| 旧 `capability-mechanism-handoff.md` | 已过时为「单能力特例」;以本文为准 |
|
||||
20
docs/briefs/capability-mechanism-handoff.md
Normal file
20
docs/briefs/capability-mechanism-handoff.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# (已迁移)实现机制单能力简报
|
||||
|
||||
本文件原先只讲「实现机制」特例,**已废弃**。
|
||||
|
||||
请改用泛用交接简报:
|
||||
|
||||
→ **[`capability-authoring-brief.md`](./capability-authoring-brief.md)**
|
||||
|
||||
其中包含:项目概述、导演/剧本/演员/能力称呼、泛用能力格式规范,以及可粘贴给其它 AI 的任务指令。
|
||||
|
||||
若要写「实现机制」,在泛用简报第六部分末行填写:
|
||||
|
||||
```text
|
||||
能力中文名:实现机制
|
||||
id:mechanism
|
||||
artifact:设计.实现机制
|
||||
上游依赖(常见):美学纲领与交互范式
|
||||
明确不做(交给谁):拓扑图谱 / Worker 规格 / 变量* / 状态栏 / 回复格式 / 细化终稿
|
||||
特殊产物要求(可选):总览最小可运行结构;含「为何需要」「未纳入与原因」;正推禁止默认世界模拟套件
|
||||
```
|
||||
@@ -11,14 +11,19 @@ Worker / skill 执行时,Runtime 将黑板 tag 与固定体裁说明拼成 LLM
|
||||
|
||||
## 2. 上半固定、下半动态
|
||||
|
||||
每条 worker prompt 分为两段:
|
||||
每条 worker prompt 自上而下拼接(**一段式 stack,越稳定越靠上**):
|
||||
|
||||
```text
|
||||
┌─ Preset(会话 presetId)────────────────────────────┐
|
||||
│ 生成参数、prompt 条目(system / 文风片段等) │
|
||||
│ 见 preset-format.md;与 skill 包正交 │
|
||||
└────────────────────────────────────────────────────┘
|
||||
┌─ 上半:固定上下文(Static)────────────────────────┐
|
||||
│ shared-context.md(包级体裁约束) │
|
||||
│ worker SKILL.md 正文(能力说明、自检) │
|
||||
│ contextSegments 中 tier=static 的 tag │
|
||||
│ 例:角色卡.确认稿、世界.蓝图、设计.交互范式 │
|
||||
│ 例:角色卡.确认稿、世界.蓝图、设计.worker集 切片 │
|
||||
│ (含 narrator 的 presentation,无单独美学纲领 tag)│
|
||||
└────────────────────────────────────────────────────┘
|
||||
┌─ 下半:动态上下文(Dynamic)────────────────────────┐
|
||||
│ contextSegments 中 tier=dynamic 的 tag │
|
||||
@@ -30,7 +35,7 @@ Worker / skill 执行时,Runtime 将黑板 tag 与固定体裁说明拼成 LLM
|
||||
原则:
|
||||
|
||||
```text
|
||||
越稳定、越少改 → 越靠上(static)
|
||||
越稳定、越少改 → 越靠上(preset / static)
|
||||
越增量、每轮变 → 越靠下(dynamic)
|
||||
```
|
||||
|
||||
@@ -80,7 +85,11 @@ contextSegments:
|
||||
| `label` | 拼进 prompt 的 Markdown 标题(可选) |
|
||||
| `policy` | 动态段裁剪,见 §5 |
|
||||
|
||||
未声明 `contextSegments` 时,Runtime 回退:按 `inputTags` 顺序输出 JSON `inputs`(当前实现)。
|
||||
未声明 `contextSegments` 时,Runtime 回退:按 `inputTags` 顺序输出 JSON `inputs`。
|
||||
|
||||
有 `contextSegments` 时:按 static → dynamic 顺序拼 Markdown(含 `label` 标题);实现见 `src/skills/context-segments.ts` → `assembleWorkerContext()`。
|
||||
|
||||
创作前情:`创作.已验收内容` 存各单位**最后一次验收**的内容切片,拼装时格式化为只读前情提要。
|
||||
|
||||
---
|
||||
|
||||
@@ -93,17 +102,20 @@ contextSegments:
|
||||
| `tail_lines_N` | 事件流等取最后 N 行 |
|
||||
| `tail_tokens_N` | 按估算 token 截断(预留) |
|
||||
|
||||
上下文过长时:
|
||||
上下文过长时(**本节管 Run worker 拼装**;创作会话见 `design-orchestrator-guide.md` §7.2):
|
||||
|
||||
1. 优先靠 policy 裁剪动态段
|
||||
2. 实例 manifest 可覆盖 variant(如 `historyPolicy: last_10_turns`)
|
||||
3. agent 可 invoke 显式 **compress-history** skill 写摘要 tag(调度 skill,不是随手删 prompt)
|
||||
2. **Run 验收后压缩**:过程 tag 归档,仅终产物 + `上下文.定稿摘要` 进入下一 worker(`compress-after-worker.ts`)
|
||||
3. 实例 manifest 可覆盖 variant(如 `historyPolicy: last_10_turns`)
|
||||
4. 长线再考虑显式 compress-history / RAG,禁止「完整历史喂一个 LLM 再筛给另一个」
|
||||
|
||||
**创作**不走本拼装栈:讨论放 session `messages`;单位验收后 **删交互、留产物**。Worker 执行仍始终按契约从黑板重装。
|
||||
|
||||
---
|
||||
|
||||
## 6. contextProfile(实例 manifest)
|
||||
|
||||
实例化阶段产出(写入 `设计.run_skill清单` 或 Book manifest),agent **只选预置档位**,不列 tag:
|
||||
实例化阶段产出(写入 `设计.worker集` 或 Book manifest),agent **只选预置档位**,不列 tag:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -161,7 +173,7 @@ user:
|
||||
{按 segment 顺序格式化的 Markdown 或结构化块}
|
||||
```
|
||||
|
||||
实现:`src/worker/executor.ts` → `assembleWorkerContext()`(待从纯 JSON inputs 升级)。
|
||||
实现:`src/skills/context-segments.ts` → `assembleWorkerContext()`;由 `src/worker/executor.ts` 调用。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
# 创作流程指南(Creation Playbook)
|
||||
|
||||
> **文档层级:剧本 / 创作流程概念(非系统架构)。**
|
||||
> 系统模块与边界见 [`architecture.md`](./architecture.md)。
|
||||
|
||||
## 0. 内容在哪
|
||||
|
||||
**流程定义在 skill 包里**(orchestrator manifest + `workers/*/SKILL.md`)。本文只保留概念。
|
||||
|
||||
写新包 → `skill-design-guide.md` → 新建 `skills/.../orchestrator.md`。
|
||||
**设计方法(正推、三大步、表与副作用)** → **`design-orchestrator-guide.md`**(权威)。
|
||||
**流程落地**在 skill 包(`orchestrator.md` + `design-intake`)。
|
||||
写新包 → `skill-design-guide.md`(包格式薄层)→ `skills/.../orchestrator.md`。
|
||||
|
||||
---
|
||||
|
||||
@@ -14,30 +17,52 @@
|
||||
Orchestrator 包 能力库 + manifest(静态)
|
||||
黑板 tag 一次 Session 的实参(动态)
|
||||
Book 跨 Session:过程、资产、游玩
|
||||
设计.worker集 实例规格(JSON;accept 后 = Worker 声明)
|
||||
```
|
||||
|
||||
| 层 | 管什么 |
|
||||
|----|--------|
|
||||
| **运行相位** | `idle` / `running` / `waiting_user` — 系统在等什么 |
|
||||
| **业务 stage** | `design`(实例化)→ `play`(运行)→ `done` |
|
||||
| **Agent** | tool loop 内 invoke 哪个 skill |
|
||||
| **Skill** | 读哪些 tag、写哪些 tag、上下文怎么拼 |
|
||||
| **业务 stage** | `design`(创作)→ `play`(游玩)→ `done` |
|
||||
| **Agent** | tool loop 内 invoke 哪个 worker |
|
||||
| **声明** | play 可调度哪些 ref;executor 读声明(非题材管道) |
|
||||
| **Book** | 长期存储,见 `book-storage.md` |
|
||||
|
||||
---
|
||||
|
||||
## 2. 启动
|
||||
## 2. 默认包创作流
|
||||
|
||||
```text
|
||||
skill_selection 选 orchestrator 包
|
||||
intake 启动询问(最小信息)
|
||||
design stage agent 按需 invoke instantiate skill
|
||||
declare ready 进入 play
|
||||
play stage 用户输入 → agent burst → run skill
|
||||
done 归档 Book
|
||||
新建作品 → UI 引导 → 用户首句 → 创作单位逐步谈
|
||||
→ 宜:phase:core → 纲领类 fixed:* → worker:* → refine
|
||||
(非死管道;可穿插,但禁止默认「先写完 worker 再填上下文」)
|
||||
→ 单位 = 已写出的 fixed:* / resident:* | worker:*(示例话题可问用户,非填空)
|
||||
→ 单位跑:只写 设计.worker集.草稿;讨论进 messages
|
||||
→ 用户接受 → 删本单位交互消息,只留产物;草稿保留
|
||||
→ ……谈完后终稿写 设计.worker集 → 验收后才可进 play
|
||||
→ (可选)开局 · 开场白 → 用户手动进游玩
|
||||
→ run:worker 按契约从黑板重装;acceptance=review 处停、压缩过程 tag
|
||||
```
|
||||
|
||||
`startupCompleted` / `instanceReady`:agent 声明 + 程序校验「当前 run_skill清单 可运行」,非固定 prerequisite 打勾。
|
||||
固定上下文 tag 的主收益是 **跨 worker 复用同一份正文**;写下游时仍依赖上游定稿(不能只报 tag 名省掉正文)。能省的是扯皮过程(验收折叠)。
|
||||
|
||||
不再要求用户选择 skill 包;默认 orchestrator 见 `src/config/default-orchestrator.ts`。
|
||||
方法细节见 **`design-orchestrator-guide.md` §7.2**。不要以「世界模拟器」为默认总形态。
|
||||
|
||||
### 进入游玩
|
||||
|
||||
- Worker 集 **accepted**(试验期最低门槛)
|
||||
- **进 play 由用户手动决定**;开局锁定非强制
|
||||
|
||||
### 编辑与重 roll
|
||||
|
||||
```text
|
||||
关键节点可存快照
|
||||
不满意 → 加载 earlier 快照 / 重 roll
|
||||
play:重 roll 替代每轮验收;新输入 = 隐式认可上轮终稿
|
||||
```
|
||||
|
||||
见 `run-snapshot.md`。
|
||||
|
||||
---
|
||||
|
||||
@@ -45,9 +70,9 @@ done 归档 Book
|
||||
|
||||
| | Agent | Runtime |
|
||||
|--|-------|---------|
|
||||
| 决定 | invoke 哪个 skill、何时 ask_user/finish | — |
|
||||
| 拼接上下文 | 只用 read_blackboard 辅助决策 | assembleWorkerContext |
|
||||
| 写黑板 | 否(边界 tool 驱动 worker 写) | 校验 outputTags 后写入 |
|
||||
| 决定 | invoke 哪个 worker、何时 ask_user/finish | — |
|
||||
| 拼接上下文 | 只用 read_blackboard 辅助决策 | 常驻上下文 + 声明拼装 |
|
||||
| 写黑板 | 否(边界 tool 驱动 worker 写) | 校验后写入;表字段尊重 rev |
|
||||
|
||||
Agent **不指定 inputTags**。见 `context-assembly.md`。
|
||||
|
||||
@@ -55,29 +80,16 @@ Agent **不指定 inputTags**。见 `context-assembly.md`。
|
||||
|
||||
## 4. Tool loop burst
|
||||
|
||||
每次用户硬事件后,agent 进入 `running`,在 **burst 上限内** 多轮 tool;碰到边界 tool 或需用户则停。
|
||||
每次用户硬事件后,agent 进入 `running`,在 burst 上限内多轮 tool;碰到边界 tool 或需用户则停。
|
||||
见 `tool-contracts.md`、`runtime-state-machine.md`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 角色卡(一种 Book 形态)
|
||||
|
||||
```text
|
||||
创建过程 designTrace + 对话 → CardBook(design)
|
||||
创建结果 角色卡.确认稿 等 → CardAsset(可导入资产)
|
||||
游玩过程 playTrace + runState → PlayBook
|
||||
游玩存档 PlaySnapshot
|
||||
```
|
||||
|
||||
不拆独立 author/play 包;见 `book-storage.md` §角色卡。
|
||||
|
||||
---
|
||||
|
||||
## 6. 相关文档
|
||||
## 5. 相关
|
||||
|
||||
| 文档 | 关系 |
|
||||
|------|------|
|
||||
| `architecture.md` | 总览 |
|
||||
| `skill-design-guide.md` | 设计方法 |
|
||||
| `context-assembly.md` | 上下文拼接 |
|
||||
| **`design-orchestrator-guide.md`** | ★ 创作设计方法 |
|
||||
| `skill-design-guide.md` | 包与字段格式 |
|
||||
| `run-snapshot.md` | 存档 |
|
||||
| `tag-blackboard.md` | 标签 |
|
||||
|
||||
274
docs/daily-use-p0.md
Normal file
274
docs/daily-use-p0.md
Normal file
@@ -0,0 +1,274 @@
|
||||
# 日常使用场景 → P0 详细路径
|
||||
|
||||
## 0. 怎么用本文
|
||||
|
||||
1. **先定场景**:日常到底在干什么(§1)
|
||||
2. **再列功能**:每个场景要哪些能力、是否已有(§2)
|
||||
3. **收成 P0**:只把「日常阻断 / 高频刚需」推进 P0 详细路(§3)
|
||||
4. **其余进后续**:体验打磨与加固,不塞进 P0(§4)
|
||||
|
||||
系统边界见 `architecture.md`;总路线见 `px-roadmap.md`。
|
||||
|
||||
### 0.1 已确认取向(2026-07-26)
|
||||
|
||||
| # | 结论 |
|
||||
|---|------|
|
||||
| 1 | 日常包含 **两类**:AIRP/交互扮演 **与** 长文/爽文创作(同一运行内核) |
|
||||
| 2 | **UI 不自研美学**:布局/控件直接抄参考产品即可;**存档与续作稳定是硬指标** |
|
||||
| 3 | **导演**:新建时用 UI **选一个**(世界模拟器 / 扩写助手…;初期可只有 1 项) |
|
||||
| 4 | **剧本动态**:不在 UI 里点选死板「剧本菜单」;选定导演后,由导演(Agent)听用户说话谈出本局剧本(创作流程 / Worker 集)与调度 |
|
||||
|
||||
用户侧拍摄术语权威对照:**`ui-glossary.md` §0**(导演 / 剧本 / 演员 / 能力)。
|
||||
|
||||
### 0.2 导演 vs 剧本(动态)
|
||||
|
||||
```text
|
||||
导演 = 用户 UI 选一次的方法起点(可调味)
|
||||
= 内部 recipes / 原「能力包·配方」合一
|
||||
= 例:世界模拟器、扩写助手
|
||||
|
||||
剧本 ≠ 再选一张固定流程卡
|
||||
= 选定导演之后:用户说话 → 导演调度创作
|
||||
→ 产出 设计.创作流程 + 设计.worker集(本局活剧本)
|
||||
→ 游玩再按声明让【演员】上场
|
||||
|
||||
能力 = 共用工序(美学纲领与交互范式…);导演从中编排
|
||||
演员 = 实际上场的 Worker
|
||||
```
|
||||
|
||||
| | 导演 | 剧本(动态) |
|
||||
|--|------|----------------|
|
||||
| 谁选 | **用户 UI**(显式,一层) | **导演 Agent + 用户对话**(隐式) |
|
||||
| 变不变 | 选项相对稳定 | 每局流程、Worker 集、回合调度都可变 |
|
||||
| 忌讳 | 再叠一层「配方」选择 | 固化成死 DAG / 死选单 |
|
||||
| 本仓库对应 | `recipes/` + 默认 skill 包 | `设计.创作流程` / `设计.worker集` |
|
||||
|
||||
因此:**只给导演一个选择 UI**;**不要**再给「剧本 / 配方」做第二层死板选择器。
|
||||
|
||||
**多导演**才有意义的情况:方法边界真不同(世界模拟 vs 扩写助手),而不是同一导演内的题材分化——后者留在剧本层动态谈成。
|
||||
|
||||
---
|
||||
|
||||
## 1. 日常使用场景(产品视角)
|
||||
|
||||
### 1.1 主场景(P0 对准)— 两条产品线,同一内核
|
||||
|
||||
#### 线 A:AIRP / 交互扮演
|
||||
|
||||
| ID | 场景 | 用户在干什么 |
|
||||
|----|------|--------------|
|
||||
| **A1** | 开新档创作 | 描述想扮演/交互什么,谈到可玩的 Worker 集 |
|
||||
| **A2** | 验收定稿 | 接受/重来/跳过询问,直到可进游玩 |
|
||||
| **A3** | 进游玩 + 多回合 | 读终稿 → 输入 → 再生成;偶发重 roll |
|
||||
| **A4** | 续作 + 存档读档 | 关掉再开接着玩;关键节点存/读档 |
|
||||
|
||||
#### 线 B:长文 / 爽文创作
|
||||
|
||||
| ID | 场景 | 用户在干什么 |
|
||||
|----|------|--------------|
|
||||
| **B1** | 开新档创作 | 描述题材/爽点/篇幅;站位偏「写手/统筹」或「助手分段」 |
|
||||
| **B2** | 验收定稿 | 钉大纲/章结构/写手类 Worker 与固定上下文,可进「写」 |
|
||||
| **B3** | 分段出文 | 按大纲或指令生成章/节正文;不满意重 roll 该段 |
|
||||
| **B4** | 续作 + 存档读档 | 连载式接着写;章节点存档、读档不丢正文与进度 |
|
||||
|
||||
A4 / B4 **共用**同一套存档语义(`session.json` + `instance`/`run` 快照);P0 把稳定性做硬。
|
||||
|
||||
### 1.2 次场景(P0 不精做)
|
||||
|
||||
| ID | 场景 | 说明 |
|
||||
|----|------|------|
|
||||
| S7 | 改表 / 状态面板 | 有就不炸;精致编辑 → 后 |
|
||||
| S8 | 改规格再开写/玩 | 有 instance 加载即可,流程打磨 → 后 |
|
||||
| S9 | 开局 swipe / 多版本对比 | 有更好,非 P0 闸门 |
|
||||
| S10 | 预设商店式管理 | 失败可读即可 |
|
||||
|
||||
### 1.3 明确非 P0
|
||||
|
||||
| ID | 说明 |
|
||||
|----|------|
|
||||
| N2 | 狼人杀级信息隔离 |
|
||||
| N3 | Context Trace 专业调试台 |
|
||||
| N4 | 多导演**运营商店**(P0 只要选择 UI,可仅 1 项) |
|
||||
| N5 | DefinitionVersion 完整发布迁移协议 |
|
||||
| N6 | SillyTavern 卡批量导入导出 |
|
||||
| N7 | 自研精美 UI / 按 `ui-design` 大改布局(**抄参考即可**) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 场景 → 功能需求(含现状)
|
||||
|
||||
图例:✅ 基本有 🟡 有但糙/易踩坑 ❌ 缺或不可靠
|
||||
|
||||
### 共用:创作 / 验收 / 进出
|
||||
|
||||
| 功能 | 现状 | P0? |
|
||||
|------|------|-----|
|
||||
| 新建作品 → 选导演(能力包;初期可仅一项) | ✅/🟡 | **必做**(有选择 UI;一项时默认选中即可) |
|
||||
| 意图可走扮演 **或** 写手/爽文(剧本层动态,不另选死板剧本卡) | 🟡 | **必做** |
|
||||
| 验收出口无死胡同 | 🟡 | **必做** |
|
||||
| 进入游玩/写作阶段门槛清晰 | 🟡 | **必做** |
|
||||
| UI 自研打磨 | — | **不做**;缺块就抄参考产品交互 |
|
||||
|
||||
### 线 A:多回合扮演
|
||||
|
||||
| 功能 | 现状 | P0? |
|
||||
|------|------|-----|
|
||||
| 连续 ≥3 回合不卡死 | 🟡 | **必做** |
|
||||
| 失败可重试 | 🟡 | **必做** |
|
||||
|
||||
### 线 B:长文 / 爽文
|
||||
|
||||
| 功能 | 现状 | P0? |
|
||||
|------|------|-----|
|
||||
| 创作能产出「大纲 + 写章」类 Worker 声明(模板或谈成) | 🟡 | **必做**(不新开第二包;缺则补 **templates / 引导**,不是多包) |
|
||||
| play 期能按段生成正文并展示 | 🟡 | **必做**(≥2 段/章连续) |
|
||||
| 章/段级重 roll 或读档回到段前 | 🟡 | **必做**(与存档同一套:load earlier → 重来) |
|
||||
| 独立长文编辑器 / Monaco | ❌ | **不做**(抄现有主舞台展示正文即可) |
|
||||
|
||||
### 存档 / 续作(最高优先级)
|
||||
|
||||
| 功能 | 现状 | P0? |
|
||||
|------|------|-----|
|
||||
| `session.json` 续作相位正确 | 🟡 | **硬必做** |
|
||||
| 手动存 / 列表 / 加载后可继续 | 🟡 | **硬必做**(线 A、线 B 各验一次) |
|
||||
| 存档不丢终稿正文与关键 tag | 🟡 | **硬必做** |
|
||||
| 分支树 UI | ❌ | 不做 |
|
||||
|
||||
---
|
||||
|
||||
## 3. P0 详细路径
|
||||
|
||||
**P0 一句话**:用户用 UI 选好**导演 Skill**(初期可只有一个)后,AIRP 与长文/爽文都在**剧本层动态**谈成并跑起来,且 **存档/续作稳定**;UI 抄参考即可。
|
||||
|
||||
### 3.1 黄金路径(两条都要绿)
|
||||
|
||||
**线 A(扮演)**
|
||||
|
||||
```text
|
||||
A-GP1 新建 → UI 选导演(可仅一项)→ 描述扮演/交互意图
|
||||
A-GP2 验收至可进游玩
|
||||
A-GP3 ≥3 回合输入→终稿
|
||||
A-GP4 存 run 档 → 刷新/重开续作成功
|
||||
A-GP5 加载 earlier 档或新开一条线之一可用
|
||||
```
|
||||
|
||||
**线 B(长文/爽文)**
|
||||
|
||||
```text
|
||||
B-GP1 新建 → UI 选导演 → 描述爽文/长篇意图(写手或分段助手)
|
||||
B-GP2 验收至可进写作(Worker 集含大纲/写章类能力,勿只能聊天扮演)
|
||||
B-GP3 连续生成 ≥2 段(章)正文
|
||||
B-GP4 存档含已生成正文 → 续作能接着写下一段
|
||||
B-GP5 回到 earlier 档重 roll 某段 或 等价恢复可用
|
||||
```
|
||||
|
||||
### 3.2 工作包
|
||||
|
||||
#### WP-S 存档稳定(最先 / 贯穿)
|
||||
|
||||
| # | 项 | 验收 |
|
||||
|---|-----|------|
|
||||
| S1 | 续作:打开 Book = 恢复可操作相位 + 输入区与等待态一致 | A-GP4 / B-GP4 |
|
||||
| S2 | 手动存:人话 kind;写入后列表可见 | 同上 |
|
||||
| S3 | 加载:黑板 + 消息 + 运行进度一致,可继续生成 | A-GP5 / B-GP5 |
|
||||
| S4 | 回归:线 A、线 B 各完整存读一轮,不手改 `books/` | 双线绿 |
|
||||
| S5 | 已知「存档丢正文 / 续作进死相位」类 bug 清零 | 无阻断 |
|
||||
|
||||
#### WP-D 导演选择(与存档并列早做)
|
||||
|
||||
| # | 项 | 验收 |
|
||||
|---|-----|------|
|
||||
| D0 | 新建作品可选导演(registry);仅 1 项时默认选中并可确认 | 进入该包创作 |
|
||||
| D1 | 人话展示导演名/简介,不暴露路径 | 可选懂 |
|
||||
|
||||
#### WP-A 进出与失败出口(文案够用即可)
|
||||
|
||||
| # | 项 | 验收 |
|
||||
|---|-----|------|
|
||||
| A1 | 裸 id 不进用户主路径(glossary 底线) | 无 `design-core` 裸露 |
|
||||
| A2 | `waiting_user` 有主按钮出口 | 无死胡同 |
|
||||
| A3 | 进游玩/写作门槛提示 | 误进可理解 |
|
||||
| A4 | LLM/worker 失败:中文 + 重试 | 可回到可输入 |
|
||||
|
||||
#### WP-R 线 A 回合可靠
|
||||
|
||||
| # | 项 | 验收 |
|
||||
|---|-----|------|
|
||||
| R1 | ≥3 回合无卡死 | A-GP3 |
|
||||
| R2 | 假死可刷新恢复且不腐档 | 与 WP-S 一致 |
|
||||
|
||||
#### WP-L 线 B 长文/爽文最小闭环
|
||||
|
||||
| # | 项 | 验收 |
|
||||
|---|-----|------|
|
||||
| L1 | 启动引导明确支持「写爽文/长篇/先大纲再写」 | B-GP1 |
|
||||
| L2 | design 能稳定收到写手向 Worker 集(补 template 或 design 提示,**同一导演内动态谈成**) | B-GP2 |
|
||||
| L3 | play 连续 ≥2 段正文 | B-GP3 |
|
||||
| L4 | 正文在存档/续作中仍在 | B-GP4 |
|
||||
|
||||
#### WP-U UI(抄,不雕)
|
||||
|
||||
| # | 项 | 验收 |
|
||||
|---|-----|------|
|
||||
| U1 | 缺的存档列表/确认/重试控件:直接仿参考产品,不写设计长文 | 能点、能完成 GP |
|
||||
| U2 | **禁止** P0 内按 `ui-design.md` 做大布局重构 | 范围不膨胀 |
|
||||
|
||||
#### WP-E 闸门
|
||||
|
||||
| # | 项 | 验收 |
|
||||
|---|-----|------|
|
||||
| E1 | 线 A、线 B 黄金路径各冷启动 + 续作通过 | 全绿 |
|
||||
| E2 | 更新 `px-roadmap` / `architecture` 进度勾选 | 文档同步 |
|
||||
|
||||
### 3.3 P0 明确不做
|
||||
|
||||
- 第二套死板「剧本选单」/ 固定题材 DAG
|
||||
- 自研精美 UI、工作台大改
|
||||
- 隔离模拟、Trace 台、完整副作用调度、版本迁移协议
|
||||
- 为文采大改全套提示词(仅修**阻断线 B 收不成写手规格**的引导/模板)
|
||||
|
||||
### 3.4 建议顺序
|
||||
|
||||
```text
|
||||
1. WP-S 存档稳定(先修已知腐档)
|
||||
2. WP-D 导演选择 UI(可仅一项)
|
||||
3. WP-A 失败出口 / 进出
|
||||
4. WP-R 线 A 回合
|
||||
5. WP-L 线 B 长文最小闭环(引导 + template + 存档验正文)
|
||||
6. WP-U 仅补抄来的缺口控件(含选导演/存档若未齐)
|
||||
7. WP-E 双线验收闸门 → P0 完成
|
||||
```
|
||||
|
||||
### 3.5 P0 DoD
|
||||
|
||||
- [ ] 线 A:A-GP1–5 通过(含选导演、续作/读档)
|
||||
- [ ] 线 B:B-GP1–5 通过(含选导演、正文不丢)
|
||||
- [ ] 导演选择 UI 可用(可仅一项)
|
||||
- [ ] 无等待死胡同;失败可重试
|
||||
- [ ] 不依赖手改磁盘文件
|
||||
- [ ] UI 无大改承诺;存档稳定优先于观感
|
||||
|
||||
---
|
||||
|
||||
## 4. 日常功能 → 阶段归属(修订)
|
||||
|
||||
| 日常能力 | 阶段 |
|
||||
|----------|------|
|
||||
| 导演选择 UI(可仅一项)、剧本层动态谈规格、AIRP+长文主路径、**存档续作硬稳定** | **P0** |
|
||||
| 抄来的 UI 缺口补齐 | **P0(U)** / 不够再后补 |
|
||||
| 表编辑精修、改规格心流、副作用完整 | **PX1/PX2** |
|
||||
| Trace、预算、版本协议、E2E | **PX3** |
|
||||
| 长文「专业编辑器 / 评测 / 多卷」加深 | **原 PX4 收窄**(P0 已含最小长文) |
|
||||
| 隔离 + 专业调试 | **PX5** |
|
||||
| 多个导演包(方法真不同时再加) | **需要时再加**;非「剧本菜单」 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 假设状态
|
||||
|
||||
| # | 原假设 | 状态 |
|
||||
|---|--------|------|
|
||||
| 1 | 仅 AIRP | **已订正**:+ 长文/爽文 |
|
||||
| 2 | UI 要按设计稿打磨 | **已订正**:抄参考;存档稳定优先 |
|
||||
| 3 | 永远自动加载、无选包 UI | **已订正**:UI 选**导演**;剧本层保持动态 |
|
||||
| 4 | 单包 = 不能多种玩法 | **已澄清**:一个导演内剧本动态分化 |
|
||||
511
docs/design-orchestrator-guide.md
Normal file
511
docs/design-orchestrator-guide.md
Normal file
@@ -0,0 +1,511 @@
|
||||
# 创作设计指导(总管 / 分步 design skills)
|
||||
|
||||
> **文档层级:剧本 / 创作方法(非系统架构)。**
|
||||
> 运行内核边界见 [`architecture.md`](./architecture.md);本文不定义相位机、持久化或 Context Compiler。
|
||||
|
||||
权威说明:如何从用户意图收成 **实例规格(`设计.worker集` JSON)**,并指导 play 期声明调度。
|
||||
非固定创作流水线;`world-simulator` 等只是可选能力 ref,不是默认总形态。
|
||||
|
||||
相关:`context-assembly.md`(拼装)、`preset-format.md`(全局预设)、`tag-blackboard.md`(黑板)、`run-snapshot.md`(快照)。
|
||||
|
||||
包内落地:
|
||||
|
||||
| 文件 | 角色 |
|
||||
|------|------|
|
||||
| `orchestrator.md` | 总管调度 |
|
||||
| `design-common.md` | 创作共同开头(注入各 design-*) |
|
||||
| `workers/design-core/` | A 核心(开放,不拆 A1→A2→A3 管道) |
|
||||
| `workers/design-worker/` | 一次一个 worker |
|
||||
| `workers/design-fixed/` | 一次一块固定上下文 |
|
||||
| `workers/design-refine/` | C 细化 / 终稿 |
|
||||
|
||||
**运行时以各 SKILL + design-common 为准**;本文是方法长文,改方法时与 skill 切片一起改。
|
||||
|
||||
---
|
||||
|
||||
## 0. 总原则
|
||||
|
||||
```text
|
||||
三大步(每步含自检,可反复,无强制题材工序)
|
||||
A 核心 → 站位 + 系统扮演/输出/交互 + 体验检验
|
||||
B 细节交互 → Worker/常驻上下文能否撑起世界与交互;冲突请用户选
|
||||
C 细化 → 钉死不能瞎发挥的关键前提;表/数据拓扑/副作用;用户是否满意
|
||||
|
||||
全程正推:需要 [体验] → 用 [常驻上下文 | 表 | worker | 程序 | 预设 | tool] 维持/产出
|
||||
每增必问:能否缩减?常驻上下文能否替代?能否合并?黑板够不够/是否多余?
|
||||
```
|
||||
|
||||
**禁止**
|
||||
|
||||
- 题材 → 固定 worker 清单;否定式路由;完整历史喂 LLM 再筛给另一 LLM
|
||||
- 格式/排版专用 worker;临时生成 tool;默认 input 整理 worker
|
||||
- 把「世界模拟」当成开场默认答案
|
||||
- 固定 instantiate 管道(world-blueprint 等)
|
||||
|
||||
**预设**:凡 LLM 请求(总管 / agent / worker)均插入用户预设。
|
||||
**进 play**:用户手动满意后进入(程序不强制开局锁定)。
|
||||
**Play 回合**:用户新输入 = 认可上一轮最终展示;否则重 roll / 编辑后重 roll。表维护可延后。
|
||||
|
||||
**规格格式**:`设计.worker集` 为 **JSON**(权威存储);解析层可兼容旧 YAML 草稿,新产出必须 JSON。
|
||||
|
||||
---
|
||||
|
||||
## 1. 交互流程(先定「怎么和人轮转」)
|
||||
|
||||
输出本质可以都是「系统给出一截用户可见结果」,但 **轮转形态**不同,规格必须写明:
|
||||
|
||||
| 形态 | 典型 | 规格要点 |
|
||||
|------|------|----------|
|
||||
| 对话回合 | 最终展示一轮 ↔ 用户一轮 | 终稿 tag;无每轮验收 |
|
||||
| 助手分段 | 先大纲/细纲,用户填表再继续 | 表可编辑;阶段门控;大纲可走表副作用 |
|
||||
| 其它 | 旁观多角、写手统筹… | 站位 + 系统扮演共同决定 |
|
||||
|
||||
用户可 **编辑表(精确到字段格)** → 必须有 **字段级版本**(见 §5)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 三大步
|
||||
|
||||
### A. 核心
|
||||
|
||||
#### A1. 用户站位(扮演什么)
|
||||
|
||||
单角代入 / 代理操控 / 旁观·实验 / 写手统筹 / 多角切换…
|
||||
→ 谁决策、输入如何解释、结构骨架。
|
||||
|
||||
#### A2. 系统扮演什么(play agent 整体)
|
||||
|
||||
指整个 **play agent 系统** 要:
|
||||
|
||||
- **扮演什么**(世界执行者?对白对手?写作助手?陈述实验?)
|
||||
- **输出什么**(叙事、摘要、选项、表、大纲块…)
|
||||
- **怎么输出**(单段终稿 / 程序拼状态栏+正文 / 多面板)
|
||||
- **怎么与用户交互**(回合对话、填表驱动、先大纲后正文…)
|
||||
|
||||
由此才谈:要不要转述、程序拼格式、表是否上屏等。
|
||||
|
||||
#### A3. 核心体验检验(首要评分,自检必做)
|
||||
|
||||
对当前理解与「现有 worker/上下文草案」打分或文字判定。
|
||||
|
||||
**对内**:可只在思维链里过一遍。
|
||||
**对外**(总管 `ask_user.assessment`,或关键分叉复述):写成用户可读的完备度评价,再据此出题。
|
||||
|
||||
**首要(必须检)**
|
||||
|
||||
| 维 | 含义 |
|
||||
|----|------|
|
||||
| 核心体验 / 满足来源 | 用户最想反复感到什么;爽点从哪来(权力、求生、关系、解谜…)——**一切评价与追问以此为轴** |
|
||||
| 用户–`<user>` 关系 | 用户与代入角色/叙述对象的关系是否清楚 |
|
||||
| 焦点位置 | 注意力与镜头落在哪 |
|
||||
|
||||
**可选(有缺口才写,不必凑齐)**
|
||||
|
||||
| 维 | 含义 |
|
||||
|----|------|
|
||||
| 内容维度 | 情节/日常/战斗/推理…比重与情绪质感 |
|
||||
| 人物与关系 | 核心关系动态 |
|
||||
| 身体与感官 | 物质、亲密、暴力等描写边界 |
|
||||
| 背景与规则 | 世界架与规则用途 |
|
||||
| 意义与主题 | 快感 / 代价 / 终点 |
|
||||
|
||||
`assessment` 建议结构(按需删维):
|
||||
|
||||
```text
|
||||
# 内容维度
|
||||
核心感觉: 完备度 40%
|
||||
已知: …
|
||||
待探: 用户期望的【情绪质感】是【方向A】?【方向B】?还是组合?
|
||||
|
||||
人物与关系: 完备度 50%
|
||||
已知: …
|
||||
待探: …
|
||||
```
|
||||
|
||||
`questions`:只针对「待探」里必须用户拍板的点;一次 1~2 题。
|
||||
- `prompt`:明确题干(「你更倾向于哪种…」)
|
||||
- `options`:**建议示范**(可含短场景钩子 + 点题句),用户可改写后采用;禁止空泛是/否
|
||||
|
||||
自问:缺口是常驻上下文、表、worker,还是必须问用户?能推断就别问。
|
||||
|
||||
变造世界提示:用户常给「基础架 + 变种设定」;核心体验往往来自变种——从设定读内核,但 **不要**写成固定工序,也不要题材锁死体验。
|
||||
|
||||
#### A 步通用自检
|
||||
|
||||
见 §3。
|
||||
|
||||
---
|
||||
|
||||
### B. 细节交互
|
||||
|
||||
对照 A 的结论,检查草案:
|
||||
|
||||
1. **现有 worker(+ 常驻上下文)能否满足需求?**
|
||||
2. **能否支撑起整个世界/助手流程?**
|
||||
3. **有无冲突必须用户选择?**(两套互斥满足来源、互斥交互形态等 → `ask_user`)
|
||||
|
||||
仍用正推补洞;能固定上下文则不上 worker(§6:示例话题可问用户,不是填空表)。
|
||||
**顺序软规则**:纲领类固定上下文(风格/叙事/美学)宜先于大量 worker——同一份 tag 挂多个 worker,而不是先列 worker 再填上下文。
|
||||
然后 §3 自检。
|
||||
|
||||
---
|
||||
|
||||
### C. 细化
|
||||
|
||||
原则:只钉 **不能交给 AI 自由发挥、又对体验关键** 的东西。
|
||||
变造世界已能推出的(已知现代 → 不问常规科技树)默认不问,除非用户提过。
|
||||
|
||||
常钉类(按需):规则与 **核心实现前提**、状态、**表结构**、**整体叙事指南**(≠ 文风:管世界态度与体验边界)、输入协议。
|
||||
|
||||
本步更盯 **数据**:
|
||||
|
||||
- 什么要 **拓扑化**(依赖、阶段边、触发边)?
|
||||
- 什么要 **数据化/进表**?
|
||||
- 表字段、显隐(折叠防剧透,非防用户)、维护规则、副作用是否清楚?
|
||||
- **用户是否满意** 这些钉死项?
|
||||
|
||||
大纲:**不必**单独「大纲管道」。需要时用 **表 + 副作用**——例如表字段 `当前章=第一章` → 边沿触发插入「第一章细纲」上下文(与好感改人设同一套机制,见 §5)。
|
||||
|
||||
然后 §3 自检。
|
||||
|
||||
---
|
||||
|
||||
## 3. 每一步自检清单(三大步与每一小步共用)
|
||||
|
||||
每完成一截推理或改草案,强制过一遍:
|
||||
|
||||
| # | 问 |
|
||||
|---|-----|
|
||||
| 1 | **能否缩减?** 这条能力/字段/worker 删了是否仍成立? |
|
||||
| 2 | **增加的必要性?** 删掉会丢掉哪段体验? |
|
||||
| 3 | **上文是否足够** 辅助主 LLM 完成其 duty?不足补常驻/表/黑板,而非盲目加工人 |
|
||||
| 4 | **写入黑板的内容** 够不够?是否多余? |
|
||||
| 5 | **能否与其它 worker 合并?** 或 **常驻上下文替代?** |
|
||||
| 6 | **上下文插入是否按「变动/重要度」顺序?**(稳定块与易变块分层;参考 ST 注入位思路) |
|
||||
| 7 | (C 步加重)数据传播、表、副作用、字段版本是否自洽?有无「持续为真每轮触发」? |
|
||||
|
||||
复述给用户时:说明有什么、**为何没有** 某重型件。
|
||||
|
||||
---
|
||||
|
||||
## 4. 短板加装(表见 §5)
|
||||
|
||||
| 短板 | 做法 |
|
||||
|------|------|
|
||||
| 可见文本质量 | 转述 worker **或** 常驻文风;能轻则轻 |
|
||||
| 记忆 / 长线 / 大纲 / 随机分支 / 人设阶段 | **统一用表**(§5):维护 + 显隐 + 副作用插换上下文 |
|
||||
| 信息差玩法 | 分角 worker;极贵,慎用 |
|
||||
| 格式 | 程序拼 |
|
||||
| 真随机 / 骰子卡组计算 | 用户触发或填入,或表;已有 tool 手动调;不临时造 tool |
|
||||
| 用户不应知道的信息 | 程序固定注入,不进可见区;非 input 整理 worker |
|
||||
| 输入协议 | 常驻上下文(`()` 元要求、`""` 对白、无包裹=事实等) |
|
||||
|
||||
长线记忆:**不上**则靠模型;**要上**则 RAG(固定少设计)或表;禁止历史互喂筛选。
|
||||
|
||||
---
|
||||
|
||||
## 5. 表(结构 · 版本 · 维护 · 副作用 · 大纲)
|
||||
|
||||
### 5.1 用途
|
||||
|
||||
状态与物品、显隐信息、阶段/章节、随机与事件权重、人设态度段、**大纲/细纲挂载**、驱动常驻上下文插入或替换。
|
||||
|
||||
### 5.2 结构与显隐
|
||||
|
||||
- 扁平、利于二维表 UI;schema 写在 Worker 集
|
||||
- **显形 / 隐藏(可折叠)**:防剧透、分层观看,不是防用户
|
||||
- 维护规则可执行,并带触发语义(默认 once + edge)
|
||||
|
||||
### 5.3 字段格版本(防互相覆盖)
|
||||
|
||||
每一 **字段格**(非仅整表)含至少:
|
||||
|
||||
```text
|
||||
value, rev, updatedAt, source # source: user | worker:<id> | system
|
||||
```
|
||||
|
||||
合并规则:
|
||||
|
||||
- 用户编辑升 `rev`,`source=user`
|
||||
- Worker 写回须带 **读时的 rev**;若当前 rev 已变 → **不覆盖**,跳过或 ask_user
|
||||
- 禁止无条件用维护结果盖掉用户格
|
||||
|
||||
### 5.4 维护 worker
|
||||
|
||||
读:正文/裁决、维护规则、当前表 → 写:字段更新。
|
||||
可不写用户终稿;可与展示链解耦、**延后**跑。
|
||||
|
||||
### 5.5 副作用(含大纲、人设、事件)
|
||||
|
||||
表值变化满足条件 → 触发声明内专用逻辑(改写态度段、插入第 N 章细纲、替换某常驻块等)。
|
||||
|
||||
优先:查表换段;跨阶段再跑改写 LLM。
|
||||
|
||||
**防无限触发(程序约定,写入规格):**
|
||||
|
||||
1. **边沿**:`prev` 不满足且 `now` 满足才触发
|
||||
2. **阶段字段**:规则绑「进入 stage」,不绑「处于 stage」
|
||||
3. **`fired` / ruleId**:once 规则触发后标记,不再入队
|
||||
4. 可重复须显式(`mode: every_edge`)+ 冷却/边沿
|
||||
5. 同 round 同 ruleId 最多一次;对本轮维护用快照算边沿,限制连锁爆炸
|
||||
|
||||
例:`好感>=60→恋爱` 不得每轮触发;`当前章=第一章`→插入细纲须 edge 或章字段变更 once。
|
||||
|
||||
**规格字段(`tables.side_effects[]`):**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "affinity-romance",
|
||||
"field": "好感",
|
||||
"op": "gte",
|
||||
"value": 60,
|
||||
"mode": "once",
|
||||
"action": {
|
||||
"type": "write_tag",
|
||||
"tag": "上下文.角色态度",
|
||||
"content": "恋爱模式:……"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 含义 |
|
||||
|------|------|
|
||||
| `op` | `eq` / `neq` / `gte` / `lte` / `gt` / `lt` / `truthy` / `changed` |
|
||||
| `mode` | `once`(边沿 + 记 fired)\| `every_edge`(可反复跨边沿) |
|
||||
| `action.type` | `write_tag` / `replace_tag` / `queue_worker` |
|
||||
|
||||
实现:`src/blackboard/table-side-effects.ts`;写 `变量.当前` / `运行.初始变量` 后由 runtime 求值;fired 存 `运行.表副作用.fired`。
|
||||
|
||||
### 5.6 与交互流程
|
||||
|
||||
助手流:生成大纲/细纲 → **用户写入/改表** → 副作用或调度读表继续;始终尊重字段 `rev`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 固定上下文族与预设
|
||||
|
||||
**「固定上下文」是概称**:不必单独开 worker、由程序按契约插入 prompt 的内容。
|
||||
**不上 worker ≠ 不重要**——谈清楚并写进规格的纲领/范式,往往比某个中间 worker 更决定体验。
|
||||
|
||||
### 6.0 Tag 的真正用途(纠正)
|
||||
|
||||
固定上下文用稳定 **tag / 单位 id** 命名(如美学纲领、叙事指南),便于:
|
||||
|
||||
1. **同一份正文挂到多个 worker**(`mount` / `contextSegments` / 声明映射)——这是主收益
|
||||
2. **创作单位分块验收**——扯皮过程可折叠,定稿切片进「创作.已验收内容」(已做)
|
||||
|
||||
**不是**「只报 tag 名就能省掉创作期依赖正文」。写 B 块时若依赖 A 的内容,仍要把 A 的定稿喂给写 B 的 LLM;tag 不能替代依赖传递。
|
||||
|
||||
```text
|
||||
能省:反复扯皮直到定稿之前的过程(验收折叠)
|
||||
不能省:写下游时对上游定稿正文的依赖
|
||||
主收益:跨 worker 复用同一份固定上下文,而不是为每个 worker 各写一份
|
||||
```
|
||||
|
||||
### 6.1 用法(重要:不是填空)
|
||||
|
||||
§6.2 里的名称 **只是示例话题**:告诉 design / 总管——**可以向用户询问类似内容**,再收成固定上下文。
|
||||
|
||||
```text
|
||||
不是:按表逐项填空、默认必谈、谈完才算过关
|
||||
而是:对话中若体验需要钉死某类边界/呈现/协议 → 可以按这类话题问用户 → 写入规格并注入
|
||||
```
|
||||
|
||||
正推仍成立:需要维持 [体验/边界/呈现] 且宜稳定注入 → 固定上下文;会变 → 表;本轮推理 → worker。
|
||||
问什么、问几句、要不要问,由当前体验缺口决定——**禁止把示例表当成问卷。**
|
||||
|
||||
### 6.2 示例话题(可问用户的方向)
|
||||
|
||||
| 话题(示例) | 大致在问什么 | 若收成规格,常见落点 |
|
||||
|--------------|--------------|----------------------|
|
||||
| 交互怎么转 | 站位、系统扮演、输出、怎么一轮一轮交手 | `interaction`(多由 `phase:core` 收) |
|
||||
| 叙事指南 | 世界/助手态度与体验边界(≠ 文风) | `narrative_guide` |
|
||||
| 美学纲领 | 呈现给用户的可读终稿气质 | `presentation` / 常驻美学块 |
|
||||
| 输入协议 | `()` / `""` / 无包裹等约定 | `input_protocol` |
|
||||
| 核心前提 | 不能瞎发挥又关键的硬前提 | `core_premises` |
|
||||
| 其它常驻片段 | 只给部分 worker 的稳定句(态度段、轻规则…) | `resident_context[]` |
|
||||
|
||||
落点 id(`fixed:*` / `resident:*`)仅在 **已经写出内容、要当创作单位验收** 时使用;没有内容就不要当成待填格子。
|
||||
实现里的 `FIXED_CONTEXT_CATALOG` 同样是提示目录,不是 UI 填空项。
|
||||
|
||||
### 6.3 创作顺序(软)
|
||||
|
||||
```text
|
||||
phase:core(核心体验 + 交互)
|
||||
→ 纲领类固定上下文(风格 / 叙事 / 美学…)——宜先于大量 worker
|
||||
→ 一次一个 worker(声明挂载哪些已有 tag,勿复制纲领)
|
||||
→ 杂项常驻 / 表 / 副作用 → 终稿
|
||||
```
|
||||
|
||||
**禁止默认**「先写完 worker 列表,再为每个 worker 填写上下文」。
|
||||
Worker 规格应回答:职责是什么、读哪些已有 tag、写哪些 tag——而不是顺带发明一份私有美学。
|
||||
|
||||
**UI**:检查器把固定上下文做成 **独立卡片区**(非 Worker 子项),每张卡展示 **塞进哪些 Worker**;见 `worker-set-view.ts` 的 `contextTags`。
|
||||
|
||||
### 6.4 常驻条目与预设
|
||||
|
||||
杂项常驻(`resident_context[]`)形状示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "tone",
|
||||
"position": "static",
|
||||
"importance": 2,
|
||||
"content": "文风克制、偏冷",
|
||||
"mount": ["narrator"]
|
||||
}
|
||||
```
|
||||
|
||||
- 默认黑板 tag `上下文.常驻.{id}`;可显式 `tag`
|
||||
- `mount` 空 = 全部 worker;否则仅列出的 ref
|
||||
- 能常驻则不上 worker;叙事指南 / 美学 / 交互等 **若已谈清**,用对应规格字段,不要全塞进含糊 `tone`
|
||||
- 越稳、越决定体验边界 → 插入位越靠上
|
||||
|
||||
预设 = 全局;固定上下文 = 实例/worker 级。
|
||||
用户不可见、模型必须知 → **程序固定拼装**,不是整理用户输入的 worker。
|
||||
实现:`src/skills/resident-context.ts`。
|
||||
|
||||
---
|
||||
|
||||
## 7. 产出与验收
|
||||
|
||||
产出:`设计.worker集` JSON,建议包含:
|
||||
|
||||
- `interaction`:站位、系统扮演、输出形态、与用户轮转方式
|
||||
- `experience_check`:首要三维(及可选维)摘要
|
||||
- `workers[]`:`ref` / `duty` / `rationale` / **`acceptance`(run 验收点,见 §7.3)**
|
||||
- `resident_context[]`:位置、内容摘要、挂载哪些 worker
|
||||
- `tables`:schema、显隐、维护规则、副作用(含 edge/once/stage)
|
||||
- `narrative_guide` / `core_premises`:叙事指南与核心实现前提
|
||||
- `input_protocol`:若需要
|
||||
- `design_end`:可选开局 · 开场白等备忘(非强制管道)
|
||||
|
||||
验收复述须含:站位;系统扮演/输出/交互;体验首要三维;有何 worker/表/副作用;**run 在哪些 worker 后停下来给人看**;为何没有某件;进 play 由用户决定。
|
||||
|
||||
交稿自检:题材词可替换仍成立;§3 清单通过;无持续为真每轮触发;格式程序化;字段级 rev 已设计;每个面向用户的可读终稿点有明确 `acceptance`。
|
||||
|
||||
---
|
||||
|
||||
## 7.1 强用户可读(前端约定)
|
||||
|
||||
创作期检查器须让用户看懂:
|
||||
|
||||
| 面板 | 内容 |
|
||||
|------|------|
|
||||
| **设计** | Worker 集卡片(含 run 验收点标注)、设计进度 |
|
||||
| **验收** | 当前产物结构化预览 + 接受/不接受(主确认点) |
|
||||
| **上下文** | 终产物 tag、活跃 tag、定稿摘要、已归档过程计数 |
|
||||
| **历史** | 调度时间线(可折叠) |
|
||||
|
||||
主 feed:隐藏过程调度;验收中的全文进验收 Tab;**创作对话 messages 全量保留供浏览**;拼给下一 design worker 的「创作.对话」才裁 AI(见 §7.2)。
|
||||
|
||||
---
|
||||
|
||||
## 7.2 创作与 Run:两套上下文(勿混)
|
||||
|
||||
| | 创作 `design` | Run / play |
|
||||
|--|---------------|------------|
|
||||
| 单位 | **创作单位**:一个 worker,或固定上下文族中的一块(交互范式 / 叙事指南 / 美学纲领 / …),同级 | 声明内的 run worker |
|
||||
| 会话 | 用户 ↔ 设计讨论进 **session `messages`(全量,用户可回看)**;**隐藏 AI 历史只作用于拼给模型的 transcript** | Worker **按契约从黑板重装**(`contextSegments` / `inputTags`) |
|
||||
| 验收后折叠 | **不删 messages**;产物在黑板/规格;同步「创作.对话」供下一单位 | 过程 tag 归档 + `上下文.定稿摘要`(`compress-after-worker.ts`) |
|
||||
| 禁止 | 一次收齐整份 worker 集 + 全部固定上下文;把纲领当成「顺便写的备注」 | 把创作闲聊塞进 worker prompt |
|
||||
|
||||
### 创作单位(粒度)
|
||||
|
||||
「固定上下文」= **族名**。族内某块 **只有谈出来并写入规格后** 才成为创作单位;§6.1 示例话题不是待填格子。
|
||||
|
||||
典型推进(非强制工序):
|
||||
|
||||
1. `phase:core`:站位 + 交互 + 核心体验
|
||||
2. 需要时:风格 / 叙事 / 美学等 **纲领类** 固定上下文先验收(`design-fixed`)
|
||||
3. 各 `worker:{ref}`:挂载已有 tag,勿复制纲领
|
||||
4. 其它 `resident:*` → 表 / 副作用 → 终稿 `设计.worker集`
|
||||
|
||||
单位跑只写 `设计.worker集.草稿`;终稿才写 `设计.worker集`(才 `designInstanceReady`)。
|
||||
|
||||
### 创作会话(简单)
|
||||
|
||||
```text
|
||||
本单位进行中:问答、复述、草稿讨论 → 照常 append 到 messages(用户浏览全量)
|
||||
拼给 AI:写入黑板 tag「创作.对话」= 用户全量 + 仅最后一次 AI 输出(含未完成提问)
|
||||
不需要:步内 policy、创作专用拼装栈改写 messages
|
||||
```
|
||||
|
||||
### 创作折叠(简单)
|
||||
|
||||
```text
|
||||
用户接受本单位产物
|
||||
→ session.messages 不删(用户可继续浏览历史)
|
||||
→ 产物本身在黑板/规格里;已验收内容写入「创作.已验收内容」
|
||||
→ 同步「创作.对话」transcript(仍按:用户全量 + AI 只留最后一次)
|
||||
→ 下一单位从「已定稿产物 + 新对话」继续
|
||||
```
|
||||
|
||||
不在创作期做「定稿摘要再注入下一 design LLM」的复杂链;产物本身在黑板/规格里,够用。
|
||||
「隐藏 AI 历史」= 仅 transcript 裁剪,**绝不**从主 feed 删掉或折叠创作过程消息。
|
||||
|
||||
### Run 压缩(与创作正交)
|
||||
|
||||
```text
|
||||
用户在 run 验收点接受产物
|
||||
→ 终产物 tag 标记 final
|
||||
→ 过程 tag(worker 答复、*.草稿 等)归档
|
||||
→ 写入 上下文.定稿摘要
|
||||
→ 下一 worker 按契约重装时带上摘要与终产物
|
||||
```
|
||||
|
||||
实现:`src/runtime/compress-after-worker.ts`。
|
||||
禁止把完整过程讨论继续喂给下一 **run** worker。
|
||||
|
||||
---
|
||||
|
||||
## 7.3 Run 验收点(创作时必须设计进 Worker 集)
|
||||
|
||||
**不是**「整条 run 有没有验收」的二选一,也不是创作阶段的特例。
|
||||
|
||||
**是:** 规定 **跑完哪个 worker 之后必须停下来让用户阅读/验收**;其余可连续调度。
|
||||
|
||||
| `workers[].acceptance` | 含义 |
|
||||
|------------------------|------|
|
||||
| `review` | 本 worker 完成后进入用户验收;接受后才压缩并允许续跑 |
|
||||
| `continue` | 完成后可接着调下一个,不打断用户 |
|
||||
|
||||
**正推示例(写入 rationale / 复述给人听):**
|
||||
|
||||
| 意图 | 典型停点 |
|
||||
|------|----------|
|
||||
| 自主长篇 | 大纲 / 主题 worker → `review`;正文链多段 → `continue`,直到用户叫停或大纲段落再 `review` |
|
||||
| 交互 / RP | 产出用户可见终稿的转述(如 narrator)→ `review`;世界裁决等中间层 → `continue` |
|
||||
|
||||
创作期按 **§7.2 创作单位** 分块验收(worker 与固定上下文同级),与上表正交。
|
||||
design-intake / 创作调度必须为 **每个** run worker 写出 `acceptance`;缺省时不得假设「全都不用验收」——面向用户的可读输出默认倾向 `review`,纯中间层倾向 `continue`,吃不准就 ask_user。
|
||||
|
||||
---
|
||||
|
||||
## 7.4 开局 · 开场白(opening-generator)
|
||||
|
||||
创作末尾、Worker 集已 accept 之后。此时 **世界设定、故事设定、表结构通常已经很详细**。
|
||||
|
||||
**主产物是开场白**(玩家迈进世界的第一段可读文本),不是填表。
|
||||
|
||||
```text
|
||||
结合前面的世界 + 故事 + 表结构 → 写开场白
|
||||
初值表 = 与开场同一真相的状态快照(辅)
|
||||
能从已有设定/用户话推出的字段 → 直接填并对齐开场,别问卷
|
||||
只有开场必须成立却推不出的点 → 轻量 ask_user
|
||||
```
|
||||
|
||||
例:已有校园末日设定 + 用户「普通大学生」→ 开场写宿舍/校园第一拍;表里年龄/资产/身份自然对齐;**不要**先发一张年龄资产问卷。
|
||||
|
||||
产出:`输出.开场白`(主)+ `运行.初始变量` / `变量.当前`(辅,字段格)。
|
||||
须用户验收。实现:`workers/opening-generator/SKILL.md`。
|
||||
|
||||
---
|
||||
|
||||
## 8. Play / Run 边界(给总管;运行时按声明执行)
|
||||
|
||||
- 只 `run_worker` 声明内的 ref
|
||||
- 遇 `acceptance: review` → 停、给人看、接受后压缩再继续
|
||||
- 遇 `acceptance: continue` → 可在 burst 内续调下一 worker(受 burst 上限约束)
|
||||
- 表维护 / 副作用可在终稿展示之后延后;写表须尊重字段 `rev` / `source=user`
|
||||
- 开局类 skill 仅创作末尾可选,禁止当 run 每轮技能
|
||||
@@ -16,28 +16,57 @@
|
||||
|
||||
## 2. 文档地图
|
||||
|
||||
| 文档 | 管什么 |
|
||||
|---|---|
|
||||
| `tag-blackboard.md` | **★ 主规格**:标签黑板、skill 分工 |
|
||||
| `context-assembly.md` | **★ 上下文拼接**:上半固定、下半动态 |
|
||||
| `architecture.md` | 总览、agent tool loop、模块边界 |
|
||||
| `runtime-state-machine.md` | 5 相位、tool 边界、burst |
|
||||
| `tool-contracts.md` | 总管 / worker tool |
|
||||
| `orchestrator-skill-format.md` | manifest 写法(非编排表) |
|
||||
| `worker-skill-format.md` | SKILL.md、contextSegments |
|
||||
| `skill-format.md` | 包存储、registry |
|
||||
| `skill-design-guide.md` | 新包设计方法 |
|
||||
| `creation-playbook.md` | 创作流程概念 |
|
||||
| `book-storage.md` | Book、CardAsset、PlayBook、过程存储 |
|
||||
| `run-snapshot.md` | 手动存档 |
|
||||
| `preset-format.md` | 预设导入 |
|
||||
| `implementation-guide.md` | 本文件:文件顺序与职责 |
|
||||
| 层级 | 文档 | 管什么 |
|
||||
|---|---|---|
|
||||
| **系统架构** | **`architecture.md`** | 模块边界、定义/实例、调度、上下文、持久化原则、风险 |
|
||||
| 运行内核 | `runtime-state-machine.md` | 5 相位、tool 边界、burst |
|
||||
| 运行内核 | `tool-contracts.md` | 总管 / worker tool |
|
||||
| 运行内核 | `context-assembly.md` | 上下文拼接:上半固定、下半动态 |
|
||||
| 运行内核 | `tag-blackboard.md` | 标签黑板 |
|
||||
| 持久化 | `book-storage.md` | Book、CardAsset、PlayBook、过程存储 |
|
||||
| 持久化 | `run-snapshot.md` | 手动存档 |
|
||||
| UI | `ui-design.md` | 工作台布局、检查器、composer |
|
||||
| UI | **`ui-glossary.md`** | 用户可见中文口径;禁止裸露内部 id |
|
||||
| 外部参考 | `references.md` | 按层借鉴的 GitHub 仓库 |
|
||||
| 本文件 | `implementation-guide.md` | 文件顺序与职责;在现有内核上补齐 |
|
||||
| **产品体验路线** | **`px-roadmap.md`** | PX0–PX5 交付、DoD、明确不做 |
|
||||
| **日常 → P0** | **`daily-use-p0.md`** | 日常场景、功能映射、P0 详细工作包 |
|
||||
| **剧本 / 方法(非系统架构)** | `design-orchestrator-guide.md` | 创作三大步、表/副作用、自检 |
|
||||
| **剧本 / 方法(非系统架构)** | `creation-playbook.md` | 创作流程概念(指向指导) |
|
||||
| **Skill 格式(非系统架构)** | `orchestrator-skill-format.md` | manifest 写法(非编排表) |
|
||||
| **Skill 格式(非系统架构)** | `worker-skill-format.md` | SKILL.md、contextSegments |
|
||||
| **Skill 格式(非系统架构)** | `skill-format.md` | 包存储、registry |
|
||||
| **Skill 格式(非系统架构)** | `skill-design-guide.md` | 包格式薄层 |
|
||||
| **Skill 格式(非系统架构)** | `preset-format.md` | 预设导入 |
|
||||
|
||||
许可证占位:仓库根 `THIRD_PARTY_NOTICES.md`。
|
||||
|
||||
---
|
||||
|
||||
## 3. 实现分期
|
||||
## 3. 在现有内核上补齐(当前优先)
|
||||
|
||||
### Phase A0 — 可运行阶段机(当前优先)
|
||||
相位机、Agent tool loop、Skill loader、Worker 声明、上下文拼装、表 rev、Web UI、快照 API **已具备**。
|
||||
**不要**按绿场「领域库 → Mastra → …」重开;增量挂在现有 `phase-runtime` / Book 存储上。系统边界见 `architecture.md` §11。
|
||||
|
||||
**权威执行顺序与验收 DoD → [`px-roadmap.md`](./px-roadmap.md)**(当前焦点 **PX0**)。
|
||||
**日常场景与 P0 工作包 → [`daily-use-p0.md`](./daily-use-p0.md)**。
|
||||
|
||||
| 路线阶段 | 目标 | 备注 |
|
||||
|----------|------|------|
|
||||
| **PX0(当前)** | 导演 UI + 双线主路径 + 存档硬稳定 | 详见 `daily-use-p0.md` / `px-roadmap.md` |
|
||||
| PX1–PX2 | 创作/游玩体验打磨;副作用 | UI 仍可抄 |
|
||||
| PX3 | Trace、预算、规格版本、E2E | — |
|
||||
| PX4 | 长文加深(P0 已含最小闭环) | — |
|
||||
| PX5 | 隔离 + 调试;必要时新导演包 | — |
|
||||
| 延后 | SQLite / 事件溯源 / Mastra·Next | — |
|
||||
|
||||
历史分期 A0–E 见下节(多数已 done,作文件职责索引,不再当作「当前从零启动」路线)。
|
||||
|
||||
---
|
||||
|
||||
## 3b. 历史实现分期(索引)
|
||||
|
||||
### Phase A0 — 可运行阶段机
|
||||
|
||||
目标:**不依赖 LLM**,阶段机整体可运行、可手动驱动、可脚本跑通最小闭环。
|
||||
|
||||
|
||||
@@ -1,37 +1,41 @@
|
||||
# 总管 Skill 格式(Orchestrator / Manifest)
|
||||
|
||||
> **文档层级:Skill 包 manifest 格式(非系统架构)。**
|
||||
> 总管与相位机边界见 [`architecture.md`](./architecture.md)、[`runtime-state-machine.md`](./runtime-state-machine.md)。
|
||||
|
||||
## 1. 定位
|
||||
|
||||
**orchestrator.md** = 本包的 **manifest**:注册有哪些 skill、如何验收、如何声明 instance ready。
|
||||
**不**写逐步流水线剧本;**不**写「总管思维链」逐步调度。
|
||||
**orchestrator.md** = 本包的 **manifest**:注册有哪些 capability、如何验收、何时可进 play。
|
||||
**不**写逐步流水线剧本。
|
||||
**创作方法**见 **`design-orchestrator-guide.md`**(三大步、表、自检)。
|
||||
|
||||
```text
|
||||
用户选包
|
||||
→ design:agent 按需 invoke instantiate skill
|
||||
→ declare ready → play:agent invoke run skill
|
||||
→ done:归档 Book
|
||||
新建作品 → 默认包
|
||||
→ design:design-intake → 设计.worker集 JSON(实例声明)
|
||||
→ 用户验收 → 用户手动进 play
|
||||
→ play:Agent 按 Worker 声明 invoke worker
|
||||
```
|
||||
|
||||
| 谁决定 | 什么 |
|
||||
|--------|------|
|
||||
| **Agent** | 何时 invoke 哪个 skill(tool loop) |
|
||||
| **Manifest** | 可用 skill 列表、验收策略、readiness 规则 |
|
||||
| **Worker SKILL.md** | inputTags、contextSegments、怎么做 |
|
||||
| **Agent** | 何时 invoke 哪个 worker id(tool loop) |
|
||||
| **Manifest** | design worker 列表、验收策略、readiness |
|
||||
| **Worker 集** | 本实例启哪些 worker、表、常驻上下文、副作用 |
|
||||
| **templates** | design-intake 合并用的可选默认契约(非运行时权威) |
|
||||
|
||||
`shared-context.md`:包级 **固定上下文上半**,注入各 worker prompt。总管不读。
|
||||
|
||||
见 `architecture.md`、`skill-design-guide.md`、`context-assembly.md`。
|
||||
见 `architecture.md`、`creation-playbook.md`、`design-orchestrator-guide.md`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 存储位置
|
||||
|
||||
```text
|
||||
skills/novel/weird-rules-short/
|
||||
skills/dialogue/world-simulator/
|
||||
├── orchestrator.md
|
||||
├── shared-context.md # 可选
|
||||
├── worker-templates/ # 可选 ref 模板(design 缺省)
|
||||
└── workers/
|
||||
└── write-rules/SKILL.md
|
||||
└── design-intake/SKILL.md
|
||||
```
|
||||
|
||||
- 文件固定名 **`orchestrator.md`**。
|
||||
@@ -43,142 +47,74 @@ skills/novel/weird-rules-short/
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: weird-rules-short
|
||||
description: >-
|
||||
何时选用:用户要写规则怪谈、守则类短篇。
|
||||
category: novel
|
||||
bookKind: novel
|
||||
name: world-simulator
|
||||
description: >
|
||||
默认能力库:Worker 集即实例声明…
|
||||
category: dialogue
|
||||
bookKind: dialogue
|
||||
workers:
|
||||
- write-rules
|
||||
- review-infer
|
||||
- review-author
|
||||
sharedContext: shared-context.md
|
||||
- design-intake
|
||||
demandTag: 用户.需求
|
||||
startupMode: agent-first
|
||||
uiPrompt: |
|
||||
…
|
||||
---
|
||||
```
|
||||
|
||||
| 字段 | 用途 |
|
||||
|------|------|
|
||||
| `description` | 何时选本包 |
|
||||
| `workers` | run / design 可调度 skill id 白名单(loader 用) |
|
||||
| `bookKind` | Book 存储形态 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 正文章节(推荐)
|
||||
|
||||
```markdown
|
||||
# 标题
|
||||
|
||||
## 启动询问 # 最小 intake
|
||||
## Skill 注册表 # instantiate + run skill 清单与说明(见 §5)
|
||||
## 验收策略 # 哪些 skill 产出需 user_confirmed / approve
|
||||
## Instance Ready # 按 run_skill清单 的动态最低可行性
|
||||
## contextProfile # 可选 variant 说明(见 context-assembly.md)
|
||||
## 用户回合 # user-turn 等(可选)
|
||||
## 禁用行为
|
||||
```
|
||||
|
||||
**不应出现:**
|
||||
|
||||
- 逐步 **Worker 编排表** / **总管思维链**(已废弃为主流程)
|
||||
- `inputTags` / `outputTags` 列表 → 在 `workers/*/SKILL.md`
|
||||
- 写作细则 → Worker SKILL 正文
|
||||
|
||||
---
|
||||
|
||||
## 5. Skill 注册表(替代编排表)
|
||||
|
||||
列出本包 **能力库**,供 agent `list_workers` 与 manifest 校验:
|
||||
## 4. Manifest 正文应有的节
|
||||
|
||||
```markdown
|
||||
## Skill 注册表
|
||||
|
||||
### Instantiate(design stage)
|
||||
|
||||
| id | 说明 | 典型 outputTags |
|
||||
|----|------|-----------------|
|
||||
| interaction-paradigm | 定交互范式与 run_skill清单 | 设计.run_skill清单 |
|
||||
| world-blueprint | 世界背景 | 设计.世界.蓝图 |
|
||||
| persona-draft | 角色卡草稿 | 角色卡.草稿 |
|
||||
|
||||
### Run(play stage)
|
||||
|
||||
| id | 说明 | 默认 acceptance |
|
||||
|----|------|-----------------|
|
||||
| write-rules | 写规则 | user_confirmed |
|
||||
| review-infer | 读者视角验收 | programmatic_review |
|
||||
## Instance Ready / 进入游玩
|
||||
## 验收策略
|
||||
## 总管优先行为
|
||||
## 禁用行为
|
||||
```
|
||||
|
||||
agent **按需 invoke**,不必按表顺序跑全。
|
||||
**不应包含:** 「第 N 步必须跑某 worker」管道剧本。
|
||||
方法细节指向 `design-orchestrator-guide.md`,勿在 manifest 重复长文。
|
||||
|
||||
---
|
||||
|
||||
## 6. 启动询问与 Instance Ready
|
||||
## 5. Skill 注册表
|
||||
|
||||
**启动询问:** 只收集 agent 无法从空推断的最小信息 → `用户.需求` 等。
|
||||
### Design
|
||||
|
||||
**Instance Ready:** 不写死「14 步全完成」,而写:
|
||||
| id | 说明 |
|
||||
|----|------|
|
||||
| design-intake | 产出 设计.worker集 JSON |
|
||||
| (可选)开局赋初值 | 创作末尾可选,非每轮 |
|
||||
|
||||
### Play
|
||||
|
||||
不在 manifest 写死清单。Agent 只调度 **`设计.worker集` 声明内的 ref**。
|
||||
|
||||
---
|
||||
|
||||
## 6. Instance Ready
|
||||
|
||||
```markdown
|
||||
## Instance Ready
|
||||
|
||||
当 `设计.run_skill清单` 已 accepted,且清单中每个 run skill 的
|
||||
**最低 input 要求**(见各 SKILL.md)已在黑板存在或已记录跳过理由。
|
||||
由 agent `declare_instance_ready` + Runtime 校验。
|
||||
当 `设计.worker集` 已 accepted;进 play 由用户手动决定。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 验收策略
|
||||
|
||||
```markdown
|
||||
## 验收策略
|
||||
|
||||
| skill | requiresApproval | acceptanceMode |
|
||||
|-------|------------------|----------------|
|
||||
| setup-scenario | true | user_confirmed |
|
||||
| world-engine | false | no_confirmation |
|
||||
| present-round | false | user_confirmed |
|
||||
```
|
||||
| design-intake | true | user_confirmed |
|
||||
|
||||
agent 通过 `run_worker(..., requiresApproval)` 触发;默认值也可写在 manifest 供 Runtime 填充。
|
||||
play:**无**每轮强制验收;用户新输入 = 认可上轮终稿;重 roll 替代 reject。
|
||||
|
||||
---
|
||||
|
||||
## 8. contextProfile
|
||||
|
||||
若同一 skill 有多种游玩/创作模式,在 manifest 列出 variant 名与含义;实例化写入 Book。
|
||||
格式见 `context-assembly.md`。
|
||||
|
||||
---
|
||||
|
||||
## 9. 与 Worker SKILL 的关系
|
||||
## 8. 与 Worker 声明的关系
|
||||
|
||||
```text
|
||||
orchestrator.md 注册 + 验收 + readiness
|
||||
workers/*/SKILL.md 能力 + 上下文契约(contextSegments)
|
||||
accept 设计.worker集 → Runtime 构建 InstanceWorkerDeclaration
|
||||
play:仅允许声明内 activeWorkerIds
|
||||
表维护 / 副作用按规格边沿触发(见 design-orchestrator-guide §5)
|
||||
```
|
||||
|
||||
Prompt 注入:
|
||||
|
||||
```text
|
||||
Agent:session 摘要 + tool;可用 read_blackboard
|
||||
Worker:shared-context + SKILL + assembleWorkerContext(上固定下动态)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 迁移说明
|
||||
|
||||
旧包中的 `## Worker 编排`、`## 总管思维链` 仍可作为 **人工参考**,但新包与 world-simulator **不应**以此为主流程。
|
||||
Runtime 目标态以 agent tool loop + manifest 注册表为准。
|
||||
|
||||
---
|
||||
|
||||
## 11. 相关文档
|
||||
|
||||
| 文档 | 关系 |
|
||||
|------|------|
|
||||
| `skill-design-guide.md` | 设计方法 |
|
||||
| `worker-skill-format.md` | SKILL.md 字段 |
|
||||
| `creation-playbook.md` | 概念 |
|
||||
|
||||
195
docs/px-roadmap.md
Normal file
195
docs/px-roadmap.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# PX 路线图(产品体验与交付)
|
||||
|
||||
## 0. 定位
|
||||
|
||||
当前仓库的**执行计划表**。系统边界见 [`architecture.md`](./architecture.md);日常场景与 P0 工作包见 [`daily-use-p0.md`](./daily-use-p0.md);文件级索引见 [`implementation-guide.md`](./implementation-guide.md)。
|
||||
|
||||
**不**绿场重写、**不**近端迁 Mastra/Next/SQLite。挂在现有相位机 + Book/快照 + 声明驱动 Worker 上增量交付。
|
||||
|
||||
### 0.1 已锁定产品取向
|
||||
|
||||
| 项 | 决定 |
|
||||
|----|------|
|
||||
| 日常玩法 | **AIRP/交互** + **长文/爽文**(同一内核) |
|
||||
| 导演 Skill | 新建时 **UI 选择**能力包;初期可只有 1 项(默认选中) |
|
||||
| 剧本层 | **动态**:听用户说话谈 Worker 集 + tool loop;不做死板剧本选单/DAG |
|
||||
| UI | **抄参考**即可,不自研美学 |
|
||||
| 硬指标 | **存档 / 续作稳定** |
|
||||
| 多导演 | 仅当方法边界真不同时再加包;爽文≠新导演 |
|
||||
|
||||
### 0.2 成功标尺
|
||||
|
||||
| 级别 | 含义 |
|
||||
|------|------|
|
||||
| **L1 可演示** | 带路能跑通一轮 |
|
||||
| **L2 可自助** | 选导演 → 创作 → 玩/写,少踩坑 |
|
||||
| **L3 可日常** | 双线存档续作可靠;少死循环 |
|
||||
| **L4 可扩展** | 新导演包或隔离模式不改内核边界 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 总计划表(主表)
|
||||
|
||||
| 阶段 | 名称 | 目标标尺 | 核心交付 | 状态 |
|
||||
|------|------|----------|----------|------|
|
||||
| **PX0** | 双线可日常最小集 | L2→摸 L3 | 导演选择 UI;线 A 扮演 + 线 B 长文/爽文主路径;**存档硬稳定**;失败出口;UI 只抄缺口 | **进行中(核心已落地)** |
|
||||
| **PX1** | 创作体验 | L3 design | 规格可读可改、进度可见、表/黑板可手改、instance 改规格 | **进行中(定稿存读+黑板改+进度面板已有)** |
|
||||
| **PX2** | 游玩/写作体验 | L3 play | 阅读主舞台(抄)、状态抽屉、重 roll 顺手、副作用调度、多 run 线打磨 | 未开始 |
|
||||
| **PX3** | 运行可信 | L3 加固 | Context Trace、调度预算、规格钉版本、mock E2E | 未开始 |
|
||||
| **PX4** | 长文加深 | L4 | 章产物/多卷、局部评测、更好的长文阅读面(P0 已含最小长文) | 未开始 |
|
||||
| **PX5** | 隔离 + 调试 | L4 | 私有上下文、泄漏测试;Trace/成本/分支调试台 | 未开始 |
|
||||
|
||||
```text
|
||||
现在 → PX0(存档+双线+导演UI)
|
||||
→ PX1(创作好改)
|
||||
→ PX2(玩/写好用)
|
||||
→ PX3(可信可测)
|
||||
→ PX4 / PX5(按需,可调序)
|
||||
```
|
||||
|
||||
顺序原则:先 **PX0 双线硬路径**,再体验(1–2),再加固(3),再扩展(4–5)。
|
||||
若 PX0–2 出现腐档/死循环,允许提前插入 PX3 对应子项。
|
||||
|
||||
预估(单人全职约):PX0 1.5–2 周;PX1/PX2 各 2–3 周;PX3 2–3 周;PX4/PX5 各 3–5 周。
|
||||
|
||||
---
|
||||
|
||||
## 2. PX0 — 双线可日常最小集(当前)
|
||||
|
||||
**权威细项**:[`daily-use-p0.md`](./daily-use-p0.md) §3。
|
||||
|
||||
### 2.1 计划表(工作包)
|
||||
|
||||
| 序 | 工作包 | 内容 | 验收要点 |
|
||||
|----|--------|------|----------|
|
||||
| 1 | **WP-S** 存档稳定 | 续作相位正确;手动存/列表/加载;正文与关键 tag 不丢 | 线 A、B 各存读一轮,不手改磁盘 |
|
||||
| 2 | **WP-D** 导演选择 | 新建作品 UI 选导演;registry 列表;仅 1 项时默认选中 | 能选中并进入该包创作 |
|
||||
| 3 | **WP-A** 进出与失败 | glossary 底线;`waiting_user` 有主按钮;LLM/worker 失败可重试 | 无死胡同、无裸 id 挡路 |
|
||||
| 4 | **WP-R** 线 A | 扮演 ≥3 回合不卡 | A-GP1–5 |
|
||||
| 5 | **WP-L** 线 B | 引导+template 能谈成写手规格;≥2 段正文;续作不丢文 | B-GP1–5 |
|
||||
| 6 | **WP-U** UI 抄补 | 仅补存档/确认/选导演等缺口控件 | 不按 ui-design 大重构 |
|
||||
| 7 | **WP-E** 闸门 | 双线冷启动+续作验收;勾选 DoD | 全绿才出 PX0 |
|
||||
|
||||
### 2.2 黄金路径(摘要)
|
||||
|
||||
- **线 A**:选导演 → 扮演意图 → 验收 → ≥3 回合 → 存档 → 续作/读档
|
||||
- **线 B**:选导演 → 爽文/长篇意图 → 验收写手规格 → ≥2 段正文 → 存档含正文 → 续作/读档
|
||||
|
||||
### 2.3 非范围
|
||||
|
||||
死板剧本选单;自研精美 UI;Trace;隔离;完整副作用;多导演商店(列表可扩展,P0 不追求多包运营)。
|
||||
|
||||
### 2.4 DoD
|
||||
|
||||
- [ ] WP-S~E 完成
|
||||
- [ ] 线 A、线 B 黄金路径各冷启动 + 续作通过
|
||||
- [ ] 导演选择 UI 可用(可仅一项)
|
||||
- [ ] 更新本文状态列 + `architecture.md` §13
|
||||
|
||||
### 2.5 挂载
|
||||
|
||||
`web/`(新建选导演、saves)、`session-manager`、`skill-catalog` / registry、`phase-runtime`、引导与 `worker-templates`
|
||||
|
||||
---
|
||||
|
||||
## 3. PX1 — 创作体验
|
||||
|
||||
| 项 | 内容 | DoD |
|
||||
|----|------|-----|
|
||||
| 规格工作台 | 产物为主舞台(可抄 Notion/PR 式验收) | 不看文档能验完 Worker 集进 play |
|
||||
| 进度 | 创作单位已验收/进行中可见 | — |
|
||||
| 表 | 字段可编辑 + rev 不覆盖手改 | 手改后 worker 写入可解释 |
|
||||
| 规格回退 | load instance 再改 | 至少一条路径可用 |
|
||||
|
||||
挂载:`worker-set-view`、`creation-units`、questions UI、表编辑 API。
|
||||
|
||||
---
|
||||
|
||||
## 4. PX2 — 游玩 / 写作体验
|
||||
|
||||
| 项 | 内容 | DoD |
|
||||
|----|------|-----|
|
||||
| 阅读面 | 终稿/章文主视线(抄 ST/阅读器) | ≥10 回合或等价连续写段不卡 |
|
||||
| 重 roll | 发现入口;与快照语义一致 | 存/读/重 roll 各至少一次 |
|
||||
| 状态 | 抽屉看表/变量 | — |
|
||||
| 副作用 | `side_effects` 边沿完整可观测 | fixture:once / changed |
|
||||
| 多线 | 同开局新开 play 线 | 不串 run 状态 |
|
||||
|
||||
挂载:`table-side-effects`、`phase-runtime`、play UI、`run-snapshot`。
|
||||
|
||||
---
|
||||
|
||||
## 5. PX3 — 运行可信
|
||||
|
||||
| 子项 | DoD |
|
||||
|------|-----|
|
||||
| Context Trace | 失败调用能看出装载了什么 |
|
||||
| 调度预算 | 重复/嵌套/Token 触顶可读停止 |
|
||||
| 规格钉版本 | 改规格不污染未迁移旧 run |
|
||||
| mock E2E | 双线+存档脚本可进 CI |
|
||||
|
||||
---
|
||||
|
||||
## 6. PX4 — 长文加深(P0 之后)
|
||||
|
||||
P0 已要求最小长文/爽文闭环。本阶段加深:
|
||||
|
||||
- 章/卷 Artifact 更清晰;多段目录
|
||||
- 简单质量检查(程序优先)
|
||||
- 更好的长文阅读/对照面(仍可抄,可不上 Monaco)
|
||||
|
||||
**不做**:固定小说 DAG;为长文另起调度内核。
|
||||
|
||||
---
|
||||
|
||||
## 7. PX5 — 隔离模拟 + 专业调试
|
||||
|
||||
| 块 | 内容 |
|
||||
|----|------|
|
||||
| 隔离 | 私有上下文、公私事件、泄漏测试(依 Trace) |
|
||||
| 调试 | 调度时间线、Trace 检查器、Token/成本、分支树 |
|
||||
|
||||
若隔离需要**不同方法边界**,以**新导演包**形式加入(UI 多一项),仍保持包内剧本动态。
|
||||
|
||||
---
|
||||
|
||||
## 8. 明确不做(周期内)
|
||||
|
||||
- Mastra / LangGraph 换相位机;Next 重写前端
|
||||
- 完整事件溯源 + SQLite 近端必达
|
||||
- 题材固定 instantiate 管道;死板剧本选单
|
||||
- 把创作方法长文塞回系统架构文
|
||||
|
||||
---
|
||||
|
||||
## 9. 里程碑
|
||||
|
||||
| 里程碑 | 退出 | 产出 |
|
||||
|--------|------|------|
|
||||
| **M0** | PX0 DoD | 双线验收记录;导演 UI;存档稳定 |
|
||||
| **M1** | PX1+PX2 DoD | 内部「可日常」说明 |
|
||||
| **M2** | PX3 DoD | Trace/预算/E2E 绿灯 |
|
||||
| **M3** | PX4 或 PX5 | 长文加深或隔离/调试演示 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 文档 / 代码映射
|
||||
|
||||
| 阶段 | 文档 | 代码 |
|
||||
|------|------|------|
|
||||
| PX0 | `daily-use-p0.md`、`ui-glossary.md` | `web/`、session、saves、catalog、templates |
|
||||
| PX1 | `ui-design.md`(可抄着落地)、design-guide | worker-set、creation-units |
|
||||
| PX2 | `run-snapshot.md`、tag-blackboard | side-effects、phase-runtime |
|
||||
| PX3 | `context-assembly.md`、architecture | assemble、budgets、tests |
|
||||
| PX4 | Skill 薄层 | artifacts、长文面 |
|
||||
| PX5 | 隔离短文(待写) | Trace 强制、debug、stats |
|
||||
|
||||
---
|
||||
|
||||
## 11. 下一行动(立即)
|
||||
|
||||
1. **手工跑双线黄金路径**(线 A ≥3 回合;线 B ≥2 段;存读档)验收 PX0 DoD
|
||||
2. 表字段格级编辑器(当前是黑板 tag 预览级手改)可继续打磨
|
||||
3. 再开 PX2(阅读面 / 副作用 / 多 run)
|
||||
|
||||
**当前**:PX0/PX1 主代码已落地,待你本地用真实 LLM 走一遍确认「能用」。
|
||||
88
docs/references.md
Normal file
88
docs/references.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# 外部项目参考(按层借鉴)
|
||||
|
||||
以下仓库用于架构与实现参考。**只借鉴标明的一层,不把外部项目整体耦合进本仓库实现。**
|
||||
|
||||
本系统运行内核为自研(相位机 + tool loop + 声明驱动 Worker)。Mastra、Next.js、assistant-ui 等**不是**近端迁移目标;若引入,须经适配层,且领域对象不得保存框架内部类型。
|
||||
|
||||
许可证与再分发注意见仓库根 [`THIRD_PARTY_NOTICES.md`](../THIRD_PARTY_NOTICES.md)。
|
||||
|
||||
---
|
||||
|
||||
## 参考原则
|
||||
|
||||
1. 按模块读代码与文档,禁止整仓复制粘贴为业务内核。
|
||||
2. 借鉴交互/产品概念时,数据模型仍用本仓库 Book / Session / 黑板。
|
||||
3. Prompt 拼接、权限、上下文编译由本仓库 Context Compiler 实现,不照搬他站拼接逻辑。
|
||||
4. 依赖使用 lockfile 精确版本;对外部项目的修改放在适配层。
|
||||
|
||||
---
|
||||
|
||||
## 仓库与借鉴层
|
||||
|
||||
### Mastra
|
||||
|
||||
- 仓库:<https://github.com/mastra-ai/mastra>
|
||||
- **可借鉴**:Agent/Tool 声明方式、模型 Provider、结构化输出、Memory/Storage 接口形态、Workflow 适用边界、Observability、TypeScript 组织
|
||||
- **不借鉴为内核**:不把动态调度、上下文权限、Definition/Instance、事件存档交给 Mastra 领域模型;本仓库已有 phase-runtime
|
||||
|
||||
### assistant-ui
|
||||
|
||||
- 仓库:<https://github.com/assistant-ui/assistant-ui>
|
||||
- **可借鉴**:流式聊天、消息组件、Tool 调用展示、会话状态、人工确认、前后端连接方式
|
||||
- **不负责**:Definition、存档、多 Agent 调度模型(仍由本仓库 API + Runtime)
|
||||
|
||||
### LobeChat
|
||||
|
||||
- 仓库:<https://github.com/lobehub/lobe-chat>
|
||||
- **可借鉴**:本地 AI 应用信息架构、会话列表、模型配置 UI、助手/角色配置、插件与知识库管理体验、布局
|
||||
- **不直接采用**:其会话数据模型
|
||||
|
||||
### SillyTavern
|
||||
|
||||
- 仓库:<https://github.com/SillyTavern/SillyTavern>
|
||||
- **可借鉴**:角色卡产品概念、世界书编辑体验、聊天存档与分支心流、角色扮演工作方式、导入导出与兼容性概念
|
||||
- **不照搬**:Prompt 拼接逻辑;世界书检索与权限由 Context Compiler 实现
|
||||
|
||||
### LangGraph.js
|
||||
|
||||
- 仓库:<https://github.com/langchain-ai/langgraphjs>
|
||||
- **可借鉴**:有状态 Runtime、Checkpoint、中断与恢复、Human-in-the-loop、长时间任务可恢复执行
|
||||
- **不采用**:用固定 Graph 表达整场创作流程(本仓库用相位机 + 动态 tool loop)
|
||||
|
||||
### AutoGen
|
||||
|
||||
- 仓库:<https://github.com/microsoft/autogen>
|
||||
- **可借鉴**:多 Agent 消息与生命周期、Agent 选择、事件驱动运行、Tool 执行边界、Runtime 抽象
|
||||
- **不采用**:Agent 间自由转发消息作为权限模型;上下文不得依赖自由转发
|
||||
|
||||
### Letta
|
||||
|
||||
- 仓库:<https://github.com/letta-ai/letta>
|
||||
- **可借鉴**:长期记忆分层、上下文窗口管理、状态与消息历史区分
|
||||
- **仍需自建**:小说、世界书、角色私有状态等领域模型
|
||||
|
||||
### OpenHands
|
||||
|
||||
- 仓库:<https://github.com/All-Hands-AI/OpenHands>
|
||||
- **可借鉴**:运行会话、事件流、Action/Observation、前后端状态同步、暂停恢复与可观测性
|
||||
- **不参考**:软件工程沙箱任务模型
|
||||
|
||||
### CopilotKit
|
||||
|
||||
- 仓库:<https://github.com/CopilotKit/CopilotKit>
|
||||
- **可借鉴**:Agent 与前端状态连接、流式事件、Human-in-the-loop、生成式 UI、动态表与确认交互呈现
|
||||
- **不替代**:本仓库 Book/黑板领域模型
|
||||
|
||||
---
|
||||
|
||||
## 与本仓库模块的映射
|
||||
|
||||
| 本仓库模块 | 主要可参考项目 |
|
||||
|------------|----------------|
|
||||
| Runtime / 调度 / 恢复 | LangGraph.js、AutoGen、OpenHands、Mastra(边界) |
|
||||
| 上下文与记忆 | Letta、Mastra Memory 接口形态 |
|
||||
| 聊天与 HITL UI | assistant-ui、CopilotKit、LobeChat |
|
||||
| 角色卡 / 世界书 / 存档体验 | SillyTavern、LobeChat |
|
||||
| 模型接入与可观测 | Mastra |
|
||||
|
||||
系统架构总览见 [`architecture.md`](./architecture.md)。
|
||||
@@ -2,52 +2,92 @@
|
||||
|
||||
用户手动保存、多档位;与自动续作 `session.json` 分离。
|
||||
|
||||
## 两种 kind(更新语义)
|
||||
**编辑 / 重 roll 的统一做法:恢复到之前的快照,再从该点继续。**
|
||||
创作(design)与运行(play)共用此语义。
|
||||
|
||||
```text
|
||||
存快照 → 继续生成 → 不满意 → 加载 earlier 快照 → 重跑后续 worker / 重出展示
|
||||
```
|
||||
|
||||
## 三种 kind(更新语义)
|
||||
|
||||
| kind | 名称 | 何时存 | 存什么 |
|
||||
|------|------|--------|--------|
|
||||
| **`instance`** | 设计完成 / 资产截面 | design 完成或从 play 剥离设定 | **CardAsset 级** tag(角色卡.确认稿、世界蓝图…);**不含**轮次、事件流 |
|
||||
| **`run`** | 游玩存档 | play 任意时刻 | instance 层 + `运行.*`、轮次、变量、对话 |
|
||||
| **`instance`** | 创作定稿截面 | Worker 集 accept;可选含 **锁定开局** | `设计.worker集`、静态设定 tag、`运行.初始变量`、`输出.开场白`;**不含**后续轮次、事件流 |
|
||||
| **`opening`** | 开局锁定(可选单独存) | 创作末尾 swipe 选定开场后 | 与 instance 中开局部分相同;便于「同 Worker 集、换开局再开 play 线」 |
|
||||
| **`run`** | 游玩存档 | play 任意时刻 | instance/opening 层 + `运行.*`、轮次、变量当前值、对话 |
|
||||
|
||||
```text
|
||||
CardBook design 完成 → 可存 instance(导出角色卡 / 世界设定)
|
||||
PlayBook 进行中 → 可存 run(第 N 轮续玩)
|
||||
Worker 集 accept → 可存 instance(规格截面)
|
||||
可选开局赋初值 → 可存 instance / opening(非进 play 强制门槛)
|
||||
play 任意时刻 → 可存 多条 run
|
||||
改 Worker 集 → load 旧 instance 再改
|
||||
改某轮输出 → load 该轮前的 run → 重 roll / 重跑
|
||||
```
|
||||
|
||||
**instance** 不再表示「14 步 pipeline 填完」,而是 **agent declare ready 时的可复用资产截面**。详见 `book-storage.md`。
|
||||
**instance** = **Worker 集(+ 可选开局截面)**。进 play 由用户手动决定。见 `book-storage.md`、`design-orchestrator-guide.md`。
|
||||
|
||||
### 同一开局 · 多条存档
|
||||
|
||||
```text
|
||||
opening-generator 锁定 checkpoint = 共用起点
|
||||
├─ run 存档 A(第 5 轮)
|
||||
├─ run 存档 B(第 12 轮,另一分支)
|
||||
└─ 「从同一开局新开一条线」= 清空 run 层、保留 opening/instance 层(见 session-manager startNewPlayLine)
|
||||
```
|
||||
|
||||
多条 **run** 快照共享同一 **opening**(或含 opening 的 **instance**),无需为每条 play 线复制 Worker 集。
|
||||
|
||||
## Swipe 与快照
|
||||
|
||||
创作末尾 **开局** 产出时,可用 message **branch / swipe** 多版本对比(可选)。
|
||||
play 对终稿可用 **重 roll**;用户新输入 = 认可上轮终稿。见 `design-orchestrator-guide.md`。
|
||||
|
||||
## 与三种存储需求
|
||||
|
||||
| 需求 | 用什么 |
|
||||
|------|--------|
|
||||
| 创建角色卡**过程** | CardBook `designTrace` + messages(快照不替代,须 Book 级) |
|
||||
| 创建好的**卡** | CardAsset 或 `instance` 快照 |
|
||||
| 游玩**过程** | PlayBook `playTrace` + `run` 快照 |
|
||||
| 创作过程 | Book `designTrace` + messages |
|
||||
| 实例规格 + 锁定开局 | `instance` / `opening` 快照 |
|
||||
| 游玩过程 | PlayBook `playTrace` + **`run` 快照(同一开局可多条)** |
|
||||
| 改设定 / 重 roll | 加载对应 kind 的 earlier 快照 |
|
||||
|
||||
## 与自动续作
|
||||
|
||||
| | `session.json` | `run-snapshots/` |
|
||||
|--|----------------|------------------|
|
||||
| 触发 | 自动 | 用户手动 |
|
||||
| 触发 | 自动 | 用户手动(关键节点建议提示存档) |
|
||||
| 数量 | 每 Book 1 份 | 多档、可命名 |
|
||||
| 打开作品 | 默认恢复 | 选档 **加载** |
|
||||
| 编辑 | — | **load = 恢复到该档状态** |
|
||||
|
||||
## API
|
||||
|
||||
```text
|
||||
GET /api/books/:bookId/saves
|
||||
POST /api/books/:bookId/saves { label, kind: "instance"|"run", note?, sessionId? }
|
||||
POST /api/books/:bookId/saves { label, kind: "instance"|"opening"|"run", note?, sessionId? }
|
||||
POST /api/books/:bookId/saves/:id/load
|
||||
DELETE /api/books/:bookId/saves/:id
|
||||
```
|
||||
|
||||
(`opening` kind 可在实现中与 `instance` 合并存储,文档层区分语义即可。)
|
||||
|
||||
## 实现
|
||||
|
||||
```text
|
||||
src/types/run-snapshot.ts
|
||||
src/book/run-snapshot-store.ts
|
||||
src/book/snapshot-filters.ts instance 时剥离 运行.* 等
|
||||
src/book/snapshot-filters.ts
|
||||
src/server/session-manager.ts
|
||||
src/server/message-branch.ts swipe / branch checkpoint
|
||||
```
|
||||
|
||||
存储:`books/{bookId}/run-snapshots/{id}.json`
|
||||
|
||||
## 相关
|
||||
|
||||
| 文档 | 关系 |
|
||||
|------|------|
|
||||
| `skill-design-guide.md` §0.4 | Worker 集与快照 |
|
||||
| `creation-playbook.md` | 创作流中的开局与存档 |
|
||||
| `context-assembly.md` | 恢复后 worker prompt 仍含 preset + static + dynamic |
|
||||
|
||||
@@ -79,8 +79,7 @@ type WaitingReason =
|
||||
## 6. 典型转移(简化)
|
||||
|
||||
```text
|
||||
idle → skill_selection
|
||||
skill_selected → intake 或 input
|
||||
idle → intake(session_started + initialSkill)
|
||||
intake 完成 / confirm → running → agent burst
|
||||
running → run_worker → approve_step 或 worker 执行
|
||||
worker_completed → review_artifact(user_confirmed)
|
||||
|
||||
@@ -1,184 +1,136 @@
|
||||
# Skill 设计指南
|
||||
# Skill 设计指南(包格式薄层)
|
||||
|
||||
指导 **如何从零设计一个 orchestrator 包**(skill 能力库 + run skill 组合)。
|
||||
格式见 `skill-format.md`、`orchestrator-skill-format.md`、`worker-skill-format.md`;上下文见 `context-assembly.md`。
|
||||
> **文档层级:Skill 包格式薄层(非系统架构)。**
|
||||
> 运行内核见 [`architecture.md`](./architecture.md);创作方法见下。
|
||||
|
||||
**设计顺序:**
|
||||
**创作方法(三大步、表、自检、正推)** 的权威文档是:
|
||||
|
||||
→ **[`design-orchestrator-guide.md`](./design-orchestrator-guide.md)**
|
||||
|
||||
本文只保留:**如何在仓库里建包、Worker 集弱结构示例、与旧概念对照**。不要在本文重复长方法文。
|
||||
|
||||
格式字段见 `orchestrator-skill-format.md`、`worker-skill-format.md`;上下文见 `context-assembly.md`。
|
||||
|
||||
**设计顺序(概念):**
|
||||
|
||||
```text
|
||||
1. 用户意图 → run skill 清单(交互范式 skill 产出)
|
||||
2. 每个 run skill 倒推需要哪些 instantiate skill / tag
|
||||
3. 定义各 skill 的 inputTags、outputTags、contextSegments
|
||||
4. 上下文隔离与验收边界
|
||||
5. orchestrator manifest(能力注册,非逐步剧本)
|
||||
1. 用户意图 → design-intake(按 design-orchestrator-guide)→ 设计.worker集 JSON
|
||||
2. 需要时合并 worker-templates 默认契约(仅 design 缺省)
|
||||
3. 用户验收 → 用户手动进 play
|
||||
4. play:按声明调度;表维护/副作用边沿触发
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. 实例化:agent 选 skill,不是填步骤
|
||||
## 0. 设计.worker集
|
||||
|
||||
### 1.1 两层倒推
|
||||
### 0.1 定位
|
||||
|
||||
```text
|
||||
用户意图(长篇 / 短篇 / 思想实验 / 角色卡扮演 …)
|
||||
→ 交互范式 skill:产出 设计.run_skill清单
|
||||
→ 每个 run skill 需要什么输入?
|
||||
→ agent 按需 invoke instantiate skill(能力库中的 SKILL.md)
|
||||
→ declare_instance_ready → play stage
|
||||
旧 **交互范式 / run_skill 清单 / 美学纲领** 合并为单一 tag:**`设计.worker集`(JSON)**。
|
||||
|
||||
| 旧概念 | 新做法 |
|
||||
|--------|--------|
|
||||
| `设计.交互范式` | `interaction`(站位、系统扮演、输出与轮转) |
|
||||
| `设计.run_skill清单` | `workers` + 数据依赖 / 表副作用 |
|
||||
| `设计.美学纲领` | 转述 presentation **或** 常驻上下文;叙事指南 ≠ 文风 |
|
||||
|
||||
Worker 集不是闭集枚举,也不是逐步管道。方法见 `design-orchestrator-guide.md`。
|
||||
|
||||
### 0.2 design-intake
|
||||
|
||||
与用户对话,增量更新草稿,accept 后即实例规格。进 play **由用户手动决定**。
|
||||
|
||||
### 0.3 弱结构示例(JSON)
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"interaction": {
|
||||
"user_stance": "单角代入",
|
||||
"system_role": "世界执行并给出可读叙事",
|
||||
"output": "输出.用户展示 + 可折叠状态表",
|
||||
"turn_shape": "对话回合"
|
||||
},
|
||||
"experience_check": {
|
||||
"user_relation": "…",
|
||||
"focus": "…",
|
||||
"satisfaction_source": "…"
|
||||
},
|
||||
"workers": [
|
||||
{
|
||||
"ref": "world-simulator",
|
||||
"duty": "世界推进与裁决",
|
||||
"rationale": "删掉则无本轮世界结果",
|
||||
"acceptance": "continue"
|
||||
},
|
||||
{
|
||||
"ref": "narrator",
|
||||
"duty": "组装用户可见回复",
|
||||
"rationale": "核心产出干巴,需要可读终稿",
|
||||
"acceptance": "review"
|
||||
}
|
||||
],
|
||||
"resident_context": [
|
||||
{
|
||||
"id": "tone",
|
||||
"position": "static",
|
||||
"content": "文风示例",
|
||||
"mount": ["narrator"]
|
||||
}
|
||||
],
|
||||
"tables": {
|
||||
"schemas": [],
|
||||
"side_effects": [
|
||||
{
|
||||
"id": "affinity-romance",
|
||||
"field": "好感",
|
||||
"op": "gte",
|
||||
"value": 60,
|
||||
"mode": "once",
|
||||
"action": {
|
||||
"type": "write_tag",
|
||||
"tag": "上下文.角色态度",
|
||||
"content": "恋爱模式"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"narrative_guide": "",
|
||||
"core_premises": [],
|
||||
"input_protocol": {
|
||||
"parens": "() 元要求",
|
||||
"quotes": "\"\" 角色对白",
|
||||
"bare": "无包裹视为事实陈述"
|
||||
},
|
||||
"design_end": {
|
||||
"opening": "optional"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**能力库**(如 world-simulator 包内十几个 instantiate skill)不是 1→N 管道;agent 跳过不需要的 skill,并记录 `设计.跳过.{skillId}`。
|
||||
`presentation` 主要挂转述类;裁决类不挂展示美学。包级共性可写 `shared-context.md`。
|
||||
|
||||
### 1.2 示例:不同意图的 run skill 组合
|
||||
### 0.4 编辑与重 roll
|
||||
|
||||
| 用户意图 | run skill 示例 | 实例化倒推 |
|
||||
|----------|----------------|------------|
|
||||
| 长篇小说 | 变量管理、大纲推荐、转述者、世界机 | 变量目录 skill、叙事指南、世界蓝图… |
|
||||
| 短篇 | 情感流生成器 | 美学纲领、情感曲线约定 |
|
||||
| 思想实验 | 世界模拟器 | 规则、行动格式;**无**转述者 |
|
||||
| 角色卡扮演 | 转述者(+ 可选世界机) | 角色卡确认稿、口吻、回复格式 |
|
||||
|
||||
### 1.3 run 阶段交互形态
|
||||
|
||||
agent 在 play stage 的 burst 内调度 run skill,形态因包而异:
|
||||
|
||||
| 形态 | 适用 | sketch |
|
||||
|------|------|--------|
|
||||
| 单 skill 直出 | 简单大纲 | `outline` |
|
||||
| 行动–反应循环 | 博弈、世界模拟 | 世界机 ↔ 角色决策 ↔ 展示 |
|
||||
| 分叉验收 | 规则怪谈 | `write` → 双 `review` |
|
||||
|
||||
形态写在各 skill 的 **能力说明**里,**不**写进全局编排表逐步剧本。
|
||||
见 `run-snapshot.md`。
|
||||
**创作**:按创作单位验收后删交互留产物。固定上下文相关名称是 **可向用户询问的示例话题**(非填空表);宜先钉纲领类 tag 再写 worker(挂载复用),见 `design-orchestrator-guide.md` §6。
|
||||
**Run** 验收点(`acceptance: review`):接受后压缩过程 tag。
|
||||
`acceptance: continue` 的中间 worker 可连跑;面向用户的可读停点由 Worker 集声明。
|
||||
|
||||
---
|
||||
|
||||
## 2. 暂停与用户
|
||||
## 1. 暂停与用户
|
||||
|
||||
### 2.1 边界 tool 即暂停
|
||||
|
||||
agent tool loop 在下列情况 **退出 burst**,进入 `waiting_user`:
|
||||
|
||||
```text
|
||||
ask_user / review_blackboard
|
||||
run_worker + requiresApproval
|
||||
worker 完成 + user_confirmed → review_artifact
|
||||
worker ask_user → worker_questions
|
||||
```
|
||||
|
||||
暂停策略写在 orchestrator manifest 的 **验收策略**,不是「第几步必须停」的管道表。
|
||||
|
||||
### 2.2 user-turn
|
||||
|
||||
用户亲自决策的环节:独立 skill,`用户.最新输入` 写入与 LLM 角色同形 tag,供世界机裁决。见 `worker-skill-format.md`。
|
||||
边界 tool 即暂停(`ask_user`、`review_artifact` 等)。策略写在 manifest **验收策略**,不是「第几步必须停」。
|
||||
Play 默认不对每轮终稿强制 accept。详见 `design-orchestrator-guide.md` §0、§7。
|
||||
|
||||
---
|
||||
|
||||
## 3. 上下文:上半固定、下半动态
|
||||
|
||||
每个 skill(`SKILL.md`)声明:
|
||||
|
||||
```text
|
||||
inputTags / outputTags 黑板接口
|
||||
contextSegments static(上)+ dynamic(下)
|
||||
contextIsolation 谁看不见什么
|
||||
contextProfile variants 实例化时选档位
|
||||
```
|
||||
|
||||
原则:**稳定在上、增量在下**;Runtime 拼接,agent 不改。
|
||||
全文见 `docs/context-assembly.md`。
|
||||
|
||||
### 3.1 分层示例(roleplay-game-theory)
|
||||
|
||||
| tier | 含义 | tag 示例 |
|
||||
|------|------|----------|
|
||||
| static | 前提、实体设定 | `情境.实验.设定`、`角色.{id}.设定` |
|
||||
| dynamic | 历史、本轮 | `运行.事件流`、`可见信息` |
|
||||
|
||||
---
|
||||
|
||||
## 4. 倒推标签
|
||||
|
||||
对每个 skill 填表:
|
||||
|
||||
```text
|
||||
skill id | 职责 | stage(design/play) | inputTags | outputTags | contextSegments | 验收者
|
||||
```
|
||||
|
||||
### 4.1 角色扮演博弈(摘录)
|
||||
|
||||
| skill | 读取 | 写入 | 验收 |
|
||||
|-------|------|------|------|
|
||||
| setup-scenario | `用户.博弈需求` | 情境、规则、角色设定 | 用户 |
|
||||
| world-engine | 情境、规则、事件流、行动 | 可见信息、事件流 | 程序 |
|
||||
| role-decide | 本人设定、事件流、可见信息 | `.思考`、`.行动` | 程序 |
|
||||
| present-round | 本轮产物 | `输出.用户展示` | 用户 |
|
||||
|
||||
展示类 skill 单独存在;agent 调度,不拼长文。
|
||||
|
||||
---
|
||||
|
||||
## 5. 上下文隔离(必查)
|
||||
|
||||
```text
|
||||
□ 哪些 tag 只给用户、不注入生产 skill?
|
||||
□ 盲读 review 不可见哪些 tag?
|
||||
□ 草稿 vs 确认稿:下游何时可当作事实?
|
||||
□ 多角色:role_pov 是否生效?
|
||||
```
|
||||
|
||||
靠 `inputTags` + `contextIsolation` + Runtime 过滤,不靠 prompt 口头禁止。
|
||||
|
||||
---
|
||||
|
||||
## 6. orchestrator manifest(非编排表)
|
||||
|
||||
orchestrator.md 应包含:
|
||||
|
||||
```text
|
||||
□ 启动询问(最小 intake)
|
||||
□ instantiate skill 注册表(id、stage、description)
|
||||
□ run skill 注册表
|
||||
□ 验收策略(哪些 skill 产出需 user_confirmed)
|
||||
□ contextProfile 可选 variant 说明
|
||||
□ declare_instance_ready 最低可行性(按 run_skill清单 动态校验)
|
||||
```
|
||||
|
||||
**不应包含:** 逐步思维链、「第 5 步必须跑 world-engine」类管道脚本。
|
||||
|
||||
---
|
||||
|
||||
## 7. checklist
|
||||
|
||||
```text
|
||||
□ 1. 一句话:本包 play stage 的交互形态
|
||||
□ 2. 交互范式 skill 产出 run_skill清单 的 schema
|
||||
□ 3. 每个 run skill 倒推 instantiate skill 需求
|
||||
□ 4. 各 SKILL.md:input/output、contextSegments、隔离
|
||||
□ 5. shared-context.md(static 上半)
|
||||
□ 6. manifest:注册表 + 验收 + readiness
|
||||
□ 7. skills/README.md 注册
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 示例包
|
||||
|
||||
| 包 | 特点 |
|
||||
|----|------|
|
||||
| `basic` | 单 run skill,最小上下文 |
|
||||
| `weird-rules-short` | 分叉验收 |
|
||||
| `roleplay-game-theory` | 行动–反应循环 |
|
||||
| `world-simulator`(规划) | 大能力库 + agent 实例化 |
|
||||
|
||||
对照时复用 **方法**,不照搬 tag 名或 skill 数量。
|
||||
|
||||
---
|
||||
|
||||
## 9. 相关文档
|
||||
## 2. 相关
|
||||
|
||||
| 文档 | 关系 |
|
||||
|------|------|
|
||||
| `context-assembly.md` | 拼接规格 |
|
||||
| `creation-playbook.md` | 概念 |
|
||||
| `orchestrator-skill-format.md` | manifest 写法 |
|
||||
| `book-storage.md` | 过程与资产归档 |
|
||||
| **design-orchestrator-guide.md** | ★ 方法 |
|
||||
| creation-playbook.md | 流程概念 |
|
||||
| orchestrator-skill-format.md | manifest |
|
||||
| worker-skill-format.md | 磁盘 SKILL(声明驱动演进中) |
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# Skill 格式与存储
|
||||
|
||||
> **文档层级:Skill 包格式(非系统架构)。**
|
||||
> 系统级模块与调度边界见 [`architecture.md`](./architecture.md)。
|
||||
|
||||
## 1. 定位:两层 Skill
|
||||
|
||||
本项目有 **两种 Skill 文档**,不要混在一个文件里:
|
||||
@@ -355,72 +358,63 @@ skills:
|
||||
|
||||
---
|
||||
|
||||
## 5. 会话启动:第一个询问是选 Skill
|
||||
## 5. 会话启动:描述需求,而非选包
|
||||
|
||||
Skill 选择发生在**任何创作逻辑之前**。
|
||||
新建作品 **不再** 让用户输入 skill name 或列表编号。Runtime 自动加载默认 orchestrator(`world-simulator`,见 `src/config/default-orchestrator.ts`),直接进入 intake。
|
||||
|
||||
### 5.1 启动转移
|
||||
|
||||
```text
|
||||
idle
|
||||
session_started
|
||||
→ waiting_user(skill_selection)
|
||||
session_started { initialSkill }
|
||||
→ waiting_user(intake)
|
||||
```
|
||||
|
||||
新增 `waitingReason`:
|
||||
`session_started` 携带 `initialSkill` 时跳过 `skill_selection`。
|
||||
registry 中其它包仅供 `startWithOrchestrator` / 旧作品读档兼容。
|
||||
|
||||
**Legacy(旧会话恢复):**
|
||||
|
||||
```text
|
||||
session_started(无 initialSkill)
|
||||
→ waiting_user(skill_selection) # 仅旧快照可能出现
|
||||
```
|
||||
|
||||
```ts
|
||||
| { kind: "skill_selection"; availableSkills: SkillIndexEntry[] }
|
||||
| { kind: "skill_selection"; availableSkills: SkillIndexEntry[] } // legacy
|
||||
| { kind: "intake"; prompt: string }
|
||||
```
|
||||
|
||||
### 5.2 向用户展示
|
||||
|
||||
来自 orchestrator `## 启动询问`,例如 world-simulator:
|
||||
|
||||
```text
|
||||
请选择创作类型:
|
||||
请用你自己的话描述想做什么,例如:
|
||||
|
||||
【小说】(Book 形态:卷 / 章)
|
||||
1. novel-standard — 标准长篇:大纲 → 事件 → 正文
|
||||
2. weird-rules-short — 短篇规则怪谈:核心 → 规则 → 成章
|
||||
3. basic — 最小演示
|
||||
- 西幻升级、长篇 AI 交互、世界推着走
|
||||
- 部分代入:() 是指令,"" 是角色话,【】 是行动
|
||||
- 或:仿写/扩写、规则怪谈、快节奏爽文……
|
||||
|
||||
【对话】(Book 形态:回合 / 多角色)
|
||||
4. theater-roleplay — 剧场式角色扮演
|
||||
|
||||
也可直接描述你想写什么,我会帮你匹配 skill name。
|
||||
Agent 会根据你的描述推理需要哪些 Worker,并产出 Worker 集。
|
||||
```
|
||||
|
||||
### 5.3 用户回答方式
|
||||
|
||||
```text
|
||||
输入编号或 name:novel-standard
|
||||
输入自然语言:我想写一个剧场扮演
|
||||
输入自定义:用 novel-standard,但是偏悬疑
|
||||
直接描述创作目标(一句话即可)
|
||||
```
|
||||
|
||||
Runtime 解析为 `skill_selected` 事件:
|
||||
Runtime 写入 `用户.需求`(或 skill 指定的 startupTargetKey),确认后进入 design stage。
|
||||
|
||||
```ts
|
||||
{ type: "skill_selected"; payload: { skillId: string; userHint?: string } }
|
||||
```
|
||||
|
||||
然后:
|
||||
### 5.4 两阶段启动(现行)
|
||||
|
||||
```text
|
||||
加载 skills/{skillId}/SKILL.md
|
||||
解析 ## 启动询问 → session.slots.activeSkill
|
||||
→ waiting_user(input)
|
||||
message 来自 SKILL.md「启动询问·向用户展示」
|
||||
必收集项 / 写入目标 同样来自该节
|
||||
阶段 1(系统) 自动绑定默认 orchestrator
|
||||
阶段 2(intake) 启动询问 → 用户描述需求 → confirm → agent burst
|
||||
```
|
||||
|
||||
### 5.4 两阶段启动
|
||||
|
||||
```text
|
||||
询问 1(系统) skill_selection 「用哪个 skill?」→ registry / description
|
||||
询问 2(skill) input 「启动询问」章节 → 每类内容问的不同
|
||||
```
|
||||
|
||||
**只有询问 1 是系统固定的。询问 2 及之后所有创作逻辑,都在 SKILL.md 里。**
|
||||
**Agent 根据需求推理 worker 集**(`design-intake`),不再让用户在 registry 里四选一。
|
||||
|
||||
---
|
||||
|
||||
@@ -495,10 +489,10 @@ Creation Playbook(概念)
|
||||
## 11. 第一版范围
|
||||
|
||||
```text
|
||||
skills/ 目录 + 2 个示例 SKILL.md(novel、theater)
|
||||
启动 → skill_selection → 用户选择 → 加载 skill
|
||||
skills/ 目录 + 示例 orchestrator 包
|
||||
启动 → 自动绑定默认 orchestrator → intake(描述需求)
|
||||
总管 prompt 注入 skill 摘要
|
||||
registry.yaml 可选
|
||||
registry.yaml 可选(legacy 包 / 读档兼容)
|
||||
```
|
||||
|
||||
不做:
|
||||
|
||||
@@ -2,572 +2,67 @@
|
||||
|
||||
## 1. 定位
|
||||
|
||||
本系统用于多 worker 协作式文本生成,覆盖:
|
||||
|
||||
```text
|
||||
简易小说快速撰写(quick-write)
|
||||
规则怪谈 / 短篇结构化创作(weird-rules-short 等)
|
||||
长篇小说 / 交互式写作助手(interactive-novel,TODO)
|
||||
场景扮演模拟(scene-roleplay,TODO)
|
||||
角色扮演 / 角色卡互动(并入 scene-roleplay 的 instantiate + run,见 §2)
|
||||
```
|
||||
|
||||
核心思想:
|
||||
多 worker 协作式文本生成;**默认能力库包名 = `world-simulator`**(创作方法见 `design-orchestrator-guide.md`,不以「世界模拟」为默认总形态)。
|
||||
|
||||
```text
|
||||
黑板 = 标签化数据池
|
||||
标签 = skill 之间的接口
|
||||
skill = SKILL.md 定义的能力;worker = 一次 invoke
|
||||
Agent = tool loop 内调度 invoke 哪个 skill
|
||||
Runtime = 按 skill 契约拼接上下文(上半固定、下半动态)
|
||||
标签 = worker 之间的接口
|
||||
设计.worker集 JSON / 声明 = 读哪些 tag、写哪些 tag、表与常驻上下文
|
||||
Agent = tool loop 内调度 invoke 哪个 id
|
||||
Runtime = 按声明拼接上下文;表字段 rev 合并
|
||||
```
|
||||
|
||||
一句话:**标签驱动 + agent 选 skill + Runtime 拼上下文**——不是固定流水线,也不是 LLM 自由分发上下文。
|
||||
## 2. Skill 包、Worker 声明与 Book
|
||||
|
||||
上下文拼接见 `docs/context-assembly.md`。不要让 agent 临场改 inputTags;分发由 `inputTags` + `contextSegments` 静态声明。
|
||||
```text
|
||||
Skill 包 能力库 + design-intake + 可选 templates
|
||||
设计.worker集(accepted,JSON) 本实例 Worker 声明(play 权威)
|
||||
Session + 黑板 一次运行的实参
|
||||
Book 过程、存档、资产(book-storage.md)
|
||||
```
|
||||
|
||||
### 业务 stage
|
||||
|
||||
```text
|
||||
design(创作:核心→细节交互→细化)→ 用户验收 → 用户手动进 play → done
|
||||
运行相位:idle | running | waiting_user | done | error
|
||||
```
|
||||
|
||||
创作方法权威:`design-orchestrator-guide.md`。
|
||||
|
||||
### 创作
|
||||
|
||||
`design-intake` → accept **`设计.worker集` JSON** → 用户手动进 play(可选开局 · 开场白,非强制锁定)。
|
||||
无独立 instantiate 管道。见 `design-orchestrator-guide.md`。
|
||||
|
||||
### Play
|
||||
|
||||
Agent 只能 `run_worker` **声明中的 ref**;执行契约来自 Worker 集条目(可选合并 `worker-templates/`)。
|
||||
|
||||
---
|
||||
|
||||
## 2. Skill 定义、实例化与 Book
|
||||
|
||||
### 2.1 类比
|
||||
## 3. 核心 tag(world-simulator)
|
||||
|
||||
```text
|
||||
Skill 包(orchestrator manifest + workers) = 能力定义(静态)
|
||||
Session + 黑板 tag = 一次运行的实参
|
||||
Book = 过程、资产、游玩(见 book-storage.md)
|
||||
play stage = agent invoke run skill
|
||||
用户.需求 / 用户.最新输入 / 用户.修订说明
|
||||
设计.worker集 / 设计.worker集.草稿
|
||||
创作.当前单位 / 创作.已验收单位 / 创作.已验收内容 / 创作.对话
|
||||
运行.初始变量 / 运行.事件流 / 运行.本轮.*
|
||||
变量.当前
|
||||
输出.用户展示 / 输出.开场白
|
||||
```
|
||||
|
||||
**写角色卡、收集设定、启动询问** 都不是独立「产品模式」,而是 **实例化阶段** 的不同形态:把 prerequisite tags 写满,然后才进入运行阶段。
|
||||
固定上下文(叙事指南、美学纲领等)在实例规格里落地,play 时经 `resident_context.mount` / `contextSegments` **挂到多个 worker**——这是 tag 化的主收益,不是省掉创作期依赖正文。
|
||||
|
||||
### 2.2 三层阶段(勿混淆)
|
||||
|
||||
```text
|
||||
运行相位(RuntimePhase)
|
||||
idle | running | waiting_user | done | error
|
||||
系统在等什么。见 runtime-state-machine.md
|
||||
|
||||
业务 stage(Book / Session)
|
||||
design(实例化)→ play(运行)→ done
|
||||
|
||||
tag 阶段(黑板条目 tag 名中的段)
|
||||
候选 | 草稿 | 确认稿 | 当前 | 更新
|
||||
单条数据的 lifecycle
|
||||
```
|
||||
|
||||
业务 stage **写在各 skill 的 orchestrator.md**,不扩运行相位 enum。
|
||||
|
||||
### 2.3 实例化(design)
|
||||
|
||||
**职责:** agent 按需 invoke instantiate skill,沉淀 `设计.*` tag;产出 `设计.run_skill清单` 后 declare ready。
|
||||
|
||||
```text
|
||||
选 orchestrator 包
|
||||
→ design stage:启动询问 → agent invoke instantiate skill(能力库)
|
||||
→ declare_instance_ready
|
||||
→ play stage:agent invoke run skill
|
||||
→ done → 归档 Book(过程 + 资产,见 book-storage.md)
|
||||
```
|
||||
|
||||
可从 Book / CardAsset 加载已有 tag,跳过部分 instantiate skill。
|
||||
|
||||
运行相位上,实例化阶段多为 `waiting_user(input)`(启动询问、总管 ask_user);实例化也可调用 **setup worker**,但仍是 worker(固定 inputTags/outputTags),不是第二个生产总管。
|
||||
|
||||
### 2.4 每个 skill 声明什么
|
||||
|
||||
在 orchestrator.md 中写清(见 orchestrator-skill-format.md):
|
||||
|
||||
```text
|
||||
## 启动询问 / ## 实例化
|
||||
prerequisiteTags:本 skill 运行前必须有的 tag
|
||||
instanceReadyWhen:何时可进入 run stage(文字条件 + tag 列表)
|
||||
写入目标 tag(可多个;不必再塞进单一 book.brief)
|
||||
|
||||
## 阶段定义
|
||||
instantiate(或沿用 stageId brief)→ run(write / review …)→ done
|
||||
|
||||
## Worker 编排
|
||||
仅 run stage 及之后调度生产 worker;instantiate 阶段只 ask_user 或 run setup worker
|
||||
```
|
||||
|
||||
### 2.5 已有雏形:weird-rules-short
|
||||
|
||||
| 业务 stage | 现 stageId | 含义 |
|
||||
|---|---|---|
|
||||
| 实例化 | `brief` | 启动询问 → `book.brief`(≈ `需求.核心要点`)→ `startupCompleted` |
|
||||
| 运行 | `write` | `write-rules` 产出规则与 core |
|
||||
| 运行 | `review` | 双验收 worker |
|
||||
| 结束 | `done` | finish |
|
||||
|
||||
`basic` 同理:`brief` = 实例化,`outline` worker = 运行。
|
||||
文档与实现迁移时,可将 stageId 改名为 `instantiate`,或保留 `brief` 但在 ## 阶段定义 注明 **brief ≡ instantiate**。
|
||||
|
||||
### 2.6 角色卡
|
||||
|
||||
不再单独维护 `character-card-author` skill 包。角色设定、口吻、行为边界等 tag 在 **扮演类 skill 的 instantiate 段** 收集或生成(可选 setup worker)。
|
||||
若需跨 Session 复用,将 `角色.A.设定`、`角色卡.确认稿` 等 **确认稿** 存入 Book,新 Session **加载 Book** 而非再跑完整实例化。
|
||||
|
||||
### 2.7 Book 与黑板
|
||||
|
||||
```text
|
||||
黑板(Session 内) 运行时 tag 池;worker 读写
|
||||
Book(项目级) 长期实例;accepted 确认稿归档;下一场 Session 可加载
|
||||
```
|
||||
|
||||
详见 `docs/book-storage.md`。Session 是一次运行;Book 是一个创作项目(一本小说、一个扮演项目)。
|
||||
命名与创作单位 id 见 `design-orchestrator-guide.md` §6、`ui-glossary.md`;词表可另补 `docs/tag-vocabulary.md`。
|
||||
|
||||
---
|
||||
|
||||
## 3. 黑板条目
|
||||
|
||||
### 2.1 最小结构
|
||||
|
||||
```ts
|
||||
type BlackboardItem = {
|
||||
id: string;
|
||||
tag: string;
|
||||
content: string;
|
||||
source: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
```
|
||||
|
||||
### 2.2 可选增强
|
||||
|
||||
```ts
|
||||
type BlackboardItem = {
|
||||
id: string;
|
||||
tag: string;
|
||||
content: string;
|
||||
source: string;
|
||||
scope?: string;
|
||||
createdAt?: number;
|
||||
updatedAt?: number;
|
||||
dependencies?: string[];
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
```
|
||||
|
||||
### 2.3 字段含义
|
||||
|
||||
```text
|
||||
id 唯一标识,追踪与依赖
|
||||
tag 标签本体,决定身份与消费路径(核心)
|
||||
content 具体内容(字符串)
|
||||
source 产生该条目的 worker id,调试用
|
||||
scope 作用范围,如当前章节、场景、项目(可选)
|
||||
dependencies 依赖的其他黑板条目 id(可选)
|
||||
metadata 置信度、真实性、控制模式等(不参与基础路由)
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
```text
|
||||
tag 是核心路由依据。
|
||||
source 不是路由依据,只用于调试与溯源。
|
||||
metadata 不参与基础路由,除非某 skill 明确约定。
|
||||
```
|
||||
|
||||
**已废弃:** 旧模型的 `key`、`summary`、`tags[]`、`readableBy`、`writableBy` 作为路由字段。迁移期代码可能仍保留 `BlackboardEntry`,以本文为准逐步替换。
|
||||
|
||||
---
|
||||
|
||||
## 4. 标签原则
|
||||
|
||||
标签不是信息分类学,而是 **工作流接口**。
|
||||
|
||||
### 3.1 设计原则
|
||||
|
||||
```text
|
||||
标签越少越好,但必须能区分不同消费路径。
|
||||
两个信息若永远被同一批 worker 消费,可合并 tag。
|
||||
若在不同阶段被不同 worker 消费,必须拆分 tag。
|
||||
若可能被误当成事实,必须加阶段段。
|
||||
若涉及角色私有认知,tag 里必须体现角色归属。
|
||||
```
|
||||
|
||||
### 3.2 推荐格式
|
||||
|
||||
```text
|
||||
对象.内容
|
||||
对象.内容.阶段
|
||||
领域.对象.内容.阶段
|
||||
```
|
||||
|
||||
不强制四段式;按复杂度逐级增加。
|
||||
|
||||
示例:
|
||||
|
||||
```text
|
||||
大纲.草稿
|
||||
事件.草稿
|
||||
正文.草稿
|
||||
正文.确认稿
|
||||
|
||||
角色.A.行动.候选
|
||||
角色.A.台词.候选
|
||||
角色.A.内心想法.候选
|
||||
角色.A.记忆.当前
|
||||
```
|
||||
|
||||
### 3.3 标签替代的旧字段
|
||||
|
||||
```text
|
||||
角色.A.内心想法.候选
|
||||
```
|
||||
|
||||
已表达:归属 A、类型为内心想法、阶段为候选、消费路径由声明 inputTags 的 worker 决定。
|
||||
|
||||
**不需要** 再写 `owner`、`visibleTo`、`type=action` 等平行字段。
|
||||
|
||||
### 3.4 匹配规则(Runtime)
|
||||
|
||||
Worker frontmatter 声明 `inputTags`:
|
||||
|
||||
```yaml
|
||||
inputTags:
|
||||
- "需求.核心要点" # 精确匹配
|
||||
- "角色.A.*" # 前缀匹配:tag 以「角色.A.」开头
|
||||
- "大纲.*.草稿" # 前缀匹配
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
```text
|
||||
无通配 → 精确匹配 tag
|
||||
以 .* 结尾 → 前缀匹配(实现优先于完整正则)
|
||||
多条命中 → 默认按 updatedAt 取最新;或 worker 声明 inputMerge: concat | latest
|
||||
```
|
||||
|
||||
总管调度时也可对 **tag 索引**(不含 content)做存在性判断,例如「是否有 规则.确认稿」。
|
||||
|
||||
---
|
||||
|
||||
## 5. 阶段标签
|
||||
|
||||
阶段表示同一类信息在不同生命周期下的语义。
|
||||
|
||||
```text
|
||||
候选 worker 生成的可能内容,不等于事实
|
||||
草稿 生成中的文本或结构
|
||||
确认稿 用户或流程确认,可进入长期状态
|
||||
当前 当前生效状态
|
||||
更新 状态变化结果
|
||||
```
|
||||
|
||||
重要规则:
|
||||
|
||||
```text
|
||||
候选 ≠ 已发生。
|
||||
草稿 ≠ 确认稿。
|
||||
角色行动候选不能直接写入长期记忆。
|
||||
长期记忆优先从正文.确认稿 与明确 记忆.更新 生成。
|
||||
```
|
||||
|
||||
验收(`user_confirmed` / `programmatic_review`)通过后,Runtime 将对应产物 tag 从 `.草稿` 升级为 `.确认稿`(或写入新的确认稿条目并标记旧草稿 superseded)。
|
||||
|
||||
---
|
||||
|
||||
## 6. Worker
|
||||
|
||||
每个 worker 是固定的标签消费者和生产者。
|
||||
|
||||
### 5.1 类型
|
||||
|
||||
```ts
|
||||
type WorkerDefinition = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
inputTags: string[];
|
||||
outputTags: string[];
|
||||
inputMerge?: "latest" | "concat";
|
||||
run: (context: WorkerContext) => Promise<WorkerResult>;
|
||||
};
|
||||
|
||||
type WorkerContext = {
|
||||
taskId: string;
|
||||
workerId: string;
|
||||
items: BlackboardItem[];
|
||||
params?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
type WorkerResult = {
|
||||
items: BlackboardItem[];
|
||||
logs?: string[];
|
||||
askUser?: string[];
|
||||
};
|
||||
```
|
||||
|
||||
### 5.2 规则
|
||||
|
||||
```text
|
||||
worker 只能读取 inputTags 声明的标签(含前缀规则)。
|
||||
worker 只能输出 outputTags 声明的标签。
|
||||
worker 不读取全量黑板。
|
||||
LLM 不决定自己能看什么。
|
||||
Runtime 校验 outputs 的 tag ⊆ outputTags。
|
||||
```
|
||||
|
||||
### 5.3 Worker Skill 落盘
|
||||
|
||||
见 `docs/worker-skill-format.md`。正文写「怎么做」;`inputTags` / `outputTags` 写在 frontmatter。
|
||||
|
||||
### 5.4 ask_user
|
||||
|
||||
提问是 worker **能力**,不是独立 worker。中途提问时 `resumeContext` 保存 workerId,**不**保存 inputTags(恢复时仍从 Worker Skill 读 inputTags)。
|
||||
|
||||
---
|
||||
|
||||
## 7. 总管(Main Agent)
|
||||
|
||||
总管只负责流程推进。
|
||||
|
||||
### 6.1 职责
|
||||
|
||||
```text
|
||||
判断当前任务属于哪种 skill / 业务阶段
|
||||
选择下一个 worker(run_worker)
|
||||
判断是否需要追问用户(ask_user)
|
||||
判断是否需要用户确认下一步(requiresApproval)
|
||||
判断是否 finish
|
||||
```
|
||||
|
||||
### 6.2 不负责
|
||||
|
||||
```text
|
||||
不决定某条信息给谁看
|
||||
不手动拼接 worker 上下文
|
||||
不让 LLM 判断信息权限
|
||||
不把全量黑板交给 worker
|
||||
不在 run_worker 里指定 inputTags / outputTags
|
||||
```
|
||||
|
||||
### 6.3 决策结构
|
||||
|
||||
```ts
|
||||
type MainAgentDecision = {
|
||||
id: string;
|
||||
action: "ask_user" | "run_worker" | "create_temp_worker" | "review_blackboard" | "finish";
|
||||
reason: string;
|
||||
workerId?: string;
|
||||
requiresApproval: boolean;
|
||||
statePatchAllowed: false;
|
||||
};
|
||||
```
|
||||
|
||||
执行链:
|
||||
|
||||
```text
|
||||
总管选择 worker
|
||||
→ Runtime 读取该 worker 的 inputTags
|
||||
→ 从黑板取匹配条目,组装 WorkerContext
|
||||
→ 调用 worker
|
||||
→ worker 输出固定 outputTags
|
||||
→ 写回黑板
|
||||
→ 按 acceptanceMode 验收
|
||||
```
|
||||
|
||||
Tool 合约见 `docs/tool-contracts.md`。
|
||||
|
||||
---
|
||||
|
||||
## 8. Skill 包与 manifest
|
||||
|
||||
Skill 包 = `orchestrator.md`(manifest)+ `workers/*/SKILL.md`。详见 `orchestrator-skill-format.md`。
|
||||
|
||||
manifest 包含:
|
||||
|
||||
```text
|
||||
Skill 注册表(instantiate + run)
|
||||
验收策略
|
||||
Instance Ready 规则
|
||||
```
|
||||
|
||||
**不写** 逐步编排表。inputTags / contextSegments 在 Worker SKILL.md。
|
||||
|
||||
### 8.1 已启用包
|
||||
|
||||
| 包 | 说明 |
|
||||
|---|---|
|
||||
| `novel/basic` | 演示:需求 → 大纲 |
|
||||
| `novel/weird-rules-short` | 规则怪谈:写 + 双验收 |
|
||||
|
||||
### 8.2 规划包(见 `skills/README.md`)
|
||||
|
||||
| 包 | 说明 |
|
||||
|---|---|
|
||||
| `novel/quick-write` | **简易档**:几乎无 tag 路由,上下文全量给 LLM |
|
||||
| `novel/interactive-novel` | 长篇 / 写作助手:instantiate + 多轮 run |
|
||||
| `novel/novel-standard` | 标准流水线(或与 interactive 合并) |
|
||||
| `dialogue/scene-roleplay` | 场景扮演(含角色设定 instantiate + 互动 run) |
|
||||
|
||||
---
|
||||
|
||||
## 9. 各模式核心 tag(参考)
|
||||
|
||||
实现 skill 时从下列词汇出发,不必一次全部实现。
|
||||
|
||||
### 8.1 简易小说(quick-write)
|
||||
|
||||
极简;可大量依赖 session + 全量上下文,tag 仅作交付锚点:
|
||||
|
||||
```text
|
||||
用户.输入
|
||||
需求.摘要
|
||||
正文.草稿
|
||||
正文.确认稿
|
||||
```
|
||||
|
||||
### 8.2 结构化短篇(如 weird-rules-short)
|
||||
|
||||
迁移目标示例(与现 key 对照实施):
|
||||
|
||||
```text
|
||||
需求.核心要点 ← book.brief
|
||||
核心.危险.隐藏 ← core.danger
|
||||
规则.草稿 ← rules.draft
|
||||
规则.说明.草稿 ← rules.commentary
|
||||
验收.读者视角.记录 ← review.infer.notes
|
||||
验收.作者视角.记录 ← review.author.notes
|
||||
```
|
||||
|
||||
### 8.3 交互式长篇
|
||||
|
||||
```text
|
||||
用户.原始输入 | 用户.意图转述 | 用户.确认结果
|
||||
项目.设定 | 项目.风格要求
|
||||
大纲.当前 | 大纲.候选修改 | 大纲.确认稿
|
||||
事件.当前 | 事件.确认稿
|
||||
正文.原文 | 正文.续写锚点 | 正文.草稿 | 正文.确认稿
|
||||
记忆.长期摘要 | 记忆.确认稿
|
||||
```
|
||||
|
||||
### 8.4 场景扮演
|
||||
|
||||
```text
|
||||
用户.行动输入 | 用户.行动意图
|
||||
世界.规则 | 世界.当前状态 | 世界.隐藏状态
|
||||
场景.可见信息 | 场景.隐藏信息
|
||||
角色.{id}.记忆 | 信念 | 行动.候选 | 台词.候选
|
||||
行动.裁决结果
|
||||
输出.场景反馈
|
||||
更新.世界状态 | 更新.角色状态
|
||||
```
|
||||
|
||||
### 8.5 角色卡
|
||||
|
||||
撰写:`角色卡.草稿` → `角色卡.确认稿`
|
||||
游玩:读 `角色卡.确认稿` + `用户.控制模式` + `NPC.*`
|
||||
|
||||
---
|
||||
|
||||
## 10. 正文 worker 与角色候选
|
||||
|
||||
角色 worker 输出 `角色.*.台词.候选` 等,**不**直接进入正文事实。
|
||||
|
||||
正文 worker 读取候选 + 风格约束,输出 `正文.草稿`;用户确认后为 `正文.确认稿`。
|
||||
|
||||
```text
|
||||
角色候选 → 提供意图
|
||||
正文 worker → 文本化、风格化、叙事化
|
||||
正文.确认稿 → 最终发生与表达
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 可选增强
|
||||
|
||||
### 10.1 采用记录
|
||||
|
||||
```text
|
||||
tag: 采用.记录
|
||||
```
|
||||
|
||||
记录正文采用了哪些候选条目,避免未采用候选污染记忆。早期可省略,记忆 worker 只从 `正文.确认稿` 抽取。
|
||||
|
||||
### 10.2 真实性 / 置信度(metadata)
|
||||
|
||||
```ts
|
||||
type TruthMode =
|
||||
| "truth" | "belief" | "claim" | "lie" | "rumor" | "plan" | "unknown";
|
||||
```
|
||||
|
||||
不参与基础路由。
|
||||
|
||||
### 10.3 RAG
|
||||
|
||||
RAG 不替代黑板。检索结果也应写成 tag,例如 `角色.A.相关记忆摘要`,由 worker 通过 inputTags 读取。
|
||||
|
||||
---
|
||||
|
||||
## 12. 与阶段机的关系
|
||||
|
||||
运行相位、业务 stage、tag 阶段 **三者正交**(详见 §2.2):
|
||||
|
||||
```text
|
||||
运行相位 系统在等什么(见 runtime-state-machine.md)
|
||||
业务 stage instantiate → run → done(orchestrator.md;brief 即 instantiate)
|
||||
tag 阶段 黑板条目 lifecycle(候选 / 草稿 / 确认稿)
|
||||
```
|
||||
|
||||
实例化阶段在运行相位上通常体现为 `waiting_user(input)`;进入 run stage 后为 `running` + worker 验收循环。
|
||||
阶段机 enum 不增加 `instantiate` 相位——业务 stage 由 orchestrator + tag 索引判断。
|
||||
|
||||
阶段机规则本身不因 tag 迁移而改变。变的是:黑板读写、worker 上下文、总管决策字段。
|
||||
|
||||
---
|
||||
|
||||
## 13. 与 Book 存储
|
||||
|
||||
Book 存长期实例;黑板存 **当前 Session** 的运行时 tag。实例化产物与 run 阶段确认稿可归档到 Book。
|
||||
详见 `docs/book-storage.md` 与 §2.7。
|
||||
|
||||
---
|
||||
|
||||
## 14. 实现原则(必须遵守)
|
||||
|
||||
```text
|
||||
1. worker 不读取全量黑板(quick-write 等显式声明全量 inputTags 的 skill 除外)。
|
||||
2. worker 只能读取 inputTags 声明的标签。
|
||||
3. worker 只能输出 outputTags 声明的标签。
|
||||
4. LLM 不决定上下文分发。
|
||||
5. 总管 run_worker 时不带 inputTags / outputTags。
|
||||
6. 标签是 worker 之间的接口。
|
||||
7. 候选不等于事实;草稿不等于确认稿。
|
||||
8. 正文 worker 可重写角色候选产物。
|
||||
9. 长期记忆优先从正文.确认稿 更新。
|
||||
10. 简单 skill 用简单 tag;复杂 skill 再增加对象与阶段段。
|
||||
11. 角色卡与设定在 instantiate 阶段写入 tag;跨 Session 复用走 Book 加载,不单独 author skill 包。
|
||||
12. 代码与旧文档冲突时,以本文为准改代码。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 15. 代码迁移顺序(参考)
|
||||
|
||||
```text
|
||||
1. src/types/blackboard.ts → BlackboardItem + listByTag / matchPrefix
|
||||
2. src/skills/types.ts + loader → 解析 inputTags / outputTags
|
||||
3. src/worker/executor.ts → 按 tag 组装 context,校验 outputTags
|
||||
4. src/types/runtime.ts + main-agent → 决策去掉 inputKeys / outputKeys
|
||||
5. skills/novel/weird-rules-short → 第一个 tag 化样板
|
||||
6. skills/novel/quick-write → 简易全量 LLM 档
|
||||
```
|
||||
|
||||
当前代码仍为旧 key 模型;实现前以本文为规格。
|
||||
|
||||
---
|
||||
|
||||
## 16. 相关文档
|
||||
|
||||
| 文档 | 内容 |
|
||||
|---|---|
|
||||
| `orchestrator-skill-format.md` | 总管 orchestrator.md 写法 |
|
||||
| `worker-skill-format.md` | Worker SKILL.md 写法 |
|
||||
| `tool-contracts.md` | 总管 / worker tool |
|
||||
| `skill-format.md` | Skill 包存储 |
|
||||
| `runtime-state-machine.md` | 5 相位阶段机 |
|
||||
| `implementation-guide.md` | 写代码顺序 |
|
||||
| `skills/README.md` | Skill 包索引与 TODO |
|
||||
## 4. 相关
|
||||
|
||||
| 文档 | 关系 |
|
||||
|------|------|
|
||||
| `creation-playbook.md` | 创作 / 游玩流 |
|
||||
| `worker-skill-format.md` | 声明驱动 |
|
||||
| `context-assembly.md` | 上下文拼接 |
|
||||
| `run-snapshot.md` | 存档 |
|
||||
|
||||
@@ -37,7 +37,7 @@ LLM 通过 **tool call** 表达意图;Runtime 校验后执行 tool 或转为 *
|
||||
| name | 行为 |
|
||||
|------|------|
|
||||
| `run_worker` | 执行 skill;`requiresApproval` → `approve_step` |
|
||||
| `ask_user` | `waiting_user(input)` |
|
||||
| `ask_user` | `waiting_user(input)`;`assessment` → 内容评价(主内容);`questions` → 挂载询问卡(可 Skip) |
|
||||
| `review_blackboard` | 向用户展示概况 → `input` |
|
||||
| `finish` | `done` |
|
||||
|
||||
@@ -72,7 +72,7 @@ running 内每轮 LLM+tool 使 burst+1;超过 maxBurst(默认 12,可配置
|
||||
|
||||
| name | 行为 |
|
||||
|------|------|
|
||||
| `ask_user` | `worker_questions` + resumeContext |
|
||||
| `ask_user` | 无产物 → `worker_questions`;有产物 → 仍 `worker_completed`,追问挂到 `review_artifact.questions`(可直接 Accept) |
|
||||
| `submit` | 校验 tag ⊆ outputTags → 写黑板 → `worker_completed` |
|
||||
|
||||
Phase A 仍用 JSON `outputs` + `askUser`,语义等价。
|
||||
@@ -83,11 +83,11 @@ Phase A 仍用 JSON `outputs` + `askUser`,语义等价。
|
||||
|
||||
| waitingReason | 用户动作 |
|
||||
|---------------|----------|
|
||||
| `skill_selection` | 选包 |
|
||||
| `skill_selection` | 选包(**legacy**,新作品不再进入) |
|
||||
| `intake` / `input` | 输入 |
|
||||
| `approve_step` | approve / reject |
|
||||
| `review_artifact` | accept / reject |
|
||||
| `worker_questions` | 输入 |
|
||||
| `review_artifact` | accept / reject;可选 `questions` 随 Accept 一并收起(表示无需再完善) |
|
||||
| `worker_questions` | 作答 / Skip(无产物时的阻塞追问) |
|
||||
| `revision` | 输入修改说明 |
|
||||
|
||||
---
|
||||
|
||||
452
docs/ui-design.md
Normal file
452
docs/ui-design.md
Normal 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 执行、调度日志,默认不抢视线 |
|
||||
|
||||
**对话是手段,产物与叙事是目的。** 聊天气泡适合承载协调叙事,不适合承载长规格全文或沉浸式阅读。
|
||||
|
||||
主场景是 **全屏浏览器、长会话(1–2 小时)**,布局应稳定、可预期,而非频繁切 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 有以下硬约束:
|
||||
|
||||
| 约束 | 设计含义 |
|
||||
|------|----------|
|
||||
| **视口宽** | 可横向分栏:工作面 55–65% + 协调区 35–45%,不必单列堆叠 |
|
||||
| **会话长** | 底栏输入/确认位置永远不变;减少布局跳动 |
|
||||
| **产物大** | 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 = Continue,Esc = 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` | 作品、存档、资产形态 |
|
||||
171
docs/ui-glossary.md
Normal file
171
docs/ui-glossary.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# 用户可见文案与术语对照(UI Glossary)
|
||||
|
||||
**用途**:凡出现在 Web 界面、导出 Markdown、状态 pill、气泡标题、询问卡、检查器上的文案,**禁止直接暴露**内部英文 id(如 `design-core`、`design`、`play`)。内部协议、SKILL 路径、黑板 tag、API 字段仍用英文 id。
|
||||
|
||||
**实现**:`src/server/display-labels.ts`(权威映射);`web/display-labels.js` 须与本文及该文件保持一致。新增高频 id 时:**先改本文 → 再改代码**。
|
||||
|
||||
**产品隐喻**:用户侧统一用**拍摄**用语——**导演 / 剧本 / 演员 / 能力**。旧称「总管 / 能力包 / 配方 / Worker」仅作内部或过渡别称。
|
||||
|
||||
---
|
||||
|
||||
## 0. 拍摄术语(首选)
|
||||
|
||||
| 拍摄术语 | 用户可见含义 | 内部对应 | 勿再对用户说 |
|
||||
|----------|--------------|----------|--------------|
|
||||
| **导演** | ① 新建时**手动选一次**的方法起点(如世界模拟器、扩写助手),可按现场「调味」;② 会话里负责调度的 Agent | `recipes/` 选型 → 黑板 `创作.选用配方`;Main Agent / orchestrator | 配方、能力包(作第二层选项)、总管 |
|
||||
| **剧本** | 本局谈成的流程与规格(活的,不是死选单) | `设计.创作流程`、`设计.worker集` | 「剧本 Skill 菜单」、死板流程卡 |
|
||||
| **演员** | 上场执行的单元(一次 `run_worker`) | Worker / design-* / play workers | 对用户堆「Worker · id」;标题用中文名 |
|
||||
| **能力** | 共用工序模块(美学纲领与交互范式…);导演从中选型编排 | `modules/` 组件池 | 组件池、模块(可作作者文档用词) |
|
||||
|
||||
```text
|
||||
用户选【导演】(世界模拟器 / 扩写助手 …)
|
||||
│
|
||||
▼
|
||||
【导演】调度 → 从【能力】里编排 → 谈成【剧本】
|
||||
│
|
||||
▼
|
||||
【演员】按剧本上场执行 → 游玩 / 成稿
|
||||
```
|
||||
|
||||
**一层选型**:新建作品只选**导演**,不要再叠「能力包 + 配方」两层。
|
||||
|
||||
**调味**:导演给出的建议步骤是**近期起点**;编排时可增量追加、可反复调用标了可反复的能力(像现场改戏),验收的是本局【剧本】,不是磁盘上的静态菜谱。
|
||||
|
||||
---
|
||||
|
||||
## 1. 原则
|
||||
|
||||
1. **内外分离**:id 给机器;中文给用户。
|
||||
2. **拍摄词优先**:对用户优先用 §0;技术文档可写「导演(Main Agent / recipe)」对照。
|
||||
3. **阶段用产品词**:lifecycle 的 `design` / `play` 用户侧统一为 **创作** / **游玩**(勿译成「设计模式 / 播放」)。
|
||||
4. **与 SKILL `name` 对齐**:磁盘 worker 若已有中文 `name`,展示优先用 name;本表是缺省与高频兜底。
|
||||
5. **气泡标题形态**:`{中文名}` 或 `{中文名} · {动作}`,例如「创作 · 流程编排 · 产出」,不要「Worker · design-flow 产出」。
|
||||
|
||||
---
|
||||
|
||||
## 2. 生命周期 / 阶段
|
||||
|
||||
| 内部 id / 词 | 用户可见 | 业界对照与选用说明 |
|
||||
|--------------|----------|-------------------|
|
||||
| `design` | **创作** | 谈【剧本】的工作台。不用「设计」作顶栏主词。 |
|
||||
| `play` | **游玩** | 【演员】按剧本上场。不用「播放」。 |
|
||||
| `done` | **已完成** | — |
|
||||
| `idle` | **待命** | — |
|
||||
| `running` | **执行中** | — |
|
||||
| `waiting_user` | **等待你** | — |
|
||||
|
||||
---
|
||||
|
||||
## 3. 角色与系统概念(对照表)
|
||||
|
||||
| 内部词 | 用户可见(首选) | 过渡别称 | 说明 |
|
||||
|--------|------------------|----------|------|
|
||||
| Agent / Main Agent / orchestrator | **导演** | 总管(旧) | 调度【演员】、推进【剧本】;气泡可用「导演 · 思考」。 |
|
||||
| Worker | **演员** | Worker / 执行单元 | UI 标题用中文名;检查器可写演员(Worker)。 |
|
||||
| Tool | **工具** | Tool | 导演 tool call;气泡可用「工具 · 读黑板」。 |
|
||||
| Skill pack / recipe(用户选型) | **导演**(选项名) | 能力包 / 配方(旧) | 新建下拉只出现导演选项(世界模拟器、扩写助手…)。 |
|
||||
| Module / modules pool | **能力** | 组件池(作者文档) | 美学纲领与交互范式等;见 `world-simulator-modules.md`。 |
|
||||
| Creation flow + Worker 集 | **剧本** | — | 动态谈成;非死选单。 |
|
||||
| Blackboard | **黑板** | 上下文板 | — |
|
||||
| Artifact | **产物** | — | 待验收输出。 |
|
||||
| Accept / Review | **验收** / **接受** | — | 按钮用「接受」;阶段说明用「验收」。 |
|
||||
| Intake | **需求描述** | 启动填空 | — |
|
||||
| Burst | **本轮调度** | — | 少对用户说 burst。 |
|
||||
| Questions card | **询问卡** | Questions | 详见 `ui-design.md` §8。 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 创作期磁盘 Worker(高频)
|
||||
|
||||
| 内部 id | 用户可见 | 备注 |
|
||||
|---------|----------|------|
|
||||
| `design-flow` | **创作 · 流程编排** | 以已选【导演】为起点,编排/增量修订可变 DAG → 写入剧本(创作流程) |
|
||||
| `design-step` | **创作 · 执行步骤** | 按剧本执行当前【能力】 |
|
||||
| `opening-generator` | **开局 · 开场白** | 创作末尾可选 |
|
||||
| `design-core` 等 | (已废弃) | 旧分步 skill;勿再调度 |
|
||||
|
||||
标题动作后缀(拼在中文名后):
|
||||
|
||||
| 动作 | 后缀 |
|
||||
|------|------|
|
||||
| 运行中 | (可省略或「执行中」) |
|
||||
| 产出 / 已完成 | **· 产出** |
|
||||
| 提问 | **· 提问** |
|
||||
| 占位 | **· 占位** |
|
||||
|
||||
---
|
||||
|
||||
## 5. 游玩期常用演员(缺省)
|
||||
|
||||
声明里可覆盖;无中文名时用下表:
|
||||
|
||||
| 内部 id | 用户可见 |
|
||||
|---------|----------|
|
||||
| `narrator` | **叙事转述** |
|
||||
| `role-decide` | **角色决策** |
|
||||
| `world-simulator` | **世界推演** |
|
||||
| `round-present` | **回合呈现** |
|
||||
|
||||
导演选项展示名示例:`world-simulator`(recipe)→ **世界模拟器**;`expand-assistant` → **扩写助手**。
|
||||
|
||||
**创造演员时**:规格里另写 `name`(中文展示名)。`ref` 仍用英文 kebab;UI 优先 `name`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 创作单位 id(检查器 / 进度)
|
||||
|
||||
| 内部 id 形态 | 用户可见规则 | 示例 |
|
||||
|--------------|--------------|------|
|
||||
| `phase:core` | **单位 · 核心** | — |
|
||||
| `phase:refine` | **单位 · 细化** | — |
|
||||
| `worker:{ref}` | **演员 · {中文名或 ref}** | `worker:narrator` → 演员 · 叙事转述 |
|
||||
| `fixed:{topic}` | **能力 · {话题中文}** | `fixed:aesthetics-interaction` → 能力 · 美学纲领与交互范式 |
|
||||
| `resident:{id}` | **常驻 · {id 或名}** | — |
|
||||
|
||||
能力话题建议译名:
|
||||
|
||||
| topic | 用户可见 |
|
||||
|-------|----------|
|
||||
| `aesthetics-interaction` | 美学纲领与交互范式 |
|
||||
| `interaction` | 交互范式(旧;已并入上一行) |
|
||||
| `narrative_guide` | 叙事指南 |
|
||||
| `input_protocol` | 输入协议 |
|
||||
| `core_premise` | 核心前提 |
|
||||
| `aesthetics` | 美学纲领(旧;已并入美学纲领与交互范式) |
|
||||
|
||||
---
|
||||
|
||||
## 7. 等待态 / 焦点(对用户)
|
||||
|
||||
| `waitingReason.kind` | 状态摘要(短) | 焦点动作 |
|
||||
|----------------------|----------------|----------|
|
||||
| `intake` | 描述需求 | 描述创作需求 |
|
||||
| `input` | 补充说明 | 补充说明 / 回答追问 |
|
||||
| `worker_questions` | 回答提问 | 回答 · {中文演员名} |
|
||||
| `approve_step` | 确认执行 | 建议调用 {中文名} |
|
||||
| `review_artifact` | 验收产物 | 验收产物 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 禁止出现在用户主路径上的写法
|
||||
|
||||
- `Worker · design-core`、`run_worker(design-core)`
|
||||
- 顶栏 / pill 写 `design` / `play` 英文
|
||||
- 「总管会调度 design-core」(应写「导演会先谈剧本」或「将开始:创作 · 流程编排」)
|
||||
- 新建作品同时出现「导演 + 配方」两层选择
|
||||
- 询问卡标题直接写 `design-core`
|
||||
|
||||
技术日志、导出里的「调试附录」、开发者文档不受本条限制,但默认导出给用户的 Markdown 应走同一套映射。
|
||||
|
||||
---
|
||||
|
||||
## 9. 相关文档
|
||||
|
||||
| 文档 | 关系 |
|
||||
|------|------|
|
||||
| `ui-design.md` | 布局与心流;文案须服从本文 |
|
||||
| `architecture.md` | 内部术语;用户侧以本文为准 |
|
||||
| `daily-use-p0.md` | 导演 / 剧本产品定义 |
|
||||
| `world-simulator-modules.md` | 【能力】与导演(recipes)作者清单 |
|
||||
| `creation-playbook.md` | 创作/游玩流程概念 |
|
||||
| `design-orchestrator-guide.md` | 创作方法(可继续写英文 id,面向作者) |
|
||||
@@ -1,279 +1,106 @@
|
||||
# Worker Skill 格式
|
||||
|
||||
## 1. 定位
|
||||
> **文档层级:Worker / 声明契约格式(非系统架构)。**
|
||||
> 上下文编译原则见 [`architecture.md`](./architecture.md)、[`context-assembly.md`](./context-assembly.md)。
|
||||
|
||||
**Worker Skill** 服务 **Worker Agent**:规定 **读哪些 tag、写哪些 tag、怎么做** 本阶段产出。
|
||||
## 1. 定位(现行:声明驱动)
|
||||
|
||||
Worker **从属于某一个总管 Skill 包**,不与其它 skill 共享。
|
||||
**默认包 `world-simulator`:**
|
||||
|
||||
```text
|
||||
总管:run_worker(write-rules)
|
||||
→ Runtime 读 workers/write-rules/SKILL.md 的 inputTags / outputTags
|
||||
→ 从黑板取匹配条目 → Worker 执行 → 写回 outputTags
|
||||
play 时执行契约 = accept 后的 设计.worker集 某条 workers[](实例 Worker 声明)
|
||||
可选模板 = worker-templates/{ref}.yaml(design-intake 合并默认值)
|
||||
磁盘 SKILL.md = 仅创建阶段必要 worker(现:design-intake)
|
||||
```
|
||||
|
||||
规格背景见 `docs/tag-blackboard.md`、`docs/context-assembly.md`。
|
||||
总管 `run_worker(id)` → Runtime 校验 id ∈ Worker 声明 → 从 **Worker 集条目**(+ 可选模板合并)拼 prompt → 写声明的 `outputs`。
|
||||
|
||||
**不要**为每个 play ref 预置 `workers/narrator/SKILL.md`;实例差异写在 Worker 集里。
|
||||
|
||||
规格见 `docs/tag-blackboard.md`、`docs/context-assembly.md`、`skills/dialogue/world-simulator/worker-templates/`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 存储位置
|
||||
## 2. 创作阶段 Worker(磁盘 SKILL.md)
|
||||
|
||||
仅包内 **design 专用** worker 用磁盘文件:
|
||||
|
||||
```text
|
||||
skills/novel/weird-rules-short/
|
||||
skills/dialogue/world-simulator/
|
||||
├── orchestrator.md
|
||||
├── worker-templates/ # 可选模板,非执行文件
|
||||
└── workers/
|
||||
├── write-rules/SKILL.md
|
||||
└── review-infer/SKILL.md
|
||||
└── design-intake/SKILL.md
|
||||
```
|
||||
|
||||
- 目录名 = 包内 **worker id**。
|
||||
- 文件统一 **`SKILL.md`**。
|
||||
- **没有** 全局共享 worker 目录。
|
||||
- 目录名 = worker id。
|
||||
- 文件名固定 **`SKILL.md`**。
|
||||
|
||||
---
|
||||
|
||||
## 3. Frontmatter
|
||||
## 3. Design Worker Frontmatter(示例)
|
||||
|
||||
```yaml
|
||||
---
|
||||
id: write-rules
|
||||
skill: weird-rules-short
|
||||
name: 规则与解析创作
|
||||
description: >-
|
||||
从 需求.核心要点 推演内部核心,产出规则与说明。
|
||||
version: 1
|
||||
id: design-intake
|
||||
skill: world-simulator
|
||||
name: 实例设计 · Worker 集
|
||||
stage: design
|
||||
inputTags:
|
||||
- "需求.核心要点"
|
||||
- "验收.读者视角.记录"
|
||||
- "验收.作者视角.记录"
|
||||
- "用户.修改说明"
|
||||
- "用户.需求"
|
||||
outputTags:
|
||||
- "核心.危险.隐藏"
|
||||
- "规则.草稿"
|
||||
- "规则.说明.草稿"
|
||||
- "设计.worker集"
|
||||
- "设计.worker集.草稿"
|
||||
inputMerge: latest
|
||||
---
|
||||
```
|
||||
|
||||
| 字段 | 用途 |
|
||||
|------|------|
|
||||
| `id` | 包内 skill id,与 manifest 注册表一致 |
|
||||
| `skill` | 所属 orchestrator 包 name |
|
||||
| `inputTags` | Runtime 从黑板取数的 tag(精确或 `前缀.*`) |
|
||||
| `outputTags` | 允许写回的 tag;Runtime 校验 |
|
||||
| `inputMerge` | 可选,`latest`(默认)或 `concat` |
|
||||
| `contextSegments` | 可选,上下文拼接:上半 static、下半 dynamic(见 §3.1) |
|
||||
| `contextIsolation` | 可选:`none` \| `role_pov` \| `blind_review` |
|
||||
| `id` | worker id |
|
||||
| `skill` | 所属包 name |
|
||||
| `inputTags` / `outputTags` | 黑板读写白名单 |
|
||||
| `contextSegments` | 可选;上下拼接 |
|
||||
|
||||
### 3.1 contextSegments(上下文拼接)
|
||||
### 3.1 contextSegments
|
||||
|
||||
见 `docs/context-assembly.md`。示例:
|
||||
|
||||
```yaml
|
||||
contextSegments:
|
||||
- id: brief
|
||||
tier: static
|
||||
tags: ["book.brief"]
|
||||
label: "## 创作需求"
|
||||
- id: history
|
||||
tier: dynamic
|
||||
tags: ["运行.事件流"]
|
||||
policy: tail_lines_80
|
||||
- id: turn
|
||||
tier: dynamic
|
||||
tags: ["可见信息", "用户.最新输入"]
|
||||
label: "## 本轮"
|
||||
```
|
||||
|
||||
未声明时 Runtime 回退为 JSON `inputs`(当前实现)。
|
||||
|
||||
**review-infer 示例**(不得读隐藏核心):
|
||||
|
||||
```yaml
|
||||
inputTags:
|
||||
- "需求.核心要点"
|
||||
- "规则.草稿"
|
||||
- "规则.说明.草稿"
|
||||
outputTags:
|
||||
- "验收.读者视角.记录"
|
||||
```
|
||||
|
||||
**review-author 示例**(可读隐藏核心):
|
||||
|
||||
```yaml
|
||||
inputTags:
|
||||
- "需求.核心要点"
|
||||
- "核心.危险.隐藏"
|
||||
- "规则.草稿"
|
||||
- "规则.说明.草稿"
|
||||
outputTags:
|
||||
- "验收.作者视角.记录"
|
||||
```
|
||||
见 `docs/context-assembly.md`。
|
||||
|
||||
---
|
||||
|
||||
## 4. 正文章节
|
||||
## 4. 实例声明字段(写入 `设计.worker集`)
|
||||
|
||||
```markdown
|
||||
# 标题
|
||||
|
||||
## 角色与口吻
|
||||
## 能力范围 # 能做什么 / 不能做什么
|
||||
## 思维链与自检
|
||||
## 上下文用法 # 各 inputTag 如何使用(不重复 frontmatter 列表)
|
||||
## 输出格式 # 各 outputTag 的 content 格式
|
||||
## 示例 # 可选
|
||||
```
|
||||
|
||||
正文中用 **tag 名** 指代上下文,例如「读 `需求.核心要点`」而非旧 key `book.brief`。
|
||||
|
||||
### 评估类 Worker
|
||||
|
||||
总管 orchestrator 只写:`rules 确认后 → run review-infer`。
|
||||
|
||||
本 SKILL 写 **评估怎么做**、verdict 写入 `验收.*.记录` 的 JSON 形状等。
|
||||
|
||||
### 用户回合 worker(user-turn)
|
||||
|
||||
**用途:** 该环节 **完全由用户输入** 组成,LLM 不替用户选行动(21 点玩家、线下人类一方等)。
|
||||
|
||||
**与 role-decide 的区别:**
|
||||
|
||||
| | role-decide | user-turn |
|
||||
|--|-------------|-----------|
|
||||
| 决策 | LLM 产出 `.思考` + `.行动` | 用户经 ask_user 提供;worker **只**写 `.行动` |
|
||||
| LLM | 需要 | 仅需展示/校验/格式化(可无生成模型) |
|
||||
|
||||
**frontmatter 示例:**
|
||||
design-intake 产出的每条 worker:
|
||||
|
||||
```yaml
|
||||
id: user-turn
|
||||
skill: blackjack-roleplay
|
||||
name: 用户回合
|
||||
description: 展示局面,收集用户合法行动,写入角色.用户.行动
|
||||
inputTags:
|
||||
- "角色.用户.可见信息"
|
||||
- "场景.公开叙述"
|
||||
outputTags:
|
||||
- "角色.用户.行动"
|
||||
```
|
||||
|
||||
**SKILL 正文要点:**
|
||||
|
||||
```markdown
|
||||
## 角色
|
||||
你是 **用户操作的采集器**,不是玩家 AI。禁止替用户选择行动。
|
||||
|
||||
## 执行
|
||||
1. 读可见信息与合法行动集
|
||||
2. ask_user:简短展示局面 + 列出可选行动
|
||||
3. 校验用户输入是否在合法集内;不合法则再问
|
||||
4. 写 `角色.用户.行动`(行动选择 + 可选说话)
|
||||
|
||||
## 禁止
|
||||
- 调用 LLM 模拟用户策略
|
||||
- 写入 `.思考`(用户无内心 tag,或仅 UI 留空)
|
||||
```
|
||||
|
||||
编排:总管在轮到用户时 `run_worker(user-turn)`;world-engine 与 role-decide **同一套** 读 `.行动` 规则。
|
||||
|
||||
---
|
||||
|
||||
## 5. 运行时输出协议
|
||||
|
||||
Worker LLM 返回 JSON(Phase A);Phase B 改为 tool call。语义不变:
|
||||
|
||||
```json
|
||||
{
|
||||
"outputs": {
|
||||
"规则.草稿": "...",
|
||||
"规则.说明.草稿": "..."
|
||||
},
|
||||
"summary": "50字以内摘要",
|
||||
"askUser": null
|
||||
}
|
||||
```
|
||||
|
||||
- `outputs` 的 key 必须是 **outputTags 中的 tag**(或与 tag 一一映射的别名,由 Runtime 归一化)。
|
||||
- 缺信息时 `askUser` 提问,不臆造。
|
||||
|
||||
Runtime 写黑板:
|
||||
|
||||
```ts
|
||||
{
|
||||
id: "...",
|
||||
tag: "规则.草稿",
|
||||
content: "...",
|
||||
source: "write-rules",
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. ask_user
|
||||
|
||||
任何 worker 可中途提问。Runtime 暂停并保存 `resumeContext`(workerId 等);恢复时 **重新** 从 SKILL 读 inputTags,不依赖总管。
|
||||
|
||||
---
|
||||
|
||||
## 7. 命名原则
|
||||
|
||||
Worker id 按 **本包流程职责** 命名,包内唯一:
|
||||
|
||||
| 包 | worker id | 职责 |
|
||||
|----|-----------|------|
|
||||
| weird-rules-short | write-rules | 写规则 |
|
||||
| weird-rules-short | review-infer | 读者视角验收 |
|
||||
| novel-standard | outline | 大纲 |
|
||||
|
||||
不要设计全局共享 worker id。
|
||||
|
||||
---
|
||||
|
||||
## 8. 与代码的关系
|
||||
|
||||
| 文档 | 代码 |
|
||||
|------|------|
|
||||
| frontmatter inputTags / outputTags | `src/skills/loader.ts` → `ParsedWorkerSkill` |
|
||||
| 运行时取数 | `src/worker/executor.ts` |
|
||||
| 角色 worker 独立 LLM | `llmProfileId` / `llm-bindings.yaml` | `src/skills/worker-llm.ts` |
|
||||
|
||||
当前代码仍为旧 `inputKeys` / `outputKeys` 模型;迁移以 `tag-blackboard.md` 为准。
|
||||
|
||||
---
|
||||
|
||||
## 9. Worker 独立 LLM(可选,预留多 AI 博弈)
|
||||
|
||||
默认:worker 与会话 **同一 ApiProfile**(设置页当前选中的 profile)。
|
||||
|
||||
### 9.1 Worker SKILL frontmatter
|
||||
|
||||
```yaml
|
||||
llmProfileId: "<profiles.json 中的 ApiProfile.id>"
|
||||
```
|
||||
|
||||
省略 = 走 skill 包 `llm-bindings.yaml` 或会话默认。
|
||||
|
||||
### 9.2 Skill 包 llm-bindings.yaml
|
||||
|
||||
```yaml
|
||||
defaultProfileId: null # null = 会话默认
|
||||
|
||||
workers:
|
||||
world-engine: {}
|
||||
role-decide:
|
||||
byRole:
|
||||
A: "<profile-id-1>"
|
||||
B: "<profile-id-2>"
|
||||
- ref: narrator # 能力库 id;null = gap
|
||||
role: transcription
|
||||
duty: …
|
||||
when: …
|
||||
rationale: …
|
||||
context:
|
||||
static: [设计.worker集]
|
||||
dynamic: [运行.本轮.裁决]
|
||||
outputs: [输出.用户展示]
|
||||
presentation:
|
||||
tone: …
|
||||
```
|
||||
|
||||
Runtime 解析顺序见 `src/skills/worker-llm.ts`。
|
||||
`role-decide` 按 `slots.世界.当前角色.id` 匹配 `byRole`。
|
||||
|
||||
### 9.3 设计意图
|
||||
|
||||
- 配置仍在 **profiles.json**(或 .env),不在 SKILL 里写密钥
|
||||
- 同一 skill 可让不同角色用不同模型/API,实现真实多 agent 博弈
|
||||
- 总管 LLM 不受 worker 绑定影响(始终会话默认)
|
||||
未写全的 `context`/`outputs` 可由 `worker-templates/{ref}.yaml` 合并。
|
||||
|
||||
---
|
||||
|
||||
## 5. 执行要点
|
||||
|
||||
- Agent **不**指定 inputTags;读 Worker 声明 / 模板。
|
||||
- `ref: null` + `gap`:声明了职责但无模板 / SKILL,需补声明或 temp worker。
|
||||
- 验收:`design-intake` 默认 `user_confirmed`;play 中间 worker 可 `no_confirmation`。
|
||||
|
||||
## 相关
|
||||
|
||||
| 文档 | 关系 |
|
||||
|------|------|
|
||||
| `creation-playbook.md` | 创作流 |
|
||||
| `worker-declaration.ts` | Runtime 声明校验 |
|
||||
| `worker-templates/README.md` | 可选模板 |
|
||||
|
||||
139
docs/world-simulator-modules.md
Normal file
139
docs/world-simulator-modules.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# 世界模拟器 · 导演与能力撰写清单
|
||||
|
||||
> 给作者用。用户侧术语:**`docs/ui-glossary.md` §0**。
|
||||
> 运行:选导演 → 编排**增量**剧本 DAG → `design-step` 执行能力;可再扩步反复调用。
|
||||
> **标准范例**:`modules/aesthetics-interaction/prompt.md`。
|
||||
> **给外部 AI 的完整泛用规范**:**`docs/briefs/capability-authoring-brief.md`**(项目概述 + 称呼 + 格式契约)。
|
||||
|
||||
## 两层
|
||||
|
||||
```text
|
||||
【导演】recipes/ 【能力】modules/
|
||||
└─ 编排增量 DAG → design-step 注入能力块 →(open 则再编排)→ 演员上场
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 能力文档格式(程序可切割)
|
||||
|
||||
每个能力 = `modules/{id}/prompt.md` + `catalog.yaml` 一行。
|
||||
|
||||
程序**只认 fence 语言标签**切割,不认散文/`##` alone:
|
||||
|
||||
| 块 id | 必填 | 用途 |
|
||||
|-------|------|------|
|
||||
| `meta` | 建议 | YAML:name / id / artifact / declaration / when / when_not / boundary |
|
||||
| `opening` | 可选 | **默认问题**正文;程序发给用户,不经 LLM |
|
||||
| `task` | 是 | 本步任务与验收边界 |
|
||||
| `principles` | 建议 | 原则 |
|
||||
| `probe` | 建议 | 追问策略 |
|
||||
| `output` | 是 | 产物形状(多为 JSON) |
|
||||
| `checklist` | 建议 | 自检 |
|
||||
| `examples` | 可选 | 好/坏对照 |
|
||||
|
||||
````markdown
|
||||
# 能力中文名
|
||||
|
||||
## meta
|
||||
```meta
|
||||
name: …
|
||||
id: …
|
||||
artifact: 设计.…
|
||||
declaration: …
|
||||
```
|
||||
|
||||
## opening
|
||||
```opening
|
||||
(用户看到的开场白;可省略整块 = 本步直接调 LLM)
|
||||
```
|
||||
|
||||
## task
|
||||
```task
|
||||
…
|
||||
```
|
||||
|
||||
## principles
|
||||
```principles
|
||||
…
|
||||
```
|
||||
|
||||
## probe
|
||||
```probe
|
||||
…
|
||||
```
|
||||
|
||||
## output
|
||||
```output
|
||||
{ … }
|
||||
```
|
||||
|
||||
## checklist
|
||||
```checklist
|
||||
- [ ] …
|
||||
```
|
||||
````
|
||||
|
||||
切割实现:`parseModulePromptSections` / `extractModuleOpening` / `formatModulePromptForLlm`(`src/skills/creation-flow.ts`)。
|
||||
注入 LLM 时按块顺序拼接,**不含** `opening`(开场已由程序发出)。
|
||||
|
||||
### catalog 一行(插入导演提示词)
|
||||
|
||||
| 字段 | 作用 |
|
||||
|------|------|
|
||||
| `id` / `name` / `declaration` / `artifact` | 选型与产物映射 |
|
||||
| `repeatable` | 可选;`true` = 允许同能力多次编入增量 DAG |
|
||||
| `opening` | 可选覆盖;一般只写在 prompt 的 `opening` 块 |
|
||||
|
||||
### 默认问题节奏(通用)
|
||||
|
||||
```text
|
||||
程序发 opening → 用户首答 → LLM(opening + 首答 + 切割后的方法块 + 依赖)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 目录与清单
|
||||
|
||||
```text
|
||||
modules/catalog.yaml
|
||||
modules/{id}/prompt.md
|
||||
recipes/world-simulator|expand-assistant/recipe.yaml
|
||||
```
|
||||
|
||||
世界模拟器**可能用到**的能力(编排按需选用,勿默认全选):
|
||||
|
||||
| 能力 | id | 状态 |
|
||||
|------|-----|------|
|
||||
| 美学纲领与交互范式 | `aesthetics-interaction` | **范例已写** |
|
||||
| 实现机制 | `mechanism` | **已写** |
|
||||
| 世界蓝图与人文地理 | `world-blueprint` | **已写** |
|
||||
| 生成规则 | `generation-rules` | 骨架,**可反复** |
|
||||
| 具体实例 | `concrete-instances` | 骨架,**可反复** |
|
||||
| 拓扑图谱 | `topology` | 骨架,待细写 |
|
||||
| 设计状态栏 | `status-bar` | 骨架,待细写 |
|
||||
| 叙事指南 | `narrative` | 骨架,待细写 |
|
||||
| 变量设计与更新规则 | `variable-design` | 骨架,待细写 |
|
||||
| 变量控制上下文 | `variable-context` | 骨架,待细写 |
|
||||
| 设计回复格式 | `reply-format` | 骨架,待细写 |
|
||||
|
||||
共用收成(池内保留,按需):
|
||||
|
||||
| 能力 | id | 状态 |
|
||||
|------|-----|------|
|
||||
| Worker 规格 | `worker-spec` | 骨架,**可反复** |
|
||||
| 细化终稿 | `refine` | 骨架,待细写 |
|
||||
|
||||
| 导演 | 状态 |
|
||||
|------|------|
|
||||
| 世界模拟器 | 建议第一步:美学纲领与交互范式 |
|
||||
| 扩写助手 | 待完善 |
|
||||
|
||||
---
|
||||
|
||||
## 验收
|
||||
|
||||
1. 只选导演 → 出**近期**创作流程(`status=open`)
|
||||
2. design-step 能切割出 `opening`/`task`/…
|
||||
3. 有 `opening` 时先程序开场再 LLM
|
||||
4. 可追加同能力多次(不同 step.id);收成前 `status=closed`
|
||||
5. UI 用拍摄术语
|
||||
@@ -1,40 +1,41 @@
|
||||
# Skill 包索引
|
||||
|
||||
总管 skill 以 `orchestrator.md` + `registry.yaml` 注册后才会出现在启动列表。
|
||||
本目录下 **仅有 README 的文件夹** 为规划占位,AI 不会加载。
|
||||
> 用户侧术语:**导演 / 剧本 / 演员 / 能力**(见 `docs/ui-glossary.md` §0)。
|
||||
> 系统内核见 `docs/architecture.md`。
|
||||
|
||||
## 业务 stage 通则
|
||||
当前仅注册 **`world-simulator`**(`registry.yaml`)。
|
||||
|
||||
每个 skill 包内 orchestrator 应区分:
|
||||
## 结构(一层包)
|
||||
|
||||
```text
|
||||
instantiate(实例化) 启动询问 / setup worker → prerequisite tags;现 stageId 常叫 brief
|
||||
run(运行) 生产 worker 流水线(write / review / outline …)
|
||||
done finish → 确认稿归档 Book
|
||||
skills/
|
||||
registry.yaml
|
||||
dialogue/
|
||||
world-simulator/ # 默认 skill 包(内部英文 id)
|
||||
orchestrator.md # 导演调度 manifest
|
||||
recipes/ # 【导演】选项(用户新建时选)
|
||||
modules/ # 【能力】共用工序
|
||||
workers/ # 创作期磁盘演员
|
||||
design-flow/
|
||||
design-step/
|
||||
opening-generator/
|
||||
worker-templates/ # 游玩演员默认契约(合并进 Worker 集)
|
||||
```
|
||||
|
||||
详见 `docs/tag-blackboard.md` §2。
|
||||
## 运行(简)
|
||||
|
||||
## 已启用
|
||||
```text
|
||||
用户选【导演】
|
||||
→ design-flow:编排【剧本】增量 DAG(设计.创作流程,可追加/可反复)
|
||||
→ design-step:逐步执行能力 → … → 设计.worker集
|
||||
→ (可选)opening-generator
|
||||
→ 用户手动进 play → 按声明调度【演员】
|
||||
```
|
||||
|
||||
| 包 | 路径 | instantiate → run |
|
||||
|---|---|---|
|
||||
| basic | `novel/basic/` | `book.brief` → outline |
|
||||
| weird-rules-short | `novel/weird-rules-short/` | `book.brief` → write-rules + 双 review |
|
||||
| roleplay-game-theory | `dialogue/roleplay-game-theory/` | instantiate → world-engine + role-decide × N + present-round(多 AI 可选) |
|
||||
路径 / `run_worker` id **保持英文**;中文只出现在 `name` / `declaration` / 正文。
|
||||
|
||||
## 规划中(TODO)
|
||||
## 相关
|
||||
|
||||
| 包 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| quick-write | `novel/quick-write/` | 简易档:弱化 tag,全量 LLM |
|
||||
| interactive-novel | `novel/interactive-novel/` | 长篇:多 tag instantiate + 多轮 run |
|
||||
| novel-standard | `novel/novel-standard/` | 标准流水线(或与 interactive 合并) |
|
||||
| scene-roleplay | `dialogue/scene-roleplay/` | 扮演:角色/世界 instantiate + 互动 run |
|
||||
| world-simulator | `dialogue/world-simulator/` | 世界模拟器:Step1–14 实例化设计 + run(大工程,设计期) |
|
||||
|
||||
**已取消独立包:** `character-card-author` / `character-card-play` — 角色设定与互动并入 `scene-roleplay` 的 instantiate / run,跨 Session 复用走 Book。
|
||||
|
||||
占位目录 `dialogue/character-card-*` 仅保留说明,不注册。
|
||||
|
||||
标签命名规范等细节以后补 `docs/tag-vocabulary.md`(低优先级)。
|
||||
- 作者清单:`docs/world-simulator-modules.md`
|
||||
- 包内说明:`dialogue/world-simulator/README.md`
|
||||
- 创作方法长文:`docs/design-orchestrator-guide.md`(部分章节仍写旧分步名,以本包为准)
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# character-card-author(已合并概念)
|
||||
|
||||
**不再作为独立 skill 包。**
|
||||
|
||||
角色卡撰写 = **实例化(instantiate)阶段** 的一种产出形态:在扮演类 skill(规划中的 `scene-roleplay`)里,通过启动询问或 setup worker 写入 tag,例如:
|
||||
|
||||
```text
|
||||
用户.角色需求 | 用户.互动偏好
|
||||
角色.A.设定 | 角色卡.口吻样例 | 角色卡.行为边界
|
||||
角色卡.确认稿
|
||||
```
|
||||
|
||||
跨 Session 复用角色 → 从 **Book** 加载已有 tag,不必再跑完整实例化。
|
||||
|
||||
见 `docs/tag-blackboard.md` §2.6、`skills/README.md`。
|
||||
@@ -1,13 +0,0 @@
|
||||
# character-card-play(已合并概念)
|
||||
|
||||
**不再作为独立 skill 包。**
|
||||
|
||||
角色卡游玩 = 同一 skill 在 **run 阶段** 的互动流水线:prerequisite tags(含 `角色卡.确认稿` 或 `角色.*.设定`)已在 instantiate 填好或从 Book 加载后,总管调度扮演 worker。
|
||||
|
||||
```text
|
||||
instantiate 收集/加载角色与世界 tag
|
||||
run 多轮互动、用户.控制模式、场景反馈
|
||||
done 归档 Book
|
||||
```
|
||||
|
||||
见 `docs/tag-blackboard.md` §2、`skills/dialogue/scene-roleplay/README.md`(规划)。
|
||||
@@ -1,16 +0,0 @@
|
||||
# 可选:为本 skill 包的 worker 指定独立 LLM API(profiles.json 中的 profile id)
|
||||
#
|
||||
# 省略本文件、或 profileId 为空 → 全部 worker 使用会话当前默认 API(与总管相同)。
|
||||
# 配置多个 profile 后,可为不同角色绑定不同模型,实现「多 AI 博弈」。
|
||||
|
||||
# defaultProfileId: null
|
||||
|
||||
workers:
|
||||
setup-scenario: {}
|
||||
world-engine: {}
|
||||
role-decide:
|
||||
# 示例:按决策角色 id 分配不同 profile(把 uuid 换成你本地的 ApiProfile.id)
|
||||
# byRole:
|
||||
# A: "00000000-0000-0000-0000-000000000001"
|
||||
# B: "00000000-0000-0000-0000-000000000002"
|
||||
present-round: {}
|
||||
@@ -1,441 +0,0 @@
|
||||
---
|
||||
name: roleplay-game-theory
|
||||
description: >-
|
||||
何时选用:用户想模拟多个不同角色在简单博弈/思想实验处境下的决策与互动
|
||||
(如囚徒困境、最后通牒、公共池、信任游戏等)。
|
||||
不适用:自由剧场扮演、写小说章节、长篇叙事、需要复杂世界观的 RPG。
|
||||
产出:结构化博弈实例 +(后续 run 阶段)多角色决策模拟记录。
|
||||
category: dialogue
|
||||
bookKind: dialogue
|
||||
version: 1
|
||||
tags:
|
||||
- game_theory
|
||||
- roleplay
|
||||
- simulation
|
||||
workers:
|
||||
- setup-scenario
|
||||
- world-engine
|
||||
- role-decide
|
||||
- present-round
|
||||
sharedContext: shared-context.md
|
||||
---
|
||||
|
||||
# 角色扮演博弈 · 总管
|
||||
|
||||
你是本 skill 的 **总管**,只负责 **流程调度**:读黑板 → 判断阶段 → `run_worker` / `ask_user` / `finish`。
|
||||
不写角色决策、不替 worker 模拟回合——执行细节在包内 `workers/*/SKILL.md`。
|
||||
固定体裁规则在 `shared-context.md`,由 Runtime 注入 **本包所有 worker**,总管不读。
|
||||
|
||||
**当前进度:** instantiate + run(单轮/多轮 simulate + present-round)已定义;`semi_auto` 推进与 programmatic 验收待接入。
|
||||
|
||||
设计方法见 `docs/skill-design-guide.md`(抽象循环 → L0~L3 分层 → 倒推标签)。
|
||||
|
||||
---
|
||||
|
||||
## 架构:三实体 + 展示 + 信息隔离
|
||||
|
||||
```text
|
||||
world-engine 中立世界机:发 L3 可见信息 → 收齐行动 → 裁决 → 追加 L2 事件流
|
||||
role-decide 各角色独立决策:读 L0~L3 → 写 `.思考`(仅用户)与 `.行动`(agent 可见)
|
||||
present-round 展示:读 L0 + 本轮产物 + 思考 → 写 `输出.用户展示`
|
||||
总管 调度轮次;role-decide 须带 workerContext.roleId
|
||||
```
|
||||
|
||||
**抽象循环:** 角色行动 → 世界反应 → 角色行动 → …(每轮末 present-round → 用户验收)
|
||||
|
||||
**分工:** 角色内心由 `role-decide` 产出;世界只写客观事实;**用户可见编排由 present-round 产出**,总管不拼长文。
|
||||
|
||||
**多 AI 博弈(可选):** 包内 `llm-bindings.yaml` 为 `role-decide.byRole` 指定不同 `ApiProfile.id`;默认全部用会话同一 API。
|
||||
|
||||
---
|
||||
|
||||
## 启动询问
|
||||
|
||||
选定本 skill 后,**第一个创作询问**。系统从本节读取问什么、写入哪。
|
||||
|
||||
**向用户展示:**
|
||||
|
||||
```text
|
||||
你选择了「角色扮演博弈」。在开始模拟之前,请告诉我:
|
||||
|
||||
1. 实验情境
|
||||
- 可直接说经典名(囚徒困境、最后通牒、公共池、信任游戏……)
|
||||
- 或用自己的话描述一个「每人要选行动、结果取决于组合」的简单局面
|
||||
|
||||
2. 参与角色(2~4 人即可)
|
||||
- 每个角色用一句话说明策略倾向(如:算计型、讲公平、怕吃亏、爱冒险)
|
||||
- 若有想用的称呼可一并说
|
||||
|
||||
3. 进程
|
||||
- 单轮定胜负 / 重复多轮 / 有限 N 轮 / 直到某条件(如有人破产)
|
||||
|
||||
4. 信息结构(可选)
|
||||
- 大家知道的都一样?有无私密信息或误解?
|
||||
|
||||
5. 输出偏好(可选)
|
||||
- 要不要看角色思考(`.思考` tag,仅你可见)?
|
||||
- 偏冷静报告还是带一点场景描写?
|
||||
|
||||
6. 特殊规则或收益(可选)
|
||||
- 例如:背叛惩罚加倍、允许口头承诺但不具约束力
|
||||
|
||||
可以一次说完。不必懂博弈论术语——我会整理成可模拟的结构。
|
||||
```
|
||||
|
||||
**必须收集:**
|
||||
|
||||
- 情境(玩什么局面:经典名或自定义)
|
||||
- 角色(至少 **2 个**参与者,各一句策略倾向)
|
||||
- 进程(怎么进行、何时结束;未说明时 setup 按单轮默认并标注)
|
||||
|
||||
**可选收集:**
|
||||
|
||||
- 信息结构
|
||||
- 输出偏好(思考可见性、叙事风格)
|
||||
- 特殊规则或收益改动
|
||||
|
||||
**写入目标:** `用户.博弈需求`
|
||||
|
||||
**足够进入 setup 当:** 上述 3 项必要填空已齐,用户确认后写入 `用户.博弈需求`,再调度 setup-scenario。
|
||||
|
||||
---
|
||||
|
||||
## 实例化
|
||||
|
||||
**职责:** 把 `用户.博弈需求` 整理为结构化 prerequisite tags,供后续 run 阶段模拟 worker 使用。
|
||||
|
||||
### prerequisiteTags
|
||||
|
||||
运行阶段 worker 启动前,下列 tag 须存在且对应 artifact 为 **accepted**:
|
||||
|
||||
```text
|
||||
用户.博弈需求
|
||||
情境.实验.设定
|
||||
博弈.规则.草稿
|
||||
博弈.参数.草稿
|
||||
博弈.角色列表
|
||||
角色.*.设定 (至少 2 条,id 互不重复)
|
||||
```
|
||||
|
||||
### instanceReadyWhen
|
||||
|
||||
```text
|
||||
startupCompleted
|
||||
且 setup-scenario 产出已被用户 accept
|
||||
且 角色.*.设定 匹配条目数 ≥ 2
|
||||
```
|
||||
|
||||
等价说法:**instantiate 阶段完成 = 用户确认结构化实例,可进入 run。**
|
||||
|
||||
### setupWorkers
|
||||
|
||||
| worker | 时机 | 说明 |
|
||||
|--------|------|------|
|
||||
| setup-scenario | `用户.博弈需求` 已写入,尚无 accepted 的 `情境.实验.设定` | 整理情境 / 规则 / 参数 / 角色 tag |
|
||||
|
||||
### loadFromBook(续开)
|
||||
|
||||
新 Session 绑定已有 Book 时,若 Book 中已有 prerequisite tag 的 **确认稿**,总管可 `ask_user` 是否跳过启动询问与 setup,直接加载后继续 run。
|
||||
|
||||
**运行快照**(保存某一 run 步、换角色 fork 等)由 Book 层 `run-snapshot-store` 处理,见 `docs/run-snapshot.md`——**不**写进本 orchestrator。
|
||||
|
||||
---
|
||||
|
||||
## 产物说明
|
||||
|
||||
| 产出 | 黑板 tag | 写入者 | 阶段 | 对用户可见 |
|
||||
|------|----------|--------|------|------------|
|
||||
| 用户原始需求 | 用户.博弈需求 | 启动询问 / 用户 | instantiate | 是 |
|
||||
| 实验情境 | 情境.实验.设定 | setup-scenario | instantiate | 是 |
|
||||
| 博弈规则 | 博弈.规则.草稿 | setup-scenario | instantiate | 是 |
|
||||
| 模拟参数 | 博弈.参数.草稿 | setup-scenario | instantiate | 是 |
|
||||
| 角色列表 | 博弈.角色列表 | setup-scenario | instantiate | 是 |
|
||||
| 角色设定 | 角色.{id}.设定 | setup-scenario | instantiate | 是 |
|
||||
| 世界状态 | 世界.当前状态 | world-engine | run | 是 |
|
||||
| 当前轮次 | 世界.当前轮次 | world-engine | run | 是 |
|
||||
| 事件流(L2 记忆) | 运行.事件流 | world-engine | run | 内部(追加式) |
|
||||
| 角色可见信息(L3) | 角色.{id}.可见信息 | world-engine | run | 内部 |
|
||||
| 思考 | 角色.{id}.思考 | role-decide | run | **仅用户**(经 present-round) |
|
||||
| 行动 | 角色.{id}.行动 | role-decide | run | 用户 + 其他 agent(经 world-engine 公开) |
|
||||
| 裁决记录 | 世界.裁决.记录 | world-engine | run | 是(仅规则与状态,无剧情) |
|
||||
| 公开叙述 | 场景.公开叙述 | world-engine | run | 是(客观事实陈述) |
|
||||
| 回合摘要 | 输出.回合摘要 | world-engine | run | 是(事实摘要;**不含**角色内心) |
|
||||
| 用户展示 | 输出.用户展示 | present-round | run | 是(验收用主稿) |
|
||||
|
||||
**Book:** `bookKind: dialogue`。实例化确认稿归档后,可在新 Session 复用同一博弈设定。运行快照见 `docs/run-snapshot.md`。
|
||||
|
||||
**流程概览:**
|
||||
|
||||
```text
|
||||
instantiate:
|
||||
用户.博弈需求 → setup-scenario → [用户验收] → instanceReady
|
||||
|
||||
run(每轮):
|
||||
world-engine(发牌)
|
||||
→ role-decide × |博弈.角色列表|
|
||||
→ world-engine(裁决;追加 运行.事件流)
|
||||
→ present-round → 输出.用户展示
|
||||
→ [用户验收](默认每轮确认)
|
||||
→ 若未终局且未达轮次上限 → 下一轮
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 阶段定义
|
||||
|
||||
| stageId | 名称 | 别名 | 进入条件 | 退出条件 |
|
||||
|---------|------|------|----------|----------|
|
||||
| brief | 需求收集 | **instantiate** | skill 已选 | `用户.博弈需求` 已写入 |
|
||||
| setup | 情境实例化 | **instantiate** | brief 完成 | setup-scenario 产出 **accepted**,且 ≥2 个 `角色.*.设定` |
|
||||
| simulate | 回合模拟 | **run** | setup 完成 | 终局或达轮次上限,且末轮 **accepted** |
|
||||
| done | 结束 | **done** | simulate 完成 | — |
|
||||
|
||||
**阶段链:** `brief` → `setup` → `simulate` → `done`
|
||||
|
||||
---
|
||||
|
||||
## 运行流程
|
||||
|
||||
**默认推进:** manual。
|
||||
|
||||
### 暂停(A · 按 worker 产出)
|
||||
|
||||
| worker 产出 | acceptanceMode | 实例化可覆盖? |
|
||||
|-------------|----------------|----------------|
|
||||
| setup-scenario 全套 | user_confirmed | 否 |
|
||||
| present-round → `输出.用户展示` | user_confirmed | 是 → 启动询问「每轮验收 / 每 N 轮 / 仅终局」(写入 `博弈.参数.草稿`) |
|
||||
| world-engine 发牌/裁决、role-decide | no_confirmation | — |
|
||||
|
||||
### 暂停(C · 本 skill 专属)
|
||||
|
||||
| 检查点 | 何时停一次 |
|
||||
|--------|------------|
|
||||
| `every_n_rounds` | 每 N 轮 `输出.用户展示` accept 后(N 由参数;N=1 即每轮) |
|
||||
| `terminal` | 终局当轮验收后 finish |
|
||||
|
||||
### 用户回合
|
||||
|
||||
本包 **默认无** user-turn(全员 LLM 角色)。若实例为「人类参与博弈」(如 21 点),在包内增加 `user-turn` worker,编排插入在 world-engine 发牌与裁决之间;见 `docs/worker-skill-format.md` §用户回合 worker。
|
||||
|
||||
---
|
||||
|
||||
## 推进策略(预留 semi_auto)
|
||||
|
||||
**默认 manual:** 每轮 `输出.用户展示` 须 `user_confirmed`。
|
||||
|
||||
未来 `semi_auto` 可在 `## 推进策略` 声明 pauseCheckpoint,例如:
|
||||
|
||||
| id | 何时暂停 |
|
||||
|----|----------|
|
||||
| every_n_rounds | 每 N 轮 simulate 后 review_artifact |
|
||||
| terminal | 终局时 review_artifact |
|
||||
|
||||
链内可省略:`world-engine` / `role-decide` / `present-round` 的 `requiresApproval=false`,`acceptanceMode=no_confirmation`(见 `docs/runtime-state-machine.md` §8)。
|
||||
|
||||
---
|
||||
|
||||
## Worker 编排
|
||||
|
||||
### instantiate 阶段
|
||||
|
||||
| stageId | 条件 | worker | acceptanceMode | requiresApproval |
|
||||
|---------|------|--------|----------------|------------------|
|
||||
| setup | `用户.博弈需求` 非空,无 **accepted** 的 `情境.实验.设定`;或 reject 后重做 | setup-scenario | user_confirmed | true |
|
||||
|
||||
### run 阶段 · 单轮子流程
|
||||
|
||||
**先读 `博弈.参数.草稿` 中的「决策顺序」**,再按下表调度。
|
||||
|
||||
#### 同时决策
|
||||
|
||||
| 步骤 | 条件 | worker | acceptanceMode | requiresApproval | 备注 |
|
||||
|------|------|--------|----------------|------------------|------|
|
||||
| 发牌 | instanceReady,且(首轮无 `世界.当前状态` **或** 上轮已裁决且无待收行动) | world-engine | no_confirmation | false | 无 `角色.*.行动` 输入 |
|
||||
| 决策 | 已发牌,存在角色 R 尚无本轮 `角色.R.行动` | role-decide | no_confirmation | false | **workerContext.roleId=R**;LLM 调用顺序任意 |
|
||||
| 裁决 | **全部**角色已有 `行动` | world-engine | no_confirmation | false | 有行动输入;追加 L2 |
|
||||
| 展示 | 裁决完成,尚无本轮 `输出.用户展示` | present-round | no_confirmation | false | |
|
||||
| 终局 | 展示完成,`输出.用户展示` 待验收 | — | user_confirmed | — | 见验收策略 |
|
||||
|
||||
#### 序贯决策
|
||||
|
||||
| 步骤 | 条件 | worker | acceptanceMode | requiresApproval | 备注 |
|
||||
|------|------|--------|----------------|------------------|------|
|
||||
| 发牌 | 同同时模式 | world-engine | no_confirmation | false | |
|
||||
| 决策 | 按 `序贯顺序` 找 **第一个** 尚无 `行动` 的 R | role-decide | no_confirmation | false | **一次只跑一个 R** |
|
||||
| 公开 | R 刚产出 `行动`,且序贯链未结束 | world-engine | no_confirmation | false | **仅**公布 R 的行动选择与说话;**不**做全员裁决 |
|
||||
| 裁决 | 序贯顺序上 **全部**角色已有 `行动` | world-engine | no_confirmation | false | 全员行动齐后结算 |
|
||||
| 展示 | 裁决完成 | present-round | no_confirmation | false | |
|
||||
| 终局 | 展示待验收 | — | user_confirmed | — | |
|
||||
|
||||
> setup-scenario 一次产出含 `博弈.角色列表`(如 `A,B`)与 `博弈.参数.草稿`(含决策顺序)。
|
||||
> role-decide:**禁止** 不带 `workerContext.roleId` 调度。
|
||||
> world-engine:**禁止** 在缺行动候选时做裁决;**禁止** 在发牌模式写行动;序贯「公开」步 **禁止** 提前结算未决策角色的收益。
|
||||
|
||||
---
|
||||
|
||||
## 总管思维链
|
||||
|
||||
每轮 `planning` 按序检查,**命中第一条即行动**:
|
||||
|
||||
### instantiate
|
||||
|
||||
1. **phase = waiting_user(input)** 且 `用户.博弈需求` 未齐 → `ask_user` 补全必收集项。
|
||||
2. **brief 已齐**,无 accepted 的 `情境.实验.设定` → `run_worker(setup-scenario)`,`requiresApproval: true`。
|
||||
3. **waiting_user(review_artifact)**(setup)→ 引导用户核对规则与角色。
|
||||
4. 用户 **accept** setup → 进入 run(见下)。
|
||||
5. 用户 **reject** setup → 收 `用户.修订说明` → 重跑 setup-scenario。
|
||||
|
||||
### run(simulate)
|
||||
|
||||
6. instanceReady,无 `世界.当前状态` 或需新开一轮(无 pending 行动)→ `run_worker(world-engine)` 发牌。
|
||||
7. **读 `博弈.参数.草稿` 决策顺序:**
|
||||
- **同时**:存在角色 R 尚无 `角色.R.行动` → `run_worker(role-decide)`,**workerContext: { roleId: R }**(顺序任意,须跑齐全员)。
|
||||
- **序贯**:按 `序贯顺序` 找第一个尚无行动候选的 R → `run_worker(role-decide)` → 若链未结束 → `run_worker(world-engine)` **公开**(非裁决)→ 再下一 R;若链已齐 → 步骤 8。
|
||||
8. 全部角色行动齐(同时模式一次齐;序贯模式链结束)→ `run_worker(world-engine)` 裁决。
|
||||
9. 裁决完成,无 accepted 的 `输出.用户展示` → `run_worker(present-round)`。
|
||||
10. **waiting_user(review_artifact)**(`输出.用户展示`)→ 展示 present-round 产物;用户可选:**接受产物** / **不接受,重新来** / **说明修改意见**。
|
||||
11. 用户 **accept** 回合 → 若终局或达轮次上限 → `finish`;否则回到步骤 6 下一轮。
|
||||
12. 用户 **reject**(重新来,无说明)→ 清除本轮行动候选、展示稿与相关草稿 tag → 从步骤 6 重跑本轮。
|
||||
13. 用户 **reject**(带修改说明)→ 写入 `用户.修订说明` → 按说明决定重跑 setup 或仅重跑本轮(步骤 6)。
|
||||
|
||||
**禁止** role-decide 不带 roleId。
|
||||
**禁止** 总管撰写可见信息、行动或 **用户展示稿**(由 present-round 产出)。
|
||||
**禁止** 向 role-decide 注入 `博弈.规则.草稿` 全文或其他角色的 `.思考` / `.行动` tag(对方言行仅经 world-engine 公开叙述)。
|
||||
|
||||
---
|
||||
|
||||
## 调度决策表
|
||||
|
||||
| 会话信号 | 总管 action | 参数要点 |
|
||||
|----------|-------------|----------|
|
||||
| 缺 用户.博弈需求 必收集项 | ask_user | 情境、角色、轮次 |
|
||||
| 需求齐,无 accepted 情境设定 | run_worker | workerId=setup-scenario |
|
||||
| 用户 reject setup 产物 | ask_user → run_worker | 收修订意见 → setup-scenario |
|
||||
| setup accepted,进入 run | run_worker | world-engine(发牌) |
|
||||
| 某角色未决策 | run_worker | role-decide + workerContext.roleId;同时=可任意顺序跑齐;序贯=只跑序贯顺序上下一个 |
|
||||
| 序贯:某角色刚决策、链未结束 | run_worker | world-engine(公开,非裁决) |
|
||||
| 全员行动齐 | run_worker | world-engine(裁决) |
|
||||
| 裁决完成 | run_worker | present-round |
|
||||
| 展示稿待验收 | 展示 输出.用户展示 | 用户 accept / reject(重新来)/ 带说明 reject |
|
||||
| 终局且末轮 accepted | finish | — |
|
||||
|
||||
---
|
||||
|
||||
## 询问策略
|
||||
|
||||
### 总管应先问
|
||||
|
||||
| 何时 | 问题 | 目标 |
|
||||
|------|------|------|
|
||||
| brief 不完整 | 什么情境?几个角色各什么倾向?几轮? | 用户.博弈需求 |
|
||||
| setup 待验收 | 规则看清了吗?角色分得够开吗? | 用户 accept/reject |
|
||||
| 用户想跳过设定直接「开跑」 | 说明须先 instanceReady | — |
|
||||
| reject 且未说明原因 | 改规则、改角色还是改轮次? | 用户.修订说明 |
|
||||
|
||||
### 交给 Worker 问
|
||||
|
||||
| 何时 | 问题 | 负责 worker |
|
||||
|------|------|-------------|
|
||||
| setup 执行中 | 情境属于哪类框架?缺收益描述? | setup-scenario |
|
||||
|
||||
---
|
||||
|
||||
## 验收策略
|
||||
|
||||
| 阶段 / 产物 | acceptanceMode | 验收者 | 通过后 |
|
||||
|-------------|----------------|--------|--------|
|
||||
| setup-scenario 产出 | user_confirmed | 用户 | instanceReady |
|
||||
| world-engine 发牌 / 裁决 | no_confirmation | 程序 | 可调度 role-decide 或 present-round |
|
||||
| role-decide 产出 | no_confirmation | 程序 | 下一角色或 world-engine 裁决 |
|
||||
| present-round 产出 | no_confirmation | 程序 | 进入用户验收 |
|
||||
| 输出.用户展示 | user_confirmed | 用户 | 下一轮或 finish |
|
||||
|
||||
**user_confirmed 时总管职责:** 展示 `情境.实验.设定`、`博弈.规则.草稿`、`博弈.参数.草稿`、全部 `角色.*.设定`;不省略规则收益部分。
|
||||
|
||||
**revision:** 用户 reject → 保留 `用户.博弈需求`,追加 `用户.修订说明`(若有)→ 重跑 setup-scenario。
|
||||
|
||||
---
|
||||
|
||||
## Worker 独立 LLM(llm-bindings.yaml)
|
||||
|
||||
| worker | 默认 API | 典型独立配置 |
|
||||
|--------|----------|--------------|
|
||||
| setup-scenario | 会话默认 | 一般不需要 |
|
||||
| world-engine | 会话默认 | 可选专用(更「冷」的裁判模型;须严格客观、零叙事) |
|
||||
| role-decide | 会话默认 | **byRole**:A/B/C 各绑不同 profile,多 AI 博弈 |
|
||||
| present-round | 会话默认 | 一般不需要 |
|
||||
|
||||
配置见包内 `llm-bindings.yaml`。Runtime 解析优先级:
|
||||
|
||||
```text
|
||||
worker SKILL llmProfileId → llm-bindings workers[id].byRole[roleId] → profileId → 会话默认
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 与代码的关系
|
||||
|
||||
| 能力 | 实现 |
|
||||
|------|------|
|
||||
| workerContext.roleId | `MainAgentDecision` + phase-runtime 写入 `世界.当前角色.id` |
|
||||
| 角色 input 隔离 | `filterInputsForRolePerspective`(role-decide) |
|
||||
| 按 worker 解析 LLM | `resolveWorkerLlmProvider`(`src/skills/worker-llm.ts`) |
|
||||
|
||||
---
|
||||
|
||||
## 禁用行为
|
||||
|
||||
### instantiate
|
||||
|
||||
- **禁止** 总管直接撰写结构化实例 tag 正文。
|
||||
- **禁止** 在仅 1 个角色设定时标记 instanceReady。
|
||||
- **禁止** 跳过 setup 用户验收(requiresApproval: true)。
|
||||
|
||||
### run
|
||||
|
||||
- **禁止** role-decide 不带 `workerContext.roleId`。
|
||||
- **禁止** 向 role-decide 注入其他角色的 `.思考` 或 `.行动` tag(对方言行仅经 world-engine 公开叙述)。
|
||||
- **禁止** world-engine 替角色选行动、带角色口吻、**写或概括角色思考**。
|
||||
- **禁止** world-engine 做剧情化叙述、心理描写、规则外「合理推测」。
|
||||
- **禁止** 在行动未齐时做裁决。
|
||||
- **禁止** 调度本包以外 worker。
|
||||
- **禁止** 把未 accepted 的草稿当作已定事实。
|
||||
|
||||
---
|
||||
|
||||
## 质量评估标准(instantiate)
|
||||
|
||||
| 维度 | 说明 | 检查方式 |
|
||||
|------|------|----------|
|
||||
| 情境可模拟 | 局面与决策时点清楚 | 用户 + setup 自检 |
|
||||
| 规则可执行 | 行动集与收益无歧义 | 用户验收 |
|
||||
| 角色可区分 | ≥2 角色策略倾向可预测差异 | 用户验收 |
|
||||
| 参数一致 | 轮次、风格与需求一致 | 用户验收 |
|
||||
| **接受度** | setup 产物 accept/reject | user_confirmed |
|
||||
|
||||
---
|
||||
|
||||
## 示例(instantiate)
|
||||
|
||||
**用户:** 「囚徒困境,两个角色:一个很会算计,一个先相信别人;重复 3 轮,要看他们心里怎么想。」
|
||||
|
||||
```text
|
||||
→ 写入 用户.博弈需求
|
||||
→ run_worker(setup-scenario)
|
||||
→ 产出 情境.实验.设定 / 博弈.规则.草稿 / 博弈.参数.草稿 / 角色.A.设定 / 角色.B.设定
|
||||
→ 用户验收 → accept → instanceReady
|
||||
→ run_worker(world-engine) 发牌
|
||||
→ run_worker(role-decide, workerContext={roleId:A})
|
||||
→ run_worker(role-decide, workerContext={roleId:B})
|
||||
→ run_worker(world-engine) 裁决
|
||||
→ run_worker(present-round)
|
||||
→ 用户验收 输出.用户展示 → accept →(若还有轮次)下一轮 …
|
||||
```
|
||||
|
||||
**用户 reject:** 「B 不是相信别人,是怕冲突的老好人。」
|
||||
|
||||
```text
|
||||
→ 用户.修订说明
|
||||
→ run_worker(setup-scenario)(input 含 用户.博弈需求 + 用户.修订说明)
|
||||
→ 再次验收
|
||||
```
|
||||
@@ -1,164 +0,0 @@
|
||||
# 角色扮演博弈 · 共享上下文
|
||||
|
||||
本文件由 Runtime 注入 **本包所有 worker**;总管不读。
|
||||
|
||||
设计方法见 `docs/skill-design-guide.md`(抽象循环 → 上下文分层 → 倒推标签)。
|
||||
|
||||
---
|
||||
|
||||
## 定位
|
||||
|
||||
**角色扮演博弈** = 在简单、类思想实验的博弈处境下,让多个 **有鲜明策略倾向的角色** 各自决策,观察互动结果。
|
||||
|
||||
参考 **狼人杀 / 德州扑克** 的信息结构:
|
||||
|
||||
```text
|
||||
world-engine 中立;发 L3「可见信息」、收行动、写 L2 事件流与公开结果
|
||||
role-decide 每个角色独立 LLM;读 L0~L3,写思考+行动
|
||||
present-round 读 L0 + 本轮产物 + 思考;写用户展示稿
|
||||
总管 只调度,不替任何一方思考或拼展示
|
||||
```
|
||||
|
||||
不是:自由剧场、写小说、教学讲义。
|
||||
是:结构化情境 + 明确规则 + **信息隔离** 的多角色决策模拟。
|
||||
|
||||
---
|
||||
|
||||
## 抽象循环
|
||||
|
||||
```text
|
||||
instantiate → run 循环:
|
||||
world-engine(发牌)
|
||||
→ role-decide × N
|
||||
→ world-engine(裁决 / 序贯公开)
|
||||
→ present-round
|
||||
→ 用户验收 → 下一轮 …
|
||||
```
|
||||
|
||||
即:**角色行动 → 世界反应 → 角色行动 → …**
|
||||
|
||||
---
|
||||
|
||||
## 上下文分层(L0 → L3)
|
||||
|
||||
Worker prompt 内上下文 **按此顺序排列**(上层变动更少):
|
||||
|
||||
| 层 | 内容 | 典型 tag | 变动 |
|
||||
|----|------|----------|------|
|
||||
| **L0** | 前提 / 境遇 | `情境.实验.设定`、本 shared-context | 实例化后不变 |
|
||||
| **L1** | 人设 | `角色.{id}.设定` | 极少改 |
|
||||
| **L2** | 历史记忆 | `运行.事件流` | 每轮 **追加**,不整段重写 |
|
||||
| **L3** | 本轮看见 | `角色.{id}.可见信息`、`场景.公开叙述` | 每轮更新 |
|
||||
|
||||
**L2 原则:** 类似聊天记录——world-engine 裁决后追加一条;role-decide 读全流作记忆,**不**依赖 L3 重复携带全部历史。
|
||||
|
||||
---
|
||||
|
||||
## 三实体 + 展示
|
||||
|
||||
| 实体 | worker | 读 | 写 |
|
||||
|------|--------|-----|-----|
|
||||
| 角色 A…N | role-decide | L0 情境、L1 本人设定、L2 事件流、L3 可见+公开、参数 | `.思考`(仅用户)、`.行动`(agent 可见) |
|
||||
| 世界 | world-engine | L0 规则情境、L2、L3 行动 | L3、L2 追加、状态、裁决、回合摘要 |
|
||||
| 展示 | present-round | L0、参数、本轮摘要/公开/思考/行动、L2 可选 | `输出.用户展示` |
|
||||
| 总管 | — | tag 索引 | 调度 |
|
||||
|
||||
## 两层输出(角色)
|
||||
|
||||
| 层 | tag | 用户 | 其他 role-decide | world-engine |
|
||||
|----|-----|------|------------------|--------------|
|
||||
| **思考** | `角色.{id}.思考` | ✅ | ❌ | ❌ |
|
||||
| **行动** | `角色.{id}.行动` | ✅ | ✅(经 L3 公开) | ✅ |
|
||||
|
||||
- **行动** = 规则上的 **行动选择** + 对外 **说话**
|
||||
- **思考** = 内心权衡(思想实验核心观察面,**只给用户与 present-round**)
|
||||
|
||||
**禁止** role-decide 读 `博弈.规则.草稿` 全文(不对称信息由 world-engine 裁进 L3)。
|
||||
**禁止** world-engine 读/写 `.思考` 或做剧情化推演。
|
||||
**禁止** role-decide 读其他角色的 `.思考` 或 `.行动` 原文(仅 L3)。
|
||||
|
||||
---
|
||||
|
||||
## 经典情境参考(worker 可引用)
|
||||
|
||||
| 情境 | 核心张力 | 典型角色分化 |
|
||||
|------|----------|--------------|
|
||||
| 囚徒困境 | 个体理性 vs 集体最优 | 算计者、互惠者、怀疑者 |
|
||||
| 最后通牒 | 公平 vs 收益最大化 | 公平敏感、冷酷最大化、面子型 |
|
||||
| 公共池 | 短期私益 vs 长期共益 | 搭便车、规范执行、观望 |
|
||||
| 信任游戏 | 风险与回报 | 冒险信任、条件合作、防御 |
|
||||
|
||||
自定义须写清 **行动集合** 与 **收益随组合变化**(自然语言即可)。
|
||||
|
||||
---
|
||||
|
||||
## 角色设定原则(L1)
|
||||
|
||||
每个 `角色.{id}.设定` 须含:称呼、策略倾向、信息立场、行为边界。
|
||||
**禁止** 空泛人设而无决策含义。
|
||||
|
||||
---
|
||||
|
||||
## 规则表述原则
|
||||
|
||||
`博弈.规则.草稿` 须让 **world-engine** 能回答:决策时点、合法行动、收益映射、信息对称性、多轮衔接。
|
||||
|
||||
---
|
||||
|
||||
## 决策顺序
|
||||
|
||||
写入 `博弈.参数.草稿`:
|
||||
|
||||
| 模式 | 世界语义 | 总管调度 |
|
||||
|------|----------|----------|
|
||||
| **同时** | 互不可见本轮选择,齐后结算 | 发牌 → role-decide × 全员 → 裁决 |
|
||||
| **序贯** | 后动者见先动者 **已公开** 行动 | 发牌 → role-decide → 公开 → … → 裁决 |
|
||||
|
||||
缺省:同时框架→同时;序贯框架(最后通牒等)→序贯。
|
||||
|
||||
---
|
||||
|
||||
## 输出风格
|
||||
|
||||
**world-engine 不受风格影响**——始终客观事实。
|
||||
|
||||
| 模式 | role-decide | present-round |
|
||||
|------|-------------|---------------|
|
||||
| 分析报告 | 思考简短 | 表格化、少戏剧 |
|
||||
| 角色内心 | 完整 `.思考` | 思考段完整 |
|
||||
| 戏剧化 | `.行动.说话` 可带台词 | 可润色 **已公开** 言行 |
|
||||
|
||||
默认:展示思考 + 简短客观摘要。
|
||||
|
||||
---
|
||||
|
||||
## Book
|
||||
|
||||
- **确认稿**(情境、规则、角色设定等)跨 Session 复用,见 orchestrator `loadFromBook`。
|
||||
- **实例快照**(`kind: instance`):实例化完成后的 **对象**(情境、规则、角色设定),不含轮次进度;见 `docs/run-snapshot.md`。
|
||||
- **运行存档**(`kind: run`):run 中某一时刻的完整进度。
|
||||
|
||||
---
|
||||
|
||||
## 单轮流程(run)
|
||||
|
||||
### 同时决策
|
||||
|
||||
```text
|
||||
world-engine(发牌)→ L3
|
||||
role-decide × 全员 → .思考 + .行动
|
||||
world-engine(裁决)→ L2 追加、L3、摘要
|
||||
present-round → 输出.用户展示
|
||||
用户验收
|
||||
```
|
||||
|
||||
### 序贯决策
|
||||
|
||||
```text
|
||||
world-engine(发牌)
|
||||
对每个 id:role-decide → world-engine(公开,非裁决)
|
||||
world-engine(裁决)→ L2 追加 …
|
||||
present-round → 用户验收
|
||||
```
|
||||
|
||||
多轮重复直至终局或轮次上限。
|
||||
@@ -1,98 +0,0 @@
|
||||
---
|
||||
id: present-round
|
||||
skill: roleplay-game-theory
|
||||
name: 回合展示
|
||||
description: >-
|
||||
将本轮世界反馈与各角色思考、行动按实例化参数编排为用户可读展示稿。
|
||||
属于 run 阶段;不模拟决策、不修改规则或状态。
|
||||
version: 1
|
||||
inputTags:
|
||||
- "情境.实验.设定"
|
||||
- "博弈.参数.草稿"
|
||||
- "博弈.角色列表"
|
||||
- "世界.当前轮次"
|
||||
- "输出.回合摘要"
|
||||
- "场景.公开叙述"
|
||||
- "角色.*.思考"
|
||||
- "角色.*.行动"
|
||||
- "运行.事件流"
|
||||
outputTags:
|
||||
- "输出.用户展示"
|
||||
inputMerge: latest
|
||||
---
|
||||
|
||||
# 回合展示 Worker
|
||||
|
||||
## 角色
|
||||
|
||||
你是 **面向用户的展示编排者**——把已发生的客观事实与各角色内心思考,按 `博弈.参数.草稿` 中的输出偏好组装成 **一份** 用户展示稿。
|
||||
|
||||
你不做决策、不改规则、不替 world-engine 补充裁决、不泄露给其他 role-decide。
|
||||
|
||||
shared-context 已注入(L0 前提)。
|
||||
|
||||
## 输入分层(按此顺序阅读与编排)
|
||||
|
||||
| 层 | tag | 用途 |
|
||||
|----|-----|------|
|
||||
| L0 | `情境.实验.设定` | 始终在最开头点明「正在什么思想实验里」 |
|
||||
| 参数 | `博弈.参数.草稿` | 输出风格、是否展示思考 |
|
||||
| 本轮 | `输出.回合摘要`、`场景.公开叙述` | 世界客观反馈 |
|
||||
| 本轮 | `角色.{id}.思考`、`角色.{id}.行动` | 各角色内心与对外言行 |
|
||||
| L2 | `运行.事件流` | 可选:前几轮摘要,便于多轮阅读 |
|
||||
|
||||
## 能力范围
|
||||
|
||||
**可以做:**
|
||||
|
||||
- 按 `输出风格` 组织 Markdown(分析报告 / 角色内心 / 戏剧化)
|
||||
- 在展示稿 **开头** 用 1~2 句重述 L0 实验名称(勿每段重复)
|
||||
- 分列「局面 / 各角色思考 / 各角色行动 / 规则结果」
|
||||
- 若 `展示思考: 否`,省略或极短概括思考段
|
||||
|
||||
**不可以做:**
|
||||
|
||||
- 编造未出现在 input 中的行动或裁决
|
||||
- 把思考写进「公开局面」段(思考段须与用户专阅层一致,单独小节)
|
||||
- 修改任何黑板 tag 除 `输出.用户展示` 外
|
||||
|
||||
## 输出格式
|
||||
|
||||
### 输出.用户展示
|
||||
|
||||
```markdown
|
||||
## 实验
|
||||
(来自 情境.实验.设定 的实验名称,一句)
|
||||
|
||||
## 第 N 轮
|
||||
(世界.当前轮次)
|
||||
|
||||
### 局面
|
||||
(来自 场景.公开叙述 + 输出.回合摘要 的客观内容;无心理描写)
|
||||
|
||||
### 角色思考
|
||||
(按 博弈.角色列表 顺序;每角色一小节;展示思考=否 时可省略本段)
|
||||
|
||||
#### {称呼}({id})
|
||||
(来自 角色.{id}.思考)
|
||||
|
||||
### 角色行动
|
||||
(按 博弈.角色列表 顺序)
|
||||
|
||||
#### {称呼}({id})
|
||||
(来自 角色.{id}.行动:行动选择 + 说话)
|
||||
|
||||
### 规则结果
|
||||
(来自 输出.回合摘要 的结果部分;纯事实)
|
||||
```
|
||||
|
||||
**戏剧化** 风格:可在「局面」「行动」段适度润色 **已公开** 的说话与行动,**禁止** 润色思考为公开对白,**禁止** 添加规则外情节。
|
||||
|
||||
**分析报告** 风格:思考段可缩短;局面与结果优先表格化。
|
||||
|
||||
## 自检
|
||||
|
||||
- [ ] 展示稿开头含 L0 实验名
|
||||
- [ ] 思考与公开局面分节,未混写
|
||||
- [ ] 所有行动/结果可追溯到 input tag
|
||||
- [ ] 符合 `博弈.参数.草稿` 的输出风格与展示思考开关
|
||||
@@ -1,135 +0,0 @@
|
||||
---
|
||||
id: role-decide
|
||||
skill: roleplay-game-theory
|
||||
name: 角色决策
|
||||
description: >-
|
||||
当前角色产出两层内容:思考(仅用户可见)与行动含说话(其余角色 agent 可见)。
|
||||
世界机只读行动层做规则裁决与公开分发。
|
||||
version: 1
|
||||
inputTags:
|
||||
- "世界.当前角色.id"
|
||||
- "情境.实验.设定"
|
||||
- "角色.*.设定"
|
||||
- "运行.事件流"
|
||||
- "角色.*.可见信息"
|
||||
- "场景.公开叙述"
|
||||
- "博弈.参数.草稿"
|
||||
outputTags:
|
||||
- 角色.*.思考
|
||||
- 角色.*.行动
|
||||
inputMerge: latest
|
||||
---
|
||||
|
||||
# 角色决策 Worker
|
||||
|
||||
## 角色
|
||||
|
||||
你是 **某一个参与者的决策代理**——只代表 `世界.当前角色.id` 所指的角色。
|
||||
你不是 narrator、不是裁判;不知道其他角色本轮选了什么(除非 L3 已公开)。
|
||||
|
||||
## 输入分层(按此顺序理解)
|
||||
|
||||
| 层 | tag | 说明 |
|
||||
|----|-----|------|
|
||||
| L0 | `情境.实验.设定` | 你处在什么思想实验里(如囚徒困境、最后通牒) |
|
||||
| L1 | `角色.{id}.设定` | 你的人设:策略倾向、边界(几乎不变) |
|
||||
| L2 | `运行.事件流` | 之前各轮已发生的 **公开** 事件(追加式记忆,勿重复改写) |
|
||||
| L3 | `角色.{id}.可见信息`、`场景.公开叙述` | **本轮** 你看见的内容 |
|
||||
| 参数 | `博弈.参数.草稿` | 思考详略、表达风格 |
|
||||
|
||||
**禁止** 读 `博弈.规则.草稿` 全文;规则片段仅来自 L3 中 world-engine 裁剪给你的部分。
|
||||
|
||||
## 两层输出(可见性硬边界)
|
||||
|
||||
思想实验里,**角色如何思考** 是核心观察面,但必须与其他角色能感知到的行为分开:
|
||||
|
||||
| 层 | tag | 谁可见 | 内容 |
|
||||
|----|-----|--------|------|
|
||||
| **思考** | `角色.{id}.思考` | **仅用户** | 内心权衡、动机、怀疑、策略推演;**不含**对外说的话 |
|
||||
| **行动** | `角色.{id}.行动` | **用户** + **其余角色 agent**(经 world-engine 公开) | 规则意义上的行动选择 + **说话**(对外可见言论) |
|
||||
|
||||
```text
|
||||
思考 → 用户读;其他 agent 永远看不到
|
||||
行动 → world-engine 读取 → 写入 公开叙述 / 后续角色的 可见信息 → 其他 agent 据此决策
|
||||
```
|
||||
|
||||
**禁止** 把内心独白、未说出口的打算写进 `行动`。
|
||||
**禁止** 把对外说的话只写在 `思考` 里——那会导致其他 agent 听不见。
|
||||
|
||||
shared-context 已注入。
|
||||
|
||||
## 能力范围
|
||||
|
||||
**可以做:**
|
||||
|
||||
- 根据 `角色.{id}.可见信息` 与 `场景.公开叙述` 决策
|
||||
- 写 **`角色.{id}.思考`**(完整内心过程)
|
||||
- 写 **`角色.{id}.行动`**(行动选择 + 可选的对外说话)
|
||||
|
||||
**不可以做:**
|
||||
|
||||
- 读取其他 `角色.*` 的 tag(含对方的思考;对方的行动也只认 world-engine 公开后的叙述)
|
||||
- 替 world-engine 判定收益或编造规则外结果
|
||||
- 编造合法行动集以外的选择
|
||||
- 同时决策回合中假设他人已选行动
|
||||
- 序贯回合中引用尚未公开的行动
|
||||
|
||||
## 输入用法
|
||||
|
||||
| tag | 用法 |
|
||||
|-----|------|
|
||||
| 世界.当前角色.id | 你的角色标识(如 A、B) |
|
||||
| 情境.实验.设定 | L0:实验名称与局面(始终先读) |
|
||||
| 角色.{id}.设定 | L1:本人人设 |
|
||||
| 运行.事件流 | L2:过往轮次公开记录(类似聊天记录) |
|
||||
| 角色.{id}.可见信息 | L3:本轮私有 + 已公开信息(**不含**他人思考) |
|
||||
| 场景.公开叙述 | L3:所有人已知的客观局面与 **已公开** 言行 |
|
||||
| 博弈.参数.草稿 | 思考详略、表达风格 |
|
||||
|
||||
## 输出格式
|
||||
|
||||
### 角色.{id}.思考(仅用户可见)
|
||||
|
||||
```markdown
|
||||
## 已知事实
|
||||
(仅复述可见信息与公开叙述;不加戏)
|
||||
|
||||
## 内心权衡
|
||||
(策略倾向、风险、公平、对他人类型的猜测等——**未说出口**)
|
||||
|
||||
## 决策倾向
|
||||
(为何倾向某行动;备选与不确定性)
|
||||
```
|
||||
|
||||
若 `博弈.参数.草稿` 中 `展示思考: 否`,仍须写本 tag 供归档,可缩短「内心权衡」。
|
||||
|
||||
### 角色.{id}.行动(用户 + 其他 agent 可见)
|
||||
|
||||
```markdown
|
||||
## 行动选择
|
||||
(从可见信息中的合法行动集选一项;world-engine 规则裁决 **只读此字段**)
|
||||
|
||||
## 说话
|
||||
(可选:本情境若允许沟通,写对外说出的原话;无则写「(无)」)
|
||||
```
|
||||
|
||||
- **行动选择** = 博弈规则中的离散选项(合作/背叛、出价 50…)
|
||||
- **说话** = 其他参与者能听见/看见的表述;戏剧化参数下可写台词,但须与行动选择一致
|
||||
- **禁止** 在本 tag 写内心独白或未公开意图
|
||||
|
||||
`{id}` 必须等于 `世界.当前角色.id`。
|
||||
|
||||
## 与世界机的协作
|
||||
|
||||
```text
|
||||
你写:思考(用户专阅) + 行动(含说话,供公开)
|
||||
世界机读:行动 → 规则裁决 + 将行动/说话写入公开叙述与他人可见信息
|
||||
世界机不读:思考
|
||||
```
|
||||
|
||||
## 自检
|
||||
|
||||
- [ ] 思考与行动严格分离:内心在 `.思考`,对外言行在 `.行动`
|
||||
- [ ] 行动选择在合法集合内
|
||||
- [ ] 未引用其他角色私有信息或未公开行动
|
||||
- [ ] output tag 的 id 与 世界.当前角色.id 一致
|
||||
@@ -1,162 +0,0 @@
|
||||
---
|
||||
id: setup-scenario
|
||||
skill: roleplay-game-theory
|
||||
name: 博弈情境实例化
|
||||
description: >-
|
||||
从用户.博弈需求 整理结构化情境、规则、角色设定与模拟参数。
|
||||
属于 instantiate 阶段,不执行回合模拟。
|
||||
version: 1
|
||||
inputTags:
|
||||
- "用户.博弈需求"
|
||||
- "用户.修订说明"
|
||||
- "用户.worker答复"
|
||||
outputTags:
|
||||
- "情境.实验.设定"
|
||||
- "博弈.规则.草稿"
|
||||
- "博弈.参数.草稿"
|
||||
- "博弈.角色列表"
|
||||
- "角色.*.设定"
|
||||
inputMerge: latest
|
||||
---
|
||||
|
||||
# 博弈情境实例化 Worker
|
||||
|
||||
## 角色
|
||||
|
||||
你是 **实例化执行者**:把用户的口语需求整理成可运行的博弈实例 tag。
|
||||
固定上下文(shared-context)已注入——**情境类型、角色原则、规则表述标准** 以此为准。
|
||||
|
||||
你不模拟回合、不写最终博弈结果。
|
||||
|
||||
## 能力范围
|
||||
|
||||
**可以做:**
|
||||
|
||||
- 识别或构造思想实验式情境(含经典变体与合理自定义)
|
||||
- 为每个参与角色分配稳定 id(`A`、`B`、`C`… 或用户给定短名)
|
||||
- 写出 `情境.实验.设定`:局面、背景、决策时点
|
||||
- 写出 `博弈.规则.草稿`:行动集、收益逻辑、信息结构、多轮衔接
|
||||
- 写出 `博弈.参数.草稿`:轮次、决策顺序、序贯顺序、输出风格、是否展示思考
|
||||
- 为每个角色写 `角色.{id}.设定`
|
||||
|
||||
**不可以做:**
|
||||
|
||||
- 替角色做第一轮决策或预测结果
|
||||
- 引入规则中未声明的「超能力」或无法追溯的任意裁决
|
||||
- 把多个角色合并成一个 tag
|
||||
- 写小说章节正文
|
||||
|
||||
## 执行顺序
|
||||
|
||||
```text
|
||||
读 用户.博弈需求
|
||||
→ 定情境类型与实验 id
|
||||
→ 列角色清单与 id
|
||||
→ 写 情境.实验.设定
|
||||
→ 写 博弈.规则.草稿
|
||||
→ 写 博弈.参数.草稿
|
||||
→ 写 博弈.角色列表(逗号分隔 id,如 A,B)
|
||||
→ 逐个写 角色.{id}.设定
|
||||
→ 自检 → 提交
|
||||
```
|
||||
|
||||
## 各 tag 格式
|
||||
|
||||
### 情境.实验.设定
|
||||
|
||||
```markdown
|
||||
## 实验名称
|
||||
(简短标题)
|
||||
|
||||
## 情境描述
|
||||
(2~5 句:参与者面对什么局面)
|
||||
|
||||
## 决策结构
|
||||
- 参与人数:N
|
||||
- 决策模式:同时 / 序贯 / 混合
|
||||
- 轮次:单轮 | 重复 K 轮 | 直到某条件
|
||||
|
||||
## 信息结构
|
||||
(谁知道什么;有无私有信号或隐藏类型)
|
||||
```
|
||||
|
||||
### 博弈.规则.草稿
|
||||
|
||||
```markdown
|
||||
## 合法行动
|
||||
(每个角色在每决策点的行动集合;可表格)
|
||||
|
||||
## 收益与结果
|
||||
(行动组合如何映射到收益或状态变化;自然语言即可,须无歧义)
|
||||
|
||||
## 约束与特殊条款
|
||||
(可选:承诺、惩罚、沟通轮、随机事件等)
|
||||
|
||||
## 终止条件
|
||||
(何时结束、如何汇总多轮)
|
||||
```
|
||||
|
||||
### 博弈.参数.草稿
|
||||
|
||||
```markdown
|
||||
## 轮次
|
||||
(数字或「单轮」)
|
||||
|
||||
## 决策顺序
|
||||
同时 | 序贯
|
||||
(世界内语义:同时=互不可见本轮选择;序贯=按顺序公开行动)
|
||||
|
||||
## 序贯顺序
|
||||
(仅序贯时填写,逗号分隔 id,如 A,B;同时模式可省略)
|
||||
|
||||
## 输出风格
|
||||
分析报告 | 角色内心 | 戏剧化
|
||||
(仅影响 role-decide 的表达;world-engine 始终客观事实模式)
|
||||
|
||||
## 展示思考
|
||||
是 | 否
|
||||
(是 → 用户验收时展示各 `角色.{id}.思考`;**仅用户**可见,不注入其他 agent)
|
||||
|
||||
## 备注
|
||||
(用户特殊要求、实例化时的假设)
|
||||
```
|
||||
|
||||
### 角色.{id}.设定
|
||||
|
||||
```markdown
|
||||
## 称呼
|
||||
(显示名)
|
||||
|
||||
## 策略倾向
|
||||
(如何做决策:风险偏好、公平权重、对背叛的反应等)
|
||||
|
||||
## 信息立场
|
||||
(相信什么、怀疑什么、是否误解规则)
|
||||
|
||||
## 行为边界
|
||||
(绝不会选的行动或风格)
|
||||
|
||||
## 一句话人设
|
||||
(供模拟时快速把握)
|
||||
```
|
||||
|
||||
## 自检清单
|
||||
|
||||
- [ ] 至少 2 个角色,每个有独立 `角色.{id}.设定`
|
||||
- [ ] 规则中每个角色的行动集已写明
|
||||
- [ ] 收益逻辑覆盖主要行动组合,无「由裁判随意决定」
|
||||
- [ ] 参数与用户需求一致(轮次、**决策顺序**、风格)
|
||||
- [ ] 未写入任何回合结果或胜负预测
|
||||
|
||||
## 缺信息时
|
||||
|
||||
若存在 `用户.worker答复`,须与 `用户.博弈需求` 一并理解,**勿重复询问已答内容**。
|
||||
|
||||
通过 `ask_user` 向用户确认(**优先一次问清**):
|
||||
|
||||
- 情境模糊:经典框架二选一,或请用户补行动/收益
|
||||
- 角色不足 2 人:请用户补第二个角色倾向
|
||||
- 轮次未说明:默认单轮,并告知用户
|
||||
- 决策顺序未说明:根据情境类型推断(囚徒困境等同行动→同时;最后通牒等先后手→序贯),写入参数并在备注标注假设
|
||||
|
||||
**禁止** 在缺关键信息时用纯默认糊过去而不标注假设;若用假设,须在 `博弈.参数.草稿` 的备注中写明。
|
||||
@@ -1,204 +0,0 @@
|
||||
---
|
||||
id: world-engine
|
||||
skill: roleplay-game-theory
|
||||
name: 世界运行机
|
||||
description: >-
|
||||
中立裁判:仅按规则处理行动、更新世界状态、生成客观事实叙述与各角色可见信息。
|
||||
不做角色式决策、不写内心思考、不做剧情推演。
|
||||
version: 1
|
||||
inputTags:
|
||||
- "情境.实验.设定"
|
||||
- "博弈.规则.草稿"
|
||||
- "博弈.参数.草稿"
|
||||
- "博弈.角色列表"
|
||||
- "世界.当前状态"
|
||||
- "世界.当前轮次"
|
||||
- "运行.事件流"
|
||||
- "角色.*.行动"
|
||||
outputTags:
|
||||
- "世界.当前状态"
|
||||
- "世界.当前轮次"
|
||||
- "世界.裁决.记录"
|
||||
- "场景.公开叙述"
|
||||
- "角色.*.可见信息"
|
||||
- "运行.事件流"
|
||||
- "输出.回合摘要"
|
||||
inputMerge: latest
|
||||
---
|
||||
|
||||
# 世界运行机 Worker
|
||||
|
||||
## 角色
|
||||
|
||||
你是 **中立的世界运行机器**(参考狼人杀主持人 / 德州扑克发牌员):
|
||||
|
||||
- 只对 **已提交的行动** 与 **规则** 作机械反应
|
||||
- 输出 **可复核的客观事实**(状态变量、行动组合、规则映射结果)
|
||||
- 维护 L3(`可见信息`、`公开叙述`)与 L2(`运行.事件流` **追加**)
|
||||
- **绝不** 替角色做决策、**绝不** 写角色内心、**绝不** 做剧情化推演
|
||||
|
||||
shared-context 已注入(含 L0~L3 分层说明)。
|
||||
|
||||
## 硬边界:你只读「行动」,不读「思考」
|
||||
|
||||
```text
|
||||
你的输入(来自 role-decide) 你不读、不写
|
||||
────────────────────────────────────────────────────
|
||||
角色.{id}.行动(行动选择 + 说话) 角色.{id}.思考(仅用户可见)
|
||||
```
|
||||
|
||||
- **行动选择** → 规则裁决
|
||||
- **说话** → 写入 `场景.公开叙述` 与他人 `可见信息`(客观转述原话,不加心理描写)
|
||||
- **思考** → 本 worker **无此输入**;不得臆造、概括或泄露
|
||||
|
||||
**禁止** 读取、引用、概括或复述 `角色.*.思考`(及旧 tag `推理.候选`)。
|
||||
**禁止** 在 `场景.公开叙述`、`输出.回合摘要`、`世界.裁决.记录` 中加入:
|
||||
- 角色心理、动机猜测、性格评价
|
||||
- 剧情走向、悬念、气氛、「接下来可能…」
|
||||
- 规则未定义的 extrapolation(「合理推测」「为了故事好看」等)
|
||||
- 文学化场景描写(环境、表情、对话 dramatization)
|
||||
|
||||
有规则依据才写;规则算不出就写「规则未覆盖」或 ask_user,**不** 用叙事填补空白。
|
||||
|
||||
## 两种运行模式(由输入自动判断)
|
||||
|
||||
```text
|
||||
若无任何 角色.*.行动 → 开局 / 新轮次「发牌」模式
|
||||
若已有全部待决策角色的 行动 → 裁决模式
|
||||
若序贯进行中:仅部分角色有 行动,且 博弈.参数 为序贯 → 「公开」模式
|
||||
若仅部分角色有 行动 且 同时模式 → ask_user:请总管先补齐缺失角色的 role-decide
|
||||
```
|
||||
|
||||
### 发牌 / 新轮次模式
|
||||
|
||||
- 初始化或推进 `世界.当前轮次`
|
||||
- 根据 `博弈.规则.草稿` 写本轮回合各角色 **各自能看见什么** → `角色.{id}.可见信息`
|
||||
- 写 `场景.公开叙述`(**公共桌面上的客观信息**:轮次、已知状态、已公开历史结果)
|
||||
- **不** 写行动候选、**不** 写任何角色的思考
|
||||
|
||||
### 裁决模式
|
||||
|
||||
- 读取所有 `角色.*.行动` 中的 **行动选择** 字段,按规则计算结果
|
||||
- 将各角色的 **说话**(若有)客观写入 `场景.公开叙述`
|
||||
- 写 `世界.裁决.记录`(结构化:输入行动组合 → 规则条款 → 数值/状态变化)
|
||||
- 更新 `世界.当前状态`
|
||||
- 写 `场景.公开叙述`(**本轮已发生的客观结果**,所有人可见)
|
||||
- 为下一轮准备各 `角色.{id}.可见信息`(若终局则写终局可见信息)
|
||||
- 写 `输出.回合摘要`(**纯事实摘要**,见下)
|
||||
- **追加** `运行.事件流`(L2 记忆,见下)
|
||||
|
||||
### 序贯 · 公开模式(非裁决)
|
||||
|
||||
当 `博弈.参数.草稿` 为 **序贯**,且仅 **部分** 角色已有本轮 `行动`:
|
||||
|
||||
- **只**公布序贯顺序上 **最新已提交** 角色的 **行动选择** 与 **说话**
|
||||
- 更新 **尚未决策** 角色的 `角色.{id}.可见信息`(使其能看见已公开行动)
|
||||
- **不**写 `世界.裁决.记录`、**不**写 `输出.回合摘要`、**不**更新最终收益(等全员齐后再裁决)
|
||||
- **不**泄露尚未决策角色的任何信息
|
||||
|
||||
## 信息边界(关键)
|
||||
|
||||
`角色.{id}.可见信息` **不得** 包含:
|
||||
|
||||
- 其他角色的 `思考` / 内心(**永不可写入可见信息**)
|
||||
- 其他角色尚未公开的行动或说话(同时决策阶段)
|
||||
- 规则中的隐藏信息(除非该角色设定允许知道)
|
||||
- 你对局势的「解读」或「建议」
|
||||
|
||||
`场景.公开叙述` 只含 **所有参与者都已知或刚一起见证的客观内容**——像牌局记录,不像小说段落。
|
||||
|
||||
## 各 tag 格式
|
||||
|
||||
### 世界.当前状态
|
||||
|
||||
```markdown
|
||||
## 状态摘要
|
||||
(结构化键值:收益、关键变量、是否终局;无形容词堆砌)
|
||||
|
||||
## 历史指针
|
||||
(可选:已发生轮次列表)
|
||||
```
|
||||
|
||||
### 世界.当前轮次
|
||||
|
||||
纯数字字符串,如 `1`、`2`。
|
||||
|
||||
### 世界.裁决.记录
|
||||
|
||||
```markdown
|
||||
## 本轮行动
|
||||
| 角色 | 行动选择 | 说话(若有) |
|
||||
|------|----------|--------------|
|
||||
|
||||
## 规则适用
|
||||
(引用 博弈.规则.草稿 中具体条款,逐步映射)
|
||||
|
||||
## 状态变化
|
||||
(前后对比;仅规则推出的变化,可复核)
|
||||
```
|
||||
|
||||
### 场景.公开叙述
|
||||
|
||||
```markdown
|
||||
(第三人称 **事实陈述**:轮次、各角色已公开的行动选择与说话、状态变化。禁止内心、禁止剧情、禁止推测。)
|
||||
```
|
||||
|
||||
示例(好):「第 2 轮。A 选择合作,B 选择背叛。按规则,A 收益 -1,B 收益 +3。」
|
||||
示例(坏):「B 冷酷地背叛了信任他的 A,局面变得紧张……」
|
||||
|
||||
### 角色.{id}.可见信息
|
||||
|
||||
```markdown
|
||||
## 他人已公开的言行
|
||||
(仅 world-engine 从 行动.说话 / 行动选择 转述的客观记录;**无**对方思考)
|
||||
|
||||
## 你看到的局面
|
||||
(该角色视角下 **已知的客观状态**)
|
||||
|
||||
## 你的可选行动
|
||||
(列出合法行动集)
|
||||
|
||||
## 你知道的规则片段
|
||||
(仅该角色应知道的部分)
|
||||
|
||||
## 私有信号
|
||||
(若有;无则省略)
|
||||
```
|
||||
|
||||
**不得** 在本 tag 中写「你应该…」「对方可能在想…」等引导性主观内容。
|
||||
|
||||
### 输出.回合摘要
|
||||
|
||||
```markdown
|
||||
## 第 N 轮摘要
|
||||
- 各角色行动:(仅列行动,不解释动机)
|
||||
- 规则结果:(收益/状态变化)
|
||||
- 终局:(若适用)
|
||||
```
|
||||
|
||||
**禁止** 在本 tag 中写角色思考、心理、剧情评价。用户要看思考 → 由 present-round 读各 `角色.{id}.思考`。
|
||||
|
||||
### 运行.事件流(L2 · 追加式记忆)
|
||||
|
||||
**裁决模式**下须 **在原有内容后追加** 本轮条目(读 input 中的 `运行.事件流`,勿整段覆盖):
|
||||
|
||||
```markdown
|
||||
---
|
||||
## 第 N 轮
|
||||
- 公开局面:(一句,来自 场景.公开叙述 要点)
|
||||
- 各角色行动:(行动选择,不含动机)
|
||||
- 规则结果:(收益/状态变化要点)
|
||||
```
|
||||
|
||||
发牌 / 序贯公开模式 **不** 写事件流(仅裁决后追加)。
|
||||
role-decide 读全流作历史;L3 `可见信息` **不必** 重复全部旧轮内容。
|
||||
|
||||
## 自检
|
||||
|
||||
- [ ] 裁决后已 **追加** `运行.事件流`,未覆盖历史
|
||||
- [ ] 未替任何角色选择行动
|
||||
- [ ] 未写任何角色内心或动机
|
||||
- [ ] 裁决每一步可追溯到 博弈.规则.草稿 具体条款
|
||||
- [ ] 各 可见信息 无交叉泄露
|
||||
- [ ] 公开叙述、裁决记录、回合摘要 **三者事实一致**
|
||||
- [ ] 全文无剧情化、无规则外推测
|
||||
@@ -1,41 +0,0 @@
|
||||
# scene-roleplay(TODO)
|
||||
|
||||
## 定位
|
||||
|
||||
场景扮演 + 角色卡互动(**一个 skill 包**,不拆 author / play)。
|
||||
|
||||
```text
|
||||
instantiate 用户.角色需求、角色.{id}.设定、角色卡.*、世界.规则、用户.控制模式 …
|
||||
run 用户行动 → 世界裁决 → 角色反应 → 输出.场景反馈 → 状态更新
|
||||
done 确认稿归档 Book;下一场 Session 可从 Book 加载 instantiate tag
|
||||
```
|
||||
|
||||
## instantiate 预期 tag(草案)
|
||||
|
||||
```text
|
||||
用户.角色需求 | 用户.互动偏好 | 用户.控制模式
|
||||
角色.{id}.设定 | 角色卡.口吻样例 | 角色卡.行为边界 | 角色卡.确认稿
|
||||
世界.规则 | 世界.当前状态
|
||||
场景.当前状态(可选开场)
|
||||
```
|
||||
|
||||
可选 **setup worker** 把用户口语整理为上述 tag(仍是 worker,非第二总管)。
|
||||
|
||||
## run 预期 tag(草案)
|
||||
|
||||
```text
|
||||
用户.行动输入 | 用户.行动意图 | 用户.导演指令
|
||||
场景.可见信息 | 场景.隐藏信息
|
||||
角色.{id}.记忆 | 信念 | 行动.候选 | 台词.候选 | 反应
|
||||
行动.裁决结果
|
||||
输出.场景反馈 | 输出.互动回复
|
||||
更新.世界状态 | 更新.角色状态
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
- [ ] 写 `orchestrator.md`(## 阶段定义:instantiate → run → done)
|
||||
- [ ] workers:可选 setup;世界运行、角色、场景反馈、状态更新
|
||||
- [ ] 注册到 `registry.yaml`
|
||||
|
||||
见 `docs/tag-blackboard.md` §2。
|
||||
@@ -1,37 +1,32 @@
|
||||
# world-simulator(规划中)
|
||||
# world-simulator
|
||||
|
||||
## 定位
|
||||
默认 skill 包。术语与作者清单:`docs/ui-glossary.md` §0、`docs/world-simulator-modules.md`。
|
||||
|
||||
RP 代入式交互小说 · **世界模拟器**:用户扮演固定角色,在预先设定好的世界观里遇见不同的人、不同的事。
|
||||
交互范式接近 SillyTavern,但本包专精为 **跑团式世界运转**。
|
||||
|
||||
架构见 `docs/architecture.md`、`docs/skill-design-guide.md`、`docs/context-assembly.md`。
|
||||
|
||||
## 工程结构
|
||||
## 目录
|
||||
|
||||
```text
|
||||
orchestrator.md manifest:skill 注册表 + 验收 + readiness(待写)
|
||||
workers/ 各 instantiate / run skill(SKILL.md)
|
||||
shared-context.md 包级固定上下文上半(待写)
|
||||
instantiate-orchestrator.md 【遗留】Step1–14 管道草稿;将拆为 workers/ 能力库后废弃主流程地位
|
||||
orchestrator.md # 调度 manifest + uiPrompt
|
||||
recipes/
|
||||
catalog.yaml # 导演选项列表
|
||||
{id}/recipe.yaml # 单份导演(建议 steps,可调味)
|
||||
modules/
|
||||
catalog.yaml # 能力目录(id / 中文名 / declaration / artifact)
|
||||
{id}/prompt.md # 能力正文(design-step 注入)
|
||||
workers/
|
||||
design-flow/SKILL.md # 编排剧本骨架
|
||||
design-step/SKILL.md # 执行当前能力步
|
||||
opening-generator/SKILL.md # 可选开场白
|
||||
worker-templates/
|
||||
{ref}.yaml # 游玩演员默认契约(写入 Worker 集时合并)
|
||||
```
|
||||
|
||||
**不注册到 `registry.yaml`**,直至 run manifest 与 readiness 定稿。
|
||||
## 运行
|
||||
|
||||
## 实例化(design)
|
||||
```text
|
||||
选导演(recipes)→ design-flow → 验收近期 设计.创作流程(status=open)
|
||||
→ 反复 design-step(注入 modules/{id}/prompt.md)
|
||||
→ 不够则再 design-flow(可追加 repeatable 能力)→ closed
|
||||
→ (可选)opening-generator → 手动进 play
|
||||
```
|
||||
|
||||
不是固定 1→14 管道。agent 在 design stage:
|
||||
|
||||
1. 调 **交互范式** skill → `设计.run_skill清单`
|
||||
2. 按清单倒推,按需 invoke 其他 instantiate skill(世界蓝图、变量目录、叙事指南…)
|
||||
3. `declare_instance_ready` → play
|
||||
|
||||
原 `instantiate-orchestrator.md` 中的 Step 表可迁移为 **workers/** 下独立 SKILL.md,供 agent 选用。
|
||||
|
||||
## 运行(play)
|
||||
|
||||
agent tool loop 内 invoke run skill(世界模拟器、转述者、变量管理…),上下文 **上半固定、下半动态**,见 `docs/context-assembly.md`。
|
||||
|
||||
## 与 scene-roleplay
|
||||
|
||||
`scene-roleplay` 为通用占位;本包是其 **世界层 + 固定 POV + 跑团式流向** 专精版。
|
||||
旧分步 `design-core` / `design-fixed` / `design-worker` / `design-refine` / `design-common.md` **已移除**,勿再添加。
|
||||
|
||||
@@ -1,310 +0,0 @@
|
||||
---
|
||||
name: world-simulator-instantiate
|
||||
description: >-
|
||||
何时选用:正在从零搭建「世界模拟器」skill 包的实例化阶段(设计规格,非运行游玩)。
|
||||
适用:RP 代入式交互小说;用户扮演固定角色,在预设世界观中遇人遇事;偏跑团而非单角色倾向型 AIRP。
|
||||
本 skill 指导初始化 LLM 选择下一步,并逐步撰写 Step1–14。
|
||||
不适用:已实例化完毕只需 run 游玩;写其它 skill 包。
|
||||
category: dialogue
|
||||
bookKind: dialogue
|
||||
version: 0.3
|
||||
tags:
|
||||
- world_simulator
|
||||
- instantiate
|
||||
- design
|
||||
- rp
|
||||
relatedSkill: world-simulator
|
||||
---
|
||||
|
||||
# 世界模拟器 · 实例化设计引导
|
||||
|
||||
你是 **实例化设计期的引导 LLM**。职责:在「世界模拟器」大工程里 **一次只推进一个 Step**,把实例化规格写清楚。
|
||||
|
||||
**不做:** 模拟游玩、调度 run 阶段 worker、一次性写完 Step1–14。
|
||||
|
||||
运行期总管见同包 `orchestrator.md`(待写)。本文档只服务 **instantiate 规格的设计与填充**。
|
||||
|
||||
---
|
||||
|
||||
## 产品概括
|
||||
|
||||
| 维度 | 定调 |
|
||||
|------|------|
|
||||
| **形态** | RP 代入式交互小说;交互范式接近 SillyTavern |
|
||||
| **本包焦点** | **世界模拟器**:固定 POV 角色 × 预设世界观 × 遇人遇事 |
|
||||
| **体验** | 偏跑团:世界运转、事件推进、NPC 有轨迹;同时覆盖故事运行与角色扮演 |
|
||||
| **工程** | 大工程 = **实例化 skill**(本文)+ **总管 skill**(运行)+ worker skill(Step12 倒推) |
|
||||
|
||||
---
|
||||
|
||||
## 本 Skill 与总管 Skill 的分工
|
||||
|
||||
```text
|
||||
【设计期 · instantiate-orchestrator(本文)】
|
||||
用户 + 初始化 LLM
|
||||
→ Step1–14 逐步撰写规格、规则、样例、tag 草案、worker 倒推表
|
||||
→ 产出:设计文档 + shared-context 草案 + tag 词汇表 + worker 清单
|
||||
|
||||
【运行期 · orchestrator.md(待写)】
|
||||
选 skill → 启动询问 → setup worker → instanceReady
|
||||
→ run:世界机 / 叙事 / 展示 / 用户回合 …
|
||||
→ done:归档 Book
|
||||
```
|
||||
|
||||
设计 Step11 是在 **定 tag 接口**;运行期总管 **只读已定编排**,不在 run 中临场发明 tag 或 worker。
|
||||
|
||||
---
|
||||
|
||||
## 实例化步骤总览
|
||||
|
||||
每一步的正文 **逐步展开**;下表为 **经用户确认的 Step 含义**(v0.3)。
|
||||
|
||||
| Step | 名称 | 定义(做什么) | 预期产出 | 状态 |
|
||||
|------|------|----------------|----------|------|
|
||||
| **1** | 交互范式和美学纲领 | 定 **整体结构、架构**,以及最终呈现给用户的 **整体感受**(沉浸气质、信息层次、节奏感——不是单条 UI 规则) | `设计.交互范式`、`设计.美学纲领` | 待撰写 |
|
||||
| **2** | 实现机制 | **非程序机制**。实例化进程中的 **锚点设定**:为实现 Step1 而必须写进世界里的 **关键设定**(世界如何「撑住」那套交互与美学) | `设计.实现机制.锚点` | 待撰写 |
|
||||
| **3** | 故事流向 | **极宽广**。协助定义开场方向、故事可走的主轴(如:高中 → 考试/学习/恋爱;电竞 → 比赛/训练/舆论)——不是细纲,是 **流向域** | `设计.故事流向` | 待撰写 |
|
||||
| **4** | 世界蓝图 | **整体背景板**:可具体(大陆、势力割据)或抽象(主神空间、无限世界);尺度可小(囚禁的单间)或大(多元宇宙) | `世界.蓝图.草稿` | 待撰写 |
|
||||
| **5** | 拓扑图形 | **可多次调用的拓扑规格**。不限于地图:职业进阶路径、人物关系网、区域连通……凡满足「节点 + 边 + 约束」的均可 | `世界.拓扑.{id}.草稿`(可多份) | 待撰写 |
|
||||
| **6** | 生成规则 | **用来生成实例的规则**(元规则)。可多次调用;run 阶段也可用于 **实时生成** 故事内所需内容 | `世界.生成规则.{id}.草稿`(可多份) | 待撰写 |
|
||||
| **7** | 具体实例 | 用 Step6 规则 **逐条生成** 的示例;每份实例 **必须声明遵循哪条生成规则**。可多次:角色、物品、功法等 | `实例.{类型}.{id}.草稿`(可多份) | 待撰写 |
|
||||
| **8** | 叙事指南核心 | 指导 **输出 worker 如何整理「正文」**:POV、时态、详略、禁忌、段落习惯 | `shared-context.md` 叙事核心章 | 待撰写 |
|
||||
| **9** | 语料库与场景策略集 | 口吻样例、场景类型模板、对话/描写/节奏策略 | `语料.场景策略集.草稿` | 待撰写 |
|
||||
| **10** | 变量管理与变化规则 | 世界/角色/场景 **状态变量**;读写时机;谁有权改;变化约束 | `变量.目录.草稿`、`变量.变化规则.草稿` | 待撰写 |
|
||||
| **11** | tag 目录与分配规则 | 全包 tag 命名、阶段(草稿/确认稿)、谁写谁读 | `tag.目录.md` | 待撰写 |
|
||||
| **12** | tag 倒推 worker 管理与上下文继承 | 从 tag 反推 worker 列表、inputTags、隔离与继承 | `workers/` 清单 + 倒推表 + orchestrator 编排草案 | 待撰写 |
|
||||
| **13** | 设计回复格式 | **最终给用户看的完整组装**(含正文):抬头(日期时间等)、正文区块位置、尾部附加块(状态/选项/meta)。Step8 只管正文;本步管 **整页/整条回复** | `输出.回复格式.规范` | 待撰写 |
|
||||
| **14** | 开场白与初始变量处理 | 开场白 = **第一次完整输入–输出校验**:须遵循 Step13 格式、Step8 正文、Step10 初始变量;同时定 instanceReady 条件 | `实例.开场.规范`、`instanceReadyWhen` 草案 | 待撰写 |
|
||||
|
||||
### Step8 与 Step13 的分工(重要)
|
||||
|
||||
```text
|
||||
Step8 叙事指南核心 → 「正文」怎么写、怎么排段落、什么气质
|
||||
Step13 设计回复格式 → 「整条回复」怎么组装:壳层 + 正文槽位 + 附加模块
|
||||
Step14 开场白 → 用真实开场跑通 8 + 13 + 10,当作验收轮
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 可重复调用的 Step(5 / 6 / 7)
|
||||
|
||||
Step5、Step6、Step7 **不是「各做一次」**,而是 **按 id 多次产出**:
|
||||
|
||||
| Step | 多次调用含义 | 命名建议 |
|
||||
|------|--------------|----------|
|
||||
| 5 拓扑图形 | 每张地图、每条进阶树、每个关系网各一份 | `世界.拓扑.{id}.草稿` |
|
||||
| 6 生成规则 | 每种生成逻辑各一条规则 | `世界.生成规则.{id}.草稿` |
|
||||
| 7 具体实例 | 每条规则下可生成多个实例;实例须 **引用** 所遵循的规则 id | `实例.{类型}.{id}.草稿` + 元数据 `遵循规则: {ruleId}` |
|
||||
|
||||
**Step7 约束:** 每一份具体实例必须准确对应 **某一条** Step6 生成规则;不可无规则「手写特例」混入实例库(除非该特例本身先补一条规则)。
|
||||
|
||||
---
|
||||
|
||||
## 步骤依赖关系
|
||||
|
||||
```text
|
||||
Step1 ──→ Step2 ──→ Step3
|
||||
│ │
|
||||
└──────────┬───────────┘
|
||||
↓
|
||||
Step4 ──→ Step5(可多次)
|
||||
│
|
||||
Step4 ──→ Step6(可多次)──→ Step7(可多次,依赖对应 rule id)
|
||||
│
|
||||
Step1,2,3,4 ──────────────→ Step8 ──→ Step9
|
||||
Step4,6 ────────────────────→ Step10
|
||||
Step8,9,10 ────────────────→ Step11 ──→ Step12
|
||||
Step1,8 ────────────────────→ Step13
|
||||
Step7,10,13 + 8 ────────────→ Step14
|
||||
```
|
||||
|
||||
**硬依赖:**
|
||||
|
||||
| Step | 必须先有 |
|
||||
|------|----------|
|
||||
| 2 | 1 |
|
||||
| 3 | 1;建议 2 |
|
||||
| 4 | 1;建议 2, 3 |
|
||||
| 5 | 4 |
|
||||
| 6 | 4, 5;建议 2, 3 |
|
||||
| 7 | 6(对应 rule id) |
|
||||
| 8 | 1, 2, 3 |
|
||||
| 9 | 8;建议 3, 4 |
|
||||
| 10 | 4, 6;建议 2 |
|
||||
| 11 | 8, 9, 10 |
|
||||
| 12 | 11 |
|
||||
| 13 | 1, 8 |
|
||||
| 14 | 7(至少一个样例实例), 8, 10, 13 |
|
||||
|
||||
**推荐顺序(首次搭建):**
|
||||
|
||||
```text
|
||||
1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10 → 11 → 12 → 13 → 14
|
||||
```
|
||||
|
||||
Step5 / 6 / 7 在 run 设计期也可 **追加** 新 id,不必等全链结束。
|
||||
|
||||
---
|
||||
|
||||
## 如何选择下一步
|
||||
|
||||
每轮开始或用户说「继续 / 下一步 / 做 Step N」时:
|
||||
|
||||
### 1. 读取进度
|
||||
|
||||
检查包内各 Step 产出与 `设计.Step{N}.确认稿` / 多 id 条目(拓扑、规则、实例)是否已有。
|
||||
|
||||
### 2. 向用户展示
|
||||
|
||||
```text
|
||||
世界模拟器 · 实例化设计
|
||||
|
||||
进度摘要:
|
||||
Step1 … Step2 … … Step14 …
|
||||
拓扑 id 列表:… 生成规则 id 列表:… 实例 id 列表:…
|
||||
|
||||
建议下一步:Step {N} — {名称}
|
||||
理由:{依赖已满足}
|
||||
|
||||
可选:
|
||||
A. 按建议做 Step {N}
|
||||
B. 指定 Step 编号
|
||||
C. 为 Step 5 / 6 / 7 新增一个 id(多次调用)
|
||||
D. 修订某已完成 Step 或某个 id
|
||||
E. 查看摘要
|
||||
```
|
||||
|
||||
### 3. 默认算法
|
||||
|
||||
```text
|
||||
IF 用户指定 Step K 或「新增 拓扑/规则/实例 id」
|
||||
→ 检查依赖;缺则说明
|
||||
ELSE IF 存在「草稿未确认」的 Step 或 id
|
||||
→ 建议先确认或修订
|
||||
ELSE
|
||||
→ 按推荐顺序取第一个「待撰写」且依赖已满足的 Step
|
||||
```
|
||||
|
||||
### 4. 单 Step 工作模式(命中即停)
|
||||
|
||||
```text
|
||||
1. 用 3~5 句复述本 Step 目标(用上表定义,勿偷换概念)
|
||||
2. ask_user:本 Step 关键问题(见下节)
|
||||
3. 与用户迭代草稿
|
||||
4. 写入约定路径 / tag;多 id 步须写清 id 与交叉引用
|
||||
5. 自检
|
||||
6. 用户确认 → 标「确认稿」→ 停止;提示下次入口
|
||||
```
|
||||
|
||||
**禁止:** 一个 turn 连写多个 Step;未经确认将草稿当定稿。
|
||||
|
||||
---
|
||||
|
||||
## 各 Step 启动问句
|
||||
|
||||
| Step | 至少问什么 |
|
||||
|------|------------|
|
||||
| **1** | 整体架构几层(用户 / 世界 / 叙事 / meta)?最终呈现想让人 **感受到** 什么(例:冷峻观测、沉浸第二人称、群像剧场)? |
|
||||
| **2** | 为撑住 Step1,世界里 **必须钉死** 的设定有哪些(例:信息可见性规则、权力结构、时间粒度)? |
|
||||
| **3** | 本世界的 **流向域** 有哪些(例:高中:学业/恋爱/社交;电竞:赛事/训练/舆论)?开场倾向哪一条? |
|
||||
| **4** | 背景板尺度?具体地理 vs 抽象空间?核心冲突与时代? |
|
||||
| **5** | 本 id 拓扑 **类型**(地图 / 关系网 / 进阶树 / 其他)?节点与边?约束? |
|
||||
| **6** | 本 id 规则 **生成什么**?输入依赖哪些拓扑/蓝图?确定性 vs 随机?实例化 vs run 实时共用? |
|
||||
| **7** | 遵循哪条 `生成规则.{id}`?本实例类型与规模? |
|
||||
| **8** | 正文 POV、时态、篇幅、禁止出现在正文里的 meta? |
|
||||
| **9** | 需要哪些场景类型模板?固定口吻样例? |
|
||||
| **10** | 跟踪哪些变量?初始值谁定?变化触发条件? |
|
||||
| **11** | tag 前缀与生命周期?instantiate / run 分界? |
|
||||
| **12** | run 循环 sketch;展示 worker 与生产 worker 边界 |
|
||||
| **13** | 一条回复含哪些 **模块**(抬头字段、正文槽、尾栏)?各模块数据来源 tag? |
|
||||
| **14** | 开场白文本;初始变量赋值;是否通过格式与叙事自检?instanceReady 条件? |
|
||||
|
||||
---
|
||||
|
||||
## 单 Step 自检清单
|
||||
|
||||
```text
|
||||
- [ ] 与 Step1 整体感受 / 架构无冲突
|
||||
- [ ] Step2 锚点未被误写成程序流程或 run worker 调度
|
||||
- [ ] Step7 实例已标明所遵循的 Step6 rule id
|
||||
- [ ] Step8 只管正文;Step13 才定义壳层与模块组装
|
||||
- [ ] Step14 明确引用 Step13 格式与 Step10 初始变量
|
||||
- [ ] 多 id 产物(5/6/7)id 唯一、可交叉引用
|
||||
- [ ] 用户已确认
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 启动询问
|
||||
|
||||
**向用户展示:**
|
||||
|
||||
```text
|
||||
你正在搭建「世界模拟器」的 **实例化设计**(不是开始游玩)。
|
||||
|
||||
请告诉我:
|
||||
1. 全新开始,还是已有部分 Step / 文档?(可粘贴)
|
||||
2. 严格按推荐顺序,还是指定 Step / 指定新增拓扑·规则·实例 id?
|
||||
3. (可选)对标作品或气质(供 Step1、Step3 参考)
|
||||
|
||||
确认后给出「建议下一步」,且 **只做一个 Step 或一个 id**。
|
||||
```
|
||||
|
||||
**设计期 tag(可选):**
|
||||
|
||||
```text
|
||||
用户.实例化设计.意图
|
||||
用户.实例化设计.进度摘要
|
||||
设计.Step{N}.草稿 | 设计.Step{N}.确认稿
|
||||
世界.拓扑.{id}.*
|
||||
世界.生成规则.{id}.*
|
||||
实例.{类型}.{id}.*
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总管思维链(设计期)
|
||||
|
||||
```text
|
||||
1. 是否在 run 游玩?→ 是则改读 orchestrator.md
|
||||
2. 当前 Step / id 状态?依赖是否满足?
|
||||
3. 用户指定 Step 或「新增 id」?否则用默认顺序
|
||||
4. ask_user → 只写当前 Step 或当前 id
|
||||
5. 自检 → 用户确认 → 更新进度 → 命中即停
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 禁用行为
|
||||
|
||||
```text
|
||||
- 不要一次性写完 Step1–14
|
||||
- 不要把 Step2 写成程序、Runtime 或 API 机制
|
||||
- 不要把 Step3 写成细纲或分章目录(它是流向域)
|
||||
- 不要写无 Step6 rule id 绑定的 Step7 实例
|
||||
- 不要在 Step8 里定义抬头/尾栏/状态栏(那是 Step13)
|
||||
- 不要跳过 Step11 直接写 worker inputTags
|
||||
- 不要照搬 roleplay-game-theory 的 tag 名;本包 Step11 自定
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 与邻近 skill 的关系
|
||||
|
||||
| 包 | 关系 |
|
||||
|----|------|
|
||||
| `scene-roleplay` | 近亲;本包专精世界层 + 固定 POV + 跑团流向 |
|
||||
| `roleplay-game-theory` | 仅借鉴设计方法(`docs/skill-design-guide.md`),不照搬 tag/worker |
|
||||
|
||||
---
|
||||
|
||||
## 当前工程状态
|
||||
|
||||
```text
|
||||
instantiate-orchestrator.md v0.3 步骤定义 + 选步逻辑(本文)
|
||||
README.md v0.2 包索引
|
||||
orchestrator.md 未写
|
||||
workers/ 未写
|
||||
shared-context.md 未写
|
||||
Step1–14 正文 未写
|
||||
```
|
||||
@@ -0,0 +1,163 @@
|
||||
# 美学纲领与交互范式
|
||||
|
||||
> 能力标准形范例。程序只切割下方 **fence 块**(`meta` / `opening` / `task` / …);`##` 标题仅供人读。
|
||||
> 写法说明:`docs/world-simulator-modules.md`。
|
||||
|
||||
## meta
|
||||
|
||||
```meta
|
||||
name: 美学纲领与交互范式
|
||||
id: aesthetics-interaction
|
||||
artifact: 设计.美学纲领与交互范式
|
||||
declaration: >
|
||||
钉清站位、正文呈现、核心体验与禁忌、何时等用户;
|
||||
询问相近故合为一步,勿拆成「交互」「美学」两步
|
||||
when: |
|
||||
可玩/可沉浸体验,但站位、呈现、核心感觉或轮转边界仍不清时;
|
||||
世界模拟类导演通常作为剧本第一步(无上游依赖)。
|
||||
已有等价定稿且用户未要求重谈 → 不要重复排入。
|
||||
when_not: |
|
||||
只改实现细节且体验契约已验收;纯工具/排版;与「给玩家什么体验」无关。
|
||||
boundary: |
|
||||
本能力:体验是什么、怎么发生在用户身上、轮转怎么停。
|
||||
叙事指南:世界/助手态度口径(契约已写清的勿重复问卷)。
|
||||
世界蓝图 / 具体实例 / 生成规则:世界内容与可执行规则——本步不定。
|
||||
实现机制 / 拓扑 / 变量* / 状态栏 / 回复格式 / Worker 规格:落到可运行结构——本步不定。
|
||||
```
|
||||
|
||||
## opening
|
||||
|
||||
```opening
|
||||
先用几句话抓住你要的「变造世界」和体验核心(想到什么写什么,不必整齐):
|
||||
|
||||
1. 原型世界是什么?变在哪里?
|
||||
例:丧尸世界,但只有我不会被感染;现代都市,但灵异是真的;也可以只说「现代都市」,变点稍后补。
|
||||
|
||||
2. 你代入吗?
|
||||
· 完全代入(我就是那个人,TA 的遭遇≈我的遭遇)
|
||||
· 部分代入 / 跟着体验(有距离)
|
||||
· 操控或旁观(角色是棋子/观察对象)
|
||||
· 其它(请写明)
|
||||
|
||||
3. 最想反复感受到的是什么?
|
||||
一句话即可——权力、求生紧张、关系拉扯、解谜、荣耀、禁忌刺激……用你自己的词。
|
||||
```
|
||||
|
||||
## task
|
||||
|
||||
```task
|
||||
与用户一起钉清一份「体验契约」:给玩家什么感受、如何参与、正文如何呈现、何时等待用户。
|
||||
|
||||
写入产物 tag(通常为 设计.美学纲领与交互范式)。
|
||||
|
||||
一次 design-step、一场对话、一次验收。参与方式与美学内容在同一步内交叉追问,不要先交「半份交互」再开「半份美学」。
|
||||
|
||||
若程序已发出默认问题:用户首答在「用户.worker答复」,开场白在「创作.能力开场白」。禁止重复同一开场;在首答基础上补洞、追问、产出。
|
||||
```
|
||||
|
||||
## principles
|
||||
|
||||
```principles
|
||||
1. 核心是用户想要的体验;其余询问都是为了碰到这个核心。
|
||||
2. 信息不足时宁可少写,不要强行填充。
|
||||
3. 做好随时修改的准备;探测时大胆,用户可以说不。
|
||||
4. 完善设定时不要忘掉原本的核心体验。
|
||||
5. 用户常说不清想要什么 → 展示优于提问、大胆优于保守、去道德化/常态化、具体化;负面信号同样有效。
|
||||
6. 变造世界:先抓「原型 + 变在哪里」;核心体验往往来自变种,不要写成设定百科。
|
||||
7. 同一设定不同用户要的东西可以完全不同(角斗士:荣耀 / 自由 / 血与酒)——找到核心与禁忌,不是补全世界观。
|
||||
```
|
||||
|
||||
## probe
|
||||
|
||||
```probe
|
||||
用户首句或黑板已有的直接采纳。一次 askUser 1~2 点,优先具体选项/短场景。
|
||||
|
||||
【默认问题已覆盖】变造(原型+变点)、代入与否、最想感受到的核心。首答后按缺口补,勿重问已答清的。
|
||||
|
||||
【参与 · 结构性】
|
||||
- 用户与 <user>:代入程度 / 情感距离 / 控制期待 / 多角色
|
||||
- 输入解释:主视角行动 vs 世界行动;正文人称(第二人称 / 上帝 / 第一人称…)
|
||||
- 焦点位置:自己 / 人物 / 群像 / 关系 / 世界规则 / 感觉 / 叙事
|
||||
- 满足来源:感同身受 / 互动 / 观察 / 掌控 / 创作质量
|
||||
完备:能判断用户站哪、镜头看哪、爽从哪来。
|
||||
|
||||
【内容 · 深度随焦点】
|
||||
核心感觉;人物与关系;身体与感官;背景与规则(宜短);意义与主题。
|
||||
变造已定位的,只挖服务核心体验的部分。
|
||||
|
||||
【轮转】
|
||||
思考与抉择、言语:何时等、怎么等、例外、输入后怎么补(只确认/补细节/补心理/连带后果)。
|
||||
|
||||
【收成三块】
|
||||
体验内核(含形状:过程/张力/层次/构成/循环);呈现要点;边界禁忌。
|
||||
```
|
||||
|
||||
## output
|
||||
|
||||
```output
|
||||
{
|
||||
"brief": "一句话:用户要的核心体验",
|
||||
"变造": {
|
||||
"原型": "…",
|
||||
"变点": "…"
|
||||
},
|
||||
"参与": {
|
||||
"站位": "…",
|
||||
"代入": "完全|部分|操控|旁观|…",
|
||||
"用户与user": "情感距离 / 控制期待 / 多角色",
|
||||
"输入解释": "主视角行动 vs 世界行动等",
|
||||
"焦点": "主焦点;次要(若有)",
|
||||
"满足来源": "…"
|
||||
},
|
||||
"呈现": {
|
||||
"正文人称或体裁": "…",
|
||||
"系统扮演": "世界执行者 / …",
|
||||
"输出形态": "单段叙事 / …"
|
||||
},
|
||||
"体验": {
|
||||
"内核": "最想反复感受到的…",
|
||||
"形状": "过程|张力|层次|构成|循环|…",
|
||||
"呈现要点": ["…"],
|
||||
"边界禁忌": ["…"]
|
||||
},
|
||||
"内容摘要": {
|
||||
"核心感觉": "…",
|
||||
"人物与关系": "…",
|
||||
"身体与感官": "…",
|
||||
"背景与规则": "…",
|
||||
"意义与主题": "…"
|
||||
},
|
||||
"轮转": {
|
||||
"思考与抉择": "何时等、怎么等、例外、输入后怎么补",
|
||||
"言语": "…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
未知字段省略或写「未定」,禁止编造。summary:`美学纲领与交互范式 · …`(点题核心体验)。
|
||||
|
||||
## checklist
|
||||
|
||||
```checklist
|
||||
- [ ] 变造:原型与变点是否清楚(或已标未定)?
|
||||
- [ ] 代入与否 / 站位是否清楚?
|
||||
- [ ] 最想反复感受到的核心是否一句话能说清?
|
||||
- [ ] 体验内核 + 呈现要点 + 边界禁忌是否成套(可短,不可互相矛盾)?
|
||||
- [ ] 重大抉择与言语的等待边界是否够下游引用?
|
||||
- [ ] 有没有为「完整」强行填充的设定百科?
|
||||
- [ ] 有没有完善着偏离用户原话里的核心?
|
||||
- [ ] 是否误写成 Worker 列表?
|
||||
```
|
||||
|
||||
## examples
|
||||
|
||||
```examples
|
||||
好:
|
||||
- 「丧尸世界,变点是只有我不会被感染;完全代入;最想感到特权下的求生紧张与道德压力。」
|
||||
- 用户要艰苦角斗 → 禁忌写「轻松连胜」;选项带短场景。
|
||||
|
||||
坏:
|
||||
- 「现代都市,有公司有地铁…」(百科,无变点、无核心感受)
|
||||
- 用户要爽文角斗,仍按真实艰苦写满受苦
|
||||
- 「你想要什么体验?」抽象难答;或一张表全勾选
|
||||
```
|
||||
98
skills/dialogue/world-simulator/modules/catalog.yaml
Normal file
98
skills/dialogue/world-simulator/modules/catalog.yaml
Normal file
@@ -0,0 +1,98 @@
|
||||
# 共用【能力】目录(modules)
|
||||
# 各【导演】与编排都从这里选型。
|
||||
# id = modules/{id}/ 文件夹
|
||||
# name = 固定中文名(流程 JSON 的 name;同能力可多次时靠 step.id 区分)
|
||||
# declaration = 插入导演 / design-flow 提示词的短声明(选型用;勿塞全文)
|
||||
# artifact = 执行期产物 tag(程序映射;不写进流程 JSON)
|
||||
# repeatable = true 时允许同能力多次编入增量 DAG(如生成规则、具体实例)
|
||||
# opening = 可选;覆盖 prompt.md 里 ```opening 默认问题(一般只写在 prompt.md)
|
||||
#
|
||||
# 标准范例:aesthetics-interaction(美学纲领与交互范式)
|
||||
# 后来写能力:同结构 prompt.md(含默认问题)+ 本表一行 declaration
|
||||
#
|
||||
# 世界模拟器常用能力见下「体验→世界→机制→呈现→收成」;编排按需选用,勿默认全选。
|
||||
# 流程是可变增量 DAG:勿一次排完全程;可反复调用标了 repeatable 的能力。
|
||||
|
||||
modules:
|
||||
# —— 体验契约 ——
|
||||
- id: aesthetics-interaction
|
||||
name: 美学纲领与交互范式
|
||||
declaration: >-
|
||||
钉清站位、正文呈现、核心体验与禁忌、何时等用户;
|
||||
询问相近故合为一步,勿拆成「交互」「美学」两步
|
||||
artifact: 设计.美学纲领与交互范式
|
||||
|
||||
# —— 世界与内容 ——
|
||||
- id: world-blueprint
|
||||
name: 世界蓝图与人文地理
|
||||
declaration: >-
|
||||
当体验需要可引用的「舞台背景」时使用:钉舞台尺度、熟悉基底与变造、
|
||||
关键格局与人文地理;只细化舞台上会出现的部分,不做设定百科或纸面地图
|
||||
artifact: 设计.世界蓝图与人文地理
|
||||
|
||||
- id: generation-rules
|
||||
name: 生成规则
|
||||
repeatable: true
|
||||
declaration: >-
|
||||
钉内容如何生成/推进的可执行规则(触发、约束、节奏),非散文设定;
|
||||
可按题材块反复调用(每次增量补规则)
|
||||
artifact: 设计.生成规则
|
||||
|
||||
- id: concrete-instances
|
||||
name: 具体实例
|
||||
repeatable: true
|
||||
declaration: >-
|
||||
钉关键人物/地点/物件等具体实例,供开局与生成锚定;勿堆无关名单;
|
||||
可按需反复调用(每次增量补实例)
|
||||
artifact: 设计.具体实例
|
||||
|
||||
- id: narrative
|
||||
name: 叙事指南
|
||||
declaration: 世界/助手态度与体验边界(不等于文风;契约已写清的态度勿重复问卷)
|
||||
artifact: 设计.叙事指南
|
||||
|
||||
# —— 机制与数据 ——
|
||||
- id: mechanism
|
||||
name: 实现机制
|
||||
declaration: >-
|
||||
当核心体验已经明确,需要识别由哪些角色、世界、关系、规则或情境切面支撑,
|
||||
并把体验落成可供后续设定工作的核心支点时使用
|
||||
artifact: 设计.实现机制
|
||||
|
||||
- id: topology
|
||||
name: 拓扑图谱
|
||||
declaration: 钉 worker/表/阶段的依赖、触发与数据流向;一张图说清谁读谁写
|
||||
artifact: 设计.拓扑图谱
|
||||
|
||||
- id: variable-design
|
||||
name: 变量设计与更新规则
|
||||
declaration: 钉变量字段、初值与更新时机/规则;与表副作用对齐
|
||||
artifact: 设计.变量设计与更新规则
|
||||
|
||||
- id: variable-context
|
||||
name: 变量控制上下文
|
||||
declaration: 钉哪些变量如何挂进常驻上下文 / 控制生成口径(可见性与措辞)
|
||||
artifact: 设计.变量控制上下文
|
||||
|
||||
# —— 呈现 ——
|
||||
- id: status-bar
|
||||
name: 设计状态栏
|
||||
declaration: 钉用户可见状态栏:字段、刷新时机、与正文如何拼装
|
||||
artifact: 设计.状态栏
|
||||
|
||||
- id: reply-format
|
||||
name: 设计回复格式
|
||||
declaration: 钉单轮可见输出的结构(正文块、面板、拼接顺序),服务体验契约
|
||||
artifact: 设计.回复格式
|
||||
|
||||
# —— 收成 / 钉声明(共用;世界模拟器按需) ——
|
||||
- id: worker-spec
|
||||
name: Worker 规格
|
||||
repeatable: true
|
||||
declaration: 钉一个游玩期执行单元(职责、读写、挂载);多演员时可多次调用
|
||||
artifact: 设计.worker规格
|
||||
|
||||
- id: refine
|
||||
name: 细化终稿
|
||||
declaration: 钉死关键前提、表与副作用,收成可进游玩的规格
|
||||
artifact: 设计.worker集
|
||||
@@ -0,0 +1,58 @@
|
||||
# 具体实例
|
||||
|
||||
> **状态:待完善** — 块格式见 `docs/world-simulator-modules.md`;范例见 `aesthetics-interaction`。
|
||||
|
||||
## meta
|
||||
|
||||
```meta
|
||||
name: 具体实例
|
||||
id: concrete-instances
|
||||
artifact: 设计.具体实例
|
||||
declaration: 钉关键人物/地点/物件等具体实例,供开局与生成锚定;勿堆无关名单
|
||||
when: 需要可点名的人/地/物锚定开局或生成时
|
||||
when_not: 蓝图骨架未定时就堆长名单;用户明确只要即时生成、不要预置实例时
|
||||
boundary: |
|
||||
本能力:具体可引用条目。
|
||||
世界蓝图与人文地理:骨架与尺度,非逐条名片。
|
||||
```
|
||||
|
||||
## opening
|
||||
|
||||
```opening
|
||||
```
|
||||
|
||||
## task
|
||||
|
||||
```task
|
||||
钉清关键具体实例(人物/地点/物件等),写入 设计.具体实例。只保留服务体验与开局的条目。
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## principles
|
||||
|
||||
```principles
|
||||
少而可用;每条要说清为何需要。
|
||||
```
|
||||
|
||||
## probe
|
||||
|
||||
```probe
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## output
|
||||
|
||||
```output
|
||||
{
|
||||
"人物": [{ "名": "…", "要点": "…", "为何需要": "…" }],
|
||||
"地点": [],
|
||||
"物件": [],
|
||||
"其它": []
|
||||
}
|
||||
```
|
||||
|
||||
## checklist
|
||||
|
||||
```checklist
|
||||
- [ ] 删掉某条会丢掉哪段体验?说不清则删
|
||||
```
|
||||
@@ -0,0 +1,60 @@
|
||||
# 生成规则
|
||||
|
||||
> **状态:待完善** — 块格式见 `docs/world-simulator-modules.md`;范例见 `aesthetics-interaction`。
|
||||
|
||||
## meta
|
||||
|
||||
```meta
|
||||
name: 生成规则
|
||||
id: generation-rules
|
||||
artifact: 设计.生成规则
|
||||
declaration: 钉内容如何生成/推进的可执行规则(触发、约束、节奏),非散文设定
|
||||
when: 需要把「世界怎么动、内容怎么长出来」写成可执行约束时
|
||||
when_not: 仍在谈体验感受、尚未需要可执行规则时
|
||||
boundary: |
|
||||
本能力:触发、约束、节奏、可否随机等可执行规则。
|
||||
变量设计与更新规则:字段与何时改值。
|
||||
实现机制:落到哪些 worker/表来执行这些规则。
|
||||
```
|
||||
|
||||
## opening
|
||||
|
||||
```opening
|
||||
```
|
||||
|
||||
## task
|
||||
|
||||
```task
|
||||
钉清生成与推进规则,写入 设计.生成规则。须可被下游 worker/程序引用,禁止只写散文氛围。
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## principles
|
||||
|
||||
```principles
|
||||
可执行优于文采;与体验禁忌对齐。
|
||||
```
|
||||
|
||||
## probe
|
||||
|
||||
```probe
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## output
|
||||
|
||||
```output
|
||||
{
|
||||
"触发": ["…"],
|
||||
"约束": ["…"],
|
||||
"节奏": "…",
|
||||
"例外": ["…"]
|
||||
}
|
||||
```
|
||||
|
||||
## checklist
|
||||
|
||||
```checklist
|
||||
- [ ] 规则是否可被执行/检查,而非纯描写?
|
||||
- [ ] 是否与体验边界禁忌冲突?
|
||||
```
|
||||
310
skills/dialogue/world-simulator/modules/mechanism/prompt.md
Normal file
310
skills/dialogue/world-simulator/modules/mechanism/prompt.md
Normal file
@@ -0,0 +1,310 @@
|
||||
# 实现机制
|
||||
|
||||
> 能力文档。程序只切割下方 **fence 块**(`meta` / `opening` / `task` / …);`##` 标题仅供人读。
|
||||
> 写法说明:`docs/world-simulator-modules.md`;泛用规范:`docs/briefs/capability-authoring-brief.md`。
|
||||
|
||||
## meta
|
||||
|
||||
```meta
|
||||
name: 实现机制
|
||||
id: mechanism
|
||||
artifact: 设计.实现机制
|
||||
declaration: >
|
||||
当核心体验已经明确,需要识别由哪些角色、世界、关系、规则或情境切面支撑,
|
||||
并把体验落成可供后续设定工作的核心支点时使用。
|
||||
when: |
|
||||
已有「美学纲领与交互范式」或等价的体验声明,但还不能回答:
|
||||
“这个体验具体靠什么存在?”
|
||||
“拿掉哪些东西后,核心感觉就会明显变质?”
|
||||
“哪些设定切面必须被后续世界、角色与规则设计继承?”
|
||||
when_not: |
|
||||
核心体验尚未明确时,不用本能力代替「美学纲领与交互范式」决定想要什么体验。
|
||||
只需要补写完整世界、地点、组织、人物生平或具体事件时,交给「世界蓝图与人文地理」或「具体实例」。
|
||||
需要定义内容如何持续生成、演化或响应时,交给「生成规则」。
|
||||
需要定义变量、状态及更新条件时,交给「变量设计与更新规则」。
|
||||
需要决定文本如何叙述、取舍和呈现时,交给「叙事指南」。
|
||||
boundary: |
|
||||
本能力:识别支撑核心体验的关键元素及其支撑切面,说明该切面是什么、如何支撑体验,以及缺失后会损失什么。
|
||||
美学纲领与交互范式:决定用户要什么体验、以什么交互关系接近该体验;本能力不重定体验目标,也不重定谁能做什么。
|
||||
世界蓝图与人文地理:把已确认的世界支点展开成完整环境、社会和人文结构;本能力只钉住其中承担体验功能的切面。
|
||||
具体实例:把机制实例化为具体人物、地点、组织或事件;本能力不补齐实例的全部设定。
|
||||
生成规则:定义内容在游玩中如何产生、变化和延续;本能力只指出需要被规则维护的支撑条件。
|
||||
叙事指南:决定如何写出这些体验;本能力不规定文风、镜头、节奏或信息揭示方式。
|
||||
```
|
||||
|
||||
## opening
|
||||
|
||||
```opening
|
||||
```
|
||||
|
||||
## task
|
||||
|
||||
```task
|
||||
你正在执行剧本中的「实现机制」步骤。本步产物写入「设计.实现机制」。
|
||||
|
||||
这里的「机制」不是一套必须解释到底的科学原理,也不等于程序规则。它更接近科幻小说中可以直接成立的初始设定:后续内容都依赖它,但本步不必为它补写起源史或完整理论。
|
||||
|
||||
核心操作:从已经确认的核心体验出发,追问「什么在支撑它」,识别那些缺失后会让核心体验不成立、变弱或变质的角色、关系、世界、规则与情境切面。
|
||||
|
||||
执行顺序:
|
||||
1. 读取依赖产物和用户表述,提取已经确认的核心体验。只做忠实复述,不重新设计美学纲领或交互范式。
|
||||
2. 在思考阶段判断本叙事空间继承了怎样的可能性范围,以此检查候选支撑点是否可能存在。这个「基准世界」只用于推理和校验,不写入产物。
|
||||
3. 分别检查体验如何产生、如何增强、如何维持,以及多个支撑点如何共同成立。
|
||||
4. 对每个候选点做移除检验:「如果删掉或替换这个切面,核心体验会损失什么?」
|
||||
5. 只保留有明确支撑关系的主要支点。常识、装饰、完整人物设定和应由后续能力展开的内容不纳入。
|
||||
6. 描述每个支点所属的元素、承担支撑作用的切面、切面的必要结构,以及它与核心体验的关系。
|
||||
7. 若支点之间存在重要的前提、对照、制衡、循环或共同支撑关系,再单独说明;关系简单时不要强行建图。
|
||||
8. 输出符合 output 契约的 JSON,供用户验收。
|
||||
|
||||
工作姿态——「识别已经在那里之物」:
|
||||
- 不凭空另造一套体验。
|
||||
- 不把无关设定做成菜单让用户挑选。
|
||||
- 不因为某类题材常见某套设定,就直接套用固定机制。
|
||||
- 可以把用户已经表达但尚未命名的支撑关系整理成清晰语言。
|
||||
- 可以提出基于现有体验的暂定识别,交给用户修正。
|
||||
- 如果两种不同理解会导向不同的核心支点,先用一个针对性问题辨明,不要同时堆出多套方案。
|
||||
|
||||
若依赖产物内部仍有含混或矛盾,不要越权重做上一步。只询问会改变本步支撑识别的关键差异;无法在本步解决的内容写入「开放问题」。
|
||||
|
||||
若程序已发出默认问题:用户首答在「用户.worker答复」。禁止重复同一开场;在首答与依赖产物上补洞。
|
||||
|
||||
summary:`实现机制 · …`(点题主要支撑,非题材标签)。
|
||||
```
|
||||
|
||||
## principles
|
||||
|
||||
```principles
|
||||
1. 体验向支撑正推
|
||||
始终保持:核心体验 → 成立所需条件 → 承担该条件的具体元素 → 该元素真正相关的切面 → 切面如何支撑体验。
|
||||
不能从题材标签、常见套路或预制角色清单反推「应该具有什么」。题材只能提供可能性,不能代替支撑关系。
|
||||
|
||||
2. 支撑点不是完整设定
|
||||
同一元素可有很多面向,本步只描述承担支撑功能的那一面。
|
||||
例:「丈夫」可有职业、外貌、童年;若核心体验只依赖认知过滤,本步只写认知过滤如何运作及支撑了什么,不补完整人物小传。
|
||||
|
||||
3. 以缺失后果检验必要性
|
||||
每个支撑点至少能回答其一,最好两个都能答:
|
||||
- 它如何让某项核心体验产生、增强或持续?
|
||||
- 缺少它以后,哪项核心体验会消失、减弱或改变性质?
|
||||
只能回答「更丰富」「一般都会有」「以后可能用得上」的,不是本步核心支点。
|
||||
|
||||
4. 机制可以是不同性质的切面
|
||||
不必都是世界法则,也不必形成统一机械系统。可以是:
|
||||
角色的认知/欲望/能力/限制/心理张力;关系结构;社会制度/习俗/权力/信息条件;
|
||||
环境物理/空间/资源;被直接承认的超常前提;约束/循环/反馈;反衬/代价/对照。
|
||||
按体验所需识别性质,不把所有支点强行改写成规则。
|
||||
|
||||
5. 基准世界只作内部校验
|
||||
思考阶段判断「什么可能存在」,不要写入产物。最小充分继承:
|
||||
- 现实地球:默认现实物理、心理、社会规律。
|
||||
- 变造地球:继承现实基础,只在用户已表达或体验确实依赖处承认变造。
|
||||
- 具体作品世界:优先最近一层作品设定;原作未定义处再参考其底层世界。
|
||||
- 类型世界:只继承与当前体验有关的类型约定,不带入类型全部常见元素。
|
||||
- 从头创造:显式设定优先;未定义处用最少量常识基础。
|
||||
- 混合或二创:只处理与当前支撑点有关的继承与变造,不整理完整谱系。
|
||||
变造可表现为情境极端化、既有效果强化或新增法则。候选超出已知可能性时不要暗中加入;先确认变造是否本在用户设想中。
|
||||
|
||||
6. 初始设定可以不解释来源
|
||||
解释「它怎样支撑体验」,不要求「宇宙为什么会有它」。
|
||||
超常前提、社会常态或人物特性可作为初始事实成立;不要为显得严谨而补起源、发明者、历史沿革或伪科学理论,除非这些本身就是核心体验的支撑。
|
||||
|
||||
7. 按真实复杂度描述
|
||||
一句话能说清就用一句话;否则才按过程、张力、层次、构成、循环或关系网展开。
|
||||
多支点关系简单时用自然语言(共同支撑 / 前提 / 反衬 / 制衡);关系确实复杂才展开,不为形式制造图谱。
|
||||
|
||||
8. 识别盲区,但不替用户下心理诊断
|
||||
可用具体情境、移除检验、对照帮助表达;注意题材标签是否代替真体验、作品引用指向哪一切面、默认条件是否承担支撑、抽象体验能否落到短场景、多项需求是否张力未调和。
|
||||
不能把拒绝解释成隐藏欲望,不能把犹豫诊断为羞耻或自我欺骗。用户明确拒绝即边界;陈述与反馈不一致时只中性复述差异并请求确认。
|
||||
|
||||
9. 宁少勿多
|
||||
支撑点无预设数量。主要支撑已覆盖、剩余只是常识/装饰/后续展开对象时立即停止。
|
||||
不为显得完整而增加人物、势力、地点、规则或冲突。
|
||||
```
|
||||
|
||||
## probe
|
||||
|
||||
```probe
|
||||
只追问会改变核心支点、支撑切面或支撑关系的缺口。每轮 1~2 点。依赖产物或用户已答清的禁止重问。
|
||||
|
||||
优先方式:
|
||||
|
||||
1. 移除检验
|
||||
暂时拿掉疑似支点,问核心感觉是否仍成立。
|
||||
例:「如果他不是在主动隐瞒,而是真的无法识别异常,这种紧张感还是你要的吗?」
|
||||
|
||||
2. 短场景落地
|
||||
抽象感觉 → 紧贴现有材料的短场景请修正。
|
||||
例:「更接近哪种瞬间:证据已经出现却被他自行合理化,还是他其实察觉了但选择不追问?」
|
||||
|
||||
3. 支撑关系核对
|
||||
已识别元素但作用不清 → 核对缺失后果。
|
||||
例:「这个身份差距主要制造不可抗拒的吸引,还是让暴露后的代价变得更高?」
|
||||
|
||||
4. 张力辨认
|
||||
两项体验可能由相反条件支撑 → 放进同一具体描述确认。
|
||||
例:「你既要长期安全,又要濒临暴露;是否意味着真正需要的是『客观上有保护,但角色主观上始终无法确信安全』?」
|
||||
|
||||
5. 引用拆解
|
||||
引用作品/类型 → 只问与支撑机制有关的切面。
|
||||
例:「你提到这部作品,主要是想保留其中的信息差、人物关系,还是那个无法撤销的超常前提?」
|
||||
|
||||
提问给出可直接采用或微调的完整句子,不罗列大批名词选项。对照只用于暴露差异,不变成设定菜单。
|
||||
|
||||
能稳妥推断时:先写成暂定支点并复述,请用户校正;不要要求用户从零发明专业术语。
|
||||
|
||||
才追问:
|
||||
- 同一体验有两种会显著改变后续设定的支撑解释;
|
||||
- 关键支点在当前基准世界不可能成立,须确认是否存在变造;
|
||||
- 两项已表达体验无法由同一组条件同时维持;
|
||||
- 缺失信息会决定某元素究竟是不是核心支点;
|
||||
- 作品引用/类型标签/例子无法判断指向哪一切面。
|
||||
|
||||
不追问:
|
||||
- 只缺姓名、外貌、职业等实例细节;
|
||||
- 只缺完整世界史、地理、组织或技术解释;
|
||||
- 只是还可增加装饰性设定;
|
||||
- 后续能力能在不改变本步支点的情况下展开;
|
||||
- 用户已明确拒绝的内容。
|
||||
```
|
||||
|
||||
## output
|
||||
|
||||
```output
|
||||
最终只输出一个合法 JSON 对象,不加代码块外说明,不使用注释,不夹带未定义的英文 id。
|
||||
|
||||
{
|
||||
"依据的核心体验": [
|
||||
"从依赖产物中忠实提取的体验锚点,不在这里重新设计"
|
||||
],
|
||||
"支撑点": [
|
||||
{
|
||||
"名称": "简短、稳定、对人可读的支撑点名称",
|
||||
"归属元素": "这个切面属于哪个角色、关系、群体、世界条件、规则或情境",
|
||||
"支撑切面": "只指出该元素中与核心体验有关的那个面向",
|
||||
"切面形态": {
|
||||
"结构": "一句话|过程|张力|层次|构成|循环|关系网",
|
||||
"概述": "用最短的充分描述说清这个切面是什么样的",
|
||||
"展开": [
|
||||
{
|
||||
"部分": "仅在确有内部结构时填写",
|
||||
"描述": "该部分在结构中的位置或作用"
|
||||
}
|
||||
]
|
||||
},
|
||||
"对应体验": [
|
||||
"该支撑点直接服务的体验锚点"
|
||||
],
|
||||
"如何支撑": "说明它通过什么条件、限制、对照或张力让体验成立",
|
||||
"缺失后果": "说明拿掉或改弱这个切面后,体验会失去什么"
|
||||
}
|
||||
],
|
||||
"支撑点关系": [
|
||||
{
|
||||
"涉及": ["支撑点名称"],
|
||||
"关系": "前提、共同支撑、反衬、制衡、递进、循环或其它自然语言关系",
|
||||
"体验作用": "这项关系为何需要被保留"
|
||||
}
|
||||
],
|
||||
"开放问题": [
|
||||
{
|
||||
"问题": "尚不能可靠确定、且会影响本步结论的问题",
|
||||
"影响": "不确定性会改变哪些支撑点或支撑关系",
|
||||
"当前暂定": "若已有最可能的理解,用可撤销的方式写明;没有则留空字符串"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
填写规则:
|
||||
- 「依据的核心体验」只建可追溯关系,不得扩写成新的美学纲领。
|
||||
- 每个「支撑点」必须同时写清归属元素、支撑切面和支撑关系(如何支撑 + 缺失后果)。
|
||||
- 简单支点「结构」填「一句话」,「展开」填空数组。
|
||||
- 只有一句话不足以保留关键结构时才用其它结构类型。
|
||||
- 「缺失后果」不能只写「体验变差」,必须指出具体损失。
|
||||
- 「支撑点关系」只记对体验有实际影响的关系;没有则空数组。
|
||||
- 基准世界、推理过程、候选菜单和被淘汰支点不得写入产物。
|
||||
- 没有开放问题时填空数组,不要制造问题。
|
||||
- 不要输出完整角色卡、世界观百科、剧情大纲、叙事规则、变量表或演员规格。
|
||||
|
||||
本步完成的定义:
|
||||
- 核心体验的主要支撑已经被覆盖;
|
||||
- 每个支撑点都通过了「如何支撑/缺了会怎样」的检验;
|
||||
- 每个元素只保留了承担支撑作用的切面;
|
||||
- 所有支点在当前叙事空间中都可能成立,没有暗中引入未经确认的变造;
|
||||
- 剩余内容属于常识、具体实例或后续能力的展开范围。
|
||||
```
|
||||
|
||||
## checklist
|
||||
|
||||
```checklist
|
||||
- [ ] 「依据的核心体验」是否忠实来自依赖产物,而非本步新设计?
|
||||
- [ ] 每个支撑点是否都有:归属元素、支撑切面、如何支撑、缺失后果?
|
||||
- [ ] 简单支点是否避免了不必要的「展开」?
|
||||
- [ ] 是否误写成设定菜单、完整人物卡、起源史或叙事文风指南?
|
||||
- [ ] 是否暗中引入了用户未确认的变造?
|
||||
- [ ] 宁少勿多:主要支撑已覆盖后是否停住?
|
||||
- [ ] 「支撑点关系」是否只含对体验有实际影响的项(或空数组)?
|
||||
- [ ] 开放问题是否都真正影响本步结论(无则空)?
|
||||
```
|
||||
|
||||
## examples
|
||||
|
||||
```examples
|
||||
示例一:简单支撑点
|
||||
|
||||
核心体验:「秘密长期存在,但每次接近暴露时都令人紧张。」
|
||||
|
||||
合格:
|
||||
{
|
||||
"名称": "丈夫的认知过滤",
|
||||
"归属元素": "丈夫",
|
||||
"支撑切面": "面对威胁完美家庭信念的证据时,会无意识地优先采用无害解释",
|
||||
"切面形态": {
|
||||
"结构": "一句话",
|
||||
"概述": "他的信任不是单纯迟钝,而是一种维护既有信念的认知过滤。",
|
||||
"展开": []
|
||||
},
|
||||
"对应体验": ["秘密能够长期存在", "证据接近暴露时的紧张"],
|
||||
"如何支撑": "认知过滤让可疑证据能够出现而不立刻终结秘密,使暴露风险可以反复逼近。",
|
||||
"缺失后果": "如果他能稳定、直接地识别证据,秘密会迅速结束;如果完全没有证据出现,濒临暴露的紧张也会消失。"
|
||||
}
|
||||
只描述认知切面,不补职业、外貌、成长史。
|
||||
|
||||
示例二:具有内部结构的支撑点
|
||||
|
||||
核心体验:「每次获得力量都伴随自我逐渐陌生化的诱惑与恐惧。」
|
||||
|
||||
合格:
|
||||
{
|
||||
"名称": "力量与自我侵蚀的同步增长",
|
||||
"归属元素": "超常力量",
|
||||
"支撑切面": "力量的每次增长都会永久改变使用者的一项感知、欲望或判断方式",
|
||||
"切面形态": {
|
||||
"结构": "循环",
|
||||
"概述": "危机促使角色使用力量,力量解决危机并造成侵蚀,侵蚀又让下一次使用更容易发生。",
|
||||
"展开": [
|
||||
{ "部分": "诱因", "描述": "现实危机让使用力量成为最有效的解决方式。" },
|
||||
{ "部分": "收益", "描述": "力量立即兑现效果,使继续使用具有真实吸引力。" },
|
||||
{ "部分": "代价", "描述": "每次使用都会留下不可完全逆转的自我改变。" },
|
||||
{ "部分": "反馈", "描述": "改变后的角色更容易接受下一次使用,循环因此加深。" }
|
||||
]
|
||||
},
|
||||
"对应体验": ["力量带来的诱惑", "逐渐失去自我的恐惧"],
|
||||
"如何支撑": "收益与侵蚀来自同一次行动,角色不能只取其一,因此诱惑和恐惧能够持续共存。",
|
||||
"缺失后果": "如果侵蚀可以轻易撤销,恐惧会退化成短期成本;如果力量没有即时收益,诱惑则无法成立。"
|
||||
}
|
||||
不解释力量由谁创造、完整物理理论或全部侵蚀实例。
|
||||
|
||||
示例三:支撑点之间的关系
|
||||
|
||||
{
|
||||
"涉及": ["力量与自我侵蚀的同步增长", "身边人对变化的延迟识别"],
|
||||
"关系": "共同支撑并形成时间差",
|
||||
"体验作用": "侵蚀让角色真实改变,延迟识别则给变化留下累积空间;两者共同维持「尚能隐藏但终将无法隐藏」的过程感。"
|
||||
}
|
||||
|
||||
不合格:
|
||||
- 「可以选择诅咒、寄生物、人格分裂、外星科技或邪神污染…」→ 脱离体验的设定菜单。
|
||||
- 「丈夫四十二岁,是律师,外表温和,童年…」→ 把认知切面扩成完整人物设定。
|
||||
- 「为了营造压抑美感,叙述应采用近距离视角…」→ 叙事呈现,不是支撑设定。
|
||||
- 「这种力量源于三千年前的实验事故…」→ 为可直接成立的初始设定补不必要起源史。
|
||||
```
|
||||
56
skills/dialogue/world-simulator/modules/narrative/prompt.md
Normal file
56
skills/dialogue/world-simulator/modules/narrative/prompt.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# 叙事指南
|
||||
|
||||
> **状态:待完善** — 块格式见 `docs/world-simulator-modules.md`;范例见 `aesthetics-interaction`。
|
||||
|
||||
## meta
|
||||
|
||||
```meta
|
||||
name: 叙事指南
|
||||
id: narrative
|
||||
artifact: 设计.叙事指南
|
||||
declaration: 世界/助手态度与体验边界(不等于文风;契约已写清的态度勿重复问卷)
|
||||
when: 需要钉世界/助手态度口径,且美学纲领与交互范式未写清或需单独收口时
|
||||
when_not: 契约里已写清态度/禁忌,仅重复问卷
|
||||
boundary: 不等于文风;呈现与站位优先读 设计.美学纲领与交互范式
|
||||
```
|
||||
|
||||
## opening
|
||||
|
||||
```opening
|
||||
```
|
||||
|
||||
## task
|
||||
|
||||
```task
|
||||
钉清世界/助手态度与体验边界。写入 设计.叙事指南。
|
||||
依赖已验收的美学纲领与交互范式时先读再写;已写清的勿重复问卷。
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## principles
|
||||
|
||||
```principles
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## probe
|
||||
|
||||
```probe
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## output
|
||||
|
||||
```output
|
||||
{
|
||||
"态度": "…",
|
||||
"边界": ["…"],
|
||||
"焦点": "…"
|
||||
}
|
||||
```
|
||||
|
||||
## checklist
|
||||
|
||||
```checklist
|
||||
- [ ] 与美学纲领与交互范式不重复、不矛盾
|
||||
```
|
||||
51
skills/dialogue/world-simulator/modules/refine/prompt.md
Normal file
51
skills/dialogue/world-simulator/modules/refine/prompt.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# 细化终稿
|
||||
|
||||
> **状态:待完善** — 块格式见 `docs/world-simulator-modules.md`。
|
||||
|
||||
## meta
|
||||
|
||||
```meta
|
||||
name: 细化终稿
|
||||
id: refine
|
||||
artifact: 设计.worker集
|
||||
declaration: 钉死关键前提、表与副作用,收成可进游玩的规格
|
||||
when: 前面步骤已大致谈清,需要收成可进游玩的 Worker 集时
|
||||
boundary: 产出设计.worker集 JSON;进 play 由用户手动
|
||||
```
|
||||
|
||||
## task
|
||||
|
||||
```task
|
||||
钉死关键前提;表/副作用;收成可进游玩的设计.worker集 JSON。
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## principles
|
||||
|
||||
```principles
|
||||
只钉不能瞎发挥又对体验关键的东西。
|
||||
```
|
||||
|
||||
## probe
|
||||
|
||||
```probe
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## output
|
||||
|
||||
```output
|
||||
{
|
||||
"version": 1,
|
||||
"interaction": {},
|
||||
"workers": [],
|
||||
"resident_context": [],
|
||||
"tables": {}
|
||||
}
|
||||
```
|
||||
|
||||
## checklist
|
||||
|
||||
```checklist
|
||||
- [ ] 验收复述含站位、体验核心、worker/表、为何没有某件
|
||||
```
|
||||
@@ -0,0 +1,60 @@
|
||||
# 设计回复格式
|
||||
|
||||
> **状态:待完善** — 块格式见 `docs/world-simulator-modules.md`;范例见 `aesthetics-interaction`。
|
||||
|
||||
## meta
|
||||
|
||||
```meta
|
||||
name: 设计回复格式
|
||||
id: reply-format
|
||||
artifact: 设计.回复格式
|
||||
declaration: 钉单轮可见输出的结构(正文块、面板、拼接顺序),服务体验契约
|
||||
when: 需要钉单轮「用户看见什么、什么顺序」时(含多块拼接)
|
||||
when_not: 美学纲领与交互范式里呈现已足够且无多块结构时
|
||||
boundary: |
|
||||
本能力:单轮可见结构与拼接。
|
||||
设计状态栏:状态栏块的字段细则。
|
||||
美学纲领与交互范式:人称、系统扮演、体验边界——本步不重谈。
|
||||
```
|
||||
|
||||
## opening
|
||||
|
||||
```opening
|
||||
```
|
||||
|
||||
## task
|
||||
|
||||
```task
|
||||
钉清单轮可见回复格式(块、顺序、可选面板),写入 设计.回复格式。对齐已验收的呈现契约。
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## principles
|
||||
|
||||
```principles
|
||||
结构服务体验;块要少;与状态栏/终稿 tag 约定一致。
|
||||
```
|
||||
|
||||
## probe
|
||||
|
||||
```probe
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## output
|
||||
|
||||
```output
|
||||
{
|
||||
"块顺序": ["状态栏", "正文", "…"],
|
||||
"正文约定": "…",
|
||||
"可选面板": [],
|
||||
"终稿tag或拼装说明": "…"
|
||||
}
|
||||
```
|
||||
|
||||
## checklist
|
||||
|
||||
```checklist
|
||||
- [ ] 是否与美学纲领的呈现/轮转一致?
|
||||
- [ ] 状态栏块是否指向 设计.状态栏(若有)?
|
||||
```
|
||||
59
skills/dialogue/world-simulator/modules/status-bar/prompt.md
Normal file
59
skills/dialogue/world-simulator/modules/status-bar/prompt.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# 设计状态栏
|
||||
|
||||
> **状态:待完善** — 块格式见 `docs/world-simulator-modules.md`;范例见 `aesthetics-interaction`。
|
||||
|
||||
## meta
|
||||
|
||||
```meta
|
||||
name: 设计状态栏
|
||||
id: status-bar
|
||||
artifact: 设计.状态栏
|
||||
declaration: 钉用户可见状态栏:字段、刷新时机、与正文如何拼装
|
||||
when: 体验需要程序拼状态栏+正文,或用户要持续可见关键状态时
|
||||
when_not: 纯单段叙事、明确不要 HUD/状态条时
|
||||
boundary: |
|
||||
本能力:可见状态栏字段与刷新/拼装。
|
||||
变量设计与更新规则:背后变量如何变。
|
||||
设计回复格式:整轮输出结构(状态栏可为其一块)。
|
||||
```
|
||||
|
||||
## opening
|
||||
|
||||
```opening
|
||||
```
|
||||
|
||||
## task
|
||||
|
||||
```task
|
||||
钉清用户可见状态栏:字段、来源、刷新时机、与正文拼装方式。写入 设计.状态栏。
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## principles
|
||||
|
||||
```principles
|
||||
只展示影响决策或沉浸的字段;与变量设计对齐。
|
||||
```
|
||||
|
||||
## probe
|
||||
|
||||
```probe
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## output
|
||||
|
||||
```output
|
||||
{
|
||||
"字段": [{ "名": "…", "来源": "…", "可见条件": "…" }],
|
||||
"刷新": "每轮|事件|…",
|
||||
"拼装": "状态栏在正文前|后|旁|…"
|
||||
}
|
||||
```
|
||||
|
||||
## checklist
|
||||
|
||||
```checklist
|
||||
- [ ] 每个字段是否有变量/表来源?
|
||||
- [ ] 是否与回复格式拼装不冲突?
|
||||
```
|
||||
59
skills/dialogue/world-simulator/modules/topology/prompt.md
Normal file
59
skills/dialogue/world-simulator/modules/topology/prompt.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# 拓扑图谱
|
||||
|
||||
> **状态:待完善** — 块格式见 `docs/world-simulator-modules.md`;范例见 `aesthetics-interaction`。
|
||||
|
||||
## meta
|
||||
|
||||
```meta
|
||||
name: 拓扑图谱
|
||||
id: topology
|
||||
artifact: 设计.拓扑图谱
|
||||
declaration: 钉 worker/表/阶段的依赖、触发与数据流向;一张图说清谁读谁写
|
||||
when: 实现机制大致清楚,需要钉依赖边、触发边与数据流向时
|
||||
when_not: 尚未决定有哪些执行单元就先画复杂图
|
||||
boundary: |
|
||||
本能力:依赖/触发/读写流向。
|
||||
Worker 规格:单个单元契约。
|
||||
实现机制:总览「要哪些件」,本步钉件与件的边。
|
||||
```
|
||||
|
||||
## opening
|
||||
|
||||
```opening
|
||||
```
|
||||
|
||||
## task
|
||||
|
||||
```task
|
||||
钉清拓扑:单元、依赖、触发、读写流向。写入 设计.拓扑图谱。
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## principles
|
||||
|
||||
```principles
|
||||
一张图说清;环依赖与隐式读写必须显式写出或拆掉。
|
||||
```
|
||||
|
||||
## probe
|
||||
|
||||
```probe
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## output
|
||||
|
||||
```output
|
||||
{
|
||||
"节点": [{ "id": "…", "类型": "worker|表|阶段|…" }],
|
||||
"边": [{ "from": "…", "to": "…", "关系": "依赖|触发|读写|…" }],
|
||||
"说明": "…"
|
||||
}
|
||||
```
|
||||
|
||||
## checklist
|
||||
|
||||
```checklist
|
||||
- [ ] 每个关键读写是否有边?
|
||||
- [ ] 是否与实现机制列表一致?
|
||||
```
|
||||
@@ -0,0 +1,57 @@
|
||||
# 变量控制上下文
|
||||
|
||||
> **状态:待完善** — 块格式见 `docs/world-simulator-modules.md`;范例见 `aesthetics-interaction`。
|
||||
|
||||
## meta
|
||||
|
||||
```meta
|
||||
name: 变量控制上下文
|
||||
id: variable-context
|
||||
artifact: 设计.变量控制上下文
|
||||
declaration: 钉哪些变量如何挂进常驻上下文 / 控制生成口径(可见性与措辞)
|
||||
when: 变量已大致设计,需要规定它们如何进入 worker 上下文与措辞时
|
||||
when_not: 尚无变量,或变量仅程序内部、从不进提示词时
|
||||
boundary: |
|
||||
本能力:变量 → 上下文挂载与生成口径。
|
||||
变量设计与更新规则:字段与改值规则本身。
|
||||
```
|
||||
|
||||
## opening
|
||||
|
||||
```opening
|
||||
```
|
||||
|
||||
## task
|
||||
|
||||
```task
|
||||
钉清变量如何挂进常驻上下文、控制哪些生成口径。写入 设计.变量控制上下文。
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## principles
|
||||
|
||||
```principles
|
||||
只挂影响生成的变量;措辞服务体验,不泄露不该剧透的内部态(除非体验需要)。
|
||||
```
|
||||
|
||||
## probe
|
||||
|
||||
```probe
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## output
|
||||
|
||||
```output
|
||||
{
|
||||
"挂载": [{ "变量": "…", "挂到": "常驻|某worker|…", "措辞要点": "…" }],
|
||||
"禁止泄露": ["…"]
|
||||
}
|
||||
```
|
||||
|
||||
## checklist
|
||||
|
||||
```checklist
|
||||
- [ ] 挂载是否与变量设计名单一致?
|
||||
- [ ] 剧透边界是否与体验禁忌一致?
|
||||
```
|
||||
@@ -0,0 +1,65 @@
|
||||
# 变量设计与更新规则
|
||||
|
||||
> **状态:待完善** — 块格式见 `docs/world-simulator-modules.md`;范例见 `aesthetics-interaction`。
|
||||
|
||||
## meta
|
||||
|
||||
```meta
|
||||
name: 变量设计与更新规则
|
||||
id: variable-design
|
||||
artifact: 设计.变量设计与更新规则
|
||||
declaration: 钉变量字段、初值与更新时机/规则;与表副作用对齐
|
||||
when: 需要可追踪状态(进度、关系、资源等)且要写清谁何时改时
|
||||
when_not: 无状态纯对话、或状态仅散文描述从不程序化时
|
||||
boundary: |
|
||||
本能力:字段、初值、更新规则、与副作用。
|
||||
变量控制上下文:这些变量如何进入提示词口径。
|
||||
设计状态栏:哪些对用户可见。
|
||||
```
|
||||
|
||||
## opening
|
||||
|
||||
```opening
|
||||
```
|
||||
|
||||
## task
|
||||
|
||||
```task
|
||||
钉清变量字段、初值与更新规则,写入 设计.变量设计与更新规则。与表/副作用命名对齐。
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## principles
|
||||
|
||||
```principles
|
||||
字段要少;更新规则可检查;禁止隐式改值。
|
||||
```
|
||||
|
||||
## probe
|
||||
|
||||
```probe
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## output
|
||||
|
||||
```output
|
||||
{
|
||||
"变量": [
|
||||
{
|
||||
"名": "…",
|
||||
"类型": "…",
|
||||
"初值": "…",
|
||||
"更新": "谁、何时、怎么变"
|
||||
}
|
||||
],
|
||||
"副作用备注": "…"
|
||||
}
|
||||
```
|
||||
|
||||
## checklist
|
||||
|
||||
```checklist
|
||||
- [ ] 每个变量是否有明确更新者与时机?
|
||||
- [ ] 是否与拓扑/表设计一致?
|
||||
```
|
||||
@@ -0,0 +1,52 @@
|
||||
# Worker 规格
|
||||
|
||||
> **状态:待完善** — 块格式见 `docs/world-simulator-modules.md`。
|
||||
|
||||
## meta
|
||||
|
||||
```meta
|
||||
name: Worker 规格
|
||||
id: worker-spec
|
||||
artifact: 设计.worker规格
|
||||
declaration: 钉一个游玩期执行单元(职责、读写、挂载)
|
||||
when: 需要钉清某个执行单元的契约时
|
||||
boundary: 一次一个为宜;终稿合并进 设计.worker集
|
||||
```
|
||||
|
||||
## task
|
||||
|
||||
```task
|
||||
一次钉一个游玩期执行单元:中文名、ref、职责、读写、挂载。写入 设计.worker规格。
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## principles
|
||||
|
||||
```principles
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## probe
|
||||
|
||||
```probe
|
||||
(待作者细写)
|
||||
```
|
||||
|
||||
## output
|
||||
|
||||
```output
|
||||
{
|
||||
"name": "叙事转述",
|
||||
"ref": "narrator",
|
||||
"职责": "…",
|
||||
"读取": ["…"],
|
||||
"写入": ["…"],
|
||||
"为何需要": "…"
|
||||
}
|
||||
```
|
||||
|
||||
## checklist
|
||||
|
||||
```checklist
|
||||
- [ ] 删掉该 worker 会丢掉哪段体验?
|
||||
```
|
||||
@@ -0,0 +1,286 @@
|
||||
# 世界蓝图与人文地理
|
||||
|
||||
> 能力文档。程序只切割下方 **fence 块**(`meta` / `opening` / `task` / …);`##` 标题仅供人读。
|
||||
> 写法说明:`docs/world-simulator-modules.md`;泛用规范:`docs/briefs/capability-authoring-brief.md`。
|
||||
|
||||
## meta
|
||||
|
||||
```meta
|
||||
name: 世界蓝图与人文地理
|
||||
id: world-blueprint
|
||||
artifact: 设计.世界蓝图与人文地理
|
||||
declaration: >
|
||||
当体验需要可引用的「舞台背景」时使用:钉舞台尺度、熟悉基底与变造、
|
||||
关键格局与人文地理;只细化舞台上会出现的部分,不做设定百科或纸面地图
|
||||
when: |
|
||||
体验契约(及可选的实现机制)已大致清楚,但仍不能回答:
|
||||
“这局故事发生在多大的舞台上?”
|
||||
“背景世界以什么大家熟悉的基底成立,又变在哪里?”
|
||||
“下游开局与生成需要引用哪些格局、势力或人文条件?”
|
||||
when_not: |
|
||||
核心体验尚未钉清时,不用本能力代替「美学纲领与交互范式」发明想要什么感觉。
|
||||
只需识别体验支撑切面、不必展开环境与社会时,交给「实现机制」。
|
||||
需要可点名的人/地/物名片时,交给「具体实例」。
|
||||
需要内容如何持续生成或推进时,交给「生成规则」。
|
||||
用户只要轻设定回合、明确拒绝世界骨架时,不要排入。
|
||||
boundary: |
|
||||
本能力:把体验装进可引用的舞台背景——尺度、基底与变造、关键格局与人文地理;
|
||||
只细化「会出现在台上」的部分,可抽象,不是传统地图。
|
||||
美学纲领与交互范式:体验是什么、用户怎么参与;本步不重定体验目标。
|
||||
实现机制:体验靠哪些切面成立;本步把其中世界侧支点展开成骨架,不重做支撑检验。
|
||||
具体实例:可点名的人/地/物条目;本步不定逐条名片与生平。
|
||||
生成规则:内容如何生成/推进;本步不定触发与节奏规则。
|
||||
叙事指南:怎么写、世界态度;本步不定文风与镜头。
|
||||
```
|
||||
|
||||
## opening
|
||||
|
||||
```opening
|
||||
先用几句话框住「这局会出现的背景舞台」(想到什么写什么):
|
||||
|
||||
1. 舞台有多大?
|
||||
例:整个地球与大国博弈;一座城市;一所学校的几个系;
|
||||
也可以很抽象——「魔族与人族对峙割据的前线」,不必是真地图。
|
||||
|
||||
2. 熟悉的基底是什么?变在哪里?
|
||||
例:现代都市,但没有国别之分;现代都市,但中美关系两极化;
|
||||
西方魔幻常见格局,但魔法极度稀缺……也可以只说基底,变点稍后补。
|
||||
|
||||
3. 真正会反复出现的舞台区是哪里?
|
||||
一句话即可——只点「会上台」的部分,其余可留黑。
|
||||
```
|
||||
|
||||
## task
|
||||
|
||||
```task
|
||||
你正在执行剧本中的「世界蓝图与人文地理」步骤。本步产物写入「设计.世界蓝图与人文地理」。
|
||||
|
||||
这里的「蓝图」不是纸面地图,也不是设定集百科。它是**会出现在台上的背景内容**:空间可大可小、可具体可抽象,尺度完全由本局要展示的体验决定。
|
||||
|
||||
同一「现代都市」基底下:
|
||||
- 「体验作为韩国顶级财阀」→ 舞台往往是地球级势力、国家与资本网络;
|
||||
- 「体验普通大学生活」→ 舞台可能只是一座城,甚至一所学校的几个系。
|
||||
西方魔幻也可以只钉「魔族与人族对峙割据」这类格局,作为舞台布景,而不画完整大陆。
|
||||
|
||||
核心操作:在已确认的体验(及可选机制支点)之上,用「熟悉基底 + 变造」钉出可引用骨架,并只细化关键舞台区。
|
||||
|
||||
执行顺序:
|
||||
1. 读取依赖产物与用户表述,提取已确认的核心体验、变造暗示、以及实现机制里属于世界侧的支点。只忠实继承,不重做美学或机制。
|
||||
2. 判定舞台尺度:本局背景需要「装得下」多大范围——以体验会碰到的边界为准,不是以题材惯例为准。
|
||||
3. 选定文化/世界基底(大家有印象的原型),再写清变点;变点优先服务体验与关键舞台,不为完整而扩写。
|
||||
4. 只展开关键舞台区的格局、势力/社群、人文地理要点;舞台外用「刻意留黑 / 继承常识」交代即可。
|
||||
5. 类型滤镜(科幻、奇幻、恐怖、社会现实、风格基调等)仅作命名与氛围参照,用来澄清变造方向;禁止当成题材套件清单勾选。
|
||||
6. 输出符合 output 契约的 JSON,供用户验收。
|
||||
|
||||
工作姿态——「搭舞台,不写百科」:
|
||||
- 不问「这个世界完整长什么样」,问「玩家会反复看见/碰到什么背景」。
|
||||
- 能继承常识与基底默认的,不追问(已知现代 → 不问常规科技树,除非体验依赖变造)。
|
||||
- 可以把用户已表达但尚未整理的尺度与变造写成清晰骨架,请用户校正。
|
||||
- 两种舞台尺度会显著改变后续设定时,先辨明再展开,不要并行堆两套地图。
|
||||
|
||||
若依赖产物内部仍有含混或矛盾,不要越权重做上一步。只询问会改变尺度、基底变造或关键舞台区的差异;无法在本步解决的写入「开放问题」。
|
||||
|
||||
若程序已发出默认问题:用户首答在「用户.worker答复」,开场白在「创作.能力开场白」。禁止重复同一开场;在首答与依赖产物上补洞。
|
||||
|
||||
summary:`世界蓝图与人文地理 · …`(点题尺度 + 基底变造,非题材标签)。
|
||||
```
|
||||
|
||||
## principles
|
||||
|
||||
```principles
|
||||
1. 舞台服务于体验
|
||||
尺度、格局、人文条件都必须能回答:它让哪段已确认体验得以发生或被感觉到?
|
||||
不能从题材标签反推「这类故事通常有完整大陆/完整国别」。
|
||||
|
||||
2. 蓝图 ≠ 地图 ≠ 百科
|
||||
可以是抽象格局(对峙、割据、阶层天井、一条街的生态)。
|
||||
不要求接壤关系、比例尺、全史年表、全物种志。
|
||||
下游需要点名条目时交给「具体实例」;本步给骨架与引用钩子即可。
|
||||
|
||||
3. 变造基底,再按舞台细化
|
||||
优先路径:大家都有印象的基底世界 → 点明变在哪里 → 只细化关键舞台区。
|
||||
例:「现代都市,但没有国别之分」「现代都市,中美关系两极化」。
|
||||
细化粒度跟舞台走:财阀博弈可写到国家/财团层;校园日常写到系馆与社团层即可。
|
||||
|
||||
4. 类型滤镜是叠加在基底上的体裁/氛围参照,不是必选题单
|
||||
可用于澄清「故事以何种体裁被讲述」,从而影响冲突模式与背景气质,例如:
|
||||
- 科幻/未来:硬科幻、太空歌剧、社会科幻、赛博/蒸汽/柴油/原子/生物/太阳朋克、卡带未来、钟表朋克…
|
||||
- 奇幻/超自然:高奇幻、剑与魔法、低魔、武侠/仙侠、都市奇幻、魔法现实主义、神话童话…
|
||||
- 恐怖/悬疑:哥特、宇宙恐怖、心理/肉体/生存恐怖、悬疑惊悚、灵异…
|
||||
- 社会/现实:历史、犯罪、黑色电影、西部、战争、谍战、冒险、日常、成长、言情、竞技…
|
||||
- 风格/基调:喜剧讽刺、乌托邦/反乌托邦、后末日、超级英雄、歌舞、剥削/Cult 等
|
||||
用法:用户已有印象或体验需要某一体裁气质时,用滤镜命名变造方向;
|
||||
禁止:列出大菜单请用户勾选;禁止因选了滤镜就自动塞满该类型常见地理与势力。
|
||||
|
||||
5. 与实现机制的分工
|
||||
机制已钉「世界侧支撑切面」时:本步展开其环境/社会骨架,不重做「如何支撑/缺了会怎样」。
|
||||
机制未排入时:仍可从体验直接推断最小舞台;不要假装已经做过支撑检验。
|
||||
|
||||
6. 宁少勿多,舞台外留黑
|
||||
关键舞台区写清即可停止。舞台外、体验碰不到的大洲/朝代/组织,默认不写。
|
||||
「未展开范围」写明刻意省略,避免下游把留黑当成缺口去补百科。
|
||||
|
||||
7. 正推,禁止否定式路由
|
||||
写「需要跨国资本压迫感 → 舞台升到国家/财团层」,
|
||||
不写「因为是校园文所以不要国际政治」(除非用户明确不要)。
|
||||
|
||||
8. 继承优先于发明
|
||||
基底已蕴含的常识默认成立;只在变点与体验依赖处显式改写。
|
||||
不要为显得严谨而补起源史、伪科学或全套神话谱系,除非它们本身就是舞台上会出现的背景。
|
||||
```
|
||||
|
||||
## probe
|
||||
|
||||
```probe
|
||||
只追问会改变舞台尺度、基底变造、关键舞台区或人文格局的缺口。每轮 1~2 点。依赖产物或用户已答清的禁止重问。
|
||||
|
||||
【默认问题已覆盖】舞台大小、基底+变点、会反复出现的舞台区。首答后按缺口补,勿重问已答清的。
|
||||
|
||||
优先方式:
|
||||
|
||||
1. 尺度对照
|
||||
用同一基底的两种舞台问差异。
|
||||
例:「同是现代都市——更接近『全国财阀与国家势力都在台上』,还是『基本不出这座城/这所学校』?」
|
||||
|
||||
2. 变造落句
|
||||
把模糊变点收成可引用短句,请用户改一个词即可采用。
|
||||
例:「是否可以写成:现代都市基底,但国家边界弱化到几乎无国别,冲突主要在公司与城市场景里发生?」
|
||||
|
||||
3. 关键舞台区边界
|
||||
问「会反复上台」的部分,而不是「世界还有什么」。
|
||||
例:「真正会反复出现的,是总部—宴会—监管听证这几类场合,还是还要经常切到海外子公司现场?」
|
||||
|
||||
4. 滤镜澄清(可选)
|
||||
仅当体裁气质会改变背景冲突模式时才问;给 2~3 个完整句,不给类型树勾选。
|
||||
例:「背景更偏赛博朋克式的巨企夜城,还是偏社会科幻式的制度压迫(科技外表不重要)?」
|
||||
|
||||
5. 机制支点落地
|
||||
若上游机制点了世界侧切面,问它在舞台上长什么样。
|
||||
例:「『信息被巨企垄断』在台上主要体现为哪几个可见势力/场所,而不是再解释一遍为何垄断支撑体验。」
|
||||
|
||||
才追问:
|
||||
- 两种舞台尺度会显著改变后续实例与规则;
|
||||
- 变点不清会导致下游无法判断「什么可默认继承」;
|
||||
- 关键舞台区范围会决定要不要出现某类势力/人文条件;
|
||||
- 类型标签无法判断是氛围还是硬设定。
|
||||
|
||||
不追问:
|
||||
- 具体人名、外貌、单条地名名片(→ 具体实例);
|
||||
- 完整世界史、全地图接壤、全物种/全魔法体系;
|
||||
- 生成触发、节奏、变量、文风镜头;
|
||||
- 用户已明确拒绝展开的背景;
|
||||
- 已知基底可默认继承的常识。
|
||||
```
|
||||
|
||||
## output
|
||||
|
||||
```output
|
||||
最终只输出一个合法 JSON 对象,不加代码块外说明,不使用注释,不夹带未定义的英文 id。
|
||||
|
||||
{
|
||||
"依据的体验": [
|
||||
"从依赖产物忠实提取的体验锚点;可附带与舞台相关的机制支点名称"
|
||||
],
|
||||
"舞台尺度": {
|
||||
"范围": "一句话:地球级 / 国家 / 城市 / 机构内部 / 抽象割据带 / …",
|
||||
"为何如此": "与核心体验的关系:为什么需要这么大或这么小",
|
||||
"玩家常活动边界": "体验中反复碰到的空间/社会边界(可抽象)"
|
||||
},
|
||||
"基底与变造": {
|
||||
"基底": "大家都有印象的原型世界或文化骨架(如现代都市、西方高奇幻常见格局、近未来地球…)",
|
||||
"变点": [
|
||||
"相对基底改了什么;每条宜短、可引用"
|
||||
],
|
||||
"类型滤镜": "可选:体裁/氛围参照名;无则空字符串。说明其如何影响背景气质,勿展开类型百科",
|
||||
"默认可继承": "基底下可默认成立、本步不写的常识范围(一句话)"
|
||||
},
|
||||
"关键舞台区": [
|
||||
{
|
||||
"名称": "会反复上台的区/层/格局名",
|
||||
"是什么": "空间、社会层或抽象舞台的最短描述",
|
||||
"为何需要": "服务哪段体验或哪个机制支点",
|
||||
"格局要点": [
|
||||
"势力、场所类型、流通关系、可见冲突等——只写台上用得上的"
|
||||
]
|
||||
}
|
||||
],
|
||||
"势力与社群": [
|
||||
{
|
||||
"名称": "…",
|
||||
"性质": "国家/财团/院系/帮派/种族阵营/阶层…",
|
||||
"在舞台上的作用": "玩家会如何感到它的存在",
|
||||
"细化程度": "点到为止|需要下游实例化|本步已够用"
|
||||
}
|
||||
],
|
||||
"人文地理要点": [
|
||||
{
|
||||
"要点": "习俗、阶层、话语、禁忌、日常节奏、资源分布等可引用条件",
|
||||
"服务体验": "它让什么感觉成立",
|
||||
"作用范围": "仅关键舞台区|全局默认"
|
||||
}
|
||||
],
|
||||
"未展开范围": [
|
||||
"刻意留黑或仅继承常识、禁止下游当缺口补百科的部分"
|
||||
],
|
||||
"开放问题": [
|
||||
{
|
||||
"问题": "尚不能可靠确定、且会影响尺度/变造/关键舞台的问题",
|
||||
"影响": "不确定性会改变什么",
|
||||
"当前暂定": "可撤销的暂定理解;没有则空字符串"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
填写规则:
|
||||
- 「依据的体验」只建追溯,不得扩写成新的美学纲领或机制表。
|
||||
- 「舞台尺度」必须能用体验解释;禁止「这类题材一般都这样」。
|
||||
- 「变点」为空数组仅当用户明确只要纯基底且无变造;否则至少标「未定」于开放问题。
|
||||
- 「关键舞台区」只含会上台的部分;不要为对称补齐未上场区域。
|
||||
- 「势力与社群」「人文地理要点」无则空数组;有则每条写清舞台作用,禁止百科句。
|
||||
- 「类型滤镜」不得连带输出该类型常见元素清单。
|
||||
- 「未展开范围」建议填写,防止下游过度补全。
|
||||
- 不要输出具体实例名片、生成规则、变量表、叙事文风或演员规格。
|
||||
|
||||
本步完成的定义:
|
||||
- 舞台尺度已能被体验解释;
|
||||
- 基底与变造可被下游引用(或明确未定);
|
||||
- 关键舞台区已覆盖玩家会反复碰到的背景;
|
||||
- 舞台外留黑已交代;
|
||||
- 剩余点名条目属于「具体实例」,生成方式属于「生成规则」。
|
||||
```
|
||||
|
||||
## checklist
|
||||
|
||||
```checklist
|
||||
- [ ] 尺度是否由体验决定(而非题材默认地图)?
|
||||
- [ ] 是否采用「基底 + 变造」,且变点可引用?
|
||||
- [ ] 是否只细化关键舞台区,舞台外有「未展开范围」?
|
||||
- [ ] 有无写成设定百科、接壤全图或完整世界史?
|
||||
- [ ] 类型滤镜若出现,是否只作氛围/体裁参照而非套件勾选?
|
||||
- [ ] 与美学/机制是否矛盾或越权重做?
|
||||
- [ ] 是否误写成具体实例名单或生成规则?
|
||||
- [ ] 开放问题是否都真正影响本步结论(无则空)?
|
||||
```
|
||||
|
||||
## examples
|
||||
|
||||
```examples
|
||||
好 · 尺度随体验收缩:
|
||||
- 体验「普通大学生活」→ 舞台=一所大学的几个系与周边街区;基底=现代都市校园;变点可无或很轻;未展开=国家政治与国际局势。
|
||||
- 体验「作为韩国顶级财阀」→ 同属现代都市基底,但舞台升到财阀—国家—跨国资本;关键舞台区=董事会、政商宴、舆论与监管场合。
|
||||
|
||||
好 · 抽象舞台:
|
||||
- 「魔族与人族对峙割据」作为关键舞台区名称;格局要点写前线、禁忌地带、两边话语;不画大陆全图。
|
||||
|
||||
好 · 变造落句:
|
||||
- 基底「现代都市」;变点「几乎无国别之分,冲突在城市与公司层发生」;或「中美关系两极化渗入日常消费与舆论」。
|
||||
|
||||
好 · 滤镜作参照:
|
||||
- 类型滤镜=「赛博朋克气质」;说明=巨企与霓虹夜城压迫感;不自动追加义体市场、全部帮派地图。
|
||||
|
||||
坏:
|
||||
- 「先写完整七大国、货币史、三万年神话…」→ 百科,无舞台优先。
|
||||
- 「选一个类型:硬科幻/太空歌剧/赛博朋克/…(全表)」→ 题材套件菜单。
|
||||
- 「因为是日常所以不要任何社会结构」→ 否定式路由;日常也可以有系馆权力与阶层天井。
|
||||
- 「地点1:XX咖啡馆,店主叫…」→ 具体实例,不是蓝图骨架。
|
||||
```
|
||||
123
skills/dialogue/world-simulator/orchestrator.md
Normal file
123
skills/dialogue/world-simulator/orchestrator.md
Normal file
@@ -0,0 +1,123 @@
|
||||
---
|
||||
name: world-simulator
|
||||
description: >-
|
||||
默认包:用户选导演 → 从能力编排剧本 → 逐步执行;
|
||||
验收后手动进游玩;play 按声明调度演员。
|
||||
category: dialogue
|
||||
bookKind: dialogue
|
||||
version: 0.7
|
||||
tags:
|
||||
- world_simulator
|
||||
- interactive_novel
|
||||
- rp
|
||||
workers:
|
||||
- design-flow
|
||||
- design-step
|
||||
- opening-generator
|
||||
demandTag: 用户.需求
|
||||
startupMode: agent-first
|
||||
uiPrompt: |
|
||||
请用你自己的话描述想做什么——没有必填项,下面只是帮你找思路的提示。
|
||||
|
||||
【你扮演什么】(可对照,也可不按表)
|
||||
· 单角代入:我就是一个固定角色
|
||||
· 代理操控:我有角色,但常发 () 指令指挥
|
||||
· 旁观/实验:我不扮演谁,看或记录推演
|
||||
· 写手/统筹:我定方向,要成稿或助手式分段(长文 / 爽文也走这条)
|
||||
· 多角切换:我轮流扮演不同身份
|
||||
|
||||
【系统要给你什么】(输出与交互,不是文风问卷)
|
||||
· 回合对话:你一句,系统回一段可见结果
|
||||
· 助手分段:先大纲/细纲,你再填表或改设定,再按章/段写正文
|
||||
· 只要事实摘要 / 要可读叙事 / 要状态表…
|
||||
|
||||
【输入约定】可选用括号区分:
|
||||
· () 圆括号:用户指令/要求,不可写成角色对白
|
||||
· "" 双引号:角色在世界内说的话
|
||||
· 【】方括号:角色在世界内的行动
|
||||
未加标记时默认可视为世界内输入;语义明显是元话语按指令处理。
|
||||
|
||||
【核心体验】若愿意可带一句:你最想反复感到的是什么——没有也没关系,我会从描述里察觉。
|
||||
|
||||
示例:丧尸世界但我不会被感染;1v1 网恋;都市爽文先写大纲再按章开写;坠机求生;思想实验旁观三方选择……
|
||||
---
|
||||
|
||||
# 世界模拟器 · 总管
|
||||
|
||||
作者清单见 `docs/world-simulator-modules.md`。
|
||||
|
||||
你是 **总管**:负责 design / play 的 **调度**,不直接写正文。
|
||||
禁止默认把一切做成「世界模拟」;按用户意图正推最小能力组合。
|
||||
|
||||
## 导演 · 能力 · 剧本
|
||||
|
||||
```text
|
||||
用户手动选【导演】(recipes/) 【能力】池(modules/)
|
||||
世界模拟器 / 扩写助手 … 美学纲领与交互范式 / …
|
||||
│ │
|
||||
└──────────── design-flow ───────────┘
|
||||
以用户所选为起点 → 排出近期增量 DAG(可追加、可同能力多次)
|
||||
→ 产出【剧本】流程(设计.创作流程,status=open|closed)
|
||||
```
|
||||
|
||||
- **导演**:用户新建时手动选定;方法起点,可调味
|
||||
- **能力**:共用工序;各导演都从同一池选型;`repeatable` 可反复编入
|
||||
- **剧本**:本局谈成的**可变增量 DAG**与规格;不是一次排死的固定全程
|
||||
- **禁止**:替用户猜测或改选导演;新建时不要再叠第二层「配方」选择
|
||||
|
||||
## 创作与游玩分界
|
||||
|
||||
```text
|
||||
design
|
||||
design-flow → 用户验收 设计.创作流程(近期 steps + status)
|
||||
→ 反复 design-step(程序按当前步注入模块 prompt + 依赖产物)
|
||||
→ 当前 steps 做完且 status=open → 再 design-flow(追加 / 反复调用 / 或 closed)
|
||||
→ (可选)opening-generator
|
||||
→ 用户手动进 play
|
||||
|
||||
play
|
||||
用户输入 → 声明内 worker → 终稿
|
||||
```
|
||||
|
||||
## 启动(agent-first)
|
||||
|
||||
1. 首屏 `uiPrompt`
|
||||
2. 用户首句 → `用户.需求` → 总管 tool loop
|
||||
3. 尚无已验收流程 → `run_worker(design-flow)`
|
||||
4. 流程已有未完成步骤 → `run_worker(design-step)`
|
||||
5. 当前步骤都验收完但 `status=open` → 再 `design-flow`(扩步或收口)
|
||||
6. `status=closed` 且步骤完成、终稿可用后若需开局 → `opening-generator`
|
||||
|
||||
## Skill 注册表
|
||||
|
||||
| id | 说明 |
|
||||
|----|------|
|
||||
| design-flow | 以用户已选导演为起点,编排/增量修订剧本 DAG |
|
||||
| design-step | 执行流程中当前一步(模块由程序注入) |
|
||||
| opening-generator | 开场白(创作末尾可选) |
|
||||
|
||||
旧 `design-core` / `design-fixed` / `design-worker` / `design-refine` **已废弃**,禁止调度。
|
||||
|
||||
## 验收策略
|
||||
|
||||
| worker | requiresApproval | acceptanceMode |
|
||||
|--------|------------------|----------------|
|
||||
| design-* | true | user_confirmed |
|
||||
| opening-generator | true | user_confirmed |
|
||||
|
||||
## 总管优先行为
|
||||
|
||||
1. 有需求、尚无已验收 `设计.创作流程` → `design-flow`
|
||||
2. 流程已有、存在未验收步骤 → `design-step`
|
||||
3. 已列步骤全验收但 `status=open` → `design-flow`(追加反复步或设 closed)
|
||||
4. `waiting_user(review_artifact)` → 引导验收
|
||||
5. reject → 收修订 → 重跑同一 worker(含修订流程 = 再调味)
|
||||
6. 终稿(含 `设计.worker集`)已 accept 且需开局 → `opening-generator`
|
||||
|
||||
## 禁用行为
|
||||
|
||||
- 调度已废弃的 design-core / design-fixed / design-worker / design-refine
|
||||
- 跳过 design-flow 直接 design-step(无流程时)
|
||||
- 一次 design-flow 排死全程固定长链(应增量)
|
||||
- 调度声明未列出的 play ref
|
||||
- Agent 挑选模型
|
||||
16
skills/dialogue/world-simulator/recipes/catalog.yaml
Normal file
16
skills/dialogue/world-simulator/recipes/catalog.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
# 导演选项目录(用户在新建作品时手动选择;对用户称「导演」)
|
||||
# id = recipes/{id}/ 文件夹
|
||||
# name = 固定中文名(下拉展示)
|
||||
# declaration = 给人看的短说明
|
||||
# 内部仍叫 recipe;勿对用户再说「配方」作第二层选项
|
||||
# 步骤 name 必须 ∈ modules/catalog.yaml(【能力】)
|
||||
# 选定后写入黑板 tag 创作.选用配方
|
||||
|
||||
recipes:
|
||||
- id: world-simulator
|
||||
name: 世界模拟器
|
||||
declaration: 回合互动、世界推进、角色扮演类体验的初始编排参考
|
||||
|
||||
- id: expand-assistant
|
||||
name: 扩写助手
|
||||
declaration: 大纲/分段写作、写手统筹、成稿向助手类体验的初始编排参考
|
||||
@@ -0,0 +1,8 @@
|
||||
# 状态:待完善 — 作者细写「何时用 / 怎么调 / 建议近期 steps」
|
||||
# 本文件是增量起点:可按现场追加;勿一次排死全程。
|
||||
# steps[].name 必须来自 modules/catalog.yaml(共用组件池)。
|
||||
|
||||
when: 大纲/分段扩写、写手统筹、先纲后章、成稿向助手类体验
|
||||
hint: 初始参考。按用户意图增量追加步骤与依赖,勿机械照搬整份 steps。
|
||||
brief: (占位)扩写助手类体验
|
||||
steps: []
|
||||
@@ -0,0 +1,16 @@
|
||||
# 世界模拟器 · 初始导演
|
||||
# steps = 近期 horizon(增量起点),不是固定全程 DAG。
|
||||
# steps[].name 必须来自 modules/catalog.yaml;可反复追加 repeatable 能力。
|
||||
|
||||
when: 回合互动、世界推进、角色扮演、沉浸推演类体验
|
||||
hint: >-
|
||||
先只排「美学纲领与交互范式」;谈完后再增量追加。
|
||||
「生成规则」「具体实例」标了 repeatable,可多次编入(不同 step.id)。
|
||||
其它按需:世界蓝图与人文地理 / 叙事指南 / 实现机制 /
|
||||
拓扑图谱 / 变量* / 状态栏 / 回复格式 / Worker 规格 / 细化终稿。
|
||||
勿一次排完全程;收成前再 closed。
|
||||
brief: 世界模拟类:先定体验与轮转,再增量落到可运行规格
|
||||
steps:
|
||||
- id: 美学纲领与交互范式
|
||||
name: 美学纲领与交互范式
|
||||
depends_on: []
|
||||
32
skills/dialogue/world-simulator/worker-templates/README.md
Normal file
32
skills/dialogue/world-simulator/worker-templates/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Worker 可选模板(ref 默认契约)
|
||||
|
||||
## 定位
|
||||
|
||||
**不是** play 时加载的 `workers/*/SKILL.md`。
|
||||
**是** 谈【剧本】、写 `设计.worker集` 时合并的 **默认建议**:context、outputs、职责摘要。
|
||||
|
||||
| 层 | 权威来源 |
|
||||
|----|----------|
|
||||
| 本局 play 怎么跑 | 用户 accept 的 **`设计.worker集`** |
|
||||
| 可选模板 | 本目录 `{ref}.yaml` — 未写全 `context`/`outputs` 时合并 |
|
||||
|
||||
Runtime 执行时读 Worker 集条目,不直接读本目录。
|
||||
|
||||
## 文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `world-simulator.yaml` | 世界推进 / 裁决 |
|
||||
| `narrator.yaml` | 转述 / 展示 |
|
||||
| `role-decide.yaml` | 单角色决策 |
|
||||
| `round-present.yaml` | 结构化回合陈述 |
|
||||
| `opening-generator.yaml` | 开局生成器(创作末尾) |
|
||||
| `outline.yaml` | 大纲 / 细纲 |
|
||||
| `chapter-writer.yaml` | 章节正文 |
|
||||
|
||||
## 用法
|
||||
|
||||
1. 按用户意图选 `ref`
|
||||
2. 读 `{ref}.yaml` 填默认字段
|
||||
3. 用户特殊需求覆盖后写入 Worker 集
|
||||
4. accept 后声明即实例规格
|
||||
@@ -0,0 +1,25 @@
|
||||
id: chapter-writer
|
||||
label: 章节正文
|
||||
role: drafting
|
||||
duty: >
|
||||
按大纲当前节点与用户指令写一章/一段可读正文;写入约定正文 tag。
|
||||
不擅自改大纲结构;用户重 roll 本段时只重写本段。
|
||||
when: 大纲已有、用户要求写下一章/段或重 roll 当前段
|
||||
suggested_context:
|
||||
static:
|
||||
- 设计.worker集
|
||||
- 大纲.当前
|
||||
dynamic:
|
||||
- 正文.已完成
|
||||
- 用户.最新输入
|
||||
- 变量.当前
|
||||
suggested_outputs:
|
||||
- 正文.当前段
|
||||
- 正文.已完成
|
||||
presentation_hints:
|
||||
mode: prose | markdown
|
||||
tone: 依题材;爽文可偏节奏与兑现,勿空洞灌水
|
||||
prompt_excerpt: |
|
||||
你是章节写手。只写本轮要求的一段/一章,对照大纲节点兑现承诺。
|
||||
输出进 正文.当前段;若需归档到 正文.已完成 按声明 outputs 执行。
|
||||
不要重写整本;不要发明与大纲冲突的主线转折,除非用户本轮明确要求。
|
||||
@@ -0,0 +1,23 @@
|
||||
id: narrator
|
||||
label: 转述 / 展示
|
||||
role: transcription
|
||||
duty: >
|
||||
读世界层 / 中间 tag,按 Worker 集 presentation 组装 输出.用户展示;
|
||||
只改表达,不改事实(除非用户要求摘要压缩)。
|
||||
when: 核心层产出齐、尚无本轮 输出.用户展示
|
||||
suggested_context:
|
||||
static:
|
||||
- 设计.worker集
|
||||
dynamic:
|
||||
- 运行.事件流
|
||||
- 运行.本轮.裁决
|
||||
- 变量.当前
|
||||
- 用户.最新输入
|
||||
suggested_outputs:
|
||||
- 输出.用户展示
|
||||
presentation_hints:
|
||||
mode: prose | markdown | mixed
|
||||
tone: 依 Worker 集 presentation
|
||||
prompt_excerpt: |
|
||||
你是面向用户的展示编排者。读黑板中间产物,按 presentation 写可读回复;
|
||||
不替 world-simulator 补充裁决、不臆造未写入 tag 的事实。
|
||||
@@ -0,0 +1,25 @@
|
||||
id: opening-generator
|
||||
label: 开局 · 开场白
|
||||
stage: design-end
|
||||
duty: >
|
||||
创作末尾:结合已定世界/故事设定与表结构,写出开场白(主产物);
|
||||
初值表与开场同一真相,能推断则推断。不是填表工具。
|
||||
when: Worker 集 accept 后、进 play 前;design_end 或 workers 声明启用时
|
||||
suggested_context:
|
||||
static:
|
||||
- 设计.worker集
|
||||
- 用户.需求
|
||||
- 上下文.定稿摘要
|
||||
dynamic:
|
||||
- 用户.最新输入
|
||||
- 用户.worker答复
|
||||
- 运行.初始变量
|
||||
- 输出.开场白
|
||||
suggested_outputs:
|
||||
- 输出.开场白
|
||||
- 运行.初始变量
|
||||
- 变量.当前
|
||||
prompt_excerpt: |
|
||||
主产物是开场白:扎根已有世界与故事,把玩家放进可行动的第一拍。
|
||||
表是配套:与开场事实一致;普通大学生等可推出的别问。
|
||||
禁止先问卷填表再糊开场;禁止开场与表打架。
|
||||
@@ -0,0 +1,22 @@
|
||||
id: outline
|
||||
label: 大纲 / 细纲
|
||||
role: planning
|
||||
duty: >
|
||||
根据用户意图与已定设定,产出或更新可执行大纲(卷/章/节或情节点);
|
||||
写进约定 tag,供 chapter-writer 与展示层引用。不直接写长章正文。
|
||||
when: 写手/分段模式下尚无可用大纲,或用户要求改大纲
|
||||
suggested_context:
|
||||
static:
|
||||
- 设计.worker集
|
||||
- 用户.需求
|
||||
dynamic:
|
||||
- 大纲.当前
|
||||
- 用户.最新输入
|
||||
suggested_outputs:
|
||||
- 大纲.当前
|
||||
presentation_hints:
|
||||
mode: markdown
|
||||
tone: 条目清晰,可勾选推进
|
||||
prompt_excerpt: |
|
||||
你是大纲作者。按用户爽点与篇幅产出可执行大纲(章标题 + 一句话节拍即可)。
|
||||
不要写成长篇正文;修改时保留用户已锁定的章,只改其点名部分。
|
||||
@@ -0,0 +1,21 @@
|
||||
id: role-decide
|
||||
label: 角色决策
|
||||
role: auxiliary
|
||||
duty: >
|
||||
仅代表 世界.当前角色.id 所指角色;产出 .思考(仅用户)与 .行动(世界层可见)。
|
||||
信息隔绝:不读其他角色 .思考 / .行动。
|
||||
when: Worker 集启用且本轮需该角色独立决策;须 workerContext.roleId
|
||||
suggested_context:
|
||||
static:
|
||||
- 设计.worker集
|
||||
dynamic:
|
||||
- 世界.当前角色.id
|
||||
- 运行.事件流
|
||||
- 角色.*.可见信息
|
||||
- 场景.公开叙述
|
||||
suggested_outputs:
|
||||
- 角色.*.思考
|
||||
- 角色.*.行动
|
||||
prompt_excerpt: |
|
||||
两层输出:思考(用户专阅)与行动(含说话,世界层可读)。
|
||||
禁止读博弈规则全文或其他角色思考 tag;仅 L3 可见信息。
|
||||
@@ -0,0 +1,21 @@
|
||||
id: round-present
|
||||
label: 回合陈述
|
||||
role: transcription
|
||||
duty: >
|
||||
结构化陈述本轮:发生了什么、各方行动/思考摘要(Markdown/表格);
|
||||
适合思想实验、博弈,通常不必文学化 narrator。
|
||||
when: 多角色模拟一轮结束,需 输出.用户展示
|
||||
suggested_context:
|
||||
static:
|
||||
- 设计.worker集
|
||||
dynamic:
|
||||
- 输出.回合摘要
|
||||
- 场景.公开叙述
|
||||
- 角色.*.思考
|
||||
- 角色.*.行动
|
||||
- 运行.事件流
|
||||
suggested_outputs:
|
||||
- 输出.用户展示
|
||||
prompt_excerpt: |
|
||||
按参数决定是否展示思考层;公开局面与思考分节,不混写。
|
||||
摘录自旧「回合展示」worker 写法,写入 Worker 集时按实例改 tag 名。
|
||||
@@ -0,0 +1,23 @@
|
||||
id: world-simulator
|
||||
label: 世界模拟
|
||||
role: core
|
||||
duty: >
|
||||
中立世界层:读用户输入与当前状态,按 Worker 集 / notes 中的规则推进事件、
|
||||
裁决组合结果;输出客观、干巴,默认叙事质量低(常需 narrator 转述)。
|
||||
when: 每轮用户输入后,或 tag_flow 要求产出本轮实质内容时
|
||||
suggested_context:
|
||||
static:
|
||||
- 设计.worker集
|
||||
- 用户.需求
|
||||
dynamic:
|
||||
- 用户.最新输入
|
||||
- 运行.事件流
|
||||
- 变量.当前
|
||||
- 运行.初始变量
|
||||
suggested_outputs:
|
||||
- 运行.本轮.裁决
|
||||
- 运行.事件流
|
||||
prompt_excerpt: |
|
||||
你是中立世界层,不是文学作者。只对规则与已提交输入作机械反应;
|
||||
不写角色内心、不做剧情化推演。输出可复核的客观事实与状态变化。
|
||||
叙事质量 intentionally 低——转述交给 narrator(若 Worker 集启用)。
|
||||
104
skills/dialogue/world-simulator/workers/design-flow/SKILL.md
Normal file
104
skills/dialogue/world-simulator/workers/design-flow/SKILL.md
Normal file
@@ -0,0 +1,104 @@
|
||||
---
|
||||
id: design-flow
|
||||
skill: world-simulator
|
||||
name: 创作 · 流程编排
|
||||
description: >-
|
||||
【编排】以用户已选导演为起点,从能力池排出近期工序与依赖,
|
||||
产出/修订可变增量 DAG(设计.创作流程)。本步不写美学/机制正文,不写 Worker 集。
|
||||
version: 1
|
||||
stage: design
|
||||
inputTags:
|
||||
- "用户.需求"
|
||||
- "book.brief"
|
||||
- "用户.最新输入"
|
||||
- "用户.worker答复"
|
||||
- "用户.修订说明"
|
||||
- "设计.创作流程"
|
||||
- "创作.选用配方"
|
||||
- "创作.已验收单位"
|
||||
outputTags:
|
||||
- "设计.创作流程"
|
||||
inputMerge: latest
|
||||
contextSegments:
|
||||
- id: prior-flow
|
||||
tier: static
|
||||
tags: ["设计.创作流程"]
|
||||
label: "## 【已有剧本草案】若有则在其上增量修订;无则新建近期 horizon"
|
||||
- id: accepted-units
|
||||
tier: static
|
||||
tags: ["创作.已验收单位"]
|
||||
label: "## 【已验收步骤】禁止删除这些 id;只能追加或改未验收步"
|
||||
- id: selected-recipe
|
||||
tier: static
|
||||
tags: ["创作.选用配方"]
|
||||
label: "## 【用户已选导演】只读引用"
|
||||
- id: user-demand
|
||||
tier: dynamic
|
||||
tags: ["用户.需求", "book.brief", "用户.最新输入", "用户.worker答复", "用户.修订说明"]
|
||||
label: "## 用户表述"
|
||||
---
|
||||
|
||||
# 创作 · 流程编排
|
||||
|
||||
你只做一件事:根据用户表述,编排或**增量修订**一份剧本流程(可变 DAG)。
|
||||
|
||||
上下文里会有:
|
||||
|
||||
1. **【用户已选导演】**:用户在界面手动选定——方法起点,**不是**锁死流水线;**禁止**替用户改选其它导演
|
||||
2. **【能力 · 可选工序】**:固定中文名 + 短声明——步骤只能从这里选;标〔可反复〕的可多次编入
|
||||
3. **【已有剧本草案】/【已验收步骤】**:若有,在其上追加或改未验收步,**不要**推倒重来
|
||||
|
||||
## 增量 DAG(核心)
|
||||
|
||||
**禁止**一次排完全程固定长链。每次只排出**近期要做**的步骤(通常 1~4 步),`status` 默认 `"open"`。
|
||||
|
||||
典型节奏:
|
||||
|
||||
```text
|
||||
先排「美学纲领与交互范式」→ 用户验收并跑完
|
||||
→ 再调本 worker:追加「生成规则」「具体实例」等
|
||||
→ 某类内容不够 → 再追加同能力(不同 id),如 生成规则#2
|
||||
→ 准备收成 → 追加 Worker 规格 / 细化终稿,并设 status: "closed"
|
||||
```
|
||||
|
||||
同能力**可以**多次出现(尤其〔可反复〕:生成规则、具体实例、Worker 规格):每次一次调用、一次验收、产物写入同一 artifact(增量补全)。
|
||||
|
||||
## 产出(唯一)
|
||||
|
||||
写入 tag `设计.创作流程`,必须是 JSON 对象,形状:
|
||||
|
||||
```json
|
||||
{
|
||||
"brief": "一句话复述用户要的体验(可选)",
|
||||
"status": "open",
|
||||
"steps": [
|
||||
{ "id": "美学纲领与交互范式", "name": "美学纲领与交互范式", "depends_on": [] },
|
||||
{ "id": "生成规则", "name": "生成规则", "depends_on": ["美学纲领与交互范式"] },
|
||||
{ "id": "生成规则#2", "name": "生成规则", "depends_on": ["生成规则"] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
1. `steps` 数组顺序 = **建议执行顺序**(依赖须指向更前的步骤)
|
||||
2. `name` = 能力池里的固定中文名(可重复)
|
||||
3. `id` = 本局步骤唯一键;同 name 多次时必须不同(如 `生成规则`、`生成规则#2`)
|
||||
4. `depends_on` = 其它步骤的 **id**(若某 name 在本流程唯一,也可写该 name)
|
||||
5. `status`:`"open"` = 还可能追加;`"closed"` = 不再扩步(可走收成)
|
||||
6. 已验收步骤的 id **必须保留**;只能追加新步,或改未验收步的依赖/顺序
|
||||
7. 导演建议 steps 只作近期起点;按需选用,勿默认全选、勿一次排满
|
||||
8. **禁止**把「交互」与「美学」拆成两步
|
||||
9. **禁止**在本步写能力正文、Worker 列表、表结构
|
||||
10. 信息不够影响选型时,用 askUser 问 1~2 点(优先带 options)
|
||||
11. `summary`:`流程 · N 步 · open|closed · …`
|
||||
|
||||
## 自检
|
||||
|
||||
- 用户是否已选导演?(未选则不要硬编)
|
||||
- 是否只排了近期 horizon,而不是假固定全图?
|
||||
- 每步 name 都在【能力】里?同名多次是否都有不同 id?
|
||||
- depends_on 是否都指向更靠前的步骤 id?
|
||||
- 已验收 id 是否都还在?
|
||||
- 需要反复补规则/实例时,是否用了新 id 追加而非改写旧步?
|
||||
- 收成前是否把 `status` 设为 `closed`?
|
||||
51
skills/dialogue/world-simulator/workers/design-step/SKILL.md
Normal file
51
skills/dialogue/world-simulator/workers/design-step/SKILL.md
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
id: design-step
|
||||
skill: world-simulator
|
||||
name: 创作 · 执行步骤
|
||||
description: >-
|
||||
按已认可的创作流程,执行当前一步工序。提示词与产物 tag 由程序按模块注入。
|
||||
流程是增量 DAG:本步只读,禁止自行扩步或重排。
|
||||
version: 1
|
||||
stage: design
|
||||
inputTags:
|
||||
- "用户.需求"
|
||||
- "book.brief"
|
||||
- "用户.最新输入"
|
||||
- "用户.worker答复"
|
||||
- "用户.修订说明"
|
||||
- "设计.创作流程"
|
||||
- "创作.当前步骤"
|
||||
outputTags:
|
||||
- "创作.当前步骤"
|
||||
inputMerge: latest
|
||||
contextSegments:
|
||||
- id: flow
|
||||
tier: static
|
||||
tags: ["设计.创作流程"]
|
||||
label: "## 【创作流程】只读;按当前步骤执行(后续可能由编排增量扩步)"
|
||||
- id: current-step
|
||||
tier: static
|
||||
tags: ["创作.当前步骤"]
|
||||
label: "## 【本步】当前工序 id(对应流程 steps[].id)"
|
||||
- id: user-demand
|
||||
tier: dynamic
|
||||
tags: ["用户.需求", "book.brief", "用户.最新输入", "用户.worker答复", "用户.修订说明"]
|
||||
label: "## 用户表述"
|
||||
---
|
||||
|
||||
# 创作 · 执行步骤
|
||||
|
||||
你只做 **【本步】** 标明的那一个工序(流程里的一步 id → 能力 name)。
|
||||
|
||||
程序会在提示词中追加该工序的方法正文,并注入依赖步骤的已验收产物。
|
||||
|
||||
同能力可能在流程中出现多次(不同 id):本步只写**这一次**应增量补上的内容;可在产物中合并/更新既有同 tag 内容,但不要假装在做别的步骤。
|
||||
|
||||
## 纪律
|
||||
|
||||
1. 只写本步产物(程序指定的 output tag);不要改其它步骤产物
|
||||
2. 产物用简洁 JSON 或结构化中文,方便界面渲染;少写机器变量名
|
||||
3. 若本步有**默认问题**:程序已先发给用户;首答在「用户.worker答复」/「创作.能力开场白」。**禁止**再用 LLM 重复同一开场白
|
||||
4. 信息不足 → askUser 1~2 点(优先 options)
|
||||
5. `summary`:`{本步能力名} · …`
|
||||
6. **禁止**重排或扩写流程;流程只读。需要追加「再来一次生成规则」等 → 由总管再调 design-flow
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
id: opening-generator
|
||||
skill: world-simulator
|
||||
name: 开局 · 开场白
|
||||
description: >-
|
||||
【创作末尾】结合已定世界/故事设定与表结构,写出可开玩的开场白;
|
||||
初值表与开场一致、能推断则推断。主产物是开场白,不是填表。须用户验收。
|
||||
version: 2
|
||||
stage: design-end
|
||||
inputTags:
|
||||
- "设计.worker集"
|
||||
- "用户.需求"
|
||||
- "用户.最新输入"
|
||||
- "用户.worker答复"
|
||||
- "用户.修订说明"
|
||||
- "运行.初始变量"
|
||||
- "输出.开场白"
|
||||
- "上下文.定稿摘要"
|
||||
outputTags:
|
||||
- "输出.开场白"
|
||||
- "运行.初始变量"
|
||||
- "变量.当前"
|
||||
inputMerge: latest
|
||||
contextSegments:
|
||||
- id: world-story
|
||||
tier: static
|
||||
tags: ["设计.worker集", "上下文.定稿摘要"]
|
||||
label: "## 已定世界与故事规格"
|
||||
- id: user
|
||||
tier: dynamic
|
||||
tags: ["用户.需求", "用户.最新输入", "用户.worker答复", "用户.修订说明", "运行.初始变量", "输出.开场白"]
|
||||
label: "## 用户与开局草稿"
|
||||
---
|
||||
|
||||
# 开局 · 开场白
|
||||
|
||||
创作末尾:前面 **世界设定、故事设定、Worker 集、表结构** 已经定下来了。
|
||||
你要做的是——**站在这些已有内容上,写出玩家迈进世界的第一段开场**,并让状态表与之对齐。
|
||||
|
||||
```text
|
||||
主产物:输出.开场白(用户读的第一幕)
|
||||
辅产物:运行.初始变量 / 变量.当前(与开场同一真相的状态快照)
|
||||
```
|
||||
|
||||
**不是**填表工具附带一句开场;**不是**玩回合;**不是**重做 Worker 集。
|
||||
|
||||
## 角色
|
||||
|
||||
你是开场作者 + 开局状态对齐者:
|
||||
|
||||
1. 先吃透已有设定(`设计.worker集` 里的世界观/notes/核心前提/叙事指南/表 schema、`用户.需求`、定稿摘要)
|
||||
2. **写出开场白**:把玩家放进可感知、可行动的第一拍
|
||||
3. **顺带**落表:表字段取值须与开场里已经发生/成立的事实一致;能从设定与用户描述推出的直接填
|
||||
|
||||
## 重心(务必遵守)
|
||||
|
||||
```text
|
||||
结合前面的世界 + 故事 + 表结构 → 写开场白
|
||||
表是开场的配套落地,服务「这一刻世界是什么样」
|
||||
禁止:先当问卷填完表,再随便糊一段开场
|
||||
禁止:开场与表互相打架(开场写身无分文,表里资产却很多)
|
||||
```
|
||||
|
||||
## 开场白怎么写
|
||||
|
||||
- 扎根已定设定:地点、规则、人物关系、核心前提(如「不会被感染」)都要在场或可感,不要另起炉灶
|
||||
- 遵守 `narrative_guide` 与 `input_protocol`;隐藏表字段不要剧透进开场
|
||||
- 第一拍就要有「接下来用户能做什么」的空间,不要说明书/设定集口吻
|
||||
- 长度以可读完、愿意点进游玩为准;不要写成第一章全文
|
||||
|
||||
## 表(辅,与开场同真相)
|
||||
|
||||
字段格格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"rows": [
|
||||
{
|
||||
"key": "年龄",
|
||||
"value": 20,
|
||||
"rev": 1,
|
||||
"updatedAt": "ISO-8601",
|
||||
"source": "worker:opening-generator",
|
||||
"visibility": "visible",
|
||||
"note": "开场身份:普通大学生 → 推断"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 自然推断(表与开场共用)
|
||||
|
||||
能从**已有世界/故事/用户话**推出的,写入表并在开场里自然体现,**不要**再问:
|
||||
|
||||
| 已有信息 | 做法 |
|
||||
|----------|------|
|
||||
| 「普通大学生」+ 表有年龄/资产 | 开场写校园/宿舍语境;表填约 18–22、资产少、学生 |
|
||||
| 核心前提「主角免疫」 | 开场可感末日压力但不写感染;表感染=否 |
|
||||
| 用户明确「身无分文开局」 | 开场与表都尊重,不要抬成小康 |
|
||||
|
||||
只有「开场必须成立、但设定与用户话都推不出来」的点,才 `ask_user`(一次 1~2 个,带建议)。
|
||||
禁止把 schema 逐项做成填空卷。
|
||||
|
||||
## 流程意图
|
||||
|
||||
```text
|
||||
1. 读透世界/故事规格 + 用户需求 + 表 schema
|
||||
2. 想清「开场第一拍」:谁在哪、世界压力/邀请是什么、用户能接什么
|
||||
3. 缺关键且推不出的口子 → 轻量 askUser;否则直接写
|
||||
4. 先(或同时)写好 输出.开场白
|
||||
5. 按开场已成立的事实填写 运行.初始变量 + 变量.当前
|
||||
6. 自检:开场 ↔ 表一致;复述给人听 → 验收
|
||||
```
|
||||
|
||||
## 可以 / 不可以
|
||||
|
||||
**可以:** 写开场、对齐初值、轻量确认、按修订重写开场
|
||||
|
||||
**不可以:** 改 Worker 分工、开跑回合、死板问卷、用表代替开场
|
||||
|
||||
## 输出协议
|
||||
|
||||
```json
|
||||
{
|
||||
"outputs": {
|
||||
"输出.开场白": "……(主产物,完整可读)",
|
||||
"运行.初始变量": "{ ... rows ... }",
|
||||
"变量.当前": "{ ... 与初始一致 ... }"
|
||||
},
|
||||
"summary": "开场要点 + 与表对齐的关键状态",
|
||||
"askUser": null
|
||||
}
|
||||
```
|
||||
|
||||
`summary` 应概括开场情境,而不是「已填 N 个字段」。
|
||||
@@ -1,85 +0,0 @@
|
||||
---
|
||||
name: basic
|
||||
description: >-
|
||||
何时选用:最小演示流程。收集创作简报后生成大纲。
|
||||
适用于快速验证状态机与 worker 调度。
|
||||
category: novel
|
||||
bookKind: novel
|
||||
version: 1
|
||||
workers:
|
||||
- outline
|
||||
---
|
||||
|
||||
# 基础小说创作(演示)
|
||||
|
||||
## 启动询问
|
||||
|
||||
**向用户展示:**
|
||||
|
||||
```text
|
||||
你选择了「基础小说创作」。请简单告诉我:
|
||||
|
||||
1. 想写什么题材?(如科幻、悬疑)
|
||||
2. 大概多长?(短篇 / 中篇 / 长篇)
|
||||
3. 用人称?(第一 / 第三人称)
|
||||
|
||||
可以一次说完。
|
||||
```
|
||||
|
||||
**必须收集:**
|
||||
|
||||
- 题材
|
||||
- 篇幅
|
||||
- 人称
|
||||
|
||||
**写入目标:** `book.brief`
|
||||
|
||||
**足够进入下一阶段当:** 题材 + 篇幅 + 人称 已明确。
|
||||
|
||||
---
|
||||
|
||||
## 产物说明
|
||||
|
||||
| 产出 | 黑板 tag(迁移期可用 key 名) |
|
||||
|------|------|
|
||||
| 创作简报 | book.brief(≈ instantiate) |
|
||||
| 大纲 | outline.draft(≈ run 产出) |
|
||||
|
||||
流程:`book.brief` → `outline` → finish。brief 阶段 = **实例化**,见 `docs/tag-blackboard.md` §2。
|
||||
|
||||
---
|
||||
|
||||
## Worker 编排
|
||||
|
||||
| 条件 | worker | inputKeys | outputKeys | acceptanceMode |
|
||||
|---|---|---|---|---|
|
||||
| `book.brief` 已齐,`startupCompleted`,无 `outline.draft` | outline | book.brief | outline.draft | user_confirmed |
|
||||
| `outline.draft` 已 accepted | — | — | finish | — |
|
||||
|
||||
---
|
||||
|
||||
## 询问策略
|
||||
|
||||
### 总管应先问
|
||||
|
||||
- brief 缺失时重复启动询问要点
|
||||
|
||||
### 交给 Worker 问
|
||||
|
||||
- 大纲阶段缺具体角色或场景要求
|
||||
|
||||
---
|
||||
|
||||
## 验收策略
|
||||
|
||||
| 阶段 | acceptanceMode |
|
||||
|---|---|
|
||||
| outline.draft | user_confirmed |
|
||||
|
||||
---
|
||||
|
||||
## 禁用行为
|
||||
|
||||
- **禁止**跳过 brief 直接调度 outline。
|
||||
- **禁止**总管直接撰写 outline.draft 正文。
|
||||
- **禁止**调度本包以外的 worker。
|
||||
@@ -1,57 +0,0 @@
|
||||
---
|
||||
id: outline
|
||||
skill: basic
|
||||
name: 大纲创作
|
||||
description: 根据 book.brief 生成小说大纲,写入 outline.draft。
|
||||
version: 1
|
||||
outputKeys:
|
||||
- outline.draft
|
||||
---
|
||||
|
||||
# 大纲 Worker(演示)
|
||||
|
||||
## 角色与口吻
|
||||
|
||||
你是小说大纲撰写者。根据简报产出结构化大纲,不写正文。
|
||||
|
||||
## 能力范围
|
||||
|
||||
**可以做:**
|
||||
|
||||
- 读取 `book.brief`,生成 `outline.draft`
|
||||
- 在信息不足时 ask_user 补充角色或场景
|
||||
|
||||
**不可以做:**
|
||||
|
||||
- 写分章正文
|
||||
- 跳过 brief 臆造题材
|
||||
|
||||
## 思维链与自检
|
||||
|
||||
1. 读 book.brief(题材、篇幅、人称)
|
||||
2. 确定结构:短篇 3~5 节,中长篇按卷/章层级
|
||||
3. 每节/章写一句要点
|
||||
4. 自检:是否覆盖 brief 中的题材与人称
|
||||
|
||||
## 上下文用法
|
||||
|
||||
| inputKey | 用法 |
|
||||
|---|---|
|
||||
| book.brief | 唯一创作依据 |
|
||||
|
||||
## 输出格式
|
||||
|
||||
### outline.draft
|
||||
|
||||
层级标题 + 要点列表,Markdown 即可。示例:
|
||||
|
||||
```text
|
||||
# 大纲
|
||||
|
||||
## 第一节
|
||||
- 要点…
|
||||
```
|
||||
|
||||
## 安全规则
|
||||
|
||||
- 不写入 brief 未提及的硬性设定,除非 ask_user 确认。
|
||||
@@ -1,27 +0,0 @@
|
||||
# interactive-novel(TODO)
|
||||
|
||||
## 定位
|
||||
|
||||
长篇小说撰写 / 写作助手:**交互式**流程——意图转述、用户确认、大纲/事件/正文迭代、长期状态更新。
|
||||
|
||||
## 预期 tag 域(草案)
|
||||
|
||||
```text
|
||||
用户.原始输入 | 用户.意图转述 | 用户.确认结果
|
||||
项目.设定 | 项目.风格要求 | 项目.写作偏好
|
||||
大纲.当前 | 大纲.候选修改 | 大纲.确认稿
|
||||
事件.当前 | 事件.候选修改 | 事件.确认稿
|
||||
正文.原文 | 正文.续写锚点 | 正文.草稿 | 正文.修正版 | 正文.确认稿
|
||||
风格.样例 | 风格.摘要 | 风格.约束
|
||||
记忆.长期摘要 | 记忆.更新候选 | 记忆.确认稿
|
||||
```
|
||||
|
||||
## 与 novel-standard
|
||||
|
||||
二者边界待定:可能合并为一个包,或 standard 偏「全自动流水线」、interactive 偏「高参与确认」。
|
||||
|
||||
## 下一步
|
||||
|
||||
- [ ] 定 orchestrator 阶段链
|
||||
- [ ] 拆分 worker(意图转述、大纲、事件、正文、记忆更新等)
|
||||
- [ ] 写 `orchestrator.md` 并注册
|
||||
@@ -1,20 +0,0 @@
|
||||
# novel-standard(TODO)
|
||||
|
||||
## 定位
|
||||
|
||||
标准小说创作流水线(见 `docs/skill-format.md` 示例):卷/章结构、大纲 → 正文,tag 驱动、阶段清晰。
|
||||
|
||||
## 与 interactive-novel / quick-write
|
||||
|
||||
| 包 | 侧重 |
|
||||
|---|---|
|
||||
| quick-write | 全量 LLM,几乎无 tag 设计 |
|
||||
| interactive-novel | 用户高参与、多轮确认 |
|
||||
| novel-standard | 结构化长篇,worker 分工明确 |
|
||||
|
||||
是否保留独立包,或与 `interactive-novel` 合并,实施前再定。
|
||||
|
||||
## 下一步
|
||||
|
||||
- [ ] 确认是否与 interactive-novel 合并
|
||||
- [ ] 写 `orchestrator.md` + workers/
|
||||
@@ -1,30 +0,0 @@
|
||||
# quick-write(TODO)
|
||||
|
||||
## 定位
|
||||
|
||||
**简易小说 / 最低设计路径**:不做细粒度 tag 路由,上下文尽量全量交给 LLM,由模型理解用户行为与意图。
|
||||
|
||||
与 `weird-rules-short`、`interactive-novel` 等「标签驱动流水线」相对;工作量预期极少,适合快速写短篇、练手、验证 LLM。
|
||||
|
||||
## 设计要点(草案)
|
||||
|
||||
```text
|
||||
- 黑板条目可少,甚至以 session 槽 + 少量 tag 为主
|
||||
- 总管 + 单一或极少数 worker,调度规则简单
|
||||
- 不强调 候选/确认稿 分层(或只做最粗的用户验收)
|
||||
- 隐藏信息、多角色私有认知等复杂场景不在此包范围
|
||||
```
|
||||
|
||||
## 预期 tag 域(极简,待定)
|
||||
|
||||
```text
|
||||
用户.输入
|
||||
需求.摘要
|
||||
正文.草稿
|
||||
正文.确认稿 # 可选,仅最终交付
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
- [ ] 写 `orchestrator.md` + 1~2 个 worker
|
||||
- [ ] 注册到 `registry.yaml`
|
||||
@@ -1,286 +0,0 @@
|
||||
---
|
||||
name: weird-rules-short
|
||||
description: >-
|
||||
何时选用:用户要写规则怪谈、守则类条目、怪谈规则集、员工手册式恐怖短文。
|
||||
不适用:分章小说、长篇连载、需要卷纲/正文的多章节创作。
|
||||
产出:编号护命规则 + 读者可见解析块(固定形态,非章节小说)。
|
||||
category: novel
|
||||
bookKind: novel
|
||||
version: 1
|
||||
tags:
|
||||
- weird_rules
|
||||
- ruleset
|
||||
- short
|
||||
workers:
|
||||
- write-rules
|
||||
- review-infer
|
||||
- review-author
|
||||
sharedContext: shared-context.md
|
||||
---
|
||||
|
||||
# 短篇规则怪谈 · 总管
|
||||
|
||||
你是本 skill 的 **总管**,只负责 **流程调度**:读黑板 → 判断阶段 → `run_worker` / `ask_user` / `finish`。
|
||||
不写规则正文、不写解析、不做逐条质检——执行细节在包内 `workers/*/SKILL.md`。
|
||||
固定体裁规则在 `shared-context.md`,由 Runtime 注入 **本包所有 worker**,总管不读。
|
||||
|
||||
---
|
||||
|
||||
## 本包 worker 设计(非通用模板)
|
||||
|
||||
**每个总管单独设计 worker 数量与职责**;其它 skill 不必、也不会照搬本包结构。
|
||||
|
||||
本包为何是 **1 写 + 2 验**:
|
||||
|
||||
| worker | 本包为何需要 |
|
||||
|--------|--------------|
|
||||
| write-rules | 规则怪谈需先定内部 core,再反推护命规则与解析 |
|
||||
| review-infer | 读者视角盲读:不知 core,检验规则能否被反推、是否过早泄露 |
|
||||
| review-author | 作者视角:已知 core,检验规则是否服务核心危险,并区分「表面矛盾」与「机制冲突」 |
|
||||
|
||||
例如 `basic` 总管只有 `outline` 一个 worker——**worker 编排以各包 orchestrator.md 为准**,无全局「必须双验收」之类约定。
|
||||
|
||||
---
|
||||
|
||||
## 启动询问
|
||||
|
||||
选定本 skill 后,**第一个创作询问**。系统从本节读取问什么、写入哪。
|
||||
|
||||
**向用户展示:**
|
||||
|
||||
```text
|
||||
你选择了「短篇规则怪谈」。在开始之前,请告诉我:
|
||||
|
||||
1. 主要场景或情境(例如:夜班便利店、老旧宿舍、空荡地铁末班车)
|
||||
2. 规则大约几条(建议 8~12 条;也可更短/更长)
|
||||
3. 呈现体裁(守则公告、员工手册、贴在墙上的条目、日记附带规则等)
|
||||
4. 基调(冷感、压迫、黑色幽默等,可选)
|
||||
5. 必须出现或必须避免的元素(可选)
|
||||
6. 是否已有「一个意象或局面」(可选;没有也可全权交给创作)
|
||||
|
||||
可以一次说完。无需提前解释怪谈背后的真相——那是 worker 内部推演的任务。
|
||||
```
|
||||
|
||||
**必须收集:**
|
||||
|
||||
- 主要场景或情境
|
||||
- 规则条数(或大致规模)
|
||||
- 呈现体裁
|
||||
|
||||
**可选收集:**
|
||||
|
||||
- 基调、参考作品
|
||||
- 必须/禁止元素
|
||||
- 用户自带意象
|
||||
|
||||
**写入目标:** `book.brief`
|
||||
|
||||
**足够进入下一阶段当:** 场景 + 条数 + 体裁 已明确。
|
||||
|
||||
---
|
||||
|
||||
## 产物说明
|
||||
|
||||
本 skill 交付 **固定形态的规则集**,不是分卷分章小说。
|
||||
|
||||
| 产出 | 黑板 key | 写入者 | 对用户可见 | 说明 |
|
||||
|------|----------|--------|------------|------|
|
||||
| 创作简报 | book.brief | 启动询问 / 用户 | 否 | 全流程输入 |
|
||||
| 内部核心危险 | core.danger | write-rules | **否** | 仅 write-rules 与 review-author 使用 |
|
||||
| 规则条文 | rules.draft | write-rules | 是 | 编号条目,成稿主体 |
|
||||
| 解析/说明 | rules.commentary | write-rules | 是 | 帮助读规则,不揭晓 core |
|
||||
| 读者视角检查 | review.infer.notes | review-infer | 内部为主 | 盲读反推;含 `verdict` |
|
||||
| 作者视角检查 | review.author.notes | review-author | 内部为主 | 对照 core;含 `verdict` |
|
||||
|
||||
**Book 形态:** `bookKind: novel`(选定后不变)。本 skill 不使用卷/章 key;finish 时 accepted 的 `rules.draft` + `rules.commentary` 即最终交付。
|
||||
|
||||
**流程概览:**
|
||||
|
||||
```text
|
||||
book.brief → write-rules → [用户验收] → review-infer → review-author → finish
|
||||
↑______________________________________________|
|
||||
任一 review fail 或用户要求改规则
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 阶段定义
|
||||
|
||||
业务 stage 与 `docs/tag-blackboard.md` §2 对齐:**brief = instantiate(实例化)**;write / review = **run(运行)**。
|
||||
|
||||
| stageId | 名称 | 别名 | 进入条件 | 退出条件 |
|
||||
|---------|------|------|----------|----------|
|
||||
| brief | 创作简报 | **instantiate** | skill 已选 | `book.brief` 已写入且 `startupCompleted` |
|
||||
| write | 规则创作 | **run** | brief 完成 | `rules.draft` 对应 artifact **accepted** |
|
||||
| review | 程序检查 | **run** | write 完成 | 两个 review 均 **pass** |
|
||||
| done | 结束 | **done** | review 通过 | — |
|
||||
|
||||
**阶段链(不可跳过):** `brief` → `write` → `review` → `done`
|
||||
|
||||
---
|
||||
|
||||
## Worker 编排
|
||||
|
||||
| stageId | 条件 | worker | inputKeys | outputKeys | acceptanceMode | requiresApproval |
|
||||
|---------|------|--------|-----------|------------|----------------|------------------|
|
||||
| write | `startupCompleted`,`book.brief` 非空,且无 **accepted** rules;或 revision 需重写 | write-rules | 见下表 | core.danger, rules.draft, rules.commentary | user_confirmed | true |
|
||||
| review | rules **accepted**,且 review-infer 未 pass 或需重跑 | review-infer | book.brief, rules.draft, rules.commentary | review.infer.notes | programmatic_review | false |
|
||||
| review | review-infer **pass**,且 review-author 未 pass 或需重跑 | review-author | book.brief, core.danger, rules.draft, rules.commentary | review.author.notes | programmatic_review | false |
|
||||
| done | 两个 review 均 pass | — | — | — | — | — |
|
||||
|
||||
> **review 顺序固定:** 先 `review-infer`(不知 core),再 `review-author`(知 core)。
|
||||
> **禁止** 向 review-infer 注入 `core.danger`。
|
||||
|
||||
### write-rules 的 inputKeys(按场景)
|
||||
|
||||
| 场景 | inputKeys |
|
||||
|------|-----------|
|
||||
| 首次创作 | book.brief |
|
||||
| review 未通过后返工 | book.brief, review.infer.notes, review.author.notes |
|
||||
| 用户验收拒绝后返工 | book.brief, revision.instruction(若有) |
|
||||
|
||||
> `revision.instruction` 来自用户拒收时的说明;若无,总管可 `ask_user` 收集后再调度。
|
||||
|
||||
### review-infer 的 inputKeys
|
||||
|
||||
固定:`book.brief`, `rules.draft`, `rules.commentary`
|
||||
**禁止:** `core.danger`
|
||||
|
||||
### review-author 的 inputKeys
|
||||
|
||||
固定:`book.brief`, `core.danger`, `rules.draft`, `rules.commentary`
|
||||
|
||||
---
|
||||
|
||||
## 总管思维链
|
||||
|
||||
每轮 `planning` 按序检查,**命中第一条即行动**:
|
||||
|
||||
1. **phase = waiting_user(input)** 且 brief 未齐 → `ask_user` 补全启动询问三项(场景、条数、体裁)。
|
||||
2. **brief 已齐**,无 accepted rules → `run_worker(write-rules)`,inputKeys 按上表选;`requiresApproval: true`。
|
||||
3. **waiting_user(review_artifact)** → 不向用户泄露 core;引导用户只看 rules.draft / rules.commentary。
|
||||
4. 用户 **accept** rules → 下一决策 `run_worker(review-infer)`,`requiresApproval: false`。
|
||||
5. review-infer **pass** → `run_worker(review-author)`。
|
||||
6. 两个 review 均 **pass** → `finish`。
|
||||
7. 用户 **reject** rules → `ask_user` 收集修改意见 → 写入 `revision.instruction` → 再 `run_worker(write-rules)`。
|
||||
8. 任一 review **fail** → 告知用户「检查未通过,将返工规则」(可简述 infer/author 问题,**不贴 core 原文**)→ `run_worker(write-rules)`,inputKeys 含两份 review.notes。
|
||||
9. 用户问「真相是什么」→ `ask_user` 说明本 skill 不揭晓 core,可讨论方向;**禁止**输出 `core.danger` 原文。
|
||||
10. 用户要求写章节/小说正文 → `ask_user` 说明本 skill 只产出规则集+解析,建议换 skill。
|
||||
|
||||
**当前 worker 运行中** → 不重复调度;等 worker 完成或 `worker_questions` 由用户回复后 resume。
|
||||
|
||||
**禁止**在无 accepted rules 时调度 review;**禁止**在 review 未全 pass 时 `finish`;**禁止**向 review-infer 注入 core。
|
||||
|
||||
---
|
||||
|
||||
## 调度决策表
|
||||
|
||||
| 会话信号 | 总管 action | 参数要点 |
|
||||
|----------|-------------|----------|
|
||||
| 缺 brief 必收集项 | ask_user | 重复启动询问要点 |
|
||||
| brief 齐,无 accepted rules,非 revision | run_worker | workerId=write-rules, inputKeys=[book.brief] |
|
||||
| 用户拒收 rules 产物 | ask_user → run_worker | 收 revision.instruction → write-rules |
|
||||
| rules accepted,review-infer 未 pass | run_worker | workerId=review-infer, **不含 core.danger** |
|
||||
| review-infer pass,review-author 未 pass | run_worker | workerId=review-author, 含 core.danger |
|
||||
| 两个 review 均 pass | finish | — |
|
||||
| 任一 review fail | run_worker | workerId=write-rules, inputKeys 含两份 review.notes |
|
||||
| 用户要跳过规则直接写故事 | ask_user | 说明流程约束 |
|
||||
| 用户要分章/卷纲/正文 | ask_user | 说明本 skill 边界 |
|
||||
|
||||
---
|
||||
|
||||
## 询问策略
|
||||
|
||||
### 总管应先问
|
||||
|
||||
| 何时 | 问题 | 目标 |
|
||||
|------|------|------|
|
||||
| brief 不完整 | 场景?条数?呈现体裁? | book.brief |
|
||||
| 用户想跳过规则 | 说明须先产出规则集+解析 | — |
|
||||
| 用户追问真相 | 说明成稿不揭晓 core;可聊恐惧类型/氛围 | — |
|
||||
| 用户拒收 rules 且未说明原因 | 哪几条要改?删增?语气? | revision.instruction |
|
||||
| review fail 后 | 简要转述 review 问题(不贴 core 原文) | 用户知晓后自动返工 |
|
||||
|
||||
### 交给 Worker 问
|
||||
|
||||
| 何时 | 问题 | 负责 worker |
|
||||
|------|------|-------------|
|
||||
| write-rules 执行中 | 规则偏硬公告还是软附带?编号风格? | write-rules |
|
||||
|
||||
**禁止**向用户索取「用一句话说出核心危险是什么」。
|
||||
|
||||
---
|
||||
|
||||
## 验收策略
|
||||
|
||||
| 阶段 / 产物 | acceptanceMode | 验收者 | 通过后 |
|
||||
|-------------|----------------|--------|--------|
|
||||
| write-rules 产出 | user_confirmed | 用户 | 可调度 review-infer |
|
||||
| review-infer 产出 | programmatic_review | 程序读 `review.infer.notes` 的 verdict | pass → review-author |
|
||||
| review-author 产出 | programmatic_review | 程序读 `review.author.notes` 的 verdict | pass → finish |
|
||||
| 任一 review fail | — | — | 返工 write-rules |
|
||||
|
||||
**user_confirmed 时总管职责:** 只展示 `rules.draft` 与 `rules.commentary`;不展示 `core.danger`。
|
||||
|
||||
**programmatic_review 判定:** 各自 notes 中 `verdict:` 行,`pass` 为通过。
|
||||
|
||||
**revision 统一规则:**
|
||||
|
||||
- 用户 reject rules → 回到 write,保留 book.brief,追加 revision.instruction。
|
||||
- 任一 review fail → 回到 write,input 必含两份 review.notes。
|
||||
- 返工后旧 rules artifact 由阶段机 superseded;以新 accepted 版本为准。
|
||||
|
||||
---
|
||||
|
||||
## 禁用行为
|
||||
|
||||
- **禁止**总管直接撰写或润色 `rules.draft`、`rules.commentary` 正文。
|
||||
- **禁止**向用户展示 `core.danger` 全文或「标准答案式」揭秘。
|
||||
- **禁止**跳过 write 阶段或跳过用户验收直接 review。
|
||||
- **禁止**review 未全 pass 时 `finish`。
|
||||
- **禁止**调度本包以外 worker(仅 `write-rules`、`review-infer`、`review-author`)。
|
||||
- **禁止**向 review-infer 注入 `core.danger`。
|
||||
- **禁止**调度 outline、drafting 或任何分章写作 worker。
|
||||
- **禁止**把未 accepted 的 draft key 当作已定稿事实告知用户。
|
||||
|
||||
---
|
||||
|
||||
## 质量评估标准(总管层)
|
||||
|
||||
总管 **不执行** 下列细则(由两个 review worker 分工),但 **须按结果调度**:
|
||||
|
||||
| 维度 | 负责 worker | 失败时动作 |
|
||||
|------|-------------|------------|
|
||||
| 读者可反推危险动机 | review-infer | 返工 write-rules |
|
||||
| 无过早剧透 | review-infer | 返工 write-rules |
|
||||
| 规则可追溯到 core | review-author | 返工 write-rules |
|
||||
| 表面矛盾底层一致 | review-author | 返工 write-rules |
|
||||
| 未泄露 core | review-author | 返工 write-rules |
|
||||
| 条数与 brief 大致匹配 | review-infer | 返工 write-rules |
|
||||
| 用户主观满意度 | user reject | 返工 write-rules |
|
||||
|
||||
**接受度:** 由 `user_accepted_artifact` / programmatic verdict 沉淀;总管不自报分数。
|
||||
|
||||
---
|
||||
|
||||
## 示例(调度级)
|
||||
|
||||
**用户:** 「10 条规则,员工手册体,场景是地下档案库,冷感。」
|
||||
|
||||
```text
|
||||
→ 写入 book.brief
|
||||
→ run_worker(write-rules, inputKeys=[book.brief], requiresApproval=true)
|
||||
→ 用户验收 rules.draft + rules.commentary → accept
|
||||
→ run_worker(review-infer, inputKeys=[book.brief, rules.draft, rules.commentary])
|
||||
→ review.infer.notes verdict=pass
|
||||
→ run_worker(review-author, inputKeys=[book.brief, core.danger, rules.draft, rules.commentary])
|
||||
→ review.author.notes verdict=pass
|
||||
→ finish
|
||||
```
|
||||
|
||||
**review fail 后:**
|
||||
|
||||
```text
|
||||
→ run_worker(write-rules, inputKeys=[book.brief, review.infer.notes, review.author.notes])
|
||||
→ 用户再次验收 → accept → review-infer → review-author → …
|
||||
```
|
||||
@@ -1,68 +0,0 @@
|
||||
# 规则怪谈 · 固定创作上下文
|
||||
|
||||
> 本文件由 Runtime 注入 **本 skill 包内所有 worker** 的 prompt 开头。
|
||||
> 总管不读此文件;worker 须将其视为不可违背的体裁约束。
|
||||
|
||||
---
|
||||
|
||||
## 体裁定义
|
||||
|
||||
规则怪谈是 **「盲人摸象」**:读者只通过护命规则反推可能遭遇的危险,而不是被点明危险本身。
|
||||
|
||||
| 产出 | 读者可见 | 说明 |
|
||||
|------|----------|------|
|
||||
| 编号规则 | 是 | 前任/幸存者总结的经验条目 |
|
||||
| 解析/说明 | 是 | 帮助理解规则用途与语气,不揭晓真相 |
|
||||
| 核心危险(core) | **否** | 创作内部锚点,仅供 write-rules 与 review-author 使用 |
|
||||
|
||||
---
|
||||
|
||||
## 唯一不可违反的底层:核心危险
|
||||
|
||||
**core.danger** 是一开始就设计的 **怪谈化危险**——整份规则集存在的理由。
|
||||
|
||||
- 所有护命规则必须 **最终可追溯到这一危险**(作者视角)。
|
||||
- 读者视角下 **不得** 在 rules / commentary 中点明 core 的名称或「标准答案式」总结。
|
||||
- 可用过于现实的危险帮助内部理解,但成稿中不得直接写出。
|
||||
|
||||
**表面矛盾 ≠ 逻辑矛盾。** 规则可以看起来互相冲突,只要它们共享同一套底层危险逻辑:
|
||||
|
||||
```text
|
||||
例:人行道红灯时,车辆可通行,行人不可穿越。
|
||||
→ 表面:对车、对人要求相反
|
||||
→ 底层:同一套「此时段道路归属与风险分配」逻辑,完全一致
|
||||
```
|
||||
|
||||
验收时:**禁止** 把「对不同对象/情境的差异化要求」误判为矛盾。
|
||||
应追问:若 core 成立,这些差异是否 **同一机制下的合理分支**?
|
||||
|
||||
---
|
||||
|
||||
## 规则怎么写
|
||||
|
||||
规则是 **帮助避害的经验**,不是迫害主角的玄学刑罚。
|
||||
|
||||
| 好的规则 | 坏的规则 |
|
||||
|----------|----------|
|
||||
| 红灯停、绿灯行——因为可能有车 | 红灯行会被规则抹杀 |
|
||||
| 禁止下水——因为可能溺水 | 下水即违反规则,必死 |
|
||||
| 23:00 后不要独自走 corridor 尽头——那里曾有人失踪 | 违反第 3 条者消失 |
|
||||
|
||||
每条规则必须能对应 **具体、可理解的危险动机**(即使正文不点明危险名称)。
|
||||
|
||||
---
|
||||
|
||||
## 解析块(commentary)怎么写
|
||||
|
||||
- 说明规则背景、使用情境、语气与体裁(公告/手册/日记附带等)。
|
||||
- **不** 写成「真相是…」「作者揭秘」。
|
||||
- **不** 复制 core.danger 中的关键词或直白总结。
|
||||
|
||||
---
|
||||
|
||||
## 全体 worker 安全规则
|
||||
|
||||
- 不向用户展示 `core.danger` 全文作为「答案」。
|
||||
- 指出问题时只引用 rules 中的 **片段**,不拼出完整真相。
|
||||
- 若 brief 要求禁忌元素,遵守并在产出中体现边界。
|
||||
- 不写分章正文、章纲、卷结构(本 skill 只产出规则集 + 解析)。
|
||||
@@ -1,117 +0,0 @@
|
||||
---
|
||||
id: review-author
|
||||
skill: weird-rules-short
|
||||
name: 作者视角一致性验收
|
||||
description: >-
|
||||
读取 core.danger。从作者视角检验规则是否服务于核心危险、表面矛盾是否底层一致、
|
||||
是否有规则偏离 core 或自相矛盾于同一机制。
|
||||
version: 1
|
||||
outputKeys:
|
||||
- review.author.notes
|
||||
---
|
||||
|
||||
# 作者视角一致性验收 Worker
|
||||
|
||||
## 角色与口吻
|
||||
|
||||
你是 **规则怪谈的作者**,已知内部 `core.danger`。你检验成稿规则是否 **忠实服务于这一危险**,并判断「看起来矛盾」的条目是否在 **同一底层机制** 下合理。
|
||||
|
||||
你不重写全文,只报告问题与修改建议。
|
||||
|
||||
## 能力范围
|
||||
|
||||
**可以做:**
|
||||
|
||||
- 对照 `core.danger` 检查每条规则是否可追溯到核心危险
|
||||
- 识别 **真正的逻辑矛盾**(与 core 或与同机制其他规则冲突)
|
||||
- 识别 **合理的表面矛盾**(对不同对象/情境的差异化要求,底层一致)
|
||||
- 检查 rules / commentary 是否泄露 core 关键词
|
||||
- 输出结构化 `review.author.notes`(pass / fail + 理由)
|
||||
|
||||
**不可以做:**
|
||||
|
||||
- 修改 rules 或 commentary
|
||||
- 向用户揭晓 core.danger 原文
|
||||
- 把合理的差异化规则误判为 fail(见固定上下文「表面矛盾 ≠ 逻辑矛盾」)
|
||||
|
||||
## 思维链与自检
|
||||
|
||||
### 检查步骤
|
||||
|
||||
1. 读 `core.danger`,提取 **禁止出现在成稿中的关键词/短语**。
|
||||
2. 读 `book.brief`,确认体裁与条数预期。
|
||||
3. 逐条读 `rules.draft`,对每条问:
|
||||
- 若 core 成立,这条规则是 **必要分支** 还是 **无关/矛盾**?
|
||||
- 与其他规则对比:差异是 **对象/情境不同**,还是 **机制打架**?
|
||||
- 是否泄露 core 关键词?
|
||||
- 是否空泛玄学惩罚而无 core 动机?
|
||||
4. 读 `rules.commentary`:是否点明 core 或锁死唯一解读?
|
||||
5. 汇总为 `review.author.notes`。
|
||||
|
||||
### 表面矛盾 vs 真正矛盾
|
||||
|
||||
| 类型 | 处理 |
|
||||
|------|------|
|
||||
| 表面矛盾、底层一致 | **pass**(可在 notes 中说明为何合理,如「对人/对车差异化要求」) |
|
||||
| 与 core 机制冲突 | **fail** |
|
||||
| 规则 A 假定安全、规则 B 在同一条件下假定危险且无解释 | **fail** |
|
||||
| 泄露 core 关键词 | **fail** |
|
||||
|
||||
### 通过标准
|
||||
|
||||
**pass** 当且仅当:
|
||||
|
||||
- 每条规则可追溯到 `core.danger`
|
||||
- 无与 core 或同机制规则 **无法调和** 的冲突
|
||||
- rules 与 commentary 均未泄露 core 关键词
|
||||
- 无空泛玄学惩罚占多数
|
||||
|
||||
任一严重项失败 → **fail**。
|
||||
|
||||
### 提交前自检
|
||||
|
||||
- [ ] 已逐条对照 core,非扫读
|
||||
- [ ] 未把合理表面矛盾标为 fail
|
||||
- [ ] review.author.notes 含明确 pass 或 fail
|
||||
- [ ] fail 时给出可操作的修改建议
|
||||
- [ ] review 中 **禁止** 复制 core.danger 全文
|
||||
|
||||
## 上下文用法
|
||||
|
||||
| inputKey | 用法 |
|
||||
|----------|------|
|
||||
| book.brief | 体裁、条数、基调 |
|
||||
| core.danger | 唯一不可违反的底层;对照泄露与一致性 |
|
||||
| rules.draft | 主要检查对象 |
|
||||
| rules.commentary | 检查泄露与体裁 |
|
||||
|
||||
## 输出格式
|
||||
|
||||
### review.author.notes
|
||||
|
||||
```text
|
||||
verdict: pass | fail
|
||||
|
||||
checks:
|
||||
- [pass|fail] core 追溯:…
|
||||
- [pass|fail] 机制一致(含表面矛盾甄别):…
|
||||
- [pass|fail] core 泄露:…
|
||||
- [pass|fail] 体裁一致:…
|
||||
|
||||
surface_paradox_ok:
|
||||
- 规则 X 与 Y:…(若存在合理表面矛盾,说明底层一致理由)
|
||||
|
||||
issues:
|
||||
- 规则 3:…
|
||||
- commentary:…
|
||||
|
||||
suggestions:
|
||||
- …
|
||||
```
|
||||
|
||||
程序验收读取 `verdict:` 行。
|
||||
|
||||
## 安全规则
|
||||
|
||||
- review.author.notes 中 **禁止** 复制 core.danger 全文。
|
||||
- 指出泄露时只引用 rules 中的 **片段**,不拼出「正确答案」。
|
||||
@@ -1,110 +0,0 @@
|
||||
---
|
||||
id: review-infer
|
||||
skill: weird-rules-short
|
||||
name: 读者视角反推验收
|
||||
description: >-
|
||||
不读取 core.danger。从 rules 与 commentary 反推隐含危险,评价规则是否可被读者理解、
|
||||
是否有可反推动机、是否过早泄露真相。
|
||||
version: 1
|
||||
outputKeys:
|
||||
- review.infer.notes
|
||||
---
|
||||
|
||||
# 读者视角反推验收 Worker
|
||||
|
||||
## 角色与口吻
|
||||
|
||||
你是 **第一次读到这份规则集的读者**。你不知道作者预设的核心危险,也不应尝试读取 `core.danger`。
|
||||
|
||||
你的任务:仅凭 `rules.draft` 与 `rules.commentary`,反推「这份规则在防什么」,并评价规则作为 **读者体验** 是否合格。
|
||||
|
||||
## 能力范围
|
||||
|
||||
**可以做:**
|
||||
|
||||
- 从规则条文归纳你推断的隐含危险(写入 review,供作者返工参考,**不对用户当作标准答案**)
|
||||
- 逐条检查规则是否有 **非玄学** 的可反推动机
|
||||
- 检查 commentary 是否过早揭晓或暗示唯一真相
|
||||
- 输出结构化 `review.infer.notes`(pass / fail + 理由)
|
||||
|
||||
**不可以做:**
|
||||
|
||||
- 读取或使用 `core.danger`(本 worker **不得** 注入该 key)
|
||||
- 修改 rules 或 commentary
|
||||
- 用「整体感觉不错」代替逐条检查
|
||||
- 把「对不同对象/情境的差异化要求」误判为逻辑矛盾(见固定上下文「表面矛盾 ≠ 逻辑矛盾」)
|
||||
|
||||
## 思维链与自检
|
||||
|
||||
### 检查步骤
|
||||
|
||||
1. 读 `book.brief`,了解场景、体裁、条数预期。
|
||||
2. **盲读** `rules.draft` 与 `rules.commentary`,写下你推断的隐含危险(2~4 句,标注为「读者推断,非标准答案」)。
|
||||
3. 逐条读 `rules.draft`:
|
||||
- 读者能否反推「为什么要有这条规则」?
|
||||
- 是否空泛「违反即死/抹杀/清除」而无具体动机?
|
||||
- 是否像作者在直接剧透危险名称?
|
||||
4. 读 `rules.commentary`:
|
||||
- 是否写成「真相是…」或唯一标准解读?
|
||||
- 是否与 brief 要求的体裁、基调一致?
|
||||
5. 汇总为 `review.infer.notes`。
|
||||
|
||||
### 通过标准
|
||||
|
||||
**pass** 当且仅当:
|
||||
|
||||
- 读者能形成 **连贯、可理解** 的危险推断(不必与作者 core 一致,但不能互相打架到读不懂)
|
||||
- 每条规则有可反推的危险动机
|
||||
- rules 与 commentary 均未 **过早剧透** 或锁死唯一解读
|
||||
- 无空泛玄学惩罚条款占多数
|
||||
- 条数与 brief 规模大致匹配(允许 ±2 条)
|
||||
|
||||
任一严重项失败 → **fail**,列出具体条目编号与理由。
|
||||
|
||||
### 提交前自检
|
||||
|
||||
- [ ] 确认未使用 core.danger
|
||||
- [ ] 已逐条检查,非扫读
|
||||
- [ ] review.infer.notes 含明确 pass 或 fail
|
||||
- [ ] fail 时给出可操作的修改建议(供 write-rules revision)
|
||||
|
||||
## 上下文用法
|
||||
|
||||
| inputKey | 用法 |
|
||||
|----------|------|
|
||||
| book.brief | 场景、条数、体裁、基调 |
|
||||
| rules.draft | 主要检查对象 |
|
||||
| rules.commentary | 检查是否剧透、体裁是否一致 |
|
||||
|
||||
**禁止注入:** `core.danger`
|
||||
|
||||
## 输出格式
|
||||
|
||||
### review.infer.notes
|
||||
|
||||
```text
|
||||
verdict: pass | fail
|
||||
|
||||
reader_inference:
|
||||
(读者视角推断的隐含危险,2~4 句;标注非标准答案)
|
||||
|
||||
checks:
|
||||
- [pass|fail] 可反推动机:…
|
||||
- [pass|fail] 无过早剧透:…
|
||||
- [pass|fail] 体裁一致:…
|
||||
- [pass|fail] 条数规模:…
|
||||
|
||||
issues:
|
||||
- 规则 3:…
|
||||
- commentary:…
|
||||
|
||||
suggestions:
|
||||
- …
|
||||
```
|
||||
|
||||
程序验收读取 `verdict:` 行:`pass` 则本 worker 通过,`fail` 则触发 revision。
|
||||
|
||||
## 安全规则
|
||||
|
||||
- 推断的危险写入 review 仅供返工,**禁止** 向用户当作「正确答案」展示。
|
||||
- 指出剧透时只引用 rules 中的 **片段**。
|
||||
@@ -1,103 +0,0 @@
|
||||
---
|
||||
id: write-rules
|
||||
skill: weird-rules-short
|
||||
name: 规则与解析创作
|
||||
description: >-
|
||||
从 book.brief 推演内部 core.danger,产出编号护命规则与解析块。
|
||||
不写章节正文、不写卷纲。
|
||||
version: 1
|
||||
outputKeys:
|
||||
- core.danger
|
||||
- rules.draft
|
||||
- rules.commentary
|
||||
---
|
||||
|
||||
# 规则与解析 Worker
|
||||
|
||||
## 角色与口吻
|
||||
|
||||
你是规则怪谈创作执行者。固定创作上下文(shared-context)已注入 prompt 开头——**体裁原则、好/坏规则对照、表面矛盾与底层一致** 均以此为准。
|
||||
|
||||
你根据简报推演 **内部核心危险**,再反推 **护命规则** 与 **读者可见的解析块**。
|
||||
|
||||
## 能力范围
|
||||
|
||||
**可以做:**
|
||||
|
||||
- 从 `book.brief` 推演 `core.danger`(内部,不对读者揭晓)
|
||||
- 撰写编号规则条文 `rules.draft`
|
||||
- 撰写解析/说明块 `rules.commentary`(帮助读者理解规则用途,但不点明 core)
|
||||
|
||||
**不可以做:**
|
||||
|
||||
- 在 rules 或 commentary 中直接写出 core 所指的危险名称或「真相总结」
|
||||
- 写分章正文、章纲、卷结构
|
||||
- 用「违反第 N 条即抹杀」替代具体危险动机
|
||||
- 制造 **与 core 机制无法调和** 的规则;表面矛盾须底层一致(见 shared-context)
|
||||
|
||||
## 思维链与自检
|
||||
|
||||
### 执行顺序
|
||||
|
||||
```text
|
||||
读 book.brief → 定 core.danger → 写 rules.draft → 写 rules.commentary → 自检 → 提交
|
||||
```
|
||||
|
||||
### 核心(core)怎么定
|
||||
|
||||
- **核心** = 主角可能遭遇的「怪谈化危险」(灵异、不可名状、环境异变等)。
|
||||
- `core.danger` 是 **唯一不可违反的底层**;所有规则须可追溯到它。
|
||||
- 写规则时可设计 **表面看似矛盾、底层一致** 的分支(如对不同对象/时段的差异化要求)。
|
||||
|
||||
### 提交前自检
|
||||
|
||||
- [ ] `core.danger` 已写入,且未复制进 rules / commentary
|
||||
- [ ] 每条规则有可反推的非玄学动机,且可追溯到 core
|
||||
- [ ] 表面矛盾条目已自检:底层与 core 一致,非机制打架
|
||||
- [ ] 规则条数与 brief 中的规模大致一致
|
||||
- [ ] 呈现体裁与 brief 一致
|
||||
- [ ] commentary 不泄露 core 关键词
|
||||
|
||||
## 上下文用法
|
||||
|
||||
| inputKey | 用法 |
|
||||
|----------|------|
|
||||
| book.brief | 场景、条数、体裁、基调、禁忌;推演 core 与规则风格 |
|
||||
| review.infer.notes | revision 时读取读者视角失败理由 |
|
||||
| review.author.notes | revision 时读取作者视角失败理由 |
|
||||
| revision.instruction | 用户拒收时的修改说明 |
|
||||
|
||||
缺 brief 时 **ask_user**,不要臆造场景。
|
||||
|
||||
## 输出格式
|
||||
|
||||
### core.danger
|
||||
|
||||
内部段落,2~5 句。描述怪谈化危险与氛围,可含创作用类比,标注「不可写入成稿」。
|
||||
|
||||
### rules.draft
|
||||
|
||||
编号条目,每条约 1~3 句。示例:
|
||||
|
||||
```text
|
||||
1. …
|
||||
2. …
|
||||
```
|
||||
|
||||
### rules.commentary
|
||||
|
||||
读者可见的说明块:规则背景、使用情境、语气说明。不揭晓 core,不写成「作者揭秘」。
|
||||
|
||||
## 示例
|
||||
|
||||
核心若是「夜间空荡处有人跟踪」(应怪谈化处理),规则可写:
|
||||
|
||||
```text
|
||||
3. 23:00 后不要独自经过地下二层通道;若听见第二脚步声,不要回头,前往最近有灯光的房间。
|
||||
```
|
||||
|
||||
而非:
|
||||
|
||||
```text
|
||||
3. 23:00 后经过地下二层者,违反规则将被清除。
|
||||
```
|
||||
@@ -1,16 +1,8 @@
|
||||
skills:
|
||||
- name: basic
|
||||
description: 最小演示:创作简报 → 大纲。
|
||||
category: novel
|
||||
bookKind: novel
|
||||
path: novel/basic/orchestrator.md
|
||||
- name: weird-rules-short
|
||||
description: 规则怪谈:隐含核心 → 护命规则 + 解析 → 检查。
|
||||
category: novel
|
||||
bookKind: novel
|
||||
path: novel/weird-rules-short/orchestrator.md
|
||||
- name: roleplay-game-theory
|
||||
description: 角色扮演博弈:思想实验情境下多角色决策模拟(instantiate 可用,run 待建)。
|
||||
- name: world-simulator
|
||||
description: >-
|
||||
默认包:用户选导演 → 从能力编排剧本 → 演员上场;
|
||||
创作末尾可选开场白;play 按声明调度。
|
||||
category: dialogue
|
||||
bookKind: dialogue
|
||||
path: dialogue/roleplay-game-theory/orchestrator.md
|
||||
path: dialogue/world-simulator/orchestrator.md
|
||||
|
||||
@@ -10,8 +10,10 @@ export class Blackboard {
|
||||
private items = new Map<string, BlackboardItem>();
|
||||
private writeSeq = 0;
|
||||
|
||||
listTagIndex(): BlackboardTagIndex[] {
|
||||
return [...this.items.values()]
|
||||
listTagIndex(options?: { includeArchived?: boolean }): BlackboardTagIndex[] {
|
||||
const includeArchived = options?.includeArchived === true;
|
||||
return latestItemsByTag([...this.items.values()])
|
||||
.filter((item) => includeArchived || item.metadata?.role !== "archived")
|
||||
.map(({ id, tag, source, scope, updatedAt }) => ({
|
||||
id,
|
||||
tag,
|
||||
@@ -43,34 +45,38 @@ export class Blackboard {
|
||||
queryByPatterns(
|
||||
patterns: string[],
|
||||
merge: "latest" | "concat" = "latest",
|
||||
options?: { includeArchived?: boolean },
|
||||
): BlackboardItem[] {
|
||||
const includeArchived = options?.includeArchived === true;
|
||||
const allItems = [...this.items.values()];
|
||||
|
||||
if (patterns.some(isFullAccessPattern)) {
|
||||
return [...this.items.values()].sort((a, b) =>
|
||||
a.updatedAt.localeCompare(b.updatedAt),
|
||||
);
|
||||
const latestByTag = latestItemsByTag(allItems);
|
||||
return latestByTag
|
||||
.filter((item) => includeArchived || item.metadata?.role !== "archived")
|
||||
.sort((a, b) => a.updatedAt.localeCompare(b.updatedAt));
|
||||
}
|
||||
|
||||
const result: BlackboardItem[] = [];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const matched = [...this.items.values()]
|
||||
.filter((item) => tagMatchesPattern(item.tag, pattern))
|
||||
.sort(compareItemsByRecency);
|
||||
const matchedLatest = latestItemsByTag(
|
||||
allItems.filter((item) => tagMatchesPattern(item.tag, pattern)),
|
||||
).filter((item) => includeArchived || item.metadata?.role !== "archived");
|
||||
|
||||
if (matched.length === 0) continue;
|
||||
if (matchedLatest.length === 0) continue;
|
||||
|
||||
if (merge === "concat" && matched.length > 1) {
|
||||
if (merge === "concat" && matchedLatest.length > 1) {
|
||||
const ordered = matchedLatest.sort((a, b) =>
|
||||
a.updatedAt.localeCompare(b.updatedAt),
|
||||
);
|
||||
result.push({
|
||||
...matched[0],
|
||||
...ordered[ordered.length - 1],
|
||||
id: `merged:${pattern}`,
|
||||
content: matched
|
||||
.slice()
|
||||
.reverse()
|
||||
.map((m) => m.content)
|
||||
.join("\n\n---\n\n"),
|
||||
content: ordered.map((m) => m.content).join("\n\n---\n\n"),
|
||||
});
|
||||
} else {
|
||||
result.push(matched[0]);
|
||||
result.push(matchedLatest.sort(compareItemsByRecency)[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,3 +121,13 @@ export class Blackboard {
|
||||
function compareItemsByRecency(a: BlackboardItem, b: BlackboardItem): number {
|
||||
return b.updatedAt.localeCompare(a.updatedAt);
|
||||
}
|
||||
|
||||
/** 每个 tag 只保留最新一条 */
|
||||
function latestItemsByTag(items: BlackboardItem[]): BlackboardItem[] {
|
||||
const byTag = new Map<string, BlackboardItem>();
|
||||
for (const item of items) {
|
||||
const prev = byTag.get(item.tag);
|
||||
if (!prev || item.updatedAt > prev.updatedAt) byTag.set(item.tag, item);
|
||||
}
|
||||
return [...byTag.values()];
|
||||
}
|
||||
|
||||
141
src/blackboard/table-cells.ts
Normal file
141
src/blackboard/table-cells.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 表字段格:扁平 rows + 每格 rev,防止用户手改被 worker 覆盖。
|
||||
*/
|
||||
export type TableCellSource = `user` | `system` | `worker:${string}`;
|
||||
|
||||
export type TableCell = {
|
||||
key: string;
|
||||
value: unknown;
|
||||
rev: number;
|
||||
updatedAt: string;
|
||||
source: TableCellSource | string;
|
||||
visibility?: "visible" | "hidden";
|
||||
note?: string;
|
||||
};
|
||||
|
||||
export type TableDoc = {
|
||||
rows: TableCell[];
|
||||
};
|
||||
|
||||
export function parseTableDoc(raw: string | undefined | null): TableDoc | null {
|
||||
if (!raw?.trim()) return null;
|
||||
try {
|
||||
const doc = JSON.parse(raw) as unknown;
|
||||
if (!doc || typeof doc !== "object" || Array.isArray(doc)) return null;
|
||||
const rowsRaw = (doc as { rows?: unknown }).rows;
|
||||
if (!Array.isArray(rowsRaw)) return null;
|
||||
const rows: TableCell[] = [];
|
||||
for (const item of rowsRaw) {
|
||||
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
||||
const row = item as Record<string, unknown>;
|
||||
const key = typeof row.key === "string" ? row.key.trim() : "";
|
||||
if (!key) continue;
|
||||
const rev = typeof row.rev === "number" && row.rev >= 1 ? row.rev : 1;
|
||||
rows.push({
|
||||
key,
|
||||
value: row.value,
|
||||
rev,
|
||||
updatedAt:
|
||||
typeof row.updatedAt === "string" && row.updatedAt
|
||||
? row.updatedAt
|
||||
: new Date().toISOString(),
|
||||
source:
|
||||
typeof row.source === "string" && row.source
|
||||
? row.source
|
||||
: "system",
|
||||
visibility:
|
||||
row.visibility === "hidden" || row.visibility === "visible"
|
||||
? row.visibility
|
||||
: "visible",
|
||||
note: typeof row.note === "string" ? row.note : undefined,
|
||||
});
|
||||
}
|
||||
return { rows };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function stringifyTableDoc(doc: TableDoc): string {
|
||||
return JSON.stringify(doc, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并表更新:worker 须带读时的 expectedRev;
|
||||
* source=user 的格默认不覆盖;rev 不匹配则跳过该格。
|
||||
*/
|
||||
export function mergeTableCells(params: {
|
||||
current: TableDoc | null;
|
||||
patch: TableDoc;
|
||||
actor: TableCellSource | string;
|
||||
/** 若提供,仅当 current.rev === expectedRev[key] 时才写入 */
|
||||
expectedRev?: Record<string, number>;
|
||||
}): { doc: TableDoc; applied: string[]; skipped: Array<{ key: string; reason: string }> } {
|
||||
const byKey = new Map<string, TableCell>();
|
||||
for (const row of params.current?.rows ?? []) {
|
||||
byKey.set(row.key, { ...row });
|
||||
}
|
||||
|
||||
const applied: string[] = [];
|
||||
const skipped: Array<{ key: string; reason: string }> = [];
|
||||
const now = new Date().toISOString();
|
||||
|
||||
for (const patch of params.patch.rows) {
|
||||
const key = patch.key.trim();
|
||||
if (!key) continue;
|
||||
const existing = byKey.get(key);
|
||||
|
||||
if (existing?.source === "user" && !String(params.actor).startsWith("user")) {
|
||||
skipped.push({ key, reason: "user-owned" });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (params.expectedRev && existing) {
|
||||
const expected = params.expectedRev[key];
|
||||
if (expected != null && existing.rev !== expected) {
|
||||
skipped.push({
|
||||
key,
|
||||
reason: `rev-conflict have=${existing.rev} expected=${expected}`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const nextRev = existing ? existing.rev + 1 : patch.rev >= 1 ? patch.rev : 1;
|
||||
byKey.set(key, {
|
||||
key,
|
||||
value: patch.value,
|
||||
rev: nextRev,
|
||||
updatedAt: now,
|
||||
source: params.actor,
|
||||
visibility: patch.visibility ?? existing?.visibility ?? "visible",
|
||||
note: patch.note ?? existing?.note,
|
||||
});
|
||||
applied.push(key);
|
||||
}
|
||||
|
||||
return {
|
||||
doc: { rows: [...byKey.values()].sort((a, b) => a.key.localeCompare(b.key)) },
|
||||
applied,
|
||||
skipped,
|
||||
};
|
||||
}
|
||||
|
||||
/** 从零创建:全部 source=actor,rev=1 */
|
||||
export function createTableFromValues(
|
||||
values: Record<string, unknown>,
|
||||
actor: TableCellSource | string,
|
||||
meta?: Record<string, { visibility?: "visible" | "hidden"; note?: string }>,
|
||||
): TableDoc {
|
||||
const now = new Date().toISOString();
|
||||
const rows: TableCell[] = Object.entries(values).map(([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
rev: 1,
|
||||
updatedAt: now,
|
||||
source: actor,
|
||||
visibility: meta?.[key]?.visibility ?? "visible",
|
||||
note: meta?.[key]?.note,
|
||||
}));
|
||||
return { rows: rows.sort((a, b) => a.key.localeCompare(b.key)) };
|
||||
}
|
||||
275
src/blackboard/table-side-effects.ts
Normal file
275
src/blackboard/table-side-effects.ts
Normal file
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* 表边沿副作用:prev 不满足且 now 满足才触发;once 规则记 fired。
|
||||
* 规则来自 设计.worker集.tables.side_effects。
|
||||
*/
|
||||
import type { Blackboard } from "./blackboard.js";
|
||||
import type { TableDoc } from "./table-cells.js";
|
||||
|
||||
export type SideEffectOp = "eq" | "neq" | "gte" | "lte" | "gt" | "lt" | "truthy" | "changed";
|
||||
|
||||
export type SideEffectMode = "once" | "edge" | "every_edge";
|
||||
|
||||
export type SideEffectAction =
|
||||
| { type: "write_tag"; tag: string; content: string }
|
||||
| { type: "replace_tag"; tag: string; content: string }
|
||||
| { type: "queue_worker"; workerId: string; note?: string };
|
||||
|
||||
export type SideEffectRule = {
|
||||
id: string;
|
||||
field: string;
|
||||
op: SideEffectOp;
|
||||
value?: unknown;
|
||||
/** once = 边沿 + 记 fired;edge/every_edge = 仅边沿,可反复跨边沿 */
|
||||
mode: SideEffectMode;
|
||||
action: SideEffectAction;
|
||||
};
|
||||
|
||||
export type FiredRegistry = Record<string, { at: string; round?: number }>;
|
||||
|
||||
export const SIDE_EFFECT_FIRED_TAG = "运行.表副作用.fired";
|
||||
|
||||
export function parseSideEffectRules(tables: unknown): SideEffectRule[] {
|
||||
if (!tables || typeof tables !== "object" || Array.isArray(tables)) return [];
|
||||
const raw = (tables as { side_effects?: unknown }).side_effects;
|
||||
if (!Array.isArray(raw)) return [];
|
||||
const out: SideEffectRule[] = [];
|
||||
for (const item of raw) {
|
||||
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
||||
const row = item as Record<string, unknown>;
|
||||
const id = typeof row.id === "string" ? row.id.trim() : "";
|
||||
const field = typeof row.field === "string" ? row.field.trim() : "";
|
||||
if (!id || !field) continue;
|
||||
const op = normalizeOp(row.op);
|
||||
const mode = normalizeMode(row.mode);
|
||||
const action = parseAction(row.action);
|
||||
if (!action) continue;
|
||||
out.push({ id, field, op, value: row.value, mode, action });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function normalizeOp(raw: unknown): SideEffectOp {
|
||||
const s = typeof raw === "string" ? raw.trim().toLowerCase() : "eq";
|
||||
const allowed: SideEffectOp[] = [
|
||||
"eq",
|
||||
"neq",
|
||||
"gte",
|
||||
"lte",
|
||||
"gt",
|
||||
"lt",
|
||||
"truthy",
|
||||
"changed",
|
||||
];
|
||||
return (allowed.includes(s as SideEffectOp) ? s : "eq") as SideEffectOp;
|
||||
}
|
||||
|
||||
function normalizeMode(raw: unknown): SideEffectMode {
|
||||
const s = typeof raw === "string" ? raw.trim().toLowerCase() : "once";
|
||||
if (s === "edge" || s === "every_edge") return "every_edge";
|
||||
return "once";
|
||||
}
|
||||
|
||||
function parseAction(raw: unknown): SideEffectAction | null {
|
||||
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
||||
const a = raw as Record<string, unknown>;
|
||||
const type = typeof a.type === "string" ? a.type.trim() : "";
|
||||
if (type === "write_tag" || type === "replace_tag") {
|
||||
const tag = typeof a.tag === "string" ? a.tag.trim() : "";
|
||||
const content = typeof a.content === "string" ? a.content : "";
|
||||
if (!tag) return null;
|
||||
return { type, tag, content };
|
||||
}
|
||||
if (type === "queue_worker") {
|
||||
const workerId =
|
||||
typeof a.workerId === "string"
|
||||
? a.workerId.trim()
|
||||
: typeof a.worker_id === "string"
|
||||
? a.worker_id.trim()
|
||||
: "";
|
||||
if (!workerId) return null;
|
||||
return {
|
||||
type: "queue_worker",
|
||||
workerId,
|
||||
note: typeof a.note === "string" ? a.note : undefined,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function tableValueMap(doc: TableDoc | null | undefined): Map<string, unknown> {
|
||||
const map = new Map<string, unknown>();
|
||||
for (const row of doc?.rows ?? []) {
|
||||
map.set(row.key, row.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export function conditionSatisfied(
|
||||
values: Map<string, unknown>,
|
||||
rule: SideEffectRule,
|
||||
prevValues?: Map<string, unknown>,
|
||||
): boolean {
|
||||
const now = values.get(rule.field);
|
||||
if (rule.op === "changed") {
|
||||
if (!prevValues) return false;
|
||||
return !sameValue(prevValues.get(rule.field), now);
|
||||
}
|
||||
return compareOp(now, rule.op, rule.value);
|
||||
}
|
||||
|
||||
function compareOp(actual: unknown, op: SideEffectOp, expected: unknown): boolean {
|
||||
switch (op) {
|
||||
case "eq":
|
||||
return sameValue(actual, expected);
|
||||
case "neq":
|
||||
return !sameValue(actual, expected);
|
||||
case "truthy":
|
||||
return Boolean(actual);
|
||||
case "gte":
|
||||
case "lte":
|
||||
case "gt":
|
||||
case "lt": {
|
||||
const a = toNumber(actual);
|
||||
const b = toNumber(expected);
|
||||
if (a == null || b == null) return false;
|
||||
if (op === "gte") return a >= b;
|
||||
if (op === "lte") return a <= b;
|
||||
if (op === "gt") return a > b;
|
||||
return a < b;
|
||||
}
|
||||
case "changed":
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function toNumber(v: unknown): number | null {
|
||||
if (typeof v === "number" && Number.isFinite(v)) return v;
|
||||
if (typeof v === "string" && v.trim() && !Number.isNaN(Number(v))) {
|
||||
return Number(v);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function sameValue(a: unknown, b: unknown): boolean {
|
||||
if (a === b) return true;
|
||||
if (a == null || b == null) return a == null && b == null;
|
||||
if (typeof a === "number" || typeof b === "number") {
|
||||
const na = toNumber(a);
|
||||
const nb = toNumber(b);
|
||||
return na != null && nb != null && na === nb;
|
||||
}
|
||||
return String(a) === String(b);
|
||||
}
|
||||
|
||||
export type SideEffectTrigger = {
|
||||
rule: SideEffectRule;
|
||||
reason: "edge";
|
||||
};
|
||||
|
||||
/**
|
||||
* 对本轮 prev→now 快照算边沿;同 round 同 ruleId 最多一次。
|
||||
* once:已 fired 则跳过;触发后写入 nextFired。
|
||||
* every_edge:每次边沿都触发,不记 fired。
|
||||
*/
|
||||
export function evaluateSideEffects(params: {
|
||||
prev: TableDoc | null;
|
||||
next: TableDoc;
|
||||
rules: SideEffectRule[];
|
||||
fired: FiredRegistry;
|
||||
round?: number;
|
||||
}): {
|
||||
triggers: SideEffectTrigger[];
|
||||
nextFired: FiredRegistry;
|
||||
queuedWorkers: Array<{ workerId: string; ruleId: string; note?: string }>;
|
||||
} {
|
||||
const prevMap = tableValueMap(params.prev);
|
||||
const nextMap = tableValueMap(params.next);
|
||||
const nextFired: FiredRegistry = { ...params.fired };
|
||||
const triggers: SideEffectTrigger[] = [];
|
||||
const seen = new Set<string>();
|
||||
const queuedWorkers: Array<{ workerId: string; ruleId: string; note?: string }> =
|
||||
[];
|
||||
|
||||
for (const rule of params.rules) {
|
||||
if (seen.has(rule.id)) continue;
|
||||
if (rule.mode === "once" && nextFired[rule.id]) continue;
|
||||
|
||||
const was = conditionSatisfied(prevMap, rule, undefined);
|
||||
const now = conditionSatisfied(nextMap, rule, prevMap);
|
||||
// 边沿:prev 不满足、now 满足(changed 用 prev/now 值差)
|
||||
const edge =
|
||||
rule.op === "changed"
|
||||
? conditionSatisfied(nextMap, rule, prevMap)
|
||||
: !was && now;
|
||||
if (!edge) continue;
|
||||
|
||||
seen.add(rule.id);
|
||||
triggers.push({ rule, reason: "edge" });
|
||||
if (rule.mode === "once") {
|
||||
nextFired[rule.id] = {
|
||||
at: new Date().toISOString(),
|
||||
round: params.round,
|
||||
};
|
||||
}
|
||||
if (rule.action.type === "queue_worker") {
|
||||
queuedWorkers.push({
|
||||
workerId: rule.action.workerId,
|
||||
ruleId: rule.id,
|
||||
note: rule.action.note,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { triggers, nextFired, queuedWorkers };
|
||||
}
|
||||
|
||||
export function parseFiredRegistry(raw: string | undefined | null): FiredRegistry {
|
||||
if (!raw?.trim()) return {};
|
||||
try {
|
||||
const doc = JSON.parse(raw) as unknown;
|
||||
if (!doc || typeof doc !== "object" || Array.isArray(doc)) return {};
|
||||
const out: FiredRegistry = {};
|
||||
for (const [k, v] of Object.entries(doc as Record<string, unknown>)) {
|
||||
if (!k.trim()) continue;
|
||||
if (v && typeof v === "object" && !Array.isArray(v)) {
|
||||
const row = v as Record<string, unknown>;
|
||||
out[k] = {
|
||||
at: typeof row.at === "string" ? row.at : new Date().toISOString(),
|
||||
round: typeof row.round === "number" ? row.round : undefined,
|
||||
};
|
||||
} else if (v === true) {
|
||||
out[k] = { at: new Date().toISOString() };
|
||||
}
|
||||
}
|
||||
return out;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export function stringifyFiredRegistry(fired: FiredRegistry): string {
|
||||
return JSON.stringify(fired, null, 2);
|
||||
}
|
||||
|
||||
/** 执行 write_tag / replace_tag;queue_worker 只返回,由上层调度 */
|
||||
export function applySideEffectTagActions(params: {
|
||||
blackboard: Blackboard;
|
||||
triggers: SideEffectTrigger[];
|
||||
source?: string;
|
||||
}): { writtenTags: string[] } {
|
||||
const writtenTags: string[] = [];
|
||||
const source = params.source ?? "system:table-side-effect";
|
||||
for (const { rule } of params.triggers) {
|
||||
const action = rule.action;
|
||||
if (action.type !== "write_tag" && action.type !== "replace_tag") continue;
|
||||
params.blackboard.write({
|
||||
tag: action.tag,
|
||||
content: action.content,
|
||||
source,
|
||||
});
|
||||
writtenTags.push(action.tag);
|
||||
}
|
||||
return { writtenTags };
|
||||
}
|
||||
@@ -1,11 +1,23 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
||||
import {
|
||||
cpSync,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
unlinkSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { BookProject, BookSummary } from "../types/book.js";
|
||||
import type { PersistedBookSession } from "../types/book-session.js";
|
||||
import type { RunSnapshot } from "../types/run-snapshot.js";
|
||||
import { ensureUserDataDirs, getUserDataDir } from "../config/user-data-dir.js";
|
||||
import { deleteBookSession } from "./session-store.js";
|
||||
import { deleteAllRunSnapshots } from "./run-snapshot-store.js";
|
||||
|
||||
const SESSION_FILENAME = "session.json";
|
||||
|
||||
function booksDir(): string {
|
||||
return path.join(getUserDataDir(), "books");
|
||||
}
|
||||
@@ -14,6 +26,10 @@ function bookPath(id: string): string {
|
||||
return path.join(booksDir(), `${id}.json`);
|
||||
}
|
||||
|
||||
function bookDataDir(id: string): string {
|
||||
return path.join(getUserDataDir(), "books", id);
|
||||
}
|
||||
|
||||
export function listBooks(): BookSummary[] {
|
||||
ensureUserDataDirs();
|
||||
mkdirSync(booksDir(), { recursive: true });
|
||||
@@ -64,7 +80,7 @@ export function createBook(input: { title?: string }): BookProject {
|
||||
const book: BookProject = {
|
||||
id: randomUUID(),
|
||||
title: input.title?.trim() || "未命名作品",
|
||||
preview: "新建作品,选择 skill 包开始…",
|
||||
preview: "新建作品,描述你想创作什么…",
|
||||
sessionIds: [],
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
@@ -84,6 +100,8 @@ export function updateBook(
|
||||
| "activeSessionId"
|
||||
| "activeSkillId"
|
||||
| "activeSkillName"
|
||||
| "orchestratorId"
|
||||
| "orchestratorName"
|
||||
>
|
||||
>,
|
||||
): BookProject {
|
||||
@@ -115,3 +133,69 @@ export function deleteBook(id: string): void {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
/** 复制作品及其会话、游玩存档为新作品 */
|
||||
export function duplicateBook(sourceId: string, title?: string): BookProject {
|
||||
const source = getBook(sourceId);
|
||||
if (!source) throw new Error("Book 不存在");
|
||||
|
||||
const newId = randomUUID();
|
||||
const newSessionId = randomUUID();
|
||||
const now = new Date().toISOString();
|
||||
const dupTitle = title?.trim() || `${source.title} 副本`;
|
||||
|
||||
ensureUserDataDirs();
|
||||
const sourceDir = bookDataDir(sourceId);
|
||||
const destDir = bookDataDir(newId);
|
||||
|
||||
if (existsSync(sourceDir)) {
|
||||
cpSync(sourceDir, destDir, { recursive: true });
|
||||
} else {
|
||||
mkdirSync(destDir, { recursive: true });
|
||||
}
|
||||
|
||||
const sessionFile = path.join(destDir, SESSION_FILENAME);
|
||||
let sessionIds: string[] = [];
|
||||
let activeSessionId: string | undefined;
|
||||
|
||||
if (existsSync(sessionFile)) {
|
||||
try {
|
||||
const snap = JSON.parse(readFileSync(sessionFile, "utf8")) as PersistedBookSession;
|
||||
snap.bookId = newId;
|
||||
snap.sessionId = newSessionId;
|
||||
snap.runtimeSession.id = newSessionId;
|
||||
snap.savedAt = now;
|
||||
writeFileSync(sessionFile, JSON.stringify(snap, null, 2), "utf8");
|
||||
sessionIds = [newSessionId];
|
||||
activeSessionId = newSessionId;
|
||||
} catch {
|
||||
/* ignore broken session */
|
||||
}
|
||||
}
|
||||
|
||||
const snapDir = path.join(destDir, "run-snapshots");
|
||||
if (existsSync(snapDir)) {
|
||||
for (const file of readdirSync(snapDir).filter((f) => f.endsWith(".json"))) {
|
||||
try {
|
||||
const full = path.join(snapDir, file);
|
||||
const raw = JSON.parse(readFileSync(full, "utf8")) as RunSnapshot;
|
||||
raw.bookId = newId;
|
||||
writeFileSync(full, JSON.stringify(raw, null, 2), "utf8");
|
||||
} catch {
|
||||
/* skip */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const newBook: BookProject = {
|
||||
...structuredClone(source),
|
||||
id: newId,
|
||||
title: dupTitle,
|
||||
sessionIds,
|
||||
activeSessionId,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
writeFileSync(bookPath(newId), JSON.stringify(newBook, null, 2), "utf8");
|
||||
return newBook;
|
||||
}
|
||||
|
||||
@@ -22,10 +22,11 @@ function printHelp(): void {
|
||||
console.log(`阶段机 + Skill 演示
|
||||
|
||||
启动流程:
|
||||
1. /start → 列出 skills/ 下的 SKILL.md
|
||||
2. 输入 skill name 或编号(如 basic 或 1)
|
||||
3. 按 SKILL.md「启动询问」回答
|
||||
4. /decide worker outline-worker approve → /approve → …
|
||||
1. /start → 自动进入默认 orchestrator(world-simulator)
|
||||
2. 按启动询问描述创作需求
|
||||
3. /decide worker design-intake approve → /approve → …
|
||||
|
||||
Legacy:/start-with world-simulator(显式指定包)
|
||||
|
||||
命令:
|
||||
/start 开始会话
|
||||
|
||||
57
src/config/default-orchestrator.ts
Normal file
57
src/config/default-orchestrator.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import type { SkillIndexEntry, SkillStartupMode, ActiveSkillSnapshot } from "../types/runtime.js";
|
||||
|
||||
/** 新建作品时自动加载的能力库(用户不再选 skill 包) */
|
||||
export const DEFAULT_ORCHESTRATOR_ID = "world-simulator";
|
||||
|
||||
/**
|
||||
* 首屏引导兜底(包内 orchestrator.md `uiPrompt` 优先)。
|
||||
* 词汇与 design-intake A1/A2 对齐:站位 + 系统扮演/输出/交互。
|
||||
*/
|
||||
export const DEFAULT_UI_PROMPT = `请用你自己的话描述想做什么——没有必填项,下面只是帮你找思路的提示。
|
||||
|
||||
【你扮演什么】(可对照,也可不按表)
|
||||
· 单角代入:我就是一个固定角色
|
||||
· 代理操控:我有角色,但常发 () 指令指挥
|
||||
· 旁观/实验:我不扮演谁,看或记录推演
|
||||
· 写手/统筹:我定方向,要成稿或助手式分段(长文 / 爽文也走这条)
|
||||
· 多角切换:我轮流扮演不同身份
|
||||
|
||||
【系统要给你什么】(输出与交互,不是文风问卷)
|
||||
· 回合对话:你一句,系统回一段可见结果
|
||||
· 助手分段:先大纲/细纲,你再填表或改设定,再按章/段写正文
|
||||
· 只要事实摘要 / 要可读叙事 / 要状态表…
|
||||
|
||||
【输入约定】可选用括号区分:
|
||||
· () 圆括号:用户指令/要求,不可写成角色对白
|
||||
· "" 双引号:角色在世界内说的话
|
||||
· 【】方括号:角色在世界内的行动
|
||||
未加标记时默认可视为世界内输入;语义明显是元话语按指令处理。
|
||||
|
||||
【核心体验】若愿意可带一句:你最想反复感到的是什么——没有也没关系,我会从描述里察觉。
|
||||
|
||||
示例:丧尸世界但我不会被感染;1v1 网恋;都市爽文先写大纲再按章开写;坠机求生;思想实验旁观三方选择……`;
|
||||
|
||||
export function resolveDefaultOrchestratorId(
|
||||
available: SkillIndexEntry[],
|
||||
): string {
|
||||
if (available.some((s) => s.name === DEFAULT_ORCHESTRATOR_ID)) {
|
||||
return DEFAULT_ORCHESTRATOR_ID;
|
||||
}
|
||||
if (available.length === 0) {
|
||||
throw new Error("registry 中没有可用 orchestrator");
|
||||
}
|
||||
return available[0].name;
|
||||
}
|
||||
|
||||
/** 兼容旧快照:world-simulator 默认 agent-first(UI 引导 → 用户输入 → Agent 调 Skill) */
|
||||
export function effectiveStartupMode(
|
||||
skill: Pick<ActiveSkillSnapshot, "name" | "startupMode">,
|
||||
): SkillStartupMode {
|
||||
if (skill.startupMode === "agent-first" || skill.startupMode === "intake") {
|
||||
return skill.startupMode;
|
||||
}
|
||||
// 旧 frontmatter design-intake bootstrap 已废弃,等同 agent-first
|
||||
if (skill.startupMode === "design-intake") return "agent-first";
|
||||
if (skill.name === DEFAULT_ORCHESTRATOR_ID) return "agent-first";
|
||||
return "intake";
|
||||
}
|
||||
@@ -8,9 +8,15 @@ export type AppSettings = {
|
||||
version: 1;
|
||||
activeProfileId: string | null;
|
||||
activePresetId: string | null;
|
||||
/**
|
||||
* 每个会话保留带全量上下文痕迹的消息条数(最新 N 条)。
|
||||
* 更早的消息仍保留,但去掉 contextTrace 正文。默认 5;0 表示不存痕迹。
|
||||
*/
|
||||
contextTraceKeepLatest?: number;
|
||||
};
|
||||
|
||||
const FILE_NAME = "settings.json";
|
||||
const DEFAULT_CONTEXT_TRACE_KEEP = 5;
|
||||
|
||||
function settingsPath(): string {
|
||||
return path.join(getUserDataDir(), FILE_NAME);
|
||||
@@ -21,9 +27,16 @@ function defaultSettings(): AppSettings {
|
||||
version: 1,
|
||||
activeProfileId: null,
|
||||
activePresetId: null,
|
||||
contextTraceKeepLatest: DEFAULT_CONTEXT_TRACE_KEEP,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeContextTraceKeepLatest(raw: unknown): number {
|
||||
const n = typeof raw === "number" ? raw : Number(raw);
|
||||
if (!Number.isFinite(n) || n < 0) return DEFAULT_CONTEXT_TRACE_KEEP;
|
||||
return Math.min(50, Math.floor(n));
|
||||
}
|
||||
|
||||
export function loadAppSettings(): AppSettings {
|
||||
ensureUserDataDirs();
|
||||
try {
|
||||
@@ -34,6 +47,9 @@ export function loadAppSettings(): AppSettings {
|
||||
version: 1,
|
||||
activeProfileId: parsed.activeProfileId ?? null,
|
||||
activePresetId: parsed.activePresetId ?? null,
|
||||
contextTraceKeepLatest: normalizeContextTraceKeepLatest(
|
||||
parsed.contextTraceKeepLatest ?? DEFAULT_CONTEXT_TRACE_KEEP,
|
||||
),
|
||||
};
|
||||
} catch {
|
||||
return defaultSettings();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { LlmConfig } from "../config/env.js";
|
||||
import type { GenerationParameters } from "../types/preset.js";
|
||||
import { consumeOpenAiToolStream } from "./stream-complete.js";
|
||||
|
||||
export type ToolCallPayload = {
|
||||
id: string;
|
||||
@@ -71,21 +72,36 @@ export type CompleteWithToolsResult = {
|
||||
model?: string;
|
||||
};
|
||||
|
||||
export type StreamCallbacks = {
|
||||
onReasoningDelta?: (delta: string) => void;
|
||||
onContentDelta?: (delta: string) => void;
|
||||
};
|
||||
|
||||
export type LlmProvider = {
|
||||
complete(
|
||||
messages: ChatMessage[],
|
||||
options?: CompleteOptions,
|
||||
): Promise<CompleteResult>;
|
||||
completeStream?(
|
||||
messages: ChatMessage[],
|
||||
options?: CompleteOptions,
|
||||
callbacks?: StreamCallbacks,
|
||||
): Promise<CompleteResult>;
|
||||
completeWithTools(
|
||||
messages: ChatMessage[],
|
||||
options: CompleteWithToolsOptions,
|
||||
): Promise<CompleteWithToolsResult>;
|
||||
completeWithToolsStream?(
|
||||
messages: ChatMessage[],
|
||||
options: CompleteWithToolsOptions,
|
||||
callbacks: StreamCallbacks,
|
||||
): Promise<CompleteWithToolsResult>;
|
||||
};
|
||||
|
||||
function buildRequestBody(
|
||||
config: LlmConfig,
|
||||
messages: ChatMessage[],
|
||||
options?: CompleteOptions & { tools?: ToolDefinition[] },
|
||||
options?: CompleteOptions & { tools?: ToolDefinition[]; stream?: boolean },
|
||||
): Record<string, unknown> {
|
||||
const gen = options?.generation ?? {};
|
||||
const body: Record<string, unknown> = {
|
||||
@@ -125,6 +141,11 @@ function buildRequestBody(
|
||||
body.response_format = { type: "json_object" };
|
||||
}
|
||||
|
||||
if (options?.stream) {
|
||||
body.stream = true;
|
||||
body.stream_options = { include_usage: true };
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
@@ -255,6 +276,44 @@ export class OpenAiCompatibleProvider implements LlmProvider {
|
||||
};
|
||||
}
|
||||
|
||||
async completeStream(
|
||||
messages: ChatMessage[],
|
||||
options?: CompleteOptions,
|
||||
callbacks: StreamCallbacks = {},
|
||||
): Promise<CompleteResult> {
|
||||
const url = `${this.config.baseUrl.replace(/\/$/, "")}/chat/completions`;
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${this.config.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify(
|
||||
buildRequestBody(this.config, messages, { ...options, stream: true }),
|
||||
),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const body = await response.text();
|
||||
throw new Error(`LLM request failed (${response.status}): ${body}`);
|
||||
}
|
||||
|
||||
if (!response.body) {
|
||||
throw new Error("LLM stream response has no body");
|
||||
}
|
||||
|
||||
const parts = await consumeOpenAiToolStream(response.body, callbacks);
|
||||
if (!parts.content && !parts.reasoning) {
|
||||
throw new Error("LLM stream returned empty content");
|
||||
}
|
||||
return {
|
||||
content: parts.content ?? parts.reasoning ?? "",
|
||||
reasoning: parts.reasoning || undefined,
|
||||
usage: parts.usage,
|
||||
model: parts.model ?? this.config.model,
|
||||
};
|
||||
}
|
||||
|
||||
async completeWithTools(
|
||||
messages: ChatMessage[],
|
||||
options: CompleteWithToolsOptions,
|
||||
@@ -296,6 +355,49 @@ export class OpenAiCompatibleProvider implements LlmProvider {
|
||||
model: data.model ?? this.config.model,
|
||||
};
|
||||
}
|
||||
|
||||
async completeWithToolsStream(
|
||||
messages: ChatMessage[],
|
||||
options: CompleteWithToolsOptions,
|
||||
callbacks: StreamCallbacks,
|
||||
): Promise<CompleteWithToolsResult> {
|
||||
const url = `${this.config.baseUrl.replace(/\/$/, "")}/chat/completions`;
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${this.config.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify(
|
||||
buildRequestBody(this.config, messages, {
|
||||
...options,
|
||||
tools: options.tools,
|
||||
stream: true,
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const body = await response.text();
|
||||
throw new Error(`LLM request failed (${response.status}): ${body}`);
|
||||
}
|
||||
|
||||
if (!response.body) {
|
||||
throw new Error("LLM stream response has no body");
|
||||
}
|
||||
|
||||
const parts = await consumeOpenAiToolStream(response.body, callbacks);
|
||||
if (!parts.content && parts.toolCalls.length === 0 && !parts.reasoning) {
|
||||
throw new Error("LLM stream returned empty content and no tool calls");
|
||||
}
|
||||
return {
|
||||
content: parts.content,
|
||||
toolCalls: parts.toolCalls,
|
||||
reasoning: parts.reasoning || undefined,
|
||||
usage: parts.usage,
|
||||
model: parts.model ?? this.config.model,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export type MockLlmStep =
|
||||
@@ -341,6 +443,39 @@ export class MockLlmProvider implements LlmProvider {
|
||||
};
|
||||
}
|
||||
|
||||
async completeStream(
|
||||
_messages: ChatMessage[],
|
||||
options?: CompleteOptions,
|
||||
callbacks: StreamCallbacks = {},
|
||||
): Promise<CompleteResult> {
|
||||
const step = this.nextStep();
|
||||
const response =
|
||||
typeof step === "string"
|
||||
? step
|
||||
: (step.content ?? JSON.stringify({ action: "ask_user", reason: "mock" }));
|
||||
const reasoning = "用户需要明确分工 → 调用 design-intake 产出 worker 集。";
|
||||
for (const ch of reasoning) {
|
||||
callbacks.onReasoningDelta?.(ch);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
}
|
||||
for (const ch of response) {
|
||||
callbacks.onContentDelta?.(ch);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
}
|
||||
const approx = Math.max(1, Math.ceil(response.length / 4));
|
||||
return {
|
||||
content: response,
|
||||
reasoning,
|
||||
usage: {
|
||||
promptTokens: approx,
|
||||
completionTokens: approx,
|
||||
totalTokens: approx * 2,
|
||||
},
|
||||
model: "mock",
|
||||
...(options?.caller ? {} : {}),
|
||||
};
|
||||
}
|
||||
|
||||
async completeWithTools(
|
||||
_messages: ChatMessage[],
|
||||
_options: CompleteWithToolsOptions,
|
||||
@@ -366,6 +501,20 @@ export class MockLlmProvider implements LlmProvider {
|
||||
model: "mock",
|
||||
};
|
||||
}
|
||||
|
||||
async completeWithToolsStream(
|
||||
_messages: ChatMessage[],
|
||||
_options: CompleteWithToolsOptions,
|
||||
callbacks: StreamCallbacks,
|
||||
): Promise<CompleteWithToolsResult> {
|
||||
const reasoning =
|
||||
"用户需要明确 Worker 分工 → 先读取黑板与 worker 列表 → 调用 design-intake 产出 worker 集。";
|
||||
for (const ch of reasoning) {
|
||||
callbacks.onReasoningDelta?.(ch);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
}
|
||||
return this.completeWithTools(_messages, _options);
|
||||
}
|
||||
}
|
||||
|
||||
export function createMockMainAgentResponse(
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
CompleteWithToolsOptions,
|
||||
CompleteWithToolsResult,
|
||||
LlmProvider,
|
||||
StreamCallbacks,
|
||||
} from "./client.js";
|
||||
|
||||
/**
|
||||
@@ -55,4 +56,23 @@ export class PresetLlmProvider implements LlmProvider {
|
||||
generation,
|
||||
});
|
||||
}
|
||||
|
||||
async completeWithToolsStream(
|
||||
messages: ChatMessage[],
|
||||
options: CompleteWithToolsOptions,
|
||||
callbacks: StreamCallbacks,
|
||||
): Promise<CompleteWithToolsResult> {
|
||||
const preset = this.getPreset();
|
||||
if (!this.inner.completeWithToolsStream) {
|
||||
return this.completeWithTools(messages, options);
|
||||
}
|
||||
const presetMessages = preset ? assemblePresetMessages(preset) : [];
|
||||
const merged = preset ? mergeMessages(presetMessages, messages) : messages;
|
||||
const generation = options.generation ?? preset?.generation;
|
||||
|
||||
return this.inner.completeWithToolsStream(merged, {
|
||||
...options,
|
||||
generation,
|
||||
}, callbacks);
|
||||
}
|
||||
}
|
||||
|
||||
149
src/llm/stream-complete.ts
Normal file
149
src/llm/stream-complete.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import type {
|
||||
ChatMessage,
|
||||
CompleteWithToolsOptions,
|
||||
CompleteWithToolsResult,
|
||||
ParsedToolCall,
|
||||
StreamCallbacks,
|
||||
TokenUsage,
|
||||
} from "./client.js";
|
||||
import { parseUsage } from "./client.js";
|
||||
|
||||
type ToolCallAccumulator = Map<
|
||||
number,
|
||||
{ id?: string; name?: string; arguments: string }
|
||||
>;
|
||||
|
||||
function applyToolCallDelta(
|
||||
acc: ToolCallAccumulator,
|
||||
raw: unknown,
|
||||
): void {
|
||||
if (!Array.isArray(raw)) return;
|
||||
for (const item of raw) {
|
||||
if (!item || typeof item !== "object") continue;
|
||||
const row = item as Record<string, unknown>;
|
||||
const index = Number(row.index ?? 0);
|
||||
const entry = acc.get(index) ?? { arguments: "" };
|
||||
if (typeof row.id === "string") entry.id = row.id;
|
||||
const fn = row.function;
|
||||
if (fn && typeof fn === "object") {
|
||||
const f = fn as Record<string, unknown>;
|
||||
if (typeof f.name === "string") entry.name = f.name;
|
||||
if (typeof f.arguments === "string") entry.arguments += f.arguments;
|
||||
}
|
||||
acc.set(index, entry);
|
||||
}
|
||||
}
|
||||
|
||||
function toolCallsFromAccumulator(acc: ToolCallAccumulator): ParsedToolCall[] {
|
||||
const out: ParsedToolCall[] = [];
|
||||
for (const [, entry] of [...acc.entries()].sort((a, b) => a[0] - b[0])) {
|
||||
const name = entry.name?.trim();
|
||||
const id = entry.id?.trim();
|
||||
if (!name || !id) continue;
|
||||
out.push({ id, name, arguments: entry.arguments || "{}" });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 解析 OpenAI 兼容 SSE 流,累积 reasoning / content / tool_calls */
|
||||
export async function consumeOpenAiToolStream(
|
||||
body: ReadableStream<Uint8Array>,
|
||||
callbacks: StreamCallbacks,
|
||||
): Promise<{
|
||||
content: string | null;
|
||||
reasoning: string;
|
||||
toolCalls: ParsedToolCall[];
|
||||
usage?: TokenUsage;
|
||||
model?: string;
|
||||
}> {
|
||||
const reader = body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
let content = "";
|
||||
let reasoning = "";
|
||||
const toolAcc: ToolCallAccumulator = new Map();
|
||||
let usage: TokenUsage | undefined;
|
||||
let model: string | undefined;
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split("\n");
|
||||
buffer = lines.pop() ?? "";
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed.startsWith("data:")) continue;
|
||||
const payload = trimmed.slice(5).trim();
|
||||
if (!payload || payload === "[DONE]") continue;
|
||||
|
||||
let parsed: Record<string, unknown>;
|
||||
try {
|
||||
parsed = JSON.parse(payload) as Record<string, unknown>;
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof parsed.model === "string") model = parsed.model;
|
||||
const u = parseUsage(parsed.usage);
|
||||
if (u) usage = u;
|
||||
|
||||
const choice = (parsed.choices as unknown[])?.[0];
|
||||
if (!choice || typeof choice !== "object") continue;
|
||||
const delta = (choice as Record<string, unknown>).delta;
|
||||
if (!delta || typeof delta !== "object") continue;
|
||||
const d = delta as Record<string, unknown>;
|
||||
|
||||
if (typeof d.reasoning_content === "string" && d.reasoning_content) {
|
||||
reasoning += d.reasoning_content;
|
||||
callbacks.onReasoningDelta?.(d.reasoning_content);
|
||||
}
|
||||
if (typeof d.content === "string" && d.content) {
|
||||
content += d.content;
|
||||
callbacks.onContentDelta?.(d.content);
|
||||
}
|
||||
if (d.tool_calls) applyToolCallDelta(toolAcc, d.tool_calls);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content: content.trim() || null,
|
||||
reasoning: reasoning.trim(),
|
||||
toolCalls: toolCallsFromAccumulator(toolAcc),
|
||||
usage,
|
||||
model,
|
||||
};
|
||||
}
|
||||
|
||||
export type StreamableLlm = {
|
||||
completeWithToolsStream?(
|
||||
messages: ChatMessage[],
|
||||
options: CompleteWithToolsOptions,
|
||||
callbacks: StreamCallbacks,
|
||||
): Promise<CompleteWithToolsResult>;
|
||||
};
|
||||
|
||||
export function supportsToolStream(llm: unknown): llm is StreamableLlm {
|
||||
return (
|
||||
typeof llm === "object" &&
|
||||
llm != null &&
|
||||
typeof (llm as StreamableLlm).completeWithToolsStream === "function"
|
||||
);
|
||||
}
|
||||
|
||||
export type ContentStreamableLlm = {
|
||||
completeStream?(
|
||||
messages: ChatMessage[],
|
||||
options?: import("./client.js").CompleteOptions,
|
||||
callbacks?: StreamCallbacks,
|
||||
): Promise<import("./client.js").CompleteResult>;
|
||||
};
|
||||
|
||||
export function supportsContentStream(llm: unknown): llm is ContentStreamableLlm {
|
||||
return (
|
||||
typeof llm === "object" &&
|
||||
llm != null &&
|
||||
typeof (llm as ContentStreamableLlm).completeStream === "function"
|
||||
);
|
||||
}
|
||||
@@ -4,12 +4,17 @@ import type {
|
||||
CompleteWithToolsOptions,
|
||||
CompleteWithToolsResult,
|
||||
LlmProvider,
|
||||
StreamCallbacks,
|
||||
} from "./client.js";
|
||||
import {
|
||||
recordTokenUsage,
|
||||
toMessageTokenUsage,
|
||||
type MessageTokenUsage,
|
||||
} from "../stats/token-store.js";
|
||||
import {
|
||||
buildContextTrace,
|
||||
type LlmContextTrace,
|
||||
} from "../types/context-trace.js";
|
||||
|
||||
export type LlmTrackingContext = {
|
||||
sessionId?: string;
|
||||
@@ -19,8 +24,23 @@ export type LlmTrackingContext = {
|
||||
/** Set after each LLM call; consumed when the next system chat message is created */
|
||||
pendingUsage?: MessageTokenUsage;
|
||||
pendingReasoning?: string;
|
||||
/** 全量请求上下文;挂到下一条「结果向」系统消息 */
|
||||
pendingContextTrace?: LlmContextTrace;
|
||||
};
|
||||
|
||||
function capturePendingTrace(
|
||||
ctx: LlmTrackingContext,
|
||||
messages: Array<{ role: string; content: string }>,
|
||||
caller: string | undefined,
|
||||
model?: string,
|
||||
): void {
|
||||
ctx.pendingContextTrace = buildContextTrace({
|
||||
caller: caller ?? "unknown",
|
||||
messages,
|
||||
model,
|
||||
});
|
||||
}
|
||||
|
||||
export class TokenTrackingProvider implements LlmProvider {
|
||||
constructor(
|
||||
private readonly inner: LlmProvider,
|
||||
@@ -33,6 +53,53 @@ export class TokenTrackingProvider implements LlmProvider {
|
||||
): Promise<CompleteResult> {
|
||||
const result = await this.inner.complete(messages, options);
|
||||
const ctx = this.getContext();
|
||||
capturePendingTrace(ctx, messages, options?.caller, result.model);
|
||||
if (result.usage) {
|
||||
const record = recordTokenUsage({
|
||||
sessionId: ctx.sessionId,
|
||||
bookId: ctx.bookId,
|
||||
bookTitle: ctx.bookTitle,
|
||||
orchestratorId: ctx.orchestratorId,
|
||||
caller: options?.caller ?? "unknown",
|
||||
model: result.model ?? "unknown",
|
||||
promptTokens: result.usage.promptTokens,
|
||||
completionTokens: result.usage.completionTokens,
|
||||
totalTokens: result.usage.totalTokens,
|
||||
cachedTokens: result.usage.cachedTokens,
|
||||
cacheMissTokens: result.usage.cacheMissTokens,
|
||||
});
|
||||
ctx.pendingUsage = toMessageTokenUsage(record);
|
||||
}
|
||||
if (result.reasoning?.trim()) {
|
||||
ctx.pendingReasoning = result.reasoning.trim();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async completeStream(
|
||||
messages: Parameters<LlmProvider["complete"]>[0],
|
||||
options?: CompleteOptions,
|
||||
callbacks: StreamCallbacks = {},
|
||||
): Promise<CompleteResult> {
|
||||
const inner = this.inner;
|
||||
if (!inner.completeStream) {
|
||||
const result = await this.complete(messages, options);
|
||||
if (result.reasoning) callbacks.onReasoningDelta?.(result.reasoning);
|
||||
if (result.content) callbacks.onContentDelta?.(result.content);
|
||||
return result;
|
||||
}
|
||||
let reasoningBuf = "";
|
||||
const result = await inner.completeStream(messages, options, {
|
||||
onReasoningDelta: (delta) => {
|
||||
reasoningBuf += delta;
|
||||
const ctx = this.getContext();
|
||||
ctx.pendingReasoning = reasoningBuf;
|
||||
callbacks.onReasoningDelta?.(delta);
|
||||
},
|
||||
onContentDelta: callbacks.onContentDelta,
|
||||
});
|
||||
const ctx = this.getContext();
|
||||
capturePendingTrace(ctx, messages, options?.caller, result.model);
|
||||
if (result.usage) {
|
||||
const record = recordTokenUsage({
|
||||
sessionId: ctx.sessionId,
|
||||
@@ -61,6 +128,50 @@ export class TokenTrackingProvider implements LlmProvider {
|
||||
): Promise<CompleteWithToolsResult> {
|
||||
const result = await this.inner.completeWithTools(messages, options);
|
||||
const ctx = this.getContext();
|
||||
capturePendingTrace(ctx, messages, options.caller, result.model);
|
||||
if (result.usage) {
|
||||
const record = recordTokenUsage({
|
||||
sessionId: ctx.sessionId,
|
||||
bookId: ctx.bookId,
|
||||
bookTitle: ctx.bookTitle,
|
||||
orchestratorId: ctx.orchestratorId,
|
||||
caller: options.caller ?? "unknown",
|
||||
model: result.model ?? "unknown",
|
||||
promptTokens: result.usage.promptTokens,
|
||||
completionTokens: result.usage.completionTokens,
|
||||
totalTokens: result.usage.totalTokens,
|
||||
cachedTokens: result.usage.cachedTokens,
|
||||
cacheMissTokens: result.usage.cacheMissTokens,
|
||||
});
|
||||
ctx.pendingUsage = toMessageTokenUsage(record);
|
||||
}
|
||||
if (result.reasoning?.trim()) {
|
||||
ctx.pendingReasoning = result.reasoning.trim();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async completeWithToolsStream(
|
||||
messages: Parameters<LlmProvider["completeWithTools"]>[0],
|
||||
options: CompleteWithToolsOptions,
|
||||
callbacks: StreamCallbacks,
|
||||
): Promise<CompleteWithToolsResult> {
|
||||
const inner = this.inner;
|
||||
if (!inner.completeWithToolsStream) {
|
||||
return this.completeWithTools(messages, options);
|
||||
}
|
||||
let reasoningBuf = "";
|
||||
const result = await inner.completeWithToolsStream(messages, options, {
|
||||
onReasoningDelta: (delta) => {
|
||||
reasoningBuf += delta;
|
||||
const ctx = this.getContext();
|
||||
ctx.pendingReasoning = reasoningBuf;
|
||||
callbacks.onReasoningDelta?.(delta);
|
||||
},
|
||||
onContentDelta: callbacks.onContentDelta,
|
||||
});
|
||||
const ctx = this.getContext();
|
||||
capturePendingTrace(ctx, messages, options.caller, result.model);
|
||||
if (result.usage) {
|
||||
const record = recordTokenUsage({
|
||||
sessionId: ctx.sessionId,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { LlmProvider } from "../llm/client.js";
|
||||
import { normalizeQuestions } from "../skills/question-protocol.js";
|
||||
import type { BlackboardTagIndex } from "../types/blackboard.js";
|
||||
import type { MainAgentDecision, RuntimeSession } from "../types/runtime.js";
|
||||
import { runMainAgentToolLoop, type ToolLoopHandlers } from "./tool-loop.js";
|
||||
@@ -22,27 +23,46 @@ function buildMainAgentSystemPrompt(
|
||||
? workers.map((w) => `- ${w.id}:${w.description}`).join("\n")
|
||||
: "- (当前 skill 未加载 worker 列表)";
|
||||
|
||||
return `你是写作系统的总管(Main Agent)。你的职责是调度 worker,而不是直接创作正文。
|
||||
return `你是写作系统的导演(Main Agent)。你的职责是调度演员(worker),而不是直接创作正文。
|
||||
|
||||
规则:
|
||||
1. 你不能直接生成小说/文章正文。
|
||||
2. 你不能修改运行状态;statePatchAllowed 必须始终为 false。
|
||||
3. 你只能建议下一步动作:ask_user、run_worker、create_temp_worker、review_blackboard、finish。
|
||||
4. 当信息不足时,使用 ask_user 向用户提问。
|
||||
4. 当信息不足时,使用 ask_user:assessment 是主内容(完备度评价);questions 挂在其下且用户可跳过;一次 1~2 题。
|
||||
5. 当需要执行任务时,使用 run_worker,只指定 workerId。不要指定 inputTags 或 outputTags——Runtime 从 Worker Skill 读取。
|
||||
6. requiresApproval 表示运行 worker 前是否需要用户确认。代笔模式通常为 true。
|
||||
7. run_worker 可选 workerContext:{ "roleId": "A" },用于 role-decide 等指定当前决策角色(Runtime 写入 世界.当前角色.id)。
|
||||
8. 你不能把未验收内容当作事实。
|
||||
9. 向用户提问是 worker 的能力(ask_user tool),不是独立 worker。总管只在调度层提问。
|
||||
9. 向用户提问是 worker 的能力(ask_user tool),不是独立 worker。导演只在调度层提问。
|
||||
10. blackboardIndex 只有 tag 索引,不含正文 content。
|
||||
|
||||
## 调度思维
|
||||
用需求正推:「用户需要 [具体体验/能力] → 调用 [worker/skill] 来 [生成/补充/调整] [什么],以便更好满足用户。」
|
||||
禁止否定式路由:「某模式 / 背景形态 → 不需要某步骤 / 跳过某 worker」。
|
||||
|
||||
反例(禁止):「背景为单一角色,无需世界构建」「不是规则怪谈,跳过 write-rules」「默认上世界模拟套件」
|
||||
正例:「用户已选世界模拟器导演且要网恋对话 → design-flow 排出近期增量步骤(可调味、可后补)→ 用户认可后反复 design-step;需要再补规则/实例 → 再 design-flow 追加同能力 → 收成后若需开局 → opening-generator;用户手动进 play」
|
||||
|
||||
当前 skill 可用 worker(workerId 必须与下列 id 完全一致):
|
||||
${workerLines}
|
||||
|
||||
## design 优先顺序
|
||||
- 尚无「设计.创作流程」→ **design-flow**(近期 horizon;status=open;未选导演则先请用户选)
|
||||
- 流程已验收且还有未完成步骤 → **design-step**(一次一步;能力由程序注入)
|
||||
- 已列步骤都验收但流程仍 **status=open** → 再 **design-flow**(追加反复步或 closed)
|
||||
- 禁止调度已废弃的 design-core / design-fixed / design-worker / design-refine
|
||||
- 终稿已 accept 且可用 opening-generator、尚无开场产物 → opening-generator
|
||||
- 进 play 由用户手动决定
|
||||
- **禁止**替用户猜测或改选导演
|
||||
- **禁止**一次编排排死全程固定 DAG
|
||||
|
||||
输出必须是 JSON 对象,字段:
|
||||
{
|
||||
"action": "ask_user" | "run_worker" | "create_temp_worker" | "review_blackboard" | "finish",
|
||||
"reason": "string",
|
||||
"assessment": "string | null",
|
||||
"questions": [{ "id": "q1", "prompt": "…", "options": [{ "label": "建议示范…" }] }] | null,
|
||||
"workerId": "string | null",
|
||||
"requiresApproval": boolean,
|
||||
"workerContext": { "roleId": "string" } | null
|
||||
@@ -109,7 +129,7 @@ export function buildMainAgentUserPrompt(context: MainAgentContext): string {
|
||||
blackboardIndex,
|
||||
availableWorkers,
|
||||
instruction:
|
||||
"根据当前状态决定下一步。若 collecting_input 或 revision_requested,优先理解用户最新输入。若 planning 且已有足够信息,建议 run_worker(仅 workerId)。",
|
||||
"根据当前状态决定下一步。尚无设计.创作流程 → design-flow;有未完成步骤 → design-step;steps 做完但 status=open → 再 design-flow;禁止旧 design-core/fixed/worker/refine。已 accept 且需开局 → opening-generator。进 play 由用户手动。",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
@@ -160,10 +180,20 @@ export function parseMainAgentDecision(raw: string): MainAgentDecision {
|
||||
if (roleId) workerContext = { roleId };
|
||||
}
|
||||
|
||||
const assessmentRaw =
|
||||
typeof obj.assessment === "string"
|
||||
? obj.assessment.trim()
|
||||
: typeof obj.message === "string"
|
||||
? obj.message.trim()
|
||||
: "";
|
||||
const questions = normalizeQuestions(obj.questions);
|
||||
|
||||
return {
|
||||
id: randomUUID(),
|
||||
action,
|
||||
reason,
|
||||
assessment: assessmentRaw || undefined,
|
||||
questions: questions.length ? questions : undefined,
|
||||
workerId,
|
||||
workerContext,
|
||||
requiresApproval: Boolean(obj.requiresApproval),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ChatMessage, LlmProvider, ParsedToolCall } from "../llm/client.js";
|
||||
import type { ChatMessage, LlmProvider, ParsedToolCall, StreamCallbacks } from "../llm/client.js";
|
||||
import { supportsToolStream } from "../llm/stream-complete.js";
|
||||
import { toolCallToDecision, validateLoopToolCall } from "../runtime/tool-registry.js";
|
||||
import type { MainAgentDecision } from "../types/runtime.js";
|
||||
import { isMainAgentTerminalTool } from "../types/tools.js";
|
||||
@@ -17,6 +18,10 @@ export type ToolLoopHandlers = {
|
||||
outputTags: string[];
|
||||
}>;
|
||||
onToolCall?: (name: string, detail: string) => void;
|
||||
/** 思维链 / reasoning 流式增量(供 UI 实时展示) */
|
||||
onThinkingDelta?: (delta: string) => void;
|
||||
/** 一轮 LLM 调用结束后的完整思考文本 */
|
||||
onThinkingDone?: (text: string) => void;
|
||||
};
|
||||
|
||||
export type ToolLoopResult = {
|
||||
@@ -37,20 +42,84 @@ function buildToolLoopSystemPrompt(
|
||||
|
||||
return `你是写作系统的总管(Main Agent)。你在 running 相位通过 **tool call** 推进流程。
|
||||
|
||||
规则:
|
||||
## 能力边界
|
||||
1. 你不能直接生成小说/文章正文。
|
||||
2. 你不能修改运行状态。
|
||||
3. 先用 read_blackboard / list_workers / list_artifacts 收集信息,再决定下一步。
|
||||
4. 终止动作只能用 tool:ask_user、run_worker、review_blackboard、finish。
|
||||
- ask_user:**主内容是 assessment(内容完备度评价)**;questions 挂在其下且可被用户跳过。
|
||||
· assessment(必填):给用户看的评价正文。以【核心体验】为首;按需列维度(内容/人物关系/感官/背景规则/意义主题…),不必凑齐。每维写完备度%、已知、待探;待探用【】标互斥或可组合方向。
|
||||
· questions(必填数组,语义可选):只针对 assessment「待探」里想确认的点;一次 1~2 题。用户可不答、直接让你基于现有信息继续。prompt=明确题干;options=2~4 条**建议示范**;required 默认 false。
|
||||
· reason:调度短句,正推,勿塞长文。
|
||||
· 能推断就别问;已写在 用户.需求 里的不要重复问。
|
||||
5. run_worker 只传 workerId;inputTags/outputTags 由 Runtime 从 Worker Skill 读取。
|
||||
6. requiresApproval=true 时 run_worker 需用户确认后再执行。
|
||||
7. run_worker 可选 roleId,用于 role-decide 等指定当前决策角色。
|
||||
8. 不能把未验收产物当作已定事实。
|
||||
7. 不能把未验收产物当作已定事实。
|
||||
|
||||
## 调度思维(必须遵守)
|
||||
用 **需求正推**,禁止 **模式否定式** 表述。
|
||||
|
||||
**要这样思考(正推):**
|
||||
「用户需要 [具体体验/能力/产物] → 因此调用 [worker/skill] 来 [生成/补充/调整] [什么],以便更好满足用户。」
|
||||
|
||||
**不要这样思考(反例):**
|
||||
「这是 xxx 模式 / 形态 → 不需要 xxx 步骤 / 跳过 xxx。」
|
||||
「背景为单一角色,无需世界构建。」
|
||||
「不是规则怪谈,跳过 write-rules。」
|
||||
|
||||
示例(好):
|
||||
- 「用户已选导演且要 1v1 网恋 → design-flow 排出近期增量步骤(可调味)→ 认可后反复 design-step;不够再追加。」
|
||||
- 「尚无设计.创作流程 → run_worker(design-flow);有未完成步骤 → design-step;steps 完但 status=open → 再 design-flow。」
|
||||
- 「Worker 集已 accept 且声明需要开局 → 调用 opening-generator 写开场白。」
|
||||
|
||||
示例(坏):
|
||||
- 「调度 design-core / design-fixed(已废弃)。」
|
||||
- 「一次 design-flow 排死全程固定 DAG。」
|
||||
- 「跳过流程编排直接写满 Worker 集。」
|
||||
- 「默认先上世界模拟全套再问用户要什么。」
|
||||
- 「替用户改选 / 猜测导演。」
|
||||
|
||||
在 reasoning / 可见 content 中请用 **正推句式** 写出调度理由;终止 tool 的 reason 字段同样用正推表述。
|
||||
|
||||
## design 阶段提示
|
||||
- 尚无「设计.创作流程」→ **优先 design-flow**(近期 horizon;status=open;未选则先请用户选)。
|
||||
- 流程已验收、还有未完成步骤 → **design-step**(一次一步)。
|
||||
- 已列步骤全验收但 **status=open** → 再 **design-flow**(追加生成规则/具体实例等,或设 closed)。
|
||||
- 禁止 run_worker(design-core|design-fixed|design-worker|design-refine)——已废弃。
|
||||
- 不要重复问用户「想做什么」——意图已在 用户.需求。
|
||||
- Worker 集已 accept,且可用 worker 含 opening-generator,尚无开场产物 → opening-generator。
|
||||
- 进 play 由用户手动决定。
|
||||
- 未声明开局、用户也未要求开场时,不要硬调 opening-generator。
|
||||
- **禁止**替用户猜测或改选导演。
|
||||
- **禁止**一次编排排死全程固定长链。
|
||||
当前 skill 可用 worker:
|
||||
${workerLines}
|
||||
|
||||
在信息足够前可多次调用 read_blackboard 等;一旦调用终止 tool,本轮循环结束。`;
|
||||
信息不足时可多次 read_blackboard;一旦调用终止 tool,本轮循环结束。`;
|
||||
}
|
||||
|
||||
async function completeToolsPreferStream(
|
||||
llm: LlmProvider,
|
||||
messages: ChatMessage[],
|
||||
callbacks: StreamCallbacks,
|
||||
): Promise<Awaited<ReturnType<LlmProvider["completeWithTools"]>>> {
|
||||
if (supportsToolStream(llm)) {
|
||||
return llm.completeWithToolsStream!(messages, {
|
||||
tools: MAIN_AGENT_TOOL_DEFINITIONS,
|
||||
caller: "main_agent",
|
||||
}, callbacks);
|
||||
}
|
||||
const result = await llm.completeWithTools(messages, {
|
||||
tools: MAIN_AGENT_TOOL_DEFINITIONS,
|
||||
caller: "main_agent",
|
||||
});
|
||||
if (result.reasoning) {
|
||||
callbacks.onReasoningDelta?.(result.reasoning);
|
||||
}
|
||||
if (result.content) {
|
||||
callbacks.onContentDelta?.(result.content);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function executeLoopTool(
|
||||
@@ -94,6 +163,16 @@ function assistantMessageFromToolCalls(
|
||||
};
|
||||
}
|
||||
|
||||
function emitThinkingDone(handlers: ToolLoopHandlers, parts: {
|
||||
reasoning?: string;
|
||||
content?: string | null;
|
||||
}): void {
|
||||
const reasoning = parts.reasoning?.trim() ?? "";
|
||||
const content = parts.content?.trim() ?? "";
|
||||
const combined = [reasoning, content].filter(Boolean).join("\n\n").trim();
|
||||
if (combined) handlers.onThinkingDone?.(combined);
|
||||
}
|
||||
|
||||
/**
|
||||
* 总管 tool loop:在 running 相位内可多轮调用 read_blackboard 等,
|
||||
* 直到调用终止 tool 并返回 MainAgentDecision。
|
||||
@@ -112,12 +191,14 @@ export async function runMainAgentToolLoop(
|
||||
];
|
||||
|
||||
const toolTrace: string[] = [];
|
||||
const streamCallbacks: StreamCallbacks = {
|
||||
onReasoningDelta: (delta) => handlers.onThinkingDelta?.(delta),
|
||||
onContentDelta: (delta) => handlers.onThinkingDelta?.(delta),
|
||||
};
|
||||
|
||||
for (let iteration = 1; iteration <= MAX_TOOL_LOOP_ITERATIONS; iteration++) {
|
||||
const result = await llm.completeWithTools(messages, {
|
||||
tools: MAIN_AGENT_TOOL_DEFINITIONS,
|
||||
caller: "main_agent",
|
||||
});
|
||||
const result = await completeToolsPreferStream(llm, messages, streamCallbacks);
|
||||
emitThinkingDone(handlers, result);
|
||||
|
||||
if (result.toolCalls.length === 0) {
|
||||
if (result.content?.trim()) {
|
||||
|
||||
@@ -50,14 +50,80 @@ export const MAIN_AGENT_TOOL_DEFINITIONS: ToolDefinition[] = [
|
||||
type: "function",
|
||||
function: {
|
||||
name: "ask_user",
|
||||
description: "信息不足时向用户提问,将暂停 agent 循环等待用户输入。",
|
||||
description:
|
||||
"向用户追问。assessment 是给用户看的主内容(完备度评价);questions 挂在其下,用户可跳过并请你基于现有信息继续。将暂停等待用户。",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
reason: { type: "string", description: "为何需要用户输入" },
|
||||
message: { type: "string", description: "展示给用户的问题或说明" },
|
||||
reason: {
|
||||
type: "string",
|
||||
description:
|
||||
"调度层短理由(正推:用户需要澄清 X → 因此追问)。勿把长文评价写这里。",
|
||||
},
|
||||
assessment: {
|
||||
type: "string",
|
||||
description:
|
||||
"给用户看的内容完备度评价(Markdown)。以【核心体验】为首要;按需列维度(不必凑齐),每维:完备度%、已知、待探。待探用【】标出互斥/可组合方向。评价只服务「用户想要什么」。",
|
||||
},
|
||||
message: {
|
||||
type: "string",
|
||||
description:
|
||||
"兼容旧字段:等同 assessment。优先传 assessment。",
|
||||
},
|
||||
questions: {
|
||||
type: "array",
|
||||
description:
|
||||
"挂在 assessment 下的可选追问(用户可不答)。基于「待探」出题,一次 1~2 题。prompt=明确选择题干;options=建议示范(可改写后采用)。",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
description: "稳定 id,如 q1 / stance",
|
||||
},
|
||||
prompt: {
|
||||
type: "string",
|
||||
description:
|
||||
"题干:引导用户选定一种详细体验倾向(例:「你更倾向于哪种皇帝的享受?」)。",
|
||||
},
|
||||
options: {
|
||||
type: "array",
|
||||
description:
|
||||
"2~4 个建议示范。label 写完整可采纳文案(可先一句场景钩子再点题),不是标签词。",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
description: "A/B/C…",
|
||||
},
|
||||
label: {
|
||||
type: "string",
|
||||
description:
|
||||
"建议正文:用户点选后可直接当答案,也可改写。例:「冰冷的、主宰一切的权力感——九重宫阙一言定生死…」",
|
||||
},
|
||||
editable: {
|
||||
type: "boolean",
|
||||
description: "默认 true:点文案可改写,点字母才选中",
|
||||
},
|
||||
},
|
||||
required: ["label"],
|
||||
},
|
||||
},
|
||||
allowOther: {
|
||||
type: "boolean",
|
||||
description: "默认 true:允许 Other 自拟",
|
||||
},
|
||||
required: {
|
||||
type: "boolean",
|
||||
description: "默认 false(可选追问);仅关键阻塞题才 true",
|
||||
},
|
||||
},
|
||||
required: ["prompt", "options"],
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ["reason"],
|
||||
required: ["reason", "assessment", "questions"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,46 +1,167 @@
|
||||
import type { PresetPackage, PresetPromptRole } from "../types/preset.js";
|
||||
import type {
|
||||
PresetPackage,
|
||||
PresetPromptEntry,
|
||||
PresetPromptRole,
|
||||
} from "../types/preset.js";
|
||||
import { savePreset } from "./store.js";
|
||||
|
||||
export type PresetEnabledEntryView = {
|
||||
export type PresetEntryView = {
|
||||
orderIndex: number;
|
||||
id: string;
|
||||
name: string;
|
||||
role: PresetPromptRole;
|
||||
marker: boolean;
|
||||
content: string;
|
||||
/** 实际会注入 LLM 请求(有非空 content) */
|
||||
/** prompt_order 与条目自身均启用 */
|
||||
enabled: boolean;
|
||||
orderEnabled: boolean;
|
||||
entryEnabled: boolean;
|
||||
/** 启用且有非空 content → 会注入 LLM */
|
||||
willInject: boolean;
|
||||
};
|
||||
|
||||
/** 按 prompt_order 列出所有启用条目及其内容 */
|
||||
export function listEnabledPresetEntries(
|
||||
preset: PresetPackage,
|
||||
): PresetEnabledEntryView[] {
|
||||
/** @deprecated 名称保留:现为全部条目;过滤启用请用 filter */
|
||||
export type PresetEnabledEntryView = PresetEntryView;
|
||||
|
||||
function sortOrder(preset: PresetPackage) {
|
||||
return [...preset.promptOrder].sort((a, b) => a.orderIndex - b.orderIndex);
|
||||
}
|
||||
|
||||
function toView(
|
||||
orderIndex: number,
|
||||
entry: PresetPromptEntry,
|
||||
orderEnabled: boolean,
|
||||
): PresetEntryView {
|
||||
const content = entry.content ?? "";
|
||||
const enabled = orderEnabled && entry.enabled;
|
||||
const trimmed = content.trim();
|
||||
return {
|
||||
orderIndex,
|
||||
id: entry.id,
|
||||
name: entry.name,
|
||||
role: entry.role,
|
||||
marker: entry.marker,
|
||||
content,
|
||||
enabled,
|
||||
orderEnabled,
|
||||
entryEnabled: entry.enabled,
|
||||
willInject: enabled && trimmed.length > 0,
|
||||
};
|
||||
}
|
||||
|
||||
/** 按 prompt_order 列出全部条目(含未启用),便于设置页编辑 */
|
||||
export function listAllPresetEntries(preset: PresetPackage): PresetEntryView[] {
|
||||
const promptById = new Map(preset.prompts.map((p) => [p.id, p]));
|
||||
const ordered = [...preset.promptOrder].sort(
|
||||
(a, b) => a.orderIndex - b.orderIndex,
|
||||
);
|
||||
const entries: PresetEnabledEntryView[] = [];
|
||||
const seen = new Set<string>();
|
||||
const entries: PresetEntryView[] = [];
|
||||
|
||||
for (const orderItem of ordered) {
|
||||
if (!orderItem.enabled) continue;
|
||||
for (const orderItem of sortOrder(preset)) {
|
||||
const entry = promptById.get(orderItem.promptId);
|
||||
if (!entry || !entry.enabled) continue;
|
||||
if (!entry) continue;
|
||||
seen.add(entry.id);
|
||||
entries.push(toView(orderItem.orderIndex, entry, orderItem.enabled));
|
||||
}
|
||||
|
||||
const content = entry.content.trim();
|
||||
entries.push({
|
||||
orderIndex: orderItem.orderIndex,
|
||||
id: entry.id,
|
||||
name: entry.name,
|
||||
role: entry.role,
|
||||
marker: entry.marker,
|
||||
content,
|
||||
willInject: content.length > 0,
|
||||
});
|
||||
// 未进 order 的条目附在末尾
|
||||
let nextIndex =
|
||||
entries.reduce((m, e) => Math.max(m, e.orderIndex), -1) + 1;
|
||||
for (const entry of preset.prompts) {
|
||||
if (seen.has(entry.id)) continue;
|
||||
entries.push(toView(nextIndex++, entry, false));
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
export function countInjectingEntries(entries: PresetEnabledEntryView[]): number {
|
||||
/** 仅启用中的条目(装配 / 旧 API 兼容) */
|
||||
export function listEnabledPresetEntries(
|
||||
preset: PresetPackage,
|
||||
): PresetEntryView[] {
|
||||
return listAllPresetEntries(preset).filter((e) => e.enabled);
|
||||
}
|
||||
|
||||
export function countInjectingEntries(
|
||||
entries: Array<{ willInject: boolean }>,
|
||||
): number {
|
||||
return entries.filter((e) => e.willInject).length;
|
||||
}
|
||||
|
||||
export function countEnabledEntries(
|
||||
entries: Array<{ enabled: boolean }>,
|
||||
): number {
|
||||
return entries.filter((e) => e.enabled).length;
|
||||
}
|
||||
|
||||
export type PresetEntryPatch = {
|
||||
id: string;
|
||||
enabled?: boolean;
|
||||
content?: string;
|
||||
name?: string;
|
||||
role?: PresetPromptRole;
|
||||
};
|
||||
|
||||
/**
|
||||
* 纯函数更新(不写盘)。启用状态同步 order + entry。
|
||||
*/
|
||||
export function applyPresetEntryPatches(
|
||||
preset: PresetPackage,
|
||||
patches: PresetEntryPatch[],
|
||||
): PresetPackage {
|
||||
if (!patches.length) return preset;
|
||||
|
||||
const prompts = preset.prompts.map((p) => ({ ...p }));
|
||||
const promptById = new Map(prompts.map((p) => [p.id, p]));
|
||||
let order = preset.promptOrder.map((o) => ({ ...o }));
|
||||
|
||||
for (const patch of patches) {
|
||||
const entry = promptById.get(patch.id);
|
||||
if (!entry) {
|
||||
throw new Error(`条目不存在: ${patch.id}`);
|
||||
}
|
||||
if (typeof patch.content === "string") {
|
||||
entry.content = patch.content;
|
||||
if (entry.content.trim()) entry.marker = false;
|
||||
}
|
||||
if (typeof patch.name === "string" && patch.name.trim()) {
|
||||
entry.name = patch.name.trim();
|
||||
}
|
||||
if (
|
||||
patch.role === "system" ||
|
||||
patch.role === "user" ||
|
||||
patch.role === "assistant"
|
||||
) {
|
||||
entry.role = patch.role;
|
||||
}
|
||||
if (typeof patch.enabled === "boolean") {
|
||||
entry.enabled = patch.enabled;
|
||||
const orderItem = order.find((o) => o.promptId === patch.id);
|
||||
if (orderItem) {
|
||||
orderItem.enabled = patch.enabled;
|
||||
} else if (patch.enabled) {
|
||||
const orderIndex =
|
||||
order.reduce((m, o) => Math.max(m, o.orderIndex), -1) + 1;
|
||||
order.push({
|
||||
promptId: patch.id,
|
||||
enabled: true,
|
||||
orderIndex,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...preset,
|
||||
prompts,
|
||||
promptOrder: order,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新一条或多条并落盘。
|
||||
*/
|
||||
export function patchPresetEntries(
|
||||
preset: PresetPackage,
|
||||
patches: PresetEntryPatch[],
|
||||
): PresetPackage {
|
||||
return savePreset(applyPresetEntryPatches(preset, patches));
|
||||
}
|
||||
|
||||
235
src/runtime/compress-after-worker.ts
Normal file
235
src/runtime/compress-after-worker.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
import type { Blackboard } from "../blackboard/blackboard.js";
|
||||
|
||||
/** 下一 worker / 总管可读的定稿摘要 tag */
|
||||
export const CONTEXT_BRIEF_TAG = "上下文.定稿摘要";
|
||||
|
||||
export type CompressWorkerResult = {
|
||||
archivedTags: string[];
|
||||
finals: Array<{ tag: string; chars: number; preview: string }>;
|
||||
briefText: string;
|
||||
};
|
||||
|
||||
const ALWAYS_ARCHIVE_AFTER_ACCEPT = ["用户.worker答复", "用户.修订说明"];
|
||||
|
||||
/**
|
||||
* Worker 产物经用户验收(或自动完工)后:
|
||||
* - 终产物打 final
|
||||
* - 过程/草稿 tag 归档(不再进入后续 worker 取数)
|
||||
* - 写入定稿摘要,供下一阶段「指点」用
|
||||
*/
|
||||
export function compressAfterWorkerAccept(params: {
|
||||
blackboard: Blackboard;
|
||||
workerId: string;
|
||||
outputTags: string[];
|
||||
summary?: string;
|
||||
/**
|
||||
* final = 终稿验收(归档草稿、写定稿摘要)
|
||||
* unit = 创作单位验收(只归档过程答复,保留 设计.worker集.草稿)
|
||||
*/
|
||||
mode?: "final" | "unit";
|
||||
}): CompressWorkerResult {
|
||||
const { blackboard, workerId, outputTags, summary } = params;
|
||||
const mode = params.mode ?? "final";
|
||||
const finals: CompressWorkerResult["finals"] = [];
|
||||
const archivedTags: string[] = [];
|
||||
|
||||
const uniqueOutputs = [...new Set(outputTags.map((t) => t.trim()).filter(Boolean))];
|
||||
|
||||
if (mode === "final") {
|
||||
for (const tag of uniqueOutputs) {
|
||||
const content = blackboard.getContentByTag(tag);
|
||||
if (content == null) continue;
|
||||
blackboard.write({
|
||||
tag,
|
||||
content,
|
||||
source: workerId,
|
||||
metadata: {
|
||||
role: "final",
|
||||
acceptedAt: new Date().toISOString(),
|
||||
workerId,
|
||||
},
|
||||
});
|
||||
finals.push({
|
||||
tag,
|
||||
chars: content.length,
|
||||
preview: previewText(content, 120),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 单位验收:草稿保持活跃,终产物列表仅作面板提示
|
||||
for (const tag of uniqueOutputs) {
|
||||
if (tag === "用户.需求" || tag === "创作.当前单位") continue;
|
||||
const content = blackboard.getContentByTag(tag);
|
||||
if (content == null) continue;
|
||||
finals.push({
|
||||
tag,
|
||||
chars: content.length,
|
||||
preview: previewText(content, 120),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const toArchive = new Set<string>(ALWAYS_ARCHIVE_AFTER_ACCEPT);
|
||||
|
||||
if (mode === "final") {
|
||||
if (blackboard.getContentByTag("设计.worker集")?.trim()) {
|
||||
toArchive.add("设计.worker集.草稿");
|
||||
}
|
||||
for (const tag of uniqueOutputs) {
|
||||
if (tag.endsWith(".草稿")) continue;
|
||||
const draft = `${tag}.草稿`;
|
||||
if (blackboard.getContentByTag(draft) != null) toArchive.add(draft);
|
||||
}
|
||||
}
|
||||
|
||||
for (const tag of toArchive) {
|
||||
if (uniqueOutputs.includes(tag) && mode === "final") continue;
|
||||
if (mode === "unit" && tag.endsWith(".草稿")) continue;
|
||||
const content = blackboard.getContentByTag(tag);
|
||||
if (content == null) continue;
|
||||
blackboard.write({
|
||||
tag,
|
||||
content: `(已压缩归档)原过程内容已折叠。定稿见:${
|
||||
finals.map((f) => f.tag).join("、") || "(无)"
|
||||
}\n\n---\n${previewText(content, 400)}`,
|
||||
source: "compress",
|
||||
metadata: {
|
||||
role: "archived",
|
||||
archivedAt: new Date().toISOString(),
|
||||
fromWorker: workerId,
|
||||
compressMode: mode,
|
||||
},
|
||||
});
|
||||
archivedTags.push(tag);
|
||||
}
|
||||
|
||||
const briefText =
|
||||
mode === "unit"
|
||||
? buildUnitBriefText({ workerId, summary, finals, archivedTags })
|
||||
: buildBriefText({
|
||||
workerId,
|
||||
summary,
|
||||
finals,
|
||||
archivedTags,
|
||||
});
|
||||
|
||||
if (mode === "final") {
|
||||
blackboard.write({
|
||||
tag: CONTEXT_BRIEF_TAG,
|
||||
content: briefText,
|
||||
source: "compress",
|
||||
metadata: { role: "final", workerId },
|
||||
});
|
||||
}
|
||||
|
||||
return { archivedTags, finals, briefText };
|
||||
}
|
||||
|
||||
function buildUnitBriefText(params: {
|
||||
workerId: string;
|
||||
summary?: string;
|
||||
finals: CompressWorkerResult["finals"];
|
||||
archivedTags: string[];
|
||||
}): string {
|
||||
return [
|
||||
`## 创作单位已验收(${params.workerId})`,
|
||||
"",
|
||||
params.summary?.trim() ? `摘要:${params.summary.trim()}` : "摘要:(无)",
|
||||
"",
|
||||
"草稿仍保留在 `设计.worker集.草稿`;下一单位继续增量,勿依赖已删的过程对话。",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function buildBriefText(params: {
|
||||
workerId: string;
|
||||
summary?: string;
|
||||
finals: CompressWorkerResult["finals"];
|
||||
archivedTags: string[];
|
||||
}): string {
|
||||
const lines = [
|
||||
`## 上一阶段定稿(${params.workerId})`,
|
||||
"",
|
||||
params.summary?.trim()
|
||||
? `摘要:${params.summary.trim()}`
|
||||
: "摘要:(无)",
|
||||
"",
|
||||
"### 保留的终产物 tag",
|
||||
];
|
||||
if (params.finals.length === 0) {
|
||||
lines.push("- (无)");
|
||||
} else {
|
||||
for (const f of params.finals) {
|
||||
lines.push(`- \`${f.tag}\`(${f.chars} 字)`);
|
||||
lines.push(` ${f.preview}`);
|
||||
}
|
||||
}
|
||||
if (params.archivedTags.length) {
|
||||
lines.push("", "### 已压缩的过程 tag");
|
||||
for (const t of params.archivedTags) {
|
||||
lines.push(`- \`${t}\`(归档,后续 worker 默认不读)`);
|
||||
}
|
||||
}
|
||||
lines.push(
|
||||
"",
|
||||
"下一 worker 应以以上终产物为准继续;勿依赖已归档的过程讨论。",
|
||||
);
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
export function previewText(content: string, max: number): string {
|
||||
const t = content.replace(/\s+/g, " ").trim();
|
||||
if (t.length <= max) return t;
|
||||
return `${t.slice(0, max)}…`;
|
||||
}
|
||||
|
||||
/** 供 UI:黑板可读条目(默认隐藏 archived 全文,只给索引) */
|
||||
export function buildBoardPanel(blackboard: Blackboard): {
|
||||
finals: Array<{ tag: string; source: string; preview: string; updatedAt: string }>;
|
||||
active: Array<{ tag: string; source: string; preview: string; updatedAt: string }>;
|
||||
archivedCount: number;
|
||||
brief?: string;
|
||||
} {
|
||||
const items = latestItemsByTag(blackboard.exportItems());
|
||||
const finals: Array<{
|
||||
tag: string;
|
||||
source: string;
|
||||
preview: string;
|
||||
updatedAt: string;
|
||||
}> = [];
|
||||
const active: typeof finals = [];
|
||||
let archivedCount = 0;
|
||||
let brief: string | undefined;
|
||||
|
||||
for (const item of items.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))) {
|
||||
const role = String(item.metadata?.role ?? "");
|
||||
if (item.tag === CONTEXT_BRIEF_TAG) {
|
||||
brief = item.content;
|
||||
continue;
|
||||
}
|
||||
if (role === "archived") {
|
||||
archivedCount += 1;
|
||||
continue;
|
||||
}
|
||||
const row = {
|
||||
tag: item.tag,
|
||||
source: item.source,
|
||||
preview: previewText(item.content, 160),
|
||||
updatedAt: item.updatedAt,
|
||||
};
|
||||
if (role === "final") finals.push(row);
|
||||
else active.push(row);
|
||||
}
|
||||
|
||||
return { finals, active, archivedCount, brief };
|
||||
}
|
||||
|
||||
function latestItemsByTag(
|
||||
items: import("../types/blackboard.js").BlackboardItem[],
|
||||
): import("../types/blackboard.js").BlackboardItem[] {
|
||||
const byTag = new Map<string, (typeof items)[0]>();
|
||||
for (const item of items) {
|
||||
const prev = byTag.get(item.tag);
|
||||
if (!prev || item.updatedAt > prev.updatedAt) byTag.set(item.tag, item);
|
||||
}
|
||||
return [...byTag.values()];
|
||||
}
|
||||
@@ -60,7 +60,7 @@ export class RuntimeOrchestrator {
|
||||
payload: {
|
||||
presetId: this.session.presetId,
|
||||
flowId: this.session.flowId,
|
||||
availableSkills: [{ name: "basic", description: "fallback", category: "novel" }],
|
||||
availableSkills: [{ name: "world-simulator", description: "fallback", category: "dialogue" }],
|
||||
},
|
||||
});
|
||||
return this.session;
|
||||
|
||||
@@ -19,22 +19,56 @@ import type {
|
||||
WaitingReason,
|
||||
} from "../types/runtime.js";
|
||||
import type { ActiveSkillSnapshot } from "../types/runtime.js";
|
||||
import { effectiveStartupMode } from "../config/default-orchestrator.js";
|
||||
import {
|
||||
buildIntakeProgress,
|
||||
buildIntakeFollowUpMessage,
|
||||
readIntakeValues,
|
||||
synthesizeDemandText,
|
||||
} from "../intake/intake.js";
|
||||
import { normalizeQuestions } from "../skills/question-protocol.js";
|
||||
import type { QuestionItem } from "../types/questions.js";
|
||||
|
||||
function nowIso(): string {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
/** 挂在产物下的追问一律可选(required=false),用户可直接 Accept */
|
||||
function asOptionalSidecarQuestions(
|
||||
raw: QuestionItem[] | string[] | undefined,
|
||||
): QuestionItem[] | undefined {
|
||||
const normalized = normalizeQuestions(raw ?? []);
|
||||
if (!normalized.length) return undefined;
|
||||
return normalized.map((q) => ({ ...q, required: false }));
|
||||
}
|
||||
|
||||
/** 更新 updatedAt 时间戳 */
|
||||
function touch(session: RuntimeSession): RuntimeSession {
|
||||
return { ...session, updatedAt: nowIso() };
|
||||
}
|
||||
|
||||
/** 用户每条输入写入 用户.最新输入;首句同时写入需求 tag */
|
||||
function applyUserTextToSlots(
|
||||
slots: Record<string, unknown>,
|
||||
text: string,
|
||||
demandKey: string,
|
||||
opts: { initial?: boolean },
|
||||
): void {
|
||||
if (!text) return;
|
||||
slots["用户.最新输入"] = text;
|
||||
if (opts.initial) {
|
||||
slots[demandKey] = text;
|
||||
slots.startupCompleted = true;
|
||||
return;
|
||||
}
|
||||
if (!slots.startupCompleted) {
|
||||
slots[demandKey] = text;
|
||||
slots.startupCompleted = true;
|
||||
return;
|
||||
}
|
||||
mergeSlotText(slots, demandKey, text);
|
||||
}
|
||||
|
||||
/** 将用户补充文本追加到 slot(用于合并多轮 ask_user / worker 答复到需求 tag) */
|
||||
function mergeSlotText(
|
||||
slots: Record<string, unknown>,
|
||||
@@ -136,6 +170,7 @@ export function getAllowedEvents(
|
||||
"user_accepted_artifact",
|
||||
"user_rejected_artifact",
|
||||
"user_requested_revision",
|
||||
"user_resolved_sidecar_questions",
|
||||
"main_agent_decision_created",
|
||||
"runtime_failed",
|
||||
];
|
||||
@@ -179,6 +214,11 @@ export function canApplyEvent(
|
||||
case "user_accepted_artifact":
|
||||
case "user_rejected_artifact":
|
||||
return reason?.kind === "review_artifact";
|
||||
case "user_resolved_sidecar_questions":
|
||||
return (
|
||||
reason?.kind === "review_artifact" &&
|
||||
Boolean(reason.questions?.length)
|
||||
);
|
||||
case "user_requested_revision":
|
||||
return (
|
||||
reason?.kind === "review_artifact" || reason?.kind === "approve_step"
|
||||
@@ -272,7 +312,7 @@ export function applyEvent(
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
// ── 启动:选 skill ──
|
||||
// ── 启动:默认 orchestrator 或 legacy 选包 ──
|
||||
case "session_started": {
|
||||
const next = appendHistory(
|
||||
{
|
||||
@@ -283,40 +323,22 @@ export function applyEvent(
|
||||
event,
|
||||
);
|
||||
const skills = event.payload.availableSkills;
|
||||
const initialSkill = event.payload.initialSkill;
|
||||
if (initialSkill) {
|
||||
return applySkillBinding(next, initialSkill, event);
|
||||
}
|
||||
return {
|
||||
session: waiting(next, {
|
||||
kind: "skill_selection",
|
||||
availableSkills: skills,
|
||||
}),
|
||||
effects: [
|
||||
{
|
||||
type: "emit_message",
|
||||
message: formatSkillSelectionPrompt(skills),
|
||||
},
|
||||
],
|
||||
effects: [{ type: "emit_message", message: formatSkillSelectionPrompt(skills) }],
|
||||
};
|
||||
}
|
||||
|
||||
case "skill_selected": {
|
||||
const { skill } = event.payload;
|
||||
const next = appendHistory(
|
||||
{
|
||||
...session,
|
||||
flowId: skill.defaultFlowId ?? session.flowId,
|
||||
slots: {
|
||||
...session.slots,
|
||||
activeSkill: skill,
|
||||
},
|
||||
},
|
||||
event,
|
||||
);
|
||||
return {
|
||||
session: waiting(next, {
|
||||
kind: "intake",
|
||||
prompt: skill.startupPrompt,
|
||||
}),
|
||||
effects: [{ type: "emit_message", message: skill.startupPrompt }],
|
||||
};
|
||||
return applySkillBinding(session, skill, event);
|
||||
}
|
||||
|
||||
case "user_confirmed_intake": {
|
||||
@@ -366,6 +388,21 @@ export function applyEvent(
|
||||
|
||||
if (session.waitingReason?.kind === "intake") {
|
||||
const skill = activeSkill as ActiveSkillSnapshot | undefined;
|
||||
|
||||
if (skill && effectiveStartupMode(skill) === "agent-first" && text) {
|
||||
const key = skill.startupTargetKey || "用户.需求";
|
||||
applyUserTextToSlots(slots, text, key, { initial: true });
|
||||
slots.lastUserInput = text;
|
||||
const next = appendHistory(
|
||||
{ ...session, slots, resumeContext: undefined },
|
||||
event,
|
||||
);
|
||||
return {
|
||||
session: touch(running(next)),
|
||||
effects: [{ type: "invoke_main_agent" }],
|
||||
};
|
||||
}
|
||||
|
||||
const intakeValues =
|
||||
event.payload.intakeValues ??
|
||||
readIntakeValues(session.slots);
|
||||
@@ -419,16 +456,17 @@ export function applyEvent(
|
||||
|
||||
if (session.waitingReason?.kind === "worker_questions") {
|
||||
slots["用户.worker答复"] = text;
|
||||
if (text) slots["用户.最新输入"] = text;
|
||||
if (demandKey && text) {
|
||||
mergeSlotText(slots, demandKey, text);
|
||||
}
|
||||
} else if (demandKey && session.waitingReason?.kind === "input" && text) {
|
||||
if (!session.slots.startupCompleted) {
|
||||
slots[demandKey] = text;
|
||||
slots.startupCompleted = true;
|
||||
} else {
|
||||
mergeSlotText(slots, demandKey, text);
|
||||
}
|
||||
} else if (session.waitingReason?.kind === "input" && text) {
|
||||
const key = demandKey || "用户.需求";
|
||||
applyUserTextToSlots(slots, text, key, {
|
||||
initial: !session.slots.startupCompleted,
|
||||
});
|
||||
} else if (text) {
|
||||
slots["用户.最新输入"] = text;
|
||||
}
|
||||
const next = appendHistory(
|
||||
{
|
||||
@@ -465,14 +503,44 @@ export function applyEvent(
|
||||
|
||||
switch (decision.action) {
|
||||
case "ask_user":
|
||||
case "review_blackboard":
|
||||
case "review_blackboard": {
|
||||
const questions = decision.questions?.length
|
||||
? decision.questions.map((q) => ({ ...q, required: false }))
|
||||
: undefined;
|
||||
const assessment =
|
||||
decision.action === "ask_user"
|
||||
? decision.assessment?.trim() || undefined
|
||||
: undefined;
|
||||
const effects: PhaseEffect[] = [];
|
||||
if (assessment) {
|
||||
effects.push({
|
||||
type: "emit_message",
|
||||
message: `[Agent] 内容评价:\n${assessment}`,
|
||||
});
|
||||
}
|
||||
if (questions?.length) {
|
||||
effects.push({
|
||||
type: "emit_message",
|
||||
message: `[Agent] 可选追问(可跳过):\n${questions.map((q) => `- ${q.prompt}`).join("\n")}`,
|
||||
});
|
||||
}
|
||||
const inputMessage = assessment
|
||||
? assessment
|
||||
: decision.action === "review_blackboard" || !questions?.length
|
||||
? decision.reason
|
||||
: undefined;
|
||||
return {
|
||||
session: waiting(
|
||||
{ ...next, pendingDecision: undefined },
|
||||
{ kind: "input", message: decision.reason },
|
||||
{
|
||||
kind: "input",
|
||||
message: inputMessage,
|
||||
questions,
|
||||
},
|
||||
),
|
||||
effects: [],
|
||||
effects,
|
||||
};
|
||||
}
|
||||
case "finish":
|
||||
return {
|
||||
session: touch({
|
||||
@@ -580,13 +648,12 @@ export function applyEvent(
|
||||
}
|
||||
|
||||
case "worker_needs_input": {
|
||||
const questions = event.payload.questions
|
||||
.map((q) => q.trim())
|
||||
.filter(Boolean);
|
||||
const normalized =
|
||||
questions.length > 0
|
||||
? questions
|
||||
: ["请补充当前步骤所需的信息(情境、参数或你的具体设想)。"];
|
||||
let normalized = normalizeQuestions(event.payload.questions);
|
||||
if (normalized.length === 0) {
|
||||
normalized = normalizeQuestions([
|
||||
"请补充当前步骤所需的信息(情境、参数或你的具体设想)。",
|
||||
]);
|
||||
}
|
||||
const ctx: ResumeContext = {
|
||||
workerId: event.payload.workerId,
|
||||
stepId: event.payload.stepId ?? session.currentStepId,
|
||||
@@ -608,7 +675,7 @@ export function applyEvent(
|
||||
effects: [
|
||||
{
|
||||
type: "emit_message",
|
||||
message: `[Worker] ${event.payload.workerId} 提问:\n${normalized.map((q) => `- ${q}`).join("\n")}`,
|
||||
message: `[Worker] ${event.payload.workerId} 提问:\n${normalized.map((q) => `- ${q.prompt}`).join("\n")}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -618,23 +685,64 @@ export function applyEvent(
|
||||
const result = handleWorkerCompleted(session, event);
|
||||
const mode = session.acceptanceMode ?? "user_confirmed";
|
||||
if (mode === "user_confirmed" && result.session.pendingArtifactId) {
|
||||
const sidecar = asOptionalSidecarQuestions(event.payload.questions);
|
||||
const effects = [...result.effects];
|
||||
if (sidecar?.length) {
|
||||
effects.push({
|
||||
type: "emit_message",
|
||||
message: `[Worker] 可选追问(可跳过,直接接受目前产物):\n${sidecar.map((q) => `- ${q.prompt}`).join("\n")}`,
|
||||
});
|
||||
}
|
||||
return {
|
||||
...result,
|
||||
effects,
|
||||
session: waiting(result.session, {
|
||||
kind: "review_artifact",
|
||||
artifactId: result.session.pendingArtifactId,
|
||||
questions: sidecar,
|
||||
}),
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
case "user_resolved_sidecar_questions": {
|
||||
const reason = session.waitingReason;
|
||||
if (reason?.kind !== "review_artifact") {
|
||||
return fail(session, "sidecar questions require review_artifact");
|
||||
}
|
||||
const answersText = event.payload.answersText?.trim();
|
||||
const slots: Record<string, unknown> = { ...session.slots };
|
||||
const effects: PhaseEffect[] = [];
|
||||
if (answersText) {
|
||||
slots["用户.worker答复"] = answersText;
|
||||
slots["用户.最新输入"] = answersText;
|
||||
effects.push({
|
||||
type: "emit_message",
|
||||
message: `[用户] 已补充可选追问(产物仍待验收)`,
|
||||
});
|
||||
}
|
||||
const next = appendHistory({ ...session, slots }, event);
|
||||
return {
|
||||
session: waiting(touch(next), {
|
||||
kind: "review_artifact",
|
||||
artifactId: reason.artifactId,
|
||||
}),
|
||||
effects,
|
||||
};
|
||||
}
|
||||
|
||||
// ── 产物验收 ──
|
||||
case "user_accepted_artifact": {
|
||||
const artifact = findArtifact(session, event.payload.artifactId);
|
||||
if (!artifact) {
|
||||
return fail(session, `Artifact not found: ${event.payload.artifactId}`);
|
||||
}
|
||||
const slots: Record<string, unknown> = { ...session.slots };
|
||||
// 仅终稿 tag「设计.worker集」表示完整 Worker 集验收;草稿单位验收不得开 play
|
||||
if (artifact.outputTags.some((tag) => tag === "设计.worker集")) {
|
||||
slots.designInstanceReady = true;
|
||||
}
|
||||
const next = appendHistory(
|
||||
updateArtifact(session, artifact.id, { status: "accepted" }),
|
||||
event,
|
||||
@@ -643,6 +751,7 @@ export function applyEvent(
|
||||
session: touch(
|
||||
running({
|
||||
...next,
|
||||
slots,
|
||||
pendingArtifactId: undefined,
|
||||
pendingDecision: undefined,
|
||||
}),
|
||||
@@ -791,3 +900,62 @@ function formatSkillSelectionPrompt(
|
||||
const lines = skills.map((s, i) => ` ${i + 1}. ${s.name} — ${s.description}`);
|
||||
return ["请选择创作 skill(输入 name 或编号):", ...lines].join("\n");
|
||||
}
|
||||
|
||||
/** agent-first:仅 UI 引导,等用户首句后再 invoke 总管 */
|
||||
function enterAwaitFirstInput(
|
||||
session: RuntimeSession,
|
||||
skill: ActiveSkillSnapshot,
|
||||
event: RuntimeEvent,
|
||||
): ApplyEventResult {
|
||||
const next = appendHistory(
|
||||
{
|
||||
...session,
|
||||
flowId: skill.defaultFlowId ?? session.flowId,
|
||||
slots: {
|
||||
...session.slots,
|
||||
activeSkill: skill,
|
||||
},
|
||||
},
|
||||
event,
|
||||
);
|
||||
return {
|
||||
session: waiting(next, { kind: "input" }),
|
||||
effects: [],
|
||||
};
|
||||
}
|
||||
|
||||
function enterIntakeWaiting(
|
||||
session: RuntimeSession,
|
||||
skill: ActiveSkillSnapshot,
|
||||
event: RuntimeEvent,
|
||||
): ApplyEventResult {
|
||||
const next = appendHistory(
|
||||
{
|
||||
...session,
|
||||
flowId: skill.defaultFlowId ?? session.flowId,
|
||||
slots: {
|
||||
...session.slots,
|
||||
activeSkill: skill,
|
||||
},
|
||||
},
|
||||
event,
|
||||
);
|
||||
return {
|
||||
session: waiting(next, {
|
||||
kind: "intake",
|
||||
prompt: skill.startupPrompt,
|
||||
}),
|
||||
effects: [{ type: "emit_message", message: skill.startupPrompt }],
|
||||
};
|
||||
}
|
||||
|
||||
function applySkillBinding(
|
||||
session: RuntimeSession,
|
||||
skill: ActiveSkillSnapshot,
|
||||
event: RuntimeEvent,
|
||||
): ApplyEventResult {
|
||||
if (effectiveStartupMode(skill) === "agent-first") {
|
||||
return enterAwaitFirstInput(session, skill, event);
|
||||
}
|
||||
return enterIntakeWaiting(session, skill, event);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { ParsedToolCall } from "../llm/client.js";
|
||||
import type { MainAgentDecision } from "../types/runtime.js";
|
||||
import { normalizeQuestions } from "../skills/question-protocol.js";
|
||||
import {
|
||||
isMainAgentLoopTool,
|
||||
isMainAgentTerminalTool,
|
||||
@@ -60,11 +61,15 @@ export function toolCallToDecision(call: ParsedToolCall): MainAgentDecision {
|
||||
switch (name as MainAgentToolName) {
|
||||
case "ask_user": {
|
||||
const reason = requireString(args, "reason");
|
||||
const message = optionalString(args, "message");
|
||||
const assessment =
|
||||
optionalString(args, "assessment") ?? optionalString(args, "message");
|
||||
const questions = normalizeQuestions(args.questions);
|
||||
return {
|
||||
id: randomUUID(),
|
||||
action: "ask_user",
|
||||
reason: message ? `${reason}\n${message}` : reason,
|
||||
reason,
|
||||
assessment: assessment || undefined,
|
||||
questions: questions.length ? questions : undefined,
|
||||
requiresApproval: false,
|
||||
statePatchAllowed: false,
|
||||
};
|
||||
|
||||
@@ -8,11 +8,18 @@ import {
|
||||
type LifecycleStage,
|
||||
type SkillCatalogEntry,
|
||||
} from "./skill-catalog.js";
|
||||
import {
|
||||
displayWorkerLabel,
|
||||
formatAgentDisplayTitle,
|
||||
formatWorkerDisplayTitle,
|
||||
} from "./display-labels.js";
|
||||
|
||||
export type AgentMessageKind =
|
||||
| "user_input"
|
||||
| "orchestrator_decision"
|
||||
| "orchestrator_thinking"
|
||||
| "orchestrator_prompt"
|
||||
| "orchestrator_assessment"
|
||||
| "agent_tool"
|
||||
| "worker_running"
|
||||
| "worker_output"
|
||||
@@ -77,18 +84,29 @@ export function classifyAgentMessage(text: string): EnrichedMessage {
|
||||
return {
|
||||
kind: "agent_tool",
|
||||
actor: "orchestrator",
|
||||
title: `Tool · ${name}`,
|
||||
title: `工具 · ${name}`,
|
||||
body: detail || "(无输出)",
|
||||
text: trimmed,
|
||||
};
|
||||
}
|
||||
|
||||
const agentThink = trimmed.match(/^\[总管 思考\]\s*\n?\n?([\s\S]*)$/);
|
||||
if (agentThink) {
|
||||
return {
|
||||
kind: "orchestrator_thinking",
|
||||
actor: "orchestrator",
|
||||
title: formatAgentDisplayTitle("思考"),
|
||||
body: agentThink[1]?.trim() || "(无内容)",
|
||||
text: trimmed,
|
||||
};
|
||||
}
|
||||
|
||||
const orchestrator = trimmed.match(/^\[总管\]\s*(\w+):\s*([\s\S]+)$/);
|
||||
if (orchestrator) {
|
||||
return {
|
||||
kind: "orchestrator_decision",
|
||||
actor: "orchestrator",
|
||||
title: `Agent · ${orchestrator[1]}`,
|
||||
title: formatAgentDisplayTitle(orchestrator[1]),
|
||||
body: orchestrator[2].trim(),
|
||||
text: trimmed,
|
||||
};
|
||||
@@ -99,8 +117,30 @@ export function classifyAgentMessage(text: string): EnrichedMessage {
|
||||
return {
|
||||
kind: "worker_running",
|
||||
actor: workerRunning[1],
|
||||
title: `Worker · ${workerRunning[1]}`,
|
||||
body: "正在调用模型执行 SKILL…",
|
||||
title: formatWorkerDisplayTitle(workerRunning[1], "running"),
|
||||
body: "正在调用模型执行…",
|
||||
text: trimmed,
|
||||
};
|
||||
}
|
||||
|
||||
const compressed = trimmed.match(/^\[上下文已压缩\]\s*([\s\S]+)$/);
|
||||
if (compressed) {
|
||||
return {
|
||||
kind: "system_info",
|
||||
actor: "system",
|
||||
title: "上下文已压缩",
|
||||
body: compressed[1].trim(),
|
||||
text: trimmed,
|
||||
};
|
||||
}
|
||||
|
||||
const unitAccepted = trimmed.match(/^\[创作单位已验收\]\s*([\s\S]+)$/);
|
||||
if (unitAccepted) {
|
||||
return {
|
||||
kind: "system_info",
|
||||
actor: "system",
|
||||
title: "创作单位已验收",
|
||||
body: unitAccepted[1].trim(),
|
||||
text: trimmed,
|
||||
};
|
||||
}
|
||||
@@ -110,7 +150,7 @@ export function classifyAgentMessage(text: string): EnrichedMessage {
|
||||
return {
|
||||
kind: "worker_output",
|
||||
actor: workerDone[1],
|
||||
title: `Worker · ${workerDone[1]} 产出`,
|
||||
title: formatWorkerDisplayTitle(workerDone[1], "output"),
|
||||
body: workerDone[2]?.trim() || "(无正文)",
|
||||
text: trimmed,
|
||||
};
|
||||
@@ -121,7 +161,7 @@ export function classifyAgentMessage(text: string): EnrichedMessage {
|
||||
return {
|
||||
kind: "worker_stub",
|
||||
actor: stub?.[1],
|
||||
title: `占位 Worker · ${stub?.[1] ?? "?"}`,
|
||||
title: formatWorkerDisplayTitle(stub?.[1], "stub"),
|
||||
body: trimmed,
|
||||
text: trimmed,
|
||||
};
|
||||
@@ -135,7 +175,7 @@ export function classifyAgentMessage(text: string): EnrichedMessage {
|
||||
return {
|
||||
kind: "worker_questions",
|
||||
actor: workerAskTagged[1],
|
||||
title: `Worker · ${workerAskTagged[1]} 提问`,
|
||||
title: formatWorkerDisplayTitle(workerAskTagged[1], "questions"),
|
||||
body,
|
||||
text: trimmed,
|
||||
};
|
||||
@@ -170,6 +210,44 @@ export function classifyAgentMessage(text: string): EnrichedMessage {
|
||||
};
|
||||
}
|
||||
|
||||
if (trimmed.startsWith("[Agent] 内容评价")) {
|
||||
return {
|
||||
kind: "orchestrator_assessment",
|
||||
actor: "orchestrator",
|
||||
title: "总管 · 内容评价",
|
||||
body: trimmed.replace(/^\[Agent\]\s*内容评价[::]\s*/, "").trim() || trimmed,
|
||||
text: trimmed,
|
||||
};
|
||||
}
|
||||
|
||||
if (trimmed.startsWith("[Agent] 提问") || trimmed.startsWith("[Agent] 可选追问")) {
|
||||
return {
|
||||
kind: "worker_questions",
|
||||
actor: "orchestrator",
|
||||
title: "总管 · 可选追问",
|
||||
body: formatWorkerQuestionBody(
|
||||
trimmed
|
||||
.replace(/^\[Agent\]\s*可选追问(可跳过)[::]\s*/, "")
|
||||
.replace(/^\[Agent\]\s*提问[::]\s*/, ""),
|
||||
),
|
||||
text: trimmed,
|
||||
};
|
||||
}
|
||||
|
||||
if (trimmed.match(/^\[Worker\]\s*可选追问/)) {
|
||||
return {
|
||||
kind: "worker_questions",
|
||||
title: "可选追问",
|
||||
body: formatWorkerQuestionBody(
|
||||
trimmed.replace(
|
||||
/^\[Worker\]\s*可选追问(可跳过,直接接受(?:目前)?产物)[::]\s*/,
|
||||
"",
|
||||
),
|
||||
),
|
||||
text: trimmed,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
trimmed.includes("请告诉我") ||
|
||||
trimmed.includes("启动询问") ||
|
||||
@@ -178,7 +256,7 @@ export function classifyAgentMessage(text: string): EnrichedMessage {
|
||||
return {
|
||||
kind: "orchestrator_prompt",
|
||||
actor: "orchestrator",
|
||||
title: "Agent · 启动询问",
|
||||
title: "总管 · 启动询问",
|
||||
body: trimmed,
|
||||
text: trimmed,
|
||||
};
|
||||
@@ -268,16 +346,37 @@ export function buildFocus(
|
||||
const detail =
|
||||
intake && intake.requiredTotal > 0
|
||||
? `必要项 ${intake.requiredFilled}/${intake.requiredTotal}`
|
||||
: "完成必要项后可进入实例化";
|
||||
: "总管将根据描述推理 Worker 集";
|
||||
return {
|
||||
actorType: "user",
|
||||
actorLabel: "你",
|
||||
action: "填写创作信息",
|
||||
action: "描述创作需求",
|
||||
detail,
|
||||
};
|
||||
}
|
||||
|
||||
if (reason?.kind === "input" && !session.slots.startupCompleted) {
|
||||
return {
|
||||
actorType: "user",
|
||||
actorLabel: "你",
|
||||
action: "描述创作需求",
|
||||
detail: "发送后总管将开始:创作 · 核心",
|
||||
};
|
||||
}
|
||||
|
||||
if (reason?.kind === "input") {
|
||||
if (reason.questions?.length) {
|
||||
const q = reason.questions
|
||||
.map((item) => item.prompt)
|
||||
.filter((s) => s?.trim())
|
||||
.join(";");
|
||||
return {
|
||||
actorType: "user",
|
||||
actorLabel: "你",
|
||||
action: "回答追问",
|
||||
detail: q.slice(0, 200) || reason.message,
|
||||
};
|
||||
}
|
||||
return {
|
||||
actorType: "user",
|
||||
actorLabel: "你",
|
||||
@@ -291,30 +390,37 @@ export function buildFocus(
|
||||
return {
|
||||
actorType: "orchestrator",
|
||||
actorId: "orchestrator",
|
||||
actorLabel: "Agent",
|
||||
action: `建议 invoke ${worker}`,
|
||||
actorLabel: "总管",
|
||||
action: `建议调用 ${displayWorkerLabel(worker)}`,
|
||||
detail: session.pendingDecision?.reason,
|
||||
};
|
||||
}
|
||||
|
||||
if (reason?.kind === "worker_questions") {
|
||||
const q = reason.questions?.filter((s) => s?.trim()).join(";") ?? "";
|
||||
const q =
|
||||
reason.questions
|
||||
?.map((item) => item.prompt)
|
||||
.filter((s) => s?.trim())
|
||||
.join(";") ?? "";
|
||||
return {
|
||||
actorType: "user",
|
||||
actorId: reason.workerId,
|
||||
actorLabel: "你",
|
||||
action: `回答 · ${reason.workerId}`,
|
||||
detail: q.slice(0, 200) || "请在下框补充",
|
||||
action: `回答 · ${displayWorkerLabel(reason.workerId)}`,
|
||||
detail: q.slice(0, 200) || "请在询问卡作答",
|
||||
};
|
||||
}
|
||||
|
||||
if (reason?.kind === "review_artifact") {
|
||||
const art = session.artifacts.find((a) => a.id === session.pendingArtifactId);
|
||||
const optionalQs = reason.questions?.length
|
||||
? `;另有 ${reason.questions.length} 道可选追问`
|
||||
: "";
|
||||
return {
|
||||
actorType: "user",
|
||||
actorLabel: "你",
|
||||
action: "验收产物",
|
||||
detail: art?.summary ?? art?.workerId,
|
||||
detail: `${art?.summary ?? displayWorkerLabel(art?.workerId) ?? ""}${optionalQs}`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -322,9 +428,9 @@ export function buildFocus(
|
||||
return {
|
||||
actorType: "worker",
|
||||
actorId: session.currentWorkerId,
|
||||
actorLabel: `Skill · ${session.currentWorkerId}`,
|
||||
actorLabel: displayWorkerLabel(session.currentWorkerId),
|
||||
action: "执行中",
|
||||
detail: "模型按 SKILL 产出…",
|
||||
detail: "模型正在产出…",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -332,9 +438,9 @@ export function buildFocus(
|
||||
return {
|
||||
actorType: "orchestrator",
|
||||
actorId: "orchestrator",
|
||||
actorLabel: "Agent",
|
||||
action: stage === "design" ? "设计 burst" : "游玩 burst",
|
||||
detail: "tool loop:读黑板 → 选 skill",
|
||||
actorLabel: "总管",
|
||||
action: stage === "design" ? "创作调度" : "游玩调度",
|
||||
detail: "读黑板 → 选下一步",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -342,7 +448,8 @@ export function buildFocus(
|
||||
return {
|
||||
actorType: "user",
|
||||
actorLabel: "你",
|
||||
action: "选择 Skill 包",
|
||||
action: "恢复中的旧会话",
|
||||
detail: "请发送任意消息继续,或联系维护者",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,190 +1,559 @@
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
|
||||
import { listSkills } from "../skills/loader.js";
|
||||
|
||||
import { createBook, deleteBook, getBook, listBooks, updateBook } from "../book/store.js";
|
||||
|
||||
import { sessionManager } from "./session-manager.js";
|
||||
|
||||
|
||||
|
||||
async function readBody(req: IncomingMessage): Promise<string> {
|
||||
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
for await (const chunk of req) {
|
||||
|
||||
chunks.push(chunk as Buffer);
|
||||
|
||||
}
|
||||
|
||||
return Buffer.concat(chunks).toString("utf8");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function json(res: ServerResponse, status: number, data: unknown): void {
|
||||
|
||||
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
||||
|
||||
res.end(JSON.stringify(data));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function handleBooksApi(
|
||||
|
||||
req: IncomingMessage,
|
||||
|
||||
res: ServerResponse,
|
||||
|
||||
pathname: string,
|
||||
|
||||
): Promise<boolean> {
|
||||
|
||||
if (pathname === "/api/skills" && req.method === "GET") {
|
||||
|
||||
const skills = await listSkills();
|
||||
|
||||
json(res, 200, {
|
||||
|
||||
skills: skills.map((s) => ({
|
||||
|
||||
id: s.name,
|
||||
|
||||
name: s.name,
|
||||
|
||||
description: s.description,
|
||||
|
||||
category: s.category,
|
||||
|
||||
bookKind: s.bookKind,
|
||||
|
||||
})),
|
||||
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (pathname === "/api/books" && req.method === "GET") {
|
||||
|
||||
json(res, 200, { books: listBooks() });
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (pathname === "/api/books" && req.method === "POST") {
|
||||
|
||||
const body = JSON.parse(await readBody(req)) as { title?: string };
|
||||
|
||||
const book = createBook({ title: body.title });
|
||||
|
||||
const session = await sessionManager.createForBook(book.id);
|
||||
|
||||
json(res, 201, { book, session });
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
const savesListMatch = pathname.match(/^\/api\/books\/([^/]+)\/saves$/);
|
||||
|
||||
if (savesListMatch) {
|
||||
|
||||
const bookId = decodeURIComponent(savesListMatch[1]);
|
||||
|
||||
const book = getBook(bookId);
|
||||
|
||||
if (!book) {
|
||||
|
||||
json(res, 404, { error: "Book 不存在" });
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (req.method === "GET") {
|
||||
|
||||
try {
|
||||
|
||||
const saves = sessionManager.listGameSnapshots(bookId);
|
||||
|
||||
json(res, 200, { saves });
|
||||
|
||||
} catch (err) {
|
||||
|
||||
json(res, 400, {
|
||||
|
||||
error: err instanceof Error ? err.message : "读取存档失败",
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (req.method === "POST") {
|
||||
|
||||
const body = JSON.parse(await readBody(req)) as {
|
||||
|
||||
label?: string;
|
||||
|
||||
note?: string;
|
||||
|
||||
sessionId?: string;
|
||||
|
||||
kind?: "instance" | "run";
|
||||
|
||||
};
|
||||
|
||||
const sessionId =
|
||||
|
||||
body.sessionId?.trim() ||
|
||||
|
||||
sessionManager.getActiveSessionForBook(bookId)?.id;
|
||||
|
||||
if (!sessionId) {
|
||||
|
||||
json(res, 400, { error: "当前作品没有活跃会话,无法存档" });
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
const kind = body.kind === "instance" ? "instance" : "run";
|
||||
|
||||
try {
|
||||
|
||||
const save = sessionManager.saveGameSnapshot(
|
||||
|
||||
sessionId,
|
||||
|
||||
body.label ?? "",
|
||||
|
||||
kind,
|
||||
|
||||
body.note,
|
||||
|
||||
);
|
||||
|
||||
json(res, 201, { save });
|
||||
|
||||
} catch (err) {
|
||||
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { listSkills } from "../skills/loader.js";
|
||||
import { createBook, deleteBook, duplicateBook, getBook, listBooks, updateBook } from "../book/store.js";
|
||||
import { sessionManager } from "./session-manager.js";
|
||||
|
||||
async function readBody(req: IncomingMessage): Promise<string> {
|
||||
const chunks: Buffer[] = [];
|
||||
for await (const chunk of req) {
|
||||
chunks.push(chunk as Buffer);
|
||||
}
|
||||
return Buffer.concat(chunks).toString("utf8");
|
||||
}
|
||||
|
||||
function json(res: ServerResponse, status: number, data: unknown): void {
|
||||
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
||||
res.end(JSON.stringify(data));
|
||||
}
|
||||
|
||||
type ModuleStatus = "ready" | "partial" | "skeleton" | "missing";
|
||||
|
||||
function moduleStatusFromSections(
|
||||
hasPrompt: boolean,
|
||||
present: string[],
|
||||
taskBody?: string,
|
||||
): ModuleStatus {
|
||||
if (!hasPrompt) return "missing";
|
||||
const hasTask = present.includes("task");
|
||||
const hasOutput = present.includes("output");
|
||||
if (!hasTask || !hasOutput) return "skeleton";
|
||||
const task = taskBody?.trim() ?? "";
|
||||
const stub =
|
||||
!task ||
|
||||
/待作者细写|待完善|(待/.test(task) ||
|
||||
task.length < 120;
|
||||
if (stub) return "skeleton";
|
||||
const depth = ["principles", "probe", "checklist", "examples"].filter((id) =>
|
||||
present.includes(id),
|
||||
).length;
|
||||
return depth >= 2 ? "ready" : "partial";
|
||||
}
|
||||
|
||||
async function loadModulesPayload(skillId: string): Promise<{
|
||||
skillPackId: string;
|
||||
modules: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
declaration: string;
|
||||
artifact: string;
|
||||
hasPrompt: boolean;
|
||||
sectionsPresent: string[];
|
||||
status: ModuleStatus;
|
||||
}>;
|
||||
}> {
|
||||
const { loadSkill } = await import("../skills/loader.js");
|
||||
const {
|
||||
loadModuleCatalog,
|
||||
loadModulePrompt,
|
||||
parseModulePromptSections,
|
||||
MODULE_SECTION_IDS,
|
||||
} = await import("../skills/creation-flow.js");
|
||||
const skill = await loadSkill(skillId);
|
||||
if (!skill.skillPackRoot) {
|
||||
return { skillPackId: skillId, modules: [] };
|
||||
}
|
||||
const catalog = await loadModuleCatalog(skill.skillPackRoot);
|
||||
const modules = [];
|
||||
for (const m of catalog?.modules ?? []) {
|
||||
const prompt = await loadModulePrompt(skill.skillPackRoot, m.id);
|
||||
const sections = prompt
|
||||
? parseModulePromptSections(prompt)
|
||||
: { raw: "", blocks: {} };
|
||||
const sectionsPresent = MODULE_SECTION_IDS.filter((id) =>
|
||||
Boolean(sections.blocks[id]?.trim()),
|
||||
);
|
||||
const hasPrompt = Boolean(prompt?.trim());
|
||||
const taskBody = sections.blocks.task?.trim();
|
||||
modules.push({
|
||||
id: m.id,
|
||||
name: m.name,
|
||||
declaration: m.declaration,
|
||||
artifact: m.artifact,
|
||||
hasPrompt,
|
||||
sectionsPresent: [...sectionsPresent],
|
||||
status: moduleStatusFromSections(hasPrompt, sectionsPresent, taskBody),
|
||||
});
|
||||
}
|
||||
return { skillPackId: skillId, modules };
|
||||
}
|
||||
|
||||
async function loadModuleDetailPayload(
|
||||
skillId: string,
|
||||
moduleId: string,
|
||||
): Promise<{
|
||||
skillPackId: string;
|
||||
id: string;
|
||||
name: string;
|
||||
declaration: string;
|
||||
artifact: string;
|
||||
hasPrompt: boolean;
|
||||
sectionsPresent: string[];
|
||||
status: ModuleStatus;
|
||||
sections: Record<string, string>;
|
||||
meta: Record<string, unknown> | null;
|
||||
raw: string;
|
||||
} | null> {
|
||||
const { loadSkill } = await import("../skills/loader.js");
|
||||
const {
|
||||
loadModuleCatalog,
|
||||
loadModulePrompt,
|
||||
parseModulePromptSections,
|
||||
MODULE_SECTION_IDS,
|
||||
} = await import("../skills/creation-flow.js");
|
||||
const { parse: parseYaml } = await import("yaml");
|
||||
const skill = await loadSkill(skillId);
|
||||
if (!skill.skillPackRoot) return null;
|
||||
const catalog = await loadModuleCatalog(skill.skillPackRoot);
|
||||
const entry = catalog?.modules.find((m) => m.id === moduleId) ?? null;
|
||||
if (!entry) return null;
|
||||
const prompt = await loadModulePrompt(skill.skillPackRoot, moduleId);
|
||||
const parsed = prompt
|
||||
? parseModulePromptSections(prompt)
|
||||
: { raw: "", blocks: {} };
|
||||
const sections: Record<string, string> = {};
|
||||
for (const id of MODULE_SECTION_IDS) {
|
||||
const body = parsed.blocks[id]?.trim();
|
||||
if (body) sections[id] = body;
|
||||
}
|
||||
const sectionsPresent = Object.keys(sections);
|
||||
const hasPrompt = Boolean(prompt?.trim());
|
||||
let meta: Record<string, unknown> | null = null;
|
||||
const metaRaw = sections.meta;
|
||||
if (metaRaw) {
|
||||
try {
|
||||
const doc = parseYaml(metaRaw);
|
||||
if (doc && typeof doc === "object" && !Array.isArray(doc)) {
|
||||
meta = doc as Record<string, unknown>;
|
||||
}
|
||||
} catch {
|
||||
meta = null;
|
||||
}
|
||||
}
|
||||
return {
|
||||
skillPackId: skillId,
|
||||
id: entry.id,
|
||||
name: entry.name,
|
||||
declaration: entry.declaration,
|
||||
artifact: entry.artifact,
|
||||
hasPrompt,
|
||||
sectionsPresent,
|
||||
status: moduleStatusFromSections(
|
||||
hasPrompt,
|
||||
sectionsPresent,
|
||||
sections.task,
|
||||
),
|
||||
sections,
|
||||
meta,
|
||||
raw: prompt ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
export async function handleBooksApi(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
pathname: string,
|
||||
): Promise<boolean> {
|
||||
if (pathname === "/api/skills" && req.method === "GET") {
|
||||
const skills = await listSkills();
|
||||
json(res, 200, {
|
||||
skills: skills.map((s) => ({
|
||||
id: s.name,
|
||||
name: s.name,
|
||||
description: s.description,
|
||||
category: s.category,
|
||||
bookKind: s.bookKind,
|
||||
})),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 新建作品:导演一层选型(内部 = recipes catalog) */
|
||||
if (pathname === "/api/directors" && req.method === "GET") {
|
||||
const { DEFAULT_ORCHESTRATOR_ID } = await import(
|
||||
"../config/default-orchestrator.js"
|
||||
);
|
||||
const skillId = DEFAULT_ORCHESTRATOR_ID;
|
||||
try {
|
||||
const { loadSkill } = await import("../skills/loader.js");
|
||||
const { loadRecipeCatalog } = await import("../skills/creation-flow.js");
|
||||
const skill = await loadSkill(skillId);
|
||||
if (!skill.skillPackRoot) {
|
||||
json(res, 200, { directors: [], skillPackId: skillId });
|
||||
return true;
|
||||
}
|
||||
const catalog = await loadRecipeCatalog(skill.skillPackRoot);
|
||||
json(res, 200, {
|
||||
skillPackId: skillId,
|
||||
directors: (catalog?.recipes ?? []).map((r) => ({
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
declaration: r.declaration,
|
||||
})),
|
||||
});
|
||||
} catch (err) {
|
||||
json(res, 404, {
|
||||
error: err instanceof Error ? err.message : "未找到导演列表",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const recipesMatch = pathname.match(/^\/api\/skills\/([^/]+)\/recipes$/);
|
||||
if (recipesMatch && req.method === "GET") {
|
||||
const skillId = decodeURIComponent(recipesMatch[1]);
|
||||
try {
|
||||
const { loadSkill } = await import("../skills/loader.js");
|
||||
const { loadRecipeCatalog } = await import("../skills/creation-flow.js");
|
||||
const skill = await loadSkill(skillId);
|
||||
if (!skill.skillPackRoot) {
|
||||
json(res, 200, { recipes: [] });
|
||||
return true;
|
||||
}
|
||||
const catalog = await loadRecipeCatalog(skill.skillPackRoot);
|
||||
json(res, 200, {
|
||||
recipes: (catalog?.recipes ?? []).map((r) => ({
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
declaration: r.declaration,
|
||||
})),
|
||||
});
|
||||
} catch (err) {
|
||||
json(res, 404, {
|
||||
error: err instanceof Error ? err.message : "未找到能力包",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 默认导演包的能力池(编排备选) */
|
||||
if (pathname === "/api/modules" && req.method === "GET") {
|
||||
const { DEFAULT_ORCHESTRATOR_ID } = await import(
|
||||
"../config/default-orchestrator.js"
|
||||
);
|
||||
try {
|
||||
const payload = await loadModulesPayload(DEFAULT_ORCHESTRATOR_ID);
|
||||
json(res, 200, payload);
|
||||
} catch (err) {
|
||||
json(res, 404, {
|
||||
error: err instanceof Error ? err.message : "未找到能力目录",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const moduleDetailMatch = pathname.match(/^\/api\/modules\/([^/]+)$/);
|
||||
if (moduleDetailMatch && req.method === "GET") {
|
||||
const { DEFAULT_ORCHESTRATOR_ID } = await import(
|
||||
"../config/default-orchestrator.js"
|
||||
);
|
||||
const moduleId = decodeURIComponent(moduleDetailMatch[1]);
|
||||
try {
|
||||
const detail = await loadModuleDetailPayload(
|
||||
DEFAULT_ORCHESTRATOR_ID,
|
||||
moduleId,
|
||||
);
|
||||
if (!detail) {
|
||||
json(res, 404, { error: `未找到能力:${moduleId}` });
|
||||
return true;
|
||||
}
|
||||
json(res, 200, detail);
|
||||
} catch (err) {
|
||||
json(res, 404, {
|
||||
error: err instanceof Error ? err.message : "未找到能力",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const skillModulesMatch = pathname.match(
|
||||
/^\/api\/skills\/([^/]+)\/modules$/,
|
||||
);
|
||||
if (skillModulesMatch && req.method === "GET") {
|
||||
const skillId = decodeURIComponent(skillModulesMatch[1]);
|
||||
try {
|
||||
const payload = await loadModulesPayload(skillId);
|
||||
json(res, 200, payload);
|
||||
} catch (err) {
|
||||
json(res, 404, {
|
||||
error: err instanceof Error ? err.message : "未找到能力目录",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const skillModuleDetailMatch = pathname.match(
|
||||
/^\/api\/skills\/([^/]+)\/modules\/([^/]+)$/,
|
||||
);
|
||||
if (skillModuleDetailMatch && req.method === "GET") {
|
||||
const skillId = decodeURIComponent(skillModuleDetailMatch[1]);
|
||||
const moduleId = decodeURIComponent(skillModuleDetailMatch[2]);
|
||||
try {
|
||||
const detail = await loadModuleDetailPayload(skillId, moduleId);
|
||||
if (!detail) {
|
||||
json(res, 404, { error: `未找到能力:${moduleId}` });
|
||||
return true;
|
||||
}
|
||||
json(res, 200, detail);
|
||||
} catch (err) {
|
||||
json(res, 404, {
|
||||
error: err instanceof Error ? err.message : "未找到能力",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pathname === "/api/books" && req.method === "GET") {
|
||||
json(res, 200, { books: listBooks() });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pathname === "/api/books" && req.method === "POST") {
|
||||
const body = JSON.parse(await readBody(req)) as {
|
||||
title?: string;
|
||||
/** 导演 Skill / 能力包 id(registry name) */
|
||||
orchestratorId?: string;
|
||||
/** 用户手动选定的导演 id(内部 recipe) */
|
||||
recipeId?: string;
|
||||
};
|
||||
const skills = await listSkills();
|
||||
const requested = body.orchestratorId?.trim();
|
||||
const director =
|
||||
(requested && skills.find((s) => s.name === requested)) ||
|
||||
skills.find((s) => s.name === "world-simulator") ||
|
||||
skills[0];
|
||||
if (!director) {
|
||||
json(res, 400, { error: "没有可用的导演 Skill(能力包)" });
|
||||
return true;
|
||||
}
|
||||
const book = createBook({ title: body.title });
|
||||
updateBook(book.id, {
|
||||
activeSkillId: director.name,
|
||||
activeSkillName: director.description?.split("\n")[0]?.slice(0, 80) || director.name,
|
||||
orchestratorId: director.name,
|
||||
orchestratorName: director.name,
|
||||
});
|
||||
const session = await sessionManager.createForBook(
|
||||
book.id,
|
||||
director.name,
|
||||
body.recipeId?.trim(),
|
||||
);
|
||||
json(res, 201, { book: getBook(book.id) ?? book, session });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pathname === "/api/books/batch" && req.method === "DELETE") {
|
||||
const body = JSON.parse(await readBody(req)) as { ids?: string[] };
|
||||
const ids = (body.ids ?? []).filter(Boolean);
|
||||
const deleted: string[] = [];
|
||||
for (const id of ids) {
|
||||
const book = getBook(id);
|
||||
if (!book) continue;
|
||||
sessionManager.dropBookSessions(id);
|
||||
deleteBook(id);
|
||||
deleted.push(id);
|
||||
}
|
||||
json(res, 200, { deleted });
|
||||
return true;
|
||||
}
|
||||
|
||||
const duplicateMatch = pathname.match(/^\/api\/books\/([^/]+)\/duplicate$/);
|
||||
if (duplicateMatch && req.method === "POST") {
|
||||
const bookId = decodeURIComponent(duplicateMatch[1]);
|
||||
const body = JSON.parse(await readBody(req)) as { title?: string };
|
||||
try {
|
||||
const book = duplicateBook(bookId, body.title);
|
||||
json(res, 201, { book });
|
||||
} catch (err) {
|
||||
json(res, 400, {
|
||||
error: err instanceof Error ? err.message : "复制失败",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const playNewMatch = pathname.match(/^\/api\/books\/([^/]+)\/play\/new$/);
|
||||
if (playNewMatch && req.method === "POST") {
|
||||
const bookId = decodeURIComponent(playNewMatch[1]);
|
||||
const book = getBook(bookId);
|
||||
if (!book) {
|
||||
json(res, 404, { error: "Book 不存在" });
|
||||
return true;
|
||||
}
|
||||
const active = sessionManager.getActiveSessionForBook(bookId);
|
||||
if (!active?.id) {
|
||||
json(res, 400, { error: "请先打开该作品" });
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
const session = await sessionManager.startNewPlayRun(active.id);
|
||||
json(res, 200, { session });
|
||||
} catch (err) {
|
||||
json(res, 400, {
|
||||
error: err instanceof Error ? err.message : "无法新建游玩",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const savesListMatch = pathname.match(/^\/api\/books\/([^/]+)\/saves$/);
|
||||
if (savesListMatch) {
|
||||
const bookId = decodeURIComponent(savesListMatch[1]);
|
||||
const book = getBook(bookId);
|
||||
if (!book) {
|
||||
json(res, 404, { error: "Book 不存在" });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (req.method === "GET") {
|
||||
try {
|
||||
const saves = sessionManager.listGameSnapshots(bookId).map((s) => ({
|
||||
...s,
|
||||
kindLabel: s.kind === "instance" ? "创作定稿" : "游玩进度",
|
||||
}));
|
||||
json(res, 200, { saves });
|
||||
} catch (err) {
|
||||
json(res, 400, {
|
||||
error: err instanceof Error ? err.message : "读取存档失败",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (req.method === "POST") {
|
||||
const body = JSON.parse(await readBody(req)) as {
|
||||
label?: string;
|
||||
note?: string;
|
||||
sessionId?: string;
|
||||
/** instance=创作定稿截面;run=游玩进度(默认) */
|
||||
kind?: "instance" | "run";
|
||||
};
|
||||
const sessionId =
|
||||
body.sessionId?.trim() ||
|
||||
sessionManager.getActiveSessionForBook(bookId)?.id;
|
||||
if (!sessionId) {
|
||||
json(res, 400, { error: "当前作品没有活跃会话,无法存档" });
|
||||
return true;
|
||||
}
|
||||
const kind = body.kind === "instance" ? "instance" : "run";
|
||||
try {
|
||||
const save =
|
||||
kind === "run"
|
||||
? sessionManager.savePlaySnapshot(sessionId, body.label ?? "", body.note)
|
||||
: sessionManager.saveGameSnapshot(
|
||||
sessionId,
|
||||
body.label ?? "",
|
||||
"instance",
|
||||
body.note,
|
||||
);
|
||||
json(res, 201, {
|
||||
save: {
|
||||
...save,
|
||||
kindLabel: save.kind === "instance" ? "创作定稿" : "游玩进度",
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
json(res, 400, {
|
||||
error: err instanceof Error ? err.message : "存档失败",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const saveItemMatch = pathname.match(
|
||||
/^\/api\/books\/([^/]+)\/saves\/([^/]+)(\/load)?$/,
|
||||
);
|
||||
if (saveItemMatch) {
|
||||
const bookId = decodeURIComponent(saveItemMatch[1]);
|
||||
const saveId = decodeURIComponent(saveItemMatch[2]);
|
||||
const isLoad = saveItemMatch[3] === "/load";
|
||||
|
||||
if (isLoad && req.method === "POST") {
|
||||
try {
|
||||
const session = await sessionManager.loadGameSnapshot(bookId, saveId);
|
||||
json(res, 200, { session });
|
||||
} catch (err) {
|
||||
json(res, 400, {
|
||||
error: err instanceof Error ? err.message : "读档失败",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (req.method === "DELETE") {
|
||||
try {
|
||||
sessionManager.deleteGameSnapshot(bookId, saveId);
|
||||
json(res, 200, { ok: true });
|
||||
} catch (err) {
|
||||
json(res, 404, {
|
||||
error: err instanceof Error ? err.message : "删除失败",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const bookMatch = pathname.match(/^\/api\/books\/([^/]+)(\/open)?$/);
|
||||
if (bookMatch) {
|
||||
const bookId = decodeURIComponent(bookMatch[1]);
|
||||
const isOpen = bookMatch[2] === "/open";
|
||||
|
||||
if (isOpen && req.method === "POST") {
|
||||
const book = getBook(bookId);
|
||||
if (!book) {
|
||||
json(res, 404, { error: "Book 不存在" });
|
||||
return true;
|
||||
}
|
||||
const session = await sessionManager.openBook(book.id);
|
||||
json(res, 200, { book, session });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (req.method === "GET") {
|
||||
const book = getBook(bookId);
|
||||
if (!book) {
|
||||
json(res, 404, { error: "Book 不存在" });
|
||||
return true;
|
||||
}
|
||||
json(res, 200, { book });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (req.method === "PUT") {
|
||||
const body = JSON.parse(await readBody(req)) as { title?: string };
|
||||
try {
|
||||
const book = updateBook(bookId, { title: body.title?.trim() });
|
||||
json(res, 200, { book });
|
||||
} catch (err) {
|
||||
json(res, 404, {
|
||||
error: err instanceof Error ? err.message : "更新失败",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (req.method === "DELETE") {
|
||||
const book = getBook(bookId);
|
||||
if (!book) {
|
||||
json(res, 404, { error: "Book 不存在" });
|
||||
return true;
|
||||
}
|
||||
sessionManager.dropBookSessions(bookId);
|
||||
deleteBook(bookId);
|
||||
json(res, 200, { ok: true });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
121
src/server/display-labels.ts
Normal file
121
src/server/display-labels.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 用户可见中文标签(与 docs/ui-glossary.md 同步)。
|
||||
* 内部 id 不变;仅展示层映射。
|
||||
*/
|
||||
|
||||
const STAGE_LABELS: Record<string, string> = {
|
||||
design: "创作",
|
||||
play: "游玩",
|
||||
done: "已完成",
|
||||
idle: "待命",
|
||||
running: "执行中",
|
||||
waiting_user: "等待你",
|
||||
error: "出错",
|
||||
};
|
||||
|
||||
const WORKER_LABELS: Record<string, string> = {
|
||||
"design-core": "创作 · 核心",
|
||||
"design-worker": "创作 · 演员规格",
|
||||
"design-fixed": "创作 · 固定上下文",
|
||||
"design-refine": "创作 · 细化与终稿",
|
||||
"design-intake": "创作 · 综合收口",
|
||||
"design-flow": "创作 · 流程编排",
|
||||
"design-step": "创作 · 执行步骤",
|
||||
"opening-generator": "开局 · 开场白",
|
||||
orchestrator: "导演",
|
||||
"agent-burst": "导演调度",
|
||||
narrator: "叙事转述",
|
||||
"role-decide": "角色决策",
|
||||
"world-simulator": "世界推演",
|
||||
"round-present": "回合呈现",
|
||||
outline: "大纲 / 细纲",
|
||||
"chapter-writer": "章节正文",
|
||||
};
|
||||
|
||||
const FIXED_TOPIC_LABELS: Record<string, string> = {
|
||||
"aesthetics-interaction": "美学纲领与交互范式",
|
||||
interaction: "交互范式",
|
||||
narrative_guide: "叙事指南",
|
||||
input_protocol: "输入协议",
|
||||
core_premise: "核心前提",
|
||||
aesthetics: "美学纲领",
|
||||
};
|
||||
|
||||
const PHASE_UNIT_LABELS: Record<string, string> = {
|
||||
core: "核心",
|
||||
refine: "细化",
|
||||
};
|
||||
|
||||
const SKILL_PACK_LABELS: Record<string, string> = {
|
||||
"world-simulator": "世界模拟器",
|
||||
"expand-assistant": "扩写助手",
|
||||
};
|
||||
|
||||
/** 生命周期 / 相位 */
|
||||
export function displayStageLabel(id: string | undefined | null): string {
|
||||
if (!id) return "";
|
||||
return STAGE_LABELS[id] ?? id;
|
||||
}
|
||||
|
||||
/** 导演选项 / skill pack 展示名 */
|
||||
export function displaySkillPackLabel(id: string | undefined | null): string {
|
||||
if (!id) return "";
|
||||
return SKILL_PACK_LABELS[id] ?? id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 演员 / 单位 / 能力 id → 用户可见名(不含动作后缀)。
|
||||
*/
|
||||
export function displayWorkerLabel(id: string | undefined | null): string {
|
||||
if (!id) return "";
|
||||
const trimmed = id.trim();
|
||||
if (!trimmed) return "";
|
||||
if (WORKER_LABELS[trimmed]) return WORKER_LABELS[trimmed]!;
|
||||
|
||||
if (trimmed.startsWith("phase:")) {
|
||||
const key = trimmed.slice("phase:".length);
|
||||
const name = PHASE_UNIT_LABELS[key] ?? key;
|
||||
return `单位 · ${name}`;
|
||||
}
|
||||
if (trimmed.startsWith("worker:")) {
|
||||
const ref = trimmed.slice("worker:".length);
|
||||
return `演员 · ${displayWorkerLabel(ref)}`;
|
||||
}
|
||||
if (trimmed.startsWith("fixed:")) {
|
||||
const topic = trimmed.slice("fixed:".length);
|
||||
return `能力 · ${FIXED_TOPIC_LABELS[topic] ?? topic}`;
|
||||
}
|
||||
if (trimmed.startsWith("resident:")) {
|
||||
return `常驻 · ${trimmed.slice("resident:".length)}`;
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
export type WorkerTitleAction = "running" | "output" | "questions" | "stub" | null;
|
||||
|
||||
/** 气泡 / 流式标题:中文名 + 可选动作 */
|
||||
export function formatWorkerDisplayTitle(
|
||||
workerId: string | undefined | null,
|
||||
action: WorkerTitleAction = null,
|
||||
): string {
|
||||
const base = displayWorkerLabel(workerId) || "演员";
|
||||
switch (action) {
|
||||
case "output":
|
||||
return `${base} · 产出`;
|
||||
case "questions":
|
||||
return `${base} · 提问`;
|
||||
case "stub":
|
||||
return `${base} · 占位`;
|
||||
case "running":
|
||||
return `${base} · 执行中`;
|
||||
default:
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
/** 导演(调度 Agent)相关标题 */
|
||||
export function formatAgentDisplayTitle(detail?: string): string {
|
||||
if (detail?.trim()) return `导演 · ${detail.trim()}`;
|
||||
return "导演";
|
||||
}
|
||||
223
src/server/message-branch.ts
Normal file
223
src/server/message-branch.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { BlackboardItem } from "../types/blackboard.js";
|
||||
import type { RuntimeSession } from "../types/runtime.js";
|
||||
|
||||
export type BranchableMessage = {
|
||||
id: string;
|
||||
role: "system" | "user";
|
||||
text: string;
|
||||
createdAt: string;
|
||||
kind?: string;
|
||||
actor?: string;
|
||||
title?: string;
|
||||
body?: string;
|
||||
branchGroupId?: string;
|
||||
branchIndex?: number;
|
||||
branchTotal?: number;
|
||||
};
|
||||
|
||||
export type SessionCheckpoint = {
|
||||
runtimeSession: RuntimeSession;
|
||||
blackboardItems: BlackboardItem[];
|
||||
};
|
||||
|
||||
export type MessageBranchVariant = {
|
||||
/** 从分支点起的消息链(含分支点消息本身) */
|
||||
messages: BranchableMessage[];
|
||||
checkpoint: SessionCheckpoint;
|
||||
};
|
||||
|
||||
export type MessageBranch = {
|
||||
anchorIndex: number;
|
||||
groupId: string;
|
||||
variants: MessageBranchVariant[];
|
||||
activeIndex: number;
|
||||
};
|
||||
|
||||
export type MessageBranchState = {
|
||||
branches: Record<string, MessageBranch>;
|
||||
/** 下标 i = 追加 messages[i] 之前的 runtime 快照 */
|
||||
preMessageCheckpoints: Record<number, SessionCheckpoint>;
|
||||
};
|
||||
|
||||
export function createMessageBranchState(): MessageBranchState {
|
||||
return { branches: {}, preMessageCheckpoints: {} };
|
||||
}
|
||||
|
||||
export function cloneCheckpoint(cp: SessionCheckpoint): SessionCheckpoint {
|
||||
return {
|
||||
runtimeSession: structuredClone(cp.runtimeSession),
|
||||
blackboardItems: structuredClone(cp.blackboardItems),
|
||||
};
|
||||
}
|
||||
|
||||
export function recordPreMessageCheckpoint(
|
||||
state: MessageBranchState,
|
||||
index: number,
|
||||
checkpoint: SessionCheckpoint,
|
||||
): void {
|
||||
state.preMessageCheckpoints[index] = cloneCheckpoint(checkpoint);
|
||||
}
|
||||
|
||||
export function attachBranchMeta(messages: BranchableMessage[], groupId: string, activeIndex: number): void {
|
||||
const count = messages.filter((m) => m.branchGroupId === groupId).length || 1;
|
||||
const total = Math.max(count, activeIndex + 1);
|
||||
for (const m of messages) {
|
||||
if (m.branchGroupId === groupId && m.branchIndex === activeIndex) {
|
||||
m.branchTotal = total;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function syncBranchTotals(
|
||||
messages: BranchableMessage[],
|
||||
branch: MessageBranch,
|
||||
): void {
|
||||
const total = branch.variants.length;
|
||||
const active = branch.activeIndex;
|
||||
const head = branch.variants[active]?.messages[0];
|
||||
if (!head) return;
|
||||
for (const m of messages) {
|
||||
if (m.id === head.id || (m.branchGroupId === branch.groupId && m.branchIndex === active)) {
|
||||
m.branchGroupId = branch.groupId;
|
||||
m.branchIndex = active;
|
||||
m.branchTotal = total;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cloneMessages(msgs: BranchableMessage[]): BranchableMessage[] {
|
||||
return msgs.map((m) => ({ ...m }));
|
||||
}
|
||||
|
||||
export function ensureBranchForEdit(
|
||||
state: MessageBranchState,
|
||||
messages: BranchableMessage[],
|
||||
messageIndex: number,
|
||||
checkpoint: SessionCheckpoint,
|
||||
): MessageBranch {
|
||||
const msg = messages[messageIndex];
|
||||
const groupId = msg.branchGroupId ?? msg.id;
|
||||
let branch = state.branches[groupId];
|
||||
if (!branch) {
|
||||
branch = {
|
||||
anchorIndex: messageIndex,
|
||||
groupId,
|
||||
activeIndex: 0,
|
||||
variants: [
|
||||
{
|
||||
messages: cloneMessages(messages.slice(messageIndex)),
|
||||
checkpoint: cloneCheckpoint(checkpoint),
|
||||
},
|
||||
],
|
||||
};
|
||||
state.branches[groupId] = branch;
|
||||
for (const m of messages.slice(messageIndex)) {
|
||||
m.branchGroupId = groupId;
|
||||
m.branchIndex = 0;
|
||||
m.branchTotal = 1;
|
||||
}
|
||||
}
|
||||
return branch;
|
||||
}
|
||||
|
||||
export function ensureBranchForRefresh(
|
||||
state: MessageBranchState,
|
||||
messages: BranchableMessage[],
|
||||
messageIndex: number,
|
||||
checkpoint: SessionCheckpoint,
|
||||
): MessageBranch {
|
||||
const msg = messages[messageIndex];
|
||||
const groupId = msg.branchGroupId ?? msg.id;
|
||||
let branch = state.branches[groupId];
|
||||
if (!branch) {
|
||||
branch = {
|
||||
anchorIndex: messageIndex,
|
||||
groupId,
|
||||
activeIndex: 0,
|
||||
variants: [
|
||||
{
|
||||
messages: cloneMessages(messages.slice(messageIndex)),
|
||||
checkpoint: cloneCheckpoint(checkpoint),
|
||||
},
|
||||
],
|
||||
};
|
||||
state.branches[groupId] = branch;
|
||||
for (const m of messages.slice(messageIndex)) {
|
||||
m.branchGroupId = groupId;
|
||||
m.branchIndex = 0;
|
||||
m.branchTotal = 1;
|
||||
}
|
||||
}
|
||||
return branch;
|
||||
}
|
||||
|
||||
export function appendBranchVariant(
|
||||
branch: MessageBranch,
|
||||
headMessage: BranchableMessage,
|
||||
checkpoint: SessionCheckpoint,
|
||||
): number {
|
||||
const index = branch.variants.length;
|
||||
branch.variants.push({
|
||||
messages: [{ ...headMessage, branchGroupId: branch.groupId, branchIndex: index }],
|
||||
checkpoint: cloneCheckpoint(checkpoint),
|
||||
});
|
||||
branch.activeIndex = index;
|
||||
return index;
|
||||
}
|
||||
|
||||
export function updateActiveBranchVariant(
|
||||
branch: MessageBranch,
|
||||
tailMessages: BranchableMessage[],
|
||||
checkpoint: SessionCheckpoint,
|
||||
): void {
|
||||
const variant = branch.variants[branch.activeIndex];
|
||||
if (!variant) return;
|
||||
variant.messages = cloneMessages(tailMessages);
|
||||
variant.checkpoint = cloneCheckpoint(checkpoint);
|
||||
}
|
||||
|
||||
export function switchBranchVariant(
|
||||
state: MessageBranchState,
|
||||
messages: BranchableMessage[],
|
||||
groupId: string,
|
||||
delta: -1 | 1,
|
||||
): { messages: BranchableMessage[]; checkpoint: SessionCheckpoint } | null {
|
||||
const branch = state.branches[groupId];
|
||||
if (!branch) return null;
|
||||
const next = branch.activeIndex + delta;
|
||||
if (next < 0 || next >= branch.variants.length) return null;
|
||||
branch.activeIndex = next;
|
||||
const variant = branch.variants[next];
|
||||
const prefix = messages.slice(0, branch.anchorIndex);
|
||||
const merged = [...prefix, ...cloneMessages(variant.messages)];
|
||||
syncBranchTotals(merged, branch);
|
||||
return { messages: merged, checkpoint: cloneCheckpoint(variant.checkpoint) };
|
||||
}
|
||||
|
||||
export function createUserVariantMessage(text: string, groupId: string, branchIndex: number): BranchableMessage {
|
||||
return {
|
||||
id: randomUUID(),
|
||||
role: "user",
|
||||
text,
|
||||
createdAt: new Date().toISOString(),
|
||||
kind: "user_input",
|
||||
title: "你的输入",
|
||||
body: text,
|
||||
branchGroupId: groupId,
|
||||
branchIndex,
|
||||
};
|
||||
}
|
||||
|
||||
export function isRefreshableMessage(msg: BranchableMessage): boolean {
|
||||
if (msg.role === "user") return false;
|
||||
const kind = msg.kind ?? "system_info";
|
||||
return kind === "worker_questions" || kind === "worker_output";
|
||||
}
|
||||
|
||||
export function findPrecedingUserIndex(messages: BranchableMessage[], fromIndex: number): number {
|
||||
for (let i = fromIndex - 1; i >= 0; i--) {
|
||||
if (messages[i].role === "user") return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
162
src/server/prune-creation-messages.ts
Normal file
162
src/server/prune-creation-messages.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 创作过程对话策略(与黑板固定上下文正交):
|
||||
* - session.messages:全量保留,供用户浏览(编辑分支只暴露当前认可版本)
|
||||
* - 拼给 AI 的「创作.对话」:用户全量 + AI 永远只留最后一次(含未完成提问)
|
||||
*
|
||||
* 「隐藏 AI 历史」只作用于拼接,不删、不藏用户可见消息。
|
||||
*/
|
||||
|
||||
export type DialogueChatMessage = {
|
||||
id: string;
|
||||
role: "system" | "user";
|
||||
text: string;
|
||||
createdAt?: string;
|
||||
kind?: string;
|
||||
actor?: string;
|
||||
title?: string;
|
||||
body?: string;
|
||||
compressed?: boolean;
|
||||
};
|
||||
|
||||
/** @deprecated 用 DialogueChatMessage */
|
||||
export type PrunableChatMessage = DialogueChatMessage;
|
||||
|
||||
/** 写入黑板、注入 design worker 的对话 transcript tag */
|
||||
export const CREATION_DIALOGUE_TAG = "创作.对话";
|
||||
|
||||
const AI_CONTENT_KINDS = new Set([
|
||||
"worker_output",
|
||||
"worker_questions",
|
||||
"orchestrator_thinking",
|
||||
"orchestrator_decision",
|
||||
]);
|
||||
|
||||
const AI_EPHEMERAL_KINDS = new Set(["worker_running", "worker_stub", "agent_tool"]);
|
||||
|
||||
function isAiContent(m: DialogueChatMessage): boolean {
|
||||
const kind = m.kind ?? "";
|
||||
if (AI_CONTENT_KINDS.has(kind)) return true;
|
||||
if (m.role === "system" && kind.startsWith("worker_")) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function isAiEphemeral(m: DialogueChatMessage): boolean {
|
||||
return AI_EPHEMERAL_KINDS.has(m.kind ?? "");
|
||||
}
|
||||
|
||||
function isUserMessage(m: DialogueChatMessage): boolean {
|
||||
return m.role === "user" || m.kind === "user_input";
|
||||
}
|
||||
|
||||
/**
|
||||
* 从全量 messages 选出「拼给 AI」的视图:全部用户 + 最后一次 AI 内容。
|
||||
* 不修改原数组。
|
||||
*/
|
||||
export function selectCreationDialogueForAi<T extends DialogueChatMessage>(
|
||||
messages: readonly T[],
|
||||
): T[] {
|
||||
let lastAiIdx = -1;
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
if (isAiContent(messages[i]!)) {
|
||||
lastAiIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const out: T[] = [];
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const m = messages[i]!;
|
||||
if (m.compressed) continue;
|
||||
if (isUserMessage(m)) {
|
||||
out.push(m);
|
||||
continue;
|
||||
}
|
||||
if (isAiEphemeral(m)) continue;
|
||||
if (isAiContent(m) && i === lastAiIdx) out.push(m);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 勿再改写 session.messages;改用 selectCreationDialogueForAi / buildCreationDialogueTranscript。
|
||||
* 保留空操作兼容旧调用点。
|
||||
*/
|
||||
export function trimCreationDialogueMessages<T extends DialogueChatMessage>(
|
||||
_messages: T[],
|
||||
): { removedCount: number } {
|
||||
return { removedCount: 0 };
|
||||
}
|
||||
|
||||
function displayText(m: DialogueChatMessage): string {
|
||||
const body = (m.body ?? m.text ?? "").trim();
|
||||
return body || "(空)";
|
||||
}
|
||||
|
||||
/** 拼给 design worker 的对话前情(用户全量 + 最后一次 AI;不改 messages) */
|
||||
export function buildCreationDialogueTranscript(
|
||||
messages: readonly DialogueChatMessage[],
|
||||
): string {
|
||||
const selected = selectCreationDialogueForAi(messages);
|
||||
const lines: string[] = [
|
||||
"以下为创作过程对话(用户发言全部保留;AI 仅保留最后一次输出,含未完成提问)。",
|
||||
"",
|
||||
];
|
||||
for (const m of selected) {
|
||||
if (isUserMessage(m)) {
|
||||
lines.push(`### 用户`);
|
||||
lines.push(displayText(m));
|
||||
lines.push("");
|
||||
continue;
|
||||
}
|
||||
if (isAiContent(m)) {
|
||||
const who =
|
||||
m.kind === "worker_questions"
|
||||
? `AI · ${m.actor ?? "worker"} 提问`
|
||||
: m.kind === "worker_output"
|
||||
? `AI · ${m.actor ?? "worker"} 产出`
|
||||
: m.kind === "orchestrator_thinking"
|
||||
? "AI · 总管思考"
|
||||
: `AI · ${m.title ?? m.kind ?? "系统"}`;
|
||||
lines.push(`### ${who}`);
|
||||
lines.push(displayText(m));
|
||||
lines.push("");
|
||||
}
|
||||
}
|
||||
const text = lines.join("\n").trim();
|
||||
return text || "(尚无创作对话)";
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 创作验收后不再删 messages(用户可继续浏览)。保留空操作兼容。
|
||||
*/
|
||||
export function pruneCreationUnitMessages<T extends DialogueChatMessage>(
|
||||
_messages: T[],
|
||||
_workerId: string,
|
||||
_options: { afterCreatedAt?: string | null } = {},
|
||||
): { removedCount: number; productKept: boolean } {
|
||||
return { removedCount: 0, productKept: false };
|
||||
}
|
||||
|
||||
/** Run 验收:过程消息标 compressed(主 feed 隐藏),不删除。 */
|
||||
export function foldRunProcessMessages<T extends DialogueChatMessage>(
|
||||
messages: T[],
|
||||
workerId: string,
|
||||
): void {
|
||||
for (const m of messages) {
|
||||
if (m.compressed) continue;
|
||||
if (
|
||||
(m.kind === "worker_questions" || m.kind === "worker_running") &&
|
||||
(m.actor === workerId || (m.text ?? "").includes(workerId))
|
||||
) {
|
||||
m.compressed = true;
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
m.kind === "worker_output" &&
|
||||
m.actor === workerId &&
|
||||
!String(m.text ?? "").includes("[上下文已压缩]")
|
||||
) {
|
||||
m.compressed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user