chore: checkpoint local workspace changes

This commit is contained in:
2026-04-23 12:45:15 +08:00
parent 3eb9390e8f
commit a6cd9afcbb
47 changed files with 2154 additions and 529 deletions

View File

@@ -0,0 +1,78 @@
# 创作 Agent 聊天区滚动跟随策略修复
日期:`2026-04-23`
## 1. 背景
当前统一创作聊天工作区 [`src/components/creation-agent/CreationAgentWorkspace.tsx`](D:/Genarrative/src/components/creation-agent/CreationAgentWorkspace.tsx) 在以下任一变化时都会强制执行一次滚动到底部:
1. `session.messages`
2. `streamingReplyText`
3. `isStreamingReply`
原实现直接对底部占位节点执行:
1. `scrollIntoView`
2. `behavior: 'smooth'`
这会导致:
1. RPG 创作聊天在 SSE 流式回复期间持续被强拉到底部。
2. 用户手动上滑查看历史消息后,只要流式文本继续更新,就会再次被抢回到底部。
3. 统一聊天工作区已经复用到多条创作链,这个问题会同时影响所有使用 `CreationAgentWorkspace` 的品类。
## 2. 目标
把统一聊天区的滚动策略改成“条件跟随”,而不是“无条件强制到底”:
1. 用户本来就在底部附近时,新消息和流式回复继续跟随到底部。
2. 用户主动上滑离开底部后,不再因为流式更新被强制拉回底部。
3. 用户自己再次发送消息或点击推荐回复时,允许重新进入“跟随底部”状态。
4. 流式阶段不再对每个增量使用 `smooth scroll` 动画,避免持续抖动。
## 3. 方案
## 3.1 工作区组件内部维护滚动跟随态
`CreationAgentWorkspace` 内新增:
1. 聊天滚动容器 `ref`
2. `shouldAutoScrollRef`
判定规则:
1. 当滚动容器距离底部小于等于 `96px` 时,视为“仍在底部附近”。
2. 只有在 `shouldAutoScrollRef=true` 时,消息/流式文本更新才自动滚到底。
## 3.2 用户手动滚动优先
聊天列表监听 `onScroll`
1. 用户上滑离开底部后,把 `shouldAutoScrollRef` 置为 `false`
2. 之后流式 `reply_delta` 继续到来,也不再改写用户当前阅读位置
## 3.3 用户主动发送可重新挂到底部
以下动作视为用户主动回到当前对话尾部:
1. 输入框发送消息
2. 点击推荐回复
执行这些动作前,把 `shouldAutoScrollRef` 重新置为 `true`,保证用户主动推进对话后仍能看到最新回复。
## 3.4 适用范围
本修复落在统一工作区层,因此会同时覆盖:
1. RPG / Custom World Agent
2. Big Fish Agent
3. Puzzle Agent
不需要在各品类 controller 内重复补滚动判断。
## 4. 验收标准
1. 用户在聊天区手动上滑后,流式回复继续生成时,页面不再被持续拉到底部。
2. 用户停留在底部附近时,新消息仍能自然跟随到最新位置。
3. 用户发送新消息后,聊天区仍能回到最新对话尾部。
4. `CreationAgentWorkspace` 定向测试补齐并通过。

View File

