Preserve partial creation replies on stream failure
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
kdletters
2026-05-05 11:31:50 +08:00
parent 100fee7e7a
commit 995661e7cc
299 changed files with 13805 additions and 1429 deletions

View File

@@ -13,12 +13,14 @@
重点补充RPG 创作与运行时脚本职责地图见 [RPG_CREATION_AND_RUNTIME_SCRIPT_RESPONSIBILITY_MAP_2026-04-28.md](./reference/RPG_CREATION_AND_RUNTIME_SCRIPT_RESPONSIBILITY_MAP_2026-04-28.md)。
- [埋点查询](./tracking/README.md):埋点原始事件与聚合投影的本地 SQL 查询。
- [运营查询](./operations/README.md):任务、领奖、钱包对账等后台核查查询。
- [PRD](./prd):产品需求与阶段计划;后台管理独立前端工程见 [ADMIN_WEB_CONSOLE_PRD_2026-04-30.md](./prd/ADMIN_WEB_CONSOLE_PRD_2026-04-30.md),新增 RPG 开场动画方案见 [AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md](./prd/AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md),新增抓大鹅 Match3D 玩法方案见 [AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md](./prd/AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md)。
- [PRD](./prd):产品需求与阶段计划;后台管理独立前端工程见 [ADMIN_WEB_CONSOLE_PRD_2026-04-30.md](./prd/ADMIN_WEB_CONSOLE_PRD_2026-04-30.md),新增 RPG 开场动画方案见 [AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md](./prd/AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md),新增抓大鹅 Match3D 玩法方案见 [AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md](./prd/AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md),方洞挑战创作、发布与试玩闭环见 [AI_NATIVE_SQUARE_HOLE_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-04.md](./prd/AI_NATIVE_SQUARE_HOLE_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-04.md)
生产部署切换到 systemd + Nginx + SpacetimeDB 自托管的总方案见 [PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md](./technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md),该文档也是当前生产 Jenkinsfile 的唯一入口。SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段迁移流程见 [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md)private 表迁移 JSON 导入导出、HTTP 413 分片导入和旧数据库迁移流水线经验见 [SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md](./technical/SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md) 与 [JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md](./technical/JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md);后台管理独立前端工程技术方案见 [ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md](./technical/ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md)。
SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段迁移流程见 [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md)。
创作 Agent 问答流式失败时保留已显示回复、并透出更具体上游错误的契约见 [CREATION_AGENT_STREAM_FAILURE_RETENTION_FIX_2026-05-05.md](./technical/CREATION_AGENT_STREAM_FAILURE_RETENTION_FIX_2026-05-05.md)。
## 推荐阅读顺序
1. 先看 [经验沉淀](./experience/README.md),快速建立这个项目的开发共识。

View File

