@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
@@ -8,8 +8,8 @@
8
8
  //! what: 写了 model 则该值进入 `--model <值>`
9
9
  //! - name: cursor-effort-refuses-compile
10
10
  //! what: 角色写 effort 则失败,不得丢掉后仍起席
11
- //! - name: cursor-second-seat-refuses
12
- //! what: 同 workspace 第二 CursorAgent 拒绝,文案含 TEAM_AGENT_ID/mcp.json
11
+ //! - name: cursor-second-seat-isolated-identity
12
+ //! what: 默认 per-seat MCP 隔离时同 workspace 两个 CursorAgent 都启动且身份各自保留
13
13
  //! boundary:
14
14
  //! - 只钉 Provider::CursorAgent;不改 grok/claude 路径
15
15
  //! maturity: wired
@@ -26,6 +26,7 @@ use std::path::{Path, PathBuf};
26
26
  use std::sync::atomic::{AtomicU64, Ordering};
27
27
 
28
28
  use serial_test::serial;
29
+ use team_agent::lifecycle::cursor_mcp_json_path;
29
30
  use team_agent::lifecycle::quick_start_with_transport_in_workspace;
30
31
  use team_agent::transport::test_support::OfflineTransport;
31
32
 
@@ -126,7 +127,9 @@ fn cursor_role_with_effort_refuses_to_start() {
126
127
 
127
128
  #[test]
128
129
  #[serial(env)]
129
- fn cursor_second_seat_in_same_workspace_is_refused() {
130
+ fn cursor_second_seat_in_same_workspace_uses_isolated_identity() {
131
+ let previous_isolation = std::env::var("TEAM_AGENT_CURSOR_MCP_ISOLATION").ok();
132
+ std::env::remove_var("TEAM_AGENT_CURSOR_MCP_ISOLATION");
130
133
  let ws = tmp_dir("cursor-two");
131
134
  let team = ws.join("cursortm");
132
135
  std::fs::create_dir_all(team.join("agents")).unwrap();
@@ -153,21 +156,36 @@ fn cursor_second_seat_in_same_workspace_is_refused() {
153
156
  Some("cursortm"),
154
157
  &transport,
155
158
  );
156
- let err = match result {
157
- Ok(report) => panic!("second cursor seat must refuse; report={report:?}"),
158
- Err(error) => error.to_string(),
159
- };
159
+ restore_isolation_env(previous_isolation);
160
+ result.expect("default per-seat MCP isolation must allow both Cursor seats");
161
+ let records = transport.spawn_records();
162
+ assert_eq!(
163
+ records.len(),
164
+ 2,
165
+ "both Cursor seats must spawn; records={records:?}"
166
+ );
167
+
168
+ let first = std::fs::read_to_string(cursor_mcp_json_path(&ws, "cursor_a").unwrap())
169
+ .expect("first seat MCP config");
170
+ let second = std::fs::read_to_string(cursor_mcp_json_path(&ws, "cursor_b").unwrap())
171
+ .expect("second seat MCP config");
160
172
  assert!(
161
- err.contains("TEAM_AGENT_ID") && err.contains("mcp.json"),
162
- "error must name the last-writer identity file; err={err}"
173
+ first.contains("\"cursor_a\"") && !first.contains("\"cursor_b\""),
174
+ "first seat must retain its own TEAM_AGENT_ID"
163
175
  );
164
176
  assert!(
165
- transport.spawn_records().is_empty(),
166
- "must refuse before any cursor spawn; records={:?}",
167
- transport.spawn_records()
177
+ second.contains("\"cursor_b\"") && !second.contains("\"cursor_a\""),
178
+ "second seat must retain its own TEAM_AGENT_ID"
168
179
  );
169
180
  }
170
181
 
182
+ fn restore_isolation_env(previous: Option<String>) {
183
+ match previous {
184
+ Some(value) => std::env::set_var("TEAM_AGENT_CURSOR_MCP_ISOLATION", value),
185
+ None => std::env::remove_var("TEAM_AGENT_CURSOR_MCP_ISOLATION"),
186
+ }
187
+ }
188
+
171
189
  fn first_spawn_argv(transport: &OfflineTransport) -> Vec<String> {
172
190
  let records = transport.spawn_records();
173
191
  assert!(
@@ -0,0 +1,222 @@
1
+ provider model context max-out thinking images
2
+ cursor auto 200K 16.4K no yes
3
+ cursor claude-fable-5@1m 1M 16.4K yes yes
4
+ cursor claude-fable-5@300k 300K 16.4K yes yes
5
+ cursor claude-haiku-4-5 200K 16.4K yes yes
6
+ cursor claude-opus-4-5 200K 16.4K yes yes
7
+ cursor claude-opus-4-6@1m 1M 16.4K yes yes
8
+ cursor claude-opus-4-6@200k 200K 16.4K yes yes
9
+ cursor claude-opus-4-7@1m 1M 16.4K yes yes
10
+ cursor claude-opus-4-7@1m:fast 1M 16.4K yes yes
11
+ cursor claude-opus-4-7@1m:slow 1M 16.4K yes yes
12
+ cursor claude-opus-4-7@300k 300K 16.4K yes yes
13
+ cursor claude-opus-4-7@300k:fast 300K 16.4K yes yes
14
+ cursor claude-opus-4-7@300k:slow 300K 16.4K yes yes
15
+ cursor claude-opus-4-8@1m 300K 16.4K yes yes
16
+ cursor claude-opus-4-8@1m:fast 1M 16.4K yes yes
17
+ cursor claude-opus-4-8@1m:slow 300K 16.4K yes yes
18
+ cursor claude-opus-4-8@300k 300K 16.4K yes yes
19
+ cursor claude-opus-4-8@300k:fast 300K 16.4K yes yes
20
+ cursor claude-opus-4-8@300k:slow 300K 16.4K yes yes
21
+ cursor claude-opus-5@1m 1M 16.4K yes yes
22
+ cursor claude-opus-5@1m:fast 1M 16.4K yes yes
23
+ cursor claude-opus-5@1m:slow 1M 16.4K yes yes
24
+ cursor claude-opus-5@300k 300K 16.4K yes yes
25
+ cursor claude-opus-5@300k:fast 300K 16.4K yes yes
26
+ cursor claude-opus-5@300k:slow 300K 16.4K yes yes
27
+ cursor claude-sonnet-4-5@200k 200K 16.4K yes yes
28
+ cursor claude-sonnet-4-6@1m 1M 16.4K yes yes
29
+ cursor claude-sonnet-4-6@200k 200K 16.4K yes yes
30
+ cursor claude-sonnet-4@200k 200K 16.4K yes yes
31
+ cursor claude-sonnet-5@1m 1M 16.4K yes yes
32
+ cursor claude-sonnet-5@300k 300K 16.4K yes yes
33
+ cursor codex 272K 16.4K yes yes
34
+ cursor codex-5.3 272K 16.4K yes yes
35
+ cursor codex-5.3:fast 272K 16.4K yes yes
36
+ cursor codex-5.3:slow 272K 16.4K yes yes
37
+ cursor codex-latest 272K 16.4K yes yes
38
+ cursor codex-latest:fast 272K 16.4K yes yes
39
+ cursor codex-latest:slow 272K 16.4K yes yes
40
+ cursor codex:fast 272K 16.4K yes yes
41
+ cursor codex:slow 272K 16.4K yes yes
42
+ cursor composer 200K 16.4K no yes
43
+ cursor composer-2 200K 16.4K no yes
44
+ cursor composer-2-5 200K 16.4K no yes
45
+ cursor composer-2-5:fast 200K 16.4K no yes
46
+ cursor composer-2-5:slow 200K 16.4K no yes
47
+ cursor composer-2:fast 200K 16.4K no yes
48
+ cursor composer-2:slow 200K 16.4K no yes
49
+ cursor composer-2.5 200K 16.4K no yes
50
+ cursor composer-2.5:fast 200K 16.4K no yes
51
+ cursor composer-2.5:slow 200K 16.4K no yes
52
+ cursor composer-latest 200K 16.4K no yes
53
+ cursor composer-latest:fast 200K 16.4K no yes
54
+ cursor composer-latest:slow 200K 16.4K no yes
55
+ cursor composer:fast 200K 16.4K no yes
56
+ cursor composer:slow 200K 16.4K no yes
57
+ cursor default 200K 16.4K no yes
58
+ cursor fable-5@1m 1M 16.4K yes yes
59
+ cursor fable-5@300k 300K 16.4K yes yes
60
+ cursor fable@1m 1M 16.4K yes yes
61
+ cursor fable@300k 300K 16.4K yes yes
62
+ cursor gemini 200K 16.4K no yes
63
+ cursor gemini-2.5-flash 200K 16.4K no yes
64
+ cursor gemini-3-flash 200K 16.4K no yes
65
+ cursor gemini-3.1-pro 200K 16.4K no yes
66
+ cursor gemini-3.5-flash 200K 16.4K no yes
67
+ cursor gemini-3.6-flash 200K 16.4K yes yes
68
+ cursor gemini-3.7-flash 200K 16.4K yes yes
69
+ cursor gemini-latest 200K 16.4K no yes
70
+ cursor gemini-pro 200K 16.4K no yes
71
+ cursor gemini-pro-latest 200K 16.4K no yes
72
+ cursor glm-5.2 200K 16.4K yes yes
73
+ cursor gpt-5-5@1m 1M 16.4K yes yes
74
+ cursor gpt-5-5@1m:fast 1M 16.4K yes yes
75
+ cursor gpt-5-5@1m:slow 1M 16.4K yes yes
76
+ cursor gpt-5-5@272k 272K 16.4K yes yes
77
+ cursor gpt-5-5@272k:fast 272K 16.4K yes yes
78
+ cursor gpt-5-5@272k:slow 272K 16.4K yes yes
79
+ cursor gpt-5-6-luna@1m 1M 16.4K yes yes
80
+ cursor gpt-5-6-luna@1m:fast 1M 16.4K yes yes
81
+ cursor gpt-5-6-luna@1m:slow 1M 16.4K yes yes
82
+ cursor gpt-5-6-luna@272k 272K 16.4K yes yes
83
+ cursor gpt-5-6-luna@272k:fast 272K 16.4K yes yes
84
+ cursor gpt-5-6-luna@272k:slow 272K 16.4K yes yes
85
+ cursor gpt-5-6-sol@1m 1M 16.4K yes yes
86
+ cursor gpt-5-6-sol@1m:fast 1M 16.4K yes yes
87
+ cursor gpt-5-6-sol@1m:slow 1M 16.4K yes yes
88
+ cursor gpt-5-6-sol@272k 272K 16.4K yes yes
89
+ cursor gpt-5-6-sol@272k:fast 272K 16.4K yes yes
90
+ cursor gpt-5-6-sol@272k:slow 272K 16.4K yes yes
91
+ cursor gpt-5-6-terra@1m 1M 16.4K yes yes
92
+ cursor gpt-5-6-terra@1m:fast 1M 16.4K yes yes
93
+ cursor gpt-5-6-terra@1m:slow 1M 16.4K yes yes
94
+ cursor gpt-5-6-terra@272k 272K 16.4K yes yes
95
+ cursor gpt-5-6-terra@272k:fast 272K 16.4K yes yes
96
+ cursor gpt-5-6-terra@272k:slow 272K 16.4K yes yes
97
+ cursor gpt-5-mini 272K 16.4K no yes
98
+ cursor gpt-5.1 272K 16.4K yes yes
99
+ cursor gpt-5.2 272K 16.4K yes yes
100
+ cursor gpt-5.2:fast 272K 16.4K yes yes
101
+ cursor gpt-5.2:slow 272K 16.4K yes yes
102
+ cursor gpt-5.3-codex 272K 16.4K yes yes
103
+ cursor gpt-5.3-codex:fast 272K 16.4K yes yes
104
+ cursor gpt-5.3-codex:slow 272K 16.4K yes yes
105
+ cursor gpt-5.4-mini 272K 16.4K yes yes
106
+ cursor gpt-5.4-nano 272K 16.4K yes yes
107
+ cursor gpt-5.4@1m 1M 16.4K yes yes
108
+ cursor gpt-5.4@1m:fast 1M 16.4K yes yes
109
+ cursor gpt-5.4@1m:slow 1M 16.4K yes yes
110
+ cursor gpt-5.4@272k 272K 16.4K yes yes
111
+ cursor gpt-5.4@272k:fast 272K 16.4K yes yes
112
+ cursor gpt-5.4@272k:slow 272K 16.4K yes yes
113
+ cursor gpt-5.5@1m 1M 16.4K yes yes
114
+ cursor gpt-5.5@1m:fast 1M 16.4K yes yes
115
+ cursor gpt-5.5@1m:slow 1M 16.4K yes yes
116
+ cursor gpt-5.5@272k 272K 16.4K yes yes
117
+ cursor gpt-5.5@272k:fast 272K 16.4K yes yes
118
+ cursor gpt-5.5@272k:slow 272K 16.4K yes yes
119
+ cursor gpt-5.6-luna@1m 1M 16.4K yes yes
120
+ cursor gpt-5.6-luna@1m:fast 1M 16.4K yes yes
121
+ cursor gpt-5.6-luna@1m:slow 1M 16.4K yes yes
122
+ cursor gpt-5.6-luna@272k 272K 16.4K yes yes
123
+ cursor gpt-5.6-luna@272k:fast 272K 16.4K yes yes
124
+ cursor gpt-5.6-luna@272k:slow 272K 16.4K yes yes
125
+ cursor gpt-5.6-sol@1m 1M 16.4K yes yes
126
+ cursor gpt-5.6-sol@1m:fast 1M 16.4K yes yes
127
+ cursor gpt-5.6-sol@1m:slow 1M 16.4K yes yes
128
+ cursor gpt-5.6-sol@272k 272K 16.4K yes yes
129
+ cursor gpt-5.6-sol@272k:fast 272K 16.4K yes yes
130
+ cursor gpt-5.6-sol@272k:slow 272K 16.4K yes yes
131
+ cursor gpt-5.6-terra@1m 1M 16.4K yes yes
132
+ cursor gpt-5.6-terra@1m:fast 1M 16.4K yes yes
133
+ cursor gpt-5.6-terra@1m:slow 1M 16.4K yes yes
134
+ cursor gpt-5.6-terra@272k 272K 16.4K yes yes
135
+ cursor gpt-5.6-terra@272k:fast 272K 16.4K yes yes
136
+ cursor gpt-5.6-terra@272k:slow 272K 16.4K yes yes
137
+ cursor gpt-5.6@1m 272K 16.4K yes yes
138
+ cursor gpt-5.6@1m:fast 1M 16.4K yes yes
139
+ cursor gpt-5.6@1m:slow 272K 16.4K yes yes
140
+ cursor gpt-5.6@272k 272K 16.4K yes yes
141
+ cursor gpt-5.6@272k:fast 272K 16.4K yes yes
142
+ cursor gpt-5.6@272k:slow 272K 16.4K yes yes
143
+ cursor gpt-latest@1m 1M 16.4K yes yes
144
+ cursor gpt-latest@1m:fast 1M 16.4K yes yes
145
+ cursor gpt-latest@1m:slow 1M 16.4K yes yes
146
+ cursor gpt-latest@272k 272K 16.4K yes yes
147
+ cursor gpt-latest@272k:fast 272K 16.4K yes yes
148
+ cursor gpt-latest@272k:slow 272K 16.4K yes yes
149
+ cursor gpt-mini-latest 272K 16.4K yes yes
150
+ cursor gpt-nano 272K 16.4K yes yes
151
+ cursor gpt-nano-latest 272K 16.4K yes yes
152
+ cursor grok-4.5 256K 16.4K yes yes
153
+ cursor grok-4.5:fast 256K 16.4K yes yes
154
+ cursor grok-4.5:slow 256K 16.4K yes yes
155
+ cursor grok-4.6 256K 16.4K yes yes
156
+ cursor grok-4.6:fast 256K 16.4K yes yes
157
+ cursor grok-4.6:slow 256K 16.4K yes yes
158
+ cursor haiku 200K 16.4K yes yes
159
+ cursor haiku-4-5 200K 16.4K yes yes
160
+ cursor haiku-4.5 200K 16.4K yes yes
161
+ cursor haiku-latest 200K 16.4K yes yes
162
+ cursor kimi 200K 16.4K no yes
163
+ cursor kimi-k2.7-code 200K 16.4K no yes
164
+ cursor kimi-k3 200K 16.4K yes yes
165
+ cursor kimi-latest 200K 16.4K no yes
166
+ cursor opus-4-5 200K 16.4K yes yes
167
+ cursor opus-4-6@1m 1M 16.4K yes yes
168
+ cursor opus-4-6@200k 200K 16.4K yes yes
169
+ cursor opus-4-7@1m 1M 16.4K yes yes
170
+ cursor opus-4-7@1m:fast 1M 16.4K yes yes
171
+ cursor opus-4-7@1m:slow 1M 16.4K yes yes
172
+ cursor opus-4-7@300k 300K 16.4K yes yes
173
+ cursor opus-4-7@300k:fast 300K 16.4K yes yes
174
+ cursor opus-4-7@300k:slow 300K 16.4K yes yes
175
+ cursor opus-4-8@1m 300K 16.4K yes yes
176
+ cursor opus-4-8@1m:fast 1M 16.4K yes yes
177
+ cursor opus-4-8@1m:slow 300K 16.4K yes yes
178
+ cursor opus-4-8@300k 300K 16.4K yes yes
179
+ cursor opus-4-8@300k:fast 300K 16.4K yes yes
180
+ cursor opus-4-8@300k:slow 300K 16.4K yes yes
181
+ cursor opus-4.5 200K 16.4K yes yes
182
+ cursor opus-4.6@1m 1M 16.4K yes yes
183
+ cursor opus-4.6@200k 200K 16.4K yes yes
184
+ cursor opus-4.7@1m 1M 16.4K yes yes
185
+ cursor opus-4.7@1m:fast 1M 16.4K yes yes
186
+ cursor opus-4.7@1m:slow 1M 16.4K yes yes
187
+ cursor opus-4.7@300k 300K 16.4K yes yes
188
+ cursor opus-4.7@300k:fast 300K 16.4K yes yes
189
+ cursor opus-4.7@300k:slow 300K 16.4K yes yes
190
+ cursor opus-4.8@1m 300K 16.4K yes yes
191
+ cursor opus-4.8@1m:fast 1M 16.4K yes yes
192
+ cursor opus-4.8@1m:slow 300K 16.4K yes yes
193
+ cursor opus-4.8@300k 300K 16.4K yes yes
194
+ cursor opus-4.8@300k:fast 300K 16.4K yes yes
195
+ cursor opus-4.8@300k:slow 300K 16.4K yes yes
196
+ cursor opus-5@1m 1M 16.4K yes yes
197
+ cursor opus-5@1m:fast 1M 16.4K yes yes
198
+ cursor opus-5@1m:slow 1M 16.4K yes yes
199
+ cursor opus-5@300k 300K 16.4K yes yes
200
+ cursor opus-5@300k:fast 300K 16.4K yes yes
201
+ cursor opus-5@300k:slow 300K 16.4K yes yes
202
+ cursor sonnet-4-5@200k 200K 16.4K yes yes
203
+ cursor sonnet-4-6@1m 1M 16.4K yes yes
204
+ cursor sonnet-4-6@200k 200K 16.4K yes yes
205
+ cursor sonnet-4.5@200k 200K 16.4K yes yes
206
+ cursor sonnet-4.6@1m 1M 16.4K yes yes
207
+ cursor sonnet-4.6@200k 200K 16.4K yes yes
208
+ cursor sonnet-4@200k 200K 16.4K yes yes
209
+ cursor sonnet-5@1m 1M 16.4K yes yes
210
+ cursor sonnet-5@300k 300K 16.4K yes yes
211
+ openai-codex gpt-5.3-codex-spark 128K 128K yes no
212
+ openai-codex gpt-5.4 272K 128K yes yes
213
+ openai-codex gpt-5.4-mini 272K 128K yes yes
214
+ openai-codex gpt-5.5 272K 128K yes yes
215
+ openai-codex gpt-5.6-luna 272K 128K yes yes
216
+ openai-codex gpt-5.6-sol 272K 128K yes yes
217
+ openai-codex gpt-5.6-terra 272K 128K yes yes
218
+ team-agent qwen3.8-27b 262.1K 32.8K yes no
219
+ xai grok-4.3 1M 30K yes yes
220
+ xai grok-4.5 500K 500K yes yes
221
+ xai grok-4.6 500K 500K yes yes
222
+ xai grok-build-0.1 256K 256K yes yes
@@ -518,8 +518,11 @@ fn assert_provider_mcp_table(case: &Case, provider: Provider, clone_id: &str) {
518
518
  }
519
519
  Provider::CursorAgent => {
520
520
  apply_cursor_mcp_overlay(&overlay_root, &cfg).expect("cursor overlay");
521
- let text = std::fs::read_to_string(overlay_root.join(".cursor/mcp.json"))
522
- .expect("cursor mcp.json");
521
+ let text = std::fs::read_to_string(
522
+ team_agent::lifecycle::cursor_mcp_json_path(&overlay_root, clone_id)
523
+ .expect("iso path"),
524
+ )
525
+ .expect("cursor mcp.json");
523
526
  assert!(
524
527
  text.contains("\"team_orchestrator\""),
525
528
  "cursor MCP injection must keep team_orchestrator; text={text}"
@@ -183,7 +183,7 @@ fn sample_mcp_config(agent_id: &str, workspace: &str) -> McpConfig {
183
183
  fn two_grok_seats_coexist_when_toml_has_no_per_seat_keys() {
184
184
  let ws = tmp_dir("grok-cwd-coexist");
185
185
  let home = tmp_dir("grok-cwd-coexist-home");
186
- seed_grok_home(&home, Some(&ws));
186
+ seed_grok_home(&home);
187
187
  let _guard = HomeGuard::set(&home);
188
188
  let team = write_grok_team_agents(&ws, "grokteam", &["g1", "g2"]);
189
189
 
@@ -211,7 +211,7 @@ fn two_grok_seats_coexist_when_toml_has_no_per_seat_keys() {
211
211
  fn leftover_per_seat_key_is_cleared_before_start() {
212
212
  let ws = tmp_dir("grok-cwd-per-seat");
213
213
  let home = tmp_dir("grok-cwd-per-seat-home");
214
- seed_grok_home(&home, Some(&ws));
214
+ seed_grok_home(&home);
215
215
  let _guard = HomeGuard::set(&home);
216
216
  let grok = ws.join(".grok");
217
217
  std::fs::create_dir_all(&grok).unwrap();
@@ -279,45 +279,12 @@ TEAM_AGENT_WORKSPACE = "/stale-ws"
279
279
  );
280
280
  }
281
281
 
282
- #[test]
283
- #[serial(env)]
284
- fn grok_untrusted_folder_refuses_to_start() {
285
- let ws = tmp_dir("grok-untrusted");
286
- let home = tmp_dir("grok-untrusted-home");
287
- seed_grok_home(&home, None);
288
- let _guard = HomeGuard::set(&home);
289
- let team = write_grok_team(&ws, "grokteam", "grok_writer");
290
- let err = quick_start_with_transport_in_workspace(
291
- &ws,
292
- &team,
293
- None,
294
- true,
295
- Some("grokteam"),
296
- &OfflineTransport::new(),
297
- )
298
- .expect_err("untrusted folder must not start a grok seat");
299
- let text = err.to_string();
300
- assert!(
301
- text.contains("not trusted") && text.contains("action:"),
302
- "untrusted-folder error must be actionable; err={text}"
303
- );
304
- assert!(
305
- text.contains("grok --trust") || text.contains("/hooks-trust"),
306
- "next step must name grok --trust or /hooks-trust; err={text}"
307
- );
308
- }
309
-
310
282
  #[test]
311
283
  #[serial(env)]
312
284
  fn grok_missing_login_refuses_to_start() {
313
285
  let ws = tmp_dir("grok-nologin");
314
286
  let home = tmp_dir("grok-nologin-home");
315
287
  std::fs::create_dir_all(home.join(".grok")).unwrap();
316
- std::fs::write(
317
- home.join(".grok").join("trusted_folders.toml"),
318
- format!("[folders.\"{}\"]\ntrusted = true\n", ws.display()),
319
- )
320
- .unwrap();
321
288
  let _guard = HomeGuard::set(&home);
322
289
  let err = ensure_grok_login_and_folder_trust(&ws).expect_err("missing auth.json");
323
290
  match err {
@@ -331,12 +298,29 @@ fn grok_missing_login_refuses_to_start() {
331
298
  }
332
299
  }
333
300
 
301
+ #[test]
302
+ #[serial(env)]
303
+ fn grok_login_present_allows_start_without_inspect_or_toml_trust() {
304
+ let ws = tmp_dir("grok-login-only");
305
+ let home = tmp_dir("grok-login-only-home");
306
+ seed_grok_home(&home);
307
+ let _guard = HomeGuard::set(&home);
308
+
309
+ assert!(!home.join("bin/grok").exists(), "no inspect shim may exist");
310
+ assert!(
311
+ !home.join(".grok/trusted_folders.toml").exists(),
312
+ "no trusted TOML may be required"
313
+ );
314
+ ensure_grok_login_and_folder_trust(&ws)
315
+ .expect("valid login alone must allow Grok TUI to own folder consent");
316
+ }
317
+
334
318
  #[test]
335
319
  #[serial(env)]
336
320
  fn grok_spawn_writes_resolved_team_agent_into_project_grok_config() {
337
321
  let ws = tmp_dir("grok-mcp-overlay");
338
322
  let home = tmp_dir("grok-mcp-overlay-home");
339
- seed_grok_home(&home, Some(&ws));
323
+ seed_grok_home(&home);
340
324
  let _guard = HomeGuard::set(&home);
341
325
  let team = write_grok_team(&ws, "grokteam", "grok_writer");
342
326
  let config_path = ws.join(".grok").join("config.toml");
@@ -451,37 +435,40 @@ fn write_grok_team_agents(ws: &Path, team_key: &str, agent_ids: &[&str]) -> Path
451
435
  team
452
436
  }
453
437
 
454
- fn seed_grok_home(home: &Path, trusted_cwd: Option<&Path>) {
438
+ fn seed_grok_home(home: &Path) {
455
439
  let grok = home.join(".grok");
456
440
  std::fs::create_dir_all(&grok).unwrap();
457
441
  std::fs::write(grok.join("auth.json"), r#"{"test":"ok"}"#).unwrap();
458
- if let Some(cwd) = trusted_cwd {
459
- std::fs::write(
460
- grok.join("trusted_folders.toml"),
461
- format!("[folders.\"{}\"]\ntrusted = true\n", cwd.display()),
462
- )
463
- .unwrap();
464
- }
465
442
  }
466
443
 
467
444
  struct HomeGuard {
468
- prev: Option<String>,
445
+ prev_home: Option<String>,
446
+ prev_path: Option<String>,
469
447
  }
470
448
 
471
449
  impl HomeGuard {
472
450
  fn set(home: &Path) -> Self {
473
- let prev = std::env::var("HOME").ok();
451
+ let prev_home = std::env::var("HOME").ok();
452
+ let prev_path = std::env::var("PATH").ok();
474
453
  std::env::set_var("HOME", home);
475
- Self { prev }
454
+ std::env::set_var("PATH", home.join("bin"));
455
+ Self {
456
+ prev_home,
457
+ prev_path,
458
+ }
476
459
  }
477
460
  }
478
461
 
479
462
  impl Drop for HomeGuard {
480
463
  fn drop(&mut self) {
481
- match &self.prev {
464
+ match &self.prev_home {
482
465
  Some(value) => std::env::set_var("HOME", value),
483
466
  None => std::env::remove_var("HOME"),
484
467
  }
468
+ match &self.prev_path {
469
+ Some(value) => std::env::set_var("PATH", value),
470
+ None => std::env::remove_var("PATH"),
471
+ }
485
472
  }
486
473
  }
487
474
 
@@ -229,7 +229,7 @@ got {status:?}"
229
229
  #[test]
230
230
  #[serial(t3_event)]
231
231
  fn t3_unknown_business_status_is_preserved_without_normalized_event() {
232
- let harness = McpSimHarness::new();
232
+ let harness = McpSimHarness::new_without_tmux();
233
233
  let mut worker = harness.spawn_mcp_client("worker_a", "teamA");
234
234
  let status = "garbage-status-xyz";
235
235
  let call = worker.call_tool(
@@ -0,0 +1,97 @@
1
+ use std::collections::BTreeSet;
2
+ use std::path::Path;
3
+
4
+ use crate::lifecycle::launch::pi_mcp::{
5
+ parse_pi_list_models_table, select_exact_pi_model, verify_started_pi_model,
6
+ };
7
+ use crate::model::enums::ProviderEffort;
8
+ use crate::provider::adapters::pi::{build_pi_command_argv, PiCommandRequest, PiSessionSelector};
9
+
10
+ const CATALOG: &[u8] = include_bytes!("fixtures/pi_list_models_g0.stdout.txt");
11
+
12
+ fn argv_has_pair(argv: &[String], flag: &str, value: &str) -> bool {
13
+ argv.windows(2)
14
+ .any(|pair| pair[0] == flag && pair[1] == value)
15
+ }
16
+
17
+ fn command_for_model(model: &str) -> Vec<String> {
18
+ build_pi_command_argv(PiCommandRequest {
19
+ executable: Path::new("/verified/pi"),
20
+ extension: Path::new("/workspace/.team/runtime/pi/t1/w1/team-mcp.ts"),
21
+ model: Some(model),
22
+ effort: Some(ProviderEffort::Medium),
23
+ system_prompt: "worker contract",
24
+ tool_categories: &["mcp_team"],
25
+ session_dir: Path::new("/workspace/.team/runtime/pi/t1/w1/sessions"),
26
+ session: PiSessionSelector::Fresh {
27
+ session_id: "716ba0cb-c491-4471-b41a-43c8d5e1f05a",
28
+ },
29
+ agent_id: "w1",
30
+ })
31
+ .expect("catalog member must build a semantic Pi argv")
32
+ }
33
+
34
+ #[test]
35
+ fn pi_catalog_discovers_all_exact_ids_and_rejects_invalid_or_ambiguous_selection() {
36
+ let models = parse_pi_list_models_table(CATALOG).expect("frozen unfiltered table must parse");
37
+ let unique: BTreeSet<&str> = models.iter().map(String::as_str).collect();
38
+ assert_eq!(models.len(), 221, "the frozen table has 221 data rows");
39
+ assert_eq!(unique.len(), 221, "exact catalog IDs must be unique");
40
+ assert!(unique.contains("team-agent/qwen3.8-27b"));
41
+ assert!(unique.iter().any(|id| id.contains('@')));
42
+ assert!(unique.iter().any(|id| id.ends_with(":fast")));
43
+ assert!(unique.iter().any(|id| id.ends_with(":slow")));
44
+
45
+ for exact in &models {
46
+ let selected = select_exact_pi_model(&models, exact)
47
+ .unwrap_or_else(|error| panic!("exact catalog member {exact} must select: {error}"));
48
+ assert_eq!(selected, *exact, "exact catalog member must round-trip");
49
+ }
50
+
51
+ for invalid in [
52
+ "",
53
+ "foo/bar",
54
+ "cursor/",
55
+ "team-agent/*",
56
+ "gpt-5.4-mini",
57
+ "grok-4.5",
58
+ "grok-4.6",
59
+ "qwen3.8-27b",
60
+ "sonnet:high",
61
+ ] {
62
+ assert!(
63
+ select_exact_pi_model(&models, invalid).is_err(),
64
+ "invalid, wildcard, or ambiguous model must refuse: {invalid:?}"
65
+ );
66
+ }
67
+ }
68
+
69
+ #[test]
70
+ fn pi_exact_catalog_id_is_forwarded_and_started_identity_must_match() {
71
+ let models = parse_pi_list_models_table(CATALOG).expect("frozen unfiltered table must parse");
72
+ assert_eq!(models.len(), 221);
73
+
74
+ for exact in &models {
75
+ let selected = select_exact_pi_model(&models, exact).expect("exact member");
76
+ let argv = command_for_model(&selected);
77
+ assert!(
78
+ argv_has_pair(&argv, "--model", exact),
79
+ "catalog ID must be forwarded byte-for-byte: {exact}; argv={argv:?}"
80
+ );
81
+ assert_eq!(
82
+ argv.iter().filter(|arg| arg.as_str() == "--model").count(),
83
+ 1,
84
+ "model selector must occur exactly once: {argv:?}"
85
+ );
86
+ }
87
+
88
+ assert!(verify_started_pi_model("team-agent/qwen3.8-27b", "team-agent/qwen3.8-27b").is_ok());
89
+ assert!(
90
+ verify_started_pi_model("team-agent/qwen3.8-27b", "xai/grok-4.6").is_err(),
91
+ "started identity mismatch must refuse rather than trust pane chrome"
92
+ );
93
+ assert!(
94
+ verify_started_pi_model("team-agent/qwen3.8-27b", "").is_err(),
95
+ "missing objective started-model evidence must not pass"
96
+ );
97
+ }