@team-agent/installer 0.5.68 → 0.5.69

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 +62 -5
  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 +830 -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 +70 -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 +146 -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 +128 -0
  37. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +207 -0
  38. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +838 -0
  39. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +320 -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 +152 -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 +2 -4
@@ -0,0 +1,128 @@
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: "team-agent/qwen3.8-27b",
11
+ effort: 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_is_exact_regular_tui_and_has_no_ambient_flags() {
22
+ let fresh = build_pi_command_argv(request(PiSessionSelector::Fresh {
23
+ session_id: "da8c3622-2378-4d05-a26c-e826a6ef6d63",
24
+ }))
25
+ .expect("fresh Pi command");
26
+ assert_eq!(
27
+ fresh,
28
+ [
29
+ "/verified/pi",
30
+ "--no-extensions",
31
+ "-e",
32
+ "/workspace/.team/runtime/pi/team-a/worker-a/team-mcp.ts",
33
+ "--no-approve",
34
+ "--no-context-files",
35
+ "--no-skills",
36
+ "--no-prompt-templates",
37
+ "--tui-mode",
38
+ "regular",
39
+ "--model",
40
+ "team-agent/qwen3.8-27b",
41
+ "--thinking",
42
+ "high",
43
+ "--system-prompt",
44
+ "frozen worker prompt",
45
+ "--tools",
46
+ "bash,edit,mcp,read,write",
47
+ "--session-dir",
48
+ "/workspace/.team/runtime/pi/team-a/worker-a/sessions",
49
+ "--session-id",
50
+ "da8c3622-2378-4d05-a26c-e826a6ef6d63",
51
+ "--name",
52
+ "worker-a",
53
+ ]
54
+ .map(str::to_string)
55
+ );
56
+
57
+ let resume = build_pi_command_argv(request(PiSessionSelector::Resume {
58
+ path: Path::new(
59
+ "/workspace/.team/runtime/pi/team-a/worker-a/sessions/2026/08/session.jsonl",
60
+ ),
61
+ }))
62
+ .expect("resume Pi command");
63
+ assert!(resume.windows(2).any(|pair| {
64
+ pair == [
65
+ "--session",
66
+ "/workspace/.team/runtime/pi/team-a/worker-a/sessions/2026/08/session.jsonl",
67
+ ]
68
+ }));
69
+ assert!(!resume.iter().any(|arg| arg == "--session-id"));
70
+
71
+ for argv in [&fresh, &resume] {
72
+ for forbidden in [
73
+ "rpc",
74
+ "--mode",
75
+ "--print",
76
+ "-p",
77
+ "-r",
78
+ "--resume",
79
+ "-c",
80
+ "--continue",
81
+ "--mcp-config",
82
+ "--provider",
83
+ "--fork",
84
+ "--workspace",
85
+ "--cwd",
86
+ "--rules",
87
+ "--always-approve",
88
+ "--force",
89
+ "--trust",
90
+ "--sandbox",
91
+ ] {
92
+ assert!(
93
+ !argv.iter().any(|arg| arg == forbidden),
94
+ "materializer-owned Pi argv must not inherit {forbidden}: {argv:?}"
95
+ );
96
+ }
97
+ }
98
+ }
99
+
100
+ #[test]
101
+ fn pi_command_uses_no_context_no_extensions_no_approve() {
102
+ let argv = build_pi_command_argv(request(PiSessionSelector::Fresh {
103
+ session_id: "76f682b1-ecc3-4586-850f-ab2e2bb04cb3",
104
+ }))
105
+ .expect("fresh Pi command");
106
+
107
+ for required in [
108
+ "--no-context-files",
109
+ "--no-extensions",
110
+ "--no-approve",
111
+ "--no-skills",
112
+ "--no-prompt-templates",
113
+ ] {
114
+ assert_eq!(
115
+ argv.iter().filter(|arg| arg.as_str() == required).count(),
116
+ 1,
117
+ "required isolation flag must occur once: {required}; argv={argv:?}"
118
+ );
119
+ }
120
+ assert!(argv.windows(2).any(|pair| {
121
+ pair == [
122
+ "-e",
123
+ "/workspace/.team/runtime/pi/team-a/worker-a/team-mcp.ts",
124
+ ]
125
+ }));
126
+ assert_eq!(argv.iter().filter(|arg| arg.as_str() == "-e").count(), 1);
127
+ assert!(!argv.iter().any(|arg| arg == "--approve"));
128
+ }
@@ -0,0 +1,207 @@
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
+ let seq = NEXT_ROLE.fetch_add(1, Ordering::Relaxed);
18
+ let root = std::env::temp_dir().join(format!(
19
+ "team-agent-pi-compiler-{}-{seq}",
20
+ std::process::id()
21
+ ));
22
+ std::fs::create_dir_all(&root).expect("create role fixture root");
23
+ let role = root.join("pi-worker.md");
24
+ std::fs::write(
25
+ &role,
26
+ format!("---\n{front_matter}\n---\nPi worker contract.\n"),
27
+ )
28
+ .expect("write role fixture");
29
+ let result = compile_role_agent(&role, &Value::Map(Vec::new()), "/workspace");
30
+ std::fs::remove_dir_all(root).expect("remove role fixture root");
31
+ result
32
+ }
33
+
34
+ fn valid_role_with(extra: &str) -> String {
35
+ format!(
36
+ "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}"
37
+ )
38
+ }
39
+
40
+ fn compile_error(result: Result<CompiledRole, ModelError>) -> ModelError {
41
+ match result {
42
+ Err(error) => error,
43
+ Ok(_) => panic!("incomplete Pi role must refuse"),
44
+ }
45
+ }
46
+
47
+ fn command(tools: &[&str], effort: ProviderEffort, model: &str) -> Result<Vec<String>, String> {
48
+ build_pi_command_argv(PiCommandRequest {
49
+ executable: Path::new("/verified/pi"),
50
+ extension: Path::new("/workspace/.team/runtime/pi/t1/pi-worker/team-mcp.ts"),
51
+ model,
52
+ effort,
53
+ system_prompt: "Pi worker contract.",
54
+ tool_categories: tools,
55
+ session_dir: Path::new("/workspace/.team/runtime/pi/t1/pi-worker/sessions"),
56
+ session: PiSessionSelector::Fresh {
57
+ session_id: "c5ddf218-24a5-4e74-960c-ab6606ea7e8c",
58
+ },
59
+ agent_id: "pi-worker",
60
+ })
61
+ .map_err(|error| error.to_string())
62
+ }
63
+
64
+ #[test]
65
+ fn pi_role_requires_qualified_model_explicit_effort_and_mcp_team() {
66
+ let positive = compile_pi_role(&valid_role_with(""));
67
+ assert!(positive.is_ok(), "fully explicit Pi role must compile");
68
+
69
+ let missing_model = valid_role_with("").replace("model: team-agent/qwen3.8-27b\n", "");
70
+ let missing_effort = valid_role_with("").replace("effort: medium\n", "");
71
+ let missing_mcp = valid_role_with("").replace(" - mcp_team\n", "");
72
+ let unqualified =
73
+ valid_role_with("").replace("model: team-agent/qwen3.8-27b", "model: qwen3.8-27b");
74
+
75
+ for (label, role) in [
76
+ ("model", missing_model),
77
+ ("effort", missing_effort),
78
+ ("mcp_team", missing_mcp),
79
+ ("qualified exact model", unqualified),
80
+ ] {
81
+ let error = compile_error(compile_pi_role(&role));
82
+ assert!(
83
+ error.to_string().to_ascii_lowercase().contains(label),
84
+ "error must name {label}; got {error}"
85
+ );
86
+ }
87
+
88
+ let models = parse_pi_list_models_table(CATALOG).expect("catalog fixture");
89
+ assert!(select_exact_pi_model(&models, "team-agent/qwen3.8-27b").is_ok());
90
+ assert!(select_exact_pi_model(&models, "foo/bar").is_err());
91
+ }
92
+
93
+ #[test]
94
+ fn pi_role_requires_intrinsic_unrestricted_ack_true() {
95
+ let compiled = compile_pi_role(&valid_role_with("")).expect("true acknowledgement");
96
+ assert_eq!(
97
+ compiled.agent.get("dangerously_skip_permissions"),
98
+ Some(&Value::Bool(true))
99
+ );
100
+
101
+ let argv = command(
102
+ &["mcp_team"],
103
+ ProviderEffort::Medium,
104
+ "team-agent/qwen3.8-27b",
105
+ )
106
+ .expect("true acknowledgement maps to intrinsic unrestricted mode");
107
+ for forbidden in [
108
+ "--approve",
109
+ "--always-approve",
110
+ "--force",
111
+ "--trust",
112
+ "--sandbox",
113
+ "dangerous_auto_approve",
114
+ ] {
115
+ assert!(
116
+ !argv.iter().any(|arg| arg == forbidden),
117
+ "Pi acknowledgement must not fabricate a bypass flag {forbidden}: {argv:?}"
118
+ );
119
+ }
120
+
121
+ let false_ack = valid_role_with("").replace(
122
+ "dangerously_skip_permissions: true",
123
+ "dangerously_skip_permissions: false",
124
+ );
125
+ let error = compile_error(compile_pi_role(&false_ack));
126
+ let text = error.to_string();
127
+ assert!(
128
+ text.contains("dangerously_skip_permissions") && text.contains("true"),
129
+ "got {text}"
130
+ );
131
+ }
132
+
133
+ #[test]
134
+ fn pi_max_effort_is_supported_without_model_suffix() {
135
+ let max_role = valid_role_with("").replace("effort: medium", "effort: max");
136
+ compile_pi_role(&max_role).expect("Pi supports explicit max effort");
137
+
138
+ let argv =
139
+ command(&["mcp_team"], ProviderEffort::Max, "team-agent/qwen3.8-27b").expect("max Pi argv");
140
+ assert!(
141
+ argv.windows(2).any(|pair| pair == ["--thinking", "max"]),
142
+ "max must map to --thinking max: {argv:?}"
143
+ );
144
+ assert!(
145
+ argv.windows(2)
146
+ .any(|pair| pair == ["--model", "team-agent/qwen3.8-27b"]),
147
+ "model must remain exact and unsuffixed: {argv:?}"
148
+ );
149
+ assert!(!argv.iter().any(|arg| arg.ends_with(":max")));
150
+
151
+ let models = parse_pi_list_models_table(CATALOG).expect("catalog fixture");
152
+ assert!(select_exact_pi_model(&models, "team-agent/qwen3.8-27b:max").is_err());
153
+ }
154
+
155
+ #[test]
156
+ fn pi_tools_allowlist_keeps_mcp_and_rejects_unknown_categories() {
157
+ assert_eq!(pi_tool_mapping("mcp_team"), PiToolMapping::Mcp);
158
+ assert_eq!(
159
+ pi_tool_mapping("fs_read"),
160
+ PiToolMapping::Builtin(&["read"])
161
+ );
162
+ assert_eq!(
163
+ pi_tool_mapping("fs_list"),
164
+ PiToolMapping::Builtin(&["grep", "find", "ls"])
165
+ );
166
+ assert_eq!(
167
+ pi_tool_mapping("fs_write"),
168
+ PiToolMapping::Builtin(&["edit", "write"])
169
+ );
170
+ assert_eq!(
171
+ pi_tool_mapping("execute_bash"),
172
+ PiToolMapping::Builtin(&["bash"])
173
+ );
174
+
175
+ for unsupported in ["git_diff", "network", "provider_builtin", "not_a_tool"] {
176
+ assert_eq!(
177
+ pi_tool_mapping(unsupported),
178
+ PiToolMapping::Unsupported,
179
+ "unknown categories must not silently broaden to bash"
180
+ );
181
+ assert!(
182
+ command(
183
+ &["mcp_team", unsupported],
184
+ ProviderEffort::Medium,
185
+ "team-agent/qwen3.8-27b"
186
+ )
187
+ .is_err(),
188
+ "unsupported tool must refuse the command: {unsupported}"
189
+ );
190
+ }
191
+
192
+ let argv = command(
193
+ &["mcp_team", "fs_write", "fs_read", "fs_list", "execute_bash"],
194
+ ProviderEffort::Medium,
195
+ "team-agent/qwen3.8-27b",
196
+ )
197
+ .expect("known allowlist");
198
+ let tools = argv
199
+ .windows(2)
200
+ .find(|pair| pair[0] == "--tools")
201
+ .map(|pair| pair[1].as_str())
202
+ .expect("--tools value");
203
+ assert_eq!(
204
+ tools, "bash,edit,find,grep,ls,mcp,read,write",
205
+ "tools must be sorted and deduplicated"
206
+ );
207
+ }