@team-agent/installer 0.5.103 → 0.5.104
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/spec.rs +1 -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_state.rs +4 -2
- 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 +1 -25
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -0
- package/crates/team-agent/src/lifecycle/profile_launch.rs +0 -12
- 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/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/package.json +4 -4
- package/skills/team-agent/SKILL.md +48 -33
- package/skills/team-agent/references/team-agent-operator.md +49 -526
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
|
@@ -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 },
|
|
@@ -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() {
|
|
@@ -390,7 +390,8 @@ fn starting_agent_entry(
|
|
|
390
390
|
let provider = meta
|
|
391
391
|
.get("provider")
|
|
392
392
|
.and_then(Value::as_str)
|
|
393
|
-
.
|
|
393
|
+
.filter(|provider| !provider.trim().is_empty())
|
|
394
|
+
.unwrap_or("pi");
|
|
394
395
|
let auth_mode = meta
|
|
395
396
|
.get("auth_mode")
|
|
396
397
|
.and_then(Value::as_str)
|
|
@@ -438,8 +439,9 @@ fn starting_agent_entry(
|
|
|
438
439
|
let meta_provider = meta
|
|
439
440
|
.get("provider")
|
|
440
441
|
.and_then(Value::as_str)
|
|
442
|
+
.filter(|provider| !provider.trim().is_empty())
|
|
441
443
|
.and_then(crate::provider::wire::parse_provider)
|
|
442
|
-
.unwrap_or(Provider::
|
|
444
|
+
.unwrap_or(Provider::Pi);
|
|
443
445
|
persist_effective_approval_policy_from_yaml_agent(obj, meta, meta_provider);
|
|
444
446
|
}
|
|
445
447
|
entry
|
|
@@ -59,12 +59,9 @@ pub fn clone_agent(
|
|
|
59
59
|
source_agent_id,
|
|
60
60
|
as_agent_id,
|
|
61
61
|
label,
|
|
62
|
-
None,
|
|
63
62
|
)?;
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
// the same; no leader-ceiling clamp). See role_source.rs for the removed
|
|
67
|
-
// clamp_materialized_role_to_leader.
|
|
63
|
+
// Materialization preserves the role body and legacy metadata; the compiler
|
|
64
|
+
// only maps supported fields, so ignored header keys remain behaviorally inert.
|
|
68
65
|
let added = add_agent(
|
|
69
66
|
&selected.run_workspace,
|
|
70
67
|
as_agent_id,
|
|
@@ -168,25 +168,21 @@ pub(crate) fn provider_effort_event_payload(
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
/// ---
|
|
171
|
-
/// purpose:
|
|
171
|
+
/// purpose: 仅在存在显式 effort override 时清除继承的 CLAUDE_EFFORT
|
|
172
172
|
/// params:
|
|
173
173
|
/// base: 已有的待清除变量名列表
|
|
174
|
-
///
|
|
175
|
-
///
|
|
176
|
-
///
|
|
177
|
-
///
|
|
178
|
-
///
|
|
179
|
-
///
|
|
180
|
-
/// (returns input unchanged). Kept as a belt-and-braces guard so a future
|
|
181
|
-
/// refactor that bypasses provider_env_unsets cannot silently drop the
|
|
182
|
-
/// scrub. The structural win is in `tmux_backend::shell_command` which now
|
|
183
|
-
/// filters env exports by env_unset (preventing inherited values from
|
|
184
|
-
/// re-introducing keys we just unset).
|
|
174
|
+
/// has_effort_override: 本次启动是否实际施加 effort
|
|
175
|
+
/// returns: 无显式 override 或非 claude 系时原样返回
|
|
176
|
+
/// ---
|
|
177
|
+
/// Preserve provider-native `CLAUDE_EFFORT` unless a supported explicit role
|
|
178
|
+
/// effort is applied; in that case prevent the inherited environment from
|
|
179
|
+
/// overriding the requested value.
|
|
185
180
|
pub(crate) fn extend_worker_env_unset_for_effort(
|
|
186
181
|
base: Vec<String>,
|
|
187
182
|
provider: Provider,
|
|
183
|
+
has_effort_override: bool,
|
|
188
184
|
) -> Vec<String> {
|
|
189
|
-
if !matches!(provider, Provider::Claude | Provider::ClaudeCode) {
|
|
185
|
+
if !has_effort_override || !matches!(provider, Provider::Claude | Provider::ClaudeCode) {
|
|
190
186
|
return base;
|
|
191
187
|
}
|
|
192
188
|
let mut out = base;
|
|
@@ -132,9 +132,8 @@ impl Drop for MaterializedRole {
|
|
|
132
132
|
/// state: 用于找源席的 dynamic_role_file,找不到时退到 team 目录下的同名 md
|
|
133
133
|
/// as_agent_id: 新席位名,写进 front matter 的 name
|
|
134
134
|
/// label: 非空时覆盖 front matter 的 role
|
|
135
|
-
/// agent_label: 非空时写入 front matter 的 label
|
|
136
135
|
/// returns: 物化结果,未调用 keep 时 Drop 会删掉该文件
|
|
137
|
-
/// errors:
|
|
136
|
+
/// errors: 源文件缺失时返回 Compile;目标已存在返回 RequirementUnmet;建目录或写盘失败返回 StatePersist
|
|
138
137
|
/// ---
|
|
139
138
|
pub(crate) fn materialize_latest_role(
|
|
140
139
|
run_workspace: &Path,
|
|
@@ -143,27 +142,10 @@ pub(crate) fn materialize_latest_role(
|
|
|
143
142
|
source_agent_id: &AgentId,
|
|
144
143
|
as_agent_id: &AgentId,
|
|
145
144
|
label: Option<&str>,
|
|
146
|
-
agent_label: Option<&str>,
|
|
147
145
|
) -> Result<MaterializedRole, LifecycleError> {
|
|
148
146
|
let source_path = resolve_role_source(run_workspace, team_dir, state, source_agent_id)?;
|
|
149
147
|
let (mut meta, body) = crate::compiler::read_front_matter(&source_path)
|
|
150
148
|
.map_err(|error| LifecycleError::Compile(error.to_string()))?;
|
|
151
|
-
let declared = meta
|
|
152
|
-
.get("name")
|
|
153
|
-
.and_then(Value::as_str)
|
|
154
|
-
.filter(|name| !name.is_empty())
|
|
155
|
-
.ok_or_else(|| {
|
|
156
|
-
LifecycleError::Compile(format!(
|
|
157
|
-
"source role file does not declare name: {}",
|
|
158
|
-
source_path.display()
|
|
159
|
-
))
|
|
160
|
-
})?;
|
|
161
|
-
if declared != source_agent_id.as_str() {
|
|
162
|
-
return Err(LifecycleError::Compile(format!(
|
|
163
|
-
"source role file declares name '{}' but source agent is '{}'",
|
|
164
|
-
declared, source_agent_id
|
|
165
|
-
)));
|
|
166
|
-
}
|
|
167
149
|
set_yaml_map_value(
|
|
168
150
|
&mut meta,
|
|
169
151
|
"name",
|
|
@@ -175,12 +157,6 @@ pub(crate) fn materialize_latest_role(
|
|
|
175
157
|
{
|
|
176
158
|
set_yaml_map_value(&mut meta, "role", Value::Str(label.to_string()))?;
|
|
177
159
|
}
|
|
178
|
-
if let Some(agent_label) = agent_label
|
|
179
|
-
.map(strip_label_quotes)
|
|
180
|
-
.filter(|value| !value.is_empty())
|
|
181
|
-
{
|
|
182
|
-
set_yaml_map_value(&mut meta, "label", Value::Str(agent_label.to_string()))?;
|
|
183
|
-
}
|
|
184
160
|
if let Some(role) = meta.get("role").and_then(Value::as_str).map(str::to_string) {
|
|
185
161
|
let role_without_quotes = strip_label_quotes(&role);
|
|
186
162
|
if !role_without_quotes.is_empty() && role_without_quotes != role {
|
|
@@ -255,6 +255,7 @@ pub(super) fn spawn_agents(
|
|
|
255
255
|
extend_worker_env_unset_for_effort(
|
|
256
256
|
profile_launch.env_unset.iter().cloned().collect(),
|
|
257
257
|
provider,
|
|
258
|
+
agent_effort.is_some(),
|
|
258
259
|
),
|
|
259
260
|
);
|
|
260
261
|
// The provider is the final typed value after profile overlays. Keep it
|
|
@@ -578,18 +578,6 @@ pub(crate) fn provider_env_unsets(provider: Provider, auth_mode: AuthMode) -> BT
|
|
|
578
578
|
unsets.insert("CLAUDE_CODE_ENTRYPOINT".to_string());
|
|
579
579
|
unsets.insert("CLAUDE_CODE_EXECPATH".to_string());
|
|
580
580
|
unsets.insert("CLAUDECODE".to_string());
|
|
581
|
-
// 0.4.x provider effort MVP step 9: CLAUDE_EFFORT carried from the
|
|
582
|
-
// launching shell would silently override the framework's effort
|
|
583
|
-
// decision (or, when no spec effort is configured, override the
|
|
584
|
-
// documented "provider default"). Scrubbing here is the single
|
|
585
|
-
// source — `extend_worker_env_unset_for_effort` is a thin alias
|
|
586
|
-
// that calls into this list. Applies to BOTH worker spawn AND
|
|
587
|
-
// leader spawn (leader_env_unset_for_provider also reads this).
|
|
588
|
-
// MVP scope: leader-effort is not configured yet, but if a
|
|
589
|
-
// launching shell already carries CLAUDE_EFFORT, the leader
|
|
590
|
-
// inheriting it would conflict with future leader-effort
|
|
591
|
-
// semantics. Scrub uniformly for predictability.
|
|
592
|
-
unsets.insert("CLAUDE_EFFORT".to_string());
|
|
593
581
|
if auth_mode == AuthMode::CompatibleApi {
|
|
594
582
|
unsets.insert("ANTHROPIC_API_KEY".to_string());
|
|
595
583
|
}
|
|
@@ -510,6 +510,7 @@ pub(super) fn spawn_agent_window(
|
|
|
510
510
|
crate::lifecycle::launch::extend_worker_env_unset_for_effort(
|
|
511
511
|
profile_launch.env_unset.iter().cloned().collect(),
|
|
512
512
|
provider,
|
|
513
|
+
restart_effort.is_some(),
|
|
513
514
|
),
|
|
514
515
|
);
|
|
515
516
|
// Keep the launcher-known typed provider independent from inherited
|
|
@@ -748,7 +749,6 @@ fn merge_command_context_fields(
|
|
|
748
749
|
"auth_mode",
|
|
749
750
|
"effort",
|
|
750
751
|
"profile",
|
|
751
|
-
"permission_mode",
|
|
752
752
|
// 0.5.66 bypass 单源:rehydrate 同步合入,restart 的 safety 构造读到新值。
|
|
753
753
|
"dangerously_skip_permissions",
|
|
754
754
|
] {
|
|
@@ -31,35 +31,23 @@ use team_agent::transport::test_support::OfflineTransport;
|
|
|
31
31
|
|
|
32
32
|
#[test]
|
|
33
33
|
#[serial(env)]
|
|
34
|
-
fn
|
|
34
|
+
fn cursor_role_missing_model_uses_provider_default() {
|
|
35
35
|
let ws = tmp_dir("cursor-no-model");
|
|
36
36
|
let team = write_role_team(&ws, "cursortm", "cursor_writer", None, None);
|
|
37
37
|
let transport = OfflineTransport::new();
|
|
38
|
-
|
|
38
|
+
quick_start_with_transport_in_workspace(
|
|
39
39
|
&ws,
|
|
40
40
|
&team,
|
|
41
41
|
None,
|
|
42
42
|
true,
|
|
43
43
|
Some("cursortm"),
|
|
44
44
|
&transport,
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
Err(error) => error.to_string(),
|
|
49
|
-
};
|
|
50
|
-
let lower = err.to_ascii_lowercase();
|
|
51
|
-
assert!(
|
|
52
|
-
lower.contains("model"),
|
|
53
|
-
"error must name the missing field; err={err}"
|
|
54
|
-
);
|
|
55
|
-
assert!(
|
|
56
|
-
lower.contains("built-in") || lower.contains("builtin") || err.contains("内建"),
|
|
57
|
-
"error must name the built-in default; err={err}"
|
|
58
|
-
);
|
|
45
|
+
)
|
|
46
|
+
.expect("Cursor may use its provider-native model default");
|
|
47
|
+
let argv = first_spawn_argv(&transport);
|
|
59
48
|
assert!(
|
|
60
|
-
|
|
61
|
-
"
|
|
62
|
-
transport.spawn_records()
|
|
49
|
+
!argv.iter().any(|arg| arg == "--model"),
|
|
50
|
+
"an omitted model must not fabricate a --model argument: {argv:?}"
|
|
63
51
|
);
|
|
64
52
|
}
|
|
65
53
|
|