@@ -0,0 +1,71 @@
# 创作 Agent 流式消息与草稿切换稳定性修复
日期:`2026-04-23`
## 1. 背景
统一创作工作区已经承载 RPG 世界共创、大鱼吃小鱼和拼图等 Agent 对话。当前 RPG 世界共创在本地联调中暴露出以下前端状态抖动:
1. AI 流式回复过程中,中文内容会先出现乱码,随后又被正常文本覆盖。
2. 玩家刚发送的消息会在聊天列表中短暂出现,随后消失又重新出现。
3. AI 回复会短暂插在玩家消息中间,之后又跳回底部。
4. 曾经打开过某个草稿后,再打开另一个草稿或创建新对话时,结果页可能在旧草稿和当前内容之间来回闪烁。
这些现象的共同原因不是单个滚动动作,而是同一 UI 区域同时被多套不同来源的状态驱动本地乐观消息、SSE 临时回复、服务端最终 session 快照、旧草稿结果页缓存和异步恢复结果。
## 2. 目标
本轮修复只收敛前端展示稳定性,不改变后端业务语义:
1. 聊天列表只展示一条稳定的玩家消息,不因最终 session 回写而闪消。
2. AI 流式回复始终作为当前尾部 assistant 消息呈现,不和正式消息互相插队。
3. SSE 中文文本按 UTF-8 流式边界安全解码,流结束时刷新解码器尾部缓存。
4. 草稿切换、打开已有草稿、新建对话时先清理旧结果页缓存,旧异步恢复结果不得覆盖当前视图。
5. 继续保留“用户主动上滑后不强制滚到底部”的聊天区滚动策略。
## 3. 设计
## 3.1 SSE 事件读取
`src/services/creation-agent/creationAgentSse.ts` 继续作为统一 SSE 读取器,但需要补齐以下边界:
1. 使用 UTF-8 `TextDecoder` 的 streaming 模式接收 chunk。
2. `reader.read()` 结束后调用 `decoder.decode()` 刷新尾部缓冲,避免多字节中文字符残留在解码器内部。
3. 事件分隔同时兼容 `\n\n``\r\n\r\n`
4. `reply_delta``text` 字段按“当前可展示文本”传给 UI不在读取器内追加避免累计文本和增量文本语义混用。
## 3.2 玩家消息展示
RPG Agent 发送消息时,本地乐观玩家消息仍保留,但最终 session 回写时必须做稳定合并:
1. 若服务端快照已包含同一个 `clientMessageId`,以服务端消息为准。
2. 若服务端快照暂未包含该消息,临时保留本地消息,直到后续快照补齐。
3. 合并只按消息 `id` 去重,不整包丢弃本地尾部消息。
## 3.3 AI 流式回复展示
统一聊天工作区不再把流式回复作为独立于列表之外的气泡随意附加,而是在展示消息数组中合成一个稳定的尾部临时 assistant 消息:
1. session 正式消息仍是基础列表。
2. 有流式文本时,追加或替换尾部临时 assistant 消息。
3. 最终 session 到来后,临时消息消失,由正式 assistant 消息接管同一视觉位置。
4. 推荐回复只挂在正式最后一条 assistant 消息上,流式临时消息不展示推荐回复。
## 3.4 草稿切换
打开已有草稿、打开 Agent 草稿、新建 RPG Agent 对话前,必须先清理旧结果页相关缓存:
1. `generatedCustomWorldProfile`
2. `customWorldGenerationViewSource`
3. `customWorldResultViewSource`
4. 自动保存状态
异步读取 session 时要以本次打开的 `sessionId` 作为准入条件,防止上一个草稿的慢响应覆盖当前草稿。
## 4. 验收标准
1. 玩家输入发送后在聊天列表中只稳定出现一次,不再闪消。
2. AI 流式回复只在底部连续更新,不插入玩家消息中间。
3. 中文流式回复不再出现先乱码后正常的过渡。
4. 从一个草稿切换到另一个草稿或新建对话时,不再短暂显示旧草稿结果页。
5. 用户手动上滑聊天区后,流式更新仍不强制抢回底部。

View File

@@ -1217,9 +1217,8 @@ Phase 4 本轮已完成以下主链接线:
- published works 明确输出 `canEnterWorld=true`
4. 前端 Agent 结果页已开始消费服务端 Phase4 状态:
- 结果页在 Agent 草稿未发布时把主 CTA 改成“发布并进入世界”
- 结果页会消费服务端 gate 语义,但不再把 preview source 做成底部常驻提示
- publish blockers 改为点击“发布并进入世界”时,通过独立面板提示
- warning 数量仍可作为非阻断摘要展示
- 结果页会展示服务端 preview source、publish blockers、warning 数量
- blocker 时会禁用“发布并进入世界”按钮,不再让前端继续假装可以直接进入世界
5. `useRpgCreationEnterWorld.ts``RpgEntryFlowShellImpl.tsx` 已把 Agent 结果页进入世界主链改成:
-`sync_result_profile`
- 再执行后端 `publish_world`

View File

@@ -89,13 +89,13 @@ scripts/jenkins-deploy-release.sh \
脚本语义:
1. 若部署目录已有旧版本且存在 `stop.sh`,先执行旧版本 `stop.sh`
2. 直接清空部署目录中的全部旧文件
3. 将指定版本目录中的内容移动到部署目录。
2. 只删除发布产物白名单中的旧文件,例如 `web/``api-server``spacetime_module.wasm``.env*``start.sh``stop.sh``web-server.mjs``README.md`
3. 将指定版本目录中的同名发布产物移动到部署目录。
4. 执行新版本 `start.sh`
如果 `RUN_DEPLOY_HOOKS_WITH_SUDO=true`,第 1 步和第 4 步会改为 `sudo -n` 调用;这要求 Jenkins 运行用户提前配置免密 sudo否则部署会直接失败不会进入交互式密码提示。
这样可以满足你要求的“直接覆盖部署目录中的所有文件”。同时这也意味着部署目录内原有的 `.env``.env.local`、日志和本地 SpacetimeDB 数据都会被清掉,最终以构建产物中的文件为准。
这样可以满足“发布文件直接覆盖”的要求,同时保留部署目录里像 `spacetimedb-data/``logs/``run/` 这类运行态目录,不会因为部署被整体删除。发布白名单内的 `.env``.env.local` 仍会以构建产物中的文件为准。
### 4.3 构建并部署

