@team-agent/installer 0.5.102 → 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.
Files changed (33) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/emit.rs +2 -1
  4. package/crates/team-agent/src/cli/models.rs +92 -226
  5. package/crates/team-agent/src/cli/spec.rs +2 -2
  6. package/crates/team-agent/src/compiler/tests.rs +19 -27
  7. package/crates/team-agent/src/compiler.rs +11 -105
  8. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +4 -2
  9. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +2 -5
  10. package/crates/team-agent/src/lifecycle/launch/identity.rs +9 -13
  11. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +37 -161
  12. package/crates/team-agent/src/lifecycle/launch/role_source.rs +1 -25
  13. package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -0
  14. package/crates/team-agent/src/lifecycle/profile_launch.rs +0 -12
  15. package/crates/team-agent/src/lifecycle/restart/avatar.rs +0 -1
  16. package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
  17. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +7 -19
  18. package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +8 -41
  19. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +14 -8
  20. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +8 -4
  21. package/crates/team-agent/src/lifecycle/worker_command_context.rs +4 -4
  22. package/crates/team-agent/src/messaging/results.rs +11 -42
  23. package/crates/team-agent/src/model/enums.rs +0 -9
  24. package/crates/team-agent/src/model/spec.rs +2 -2
  25. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +7 -8
  26. package/crates/team-agent/src/provider/adapters/grok.rs +5 -18
  27. package/crates/team-agent/src/provider/mod.rs +1 -0
  28. package/crates/team-agent/src/provider/model_catalog.rs +650 -0
  29. package/crates/team-agent/src/provider/tests/adapter.rs +15 -24
  30. package/crates/team-agent/src/provider/wire.rs +2 -29
  31. package/package.json +4 -4
  32. package/skills/team-agent/SKILL.md +48 -33
  33. package/skills/team-agent/references/team-agent-operator.md +49 -526
@@ -27,11 +27,10 @@ use serial_test::serial;
27
27
  use team_agent::lifecycle::quick_start_with_transport_in_workspace;
28
28
  use team_agent::transport::test_support::OfflineTransport;
29
29
 
30
- /// 1. grok 角色缺 model ⇒ 启动被拒,错误含 "model" 与「内建默认 / 隐式来源」。
31
- /// 40e91fba 上会成功启动(有 overlay / 有 spawn)⇒ 本条红。
30
+ /// An omitted Grok role model preserves the provider-native default.
32
31
  #[test]
33
32
  #[serial(env)]
