@team-agent/installer 0.5.106 → 0.5.108

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 (41) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +1 -1
  4. package/crates/team-agent/src/cli/emit.rs +3 -3
  5. package/crates/team-agent/src/cli/mod.rs +87 -4
  6. package/crates/team-agent/src/cli/models.rs +252 -3
  7. package/crates/team-agent/src/cli/spec.rs +1 -1
  8. package/crates/team-agent/src/cli/status_port/brief.rs +155 -37
  9. package/crates/team-agent/src/cli/tests/base.rs +112 -225
  10. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +184 -0
  11. package/crates/team-agent/src/cli/tests/status_brief.rs +624 -0
  12. package/crates/team-agent/src/compiler/tests.rs +46 -59
  13. package/crates/team-agent/src/leader/tests/byte_findings.rs +1 -14
  14. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +57 -173
  15. package/crates/team-agent/src/lifecycle/launch/pi_mcp_extension.js +226 -0
  16. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +0 -37
  17. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +0 -9
  18. package/crates/team-agent/src/lifecycle/tests/fixtures/pi_native_mcp_host.mjs +71 -0
  19. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +0 -66
  20. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +0 -392
  21. package/crates/team-agent/src/lifecycle/tests/lifecycle_lock.rs +0 -5
  22. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +2 -361
  23. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +1 -16
  24. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +20 -112
  25. package/crates/team-agent/src/lifecycle/tests/pi_fork_session_inheritance_red.rs +1 -12
  26. package/crates/team-agent/src/lifecycle/tests/pi_native_mcp_capability_red.rs +546 -0
  27. package/crates/team-agent/src/lifecycle/tests.rs +1 -0
  28. package/crates/team-agent/src/lifecycle/worker_command_context.rs +11 -54
  29. package/crates/team-agent/src/messaging/tests/wave2.rs +2 -302
  30. package/crates/team-agent/src/model/name_similarity.rs +52 -67
  31. package/crates/team-agent/src/model/task_graph.rs +37 -124
  32. package/crates/team-agent/src/model/yaml/tests.rs +46 -165
  33. package/crates/team-agent/src/provider/model_catalog.rs +166 -3
  34. package/crates/team-agent/src/provider/testdata/cursor-list-models-native.stdout +250 -0
  35. package/crates/team-agent/src/provider/testdata/grok-models-authenticated.stdout +9 -0
  36. package/crates/team-agent/src/provider/testdata/grok-models-unauthenticated.stdout +7 -0
  37. package/crates/team-agent/src/provider/tests/adapter.rs +0 -20
  38. package/crates/team-agent/src/provider/tests/classify.rs +106 -198
  39. package/crates/team-agent/src/tmux_backend/tests.rs +178 -501
  40. package/crates/team-agent/src/transport/tests/wire.rs +0 -55
  41. package/package.json +4 -4
@@ -119,65 +119,52 @@ Implement bounded tasks and report result_envelope_v1.
119
119
  // ───────────────────────────── read_front_matter ─────────────────────────────
120
120
 
121
121
  #[test]
