@team-agent/installer 0.5.101 → 0.5.102

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 (42) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/tests/main_preserved.rs +0 -74
  4. package/crates/team-agent/src/compiler/tests.rs +20 -23
  5. package/crates/team-agent/src/compiler.rs +49 -65
  6. package/crates/team-agent/src/coordinator/tests/s3_bounded.rs +16 -48
  7. package/crates/team-agent/src/leader/start.rs +1 -14
  8. package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -1
  9. package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
  10. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +0 -28
  11. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +11 -9
  12. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +8 -401
  13. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +2 -2
  14. package/crates/team-agent/src/lifecycle/worker_command_context.rs +22 -8
  15. package/crates/team-agent/src/mcp_server/helpers.rs +0 -16
  16. package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +2 -7
  17. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -127
  18. package/crates/team-agent/src/mcp_server/mod.rs +7 -12
  19. package/crates/team-agent/src/mcp_server/tests/scoped.rs +1 -132
  20. package/crates/team-agent/src/mcp_server/tests/send.rs +1 -93
  21. package/crates/team-agent/src/mcp_server/tests/tools.rs +0 -61
  22. package/crates/team-agent/src/mcp_server/tests/wire.rs +6 -51
  23. package/crates/team-agent/src/mcp_server/tests.rs +4 -200
  24. package/crates/team-agent/src/mcp_server/tools.rs +5 -396
  25. package/crates/team-agent/src/mcp_server/types.rs +3 -36
  26. package/crates/team-agent/src/mcp_server/wire.rs +2 -223
  27. package/crates/team-agent/src/messaging/leader_receiver.rs +1 -1
  28. package/crates/team-agent/src/messaging/mod.rs +2 -2
  29. package/crates/team-agent/src/messaging/scheduler.rs +2 -205
  30. package/crates/team-agent/src/messaging/tests/basic.rs +0 -31
  31. package/crates/team-agent/src/messaging/tests/main_preserved.rs +0 -73
  32. package/crates/team-agent/src/messaging/tests/runtime.rs +0 -26
  33. package/crates/team-agent/src/messaging/types.rs +0 -20
  34. package/crates/team-agent/src/model/spec.rs +14 -18
  35. package/crates/team-agent/src/state/repository/tests.rs +1 -16
  36. package/crates/team-agent/src/state/repository.rs +1 -33
  37. package/package.json +4 -4
  38. package/skills/team-agent/SKILL.md +5 -5
  39. package/skills/team-agent/command-coverage.json +7 -0
  40. package/skills/team-agent/references/team-agent-operator.md +24 -116
  41. package/skills/team-agent/references/team-in-team.md +4 -19
  42. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +0 -484
@@ -1,9 +1,8 @@
1
1
  use crate::cli::spec::{CommandKind, COMMAND_SPECS};
2
2
  use crate::compiler::compile_role_agent;
3
3
  use crate::lifecycle::launch::pi_mcp::parse_pi_leader_args;
4
- use crate::mcp_server::TeamOrchestratorTools;
5
4
  use crate::model::enums::{AuthMode, Provider, ProviderEffort};
6
- use crate::model::ids::{AgentId, TeamKey};
5
+ use crate::model::ids::AgentId;
7
6
  use crate::model::yaml::Value;
8
7
  use crate::provider::{get_adapter, ProviderCommandContext};
9
8
  use crate::transport::test_support::OfflineTransport;
@@ -44,71 +43,6 @@ fn run_process_isolated(marker: &str, test_name: &str, body: impl FnOnce()) {
44
43
  );
45
44
  }
46
45
 
