Files
Genarrative/apps/ai-game-creator-shell/src-tauri/src/collaboration.rs
T
kdletters 33ec7b2861
Project CI / Repository checks (push) Failing after 2m19s
Project CI / Frontend tests (push) Failing after 2m50s
Project CI / Backend tests (push) Successful in 4m20s
Project CI / Native shell tests (push) Successful in 12m0s
修复AI游戏创作长耗时与运行收口
自主构建只执行正式清单任务图并移除重复静态委派
预览基础设施失败立即收口并绑定当前版本验证凭证
客户端退出前检查Runner持久任务状态并阻止忙碌关闭
修复上下文检查点恢复边界并补齐恢复测试
为全部Agent配置默认推理强度与实际重试状态展示
补齐配置、协作、预览、Runner及全量回归测试
同步技术方案、开发流程与长期决策记录
2026-07-31 18:26:35 +08:00

1788 lines
71 KiB
Rust

use super::*;
use serde_json::Value;
use sha2::{Digest, Sha256};
const SUPERVISOR_COLLABORATION_POLICY_SCHEMA_VERSION: &str =
"game-creator-supervisor-collaboration-policy.v1";
const SUPERVISOR_COLLABORATION_CONTRACT_SCHEMA_VERSION: &str =
"game-creator-supervisor-collaboration-contract.v1";
const SUPERVISOR_COLLABORATION_POLICY_SNAPSHOT_SCHEMA_VERSION: &str =
"game-creator-supervisor-collaboration-policy-snapshot.v1";
const SUPERVISOR_COLLABORATION_POLICY_SNAPSHOT_BINDING_SCHEMA_VERSION: &str =
"game-creator-supervisor-collaboration-policy-snapshot-binding.v1";
pub(crate) const SUPERVISOR_COLLABORATION_POLICY_RELATIVE_PATH: &str =
".agent/collaboration-policy.json";
const SUPERVISOR_COLLABORATION_POLICY_MAX_BYTES: usize = 16 * 1024;
const SUPERVISOR_COLLABORATION_POLICY_SNAPSHOT_MAX_BYTES: usize = 24 * 1024;
const SUPERVISOR_COLLABORATION_POLICY_SNAPSHOT_BINDING_MAX_BYTES: usize = 8 * 1024;
const SUPERVISOR_COLLABORATION_POLICY_BINDING_INITIAL_BATCH: &str = "initial-collaboration-batch";
const SUPERVISOR_COLLABORATION_POLICY_BINDING_LEGACY_BATCH: &str = "legacy-provider-batch-contract";
const SUPERVISOR_COLLABORATION_POLICY_BINDING_LEGACY_CURRENT: &str =
"legacy-current-project-policy";
const SUPERVISOR_COLLABORATION_MAX_STATIC_DELEGATES: usize = 3;
const SUPERVISOR_COLLABORATION_MAX_ISOLATED_CHILDREN: usize = 3;
const SUPERVISOR_COLLABORATION_MAX_ISOLATED_GROUPS_BEFORE_CLAIM: usize = 16;
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum SupervisorInitialCollaborationWave {
#[default]
Auto,
Static,
Isolated,
Mixed,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub(crate) struct SupervisorCollaborationPolicy {
pub(crate) schema_version: String,
#[serde(default)]
pub(crate) required_initial_wave: SupervisorInitialCollaborationWave,
#[serde(default)]
pub(crate) min_static_delegates: usize,
#[serde(default)]
pub(crate) required_static_agent_ids: Vec<String>,
#[serde(default)]
pub(crate) min_isolated_children: usize,
#[serde(default, skip_serializing_if = "is_zero")]
pub(crate) min_isolated_groups_before_claim: usize,
#[serde(default = "default_orchestrator_only_after_delegation")]
pub(crate) orchestrator_only_after_delegation: bool,
}
impl Default for SupervisorCollaborationPolicy {
fn default() -> Self {
Self {
schema_version: SUPERVISOR_COLLABORATION_POLICY_SCHEMA_VERSION.to_string(),
required_initial_wave: SupervisorInitialCollaborationWave::Auto,
min_static_delegates: 0,
required_static_agent_ids: Vec::new(),
min_isolated_children: 0,
min_isolated_groups_before_claim: 0,
orchestrator_only_after_delegation: true,
}
}
}
fn autonomous_game_build_supervisor_collaboration_policy(
_root: &Path,
) -> SupervisorCollaborationPolicy {
SupervisorCollaborationPolicy::default()
}
#[derive(Clone, Debug)]
struct SupervisorCollaborationUnboundPolicy {
policy: SupervisorCollaborationPolicy,
source: &'static str,
project_policy_status: &'static str,
project_policy_present: bool,
}
fn read_supervisor_collaboration_unbound_policy_for_run_at(
root: &Path,
parent_agent_id: &str,
parent_run_id: &str,
) -> Result<SupervisorCollaborationUnboundPolicy, String> {
let (run_profile, _) =
agent_runtime_run_profile_identity_at(root, parent_agent_id, parent_run_id, None, None)?;
let autonomous_supervisor = parent_agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
&& run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD;
let policy_path = root.join(SUPERVISOR_COLLABORATION_POLICY_RELATIVE_PATH);
match fs::symlink_metadata(&policy_path) {
Ok(_) => {
let policy = read_supervisor_collaboration_policy_at(root)?;
return Ok(SupervisorCollaborationUnboundPolicy {
policy,
source: "project-policy-unbound",
project_policy_status: "current",
project_policy_present: true,
});
}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(error) => {
return Err(format!(
"读取 Project Supervisor 协作策略文件状态失败:{error}"
));
}
}
if autonomous_supervisor {
return Ok(SupervisorCollaborationUnboundPolicy {
policy: normalize_supervisor_collaboration_policy(
autonomous_game_build_supervisor_collaboration_policy(root),
)?,
source: "autonomous-run-default",
project_policy_status: "absent",
project_policy_present: false,
});
}
Ok(SupervisorCollaborationUnboundPolicy {
policy: SupervisorCollaborationPolicy::default(),
source: "project-policy-unbound",
project_policy_status: "current",
project_policy_present: false,
})
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(crate) struct SupervisorCollaborationState {
pub(crate) initial_static_agent_ids: Vec<String>,
pub(crate) isolated_group_count: usize,
pub(crate) isolated_child_count: usize,
pub(crate) max_isolated_children_per_group: usize,
}
impl SupervisorCollaborationState {
pub(crate) fn has_collaboration(&self) -> bool {
!self.initial_static_agent_ids.is_empty() || self.isolated_group_count > 0
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub(crate) struct SupervisorCollaborationContract {
pub(crate) schema_version: String,
pub(crate) policy: SupervisorCollaborationPolicy,
pub(crate) policy_fingerprint: String,
pub(crate) initial_wave: bool,
pub(crate) initial_static_agent_ids: Vec<String>,
pub(crate) repair_delegate_count: usize,
pub(crate) isolated_spawn_count: usize,
pub(crate) isolated_child_count: usize,
pub(crate) contract_fingerprint: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub(crate) struct SupervisorCollaborationPolicySnapshot {
pub(crate) schema_version: String,
pub(crate) project_id: String,
pub(crate) parent_agent_id: String,
pub(crate) parent_run_id: String,
pub(crate) bound_from: String,
pub(crate) policy: SupervisorCollaborationPolicy,
pub(crate) policy_fingerprint: String,
pub(crate) snapshot_fingerprint: String,
pub(crate) bound_at: u64,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub(crate) struct SupervisorCollaborationPolicySnapshotBinding {
pub(crate) schema_version: String,
pub(crate) project_id: String,
pub(crate) parent_agent_id: String,
pub(crate) parent_run_id: String,
pub(crate) bound_from: String,
pub(crate) policy_fingerprint: String,
pub(crate) snapshot_fingerprint: String,
pub(crate) bound_at: u64,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SupervisorCollaborationPolicyResolution {
pub(crate) policy: SupervisorCollaborationPolicy,
pub(crate) policy_fingerprint: String,
pub(crate) snapshot_fingerprint: Option<String>,
pub(crate) binding_source: Option<String>,
pub(crate) source: &'static str,
pub(crate) project_policy_status: &'static str,
pub(crate) current_project_policy_fingerprint: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SupervisorCollaborationViolation {
pub(crate) summary: String,
pub(crate) detail: String,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(crate) struct SupervisorCollaborationPreflight {
pub(crate) contract: Option<SupervisorCollaborationContract>,
pub(crate) force_durable_batch: bool,
pub(crate) violation: Option<SupervisorCollaborationViolation>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct SupervisorCollaborationActionSummary {
initial_static_agent_ids: Vec<String>,
repair_delegate_count: usize,
isolated_spawn_count: usize,
isolated_child_count: usize,
has_project_mutation: bool,
}
impl SupervisorCollaborationActionSummary {
fn has_collaboration_action(&self) -> bool {
!self.initial_static_agent_ids.is_empty()
|| self.repair_delegate_count > 0
|| self.isolated_spawn_count > 0
}
}
fn default_orchestrator_only_after_delegation() -> bool {
true
}
fn is_zero(value: &usize) -> bool {
*value == 0
}
pub(crate) fn read_supervisor_collaboration_policy_at(
root: &Path,
) -> Result<SupervisorCollaborationPolicy, String> {
let policy = read_agent_runtime_json_sidecar_with_max_bytes::<SupervisorCollaborationPolicy>(
root,
SUPERVISOR_COLLABORATION_POLICY_RELATIVE_PATH,
"Project Supervisor 协作策略",
SUPERVISOR_COLLABORATION_POLICY_MAX_BYTES,
)?
.unwrap_or_default();
normalize_supervisor_collaboration_policy(policy)
}
pub(crate) fn write_supervisor_collaboration_policy_at(
root: &Path,
policy: SupervisorCollaborationPolicy,
) -> Result<SupervisorCollaborationPolicy, String> {
let policy = normalize_supervisor_collaboration_policy(policy)?;
write_agent_runtime_json_sidecar_with_max_bytes(
root,
SUPERVISOR_COLLABORATION_POLICY_RELATIVE_PATH,
"Project Supervisor 协作策略",
&policy,
SUPERVISOR_COLLABORATION_POLICY_MAX_BYTES,
)?;
Ok(policy)
}
pub(crate) fn render_supervisor_collaboration_policy_for_prompt_at(
root: &Path,
parent_agent_id: &str,
parent_run_id: &str,
) -> Result<String, String> {
let resolution =
resolve_supervisor_collaboration_policy_for_run_at(root, parent_agent_id, parent_run_id)?;
serde_json::to_string_pretty(&resolution.policy)
.map_err(|error| format!("序列化 Project Supervisor 协作策略失败:{error}"))
}
fn supervisor_collaboration_policy_snapshot_path_component(value: &str, fallback: &str) -> String {
let normalized = agent_runtime_confirmation_path_component(value, fallback);
if normalized == value {
return normalized;
}
let readable = normalized.chars().take(80).collect::<String>();
format!("{readable}--{:x}", Sha256::digest(value.as_bytes()))
}
fn supervisor_collaboration_policy_snapshot_relative_path(
parent_agent_id: &str,
parent_run_id: &str,
) -> String {
format!(
".agent/runtime/collaboration-policy-snapshots/{}/{}.json",
supervisor_collaboration_policy_snapshot_path_component(parent_agent_id, "agent"),
supervisor_collaboration_policy_snapshot_path_component(parent_run_id, "run")
)
}
fn supervisor_collaboration_policy_snapshot_binding_relative_path(
parent_agent_id: &str,
parent_run_id: &str,
) -> String {
format!(
".agent/runtime/collaboration-policy-snapshot-bindings/{}/{}.json",
supervisor_collaboration_policy_snapshot_path_component(parent_agent_id, "agent"),
supervisor_collaboration_policy_snapshot_path_component(parent_run_id, "run")
)
}
fn supervisor_collaboration_policy_snapshot_lock_id(
parent_agent_id: &str,
parent_run_id: &str,
) -> String {
format!(
"snapshot-{:x}",
Sha256::digest(format!("{parent_agent_id}\0{parent_run_id}").as_bytes())
)
}
pub(crate) fn supervisor_collaboration_policy_snapshot_path(
root: &Path,
parent_agent_id: &str,
parent_run_id: &str,
) -> PathBuf {
root.join(supervisor_collaboration_policy_snapshot_relative_path(
parent_agent_id,
parent_run_id,
))
}
pub(crate) fn supervisor_collaboration_policy_snapshot_binding_path(
root: &Path,
parent_agent_id: &str,
parent_run_id: &str,
) -> PathBuf {
root.join(
supervisor_collaboration_policy_snapshot_binding_relative_path(
parent_agent_id,
parent_run_id,
),
)
}
fn supervisor_collaboration_policy_snapshot_fingerprint(
snapshot: &SupervisorCollaborationPolicySnapshot,
) -> Result<String, String> {
let identity = serde_json::to_vec(&serde_json::json!({
"schemaVersion": snapshot.schema_version,
"projectId": snapshot.project_id,
"parentAgentId": snapshot.parent_agent_id,
"parentRunId": snapshot.parent_run_id,
"boundFrom": snapshot.bound_from,
"policy": snapshot.policy,
"policyFingerprint": snapshot.policy_fingerprint,
}))
.map_err(|error| format!("序列化 Project Supervisor 协作策略快照指纹失败:{error}"))?;
Ok(format!("{:x}", Sha256::digest(identity)))
}
fn validate_supervisor_collaboration_policy_snapshot(
root: &Path,
snapshot: &SupervisorCollaborationPolicySnapshot,
parent_agent_id: &str,
parent_run_id: &str,
) -> Result<(), String> {
if snapshot.schema_version != SUPERVISOR_COLLABORATION_POLICY_SNAPSHOT_SCHEMA_VERSION {
return Err(format!(
"不支持的 Project Supervisor 协作策略快照版本:{}",
snapshot.schema_version
));
}
if parent_agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|| snapshot.parent_agent_id != parent_agent_id
|| snapshot.parent_run_id != parent_run_id
|| parent_run_id.trim().is_empty()
{
return Err("Project Supervisor 协作策略快照父 run 身份不匹配".to_string());
}
if snapshot.project_id != game_creator_agent_runtime_context_project_id(root)? {
return Err("Project Supervisor 协作策略快照项目身份不匹配".to_string());
}
if !matches!(
snapshot.bound_from.as_str(),
SUPERVISOR_COLLABORATION_POLICY_BINDING_INITIAL_BATCH
| SUPERVISOR_COLLABORATION_POLICY_BINDING_LEGACY_BATCH
| SUPERVISOR_COLLABORATION_POLICY_BINDING_LEGACY_CURRENT
) {
return Err("Project Supervisor 协作策略快照绑定来源无效".to_string());
}
let normalized_policy = normalize_supervisor_collaboration_policy(snapshot.policy.clone())?;
if normalized_policy != snapshot.policy {
return Err("Project Supervisor 协作策略快照包含未规范化策略".to_string());
}
let policy_fingerprint = supervisor_collaboration_policy_fingerprint(&snapshot.policy)?;
if snapshot.policy_fingerprint != policy_fingerprint {
return Err("Project Supervisor 协作策略快照的策略指纹不匹配".to_string());
}
if snapshot.bound_at == 0
|| snapshot.snapshot_fingerprint
!= supervisor_collaboration_policy_snapshot_fingerprint(snapshot)?
{
return Err("Project Supervisor 协作策略快照身份指纹已变化".to_string());
}
Ok(())
}
fn supervisor_collaboration_policy_snapshot_binding(
snapshot: &SupervisorCollaborationPolicySnapshot,
) -> SupervisorCollaborationPolicySnapshotBinding {
SupervisorCollaborationPolicySnapshotBinding {
schema_version: SUPERVISOR_COLLABORATION_POLICY_SNAPSHOT_BINDING_SCHEMA_VERSION.to_string(),
project_id: snapshot.project_id.clone(),
parent_agent_id: snapshot.parent_agent_id.clone(),
parent_run_id: snapshot.parent_run_id.clone(),
bound_from: snapshot.bound_from.clone(),
policy_fingerprint: snapshot.policy_fingerprint.clone(),
snapshot_fingerprint: snapshot.snapshot_fingerprint.clone(),
bound_at: snapshot.bound_at,
}
}
fn validate_supervisor_collaboration_policy_snapshot_binding(
root: &Path,
binding: &SupervisorCollaborationPolicySnapshotBinding,
parent_agent_id: &str,
parent_run_id: &str,
) -> Result<(), String> {
if binding.schema_version != SUPERVISOR_COLLABORATION_POLICY_SNAPSHOT_BINDING_SCHEMA_VERSION {
return Err(format!(
"不支持的 Project Supervisor 协作策略快照绑定记录版本:{}",
binding.schema_version
));
}
if parent_agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|| binding.parent_agent_id != parent_agent_id
|| binding.parent_run_id != parent_run_id
|| parent_run_id.trim().is_empty()
{
return Err("Project Supervisor 协作策略快照绑定记录父 run 身份不匹配".to_string());
}
if binding.project_id != game_creator_agent_runtime_context_project_id(root)? {
return Err("Project Supervisor 协作策略快照绑定记录项目身份不匹配".to_string());
}
if !matches!(
binding.bound_from.as_str(),
SUPERVISOR_COLLABORATION_POLICY_BINDING_INITIAL_BATCH
| SUPERVISOR_COLLABORATION_POLICY_BINDING_LEGACY_BATCH
| SUPERVISOR_COLLABORATION_POLICY_BINDING_LEGACY_CURRENT
) || !matches!(binding.policy_fingerprint.len(), 64)
|| !binding
.policy_fingerprint
.bytes()
.all(|byte| byte.is_ascii_hexdigit())
|| !matches!(binding.snapshot_fingerprint.len(), 64)
|| !binding
.snapshot_fingerprint
.bytes()
.all(|byte| byte.is_ascii_hexdigit())
|| binding.bound_at == 0
{
return Err("Project Supervisor 协作策略快照绑定记录内容无效".to_string());
}
Ok(())
}
pub(crate) fn read_supervisor_collaboration_policy_snapshot_binding_at(
root: &Path,
parent_agent_id: &str,
parent_run_id: &str,
) -> Result<Option<SupervisorCollaborationPolicySnapshotBinding>, String> {
let relative_path = supervisor_collaboration_policy_snapshot_binding_relative_path(
parent_agent_id,
parent_run_id,
);
let Some(binding) = read_agent_runtime_json_sidecar_with_max_bytes(
root,
&relative_path,
"Project Supervisor 协作策略快照绑定记录",
SUPERVISOR_COLLABORATION_POLICY_SNAPSHOT_BINDING_MAX_BYTES,
)?
else {
return Ok(None);
};
validate_supervisor_collaboration_policy_snapshot_binding(
root,
&binding,
parent_agent_id,
parent_run_id,
)?;
Ok(Some(binding))
}
fn write_supervisor_collaboration_policy_snapshot_binding_at(
root: &Path,
snapshot: &SupervisorCollaborationPolicySnapshot,
) -> Result<SupervisorCollaborationPolicySnapshotBinding, String> {
let binding = supervisor_collaboration_policy_snapshot_binding(snapshot);
let relative_path = supervisor_collaboration_policy_snapshot_binding_relative_path(
&snapshot.parent_agent_id,
&snapshot.parent_run_id,
);
write_agent_runtime_json_sidecar_with_max_bytes(
root,
&relative_path,
"Project Supervisor 协作策略快照绑定记录",
&binding,
SUPERVISOR_COLLABORATION_POLICY_SNAPSHOT_BINDING_MAX_BYTES,
)?;
let persisted = read_supervisor_collaboration_policy_snapshot_binding_at(
root,
&snapshot.parent_agent_id,
&snapshot.parent_run_id,
)?
.ok_or_else(|| "Project Supervisor 协作策略快照绑定记录写入后不存在".to_string())?;
if persisted != binding {
return Err("Project Supervisor 协作策略快照绑定记录并发写入后内容冲突".to_string());
}
Ok(binding)
}
pub(crate) fn read_supervisor_collaboration_policy_snapshot_at(
root: &Path,
parent_agent_id: &str,
parent_run_id: &str,
) -> Result<Option<SupervisorCollaborationPolicySnapshot>, String> {
let relative_path =
supervisor_collaboration_policy_snapshot_relative_path(parent_agent_id, parent_run_id);
let Some(snapshot) = read_agent_runtime_json_sidecar_with_max_bytes(
root,
&relative_path,
"Project Supervisor 协作策略快照",
SUPERVISOR_COLLABORATION_POLICY_SNAPSHOT_MAX_BYTES,
)?
else {
return Ok(None);
};
validate_supervisor_collaboration_policy_snapshot(
root,
&snapshot,
parent_agent_id,
parent_run_id,
)?;
Ok(Some(snapshot))
}
pub(crate) fn bind_supervisor_collaboration_policy_snapshot_at(
root: &Path,
parent_agent_id: &str,
parent_run_id: &str,
policy: &SupervisorCollaborationPolicy,
bound_from: &str,
) -> Result<SupervisorCollaborationPolicySnapshot, String> {
let policy = normalize_supervisor_collaboration_policy(policy.clone())?;
if !matches!(
bound_from,
SUPERVISOR_COLLABORATION_POLICY_BINDING_INITIAL_BATCH
| SUPERVISOR_COLLABORATION_POLICY_BINDING_LEGACY_BATCH
| SUPERVISOR_COLLABORATION_POLICY_BINDING_LEGACY_CURRENT
) {
return Err("Project Supervisor 协作策略快照绑定来源无效".to_string());
}
if parent_agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|| parent_run_id.trim().is_empty()
{
return Err("只能为有效的 Project Supervisor 父 run 绑定协作策略快照".to_string());
}
let lock_id = supervisor_collaboration_policy_snapshot_lock_id(parent_agent_id, parent_run_id);
let _binding_lock = try_acquire_game_creator_agent_delegation_lock_with_wait(
root,
&lock_id,
"collaboration-policy-snapshot",
)?
.ok_or_else(|| "Project Supervisor 协作策略快照并发绑定冲突:正被其他进程绑定".to_string())?;
let existing_binding = read_supervisor_collaboration_policy_snapshot_binding_at(
root,
parent_agent_id,
parent_run_id,
)?;
if let Some(existing) =
read_supervisor_collaboration_policy_snapshot_at(root, parent_agent_id, parent_run_id)?
{
if existing.policy != policy {
return Err("Project Supervisor 协作策略快照已绑定且与待恢复策略冲突".to_string());
}
let expected_binding = supervisor_collaboration_policy_snapshot_binding(&existing);
match existing_binding {
Some(binding) if binding != expected_binding => {
return Err("Project Supervisor 协作策略快照与绑定记录冲突".to_string());
}
Some(_) => {}
None => {
write_supervisor_collaboration_policy_snapshot_binding_at(root, &existing)?;
}
}
return Ok(existing);
}
let policy_fingerprint = supervisor_collaboration_policy_fingerprint(&policy)?;
let (effective_bound_from, bound_at) = if let Some(binding) = existing_binding.as_ref() {
if binding.policy_fingerprint != policy_fingerprint {
return Err("Project Supervisor 协作策略快照绑定记录与待恢复策略冲突".to_string());
}
let existing_batch_derived = matches!(
binding.bound_from.as_str(),
SUPERVISOR_COLLABORATION_POLICY_BINDING_INITIAL_BATCH
| SUPERVISOR_COLLABORATION_POLICY_BINDING_LEGACY_BATCH
);
let requested_batch_derived = matches!(
bound_from,
SUPERVISOR_COLLABORATION_POLICY_BINDING_INITIAL_BATCH
| SUPERVISOR_COLLABORATION_POLICY_BINDING_LEGACY_BATCH
);
if binding.bound_from != bound_from && !(existing_batch_derived && requested_batch_derived)
{
return Err("Project Supervisor 协作策略快照绑定来源与恢复来源冲突".to_string());
}
(binding.bound_from.clone(), binding.bound_at)
} else {
(bound_from.to_string(), unix_timestamp())
};
let mut snapshot = SupervisorCollaborationPolicySnapshot {
schema_version: SUPERVISOR_COLLABORATION_POLICY_SNAPSHOT_SCHEMA_VERSION.to_string(),
project_id: game_creator_agent_runtime_context_project_id(root)?,
parent_agent_id: parent_agent_id.to_string(),
parent_run_id: parent_run_id.to_string(),
bound_from: effective_bound_from,
policy_fingerprint,
policy,
snapshot_fingerprint: String::new(),
bound_at,
};
snapshot.snapshot_fingerprint =
supervisor_collaboration_policy_snapshot_fingerprint(&snapshot)?;
if existing_binding.as_ref().is_some_and(|binding| {
binding != &supervisor_collaboration_policy_snapshot_binding(&snapshot)
}) {
return Err("Project Supervisor 协作策略快照无法按原绑定记录恢复".to_string());
}
let relative_path =
supervisor_collaboration_policy_snapshot_relative_path(parent_agent_id, parent_run_id);
write_agent_runtime_json_sidecar_with_max_bytes(
root,
&relative_path,
"Project Supervisor 协作策略快照",
&snapshot,
SUPERVISOR_COLLABORATION_POLICY_SNAPSHOT_MAX_BYTES,
)?;
let persisted =
read_supervisor_collaboration_policy_snapshot_at(root, parent_agent_id, parent_run_id)?
.ok_or_else(|| "Project Supervisor 协作策略快照写入后不存在".to_string())?;
if persisted != snapshot {
return Err("Project Supervisor 协作策略快照并发绑定后内容冲突".to_string());
}
if existing_binding.is_none() {
write_supervisor_collaboration_policy_snapshot_binding_at(root, &snapshot)?;
}
Ok(snapshot)
}
pub(crate) fn bind_supervisor_collaboration_policy_snapshot_for_new_batch_at(
root: &Path,
parent_agent_id: &str,
parent_run_id: &str,
policy: &SupervisorCollaborationPolicy,
) -> Result<SupervisorCollaborationPolicySnapshot, String> {
let policy = normalize_supervisor_collaboration_policy(policy.clone())?;
bind_supervisor_collaboration_policy_snapshot_at(
root,
parent_agent_id,
parent_run_id,
&policy,
SUPERVISOR_COLLABORATION_POLICY_BINDING_INITIAL_BATCH,
)
}
pub(crate) fn recover_supervisor_collaboration_policy_snapshot_from_batch_at(
root: &Path,
parent_agent_id: &str,
parent_run_id: &str,
policy: &SupervisorCollaborationPolicy,
) -> Result<SupervisorCollaborationPolicySnapshot, String> {
bind_supervisor_collaboration_policy_snapshot_at(
root,
parent_agent_id,
parent_run_id,
policy,
SUPERVISOR_COLLABORATION_POLICY_BINDING_LEGACY_BATCH,
)
}
pub(crate) fn resolve_supervisor_collaboration_policy_for_run_at(
root: &Path,
parent_agent_id: &str,
parent_run_id: &str,
) -> Result<SupervisorCollaborationPolicyResolution, String> {
if let Some(snapshot) =
read_supervisor_collaboration_policy_snapshot_at(root, parent_agent_id, parent_run_id)?
{
let snapshot = bind_supervisor_collaboration_policy_snapshot_at(
root,
parent_agent_id,
parent_run_id,
&snapshot.policy,
&snapshot.bound_from,
)?;
return supervisor_collaboration_policy_resolution_from_snapshot(root, snapshot);
}
let existing_binding = read_supervisor_collaboration_policy_snapshot_binding_at(
root,
parent_agent_id,
parent_run_id,
)?;
if let Some(snapshot) =
recover_supervisor_collaboration_policy_snapshot_from_pending_batch_if_any_at(
root,
parent_agent_id,
parent_run_id,
)?
{
return supervisor_collaboration_policy_resolution_from_snapshot(root, snapshot);
}
if existing_binding.is_some() {
return Err(
"Project Supervisor 协作策略快照绑定记录存在,但快照与可信 v2 batch 均不可恢复"
.to_string(),
);
}
let unbound_policy = read_supervisor_collaboration_unbound_policy_for_run_at(
root,
parent_agent_id,
parent_run_id,
)?;
let policy = unbound_policy.policy.clone();
let state = read_supervisor_collaboration_state_at(root, parent_agent_id, parent_run_id)?;
if state.has_collaboration() {
if !game_creator_agent_runtime_run_is_non_terminal_for_collaboration_migration_at(
root,
parent_agent_id,
parent_run_id,
)? {
return Err(
"只有仍在运行且没有可信 v2 contract 的旧 Project Supervisor 父 run 才能迁移当前项目策略"
.to_string(),
);
}
let snapshot = bind_supervisor_collaboration_policy_snapshot_at(
root,
parent_agent_id,
parent_run_id,
&policy,
SUPERVISOR_COLLABORATION_POLICY_BINDING_LEGACY_CURRENT,
)?;
return Ok(SupervisorCollaborationPolicyResolution {
policy: snapshot.policy,
policy_fingerprint: snapshot.policy_fingerprint,
snapshot_fingerprint: Some(snapshot.snapshot_fingerprint),
binding_source: Some(snapshot.bound_from),
source: "legacy-run-migration",
project_policy_status: "matched",
current_project_policy_fingerprint: Some(supervisor_collaboration_policy_fingerprint(
&policy,
)?),
});
}
Ok(SupervisorCollaborationPolicyResolution {
policy_fingerprint: supervisor_collaboration_policy_fingerprint(&policy)?,
policy,
snapshot_fingerprint: None,
binding_source: None,
source: unbound_policy.source,
project_policy_status: unbound_policy.project_policy_status,
current_project_policy_fingerprint: None,
})
}
fn supervisor_collaboration_policy_resolution_from_snapshot(
root: &Path,
snapshot: SupervisorCollaborationPolicySnapshot,
) -> Result<SupervisorCollaborationPolicyResolution, String> {
let current_policy = read_supervisor_collaboration_unbound_policy_for_run_at(
root,
&snapshot.parent_agent_id,
&snapshot.parent_run_id,
);
let (project_policy_status, current_project_policy_fingerprint) = match current_policy {
Ok(current) => {
let fingerprint = current
.project_policy_present
.then(|| supervisor_collaboration_policy_fingerprint(&current.policy))
.transpose()?;
let status = if current.policy == snapshot.policy {
"matched"
} else {
"drifted"
};
(status, fingerprint)
}
Err(_) => ("unreadable", None),
};
Ok(SupervisorCollaborationPolicyResolution {
policy: snapshot.policy,
policy_fingerprint: snapshot.policy_fingerprint,
snapshot_fingerprint: Some(snapshot.snapshot_fingerprint),
binding_source: Some(snapshot.bound_from),
source: "run-snapshot",
project_policy_status,
current_project_policy_fingerprint,
})
}
pub(crate) fn supervisor_collaboration_policy_status_for_run_at(
root: &Path,
parent_agent_id: &str,
parent_run_id: &str,
) -> Result<String, String> {
let resolution =
resolve_supervisor_collaboration_policy_for_run_at(root, parent_agent_id, parent_run_id)?;
Ok(format!(
"source={} · bindingSource={} · policyFingerprint={} · snapshotFingerprint={} · projectPolicyStatus={} · currentProjectPolicyFingerprint={}",
resolution.source,
resolution.binding_source.as_deref().unwrap_or("none"),
resolution.policy_fingerprint,
resolution.snapshot_fingerprint.as_deref().unwrap_or("none"),
resolution.project_policy_status,
resolution
.current_project_policy_fingerprint
.as_deref()
.unwrap_or("unavailable"),
))
}
pub(crate) fn read_supervisor_collaboration_state_at(
root: &Path,
parent_agent_id: &str,
parent_run_id: &str,
) -> Result<SupervisorCollaborationState, String> {
let initial_static_agent_ids =
static_delegate_target_agent_ids_at(root, parent_agent_id, parent_run_id)?;
let isolated = isolated_agent_group_summary_at(root, parent_agent_id, parent_run_id)?;
Ok(SupervisorCollaborationState {
initial_static_agent_ids,
isolated_group_count: isolated.group_count,
isolated_child_count: isolated.child_count,
max_isolated_children_per_group: isolated.max_child_count,
})
}
pub(crate) fn supervisor_collaboration_policy_fingerprint(
policy: &SupervisorCollaborationPolicy,
) -> Result<String, String> {
let bytes = serde_json::to_vec(policy)
.map_err(|error| format!("序列化 Project Supervisor 协作策略指纹失败:{error}"))?;
Ok(format!("{:x}", Sha256::digest(bytes)))
}
pub(crate) fn preflight_supervisor_collaboration_plan(
agent_id: &str,
actions: &[AgentRuntimeToolAction],
policy: &SupervisorCollaborationPolicy,
state: &SupervisorCollaborationState,
) -> Result<SupervisorCollaborationPreflight, String> {
if agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
return Ok(SupervisorCollaborationPreflight::default());
}
let policy = normalize_supervisor_collaboration_policy(policy.clone())?;
let initial_wave = !state.has_collaboration();
if actions.is_empty() {
if initial_wave && supervisor_collaboration_policy_has_initial_requirements(&policy) {
return Ok(SupervisorCollaborationPreflight {
violation: Some(SupervisorCollaborationViolation {
summary: "Project Supervisor 首批协作不能停留在计划更新".to_string(),
detail: "当前父 run 尚无协作事实,且项目 policy 明确要求首批专业协作;首批不能停留在计划更新,必须在同一 Provider 批次完整提交协作。".to_string(),
}),
..SupervisorCollaborationPreflight::default()
});
}
return Ok(SupervisorCollaborationPreflight::default());
}
let summary = summarize_supervisor_collaboration_actions(actions)?;
let has_collaboration_action = summary.has_collaboration_action();
if !initial_wave
&& has_collaboration_action
&& supervisor_collaboration_policy_has_initial_requirements(&policy)
{
if let Some(detail) = supervisor_collaboration_initial_wave_gap(&policy, state) {
return Ok(SupervisorCollaborationPreflight {
violation: Some(SupervisorCollaborationViolation {
summary: "Project Supervisor 首批协作不能跨 Provider 批次补齐".to_string(),
detail: format!(
"当前父 run 已有部分首批协作事实,但仍不满足项目合同:{detail}。只能恢复原 Provider action 批次或进入人工核对,不能另起批次补齐。"
),
}),
..SupervisorCollaborationPreflight::default()
});
}
}
if policy.orchestrator_only_after_delegation
&& summary.has_project_mutation
&& (state.has_collaboration() || has_collaboration_action)
{
return Ok(SupervisorCollaborationPreflight {
violation: Some(SupervisorCollaborationViolation {
summary: "Project Supervisor 已进入协作编排,不能直接修改项目".to_string(),
detail: "当前父 run 已有协作事实,或本批次正在创建协作;请把项目修改交给专业 Agent,Supervisor 只继续委派、读取、认领回执和验证。".to_string(),
}),
..SupervisorCollaborationPreflight::default()
});
}
if initial_wave
&& supervisor_collaboration_policy_has_initial_requirements(&policy)
&& (has_collaboration_action || summary.has_project_mutation)
{
let candidate_state = SupervisorCollaborationState {
initial_static_agent_ids: summary.initial_static_agent_ids.clone(),
isolated_group_count: summary.isolated_spawn_count,
isolated_child_count: summary.isolated_child_count,
max_isolated_children_per_group: summary.isolated_child_count,
};
if let Some(detail) = supervisor_collaboration_initial_wave_gap(&policy, &candidate_state) {
return Ok(SupervisorCollaborationPreflight {
violation: Some(SupervisorCollaborationViolation {
summary: "Project Supervisor 首批协作不满足项目合同".to_string(),
detail,
}),
..SupervisorCollaborationPreflight::default()
});
}
}
if !has_collaboration_action {
return Ok(SupervisorCollaborationPreflight::default());
}
let contract = build_supervisor_collaboration_contract(&policy, initial_wave, &summary)?;
Ok(SupervisorCollaborationPreflight {
contract: Some(contract),
force_durable_batch: true,
violation: None,
})
}
pub(crate) fn validate_supervisor_collaboration_contract(
actions: &[AgentRuntimeToolAction],
current_policy: &SupervisorCollaborationPolicy,
contract: &SupervisorCollaborationContract,
expected_initial_wave: Option<bool>,
) -> Result<(), String> {
if contract.schema_version != SUPERVISOR_COLLABORATION_CONTRACT_SCHEMA_VERSION {
return Err(format!(
"不支持的 Project Supervisor 协作合同版本:{}",
contract.schema_version
));
}
let policy = normalize_supervisor_collaboration_policy(current_policy.clone())?;
if contract.policy != policy {
return Err("Project Supervisor 协作策略在 Provider 批次执行前已变化".to_string());
}
let policy_fingerprint = supervisor_collaboration_policy_fingerprint(&policy)?;
if contract.policy_fingerprint != policy_fingerprint {
return Err("Project Supervisor 协作合同的策略指纹不匹配".to_string());
}
if expected_initial_wave.is_some_and(|expected| contract.initial_wave != expected) {
return Err("Project Supervisor 协作合同的 initialWave 与当前父 run 不一致".to_string());
}
let summary = summarize_supervisor_collaboration_actions(actions)?;
if contract.initial_static_agent_ids != summary.initial_static_agent_ids
|| contract.repair_delegate_count != summary.repair_delegate_count
|| contract.isolated_spawn_count != summary.isolated_spawn_count
|| contract.isolated_child_count != summary.isolated_child_count
{
return Err("Project Supervisor 协作合同与 Provider 动作组成不一致".to_string());
}
if policy.orchestrator_only_after_delegation
&& summary.has_project_mutation
&& summary.has_collaboration_action()
{
return Err("Project Supervisor 协作批次混入了项目 mutation".to_string());
}
if contract.initial_wave && supervisor_collaboration_policy_has_initial_requirements(&policy) {
let candidate_state = SupervisorCollaborationState {
initial_static_agent_ids: summary.initial_static_agent_ids.clone(),
isolated_group_count: summary.isolated_spawn_count,
isolated_child_count: summary.isolated_child_count,
max_isolated_children_per_group: summary.isolated_child_count,
};
if let Some(detail) = supervisor_collaboration_initial_wave_gap(&policy, &candidate_state) {
return Err(format!("Project Supervisor 协作合同不完整:{detail}"));
}
}
let fingerprint = supervisor_collaboration_contract_fingerprint(contract)?;
if contract.contract_fingerprint != fingerprint {
return Err("Project Supervisor 协作合同指纹已变化".to_string());
}
Ok(())
}
pub(crate) fn supervisor_collaboration_completion_gap(
policy: &SupervisorCollaborationPolicy,
state: &SupervisorCollaborationState,
) -> Option<String> {
if let Some(detail) = supervisor_collaboration_initial_wave_gap(policy, state) {
return Some(detail);
}
if state.isolated_group_count < policy.min_isolated_groups_before_claim {
return Some(format!(
"minIsolatedGroupsBeforeClaim={} · isolatedGroups={}/{}",
policy.min_isolated_groups_before_claim,
state.isolated_group_count,
policy.min_isolated_groups_before_claim,
));
}
None
}
pub(crate) fn supervisor_collaboration_initial_wave_gap(
policy: &SupervisorCollaborationPolicy,
state: &SupervisorCollaborationState,
) -> Option<String> {
let required_static_count = required_static_delegate_count(policy);
let required_isolated_count = required_isolated_child_count(policy);
let actual_static = state
.initial_static_agent_ids
.iter()
.cloned()
.collect::<std::collections::BTreeSet<_>>();
let missing_static_agents = policy
.required_static_agent_ids
.iter()
.filter(|agent_id| !actual_static.contains(*agent_id))
.cloned()
.collect::<Vec<_>>();
if actual_static.len() >= required_static_count
&& state.max_isolated_children_per_group >= required_isolated_count
&& missing_static_agents.is_empty()
{
return None;
}
Some(format!(
"requiredInitialWave={:?} · static={}/{} · isolatedChildrenPerGroup={}/{} · isolatedChildrenTotal={} · missingStaticAgents={}",
policy.required_initial_wave,
actual_static.len(),
required_static_count,
state.max_isolated_children_per_group,
required_isolated_count,
state.isolated_child_count,
if missing_static_agents.is_empty() {
"none".to_string()
} else {
missing_static_agents.join(",")
}
))
}
pub(crate) fn supervisor_collaboration_policy_has_initial_requirements(
policy: &SupervisorCollaborationPolicy,
) -> bool {
required_static_delegate_count(policy) > 0 || required_isolated_child_count(policy) > 0
}
pub(crate) fn is_supervisor_orchestrator_project_mutation_tool(tool: &str) -> bool {
matches!(
tool.trim(),
"file.write"
| "file.patch"
| "file.delete"
| "project.patchset"
| "project.restore"
| "project.git_commit"
| "command.exec"
| "command.start"
| "command.stdin"
| "canvas.asset_generate"
)
}
fn normalize_supervisor_collaboration_policy(
mut policy: SupervisorCollaborationPolicy,
) -> Result<SupervisorCollaborationPolicy, String> {
if policy.schema_version != SUPERVISOR_COLLABORATION_POLICY_SCHEMA_VERSION {
return Err(format!(
"不支持的 Project Supervisor 协作策略版本:{}",
policy.schema_version
));
}
if policy.min_static_delegates > SUPERVISOR_COLLABORATION_MAX_STATIC_DELEGATES {
return Err(format!(
"minStaticDelegates 不能超过 {SUPERVISOR_COLLABORATION_MAX_STATIC_DELEGATES}"
));
}
if policy.min_isolated_children > SUPERVISOR_COLLABORATION_MAX_ISOLATED_CHILDREN {
return Err(format!(
"minIsolatedChildren 不能超过 {SUPERVISOR_COLLABORATION_MAX_ISOLATED_CHILDREN}"
));
}
if policy.min_isolated_groups_before_claim
> SUPERVISOR_COLLABORATION_MAX_ISOLATED_GROUPS_BEFORE_CLAIM
{
return Err(format!(
"minIsolatedGroupsBeforeClaim 不能超过 {SUPERVISOR_COLLABORATION_MAX_ISOLATED_GROUPS_BEFORE_CLAIM}"
));
}
let mut required_ids = std::collections::BTreeSet::new();
for agent_id in policy.required_static_agent_ids {
let agent_id = normalize_game_creator_runtime_agent_id(&agent_id)?;
if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID || agent_id.starts_with("child-") {
return Err(
"requiredStaticAgentIds 只能包含静态专业 Agent,不能包含 Supervisor 或动态 child"
.to_string(),
);
}
required_ids.insert(agent_id);
}
if required_ids.len() > SUPERVISOR_COLLABORATION_MAX_STATIC_DELEGATES {
return Err(format!(
"requiredStaticAgentIds 不能超过 {SUPERVISOR_COLLABORATION_MAX_STATIC_DELEGATES} 项"
));
}
policy.required_static_agent_ids = required_ids.into_iter().collect();
let required_action_slots = required_static_delegate_count(&policy)
.saturating_add(usize::from(required_isolated_child_count(&policy) > 0));
if required_action_slots > AGENT_RUNTIME_BACKGROUND_TOOL_ACTION_LIMIT {
return Err(format!(
"协作策略至少需要 {required_action_slots} 个 Provider action,超过单批次上限 {AGENT_RUNTIME_BACKGROUND_TOOL_ACTION_LIMIT}"
));
}
Ok(policy)
}
fn required_static_delegate_count(policy: &SupervisorCollaborationPolicy) -> usize {
let mode_minimum = usize::from(matches!(
policy.required_initial_wave,
SupervisorInitialCollaborationWave::Static | SupervisorInitialCollaborationWave::Mixed
));
mode_minimum
.max(policy.min_static_delegates)
.max(policy.required_static_agent_ids.len())
}
fn required_isolated_child_count(policy: &SupervisorCollaborationPolicy) -> usize {
let mode_minimum = usize::from(matches!(
policy.required_initial_wave,
SupervisorInitialCollaborationWave::Isolated | SupervisorInitialCollaborationWave::Mixed
));
mode_minimum.max(policy.min_isolated_children)
}
fn summarize_supervisor_collaboration_actions(
actions: &[AgentRuntimeToolAction],
) -> Result<SupervisorCollaborationActionSummary, String> {
let mut summary = SupervisorCollaborationActionSummary::default();
let mut static_agents = std::collections::BTreeSet::new();
for action in actions {
let tool = action.tool.trim();
summary.has_project_mutation |= is_supervisor_orchestrator_project_mutation_tool(tool);
match tool {
"agent.delegate" => {
let input = action.input.as_object().ok_or_else(|| {
"Project Supervisor agent.delegate input 必须是 object".to_string()
})?;
let agent_id = input
.get("agentId")
.or_else(|| input.get("agent_id"))
.and_then(Value::as_str)
.ok_or_else(|| "Project Supervisor agent.delegate 缺少 agentId".to_string())?;
let agent_id = normalize_game_creator_runtime_agent_id(agent_id)?;
if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|| agent_id.starts_with("child-")
{
return Err(
"Project Supervisor agent.delegate 只能指向静态专业 Agent".to_string()
);
}
let expected_artifacts = input
.get("expectedArtifacts")
.or_else(|| input.get("expected_artifacts"))
.and_then(Value::as_array)
.into_iter()
.flatten();
for artifact in expected_artifacts.filter_map(Value::as_str) {
let expected_owner = match artifact.trim() {
"assets/art-spec.png" => Some("art-director"),
"assets/ui-prototype.png" => Some("design-foundation"),
"assets/art-spritesheet.png" => Some("art-asset-plan"),
_ => None,
};
if expected_owner.is_some_and(|owner| owner != agent_id) {
return Err(format!(
"Project Supervisor 视觉委派不能跨 owner 声明固定产物:{artifact} 必须交给 {}",
expected_owner.expect("checked visual artifact owner")
));
}
}
let repair = input
.get("repairOfDelegationId")
.or_else(|| input.get("repair_of_delegation_id"))
.and_then(Value::as_str)
.is_some_and(|value| !value.trim().is_empty());
if repair {
summary.repair_delegate_count = summary.repair_delegate_count.saturating_add(1);
} else {
static_agents.insert(agent_id);
}
}
"agent.spawn_isolated" => {
let input = action.input.as_object().ok_or_else(|| {
"Project Supervisor agent.spawn_isolated input 必须是 object".to_string()
})?;
if input.get("joinMode").and_then(Value::as_str) != Some("all") {
return Err(
"Project Supervisor agent.spawn_isolated 只允许 joinMode=all".to_string(),
);
}
let children =
input
.get("children")
.and_then(Value::as_array)
.ok_or_else(|| {
"Project Supervisor agent.spawn_isolated 缺少 children".to_string()
})?;
if children.is_empty()
|| children.len() > SUPERVISOR_COLLABORATION_MAX_ISOLATED_CHILDREN
{
return Err(format!(
"Project Supervisor agent.spawn_isolated children 必须在 1-{SUPERVISOR_COLLABORATION_MAX_ISOLATED_CHILDREN} 之间"
));
}
summary.isolated_spawn_count = summary.isolated_spawn_count.saturating_add(1);
if summary.isolated_spawn_count > 1 {
return Err(
"Project Supervisor 单个 Provider 批次最多包含一个 agent.spawn_isolated"
.to_string(),
);
}
summary.isolated_child_count = children.len();
}
_ => {}
}
}
summary.initial_static_agent_ids = static_agents.into_iter().collect();
Ok(summary)
}
pub(crate) fn supervisor_collaboration_actions_require_contract(
actions: &[AgentRuntimeToolAction],
) -> Result<bool, String> {
summarize_supervisor_collaboration_actions(actions)
.map(|summary| summary.has_collaboration_action())
}
fn build_supervisor_collaboration_contract(
policy: &SupervisorCollaborationPolicy,
initial_wave: bool,
summary: &SupervisorCollaborationActionSummary,
) -> Result<SupervisorCollaborationContract, String> {
let policy_fingerprint = supervisor_collaboration_policy_fingerprint(policy)?;
let mut contract = SupervisorCollaborationContract {
schema_version: SUPERVISOR_COLLABORATION_CONTRACT_SCHEMA_VERSION.to_string(),
policy: policy.clone(),
policy_fingerprint,
initial_wave,
initial_static_agent_ids: summary.initial_static_agent_ids.clone(),
repair_delegate_count: summary.repair_delegate_count,
isolated_spawn_count: summary.isolated_spawn_count,
isolated_child_count: summary.isolated_child_count,
contract_fingerprint: String::new(),
};
contract.contract_fingerprint = supervisor_collaboration_contract_fingerprint(&contract)?;
Ok(contract)
}
fn supervisor_collaboration_contract_fingerprint(
contract: &SupervisorCollaborationContract,
) -> Result<String, String> {
let identity = serde_json::to_vec(&serde_json::json!({
"schemaVersion": contract.schema_version,
"policy": contract.policy,
"policyFingerprint": contract.policy_fingerprint,
"initialWave": contract.initial_wave,
"initialStaticAgentIds": contract.initial_static_agent_ids,
"repairDelegateCount": contract.repair_delegate_count,
"isolatedSpawnCount": contract.isolated_spawn_count,
"isolatedChildCount": contract.isolated_child_count,
}))
.map_err(|error| format!("序列化 Project Supervisor 协作合同指纹失败:{error}"))?;
Ok(format!("{:x}", Sha256::digest(identity)))
}
#[cfg(test)]
mod tests {
use super::*;
fn delegate(agent_id: &str, repair: Option<&str>) -> AgentRuntimeToolAction {
AgentRuntimeToolAction {
tool: "agent.delegate".to_string(),
reason: Some("委派专业交付".to_string()),
input: serde_json::json!({
"agentId": agent_id,
"task": "完成专业交付",
"acceptanceCriteria": ["交付可验收"],
"expectedArtifacts": [],
"repairOfDelegationId": repair,
"runId": null,
}),
}
}
fn spawn(children: usize) -> AgentRuntimeToolAction {
AgentRuntimeToolAction {
tool: "agent.spawn_isolated".to_string(),
reason: Some("并行隔离检查".to_string()),
input: serde_json::json!({
"children": (0..children).map(|index| serde_json::json!({
"templateAgentId": "code-prototype",
"task": format!("检查 {index}"),
"acceptanceCriteria": ["检查完成"],
"expectedArtifacts": [],
"writeScopes": [format!("game/check-{index}/**")],
})).collect::<Vec<_>>(),
"joinMode": "all",
}),
}
}
fn verify() -> AgentRuntimeToolAction {
AgentRuntimeToolAction {
tool: "project.verify".to_string(),
reason: Some("验证项目".to_string()),
input: serde_json::json!({
"script": "test",
"expectedCommand": "cargo test",
"timeoutSeconds": 120,
}),
}
}
fn mixed_policy() -> SupervisorCollaborationPolicy {
SupervisorCollaborationPolicy {
required_initial_wave: SupervisorInitialCollaborationWave::Mixed,
min_static_delegates: 2,
required_static_agent_ids: vec![
"art-director".to_string(),
"design-director".to_string(),
],
min_isolated_children: 2,
..SupervisorCollaborationPolicy::default()
}
}
#[test]
fn supervisor_collaboration_policy_default_json_and_fingerprints_remain_v1_compatible() {
const OLD_DEFAULT_POLICY_JSON: &str = "{\"schemaVersion\":\"game-creator-supervisor-collaboration-policy.v1\",\"requiredInitialWave\":\"auto\",\"minStaticDelegates\":0,\"requiredStaticAgentIds\":[],\"minIsolatedChildren\":0,\"orchestratorOnlyAfterDelegation\":true}";
const OLD_DEFAULT_POLICY_FINGERPRINT: &str =
"9962617595c7d20ea24d7b18b4f77eac2160edaf0daaf83960d5c92014e8bd2b";
const OLD_DEFAULT_CONTRACT_FINGERPRINT: &str =
"90845e3e0f817ac99fb4f4574eae1662235739752268c4d9884f815518eab2bb";
let policy = SupervisorCollaborationPolicy::default();
assert_eq!(
serde_json::to_string(&policy).expect("serialize default policy"),
OLD_DEFAULT_POLICY_JSON
);
assert_eq!(
serde_json::from_str::<SupervisorCollaborationPolicy>(OLD_DEFAULT_POLICY_JSON)
.expect("deserialize old default policy"),
policy
);
assert_eq!(
supervisor_collaboration_policy_fingerprint(&policy)
.expect("fingerprint default policy"),
OLD_DEFAULT_POLICY_FINGERPRINT
);
let actions = vec![delegate("design-director", None)];
let contract = preflight_supervisor_collaboration_plan(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&actions,
&policy,
&SupervisorCollaborationState::default(),
)
.expect("preflight default policy")
.contract
.expect("default policy contract");
let contract_json = serde_json::to_value(&contract).expect("serialize default contract");
assert!(contract_json["policy"]
.get("minIsolatedGroupsBeforeClaim")
.is_none());
assert_eq!(contract.policy_fingerprint, OLD_DEFAULT_POLICY_FINGERPRINT);
assert_eq!(
contract.contract_fingerprint,
OLD_DEFAULT_CONTRACT_FINGERPRINT
);
}
#[test]
fn supervisor_collaboration_policy_initial_mixed_wave_allows_one_staged_group() {
let policy = SupervisorCollaborationPolicy {
min_isolated_groups_before_claim: 2,
..mixed_policy()
};
let actions = vec![
delegate("design-director", None),
delegate("art-director", None),
spawn(2),
];
let result = preflight_supervisor_collaboration_plan(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&actions,
&policy,
&SupervisorCollaborationState::default(),
)
.expect("preflight staged mixed wave");
assert!(result.violation.is_none());
let contract = result.contract.expect("staged mixed contract");
validate_supervisor_collaboration_contract(&actions, &policy, &contract, Some(true))
.expect("validate staged mixed contract");
}
#[test]
fn supervisor_collaboration_policy_completion_requires_staged_isolated_groups() {
let policy = SupervisorCollaborationPolicy {
min_isolated_groups_before_claim: 2,
..mixed_policy()
};
let mut state = SupervisorCollaborationState {
initial_static_agent_ids: vec![
"art-director".to_string(),
"design-director".to_string(),
],
isolated_group_count: 1,
isolated_child_count: 2,
max_isolated_children_per_group: 2,
};
let gap = supervisor_collaboration_completion_gap(&policy, &state)
.expect("one isolated group must leave a completion gap");
assert!(gap.contains("isolatedGroups=1/2"));
state.isolated_group_count = 2;
state.isolated_child_count = 4;
assert!(supervisor_collaboration_completion_gap(&policy, &state).is_none());
}
#[test]
fn supervisor_collaboration_policy_nonzero_group_requirement_round_trips_stably() {
let policy = SupervisorCollaborationPolicy {
min_isolated_groups_before_claim: 2,
..SupervisorCollaborationPolicy::default()
};
let json = serde_json::to_string(&policy).expect("serialize nonzero group policy");
assert!(json.contains("\"minIsolatedGroupsBeforeClaim\":2"));
let decoded = serde_json::from_str::<SupervisorCollaborationPolicy>(&json)
.expect("deserialize nonzero group policy");
assert_eq!(decoded, policy);
assert_eq!(
supervisor_collaboration_policy_fingerprint(&policy)
.expect("fingerprint nonzero group policy"),
"7c2764dde3a5be4a62e11f0acdb7e1b2f6445377f1afdc548e1b7e137ce9fc84"
);
assert_eq!(
supervisor_collaboration_policy_fingerprint(&decoded)
.expect("fingerprint round-tripped group policy"),
"7c2764dde3a5be4a62e11f0acdb7e1b2f6445377f1afdc548e1b7e137ce9fc84"
);
}
#[test]
fn supervisor_collaboration_policy_rejects_excessive_group_requirement() {
let policy = SupervisorCollaborationPolicy {
min_isolated_groups_before_claim:
SUPERVISOR_COLLABORATION_MAX_ISOLATED_GROUPS_BEFORE_CLAIM + 1,
..SupervisorCollaborationPolicy::default()
};
let error = normalize_supervisor_collaboration_policy(policy)
.expect_err("excessive group requirement must fail");
assert!(error.contains("minIsolatedGroupsBeforeClaim 不能超过 16"));
}
#[test]
fn supervisor_collaboration_policy_blocks_empty_required_initial_wave() {
let result = preflight_supervisor_collaboration_plan(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&[],
&mixed_policy(),
&SupervisorCollaborationState::default(),
)
.expect("preflight empty required initial wave");
let violation = result
.violation
.expect("required initial wave must reject empty actions");
assert_eq!(
violation.summary,
"Project Supervisor 首批协作不能停留在计划更新"
);
assert!(violation.detail.contains("首批不能停留在计划更新"));
assert!(violation
.detail
.contains("必须在同一 Provider 批次完整提交协作"));
assert!(result.contract.is_none());
assert!(!result.force_durable_batch);
}
#[test]
fn supervisor_collaboration_policy_allows_empty_actions_after_collaboration() {
let state = SupervisorCollaborationState {
initial_static_agent_ids: vec!["design-director".to_string()],
..SupervisorCollaborationState::default()
};
let result = preflight_supervisor_collaboration_plan(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&[],
&mixed_policy(),
&state,
)
.expect("preflight empty actions after collaboration");
assert_eq!(result, SupervisorCollaborationPreflight::default());
}
#[test]
fn supervisor_collaboration_policy_allows_empty_actions_without_initial_requirements() {
let result = preflight_supervisor_collaboration_plan(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&[],
&SupervisorCollaborationPolicy::default(),
&SupervisorCollaborationState::default(),
)
.expect("preflight empty actions without initial requirements");
assert_eq!(result, SupervisorCollaborationPreflight::default());
}
#[test]
fn supervisor_collaboration_policy_blocks_partial_mixed_wave() {
let result = preflight_supervisor_collaboration_plan(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&[delegate("design-director", None)],
&mixed_policy(),
&SupervisorCollaborationState::default(),
)
.expect("preflight partial mixed wave");
assert!(result.violation.is_some());
assert!(result.contract.is_none());
assert!(!result.force_durable_batch);
}
#[test]
fn supervisor_collaboration_policy_accepts_complete_mixed_wave() {
let actions = vec![
delegate("design-director", None),
delegate("art-director", None),
spawn(2),
];
let result = preflight_supervisor_collaboration_plan(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&actions,
&mixed_policy(),
&SupervisorCollaborationState::default(),
)
.expect("preflight complete mixed wave");
assert!(result.violation.is_none());
assert!(result.force_durable_batch);
let contract = result.contract.expect("durable collaboration contract");
validate_supervisor_collaboration_contract(
&actions,
&mixed_policy(),
&contract,
Some(true),
)
.expect("validate complete mixed contract");
}
#[test]
fn supervisor_collaboration_policy_allows_later_isolated_group_after_complete_wave() {
let state = SupervisorCollaborationState {
initial_static_agent_ids: vec![
"art-director".to_string(),
"design-director".to_string(),
],
isolated_group_count: 1,
isolated_child_count: 2,
max_isolated_children_per_group: 2,
};
let actions = vec![spawn(1)];
let result = preflight_supervisor_collaboration_plan(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&actions,
&mixed_policy(),
&state,
)
.expect("preflight later isolated group");
assert!(result.violation.is_none());
assert!(result.force_durable_batch);
let contract = result.contract.expect("later collaboration contract");
assert!(!contract.initial_wave);
assert_eq!(contract.isolated_spawn_count, 1);
assert_eq!(contract.isolated_child_count, 1);
validate_supervisor_collaboration_contract(
&actions,
&mixed_policy(),
&contract,
Some(false),
)
.expect("validate later isolated contract");
}
#[test]
fn supervisor_collaboration_policy_rejects_cross_batch_initial_wave_completion() {
let state = SupervisorCollaborationState {
initial_static_agent_ids: vec!["design-director".to_string()],
..SupervisorCollaborationState::default()
};
let result = preflight_supervisor_collaboration_plan(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&[delegate("art-director", None), spawn(2)],
&mixed_policy(),
&state,
)
.expect("preflight cross-batch initial wave completion");
let violation = result
.violation
.expect("partial initial wave must not be completed by a new batch");
assert!(violation.summary.contains("不能跨 Provider 批次补齐"));
assert!(result.contract.is_none());
assert!(!result.force_durable_batch);
}
#[test]
fn supervisor_collaboration_policy_blocks_mutation_with_collaboration() {
let mut actions = vec![delegate("design-director", None)];
actions.push(AgentRuntimeToolAction {
tool: "file.write".to_string(),
reason: Some("总控直接写项目".to_string()),
input: serde_json::json!({"path":"game/out.txt","content":"blocked"}),
});
let result = preflight_supervisor_collaboration_plan(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&actions,
&SupervisorCollaborationPolicy::default(),
&SupervisorCollaborationState::default(),
)
.expect("preflight collaboration mutation");
assert!(result.violation.is_some());
}
#[test]
fn supervisor_collaboration_policy_allows_repair_and_verify_after_delegation() {
let state = SupervisorCollaborationState {
initial_static_agent_ids: vec!["design-director".to_string()],
isolated_group_count: 0,
isolated_child_count: 0,
max_isolated_children_per_group: 0,
};
let actions = vec![delegate("design-director", Some("delivery-1")), verify()];
let result = preflight_supervisor_collaboration_plan(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&actions,
&SupervisorCollaborationPolicy::default(),
&state,
)
.expect("preflight repair and verify");
assert!(result.violation.is_none());
assert!(result.force_durable_batch);
}
#[test]
fn supervisor_collaboration_policy_rejects_contract_fingerprint_tampering() {
let actions = vec![delegate("design-director", None)];
let result = preflight_supervisor_collaboration_plan(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&actions,
&SupervisorCollaborationPolicy::default(),
&SupervisorCollaborationState::default(),
)
.expect("preflight contract");
let mut contract = result.contract.expect("contract");
contract.contract_fingerprint = "0".repeat(64);
assert!(validate_supervisor_collaboration_contract(
&actions,
&SupervisorCollaborationPolicy::default(),
&contract,
Some(true),
)
.expect_err("tampered contract must fail")
.contains("合同指纹"));
}
#[test]
fn supervisor_collaboration_policy_default_has_no_completion_gap() {
assert!(supervisor_collaboration_completion_gap(
&SupervisorCollaborationPolicy::default(),
&SupervisorCollaborationState::default(),
)
.is_none());
}
#[test]
fn supervisor_collaboration_policy_classifies_process_stdin_as_mutation() {
assert!(is_supervisor_orchestrator_project_mutation_tool(
"command.stdin"
));
assert!(!is_supervisor_orchestrator_project_mutation_tool(
"command.poll"
));
assert!(!is_supervisor_orchestrator_project_mutation_tool(
"command.terminate"
));
}
#[test]
fn supervisor_collaboration_policy_requires_isolated_minimum_in_one_group() {
let policy = SupervisorCollaborationPolicy {
required_initial_wave: SupervisorInitialCollaborationWave::Isolated,
min_isolated_children: 3,
..SupervisorCollaborationPolicy::default()
};
let split_groups = SupervisorCollaborationState {
isolated_group_count: 3,
isolated_child_count: 3,
max_isolated_children_per_group: 1,
..SupervisorCollaborationState::default()
};
assert!(supervisor_collaboration_completion_gap(&policy, &split_groups).is_some());
let complete_group = SupervisorCollaborationState {
isolated_group_count: 1,
isolated_child_count: 3,
max_isolated_children_per_group: 3,
..SupervisorCollaborationState::default()
};
assert!(supervisor_collaboration_completion_gap(&policy, &complete_group).is_none());
}
#[test]
fn supervisor_collaboration_policy_rejects_multiple_isolated_spawns_per_batch() {
let error = preflight_supervisor_collaboration_plan(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&[spawn(1), spawn(1)],
&SupervisorCollaborationPolicy::default(),
&SupervisorCollaborationState::default(),
)
.expect_err("multiple isolated groups must not share one provider batch");
assert!(error.contains("最多包含一个"));
}
#[test]
fn supervisor_collaboration_policy_classifies_snake_case_repair_as_repair() {
let mut action = delegate("design-director", None);
let input = action.input.as_object_mut().expect("delegate input");
input.remove("repairOfDelegationId");
input.insert(
"repair_of_delegation_id".to_string(),
Value::String("delivery-original".to_string()),
);
let result = preflight_supervisor_collaboration_plan(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&[action],
&SupervisorCollaborationPolicy::default(),
&SupervisorCollaborationState::default(),
)
.expect("preflight snake-case repair");
let contract = result.contract.expect("repair contract");
assert!(contract.initial_static_agent_ids.is_empty());
assert_eq!(contract.repair_delegate_count, 1);
}
#[test]
fn supervisor_collaboration_policy_rejects_dynamic_child_as_static_delegate() {
let error = preflight_supervisor_collaboration_plan(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&[delegate("child-code-prototype-1", None)],
&SupervisorCollaborationPolicy::default(),
&SupervisorCollaborationState::default(),
)
.expect_err("dynamic child must not count as static delegate");
assert!(error.contains("静态专业 Agent"));
}
#[test]
fn supervisor_collaboration_policy_rejects_cross_owner_visual_artifacts() {
let mut action = delegate("art-director", None);
action.input["expectedArtifacts"] = serde_json::json!([
"assets/art-spec.png",
"assets/ui-prototype.png",
"assets/art-spritesheet.png"
]);
let error = preflight_supervisor_collaboration_plan(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&[action],
&SupervisorCollaborationPolicy::default(),
&SupervisorCollaborationState::default(),
)
.expect_err("cross-owner visual artifacts must fail before dispatch");
assert!(error.contains("不能跨 owner"));
assert!(error.contains("assets/ui-prototype.png"));
assert!(error.contains("design-foundation"));
}
}