@team-agent/installer 0.5.85 → 0.5.86

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 (58) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/mod.rs +0 -1
  4. package/crates/team-agent/src/compiler/tests.rs +13 -24
  5. package/crates/team-agent/src/compiler.rs +1 -50
  6. package/crates/team-agent/src/leader/start.rs +0 -2
  7. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +0 -1
  8. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +0 -1
  9. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +0 -6
  10. package/crates/team-agent/src/lifecycle/launch/identity.rs +0 -1
  11. package/crates/team-agent/src/lifecycle/launch/layout.rs +0 -1
  12. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +0 -1
  13. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +0 -58
  14. package/crates/team-agent/src/lifecycle/launch/ownership.rs +0 -1
  15. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +0 -3
  16. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +14 -24
  17. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +0 -1
  18. package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -1
  19. package/crates/team-agent/src/lifecycle/launch/spawn.rs +4 -8
  20. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +0 -1
  21. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +0 -1
  22. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +0 -1
  23. package/crates/team-agent/src/lifecycle/launch.rs +0 -9
  24. package/crates/team-agent/src/lifecycle/restart/common.rs +4 -8
  25. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +1 -3
  26. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +42 -46
  27. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +30 -44
  28. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +32 -77
  29. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -13
  30. package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +0 -1
  31. package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +0 -2
  32. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +14 -85
  33. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +1 -1
  34. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +0 -5
  35. package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +0 -1
  36. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +4 -4
  37. package/crates/team-agent/src/lifecycle/tests.rs +0 -1
  38. package/crates/team-agent/src/lifecycle/types.rs +0 -10
  39. package/crates/team-agent/src/lifecycle/worker_command_context.rs +4 -248
  40. package/crates/team-agent/src/model/enums.rs +0 -35
  41. package/crates/team-agent/src/model/mod.rs +0 -1
  42. package/crates/team-agent/src/model/spec.rs +23 -84
  43. package/crates/team-agent/src/provider/adapter.rs +48 -50
  44. package/crates/team-agent/src/provider/adapters/claude.rs +5 -30
  45. package/crates/team-agent/src/provider/adapters/codex.rs +5 -25
  46. package/crates/team-agent/src/provider/adapters/copilot.rs +8 -41
  47. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +4 -8
  48. package/crates/team-agent/src/provider/adapters/grok.rs +5 -58
  49. package/crates/team-agent/src/provider/adapters/mod.rs +5 -7
  50. package/crates/team-agent/src/provider/adapters/pi.rs +1 -56
  51. package/crates/team-agent/src/provider/bypass_flags.rs +1 -2
  52. package/crates/team-agent/src/provider/session/capture.rs +4 -4
  53. package/crates/team-agent/src/provider/tests/adapter.rs +7 -24
  54. package/crates/team-agent/src/provider/types.rs +1 -1
  55. package/package.json +4 -4
  56. package/schemas/team.schema.json +4 -9
  57. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +0 -784
  58. package/crates/team-agent/src/model/permissions.rs +0 -721
@@ -17,7 +17,6 @@ use std::process::Command;
17
17
 
18
18
  use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
19
19
  use crate::model::ids::AgentId;
20
- use crate::model::permissions::{self, AgentPermissionInput};
21
20
  use crate::model::yaml::{self, Value};
22
21
  use crate::state::persist::load_runtime_state;
23
22
  use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
@@ -133,13 +132,6 @@ pub fn launch_with_transport_in_workspace(
133
132
  session_name.as_str()
134
133
  )));
135
134
  }
136
- let permissions = spec_agents(&spec)
137
- .into_iter()
138
- .map(|agent| PermissionSummary {
139
- agent_id: agent,
140
- raw: serde_json::json!({"source": "compiled_spec"}),
141
- })
142
- .collect::<Vec<_>>();
143
135
  let routes = spec_routes(&spec);
