diff --git a/desktop/src-tauri/src/app_state.rs b/desktop/src-tauri/src/app_state.rs index 67bcfba6c6..165fd7019b 100644 --- a/desktop/src-tauri/src/app_state.rs +++ b/desktop/src-tauri/src/app_state.rs @@ -1,3 +1,8 @@ +use crate::huddle::HuddleState; +pub(crate) use crate::identity_storage::{IdentityStorage, RecoveryState, ResolvedIdentity}; +use crate::managed_agents::config_bridge::SessionConfigCache; +use crate::managed_agents::{ManagedAgentPairRuntime, ManagedAgentRuntimeKey}; +use nostr::{Keys, ToBech32}; use std::{ collections::HashMap, io::Write, @@ -6,16 +11,8 @@ use std::{ Arc, Mutex, }, }; - -use nostr::{Keys, ToBech32}; use tauri::{AppHandle, Manager}; use tokio::sync::Mutex as AsyncMutex; - -use crate::huddle::HuddleState; -pub(crate) use crate::identity_storage::{IdentityStorage, RecoveryState, ResolvedIdentity}; -use crate::managed_agents::config_bridge::SessionConfigCache; -use crate::managed_agents::{ManagedAgentPairRuntime, ManagedAgentRuntimeKey}; - pub struct AppState { pub keys: Mutex, /// Durable backend holding `keys`. Updated after the key write and before @@ -135,7 +132,6 @@ pub struct AppState { pub pending_owned_channels: Mutex>, pub archive_db: crate::archive::ArchiveDb, } - /// Parse the `BUZZ_PRIVATE_KEY` env var into identity keys. `Some` means the /// env var was present and valid and MUST win over any persisted/keyring key /// (the dev/CI/harness override). `None` means absent or malformed — callers @@ -157,7 +153,6 @@ fn identity_from_env() -> Option { Err(std::env::VarError::NotPresent) => None, } } - /// Build the no-redirect HTTP client used for authenticated relay media /// fetches (download / copy). /// diff --git a/desktop/src-tauri/src/commands/agents/agents_deploy_tests.rs b/desktop/src-tauri/src/commands/agents/agents_deploy_tests.rs new file mode 100644 index 0000000000..11f23ad68f --- /dev/null +++ b/desktop/src-tauri/src/commands/agents/agents_deploy_tests.rs @@ -0,0 +1,724 @@ +use super::*; +use crate::managed_agents::{readiness::EffectiveHarnessDescriptor, RespondTo, TeamRecord}; + +fn record() -> ManagedAgentRecord { + serde_json::from_value(serde_json::json!({ + "pubkey": "abcd1234", + "name": "agent-handle", + "display_name": "Agent\u{0000} Name", + "private_key_nsec": "nsec1fake", + "relay_url": "wss://relay.example", + "acp_command": "buzz-acp", + "agent_command": "goose", + "agent_args": [], + "mcp_command": "", + "turn_timeout_seconds": 320, + "idle_timeout_seconds": 17, + "max_turn_duration_seconds": 23, + "parallelism": 4, + "respond_to": RespondTo::OwnerOnly, + "respond_to_allowlist": [], + "team_id": "team-1", + "created_at": "2026-01-01T00:00:00Z", + "updated_at": "2026-01-01T00:00:00Z" + })) + .unwrap() +} + +#[test] +fn linked_assignments_resolve_from_live_definition() { + let mut record = record(); + record.persona_id = Some("persona-1".into()); + record.assigned_shared_instructions = vec!["stale-a".into(), "revoked-b".into()]; + let mut persona: AgentDefinition = serde_json::from_value(serde_json::json!({ + "id": "persona-1", + "display_name": "Persona", + "system_prompt": "prompt", + "name_pool": [], + "is_builtin": false, + "is_active": true, + "created_at": "2026-01-01T00:00:00Z", + "updated_at": "2026-01-01T00:00:00Z" + })) + .unwrap(); + persona.assigned_shared_instructions = vec!["current-a".into()]; + let personas = [persona]; + let descriptor = EffectiveHarnessDescriptor { + command: "goose".into(), + args: vec![], + env: BTreeMap::new(), + }; + let launch = build_launch_block( + &record, + &descriptor, + &[], + &personas, + None, + LaunchExperimentContext { + effective_model: None, + + shared_instructions_enabled: true, + }, + "owner-hex", + ) + .unwrap(); + + assert_eq!( + launch["policy_env"]["BUZZ_ACP_ASSIGNED_SHARED_INSTRUCTIONS"], + "current-a" + ); + assert!( + !launch["policy_env"]["BUZZ_ACP_ASSIGNED_SHARED_INSTRUCTIONS"] + .as_str() + .unwrap() + .contains("revoked-b") + ); +} + +#[test] +fn launch_block_omits_shared_instructions_when_experiment_is_disabled() { + let mut record = record(); + record.assigned_shared_instructions = vec![format!("30023:{}:review", "a".repeat(64))]; + let descriptor = EffectiveHarnessDescriptor { + command: "goose".into(), + args: vec![], + env: BTreeMap::from([( + "BUZZ_ACP_ASSIGNED_SHARED_INSTRUCTIONS".into(), + "user-supplied".into(), + )]), + }; + + let launch = build_launch_block( + &record, + &descriptor, + &[], + &[], + None, + LaunchExperimentContext { + effective_model: None, + + shared_instructions_enabled: false, + }, + "owner-hex", + ) + .unwrap(); + + assert!(launch["policy_env"]["BUZZ_ACP_ASSIGNED_SHARED_INSTRUCTIONS"].is_null()); + assert!(launch["env"]["BUZZ_ACP_ASSIGNED_SHARED_INSTRUCTIONS"].is_null()); +} + +#[test] +fn launch_block_preserves_descriptor_and_spawn_policy() { + let mut record = record(); + let coordinate = format!("30023:{}:review", "a".repeat(64)); + record.assigned_shared_instructions = vec![coordinate.clone()]; + let descriptor = EffectiveHarnessDescriptor { + command: "goose".into(), + args: vec!["acp".into()], + env: BTreeMap::from([ + ("GOOSE_MODE".into(), "custom".into()), + ("SECRET_FROM_PERSONA".into(), "secret".into()), + ( + "BUZZ_ACP_ASSIGNED_SHARED_INSTRUCTIONS".into(), + "user-supplied".into(), + ), + ]), + }; + let teams: Vec = serde_json::from_value(serde_json::json!([{ + "id": "team-1", "name": "Team", "instructions": "Coordinate", "persona_ids": [], "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z" + }])).unwrap(); + + let launch = build_launch_block( + &record, + &descriptor, + &teams, + &[], + Some("prompt"), + LaunchExperimentContext { + effective_model: Some("model"), + + shared_instructions_enabled: true, + }, + "owner-hex", + ) + .unwrap(); + + assert_eq!(launch["command"], "goose"); + assert_eq!(launch["args"], serde_json::json!(["acp"])); + assert_eq!(launch["env"]["GOOSE_MODE"], "custom"); + // policy_env is applied first, so this default remains separate from + // the descriptor value that wins in launch.env. + assert_eq!(launch["policy_env"]["GOOSE_MODE"], "auto"); + assert_eq!(launch["policy_env"]["BUZZ_ACP_LAZY_POOL"], "true"); + assert_eq!(launch["policy_env"]["BUZZ_ACP_RELAY_OBSERVER"], "true"); + assert_eq!( + launch["policy_env"]["BUZZ_ACP_TEAM_INSTRUCTIONS"], + "Coordinate" + ); + assert_eq!( + launch["policy_env"]["BUZZ_ACP_ASSIGNED_SHARED_INSTRUCTIONS"], + coordinate + ); + assert!(launch["env"]["BUZZ_ACP_ASSIGNED_SHARED_INSTRUCTIONS"].is_null()); + assert_eq!(launch["policy_env"]["BUZZ_ACP_SESSION_TITLE"], "Agent Name"); + assert_eq!(launch["policy_env"]["BUZZ_ACP_DISPLAY_NAME"], "Agent Name"); + assert_eq!(launch["policy_env"]["BUZZ_ACP_SYSTEM_PROMPT"], "prompt"); + // goose runtime: model goes via BUZZ_ACP_MODEL (non-claude path). + assert_eq!(launch["policy_env"]["BUZZ_ACP_MODEL"], "model"); + assert!( + launch["policy_env"]["ANTHROPIC_MODEL"].is_null(), + "goose must NOT receive ANTHROPIC_MODEL" + ); + assert_eq!(launch["policy_env"]["BUZZ_ACP_IDLE_TIMEOUT"], "17"); + assert_eq!(launch["policy_env"]["BUZZ_ACP_MAX_TURN_DURATION"], "23"); + assert_eq!(launch["policy_env"]["BUZZ_ACP_AGENTS"], "4"); + assert_eq!(launch["owner_pubkey"], "owner-hex"); +} + +#[test] +fn launch_block_claude_runtime_uses_anthropic_model_not_buzz_acp_model() { + // B2: remote claude deploys must send ANTHROPIC_MODEL, not BUZZ_ACP_MODEL, + // so the remote harness has a single startup model authority matching A1. + let record = record(); + let descriptor = EffectiveHarnessDescriptor { + command: "claude".into(), + args: vec![], + env: BTreeMap::new(), + }; + let teams: Vec = vec![]; + let launch = build_launch_block( + &record, + &descriptor, + &teams, + &[], + None, + LaunchExperimentContext { + effective_model: Some("claude-opus-4"), + + shared_instructions_enabled: true, + }, + "owner-hex", + ) + .unwrap(); + assert_eq!( + launch["policy_env"]["ANTHROPIC_MODEL"], "claude-opus-4", + "claude remote must receive ANTHROPIC_MODEL" + ); + assert!( + launch["policy_env"]["BUZZ_ACP_MODEL"].is_null(), + "claude remote must NOT receive BUZZ_ACP_MODEL" + ); +} + +/// F2: remote Claude launch must mirror local A1 — ALWAYS strip BOTH +/// BUZZ_ACP_MODEL and ANTHROPIC_MODEL from launch.env (tier 2), so the +/// canonical model in policy_env (tier 1) is the sole authority. Since +/// launch.env later-wins over policy_env, a user BUZZ_ACP_MODEL would add a +/// second startup authority and a user ANTHROPIC_MODEL would silently +/// override the canonical model. +#[test] +fn launch_block_claude_strips_both_model_keys_from_launch_env() { + let record = record(); + let descriptor = EffectiveHarnessDescriptor { + command: "claude".into(), + args: vec![], + env: BTreeMap::from([ + ("BUZZ_ACP_MODEL".to_string(), "user-sonnet".to_string()), + ("ANTHROPIC_MODEL".to_string(), "user-opus".to_string()), + ("KEEP_ME".to_string(), "yes".to_string()), + ]), + }; + let launch = build_launch_block( + &record, + &descriptor, + &[], + &[], + None, + LaunchExperimentContext { + effective_model: Some("claude-opus-4"), + + shared_instructions_enabled: true, + }, + "owner-hex", + ) + .unwrap(); + + // Canonical model rides policy_env alone. + assert_eq!(launch["policy_env"]["ANTHROPIC_MODEL"], "claude-opus-4"); + assert!(launch["policy_env"]["BUZZ_ACP_MODEL"].is_null()); + // Both model keys are stripped from launch.env — neither can later-win. + assert!( + launch["env"]["BUZZ_ACP_MODEL"].is_null(), + "user BUZZ_ACP_MODEL must be stripped from launch.env for claude" + ); + assert!( + launch["env"]["ANTHROPIC_MODEL"].is_null(), + "user ANTHROPIC_MODEL must be stripped from launch.env for claude" + ); + // Unrelated user env survives. + assert_eq!(launch["env"]["KEEP_ME"], "yes"); +} + +/// F2: when no canonical model resolves, a Claude launch still strips both +/// model keys from launch.env, so neither authority reaches the remote +/// process — matching local `apply_claude_model_env(None)`, which removes +/// both. +#[test] +fn launch_block_claude_strips_model_keys_even_without_canonical() { + let record = record(); + let descriptor = EffectiveHarnessDescriptor { + command: "claude".into(), + args: vec![], + env: BTreeMap::from([ + ("BUZZ_ACP_MODEL".to_string(), "user-sonnet".to_string()), + ("ANTHROPIC_MODEL".to_string(), "user-opus".to_string()), + ]), + }; + let launch = build_launch_block( + &record, + &descriptor, + &[], + &[], + None, + LaunchExperimentContext { + effective_model: None, + + shared_instructions_enabled: true, + }, + "owner-hex", + ) + .unwrap(); + + assert!(launch["policy_env"]["ANTHROPIC_MODEL"].is_null()); + assert!(launch["policy_env"]["BUZZ_ACP_MODEL"].is_null()); + assert!( + launch["env"]["BUZZ_ACP_MODEL"].is_null(), + "user BUZZ_ACP_MODEL must be stripped even without a canonical model" + ); + assert!( + launch["env"]["ANTHROPIC_MODEL"].is_null(), + "user ANTHROPIC_MODEL must be stripped even without a canonical model" + ); +} + +/// F2: non-Claude runtimes must NOT strip model keys from launch.env — the +/// model authority stripping is Claude-specific (BUZZ_ACP_MODEL is the +/// spawn authority for other runtimes and rides policy_env there). +#[test] +fn launch_block_non_claude_preserves_user_model_env() { + let record = record(); // goose command + let descriptor = EffectiveHarnessDescriptor { + command: "goose".into(), + args: vec![], + env: BTreeMap::from([("BUZZ_ACP_MODEL".to_string(), "user-model".to_string())]), + }; + let launch = build_launch_block( + &record, + &descriptor, + &[], + &[], + None, + LaunchExperimentContext { + effective_model: Some("model"), + + shared_instructions_enabled: true, + }, + "owner-hex", + ) + .unwrap(); + + // goose puts canonical in policy_env, and the user launch.env value is + // preserved (later-wins is the intended goose behavior). + assert_eq!(launch["policy_env"]["BUZZ_ACP_MODEL"], "model"); + assert_eq!(launch["env"]["BUZZ_ACP_MODEL"], "user-model"); +} + +#[test] +fn launch_block_claude_runtime_injects_effort_level_when_set() { + // I-4: remote parity — record.effort_level → BUZZ_ACP_EFFORT_LEVEL in policy_env. + let mut record = record(); + record.effort_level = Some("high".to_string()); + let descriptor = EffectiveHarnessDescriptor { + command: "claude".into(), + args: vec![], + env: BTreeMap::new(), + }; + let launch = build_launch_block( + &record, + &descriptor, + &[], + &[], + None, + LaunchExperimentContext { + effective_model: None, + + shared_instructions_enabled: true, + }, + "owner-hex", + ) + .unwrap(); + assert_eq!( + launch["policy_env"]["BUZZ_ACP_EFFORT_LEVEL"], "high", + "claude remote must receive BUZZ_ACP_EFFORT_LEVEL when effort_level is set" + ); +} + +#[test] +fn launch_block_does_not_inject_effort_level_when_absent() { + // I-4: no BUZZ_ACP_EFFORT_LEVEL in policy_env when record.effort_level is None. + let record = record(); // effort_level is None by default + let descriptor = EffectiveHarnessDescriptor { + command: "claude".into(), + args: vec![], + env: BTreeMap::new(), + }; + let launch = build_launch_block( + &record, + &descriptor, + &[], + &[], + None, + LaunchExperimentContext { + effective_model: None, + + shared_instructions_enabled: true, + }, + "owner-hex", + ) + .unwrap(); + assert!( + launch["policy_env"]["BUZZ_ACP_EFFORT_LEVEL"].is_null(), + "policy_env must NOT contain BUZZ_ACP_EFFORT_LEVEL when effort_level is None" + ); +} + +/// B5 remote parity: when a canonical effort_level is persisted, a conflicting +/// user-supplied BUZZ_ACP_EFFORT_LEVEL in descriptor.env must NOT shadow it. +/// The canonical value in policy_env (tier 1) must win in the final build_env +/// output — the key must be absent from launch.env (tier 2) so tier 1 is +/// authoritative. +#[test] +fn launch_block_canonical_effort_strips_user_env_collision() { + let mut record = record(); + record.effort_level = Some("high".to_string()); + let descriptor = EffectiveHarnessDescriptor { + command: "claude".into(), + args: vec![], + // User-supplied conflicting value in descriptor.env. + env: BTreeMap::from([("BUZZ_ACP_EFFORT_LEVEL".to_string(), "low".to_string())]), + }; + let launch = build_launch_block( + &record, + &descriptor, + &[], + &[], + None, + LaunchExperimentContext { + effective_model: None, + + shared_instructions_enabled: true, + }, + "owner-hex", + ) + .unwrap(); + + // Canonical must be in policy_env (tier 1). + assert_eq!( + launch["policy_env"]["BUZZ_ACP_EFFORT_LEVEL"], "high", + "canonical effort must be in policy_env when record.effort_level is Some" + ); + // Conflicting user value must be absent from launch.env (tier 2) so it + // cannot shadow the canonical tier-1 value in build_env. + assert!( + launch["env"]["BUZZ_ACP_EFFORT_LEVEL"].is_null(), + "user BUZZ_ACP_EFFORT_LEVEL must be stripped from launch.env when canonical is present" + ); +} + +/// B5 remote parity: when no canonical effort is persisted (effort_level is +/// None), a user-supplied BUZZ_ACP_EFFORT_LEVEL in descriptor.env survives +/// into launch.env — passthrough preserved for startup seeding. +#[test] +fn launch_block_user_effort_env_survives_when_no_canonical_value() { + let record = record(); // effort_level is None + let descriptor = EffectiveHarnessDescriptor { + command: "claude".into(), + args: vec![], + env: BTreeMap::from([("BUZZ_ACP_EFFORT_LEVEL".to_string(), "low".to_string())]), + }; + let launch = build_launch_block( + &record, + &descriptor, + &[], + &[], + None, + LaunchExperimentContext { + effective_model: None, + + shared_instructions_enabled: true, + }, + "owner-hex", + ) + .unwrap(); + + // No canonical — key must NOT appear in policy_env. + assert!( + launch["policy_env"]["BUZZ_ACP_EFFORT_LEVEL"].is_null(), + "policy_env must NOT contain BUZZ_ACP_EFFORT_LEVEL when effort_level is None" + ); + // User value must survive in launch.env so the harness can use it. + assert_eq!( + launch["env"]["BUZZ_ACP_EFFORT_LEVEL"], "low", + "user-supplied effort must survive in launch.env when no canonical value" + ); +} + +/// OpenClaw descriptor: `launch.policy_env["BUZZ_ACP_AGENTS"]` must be "5" +/// even when the record's requested parallelism is 10. This is the direct +/// `launch.policy_env` seam test — the executable contract for remote providers. +#[test] +fn launch_block_openclaw_over_cap_policy_env_is_capped() { + let mut record = record(); + record.agent_command = "openclaw".into(); + record.parallelism = 10; // above the OpenClaw spawn-time cap + let descriptor = EffectiveHarnessDescriptor { + command: "openclaw".into(), + args: vec![], + env: BTreeMap::new(), + }; + + let launch = build_launch_block( + &record, + &descriptor, + &[], + &[], + None, + LaunchExperimentContext { + effective_model: None, + + shared_instructions_enabled: true, + }, + "owner-hex", + ) + .unwrap(); + + assert_eq!( + launch["policy_env"]["BUZZ_ACP_AGENTS"], + crate::managed_agents::parallelism::OPENCLAW_MAX_PARALLELISM.to_string(), + "launch.policy_env[BUZZ_ACP_AGENTS] must be capped at {} for OpenClaw, not 10", + crate::managed_agents::parallelism::OPENCLAW_MAX_PARALLELISM + ); +} + +/// Uncapped harness (goose): `launch.policy_env["BUZZ_ACP_AGENTS"]` passes +/// the requested value through unchanged. +#[test] +fn launch_block_goose_policy_env_is_not_capped() { + let mut record = record(); + record.parallelism = 8; + let descriptor = EffectiveHarnessDescriptor { + command: "goose".into(), + args: vec![], + env: BTreeMap::new(), + }; + + let launch = build_launch_block( + &record, + &descriptor, + &[], + &[], + None, + LaunchExperimentContext { + effective_model: None, + + shared_instructions_enabled: true, + }, + "owner-hex", + ) + .unwrap(); + + assert_eq!( + launch["policy_env"]["BUZZ_ACP_AGENTS"], "8", + "goose: policy_env[BUZZ_ACP_AGENTS] must pass through requested value 8" + ); +} + +/// deploy_payload_json: legacy top-level `parallelism` is the effective value +/// derived from the descriptor, not `record.agent_command`. +/// +/// Stale-persona scenario: `record.agent_command` is "goose" (created before +/// the user switched the persona to OpenClaw), but the live descriptor resolves +/// OpenClaw. Both `launch.policy_env["BUZZ_ACP_AGENTS"]` and the legacy +/// top-level `parallelism` must be the effective OpenClaw value (5), not the +/// record's stale Goose identity (requested 10). +#[test] +fn deploy_payload_json_stale_goose_record_live_openclaw_descriptor_both_capped() { + let mut record = record(); + // Stale agent_command from record creation — persona has since switched to OpenClaw. + record.agent_command = "goose".into(); + record.parallelism = 10; + // Resolved descriptor reflects the live persona (OpenClaw). + let descriptor = EffectiveHarnessDescriptor { + command: "openclaw".into(), + args: vec![], + env: BTreeMap::new(), + }; + let cap = crate::managed_agents::parallelism::OPENCLAW_MAX_PARALLELISM; + + let launch = build_launch_block( + &record, + &descriptor, + &[], + &[], + None, + LaunchExperimentContext { + effective_model: None, + + shared_instructions_enabled: true, + }, + "owner-hex", + ) + .unwrap(); + let effective_parallelism = + crate::managed_agents::effective_parallelism(&descriptor.command, record.parallelism); + let payload = deploy_payload_json( + &record, + "wss://relay.example".to_string(), + DeployProjections { + effective_model: None, + effective_provider: None, + effective_prompt: None, + effective_parallelism, + owner_only_access: false, + }, + BTreeMap::new(), + launch.clone(), + ); + + assert_eq!( + launch["policy_env"]["BUZZ_ACP_AGENTS"], + cap.to_string(), + "launch.policy_env[BUZZ_ACP_AGENTS] must be capped at {cap} for live OpenClaw descriptor" + ); + assert_eq!( + payload["parallelism"], cap, + "legacy top-level parallelism must match launch.policy_env — both must be {cap}" + ); +} + +/// Inverse stale-persona scenario: `record.agent_command` is "openclaw" +/// (created before the user switched the persona to Goose), but the live +/// descriptor resolves Goose. Both projections must be the uncapped requested +/// value (4), not the old OpenClaw cap. +#[test] +fn deploy_payload_json_stale_openclaw_record_live_goose_descriptor_both_uncapped() { + let mut record = record(); + // Stale agent_command from record creation — persona has since switched to Goose. + record.agent_command = "openclaw".into(); + record.parallelism = 4; + // Resolved descriptor reflects the live persona (Goose). + let descriptor = EffectiveHarnessDescriptor { + command: "goose".into(), + args: vec![], + env: BTreeMap::new(), + }; + + let launch = build_launch_block( + &record, + &descriptor, + &[], + &[], + None, + LaunchExperimentContext { + effective_model: None, + + shared_instructions_enabled: true, + }, + "owner-hex", + ) + .unwrap(); + let effective_parallelism = + crate::managed_agents::effective_parallelism(&descriptor.command, record.parallelism); + let payload = deploy_payload_json( + &record, + "wss://relay.example".to_string(), + DeployProjections { + effective_model: None, + effective_provider: None, + effective_prompt: None, + effective_parallelism, + owner_only_access: false, + }, + BTreeMap::new(), + launch.clone(), + ); + + assert_eq!( + launch["policy_env"]["BUZZ_ACP_AGENTS"], + "4", + "launch.policy_env[BUZZ_ACP_AGENTS] must pass through requested 4 for live Goose descriptor" + ); + assert_eq!( + payload["parallelism"], 4, + "legacy top-level parallelism must match launch.policy_env — both must be 4 (uncapped)" + ); +} + +/// Explicit agent_command_override direction: record has an explicit override +/// pinning OpenClaw while the persona default is Goose. The override wins +/// via the descriptor — both projections must be capped at the OpenClaw limit. +#[test] +fn deploy_payload_json_explicit_openclaw_override_both_capped() { + let mut record = record(); + // Explicit override: user pinned OpenClaw on this agent. + record.agent_command_override = Some("openclaw".into()); + record.agent_command = "goose".into(); // persona default, overridden + record.parallelism = 10; + // Descriptor reflects the resolved override (OpenClaw wins). + let descriptor = EffectiveHarnessDescriptor { + command: "openclaw".into(), + args: vec![], + env: BTreeMap::new(), + }; + let cap = crate::managed_agents::parallelism::OPENCLAW_MAX_PARALLELISM; + + let launch = build_launch_block( + &record, + &descriptor, + &[], + &[], + None, + LaunchExperimentContext { + effective_model: None, + + shared_instructions_enabled: true, + }, + "owner-hex", + ) + .unwrap(); + let effective_parallelism = + crate::managed_agents::effective_parallelism(&descriptor.command, record.parallelism); + let payload = deploy_payload_json( + &record, + "wss://relay.example".to_string(), + DeployProjections { + effective_model: None, + effective_provider: None, + effective_prompt: None, + effective_parallelism, + owner_only_access: false, + }, + BTreeMap::new(), + launch.clone(), + ); + + assert_eq!( + launch["policy_env"]["BUZZ_ACP_AGENTS"], + cap.to_string(), + "launch.policy_env[BUZZ_ACP_AGENTS] must be {cap} for explicit OpenClaw override" + ); + assert_eq!( + payload["parallelism"], cap, + "legacy top-level parallelism must match launch.policy_env — both must be {cap}" + ); +} diff --git a/desktop/src-tauri/src/commands/agents_deploy.rs b/desktop/src-tauri/src/commands/agents_deploy.rs index 1eb60a8b32..20f619c25b 100644 --- a/desktop/src-tauri/src/commands/agents_deploy.rs +++ b/desktop/src-tauri/src/commands/agents_deploy.rs @@ -307,729 +307,5 @@ pub(super) fn deploy_payload_json( } #[cfg(test)] -mod tests { - use super::*; - use crate::managed_agents::{readiness::EffectiveHarnessDescriptor, RespondTo, TeamRecord}; - - fn record() -> ManagedAgentRecord { - serde_json::from_value(serde_json::json!({ - "pubkey": "abcd1234", - "name": "agent-handle", - "display_name": "Agent\u{0000} Name", - "private_key_nsec": "nsec1fake", - "relay_url": "wss://relay.example", - "acp_command": "buzz-acp", - "agent_command": "goose", - "agent_args": [], - "mcp_command": "", - "turn_timeout_seconds": 320, - "idle_timeout_seconds": 17, - "max_turn_duration_seconds": 23, - "parallelism": 4, - "respond_to": RespondTo::OwnerOnly, - "respond_to_allowlist": [], - "team_id": "team-1", - "created_at": "2026-01-01T00:00:00Z", - "updated_at": "2026-01-01T00:00:00Z" - })) - .unwrap() - } - - #[test] - fn linked_assignments_resolve_from_live_definition() { - let mut record = record(); - record.persona_id = Some("persona-1".into()); - record.assigned_shared_instructions = vec!["stale-a".into(), "revoked-b".into()]; - let mut persona: AgentDefinition = serde_json::from_value(serde_json::json!({ - "id": "persona-1", - "display_name": "Persona", - "system_prompt": "prompt", - "name_pool": [], - "is_builtin": false, - "is_active": true, - "created_at": "2026-01-01T00:00:00Z", - "updated_at": "2026-01-01T00:00:00Z" - })) - .unwrap(); - persona.assigned_shared_instructions = vec!["current-a".into()]; - let personas = [persona]; - let descriptor = EffectiveHarnessDescriptor { - command: "goose".into(), - args: vec![], - env: BTreeMap::new(), - }; - let launch = build_launch_block( - &record, - &descriptor, - &[], - &personas, - None, - LaunchExperimentContext { - effective_model: None, - - shared_instructions_enabled: true, - }, - "owner-hex", - ) - .unwrap(); - - assert_eq!( - launch["policy_env"]["BUZZ_ACP_ASSIGNED_SHARED_INSTRUCTIONS"], - "current-a" - ); - assert!( - !launch["policy_env"]["BUZZ_ACP_ASSIGNED_SHARED_INSTRUCTIONS"] - .as_str() - .unwrap() - .contains("revoked-b") - ); - } - - #[test] - fn launch_block_omits_shared_instructions_when_experiment_is_disabled() { - let mut record = record(); - record.assigned_shared_instructions = vec![format!("30023:{}:review", "a".repeat(64))]; - let descriptor = EffectiveHarnessDescriptor { - command: "goose".into(), - args: vec![], - env: BTreeMap::from([( - "BUZZ_ACP_ASSIGNED_SHARED_INSTRUCTIONS".into(), - "user-supplied".into(), - )]), - }; - - let launch = build_launch_block( - &record, - &descriptor, - &[], - &[], - None, - LaunchExperimentContext { - effective_model: None, - - shared_instructions_enabled: false, - }, - "owner-hex", - ) - .unwrap(); - - assert!(launch["policy_env"]["BUZZ_ACP_ASSIGNED_SHARED_INSTRUCTIONS"].is_null()); - assert!(launch["env"]["BUZZ_ACP_ASSIGNED_SHARED_INSTRUCTIONS"].is_null()); - } - - #[test] - fn launch_block_preserves_descriptor_and_spawn_policy() { - let mut record = record(); - let coordinate = format!("30023:{}:review", "a".repeat(64)); - record.assigned_shared_instructions = vec![coordinate.clone()]; - let descriptor = EffectiveHarnessDescriptor { - command: "goose".into(), - args: vec!["acp".into()], - env: BTreeMap::from([ - ("GOOSE_MODE".into(), "custom".into()), - ("SECRET_FROM_PERSONA".into(), "secret".into()), - ( - "BUZZ_ACP_ASSIGNED_SHARED_INSTRUCTIONS".into(), - "user-supplied".into(), - ), - ]), - }; - let teams: Vec = serde_json::from_value(serde_json::json!([{ - "id": "team-1", "name": "Team", "instructions": "Coordinate", "persona_ids": [], "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z" - }])).unwrap(); - - let launch = build_launch_block( - &record, - &descriptor, - &teams, - &[], - Some("prompt"), - LaunchExperimentContext { - effective_model: Some("model"), - - shared_instructions_enabled: true, - }, - "owner-hex", - ) - .unwrap(); - - assert_eq!(launch["command"], "goose"); - assert_eq!(launch["args"], serde_json::json!(["acp"])); - assert_eq!(launch["env"]["GOOSE_MODE"], "custom"); - // policy_env is applied first, so this default remains separate from - // the descriptor value that wins in launch.env. - assert_eq!(launch["policy_env"]["GOOSE_MODE"], "auto"); - assert_eq!(launch["policy_env"]["BUZZ_ACP_LAZY_POOL"], "true"); - assert_eq!(launch["policy_env"]["BUZZ_ACP_RELAY_OBSERVER"], "true"); - assert_eq!( - launch["policy_env"]["BUZZ_ACP_TEAM_INSTRUCTIONS"], - "Coordinate" - ); - assert_eq!( - launch["policy_env"]["BUZZ_ACP_ASSIGNED_SHARED_INSTRUCTIONS"], - coordinate - ); - assert!(launch["env"]["BUZZ_ACP_ASSIGNED_SHARED_INSTRUCTIONS"].is_null()); - assert_eq!(launch["policy_env"]["BUZZ_ACP_SESSION_TITLE"], "Agent Name"); - assert_eq!(launch["policy_env"]["BUZZ_ACP_DISPLAY_NAME"], "Agent Name"); - assert_eq!(launch["policy_env"]["BUZZ_ACP_SYSTEM_PROMPT"], "prompt"); - // goose runtime: model goes via BUZZ_ACP_MODEL (non-claude path). - assert_eq!(launch["policy_env"]["BUZZ_ACP_MODEL"], "model"); - assert!( - launch["policy_env"]["ANTHROPIC_MODEL"].is_null(), - "goose must NOT receive ANTHROPIC_MODEL" - ); - assert_eq!(launch["policy_env"]["BUZZ_ACP_IDLE_TIMEOUT"], "17"); - assert_eq!(launch["policy_env"]["BUZZ_ACP_MAX_TURN_DURATION"], "23"); - assert_eq!(launch["policy_env"]["BUZZ_ACP_AGENTS"], "4"); - assert_eq!(launch["owner_pubkey"], "owner-hex"); - } - - #[test] - fn launch_block_claude_runtime_uses_anthropic_model_not_buzz_acp_model() { - // B2: remote claude deploys must send ANTHROPIC_MODEL, not BUZZ_ACP_MODEL, - // so the remote harness has a single startup model authority matching A1. - let record = record(); - let descriptor = EffectiveHarnessDescriptor { - command: "claude".into(), - args: vec![], - env: BTreeMap::new(), - }; - let teams: Vec = vec![]; - let launch = build_launch_block( - &record, - &descriptor, - &teams, - &[], - None, - LaunchExperimentContext { - effective_model: Some("claude-opus-4"), - - shared_instructions_enabled: true, - }, - "owner-hex", - ) - .unwrap(); - assert_eq!( - launch["policy_env"]["ANTHROPIC_MODEL"], "claude-opus-4", - "claude remote must receive ANTHROPIC_MODEL" - ); - assert!( - launch["policy_env"]["BUZZ_ACP_MODEL"].is_null(), - "claude remote must NOT receive BUZZ_ACP_MODEL" - ); - } - - /// F2: remote Claude launch must mirror local A1 — ALWAYS strip BOTH - /// BUZZ_ACP_MODEL and ANTHROPIC_MODEL from launch.env (tier 2), so the - /// canonical model in policy_env (tier 1) is the sole authority. Since - /// launch.env later-wins over policy_env, a user BUZZ_ACP_MODEL would add a - /// second startup authority and a user ANTHROPIC_MODEL would silently - /// override the canonical model. - #[test] - fn launch_block_claude_strips_both_model_keys_from_launch_env() { - let record = record(); - let descriptor = EffectiveHarnessDescriptor { - command: "claude".into(), - args: vec![], - env: BTreeMap::from([ - ("BUZZ_ACP_MODEL".to_string(), "user-sonnet".to_string()), - ("ANTHROPIC_MODEL".to_string(), "user-opus".to_string()), - ("KEEP_ME".to_string(), "yes".to_string()), - ]), - }; - let launch = build_launch_block( - &record, - &descriptor, - &[], - &[], - None, - LaunchExperimentContext { - effective_model: Some("claude-opus-4"), - - shared_instructions_enabled: true, - }, - "owner-hex", - ) - .unwrap(); - - // Canonical model rides policy_env alone. - assert_eq!(launch["policy_env"]["ANTHROPIC_MODEL"], "claude-opus-4"); - assert!(launch["policy_env"]["BUZZ_ACP_MODEL"].is_null()); - // Both model keys are stripped from launch.env — neither can later-win. - assert!( - launch["env"]["BUZZ_ACP_MODEL"].is_null(), - "user BUZZ_ACP_MODEL must be stripped from launch.env for claude" - ); - assert!( - launch["env"]["ANTHROPIC_MODEL"].is_null(), - "user ANTHROPIC_MODEL must be stripped from launch.env for claude" - ); - // Unrelated user env survives. - assert_eq!(launch["env"]["KEEP_ME"], "yes"); - } - - /// F2: when no canonical model resolves, a Claude launch still strips both - /// model keys from launch.env, so neither authority reaches the remote - /// process — matching local `apply_claude_model_env(None)`, which removes - /// both. - #[test] - fn launch_block_claude_strips_model_keys_even_without_canonical() { - let record = record(); - let descriptor = EffectiveHarnessDescriptor { - command: "claude".into(), - args: vec![], - env: BTreeMap::from([ - ("BUZZ_ACP_MODEL".to_string(), "user-sonnet".to_string()), - ("ANTHROPIC_MODEL".to_string(), "user-opus".to_string()), - ]), - }; - let launch = build_launch_block( - &record, - &descriptor, - &[], - &[], - None, - LaunchExperimentContext { - effective_model: None, - - shared_instructions_enabled: true, - }, - "owner-hex", - ) - .unwrap(); - - assert!(launch["policy_env"]["ANTHROPIC_MODEL"].is_null()); - assert!(launch["policy_env"]["BUZZ_ACP_MODEL"].is_null()); - assert!( - launch["env"]["BUZZ_ACP_MODEL"].is_null(), - "user BUZZ_ACP_MODEL must be stripped even without a canonical model" - ); - assert!( - launch["env"]["ANTHROPIC_MODEL"].is_null(), - "user ANTHROPIC_MODEL must be stripped even without a canonical model" - ); - } - - /// F2: non-Claude runtimes must NOT strip model keys from launch.env — the - /// model authority stripping is Claude-specific (BUZZ_ACP_MODEL is the - /// spawn authority for other runtimes and rides policy_env there). - #[test] - fn launch_block_non_claude_preserves_user_model_env() { - let record = record(); // goose command - let descriptor = EffectiveHarnessDescriptor { - command: "goose".into(), - args: vec![], - env: BTreeMap::from([("BUZZ_ACP_MODEL".to_string(), "user-model".to_string())]), - }; - let launch = build_launch_block( - &record, - &descriptor, - &[], - &[], - None, - LaunchExperimentContext { - effective_model: Some("model"), - - shared_instructions_enabled: true, - }, - "owner-hex", - ) - .unwrap(); - - // goose puts canonical in policy_env, and the user launch.env value is - // preserved (later-wins is the intended goose behavior). - assert_eq!(launch["policy_env"]["BUZZ_ACP_MODEL"], "model"); - assert_eq!(launch["env"]["BUZZ_ACP_MODEL"], "user-model"); - } - - #[test] - fn launch_block_claude_runtime_injects_effort_level_when_set() { - // I-4: remote parity — record.effort_level → BUZZ_ACP_EFFORT_LEVEL in policy_env. - let mut record = record(); - record.effort_level = Some("high".to_string()); - let descriptor = EffectiveHarnessDescriptor { - command: "claude".into(), - args: vec![], - env: BTreeMap::new(), - }; - let launch = build_launch_block( - &record, - &descriptor, - &[], - &[], - None, - LaunchExperimentContext { - effective_model: None, - - shared_instructions_enabled: true, - }, - "owner-hex", - ) - .unwrap(); - assert_eq!( - launch["policy_env"]["BUZZ_ACP_EFFORT_LEVEL"], "high", - "claude remote must receive BUZZ_ACP_EFFORT_LEVEL when effort_level is set" - ); - } - - #[test] - fn launch_block_does_not_inject_effort_level_when_absent() { - // I-4: no BUZZ_ACP_EFFORT_LEVEL in policy_env when record.effort_level is None. - let record = record(); // effort_level is None by default - let descriptor = EffectiveHarnessDescriptor { - command: "claude".into(), - args: vec![], - env: BTreeMap::new(), - }; - let launch = build_launch_block( - &record, - &descriptor, - &[], - &[], - None, - LaunchExperimentContext { - effective_model: None, - - shared_instructions_enabled: true, - }, - "owner-hex", - ) - .unwrap(); - assert!( - launch["policy_env"]["BUZZ_ACP_EFFORT_LEVEL"].is_null(), - "policy_env must NOT contain BUZZ_ACP_EFFORT_LEVEL when effort_level is None" - ); - } - - /// B5 remote parity: when a canonical effort_level is persisted, a conflicting - /// user-supplied BUZZ_ACP_EFFORT_LEVEL in descriptor.env must NOT shadow it. - /// The canonical value in policy_env (tier 1) must win in the final build_env - /// output — the key must be absent from launch.env (tier 2) so tier 1 is - /// authoritative. - #[test] - fn launch_block_canonical_effort_strips_user_env_collision() { - let mut record = record(); - record.effort_level = Some("high".to_string()); - let descriptor = EffectiveHarnessDescriptor { - command: "claude".into(), - args: vec![], - // User-supplied conflicting value in descriptor.env. - env: BTreeMap::from([("BUZZ_ACP_EFFORT_LEVEL".to_string(), "low".to_string())]), - }; - let launch = build_launch_block( - &record, - &descriptor, - &[], - &[], - None, - LaunchExperimentContext { - effective_model: None, - - shared_instructions_enabled: true, - }, - "owner-hex", - ) - .unwrap(); - - // Canonical must be in policy_env (tier 1). - assert_eq!( - launch["policy_env"]["BUZZ_ACP_EFFORT_LEVEL"], "high", - "canonical effort must be in policy_env when record.effort_level is Some" - ); - // Conflicting user value must be absent from launch.env (tier 2) so it - // cannot shadow the canonical tier-1 value in build_env. - assert!( - launch["env"]["BUZZ_ACP_EFFORT_LEVEL"].is_null(), - "user BUZZ_ACP_EFFORT_LEVEL must be stripped from launch.env when canonical is present" - ); - } - - /// B5 remote parity: when no canonical effort is persisted (effort_level is - /// None), a user-supplied BUZZ_ACP_EFFORT_LEVEL in descriptor.env survives - /// into launch.env — passthrough preserved for startup seeding. - #[test] - fn launch_block_user_effort_env_survives_when_no_canonical_value() { - let record = record(); // effort_level is None - let descriptor = EffectiveHarnessDescriptor { - command: "claude".into(), - args: vec![], - env: BTreeMap::from([("BUZZ_ACP_EFFORT_LEVEL".to_string(), "low".to_string())]), - }; - let launch = build_launch_block( - &record, - &descriptor, - &[], - &[], - None, - LaunchExperimentContext { - effective_model: None, - - shared_instructions_enabled: true, - }, - "owner-hex", - ) - .unwrap(); - - // No canonical — key must NOT appear in policy_env. - assert!( - launch["policy_env"]["BUZZ_ACP_EFFORT_LEVEL"].is_null(), - "policy_env must NOT contain BUZZ_ACP_EFFORT_LEVEL when effort_level is None" - ); - // User value must survive in launch.env so the harness can use it. - assert_eq!( - launch["env"]["BUZZ_ACP_EFFORT_LEVEL"], "low", - "user-supplied effort must survive in launch.env when no canonical value" - ); - } - - /// OpenClaw descriptor: `launch.policy_env["BUZZ_ACP_AGENTS"]` must be "5" - /// even when the record's requested parallelism is 10. This is the direct - /// `launch.policy_env` seam test — the executable contract for remote providers. - #[test] - fn launch_block_openclaw_over_cap_policy_env_is_capped() { - let mut record = record(); - record.agent_command = "openclaw".into(); - record.parallelism = 10; // above the OpenClaw spawn-time cap - let descriptor = EffectiveHarnessDescriptor { - command: "openclaw".into(), - args: vec![], - env: BTreeMap::new(), - }; - - let launch = build_launch_block( - &record, - &descriptor, - &[], - &[], - None, - LaunchExperimentContext { - effective_model: None, - - shared_instructions_enabled: true, - }, - "owner-hex", - ) - .unwrap(); - - assert_eq!( - launch["policy_env"]["BUZZ_ACP_AGENTS"], - crate::managed_agents::parallelism::OPENCLAW_MAX_PARALLELISM.to_string(), - "launch.policy_env[BUZZ_ACP_AGENTS] must be capped at {} for OpenClaw, not 10", - crate::managed_agents::parallelism::OPENCLAW_MAX_PARALLELISM - ); - } - - /// Uncapped harness (goose): `launch.policy_env["BUZZ_ACP_AGENTS"]` passes - /// the requested value through unchanged. - #[test] - fn launch_block_goose_policy_env_is_not_capped() { - let mut record = record(); - record.parallelism = 8; - let descriptor = EffectiveHarnessDescriptor { - command: "goose".into(), - args: vec![], - env: BTreeMap::new(), - }; - - let launch = build_launch_block( - &record, - &descriptor, - &[], - &[], - None, - LaunchExperimentContext { - effective_model: None, - - shared_instructions_enabled: true, - }, - "owner-hex", - ) - .unwrap(); - - assert_eq!( - launch["policy_env"]["BUZZ_ACP_AGENTS"], "8", - "goose: policy_env[BUZZ_ACP_AGENTS] must pass through requested value 8" - ); - } - - /// deploy_payload_json: legacy top-level `parallelism` is the effective value - /// derived from the descriptor, not `record.agent_command`. - /// - /// Stale-persona scenario: `record.agent_command` is "goose" (created before - /// the user switched the persona to OpenClaw), but the live descriptor resolves - /// OpenClaw. Both `launch.policy_env["BUZZ_ACP_AGENTS"]` and the legacy - /// top-level `parallelism` must be the effective OpenClaw value (5), not the - /// record's stale Goose identity (requested 10). - #[test] - fn deploy_payload_json_stale_goose_record_live_openclaw_descriptor_both_capped() { - let mut record = record(); - // Stale agent_command from record creation — persona has since switched to OpenClaw. - record.agent_command = "goose".into(); - record.parallelism = 10; - // Resolved descriptor reflects the live persona (OpenClaw). - let descriptor = EffectiveHarnessDescriptor { - command: "openclaw".into(), - args: vec![], - env: BTreeMap::new(), - }; - let cap = crate::managed_agents::parallelism::OPENCLAW_MAX_PARALLELISM; - - let launch = build_launch_block( - &record, - &descriptor, - &[], - &[], - None, - LaunchExperimentContext { - effective_model: None, - - shared_instructions_enabled: true, - }, - "owner-hex", - ) - .unwrap(); - let effective_parallelism = - crate::managed_agents::effective_parallelism(&descriptor.command, record.parallelism); - let payload = deploy_payload_json( - &record, - "wss://relay.example".to_string(), - DeployProjections { - effective_model: None, - effective_provider: None, - effective_prompt: None, - effective_parallelism, - owner_only_access: false, - }, - BTreeMap::new(), - launch.clone(), - ); - - assert_eq!( - launch["policy_env"]["BUZZ_ACP_AGENTS"], - cap.to_string(), - "launch.policy_env[BUZZ_ACP_AGENTS] must be capped at {cap} for live OpenClaw descriptor" - ); - assert_eq!( - payload["parallelism"], cap, - "legacy top-level parallelism must match launch.policy_env — both must be {cap}" - ); - } - - /// Inverse stale-persona scenario: `record.agent_command` is "openclaw" - /// (created before the user switched the persona to Goose), but the live - /// descriptor resolves Goose. Both projections must be the uncapped requested - /// value (4), not the old OpenClaw cap. - #[test] - fn deploy_payload_json_stale_openclaw_record_live_goose_descriptor_both_uncapped() { - let mut record = record(); - // Stale agent_command from record creation — persona has since switched to Goose. - record.agent_command = "openclaw".into(); - record.parallelism = 4; - // Resolved descriptor reflects the live persona (Goose). - let descriptor = EffectiveHarnessDescriptor { - command: "goose".into(), - args: vec![], - env: BTreeMap::new(), - }; - - let launch = build_launch_block( - &record, - &descriptor, - &[], - &[], - None, - LaunchExperimentContext { - effective_model: None, - - shared_instructions_enabled: true, - }, - "owner-hex", - ) - .unwrap(); - let effective_parallelism = - crate::managed_agents::effective_parallelism(&descriptor.command, record.parallelism); - let payload = deploy_payload_json( - &record, - "wss://relay.example".to_string(), - DeployProjections { - effective_model: None, - effective_provider: None, - effective_prompt: None, - effective_parallelism, - owner_only_access: false, - }, - BTreeMap::new(), - launch.clone(), - ); - - assert_eq!( - launch["policy_env"]["BUZZ_ACP_AGENTS"], - "4", - "launch.policy_env[BUZZ_ACP_AGENTS] must pass through requested 4 for live Goose descriptor" - ); - assert_eq!( - payload["parallelism"], 4, - "legacy top-level parallelism must match launch.policy_env — both must be 4 (uncapped)" - ); - } - - /// Explicit agent_command_override direction: record has an explicit override - /// pinning OpenClaw while the persona default is Goose. The override wins - /// via the descriptor — both projections must be capped at the OpenClaw limit. - #[test] - fn deploy_payload_json_explicit_openclaw_override_both_capped() { - let mut record = record(); - // Explicit override: user pinned OpenClaw on this agent. - record.agent_command_override = Some("openclaw".into()); - record.agent_command = "goose".into(); // persona default, overridden - record.parallelism = 10; - // Descriptor reflects the resolved override (OpenClaw wins). - let descriptor = EffectiveHarnessDescriptor { - command: "openclaw".into(), - args: vec![], - env: BTreeMap::new(), - }; - let cap = crate::managed_agents::parallelism::OPENCLAW_MAX_PARALLELISM; - - let launch = build_launch_block( - &record, - &descriptor, - &[], - &[], - None, - LaunchExperimentContext { - effective_model: None, - - shared_instructions_enabled: true, - }, - "owner-hex", - ) - .unwrap(); - let effective_parallelism = - crate::managed_agents::effective_parallelism(&descriptor.command, record.parallelism); - let payload = deploy_payload_json( - &record, - "wss://relay.example".to_string(), - DeployProjections { - effective_model: None, - effective_provider: None, - effective_prompt: None, - effective_parallelism, - owner_only_access: false, - }, - BTreeMap::new(), - launch.clone(), - ); - - assert_eq!( - launch["policy_env"]["BUZZ_ACP_AGENTS"], - cap.to_string(), - "launch.policy_env[BUZZ_ACP_AGENTS] must be {cap} for explicit OpenClaw override" - ); - assert_eq!( - payload["parallelism"], cap, - "legacy top-level parallelism must match launch.policy_env — both must be {cap}" - ); - } -} +#[path = "agents/agents_deploy_tests.rs"] +mod tests; diff --git a/desktop/src-tauri/src/managed_agents/runtime.rs b/desktop/src-tauri/src/managed_agents/runtime.rs index 2713b9f8ad..f695c9482a 100644 --- a/desktop/src-tauri/src/managed_agents/runtime.rs +++ b/desktop/src-tauri/src/managed_agents/runtime.rs @@ -1,9 +1,6 @@ -use std::collections::HashMap; - -use tauri::{AppHandle, Manager}; - use super::agent_env::{build_buzz_agent_provider_defaults, idle_pool_sleep_env}; - +use super::claude_config::{apply_claude_model_env, apply_effort_env}; +use super::shared_instructions::resolve_and_apply_assigned_shared_instructions_env; use crate::{ managed_agents::{ append_log_marker, known_acp_runtime, login_shell_path, managed_agent_log_path, @@ -13,28 +10,22 @@ use crate::{ }, util::now_iso, }; - -use super::claude_config::{apply_claude_model_env, apply_effort_env}; -use super::shared_instructions::resolve_and_apply_assigned_shared_instructions_env; +use std::collections::HashMap; +use tauri::{AppHandle, Manager}; mod path; +pub(crate) use super::access_policy::{build_respond_to_env_with_policy, RespondToEnv}; pub(in crate::managed_agents) use path::build_augmented_path; pub(crate) use path::{compose_path_entries, should_skip_claude_executable, should_use_inherited}; - -pub(crate) use super::access_policy::{build_respond_to_env_with_policy, RespondToEnv}; - mod metadata; pub(crate) use metadata::{ apply_agent_display_env, resolve_session_title, runtime_metadata_env_vars, DISPLAY_NAME_ENV_VAR, SESSION_TITLE_ENV_VAR, }; - mod stop; pub(crate) use stop::managed_agent_runtime_keys; pub use stop::{stop_managed_agent_process, stop_managed_agent_workspace_pair}; - mod sweep; pub(crate) use sweep::sweep_untracked_bundle_harnesses; - mod process; #[cfg(test)] use process::{ @@ -45,7 +36,6 @@ pub(crate) use process::{ current_instance_id, process_belongs_to_us, process_has_buzz_marker, process_is_running, terminate_process, terminate_untracked_pair_runtime, valid_agent_runtime_receipt, }; - mod orphan_sweep; #[cfg(target_os = "macos")] use orphan_sweep::proc_pidinfo; @@ -57,7 +47,6 @@ pub(crate) use orphan_sweep::{ use orphan_sweep::{BSDInfo, PROC_PIDTBSDINFO}; #[cfg(unix)] use process::resolve_pgids_and_kill; - mod instance_reaper; pub(crate) use instance_reaper::reap_dead_instance_agents; #[cfg(test)] diff --git a/desktop/src-tauri/src/managed_agents/runtime/tests.rs b/desktop/src-tauri/src/managed_agents/runtime/tests.rs index bd3f554980..fa3d1b2597 100644 --- a/desktop/src-tauri/src/managed_agents/runtime/tests.rs +++ b/desktop/src-tauri/src/managed_agents/runtime/tests.rs @@ -1,5 +1,4 @@ use crate::managed_agents::known_acp_runtime; - #[path = "cli_tests.rs"] mod cli_tests; diff --git a/desktop/src-tauri/src/managed_agents/shared_instructions.rs b/desktop/src-tauri/src/managed_agents/shared_instructions.rs index beb520bb9d..03e4b127ac 100644 --- a/desktop/src-tauri/src/managed_agents/shared_instructions.rs +++ b/desktop/src-tauri/src/managed_agents/shared_instructions.rs @@ -697,316 +697,4 @@ fn exact_single_tag_value<'a>(event: &'a Event, name: &str) -> Option<&'a str> { } #[cfg(test)] -mod tests { - use super::*; - use nostr::{EventBuilder, Tag, Timestamp}; - - fn note(keys: &Keys, slug: &str, content: &str, created_at: u64) -> Event { - EventBuilder::new(Kind::Custom(SHARED_INSTRUCTION_KIND), content) - .tags([ - Tag::parse(["d", slug]).unwrap(), - Tag::parse(["title", &format!("Title {slug}")]).unwrap(), - Tag::parse(["summary", &format!("Summary {slug}")]).unwrap(), - ]) - .custom_created_at(Timestamp::from(created_at)) - .sign_with_keys(keys) - .unwrap() - } - - #[test] - fn assigned_shared_instruction_env_is_canonical() { - let coordinate = format!("30023:{}:review", "a".repeat(64)); - let mut command = std::process::Command::new("buzz-acp"); - command.env(ASSIGNED_SHARED_INSTRUCTIONS_ENV, "user-supplied"); - apply_assigned_shared_instructions_env(&mut command, std::slice::from_ref(&coordinate)); - let env = command - .get_envs() - .find(|(key, _)| *key == ASSIGNED_SHARED_INSTRUCTIONS_ENV) - .and_then(|(_, value)| value) - .and_then(|value| value.to_str()); - assert_eq!(env, Some(coordinate.as_str())); - } - - #[test] - fn effective_assigned_shared_instruction_env_uses_live_definition() { - let stale = format!("30023:{}:revoked", "a".repeat(64)); - let current = format!("30023:{}:kept", "b".repeat(64)); - let record: super::super::ManagedAgentRecord = serde_json::from_value(serde_json::json!({ - "pubkey": "agent", - "name": "agent", - "persona_id": "persona", - "private_key_nsec": "nsec1fake", - "relay_url": "wss://relay.example", - "acp_command": "buzz-acp", - "agent_command": "goose", - "agent_args": [], - "mcp_command": "", - "turn_timeout_seconds": 300, - "parallelism": 1, - "assigned_shared_instructions": [stale.clone()], - "created_at": "now", - "updated_at": "now" - })) - .unwrap(); - let definition: super::super::AgentDefinition = serde_json::from_value(serde_json::json!({ - "id": "persona", - "display_name": "Persona", - "system_prompt": "prompt", - "name_pool": [], - "is_builtin": false, - "is_active": true, - "assigned_shared_instructions": [current.clone()], - "created_at": "now", - "updated_at": "now" - })) - .unwrap(); - let mut command = std::process::Command::new("buzz-acp"); - - let definitions = [definition]; - let assigned = resolve_and_apply_assigned_shared_instructions_env( - &mut command, - &record, - &definitions, - true, - ) - .unwrap(); - assert_eq!(assigned, definitions[0].assigned_shared_instructions); - - let env = command - .get_envs() - .find(|(key, _)| *key == ASSIGNED_SHARED_INSTRUCTIONS_ENV) - .and_then(|(_, value)| value) - .and_then(|value| value.to_str()); - assert_eq!(env, Some(current.as_str())); - } - - #[test] - fn disabled_shared_instructions_remove_env_and_effective_assignment() { - let coordinate = format!("30023:{}:kept", "a".repeat(64)); - let record: super::super::ManagedAgentRecord = serde_json::from_value(serde_json::json!({ - "pubkey": "agent", - "name": "agent", - "private_key_nsec": "nsec1fake", - "relay_url": "wss://relay.example", - "acp_command": "buzz-acp", - "agent_command": "goose", - "agent_args": [], - "mcp_command": "", - "turn_timeout_seconds": 300, - "parallelism": 1, - "assigned_shared_instructions": [coordinate], - "created_at": "now", - "updated_at": "now" - })) - .unwrap(); - let mut command = std::process::Command::new("buzz-acp"); - command.env(ASSIGNED_SHARED_INSTRUCTIONS_ENV, "user-supplied"); - - let assigned = - resolve_and_apply_assigned_shared_instructions_env(&mut command, &record, &[], false) - .unwrap(); - - assert!(assigned.is_empty()); - assert!(command - .get_envs() - .find(|(key, _)| *key == ASSIGNED_SHARED_INSTRUCTIONS_ENV) - .is_some_and(|(_, value)| value.is_none())); - } - - #[test] - fn accepts_exact_coordinate_and_deduplicates() { - let key = "a".repeat(64); - let coordinate = format!("30023:{key}:design-engineering"); - assert_eq!( - validate_assigned_shared_instructions(vec![coordinate.clone(), coordinate.clone()]) - .unwrap(), - vec![coordinate] - ); - } - - #[test] - fn rejects_noncanonical_coordinates() { - let key = "a".repeat(64); - for invalid in [ - "design-engineering".to_string(), - format!("30175:{key}:design-engineering"), - format!("30023:{key}:design\nengineering"), - format!("30023:{key}:Design-Engineering"), - format!("30023:{key}:design/engineering"), - format!("30023:{}:design-engineering", "A".repeat(64)), - "30023:npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqp3d0u5:design-engineering".to_string(), - ] { - assert!( - parse_shared_instruction_coordinate(&invalid).is_err(), - "{invalid:?}" - ); - } - } - - #[test] - fn projects_mine_only_latest_heads_with_explicit_compatibility() { - let owner = Keys::generate(); - let stranger = Keys::generate(); - let old = note(&owner, "design-engineering", "old body", 10); - let current = note(&owner, "design-engineering", "current body", 20); - let invalid_name = note(&owner, "Design_skill", "body", 30); - let missing_summary = EventBuilder::new(Kind::Custom(SHARED_INSTRUCTION_KIND), "body") - .tags([ - Tag::parse(["d", "missing-summary"]).unwrap(), - Tag::parse(["title", "Missing summary"]).unwrap(), - ]) - .custom_created_at(Timestamp::from(40)) - .sign_with_keys(&owner) - .unwrap(); - let foreign = note(&stranger, "foreign", "body", 50); - - let covers = shared_instruction_covers_from_events( - &owner.public_key().to_hex(), - vec![old, current, invalid_name, missing_summary, foreign], - ); - - assert_eq!(covers.len(), 3); - assert_eq!(covers[0].slug, "missing-summary"); - assert!(!covers[0].compatible); - assert_eq!( - covers[0].incompatibilities[0].code, - SharedInstructionIncompatibilityCode::MissingDescription - ); - assert_eq!(covers[1].slug, "Design_skill"); - assert!(!covers[1].compatible); - assert_eq!( - covers[1].incompatibilities[0].code, - SharedInstructionIncompatibilityCode::InvalidName - ); - assert_eq!(covers[2].slug, "design-engineering"); - assert!(covers[2].compatible); - assert!(covers[2].incompatibilities.is_empty()); - assert_eq!(covers[2].updated_at, 20); - } - - #[test] - fn validation_rejects_instruction_name_edge_cases_and_empty_body() { - for slug in ["", "-skill", "skill-", "two--hyphens", "with_underscore"] { - let reasons = - shared_instruction_incompatibilities(slug, Some("Useful summary"), "body"); - assert!(reasons - .iter() - .any(|reason| reason.code == SharedInstructionIncompatibilityCode::InvalidName)); - } - let reasons = shared_instruction_incompatibilities("valid-name", Some("summary"), "\n"); - assert_eq!( - reasons[0].code, - SharedInstructionIncompatibilityCode::EmptyBody - ); - } - - #[test] - fn exact_query_filters_do_not_admit_author_slug_cross_products() { - let alpha = Keys::generate(); - let beta = Keys::generate(); - let coordinates = [ - format!("30023:{}:alpha", alpha.public_key().to_hex()), - format!("30023:{}:beta", beta.public_key().to_hex()), - ] - .iter() - .map(|value| parse_shared_instruction_coordinate(value).unwrap()) - .collect::>(); - - let filters = exact_coordinate_filters(&coordinates); - - assert_eq!(filters.len(), 2); - assert_eq!( - filters[0]["authors"], - serde_json::json!([alpha.public_key().to_hex()]) - ); - assert_eq!(filters[0]["#d"], serde_json::json!(["alpha"])); - assert_eq!(filters[0]["limit"], serde_json::json!(1)); - assert_eq!( - filters[1]["authors"], - serde_json::json!([beta.public_key().to_hex()]) - ); - assert_eq!(filters[1]["#d"], serde_json::json!(["beta"])); - } - - #[test] - fn resolves_only_exact_requested_signer_and_slug() { - let alpha = Keys::generate(); - let beta = Keys::generate(); - let coordinate = format!("30023:{}:design-engineering", alpha.public_key().to_hex()); - let wrong_author_same_slug = note(&beta, "design-engineering", "wrong author", 20); - let right_author_wrong_slug = note(&alpha, "other", "wrong slug", 30); - let expected = note(&alpha, "design-engineering", "trusted body", 10); - - let resolved = resolved_heads_from_events( - std::slice::from_ref(&coordinate), - vec![ - wrong_author_same_slug, - right_author_wrong_slug, - expected.clone(), - ], - ); - - assert_eq!(resolved.len(), 1); - assert_eq!(resolved[0].coordinate, coordinate); - assert_eq!(resolved[0].publisher, alpha.public_key().to_hex()); - assert_eq!(resolved[0].slug, "design-engineering"); - assert_eq!(resolved[0].content, "trusted body"); - assert_eq!( - resolved[0].summary.as_deref(), - Some("Summary design-engineering") - ); - assert_eq!(resolved[0].event_id, expected.id.to_hex()); - } - - #[test] - fn selects_newest_verified_head_and_preserves_assignment_order() { - let alpha = Keys::generate(); - let beta = Keys::generate(); - let alpha_coordinate = format!("30023:{}:alpha", alpha.public_key().to_hex()); - let beta_coordinate = format!("30023:{}:beta", beta.public_key().to_hex()); - let old_alpha = note(&alpha, "alpha", "old", 10); - let new_alpha = note(&alpha, "alpha", "new", 20); - let beta_note = note(&beta, "beta", "beta", 15); - - let resolved = resolved_heads_from_events( - &[beta_coordinate.clone(), alpha_coordinate.clone()], - vec![old_alpha, new_alpha, beta_note], - ); - - assert_eq!( - resolved - .iter() - .map(|skill| skill.coordinate.as_str()) - .collect::>(), - vec![beta_coordinate.as_str(), alpha_coordinate.as_str()] - ); - assert_eq!(resolved[1].content, "new"); - } - - #[test] - fn rejects_tampered_oversize_and_ambiguous_coordinate_events() { - let keys = Keys::generate(); - let coordinate = format!("30023:{}:skill", keys.public_key().to_hex()); - let mut tampered = note(&keys, "skill", "original", 10); - tampered.content = "tampered".to_string(); - let oversize = note( - &keys, - "skill", - &"x".repeat(MAX_SHARED_INSTRUCTION_BODY_BYTES + 1), - 20, - ); - let ambiguous = EventBuilder::new(Kind::Custom(SHARED_INSTRUCTION_KIND), "ambiguous") - .tags([ - Tag::parse(["d", "skill"]).unwrap(), - Tag::parse(["d", "skill"]).unwrap(), - ]) - .custom_created_at(Timestamp::from(30)) - .sign_with_keys(&keys) - .unwrap(); - - assert!( - resolved_heads_from_events(&[coordinate], vec![tampered, oversize, ambiguous]) - .is_empty() - ); - } -} +mod tests; diff --git a/desktop/src-tauri/src/managed_agents/shared_instructions/tests.rs b/desktop/src-tauri/src/managed_agents/shared_instructions/tests.rs new file mode 100644 index 0000000000..13cb9526b3 --- /dev/null +++ b/desktop/src-tauri/src/managed_agents/shared_instructions/tests.rs @@ -0,0 +1,310 @@ +use super::*; +use nostr::{EventBuilder, Tag, Timestamp}; + +fn note(keys: &Keys, slug: &str, content: &str, created_at: u64) -> Event { + EventBuilder::new(Kind::Custom(SHARED_INSTRUCTION_KIND), content) + .tags([ + Tag::parse(["d", slug]).unwrap(), + Tag::parse(["title", &format!("Title {slug}")]).unwrap(), + Tag::parse(["summary", &format!("Summary {slug}")]).unwrap(), + ]) + .custom_created_at(Timestamp::from(created_at)) + .sign_with_keys(keys) + .unwrap() +} + +#[test] +fn assigned_shared_instruction_env_is_canonical() { + let coordinate = format!("30023:{}:review", "a".repeat(64)); + let mut command = std::process::Command::new("buzz-acp"); + command.env(ASSIGNED_SHARED_INSTRUCTIONS_ENV, "user-supplied"); + apply_assigned_shared_instructions_env(&mut command, std::slice::from_ref(&coordinate)); + let env = command + .get_envs() + .find(|(key, _)| *key == ASSIGNED_SHARED_INSTRUCTIONS_ENV) + .and_then(|(_, value)| value) + .and_then(|value| value.to_str()); + assert_eq!(env, Some(coordinate.as_str())); +} + +#[test] +fn effective_assigned_shared_instruction_env_uses_live_definition() { + let stale = format!("30023:{}:revoked", "a".repeat(64)); + let current = format!("30023:{}:kept", "b".repeat(64)); + let record: super::super::ManagedAgentRecord = serde_json::from_value(serde_json::json!({ + "pubkey": "agent", + "name": "agent", + "persona_id": "persona", + "private_key_nsec": "nsec1fake", + "relay_url": "wss://relay.example", + "acp_command": "buzz-acp", + "agent_command": "goose", + "agent_args": [], + "mcp_command": "", + "turn_timeout_seconds": 300, + "parallelism": 1, + "assigned_shared_instructions": [stale.clone()], + "created_at": "now", + "updated_at": "now" + })) + .unwrap(); + let definition: super::super::AgentDefinition = serde_json::from_value(serde_json::json!({ + "id": "persona", + "display_name": "Persona", + "system_prompt": "prompt", + "name_pool": [], + "is_builtin": false, + "is_active": true, + "assigned_shared_instructions": [current.clone()], + "created_at": "now", + "updated_at": "now" + })) + .unwrap(); + let mut command = std::process::Command::new("buzz-acp"); + + let definitions = [definition]; + let assigned = resolve_and_apply_assigned_shared_instructions_env( + &mut command, + &record, + &definitions, + true, + ) + .unwrap(); + assert_eq!(assigned, definitions[0].assigned_shared_instructions); + + let env = command + .get_envs() + .find(|(key, _)| *key == ASSIGNED_SHARED_INSTRUCTIONS_ENV) + .and_then(|(_, value)| value) + .and_then(|value| value.to_str()); + assert_eq!(env, Some(current.as_str())); +} + +#[test] +fn disabled_shared_instructions_remove_env_and_effective_assignment() { + let coordinate = format!("30023:{}:kept", "a".repeat(64)); + let record: super::super::ManagedAgentRecord = serde_json::from_value(serde_json::json!({ + "pubkey": "agent", + "name": "agent", + "private_key_nsec": "nsec1fake", + "relay_url": "wss://relay.example", + "acp_command": "buzz-acp", + "agent_command": "goose", + "agent_args": [], + "mcp_command": "", + "turn_timeout_seconds": 300, + "parallelism": 1, + "assigned_shared_instructions": [coordinate], + "created_at": "now", + "updated_at": "now" + })) + .unwrap(); + let mut command = std::process::Command::new("buzz-acp"); + command.env(ASSIGNED_SHARED_INSTRUCTIONS_ENV, "user-supplied"); + + let assigned = + resolve_and_apply_assigned_shared_instructions_env(&mut command, &record, &[], false) + .unwrap(); + + assert!(assigned.is_empty()); + assert!(command + .get_envs() + .find(|(key, _)| *key == ASSIGNED_SHARED_INSTRUCTIONS_ENV) + .is_some_and(|(_, value)| value.is_none())); +} + +#[test] +fn accepts_exact_coordinate_and_deduplicates() { + let key = "a".repeat(64); + let coordinate = format!("30023:{key}:design-engineering"); + assert_eq!( + validate_assigned_shared_instructions(vec![coordinate.clone(), coordinate.clone()]) + .unwrap(), + vec![coordinate] + ); +} + +#[test] +fn rejects_noncanonical_coordinates() { + let key = "a".repeat(64); + for invalid in [ + "design-engineering".to_string(), + format!("30175:{key}:design-engineering"), + format!("30023:{key}:design\nengineering"), + format!("30023:{key}:Design-Engineering"), + format!("30023:{key}:design/engineering"), + format!("30023:{}:design-engineering", "A".repeat(64)), + "30023:npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqp3d0u5:design-engineering" + .to_string(), + ] { + assert!( + parse_shared_instruction_coordinate(&invalid).is_err(), + "{invalid:?}" + ); + } +} + +#[test] +fn projects_mine_only_latest_heads_with_explicit_compatibility() { + let owner = Keys::generate(); + let stranger = Keys::generate(); + let old = note(&owner, "design-engineering", "old body", 10); + let current = note(&owner, "design-engineering", "current body", 20); + let invalid_name = note(&owner, "Design_skill", "body", 30); + let missing_summary = EventBuilder::new(Kind::Custom(SHARED_INSTRUCTION_KIND), "body") + .tags([ + Tag::parse(["d", "missing-summary"]).unwrap(), + Tag::parse(["title", "Missing summary"]).unwrap(), + ]) + .custom_created_at(Timestamp::from(40)) + .sign_with_keys(&owner) + .unwrap(); + let foreign = note(&stranger, "foreign", "body", 50); + + let covers = shared_instruction_covers_from_events( + &owner.public_key().to_hex(), + vec![old, current, invalid_name, missing_summary, foreign], + ); + + assert_eq!(covers.len(), 3); + assert_eq!(covers[0].slug, "missing-summary"); + assert!(!covers[0].compatible); + assert_eq!( + covers[0].incompatibilities[0].code, + SharedInstructionIncompatibilityCode::MissingDescription + ); + assert_eq!(covers[1].slug, "Design_skill"); + assert!(!covers[1].compatible); + assert_eq!( + covers[1].incompatibilities[0].code, + SharedInstructionIncompatibilityCode::InvalidName + ); + assert_eq!(covers[2].slug, "design-engineering"); + assert!(covers[2].compatible); + assert!(covers[2].incompatibilities.is_empty()); + assert_eq!(covers[2].updated_at, 20); +} + +#[test] +fn validation_rejects_instruction_name_edge_cases_and_empty_body() { + for slug in ["", "-skill", "skill-", "two--hyphens", "with_underscore"] { + let reasons = shared_instruction_incompatibilities(slug, Some("Useful summary"), "body"); + assert!(reasons + .iter() + .any(|reason| reason.code == SharedInstructionIncompatibilityCode::InvalidName)); + } + let reasons = shared_instruction_incompatibilities("valid-name", Some("summary"), "\n"); + assert_eq!( + reasons[0].code, + SharedInstructionIncompatibilityCode::EmptyBody + ); +} + +#[test] +fn exact_query_filters_do_not_admit_author_slug_cross_products() { + let alpha = Keys::generate(); + let beta = Keys::generate(); + let coordinates = [ + format!("30023:{}:alpha", alpha.public_key().to_hex()), + format!("30023:{}:beta", beta.public_key().to_hex()), + ] + .iter() + .map(|value| parse_shared_instruction_coordinate(value).unwrap()) + .collect::>(); + + let filters = exact_coordinate_filters(&coordinates); + + assert_eq!(filters.len(), 2); + assert_eq!( + filters[0]["authors"], + serde_json::json!([alpha.public_key().to_hex()]) + ); + assert_eq!(filters[0]["#d"], serde_json::json!(["alpha"])); + assert_eq!(filters[0]["limit"], serde_json::json!(1)); + assert_eq!( + filters[1]["authors"], + serde_json::json!([beta.public_key().to_hex()]) + ); + assert_eq!(filters[1]["#d"], serde_json::json!(["beta"])); +} + +#[test] +fn resolves_only_exact_requested_signer_and_slug() { + let alpha = Keys::generate(); + let beta = Keys::generate(); + let coordinate = format!("30023:{}:design-engineering", alpha.public_key().to_hex()); + let wrong_author_same_slug = note(&beta, "design-engineering", "wrong author", 20); + let right_author_wrong_slug = note(&alpha, "other", "wrong slug", 30); + let expected = note(&alpha, "design-engineering", "trusted body", 10); + + let resolved = resolved_heads_from_events( + std::slice::from_ref(&coordinate), + vec![ + wrong_author_same_slug, + right_author_wrong_slug, + expected.clone(), + ], + ); + + assert_eq!(resolved.len(), 1); + assert_eq!(resolved[0].coordinate, coordinate); + assert_eq!(resolved[0].publisher, alpha.public_key().to_hex()); + assert_eq!(resolved[0].slug, "design-engineering"); + assert_eq!(resolved[0].content, "trusted body"); + assert_eq!( + resolved[0].summary.as_deref(), + Some("Summary design-engineering") + ); + assert_eq!(resolved[0].event_id, expected.id.to_hex()); +} + +#[test] +fn selects_newest_verified_head_and_preserves_assignment_order() { + let alpha = Keys::generate(); + let beta = Keys::generate(); + let alpha_coordinate = format!("30023:{}:alpha", alpha.public_key().to_hex()); + let beta_coordinate = format!("30023:{}:beta", beta.public_key().to_hex()); + let old_alpha = note(&alpha, "alpha", "old", 10); + let new_alpha = note(&alpha, "alpha", "new", 20); + let beta_note = note(&beta, "beta", "beta", 15); + + let resolved = resolved_heads_from_events( + &[beta_coordinate.clone(), alpha_coordinate.clone()], + vec![old_alpha, new_alpha, beta_note], + ); + + assert_eq!( + resolved + .iter() + .map(|skill| skill.coordinate.as_str()) + .collect::>(), + vec![beta_coordinate.as_str(), alpha_coordinate.as_str()] + ); + assert_eq!(resolved[1].content, "new"); +} + +#[test] +fn rejects_tampered_oversize_and_ambiguous_coordinate_events() { + let keys = Keys::generate(); + let coordinate = format!("30023:{}:skill", keys.public_key().to_hex()); + let mut tampered = note(&keys, "skill", "original", 10); + tampered.content = "tampered".to_string(); + let oversize = note( + &keys, + "skill", + &"x".repeat(MAX_SHARED_INSTRUCTION_BODY_BYTES + 1), + 20, + ); + let ambiguous = EventBuilder::new(Kind::Custom(SHARED_INSTRUCTION_KIND), "ambiguous") + .tags([ + Tag::parse(["d", "skill"]).unwrap(), + Tag::parse(["d", "skill"]).unwrap(), + ]) + .custom_created_at(Timestamp::from(30)) + .sign_with_keys(&keys) + .unwrap(); + + assert!( + resolved_heads_from_events(&[coordinate], vec![tampered, oversize, ambiguous]).is_empty() + ); +} diff --git a/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx b/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx index 619320b6a8..8806414333 100644 --- a/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx +++ b/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx @@ -1,7 +1,6 @@ import * as React from "react"; import { ChevronDown } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; - import type { AcpRuntimeCatalogEntry, CreatePersonaInput, @@ -91,7 +90,6 @@ import { runtimeDropdownAction, usePendingHarnessSelection, } from "./addCustomHarness"; - type AgentDefinitionDialogProps = { open: boolean; embedded?: boolean; @@ -115,11 +113,9 @@ type AgentDefinitionDialogProps = { /** Extra create-mode submit gate (e.g. incomplete provider config). */ createSubmitBlocked?: boolean; }; - export type AgentDefinitionSubmitOptions = { publishCatalogUpdates: boolean; }; - export function AgentDefinitionDialog({ open, embedded = false, @@ -202,7 +198,6 @@ export function AgentDefinitionDialog({ !hasText(initialValues.runtime) && (hasText(initialValues.model) || hasText(initialValues.provider)), ); - React.useEffect(() => { onDirtyChange?.(hasUserChanges); }, [hasUserChanges, onDirtyChange]); diff --git a/desktop/src/shared/api/tauri.ts b/desktop/src/shared/api/tauri.ts index a7bcb0e92b..f046da82d6 100644 --- a/desktop/src/shared/api/tauri.ts +++ b/desktop/src/shared/api/tauri.ts @@ -40,13 +40,10 @@ import type { GitBashPrerequisite, RuntimeConfigSurface, } from "@/shared/api/types"; - export * from "@/shared/api/tauriChannels"; export { sendChannelMessage } from "@/shared/api/tauriMessages"; export { getEventById, getEventsByIds } from "@/shared/api/tauriEvents"; - type RawPresenceLookup = Record; - type RawAddChannelMembersResult = { added: string[]; errors: Array<{ @@ -54,7 +51,6 @@ type RawAddChannelMembersResult = { error: string; }>; }; - type RawFeedItem = { id: string; kind: number; @@ -67,7 +63,6 @@ type RawFeedItem = { tags: string[][]; category: HomeFeedResponse["feed"]["mentions"][number]["category"]; }; - type RawHomeFeedResponse = { feed: { mentions: RawFeedItem[];