@team-agent/installer 0.5.103 → 0.5.105
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +2 -0
- package/crates/team-agent/src/cli/emit.rs +102 -5
- package/crates/team-agent/src/cli/mod.rs +8 -14
- package/crates/team-agent/src/cli/spec.rs +3 -3
- package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -0
- package/crates/team-agent/src/cli/types.rs +3 -1
- package/crates/team-agent/src/compiler/tests.rs +19 -27
- package/crates/team-agent/src/compiler.rs +11 -105
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +6 -2
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +10 -6
- package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +2 -5
- package/crates/team-agent/src/lifecycle/launch/identity.rs +9 -13
- package/crates/team-agent/src/lifecycle/launch/role_source.rs +2 -26
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +7 -2
- package/crates/team-agent/src/lifecycle/launch.rs +4 -1
- package/crates/team-agent/src/lifecycle/mod.rs +1 -0
- package/crates/team-agent/src/lifecycle/profile_launch.rs +0 -12
- package/crates/team-agent/src/lifecycle/restart/agent.rs +31 -0
- package/crates/team-agent/src/lifecycle/restart/avatar.rs +0 -1
- package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart.rs +1 -1
- package/crates/team-agent/src/lifecycle/role_config.rs +1123 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +7 -19
- package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +8 -41
- package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +14 -8
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +8 -4
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +4 -4
- package/crates/team-agent/src/model/enums.rs +0 -9
- package/crates/team-agent/src/model/spec.rs +2 -2
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +7 -8
- package/crates/team-agent/src/provider/adapters/grok.rs +5 -18
- package/crates/team-agent/src/provider/tests/adapter.rs +15 -24
- package/crates/team-agent/src/provider/wire.rs +2 -29
- package/crates/team-agent/src/state/repository.rs +2 -1
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +83 -33
- package/skills/team-agent/references/team-agent-operator.md +54 -523
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
|
@@ -1100,6 +1100,7 @@ pub fn cmd_start_agent(args: &StartAgentArgs) -> Result<CmdResult, CliError> {
|
|
|
1100
1100
|
args.force,
|
|
1101
1101
|
args.allow_fresh,
|
|
1102
1102
|
args.team.as_deref(),
|
|
1103
|
+
&args.role_config,
|
|
1103
1104
|
)?;
|
|
1104
1105
|
if value.get("agent_ids").is_none() {
|
|
1105
1106
|
if let Some(object) = value.as_object_mut() {
|
|
@@ -1142,6 +1143,7 @@ pub fn cmd_add_agent(args: &AddAgentArgs) -> Result<CmdResult, CliError> {
|
|
|
1142
1143
|
&args.role_file,
|
|
1143
1144
|
args.team.as_deref(),
|
|
1144
1145
|
args.force,
|
|
1146
|
+
&args.role_config,
|
|
1145
1147
|
)?;
|
|
1146
1148
|
if value.get("agent_ids").is_none() {
|
|
1147
1149
|
if let Some(object) = value.as_object_mut() {
|
|
@@ -1430,9 +1430,107 @@ fn restart_args(args: &[String], cwd: &Path) -> Result<RestartArgs, CliError> {
|
|
|
1430
1430
|
})
|
|
1431
1431
|
}
|
|
1432
1432
|
|
|
1433
|
+
// Keep strict role CLI parsing separate from the legacy shared parser.
|
|
1434
|
+
fn role_agent_args(args: &[String], add: bool) -> Result<(ParsedArgs, crate::lifecycle::role_config::RoleConfigPatch), CliError> {
|
|
1435
|
+
let mut role = crate::lifecycle::role_config::RoleConfigPatch::default();
|
|
1436
|
+
let mut forwarded = Vec::new();
|
|
1437
|
+
let mut seen = std::collections::BTreeSet::new();
|
|
1438
|
+
let mut index = 0;
|
|
1439
|
+
while index < args.len() {
|
|
1440
|
+
let argument = &args[index];
|
|
1441
|
+
if !argument.starts_with('-') {
|
|
1442
|
+
forwarded.push(argument.clone());
|
|
1443
|
+
index += 1;
|
|
1444
|
+
continue;
|
|
1445
|
+
}
|
|
1446
|
+
let (flag, inline) = argument.split_once('=').map_or((argument.as_str(), None), |(key, value)| (key, Some(value)));
|
|
1447
|
+
if !seen.insert(flag.to_string()) { return Err(CliError::Usage(format!("duplicate {flag}"))); }
|
|
1448
|
+
if matches!(flag, "--json" | "--allow-fresh" | "--force") {
|
|
1449
|
+
if inline.is_some() || (add && flag == "--allow-fresh") {
|
|
1450
|
+
return Err(CliError::Usage(format!("unsupported option: {argument}")));
|
|
1451
|
+
}
|
|
1452
|
+
forwarded.push(flag.to_string());
|
|
1453
|
+
index += 1;
|
|
1454
|
+
continue;
|
|
1455
|
+
}
|
|
1456
|
+
if !matches!(flag, "--provider" | "--model" | "--effort" | "--bypass" | "--prompt" | "--profile" | "--workspace" | "--team" | "--role-file")
|
|
1457
|
+
|| (!add && flag == "--role-file") {
|
|
1458
|
+
return Err(CliError::Usage(format!("unknown option: {flag}")));
|
|
1459
|
+
}
|
|
1460
|
+
let value = if let Some(value) = inline { value.to_string() } else {
|
|
1461
|
+
index += 1;
|
|
1462
|
+
args.get(index).filter(|value| !value.starts_with("--"))
|
|
1463
|
+
.ok_or_else(|| CliError::Usage(format!("missing value for {flag}")))?.clone()
|
|
1464
|
+
};
|
|
1465
|
+
if value.trim().is_empty() { return Err(CliError::Usage(format!("empty value for {flag}"))); }
|
|
1466
|
+
match flag {
|
|
1467
|
+
"--provider" => {
|
|
1468
|
+
let provider = crate::provider::wire::parse_provider(&value)
|
|
1469
|
+
.ok_or_else(|| CliError::Usage(format!("unknown provider: {value}")))?;
|
|
1470
|
+
role.provider = Some(crate::provider::wire::provider_wire(provider).to_string());
|
|
1471
|
+
}
|
|
1472
|
+
"--bypass" => role.bypass = Some(match value.as_str() {
|
|
1473
|
+
"true" => true, "false" => false,
|
|
1474
|
+
_ => return Err(CliError::Usage("--bypass requires true or false".into())),
|
|
1475
|
+
}),
|
|
1476
|
+
"--model" => role.model = Some(value),
|
|
1477
|
+
"--effort" => {
|
|
1478
|
+
if crate::model::enums::ProviderEffort::parse(&value).is_none() {
|
|
1479
|
+
return Err(CliError::Usage(format!("unknown effort: {value}")));
|
|
1480
|
+
}
|
|
1481
|
+
role.effort = Some(value);
|
|
1482
|
+
}
|
|
1483
|
+
"--prompt" => role.prompt = Some(value),
|
|
1484
|
+
"--profile" => role.profile = Some(value),
|
|
1485
|
+
_ => { forwarded.push(flag.to_string()); forwarded.push(value); }
|
|
1486
|
+
}
|
|
1487
|
+
index += 1;
|
|
1488
|
+
}
|
|
1489
|
+
let parsed = parse_args(&forwarded);
|
|
1490
|
+
if add && parsed.role_file.is_none() {
|
|
1491
|
+
if role.provider.is_none() { return Err(CliError::Usage("add-agent requires --provider <name>".into())); }
|
|
1492
|
+
if role.bypass.is_none() { return Err(CliError::Usage("add-agent requires --bypass <true|false>".into())); }
|
|
1493
|
+
}
|
|
1494
|
+
if parsed.positionals.len() != 1 { return Err(CliError::Usage("expected exactly one agent id".into())); }
|
|
1495
|
+
Ok((parsed, role))
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
#[cfg(test)]
|
|
1499
|
+
mod role_cli_tests {
|
|
1500
|
+
use super::*;
|
|
1501
|
+
fn strings(values: &[&str]) -> Vec<String> { values.iter().map(|value| (*value).to_string()).collect() }
|
|
1502
|
+
|
|
1503
|
+
#[test]
|
|
1504
|
+
fn add_requires_decisions_without_a_template_but_accepts_file_input() {
|
|
1505
|
+
for args in [vec!["w"], vec!["w", "--provider", "pi"], vec!["w", "--bypass", "false"]] {
|
|
1506
|
+
assert!(role_agent_args(&strings(&args), true).is_err());
|
|
1507
|
+
}
|
|
1508
|
+
assert!(role_agent_args(&strings(&["w", "--role-file", "w.md"]), true).is_ok());
|
|
1509
|
+
let (parsed, _) = role_agent_args(&strings(&["w", "--role-file", "w.md", "--force"]), true).unwrap();
|
|
1510
|
+
assert!(parsed.force);
|
|
1511
|
+
assert!(role_agent_args(&strings(&["w", "--role-file", "w.md", "--force=true"]), true).is_err());
|
|
1512
|
+
assert!(role_agent_args(&strings(&["w", "--role-file", "w.md", "--allow-fresh"]), true).is_err());
|
|
1513
|
+
let (_, patch) = role_agent_args(&strings(&["w", "--provider=pi", "--bypass=false"]), true).unwrap();
|
|
1514
|
+
assert_eq!(patch.provider.as_deref(), Some("pi"));
|
|
1515
|
+
assert_eq!(patch.bypass, Some(false));
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
#[test]
|
|
1519
|
+
fn strict_role_options_reject_ambiguous_values() {
|
|
1520
|
+
for args in [
|
|
1521
|
+
vec!["w", "--bypass"], vec!["w", "--bypass", "yes"],
|
|
1522
|
+
vec!["w", "--no-bypass"], vec!["w", "--bypass", "true", "--bypass=false"],
|
|
1523
|
+
vec!["w", "--unknown", "x"], vec!["w", "extra"],
|
|
1524
|
+
] { assert!(role_agent_args(&strings(&args), false).is_err()); }
|
|
1525
|
+
let (_, patch) = role_agent_args(&strings(&["w"]), false).unwrap();
|
|
1526
|
+
assert_eq!(patch, crate::lifecycle::role_config::RoleConfigPatch::default());
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1433
1530
|
fn start_agent_args(args: &[String], cwd: &Path) -> Result<StartAgentArgs, CliError> {
|
|
1434
|
-
let parsed =
|
|
1531
|
+
let (parsed, role_config) = role_agent_args(args, false)?;
|
|
1435
1532
|
Ok(StartAgentArgs {
|
|
1533
|
+
role_config,
|
|
1436
1534
|
agent: required_pos(&parsed, 0, "agent")?,
|
|
1437
1535
|
workspace: workspace(&parsed, cwd),
|
|
1438
1536
|
team: parsed.team,
|
|
@@ -1466,14 +1564,13 @@ fn reset_agent_args(args: &[String], cwd: &Path) -> Result<ResetAgentArgs, CliEr
|
|
|
1466
1564
|
}
|
|
1467
1565
|
|
|
1468
1566
|
fn add_agent_args(args: &[String], cwd: &Path) -> Result<AddAgentArgs, CliError> {
|
|
1469
|
-
let parsed =
|
|
1567
|
+
let (parsed, role_config) = role_agent_args(args, true)?;
|
|
1470
1568
|
Ok(AddAgentArgs {
|
|
1569
|
+
role_config,
|
|
1471
1570
|
agent: required_pos(&parsed, 0, "agent")?,
|
|
1472
1571
|
workspace: workspace(&parsed, cwd),
|
|
1473
1572
|
team: parsed.team,
|
|
1474
|
-
role_file: parsed
|
|
1475
|
-
.role_file
|
|
1476
|
-
.ok_or_else(|| CliError::Usage("missing --role-file".to_string()))?,
|
|
1573
|
+
role_file: parsed.role_file.unwrap_or_default(),
|
|
1477
1574
|
force: parsed.force,
|
|
1478
1575
|
json: parsed.json,
|
|
1479
1576
|
})
|
|
@@ -2750,15 +2750,12 @@ pub mod lifecycle_port {
|
|
|
2750
2750
|
force: bool,
|
|
2751
2751
|
allow_fresh: bool,
|
|
2752
2752
|
team: Option<&str>,
|
|
2753
|
+
role_config: &crate::lifecycle::role_config::RoleConfigPatch,
|
|
2753
2754
|
) -> Result<Value, CliError> {
|
|
2755
|
+
let _ = force; // Never grants authority to stop an already-running seat.
|
|
2754
2756
|
let agent_id = crate::model::ids::AgentId::new(agent);
|
|
2755
|
-
match crate::lifecycle::
|
|
2756
|
-
workspace,
|
|
2757
|
-
&agent_id,
|
|
2758
|
-
force,
|
|
2759
|
-
true,
|
|
2760
|
-
allow_fresh,
|
|
2761
|
-
team,
|
|
2757
|
+
match crate::lifecycle::role_config::start_agent_from_role(
|
|
2758
|
+
workspace, &agent_id, role_config, allow_fresh, team,
|
|
2762
2759
|
) {
|
|
2763
2760
|
Ok(crate::lifecycle::StartAgentOutcome::Running {
|
|
2764
2761
|
env,
|
|
@@ -2876,15 +2873,12 @@ pub mod lifecycle_port {
|
|
|
2876
2873
|
role_file: &str,
|
|
2877
2874
|
team: Option<&str>,
|
|
2878
2875
|
force: bool,
|
|
2876
|
+
role_config: &crate::lifecycle::role_config::RoleConfigPatch,
|
|
2879
2877
|
) -> Result<Value, CliError> {
|
|
2880
2878
|
let agent_id = crate::model::ids::AgentId::new(agent);
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
&agent_id,
|
|
2884
|
-
Path::new(role_file),
|
|
2885
|
-
true,
|
|
2886
|
-
team,
|
|
2887
|
-
force,
|
|
2879
|
+
let source = (!role_file.is_empty()).then(|| Path::new(role_file));
|
|
2880
|
+
match crate::lifecycle::role_config::add_agent_from_role(
|
|
2881
|
+
workspace, &agent_id, source, role_config, force, team,
|
|
2888
2882
|
) {
|
|
2889
2883
|
Ok(report) => Ok(agent_action_value(
|
|
2890
2884
|
agent,
|
|
@@ -152,8 +152,8 @@ pub(crate) const COMMAND_SPECS: &[CommandSpec] = &[
|
|
|
152
152
|
CommandSpec { name: "wait", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Wait), summary: "block until a task result is stored", usage: "usage: team-agent wait --task TASK [--workspace WORKSPACE] [--json]", default_help: false, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
|
|
153
153
|
CommandSpec { name: "restart", tier: CommandTier::Core, category: CommandCategory::TeamLifecycle, kind: CommandKind::Dispatch(DispatchKind::Restart), summary: "restart the selected team", usage: "usage: team-agent restart [WORKSPACE] [--team TEAM] [--allow-fresh] [--session-converge-deadline SECONDS] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
|
|
154
154
|
CommandSpec { name: "shutdown", tier: CommandTier::Core, category: CommandCategory::TeamLifecycle, kind: CommandKind::Dispatch(DispatchKind::Shutdown), summary: "stop the selected team", usage: "usage: team-agent shutdown [--workspace WORKSPACE] [--team TEAM] [--keep-logs] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
|
|
155
|
-
CommandSpec { name: "add-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::AddAgent), summary: "
|
|
156
|
-
CommandSpec { name: "start-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::StartAgent), summary: "start
|
|
155
|
+
CommandSpec { name: "add-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::AddAgent), summary: "create and start a new worker", usage: "usage: team-agent add-agent AGENT [--role-file FILE] [--provider NAME] [--bypass true|false] [--model SLUG] [--effort LEVEL] [--prompt TEXT] [--profile NAME] [--force] [--workspace WORKSPACE] [--team TEAM] [--json] (provider/bypass required from CLI or role file; conflicting values rejected; force only recreates a confirmed dead registered pane)", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
|
|
156
|
+
CommandSpec { name: "start-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::StartAgent), summary: "update and start a stopped worker (stop first if running)", usage: "usage: team-agent start-agent AGENT [--model SLUG] [--effort LEVEL] [--bypass true|false] [--prompt TEXT] [--profile NAME] [--provider NAME] [--workspace WORKSPACE] [--team TEAM] [--allow-fresh] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
|
|
157
157
|
CommandSpec { name: "stop-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::StopAgent), summary: "stop a worker", usage: "usage: team-agent stop-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
|
|
158
158
|
CommandSpec { name: "reset-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::ResetAgent), summary: "reset a worker session", usage: "usage: team-agent reset-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--discard-session] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
|
|
159
159
|
CommandSpec { name: "claim-leader", tier: CommandTier::Guided, category: CommandCategory::GuidedRecovery, kind: CommandKind::Dispatch(DispatchKind::ClaimLeader), summary: "bind this pane when doctor reports rebind_required", usage: "usage: team-agent claim-leader [--workspace WORKSPACE] [--team TEAM] [--confirm] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
|
|
@@ -165,7 +165,7 @@ pub(crate) const COMMAND_SPECS: &[CommandSpec] = &[
|
|
|
165
165
|
CommandSpec { name: "copilot", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "copilot" }, summary: "launch a Copilot leader", usage: "usage: team-agent copilot [provider args...]", default_help: false, command_help: true, suggestion_index: true, token_usage: TokenUsage::Yes, sunset: None, action: None, governance: None },
|
|
166
166
|
CommandSpec { name: "grok", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "grok" }, summary: "launch a Grok leader", usage: "usage: team-agent grok [provider args...]", default_help: false, command_help: true, suggestion_index: true, token_usage: TokenUsage::Yes, sunset: None, action: None, governance: None },
|
|
167
167
|
CommandSpec { name: "cursor", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "cursor" }, summary: "launch a Cursor leader", usage: "usage: team-agent cursor [provider args...]", default_help: false, command_help: true, suggestion_index: true, token_usage: TokenUsage::Yes, sunset: None, action: None, governance: None },
|
|
168
|
-
CommandSpec { name: "pi", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "pi" }, summary: "launch a Pi leader", usage: "usage: team-agent pi
|
|
168
|
+
CommandSpec { name: "pi", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "pi" }, summary: "launch a Pi leader", usage: "usage: team-agent pi [provider args...]", default_help: false, command_help: true, suggestion_index: true, token_usage: TokenUsage::Yes, sunset: None, action: None, governance: None },
|
|
169
169
|
|
|
170
170
|
CommandSpec { name: "leaders", tier: CommandTier::Secondary, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Leaders), summary: "list live, stale, or ambiguous host leaders", usage: "usage: team-agent leaders [QUERY|--search TEXT] [--all|--stale] [--json] | --prune [--dry-run] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
|
|
171
171
|
CommandSpec { name: "doctor", tier: CommandTier::Core, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Doctor), summary: "inspect environment and Team runtime health", usage: "usage: team-agent doctor [SPEC] [--workspace WORKSPACE] [--team TEAM] [--gate orphans|comms] [--comms] [--fix] [--fix-schema] [--cleanup-orphans] [--confirm] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
|
|
@@ -317,6 +317,7 @@ fn cli_add_agent_duplicate_id_surfaces_real_error() {
|
|
|
317
317
|
let dup_role = team.join("dup-role.md");
|
|
318
318
|
std::fs::write(&dup_role, DELEG_VALID_ROLE).unwrap(); // role file must exist
|
|
319
319
|
let args = AddAgentArgs {
|
|
320
|
+
role_config: Default::default(),
|
|
320
321
|
agent: "implementer".to_string(),
|
|
321
322
|
workspace: team,
|
|
322
323
|
team: None,
|
|
@@ -475,6 +475,7 @@ pub struct RestartArgs {
|
|
|
475
475
|
/// `start-agent`(`parser.py:369`)。
|
|
476
476
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
477
477
|
pub struct StartAgentArgs {
|
|
478
|
+
pub role_config: crate::lifecycle::role_config::RoleConfigPatch,
|
|
478
479
|
pub agent: String,
|
|
479
480
|
pub workspace: PathBuf,
|
|
480
481
|
pub team: Option<String>,
|
|
@@ -502,9 +503,10 @@ pub struct ResetAgentArgs {
|
|
|
502
503
|
pub json: bool,
|
|
503
504
|
}
|
|
504
505
|
|
|
505
|
-
/// `add-agent
|
|
506
|
+
/// `add-agent`: provider and bypass are explicit CLI or role-file decisions.
|
|
506
507
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
507
508
|
pub struct AddAgentArgs {
|
|
509
|
+
pub role_config: crate::lifecycle::role_config::RoleConfigPatch,
|
|
508
510
|
pub agent: String,
|
|
509
511
|
pub workspace: PathBuf,
|
|
510
512
|
pub team: Option<String>,
|
|
@@ -221,9 +221,8 @@ fn compile_subscription_without_profile_is_thin_manifest() {
|
|
|
221
221
|
);
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
// Runtime front-matter defaults:
|
|
225
|
-
//
|
|
226
|
-
// (TEAM.md has no `model:` key, only `default_model:`).
|
|
224
|
+
// Runtime front-matter defaults: TEAM.md runtime settings remain available,
|
|
225
|
+
// but its default_model does not fill an omitted role model; leader.model is null.
|
|
227
226
|
const RUNTIME_DEFAULTS_TEAM: &str = "\
|
|
228
227
|
---
|
|
229
228
|
name: doc-team
|
|
@@ -256,7 +255,7 @@ dangerously_skip_permissions: false
|
|
|
256
255
|
Implement bounded tasks.
|
|
257
256
|
";
|
|
258
257
|
|
|
259
|
-
const RUNTIME_DEFAULTS_JSON: &str = r#"{"version":1,"team":{"name":"doc-team","mode":"supervisor_worker","objective":"Compile role docs.","workspace":"__WS__"},"leader":{"id":"leader","role":"leader","provider":"codex","model":null,"context_policy":{"keep_user_thread":true,"receive_worker_outputs":"business_messages_and_short_summaries","max_worker_result_tokens":2000}},"agents":[{"id":"implementer","role":"Implementation Engineer","provider":"codex","model":
|
|
258
|
+
const RUNTIME_DEFAULTS_JSON: &str = r#"{"version":1,"team":{"name":"doc-team","mode":"supervisor_worker","objective":"Compile role docs.","workspace":"__WS__"},"leader":{"id":"leader","role":"leader","provider":"codex","model":null,"context_policy":{"keep_user_thread":true,"receive_worker_outputs":"business_messages_and_short_summaries","max_worker_result_tokens":2000}},"agents":[{"id":"implementer","role":"Implementation Engineer","provider":"codex","model":null,"auth_mode":"subscription","working_directory":"__WS__","system_prompt":{"inline":"Implement bounded tasks.","file":null},"dangerously_skip_permissions":false,"communication_mode":"leader_centric","preferred_for":["implementer","Implementation Engineer"],"avoid_for":[],"output_contract":{"format":"result_envelope_v1","required_fields":["task_id","status","summary","artifacts"]}}],"routing":{"default_assignee":"implementer","rules":[{"id":"route-implementer","match":{"assignee":["implementer"]},"assign_to":"implementer","priority":10}]},"communication":{"protocol":"mcp_inbox","topology":"leader_centered","worker_to_worker":true,"ack_timeout_sec":60,"result_format":"result_envelope_v1","message_store":{"sqlite":".team/runtime/team.db","mirror_files":".team/messages"}},"runtime":{"backend":"tmux","session_name":"team-doc-team","auto_launch":true,"require_user_approval_before_launch":true,"max_active_agents":1,"startup_order":["implementer"],"fast":true,"tick_interval_sec":1,"push_min_interval_sec":3,"stuck_timeout_sec":5},"context":{"state_file":"team_state.md","artifact_dir":".team/artifacts","log_dir":".team/logs","summarization":{"worker_full_logs":"retain_outside_leader_context","state_update":"after_each_result"}},"tasks":[{"id":"task_initial","title":"Initial document-driven team task","type":"implementation","assignee":"implementer","deps":[],"acceptance":["Worker reports valid result_envelope_v1"],"status":"pending","requires_tools":["mcp_team"],"files":[],"risk":"low"}]}"#;
|
|
260
259
|
|
|
261
260
|
#[test]
|
|
262
261
|
fn compile_runtime_front_matter_defaults_match_python() {
|
|
@@ -269,8 +268,7 @@ fn compile_runtime_front_matter_defaults_match_python() {
|
|
|
269
268
|
assert_eq!(templated_compact_json(&spec), RUNTIME_DEFAULTS_JSON);
|
|
270
269
|
}
|
|
271
270
|
|
|
272
|
-
// provider_models
|
|
273
|
-
// key in provider_models falls back to the `claude` key (compiler.py:317-318).
|
|
271
|
+
// TEAM provider_models do not fill a role whose model is omitted.
|
|
274
272
|
const ALIAS_TEAM: &str = "\
|
|
275
273
|
---
|
|
276
274
|
name: debate-team
|
|
@@ -296,16 +294,16 @@ dangerously_skip_permissions: false
|
|
|
296
294
|
Edit and defend the argument.
|
|
297
295
|
";
|
|
298
296
|
|
|
299
|
-
const ALIAS_JSON: &str = r#"{"version":1,"team":{"name":"debate-team","mode":"supervisor_worker","objective":"Compile thin role docs.","workspace":"__WS__"},"leader":{"id":"leader","role":"leader","provider":"codex","model":null,"context_policy":{"keep_user_thread":true,"receive_worker_outputs":"business_messages_and_short_summaries","max_worker_result_tokens":2000}},"agents":[{"id":"editor","role":"Editor and Defender","provider":"claude_code","model":
|
|
297
|
+
const ALIAS_JSON: &str = r#"{"version":1,"team":{"name":"debate-team","mode":"supervisor_worker","objective":"Compile thin role docs.","workspace":"__WS__"},"leader":{"id":"leader","role":"leader","provider":"codex","model":null,"context_policy":{"keep_user_thread":true,"receive_worker_outputs":"business_messages_and_short_summaries","max_worker_result_tokens":2000}},"agents":[{"id":"editor","role":"Editor and Defender","provider":"claude_code","model":null,"auth_mode":"subscription","working_directory":"__WS__","system_prompt":{"inline":"Edit and defend the argument.","file":null},"dangerously_skip_permissions":false,"communication_mode":"leader_centric","preferred_for":["editor","Editor and Defender"],"avoid_for":[],"output_contract":{"format":"result_envelope_v1","required_fields":["task_id","status","summary","artifacts"]}}],"routing":{"default_assignee":"editor","rules":[{"id":"route-editor","match":{"assignee":["editor"]},"assign_to":"editor","priority":10}]},"communication":{"protocol":"mcp_inbox","topology":"leader_centered","worker_to_worker":true,"ack_timeout_sec":60,"result_format":"result_envelope_v1","message_store":{"sqlite":".team/runtime/team.db","mirror_files":".team/messages"}},"runtime":{"backend":"tmux","session_name":"team-debate-team","auto_launch":true,"require_user_approval_before_launch":true,"max_active_agents":1,"startup_order":["editor"],"fast":false,"tick_interval_sec":2,"push_min_interval_sec":60,"stuck_timeout_sec":300},"context":{"state_file":"team_state.md","artifact_dir":".team/artifacts","log_dir":".team/logs","summarization":{"worker_full_logs":"retain_outside_leader_context","state_update":"after_each_result"}},"tasks":[{"id":"task_initial","title":"Initial document-driven team task","type":"implementation","assignee":"editor","deps":[],"acceptance":["Worker reports valid result_envelope_v1"],"status":"pending","requires_tools":["mcp_team"],"files":[],"risk":"low"}]}"#;
|
|
300
298
|
|
|
301
299
|
#[test]
|
|
302
|
-
fn
|
|
300
|
+
fn compile_provider_models_do_not_fill_claude_code_role_model() {
|
|
303
301
|
let team = build_team(ALIAS_TEAM, &[("implementer.md", ALIAS_ROLE)], &[]);
|
|
304
302
|
let spec = compile_team(&team).unwrap();
|
|
305
303
|
assert_eq!(templated_compact_json(&spec), ALIAS_JSON);
|
|
306
304
|
}
|
|
307
305
|
|
|
308
|
-
//
|
|
306
|
+
// Omitted role model remains null; the provider supplies its native default.
|
|
309
307
|
const BUILTIN_TEAM: &str = "\
|
|
310
308
|
---
|
|
311
309
|
name: default-model-team
|
|
@@ -329,10 +327,10 @@ tools:
|
|
|
329
327
|
Implement bounded tasks.
|
|
330
328
|
";
|
|
331
329
|
|
|
332
|
-
const BUILTIN_JSON: &str = r#"{"version":1,"team":{"name":"default-model-team","mode":"supervisor_worker","objective":"Compile role docs without model fields.","workspace":"__WS__"},"leader":{"id":"leader","role":"leader","provider":"codex","model":null,"context_policy":{"keep_user_thread":true,"receive_worker_outputs":"business_messages_and_short_summaries","max_worker_result_tokens":2000}},"agents":[{"id":"implementer","role":"Implementation Engineer","provider":"codex","model":
|
|
330
|
+
const BUILTIN_JSON: &str = r#"{"version":1,"team":{"name":"default-model-team","mode":"supervisor_worker","objective":"Compile role docs without model fields.","workspace":"__WS__"},"leader":{"id":"leader","role":"leader","provider":"codex","model":null,"context_policy":{"keep_user_thread":true,"receive_worker_outputs":"business_messages_and_short_summaries","max_worker_result_tokens":2000}},"agents":[{"id":"implementer","role":"Implementation Engineer","provider":"codex","model":null,"auth_mode":"subscription","working_directory":"__WS__","system_prompt":{"inline":"Implement bounded tasks.","file":null},"dangerously_skip_permissions":false,"communication_mode":"leader_centric","preferred_for":["implementer","Implementation Engineer"],"avoid_for":[],"output_contract":{"format":"result_envelope_v1","required_fields":["task_id","status","summary","artifacts"]}}],"routing":{"default_assignee":"implementer","rules":[{"id":"route-implementer","match":{"assignee":["implementer"]},"assign_to":"implementer","priority":10}]},"communication":{"protocol":"mcp_inbox","topology":"leader_centered","worker_to_worker":true,"ack_timeout_sec":60,"result_format":"result_envelope_v1","message_store":{"sqlite":".team/runtime/team.db","mirror_files":".team/messages"}},"runtime":{"backend":"tmux","session_name":"team-default-model-team","auto_launch":true,"require_user_approval_before_launch":true,"max_active_agents":1,"startup_order":["implementer"],"fast":false,"tick_interval_sec":2,"push_min_interval_sec":60,"stuck_timeout_sec":300},"context":{"state_file":"team_state.md","artifact_dir":".team/artifacts","log_dir":".team/logs","summarization":{"worker_full_logs":"retain_outside_leader_context","state_update":"after_each_result"}},"tasks":[{"id":"task_initial","title":"Initial document-driven team task","type":"implementation","assignee":"implementer","deps":[],"acceptance":["Worker reports valid result_envelope_v1"],"status":"pending","requires_tools":["mcp_team"],"files":[],"risk":"low"}]}"#;
|
|
333
331
|
|
|
334
332
|
#[test]
|
|
335
|
-
fn
|
|
333
|
+
fn compile_subscription_without_model_keeps_provider_native_default() {
|
|
336
334
|
let team = build_team(BUILTIN_TEAM, &[("implementer.md", BUILTIN_ROLE)], &[]);
|
|
337
335
|
let spec = compile_team(&team).unwrap();
|
|
338
336
|
assert_eq!(templated_compact_json(&spec), BUILTIN_JSON);
|
|
@@ -504,38 +502,32 @@ fn build_layout(parent: &str, leaf: &str, team_md: Option<&str>, agents: AgentsD
|
|
|
504
502
|
// ── A1 model resolution ──
|
|
505
503
|
|
|
506
504
|
#[test]
|
|
507
|
-
fn
|
|
508
|
-
//
|
|
505
|
+
fn team_model_defaults_do_not_fill_role_model() {
|
|
506
|
+
// TEAM default_model and provider_models are not implicit role settings.
|
|
509
507
|
let tm = "---\nname: T\nprovider: codex\ndefault_model: team-y\nprovider_models:\n codex: pm-z\n---\nx\n";
|
|
510
508
|
let team = build_team(tm, &[("w.md", &role_nomodel("codex"))], &[]);
|
|
511
509
|
let spec = compile_team(&team).unwrap();
|
|
512
|
-
assert_eq!(
|
|
513
|
-
agent0(&spec).get("model").and_then(Value::as_str),
|
|
514
|
-
Some("pm-z")
|
|
515
|
-
);
|
|
510
|
+
assert_eq!(agent0(&spec).get("model"), Some(&Value::Null));
|
|
516
511
|
}
|
|
517
512
|
|
|
518
513
|
#[test]
|
|
519
|
-
fn
|
|
520
|
-
//
|
|
514
|
+
fn team_provider_model_aliases_do_not_fill_role_model() {
|
|
515
|
+
// TEAM provider-model aliases do not affect an omitted role model.
|
|
521
516
|
let tm = "---\nname: T\nprovider: codex\nprovider_models:\n claude_code: cc-v\n---\nx\n";
|
|
522
517
|
let team = build_team(tm, &[("w.md", &role_nomodel("claude"))], &[]);
|
|
523
518
|
let spec = compile_team(&team).unwrap();
|
|
524
|
-
assert_eq!(
|
|
525
|
-
agent0(&spec).get("model").and_then(Value::as_str),
|
|
526
|
-
Some("cc-v")
|
|
527
|
-
);
|
|
519
|
+
assert_eq!(agent0(&spec).get("model"), Some(&Value::Null));
|
|
528
520
|
}
|
|
529
521
|
|
|
530
522
|
#[test]
|
|
531
|
-
fn
|
|
532
|
-
//
|
|
523
|
+
fn claude_role_without_model_keeps_provider_native_default() {
|
|
524
|
+
// A missing explicit model stays null for both Claude provider names.
|
|
533
525
|
for prov in ["claude", "claude_code"] {
|
|
534
526
|
let team = build_team(TM_CODEX, &[("w.md", &role_nomodel(prov))], &[]);
|
|
535
527
|
let spec = compile_team(&team).unwrap();
|
|
536
528
|
assert_eq!(
|
|
537
|
-
agent0(&spec).get("model")
|
|
538
|
-
Some(
|
|
529
|
+
agent0(&spec).get("model"),
|
|
530
|
+
Some(&Value::Null),
|
|
539
531
|
"provider {prov}"
|
|
540
532
|
);
|
|
541
533
|
}
|
|
@@ -15,11 +15,8 @@
|
|
|
15
15
|
//! comparing byte-for-byte to Python golden. The absolute `workspace` path (env-
|
|
16
16
|
//! dependent) is templated to `__WS__` on both sides so every other byte is pinned.
|
|
17
17
|
//!
|
|
18
|
-
//!
|
|
19
|
-
//!
|
|
20
|
-
//! `rust_core` inline-secret *detection* (`contains_inline_secret` / the secret-
|
|
21
|
-
//! lint rejection test) are DEFERRED to a follow-on — the compile path here only
|
|
22
|
-
//! needs `contains_inline_secret` to return `false` for clean (non-secret) input.
|
|
18
|
+
//! Profile references are carried as role metadata; profile files and secrets are
|
|
19
|
+
//! handled by lifecycle/profile_launch, not loaded or inspected by this compiler.
|
|
23
20
|
//!
|
|
24
21
|
//! §10: pure lib layer — no panic on malformed input; every parse/validate path
|
|
25
22
|
//! returns `Result<_, ModelError>` (mirrors Python `ValidationError`).
|
|
@@ -31,10 +28,7 @@ use crate::communication_mode::CommunicationMode;
|
|
|
31
28
|
use crate::model::enums::{Provider, ProviderEffort};
|
|
32
29
|
use crate::model::yaml::Value;
|
|
33
30
|
use crate::model::{paths, spec, yaml, ModelError};
|
|
34
|
-
use crate::provider::wire::
|
|
35
|
-
builtin_provider_model as wire_builtin_provider_model, parse_canonical_provider,
|
|
36
|
-
provider_model_keys,
|
|
37
|
-
};
|
|
31
|
+
use crate::provider::wire::parse_canonical_provider;
|
|
38
32
|
|
|
39
33
|
pub const IGNORED_OWNER_TEAM_ID_FIELD: &str = "owner_team_id";
|
|
40
34
|
|
|
@@ -494,18 +488,12 @@ fn compile_role_agent_with_mode(
|
|
|
494
488
|
let provider = string_field(&meta, "provider")
|
|
495
489
|
.filter(|provider| !provider.trim().is_empty())
|
|
496
490
|
.unwrap_or_else(|| "pi".to_string());
|
|
497
|
-
require_explicit_grok_role_model(&meta, role_path, &provider)?;
|
|
498
|
-
require_explicit_cursor_role_model(&meta, role_path, &provider)?;
|
|
499
|
-
validate_pi_role_fields(&meta, role_path, &provider)?;
|
|
500
491
|
let is_pi = parse_canonical_provider(&provider) == Some(Provider::Pi);
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
} else {
|
|
507
|
-
resolve_model(&meta, team_meta, &provider)
|
|
508
|
-
};
|
|
492
|
+
validate_pi_role_fields(&meta, role_path, &provider)?;
|
|
493
|
+
let model = string_field(&meta, "model")
|
|
494
|
+
.filter(|value| !value.trim().is_empty())
|
|
495
|
+
.map(Value::Str)
|
|
496
|
+
.unwrap_or(Value::Null);
|
|
509
497
|
let auth_mode = string_field(&meta, "auth_mode")
|
|
510
498
|
.or_else(|| string_field(team_meta, "default_auth_mode"))
|
|
511
499
|
.unwrap_or_else(|| "subscription".to_string());
|
|
@@ -557,15 +545,11 @@ fn compile_role_agent_with_mode(
|
|
|
557
545
|
]),
|
|
558
546
|
),
|
|
559
547
|
];
|
|
560
|
-
if let Some(label) = string_field(&meta, "label") {
|
|
561
|
-
agent_items.insert(1, ("label", Value::Str(label)));
|
|
562
|
-
}
|
|
563
548
|
if let Some(profile) = string_field(&meta, "profile") {
|
|
564
549
|
agent_items.push(("profile", Value::Str(profile)));
|
|
565
550
|
}
|
|
566
|
-
//
|
|
567
|
-
//
|
|
568
|
-
// here so unsupported combinations fail at compile, not at runtime.
|
|
551
|
+
// Role effort wins; Pi uses only an explicit role value. Other providers may
|
|
552
|
+
// inherit TEAM provider_effort for Issue #238 compatibility.
|
|
569
553
|
let role_effort = match string_field(&meta, "effort") {
|
|
570
554
|
Some(raw) if !raw.trim().is_empty() => {
|
|
571
555
|
let value = raw.trim();
|
|
@@ -577,7 +561,6 @@ fn compile_role_agent_with_mode(
|
|
|
577
561
|
})?;
|
|
578
562
|
Some(parsed)
|
|
579
563
|
}
|
|
580
|
-
_ if is_pi => Some(ProviderEffort::Max),
|
|
581
564
|
_ => None,
|
|
582
565
|
};
|
|
583
566
|
let team_effort = match string_field(team_meta, "provider_effort") {
|
|
@@ -674,54 +657,6 @@ fn string_field(meta: &Value, key: &str) -> Option<String> {
|
|
|
674
657
|
.map(ToString::to_string)
|
|
675
658
|
}
|
|
676
659
|
|
|
677
|
-
/// 缺 model 时框架会填内建默认(grok 上是 grok-4),席位因此静默拿到
|
|
678
|
-
/// 与角色文件无关的模型和上下文窗口,而 argv 看起来完全正常。
|
|
679
|
-
/// 角色文件必须自己写死;内建默认、team 级默认、CLI 全局默认都是隐式来源。
|
|
680
|
-
fn require_explicit_grok_role_model(
|
|
681
|
-
meta: &Value,
|
|
682
|
-
path: &Path,
|
|
683
|
-
provider: &str,
|
|
684
|
-
) -> Result<(), ModelError> {
|
|
685
|
-
if parse_canonical_provider(provider) != Some(Provider::Grok) {
|
|
686
|
-
return Ok(());
|
|
687
|
-
}
|
|
688
|
-
if string_field(meta, "model").is_some_and(|value| !value.trim().is_empty()) {
|
|
689
|
-
return Ok(());
|
|
690
|
-
}
|
|
691
|
-
Err(ModelError::Validation(format!(
|
|
692
|
-
"{}: missing front matter field model. \
|
|
693
|
-
Without an explicit model the framework fills a built-in default (grok-4 for grok), \
|
|
694
|
-
so the seat silently gets a model and context window that are not in the role file, \
|
|
695
|
-
while argv still looks normal. The role file must name the model itself; \
|
|
696
|
-
built-in defaults, team-level defaults, and the Grok CLI global default are all implicit sources. Example:\n\
|
|
697
|
-
model: grok-4.6",
|
|
698
|
-
path.display()
|
|
699
|
-
)))
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
/// 缺 model 时框架曾填 builtin `sonnet-4-thinking`,本机 shim 还会剥掉该默认。
|
|
703
|
-
/// 角色必须自己写死;内建默认、team 级默认、CLI 全局默认都是隐式来源。
|
|
704
|
-
fn require_explicit_cursor_role_model(
|
|
705
|
-
meta: &Value,
|
|
706
|
-
path: &Path,
|
|
707
|
-
provider: &str,
|
|
708
|
-
) -> Result<(), ModelError> {
|
|
709
|
-
if parse_canonical_provider(provider) != Some(Provider::CursorAgent) {
|
|
710
|
-
return Ok(());
|
|
711
|
-
}
|
|
712
|
-
if string_field(meta, "model").is_some_and(|value| !value.trim().is_empty()) {
|
|
713
|
-
return Ok(());
|
|
714
|
-
}
|
|
715
|
-
Err(ModelError::Validation(format!(
|
|
716
|
-
"{}: missing front matter field model. \
|
|
717
|
-
Without an explicit model the framework fills a built-in default (sonnet-4-thinking for cursor_agent), \
|
|
718
|
-
so the seat silently gets a model that is not in the role file, while argv still looks normal. \
|
|
719
|
-
The role file must name the model itself; built-in defaults, team-level defaults, and the Cursor CLI default are all implicit sources. Example:\n\
|
|
720
|
-
model: sonnet-4-thinking",
|
|
721
|
-
path.display()
|
|
722
|
-
)))
|
|
723
|
-
}
|
|
724
|
-
|
|
725
660
|
fn validate_pi_role_fields(meta: &Value, path: &Path, provider: &str) -> Result<(), ModelError> {
|
|
726
661
|
if parse_canonical_provider(provider) != Some(Provider::Pi) {
|
|
727
662
|
return Ok(());
|
|
@@ -746,7 +681,7 @@ fn validate_pi_role_fields(meta: &Value, path: &Path, provider: &str) -> Result<
|
|
|
746
681
|
fn optional_dangerously_skip_permissions(meta: &Value) -> bool {
|
|
747
682
|
match meta.get("dangerously_skip_permissions") {
|
|
748
683
|
Some(Value::Bool(value)) => *value,
|
|
749
|
-
_ =>
|
|
684
|
+
_ => false,
|
|
750
685
|
}
|
|
751
686
|
}
|
|
752
687
|
|
|
@@ -830,35 +765,6 @@ fn py_int_value(value: &Value) -> Option<i64> {
|
|
|
830
765
|
}
|
|
831
766
|
}
|
|
832
767
|
|
|
833
|
-
fn resolve_model(role_meta: &Value, team_meta: &Value, provider: &str) -> Value {
|
|
834
|
-
if let Some(model) = string_field(role_meta, "model") {
|
|
835
|
-
return Value::Str(model);
|
|
836
|
-
}
|
|
837
|
-
if let Some(model) =
|
|
838
|
-
provider_model(team_meta, provider).or_else(|| string_field(team_meta, "default_model"))
|
|
839
|
-
{
|
|
840
|
-
return Value::Str(model);
|
|
841
|
-
}
|
|
842
|
-
if role_meta.get("profile").is_some() {
|
|
843
|
-
return Value::Null;
|
|
844
|
-
}
|
|
845
|
-
builtin_provider_model(provider)
|
|
846
|
-
.map(|m| Value::Str(m.to_string()))
|
|
847
|
-
.unwrap_or(Value::Null)
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
fn provider_model(team_meta: &Value, provider: &str) -> Option<String> {
|
|
851
|
-
let models = team_meta.get("provider_models")?;
|
|
852
|
-
let provider = parse_canonical_provider(provider)?;
|
|
853
|
-
provider_model_keys(provider)
|
|
854
|
-
.iter()
|
|
855
|
-
.find_map(|key| string_field(models, key))
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
fn builtin_provider_model(provider: &str) -> Option<&'static str> {
|
|
859
|
-
parse_canonical_provider(provider).and_then(wire_builtin_provider_model)
|
|
860
|
-
}
|
|
861
|
-
|
|
862
768
|
fn non_empty_trimmed(text: &str) -> Option<String> {
|
|
863
769
|
let trimmed = text.trim();
|
|
864
770
|
if trimmed.is_empty() {
|
|
@@ -418,7 +418,7 @@ pub(crate) fn add_agent_with_transport_force(
|
|
|
418
418
|
/// returns: 新席报告;成功后还要过快照的一致性校验
|
|
419
419
|
/// errors: 角色文件不存在先行返回 Compile;摘除、加回或一致性校验失败时按快照恢复并返回错误
|
|
420
420
|
/// ---
|
|
421
|
-
pub(
|
|
421
|
+
pub(crate) fn force_recreate_with_transport_locked(
|
|
422
422
|
run_workspace: &Path,
|
|
423
423
|
team_dir: &Path,
|
|
424
424
|
agent_id: &AgentId,
|
|
@@ -546,7 +546,7 @@ pub(super) fn add_agent_with_transport_at_paths(
|
|
|
546
546
|
}
|
|
547
547
|
|
|
548
548
|
#[allow(clippy::too_many_arguments)]
|
|
549
|
-
fn add_agent_with_transport_at_paths_locked(
|
|
549
|
+
pub(crate) fn add_agent_with_transport_at_paths_locked(
|
|
550
550
|
run_workspace: &Path,
|
|
551
551
|
team_dir: &Path,
|
|
552
552
|
agent_id: &AgentId,
|
|
@@ -683,6 +683,7 @@ fn add_agent_with_transport_at_paths_reserved(
|
|
|
683
683
|
&spec_path,
|
|
684
684
|
pre_spec_text.as_deref(),
|
|
685
685
|
None,
|
|
686
|
+
Some(&canonical_team_key),
|
|
686
687
|
agent_id,
|
|
687
688
|
"state_upsert_failed",
|
|
688
689
|
);
|
|
@@ -705,6 +706,7 @@ fn add_agent_with_transport_at_paths_reserved(
|
|
|
705
706
|
&spec_path,
|
|
706
707
|
pre_spec_text.as_deref(),
|
|
707
708
|
None,
|
|
709
|
+
Some(&canonical_team_key),
|
|
708
710
|
agent_id,
|
|
709
711
|
"start_agent_failed",
|
|
710
712
|
);
|
|
@@ -721,6 +723,7 @@ fn add_agent_with_transport_at_paths_reserved(
|
|
|
721
723
|
&spec_path,
|
|
722
724
|
pre_spec_text.as_deref(),
|
|
723
725
|
None,
|
|
726
|
+
Some(&canonical_team_key),
|
|
724
727
|
agent_id,
|
|
725
728
|
"start_agent_noop",
|
|
726
729
|
);
|
|
@@ -734,6 +737,7 @@ fn add_agent_with_transport_at_paths_reserved(
|
|
|
734
737
|
&spec_path,
|
|
735
738
|
pre_spec_text.as_deref(),
|
|
736
739
|
None,
|
|
740
|
+
Some(&canonical_team_key),
|
|
737
741
|
agent_id,
|
|
738
742
|
"added_agent_paused",
|
|
739
743
|
);
|