View File

@@ -0,0 +1,73 @@
# 手机验证码阿里云响应字段映射修复
日期:`2026-04-23`
## 1. 文档目的
这份文档用于冻结验证清单第一项在 Rust 本地联调中暴露出的一个实现级问题:
1. 前端点击“获取验证码”后,请求确实进入了 Rust `api-server`
2. `platform-auth` 也确实发起了阿里云短信 RPC 请求。
3. 但 Rust 侧把阿里云响应误判成失败,导致界面显示“短信验证码发送失败”。
## 2. 根因
根因不是前端请求没发出,也不是 `module-auth` 仍在走 mock而是 `platform-auth` 对阿里云原始 JSON 的字段名映射不正确。
阿里云 `SendSmsVerifyCode / CheckSmsVerifyCode` 原始响应字段使用首字母大写命名,例如:
1. `Code`
2. `Message`
3. `RequestId`
4. `Success`
5. `Model`
而 Rust 侧此前按小写字段解析:
1. `code`
2. `message`
3. `request_id`
4. `success`
5. `model`
这会导致:
1. 即使阿里云真实返回 `Code=OK``Success=true`
2. Rust 反序列化后仍得到 `None`
3. 后续逻辑把这次发送误判成“短信验证码发送失败”
## 3. 本次修复
本次修复只做最小映射纠偏,不改动现有 DTO contract 和业务边界:
1.`AliyunSendSmsVerifyCodeResponse` 显式补充 `Code / Message / RequestId / Success / Model``serde rename`
2.`AliyunCheckSmsVerifyCodeResponse` 显式补充 `Code / Message / Success / Model``serde rename`
3. 补充单元测试,确保首字母大写的阿里云 JSON 可以被 Rust 正确解析
## 4. 影响范围
本次只影响:
1. `server-rs/crates/platform-auth`
本次不影响:
1. 前端手机号登录交互 contract
2. `module-auth` 的冷却、过期与失败次数逻辑
3. `api-server` 对外错误映射结构
## 5. 验收标准
修复后再次点击“获取验证码”时,应满足:
1. 如果阿里云真实返回成功Rust 日志输出“手机号验证码发送请求已提交”
2. 日志中出现:
- `provider=aliyun`
- `provider_request_id`
- `provider_out_id`
3. 前端不再因为字段解析错误而直接提示“短信验证码发送失败”
## 6. 关联文档
1. [PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md](./PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md)
2. [PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md](./PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md)

View File

