@team-agent/installer 0.5.65 → 0.5.68

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 (208) hide show
  1. package/Cargo.lock +8 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/Cargo.toml +6 -0
  4. package/crates/team-agent/src/cli/adapters.rs +6 -1
  5. package/crates/team-agent/src/cli/diagnose.rs +82 -0
  6. package/crates/team-agent/src/cli/emit.rs +3 -3
  7. package/crates/team-agent/src/cli/grok_slot.rs +299 -0
  8. package/crates/team-agent/src/cli/leader.rs +21 -7
  9. package/crates/team-agent/src/cli/leaders.rs +125 -1
  10. package/crates/team-agent/src/cli/mod.rs +20 -0
  11. package/crates/team-agent/src/cli/send/presentation.rs +7 -1
  12. package/crates/team-agent/src/cli/spec.rs +2 -0
  13. package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
  14. package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
  15. package/crates/team-agent/src/cli/tests/base.rs +26 -3
  16. package/crates/team-agent/src/cli/tests/compile.rs +1 -1
  17. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
  18. package/crates/team-agent/src/cli/tests/mod.rs +2 -2
  19. package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -1
  20. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
  21. package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
  22. package/crates/team-agent/src/cli/tests/verb_validate.rs +1 -1
  23. package/crates/team-agent/src/communication_mode/mod.rs +6 -4
  24. package/crates/team-agent/src/compiler/tests.rs +23 -20
  25. package/crates/team-agent/src/compiler.rs +84 -5
  26. package/crates/team-agent/src/conpty/backend.rs +16 -0
  27. package/crates/team-agent/src/coordinator/backoff.rs +55 -0
  28. package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
  29. package/crates/team-agent/src/coordinator/health.rs +173 -0
  30. package/crates/team-agent/src/coordinator/mod.rs +31 -0
  31. package/crates/team-agent/src/coordinator/orphan.rs +31 -0
  32. package/crates/team-agent/src/coordinator/runtime_detectors.rs +30 -0
  33. package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
  34. package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
  35. package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
  36. package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
  37. package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
  38. package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
  39. package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
  40. package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
  41. package/crates/team-agent/src/coordinator/tick.rs +133 -22
  42. package/crates/team-agent/src/coordinator/types.rs +49 -0
  43. package/crates/team-agent/src/db/message_store.rs +39 -5
  44. package/crates/team-agent/src/layout/worker_env.rs +20 -3
  45. package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
  46. package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
  47. package/crates/team-agent/src/leader/registry.rs +65 -0
  48. package/crates/team-agent/src/leader/start.rs +1080 -58
  49. package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1 -1
  50. package/crates/team-agent/src/lifecycle/display.rs +56 -0
  51. package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
  52. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +332 -14
  53. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +69 -2
  54. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +196 -2
  55. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
  56. package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
  57. package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +332 -0
  58. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +204 -457
  59. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
  60. package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
  61. package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
  62. package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
  63. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
  64. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +530 -0
  65. package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
  66. package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
  67. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
  68. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +78 -0
  69. package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
  70. package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
  71. package/crates/team-agent/src/lifecycle/launch/spawn.rs +101 -4
  72. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +244 -48
  73. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -2
  74. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
  75. package/crates/team-agent/src/lifecycle/launch.rs +209 -43
  76. package/crates/team-agent/src/lifecycle/lock.rs +42 -1
  77. package/crates/team-agent/src/lifecycle/mod.rs +11 -0
  78. package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
  79. package/crates/team-agent/src/lifecycle/profile_launch.rs +98 -0
  80. package/crates/team-agent/src/lifecycle/profile_smoke.rs +51 -1
  81. package/crates/team-agent/src/lifecycle/restart/agent.rs +213 -3
  82. package/crates/team-agent/src/lifecycle/restart/common.rs +373 -18
  83. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
  84. package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
  85. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +165 -15
  86. package/crates/team-agent/src/lifecycle/restart/remove.rs +196 -5
  87. package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
  88. package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
  89. package/crates/team-agent/src/lifecycle/restart.rs +29 -0
  90. package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1 -1
  91. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +3 -4
  92. package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +1 -1
  93. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +2 -2
  94. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +4 -4
  95. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
  96. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +159 -174
  97. package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +2 -2
  98. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +15 -13
  99. package/crates/team-agent/src/lifecycle/tests/core.rs +4 -18
  100. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +1 -1
  101. package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +287 -0
  102. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +229 -0
  103. package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
  104. package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +1 -1
  105. package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +1 -1
  106. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +784 -0
  107. package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
  108. package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
  109. package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +498 -0
  110. package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
  111. package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
  112. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +3 -0
  113. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +3 -3
  114. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +65 -40
  115. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +20 -32
  116. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +17 -9
  117. package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
  118. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +24 -23
  119. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +98 -62
  120. package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +2 -2
  121. package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +1 -1
  122. package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +1 -1
  123. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +3 -1
  124. package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -2
  125. package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +1 -1
  126. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +1 -1
  127. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +41 -8
  128. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +3 -3
  129. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1 -1
  130. package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +1 -1
  131. package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +1 -1
  132. package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1 -1
  133. package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1 -1
  134. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +1 -1
  135. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +60 -2
  136. package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +1 -1
  137. package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +2 -2
  138. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
  139. package/crates/team-agent/src/lifecycle/tests.rs +23 -15
  140. package/crates/team-agent/src/lifecycle/types.rs +61 -2
  141. package/crates/team-agent/src/lifecycle/worker_command_context.rs +200 -33
  142. package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
  143. package/crates/team-agent/src/mcp_server/tests.rs +196 -2
  144. package/crates/team-agent/src/messaging/delivery.rs +405 -40
  145. package/crates/team-agent/src/messaging/helpers.rs +2 -0
  146. package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
  147. package/crates/team-agent/src/messaging/results.rs +4 -0
  148. package/crates/team-agent/src/messaging/send.rs +34 -0
  149. package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
  150. package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
  151. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
  152. package/crates/team-agent/src/messaging/tests/main_preserved.rs +1 -1
  153. package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
  154. package/crates/team-agent/src/messaging/types.rs +4 -1
  155. package/crates/team-agent/src/model/enums.rs +14 -5
  156. package/crates/team-agent/src/model/permissions.rs +3 -0
  157. package/crates/team-agent/src/model/spec.rs +129 -6
  158. package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +3 -1
  159. package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +3 -1
  160. package/crates/team-agent/src/model/testdata/team.spec.yaml +3 -1
  161. package/crates/team-agent/src/os_probe.rs +73 -2
  162. package/crates/team-agent/src/provider/adapter.rs +231 -4
  163. package/crates/team-agent/src/provider/adapters/claude.rs +5 -2
  164. package/crates/team-agent/src/provider/adapters/codex.rs +5 -2
  165. package/crates/team-agent/src/provider/adapters/copilot.rs +5 -2
  166. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +86 -0
  167. package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
  168. package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
  169. package/crates/team-agent/src/provider/bypass_flags.rs +124 -0
  170. package/crates/team-agent/src/provider/classify.rs +4 -2
  171. package/crates/team-agent/src/provider/faults.rs +2 -1
  172. package/crates/team-agent/src/provider/mod.rs +13 -0
  173. package/crates/team-agent/src/provider/session/capture.rs +158 -20
  174. package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
  175. package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
  176. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
  177. package/crates/team-agent/src/provider/session/context_fork.rs +77 -3
  178. package/crates/team-agent/src/provider/session/mod.rs +18 -0
  179. package/crates/team-agent/src/provider/session/resume.rs +57 -0
  180. package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
  181. package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
  182. package/crates/team-agent/src/provider/session_scan/common.rs +204 -2
  183. package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
  184. package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
  185. package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
  186. package/crates/team-agent/src/provider/session_scan.rs +8 -0
  187. package/crates/team-agent/src/provider/submit_now.rs +94 -0
  188. package/crates/team-agent/src/provider/tests/adapter.rs +304 -0
  189. package/crates/team-agent/src/provider/types.rs +4 -2
  190. package/crates/team-agent/src/provider/wire.rs +23 -1
  191. package/crates/team-agent/src/state/persist.rs +301 -8
  192. package/crates/team-agent/src/state/repository.rs +5 -0
  193. package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
  194. package/crates/team-agent/src/tmux_backend.rs +1083 -75
  195. package/crates/team-agent/src/transport/test_support.rs +20 -0
  196. package/crates/team-agent/src/transport/tests/wire.rs +28 -2
  197. package/crates/team-agent/src/transport.rs +265 -1
  198. package/npm/install.mjs +129 -73
  199. package/package.json +4 -4
  200. package/skills/team-agent/SKILL.md +33 -238
  201. package/skills/team-agent/command-coverage.json +31 -0
  202. package/crates/team-agent/src/lifecycle/launch/approval.rs +0 -134
  203. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
  204. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -483
  205. package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
  206. package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
  207. package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +0 -1187
  208. package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +0 -327