34
- fn grok_role_missing_model_refuses_to_start() {
33
+ fn grok_role_missing_model_uses_provider_default() {
35
34
  let ws = tmp_dir("grok-no-model");
36
35
  let home = tmp_dir("grok-no-model-home");
37
36
  seed_grok_home(&home, Some(&ws));
@@ -46,51 +45,19 @@ fn grok_role_missing_model_refuses_to_start() {
46
45
  );
47
46
  let transport = OfflineTransport::new();
48
47
 
49
- let result = quick_start_with_transport_in_workspace(
48
+ quick_start_with_transport_in_workspace(
50
49
  &ws,
51
50
  &team,
52
51
  None,
53
52
  true,
54
53
  Some("grokteam"),
55
54
  &transport,
56
- );
57
-
58
- let err = match result {
59
- Ok(report) => panic!(
60
- "grok role without model must refuse to start; a built-in default \
61
- would silently pick the model; report={report:?}"
62
- ),
63
- Err(error) => error.to_string(),
64
- };
65
- let lower = err.to_ascii_lowercase();
66
- assert!(
67
- lower.contains("model"),
68
- "error must name the missing field; err={err}"
69
- );
70
- assert!(
71
- lower.contains("built-in") || lower.contains("builtin") || err.contains("内建"),
72
- "error must name the built-in default the framework would fill; err={err}"
73
- );
74
- assert!(
75
- lower.contains("implicit") || err.contains("隐式"),
76
- "error must reject every implicit source, not just one fallback; err={err}"
77
- );
78
- assert!(
79
- err.contains("model: grok-4.6") || err.contains("model: grok-4"),
80
- "error must show how to write the field; err={err}"
81
- );
82
- assert!(
83
- !err.to_ascii_lowercase().contains("worktree") && !err.contains("then retry"),
84
- "must not promise a remedy this version cannot honor; err={err}"
85
- );
86
- assert!(
87
- transport.spawn_records().is_empty(),
88
- "refusal must happen before spawn; records={:?}",
89
- transport.spawn_records()
90
- );
55
+ )
56
+ .expect("Grok may use its provider-native model default");
57
+ let argv = first_spawn_argv(&transport);
91
58
  assert!(
92
- !ws.join(".grok").join("config.toml").exists(),
93
- "must refuse before writing grok overlay; leftover .grok/config.toml means the seat started"
59
+ !argv.iter().any(|arg| arg == "--model"),
60
+ "an omitted model must not fabricate a --model argument: {argv:?}"
94
61
  );
95
62
  }
96
63
 
@@ -86,7 +86,8 @@ fn pi_role_ignores_tools_and_preserves_provider_defaults() {
86
86
 
87
87
  let defaults = valid_role_with("")
88
88
  .replace("model: team-agent/qwen3.8-27b\n", "")
89
- .replace("effort: medium\n", "");
89
+ .replace("effort: medium\n", "")
90
+ .replace("dangerously_skip_permissions: true\n", "");
90
91
  let team_meta = Value::Map(vec![
91
92
  (
92
93
  "provider_models".to_string(),
@@ -105,16 +106,21 @@ fn pi_role_ignores_tools_and_preserves_provider_defaults() {
105
106
  ),
106
107
  ]);
107
108
  let compiled = compile_pi_role_with_team(&defaults, &team_meta)
108
- .expect("Pi model and effort may use provider defaults");
109
+ .expect("omitted model and effort preserve provider-native defaults");
109
110
  assert_eq!(
110
111
  compiled.agent.get("model"),
111
- Some(&Value::Str("openai-codex/gpt-6-luna".to_string())),
112
- "omitted Pi model uses the Luna default rather than team metadata"
112
+ Some(&Value::Null),
113
+ "omitted Pi model stays unset rather than using team metadata"
113
114
  );
114
115
  assert_eq!(
115
116
  compiled.agent.get("effort"),
116
- Some(&Value::Str("max".to_string())),
117
- "omitted Pi effort uses the global max default rather than team metadata"
117
+ None,
118
+ "omitted Pi effort stays unset rather than using team metadata"
119
+ );
120
+ assert_eq!(
121
+ compiled.agent.get("dangerously_skip_permissions"),
122
+ Some(&Value::Bool(false)),
123
+ "omitted Pi bypass defaults to safe false"
118
124
  );
119
125
 
120
126
  let models = parse_pi_list_models_table(CATALOG).expect("catalog fixture");
@@ -162,10 +168,10 @@ fn pi_role_uses_standard_bypass_field_without_fabricating_argv() {
162
168
 
163
169
  let missing_ack = valid_role_with("").replace("dangerously_skip_permissions: true\n", "");
164
170
  let compiled = compile_pi_role(&missing_ack)
165
- .expect("omitted bypass metadata defaults to true");
171
+ .expect("omitted bypass metadata defaults to false");
166
172
  assert_eq!(
167
173
  compiled.agent.get("dangerously_skip_permissions"),
168
- Some(&Value::Bool(true))
174
+ Some(&Value::Bool(false))
169
175
  );
170
176
  }
171
177
 
@@ -216,18 +216,22 @@ fn pi_leader_and_teammate_body(hermetic: &HermeticTestEnv) {
216
216
 
217
217
  std::fs::write(
218
218
  &role,
219
- "---\nname: worker-a\nrole: developer\nprovider: pi\nauth_mode: subscription\ntools:\n - mcp_team\ndangerously_skip_permissions: true\n---\nworker contract\n",
219
+ "---\nname: worker-a\nrole: developer\nprovider: pi\nauth_mode: subscription\ntools:\n - mcp_team\n---\nworker contract\n",
220
220
  )
221
221
  .expect("write provider-default teammate role");
222
222
  let defaults = compile_role_agent(&role, &Value::Map(Vec::new()), "/workspace")
223
- .expect("provider: pi teammate may use Pi model and effort defaults");
223
+ .expect("provider: pi teammate may preserve provider-native defaults");
224
224
  assert_eq!(
225
225
  defaults.agent.get("model"),
226
- Some(&Value::Str("openai-codex/gpt-6-luna".to_string()))
226
+ Some(&Value::Null)
227
227
  );
228
228
  assert_eq!(
229
229
  defaults.agent.get("effort"),
230
- Some(&Value::Str("max".to_string()))
230
+ None
231
+ );
232
+ assert_eq!(
233
+ defaults.agent.get("dangerously_skip_permissions"),
234
+ Some(&Value::Bool(false))
231
235
  );
232
236
  std::fs::remove_dir_all(root).expect("remove role fixture");
233
237
 
@@ -58,7 +58,7 @@ pub(crate) struct WorkerCommandAgent {
58
58
  system_prompt_file: Option<String>,
59
59
  output_contract_format: Option<String>,
60
60
  communication_mode: CommunicationMode,
61
- /// Missing `dangerously_skip_permissions` defaults to true; an explicit false remains respected.
61
+ /// Missing `dangerously_skip_permissions` defaults to false; only explicit true enables bypass.
62
62
  dangerously_skip_permissions: bool,
63
63
  }
64
64
 
@@ -110,9 +110,9 @@ impl WorkerCommandAgent {
110
110
  .get("communication_mode")
111
111
  .and_then(crate::model::yaml::Value::as_str),
112
112
  )?,
113
- dangerously_skip_permissions: !matches!(
113
+ dangerously_skip_permissions: matches!(
114
114
  agent.get("dangerously_skip_permissions"),
115
- Some(crate::model::yaml::Value::Bool(false))
115
+ Some(crate::model::yaml::Value::Bool(true))
116
116
  ),
117
117
  })
118
118
  }
@@ -167,7 +167,7 @@ impl WorkerCommandAgent {
167
167
  dangerously_skip_permissions: agent
168
168
  .get("dangerously_skip_permissions")
169
169
  .and_then(serde_json::Value::as_bool)
170
- .unwrap_or(true),
170
+ .unwrap_or(false),
171
171
  })
172
172
  }
