@team-agent/installer 0.5.71 → 0.5.73

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 (32) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +107 -9
  4. package/crates/team-agent/src/cli/emit.rs +35 -5
  5. package/crates/team-agent/src/cli/mod.rs +78 -7
  6. package/crates/team-agent/src/cli/models.rs +443 -0
  7. package/crates/team-agent/src/cli/spec.rs +4 -1
  8. package/crates/team-agent/src/cli/types.rs +8 -0
  9. package/crates/team-agent/src/compiler.rs +157 -0
  10. package/crates/team-agent/src/leader/owner_bind.rs +21 -0
  11. package/crates/team-agent/src/leader/provider_attribution.rs +25 -0
  12. package/crates/team-agent/src/leader/registry.rs +25 -1
  13. package/crates/team-agent/src/leader/start.rs +41 -9
  14. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +169 -10
  15. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +119 -9
  16. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +859 -12
  17. package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -0
  18. package/crates/team-agent/src/lifecycle/restart/common.rs +1 -0
  19. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +168 -0
  20. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +209 -0
  21. package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +1 -1
  22. package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +71 -1
  23. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +3 -1
  24. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +396 -3
  25. package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +3 -1
  26. package/crates/team-agent/src/lifecycle/types.rs +8 -0
  27. package/crates/team-agent/src/provider/adapters/pi.rs +12 -6
  28. package/npm/check-skill-links.mjs +88 -0
  29. package/package.json +5 -4
  30. package/skills/team-agent/SKILL.md +15 -49
  31. package/skills/team-agent/command-coverage.json +8 -1
  32. package/skills/team-agent/references/team-agent-operator.md +665 -0
@@ -15,10 +15,12 @@
15
15
 
16
16
  use std::collections::BTreeSet;
17
17
  use std::fs::OpenOptions;
18
- use std::io::Write;
18
+ use std::io::{Read, Write};
19
19
  use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
20
20
  use std::path::{Path, PathBuf};
21
- use std::process::Command;
21
+ use std::process::{Command, Stdio};
22
+ use std::sync::mpsc;
23
+ use std::time::{Duration, Instant};
22
24
 
23
25
  use sha2::{Digest, Sha256};
24
26
 
@@ -586,6 +588,78 @@ fn executable_file_type(path: &Path) -> Result<PiExecutableFileType, ProviderErr
586
588
  }
587
589
  }
588
590
 
591
+ /// Discover candidates with one PATH-first catalog observation. This deliberately
592
+ /// does not resolve adapters or scan wrapper chains; callers use it only for
593
+ /// rejecting an unqualified role model before lifecycle mutation.
594
+ pub(crate) fn run_pi_catalog(
595
+ executable: &Path,
596
+ timeout: Duration,
597
+ max_bytes: u64,
598
+ ) -> Result<Vec<u8>, String> {
599
+ let mut child = Command::new(executable)
600
+ .arg("--list-models")
601
+ .stdin(Stdio::null())
602
+ .stdout(Stdio::piped())
603
+ .stderr(Stdio::null())
604
+ .spawn()
605
+ .map_err(|_| "Pi executable is unavailable on PATH".to_string())?;
606
+ let stdout = child.stdout.take().ok_or_else(|| {
607
+ let _ = child.kill();
608
+ let _ = child.wait();
609
+ "Pi model catalog output unavailable".to_string()
610
+ })?;
611
+ let (sender, receiver) = mpsc::channel();
612
+ std::thread::spawn(move || {
613
+ let mut bytes = Vec::new();
614
+ let result = stdout.take(max_bytes + 1).read_to_end(&mut bytes);
615
+ let _ = sender.send((result, bytes));
616
+ });
617
+ let start = Instant::now();
618
+ loop {
619
+ match child.try_wait() {
620
+ Ok(Some(status)) => {
621
+ let (result, bytes) = receiver.recv_timeout(timeout.saturating_sub(start.elapsed()))
622
+ .map_err(|_| "Pi model catalog command timed out".to_string())?;
623
+ if !status.success() { return Err("Pi model catalog command failed".into()); }
624
+ result.map_err(|_| "Pi model catalog could not be read".to_string())?;
625
+ if bytes.len() as u64 > max_bytes { return Err("Pi model catalog exceeds the bounded output limit".into()); }
626
+ return Ok(bytes);
627
+ }
628
+ Ok(None) if start.elapsed() < timeout => std::thread::sleep(Duration::from_millis(20)),
629
+ Ok(None) => {
630
+ let _ = child.kill();
631
+ let _ = child.wait();
632
+ return Err("Pi model catalog command timed out".into());
633
+ }
634
+ Err(_) => {
635
+ let _ = child.kill();
636
+ let _ = child.wait();
637
+ return Err("Pi model catalog command could not be observed".into());
638
+ }
639
+ }
640
+ }
641
+ }
642
+
643
+ pub(crate) fn pi_model_candidates(requested: &str) -> Result<Vec<String>, ProviderError> {
644
+ let path = std::env::var_os("PATH")
645
+ .ok_or_else(|| ProviderError::Command("PATH is not set".to_string()))?;
646
+ let executable = std::env::split_paths(&path)
647
+ .map(|directory| directory.join("pi"))
648
+ .find(|candidate| candidate.is_file())
649
+ .ok_or_else(|| ProviderError::Command("Pi executable not found on PATH".to_string()))?;
650
+ let catalog = run_pi_catalog(&executable, Duration::from_secs(10), 1024 * 1024)
651
+ .map_err(ProviderError::Command)?;
652
+ let models = parse_pi_list_models_table(&catalog)?;
653
+ Ok(models
654
+ .into_iter()
655
+ .filter(|model| {
656
+ model
657
+ .rsplit_once('/')
658
+ .is_some_and(|(_, name)| name == requested)
659
+ })
660
+ .collect())
661
+ }
662
+
589
663
  fn pi_path_entries() -> Result<Vec<PathBuf>, ProviderError> {
590
664
  let path = std::env::var_os("PATH")
591
665
  .ok_or_else(|| ProviderError::Command("PATH is not set".to_string()))?;
@@ -742,6 +816,27 @@ pub(crate) fn resolve_pi_executable_chain(
742
816
  Ok((chain, models))
743
817
  }
744
818
 
819
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
820
+ pub(crate) enum PiSessionScope {
821
+ Isolated,
822
+ NativeDefault,
823
+ }
824
+
825
+ /// Zero-argument managed Pi is the interactive leader entry. Only that route
826
+ /// may use Pi's cwd-keyed native session root; every explicit or worker route
827
+ /// remains Team Agent session-isolated.
828
+ pub(crate) fn pi_leader_session_scope(
829
+ provider_args: &[String],
830
+ external_path: bool,
831
+ allow_nested_attach: bool,
832
+ ) -> PiSessionScope {
833
+ if provider_args.is_empty() && !external_path && !allow_nested_attach {
834
+ PiSessionScope::NativeDefault
835
+ } else {
836
+ PiSessionScope::Isolated
837
+ }
838
+ }
839
+
745
840
  pub(crate) struct PiMaterializeRequest<'a> {
746
841
  pub workspace: &'a Path,
747
842
  pub team_id: &'a str,
@@ -752,11 +847,13 @@ pub(crate) struct PiMaterializeRequest<'a> {
752
847
  pub tool_categories: &'a [&'a str],
