diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/mod.rs index d5ccc94c4..2488ae4ad 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/mod.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/mod.rs @@ -5,8 +5,9 @@ mod validation; mod wire; pub(crate) use model::{ - DirectCodexUserContentPart, DirectCodexUserItem, DirectCodexUserMessageEnvelope, - DirectCodexUserMessageItem, DirectCodexUserRole, DirectCodexUserRuntimeRegionPart, + DirectCodexUserAttachmentReferencePart, DirectCodexUserContentPart, DirectCodexUserItem, + DirectCodexUserMessageEnvelope, DirectCodexUserMessageItem, DirectCodexUserRole, + DirectCodexUserRuntimeRegionPart, }; pub(crate) use validation::validate_direct_codex_user_item; pub(crate) use wire::{ diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/model.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/model.rs index b46336b66..e739b420b 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/model.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/model.rs @@ -36,6 +36,23 @@ pub(crate) enum DirectCodexUserContentPart { AgcResourceReference { resource_id: String }, #[serde(rename = "agc_runtime_region_reference")] AgcRuntimeRegionReference(DirectCodexUserRuntimeRegionPart), + /// Uploaded project attachment kept inline in canonical content. + #[serde(rename = "agc_attachment_reference")] + AgcAttachmentReference(DirectCodexUserAttachmentReferencePart), + #[serde(rename = "agc_image_reference")] + AgcImageReference(DirectCodexUserAttachmentReferencePart), +} + +#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/features/project-workspace/generated/"))] +pub(crate) struct DirectCodexUserAttachmentReferencePart { + pub(crate) name: String, + pub(crate) media_type: String, + #[ts(type = "number")] + pub(crate) size: u64, + pub(crate) local_path: String, + pub(crate) status: String, } #[derive(Clone, Debug, Deserialize, Serialize, TS)] diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/validation.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/validation.rs index 01f6a6868..50c285729 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/validation.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/validation.rs @@ -40,6 +40,19 @@ pub(crate) fn validate_direct_codex_user_item( reference_count = reference_count.saturating_add(1); validate_runtime_region_reference(&manifest, reference)?; } + DirectCodexUserContentPart::AgcAttachmentReference(reference) + | DirectCodexUserContentPart::AgcImageReference(reference) => { + if reference.name.trim().is_empty() { + return Err("附件缺少文件名".to_string()); + } + if !reference.local_path.trim().is_empty() { + sanitize_attachment_local_path(&reference.local_path) + .ok_or_else(|| "附件项目路径无效".to_string())?; + } + if !matches!(reference.status.trim(), "imported" | "failed") { + return Err("附件状态无效".to_string()); + } + } } } if reference_count > MAX_DIRECT_CODEX_REFERENCES { diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/wire.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/wire.rs index 2ab90cc62..c4352f0cf 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/wire.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/wire.rs @@ -100,6 +100,21 @@ pub(crate) fn direct_codex_user_item_to_wire_input( summary.push(']'); summary } + DirectCodexUserContentPart::AgcAttachmentReference(reference) + | DirectCodexUserContentPart::AgcImageReference(reference) => { + let mut summary = format!( + "[附件:名称={};类型={};大小={} 字节", + reference.name.trim(), + reference.media_type.trim(), + reference.size + ); + if !reference.local_path.trim().is_empty() { + summary.push_str(&format!(";项目路径={}", reference.local_path.trim())); + } + summary.push_str(&format!(";状态={}", reference.status.trim())); + summary.push(']'); + summary + } }; input.push(serde_json::json!({ "type": "text", "text": text })); } @@ -174,4 +189,28 @@ mod tests { .expect_err("history item without type must fail"); assert!(error.contains("缺少 type"), "{error}"); } + + #[test] + fn attachment_and_image_parts_remain_in_canonical_order_when_projected() { + let root = tempfile::tempdir().expect("temp project"); + crate::init_local_game_project_at(root.path(), "wire-test", "wire 投影测试") + .expect("init project"); + let item = json!({ + "type": "message", + "role": "user", + "id": "turn-1:user", + "content": [ + {"type": "input_text", "text": "先看"}, + {"type": "agc_image_reference", "name": "hero.png", "mediaType": "image/png", "size": 12, "localPath": "assets/hero.png", "status": "imported"}, + {"type": "agc_attachment_reference", "name": "notes.txt", "mediaType": "text/plain", "size": 4, "localPath": "assets/notes.txt", "status": "imported"} + ] + }); + let projected = direct_codex_user_item_to_response_item(root.path(), &item) + .expect("user response item should project"); + let content = projected["content"].as_array().expect("content array"); + assert_eq!(content.len(), 3); + assert!(content[0]["text"].as_str().unwrap().contains("先看")); + assert!(content[1]["text"].as_str().unwrap().contains("hero.png")); + assert!(content[2]["text"].as_str().unwrap().contains("notes.txt")); + } } diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/user_input.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/user_input.rs index aa9bbe8f7..1e631cd1f 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/user_input.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/user_input.rs @@ -31,10 +31,9 @@ pub(crate) fn normalize_direct_client_turn_id( pub(crate) async fn chat_with_game_creator_direct_codex( project_path: String, prompt: String, - mut user_item: DirectCodexUserItem, + user_item: DirectCodexUserItem, creation_type: Option, client_turn_id: Option, - attachments: Option>, ) -> Result { let root = Path::new(project_path.trim()); let turn_id = normalize_direct_client_turn_id(client_turn_id.as_deref())?; @@ -43,24 +42,7 @@ pub(crate) async fn chat_with_game_creator_direct_codex( redact_agent_runtime_error(root, &format!("恢复上一轮陶泥儿整包事务失败:{error}"), 500) })?; let turn_emitter = DirectGameCreatorTurnUpdateEmitter::new(root, turn_id.clone()); - let mut audit = DirectCodexTurnAudit::start( - root, - &turn_id, - &prompt, - attachments.as_deref().unwrap_or_default(), - ); - let attachments = attachments.unwrap_or_default(); - if !attachments.is_empty() { - let attachment_context = - render_direct_codex_user_prompt("", &attachments).map_err(|error| { - audit.finish(false); - error - })?; - let DirectCodexUserItem::Message(message) = &mut user_item; - message.content.push(DirectCodexUserContentPart::InputText { - text: attachment_context, - }); - } + let mut audit = DirectCodexTurnAudit::start(root, &turn_id, &prompt, &[]); validate_direct_codex_user_item(root, &user_item).map_err(|error| { audit.finish(false); error diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index b2a05ae88..937e48ec6 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -893,8 +893,7 @@ export function App({ /** * 输入盒待发送附件(direct-codex 回合附件):上传成功后先生成 chip,随下次提交一起 - * 交给 `chat_with_game_creator_direct_codex` 的 `attachments`。附件只存在于前端状态, - * 提交后即清空——后端协议不变。 + * 转换为 canonical user item 的 `content[]`。附件只存在于前端待发状态,提交后即清空。 */ const [chatAttachments, setChatAttachments] = useState< DirectCodexTurnAttachment[] @@ -7035,12 +7034,32 @@ export function App({ if (directProjectPath && directProjectId && directInvoke) { const clientTurnId = directConversationTurnId ?? createDirectCodexConversationTurnId(); - const effectiveUserItem = + const baseUserItem = userItem ?? chatComposerDraftToDirectCodexUserItem( { text: prompt, references: references ?? [], content: [] }, directCodexConversationMessageId(clientTurnId, 'user'), ); + const effectiveUserItem = attachments?.length + ? { + ...baseUserItem, + content: [ + ...baseUserItem.content, + ...attachments.map((attachment) => ({ + type: attachment.mediaType.toLowerCase().startsWith('image/') + ? ('agc_image_reference' as const) + : ('agc_attachment_reference' as const), + name: attachment.name, + mediaType: attachment.mediaType, + size: attachment.size ?? 0, + localPath: attachment.localPath ?? '', + status: + attachment.status ?? + (attachment.localPath ? 'imported' : 'failed'), + })), + ], + } + : baseUserItem; if ( !directPolicyChecked && projectConversationWriteConfirmedRef.current !== directProjectPath @@ -7181,7 +7200,6 @@ export function App({ prompt: string; clientTurnId: string; creationType?: HomeCreationType; - attachments?: DirectCodexTurnAttachment[]; userItem: ReturnType; } = { projectPath: directProjectPath, @@ -7192,9 +7210,6 @@ export function App({ if (creationType) { directTurnInput.creationType = creationType; } - if (attachments?.length) { - directTurnInput.attachments = attachments; - } directTurnInput.userItem = effectiveUserItem; const reply = await withDirectCodexSessionRefresh(() => { // 每次调用都会新建 Rust 事件流;续期重试需重新接收同一回合的进度。 @@ -12595,6 +12610,20 @@ export function App({ content?: DirectCodexUserContentPart[]; }) { const clientTurnId = createDirectCodexConversationTurnId(); + const attachmentContent: DirectCodexUserContentPart[] = ( + input.attachments ?? [] + ).map((attachment) => ({ + type: attachment.mediaType.toLowerCase().startsWith('image/') + ? ('agc_image_reference' as const) + : ('agc_attachment_reference' as const), + name: attachment.name, + mediaType: attachment.mediaType, + size: attachment.size ?? 0, + localPath: attachment.localPath ?? '', + status: + attachment.status ?? (attachment.localPath ? 'imported' : 'failed'), + })); + const content = [...(input.content ?? []), ...attachmentContent]; supervisorChatShouldFollowLatestRef.current = true; setMessages((current) => [ ...current, @@ -12609,16 +12638,18 @@ export function App({ void executeChatAgentReply({ prompt: input.prompt, clientTurnId, - attachments: input.attachments?.length ? input.attachments : undefined, references: input.references, userItem: chatComposerDraftToDirectCodexUserItem( { text: input.prompt, references: input.references ?? [], - content: input.content ?? [], + content, }, directCodexConversationMessageId(clientTurnId, 'user'), ), + // DirectProject 的附件已经是 canonical content part;不能再作为 sidecar + // 传给 Rust,否则会重复追加。 + attachments: undefined, }); } diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserAttachmentReferencePart.ts b/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserAttachmentReferencePart.ts new file mode 100644 index 000000000..7394ae07d --- /dev/null +++ b/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserAttachmentReferencePart.ts @@ -0,0 +1,9 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type DirectCodexUserAttachmentReferencePart = { + name: string; + mediaType: string; + size: number; + localPath: string; + status: string; +}; diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserContentPart.ts b/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserContentPart.ts index 10c4e0f5a..c3b7b0ea1 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserContentPart.ts +++ b/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserContentPart.ts @@ -1,5 +1,5 @@ -// This file is generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DirectCodexUserAttachmentReferencePart } from './DirectCodexUserAttachmentReferencePart'; import type { DirectCodexUserRuntimeRegionPart } from './DirectCodexUserRuntimeRegionPart'; export type DirectCodexUserContentPart = @@ -7,4 +7,8 @@ export type DirectCodexUserContentPart = | { type: 'agc_resource_reference'; resourceId: string } | ({ type: 'agc_runtime_region_reference'; - } & DirectCodexUserRuntimeRegionPart); + } & DirectCodexUserRuntimeRegionPart) + | ({ + type: 'agc_attachment_reference'; + } & DirectCodexUserAttachmentReferencePart) + | ({ type: 'agc_image_reference' } & DirectCodexUserAttachmentReferencePart); diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserItem.ts b/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserItem.ts index 4c9ea8ca3..aa1ee88dd 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserItem.ts +++ b/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserItem.ts @@ -1,5 +1,9 @@ -// This file is generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DirectCodexUserMessageItem } from './DirectCodexUserMessageItem'; -export type DirectCodexUserItem = DirectCodexUserMessageItem; +/** + * DirectProject 本轮 user input 的唯一结构化入口。 + */ +export type DirectCodexUserItem = { + type: 'message'; +} & DirectCodexUserMessageItem; diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserMessageEnvelope.ts b/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserMessageEnvelope.ts new file mode 100644 index 000000000..97e0b0dae --- /dev/null +++ b/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserMessageEnvelope.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DirectCodexUserItem } from './DirectCodexUserItem'; + +export type DirectCodexUserMessageEnvelope = { item: DirectCodexUserItem }; diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserMessageItem.ts b/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserMessageItem.ts index f7447dc5d..a1101c8cb 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserMessageItem.ts +++ b/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserMessageItem.ts @@ -1,11 +1,9 @@ -// This file is generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DirectCodexUserContentPart } from './DirectCodexUserContentPart'; import type { DirectCodexUserRole } from './DirectCodexUserRole'; export type DirectCodexUserMessageItem = { - type: 'message'; role: DirectCodexUserRole; - content: DirectCodexUserContentPart[]; + content: Array; id: string; }; diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserRole.ts b/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserRole.ts index e1dc59c59..0c2540122 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserRole.ts +++ b/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserRole.ts @@ -1,3 +1,3 @@ -// This file is generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export type DirectCodexUserRole = 'user'; diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserRuntimeRegionPart.ts b/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserRuntimeRegionPart.ts index 296c0d19a..b134fb7bc 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserRuntimeRegionPart.ts +++ b/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserRuntimeRegionPart.ts @@ -1,13 +1,13 @@ -// This file is generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export type DirectCodexUserRuntimeRegionPart = { label: string; - runId?: string; - versionId?: string; - elementTag?: string; - elementRole?: string; - text?: string; - width?: number; - height?: number; - resourceIds: string[]; + runId: string | null; + versionId: string | null; + elementTag: string | null; + elementRole: string | null; + text: string | null; + width: number | null; + height: number | null; + resourceIds: Array; }; diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/generated/index.ts b/apps/ai-game-creator-shell/src/features/project-workspace/generated/index.ts index f1012a4c5..aff993c0d 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/generated/index.ts +++ b/apps/ai-game-creator-shell/src/features/project-workspace/generated/index.ts @@ -1,5 +1,7 @@ export type { DirectCodexUserContentPart } from './DirectCodexUserContentPart'; +export type { DirectCodexUserAttachmentReferencePart } from './DirectCodexUserAttachmentReferencePart'; export type { DirectCodexUserItem } from './DirectCodexUserItem'; export type { DirectCodexUserMessageItem } from './DirectCodexUserMessageItem'; export type { DirectCodexUserRole } from './DirectCodexUserRole'; export type { DirectCodexUserRuntimeRegionPart } from './DirectCodexUserRuntimeRegionPart'; +export type { DirectCodexUserMessageEnvelope } from './DirectCodexUserMessageEnvelope'; diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts b/apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts index c6e6959c8..72f46c677 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts +++ b/apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts @@ -13,7 +13,6 @@ import { import type { DirectCodexUserContentPart, DirectCodexUserItem, - DirectCodexUserMessageItem, } from './generated'; export type ResourceReferenceSource = @@ -103,13 +102,13 @@ export function chatReferenceToContentPart( return { type: 'agc_runtime_region_reference', label: reference.label, - runId: reference.runId, - versionId: reference.versionId, - elementTag: reference.elementTag, - elementRole: reference.elementRole, - text: reference.text, - width: reference.width, - height: reference.height, + runId: reference.runId ?? null, + versionId: reference.versionId ?? null, + elementTag: reference.elementTag ?? null, + elementRole: reference.elementRole ?? null, + text: reference.text ?? null, + width: reference.width ?? null, + height: reference.height ?? null, resourceIds: reference.resourceIds, }; } @@ -126,7 +125,7 @@ export function chatComposerDraftToDirectCodexUserItem( role: 'user', content, id, - } satisfies DirectCodexUserMessageItem; + } satisfies DirectCodexUserItem; } export function resourceDisplayName(asset: GameCreationAppAssetManifestEntry) { diff --git a/docs/project-memory/plans/【实施计划】DirectProject用户ResponseItem输入-2026-09-15.md b/docs/project-memory/plans/【实施计划】DirectProject用户ResponseItem输入-2026-09-15.md index dac2a7297..51b9ea7b8 100644 --- a/docs/project-memory/plans/【实施计划】DirectProject用户ResponseItem输入-2026-09-15.md +++ b/docs/project-memory/plans/【实施计划】DirectProject用户ResponseItem输入-2026-09-15.md @@ -9,7 +9,7 @@ ## 修改边界 - 允许修改:AGC 壳 Rust agent 输入合同、DirectProject 历史适配、前端聊天引用模型、ts-rs 生成配置、当前聊天素材文档。 -- 明确不修改:assistant 返回协议、工具 activity、附件/图片 DTO 协议、SpacetimeDB、HTTP API;仅补齐既有 sidecar 在 DirectProject prompt 的消费。 +- 明确不修改:assistant 返回协议、工具 activity、附件/图片上传 DTO、SpacetimeDB、HTTP API;上传 DTO 仅在入口转换为 canonical `content[]`,不再以 sidecar 文本追加。 ## 实现顺序 diff --git a/docs/project-memory/plans/【里程碑】DirectProject用户ResponseItem输入-2026-09-15.md b/docs/project-memory/plans/【里程碑】DirectProject用户ResponseItem输入-2026-09-15.md index 392505040..963ad4791 100644 --- a/docs/project-memory/plans/【里程碑】DirectProject用户ResponseItem输入-2026-09-15.md +++ b/docs/project-memory/plans/【里程碑】DirectProject用户ResponseItem输入-2026-09-15.md @@ -19,13 +19,13 @@ - `agc_runtime_region_reference` 保留运行区域语义摘要。 - Rust 在持久化前完成白名单、manifest 与路径校验。 - 现有标准 `response_item` 原样兼容;legacy conversation 行不提供 fallback。 -- 保持 assistant 返回、工具 activity、附件/图片协议不变;沿用 sidecar DTO 将附件与图片路径映射纳入 DirectProject prompt。 +- 保持 assistant 返回、工具 activity 与上传 DTO 不变;但 DirectProject canonical user item 不再保存附件 sidecar,附件和图片必须作为 `content[]` 中按顺序排列的 `agc_attachment_reference` / `agc_image_reference`。 ## 不在范围内 - assistant item 前端投影或 Tauri 返回值改造。 - 工具 item、reasoning、file change、MCP item 的 UI 模型化。 -- 附件/图片 content part(不新增 content-part,继续消费现有 sidecar DTO)。 +- 附件/图片 content part:本里程碑明确新增并生成绑定;上传 DTO 只作为输入适配,不作为历史事实源。 - SpacetimeDB schema 或 HTTP API 变更。 ## 依赖与前置条件 @@ -42,7 +42,7 @@ - [x] 未知 part、失效资源或非法路径在持久化前失败关闭。 - [x] canonical item 以 `response_item` 写入历史,标准旧 item 原样可读。 - [x] Codex wire input 不含 AGC 私有 part,且顺序与 canonical content 一致。 -- [x] assistant、附件和工具链路行为无变化;附件-only 输入也能进入 DirectProject prompt。 +- [x] assistant、附件和工具链路行为无变化;附件与图片按 content 顺序内联,附件-only 输入也能进入 DirectProject prompt。 ## 证据要求