122
- fn front_matter_no_marker_returns_empty_meta_and_full_text() {
123
- // No leading "---\n" → ({}, text) verbatim (compiler.py:175-176).
124
- let p = write_tmp("no_marker.md", "hello\nworld\n");
125
- let (meta, body) = read_front_matter(&p).unwrap();
126
- assert_eq!(meta, Value::Map(vec![]));
127
- assert_eq!(body, "hello\nworld\n");
128
- }
129
-
130
- #[test]
131
- fn front_matter_basic_splits_meta_and_lstrips_body() {
132
- let p = write_tmp("basic.md", "---\nname: x\nrole: R\n---\n\nbody line\n");
133
- let (meta, body) = read_front_matter(&p).unwrap();
134
- assert_eq!(
135
- meta,
136
- Value::Map(vec![
137
- ("name".to_string(), Value::Str("x".to_string())),
138
- ("role".to_string(), Value::Str("R".to_string())),
139
- ])
140
- );
141
- assert_eq!(body, "body line\n");
142
- }
143
-
144
- #[test]
145
- fn front_matter_empty_block_is_empty_map() {
146
- // "---\n\n---\nbody\n": closing marker at the blank line → raw "" → {} ; body "body\n".
147
- let p = write_tmp("empty.md", "---\n\n---\nbody\n");
148
- let (meta, body) = read_front_matter(&p).unwrap();
149
- assert_eq!(meta, Value::Map(vec![]));
150
- assert_eq!(body, "body\n");
151
- }
152
-
153
- #[test]
154
- fn front_matter_body_lstrip_strips_only_newlines() {
155
- // body.lstrip("\n") removes leading NEWLINES but keeps the 2-space indent.
156
- let p = write_tmp("lstrip.md", "---\nname: x\n---\n\n\n indented body\n");
157
- let (meta, body) = read_front_matter(&p).unwrap();
158
- assert_eq!(
159
- meta,
160
- Value::Map(vec![("name".to_string(), Value::Str("x".to_string()))])
161
- );
162
- assert_eq!(body, " indented body\n");
163
- }
164
-
165
- #[test]
166
- fn front_matter_unterminated_falls_back_to_plain_text() {
167
- let text = "---\nname: x\n";
168
- let p = write_tmp("unterminated.md", text);
169
- let (meta, body) = read_front_matter(&p).expect("unterminated front matter is plain text");
170
- assert_eq!(meta, Value::Map(Vec::new()));
171
- assert_eq!(body, text);
172
- }
173
-
174
- #[test]
175
- fn front_matter_non_object_falls_back_to_plain_text() {
176
- let text = "---\n- a\n- b\n---\nbody\n";
177
- let p = write_tmp("list.md", text);
178
- let (meta, body) = read_front_matter(&p).expect("non-object front matter is plain text");
179
- assert_eq!(meta, Value::Map(Vec::new()));
180
- assert_eq!(body, text);
122
+ fn front_matter_cases() {
123
+ struct Case {
124
+ id: &'static str,
125
+ file: &'static str,
126
+ text: &'static str,
127
+ meta: &'static [(&'static str, &'static str)],
128
+ body: &'static str,
129
+ }
130
+ const CASES: &[Case] = &[
131
+ // No leading "---\n" → ({}, text) verbatim (compiler.py:175-176).
132
+ Case {
133
+ id: "front_matter_no_marker_returns_empty_meta_and_full_text", file: "no_marker.md",
134
+ text: "hello\nworld\n", meta: &[], body: "hello\nworld\n",
135
+ },
136
+ Case {
137
+ id: "front_matter_basic_splits_meta_and_lstrips_body", file: "basic.md",
138
+ text: "---\nname: x\nrole: R\n---\n\nbody line\n", meta: &[("name", "x"), ("role", "R")], body: "body line\n",
139
+ },
140
+ // Closing marker at the blank line → raw "" → {}; body "body\n".
141
+ Case {
142
+ id: "front_matter_empty_block_is_empty_map", file: "empty.md",
143
+ text: "---\n\n---\nbody\n", meta: &[], body: "body\n",
144
+ },
145
+ // body.lstrip("\n") removes leading NEWLINES but keeps the 2-space indent.
146
+ Case {
147
+ id: "front_matter_body_lstrip_strips_only_newlines", file: "lstrip.md",
148
+ text: "---\nname: x\n---\n\n\n indented body\n", meta: &[("name", "x")], body: " indented body\n",
149
+ },
150
+ Case {
151
+ id: "front_matter_unterminated_falls_back_to_plain_text", file: "unterminated.md",
152
+ text: "---\nname: x\n", meta: &[], body: "---\nname: x\n",
153
+ },
154
+ Case {
155
+ id: "front_matter_non_object_falls_back_to_plain_text", file: "list.md",
156
+ text: "---\n- a\n- b\n---\nbody\n", meta: &[], body: "---\n- a\n- b\n---\nbody\n",
157
+ },
158
+ ];
159
+ for case in CASES {
160
+ let p = write_tmp(case.file, case.text);
161
+ let (meta, body) = read_front_matter(&p).unwrap_or_else(|err| panic!("{}: {err}", case.id));
162
+ let expected_meta = Value::Map(case.meta.iter().map(|(key, value)| {
163
+ ((*key).to_string(), Value::Str((*value).to_string()))
164
+ }).collect());
165
+ assert_eq!(meta, expected_meta, "{}: metadata", case.id);
166
+ assert_eq!(body, case.body, "{}: body", case.id);
167
+ }
181
168
  }
182
169
 
183
170
  // ───────────────────────────── compile_team: full dict parity ─────────────────────────────
@@ -54,8 +54,7 @@ fn d1_claim_team_owner_includes_os_user() {
54
54
  // fingerprint/requested_provider/warning. Golden _receiver_from_claim_target (__init__.py:861-877).
55
55
  // Rust LeaderReceiver serializes all 17 (no skip_serializing_if) -> 3 always-null extras leak. RED.
56
56
  // (The POPULATED tmux values session_name/window_*/pane_* come from the caller-target scan — a
57
- // deferred real-tmux seam, see d2_receiver_populated_from_caller_target_seam; the KEY-SET + ORDER
58
- // locked here are unchanged by that scan.)
57
+ // deferred real-tmux seam; the KEY-SET + ORDER locked here are unchanged by that scan.)
59
58
  #[test]
60
59
  #[serial_test::serial(env)]
61
60
  fn d2_claim_leader_receiver_is_fifteen_golden_keys_in_order_no_extras() {
@@ -98,18 +97,6 @@ fn d2_claim_leader_receiver_is_fifteen_golden_keys_in_order_no_extras() {
98
97
  ], "golden _receiver_from_claim_target 15-key set + ORDER (__init__.py:861-877 + BUG-4 socket-qualified receiver)");
99
98
  }
100
99
 
101
- // D2 seam — the caller-target SCAN that fills session_name/window_index/window_name/pane_index/
102
- // pane_tty/pane_current_command from core_list_targets (golden _receiver_from_claim_target reads
103
- // target[...]). Rust make_receiver leaves them None (no scan). Real-tmux: needs core_list_targets.
104
- #[test]
105
- #[ignore = "real-tmux seam: leader_receiver session_name/window_*/pane_* are populated from the \
106
- caller target via core_list_targets (golden _receiver_from_claim_target); Rust has no \
107
- scan (values null). Porter wires the target scan; this asserts the populated values."]
108
- fn d2_receiver_populated_from_caller_target_seam() {
109
- // Golden contract: new_receiver.session_name == caller_target.session_name, etc. Needs a live
110
- // tmux target list (core_list_targets) — out of in-process scope.
111
- }
112
-
113
100
  // D3 [BLOCK] — bound_pane is RECEIVER-first (golden :624 receiver.pane_id OR owner.pane_id). PROBE-B
114
101
  // (receiver=%9, owner=%1, caller=%9): golden ok=True already_bound. Rust derives OWNER-first
115
102
  // (owner %1 != caller %9) -> owner live + !confirm -> refused force_confirm_required. RED.
@@ -1,5 +1,5 @@
1
1
  //! ---
2
- //! purpose: Pi executable/catalog/adapter 验证、per-seat wrapper 与共享启动计划物化
2
+ //! purpose: Pi executable/catalog 验证、原生/公共 MCP 能力注册与共享启动计划物化
3
3
  //! contract:
4
4
  //! provides:
5
5
  //! - name: materialize_pi_plan
@@ -29,8 +29,6 @@ use crate::model::enums::{Provider, ProviderEffort};
29
29
  use crate::provider::adapters::pi::{build_pi_command_argv, PiCommandRequest, PiSessionSelector};
30
30
  use crate::provider::{CommandPlan, McpConfig, ProviderError, SessionId};
31
31
 
32
- const ADAPTER_NAME: &str = "pi-mcp-adapter";
33
-
34
32
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
35
33
  pub(crate) enum PiExecutableFileType {
36
34
  Wrapper,
@@ -38,17 +36,6 @@ pub(crate) enum PiExecutableFileType {
38
36
  Binary,
39
37
  }
40
38
 
41
- #[derive(Debug, Clone, PartialEq, Eq)]
42
- pub(crate) struct PiAdapterIdentity {
43
- pub package_name: String,
44
- pub version: String,
45
- pub extension_entry: String,
46
- pub package_json: PathBuf,
47
- pub index_ts: PathBuf,
48
- pub package_json_sha256: String,
49
- pub index_ts_sha256: String,
50
- }
51
-
52
39
  #[derive(Debug, Clone, PartialEq, Eq)]
53
40
  pub(crate) struct PiExecutableChain {
54
41
  pub path_entry: PathBuf,
@@ -57,7 +44,6 @@ pub(crate) struct PiExecutableChain {
57
44
  pub real_binary: PathBuf,
58
45
  pub pi_version: String,
59
46
  pub catalog_sha256: String,
60
- pub adapter: PiAdapterIdentity,
61
47
  }
62
48
 
63
49
  #[derive(Debug, Clone, PartialEq, Eq)]
@@ -183,54 +169,9 @@ pub(crate) fn verify_started_pi_model(selected: &str, observed: &str) -> Result<
183
169
  }
184
170
 
185
171
  /// ---
186
- /// purpose: 校验 pi-mcp-adapter 的公共 package/entry 能力
187
- /// returns: package identity 与可解析的 absolute entry 完整时成功
188
- /// errors: package 或 entry 缺失时返回 ProviderError;version/digests 仅保留为观测值
189
- /// ---
190
- pub(crate) fn validate_pi_adapter_identity(
191
- identity: &PiAdapterIdentity,
192
- ) -> Result<(), ProviderError> {
193
- if identity.package_name != ADAPTER_NAME
194
- || identity.extension_entry.is_empty()
195
- || !identity.package_json.is_absolute()
196
- || !identity.index_ts.is_absolute()
197
- {
198
- return Err(ProviderError::Command(
199
- "Pi MCP adapter package or extension entry is not resolvable".to_string(),
200
- ));
201
- }
202
- let package_root = identity.package_json.parent().ok_or_else(|| {
203
- ProviderError::Command("Pi MCP adapter package root is not resolvable".to_string())
204
- })?;
205
- let extension_entry = Path::new(&identity.extension_entry);
206
- if extension_entry.is_absolute() {
207
- return Err(ProviderError::Command(
208
- "Pi MCP adapter extension entry must be package-relative".to_string(),
209
- ));
210
- }
211
- let declared_entry =
212
- std::fs::canonicalize(package_root.join(extension_entry)).map_err(|error| {
213
- ProviderError::Io(format!(
214
- "Pi MCP adapter extension entry {}: {error}",
215
- identity.index_ts.display()
216
- ))
217
- })?;
218
- let loaded_entry = std::fs::canonicalize(&identity.index_ts)
219
- .map_err(|error| ProviderError::Io(format!("{}: {error}", identity.index_ts.display())))?;
220
- if declared_entry != loaded_entry || !identity.package_json.is_file() {
221
- return Err(ProviderError::Command(
222
- "Pi MCP adapter package or extension entry is not loadable".to_string(),
223
- ));
224
- }
225
- std::fs::read(&loaded_entry)
226
- .map_err(|error| ProviderError::Io(format!("{}: {error}", loaded_entry.display())))?;
227
- Ok(())
228
- }
229
-
230
- /// ---
231
- /// purpose: 校验 Pi launch/real path、catalog 与 adapter 公共能力
232
- /// returns: executable chain 与 capability receipts 完整时成功
233
- /// errors: 缺路径、catalog receipt 或 adapter capability 时返回 ProviderError
172
+ /// purpose: 校验 Pi launch/real path 与 live catalog;MCP 能力在实际 Pi context 验证
173
+ /// returns: executable chain 与 catalog receipt 完整时成功
174
+ /// errors: 缺路径或 catalog receipt 时返回 ProviderError
234
175
  /// ---
235
176
  pub(crate) fn validate_pi_executable_chain(chain: &PiExecutableChain) -> Result<(), ProviderError> {
236
177
  if !chain.path_entry.is_absolute()
@@ -253,12 +194,11 @@ pub(crate) fn validate_pi_executable_chain(chain: &PiExecutableChain) -> Result<
253
194
  "Pi executable chain does not provide the required public protocol".to_string(),
254
195
  ));
255
196
  }