@@ -0,0 +1,185 @@
# 手机验证码真实 Provider 手动验证运行手册
日期:`2026-04-23`
## 1. 文档目的
这份文档用于冻结 `验证清单.md` 第一项“短信验证功能真实走到阿里云服务”的本地验证口径,避免继续出现以下问题:
1. 本地服务虽然返回 `200`,但没有证据证明请求真的打到了阿里云短信接口。
2. 前端手动输入手机号后,后端日志缺少可核验字段,导致无法判断是前端没发出请求、后端没收到请求,还是上游阿里云返回了错误。
3. 本地验证动作与后续回归标准没有文档化,下一轮继续验证时还要重复人工猜步骤。
## 2. 本次验证范围
本次只验证“发送验证码”和“验证码登录”主链是否真实经过 Rust `api-server` 和阿里云短信 Provider。
本次验证固定范围:
1. 前端入口使用本仓库 Web 端登录弹窗。
2. 后端入口使用 `server-rs/crates/api-server`
3. 手机验证码 provider 使用 `SMS_AUTH_PROVIDER=aliyun`
4. 当前只验证中国大陆手机号。
本次明确不验证:
1. 阿里云短信送达回执是否已完整回流仓库。
2. 运营商最终投递状态。
3. 微信绑定手机号链路。
4.`server-node` 短信链路。
## 3. 前置条件
开始验证前,必须同时满足以下条件:
1. 仓库根目录 `.env.local` 中已启用:
- `SMS_AUTH_ENABLED="true"`
- `SMS_AUTH_PROVIDER="aliyun"`
2. `.env.local` 中阿里云短信配置不为空:
- `ALIYUN_SMS_ACCESS_KEY_ID`
- `ALIYUN_SMS_ACCESS_KEY_SECRET`
- `ALIYUN_SMS_SIGN_NAME`
- `ALIYUN_SMS_TEMPLATE_CODE`
3. 本机已安装:
- `cargo`
- `node`
- `spacetime`
4. 本地端口可用或已有可复用开发栈:
- Web`3000`
- Rust API`8082`
- SpacetimeDB`3101`
说明:
1. Rust `api-server` 启动时会自动读取仓库根目录 `.env``.env.local`
2. 若本机已有 `3000 / 8082 / 3101` 运行中的 Genarrative Rust 栈,可以直接复用,但必须确认它已经加载当前分支代码。
## 4. 启动方式
推荐统一使用:
```powershell
npm run dev:rust
```
该命令会完成以下动作:
1. 启动本地 `SpacetimeDB standalone`
2. 发布 `server-rs/crates/spacetime-module`
3. 启动 Rust `api-server`
4. 启动 Vite Web 开发服务器。
如果已有栈在运行,至少要确认以下健康状态:
1. Web 可访问:`http://127.0.0.1:3000`
2. Rust API 正在监听:`http://127.0.0.1:8082`
3. `api-server` 控制台可实时看到日志输出
## 5. 手动验证步骤
### 5.1 发送验证码
1. 打开 `http://127.0.0.1:3000`
2. 进入任一受保护动作,拉起手机号验证码登录弹窗。
3. 输入中国大陆手机号。
4. 点击“获取验证码”。
5. 观察前端是否提示发送请求已提交。
6. 同时观察 Rust `api-server` 控制台日志。
发送成功时,日志必须至少包含以下字段:
1. `request_id`
2. `scene`
3. `phone_masked`
4. `provider`
5. `provider_request_id`
6. `provider_out_id`
7. `cooldown_seconds`
8. `expires_in_seconds`
期望值约束:
1. `provider` 必须为 `aliyun`
2. `provider_request_id` 不能为空或 `unknown`
3. `provider_out_id` 不能为空或 `unknown`
### 5.2 验证码登录
1. 如果手机已收到验证码,在同一弹窗输入验证码。
2. 点击登录。
3. 观察前端是否进入已登录态。
4. 同时观察 Rust `api-server` 控制台日志。
登录成功时,日志必须至少包含以下字段:
1. `request_id`
2. `scene=login`
3. `phone_masked`
4. `provider`
5. `provider_out_id`
6. `user_id`
7. `created`
期望值约束:
1. `provider` 必须为 `aliyun`
2. 日志必须明确输出“手机号验证码登录成功”
## 6. 通过标准
本次验证满足以下条件时,视为通过:
1. 前端点击“获取验证码”后Rust `api-server` 日志明确输出“手机号验证码发送请求已提交”。
2. 发送成功日志里的 `provider=aliyun`
3. 发送成功日志里的 `provider_request_id``provider_out_id` 都不是空值。
4. 手机实际收到验证码后,输入验证码可以成功登录。
5. 登录成功日志明确输出“手机号验证码登录成功”。
## 7. 失败判定与排查
### 7.1 发送阶段失败
优先看 Rust `api-server` 日志中的错误文本:
1. `手机号格式不正确`
- 说明前端输入未满足中国大陆手机号格式要求。
2. `验证码发送过于频繁,请稍后再试`
- 说明命中了本地冷却限制,不代表阿里云不可用。
3. `阿里云短信 AccessKey 未配置`
- 说明 `.env.local` 配置缺失。
4. `短信验证码发送失败`
- 说明请求已经进入 provider 调用,但阿里云返回了上游错误或网络错误。
### 7.2 登录阶段失败
1. `验证码错误`
- 说明验证码校验已真实进入 provider 校验链,但输入的验证码不正确。
2. `验证码不存在或已失效`
- 说明本地验证码快照已过期或已被消费。
3. `验证码错误次数过多,请重新获取验证码`
- 说明当前快照已耗尽,需要重新发送。
## 8. 结果解释边界
即使本次验证通过,也只代表:
1. 前端请求已经进入 Rust `api-server`
2. Rust `api-server` 已按 `aliyun` provider 口径调用阿里云接口
3. 阿里云返回了可追踪的 `provider_request_id / provider_out_id`
它不自动代表:
1. 运营商已经完成最终投递
2. 用户手机一定已经收到短信
关于“平台受理成功”和“最终送达成功”的差异,后续统一按:
`docs/technical/PHONE_SMS_DELIVERY_OBSERVABILITY_AND_RECEIPT_DESIGN_2026-04-22.md`
继续补齐回执与状态追踪闭环。
## 9. 关联文档
1. [PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md](./PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md)
2. [PHONE_SMS_DELIVERY_OBSERVABILITY_AND_RECEIPT_DESIGN_2026-04-22.md](./PHONE_SMS_DELIVERY_OBSERVABILITY_AND_RECEIPT_DESIGN_2026-04-22.md)
3. [PHONE_SMS_LOGIN_STAGE_A_IMPLEMENTATION_2026-04-21.md](./PHONE_SMS_LOGIN_STAGE_A_IMPLEMENTATION_2026-04-21.md)

