@team-agent/installer 0.5.41 → 0.5.43

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 (152) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +129 -54
  4. package/crates/team-agent/src/cli/diagnose.rs +1 -2
  5. package/crates/team-agent/src/cli/emit.rs +1 -7
  6. package/crates/team-agent/src/cli/helpers.rs +3 -1
  7. package/crates/team-agent/src/cli/leader.rs +2 -1
  8. package/crates/team-agent/src/cli/mod.rs +27 -7
  9. package/crates/team-agent/src/cli/named_address.rs +14 -5
  10. package/crates/team-agent/src/cli/profile.rs +19 -7
  11. package/crates/team-agent/src/cli/send.rs +9 -3
  12. package/crates/team-agent/src/cli/status.rs +8 -30
  13. package/crates/team-agent/src/cli/status_port.rs +1341 -1272
  14. package/crates/team-agent/src/cli/tests/base.rs +738 -660
  15. package/crates/team-agent/src/cli/tests/compile.rs +45 -18
  16. package/crates/team-agent/src/cli/tests/divergence.rs +462 -444
  17. package/crates/team-agent/src/cli/tests/lane_c.rs +365 -282
  18. package/crates/team-agent/src/cli/tests/leader_watch.rs +356 -329
  19. package/crates/team-agent/src/cli/tests/main_preserved.rs +672 -564
  20. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +284 -224
  21. package/crates/team-agent/src/cli/tests/mod.rs +17 -7
  22. package/crates/team-agent/src/cli/tests/named_address.rs +8 -2
  23. package/crates/team-agent/src/cli/tests/peer_allow.rs +10 -2
  24. package/crates/team-agent/src/cli/tests/run_delegation.rs +314 -273
  25. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +26 -15
  26. package/crates/team-agent/src/cli/tests/status_send.rs +707 -629
  27. package/crates/team-agent/src/cli/tests/verb_install_skill.rs +20 -4
  28. package/crates/team-agent/src/cli/tests/verb_profile.rs +46 -17
  29. package/crates/team-agent/src/cli/tests/verb_validate.rs +15 -3
  30. package/crates/team-agent/src/codex_app_server.rs +2 -5
  31. package/crates/team-agent/src/compiler/tests.rs +139 -33
  32. package/crates/team-agent/src/compiler.rs +55 -22
  33. package/crates/team-agent/src/conpty/backend.rs +23 -33
  34. package/crates/team-agent/src/coordinator/backoff.rs +2 -7
  35. package/crates/team-agent/src/coordinator/conpty_shim.rs +55 -67
  36. package/crates/team-agent/src/coordinator/health.rs +46 -31
  37. package/crates/team-agent/src/coordinator/mod.rs +3 -3
  38. package/crates/team-agent/src/coordinator/orphan.rs +22 -10
  39. package/crates/team-agent/src/coordinator/steps/abnormal.rs +47 -52
  40. package/crates/team-agent/src/coordinator/tests/abnormal.rs +55 -19
  41. package/crates/team-agent/src/coordinator/tests/basics.rs +179 -41
  42. package/crates/team-agent/src/coordinator/tests/daemon.rs +53 -13
  43. package/crates/team-agent/src/coordinator/tests/health_sync.rs +78 -19
  44. package/crates/team-agent/src/coordinator/tests/main_preserved.rs +61 -11
  45. package/crates/team-agent/src/coordinator/tests/mod.rs +33 -39
  46. package/crates/team-agent/src/coordinator/tests/spine.rs +52 -12
  47. package/crates/team-agent/src/coordinator/tests/takeover.rs +73 -15
  48. package/crates/team-agent/src/coordinator/tests/tick_core.rs +50 -15
  49. package/crates/team-agent/src/coordinator/tests/watch.rs +74 -20
  50. package/crates/team-agent/src/coordinator/tick.rs +19 -1
  51. package/crates/team-agent/src/db/message_store.rs +138 -30
  52. package/crates/team-agent/src/db/migration.rs +249 -61
  53. package/crates/team-agent/src/db/schema.rs +303 -82
  54. package/crates/team-agent/src/diagnose/comms.rs +9 -2
  55. package/crates/team-agent/src/diagnose/mod.rs +1 -3
  56. package/crates/team-agent/src/diagnose/orphans.rs +79 -61
  57. package/crates/team-agent/src/event_log.rs +70 -16
  58. package/crates/team-agent/src/layout/manager.rs +15 -4
  59. package/crates/team-agent/src/layout/mod.rs +4 -4
  60. package/crates/team-agent/src/layout/overlay.rs +10 -3
  61. package/crates/team-agent/src/layout/placement.rs +5 -1
  62. package/crates/team-agent/src/layout/recovery.rs +4 -2
  63. package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
  64. package/crates/team-agent/src/layout/sessions.rs +17 -9
  65. package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
  66. package/crates/team-agent/src/layout/worker_env.rs +52 -19
  67. package/crates/team-agent/src/leader/helpers.rs +7 -1
  68. package/crates/team-agent/src/leader/lease.rs +195 -82
  69. package/crates/team-agent/src/leader/owner_bind.rs +55 -22
  70. package/crates/team-agent/src/leader/rediscover/tests.rs +88 -24
  71. package/crates/team-agent/src/leader/rediscover.rs +74 -25
  72. package/crates/team-agent/src/leader/start.rs +75 -54
  73. package/crates/team-agent/src/leader/takeover.rs +46 -11
  74. package/crates/team-agent/src/leader/tests/basics.rs +320 -167
  75. package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
  76. package/crates/team-agent/src/leader/tests/identity.rs +428 -356
  77. package/crates/team-agent/src/leader/tests/idle.rs +285 -254
  78. package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
  79. package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
  80. package/crates/team-agent/src/leader/tests/mod.rs +115 -99
  81. package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
  82. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
  83. package/crates/team-agent/src/lib.rs +4 -4
  84. package/crates/team-agent/src/lifecycle/display.rs +7 -3
  85. package/crates/team-agent/src/lifecycle/launch.rs +6 -1
  86. package/crates/team-agent/src/lifecycle/mod.rs +9 -1
  87. package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
  88. package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
  89. package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
  90. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
  91. package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
  92. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
  93. package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
  94. package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
  95. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
  96. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
  97. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
  98. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
  99. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
  100. package/crates/team-agent/src/lifecycle/tests.rs +2 -2
  101. package/crates/team-agent/src/main.rs +4 -4
  102. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
  103. package/crates/team-agent/src/mcp_server/mod.rs +11 -2
  104. package/crates/team-agent/src/mcp_server/types.rs +14 -3
  105. package/crates/team-agent/src/mcp_server/wire.rs +230 -68
  106. package/crates/team-agent/src/messaging/delivery.rs +20 -18
  107. package/crates/team-agent/src/messaging/helpers.rs +25 -4
  108. package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
  109. package/crates/team-agent/src/messaging/mod.rs +2 -3
  110. package/crates/team-agent/src/messaging/selftest.rs +46 -26
  111. package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
  112. package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
  113. package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
  114. package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
  115. package/crates/team-agent/src/messaging/trust.rs +25 -2
  116. package/crates/team-agent/src/messaging/watchers.rs +37 -9
  117. package/crates/team-agent/src/model/enums.rs +65 -16
  118. package/crates/team-agent/src/model/ids.rs +12 -3
  119. package/crates/team-agent/src/model/paths.rs +28 -7
  120. package/crates/team-agent/src/model/permissions.rs +176 -33
  121. package/crates/team-agent/src/model/routing.rs +66 -20
  122. package/crates/team-agent/src/model/spec.rs +365 -69
  123. package/crates/team-agent/src/model/task_graph.rs +36 -9
  124. package/crates/team-agent/src/model/yaml/tests.rs +24 -6
  125. package/crates/team-agent/src/model/yaml.rs +7 -6
  126. package/crates/team-agent/src/packaging/install.rs +23 -9
  127. package/crates/team-agent/src/packaging/migrate.rs +5 -7
  128. package/crates/team-agent/src/packaging/mod.rs +9 -1
  129. package/crates/team-agent/src/packaging/repair.rs +13 -6
  130. package/crates/team-agent/src/packaging/tests.rs +63 -16
  131. package/crates/team-agent/src/packaging/types.rs +22 -7
  132. package/crates/team-agent/src/platform/argv.rs +4 -1
  133. package/crates/team-agent/src/platform/file_lock.rs +22 -8
  134. package/crates/team-agent/src/platform/process.rs +21 -22
  135. package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
  136. package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
  137. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
  138. package/crates/team-agent/src/provider/classify.rs +127 -42
  139. package/crates/team-agent/src/provider/faults.rs +17 -5
  140. package/crates/team-agent/src/provider/helpers.rs +6 -5
  141. package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
  142. package/crates/team-agent/src/state/repository.rs +27 -14
  143. package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
  144. package/crates/team-agent/src/tmux_backend.rs +80 -44
  145. package/crates/team-agent/src/topology.rs +27 -20
  146. package/crates/team-agent/src/transport/test_support.rs +20 -23
  147. package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
  148. package/crates/team-agent/src/transport/tests/mod.rs +178 -187
  149. package/crates/team-agent/src/transport/tests/wire.rs +561 -525
  150. package/crates/team-agent/src/transport.rs +12 -17
  151. package/crates/team-agent/src/transport_factory.rs +29 -14
  152. package/package.json +4 -4