173
173
  }
@@ -1046,25 +1046,6 @@ fn leader_result_presentation(task_id: &str) -> super::presentation::Presentatio
1046
1046
  )
1047
1047
  }
1048
1048
 
1049
- const LEADER_NOTIFICATION_LIMIT_BYTES: usize = 160;
1050
-
1051
- fn truncate_utf8(text: &str, max_bytes: usize) -> String {
1052
- if text.len() <= max_bytes {
1053
- return text.to_string();
1054
- }
1055
- let suffix = "...";
1056
- let budget = max_bytes.saturating_sub(suffix.len());
1057
- let mut end = 0;
1058
- for (index, ch) in text.char_indices() {
1059
- let next = index + ch.len_utf8();
1060
- if next > budget {
1061
- break;
1062
- }
1063
- end = next;
1064
- }
1065
- format!("{}{}", &text[..end], suffix)
1066
- }
1067
-
1068
1049
  fn format_report_result_leader_summary(
1069
1050
  result_id: &str,
1070
1051
  task_id: &str,
@@ -1075,19 +1056,8 @@ fn format_report_result_leader_summary(
1075
1056
  let summary = envelope
1076
1057
  .get("summary")
1077
1058
  .and_then(serde_json::Value::as_str)
1078
- .unwrap_or("")
1079
- .lines()
1080
- .next()
1081
- .unwrap_or("")
1082
- .chars()
1083
- .filter(|ch| !ch.is_control())
1084
- .collect::<String>();
1085
- truncate_utf8(
1086
- &format!(
1087
- "Task {task_id} reported {status} from {agent_id}: {summary}; Result id: {result_id}"
1088
- ),
1089
- LEADER_NOTIFICATION_LIMIT_BYTES,
1090
- )
1059
+ .unwrap_or("");
1060
+ format!("Task {task_id} reported {status} from {agent_id}: {summary}; Result id: {result_id}")
1091
1061
  }
1092
1062
 
1093
1063
  fn auto_finalize_result(
@@ -1441,18 +1411,17 @@ mod tests {
1441
1411
  }
1442
1412
 
1443
1413
  #[test]
1444
- fn leader_result_summary_is_single_line_and_byte_bounded() {
1445
- let envelope = serde_json::json!({"summary": "中".repeat(4096)});
1414
+ fn leader_result_summary_preserves_long_multiline_text_verbatim() {
1415
+ let source = format!("首行\nbranch: fix/keyboard-receive-and-compose\ncommit: {}", "a1b2c3d4e5f67890".repeat(24));
1416
+ let envelope = serde_json::json!({"summary": source});
1446
1417
  let summary = super::format_report_result_leader_summary(
1447
- "res-1",
1448
- "task-1",
1449
- "worker",
1450
- "success",
1451
- &envelope,
1418
+ "res-1", "task-1", "worker", "success", &envelope,
1452
1419
  );
1453
- assert!(summary.len() <= 160);
1454
- assert!(!summary.contains('\n'));
1455
- assert!(summary.ends_with("..."));
1420
+ let source = envelope["summary"].as_str().unwrap();
1421
+ assert_eq!(summary, format!("Task task-1 reported success from worker: {source}; Result id: res-1"));
1422
+ assert!(summary.len() > 160);
1423
+ assert!(summary.contains("\nbranch: fix/keyboard-receive-and-compose\ncommit: "));
1424
+ assert!(summary.contains("a1b2c3d4e5f67890".repeat(24).as_str()));
1456
1425
  }
1457
1426
 
1458
1427
  #[test]
@@ -213,15 +213,6 @@ pub enum Backend {
213
213
  Pty,
214
214
  }
215
215
 
216
- /// agent permission mode(schema `agent.permission_mode`;compiler 恒发 `restricted`)。
217
- #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
218
- #[serde(rename_all = "snake_case")]
219
- pub enum PermissionMode {
220
- Restricted,
221
- Ask,
222
- Trusted,
223
- }
224
-
225
216
  /// tmux pane 存活态(`state.py:336-341`)。**`Unknown` 既不可当 dead 也不可当 live**
226
217
  /// (owner-gate `state.py:382` 用 `!= LIVE`)—— 穷尽 match,不 fallthrough(§11)。
227
218
  #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
@@ -1136,7 +1136,7 @@ mod tests {
1136
1136
  }
1137
1137
 
1138
1138
  // 0.5.66 bypass 单源 §4.1:7 个生命周期入口共享编译后的 spec。
1139
- // 缺失 role frontmatter 值时由编译器注入 true;此处锁定公共编译入口的默认结果。
1139
+ // 缺失 role frontmatter 值时编译为安全默认 false;此处锁定公共编译入口的结果。
1140
1140
  #[test]
1141
1141
  fn test_all_7_triggers_use_compiled_default_bypass() {
1142
1142
  let team = std::env::temp_dir().join(format!(
@@ -1165,7 +1165,7 @@ mod tests {
1165
1165
  .expect("compiled spec has a worker");
1166
1166
  assert_eq!(
1167
1167
  agent.get("dangerously_skip_permissions"),
1168
- Some(&Yaml::Bool(true))
1168
+ Some(&Yaml::Bool(false))
1169
1169
  );
1170
1170
  let _ = std::fs::remove_dir_all(&team);
1171
1171
  }
@@ -11,9 +11,9 @@
11
11
  //! maturity: wired
12
12
  //! ---
13
13
  //!
14
- //! Cursor `agent` CLI(与 `cursor-agent` 同二进制)。主路径与
15
- //! `.team/scripts/cursor_seat.sh` 实测一致:
16
- //! `--trust --sandbox disabled --workspace <物理路径> [--force] [--model]`
14
+ //! Cursor `agent` CLI(与 `cursor-agent` 同二进制)。默认不跳过信任或沙箱设置。
15
+ //! 显式 bypass 时才使用 `.team/scripts/cursor_seat.sh` 的 trusted/unsandboxed 选项:
16
+ //! `--workspace <物理路径> [--trust --sandbox disabled --force] [--model]`
17
17
  //! Role 不入 argv,写 `<workspace>/.cursor/rules/*.mdc` + `alwaysApply: true`。
18
18
  //! MCP 无 `--mcp-config`;身份必须写进 `.cursor/mcp.json` 的 env 表
19
19
  //! (cursor 不把父进程 TEAM_AGENT_* 传给 MCP 子进程)。
@@ -57,14 +57,13 @@ pub(crate) fn cursor_agent_base_command(
57
57
  effort: Option<crate::model::enums::ProviderEffort>,
58
58
  ) -> Result<Vec<String>, ProviderError> {
59
59
  let mut argv = vec!["agent".to_string()];
60
- // --trust 跳过 Workspace Trust 闸(已实测)。无此 flag 会停在 Do you trust。
61
- argv.push("--trust".to_string());
62
60
  if dangerously_skip_permissions {
61
+ // Explicit bypass also opts into Cursor's trusted, unsandboxed launch.
62
+ argv.push("--trust".to_string());
63
+ argv.push("--sandbox".to_string());
64
+ argv.push("disabled".to_string());
63
65
  argv.push("--force".to_string());
64
66
  }
65
- // 与 cursor_seat.sh 主路径一致。沙箱实际隔离面未再拆,但 flag 本身已实测。
66
- argv.push("--sandbox".to_string());
67
- argv.push("disabled".to_string());
68
67
  if let Some(model) = model {
69
68
  argv.push("--model".to_string());
70
69
  argv.push(model.to_string());
@@ -1,7 +1,7 @@
1
1
  //!
2
2
  //! purpose: Grok CLI argv + permission deny 映射;MCP 不走 CLI flag
3
3
  //! contract: launch 写 `<cwd>/.grok/config.toml`;未登录 / 目录未信任必须拒绝启动。
4
- //! 角色缺 model 必须拒绝启动(禁止内建 / team / CLI 任何隐式默认)。
4
+ //! 未指定 model 时不注入 `--model`,由 Grok CLI 使用原生默认。
5
5
  //! 未知工具映射记成 Unsupported,不发明 deny 名
6
6
  //! mcp_injection: dir_scoped —— 每席必须独立工作目录;同 cwd 多席会静默串台,故拒绝。
7
7
  //! 对照 claude/codex 是 argv 作用域(`--mcp-config`),同目录多席没有这个问题。
@@ -71,23 +71,10 @@ pub(crate) fn grok_base_command(
71
71
  if dangerously_skip_permissions {
72
72
  argv.push("--always-approve".to_string());
73
73
  }
74
- let model = match model.map(str::trim).filter(|value| !value.is_empty()) {
75
- Some(model) => model,
76
- None => {
77
- return Err(ProviderError::Command(
78
- "grok launch requires an explicit model. \
79
- Without an explicit model the framework fills a built-in default (grok-4 for grok), \
80
- so the seat silently gets a model and context window that are not in the role file, \
81
- while argv still looks normal. The role file must name the model itself; \
82
- built-in defaults, team-level defaults, and the Grok CLI global default are all implicit sources. \
83
- Set the role front-matter field, for example:\n\
84
- model: grok-4.6"
85
- .to_string(),
86
- ));
87
- }
88
- };
89
- argv.push("--model".to_string());
90
- argv.push(model.to_string());
74
+ if let Some(model) = model.map(str::trim).filter(|value| !value.is_empty()) {
75
+ argv.push("--model".to_string());
76
+ argv.push(model.to_string());
77
+ }
91
78
  if let Some(effort) = effort {
92
79
  // Grok accepts `--effort` as alias for `--reasoning-effort`.
93
80
  argv.push("--effort".to_string());
@@ -51,6 +51,7 @@ pub(crate) mod adapters;
51
51
  pub mod approvals;
52
52
  pub mod classify;
53
53
  pub mod faults;
54
+ pub mod model_catalog;
54
55
  pub mod session;
55
56
  pub mod session_scan;
56
57
  pub mod startup_prompt;