diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/claude_code_cli.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/claude_code_cli.rs index 641ace7bb..8adffce7d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/claude_code_cli.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/claude_code_cli.rs @@ -898,7 +898,6 @@ mod tests { let error = parse_direct_stream_result( br#"{"type":"result","is_error":true,"result":"Authentication failed"} "#, - "turn-1", None, ) .expect_err("failed terminal must be rejected"); diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent_native_tools.rs b/apps/ai-game-creator-shell/src-tauri/src/agent_native_tools.rs index 8262ccb0c..7fa421b6a 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent_native_tools.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent_native_tools.rs @@ -1137,7 +1137,7 @@ fn runtime_tool_input_schema(tool: &str) -> Value { "properties": { "folderId": { "type": ["string", "null"], "maxLength": 512 }, "query": { "type": ["string", "null"], "maxLength": 120 }, - "offset": { "type": ["integer", "null"], "minimum": 0, "maximum": 500 }, + "offset": { "type": ["integer", "null"], "minimum": 0 }, "limit": { "type": ["integer", "null"], "minimum": 1, "maximum": 100 } } }), @@ -2006,6 +2006,40 @@ mod tests { assert!(issues.is_empty(), "{}", issues.join("\n")); } + #[test] + fn account_asset_native_function_schema_accepts_later_pages() { + let functions = build_agent_runtime_native_function_tools().unwrap(); + let function = functions + .iter() + .find(|function| { + function.name == native_runtime_function_name_for_tool("asset.library.list") + }) + .expect("account asset native function"); + assert!(function.strict); + let validator = jsonschema::validator_for(&function.parameters).unwrap(); + let mut arguments = json!({ + "reason": "读取账户素材后续页", + "input": { "folderId": null, "query": null, "offset": null, "limit": null } + }); + assert!(validator.is_valid(&arguments)); + for offset in [0, 500, 501, 600, 10_000] { + for limit in [1, 100] { + arguments["input"]["offset"] = json!(offset); + arguments["input"]["limit"] = json!(limit); + assert!(validator.is_valid(&arguments), "{arguments}"); + } + } + for offset in [json!(-1), json!(501.5)] { + arguments["input"]["offset"] = offset; + assert!(!validator.is_valid(&arguments), "{arguments}"); + } + arguments["input"]["offset"] = json!(600); + for limit in [0, 101] { + arguments["input"]["limit"] = json!(limit); + assert!(!validator.is_valid(&arguments), "{arguments}"); + } + } + #[test] fn canvas_asset_generate_schema_uses_shared_asset_kind_catalog() { let schema = runtime_tool_input_schema("canvas.asset_generate"); diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 83395954a..7d63d3e3a 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -13,7 +13,7 @@ ## 2026-10-01 AGC 账户总量门槛会阻断单项查询与导入 - 账户素材库接口当前返回全量快照;客户端不能以全库条数代替单次查询、返回或导入的资源边界。原先在解析阶段拒绝超过 500 项,会让 `limit=1` 和指定 ID 导入一起失效,且检查发生在下载和 JSON 解析之后,不能保护这两步开销。 -- 账户查询允许偏移超过 500,仍按单页最多 100 项返回安全元数据。修改时同步 MCP schema/校验、Direct 工具桥和 Runtime;共享分页函数的项目文件调用方仍保留自己的范围,画布资源数量边界独立维护。 +- 账户查询允许偏移超过 500,仍按单页最多 100 项返回安全元数据。修改时同步 MCP schema/校验、Direct 工具桥、Runtime 执行层及 `agent_native_tools.rs` 下发的 strict 原生函数 schema;仅直接调用 observation 会绕过模型侧参数约束,无法证明后续页可达,必须验证实际生成的函数参数 schema。共享分页函数的项目文件调用方仍保留自己的范围,画布资源数量边界独立维护。 - 实际平台会话导入还需避免在身份租约内再次调用会话读取/校验:租约持有非重入互斥锁,提交 helper 重复校验会等待自身释放。下载后校验并取得租约,持有至本地提交完成即可;不能用无平台会话的提交夹具替代完整导入链路验证。 - 后端有界分页与按 ID 读取由 #574 跟进,不作为 #549 客户端恢复可用的前置条件。当前合同与验证入口见 [AGC 实施计划](../../technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md) 的“账户 / 项目画布 / 本地素材导入”。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index df7af577b..d2ab76784 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -506,6 +506,7 @@ npm 游戏的可预览产物固定为对应 package 目录下的 `dist/index.htm ## 2026-08-25 账户 / 项目画布 / 本地素材导入 - 账户素材库总量不作为客户端查询、预览或按 ID 导入的拒绝条件。账户查询继续先读取后端快照,再按静态图片、文件夹与关键词筛选并分页;`offset` 接受可表示的非负整数,超过结果总数返回空页,`limit` 保持 1–100(默认 100),结果保留 `total / offset / limit / nextOffset`。Direct MCP 的关键词匹配素材名或文件夹名,Runtime 保留现有素材名匹配语义。 +- Runtime 下发给模型的 `asset.library.list` strict 原生函数 schema 必须与执行层分页边界一致;验收须校验 `build_agent_runtime_native_function_tools` 生成的完整函数参数,确认 `offset=501/600` 可用、负数和非整数偏移及 `limit>100` 被拒绝,同时保留可选字段的 `null` 语义。直接调用 observation 函数不能替代模型可见 schema 的验证;定向用例为 `account_asset_native_function_schema_accepts_later_pages`。 - 账户查询只把当前页安全元数据作为工具文本返回,不把全库作为附件交给 Agent。指定 ID 导入仍先读取账号快照再选取目标;单批数量、下载体积、账号/项目归属、会话、媒体类型和原子登记校验保持有效。绑定画布资源数量与项目文件发现的既有边界不随账户总量限制解除而放宽。无需后端 API、DTO、数据库或持久化迁移;全库传输与解析开销由后续 #574 的服务端分页及按 ID 读取处理,不阻塞 #549 的客户端修复。 - 平台账号导入在下载后重新校验会话并取得身份租约,租约覆盖本地写入、manifest 登记与 revision 推进;提交过程不得再次读取同一登录态互斥锁,避免在身份保护内自锁。身份变更仍须在取得租约时拒绝,取消写入许可仍不得写入。 - 验收覆盖 500/501 项账户快照、按文件夹/关键词查询、偏移超过 500 的后续页、越界空页和大库指定 ID 导入,并复核无效 ID/不支持媒体拒绝、安全投影、取消写入和账号切换。使用 `agent_asset_import_tests`、`account_asset`、`semantic_resource_tools_reject_unreviewed_or_inconsistent_arguments`、`cancelled_import_write_permit`、`platform_session::tests` 定向 Rust 用例及提示词合同测试;模拟平台证据与真实账号验证分别记录。