256
- validate_pi_adapter_identity(&chain.adapter)
197
+ Ok(())
257
198
  }
258
199
 
259
200
  pub(crate) struct PiWrapperRequest<'a> {
260
201
  pub destination: &'a Path,
261
- pub adapter: &'a PiAdapterIdentity,
262
202
  pub candidate_executable: &'a Path,
263
203
  pub mcp_config: &'a McpConfig,
264
204
  pub team_id: &'a str,
@@ -301,7 +241,17 @@ fn candidate_from_mcp_config(config: &McpConfig) -> Result<PathBuf, ProviderErro
301
241
  }
302
242
 
303
243
  fn render_pi_wrapper(request: &PiWrapperRequest<'_>) -> Result<String, ProviderError> {
304
- validate_pi_adapter_identity(request.adapter)?;
244
+ let allowed = request.include_tools.iter().copied().collect::<BTreeSet<_>>();
245
+ if allowed.is_empty()
246
+ || allowed.len() != request.include_tools.len()
247
+ || allowed
248
+ .iter()
249
+ .any(|tool| !["send_message", "report_result", "get_team_status"].contains(tool))
250
+ {
251
+ return Err(ProviderError::Command(
252
+ "Pi MCP tools must be a non-empty subset of the three Team tools".to_string(),
253
+ ));
254
+ }
305
255
  let configured_candidate = candidate_from_mcp_config(request.mcp_config)?;
306
256
  let expected = std::fs::canonicalize(request.candidate_executable)
307
257
  .unwrap_or_else(|_| request.candidate_executable.to_path_buf());
@@ -349,63 +299,55 @@ fn render_pi_wrapper(request: &PiWrapperRequest<'_>) -> Result<String, ProviderE
349
299
  "TEAM_AGENT_OWNER_TEAM_ID".to_string(),
350
300
  serde_json::Value::String(request.team_id.to_string()),
351
301
  );
352
- let definition = serde_json::to_string_pretty(&serde_json::Value::Object(server))
302
+ let definition = serde_json::to_string(&serde_json::Value::Object(server))
353
303
  .map_err(|error| ProviderError::Command(format!("serialize Pi MCP server: {error}")))?;
304
+ let receipt_root = request
305
+ .destination
306
+ .parent()
307
+ .filter(|path| path.is_absolute())
308
+ .ok_or_else(|| {
309
+ ProviderError::Command("Pi MCP wrapper requires an absolute seat directory".to_string())
310
+ })?;
311
+ let receipt_root = serde_json::to_string(&receipt_root.to_string_lossy())
312
+ .map_err(|error| ProviderError::Command(format!("serialize Pi MCP seat directory: {error}")))?;
354
313
  Ok(format!(
355
- r#"const MCP_RUNTIME_REGISTER_EVENT = "pi-mcp-adapter:runtime-register:v1";
356
-
357
- export default function teamAgentMcp(pi: any) {{
358
- let registration: {{ dispose(): Promise<void> }} | undefined;
359
-
360
- pi.on("session_start", () => {{
361
- if (registration) return;
362
- const request: any = {{
363
- version: 1,
364
- name: "team_orchestrator",
365
- definition: {definition},
366
- }};
367
- pi.events.emit(MCP_RUNTIME_REGISTER_EVENT, request);
368
- if (!request.result) throw new Error("pi-mcp-adapter is not enabled");
369
- if (!request.result.ok) throw request.result.error;
370
- registration = request.result.registration;
371
- }});
372
-
373
- pi.on("session_shutdown", async () => {{
374
- const current = registration;
375
- registration = undefined;
376
- await current?.dispose();
377
- }});
378
- }}
379
- "#
314
+ "const teamMcpDefinition = {definition};\nconst teamMcpReceiptRoot = {receipt_root};\n\n{}",
315
+ include_str!("pi_mcp_extension.js")
380
316
  ))
381
317
  }