@@ -293,20 +293,62 @@ mod tests {
293
293
  #[test]
294
294
  fn enums_serialize_to_exact_python_strings() {
295
295
  let cases: &[(String, &str)] = &[
296
- (serde_json::to_string(&Provider::ClaudeCode).unwrap(), "\"claude_code\""),
297
- (serde_json::to_string(&Provider::GeminiCli).unwrap(), "\"gemini_cli\""),
298
- (serde_json::to_string(&AuthMode::CompatibleApi).unwrap(), "\"compatible_api\""),
299
- (serde_json::to_string(&TaskStatus::NeedsRetry).unwrap(), "\"needs_retry\""),
300
- (serde_json::to_string(&TestStatus::NotRun).unwrap(), "\"not_run\""),
301
- (serde_json::to_string(&TeamMode::SupervisorWorker).unwrap(), "\"supervisor_worker\""),
302
- (serde_json::to_string(&CommProtocol::McpInbox).unwrap(), "\"mcp_inbox\""),
303
- (serde_json::to_string(&DisplayBackend::TmuxAttach).unwrap(), "\"tmux_attach\""),
304
- (serde_json::to_string(&DisplayBackend::GhosttyWorkspace).unwrap(), "\"ghostty_workspace\""),
305
- (serde_json::to_string(&DisplayBackend::Adaptive).unwrap(), "\"adaptive\""),
306
- (serde_json::to_string(&Tool::ExecuteBash).unwrap(), "\"execute_bash\""),
307
- (serde_json::to_string(&Tool::ProviderBuiltin).unwrap(), "\"provider_builtin\""),
308
- (serde_json::to_string(&Enforcement::PromptOnly).unwrap(), "\"prompt_only\""),
309
- (serde_json::to_string(&PaneLiveness::Unknown).unwrap(), "\"unknown\""),
296
+ (
297
+ serde_json::to_string(&Provider::ClaudeCode).unwrap(),
298
+ "\"claude_code\"",
299
+ ),
300
+ (
301
+ serde_json::to_string(&Provider::GeminiCli).unwrap(),
302
+ "\"gemini_cli\"",
303
+ ),
304
+ (
305
+ serde_json::to_string(&AuthMode::CompatibleApi).unwrap(),
306
+ "\"compatible_api\"",
307
+ ),
308
+ (
309
+ serde_json::to_string(&TaskStatus::NeedsRetry).unwrap(),
310
+ "\"needs_retry\"",
311
+ ),
312
+ (
313
+ serde_json::to_string(&TestStatus::NotRun).unwrap(),
314
+ "\"not_run\"",
315
+ ),
316
+ (
317
+ serde_json::to_string(&TeamMode::SupervisorWorker).unwrap(),
318
+ "\"supervisor_worker\"",
319
+ ),
320
+ (
321
+ serde_json::to_string(&CommProtocol::McpInbox).unwrap(),
322
+ "\"mcp_inbox\"",
323
+ ),
324
+ (
325
+ serde_json::to_string(&DisplayBackend::TmuxAttach).unwrap(),
326
+ "\"tmux_attach\"",
327
+ ),
328
+ (
329
+ serde_json::to_string(&DisplayBackend::GhosttyWorkspace).unwrap(),
330
+ "\"ghostty_workspace\"",
331
+ ),
332
+ (
333
+ serde_json::to_string(&DisplayBackend::Adaptive).unwrap(),
334
+ "\"adaptive\"",
335
+ ),
336
+ (
337
+ serde_json::to_string(&Tool::ExecuteBash).unwrap(),
338
+ "\"execute_bash\"",
339
+ ),
340
+ (
341
+ serde_json::to_string(&Tool::ProviderBuiltin).unwrap(),
342
+ "\"provider_builtin\"",
343
+ ),
344
+ (
345
+ serde_json::to_string(&Enforcement::PromptOnly).unwrap(),
346
+ "\"prompt_only\"",
347
+ ),
348
+ (
349
+ serde_json::to_string(&PaneLiveness::Unknown).unwrap(),
350
+ "\"unknown\"",
351
+ ),
310
352
  (serde_json::to_string(&Backend::Pty).unwrap(), "\"pty\""),
311
353
  ];
312
354
  for (got, want) in cases {
@@ -357,7 +399,11 @@ mod tests {
357
399
  ] {
358
400
  assert!(b.has_worker_views());
359
401
  }
360
- for b in [DisplayBackend::None, DisplayBackend::TmuxAttach, DisplayBackend::Iterm] {
402
+ for b in [
403
+ DisplayBackend::None,
404
+ DisplayBackend::TmuxAttach,
405
+ DisplayBackend::Iterm,
406
+ ] {
361
407
  assert!(!b.has_worker_views());
362
408
  }
363
409
  }
@@ -367,7 +413,10 @@ mod tests {
367
413
  // compiler 发的 drift 值必须原样保留(byte 不丢),不被拒。
368
414
  let drift = "\"business_messages_and_short_summaries\"";
369
415
  let v: ReceiveWorkerOutputs = serde_json::from_str(drift).unwrap();
370
- assert_eq!(v, ReceiveWorkerOutputs::Other("business_messages_and_short_summaries".to_string()));
416
+ assert_eq!(
417
+ v,
418
+ ReceiveWorkerOutputs::Other("business_messages_and_short_summaries".to_string())
419
+ );
371
420
  assert_eq!(serde_json::to_string(&v).unwrap(), drift);
372
421
  // 已知值仍映射到 variant。
373
422
  assert_eq!(
@@ -141,8 +141,14 @@ mod tests {
141
141
 
142
142
  #[test]
143
143
  fn id_newtypes_serialize_as_bare_string() {
144
- assert_eq!(serde_json::to_string(&AgentId::new("leader")).unwrap(), "\"leader\"");
145
- assert_eq!(serde_json::from_str::<TaskId>("\"t1\"").unwrap(), TaskId::new("t1"));
144
+ assert_eq!(
145
+ serde_json::to_string(&AgentId::new("leader")).unwrap(),
146
+ "\"leader\""
147
+ );
148
+ assert_eq!(
149
+ serde_json::from_str::<TaskId>("\"t1\"").unwrap(),
150
+ TaskId::new("t1")
151
+ );
146
152
  assert_eq!(TeamKey::from("default").as_str(), "default");
147
153
  }
148
154
 
@@ -150,6 +156,9 @@ mod tests {
150
156
  fn owner_epoch_first_is_zero_and_serializes_as_int() {
151
157
  assert_eq!(OwnerEpoch::FIRST.0, 0);
152
158
  assert_eq!(serde_json::to_string(&OwnerEpoch(3)).unwrap(), "3");
153
- assert_eq!(serde_json::from_str::<OwnerEpoch>("0").unwrap(), OwnerEpoch::FIRST);
159
+ assert_eq!(
160
+ serde_json::from_str::<OwnerEpoch>("0").unwrap(),
161
+ OwnerEpoch::FIRST
162
+ );
154
163
  }
155
164
  }
@@ -137,7 +137,10 @@ mod tests {
137
137
  assert_eq!(messages_dir(ws), PathBuf::from("/ws/proj/.team/messages"));
138
138
 
139
139
  let ws2 = Path::new("/Users/alauda/x");
140
- assert_eq!(runtime_dir(ws2), PathBuf::from("/Users/alauda/x/.team/runtime"));
140
+ assert_eq!(
141
+ runtime_dir(ws2),
142
+ PathBuf::from("/Users/alauda/x/.team/runtime")
143
+ );
141
144
  }
142
145
 
143
146
  #[test]
@@ -145,7 +148,10 @@ mod tests {
145
148
  // Python: runtime_dir(Path("relative/path")) -> relative/path/.team/runtime
146
149
  // (无 resolve,纯拼接)。
147
150
  let ws = Path::new("relative/path");
148
- assert_eq!(runtime_dir(ws), PathBuf::from("relative/path/.team/runtime"));
151
+ assert_eq!(
152
+ runtime_dir(ws),
153
+ PathBuf::from("relative/path/.team/runtime")
154
+ );
149
155
  }
150
156
 
151
157
  #[test]
@@ -160,7 +166,10 @@ mod tests {
160
166
  PathBuf::from("/ws/proj")
161
167
  );
162
168
  // golden: /a/.team/runtime -> /a ; /a/b/c/.team/logs -> /a/b/c
163
- assert_eq!(team_workspace(Path::new("/a/.team/runtime")).unwrap(), PathBuf::from("/a"));
169
+ assert_eq!(
170
+ team_workspace(Path::new("/a/.team/runtime")).unwrap(),
171
+ PathBuf::from("/a")
172
+ );
164
173
  assert_eq!(
165
174
  team_workspace(Path::new("/a/b/c/.team/logs")).unwrap(),
166
175
  PathBuf::from("/a/b/c")
@@ -174,14 +183,23 @@ mod tests {
174
183
  team_workspace(Path::new("/ws/proj/teamdir")).unwrap(),
175
184
  PathBuf::from("/ws/proj")
176
185
  );
177
- assert_eq!(team_workspace(Path::new("/ws/proj/.team")).unwrap(), PathBuf::from("/ws/proj"));
178
- assert_eq!(team_workspace(Path::new("/x/y")).unwrap(), PathBuf::from("/x"));
186
+ assert_eq!(
187
+ team_workspace(Path::new("/ws/proj/.team")).unwrap(),
188
+ PathBuf::from("/ws/proj")
189
+ );
190
+ assert_eq!(
191
+ team_workspace(Path::new("/x/y")).unwrap(),
192
+ PathBuf::from("/x")
193
+ );
179
194
  }
180
195
 
181
196
  #[test]
182
197
  fn team_workspace_at_root() {
183
198
  // golden: /.team/runtime -> / (parent ".team", grandparent "/")
184
- assert_eq!(team_workspace(Path::new("/.team/runtime")).unwrap(), PathBuf::from("/"));
199
+ assert_eq!(
200
+ team_workspace(Path::new("/.team/runtime")).unwrap(),
201
+ PathBuf::from("/")
202
+ );
185
203
  }
186
204
 
187
205
  #[test]
@@ -218,7 +236,10 @@ mod tests {
218
236
  std::fs::create_dir_all(&team_dir).unwrap();
219
237
 
220
238
  assert_eq!(canonical_run_workspace(&run_ws).unwrap(), run_ws);
221
- assert_eq!(canonical_run_workspace(&team_dir).unwrap(), base.join("project"));
239
+ assert_eq!(
240
+ canonical_run_workspace(&team_dir).unwrap(),
241
+ base.join("project")
242
+ );
222
243
  }
223
244
 
224
245
  #[test]
@@ -137,7 +137,11 @@ where
137
137
  let mut out: Vec<String> = Vec::new();
138
138
  for raw in tools {
139
139
  match raw.as_ref() {
140
- "fs_*" => out.extend(["fs_read", "fs_write", "fs_list"].into_iter().map(String::from)),
140
+ "fs_*" => out.extend(
141
+ ["fs_read", "fs_write", "fs_list"]
142
+ .into_iter()
143
+ .map(String::from),
144
+ ),
141
145
  "@builtin" => out.push("provider_builtin".to_string()),
142
146
  "@team-orchestrator" | "@cao-mcp-server" => out.push("mcp_team".to_string()),
143
147
  "*" => out.extend(
@@ -306,7 +310,10 @@ pub fn missing_tools(
306
310
  let allowed = resolve_permissions(agent)?.tools;
307
311
  let required = task_required_tools(task_type, requires_tools)?;
308
312
  // required 已是去重集;按字符串字母序输出,对齐 Python。
309
- let mut missing: Vec<Tool> = required.into_iter().filter(|t| !allowed.contains(t)).collect();
313
+ let mut missing: Vec<Tool> = required
314
+ .into_iter()
315
+ .filter(|t| !allowed.contains(t))
316
+ .collect();
310
317
  missing.sort_unstable_by_key(|t| t.canonical_str());
311
318
  Ok(missing)
312
319
  }
@@ -323,7 +330,11 @@ mod tests {
323
330
  v
324
331
  }
325
332
 
326
- fn agent(provider: Provider, role: Option<&str>, tools: Option<&[&str]>) -> AgentPermissionInput {
333
+ fn agent(
334
+ provider: Provider,
335
+ role: Option<&str>,
336
+ tools: Option<&[&str]>,
337
+ ) -> AgentPermissionInput {
327
338
  AgentPermissionInput {
328
339
  id: Some(AgentId::new("x")),
329
340
  provider,
@@ -336,9 +347,15 @@ mod tests {
336
347
  #[test]
337
348
  fn expand_tools_matches_python_golden() {
338
349
  // E1
339
- assert_eq!(strs(&expand_tools(["fs_*"]).unwrap()), ["fs_list", "fs_read", "fs_write"]);
350
+ assert_eq!(
351
+ strs(&expand_tools(["fs_*"]).unwrap()),
352
+ ["fs_list", "fs_read", "fs_write"]
353
+ );
340
354
  // E2
341
- assert_eq!(strs(&expand_tools(["@builtin"]).unwrap()), ["provider_builtin"]);
355
+ assert_eq!(
356
+ strs(&expand_tools(["@builtin"]).unwrap()),
357
+ ["provider_builtin"]
358
+ );
342
359
  // E3
343
360
  assert_eq!(
344
361
  strs(&expand_tools(["*"]).unwrap()),
@@ -368,7 +385,13 @@ mod tests {
368
385
  // E7
369
386
  assert_eq!(
370
387
  strs(&expand_tools(["fs_*", "@builtin", "network"]).unwrap()),
371
- ["fs_list", "fs_read", "fs_write", "network", "provider_builtin"]
388
+ [
389
+ "fs_list",
390
+ "fs_read",
391
+ "fs_write",
392
+ "network",
393
+ "provider_builtin"
394
+ ]
372
395
  );
373
396
  // E8 — * 已含 fs_read,去重无变化
374
397
  assert_eq!(
@@ -408,15 +431,33 @@ mod tests {
408
431
  );
409
432
  assert_eq!(
410
433
  strs(&default_tools_for_role("researcher")),
411
- ["fs_list", "fs_read", "mcp_team", "network", "provider_builtin"]
434
+ [
435
+ "fs_list",
436
+ "fs_read",
437
+ "mcp_team",
438
+ "network",
439
+ "provider_builtin"
440
+ ]
412
441
  );
413
442
  assert_eq!(
414
443
  strs(&default_tools_for_role("reviewer")),
415
- ["fs_list", "fs_read", "git_diff", "mcp_team", "provider_builtin"]
444
+ [
445
+ "fs_list",
446
+ "fs_read",
447
+ "git_diff",
448
+ "mcp_team",
449
+ "provider_builtin"
450
+ ]
416
451
  );
417
452
  assert_eq!(
418
453
  strs(&default_tools_for_role("code_reviewer")),
419
- ["fs_list", "fs_read", "git_diff", "mcp_team", "provider_builtin"]
454
+ [
455
+ "fs_list",
456
+ "fs_read",
457
+ "git_diff",
458
+ "mcp_team",
459
+ "provider_builtin"
460
+ ]
420
461
  );
421
462
  let dev = [
422
463
  "execute_bash",
@@ -428,7 +469,10 @@ mod tests {
428
469
  "provider_builtin",
429
470
  ];
430
471
  assert_eq!(strs(&default_tools_for_role("developer")), dev);
431
- assert_eq!(strs(&default_tools_for_role("implementation_engineer")), dev);
472
+ assert_eq!(
473
+ strs(&default_tools_for_role("implementation_engineer")),
474
+ dev
475
+ );
432
476
  // 未知 role → developer 集。
433
477
  assert_eq!(strs(&default_tools_for_role("qa")), dev);
434
478
  }
@@ -438,18 +482,47 @@ mod tests {
438
482
  fn provider_enforcement_matches_python_matrix() {
439
483
  use Enforcement::{Hard, PromptOnly};
440
484
  // claude_code: network=prompt_only,其余 hard。
441
- assert_eq!(provider_enforcement(Provider::ClaudeCode, Tool::Network), PromptOnly);
442
- for t in [Tool::FsRead, Tool::FsWrite, Tool::FsList, Tool::ExecuteBash, Tool::GitDiff, Tool::McpTeam, Tool::ProviderBuiltin] {
485
+ assert_eq!(
486
+ provider_enforcement(Provider::ClaudeCode, Tool::Network),
487
+ PromptOnly
488
+ );
489
+ for t in [
490
+ Tool::FsRead,
491
+ Tool::FsWrite,
492
+ Tool::FsList,
493
+ Tool::ExecuteBash,
494
+ Tool::GitDiff,
495
+ Tool::McpTeam,
496
+ Tool::ProviderBuiltin,
497
+ ] {
443
498
  assert_eq!(provider_enforcement(Provider::ClaudeCode, t), Hard);
444
499
  }
445
500
  // gemini_cli: network + mcp_team = prompt_only,其余 hard。
446
- assert_eq!(provider_enforcement(Provider::GeminiCli, Tool::Network), PromptOnly);
447
- assert_eq!(provider_enforcement(Provider::GeminiCli, Tool::McpTeam), PromptOnly);
448
- for t in [Tool::FsRead, Tool::FsWrite, Tool::FsList, Tool::ExecuteBash, Tool::GitDiff, Tool::ProviderBuiltin] {
501
+ assert_eq!(
502
+ provider_enforcement(Provider::GeminiCli, Tool::Network),
503
+ PromptOnly
504
+ );
505
+ assert_eq!(
506
+ provider_enforcement(Provider::GeminiCli, Tool::McpTeam),
507
+ PromptOnly
508
+ );
509
+ for t in [
510
+ Tool::FsRead,
511
+ Tool::FsWrite,
512
+ Tool::FsList,
513
+ Tool::ExecuteBash,
514
+ Tool::GitDiff,
515
+ Tool::ProviderBuiltin,
516
+ ] {
449
517
  assert_eq!(provider_enforcement(Provider::GeminiCli, t), Hard);
450
518
  }
451
519
  // codex: 全 prompt_only。fake: 全 hard。
452
- for t in [Tool::FsRead, Tool::Network, Tool::McpTeam, Tool::ProviderBuiltin] {
520
+ for t in [
521
+ Tool::FsRead,
522
+ Tool::Network,
523
+ Tool::McpTeam,
524
+ Tool::ProviderBuiltin,
525
+ ] {
453
526
  assert_eq!(provider_enforcement(Provider::Codex, t), PromptOnly);
454
527
  assert_eq!(provider_enforcement(Provider::Fake, t), Hard);
455
528
  // claude 不在表中 → fallback prompt_only。
@@ -462,7 +535,10 @@ mod tests {
462
535
  fn resolve_permissions_leader_claude_code() {
463
536
  // R-leader-cc
464
537
  let r = resolve_permissions(&agent(Provider::ClaudeCode, Some("leader"), None)).unwrap();
465
- assert_eq!(r.sorted_tool_strings(), ["fs_list", "fs_read", "mcp_team", "provider_builtin"]);
538
+ assert_eq!(
539
+ r.sorted_tool_strings(),
540
+ ["fs_list", "fs_read", "mcp_team", "provider_builtin"]
541
+ );
466
542
  assert!(!r.has_prompt_only);
467
543
  for e in &r.resolved_tools {
468
544
  assert_eq!(e.enforcement, Enforcement::Hard);
@@ -475,7 +551,15 @@ mod tests {
475
551
  let r = resolve_permissions(&agent(Provider::Codex, Some("developer"), None)).unwrap();
476
552
  assert_eq!(
477
553
  r.sorted_tool_strings(),
478
- ["execute_bash", "fs_list", "fs_read", "fs_write", "git_diff", "mcp_team", "provider_builtin"]
554
+ [
555
+ "execute_bash",
556
+ "fs_list",
557
+ "fs_read",
558
+ "fs_write",
559
+ "git_diff",
560
+ "mcp_team",
561
+ "provider_builtin"
562
+ ]
479
563
  );
480
564
  assert!(r.has_prompt_only);
481
565
  for e in &r.resolved_tools {
@@ -486,19 +570,40 @@ mod tests {
486
570
  #[test]
487
571
  fn resolve_permissions_impl_gemini_mcp_team_prompt_only() {
488
572
  // R-impl-gem:mcp_team=prompt_only,其余 hard → has_prompt_only=true。
489
- let r = resolve_permissions(&agent(Provider::GeminiCli, Some("implementation_engineer"), None)).unwrap();
573
+ let r = resolve_permissions(&agent(
574
+ Provider::GeminiCli,
575
+ Some("implementation_engineer"),
576
+ None,
577
+ ))
578
+ .unwrap();
490
579
  assert!(r.has_prompt_only);
491
- let mcp = r.resolved_tools.iter().find(|e| e.tool == Tool::McpTeam).unwrap();
580
+ let mcp = r
581
+ .resolved_tools
582
+ .iter()
583
+ .find(|e| e.tool == Tool::McpTeam)
584
+ .unwrap();
492
585
  assert_eq!(mcp.enforcement, Enforcement::PromptOnly);
493
- let gd = r.resolved_tools.iter().find(|e| e.tool == Tool::GitDiff).unwrap();
586
+ let gd = r
587
+ .resolved_tools
588
+ .iter()
589
+ .find(|e| e.tool == Tool::GitDiff)
590
+ .unwrap();
494
591
  assert_eq!(gd.enforcement, Enforcement::Hard);
495
592
  }
496
593
 
497
594
  #[test]
498
595
  fn resolve_permissions_explicit_tools_and_norole_and_unknownprov() {
499
596
  // R-explicit:显式 tools 走 expand_tools,provider=claude_code。
500
- let r = resolve_permissions(&agent(Provider::ClaudeCode, None, Some(&["fs_*", "network"]))).unwrap();
501
- assert_eq!(r.sorted_tool_strings(), ["fs_list", "fs_read", "fs_write", "network"]);
597
+ let r = resolve_permissions(&agent(
598
+ Provider::ClaudeCode,
599
+ None,
600
+ Some(&["fs_*", "network"]),
601
+ ))
602
+ .unwrap();
603
+ assert_eq!(
604
+ r.sorted_tool_strings(),
605
+ ["fs_list", "fs_read", "fs_write", "network"]
606
+ );
502
607
  assert!(r.has_prompt_only); // network=prompt_only
503
608
 
504
609
  // R-norole:无 role → developer 默认,全 hard(claude_code)。
@@ -511,7 +616,15 @@ mod tests {
511
616
  .unwrap();
512
617
  assert_eq!(
513
618
  r2.sorted_tool_strings(),
514
- ["execute_bash", "fs_list", "fs_read", "fs_write", "git_diff", "mcp_team", "provider_builtin"]
619
+ [
620
+ "execute_bash",
621
+ "fs_list",
622
+ "fs_read",
623
+ "fs_write",
624
+ "git_diff",
625
+ "mcp_team",
626
+ "provider_builtin"
627
+ ]
515
628
  );
516
629
  assert!(!r2.has_prompt_only);
517
630
 
@@ -529,26 +642,41 @@ mod tests {
529
642
  #[test]
530
643
  fn resolve_permissions_empty_tools_falls_back_to_role_default() {
531
644
  // Python: agent.get("tools") or default → 空 list falsy → 用 role 默认。
532
- let r = resolve_permissions(&agent(Provider::ClaudeCode, Some("leader"), Some(&[]))).unwrap();
533
- assert_eq!(r.sorted_tool_strings(), ["fs_list", "fs_read", "mcp_team", "provider_builtin"]);
645
+ let r =
646
+ resolve_permissions(&agent(Provider::ClaudeCode, Some("leader"), Some(&[]))).unwrap();
647
+ assert_eq!(
648
+ r.sorted_tool_strings(),
649
+ ["fs_list", "fs_read", "mcp_team", "provider_builtin"]
650
+ );
534
651
  }
535
652
 
536
653
  // ---- task_required_tools 行为对拍(golden) ----
537
654
  #[test]
538
655
  fn task_required_tools_matches_python_golden() {
539
656
  // T1
540
- assert_eq!(strs(&task_required_tools(Some("implementation"), &[]).unwrap()), ["execute_bash", "fs_write"]);
657
+ assert_eq!(
658
+ strs(&task_required_tools(Some("implementation"), &[]).unwrap()),
659
+ ["execute_bash", "fs_write"]
660
+ );
541
661
  // T2
542
- assert_eq!(strs(&task_required_tools(Some("review"), &[]).unwrap()), ["fs_read", "git_diff"]);
662
+ assert_eq!(
663
+ strs(&task_required_tools(Some("review"), &[]).unwrap()),
664
+ ["fs_read", "git_diff"]
665
+ );
543
666
  // T3
544
- assert_eq!(strs(&task_required_tools(Some("research"), &[]).unwrap()), ["fs_read"]);
667
+ assert_eq!(
668
+ strs(&task_required_tools(Some("research"), &[]).unwrap()),
669
+ ["fs_read"]
670
+ );
545
671
  // T4
546
672
  assert_eq!(
547
673
  strs(&task_required_tools(Some("bug_fix"), &["network".to_string()]).unwrap()),
548
674
  ["execute_bash", "fs_write", "network"]
549
675
  );
550
676
  // T5 未知 type
551
- assert!(task_required_tools(Some("unknown_type"), &[]).unwrap().is_empty());
677
+ assert!(task_required_tools(Some("unknown_type"), &[])
678
+ .unwrap()
679
+ .is_empty());
552
680
  // T6 无 type
553
681
  assert!(task_required_tools(None, &[]).unwrap().is_empty());
554
682
  // T7 requires_tools 走 expand_tools(fs_* 展开)
@@ -562,13 +690,28 @@ mod tests {
562
690
  #[test]
563
691
  fn missing_tools_matches_python_golden() {
564
692
  // M1:reviewer(claude_code)缺 implementation 所需 fs_write/execute_bash。
565
- let m1 = missing_tools(&agent(Provider::ClaudeCode, Some("reviewer"), None), Some("implementation"), &[]).unwrap();
693
+ let m1 = missing_tools(
694
+ &agent(Provider::ClaudeCode, Some("reviewer"), None),
695
+ Some("implementation"),
696
+ &[],
697
+ )
698
+ .unwrap();
566
699
  assert_eq!(m1, [Tool::ExecuteBash, Tool::FsWrite]);
567
700
  // M2:developer 不缺。
568
- let m2 = missing_tools(&agent(Provider::ClaudeCode, Some("developer"), None), Some("implementation"), &[]).unwrap();
701
+ let m2 = missing_tools(
702
+ &agent(Provider::ClaudeCode, Some("developer"), None),
703
+ Some("implementation"),
704
+ &[],
705
+ )
706
+ .unwrap();
569
707
  assert!(m2.is_empty());
570
708
  // M3:researcher 缺 git_diff(review 需要),但有 fs_read。
571
- let m3 = missing_tools(&agent(Provider::ClaudeCode, Some("researcher"), None), Some("review"), &[]).unwrap();
709
+ let m3 = missing_tools(
710
+ &agent(Provider::ClaudeCode, Some("researcher"), None),
711
+ Some("review"),
712
+ &[],
713
+ )
714
+ .unwrap();
572
715
  assert_eq!(m3, [Tool::GitDiff]);
573
716
  }
574
717
  }