接通实时清单更新并修复资源焦点竞态

通过统一 Runtime emitter 和受令牌保护的 Runner relay 触发 manifest 重读
为 manifest 重读增加事件合并、项目隔离和迟到响应保护
按稳定资源 ID 重构详情焦点恢复与资源删除 fallback
补充真实 App 事件链、乱序项目切换、媒体焦点和 Rust relay 回归测试
同步更新工作台技术方案、PRD 与共享项目记忆
This commit is contained in:
2026-08-05 12:48:42 +08:00
parent 96c66e619d
commit 4a3ff531fb
18 changed files with 1014 additions and 97 deletions
@@ -1,6 +1,6 @@
# AI 游戏创作项目开发工作台 PRD
更新时间:`2026-08-04`依赖视图视觉口径调整
更新时间:`2026-08-05`实时 manifest 与资源焦点状态机收口
## 1. 产品定位
@@ -129,7 +129,8 @@ idle -> focused(document|art|audio|version) -> idle
- 版本:只展示 manifest 中正式、不可变的迭代版本记录;版本卡展示项目修订、创建原因与父版本,聚焦态同时展示直接子版本和资源绑定。点击版本卡后高亮仍存在于当前资源投影中的引用资源;缺失历史资源只保留绑定身份,不生成幽灵资源卡。资源替换仍留给后续切片。
- mentor 最新决定:资源聚焦不提供工具栏,也不提供工具侧边栏。
- 点击资源后,中央主视窗从 `resources.list` 切换为 `resources.focused.document / art / audio / version`,左侧平台导航、右侧 Supervisor 对话和底部 Agent 状态栏保持原位;聚焦容器只包含标题、资源主体、必要元数据与右上角收起按钮,不使用页面级浮层或可拖动标题栏。
- 退出聚焦后恢复进入前的搜索条件、dependency / type 布局模式、资源画布滚动位置和选中资源;这些只属于当前前端会话,不写入布局 sidecar
- 焦点转换以稳定资源 ID 为准。只有从资源列表进入详情或从一个资源 ID 切换到另一个 ID 时聚焦详情 region;同一资源 ID 因 manifest 更新而重新投影时,不得抢走详情内音频 / 视频控件、文档链接或收起按钮的当前焦点
- 显式收起或按 Escape 后恢复进入前的搜索条件、dependency / type 布局模式、资源画布滚动位置和选中资源,并优先把键盘焦点还给原触发资源卡;这些只属于当前前端会话,不写入布局 sidecar。若资源已经被后台删除,必须清理 stale focused / selected ID、关闭详情并把焦点落到“搜索项目资源”,不得落到 `body`。项目切换和进入运行视图必须取消旧项目的焦点恢复意图。
- 阶段四只新增上述受控读取与媒体展示;阶段六在同一聚焦容器内补齐正式版本只读展示和引用高亮,但不新增资源聚焦工具栏 / 工具侧边栏,不新增美术编辑、音频编辑 / 替换、资源重新生成、版本替换或运行模块。飞书原需求中“编辑并生成新资源”的条件项仍暂缓,不能只打开画板却缺少回写、`referenceResourceIds` 血缘登记、新资源自动选中与邻近布局的完整闭环。
### 4.4 历史成果与当前状态
@@ -416,6 +417,8 @@ type ProjectAgentMudPointAttribution = {
7. 未识别任务产物不进入“项目版本”,只有正式版本 read model 可以生成版本卡;资源显示名称变化不改变资源身份。
8. 点击任一资源后只替换中央主视窗,右侧对话与底部 Agent 状态栏保持原位;收起或按 Escape 退出后恢复原搜索、布局模式、滚动位置、选中资源和触发资源卡键盘焦点。
9. 当前 Supervisor 运行期间 manifest 新增资产、任务状态、预览状态和正式版本后,工作台无需重开项目即可同步更新资源列表、依赖图输入、运行入口和版本卡;旧项目迟到回调不得覆盖当前项目。
10. 实时 manifest 验收必须捕获真实 App Tauri listener,并让 `get_local_game_manifest` 在非 Supervisor Agent 的 Runtime / manifest 失效事件后返回新快照;测试不得直接调用 `onManifestChange` 冒充数据源。读取合并、旧项目迟到响应和项目切换隔离必须分别有回归证据。
11. 音频或视频控件获得焦点后,同一资源 ID 的 manifest 更新不得把焦点移回详情 region;当前资源被删除后详情关闭、focused / selected ID 清理且焦点落到资源搜索框。显式收起与 Escape 的原卡片焦点和滚动恢复继续成立。
### 7.2 P1 资源画布布局持久化验收
@@ -6031,6 +6031,13 @@
- UI 决策:Project Supervisor 持有运行中 manifest 状态并向外层启动器同步完整快照;外层项目上下文继续是工作台投影的唯一输入,只接受当前项目路径的更新,不另建资产、任务或版本平行状态。
- 依赖图决策:Agent DB 尾部读取一旦截断,审计 producer、task flow 与对应 `cyclicTaskIds` 失败关闭;独立 `dependencyDepths` 仍由 Rust 从当前 manifest、精确资源引用和仍可信的任务深度下限构建,前端只做资源存在性与非负安全整数校验后继续消费。manifest 精确资源引用、reference connection index、资源环和 unresolved reference 与审计生产者证据分离。SVG 保持装饰性,辅助技术消费画布关联的文本关系列表。
## 2026-08-05 AI 游戏项目实时 manifest 失效与资源焦点状态机
- 失效源决策:后台 `task.update``canvas.asset_generate`、任务起止 / 终态投影、正式版本追加和 autonomous manifest reset 都处于 Runtime 动作或生命周期内,并在写入后回到共用 Runtime emitter;因此以该 emitter 作为统一 manifest 失效因果点,不在 WorkspaceLauncher 新增平行回调,也不轮询 manifest。Rust / TypeScript 的 `game-creator-agent-runtime-update` 合同增加 `manifestInvalidated`App 在全部 Supervisor、selected agent、session / run early return 之前消费它。
- 跨进程决策:External Runner 没有 GUI `AppHandle`,不能假设普通 Tauri Runtime event 会跨进程到达。Runner IPC 协议升级为 v5GUI owner attach 同时登记 GUI 创建的 loopback 随机端口和 64 位随机十六进制令牌;Runner 内同一 Runtime emitter 发送最小 `projectPath + agentId` relayGUI 校验令牌后转成 `game-creator-manifest-invalidated`。GUI 内 Runtime 继续直接发送完整 Runtime update。两条路径汇合到同一个 App manifest 重读器。
- 重读决策:`get_local_game_manifest` 按项目 single-flight;同项目读取中再次失效只排队一轮后续读取,不启动并发请求。响应应用必须同时匹配 mounted、活动项目路径和 project scope version,旧项目、旧 scope 或卸载后的响应全部丢弃。重读后的 App state 继续沿既有 `onManifestChange -> currentProjectContext -> ProjectDevelopmentView` 单向投影,不复制资产 / 任务 / 版本状态。
- 焦点决策:资源详情焦点以稳定 `resourceId` 的转换而非重建后的资源对象决定。`null -> id``idA -> idB` 聚焦详情;`idA -> idA` 保留详情内部 active element。显式收起 / Escape 恢复滚动并优先返回触发卡片;资源已删除时清理 focused / matching selected ID 并聚焦资源搜索框;项目或运行视图切换清除旧 trigger 与 restore 标志,禁止跨项目恢复。
## 2026-08-04 图片画布素材类型采用资源默认值与布局覆盖双层模型
- 背景:画布复制逻辑曾为副本生成 `local-resource-copy-*`,导致同一媒体被伪装成未登记资源;随后改为复用 `resourceId`,但手动修改图层标签仍通过“按新 `assetKind` 查找 / 创建项目资源并换绑当前图层”实现。这会让单纯标签修改增加资源行、漂移 `resourceId`,并在异步回填与复制交错时形成“新类型 + 旧资源”的副本。
@@ -4115,6 +4115,20 @@
- 处理:调用方在未认证时不得启动受保护的钱包刷新;可取消的读取要为每轮分配 `AbortController`,新读取先失效并中止旧读取,组件卸载时同时推进 revision、abort 当前请求并清空句柄。所有 `then / catch / finally` 在更新状态前都要检查 signal 与 revision。
- 验证:定向测试覆盖卸载后请求 signal 已中止;同时复跑触发钱包刷新回调的画布生成集成测试和完整前端测试,不能以单文件偶然快速收束代替全量验证。
## 下游 manifest 回调测试不能冒充实时数据源(2026-08-05)
- 现象:工作台的资源、任务与版本重投影单测保持绿色,但后台 Agent 已更新 `.agent/manifest.json` 后,打开中的工作台仍长期显示旧快照,只有重开项目才更新。
- 原因:测试 Supervisor 直接调用 `onManifestChange`,只证明 `App manifest -> WorkspaceLauncher -> ProjectDevelopmentView` 的下游桥接;真实 Runtime event 没有失效字段,监听器也没有重读 manifest。External Runner 又与 GUI 分属不同进程,Runner 内无法使用 GUI `AppHandle`,只补普通 Tauri event 仍不能形成生产链路。
- 处理:后台 manifest mutation 收敛到共用 Runtime emitterGUI 内进程用带 `manifestInvalidated` 的 Runtime updateExternal Runner 通过 GUI owner attach 登记的受令牌保护 loopback sink 转发专用失效事件。App 对当前项目做 single-flight manifest 重读,并以 mounted、项目路径和 scope version 丢弃迟到结果;WorkspaceLauncher 继续只消费完整 manifest 快照,不新增平行状态或轮询。
- 验证:集成测试必须渲染真实 `App + WorkspaceLauncher`、捕获真实 Tauri listener,让 `get_local_game_manifest` 从旧快照切换到新快照,并由非 Supervisor Agent 事件驱动资产、completed 任务、运行入口和版本卡出现;另测项目切换时旧请求迟到。旧的直接 `onManifestChange` 测试只能标记为下游桥接证据。
## React 资源详情焦点不能依赖重建对象身份(2026-08-05)
- 现象:音频 / 视频播放器、文档链接或收起按钮正在获得焦点时,后台 manifest 更新会把焦点突然移回详情 region;若当前资源被删除,详情虽然消失,stale focused ID 和焦点可能残留到 `body`
- 原因:资源投影每次生成新对象,`useLayoutEffect([focusedResource])` 把同一资源的内容更新误判为重新进入详情;删除路径没有显式恢复状态和可聚焦 fallback,项目 / 运行视图切换也可能沿用旧 trigger。
- 处理:焦点状态机只比较稳定 `resourceId``null -> id``idA -> idB` 聚焦详情,`idA -> idA` 保持当前 active element。显式收起 / Escape 才恢复原卡片与滚动;后台删除清理 focused / matching selected ID 并聚焦搜索框;项目或运行视图切换清空 trigger / restore。媒体预览副作用依赖稳定 ID、路径和类别,不因同 ID 对象重建先卸载控件。
- 验证:媒体控件获得焦点后用同 ID 新 manifest 重渲染并断言 active element 不变;删除资源后断言详情关闭、选中清理且搜索框获得焦点;既有收起、Escape、项目切换和运行切换测试继续通过。
## 不要用自然语言精确 `.replace()` 维护 Runtime Prompt
- 现象:Prompt 文案稍作改写、增删空格或调整段落后,替换静默失效,代码中出现难以审阅的链式 `.replace()`
@@ -895,6 +895,7 @@ game-project/
## 2026-08-04 manifest 与工作台一致性收口
- `.agent/manifest.json` 的存储写边界使用同目录持久文件锁跨线程、跨进程串行化;锁必须覆盖旧 manifest 读取、不可变版本前缀校验、临时文件安装和安装后回读一致性校验。锁文件拒绝符号链接、非普通文件和异常所有权 / 硬链接;Windows 使用不共享写句柄,Unix 使用 `O_NOFOLLOW + flock`。旧快照在新版本安装后只能被拒绝,不能覆盖已追加版本。
- 嵌入项目工作台的 Project Supervisor 在本地 manifest 状态变化时向启动器外传完整 manifest,并携带来源项目路径。启动器只更新仍为同一路径的活动项目上下文;资源列表、依赖图输入、任务状态、运行入口和正式版本卡必须在当前页面实时重投影,不要求关闭或重开项目
- 后台 Agent 的 manifest 变化以共用 Runtime 状态投影 / 终态 emitter 作为失效因果点:`game-creator-agent-runtime-update` 的 Rust / TypeScript DTO 固定携带 `manifestInvalidated`,且 App 必须在 Supervisor、selected agent、session 和 run 身份的任何 early return 之前处理失效。GUI 进程内 Runtime 直接发该事件;External Runner 是独立进程、没有 GUI `AppHandle`,因此 Runner 协议 v5 的 `runner.attach_gui_owner` 必须登记 GUI 创建的随机 loopback 端口和 64 位随机令牌,Runner 的同一 emitter 通过受令牌保护的短连接转发 `game-creator-manifest-invalidated`。两条路径都只传项目路径与 Agent 身份,不复制 manifest,也不靠轮询补偿
- App 收到当前项目的 Runtime / relay 失效后重新调用 `get_local_game_manifest`。重读按项目 single-flight 合并事件风暴;读取中再到达失效只追加一轮串行重读,不并发提交同项目响应。应用结果同时校验组件仍挂载、当前项目路径和项目 scope version;项目切换、组件卸载或旧 scope 的迟到响应不得覆盖新项目。Project Supervisor 再通过既有 `onManifestChange(projectPath, manifest)` 向启动器外传完整快照,启动器只更新仍为同一路径的活动项目上下文;资源列表、依赖图输入、任务状态、运行入口和正式版本卡必须在当前页面实时重投影,不要求关闭或重开项目。
- `.agent/agent.db` 有界尾部读取报告截断时,审计 producer 映射失败关闭,不生成基于不完整审计的 producer 或 task flow。前端收到截断 DTO 时再次清空 producer、task flow、任务环和依赖深度派生结果;只依赖 manifest 唯一外部资源 ID 的精确引用关系继续保留。
- 资源依赖 SVG 继续作为不可交互装饰层隐藏,但 dependency 画布通过 `aria-describedby` 提供当前可见精确引用和任务流的文本等价列表。中央资源聚焦关闭或按 Escape 退出后恢复触发卡片焦点;橙色引用线及箭头使用对 `#fffdfa` 画布达到至少 `3:1` 的颜色。
- 资源依赖 SVG 继续作为不可交互装饰层隐藏,但 dependency 画布通过 `aria-describedby` 提供当前可见精确引用和任务流的文本等价列表。中央资源聚焦按稳定 `resourceId` 驱动焦点状态:仅 `null -> id``idA -> idB` 聚焦详情 region,同一 ID 的 manifest 重投影不得抢走音频、视频、链接或关闭按钮焦点;显式收起和 Escape 恢复画布滚动并优先聚焦原触发卡片。聚焦资源被删除时清理 stale focused / selected ID,关闭详情并把焦点落到资源搜索框;项目切换或运行视图切换清除旧恢复意图,不得恢复旧项目卡片。橙色引用线及箭头使用对 `#fffdfa` 画布达到至少 `3:1` 的颜色。