47
- #[cfg(unix)]
48
- const FILE_H_NATIVE_COMPILE_DEADLINE: std::time::Duration = std::time::Duration::from_secs(15);
49
- #[cfg(unix)]
50
- const FILE_H_NATIVE_READINESS_DEADLINE: std::time::Duration = std::time::Duration::from_secs(3);
51
-
52
- #[cfg(unix)]
53
- #[derive(Debug)]
54
- struct FileHCommandTimeout {
55
- stage: &'static str,
56
- pid: u32,
57
- deadline: std::time::Duration,
58
- elapsed: std::time::Duration,
59
- reaped_status: std::process::ExitStatus,
60
- }
61
-
62
- #[cfg(unix)]
63
- fn run_file_h_command_with_deadline(
64
- command: &mut std::process::Command,
65
- stage: &'static str,
66
- deadline: std::time::Duration,
67
- stdout_path: &Path,
68
- stderr_path: &Path,
69
- ) -> Result<std::process::Output, FileHCommandTimeout> {
70
- let stdout_file = std::fs::File::create(stdout_path)
71
- .unwrap_or_else(|error| panic!("{stage}: create stdout receipt failed: {error}"));
72
- let stderr_file = std::fs::File::create(stderr_path)
73
- .unwrap_or_else(|error| panic!("{stage}: create stderr receipt failed: {error}"));
74
- let mut child = command
75
- .stdout(std::process::Stdio::from(stdout_file))
76
- .stderr(std::process::Stdio::from(stderr_file))
77
- .spawn()
78
- .unwrap_or_else(|error| panic!("{stage}: spawn failed: {error}"));
79
- let pid = child.id();
80
- let started = std::time::Instant::now();
81
- loop {
82
- if let Some(status) = child
83
- .try_wait()
84
- .unwrap_or_else(|error| panic!("{stage}: poll pid {pid} failed: {error}"))
85
- {
86
- return Ok(std::process::Output {
87
- status,
88
- stdout: std::fs::read(stdout_path).unwrap_or_default(),
89
- stderr: std::fs::read(stderr_path).unwrap_or_default(),
90
- });
91
- }
92
- let elapsed = started.elapsed();
93
- if elapsed >= deadline {
94
- child
95
- .kill()
96
- .unwrap_or_else(|error| panic!("{stage}: kill exact pid {pid} failed: {error}"));
97
- let reaped_status = child
98
- .wait()
99
- .unwrap_or_else(|error| panic!("{stage}: reap exact pid {pid} failed: {error}"));
100
- return Err(FileHCommandTimeout {
101
- stage,
102
- pid,
103
- deadline,
104
- elapsed,
105
- reaped_status,
106
- });
107
- }
108
- std::thread::park_timeout(std::time::Duration::from_millis(10));
109
- }
110
- }
111
-
112
46
  fn dynamic_add_fixture(root: &Path, label: &str, role_doc: &str) -> (PathBuf, PathBuf) {
113
47
  let team = root.join(label);
114
48
  std::fs::create_dir_all(team.join("agents")).expect("create dynamic team fixture");
@@ -287,10 +221,13 @@ fn pi_leader_and_teammate_body(hermetic: &HermeticTestEnv) {
287
221
  .expect("write provider-default teammate role");
288
222
  let defaults = compile_role_agent(&role, &Value::Map(Vec::new()), "/workspace")
289
223
  .expect("provider: pi teammate may use Pi model and effort defaults");
290
- assert_eq!(defaults.agent.get("model"), Some(&Value::Null));
291
- assert!(
292
- defaults.agent.get("effort").is_none(),
293
- "omitted Pi model and effort must remain absent"
224
+ assert_eq!(
225
+ defaults.agent.get("model"),
226
+ Some(&Value::Str("openai-codex/gpt-6-luna".to_string()))
227
+ );
228
+ assert_eq!(
229
+ defaults.agent.get("effort"),
230
+ Some(&Value::Str("max".to_string()))
294
231
  );
295
232
  std::fs::remove_dir_all(root).expect("remove role fixture");
296
233
 
@@ -572,336 +509,6 @@ fn pi_leader_and_teammate_body(hermetic: &HermeticTestEnv) {
572
509
  crate::state::persist::load_runtime_state(&dead_team).expect("load dead rollback state");
573
510
  assert!(dead_state.pointer("/agents/mate").is_none());
574
511
 
575
- #[cfg(unix)]
576
- {
577
- let live = dynamic_root.join("live-public-add");
578
- std::fs::create_dir_all(live.join("agents")).expect("create live agents dir");
579
- std::fs::create_dir_all(live.join("roles")).expect("create live roles dir");
580
- std::fs::write(
581
- live.join("TEAM.md"),
582
- "---\nname: current\nobjective: Public no-spec add fixture.\nprovider: fake\n---\n\nfixture\n",
583
- )
584
- .expect("write live TEAM.md");
585
- std::fs::write(
586
- live.join("agents/implementer.md"),
587
- "---\nname: implementer\nrole: Existing Worker\nprovider: fake\nmodel: fake\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nexisting\n",
588
- )
589
- .expect("write live existing role");
590
- std::fs::write(live.join("roles/mate.md"), fake_role).expect("write live add role");
591
- let live_socket = "/private/tmp/tmux-501/ta-live-owning";
592
- let live_session = "team-agent-leader-pi-live-owning";
593
- crate::state::persist::save_runtime_state(
594
- &live,
595
- &json!({
596
- "active_team_key": "current",
597
- "tmux_endpoint": worker_socket,
598
- "tmux_socket": worker_socket,
599
- "agents": {
600
- "implementer": {
601
- "status": "running",
602
- "provider": "fake",
603
- "model": "fake",
604
- "auth_mode": "subscription",
605
- "window": "implementer"
606
- }
607
- },
608
- "teams": {
609
- "current": {
610
- "status": "alive",
611
- "team_dir": live,
612
- "session_name": null,
613
- "tmux_endpoint": worker_socket,
614
- "tmux_socket": worker_socket,
615
- "agents": {
616
- "implementer": {
617
- "status": "running",
618
- "provider": "fake",
619
- "model": "fake",
620
- "auth_mode": "subscription",
621
- "window": "implementer"
622
- }
623
- },
624
- "leader_receiver": {
625
- "status": "attached",
626
- "session_name": live_session,
627
- "tmux_socket": live_socket
628
- }
629
- },
630
- "sibling": {
631
- "status": "alive",
632
- "team_dir": live.join("sibling"),
633
- "session_name": null,
634
- "agents": {},
635
- "leader_receiver": {
636
- "status": "attached",
637
- "session_name": "team-agent-leader-pi-sibling",
638
- "tmux_socket": "/private/tmp/tmux-501/ta-live-sibling"
639
- }
640
- }
641
- }
642
- }),
643
- )
644
- .expect("seed live nested receiver");
645
- super::launch_spawn::seed_healthy_coordinator(&live);
646
- let runtime_spec = crate::model::paths::runtime_spec_path(&live, "current");
647
- assert!(
648
- !runtime_spec.exists(),
649
- "fixture must enter the TEAM.md fallback before runtime spec exists"
650
- );
651
-
652
- let shim_root = live.join("tmux-shim");
653
- std::fs::create_dir_all(&shim_root).expect("create tmux shim dir");
654
- let shim_log = shim_root.join("argv.log");
655
- let shim_source = shim_root.join("tmux.c");
656
- let shim = shim_root.join("tmux");
657
- std::fs::write(
658
- &shim_source,
659
- r#"#include <stdio.h>
660
- #include <stdlib.h>
661
- #include <string.h>
662
- #include <unistd.h>
663
-
664
- static int has_arg(int argc, char **argv, const char *needle) {
665
- for (int i = 1; i < argc; i++) {
666
- if (strcmp(argv[i], needle) == 0) return 1;
667
- }
668
- return 0;
669
- }
670
-
671
- int main(int argc, char **argv) {
672
- if (getenv("TEAM_AGENT_PI_TEST_HANG") != NULL) for (;;) pause();
673
- const char *log_path = getenv("TEAM_AGENT_PI_TMUX_LOG");
674
- const char *socket = getenv("TEAM_AGENT_PI_OWNING_SOCKET");
675
- const char *session = getenv("TEAM_AGENT_PI_OWNING_SESSION");
676
- const char *workspace = getenv("TEAM_AGENT_PI_RUN_WORKSPACE");
677
- FILE *log = log_path == NULL ? NULL : fopen(log_path, "a");
678
- if (log == NULL || socket == NULL || session == NULL || workspace == NULL) return 92;
679
- for (int i = 1; i < argc; i++) fprintf(log, "%s%s", i == 1 ? "" : " ", argv[i]);
680
- fputc('\n', log);
681
- fclose(log);
682
-
683
- int owns_socket = 0;
684
- for (int i = 1; i + 1 < argc; i++) {
685
- if (strcmp(argv[i], "-S") == 0 && strcmp(argv[i + 1], socket) == 0) owns_socket = 1;
686
- }
687
- if (!owns_socket) return 91;
688
- if (has_arg(argc, argv, "new-window") || has_arg(argc, argv, "display-message")) {
689
- puts("%9101");
690
- } else if (has_arg(argc, argv, "list-panes")) {
691
- printf("%%9101__TA_FIELD__%s__TA_FIELD__0__TA_FIELD__mate__TA_FIELD__0__TA_FIELD__/dev/ttys099__TA_FIELD__sh__TA_FIELD__1__TA_FIELD__%s__TA_FIELD__1__TA_FIELD__0__TA_FIELD__9101__TA_FIELD__\n", session, workspace);
692
- }
693
- return 0;
694
- }
695
- "#,
696
- )
697
- .expect("write native tmux shim source");
698
- let compile = run_file_h_command_with_deadline(
699
- std::process::Command::new("cc")
700
- .args(["-O0", "-o"])
701
- .arg(&shim)
702
- .arg(&shim_source),
703
- "File-H native fake compile",
704
- FILE_H_NATIVE_COMPILE_DEADLINE,
705
- &shim_root.join("compile.stdout"),
706
- &shim_root.join("compile.stderr"),
707
- )
708
- .unwrap_or_else(|timeout| panic!("{timeout:?}"));
709
- assert!(
710
- compile.status.success(),
711
- "native tmux shim compile failed: {}",
712
- String::from_utf8_lossy(&compile.stderr)
713
- );
714
- let tooth_started = std::time::Instant::now();
715
- let tooth = run_file_h_command_with_deadline(
716
- std::process::Command::new(&shim).env("TEAM_AGENT_PI_TEST_HANG", "1"),
717
- "File-H native fake readiness timeout tooth",
718
- FILE_H_NATIVE_READINESS_DEADLINE,
719
- &shim_root.join("readiness-tooth.stdout"),
720
- &shim_root.join("readiness-tooth.stderr"),
721
- )
722
- .expect_err("non-exiting native fake must hit the readiness deadline");
723
- assert_eq!(tooth.stage, "File-H native fake readiness timeout tooth");
724
- assert_eq!(tooth.deadline, FILE_H_NATIVE_READINESS_DEADLINE);
725
- assert!(!tooth.reaped_status.success(), "{tooth:?}");
726
- assert!(
727
- !crate::platform::process::pid_is_alive(tooth.pid),
728
- "readiness timeout must leave its exact child dead and reaped: {tooth:?}"
729
- );
730
- assert!(
731
- tooth_started.elapsed()
732
- <= FILE_H_NATIVE_READINESS_DEADLINE + std::time::Duration::from_secs(1),
733
- "readiness timeout tooth exceeded its deadline plus reap allowance: {tooth:?}"
734
- );
735
-
736
- let preflight = run_file_h_command_with_deadline(
737
- std::process::Command::new(&shim)
738
- .args(["-S", worker_socket, "has-session"])
739
- .env("TEAM_AGENT_PI_TMUX_LOG", shim_root.join("preflight.log"))
740
- .env("TEAM_AGENT_PI_OWNING_SOCKET", worker_socket)
741
- .env("TEAM_AGENT_PI_OWNING_SESSION", live_session)
742
- .env("TEAM_AGENT_PI_RUN_WORKSPACE", &live),
743
- "File-H native fake has-session preflight",
744
- FILE_H_NATIVE_READINESS_DEADLINE,
745
- &shim_root.join("preflight.stdout"),
746
- &shim_root.join("preflight.stderr"),
747
- )
748
- .unwrap_or_else(|timeout| panic!("{timeout:?}"));
749
- assert!(
750
- preflight.status.success(),
751
- "native tmux shim preflight failed: {}",
752
- String::from_utf8_lossy(&preflight.stderr)
753
- );
754
- let previous_path = std::env::var_os("PATH").unwrap_or_default();
755
- let joined_path = std::env::join_paths(
756
- std::iter::once(shim_root.clone()).chain(std::env::split_paths(&previous_path)),
757
- )
758
- .expect("join native shim PATH");
759
- let _path = EnvVarGuard::set("PATH", joined_path);
760
- let _log = EnvVarGuard::set("TEAM_AGENT_PI_TMUX_LOG", &shim_log);
761
- let _socket = EnvVarGuard::set("TEAM_AGENT_PI_OWNING_SOCKET", worker_socket);
762
- let _session = EnvVarGuard::set("TEAM_AGENT_PI_OWNING_SESSION", live_session);
763
- let _workspace = EnvVarGuard::set("TEAM_AGENT_PI_RUN_WORKSPACE", &live);
764
-
765
- let tools = TeamOrchestratorTools::with_identity(
766
- &live,
767
- Some(AgentId::new("leader")),
768
- Some(TeamKey::new("current")),
769
- );
770
- let result = tools
771
- .add_agent("mate", "roles/mate.md")
772
- .expect("public MCP add must use the persisted worker endpoint");
773
- assert_eq!(
774
- result.fields.get("ok").and_then(serde_json::Value::as_bool),
775
- Some(true)
776
- );
777
- let tmux_argv = std::fs::read_to_string(&shim_log).expect("read tmux shim log");
778
- assert!(
779
- tmux_argv.contains(&format!("-S {worker_socket}")),
780
- "public add must address the persisted worker socket: {tmux_argv}"
781
- );
782
- assert!(
783
- tmux_argv.contains(&format!(
784
- "new-window -d -P -F #{{pane_id}} -t {live_session} -n mate"
785
- )),
786
- "public add must reuse the owner session with new-window: {tmux_argv}"
787
- );
788
- assert!(
789
- tmux_argv.contains(&live.to_string_lossy().to_string()),
790
- "spawn command must retain the MCP scratch as cwd: {tmux_argv}"
791
- );
792
- assert!(
793
- !tmux_argv.contains("new-session"),
794
- "public add must not start a same-named session on another server: {tmux_argv}"
795
- );
796
- let parent_socket = crate::tmux_backend::socket_name_for_workspace(
797
- live.parent().expect("live scratch parent"),
798
- );
799
- assert!(
800
- !tmux_argv.contains(&parent_socket),
801
- "public add must not derive transport from the scratch parent: {tmux_argv}"
802
- );
803
-
804
- let added_state = crate::state::persist::load_runtime_state(&live)
805
- .expect("load public add state for send/status");
806
- assert_eq!(
807
- added_state
808
- .pointer("/teams/current/session_name")
809
- .and_then(serde_json::Value::as_str),
810
- Some(live_session),
811
- "dynamic add must persist the selected receiver session for shared send/status routing"
812
- );
813
- assert!(
814
- added_state
815
- .pointer("/teams/sibling/session_name")
816
- .is_some_and(serde_json::Value::is_null),
817
- "selected-team routing must not borrow or rewrite a sibling session: {added_state}"
818
- );
819
- assert_eq!(
820
- added_state
821
- .pointer("/teams/sibling/leader_receiver/session_name")
822
- .and_then(serde_json::Value::as_str),
823
- Some("team-agent-leader-pi-sibling")
824
- );
825
-
826
- let mut old_state = added_state.clone();
827
- old_state["teams"]["current"]["session_name"] = serde_json::Value::Null;
828
- crate::state::persist::save_runtime_state(&live, &old_state)
829
- .expect("seed controlled old null-session view");
830
- let blocked = tools
831
- .send_message(
832
- &crate::messaging::MessageTarget::Single("mate".to_string()),
833
- "controlled old session routing probe",
834
- None,
835
- None,
836
- None,
837
- )
838
- .expect("old public send must return a structured refusal")
839
- .to_value();
840
- assert_eq!(blocked.get("ok"), Some(&json!(false)), "{blocked}");
841
- assert_eq!(
842
- blocked.get("reason"),
843
- Some(&json!("tmux_target_missing")),
844
- "the old null-session view must fail at real send target resolution: {blocked}"
845
- );
846
- let old_status = tools
847
- .get_team_status()
848
- .expect("old public status must return an honest dead view");
849
- assert_eq!(
850
- old_status
851
- .fields
852
- .get("agents")
853
- .and_then(|agents| agents.get("mate"))
854
- .and_then(|mate| mate.get("stale_reason"))
855
- .and_then(serde_json::Value::as_str),
856
- Some("pane_dead"),
857
- "the old null-session view must fail at real status liveness resolution: {:?}",
858
- old_status.fields
859
- );
860
- crate::state::persist::save_runtime_state(&live, &added_state)
861
- .expect("restore corrected selected-team session");
862
-
863
- let sent = tools
864
- .send_message(
865
- &crate::messaging::MessageTarget::Single("mate".to_string()),
866
- "session routing probe",
867
- None,
868
- None,
869
- None,
870
- )
871
- .expect("public send must resolve the added mate")
872
- .to_value();
873
- assert_eq!(sent.get("status"), Some(&json!("accepted")), "{sent}");
874
- assert!(
875
- sent.get("message_id")
876
- .and_then(serde_json::Value::as_str)
877
- .is_some_and(|message_id| message_id.starts_with("msg_")),
878
- "blocked target resolution must not be reported as a successful send: {sent}"
879
- );
880
-
881
- let status = tools
882
- .get_team_status()
883
- .expect("public status must resolve the selected owner session");
884
- assert_eq!(
885
- status
886
- .fields
887
- .get("session_name")
888
- .and_then(serde_json::Value::as_str),
889
- Some(live_session),
890
- "status must report the selected receiver session"
891
- );
892
- assert_ne!(
893
- status
894
- .fields
895
- .get("agents")
896
- .and_then(|agents| agents.get("mate"))
897
- .and_then(|mate| mate.get("stale_reason"))
898
- .and_then(serde_json::Value::as_str),
899
- Some("pane_dead"),
900
- "the live owner-session mate must not be reported pane_dead: {:?}",
901
- status.fields
902
- );
903
- }
904
-
905
512
  let shared_source = include_str!("../launch/pi_mcp.rs");
906
513
  let leader_source = include_str!("../../leader/start.rs");
907
514
  let teammate_source = include_str!("../launch/spawn.rs");
@@ -372,7 +372,7 @@ fn pi_leader_wrapper_materializes_from_empty_unicode_wsl_workspace() {
372
372
  team_id: "current",
373
373
  agent_id: "leader",
374
374
  workspace: &workspace,
375
- include_tools: &["assign_task", "send_message"],
375
+ include_tools: &["send_message", "report_result", "get_team_status"],
376
376
  })
377
377
  .expect("materialize Pi leader wrapper from an empty Unicode workspace");
378
378
 
@@ -437,7 +437,7 @@ fn pi_wrapper_publish_needs_no_final_path_metadata_lookup() {
437
437
  team_id: "current",
438
438
  agent_id: "leader",
439
439
  workspace: &workspace,
440
- include_tools: &["assign_task", "send_message"],
440
+ include_tools: &["send_message", "report_result", "get_team_status"],
441
441
  },
442
442
  |temp, destination| {
443
443
  std::fs::rename(temp, destination)?;
@@ -30,6 +30,9 @@ All communication must go through Team Agent MCP tools.
30
30
  - Teammate: {send_message}(to='<agent_id>', content='...')
31
31
  - Broadcast: {send_message}(to='*', content='...')
32
32
  - Complete: {report_result}(summary='...') — call exactly once
33
+ - Optional roster/status: {get_team_status} — use only when useful; it is not a communication or completion prerequisite.
34
+
35
+ Only these three Team Agent MCP tools are available. Lifecycle management remains on the separately authorized operator CLI.
33
36
 
34
37
  ## Rules:
35
38
 
@@ -55,7 +58,7 @@ pub(crate) struct WorkerCommandAgent {
55
58
  system_prompt_file: Option<String>,
56
59
  output_contract_format: Option<String>,
57
60
  communication_mode: CommunicationMode,
58
- /// 0.5.66 bypass 单源:agent-level `dangerously_skip_permissions`(必填 bool)。
61
+ /// Missing `dangerously_skip_permissions` defaults to true; an explicit false remains respected.
59
62
  dangerously_skip_permissions: bool,
60
63
  }
61
64
 
@@ -107,9 +110,9 @@ impl WorkerCommandAgent {
107
110
  .get("communication_mode")
108
111
  .and_then(crate::model::yaml::Value::as_str),
109
112
  )?,
110
- dangerously_skip_permissions: matches!(
113
+ dangerously_skip_permissions: !matches!(
111
114
  agent.get("dangerously_skip_permissions"),
112
- Some(crate::model::yaml::Value::Bool(true))
115
+ Some(crate::model::yaml::Value::Bool(false))
113
116
  ),
114
117
  })
115
118
  }
@@ -164,7 +167,7 @@ impl WorkerCommandAgent {
164
167
  dangerously_skip_permissions: agent
165
168
  .get("dangerously_skip_permissions")
166
169
  .and_then(serde_json::Value::as_bool)
167
- .unwrap_or(false),
170
+ .unwrap_or(true),
168
171
  })
169
172
  }
