@team-agent/installer 0.5.68 → 0.5.70

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 (63) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/diagnose.rs +74 -0
  4. package/crates/team-agent/src/cli/emit.rs +5 -4
  5. package/crates/team-agent/src/cli/leader.rs +1 -0
  6. package/crates/team-agent/src/cli/spec.rs +1 -0
  7. package/crates/team-agent/src/compiler.rs +54 -7
  8. package/crates/team-agent/src/coordinator/runtime_detectors.rs +1 -0
  9. package/crates/team-agent/src/layout/worker_window_helpers.rs +1 -0
  10. package/crates/team-agent/src/leader/start.rs +53 -12
  11. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +67 -15
  12. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +18 -0
  13. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +1 -0
  14. package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +260 -44
  15. package/crates/team-agent/src/lifecycle/launch/cursor_mcp_iso.rs +120 -0
  16. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +5 -1
  17. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +10 -59
  18. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +838 -0
  19. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +9 -3
  20. package/crates/team-agent/src/lifecycle/launch/spawn.rs +60 -28
  21. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +1 -0
  22. package/crates/team-agent/src/lifecycle/launch.rs +10 -2
  23. package/crates/team-agent/src/lifecycle/profile_launch.rs +3 -2
  24. package/crates/team-agent/src/lifecycle/profile_smoke.rs +1 -0
  25. package/crates/team-agent/src/lifecycle/restart/agent.rs +90 -160
  26. package/crates/team-agent/src/lifecycle/restart/common.rs +136 -28
  27. package/crates/team-agent/src/lifecycle/restart/remove.rs +37 -1
  28. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +24 -3
  29. package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +195 -18
  30. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +30 -12
  31. package/crates/team-agent/src/lifecycle/tests/fixtures/pi_list_models_g0.stdout.txt +222 -0
  32. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +5 -2
  33. package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +35 -48
  34. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +1 -1
  35. package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +97 -0
  36. package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +145 -0
  37. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +243 -0
  38. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +856 -0
  39. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +387 -0
  40. package/crates/team-agent/src/lifecycle/tests/pi_lifecycle_doctor_red.rs +98 -0
  41. package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +216 -0
  42. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +1 -0
  43. package/crates/team-agent/src/lifecycle/tests.rs +7 -0
  44. package/crates/team-agent/src/lifecycle/worker_command_context.rs +61 -4
  45. package/crates/team-agent/src/messaging/delivery.rs +36 -12
  46. package/crates/team-agent/src/model/enums.rs +3 -1
  47. package/crates/team-agent/src/model/permissions.rs +1 -1
  48. package/crates/team-agent/src/provider/adapter.rs +29 -3
  49. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +5 -2
  50. package/crates/team-agent/src/provider/adapters/mod.rs +1 -0
  51. package/crates/team-agent/src/provider/adapters/pi.rs +146 -0
  52. package/crates/team-agent/src/provider/bypass_flags.rs +1 -0
  53. package/crates/team-agent/src/provider/classify.rs +2 -2
  54. package/crates/team-agent/src/provider/faults.rs +1 -1
  55. package/crates/team-agent/src/provider/session/context_fork.rs +2 -1
  56. package/crates/team-agent/src/provider/session_scan/common.rs +1 -0
  57. package/crates/team-agent/src/provider/session_scan/pi.rs +172 -0
  58. package/crates/team-agent/src/provider/session_scan.rs +4 -0
  59. package/crates/team-agent/src/provider/tests/adapter.rs +80 -0
  60. package/crates/team-agent/src/provider/wire.rs +7 -1
  61. package/crates/team-agent/src/tmux_backend.rs +52 -0
  62. package/package.json +4 -4
  63. package/skills/team-agent/SKILL.md +9 -5