@@ -0,0 +1,278 @@
# AI 原生方洞挑战玩法创作工具与玩法系统 PRD
更新时间:`2026-05-04`
## 0. 文档目的
这份 PRD 用于在当前平台内新增一条“方洞挑战”玩法类型,并先冻结它从入口占位到完整可创建闭环的产品边界。
本玩法来自参考视频中的核心反差:玩家看到不同形状的积木与多个洞口,会本能判断“形状应放入对应洞口”,但演示不断把不同形状都放进同一个方洞,形成“规则预期被打破”的喜剧张力。
本次不是简单复制视频内容,也不是只新增一个前端小游戏。正式版本需要把这种反直觉机制抽象成平台内可创作、可试玩、可发布的玩法类型。
---
## 1. 一句话定义
“方洞挑战”是一个反直觉形状分拣玩法:百梦主通过 Agent 设定形状主题、洞口规则、误导节奏和反差演出,系统生成一个移动端优先的单局挑战;玩家在限时内把连续出现的形状投入正确洞口,后端根据当前关卡的真实兼容规则裁决成功、失败和连击。
---
## 2. 当前接入级别
根据 `genarrative-play-type-integration` 的接入分级,本次落地到 **完整玩法闭环**
1. 新增玩法 ID`square-hole`
2. 新增展示名称:`方洞挑战`
3. 新增子标题:`反直觉形状分拣`
4. 在新建作品入口、创作类型弹层、结果页、运行态和作品架展示。
5. 支持 Agent 创作、草稿生成、结果页编辑、试玩、发布和公开运行。
6. 后端以 `server-rs + Axum + SpacetimeDB` 为真相源,前端只渲染快照与交互。
不允许把运行规则临时写成前端本地真相,也不复用 `server-node`、Express 或 PostgreSQL。
---
## 3. 产品定位
## 3.1 模板名称
1. 对外模板名称:`方洞挑战`
2. 对外子标题:`反直觉形状分拣`
3. 开发代号:`SquareHole`
4. 工程玩法域:`square-hole`
5. 后端模块命名预期:`square_hole`
## 3.2 核心乐趣
1. 玩家先根据形状轮廓做直觉判断。
2. 关卡通过洞口、提示、视觉遮挡和演出制造误导。
3. 真实规则可以是“方洞万能”“指定洞口万能”“颜色优先于形状”“本轮只看尺寸”等反直觉规则。
4. 玩家通过连续试探和反馈理解规则,形成短局重复挑战。
## 3.3 与现有玩法的区别
1. 不等同于拼图:不切图、不交换、不合并拼块。
2. 不等同于抓大鹅:不做三消备选栏,不做堆叠遮挡点击。
3. 不等同于大鱼吃小鱼:不做摇杆移动、吞噬、成长等级。
4. 不复用 RPG 的世界、角色、章节或剧情推进结构。
5. 运行态只保留“当前形状 + 洞口选择 + 后端裁决”,不在前端写正式规则真相。
---
## 4. 完整闭环目标
本次完整闭环必须补齐:
1. 平台创作入口选择“方洞挑战”。
2. Agent 对话收集玩法锚点。
3. 生成方洞挑战草稿。
4. 进入结果页编辑作品名、标签、封面、形状数量、反差规则和视觉主题。
5. 支持发布前试玩。
6. 发布作品。
7. 玩家从作品详情或广场进入运行态。
8. 后端初始化单局形状队列、洞口兼容规则和计分状态。
9. 玩家拖拽或点击形状投入洞口。
10. 后端裁决投入结果、连击、扣时、失败、胜利和成绩。
11. 前端只渲染后端快照与即时反馈,不承接正式规则真相。
---
## 5. 创作锚点设计
Agent 型创作版本至少收集下面 5 个锚点:
| 锚点 | 字段建议 | 用途 |
| --- | --- | --- |
| 主题外观 | `themePrompt` | 决定玩具、洞板、背景、形状材质和色彩风格。 |
| 反差规则 | `twistRule` | 决定“为什么不是按形状匹配”的真实判定规则。 |
| 洞口组 | `holeSet` | 决定本局出现的洞口种类、数量、位置和视觉误导强度。 |
| 形状队列 | `shapeSequence` | 决定连续出现的形状、颜色、大小和难度递增。 |
| 反馈节奏 | `feedbackRhythm` | 决定成功、错误、连击、惊讶和结算演出风格。 |
Agent 需要把玩家一句灵感收束为上述锚点,不允许逐项盘问低价值字段。
## 5.1 Agent AI 生成契约
方洞挑战的创作对话必须接入 `api-server` 的现有 LLM 能力,不能把用户输入解析成固定模板后直接写回会话。工程实现以 `state.llm_client()` 为唯一模型入口,通过方洞专属 Agent turn 生成回复与下一轮配置。
单轮模型输出必须是严格 JSON
```json
{
"replyText": "",
"progressPercent": 0,
"nextConfig": {
"themeText": "",
"twistRule": "",
"shapeCount": 12,
"difficulty": 4
}
}
```
落地约束:
1. `replyText` 是直接展示给百梦主的中文回复,不得出现 JSON、字段名、内部结构等说明。
2. `nextConfig` 必须是完整配置,不允许只输出 patch缺失字段只能由后端用当前会话配置兜底。
3. `shapeCount` 由后端限制在 `6``24``difficulty` 限制在 `1``10`
4. `quickFillRequested=true` 时,模型应直接补齐剩余配置,后端把 `progressPercent` 固定为 `100`
5. 模型不可用或结果无法解析时,接口返回明确错误,不允许用确定性模板伪装成 AI 回复。
6. 非流式消息接口和 SSE 流式消息接口都必须走同一套方洞 Agent turnSSE 只额外负责把 `replyText` 增量回传。
---
## 6. 运行规则设计
## 6.1 单局结构
1. 单局默认 `60` 秒。
2. 每局默认 `12` 个形状。
3. 洞口数量默认 `4``6` 个。
4. 玩家每次只能操作当前形状。
5. 正确投入后进入下一个形状。
6. 错误投入扣除时间或清空连击。
7. 全部形状完成即胜利。
8. 时间归零即失败。
## 6.2 真实兼容规则
首版可支持下面几类规则:
1. `shape_match`:形状轮廓匹配。
2. `square_hole_priority`:方洞兼容所有形状,其他洞口只作为误导。
3. `color_match`:颜色优先于形状。
4. `size_match`:尺寸优先于形状。
5. `round_prompt`:本轮按后端给出的短提示规则判定。
其中 `square_hole_priority` 是参考视频核心反差的首选默认规则。
## 6.3 前端表现
1. 竖屏优先,桌面端居中显示游戏台。
2. 当前形状位于屏幕下半区域,洞板位于上半区域。
3. 只显示必要状态:剩余时间、连击、当前进度。
4. 不默认展示长篇规则说明。
5. 错误反馈用短促动画、颜色闪烁和轻量文字状态,不堆解释。
6. 点击按钮弹出的配置或结算必须使用独立面板,不在当前面板下方展开。
---
## 7. 后端分层边界
完整实现时必须遵循当前 `server-rs + Axum + SpacetimeDB` 路线:
1. `server-rs/crates/module-square-hole`
- 纯领域规则、形状队列生成、兼容性裁决、分数计算。
- 不依赖 Axum、SpacetimeDB、OSS 或 LLM。
2. `server-rs/crates/shared-contracts`
- 暴露 Agent、作品、运行态 DTO。
3. `server-rs/crates/spacetime-module`
- 存储 session、message、work profile、runtime run。
- 表结构变化必须同步 `migration.rs` 与表目录。
4. `server-rs/crates/spacetime-client`
- 提供 api-server 调用 SpacetimeDB 的 typed facade。
5. `server-rs/crates/api-server`
- 暴露 `/api/creation/square-hole/*``/api/runtime/square-hole/*`
- 处理鉴权、错误 envelope、LLM turn 和 HTTP facade。
---
## 8. 数据模型
## 8.1 创作 session
1. `sessionId`
2. `ownerUserId`
3. `currentTurn`
4. `progressPercent`
5. `stage`
6. `config`
7. `draft`
8. `messages`
9. `lastAssistantReply`
10. `publishedProfileId`
## 8.2 结果页 work profile
1. `workId`
2. `profileId`
3. `ownerUserId`
4. `sourceSessionId`
5. `gameName`
6. `themeText`
7. `twistRule`
8. `summary`
9. `tags`
10. `coverImageSrc`
11. `shapeCount`
12. `difficulty`
13. `publicationStatus`
14. `playCount`
15. `updatedAt`
16. `publishedAt`
## 8.3 运行态 run snapshot
1. `runId`
2. `profileId`
3. `ownerUserId`
4. `status`
5. `snapshotVersion`
6. `startedAtMs`
7. `durationLimitMs`
8. `remainingMs`
9. `totalShapeCount`
10. `completedShapeCount`
11. `combo`
12. `bestCombo`
13. `score`
14. `ruleLabel`
15. `currentShape`
16. `holes`
17. `lastFeedback`
---
## 9. API 设计
## 9.1 创作接口
1. `POST /api/creation/square-hole/sessions`
2. `GET /api/creation/square-hole/sessions/{sessionId}`
3. `POST /api/creation/square-hole/sessions/{sessionId}/messages`
4. `POST /api/creation/square-hole/sessions/{sessionId}/messages/stream`
5. `POST /api/creation/square-hole/sessions/{sessionId}/actions`
6. `POST /api/creation/square-hole/sessions/{sessionId}/compile`
7. `GET /api/creation/square-hole/works`
8. `GET /api/creation/square-hole/works/{profileId}`
9. `PUT /api/creation/square-hole/works/{profileId}`
10. `POST /api/creation/square-hole/works/{profileId}/publish`
11. `DELETE /api/creation/square-hole/works/{profileId}`
## 9.2 运行接口
1. `GET /api/runtime/square-hole/gallery`
2. `GET /api/runtime/square-hole/gallery/{profileId}`
3. `POST /api/runtime/square-hole/works/{profileId}/runs`
4. `GET /api/runtime/square-hole/runs/{runId}`
5. `POST /api/runtime/square-hole/runs/{runId}/drop`
6. `POST /api/runtime/square-hole/runs/{runId}/stop`
7. `POST /api/runtime/square-hole/runs/{runId}/restart`
8. `POST /api/runtime/square-hole/runs/{runId}/time-up`
---
## 10. 验收标准
1. `src/config/newWorkEntryConfig.ts` 中存在 `square-hole` 类型且开放创建。
2. 新建作品入口和创作类型弹层能展示“方洞挑战”。
3. 能进入 `square-hole` Agent 工作台。
4. 能生成草稿并进入结果页。
5. 能编辑结果页并保存、发布。
6. 能从作品详情或广场进入运行态。
7. 能点击或拖拽当前形状投入洞口。
8. 后端裁决命中规则、连击、失败和胜利。
9. 刷新后可恢复作品与运行态快照。
10. `docs/technical/NEW_WORK_ENTRY_CONFIG_2026-05-01.md` 记录该入口开放状态。
11. 后端改动完成后必须执行 `npm run api-server:maincloud`,以 `GET /healthz` 返回 `200` 作为主云配置启动 smoke 通过标准,并在 smoke 后清理本次启动进程。