170
173
  }
@@ -182,15 +185,20 @@ pub(crate) fn compile_worker_system_prompt(
182
185
  // C-1 cr verdict / B2 灵魂件 — identity 必须 FIRST(MUST-4 行为层守:空白上下文问
183
186
  // "你是谁"必须先答 Team Agent worker 身份)。runtime contract 跟后。
184
187
  let send_message = mcp_tool_name(agent.provider, "team_orchestrator", "send_message");
188
+ let get_team_status = if agent.provider == Provider::Pi {
189
+ r#"mcp({tool:"team_orchestrator_get_team_status", args:{}})"#.to_string()
190
+ } else {
191
+ format!("{}()", mcp_tool_name(agent.provider, "team_orchestrator", "get_team_status"))
192
+ };
185
193
  let report_result = if agent.provider == Provider::Pi {
186
194
  r#"mcp({tool:"team_orchestrator_report_result", args:{summary:"..."}})"#.to_string()
187
195
  } else {
188
196
  mcp_tool_name(agent.provider, "team_orchestrator", "report_result")
189
197
  };
190
198
  let runtime_contract = if agent.provider == Provider::Pi {
191
- pi_runtime_contract_section()
199
+ pi_runtime_contract_section(&get_team_status)
192
200
  } else {
193
- runtime_contract_section(&send_message, &report_result)
201
+ runtime_contract_section(&send_message, &report_result, &get_team_status)
194
202
  };
195
203
  let communication_contract = if agent.provider == Provider::Pi {
196
204
  pi_communication_contract(agent.communication_mode)
@@ -241,13 +249,18 @@ fn mcp_tool_name(provider: Provider, server: &str, tool: &str) -> String {
241
249
  }
242
250
  }
243
251
 
244
- fn runtime_contract_section(send_message: &str, report_result: &str) -> String {
252
+ fn runtime_contract_section(
253
+ send_message: &str,
254
+ report_result: &str,
255
+ get_team_status: &str,
256
+ ) -> String {
245
257
  RUNTIME_CONTRACT_SECTION
246
258
  .replace("{send_message}", send_message)
247
259
  .replace("{report_result}", report_result)
260
+ .replace("{get_team_status}", get_team_status)
248
261
  }
249
262
 
250
- fn pi_runtime_contract_section() -> String {
263
+ fn pi_runtime_contract_section(get_team_status: &str) -> String {
251
264
  RUNTIME_CONTRACT_SECTION
252
265
  .replace(
253
266
  "{send_message}(to='<agent_id>', content='...')",
@@ -261,6 +274,7 @@ fn pi_runtime_contract_section() -> String {
261
274
  "{report_result}(summary='...')",
262
275
  r#"mcp({tool:"team_orchestrator_report_result", args:{summary:"..."}})"#,
263
276
  )
277
+ .replace("{get_team_status}", get_team_status)
264
278
  }
265
279
 
266
280
  fn pi_communication_contract(mode: CommunicationMode) -> String {
@@ -40,22 +40,6 @@ pub fn is_worker_recipient(to: &MessageTarget) -> bool {
40
40
  }
41
41
  }
42
42
 
43
- /// `_merge_tasks_by_id` (`tools.py:30-49`): dedupe a task list keyed by `id`,
44
- /// `prefer` entries winning on duplicates (so an earlier `done` is not regressed).
45
- pub fn merge_tasks_by_id(prefer: &[Value], fallback: &[Value]) -> Vec<Value> {
46
- let mut seen = std::collections::BTreeSet::new();
47
- let mut out = Vec::new();
48
- for item in prefer.iter().chain(fallback.iter()) {
49
- let Some(id) = item.get("id").and_then(Value::as_str) else {
50
- continue;
51
- };
52
- if seen.insert(id.to_string()) && item.is_object() {
53
- out.push(item.clone());
54
- }
55
- }
56
- out
57
- }
58
-
59
43
  pub(crate) fn tool_error_reason_wire(reason: ToolErrorReason) -> &'static str {
60
44
  match reason {
61
45
  ToolErrorReason::UnknownTool => "unknown_tool",
@@ -1,11 +1,6 @@
1
1
  //!
2
- //! MCP lifecycle tool facades.
3
- //!
4
- //! S0 keeps the old placeholder behavior behind stable module boundaries so
5
- //! follow-up lanes can implement real lifecycle logic without touching tools.rs.
2
+ //! MCP status query facade.
6
3
 
7
- mod agent_ops;
8
4
  mod state_status;
9
5
 
10
- pub(crate) use agent_ops::{clone_agent, fork_agent, reset_agent, stop_agent};
11
- pub(crate) use state_status::{get_team_status, update_state};
6
+ pub(crate) use state_status::get_team_status;