View File

@@ -0,0 +1,113 @@
# 手机验证码发送链路可观测性补强
日期:`2026-04-23`
## 1. 文档目的
这份文档用于冻结 Rust `api-server + module-auth + platform-auth` 在手机号验证码发送链路上的新增日志口径,解决当前排查成本过高的问题:
1. 前端点击“获取验证码”后,只能看到 `/api/auth/phone/send-code` 返回了 `500`
2. 服务端虽然已有部分失败日志,但关键上下文不完整,仍需要人工反推是哪一层失败。
3. 阿里云真实返回 `UNKNOWN``check frequency failed` 这类错误时,缺少足够字段判断是本地冷却、上游频控、配置异常还是响应解析问题。
## 2. 本次补强范围
本次只补强短信发送链路日志,不改前端 UI不扩散到无关模块
1. `server-rs/crates/api-server`
2. `server-rs/crates/module-auth`
3. `server-rs/crates/platform-auth`
## 3. 日志补强目标
新增日志后,排查一次 `send-code` 失败时,单看日志应能回答:
1. 请求是否真的进入 `api-server`
2. 当前使用的是 `mock` 还是 `aliyun`
3. 手机号和场景是否已经通过服务端规范化
4. 失败发生在本地冷却检查前、阿里云请求发出前、阿里云返回后,还是快照写入阶段
5. 阿里云返回的 HTTP 状态、`Code``Message``RequestId` 是否可见
## 4. 新增日志位置
### 4.1 `api-server`
`POST /api/auth/phone/send-code` handler 内新增发送前日志,至少输出:
1. `request_id`
2. `operation`
3. `scene`
4. `provider`
5. `phone_input_masked`
失败日志继续保留,但补充:
1. `provider`
2. `phone_input_masked`
### 4.2 `module-auth`
`PhoneAuthService::send_code` 内新增:
1. 规范化手机号后、调用 provider 前的调试日志
2. 本地冷却命中时的日志
3. provider 成功后、写入本地验证码快照前的日志
字段至少包含:
1. `scene`
2. `phone_e164_masked`
3. `phone_national_masked`
4. `provider`
5. `cooldown_seconds`
6. `expires_in_seconds`
7. `provider_request_id`
8. `provider_out_id`
### 4.3 `platform-auth`
在阿里云 provider 内新增:
1. 发起 `SendSmsVerifyCode` 前的日志
2. 收到阿里云 HTTP 响应后的日志
3. 错误分类前的日志
字段至少包含:
1. `provider=aliyun`
2. `scene`
3. `phone_masked`
4. `endpoint`
5. `http_status`
6. `provider_code`
7. `provider_message`
8. `provider_request_id`
9. `provider_out_id`
## 5. 约束
为了避免日志变成新的风险源,本次必须满足:
1. 不打印完整手机号
2. 不打印验证码明文
3. 不打印 `AccessKeySecret`
4. 不打印完整签名串
5. 只打印排查发送链路需要的最小字段
## 6. 验收标准
补强后再次请求 `/api/auth/phone/send-code`,无论成功还是失败,都应满足:
1. `api-server` 能看到请求进入日志
2. `module-auth` 能看到 provider 调用前的规范化结果
3. `platform-auth` 能看到阿里云返回的状态和关键字段
4. 如果失败,日志里可以直接区分:
- 本地冷却
- 阿里云配置缺失
- 阿里云上游失败
- 阿里云频控或业务拒绝
## 7. 关联文档
1. [PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md](./PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md)
2. [PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md](./PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md)

View File