@@ -0,0 +1,309 @@
1
+ //! ---
2
+ //! purpose: 回归——clone-agent 分身 tools 必须与源席集合相等,禁止静默夹成 leader 三件套
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: clone_agent_preserves_source_tools
6
+ //! what: 真跑 clone-agent 后,dynamic-role-files / runtime spec 与源席 tools 集合相等
7
+ //! requires:
8
+ //! - name: source-six-set-vs-leader-three-set
9
+ //! what: 同一 team 里 leader 三件套 + 源席六件套的共享冲突面
10
+ //! boundary:
11
+ //! - 不扩权:源席故意三件套时分身仍是那三件
12
+ //! - 不改 add-agent / approval / clone 产品路径
13
+ //! maturity: wired
14
+ //! ---
15
+
16
+ #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
17
+
18
+ use std::collections::BTreeSet;
19
+ use std::path::{Path, PathBuf};
20
+ use std::process::Output;
21
+
22
+ #[path = "../../../tests/support/hermetic.rs"]
23
+ mod hermetic_guard;
24
+ use hermetic_guard::HermeticTestEnv;
25
+
26
+ use team_agent::model::yaml::Value;
27
+
28
+ const TEAM_NAME: &str = "g1ct";
29
+ const SOURCE: &str = "src_worker";
30
+ const CLONE: &str = "new_worker";
31
+ const NARROW: &str = "narrow_src";
32
+ const NARROW_CLONE: &str = "narrow_clone";
33
+
34
+ const SIX: &[&str] = &[
35
+ "execute_bash",
36
+ "fs_list",
37
+ "fs_read",
38
+ "fs_write",
39
+ "mcp_team",
40
+ "provider_builtin",
41
+ ];
42
+ const THREE: &[&str] = &["fs_list", "fs_read", "mcp_team"];
43
+
44
+ struct Case {
45
+ env: HermeticTestEnv,
46
+ workspace: PathBuf,
47
+ }
48
+
49
+ impl Case {
50
+ fn start() -> Self {
51
+ ensure_team_agent_cli();
52
+ let env = HermeticTestEnv::enter("g1ct");
53
+ let workspace = env.workspace("ws");
54
+ write_team_docs(&workspace);
55
+ Self { env, workspace }
56
+ }
57
+
58
+ fn ws(&self) -> &str {
59
+ self.workspace.to_str().expect("ws utf8")
60
+ }
61
+
62
+ fn run(&self, args: &[&str]) -> Output {
63
+ self.env.run_cli(&self.workspace, args)
64
+ }
65
+
66
+ fn shutdown(&self) {
67
+ let _ = self.run(&[
68
+ "shutdown",
69
+ "--workspace",
70
+ self.ws(),
71
+ "--team",
72
+ TEAM_NAME,
73
+ "--json",
74
+ ]);
75
+ }
76
+ }
77
+
78
+ /// `cargo test --lib` does not build `bin/team-agent`. Hermetic CLI
79
+ /// lookup then panics and impl_check.sh sees 1 FAILED. Build the bin
80
+ /// into the same CARGO_TARGET_DIR the lib test already uses.
81
+ fn ensure_team_agent_cli() {
82
+ if let Ok(path) = std::env::var("CARGO_BIN_EXE_team-agent") {
83
+ assert!(
84
+ std::path::Path::new(&path).is_file(),
85
+ "CARGO_BIN_EXE_team-agent missing: {path}"
86
+ );
87
+ return;
88
+ }
89
+ let bin = std::env::current_exe()
90
+ .expect("test exe")
91
+ .parent()
92
+ .and_then(|deps| deps.parent())
93
+ .map(|target| target.join("team-agent"))
94
+ .expect("target/debug/team-agent");
95
+ if bin.is_file() {
96
+ return;
97
+ }
98
+ let manifest = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR");
99
+ let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
100
+ let status = std::process::Command::new(&cargo)
101
+ .args([
102
+ "build",
103
+ "-p",
104
+ "team-agent",
105
+ "--bin",
106
+ "team-agent",
107
+ "--manifest-path",
108
+ ])
109
+ .arg(std::path::Path::new(&manifest).join("Cargo.toml"))
110
+ .status()
111
+ .unwrap_or_else(|e| panic!("spawn {cargo} build: {e}"));
112
+ assert!(
113
+ status.success(),
114
+ "{cargo} build -p team-agent --bin team-agent failed: {status}"
115
+ );
116
+ assert!(
117
+ bin.is_file(),
118
+ "team-agent still missing after cargo build: {}",
119
+ bin.display()
120
+ );
121
+ }
122
+
123
+ fn write_team_docs(workspace: &Path) {
124
+ std::fs::create_dir_all(workspace.join("agents")).expect("agents dir");
125
+ std::fs::write(
126
+ workspace.join("TEAM.md"),
127
+ format!(
128
+ "---\nname: {TEAM_NAME}\nobjective: clone-agent tools preservation\nprovider: fake\ndisplay_backend: none\n---\n"
129
+ ),
130
+ )
131
+ .expect("TEAM.md");
132
+ write_role(workspace, SOURCE, SIX);
133
+ write_role(workspace, NARROW, THREE);
134
+ }
135
+
136
+ fn write_role(workspace: &Path, name: &str, tools: &[&str]) {
137
+ let tools_yaml = tools
138
+ .iter()
139
+ .map(|tool| format!(" - {tool}"))
140
+ .collect::<Vec<_>>()
141
+ .join("\n");
142
+ std::fs::write(
143
+ workspace.join("agents").join(format!("{name}.md")),
144
+ format!(
145
+ "---\nname: {name}\nrole: {name}\nprovider: fake\nmodel: fake\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n{tools_yaml}\n---\n\n{name} body.\n"
146
+ ),
147
+ )
148
+ .expect("role doc");
149
+ }
150
+
151
+ fn role_tools(path: &Path) -> BTreeSet<String> {
152
+ let (meta, _) = team_agent::compiler::read_front_matter(path)
153
+ .unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
154
+ let items = meta
155
+ .get("tools")
156
+ .and_then(Value::as_list)
157
+ .unwrap_or_else(|| panic!("{} missing tools list", path.display()));
158
+ items
159
+ .iter()
160
+ .filter_map(Value::as_str)
161
+ .map(str::to_string)
162
+ .collect()
163
+ }
164
+
165
+ fn load_runtime_spec(workspace: &Path) -> Value {
166
+ let runtime = workspace.join(".team").join("runtime");
167
+ let spec = std::fs::read_dir(&runtime)
168
+ .expect("runtime dir")
169
+ .flatten()
170
+ .map(|entry| entry.path().join("team.spec.yaml"))
171
+ .find(|path| path.is_file())
172
+ .unwrap_or_else(|| panic!("runtime spec missing"));
173
+ team_agent::model::yaml::loads(&std::fs::read_to_string(&spec).expect("spec"))
174
+ .expect("parse spec")
175
+ }
176
+
177
+ fn yaml_tool_set(node: &Value) -> BTreeSet<String> {
178
+ node.get("tools")
179
+ .and_then(Value::as_list)
180
+ .unwrap_or_else(|| panic!("tools list missing"))
181
+ .iter()
182
+ .filter_map(Value::as_str)
183
+ .map(str::to_string)
184
+ .collect()
185
+ }
186
+
187
+ fn spec_tools(workspace: &Path, agent: &str) -> BTreeSet<String> {
188
+ let parsed = load_runtime_spec(workspace);
189
+ let agents = parsed
190
+ .get("agents")
191
+ .and_then(Value::as_list)
192
+ .expect("spec agents");
193
+ for row in agents {
194
+ if row.get("id").and_then(Value::as_str) == Some(agent) {
195
+ return yaml_tool_set(row);
196
+ }
197
+ }
198
+ panic!("agent {agent} missing from runtime spec");
199
+ }
200
+
201
+ fn leader_spec_tools(workspace: &Path) -> BTreeSet<String> {
202
+ yaml_tool_set(
203
+ load_runtime_spec(workspace)
204
+ .get("leader")
205
+ .expect("spec leader"),
206
+ )
207
+ }
208
+
209
+ fn set_of(items: &[&str]) -> BTreeSet<String> {
210
+ items.iter().map(|s| (*s).to_string()).collect()
211
+ }
212
+
213
+ fn clone_ok(case: &Case, source: &str, dest: &str) {
214
+ let out = case.run(&[
215
+ "clone-agent",
216
+ source,
217
+ "--as",
218
+ dest,
219
+ "--workspace",
220
+ case.ws(),
221
+ "--team",
222
+ TEAM_NAME,
223
+ "--no-display",
224
+ "--json",
225
+ ]);
226
+ let stderr = String::from_utf8_lossy(&out.stderr);
227
+ let stdout = String::from_utf8_lossy(&out.stdout);
228
+ assert!(
229
+ out.status.success(),
230
+ "clone-agent {source} --as {dest} must exit 0; code={:?} stderr={stderr} stdout={stdout}",
231
+ out.status.code()
232
+ );
233
+ }
234
+
235
+ /// clone 后分身 role + spec tools 必须等于源席;三件套源不得被扩成六件。
236
+ #[test]
237
+ fn clone_agent_preserves_source_tools() {
238
+ let case = Case::start();
239
+ let qs = case.run(&[
240
+ "quick-start",
241
+ case.ws(),
242
+ "--workspace",
243
+ case.ws(),
244
+ "--name",
245
+ TEAM_NAME,
246
+ "--yes",
247
+ "--json",
248
+ ]);
249
+ let spec = case
250
+ .workspace
251
+ .join(".team")
252
+ .join("runtime")
253
+ .read_dir()
254
+ .ok()
255
+ .and_then(|entries| {
256
+ entries
257
+ .flatten()
258
+ .find(|e| e.path().join("team.spec.yaml").is_file())
259
+ });
260
+ assert!(
261
+ spec.is_some(),
262
+ "quick-start must leave a runtime spec (fixture team); stderr={} stdout={}",
263
+ String::from_utf8_lossy(&qs.stderr),
264
+ String::from_utf8_lossy(&qs.stdout)
265
+ );
266
+
267
+ let source_spec = spec_tools(&case.workspace, SOURCE);
268
+ assert_eq!(
269
+ source_spec,
270
+ set_of(SIX),
271
+ "fixture source must be the six-set"
272
+ );
273
+ let leader_spec = leader_spec_tools(&case.workspace);
274
+ assert_eq!(
275
+ leader_spec,
276
+ set_of(THREE),
277
+ "leader must stay the three-set ceiling so the conflict surface exists; leader={leader_spec:?}"
278
+ );
279
+
280
+ clone_ok(&case, SOURCE, CLONE);
281
+
282
+ let clone_role = role_tools(
283
+ &case
284
+ .workspace
285
+ .join(".team")
286
+ .join("dynamic-role-files")
287
+ .join(format!("{CLONE}.md")),
288
+ );
289
+ let clone_spec = spec_tools(&case.workspace, CLONE);
290
+ assert_eq!(
291
+ clone_role, source_spec,
292
+ "clone role tools must equal source; source={source_spec:?} clone={clone_role:?}"
293
+ );
294
+ assert_eq!(
295
+ clone_spec, source_spec,
296
+ "clone spec tools must equal source; source={source_spec:?} spec={clone_spec:?}"
297
+ );
298
+
299
+ let narrow_src = spec_tools(&case.workspace, NARROW);
300
+ assert_eq!(narrow_src, set_of(THREE), "narrow source fixture");
301
+ clone_ok(&case, NARROW, NARROW_CLONE);
302
+ let narrow_clone = spec_tools(&case.workspace, NARROW_CLONE);
303
+ assert_eq!(
304
+ narrow_clone, narrow_src,
305
+ "narrow clone must keep the three-set (preserve, do not widen); got={narrow_clone:?}"
306
+ );
307
+
308
+ case.shutdown();
309
+ }