@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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/diagnose.rs +74 -0
- package/crates/team-agent/src/cli/emit.rs +5 -4
- package/crates/team-agent/src/cli/leader.rs +1 -0
- package/crates/team-agent/src/cli/spec.rs +1 -0
- package/crates/team-agent/src/compiler.rs +62 -5
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +1 -0
- package/crates/team-agent/src/layout/worker_window_helpers.rs +1 -0
- package/crates/team-agent/src/leader/start.rs +53 -12
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +67 -15
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +18 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +260 -44
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp_iso.rs +120 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +5 -1
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +10 -59
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +830 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +9 -3
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +70 -28
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch.rs +10 -2
- package/crates/team-agent/src/lifecycle/profile_launch.rs +3 -2
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +1 -0
- package/crates/team-agent/src/lifecycle/restart/agent.rs +90 -160
- package/crates/team-agent/src/lifecycle/restart/common.rs +146 -28
- package/crates/team-agent/src/lifecycle/restart/remove.rs +37 -1
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +24 -3
- package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +195 -18
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +30 -12
- package/crates/team-agent/src/lifecycle/tests/fixtures/pi_list_models_g0.stdout.txt +222 -0
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +5 -2
- package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +35 -48
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +97 -0
- package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +128 -0
- package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +207 -0
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +838 -0
- package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +320 -0
- package/crates/team-agent/src/lifecycle/tests/pi_lifecycle_doctor_red.rs +98 -0
- package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +216 -0
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +1 -0
- package/crates/team-agent/src/lifecycle/tests.rs +7 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +61 -4
- package/crates/team-agent/src/messaging/delivery.rs +36 -12
- package/crates/team-agent/src/model/enums.rs +3 -1
- package/crates/team-agent/src/model/permissions.rs +1 -1
- package/crates/team-agent/src/provider/adapter.rs +29 -3
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +5 -2
- package/crates/team-agent/src/provider/adapters/mod.rs +1 -0
- package/crates/team-agent/src/provider/adapters/pi.rs +152 -0
- package/crates/team-agent/src/provider/bypass_flags.rs +1 -0
- package/crates/team-agent/src/provider/classify.rs +2 -2
- package/crates/team-agent/src/provider/faults.rs +1 -1
- package/crates/team-agent/src/provider/session/context_fork.rs +2 -1
- package/crates/team-agent/src/provider/session_scan/common.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/pi.rs +172 -0
- package/crates/team-agent/src/provider/session_scan.rs +4 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +80 -0
- package/crates/team-agent/src/provider/wire.rs +7 -1
- package/crates/team-agent/src/tmux_backend.rs +52 -0
- package/package.json +4 -4
- 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
|
+
}
|