382
318
 
383
319
  /// ---
384
- /// purpose: 静态核对 wrapper 只做 per-seat runtime MCP registration
385
- /// returns: wrapper 保留 direct Pi 配置且冻结 seat identity 时成功
386
- /// errors: identity、runtime registration 或 candidate 缺失时返回 ProviderError
320
+ /// purpose: 静态核对 wrapper 使用原生/公共 MCP 能力模板与指定 candidate
321
+ /// returns: 模板完整且 candidate、seat 目录正确时成功
322
+ /// errors: 模板、candidate 或 seat 目录不匹配时返回 ProviderError
387
323
  /// ---
388
324
  pub(crate) fn validate_pi_wrapper_source(
389
325
  source: &str,
390
- adapter: &PiAdapterIdentity,
391
326
  candidate_executable: &Path,
392
327
  ) -> Result<(), ProviderError> {
393
- validate_pi_adapter_identity(adapter)?;
394
- let candidate = candidate_executable.to_string_lossy();
395
- if !source.contains(candidate.as_ref())
396
- || !source.contains("pi-mcp-adapter:runtime-register:v1")
397
- || !source.contains("session_start")
398
- || !source.contains("session_shutdown")
399
- || !source.contains("team_orchestrator")
400
- || !source.contains("includeTools")
401
- || source.contains("createMcpAdapter")
402
- || source.contains("directTools")
403
- || source.contains("toolPrefix")
404
- || source.contains("--mcp-config")
405
- {
406
- return Err(ProviderError::Command(
407
- "Pi wrapper source does not preserve direct Pi plus per-seat runtime MCP".to_string(),
408
- ));
328
+ let invalid = || {
329
+ ProviderError::Command(
330
+ "Pi wrapper source is not the owned MCP capability template".to_string(),
331
+ )
332
+ };
333
+ let prefix = source
334
+ .strip_suffix(include_str!("pi_mcp_extension.js"))
335
+ .ok_or_else(invalid)?;
336
+ let (definition, root) = prefix
337
+ .strip_prefix("const teamMcpDefinition = ")
338
+ .and_then(|value| value.split_once(";\nconst teamMcpReceiptRoot = "))
339
+ .ok_or_else(invalid)?;
340
+ let definition: serde_json::Value = serde_json::from_str(definition).map_err(|_| invalid())?;
341
+ let command = definition
342
+ .get("command")
343
+ .and_then(serde_json::Value::as_str)
344
+ .ok_or_else(invalid)?;
345
+ let root: String = serde_json::from_str(root.strip_suffix(";\n\n").ok_or_else(invalid)?)
346
+ .map_err(|_| invalid())?;
347
+ let expected = std::fs::canonicalize(candidate_executable)
348
+ .unwrap_or_else(|_| candidate_executable.to_path_buf());
349
+ if Path::new(command) != expected || !Path::new(&root).is_absolute() {
350
+ return Err(invalid());
409
351
  }
410
352
  Ok(())
411
353
  }
@@ -424,7 +366,7 @@ pub(crate) fn write_pi_wrapper_with_publish(
424
366
  publish: impl FnOnce(&Path, &Path) -> std::io::Result<()>,
425
367
  ) -> Result<PathBuf, ProviderError> {
426
368
  let source = render_pi_wrapper(&request)?;
427
- validate_pi_wrapper_source(&source, request.adapter, request.candidate_executable)?;
369
+ validate_pi_wrapper_source(&source, request.candidate_executable)?;
428
370
  let parent = request.destination.parent().ok_or_else(|| {
429
371
  ProviderError::Io("Pi wrapper destination has no parent directory".to_string())
430
372
  })?;
@@ -726,63 +668,8 @@ fn command_stdout(executable: &Path, args: &[&str]) -> Result<Vec<u8>, ProviderE
726
668
  Ok(output.stdout)
727
669
  }
728
670
 
729
- fn discover_pi_adapter(launch_executable: &Path) -> Result<PiAdapterIdentity, ProviderError> {
730
- let listing = command_stdout(launch_executable, &["list"])?;
731
- let listing = std::str::from_utf8(&listing).map_err(|error| {
732
- ProviderError::Command(format!("Pi package list is not UTF-8: {error}"))
733
- })?;
734
- let mut lines = listing.lines();
735
- let mut package_root = None;
736
- while let Some(line) = lines.next() {
737
- if line.trim() == "npm:pi-mcp-adapter" {
738
- package_root = lines
739
- .next()
740
- .map(str::trim)
741
- .filter(|path| Path::new(path).is_absolute())
742
- .map(PathBuf::from);
743
- break;
744
- }
745
- }
746
- let package_root = package_root.ok_or_else(|| {
747
- ProviderError::Command("Pi package list does not resolve npm:pi-mcp-adapter".to_string())
748
- })?;
749
- let package_json = package_root.join("package.json");
750
- let package_bytes = std::fs::read(&package_json)
751
- .map_err(|error| ProviderError::Io(format!("{}: {error}", package_json.display())))?;
752
- let package: serde_json::Value = serde_json::from_slice(&package_bytes)
753
- .map_err(|error| ProviderError::Command(format!("{}: {error}", package_json.display())))?;
754
- let extension_entry = package
755
- .pointer("/pi/extensions")
756
- .and_then(serde_json::Value::as_array)
757
- .and_then(|entries| entries.iter().find_map(serde_json::Value::as_str))
758
- .unwrap_or("")
759
- .to_string();
760
- let index_ts = package_root.join(extension_entry.trim_start_matches("./"));
761
- let index_bytes = std::fs::read(&index_ts)
762
- .map_err(|error| ProviderError::Io(format!("{}: {error}", index_ts.display())))?;
763
- let identity = PiAdapterIdentity {
764
- package_name: package
765
- .get("name")
766
- .and_then(serde_json::Value::as_str)
767
- .unwrap_or("")
768
- .to_string(),
769
- version: package
770
- .get("version")
771
- .and_then(serde_json::Value::as_str)
772
- .unwrap_or("")
773
- .to_string(),
774
- extension_entry,
775
- package_json,
776
- index_ts,
777
- package_json_sha256: sha256(&package_bytes),
778
- index_ts_sha256: sha256(&index_bytes),
779
- };
780
- validate_pi_adapter_identity(&identity)?;
781
- Ok(identity)
782
- }
783
-
784
671
  /// ---
785
- /// purpose: 从当前 PATH 环境测量 Pi launch/real executable、catalog 与 adapter
672
+ /// purpose: 从当前 PATH 环境测量 Pi launch/real executable 与 live catalog,不检查 Pi packages
786
673
  /// returns: 已验证的 chain 与同次 live catalog exact ids
787
674
  /// errors: 缺失、命令失败或任一公共 protocol capability 缺失时返回 ProviderError
788
675
  /// ---
@@ -829,7 +716,6 @@ pub(crate) fn resolve_pi_executable_chain(
829
716
  })?;
