33ec7b2861
自主构建只执行正式清单任务图并移除重复静态委派 预览基础设施失败立即收口并绑定当前版本验证凭证 客户端退出前检查Runner持久任务状态并阻止忙碌关闭 修复上下文检查点恢复边界并补齐恢复测试 为全部Agent配置默认推理强度与实际重试状态展示 补齐配置、协作、预览、Runner及全量回归测试 同步技术方案、开发流程与长期决策记录
1788 lines
71 KiB
Rust
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(¤t.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"));
|
|
}
|
|
}
|