统一M0固定产物验证与可玩验收边界

新增四个固定 owner 的 Runtime 内部产物验证、身份校验与恢复约束

收紧 code-prototype 静态检查、试玩执行者与动态 Canvas 委派谱系

补齐项目锁竞争、完成门、路径策略和失败关闭回归测试

同步 M0A-2 决策、技术方案与项目记忆
This commit is contained in:
2026-08-11 09:02:00 +00:00
parent da62f6bc01
commit 9a7a799512
28 changed files with 4036 additions and 307 deletions
@@ -488,14 +488,14 @@ fn game_creator_design_foundation_tool_plan_prompt(
prompt: &str,
editor_api_key_is_configured: bool,
) -> String {
let role_boundary = "角色边界:项目文件写入只允许 memory/project.md 与 game/game_design.md;配置 External Editor API Key 且任务要求界面原型时,可额外产出指定的 assets/ui-prototype.png。不得创建、修改、删除或补丁 game/index.html,也不得改动任何其他程序实现、发布、音频或美术素材文件。可以调用 command.run_limitedgame.static_smoke 提交当前 revision 的只读静态验证;不得调用 preview.startpreview.validate,也不得通过 command.exec、command.start 或其他工具启动本地预览服务、浏览器、Playwright,或执行任何桌面端、移动端试玩验证;这些集成验证必须交由程序或质量 Agent 完成";
let role_boundary = "角色边界:项目文件写入只允许 memory/project.md 与 game/game_design.md;配置 External Editor API Key 且任务要求界面原型时,可额外产出指定的 assets/ui-prototype.png。不得创建、修改、删除或补丁 game/index.html,也不得改动任何其他程序实现、发布、音频或美术素材文件。完成固定正式产物后直接交付,由 Runtime 在收束门内验证本人 owner 产物;不得调用 project.verify、command.run_limitedgame.static_smokepreview.startpreview.validate,也不得通过 command.exec、command.start 或其他工具启动本地预览服务、浏览器、Playwright,或执行任何桌面端、移动端试玩验证。完整 DAG 的最终静态验收仍属于 preview-readiness,浏览器验收仍属于 preview-playtest";
if !editor_api_key_is_configured {
return format!(
"{prompt}\n\n你负责玩法规格与界面原型基础交付。当前未配置 External Editor API Key,因此本轮必须完成 memory/project.md 与 game/game_design.md,不调用 canvas.asset_generate,也不伪造 assets/ui-prototype.png。把界面结构、控件、状态和双视口要求写进玩法规格,供后续程序组直接实现;完成写入并通过当前 revision 的非浏览器验证后即可交付{role_boundary}"
"{prompt}\n\n你负责玩法规格与界面原型基础交付。当前未配置 External Editor API Key,因此本轮必须完成 memory/project.md 与 game/game_design.md,不调用 canvas.asset_generate,也不伪造 assets/ui-prototype.png。把界面结构、控件、状态和双视口要求写进玩法规格,供后续程序组直接实现;完成写入后直接交付,不要自行运行任何验证命令{role_boundary}"
);
}
format!(
"{prompt}\n\n你负责玩法规格与界面原型交付。玩法类型和机制描述不代表用户授权复刻现有游戏;必须先为项目创造原创标题、实体、资源、目标名称与视觉语言,并在 memory/project.md、game/game_design.md 和图片提示中保持一致。不得沿用或近似改写知名游戏单位、角色、Logo、界面术语或受保护视觉语言。文本策划只是中间结果;最终必须先用 asset.list 确认 assets/art-spec.png 已登记为当前项目的 icon-spec 画布资源,再调用 canvas.asset_generate 生成 16:9、2K 横屏界面原型图并登记到 assets/ui-prototype.pngassetKind=ui-prototype、assetLabel=游戏横屏界面原型图、replaceExisting=false。图片 prompt 必须逐项继承当前任务和 game/game_design.md 的真实玩法、HUD、可玩区域、关键实体、主要操作、失败/重开与移动端触控要求;不得假设为塔防或补入合同中不存在的单位卡牌、费用、波次、敌人入口等结构。Runtime 固定把规范图资源作为 referenceImageSrcs 第一项,调用 External Editor v1 的 POST /api/external/v1/editor/images/generationskind=ui-design);不得误用 POST /api/external/v1/editor/ui-designs/assets/extractions,后者只用于从已有且带标注的 UI 设计图提取独立透明 UI 素材。缺少规范图时必须等待 art-director 依赖并如实阻塞,不得回退为无规范参考的普通生图。canvas.asset_generate 成功动作本身就是当前 revision 的验证。已有同路径画布资产时先核对登记,再在当前 run 对且只对 assets/ui-prototype.png 调用 image.inspect;检查已通过时不得重复生成或再次扣费。只有 ui-prototype.v2 的 informationHud、gameplaySurface、objectiveEntities、primaryControls、failureRestartFlow、responsiveLayout、implementationClarity、originalTheme 八项检查全部通过才可完成。纯场景图、概念图、地图、海报或只有角色而没有可玩界面的画面都不是 UI 原型。视觉检查未通过时不得提交最终回复;只有任务正文明确标识这是带 repairOfDelegationId 的唯一返工轮时,才可使用固定输出合同和 replaceExisting=true 原位替换旧候选;不得先删除正式图片。图片生成未配置、待确认或失败时同样不得提交最终回复,也不得把计划写完当成 completed。{role_boundary}"
"{prompt}\n\n你负责玩法规格与界面原型交付。玩法类型和机制描述不代表用户授权复刻现有游戏;必须先为项目创造原创标题、实体、资源、目标名称与视觉语言,并在 memory/project.md、game/game_design.md 和图片提示中保持一致。不得沿用或近似改写知名游戏单位、角色、Logo、界面术语或受保护视觉语言。文本策划只是中间结果;最终必须先用 asset.list 确认 assets/art-spec.png 已登记为当前项目的 icon-spec 画布资源,再调用 canvas.asset_generate 生成 16:9、2K 横屏界面原型图并登记到 assets/ui-prototype.pngassetKind=ui-prototype、assetLabel=游戏横屏界面原型图、replaceExisting=false。图片 prompt 必须逐项继承当前任务和 game/game_design.md 的真实玩法、HUD、可玩区域、关键实体、主要操作、失败/重开与移动端触控要求;不得假设为塔防或补入合同中不存在的单位卡牌、费用、波次、敌人入口等结构。Runtime 固定把规范图资源作为 referenceImageSrcs 第一项,调用 External Editor v1 的 POST /api/external/v1/editor/images/generationskind=ui-design);不得误用 POST /api/external/v1/editor/ui-designs/assets/extractions,后者只用于从已有且带标注的 UI 设计图提取独立透明 UI 素材。缺少规范图时必须等待 art-director 依赖并如实阻塞,不得回退为无规范参考的普通生图。canvas.asset_generate 成功只表示候选图片已生成并登记,不等于视觉验收完成。已有同路径画布资产时先核对登记,再在当前 run 对且只对 assets/ui-prototype.png 调用 image.inspect;检查已通过时不得重复生成或再次扣费。只有 ui-prototype.v2 的 informationHud、gameplaySurface、objectiveEntities、primaryControls、failureRestartFlow、responsiveLayout、implementationClarity、originalTheme 八项检查全部通过才可完成。八项视觉检查通过后直接交付,由 Runtime 在收束门内同时核对固定 owner 文档、当前 revision 与视觉证据。纯场景图、概念图、地图、海报或只有角色而没有可玩界面的画面都不是 UI 原型。视觉检查未通过时不得提交最终回复;只有任务正文明确标识这是带 repairOfDelegationId 的唯一返工轮时,才可使用固定输出合同和 replaceExisting=true 原位替换旧候选;不得先删除正式图片。图片生成未配置、待确认或失败时同样不得提交最终回复,也不得把计划写完当成 completed。{role_boundary}"
)
}
@@ -504,9 +504,9 @@ fn game_creator_art_director_tool_plan_prompt(
editor_api_key_is_configured: bool,
) -> String {
if !editor_api_key_is_configured {
return format!("{prompt}\n\n你负责确定原创视觉方向。当前未配置 External Editor API Key,只完成正式 director 文档,不调用 canvas.asset_generate,也不伪造 assets/art-spec.png。");
return format!("{prompt}\n\n你负责确定原创视觉方向。当前未配置 External Editor API Key这是只读协调任务:只完成正式 director 结论并直接交付,不修改项目文件,不调用 canvas.asset_generate,也不伪造 assets/art-spec.png。seed task 中生成规范图的图片产物与验收条款在本轮不适用。");
}
format!("{prompt}\n\n你负责生成项目唯一的统一视觉规范图。视觉方向文档只是中间结果;最终必须调用 canvas.asset_generate,以固定合同 outputPath=assets/art-spec.png、aspectRatio=1:1、imageSize=1K、assetKind=icon-spec、assetLabel=游戏统一视觉规范图、replaceExisting=false 生成真实图片。Runtime 固定调用 External Editor v1 的 POST /api/external/v1/editor/images/generationskind=spec),并把结果同时登记到同名画布、素材库和项目 manifest。规范图必须覆盖玩家主体、目标物、地块、UI 图标、状态反馈、色板与材质规则,作为后续 UI 和透明图集共同引用的权威资源;不得用 generationInputs.artSpec JSON、纯文本计划、完整游戏截图、海报或普通黑底图集冒充。已有有效同路径资产时不得重复生成或扣费;只有带 repairOfDelegationId 的唯一返工轮可设置 replaceExisting=true 原位替换。生成失败或缺少 resourceId 时不得提交最终回复,也不得把计划写完当成 completed。")
format!("{prompt}\n\n你负责生成项目唯一的统一视觉规范图。视觉方向文档只是中间结果;最终必须调用 canvas.asset_generate,以固定合同 outputPath=assets/art-spec.png、aspectRatio=1:1、imageSize=1K、assetKind=icon-spec、assetLabel=游戏统一视觉规范图、replaceExisting=false 生成真实图片。Runtime 固定调用 External Editor v1 的 POST /api/external/v1/editor/images/generationskind=spec),并把结果同时登记到同名画布、素材库和项目 manifest。规范图必须覆盖玩家主体、目标物、地块、UI 图标、状态反馈、色板与材质规则,作为后续 UI 和透明图集共同引用的权威资源;不得用 generationInputs.artSpec JSON、纯文本计划、完整游戏截图、海报或普通黑底图集冒充。canvas.asset_generate 成功只表示固定候选已生成并登记,不等于视觉门已经通过;生成成功后直接交付,由 Runtime 在收束时核对当前 revision、Canvas 登记、资源身份和视觉产物门。已有有效同路径资产时不得重复生成或扣费;只有带 repairOfDelegationId 的唯一返工轮可设置 replaceExisting=true 原位替换。生成失败或缺少 resourceId 时不得提交最终回复,也不得把计划写完当成 completed。")
}
fn game_creator_art_asset_plan_tool_plan_prompt(
@@ -515,11 +515,11 @@ fn game_creator_art_asset_plan_tool_plan_prompt(
) -> String {
if !editor_api_key_is_configured {
return format!(
"{prompt}\n\n你负责首版美术资产清单交付。当前未配置 External Editor API Key,因此本轮必须写入可解析的 assets/manifest.art.json,记录所需素材、用途、推荐规格和当前未生成状态;不调用 canvas.asset_generate,也不伪造 assets/art-spritesheet.png。完成清单并通过当前 revision 的验证后即可交付,不得编辑 game/index.html。"
"{prompt}\n\n你负责首版美术资产清单交付。当前未配置 External Editor API Key,因此本轮必须写入可解析的 assets/manifest.art.json,记录所需素材、用途、推荐规格和当前未生成状态;不调用 canvas.asset_generate,也不伪造 assets/art-spritesheet.png。完成清单后直接交付,由 Runtime 验证本人固定 owner 产物;不得调用 project.verify、game.static_smoke 或 preview.validate,也不得编辑 game/index.html。"
);
}
format!(
"{prompt}\n\n你负责首版美术素材实际生成。资产清单和美术计划只是中间结果;最终必须调用 canvas.asset_generate 生成并登记 assets/art-spritesheet.png,固定使用 1:1、1K、assetKind=art-spritesheet、assetLabel=游戏首版核心美术素材、replaceExisting=false,并写入可解析的 assets/manifest.art.json。调用前必须用 asset.list 确认 assets/art-spec.png 已登记为当前项目的 icon-spec 画布资源,并依据当前任务、game/game_design.md 与 manifest 逐项说明真实需要的玩家主体及朝向/状态、目标或收集物、障碍/场景元素和反馈特效,由 Runtime 形成 iconDescriptions;不得假设为塔防或加入合同中不存在的单位、敌人、波次、卡牌。Runtime 固定以规范图的权威 resourceId 作为 referenceId,调用 POST /api/external/v1/editor/icon-spritesheets/generations,并用 screenColor=auto 完成透明后处理;不得把 UI 原型、Data URL、Blob URL、本地路径或结构化 JSON 冒充规范图引用,不得回退普通生图或 UI extraction。缺少规范图时必须等待 art-director 依赖并如实阻塞。成功后回读 observation 与 asset.list,核对服务端返回的透明 spritesheet、真实 alpha、warning 和 sliceWarning。warning.code=postprocess-failed-source-preserved 时没有透明图集,不得登记、验收或自动重试;仅 sliceWarning 时可保留完整透明图集,但不得声称独立切片已生成。已有有效同路径资产时不得重复生成或扣费;只有带 repairOfDelegationId 的唯一返工轮可 replaceExisting=true 原位替换。不得运行 game.static_smoke 或 preview.validate,也不得编辑 game/index.html。图片生成未配置、待确认、失败或透明证据不足时不得提交最终回复。"
"{prompt}\n\n你负责首版美术素材实际生成。资产清单和美术计划只是中间结果;最终必须调用 canvas.asset_generate 生成并登记 assets/art-spritesheet.png,固定使用 1:1、1K、assetKind=art-spritesheet、assetLabel=游戏首版核心美术素材、replaceExisting=false,并写入可解析的 assets/manifest.art.json。调用前必须用 asset.list 确认 assets/art-spec.png 已登记为当前项目的 icon-spec 画布资源,并依据当前任务、game/game_design.md 与 manifest 逐项说明真实需要的玩家主体及朝向/状态、目标或收集物、障碍/场景元素和反馈特效,由 Runtime 形成 iconDescriptions;不得假设为塔防或加入合同中不存在的单位、敌人、波次、卡牌。Runtime 固定以规范图的权威 resourceId 作为 referenceId,调用 POST /api/external/v1/editor/icon-spritesheets/generations,并用 screenColor=auto 完成透明后处理;不得把 UI 原型、Data URL、Blob URL、本地路径或结构化 JSON 冒充规范图引用,不得回退普通生图或 UI extraction。缺少规范图时必须等待 art-director 依赖并如实阻塞。成功后回读 observation 与 asset.list,核对服务端返回的透明 spritesheet、真实 alpha、warning 和 sliceWarning。warning.code=postprocess-failed-source-preserved 时没有透明图集,不得登记、验收或自动重试;仅 sliceWarning 时可保留完整透明图集,但不得声称独立切片已生成。透明证据核对完成后直接交付,由 Runtime 在收束门内验证本人固定 manifest 产物并复核 Canvas 证据。已有有效同路径资产时不得重复生成或扣费;只有带 repairOfDelegationId 的唯一返工轮可 replaceExisting=true 原位替换。不得运行 game.static_smoke 或 preview.validate,也不得编辑 game/index.html。图片生成未配置、待确认、失败或透明证据不足时不得提交最终回复。"
)
}
@@ -1178,12 +1178,16 @@ mod tests {
assert!(prompt.contains("项目文件写入只允许 memory/project.md 与 game/game_design.md"));
assert!(prompt.contains("不得创建、修改、删除或补丁 game/index.html"));
assert!(prompt.contains("可以调用 command.run_limited 的 game.static_smoke"));
assert!(prompt.contains("不得调用 preview.start、preview.validate"));
assert!(prompt.contains("由 Runtime 在收束门内验证本人 owner 产物"));
assert!(
prompt.contains("不得调用 project.verify、command.run_limited、game.static_smoke")
);
assert!(prompt.contains("preview.start 或 preview.validate"));
assert!(prompt.contains("最终静态验收仍属于 preview-readiness"));
assert!(prompt.contains("浏览器验收仍属于 preview-playtest"));
assert!(prompt.contains(
"不得通过 command.exec、command.start 或其他工具启动本地预览服务、浏览器、Playwright"
));
assert!(prompt.contains("这些集成验证必须交由程序或质量 Agent 完成"));
}
}
@@ -1202,6 +1206,9 @@ mod tests {
assert!(with_canvas.contains("assets/ui-prototype.png"));
assert!(with_canvas.contains("调用 image.inspect"));
assert!(with_canvas.contains("ui-prototype.v2"));
assert!(with_canvas.contains("成功只表示候选图片已生成并登记,不等于视觉验收完成"));
assert!(with_canvas.contains("由 Runtime 在收束门内同时核对固定 owner 文档"));
assert!(!with_canvas.contains("成功动作本身就是当前 revision 的验证"));
assert!(with_canvas.contains("informationHud"));
assert!(with_canvas.contains("failureRestartFlow"));
assert!(with_canvas.contains("不得假设为塔防"));
@@ -1211,6 +1218,24 @@ mod tests {
.contains("不得误用 POST /api/external/v1/editor/ui-designs/assets/extractions"));
}
#[test]
fn agent_prompt_art_director_switches_between_read_only_and_conditional_canvas_owner() {
let without_canvas =
game_creator_art_director_tool_plan_prompt("shared runtime contract", false);
assert!(without_canvas.contains("这是只读协调任务"));
assert!(without_canvas.contains("只完成正式 director 结论并直接交付"));
assert!(without_canvas.contains("不调用 canvas.asset_generate"));
assert!(without_canvas.contains("图片产物与验收条款在本轮不适用"));
let with_canvas =
game_creator_art_director_tool_plan_prompt("shared runtime contract", true);
assert!(with_canvas.contains("outputPath=assets/art-spec.png"));
assert!(with_canvas.contains("assetKind=icon-spec"));
assert!(with_canvas.contains("成功只表示固定候选已生成并登记,不等于视觉门已经通过"));
assert!(with_canvas.contains("由 Runtime 在收束时核对当前 revision"));
assert!(!with_canvas.contains("成功会为本人当前 revision 形成验证凭证"));
}
#[test]
fn agent_prompt_art_asset_plan_uses_fixed_transparent_spritesheet_route_and_warnings() {
let without_canvas =
@@ -1234,6 +1259,7 @@ mod tests {
assert!(with_canvas.contains("warning.code=postprocess-failed-source-preserved"));
assert!(with_canvas.contains("不得登记、验收或自动重试"));
assert!(with_canvas.contains("仅 sliceWarning"));
assert!(with_canvas.contains("由 Runtime 在收束门内验证本人固定 manifest 产物"));
}
#[test]
@@ -140,6 +140,7 @@ pub(crate) use tool_plan_protocol::{
parse_game_creator_agent_tool_plan_response,
};
pub(crate) use tool_policy_snapshot::{
agent_runtime_executable_tools, agent_runtime_native_executable_tools,
agent_runtime_tool_policy_snapshot_for_run_at, AGENT_RUNTIME_CANVAS_ASSET_KINDS,
agent_runtime_autonomous_design_foundation_command_is_allowed, agent_runtime_executable_tools,
agent_runtime_native_executable_tools, agent_runtime_tool_policy_snapshot_for_run_at,
AGENT_RUNTIME_CANVAS_ASSET_KINDS,
};
@@ -176,9 +176,18 @@ pub(super) const AGENT_RUNTIME_AUTONOMOUS_SPECIALIST_MUTATION_ONLY_REPAIR_ERROR_
"自主构建非只读专业 Agent 必须先完成本人 run 的项目修改";
pub(super) const AGENT_RUNTIME_AUTONOMOUS_SPECIALIST_VERIFICATION_ONLY_REPAIR_ERROR_PREFIX: &str =
"自主构建非只读专业 Agent 必须先验证本人 run 的项目修改";
pub(super) const AGENT_RUNTIME_AUTONOMOUS_OWNER_ARTIFACT_IDENTITY_ERROR_PREFIX: &str =
"自主构建固定 owner 的 Runtime 内部产物验证身份不可用";
pub(super) const AGENT_RUNTIME_AUTONOMOUS_READ_ONLY_MUTATION_ERROR_PREFIX: &str =
"自主构建只读专业 Agent 禁止执行写入或副作用动作";
pub(in crate::agent) fn agent_runtime_autonomous_uses_owner_artifact_validation(
agent_id: &str,
) -> bool {
!autonomous_manifest_owner_artifact_paths(agent_id).is_empty()
&& !matches!(agent_id, "code-prototype" | "publish-package")
}
fn autonomous_initial_collaboration_contract_error(detail: impl AsRef<str>) -> String {
format!(
"{AGENT_RUNTIME_SUPERVISOR_INITIAL_COLLABORATION_LIVENESS_ERROR_PREFIX}autonomous-game-build 首批协作合同无效:{}",
@@ -439,7 +448,7 @@ pub(crate) fn refresh_agent_runtime_autonomous_convergence_snapshot_after_provid
))
}
pub(super) fn validate_agent_runtime_autonomous_plan_liveness_at(
pub(in crate::agent) fn validate_agent_runtime_autonomous_plan_liveness_at(
root: &Path,
agent_id: &str,
run_id: &str,
@@ -509,6 +518,15 @@ pub(super) fn validate_agent_runtime_autonomous_plan_liveness_at(
}
}
if autonomous_owner_artifact_validation_available_for_run_at(root, agent_id, run_id)?
&& !plan.response.trim().is_empty()
{
// 固定 pre-code 产物没有 Provider 可调用的验证动作。专业回复门确认
// 当前 run 已有 mutation 后,即使此前累积了较长只读尾部,也必须让
// finalization 进入 Runtime 确定性验证,不能再强迫模型伪造验证动作。
return Ok(());
}
let has_current_playtest_receipt = if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
let contract = read_autonomous_completion_contract(root, agent_id, run_id)?;
match contract.as_ref() {
@@ -783,14 +801,22 @@ pub(in crate::agent) fn agent_runtime_autonomous_verified_delivery_allows_plan_c
agent_id: &str,
verification_gate: &AgentRuntimeVerificationGate,
) -> bool {
agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
&& verification_gate
.mutation_revision
.is_some_and(|mutation_revision| {
verification_gate
.verified_revision
.is_some_and(|verified_revision| verified_revision >= mutation_revision)
})
if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
return false;
}
let Some(mutation_revision) = verification_gate.mutation_revision else {
return false;
};
if agent_id == "code-prototype"
&& !verification_gate
.static_smoke_verified_revision
.is_some_and(|revision| revision >= mutation_revision)
{
return false;
}
verification_gate
.verified_revision
.is_some_and(|verified_revision| verified_revision >= mutation_revision)
&& verification_gate.last_verification_status.as_deref()
== Some(AGENT_RUNTIME_VERIFICATION_STATUS_PASSED)
}
@@ -841,9 +867,11 @@ pub(in crate::agent) fn validate_agent_runtime_autonomous_read_only_delivery_pla
}
pub(in crate::agent) fn validate_agent_runtime_autonomous_specialist_response_delivery(
root: &Path,
agent_id: &str,
run_id: &str,
read_only_delivery: bool,
runtime_owner_artifact_validation_available: bool,
verification_gate: &AgentRuntimeVerificationGate,
plan: &AgentRuntimeToolPlan,
) -> Result<(), String> {
@@ -861,12 +889,42 @@ pub(in crate::agent) fn validate_agent_runtime_autonomous_specialist_response_de
"{AGENT_RUNTIME_AUTONOMOUS_SPECIALIST_MUTATION_ONLY_REPAIR_ERROR_PREFIX};当前 respond_to_user 没有本人 run 的 mutationRevision。必须先执行实际项目修改,不能以其它 Agent 的 revision、只读检查、空验证或任务文案代替"
));
}
if runtime_owner_artifact_validation_available
&& agent_runtime_autonomous_uses_owner_artifact_validation(agent_id)
{
return Ok(());
}
let trusted_game_chat_canvas_delivery = agent_id == "art-asset-plan"
&& game_chat_delegated_art_asset_plan_uses_canvas_verification_at(root, agent_id, run_id)?;
if agent_runtime_autonomous_uses_owner_artifact_validation(agent_id)
&& !trusted_game_chat_canvas_delivery
{
return Err(format!(
"{AGENT_RUNTIME_AUTONOMOUS_OWNER_ARTIFACT_IDENTITY_ERROR_PREFIX};当前 {agent_id}/{run_id} 既不是可信完整 autonomous DAG 的当前固定 owner child,也不是可信 game-chat code-prototype 的当前动态美术委派,不能借用 Runtime 内部验证或普通 Canvas 凭证"
));
}
if trusted_game_chat_canvas_delivery
&& (verification_gate.last_verification_tool.as_deref() != Some("canvas.asset_generate")
|| verification_gate.static_smoke_verified_revision.is_some())
{
return Err(format!(
"{AGENT_RUNTIME_AUTONOMOUS_SPECIALIST_VERIFICATION_ONLY_REPAIR_ERROR_PREFIX};可信 game-chat 动态美术委派只接受本人 canvas.asset_generate 的通过凭证,不能借用 project.verify、game.static_smoke 或其它验证"
));
}
if !agent_runtime_autonomous_verified_delivery_allows_plan_completion(
agent_id,
verification_gate,
) {
let required_verification = if agent_id == "code-prototype" {
format!(
"code-prototype 必须由本人 run 执行 game.static_smoke,且 staticSmokeVerifiedRevision 覆盖 mutationRevision;当前 staticSmokeVerifiedRevision={:?}",
verification_gate.static_smoke_verified_revision
)
} else {
"必须先只验证本人 run 的最新 mutation revision".to_string()
};
return Err(format!(
"{AGENT_RUNTIME_AUTONOMOUS_SPECIALIST_VERIFICATION_ONLY_REPAIR_ERROR_PREFIX};当前 mutationRevision={:?}、verifiedRevision={:?}、verificationStatus={}必须先只验证本人 run 的最新 mutation revision,通过后才能 respond_to_user",
"{AGENT_RUNTIME_AUTONOMOUS_SPECIALIST_VERIFICATION_ONLY_REPAIR_ERROR_PREFIX};当前 mutationRevision={:?}、verifiedRevision={:?}、verificationStatus={}{required_verification},通过后才能 respond_to_user",
verification_gate.mutation_revision,
verification_gate.verified_revision,
verification_gate
@@ -1763,6 +1821,118 @@ mod tests {
}
}
#[test]
fn autonomous_owner_delivery_uses_internal_validation_only_for_fixed_pre_code_roles() {
let temporary = tempfile::tempdir().expect("create owner delivery policy root");
let root = temporary.path().join("project");
init_local_game_project_at(&root, "owner-delivery-policy", "owner 收束策略")
.expect("init owner delivery policy project");
let plan = AgentRuntimeToolPlan {
response: "玩法规格与界面基础已经完成。".to_string(),
..AgentRuntimeToolPlan::default()
};
let gate_for = |agent_id: &str, run_id: &str| AgentRuntimeVerificationGate {
schema_version: "test".to_string(),
project_id: "test".to_string(),
agent_id: agent_id.to_string(),
run_id: run_id.to_string(),
requires_verification: true,
mutation_revision: Some(7),
verified_revision: None,
last_mutation_tool: Some("file.write".to_string()),
last_verification_tool: None,
last_verification_status: None,
static_smoke_verified_revision: None,
failed_playtest_revision: None,
updated_at: 0,
};
for agent_id in [
"design-foundation",
"balance-seed",
"art-asset-plan",
"audio-asset-plan",
] {
assert!(agent_runtime_autonomous_uses_owner_artifact_validation(
agent_id
));
let run_id = format!("{agent_id}-run");
let gate = gate_for(agent_id, &run_id);
let unavailable_error = validate_agent_runtime_autonomous_specialist_response_delivery(
&root, agent_id, &run_id, false, false, &gate, &plan,
)
.expect_err("fixed owner must not fall back to Provider-visible verification");
assert!(unavailable_error
.contains(AGENT_RUNTIME_AUTONOMOUS_OWNER_ARTIFACT_IDENTITY_ERROR_PREFIX));
assert!(!unavailable_error.starts_with(
AGENT_RUNTIME_AUTONOMOUS_SPECIALIST_VERIFICATION_ONLY_REPAIR_ERROR_PREFIX
));
validate_agent_runtime_autonomous_specialist_response_delivery(
&root, agent_id, &run_id, false, true, &gate, &plan,
)
.expect("trusted Runtime owner-artifact validation may run inside finalization");
let mut manually_verified = gate.clone();
manually_verified.verified_revision = Some(7);
manually_verified.last_verification_tool = Some("game.static_smoke".to_string());
manually_verified.last_verification_status =
Some(AGENT_RUNTIME_VERIFICATION_STATUS_PASSED.to_string());
manually_verified.static_smoke_verified_revision = Some(7);
assert!(
validate_agent_runtime_autonomous_specialist_response_delivery(
&root,
agent_id,
&run_id,
false,
false,
&manually_verified,
&plan,
)
.expect_err("manual smoke must not replace fixed owner Runtime validation identity")
.contains(AGENT_RUNTIME_AUTONOMOUS_OWNER_ARTIFACT_IDENTITY_ERROR_PREFIX)
);
let mut wrong_run_gate = gate;
wrong_run_gate.run_id = "other-run".to_string();
assert!(
validate_agent_runtime_autonomous_specialist_response_delivery(
&root,
agent_id,
&run_id,
false,
true,
&wrong_run_gate,
&plan,
)
.expect_err("another run's gate must not authorize Runtime owner validation")
.contains("verification gate 与当前 run 身份不匹配")
);
}
for agent_id in ["code-prototype", "publish-package", "art-director"] {
assert!(!agent_runtime_autonomous_uses_owner_artifact_validation(
agent_id
));
}
let publish_gate = gate_for("publish-package", "publish-package-run");
assert!(
validate_agent_runtime_autonomous_specialist_response_delivery(
&root,
"publish-package",
"publish-package-run",
false,
true,
&publish_gate,
&plan,
)
.expect_err(
"caller flag must not classify publish-package as an internal-validation owner"
)
.contains(AGENT_RUNTIME_AUTONOMOUS_SPECIALIST_VERIFICATION_ONLY_REPAIR_ERROR_PREFIX)
);
}
#[test]
fn autonomous_read_only_validation_actions_are_role_scoped() {
let plan_for = |tool: &str, input: serde_json::Value| AgentRuntimeToolPlan {
@@ -373,6 +373,8 @@ pub(crate) fn finish_agent_runtime_project_verification_locked(
}
if verification_tool.as_deref() == Some("game.static_smoke") {
gate.static_smoke_verified_revision = passed.then_some(current_revision.revision);
} else if verification_tool.as_deref() == Some(AGENT_RUNTIME_OWNER_ARTIFACT_VERIFICATION_TOOL) {
gate.static_smoke_verified_revision = None;
}
gate.last_verification_status = Some(
if passed {
@@ -1391,17 +1393,217 @@ pub(in crate::agent) fn project_verification_completion_blocker_at_locked(
}
}
fn owner_artifact_verification_is_current(
gate: &AgentRuntimeVerificationGate,
current_revision: u64,
) -> bool {
gate.last_verification_tool.as_deref() == Some(AGENT_RUNTIME_OWNER_ARTIFACT_VERIFICATION_TOOL)
&& gate.last_verification_status.as_deref()
== Some(AGENT_RUNTIME_VERIFICATION_STATUS_PASSED)
&& gate.static_smoke_verified_revision.is_none()
&& gate.mutation_revision.is_some_and(|mutation_revision| {
gate.verified_revision.is_some_and(|verified_revision| {
verified_revision >= mutation_revision && verified_revision <= current_revision
})
})
}
fn ensure_owner_artifact_validation_audit_at_locked(
root: &Path,
agent_id: &str,
run_id: &str,
revision: u64,
paths: &[&str],
) -> Result<(), String> {
let binding = read_game_creator_agent_runtime_run_profile_binding(root, agent_id, run_id)?
.ok_or_else(|| "owner-artifact 验证审计缺少 Run Profile 绑定".to_string())?;
let (records, _) =
read_agent_db_records_bounded(root, AGENT_RUNTIME_ACTION_HISTORY_MAX_DB_BYTES)?;
let audit_exists = records.iter().rev().any(|record| {
record.get("recordType").and_then(serde_json::Value::as_str)
== Some("agent.runtime.owner_artifacts.validated")
&& record.get("agentId").and_then(serde_json::Value::as_str) == Some(agent_id)
&& record.get("runId").and_then(serde_json::Value::as_str) == Some(run_id)
&& record.get("rootRunId").and_then(serde_json::Value::as_str)
== Some(binding.root_run_id.as_str())
&& record
.get("runProfileBindingFingerprint")
.and_then(serde_json::Value::as_str)
== Some(binding.binding_fingerprint.as_str())
&& record.get("revision").and_then(serde_json::Value::as_u64) == Some(revision)
&& record
.get("verificationTool")
.and_then(serde_json::Value::as_str)
== Some(AGENT_RUNTIME_OWNER_ARTIFACT_VERIFICATION_TOOL)
&& record
.get("paths")
.and_then(serde_json::Value::as_array)
.is_some_and(|items| {
items.len() == paths.len()
&& items
.iter()
.zip(paths)
.all(|(item, path)| item.as_str().is_some_and(|value| value == *path))
})
});
if audit_exists {
return Ok(());
}
append_agent_db_record(
root,
serde_json::json!({
"recordType": "agent.runtime.owner_artifacts.validated",
"agentId": agent_id,
"runId": run_id,
"rootAgentId": binding.root_agent_id,
"rootRunId": binding.root_run_id,
"parentAgentId": binding.parent_agent_id,
"parentRunId": binding.parent_run_id,
"source": binding.source,
"runProfile": binding.profile,
"runProfileBindingFingerprint": binding.binding_fingerprint,
"revision": revision,
"verificationTool": AGENT_RUNTIME_OWNER_ARTIFACT_VERIFICATION_TOOL,
"paths": paths,
}),
)
}
pub(in crate::agent) fn evaluate_project_verification_completion_at_locked(
root: &Path,
agent_id: &str,
run_id: &str,
observations: &[AgentRuntimeToolObservation],
) -> Result<Option<AgentRuntimeToolObservation>, String> {
if let Some(blocker) = project_verification_completion_blocker(observations) {
return Ok(Some(blocker));
}
let revision = read_game_creator_agent_runtime_project_revision(root)?;
let gate = read_game_creator_agent_runtime_verification_gate(root, agent_id, run_id)?;
let runtime_owner_artifact_validation_available =
autonomous_owner_artifact_validation_available_for_run_at(root, agent_id, run_id)?;
let mut gate = read_game_creator_agent_runtime_verification_gate(root, agent_id, run_id)?;
let autonomous_owner_artifact_role =
if agent_runtime_autonomous_uses_owner_artifact_validation(agent_id) {
match read_game_creator_agent_runtime_run_profile_binding(root, agent_id, run_id)? {
Some(binding) => binding.profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD,
None => {
read_latest_game_creator_agent_runtime_task_by_run_id(root, agent_id, run_id)?
.is_some_and(|task| {
task.run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
})
}
}
} else {
false
};
let trusted_game_chat_canvas_delivery = autonomous_owner_artifact_role
&& game_chat_delegated_art_asset_plan_uses_canvas_verification_at(root, agent_id, run_id)?;
if autonomous_owner_artifact_role
&& !runtime_owner_artifact_validation_available
&& !trusted_game_chat_canvas_delivery
{
return Ok(Some(agent_runtime_verification_blocker(
"当前 owner Run 不具备可用的验证身份,不能把任务标记为完成",
"只有完整 GUI/CLI autonomous DAG 的当前 fixed owner 可由 Runtime 内部验证;只有可信 game-chat code-prototype 的当前 art-asset-plan 动态委派可沿用普通 Canvas 验证。",
)));
}
if trusted_game_chat_canvas_delivery
&& (gate.last_verification_tool.as_deref() != Some("canvas.asset_generate")
|| gate.last_verification_status.as_deref()
!= Some(AGENT_RUNTIME_VERIFICATION_STATUS_PASSED)
|| gate.static_smoke_verified_revision.is_some()
|| !gate.mutation_revision.is_some_and(|mutation_revision| {
gate.verified_revision
.is_some_and(|verified_revision| verified_revision >= mutation_revision)
}))
{
return Ok(Some(agent_runtime_verification_blocker(
"game-chat 动态 art-asset-plan 尚未形成可信 Canvas 交付凭证",
"当前动态美术 child 必须由本人 canvas.asset_generate 生成正式素材并通过当前 mutation revisionproject.verify、game.static_smoke 或其它 Agent 的凭证均不能替代。",
)));
}
if runtime_owner_artifact_validation_available && gate.requires_verification {
let paths =
match validate_autonomous_owner_artifacts_for_run_at_locked(root, agent_id, run_id) {
Ok(paths) => paths,
Err(error) => {
return Ok(Some(agent_runtime_verification_blocker(
"当前 owner 正式产物尚未通过 Runtime 结构验证",
error,
)));
}
};
let owner_artifact_verification_current =
owner_artifact_verification_is_current(&gate, revision.revision);
let audit_revision = if owner_artifact_verification_current {
gate.verified_revision
.expect("current owner-artifact verification has a revision")
} else {
revision.revision
};
ensure_owner_artifact_validation_audit_at_locked(
root,
agent_id,
run_id,
audit_revision,
paths,
)?;
if !owner_artifact_verification_current {
let (expected_revision, verification_gate) =
begin_agent_runtime_project_verification_locked(
root,
agent_id,
run_id,
AGENT_RUNTIME_OWNER_ARTIFACT_VERIFICATION_TOOL,
)?;
finish_agent_runtime_project_verification_locked(
root,
&expected_revision,
verification_gate,
true,
)?;
gate = read_game_creator_agent_runtime_verification_gate(root, agent_id, run_id)?;
}
}
let autonomous_code_prototype = if agent_id == "code-prototype" {
match read_game_creator_agent_runtime_run_profile_binding(root, agent_id, run_id)? {
Some(binding) => binding.profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD,
None => read_latest_game_creator_agent_runtime_task_by_run_id(root, agent_id, run_id)?
.is_some_and(|task| {
task.run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
}),
}
} else {
false
};
if autonomous_code_prototype {
ensure_current_autonomous_ready_child_mutation_at_locked(root, agent_id, run_id)?;
let Some(mutation_revision) = gate.mutation_revision.filter(|revision| *revision > 0)
else {
return Ok(Some(agent_runtime_verification_blocker(
"code-prototype 尚未形成本人 run 的项目修改,不能把任务标记为完成",
"完整 autonomous DAG 的 code-prototype 必须先实际修改可玩入口,再由本人执行 game.static_smoke。",
)));
};
if !gate
.static_smoke_verified_revision
.is_some_and(|revision| revision >= mutation_revision)
{
return Ok(Some(agent_runtime_verification_blocker(
"code-prototype 尚未通过覆盖本人最后一次修改的 game.static_smoke",
format!(
"mutationRevision={mutation_revision}, staticSmokeVerifiedRevision={}project.verify 或其它 Agent 的验证凭证不能替代本人 run 的 game.static_smoke。",
gate.static_smoke_verified_revision
.map(|revision| revision.to_string())
.unwrap_or_else(|| "none".to_string())
),
)));
}
}
let owner_artifact_verification_current = runtime_owner_artifact_validation_available
&& owner_artifact_verification_is_current(&gate, revision.revision);
if !owner_artifact_verification_current {
if let Some(blocker) = project_verification_completion_blocker(observations) {
return Ok(Some(blocker));
}
}
if let Some(status) = gate.last_verification_status.as_deref() {
if status == AGENT_RUNTIME_VERIFICATION_STATUS_RUNNING {
return Ok(Some(agent_runtime_verification_blocker(
@@ -1,5 +1,6 @@
use super::*;
use crate::mcp::GAME_CREATOR_MCP_CALL_TOOL;
use platform_llm::LlmFunctionTool;
const AGENT_RUNTIME_COMPLETION_BLOCKER_TOOL_PLAN_PROTOCOL: &str = "通用完成阻断规则:如果最新 observation 的 tool 为 runtime.autonomous_completion 且 status 为 blocked,本轮禁止调用 respond_to_user;必须先读取该 observation.detail 的 nextRequiredAction,并据此调用合适的读取、修复和验证工具。只有完成要求的动作、取得后续可信 observation 且完成门禁不再阻断后,才能给最终回复;不得反复提交 final response,也不得按项目正文硬编码某一种 blocker 的处理方式。";
@@ -63,6 +64,36 @@ fn game_creator_agent_context_preload_notice(agent_id: &str) -> &'static str {
}
}
pub(in crate::agent) fn remove_autonomous_owner_manual_verification_tools(
tools: &mut Vec<LlmFunctionTool>,
) -> Result<(), String> {
let project_verify = native_runtime_function_name("project.verify")
.ok_or_else(|| "无法生成 project.verify 原生函数名".to_string())?;
let limited_command = native_runtime_function_name("command.run_limited")
.ok_or_else(|| "无法生成 command.run_limited 原生函数名".to_string())?;
tools.retain(|tool| tool.name != project_verify && tool.name != limited_command);
Ok(())
}
pub(in crate::agent) fn remove_autonomous_art_director_non_canvas_validation_tools(
tools: &mut Vec<LlmFunctionTool>,
) -> Result<(), String> {
let denied_function_names = [
"project.verify",
"command.run_limited",
"preview.start",
"preview.validate",
]
.into_iter()
.map(|tool| {
native_runtime_function_name(tool)
.ok_or_else(|| format!("无法生成 art-director 禁用工具函数名:{tool}"))
})
.collect::<Result<BTreeSet<_>, _>>()?;
tools.retain(|tool| !denied_function_names.contains(&tool.name));
Ok(())
}
pub(in crate::agent) fn build_game_creator_agent_background_tool_plan_request(
root: &Path,
agent_id: &str,
@@ -93,8 +124,10 @@ pub(in crate::agent) fn build_game_creator_agent_background_tool_plan_request(
agent_runtime_tool_policy_snapshot_for_run_at(root, agent_id, run_id, None, None)?;
let autonomous_game_build =
tool_policy.run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD;
let autonomous_project_verify_available =
!autonomous_game_build || agent_runtime_autonomous_project_verify_available(root);
let runtime_owner_artifact_validation_available = autonomous_game_build
&& autonomous_owner_artifact_validation_available_for_run_at(root, agent_id, run_id)?;
let autonomous_project_verify_available = !runtime_owner_artifact_validation_available
&& (!autonomous_game_build || agent_runtime_autonomous_project_verify_available(root));
let mut allowed_tools = tool_policy.allowed_tools.clone();
let mut auto_tools = tool_policy.auto_tools.clone();
let mut confirm_tools = tool_policy.confirm_tools.clone();
@@ -148,6 +181,16 @@ pub(in crate::agent) fn build_game_creator_agent_background_tool_plan_request(
let loop_index = loop_index.saturating_add(1);
let context_preload_notice = game_creator_agent_context_preload_notice(agent_id);
let canvas_asset_kind_catalog = AGENT_RUNTIME_CANVAS_ASSET_KINDS.join("|");
let limited_command_contract = if runtime_owner_artifact_validation_available {
"当前固定 owner 不得调用 command.run_limited 或 project.verify;完成固定正式产物后直接交付,由 Runtime 在收束门内执行结构验证。".to_string()
} else {
"command.run_limited 使用 {\"commandId\":\"game.static_smoke\"}。".to_string()
};
let project_tools_contract = if runtime_owner_artifact_validation_available {
"project.search 使用 {\"query\":\"要查找的字面文本\",\"path\":\"\",\"maxResults\":20,\"caseSensitive\":false},path 为空字符串时搜索整个项目,返回 path:line 和匹配行;当前固定 owner 的函数目录不广告 project.verifyproject.checkpoint 使用空对象,只用于多个 file.* 写动作前或需要独立回退点时创建本地 checkpointproject.patchset 会自动创建 checkpoint,不要为同一批变更额外调用 project.checkpointproject.restore 使用 {\"checkpointId\":\"checkpoint id\"}project.diff 使用 {\"checkpointId\":\"checkpoint id\",\"includeContent\":true,\"maxFiles\":20,\"maxChars\":24000}git.inspect 使用 {\"includeDiff\":true,\"maxFiles\":20,\"maxChars\":24000},只读项目根 Git 状态和有界 diff,不得用它提交、暂存、切分支、合并、重置、stash、worktree 或访问 remote。".to_string()
} else {
"project.search 使用 {\"query\":\"要查找的字面文本\",\"path\":\"\",\"maxResults\":20,\"caseSensitive\":false},path 为空字符串时搜索整个项目,返回 path:line 和匹配行;project.verify 使用 {\"script\":\"check|typecheck|test|lint|build\",\"expectedCommand\":\"从 package.json 读取的完整原始脚本\",\"timeoutSeconds\":120},只执行项目根 package.json 中同名 npm 脚本,expectedCommand 不一致时拒绝执行,确认策略以当前工具策略中 project.verify 的独立权限为准;project.checkpoint 使用空对象,只用于多个 file.* 写动作前或需要独立回退点时创建本地 checkpointproject.patchset 会自动创建 checkpoint,不要为同一批变更额外调用 project.checkpointproject.restore 使用 {\"checkpointId\":\"checkpoint id\"}project.diff 使用 {\"checkpointId\":\"checkpoint id\",\"includeContent\":true,\"maxFiles\":20,\"maxChars\":24000}git.inspect 使用 {\"includeDiff\":true,\"maxFiles\":20,\"maxChars\":24000},只读项目根 Git 状态和有界 diff,不得用它提交、暂存、切分支、合并、重置、stash、worktree 或访问 remote。".to_string()
};
let prompt = format!(
concat!(
"当前工具策略:\n{tool_policy_json}\n\n",
@@ -162,10 +205,10 @@ pub(in crate::agent) fn build_game_creator_agent_background_tool_plan_request(
"计划更新约定:复杂任务首次拆解、实际进度变化、steer 改变顺序或最终收束时调用 update_agent_planarguments 只提交 {{\"explanation\":\"本次计划变化\",\"steps\":[{{\"step\":\"步骤\",\"status\":\"pending|in_progress|completed\"}}]}};无需更新时不要调用 update_agent_plan。steps 最多 8 条且同时最多一个 in_progress,已完成步骤必须继续保留且不得回退。持久计划仍有 pending / in_progress 时不得调用 respond_to_userRuntime 也不会按动作返回顺序自动完成步骤。\n\n",
"工具 input 字段约定:下列每个示例对象都必须放入对应动作函数的 arguments.inputarguments 外层严格为 {{\"reason\":\"为什么需要\",\"input\":{{...}}}},禁止把 input 扁平到 arguments 顶层。\n",
"memory.read 使用 {{\"scope\":\"session|project|blackboard|agent\"}}memory.write 使用 {{\"scope\":\"agent|project|session|blackboard\",\"title\":\"标题\",\"content\":\"要沉淀的稳定结论\",\"mode\":\"append|overwrite\"}},其中 agent scope 只能写当前 Agent 自己的私有记忆,跨 Agent 共享请用 blackboard.write 或 agent.message。\n",
"project.search 使用 {{\"query\":\"要查找的字面文本\",\"path\":\"\",\"maxResults\":20,\"caseSensitive\":false}},path 为空字符串时搜索整个项目,返回 path:line 和匹配行;project.verify 使用 {{\"script\":\"check|typecheck|test|lint|build\",\"expectedCommand\":\"从 package.json 读取的完整原始脚本\",\"timeoutSeconds\":120}},只执行项目根 package.json 中同名 npm 脚本,expectedCommand 不一致时拒绝执行,确认策略以当前工具策略中 project.verify 的独立权限为准;project.checkpoint 使用空对象,只用于多个 file.* 写动作前或需要独立回退点时创建本地 checkpointproject.patchset 会自动创建 checkpoint,不要为同一批变更额外调用 project.checkpointproject.restore 使用 {{\"checkpointId\":\"checkpoint id\"}}project.diff 使用 {{\"checkpointId\":\"checkpoint id\",\"includeContent\":true,\"maxFiles\":20,\"maxChars\":24000}}git.inspect 使用 {{\"includeDiff\":true,\"maxFiles\":20,\"maxChars\":24000}},只读项目根 Git 状态和有界 diff,不得用它提交、暂存、切分支、合并、重置、stash、worktree 或访问 remote。\n",
"{project_tools_contract}\n",
"project.patchset 的每个 change 必须显式提交七个字段。create 使用 {{\"operation\":\"create\",\"path\":\"项目内相对文件\",\"content\":\"完整内容\",\"expectedSha256\":null,\"oldText\":null,\"newText\":null,\"expectedReplacements\":null}}update 使用 {{\"operation\":\"update\",\"path\":\"项目内相对文件\",\"content\":null,\"expectedSha256\":\"file.read 返回的 SHA-256\",\"oldText\":\"精确原文\",\"newText\":\"替换后文本\",\"expectedReplacements\":1}}delete 使用 {{\"operation\":\"delete\",\"path\":\"项目内相对文件\",\"content\":null,\"expectedSha256\":\"file.read 返回的 SHA-256\",\"oldText\":null,\"newText\":null,\"expectedReplacements\":null}}。成功后必须用返回的 checkpointId 调用 project.diff includeContent=true 审查整体变更。\n",
"file.list 使用 {{\"path\":\"\"}},path 为空字符串时列出项目摘要;file.read 使用 {{\"path\":\"项目内相对路径\",\"startLine\":1,\"maxLines\":120}}file.write 使用 {{\"path\":\"项目内相对路径\",\"content\":\"完整文件内容\"}}file.patch 使用 {{\"path\":\"项目内相对路径\",\"oldText\":\"必须精确匹配的原文\",\"newText\":\"替换后的文本\",\"expectedReplacements\":1}}file.delete 使用 {{\"path\":\"项目内相对路径\"}},只删除项目内普通文件,不删除目录或任何 .agent 控制面文件。\n",
"task.create 使用 {{\"taskId\":null,\"title\":\"任务标题\",\"group\":\"design|art|code|balance|audio|publishing\",\"role\":\"角色名\",\"dependencies\":[],\"artifacts\":[],\"acceptanceCriteria\":[\"验收标准\"],\"status\":\"pending|running|waiting-for-confirmation|completed|failed\"}},需要自定义 taskId 时把 null 替换为合法 IDtask.update 使用 {{\"taskId\":\"manifest taskId\",\"status\":\"pending|running|waiting-for-confirmation|completed|failed\"}}command.run_limited 使用 {{\"commandId\":\"game.static_smoke\"}}。\n",
"task.create 使用 {{\"taskId\":null,\"title\":\"任务标题\",\"group\":\"design|art|code|balance|audio|publishing\",\"role\":\"角色名\",\"dependencies\":[],\"artifacts\":[],\"acceptanceCriteria\":[\"验收标准\"],\"status\":\"pending|running|waiting-for-confirmation|completed|failed\"}},需要自定义 taskId 时把 null 替换为合法 IDtask.update 使用 {{\"taskId\":\"manifest taskId\",\"status\":\"pending|running|waiting-for-confirmation|completed|failed\"}}{limited_command_contract}\n",
"canvas.asset_generate 使用 {{\"prompt\":\"图片描述\",\"outputPath\":null,\"aspectRatio\":null,\"imageSize\":null,\"assetKind\":null,\"assetLabel\":null,\"replaceExisting\":false}};需要指定时,aspectRatio 只允许 1:1|2:3|3:2|9:16|16:9imageSize 只允许 0.5K|1K|2KassetKind 只允许 {canvas_asset_kind_catalog}。replaceExisting 只能在带 repairOfDelegationId 的唯一返工委派中设为 true,普通生成必须为 false,并通过配置的 External Editor API 同时写入画布、同名素材库目录和本地 assets。\n",
"blackboard.write 使用 {{\"title\":\"标题\",\"content\":\"要共享给所有 Agent 的稳定结论\"}}agent.message 使用 {{\"agentId\":\"目标 taskId\",\"content\":\"给目标 Agent 的定向消息\"}}agent.delegate 使用 {{\"agentId\":\"目标 taskId\",\"task\":\"要委派的后台任务\",\"acceptanceCriteria\":[\"可核对的语义验收条件\"],\"expectedArtifacts\":[],\"repairOfDelegationId\":null,\"runId\":null}}expectedArtifacts 无产物时传空数组且不接受 glob;返工时 repairOfDelegationId 指向已认领原 delivery 且 runId 必须为 nullagent.schedule_ready 使用 {{\"limit\":1}}agent.route_manifest 使用 {{\"strategy\":\"audit-existing-first|use-existing-art|generate-missing-art\",\"intentSummary\":\"Supervisor 自行理解的用户意图,仅 Supervisor 提交\",\"missingAssetSlots\":[]}}Supervisor 必须自行概括非空 intentSummary,并以 audit-existing-first 提交执行安全策略;code-prototype 先 asset.list 后只能按权威缺口提交 use-existing-art 或 generate-missing-art,且无需提交 intentSummaryagent.run_status 使用 {{\"agentId\":null,\"scope\":\"all\",\"delegationId\":null}},指定目标 Agent 或已认领 delegation 时把对应 null 替换为实际 IDProject Supervisor 传 delegationId 时读取当前父 run 的未截断权威返工合同。\n",
"当前请求中的每个 MCP 工具都以单独的动态函数广告;必须从实际广告函数中选择,并严格按该函数的 input schema 提交 arguments.input。server、tool、catalogFingerprint 和 toolFingerprint 由 Runtime 注入,禁止构造目录外包装调用。\n",
@@ -184,14 +227,22 @@ pub(in crate::agent) fn build_game_creator_agent_background_tool_plan_request(
steers_json = steers_json,
observations_json = observations_json,
canvas_asset_kind_catalog = canvas_asset_kind_catalog,
limited_command_contract = limited_command_contract,
project_tools_contract = project_tools_contract,
);
let command_exec_contract = provider_command_exec_contract();
let prompt = format!(
"{prompt}\n\n补充协议:project.verify 的 script 除 check、typecheck、test、lint、build 外,还可使用 check:<name>、test:<name>(例如 test:unit)、lint:<name>、typecheck:<name>、build:<name>、verify:<name>、validate:<name> 形式的命名脚本;冒号后的每个非空段必须以字母或数字开头且只能包含字母、数字、连字符、下划线或点,并且 script 与 expectedCommand 都必须原样来自项目根 package.json。{command_exec_contract};args 中的项目路径必须相对 cwd,禁止绝对路径、file URI、路径加行号以及把绝对路径嵌入脚本或说明文字。该工具默认需要精确确认,适合运行定向测试、构建检查和只读诊断。durable command.exec observation 会直接返回可复用的 sourceActionId;短 observation 不足以定位失败时,使用 command.output_read {{\"actionId\":\"该 sourceActionId\",\"startLine\":1,\"maxLines\":160}} 分页读取同一 Agent 的已清洗命令输出,并按 nextLine 继续,不要先猜 actionId 或为取得它额外查询动作历史,也不得仅凭输出尾部猜测。只有 cargo check/test/clippy/fmt/build、npm test 或命名为 check/typecheck/test/lint/build/verify/validate 的验证脚本,以及精确 node --test 测试文件可签发验证凭证;git、rg、cargo metadata 和普通 npm run 只作为诊断结果。每次成功执行 file.write、file.patch、file.delete、project.patchset 或 project.restore,以及每次真正启动 command.exec 或 command.start,都会产生新的项目 revision;最后一次修改后必须成功执行 project.verify、可验证 command.exec,或成功执行 command.run_limited 的 game.static_smoke,才能调用 respond_to_user 收束。文件回读不能替代可执行验证,验证后再次修改必须重新验证。每 {AGENT_RUNTIME_BACKGROUND_LOOP_LIMIT} 轮只是一次进度 checkpoint 与停滞检测,不是上下文压缩或 run 的终止上限;只要 observation 出现新的独立进展,就在同一 run 继续下一窗口,只有窗口没有新进展时才按停滞处理。真正的上下文压缩仅由 token 阈值或显式 compact 触发。"
);
let prompt = format!(
"{prompt}\n\n受控本地 Git 提交:git.inspect 会返回 commitSnapshotFingerprint;只有在完整审阅变更且最后一次源码修改已获得当前 revision 的 passed 验证后,才能调用 project.git_commit {{\"message\":\"提交标题和正文\",\"paths\":[\"显式相对路径\"],\"expectedHead\":\"git.inspect 返回的 head\",\"expectedSnapshotFingerprint\":\"git.inspect 返回的 commitSnapshotFingerprint\"}}。project.git_commit 最多提交 12 个显式安全路径,要求 attached branch 和空 staged index,只创建本地 commit;不得用它或 command.exec 执行 push、分支、merge、rebase、reset、stash、tag、submodule 或 worktree 写操作。"
);
let prompt = if runtime_owner_artifact_validation_available {
format!(
"{prompt}\n\n固定 owner 收束协议:当前请求不广告 project.verify 或 command.run_limited。完成本人固定路径的正式产物后直接调用 respond_to_userRuntime 会在同一活跃根、同一 Agent/run 和当前 mutation revision 上检查非空内容、JSON 解析、未完成标记与父完成合同 baseline 变化。不得为了取得验证凭证运行项目命令、静态 smoke、预览或提交 Git;文件回读也不能代替 Runtime 收束门。再次修改会使旧 owner 凭证失效,必须重新直接交付并接受收束检查。每 {AGENT_RUNTIME_BACKGROUND_LOOP_LIMIT} 轮只是一次进度 checkpoint 与停滞检测,不是上下文压缩或 run 的终止上限;只要 observation 出现新的独立进展,就在同一 run 继续下一窗口,只有窗口没有新进展时才按停滞处理。真正的上下文压缩仅由 token 阈值或显式 compact 触发。"
)
} else {
let command_exec_contract = provider_command_exec_contract();
let prompt = format!(
"{prompt}\n\n补充协议:project.verify 的 script 除 check、typecheck、test、lint、build 外,还可使用 check:<name>、test:<name>(例如 test:unit)、lint:<name>、typecheck:<name>、build:<name>、verify:<name>、validate:<name> 形式的命名脚本;冒号后的每个非空段必须以字母或数字开头且只能包含字母、数字、连字符、下划线或点,并且 script 与 expectedCommand 都必须原样来自项目根 package.json。{command_exec_contract};args 中的项目路径必须相对 cwd,禁止绝对路径、file URI、路径加行号以及把绝对路径嵌入脚本或说明文字。该工具默认需要精确确认,适合运行定向测试、构建检查和只读诊断。durable command.exec observation 会直接返回可复用的 sourceActionId;短 observation 不足以定位失败时,使用 command.output_read {{\"actionId\":\"该 sourceActionId\",\"startLine\":1,\"maxLines\":160}} 分页读取同一 Agent 的已清洗命令输出,并按 nextLine 继续,不要先猜 actionId 或为取得它额外查询动作历史,也不得仅凭输出尾部猜测。只有 cargo check/test/clippy/fmt/build、npm test 或命名为 check/typecheck/test/lint/build/verify/validate 的验证脚本,以及精确 node --test 测试文件可签发验证凭证;git、rg、cargo metadata 和普通 npm run 只作为诊断结果。每次成功执行 file.write、file.patch、file.delete、project.patchset 或 project.restore,以及每次真正启动 command.exec 或 command.start,都会产生新的项目 revision;最后一次修改后必须使用当前角色可用的 project.verify、可验证 command.exec 或 game.static_smoke 完成验证,才能调用 respond_to_user。文件回读不能替代验证,验证后再次修改必须重新验证。每 {AGENT_RUNTIME_BACKGROUND_LOOP_LIMIT} 轮只是一次进度 checkpoint 与停滞检测,不是上下文压缩或 run 的终止上限;只要 observation 出现新的独立进展,就在同一 run 继续下一窗口,只有窗口没有新进展时才按停滞处理。真正的上下文压缩仅由 token 阈值或显式 compact 触发。"
);
format!(
"{prompt}\n\n受控本地 Git 提交:git.inspect 会返回 commitSnapshotFingerprint;只有在完整审阅变更且最后一次源码修改已获得当前 revision 的 passed 验证后,才能调用 project.git_commit {{\"message\":\"提交标题和正文\",\"paths\":[\"显式相对路径\"],\"expectedHead\":\"git.inspect 返回的 head\",\"expectedSnapshotFingerprint\":\"git.inspect 返回的 commitSnapshotFingerprint\"}}。project.git_commit 最多提交 12 个显式安全路径,要求 attached branch 和空 staged index,只创建本地 commit;不得用它或 command.exec 执行 push、分支、merge、rebase、reset、stash、tag、submodule 或 worktree 写操作。"
)
};
let isolated_tool_contract =
required_runtime_prompt_section(RUNTIME_PROMPT_PROVIDER_ISOLATED_TOOL_CONTRACT_SECTION);
let prompt = format!(
@@ -226,7 +277,25 @@ pub(in crate::agent) fn build_game_creator_agent_background_tool_plan_request(
let playtest_contract = autonomous_playtest_contract_prompt(playtest_scenario);
system_prompt.push_str("\n\n");
system_prompt.push_str(playtest_contract);
system_prompt.push_str(" 只有当前 revision 通过 game.static_smoke,并由 preview.validate 对上述固定状态面和控件完成真实浏览器动作后,Runtime 才允许最终回复;不要伪造已通过 observation。");
let root_source = agent_runtime_root_source_at(root, agent_id, run_id)?;
let verification_contract = if runtime_owner_artifact_validation_available {
" 当前固定 owner 写入后直接交付;Runtime 会在收束门内检查本人正式产物,禁止调用 game.static_smoke、project.verify 或 preview.validate 冒充。"
} else if agent_id == "preview-readiness" {
" 当前只读静态验收任务必须对最终 revision 执行 game.static_smoke,不执行 preview.validate。"
} else if agent_id == "preview-playtest" {
" 当前只读试玩任务必须执行 preview.validate,不执行 game.static_smoke。"
} else if agent_id == "code-prototype"
&& root_source == AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE
{
" 当前 game-chat 主 Agent 必须对本人最终 mutation revision 依次通过 game.static_smoke 与 preview.validate。"
} else if agent_id == "code-prototype" {
" 当前程序 owner 必须对可玩入口执行 game.static_smoke;完整 DAG 的最终静态与浏览器验收仍由后续质量任务负责。"
} else if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
" 根 Supervisor 只按完成合同防御性复核最终 revision,不得把自身复核冒充专业 child 职责。"
} else {
" 当前专业任务只按自身完成合同收束,不得伪造 smoke 或 preview observation。"
};
system_prompt.push_str(verification_contract);
}
if autonomous_game_build {
let root_source = agent_runtime_root_source_at(root, agent_id, run_id)?;
@@ -249,6 +318,12 @@ pub(in crate::agent) fn build_game_creator_agent_background_tool_plan_request(
.with_response_text_verbosity(platform_llm::LlmResponseTextVerbosity::Low)
.with_function_tools(build_agent_runtime_native_function_tools(mcp_catalog)?)
.with_tool_choice(platform_llm::LlmToolChoice::Required);
if runtime_owner_artifact_validation_available {
remove_autonomous_owner_manual_verification_tools(&mut request.function_tools)?;
}
if autonomous_game_build && agent_id == "art-director" {
remove_autonomous_art_director_non_canvas_validation_tools(&mut request.function_tools)?;
}
if autonomous_game_build && !editor_api_key_is_configured() {
let canvas_function = native_runtime_function_name("canvas.asset_generate")
.ok_or_else(|| "无法生成画布素材工具函数名".to_string())?;
@@ -461,7 +536,8 @@ mod tests {
build_game_creator_agent_background_tool_plan_request,
game_creator_agent_context_preload_notice, game_creator_agent_runtime_role_overlay_prompt,
game_creator_project_supervisor_chat_system_prompt, init_local_game_project_at,
provider_command_exec_contract, provider_command_start_contract,
new_game_creation_app_seed_tasks, provider_command_exec_contract,
provider_command_start_contract, render_autonomous_manifest_ready_task_background_prompt,
required_runtime_prompt_section, resolve_agent_conversation_session_id_at,
start_game_creator_agent_runtime_task_at, AgentRuntimeTaskLink, AgentRuntimeToolPlan,
GameCreatorMcpCatalog, GameCreatorMcpCatalogTool,
@@ -585,6 +661,92 @@ mod tests {
request.messages[0].content.clone()
}
fn build_autonomous_ready_child_request(
agent_id: &str,
root_source: &str,
suffix: &str,
) -> platform_llm::LlmRunRequest {
let temporary =
crate::tests::canonical_test_tempdir(&format!("provider-owner-artifact-{suffix}-"));
let root = temporary.path().join("project");
init_local_game_project_at(
&root,
&format!("owner-artifact-{suffix}"),
"owner artifact provider contract",
)
.expect("project init");
let parent_run_id = format!("owner-artifact-parent-{suffix}");
let parent = bind_game_creator_agent_runtime_run_profile_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&parent_run_id,
root_source,
Some(AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD),
None,
)
.expect("bind autonomous parent profile");
let child_run_id = format!("owner-artifact-child-{suffix}");
let child_link = AgentRuntimeTaskLink {
parent_agent_id: Some(parent.agent_id),
parent_run_id: Some(parent.run_id),
delegation_id: None,
};
bind_game_creator_agent_runtime_run_profile_at(
&root,
agent_id,
&child_run_id,
"agent-ready-task-scheduler",
None,
Some(&child_link),
)
.expect("bind autonomous ready child profile");
let seed_task = new_game_creation_app_seed_tasks()
.into_iter()
.find(|task| task.id == agent_id)
.unwrap_or_else(|| panic!("missing seed task {agent_id}"));
let task = render_autonomous_manifest_ready_task_background_prompt(&seed_task);
assert!(
task.contains("这是 autonomous-game-build"),
"ready task prompt lost autonomous overlay: {task}"
);
let state = start_game_creator_agent_runtime_task_at(
&root,
agent_id,
&task,
&child_run_id,
"agent-ready-task-scheduler",
"构建 owner artifact planning request",
vec!["交付当前 manifest task".to_string()],
)
.expect("start autonomous ready child task");
let catalog = GameCreatorMcpCatalog {
fingerprint: String::new(),
servers: Vec::new(),
tools: Vec::new(),
};
let (_, _, request, _) = build_game_creator_agent_background_tool_plan_request(
&root,
agent_id,
&state.session_id,
&state.run_id,
&state.current_task,
&[],
0,
&catalog,
)
.expect("build autonomous ready child request");
request
}
fn request_advertises_native_tool(request: &platform_llm::LlmRunRequest, tool: &str) -> bool {
let function_name = crate::agent_native_tools::native_runtime_function_name(tool)
.expect("native runtime function name");
request
.function_tools
.iter()
.any(|function| function.name == function_name)
}
#[test]
fn context_preload_notice_matches_agent_context() {
assert_eq!(
@@ -599,6 +761,177 @@ mod tests {
);
}
#[test]
fn full_dag_pre_code_owner_requests_do_not_advertise_manual_verification() {
let _config_guard = crate::tests::write_test_local_config("{}".to_string());
for (index, agent_id) in [
"design-foundation",
"balance-seed",
"art-asset-plan",
"audio-asset-plan",
]
.into_iter()
.enumerate()
{
let root_source = if index % 2 == 0 {
AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE
} else {
AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE
};
let request = build_autonomous_ready_child_request(
agent_id,
root_source,
&format!("owner-{index}"),
);
assert!(!request_advertises_native_tool(&request, "project.verify"));
assert!(!request_advertises_native_tool(
&request,
"command.run_limited"
));
let system_prompt = &request.messages[0].content;
let user_prompt = &request.messages[1].content;
assert!(system_prompt.contains("固定 owner 写入后直接交付"));
assert!(system_prompt.contains("Runtime 会在收束门内检查本人正式产物"));
assert!(user_prompt.contains("固定 owner 收束协议"));
assert!(user_prompt.contains("当前请求不广告 project.verify 或 command.run_limited"));
assert!(!user_prompt.contains("project.verify 使用"));
assert!(!user_prompt.contains("command.run_limited 使用"));
assert!(user_prompt.contains("完成本人固定路径的正式产物后直接调用 respond_to_user"));
}
}
#[test]
fn autonomous_art_director_advertises_canvas_without_smoke_or_preview_tools() {
let _config_guard = crate::tests::write_test_local_config(
r#"{"editorApi":{"apiKey":"art-director-provider-key"}}"#.to_string(),
);
let request = build_autonomous_ready_child_request(
"art-director",
AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE,
"art-director-canvas-only",
);
assert!(request_advertises_native_tool(
&request,
"canvas.asset_generate"
));
for tool in [
"project.verify",
"command.run_limited",
"preview.start",
"preview.validate",
] {
assert!(
!request_advertises_native_tool(&request, tool),
"art-director must not advertise non-Canvas validation tool {tool}"
);
}
}
#[test]
fn playable_and_late_stage_requests_keep_their_existing_verification_boundaries() {
let _config_guard = crate::tests::write_test_local_config("{}".to_string());
let code = build_autonomous_ready_child_request(
"code-prototype",
AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE,
"code-prototype",
);
assert!(request_advertises_native_tool(&code, "command.run_limited"));
assert!(code.messages[0]
.content
.contains("程序 owner 必须对可玩入口执行 game.static_smoke"));
assert!(code.messages[1]
.content
.contains("必须对可玩入口执行 game.static_smoke"));
let readiness = build_autonomous_ready_child_request(
"preview-readiness",
AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE,
"preview-readiness",
);
assert!(request_advertises_native_tool(
&readiness,
"command.run_limited"
));
assert!(readiness.messages[0]
.content
.contains("必须对最终 revision 执行 game.static_smoke,不执行 preview.validate"));
assert!(readiness.messages[1]
.content
.contains("且只能是 command.run_limited(commandId=game.static_smoke)"));
let publish = build_autonomous_ready_child_request(
"publish-package",
AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE,
"publish-package",
);
assert!(!request_advertises_native_tool(&publish, "project.verify"));
assert!(request_advertises_native_tool(
&publish,
"command.run_limited"
));
let publish_prompts = publish
.messages
.iter()
.map(|message| message.content.as_str())
.collect::<Vec<_>>()
.join("\n");
assert!(publish_prompts.contains("不在前置固定 owner 的内部产物验证范围内"));
assert!(!publish_prompts.contains("当前固定 owner 写入后直接交付"));
assert!(!publish_prompts.contains("固定 owner 收束协议"));
}
#[test]
fn art_director_request_exposes_canvas_only_for_the_keyed_owner_route() {
{
let _config_guard = crate::tests::write_test_local_config("{}".to_string());
let request = build_autonomous_ready_child_request(
"art-director",
AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE,
"art-director-no-key",
);
assert!(!request_advertises_native_tool(
&request,
"canvas.asset_generate"
));
let prompts = request
.messages
.iter()
.map(|message| message.content.as_str())
.collect::<Vec<_>>()
.join("\n");
assert!(
prompts.contains("无生图凭据只读协调任务"),
"unexpected no-key art-director prompts: {prompts}"
);
assert!(prompts.contains("不调用 canvas.asset_generate"));
}
{
let _config_guard = crate::tests::write_test_local_config(
r#"{"editorApi":{"apiKey":"provider-art-director-key"}}"#.to_string(),
);
let request = build_autonomous_ready_child_request(
"art-director",
AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE,
"art-director-keyed",
);
assert!(request_advertises_native_tool(
&request,
"canvas.asset_generate"
));
let prompts = request
.messages
.iter()
.map(|message| message.content.as_str())
.collect::<Vec<_>>()
.join("\n");
assert!(prompts.contains("条件 Canvas owner 任务"));
assert!(prompts.contains("assets/art-spec.png"));
assert!(prompts.contains("成功只表示候选已生成并登记,不等于视觉门已经通过"));
assert!(!prompts.contains("成功会为本人当前 revision 形成验证凭证"));
}
}
#[test]
fn provider_request_source_does_not_patch_natural_language_with_replace_chains() {
let source = include_str!("provider_request_builders.rs");
@@ -289,20 +289,28 @@ pub(in crate::agent) async fn request_game_creator_agent_background_tool_plan_at
&& agent_runtime_task_requires_read_only_delivery_at(
root, agent_id, session_id, run_id, task,
)?;
let runtime_owner_artifact_validation_available = run_profile
== AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
&& autonomous_owner_artifact_validation_available_for_run_at(root, agent_id, run_id)?;
let code_prototype_requires_static_smoke = run_profile
== AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
&& agent_id == "code-prototype";
let verified_delivery = if run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD {
let verification_gate =
read_game_creator_agent_runtime_verification_gate(root, agent_id, run_id)?;
agent_runtime_autonomous_verified_delivery_allows_plan_completion(
agent_id,
&verification_gate,
)
runtime_owner_artifact_validation_available
|| agent_runtime_autonomous_verified_delivery_allows_plan_completion(
agent_id,
&verification_gate,
)
} else {
false
};
let allow_runtime_plan_completion = read_only_delivery || verified_delivery;
let autonomous_project_verify_available = run_profile
!= AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|| agent_runtime_autonomous_project_verify_available(root);
|| (!runtime_owner_artifact_validation_available
&& agent_runtime_autonomous_project_verify_available(root));
let mut autonomous_scaffold_repair_active = false;
let mut supervisor_collaboration_repair_active = false;
let mut supervisor_collaboration_repair_actions = Vec::new();
@@ -434,9 +442,11 @@ pub(in crate::agent) async fn request_game_creator_agent_background_tool_plan_at
)
})?;
validate_agent_runtime_autonomous_specialist_response_delivery(
root,
agent_id,
run_id,
read_only_delivery,
runtime_owner_artifact_validation_available,
&verification_gate,
&parsed.plan,
)
@@ -849,6 +859,11 @@ pub(in crate::agent) async fn request_game_creator_agent_background_tool_plan_at
&& protocol_error
.starts_with(AGENT_RUNTIME_AUTONOMOUS_TRUNCATED_SCAFFOLD_ERROR_PREFIX)
&& !request.function_tools.is_empty();
let force_autonomous_owner_artifact_delivery =
runtime_owner_artifact_validation_available
&& (force_autonomous_specialist_verification_only
|| force_autonomous_pending_verification
|| force_autonomous_reverify_after_mutation);
let force_supervisor_initial_collaboration =
agent_runtime_protocol_error_requires_supervisor_collaboration_repair(
&protocol_error,
@@ -871,6 +886,18 @@ pub(in crate::agent) async fn request_game_creator_agent_background_tool_plan_at
{
request.function_tools =
build_agent_runtime_native_function_tools(&mcp_catalog)?;
if runtime_owner_artifact_validation_available {
remove_autonomous_owner_manual_verification_tools(
&mut request.function_tools,
)?;
}
if run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
&& agent_id == "art-director"
{
remove_autonomous_art_director_non_canvas_validation_tools(
&mut request.function_tools,
)?;
}
if !autonomous_project_verify_available {
let project_verify_function =
native_runtime_function_name("project.verify")
@@ -920,12 +947,20 @@ pub(in crate::agent) async fn request_game_creator_agent_background_tool_plan_at
request.messages.push(LlmMessage::user(format!(
"上一条输出不符合工具计划协议:{protocol_error}\n当前是 autonomous-game-build 的非只读专业任务,本次修复的原生工具目录只保留项目 mutation 工具。必须立即完成本人 run 的实际项目修改;不得 respond_to_user、验证、更新计划、读取、搜索、查询状态或委派。首次源码字段不得超过 {AGENT_RUNTIME_AUTONOMOUS_SCAFFOLD_SOURCE_MAX_CHARS} 字符,完整写入 game/index.html 时必须保持 HTML 与 script 闭合。不要解释,不要 markdown,不要代码围栏。"
)));
} else if force_autonomous_owner_artifact_delivery {
restrict_agent_runtime_autonomous_verified_delivery_repair_tools(&mut request)?;
request.messages.push(LlmMessage::user(format!(
"上一条输出不符合工具计划协议:{protocol_error}\n当前是完整 autonomous DAG 的固定 owner 写入任务。若本人正式产物已经完整,立即 respond_to_userRuntime 会在收束门内按固定路径和父完成合同 baseline 执行结构验证。不得调用 project.verify、command.run_limited、preview、继续读取或解释;产物仍有缺口时,本轮 finalization 会返回精确 blocker,再按该 blocker 修复。不要 markdown,不要代码围栏。"
)));
} else if force_autonomous_specialist_verification_only {
restrict_agent_runtime_autonomous_reverification_repair_tools(
&mut request,
autonomous_project_verify_available,
autonomous_project_verify_available
&& !code_prototype_requires_static_smoke,
)?;
let verification_instruction = if autonomous_project_verify_available {
let verification_instruction = if code_prototype_requires_static_smoke {
"本次修复的原生工具目录只保留 command.run_limited;必须立即调用 game.static_smoke 验证本人 run 的最新 mutation revisionproject.verify 不能满足 code-prototype 的可玩入口交付合同"
} else if autonomous_project_verify_available {
"本次修复的原生工具目录只保留 project.verify 与 command.run_limited;必须立即验证本人 run 的最新 mutation revision"
} else {
"当前项目没有可用的 package.json,本次修复的原生工具目录只保留 command.run_limited;必须立即调用 game.static_smoke 验证本人 run 的最新 mutation revision"
@@ -981,9 +1016,12 @@ pub(in crate::agent) async fn request_game_creator_agent_background_tool_plan_at
} else if force_autonomous_reverify_after_mutation {
restrict_agent_runtime_autonomous_reverification_repair_tools(
&mut request,
autonomous_project_verify_available,
autonomous_project_verify_available
&& !code_prototype_requires_static_smoke,
)?;
let verification_instruction = if autonomous_project_verify_available {
let verification_instruction = if code_prototype_requires_static_smoke {
"本次修复的原生工具目录只保留 command.run_limited;必须立即调用 game.static_smoke 验证当前 revisionproject.verify 不能满足 code-prototype 的可玩入口交付合同"
} else if autonomous_project_verify_available {
"本次修复的原生工具目录只保留 project.verify 与 command.run_limited;必须立即验证当前 revision"
} else {
"当前项目没有可用的 package.json,本次修复的原生工具目录只保留 command.run_limited;必须立即调用 game.static_smoke 验证当前 revision"
@@ -994,9 +1032,12 @@ pub(in crate::agent) async fn request_game_creator_agent_background_tool_plan_at
} else if force_autonomous_pending_verification {
restrict_agent_runtime_autonomous_verification_repair_tools(
&mut request,
autonomous_project_verify_available,
autonomous_project_verify_available
&& !code_prototype_requires_static_smoke,
)?;
let verification_instruction = if autonomous_project_verify_available {
let verification_instruction = if code_prototype_requires_static_smoke {
"或在项目已经满足要求时立即调用 command.run_limited 的 game.static_smokeproject.verify 只可作为早期诊断,不能满足 code-prototype 完成合同"
} else if autonomous_project_verify_available {
"或在项目已经满足要求时立即调用 project.verify / command.run_limited"
} else {
"或在项目已经满足要求时立即调用 command.run_limited 的 game.static_smoke"
@@ -77,6 +77,30 @@ fn autonomous_game_build_agent_can_generate_canvas_asset(agent_id: &str) -> bool
)
}
pub(crate) fn agent_runtime_autonomous_design_foundation_command_is_allowed(
command_id: &str,
) -> bool {
matches!(
command_id,
"memory.read"
| "conversation.read"
| "asset.list"
| "project.index"
| "file.read"
| "project.diff"
| "project.git_inspect"
| "file.list"
| "file.write"
| "file.delete"
| "project.patchset"
| "task.list"
| "image.inspect"
| "canvas.asset_generate"
| "agent.audit"
| "agent.run_status"
)
}
pub(in crate::agent) fn agent_runtime_tool_policy_snapshot_at(
root: &Path,
agent_id: &str,
@@ -182,6 +206,37 @@ pub(crate) fn agent_runtime_tool_policy_snapshot_for_run_at(
.push("canvas.asset_generate".to_string());
}
}
if agent_id == "design-foundation" {
for tool in agent_runtime_executable_tools() {
let allowed = game_creator_agent_runtime_tool_command_id(tool)
.is_some_and(agent_runtime_autonomous_design_foundation_command_is_allowed);
if allowed {
continue;
}
snapshot.auto_tools.retain(|candidate| candidate != tool);
snapshot.confirm_tools.retain(|candidate| candidate != tool);
if !snapshot
.denied_tools
.iter()
.any(|candidate| candidate == tool)
{
snapshot.denied_tools.push(tool.to_string());
}
}
}
if autonomous_owner_artifact_validation_available_for_run_at(root, agent_id, run_id)? {
for tool in ["project.verify", "command.run_limited"] {
snapshot.auto_tools.retain(|candidate| candidate != tool);
snapshot.confirm_tools.retain(|candidate| candidate != tool);
if !snapshot
.denied_tools
.iter()
.any(|candidate| candidate == tool)
{
snapshot.denied_tools.push(tool.to_string());
}
}
}
for tool in agent_runtime_executable_tools() {
let Some(command_id) = game_creator_agent_runtime_tool_command_id(tool) else {
continue;
@@ -123,10 +123,12 @@ pub(super) const AGENT_RUNTIME_RUN_PROFILE_BINDING_SCHEMA_VERSION: &str =
pub(super) const AGENT_RUNTIME_AUTONOMOUS_COMPLETION_CONTRACT_SCHEMA_VERSION: &str =
"game-creator-autonomous-completion-contract.v2";
pub(super) const AGENT_RUNTIME_AUTONOMOUS_PLAYTEST_RECEIPT_SCHEMA_VERSION: &str =
"game-creator-autonomous-playtest-receipt.v1";
"game-creator-autonomous-playtest-receipt.v2";
pub(super) const AGENT_RUNTIME_AUTONOMOUS_GAME_INDEX_MAX_BYTES: u64 = 4 * 1024 * 1024;
pub(super) const AGENT_RUNTIME_AUTONOMOUS_BROWSER_EVIDENCE_MAX_BYTES: u64 = 16 * 1024 * 1024;
pub(super) const AGENT_RUNTIME_GAME_INDEX_PATH: &str = "game/index.html";
pub(crate) const AGENT_RUNTIME_OWNER_ARTIFACT_VERIFICATION_TOOL: &str =
"runtime.owner_artifacts_validate";
pub(super) const AGENT_RUNTIME_AUTONOMOUS_GAME_BUILD_AUTO_COMMAND_IDS: &[&str] = &[
"file.write",
"file.delete",
@@ -201,6 +203,14 @@ pub(super) struct AgentRuntimeAutonomousPlaytestReceipt {
pub(super) agent_id: String,
pub(super) run_id: String,
pub(super) run_profile_binding_fingerprint: String,
#[serde(default)]
pub(super) executor_agent_id: String,
#[serde(default)]
pub(super) executor_run_id: String,
#[serde(default)]
pub(super) executor_source: String,
#[serde(default)]
pub(super) executor_run_profile_binding_fingerprint: String,
pub(super) action_id: String,
pub(super) action_fingerprint: String,
pub(super) revision: u64,
@@ -1426,6 +1426,13 @@ pub(crate) fn game_chat_fast_path_scheduled_art_contract_repair_is_authorized_at
.ok_or_else(|| "game-chat 美术 child 缺少 Run Profile 绑定".to_string())?;
if task.agent_id != agent_id
|| task.run_id != run_id
|| child_binding.agent_id != task.agent_id
|| child_binding.run_id != task.run_id
|| child_binding.source != task.source
|| child_binding.profile != task.run_profile
|| child_binding.binding_fingerprint != task.run_profile_binding_fingerprint
|| child_binding.parent_agent_id != task.parent_agent_id
|| child_binding.parent_run_id != task.parent_run_id
|| task.source != "agent-delegate"
|| task.run_profile != AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|| task.status != "running"
@@ -1447,6 +1454,27 @@ pub(crate) fn game_chat_fast_path_scheduled_art_contract_repair_is_authorized_at
else {
return Ok(false);
};
let Some(parent_binding) =
read_game_creator_agent_runtime_run_profile_binding(root, parent_agent_id, parent_run_id)?
else {
return Ok(false);
};
let Some(root_binding) = read_game_creator_agent_runtime_run_profile_binding(
root,
&child_binding.root_agent_id,
&child_binding.root_run_id,
)?
else {
return Ok(false);
};
let Some(root_task) = read_latest_game_creator_agent_runtime_task_by_run_id(
root,
&root_binding.agent_id,
&root_binding.run_id,
)?
else {
return Ok(false);
};
let parent_is_main = parent.agent_id == "code-prototype"
&& parent.source == "agent-ready-task-scheduler"
&& parent.parent_agent_id.as_deref() == Some(GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID)
@@ -1454,6 +1482,38 @@ pub(crate) fn game_chat_fast_path_scheduled_art_contract_repair_is_authorized_at
if !parent_is_main
|| parent.run_profile != AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|| parent.status != "running"
|| parent.delegation_id.is_some()
|| parent_binding.agent_id != parent.agent_id
|| parent_binding.run_id != parent.run_id
|| parent_binding.source != parent.source
|| parent_binding.profile != parent.run_profile
|| parent_binding.binding_fingerprint != parent.run_profile_binding_fingerprint
|| parent_binding.parent_agent_id != parent.parent_agent_id
|| parent_binding.parent_run_id != parent.parent_run_id
|| parent_binding.project_id != child_binding.project_id
|| parent_binding.root_agent_id != child_binding.root_agent_id
|| parent_binding.root_run_id != child_binding.root_run_id
|| child_binding.parent_binding_fingerprint.as_deref()
!= Some(parent_binding.binding_fingerprint.as_str())
|| root_binding.project_id != child_binding.project_id
|| root_binding.agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|| root_binding.run_id != child_binding.root_run_id
|| root_binding.source != AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE
|| root_binding.profile != AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|| root_binding.root_agent_id != root_binding.agent_id
|| root_binding.root_run_id != root_binding.run_id
|| root_binding.parent_agent_id.is_some()
|| root_binding.parent_run_id.is_some()
|| parent_binding.parent_binding_fingerprint.as_deref()
!= Some(root_binding.binding_fingerprint.as_str())
|| root_task.agent_id != root_binding.agent_id
|| root_task.run_id != root_binding.run_id
|| root_task.source != root_binding.source
|| root_task.run_profile != root_binding.profile
|| root_task.run_profile_binding_fingerprint != root_binding.binding_fingerprint
|| root_task.parent_agent_id.is_some()
|| root_task.parent_run_id.is_some()
|| root_task.delegation_id.is_some()
{
return Ok(false);
}
@@ -1465,14 +1525,187 @@ pub(crate) fn game_chat_fast_path_scheduled_art_contract_repair_is_authorized_at
{
return Ok(false);
}
Ok(
read_game_chat_asset_route_at(root, &child_binding.root_run_id)?.is_some_and(|route| {
let delegation_id = task
.delegation_id
.as_deref()
.expect("validated game-chat art child has delegation id");
let Some(delivery) = read_static_delegate_delivery_at(root, delegation_id)? else {
return Ok(false);
};
let expected_delegation_id = agent_runtime_delegation_id(
&parent.agent_id,
&parent.run_id,
&task.agent_id,
&delivery.parent_action_id,
);
let route_authorizes_agent = read_game_chat_asset_route_at(root, &child_binding.root_run_id)?
.is_some_and(|route| {
route
.generated_task_ids
.iter()
.any(|task_id| task_id == agent_id)
}),
)
});
Ok(route_authorizes_agent
&& delivery.delegation_id == delegation_id
&& expected_delegation_id == delegation_id
&& delivery.parent_agent_id == parent.agent_id
&& delivery.parent_session_id == parent.session_id
&& delivery.parent_run_id == parent.run_id
&& delivery.target_agent_id == task.agent_id
&& delivery.target_session_id == task.session_id
&& delivery.target_run_id == task.run_id
&& !delivery.acceptance_criteria.is_empty()
&& delivery.expected_artifacts.len() == 1
&& delivery.expected_artifacts.first().map(String::as_str) == Some(output_path)
&& delivery.repair_of_delegation_id.is_none()
&& delivery.status == StaticDelegateDeliveryStatus::Dispatched)
}
pub(in crate::agent) fn game_chat_delegated_art_asset_plan_uses_canvas_verification_at(
root: &Path,
agent_id: &str,
run_id: &str,
) -> Result<bool, String> {
if agent_id != "art-asset-plan"
|| !game_chat_fast_path_scheduled_art_contract_repair_is_authorized_at(
root,
agent_id,
run_id,
AGENT_RUNTIME_ART_SPRITESHEET_PATH,
)?
{
return Ok(false);
}
let Some(task) = read_latest_game_creator_agent_runtime_task_by_run_id(root, agent_id, run_id)?
else {
return Ok(false);
};
let Some(delegation_id) = task
.delegation_id
.as_deref()
.filter(|value| !value.trim().is_empty())
else {
return Ok(false);
};
let Some(binding) =
read_game_creator_agent_runtime_run_profile_binding(root, agent_id, run_id)?
else {
return Ok(false);
};
if binding.agent_id != task.agent_id
|| binding.run_id != task.run_id
|| binding.source != task.source
|| binding.profile != task.run_profile
|| binding.binding_fingerprint != task.run_profile_binding_fingerprint
|| binding.parent_agent_id != task.parent_agent_id
|| binding.parent_run_id != task.parent_run_id
|| binding.source != "agent-delegate"
|| binding.profile != AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|| binding.parent_agent_id.as_deref() != Some("code-prototype")
|| binding.root_agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|| task.status != "running"
{
return Ok(false);
}
let parent_agent_id = binding
.parent_agent_id
.as_deref()
.expect("validated game-chat art child has parent agent");
let parent_run_id = binding
.parent_run_id
.as_deref()
.expect("validated game-chat art child has parent run");
let Some(parent) = read_latest_game_creator_agent_runtime_task_by_run_id(
root,
parent_agent_id,
parent_run_id,
)?
else {
return Ok(false);
};
let Some(parent_binding) =
read_game_creator_agent_runtime_run_profile_binding(root, parent_agent_id, parent_run_id)?
else {
return Ok(false);
};
let Some(root_binding) = read_game_creator_agent_runtime_run_profile_binding(
root,
&binding.root_agent_id,
&binding.root_run_id,
)?
else {
return Ok(false);
};
let Some(root_task) = read_latest_game_creator_agent_runtime_task_by_run_id(
root,
&root_binding.agent_id,
&root_binding.run_id,
)?
else {
return Ok(false);
};
if parent_binding.agent_id != parent.agent_id
|| parent_binding.run_id != parent.run_id
|| parent_binding.source != parent.source
|| parent_binding.profile != parent.run_profile
|| parent_binding.binding_fingerprint != parent.run_profile_binding_fingerprint
|| parent_binding.parent_agent_id != parent.parent_agent_id
|| parent_binding.parent_run_id != parent.parent_run_id
|| parent_binding.source != "agent-ready-task-scheduler"
|| parent_binding.profile != AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|| parent_binding.project_id != binding.project_id
|| parent_binding.root_agent_id != binding.root_agent_id
|| parent_binding.root_run_id != binding.root_run_id
|| parent_binding.parent_agent_id.as_deref() != Some(root_binding.agent_id.as_str())
|| parent_binding.parent_run_id.as_deref() != Some(root_binding.run_id.as_str())
|| parent_binding.parent_binding_fingerprint.as_deref()
!= Some(root_binding.binding_fingerprint.as_str())
|| binding.parent_binding_fingerprint.as_deref()
!= Some(parent_binding.binding_fingerprint.as_str())
|| root_binding.project_id != binding.project_id
|| root_binding.agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|| root_binding.run_id != binding.root_run_id
|| root_binding.source != AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE
|| root_binding.profile != AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|| root_binding.root_agent_id != root_binding.agent_id
|| root_binding.root_run_id != root_binding.run_id
|| root_binding.parent_agent_id.is_some()
|| root_binding.parent_run_id.is_some()
|| root_task.agent_id != root_binding.agent_id
|| root_task.run_id != root_binding.run_id
|| root_task.source != root_binding.source
|| root_task.run_profile != root_binding.profile
|| root_task.run_profile_binding_fingerprint != root_binding.binding_fingerprint
|| root_task.parent_agent_id.is_some()
|| root_task.parent_run_id.is_some()
|| root_task.delegation_id.is_some()
|| parent.status != "running"
{
return Ok(false);
}
let Some(delivery) = read_static_delegate_delivery_at(root, delegation_id)? else {
return Ok(false);
};
let expected_delegation_id = agent_runtime_delegation_id(
&parent.agent_id,
&parent.run_id,
&task.agent_id,
&delivery.parent_action_id,
);
Ok(delivery.delegation_id == delegation_id
&& expected_delegation_id == delegation_id
&& delivery.parent_agent_id == parent.agent_id
&& delivery.parent_session_id == parent.session_id
&& delivery.parent_run_id == parent.run_id
&& delivery.target_agent_id == task.agent_id
&& delivery.target_session_id == task.session_id
&& delivery.target_run_id == task.run_id
&& !delivery.acceptance_criteria.is_empty()
&& delivery.expected_artifacts.len() == 1
&& delivery.expected_artifacts.first().map(String::as_str)
== Some(AGENT_RUNTIME_ART_SPRITESHEET_PATH)
&& delivery.repair_of_delegation_id.is_none()
&& delivery.status == StaticDelegateDeliveryStatus::Dispatched)
}
pub(in crate::agent) fn game_chat_fast_path_art_slice_paths(
File diff suppressed because it is too large Load Diff
@@ -1460,8 +1460,14 @@ fn render_autonomous_manifest_ready_task_owner_prompt(task: &GameCreationAppTask
} else {
""
};
let verification_requirement = match task.id.as_str() {
"code-prototype" => "code-prototype 必须对可玩入口执行 game.static_smoke;完整 DAG 的最终静态与浏览器验收继续由后续质量任务承担。",
task_id if agent_runtime_autonomous_uses_owner_artifact_validation(task_id) => "完成固定正式产物后直接交付,由 Runtime 在收束门内验证本人固定 owner 产物;禁止调用 game.static_smoke、project.verify、command.run_limited 或 preview 工具冒充 owner 产物验证。",
"publish-package" => "publish-package 不在前置固定 owner 的内部产物验证范围内;必须继续按现有发布完成合同和当前 run 的可用验证门收束,不得借用前置 owner 的验证凭证。",
_ => "完成修改后按当前任务的既有验证合同收束。",
};
format!(
"{base}\n\n这是 autonomous-game-build 的正式 owner 写入任务。必须实际生成并写入非空正式产物:{paths};JSON 文件必须是可解析 JSONcode-prototype 的 game/index.html 不能沿用初始化占位。{publish_package_requirement}任务声明中的视觉图片继续按现有 visual gate 生成、登记并验收。完成修改后按当前 run 的验证门完成验证并直接交付结论;不要调用 task.updateRuntime 会在子 Run 终态后幂等投影 manifest。"
"{base}\n\n这是 autonomous-game-build 的正式 owner 写入任务。必须实际生成并写入非空正式产物:{paths};JSON 文件必须是可解析 JSONcode-prototype 的 game/index.html 不能沿用初始化占位。{publish_package_requirement}任务声明中的视觉图片继续按现有 visual gate 生成、登记并验收。{verification_requirement}不要调用 task.updateRuntime 会在子 Run 终态后幂等投影 manifest。"
)
}
@@ -1491,6 +1497,16 @@ pub(in crate::agent) fn render_autonomous_manifest_ready_task_background_prompt(
"{base}\n\n这是 autonomous-game-build 的只读试玩验收任务,不要修改项目文件。固定核心动作是且只能是 preview.validate;完成当前 revision 的桌面与移动试玩后直接交付验收结论,不要调用项目 mutation、其它预览动作或 task.updateRuntime 会在子 Run 终态后幂等投影 manifest。"
);
}
if task.id == "art-director" {
if autonomous_manifest_ready_task_requires_visual_asset(&task.id) {
return format!(
"{base}\n\n这是 autonomous-game-build 的条件 Canvas owner 任务。当前已配置 External Editor 生图凭据,必须按角色固定合同生成并登记 assets/art-spec.png。canvas.asset_generate 成功只表示候选已生成并登记,不等于视觉门已经通过;完成后直接交付,由 Runtime 在收束时核对当前 revision、Canvas 登记、资源身份和视觉产物门。项目 mutation 仅限固定 Canvas 产物;不得调用 game.static_smoke、project.verify、command.run_limited、preview 或 task.updateRuntime 会在子 Run 终态后幂等投影 manifest。"
);
}
return format!(
"{base}\n\n这是 autonomous-game-build 的无生图凭据只读协调任务。当前未配置 External Editor 生图凭据,上述 seed task 中 assets/art-spec.png 图片产物与生成验收条款在本轮不适用;只交付正式视觉方向结论,不要修改项目文件,不调用 canvas.asset_generate、game.static_smoke、project.verify、command.run_limited、preview 或 task.update。Runtime 会在子 Run 终态后幂等投影 manifest。"
);
}
if autonomous_manifest_task_requires_project_mutation(&task.id) {
let owner_prompt = render_autonomous_manifest_ready_task_owner_prompt(task);
let code_visual_asset_requirement =
@@ -1535,3 +1551,89 @@ pub(in crate::agent) fn render_manifest_ready_task_background_prompt(
acceptance
)
}
#[cfg(test)]
mod tests {
use super::*;
fn seed_task(task_id: &str) -> GameCreationAppTaskState {
new_game_creation_app_seed_tasks()
.into_iter()
.find(|task| task.id == task_id)
.unwrap_or_else(|| panic!("missing seed task {task_id}"))
}
#[test]
fn autonomous_ready_task_prompts_separate_owner_artifacts_from_playable_validation() {
let _config_guard = crate::tests::write_test_local_config("{}".to_string());
for task_id in [
"design-foundation",
"balance-seed",
"art-asset-plan",
"audio-asset-plan",
] {
let prompt =
render_autonomous_manifest_ready_task_background_prompt(&seed_task(task_id));
assert!(prompt.contains("由 Runtime 在收束门内验证本人固定 owner 产物"));
assert!(
prompt.contains("禁止调用 game.static_smoke、project.verify、command.run_limited")
);
}
let code_prompt =
render_autonomous_manifest_ready_task_background_prompt(&seed_task("code-prototype"));
assert!(code_prompt.contains("必须对可玩入口执行 game.static_smoke"));
assert!(!code_prompt.contains("验证本人固定 owner 产物"));
let readiness_prompt = render_autonomous_manifest_ready_task_background_prompt(&seed_task(
"preview-readiness",
));
assert!(
readiness_prompt.contains("且只能是 command.run_limited(commandId=game.static_smoke)")
);
assert!(!readiness_prompt.contains("preview.validate"));
let publish_prompt =
render_autonomous_manifest_ready_task_background_prompt(&seed_task("publish-package"));
assert!(publish_prompt.contains("不在前置固定 owner 的内部产物验证范围内"));
assert!(!publish_prompt.contains("验证本人固定 owner 产物"));
}
#[test]
fn art_director_ready_task_is_read_only_without_key_and_canvas_owner_with_key() {
let task = seed_task("art-director");
{
let _config_guard = crate::tests::write_test_local_config("{}".to_string());
let prompt = render_autonomous_manifest_ready_task_background_prompt(&task);
assert!(prompt.contains("无生图凭据只读协调任务"));
assert!(prompt.contains("assets/art-spec.png 图片产物与生成验收条款在本轮不适用"));
assert!(prompt.contains("不要修改项目文件"));
assert!(!autonomous_manifest_ready_task_requires_visual_asset(
"art-director"
));
assert!(agent_runtime_task_requires_read_only_delivery(
"art-director",
&prompt
));
}
{
let _config_guard = crate::tests::write_test_local_config(
r#"{"editorApi":{"apiKey":"art-director-ready-task-key"}}"#.to_string(),
);
let prompt = render_autonomous_manifest_ready_task_background_prompt(&task);
assert!(prompt.contains("条件 Canvas owner 任务"));
assert!(prompt.contains("生成并登记 assets/art-spec.png"));
assert!(prompt.contains("成功只表示候选已生成并登记,不等于视觉门已经通过"));
assert!(prompt.contains("由 Runtime 在收束时核对当前 revision"));
assert!(!prompt.contains("成功会为本人当前 revision 形成验证凭证"));
assert!(autonomous_manifest_ready_task_requires_visual_asset(
"art-director"
));
assert!(!agent_runtime_task_requires_read_only_delivery(
"art-director",
&prompt
));
}
}
}
@@ -154,6 +154,7 @@ pub(in crate::agent) fn validate_agent_runtime_verification_gate(
tool,
"project.verify"
| "game.static_smoke"
| AGENT_RUNTIME_OWNER_ARTIFACT_VERIFICATION_TOOL
| "command.exec"
| "preview.validate"
| "canvas.asset_generate"
@@ -730,6 +730,28 @@ pub(crate) fn observe_agent_runtime_limited_command(
detail: None,
};
}
match autonomous_owner_artifact_validation_available_for_run_at(root, agent_id, run_id) {
Ok(true) => {
return AgentRuntimeToolObservation {
tool: "command.run_limited".to_string(),
status: "failed".to_string(),
summary: "固定 owner 产物不能使用 game.static_smoke 验证".to_string(),
detail: Some(
"Runtime 会在收束门内验证当前 Agent/run 的固定正式产物;game.static_smoke 只验证可玩入口"
.to_string(),
),
};
}
Ok(false) => {}
Err(error) => {
return AgentRuntimeToolObservation {
tool: "command.run_limited".to_string(),
status: "failed".to_string(),
summary: "无法核对 game.static_smoke 的当前 run 身份".to_string(),
detail: Some(redact_agent_runtime_project_paths(root, &error, 500)),
};
}
}
let _lock = match acquire_project_write_lock(root, "command.run_limited") {
Ok(lock) => lock,
Err(error) => {
@@ -1,42 +1,25 @@
use super::*;
fn design_foundation_owned_project_path(path: &str) -> bool {
matches!(path, "memory/project.md" | "game/game_design.md")
}
fn game_chat_delegated_art_agent_is_assets_only_at(
root: &Path,
agent_id: &str,
run_id: &str,
) -> Result<bool, String> {
if !matches!(agent_id, "art-director" | "art-asset-plan") {
return Ok(false);
}
let Some(binding) =
read_game_creator_agent_runtime_run_profile_binding(root, agent_id, run_id)?
else {
return Ok(false);
let canonical_output = match agent_id {
"art-director" => AGENT_RUNTIME_ART_SPEC_PATH,
"art-asset-plan" => AGENT_RUNTIME_ART_SPRITESHEET_PATH,
_ => return Ok(false),
};
if binding.profile != AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|| binding.source != "agent-delegate"
|| binding.parent_agent_id.as_deref() != Some("code-prototype")
|| binding.parent_run_id.as_deref().is_none_or(str::is_empty)
|| binding.root_agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
{
return Ok(false);
}
let root_binding = read_game_creator_agent_runtime_run_profile_binding(
// `assets/**` is a broad write scope. Grant it only after the same
// canonical-output authorization used by Canvas replacement has verified
// the live root, main parent, child binding, durable delivery and audited
// generate-missing route.
game_chat_fast_path_scheduled_art_contract_repair_is_authorized_at(
root,
&binding.root_agent_id,
&binding.root_run_id,
)?
.ok_or_else(|| "美术 Agent 写入路径校验缺少根 Run Profile 绑定".to_string())?;
if root_binding.source != AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE
|| root_binding.profile != AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
{
return Ok(false);
}
Ok(true)
agent_id,
run_id,
canonical_output,
)
}
fn game_chat_art_child_tool_is_read_only(tool: &str) -> bool {
@@ -161,21 +144,121 @@ pub(in crate::agent) fn game_chat_delegated_art_agent_input_mutation_block(
}
pub(in crate::agent) fn agent_role_project_path_mutation_block(
root: &Path,
agent_id: &str,
run_id: &str,
tool: &str,
path: &str,
) -> Option<AgentRuntimeToolObservation> {
if agent_id != "design-foundation" || design_foundation_owned_project_path(path) {
return None;
match autonomous_owner_artifact_validation_available_for_run_at(root, agent_id, run_id) {
Ok(true) => {
let allowed = autonomous_manifest_owner_artifact_paths(agent_id);
if allowed.contains(&path) {
None
} else {
Some(AgentRuntimeToolObservation {
tool: tool.to_string(),
status: "blocked".to_string(),
summary: if agent_id == "design-foundation" {
"design-foundation 只能修改项目基础设计文档".to_string()
} else {
"autonomous owner 只能修改本人固定正式产物".to_string()
},
detail: Some(format!("path={path} · allowed={}", allowed.join(","))),
})
}
}
Ok(false) => {
match game_chat_delegated_art_agent_is_assets_only_at(root, agent_id, run_id) {
Ok(true) if path == "assets" || path.starts_with("assets/") => return None,
Ok(true) => {
return Some(AgentRuntimeToolObservation {
tool: tool.to_string(),
status: "blocked".to_string(),
summary: "game-chat 临时美术 Agent 只能修改 assets/**".to_string(),
detail: Some(format!(
"path={path} · allowed=assets/** · parentAgentId=code-prototype"
)),
});
}
Ok(false) => {}
Err(error) => {
return Some(AgentRuntimeToolObservation {
tool: tool.to_string(),
status: "blocked".to_string(),
summary: "无法校验 game-chat 临时美术 Agent 的写入边界".to_string(),
detail: Some(sanitize_agent_runtime_text(&error, 240)),
});
}
}
let autonomous_fixed_owner =
if agent_runtime_autonomous_uses_owner_artifact_validation(agent_id) {
match read_game_creator_agent_runtime_run_profile_binding(
root, agent_id, run_id,
) {
Ok(Some(binding)) => {
binding.profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
}
Ok(None) => match read_latest_game_creator_agent_runtime_task_by_run_id(
root, agent_id, run_id,
) {
Ok(Some(task)) => {
task.run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
}
Ok(None) => false,
Err(error) => {
return Some(AgentRuntimeToolObservation {
tool: tool.to_string(),
status: "blocked".to_string(),
summary: "无法核对 autonomous owner 写入边界".to_string(),
detail: Some(sanitize_agent_runtime_text(&error, 240)),
});
}
},
Err(error) => {
return Some(AgentRuntimeToolObservation {
tool: tool.to_string(),
status: "blocked".to_string(),
summary: "无法核对 autonomous owner 写入边界".to_string(),
detail: Some(sanitize_agent_runtime_text(&error, 240)),
});
}
}
} else {
false
};
if autonomous_fixed_owner {
Some(AgentRuntimeToolObservation {
tool: tool.to_string(),
status: "blocked".to_string(),
summary: "autonomous fixed owner 的当前 lineage 不受信任,禁止修改项目"
.to_string(),
detail: Some(format!("agentId={agent_id} · runId={run_id} · path={path}")),
})
} else if agent_id == "design-foundation" {
let allowed = autonomous_manifest_owner_artifact_paths(agent_id);
if allowed.contains(&path) {
None
} else {
Some(AgentRuntimeToolObservation {
tool: tool.to_string(),
status: "blocked".to_string(),
summary: "design-foundation 只能修改项目基础设计文档".to_string(),
detail: Some(format!("path={path} · allowed={}", allowed.join(","))),
})
}
} else {
None
}
}
Err(error) => Some(AgentRuntimeToolObservation {
tool: tool.to_string(),
status: "blocked".to_string(),
summary: "无法核对 autonomous owner 写入边界".to_string(),
detail: Some(sanitize_agent_runtime_text(&error, 240)),
}),
}
Some(AgentRuntimeToolObservation {
tool: tool.to_string(),
status: "blocked".to_string(),
summary: "design-foundation 只能修改项目基础设计文档".to_string(),
detail: Some(format!(
"path={path} · allowed=memory/project.md,game/game_design.md"
)),
})
}
pub(in crate::agent) fn observe_agent_runtime_file(
@@ -337,7 +420,9 @@ pub(in crate::agent) fn observe_agent_runtime_file_write(
};
}
};
if let Some(blocked) = agent_role_project_path_mutation_block(agent_id, "file.write", &path) {
if let Some(blocked) =
agent_role_project_path_mutation_block(root, agent_id, run_id, "file.write", &path)
{
return blocked;
}
if let Some(blocked) = game_chat_delegated_art_agent_project_path_mutation_block(
@@ -450,7 +535,9 @@ pub(in crate::agent) fn observe_agent_runtime_file_delete(
};
}
};
if let Some(blocked) = agent_role_project_path_mutation_block(agent_id, "file.delete", &path) {
if let Some(blocked) =
agent_role_project_path_mutation_block(root, agent_id, run_id, "file.delete", &path)
{
return blocked;
}
if let Some(blocked) = game_chat_delegated_art_agent_project_path_mutation_block(
@@ -669,7 +756,9 @@ pub(in crate::agent) fn observe_agent_runtime_file_patch(
};
}
};
if let Some(blocked) = agent_role_project_path_mutation_block(agent_id, "file.patch", &path) {
if let Some(blocked) =
agent_role_project_path_mutation_block(root, agent_id, run_id, "file.patch", &path)
{
return blocked;
}
if let Some(blocked) = game_chat_delegated_art_agent_project_path_mutation_block(
@@ -7,29 +7,23 @@ fn autonomous_game_build_agent_can_execute_canvas_asset_generate(agent_id: &str)
)
}
fn autonomous_design_foundation_command_is_allowed(command_id: &str) -> bool {
matches!(
command_id,
"memory.read"
| "conversation.read"
| "asset.list"
| "project.index"
| "project.search"
| "file.read"
| "project.diff"
| "git.inspect"
| "file.list"
| "file.write"
| "file.delete"
| "project.patchset"
| "task.list"
| "command.run_limited"
| "image.inspect"
| "canvas.asset_generate"
| "agent.audit"
| "agent.action_history"
| "agent.run_status"
)
fn autonomous_owner_manual_verification_command_is_denied(
agent_id: &str,
command_id: &str,
) -> bool {
agent_runtime_autonomous_uses_owner_artifact_validation(agent_id)
&& matches!(command_id, "project.verify" | "command.run_limited")
}
fn autonomous_art_director_non_canvas_validation_command_is_denied(
agent_id: &str,
command_id: &str,
) -> bool {
agent_id == "art-director"
&& matches!(
command_id,
"project.verify" | "command.run_limited" | "preview.start" | "preview.validate"
)
}
pub(in crate::agent) fn refresh_game_creator_agent_runtime_tool_policy(
@@ -71,6 +65,20 @@ pub(crate) fn game_creator_agent_runtime_tool_policy_rule_for_run(
Ok(identity) => identity,
Err(error) => return Some(AgentRuntimeToolPolicyBlock::Denied(error)),
};
if run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
&& autonomous_owner_manual_verification_command_is_denied(agent_id, command_id)
{
return Some(AgentRuntimeToolPolicyBlock::Denied(format!(
"自主构建固定 owner {agent_id} 的正式产物只允许由 Runtime 内部验证,拒绝回退执行:{command_id}"
)));
}
if run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
&& autonomous_art_director_non_canvas_validation_command_is_denied(agent_id, command_id)
{
return Some(AgentRuntimeToolPolicyBlock::Denied(format!(
"自主构建 art-director 只负责条件 Canvas 规范图或无 Key 只读结论,拒绝执行非 Canvas 验证:{command_id}"
)));
}
if run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
&& command_id == "canvas.asset_generate"
&& !autonomous_game_build_agent_can_execute_canvas_asset_generate(agent_id)
@@ -81,10 +89,10 @@ pub(crate) fn game_creator_agent_runtime_tool_policy_rule_for_run(
}
if run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
&& agent_id == "design-foundation"
&& !autonomous_design_foundation_command_is_allowed(command_id)
&& !agent_runtime_autonomous_design_foundation_command_is_allowed(command_id)
{
return Some(AgentRuntimeToolPolicyBlock::Denied(format!(
"design-foundation 只允许只读工具、受限设计文档写入、UI 原型画布生成和 game.static_smoke{command_id}"
"design-foundation 只允许只读工具、受限设计文档写入、UI 原型画布生成和 Runtime 内部产物验证{command_id}"
)));
}
match blocked {
@@ -590,20 +598,17 @@ mod tests {
"conversation.read",
"asset.list",
"project.index",
"project.search",
"file.read",
"project.diff",
"git.inspect",
"project.git_inspect",
"file.list",
"file.write",
"file.delete",
"project.patchset",
"task.list",
"command.run_limited",
"image.inspect",
"canvas.asset_generate",
"agent.audit",
"agent.action_history",
"agent.run_status",
] {
assert!(game_creator_agent_runtime_tool_policy_rule_for_run(
@@ -617,6 +622,90 @@ mod tests {
.is_none());
}
let snapshot = agent_runtime_tool_policy_snapshot_for_run_at(
&root,
"design-foundation",
"policy-design-foundation-run",
Some(&binding.profile),
Some(&binding.binding_fingerprint),
)
.expect("read design-foundation policy snapshot");
for (tool, command_id) in [
("project.search", "file.read"),
("git.inspect", "project.git_inspect"),
("file.patch", "file.write"),
("agent.action_history", "agent.audit"),
] {
assert!(game_creator_agent_runtime_tool_policy_rule_for_run(
&root,
"design-foundation",
"policy-design-foundation-run",
Some(&binding.profile),
Some(&binding.binding_fingerprint),
command_id,
)
.is_none());
assert!(snapshot
.auto_tools
.iter()
.any(|candidate| candidate == tool));
assert!(!snapshot
.confirm_tools
.iter()
.any(|candidate| candidate == tool));
assert!(!snapshot
.denied_tools
.iter()
.any(|candidate| candidate == tool));
}
for (tool, command_id) in [
("memory.write", "memory.write"),
("project.verify", "project.verify"),
("project.restore", "project.restore"),
("project.git_commit", "project.git_commit"),
("command.exec", "command.exec"),
("command.output_read", "command.output_read"),
("command.start", "command.start"),
("command.poll", "command.poll"),
("command.stdin", "command.stdin"),
("command.terminate", "command.terminate"),
("command.run_limited", "command.run_limited"),
("preview.start", "preview.start"),
("preview.validate", "preview.validate"),
("agent.message", "conversation.write"),
("agent.delegate", "agent.delegate"),
("agent.spawn_isolated", "agent.spawn_isolated"),
("agent.schedule_ready", "agent.schedule_ready"),
("agent.route_manifest", "agent.route_manifest"),
] {
assert!(matches!(
game_creator_agent_runtime_tool_policy_rule_for_run(
&root,
"design-foundation",
"policy-design-foundation-run",
Some(&binding.profile),
Some(&binding.binding_fingerprint),
command_id,
),
Some(AgentRuntimeToolPolicyBlock::Denied(_))
));
assert!(
snapshot
.denied_tools
.iter()
.any(|candidate| candidate == tool),
"{tool} must be visible as denied for design-foundation"
);
assert!(!snapshot
.auto_tools
.iter()
.any(|candidate| candidate == tool));
assert!(!snapshot
.confirm_tools
.iter()
.any(|candidate| candidate == tool));
}
let code_binding =
bind_delegated_autonomous_run(&root, supervisor_run_id, "code-prototype");
assert!(game_creator_agent_runtime_tool_policy_rule_for_run(
@@ -629,4 +718,134 @@ mod tests {
)
.is_none());
}
#[test]
fn autonomous_fixed_owners_deny_manual_verification_without_trusted_dag_fallback() {
let temporary = tempfile::tempdir().expect("create owner verification policy root");
let root = temporary.path().join("project");
init_local_game_project_at(
&root,
"project-owner-verification-policy",
"固定 owner 验证执行边界测试",
)
.expect("project init");
let supervisor_run_id = "policy-owner-verification-parent-run";
bind_game_creator_agent_runtime_run_profile_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
supervisor_run_id,
AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE,
Some(AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD),
None,
)
.expect("bind supervisor autonomous run");
for agent_id in [
"design-foundation",
"balance-seed",
"art-asset-plan",
"audio-asset-plan",
] {
let binding = bind_delegated_autonomous_run(&root, supervisor_run_id, agent_id);
assert!(!autonomous_owner_artifact_validation_available_for_run_at(
&root,
agent_id,
&binding.run_id,
)
.expect("evaluate deliberately non-scheduler owner binding"));
for command_id in ["project.verify", "command.run_limited"] {
assert!(matches!(
game_creator_agent_runtime_tool_policy_rule_for_run(
&root,
agent_id,
&binding.run_id,
Some(&binding.profile),
Some(&binding.binding_fingerprint),
command_id,
),
Some(AgentRuntimeToolPolicyBlock::Denied(reason))
if reason.contains("固定 owner")
&& reason.contains(agent_id)
&& reason.contains(command_id)
));
}
assert!(matches!(
game_creator_agent_runtime_tool_policy_rule_for_run(
&root,
agent_id,
&binding.run_id,
Some(&binding.profile),
Some("forged-recovery-fingerprint"),
"project.verify",
),
Some(AgentRuntimeToolPolicyBlock::Denied(reason))
if reason.contains("持久绑定不一致")
));
}
assert!(matches!(
game_creator_agent_runtime_tool_policy_rule_for_run(
&root,
"balance-seed",
"missing-recovered-owner-run",
Some(AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD),
Some("missing-binding-fingerprint"),
"command.run_limited",
),
Some(AgentRuntimeToolPolicyBlock::Denied(reason))
if reason.contains("绑定缺失") && reason.contains("拒绝降级")
));
for agent_id in ["code-prototype", "publish-package"] {
let binding = bind_delegated_autonomous_run(&root, supervisor_run_id, agent_id);
for command_id in ["project.verify", "command.run_limited"] {
assert!(
game_creator_agent_runtime_tool_policy_rule_for_run(
&root,
agent_id,
&binding.run_id,
Some(&binding.profile),
Some(&binding.binding_fingerprint),
command_id,
)
.is_none(),
"{agent_id} must remain outside the fixed pre-code owner verification rule"
);
}
}
let art_director_binding =
bind_delegated_autonomous_run(&root, supervisor_run_id, "art-director");
for command_id in [
"project.verify",
"command.run_limited",
"preview.start",
"preview.validate",
] {
assert!(matches!(
game_creator_agent_runtime_tool_policy_rule_for_run(
&root,
"art-director",
&art_director_binding.run_id,
Some(&art_director_binding.profile),
Some(&art_director_binding.binding_fingerprint),
command_id,
),
Some(AgentRuntimeToolPolicyBlock::Denied(reason))
if reason.contains("art-director")
&& reason.contains("非 Canvas 验证")
&& reason.contains(command_id)
));
}
assert!(game_creator_agent_runtime_tool_policy_rule_for_run(
&root,
"art-director",
&art_director_binding.run_id,
Some(&art_director_binding.profile),
Some(&art_director_binding.binding_fingerprint),
"canvas.asset_generate",
)
.is_none());
}
}
@@ -175,13 +175,14 @@ pub(in crate::agent) async fn observe_agent_runtime_preview_validate(
}
};
let completion_contract =
match autonomous_playtest_completion_contract_for_state_at(root, &runtime) {
match autonomous_playtest_execution_contract_for_state_at(root, &runtime) {
Ok(contract) => contract,
Err(error) => {
return AgentRuntimeToolObservation {
tool: "preview.validate".to_string(),
status: "failed".to_string(),
summary: "自主构建完成合同不可用,未执行浏览器试玩".to_string(),
summary: "preview.validate 执行身份或自主构建完成合同不可用,未执行浏览器试玩"
.to_string(),
detail: Some(redact_agent_runtime_project_paths(root, &error, 500)),
};
}
@@ -248,14 +249,10 @@ pub(in crate::agent) async fn observe_agent_runtime_preview_validate(
}
},
};
let (evidence_agent_id, evidence_run_id) = completion_contract
.as_ref()
.map(|contract| (contract.agent_id.as_str(), contract.run_id.as_str()))
.unwrap_or((agent_id, run_id));
let evidence_relative_root = format!(
".agent/runtime/browser-validations/{}/{}/{}",
agent_runtime_confirmation_path_component(evidence_agent_id, "agent"),
agent_runtime_confirmation_path_component(evidence_run_id, "run"),
agent_runtime_confirmation_path_component(agent_id, "agent"),
agent_runtime_confirmation_path_component(run_id, "run"),
revision_before.revision,
);
let evidence_root = match resolve_local_project_path(root, &evidence_relative_root) {
@@ -379,6 +376,7 @@ pub(in crate::agent) async fn observe_agent_runtime_preview_validate(
let receipt = match write_autonomous_playtest_receipt_at(
root,
contract,
&runtime,
action_id,
action_fingerprint,
revision_after.revision,
@@ -193,13 +193,10 @@ where
};
}
};
if agent_id == "design-foundation" {
if let Some(summary) = prepared.summaries().iter().find(|summary| {
agent_role_project_path_mutation_block(agent_id, tool, summary.path()).is_some()
}) {
return agent_role_project_path_mutation_block(agent_id, tool, summary.path())
.expect("design-foundation unauthorized patchset path must be blocked");
}
if let Some(blocked) = prepared.summaries().iter().find_map(|summary| {
agent_role_project_path_mutation_block(root, agent_id, run_id, tool, summary.path())
}) {
return blocked;
}
if let Some(summary) = prepared.summaries().iter().find(|summary| {
game_chat_delegated_art_agent_project_path_mutation_block(
@@ -1008,10 +1008,16 @@ pub(crate) fn game_chat_art_delivery_gap_at(
|| delivery.structured_result.as_ref().is_none_or(|result| {
result.contract_status != StaticDelegateContractStatus::EvidenceReady
|| !result.missing_expected_artifacts.is_empty()
|| !result.verification_required
|| result.verified_revision.is_none()
|| !result
.evidence
.iter()
.any(|evidence| evidence.kind == "canvas.asset_generate")
})
{
return Ok(Some(format!(
"target={target_agent_id} delivery={} 尚未形成 EvidenceReady 美术回执",
"target={target_agent_id} delivery={} 尚未形成本人 canvas.asset_generate 的 EvidenceReady 美术回执",
delivery.delegation_id
)));
}
@@ -65,7 +65,8 @@ fn visual_specialist_prompts_require_real_registered_image_deliveries() {
"referenceImageSrcs 第一项",
"POST /api/external/v1/editor/images/generationskind=ui-design",
"不得误用 POST /api/external/v1/editor/ui-designs/assets/extractions",
"canvas.asset_generate 成功动作本身就是当前 revision 的验证",
"canvas.asset_generate 成功只表示候选图片已生成并登记,不等于视觉验收完成",
"由 Runtime 在收束门内同时核对固定 owner 文档",
"已有同路径画布资产时先核对登记",
"检查已通过时不得重复生成或再次扣费",
"纯场景图",
@@ -85,6 +86,8 @@ fn visual_specialist_prompts_require_real_registered_image_deliveries() {
"POST /api/external/v1/editor/images/generationskind=spec",
"后续 UI 和透明图集共同引用",
"不得用 generationInputs.artSpec JSON",
"成功只表示固定候选已生成并登记,不等于视觉门已经通过",
"由 Runtime 在收束时核对当前 revision",
"缺少 resourceId 时不得提交最终回复",
] {
assert!(
@@ -600,20 +600,23 @@ async fn background_agent_runtime_can_run_limited_static_smoke() {
)
.expect("start background task");
let plan_request = receiver
.recv_timeout(Duration::from_secs(2))
.expect("plan llm request");
let plan_request = wait_for_captured_mock_request(&receiver, "plan llm request").await;
assert!(plan_request.contains("file.write"));
assert!(plan_request.contains("command.run_limited"));
let final_request = receiver
.recv_timeout(Duration::from_secs(2))
.expect("final reply llm request");
let final_request = wait_for_captured_mock_request(&receiver, "final reply llm request").await;
assert!(final_request.contains("game.static_smoke 已完成"));
assert!(final_request.contains("通过:game/index.html"));
let root_display = root.to_string_lossy();
assert!(!final_request.contains(root_display.as_ref()));
let runtime = wait_for_agent_runtime_idle(&root, "code-prototype");
let runtime = wait_for_agent_runtime_terminal_and_lane_release(
&root,
"code-prototype",
"code-smoke-run",
"idle",
"completed",
)
.state;
assert_eq!(runtime.status, "idle");
assert_eq!(runtime.phase, "completed");
assert!(runtime
@@ -1,18 +1,21 @@
use super::super::support::*;
use crate::{
active_static_delegate_delivery_count_at, autonomous_game_build_root_run_active_at,
bind_supervisor_collaboration_policy_snapshot_at, build_static_delegate_structured_result_at,
claim_ready_static_delegate_receipts_at, create_or_read_static_delegate_delivery_at,
mark_static_delegate_claim_observed_at, mark_static_delegate_delivery_ready_at,
mark_static_delegate_delivery_ready_with_result_at, new_game_creation_app_seed_tasks,
new_static_delegate_delivery, new_static_delegate_delivery_with_contract,
observe_agent_runtime_run_status, record_command_run, record_preview_state,
active_static_delegate_delivery_count_at,
append_unique_game_creator_agent_runtime_pending_task,
autonomous_game_build_root_run_active_at, bind_supervisor_collaboration_policy_snapshot_at,
build_static_delegate_structured_result_at, claim_ready_static_delegate_receipts_at,
create_or_read_static_delegate_delivery_at, mark_static_delegate_claim_observed_at,
mark_static_delegate_delivery_ready_at, mark_static_delegate_delivery_ready_with_result_at,
new_game_creation_app_seed_tasks, new_static_delegate_delivery,
new_static_delegate_delivery_with_contract, observe_agent_runtime_run_status,
record_command_run, record_preview_state,
refresh_agent_runtime_autonomous_convergence_snapshot_after_provider_at,
start_game_creator_supervisor_background_task_for_session_at,
static_delegate_completion_barrier_at, validate_agent_runtime_autonomous_plan_liveness,
GameCreationAppCommandRunState, GameCreationAppCommandRunStatus, GameCreationAppPreviewStatus,
StaticDelegateContractStatus,
};
use sha2::{Digest as _, Sha256};
fn write_autonomous_editor_api_config_for_test(config_dir: &Path, api_key: &str) {
fs::create_dir_all(config_dir).expect("create autonomous runtime config dir");
@@ -605,6 +608,84 @@ fn bind_autonomous_specialist_runtime_for_test(
runtime
}
fn bind_autonomous_manifest_owner_runtime_for_test(
root: &Path,
parent_run_id: &str,
agent_id: &str,
task: &str,
) -> AgentRuntimeState {
let parent_session_id = resolve_agent_conversation_session_id_at(
root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
None,
true,
)
.expect("resolve autonomous manifest parent session");
append_unique_game_creator_agent_runtime_pending_task(
root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&parent_session_id,
task,
parent_run_id,
AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE,
Some(AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD),
None,
)
.expect("queue autonomous manifest parent and freeze its completion baseline");
start_game_creator_agent_runtime_task_at(
root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
task,
parent_run_id,
AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE,
"调度固定 owner",
Vec::new(),
)
.expect("start durable autonomous manifest parent");
update_manifest_task_status_at(root, agent_id, GameCreationAppTaskStatus::Running)
.expect("mark autonomous manifest owner running");
let child_run_id = autonomous_manifest_ready_task_run_id_for_test(parent_run_id, agent_id);
let child_link = AgentRuntimeTaskLink {
parent_agent_id: Some(GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID.to_string()),
parent_run_id: Some(parent_run_id.to_string()),
delegation_id: None,
};
let child_session_id = resolve_agent_conversation_session_id_at(root, agent_id, None, true)
.expect("resolve autonomous manifest owner session");
append_unique_game_creator_agent_runtime_pending_task(
root,
agent_id,
&child_session_id,
task,
&child_run_id,
"agent-ready-task-scheduler",
None,
Some(&child_link),
)
.expect("queue autonomous manifest owner");
start_game_creator_agent_runtime_task_at(
root,
agent_id,
task,
&child_run_id,
"agent-ready-task-scheduler",
"执行固定 owner 交付",
Vec::new(),
)
.expect("start autonomous manifest owner")
}
fn autonomous_manifest_ready_task_run_id_for_test(parent_run_id: &str, task_id: &str) -> String {
let identity = format!("{parent_run_id}\n{task_id}\nagent-ready-task-scheduler");
let fingerprint = format!("{:x}", Sha256::digest(identity.as_bytes()));
format!(
"autonomous-ready-{}-{}",
task_id,
fingerprint.chars().take(20).collect::<String>()
)
}
#[tokio::test]
async fn autonomous_game_build_non_read_only_code_first_round_repairs_response_into_mutation_only()
{
@@ -838,6 +919,198 @@ async fn autonomous_game_build_unverified_mutation_immediately_repairs_into_veri
fs::remove_dir_all(root).ok();
}
#[tokio::test]
async fn autonomous_manifest_code_prototype_requires_its_own_static_smoke_after_project_verify() {
let root = unique_project_path();
init_local_game_project_at(
&root,
"project-autonomous-code-owner-smoke",
"自主构建程序 owner 静态验收测试",
)
.expect("project init");
fs::write(
root.join("package.json"),
br#"{"scripts":{"check":"node --check game/index.html"}}"#,
)
.expect("write package manifest so project.verify is advertised diagnostically");
let parent_run_id = "autonomous-code-owner-smoke-parent";
let child_run_id =
autonomous_manifest_ready_task_run_id_for_test(parent_run_id, "code-prototype");
let task = "实现可玩 game/index.html,并由 code-prototype 本人完成 game.static_smoke。";
let response_arguments = serde_json::json!({
"response": "可玩入口已经通过项目检查,可以交付。"
})
.to_string();
let smoke_arguments = serde_json::json!({
"reason": "补齐 code-prototype 本人静态验收凭证",
"input": {"commandId": "game.static_smoke"}
})
.to_string();
let (sender, receiver) = mpsc::channel();
let base_url = spawn_mock_llm_raw_responses_with_capture(
vec![
native_agent_tool_plan_chat_response(
"call-autonomous-code-owner-project-verified-response",
AGENT_RUNTIME_RESPOND_FUNCTION_NAME,
response_arguments.clone(),
),
native_agent_tool_plan_chat_response(
"call-autonomous-code-owner-static-smoke",
&native_runtime_function_name("command.run_limited")
.expect("limited command function"),
smoke_arguments,
),
native_agent_tool_plan_chat_response(
"call-autonomous-code-owner-smoke-verified-response",
AGENT_RUNTIME_RESPOND_FUNCTION_NAME,
response_arguments,
),
],
Some(sender),
);
let _config_guard = write_test_local_config(format!(
r#"{{
"agentLlm": {{
"code-prototype": {{
"apiKey": "autonomous-code-owner-smoke-key",
"baseUrl": {base_url:?},
"model": "autonomous-code-owner-smoke-model",
"apiKind": "openai_chat",
"maxRetries": 0
}}
}}
}}"#
));
let runtime = bind_autonomous_manifest_owner_runtime_for_test(
&root,
parent_run_id,
"code-prototype",
task,
);
assert_eq!(runtime.run_id, child_run_id);
let mutation_revision = prepare_agent_runtime_project_mutation_locked(
&root,
"code-prototype",
&child_run_id,
"file.patch",
)
.expect("record code-prototype mutation");
persist_project_verification_for_test(
&root,
"code-prototype",
&child_run_id,
"project.verify",
true,
);
let project_verified_gate =
read_game_creator_agent_runtime_verification_gate(&root, "code-prototype", &child_run_id)
.expect("read project.verify gate");
assert_eq!(
project_verified_gate.mutation_revision,
Some(mutation_revision)
);
assert_eq!(
project_verified_gate.verified_revision,
Some(mutation_revision)
);
assert_eq!(
project_verified_gate.last_verification_tool.as_deref(),
Some("project.verify")
);
assert_eq!(project_verified_gate.static_smoke_verified_revision, None);
let blocker =
project_verification_completion_blocker_at(&root, "code-prototype", &child_run_id, &[])
.expect("project.verify alone must not complete code-prototype");
assert!(blocker.summary.contains("game.static_smoke"));
let repair_plan = request_game_creator_agent_background_tool_plan_for_test(
&root,
"code-prototype",
&runtime.session_id,
&child_run_id,
task,
&[],
2,
0,
)
.await
.expect("repair project.verify-only delivery")
.expect("static-smoke repair plan");
assert!(repair_plan.response.is_empty());
assert_eq!(repair_plan.actions.len(), 1);
assert_eq!(repair_plan.actions[0].tool, "command.run_limited");
assert_eq!(
repair_plan.actions[0].input["commandId"],
"game.static_smoke"
);
receiver
.recv_timeout(Duration::from_secs(2))
.expect("initial project.verify-only response request");
let repair_request = receiver
.recv_timeout(Duration::from_secs(2))
.expect("static-smoke-only repair request");
assert!(repair_request.contains("project.verify 不能满足 code-prototype"));
assert_eq!(
captured_native_function_names_for_test(&repair_request),
["command.run_limited"]
.into_iter()
.map(|tool| native_runtime_function_name(tool).expect("static smoke function"))
.collect::<BTreeSet<_>>()
);
persist_project_verification_for_test(
&root,
"code-prototype",
&child_run_id,
"game.static_smoke",
true,
);
let smoke_gate =
read_game_creator_agent_runtime_verification_gate(&root, "code-prototype", &child_run_id)
.expect("read static smoke gate");
assert_eq!(
smoke_gate.static_smoke_verified_revision,
Some(mutation_revision)
);
let completed_plan = request_game_creator_agent_background_tool_plan_for_test(
&root,
"code-prototype",
&runtime.session_id,
&child_run_id,
task,
&[AgentRuntimeToolObservation {
tool: "command.run_limited".to_string(),
status: "ok".to_string(),
summary: "game.static_smoke 已通过当前 revision".to_string(),
detail: None,
}],
3,
0,
)
.await
.expect("request delivery after static smoke")
.expect("code-prototype may deliver after its own static smoke");
assert_eq!(
completed_plan.response,
"可玩入口已经通过项目检查,可以交付。"
);
assert!(completed_plan.actions.is_empty());
assert!(project_verification_completion_blocker_at(
&root,
"code-prototype",
&child_run_id,
&[],
)
.is_none());
receiver
.recv_timeout(Duration::from_secs(2))
.expect("post-smoke final response request");
assert!(receiver.recv_timeout(Duration::from_millis(200)).is_err());
fs::remove_dir_all(root).ok();
}
async fn assert_autonomous_repair_waits_for_receipt_observation_for_test(unobserved_claim: bool) {
let root = unique_project_path();
let case_name = if unobserved_claim {
@@ -3293,7 +3566,7 @@ async fn autonomous_game_build_verified_revision_forces_response_only_delivery()
}
#[tokio::test]
async fn autonomous_static_art_asset_verification_does_not_require_node_project() {
async fn autonomous_static_art_asset_delivery_uses_runtime_owner_validation_without_node_project() {
let root = unique_project_path();
init_local_game_project_at(
&root,
@@ -3305,39 +3578,21 @@ async fn autonomous_static_art_asset_verification_does_not_require_node_project(
!root.join("package.json").exists(),
"static HTML fixture must not gain a Node project manifest"
);
fs::write(
root.join("assets/manifest.art.json"),
br#"{"assets":[{"path":"assets/art-spritesheet.png","kind":"art-spritesheet"}]}"#,
)
.expect("write static art manifest");
register_canvas_visual_asset_fixture(&root, "assets/art-spritesheet.png", "art-spritesheet");
let child_run_id = "autonomous-static-art-verification-child";
let parent_run_id = "autonomous-static-art-verification-parent";
let child_run_id =
autonomous_manifest_ready_task_run_id_for_test(parent_run_id, "art-asset-plan");
let task = "为纯静态 HTML 游戏生成并登记 assets/manifest.art.json 与 assets/art-spritesheet.png;项目不使用 Node 或 npm。";
let response_arguments = serde_json::json!({
"response": "美术清单和精灵图已经生成,可以交付。"
})
.to_string();
let smoke_arguments = serde_json::json!({
"reason": "使用静态 smoke 验证当前纯静态项目 revision",
"input": {"commandId": "game.static_smoke"}
})
.to_string();
let (sender, receiver) = mpsc::channel();
let base_url = spawn_mock_llm_raw_responses_with_capture(
vec![
native_agent_tool_plan_chat_response(
"call-autonomous-static-art-response",
AGENT_RUNTIME_RESPOND_FUNCTION_NAME,
response_arguments,
),
native_agent_tool_plan_chat_response(
"call-autonomous-static-art-smoke",
&native_runtime_function_name("command.run_limited")
.expect("limited command function"),
smoke_arguments,
),
],
vec![native_agent_tool_plan_chat_response(
"call-autonomous-static-art-response",
AGENT_RUNTIME_RESPOND_FUNCTION_NAME,
response_arguments,
)],
Some(sender),
);
let _config_guard = write_test_local_config(format!(
@@ -3353,17 +3608,22 @@ async fn autonomous_static_art_asset_verification_does_not_require_node_project(
}}
}}"#
));
let runtime = bind_autonomous_specialist_runtime_for_test(
let runtime = bind_autonomous_manifest_owner_runtime_for_test(
&root,
"autonomous-static-art-verification-parent",
parent_run_id,
"art-asset-plan",
child_run_id,
task,
);
fs::write(
root.join("assets/manifest.art.json"),
br#"{"assets":[{"path":"assets/art-spritesheet.png","kind":"art-spritesheet"}]}"#,
)
.expect("write static art manifest after the parent completion baseline");
register_canvas_visual_asset_fixture(&root, "assets/art-spritesheet.png", "art-spritesheet");
prepare_agent_runtime_project_mutation_locked(
&root,
"art-asset-plan",
child_run_id,
&child_run_id,
"canvas.asset_generate",
)
.expect("record generated art mutation");
@@ -3378,43 +3638,21 @@ async fn autonomous_static_art_asset_verification_does_not_require_node_project(
&root,
"art-asset-plan",
&runtime.session_id,
child_run_id,
&child_run_id,
task,
&observations,
2,
0,
)
.await
.expect("repair static art delivery into static verification")
.expect("static smoke verification plan");
assert!(plan.response.is_empty());
assert_eq!(plan.actions.len(), 1);
assert_eq!(plan.actions[0].tool, "command.run_limited");
assert_eq!(plan.actions[0].input["commandId"], "game.static_smoke");
.expect("accept static art delivery for runtime owner validation")
.expect("static art delivery plan");
assert_eq!(plan.response, "美术清单和精灵图已经生成,可以交付。");
assert!(plan.actions.is_empty());
receiver
.recv_timeout(Duration::from_secs(2))
.expect("initial static art response request");
let repair_request = receiver
.recv_timeout(Duration::from_secs(2))
.expect("static art verification-only repair request");
let repair_function_names = captured_native_function_names_for_test(&repair_request);
assert_eq!(
repair_function_names,
BTreeSet::from([
native_runtime_function_name("command.run_limited").expect("static smoke function")
]),
"pure static asset delivery must not expose package.json-backed project.verify"
);
let repair_request_json = mock_http_request_json(&repair_request);
let repair_instruction = repair_request_json["messages"]
.as_array()
.and_then(|messages| messages.last())
.and_then(|message| message.get("content"))
.and_then(serde_json::Value::as_str)
.expect("static art repair instruction");
assert!(repair_instruction.contains("game.static_smoke"));
assert!(!repair_instruction.contains("project.verify"));
assert!(receiver.recv_timeout(Duration::from_millis(200)).is_err());
fs::remove_dir_all(root).ok();
@@ -1569,15 +1569,125 @@ async fn autonomous_design_foundation_denies_indirect_execution_before_side_effe
let agent_db = fs::read_to_string(root.join(".agent/agent.db")).expect("agent db");
assert!(!agent_db.contains("agent.runtime.project.verify"));
assert!(!agent_db.contains("agent.runtime.mcp.call"));
assert!(game_creator_agent_runtime_tool_policy_rule_for_run(
assert!(matches!(
game_creator_agent_runtime_tool_policy_rule_for_run(
&root,
"design-foundation",
run_id,
Some(&binding.profile),
Some(&binding.binding_fingerprint),
"command.run_limited",
),
Some(AgentRuntimeToolPolicyBlock::Denied(_))
));
let smoke = execute_game_creator_agent_runtime_tool_action(
&root,
"design-foundation",
run_id,
Some(&binding.profile),
Some(&binding.binding_fingerprint),
"command.run_limited",
"不得借游戏 smoke 验证策划文档",
&AgentRuntimeToolAction {
tool: "command.run_limited".to_string(),
reason: Some("验证角色门禁先于静态检查".to_string()),
input: serde_json::json!({ "commandId": "game.static_smoke" }),
},
)
.is_none());
.await;
assert_eq!(smoke.status, "blocked");
assert!(smoke.summary.contains("design-foundation"));
assert!(!root.join(".agent/logs/command.log").exists());
fs::remove_dir_all(root).ok();
}
#[tokio::test]
async fn autonomous_fixed_pre_code_owners_deny_manual_verification_before_side_effects() {
let root = unique_project_path();
init_local_game_project_at(
&root,
"project-fixed-owner-manual-verification-policy",
"固定 owner 手工验证门禁测试",
)
.expect("project init");
let supervisor_run_id = "fixed-owner-manual-verification-parent-run";
bind_game_creator_agent_runtime_run_profile_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
supervisor_run_id,
AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE,
Some(AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD),
None,
)
.expect("bind supervisor autonomous run");
let verify_command =
"node -e \"require('fs').writeFileSync('owner-project-verify-executed.txt','executed')\"";
fs::write(
root.join("package.json"),
serde_json::json!({
"scripts": {
"check": verify_command,
}
})
.to_string(),
)
.expect("write project verify canary script");
for agent_id in ["balance-seed", "art-asset-plan", "audio-asset-plan"] {
let run_id = format!("fixed-owner-{agent_id}-run");
bind_game_creator_agent_runtime_run_profile_at(
&root,
agent_id,
&run_id,
"agent-delegate",
None,
Some(&AgentRuntimeTaskLink {
parent_agent_id: Some(GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID.to_string()),
parent_run_id: Some(supervisor_run_id.to_string()),
delegation_id: Some(format!("fixed-owner-{agent_id}-delegation")),
}),
)
.expect("bind delegated fixed owner autonomous run");
let project_verify = execute_game_creator_agent_runtime_tool_action(
&root,
agent_id,
&run_id,
"不得回退执行项目脚本",
&AgentRuntimeToolAction {
tool: "project.verify".to_string(),
reason: Some("验证固定 owner 门禁先于项目命令执行".to_string()),
input: serde_json::json!({
"script": "check",
"expectedCommand": verify_command,
"timeoutSeconds": 30,
}),
},
)
.await;
let smoke = execute_game_creator_agent_runtime_tool_action(
&root,
agent_id,
&run_id,
"不得借游戏 smoke 验证固定 owner 产物",
&AgentRuntimeToolAction {
tool: "command.run_limited".to_string(),
reason: Some("验证固定 owner 门禁先于静态检查".to_string()),
input: serde_json::json!({ "commandId": "game.static_smoke" }),
},
)
.await;
for observation in [&project_verify, &smoke] {
assert_eq!(observation.status, "blocked");
assert!(observation.summary.contains("固定 owner"));
assert!(observation.summary.contains(agent_id));
}
}
assert!(!root.join("owner-project-verify-executed.txt").exists());
assert!(!root.join(".agent/logs/command.log").exists());
let agent_db = fs::read_to_string(root.join(".agent/agent.db")).expect("agent db");
assert!(!agent_db.contains("agent.runtime.project.verify"));
assert!(!agent_db.contains("agent.runtime.command.run_limited"));
fs::remove_dir_all(root).ok();
}
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More