830
717
  let catalog = command_stdout(&path_entry, &["--list-models"])?;
831
718
  let models = parse_pi_list_models_table(&catalog)?;
832
- let adapter = discover_pi_adapter(&path_entry)?;
833
719
  let chain = PiExecutableChain {
834
720
  path_entry: path_entry.clone(),
835
721
  path_entry_type,
@@ -837,7 +723,6 @@ pub(crate) fn resolve_pi_executable_chain(
837
723
  real_binary,
838
724
  pi_version: version,
839
725
  catalog_sha256: sha256(&catalog),
840
- adapter,
841
726
  };
842
727
  validate_pi_executable_chain(&chain)?;
843
728
  Ok((chain, models))
@@ -906,7 +791,7 @@ fn materialize_pi_plan_with_session(
906
791
  request: PiMaterializeRequest<'_>,
907
792
  resume: Option<(&SessionId, &Path, &Path)>,
908
793
  ) -> Result<CommandPlan, ProviderError> {
909
- let (chain, catalog) = resolve_pi_executable_chain()?;
794
+ let (_chain, catalog) = resolve_pi_executable_chain()?;
910
795
  let model = request
911
796
  .model
912
797
  .map(|model| select_exact_pi_model(&catalog, model))
@@ -954,7 +839,6 @@ fn materialize_pi_plan_with_session(
954
839
  let candidate = candidate_from_mcp_config(request.mcp_config)?;
955
840
  let wrapper = write_pi_wrapper(PiWrapperRequest {
956
841
  destination: &paths.wrapper,
957
- adapter: &chain.adapter,
958
842
  candidate_executable: &candidate,
959
843
  mcp_config: request.mcp_config,
960
844
  team_id: request.team_id,