144
136
  let started = if dry_run {
145
137
  Vec::new()
@@ -208,7 +200,6 @@ pub fn launch_with_transport_in_workspace(
208
200
  dry_run,
209
201
  tmux_endpoint: transport.tmux_endpoint(),
210
202
  routes,
211
- permissions,
212
203
  leader_receiver_attached: false,
213
204
  leader_bind_stage: None,
214
205
  leader_bind_reason: None,
@@ -301,11 +301,6 @@ pub(super) fn spawn_agent_window(
301
301
  )?;
302
302
  let system_prompt =
303
303
  crate::lifecycle::worker_command_context::compile_worker_system_prompt(&command_agent)?;
304
- let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
305
- &command_agent,
306
- provider,
307
- )?;
308
- let resolved_tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
309
304
  // owner_team_id resolution priority (Issue 2 fix):
310
305
  // 1. caller's explicit override (restart paths pass `selected.team_key`)
311
306
  // 2. agent row's persisted `owner_team_id` (set by prior launch/restart)
@@ -374,7 +369,10 @@ pub(super) fn spawn_agent_window(
374
369
  mcp_config: Some(&mcp_config),
375
370
  system_prompt: Some(system_prompt.as_str()),
376
371
  model: command_model,
377
- tools: &resolved_tool_refs,
372
+ dangerously_skip_permissions: agent
373
+ .get("dangerously_skip_permissions")
374
+ .and_then(serde_json::Value::as_bool)
375
+ .unwrap_or(false),
378
376
  profile_launch: Some(&profile_launch),
379
377
  agent_id_hint: Some(agent_id.as_str()),
380
378
  effort: restart_effort,
@@ -399,7 +397,6 @@ pub(super) fn spawn_agent_window(
399
397
  model: command_model,
400
398
  effort: restart_effort,
401
399
  system_prompt: &system_prompt,
402
- tool_categories: &resolved_tool_refs,
403
400
  team_mcp_tools: &["send_message", "report_result"],
404
401
  mcp_config: &mcp_config,
405
402
  session_scope: crate::lifecycle::launch::pi_mcp::PiSessionScope::Isolated,
@@ -744,7 +741,6 @@ fn merge_command_context_fields(
744
741
  };
745
742
  for field in [
746
743
  "role",
747
- "tools",
748
744
  "system_prompt",
749
745
  "output_contract",
750
746
  "provider",
@@ -97,15 +97,13 @@ fn managed_profile_command_plan_omits_mcp_config_even_when_mcp_config_is_availab
97
97
  }
98
98
  }),
99
99
  };
100
- let tools = ["mcp_team"];
101
-
102
100
  let plan = get_adapter(Provider::Claude)
