Merge remote-tracking branch 'origin/master' into feat/game-works-management
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m52s
Project CI / Backend tests (pull_request) Failing after 14s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m19s
Project CI / Frontend tests (pull_request) Successful in 2m10s
Project CI / Repository checks (pull_request) Failing after 13s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m40s
Project CI / Native shell tests (pull_request) Successful in 5m16s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m42s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 9m16s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m52s
Project CI / Backend tests (pull_request) Failing after 14s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m19s
Project CI / Frontend tests (pull_request) Successful in 2m10s
Project CI / Repository checks (pull_request) Failing after 13s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m40s
Project CI / Native shell tests (pull_request) Successful in 5m16s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m42s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 9m16s
This commit is contained in:
@@ -38,6 +38,7 @@
|
||||
|
||||
- [AI 游戏创作智能体 App 实施计划](./technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md):当前 DirectProject、受控语义工具、UI workflow、资源和运行时合同。
|
||||
- [AGC 后端框架整理与演进路线](./technical/【技术方案】AGC后端框架整理与演进路线-2026-09-18.md):共享 Runtime、本地执行宿主、云端控制面、领域/平台适配器及分阶段收口边界。
|
||||
- [AGC 随包资源 staging 归位](./technical/【技术方案】AGC随包资源staging归位-2026-09-26.md):随包资源改由准备步骤在 `tauri dev|build` 之前一次性生成、`build.rs` 退化为校验者;含缓存与原子性合同、入口接线、验收判据与里程碑拆分。
|
||||
- [AGC 异步操作可恢复闭环](./【技术方案】AGC异步操作可恢复闭环-2026-09-14.md):认证响应体、最近项目检查和首页自动创建的超时、逐项恢复与跨页防重合同。
|
||||
- [AGC 客户端稳定版生命周期大切换](./【技术方案】AGC客户端稳定版生命周期大切换-2026-09-14.md):统一 operation、认证/Runner、项目入口、本地恢复和 dev-stack 身份边界。
|
||||
- [策划会话 Runtime V2 接入与旧链路退役方案](./technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md):历史方案,仅用于追溯 V2 的实现与退役过程,不作为当前实现依据。
|
||||
@@ -49,6 +50,8 @@
|
||||
- [引用候选由宿主注入](./adr/【ADR】引用候选由宿主注入-2026-09-22.md):引用输入区只接受宿主注入的引用 provider,素材选择面板独立成组件,附件芯片成为本轮附件唯一事实源。
|
||||
- [DirectProject 命令接单化](./adr/【ADR】DirectProject命令接单化-2026-09-23.md):命令只负责接单、事件流回答整轮结果;拒单前置、失败后置。
|
||||
- [DirectProject 命令接单化实施计划](./technical/【实施计划】DirectProject命令接单化-2026-09-23.md):四步落地顺序、每步不变式与验收;四步均已落地。
|
||||
- [DirectProject 命令入队化与待发消息队列归宿主](./adr/【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md):命令只负责入队,放行归 Thread Manager;待发消息队列作为运行态事件归宿主、前端只投影;CLI 直连入口与调用身份守卫一并退役。
|
||||
- [DirectProject 命令入队化与待发消息队列归宿主实施计划](./technical/【实施计划】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md):五步落地顺序、每步不变式与验收;待实施。
|
||||
- [GameAgent 对话工具调用卡片](./technical/【技术方案】GameAgent对话工具调用卡片-2026-09-14.md):把右侧对话里的执行命令 / 写文件投影成 Codex 风格可折叠卡片,含采集、独立历史文件、事件字段与回读契约。
|
||||
- [DirectProject 客户端 Skill 与 MCP 扩展导入方案](./technical/【技术方案】DirectProject客户端Skill与MCP扩展导入方案-2026-08-31.md):客户端扩展导入、按独立 Skill/MCP 拆分、命名、启用和启动时注入边界。
|
||||
- [AGC 通用插件宿主与编辑器适配](./technical/【技术方案】AGC通用插件宿主与编辑器适配-2026-09-09.md):通用插件宿主、SDK、权限审计、UI 挂载和 Cocos 编辑器适配边界。
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
# 【ADR】DirectProject命令入队化与待发消息队列归宿主
|
||||
|
||||
状态:已接受(2026-09-24 设计定稿;**2026-09-30 落地完成**,含当日修订条目:队列条目不再另存产物,
|
||||
引用的解析文本持久化进条目、`prompt` 与 canonical 形状放行时重投影,埋点候选 / settle 两阶段链退役
|
||||
(见 §3 / §4 / §5 与「备选方案与取舍」6–9 条)。实施顺序、落地进度与验证证据见
|
||||
[`【实施计划】DirectProject命令入队化与待发消息队列归宿主-2026-09-24`](../technical/【实施计划】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md))
|
||||
|
||||
## 背景
|
||||
|
||||
待发消息队列今天是纯前端状态:回合运行中用户再发送就进本地 FIFO
|
||||
(`chat/components/DirectProjectComposer/chatComposerQueue.ts`),回合终态事件到达后由**恰好开着的那个窗口**放行队首
|
||||
(`useDirectProjectChatController.ts` 的完成计数 effect 与 `startTurn` 的 `finally`)。三个问题:
|
||||
|
||||
1. 队列是这条对话里唯一没有宿主持有者的事实:另一个窗口、另一个订阅者,或只是离开工作台再回来,都看不到已经排了什么队。
|
||||
2. 放行落在"哪个窗口恰好开着"上。队列一旦共享(本 ADR 要做的),两个窗口都会去放行队首,必然双发。
|
||||
3. 命令边界今天写的是"接单":校验通过就登记占用、落盘用户条目、起整轮。但用户按下发送时想要的是"这条消息会被依次处理"——
|
||||
命令的成功含义与用户意图之间隔着一次长度未定义的等待。
|
||||
|
||||
前置口径:`【ADR】DirectProject命令接单化-2026-09-23` 已把逻辑回合收归 Thread Manager,并在 §8 留了 TODO
|
||||
「以后这条队列挪到 Rust 端,落点就是 Thread Manager 的接单动作」,同时把 Rust 端发送队列列进"明确不做"。
|
||||
本 ADR 就是那条 TODO 的收口,并顺带收掉两处已经没有现役价值的实现。
|
||||
|
||||
## 决策
|
||||
|
||||
### 1. 词表:入队 / 入队失败 / 放行
|
||||
|
||||
- 命令边界的成功与失败改叫 **入队 / 入队失败**;「接单」「拒单」两个词退役,不再出现在文档、注释、标识符与测试名里。
|
||||
- 旧「接单」在语义上的角色(这一轮真正成立的那一刻)改叫 **放行**。
|
||||
- 因此旧句「接单成立 ⇔ 事件流里有开始有结束」要改写成「**放行成立** ⇔ 事件流里有开始有结束」。这不是换词而是角色搬家:
|
||||
逐处改写时按角色判,不做字面替换(命令边界→入队/入队失败;回合成立→放行;检查归属→入队时的检查)。
|
||||
|
||||
### 2. 命令 = 入队
|
||||
|
||||
`chat_with_game_creator_direct_codex` 改义改名(建议 `enqueue_direct_codex_turn`),主体是今天"接单前"那条链**原样**跑完:
|
||||
`clientTurnId` 校验 → 工作流恢复 → 用户条目校验与冻结(引用解析文本写进条目,§5)→ 前置条件 → 工程准备;通过后**只入队**——
|
||||
追加 `queue.enqueued`,不登记占用、不落盘用户条目、不发回合事件、不起 codex。
|
||||
任何一步失败就是**入队失败**,走命令返回的 typed 载荷(`DirectTurnRejection` → `DirectTurnEnqueueFailure`),用户就在现场。
|
||||
|
||||
入队按 `clientTurnId` 幂等,判重范围是"在队 ∪ 正在跑的那一轮";重复入队返回同一次成功,不再是并发入队失败。
|
||||
|
||||
### 3. 队列归 Thread Manager
|
||||
|
||||
- 每个 thread(线程身份就是项目规范路径)一条 FIFO;本期只支持**按顺序追加**与**按身份移除**,不做重排、优先级、编辑。
|
||||
- 队列**就是事件列表本身**:在队条目 = 事件窗口里"有 `queue.enqueued`、且还没有配对 `queue.removed`"的那些事件,事件顺序即队首到队尾。
|
||||
宿主不为条目另存第二份产物(`StoredEvent` 上没有"挂着的队列条目"这种字段),成员与顺序都从事件折出来。
|
||||
`queue.enqueued` 在队期间不可回收,离开队列(取消或放行)时与配对的 `queue.removed` 一起转可回收并被既有规则回收。
|
||||
`is_bootstrap_event` 不改——`subscribe` 的 live-set bootstrap 因此天然把当前队列交给新订阅者,这就是"加入者看到的那几条"。
|
||||
- 上限 5 只数**在队条目**(不数正在跑的那一轮),由 Rust 持有;满队时入队失败并给出既有提示文案。
|
||||
|
||||
### 4. 线上形状
|
||||
|
||||
- `queue.enqueued { clientTurnId, userItem, creationType?, at }`:`userItem` 是 canonical 用户条目,前端据此派生 chip 文案,
|
||||
Rust 不渲染、不裁成展示形状。事件**不带** prompt(prompt 不是下发形状,放行时由条目重投影,§5),
|
||||
也不带埋点身份(成绩由宿主自己结算,§5)。队列事件**没有**宿主私有的第二类字段:这一条消息的全部事实就是它自己。
|
||||
- `queue.removed { clientTurnId, reason }`:`reason` 是 ts-rs 导出的 **typed 枚举**(`cancelled | dispatched`),永不用字符串,
|
||||
形状与 `turn.completed{status, failure?}` 同构。
|
||||
- 两条事件与其它运行态事件同一条流、同一个 reducer。
|
||||
|
||||
### 5. 放行 = 旧「接单」的语义角色
|
||||
|
||||
Thread Manager 在一个回合收口**之后**原子地做:取队首 → 登记占用 → 发 `turn.started` → 落盘用户条目 → 下发用户条目 → 起整轮,
|
||||
并在同一临界区写 `queue.removed{ dispatched }`(chip 消失与气泡出现在同一批 consume 里,中间没有空窗)。
|
||||
|
||||
**放行不重跑任何检查,也不存在"放行失败"这种状态**:放行之后的一切失败都是**回合失败**,走既有 `turn.completed.failure` 通道;
|
||||
不新增任何失败通道,也不为放行补失败出口。
|
||||
|
||||
prompt 同样靠**重投影**,不靠另存:`userItem` 是唯一输入。引用 part 的解析文本(素材摘要 + 引用 UI 设计文档时要展开的
|
||||
代码上下文,渲染它要往项目里写 `ui/generated-*.js`)由入队检查写进该 part 自己的 `resolved_text`,
|
||||
放行、历史回读、turn input 三个读点共用这一份——**历史必须回放出当初那条消息**,所以它是条目事实的一部分、随条目持久化,
|
||||
不是内存备忘;字段缺省即合法(`serde(default)`),旧历史没有它照样解析、按当前 manifest 现算。于是放行侧的 prompt 是条目的纯投影:零 IO、零校验、
|
||||
不可失败,与"放行不重跑任何检查"同一个口径。
|
||||
|
||||
埋点成绩同样不靠渲染侧结算:`identity_generation`(`platform_session`:只在登录主体 / 服务 origin / 登出状态变化时推进)
|
||||
在**放行**那一刻被读一次,回合终态再读一次,变了就整条不记。所以队列事件上不需要任何埋点身份字段,
|
||||
也不需要"先写候选、等渲染侧确认"的两阶段。
|
||||
|
||||
### 6. 放行的触发与监护顺序
|
||||
|
||||
- 唯一放行点:回合任务收尾之后的 `kick`(正常 / 失败 / 中止三条路径共用,外加一个 drop 守卫盖 panic),幂等,并且在临界区里原子认领队首。
|
||||
- 入队时也踢一脚:「队列非空 + 线程空闲」是合法状态,对应今天的"直接发送"。
|
||||
- 入队**不取** `DirectTaonierActiveInvocationGuard`:它必须整轮持有(它是这一轮的调用身份,付费美术、执行会话、MCP、校验、
|
||||
上下文预取都靠它把工作归属到自己那一轮),入队若取它等于"回合运行中不能入队"。入队路径的并发由工程准备自身的项目写锁与队列兜。
|
||||
|
||||
### 7. 渲染侧
|
||||
|
||||
前端不再持有队列副本:chip 只由事件投影(入队被拒时只由命令返回值给反馈,保留既有提示文案与"不丢草稿"行为);
|
||||
忙态只由事件投影加「队列非空」指示。排队消息在放行前**不写** `project.jsonl`,用户气泡仍然只来自宿主条目("落盘即放行"不变)。
|
||||
|
||||
### 8. 作用域与寿命
|
||||
|
||||
每项目一条、全进程共享:A 窗口排队 B 窗口可见可取消;切项目、离开工作台、关窗口都不影响队列继续放行;进程结束队列消失。
|
||||
**不做跨进程持久化**。
|
||||
|
||||
### 9. 顺带退役
|
||||
|
||||
- `--direct-codex-chat` 整个退役(解析、派发,以及只服务它的 `run_direct_game_creator_turn_at` 一对函数)。
|
||||
它的历史用途只有一个:手工生产验证夹具 `scripts/direct-execution-production-fixture.mjs`(PR #439 引入,不在 CI、
|
||||
没有 npm 脚本或 harness 注册、没有任何测试钉它,唯一硬依赖是进程退出码)。没有产品入口价值,也不该在入队化之后
|
||||
成为第二条直接起回合的路径;夹具脚本一并退役。
|
||||
- 夹具的一键入口 `scripts/run-agc-direct-execution-fixture.mjs`(09-29 新增,只把 `--agc-exe` 默认成工作区
|
||||
debug 二进制)与 `package.json` 的 `check:agc-direct-execution-fixture` 随夹具一起删:夹具没了它没有第二个消费者。
|
||||
- CLI 一退,`DirectTurnError::TurnAlreadyRunning` 的两个生产点(调用身份守卫、占用登记)都没有调用方,
|
||||
它连同前端"同一轮消息仍在处理中"文案、专属分支与测试一起删。
|
||||
- `DirectTaonierActiveInvocationGuard` 的**身份**与 Thread Manager 的 `active_turn.turn_id` 是同一件事的两份记录
|
||||
(GUI 路径下同源字符串),而 09-23 ADR 立的是"同一件事只许有一处真相"。CLI 退役后它的硬阻塞消失:五个读者
|
||||
(`direct_execution` / `direct_tool_bridge` / `direct_validation` / `direct_project_context` / 付费美术重生成)
|
||||
已改读 Thread Manager 的活动回合身份(`active_turn_id_at`),守卫连同它的测试一起删掉,不再有第二份
|
||||
进程内记录。
|
||||
- 删守卫的前置判据**已核实(2026-09-30)**,不需要新增"取消路径补无条件终态":`d833ca9d3` 的兜底路径本来就
|
||||
同时做三件事——释放占用、往事件流补一条 `turn.completed{aborted}`(否则前端会永远停在运行中)、调
|
||||
`complete_turn` 解除占用。删掉的只有 `DirectTaonierActiveInvocationGuard` 那张进程内表;
|
||||
唯一独有的东西是 60 秒启动窗口闸门,改挂在 Thread Manager 的占用登记年龄上
|
||||
(`stale_turn_for_release` 的 `NeverReachedExecutor` 分支)。连接死亡那条失败事实由 app-server
|
||||
连接层自己落地(`ab970b9fd`),与守卫无关。
|
||||
|
||||
## 备选方案与取舍
|
||||
|
||||
1. **状态归宿主、放行留前端**(再用一条 `claim` 命令做 CAS 认领):前端机器原样保留,但队列的继续推进依赖至少一个窗口活着,
|
||||
且"谁去认领"要靠竞态解决——正是要消掉的东西。作废。
|
||||
2. **入队只做形状校验、把前置检查留到放行**:会造出"放行失败"这种状态——一个没有调用方在等的失败,得为它发明新通道;
|
||||
而且"首个回合还在建工程、第二条已经排队"这类合法流程会被入队误拒。作废(检查跟着入队走)。
|
||||
3. **一条 `queue.changed{items:[…]}` 快照事件**代替两条细粒度事件:reducer 更傻,但每次变更搬全量、与既有细粒度事件风格不一致。不选。
|
||||
4. **`reason` 用字符串**:前端只能猜、无法穷举、无法在类型层穷尽分支。不选,用 typed 枚举。
|
||||
5. **保留 CLI**:省掉夹具改写,但等于为手工验证工具长期保留第二条直接起回合的路径,与"命令只有入队一个入口"冲突。不选。
|
||||
6. **宿主侧另存条目产物**(`StoredEvent.pending` 挂 `clientTurnId` / `userItem` / `canonical_user_item` / `prompt` / `creation_type` / `at`):
|
||||
前四项与 `queue.enqueued` 的载荷是同一份事实的第二、第三份拷贝,取消与放行要同时改两处,回收规则里还得加"产物还在就不算可回收"
|
||||
的防御来兜住不一致。作废:条目就是事件载荷的投影,claim 时现推、不存。
|
||||
7. **把整条 prompt 存进条目**:prompt 是 `userItem` 的投影,存整条等于把 `userItem` 的 JSON 再存一遍;唯一的例外是引用 UI 设计文档时
|
||||
要写盘的那段代码上下文。改为只把算不出的片段冻结在引用 part 上,整条 prompt 从条目重投影。
|
||||
8. **用 `clientTurnId` 兼作埋点身份**:埋点候选的 `attempt_id` 必须是 UUID(`analytics/run.rs` 的 `validate`),
|
||||
而 `clientTurnId` 在 WebView 没有 `crypto.randomUUID` 时会退化成时间戳 + 序号的形状。不选,两个身份各留在自己那一层。
|
||||
9. **保留"宿主写候选 + 渲染侧 settle"两阶段埋点**:入队化之后一次发送只有一次尝试(命令只入队、放行由宿主自己做),
|
||||
"哪一次尝试算数"不再是渲染侧才知道的事;宿主自己就有 `identity_generation`,直接写终态即可。
|
||||
候选表、`settle` 命令与 attempt id 一起退役,成绩也不再依赖"恰好有一个窗口在消费终态事件"。
|
||||
|
||||
## 影响与代价
|
||||
|
||||
- **入队即写盘**:引用 UI 设计文档的条目在 prompt 投影时会生成 `ui/generated-<stem>.js`(`ui_editor/persistence.rs`);
|
||||
从队列里取消不撤该文件。
|
||||
- **检查是时间点事实**:放行不重跑,按入队那一刻的结论放行;manifest、权限、目录在入队之后变化也照旧放行,偏差落到回合失败。
|
||||
- **prompt 形状随冻结片段走**:引用的 UI 设计文档代码上下文不再统一追加在 prompt 末尾,而是跟在它所属的引用片段里。
|
||||
只引用一条时 prompt 与改前逐字节相同;多条引用时块的先后变、语义不变。
|
||||
- **队列随进程消失**:待发消息只在内存与事件流里,`kill -9` / 退出后重进看不到(与 09-16 ADR 的 `kill -9` 口径一致)。
|
||||
- **退役面**:前端 `completionPendingRef`、`handledCompletedTurnCountRef`、`busyBaselineTurnCountRef`、`dispatchNextQueuedTurn`、
|
||||
`queueSequenceRef`、`queuedTurns*` 与 `chatComposerQueue.ts` 整体退役(含它的上限常量与"队列已满"文案——
|
||||
上限只留宿主一处);`turnBusy`/`beginTurnBusy`/`endTurnBusy` 改名 `commandInFlight` 并收窄成 IPC 在飞;
|
||||
`directProjectTurnStatus` 的忙态改成「原生在跑 ∨ IPC 在飞 ∨ 待发消息非空」。
|
||||
队列投影落在 `chat/conversation/directPendingTurns.ts`,chip 文案落在
|
||||
`chat/components/DirectProjectComposer/pendingTurnChipLabel.ts`。
|
||||
- **词表切换是一次性跨文档动作**:仓库里「接单/拒单」共 384 处,并非全属同一个域
|
||||
(`features/agent-runtime` 的"拒单文案"属另一个域,改成"请求被拒";`单测` 这类是假阳性)。
|
||||
两份已接受的 ADR 保留正文与文件名,顶部加词表注记。
|
||||
- **失去手工验证手段**:CLI 退役同时带走"真实二进制驱动执行层生产验证"这条手工路径(夹具脚本一并退役)。
|
||||
它不在 CI,损失的是排障时的一次性手段,不是门禁。
|
||||
- **埋点**:成绩由宿主在回合终态直接写,中间不再有候选与 settle:放行那一刻读一次 `identity_generation`、终态再读一次,变了就整条不记。
|
||||
于是前端不再生成、持有或回传任何埋点身份(`analyticsAttemptId` 命令参数、`beginDirectRunAnalytics`、句柄表、
|
||||
settle 调用与"入队失败就删掉句柄"一并退役),队列事件也不需要埋点字段。判据口径随放行搬家:从"发送时与终态同代"
|
||||
改成"放行时与终态同代",入队后、放行前发生的账号切换因此不再丢弃这一轮的成绩——那一轮确实是在新身份下跑的。
|
||||
代际比对本身也不再是渲染侧职责:放行侧读一次代次、终态侧再读一次,都在宿主内完成。
|
||||
|
||||
## 明确不做
|
||||
|
||||
- 不做跨进程持久化(不在入队时写 `project.jsonl`:那会在历史里留下一条永远不会跑的假消息,破坏"用户气泡只来自宿主条目")。
|
||||
- 不做重排、优先级、编辑待发条目;也不做"队列挂起 / 继续"状态——入队化之后队列里不存在会被拒的条目。
|
||||
- 不给放行新增失败通道,不为"放行不重跑检查"补兜底。
|
||||
- 不保留 CLI,也不为夹具保留别名或兼容入口。
|
||||
|
||||
## 落地时要同步的文档与注释
|
||||
|
||||
- `CONTEXT.md`:词条(待发消息队列 / 待发消息 / 入队 / 入队失败 / 放行 / 逻辑回合 / 在途回合)。
|
||||
- `docs/adr/【ADR】DirectProject命令接单化-2026-09-23.md`:§8 的 TODO 与"明确不做:Rust 端发送队列"、§7 的"命令在飞"措辞、
|
||||
CLI 保持 await 的分工,全部改为入队化口径(正文保留,顶部加词表注记)。
|
||||
- `docs/technical/【技术方案】DirectProject Codex原始历史与异常恢复-2026-09-04.md`:事件协议一节补两个队列事件与放行时序。
|
||||
- `docs/technical/【实施计划】DirectProject命令接单化-2026-09-23.md`:第 4 步的队列 TODO 指向本 ADR。
|
||||
- `docs/technical/【技术方案】Direct回合行为审计账本-2026-08-31.md`、`【技术方案】DirectProject本轮附件路径映射-2026-08-31.md`、
|
||||
`docs/project-memory/plans/【里程碑】退役AGC项目对话斜杠命令与终端swarm chat入口-2026-09-22.md`:删掉对 CLI 入口的承诺
|
||||
(最后那处现在写在"范围外(保留)"里,口径反转)。
|
||||
- `docs/project-memory/shared-memory/decision-log.md`:追加本决定,并修正"CLI 保持 await"那条。
|
||||
- 代码注释:`agent/direct_runtime/user_input.rs` 模块注释里的 CLI 分工一段删掉;`agent/thread_manager/` 的
|
||||
`dispatch.rs` / `mod.rs` 与 `useDirectProjectChatController.ts` 的旧措辞与 TODO 一并改。
|
||||
@@ -110,7 +110,9 @@
|
||||
界面不会卡在忙碌态。
|
||||
- 必须同步的注释:`chat/controller/useDirectProjectChatController.ts`(catch 的职责)、
|
||||
`chat/conversation/directTurnPresentation.ts`("`invoke` 直到整轮结束才返回"这句会变成错的)。
|
||||
- CLI 保持 await(它要那段回复文本),两个入口的分工在命令模块里写清楚。
|
||||
- ~~CLI 保持 await(它要那段回复文本),两个入口的分工在命令模块里写清楚。~~ **口径反转**:`--direct-codex-chat`
|
||||
整个退役(见 [`【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24`](./【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md)),
|
||||
DirectProject 只剩「入队」一个命令入口。
|
||||
|
||||
## 备选方案与取舍
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# 【实施计划】AGC 随包资源改由校验器读入
|
||||
|
||||
| 字段 | 值 |
|
||||
| --------- | --------------------------------------------------------------- |
|
||||
| Milestone | `docs/project-memory/plans/【里程碑】AGC随包资源改由校验器读入-2026-09-26.md` |
|
||||
| Status | ready(待里程碑规范评审通过后开工) |
|
||||
| Owner | suzmii / Agent |
|
||||
|
||||
## 修改边界
|
||||
|
||||
允许修改:
|
||||
|
||||
- `apps/ai-game-creator-shell/src-tauri/build.rs`:新增只读校验调用点;本里程碑内保持现有写入分支不变(不改变既有构建行为)。
|
||||
- `apps/ai-game-creator-shell/src-tauri/build_support/**`:把平台布局、组件白名单、摘要校验整理为可被构建脚本之外的独立工具复用的一处声明。
|
||||
- 新增随包资源准备工具及其测试(位置见「待确认决策」)。
|
||||
- 需要时扩展 `apps/ai-game-creator-shell/scripts/check-config.mjs` 的断言。
|
||||
- 文档:主规范未决问题收口、开发运维文档对应段落。
|
||||
|
||||
明确不修改:
|
||||
|
||||
- 三份 tauri 配置的 `resources` 映射、包内路径与安装包形态。
|
||||
- 运行时资源解析与完整性校验(`codex_cli.rs`、`plugin_host.rs`、`editor_adapters.rs`、`environment_check.rs`)。
|
||||
- 发布脚本流程、版本号机制、签名与上传。
|
||||
- dev 启动器与发布入口的接线(下一里程碑)。
|
||||
- 编辑器分支产物(Unity/Godot/Cocos)的生成方式(最后一个里程碑)。
|
||||
|
||||
## 实现顺序
|
||||
|
||||
1. **共用能力可复用**:确认 `build_support` 内的平台布局与组件白名单能被独立工具引用(现状先例:`src/agent/codex_cli.rs` 与 `main.rs` 已通过 `#[path]` 复用同一模块),把「布局 + 白名单 + 摘要校验」收敛为单一入口,避免准备工具另写一份清单。
|
||||
2. **准备工具骨架**:目标目录与清单写出、缓存 key(上游 lockfile 的 `resolved` + `integrity` + 布局版本 + 目标三元)、临时目录 + 原子替换、所有权与符号链接校验、并发串行化、单行汇总日志。先实现纯复制两条路径(随包组件、插件工作区),编辑器分支产物本轮仍由构建脚本生成。
|
||||
3. **幂等与失败关闭**:重复执行不改变内容与时间戳;上游缺失、摘要不匹配、目录被非本工具占用、目标平台不支持四类场景各自失败并给出可定位原因。
|
||||
4. **校验路径上线**:构建脚本在既有产物上执行只读校验(默认不影响现有写入行为),校验失败以明确原因中止。
|
||||
5. **测试与证据**:按里程碑「证据要求」补齐用例与运行记录。
|
||||
|
||||
## 验证命令
|
||||
|
||||
1. 声明唯一性与门禁:`npm run agc:bundled-resources:check`(已进 `agc:typecheck` 链),不一致时用 `npm run agc:bundled-resources:sync` 重新生成。
|
||||
2. 准备工具用例(含幂等与失败关闭):`npm run agc:bundled-resources:test`。
|
||||
3. 校验路径独立运行(跳过写入分支):`AGC_SKIP_RESOURCE_STAGING=1 cargo check --no-default-features --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml`。
|
||||
4. Rust 用例:`cargo test --no-default-features --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml package_layout` 与 `cargo test --no-default-features --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml codex_bundle`。
|
||||
5. 幂等(真实工作区):连续两次 `node apps/ai-game-creator-shell/scripts/prepare-bundled-resources.mjs`,第二次必须全部「命中缓存」,且两次之后的目录快照(相对路径、大小、mtime、sha256)完全一致。
|
||||
6. 并存一致:准备步骤产物与构建脚本产物逐文件比对(相对路径、大小、sha256)一致。
|
||||
7. 行为不回归:`cargo build --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --no-default-features`(本里程碑不承诺构建变快,仅确认行为与改造前一致,并记录当前构建耗时作为后续里程碑基线)。
|
||||
8. 门禁:`node apps/ai-game-creator-shell/scripts/check-config.mjs`、`npm run check:encoding`、`npm run check:doc-index`、`git diff --check`,以及改动范围内相关 vitest/Rust 测试。
|
||||
|
||||
## 风险与回滚点
|
||||
|
||||
| 风险 | 影响 | 处理 |
|
||||
| --- | --- | --- |
|
||||
| 校验器误判把构建卡死 | 影响所有本机构建 | 只读校验先以「不影响写入行为」的方式接入;出现误判可先关闭校验调用点回滚 |
|
||||
| 准备工具与构建脚本并存产生双写 | 两处结果漂移、时间戳变化 | 并存期以「准备工具产物 == 构建脚本产物」逐文件比对作为过渡判据;不一致视为失败 |
|
||||
| 缓存 key 漏掉上游变化 | 静默用旧组件 | key 含 lockfile `resolved` + `integrity` + 布局版本 + 三元;清单校验作为第二道闸 |
|
||||
| 准备工具实现形态选错 | 返工 | 见「待确认决策」,评审时一次定清 |
|
||||
|
||||
回滚点:本里程碑不改变既有构建行为,回滚只需移除校验调用点与准备工具,不影响产物与发布流程。
|
||||
|
||||
## 已定决策
|
||||
|
||||
准备工具的实现形态(主规范未决问题 1)**已定为混合**(2026-09-27,机制见主规范 §4.8):
|
||||
|
||||
- 上游获取、`integrity` 校验、归档安全与原子替换复用 Node 侧既有范式(`scripts/stage-node-runtime.mjs`、`scripts/prepare-macos-codex.mjs`);
|
||||
- 平台布局、组件白名单与逐文件摘要校验复用 Rust 侧既有声明(`build_support/codex_bundle.rs`、`build_support/godot_bundle.rs`),由准备工具与校验路径共用同一份声明文件承载,不再各写一份清单。(上游原生包元数据的期望值后来并入同一份声明;`build_support/codex_package_metadata.rs` 已在 M2 因失去调用方删除。)
|
||||
|
||||
理由:避免出现第二份组件白名单,同时不必重写 registry 下载、`integrity` 与 tar 安全校验;缺点是声明需要经过一次生成步骤才能在 Rust 侧使用,由 `check-package-layout.mjs` 门禁保证两者一致。
|
||||
@@ -0,0 +1,115 @@
|
||||
# 【里程碑】AGC 编辑器分支产物归位与症状层补丁清理
|
||||
|
||||
| 字段 | 值 |
|
||||
| ----------- | --------------------------------------------------------------- |
|
||||
| Version | 1.0 |
|
||||
| Status | in-progress(2026-09-28 已过 Windows 真机核心评审;2026-09-29 完成打包一致性验收,客户端加载待有编辑器环境的机器;Cocos payload feature 集差异待裁决) |
|
||||
| Date | 2026-09-26 |
|
||||
| Parent Spec | `docs/technical/【技术方案】AGC随包资源staging归位-2026-09-26.md` |
|
||||
|
||||
## 已实现(2026-09-27)
|
||||
|
||||
- 声明新增三类「准备步骤」产物:`subdirectories[origin=prepared]`(Unity publish 目录)、`libraryStaging[].prepare + files`(Godot gdextension)、`nativePayloads[]`(Cocos bridge dll);`plugins.prepareSteps` 描述每个准备步骤的程序、工作目录、指纹与必需产物。
|
||||
- 准备步骤(`scripts/prepare-bundled-resources.mjs`)按声明执行 `powershell.exe -File build.ps1` 与 `cargo build -p … --target …`,用内容指纹跳过未变化的步骤,校验必需产物齐全后才复制;命中指纹且产物齐全时零写入。
|
||||
- 构建脚本删除了三处产物生成与整棵树复制(`prepare_unity_editor_helper`、`prepare_godot_editor_extension`、`stage_cocos_editor_payload`、`stage_build_generated_plugin_payloads` 及其辅助函数,共减少约 220 行),只保留只读校验:源码派生内容逐文件比对、已准备产物存在性、Godot 随包库沿用既有深度校验(`godot_bundle::validate`)。Godot 随包文件清单改由声明提供(单一来源)。
|
||||
- `.taurignore` 的两份 staging 条目已删除(构建期不再写 `resources/plugins`,无需忽略)。
|
||||
- 准备步骤的 Windows 侧命令路径已随系统性审计在真机复跑(powershell/cargo 两条路径、产物归位与幂等均已验证)。
|
||||
|
||||
## 目标
|
||||
|
||||
编辑器分支(Unity/Godot/Cocos)的随包产物也由准备步骤生成,构建脚本不再调用外部工具链产出随包资源;此前为绕开自触发问题而加入的症状层补丁与说明全部删除,实现形态与主规范一致。
|
||||
|
||||
## 范围
|
||||
|
||||
- 三个编辑器分支产物的生成职责迁出构建脚本,包括需要外部工具链的两条路径。
|
||||
- 构建脚本内与资源生成相关的规避手段删除:残留清理逻辑、为幂等而设的辅助常量与判断、开发监听忽略条目中与随包资源相关的部分。
|
||||
- 平台与特性开关(哪些平台、哪些特性才需要这些产物)在新形态下保持既有语义。
|
||||
- 与发布打包、包内资源门禁、运行时解析的一致性核对。
|
||||
|
||||
## 不在范围内
|
||||
|
||||
- 编辑器分支本身的接入协议、宿主能力与运行时行为。
|
||||
- 外部工具链版本管理与安装流程(沿用现状)。
|
||||
- 随包组件与插件工作区的生成形态(上一里程碑已完成)。
|
||||
|
||||
## 依赖与前置条件
|
||||
|
||||
- 前两个里程碑验收通过。
|
||||
- Windows 环境具备 Unity/Godot 分支所需的工具链(.NET 与 CMake 等),以便验证产物生成与打包。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [ ] 构建脚本中不再存在向随包资源目录写入的分支,也不再调用产出随包资源的外部工具链。
|
||||
- [ ] 三个编辑器分支的随包产物路径、内容摘要、可执行位与迁移前逐项一致,且由准备步骤稳定产出。(2026-09-29 验收:路径集合与源码派生内容、Unity/Godot 产物逐字节一致;Cocos payload 因声明只构建 `windows-injection` 而与迁移前不同,且 MSVC 链接产物本身不可字节复现,见下方验收记录,口径待裁决)
|
||||
- [ ] 此前为规避自触发而加入的补丁(残留清理、幂等辅助、监听忽略条目)在代码与文档中全部移除,不再有「为了绕开构建问题」的说明。
|
||||
- [ ] 平台与特性开关语义不变:不支持的平台不产出这些资源,且不因此失败。
|
||||
- [ ] 全量门禁通过,且客户端在具备条件与不具备条件两种环境下都能给出明确结论(可用 / 缺组件及原因)。
|
||||
|
||||
## 验收记录(2026-09-29,Windows 本机)
|
||||
|
||||
### 打包一致性(验收标准第 2 条)
|
||||
|
||||
准备步骤按发布入口同样的参数复跑(`prepareBundledResources({ target: 'x86_64-pc-windows-msvc', features: Set('unity-editor-execute','godot-editor-execute','cocos-editor-injection'), profile: 'release' })`):`cocos-bridge-build 已构建`、`unity-helper-publish` / `godot-extension-build 命中缓存`、`plugins 重新生成`;随后连续三次复跑,`resources/plugins` 的 32 个文件内容与 mtime 均不再变化(稳定产出)。
|
||||
|
||||
出包走同一发布入口(`runTauriBuild` + `--bundles nsis`),并临时把 `bundle.createUpdaterArtifacts` 置 false——本机没有 updater 签名私钥,只出安装包;产物 `target/x86_64-pc-windows-msvc/release/bundle/nsis/陶泥儿开发版_0.1.67_x64-setup.exe`(166452206 B,sha256 `04a0be8ab8f54d8f032ce86d3e5c7a99c1dd6e81f71d49b63486827b11a01940`)。`tauri.windows.conf.json` 里 `resources/plugins → plugins` 是目录级映射。
|
||||
|
||||
包内核对:7z 解包得 2108 个文件,包内 `plugins/` 的 32 个文件与准备步骤产出的 `resources/plugins` **逐文件 sha256 完全一致**;`editor_adapters.rs` 的三个运行时相对路径(`UNITY_ATTACH_HELPER_RELATIVE` / `GODOT_BRIDGE_PAYLOAD_RELATIVE` / `COCOS_BRIDGE_PAYLOAD_RELATIVE`)加上声明里的必需产物共 17 项在包内全部命中。
|
||||
|
||||
迁移前对照取两份:本机已安装的 2026-09-24 包(`%LOCALAPPDATA%\陶泥儿开发版\plugins`,迁移前产品产物),以及把 `src-tauri` 整体切回合并基线 `8f59c034f` 后在同一个工作树里跑 `cargo build --release --target x86_64-pc-windows-msvc --features=unity-editor-execute,godot-editor-execute,cocos-editor-injection` 得到的 `resources/plugins`。三份对照路径集合一致,源码派生内容(JS/HTML/JSON/license/notice)逐字节一致,Unity helper、Godot 扩展逐字节一致。
|
||||
|
||||
两处已定性的差异:
|
||||
|
||||
1. **Cocos payload 的构建 feature 集不同(需裁决口径)**:迁移前由同一次应用构建产出(`windows-bootstrap` + `windows-injection`,345088 B);准备步骤按声明只构建 `windows-injection`(26112 B,见 2026-09-27 决策日志条)。两者导出面完全相同(`DllMain`、`cocos_editor_bridge_bootstrap_source`),差掉的是宿主侧 bootstrap 传输(`reqwest`/`tungstenite`/`inspector`),注入进程不使用;但按「内容摘要与迁移前逐项一致」的字面判据不成立。
|
||||
2. **MSVC 链接产物不可字节复现**:同一 source / feature / profile / target 连续构建的 payload 摘要不同(除 PE `TimeDateStamp` 外还有 22 字节 RSDS GUID 差异);.NET publish 相反是确定的(Unity helper 跨两次重新发布逐字节一致),Godot 因 `buildId` 早退未重链也保持逐字节一致。因此「摘要一致」只在源码派生物、.NET 产物与命中工具链内部缓存的产物上成立。
|
||||
|
||||
新发现的风险(本轮未修,不影响上述结论):
|
||||
|
||||
- 准备步骤的 `cocos-bridge-build` 与同一次应用构建写同一个输出路径 `target/<triple>/<profile>/deps/cocos_editor_bridge.dll`(两个 feature 单元同名产物),准备步骤的候选查找可能取到另一单元刚写下的文件;本轮实测两者交替后 cargo 会多一次重链。建议让准备步骤在独立 target 目录构建,或与应用的 feature 集对齐后从同一单元取产物。
|
||||
|
||||
### 客户端编辑器分支(验收标准第 5 条)
|
||||
|
||||
本机未安装 Unity / Godot / Cocos Creator(`Program Files`、`UnityHub`、scoop shims 均无),进入编辑器分支只会停在「未检测到编辑器进程」,拿不到「helper/扩展被加载」的真机结论,因此**本轮未执行**,需在有三种编辑器的机器上补做。
|
||||
|
||||
已完成的自动化前段(本轮实测,用安装包解出的客户端、不安装):
|
||||
|
||||
- 从 NSIS 包 7z 解出后直接运行 `genarrative-ai-game-creator-shell.exe`:窗口落在 `http://tauri.localhost/`(生产态嵌入前端,不是 `devUrl`),首页正常渲染,最近项目与模板库可见——说明包内 `plugins/`、`skills`、模板资源都被正确读取(对照:用 `cargo build --release` 直接编出来的 exe 没有 `custom-protocol`,会去连 `127.0.0.1:3080` 并落到 chrome 错误页,不能拿它当打包客户端)。
|
||||
- CDP 主世界(`page.target().createCDPSession()` + `Runtime.evaluate`)可读只读投影:`list_agc_plugins` 返回 `agc-cocos-editor` / `agc-unity-editor`(`builtin=true`、`hasRuntime=true`、`adapter` 正确),`list_agc_skill_catalog` 返回 8 条 —— 插件的 JS 入口与 Skill 包都按声明进包。
|
||||
- `agc-godot-editor` 插件不在该列表里符合现役语义:`plugin_host.rs::plugin_matches_project` 只在当前项目是 Godot 工程(根或一层子目录有 `project.godot`)时才让它可见。
|
||||
- 三种编辑器分支的判据入口:`require_plugin_adapter` 缺适配器时报「当前客户端不支持 X 编辑器桥接」,适配器在 payload/helper 缺失时报各自缺组件文案(如 Godot「插件缺少原生 DLL 资源」),编辑器没开时报探测失败——补做时要按这三类分开记录。
|
||||
|
||||
### 顺带定性:CI 唯一红项与本 PR 无关
|
||||
|
||||
run 2980(HEAD `0cf536b6`)唯一失败项是 `tests::sessions::background_agent_runtime_can_write_memory_and_project_files`(`src-tauri/src/tests/sessions.rs:405`,第二个 provider follow-up 请求 `recv_timeout(2s)` 超时):
|
||||
|
||||
- 本 PR 对这条路径零改动:`src/` 下只有 `main.rs`(`#[cfg(test)]` 引入 `package_layout` 单测)与 `agent/codex_cli.rs`(去掉 `codex_package_metadata` 测试模块)两处**测试编译期**改动;`sessions.rs` 与 agent runtime 与合并基线逐字节相同。
|
||||
- 把 `src-tauri` 整体切回合并基线 `8f59c034f` 后,同一条命令在本机失败在同一断言(`second llm request: Timeout`)。
|
||||
- 本机实测第二个 follow-up 请求耗时 **5.18s / 4.87s**(两次),而测试预算 2s:该断言在本机裕量不足。master run 2979(lane 2 shard 3)也因另一条并发用例失败,属同一类时间预算抖动。
|
||||
- 结论:既有测试时间预算问题,不在本 PR 内顺手修。
|
||||
|
||||
## 验证步骤(Windows 侧执行清单)
|
||||
|
||||
准备:切到本里程碑分支,`npm ci`(需装上 `@openai/codex-win32-x64`),确认 `spacetime --version` 与 `server-rs` 锁定版本一致(仅本地 dev 需要)。
|
||||
|
||||
1. **准备步骤单独跑(不打包)**
|
||||
- `npm run agc:bundled-resources:prepare -- --target x86_64-pc-windows-msvc`
|
||||
- 预期:一行汇总日志;`src-tauri/resources/plugins/agc-unity-editor/dotnet/publish/win-x64/Agc.Unity.Attach.exe`、`agc-godot-editor/native/gdextension/` 下的扩展在位。
|
||||
- Cocos payload 只在 injection 构建下交付(与迁移前一致):加 `--features=cocos-editor-execute,unity-editor-execute,godot-editor-execute,cocos-editor-injection` 再跑一次,确认 `agc-cocos-editor/native/payload/cocos-editor-bridge.dll` 同时出现在插件工作区与随包目录。
|
||||
- 再跑一次:预期全部「命中缓存」,且 `resources/**` 的文件时间戳不变。
|
||||
2. **构建期不再写随包资源**
|
||||
- 取 `src-tauri/resources` 全量快照(相对路径/大小/mtime/sha256)→ `touch apps/ai-game-creator-shell/src-tauri/build.rs` → 再 `cargo build` → 两次快照必须逐项一致。
|
||||
3. **构建新鲜度**:源码不变时连续两次 `cargo build --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml`,第二次应为秒级 `Finished`,且不再出现 `Compiling genarrative-ai-game-creator-shell`。
|
||||
4. **打包一致性**:出一次 Windows 安装包,核对包内 `plugins/` 下三种编辑器分支产物的路径与 sha256 与迁移前一致;`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --no-run` 必须通过(会触发只读校验,所以要先跑过准备步骤)。
|
||||
5. **客户端启动**:进入 Unity/Godot/Cocos 编辑器分支各一次,确认对应 helper/扩展被加载,没有「缺少组件」类提示。
|
||||
6. **边界(负例)**
|
||||
- 删掉 `plugins/agc-unity-editor/dotnet/publish/` 且让工具链不可用后打包:准备步骤必须给出明确失败原因(缺工具链/缺产物),而不是静默产出缺组件的包。
|
||||
- 删掉 `target/agc-resource-staging.json` 再跑准备步骤:预期重新生成,产物内容不变(丢缓存只多一次哈希)。
|
||||
- 删掉 `plugins/agc-unity-editor/dotnet/publish/win-x64/.agc-source.sha256` 后重跑准备步骤:预期重新执行 dotnet publish,而不是复用旧产物。
|
||||
- 手工改 `resources/**` 一个字节后 `cargo build`:只读校验必须失败(该规则覆盖 source 派生内容;prepared 产物只查存在性,见排障经验)。
|
||||
|
||||
记录:把每步命令、关键输出与结论贴回本里程碑或对应 PR;未通过项回到主规范 §9 记为未决问题。
|
||||
|
||||
## 证据要求
|
||||
|
||||
- 自动化:编辑器分支产物的摘要对比、平台门槛用例、配置门禁与包内资源门禁。
|
||||
- 运行时:Windows 上一次完整打包与一次客户端启动,确认编辑器分支产物被读取。
|
||||
- 边界:缺少外部工具链、缺少组件、非目标平台三种情形下的失败与跳过语义。
|
||||
@@ -0,0 +1,51 @@
|
||||
# 【里程碑】AGC 随包资源改由校验器读入
|
||||
|
||||
| 字段 | 值 |
|
||||
| ----------- | ----------------------------------------------------------- |
|
||||
| Version | 1.0 |
|
||||
| Status | completed(2026-09-27) |
|
||||
| Date | 2026-09-26 |
|
||||
| Parent Spec | `docs/technical/【技术方案】AGC随包资源staging归位-2026-09-26.md` |
|
||||
|
||||
## 已定决策(2026-09-27)
|
||||
|
||||
- **实现形态:混合**——生成归 Node(`scripts/prepare-bundled-resources.mjs`),声明与校验归 Rust。依据与对比见主规范 §4.8。
|
||||
- **单一声明**:`build_support/package-layout.json` 是唯一人工声明;Rust 侧使用由 `scripts/check-package-layout.mjs` 生成的编译期常量(`package-layout.generated.rs`),门禁 `npm run agc:bundled-resources:check` 已进 `agc:typecheck` 链;运行期 `codex_bundle.rs` 的公开接口与取值不变。
|
||||
- **校验收口边界**:本里程碑对随包 Codex 目录做全量校验(清单 schema/平台/版本、文件集合、逐文件摘要、第三方声明、可执行位、白名单外文件);插件随包目录只校验必需组件与符号链接。插件产物的逐文件摘要校验在 M2 由准备步骤写入树内清单后启用——M1 期间构建脚本仍整体重建 `resources/plugins`,树内清单会被清掉。
|
||||
- **独立验证入口**:`AGC_SKIP_RESOURCE_STAGING=1` 让构建脚本只跑只读校验、跳过写入分支。
|
||||
|
||||
## 目标
|
||||
|
||||
构建脚本不再需要「自己写随包资源」才能成立:在约定目录已有合规资源时,构建只做只读校验并通过;校验失败时给出明确原因并拒绝继续,而不是静默重新生成。
|
||||
|
||||
## 范围
|
||||
|
||||
- 随包资源的合规性判定:平台目录存在、清单 schema 与平台一致、逐文件摘要一致、必需组件齐全、版本与上游锁定一致。
|
||||
- 资源生成能力的可复用化:同一份布局与摘要校验能力既能被构建期校验使用,也能被准备步骤使用,不得出现第二份组件白名单。
|
||||
- 生成结果的稳定性要求:同一输入重复生成时,产物内容与文件时间戳不发生变化。
|
||||
|
||||
## 不在范围内
|
||||
|
||||
- 接入 dev 与发布入口(下一里程碑)。
|
||||
- 移除构建脚本里的资源写入分支。
|
||||
- 编辑器分支产物(Unity/Godot/Cocos)的生成方式与外部工具链调用。
|
||||
- 运行时资源解析顺序、完整性校验语义与打包配置里的资源映射。
|
||||
- Linux 产物支持。
|
||||
|
||||
## 依赖与前置条件
|
||||
|
||||
- 主规范第 4.3 与第 4.4 节的合同(准备步骤合同、构建脚本退化后的职责边界)。
|
||||
- 现有随包资源与清单已由当前实现产出,可用于校验回归。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [ ] 资源合规时,校验路径可独立运行并通过,不依赖构建脚本的写入分支。
|
||||
- [ ] 上游锁定版本、平台、逐文件摘要、必需组件四类不一致各自被拒绝,并给出可定位的原因。
|
||||
- [ ] 重复执行资源生成,产物内容与文件时间戳不变(幂等)。
|
||||
- [ ] 同一份布局与组件白名单只有一处声明,构建期校验与准备步骤共用。
|
||||
|
||||
## 证据要求
|
||||
|
||||
- 自动化:资源校验的通过/拒绝用例;同输入重复生成后目录快照对比(内容 + 时间戳)。
|
||||
- 运行时:本机在既有随包资源上运行一次校验与一次构建,确认资源被正常读取且构建行为与改造前一致。
|
||||
- 边界:目标平台不支持、上游缺失、摘要不匹配、目录被非本工具内容占用四种场景各自的失败输出。
|
||||
@@ -0,0 +1,48 @@
|
||||
# 【里程碑】AGC 随包资源生成接入 dev 与发布入口
|
||||
|
||||
| 字段 | 值 |
|
||||
| ----------- | --------------------------------------------------------------- |
|
||||
| Version | 1.0 |
|
||||
| Status | in-progress(2026-09-27 起实施) |
|
||||
| Date | 2026-09-26 |
|
||||
| Parent Spec | `docs/technical/【技术方案】AGC随包资源staging归位-2026-09-26.md` |
|
||||
|
||||
## 目标
|
||||
|
||||
随包资源在客户端开发与发布两条链上,都由启动/打包之前的准备步骤一次性生成;构建脚本不再承担生成职责,源码不变时构建不再重复编译。
|
||||
|
||||
## 范围
|
||||
|
||||
- 客户端开发的启动流程:在拉起客户端之前完成资源准备,命中缓存时不重写任何文件。
|
||||
- 发布打包流程:Windows 与 macOS 两条链在拉起打包工具之前完成资源准备,包括既有的运行时资源准备点。
|
||||
- 纯复制型资源(随包组件与插件工作区)的生成职责从构建脚本迁出。
|
||||
- 不打包场景(仅校验、不产包)的放行口径。
|
||||
|
||||
## 不在范围内
|
||||
|
||||
- 编辑器分支产物(Unity/Godot/Cocos)的生成方式与外部工具链调用(下一里程碑)。
|
||||
- 打包配置里的资源映射、包内资源门禁与安装包形态。
|
||||
- 运行时资源解析与完整性校验语义。
|
||||
- 构建脚本中与资源无关的既有职责(配置能力、提示词产物、元数据)。
|
||||
|
||||
## 依赖与前置条件
|
||||
|
||||
- 上一里程碑的验收通过:资源校验可只读通过、生成幂等、白名单唯一。
|
||||
- M1 交付的准备步骤与声明门禁已在位:`apps/ai-game-creator-shell/scripts/prepare-bundled-resources.mjs`(Codex 与插件两条纯复制路径,写临时目录后原子替换,命中缓存不重写)与 `npm run agc:bundled-resources:check`(已进 `agc:typecheck` 链)。本里程碑需要让准备步骤改为在 `resources/plugins` 内写入自己的清单,并停止构建脚本对该目录的整体重建,插件产物的逐文件摘要校验才能启用。
|
||||
- 开发与发布两条链在拉起客户端/打包工具之前都有明确可插入的准备阶段。
|
||||
- Windows 与 macOS 均需具备可验证的开发环境(两个平台各自验收)。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [x] 客户端开发启动一次成功:不再出现因资源变更而触发的重复构建,客户端与运行器进程稳定存活。(证据:清理 `AGC` dev 探针——`tauri dev` 全程 `Rebuilding application` 0 次、`Running DevCommand` 1 次、主 crate 仅编译 1 次,app 起来后持续处理项目;完整 `npm run agc` 在本机被 SpacetimeDB `Pre-publish check`(401 InvalidSignature / 502 Bad Gateway)阻断,属既有本机环境问题。)
|
||||
- [x] 源码不变时连续两次构建,第二次为秒级完成;构建脚本声明的输入中不再出现随包资源路径。(证据:`cargo build --no-default-features` 连续三次 0.69 / 0.22 / 0.22 秒;强制构建脚本重跑后 `resources/codex` 与 `resources/plugins` 快照逐项不变。)
|
||||
- [ ] Windows 与 macOS 打包产物中的随包资源,与迁移前逐项一致(路径、内容摘要、可执行位)。(macOS 侧 `check-macos-bundle.mjs` 待打包验证;Windows 待 M3 归位三处构建期产物后复验。)
|
||||
- [x] 准备步骤连续执行两次不改变产物内容与时间戳;缺少准备步骤时,打包与启动以明确错误失败,而不是静默产出缺组件的包。(证据:准备步骤 10 条用例含幂等、上游缺失、上游元数据漂移与失败关闭;`AGC_SKIP_RESOURCE_STAGING=1` 在既有产物上只读通过;构建脚本校验缺失组件时 fail closed。)
|
||||
- [ ] 本机 Rust 门禁(会触发构建脚本的测试入口)与不打包构建路径仍然可用。(macOS 侧已验;Windows 的 `check:rust:shell` 待真机确认。)
|
||||
|
||||
## 证据要求
|
||||
|
||||
- 自动化:构建新鲜度日志、构建脚本输入清单、准备步骤幂等快照、包内资源门禁脚本结果。
|
||||
- 运行时:macOS 与 Windows 各一次客户端启动,确认随包组件被读取而非回退到外部安装。
|
||||
- 边界:缺少准备步骤、缓存命中、上游锁定变化三种情形下的行为。
|
||||
1
|
||||
@@ -95,4 +95,4 @@ AGC 项目开发对话的显示与恢复只依赖两项输入:**项目对话
|
||||
- 宿主确实对外发布「这一轮正在跑」:快照 `phase=working`、`active` 里恰好一条 `kind=execute` 的在途许可、`executorStopped=false` —— 这正是界面重进时判断「有运行中回合可恢复/可终止」读的事实;
|
||||
- 收尾后账本落 `completed`、`active` 清空、`executorStopped=true`;宿主侧落的条目(中间文本 → 工具卡片 → 工具回执)顺序不变。
|
||||
- 顺带记实:CLI / 无前端宿主的历史里**既没有用户消息、也没有最终助手回复**(两者都由前端 `append_direct_project_conversation_message` 写),CLI 只有宿主侧条目与回执文本;夹具断言据此只校验宿主侧条目。
|
||||
- **可复跑入口(2026-09-29)**:新增 `npm run check:agc-direct-execution-fixture`(`scripts/run-agc-direct-execution-fixture.mjs`)——默认用工作区 `src-tauri/target/debug` 里的 AGC 二进制跑**全部 11 个用例**(`completed / passes / mcp / mcp-write / native / native-resources / patch / deadline / native-session / restart / running`),也支持 `-- --cases <子集>` 与 `-- --agc-exe <path>` 透传。本轮实跑:**11/11 passed**(修复前这些用例要么根本到不了 Provider,要么统一卡在账本阶段)。这样这条真机证路不再需要手打长命令,后续改 DirectProject/执行许可链路可以直接拿它当判据。
|
||||
- **可复跑入口(2026-09-29)**:新增 `npm run check:agc-direct-execution-fixture`(`scripts/run-agc-direct-execution-fixture.mjs`)——默认用工作区 `src-tauri/target/debug` 里的 AGC 二进制跑**全部 11 个用例**(`completed / passes / mcp / mcp-write / native / native-resources / patch / deadline / native-session / restart / running`),也支持 `-- --cases <子集>` 与 `-- --agc-exe <path>` 透传。本轮实跑:**11/11 passed**(修复前这些用例要么根本到不了 Provider,要么统一卡在账本阶段)。这样这条真机证路不再需要手打长命令,后续改 DirectProject/执行许可链路可以直接拿它当判据。**(该入口与它服务的夹具已随 `--direct-codex-chat` CLI 退役一并删除,见 09-24 实施计划第 4 步;本段记的是当时的原命令与结果。)**
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
|
||||
- **随包载荷完整**:安装目录 `coding-agent/win-x64/` 下 6 个声明组件全部在位(`bin/codex.exe`、`bin/codex-code-mode-host.exe`、`codex-path/rg.exe`、`codex-resources/codex-command-runner.exe`、`codex-resources/codex-windows-sandbox-setup.exe`、`codex-package.json`),且**逐文件 SHA-256 与 `manifest.json` 全部一致(6/6)**;`codex-package.json` 声明 `codex-cli 0.155.1` / `layoutVersion=1` / `target=x86_64-pc-windows-msvc`。
|
||||
- **随包 Codex 确实能起来**:把已安装的 exe 直接交给仓库夹具当被测对象
|
||||
(`npm run check:agc-direct-execution-fixture -- --agc-exe "%LOCALAPPDATA%\陶泥儿开发版\genarrative-ai-game-creator-shell.exe" --cases completed`),
|
||||
(当时的命令是 `npm run check:agc-direct-execution-fixture -- --agc-exe "%LOCALAPPDATA%\陶泥儿开发版\genarrative-ai-game-creator-shell.exe" --cases completed`;
|
||||
该入口与夹具已随 `--direct-codex-chat` CLI 退役删除,见 09-24 实施计划第 4 步),
|
||||
结果是随包 app-server **启动并回了一个 JSON-RPC 错误**:`Codex app-server JSON-RPC 失败:items must not be empty`。也就是说缺的不是随包依赖,而是客户端请求体(`agent.codex_app_server.remote_control disabled reason=provider-proxy-auth` 是夹具本地回环模式的预期行为)。
|
||||
- **这是已修缺陷的现场复现,不是新问题**:该报错对应的修复是 `7ec984d0d`(2026-09-28 22:45「修复 DirectProject 空历史注入导致新项目第一条消息失败」)。`0.1.154` 的源码是 `76cdb96c5`(20:52),`git merge-base --is-ancestor 7ec984d0d 76cdb96c5` 不成立;`dev-win` 当前 `0.1.158` 的源码 `e1dacccec5` 包含它。同一条 `completed` 用例在**当前 master 的调试构建**上 PASS,所以差异来自版本而不是构建配置。
|
||||
- **对本条验收的含义**:①「随包 Codex 在 Windows 上能起来」已有现场证据;②本机这份 0.1.154 在执行链路上是坏的,**更新到 `dev-win` 当前的 0.1.158 才会恢复**(这次更新同时是「AGC 客户端更新切换到官方更新插件」那条的验收动作);③仍只能由用户在桌面会话完成的:**NSIS 安装器实跑**(安装 / 卸载 / 覆盖升级)与 **GUI 登录对话 + Cocos 编辑器回归**。
|
||||
|
||||
@@ -8972,7 +8972,7 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- 决策(队列与埋点听回合终态,不听命令返回):前端发送队列的放行改由"回合完成(终态事件)或接单被拒"驱动,reducer 新增 `completedTurnCount` 作为唯一判据——不能用 `turnRunning` 的下降沿,一轮可能同批开始 + 结束。埋点结算同样挂到回合终态:不能在接单返回时结算,成绩是回合末才入 `pending_runs`,提前结算会变成空操作;`runTurn` 返回"是否接单",接单失败的路径只清句柄、不结算。**加 TODO:这条队列以后挪到 Rust 端,落点就是 Thread Manager 的接单动作。** 首页"运行中的项目"快照改由 TM 的逻辑回合导出,任务侧不再单独维护一张表。
|
||||
- 决策(认证失败不再重跑整轮):删掉 `withDirectCodexSessionRefresh` 的"刷新 + 重跑整轮"(重跑会重复落盘用户消息),登录态失效按普通回合失败呈现;用同一个包装的 `cancel_direct_codex_turn` 一并去掉。
|
||||
- 决策(失败原因本轮不落历史):失败原因只走事件载荷与宿主诊断(`.agent/runtime/errors` + 应用日志 + 错误上报池由宿主投影写出,进池责任从前端 catch 移到宿主),不写进 `project.jsonl`——重进项目只会看到那条没有回复的用户消息。**加 TODO(暂定做法见 ADR 备选方案第 3 条 (b)):以后要做"进历史但不喂模型"的失败条目,本轮明确不持久化。**
|
||||
- 决策(CLI 保持 await):CLI 入口(`cli.rs` 的 `direct-codex.chat`)继续 await 整轮,因为它要把回复文本打到终端、没有事件订阅可用;两个入口共用同一份接单前检查、同一个命令主体和同一份 `Display` 文案,不各写一套判据。
|
||||
- 决策(CLI 保持 await):CLI 入口(`cli.rs` 的 `direct-codex.chat`)继续 await 整轮,因为它要把回复文本打到终端、没有事件订阅可用;两个入口共用同一份接单前检查、同一个命令主体和同一份 `Display` 文案,不各写一套判据。**(口径修正:本条已作废——2026-09-24 的 `--direct-codex-chat` 退役把 CLI 入口整个删掉,DirectProject 只剩「入队」一个命令入口;见本文件同日「命令入队化」那条与 `【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24`。)**
|
||||
- 明确不做:不给失败载荷加字段(不加 `detailRef`);不恢复 invoke 拒绝通道,也不为"接单后的前置失败"新增事件类型;本轮不做"失败条目进历史但不喂模型"(TODO)、不做 Rust 端发送队列(TODO)。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/agent/{direct_turn_accept.rs,direct_thread_manager.rs,direct_thread_wire.rs,direct_turn_error.rs,direct_turn_failure.rs,direct_project_context.rs,direct_runtime/{mod.rs,user_input.rs},codex_app_server/mod.rs,runtime_driver/entrypoints.rs,cli.rs}`、前端 `chat/{controller/useDirectProjectChatController.ts,controller/useDirectProjectTurnStatus.ts,controller/useDirectThreadChatSubscription.ts,conversation/directCodexConversation.ts,conversation/directThreadChat.ts,conversation/directTurnPresentation.ts}`、`chat/generated/{DirectTurnError,DirectTurnRejection,DirectThreadEvent,...}.ts` 与 `tests/{directThreadChat.test.ts,appSurface/*.suite.ts}`;文档 `docs/adr/【ADR】DirectProject命令接单化-2026-09-23.md`、`docs/technical/【实施计划】DirectProject命令接单化-2026-09-23.md`、`docs/technical/【技术方案】DirectProject Codex原始历史与异常恢复-2026-09-04.md` 与 `docs/adr/【ADR】DirectProject对话历史单一事实源-2026-09-16.md`。
|
||||
- 已知坑:`cargo test export_bindings` 会重写全部 `chat/generated/`(引号风格漂移),跑完要 `git checkout --` 掉不是本次新增的文件;本机 rust 全量 `--bins` 测试会挂在 mock server 的 `inet_csk_accept` 上,用 `--bins "agent::"` 之类过滤跑。
|
||||
@@ -9079,6 +9079,134 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- 验证(真实上游 smoke,本地 dev DB):清空 `agc_model_catalog` 后启动 api-server → 日志 `已按上游模型列表初始化 AGC 模型目录 revision=1 model_count=6`;登录后 `GET /api/llm/models` 返回同一批模型、`displayName` 即上游原名、默认项为排序后第一项;上游不可达/非 2xx 时启动只记录 error、AGC 接口 `503` 且目录保持未初始化;目录已存在时重启不重写。
|
||||
- 边界(未验证/残留):上游在售模型超过 32 条时同步会失败(目录项上限未改);`qwen-image-3.0` 这类图像模型会一起进入目录,是否对 AGC 隐藏由 owner 在后台停用;混合版本期间未升级的 api-server 会把自己的 AGC 接口打到 `503`,module 与 api-server 必须同批发布/回滚。
|
||||
|
||||
## 2026-09-27 AGC 随包资源改为「单一声明 + 准备步骤生成 + 构建期只读校验」
|
||||
|
||||
- 背景:随包资源(内置 Codex CLI、插件工作区)由 `build.rs` 在构建期写入 `src-tauri/resources/**`,而这些路径同时被 tauri 配置的 `bundle.resources` 登记成构建输入,cargo 因此永远判 stale:Windows/macOS 每次构建重编主 crate(41–87 秒),macOS dev 反复 `Rebuilding application`、客户端起不来(issue #519)。三轮症状层修复(内容比对、权限跳过、`.taurignore`)都只减少写入次数,没有改变「构建期写被登记文件」这一结构。
|
||||
- 决策(形态:混合):准备步骤用 Node(复用 `stage-node-runtime.mjs` / `prepare-macos-codex.mjs` 的下载、`integrity`、临时目录 + rename 原子替换),布局与摘要校验留在 Rust(复用 `codex_bundle.rs` / `godot_bundle.rs`),运行期模块公开接口与取值不变。
|
||||
- 决策(单一声明):唯一人工声明是 `apps/ai-game-creator-shell/src-tauri/build_support/package-layout.json`(Codex 三元表与组件白名单、上游候选路径、第三方声明来源、插件随包子目录与跳过规则、平台与 feature 门槛)。Node 直接读该 JSON;Rust 读由 `scripts/check-package-layout.mjs` 生成的 `package-layout.generated.rs` 编译期常量(不解析 JSON、不引入生命周期妥协)。门禁 `npm run agc:bundled-resources:check` 已进 `agc:typecheck` 链,同时校验声明自身不变量:目标唯一、`executable` 属于白名单、每个目标恰有一条第三方声明来源,且 `codex.version` 与应用锁定的 `@openai/codex` 一致。
|
||||
- 决策(校验与独立入口):`build.rs` 新增只读校验——Codex 目录校验清单 schema/平台/版本、文件集合、逐文件 sha256、第三方声明、可执行位与白名单外文件;插件目录校验必需组件与整树符号链接。`AGC_SKIP_RESOURCE_STAGING=1` 可跳过写入分支、只跑校验,用于在既有产物上单独验证校验路径。插件产物的逐文件摘要校验留到 M2(届时准备步骤在树内写清单,不再被构建脚本整体重建覆盖)。
|
||||
- 影响面:`apps/ai-game-creator-shell/src-tauri/{build.rs,build_support/**}`、`apps/ai-game-creator-shell/scripts/{check-package-layout.mjs,prepare-bundled-resources.mjs,prepare-bundled-resources.test.mjs}`、`apps/ai-game-creator-shell/package.json`、根 `package.json`、`.gitignore`、AGC 技术方案 §4.8/§8/§9、M1 里程碑规范与实施计划、开发运维文档。三份 tauri 配置的 `resources` 映射与包内路径不变。
|
||||
- 验证:`npm run agc:bundled-resources:check`;`npm run agc:bundled-resources:test`(9 passed,含幂等、上游缺失、非本工具目录、目标不支持、dry-run);`AGC_SKIP_RESOURCE_STAGING=1 cargo check --no-default-features --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml` 在准备步骤产物上通过;准备步骤产物与构建脚本产物逐文件一致(相对路径、大小、sha256);连续两次运行准备步骤第二次全部命中缓存,目录快照(含 mtime)不变。
|
||||
- 边界(未验证):准备步骤尚未接入 dev 与发布入口(M2);Windows 真机的构建新鲜度与打包未验证;Unity/Godot/Cocos 产物仍由构建脚本生成(M3);Linux 上五条 staging 与校验均为 no-op。
|
||||
|
||||
## 2026-09-27 AGC 随包资源准备步骤接入 dev 与发布入口,构建脚本退出写入
|
||||
|
||||
- 背景:M1 只交付了单一声明、准备步骤与只读校验,构建脚本仍在写随包资源,所以 macOS 的 `npm run agc` 仍会因 `resources/codex/mac-native` 被重写而反复重建、`cargo build` 每次重编主 crate(41–87 秒)。
|
||||
- 决策(接线):`start-tauri-dev.mjs` 在前端与配套后端就绪之后、spawn Tauri CLI 之前调用准备步骤(命中缓存零写入,日志前缀 `[ai-game-creator-shell]`);`build-release.mjs` 的 `runTauriBuild` 与既有 `stageRuntime(target)` 并列调用 `stageBundledResources(target)`,`tauri build --no-bundle` 仍不强制 staging。两处都保留依赖注入,便于入口测试断言调用顺序与 no-bundle 行为。
|
||||
- 决策(写入边界,声明新增 `origin`):`origin: source`(Codex 组件、插件 `src`/`panels`/`skills`/`native/payload`)由准备步骤写;`origin: build`(Unity `dotnet/publish/win-x64`)与外部工具链产物(Godot `native/gdextension`、Cocos payload)由构建脚本在产物生成后写。构建脚本删除 codex 与插件白名单的写入分支及 `stage_plugin_file`/`copy_plugin_tree`/`copy_plugin_file`,改为 `stage_build_generated_plugin_payloads`。
|
||||
- 决策(契约收口):插件随包工作区改为与仓库源码逐文件比对(清单 + 逐文件 sha256 + 整树符号链接,构建期派生内容只查存在性),实现移入 `build_support/package_layout.rs` 以复用单测;上游原生包元数据(layoutVersion/version/target/entrypoint/resourcesDir/pathDir)改由准备步骤按声明校验,`build_support/codex_package_metadata.rs` 因失去调用方而删除。准备步骤改为同步实现(全部是本地同步 IO),入口可直接调用而无需子进程。
|
||||
- 影响面:`apps/ai-game-creator-shell/scripts/{prepare-bundled-resources.mjs,prepare-bundled-resources.test.mjs,start-tauri-dev.mjs,build-release.mjs,build-release.test.mjs}`、`apps/ai-game-creator-shell/tests/start-tauri-dev.test.ts`、`src-tauri/build.rs`、`build_support/{package_layout.rs,package-layout.json,package-layout.generated.rs}`(`codex_package_metadata.rs` 删除)、`src/agent/codex_cli.rs`、技术方案 §4.9、M1/M2 里程碑与运维文档。
|
||||
- 验证:`cargo build --no-default-features` 连续三次 0.69 / 0.22 / 0.22 秒全程 fresh;强制构建脚本重跑(`touch build.rs`)后 `resources/codex` 与 `resources/plugins` 的快照(相对路径/大小/mtime/sha256)逐项不变;`cargo test --no-default-features … package_layout` 36 passed;`node --test scripts/prepare-bundled-resources.test.mjs` 10 passed(含上游元数据漂移被拒);`node --test scripts/build-release.test.mjs` 39 passed(含 `stage → bundled → build` 顺序与 no-bundle 不 staging);`npx vitest run tests/start-tauri-dev.test.ts` 12 passed(含「准备步骤先于 CLI 启动」)。
|
||||
- 边界(未验证):Windows 真机未验证,且 Unity publish 目录、Godot gdextension、Cocos payload 仍是构建期写入,Windows 构建新鲜度要等 M3 归位;完整 `npm run agc` 在本机被 SpacetimeDB `Pre-publish check`(先后 401 InvalidSignature 与 502 Bad Gateway,属既有本机环境问题)阻断,未跑通整条 dev 启动链路。
|
||||
|
||||
## 2026-09-27 AGC 编辑器分支产物归位:构建脚本彻底退出写入
|
||||
|
||||
- 背景:M2 之后构建脚本仍生成 Unity publish 目录、Godot gdextension 与 Cocos bridge payload,这三处写入落在 `resources/plugins/**`(`bundle.resources` 映射目录),Windows 上仍会触发每次重编,`.taurignore` 的 staging 条目也还不能删。
|
||||
- 决策(声明扩展):`subdirectories` 新增 `origin: prepared`;`libraryStaging` 增加 `prepare` 与 `files`;新增 `nativePayloads` 与 `plugins.prepareSteps`(程序类型、工作目录、指纹、必需产物)。Godot 随包文件清单改由声明提供——`godot_bundle::BUNDLE_FILES` 从生成的编译期常量取值,不再各写一份。
|
||||
- 决策(准备步骤执行器):准备步骤按声明运行 `powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File build.ps1`(Unity/Godot,Godot 额外移除 `PSModulePath`)与 `cargo build -p cocos-editor-bridge --target … --features windows-injection`;内容指纹命中且必需产物齐全时零写入;命令执行器可注入,便于在 macOS 上用假执行器覆盖调度、指纹与失败关闭逻辑。
|
||||
- 决策(构建脚本瘦身):删除 `prepare_unity_editor_helper`、`prepare_godot_editor_extension`、`stage_cocos_editor_payload`、`stage_build_generated_plugin_payloads` 及其辅助函数(build.rs 415 → 193 行),只留只读校验,并新增「已准备产物存在性 + Godot 随包库深度校验」;`AGC_SKIP_RESOURCE_STAGING` 开关随写入分支一并删除;两份只含 staging 条目的 `.taurignore` 删除。
|
||||
- 影响面:`apps/ai-game-creator-shell/src-tauri/build_support/{package-layout.json,package-layout.generated.rs,package_layout.rs,godot_bundle.rs}`、`src-tauri/build.rs`、`scripts/{prepare-bundled-resources.mjs,prepare-bundled-resources.test.mjs,check-package-layout.mjs,build-release.mjs}`、两份 `.taurignore`、技术方案 §4.9/§8、M3 里程碑、运维文档、决策日志与排障经验。
|
||||
- 验证:准备步骤 13 条用例通过(含三类准备步骤调度、指纹跳过、缺产物失败关闭、幂等与失败关闭);`npm run agc:bundled-resources:check` 通过;`cargo check --no-default-features` 通过(构建脚本仅剩只读校验,且不再出现在随包资源的写入路径上)。
|
||||
- 边界(未验证):Windows 真机未验证——powershell/cargo 两条命令路径、Unity/Godot/Cocos 产物归位、包内容一致性与客户端加载,需按 M3 里程碑的验收清单在 Windows 上确认。
|
||||
## 2026-09-24 命令入队化与待发消息队列归宿主:放行归 Thread Manager,CLI 直连入口退役
|
||||
|
||||
- 决策(词表):「接单 / 拒单」退役,命令边界的成功与失败改叫「入队 / 入队失败」;旧「接单」的语义角色
|
||||
(这一轮真正成立的那一刻)改叫「放行」。所以旧句"接单成立 ⇔ 事件流里有开始有结束"改成"放行成立 ⇔ …",
|
||||
逐处按角色改、不做字面替换;`features/agent-runtime` 的"拒单文案"属另一个域,改"请求被拒"。
|
||||
- 决策(命令 = 入队):`chat_with_game_creator_direct_codex` 改义改名(暂定 `enqueue_direct_codex_turn`),
|
||||
把今天"接单前"那条检查链原样跑完(身份 → 工作流恢复 → 用户条目校验 → prompt 投影 → 前置条件 → 工程准备),
|
||||
通过后只入队:不登记占用、不落盘用户条目、不发回合事件、不起 codex;失败走命令返回的 typed 载荷
|
||||
(`DirectTurnRejection` → `DirectTurnEnqueueFailure`)。入队按 `clientTurnId` 幂等(判重范围 = 在队 ∪ 在跑)。
|
||||
- 决策(队列归 Thread Manager):每项目一条 FIFO,只支持按顺序追加与按身份移除;队列的成员与顺序就是事件列表本身
|
||||
(`queue.enqueued` 在队期间不可回收、离开队列后可回收),`subscribe` 的 live-set bootstrap 因此天然把当前队列
|
||||
交给中途加入的订阅者;上限 5 只数在队条目,由 Rust 持有。
|
||||
- 决策(线上形状):`queue.enqueued{clientTurnId,userItem,creationType?,at}` + `queue.removed{clientTurnId,reason}`,
|
||||
`reason` 是 ts-rs 导出的 typed 枚举(`cancelled|dispatched`),不用字符串;事件不带 prompt——
|
||||
prompt 是入队检查的产物,只留在宿主的队列条目里。
|
||||
- 决策(放行):Thread Manager 在回合收口之后原子地「取队首 → 登记占用 → `turn.started` + `queue.removed{dispatched}`」,
|
||||
再落盘用户条目、下发用户条目、起整轮。**放行不重跑检查、不存在放行失败**,放行之后的一切失败都是回合失败,
|
||||
走既有 `turn.completed.failure`,不新增通道。kick 点 = 回合任务收尾(含 drop 守卫盖 panic)+ 中止路径 + 入队之后,
|
||||
幂等且在临界区里认领队首;入队不取 `DirectTaonierActiveInvocationGuard`(它必须整轮持有,是这一轮的调用身份)。
|
||||
- 决策(前端):不再持有队列副本,chip 只由事件投影,入队失败只由命令返回值给反馈(保留提示文案与"不丢草稿")。
|
||||
**(落地修正,2026-09-24)**:埋点句柄仍由前端在入队那一刻生成并随命令交给宿主(放行不重算,与其它入队检查产物一起
|
||||
存进队列条目),前端把句柄按 `clientTurnId` 存成一张表、回合终态按本轮开口条目的 canonical 身份认领结算;
|
||||
"不丢草稿"由「提交等命令的入队结果、只有用户自己能改的入队失败才返回 false」实现(不再是"宿主在放行时开句柄")。
|
||||
- 决策(顺带退役):`--direct-codex-chat` 整个退役——它唯一的实际消费者是手工夹具
|
||||
`scripts/direct-execution-production-fixture.mjs`(PR #439 引入、不在 CI、无 npm/harness 注册、无测试钉它),
|
||||
夹具一并退役。**(落地修正,2026-09-24)**:`TurnAlreadyRunning` 的最后一个生产点在调用身份守卫里
|
||||
(占用登记那条早已随入队化消失),所以它连同前端"同一轮消息仍在处理中"分支一起放到**守卫清理那一步**再删。
|
||||
`DirectTaonierActiveInvocationGuard` 的身份与 Thread Manager 的 `active_turn.turn_id` 是同一件事的两份记录,
|
||||
CLI 退役后让那五个读者改读 Thread Manager,再在第二步删掉守卫与它的 60 秒卡死兜底
|
||||
(删前必须保住"回合卡死可被取消解开"这条由 `d833ca9d3` 事故换来的保证)。
|
||||
**(落地,2026-09-30)**:见下方 09-30 条,守卫已删、`TurnAlreadyRunning` 已删,60 秒闸门改挂
|
||||
Thread Manager 的占用登记年龄。夹具的一键入口 `scripts/run-agc-direct-execution-fixture.mjs` 与
|
||||
`npm run check:agc-direct-execution-fixture`(09-29 新增,只把 `--agc-exe` 默认成工作区 debug 二进制)
|
||||
同样没有任何现役消费者,随夹具一并摘掉。
|
||||
- 代价:入队即写盘(引用 UI 设计文档的条目会生成 `ui/generated-<stem>.js`,取消不撤);检查是时间点事实、
|
||||
放行不重跑(manifest / 权限 / 目录变化后照旧放行,偏差落到回合失败);队列随进程消失,不做跨进程持久化;
|
||||
失去"真实二进制驱动执行层生产验证"这条手工路径。
|
||||
- 验证方式:设计见 `docs/adr/【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md` 与
|
||||
`docs/technical/【实施计划】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md`;落地提交与验证证据
|
||||
见下方 2026-09-30 三条,以及该实施计划的「落地进度」与「2026-09-30 收口记录」。
|
||||
|
||||
## 2026-09-30 守卫清理前置判据核实:取消兜底不需要新增无条件终态,60 秒闸门改挂占用登记年龄
|
||||
|
||||
- 背景:按 09-24 ADR §9 的计划,删 `DirectTaonierActiveInvocationGuard` 前必须确认"回合任务 park / 泄漏时,
|
||||
取消仍能清空占用并让队列继续放行"。查 git 历史(`d833ca9d3` / `ab970b9fd`)与当前实现后确认:
|
||||
`cancel_direct_codex_turn_at` 的兜底路径本来就同时做三件事——释放占用、往事件流补一条
|
||||
`turn.completed{aborted}`(否则前端会永远停在运行中)、调 `complete_direct_thread_turn` 解除占用并踢队列。
|
||||
所以删守卫**不需要**新增"取消路径补无条件终态",那是当时就有的东西,不是守卫带来的。
|
||||
- 决策:守卫只在两件事上是独有的——(1) 它是"这一轮是谁"的第二份进程内记录(与 Thread Manager 的
|
||||
`active_turn.turn_id` 同源),CLI 退役后已无第二个入口,按"同一件事只许有一处真相"删;
|
||||
(2) 它的 60 秒启动窗口闸门(防"刚放行、还在本地准备的回合被终止误伤")保留,改挂在 Thread Manager
|
||||
的占用登记年龄上:`direct_stale_turn_for_release(root, expected, reason)` 只在
|
||||
`NeverReachedExecutor` 时校验年龄(`DIRECT_STALE_TURN_RELEASE_MIN_AGE_MS`),返回身份;释放本身仍由
|
||||
既有的 `complete_direct_thread_turn` + `kick_direct_queue_dispatch` 完成(天然幂等)。
|
||||
- 决策:连接死亡的失败事实由 app-server 连接层自己落地(`ab970b9fd`),与守卫无关,删除守卫不动那条链路。
|
||||
- 落地(`direct_runtime/mod.rs` 删守卫表与只读探测,改 `direct_active_turn_id_at`;`direct_thread_manager.rs`
|
||||
补 `DirectTurnIdentity` + `read_direct_turn_identity`;`codex_app_server/mod.rs` 兜底目标改
|
||||
`DirectStaleTurnReleaseReason` 并接 `direct_stale_turn_for_release`;五个身份读者与相关测试改写;
|
||||
`direct_turn_dispatch.rs` 不再另取调用身份;删 `DirectTurnError::TurnAlreadyRunning` 与前端分支 / 生成绑定)。
|
||||
- 验证:`cargo test --bin genarrative-ai-game-creator-shell -- agent::direct`(370 passed);新增
|
||||
`stale_cancel_releases_the_occupancy_and_dispatches_the_next_pending_turn` 与两条 `direct_stale_turn_for_release`
|
||||
单测;`npx vitest run tests/appSurface.test.ts`(214 passed / 9 skipped)、`directThreadChat` /
|
||||
`directTurnPresentation` / `project-conversation` 定向用例、`npm --workspace apps/ai-game-creator-shell run typecheck`、
|
||||
`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 2026-09-30 待发消息条目不再另存产物:队列成员由事件折出,prompt 改为条目重投影
|
||||
|
||||
- 背景:09-24 落地时给 `StoredEvent` 加了一个"挂着的队列条目"字段(`Option<PendingDirectTurn>`,
|
||||
归位后是 `agent/thread_manager/mod.rs` 的 `StoredEvent.pending: Option<PendingTurn>`),条目上带七个字段。
|
||||
复核发现其中 `client_turn_id` / `user_item` / `creation_type` / `at` 与 `queue.enqueued` 的载荷是同一份事实的第二份拷贝,
|
||||
`canonical_user_item` 是 `serde_json::to_value(user_item)`(第三份),而这两项在构造事件之后再没被读过;
|
||||
`Some/None` 同时兼着"宿主产物袋"与"在队标记"两职,于是取消 / 放行要写两处事实、回收规则还要加"产物还在就不许回收"的防御。
|
||||
与该 ADR §3 写的"队列的成员与顺序就是事件列表本身"已经不一致。
|
||||
- 决策(队列成员):删掉 `StoredEvent.pending`(`append_inner` 并入 `append`);在队条目 = 事件窗口里"有 `queue.enqueued`、
|
||||
且还没有配对 `queue.removed`"的那些事件,按事件顺序即队首到队尾。判重、容量、取消、认领、bootstrap 全部改读这一次折叠,
|
||||
`mark_queue_events_cleanable` 删掉产物防御。
|
||||
- 决策(prompt):不另存整条 prompt。引用 part 的解析文本(素材摘要 + 引用 UI 设计文档时要展开的代码上下文,
|
||||
后者渲染会往项目里写 `ui/generated-*.js`)由入队检查写进该 part 自己的 `resolved_text`,**随条目持久化**:
|
||||
放行、历史回读、turn input 三个读点共用这一份,历史因此回放出当初那条消息(字段缺省合法、旧历史照样解析,
|
||||
只是在缺省时才退回按当前 manifest 现算)。投影拆成两半:入队侧的 `freeze`(校验 + 算片段 + 写盘 + 冻结 + 判空)
|
||||
与放行侧的纯折叠(零 IO、零校验、无失败出口),于是"放行不重跑任何检查、没有放行失败"这条不变式对新形状仍然成立。
|
||||
`canonical_user_item` 同理改为放行时 `serde_json::to_value(user_item)` 重投影。
|
||||
副作用:引用的代码上下文片段不再统一追加在 prompt 末尾,而是跟在它所属的引用片段里,单条引用时逐字节不变。
|
||||
- 决策(埋点成绩):`analyticsAttemptId` 那条链整条退役,不换字段存。入队化之后一次发送只有一次尝试,
|
||||
"哪一次算数"不再是渲染侧才知道的事;宿主在**放行**那一刻读一次 `platform_session` 的 `identity_generation`、
|
||||
回合终态再读一次,变了就整条不记,成绩直接写终态(不再先写候选、等渲染侧 `settle`)。
|
||||
于是 `analyticsAttemptId` 命令参数、候选表 `pending_runs`、`settle_direct_run_analytics`、前端
|
||||
`beginDirectRunAnalytics` 与句柄表一起删,`queue.enqueued` 也不需要埋点字段。判据口径随放行搬家:
|
||||
从"发送时与终态同代"改成"放行时与终态同代"——入队后、放行前发生的账号切换不再丢弃这一轮的成绩。
|
||||
- 落地(`2b98b022c` 文档 → `15a495dc2` `resolved_text` 与 prompt 拆 freeze / 纯折叠 → `0270cd601` 埋点归宿主
|
||||
→ `28b64cf25` 条目改事件投影):`PendingTurn` 只留 `client_turn_id` / `user_item` / `creation_type` / `at` 并配
|
||||
`from_event` / `enqueued_event` 往返;`StoredEvent` 上没有队列字段,`pending_turns` 一次折叠出成员与顺序;
|
||||
`freeze_direct_codex_user_item` 是入队侧唯一算片段 / 写盘处,`direct_codex_user_item_to_prompt(&item)` 变纯折叠;
|
||||
`direct_finished` 收放行代次并直写 `Request::Terminal`。
|
||||
- 验证:`cargo test -- thread_manager::`(62 passed,进程级计数器用例单线程)、`-- agent::`(948 passed)、
|
||||
`-- direct_runtime:: analytics:: direct_codex_user_item`(182 passed);`analytics::store_tests` 换成"终态直写"
|
||||
与"放行后代际变化不记"两条用例;`npx vitest run tests/appSurface.test.ts`(194 passed)与 chat / direct 定向套件
|
||||
(249 passed);`npm --workspace apps/ai-game-creator-shell run typecheck`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 2026-09-28 渲染层下沉分支与最新 master 对齐:活动回合事实源、维护态出口与诊断详情
|
||||
|
||||
- 背景:`codex/agc-renderer-io-downshift` 把 AGC 渲染层的网络与状态下沉 Rust 之后,要重新落到 master 已经演进出的新形状上。29 处冲突里 25 处是机械取舍,真正的分歧只有三处需要定口径。
|
||||
@@ -9159,7 +9287,7 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- 列表排序(新增能力,同日):`AdminListPanel` 增加 `sortable` / `sortValue` / `sortDescription`——列头渲染与表查询一致的排序按钮(`admin-table-sort-button` + 升/降/双向图标),点击按「正序 → 倒序 → 不排序」循环,同步 `th[aria-sort]`,排序是稳定排序(值相同保持服务端原顺序)。已接入**一次取全**的 5 个列表:邀请码列表、操作记录、灰度 Gate 列表、可配置开关、任务配置列表(这些接口没有分页,前端排序语义正确)。
|
||||
- 列表实现全量收口(同日续做):把剩余 **15 处** `children` 形态列表全部迁到 `columns` + `renderRow`(表体 JSX 原样搬进 `renderRow`,外壳/状态/分页槽位不变):`AdminRedeemCodePage`(2)、`AdminRechargeProductPage`、`AdminProjectSnapshotsPage`、`AdminErrorReportsPage`、`AdminAgcTrackingPage`、`AdminGameDistributionReviewPage`、`AdminGameManagementPage`(主列表 + 版本历史)、`AdminUserDetailDialog`(充值订单)、`AdminRechargeOrderPage`、`AdminAgcModelsPage`、`AdminEditorAssetQueryPage`、`AdminEditorShowcaseReviewPage`、`AdminAgcTemplatesPage`。迁移后全仓统计:`AdminListPanel` **26 处 / 21 个文件**,其中 **24 处 columns 形态**(含 2 处弹窗内 `surface="plain"`),仅剩 2 处非表格列表仍是 children 形态(账号管理卡片列表、账号配置的键值列表);`<AdminTable>` 作为 children 的写法已归零。可排序列累计 **45 个**(新增写入 `AdminAgcTemplatesPage` 列头 4 个:模板/引擎版本/包大小/状态)。
|
||||
- 迁移中顺带处理的形态差异:① `AdminErrorReportsPage` 的详情弹窗原本嵌在列表面板里,随表格一起搬到面板外(遮罩是 fixed,视觉不变);② `AdminAgcModelsPage` 的工具栏与状态行改为走 `toolbar` 槽位(列表面板按 toolbar → 加载行 → 表格渲染,顺序与原来一致);③ `AdminAgcTemplatesPage` 的列表从共享 `ui/Table`(`genarrative-ui-table*`)换成 `AdminTable`,与其它页签视觉统一——该页唯一的 ui Table 只剩「上传模板」弹窗里的待上传队列表(有逐行校验/进度状态的编辑态表格,未纳入列表组件,属有意保留)。
|
||||
- 排序能力边界(重要):后端目前**只有** `GET /admin/api/database/tables/{table}/rows` 与 `GET /admin/api/external-api-keys` 接受 `sortColumn`/`sortDirection`;其余列表在 handler 里写死顺序(例如埋点数据固定 `occurred_at desc`、错误报告按时间倒序)。因此埋点数据、客户端埋点、错误报告、项目工程、充值订单这类**分页明细列表暂时不能排序**——只在前端排「当前页」会给出错误结论,必须给对应接口加排序参数(DTO + handler + `adminApiTypes` + 契约/测试)后前端复用同一列头。账号管理是卡片列表(`children` 形态),本轮未加排序。
|
||||
- 排序能力边界(重要):后端目前**只有** `GET /admin/api/database/tables/{table}/rows` 与 `GET /admin/api/external-api-keys` 接受 `sortColumn`/`sortDirection`;其余列表在 handler 里写死顺序(例如埋点数据固定 `occurred_at desc`、错误报告按时间倒序)。埋点数据、客户端埋点、错误报告、充值订单等分页明细不能只在前端排「当前页」,必须先让对应接口支持全局排序,再复用列头。项目工程采用独立的主动全量读取按钮:当前渠道完整读取成功后按同步时间降序、用户/项目 ID 升序,本地分页;原目录接口及上传/下载契约保持不变,不新增 OSS 索引或数据库表。读取限制为 200 页、10,000 个唯一项目、60 秒,可取消;失败保留原列表,切换渠道/令牌取消请求并销毁临时集合。账号管理是卡片列表(`children` 形态),本轮未加排序。
|
||||
- 本地假数据补齐:`scripts/admin-web-fake-api.mjs` 新增 `agc-models`、`game-distribution/games`、`profile/recharge-products`、`profile/redeem-codes`、`profile/tasks`、`agc/tracking-events` 夹具,并对 `profile/recharge-orders` 按 `AdminRechargeOrderEntryPayload` 的真实字段补齐(缺字段会让页面抛 `Cannot read properties of undefined`——后台没有 error boundary,整页会白屏)。**已知缺口**:充值管理页仍缺一处夹具字段(页面读 `undefined.find`),本轮没能出图;该页自身 21 条单测通过、类型检查通过,仅缺截图。
|
||||
- 排序验证:共享组件新增用例覆盖「正序 / 倒序 / 取消 + aria-sort + 行序」;现场实测邀请码列表按「创建」排序:正序 `EXPIRED-CODE, BETA-CREATOR, TAONIER-VIP-2026`、倒序翻回 `TAONIER…, BETA…, EXPIRED…`,`th[aria-sort]` 依次为 `ascending` / `descending`。
|
||||
- 边界(未完成):未跑生产后台构建与真实后台接口联调(截图用假数据);`apps/admin-web` 目前没有 error boundary,任何接口形状不符仍会把整页渲染清空(本次只加固了 `AdminAgcTrackingPage` 一处,其余页面同类写法未逐个排查);`AdminAgcTemplatesPage` 的列表面板是本次新增的外壳(原页面没有面板),视觉上多了白底卡片。
|
||||
@@ -9177,3 +9305,15 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- 后台 AGC 模型目录新增 `agentMode`,只允许 `codex` / `cc`,缺失的历史目录按 `codex` 兼容;模型选择返回的公开摘要同步携带该绑定。
|
||||
- 客户端把后台 `codex` 映射到现有 Codex app-server,把 `cc` 映射到独立 Claude Code CLI adapter;不通过替换 Codex JSON-RPC 可执行文件实现。
|
||||
- Claude Code 只使用隔离环境和 AGC loopback MCP,禁用原生工具;取消通过独立 Direct 回合进程树回收处理。Codex、provider 和自定义 Responses 链路保持原路径。
|
||||
|
||||
## 2026-09-30 合入 master 时把 Claude Agent SDK sidecar 归位到随包资源准备步骤
|
||||
|
||||
- 背景:master `fb130d184` 新增 `cc` 执行模式与 Claude Agent SDK sidecar,sidecar 的 staging 写在 `build.rs`(构建期 `remove_dir_all` + 从 `node_modules/@anthropic-ai/**` 复制 `resources/claude-agent`),同时把 `resources/claude-agent` 映射进**基线** `tauri.conf.json`。本分支的 M1–M3(issue #519)已把「构建期写随包资源」定性为结构问题,合入时必须按同一套架构落地,不能把写入分支带回来。
|
||||
- 决策(归位实现):新增声明 section `claudeAgent`(锁定版本、资源目录、sidecar 入口源码、上游 SDK 包与平台原生运行时包的目标表、复制跳过规则)。Node 准备步骤整目录原子替换 staging,缓存 key = `layoutVersion + target + 声明版本 + 入口摘要`,命中即零写入;`build.rs` 只读校验:入口与仓库源码逐字节一致、SDK 与原生运行时 `package.json` 版本等于声明、原生运行时在位(unix 还要求可执行位)、随包目录里没有白名单外的文件。
|
||||
- 决策(版本单一真源):`CLAUDE_AGENT_SDK_VERSION` 由声明生成;`claude_code_cli.rs` 的 sidecar 身份串改用 `cargo:rustc-env=AGC_CLAUDE_AGENT_SDK_VERSION`,门禁断言声明版本等于 `apps/ai-game-creator-shell/package.json` 与 `agent-sidecar/package.json` 锁定的 `@anthropic-ai/claude-agent-sdk`。
|
||||
- 决策(平台映射从基线配置移到平台配置):`resources/claude-agent` 由 `tauri.conf.json` 移入 `tauri.windows.conf.json` 与 `tauri.macos.conf.json`。基线配置同时服务 Linux——CI 只在那里编译壳 crate 且按设计不装 npm 依赖,而 `tauri-build` 会把 `bundle.resources` 的每个路径拷进 target、缺失即失败;留在基线等于要求一份只有 Windows/macOS 才产出的资源。`check-config.mjs` 增加「基线不得声明 `resources/**`」的守卫。
|
||||
- 决策(删掉自带的清理逻辑):不保留 master 的 `prune_stale_codex_components`。准备步骤对 staging 单元整目录原子替换已经清掉旧布局残留,构建期另有「白名单外的文件」断言;构建脚本不再删任何人的文件。
|
||||
- 影响面:`src-tauri/build_support/{package-layout.json,package-layout.generated.rs,package_layout.rs}`、`src-tauri/build.rs`、`scripts/{prepare-bundled-resources.mjs,prepare-bundled-resources.test.mjs,check-package-layout.mjs,check-config.mjs}`、三份 tauri 配置、`src/agent/claude_code_cli.rs`、技术方案 §4.5/§4.9、排障记录。
|
||||
- 验证:`npm run agc:bundled-resources:test`(18 passed,含 sidecar 归位、跳过规则、上游缺失、版本漂移、目录被占);`npm run agc:bundled-resources:check`、`check-config.mjs`、`cargo test --bin genarrative-ai-game-creator-shell package_layout::tests`、`cargo check --no-default-features`、`cargo fmt --check`、`check:encoding`、eslint/prettier 全部通过;Windows 真机准备步骤 staging 24 个文件(含 243MB `claude.exe`)后 `cargo check` 不再出现构建期写入。
|
||||
- 边界(未验证):macOS 真机的 sidecar 加载与 `check-macos-bundle.mjs` 包内容门禁未在本机验证;Linux 门禁按新配置不再要求 sidecar 资源,需 CI 实跑确认转绿。
|
||||
- 关联:issue #519、master `fb130d184`、`docs/technical/【技术方案】AGC随包资源staging归位-2026-09-26.md`、CI run 3083。
|
||||
|
||||
@@ -114,4 +114,4 @@ Gitea 缓存部署必须区分网络:runner 的 RPC 走 `gitea-runner-fetch-ga
|
||||
|
||||
AGC Rust 两条 lane、crates、smoke、Backend 和桌面壳测试使用镜像内可信 sccache 对象快照;Native shell release step 显式清空双 wrapper,前端/repository checks 不启用。仅首次人工 bootstrap 时,维护者通过 `scripts/build-gitea-rust-cache.sh` 从远端 master 在限额、无宿主挂载的临时容器中按实际 cwd/profile/目标预热全部测试组,仅编译、不执行测试/应用;后端 workspace 与 spacetime-module 保持独立,AGC 的三个 cwd 入口之间清理预热 target,防止 fresh 判断漏产缓存键。最终镜像只追加 sccache、对象和来源元数据,不包含源码或 target。容量上限 4 GiB,不替代宿主旧镜像/归档清理。PR 只写当前容器层、不回传,不开放 Docker API/发布权限;继续禁用 incremental。`ci-rust-cache.sh` 在快照缺失、工具链不符或 wrapper 探测失败时直接编译,并隔离远程缓存配置和 daemon。分片日志记录编译耗时,收尾输出命中统计;两个 lane 的测试和前置检查不同,耗时差不是严格 A/B。线上存在活跃 CI 时不得重启 runner 或切换标签;全组启用前须刷新完整快照并逐组验证,详见开发运维文档。
|
||||
|
||||
`.gitea/workflows/project-ci.yml` 的客户端门禁拆成 lane 与功能 job,每个 job 只预热自己会构建的那几份依赖:`AI game creator shell Rust lane 1/2`、`lane 2/2` 各自预取一次 AGC 壳 manifest,并顺序运行两片 Rust bin 单测;`AI game creator shell Rust smoke` 同样只预取 AGC 壳 manifest(`agent-run` smoke 会用 `src-tauri/Cargo.toml` spawn `cargo run`),`AI game creator shell Rust crates` 预取 `server-rs/Cargo.toml` 与独立 crate,`Native shell tests` 预取桌面壳与 AGC 壳 manifest,`AI game creator shell web tests` 不触碰 Cargo,不预热。两条 Rust lane、smoke job 与 crates job 只用 cargo 与 node 内建模块,因此不执行 `npm ci`。两个被 `server-rs/Cargo.toml` 排除、且没有提交 `Cargo.lock` 的独立 crate(`agent-runtime-core`、`agent-runtime-orchestration`)只能在 `AI game creator shell Rust crates` 里用不带锁标志的 fetch。AGC 壳的 bin target 单测(约 2466 条)由 `apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.mjs` 编译后按 `--list` 名单分 4 片:每次分片调用用 `--shard-index=<i>` 只跑自己那片,片内保持 `--test-threads=1` 并使用独立 `TMPDIR`;两条 lane 之间并发,lane 内顺序运行两片,避免重复依赖预热和同一容器内多进程争抢。不要改回「一个 job 内多进程并行这几片」——同一容器里它们会争抢共享 `HOME`、target 目录与固定临时路径,实测比整套串行还慢。每个分片调用都会自校验「片并集等于全集且互斥」,因此改分片规则不会静默漏跑。Backend host workspace tests 使用 `cargo test --locked --workspace --exclude spacetime-module --no-fail-fast`,避免 `spacetime-module` 的 `spacetime-types` feature 统一污染普通领域 crate 的 host 测试;随后单独执行 `cargo test --locked -p spacetime-module --no-fail-fast`,由 `spacetime-module/src/active.rs` 在 host 测试构建期间提供仅测试期的 SpacetimeDB ABI 链接支持,使该 crate 的纯单元测试也纳入 Backend 门禁。`spacetime-module` 的 reducer / procedure 运行时行为仍必须通过真实 SpacetimeDB runtime/integration harness 验证,host 链接支持不得被当作运行时替身。Backend 另外执行 `cargo check --locked -p spacetime-module` 验证模块源码。AGC 壳检查还会运行 `platform-llm` 与 `shared-contracts` 的 server-rs workspace 测试,这些命令以及 AGC 壳测试必须带 `--locked`,避免在测试阶段重新解析 registry index;锁文件发生变化时应先更新受信任 CI 镜像缓存,再重跑门禁。
|
||||
`.gitea/workflows/project-ci.yml` 的客户端门禁拆成 lane 与功能 job,每个 job 只预热自己会构建的那几份依赖:`AI game creator shell Rust lane 1/2`、`lane 2/2` 各自预取一次 AGC 壳 manifest,并顺序运行两片 Rust bin 单测;`AI game creator shell Rust smoke` 同样只预取 AGC 壳 manifest(`agent-run` smoke 会用 `src-tauri/Cargo.toml` spawn `cargo run`),`AI game creator shell Rust crates` 预取 `server-rs/Cargo.toml` 与独立 crate,`Native shell tests` 预取桌面壳与 AGC 壳 manifest,`AI game creator shell web tests` 不触碰 Cargo,不预热。两条 Rust lane 与 smoke job 必须在编译前通过 `scripts/ci-npm-ci-with-retry.sh` 执行根 `npm ci`:AGC 壳的 `build.rs` 会从 `node_modules` 准备 Claude Agent SDK 与目标平台原生运行时,镜像中的 npm 下载缓存不能替代安装。人工 `scripts/build-gitea-rust-cache.sh` bootstrap 同样在首次编译 AGC 壳前安装 npm 依赖;只有不构建壳的 crates job 继续省略 npm 安装。两个被 `server-rs/Cargo.toml` 排除、且没有提交 `Cargo.lock` 的独立 crate(`agent-runtime-core`、`agent-runtime-orchestration`)只能在 `AI game creator shell Rust crates` 里用不带锁标志的 fetch。AGC 壳的 bin target 单测(约 2466 条)由 `apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.mjs` 编译后按 `--list` 名单分 4 片:每次分片调用用 `--shard-index=<i>` 只跑自己那片,片内保持 `--test-threads=1` 并使用独立 `TMPDIR`;两条 lane 之间并发,lane 内顺序运行两片,避免重复依赖预热和同一容器内多进程争抢。不要改回「一个 job 内多进程并行这几片」——同一容器里它们会争抢共享 `HOME`、target 目录与固定临时路径,实测比整套串行还慢。每个分片调用都会自校验「片并集等于全集且互斥」,因此改分片规则不会静默漏跑。Backend host workspace tests 使用 `cargo test --locked --workspace --exclude spacetime-module --no-fail-fast`,避免 `spacetime-module` 的 `spacetime-types` feature 统一污染普通领域 crate 的 host 测试;随后单独执行 `cargo test --locked -p spacetime-module --no-fail-fast`,由 `spacetime-module/src/active.rs` 在 host 测试构建期间提供仅测试期的 SpacetimeDB ABI 链接支持,使该 crate 的纯单元测试也纳入 Backend 门禁。`spacetime-module` 的 reducer / procedure 运行时行为仍必须通过真实 SpacetimeDB runtime/integration harness 验证,host 链接支持不得被当作运行时替身。Backend 另外执行 `cargo check --locked -p spacetime-module` 验证模块源码。AGC 壳检查还会运行 `platform-llm` 与 `shared-contracts` 的 server-rs workspace 测试,这些命令以及 AGC 壳测试必须带 `--locked`,避免在测试阶段重新解析 registry index;锁文件发生变化时应先更新受信任 CI 镜像缓存,再重跑门禁。
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
这里只记录对当前开发仍有用的症状、根因、排查方法和风险边界。同一事实保留一个当前口径;退役对象的专属过程与单轮测试结果由 Git 历史追溯。遇到旧路径或版本时,以现行代码和专题文档为准。
|
||||
|
||||
## 2026-09-30 构建期 staging 撞上不装 npm 依赖的 Linux 门禁:AGC 壳 Rust lane 全红
|
||||
|
||||
- **现象**:`Project CI` 的 AGC 壳 Rust 三条 lane(`npm run check:native-shells:agc-rust-shard-*`)在 `fb130d184` 之后全部失败,日志只有 `error: failed to run custom build command for genarrative-ai-game-creator-shell` 与 `thread 'main' panicked at build.rs:65:28: Claude Agent SDK 缺失;请先执行 npm ci`(run 3083 / job 17521 实测,1 分钟即失败)。
|
||||
- **原因**:Claude Agent SDK sidecar 的 staging 写在 `build.rs`(构建期 `remove_dir_all` + 从 `node_modules/@anthropic-ai/**` 复制),而这三条 lane 按设计**不装 npm 依赖**(`scripts/project-ci-workflow.test.ts` 的 `jobsWithoutNpmInstall` 显式允许它们没有 `node_modules`),构建脚本一跑就必 panic。同一批改动还把 `resources/claude-agent` 映射进**基线** `tauri.conf.json`:`tauri-build` 会把 `bundle.resources` 的每个路径拷进 target,缺失即 fail(`tauri-utils` 的 `ResourcePathNotFound`),所以即使绕开 panic,Linux 也会在资源解析处再红一次。
|
||||
- **处理(现行口径)**:随包资源一律由准备步骤在 `tauri dev|build` 之前 staging,`build.rs` 只读校验(sidecar 走声明 section `claudeAgent` + `scripts/prepare-bundled-resources.mjs`);平台专属资源只允许出现在 `tauri.<platform>.conf.json`,基线 `tauri.conf.json` 里不得出现 `resources/**`——基线同时服务不产出客户端包的 Linux,`check-config.mjs` 已加该守卫。
|
||||
- **判据/取证**:`node --test apps/ai-game-creator-shell/scripts/prepare-bundled-resources.test.mjs`、`node apps/ai-game-creator-shell/scripts/check-config.mjs`;Linux 侧判据是三条 AGC Rust lane 转绿且构建期不再出现 `Claude Agent SDK 缺失`。
|
||||
- **关联**:`apps/ai-game-creator-shell/src-tauri/build.rs`、`apps/ai-game-creator-shell/scripts/{prepare-bundled-resources.mjs,check-config.mjs}`、`apps/ai-game-creator-shell/src-tauri/{tauri.conf.json,tauri.windows.conf.json,tauri.macos.conf.json}`、`.gitea/workflows/project-ci.yml`、CI run 3083。
|
||||
|
||||
## 2026-09-30 Jenkins release 渠道环境污染 AGC 构建单测
|
||||
|
||||
- **现象**:Jenkins `Genarrative-Agc-MacOS-Build` 的 release lane 在执行 `build-release.test.mjs` 时,`release stages Node before Tauri...` 用例报 `Cannot read properties of undefined (reading 'nsis')`。
|
||||
@@ -59,8 +67,17 @@
|
||||
- **处理**:两侧都补了保留上限。服务端:`production-api-deploy.sh` 新增 `--keep-releases`(默认 `2`),发布成功后保留 `current` 目标与最近 1 个历史 release,只删除同时含 `api-server` 或 `web` 标记的旧目录,清理失败只告警、不改变发布结论。CI 侧:`Genarrative-Api-Deploy` / `Genarrative-Web-Deploy` / `Genarrative-Stdb-Module-Publish` 在各自部署 / 发布步骤成功后只保留最近 2 个 `build/<version>/`,失败时不清理以便诊断和重跑。`npm run check:production-api-deploy` 增加默认值、显式值和非法值三类夹具,`npm run check:production-ops` 增加对应合同。
|
||||
- **不要踩的坑**:直接按 mtime 排序删除会连带删掉发布根目录下不属于发布产物的目录(例如 `dev-mcp-host-*`),必须用 `api-server`/`web` 标记筛选;脚本里的 `mv -T`、`find -printf` 都是 GNU 语义,`npm run check:production-api-deploy` 需要 `sha256sum` 和 `/usr/bin/cp`,Windows 本地跑不了,只在 Linux CI / Linux 检出上有效(本地最低限度用 `bash -n` + `npm run check:production-ops`)。
|
||||
- **写 Jenkins 内联 shell 的两个坑**:① Groovy 会处理 `sh '''…'''` / `sh """…"""` 里的反斜杠转义——`\n` 到 shell 手上会变成真实换行(`Jenkinsfile.production-stdb-module-build` 里必须写 `printf "\\r"` 就是同一件事),所以内联片段要么完全不用 `\`,要么写 `\\`;`"""` 是 GString,shell 变量必须写 `\$name`,而 `'''` 不插值、保持 `${name}`。② `set -euo pipefail` 下 `ls build/*/` 在 glob 不匹配时会因 pipefail 把整个部署步骤判失败(实测:`build/` 为空时清理步骤会把一次成功发布判成失败),必须用 `if [ -d build ]` 守卫 + `|| true` 兜底。
|
||||
- **GString 里的命令替换同样要转义**:`sh """…"""` 内的 shell 命令替换必须写 `\$(...)`。写成裸 `$(...)` 时 Jenkins/Groovy 会在加载 Jenkinsfile 时直接报 `illegal string body character after dollar sign`,构建不会进入任何 stage;`npm run check:production-ops` 现已钉住 Stdb Publish 的暂存清理命令。
|
||||
- **关联**:`scripts/deploy/production-api-deploy.sh`、`scripts/check-production-api-deploy.mjs`、`scripts/check-production-ops-guardrails.mjs`、`jenkins/Jenkinsfile.production-api-deploy`、`jenkins/Jenkinsfile.production-web-deploy`、`jenkins/Jenkinsfile.production-stdb-module-publish`。
|
||||
|
||||
## 2026-09-30 AGC macOS 包内容门禁必须识别随包 Claude Agent SDK
|
||||
|
||||
- **现象**:`Genarrative-Agc-MacOS-Build #80` 已成功生成并核对 `陶泥儿开发版.app` 的身份与版本,却在 `check-macos-bundle.mjs` 的 resources 白名单断言处失败,Jenkins 只打印一条无文件名的 `AssertionError`。
|
||||
- **原因**:新增 Claude Agent SDK sidecar 后,构建会把 SDK 与匹配平台的 Claude runtime 放到 `claude-agent/node_modules/@anthropic-ai/`;macOS 包内容门禁仍按旧口径把除 Node runtime npm 以外的所有 `node_modules` 都判为禁止。
|
||||
- **处理**:把包内容策略抽成纯函数,只放行 `game-runtime/node/node_modules/npm` 和 `claude-agent/node_modules/@anthropic-ai/claude-agent-sdk[-darwin-*]` 两棵明确子树;同时显式要求 sidecar 入口、SDK 与平台 runtime 存在,并让违规时输出具体相对路径。
|
||||
- **验证**:`node --test apps/ai-game-creator-shell/scripts/macos-release-identity.test.mjs apps/ai-game-creator-shell/scripts/prepare-macos-codex.test.mjs`;macOS 实包再由 `check-macos-bundle.mjs` 复核。
|
||||
- **关联**:`apps/ai-game-creator-shell/scripts/macos-bundle-policy.mjs`、`apps/ai-game-creator-shell/scripts/check-macos-bundle.mjs`、`apps/ai-game-creator-shell/scripts/macos-release-identity.test.mjs`、`apps/ai-game-creator-shell/src-tauri/build.rs`、`jenkins/Jenkinsfile.ai-game-creator-shell-macos-build`。
|
||||
|
||||
## 2026-09-29 dev 上的 JNLP inbound agent 是历史残留,会在死端口上无限重连刷爆 syslog
|
||||
|
||||
- **现象**:dev 的 `/var/log/syslog` 约 250MB/天,内容是 `jenkins-inbound-agent-start[pid]` 反复输出指向 `http://127.0.0.1:18080/tcpSlaveAgentListener/` 的 `Connection refused` 完整栈(2.6 天 61 万行,其中 `genarrative-release-deploy-01` 占 53 万行)。
|
||||
@@ -103,6 +120,23 @@
|
||||
- **leader 卡死的兜底**:闸门只有 follower 的有界等待(60s),若提权子进程真的挂死(`Start-Process -Wait` 无超时),`leader_deadline`(5 分钟)之前该 key 一直被占住,之后新调用会接管并按新 leader 执行;被接管后旧 leader 迟到的结果按令牌丢弃,不会覆盖接管者。`clear_game_creator_acl_elevation_denials` 只清「被拒绝」记忆,不清理 running。
|
||||
- **关联**:`src-tauri/src/acl_repair_gate.rs`、`src-tauri/src/config.rs`、issue #498。
|
||||
|
||||
## 2026-09-29 随包资源的编译产物摘要不可复现,且准备步骤与应用构建共用同一输出路径
|
||||
|
||||
- **摘要不可复现**:同一 source / feature / profile / target 连续构建的 `cocos-editor-bridge` payload 摘要不同(除 PE `TimeDateStamp` 外还有 RSDS GUID 等 22 字节差异),所以「与迁移前逐项一致」只能对**源码派生物**(JS/HTML/JSON/license/notice,逐字节比对)、**.NET publish 产物**(Unity helper 跨两次重新发布逐字节一致)和**命中工具链内部缓存的产物**(Godot 走 `buildId` 早退,不重链)成立。核对打包一致性时不要用编译产物的 sha256 判回归,改比路径集合 + 导出面(`DllMain`、`cocos_editor_bridge_bootstrap_source`)+ 源码派生物摘要。
|
||||
- **共用输出路径**:准备步骤的 `cocos-bridge-build` 用 `cargo build -p cocos-editor-bridge --features windows-injection`,而应用构建带的是 `windows-bootstrap + windows-injection`(`cocos-editor-injection` 的闭包),两个单元写同一个 `target/<triple>/<profile>/deps/cocos_editor_bridge.dll`。后构建的单元覆盖先构建的产物时,准备步骤的候选查找会取到「上一次遗留的另一个单元」,交替构建还会多一次重链。要改就从这里改:让准备步骤用独立 target 目录,或与应用的 feature 集对齐。
|
||||
- **验证方式**:`runTauriBuild`(`scripts/build-release.mjs`)+ `--bundles nsis`,再 `7z x` 解包比 `plugins/**`;准备步骤连续三次复跑要求 `resources/plugins` 的 32 个文件内容与 mtime 全不变。
|
||||
|
||||
## 2026-09-27 随包资源的写入方按产物来源分界:源码派生直接复制,需工具链的先由准备步骤产出
|
||||
|
||||
- **写法**:新增随包内容先判断来源——能从仓库源码复制就写进 `build_support/package-layout.json` 的 `subdirectories`(`origin: source`);需要外部工具链或同一次 cargo 构建才能产出的,写成 `origin: prepared` / `libraryStaging` / `nativePayloads`,并在 `plugins.prepareSteps` 里声明要跑的程序、工作目录、指纹与必需产物——**不要写进构建脚本**(构建脚本自 M3 起只做只读校验,不再生成任何随包资源)。
|
||||
- **校验口径**:`origin: source` 的内容在构建期会与仓库源码逐文件比对(插件清单 + 逐文件 sha256 + 整树符号链接),手改这部分会被 `cargo build` 直接拒绝;`origin: prepared` 只查存在性(Godot 随包库额外跑 `godot_bundle::validate`),手改 prepared 产物不会被拒,要改就改准备步骤的来源或声明。
|
||||
- **准备步骤指纹**:声明了指纹的步骤(Unity)命中后不会重跑工具链,改 `plugins/**` 源码即失效;指纹戳文件(`publish/win-x64/.agc-source.sha256`)删掉只会多跑一次构建。`resources/plugins` 由准备步骤拥有,不要手工往里放文件。
|
||||
|
||||
## 2026-09-27 AGC 随包资源的布局只能改声明文件,生成物由门禁锁死
|
||||
|
||||
- **现象**:直接编辑 `apps/ai-game-creator-shell/src-tauri/build_support/package-layout.generated.rs`,或另写一份组件白名单,`npm run agc:typecheck`(链内含 `npm run agc:bundled-resources:check`)会立刻失败并报「随包资源声明与 Rust 常量不一致」。
|
||||
- **正确做法**:改 `build_support/package-layout.json`,运行 `npm run agc:bundled-resources:sync` 重新生成;改布局同时递增 `layoutVersion`(参与准备步骤的缓存 key)。声明里的 `codex.version` 必须与应用锁定的 `@openai/codex` 一致,门禁会对照 `apps/ai-game-creator-shell/package.json` 校验。
|
||||
- **边界(M1 完成时)**:准备步骤 `scripts/prepare-bundled-resources.mjs` 尚未接入 dev / 发布入口,`npm run agc` 仍由构建脚本 staging;构建脚本当前既写资源又做只读校验,`AGC_SKIP_RESOURCE_STAGING=1` 可只跑校验。构建脚本重建 `resources/plugins` 时会整体删除该目录,所以插件侧的准备步骤清单要等 M2 接管写入后才成立,插件目录现在只校验必需组件与符号链接。
|
||||
## 2026-09-24 模型输出的围栏会粘在正文行里:聊天 Markdown 必须先归一化再解析
|
||||
|
||||
- **现象**:AGC 对话里代码块解析错位——引言行被当成代码渲染(`…实现细节(game.js):```js`),或者代码块收不住、把后面的正文一起吞进去(`… return centerOn(projection); }````)。文本本身「看起来没问题」,容易被当成渲染器坏了。
|
||||
@@ -2967,9 +3001,9 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
|
||||
- 现象:Cargo 报 `could not execute process sccache ... rustc.exe -vV (never executed)`、`sccache: error: Timed out waiting for server startup`,或 `sccache: caused by: Failed to send data to or receive data from server / Failed to read response header / failed to fill whole buffer`;真实 `rustc -Vv` 可以执行,但构建在调用包装器时失败。
|
||||
- 原因:环境、Jenkinsfile 或 `server-rs/.cargo/config.toml` 启用了 `sccache` wrapper,但当前 agent 没有可执行的 `sccache`、PATH 中 shim 损坏,或本地 sccache server/client 通道状态损坏。Windows 本机若配置了 `SCCACHE_OSS_*`,sccache daemon 冷启动会先经 OSS/本机代理完成缓存读写检查,再监听 `127.0.0.1:4226`;代理或 OSS 链路慢时,Cargo 的 `sccache rustc -vV` 可能先超时。
|
||||
- 处理:保留 `server-rs/.cargo/config.toml` 的 `rustc-wrapper = "sccache"`;本地 `npm run dev` / `npm run dev:spacetime` / `npm run dev:api-server` 在 Windows 下限时执行真实 wrapper 探测 `sccache rustc -vV`,成功才启用 sccache,缺少命令、daemon 启动超时或 wrapper 返回非零时立即给 Rust 子进程注入空 wrapper,回退到直接 rustc,避免损坏的 daemon 阻断启动;显式设置的非 sccache 自定义 wrapper 会被保留。Windows 本机优先在 `%APPDATA%\Mozilla\sccache\config\config` 写入 `server_startup_timeout_ms = 60000`,拉长 client 等待 daemon 完成 OSS 初始化的时间,然后删除 `server-rs/target/.rustc_info.json` 里缓存的失败探测结果并重跑原始 Cargo 命令。冷启动验证优先用 `sccache --stop-server`,不要在另一个 `cargo` / `rustc` 仍在编译时 `taskkill /F /IM sccache.exe /T`,否则 proc-macro crate 可能被打断并表现为 `serde_derive` / `spacetimedb-bindings-macro` 的 `sccache ... exit code: 1`。若只做临时排障,可在 Git Bash 中执行 `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo build ...`,或在 PowerShell 用 `cargo check -p api-server --config "build.rustc-wrapper=''"` 一次性绕过 wrapper;生产流水线必须先实际执行 `sccache --version`,失败时移除 `RUSTC_WRAPPER` 并回退到直接 `rustc`。
|
||||
- 处理:保留 `server-rs/.cargo/config.toml` 的 `rustc-wrapper = "sccache"`;本地 `npm run dev` / `npm run dev:spacetime` / `npm run dev:api-server` 在 Windows 下限时执行真实 wrapper 探测 `sccache rustc -vV`,成功才启用 sccache,缺少命令、daemon 启动超时或 wrapper 返回非零时立即给 Rust 子进程注入空 wrapper,回退到直接 rustc,避免损坏的 daemon 阻断启动;显式设置的非 sccache 自定义 wrapper 会被保留。`npm run agc` 的 Tauri Cargo 原先直接继承启动器环境,用户级 `~/.cargo/config.toml` 的 `rustc-wrapper` 会在这里生效并复现同一故障(表现为 `failed to run rustc to learn about target-specific information`,AGC 前端与配套后端已经起来、只有 Tauri 客户端退出);现在 `start-tauri-dev.mjs` 在启动 Tauri CLI 前调用 `scripts/dev.mjs` 的 `buildLocalRustProcessEnv`,把两个 wrapper 变量显式写进子进程环境——空环境变量同样能覆盖 Cargo 配置文件里的 wrapper,不能只依赖「本机没配 sccache」。Windows 本机优先在 `%APPDATA%\Mozilla\sccache\config\config` 写入 `server_startup_timeout_ms = 60000`,拉长 client 等待 daemon 完成 OSS 初始化的时间,然后删除 `server-rs/target/.rustc_info.json` 里缓存的失败探测结果并重跑原始 Cargo 命令。冷启动验证优先用 `sccache --stop-server`,不要在另一个 `cargo` / `rustc` 仍在编译时 `taskkill /F /IM sccache.exe /T`,否则 proc-macro crate 可能被打断并表现为 `serde_derive` / `spacetimedb-bindings-macro` 的 `sccache ... exit code: 1`。若只做临时排障,可在 Git Bash 中执行 `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo build ...`,或在 PowerShell 用 `cargo check -p api-server --config "build.rustc-wrapper=''"` 一次性绕过 wrapper;生产流水线必须先实际执行 `sccache --version`,失败时移除 `RUSTC_WRAPPER` 并回退到直接 `rustc`。
|
||||
- 验证:`rustc -Vv` 能输出版本;本地 `npm run dev` 能完成 `spacetime publish`、`api-server` `/healthz`、主站 Vite 和后台 Vite 启动;冷启动后原始 `cargo check -p api-server` 和 `cargo check -p spacetime-module` 能通过;`sccache --show-stats` 显示 `Cache location oss, name: genarrative-sccache`,证明原始 Cargo/Jenkins 路径仍可使用 sccache/OSS 缓存;Jenkins 日志出现“未找到可用 sccache,改用 rustc 直接构建”后仍继续真实构建。
|
||||
- 关联:`scripts/dev.mjs`、`jenkins/Jenkinsfile.production-stdb-module-build`、`docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md`、`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。
|
||||
- 关联:`scripts/dev.mjs`、`apps/ai-game-creator-shell/scripts/start-tauri-dev.mjs`、`jenkins/Jenkinsfile.production-stdb-module-build`、`docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md`、`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。
|
||||
|
||||
## 生产发布入口不要沿用旧 Jenkinsfile / 一体化脚本
|
||||
|
||||
@@ -6073,7 +6107,7 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
- **现状(正确)**:`src/services/appUpdate.ts` 的 `installAppUpdate` 先 `await update.downloadAndInstall(...)`、成功后才清空待装更新并 `restartAppAfterUpdate()`;失败时保留待装更新,重试走同一条链路。
|
||||
- **判据**:`apps/ai-game-creator-shell/tests/appUpdate.test.ts` 新增「签名校验失败时拒绝安装、不重启进程,并保留待装更新供重试」——插件抛 `signature verification failed` 时断言 ①错误原样上抛 ②`restart_agc_app` 未被调用 ③再次安装仍会走插件调用并在成功后重启。变异验证:把 `restartAppAfterUpdate()` 挪到 `await` 之前,该用例立即以 `expected "spy" to not be called with arguments: [ 'restart_agc_app' ]` 变红。
|
||||
- **边界**:真正的验签与临时文件清理都在官方插件原生实现里,本地只能证明"客户端不把失败当成功",真机安装闭环仍需已发布包与真实设备。
|
||||
- **顺带记一条环境陷阱(2026-09-28 已修)**:`apps/ai-game-creator-shell/src-tauri/resources/codex/win-x64/` 下曾有两个 codex 二进制——`bin/codex.exe` 是**真正被解析**的那份(0.155.1),而包根目录那份 `codex.exe` 是 0.147.0 的旧残留(tauri 的 Windows 资源映射只引用 `bin/` 等路径),检查都查不出来,却会让本地核对误判「应用跑的是 0.147.0」。根因是 `src-tauri/build.rs` 的 `stage_codex_target()` 只按布局拷贝、从不清理目录,旧布局的组件会永久留在随包资源目录里。现在加了 `prune_stale_codex_components()`:拷贝前删掉不在本轮布局、也不在 `manifest.json`/`NOTICE.md` 白名单里的文件并收掉空目录;实测重建后根目录 `codex.exe` 被清掉、六个声明组件与清单/声明保留。
|
||||
- **顺带记一条环境陷阱(2026-09-28 已修)**:`apps/ai-game-creator-shell/src-tauri/resources/codex/win-x64/` 下曾有两个 codex 二进制——`bin/codex.exe` 是**真正被解析**的那份(0.155.1),而包根目录那份 `codex.exe` 是 0.147.0 的旧残留(tauri 的 Windows 资源映射只引用 `bin/` 等路径),检查都查不出来,却会让本地核对误判「应用跑的是 0.147.0」。根因是构建脚本只按布局拷贝、从不清理目录,旧布局的组件会永久留在随包资源目录里。**现行口径(2026-09-30 起)**:随包资源改由准备步骤整目录原子替换(`scripts/prepare-bundled-resources.mjs` 的 `stageAtomically`),旧布局残留随替换消失;构建脚本只剩只读校验,遇到白名单外的文件会立即失败,所以「本机留着旧组件」最多表现为一次可读的失败,不会再静默随包。(2026-09-28 加的构建期 `prune_stale_codex_components()` 已随 M3 退役,实现不再存在。)
|
||||
|
||||
## 2026-09-29 Vite dev 冷启动会让 web E2E 的首个 goto 超时,别当成页面回归
|
||||
|
||||
@@ -6105,7 +6139,7 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
|
||||
## 2026-09-29 用夹具直接验证「已安装渠道包里的随包 Codex 能不能跑」(不用开 GUI)
|
||||
|
||||
- **做法**:`npm run check:agc-direct-execution-fixture -- --agc-exe "<已安装渠道包的主程序绝对路径>" --cases completed`。夹具在临时项目和 loopback Provider 上跑 CLI 执行链路,可直接核对已安装包的随包 Codex。
|
||||
- **做法**:`npm run check:agc-direct-execution-fixture -- --agc-exe "<已安装渠道包的主程序绝对路径>" --cases completed`。夹具在临时项目和 loopback Provider 上跑 CLI 执行链路,可直接核对已安装包的随包 Codex。(该入口、夹具与 `--direct-codex-chat` CLI 已随入队化退役删除,见 09-24 实施计划第 4 步;这里记的是当时的做法。)
|
||||
- **判读**:若报 `Codex app-server JSON-RPC 失败`,sidecar 已启动并返回协议错误,应先检查请求体及已安装包的源码版本;若根本无法启动,再查安装目录 `coding-agent/win-x64/manifest.json` 的组件哈希、`codex-package.json` 的版本和缺失组件。夹具模式中的 `remote_control disabled reason=provider-proxy-auth` 是预期诊断行。
|
||||
|
||||
## 2026-09-30 居中溢出叠加内部滚动,会把顶部内容裁到滚不到的地方(游玩页启动面板)
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
# DirectProject 命令入队化与待发消息队列归宿主实施计划
|
||||
|
||||
更新时间:`2026-09-24`
|
||||
|
||||
状态:**第 0–7 步全部落地**(2026-09-30 修订并收口)
|
||||
|
||||
> 文件路径口径(2026-09-30):本文里 2026-09-24 写的 `direct_thread_manager.rs` / `direct_thread_wire.rs` /
|
||||
> `direct_thread_queue.rs` / `direct_turn_dispatch.rs` 现在分别是 `agent/thread_manager/{mod,wire,queue,dispatch}.rs`,
|
||||
> 类型与函数去掉 `Direct` 前缀(`ThreadEvent`、`PendingTurn`、`subscribe_thread` …,见提交 `f121257cc`)。
|
||||
> 同一次归位里 thread_manager 之外的几个函数也去掉了 `direct_` 前缀:`direct_active_turn_id_at` → `active_turn_id_at`、
|
||||
> `direct_stale_turn_for_release` → `stale_turn_for_release`、`kick_direct_queue_dispatch` → `kick_queue_dispatch`、
|
||||
> `complete_direct_thread_turn` → `complete_turn`。
|
||||
|
||||
设计口径见 [`【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24`](../adr/【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md)。
|
||||
本文件只排实施顺序、不变式与验收,不重复设计理由。
|
||||
|
||||
## 落地进度
|
||||
|
||||
| 步骤 | 状态 | 落地说明 |
|
||||
| --- | --- | --- |
|
||||
| 第 0 步 词表切换 | 已落地 | `rg "接单\|拒单"` 只剩 `direct_runtime/mod.rs` 对旧 ADR 文件名的引用(链接完整性,故意保留)与 `codex_app_server/mod.rs` 的一处假阳性 |
|
||||
| 第 1 步 命令 = 入队 | 已落地 | `enqueue_direct_codex_turn`(`+_typed`);队列条目落在 `agent/direct_thread_queue.rs`(第 6 步改为事件投影,不再另存 `prompt` / canonical 形状) |
|
||||
| 第 2 步 队列归 Thread Manager | 已落地 | `StoredEvent.pending` 产物字段 + `enqueue_pending_turn` / `remove_pending_turn` / `claim_pending_turn`;放行在 `agent/direct_turn_dispatch.rs`(`kick_direct_queue_dispatch` + `DirectTurnReservation`) |
|
||||
| 第 3 步 前端收口 | 已落地 | 见下面「第 3 步的落地细则」 |
|
||||
| 第 4 步 CLI 与夹具退役 | 已落地 | 删 `CliCommand::DirectCodexChat`(变体 / `project_path_mut` / 解析 / 派发)、`run_direct_game_creator_turn_at` 一对包装函数与夹具脚本;顺带删掉只剩测试在用的 `direct_turn_error_boundary_text`(判据只剩 `direct_turn_enqueue_failure` 一处),三条边界测试改打 `direct_turn_enqueue_failure(...).message`。**`TurnAlreadyRunning` 挪到第 5 步**(它最后一个生产点在调用身份守卫里) |
|
||||
| 第 5 步 守卫清理 | 已落地 | 五个身份读者改读 `direct_active_turn_id_at`;删 `DirectTaonierActiveInvocationGuard` / `DirectActiveTurnView` / 只读探测与 `release_stale_direct_taonier_active_invocation`(改 `direct_stale_turn_for_release` 只做前置校验);删 `DirectTurnError::TurnAlreadyRunning` 与前端分支 / 生成绑定;补前置判据用例 |
|
||||
| 第 6 步 条目不再另存产物 | 已落地 | `PendingTurn` 就是 `queue.enqueued` 的投影(`from_event` / `enqueued_event` 往返),删掉 `canonical_user_item` 与 `prompt` 两个宿主私有产物字段;`StoredEvent` 上没有队列条目字段(`pending_turns` 一次事件折叠);引用解析文本落 `AgcResourceReference.resolved_text` 并随条目持久化;`canonical` 与 `prompt` 放行时重投影(提交 `15a495dc2` / `28b64cf25`) |
|
||||
| 第 7 步 埋点成绩归宿主 | 已落地 | 删候选 / settle 两阶段与 `analyticsAttemptId`(`Request::DirectCandidate` / `Request::Settle` / `settle` / `pending_runs` / `settle_direct_run_analytics` / 前端句柄表与结算 effect);放行读一次 `identity_generation`、终态再读一次后直写 `Request::Terminal`(提交 `0270cd601`) |
|
||||
|
||||
## 第 0 步:词表切换(与代码同批,不单独提交)
|
||||
|
||||
「接单 / 拒单」退役,改成「入队 / 入队失败 / 放行」。逐处按角色改,**不做字面替换**:
|
||||
|
||||
| 旧写法 | 新写法 |
|
||||
| --- | --- |
|
||||
| 命令的成功 / 失败(接单 / 拒单) | 入队 / 入队失败 |
|
||||
| 这一轮真正成立的那一刻(接单) | 放行 |
|
||||
| 接单前的检查 | 入队时的检查 |
|
||||
| 接单成立 ⇔ 事件流里有开始有结束 | **放行成立** ⇔ 事件流里有开始有结束 |
|
||||
| 接单后的失败都是回合失败 | **放行后**的失败都是回合失败 |
|
||||
|
||||
三类出现点必须分开处理(全仓 384 处):
|
||||
|
||||
1. DirectProject 域(文档、注释、标识符、测试名):按上表改。主要落点 `docs/adr/【ADR】DirectProject命令接单化-2026-09-23.md`、
|
||||
`docs/technical/【实施计划】DirectProject命令接单化-2026-09-23.md`、
|
||||
`docs/technical/【技术方案】DirectProject Codex原始历史与异常恢复-2026-09-04.md`、`CONTEXT.md`、
|
||||
`agent/direct_runtime/user_input.rs`、`agent/direct_turn_accept.rs`、`agent/direct_turn_error.rs`、`agent/direct_thread_manager.rs`、
|
||||
`chat/controller/useDirectProjectChatController.ts`、`chat/conversation/directTurnPresentation.ts`、`tests/appSurface/chat-composer.suite.ts`。
|
||||
2. `features/agent-runtime` 域的「拒单文案」(`model.ts`、`tests/agentRuntimeModel.test.ts`):那里没有队列,
|
||||
改成「请求被拒 / 拒绝」,不要写成「入队失败」。
|
||||
3. 假阳性:`单测` 这类词不动(如 `server-rs/crates/api-server/src/editor_project.rs`)。
|
||||
|
||||
标识符同批改(映射表):
|
||||
|
||||
| 旧 | 新 |
|
||||
| --- | --- |
|
||||
| `chat_with_game_creator_direct_codex` | `enqueue_direct_codex_turn` |
|
||||
| `DirectTurnRejection`(ts-rs 导出) | `DirectTurnEnqueueFailure` |
|
||||
| 前端 `readDirectTurnRejection` / `directTurnRejectionNotice*` | `readDirectTurnEnqueueFailure` / `directTurnEnqueueFailureNotice*` |
|
||||
| Rust `direct_turn_rejection` | `direct_turn_enqueue_failure` |
|
||||
| `DirectTurnReservation::accept` | `DirectTurnReservation::start` |
|
||||
| `accept_direct_thread_turn` / `DirectThreadManager::accept_turn` | `start_direct_thread_turn` / `start_turn` |
|
||||
| `agent/direct_turn_accept.rs` | `agent/direct_turn_dispatch.rs` |
|
||||
| `DirectTurnError::TurnAlreadyRunning` | 删除(第 4 步判据确认零调用方后) |
|
||||
|
||||
两份已接受的 ADR 保留正文与文件名,只在顶部加一行词表注记,避免正文里的旧词变成假命题。
|
||||
|
||||
验收:`rg -n "接单|拒单"` 在 DirectProject 域与 `agent-runtime` 域均为 0;生成绑定重跑(`cargo test export_bindings`)后
|
||||
`git diff` 只剩映射表内的改动。
|
||||
|
||||
## 第 1 步:命令 = 入队(Rust)
|
||||
|
||||
改动点:
|
||||
|
||||
- `agent/direct_runtime/user_input.rs`:把命令主体拆成两半。
|
||||
**入队侧**:`clientTurnId` 校验 → 工作流恢复 → 用户条目校验 → prompt 投影 → 前置条件 → 容量预判 → 工程准备 → 入队;
|
||||
任何一步失败返回 typed 入队失败。容量预判只是一次提前的快速失败,权威判据仍在入队临界区里(工程准备是分钟级、会
|
||||
在磁盘留产物的活,满了就不该先做它)。**放行侧**(第 2 步)从占用登记起。
|
||||
其中"工作流恢复"只在**真的需要恢复**时才去取整轮任务锁(`direct-codex-art`):正在跑的那一轮整包重生成整轮持有
|
||||
它,入队无条件取锁会先同步阻塞约 1 秒再被拒(见 ADR §6"入队不取整轮持有的锁")。
|
||||
- 队列条目(宿主侧产物,只在内存):`PendingDirectTurn { client_turn_id, user_item: Value, prompt: String, creation_type: Option<String>, at: u64 }`。
|
||||
`prompt` 与 `creation_type` 是入队检查的产物,放行不再重算;事件里**不带** `prompt`。
|
||||
- `agent/direct_thread_manager.rs`:
|
||||
- `MAX_PENDING_DIRECT_TURNS = 5` 落在 Rust,只数在队条目;满队 → typed 入队失败。
|
||||
- 队列的成员与顺序**就是事件列表本身**:宿主侧产物挂在对应的 `queue.enqueued` 事件上(`StoredEvent` 增加一个非序列化的
|
||||
可选产物字段),不另建队列表。
|
||||
- `observe_event`:`queue.enqueued` 在队期间**不可回收**;`queue.removed` 可回收,并把同 `clientTurnId` 的 enqueued 标记为可回收。
|
||||
`is_bootstrap_event` 不改——live-set bootstrap 因此自动把当前队列交给新订阅者。
|
||||
- 入队幂等:判重范围「在队 ∪ 正在跑的那一轮」,重复入队返回同一次成功。
|
||||
- `remove_direct_project_pending_turn(project_path, client_turn_id)`:typed 结果枚举 `Removed | AlreadyDispatched | NotFound`,
|
||||
在临界区里判「是否仍未被认领」。
|
||||
- 线上形状:`DirectThreadEvent::QueueEnqueued { client_turn_id, user_item, creation_type?, at }`(`queue.enqueued`)、
|
||||
`QueueRemoved { client_turn_id, reason: DirectQueueRemovalReason }`(`queue.removed`),
|
||||
`DirectQueueRemovalReason` 是 ts-rs 导出的 typed 枚举 `cancelled | dispatched`。
|
||||
|
||||
不变式:入队不登记占用、不落盘、不发回合事件、不起 codex;入队失败不写用户条目、不产生事件。
|
||||
|
||||
## 第 2 步:放行与 kick
|
||||
|
||||
- `start_direct_thread_turn`:同一个临界区里「取队首 → 占用登记 → `turn.started` + `queue.removed{dispatched}`」;
|
||||
之后落盘用户条目 → 下发用户条目 → spawn 整轮(顺序与今天的接单后半段一致,`accept` 必须早于落盘与 `turn/start`)。
|
||||
- `kick_direct_queue_dispatch(thread_id)`:幂等;在临界区里判「无占用 + 队首存在 + 未被认领」,认领后 spawn 放行任务。
|
||||
调用点三个:回合任务收尾(正常 / 失败共用)、中止路径、入队之后。
|
||||
- panic 兜底:回合任务里的一个 drop 守卫负责踢一脚,保证任务 panic 或 future 被丢弃时队列不会永久停住。
|
||||
- 入队不取 `DirectTaonierActiveInvocationGuard`;该守卫继续由整轮持有。
|
||||
- 不变式:放行不重跑检查、没有放行失败;放行之后的一切失败都走 `turn.completed.failure`。
|
||||
|
||||
验收(Rust 单测):放行原子性(`queue.removed{dispatched}` 与 `turn.started` 同批、无中间窗口);
|
||||
kick 幂等(并发两次只认领一次);队首在放行后被移除、remove 对已放行条目返回 `AlreadyDispatched`;
|
||||
回合失败 / 中止后队列继续放行下一条;任务 panic 后队列仍能继续;多订阅者游标各自独立时 bootstrap 仍重建完整队列。
|
||||
|
||||
## 第 3 步:前端收口
|
||||
|
||||
### 第 3 步的落地细则
|
||||
|
||||
- 队列投影是新文件 `chat/conversation/directPendingTurns.ts`:只做 `enqueuePendingTurn` /
|
||||
`removePendingTurn` 两个纯函数,**不判上限、不排期、不排序**;上限与认领顺序只在宿主。
|
||||
reducer 的 `queue.enqueued` / `queue.removed` 两个分支是它唯一的调用方。
|
||||
- chip 文案派生搬到 `chat/components/DirectProjectComposer/pendingTurnChipLabel.ts`
|
||||
(`pendingTurnChipLabel`),与 `chatComposerQueue.ts` 一起把"队列在本地"的最后一份实现删掉。
|
||||
- **草稿清不清由命令的入队结果回答**:`onSubmit` 改成返回 `Promise<boolean>`,composer 只在
|
||||
`true` 时清草稿。返回 `false` 的口子是"用户自己就能改的入队失败"(队列已满、参数无效这类)——
|
||||
宿主已经给了同级提示,草稿再没了就等于让用户重打一遍。写权限门让路给确认流程时返回 `true`
|
||||
(内容已经在重跑的入参里),与入队化之前一致。
|
||||
- **埋点句柄按 `clientTurnId` 存成一张表**(`pendingRunAnalyticsRef`),回合终态按本轮开口条目的
|
||||
canonical 身份(`turn.completed.userItemId` ↔ `directCodexConversationMessageId(clientTurnId,'user')`)
|
||||
认领结算;身份缺失时退回结算最早的那一条(放行严格按队首顺序,收口顺序就是入队顺序)。
|
||||
入队失败的那一轮直接把句柄删掉,不结算。
|
||||
- `displayBusy` = 原生在跑 ∨ IPC 在飞 ∨ 待发消息非空;`commandInFlight` 收窄成"IPC 在飞"。
|
||||
- 取消 chip 调 `remove_direct_project_pending_turn`,按 typed 结果 `removed / alreadyDispatched /
|
||||
notFound` 说清楚;chip 的撤除仍然只认 `queue.removed` 事件(界面不改本地队列)。
|
||||
|
||||
|
||||
退役:
|
||||
|
||||
- `chatComposerQueue.ts` 的 `enqueueChatTurn` / `dequeueChatTurn` / `removeQueuedChatTurn` / `isChatTurnQueueFull` /
|
||||
`MAX_QUEUED_CHAT_TURNS`(提示文案 `chatQueueFullNotice` 保留,改由 Rust 的 typed 入队失败驱动)。
|
||||
- controller 的 `queuedTurns` / `queuedTurnsRef` / `queueSequenceRef` / `completionPendingRef` / `handledCompletedTurnCountRef` /
|
||||
`busyBaselineTurnCountRef` / `dispatchNextQueuedTurn` / `beginTurnBusy` / `endTurnBusy` / `turnBusyRef` 与排序用的计数 effect。
|
||||
- 排队条目那部分 `pendingRunAnalyticsRef` 与 `beginDirectRunAnalytics` 的调用时序(埋点句柄改由宿主在放行时开)。
|
||||
|
||||
保留与改写:
|
||||
|
||||
- chip 由运行态事件的投影驱动(新增 pending 列表投影与两条队列事件的 reducer 分支);
|
||||
chip 文案仍用现成的派生(`directCodexContentToPromptText` + `resourceLabelResolver`),只是输入换成事件里的 `userItem`。
|
||||
- 取消 chip 改调 `remove_direct_project_pending_turn`;入队失败只由命令返回值驱动提示(保留"不丢草稿"行为)。
|
||||
- 忙态 = 事件投影 + 「队列非空」指示;`directProjectTurnStatus` 的"命令在飞"分支收成 IPC 在飞。
|
||||
- 写权限门 `ensureConversationWriteAllowed` 留在入队之前(确认框必须在用户在场时弹)。
|
||||
|
||||
验收:`tests/directThreadChat.test.ts` 补队列事件投影用例(顺序、幂等、按身份移除、bootstrap 带出在队条目、
|
||||
回合收口不清队列);`tests/appSurface/chat-composer.suite.ts` 的排队 / 取消 / 满队 / 放行四组用例改成新语义,
|
||||
并新增「入队只发一次 IPC」「IPC 在飞时挡住第二次提交」两条;`npm --workspace apps/ai-game-creator-shell run typecheck` 通过。
|
||||
|
||||
## 第 4 步:CLI 与夹具退役
|
||||
|
||||
- `cli.rs`:删 `CliCommand::DirectCodexChat` 变体、`project_path_mut` 分支(`cli.rs:181`)与派发分支(`cli.rs:899-935`)。
|
||||
- `agent/direct_runtime/mod.rs`:删 `run_direct_game_creator_turn_at` 与 `run_direct_game_creator_turn_at_with_creation_type`
|
||||
(各自只有彼此与 CLI 一个调用方)。
|
||||
- 删 `apps/ai-game-creator-shell/scripts/direct-execution-production-fixture.mjs`(CLI 的唯一消费者)。
|
||||
- 删 `scripts/run-agc-direct-execution-fixture.mjs` 与 `package.json` 的 `check:agc-direct-execution-fixture`
|
||||
(只服务该夹具,夹具没了没有第二个消费者)。
|
||||
- `DirectTurnError::TurnAlreadyRunning` 与前端"同一轮消息仍在处理中"文案、专属分支、`project-conversation.suite.ts`
|
||||
的对应断言**挪到第 5 步**(它最后一个生产点在调用身份守卫里)。
|
||||
- 文档同步:09-22 里程碑把 `--direct-codex-chat` 从"范围外(保留)"改成退役项;两份 Direct 技术方案的 CLI 承诺删掉;
|
||||
09-23 ADR 与 `decision-log.md` 里"CLI 保持 await"的口径改掉。
|
||||
|
||||
验收:`rg -n -- "--direct-codex-chat"` 与 `rg -n "run_direct_game_creator_turn_at"` 零命中;
|
||||
`cargo check --tests` 无新增 `dead_code` 告警;`check-config.mjs` 与 `.gitea/workflows/project-ci.yml` 不受影响(已核实无引用)。
|
||||
|
||||
## 第 5 步:守卫清理(CLI 退役之后)
|
||||
|
||||
> 前置判据**已核实(2026-09-30)**:`d833ca9d3` 的取消兜底本来就同时做三件事——释放占用、往事件流补一条
|
||||
> `turn.completed{aborted}`(否则前端永远停在运行中)、解除 `complete_direct_thread_turn` 占用;所以删守卫
|
||||
> **不需要**新增任何"取消路径补无条件终态"。连接死亡那条失败事实由 app-server 连接层自己落地(`ab970b9fd`),
|
||||
> 也与守卫无关。守卫唯一独有的东西是 60 秒启动窗口闸门,改挂在 Thread Manager 的占用登记年龄上。
|
||||
|
||||
- 五个身份读者(`agent/direct_execution.rs`、`agent/direct_tool_bridge.rs`、`agent/direct_validation.rs`、
|
||||
`agent/direct_project_context.rs`、`agent/direct_runtime/mod.rs` 的付费美术重生成)改读
|
||||
`direct_active_turn_id_at`(Thread Manager 的活动回合,只读、不改占用)。
|
||||
- 删 `DirectTaonierActiveInvocationGuard`、`DirectActiveTurnView`、`read_direct_taonier_active_invocation_at` 与
|
||||
`release_stale_direct_taonier_active_invocation` 及它们的测试;`codex_app_server` 的兜底目标改
|
||||
`DirectStaleTurnReleaseReason`,释放本身仍由既有的 `complete_direct_thread_turn` + `kick_direct_queue_dispatch` 完成。
|
||||
- 删 `DirectTurnError::TurnAlreadyRunning`(最后一个生产点在守卫里)与前端"同一轮消息仍在处理中"文案、专属分支、
|
||||
生成绑定与 `tests/appSurface/project-conversation.suite.ts` 的对应断言。
|
||||
- 前置判据测试:`cancel_direct_codex_turn_at` 在"回合任务泄漏、app-server 侧没有可中断句柄"时仍能解除占用,
|
||||
并把队首放行出去(`codex_app_server` 的兜底用例 + `direct_stale_turn_for_release` 的单测)。
|
||||
|
||||
## 第 6 步:条目不再另存产物(2026-09-30 修订)
|
||||
|
||||
判据:**在队条目除 `queue.enqueued` 的载荷之外不得有第二个字段**,`prompt` 也不得整条另存。
|
||||
|
||||
- `agent/direct_codex_user_item/model.rs`:`AgcResourceReference` 增加 `resolved_text: Option<String>`,
|
||||
存这个引用 part 被解析出来的文本(素材摘要 + 引用 UI 设计文档时要展开的代码上下文)。它是条目事实的一部分、
|
||||
**随条目持久化**:放行、历史回读、turn input 三个读点共用这一份,历史因此回放出当初那条消息;
|
||||
字段缺省合法(`#[serde(default)]`,序列化时 `None` 不写字段),旧历史没有它照样解析、只在缺省时按当前 manifest 现算。
|
||||
- `agent/direct_codex_user_item/wire.rs`:prompt 投影拆成两半——入队检查时 `freeze`(校验 + 算片段 + 写盘 + 冻结进 part + 判空),
|
||||
之后 `direct_codex_user_item_to_prompt(&item)` 是纯折叠(零 IO、零校验、无失败出口)。
|
||||
- `agent/thread_manager/queue.rs`:`PendingTurn` 改成 `queue.enqueued` 的**投影**(`from_event` / `enqueued_event` 往返),
|
||||
不再有 `canonical_user_item` 与 `prompt`;新增"在队 = 有 enqueued、没有配对 removed"的事件折叠。
|
||||
- `agent/thread_manager/mod.rs`:删 `StoredEvent.pending`(`append_inner` 并入 `append`);`pending_turns` / 容量 /
|
||||
判重 / `remove_pending_turn` / `claim_pending_turn` 全部改读事件折叠;`mark_queue_events_cleanable` 删掉
|
||||
"产物还在就不回收"的防御。
|
||||
- `agent/thread_manager/dispatch.rs`:放行的 canonical 形状 = `serde_json::to_value(&pending.user_item)`,prompt = 纯投影。
|
||||
|
||||
不变式:放行侧不写盘、不读 manifest、不重跑校验;入队检查仍然是这条消息唯一的失败出口。
|
||||
|
||||
验收(Rust 单测):`PendingTurn` 与 `queue.enqueued` 往返一致;队列折叠与原有 `pending_turn_ids` 语义一致
|
||||
(顺序、按身份移除、已放行返回 `AlreadyDispatched`);`resolved_text` 缺省的历史条目仍能解析并投影;
|
||||
同一批 `d341a9be1` / `e0ca5ad9b` 的中途加入 bootstrap 用例仍通过。
|
||||
|
||||
## 第 7 步:埋点成绩由宿主自己结算(2026-09-30 修订)
|
||||
|
||||
判据:`analyticsAttemptId` 整条链删除,且**不换字段存**。
|
||||
|
||||
- `agent/direct_runtime/user_input.rs`:入队命令不再收 `analyticsAttemptId`;`agent/thread_manager/wire.rs` 的
|
||||
`queue.enqueued` 不加埋点字段(队列事件相对第 5 步零新增字段)。
|
||||
- `agent/thread_manager/dispatch.rs`:放行时读一次 `platform_session` 的 `identity_generation`,随放行调用链交给回合。
|
||||
- `agent/direct_runtime/mod.rs`:回合终态再读一次,与放行时不同则整条不记;相同则直接写 `Request::Terminal`。
|
||||
- `analytics/run.rs`:删 `Request::DirectCandidate` / `Request::Settle` 与 `settle`,`direct_finished` 改收代际并直接落终态。
|
||||
- `analytics/store.rs`:删 `pending_runs` / `pending_run_bytes` 与两条候选分支(连带 16 条上限)。
|
||||
- `analytics/gui.rs` + `main.rs`:删 `settle_direct_run_analytics` 命令与注册。
|
||||
- 渲染层:删 `services/clientAnalytics.ts` 的 `beginDirectRunAnalytics`、控制器的句柄表与结算 effect、
|
||||
`TauriInvoke` 里的命令签名;`userItemId` 不再用于认领结算。
|
||||
|
||||
不变式:成绩不再依赖"恰好有一个窗口在消费终态事件";判据口径随放行搬家——入队后、放行前发生的账号切换不再丢弃成绩。
|
||||
|
||||
验收(Rust 单测):`analytics::store_tests` 的候选 / settle 用例改为终态直写用例(含"放行后代际变化不记");
|
||||
`cargo test` 定向 + 前端 `npx vitest run tests/appSurface.test.ts`、`npm --workspace apps/ai-game-creator-shell run typecheck`。
|
||||
|
||||
## 验收与证据
|
||||
|
||||
- Rust:第 1、2、5 步各自的单测;`cargo test` 定向 + `cargo check --tests` 无新增告警。
|
||||
- Node:`npx vitest run tests/appSurface.test.ts`、`npm --workspace apps/ai-game-creator-shell run typecheck`。
|
||||
- 端到端(`chat-composer.suite.ts`):回合运行中入队两条 → 取消一条 → 终态后只放行剩下那条;
|
||||
入队只发生一次 IPC、没有第二次发送命令;满队提示;入队失败时草稿不丢。
|
||||
- 手工:两个窗口看同一项目(A 排队 B 可见可取消);离开工作台再回来队列仍在并继续放行;
|
||||
`kill -9` 后重进队列消失(与 ADR 的已知边界一致)。
|
||||
- 全仓:`npm run check:encoding`、`npm run check:doc-index`、`git diff --check`。
|
||||
- 不涉及 SpacetimeDB schema,不需要 `npm run check:spacetime-schema`。
|
||||
|
||||
## 2026-09-30 收口记录
|
||||
|
||||
第 6、7 步按上面两节落地,提交按"文档定稿 → 引用解析文本持久化 → 埋点归宿主 → 条目改事件投影"切开:
|
||||
`2b98b022c`(文档)、`15a495dc2`(`resolved_text` 与 prompt 拆 freeze / 纯折叠)、`0270cd601`(埋点成绩归宿主)、
|
||||
`28b64cf25`(条目改事件投影)。
|
||||
|
||||
- Rust:`cargo test --bin genarrative-ai-game-creator-shell -- thread_manager::`(62 passed,进程级计数器用例单线程跑)、
|
||||
`-- agent::`(948 passed;`design_runtime` 与两条历史并发用例在整包并行下偶发,单跑通过,与本次改动无关)、
|
||||
`-- direct_runtime:: analytics:: direct_codex_user_item`(182 passed)、`-- analytics_real_file_write_...`(1 passed)。
|
||||
- Node:`npx vitest run tests/appSurface.test.ts`(194 passed)、`tests/directThreadChat.test.ts`、
|
||||
`tests/directHistoryPaging.test.ts`、`tests/directProjectTurn.test.ts` 等 chat / direct 单测(249 passed)、
|
||||
`npm --workspace apps/ai-game-creator-shell run typecheck` 通过。
|
||||
- 全仓:`npm run check:encoding`、`npm run check:doc-index`、`git diff --check`。
|
||||
@@ -4,6 +4,9 @@
|
||||
|
||||
状态:**四步全部落地**。
|
||||
|
||||
后续:09-24 起命令边界改叫「入队 / 入队失败 / 放行」,本文里的「接单 / 拒单」与 `TurnAlreadyRunning`
|
||||
都已退役(见 09-24 ADR 与 09-30 决策记录);正文保留当时口径,不再回改。
|
||||
|
||||
设计口径见 [`【ADR】DirectProject命令接单化-2026-09-23`](../adr/【ADR】DirectProject命令接单化-2026-09-23.md)。
|
||||
本文件只排实施顺序、不变式与验收,不重复设计理由。
|
||||
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
# AGC 随包资源 staging 归位技术方案
|
||||
|
||||
状态:待评审(方案草案,评审通过前不进入实现)
|
||||
日期:2026-09-26
|
||||
范围:AGC 客户端(`apps/ai-game-creator-shell`)随包资源的生成、校验与打包链路
|
||||
关联:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/pitfalls.md`
|
||||
|
||||
## 1. 一句话目标
|
||||
|
||||
把「随包资源」从 build script 的**产物**改回它的**输入**:由准备步骤在 `tauri dev|build` 之前一次性 staging,`build.rs` 退化为校验者,构建期不再向 `src-tauri/resources/**` 写任何文件。
|
||||
|
||||
## 2. 目标与非目标
|
||||
|
||||
### 2.1 目标
|
||||
|
||||
1. `build.rs` 的输入集合里不再包含任何「本次构建会写」的文件,`cargo` 在源码不变时稳定 fresh。
|
||||
2. Tauri dev 的文件监听不再因为 staging 变更重启 `cargo run`(macOS 与 Windows 一致,不依赖 `.taurignore` 兜)。
|
||||
3. staging 与上游版本的绑定可验证:版本、平台、逐文件摘要由校验器 fail closed 检出,不会静默发旧二进制。
|
||||
4. 打包产物内容与当前口径一致(三份 tauri 配置的 `resources` 映射与包内资源门禁不变)。
|
||||
5. 删除症状层补丁(整目录重建的规避、为绕开自触发而加的 `.taurignore` staging 条目)。`prune_staging`、`STAGED_*` 一类命名只出现在未合入的症状层补丁里,主线从未有过,不需要清理。
|
||||
|
||||
### 2.2 非目标
|
||||
|
||||
1. 不改发布渠道、版本号机制、签名与上传流程。
|
||||
2. 不改运行时资源解析顺序与完整性校验语义(`codex_cli.rs`、`plugin_host.rs`、`environment_check.rs`、`editor_adapters.rs` 保持行为)。
|
||||
3. 不统一 `server-rs` 与 `src-tauri` 两个 workspace,不改 target 布局。
|
||||
4. 不为 Linux 增加客户端产物(Linux 上五条 staging 全为 no-op,见 §3.6)。
|
||||
5. 不改 Windows/macOS 之外的平台支持面。
|
||||
|
||||
## 3. 现状与证据
|
||||
|
||||
### 3.1 build script 目前负责五条 staging
|
||||
|
||||
`apps/ai-game-creator-shell/src-tauri/build.rs` 的 `main()` 前段依次执行(`build.rs:225-229`):
|
||||
|
||||
| # | 步骤 | 动作类型 | 平台门槛 | 目标路径 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `stage_bundled_codex_cli` | 纯复制(内容+权限比对) | Windows / macOS | `resources/codex/**` |
|
||||
| 2 | `prepare_unity_editor_helper` | **外部工具链**(`powershell -File build.ps1`,需 .NET 10 SDK + VS C++ x64) | Windows + unity feature | `resources/plugins/agc-unity-editor/**` |
|
||||
| 3 | `prepare_godot_editor_extension` | **外部工具链**(`powershell -File build.ps1`,需 CMake ≥ 3.25 + VS17 2022 + Python 3) | Windows + godot feature | `resources/plugins/agc-godot-editor/native/gdextension/**` |
|
||||
| 4 | `stage_plugin_workspace` | 复制 + 清残留 | Windows / macOS | `resources/plugins/**` |
|
||||
| 5 | `stage_cocos_editor_payload` | 复制(同一 cargo 构建产出的 cdylib) | Windows | `resources/plugins/agc-cocos-editor/native/payload/**` |
|
||||
|
||||
(Prompt Bundle 生成代码写 `OUT_DIR`,属正确形态;Node 运行时已由 `scripts/stage-node-runtime.mjs` 在发布前一次性 staging,是本次改造的既有先例。)
|
||||
|
||||
### 3.2 这些写入为什么会让 build script 永远失效
|
||||
|
||||
`tauri-build` 会对 `bundle.resources` 里的**每个文件**发 `cargo:rerun-if-changed`,并把它拷进 target(`tauri-build-2.6.3/src/lib.rs:88-93`)。我们的三份 tauri 配置把 `resources/codex/**` 与 `resources/plugins` 列进了 `bundle.resources`(`tauri.windows.conf.json:7-15`、`tauri.macos.conf.json:8-22`;基线配置故意不含,见 `check-config.mjs:1377-1383`)。
|
||||
|
||||
于是「构建期写的文件」与「build script 声明的输入」是同一批:
|
||||
|
||||
```text
|
||||
staging 写 resources/** → tauri-build 把同一批文件登记成输入 → mtime 变化
|
||||
↑ ↓
|
||||
└────────────── cargo 判 build script stale,重跑脚本 ←──────┘
|
||||
```
|
||||
|
||||
判据是「输入文件 mtime 比 build script 的输出新」,与目录位置无关:把 staging 搬到 `target/` 也躲不开——登记的是配置里写的那些路径,只要构建期写它们,照样自触发。所以关键不是「产物放哪」,而是「**构建期有没有人写这些被登记的文件**」。
|
||||
|
||||
### 3.3 已发生的故障
|
||||
|
||||
| 症状 | 范围 | 观察证据 |
|
||||
|---|---|---|
|
||||
| 每次构建都重编主 crate(41–87 秒,从不变 fresh) | Windows + macOS(Linux 上五条 staging 全为 no-op,不受影响) | `CARGO_LOG=cargo::core::compiler::fingerprint=info cargo build --no-default-features` 打印 `stale: changed "resources/codex/mac-native/darwin-arm64/NOTICE.md"`,且 `.fingerprint/**/run-build-script-build-script-build.json` 的 `RerunIfChanged.paths` 含 staging 目标路径 |
|
||||
| `npm run agc` 反复 `Rebuilding application`,客户端起不来 | 仅 macOS | dev 日志一轮 28 次以上不收敛;原因是被重写的 `resources/codex/mac-native/**` 落在监听范围内且未被忽略 |
|
||||
| `remove_dir_all` 抛 `DirectoryNotEmpty`,整次启动中断 | 并发重建时 | `清理 macOS Codex staging 失败` |
|
||||
|
||||
### 3.4 三轮症状层修复都没有收口
|
||||
|
||||
| commit | 日期 | 修的是什么 | 结果 |
|
||||
|---|---|---|---|
|
||||
| `299877b19` | 2026-09-11 | 插件资源改成「内容变化才落盘」+ 引入 `.taurignore` | 只覆盖插件路径,且挡不住 cargo stale |
|
||||
| `710d6dddf` | 2026-09-23(PR #487) | Windows 上「权限一致就不写元数据」 | 只减少一类写入,未解决整目录重建 |
|
||||
| 本次未合入的 B 改动 | 2026-09-26 | 全部写入改幂等 + 按布局清残留 + 补 `.taurignore` | 实测可行(第二次 `cargo build` 0.25 秒 fresh),但规则靠人守:新增一条写 `resources/**` 的步骤漏改即回退(godot/cocos/plugin.json 正是三个漏点) |
|
||||
|
||||
结论:**只要 build script 继续写这些受跟踪路径,就必须持续为每一处写入维护「无改动不写」,成本随写入点增长**;这是结构问题,不是疏漏问题。
|
||||
|
||||
## 4. 方案设计
|
||||
|
||||
### 4.1 原则
|
||||
|
||||
build script 只写 `OUT_DIR`/`target`;随包资源是它的输入。凡需要「由本仓库生成、再随包分发」的内容,都由 `tauri dev|build` 之前的**准备步骤**生成,build script 只校验。
|
||||
|
||||
### 4.2 目标形态
|
||||
|
||||
```text
|
||||
准备步骤(新,唯一写入方) build.rs(退化为校验者)
|
||||
fetch/校验上游 → 生成到 staging 目录 只读 resources/** → 校验 manifest/hash/版本/平台
|
||||
→ 原子替换到 resources/** → 不写任何随包资源
|
||||
→ 写 manifest.json(schema 化)
|
||||
↑ ↑
|
||||
dev: start-tauri-dev 内、spawn tauri 之前 release: build-release/build-macos-ci 内
|
||||
```
|
||||
|
||||
### 4.3 准备步骤的合同(必须成立的行为)
|
||||
|
||||
1. **调用时机**:`tauri dev` / `tauri build` 之前完成;任何入口都不得依赖 build script 兜底生成。
|
||||
2. **缓存与幂等**:以「上游 lockfile `resolved` + `integrity` + 布局版本 + 目标三元」为 key;命中且 manifest 校验通过的 staging 目录不重写任何文件(避免把「产物」变成「每次构建都变」的新源头)。
|
||||
3. **原子性**:写入 staging 临时目录后 rename 替换;不得出现半成品目录(杜绝并发下的 `DirectoryNotEmpty`)。
|
||||
4. **所有权**:只允许替换由本工具创建并带 manifest 的目录;遇到非本工具目录、符号链接、越界路径必须 fail closed(沿用 `stage-node-runtime.mjs` 与 `prepare-macos-codex.mjs` 既有判定)。
|
||||
5. **清理语义**:准备步骤负责删除本轮布局不再产出的残留(否则旧组件会继续被打包),删除范围限于自己的 staging 目录,不得触碰受版本控制的文件(例如 `resources/codex/win-x64/NOTICE.md`)。
|
||||
6. **失败语义**:上游缺失、integrity 不匹配、目标平台不支持、外部工具链缺失 → 立即失败并给出可执行提示;不允许「跳过生成、继续打包」。
|
||||
7. **可观测**:输出一行汇总(命中缓存 / 重新生成 / 跳过原因),供本地与 CI 排障。
|
||||
8. **并发**:不做并发支持(无实际场景)。同一 staging 目录的并发调用未加锁,会以可读错误失败并保留已生成的 staging 目录;需要并发时由调用方外部串行化。
|
||||
9. **实际构建目标**:资源准备与 Cargo 必须使用同一目标和 feature 集。正式发布使用发布目标;无显式 `--target` 的 `--no-bundle` 使用宿主平台,Windows/macOS 仍须准备资源,Linux 编译 smoke 不准备桌面平台专属资源。feature 从最终 Tauri/Cargo 参数解析,不能被开发环境变量单独覆盖;dev 启动器转交的应用参数不参与构建参数解析。
|
||||
|
||||
### 4.4 build.rs 退化后的职责
|
||||
|
||||
保留:
|
||||
|
||||
1. 读取 `TARGET` 并写 `cargo:rustc-env=AGC_BUILD_TARGET`(运行时定位随包目录依赖它)。
|
||||
2. 校验 `resources/**` 与清单一致:平台目录存在、manifest schema/平台/版本、逐文件 sha256、必需组件齐全(缺一即 fail closed)。
|
||||
3. 声明**真实输入**:上游源文件、布局表、受版本控制资源(含 macOS 声明文件)的 `rerun-if-changed`;不得声明任何由本脚本或准备步骤生成的文件。
|
||||
4. `tauri_build::build()` 与既有 Prompt Bundle、能力与配置校验。
|
||||
|
||||
删除:
|
||||
|
||||
1. 五条 staging 的写入逻辑(M2 删除了 codex 与插件工作区的写入分支,仍是构建期产物的三处留在 M3)、针对大目录的整目录重建。
|
||||
2. 为绕开自触发而加的 `.taurignore` staging 条目与说明(准备步骤在监听启动前完成,不再需要)。
|
||||
|
||||
### 4.5 资源清单(谁生成、谁消费)
|
||||
|
||||
| 资源 | 生成方式 | 运行时消费方 | dev 是否需要 |
|
||||
|---|---|---|---|
|
||||
| `resources/codex/**` | 纯复制(上游平台包 `vendor/<triple>`) | `codex_cli.rs`(内置 sidecar 优先,其次 npm 目录、PATH) | macOS dev 只接受真 `.app` 的 `Contents/Resources`,非 `.app` 场景走 npm/PATH 回退;Windows dev 从 exe 同级读取 |
|
||||
| `resources/plugins/**` | 复制 + 三个编辑器分支的产物 | `plugin_host.rs`(`AGC_PLUGIN_WORKSPACE` → `resource_dir/plugins` → dev 回退仓库 `plugins/`)、`editor_adapters.rs` | dev 有仓库回退,但 cocos/unity/godot payload 仍以随包路径为准 |
|
||||
| `resources/plugins/agc-unity-editor/**`、`agc-godot-editor/native/gdextension/**` | 外部工具链(Windows 专属) | `editor_adapters.rs` 候选链 | 仅 Windows |
|
||||
| `resources/node-runtime/**` | 已有:`stage-node-runtime.mjs` | `environment_check.rs`(`agc-node-runtime.v1` 全量 sha256) | 发布与需要随包 Node 的 dev |
|
||||
| `resources/claude-agent/**` | 纯复制(应用 `agent-sidecar/src/index.mjs` + 上游 `@anthropic-ai/claude-agent-sdk`、`@anthropic-ai/claude-agent-sdk-<platform>`) | `claude_code_cli.rs`(`exe_dir/claude-agent/index.mjs`、macOS `.app` 的 `Contents/Resources/claude-agent`) | Windows / macOS dev:准备步骤按声明 staging |
|
||||
| `design-agent`、`vendor/*` 许可 | 受版本控制 | `design_tools.rs` 等 | 无需 staging |
|
||||
|
||||
### 4.6 入口接线
|
||||
|
||||
| 入口 | 位置 | 现状 | 改造后 |
|
||||
|---|---|---|---|
|
||||
| AGC dev | `start-tauri-dev.mjs`(`runTauriDev` → 预检 → 前端 → `spawnCli`,准备点在前端就绪之后、`spawnCli` 之前) | 无准备步骤,依赖 build script | 在 `spawnCli` 之前调用准备步骤(命中缓存时秒退) |
|
||||
| Windows 发布 | `build-release.mjs`(`runTauriBuild`,现有 `stageRuntime(target)` 紧邻 spawn tauri) | 只有 Node 运行时走准备步骤 | 同一挂点串上全部 staging |
|
||||
| macOS 发布 | `build-macos-ci.mjs`(复用 `runTauriBuild`)→ `check-macos-bundle.mjs` | 同上 | 同上 |
|
||||
| 本机 Rust 门禁 | `ai-game-creator-shell:check:rust:shell`(Windows 上 `cargo test --no-run` 会跑 build script) | 依赖 build script 生成资源 | 校验器在该场景必须能只读通过;需要真实资源的用例沿用既有 fixture,不得依赖本机 staging 产物 |
|
||||
| `tauri build --no-bundle` | `build-release.mjs` 的 no-bundle 分支(当前跳过 `stageRuntime`) | 不生成 Node 运行时 | 改为:`--no-bundle` 也执行 staging(app 构建本身需要随包资源),只跳过总号发布;校验器在资源缺失时仍然 fail closed |
|
||||
| CI(Linux) | `.gitea/workflows/project-ci.yml` 的 AGC 分组 | 五条 staging 全为 no-op | 不需要新增准备步骤;分片与 smoke 命令不变 |
|
||||
|
||||
### 4.7 与现有机制的关系
|
||||
|
||||
1. **已有先例**:`stage-node-runtime.mjs` 已实现 schema 常量、目标平台失败关闭、staging 目录 + rename 原子替换、拒绝覆盖非本工具目录;`prepare-macos-codex.mjs` 已实现 lockfile `integrity` 驱动的下载、缓存与原子替换。准备步骤应复用这两套范式而不是另起一套。
|
||||
2. **打包侧不变**:三份 tauri 配置的 `resources` 映射与 `check-config.mjs:1356-1424` 的逐字断言保持不变;`check-macos-bundle.mjs` 对包内 `coding-agent/mac-native`、`game-runtime/node`、`plugins/agc-cocos-editor` 的存在性、架构与 sha256 断言继续作为发布后门禁。
|
||||
3. **fail closed 已有兜底**:`tauri-build` 在资源缺失时以 `ResourcePathNotFound` 直接失败;校验器应比它更早、更明确地报错。
|
||||
|
||||
### 4.8 单一声明与实现形态(M1 定案)
|
||||
|
||||
准备步骤与构建期校验共用一份人工声明:`apps/ai-game-creator-shell/src-tauri/build_support/package-layout.json`。
|
||||
|
||||
| 侧 | 读取方式 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| Node 准备步骤(`scripts/prepare-bundled-resources.mjs`) | 直接读声明 JSON | 组件白名单、Codex 上游候选路径、插件随包子目录与跳过规则、平台与 feature 门槛、缓存 key 组成、manifest 序列化 |
|
||||
| Rust 构建期校验与运行期布局 | 读声明生成的 `build_support/package-layout.generated.rs`(编译期常量) | 只读校验既有产物、运行期定位随包组件(`codex_bundle.rs` 接口不变) |
|
||||
| 声明门禁 | `scripts/check-package-layout.mjs`(`npm run agc:bundled-resources:check`,已进 `agc:typecheck` 链) | 校验生成物与声明一致,并检查声明自身不变量:目标唯一、`executable` 属于白名单、每个目标恰有一条第三方声明来源、`codex.version` 与应用锁定的 `@openai/codex` 一致 |
|
||||
|
||||
形态选择**混合**:生成归 Node(复用 `stage-node-runtime.mjs` / `prepare-macos-codex.mjs` 的下载、`integrity`、临时目录 + rename 原子替换范式),声明与校验归 Rust(复用 `codex_bundle.rs` / `godot_bundle.rs` 的布局与摘要校验,运行期模块不改公开接口)。理由:Rust 侧没有下载与 lockfile 解析能力(`[build-dependencies]` 无 HTTP 客户端),Node 侧没有 staging 能力;任选单一语言都要迁移另一侧既有资产。Rust 侧刻意不解析 JSON:声明经生成器变成编译期常量,避免运行期解析与生命周期妥协,也让 `&'static` 布局表与现有调用点保持不变。
|
||||
|
||||
构建脚本自 M3 起只做只读校验(写入分支与 `AGC_SKIP_RESOURCE_STAGING` 开关一并删除),`cargo build/check` 本身就是对既有产物的校验。
|
||||
|
||||
### 4.9 M2/M3 实况:构建期写入边界
|
||||
|
||||
「谁写随包资源」按产物来源分界,声明里的 `origin` 字段表达同一口径:
|
||||
|
||||
| 来源 | 例子 | 谁写 | 时机 |
|
||||
| --- | --- | --- | --- |
|
||||
| `source`:声明 + 仓库源码即可生成 | `resources/codex/**`、插件工作区的 `src`/`panels`/`skills`/`native/payload` | 准备步骤(Node) | `tauri dev` / `tauri build` 之前 |
|
||||
| `prepared` / `libraryStaging` / `nativePayloads`:需要外部工具链或同一次 cargo 构建 | Unity `dotnet/publish/win-x64`、Godot `native/gdextension`、Cocos `native/payload` | 准备步骤:先按声明运行 `powershell.exe -File build.ps1` 或 `cargo build -p … --target …`,再复制产物 | 同上 |
|
||||
|
||||
M2 之后构建脚本只做只读校验;M3 之后它也不再生成任何随包资源(连编辑器分支产物一并交给准备步骤),并在校验阶段确认源码派生内容逐文件一致、已准备产物在位、Godot 随包库通过既有深度校验。因此源码不变时 `cargo` 稳定 fresh(macOS 实测连续三次 `cargo build --no-default-features` 为 0.69 / 0.22 / 0.22 秒),`resources/plugins` 也不再需要在 `.taurignore` 里忽略(两份 staging 条目已删除)。
|
||||
|
||||
准备步骤的 Windows 侧命令执行(powershell / cargo)只在本机无法验证,验收清单见 M3 里程碑规范。
|
||||
|
||||
**2026-09-30(合并 master)**:新增随包组件 Claude Agent SDK sidecar(cc 执行模式)沿用同一口径——声明 `claudeAgent` section(上游 SDK 包与平台原生运行时包的目标表、复制跳过规则、锁定版本),staging 由准备步骤整目录原子替换,`build.rs` 只读校验(入口与仓库源码一致、SDK 与原生运行时版本等于声明、白名单外文件)。它的 `resources/claude-agent` 映射放在 `tauri.windows.conf.json` 与 `tauri.macos.conf.json`,**不得**回到基线 `tauri.conf.json`:基线同时服务 Linux(CI 只在那里编译壳 crate 且不装 npm 依赖),`tauri-build` 会因资源缺失直接失败。
|
||||
|
||||
## 5. 兼容与迁移
|
||||
|
||||
1. **产物兼容**:包内路径、manifest schema(`genarrative-codex-sidecar.v2`、`agc-node-runtime.v1`、插件 `plugin.json`)不变,安装包内容逐项对得上;升级路径不需要用户侧动作。
|
||||
2. **过渡期(已完成)**:M1 让准备步骤与构建脚本产物并存并逐文件比对一致,M2 移除了构建脚本里 codex 与插件工作区的写入分支;剩余在构建期写入的三处(Unity publish 目录、Godot gdextension、Cocos payload)随 M3 归位。删除与新增不跨里程碑混在一起。
|
||||
3. **本机残留**:M2 已删除 codex 与插件工作区的写入逻辑与整目录重建;`.taurignore` 的 staging 条目及其原因说明保留到 M3——Windows 上仍有三处构建期写入落在 `resources/plugins/**`,去掉忽略会重新引入监听自触发。`resources/**` 仍保持 gitignored。
|
||||
4. **回滚**:准备步骤与校验器保持独立可关闭(例如校验器只读、不写),回滚只需恢复 build script 的写入分支,不涉及数据迁移。
|
||||
|
||||
## 6. 验收标准与证据
|
||||
|
||||
| 项 | 判据 |
|
||||
|---|---|
|
||||
| 构建新鲜度 | 源码不变时连续两次 `cargo build --no-default-features` 第二次为秒级 `Finished`;IDE 的 `cargo check --all-targets` 同样不重复构建 |
|
||||
| 无自触发 | `cargo:rerun-if-changed` 输出与 fingerprint 记录里不出现任何 `src-tauri/resources/**` 路径 |
|
||||
| dev 可用 | macOS/Windows `npm run agc` 在准备步骤后一次成功:`Rebuilding application` 为 0 次、`Running DevCommand` 为 1 次,客户端与 Runner 进程稳定存活 |
|
||||
| 包内容 | `check-macos-bundle.mjs` 全绿;Windows 安装包内 `coding-agent`、`plugins`、`game-runtime/node` 与改造前逐项一致 |
|
||||
| 失败关闭 | 上游缺失 / integrity 不匹配 / 清单缺组件 / 目标平台不支持 四类场景各自返回明确错误且不产出包 |
|
||||
| 幂等 | 准备步骤连续执行两次,staging 目录内容与 mtime 不变(不改动受跟踪输入) |
|
||||
| 门禁 | `check-config.mjs`、`check:encoding`、`check:doc-index`、`git diff --check`、AGC 相关 vitest 与 Rust 测试全绿 |
|
||||
|
||||
未验证项必须在交付记录中标注(例如 Windows 真机行为、真实上游包下载在受限网络下的表现)。
|
||||
|
||||
## 7. 风险与回滚
|
||||
|
||||
| 风险 | 影响 | 措施 |
|
||||
|---|---|---|
|
||||
| 忘记调用准备步骤(dev 或某个发布入口) | 资源缺失,打包或启动失败 | 校验器 fail closed + 入口测试断言「spawn tauri 前已调用准备步骤」 |
|
||||
| staging 缓存 key 不覆盖上游变化 | 静默发旧二进制 | key 含 lockfile `resolved`+`integrity`+布局版本+三元;manifest 校验作为第二道闸 |
|
||||
| 外部工具链步骤(unity/godot)搬出后顺序变化 | Windows 打包失败 | 准备步骤显式声明工具链前置检查;先在 Windows 上单独验证再合入 |
|
||||
| 并发调用同一 staging 目录 | 失败可读、不丢 staging | 不加锁也不支持并发:替换失败给出可执行提示并保留 staging 目录,需要并发时外部串行化 |
|
||||
| 迁移期两套生成并存 | 结果漂移 | 并存阶段以「准备步骤生成结果 == build script 生成结果」逐文件比对作为过渡判据 |
|
||||
|
||||
回滚点:准备步骤上线但校验器未启用前,任一步失败都可直接恢复 build script 写入分支,无需数据迁移。
|
||||
|
||||
## 8. 里程碑拆分(建议)
|
||||
|
||||
| 里程碑 | 交付 | 停止条件 |
|
||||
|---|---|---|
|
||||
| M1 校验器化 | 单一声明 + 生成门禁、`build.rs` 只读校验路径、准备步骤脚本(codex + plugins 两条纯复制路径)、缓存与原子替换 | 校验器在既有 staging 产物上全绿且不改变现有构建行为(写入分支与 `AGC_SKIP_RESOURCE_STAGING` 开关在 M3 一并删除) |
|
||||
| M2 入口接线 | dev 与两个发布入口调用准备步骤;codex/plugins 的写入分支从 build.rs 移除;`cargo` 新鲜度与 dev 不再重建达标 | 已交付(2026-09-27):macOS 侧 §6 前三行达标(连续 `cargo build` 0.69 / 0.22 / 0.22 秒 fresh;`tauri dev` 全程 `Rebuilding application` 0 次、`Running DevCommand` 1 次);Windows 新鲜度待 M3 归位三处构建期产物后复验 |
|
||||
| M3 外部工具链归位与清理 | unity/godot/cocos 的产物生成移出 build.rs;删除 `.taurignore` 的 staging 条目与相关注释;文档收口 | 已实现(2026-09-27):三处产物改由准备步骤按声明运行 powershell/cargo 后复制,build.rs 只剩只读校验,两份 `.taurignore` 已删除;已通过第五轮 Windows 真机评审:构建脚本不再写资源(90 个文件 0 变化)、第二次 `cargo build` 1.13s fresh、三分支产物齐备、幂等与 fail-closed 通过;仍待验收的是「安装包内产物路径/sha256 比对」与「三个编辑器分支客户端加载」 |
|
||||
|
||||
里程碑规范与单里程碑实现计划按 [`docs/【协作规范】规范驱动开发工作流-2026-09-12.md`](../【协作规范】规范驱动开发工作流-2026-09-12.md) 另立 `docs/project-memory/plans/` 下的临时文件;本方案是它们的主规范来源。
|
||||
|
||||
## 9. 未决问题
|
||||
|
||||
1. ~~准备步骤用 Rust bin 还是 Node 脚本?~~ **已定(2026-09-27):混合**——生成归 Node、声明与校验归 Rust,布局与白名单收敛为单一声明文件。机制、门禁与验证入口见 §4.8。
|
||||
2. unity/godot 的外部工具链步骤是否值得搬出 build script(它们本身是构建动作,搬出后需要显式前置顺序)——需在 Windows 上确认收益与风险。
|
||||
3. `resources/**` 是否需要继续保留在 crate 内(`bundle.resources` 相对路径解析要求),还是改用生成式配置指向 `target/` 下的 staging:前者改动小、后者更彻底,需与 Tauri 的资源解析规则一起评估。
|
||||
@@ -91,6 +91,10 @@ UI 编辑器的“分析参考图”步骤、Rust 命令 `suggest_ui_design_sema
|
||||
| 保持不变的边界 | 工作区根本身是链接、候选子目录是链接、二层及更深目录不递归,这三条既有边界不动 | `import_tests::ignores_symbolic_link_child_candidate_without_writing_agent_metadata`、`import_tests::ignores_windows_reparse_child_candidate_without_writing_agent_metadata`、`import_tests::ignores_godot_projects_below_the_first_child_level` |
|
||||
| 内置插件行 | 设置→扩展 的内置插件行不再渲染手动「启动 / 停止」按钮;启动由项目切换时的前端自动启动承担,停止走该行启用开关(禁用即停止并断开编辑器连接);导入扩展行的启动按钮保留 | `apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx`、`tests/pluginHost.test.ts` |
|
||||
|
||||
## 2026-09-30 扩展设置隐藏插件进程诊断文案
|
||||
|
||||
设置→扩展的内置插件与导入插件卡片不再渲染宿主返回的 `lastError`(包括“插件进程已退出”)。插件启用状态、运行状态、重载与启用/禁用操作保持现有行为;错误仍由宿主保留用于状态判定和操作反馈。
|
||||
|
||||
## 2026-09-20 DirectProject 七项效率闭环(补齐合同)
|
||||
|
||||
本节补齐并覆盖下节中仅靠 Skill 要求预检、收尾、批读和原生命令预算的部分。完整目标仍为:自动预检、宿主验收与收尾、分层验证、统一执行/返修预算、稳定测试基线、请求耗时与批量读取、所有工具并行。已有代码及测试不等于全部目标已完成;按下表逐项验收。
|
||||
@@ -740,6 +744,8 @@ Prompt 静态门禁必须断言上述 Bundle section 当前定义的权威语义
|
||||
|
||||
Agent 可见的系统指令、工具与参数说明、恢复指引和上下文模板统一由外置提示词文件维护。AGC 沿用 `prompts/runtime/manifest.json`:已有 composition/section 保持原有组合关系,独立调用的文本按职责登记在 `textCatalogs`,目录为 `prompts/runtime/texts/`,每份 JSON 是稳定文本键到正文的映射。构建期校验目录、文件、重复键和空正文,并生成可供 `format!` 使用的编译期文本宏;变量填充沿用 Rust 格式语法。Runtime 状态、用户内容、schema 类型与枚举、权限和校验继续由代码生成。服务端 Agent 的独立 crate 使用各自 `prompts/` 中的编译期文本文件。迁移以当前组装结果和工具 schema 等价为验收依据,源码门禁检查各提示词入口的内联正文与外置引用。
|
||||
|
||||
`tests/prompt_source_boundaries.rs` 的入口清单随生产函数迁移同步更新。UI 设计文档代码上下文在入队冻结时由 `src/agent/direct_codex_user_item/wire.rs::ui_design_code_context_for` 生成,门禁检查该函数继续使用外置的 `projectContext.uiDesign.codeContext` 与 `projectContext.uiDesign.generationErrorContext`;旧 `render_ui_design_code_context` 已随冻结与纯投影拆分删除,不保留兼容入口。
|
||||
|
||||
2026-07-12 起,通用开发能力的 Runtime V1.1 增量以 [`【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`](<./【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md>) 为编码级事实源。它补充仓库启动上下文、同一发布二进制独立 Runner、受限本地预览浏览器验证、动态隔离子 Agent 和真实 Provider 全链路验收;本文件中“进程内 tokio task”“首轮不预加载项目内容”和“不创建动态执行实例”的旧口径由 V1.1 明确替代,未涉及能力继续沿用本文件。
|
||||
|
||||
同一文档的“V1.2 对标 Codex CLI 增量”继续作为受控命令与推理档位的事实源。对一次性 `command.exec` 而言,只接受 Runtime 白名单内的固定 `program` 和逐项 `args` argv,默认 `confirm`,可执行文件解析为项目外绝对路径且子进程只使用安全 PATH;不解析 shell 字符串,不提供管道、重定向、PTY 或后台进程。这里对 PTY 和后台进程的排除仅适用于 `command.exec`,不能用来否定 V1.10 的独立持久进程工具,也不能把 `command.exec` 自身改成长驻入口。`command.exec` 的 action、stdout / stderr、退出码、超时与源码指纹结果统一进入现有 `action / observation`、project revision、verification gate 和 `needs-reconciliation` 链路;只有明确验证型命令且退出码、源码指纹、命令日志、manifest 与 Agent DB 审计全通过才签发 passed gate,Git / rg / cargo metadata / 普通 npm run 只作诊断。首版只请求终止受控进程组,安全等级与 `project.verify` 相同,不宣称已具备完整 OS sandbox 或 detached-process 隔离。
|
||||
@@ -1781,6 +1787,9 @@ Direct 回合的所有权属于进程内项目身份锁,不属于当前页面
|
||||
- 清单补充可选 `projectName` 与 `pendingFiles`:名称来自本地 manifest;`pendingFiles` 是本轮失败、延后、并发变动与非策略排除的跳过文件数量。`0` 表示扫描范围已同步;大文件等被跳过不能标成完整。旧清单字段缺失表示完整性未知,维持可读取兼容,不反写旧清单。
|
||||
- 项目名称或完整性发生变化时,即使文件内容没有差异也要提交新清单;本机索引记录上次已提交的这两个字段。实际客户端下一次正常同步可补齐历史清单元数据;后台只读访问不迁移旧清单。缺失 `pendingFiles` 不能默认成 0,临时跳过原因消失后允许无文件上传的 `partial → ready` 转换。
|
||||
- 后台增加“项目工程”入口,仅 owner 及拥有 `project-snapshots` 页签权限的管理员可访问。`GET /admin/api/project-snapshots?cursor=&limit=20` 读取私有 OSS 清单并返回 `{items,nextCursor}`;单页最多 100 个,游标由服务端校验,目录与清单读取有界。条目为 `{userId,projectId,projectName,syncRevision,syncedAtMs,fileCount,totalBytes,status}`,状态为 `ready / partial / unverified`,名称缺失时显示 projectId。
|
||||
- 后台“按同步时间排序”是主动读取当前渠道全量项目的临时展示操作:从首游标开始,以每次 100 条沿 `nextCursor` 顺序读取,只有全部读取成功后才切换为 `syncedAtMs` 降序、`userId` 字符串升序、`projectId` 字符串升序;不能只排序当前远端页。成功后回到第一页,20/50/100 条分页及上一页/下一页复用浏览器中的完整列表,不再请求远端;“刷新”重新读取全量并回到第一页,“恢复默认顺序”在远端目录第一页读取成功后统一切换列表、排序模式和分页游标;读取失败保留原排序集合、当前页码和本地分页,支持重试。切换渠道、令牌或离开页面销毁临时结果并取消在途读取,不跨会话持久化。
|
||||
- 全量读取展示已取得项目数并允许取消;单次最多 200 个远端页、10,000 个唯一项目、60 秒,重复游标或超限明确报错。读取失败、鉴权失败、超时或取消保留原来的列表和分页,不能发布部分数据作为排序完成的结果。并发同步期间重复项目按用户/项目身份合并,保留同步时间较新的一份,相同时间取后读到的清单;该列表是遍历期间读到的数据集合,不承诺 OSS 全局事务快照,后续同步由管理员刷新读取。仍由原接口实施渠道和页签鉴权,不新增 OSS 索引、数据库表、API 参数或客户端迁移。
|
||||
- 排序验收覆盖远端多页与空中间页、时间相同的身份排序、本地翻页/页容量、刷新与恢复默认、失败/取消/超时/限额/重复游标、并发重复身份,以及渠道/令牌切换和卸载后的旧响应隔离;证据入口为 `apps/admin-web/src/pages/AdminProjectSnapshotsPage.test.tsx`、后台类型检查和编码/文档索引检查。
|
||||
- `GET /admin/api/project-snapshots/{userId}/{projectId}/download` 只读取该用户/项目的固定清单与其引用对象,返回 `application/zip` 附件。ZIP 中路径直接使用原始相对路径,不包含 userId、摘要目录或 OSS 前缀;名称使用经过安全处理的项目名和 revision。下载固定本次读到的清单,远端并发回收导致对象缺失则整体失败,不能静默遗漏。
|
||||
- `partial` 快照下载返回 409;`unverified` 历史快照可导出已同步文件,列表明确显示“完整性未知”,动作称“下载已存文件”。`ready` 才显示“下载完整工程”。ZIP 构建核验每一文件的长度与 fnv1a64 摘要,拒绝穿越、绝对路径、重复/大小写冲突路径、非法项目身份;缺失或损坏整体失败,不返回成功的残缺 ZIP。
|
||||
- ZIP 使用服务端临时文件并限制并发,不将 2 GiB 工程整体驻留内存;成功、失败、客户端取消均清理临时文件。单文件、总量、文件数沿用上传上限,超限明确拒绝。零字节工程文件可以上传和导出。OSS 凭据与签名不下发浏览器,列表失败保留错误而非伪造空列表。
|
||||
|
||||
@@ -165,7 +165,8 @@ Supervisor / 做方案首轮忽略 `attachments`,行为不变。
|
||||
- `chat_with_game_creator_direct_codex` 增加 `attachments: Option<Vec<DirectCodexTurnAttachment>>`,先渲染再调用现有 `run_direct_game_creator_turn_at_with_creation_type_and_emitter`
|
||||
- 把现有 Home 渲染测试迁到新文件;本文件不再保留一份平行实现
|
||||
|
||||
不要把 attachments 顺着 inner turn / emitter / CLI 往下传。CLI `run_direct_game_creator_turn_at` 不变。
|
||||
不要把 attachments 顺着 inner turn / emitter 往下传。(CLI 入口 `run_direct_game_creator_turn_at` 已随命令入队化退役,见
|
||||
[`【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24`](../adr/【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md)。)
|
||||
|
||||
### 5.2 前端
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ Direct GUI 回合已经能看见 Codex `item/completed`,但只收成 UI 活动
|
||||
- 不拷隔离 `CODEX_HOME`、不落 `auth.json`、不落 `aggregated_output` / MCP `result` / patch `diff` / `FunctionCallOutput` 正文。
|
||||
- 不把原始 item JSON 送进 Tauri 前端事件(现有 `DirectCodexTurnObservation` 仍只允许安全活动词和流式正文)。
|
||||
- 不扫 `kind=uploaded` 历史附件;只记本轮 sidecar 提供的集合。
|
||||
- DirectHome、ToolHost、CLI `--direct-codex-chat`(无 `clientTurnId`)本期不写这份账本。
|
||||
- DirectHome、ToolHost 本期不写这份账本。(`--direct-codex-chat` 已随命令入队化退役,不再是入口。)
|
||||
- 本期不改 UI,不在聊天面板展示审计。
|
||||
- 不把 issue #212 标成已修复;sidecar 与本账本是两段工作。
|
||||
|
||||
@@ -292,7 +292,9 @@ chat_with_game_creator_direct_codex
|
||||
→ 写 turn_end + agent.db 摘要
|
||||
```
|
||||
|
||||
- CLI `run_direct_game_creator_turn_at` **不** 接 audit(无 `clientTurnId`)。
|
||||
- CLI 入口已退役:`run_direct_game_creator_turn_at` 与 `--direct-codex-chat` 一起删除(见
|
||||
[`【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24`](../adr/【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md)),
|
||||
所以「无 `clientTurnId` 的 Direct 回合」这条路径不存在了。
|
||||
- Home command 不接 audit。
|
||||
- 不要把 attachments / audit 顺着 CLI inner、pool、ToolHost 往下传。
|
||||
- `DirectCodexTurnObservation` **不** 增加原始 `params`。审计走独立 `DirectCodexTurnAudit`,避免 stdout 正文进入 Tauri 事件。
|
||||
|
||||
@@ -99,10 +99,16 @@ AI 游戏创作客户端使用 `npm run agc`。该入口由 `apps/ai-game-creato
|
||||
|
||||
AGC 开发态还会按后台 Web 的端口约定额外拉起 `apps/admin-web`:Linux 用当前用户端口段的 `start + 3` 槽位,非 Linux 以 `3102` 为兼容首选并允许统一漂移,`ADMIN_WEB_PORT` 可显式指定且必须避开已解析的 AGC Vite 端口;设置 `AGC_DEV_ADMIN_WEB=0` 可关闭。后台 Vite 与 AGC Vite 一样由 `start-dev-stack.mjs` 直接持有并随启动器退出收束,不走 `npm run dev:admin-web`——后者会整体重写 `.app/dev-stack.json`,覆盖本次配套后端的归属状态;后台 Web 的端口解析、启动失败或运行中意外退出都只打印告警,不阻断也不连带停止 AGC 客户端与配套后端。前端与配套后端就绪后,启动器会打印一行 `[ai-game-creator-shell] 启动汇总:`,依次给出前端、后端、后台、数据库与 `bgfilter-worker` 的实际地址;端口漂移或默认端口被其它工作树占用时,以这一行为准。
|
||||
|
||||
Tauri `beforeDevCommand` 默认与客户端构建并行,不能把上述检查只放在 `beforeDevCommand` 内:选定地址上若已有旧 Vite,Tauri 可能先创建加载旧前端的窗口,随后配套后端才因代理不匹配退出。外层启动器会把 Tauri CLI 放入受控进程树;CLI 正常退出、启动失败或收到终止信号后,POSIX 先向保留的 PGID 发送 `SIGTERM`、有界等待后升级 `SIGKILL`,Windows 使用 `taskkill /PID <pid> /T /F`。Windows 下每个长驻服务都经 `cmd.exe /d /s /c` 包装层启动,Ctrl+C 会先杀掉包装层(退出码 `0xC000013A`),因此清理不能只看直接子进程是否存活:`taskkill` 对已退出的 PID 只会失败,必须继续按记录下来的根 PID 遍历,并在退出时按本工作树 `api-server.exe` 绝对路径(以及本次自己拉起的 SpacetimeDB `--data-dir`)做一次身份兜底清扫;`scripts/dev-windows-process.mjs` 是这套判定的唯一实现。Linux 容器中的孤儿后代退出后可能暂时保留为 zombie,`kill(-PGID, 0)` 仍会返回成功;启动器必须结合 `/proc/<pid>/stat` 判断同组是否还存在非 zombie 成员,不能把等待 PID 1 回收误报为清理失败。配套后端和 Vite 仍由 `start-dev-stack.mjs` 各自持有,退出时同样有界收束,避免只剩客户端、Runner、Cargo 或旧订阅进程。排障时同时核对控制台输出的 AGC Vite 实际地址及其 marker、`.app/dev-stack.json` 的实际 API URL 和进程 cwd;不要把“终端已返回”当成客户端及其 Runner 已退出的证据。
|
||||
Tauri `beforeDevCommand` 默认与客户端构建并行,不能把上述检查只放在 `beforeDevCommand` 内:选定地址上若已有旧 Vite,Tauri 可能先创建加载旧前端窗口,随后配套后端才因代理不匹配退出。外层启动器会把 Tauri CLI 放入受控进程树;CLI 正常退出、启动失败或收到终止信号后,POSIX 先向保留的 PGID 发送 `SIGTERM`、有界等待后升级 `SIGKILL`,Windows 使用 `taskkill /PID <pid> /T /F`。Windows 下每个长驻服务都经 `cmd.exe /d /s /c` 包装层启动,Ctrl+C 会先杀掉包装层(退出码 `0xC000013A`),因此清理不能只看直接子进程是否存活:`taskkill` 对已退出的 PID 只会失败,必须继续按记录下来的根 PID 遍历,并在退出时按本工作树 `api-server.exe` 绝对路径(以及本次自己拉起的 SpacetimeDB `--data-dir`)做一次身份兜底清扫;`scripts/dev-windows-process.mjs` 是这套判定的唯一实现。Linux 容器中的孤儿后代退出后可能暂时保留为 zombie,`kill(-PGID, 0)` 仍会返回成功;启动器必须结合 `/proc/<pid>/stat` 判断同组是否还存在非 zombie 成员,不能把等待 PID 1 回收误报为清理失败。配套后端和 Vite 仍由 `start-dev-stack.mjs` 各自持有,退出时同样有界收束,避免只剩客户端、Runner、Cargo 或旧订阅进程。排障时同时核对控制台输出的 AGC Vite 实际地址及其 marker、`.app/dev-stack.json` 的实际 API URL 和进程 cwd;不要把“终端已返回”当成客户端及其 Runner 已退出的证据。
|
||||
|
||||
Windows 本地 `npm run dev` / `npm run dev:api-server` / `npm run dev:bgfilter-worker` 默认不主动启用 sccache;只有用户通过 `RUSTC_WRAPPER` 或 `CARGO_BUILD_RUSTC_WRAPPER` 显式配置 wrapper 时才进入处理流程。配置为 sccache 时会限时执行真实 wrapper 探测,成功才使用缓存;未安装、不可执行、超时或两个变量冲突时设置为空值,回退到真实 `rustc`,不阻断启动。完整栈和 `dev:api-server` 把 API 与 BgFilter worker 作为一个 Rust 重启单元:源码变化时先停两个进程,再先启动并验活 worker、最后启动并验活 API,避免两个 `cargo run` 并发链接同一个 Windows 可执行文件。不要把 wrapper 绕过值写成 `rustc`;Cargo 会按 wrapper 协议调用 `rustc <真实rustc路径> - ...`,最终报 `multiple input filenames provided` 并导致 api-server 无法启动。排查本地启动失败时,先看 dev 日志中的 wrapper 启用、冲突或回退提示。
|
||||
|
||||
`npm run agc` 的 Tauri Cargo 走同一套本地 wrapper 规则:`apps/ai-game-creator-shell/scripts/start-tauri-dev.mjs` 在启动 Tauri CLI 前调用 `scripts/dev.mjs` 导出的 `buildLocalRustProcessEnv`,把决定结果显式写进 `RUSTC_WRAPPER` 与 `CARGO_BUILD_RUSTC_WRAPPER`。用户级或仓库级 Cargo 配置里的 `rustc-wrapper`(本地常见为 `~/.cargo/config.toml` 的 sccache)只在环境变量非空时才会被覆盖,所以这两个变量必须由脚本写入而不能留空;否则本机 sccache daemon 状态损坏时,Tauri Cargo 的首次 rustc 探测(`failed to run rustc to learn about target-specific information`)就会中断整个 AGC 启动,而配套后端因为已经在用同一规则而能正常起来。AGC 启动日志出现 `[dev:rust]` 提示即为该规则生效。
|
||||
|
||||
AGC 随包资源(内置 Codex CLI、插件工作区)的布局与组件白名单只有一份人工声明:`apps/ai-game-creator-shell/src-tauri/build_support/package-layout.json`。Node 侧准备步骤直接读它,Rust 侧读由 `node scripts/check-package-layout.mjs --write`(仓库根 `npm run agc:bundled-resources:sync`)生成的 `build_support/package-layout.generated.rs`;门禁 `npm run agc:bundled-resources:check` 已进 `agc:typecheck` 链,两者不一致直接失败。改布局只能改声明文件再同步生成物,不要手改生成文件,也不要另写第二份白名单。准备步骤是 `node apps/ai-game-creator-shell/scripts/prepare-bundled-resources.mjs`:写临时目录后原子替换、命中缓存不写任何文件、只替换本工具产物、失败即退出并给出可执行提示;其用例为 `npm run agc:bundled-resources:test`。内置 Codex CLI 的上游平台包来自仓库根 `npm ci`,缺失时工具会直接提示重新安装。构建脚本对既有随包产物做只读校验(不再有写入分支,因此也没有跳过写入的开关)。
|
||||
|
||||
随包资源由准备步骤在 Tauri 之前生成:`npm run agc` 在 `apps/ai-game-creator-shell/scripts/start-tauri-dev.mjs` 里、spawn Tauri CLI 之前调用(日志以 `[ai-game-creator-shell]` 前缀给出命中缓存或重新生成);发布链在 `build-release.mjs` 的 `runTauriBuild` 内与 Node 运行时 staging 并列调用,并传 `profile: 'release'`,`tauri build --no-bundle` 也会执行随包资源 staging(app 构建本身就需要这些资源),只跳过总号发布。**构建脚本完全不生成随包资源**(源码派生内容逐文件比对、已准备产物查存在性、Godot 随包库跑既有深度校验),源码不变时 `cargo build` 稳定 fresh。需要外部工具链或同一次 cargo 构建才能产出的内容(Unity publish 目录、Godot gdextension、Cocos bridge dll)也由准备步骤按 `build_support/package-layout.json` 的 `prepareSteps` 先运行 `powershell.exe -File build.ps1` 或 `cargo build -p … --target …` 再复制;这些步骤按内容指纹跳过未变化的情况。`resources/plugins` 由准备步骤拥有:不要手工往里放东西,准备步骤会按仓库 `plugins/` 与声明重建;dev 构建下客户端读的是 `target/debug/plugins/**`(Tauri 在 debug 配置下把随包资源拷到那里),所以改完资源要重跑准备步骤而不是手动改 `resources/`;`.taurignore` 的 staging 条目已删除(构建期不再写该目录)。
|
||||
|
||||
### 本地 Rust 构建缓存与磁盘上限
|
||||
|
||||
`server-rs/Cargo.toml` 和 `apps/ai-game-creator-shell/src-tauri/Cargo.toml` 是两个独立 Cargo workspace;AGC 会以 path dependency 复用 `agent-runtime-core`、`platform-llm`、`platform-agent` 和 `shared-contracts`,但两边默认仍分别写入 `server-rs/target` 与 `apps/ai-game-creator-shell/src-tauri/target`。这个代码和锁文件边界继续保留,不为节省磁盘直接合并 workspace;生产构建脚本和 Tauri 发布还依赖当前 manifest / lock / target 身份。
|
||||
@@ -329,8 +335,8 @@ Linux process-session 的 owner SIGKILL 用例必须在启动 owner 后立即建
|
||||
- `Backend tests`:先对 `server-rs/Cargo.lock` 执行带 5 次整命令级有界重试的 `cargo fetch --locked`,再执行 `npm run check:server-rs-ddd`、`cargo test --locked --workspace --exclude spacetime-module --no-fail-fast`、`cargo test --locked -p spacetime-module --no-fail-fast`、`api-server --all-targets` 编译和 `cargo check --locked -p spacetime-module`;普通 workspace host 测试排除 `spacetime-module` 以避免其 `spacetime-types` feature 统一污染领域 crate,模块自身的纯单元测试通过独立 package test 纳入门禁。`spacetime-module` 的 reducer / procedure 运行时行为仍必须通过真实 SpacetimeDB runtime/integration harness 验证,不能把 host 链接支持当作运行时替身。依赖准备必须位于会触发 Cargo build 的 DDD / 产物边界门禁之前,避免锁新增依赖未命中镜像缓存时绕过既有下载重试。runner 安装 `ffmpeg`,避免视频抽帧测试因工具缺失提前返回。依赖真实服务或密钥的测试必须显式 `ignored`,不能让普通 PR job访问现场环境。
|
||||
- `Native shell tests`:按唯一根 workspace lockfile 安装全部 App 依赖后,用 `npm run check:native-shells:contract`、`npm run check:native-shells:shells` 和 `npm run check:native-shells:release` 分别执行静态契约、H5 / 微信 / Expo / Tauri 桌面壳运行时门禁,以及依赖发布产物的构建 smoke,最后确认桌面壳与 AI 游戏创作壳的 `Cargo.lock` 都没有被构建过程改写。
|
||||
- `AI game creator shell web tests`:执行 `npm run check:native-shells:agc-web`(即 `npm run ai-game-creator-shell:check:web`:AGC 壳 typecheck 与壳内测试)。该分组不触碰 Cargo,因此不预热 Rust 依赖。
|
||||
- `AI game creator shell Rust lane 1/2`、`lane 2/2`:两条 lane 各自只预热一次 AGC 壳自己的锁定依赖(`apps/ai-game-creator-shell/src-tauri/Cargo.lock` 的 path 依赖已含 `platform-llm`、`platform-agent`、`agent-runtime-core` 与 `shared-contracts`),然后顺序执行两次 `npm run check:native-shells:agc-rust-shard-<i>`(每次分片运行器使用对应的 `--shard-index=<i>`):AGC 壳 bin target 的 2466 条 Rust 单测按 `--list` 名单排序后切 4 片,片内保持 `--test-threads=1`、各片独立 `TMPDIR`,两条 lane 之间靠 job 级并发摊开;每次分片调用都会自校验「片并集等于全集且互斥」。**不要**改回「一个 job 里多进程并行这几片」:同一容器内它们共享 `HOME`、target 与固定临时路径,实测(run 2102)比整套串行还慢。这些 lane 只用 cargo 与 node 内建模块,因此不装 npm 依赖。
|
||||
- `AI game creator shell Rust smoke`:同样只预热 AGC 壳那份锁定依赖,执行 `npm run check:native-shells:agc-rust-smoke`(即 `npm run ai-game-creator-shell:agent-run:smoke`)。smoke 会用 `src-tauri/Cargo.toml` spawn `cargo run`,单独一个 job 以免把已经压到分钟级的片 job 拖长;只用 cargo 与 node 内建模块(脚本只 import `node:*`),因此不装 npm 依赖。
|
||||
- `AI game creator shell Rust lane 1/2`、`lane 2/2`:两条 lane 各自只预热一次 AGC 壳自己的锁定依赖(`apps/ai-game-creator-shell/src-tauri/Cargo.lock` 的 path 依赖已含 `platform-llm`、`platform-agent`、`agent-runtime-core` 与 `shared-contracts`),然后顺序执行两次 `npm run check:native-shells:agc-rust-shard-<i>`(每次分片运行器使用对应的 `--shard-index=<i>`):AGC 壳 bin target 的 2466 条 Rust 单测按 `--list` 名单排序后切 4 片,片内保持 `--test-threads=1`、各片独立 `TMPDIR`,两条 lane 之间靠 job 级并发摊开;每次分片调用都会自校验「片并集等于全集且互斥」。**不要**改回「一个 job 里多进程并行这几片」:同一容器内它们共享 `HOME`、target 与固定临时路径,实测(run 2102)比整套串行还慢。这些 lane 在编译前通过 `scripts/ci-npm-ci-with-retry.sh` 执行根 `npm ci`,为 `build.rs` 准备 Claude Agent SDK 与 Linux 原生运行时。
|
||||
- `AI game creator shell Rust smoke`:同样只预热 AGC 壳那份锁定依赖,执行 `npm run check:native-shells:agc-rust-smoke`(即 `npm run ai-game-creator-shell:agent-run:smoke`)。smoke 会用 `src-tauri/Cargo.toml` spawn `cargo run`,单独一个 job 以免把已经压到分钟级的片 job 拖长;smoke 脚本自身只 import `node:*`,但壳的 `build.rs` 需要 Claude Agent SDK,因此同样在编译前执行根 `npm ci`。
|
||||
- `AI game creator shell Rust crates`:预热 `server-rs/Cargo.toml` 与两个无锁独立 crate(`agent-runtime-core`、`agent-runtime-orchestration`)后执行 `npm run check:native-shells:agc-rust-crates`(即 `npm run ai-game-creator-shell:check:rust:crates`),覆盖 `agent-runtime-core`、`agent-runtime-orchestration`、`platform-llm` 与 `shared-contracts`。这四条命令用的是 server-rs workspace 与独立 crate 的 manifest,属另一套依赖图,因此单独一个 job,也只跑 cargo、不装 npm 依赖。
|
||||
|
||||
九个 job 合起来覆盖根 `npm run check`,并补齐根检查没有包含的 BgFilter worker smoke harness、无密钥生产巡检 / 发布 / 部署行为 fixture、server-rs DDD、正式 workspace Rust 测试与现役后端编译门禁。客户端门禁的拆分口径是 `scripts/check-native-shells.mjs` 的 `--groups=`:十个分组(`contract`、`shells`、`agc-web`、`agc-rust-crates`、`agc-rust-shard-1` ~ `agc-rust-shard-4`、`agc-rust-smoke`、`release`)各自对应一个 `check:native-shells:<group>` 根脚本,并在 workflow 的某个 lane/job 里被恰好调用一次;每个 Rust lane 顺序调用两组,不带 `--groups=` 时脚本仍然串行跑全部分组,本地语义不变。`scripts/project-ci-workflow.test.ts` 会同时校验分组清单、根脚本内容、lane/job 覆盖与分片运行器,新增分组必须三处同步。普通 PR CI 不注入业务密钥,不启动真实 API、SpacetimeDB、OSS、支付、图片生成或生产 live smoke;需要现场环境、可变外部状态、Docker 编排或发布凭据的 `check:*` 继续按对应专题和 Jenkins 发布流程执行,不能遍历所有同名前缀脚本冒充 PR 门禁。
|
||||
@@ -354,7 +360,7 @@ bash scripts/gitea-ci-job-image.sh load-runner
|
||||
|
||||
执行账号只要有权访问宿主 Docker API 并管理 runner 容器即可,不强制使用 root;无该权限时由 runner 运维人员执行。更新顺序必须是 `build/verify -> export 仓库外镜像归档与 SHA-256 sidecar -> load-runner -> 确认无活跃 job -> 备份当前 config -> 增加或替换 label -> docker restart --timeout 660 gitea-runner`。`--timeout 660` 只是停止宽限,不是 drain API;rootless DinD supervisor 可能同时停止内层 dockerd,因此重启前必须确认 Gitea 没有 `in_progress` run 且内层 `docker ps` 为空。config 和镜像归档只保存到仓库外受控位置,不在文档、仓库或日志中记录注册信息。重启后先重跑真实 PR 的九个 job,复核隔离边界并确认全部通过,再清理旧镜像。回滚时先把 workflow 的 `runs-on` 改回 `ubuntu-latest`,再恢复 config 备份并重启 runner。
|
||||
|
||||
九个 job 先运行镜像内 `genarrative-gitea-checkout`,再以 `GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1` 执行 `scripts/check-gitea-ci-job-image.sh`,校验 Node 与 npm 固定版本、仓库 Rust toolchain、受信任 PATH、四份缓存锁命中状态、原生命令、pkg-config 依赖、完整 bwrap sandbox 和 Chrome headless。运行时发现锁不匹配时必须输出对应 `*_cache_lock=partial` 和 Actions warning,提示可信分支落地后刷新镜像,不能把陈旧缓存误报为闭合。`RUSTUP_AUTO_INSTALL=0`,因此仓库 `rust-toolchain.toml` 变更必须先更新镜像,不能让 job 现场下载。需要 `node_modules` 的 job 仍各自独立运行一次根 `npm ci`,以唯一 workspace lock 验证 PR 的全部 App 依赖;两条 AGC 壳 Rust lane、`AI game creator shell Rust smoke` 与 `AI game creator shell Rust crates` 是纯 cargo 门禁(只用 cargo 与 node 内建模块),显式不装 npm 依赖,这也由 `scripts/project-ci-workflow.test.ts` 钉住。`npm ci` 统一通过 `scripts/ci-npm-ci-with-retry.sh` 做最多 3 次整命令级有界重试,同时保留 `NPM_CONFIG_PREFER_OFFLINE=true` 和 npm 自身 10 次 fetch retry。命中镜像 cache 时只做干净解包,lock 变化时允许补齐差量。不在镜像内烘入 `node_modules`,也不挂载跨 PR 可写缓存。任何 job 的 sandbox canary 失败都必须停止,不允许跳过。Cargo 通过受控 proxy 下载 lock 差量时继续关闭 HTTP multiplexing,并设置 `CARGO_NET_RETRY=10`。
|
||||
九个 job 先运行镜像内 `genarrative-gitea-checkout`,再以 `GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1` 执行 `scripts/check-gitea-ci-job-image.sh`,校验 Node 与 npm 固定版本、仓库 Rust toolchain、受信任 PATH、四份缓存锁命中状态、原生命令、pkg-config 依赖、完整 bwrap sandbox 和 Chrome headless。运行时发现锁不匹配时必须输出对应 `*_cache_lock=partial` 和 Actions warning,提示可信分支落地后刷新镜像,不能把陈旧缓存误报为闭合。`RUSTUP_AUTO_INSTALL=0`,因此仓库 `rust-toolchain.toml` 变更必须先更新镜像,不能让 job 现场下载。需要 `node_modules` 的 job 仍各自独立运行一次根 `npm ci`,以唯一 workspace lock 验证 PR 的全部 App 依赖;两条 AGC 壳 Rust lane 与 `AI game creator shell Rust smoke` 也必须在编译前安装 npm 依赖,因为 `build.rs` 会准备随包 Claude Agent SDK 与目标平台原生运行时;只有不构建壳的 `AI game creator shell Rust crates` 省略 npm 安装。安装覆盖与编译前顺序由 `scripts/project-ci-workflow.test.ts` 验证。`npm ci` 统一通过 `scripts/ci-npm-ci-with-retry.sh` 做最多 3 次整命令级有界重试,同时保留 `NPM_CONFIG_PREFER_OFFLINE=true` 和 npm 自身 10 次 fetch retry。命中镜像 cache 时只做干净解包,lock 变化时允许补齐差量。不在镜像内烘入 `node_modules`,也不挂载跨 PR 可写缓存。任何 job 的 sandbox canary 失败都必须停止,不允许跳过。Cargo 通过受控 proxy 下载 lock 差量时继续关闭 HTTP multiplexing,并设置 `CARGO_NET_RETRY=10`。
|
||||
|
||||
站点 stack 仍由宿主受控目录管理,`.env`、runner 注册文件和数据库凭据不进入仓库。Compose 必须在 helper/container 内把该目录挂到与宿主相同的绝对路径再执行;挂载到不同路径会让相对 bind source 被 Docker daemon 解析到错误的宿主目录并启动空数据。升级或 runner 迁移前先停止 Gitea 写入,并把 Gitea 冷快照、数据库导出、compose/env 与 runner config/.runner 保存到仓库外受控备份位置。备份文件、绝对宿主配置和注册 token 不得提交 Git,也不在共享文档中记录具体路径或注册内容。
|
||||
|
||||
@@ -368,7 +374,7 @@ master 日常交付必须禁止直接 push,只允许经 PR 在最近一次 Pro
|
||||
|
||||
AGC Rust 两条 lane、crates、agent-run smoke、Backend 和 Native shell 的桌面壳测试均启用 sccache。Native shell 的 release build smoke 显式清空两个 wrapper,保持发布构建原有 profile/features 与资源 staging;前端和 repository checks 不启用对象缓存。继续设置 `CARGO_INCREMENTAL=0`,不共享 target、不恢复 Actions 可写缓存。可信快照通过已有固定 Image ID 分发:镜像只增加固定版本的 sccache、编译对象和来源元数据,不包含源码、target 或凭据;容器写时复制层承接本 job 的新增对象,job 删除后丢弃,PR 没有 Docker API 或快照发布权限。该权限边界由 runner 基础设施保证,不能仅用 workflow 的分支条件替代。
|
||||
|
||||
人工 bootstrap 在没有可消费快照时使用 `bash scripts/build-gitea-rust-cache.sh <已验证基础镜像> <候选镜像tag> [完整master-SHA]`;自动维护不调用它。bootstrap 固定 master 归档,在无宿主挂载、无凭据、有资源限额的容器中只编译预热,不执行测试/应用,Cargo features 和工作目录保持实际 CI 口径。日常更新由 master CI 导出新 key,命中继承对象只传使用时间;PR 不导出、不扫描。宿主验证来源、大小和哈希,不从上传产物执行程序,只从可信来源镜像复制固定 sccache。工具链或基础镜像输入变化时重建无对象缓存基础镜像,缓存工具链必须匹配。公共快照不接受 PR,不复用 Jenkins 发布缓存。
|
||||
人工 bootstrap 在没有可消费快照时使用 `bash scripts/build-gitea-rust-cache.sh <已验证基础镜像> <候选镜像tag> [完整master-SHA]`;自动维护不调用它。bootstrap 固定 master 归档,在无宿主挂载、无凭据、有资源限额的容器中先通过 `scripts/ci-npm-ci-with-retry.sh` 安装根 npm 依赖,再编译预热 AGC 壳,以满足 Claude Agent SDK 随包资源的构建要求;不执行测试/应用,Cargo features 和工作目录保持实际 CI 口径。日常更新由 master CI 导出新 key,命中继承对象只传使用时间;PR 不导出、不扫描。宿主验证来源、大小和哈希,不从上传产物执行程序,只从可信来源镜像复制固定 sccache。工具链或基础镜像输入变化时重建无对象缓存基础镜像,缓存工具链必须匹配。公共快照不接受 PR,不复用 Jenkins 发布缓存。
|
||||
|
||||
各 Rust job 在编译前执行 `scripts/ci-rust-cache.sh prepare`:检查快照与 rustc 身份,隔离 sccache 配置和 daemon,限时探测真实 wrapper。旧镜像没有快照或探测失败时保留空 wrapper,输出 fallback 原因;缓存故障不得把真实编译/测试失败改成成功,也不允许重跑整个测试组掩盖失败。结束时 `report` 输出命中统计;分片日志单独记录 Cargo 编译耗时。对象缓存上限为 4 GiB,快照构建完成后输出实际体积;最终测试 bin 的链接仍须执行。全组开启时必须生成覆盖全部目标的新快照,不能把旧 AGC 单目标快照当作后端/桌面壳的预热验收。
|
||||
|
||||
@@ -620,6 +626,8 @@ curl -fsS --max-time 5 http://127.0.0.1/api/editor/showcase/resources >/dev/null
|
||||
|
||||
后台“项目工程”(`/admin/#project-snapshots`)按项目列出远端快照,默认只看本部署渠道,顶部“渠道”选择框可切换远端已存在的其它渠道;列表按游标分页(每页 20/50/100,上一页复用已取得的游标,远端不给总数所以只显示当前页)。完整快照提供“下载完整工程”,按原始目录返回 ZIP;未完成同步的项目暂不可下载,旧清单缺少完整性声明时显示“完整性未知”,只能“下载已存文件”。“用户”列与“素材查询”同口径展示昵称与陶泥号,并可点开用户详情;不要直接把 OSS 的 `files/{size}-{digest}/` 目录下载当成工程。
|
||||
|
||||
“按同步时间排序”按钮按需读取所选渠道全部项目,完成后按同步时间从新到旧、用户 ID 和项目 ID 字符串升序显示,并在浏览器本地按 20/50/100 条分页;列表会显示本轮项目总数。读取中可取消,失败保留原结果。排序模式下“刷新”重新读取全部项目,“恢复默认顺序”返回远端目录分页;切换渠道或重新登录清除临时排序结果。每次全量读取上限为 200 页、10,000 个唯一项目和 60 秒,超限报错,不将部分集合视作排序完成。该操作增加当次 OSS 清单读取量,不写 OSS 索引、不改数据库或 AGC 上传契约;并发同步结果以本轮读取集合为准,最新变化需刷新。
|
||||
|
||||
自动上传以原生登记的活动工程为准:打开即首传、每 300 秒周期同步、切换/关闭补传。排障同时核对 AppData `project-snapshots` 索引、`project_snapshot.sync.*` 日志和远端清单;只有测试项目的历史清单不能证明现役项目同步生效。前端在同一窗口内切项目时必须登记生命周期,不能只检查 URL 是否包含 `projectPath`。
|
||||
|
||||
后台枚举另外需要 AGC 私有前缀的 `ListObjects`(RAM `oss:ListObjects`,限制 prefix)与 `GetObject` 权限;下载不需要写入权限。客户端修复、后台页面与 api-server 必须分别发布才能在安装版和线上后台使用。本地定向测试及页面模拟不能代替发布后的真实上传与 ZIP 下载验收。
|
||||
|
||||
Reference in New Issue
Block a user