View File

@@ -0,0 +1,53 @@
# 创作 Agent 流式失败保留可见回复修复 2026-05-05
## 1. 问题
方洞挑战等轻量玩法复用 `usePlatformCreationAgentFlowController``creationAgentSse.ts` 消费 `reply_delta / session / error`。当上游 LLM 已经返回部分 `replyText`但后续因为超时、上游断流、SSE 解析或最终 JSON 解析失败而发送 `error` 事件时,前端会在 `finally` 里退出流式态。
旧 UI 只在 `isStreamingReply=true` 时展示临时 assistant 气泡,因此用户会先看到一段回答,然后回答突然消失,只剩错误提示。
## 2. 目标
1. 已经展示给用户的流式回复不能因为最终失败从聊天区消失。
2. SSE `error` 仍然终止本轮提交,并保留错误提示。
3. 后端错误不能只压成 `上游服务请求失败`,应优先把 LLM 流错误原因放到业务 `message`
4. 不修改 SpacetimeDB schema、消息表结构或玩法运行规则。
## 3. 前端契约
`readCreationAgentSessionFromSse()` 在收到 `reply_delta` 后再收到 `error` 时,必须先触发 `onUpdate(text)`,再抛出错误。调用方可以从最近一次可见文本中恢复 UI。
`usePlatformCreationAgentFlowController.submitMessage()` 的失败收尾规则:
1. 提交时仍先追加 optimistic user message。
2. 每次 `onUpdate` 同步更新 `streamingReplyText` 与最近可见回复引用。
3. 如果 `streamMessage()` 抛错且最近可见回复非空,把该文本追加为本地 assistant `warning` 消息。
4. 再设置 `error`,最后关闭 `isStreamingReply`
5. 成功拿到最终 session 时,以后端 session snapshot 为准,并清空最近可见回复。
这条本地 `warning` 消息只用于失败态 UI 保留,不代表该 assistant 消息已经写入 SpacetimeDB。
## 4. 后端契约
`creation_agent_llm_turn``LlmClient::stream_text()` 失败时,返回:
```text
<玩法 generation_failed 文案><LlmError Display>
```
同时写 `warn` 日志,便于结合 `logs/llm-raw` 定位上游原始输入输出。
方洞挑战 SSE 错误提取优先级:
1. `error.details.message`
2. `error.message`
3. 其它嵌套 JSON message
4. 原始 body 文本
5. 状态码兜底
## 5. 验收
1. `reply_delta` 后收到 `error` 时,测试应断言 `onUpdate` 已经收到可见文本。
2. 控制器测试应断言失败后本地消息列表包含 user 消息和 assistant warning 消息。
3. `cargo check -p api-server` 通过。
4. `npm run typecheck` 与编码检查通过。

