@team-agent/installer 0.5.84 → 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 (64) 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/cli/profile.rs +4 -12
  5. package/crates/team-agent/src/cli/tests/verb_profile.rs +66 -0
  6. package/crates/team-agent/src/compiler/tests.rs +13 -24
  7. package/crates/team-agent/src/compiler.rs +1 -50
  8. package/crates/team-agent/src/leader/start.rs +0 -2
  9. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +0 -1
  10. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +0 -1
  11. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +0 -6
  12. package/crates/team-agent/src/lifecycle/launch/identity.rs +0 -1
  13. package/crates/team-agent/src/lifecycle/launch/layout.rs +0 -1
  14. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +0 -1
  15. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +1 -89
  16. package/crates/team-agent/src/lifecycle/launch/ownership.rs +0 -1
  17. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +0 -3
  18. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +14 -24
  19. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +0 -1
  20. package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -1
  21. package/crates/team-agent/src/lifecycle/launch/spawn.rs +4 -8
  22. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +0 -1
  23. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +0 -1
  24. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +0 -1
  25. package/crates/team-agent/src/lifecycle/launch.rs +0 -9
  26. package/crates/team-agent/src/lifecycle/profile_launch.rs +1 -1
  27. package/crates/team-agent/src/lifecycle/restart/common.rs +6 -9
  28. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +1 -3
  29. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +42 -46
  30. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +209 -52
  31. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +32 -77
  32. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -13
  33. package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +0 -1
  34. package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +0 -2
  35. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +14 -85
  36. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +1 -1
  37. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +0 -5
  38. package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +0 -1
  39. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +4 -4
  40. package/crates/team-agent/src/lifecycle/tests.rs +0 -1
  41. package/crates/team-agent/src/lifecycle/types.rs +0 -10
  42. package/crates/team-agent/src/lifecycle/worker_command_context.rs +4 -248
  43. package/crates/team-agent/src/messaging/results/collection_tests.rs +719 -0
  44. package/crates/team-agent/src/messaging/results.rs +104 -27
  45. package/crates/team-agent/src/model/enums.rs +0 -35
  46. package/crates/team-agent/src/model/mod.rs +0 -1
  47. package/crates/team-agent/src/model/spec.rs +23 -84
  48. package/crates/team-agent/src/provider/adapter.rs +48 -50
  49. package/crates/team-agent/src/provider/adapters/claude.rs +5 -30
  50. package/crates/team-agent/src/provider/adapters/codex.rs +5 -25
  51. package/crates/team-agent/src/provider/adapters/copilot.rs +9 -42
  52. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +4 -8
  53. package/crates/team-agent/src/provider/adapters/grok.rs +5 -58
  54. package/crates/team-agent/src/provider/adapters/mod.rs +5 -7
  55. package/crates/team-agent/src/provider/adapters/pi.rs +1 -56
  56. package/crates/team-agent/src/provider/bypass_flags.rs +1 -2
  57. package/crates/team-agent/src/provider/session/capture.rs +4 -4
  58. package/crates/team-agent/src/provider/tests/adapter.rs +7 -24
  59. package/crates/team-agent/src/provider/types.rs +1 -1
  60. package/crates/team-agent/src/state/persist.rs +15 -1
  61. package/package.json +4 -4
  62. package/schemas/team.schema.json +4 -9
  63. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +0 -784
  64. package/crates/team-agent/src/model/permissions.rs +0 -721
@@ -6,9 +6,7 @@ use crate::lifecycle::launch::pi_mcp::{parse_pi_list_models_table, select_exact_
6
6
  use crate::model::enums::ProviderEffort;
7
7
  use crate::model::yaml::Value;
8
8
  use crate::model::ModelError;
9
- use crate::provider::adapters::pi::{
10
- build_pi_command_argv, pi_tool_mapping, PiCommandRequest, PiSessionSelector, PiToolMapping,
11
- };
9
+ use crate::provider::adapters::pi::{build_pi_command_argv, PiCommandRequest, PiSessionSelector};
12
10
 
13
11
  const CATALOG: &[u8] = include_bytes!("fixtures/pi_list_models_g0.stdout.txt");
14
12
  static NEXT_ROLE: AtomicU32 = AtomicU32::new(0);
@@ -51,13 +49,12 @@ fn compile_error(result: Result<CompiledRole, ModelError>) -> ModelError {
51
49
  }
52
50
  }