753
848
  pub team_mcp_tools: &'a [&'a str],
754
849
  pub mcp_config: &'a McpConfig,
850
+ pub session_scope: PiSessionScope,
755
851
  }
756
852
 
757
853
  /// ---
758
854
  /// purpose: 共享物化 Pi leader 与 TeamMate 的 wrapper、UUID 和 CommandPlan
759
- /// returns: fresh Pi CommandPlan,含 expected UUID 与 seat session root
855
+ /// returns: fresh Pi CommandPlan;isolated seats carry their session root, while the
856
+ /// interactive leader preserves Pi's native default root
760
857
  /// errors: resolver、exact model、wrapper 或 argv 构造失败时返回 ProviderError
761
858
  /// ---
762
859
  pub(crate) fn materialize_pi_plan(
@@ -789,8 +886,18 @@ fn materialize_pi_plan_with_session(
789
886
  .map(|model| select_exact_pi_model(&catalog, model))
790
887
  .transpose()?;
791
888
  let paths = pi_seat_paths(request.workspace, request.team_id, request.agent_id);
889
+ let session_dir = match request.session_scope {
890
+ PiSessionScope::Isolated => Some(paths.sessions.as_path()),
891
+ PiSessionScope::NativeDefault => None,
892
+ };
792
893
  let resume = if let Some((session_id, session_path, spawn_cwd)) = resume {
793
- let session_root = std::fs::canonicalize(&paths.sessions).map_err(|error| {
894
+ let session_dir = session_dir.ok_or_else(|| {
895
+ ProviderError::ResumeUnavailable(format!(
896
+ "Pi session root is unavailable {}",
897
+ paths.sessions.display()
898
+ ))
899
+ })?;
900
+ let session_root = std::fs::canonicalize(session_dir).map_err(|error| {
794
901
  ProviderError::ResumeUnavailable(format!(
795
902
  "Pi session root is unavailable {}: {error}",
796
903
  paths.sessions.display()
@@ -811,8 +918,11 @@ fn materialize_pi_plan_with_session(
811
918
  let _ = spawn_cwd;
812
919
  Some((session_id, exact_path))
813
920
  } else {
814
- std::fs::create_dir_all(&paths.sessions)
815
- .map_err(|error| ProviderError::Io(format!("{}: {error}", paths.sessions.display())))?;
921
+ if let Some(session_dir) = session_dir {
922
+ std::fs::create_dir_all(session_dir).map_err(|error| {
923
+ ProviderError::Io(format!("{}: {error}", session_dir.display()))
924
+ })?;
925
+ }
816
926
  None
817
927
  };
818
928
  let candidate = candidate_from_mcp_config(request.mcp_config)?;
@@ -834,7 +944,7 @@ fn materialize_pi_plan_with_session(
834
944
  effort: request.effort,
835
945
  system_prompt: request.system_prompt,
836
946
  tool_categories: request.tool_categories,
837
- session_dir: &paths.sessions,
947
+ session_dir,
838
948
  session: PiSessionSelector::Resume { path: &exact_path },
839
949
  agent_id: request.agent_id,
840
950
  })?;
@@ -848,7 +958,7 @@ fn materialize_pi_plan_with_session(
848
958
  effort: request.effort,
849
959
  system_prompt: request.system_prompt,
850
960
  tool_categories: request.tool_categories,
851
- session_dir: &paths.sessions,
961
+ session_dir,
852
962
  session: PiSessionSelector::Fresh {
853
963
  session_id: session_id.as_str(),
854
964
  },
@@ -859,7 +969,7 @@ fn materialize_pi_plan_with_session(
859
969
  Ok(CommandPlan {
860
970
  argv,
861
971
  expected_session_id,
862
- provider_projects_root: Some(paths.sessions),
972
+ provider_projects_root: session_dir.map(Path::to_path_buf),
863
973
  managed_mcp_config: false,
864
974
  })
865
975
  }