Merge remote-tracking branch 'origin/master' into feat/ui-editor-v3
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 5m0s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m54s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 7m15s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 6m32s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 6m56s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m21s
Project CI / Frontend tests (pull_request) Failing after 4m23s
Project CI / Repository checks (pull_request) Failing after 5m22s
Project CI / Native shell tests (pull_request) Successful in 9m10s
Project CI / Backend tests (pull_request) Successful in 9m41s
Project CI / AI game creator shell web tests (pull_request) Failing after 5m32s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 5m0s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m54s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 7m15s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 6m32s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 6m56s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m21s
Project CI / Frontend tests (pull_request) Failing after 4m23s
Project CI / Repository checks (pull_request) Failing after 5m22s
Project CI / Native shell tests (pull_request) Successful in 9m10s
Project CI / Backend tests (pull_request) Successful in 9m41s
Project CI / AI game creator shell web tests (pull_request) Failing after 5m32s
# Conflicts: # apps/ai-game-creator-shell/tests/uiEditorPage.test.ts # docs/project-memory/shared-memory/decision-log.md
This commit is contained in:
@@ -151,7 +151,7 @@ Authorization: Bearer <internal-token>
|
||||
- 当前部署只有一个配置内私有 OSS bucket,因此请求只传 `sourceObjectKey`,子 worker 从自身 OSS 配置取 bucket 并生成短期签名 URL。
|
||||
- 如果未来确实支持多个 bucket,新增字段也必须由服务端 allowlist 校验;不能接受调用方提供任意下载 URL。
|
||||
- `backgroundMode` 只允许 `flat / complex`;`segModel` 继续沿用当前 `birefnet / anime-seg` allowlist;complex 固定使用当前参数组合。
|
||||
- `screenColor` 只对 flat 必填;complex 不得误接 flat 参数,两种模式的熔断状态必须隔离。
|
||||
- `screenColor` 在 flat 下可省略,也可传 `auto` 或 `#RRGGBB`;省略或 `auto` 由 BgFilter 自动识别。complex 不得携带背景色,两种模式的熔断状态必须隔离。生成角色、图集等既有链路继续传已确定的背景色。
|
||||
- `maxQueueWaitMs` 与 `callBudgetMs` 都是相对预算,不是跨机器绝对时间。前者从 admission 起约束排队阶段(worker 还会用 §5.2 的动态估计对其取 min);后者从取得 provider permit 起计时,覆盖签名、两次 attempt、结果校验和响应构造。`callBudgetMs` 是父侧按 `N / est` 公式算出的“配置指纹”,仅作核对:worker 始终以自己按同一公式派生的值执行,不一致时不拒绝请求,而是记录 warn 日志并递增漂移指标。发布调优 N / est 时新旧进程共存的瞬态漂移因此不会误伤在途任务;持久性漂移的硬拦截由部署脚本的共享 env 对齐校验承担。
|
||||
- JSON body 设置很小的固定上限;源图字节不进入该 JSON。
|
||||
|
||||
|
||||
@@ -1,6 +1,36 @@
|
||||
# AGC 后台模型别名与对话选择
|
||||
|
||||
## 契约
|
||||
## 本地自定义 LLM
|
||||
|
||||
- 本地 `game-creator.config.json` 的 `llm.customEnabled` 默认 `false`;显式设为 `true` 后,常用设置展示 API 地址、API Key、读取模型列表与勾选区域。DirectProject 沿用 OpenAI Responses 协议,地址填写 API 根地址(例如 `https://provider.example/v1`)。开关只由配置文件控制。
|
||||
- 点击读取后,客户端原生侧直接请求该地址下的 `GET /models`,使用自定义 Bearer Key,读取 OpenAI 兼容的 `data[].id`。请求有超时与响应大小上限,禁止携带平台登录凭据、禁止重定向;错误仅展示安全状态,不回显上游响应体或 Key。
|
||||
- 模型支持搜索、逐项勾选和独立的已勾选列表预览。`llm.visibleModels` 按勾选顺序保存模型 ID,第一项作为默认项。至少勾选一项才可保存;读取失败保留草稿和已勾选列表,不替用户清空或新增选择。
|
||||
- 首页与项目对话复用现有模型选择器;自定义模式只读取本地勾选目录,不请求平台模型目录。模型 ID 原样用于上游请求(允许 `/`、`.`、`:`),所选项被取消勾选时回退第一项。保存后刷新目录,下一回合使用新连接与模型,活动回合继续使用原快照。
|
||||
- 开启后,Codex 客户端凭据代理和 Rust LLM 调用均直连自定义端点,不走平台 `/api/llm` 或内置中转;配置缺失或请求失败明确报错,不回退官方路由。真实 Key 留在客户端,不进入 Codex 子进程环境、参数或模型上下文。平台图片、音频、账户和计费契约不变。
|
||||
- 配置加载、迁移和覆盖文件写入保留显式开启的连接和勾选列表;关闭时仍使用官方模型目录与官方路由。缓存不能跨自定义/官方目录或不同自定义连接复用。旧配置缺少新字段时保持官方行为。
|
||||
- 验收覆盖开关默认值、配置合并/保存/重载、直连凭据与模型传递、模型发现成功/失败/超时、勾选预览、仅显示勾选模型、目录切换、无平台目录请求和设置保存失败。自动化本地 HTTP 验证与真实供应商 smoke 分开报告。
|
||||
|
||||
在设置页显示的本地配置文件路径中,手动把现有 `llm` 对象的 `customEnabled` 设为 `true`(保留其它字段),重新打开常用设置即可配置连接和读取模型。界面没有开启开关,保存设置也不能把关闭状态改为开启。配置文件也支持直接填写以下字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"llm": {
|
||||
"customEnabled": true,
|
||||
"baseUrl": "https://provider.example/v1",
|
||||
"apiKey": "填写自己的密钥",
|
||||
"apiKind": "openai_responses",
|
||||
"visibleModels": ["provider/model-name", "another-model"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`visibleModels` 可先留空,再从端点读取并勾选;未完成勾选前不能发起自定义 LLM 对话。关闭时手动改回 `false`,后续回合使用官方目录和路由。
|
||||
|
||||
本地配置文件始终保留 `customEnabled`、`visibleModels`、`apiKey`、`baseUrl`、`model`、`apiKind`、`reasoningEffort` 七个键:官方路由下连接字段写官方地址与空 Key、协议写 `openai_responses`,便于手写自定义连接时对照;切换为自定义后这些值原样保留而不被启动清理。
|
||||
|
||||
自定义连接固定使用 OpenAI Responses 协议(`apiKind` 只接受 `openai_responses`),设置页把协议展示为只读项;推理档沿用对话输入盒中按回合生效的选择器,设置页只读展示当前档位,不新增第二处写入入口。
|
||||
|
||||
## 官方路由契约
|
||||
|
||||
- 后台 owner 在“AGC 模型”维护列表;每项包含稳定 `id`、必填 `alias`、服务端 `modelId`、`enabled`。默认项必须启用。标识唯一,别名唯一,列表最多 32 项。
|
||||
- 配置保存到私有 `agc_model_catalog` 单例表,使用 revision 乐观锁,重启及多 api-server 实例共享同一事实。缺少配置时使用初始目录,高质量对应 `gpt-6-astra`,快速对应 `gpt-5.6-luna`。
|
||||
@@ -14,7 +44,7 @@
|
||||
- `selectedModelIsDefault` 为真表示选择由平台默认项驱动(首次进入、默认项变化、所选模型失效回退),后台默认项变化时客户端跟随切换并提示;用户手动选择后置为假,不再被默认项变化覆盖。
|
||||
- 首页聊天框架的右下角同样提供模型选择入口(与项目对话右侧一致)。首页入口与项目对话共用同一份目录缓存、挂载即加载(失败时沿用上一次成功目录),选择仅影响后续创建/发送的轮次,不阻塞「开启创作」,因此模型目录不可用时仍可创建项目并使用后台默认项。
|
||||
- 项目右侧对话的模型选择器在对话进行中保持可交互:切换模型只写回客户端配置并作用于下一轮,当前回合不受影响;发送按钮仍由 `controlBusy` / `modelReady` 把关。
|
||||
- 设置页恢复到布局改版前的官方代理版本,不包含模型管理或模型选择,保留配置安全清理和官方代理锁定。
|
||||
- 自定义开关关闭时,设置页不包含模型管理或模型选择,使用官方代理锁定。
|
||||
|
||||
## 验收
|
||||
|
||||
|
||||
@@ -1,68 +1,138 @@
|
||||
# AGC 客户端更新检查与下载
|
||||
|
||||
## 交付范围
|
||||
更新时间:`2026-09-17`
|
||||
|
||||
AGC 每次启动时由根窗口检查一次公开 OSS 更新清单。清单默认位于
|
||||
`https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/latest.json`,构建时可用
|
||||
`VITE_AGC_UPDATE_MANIFEST_URL` 覆盖为同一受信任 OSS 域名下的 HTTPS 地址。客户端版本取
|
||||
`apps/ai-game-creator-shell/package.json`,通过 `version` 与清单版本比较;只有远端版本更高时显示更新提示。
|
||||
本文件是 AGC 客户端自动更新的主规范:更新能力由 Tauri 官方插件 `tauri-plugin-updater` 承担,并按下文渠道分发。
|
||||
|
||||
清单格式:
|
||||
## 目标
|
||||
|
||||
- 客户端自动更新改用 Tauri 官方 `tauri-plugin-updater`:清单请求、版本比较、更新包下载、签名校验、安装与退出全部在原生侧完成;前端只负责触发、展示和渠道选择。
|
||||
- 更新按渠道分发。当前渠道集合为 `dev-win`(Windows x64)与 `dev-mac`(macOS);构建管线按渠道产出并上传清单,客户端只读取自己渠道的清单。
|
||||
- 更新链路的信任来源从「清单里的 sha256 + 受信域名」升级为「发布签名 + 受信域名」:清单里的 `signature` 由构建期私钥生成,客户端用内置公钥校验,校验不过就拒绝安装。
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不做灰度放量、分批更新、强制更新和自动回滚;渠道只决定「取哪份清单」。
|
||||
- 不做后台静默自动安装:是否下载安装始终由用户在更新提示里确认(仅「是否显示提示」受渠道与开发态开关影响)。
|
||||
- 不支持应用商店分发(Microsoft Store / App Store)、移动端更新和企业内网自建更新服务。
|
||||
- 不为自研 sha256 清单协议保留长期实现;迁移桥(见「契约与迁移」)只用于把已发布客户端带到新协议,随后整条删除。
|
||||
|
||||
## 入口与边界
|
||||
|
||||
- 用户入口:
|
||||
- 客户端启动时在根窗口检查一次渠道清单,发现新版本时显示更新提示,用户可下载并安装。
|
||||
- 运行时设置「关于」页提供手动检查更新(强制刷新)。
|
||||
- 涉及模块:AGC 客户端(Rust `src-tauri`、前端 `src/`)、AGC 构建与发布脚本(`apps/ai-game-creator-shell/scripts/`)、Jenkins 发布流水线、OSS 对象布局。
|
||||
- 正式状态来源:
|
||||
- 客户端当前版本以 Tauri app version 为唯一权威来源(`tauri.conf.json`,由发布脚本与 `package.json`、`Cargo.toml`、`Cargo.lock` 同步递增)。
|
||||
- 远端最新版本取自当前渠道的 `latest.json`。
|
||||
- 信任边界:清单地址在构建期确定并烘焙进产物;客户端不接受用户输入、后端响应或项目文件提供的更新地址,也不回退到其它渠道或旧协议地址。
|
||||
|
||||
## 必须成立的行为
|
||||
|
||||
### 正常路径
|
||||
|
||||
- 正式包启动时检查一次渠道清单;仅当清单版本高于当前版本时显示更新提示,提示包含目标版本与发布说明。
|
||||
- 用户确认后下载更新包:下载期间显示进度与已下载字节数;下载完成后按平台安装。
|
||||
- Windows 使用静默安装模式(NSIS `quiet`),安装启动成功后客户端退出并由安装程序重启新版本;macOS 由客户端在安装完成后重启进程接管新版本。
|
||||
- 渠道在构建期确定并烘焙进产物:`dev-win` 产物只读 `dev-win` 清单,`dev-mac` 产物只读 `dev-mac` 清单,同一份二进制不会在运行期跨渠道切换。
|
||||
- 开发态(`npm run agc` / `agc:serve` 由 Vite dev server 提供前端)不检查更新、不显示更新入口,也不下载任何更新包。
|
||||
|
||||
### 失败、重试与幂等
|
||||
|
||||
- 清单请求失败均静默忽略,不阻塞客户端启动:网络错误、TLS 错误、404(渠道尚未发布版本)、格式非法、渠道没有当前平台条目、远端版本不高于当前版本。
|
||||
- 同一客户端生命周期内只自动检查一次;手动检查可强制刷新。
|
||||
- 签名校验失败、下载中断或写入失败必须失败关闭:删除临时文件、不启动安装程序,并给出可读错误文案;不接受「校验失败但继续安装」。
|
||||
- 重复点击下载或安装不产生并发安装;安装开始后客户端不再接受新的更新操作。
|
||||
|
||||
### 权限、归属与数据边界
|
||||
|
||||
- 更新能力通过 Tauri capability 显式授予客户端主窗口,其它窗口(调试窗口等)不得授予。
|
||||
- 客户端只允许访问渠道清单声明的地址,只允许安装清单声明且签名校验通过的对象。
|
||||
- 清单与安装包在 OSS 上保持公开可读;签名私钥与 OSS 凭据只存在于构建环境(Jenkins 凭据、本机发布配置),不写入仓库、日志、构建产物或客户端包。
|
||||
- 客户端不记录更新地址以外的敏感信息;失败文案不回显凭据、绝对路径或响应正文。
|
||||
|
||||
## 契约与迁移
|
||||
|
||||
- 清单格式(Tauri updater v2):
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.1.13",
|
||||
"downloadUrl": "https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/0.1.13/Genarrative-AI-Game-Creator.exe",
|
||||
"sha256": "<64位十六进制摘要>",
|
||||
"size": 123456789,
|
||||
"releaseNotes": "修复与改进"
|
||||
"version": "0.1.48",
|
||||
"notes": "发布说明,可为空",
|
||||
"pub_date": "2026-09-17T00:00:00Z",
|
||||
"platforms": {
|
||||
"windows-x86_64": {
|
||||
"signature": "<.sig 文件内容>",
|
||||
"url": "https://<oss>/agc/dev-win/0.1.48/<安装包文件名>"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`downloadUrl` 必须是 HTTPS;如提供 `sha256` / `size`,Tauri 下载时会校验摘要和字节数。点击“下载更新”后,客户端将安装包流式写入系统临时目录并显示进度,校验成功后通过 Windows UAC 提权启动 NSIS 静默安装并退出旧客户端。
|
||||
- 渠道与平台映射:
|
||||
|
||||
## 启动与失败策略
|
||||
| 渠道 | 构建目标 | 清单平台键 | 更新包 | 清单地址 |
|
||||
| --------- | ------------------------ | ---------------------------------------------- | ------------------------ | ------------------------------------ |
|
||||
| `dev-win` | `x86_64-pc-windows-msvc` | `windows-x86_64` | NSIS `.exe` + `.exe.sig` | `<OSS base>/agc/dev-win/latest.json` |
|
||||
| `dev-mac` | `aarch64-apple-darwin` 或 `x86_64-apple-darwin` | 对应 `darwin-aarch64` 或 `darwin-x86_64` | `*.app.tar.gz` + `.sig` | `<OSS base>/agc/dev-mac/latest.json` |
|
||||
|
||||
- 检查挂在 `WindowChrome` 根组件,覆盖首页、工作台和调试窗口;网络错误、格式错误或版本不高于当前版本均静默忽略,不阻塞客户端启动。
|
||||
- 更新请求使用单例 Promise,React StrictMode 或同一窗口重复挂载不会重复请求。
|
||||
- Tauri HTTP capability 与 CSP 仅放行默认 OSS 域名;若更换域名,需同步更新 `capabilities/main.json`、`tauri.conf.json` 和发布环境配置。
|
||||
- 对象布局:清单固定写成 `agc/<channel>/latest.json`;安装包与签名写成 `agc/<channel>/<version>/<file>` 与 `<file>.sig`。
|
||||
- macOS 当前采用单架构包:Apple Silicon 使用 `aarch64-apple-darwin`,Intel 使用 `x86_64-apple-darwin`;每次生成的清单只登记本次实际构建的架构,不把单架构原生 Codex 资源挂到另一架构。`universal-apple-darwin` 在版本读取/写入、构建和清单生成之前拒绝。
|
||||
- 渠道清单以实际运行架构为键。两种单架构构建不可轮流覆盖同一个 `latest.json` 并宣称双架构均可更新;当前不实现跨构建合并,Intel 发布需先完成其构建验证与多架构清单发布方案。
|
||||
- 构建期要求:打开 `bundle.createUpdaterArtifacts` 以生成 `.sig`;构建环境提供签名私钥与密码(私钥内容不得入库);公钥写入客户端配置。公钥在首个带更新能力的版本发布后不可更换,更换等于放弃自动更新(只能手动重装)。
|
||||
- 版本递增按渠道独立进行:发布脚本读取该渠道远端 `latest.json` 的 `version`,与本地版本取较高者递增 patch;两个渠道的版本号互不影响。
|
||||
- 版本高水位:发布脚本取「渠道清单版本」与「旧协议迁移指针版本」(迁移窗口内)中的较大值再递增。只看渠道清单会在渠道启用初期把版本链改小 —— 2026-09-17 首次渠道发布即把旧指针的 0.1.57 退回 0.1.48,随后以显式 0.1.60 纠偏;迁移窗口结束(旧指针 404)后自动只剩渠道清单,`dev-mac` 不参与旧指针比较。
|
||||
- 迁移(旧协议 → 渠道清单):
|
||||
- 迁移起点:已发布客户端(含当前线上版本)内置自研清单地址 `agc/latest.json`(sha256 格式),下载与安装由自研 Rust 命令完成。
|
||||
- 迁移策略见「未决问题与决策」。迁移完成后,自研清单解析、下载命令、下载进度事件以及为此放行的 CSP / HTTP 白名单条目按「四不写」整条删除,不留兼容分支与墓碑说明。
|
||||
|
||||
## 发布约定
|
||||
## 构建与发布
|
||||
|
||||
当前发布目标固定为 Windows x64 NSIS。执行 `npm run ai-game-creator-shell:build` 会先读取
|
||||
`VITE_AGC_UPDATE_MANIFEST_URL`(默认 `https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/latest.json`)的
|
||||
`latest.json`,取本地与 OSS 的较高版本并递增一个 patch,然后同步更新 package、Tauri 和 Cargo
|
||||
版本后再向 Tauri 传入 `--target x86_64-pc-windows-msvc` 构建。OSS 清单首次不存在时按本地版本递增;
|
||||
OSS 请求失败、清单格式错误或版本无效会终止发布,避免覆盖线上版本。构建完成后自动扫描 `.exe`
|
||||
安装包,并在 `apps/ai-game-creator-shell/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/latest.json`
|
||||
生成包含版本、下载地址、大小和 SHA-256 的清单。可通过 `AGC_BUILD_TARGET` 显式覆盖目标(发布仍应使用
|
||||
Windows x64),通过 `AGC_UPDATE_ARTIFACT` 指定要发布的安装包,通过 `AGC_UPDATE_OSS_BASE_URL` 指定
|
||||
OSS 前缀,通过 `AGC_RELEASE_VERSION` 指定三段版本号(仅在明确需要复现指定版本时使用),通过
|
||||
`AGC_UPDATE_RELEASE_NOTES` 写入发布说明,支持多行文本且保留内部换行;`--no-bundle` smoke 构建不会读取 OSS、修改版本或生成清单。
|
||||
- 发布入口:`npm run ai-game-creator-shell:release:upload`(构建 + 按渠道上传);仅构建不发布的 smoke 使用 `--no-bundle` 分支,不读远端版本、不改版本、不生成清单。
|
||||
- 发布入口只解析一次目标,优先级为 CLI `--target value` / `--target=value` / `-t value`、`AGC_BUILD_TARGET`、Windows 默认值;重复/空目标与不支持目标失败关闭。版本高水位、构建 feature/渠道端点、bundle 路径、产物后缀、清单平台键及摘要必须消费同一个发布上下文,不能分别回读默认目标。
|
||||
- 渠道由构建参数显式指定,并按目标平台校验:Windows 目标只允许 `dev-win`,macOS 目标只允许 `dev-mac`;未显式指定时按目标平台取默认渠道。
|
||||
- 定时调度只在本轮到达的提交包含 AGC 相关路径(客户端、共享包、`server-rs/crates`、AGC 插件、桌面壳图标、根依赖清单)时才触发渠道发布;纯文档或流水线自身的提交只跑 Full Build,不推高客户端版本号。判定失败或勾选强制触发时按"需要发布"处理。
|
||||
- 更新摘要自动生成:发布脚本用渠道清单里的 `commit` 字段(上一次发布的提交)到本次提交之间、且只覆盖客户端相关路径的提交列表生成 `notes`(每条 `- 提交标题(短 SHA)`,最多 12 条、主题 80 字、整体 900 字,超出折叠或截断),同时写入旧协议清单的 `releaseNotes` 和归档文件 `release-notes.txt`。`AGC_UPDATE_RELEASE_NOTES` 非空时以手动文案为准;无法判定起点(缺少上次 `commit` 或本地没有该提交)时不写摘要。清单缺少 `commit` 时回退用上一次成功构建的 `COMMIT_HASH`(CI 通过 `AGC_UPDATE_PREVIOUS_COMMIT` 传入)作为锚点,因此首次启用摘要或更换渠道后也能立即产出摘要。锚点仍不可得(清单读取失败或没有 CI 锚点)时降级为「最近客户端改动」列表并注明可能与上一版重复 —— 摘要属于附注,任何情况下都不允许因为它让发布失败。
|
||||
- 清单里的 `commit` 是非标准字段:更新插件忽略未知字段,发布脚本用它定位下一次摘要的起点。
|
||||
- 上传:安装包与 `.sig` 上传到 `agc/<channel>/<version>/`,清单以 `--force` 覆盖上传到 `agc/<channel>/latest.json`,保证 latest 指针与清单内 URL 指向已存在的对象。
|
||||
- Jenkins 流水线需要新增渠道参数与签名凭据;签名私钥与密码只以受保护凭据注入当前进程,不写入 workspace、日志或归档产物。
|
||||
- 归档证据:安装包、`.sig`、渠道清单与源码 commit。
|
||||
|
||||
每次发布安装包上传完成后,再使用 ossutil 的 `--force` 覆盖上传同一目录生成的 `latest.json`,确保固定的 latest 指针和 `downloadUrl` 指向已存在的 OSS 对象;未显式强制覆盖时,ossutil 在目标已存在时会交互询问并按默认值跳过,不能作为 Jenkins 非交互发布方式。清单和安装包均使用公开可读对象,不在清单中保存凭据、签名或本地路径。构建脚本本身不负责上传 OSS,发布流水线通过 `release:upload` 完成上传。
|
||||
## 验收标准与证据
|
||||
|
||||
如需一键构建并上传,可执行 `npm run ai-game-creator-shell:release:upload`。该命令要求本机已安装并配置 `ossutil`,
|
||||
先按上述规则比较 OSS 版本、递增 patch、构建 Windows x64 NSIS,再上传安装包和 `latest.json`。默认上传到
|
||||
`agc-dev` / `oss-rg-china-mainland.aliyuncs.com`,也可用 `AGC_OSS_BUCKET`、`AGC_OSS_ENDPOINT` 和 `OSSUTIL_BIN`
|
||||
覆盖;本机执行时凭据由 ossutil 本机配置读取,不能写入仓库或命令行参数。
|
||||
已获得的证据:
|
||||
|
||||
## Jenkins Windows 构建节点
|
||||
| 条款 | 验收方式 | 证据 |
|
||||
| ---------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 渠道与端点映射、渠道校验 | `node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs` | 通过(默认渠道、错配失败关闭、未知渠道失败关闭) |
|
||||
| macOS 单架构清单与 universal 拒绝 | 定向发布脚本测试 | 单架构各用对应平台键;拒绝未闭合的 universal 发布 |
|
||||
| 缺签名时失败关闭 | 同上 | 通过 |
|
||||
| 开发态不检查更新 | `vitest run apps/ai-game-creator-shell/tests/appUpdate.test.ts` | 通过(开关关闭时不请求清单) |
|
||||
| 旧自研链路整条删除 | 代码检索无残留命令、事件与白名单条目 | 通过(`download_agc_update` / 下载事件 / 清单常量均无残留) |
|
||||
| 清单与对象布局符合渠道约定 | `Genarrative-Agc-Windows-Build` #68(2026-09-17,SUCCESS) | 通过:`agc/dev-win/latest.json` = 0.1.48 + `windows-x86_64`;`agc/dev-win/0.1.48/陶泥儿_0.1.48_x64-setup.exe` 与同名 `.sig` 公网可读 |
|
||||
| 清单签名与签名对象一致 | 取回 `.sig` 对象与渠道清单 `signature` 比对 | 通过(逐字相同,420 字节) |
|
||||
| 安装包与清单登记一致 | 下载安装包实算 SHA-256 与尺寸后与迁移桥清单比对 | 通过(size `104678031`、sha256 `1f67…4fd0` 一致) |
|
||||
| 旧协议迁移桥 | 公网读取 `agc/latest.json` | 通过(0.1.48,`downloadUrl` 指向同一对象,含 `sha256` / `size`) |
|
||||
| 真实更新闭环(含升级后重启) | 0.1.47 客户端按提示下载安装并重启 | 通过(2026-09-17 用户实测:提示 → 下载 → 安装 → 关于页显示新版本,再次检查为已是最新) |
|
||||
| 更新摘要端到端展示 | 公网读取渠道清单 `notes` 与客户端更新提示 | 通过(2026-09-17 用户实测:0.1.62 清单带 8 条自动摘要,客户端提示正常显示多行内容) |
|
||||
|
||||
AGC 发布流水线使用 `jenkins/Jenkinsfile.ai-game-creator-shell-build`,当前节点标签为
|
||||
`windows && win2022`。节点应为 Windows Server 2022 x64 虚拟机,预装 Node.js 22、npm
|
||||
10.9.7、Rust 1.96.0、Visual Studio Build Tools(MSVC 与 Windows SDK)、Git 和 ossutil;
|
||||
Jenkins Agent 服务必须能在同一用户环境中找到这些命令。Tauri Windows bundler 使用
|
||||
`tauri.windows.conf.json` 中的 `bundle.useLocalToolsDir: true`,把固定版本的 NSIS 工具缓存到
|
||||
`src-tauri/target/.tauri/NSIS`,不依赖 Jenkins 服务账户的 `%LOCALAPPDATA%\tauri` 或 PATH 中的系统 NSIS。
|
||||
Jenkins Checkout 的 `git clean -fdx` 会清理该构建目录,因此每次全新工作区可能重新下载 NSIS;这只影响构建耗时,不改变工具来源或执行权限要求。
|
||||
流水线参数 `AGC_UPDATE_RELEASE_NOTES` 使用 Jenkins `text` 类型,可直接输入多行发布说明;执行根 workspace 的 `npm ci`,然后调用
|
||||
`npm run ai-game-creator-shell:release:upload`,并归档 Windows 安装包、`latest.json` 与源码 commit。
|
||||
流水线会将未导出的空参数按空字符串处理:`COMMIT_HASH` 留空时沿用 Jenkins SCM 当前提交,`OSSUTIL_BIN` 留空时使用节点 PATH 中的 `ossutil`,不会因 PowerShell 对空环境变量调用 `.Trim()` 而提前失败。
|
||||
待执行证据(首次渠道发布后回填):
|
||||
|
||||
Jenkins Job 在“Build and upload”阶段通过受保护凭据 ID `AliyunAccessKeyId` 和
|
||||
`AliyunaccessKeySecret` 注入 AccessKey,仅在当前进程运行时传给 ossutil,不写入仓库、workspace 或构建日志;
|
||||
本机运行仍使用 ossutil 配置。凭据必须具备 `PutObject` 权限;OSS 对客户端保持公共读即可,公共读本身不授予
|
||||
Jenkins 上传权限。由于版本号取决于 OSS 当前清单,Job 已关闭并发构建;若 Jenkins
|
||||
上存在多个 AGC 发布 Job,还应使用同一个 Lockable Resource 串行化发布。Job 参数
|
||||
`AGC_RELEASE_VERSION` 留空时自动递增,填写后会使用指定版本并更新对应的 `latest.json`,因此回滚或测试旧版本前应确认不会覆盖线上更新入口。
|
||||
| 条款 | 验收方式 | 证据 |
|
||||
| -------------------- | --------------------------------------------------- | ------ |
|
||||
| 签名校验失败拒绝安装 | 篡改渠道清单 `signature` 后观察客户端拒绝安装的表现 | 待执行 |
|
||||
| 签名校验失败拒绝安装 | 篡改渠道清单 `signature` 后观察客户端拒绝安装的表现 | 待执行 |
|
||||
|
||||
## 未决问题与决策
|
||||
|
||||
已决策:
|
||||
|
||||
- macOS 采用单架构包,只登记实际构建架构;Intel 真机构建与跨架构清单合并未验收,不公开宣称双架构分发就绪。
|
||||
- 旧客户端迁移桥:保留一个版本周期。渠道清单上线后,发布管线同时把旧的 `agc/latest.json`(sha256 格式)指向 `dev-win` 最新安装包,让已发布客户端自动升级到新协议;下个周期整条删除。
|
||||
- 签名密钥:由本仓库维护者生成并保管,私钥保存在仓库外(`%USERPROFILE%\.tauri\genarrative-agc-updater.key`),只有公钥进入客户端配置;Jenkins 用受保护凭据 `AgcUpdaterSigningKey` 与 `AgcUpdaterSigningKeyPassword` 注入为 Tauri 打包器读取的 `TAURI_SIGNING_PRIVATE_KEY` 与 `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`,本机可用 `TAURI_SIGNING_PRIVATE_KEY_PATH` 指向同一私钥。当前密钥不带密码;首次发布前仍可重新生成,首次发布后不可更换。
|
||||
- macOS 发布方式:`dev-mac` 产物在本机 mac 上执行发布入口上传,Jenkins 暂不新增 macOS 节点;macOS 代码签名与公证凭据未确认前,相关闭环记为未验证项,不静默通过。
|
||||
|
||||
待办:
|
||||
|
||||
- macOS `dev-mac` 渠道落地(macOS 构建机、签名与公证、安装后重启验证、是否接入 Jenkins macOS 节点)暂缓,由后续独立变更单独完成;在此之前 `dev-mac` 渠道只有构建与清单能力,不发布。
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
# AGC 抠图模式与背景色透传方案
|
||||
|
||||
## 目标
|
||||
|
||||
主站编辑器保持现有前端行为(继续使用 `complex`),同时扩展 External v1 抠图接口和 AGC 客户端,使客户端可以选择 `complex` / `flat`,并把 `screenColor` 原样交给 BgFilter。`complex` 用语义分割识别前景,`flat` 用于纯色背景抠图;确定背景为纯色时优先使用 `flat`。`auto` 的背景色识别完全由下游服务负责,主站不读取图片、不调用模型决策颜色、不生成颜色兜底值。
|
||||
|
||||
## 当前 BgFilter 契约
|
||||
|
||||
已登录生产服务器核对 `/root/BGfilter`,当前代码版本为 `f1a0833`,运行进程为 `python -m uvicorn app:app --host 0.0.0.0 --port 6006 --workers 1 --no-access-log`。服务契约为 `POST /remove-background` multipart:
|
||||
|
||||
- `background_mode`:可选,`flat` 或 `complex`;
|
||||
- `screen_color`:可选,支持 `#RRGGBB`、`auto` 或省略;省略/`auto` 时由 BgFilter 从图片边框自动检测;
|
||||
- `complex` 模式忽略 `screen_color`;
|
||||
- 自动检测失败由 BgFilter 返回 400。
|
||||
- 若配置 `BGFILTER_AUTH_TOKEN`,必须发送 `X-Genarrative-Image-Token`;缺失或错误返回 401;未配置时该接口不在服务层做 token 校验。
|
||||
|
||||
主站向 BgFilter 发送 `#RRGGBB` 时保留 `#`,`auto` 也原样发送,不做所谓的“hex 转换”。
|
||||
|
||||
服务器行为:`screen_color` 在 `auto` 或省略时由服务自动检测;`#RRGGBB` 用作指定背景色。`background_mode` 缺省在 BgFilter 侧为 `flat`,因此主站必须为 External v1 旧请求显式归一化为 `complex`,不能把下游服务的默认值直接当成主站默认值。
|
||||
|
||||
## External v1 请求契约
|
||||
|
||||
接口保持:
|
||||
|
||||
```text
|
||||
POST /api/external/v1/editor/images/background-removals
|
||||
```
|
||||
|
||||
新增可选字段:
|
||||
|
||||
| 字段 | 取值 | 缺省/行为 |
|
||||
| --- | --- | --- |
|
||||
| `backgroundMode` | `complex`、`flat` | 不填按 `complex`,保证旧客户端兼容 |
|
||||
| `screenColor` | `auto` 或 `#RRGGBB` | 不填则不向 BgFilter 发送该字段 |
|
||||
|
||||
组合规则:
|
||||
|
||||
1. 不传新增字段:按 `complex` 执行。
|
||||
2. `complex` 不允许传 `screenColor`,返回 400。
|
||||
3. `flat` 可以传具体 `#RRGGBB`、`auto`,也可以省略颜色。
|
||||
4. 模式和颜色严格按原值校验;非法值、空字符串、前后空格和大写 `AUTO` / `FLAT` 返回 400。十六进制颜色的字母允许大小写。
|
||||
5. 主站只做格式和组合校验;`auto` 不在主站解析,直接转发给 BgFilter。
|
||||
6. HTTP 请求中的 `null` 视同省略;模式省略时提供非 null 颜色同样违反 complex 约束。客户端 MCP 可选参数应省略,不传 null。
|
||||
|
||||
格式或组合错误在入队前返回 400;BgFilter 自动检测失败发生在异步执行阶段,任务通过既有失败状态收口,不把已接受的 202 改成同步 400,不启动其他抠图方式兜底。
|
||||
|
||||
OpenAPI 与客户端工具的对外说明只描述模式用途、参数约束和调用方可见的结果;provider 选择与兜底策略保留在内部技术方案中,不写入对外 description。
|
||||
|
||||
主站前端继续不传新增字段,因此用户行为不变。
|
||||
|
||||
## AGC 客户端改动
|
||||
|
||||
`agc_remove_background` 增加可选参数:
|
||||
|
||||
```json
|
||||
{
|
||||
"sourceLocalAssetId": "...",
|
||||
"assetName": "...",
|
||||
"backgroundMode": "flat",
|
||||
"screenColor": "auto"
|
||||
}
|
||||
```
|
||||
|
||||
客户端保留旧参数调用;新字段不填时不改变模式语义。客户端不自动选色、不把 `auto` 改写为具体颜色;源资源校验、认证、异步任务查询、结果下载和本地登记由客户端负责。
|
||||
|
||||
工具 schema、桥接参数校验和随包 `agc-client-projection` Skill/契约说明必须保持一致。模式与颜色属于请求意图,必须参与客户端幂等指纹;同一图片与名称的不同模式不能复用同一次请求。缺省 complex 且没有颜色时保留既有指纹。主站在默认值归一化之前计算 External 请求指纹,缺失的新字段不序列化,避免旧请求重放发生冲突。
|
||||
|
||||
客户端提交前建立的本地项目绑定会返回主站远端 `projectId` 与 `assetFolderId`,抠图请求必须使用这两个远端身份;本地 manifest `projectId` 仅用于绑定和本地状态,不能直接提交给主站。
|
||||
|
||||
### 本地结果与恢复合同
|
||||
|
||||
`agc_remove_background` 复用本地资源编辑账本,类型为 `background-removal`。源图片保持不变,抠图结果作为新资源写入项目。模式和背景色随账本持久化并参与请求指纹,其它编辑类型的历史指纹保持不变。
|
||||
|
||||
1. 客户端建立源图片的正式资源绑定,在提交前保存 operation、幂等键和请求意图。普通登录态提交 `/api/editor/images/background-removals`,从 `data.queueState.operationId` 读取受理身份;开发者模式提交 External v1 对应路由,从 `data.operationId` 读取身份。
|
||||
2. 受理后持续查询账号路由 `/api/runtime/external-generation/jobs/{operationId}` 或 External v1 对应状态路由。`queued`、`running` 只描述远端返回状态;固定进度值、本地文件缺失或 pending 清单为空均不能证明 BgFilter 排队。
|
||||
3. 远端 completed 后按稳定资源身份换取有效下载 URL,校验结果为带 alpha 通道的有效 PNG,随后复用 staging、manifest 和 revision 提交。只在本地登记完成后向 Agent 返回 `completed`、`operationId`、`resource.localAssetId`、相对路径和安全告警,不暴露临时 URL 或凭据。
|
||||
4. 轮询中断、超时或下载失败保留已受理 operation;`agc_list_registered_assets.pendingOperations` 与客户端恢复面板可见。相同源资源、结果名称、模式和颜色的后续调用优先恢复同一任务,不再次提交。不同账号不能恢复原账号任务;切回原账号后按既有恢复规则续接。
|
||||
5. 远端 failed 明确失败;提交响应不确定且无法确认 operation 时进入人工对账状态,不自动换键重发。失败/未知均不得伪造透明图或自动切换本地抠图方式。
|
||||
6. 升级前仅返回 queued、没有本地账本的任务不自动迁移;已有远端结果须通过正式资源查询和导入恢复,不据旧回执重新发起付费请求。
|
||||
|
||||
本修复只扩展 AGC 客户端现有工作流,不修改主站队列、BgFilter 或 SpacetimeDB schema。验收覆盖账号与开发者两种响应封装、queued/running/completed、已受理中断恢复不重复 POST、远端失败不登记结果,以及既有资源编辑回归。
|
||||
|
||||
## 实施任务
|
||||
|
||||
### 任务一:冻结 BgFilter 契约
|
||||
|
||||
记录服务器已支持的模式、颜色格式、自动检测和错误行为。不得把 SSH 地址、服务器凭据写入客户端或公开契约。
|
||||
|
||||
### 任务二:更新主站 DTO 与 OpenAPI
|
||||
|
||||
为 External v1 和内部任务 DTO 增加可选字段,更新 `docs/openapi/genarrative-external-v1.openapi.json`,写明默认值、组合约束和 400 响应。
|
||||
|
||||
### 任务三:更新主站归一化与队列
|
||||
|
||||
缺省模式归一化为 `complex`;`complex + screenColor` 拒绝;`flat` 允许颜色、省略或 `auto`。队列保存字段,worker 始终发送 `background_mode`,仅在调用方提供颜色时发送 `screen_color`,值原样透传。
|
||||
|
||||
### 任务四:更新 AGC 客户端
|
||||
|
||||
增加参数 schema、请求体字段和本地校验,更新 Skill、projection contract 与测试。旧客户端请求必须继续有效。
|
||||
|
||||
### 任务五:联调与验收
|
||||
|
||||
覆盖旧请求、`flat + auto`、`flat + #RRGGBB`、`flat` 不传颜色、`complex`、`complex + screenColor` 和非法值;使用真实 BgFilter 验证 multipart 字段及自动检测错误传播。
|
||||
|
||||
## 依赖、发布与回滚
|
||||
|
||||
先发布兼容的新主站,再发布支持新参数的 AGC 客户端。主站前端无需发布改动。若联调失败,客户端可回退为只传旧字段,主站仍按 `complex` 处理;主站回滚时不改变旧字段语义。
|
||||
|
||||
## 验收证据
|
||||
|
||||
2026-09-17 客户端闭环验证:
|
||||
|
||||
- `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell project::resource_editor:: -- --test-threads=1` 通过。新增 HTTP fixture 覆盖开发者 queued/running/completed、账号 HTTP 200 queueState 与 `data.job`、换签下载、PNG alpha 校验、原图保留和新资源提交。
|
||||
- 已受理任务的首次轮询失败后,pending 保留模式与颜色;恢复只查询原 operation,整个流程只 POST 一次,成功后清除 pending。远端 failed 不新增结果资源。既有账号隔离、提交原子性和崩溃恢复用例通过。
|
||||
- `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell agent::direct_tool_bridge::tests -- --test-threads=1`、AGC 类型检查、技能包校验、Rust 格式、编码、文档索引与 diff 检查通过。
|
||||
- 本轮未运行真实登录客户端 → 本地主站 → BgFilter 的端到端 smoke;当前本地后端已停止,自动化证据使用模拟 HTTP 服务。此前 BgFilter 成功日志只证明上游处理完成,不证明主站结果持久化或客户端导入成功。
|
||||
|
||||
2026-09-16 实测:
|
||||
|
||||
- 主站 `cargo test -p api-server background_removal`:36 项通过,覆盖非法请求入队前拒绝、缺省 complex、队列参数保留、旧请求指纹、父侧内部 RPC 和 provider multipart。
|
||||
- `cargo test -p api-server bgfilter`:52 项通过,包括 flat 的 auto/省略/具体颜色以及既有生成链路。
|
||||
- `exported_openapi_json_contains_external_editor_routes_and_security` 契约测试通过。
|
||||
- 客户端 `agent::direct_tools_mcp::tests` 18 项、`agent::skill_pack::tests` 4 项与抠图幂等指纹测试通过;Skill manifest 内容指纹已同步。主站与客户端 rustfmt、文档索引、编码及 diff 检查通过。
|
||||
- 真实 BgFilter(版本 `f1a0833`):使用进程内凭据串行请求 `flat + auto`、flat 省略颜色、`flat + #CFEFFF`、complex;四组均返回 200、512×512 RGBA PNG,alpha 范围均为 0–255。
|
||||
- 无纯色背景的随机噪声图片使用 flat + auto 返回 400,确认自动识别失败要求调用方提供颜色。测试没有修改服务器代码或配置。
|
||||
- 本地 `npm run dev:api-server` 已尝试,但当前配置指向的 SpacetimeDB 不可连接,服务停留在启动恢复重试,`/healthz` 未通过;已结束本次启动。完整登录客户端 → 主站持久化队列 → 结果回写的运行时验收尚未完成,不能用真实 BgFilter 的独立测试代替。没有部署本次主站或客户端代码。
|
||||
@@ -0,0 +1,136 @@
|
||||
# 【技术方案】AGC 模板库与模板建项
|
||||
|
||||
## 交付范围
|
||||
|
||||
AGC 客户端接入公共 OSS 上的**游戏模板库**(真·游戏模板,正文是 zip),并让用户能浏览、搜索、筛选、下载模板,直接由模板创建项目。模板更新不再依赖客户端发版。
|
||||
|
||||
- OSS 侧:bucket `agc-dev`(endpoint `oss-rg-china-mainland.aliyuncs.com`)下的 `templates/` 前缀,公共读。
|
||||
- 客户端侧:Rust `template_library` 模块(读清单、下载、安装、建项目)+ 模板库全屏页 + 首页模板推荐 + 左侧导航入口。
|
||||
- 不在本次范围:模板制作工具、模板审核、模板计费、增量更新、已建项目的模板回填。
|
||||
|
||||
## OSS 契约
|
||||
|
||||
```text
|
||||
templates/
|
||||
index.json # 模板库清单,客户端唯一读取入口
|
||||
v1/<templateId>/
|
||||
template.json # 单模板元数据(含文件级摘要)
|
||||
template.zip # 模板正文,zip 根 == AGC 项目根(如 game/index.html)
|
||||
cover.(png|jpg|webp|svg) # 封面图(卡片展示,客户端 <img> 直接取)
|
||||
```
|
||||
|
||||
`index.json`(schema `agc-template-library.v1`):
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `schemaVersion` | 固定 `agc-template-library.v1`;破坏性变更换 schema,不原地改语义 |
|
||||
| `library` / `libraryVersion` / `updatedAt` | 库标识、库格式版本、本次更新时间 |
|
||||
| `templates[].id` | 稳定标识,`[a-z0-9][a-z0-9._-]{0,63}`,同时是目录名 |
|
||||
| `templates[].title/summary/tags[]` | 展示与搜索/筛选用文案;`tags` 参与标签筛选与关键词命中 |
|
||||
| `templates[].runtime` | `html` / `unity` / `godot` / `cocos` |
|
||||
| `templates[].engine` / `engineVersion` | 引擎标识与版本(如 `phaser` 4.2.1、`three.js` 0.180.0) |
|
||||
| `templates[].templateVersion` / `updatedAt` | 模板内容版本;客户端按它判断是否需要重新下载 |
|
||||
| `templates[].entry` | 解压后的项目入口相对路径,如 `game/index.html` |
|
||||
| `templates[].zipKey` / `zipSizeBytes` / `zipSha256` | 模板包对象键、字节数、SHA-256(下载后强校验) |
|
||||
| `templates[].coverKey` / `coverWidth` / `coverHeight` / `coverSha256` | 封面对象键与尺寸/摘要 |
|
||||
| `templates[].metadataKey` | 单模板元数据对象键(`template.json`) |
|
||||
|
||||
约束:
|
||||
|
||||
- 所有对象键必须落在 `templates/` 前缀内;客户端只用「受信任 OSS 主机 + 对象键」自行拼 URL,**不直接信任清单里的地址**。
|
||||
- 任何一项校验失败(schema、标识符、sha256、尺寸、键前缀)都让整次清单读取失败,前端拿到的是全有或全无的清单。
|
||||
- 模板源在仓库 `apps/ai-game-creator-shell/template-library/`:`v1/<id>/{meta.json, project/**, cover.(png|jpg|webp|svg)}`,`template.zip` **不落仓库**,由脚本按 `project/` 现场打包(条目排序、固定时间戳,同内容重复打包摘要一致)。
|
||||
- 上传与校验由 [`scripts/agc-template-library-publish.mjs`](../../scripts/agc-template-library-publish.mjs) 完成:`--source apps/ai-game-creator-shell/template-library [--dry-run] [--prune]`,脚本生成 `template.json` 与 `index.json`、上传后回读 zip 摘要;`--prune` 清理该模板前缀下本次没有产出的旧对象(例如换封面扩展名后的残留)。
|
||||
- 当前模板:`blank-web`(空白网页)、`blank-2d-canvas`(空白二维画布)、`blank-3d-scene`(空白三维场景)、`phaser-2d-starter`(Phaser 2D 起步工程)、`threejs-3d-starter`(Three.js 3D 起步工程)。
|
||||
- 客户端可用 `AGC_TEMPLATE_LIBRARY_BASE_URL` 覆盖库地址;只接受 `https://agc-dev.oss-rg-china-mainland.aliyuncs.com`(拒绝其他主机、路径、http)。
|
||||
|
||||
## 客户端实现
|
||||
|
||||
### Rust:`apps/ai-game-creator-shell/src-tauri/src/template_library.rs`
|
||||
|
||||
| 命令 | 行为 |
|
||||
| --- | --- |
|
||||
| `fetch_game_template_library` | 读 `templates/index.json`(≤4 MiB),校验后缓存到 `<app_data>/templates/index.json`;网络失败时回退本机缓存并在 `source` 标 `cache` |
|
||||
| `download_game_template` | 取清单里对应条目,流式下载 zip(≤512 MiB),校验字节数与 SHA-256,解压到 `<app_data>/templates/installed/<id>/<version>/`,最后写 `installed.json` 作为安装完成的唯一标记 |
|
||||
| `create_automatic_local_game_project_from_template` | 需要时先安装模板,然后在 `<app_data>/projects/` 下按既有自动工作区规则建目录:先复制模板文件,再走 `init_local_game_project_at` 补 `.agent` 清单与标准目录。根目录可用 `projectsRoot` 覆盖(必须来自本机目录选择器并通过私有路径门禁),未指定时仍是 `<app_data>/projects/`;见 [`【实施计划】AGC项目创建目录可选-2026-09-17.md`](../project-memory/plans/【实施计划】AGC项目创建目录可选-2026-09-17.md) |
|
||||
|
||||
安全与健壮性:
|
||||
|
||||
- 解压只接受普通文件与目录:拒绝绝对路径、`..`、盘符、反斜杠、符号链接,并有文件数(4096)与单文件大小(256 MiB)上限。
|
||||
- 安装目录名由标识符白名单拼出,不拼接远端字符串;重装时只清理该模板自己的安装目录。
|
||||
- 模板文件与安装记录统一走 `write_game_creator_private_file` / `ensure_game_creator_private_directory_tree`,保持项目目录的私有 DACL 口径。
|
||||
- 建项目失败时删除刚创建的项目目录,不留半成品。
|
||||
|
||||
### 前端
|
||||
|
||||
- `src/features/template-library/templateLibraryModel.ts`:清单类型、搜索(空白分隔多关键词「与」)、标签/运行时/已下载筛选、标签选项聚合、体积格式化等纯函数。
|
||||
- `src/features/template-library/useTemplateLibrary.ts`:一次拉清单,暴露筛选状态、下载与「用模板建项目」;下载成功后只就地更新该条目的已下载状态。
|
||||
- `src/view/template-library/index.tsx`:模板库全屏页(返回、刷新、搜索、运行时/标签筛选、仅看已下载、卡片显示封面与已下载徽标、下载/使用模板)。
|
||||
- 卡片动作按安装状态收口:已下载且版本一致时**不再显示下载入口**,只留「使用模板」;版本落后才显示「更新」;缺包显示「下载」。
|
||||
- 过程提示(下载完成、开始建项目)走浮层 toast(复用 `packages/shared` 的 `PlatformRuntimeStatusToast`,`document.body` 浮层 + 2.6 秒自动消失),不再占用页面内位置;页面内只保留可操作的错误与空态。
|
||||
- 首页「灵感推荐」替换为「模板库」推荐位(`src/view/home/TemplateRecommendations.tsx`):只展示封面、标题、运行时与已下载徽标,点击进入模板库页面;首页不再直接触发建项目。
|
||||
- 左侧导航新增模板库入口(`LauncherView = 'template-library'`)。
|
||||
- `src-tauri/tauri.conf.json` 的 `csp` / `devCsp` 在 `img-src` 放行 `https://agc-dev.oss-rg-china-mainland.aliyuncs.com`,用于封面图;`connect-src` 原本已放行同一域名。
|
||||
- 旧的本机灵感图目录 `src/view/home/assets/inspiration/` 与 `InspirationGallery.tsx` 一并删除,不再保留退役实现。
|
||||
|
||||
## 验收与验证
|
||||
|
||||
## 本地压测假数据注入(feature 控制)
|
||||
|
||||
模板库的数据源在 Rust 侧(清单校验、安装状态、下载与建项目都在这里),TS 只消费快照做渲染,所以假数据注入也放在 Rust 侧,走与真实完全一致的链路。
|
||||
|
||||
- 开关:Cargo feature `template-library-fixtures`(**默认关闭**)。关闭时 `apply_template_library_fixtures` 是恒等透传,正式产物里不存在注入分支,并有单测保证这一点。
|
||||
- 条数:环境变量 `AGC_TEMPLATE_LIBRARY_SYNTHETIC_COUNT`(默认 1000;`0` 表示不注入;上限 20000)。
|
||||
- 假数据特征:真实条目保留在最前,其余按真实条目循环复制;`id`/标题唯一,封面地址追加 `?synthetic=N`(强制逐张请求,模拟“每个模板各自封面”);标签追加 `批次-00..19`;安装态按 1/3 混合。
|
||||
- 运行方式:
|
||||
|
||||
```bash
|
||||
# 本机 dev 客户端(保留 Windows 默认 feature)
|
||||
AGC_DEV_CARGO_FEATURES=cocos-editor-execute,template-library-fixtures npm run dev
|
||||
# 直接跑二进制
|
||||
cargo run --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --features template-library-fixtures
|
||||
# 覆盖条数
|
||||
AGC_TEMPLATE_LIBRARY_SYNTHETIC_COUNT=300 AGC_DEV_CARGO_FEATURES=template-library-fixtures npm run dev
|
||||
```
|
||||
|
||||
两种编译模式都要过模板库单测:默认构建跑「恒等透传」用例,`--features template-library-fixtures` 跑「补齐到配置条数」用例。
|
||||
|
||||
### 1000 条实测结论
|
||||
|
||||
### 卡片列表虚拟滚动(react-window)
|
||||
|
||||
- 列表改用 workspace 里已有的 `react-window@1.8.11` 的 `FixedSizeGrid`(`react-arborist` 已在用同一版本,不引入新包;类型来自 devDependency `@types/react-window`)。
|
||||
- 布局契约收在纯函数 `templateLibraryGrid.ts`(单测覆盖):列数 = `floor((容器宽 + gap) / (最小卡宽 + gap))`、列宽 = 容器宽 / 列数、行高 = `卡片宽 × 9/16 + 文字区 150 + gap`;`buildTemplateRows` 按行切分并在行尾补 `null` 占位。
|
||||
- 卡片抽成 `TemplateCard`(`memo`),网格只渲染可视行 + 2 行 overscan;筛选条件(关键词/标签/运行时/仅看已下载)变化时把滚动位置复位到顶部,避免"从筛选切回全量后停在空白处"。
|
||||
- **页面高度契约**:页面根节点的高度按**父级 `.launcher-main` 的实测高度**内联设置,既不用百分比也不用 `100vh`。原因:外壳样式 `.launcher-main > .platform-theme { height: 100% }` 特异性高于 Tailwind 工具类,而这条百分比在 `.launcher-shell { min-height: 100vh }` 链路上是不定高,页面会退化成内容高度(虚拟网格视口高度 0、卡片区整片空白);`100vh` 又比真实舞台高一个标题栏高度(窗口 100vh=800 / 舞台 750),底部会被裁掉。
|
||||
- 筛选区(运行时/标签)改成可独立滚动的区块(`max-h-[24vh]`),标签数量随库量增长时不再把卡片区挤出窗口。
|
||||
- 回归:`templateLibraryGrid.test.ts` 覆盖列数/行高/行数/切行;页面测试用固定视口断言「1000 条只渲染 ≤ 40 张卡片,滚动高度仍按 250 行计算」。
|
||||
|
||||
- 页面能正常渲染 1000 张卡片(头部显示「共 1000 个模板 · 已下载 335 个」),并且滚动容器生效(窗口高度压到 430px 时右侧出现滚动条,页面内容被裁切而不是溢出到窗口外)。
|
||||
- 需要后续收口的两点(本次未改):① 标签筛选条随库量膨胀——1000 条时聚合出 35 个标签、占三行;② 一次性渲染 1000 个卡片节点并触发 1000 次封面请求。建议标签只展示 Top N + 「更多」,卡片列表加分页或虚拟滚动。
|
||||
- 前端回归:1000 条渲染 + 已安装过滤(334)/标签过滤(50)/关键词过滤数量自洽,见 `apps/ai-game-creator-shell/tests/templateLibraryView.test.tsx`。
|
||||
|
||||
```bash
|
||||
# 模板库单测(清单校验、键安全、解压路径逃逸、安装与建项目)
|
||||
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell template_library
|
||||
# 模板库真连检查(可选,需要网络):读线上清单、下载安装线上模板包并据此建项目
|
||||
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell template_library -- --ignored
|
||||
# 前端模型与页面单测(AGC 测试统一在 apps/ai-game-creator-shell/tests/)
|
||||
npx vitest run apps/ai-game-creator-shell/tests/templateLibraryModel.test.ts apps/ai-game-creator-shell/tests/templateLibraryView.test.tsx
|
||||
# 类型检查 / 编码 / 空白
|
||||
cd apps/ai-game-creator-shell && npm run typecheck
|
||||
npm run check:encoding
|
||||
git diff --check
|
||||
# OSS 侧匿名可读
|
||||
curl -s https://agc-dev.oss-rg-china-mainland.aliyuncs.com/templates/index.json
|
||||
```
|
||||
|
||||
手工验收:打开模板库 → 搜索与筛选 → 下载(出现「已下载」徽标)→ 「使用模板」→ 进入项目工作台且项目里已有模板文件。
|
||||
|
||||
运行态核验(客户端真的拉过清单时):本机缓存 `<app_data>/templates/index.json` 与线上 `templates/index.json` 逐字节一致;`<app_data>/templates/installed/<id>/<version>/installed.json` 出现即表示该模板已下载完成。
|
||||
|
||||
## 失败与回退
|
||||
|
||||
- 清单读不到且没有本机缓存:模板库页显示错误与重试,首页推荐位显示「模板库暂时没有可用的模板」。
|
||||
- 版本落后:`installedVersion != templateVersion` 视为需要重新下载,点「使用模板」会先重下再建项目。
|
||||
- 需要回退整条链路时,删除 `templates/` 前缀即可让客户端回到"空模板库";客户端代码路径不受影响。
|
||||
@@ -69,6 +69,10 @@ OpenAI 的标准模型是“Plugin 作为可安装包,组合 Skills、可选 M
|
||||
|
||||
除 AppData 导入外,宿主还扫描 `plugins/` 工作区:每个含根目录 `plugin.json` 的一级子目录是一个插件包。解析顺序为环境变量 `AGC_PLUGIN_WORKSPACE`、随包资源目录 `<resource_dir>/plugins`、开发构建的仓库 `plugins/`。仓库工作区约定见 [`plugins/README.md`](../../../plugins/README.md)。
|
||||
|
||||
Windows 与 macOS 构建都将内置插件的清单、JS 入口与面板复制到应用资源目录;staging 每次重建,避免已删除插件或跨目标原生 payload 残留。macOS 不携带 Windows native payload。Cocos 进程桥接仍仅按既有 Windows 平台实现提供,插件文件可被发现不代表 macOS 已支持编辑器控制;JS 入口的系统 Node 前提不变。
|
||||
|
||||
Cocos 插件对用户可见与可启动必须同时满足当前为 Cocos 项目、宿主已注册 `cocos-editor` 原生适配器;没有适配器时从插件/扩展列表隐藏,直接启动或读取面板也在产生子进程前拒绝。正式适配器仅在 Windows 且编译 `cocos-editor-execute` 时注册;Agent 工具使用相同平台与 feature 门禁。前端只按后端列表投影判断是否自动启动,不自行推断平台能力。
|
||||
|
||||
### 内置插件与可用开关
|
||||
|
||||
`plugins/` 工作区里的插件是**内置插件**:随客户端分发,用户不能卸载或删除,只能通过可用开关控制是否生效。开关状态持久化在 AppData `extensions/builtin-plugins.json`(`schemaVersion = agc.builtin-plugins.v1`,`enabled` 是 id 到布尔的映射);文件缺失按插件 manifest 的 `enabled` 处理,坏文件失败关闭。内置插件优先级高于同名导入插件,AppData 里的同名 Plugin 不会覆盖或间接卸载它。
|
||||
|
||||
@@ -1,13 +1,82 @@
|
||||
# AI 游戏创作智能体 App 实施计划
|
||||
|
||||
## 游戏画布居中指引
|
||||
|
||||
开发 Agent 的系统工程提示与 `agc-web-game-development` Skill 明确约束同一 canvas 的居中只能由一方负责:Phaser `FIT + CENTER_BOTH` 配合尺寸明确的普通块级父容器,不叠加同一父容器的 Grid/Flex 居中、自动外边距或居中 transform;如由 CSS 居中则设置 Phaser `NO_CENTER`,外围页面的 Grid/Flex 不受此限制。布局修改后构建实际 dist,并在桌面、移动和 resize 下核对 canvas 对游戏父容器中心偏差不超过 1 CSS px、无溢出与意外滚动条。出现偏移先检查游戏项目的 CSS/scale,不修改 AGC 预览固定偏移掩盖问题;这些要求通过 Agent 指引执行,不新增运行时门禁或平行校验系统。
|
||||
|
||||
## 画布验收返修边界
|
||||
|
||||
- 生成浮层的实例、输入、失败状态与请求身份必须按占位草稿身份隔离;切换到另一张占位时不能沿用上一张的工具类型、输入或 operation。切换未提交草稿仍须保留各自输入,回到原占位可继续编辑。
|
||||
- 失败任务重试必须保留原请求的完整参考资产 ID 集合。参考资产失效时明确提示并阻止旧请求提交,不得静默过滤后用改变的参考集恢复原 operation,也不得自动创建新的付费请求。
|
||||
- 显式整理的待写意图不能因切换栏目而覆盖其他栏目的排队范围。每个受影响范围与撤销步骤保持一致;拖动预览和最终写回使用同一份按下时冻结的起点快照,即使拖动期间布局收到异步更新也不跳位。
|
||||
|
||||
## 多选素材批量标签
|
||||
|
||||
- 画布与资源面板共用选中集合。选择至少两项同项目已登记素材后,从已有“编辑标签”入口或资源面板的“批量标签”动作打开同一标签编辑器的批量模式;入口遵循既有“常用操作 + 更多”收纳,不新增平行资源管理页。
|
||||
- 批量模式只将用户填写的标签追加到整组选中素材。输入按既有中英文逗号、顿号、换行拆分,trim、去空及去重;每份素材原有标签、正式分类、类型、路径、来源、版本关系均保留。批量删除/替换既有标签、批量改素材类型及 AI 自动分类不在本次范围。
|
||||
- 操作对象在打开面板时冻结为去重后的素材 ID 列表,不随后续选择变化扩散;项目切换关闭面板并忽略迟到响应。混合选择含虚拟版本、未登记附件或已删除资源时,不允许只对其中一部分静默保存,入口禁用并给出原因。
|
||||
- 批量范围为当前入口展示的完整选中集合:资源面板保留跨筛选的选择时,也必须显示实际目标数量;不得仅修改第一项或仅修改当前可见项。每批最多 200 个不同素材,超过上限要求缩小选择。
|
||||
- 原生新增局部命令 `add_local_project_resource_tags`,参数 `input: { projectPath, expectedProjectId, expectedProjectRevision, assetIds, tags }`;仅消费当前 manifest 素材 ID。响应 `{ assets, committedProjectRevision }` 返回整组选中素材最新条目。沿用 `asset.register` 权限、项目身份、写锁及 revision CAS;不新建 manifest schema、数据库字段或外部 API。
|
||||
- 原生按锁内最新 manifest 为每项合并标签,沿用现有单标签长度与标签总量限制,先校验全部目标及合并后上限,再一次写 manifest;非法 ID、标签、项目身份或版本冲突不写入任何一项。重复标签重试不重复追加;整批无变化时不推进 revision、不追加修改审计。已写 manifest 后的审计或 revision 异常必须明确返回“已写入”状态信息,沿用既有单资源写入错误语义,不能谎称回滚。
|
||||
- 保存期间禁用重复提交及关闭;失败保留待追加标签,CAS 冲突需明确报错而非自动覆盖。成功用原生返回条目刷新宿主 manifest 与筛选统计,不循环调用单素材保存、不按旧闭包覆盖最新其他素材。
|
||||
- 单素材标签编辑仍保持既有增删标签行为;菜单信息/类型入口、批量移动、文档与引擎资源预览不得回退。验收覆盖不同原始标签与分类、资源面板/画布多选入口、重复标签、超限及混合选择、批次失败零部分写、一次 revision、项目切换迟到回包和单素材回归。
|
||||
- 批量模式的标签输入仅显示待追加草稿,不把各素材已有标签并集作为提交值。资源面板入口放入现有动作行,画布入口沿用选中工具栏的收纳。响应条目按去重后请求 ID 的首次出现顺序返回,无变化时返回当前 revision;DTO 使用 camelCase 并拒绝未知字段,批次上限按去重后数量计算。
|
||||
|
||||
## 资源画布生成、展示与布局合同
|
||||
|
||||
- 生成工具点击后先在当前栏目创建临时占位卡,并以卡片为锚点展示独立生成浮层;占位不登记为正式素材、不进入 Agent 可引用资源集。上传仍沿用文件选择,不创建虚假生成任务。关闭编辑浮层不应丢失正在执行的任务;切换项目不得将旧项目结果或草稿写入新项目。
|
||||
- 图片生成支持从现有素材选择器添加真实参考;引用携带稳定资源身份并经既有原生权限、归属与类型校验传至生成链路,不能仅拼接名称。缺少规范图不阻止打开面板;确有规范前置的操作必须在提交前满足要求,不能绕过后端校验。用户可通过现有工具栏先生成规范图。
|
||||
- 占位可移动。提交复用正式生成任务、幂等与结果登记链路;成功结果使用占位最新位置,失败保留输入与引用供重试。已受理但响应不确定时先对账,不能无条件再次发起付费生成。关闭、删除占位和后台任务的行为需保持既有任务所有权,不把隐藏展示当作取消任务。
|
||||
- 所有资源卡显示正式素材名称(Agent 生成的 assetName 或用户重命名),没有名称时才使用既有文件名兜底;长名称省略但可查看完整名称。文档卡以居中图标呈现,不显示无效正文片段;独立详情保留原文预览、UI JSON 识别、权限及读取预算。
|
||||
- “整理画布”显式重排当前栏目全部素材,包括手动坐标;筛选不缩小整理集合,其他栏目不变。重排作为一次可撤销操作保存,失败继续使用现有写队列及冲突处理,不伪报保存成功。
|
||||
- 框选多个素材后拖动任一已选卡,按统一位移移动整个选择集并保持相对位置;拖动未选卡保留单选语义。松手统一提交,整次操作可一次撤销;缩放坐标、指针取消、窗口失焦、保存失败和项目切换不得导致选择丢失或布局串写。
|
||||
- 本合同不包含拖入对话批量 @、复制聊天引用、历史替换交互、SpacetimeDB schema 或新的远程公开 API。共享表现与交互优先扩展公共组件,正式资源状态仍由宿主/原生链路维护。
|
||||
- 参考选择范围为同一项目已登记图片,可跨栏目、多选,无规范前置时最多 5 张,有规范前置时最多 4 张用户参考(总计最多 5 张);复用资源引用选择组件,不允许文档、音视频、占位或跨项目素材。本地生成命令补最小引用 ID 参数并转换为当前账号绑定下的远端资源 ID,沿用图片生成 API 已有 `referenceImageSrcs`。需要规范图的普通图片请求合并并去重规范引用,总数不超过现有 API 限制;只接受单规范引用的图集操作不显示用户参考选择器,原生提交拒绝额外参考而非静默丢弃。不得降级成纯提示词。
|
||||
- 占位由宿主按项目与独立草稿 ID 管理,提交后关联任务 ID;失败重试使用同一占位。切项目清理未提交草稿与界面位置,已提交任务继续沿用账本恢复,重开后不承诺恢复未持久化的占位位置。迟到结果先核对项目和任务归属;只有本会话仍存在的占位才应用最新位置。删除占位只隐藏展示,不取消后台任务或丢弃正式结果。
|
||||
- 参考必须是原生可解码的栅格图片,SVG 不进入参考候选;原生在上传任何引用前预校验整组素材的归属、受控路径、文件及解码,失败不静默丢图。需要重新上传当前账号绑定的参考遵循 `asset.upload` 权限。manifest 读侧的引用形状检查不证明远端账号归属,实际生成始终通过当前账号 binding 解析,不凭历史来源 ID 发起请求。
|
||||
- 图片类 GUI 生成通过可选 `targetCategory` 在原生登记时写入入口栏目,使用既有 manifest `category` 字段及合法分类词表;不传时保留按 kind 派生的行为,Agent 不传。该值不改变远端生成内容及计费幂等槽,仅决定本地生成结果分类;重试保持原占位栏目。同路径重新生成时,主产物按本次入口栏目更新分类(包含覆盖此前手动分类),图集附属切片保持既有独立分类规则。
|
||||
- 生成面板打开后,占位和面板需处于当前画布标题栏与底部工具栏之间;面板复用公共外观,空间不足时面板内部滚动,提交按钮可达。音频/BGM 占位绑定原有 operation/idempotency 身份,进行中不允许换身份重复提交;失败可用原身份重试,成功结果与图片一样接管占位。关闭未提交浮层保留可继续编辑的草稿,删除占位才丢弃该草稿。
|
||||
- 整理范围为当前栏目页全部资源;“所有资源”页为当前项目所有可展示资源,总览不新增整理行为。重排结果成为自动坐标,可撤销恢复原坐标与手动标记;历史仅保留当前会话,切项目清空。多选仅作用于当前画布可见选中资源,不携带筛选隐藏或跨栏目残留选择;取消手势恢复拖动前坐标,切项目清空选择。
|
||||
- 当前素材名以现有正式命名链路为准:生成时 assetName 参与落盘名称,重命名更新文件名;卡片消费正式资源 label,不从临时输入或历史任务名覆盖后续重命名,不新增平行显示名持久化。若原有命名链路丢失 assetName,则修复原链路,而非只在卡片本地伪造。文档卡不显示任何正文摘要,但详情原文与 JSON 识别读取不变。
|
||||
- 验收覆盖空素材项目进入工具、真实引用入参、成功/失败/重试与迟到响应、占位移动后落点、全类型名称、文档详情、当前栏目重排/撤销、不同缩放的多选移动/撤销以及其他栏目不变。自动化、真实客户端和真实 Provider 验证分别报告;未实际运行的路径不得标为通过。
|
||||
|
||||
## 策划 Agent 批量局部修改
|
||||
|
||||
`patch_file` 的所有 edits 均匹配同一份原文件,参数顺序不影响结果。完成唯一匹配与不重叠校验后,按原文起点升序拼接未修改片段与替换文本,最后一次性写入;任一校验失败时不写文件。回归用例覆盖乱序 edits、中文内容与替换长度增减,并核对完整落盘内容。此行为仅属于策划 Agent 文件工具。
|
||||
|
||||
## 策划 Agent 运行 panic 边界
|
||||
|
||||
`finish_design_command` 的运行段包在 `catch_unwind` 边界内:任何运行期 panic 被转成一次普通失败,由既有错误分支从最后一个持久检查点恢复,向会话写入固定公开文案"策划运行发生内部错误,本轮已中断,可直接重试;若反复出现请反馈。",并以 `running=false、可重试、lastError 有值` 的视图正常返回。panic 负载只写入私有 design_debug,不进入用户可见消息;continue、recover_uncertain 与 decide 三个入口共享同一边界。边界不改变工具错误、Provider 瞬态重试和批次不确定恢复的既有语义。
|
||||
|
||||
运行段通过 task-local 携带项目根;首次运行时安装的 panic hook 在 panic 瞬间把代码位置(file:line:column)和负载追加写入私有 design_debug,随后交给原 hook 维持既有 stderr 输出。hook 只在策划运行段内生效(其它任务无 task-local 上下文时直接透传),多线程 runtime 下 future 跨 worker 迁移也能正确归因。
|
||||
|
||||
## 资源画布交互与工作台状态同步
|
||||
|
||||
- 未完成抠图的恢复项在现有类型行显示账本中的复杂/平面背景模式;平面模式显示已记录的自动背景色或颜色值,缺失模式/颜色不补默认值,恢复仍按原 operation 身份执行。
|
||||
- 活动回合初始空快照、首次成功读取的空结果及禁用后的空态保持同一数组引用;快照签名初值与空态重置值均为 `[]`。无原生 invoke 的窗口测试只期待首次状态发布,异步空结果测试显式控制请求完成,不以初始空数组作为请求已完成的证据。
|
||||
|
||||
- 活动回合轮询的初始空快照与后续空结果保持同一引用;停用或切换读取器使旧请求失效,晚到快照不得恢复已停用的活动回合或覆盖新轮询结果。无原生读取器时窗口只发布一次空状态,不通过额外空数组触发重复发布。
|
||||
- 工作台向窗口标题栏发布正在运行的项目时,输入未变化不得形成重复发布与清理的渲染循环;打开项目动作始终使用当前工作台处理逻辑,退出工作台后清除其标题栏状态。
|
||||
- 资源子画布(含「所有资源」)保留空白处左键框选、资源卡左键选中/拖动、触摸板双指平移及捏合缩放;右键按住空白处或资源卡拖动时平移画布,不改变资源选择与布局。中键和空格抓手继续可用。总览保留既有左键平移,并支持右键平移。
|
||||
- 画布接管的右键手势不弹出原生菜单;输入框、媒体操作、工具条和独立浮层不被画布抢占。指针取消、捕获丢失或窗口失焦后终止平移,不能继续跟随指针。
|
||||
- 运行不可用提示只取决于运行能力与 UI 编辑器状态,不因资源选中、取消选中或框选而消失,且不改变运行入口的真实可用性。
|
||||
- 本次边界不包含布局算法、持久化坐标、预览读取预算或后端契约调整。首次进入抖动与平移异常须在更新循环消除后单独实测,不能仅凭状态循环修复宣称已解决。
|
||||
- 验收包含真实窗口 Context 与工作台的状态同步回归、运行提示与选中并存、左右键分流、平移中断及连续滚轮事件;真实客户端首次进入与触摸板手感为独立人工验收项。
|
||||
|
||||
## 资源卡选中工具栏与导出
|
||||
|
||||
- AGC 选中工具栏按既有动作顺序最多直接显示前 5 项(不计分隔线),剩余动作进入「更多」。悬停、点击及键盘均可展开独立纵向浮层,优先向上展开,窗口顶边空间不足时向下避让;浮层限制在窗口内,超高时自行滚动,不带动画布。动作执行、点击外部、Escape 或换选资源后关闭;禁用状态和原处理链路保持不变。Web 美术画布默认不折叠。
|
||||
- 「素材类型」与「信息」不占工具栏名额,改为资源卡右上角的类型标签和信息圆钮,与 Web 美术画布共用卡片控件。未选中卡片可直接打开信息;类型入口仅对 manifest 资产可用。控件不触发卡片拖拽或多选,信息面板仍复用运行页签的字段。
|
||||
- 共享选中工具栏按实际显示的快速编辑、编辑动作、改造、导出与宿主动作组生成分隔线;空组不产生分隔线,不依赖宿主 CSS 隐藏重复线。
|
||||
- AGC 所有具有本地文件路径的素材都显示带文字的「导出」按钮,位于工具栏末组的「删除素材」之前,两者之间不插入分隔线;「重命名」继续保留在前面的常规动作组。工具栏宽度上限为 `min(92vw, 800px)`,窄屏仍可横向滚动。图片、视频、音频、动画、UI、文档及其它文件共用 `isResourceCanvasExportable`,不按媒体类型限制导出;无文件路径及虚拟项目版本不提供文件导出入口。
|
||||
- 导出继续复用 `saveProjectResourcesToDisk`:原生保存对话框选择路径,`save_local_project_asset_file` 复制原始文件字节,不转图片、不重编码、不另建 IPC。后端继续校验源文件、敏感路径和目标路径;取消不写文件,失败通过工作台提示。
|
||||
|
||||
## 文档与代码素材预览
|
||||
|
||||
- JSON 资源的卡片按内容语义分流:普通 JSON 显示 JSON 图标/标签,详情按 JSON 代码块显示;UI 设计 JSON 显示 UI 设计标识并提供现有 UI 编辑器入口,不再把 State 原文铺在卡面上。功能分类、manifest kind 和资产身份不因识别而改写。
|
||||
- UI 识别由原生文本预览在已登记、受控、完整读取的同一份 UTF-8 内容上完成,复用编辑器的 `game-creator-ui-design-state.v1` 契约解析、canonical 校验、revision/State 校验及项目/资产身份校验。前端只消费识别结果,不按文件名、kind 大小写或正文片段自行认定 UI;普通、损坏、未知 schema、跨项目或跨资产 JSON 不获得 UI 编辑能力。
|
||||
- JSON 为完成内容识别继续走现有受限预取队列、并发/容量和 2 MiB 原生文本读取上限。识别失败不写文件、不生成空 State、不做格式迁移;能读取的原文仍可按 JSON 查看,读取失败仍展示原有错误。非 JSON 代码卡继续仅按用户详情请求读取。
|
||||
- 编辑器加载/保存/代码生成复核当前项目的已登记 JSON 资产及完整文档,不以 kind 必须精确等于 `UI` 阻断合法已有设计;创建新 UI 状态仍要求正式 UI 资源,不能借普通 JSON 预览初始化或覆盖文件。现有 State 保存锁、CAS、revision 与恢复边界不变。
|
||||
- 文档与代码素材选中工具栏提供「预览」,打开独立、可滚动的只读弹窗;关闭、切换素材或项目后不残留旧内容。加载中、空文件与读取失败分别呈现,允许重试可重试的错误。
|
||||
- 复用资源预览队列、身份缓存、项目 scope、失效与权限校验,继续调用 `read_local_project_text_preview`。代码卡不做可见性预取,仅用户显式打开详情时读取;不新增 IPC,不扩大可读取文件范围,不增加编辑/保存能力。
|
||||
- 文档正文统一使用现有 Markdown 渲染器;代码文件以按扩展名标注语言的 Markdown 围栏代码块呈现。围栏必须长于正文内的反引号串,正文空行与缩进保持原样,不把源码当 Markdown 正文或 HTML 执行。
|
||||
@@ -32,9 +101,22 @@
|
||||
|
||||
DirectProject 的 AGC 工具、构建、验证和浏览器试玩错误,若不属于鉴权、权限、余额、项目身份、历史损坏、传输断开、取消或付费操作状态不确定等安全终止边界,必须作为脱敏错误上下文回传同一 LLM 会话,由 LLM 读取当前项目、修改真实文件并重跑失败阶段。客户端最多连续反馈三次;每次保留 stage、工具 / 命令、错误正文和已有证据,不得静默吞错、伪造成功或用占位产物跳过阶段。达到三次仍失败后,才向用户投影终态错误和诊断引用。
|
||||
|
||||
## 2026-09-16 平台会话身份与凭据分离(并发续期不再中断在途生成)
|
||||
|
||||
平台会话在 AGC renderer、原生 Rust 层和独立 Runner 中统一拆成两个互不替代的概念:**身份**(`userId + api origin + identity generation`)与**凭据**(当前 access token)。身份代次只表达登录主体或服务 origin 的更换;凭据轮换必须保持身份代次不变。
|
||||
|
||||
- 同一身份内的 access token 轮换 —— 长回合保活、`401` 续期、同一账号重新登录 —— 只更新凭据,不推进身份代次,也不得让任何在途生成、编辑、上传、确认或下载 operation 的冻结会话失效。此前把 token 字节和 native generation 一起当作身份判据,使一次正常续期被等价成换号,在途生图被判为 `authentication-required: 陶泥儿登录态已变化`;本条款取代该判据。
|
||||
- 冻结会话的校验判据只包含身份,不含 token 字节。换号、退出或 origin 变化仍然失败关闭:旧身份的 operation 后续一切 POST、poll、下载和结果安装都必须停止,只保留对账证据,不得改绑或重放。
|
||||
- 原生 install / clear 继续使用单调 revision 作为写入顺序判据,防止迟到写入复活旧状态;身份代次不参与写入排序,只参与身份归属判定。同身份凭据更新必须携带不小于当前 revision 的新 revision 且保持身份代次不变,才能替换 token。
|
||||
- `authentication-required` 只表达"当前 operation 对应的身份已不是权威身份"。同一身份的凭据轮换不得产生该错误。
|
||||
- 刷新失败语义:只有服务端明确返回 `401` / `403`,且收敛重试后仍然失败,才允许清除本地会话与 access token。网络错误、`5xx`、网关错误和响应契约异常必须保留既有会话与 access token,只让发起刷新的那个动作失败。
|
||||
- 并发刷新收敛:同一 origin 的 refresh 单飞;一次刷新返回 `401` 后允许用当前 refresh cookie 收敛重试一次,重试成功则继续使用新凭据,重试仍为 `401` 才判定登录态权威失效。
|
||||
- 服务端 `/api/auth/refresh` 的轮换失败不再下发清空 refresh cookie 的响应。refresh cookie 的失效只由会话吊销、过期或身份变更语义决定,不由一次轮换竞争决定;客户端以"收敛重试后仍 401"作为登出判据。
|
||||
- 证据要求:Rust 定向用例覆盖"同身份 token 轮换后冻结会话与在途 operation 仍有效""换号 / 退出后冻结会话失效""迟到 install 被 revision 拒绝";AGC 前端用例覆盖"续期不推进身份代次""瞬时刷新失败不清会话""收敛重试成功不登出";`api-server` 用例覆盖"刷新轮换失败不下发清 cookie"。
|
||||
|
||||
## 2026-09-15 DirectProject 长回合平台会话保活
|
||||
|
||||
DirectProject 的生图、素材处理、构建和试玩可能跨越短生命周期 access token 的有效期。普通 `/api/*` 请求和 Codex app-server 已有 401 刷新路径,但 AGC 工具由 Rust 工具桥直接使用客户端当前会话,工具内部的 401 不会自动触发前端刷新。客户端在 DirectProject 回合处于 busy 状态时每 5 分钟调用现有 `requestPlatformSessionRefresh()`;刷新仍复用单飞请求、generation 校验和 native session 安装,不改变凭据来源,也不把 401 降级为成功。刷新失败保持静默,由原始 AGC 工具错误按现有鉴权失败合同返回,避免后台保活覆盖真实错误。
|
||||
DirectProject 的生图、素材处理、构建和试玩可能跨越短生命周期 access token 的有效期。普通 `/api/*` 请求和 Codex app-server 已有 401 刷新路径,但 AGC 工具由 Rust 工具桥直接使用客户端当前会话,工具内部的 401 不会自动触发前端刷新。客户端在 DirectProject 回合处于 busy 状态时每 5 分钟调用现有 `requestPlatformSessionRefresh()`;刷新复用单飞请求、身份判据和 native session 安装,不改变凭据来源,也不把 401 降级为成功。刷新失败保持静默,由原始 AGC 工具错误按现有鉴权失败合同返回,避免后台保活覆盖真实错误。2026-09-16 起,同一账号的保活续期只更新凭据、不推进身份代次,因此不得中断在途生成 operation。
|
||||
|
||||
完成门禁同时允许已登记的普通平台图片作为运行时素材。此前只把 canonical art-spec、背景、图集和图集切片加入来源白名单;`agc_generate_image` 生成的 `assets/neon-*.png` 即使已经登记并被源码引用,也会被判成“未引用平台图片”,触发同一回合的重复修复。浏览器预览把本地图片 URL 改写成 UUID 路径时,验收按每个视口的已渲染本地图片数量与源码引用数量做有界匹配;仍要求两个视口都有对应观察,空视口继续进入修复。
|
||||
|
||||
@@ -50,6 +132,8 @@ DirectProject 自身的 `read_direct_project_conversation` 也必须在 blocking
|
||||
|
||||
## 图片生成恢复与测试边界
|
||||
|
||||
App 界面测试中,关闭 Agent 弹窗后的迟到读取用例先等待「刷新 Agent」按钮启用,以项目已载入作为前置条件;该等待最多 5 秒,整条用例最多 10 秒,弹窗关闭后的状态断言保持不变。权限策略 deny/confirm 互斥用例保留连续命令写入与累计策略断言,因包含 12 次聊天提交,单独设置 15 秒预算。其余用例继续使用默认超时,不以固定 sleep 替代状态等待。
|
||||
|
||||
已有持久生成账本的 Provider 待执行动作恢复时,若动作省略了旧视觉 Agent 自动补齐的参数,只在 Agent、动作身份、生成种类和冻结提示词均匹配旧合同后补齐缺省参数;显式参数不得被覆盖。新请求继续按当前自由图片合同执行,不能重新引入固定视觉产物门禁。恢复复用原 operation 与幂等账本,不因默认值变化重复提交已受理请求。
|
||||
|
||||
单 HTML 测试须在项目初始化前准备 HTML;npm 项目的预览与导出测试须准备构建目录。图片测试按现行数量和布局合同验证资源、透明度、引用和持久恢复,不继续要求固定四切片。
|
||||
@@ -58,9 +142,11 @@ DirectProject 自身的 `read_direct_project_conversation` 也必须在 blocking
|
||||
|
||||
## 常用设置职责
|
||||
|
||||
本地配置 `llm.customEnabled` 为 `true` 时,常用设置开放自定义 Responses 连接、端点模型发现与勾选预览;开关只允许手动修改配置文件。模型选择、直连与凭据边界以 [AGC 后台模型别名与对话选择](./【技术方案】AGC后台模型别名与对话选择-2026-09-05.md) 的“本地自定义 LLM”为准,默认仍使用官方服务。
|
||||
|
||||
常用设置负责运行参数的读取、编辑和保存,配置读写独立于账号权限诊断。账号权限由登录会话与实际智能服务请求链路处理,设置面板只维护配置草稿与读写反馈。
|
||||
|
||||
保存配置复用写入前读取的高优先级本地覆盖内容:常用设置同步覆盖文件中已有的对应配置项,并保留当前模型 ID 和默认模型标记;模型选择仅同步 `selectedModelId` 与 `selectedModelIsDefault`;无冲突时不写覆盖文件。所有内容先完成序列化,多文件写入前保存原始内容,任一写入失败时逆序恢复已变更文件,回滚失败须明确报告。各文件沿用现有原子写入,不提供断电或进程崩溃下的多文件事务保证。全部成功后直接返回规范化配置,不执行保存后回读或外部诊断;单文件保存保持原路径。
|
||||
保存配置复用写入前读取的高优先级本地覆盖内容:常用设置同步覆盖文件中已有的对应配置项,并保留当前模型 ID 和默认模型标记;自定义模式取消勾选当前模型时回退第一项,并同步覆盖文件中的选择。模型选择仅同步 `selectedModelId` 与 `selectedModelIsDefault`;无冲突时不写覆盖文件。所有内容先完成序列化,多文件写入前保存原始内容,任一写入失败时逆序恢复已变更文件,回滚失败须明确报告。各文件沿用现有原子写入,不提供断电或进程崩溃下的多文件事务保证。全部成功后直接返回规范化配置,不执行保存后回读或外部诊断;单文件保存保持原路径。
|
||||
|
||||
## 2026-09-09 manifest 资源功能分类与自定义标签(UI 与写入)
|
||||
|
||||
@@ -108,7 +194,7 @@ npm 游戏的可预览产物固定为对应 package 目录下的 `dist/index.htm
|
||||
|
||||
- 素材读取区分三类来源:`asset.list` / `agc_list_registered_assets` 是当前项目本地 manifest,`agc_list_project_files` / `file.list` 只发现项目目录中实际存在但可能未登记的文件,`asset.library.list` 是当前登录账号素材库,项目画布资源读取是当前网页项目/画布的完整图片清单;账户素材库不能替代项目画布清单。
|
||||
- Agent 只接收稳定素材 ID、类型、尺寸和项目相对路径等安全投影。客户端负责重新校验账号/项目归属、换签下载、媒体校验,以及 manifest/画布原子登记;不得向 Agent 暴露绝对路径、签名 URL、objectKey、token 或 Cookie。
|
||||
- `canvas.asset_import` 支持账户/画布资源 ID 和项目内本地相对路径。项目文件发现结果以 `assetImportable` 明确区分当前可登记的已识别图片、字体、音频、视频、文档和代码文件与其它文件;Agent 只能提交前者。导入拒绝路径穿越、`.agent`、符号链接/reparse point 及敏感配置文件;外部宿主文件须由 UI 原生文件选择器授权后导入,不开放任意绝对路径。
|
||||
- `canvas.asset_import` 支持账户/画布资源 ID 和项目内本地相对路径。项目文件发现结果以 `assetImportable` 明确区分当前可登记的已识别图片、字体、音频、视频、文档、代码与**引擎资源**(Cocos Creator 的模型、动画、场景/预制体、材质/特效、图集与压缩纹理容器)与其它文件;Agent 只能提交前者。引擎资源在资源画布上是**只读预览**:模型出缩略图、序列化资源出结构摘要、客户端解不了的容器出类型卡,不承接编辑与派生;`.meta`、`library/`、`temp/` 等引擎生成物仍然只可发现、不可登记。导入拒绝路径穿越、`.agent`、符号链接/reparse point 及敏感配置文件;外部宿主文件须由 UI 原生文件选择器授权后导入,不开放任意绝对路径。
|
||||
- Runtime `asset.list` 与 `file.list` 的详情使用文件上下文上限,而不是普通工具短摘要上限,确保有界候选/目录清单不会因前部内容较长而整体丢失;`asset.list` 超出 48 项或 `file.list` 超出 40 项时仍显式返回剩余数量,Agent 再按候选父目录(例如 `assets`、`game/assets`)缩小范围查询。
|
||||
- 结果仅返回成功/跳过/失败数量、安全 ID、相对路径、来源、脱敏失败摘要和实际 `revisionAdvanceCount`;幂等跳过不得虚增 revision,部分失败仍须准确记录已发生的 revision 变化。
|
||||
- 普通 Prompt 上下文与错误诊断必须使用分离的脱敏边界:Prompt 继续对疑似凭据行整体隐藏;错误诊断保留 HTTP 状态以及 `code / field / message / reason / detail` 等安全字段,仅替换 Token、Cookie、私钥、配置名、URL 和宿主路径等敏感值。`agc_create_or_derive_resource.assetName` 是必填的人类可读资源显示名称,不接受项目路径、URL、objectKey、Token 或其它凭据。
|
||||
@@ -116,10 +202,11 @@ npm 游戏的可预览产物固定为对应 package 目录下的 `dist/index.htm
|
||||
## 2026-08-24 Direct Codex 已登记资源查询与媒体生成语义工具
|
||||
|
||||
- `agc_tools` 新增 `agc_list_registered_assets` 与 `agc_create_or_derive_resource`。前者按 `kind / assetId / offset / limit` 有界查询客户端权威 manifest,并可显式返回角色动画正式序列帧的稳定 objectKey、assetObjectId 和尺寸;结果不包含完整 manifest、prompt、model、provider route、签名 URL、宿主路径或凭据。后者只接受 `kind / mode / sourceLocalAssetId / prompt / assetName`,`create` 仅允许无源视频、音效和背景音乐,`derive` 必须引用当前项目已登记的 localAssetId,角色动画固定为 derive。
|
||||
- `prompt` 上限按 `kind` 分别生效,且工具 schema、MCP 校验、客户端工具桥与提交校验共用同一权威口径(`resource_edit_prompt_max_chars`):背景音乐 140、音效 1900、视频与角色动画 4000、图片编辑 32000。schema 逐 kind 声明 `maxLength` 并在 `prompt` 描述里写明数字,超限必须在发起任何桥请求与付费提交之前失败并回报真实上限;`sourceLocalAssetId` 不是当前项目已登记资源时,错误文案必须直接给出 `agc_list_registered_assets` 与 `agc_list_project_files` → `agc_import_account_assets.localPaths` 两步后续动作。
|
||||
- 项目路径、projectId、当前 revision、源文件路径与媒体类型、operationId、Idempotency-Key、登录态、项目锁、付费提交、轮询恢复、下载校验与 manifest 事务全部由客户端持有。模型不能提交或覆盖这些字段。同一 Direct `clientTurnId + 规范语义参数` 生成稳定 UUID v4 身份;单回合同参重试复用原 operation,不同请求串行且最多四项。跨回合存在完全匹配的 pending 账本时优先恢复原 operation,不能换键重发。
|
||||
- 资源查询同时投影未完成 operation 的安全状态。媒体工具成功只返回 operation、本地相对路径、资源类型、Canvas/resource/asset/task 身份、正式序列帧以及脱敏后的 `warnings / sliceWarnings`;错误继续使用统一脱敏边界。客户端资源账本持久化 completed 结果的两类告警,committed replay 不能把历史告警伪装成空集合。
|
||||
- 角色动画、视频、音效和背景音乐在构造新的远端请求前统一准备当前项目同名画布与素材目录上下文,并在端点支持时携带 `projectId / assetFolderId / canvasCompletion`。角色动画 placeholder 使用源图片真实宽高,避免非方形角色进入画布时失真;正式 resource/asset 与序列帧继续直接复用 External 返回身份,不从首帧伪造重复资源。已有冻结 request body 或已受理 operation 保持不变,不因本次升级重建请求或重复扣费。
|
||||
- 抠图通过新增 `agc_remove_background` 语义工具开放:模型只提交当前 manifest 的图片 `sourceLocalAssetId` 与结果名称;客户端解析稳定 `resourceId`,准备同名画布/素材目录并生成稳定 operation/idempotency 身份,调用 External v1 `/api/external/v1/editor/images/background-removals` 后只返回有界队列状态。抠图服务仍由客户端和服务端负责源校验、BgFilter、素材登记与画布事务,Codex 不获得内部 worker、凭据或任意 API 调用权。
|
||||
- 抠图通过新增 `agc_remove_background` 语义工具开放:模型只提交当前 manifest 的图片 `sourceLocalAssetId` 与结果名称;客户端解析稳定 `resourceId`,准备同名画布/素材目录并生成稳定 operation/idempotency 身份。普通登录态使用账号鉴权的 `/api/editor/images/background-removals`,ExternalDeveloper 模式使用 External v1 `/api/external/v1/editor/images/background-removals`;客户端接收异步受理后轮询任务状态,下载完成媒体并登记到本地 manifest,未知结果保留同一 operation 供恢复。抠图服务仍由客户端和服务端负责源校验、BgFilter、素材登记与画布事务,Codex 不获得内部 worker、凭据或任意 API 调用权。
|
||||
|
||||
## 2026-08-23 AGC 资源生成补齐(视频 / 动画 / 音效 / 背景音乐)
|
||||
|
||||
@@ -305,6 +392,11 @@ Supervisor 认领该回执后,由父 run 自己为每个原 delivery 逐一创
|
||||
- 调度边界:正式 DAG、manifest、Agent task/session/run 身份、队列、锁、委派、all-join、完成门、Provider lifecycle、持久 retry/handoff 与 `needs-reconciliation` 继续由现有 AGC Runtime 掌控。每个被调度节点在 `codex_cli` 模式下直接启动一次非交互 `codex exec` 充当该节点的推理 Agent;Codex 返回当前 Runtime 广告函数的结构化调用,Runtime 仍是唯一 ToolHost,不允许 CLI 自己写项目、执行命令、调用 MCP 或形成第二套 revision / verification 真相。
|
||||
- 安装包侧车:Windows x64 release 固定随 Tauri resource 打包 `@openai/codex@0.147.0` 的原生 `codex.exe`;Rust build script 从 AGC 子包锁定依赖 stage 到 resource,并写入版本与 SHA-256 清单。Windows 侧车映射只写入 `tauri.windows.conf.json`,通用 `tauri.conf.json` 不得让 Linux / macOS 构建依赖未生成的 Windows 二进制。运行时只在文件摘要和 `codex-cli` 版本同时匹配清单时优先选内置侧车;缺失、损坏或版本漂移时跳过它,按既有 npm 安装、PATH 顺序回退。安装包同时携带 Apache-2.0 第三方声明;API Key、`auth.json`、Cookie、Token、用户 `CODEX_HOME`、用户配置和项目数据绝不打包。
|
||||
- Windows x64 release 安装包只生成 NSIS,不生成 MSI:`tauri.windows.conf.json` 的 `bundle.targets` 固定为 `["nsis"]`,通用配置继续保留其它平台的默认打包目标。安装后的产品名、开始菜单 / 桌面快捷方式和 EXE 产品描述统一由 `tauri.conf.json` 的 `productName: "陶泥儿"` 生成;应用 identifier 与内部可执行文件名保持稳定。内置 Codex 资源安装到顶层 `coding-agent/win-x64/`,运行时从同一路径查找 `bin/codex.exe` 与 `manifest.json`;仓库 staging 仍使用 `resources/codex/win-x64/`,包内子目录、组件名、版本和完整性校验保持原合同。
|
||||
- macOS 单架构安装包同样必须携带锁定版本的原生 Codex、`codex-code-mode-host`、`rg`、上游 zsh、`codex-package.json` 和第三方声明,保留上游相对布局;构建时按 Cargo 目标选择 npm 原生依赖,缺文件、版本或目标不匹配立即失败,不借用开发机 PATH 里的 Codex。资源只在 `tauri.macos.conf.json` 映射到 `Contents/Resources/coding-agent/mac-native/`。构建与运行共享平台文件白名单,运行时由当前 `.app/Contents/MacOS` 定位相邻 `Resources`,完整性与版本验证通过后优先使用内置组件;失败沿既有外部安装回退,不能运行未校验的内置文件。单架构资源不能冒充 universal 包。
|
||||
- 内置插件的清单、运行入口与面板同时在 Windows/macOS 随包分发,继续由既有 PluginHost 的应用资源目录扫描入口发现;不携带开发依赖、缓存、测试或私有配置。插件文件随包不等于原生适配器跨平台:Cocos 进程桥接仍受现有 Windows 实现和 feature 门禁约束,macOS 原生桥接另行设计与验收,不复制 Windows DLL 冒充支持。系统 Node、用户 Cocos Creator、账号登录、网络和生成工程的 npm 工具链仍是现有外部前提,不在此次 Codex 侧车补齐中隐式变更。
|
||||
- macOS 安装包验收必须包括:脱离仓库位置的 `.app` 资源与架构检查、受限 PATH/隔离 HOME 下内置 Codex 启动和 app-server 握手、必需文件缺失/篡改/平台错误的拒绝测试,以及 DMG 完整性检查。真实登录、Provider 对话、GUI 和 Cocos 操作必须独立列出证据,不能用压缩包生成或 `--version` 成功替代。未配置正式签名、公证的本地测试包不得作为公开发行包。
|
||||
- macOS 安装包的系统下限取主程序和全部原生组件中的最高要求;锁定 Codex 0.147.0 原生依赖所携带的 zsh 要求 macOS 15.0,因此 `bundle.macOS.minimumSystemVersion` 明确为 `15.0`。更新原生依赖时重新检查 Mach-O 的系统下限,不能只按 AGC 主程序宣称兼容版本。
|
||||
- 发布链路的目标解析和单架构清单以《AGC客户端更新检查与下载》为准:CLI 目标优先,版本、构建、端点、bundle 与更新清单共用单一发布上下文。插件能力以《AGC通用插件宿主与编辑器适配》为准:无已注册 Cocos 原生适配器时隐藏且拒绝启动,前端自动启动只消费后端可用性投影。
|
||||
- CLI 安全边界:CLI 固定使用 argv 启动,禁止 shell 拼接;工作目录使用本次请求专用的空临时目录,不把游戏项目绝对路径写入 prompt、stdout、stderr 或持久记录。调用固定使用 ephemeral、忽略用户配置和 exec rules、read-only sandbox、never approval,并关闭 Codex shell tool;只继承 CLI 运行和认证所需的最小环境,显式移除宿主 `CODEX_API_KEY`。用户级 Codex 登录态继续由本机 Codex 自己读取,API Key、auth 文件、Cookie、Token、`CODEX_HOME` 私有内容不得复制到项目配置、Runtime sidecar、Agent DB、conversation 或日志;stdout / stderr 无换行时也受硬上限约束,stderr 诊断只记录固定分类、字节数和 SHA-256。
|
||||
- 协议边界:Runtime 把既有 `LlmRunRequest` 的消息和当前函数目录编码为有界 prompt,并从同一函数 JSON Schema 生成 Codex structured-output schema。CLI 输出转换为现有 `LlmRunResponse / LlmToolCall` 后,继续经过 native tool / MCP 参数校验、动作上限、权限、pending、receipt、验证与格式修复链;最终回复仍走唯一提交路径,不新增平行响应协议。
|
||||
- 取消与恢复:Codex 子进程绑定当前 Provider request lifecycle,取消、暂停、Runner draining 或 GUI owner 丢失时终止并回收当前进程;started 后没有可信终态仍沿现有 Provider reconciliation 处理。`agentMode`、CLI 可执行身份和影响输出的 Codex 参数进入 `providerConfigFingerprint`,模式切换不得消费另一模式遗留的 retry/handoff。
|
||||
@@ -546,6 +638,7 @@ Agent Runtime 负责:
|
||||
- 2026-07-10 补充:`agent.delegate` 已形成可恢复的父子任务闭环。`delegationId` 由 durable pending action 的 `actionId` 派生,子任务记录会保存 `parentAgentId / parentRunId / delegationId`,终态记录额外保存经过统一凭据清洗和安全截断的 `terminalDetail`;同一委派的提交和回执分别受 delegation 级 OS 文件锁保护,同一目标 Agent 的 runId 分配与 pending 追加还受任务账本 OS 锁保护。子任务进入 `completed / failed / cancelled / budget-exhausted` 任一终态时,Runtime 按 `delegationId` 幂等生成且至多生成一次 `agent.delegate.result` 回执,失败、排队或活跃取消、预算耗尽都必须回传,不能只覆盖成功。回执会向父 Agent 既有队列追加固定 runId、`source=agent-delegate-receipt` 的续跑任务,把完整的已清洗 `terminalDetail` 交回父 run,不再只保留 80 字符 UI 摘要;回执 prompt 明确禁止重复同一委派,排队期间不提前写入父会话,真正开始执行时才幂等落盘,用户消息或回执消息落盘失败时不会进入 LLM。回执任务保留父 run 关联,并在真正开始或恢复前再次检查父 run 状态,关联缺失或父 run 不存在时失败关闭;该续跑仍受父 Agent 原有 FIFO、per-Agent OS 锁、权限确认、取消、恢复和 `needs-reconciliation` 屏障约束,不直接重入父 run、不插队、不新增独立 worker;父 run 已取消或普通失败时只保留 suppressed receipt 审计,不自动复活,父 Session 归档与切换会被未结束委派阻止,极端归档竞态下回执回落到父 Agent 当前可写 Session。恢复先恢复 pending action / reconciliation 屏障,再扫描“子任务终态已落盘但回执未提交”的窗口并补齐缺失回执;`needs-reconciliation` 本身不回执,只有人工核对后最终取消才回传 `cancelled`。
|
||||
- 历史记录(已由 V1.1 独立 Runner 替代):Runtime 最初通过 `resume_game_creator_agent_runtime_tasks` 把本地 JSONL 队列重接到当前 App 进程。当前恢复入口仍保留权限、任务顺序和 `agent.runtime.background_task.recovered` 审计语义,但实际由独立 Runner 接管原 run / session;已发出的上游 LLM 请求仍不能从网络中间点续传。2026-07-27 起,Runner 归 Tauri GUI 生命周期所有,同一 AppData 只允许一个 GUI owner。GUI 启动子进程会显式声明 `--gui-owner-required` 并在就绪后 attach owner;Runner 若在启动检查前已发现 owner 释放则直接失败,不得退化成 CLI-owned Runner。Runner 使用独立 watchdog 线程每 100ms 监控 owner OS 锁,不依赖服务端主循环继续推进;owner 丢失后先触发 1.5 秒共享 deadline 的 draining、Provider 中断和 process session 回收,若主循环或排空链路卡死则在 1.75 秒后由 Runner 自身进程安全硬退出并清理匹配 bootId 的 endpoint。GUI 客户端还必须把完整 `runner.attach_gui_owner` 参数作为绑定规范化 AppData 的进程内登记保存;`ensure_external_agent_runner` 无论复用既有 endpoint 还是启动新 Runner,都要在把 endpoint 交给 Runtime 写请求前按新 `bootId` 补登记。同一登记 generation 在同一 boot 上幂等,补登记失败不得记录成功 boot 且本次 `ensure` 失败关闭;未建立 GUI 登记的普通 CLI 不执行该重放。OS owner 锁与 watchdog 已成立只代表进程受 GUI 生命周期约束,不能替代事件 sink 等进程内附加能力的逐 boot 恢复。因此正常最终退出、panic、SIGKILL 和 setup 中途失败都不会再因 busy 或主循环卡死而残留后台进程。endpoint 缺失 / 读取失败必须结合 Runner 实例锁判断;GUI 客户端强制兜底在 Linux 使用 pidfd、Windows 使用稳定进程 handle。macOS 没有等价稳定句柄,客户端不得在 start identity 检查后按裸 PID 强杀,而由跨平台 Runner 自身 watchdog 提供硬退出兜底。旧 endpoint 缺 start identity 时,只有认证 ping 精确匹配 PID + bootId 才允许迁移 busy 旧 Runner。未完成任务保持 durable 状态并在下一次启动走 reconciliation / recovery,不能伪造 completed 或重放副作用。关闭单个 WebView / 子窗口和普通 CLI 退出不触发该行为,版本切换与人工命令仍可使用只关闭空闲实例的 `runner.shutdown_if_idle`。
|
||||
- 2026-08-23 Runner 协议 v7 GUI owner 会话权威补充:GUI 取得 owner OS 锁时产生随机 `owner epoch`,并在私有 AppData 持久化只含 `owner epoch + session revision` 的 claim;每次登录、refresh、退出或换号都必须先单调推进 durable session revision,再同步 Runner。`runner.attach_gui_owner` 是 Runner 接受平台会话快照的唯一授权入口;只有 attach 携带的 epoch/revision 与 durable claim 完全一致才可安装或清除会话,新 GUI epoch 可替换旧进程留下的高 `authGeneration`,不用可在新 WebView 重置的 generation 猜测进程所有权。Runner 在 claim 缺失、不可读或与当前 attach 身份失配时立即清空进程内平台会话,并阻断除重新 attach 及必要管理请求以外的 Runtime 工作;旧 `platform.session.install/clear` 协议不再是授权入口。GUI 会话同步未得到完整 attach 确认时本地变更必须失败,并隔离或停止旧 Runner;即使进程终止失败,claim 失配门禁也不允许旧账号继续发起 Runtime 请求。claim 不保存 Access Token,Token 只随当次受保护的 attach IPC 进入 Runner 内存。
|
||||
- 2026-09-16 多窗口更正:同一 AppData 不再只允许一个 GUI 界面进程。原 owner OS 锁改为可被多个界面进程同时持有的参与者锁(`agent-runner.gui-participant.lock`),Runner 以“能否独占取得该锁”判断是否仍有界面进程存活,watchdog 与 attach 门禁都改用该判定。新增窗口默认只**采纳** durable claim(读同一 epoch/revision 并 attach),只有登录、refresh、退出或换号才发布新 claim(新 epoch + 本窗口 revision),因此同 claim 的重复 attach 不再清空平台会话,epoch 变化才允许强制替换。事件接收端从单槽改为按 token 去重的注册表并广播,保证第二个窗口 attach 后第一个窗口仍收到 manifest 失效与 Runtime update relay。claim 一致性与失败关闭语义不变。详见同文档「2026-09-16 AGC 同 AppData 多窗口共享 Agent Runner」。
|
||||
- 2026-08-05 GUI owner attachment 确认补充:登记参数必须保存 GUI manifest 事件接收端的真实 `event_sink_port` 与 `event_sink_token`,不得借用 actionId 等无关字段作为测试替身。每次 attach RPC 只有同时返回 `attached=true` 与 `eventSinkAttached=true` 才能把当前 `bootId` 标记为已登记;`eventSinkAttached` 缺失、为 false 或普通 RPC 失败都保持当前 boot 待重试。sink token 只留在私有进程内登记和 RPC 参数中,不进入日志、错误文本或公共状态。
|
||||
- 2026-07-10 补充,2026-07-16 由 V1.28 澄清:后台 planning 与预算内 final reply 使用专用最小上下文,只预置 Agent 身份、sessionId、runId、执行模式和工具策略;Agent 私有记忆、项目记忆、黑板、对话、资产、项目索引与文件正文只能经对应工具通过权限 gate 后作为 observation 进入下一轮。只有开发窗口的专业 Agent 前台直调可使用对应角色上下文;正式用户前台现已统一进入 `project-supervisor`。长黑板、记忆和对话按尾部截断,确保最新结论与最新定向消息优先保留。
|
||||
- 2026-07-10 补充,2026-07-16 由 V1.28 澄清:同一 Agent 的开发前台直调、流式调试和后台任务统一使用 `.agent/runtime/locks/<agentId>.lock` OS 文件锁。开发前台不再在整个 LLM 请求期间占用项目级写锁;同 Agent 后台任务在开发前台运行时只入队,前台成功或失败后把当前 Agent 锁直接移交给 drain,不重新抢锁,也不允许 drain 启动异常把已经完成的调试结果改判为失败。正式用户 GUI 不通过该入口直聊专业 Agent;不同 Agent 继续并行,真实项目写工具只在副作用执行期间短暂申请项目写锁。
|
||||
@@ -1445,3 +1538,95 @@ Direct 回合的所有权属于进程内项目身份锁,不属于当前页面
|
||||
- 运行中的正文和工具按原有唯一回合流实时显示;完成后,除最终回复和失败提示外,中间文本与所有工具调用统一放入默认收起的“执行过程”,允许手动展开,刷新或重新进入仍默认收起。
|
||||
- 最终回复沿用 Runtime 的最后一个 assistant item 合同,不按文本长度或相似度判断。失败回合不把最后一句过程输出伪装成最终回复。无流历史按同一用户消息边界划分,只保留最后一条 assistant 回复在外;用户消息与失败提示始终保留。
|
||||
- 验收覆盖已完成回合重进、真实活动回合恢复、跨项目迟到快照、运行到完成自动收起、历史无流、失败、发送时间刷新和旧记录时间缺失。不改变实际工具执行、鉴权、数据库或用户项目内容。
|
||||
|
||||
## 2026-09-16 AGC 同 AppData 多窗口共享 Agent Runner
|
||||
|
||||
### 目标与非目标
|
||||
|
||||
- 目标:同一个 AppData 可以同时运行多个 AGC 界面进程,它们共享同一个 Agent Runner、同一份平台登录态权威和同一份项目事实,并且都继续收到 manifest 失效与 Runtime update relay。
|
||||
- 非目标:不引入多 Runner、不做跨 AppData 的会话共享、不改变渲染层 generation 语义、不修改平台 HTTP 契约、SpacetimeDB schema 与 `/api/external/v1`。
|
||||
|
||||
### 参与入口、状态与跨模块边界
|
||||
|
||||
- 界面进程持有的 OS 锁改为**参与者锁** `agent-runner.gui-participant.lock`:它以共享句柄打开,任意数量的界面进程可同时持有;Runner、watchdog 与 attach 门禁只做“能否独占取得该锁”的探测,独占成功即表示已无界面进程存活。
|
||||
- durable claim `agent-runner.gui-owner.claim.json` 仍是唯一的 owner 授权记录,内容仍为 `ownerEpoch + sessionRevision`;claim 不保存 Access Token。
|
||||
- 项目级 `.agent/project.lock` 不变:多窗口共享 Runner 不等于共享项目写权,同一项目同一时刻仍只有一个写者。
|
||||
|
||||
### 正常、失败、重试与幂等行为
|
||||
|
||||
- 采纳:窗口启动时先读取 durable claim 并以同一 `epoch/revision` attach;claim 缺失或不可读时才发布新 claim。采纳路径不写 claim。
|
||||
- 发布:登录、refresh、退出或换号时,窗口写入新 claim(新 epoch + 本窗口 revision)再 attach;同一次发布在多个窗口并发发生时以最后一次成功写入的 claim 为准,落败窗口按最新 claim 重试,重试仍有界失败时向用户暴露错误,不做隐式合并。
|
||||
- 幂等:同一 claim 的重复 attach 是空操作,不得清空或替换 Runner 平台登录态;只有 epoch 变化或携带明确登出参数的 attach 才允许替换 / 清空。
|
||||
- 失败关闭:attach 携带的 epoch/revision 与 durable claim 不一致、claim 不可读、或 claim 在 attach 提交期间变化时,Runner 继续清空进程内平台会话并阻断除重新 attach 与必要管理请求以外的 Runtime 工作。
|
||||
- 事件:manifest 失效与 Runtime update relay 广播到全部已登记接收端(按 `event_sink_token` 去重,发送失败即淘汰该接收端),单个窗口退出或接收端失效不得影响其它窗口。
|
||||
- 生命周期:Runner 跟随“是否仍有界面进程存活”,不跟随某一个窗口;全部窗口退出后必须在有界时间内关停并清理 endpoint、释放实例锁。
|
||||
|
||||
### 契约与兼容
|
||||
|
||||
- 本机 GUI ↔ Runner 协议方法与参数不变;变化只在参与锁语义、claim 采纳/发布时机与事件接收端注册表。
|
||||
- 已退役的“同一 AppData 只允许一个 GUI owner”行为不再保留兼容分支;`startup.runner.owner-lock.failed` 诊断分类改名为参与者锁失败,仅在真正无法建立参与锁时出现。
|
||||
- 混用新旧版本二进制访问同一 AppData 不属于支持场景:旧版本仍以独占方式持有旧锁文件,可能让新版本判定为“仍有界面进程存活”。
|
||||
|
||||
### 验收标准与证据来源
|
||||
|
||||
- 定向 Rust 测试:参与者锁可多进程同时持有、全部释放后存活判定为假;同 claim 重复 attach 不改写登录态;epoch 变化才强制替换;两个接收端都收到同一事件;claim 采纳与发布的失败关闭路径。
|
||||
- 运行时 smoke:同一 AppData 启动两个真实 GUI,两个进程都完成 `startup.setup.complete`,`--agent-runner` 进程只有一个,关闭其中一个后另一个仍可继续使用 Runner。
|
||||
- 边界:项目级写锁继续拒绝两个窗口同时写同一项目;新增日志与错误文案不含 Token、Access Token、API Key 与绝对路径。
|
||||
|
||||
### 未决问题
|
||||
|
||||
- 两个窗口同时对同一项目发起 Runtime 写请求时,用户体验仍由项目级写锁串行决定;本次不引入跨窗口排队提示。
|
||||
- 平台会话在窗口间传播依赖共享 localStorage 与 Runner 权威;渲染层不做跨窗口事件推送,另一个窗口在下一次会话校验或刷新时收敛。
|
||||
|
||||
## 2026-09-17 AGC 项目定时快照上传(agc-dev)
|
||||
|
||||
### 目标与非目标
|
||||
|
||||
- 目标:AGC 在项目工作区打开期间按固定周期把用户项目增量上传到 OSS `agc-dev`,并在项目关闭时立即补一次同步;重复内容不重复上传,远端占用跟随当前清单收敛。
|
||||
- 非目标:不做云端下载/恢复、不做跨设备合并、不保留多版本历史、不新增面向用户的上传界面、不修改 `/api/external/v1` 与 OpenAPI、不新增 SpacetimeDB 表。
|
||||
- 非目标:不把 OSS AccessKey 放进客户端;客户端不直连 OSS。
|
||||
|
||||
### 参与入口、状态与跨模块边界
|
||||
|
||||
- 触发入口有两个:工作区窗口 `main` 存活期间的周期定时器、工作区窗口关闭事件(`CloseRequested`)。两者共用同一个进程内同步器,同一项目的同步串行执行,周期触发在已有同步进行时直接让位,不排队堆积。
|
||||
- 应用退出(`RunEvent::Exit`)不重复发起同步:该时刻窗口已销毁,按窗口重新枚举项目只会得到空集;退出路径只负责在有界预算(15 秒)内等待在途同步收尾,让关窗触发的那一次同步能写完索引再退出。
|
||||
- 客户端扫描、差异对比、索引持久化与上传编排都在 Tauri Rust 进程(`src-tauri/src/project_snapshot/`);WebView 只读状态,不参与差异计算。
|
||||
- 本地索引是增量对比的唯一依据:`<AppData>/project-snapshots/<projectId>/index.json` 保存上次成功同步的相对路径、校验和、字节数和修改时间。项目根使用现有 manifest 的稳定 `project_id` 作为远端身份,路径不再作为身份。
|
||||
- 可观测性按产品口径收敛到本机日志:同步结果、失败分类、延后与跳过计数只写入 AppData 诊断日志(`project_snapshot.sync.*` 前缀),客户端界面不暴露上传状态、时间线或入口按钮。`read_local_project_snapshot_state` 与 `sync_local_project_snapshot` 两条命令仅作为 native-only 的排障与联调入口登记,不在渲染层调用。
|
||||
- 远端写入经 `api-server`,客户端只持平台登录态 Access Token。两条登录态路由:`POST /api/agc/project-snapshots/files`(单文件,正文为原始字节,元数据走查询串)与 `POST /api/agc/project-snapshots/manifest`(本次同步后的完整清单)。
|
||||
- 对象键与清单由服务端决定:文件键为 `agc/project-snapshots/v1/{userId}/{projectId}/files/{sizeBytes}-{checksumDigest}/{relPath}`,清单键为 `agc/project-snapshots/v1/{userId}/{projectId}/manifest.json`。键里带字节数与摘要,因此"对象已存在且长度一致"可以作为内容一致的判据;路径按原始大小写保留,不走 `put_object` 的低位规范化。`agc` 前缀继续是服务端专用私有前缀,通用对象键解析与客户端直传票据都不覆盖它。
|
||||
- 目标 bucket 使用独立配置 `GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_BUCKET` / `_ENDPOINT` / `_ACCESS_KEY_ID` / `_ACCESS_KEY_SECRET`,默认 `agc-dev` + `oss-rg-china-mainland.aliyuncs.com`,未配置时回退 `ALIYUN_OSS_*`;与"资源 bucket 与备份 bucket 分离"的既有口径一致。
|
||||
|
||||
### 正常、失败、重试与幂等行为
|
||||
|
||||
- 差异对比口径:先按 `相对路径 + 字节数 + 修改时间` 判定是否候选变更,命中旧记录则复用已存 `sha256`,只有 `(size, mtime)` 变化才重算摘要。产出新增、修改、删除三类集合,只上传新增与修改的文件。
|
||||
- 每次成功同步的最后一步上传该项目的 `manifest.json`(当前全量文件清单:相对路径、摘要、字节数、同步序号)。清单描述的是项目当前全量内容,因此清单体积就是该项目在 OSS 上的常驻占用。
|
||||
- 远端回收:清单写入成功后,服务端读取上一版清单,按 `(路径, 字节数, 摘要)` 反推出不再被当前清单引用的对象键并删除。只处理上一版清单登记过的键,不做 LIST,因此不可能误删其它项目或其它功能的对象;单次最多回收 2000 个对象,剩余部分留到下一次清单写入继续;上一版清单读不到或解析失败时整轮跳过回收(fail-closed)。单个删除失败只记日志,不影响本次同步语义。
|
||||
- 因此本功能是"当前状态镜像 + 清单",不保留历史版本:同一路径的内容变化会覆盖式替换远端对象,回滚能力不在本轮范围内。
|
||||
- 幂等:同一摘要与字节数的对象重复提交由服务端 HEAD 校验后跳过;探测失败按"未存在"处理并照常 PUT,宁可多传一次也不漏传。索引只在清单写入成功后推进,失败时保留旧索引以便下次重算。
|
||||
- 与项目写锁解耦:同步不持有项目写锁,也不阻塞 Agent 写入。读取摘要后与上传前各按 `(字节数, 修改时间)` 复核一次,任一处不一致就判定该文件"同步期间发生变化":本轮不上传、不写入索引;若该路径上一轮已同步过则沿用旧记录,避免被误判成删除而触发远端回收。这类文件在下一个周期或下次关窗时重算重试。
|
||||
- 失败关闭:单个文件失败不推进该文件的索引项,失败文件与剩余文件在下一次周期或下次关闭时重试。鉴权失败(401/403)与格式类拒绝(400/413)是确定性失败,停止本轮剩余请求并等待用户处理后重试;服务端配额或频率拒绝(429)与传输类失败按可重试处理。
|
||||
- 配额与限流:单文件 64 MiB、单次同步上传预算 512 MiB(超出部分延后到下一次)、单项目常驻上限 2 GiB(客户端在扫描后先判,超限直接给出明确失败;服务端按清单累计体积复核并返回 413);服务端按用户做进程内小时配额(文件 3000 次、清单 120 次)并对同一项目强制 5 秒最小清单间隔,超限返回 429 且带 `Retry-After`。进程内配额只用于抑制异常客户端与失控重试,跨节点配额由"单项目上限 + 清单引用回收"保证。
|
||||
- 生命周期:同步有界超时(单文件与整次同步分别设上限),项目关闭与应用退出路径不因同步失败而阻塞或延迟退出超过超时上限。
|
||||
- 上传内容边界:复用项目索引与 checkpoint 同一份 `should_skip_project_snapshot_path` 口径——整个 `.agent`(含 runtime、logs、checkpoint、manifest、project.lock)、版本控制目录、`node_modules`/`target`/`dist`/`build`/`coverage`/`.cache`、凭据目录与 `.pem`/`.key` 等敏感后缀都不参与同步;符号链接与重解析点同样跳过。单文件(64 MiB)与单次同步总量(512 MiB)各有上限,超限文件进入跳过或延后清单而不是静默丢弃。
|
||||
|
||||
### 契约与兼容
|
||||
|
||||
- 新增登录态内部路由 `POST /api/agc/project-snapshots/files`,请求 DTO 放在 `shared-contracts`;不属于 `/api/external/v1`,因此不更新 External OpenAPI,与 `/api/error-reports` 同类。
|
||||
- 服务端校验 `projectId` 形态(拒绝路径分隔符、`..`、控制字符与超长值)、相对路径规范(正斜杠、拒绝绝对路径与穿越)、摘要形态(`fnv1a64:` + 16 位十六进制)和字节数上限,任何越界返回 4xx 而不是写入 OSS。
|
||||
- 不改变客户端与 Runner 的本机协议、平台会话语义、项目写锁与 manifest 结构;新增索引文件位于 AppData,不进入用户项目目录。
|
||||
|
||||
### 验收标准与证据来源
|
||||
|
||||
- 定向 Rust 测试:首次同步全量、仅改一个文件时只产生一个修改项、删除文件只体现在清单、`(size,mtime)` 未变时复用旧摘要、排除规则与上限跳过、同步失败不推进索引、同一项目并发触发串行化。
|
||||
- 服务端测试:越界 `projectId`/相对路径/摘要被拒;相同摘要重复提交走跳过分支;超过单项目上限返回 413;超过用户小时配额返回 429;鉴权缺失返回 401;OSS 未配置返回明确的 5xx 而不是写入空对象。
|
||||
- 运行时 smoke:AGC 开发态打开项目、观察索引写入与同步日志、关闭工作区窗口后确认关闭触发的那次同步执行;报告为"客户端 diff 已验证 / 服务端已配置环境联调"两层,不合并成一句"已通"。
|
||||
- 边界:新增日志与错误文案不含 Access Token、AccessKey、绝对路径与项目内容。
|
||||
|
||||
### 未决问题
|
||||
|
||||
- 用户侧看不到同步状态与失败原因(界面按产品口径不暴露),排障只能读 AppData 诊断日志或调用 native-only 命令;如果后续要支持用户自助排查,需要先确认是否允许在客户端出现上传相关 UI。
|
||||
- 历史版本:本轮只保留"当前状态镜像 + 清单",旧内容对象在清单写入成功后即被回收,没有回滚能力;要保留历史版本需要先定"保留几个 revision + 由谁回收"的策略。
|
||||
- 用户级配额:跨节点的用户总量配额与计费口径未定;当前用单项目 2 GiB 上限 + 清单引用回收保证常驻占用有界,用户级总量只能靠项目数间接约束。
|
||||
- 目标 bucket 的生命周期规则(例如转低频/过期删除)需要在部署环境确认后单独收口;功能本身已不再依赖它来控制增长。
|
||||
- 大项目(素材数量多、单文件大)的首轮全量上传耗时与带宽占用未实测;单次预算 512 MiB 会把超出部分留到下一次同步,但并发上限与断点续传仍未引入。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# DirectProject Codex 原始历史与异常恢复
|
||||
|
||||
更新时间:`2026-09-15`
|
||||
更新时间:`2026-09-16`
|
||||
|
||||
## 目标
|
||||
|
||||
@@ -18,7 +18,7 @@ DirectProject 只使用 `.agent/conversations/project.jsonl` 作为对话历史
|
||||
|
||||
`project.jsonl` 的 `payload` 必须是未经改写的 Responses item。AGC 前端 user input 先以 canonical user message item 形式写入;发送给 app-server 前由 Rust 投影为 Codex 可接受的 `message` item,AGC 私有 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 历史读取。
|
||||
Thread Manager 的运行态事件是另一份内存协议:app-server 通知先经过安全投影(挑字段、脱敏、截断、路径归一),再按与历史切片同形的脱敏原始条目(`itemType`、唯一 `itemId`、正文或工具明细)加 delta 文本与 turn 终态下发;搬运层不生成卡片形状,也不把未经脱敏的完整 item 转发到前端。注入 Codex 用的完整 item 仍只从上述 JSONL 历史读取。
|
||||
|
||||
DirectProject 自己的写侧只写新格式:格式切换(#282)时仍会写旧行的路径已收口——显式 Codex 返回只落在自己的 journal `.agent/conversations/codex-responses.jsonl`,不再投影进 `project.jsonl`。
|
||||
|
||||
@@ -96,26 +96,54 @@ readHistory(threadId, { beforeItemId?, limit }) -> {
|
||||
|
||||
`consume` 不接收或返回 cursor。每个 subscriber 在 Rust 内部持有自己的 cursor,并在加锁的临界区内完成过期判断、读取和 cursor 前进。前端只持有 `subscriptionId` 与 reducer state。并发 `consume` 不重复返回同一批事件。
|
||||
|
||||
`notify` 只负责唤醒,不携带事件、cursor 或持久化状态。前端收到通知后调用 `consume`;通知可合并、重复或丢失,事件完整性由 `consume` 保证。
|
||||
`notify` 只负责唤醒,不携带事件、cursor 或持久化状态。前端收到通知后调用 `consume`;通知可合并、重复或丢失,事件完整性由 `consume` 保证——但前提是前端确实唤醒了 `consume`,见下条的回执竞态。
|
||||
|
||||
`subscribe` 在同一个边界内先把新 subscriber 的游标钉在当时的队尾,再收集 bootstrap 的运行态事件,因此 bootstrap 返回的那批事件**就是**该 subscriber 此刻应处理的事件:前端直接 reduce 它们即可,不存在"先补一次 `consume` 才能拿到已暂存事件"的步骤。第一个例外只有回执竞态:Rust 侧一注册完 subscriber 就开始 `notify`,而前端要等回执到达才知道自己的 `subscriptionId`,这段窗口内的通知拿不到订阅身份。前端因此必须记一笔欠账,回执到达后立刻补一次 `consume` 取回那批事件;否则事件会卡在队列里等下一次通知,而一次回合的最后一个事件之后可能再也没有通知。除此之外不轮询,也不设任何定时 `consume`——唤醒只由 `notify` 负责。用定时器兜底既自举不了(判断"有活动回合"本身依赖事件),也把唤醒机制变成两套。
|
||||
|
||||
首屏历史不通过"读取整份对话"的命令获取:`subscribe` 返回的 `lastCompletedItemId` 就是首屏锚点,前端据此调用 `readHistory` 取最近的切片,再按滚动或按钮继续向前分页。系统不提供返回整份对话历史的命令。
|
||||
|
||||
### 事件和顺序
|
||||
|
||||
Thread 内所有公开事件共用一个单调递增 seq;seq 允许跳号,前端不要求连续。事件 envelope 至少包含:
|
||||
Thread 内所有公开事件共用一个单调递增 seq,但 **seq 只是 Thread Manager 的内部游标事实,不下发**:同一个 subscriber 的 `consume` 按队列顺序返回事件数组,数组顺序就是前端要处理的顺序,前端因此不需要 item 级 cursor 或第二套 reducer。
|
||||
|
||||
线上模型是 ts-rs 导出的 tagged enum(`agent/direct_thread_wire.rs`),前端消费 `src/features/project-workspace/generated/` 里的生成绑定,改 Rust 模型后跑 `cargo test export_bindings` 重新生成;毫秒时间戳标 `#[ts(as = "f64")]`,因为 ts-rs 默认把 `u64` 映射成 `bigint`,而 Tauri 的 JSON 通道传的是 `number`。
|
||||
|
||||
事件按 `type` 区分,条目按 `itemType` 区分:
|
||||
|
||||
```ts
|
||||
{
|
||||
seq: number,
|
||||
type: string,
|
||||
turnId: string,
|
||||
itemId?: string,
|
||||
payload: unknown,
|
||||
}
|
||||
type DirectThreadEvent =
|
||||
| { type: 'turn.started' }
|
||||
| { type: 'turn.completed'; status: string }
|
||||
| { type: 'item.started'; item: DirectThreadItem }
|
||||
| { type: 'item.completed'; item: DirectThreadItem }
|
||||
| { type: 'item.delta'; itemId: string; kind: 'message' | 'reasoning'; delta: string }
|
||||
| { type: 'request'; kind: 'approval.requested' | 'ask.requested' | 'request.resolved'; requestId: string | null };
|
||||
```
|
||||
|
||||
进入 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 Manager 的是已经完成安全过滤和协议标准化的公开 raw event,不是未经审查的 app-server JSON。事件可交错包含多个并发 item:`item.started`、`item.delta`、`item.completed`、approval/request/resolved 事件,以及 `turn.started`、`turn.completed` 生命周期事件。前端按事件顺序 reduce,只用一个 reducer。
|
||||
|
||||
**事件不带回合身份。** DirectProject 同一时刻只有一个回合在跑,`turn.started` 无载荷、`turn.completed` 只带 `status`;条目、增量、请求与生命周期锚点都不带 turn id。前端 state 里只有一个 `turnRunning` 布尔,历史条目也不记录回合身份。
|
||||
|
||||
一个 thread 同时最多有一个 active turn;一个 turn 内允许多个并发 item。`turn.completed` 必须在该 turn 的完成 item 均成功持久化后进入队列,前端据此结束运行态;不能用“不存在 unfinished item”猜测 turn 是否完成。
|
||||
|
||||
前端 reducer 的活动回合判定只有一条:事件序列中出现 `turn.started` 且其后没有 `turn.completed` 时才是活动回合,界面才允许显示忙碌态。`subscribe` bootstrap 里没有这样的序列,就表示当前没有活动回合;Thread Manager 队列随进程消失,因此进程重启后历史里留下的半截回合一律按已结束渲染,前端不发明中断态,也不从历史条目反推忙碌态。
|
||||
|
||||
生命周期锚点独立于 replay 队列保存:`turn.started` / `turn.completed` 事件即使已被队列前缀回收,`subscribe` 仍必须把最新的一条作为 bootstrap 事件返回。因此进程内任意时刻新建订阅,都能判定最新回合是运行中还是已结束,不依赖"未完成 item 恰好还在队列里"。
|
||||
|
||||
`item.started` 与 `item.completed` 必须携带与历史切片同形的**脱敏原始条目**(经同一套挑字段、脱敏、截断、路径归一),不得只给 item 类型或空 payload。前端不得依赖"按 `itemId` 单点取快照"补齐正文:Rust 不提供 `getItemSnapshot(itemId)`,未完成条目的正文随事件下发,已完成条目一律通过历史读取。
|
||||
|
||||
条目形状的职责边界固定为三条:
|
||||
|
||||
1. **搬运层不生成展示形状**。Thread Manager 只下发 Codex 原始条目(`itemType` 原样透传,正文与工具明细脱敏后带上限截断),不生成工具卡片的 `kind`、标题、折叠摘要,也不判断哪些条目要显示。
|
||||
2. **只有一个条目身份**。工具条目在 `project.jsonl` 里带两个 id(调用 id 与 response item id,同一调用的调用与输出共用前者;codex-rs `thread_history.rs` 中所有工具 item 都是 `id: payload.call_id.clone()`),所以在进队列前归一成一个 `itemId`。Thread Manager 与前端都不得再出现第二个 id 概念。
|
||||
3. **合并只在前端,且只保留"先到定形、后到补空白"**。第一次见到的快照决定卡片形状,后续快照只补输出与状态;同一调用只出现一张卡片。只有"后到信息一定更全"时才例外:正文取更长的一份、工具状态允许从 `running` 升级到终态、`updatedAt` 取较新的时间。
|
||||
|
||||
前端不保留增量缓冲:`item.delta` 直接追加到运行态条目的正文(正文只增不减)。`turn.completed` 把当前回合的运行态条目并入历史再清空,条目既不消失也不重复;失败与中止说明只在运行期显示,不写进 `project.jsonl`。
|
||||
|
||||
历史切片的 `firstItemId` 不是上述归一身份:分页锚点必须是 `project.jsonl` 里的原始 item id,由 Rust 从文件扫描单独算出。
|
||||
|
||||
思考正文以 `item.delta{kind:"reasoning"}` 流式下发(来源是 app-server 的 `item/reasoning/summaryTextDelta` 与 `item/reasoning/textDelta`)。这不放宽可见文本范围:被下发的就是此前已在 `item.completed` 展示、并已落进 `project.jsonl` 的同一段文本;plan 文本与命令输出仍然只降级为活动状态,不下发正文。
|
||||
|
||||
### 队列、subscriber 和回收
|
||||
|
||||
每个 thread 一个 Vec-based append-only replay queue,使用逻辑 head 偏移清理前缀,不做中间删除。完成 item 的事件在持久化成功后才可进入普通 replay 回收流程;unfinished item 的事件必须保留到 item 完成,不能被普通上限截断。
|
||||
|
||||
@@ -185,7 +185,7 @@ camelCase JSON。禁止出现附件正文、命令 stdout、patch diff、宿主
|
||||
| `agc_list_project_files` | `path`、`query`(120)、`kind`、`offset`、`limit` | 无 |
|
||||
| `agc_write_file` | `path`、`contentChars`(`content` 的字符数,不是正文) | 不落 `content` |
|
||||
| `taonier_prepare_game_art` | `mode`、`brief`(截断 4000)、`briefChars`、`briefSha256` | **要 brief 原文**(分析定玩法的吸烟枪;上限已是 MCP 合同) |
|
||||
| `agc_generate_image` | `kind`、`aspectRatio`、`imageSize`、`assetName`、`outputPath`、`prompt` 截断 4000、`promptChars`、`promptSha256` | 不落 32k 全文 |
|
||||
| `agc_generate_image` | `kind`、`sliceMode`、`sliceCount`、`screenColor`、`aspectRatio`、`imageSize`、`assetName`、`outputPath`、`prompt` 截断 4000、`promptChars`、`promptSha256` | 不落 32k 全文 |
|
||||
| `agc_edit_image` | `sourceLocalAssetId`、`assetName`、`prompt` 截断 4000、`promptChars`、`promptSha256` | 同上 |
|
||||
| `agc_create_or_derive_resource` | `kind`、`mode`、`sourceLocalAssetId`、`assetName`、`prompt` 截断 4000、`promptChars`、`promptSha256` | MCP 上限已是 4000 |
|
||||
| `agc_list_registered_assets` | `kind`、`assetId`、`includeSequenceFrames`、`offset`、`limit` | 无 |
|
||||
|
||||
@@ -1,8 +1,48 @@
|
||||
# 【技术方案】GameAgent 对话工具调用卡片(Codex 风格)-2026-09-14
|
||||
|
||||
## 2026-09-16 修订(当前状态)
|
||||
|
||||
本方案的**卡片表现层**(折叠 / 展开、标题与摘要文案、耗时与时间显示、脱敏、无障碍、样式)仍然是有效契约;**数据来源层**已被 `docs/adr/【ADR】DirectProject对话历史单一事实源-2026-09-16.md` 取代,边界改为:
|
||||
|
||||
- DirectProject 聊天框的工具卡片由**运行态事件 + 项目对话历史**在前端投影生成(`features/project-workspace/directThreadItemProjection.ts`),不再读取 `tool-calls.jsonl`;`read_direct_tool_calls` 命令与 Rust 侧 `read_direct_tool_calls_at` 回读函数已删除,该文件现在只有 DirectRuntime 的写入。
|
||||
- 报文中不再有 `toolCalls` 增量字段与 `GameCreatorDirectTurnUpdateEvent` 这条实时链路:卡片形状由前端从脱敏原始条目生成,事件里只有 `item.started` / `item.completed` / `item.delta`(线上模型见 `agent/direct_thread_wire.rs`,由 ts-rs 导出绑定)。
|
||||
- 卡片身份只有一个 `itemId`(工具条目在 `project.jsonl` 里带的两个 id 已在 Rust 边界归一),前端卡片形状是 `Omit<GameCreatorDirectToolCall, 'turnId'>`:聊天卡片不再有回合身份。
|
||||
- 下面「### 1. 工具调用条目」「### 2. 实时事件」「### 3. 回读命令」三节描述的是 DirectRuntime 自己的账本(`tool-calls.jsonl` 的写入形状与脱敏规则仍然有效,DirectRuntime 保留),**不再是 DirectProject 聊天框的读路径**;「### 4. 前端合并与渲染」中按 `turnId` 归并、按 `turn-stream.jsonl` 的 `seq` 交替的规则已作废,改为按事件顺序 + 历史文件顺序投影。
|
||||
|
||||
## 一句话交付
|
||||
|
||||
把 GameAgent 右侧对话面板里的「执行命令 / 写文件 / 调工具」从一行中文进度文本,改成 Codex 桌面客户端那样的**可折叠卡片**(折叠态一行摘要,展开态看命令与文件明细),并且在**刷新页面、重开项目后仍然存在**。
|
||||
把 GameAgent 右侧对话面板里的「执行命令 / 写文件 / 调工具」改成可折叠卡片,整轮总耗时只在回合状态/完成小结显示一处,工具组与内部工具分别显示各自耗时;运行中的计时动态增长,不足一分钟保留一位小数,达到分钟后显示整数秒。
|
||||
|
||||
## 总耗时与动态工具计时
|
||||
|
||||
### 紧凑过程入口与 Markdown
|
||||
|
||||
- 思考过程、执行过程和工具组统一为无大块底色的单行折叠入口:左侧小图标/简短预览,最右侧展开箭头;长文本省略,不把箭头挤出窄聊天列。沿用共享过程色和 12px 层级,键盘可展开、有可见焦点。
|
||||
- 思考折叠态直接显示浅色的内容预览,而不是只有“思考过程”标题;预览不显示 Markdown 控制符、原始 HTML 或链接地址。展开后复用现有安全 Markdown 渲染链路,支持段落、强调、列表、链接、代码块等,不开启原始 HTML 执行。实时与历史、当前 Agent 与策划 Agent 使用同一呈现。
|
||||
- 思考入口使用灯泡图标;展开后入口文字切换为“思考过程”,原内容仅在 Markdown 正文显示一份,不同时保留相同摘要。收起后恢复浅色内容预览;键盘开合与鼠标开合行为一致。
|
||||
- 回合结束后最外层“执行了 N 个操作,耗时 XXX”使用正文颜色;收进该折叠层的内部过程保持运行中原有颜色、行高和块间距,不能因额外 Grid gap 与子元素 margin 叠加而拉大间距。
|
||||
- 思考与工具组按紧凑连续列表呈现,相邻过程块间距 4px,内部工具行使用紧凑的 2px 上下内边距;运行与完成后的容器使用同一口径。失败工具的名称、图标、状态及耗时使用错误红色,所属组摘要有“有操作失败”提示且标红;同组其它工具仍运行时也不能吞掉失败标识,成功行不得被连带染红。
|
||||
- 工具组摘要只统计本组工具条目总数,显示“执行了 N 个操作,耗时 XXX”;运行中另有状态提示,不把操作数称作成功数,失败仍保留明确状态。回合外层执行过程统计所有工具块的操作数,不把思考段落或文本消息当工具操作;缺失耗时不伪造。
|
||||
- 所有 AGC 耗时统一用中文时分秒:不足一分钟显示 `5.2秒`,达到分钟后显示整数秒,如 `2分05秒`、`1时02分05秒`。省略前导零单位,带小时则保留两位分钟,带分钟则秒补齐两位整数;先整体舍入再拆分单位,避免出现 60 秒/60 分。工具行、组、整轮、生成任务及策划耗时共用一个纯格式化函数,不改变各自计时来源和动态刷新。
|
||||
- Direct 对话发送后、本轮首个可见思考/文字/工具响应到达前,在消息列表内、工具调用块外临时显示“思考中…”。收到首个响应后移除,失败、取消和空闲时同样不显示;直接从现有忙碌状态与本轮投影派生,不新增原生事件、定时器或持久化消息。工具间等待不在本次轻量实现范围内。
|
||||
- 独立计时保持不变,组状态/用时在单行中作为次要信息,空间不足可移至展开内容,不能占第二行破坏紧凑入口;整轮总耗时仍只显示一次。工具输入/输出、失败信息与操作明细不因样式变更丢失。
|
||||
- 只改展示与对应测试,不改变消息身份、分组顺序、生成或原生执行语义。验证覆盖 Markdown 安全与语义、预览省略、准确计数、展开/键盘操作、窄屏布局及原有计时冻结。
|
||||
|
||||
- “总耗时”表示同一轮用户请求从发送到回合终态的墙钟跨度,包含 LLM 推理、工具调用和等待;只在本轮运行状态或完成小结显示,不重复放进各工具组。
|
||||
- 工具组顶部“执行了 N 个操作,耗时 XXX”中的耗时范围是本组首个工具开始到最后一个工具完成,包含组内等待但不是工具耗时之和。不同工具组独立计时;本组全部终态后立即固定,即使本轮或后面的工具组仍运行,也不能显示“进行中”或继续增加。组头不展示发送/结束时刻,不沿用用户发送时间计算本组。
|
||||
- 回合仍运行但全部工具暂时结束时,只增长本轮总耗时。成功、失败或终止到达后整轮按实际终态时间固定;随后打开折叠块、翻页或其它回合的新事件不得改变已完成的组或回合用时。
|
||||
- 单条工具从其实际开始到完成计时。运行中的工具按当前时间持续增长,不能把最近一次快照更新时间当作当前时间;已经结束的工具必须立即固定,即使同组其它工具或 LLM 仍在运行。
|
||||
- 对话的组、工具及整轮耗时运行中均每 100 毫秒刷新,统一使用上述中文时分秒格式。以时间戳计算而不是按 tick 累加,避免后台节流后的累计漂移;缺失或倒序边界不伪造 `0.0`。非对话场景只统一文本格式,不改变原有刷新或后端累计时间语义。
|
||||
- 各层时间范围与对应耗时使用相同的起止边界。运行标记也按本层状态判定;完成后如果展示起止时间,其精度不得造成范围差与用时矛盾。组内存在缺失或倒序的工具边界时,不能用其他组或整轮的时间补造本组耗时。
|
||||
- 原生事件保留各阶段的时间语义:开始与完成不能都优先折叠成开始时间。优先采用上游明确提供的阶段时间或实际调用时长,缺失时使用宿主观察该阶段的时间;重放沿用原事件时间,不能在前端收到或重放时重新取当前时间。
|
||||
- 工具开始/完成的权威边界是事件级 `at`,不是条目展示字段 `item.at`。整轮起点优先采用该轮实际用户消息的发送时间(与气泡一致,不取所有条目的最小时间),缺失时采用原生 `turn.started.at`;终点只采用 `turn.completed.at` 或明确的终止/失败收口事件。首次补到更早的真实发送时间可以校正起点,但旧历史或重复事件不能覆盖已经固定的终点。
|
||||
- 回合阶段使用宿主观测阶段的毫秒时间,或上游明确的毫秒边界/可靠时长;不把上游已截断的整秒时间冒充十分之一秒精度。回合是否开始以 Thread Manager 的事件顺序为准,不能用时间戳大小拒绝取消后同一秒内的新请求。回合完成的展示时间在当前会话中冻结;重新打开后若历史未保存完整生命周期边界,则隐藏未知用时而非补造。
|
||||
- 无效边界包括缺字段、非有限值、时间戳为 0、结束早于开始;这些情况隐藏不能证明的耗时。合法开始时刻的运行中 `0.0秒` / `0.0s` 则正常显示。只有完成快照而没有开始事件的工具不猜测开始时间。
|
||||
- 沿用 Thread Manager 的生命周期与条目身份,不引入第二套回合状态源、计时账本、远程 API 或数据库字段。前端仅保存原事件的展示时间边界;旧历史缺少完整边界时不声称知道精确总耗时或工具耗时,不为补齐计时启动模型请求。
|
||||
- 必须覆盖:前组已完成而后组运行时各自时间独立、整轮总耗时仅出现一次、无新事件仍增长、工具完成后 LLM 继续、并行工具分别计时、完成/失败/终止冻结、同身份重放不改终态时间、切项目与卸载停止计时、缺失与倒序时间、0.0 / 59.9 / 60.0 秒边界。真实 Provider 验证与模拟事件的客户端验证分开报告。
|
||||
- Direct 对话的初始消息占位仅在正式用户消息尚未进入当前显示链路时显示;正式条目或乐观用户条目出现后撤下占位,不在消息列表外额外保留一份。只控制占位是否显示,不按文本合并或删除用户真实重复发送的消息;分页已有更早历史时不把初始占位补到当前页。
|
||||
- 同一用户消息从本地发送转为正式条目时必须保留原发送时间,不能因去重丢掉该时间而改用启动应答后的观测时间。回合明确结束时,即使当前 live 集合为空,也应将终态边界关联到已知的本轮用户条目;不得仅按“历史最后一项”猜测或向无关旧回合补时间。
|
||||
- 生命周期事件可携带已有用户条目的 `userItemId`,用于把时间边界精确关联到同一条历史消息(不产生新的回合 ID 或持久化字段)。恢复时该关联随 Thread Manager 原事件重放;带旧用户身份的终态不能收口另一条新请求。缺少身份的旧事件不据时间猜测归属。
|
||||
|
||||
## 背景与现状(已核实)
|
||||
|
||||
@@ -47,15 +87,15 @@ toolCalls?: DirectTurnToolCall[] | null;
|
||||
- 字段**可选**:老版本事件解析路径必须保持兼容(前端拿到 `undefined` 时行为与现在一致)。
|
||||
- `DirectTurnToolCall` 与上面 payload 同形(去掉 `turnId`)。
|
||||
|
||||
### 3. 回读命令
|
||||
### 3. 落盘契约(写侧)
|
||||
|
||||
新增 Tauri 命令 `read_direct_tool_calls(projectPath)`,返回按时间正序的 `DirectTurnToolCall[]`。
|
||||
DirectRuntime 写 `<projectRoot>/.agent/conversations/tool-calls.jsonl`;回读命令与 Rust 侧回读函数已随聊天读路径退役删除,下面的语义约束的是**写进文件的行**。
|
||||
|
||||
- **上限语义**:200 条是「按时间保留最新 200 条」。超出时更早回合的卡片会被**静默丢弃**(老回合卡片会消失),不做分页、不做历史回填;同一 `id` 的多条记录先按 `updatedAt` 合并,再按时间正序裁剪。
|
||||
- 历史文件缺失 → 返回空数组,不报错。
|
||||
- 单行损坏 → 逐行读字节并逐行解码,跳过该行继续,不整体失败;只有损坏字节与下一行黏成一行(例如写入被截断、缺失换行)时,被丢掉的也只是那**一行**,其后的合法记录必须继续读回(与 Codex item 流一样是"尽力而为"的展示数据,不是业务真相)。
|
||||
|
||||
### 4. 前端合并与渲染(回合唯一归属,连续工具成块)
|
||||
### 4. 前端合并与渲染(回合唯一归属,连续工具成块)——已作废,见文首修订
|
||||
|
||||
- 加载对话时按 `turnId` 归并为唯一回合容器,用户消息保留在该回合前部。有 `turn-stream.jsonl` 时,文本与工具按 item `seq` 交替,连续工具合为一块,遇到文本另起一块;没有流的历史回合才采用“工具块 + 历史正文”。
|
||||
- 回合完成后,中间文本及所有工具块统一收进默认关闭的“执行过程”;最终回复及失败提示留在外面。展开后仍按原顺序查看中间输出和工具详情;运行中不使用外层折叠区。用户消息的发送时间从消息自身的历史时间读取,不能拿工具起点补造。
|
||||
@@ -65,21 +105,20 @@ toolCalls?: DirectTurnToolCall[] | null;
|
||||
```html
|
||||
<section class="agent-tool-call-group" data-testid="agent-tool-call-group" data-status="completed">
|
||||
<button type="button" class="agent-tool-call-group-head" aria-expanded="false" aria-controls="…"
|
||||
aria-label="已执行 2 个命令、1 个文件变更,用时 42秒">
|
||||
aria-label="执行了 3 个操作,耗时 42.0秒">
|
||||
<span class="agent-tool-call-group-icon" aria-hidden="true"></span>
|
||||
<span class="agent-tool-call-group-summary">已执行 2 个命令、1 个文件变更</span>
|
||||
<span class="agent-tool-call-group-time">14:20:05 → 14:21:02</span>
|
||||
<span class="agent-tool-call-group-duration">用时 42秒</span>
|
||||
<span class="agent-process-summary-preview">执行了 3 个操作</span>
|
||||
<span class="agent-process-summary-meta">,耗时 42.0秒</span>
|
||||
<svg class="agent-tool-call-group-chevron" aria-hidden="true"></svg>
|
||||
</button>
|
||||
<div class="agent-tool-call-group-body" hidden>
|
||||
<ul class="agent-tool-call-group-rows">
|
||||
<li class="agent-tool-call-group-row" data-testid="agent-tool-call-row" data-kind="command" data-duration-ms="12300">
|
||||
<button type="button" class="agent-tool-call-row-head" aria-expanded="false" aria-controls="…"
|
||||
aria-label="已运行 npm run build,耗时 12.3s">
|
||||
aria-label="已运行 npm run build,耗时 12.3秒">
|
||||
<span class="agent-tool-call-row-icon" aria-hidden="true"></span>
|
||||
<span class="agent-tool-call-row-text">已运行 npm run build</span>
|
||||
<span class="agent-tool-call-row-duration">12.3s</span>
|
||||
<span class="agent-tool-call-row-duration">12.3秒</span>
|
||||
<svg class="agent-tool-call-row-chevron" aria-hidden="true"></svg>
|
||||
</button>
|
||||
<div class="agent-tool-call-row-detail" hidden>
|
||||
@@ -94,10 +133,10 @@ toolCalls?: DirectTurnToolCall[] | null;
|
||||
```
|
||||
|
||||
- 文案规则(按 kind,不允许自由发挥):
|
||||
- 块头汇总按 kind 计数、顺序固定 `command → file_change → mcp_tool → web_search → context_compaction → other`,标签 `命令`/`文件变更`/`工具调用`/`联网搜索`/`上下文整理`/`其他操作`,形如 `已执行 5 个命令、2 个文件变更`;空集合不渲染块。
|
||||
- 块头按实际工具条目计总数,形如 `执行了 7 个操作,耗时 1分05秒`;空集合不渲染块。操作数不是成功数,失败与运行状态仍单独可见。
|
||||
- 行文案:展示工具摘要,不重复添加动词前缀;`context_compaction` 固定为“整理上下文”。状态单独放在行尾(执行中 / 已执行 / 失败),`failed` 使用现有 `--platform-*` 错误色;已结束回合不因残留 `running` 快照显示“执行中”。
|
||||
- 耗时:单条工具 = `startedAt` → `updatedAt`,块头总用时 = 该回合所有工具的 `min(startedAt)` → `max(updatedAt)`。单条格式:`<1s` → `0.4s`、`<60s` → `12.3s`(整秒省略小数)、`≥60s` → `2m 5s`;块头格式:`42秒` / `4分钟` / `5分钟 45秒`。`startedAt` 为 0 或 `updatedAt < startedAt` 时不显示耗时(不显示 `0s` / 负数),耗时为 0 时同样不显示 `0s`。
|
||||
- 时间:块头显示该回合结束时间(`max(updatedAt)` 的本地 `HH:mm:ss`);同一回合能拿到用户消息时间(`updatedAt > 0`)时显示 `HH:mm:ss → HH:mm:ss`(发送 → 结束),取不到就只显示结束时间,不编造。
|
||||
- 耗时:执行“总耗时与动态工具计时”合同。块头是本组用时,单条是该工具独立耗时,整轮总耗时只在本轮状态/小结显示;不足一分钟显示一位小数,达到分钟后显示整数秒,运行中每 100 毫秒刷新,各自终态固定。
|
||||
- 时间:范围与对应层级用时采用同一边界,不把用户发送起点与局部工具组终点混搭。缺失的历史时间不编造。
|
||||
- 回合结束时间与耗时在正文下方右对齐;Direct 对话输入框提示统一为“描述你的想法,或 @ 引用素材”,引用按钮保留输入盒的 12px 内边距,不使用负边距贴边。
|
||||
- 当前 Agent 与策划 Agent 共用 `packages/shared` 的 `AgentMessageContent` 表现组件:正文为 14px / `--platform-text-strong`,思考、中间输出和工具调用为 12px / `--platform-text-soft`。实时与历史思考共用同一个折叠入口;工具输入输出继承过程色,失败状态保留错误色。Markdown 标题、表格及代码高亮在过程区同步弱化,最终回复和文档预览仍保留正常排版,不按 Agent 类型复制样式。输入提示与禁用状态保持原有反馈。
|
||||
- Windows 命令展示:仅 `command` 卡片识别 `pwsh` / `powershell`(含完整路径、`.exe`、常见启动选项)的 `-Command` / `-c` 外层包装,摘要和展开输入只展示脚本正文,并解开单个 shell 参数的引用拼接。摘要优先读取已脱敏的 `detail.command`,再按首行 120 字符截断,避免历史摘要被可执行文件路径占满。无法识别的启动方式、`-File`、`-EncodedCommand`、普通命令和 MCP 输入原样展示;执行参数、持久化原文、脱敏和输出均不改变。
|
||||
|
||||
@@ -132,6 +132,8 @@ Runtime 不维护文档版本号,不解析文档版本,不提供版本回退
|
||||
|
||||
一轮有多个工具调用时沿用正常工具执行循环。澄清或审批进入等待后,不继续请求 Provider,也不执行同批剩余文件操作;未执行调用明确记录为因等待用户而未执行,不伪造成功结果。恢复历史必须保持工具调用与结果配对,避免出现缺少 tool output 的协议错误。这属于协议与暂停处理,不引入同轮调用次数门禁。
|
||||
|
||||
单次策划工具不再竞争项目级写锁;策划命令锁与会话原子写入已保证同一会话内工具按批次顺序执行。若进程在工具执行标记与结果落盘之间中断,重开项目时的只读 hydrate 必须在拿到策划命令锁后自动续跑原回合,为不确定调用补写“执行结果未保存”的工具错误、跳过剩余调用,并把错误交回 Provider 自愈;不得重放文件副作用,也不要求用户手动恢复。
|
||||
|
||||
迁移工具集合:
|
||||
|
||||
```text
|
||||
@@ -150,7 +152,7 @@ get_workflow_status
|
||||
|
||||
工具使用相对工作区路径。工具执行结果继续通过 Runtime 统一记录和展示,但不向 Agent 暴露宿主绝对路径。
|
||||
|
||||
`patch_file` 保留原型按唯一原文匹配修改的语义、换行归一化和缺文件错误。正常工作区写入与删除不逐次请求用户审批;阶段审批不能被复用为文件操作许可。
|
||||
`patch_file` 保留原型按唯一原文匹配、范围不重叠、全部通过才原子写入的语义、换行归一化和缺文件错误。批量 edits 会一次性完成全部校验,并把未找到、多处匹配、重叠等失败项汇总返回;未找到时同时给出候选行号和可见化缩进提示,帮助 Provider 基于当前文件修正锚点。正常工作区写入与删除不逐次请求用户审批;阶段审批不能被复用为文件操作许可。
|
||||
|
||||
`list_resources` 一次返回完整逻辑分类、资源 ID、标题和简介;`read_resource` 按一个资源 ID 读取一个文件。资源描述不增加 `required=true/false` 分类,也不增加引导同轮多次调用的说明。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user