@@ -0,0 +1,145 @@
1
+ use std::path::Path;
2
+
3
+ use crate::model::enums::ProviderEffort;
4
+ use crate::provider::adapters::pi::{build_pi_command_argv, PiCommandRequest, PiSessionSelector};
5
+
6
+ fn request(session: PiSessionSelector<'static>) -> PiCommandRequest<'static> {
7
+ PiCommandRequest {
8
+ executable: Path::new("/verified/pi"),
9
+ extension: Path::new("/workspace/.team/runtime/pi/team-a/worker-a/team-mcp.ts"),
10
+ model: Some("team-agent/qwen3.8-27b"),
11
+ effort: Some(ProviderEffort::High),
12
+ system_prompt: "frozen worker prompt",
13
+ tool_categories: &["mcp_team", "fs_read", "fs_write", "execute_bash"],
14
+ session_dir: Path::new("/workspace/.team/runtime/pi/team-a/worker-a/sessions"),
15
+ session,
16
+ agent_id: "worker-a",
17
+ }
18
+ }
19
+
20
+ #[test]
21
+ fn pi_command_omits_unset_model_and_thinking_flags() {
22
+ let mut request = request(PiSessionSelector::Fresh {
23
+ session_id: "a2320d4e-7b3a-44ae-b3b8-d5de57033b01",
24
+ });
25
+ request.model = None;
26
+ request.effort = None;
27
+ let argv = build_pi_command_argv(request).expect("Pi provider defaults");
28
+
29
+ assert!(!argv.iter().any(|arg| arg == "--model"), "argv={argv:?}");
30
+ assert!(!argv.iter().any(|arg| arg == "--thinking"), "argv={argv:?}");
31
+ }
32
+
33
+ #[test]
34
+ fn pi_command_preserves_direct_pi_defaults_with_only_team_runtime_additions() {
35
+ let fresh = build_pi_command_argv(request(PiSessionSelector::Fresh {
36
+ session_id: "da8c3622-2378-4d05-a26c-e826a6ef6d63",
37
+ }))
38
+ .expect("fresh Pi command");
39
+ assert_eq!(
40
+ fresh,
41
+ [
42
+ "/verified/pi",
43
+ "-e",
44
+ "/workspace/.team/runtime/pi/team-a/worker-a/team-mcp.ts",
45
+ "--model",
46
+ "team-agent/qwen3.8-27b",
47
+ "--thinking",
48
+ "high",
49
+ "--append-system-prompt",
50
+ "frozen worker prompt",
51
+ "--session-dir",
52
+ "/workspace/.team/runtime/pi/team-a/worker-a/sessions",
53
+ "--session-id",
54
+ "da8c3622-2378-4d05-a26c-e826a6ef6d63",
55
+ "--name",
56
+ "worker-a",
57
+ ]
58
+ .map(str::to_string)
59
+ );
60
+
61
+ let resume = build_pi_command_argv(request(PiSessionSelector::Resume {
62
+ path: Path::new(
63
+ "/workspace/.team/runtime/pi/team-a/worker-a/sessions/2026/08/session.jsonl",
64
+ ),
65
+ }))
66
+ .expect("resume Pi command");
67
+ assert!(resume.windows(2).any(|pair| {
68
+ pair == [
69
+ "--session",
70
+ "/workspace/.team/runtime/pi/team-a/worker-a/sessions/2026/08/session.jsonl",
71
+ ]
72
+ }));
73
+ assert!(!resume.iter().any(|arg| arg == "--session-id"));
74
+
75
+ for argv in [&fresh, &resume] {
76
+ for forbidden in [
77
+ "rpc",
78
+ "--mode",
79
+ "--print",
80
+ "-p",
81
+ "-r",
82
+ "--resume",
83
+ "-c",
84
+ "--continue",
85
+ "--mcp-config",
86
+ "--provider",
87
+ "--fork",
88
+ "--workspace",
89
+ "--cwd",
90
+ "--rules",
91
+ "--always-approve",
92
+ "--force",
93
+ "--trust",
94
+ "--sandbox",
95
+ "--no-extensions",
96
+ "--no-approve",
97
+ "--no-context-files",
98
+ "--no-skills",
99
+ "--no-prompt-templates",
100
+ "--tui-mode",
101
+ "--tools",
102
+ "--system-prompt",
103
+ ] {
104
+ assert!(
105
+ !argv.iter().any(|arg| arg == forbidden),
106
+ "materializer-owned Pi argv must not inherit {forbidden}: {argv:?}"
107
+ );
108
+ }
109
+ }
110
+ }
111
+
112
+ #[test]
113
+ fn pi_command_keeps_direct_plugins_skills_context_auth_and_tools() {
114
+ let argv = build_pi_command_argv(request(PiSessionSelector::Fresh {
115
+ session_id: "76f682b1-ecc3-4586-850f-ab2e2bb04cb3",
116
+ }))
117
+ .expect("fresh Pi command");
118
+
119
+ for forbidden in [
120
+ "--no-context-files",
121
+ "--no-extensions",
122
+ "--no-approve",
123
+ "--no-skills",
124
+ "--no-prompt-templates",
125
+ "--tools",
126
+ "--system-prompt",
127
+ "--tui-mode",
128
+ ] {
129
+ assert_eq!(
130
+ argv.iter().filter(|arg| arg.as_str() == forbidden).count(),
131
+ 0,
132
+ "TeamMate Pi must preserve the direct Pi default instead of adding {forbidden}; argv={argv:?}"
133
+ );
134
+ }
135
+ assert!(argv.windows(2).any(|pair| {
136
+ pair == [
137
+ "-e",
138
+ "/workspace/.team/runtime/pi/team-a/worker-a/team-mcp.ts",
139
+ ]
140
+ }));
141
+ assert_eq!(argv.iter().filter(|arg| arg.as_str() == "-e").count(), 1);
142
+ assert!(argv
143
+ .windows(2)
144
+ .any(|pair| pair == ["--append-system-prompt", "frozen worker prompt"]));
145
+ }
@@ -0,0 +1,243 @@
1
+ use std::path::Path;
2
+ use std::sync::atomic::{AtomicU32, Ordering};
3
+
4
+ use crate::compiler::{compile_role_agent, CompiledRole};
5
+ use crate::lifecycle::launch::pi_mcp::{parse_pi_list_models_table, select_exact_pi_model};
6
+ use crate::model::enums::ProviderEffort;
7
+ use crate::model::yaml::Value;
8
+ use crate::model::ModelError;
9
+ use crate::provider::adapters::pi::{
10
+ build_pi_command_argv, pi_tool_mapping, PiCommandRequest, PiSessionSelector, PiToolMapping,
11
+ };
12
+
13
+ const CATALOG: &[u8] = include_bytes!("fixtures/pi_list_models_g0.stdout.txt");
14
+ static NEXT_ROLE: AtomicU32 = AtomicU32::new(0);
15
+
16
+ fn compile_pi_role(front_matter: &str) -> Result<CompiledRole, ModelError> {
17
+ compile_pi_role_with_team(front_matter, &Value::Map(Vec::new()))
18
+ }
19
+
20
+ fn compile_pi_role_with_team(
21
+ front_matter: &str,
22
+ team_meta: &Value,
23
+ ) -> Result<CompiledRole, ModelError> {
24
+ let seq = NEXT_ROLE.fetch_add(1, Ordering::Relaxed);
25
+ let root = std::env::temp_dir().join(format!(
26
+ "team-agent-pi-compiler-{}-{seq}",
27
+ std::process::id()
28
+ ));
29
+ std::fs::create_dir_all(&root).expect("create role fixture root");
30
+ let role = root.join("pi-worker.md");
31
+ std::fs::write(
32
+ &role,
33
+ format!("---\n{front_matter}\n---\nPi worker contract.\n"),
34
+ )
35
+ .expect("write role fixture");
36
+ let result = compile_role_agent(&role, team_meta, "/workspace");
37
+ std::fs::remove_dir_all(root).expect("remove role fixture root");
38
+ result
39
+ }
40
+
41
+ fn valid_role_with(extra: &str) -> String {
42
+ format!(
43
+ "name: pi-worker\nrole: developer\nprovider: pi\nmodel: team-agent/qwen3.8-27b\nauth_mode: subscription\neffort: medium\ntools:\n - mcp_team\n - fs_read\n - fs_list\n - fs_write\n - execute_bash\ndangerously_skip_permissions: true\n{extra}"
44
+ )
45
+ }
46
+
47
+ fn compile_error(result: Result<CompiledRole, ModelError>) -> ModelError {
48
+ match result {
49
+ Err(error) => error,
50
+ Ok(_) => panic!("incomplete Pi role must refuse"),
51
+ }
52
+ }
53
+
54
+ fn command(tools: &[&str], effort: ProviderEffort, model: &str) -> Result<Vec<String>, String> {
55
+ build_pi_command_argv(PiCommandRequest {
56
+ executable: Path::new("/verified/pi"),
57
+ extension: Path::new("/workspace/.team/runtime/pi/t1/pi-worker/team-mcp.ts"),
58
+ model: Some(model),
59
+ effort: Some(effort),
60
+ system_prompt: "Pi worker contract.",
61
+ tool_categories: tools,
62
+ session_dir: Path::new("/workspace/.team/runtime/pi/t1/pi-worker/sessions"),
63
+ session: PiSessionSelector::Fresh {
64
+ session_id: "c5ddf218-24a5-4e74-960c-ab6606ea7e8c",
65
+ },
66
+ agent_id: "pi-worker",
67
+ })
68
+ .map_err(|error| error.to_string())
69
+ }
70
+
71
+ #[test]
72
+ fn pi_role_requires_mcp_team_and_preserves_provider_defaults() {
73
+ let positive = compile_pi_role(&valid_role_with(""));
74
+ assert!(positive.is_ok(), "fully explicit Pi role must compile");
75
+
76
+ let missing_mcp = valid_role_with("").replace(" - mcp_team\n", "");
77
+ let unqualified =
78
+ valid_role_with("").replace("model: team-agent/qwen3.8-27b", "model: qwen3.8-27b");
79
+
80
+ for (label, role) in [
81
+ ("mcp_team", missing_mcp),
82
+ ("qualified exact model", unqualified),
83
+ ] {
84
+ let error = compile_error(compile_pi_role(&role));
85
+ assert!(
86
+ error.to_string().to_ascii_lowercase().contains(label),
87
+ "error must name {label}; got {error}"
88
+ );
89
+ }
90
+
91
+ let defaults = valid_role_with("")
92
+ .replace("model: team-agent/qwen3.8-27b\n", "")
93
+ .replace("effort: medium\n", "");
94
+ let team_meta = Value::Map(vec![
95
+ (
96
+ "provider_models".to_string(),
97
+ Value::Map(vec![(
98
+ "pi".to_string(),
99
+ Value::Str("team-default/forced-model".to_string()),
100
+ )]),
101
+ ),
102
+ (
103
+ "default_model".to_string(),
104
+ Value::Str("global-default/forced-model".to_string()),
105
+ ),
106
+ (
107
+ "provider_effort".to_string(),
108
+ Value::Str("high".to_string()),
109
+ ),
110
+ ]);
111
+ let compiled = compile_pi_role_with_team(&defaults, &team_meta)
112
+ .expect("Pi model and effort may use provider defaults");
113
+ assert_eq!(
114
+ compiled.agent.get("model"),
115
+ Some(&Value::Null),
116
+ "omitted Pi model must not inherit a synthesized framework default"
117
+ );
118
+ assert!(
119
+ compiled.agent.get("effort").is_none(),
120
+ "omitted Pi effort must not inherit a synthesized framework default"
121
+ );
122
+
123
+ let models = parse_pi_list_models_table(CATALOG).expect("catalog fixture");
124
+ assert!(select_exact_pi_model(&models, "team-agent/qwen3.8-27b").is_ok());
125
+ assert!(select_exact_pi_model(&models, "foo/bar").is_err());
126
+ }
127
+
128
+ #[test]
129
+ fn pi_role_uses_standard_bypass_field_without_fabricating_argv() {
130
+ let compiled = compile_pi_role(&valid_role_with("")).expect("true acknowledgement");
131
+ assert_eq!(
132
+ compiled.agent.get("dangerously_skip_permissions"),
133
+ Some(&Value::Bool(true))
134
+ );
135
+
136
+ let argv = command(
137
+ &["mcp_team"],
138
+ ProviderEffort::Medium,
139
+ "team-agent/qwen3.8-27b",
140
+ )
141
+ .expect("true acknowledgement maps to intrinsic unrestricted mode");
142
+ for forbidden in [
143
+ "--approve",
144
+ "--always-approve",
145
+ "--force",
146
+ "--trust",
147
+ "--sandbox",
148
+ "dangerous_auto_approve",
149
+ ] {
150
+ assert!(
151
+ !argv.iter().any(|arg| arg == forbidden),
152
+ "Pi acknowledgement must not fabricate a bypass flag {forbidden}: {argv:?}"
153
+ );
154
+ }
155
+
156
+ let false_ack = valid_role_with("").replace(
157
+ "dangerously_skip_permissions: true",
158
+ "dangerously_skip_permissions: false",
159
+ );
160
+ let compiled = compile_pi_role(&false_ack).expect("standard false bypass value");
161
+ assert_eq!(
162
+ compiled.agent.get("dangerously_skip_permissions"),
163
+ Some(&Value::Bool(false))
164
+ );
165
+
166
+ let missing_ack = valid_role_with("").replace("dangerously_skip_permissions: true\n", "");
167
+ let text = compile_error(compile_pi_role(&missing_ack)).to_string();
168
+ assert!(
169
+ text.contains("missing front matter field dangerously_skip_permissions"),
170
+ "got {text}"
171
+ );
172
+ }
173
+
174
+ #[test]
175
+ fn pi_max_effort_is_supported_without_model_suffix() {
176
+ let max_role = valid_role_with("").replace("effort: medium", "effort: max");
177
+ compile_pi_role(&max_role).expect("Pi supports explicit max effort");
178
+
179
+ let argv =
180
+ command(&["mcp_team"], ProviderEffort::Max, "team-agent/qwen3.8-27b").expect("max Pi argv");
181
+ assert!(
182
+ argv.windows(2).any(|pair| pair == ["--thinking", "max"]),
183
+ "max must map to --thinking max: {argv:?}"
184
+ );
185
+ assert!(
186
+ argv.windows(2)
187
+ .any(|pair| pair == ["--model", "team-agent/qwen3.8-27b"]),
188
+ "model must remain exact and unsuffixed: {argv:?}"
189
+ );
190
+ assert!(!argv.iter().any(|arg| arg.ends_with(":max")));
191
+
192
+ let models = parse_pi_list_models_table(CATALOG).expect("catalog fixture");
193
+ assert!(select_exact_pi_model(&models, "team-agent/qwen3.8-27b:max").is_err());
194
+ }
195
+
196
+ #[test]
197
+ fn pi_role_tools_validate_team_capabilities_without_replacing_direct_pi_tools() {
198
+ assert_eq!(pi_tool_mapping("mcp_team"), PiToolMapping::Mcp);
199
+ assert_eq!(
200
+ pi_tool_mapping("fs_read"),
201
+ PiToolMapping::Builtin(&["read"])
202
+ );
203
+ assert_eq!(
204
+ pi_tool_mapping("fs_list"),
205
+ PiToolMapping::Builtin(&["grep", "find", "ls"])
206
+ );
207
+ assert_eq!(
208
+ pi_tool_mapping("fs_write"),
209
+ PiToolMapping::Builtin(&["edit", "write"])
210
+ );
211
+ assert_eq!(
212
+ pi_tool_mapping("execute_bash"),
213
+ PiToolMapping::Builtin(&["bash"])
214
+ );
215
+
216
+ for unsupported in ["git_diff", "network", "provider_builtin", "not_a_tool"] {
217
+ assert_eq!(
218
+ pi_tool_mapping(unsupported),
219
+ PiToolMapping::Unsupported,
220
+ "unknown categories must not silently broaden to bash"
221
+ );
222
+ assert!(
223
+ command(
224
+ &["mcp_team", unsupported],
225
+ ProviderEffort::Medium,
226
+ "team-agent/qwen3.8-27b"
227
+ )
228
+ .is_err(),
229
+ "unsupported tool must refuse the command: {unsupported}"
230
+ );
231
+ }
232
+
233
+ let argv = command(
234
+ &["mcp_team", "fs_write", "fs_read", "fs_list", "execute_bash"],
235
+ ProviderEffort::Medium,
236
+ "team-agent/qwen3.8-27b",
237
+ )
238
+ .expect("known standard role tools");
239
+ assert!(
240
+ !argv.iter().any(|arg| arg == "--tools"),
241
+ "role tools validate Team Agent capabilities but must not replace direct Pi tools: {argv:?}"
242
+ );
243
+ }