纳入 AGC Cargo workspace 的统一 rustfmt 检查与格式化入口 完成项目 TypeScript/Prettier 与 Rust 全量格式化 修复 Pingora expected executable 门禁的空白敏感误报 同步开发运维文档与 AGC skill pack 格式化忽略规则
13 KiB
DirectProject 本轮附件路径映射
- 日期:2026-08-31
- 状态:现行合同(已按本文落地)
- 问题:Gitea issue #212(DirectProject 未消费用户上传权威文档)
- 关联入口:PR #210「批准 GDD 回填做游戏入口」(
feat/create_entrance,未合入时仍按该 PR 的调用链理解) - 原则:落地后代码简洁可维护,不为了 diff 最小而打补丁;附件一律同等对待,不给 GDD 开协议特例
0. 一句话
首页带进项目的附件已经复制并登记,但 Direct 首轮只拿到用户原文。本方案让 Direct 回合在发给 Codex 的 user prompt 末尾附上有界路径映射(原文件名 → 项目相对路径),不灌正文、不强制读取、不改做方案注入。
1. 目标与非目标
目标
- Direct 首轮知道本轮用户附件的原文件名、项目相对路径、媒体类型、大小和导入状态。
- 用户原文不被改写;路径映射是独立 sidecar。
- 图片、Markdown、其它文件走同一条协议。
- 做成游戏(PR #210:读
game/fast_gdd.md→ 当成fast_gdd.md附件 →createHomeDraftAutomatically)自动吃到效果,因为那条链就是「带附件的 Direct 首轮」。
非目标
- 不改 PR #210 的按钮、固定 prompt、
startGameFromApprovedGdd、读game/fast_gdd.md的方式。 - 不改
approvedGddRef、审批 receipt、策划项目里的game/fast_gdd.md投影。 - 不改上传命名
assets/uploads/upload-<ts>-<name>。 - 不把附件全文拼进 prompt,不按扩展名决定是否读取。
- 不把「没读到就阻断」做成门禁。
- 不扫 manifest 里历史
kind=uploaded。 - 不做 native 读取审计;该项由
【技术方案】Direct回合行为审计账本-2026-08-31.md承接。 - 不改 DirectHome 在「无项目路径」时的现有文案和列表格式。
- 不改
enterCreatedHomeProject的空正文兜底句(与做方案共用)。
2. 现行断点
Home 附件 / 做成游戏 File(fast_gdd.md)
→ upload_local_asset
→ LauncherProjectContext.attachments // 已有,只给资源画布
→ ProjectSupervisor // 无 attachments 字段
→ chat_with_game_creator_direct_codex
{ projectPath, prompt, clientTurnId, creationType? }
做成游戏的固定 prompt 仍写「附件中的 fast_gdd.md」,磁盘文件却是 assets/uploads/upload-<ts>-fast_gdd.md。映射没有进 Direct。
DirectHome 已有 { name, mediaType, size } 元数据注入,但标明「尚未打开项目,内容尚不可读取」。项目已落盘后这条元数据被丢掉。
3. 目标合同
3.1 唯一 DTO
Home 与 Project 共用一个附件结构,缺省字段表示 Home 现状:
{
name: string; // 原文件名
mediaType: string;
size?: number; // 缺省按 0
localPath?: string; // 仅已落入项目时出现
status?: 'imported' | 'failed';
}
- 不把
error字符串送给模型。 - 前端
LauncherImportedAttachment继续给画布;invoke 前映射成上述瘦 DTO。 importHomeAttachments把File.size写入可选size,不改upload_local_asset返回值。
Rust:
struct DirectCodexTurnAttachment {
name: String,
media_type: String,
#[serde(default)]
size: u64,
#[serde(default)]
local_path: Option<String>,
#[serde(default)]
status: Option<String>, // 只接受 imported | failed,其它忽略
}
DirectCodexHomeAttachment 删除,Home command 改用同一类型。现有 Home JSON(无 localPath / status)继续能反序列化。
3.2 渲染
一个函数 render_direct_codex_user_prompt(prompt, attachments) -> Result<String, String>:
| 输入 | 输出 |
|---|---|
| 无附件 | prompt.trim();若也空则 Err("聊天内容不能为空") |
附件都没有 localPath 且都没有 status |
保持现有 Home 文案与行格式,测试须逐字兼容 |
任一条有 localPath 或 status |
Project 头 + Project 行格式 |
Home 行(禁止改字):
[首页附件说明:当前尚未打开项目,以下仅为附件元数据,附件内容尚不可读取]
- {name};类型:{mediaType};大小:{n} 字节
Project 头与行(禁止出现 GDD / 规格 / 权威 / 必须读取):
<用户原文>
[本轮用户附件:已复制到当前项目。请用「项目路径」读取;原文件名不是磁盘路径。]
- 原文件名:fast_gdd.md;项目路径:assets/uploads/upload-1788083777445-fast_gdd.md;类型:text/markdown;大小:7944 字节;状态:imported
规则:
- 条数上限仍为 8,超出写
- 另有 N 个附件未展开。 - 名字清洗沿用 Home:basename、去掉控制字符、最多 160 字、空则「未命名附件」。
- 媒体类型清洗沿用 Home。
localPath只接受项目相对 POSIX 路径:无..、无盘符/根路径、首段不是.agent/.git,\归一为/,最长 512;不合法则该条不输出路径,状态按failed。- 有附件时允许原文为空(Home 已如此)。Direct 内层若仍要求非空,在 command 边界先渲染再下传,避免空原文 + 有附件被拒。
不在 sidecar 里写「若用户要求按附件实施请先读取」。意图留在用户原文;做成游戏的固定 prompt 已经在说这件事。
3.3 谁渲染、谁看见
- sidecar 只在进 Codex 前由 Rust 拼装。
.agent/conversations/project.jsonl继续写用户原文(现有append_local_conversation_message)。- 工作台气泡继续显示 latch / 输入框原文,不把 sidecar 画进 UI。
- 未完成首轮的 hydration 重放目前只带 prompt:本期不把附件写进 jsonl,进程重启后的未完成首轮可能丢映射。完整成功首轮不受影响。不为此新增会话 schema。
4. 数据流
Home 上传 / 做成游戏 File
→ upload_local_asset(已有)
→ LauncherProjectContext.attachments(已有)
→ ProjectSupervisor.initialAttachments
→ 首轮 latch(与 prompt、creationType 同级)
→ 仅 chat_with_game_creator_direct_codex.attachments
→ render_direct_codex_user_prompt
→ 现有 Direct turn(cwd = 项目根)
Supervisor / 做方案首轮忽略 attachments,行为不变。
后续工作台手打消息不带 attachments。附件是这一轮带来的,不是项目终身上下文。
5. 代码落地(按最终结构,不按最小补丁)
5.1 Rust
新增 apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_attachments.rs:
- DTO、清洗、上限常量、
render_direct_codex_user_prompt - 单元测试(见第 6 节)
agent.rs 增加 mod direct_codex_attachments。
- 删除 Home 专用 struct / sanitizer /
render_direct_codex_home_user_prompt run_direct_game_creator_home_turn改为调用共享渲染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 不变。
5.2 前端
model.ts ProjectSupervisorComponentProps 增加:
initialAttachments?: LauncherImportedAttachment[];
initialAttachments={currentProjectContext.attachments}
- props / latch 增加
attachments(默认[]) executeChatAgentReply不要再加第 5 个位置参数,收成:
{
prompt: string;
clientTurnId?: string;
creationType?: HomeCreationType | null;
attachments?: DirectCodexTurnAttachment[];
}
- Direct invoke:有
creationType才写该字段(现有);attachments?.length才写attachments - Supervisor 分支完全不读
attachments - 现有
executeChatAgentReply(prompt)/ 恢复未完成 turn 的调用改为对象形式,不传 attachments
瘦映射不要写在 1 万行的 App.tsx 里,放到例如 apps/ai-game-creator-shell/src/features/app-shell/directCodexTurnAttachments.ts:toDirectCodexTurnAttachments(imported),去掉 error,空 localPath 不输出该键。
useHomeProjectCreation.ts:importHomeAttachments 写入 size: attachment.file.size。不改 initialPrompt 兜底句,不改做成游戏(即便本分支尚未合入 PR #210,也不预埋 GDD 字段)。
types.ts:LauncherImportedAttachment 增加可选 size?: number。
5.3 文档
落地提交时(不是本方案文件自身):
- 本文件标为现行合同
docs/README.md、docs/project-memory/shared-memory/document-map.md增加条目decision-log.md记一条:Direct 本轮附件只映射路径,不灌正文、不区别 GDD- 不把 issue #212 技术说明改写成「已修复」,等代码合入后再改状态
6. 测试
Rust(新文件)
- 无附件:原文 trim 后原样返回;空原文报错。
- Home 形态(无 path、无 status):与迁过来的两条现有测试逐字一致(含路径剥离、非法 mediaType、8 条上限)。
- Project 形态:原文保留;含原名与
assets/uploads/...;断言不得出现「GDD」「规格」「权威」。 - 同一列表里 png 与 md 行格式相同(只是 name/path/type 不同)。
status=failed且无 path:有状态、无项目路径、无 error 正文。- 非法
localPath(../、.agent/x、绝对路径)不出现在输出中。 - 空原文 + 有附件:成功,且含 Project 头。
前端
home.suite.ts「imports home attachments…」:Direct invoke 必须带attachments,其中name为角色参考.png、localPath为 upload 返回路径、status: 'imported'。用 png 证明不是 md 特例。- 无附件的 Direct invoke 仍不得出现
attachments键(或等价:不传该字段)。 planningStartMode首轮仍走 Supervisor,chat_with_game_creator_agent的 payload 不含附件 sidecar。- 工作台后发的普通消息:
chat_with_game_creator_direct_codex只有projectPath/prompt/clientTurnId(及既有 creationType 规则),不带 attachments。 - 若本分支已能跑 PR #210 的 home.suite / plan-gdd 做成游戏用例:只断言它仍调用
createHomeDraftAutomatically/ 仍使用原固定 prompt;不要给做成游戏加第二条附件协议。sidecar 由通用 Direct 断言覆盖。
不测
- 不把「必须生成弹幕射击」写成单测。
- 不测 native
file.read是否进agent.db。
7. 做成游戏为什么不用改
PR #210 startGameFromApprovedGdd:
- 读策划项目
game/fast_gdd.md new File([content], 'fast_gdd.md')createHomeDraftAutomatically({ creationType: 'game', prompt: APPROVED_GDD_BUILD_PROMPT, attachments }, 'direct-build')
之后与首页拖一个 md 完全相同。sidecar 见到的是「原文件名 fast_gdd.md + 新项目 assets/uploads/upload-…-fast_gdd.md」。固定 prompt 继续说「读附件中的 fast_gdd.md」,映射补上真实路径。
8. 验收
- 首页做游戏:上传任意文本或图片 + 一句话,Direct 首轮 prompt 含原名和
assets/uploads/...。 - 做成游戏(PR #210 合入后或该分支上):固定 prompt 一字不改,同时出现改写后的项目路径。
- 做方案首轮:Supervisor 行为与现在一致,无 sidecar。
- 无附件:Direct 入参与现在一致。
npm run check:encoding、git diff --check、相关home.suite/ Direct Rust 测试通过。
9. 实现顺序
- Rust 共享渲染 + 迁 Home 测试 + Direct command 接
attachments - 前端 latch / invoke / 映射 /
size - 改
home.suite附件断言 - encoding 与定向测试
- 合入时补 decision-log 与文档索引