@team-agent/installer 0.5.69 → 0.5.71

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.
@@ -3,8 +3,9 @@ use std::path::{Path, PathBuf};
3
3
  use std::sync::atomic::{AtomicU32, Ordering};
4
4
 
5
5
  use crate::lifecycle::launch::pi_mcp::{
6
- parse_pi_list_models_table, validate_pi_adapter_identity, validate_pi_executable_chain,
7
- validate_pi_wrapper_source, write_pi_wrapper, PiAdapterIdentity, PiExecutableChain,
6
+ parse_pi_list_models_table, pi_seat_paths, resolve_pi_executable_chain,
7
+ validate_pi_adapter_identity, validate_pi_executable_chain, validate_pi_wrapper_source,
8
+ write_pi_wrapper, write_pi_wrapper_with_publish, PiAdapterIdentity, PiExecutableChain,
8
9
  PiExecutableFileType, PiWrapperRequest,
9
10
  };
10
11
  use crate::provider::McpConfig;
@@ -18,15 +19,20 @@ const INDEX_DIGEST: &str = "16d260ac25b66346baab6ecef76680324336953bafc7be8cf95b
18
19
  const CATALOG_DIGEST: &str = "726cedb6c3f6fe80a0d7b98918d8ed5063695e01f510a48f46c4bad5daab49fe";
19
20
  static NEXT_ROOT: AtomicU32 = AtomicU32::new(0);
20
21
  const PI_WRAPPER_CHILD: &str = "TEAM_AGENT_TEST_PI_WRAPPER_CHILD";
21
- const PI_WRAPPER_NEGATIVE_CHILD: &str = "TEAM_AGENT_TEST_PI_WRAPPER_NEGATIVE_CHILD";
22
+ const PI_WRAPPER_AMBIENT_CHILD: &str = "TEAM_AGENT_TEST_PI_WRAPPER_AMBIENT_CHILD";
23
+ const PI_SYMLINK_CHILD: &str = "TEAM_AGENT_TEST_PI_SYMLINK_CHILD";
22
24
  const PI_WRAPPER_PARENT_PID: &str = "TEAM_AGENT_TEST_PI_WRAPPER_PARENT_PID";
23
25
  const PI_WRAPPER_TEST: &str = concat!(
24
26
  "lifecycle::tests::pi_executable_mcp_red::",
25
27
  "pi_wrapper_is_atomic_per_seat_and_embeds_exact_candidate"
26
28
  );