@@ -5,6 +5,9 @@
## 文档列表
- [CUSTOM_WORLD_AGENT_LLM_REPLY_RESTORE_2026-04-22.md](./CUSTOM_WORLD_AGENT_LLM_REPLY_RESTORE_2026-04-22.md):恢复 Custom World Agent 聊天必须走大模型推理的 Rust 落地方案,冻结 submit/finalize 两阶段职责、旧 Node 提示词原样搬运、SSE 流式回复与 session 回写边界。
- [RUST_LOCAL_DEV_SPACETIMEDB_PUBLISH_GUARD_AND_AGENT_LLM_FAILURE_POLICY_2026-04-23.md](./RUST_LOCAL_DEV_SPACETIMEDB_PUBLISH_GUARD_AND_AGENT_LLM_FAILURE_POLICY_2026-04-23.md):冻结 Rust 本地联调启动前必须 publish/generate 最新 `spacetime-module` 的守卫,以及 Custom World Agent 在 LLM 失败时禁止写固定 assistant 回复的 finalize 与 HTTP/SSE 错误策略。
- [CREATION_AGENT_CHAT_SCROLL_FOLLOW_POLICY_FIX_2026-04-23.md](./CREATION_AGENT_CHAT_SCROLL_FOLLOW_POLICY_FIX_2026-04-23.md):记录统一创作聊天工作区从“每次更新都强制滚到底”改为“仅在用户仍停留在底部附近时跟随”的滚动策略修复,避免流式回复持续抢走阅读位置。
- [CREATION_AGENT_STREAMING_MESSAGE_STABILITY_FIX_2026-04-23.md](./CREATION_AGENT_STREAMING_MESSAGE_STABILITY_FIX_2026-04-23.md):记录创作 Agent 聊天流式文本、玩家乐观消息、最终 session 回写和草稿切换的展示稳定性修复,避免乱码、闪消、插队和旧草稿闪烁。
- [CREATION_HUB_CARD_ACTIONS_2026-04-22.md](./CREATION_HUB_CARD_ACTIONS_2026-04-22.md):冻结创作中心作品卡“体验 / 删除”入口的最小落地语义,明确 RPG 已发布作品软删除、卡片直达运行时,以及暂不扩草稿 / 拼图删除契约。
- [CREATION_CATEGORY_OPENING_TIMEOUT_GUARD_FIX_2026-04-22.md](./CREATION_CATEGORY_OPENING_TIMEOUT_GUARD_FIX_2026-04-22.md):记录创作中心点击类别后长时间停留在“正在开启”的根因与修复口径,收口前端创建会话启动超时、中文错误提示以及 Big Fish / 拼图代理上游超时兜底。
- [JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md](./JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md):冻结 Jenkins `构建 / 部署 / 构建并部署` 三条流水线的职责、版本号传递、上游触发门禁、本地目录部署脚本与 `/home/ubuntu/Genarrative-deploy/` 覆盖策略。
@@ -22,6 +25,7 @@
- [PHONE_AUTH_AXUM_RATE_LIMIT_AND_FAILURE_DESIGN_2026-04-21.md](./PHONE_AUTH_AXUM_RATE_LIMIT_AND_FAILURE_DESIGN_2026-04-21.md):手机号验证码冷却与失败次数限制设计,冻结同手机号同场景发送冷却、错误次数耗尽、`429``Retry-After` contract。
- [PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md](./PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md):冻结 Rust `api-server + module-auth + platform-auth` 接入真实阿里云短信 provider 的 crate 边界、发送与校验职责、配置项和错误语义。
- [PHONE_SMS_ALIYUN_RESPONSE_FIELD_MAPPING_FIX_2026-04-23.md](./PHONE_SMS_ALIYUN_RESPONSE_FIELD_MAPPING_FIX_2026-04-23.md):记录 Rust `platform-auth` 把阿里云 PascalCase 响应字段误判成空值的问题根因,并冻结字段映射修复与回归标准。
- [PHONE_SMS_SEND_CODE_OBSERVABILITY_FIX_2026-04-23.md](./PHONE_SMS_SEND_CODE_OBSERVABILITY_FIX_2026-04-23.md):冻结手机号验证码发送链路的日志补强口径,确保 `api-server``module-auth``platform-auth` 能直接暴露发送前后与错误分类关键字段。
- [PHONE_SMS_DELIVERY_OBSERVABILITY_AND_RECEIPT_DESIGN_2026-04-22.md](./PHONE_SMS_DELIVERY_OBSERVABILITY_AND_RECEIPT_DESIGN_2026-04-22.md):冻结短信平台受理成功与最终送达状态的区分方式、追踪字段、送达回执接口和前端提示文案边界。
- [PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md](./PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md):冻结验证清单第一项“真实短信验证码链路”的本地启动、前端操作、日志观察点、通过标准与失败排查步骤。
- [WECHAT_LOGIN_AXUM_IMPLEMENTATION_DESIGN_2026-04-21.md](./WECHAT_LOGIN_AXUM_IMPLEMENTATION_DESIGN_2026-04-21.md)Rust `api-server` 微信登录实现设计,冻结微信 provider 接入、系统 JWT 签发边界、`wechat/start` / `wechat/callback` / `wechat/bind-phone` 闭环,以及与后续 `SpacetimeDB` claims 透传的关系。

