@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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +107 -9
- package/crates/team-agent/src/cli/emit.rs +35 -5
- package/crates/team-agent/src/cli/mod.rs +78 -7
- package/crates/team-agent/src/cli/models.rs +443 -0
- package/crates/team-agent/src/cli/spec.rs +4 -1
- package/crates/team-agent/src/cli/types.rs +8 -0
- package/crates/team-agent/src/compiler.rs +157 -0
- package/crates/team-agent/src/leader/owner_bind.rs +21 -0
- package/crates/team-agent/src/leader/provider_attribution.rs +25 -0
- package/crates/team-agent/src/leader/registry.rs +25 -1
- package/crates/team-agent/src/leader/start.rs +41 -9
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +169 -10
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +119 -9
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +859 -12
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +1 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +168 -0
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +209 -0
- package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +71 -1
- package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +3 -1
- package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +396 -3
- package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +3 -1
- package/crates/team-agent/src/lifecycle/types.rs +8 -0
- package/crates/team-agent/src/provider/adapters/pi.rs +12 -6
- package/npm/check-skill-links.mjs +88 -0
- package/package.json +5 -4
- package/skills/team-agent/SKILL.md +15 -49
- package/skills/team-agent/command-coverage.json +8 -1
- 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
|
|
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
|
|
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
|
-
|
|
815
|
-
.map_err(|error|
|
|
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
|
|
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
|
|
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:
|
|
972
|
+
provider_projects_root: session_dir.map(Path::to_path_buf),
|
|
863
973
|
managed_mcp_config: false,
|
|
864
974
|
})
|
|
865
975
|
}
|