103
101
  .build_command_plan(ProviderCommandContext {
104
102
  auth_mode: AuthMode::CompatibleApi,
105
103
  mcp_config: Some(&config),
106
104
  system_prompt: Some("worker prompt requiring native MCP"),
107
105
  model: Some("profile-effective-haiku"),
108
- tools: &tools,
106
+ dangerously_skip_permissions: false,
109
107
  profile_launch: Some(&profile),
110
108
  agent_id_hint: None,
111
109
  effort: None,
@@ -1,9 +1,9 @@
1
1
  //! ---
2
- //! purpose: 回归——clone-agent 分身 tools 必须与源席集合相等,禁止静默夹成 leader 三件套
2
+ //! purpose: 回归——clone-agent 分身保留源席 tools,编译 spec 不投影 tools
3
3
  //! contract:
4
4
  //! provides:
5
5
  //! - name: clone_agent_preserves_source_tools
6
- //! what: 真跑 clone-agent 后,dynamic-role-files / runtime spec 与源席 tools 集合相等
6
+ //! what: 真跑 clone-agent 后,dynamic-role-files 保留源席 tools,runtime spec 不投影 tools
7
7
  //! requires:
8
8
  //! - name: source-six-set-vs-leader-three-set
9
9
  //! what: 同一 team 里 leader 三件套 + 源席六件套的共享冲突面
@@ -275,6 +275,10 @@ fn role_tools(path: &Path) -> BTreeSet<String> {
275
275
  .collect()
276
276
  }
277
277
 
278
+ fn set_of(items: &[&str]) -> BTreeSet<String> {
279
+ items.iter().map(|s| (*s).to_string()).collect()
280
+ }
281
+
278
282
  fn load_runtime_spec(workspace: &Path) -> Value {
279
283
  let runtime = workspace.join(".team").join("runtime");
280
284
  let spec = std::fs::read_dir(&runtime)
@@ -287,17 +291,7 @@ fn load_runtime_spec(workspace: &Path) -> Value {
287
291
  .expect("parse spec")
288
292
  }
289
293
 
290
- fn yaml_tool_set(node: &Value) -> BTreeSet<String> {
291
- node.get("tools")
292
- .and_then(Value::as_list)
293
- .unwrap_or_else(|| panic!("tools list missing"))
294
- .iter()
295
- .filter_map(Value::as_str)
296
- .map(str::to_string)
297
- .collect()
298
- }
299
-
300
- fn spec_tools(workspace: &Path, agent: &str) -> BTreeSet<String> {
294
+ fn spec_projects_tools(workspace: &Path, agent: &str) -> bool {
301
295
  let parsed = load_runtime_spec(workspace);
302
296
  let agents = parsed
303
297
  .get("agents")
@@ -305,22 +299,18 @@ fn spec_tools(workspace: &Path, agent: &str) -> BTreeSet<String> {
305
299
  .expect("spec agents");
306
300
  for row in agents {
307
301
  if row.get("id").and_then(Value::as_str) == Some(agent) {
308
- return yaml_tool_set(row);
302
+ return row.get("tools").is_some();
309
303
  }
310
304
  }
311
305
  panic!("agent {agent} missing from runtime spec");
312
306
  }
313
307
 
314
- fn leader_spec_tools(workspace: &Path) -> BTreeSet<String> {
315
- yaml_tool_set(
316
- load_runtime_spec(workspace)
317
- .get("leader")
318
- .expect("spec leader"),
319
- )
320
- }
321
-
322
- fn set_of(items: &[&str]) -> BTreeSet<String> {
323
- items.iter().map(|s| (*s).to_string()).collect()
308
+ fn leader_spec_projects_tools(workspace: &Path) -> bool {
309
+ load_runtime_spec(workspace)
310
+ .get("leader")
311
+ .expect("spec leader")
312
+ .get("tools")
313
+ .is_some()
324
314
  }
325
315
 
326
316
  fn clone_ok(case: &Case, source: &str, dest: &str, phase: &str) {
@@ -347,7 +337,7 @@ fn clone_ok(case: &Case, source: &str, dest: &str, phase: &str) {
347
337
  );
348
338
  }
349
339
 
350
- /// clone 后分身 role + spec tools 必须等于源席;三件套源不得被扩成六件。
340
+ /// clone 后分身 role 保留源席 tools;编译 spec 不投影 tools。
351
341
  #[test]
352
342
  fn clone_agent_preserves_source_tools() {
353
343
  let case = Case::start();
@@ -380,21 +370,18 @@ fn clone_agent_preserves_source_tools() {
380
370
  String::from_utf8_lossy(&qs.stdout)
381
371
  );
382
372
 
383
- let source_spec = spec_tools(&case.workspace, SOURCE);
384
- assert_eq!(
385
- source_spec,
386
- set_of(SIX),
387
- "fixture source must be the six-set"
373
+ assert!(
374
+ !spec_projects_tools(&case.workspace, SOURCE),
375
+ "compiled source spec must not project tools metadata"
388
376
  );
389
- let leader_spec = leader_spec_tools(&case.workspace);
390
- assert_eq!(
391
- leader_spec,
392
- set_of(THREE),
393
- "leader must stay the three-set ceiling so the conflict surface exists; leader={leader_spec:?}"
377
+ assert!(
378
+ !leader_spec_projects_tools(&case.workspace),
379
+ "compiled leader spec must not project tools metadata"
394
380
  );
395
381
 
396
382
  clone_ok(&case, SOURCE, CLONE, "first_clone");
397
383
 
384
+ let source_role = role_tools(&case.workspace.join("agents").join(format!("{SOURCE}.md")));
398
385
  let clone_role = role_tools(
399
386
  &case
400
387
  .workspace
@@ -402,23 +389,32 @@ fn clone_agent_preserves_source_tools() {
402
389
  .join("dynamic-role-files")
403
390
  .join(format!("{CLONE}.md")),
404
391
  );
405
- let clone_spec = spec_tools(&case.workspace, CLONE);
406
392
  assert_eq!(
407
- clone_role, source_spec,
408
- "clone role tools must equal source; source={source_spec:?} clone={clone_role:?}"
393
+ clone_role, source_role,
394
+ "clone role tools must equal source; source={source_role:?} clone={clone_role:?}"
409
395
  );
410
- assert_eq!(
411
- clone_spec, source_spec,
412
- "clone spec tools must equal source; source={source_spec:?} spec={clone_spec:?}"
396
+ assert!(
397
+ !spec_projects_tools(&case.workspace, CLONE),
398
+ "compiled clone spec must not project tools metadata"
413
399
  );
414
400
 
415
- let narrow_src = spec_tools(&case.workspace, NARROW);
416
- assert_eq!(narrow_src, set_of(THREE), "narrow source fixture");
401
+ let narrow_source_role = role_tools(&case.workspace.join("agents").join(format!("{NARROW}.md")));
402
+ assert_eq!(narrow_source_role, set_of(THREE), "narrow source fixture");
417
403
  clone_ok(&case, NARROW, NARROW_CLONE, "second_clone");
418
- let narrow_clone = spec_tools(&case.workspace, NARROW_CLONE);
404
+ let narrow_clone_role = role_tools(
405
+ &case
406
+ .workspace
407
+ .join(".team")
408
+ .join("dynamic-role-files")
409
+ .join(format!("{NARROW_CLONE}.md")),
410
+ );
419
411
  assert_eq!(
420
- narrow_clone, narrow_src,
421
- "narrow clone must keep the three-set (preserve, do not widen); got={narrow_clone:?}"
412
+ narrow_clone_role, narrow_source_role,
413
+ "narrow clone role must keep the three-set (preserve, do not widen); got={narrow_clone_role:?}"
414
+ );
415
+ assert!(
416
+ !spec_projects_tools(&case.workspace, NARROW_CLONE),
417
+ "compiled narrow clone spec must not project tools metadata"
422
418
  );
423
419
 
424
420
  case.shutdown();
@@ -17,7 +17,7 @@
17
17
  //! 1. worker argv golden (§C1 + C-5-1/3) — copilot_argv_* tests
18
18
  //! 2. per-worker AGENTS.md == compiled prompt — copilot_agents_md_* tests (C-1-1/2,
19
19
  //! identity-first per the 0.3.4 B2/D6 lock)
20
- //! 3. permission mapping (C-2-1, via the prompt's Permission note + argv deny flags)
20
+ //! 3. tools metadata transparency (C-2-1, no prompt permission note or argv deny flags)
21
21
  //! 4. fork = structured CapabilityUnsupported (C-4-2/3)
22
22
  //! 5. startup recognizer supports copilot trust/ready; turn-state Unknown never idles
23
23
  //! (N11, C-3-1/4)
@@ -62,12 +62,12 @@ const ANCESTRY_KEY: &str = "TEAM_AGENT_TEST_PROCESS_ANCESTRY_ARGV_JSON";
62
62
  const NEUTRAL_ANCESTRY: &str = "[\"/bin/zsh\"]";
63
63
  const COPILOT_TMP_PREFIX: &str = "ta-rs-copilot";
64
64
 
65
- /// Face 1+3 (§C1 golden + C-5-3, verdict cases `copilot_argv_golden_per_role_tools`
66
- /// and `non_dangerous_argv_contains_deny_flags_no_allow_all`): a restricted copilot
67
- /// worker (mcp_team only) gets the full §C1 argv — noise control, inline/`@file` MCP
68
- /// config carrying team_orchestrator, a pre-assigned `--session-id` equal to the
69
- /// persisted `_pending_session_id`, `-C <workspace>`, deny flags for the missing
70
- /// capabilities, and NO --allow-all family flag.
65
+ /// Face 1+3 (§C1 golden + C-5-3, verdict case `copilot_argv_golden_per_role_tools`):
66
+ /// a copilot worker (mcp_team only) gets the full §C1 argv — noise control,
67
+ /// inline/`@file` MCP config carrying team_orchestrator, a pre-assigned
68
+ /// `--session-id` equal to the persisted `_pending_session_id`, and `-C <workspace>`.
69
+ /// Tools metadata must not derive deny flags; non-dangerous workers still omit the
70
+ /// --allow-all family flag.
71
71
  #[test]
72
72
  #[serial(env)]
73
73
  fn copilot_argv_golden_restricted_role() {
@@ -176,21 +176,11 @@ team_orchestrator or @<.team/runtime/mcp/worker_a.json>; got {value:?}"
176
176
  flag_value(argv, "-C")
177
177
  ));
178
178
  }
179
- // C-5-3: restricted role (no execute_bash / fs_write / network) → deny flags, and
180
- // no --allow-all family flag anywhere.
181
- let deny_tools = flag_values(argv, "--deny-tool");
182
- for denied in ["shell", "write"] {
183
- if !deny_tools.iter().any(|tool| tool.contains(denied)) {
184
- failures.push(format!(
185
- "C-5-3: restricted copilot worker must carry --deny-tool '{denied}'; deny={deny_tools:?}"
186
- ));
187
- }
188
- }
189
- // RC-15 (C-5-2 v2): network deny is `--deny-tool 'url'` (the help has only shell/
190
- // write/mcp/url tool kinds; v1's --deny-url is not the v2 form).
191
- if !deny_tools.iter().any(|tool| tool.contains("url")) {
179
+ // Tools metadata is transparent compatibility data; it must not derive
180
+ // provider-specific deny flags (shell/write/url or any other category).
181
+ if argv.iter().any(|arg| arg == "--deny-tool") {
192
182
  failures.push(format!(
193
- "RC-15/C-5-2: restricted copilot worker must deny network via --deny-tool 'url'; deny={deny_tools:?}"
183
+ "C-5-3: tools metadata must not derive copilot --deny-tool flags; argv={argv:?}"
194
184
  ));
195
185
  }
196
186
  // RC-10 (C-3-5/C-5-2): mcp_team is allowlisted by server name (no approval prompt).
@@ -289,8 +279,8 @@ used for the dangerous tier; argv={argv:?}"
289
279
  /// the spawn env must carry COPILOT_CUSTOM_INSTRUCTIONS_DIRS pointing at the
290
280
  /// per-worker dir (path contains the agent id; works with NO profile configured), the
291
281
  /// AGENTS.md there must BE the compiled worker system prompt (single source: identity
292
- /// first, runtime contract, role body, output contract, permission note — 0.3.4 B2/D6
293
- /// lock), and no global ~/.copilot/AGENTS.md may be written.
282
+ /// first, runtime contract, role body, and output contract), and no global
283
+ /// ~/.copilot/AGENTS.md may be written.
294
284
  #[test]
295
285
  #[serial(env)]
296
286
  fn copilot_agents_md_is_the_compiled_prompt_and_env_points_at_it() {
@@ -362,7 +352,6 @@ with the identity section FIRST; head={:?}",
362
352
  "# Team Agent Teammate Runtime Contract",
363
353
  "COPILOT ROLE BODY SENTINEL",
364
354
  "report_result exactly once",
365
- "Permission note: these tools are prompt-only for this provider and not hard-enforced:",
366
355
  ] {
367
356
  if !agents_md.contains(marker) {
368
357
  failures.push(format!(
@@ -386,14 +375,11 @@ with the identity section FIRST; head={:?}",
386
375
  );
387
376
  }
388
377
 
389
- /// Face 3 (C-2-1, verdict `permission_enforcement_table_copilot_row_prompt_only_for_fs`):
390
- /// the copilot enforcement row must honestly register the no-hard-deny tools as
391
- /// prompt-only. Observable single-source: the compiled prompt's Permission note (built
392
- /// from PROVIDER_ENFORCEMENT) for a full-capability role must list exactly
393
- /// fs_list/fs_read/git_diff/provider_builtin (sorted).
378
+ /// Face 3: tools metadata is transparent compatibility data and must not produce
379
+ /// a provider-specific Permission note in the compiled Copilot prompt.
394
380
  #[test]
395
381
  #[serial(env)]
396
- fn copilot_permission_note_registers_fs_tools_prompt_only() {
382
+ fn copilot_prompt_omits_removed_permission_note() {
397
383
  let _hermetic = enter_hermetic("copilot-permission-note");
398
384
  let _guard = EnvGuard::set(&[(ANCESTRY_KEY, NEUTRAL_ANCESTRY)]);
399
385
  let ws = tmp_ws("enforcement");
@@ -428,21 +414,21 @@ fn copilot_permission_note_registers_fs_tools_prompt_only() {
428
414
  .env
429
415
  .get("COPILOT_CUSTOM_INSTRUCTIONS_DIRS")
430
416
  .cloned()
431
- .unwrap_or_default();
432
- let agents_md = std::fs::read_to_string(Path::new(&dir).join("AGENTS.md")).unwrap_or_default();
433
-
434
- let note_line = agents_md
435
- .lines()
436
- .find(|line| line.starts_with("Permission note:"))
437
- .unwrap_or("")
438
- .to_string();
417
+ .expect("Copilot spawn must carry COPILOT_CUSTOM_INSTRUCTIONS_DIRS");
418
+ assert!(
419
+ !dir.is_empty(),
420
+ "Copilot instructions directory must not be empty"
421
+ );
422
+ let agents_md = std::fs::read_to_string(Path::new(&dir).join("AGENTS.md"))
423
+ .expect("per-worker Copilot AGENTS.md must be readable");
424
+ let identity = "You are Team Agent worker `worker_a` with role `Copilot Worker`.";
425
+ assert!(
426
+ agents_md.starts_with(identity) && agents_md.contains("COPILOT ROLE BODY SENTINEL"),
427
+ "C-2-1: compiled Copilot prompt must include its identity and role body; content={agents_md:?}"
428
+ );
439
429
  assert!(
440
- note_line.contains("fs_list, fs_read, git_diff, provider_builtin"),
441
- "C-2-1: the copilot enforcement row must register exactly \
442
- fs_read/fs_list/git_diff/provider_builtin as prompt-only (sorted in the Permission \
443
- note; execute_bash/fs_write/network/mcp_team are hard); note={note_line:?} \
444
- agents_md_present={}",
445
- !agents_md.is_empty()
430
+ !agents_md.contains("Permission note:"),
431
+ "C-2-1: removed provider tool enforcement must not emit a Permission note; content={agents_md:?}"
446
432
  );
447
433
  }
448
434
 
@@ -37,7 +37,7 @@ fn bin() -> &'static str {
37
37
 
38
38
  #[test]
39
39
  #[serial(env)]
40
- fn claude_worker_spawn_argv_uses_compiled_prompt_and_resolved_role_tools() {
40
+ fn claude_worker_spawn_argv_uses_compiled_prompt_without_tool_restrictions() {
41
41
  let _hermetic = enter_hermetic("core034-claude-argv");
42
42
  let _ancestry = EnvGuard::set(
43
43
  "TEAM_AGENT_TEST_PROCESS_ANCESTRY_ARGV_JSON",
@@ -80,7 +80,6 @@ fn claude_worker_spawn_argv_uses_compiled_prompt_and_resolved_role_tools() {
80
80
  "send_message(to='leader'",
81
81
  "report_result exactly once",
82
82
  "ROLE BODY SENTINEL",
83
- "Permission note",
84
83
  ] {
85
84
  if !prompt.contains(marker) {
86
85
  failures.push(format!("B2 missing compiled prompt marker {marker:?}"));
@@ -90,31 +89,15 @@ fn claude_worker_spawn_argv_uses_compiled_prompt_and_resolved_role_tools() {
90
89
  failures.push("B2 prompt collapsed to the bare role label".to_string());
91
90
  }
92
91
 
93
- let disallowed = flag_values(&spawn.argv, "--disallowedTools");
94
- for forbidden in [
95
- "Bash",
96
- "Read",
97
- "Edit",
98
- "Write",
99
- "MultiEdit",
100
- "NotebookEdit",
101
- "Glob",
102
- "Grep",
103
- ] {
104
- if disallowed.iter().any(|tool| tool == forbidden) {
105
- failures.push(format!(
106
- "B1 declared capability still disallowed via --disallowedTools {forbidden:?}"
107
- ));
108
- }
92
+ if argv_has_flag(&spawn.argv, "--disallowedTools") {
93
+ failures.push("B1 Claude argv must not derive --disallowedTools from tools metadata".to_string());
109
94
  }
110
95
  if argv_has_flag(&spawn.argv, "--allowedTools") {
111
- failures.push(
112
- "B1 Python parity violated: Claude argv must not switch to --allowedTools".to_string(),
113
- );
96
+ failures.push("B1 Claude argv must not derive --allowedTools from tools metadata".to_string());
114
97
  }
115
98
  assert!(
116
99
  failures.is_empty(),
117
- "B1/B2 worker argv contract failed:\n{}\nprompt={prompt:?}\ndisallowed={disallowed:?}\nargv={:?}",
100
+ "B1/B2 worker argv contract failed:\n{}\nprompt={prompt:?}\nargv={:?}",
118
101
  failures.join("\n"),
119
102
  spawn.argv
120
103
  );
@@ -122,7 +105,7 @@ fn claude_worker_spawn_argv_uses_compiled_prompt_and_resolved_role_tools() {
122
105
 
123
106
  #[test]
124
107
  #[serial(env)]
125
- fn claude_control_role_without_native_tools_still_disallows_native_tools() {
108
+ fn claude_control_role_does_not_derive_native_tool_restrictions() {
126
109
  let _hermetic = enter_hermetic("core034-claude-control");
127
110
  let _ancestry = EnvGuard::set(
128
111
  "TEAM_AGENT_TEST_PROCESS_ANCESTRY_ARGV_JSON",
@@ -156,29 +139,21 @@ fn claude_control_role_without_native_tools_still_disallows_native_tools() {
156
139
  .expect("quick-start should record the control worker command");
157
140
 
158
141
  let spawn = transport.single_spawn();
159
- let disallowed = flag_values(&spawn.argv, "--disallowedTools");
160
- for expected in [
161
- "Bash",
162
- "Read",
163
- "Edit",
164
- "Write",
165
- "MultiEdit",
166
- "NotebookEdit",
167
- "Glob",
168
- "Grep",
169
- ] {
170
- assert!(
171
- disallowed.iter().any(|tool| tool == expected),
172
- "B1 negative case: mcp_team-only control roles must still disallow native Claude tools; \
173
- missing={expected} disallowed={disallowed:?}\nargv={:?}",
174
- spawn.argv
175
- );
176
- }
142
+ assert!(
143
+ !argv_has_flag(&spawn.argv, "--disallowedTools"),
144
+ "B1 control role must not derive native-tool restrictions from tools metadata; argv={:?}",
145
+ spawn.argv
146
+ );
147
+ assert!(
148
+ !argv_has_flag(&spawn.argv, "--allowedTools"),
149
+ "B1 control role must not derive an allowlist from tools metadata; argv={:?}",
150
+ spawn.argv
151
+ );
177
152
  }
178
153
 
179
154
  #[test]
180
155
  #[serial(env)]
181
- fn restart_claude_worker_resolves_tools_from_spec_when_runtime_state_has_no_raw_tools() {
156
+ fn restart_claude_worker_does_not_derive_tool_restrictions_when_runtime_state_has_no_raw_tools() {
182
157
  let _hermetic = enter_hermetic("core034-restart-tools");
183
158
  let _ancestry = EnvGuard::set(
184
159
  "TEAM_AGENT_TEST_PROCESS_ANCESTRY_ARGV_JSON",
@@ -215,34 +190,20 @@ fn restart_claude_worker_resolves_tools_from_spec_when_runtime_state_has_no_raw_
215
190
  let report = restart_with_transport(&ws, true, Some("restarttools"), &restart_transport)
216
191
  .expect("restart should reach worker command construction");
217
192
  let spawn = restart_transport.single_spawn();
218
- let disallowed = flag_values(&spawn.argv, "--disallowedTools");
219
-
220
- for forbidden in [
221
- "Bash",
222
- "Read",
223
- "Edit",
224
- "Write",
225
- "MultiEdit",
226
- "NotebookEdit",
227
- "Glob",
228
- "Grep",
229
- ] {
230
- assert!(
231
- !disallowed.iter().any(|tool| tool == forbidden),
232
- "B1 restart contract: when runtime state lacks raw tools, restart must resolve tools from the selected spec/role through resolve_permissions. \
233
- {forbidden} must not be disallowed for this role; disallowed={disallowed:?}\nargv={:?}\nreport={report:?}",
234
- spawn.argv
235
- );
236
- }
193
+ assert!(
194
+ !argv_has_flag(&spawn.argv, "--disallowedTools"),
195
+ "B1 restart must not derive native-tool restrictions from tools metadata; argv={:?} report={report:?}",
196
+ spawn.argv
197
+ );
237
198
  assert!(
238
199
  !argv_has_flag(&spawn.argv, "--allowedTools"),
239
- "B1 Python parity: restart must keep --disallowedTools semantics, not switch to --allowedTools; argv={:?}",
200
+ "B1 restart must not derive an allowlist from tools metadata; argv={:?} report={report:?}",
240
201
  spawn.argv
241
202
  );
242
203
  }
243
204
 
244
205
  #[test]
245
- fn worker_command_context_uses_single_prompt_and_permission_sources() {
206
+ fn worker_command_context_uses_single_prompt_without_permission_sources() {
246
207
  let crate_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
247
208
  let src_root = crate_root.join("src");
248
209
  let launch = composite_source::composite_source("src/lifecycle/launch.rs");
@@ -276,10 +237,9 @@ fn worker_command_context_uses_single_prompt_and_permission_sources() {
276
237
  && all_src.contains("runtime_contract_section")
277
238
  && all_src.contains("identity_section")
278
239
  && all_src.contains("role_body")
279
- && all_src.contains("output_contract")
280
- && all_src.contains("permission_notes"),
281
- "B2 grep guard: worker command construction needs one compiled prompt helper with the CR-approved five sections \
282
- (runtime_contract_section, identity_section, role_body, output_contract, permission_notes)."
240
+ && all_src.contains("output_contract"),
241
+ "B2 grep guard: worker command construction needs one compiled prompt helper with the CR-approved four sections \
242
+ (runtime_contract_section, identity_section, role_body, output_contract)."
283
243
  );
284
244
  assert!(
285
245
  !command_sources.contains("system_prompt: role"),
@@ -296,8 +256,9 @@ fn worker_command_context_uses_single_prompt_and_permission_sources() {
296
256
  );
297
257
 
298
258
  assert!(
299
- all_src.contains("resolve_permissions") && all_src.contains("resolved_tool_strings"),
300
- "B1 grep guard: command argv tools must be calculated through a single resolve_permissions helper, including role defaults and leader ceiling."
259
+ !command_sources.contains("resolve_permissions")
260
+ && !command_sources.contains("resolved_tool_strings"),
261
+ "B1 grep guard: command construction must not derive argv restrictions from tools metadata."
301
262
  );
302
263
  assert!(
303
264
  !command_sources.contains("worker_tool_refs(agent_tool_strings")
@@ -305,8 +266,8 @@ fn worker_command_context_uses_single_prompt_and_permission_sources() {
305
266
  "B1 grep guard: command construction must not raw-pass agent.tools/tool_refs into ProviderCommandContext."
306
267
  );
307
268
  assert!(
308
- adapter.contains("--disallowedTools") && !adapter.contains("--allowedTools"),
309
- "B1 Python parity: provider adapter must keep Claude --disallowedTools and not invent --allowedTools."
269
+ !adapter.contains("--disallowedTools") && !adapter.contains("--allowedTools"),
270
+ "B1 provider adapter must not derive Claude tool restrictions from tools metadata."
310
271
  );
311
272
  assert!(
312
273
  !adapter.contains("resolve_permissions"),
@@ -837,12 +798,6 @@ fn flag_value(argv: &[String], flag: &str) -> Option<String> {
837
798
  .find_map(|pair| (pair[0] == flag).then(|| pair[1].clone()))
838
799
  }
839
800
 
840
- fn flag_values(argv: &[String], flag: &str) -> Vec<String> {
841
- argv.windows(2)
842
- .filter_map(|pair| (pair[0] == flag).then(|| pair[1].clone()))
843
- .collect()
844
- }
845
-
846
801
  fn argv_has_flag(argv: &[String], flag: &str) -> bool {
847
802
  argv.iter().any(|arg| arg == flag)
848
803
  }
@@ -520,8 +520,8 @@ fn launch_dry_run_resolves_real_plan_not_stub_error() {
520
520
  Ok(report) => {
521
521
  assert!(report.dry_run, "dry_run launch must report dry_run=true");
522
522
  assert!(
523
- !report.routes.is_empty() || !report.permissions.is_empty(),
524
- "dry_run launch must resolve a real route/permission plan from the compiled spec"
523
+ !report.routes.is_empty(),
524
+ "dry_run launch must resolve a real route plan from the compiled spec"
525
525
  );
526
526
  }
527
527
  // any OTHER Err (a real requirement/transport error) still proves the stub is gone.
@@ -3210,7 +3210,6 @@ fn quick_start_running_agent_state_shape_after_spawn_is_golden() {
3210
3210
  "profile",
3211
3211
  "window",
3212
3212
  "mcp_config",
3213
- "permissions",
3214
3213
  "effective_approval_policy",
3215
3214
  // 0.5.66 bypass 单源:fresh spawn 记录运行面 bypass 值(restart drift 挡板)。
3216
3215
  "as_launched_dangerously_skip_permissions",
@@ -3239,16 +3238,6 @@ fn quick_start_running_agent_state_shape_after_spawn_is_golden() {
3239
3238
  .join(".team/runtime/mcp/implementer.json")
3240
3239
  .to_string_lossy())
3241
3240
  );
3242
- assert_eq!(
3243
- agent["permissions"],
3244
- json!({
3245
- "agent_id": "implementer",
3246
- "provider": "codex",
3247
- "tools": ["mcp_team"],
3248
- "resolved_tools": [{"tool": "mcp_team", "enforcement": "prompt_only"}],
3249
- "has_prompt_only": true
3250
- })
3251
- );
3252
3241
  assert!(agent["session_id"].is_null());
3253
3242
  assert!(agent["rollout_path"].is_null());
3254
3243
  assert!(agent["captured_at"].is_null());
@@ -91,7 +91,6 @@ fn command_for_model(model: &str) -> Vec<String> {
91
91
  model: Some(model),
92
92
  effort: Some(ProviderEffort::Medium),
93
93
  system_prompt: "worker contract",
94
- tool_categories: &["mcp_team"],
95
94
  session_dir: Some(Path::new("/workspace/.team/runtime/pi/t1/w1/sessions")),
96
95
  session: PiSessionSelector::Fresh {
97
96
  session_id: "716ba0cb-c491-4471-b41a-43c8d5e1f05a",
@@ -10,7 +10,6 @@ fn request(session: PiSessionSelector<'static>) -> PiCommandRequest<'static> {
10
10
  model: Some("team-agent/qwen3.8-27b"),
11
11
  effort: Some(ProviderEffort::High),
12
12
  system_prompt: "frozen worker prompt",
13
- tool_categories: &["mcp_team", "fs_read", "fs_write", "execute_bash"],
14
13
  session_dir: Some(Path::new(
15
14
  "/workspace/.team/runtime/pi/team-a/worker-a/sessions",
16
15
  )),
@@ -38,7 +37,6 @@ fn leader_request() -> PiCommandRequest<'static> {
38
37
  model: None,
39
38
  effort: None,
40
39
  system_prompt: "frozen leader prompt",
41
- tool_categories: &["mcp_team", "fs_read", "fs_write", "execute_bash"],
42
40
  session_dir: None,
43
41
  session: PiSessionSelector::Fresh {
44
42
  session_id: "da8c3622-2378-4d05-a26c-e826a6ef6d63",