View File

@@ -104,7 +104,7 @@ npm run deploy:rust:remote
5. 执行 `cargo build -p spacetime-module --release --target wasm32-unknown-unknown --manifest-path server-rs/Cargo.toml`,并把 `spacetime_module.wasm` 复制到目标目录。
6. 把仓库根目录的 `.env``.env.local` 分别复制到目标目录根部和目标目录的 `web/` 下。
7. 在目标目录写入 `web-server.mjs`,用于托管 `web/` 并把 `/api/*``/generated-*``/healthz` 反代到本包内的 `api-server`
8. 在目标目录写入 `start.sh``stop.sh``start.sh` 会先加载发布目录根部的 `.env``.env.local`,再回退到构建时通过 `--database``--api-port``--web-port``--spacetime-host``--spacetime-port` 写入的默认值。
8. 在目标目录写入 `start.sh``stop.sh``start.sh` 会先加载发布目录根部的 `.env``.env.local`,再回退到构建时通过 `--database``--api-port``--web-port``--spacetime-host``--spacetime-port` 写入的默认值,并默认导出 `NO_COLOR=1``CARGO_TERM_COLOR=never`,避免 ANSI 控制码写入日志文件
9. 默认执行 `scp -r -i ~\.ssh\dsk.pem build/<timestamp> ubuntu@82.157.175.59:/home/ubuntu/genarrative/` 上传发布包。
发布包结构:
@@ -140,6 +140,8 @@ cd build/<timestamp>
./stop.sh
```
如果后续通过 Jenkins 的部署脚本把发布包覆盖到固定部署目录,部署阶段默认只替换 `web/``api-server``spacetime_module.wasm``.env*``start.sh``stop.sh``web-server.mjs``README.md` 等发布产物,不会删除部署目录中的 `spacetimedb-data/``logs/``run/` 这类运行态目录。
安全边界:
1. 构建脚本会把仓库根目录已有的 `.env``.env.local` 一并复制进发布包,因此运行前必须确认这些文件内容适合被带入目标环境。

View File

@@ -0,0 +1,150 @@
# Rust 本地联调 Spacetime 发布守卫与 Agent LLM 失败策略
日期:`2026-04-23`
## 1. 背景
当前第 2 项验证要求:
1. 创作中心里的 RPG 入口必须走真实后端链路。
2. 聊天引导必须真实接入 LLM。
3. 不允许用固定模板伪造 assistant 回复。
本地联调时暴露出两个直接阻塞点:
1. 本地 `spacetime-module` schema 未及时 publishRust `api-server` 在 finalize Agent 回合时调用不到最新 procedure。
2. Custom World Agent 在 LLM 不可用、上游失败或输出 JSON 非法时,仍会写入固定中文 assistant 文案,和“不得使用固定模板来回答”的验收要求冲突。
## 2. 当前本地真实目标库
以仓库当前配置为准:
1. `spacetime.local.json`
- `database = xushi-p4wfr`
2. `spacetime.json`
- `server = http://127.0.0.1:3101`
本地 `npm run dev``npm run dev:rust`、手工 publish / generate 都必须以这一组配置为真实来源,除非开发者显式通过环境变量覆盖。
## 3. 本地开发守卫
### 3.1 启动前强制 publish
`scripts/dev-node.mjs` 在拉起 Rust `api-server` 前,必须先执行:
```bash
spacetime publish xushi-p4wfr --server http://127.0.0.1:3101 --module-path D:\Genarrative\server-rs\crates\spacetime-module --yes
```
要求:
1. `spacetime` CLI 不存在时直接 fail-fast。
2. publish 失败时整个本地 dev 启动终止,不允许继续拉起前后端。
3. 不允许继续依赖“开发者记得手工 publish”这种脆弱约定。
4. 默认不清空本地库;只有显式设置 `GENARRATIVE_SPACETIME_DELETE_DATA_ON_CONFLICT=1` 时,才允许追加 `--delete-data=on-conflict` 处理开发库 schema 冲突。
### 3.2 启动前强制 generate Rust bindings
publish 成功后,继续执行:
```bash
spacetime generate --no-config --lang rust --out-dir D:\Genarrative\server-rs\crates\spacetime-client\src\module_bindings --module-path D:\Genarrative\server-rs\crates\spacetime-module --include-private --yes
```
要求:
1. bindings 生成失败时整个本地 dev 启动终止。
2. 不手改 `server-rs/crates/spacetime-client/src/module_bindings` 生成物。
3. Rust contract 变化后,以重新 generate 的 bindings 作为唯一真相。
### 3.3 已知本地库迁移门禁
当前 `xushi-p4wfr` 本地库已存在旧版 `custom_world_profile` 表,缺少软删除字段 `deleted_at`。直接 publish 最新 `spacetime-module`SpacetimeDB 会拒绝自动迁移并提示:
1. `Reordering table custom_world_profile requires a manual migration`
2. `Adding a column deleted_at to table custom_world_profile requires a default value annotation`
处理方式:
1. 优先执行正式迁移,补齐 `deleted_at`
2. 若确认只是本地开发库且可丢弃数据,可临时设置 `GENARRATIVE_SPACETIME_DELETE_DATA_ON_CONFLICT=1` 后重新启动。
3. 禁止在未确认的情况下默认清库。
## 4. Agent LLM 失败策略
### 4.1 明确禁止的旧行为
以下场景不允许再写固定 assistant 回复:
1. `llm_client` 缺失。
2. `platform-llm.stream_text(...)` 调用失败。
3. 大模型输出不是合法 JSON。
4. 输出缺失 `replyText``replyText` 为空白。
这类失败只能被视为:
1. 本轮 `process_message` operation 失败。
2. 当前回合未产出正式 assistant message。
### 4.2 finalize contract
`finalize_custom_world_agent_message_turn` 需要允许失败态:
1. `operation_status = failed`
2. `assistant_message_id = None`
3. `assistant_reply_text = None`
同时保持成功态约束不变:
1. `completed` 必须写 assistant message。
2. `completed` 必须推进 `current_turn``last_assistant_reply`
### 4.3 SpacetimeDB 真相更新规则
`spacetime-module` 的 finalize 事务必须分支处理:
1. 成功态:
- 插入 assistant message
- `current_turn += 1`
- 更新 `last_assistant_reply`
- 更新 session 聚合 JSON
- operation 标记为 `completed`
2. 失败态:
- 不插入 assistant message
- 不推进 `current_turn`
- 不更新 `last_assistant_reply`
- 不覆盖 session 聚合 JSON
- 仅更新 session `updated_at`
- operation 标记为 `failed`
## 5. HTTP / SSE 口径
### 5.1 `/messages`
普通提交接口在 finalize 后:
1. 若 operation 为 `completed`,返回成功响应。
2. 若 operation 为 `failed`,返回正式 HTTP 错误,不返回成功 envelope。
### 5.2 `/messages/stream`
流式接口在 finalize 后:
1. 若 operation 为 `completed`,继续发送 `session``done`
2. 若 operation 为 `failed`,只发送 `error`,不得再发送 `session` / `done`
注意:
1. 失败前已经产生的真实 `reply_delta` 可以保留。
2. 失败时不得额外补发固定中文 assistant 兜底文案。
## 6. 验收标准
满足以下条件才算第 2 项通过:
1. 本地 dev 启动前会自动 publish 最新 `spacetime-module`
2. 本地 dev 启动前会自动 generate 最新 Rust bindings。
3. `finalize_custom_world_agent_message_turn` 已存在于本地目标库 schema。
4. 流式 RPG 创作聊天不再报 `No such procedure`
5. LLM 成功时,前端看到的回复来自真实大模型。
6. LLM 失败时,只看到正式错误,不再看到固定 assistant 回复。

View File

@@ -50,10 +50,9 @@ src/services/creation-agent/
聊天页展示规则:
1. Agent 聊天页不展示锚点内容卡片,锚点只作为进度与后端生成依据。
2. 标题区文案支持按品类留空;当 `title``assistantSummary` 都为空时,顶部模块只保留返回、进度和操作按钮,不显示额外标题与副文案
3. 生成草稿 / 生成结果页主按钮只在 `progressPercent` 归一化后达到 `100%` 时显示
4. 进度条下方承载“总结当前设定”“补全剩余设定”等进度操作按钮
5. “补全剩余设定”必须配置 `minTurn: 2`,对话不足两轮时不显示。
2. 生成草稿 / 生成结果页主按钮只在 `progressPercent` 归一化后达到 `100%` 时显示
3. 进度条下方承载“总结当前设定”“补全剩余设定”等进度操作按钮
4. “补全剩余设定”必须配置 `minTurn: 2`,对话不足两轮时不显示
组件内部只做表现,不读取任何 RPG、Big Fish、Puzzle 专属字段。