From d574732156ead51a156feb538ffd228675d17a8a Mon Sep 17 00:00:00 2001 From: kdletters Date: Tue, 4 Aug 2026 13:45:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BE=AA=E7=8E=AF=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=9B=9E=E6=8E=A5=E4=B8=8E=E5=86=85=E8=81=94=E6=BA=90?= =?UTF-8?q?=E7=A0=81=E9=99=90=E9=A2=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 循环投影声明去重后继续把 import 引用回接到既有 canonical,并补足稳定确认轮次。 内联模块先按原文累计 2 MiB 源码预算,再执行语法和语义失败关闭。 新增 self-cycle、无效超限与无效小型内联模块回归,并同步技术方案与共享决策。 --- .../runtime_protocol/autonomous_completion.rs | 42 ++++++++++++------- .../autonomous_completion_contract_tests.rs | 42 +++++++++++++++++++ .../shared-memory/decision-log.md | 2 +- ...案】AI游戏创作智能体App实施计划-2026-06-24.md | 2 +- 4 files changed, 71 insertions(+), 17 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs index d04a26035..bb54d9a0d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs @@ -4152,9 +4152,7 @@ fn executable_inline_module_javascript_units_from_html(content: &str) -> Vec::new(); let mut projection_work_bytes = 0usize; let mut projection_converged = false; - for _ in 0..module_bindings.len().max(1) { + for _ in 0..module_bindings.len().saturating_add(1).max(1) { let mut round_updates = BTreeMap::::new(); let mut round_identity_updates = BTreeMap::>::new(); @@ -7835,6 +7833,7 @@ pub(in crate::agent) fn read_external_gameplay_javascript_at( }; selected_projection_identities.insert(binding.clone(), identity.clone()); } + let projection_identities_before_dedup = selected_projection_identities.clone(); if !javascript_remove_duplicate_projection_declarations( &mut projection, &mut selected_projection_identities, @@ -7844,9 +7843,15 @@ pub(in crate::agent) fn read_external_gameplay_javascript_at( "自主构建循环模块投影声明范围无效:{importer} <- {origin}" )); } - if projection.trim().is_empty() { - continue; - } + let existing_projected_binding_names = projection_identities_before_dedup + .iter() + .filter(|(binding, _)| !selected_projection_identities.contains_key(*binding)) + .filter_map(|(binding, identity)| { + included_projection_names + .get(identity) + .map(|canonical| (binding.clone(), canonical.clone())) + }) + .collect::>(); projection_work_bytes = projection_work_bytes .checked_add(projection.len()) .filter(|bytes| { @@ -7895,7 +7900,8 @@ pub(in crate::agent) fn read_external_gameplay_javascript_at( .difference(&projection_analysis.root_bindings) .cloned(), ); - let mut projected_binding_names = BTreeMap::::new(); + let mut projected_binding_names = existing_projected_binding_names; + let mut newly_projected_binding_names = BTreeMap::::new(); for original in &projection_analysis.root_bindings { remaining_projection.remove(original); let preferred = preferred_synthetic_alias @@ -7909,18 +7915,18 @@ pub(in crate::agent) fn read_external_gameplay_javascript_at( &remaining_projection, ); assigned_names.insert(canonical.clone()); - projected_binding_names.insert(original.clone(), canonical); + newly_projected_binding_names.insert(original.clone(), canonical); } - for (original, canonical) in &projected_binding_names { + for (original, canonical) in &newly_projected_binding_names { if original != canonical && !rename_javascript_root_binding(&mut projection, original, canonical) { return Err(format!( "自主构建模块投影绑定无法按符号重命名:{origin}::{original} -> {canonical}" - )); + )); } } - for (original, canonical) in &projected_binding_names { + for (original, canonical) in &newly_projected_binding_names { let Some(identity) = selected_projection_identities.get(original) else { return Err(format!( "自主构建模块投影重命名后缺少声明身份:{origin}::{original}" @@ -7929,6 +7935,7 @@ pub(in crate::agent) fn read_external_gameplay_javascript_at( included_projection_names.insert(identity.clone(), canonical.clone()); unit_projection_identities.insert(canonical.clone(), identity.clone()); } + projected_binding_names.extend(newly_projected_binding_names); if javascript_module_analysis(&projection, true).is_none() { return Err(format!("自主构建模块投影重命名后语义无效:{origin}")); } @@ -8030,9 +8037,14 @@ pub(in crate::agent) fn read_external_gameplay_javascript_at( } } } - unit.push('\n'); - unit.push_str(&projection); - added_projection = true; + if !projection.trim().is_empty() { + unit.push('\n'); + unit.push_str(&projection); + added_projection = true; + } + if !unit_replacements.is_empty() { + added_projection = true; + } } if added_projection { if !apply_javascript_span_replacements(&mut unit, &mut unit_replacements) { diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs index 1d8b06e51..0eee3b67e 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs @@ -3806,6 +3806,29 @@ fn javascript_projection_preserves_nested_names_shorthand_keys_and_cycles() { assert!(!projected.contains("__agc_import_2")); assert!(projected.contains("return a();")); assert!(javascript_is_syntactically_valid(projected, true)); + + fs::write( + root.join("game/self.mjs"), + "import { start as again } from './self.mjs'; export function start() { return 1; } again();", + ) + .expect("write self-cyclic module"); + let self_cycle_html = ""; + let self_cycle = read_external_gameplay_javascript_at(root, self_cycle_html) + .expect("a self-cycle must reconnect its import to the existing declaration"); + let projected = self_cycle + .module_units() + .iter() + .find(|unit| unit.contains("function start()") && !unit.contains("import {")) + .unwrap_or_else(|| { + panic!( + "self-cycle must emit an import-free projected unit: {:#?}", + self_cycle.module_units() + ) + }); + assert_eq!(projected.matches("function start()").count(), 1); + assert!(projected.contains("start();")); + assert!(!projected.contains("again();")); + assert!(javascript_is_syntactically_valid(projected, true)); } #[test] @@ -3820,6 +3843,25 @@ fn javascript_inline_modules_share_the_external_script_byte_limit() { error.contains("累计超过 2 MiB"), "unexpected error: {error}" ); + + let invalid_oversized = format!( + "", + " ".repeat(2 * 1024 * 1024) + ); + let error = read_external_gameplay_javascript_at(temporary.path(), &invalid_oversized) + .expect_err("an invalid oversized inline module must fail at the byte boundary"); + assert!( + error.contains("累计超过 2 MiB"), + "oversized invalid modules must not be filtered before accounting: {error}" + ); + + let invalid = ""; + let error = read_external_gameplay_javascript_at(temporary.path(), invalid) + .expect_err("an invalid inline module must fail closed"); + assert!( + error.contains("内联模块不是有效 JavaScript:inline-module:0"), + "unexpected invalid inline module error: {error}" + ); } #[test] diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 53239aeba..055081608 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -5988,5 +5988,5 @@ - JavaScript / ESM 构造、继承与 callable 补充:`new` 沿冻结的 class owner 图执行本类显式 constructor、显式 `super()` 或隐式 derived constructor,并支持直接 class expression;instance / static 成员未 override 时继续沿 `extends` 链查找。普通嵌套 function 不继承 class `this`,arrow 保持词法 owner。`let binding; binding = function/arrow`、本地 function alias 和 Function.prototype `.bind()` 结果都建立 callable identity;`.call/.apply/.bind` 只有在 receiver 可解析为 callable 时采用 Function.prototype 语义,业务对象同名方法仍作为普通 receiver method 执行。 - 浏览器因果:状态证据仍只冻结 trusted input listener 及其点击派生微任务内的变化;完整手势身份改由宿主在成功完成 Chromium 元素鼠标输入后调用隔离世界 finish。更早注册的 `window` capture listener 即使调用 `stopImmediatePropagation()` 也不能阻断探针自身的完成身份,页面脚本不能伪造 host finish,RAF / timer 继续不计入动作结果。 - 验证边界:Linux 定向回归覆盖目录相对读写与清理、祖先 symlink、CAS 安装后错误、九文件混合快照、Tetris AST 反例和七项真实 Chrome generic 试玩。Windows cfg 代码必须继续在真实 Windows CI / 发布构建验证;本地缺少 MinGW C compiler 时,安装了 Rust target 也不能把交叉 `cargo check` 失败误报为源码失败。 -- JavaScript / ESM 循环与体积补充:投影声明按 `(origin module, original root binding)` 保存身份,canonical 重命名不能改写原始身份;删除回流声明前先把引用改接到 importer 已有 canonical,有界轮次未收敛时失败关闭。inline module 与外部脚本共同占用累计 `2 MiB` 源码预算。 +- JavaScript / ESM 循环与体积补充:投影声明按 `(origin module, original root binding)` 保存身份,canonical 重命名不能改写原始身份;删除回流声明后仍把 import 引用改接到 importer 已有 canonical,并给固定点保留“模块数 + 1”轮的产出与稳定确认预算,未收敛时失败关闭。inline module 先按浏览器可执行标签提取原文并计入与外部脚本共享的累计 `2 MiB` 源码预算,再做语法和语义校验;无效超限模块不能被 helper 静默过滤,无效小模块也明确失败关闭。 - JavaScript / ESM alias 求值顺序补充:receiver alias 保存赋值完成时刻并在该时刻解析 source owner,后续 source 重赋值不得倒灌。调用事件按内层参数 / RHS 先于外层调用 / assignment 生效;`switch case/default` 赋值一律保留跳过与各分支可能状态,普通函数内无条件 `return / throw` 截断之后的 alias 副作用。恒真 / 恒假关键字大小写敏感,可能被局部或参数遮蔽的 `undefined` 不再作为文本恒假值。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index 6834fd0e9..d729a1028 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -839,7 +839,7 @@ game-project/ - 泥点不足是确定性业务中断,不是瞬态 Provider 故障或未知副作用。钱包的 `泥点余额不足` 与 `可消费泥点不足:...` 两种领域文案统一映射为稳定原因 `mud-points-insufficient`,不得自动重试;即使 External Generation durable ledger 已存在,也必须落为 `failed`,不能误入 `needs-reconciliation`。game-chat 顶部状态、持久失败对话与 `【Supervisor 阶段记录】` 统一显示“泥点余额不足,本轮游戏生成已中断。请充值后发送“继续”,系统会从当前项目进度接着完成。”,并禁止透传 operationId、URL、路径、密钥或任意上游正文。 - tool-plan 成功响应落账前,对内置 Runtime 原生函数与 legacy wrapper 的合法、无重复 key JSON arguments 按工具 schema 的精确位置做项目路径 canonicalization:`file.*.path`、`project.patchset.changes[*].path`、`project.git_commit.paths[*]`、`command.*.cwd`、`image.inspect.paths[*]` 与 `canvas.asset_generate.outputPath` 若是当前项目根目录内的完整绝对路径,转换为 `/` 分隔的项目相对路径后再校验、持久化并执行;源码/叙述字段、任务产物描述、动态 MCP arguments 和项目外绝对路径不得改写,后两者继续由绝对路径门禁失败关闭。项目根只允许搜索/列举范围与命令 cwd 规范化为 `.`,不能成为文件目标。当前进程与重启恢复都必须从同一份规范化 handoff 重放,禁止分别执行原响应和持久响应。 -- 循环 ESM 的投影身份固定为 `(origin module, original root binding)`;canonical 重命名只改变组合单元中的展示名,身份随投影闭包传播。删除已存在的回流声明前,必须先把其引用改接到 importer 中同一身份的既有 canonical;固定点未在模块数限定轮次内收敛时失败关闭,不能返回最后一轮仍变化的部分结果。inline module 与外部脚本共同计入累计源码 `2 MiB` 上限,不能绕过文件读取预算。 +- 循环 ESM 的投影身份固定为 `(origin module, original root binding)`;canonical 重命名只改变组合单元中的展示名,身份随投影闭包传播。删除已存在的回流声明后仍必须把 import 引用改接到 importer 中同一身份的既有 canonical;固定点未在“模块数 + 1”轮内收敛时失败关闭,不能返回最后一轮仍变化的部分结果。inline module 必须先提取原文并计入与外部脚本共享的累计源码 `2 MiB` 上限,再执行语法和语义校验;超限源码不能因无效语法被过滤,小型无效模块也必须明确失败关闭。 ## 2026-07-31 长耗时与恢复收口