Merge remote-tracking branch 'origin/master' into rm/design-v2

This commit is contained in:
2026-09-15 15:52:12 +00:00
136 changed files with 5060 additions and 2778 deletions
@@ -3370,16 +3370,32 @@
"maxLength": 200
}
},
"sliceLayout": {
"sliceMode": {
"type": "string",
"deprecated": true,
"description": "历史兼容字段,新的调用请使用 sliceCount"
"enum": [
"connected-components",
"grid"
],
"default": "connected-components",
"description": "图集切分模式。connected-components 按透明像素 alpha 连通域识别独立素材;grid 按用户提供的 gridX/gridY 划分网格槽。省略时使用 connected-components。"
},
"gridX": {
"type": "integer",
"minimum": 1,
"maximum": 32,
"description": "grid 模式的横向网格数量。"
},
"gridY": {
"type": "integer",
"minimum": 1,
"maximum": 32,
"description": "grid 模式的纵向网格数量。"
},
"sliceCount": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"description": "可选的目标切片数量;省略时按图像内容自动识别。"
"description": "connected-components 模式下可选的目标切片数量;省略时按图像内容自动识别。grid 模式的切片数量由 gridX×gridY 决定。"
},
"screenColor": {
"type": ["string", "null"],
@@ -3603,15 +3619,28 @@
},
"iconImageSrcs": {
"type": "array",
"description": "识别图集中有效 alpha 连通域并持久化的独立素材,按视觉阅读顺序命名为“素材 N”;可通过 sliceCount 指定目标数量。",
"description": "按 sliceMode 识别或裁切并持久化的独立素材,按视觉阅读顺序命名为“素材 N”;connected-components 模式可通过 sliceCount 指定目标数量。",
"items": {
"$ref": "#/components/schemas/EditorIconSpritesheetIconResult"
}
},
"sliceLayout": {
"sliceMode": {
"type": "string",
"deprecated": true,
"description": "历史兼容字段。"
"enum": [
"connected-components",
"grid"
],
"description": "实际采用的图集切分模式。"
},
"gridX": {
"type": "integer",
"minimum": 1,
"maximum": 32
},
"gridY": {
"type": "integer",
"minimum": 1,
"maximum": 32
},
"sliceCount": {
"type": "integer",
@@ -3628,7 +3657,7 @@
"type": "null"
}
],
"description": "可信透明图集已成功持久化,但全连通域自动拆分未完成时返回;此时 iconImageSrcs 为空,调用方仍应使用整张图集。原始连通域、输出数量或 CPU 预算超限不会产生切片 PUT、资源或画布切片。透明处理、Alpha/尺寸恢复、provider 原图修复性回读或透明图完整解码失败时走 provider 原图 source-onlysliceWarning 为 null。"
"description": "可信透明图集已成功持久化,但所选 sliceMode 的自动拆分未完成时返回;此时 iconImageSrcs 为空,调用方仍应使用整张图集。原始连通域、输出数量、网格裁切或 CPU 预算超限不会产生切片 PUT、资源或画布切片。透明处理、Alpha/尺寸恢复、provider 原图修复性回读或透明图完整解码失败时走 provider 原图 source-onlysliceWarning 为 null。"
},
"prompt": {
"type": "string"
@@ -0,0 +1,35 @@
# AGC 统一错误诊断与验收反馈实施计划
Version: 1.0
Status: active
Date: 2026-09-15
Parent Milestone: `【里程碑】AGC统一错误诊断与验收反馈-2026-09-15.md`
## 修改边界
1. 新增 `agent/runtime_error.rs`,承载统一事件字段、code/stage 白名单、脱敏后的 public projection、项目错误 JSONL/sidecar 落库和 detail 读取边界。
2. `direct_runtime.rs` 使用统一事件替代仅写 `failure.json` 的路径;失败 assistant 投影带稳定 ID,下一轮 prompt 注入最近失败事件摘要。
3. `codex_app_server.rs` 将 failed turn、idle/hard timeout、transport close、invalid terminal 和 stderr tail 转成稳定事件字段;不公开原始 detail。
4. `direct_tool_bridge.rs``direct_tools_mcp.rs` 让 attempt 由客户端回合状态约束,越界请求返回终态工具错误;不扩展重试预算。
5. `direct_runtime.rs` 的素材扫描递归覆盖可执行源码模块,基于 manifest 身份和浏览器 URL 映射判定;补充模块引用回归测试。
6. 前端读取后端 `publicText/detailRef`,在现有 Runtime 错误面板中加入详情入口;不在 React 侧重新分类错误。
## 实现顺序
先写统一事件模型和 Rust 单测,再接 direct failure/app-server/tool bridge,随后接 prompt/history 与前端详情,最后修素材验收和 attempt 生命周期。每一步保留原有脱敏和失败关闭行为。
## 验证命令
- `cargo fmt --check`
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml runtime_error direct_runtime codex_app_server direct_tool_bridge`
- `npm run --prefix apps/ai-game-creator-shell typecheck`
- `npm run check:encoding`
- `git diff --check`
- 必要时运行 AGC deterministic playable E2E;真实 Provider smoke 与浏览器双视口 smoke 单独报告。
## 风险与回滚
- 统一事件 schema 只新增项目内文件和对话投影,不修改已有 manifest、公开 API 或 SpacetimeDB schema。
- 若前端详情读取失败,仍展示安全 `publicText`,不阻塞错误终态。
- 若素材身份无法映射,继续失败关闭并记录明确 code,不回退为路径字符串通过。
- 回滚可删除新事件写入和详情入口,保留旧 `failure.json` 读取兼容。
@@ -0,0 +1,37 @@
# 【实施计划】DirectProject Thread Manager 事件订阅
| 字段 | 值 |
| --- | --- |
| Milestone | `docs/project-memory/plans/【里程碑】DirectProject Thread Manager事件订阅-2026-09-15.md` |
| Status | ready |
| Owner | Codex |
## 修改边界
- 允许修改:DirectProject Rust Thread Manager 深模块、app-server 事件适配、Tauri command/event 桥接、DirectProject 前端订阅/reducer/历史加载、对应测试和主规范。
- 明确不修改:SpacetimeDB、HTTP API、非 DirectProject Runtime、Codex app-server durable thread、用户可见 JSONL 细节。
- 保持已有 `.env` 未提交修改,不触碰个人配置。
## 实现顺序
1. 先新增独立 Rust queue/subscriber 深模块,只承载事件追加、逻辑队头回收、subscriber cursor 锁和纯单测。
2. 将 app-server 公开事件安全标准化后接入 Thread Manager;在 item 完成持久化成功后追加完成事件,并追加 turn 生命周期事件。
3. 增加 Tauri `subscribe/consume/readHistory` 命令与 notify 事件,固定错误和 bootstrap 原子边界。
4. 前端改为 subscriptionId 驱动的 raw event reducer;重进/过期时先 bootstrap,完成后原子替换;历史按 itemId 懒加载。
5. 移除 DirectProject legacy conversation 读取分支,补齐契约、并发、恢复和失败关闭测试;让初始历史切片的 `hasMore` 独立驱动“显示更早”按钮和滚动入口。
6. 每个独立切片分别运行定向验证并形成中文小提交;里程碑验收后再清理临时计划。
## 验证命令
1. `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml` 的 DirectProject/Thread Manager 定向测试。
2. 相关前端 Vitest 与类型检查。
3. `npm run check:encoding`
4. `npm run check:doc-index`
5. `git diff --check`
## 风险与回滚点
- 现有 app-server 事件模型与公开 raw event envelope 不完全一致:先在适配层收口,不让协议细节泄漏到前端。
- 单 Vec 队列不能中间删除;unfinished item 长时间不结束可能暂时 pin 住队头,必须保留可观测上限和测试。
- Tauri command 无传输层断开回调,subscriber 只通过 queue eviction 失效;测试不能依赖 unsubscribe 或连接断开清理。
- legacy 删除属于 breaking history 行为;失败关闭测试必须确认不会 fallback 或迁移。
@@ -0,0 +1,36 @@
# 【实施计划】DirectProject 用户 Response item 输入
| 字段 | 值 |
| --- | --- |
| Milestone | `docs/project-memory/plans/【里程碑】DirectProject用户ResponseItem输入-2026-09-15.md` |
| Status | in-progress |
| Owner | Codex |
## 修改边界
- 允许修改:AGC 壳 Rust agent 输入合同、DirectProject 历史适配、前端聊天引用模型、ts-rs 生成配置、当前聊天素材文档。
- 明确不修改:assistant 返回协议、工具 activity、附件/图片协议、SpacetimeDB、HTTP API。
## 实现顺序
1. 提取深模块:Rust canonical user item 的定义、校验和 Codex wire 转换。
2. 生成并接入 ts-rs 类型,排除生成文件 lint/style。
3. 将前端 Lexical 草稿从 `text + references[]` 改为 inline content parts。
4. 修改 Tauri command 与 DirectProject turn:校验通过后持久化 canonical item,再发送转换后的 Codex 输入。
5. 删除本链路对 legacy conversation 行的读取 fallback,保留标准 `response_item`
6. 补齐定向测试与文档验证;每个独立切片形成小提交。
## 验证命令
1. `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml direct_codex`
2. 相关前端 Vitest 与类型检查。
3. `npm run check:encoding`
4. `npm run check:doc-index`
5. `git diff --check`
## 风险与回滚点
- ts-rs 生成路径或 workspace lint 配置不一致:先固定生成入口,再接入业务。
- 历史中 canonical item 含 AGC part 时,thread replay 必须经过同一 wire converter;转换失败不得启动 turn。
- 共享工作树存在用户 `.env` 修改,禁止覆盖或提交。
- 每个切片保持独立提交,出现协议问题时按提交粒度回滚。
@@ -0,0 +1,51 @@
# 【实施计划】Direct回合跨页面生命周期与运行中项目可见性-2026-09-15
Version: 1
Status: in-progress
Date: 2026-09-15
Milestone: `【里程碑】Direct回合跨页面生命周期与运行中项目可见性-2026-09-15.md`
## 固定契约
只读快照命令(Tauri 本地命令,`src-tauri/src/agent/direct_runtime/mod.rs`):
- `list_game_creator_direct_active_turns() -> Vec<GameCreatorDirectActiveTurn>`
- 字段(camelCase):`projectPath``projectName``turnId``status``activity`(可空)、`startedAt``updatedAt``sequence`
- `status` 取值集合与既有 Direct 回合事件一致:`accepted` / `running` / `streaming` / `finalizing` / `completed` / `failed`
身份锁与快照共用同一份进程内注册表;注册表条目在回合进入时写入 `startedAt``projectName`,在每次回合事件发射时更新 `status` / `activity` / `sequence` / `updatedAt`,在回合结束(guard drop)时移除。
## 代码边界
- `apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs`:注册表结构扩展、快照读写、新命令、Rust 定向测试
- `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/entrypoints.rs`:事件发射时投影到注册表
- `apps/ai-game-creator-shell/src-tauri/src/main.rs`:命令注册
- `apps/ai-game-creator-shell/src/App.tsx`:项目打开时重连、忙碌态与进度恢复、面板挂载
- `apps/ai-game-creator-shell/src/features/agent-runtime/directActiveTurns.ts`:快照轮询与单飞刷新(面板与重连共用)
- 面板组件(新文件,落在既有 feature 目录下)+ 对应测试
- `apps/ai-game-creator-shell/src/features/agent-runtime/model.ts` + 测试:报错归类修正
## 修改顺序
1. Rust:扩展活动回合注册表并暴露只读快照命令,配定向用例(进入 / 进度 / 终态移除 / 多项目并存)。
2. 前端:接入快照读取,实现“重新进入项目 → 恢复忙碌态与进度 → 以快照 sequence 续接 → 阻止并发提交”。
3. 前端:在左上角空白区域挂载“正在运行的项目”面板,复用既有组件与设计 token。
4. 报错归类:按审计结论修正会误导的映射,逐条加回归用例;真实权限拒绝保持原提示。
5. 文档:主规范与共享记忆同步;里程碑验收后删除临时计划文件。
## 验证命令
- `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml direct_active_turns -- --test-threads=1`(名称按实际用例调整)
- `npx vitest run apps/ai-game-creator-shell/tests/agentRuntimeModel.test.ts`
- `npx vitest run apps/ai-game-creator-shell/tests/appSurface.test.ts`
- 面板组件测试文件单独一条 vitest
- `npm --prefix apps/ai-game-creator-shell run typecheck`
- `npm run check:encoding``git diff --check`
## 风险与回滚点
- 快照命令暴露项目绝对路径给前端:与现有 `projectPath` 口径一致,不得额外泄露配置或 token;命令必须是只读、无副作用。
- 续接基线 `sequence` 若取错,会让重新进入后的进度事件被丢弃或重复消费;取错时回滚“重连”部分,保留只读面板。
- 忙碌态恢复不得与既有 `chatAgentBusy` 的失败清理互相覆盖;出现卡死忙碌态时优先回滚重连,不影响身份锁与后台回合本体。
- 面板若在窄窗口挤压主内容,先按既有响应式约定隐藏面板,不改主布局。
- 报错归类修正若与既有断言冲突,先确认断言锁的是“正确行为”还是历史错误文案,再决定改断言还是改实现。
@@ -0,0 +1,39 @@
# AGC 统一错误诊断与验收反馈
Version: 1.0
Status: active
Date: 2026-09-15
Parent Spec: `docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md` 的“2026-09-15 AGC 统一错误事件、诊断落库与验收反馈”
## 目标
让 DirectProject 和共享 Agent Runtime 对失败使用同一份安全、可追踪、可恢复的错误事件合同;用户追问失败原因时能够读取上一轮证据;构建与浏览器验收只依据真实源码、manifest 身份和运行时证据判断。
## 范围
- 统一错误事件模型与项目内诊断落库。
- DirectProject 失败 assistant 投影、下一轮诊断上下文和前端详情入口。
- app-server 终态/超时、内置 MCP 工具错误和试玩 attempt 上限的分类。
- 游戏源码模块素材扫描、manifest 身份映射与浏览器观察映射。
- 定向 Rust/前端回归和现有 AGC 运行时门禁。
## 不做
- 不改变 Provider、External Editor 或 app-server 的 wire 协议。
- 不放宽项目写锁、凭据隔离、工具白名单或完成门安全边界。
- 不迁移历史项目文件;旧诊断只读兼容,新增事件使用新 schema。
- 不把原始 stderr、请求正文或绝对路径展示给用户。
## 验收标准
1. 任一 DirectProject 失败均生成统一事件、稳定 `eventId` 和有界诊断引用;落库失败不覆盖原始错误。
2. 失败安全投影写入对话历史,下一轮能读取 `publicText / code / stage / detailRef`,不会因追问而自动试玩。
3. 结构化 failed turn、idle/hard timeout、transport close、MCP 参数错误和 `other` 各有稳定 code 与 recoveryHint。
4. `attempt` 由客户端按回合分配并有上限;越界调用不会让回合继续等待。
5. `game/src` 下模块引用已登记素材、Vite dist 稳定映射和浏览器实际观察均能通过;未登记素材仍失败。
6. 脱敏测试证明 Token、Cookie、URL/query、私钥、宿主绝对路径和 stderr 私密内容不会进入用户文本。
## 依赖
- 现有 `direct_project_history``runtime_state``codex_app_server``direct_tool_bridge` 与浏览器 validation 证据。
- 现有 DirectProject 诊断 sidecar 和 manifest 资源身份。
@@ -0,0 +1,53 @@
# 【里程碑】DirectProject Thread Manager 事件订阅
| 字段 | 值 |
| --- | --- |
| Version | 1.0 |
| Status | ready |
| Date | 2026-09-15 |
| Parent Spec | `docs/technical/【技术方案】DirectProject Codex原始历史与异常恢复-2026-09-04.md` |
## 目标
让 DirectProject 对话在页面离开、重进和短暂断线后仍能由前端重建运行态;运行态事件由 Tauri 进程级 Thread Manager 管理,已完成 item 继续以 `project.jsonl` 为持久化事实源。
## 范围
- 每 thread 一个全局 seq 和 append-only replay queue。
- 每 subscriber 独立的 Rust 内部 cursor、并发安全消费和 notify 唤醒。
- `subscribe` bootstrap、`consume``SUBSCRIPTION_EXPIRED` 和历史 item 锚点。
- app-server 公开事件的安全标准化、item 持久化先于完成事件转发。
- 前端 raw event reducer、历史懒加载和过期重订阅。
- 删除本链路 legacy conversation 格式支持,不提供 fallback 或 migration。
## 不在范围内
- SpacetimeDB、HTTP API、Codex thread durable recovery。
- 新的 item durable/status/pendingInteraction 字段或持久化确认事件。
- 前端访问 JSONL 路径、格式或持久化细节。
- 多 active turn;同一 thread 仍只有一个 active turn。
## 依赖与前置条件
- DirectProject 现有 app-server 事件解析和 `project.jsonl` 读写。
- 当前 Tauri command/event 注册入口。
- 现有前端 DirectProject 聊天 reducer 与历史加载入口。
## 验收标准
- [ ] 页面离开后 app-server 回合继续,重进页面能通过 subscribe 重建 unfinished item。
- [ ] 同一 thread 的多个 subscriber 各自消费,不互相覆盖或重复推进 cursor。
- [ ] `consume` 返回 cursor 之后的全局 raw events,通知不携带 payload。
- [ ] queue eviction 只清理队头;落后 subscriber 得到 `SUBSCRIPTION_EXPIRED` 并可重新 subscribe。
- [ ] item 完成先持久化,成功后才进入完成事件队列;失败不发送正常完成事件。
- [ ] `turn.completed` 由 app-server 终态进入 raw queue,前端据此结束运行态。
- [ ] subscribe 返回 item 历史锚点而不是完整 history;前端可按 itemId 懒加载。
- [ ] 未完成 item 的每个 delta 可从 `item.started` 开始重放;不截断 active item。
- [ ] legacy conversation 行直接失败关闭,无 fallback、无迁移。
- [x] DirectProject 首页历史切片存在 `hasMore` 时,即使当前可见窗口没有隐藏消息,也提供按钮和滚动两种“显示更早”入口。
## 证据要求
- 自动化:queue/cursor/eviction 并发单测、事件标准化和持久化顺序测试、Tauri command 测试、前端 reducer 与重订阅测试。
- 运行时:关闭/切页后重进 DirectProject;并发 item;短暂断线 consumecursor 过期重订阅。
- 边界:持久化失败、未知 subscription、queue 超限、多个 subscriber、turn 无 item 间隙、legacy 行拒绝。
@@ -0,0 +1,51 @@
# 【里程碑】DirectProject 用户 Response item 输入
| 字段 | 值 |
| --- | --- |
| Version | 1.0 |
| Status | proposed |
| Date | 2026-09-15 |
| Parent Spec | `docs/【功能说明】AGC聊天素材引用-2026-09-08.md` |
## 目标
将 DirectProject 的用户消息升级为受限 Response API `message` item:文本与 AGC 引用按编辑顺序内联,Rust 校验后持久化 canonical item,并转换为 Codex 可接受的输入。
## 范围
- Rust 定义 user item 与 content part,并通过 `ts-rs` 生成 TypeScript 绑定。
- 前端 Lexical 草稿生成 inline `content[]`
- `agc_resource_reference` 只使用稳定 `resourceId`
- `agc_runtime_region_reference` 保留运行区域语义摘要。
- Rust 在持久化前完成白名单、manifest 与路径校验。
- 现有标准 `response_item` 原样兼容;legacy conversation 行不提供 fallback。
- 保持 assistant 返回、工具 activity、附件/图片协议不变。
## 不在范围内
- assistant item 前端投影或 Tauri 返回值改造。
- 工具 item、reasoning、file change、MCP item 的 UI 模型化。
- 附件/图片 content part。
- SpacetimeDB schema 或 HTTP API 变更。
## 依赖与前置条件
- DirectProject 现有 app-server thread/inject_items/turn/start 链路。
- 项目 manifest 作为资源身份与路径权威。
- 现有 `project.jsonl``response_item` envelope。
## 验收标准
- [x] 前端生成的 canonical user item 保留 Lexical 中文本与引用的相对顺序。
- [x] `agc_resource_reference` 仅包含 `resourceId`,显示信息由 manifest 派生。
- [x] runtime-region 字段经过 Rust 有界清洗并验证关联资源。
- [x] 未知 part、失效资源或非法路径在持久化前失败关闭。
- [x] canonical item 以 `response_item` 写入历史,标准旧 item 原样可读。
- [x] Codex wire input 不含 AGC 私有 part,且顺序与 canonical content 一致。
- [ ] assistant、附件和工具链路行为无变化。
## 证据要求
- 自动化:Rust item 校验/转换/历史测试;前端草稿顺序与类型测试;ts-rs 生成检查。
- 运行时:DirectProject 本地 app-server smoke(如环境可用)。
- 边界:未知 part、资源删除、非法路径、重复提交 clientTurnId、legacy 行拒绝。
@@ -0,0 +1,33 @@
# 【里程碑】Direct回合跨页面生命周期与运行中项目可见性-2026-09-15
Version: 1
Status: in-progress
Date: 2026-09-15
Parent Spec: `docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`「2026-09-15 Direct 回合跨页面生命周期与运行中项目可见性」
## 目标
离开项目界面不再等于“回合消失”:后台继续跑的 Direct 回合必须能被前端重新发现并续接进度,同一项目在回合结束前不允许再发起第二条付费回合;壳层左上角提供“正在运行的项目”面板,列出当前确有在跑回合的项目并可点击进入。
## 边界
- 只读投影:新增命令只读当前 GUI 进程内的活动回合注册表,不写项目文件、不新增持久化账本。
- 不新增取消入口;不改变身份锁排他性、项目写锁语义、计费与幂等身份。
- 不新增跨端契约(Tauri 本地命令,不进 `packages/shared` / `shared-contracts` / OpenAPI)。
- 面板与重连共用同一份快照,不各自维护第二份“谁在跑”的真相。
- 报错归类修正只处理“说明与真相无关”的情况,不放宽身份锁、不吞真实失败。
## 验收标准
- 重新进入有在跑回合的项目后:界面进入“正在处理”、显示最近一次进度、以快照 `sequence` 续接后续事件;回合结束前提交第二条需求不会真正发起第二条付费回合。
- 回合结束(completed / failed)后:忙碌态解除、可以再次发送;不重复追加助手消息。
- 无在跑回合的项目:行为与今天一致(可正常发送,不出现额外提示或阻塞)。
- 左上角面板:列出所有在跑项目,按 `startedAt` 升序,显示项目名(缺失时回退目录名)与状态/时长,点击进入对应项目;没有在跑回合时不渲染面板外壳。
- 快照读取失败不得阻断发送、不得显示成业务失败。
- 已修的错误映射不回归:`direct-codex-turn-already-running:` 与历史同义中文正文都归一到“仍在处理这个项目的上一条需求”;真正的 `项目权限策略拒绝执行:<command>` 仍显示审批提示。
## 未决事项
- “离开页面即取消”仍是未采纳的另一种语义;本轮只实现后台继续。
- 应用重启后的“未完成回合”恢复不在本里程碑范围(回合注册表是进程内状态);若未来要求跨重启恢复,需要另立里程碑并定义持久化身份与对账合同。
- 面板是否需要展示非 Direct(专业 Agent / 策划 Agent)运行中的项目,本轮不做;先把 Direct 回合这条事实链路做正确。
@@ -8740,3 +8740,26 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- 决策(面板可关 + 非模态任务面板):两块生成浮层在提交期间放开 × / 遮罩 / Esc,提交按钮旁给「后台运行并关闭」;**关闭 ≠ 取消**(表单的 `await` 挂在该任务的终局上,不是面板生命周期)。新增「生成任务」非模态浮层(不铺遮罩、不做焦点陷阱、**不进** `isResourceCanvasFloatingPanelOpen` / `resourceCanvasHostGenerationPanelOpen` 遮挡判据),入口按钮 `aria-label="生成任务"`;已完成的条目按 `assetId` 复用既有 `pendingResourceFocusRef` 聚焦链定位素材卡。
- 影响范围:新增 `apps/ai-game-creator-shell/src-tauri/src/asset_generation_tasks.rs`+ `main.rs` 注册)、`src/features/resource-canvas/{resourceCanvasAssetGenerationTaskModel.ts,resourceCanvasAssetGenerationQueue.ts,ResourceCanvasAssetGenerationTasksPanelView.tsx}`;改动 `ResourceCanvasAssetGenerationPanelView.tsx` / `ResourceCanvasGenerationPanelView.tsx` / `src/view/project-development/index.tsx`;测试改动 `tests/{resourceCanvasAssetGenerationBackgroundClose.test.tsx,resourceCanvasAssetGenerationQueue.test.ts,resourceCanvasAssetGenerationTasksPanel.test.tsx}`(新增)与 `tests/appSurface/project-development.suite.ts`(把「每个入口一次 `generate_local_project_asset`」改成 `start_local_project_asset_generation` + `list_...` 轮询桩,载荷断言逐字不变)。**未动**external v1 / OpenAPI、`packages/`、SpacetimeDB、音频入口的 pending-edit 账本语义、生成参数与 IPC 载荷字段名。
- 关联文档:`docs/technical/【AGC】栏目画布底部工具栏入口矩阵-2026-09-13.md`(§4 / §4a / §8)、`docs/technical/【测试用例】AGC资源工作台V3端到端验收-2026-09-11.md`S11a / §7.3)。
## 2026-09-15 非 Suno 的 VectorEngine 能力切换到 Tiantoken
- 决策:新增本地私密环境变量 `TIANTOKEN_BASE_URL` / `TIANTOKEN_API_KEY`(图片 timeout 可独立配置),承载原 VectorEngine 的文本和图片;`VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` 仅保留给 Suno 背景音乐与 Suno 音效。编辑器 SFX V2 继续走 ElevenLabs。
- 实现边界:api-server 在创建状态时冻结 Tiantoken 配置,LLM、图片和旧版非 Suno 音频按该配置路由;Suno 的提交 / 轮询仍使用旧 VectorEngine 配置。旧 `vector_engine_*` 测试构造保留为 Tiantoken fallback,生产新环境变量优先。
- 验证:Tiantoken `/v1/models` 返回 HTTP 200126 个模型,含 `gpt-image-2``gpt-5.4-mini`);api-server Tiantoken 配置单测、platform-audio 全量测试、图片定向测试、前端 `apiClient` 定向测试、`npm run typecheck``npm run check:api-server-env`、编码 / fmt / diff 检查通过。未对音频上游提交生成任务,模型列表未列出 audio / Vidu 条目。
## 2026-09-15 删除旧版 Vidu 音效实现
- 决策:旧版 Vidu `audio1.0` 的 submit / poll / download builder、旧视觉小说与创建音效死代码、对应 platform-audio 请求类型和测试全部删除。历史素材的 `audio1.0` 展示与定价兼容数据保留;新编辑器音效仍只走 ElevenLabs,Suno 音乐链路不变。
- 验证:platform-audio 全量测试 55 条通过,api-server `cargo check` 通过,fmt / 编码 / diff 检查通过;仓库现役源码不再包含 `VIDU_AUDIO_MODEL``AudioTaskKind::SoundEffect` 或 Vidu submit/poll 实现。
## 2026-09-15 AGC 统一错误事件与项目诊断落库
- 背景:DirectProject 的 app-server 超时、MCP 参数错误、浏览器完成门误判和普通 Agent Runtime 失败分别投影为短文案;失败正文没有稳定落库,下一轮模型看不到上一轮失败证据,用户追问原因时可能继续试玩或重复修改。
- 决策:新增 `agent/runtime_error.rs` 作为统一错误事件与有界诊断 sidecar 边界。DirectProject 失败、Agent Runtime terminal failure 均持久化 `.agent/runtime/errors/<eventId>.json`,并将脱敏 assistant 终态写回 `project.jsonl`;前端只通过 `read_agent_runtime_error_detail` 读取脱敏详情。旧 `failure.json` 保留兼容,不把原始 stderr、凭据、URL/query、宿主绝对路径写入用户文本。
- 决策:错误使用稳定 `source / stage / code / retryable / publicText / recoveryHint / detailRef` 字段;试玩 attempt 越界返回终态错误并停止继续等待。素材完成门扫描实际 npm 源码模块,并把 manifest 中合法的自定义 art-spritesheet 路径纳入候选,构建和浏览器观察仍需通过既有完成门。
- 关联规范:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md` 的“2026-09-15 AGC 统一错误事件、诊断落库与验收反馈”;开发期计划见 `docs/project-memory/plans/【里程碑】AGC统一错误诊断与验收反馈-2026-09-15.md` 与对应实施计划。
## 2026-09-15 Direct 回合跨页面继续运行与活动项目面板
- 决策:采用后台继续运行语义。Direct 回合由进程内项目身份锁持有,页面离开不取消;重进项目通过活动回合只读快照与 Thread Manager bootstrap/consume 恢复忙碌态和进度。左上角面板复用同一快照列出正在运行的 Direct 项目并支持进入。
- 边界:快照不写项目文件、不进入公共 API、不跨应用重启恢复;读取失败保留上一份结果并单独提示,不改写成权限或审批失败。身份锁排他性、付费身份和项目写锁不变。
@@ -5596,3 +5596,10 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- 原因:健康检查只能证明“有服务响应”,不能证明服务属于当前工作树;旧 `.app/dev-stack.json` 可能没有当前 `repoRoot``instanceId` 和服务级 dataDir 身份。
- 处理:先读取 `.app/dev-stack.json`,核对顶层 `repoRoot + instanceId`,再核对服务 `repoRoot + instanceId + dataDir + pid + port`AGC Vite marker 还必须带 `repoRoot + processId + port`。任何字段缺失或不匹配都拒绝静默复用,改为启动当前工作树自己的服务或明确提示清理。
- 验证:`scripts/dev.test.ts``apps/ai-game-creator-shell/tests/start-dev-stack.test.ts` 覆盖 snapshot identity 和旧状态拒绝复用;运行时记录实际端口、进程命令行和 dataDir,不要只记录 HTTP 200。
## 2026-09-15 登录失败提示必须保留接口返回原因
- **现象**:账号登录失败时页面只显示“登录失败”,用户无法判断是手机号、验证码、密码还是服务状态问题。
- **原因**:统一错误解析器只处理标准 `error.message/details` 结构;部分网关或旧兼容响应使用字符串 `error`,解析失败后回落到登录接口传入的通用文案。
- **处理**`parseApiErrorMessage` 同时支持字符串 `error`,标准嵌套结构保持原有优先级;未知或空响应继续使用通用兜底。
- **验证**`src/services/apiClient.test.ts` 新增字符串错误响应回归用例,定向测试 32 项通过,`npm run typecheck` 通过。
@@ -1,5 +1,11 @@
# AI 游戏创作智能体 App 实施计划
## 2026-09-15 DirectProject 长回合平台会话保活
DirectProject 的生图、素材处理、构建和试玩可能跨越短生命周期 access token 的有效期。普通 `/api/*` 请求和 Codex app-server 已有 401 刷新路径,但 AGC 工具由 Rust 工具桥直接使用客户端当前会话,工具内部的 401 不会自动触发前端刷新。客户端在 DirectProject 回合处于 busy 状态时每 5 分钟调用现有 `requestPlatformSessionRefresh()`;刷新仍复用单飞请求、generation 校验和 native session 安装,不改变凭据来源,也不把 401 降级为成功。刷新失败保持静默,由原始 AGC 工具错误按现有鉴权失败合同返回,避免后台保活覆盖真实错误。
完成门禁同时允许已登记的普通平台图片作为运行时素材。此前只把 canonical art-spec、背景、图集和图集切片加入来源白名单;`agc_generate_image` 生成的 `assets/neon-*.png` 即使已经登记并被源码引用,也会被判成“未引用平台图片”,触发同一回合的重复修复。浏览器预览把本地图片 URL 改写成 UUID 路径时,验收按每个视口的已渲染本地图片数量与源码引用数量做有界匹配;仍要求两个视口都有对应观察,空视口继续进入修复。
## 2026-09-12 已有项目打开响应性
DirectProject 工作区只恢复自身对话,不按专业 Agent 默认任务占位行批量读取旧会话或生成专业 Agent 文本回执。专业 Agent 结果加载 effect 必须以当前 Runtime 模式为边界,并在模式切换时清空旧结果。仍供开发入口使用的 `read_local_conversation` 在 blocking worker 内完整执行权限校验、会话目录解析和历史读取,避免文件访问或锁等待阻塞 Tauri 窗口线程。
@@ -57,6 +63,7 @@ npm 游戏的可预览产物固定为对应 package 目录下的 `dist/index.htm
- AGC 客户端启动恢复按“读取本地凭据 → 刷新会话(无 token 或失效时)→ 读取当前用户 → Tauri 本地运行时会话安装”阶段执行。界面必须展示当前阶段和已等待时间;不能以无期限的单一 loading 文案隐藏网络或 Runner 故障。
- 客户端 HTTP 传输默认使用 15 秒超时并通过独立 `AbortController` 终止请求;调用方可为确需长耗时的请求显式传入 `timeoutMs: null`。调用方主动取消仍保留原始 `AbortError`,超时使用稳定的 `ClientHttpTimeoutError`,由认证层转换为可操作的中文提示。
- 会话恢复或本地 Runner 连接超时后必须进入登录页并提供“重试登录状态检查”。重试递增恢复代次并以运行标识忽略旧恢复任务的迟到 UI 写回;不得清除仍可用于后续重试的 access token,也不得重复并发刷新同一服务器的 refresh 请求。
- AGC 壳启动认证遇到空响应、非 JSON 维护页或 5xx 时,必须按 HTTP 状态生成可操作的中文提示(503 明确标记服务暂不可用/可能维护),不能退化为 `读取当前用户失败`;后端返回的结构化错误 message 仍优先展示。
- Tauri Runner 的启动与 IPC 超时继续以 `runner/protocol.rs` 的 30 秒启动、10 秒读写为权威;启动等待循环会把 endpoint 探测预算裁剪到剩余启动期限,避免单次 ping 把 30 秒门禁延长。考虑复用旧 endpoint 前可能先消耗一次 IPC 等待,前端 UI 兜底取 45 秒,不改变 Runner 协议、启动策略或认证接口。
## 2026-08-26 运行中自主扩图提案
@@ -1379,3 +1386,23 @@ DirectProject 使用 `approvalPolicy=never`,避免每次原生调用再经过
## 2026-09-14 新游戏策划到真实美术接入的连续交付
DirectProject 在收到完整游戏策划或游戏制作请求后,必须把视觉素材作为同一交付链路处理:先读取当前项目已登记资源;策划案包含角色、对象、背景、特效、界面或其它视觉实体且现有资源不满足时,Codex 必须在同一游戏实现任务中调用审核的 `agc_tools` 生图或编辑工具,读取返回的资源身份与相对路径,把真实产物接入游戏源码,再构建并验证实际渲染。生成了素材但源码仍使用 emoji、CSS 形状或临时占位图替代策划要求的视觉元素,不能报告游戏完成。只有策划明确不需要视觉素材,或现有已登记素材完全满足需求时,才允许跳过生图;图片生成、处理、登记和接入不因用户没有重复输入“生图”而降级为可选建议。
## 2026-09-15 AGC 统一错误事件、诊断落库与验收反馈
DirectProject、Agent Runtime、Provider、app-server、内置 MCP、命令执行、构建和浏览器试玩的失败必须先转换为统一的 `AgentRuntimeErrorEvent`,再分别投影到用户消息、运行面板和项目诊断文件;业务模块不得自行拼接只有一句“执行失败”的终态文案。统一事件至少包含 `schemaVersion / eventId / clientTurnId / source / stage / code / retryable / occurredAt / elapsedMs / publicText / recoveryHint / detailRef`,其中 `publicText` 是脱敏后的可行动摘要,`detailRef` 指向项目内有界诊断记录;Token、Cookie、URL/query、私钥、宿主绝对路径、原始请求正文和未脱敏 stderr 不得进入对话或用户可见文本。
项目内统一落库目录为 `.agent/runtime/errors/`,事件记录采用幂等 JSONL 或 JSON sidecar;写入失败不能覆盖原始业务错误,但必须在事件中标记 `persistenceFailed`。DirectProject 对话历史必须持久化本轮用户消息、终态错误的安全 assistant 投影和诊断引用,使下一轮能够读取上一轮失败证据。前端只展示 `publicText`,点击详情后按 `detailRef` 读取有界、脱敏的诊断,不直接展示私有 `detail`
`turn/completed` 等待超时必须区分 `idle-timeout``hard-timeout``transport-closed``failed-turn``invalid-terminal``tool-error`;收到内置工具参数错误后必须结束当前工具调用并进入可行动终态,不能继续使用越界的试玩 `attempt` 或无限等待。试玩次数由客户端按当前 `clientTurnId` 持久化分配,模型不能自由递增;超过上限必须返回一次终态并停止回合。
游戏素材完成门必须扫描实际参与构建的 `game/` 源码模块,读取 manifest 的登记身份与相对路径,并把构建后的 URL 映射回登记身份。固定素材路径只能作为兼容候选,不能作为唯一准入。已登记且被真实源码引用、被构建纳入并在浏览器证据中观察到的资源通过;未登记、来源不匹配或只存在于设计规范中的资源继续失败关闭。
验收至少覆盖:普通错误、结构化 app-server failed turn、idle/hard timeout、MCP 参数错误、历史落库失败、脱敏边界、下一轮诊断上下文、源码子模块素材引用、Vite 构建 URL 映射以及试玩次数上限。统一错误事件和诊断落库先于 UI 美化或增加重试预算;不能用延长超时、删除完成门或把失败投影为成功来规避问题。
## 2026-09-15 Direct 回合跨页面生命周期与运行中项目可见性
Direct 回合的所有权属于进程内项目身份锁,不属于当前页面。离开工作台或切换到首页时,正在运行的回合继续执行;重新进入项目时,前端先读取同一份只读活动回合快照,再通过 Thread Manager 订阅 bootstrap 和后续事件恢复忙碌态、进度与未完成回复。活动回合结束后移除快照并解除发送阻断;没有活动回合的项目保持原有发送行为。
壳层左上角的“正在运行”面板只呈现活动 Direct 回合快照,按开始时间排序,显示项目名、状态、活动时长并允许进入对应项目。快照读取失败只显示读取失败并保留上一份结果,不得改写成权限、审批或业务失败;面板不建立第二份运行真相。应用重启后的恢复、取消入口和非 Direct Agent 项目不在本合同内。
活动回合快照命令是进程内 Tauri 只读命令,不进入公共 API 或持久化协议;字段包含 `projectPath / projectName / turnId / status / activity / startedAt / updatedAt / sequence`,状态和序号与既有 Direct 回合进度事件一致。
@@ -1,6 +1,6 @@
# DirectProject Codex 原始历史与异常恢复
更新时间:`2026-09-11`
更新时间:`2026-09-15`
## 目标
@@ -16,16 +16,18 @@ DirectProject 只使用 `.agent/conversations/project.jsonl` 作为对话历史
{"type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"你好"}]}}
```
`payload` 必须是未经改写的 Responses item。Direct 回合不由浏览器预写用户 message;Codex 返回的 `rawResponseItem/completed.params.item` 原样追加。显式的本地 user/assistant 补写只能通过受权限保护的 `append_direct_project_conversation_message` 命令完成。native 工具、MCP 工具、reasoning、调用参数和调用结果都保留完整内容,不截断、不摘要、不保存 delta/started 事件。
`project.jsonl``payload` 必须是未经改写的 Responses item。AGC 前端 user input 先以 canonical user message item 形式写入;发送给 app-server 前由 Rust 投影为 Codex 可接受的 `message` itemAGC 私有 content part 不会穿透到 wire。Codex 返回的 `rawResponseItem/completed.params.item` 原样追加。native 工具、MCP 工具、reasoning、调用参数和调用结果都保留完整内容,不截断、不摘要、不保存运行态 delta/started 事件。
Thread Manager 的运行态事件是另一份内存协议:app-server 通知先经过安全投影,再只发送 item 类型、item ID、delta 文本和 turn 终态等必要字段;不得把完整 item、工具参数或调用结果转发到前端。完整 item 仍只通过上述 JSONL 历史读取。
DirectProject 自己的写侧只写新格式:格式切换(#282)时仍会写旧行的路径已收口——显式 Codex 返回只落在自己的 journal `.agent/conversations/codex-responses.jsonl`,不再投影进 `project.jsonl`
侧白名单兼容旧 `{role,content}` 行:格式切换前,DirectProject 主对话由通用对话写入器落到同一份 `project.jsonl`,存量用户项目的历史文件整份都是这种行。读取时把**明确枚举的那一种**旧行形状(`schemaVersion=game-creator-conversation.v1`、无 `type`、role 在 legacy 写入器自己的角色集合 `user`/`assistant`/`tool` 内、content 为非空字符串)认下来:`user`/`assistant` 投影成与 `direct_project_local_message_item` 同形状的 message item`role``content` 逐字节保留;`tool` 行已识别但不进 Codex 上下文(它不是 Responses item,无法还原成真正的工具 item,聊天投影本来也只展示 user/assistant),与 developer/system item 同样过滤。角色集合取的是 `project/conversation.rs` 里那条 `matches!(role, "user" | "assistant" | "tool")` 校验,所以「legacy 写入器能写出的行」被完整覆盖;白名单之外的角色、换了 `schemaVersion`、带 `type`、content 非字符串或为空、缺 `payload`、坏 JSON 仍按损坏失败关闭。这条兼容是只读的,不迁移、不改写历史文件
取只接受 `response_item` envelope;旧 `game-creator-conversation.v1` 行不提供迁移或 fallback,直接失败关闭。未知或无法投影的 canonical item 在发送前失败,不能产生本轮新增历史
## 正常回合
1. 启动 `ephemeral: true` 线程,并启用 `experimentalRawEvents: true`
2. 新线程先把历史 item 数组一次注入;注入成功后执行新的 `turn/start`。本轮用户 item 只接受 Codex 回传的 `rawResponseItem/completed`,不由 AGC 预写
2. 新线程先把历史 item 数组逐项投影为 Codex 可接受 item 后一次注入;注入成功后执行新的 `turn/start`。本轮 canonical user item 在发送前完成同样的投影校验,再写入项目历史
3. 收到 `rawResponseItem/completed` 后立即追加其 `params.item` 并 flush。
4. 正常 `turn/completed: completed` 不生成额外记录。
@@ -55,12 +57,75 @@ Codex 启动时注入的 `host_skills.instructions`、`permissions.instructions`
DirectProject 的浏览器层只负责显示和乐观状态,不再调用通用对话写入器。历史读写与回合累计分别位于 `agent/direct_project_history.rs``agent/direct_project_turn_history.rs`
`project.jsonl` 不是 DirectProject 独享的写入方:通用对话链(`project/conversation.rs`)把「项目主对话」(`agent_id=None`)映射到同一份文件,非 DirectProject 模式(`codex_cli`/`provider`)的项目对话、以及 Agent Runtime 的项目级公开状态消息(`agent/runtime_state.rs` 的 public status 写入点)都由它追加 `game-creator-conversation.v1` 行。两条链的行形状不同但**互读兼容**:DirectProject 侧投影旧行(见上),通用对话侧跳过 `type=response_item` 且带 `payload` 的行(不把它二次投影成自己的记录,DirectProject 侧已经拥有那份投影),其余坏行两侧都失败关闭。因此同一份文件里出现两种行不会让任何一侧失败
这里**故意不给通用对话写入器加「文件已属于 DirectProject 就拒绝追加」的硬报错**:这些写入点不是尽力而为的旁路——`agent/runtime_driver/task_start.rs``ensure_game_creator_agent_runtime_accepted_public_status_at` 返回 `Err` 时会直接中止本次后台任务(「后台任务启动确认落盘失败,任务未执行」),`agent/runtime_protocol/steering.rs` 的三处调用也用 `?` 上抛。加硬报错会把「旧行噪声」换成「任务起不来」,比它要解决的问题更糟;而毒化本身已经不可能发生——legacy 写入器的行形状与角色集合都被 `conversation.rs` 的校验穷举,全部落在 DirectProject 的读侧白名单内。
`project.jsonl` DirectProject 现行合同只允许 `response_item` envelope。其它模式产生的旧 conversation 行不属于本合同,不得注入 DirectProject
## 写入与损坏边界
写入使用 `write_all + flush`。读取时允许丢弃文件末尾一条不完整 JSON 行;白名单化的旧行投影成 message item;其余中间坏行直接失败。兼容只发生在读取侧,不对旧格式做数据迁移或改写
写入使用 `write_all + flush`。读取时允许丢弃文件末尾一条不完整 JSON 行;`response_item` 行和无法投影的 item 直接失败,不做数据迁移或 fallback
该失败有专门恢复提示,并按不可重试处理:同一份历史文件每次读都会得到同一结论,重试不会改变结果,因此不会向用户显示「可直接重试」。
## Thread Manager 运行态事件订阅
DirectProject 的页面不是回合执行的所有者。Tauri 进程内的 Thread Manager 按 thread 维护运行态事件,并允许同一 thread 存在多个独立 subscriber。事件队列只服务运行期间和短期断线恢复,不替代 `project.jsonl` 历史事实源。
### 公开契约
概念接口如下:
```ts
subscribe(threadId) -> {
subscriptionId,
lastCompletedItemId: string | null,
events: RawEvent[],
}
consume(subscriptionId) -> {
events: RawEvent[],
}
notify -> { subscriptionId }
readHistory(threadId, { beforeItemId?, limit }) -> {
items: CompletedItem[],
hasMore: boolean,
}
```
`subscribe` 不返回完整历史。`lastCompletedItemId` 只是历史读取锚点,前端自行按 item ID 懒加载需要的历史切片。`events` 是当前运行态重建所需的未完成 item 原始事件,以及当前 turn 的生命周期锚点;前端用同一个 reducer 重放 bootstrap 和后续事件。Rust 不保存或理解前端 reducer state。只要 DirectProject 历史切片返回 `hasMore`,聊天视图必须显示“显示更早的对话”入口,并允许按钮或滚动触发下一页,即使当前可见消息窗口没有隐藏消息。
`consume` 不接收或返回 cursor。每个 subscriber 在 Rust 内部持有自己的 cursor,并在加锁的临界区内完成过期判断、读取和 cursor 前进。前端只持有 `subscriptionId` 与 reducer state。并发 `consume` 不重复返回同一批事件。
`notify` 只负责唤醒,不携带事件、cursor 或持久化状态。前端收到通知后调用 `consume`;通知可合并、重复或丢失,事件完整性由 `consume` 保证。
### 事件和顺序
Thread 内所有公开事件共用一个单调递增 seq;seq 允许跳号,前端不要求连续。事件 envelope 至少包含:
```ts
{
seq: number,
type: string,
turnId: string,
itemId?: string,
payload: unknown,
}
```
进入 Thread Manager 的是已经完成安全过滤和协议标准化的公开 raw event,不是未经审查的 app-server JSON。事件可交错包含多个并发 item:`item.started``item.delta``item.completed`、approval/request/resolved 事件,以及 `turn.started``turn.completed` 生命周期事件。前端按 `turnId` / `itemId` 分发并 reduce,不需要 item 级 cursor 或第二套 reducer。
一个 thread 同时最多有一个 active turn;一个 turn 内允许多个并发 item。`turn.completed` 必须在该 turn 的完成 item 均成功持久化后进入队列,前端据此结束运行态;不能用“不存在 unfinished item”猜测 turn 是否完成。
### 队列、subscriber 和回收
每个 thread 一个 Vec-based append-only replay queue,使用逻辑 head 偏移清理前缀,不做中间删除。完成 item 的事件在持久化成功后才可进入普通 replay 回收流程;unfinished item 的事件必须保留到 item 完成,不能被普通上限截断。
队列有内部最大事件数和最大序列化字节数。超限时先标记长期落后的 subscriber 为 expired,并将其移出有效 subscriber 的最小 cursor 计算;随后只能清理队头连续、已无有效 subscriber 需要且所属 item 已持久化的事件。没有 subscriber 时,已持久化完成 item 的事件副本可以直接清理。未完成 item 的事件仍保留。
subscriber 不依赖 `unsubscribe` 或传输层断开清理。每次 `subscribe` 都创建新的独立 subscription;同一 thread 的其它 subscriber 不受影响。旧 subscription 只有在 queue eviction 后才失效,调用 `consume` 返回统一错误 `SUBSCRIPTION_EXPIRED`。前端保留旧 reducer state,重新 subscribe 完成 bootstrap 后再原子替换。
### Bootstrap 原子性和恢复
`subscribe` 必须在同一个 Thread Manager 边界注册 subscriber、捕获 queue 尾部、确定历史锚点和当前运行态事件;bootstrap 期间产生的新事件由该 subscriber 的内部 cursor 继续通过 `consume` 获取,不能丢失。
断线恢复优先调用 `consume(subscriptionId)`。subscription 仍有效时只返回该 subscriber 尚未消费的 queue 事件;subscription 已过期或 Thread Manager 重启后统一走新的 `subscribe`,再由前端按 `lastCompletedItemId` 从历史懒加载。Rust 不提供 `getItemSnapshot(itemId)`,已完成 item 始终通过历史读取。
@@ -13,7 +13,7 @@ AGC 聊天输入框支持以结构化引用标记当前项目已登记素材。
确认后素材以 `@素材名` 芯片插入编辑器,用户可以在芯片前后继续编辑自然语言,也可以单独删除芯片。芯片内部保存稳定 `resourceId`,展示名称只用于界面,不参与引用解析;资源改名后,编辑区已有芯片与候选列表都会按 `resourceId` 刷新成 manifest 的最新显示名,并同步回父级草稿。
提交时前端同时发送用户文本和 `references` 数组。Rust 在发起 Agent 回合前读取当前项目 manifest,逐项复核资源是否存在、路径是否安全,并以 manifest 中的 `id / kind / mediaType / localPath` 作为权威投影;客户端传入的路径、名称和类型不会被直接信任。已删除或不存在的资源会阻止发送并提示用户移除后重新选择
提交时前端把 Lexical 草稿直接编码为受限 Response API user `message` item`input_text` 与 AGC 引用 part 按编辑顺序内联在同一个 `content[]` 中。资源引用只携带稳定 `resourceId`;运行画面引用携带区域语义摘要及关联资源 ID。Rust 是唯一 schema source(通过 `ts-rs` 生成 TypeScript 绑定),在发起回合前完成 item 白名单、字段边界、manifest 归属和路径安全校验;校验失败时本轮不持久化、不发送。通过校验的 canonical item 以 `response_item` envelope 写入项目历史,随后由 Rust 将 AGC part 临时转换为 Codex 可接受的 `input_text`,保持原始 content 顺序。已有标准 `response_item` 原样读取与复用;旧 legacy conversation 行不再提供 fallback
当前已完成:
@@ -24,9 +24,9 @@ AGC 聊天输入框支持以结构化引用标记当前项目已登记素材。
- 素材芯片可插入、编辑和删除;
- 资源画布素材卡的选中工具条提供「引用」入口:图标本身就是 `@`,可见文案与 `title` 都只写「引用」,插入对话里的仍是 `@素材名` 芯片;
- 运行画面提供“点选素材”,可选中 HTML 区域并生成 `runtime-region` 引用;
- 提交请求携带结构化 `references`
- Rust 按 manifest 二次校验并生成安全投影
- 提交请求携带 canonical user message item
- Rust 按 manifest 二次校验、持久化 canonical item,并生成 Codex wire input
- 普通无引用消息保持原有行为;
- 素材选择面板的「当前版本素材 / 全部画布素材」两个页签与独立筛选、搜索状态;
- 资源改名后引用芯片与候选列表的显示名自动刷新;
- 切换 / 重开会话恢复草稿后光标落在文本末尾,引用按顺序追加到文本之后
- 切换 / 重开会话恢复草稿后光标落在文本末尾,引用按原 content 顺序恢复为 inline 芯片
File diff suppressed because one or more lines are too long
@@ -214,7 +214,7 @@ spacetime sql <database> "SELECT * FROM runtime_setting LIMIT 1" --server http:/
本地 `spacetime` CLI / standalone 版本必须和 `server-rs/Cargo.toml` 里锁定的 `spacetimedb` 版本一致;当前统一版本为 `2.8.3`CLI / standalone commit 固定核对为 `8e410d2842147bd8e5a32a9589cc00c19f7478e2`。若版本或 commit 错配,procedure 返回值可能在宿主侧触发 `Failed to BSATN deserialize procedure return value`api-server 最终表现为现役 settings、editor project 或 profile procedure 超时。排障时先运行 `spacetime --version`,再对照 `server-rs/Cargo.toml``spacetimedb = "..."`;其它版本可执行 `spacetime version install <version> && spacetime version use <version>`,升级后重启 `npm run dev:spacetime` 再重试。当前 `scripts/dev.mjs` 会把 tool version 和 commit 一起写入 `dev-spacetime-tool-version`,启动新 standalone 与复用已有本地进程时都要求 `2.8.3 + 8e410d28...` 同时匹配;旧版本或旧单行版本记录会拒绝复用并要求重启。2.6.1 修复了 procedure context 中调用者 `Identity` / `ConnectionId` 始终为空的回归,依赖 `ctx.sender` 鉴权时必须同时确认宿主已升级。
本地 `.env``.env.local``.env.secrets.local` 修改后必须重启 `api-server` 才会生效;若已经通过 `npm run dev` 启动完整联调,可在该终端输入 `rs api-server`。排查图片编辑器 VectorEngine 生成链路时,确认 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY``VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 只在本地或服务器密钥文件中配置,不能写入 Git。`VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 是单次 attempt 的配置上限,默认 `1000000`;配置加载层允许显式值低于该默认值,不再在读取环境变量时强制抬高。业务模型和 VectorEngine provider 首选请求都使用 `gpt-image-2`,符合条件时才回退到兜底模型 `gpt-image-2-c`;图片协议、URL / base64 响应解析、远端图片下载和 provider 侧结构化日志在 `server-rs/crates/platform-image``api-server` 只做编辑器请求编排、OSS / asset 持久化、计费和失败审计落库。`platform-image` 会在 JSON 生成和 multipart 编辑请求发送前按同一 GPT-image-2 family 规则归一显式像素尺寸;若请求发送失败,先按同一 `request_id` 查看 provider 日志与 `external_api_call_failure.metadata_json.errorSource`,当前 multipart `/v1/images/edits` 单独强制 HTTP/1.1。
本地 `.env``.env.local``.env.secrets.local` 修改后必须重启 `api-server` 才会生效;若已经通过 `npm run dev` 启动完整联调,可在该终端输入 `rs api-server`。排查图片编辑器 Tiantoken 生成链路时,确认 `TIANTOKEN_BASE_URL``TIANTOKEN_API_KEY``TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS` 只在本地或服务器密钥文件中配置,不能写入 Git。VectorEngine 配置仅保留给 Suno 音乐任务。`TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS` 是单次 attempt 的配置上限,默认 `1000000`;配置加载层允许显式值低于该默认值,不再在读取环境变量时强制抬高。业务模型和 Tiantoken provider 首选请求都使用 `gpt-image-2`,符合条件时才回退到兜底模型 `gpt-image-2-c`;图片协议、URL / base64 响应解析、远端图片下载和 provider 侧结构化日志在 `server-rs/crates/platform-image``api-server` 只做编辑器请求编排、OSS / asset 持久化、计费和失败审计落库。`platform-image` 会在 JSON 生成和 multipart 编辑请求发送前按同一 GPT-image-2 family 规则归一显式像素尺寸;若请求发送失败,先按同一 `request_id` 查看 provider 日志与 `external_api_call_failure.metadata_json.errorSource`,当前 multipart `/v1/images/edits` 单独强制 HTTP/1.1。
编辑器 ElevenLabs 音效生成只从服务端读取 `ELEVENLABS_BASE_URL``ELEVENLABS_API_KEY``ELEVENLABS_REQUEST_TIMEOUT_MS`timeout 默认 `180000ms`base URL 或 Key 缺失时失败关闭,不回退 Vidu。生产 API 与 external-generation worker 通过共享 API env 取得同一配置,模板见 `deploy/env/api-server.env.example`Key 不得进入 Web/Vite 环境、命令参数、日志、fixture 或仓库。普通测试只使用 loopback mock,禁止把真实付费请求作为 T3 自动验收。
@@ -431,7 +431,7 @@ UI 相关修改要重点验证:
npm run database:backup:oss -- --data-dir /stdb --stop-service spacetimedb.service --restart-service-after genarrative-api.service --restart-service-after genarrative-external-generation-worker@1.service --restart-service-after genarrative-external-generation-controller.service
```
脚本会将数据目录打包成 `tar.gz`,上传到 `oss://<bucket>/<prefix>/<database>/<database>-<UTC时间>.tar.gz`。生产建议做冷备份:传入 `--stop-service spacetimedb.service`,脚本会在打包前停止服务、打包后恢复服务,再上传 OSS;因 `genarrative-api.service``genarrative-external-generation-worker@*.service``genarrative-external-generation-controller.service` 都依赖 `spacetimedb.service`,生产定时冷备份还必须传入对应的 `--restart-service-after`,确保备份后 API、保底 worker 和 controller 随数据库一起恢复。`2026-06-10` release 故障就是现场 unit 漏掉 API 重启参数,`03:20` 冷备份停止 SpacetimeDB 后 API 被依赖关系一并停止,备份脚本只恢复了 SpacetimeDB,API 直到人工重启前都不可用;`2026-06-24` release 又出现同类依赖停机后只恢复 API、未恢复外部生成 worker/controller,导致图片画布生成任务长期停留在队列中。后续现场变更、provision 模板和 Jenkins 归档都必须通过 `npm run check:production-ops` 防止回退。由于 OSS 上传可能受服务器带宽限制,`Genarrative-Stdb-Module-Publish` 默认使用 `DATABASE_BACKUP_MODE=async`:先在 publish 前用 `--defer-upload` 生成本地冷备份和 `.manifest.json`,随后继续执行 publish;发布脚本退出前会用独立 `systemd-run` transient service 执行 `--upload-deferred-dir <backup-dir>`,串行补传该目录内同库的 `deferred/pending` 归档,不依赖 Jenkins 作业进程树存活。任一归档只有在 OSS archive、manifest 和 baseline state 全部上传并验真后,才按 `keep-local` 规则删除;失败归档保留原 manifest,由下次 publish 重试。`Genarrative-Full-Build-And-Deploy` 必须显式暴露并透传同一个 `DATABASE_BACKUP_MODE`,不得静默使用下游 `async`;release 已有验真冷备且明确禁止再上传时,Full 必须选择 `skip`。发布脚本在校验 wasm 后、执行 `spacetime publish` 前会等待显式 `SPACETIME_SERVER_URL``/v1/ping` 就绪,默认最多等待 `60` 秒;如生产机器冷备份恢复 `spacetimedb.service` 较慢,可临时设置 `GENARRATIVE_STDB_PUBLISH_READY_TIMEOUT_SECONDS` 调整等待时间。需要强一致发布闸门时改用 `DATABASE_BACKUP_MODE=sync`(等价脚本参数 `--backup-mode sync`),备份会在 publish 前同步打包并上传,失败会阻断 publish;确认已有其他备份窗口时才使用 `DATABASE_BACKUP_MODE=skip`(兼容脚本参数 `--skip-backup`)。若业务不能接受停机窗口,应先规划 SpacetimeDB 原生快照或主备策略,不要直接在写入中的数据目录上做热拷贝并当作强一致备份。
脚本会将数据目录打包成 `tar.gz`,上传到 `oss://<bucket>/<prefix>/<database>/<database>-<UTC时间>.tar.gz`备份使用同库进程锁:仍存活的备份进程会阻断并发执行;持有锁的进程已退出时,脚本会自动清理失效锁并重试获取,不需要人工删除锁文件。生产建议做冷备份:传入 `--stop-service spacetimedb.service`,脚本会在打包前停止服务、打包后恢复服务,再上传 OSS;因 `genarrative-api.service``genarrative-external-generation-worker@*.service``genarrative-external-generation-controller.service` 都依赖 `spacetimedb.service`,生产定时冷备份还必须传入对应的 `--restart-service-after`,确保备份后 API、保底 worker 和 controller 随数据库一起恢复。`2026-06-10` release 故障就是现场 unit 漏掉 API 重启参数,`03:20` 冷备份停止 SpacetimeDB 后 API 被依赖关系一并停止,备份脚本只恢复了 SpacetimeDB,API 直到人工重启前都不可用;`2026-06-24` release 又出现同类依赖停机后只恢复 API、未恢复外部生成 worker/controller,导致图片画布生成任务长期停留在队列中。后续现场变更、provision 模板和 Jenkins 归档都必须通过 `npm run check:production-ops` 防止回退。由于 OSS 上传可能受服务器带宽限制,`Genarrative-Stdb-Module-Publish` 默认使用 `DATABASE_BACKUP_MODE=async`:先在 publish 前用 `--defer-upload` 生成本地冷备份和 `.manifest.json`,随后继续执行 publish;发布脚本退出前会用独立 `systemd-run` transient service 执行 `--upload-deferred-dir <backup-dir>`,串行补传该目录内同库的 `deferred/pending` 归档,不依赖 Jenkins 作业进程树存活。任一归档只有在 OSS archive、manifest 和 baseline state 全部上传并验真后,才按 `keep-local` 规则删除;失败归档保留原 manifest,由下次 publish 重试。`Genarrative-Full-Build-And-Deploy` 必须显式暴露并透传同一个 `DATABASE_BACKUP_MODE`,不得静默使用下游 `async`;release 已有验真冷备且明确禁止再上传时,Full 必须选择 `skip`。发布脚本在校验 wasm 后、执行 `spacetime publish` 前会等待显式 `SPACETIME_SERVER_URL``/v1/ping` 就绪,默认最多等待 `60` 秒;如生产机器冷备份恢复 `spacetimedb.service` 较慢,可临时设置 `GENARRATIVE_STDB_PUBLISH_READY_TIMEOUT_SECONDS` 调整等待时间。需要强一致发布闸门时改用 `DATABASE_BACKUP_MODE=sync`(等价脚本参数 `--backup-mode sync`),备份会在 publish 前同步打包并上传,失败会阻断 publish;确认已有其他备份窗口时才使用 `DATABASE_BACKUP_MODE=skip`(兼容脚本参数 `--skip-backup`)。若业务不能接受停机窗口,应先规划 SpacetimeDB 原生快照或主备策略,不要直接在写入中的数据目录上做热拷贝并当作强一致备份。
生产环境变量模板在 `deploy/env/api-server.env.example`
@@ -790,14 +790,14 @@ PowerShell 下按测试文件头部示例依次设置三个必填变量,并按
该用例只从进程环境变量读取凭据,不读 `.env.secrets.local`,也不会写入任何文件。真实 API Key 一律不得提交进仓库,也不要写进 `docs/`、脚本默认值或测试 fixture;临时密钥用完应在上游及时吊销。
创意 Agent `gpt-5` 文本链路已从 APIMart 切到 VectorEngine`api-server` 读取 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` 构造 OpenAI-compatible LLM client,并自动补齐 `/v1` 前缀用于 Responses 协议。排查或切换密钥后,可在本地运行:
创意 Agent `gpt-5.4-mini` 文本链路已从 APIMart 切到 VectorEngine`api-server` 读取 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` 构造 OpenAI-compatible LLM client,并自动补齐 `/v1` 前缀后请求 `/chat/completions`。通用 `/api/llm/chat/completions` 代理使用 `GENARRATIVE_LLM_PROVIDER=openai-compatible``GENARRATIVE_LLM_BASE_URL=https://api.vectorengine.cn/v1``GENARRATIVE_LLM_MODEL=gpt-5.4-mini`,未单独配置 `GENARRATIVE_LLM_API_KEY` 时可复用 `VECTOR_ENGINE_API_KEY`。排查或切换密钥后,可在本地运行:
创意 Agent 文本链路使用 Tiantoken`api-server` 读取 `TIANTOKEN_BASE_URL` / `TIANTOKEN_API_KEY` 构造 OpenAI-compatible LLM client,并自动补齐 `/v1` 前缀用于文本协议。排查或切换密钥后,可在本地运行:
创意 Agent `gpt-5.4-mini` 文本链路使用 Tiantoken`api-server` 读取 `TIANTOKEN_BASE_URL` / `TIANTOKEN_API_KEY` 构造 OpenAI-compatible LLM client,并自动补齐 `/v1` 前缀后请求 `/chat/completions`。通用 `/api/llm/chat/completions` 代理使用 `GENARRATIVE_LLM_PROVIDER=openai-compatible``GENARRATIVE_LLM_BASE_URL=https://api.tiantoken.com/v1``GENARRATIVE_LLM_MODEL=gpt-5.4-mini`,未单独配置 `GENARRATIVE_LLM_API_KEY` 时可复用 Tiantoken 凭据。排查或切换密钥后,可在本地运行:
```bash
node scripts/test-ve-llm.mjs
```
该脚本读取仓库根目录 `.env.secrets.local` 中的 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY`,依次探测 `/v1/models``/v1/chat/completions``/v1/responses``gpt-5.4-mini` Chat Completions 和基础 JSON 输出能力;脚本只输出 HTTP 状态、耗时、模型和截断摘要,不应打印密钥。若 `.env.secrets.local` 不存在,先补本地 secrets 文件再运行,不要把 secrets 提交进仓库。
该脚本读取仓库根目录 `.env.secrets.local` 中的 `TIANTOKEN_BASE_URL``TIANTOKEN_API_KEY`,依次探测 `/v1/models``/v1/chat/completions``/v1/responses``gpt-5.4-mini` Chat Completions 和基础 JSON 输出能力;脚本只输出 HTTP 状态、耗时、模型和截断摘要,不应打印密钥。若 `.env.secrets.local` 不存在,先补本地 secrets 文件再运行,不要把 secrets 提交进仓库。
### 手机验证码短信
@@ -40,6 +40,7 @@
## 生成契约
- 前端提交到 `POST /api/editor/icon-spritesheets/generations`
- 图集拆分通过 `sliceMode` 显式选择:`connected-components` 按透明像素连通域切分(默认),`grid` 按用户提供的 `gridX × gridY` 网格切分。
- 图标规范生成在 inline 模式下也必须先建立带稳定请求指纹的 generation operation,并由编辑器生成 durable billing 边界包住共享执行器;不得在 `operation=None` 时调用 provider 后再进入原子结果持久化。
- 图标 spritesheet 的入队与实际执行路径都必须在引用解析、generation input 重建、定价和 provider / OSS 副作用之前预检 owner、项目和最终素材目录,并将返回的 canonical `projectId + assetFolderId` 回写到后续流程;请求省略目录时按实际写入的 owner 默认目录预检,worker 不得只信任入队时的旧校验结果。
- queued 图标规范生成由共享原子结果持久化使用 worker caller 中的 lease 一并完成任务并清理 lease;共享执行器返回成功后 worker 只能返回 `Ok(())`,不得再次调用 job completion。
@@ -30,7 +30,7 @@ SFX V2 已完成产品与技术口径冻结及 T0–T6 工程实施;这不表
- `prompt`:用户在输入框确认的原始语言音效描述,语义为 `userPrompt`,继续复用对外请求和持久化字段 `prompt`。消费动作前按 ECMAScript `String.trim()` 语义只删除首尾空白和行终止符,包含 `U+FEFF`;不改写内部空白、换行、标点、零宽字符或 Unicode 形式。首尾 `U+0085` 不属于该删除集合。按 Unicode code point 计数,合法范围为 `1-2048`。空值和全空白不得回退“游戏音效”。
- `actualPrompt`Worker 在正式生成时对冻结的 `userPrompt` 进行统一英文化并严格验收后得到的英文 Prompt,继续复用持久化字段 `actual_prompt`。前端不提交 `actualPrompt`ElevenLabs 只接收验收通过的 `actualPrompt`
- `model`:新任务固定 `eleven_text_to_sound_v2`UI 以禁用态模型胶囊显示 `ElevenLabs`,客户端不决定模型。历史 `audio1.0` 只作旧素材展示和其它未迁移调用方的兼容标识,不是新编辑器 SFX 任务的 alias 或 fallback。
- `model`:新任务固定 `eleven_text_to_sound_v2`UI 以禁用态模型胶囊显示 `ElevenLabs`,客户端不决定模型。历史 `audio1.0` 只作旧素材展示标识,不是新编辑器 SFX 任务的 alias 或 fallback。
- `duration``null` 表示自动时长,有限数值表示手动时长。首次打开面板默认处于自动模式,并预置最近手动值为 `5s`;手动范围 `0.5-30s`UI 步进 `0.1s`。自动模式禁用 slider 但保留最近手动值,关闭自动后恢复该值。服务端只校验有限值与范围,不把 UI 步进扩大成 provider 精度限制。
- `loop`:独立布尔参数,默认 `false`,由页面开关原样冻结并传入 ElevenLabs。Prompt 是自由文本,系统不从 Prompt 推断、同步或校验 LoopPrompt 文本与 Loop 开关不建立业务一致性门禁。
- `prompt_influence`:服务端固定 `0.3`,不向前端开放滑杆或请求字段。输出格式固定为 query `output_format=mp3_44100_128`
@@ -468,7 +468,7 @@ POST / api / editor / audios / background - music / prompts / simplifications;
- 背景音乐在 body builder 边界防御性执行幂等 canonicalization,再按 canonical Prompt 检查至少一个有效字符和最多 200 个 Unicode code point;校验通过后用 canonical Prompt 构造 Suno body。不得复用语义不同的 `normalize_limited_text`,不得提供默认 Prompt。
- Suno 音乐接口路径固定为 `/suno/submit/music``VECTOR_ENGINE_BASE_URL` 即使配置为带 `/v1` 的图片接口根,也要在 `platform-audio` 中归一为根路径后再拼接,避免误请求 `/v1/suno/submit/music`
- 新编辑器音效使用独立 ElevenLabs 直接二进制 adapter,不伪装成 Vidu / Suno 的 submit + poll 任务。adapter 负责 endpoint 归一、`xi-api-key` header、固定 query / body、单次 POST、有界二进制读取、MP3 验证和时长探测。
- Vidu `audio1.0` 的 body builder、轮询和下载能力仅保留给历史展示和其它未迁移调用方;`audio-sound-effect` 任务不进入 `/ent/v2/text2audio``/ent/v2/tasks/{taskId}/creations`,也不使用 Suno `task: "sound"`
- 旧版 Vidu `audio1.0` 的 body builder、轮询和下载代码已移除;历史 `audio1.0` 只读展示,`audio-sound-effect` 任务不进入 `/ent/v2/text2audio``/ent/v2/tasks/{taskId}/creations`,也不使用 Suno `task: "sound"`
- Suno 提交成功后的任务 ID 兼容与 wav clip 轮询逻辑只保留给背景音乐链路;`/suno/fetch/{taskId}` 返回 `audiopipe.suno.ai/?item_id=...` 时,该地址只作为 clip id 来源,不作为最终下载文件,后端继续调用 `/suno/act/wav/{clipId}` 获取稳定 wav URL,避免 worker 在不完整 chunked body 上卡满超时。
- VectorEngine 音频响应的 `code` 需要兼容 `"success"``"ok"``"0"``"200"` 以及数字 `0` / `200`;HTTP 非 2xx 时后端错误信息应透出安全的上游状态和短响应摘要,避免前端只显示笼统提交失败。
-`api-server/src/vector_engine_audio_generation/generation.rs` 原地演进现有编辑器音频 generation handler;以下正式路由保持原路径和现有注册,不新增平行 BFF:
@@ -526,7 +526,7 @@ POST / api / editor / audios / background - music / prompts / simplifications;
- BGM 边界测试覆盖 200 / 201 个纯 Unicode `White_Space` 均归一为空并禁止三动作、大量边界空白包围 `A` 后只允许生成、`A` 加 199 个内部空格再加 `B` 后只允许简化、201 个 U+200B 或 U+FEFF 只允许简化、边界空白包围 200 个 `A` 后允许补全和生成,以及 TypeScript 与 Rust 对 U+0085、U+200B 和 U+FEFF 的一致行为。
- BGM 助手入口测试覆盖 canonical 2000 字允许简化、2001 字返回 `400` 且不调用 LLM;两个助手路由 body 超过 `32 KiB` 时返回 `413`;连续合法请求不因本功能新增限流器返回 `429`
- 两个助手成功路由分别产生 `editor_background_music_prompt_completion` / `editor_background_music_prompt_simplification` tracking event,均为 `module_key = editor`、User scope;失败响应沿用普通 route tracking 只记录成功的现状。
- BGM Suno body 仍只包含 `mv``gpt_description_prompt``make_instrumental`,固定 `Suno` 胶囊和动态泥点价格不变;历史和其它未迁移 Vidu 调用方的 builder / 轮询能力保持可用,但新编辑器 SFX 任务只调用 ElevenLabs。
- BGM Suno body 仍只包含 `mv``gpt_description_prompt``make_instrumental`,固定 `Suno` 胶囊和动态泥点价格不变;旧版 Vidu builder / 轮询能力已移除,历史素材只读展示,新编辑器 SFX 任务只调用 ElevenLabs。
- `audio-sound-effect``audio-background-music` 必须由同一个音频 composer 渲染,并在组件内通过 `isSoundEffect` 分支。SFX 不得渲染 BGM 的补全 / 简化、Suno 模型和 BGM 字符规则;BGM 不得渲染 SFX 的一键优化、Loop、ElevenLabs 模型和 SFX 时长控件。两个 mode 相互切换时,菜单、预设滚动、锁、快照和助手状态不得跨分支泄漏。
- SFX V2 翻译失败时 ElevenLabs 请求数为 0;成功时每个平台 job 最多一次 ElevenLabs POST`prompt / actual_prompt`、实际时长、Loop、model、provider 和 Task ID 在队列、素材、画布、响应、刷新和重绘后保持权威一致。
- SFX 两类 LLM 请求契约测试分别断言:一键优化每次请求为 Luna + Medium + `max_completion_tokens = 8192`,翻译每次业务尝试为 Luna + Low + `max_completion_tokens = 8192`,两者都不含 `max_tokens``max_output_tokens` 或 temperature;测试命名和说明必须把 `8192` 解释为包含 reasoning 的 completion tokens 总预算。优化遇到 `finish_reason = length` 直接失败且不写回;翻译首轮 `length` 只重试一次,第二轮 `length` 最终失败且 ElevenLabs 请求数为 0。