53
51
 
54
- fn command(tools: &[&str], effort: ProviderEffort, model: &str) -> Result<Vec<String>, String> {
52
+ fn command(_tools: &[&str], effort: ProviderEffort, model: &str) -> Result<Vec<String>, String> {
55
53
  build_pi_command_argv(PiCommandRequest {
56
54
  extension: Path::new("/workspace/.team/runtime/pi/t1/pi-worker/team-mcp.ts"),
57
55
  model: Some(model),
58
56
  effort: Some(effort),
59
57
  system_prompt: "Pi worker contract.",
60
- tool_categories: tools,
61
58
  session_dir: Some(Path::new(
62
59
  "/workspace/.team/runtime/pi/t1/pi-worker/sessions",
63
60
  )),
@@ -70,24 +67,22 @@ fn command(tools: &[&str], effort: ProviderEffort, model: &str) -> Result<Vec<St
70
67
  }
71
68
 
72
69
  #[test]
73
- fn pi_role_requires_mcp_team_and_preserves_provider_defaults() {
70
+ fn pi_role_ignores_tools_and_preserves_provider_defaults() {
74
71
  let positive = compile_pi_role(&valid_role_with(""));
75
72
  assert!(positive.is_ok(), "fully explicit Pi role must compile");
76
73
 
77
- let missing_mcp = valid_role_with("").replace(" - mcp_team\n", "");
74
+ let missing_tools = valid_role_with("").replace(
75
+ "tools:\n - mcp_team\n - fs_read\n - fs_list\n - fs_write\n - execute_bash\n",
76
+ "",
77
+ );
78
+ compile_pi_role(&missing_tools).expect("omitted tools metadata must compile");
78
79
  let unqualified =
79
80
  valid_role_with("").replace("model: team-agent/qwen3.8-27b", "model: qwen3.8-27b");
80
-
81
- for (label, role) in [
82
- ("mcp_team", missing_mcp),
83
- ("qualified exact model", unqualified),
84
- ] {
85
- let error = compile_error(compile_pi_role(&role));
86
- assert!(
87
- error.to_string().to_ascii_lowercase().contains(label),
88
- "error must name {label}; got {error}"
89
- );
90
- }
81
+ let error = compile_error(compile_pi_role(&unqualified));
82
+ assert!(
83
+ error.to_string().to_ascii_lowercase().contains("qualified exact model"),
84
+ "error must name qualified model; got {error}"
85
+ );
91
86
 
92
87
  let defaults = valid_role_with("")
93
88
  .replace("model: team-agent/qwen3.8-27b\n", "")
@@ -195,42 +190,7 @@ fn pi_max_effort_is_supported_without_model_suffix() {
195
190
  }
196
191
 
197
192
  #[test]
198
- fn pi_role_tools_validate_team_capabilities_without_replacing_direct_pi_tools() {
199
- assert_eq!(pi_tool_mapping("mcp_team"), PiToolMapping::Mcp);
200
- assert_eq!(
201
- pi_tool_mapping("fs_read"),
202
- PiToolMapping::Builtin(&["read"])
203
- );
204
- assert_eq!(
205
- pi_tool_mapping("fs_list"),
206
- PiToolMapping::Builtin(&["grep", "find", "ls"])
207
- );
208
- assert_eq!(
209
- pi_tool_mapping("fs_write"),
210
- PiToolMapping::Builtin(&["edit", "write"])
211
- );
212
- assert_eq!(
213
- pi_tool_mapping("execute_bash"),
214
- PiToolMapping::Builtin(&["bash"])
215
- );
216
-
217
- for unsupported in ["git_diff", "network", "provider_builtin", "not_a_tool"] {
218
- assert_eq!(
219
- pi_tool_mapping(unsupported),
220
- PiToolMapping::Unsupported,
221
- "unknown categories must not silently broaden to bash"
222
- );
223
- assert!(
224
- command(
225
- &["mcp_team", unsupported],
226
- ProviderEffort::Medium,
227
- "team-agent/qwen3.8-27b"
228
- )
229
- .is_err(),
230
- "unsupported tool must refuse the command: {unsupported}"
231
- );
232
- }
233
-
193
+ fn pi_role_command_ignores_legacy_tool_metadata() {
234
194
  let argv = command(
235
195
  &["mcp_team", "fs_write", "fs_read", "fs_list", "execute_bash"],
236
196
  ProviderEffort::Medium,
@@ -242,34 +202,3 @@ fn pi_role_tools_validate_team_capabilities_without_replacing_direct_pi_tools()
242
202
  "role tools validate Team Agent capabilities but must not replace direct Pi tools: {argv:?}"
243
203
  );
244
204
  }
245
-
246
- #[test]
247
- fn pi_role_rejects_unsupported_categories_during_compile() {
248
- compile_pi_role(&valid_role_with("")).expect("supported Pi tools must compile");
249
-
250
- for unsupported in ["git_diff", "network", "provider_builtin"] {
251
- let role = valid_role_with("").replace(
252
- " - execute_bash\n",
253
- &format!(" - execute_bash\n - {unsupported}\n"),
254
- );
255
- let error = compile_error(compile_pi_role(&role)).to_string();
256
- assert!(
257
- error.contains(&format!("Pi does not support Team Agent tool category {unsupported:?}")),
258
- "compile must reject {unsupported} before launch preparation: {error}"
259
- );
260
- assert!(
261
- error.contains("remove it from the role's tools"),
262
- "compile error must provide an actionable fix: {error}"
263
- );
264
- }
265
-
266
- let alias = valid_role_with("").replace(
267
- " - execute_bash\n",
268
- " - execute_bash\n - \"@builtin\"\n",
269
- );
270
- let error = compile_error(compile_pi_role(&alias)).to_string();
271
- assert!(
272
- error.contains("Pi does not support Team Agent tool category \"provider_builtin\""),
273
- "@builtin must use the same Pi capability contract: {error}"
274
- );
275
- }
@@ -300,7 +300,7 @@ fn pi_leader_and_teammate_body(hermetic: &HermeticTestEnv) {
300
300
  mcp_config: None,
301
301
  system_prompt: None,
302
302
  model: None,
303
- tools: &[],
303
+ dangerously_skip_permissions: false,
304
304
  profile_launch: None,
305
305
  agent_id_hint: None,
306
306
  effort: None,
@@ -572,7 +572,6 @@ fn pi_materializer_and_worker_routes_body(hermetic: &HermeticTestEnv) {
572
572
  model: None,
573
573
  effort: None,
574
574
  system_prompt: "native leader fixture",
575
- tool_categories: &["mcp_team"],
576
575
  team_mcp_tools: &["send_message"],
577
576
  mcp_config: &native_config,
578
577
  session_scope: PiSessionScope::NativeDefault,
@@ -605,7 +604,6 @@ fn pi_materializer_and_worker_routes_body(hermetic: &HermeticTestEnv) {
605
604
  model: Some("team-agent/qwen3.8-27b"),
606
605
  effort: Some(ProviderEffort::Medium),
607
606
  system_prompt: "isolated worker fixture",
608
- tool_categories: &["mcp_team"],
609
607
  team_mcp_tools: &["send_message"],
610
608
  mcp_config: &isolated_config,
611
609
  session_scope: PiSessionScope::Isolated,
@@ -633,7 +631,6 @@ fn pi_materializer_and_worker_routes_body(hermetic: &HermeticTestEnv) {
633
631
  model: None,
634
632
  effort: None,
635
633
  system_prompt: "isolated resume fixture",
636
- tool_categories: &["mcp_team"],
637
634
  team_mcp_tools: &["send_message"],
638
635
  mcp_config: &isolated_config,
639
636
  session_scope: PiSessionScope::Isolated,
@@ -664,7 +661,6 @@ fn pi_materializer_and_worker_routes_body(hermetic: &HermeticTestEnv) {
664
661
  model: None,
665
662
  effort: None,
666
663
  system_prompt: "native resume fixture",
667
- tool_categories: &["mcp_team"],
668
664
  team_mcp_tools: &["send_message"],
669
665
  mcp_config: &native_config,
670
666
  session_scope: PiSessionScope::NativeDefault,
@@ -699,7 +695,6 @@ fn pi_materializer_and_worker_routes_body(hermetic: &HermeticTestEnv) {
699
695
  model: None,
700
696
  effort: None,
701
697
  system_prompt: "isolated negative resume fixture",
702
- tool_categories: &["mcp_team"],
703
698
  team_mcp_tools: &["send_message"],
704
699
  mcp_config: &isolated_config,
705
700
  session_scope: PiSessionScope::Isolated,
@@ -58,7 +58,6 @@ fn resume_argv(path: &Path) -> Result<Vec<String>, ProviderError> {
58
58
  model: Some("team-agent/qwen3.8-27b"),
59
59
  effort: Some(ProviderEffort::Medium),
60
60
  system_prompt: "worker contract",
61
- tool_categories: &["mcp_team"],
62
61
  session_dir: Some(Path::new(
63
62
  "/workspace/.team/runtime/pi/team-a/worker-a/sessions",
64
63
  )),
@@ -901,13 +901,13 @@ impl ProviderAdapter for StaticCaptureAdapter {
901
901
  Ok(vec!["fake".to_string()])
902
902
  }
903
903
 
904
- fn build_command_with_tools(
904
+ fn build_command_with_permissions(
905
905
  &self,
906
906
  auth_mode: AuthMode,
907
907
  mcp_config: Option<&McpConfig>,
908
908
  system_prompt: Option<&str>,
909
909
  model: Option<&str>,
910
- _tools: &[&str],
910
+ _dangerously_skip_permissions: bool,
911
911
  ) -> Result<Vec<String>, ProviderError> {
912
912
  self.build_command(auth_mode, mcp_config, system_prompt, model)
913
913
  }
@@ -959,7 +959,7 @@ impl ProviderAdapter for StaticCaptureAdapter {
959
959
  mcp_config: Option<&McpConfig>,
960
960
  _system_prompt: Option<&str>,
961
961
  _model: Option<&str>,
962
- _tools: &[&str],
962
+ _dangerously_skip_permissions: bool,
963
963
  ) -> Result<Vec<String>, ProviderError> {
964
964
  self.build_resume_command(session_id, auth_mode, mcp_config)
965
965
  }
@@ -980,7 +980,7 @@ impl ProviderAdapter for StaticCaptureAdapter {
980
980
  mcp_config: Option<&McpConfig>,
981
981
  _system_prompt: Option<&str>,
982
982
  _model: Option<&str>,
983
- _tools: &[&str],
983
+ _dangerously_skip_permissions: bool,
984
984
  ) -> Result<Vec<String>, ProviderError> {
985
985
  self.fork(session_id, auth_mode, mcp_config)
986
986
  }
@@ -64,7 +64,6 @@ mod cursor_require_explicit_model_red;
64
64
  mod cursor_restart_resume_red;
65
65
  mod display_adaptive_red;
66
66
  mod f032_startup_prompt_best_effort_red;
67
- mod g1_silent_faces;
68
67
  mod grok_effort_argv_red;
69
68
  mod grok_mcp_overlay_red;
70
69
  mod grok_require_explicit_model_red;
@@ -540,8 +540,6 @@ pub struct LaunchReport {
540
540
  pub tmux_endpoint: Option<String>,
541
541
  /// 路由决策(每 task 一条;`routing.decision` 事件)。
542
542
  pub routes: Vec<RoutingDecision>,
543
- /// 权限摘要(每 agent 一条)。
544
- pub permissions: Vec<PermissionSummary>,
545
543
  /// leader receiver(attach 成功时;经 step10 leader::attach_leader_to_state)。
546
544
  pub leader_receiver_attached: bool,
547
545
  pub leader_bind_stage: Option<String>,
@@ -577,14 +575,6 @@ pub struct RoutingDecision {
577
575
  pub manual_override: bool,
578
576
  }
579
577
 
580
- /// 权限摘要(`resolve_permissions(agent)` 的 typed 版)。**PLACEHOLDER 字段**:
581
- /// 实际形状由 step6 compiler 的 `resolve_permissions` 决定,集成时映射。
582
- #[derive(Debug, Clone, PartialEq, Eq)]
583
- pub struct PermissionSummary {
584
- pub agent_id: AgentId,
585
- pub raw: serde_json::Value,
586
- }
587
-
588
578
  /// BUG-7 (0.3.1): quick-start cannot honestly report "ready" before the workers'
589
579
  /// MCP tool sets have actually loaded — provider-side schema rejections (codex
590
580
  /// invalid_function_parameters etc.) happen AFTER spawn and silently disable the
@@ -1,17 +1,13 @@
1
1
  //! ---
2
- //! purpose: 从 spec 里的一个 agent 定义,编译出 worker 启动所需的系统提示词与工具串
2
+ //! purpose: 从 spec 里的一个 agent 定义,编译出 worker 启动所需的系统提示词
3
3
  //! contract:
4
4
  //! provides:
5
5
  //! - name: WorkerCommandAgent
6
6
  //! what: 从 YAML 或 JSON 读出的单 agent 命令上下文
7
7
  //! - name: compile_worker_system_prompt
8
8
  //! what: 按身份、runtime 契约、通信模式、角色正文、输出契约、权限说明拼系统提示词
9
- //! - name: resolved_tool_strings_for_command
10
- //! what: 解析出该 agent 的工具串,声明了 bypass 时追加 dangerous_auto_approve 哨兵
11
9
  //! depends:
12
- //! - crate::model::permissions
13
10
  //! - crate::communication_mode
14
- //! - crate::provider::bypass_flags
15
11
  //! boundary:
16
12
  //! - 只产出字符串,不 spawn 进程、不写盘
17
13
  //! - bypass 只认 agent 自身声明,不从 team/runtime/leader argv 继承
@@ -22,9 +18,7 @@ use std::path::Path;
22
18
 
23
19
  use crate::communication_mode::CommunicationMode;
24
20
  use crate::lifecycle::types::LifecycleError;
25
- use crate::model::enums::{Enforcement, Provider};
26
- use crate::model::ids::AgentId;
27
- use crate::model::permissions::{resolve_permissions, AgentPermissionInput};
21
+ use crate::model::enums::Provider;
28
22
 
29
23
  const RUNTIME_CONTRACT_SECTION: &str = r#"# Team Agent Teammate Runtime Contract
30
24
 
@@ -57,7 +51,6 @@ pub(crate) struct WorkerCommandAgent {
57
51
  id: Option<String>,
58
52
  provider: Provider,
59
53
  role: Option<String>,
60
- declared_tools: Option<Vec<String>>,
61
54
  system_prompt_inline: Option<String>,
62
55
  system_prompt_file: Option<String>,
63
56
  output_contract_format: Option<String>,
@@ -94,16 +87,6 @@ impl WorkerCommandAgent {
94
87
  .get("role")
95
88
  .and_then(crate::model::yaml::Value::as_str)
96
89
  .map(str::to_string),
97
- declared_tools: agent
98
- .get("tools")
99
- .and_then(crate::model::yaml::Value::as_list)
100
- .map(|items| {
101
- items
102
- .iter()
103
- .filter_map(crate::model::yaml::Value::as_str)
104
- .map(str::to_string)
105
- .collect()
106
- }),
107
90
  system_prompt_inline: system_prompt
108
91
  .and_then(|prompt| prompt.get("inline"))
109
92
  .and_then(crate::model::yaml::Value::as_str)
@@ -158,16 +141,6 @@ impl WorkerCommandAgent {
158
141
  .get("role")
159
142
  .and_then(serde_json::Value::as_str)
160
143
  .map(str::to_string),
161
- declared_tools: agent
162
- .get("tools")
163
- .and_then(serde_json::Value::as_array)
164
- .map(|items| {
165
- items
166
- .iter()
167
- .filter_map(serde_json::Value::as_str)
168
- .map(str::to_string)
169
- .collect()
170
- }),
171
144
  system_prompt_inline: system_prompt
172
145
  .and_then(|prompt| prompt.get("inline"))
173
146
  .and_then(serde_json::Value::as_str)
@@ -233,9 +206,6 @@ pub(crate) fn compile_worker_system_prompt(
233
206
  if let Some(contract) = output_contract(agent, &report_result) {
234
207
  chunks.push(contract);
235
208
  }
236
- if let Some(notes) = permission_notes(agent)? {
237
- chunks.push(notes);
238
- }
239
209
  Ok(chunks
240
210
  .into_iter()
241
211
  .filter(|chunk| !chunk.is_empty())
@@ -254,43 +224,6 @@ Do not invent a second team protocol or assume that a configured lazy MCP server
254
224
  .to_string()
255
225
  }
256
226
 
257
- /// ---
258
- /// purpose: 解析该 agent 最终生效的工具串
259
- /// params:
260
- /// provider: 用于查 bypass argv 定义的 provider
261
- /// returns: 排序后的工具串;agent 声明 bypass 时末尾追加 dangerous_auto_approve
262
- /// errors: 权限解析失败,或声明了 bypass 但该 provider 没有 bypass argv 定义时返回 RequirementUnmet
263
- /// ---
264
- pub(crate) fn resolved_tool_strings_for_command(
265
- agent: &WorkerCommandAgent,
266
- provider: Provider,
267
- ) -> Result<Vec<String>, LifecycleError> {
268
- let mut tools: Vec<String> = resolve_agent_permissions(agent, provider)?
269
- .sorted_tool_strings()
270
- .into_iter()
271
- .map(str::to_string)
272
- .collect();
273
- // 0.5.66 bypass 单源:读 agent 自己的 `dangerously_skip_permissions` bool,
274
- // 不再收 team/runtime/leader argv 派生的 `DangerousApproval`。true → 加
275
- // `dangerous_auto_approve` 哨兵(adapter 消费点不变);provider 无 bypass
276
- // argv 定义 → fail-loud,不静默 fallback。
277
- if agent.dangerously_skip_permissions && provider != Provider::Pi {
278
- let flag = crate::provider::bypass_flags::provider_bypass_flag(provider).ok_or_else(
279
- || {
280
- LifecycleError::RequirementUnmet(format!(
281
- "provider {} has no bypass argv flag defined; dangerously_skip_permissions=true cannot be honored",
282
- provider_display_name(provider)
283
- ))
284
- },
285
- )?;
286
- debug_assert!(flag.starts_with("--"));
287
- if !tools.iter().any(|tool| tool == "dangerous_auto_approve") {
288
- tools.push("dangerous_auto_approve".to_string());
289
- }
290
- }
291
- Ok(tools)
292
- }
293
-
294
227
  /// Provider-facing MCP tool name. Only verified call forms are filled in.
295
228
  /// Unverified providers keep the historical dotted spelling.
296
229
  fn mcp_tool_name(provider: Provider, server: &str, tool: &str) -> String {
@@ -308,33 +241,6 @@ fn mcp_tool_name(provider: Provider, server: &str, tool: &str) -> String {
308
241
  }
309
242
  }
310
243
 
311
- fn provider_display_name(provider: Provider) -> &'static str {
312
- match provider {
313
- Provider::Claude => "claude",
314
- Provider::ClaudeCode => "claude_code",
315
- Provider::Codex => "codex",
316
- Provider::Copilot => "copilot",
317
- Provider::GeminiCli => "gemini_cli",
318
- Provider::Grok => "grok",
319
- Provider::CursorAgent => "cursor_agent",
320
- Provider::Pi => "pi",
321
- Provider::Fake => "fake",
322
- }
323
- }
324
-
325
- fn resolve_agent_permissions(
326
- agent: &WorkerCommandAgent,
327
- provider: Provider,
328
- ) -> Result<crate::model::permissions::ResolvedPermissions, LifecycleError> {
329
- resolve_permissions(&AgentPermissionInput {
330
- id: agent.id.as_deref().map(AgentId::new),
331
- provider,
332
- role: agent.role.clone(),
333
- tools: agent.declared_tools.clone(),
334
- })
335
- .map_err(|e| LifecycleError::Compile(e.to_string()))
336
- }
337
-
338
244
  fn runtime_contract_section(send_message: &str, report_result: &str) -> String {
339
245
  RUNTIME_CONTRACT_SECTION
340
246
  .replace("{send_message}", send_message)
@@ -405,161 +311,14 @@ fn output_contract(agent: &WorkerCommandAgent, report_result: &str) -> Option<St
405
311
  .then(|| RESULT_ENVELOPE_OUTPUT_CONTRACT.replace("{report_result}", report_result))
406
312
  }
407
313
 
408
- fn permission_notes(agent: &WorkerCommandAgent) -> Result<Option<String>, LifecycleError> {
409
- let permissions = resolve_agent_permissions(agent, agent.provider)?;
410
- // C-2-1/C-2-2 cr verdict — Copilot 一期 framework 不替决 fs_read/fs_list/git_diff/
411
- // provider_builtin(provider prompt 控);为诚实(MUST-NOT-13)在 system prompt 内
412
- // 总是声明这些 provider-level prompt_only 工具,即便角色未显式声明。
413
- let provider_prompt_only_extras = provider_default_prompt_only_tools(agent.provider);
414
- let mut prompt_only: std::collections::BTreeSet<String> = permissions
415
- .resolved_tools
416
- .iter()
417
- .filter(|tool| tool.enforcement == Enforcement::PromptOnly)
418
- .filter_map(|tool| serde_json::to_value(tool.tool).ok())
419
- .filter_map(|value| value.as_str().map(str::to_string))
420
- .collect();
421
- for tool in provider_prompt_only_extras {
422
- prompt_only.insert((*tool).to_string());
423
- }
424
- if prompt_only.is_empty() {
425
- return Ok(None);
426
- }
427
- let prompt_only: Vec<String> = prompt_only.into_iter().collect();
428
- Ok(Some(format!(
429
- "Permission note: these tools are prompt-only for this provider and not hard-enforced: {}",
430
- prompt_only.join(", ")
431
- )))
432
- }
433
-
434
- /// C-2-1 cr verdict — provider-level prompt_only tools that the framework cannot
435
- /// hard-enforce. The system prompt declares them so the worker is honest about
436
- /// where consent gates actually live (provider prompt vs framework).
437
- fn provider_default_prompt_only_tools(provider: Provider) -> &'static [&'static str] {
438
- match provider {
439
- // C-2-1: fs_read / fs_list / git_diff / provider_builtin 由 provider prompt
440
- // 控制,framework 不替决(prompt_only 诚实声明)。
441
- Provider::Copilot => &["fs_list", "fs_read", "git_diff", "provider_builtin"],
442
- _ => &[],
443
- }
444
- }
445
-
446
314
  #[cfg(test)]
447
315
  mod tests {
448
316
  use super::*;
449
317
 
450
- #[test]
451
- fn empty_tools_use_role_defaults_and_aliases_resolve_before_command() {
452
- let agent = WorkerCommandAgent {
453
- id: Some("dev".to_string()),
454
- provider: Provider::ClaudeCode,
455
- role: Some("developer".to_string()),
456
- declared_tools: Some(Vec::new()),
457
- system_prompt_inline: None,
458
- system_prompt_file: None,
459
- output_contract_format: None,
460
- communication_mode: CommunicationMode::default(),
461
- dangerously_skip_permissions: false,
462
- };
463
- let tools =
464
- resolved_tool_strings_for_command(&agent, Provider::ClaudeCode).unwrap();
465
- assert_eq!(
466
- tools,
467
- [
468
- "execute_bash",
469
- "fs_list",
470
- "fs_read",
471
- "fs_write",
472
- "git_diff",
473
- "mcp_team",
474
- "provider_builtin"
475
- ]
476
- );
477
-
478
- let agent = WorkerCommandAgent {
479
- declared_tools: Some(vec!["fs_*".to_string(), "@team-orchestrator".to_string()]),
480
- ..agent
481
- };
482
- let tools =
483
- resolved_tool_strings_for_command(&agent, Provider::ClaudeCode).unwrap();
484
- assert_eq!(tools, ["fs_list", "fs_read", "fs_write", "mcp_team"]);
485
- }
486
-
487
- // 0.5.66 bypass 单源 §4.1:true → tools 追加 `dangerous_auto_approve` 哨兵。
488
- #[test]
489
- fn test_true_field_produces_bypass_flag_argv() {
490
- let agent = WorkerCommandAgent {
491
- id: Some("dev".to_string()),
492
- provider: Provider::ClaudeCode,
493
- role: Some("developer".to_string()),
494
- declared_tools: Some(vec!["mcp_team".to_string()]),
495
- system_prompt_inline: None,
496
- system_prompt_file: None,
497
- output_contract_format: None,
498
- communication_mode: CommunicationMode::default(),
499
- dangerously_skip_permissions: true,
500
- };
501
- let tools = resolved_tool_strings_for_command(&agent, Provider::ClaudeCode).unwrap();
502
- assert!(
503
- tools.iter().any(|tool| tool == "dangerous_auto_approve"),
504
- "true field must append the dangerous_auto_approve sentinel; tools={tools:?}"
505
- );
506
- assert!(
507
- tools.iter().any(|tool| tool == "mcp_team"),
508
- "normal tools must remain; tools={tools:?}"
509
- );
510
- }
511
-
512
- // 0.5.66 bypass 单源 §4.1:false → 不追加哨兵。
513
- #[test]
514
- fn test_false_field_produces_no_bypass_flag_argv() {
515
- let agent = WorkerCommandAgent {
516
- id: Some("dev".to_string()),
517
- provider: Provider::ClaudeCode,
518
- role: Some("developer".to_string()),
519
- declared_tools: Some(vec!["mcp_team".to_string()]),
520
- system_prompt_inline: None,
521
- system_prompt_file: None,
522
- output_contract_format: None,
523
- communication_mode: CommunicationMode::default(),
524
- dangerously_skip_permissions: false,
525
- };
526
- let tools = resolved_tool_strings_for_command(&agent, Provider::ClaudeCode).unwrap();
527
- assert!(
528
- !tools.iter().any(|tool| tool == "dangerous_auto_approve"),
529
- "false field must not append the sentinel; tools={tools:?}"
530
- );
531
- }
532
-
533
- // 0.5.66 bypass 单源 §4.1:provider 未定义 bypass 参数 + true → fail-loud。
534
- #[test]
535
- fn test_provider_without_bypass_flag_definition_errors() {
536
- let agent = WorkerCommandAgent {
537
- id: Some("dev".to_string()),
538
- provider: Provider::GeminiCli,
539
- role: Some("developer".to_string()),
540
- declared_tools: Some(vec!["mcp_team".to_string()]),
541
- system_prompt_inline: None,
542
- system_prompt_file: None,
543
- output_contract_format: None,
544
- communication_mode: CommunicationMode::default(),
545
- dangerously_skip_permissions: true,
546
- };
547
- let err = resolved_tool_strings_for_command(&agent, Provider::GeminiCli).unwrap_err();
548
- let msg = err.to_string();
549
- assert!(
550
- msg.contains("no bypass argv flag defined"),
551
- "provider without bypass flag + true must fail-loud; got {msg}"
552
- );
553
- assert!(
554
- msg.contains("gemini_cli"),
555
- "error must name the provider; got {msg}"
556
- );
557
- }
558
-
559
318
  #[test]
560
319
  fn system_prompt_uses_identity_then_runtime_contract_python_order() {
561
320
  // #264 D6: Python truth source (prompt.py:39, live ps confirmed) builds
562
- // chunks = [identity, TEAMMATE_SYSTEM_PROMPT, role_body, output, permissions].
321
+ // chunks = [identity, TEAMMATE_SYSTEM_PROMPT, role_body, output].
563
322
  // The previous assertion locked the inverted contract-first order with no
564
323
  // Python evidence; this is the corrected golden.
565
324
  // 0.3.5 union (copilot v2 C-1 / B2 MUST-4 行为层守): identity 必须 FIRST —
@@ -569,7 +328,6 @@ mod tests {
569
328
  id: Some("coder".to_string()),
570
329
  provider: Provider::Codex,
571
330
  role: Some("Runtime Developer".to_string()),
572
- declared_tools: Some(vec!["mcp_team".to_string()]),
573
331
  system_prompt_inline: Some("Implement the assigned slice.".to_string()),
574
332
  system_prompt_file: None,
575
333
  output_contract_format: Some("result_envelope_v1".to_string()),
@@ -590,8 +348,7 @@ mod tests {
590
348
  let output = prompt
591
349
  .find("Final completion must call team_orchestrator.report_result exactly once")
592
350
  .unwrap();
593
- let permissions = prompt.find("Permission note:").unwrap();
594
- assert!(identity < runtime && runtime < role && role < output && output < permissions);
351
+ assert!(identity < runtime && runtime < role && role < output);
595
352
  let slowdown_phrase = format!("500/{}", 500 + 29);
596
353
  assert!(prompt.contains(&slowdown_phrase));
597
354
  assert!(prompt.contains("Runtime Developer"));
@@ -605,7 +362,6 @@ mod tests {
605
362
  id: Some("worker".to_string()),
606
363
  provider,
607
364
  role: Some("developer".to_string()),
608
- declared_tools: Some(vec!["mcp_team".to_string()]),
609
365
  system_prompt_inline: Some("worker body".to_string()),
610
366
  system_prompt_file: None,
611
367
  output_contract_format: Some("result_envelope_v1".to_string()),