View File

@@ -22,6 +22,7 @@
| 大鱼吃小鱼 | 否 | 是 | 功能仍保留,不在新建作品入口展示 |
| 拼图 | 是 | 是 | 点击后进入拼图 Agent 共创工作台 |
| 抓大鹅 | 是 | 是 | 点击后进入抓大鹅 Agent 共创工作台 |
| 方洞挑战 | 是 | 是 | 点击后进入方洞挑战 Agent 共创工作台,支持草稿、结果页、发布、试玩、作品架与广场 |
| AIRP | 是 | 否 | 保留入口,显示敬请期待 |
| 视觉小说 | 是 | 否 | 保留入口,显示敬请期待 |
@@ -31,3 +32,4 @@
2. 隐藏玩法不触发入口预加载,也不出现在新建作品入口中。
3. 未开放玩法点击态保持禁用,不应进入鉴权或创建会话链路。
4. 已开放玩法点击后必须进入对应创建链路;若用户未登录,先走登录保护。
5. 方洞挑战作品发布后应生成 `SH-` 作品号,并能从作品架、广场详情和试玩 runtime 回到同一作品详情。

View File

@@ -29,6 +29,7 @@ spacetime sql <db> "SELECT * FROM custom_world_gallery_entry"
| 世界创作 | `custom_world_profile`, `custom_world_session`, `custom_world_agent_session`, `custom_world_agent_message`, `custom_world_agent_operation`, `custom_world_draft_card`, `custom_world_gallery_entry` |
| 拼图 | `puzzle_agent_session`, `puzzle_agent_message`, `puzzle_work_profile`, `puzzle_event`, `puzzle_runtime_run`, `puzzle_leaderboard_entry` |
| 抓大鹅 Match3D | `match3d_agent_session`, `match3d_agent_message`, `match3d_work_profile`, `match3d_runtime_run` |
| 方洞挑战 | `square_hole_agent_session`, `square_hole_agent_message`, `square_hole_work_profile`, `square_hole_runtime_run` |
| 大鱼吃小鱼 | `big_fish_creation_session`, `big_fish_agent_message`, `big_fish_asset_slot`, `big_fish_event`, `big_fish_runtime_run` |
| 资产 | `asset_object`, `asset_entity_binding`, `asset_event` |
| AI 任务 | `ai_task`, `ai_task_stage`, `ai_text_chunk`, `ai_result_reference`, `ai_task_event` |
@@ -669,6 +670,53 @@ SELECT * FROM match3d_runtime_run WHERE owner_user_id = '<user_id>' ORDER BY upd
SELECT * FROM match3d_runtime_run WHERE profile_id = '<profile_id>';
```
## 方洞挑战表
### `square_hole_agent_session`
- 作用:方洞挑战创作 Agent 会话表,保存种子、配置 JSON、草稿 JSON 和发布 profile 指针。
- 结构:`session_id PK: String`, `owner_user_id: String`, `seed_text: String`, `current_turn: u32`, `progress_percent: u32`, `stage: String`, `config_json: String`, `draft_json: String`, `last_assistant_reply: String`, `published_profile_id: String`, `created_at: Timestamp`, `updated_at: Timestamp`
- 索引:`owner_user_id`
```sql
SELECT * FROM square_hole_agent_session WHERE session_id = '<session_id>';
SELECT * FROM square_hole_agent_session WHERE owner_user_id = '<user_id>' ORDER BY updated_at DESC;
```
### `square_hole_agent_message`
- 作用:方洞挑战创作 Agent 消息流水。
- 结构:`message_id PK: String`, `session_id: String`, `role: String`, `kind: String`, `text: String`, `created_at: Timestamp`
- 索引:`session_id`
```sql
SELECT * FROM square_hole_agent_message WHERE session_id = '<session_id>' ORDER BY created_at ASC;
```
### `square_hole_work_profile`
- 作用:方洞挑战作品主表,保存作品基础信息、反直觉规则、配置、发布状态和游玩次数。
- 结构:`profile_id PK: String`, `work_id: String`, `owner_user_id: String`, `source_session_id: String`, `author_display_name: String`, `game_name: String`, `theme_text: String`, `twist_rule: String`, `summary_text: String`, `tags_json: String`, `cover_image_src: String`, `shape_count: u32`, `difficulty: u32`, `config_json: String`, `publication_status: String`, `play_count: u32`, `updated_at: Timestamp`, `published_at: Option<Timestamp>`
- 索引:`owner_user_id`, `publication_status`
```sql
SELECT * FROM square_hole_work_profile WHERE profile_id = '<profile_id>';
SELECT * FROM square_hole_work_profile WHERE owner_user_id = '<user_id>' ORDER BY updated_at DESC;
SELECT * FROM square_hole_work_profile WHERE publication_status = 'Published';
```
### `square_hole_runtime_run`
- 作用:方洞挑战单局运行态表,保存后端权威快照、快照版本、胜负状态和成绩基础字段。
- 结构:`run_id PK: String`, `owner_user_id: String`, `profile_id: String`, `status: String`, `snapshot_version: u64`, `started_at_ms: i64`, `duration_limit_ms: i64`, `finished_at_ms: i64`, `elapsed_ms: i64`, `total_shape_count: u32`, `completed_shape_count: u32`, `score: u32`, `snapshot_json: String`, `created_at: Timestamp`, `updated_at: Timestamp`
- 索引:`owner_user_id`, `profile_id`
```sql
SELECT * FROM square_hole_runtime_run WHERE run_id = '<run_id>';
SELECT * FROM square_hole_runtime_run WHERE owner_user_id = '<user_id>' ORDER BY updated_at DESC;
SELECT * FROM square_hole_runtime_run WHERE profile_id = '<profile_id>';
```
## 大鱼吃小鱼表
### `big_fish_creation_session`