27
- const PI_WRAPPER_NEGATIVE_TEST: &str = concat!(
29
+ const PI_WRAPPER_AMBIENT_TEST: &str = concat!(
28
30
  "lifecycle::tests::pi_executable_mcp_red::",
29
- "pi_wrapper_import_failure_has_no_ambient_merge_fallback"
31
+ "pi_wrapper_runtime_registration_does_not_copy_ambient_config"
32
+ );
33
+ const PI_SYMLINK_TEST: &str = concat!(
34
+ "lifecycle::tests::pi_executable_mcp_red::",
35
+ "pi_standard_npm_symlink_is_a_verified_launch_entry"
30
36
  );
31
37
 
32
38
  fn run_process_isolated(marker: &str, test_name: &str, body: impl FnOnce()) {
@@ -165,6 +171,60 @@ fn pi_executable_chain_freezes_wrapper_real_binary_catalog_and_plugin_identity()
165
171
  std::fs::remove_dir_all(root).expect("remove executable chain fixture");
166
172
  }
167
173
 
174
+ #[test]
175
+ fn pi_standard_npm_symlink_is_a_verified_launch_entry() {
176
+ run_process_isolated(PI_SYMLINK_CHILD, PI_SYMLINK_TEST, || {
177
+ assert_isolated_child();
178
+ let root = temp_root("npm-symlink");
179
+ let bin = root.join("bin");
180
+ let later_bin = root.join("later-bin");
181
+ let package_root = root.join("pi-mcp-adapter");
182
+ std::fs::create_dir_all(&bin).expect("create bin");
183
+ std::fs::create_dir_all(&later_bin).expect("create later bin");
184
+ std::fs::create_dir_all(&package_root).expect("create adapter root");
185
+ let real = root.join("cli.js");
186
+ let script = format!(
187
+ "#!/bin/sh\ncase \"$1\" in\n --version) printf '0.84.4\\n' ;;\n --list-models) printf 'provider model\\nteam-agent qwen3.8-27b\\n' ;;\n list) printf 'npm:pi-mcp-adapter\\n{}\\n' ;;\n *) exit 64 ;;\nesac\n",
188
+ package_root.display()
189
+ );
190
+ std::fs::write(&real, &script).expect("write protocol-capable Pi entry");
191
+ std::fs::set_permissions(&real, std::fs::Permissions::from_mode(0o755))
192
+ .expect("make Pi entry executable");
193
+ std::os::unix::fs::symlink(&real, bin.join("pi")).expect("create npm-style symlink");
194
+ std::fs::write(later_bin.join("pi"), script).expect("write later Pi wrapper");
195
+ std::fs::set_permissions(later_bin.join("pi"), std::fs::Permissions::from_mode(0o755))
196
+ .expect("make later Pi wrapper executable");
197
+ std::fs::write(
198
+ package_root.join("package.json"),
199
+ br#"{"name":"pi-mcp-adapter","version":"2.30.0","pi":{"extensions":["./index.ts"]}}"#,
200
+ )
201
+ .expect("write adapter package");
202
+ std::fs::write(
203
+ package_root.join("index.ts"),
204
+ b"export const createMcpAdapter = () => {};\n",
205
+ )
206
+ .expect("write adapter entry");
207
+ unsafe {
208
+ std::env::set_var(
209
+ "PATH",
210
+ std::env::join_paths([&bin, &later_bin]).expect("join fixture PATH"),
211
+ );
212
+ }
213
+
214
+ let (chain, models) = resolve_pi_executable_chain()
215
+ .expect("the first PATH Pi entry must define direct Pi behavior");
216
+ assert_eq!(chain.path_entry, bin.join("pi"));
217
+ assert_eq!(chain.path_entry_type, PiExecutableFileType::Symlink);
218
+ assert_eq!(chain.launch_executable, bin.join("pi"));
219
+ assert_eq!(
220
+ chain.real_binary,
221
+ std::fs::canonicalize(&real).expect("canonical real Pi entry")
222
+ );
223
+ assert_eq!(models, ["team-agent/qwen3.8-27b"]);
224
+ std::fs::remove_dir_all(root).expect("remove symlink fixture");
225
+ });
226
+ }
227
+
168
228
  #[test]
169
229
  fn pi_adapter_detector_requires_exact_2_30_0_entry_and_digest() {
170
230
  let root = temp_root("adapter-identity");
@@ -234,18 +294,37 @@ fn pi_wrapper_is_atomic_body(hermetic: &HermeticTestEnv) {
234
294
  );
235
295
 
236
296
  let source = std::fs::read_to_string(&written).expect("read wrapper");
237
- assert!(source.contains(adapter.index_ts.to_string_lossy().as_ref()));
238
297
  assert!(source.contains(candidate.to_string_lossy().as_ref()));
239
298
  assert!(source.contains("TEAM_AGENT_ID") && source.contains("worker-a"));
240
299
  assert!(source.contains("TEAM_AGENT_OWNER_TEAM_ID") && source.contains("team-a"));
241
- assert!(source.contains("lifecycle") && source.contains("lazy"));
242
- assert!(source.contains("directTools") && source.contains("false"));
243
- assert!(source.contains("toolPrefix") && source.contains("server"));
300
+ assert!(source.contains("pi-mcp-adapter:runtime-register:v1"));
301
+ assert!(source.contains("session_start") && source.contains("session_shutdown"));
244
302
  assert!(source.contains("includeTools"));
245
303
  assert!(source.contains("send_message") && source.contains("report_result"));
304
+ assert!(!source.contains("createMcpAdapter"));
305
+ assert!(!source.contains("directTools"));
306
+ assert!(!source.contains("toolPrefix"));
307
+ assert!(!source.contains("\"lifecycle\""));
246
308
  assert!(!source.contains("command: \"team-agent\""));
247
309
  assert!(!source.contains("--mcp-config"));
248
310
 
311
+ let other_destination = workspace.join(".team/runtime/pi/team-a/worker-b/team-mcp.ts");
312
+ write_pi_wrapper(PiWrapperRequest {
313
+ destination: &other_destination,
314
+ adapter: &adapter,
315
+ candidate_executable: &candidate,
316
+ mcp_config: &mcp_config(&candidate, &workspace, "worker-b"),
317
+ team_id: "team-a",
318
+ agent_id: "worker-b",
319
+ workspace: &workspace,
320
+ include_tools: &["send_message", "report_result"],
321
+ })
322
+ .expect("write second seat wrapper");
323
+ let other_source = std::fs::read_to_string(&other_destination).expect("read second wrapper");
324
+ assert!(source.contains("worker-a") && !source.contains("worker-b"));
325
+ assert!(other_source.contains("worker-b") && !other_source.contains("worker-a"));
326
+ assert_ne!(written, other_destination);
327
+
249
328
  let entries = std::fs::read_dir(destination.parent().expect("wrapper parent"))
250
329
  .expect("list wrapper parent")
251
330
  .map(|entry| entry.expect("entry").file_name())
@@ -259,8 +338,126 @@ fn pi_wrapper_is_atomic_body(hermetic: &HermeticTestEnv) {
259
338
  }
260
339
 
261
340
  #[test]
262
- fn pi_wrapper_import_failure_has_no_ambient_merge_fallback() {
263
- run_process_isolated(PI_WRAPPER_NEGATIVE_CHILD, PI_WRAPPER_NEGATIVE_TEST, || {
341
+ fn pi_leader_wrapper_materializes_from_empty_unicode_wsl_workspace() {
342
+ let root = temp_root("empty-unicode-leader");
343
+ let workspace = root.join("mnt/f/测试基础设施");
344
+ let candidate = root.join("candidate/team-agent");
345
+ std::fs::create_dir_all(&workspace).expect("create empty Unicode workspace");
346
+ std::fs::create_dir_all(candidate.parent().expect("candidate parent"))
347
+ .expect("create candidate parent");
348
+ std::fs::write(&candidate, b"candidate").expect("write candidate fixture");
349
+ assert!(
350
+ !workspace.join(".team").exists(),
351
+ "fixture must begin without a .team directory"
352
+ );
353
+
354
+ let paths = pi_seat_paths(&workspace, "current", "leader");
355
+ let adapter = adapter_identity(&root);
356
+ let written = write_pi_wrapper(PiWrapperRequest {
357
+ destination: &paths.wrapper,
358
+ adapter: &adapter,
359
+ candidate_executable: &candidate,
360
+ mcp_config: &mcp_config(&candidate, &workspace, "leader"),
361
+ team_id: "current",
362
+ agent_id: "leader",
363
+ workspace: &workspace,
364
+ include_tools: &["assign_task", "send_message"],
365
+ })
366
+ .expect("materialize Pi leader wrapper from an empty Unicode workspace");
367
+
368
+ assert_eq!(written, paths.wrapper);
369
+ assert!(written.is_file(), "published leader wrapper must exist");
370
+ assert!(
371
+ paths.runtime_root.is_dir(),
372
+ "leader runtime root must be created recursively"
373
+ );
374
+ let source = std::fs::read_to_string(&written).expect("read complete leader wrapper");
375
+ validate_pi_wrapper_source(&source, &adapter, &candidate)
376
+ .expect("published leader wrapper must be complete and valid");
377
+ assert!(source.contains("TEAM_AGENT_ID") && source.contains("leader"));
378
+ assert!(source.contains("TEAM_AGENT_OWNER_TEAM_ID") && source.contains("current"));
379
+ std::fs::remove_dir_all(root).expect("remove Unicode leader fixture");
380
+ }
381
+
382
+ #[test]
383
+ fn pi_wrapper_permissions_are_finalized_before_atomic_publish() {
384
+ let source = include_str!("../launch/pi_mcp.rs");
385
+ let write_start = source
386
+ .find("pub(crate) fn write_pi_wrapper")
387
+ .expect("write_pi_wrapper source");
388
+ let write_source = &source[write_start..];
389
+ let permissions = write_source
390
+ .find("file.set_permissions")
391
+ .expect("wrapper permissions must use the open temp-file handle");
392
+ let publish = write_source
393
+ .find("publish(&temp, request.destination)")
394
+ .expect("atomic wrapper publish");
395
+
396
+ assert!(
397
+ permissions < publish,
398
+ "DrvFS may not resolve the destination immediately after rename; finalize mode through the open temp-file handle before publishing"
399
+ );
400
+ assert!(
401
+ !write_source.contains("std::fs::set_permissions(request.destination"),
402
+ "atomic publish must not require a second destination-path lookup"
403
+ );
404
+ }
405
+
406
+ #[test]
407
+ fn pi_wrapper_publish_needs_no_final_path_metadata_lookup() {
408
+ let root = temp_root("publish-path-enoent");
409
+ let workspace = root.join("mnt/f/测试基础设施");
410
+ let candidate = root.join("candidate/team-agent");
411
+ std::fs::create_dir_all(&workspace).expect("create Unicode workspace");
412
+ std::fs::create_dir_all(candidate.parent().expect("candidate parent"))
413
+ .expect("create candidate parent");
414
+ std::fs::write(&candidate, b"candidate").expect("write candidate fixture");
415
+
416
+ let paths = pi_seat_paths(&workspace, "current", "leader");
417
+ let published_parent = paths.runtime_root.clone();
418
+ let hidden_parent = published_parent.with_file_name("leader-after-rename");
419
+ let adapter = adapter_identity(&root);
420
+ write_pi_wrapper_with_publish(
421
+ PiWrapperRequest {
422
+ destination: &paths.wrapper,
423
+ adapter: &adapter,
424
+ candidate_executable: &candidate,
425
+ mcp_config: &mcp_config(&candidate, &workspace, "leader"),
426
+ team_id: "current",
427
+ agent_id: "leader",
428
+ workspace: &workspace,
429
+ include_tools: &["assign_task", "send_message"],
430
+ },
431
+ |temp, destination| {
432
+ std::fs::rename(temp, destination)?;
433
+ std::fs::rename(&published_parent, &hidden_parent)?;
434
+ assert_eq!(
435
+ std::fs::metadata(destination)
436
+ .expect_err("simulate DrvFS final-path metadata ENOENT after rename")
437
+ .kind(),
438
+ std::io::ErrorKind::NotFound
439
+ );
440
+ Ok(())
441
+ },
442
+ )
443
+ .expect("successful publish must not perform final-path metadata operations");
444
+
445
+ let published = hidden_parent.join("team-mcp.ts");
446
+ assert!(published.is_file(), "rename published a complete wrapper");
447
+ assert_eq!(
448
+ std::fs::metadata(&published)
449
+ .expect("published wrapper metadata through converged path")
450
+ .permissions()
451
+ .mode()
452
+ & 0o777,
453
+ 0o600
454
+ );
455
+ std::fs::remove_dir_all(root).expect("remove publish-path fixture");
456
+ }
457
+
458
+ #[test]
459
+ fn pi_wrapper_runtime_registration_does_not_copy_ambient_config() {
460
+ run_process_isolated(PI_WRAPPER_AMBIENT_CHILD, PI_WRAPPER_AMBIENT_TEST, || {
264
461
  let hermetic = HermeticTestEnv::enter("pi-wrapper-negative-send");
265
462
  assert_isolated_child();
266
463
  pi_wrapper_import_failure_body(&hermetic);
@@ -293,24 +490,13 @@ fn pi_wrapper_import_failure_body(hermetic: &HermeticTestEnv) {
293
490
  workspace: &workspace,
294
491
  include_tools: &["send_message", "report_result"],
295
492
  })
296
- .expect("isolated wrapper ignores ambient config");
493
+ .expect("runtime wrapper leaves ambient config to direct Pi");
297
494
  let source = std::fs::read_to_string(&destination).expect("wrapper source");
298
495
  assert!(!source.contains("ambient-evil"));
299
496
  validate_pi_wrapper_source(&source, &adapter, &candidate)
300
- .expect("exact isolated import and candidate");
301
-
302
- let broken = source.replace(
303
- adapter.index_ts.to_string_lossy().as_ref(),
304
- "/missing/pi-mcp-adapter/index.ts",
305
- );
306
- let error = validate_pi_wrapper_source(&broken, &adapter, &candidate)
307
- .expect_err("broken import must refuse");
308
- let text = error.to_string();
309
- assert!(
310
- text.contains("import") || text.contains("adapter"),
311
- "got {text}"
312
- );
313
- assert!(!broken.contains("--mcp-config"));
497
+ .expect("runtime registration and exact candidate");
498
+ assert!(!source.contains("createMcpAdapter"));
499
+ assert!(!source.contains("--mcp-config"));
314
500
 
315
501
  let missing_proxy_tools = source.replace("\"includeTools\"", "\"missingTools\"");
316
502
  validate_pi_wrapper_source(&missing_proxy_tools, &adapter, &candidate)
@@ -56,8 +56,8 @@ fn resume_argv(path: &Path) -> Result<Vec<String>, ProviderError> {
56
56
  build_pi_command_argv(PiCommandRequest {
57
57
  executable: Path::new("/verified/pi"),
58
58
  extension: Path::new("/workspace/.team/runtime/pi/team-a/worker-a/team-mcp.ts"),
59
- model: "team-agent/qwen3.8-27b",
60
- effort: ProviderEffort::Medium,
59
+ model: Some("team-agent/qwen3.8-27b"),
60
+ effort: Some(ProviderEffort::Medium),
61
61
  system_prompt: "worker contract",
62
62
  tool_categories: &["mcp_team"],
63
63
  session_dir: Path::new("/workspace/.team/runtime/pi/team-a/worker-a/sessions"),
@@ -3,14 +3,13 @@
3
3
  //! contract:
4
4
  //! provides:
5
5
  //! - name: build_pi_command_argv
6
- //! what: 仅从已验证输入构造隔离的 regular-TUI Pi argv
6
+ //! what: 仅从已验证输入构造保留 direct Pi 配置的 Team Agent argv
7
7
  //! boundary:
8
8
  //! - 不发现 executable、catalog、adapter 或 MCP 配置
9
9
  //! - 除 resume exact backing 存在性外不读文件;不写文件、不启动进程
10
10
  //! maturity: wired
11
11
  //! ---
12
12
 
13
- use std::collections::BTreeSet;
14
13
  use std::path::Path;
15
14
 
16
15
  use crate::model::enums::ProviderEffort;
@@ -47,8 +46,8 @@ pub(crate) enum PiSessionSelector<'a> {
47
46
  pub(crate) struct PiCommandRequest<'a> {
48
47
  pub executable: &'a Path,
49
48
  pub extension: &'a Path,
50
- pub model: &'a str,
51
- pub effort: ProviderEffort,
49
+ pub model: Option<&'a str>,
50
+ pub effort: Option<ProviderEffort>,
52
51
  pub system_prompt: &'a str,
53
52
  pub tool_categories: &'a [&'a str],
54
53
  pub session_dir: &'a Path,
@@ -57,7 +56,7 @@ pub(crate) struct PiCommandRequest<'a> {
57
56
  }
58
57
 
59
58
  /// ---
60
- /// purpose: 从已验证 semantic request 构造无 ambient flag 的 Pi argv
59
+ /// purpose: 从已验证 semantic request 构造仅追加 Team Agent MCP/提示/session 的 Pi argv
61
60
  /// returns: exact ordered fresh/resume argv
62
61
  /// errors: 必需字段、mcp_team 或工具 category 非法时返回 ProviderError
63
62
  /// ---
@@ -67,23 +66,21 @@ pub(crate) fn build_pi_command_argv(
67
66
  if request.executable.as_os_str().is_empty()
68
67
  || request.extension.as_os_str().is_empty()
69
68
  || request.session_dir.as_os_str().is_empty()
70
- || request.model.trim().is_empty()
69
+ || request.model.is_some_and(|model| model.trim().is_empty())
71
70
  || request.system_prompt.trim().is_empty()
72
71
  || request.agent_id.trim().is_empty()
73
72
  {
74
73
  return Err(ProviderError::Command(
75
- "Pi command requires executable, extension, model, prompt, session directory, and agent id"
74
+ "Pi command requires executable, extension, prompt, session directory, and agent id"
76
75
  .to_string(),
77
76
  ));
78
77
  }
79
78
 
80
- let mut tools = BTreeSet::new();
79
+ let mut has_mcp = false;
81
80
  for category in request.tool_categories {
82
81
  match pi_tool_mapping(category) {
83
- PiToolMapping::Mcp => {
84
- tools.insert("mcp");
85
- }
86
- PiToolMapping::Builtin(names) => tools.extend(names.iter().copied()),
82
+ PiToolMapping::Mcp => has_mcp = true,
83
+ PiToolMapping::Builtin(_) => {}
87
84
  PiToolMapping::Unsupported => {
88
85
  return Err(ProviderError::Command(format!(
89
86
  "Pi does not support Team Agent tool category {category:?}"
@@ -91,7 +88,7 @@ pub(crate) fn build_pi_command_argv(
91
88
  }
92
89
  }
93
90
  }
94
- if !tools.contains("mcp") {
91
+ if !has_mcp {
95
92
  return Err(ProviderError::Command(
96
93
  "Pi command requires mcp_team".to_string(),
97
94
  ));
@@ -99,26 +96,23 @@ pub(crate) fn build_pi_command_argv(
99
96
 
100
97
  let mut argv = vec![
101
98
  request.executable.to_string_lossy().into_owned(),
102
- "--no-extensions".to_string(),
103
99
  "-e".to_string(),
104
100
  request.extension.to_string_lossy().into_owned(),
105
- "--no-approve".to_string(),
106
- "--no-context-files".to_string(),
107
- "--no-skills".to_string(),
108
- "--no-prompt-templates".to_string(),
109
- "--tui-mode".to_string(),
110
- "regular".to_string(),
111
- "--model".to_string(),
112
- request.model.to_string(),
113
- "--thinking".to_string(),
114
- request.effort.as_str().to_string(),
115
- "--system-prompt".to_string(),
101
+ ];
102
+ if let Some(model) = request.model {
103
+ argv.push("--model".to_string());
104
+ argv.push(model.to_string());
105
+ }
106
+ if let Some(effort) = request.effort {
107
+ argv.push("--thinking".to_string());
108
+ argv.push(effort.as_str().to_string());
109
+ }
110
+ argv.extend([
111
+ "--append-system-prompt".to_string(),
116
112
  request.system_prompt.to_string(),
117
- "--tools".to_string(),
118
- tools.into_iter().collect::<Vec<_>>().join(","),
119
113
  "--session-dir".to_string(),
120
114
  request.session_dir.to_string_lossy().into_owned(),
121
- ];
115
+ ]);
122
116
  // The shared lifecycle materializer always revalidates resume root/path/header.
123
117
  // Keep the pure argv builder usable before a fixture root is materialized, while
124
118
  // refusing a missing exact file once the recorded path's parent exists.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-agent/installer",
3
- "version": "0.5.69",
3
+ "version": "0.5.71",
4
4
  "description": "npx installer for Team Agent",
5
5
  "keywords": [
6
6
  "codex",
@@ -20,9 +20,9 @@
20
20
  "team-agent-installer": "npm/install.mjs"
21
21
  },
22
22
  "optionalDependencies": {
23
- "@team-agent/cli-darwin-arm64": "0.5.69",
24
- "@team-agent/cli-darwin-x64": "0.5.69",
25
- "@team-agent/cli-linux-x64": "0.5.69"
23
+ "@team-agent/cli-darwin-arm64": "0.5.71",
24
+ "@team-agent/cli-darwin-x64": "0.5.71",
25
+ "@team-agent/cli-linux-x64": "0.5.71"
26
26
  },
27
27
  "scripts": {
28
28
  "postinstall": "node npm/bincheck.mjs",
@@ -2,7 +2,7 @@
2
2
  name: team-agent
3
3
  description: Use only when the user explicitly asks to start, operate, inspect, shutdown, or restart a Team Agent team. Treat the team-agent CLI as a sealed appliance.
4
4
  requires_team_agent: ">=0.5.0"
5
- last_verified_against: "0.5.66"
5
+ last_verified_against: "0.5.71"
6
6
  ---
7
7
 
8
8
  # Team Agent
@@ -42,6 +42,12 @@ Grok / `cursor_agent` have no JSONL turn-state reader (classify → Unknown).
42
42
 
43
43
  ## Provider Prep
44
44
 
45
+ ### Pi TeamMate roles
46
+
47
+ Use the standard role schema with `provider: pi`, a block-style `tools` list containing `mcp_team`, and the required boolean `dangerously_skip_permissions`. That role boolean is the only bypass setting; `TEAM.md` `dangerous_auto_approve` is compatibility input, not a second user-facing switch.
48
+
49
+ Pi TeamMates keep the same login, model, extensions/plugins, skills, context files, project trust, and tools as a direct `pi` run on that machine. Team Agent only appends the role contract, a per-seat Team MCP registration, and per-seat session storage; do not duplicate those direct Pi settings in the role. `model` and `effort` remain optional compatibility overrides, and explicit values are passed through.
50
+
45
51
  ### Cursor provider notes
46
52
 
47
53
  Frontmatter: `provider: cursor_agent` (not `cursor`; launcher verb is `team-agent cursor`), `auth_mode: subscription`, `name:` required (omit → `missing front matter field name`). Also required: `role:`, `tools:`, `dangerously_skip_permissions:` (bool). Subscription needs no `profile`.