@team-agent/installer 0.5.49 → 0.5.50
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 +1 -1
- package/crates/team-agent/src/cli/emit.rs +89 -38
- package/crates/team-agent/src/cli/mod.rs +1 -1
- package/crates/team-agent/src/cli/named_address.rs +71 -5
- package/crates/team-agent/src/cli/send.rs +438 -518
- package/crates/team-agent/src/cli/spec.rs +1 -1
- package/crates/team-agent/src/cli/tests/leader_watch.rs +1 -1
- package/crates/team-agent/src/cli/tests/named_address.rs +9 -7
- package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -3
- package/crates/team-agent/src/cli/tests/status_send.rs +17 -33
- package/crates/team-agent/src/cli/types.rs +4 -8
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +1 -1
- package/crates/team-agent/src/mcp_server/mod.rs +1 -1
- package/crates/team-agent/src/mcp_server/tests/send.rs +16 -5
- package/crates/team-agent/src/mcp_server/tests/wire.rs +6 -0
- package/crates/team-agent/src/mcp_server/tools.rs +12 -10
- package/crates/team-agent/src/mcp_server/wire.rs +2 -18
- package/crates/team-agent/src/messaging/mod.rs +1 -0
- package/crates/team-agent/src/messaging/send.rs +34 -11
- package/package.json +4 -4
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
|
@@ -721,7 +721,7 @@ fn run_fake_e2e(workspace: &Path) -> Result<Value, CliError> {
|
|
|
721
721
|
&SendOptions {
|
|
722
722
|
task_id: Some(TaskId::new("task_impl")),
|
|
723
723
|
route_task_id: false,
|
|
724
|
-
sender:
|
|
724
|
+
sender: messaging::TrustedSender::leader(),
|
|
725
725
|
requires_ack: true,
|
|
726
726
|
..SendOptions::default()
|
|
727
727
|
},
|
|
@@ -315,21 +315,9 @@ fn command_help(command: Option<&str>) -> String {
|
|
|
315
315
|
Some("start") => compat_hidden_help("start", "usage: team-agent start [TEAMDIR] [--yes] [--fresh] [--json]"),
|
|
316
316
|
Some("compile") => "usage: team-agent compile --team TEAM [--out FILE] [--json]".to_string(),
|
|
317
317
|
Some("send") => concat!(
|
|
318
|
-
"usage: team-agent send
|
|
319
|
-
"[--workspace WORKSPACE] [--team TEAM] [--
|
|
320
|
-
"
|
|
321
|
-
"[--watch-result] [--requires-ack|--no-ack] [--no-wait] ",
|
|
322
|
-
"[--timeout SECONDS] [--confirm-human] [--message-id ID] [--json]\n\n",
|
|
323
|
-
"TARGET is a short id scoped by --team; MCP `to` is a short id scoped ",
|
|
324
|
-
"by the worker's owner team.\n",
|
|
325
|
-
"--to-name accepts: --to-name AGENT, --to-name TEAM/AGENT, or ",
|
|
326
|
-
"--to-name WORKSPACE::TEAM/AGENT.\n",
|
|
327
|
-
"--team scopes only a bare --to-name AGENT; qualified forms keep the ",
|
|
328
|
-
"scope in the address.\n",
|
|
329
|
-
"TEAM/AGENT and WORKSPACE::TEAM/AGENT are not valid positional TARGET ",
|
|
330
|
-
"or MCP `to` values.\n\n",
|
|
331
|
-
"MVP: name-based cross-workspace addressing assumes trusted local ",
|
|
332
|
-
"caller; no auth gate."
|
|
318
|
+
"usage: team-agent send TO MESSAGE... ",
|
|
319
|
+
"[--workspace WORKSPACE] [--team TEAM] [--json]\n\n",
|
|
320
|
+
"TO is a logical recipient; send returns after the message is persisted."
|
|
333
321
|
)
|
|
334
322
|
.to_string(),
|
|
335
323
|
Some("allow-peer-talk") => "usage: team-agent allow-peer-talk A B [--workspace WORKSPACE] [--json]".to_string(),
|
|
@@ -732,7 +720,6 @@ struct ParsedArgs {
|
|
|
732
720
|
team_id: Option<String>,
|
|
733
721
|
targets: Option<String>,
|
|
734
722
|
task: Option<String>,
|
|
735
|
-
sender: Option<String>,
|
|
736
723
|
watch_result: bool,
|
|
737
724
|
requires_ack: bool,
|
|
738
725
|
no_ack: bool,
|
|
@@ -816,7 +803,6 @@ fn parse_args(args: &[String]) -> ParsedArgs {
|
|
|
816
803
|
"--targets" | "--target" | "--to" => parsed.targets = next_arg(args, &mut i),
|
|
817
804
|
"--task" => parsed.task = next_arg(args, &mut i),
|
|
818
805
|
"--task-id" => parsed.task_id = next_arg(args, &mut i),
|
|
819
|
-
"--sender" => parsed.sender = next_arg(args, &mut i),
|
|
820
806
|
"--agent-id" => parsed.agent_id = next_arg(args, &mut i),
|
|
821
807
|
"--watch-result" => parsed.watch_result = true,
|
|
822
808
|
"--requires-ack" => parsed.requires_ack = true,
|
|
@@ -1027,6 +1013,8 @@ fn resolve_cli_path(cwd: &Path, path: &Path) -> PathBuf {
|
|
|
1027
1013
|
}
|
|
1028
1014
|
|
|
1029
1015
|
fn send_args(args: &[String], cwd: &Path) -> Result<SendArgs, CliError> {
|
|
1016
|
+
validate_send_flags(args)?;
|
|
1017
|
+
warn_send_legacy_delivery_flags(args);
|
|
1030
1018
|
let parsed = parse_args(args);
|
|
1031
1019
|
let target = if parsed.targets.is_some()
|
|
1032
1020
|
|| parsed.pane.is_some()
|
|
@@ -1050,21 +1038,96 @@ fn send_args(args: &[String], cwd: &Path) -> Result<SendArgs, CliError> {
|
|
|
1050
1038
|
targets: parsed.targets,
|
|
1051
1039
|
workspace,
|
|
1052
1040
|
team: parsed.team,
|
|
1053
|
-
task:
|
|
1054
|
-
sender:
|
|
1055
|
-
no_ack:
|
|
1056
|
-
no_wait:
|
|
1057
|
-
watch_result:
|
|
1058
|
-
timeout:
|
|
1059
|
-
confirm_human:
|
|
1041
|
+
task: None,
|
|
1042
|
+
sender: trusted_cli_sender(),
|
|
1043
|
+
no_ack: false,
|
|
1044
|
+
no_wait: true,
|
|
1045
|
+
watch_result: false,
|
|
1046
|
+
timeout: 0.0,
|
|
1047
|
+
confirm_human: false,
|
|
1060
1048
|
json: parsed.json,
|
|
1061
|
-
message_id:
|
|
1049
|
+
message_id: None,
|
|
1062
1050
|
pane: parsed.pane.clone(),
|
|
1063
1051
|
to_name: parsed.to_name.clone(),
|
|
1064
1052
|
to_leader: parsed.to_leader.clone(),
|
|
1065
1053
|
})
|
|
1066
1054
|
}
|
|
1067
1055
|
|
|
1056
|
+
fn trusted_cli_sender() -> TrustedSender {
|
|
1057
|
+
std::env::var("TEAM_AGENT_ID")
|
|
1058
|
+
.ok()
|
|
1059
|
+
.map(|value| value.trim().to_string())
|
|
1060
|
+
.filter(|value| !value.is_empty())
|
|
1061
|
+
.map(crate::model::ids::AgentId::new)
|
|
1062
|
+
.map(TrustedSender::from_runtime_identity)
|
|
1063
|
+
.unwrap_or_else(TrustedSender::leader)
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
fn warn_send_legacy_delivery_flags(args: &[String]) {
|
|
1067
|
+
const FLAGS: &[&str] = &[
|
|
1068
|
+
"--task",
|
|
1069
|
+
"--watch-result",
|
|
1070
|
+
"--requires-ack",
|
|
1071
|
+
"--no-ack",
|
|
1072
|
+
"--no-wait",
|
|
1073
|
+
"--timeout",
|
|
1074
|
+
"--confirm-human",
|
|
1075
|
+
"--message-id",
|
|
1076
|
+
];
|
|
1077
|
+
let spec = command_spec("send");
|
|
1078
|
+
let sunset = spec
|
|
1079
|
+
.and_then(|spec| spec.sunset)
|
|
1080
|
+
.unwrap_or("next compatibility release");
|
|
1081
|
+
let action = spec
|
|
1082
|
+
.and_then(|spec| spec.action)
|
|
1083
|
+
.unwrap_or("use positional logical TO and the returned message id");
|
|
1084
|
+
for flag in FLAGS {
|
|
1085
|
+
if args
|
|
1086
|
+
.iter()
|
|
1087
|
+
.any(|arg| arg == flag || arg.starts_with(&format!("{flag}=")))
|
|
1088
|
+
{
|
|
1089
|
+
eprintln!("warning: {flag} is deprecated; sunset: {sunset}; action: {action}");
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
fn validate_send_flags(args: &[String]) -> Result<(), CliError> {
|
|
1095
|
+
const ALLOWED: &[&str] = &[
|
|
1096
|
+
"--workspace",
|
|
1097
|
+
"--team",
|
|
1098
|
+
"--targets",
|
|
1099
|
+
"--target",
|
|
1100
|
+
"--to",
|
|
1101
|
+
"--to-name",
|
|
1102
|
+
"--to-leader",
|
|
1103
|
+
"--pane",
|
|
1104
|
+
"--task",
|
|
1105
|
+
"--watch-result",
|
|
1106
|
+
"--requires-ack",
|
|
1107
|
+
"--no-ack",
|
|
1108
|
+
"--no-wait",
|
|
1109
|
+
"--timeout",
|
|
1110
|
+
"--confirm-human",
|
|
1111
|
+
"--message-id",
|
|
1112
|
+
"--json",
|
|
1113
|
+
"-h",
|
|
1114
|
+
"--help",
|
|
1115
|
+
];
|
|
1116
|
+
const ALLOWED_PREFIXES: &[&str] = &["--team=", "--pane=", "--to-name=", "--to-leader="];
|
|
1117
|
+
if let Some(flag) = args.iter().find(|arg| {
|
|
1118
|
+
arg.starts_with('-')
|
|
1119
|
+
&& !ALLOWED.contains(&arg.as_str())
|
|
1120
|
+
&& !ALLOWED_PREFIXES
|
|
1121
|
+
.iter()
|
|
1122
|
+
.any(|prefix| arg.starts_with(prefix))
|
|
1123
|
+
}) {
|
|
1124
|
+
return Err(CliError::Usage(format!(
|
|
1125
|
+
"unrecognized argument for `send`: {flag}"
|
|
1126
|
+
)));
|
|
1127
|
+
}
|
|
1128
|
+
Ok(())
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1068
1131
|
/// Stage 4 of identity-boundary unified plan (architect direction
|
|
1069
1132
|
/// 2026-06-24, .team/artifacts/identity-boundary-unified-plan.md §2 Stage
|
|
1070
1133
|
/// 4): destructive command ambiguity gate. When the workspace has 2+
|
|
@@ -1760,19 +1823,7 @@ mod tests {
|
|
|
1760
1823
|
"quick-start",
|
|
1761
1824
|
&["--workspace", "--team-id", "--yes", "--json"][..],
|
|
1762
1825
|
),
|
|
1763
|
-
(
|
|
1764
|
-
"send",
|
|
1765
|
-
&[
|
|
1766
|
-
"--workspace",
|
|
1767
|
-
"--team",
|
|
1768
|
-
"--targets",
|
|
1769
|
-
"--to-name",
|
|
1770
|
-
"--pane",
|
|
1771
|
-
"--watch-result",
|
|
1772
|
-
"--timeout",
|
|
1773
|
-
"--json",
|
|
1774
|
-
][..],
|
|
1775
|
-
),
|
|
1826
|
+
("send", &["--workspace", "--team", "--json"][..]),
|
|
1776
1827
|
(
|
|
1777
1828
|
"status",
|
|
1778
1829
|
&["--workspace", "--team", "--summary", "--json", "--detail"][..],
|
|
@@ -44,7 +44,7 @@ use serde_json::{json, Map, Value};
|
|
|
44
44
|
use thiserror::Error;
|
|
45
45
|
|
|
46
46
|
// REUSE in-tree(只 import,不 redefine):
|
|
47
|
-
use crate::messaging::{self, AlertType, MessageTarget, SendOptions};
|
|
47
|
+
use crate::messaging::{self, AlertType, MessageTarget, SendOptions, TrustedSender};
|
|
48
48
|
use crate::model::ids::{TaskId, TeamKey};
|
|
49
49
|
|
|
50
50
|
pub(crate) const COMMS_BOUNDARY_TEXT: &str = "validates live pane binding consistency and zero-token comms contracts. Does NOT perform live runtime message round-trip. (zero token, zero pollution)";
|
|
@@ -680,12 +680,38 @@ fn resolve_worker(
|
|
|
680
680
|
.with_scoped_suggestions_for_agent(team_entry, team, agent, parsed));
|
|
681
681
|
}
|
|
682
682
|
};
|
|
683
|
-
let session = string_field(team_entry, "session_name")
|
|
684
|
-
|
|
683
|
+
let Some(session) = string_field(team_entry, "session_name") else {
|
|
684
|
+
return Ok(resolved_worker_without_live(
|
|
685
|
+
sender_workspace,
|
|
686
|
+
target_workspace,
|
|
687
|
+
team,
|
|
688
|
+
agent,
|
|
689
|
+
parsed,
|
|
690
|
+
agent_entry,
|
|
691
|
+
None,
|
|
692
|
+
agent,
|
|
693
|
+
transport.tmux_endpoint(),
|
|
694
|
+
));
|
|
695
|
+
};
|
|
685
696
|
let window = string_field(agent_entry, "window")
|
|
686
697
|
.or_else(|| string_field(agent_entry, "window_name"))
|
|
687
698
|
.unwrap_or(agent);
|
|
688
|
-
let targets = list_targets(transport)
|
|
699
|
+
let targets = match list_targets(transport) {
|
|
700
|
+
Ok(targets) => targets,
|
|
701
|
+
Err(_) => {
|
|
702
|
+
return Ok(resolved_worker_without_live(
|
|
703
|
+
sender_workspace,
|
|
704
|
+
target_workspace,
|
|
705
|
+
team,
|
|
706
|
+
agent,
|
|
707
|
+
parsed,
|
|
708
|
+
agent_entry,
|
|
709
|
+
Some(session),
|
|
710
|
+
window,
|
|
711
|
+
transport.tmux_endpoint(),
|
|
712
|
+
));
|
|
713
|
+
}
|
|
714
|
+
};
|
|
689
715
|
let matches = matching_session_window(&targets, session, window);
|
|
690
716
|
match matches.len() {
|
|
691
717
|
1 => {
|
|
@@ -720,10 +746,14 @@ fn resolve_worker(
|
|
|
720
746
|
warning,
|
|
721
747
|
})
|
|
722
748
|
}
|
|
723
|
-
0 =>
|
|
749
|
+
0 => Ok(resolved_worker_without_live(
|
|
750
|
+
sender_workspace,
|
|
751
|
+
target_workspace,
|
|
724
752
|
team,
|
|
725
753
|
agent,
|
|
726
|
-
|
|
754
|
+
parsed,
|
|
755
|
+
agent_entry,
|
|
756
|
+
Some(session),
|
|
727
757
|
window,
|
|
728
758
|
transport.tmux_endpoint(),
|
|
729
759
|
)),
|
|
@@ -747,6 +777,42 @@ fn resolve_worker(
|
|
|
747
777
|
}
|
|
748
778
|
}
|
|
749
779
|
|
|
780
|
+
#[allow(clippy::too_many_arguments)]
|
|
781
|
+
fn resolved_worker_without_live(
|
|
782
|
+
sender_workspace: &Path,
|
|
783
|
+
target_workspace: &Path,
|
|
784
|
+
team: &str,
|
|
785
|
+
agent: &str,
|
|
786
|
+
parsed: &ParsedNamedAddress,
|
|
787
|
+
agent_entry: &Value,
|
|
788
|
+
session: Option<&str>,
|
|
789
|
+
window: &str,
|
|
790
|
+
tmux_endpoint: Option<String>,
|
|
791
|
+
) -> ResolvedNamedAddress {
|
|
792
|
+
let state_pane_id = string_field(agent_entry, "pane_id").map(str::to_string);
|
|
793
|
+
ResolvedNamedAddress {
|
|
794
|
+
raw_name: parsed.display_name(),
|
|
795
|
+
target_kind: NamedTargetKind::Worker,
|
|
796
|
+
sender_workspace: sender_workspace.to_path_buf(),
|
|
797
|
+
target_workspace: target_workspace.to_path_buf(),
|
|
798
|
+
team_key: Some(team.to_string()),
|
|
799
|
+
agent_id: Some(agent.to_string()),
|
|
800
|
+
pane_id: state_pane_id.clone().unwrap_or_default(),
|
|
801
|
+
session_name: session.map(str::to_string),
|
|
802
|
+
window_name: Some(window.to_string()),
|
|
803
|
+
tmux_endpoint,
|
|
804
|
+
transport_kind: Some("direct_tmux".to_string()),
|
|
805
|
+
app_server: None,
|
|
806
|
+
state_pane_id,
|
|
807
|
+
state_pane_stale: true,
|
|
808
|
+
agent_status: string_field(agent_entry, "status").map(str::to_string),
|
|
809
|
+
warning: Some(
|
|
810
|
+
"agent has no live pane; message will be persisted for standard delivery recovery"
|
|
811
|
+
.to_string(),
|
|
812
|
+
),
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
750
816
|
fn resolve_bare_agent(
|
|
751
817
|
sender_workspace: &Path,
|
|
752
818
|
target_workspace: &Path,
|