@team-agent/installer 0.5.49 → 0.5.51
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 +6 -4
- package/crates/team-agent/src/cli/emit.rs +91 -39
- package/crates/team-agent/src/cli/mod.rs +33 -15
- package/crates/team-agent/src/cli/named_address.rs +82 -53
- package/crates/team-agent/src/cli/send/coordinator.rs +163 -0
- package/crates/team-agent/src/cli/send/mailbox.rs +99 -0
- package/crates/team-agent/src/cli/send/persist.rs +154 -0
- package/crates/team-agent/src/cli/send/presentation.rs +333 -0
- package/crates/team-agent/src/cli/send/resolve.rs +361 -0
- package/crates/team-agent/src/cli/send.rs +103 -1308
- package/crates/team-agent/src/cli/spec.rs +2 -2
- package/crates/team-agent/src/cli/status_port/agents.rs +358 -0
- package/crates/team-agent/src/cli/status_port/approvals.rs +79 -0
- package/crates/team-agent/src/cli/status_port/compact.rs +207 -0
- package/crates/team-agent/src/cli/status_port/format.rs +145 -0
- package/crates/team-agent/src/cli/status_port/inbox.rs +36 -0
- package/crates/team-agent/src/cli/status_port/runtime.rs +195 -0
- package/crates/team-agent/src/cli/status_port/snapshot.rs +181 -0
- package/crates/team-agent/src/cli/status_port/store.rs +412 -0
- package/crates/team-agent/src/cli/status_port/tests.rs +54 -0
- package/crates/team-agent/src/cli/status_port.rs +47 -1548
- 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 +2 -3
- package/crates/team-agent/src/cli/tests/status_send.rs +17 -33
- package/crates/team-agent/src/cli/types.rs +5 -8
- package/crates/team-agent/src/coordinator/conpty_shim.rs +34 -30
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +135 -13
- package/crates/team-agent/src/coordinator/tick.rs +37 -0
- package/crates/team-agent/src/db/agent_health_capture.rs +18 -13
- package/crates/team-agent/src/db/message_store.rs +154 -44
- package/crates/team-agent/src/event_log.rs +73 -0
- package/crates/team-agent/src/leader/start.rs +28 -4
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +424 -0
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +297 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +160 -0
- package/crates/team-agent/src/lifecycle/launch/approval.rs +134 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +492 -0
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +297 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +372 -0
- package/crates/team-agent/src/lifecycle/launch/layout.rs +313 -0
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +478 -0
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +201 -0
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +66 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +477 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +278 -0
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +123 -0
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +377 -0
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +434 -0
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +499 -0
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +438 -0
- package/crates/team-agent/src/lifecycle/launch.rs +119 -5351
- package/crates/team-agent/src/lifecycle/restart/agent.rs +44 -26
- package/crates/team-agent/src/lifecycle/restart/common.rs +53 -27
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +67 -26
- package/crates/team-agent/src/lifecycle/restart/remove.rs +435 -72
- package/crates/team-agent/src/lifecycle/restart.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +575 -17
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +55 -2
- package/crates/team-agent/src/lifecycle/tests/lifecycle_lock.rs +24 -1
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +1 -1
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +30 -7
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +8 -4
- package/crates/team-agent/src/mcp_server/mod.rs +2 -2
- package/crates/team-agent/src/mcp_server/tests/send.rs +22 -15
- package/crates/team-agent/src/mcp_server/tests/wire.rs +6 -0
- package/crates/team-agent/src/mcp_server/tools.rs +26 -15
- package/crates/team-agent/src/mcp_server/wire.rs +2 -18
- package/crates/team-agent/src/messaging/activity.rs +4 -2
- package/crates/team-agent/src/messaging/address.rs +86 -0
- package/crates/team-agent/src/messaging/delivery.rs +165 -39
- package/crates/team-agent/src/messaging/helpers.rs +17 -13
- package/crates/team-agent/src/messaging/leader_receiver.rs +60 -35
- package/crates/team-agent/src/messaging/mod.rs +11 -2
- package/crates/team-agent/src/messaging/persist.rs +309 -0
- package/crates/team-agent/src/messaging/results.rs +16 -24
- package/crates/team-agent/src/messaging/scheduler.rs +4 -2
- package/crates/team-agent/src/messaging/selftest.rs +19 -12
- package/crates/team-agent/src/messaging/send.rs +133 -58
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +305 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +38 -17
- package/crates/team-agent/src/messaging/watchers.rs +13 -3
- package/crates/team-agent/src/redaction.rs +72 -2
- package/crates/team-agent/src/state/persist.rs +2 -1
- package/crates/team-agent/src/state/repository/tests.rs +47 -0
- package/crates/team-agent/src/state/repository.rs +59 -16
- 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
|
},
|
|
@@ -841,8 +841,8 @@ fn provider_command(provider: &str) -> &str {
|
|
|
841
841
|
}
|
|
842
842
|
|
|
843
843
|
fn seed_fake_e2e_state(workspace: &Path) -> Result<(), CliError> {
|
|
844
|
-
crate::state::
|
|
845
|
-
|
|
844
|
+
crate::state::repository::StateRepository::new(workspace).save(
|
|
845
|
+
crate::state::repository::StateWriteIntent::FakeE2eSeed,
|
|
846
846
|
&json!({
|
|
847
847
|
"leader": {"id": "leader"},
|
|
848
848
|
"session_name": "team-agent-fake-e2e",
|
|
@@ -876,7 +876,8 @@ fn fake_shutdown(workspace: &Path) -> Result<Value, CliError> {
|
|
|
876
876
|
}
|
|
877
877
|
}
|
|
878
878
|
}
|
|
879
|
-
crate::state::
|
|
879
|
+
crate::state::repository::StateRepository::new(workspace)
|
|
880
|
+
.save(crate::state::repository::StateWriteIntent::FakeE2eSeed, &state)?;
|
|
880
881
|
Ok(json!({
|
|
881
882
|
"ok": true,
|
|
882
883
|
"session_name": state.get("session_name").cloned().unwrap_or(Value::Null),
|
|
@@ -1353,6 +1354,7 @@ pub fn cmd_add_agent(args: &AddAgentArgs) -> Result<CmdResult, CliError> {
|
|
|
1353
1354
|
&args.role_file,
|
|
1354
1355
|
!args.no_display,
|
|
1355
1356
|
args.team.as_deref(),
|
|
1357
|
+
args.force,
|
|
1356
1358
|
)?,
|
|
1357
1359
|
args.json,
|
|
1358
1360
|
))
|
|
@@ -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(),
|
|
@@ -341,7 +329,7 @@ fn command_help(command: Option<&str>) -> String {
|
|
|
341
329
|
Some("reset-agent") => "usage: team-agent reset-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--discard-session] [--no-display] [--json]".to_string(),
|
|
342
330
|
Some("start-agent") => "usage: team-agent start-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--force] [--allow-fresh] [--no-display] [--json]".to_string(),
|
|
343
331
|
Some("stop-agent") => "usage: team-agent stop-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
|
|
344
|
-
Some("add-agent") => "usage: team-agent add-agent AGENT --role-file FILE [--workspace WORKSPACE] [--team TEAM] [--no-display] [--json]".to_string(),
|
|
332
|
+
Some("add-agent") => "usage: team-agent add-agent AGENT --role-file FILE [--force] [--workspace WORKSPACE] [--team TEAM] [--no-display] [--json]".to_string(),
|
|
345
333
|
Some("fork-agent") => "usage: team-agent fork-agent SOURCE_AGENT --as AGENT [--label LABEL] [--workspace WORKSPACE] [--team TEAM] [--no-display] [--json]".to_string(),
|
|
346
334
|
Some("remove-agent") => "usage: team-agent remove-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--from-spec] [--confirm] [--force] [--json]".to_string(),
|
|
347
335
|
// 0.5.26 (§7.6): removed from help; dispatch was never wired.
|
|
@@ -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+
|
|
@@ -1282,6 +1345,7 @@ fn add_agent_args(args: &[String], cwd: &Path) -> Result<AddAgentArgs, CliError>
|
|
|
1282
1345
|
role_file: parsed
|
|
1283
1346
|
.role_file
|
|
1284
1347
|
.ok_or_else(|| CliError::Usage("missing --role-file".to_string()))?,
|
|
1348
|
+
force: parsed.force,
|
|
1285
1349
|
no_display: parsed.no_display,
|
|
1286
1350
|
json: parsed.json,
|
|
1287
1351
|
})
|
|
@@ -1760,19 +1824,7 @@ mod tests {
|
|
|
1760
1824
|
"quick-start",
|
|
1761
1825
|
&["--workspace", "--team-id", "--yes", "--json"][..],
|
|
1762
1826
|
),
|
|
1763
|
-
(
|
|
1764
|
-
"send",
|
|
1765
|
-
&[
|
|
1766
|
-
"--workspace",
|
|
1767
|
-
"--team",
|
|
1768
|
-
"--targets",
|
|
1769
|
-
"--to-name",
|
|
1770
|
-
"--pane",
|
|
1771
|
-
"--watch-result",
|
|
1772
|
-
"--timeout",
|
|
1773
|
-
"--json",
|
|
1774
|
-
][..],
|
|
1775
|
-
),
|
|
1827
|
+
("send", &["--workspace", "--team", "--json"][..]),
|
|
1776
1828
|
(
|
|
1777
1829
|
"status",
|
|
1778
1830
|
&["--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)";
|
|
@@ -884,7 +884,13 @@ pub mod lifecycle_port {
|
|
|
884
884
|
if session_killed && !verification_degraded {
|
|
885
885
|
mark_active_team_shutdown(&mut state, team_shutdown_status);
|
|
886
886
|
}
|
|
887
|
-
crate::state::
|
|
887
|
+
crate::state::repository::StateRepository::new(&run_workspace).save(
|
|
888
|
+
crate::state::repository::StateWriteIntent::ShutdownTeam {
|
|
889
|
+
team_key: team,
|
|
890
|
+
clean: session_killed && !verification_degraded,
|
|
891
|
+
},
|
|
892
|
+
&state,
|
|
893
|
+
)?;
|
|
888
894
|
promote_live_sibling_after_scoped_shutdown(&run_workspace, &state)?;
|
|
889
895
|
} else {
|
|
890
896
|
let _changed_keys = mark_matching_session_teams_stopped(
|
|
@@ -893,7 +899,13 @@ pub mod lifecycle_port {
|
|
|
893
899
|
session_killed && !verification_degraded,
|
|
894
900
|
team_shutdown_status,
|
|
895
901
|
);
|
|
896
|
-
crate::state::
|
|
902
|
+
crate::state::repository::StateRepository::new(&run_workspace).save(
|
|
903
|
+
crate::state::repository::StateWriteIntent::ShutdownTeam {
|
|
904
|
+
team_key: None,
|
|
905
|
+
clean: session_killed && !verification_degraded,
|
|
906
|
+
},
|
|
907
|
+
&state,
|
|
908
|
+
)?;
|
|
897
909
|
}
|
|
898
910
|
let coordinator_status = if coordinator_timeout {
|
|
899
911
|
"timeout"
|
|
@@ -2310,14 +2322,16 @@ pub mod lifecycle_port {
|
|
|
2310
2322
|
role_file: &str,
|
|
2311
2323
|
open_display: bool,
|
|
2312
2324
|
team: Option<&str>,
|
|
2325
|
+
force: bool,
|
|
2313
2326
|
) -> Result<Value, CliError> {
|
|
2314
2327
|
let agent_id = crate::model::ids::AgentId::new(agent);
|
|
2315
|
-
match crate::lifecycle::
|
|
2328
|
+
match crate::lifecycle::add_agent_force(
|
|
2316
2329
|
workspace,
|
|
2317
2330
|
&agent_id,
|
|
2318
2331
|
Path::new(role_file),
|
|
2319
2332
|
open_display,
|
|
2320
2333
|
team,
|
|
2334
|
+
force,
|
|
2321
2335
|
) {
|
|
2322
2336
|
Ok(report) => Ok(json!({
|
|
2323
2337
|
"ok": true,
|
|
@@ -2372,7 +2386,7 @@ pub mod lifecycle_port {
|
|
|
2372
2386
|
));
|
|
2373
2387
|
}
|
|
2374
2388
|
}
|
|
2375
|
-
Err(error) if confirm => return Ok(error_value(error)),
|
|
2389
|
+
Err(error) if confirm && !force => return Ok(error_value(error)),
|
|
2376
2390
|
Err(_) => {}
|
|
2377
2391
|
}
|
|
2378
2392
|
if !confirm {
|
|
@@ -2548,7 +2562,13 @@ pub mod lifecycle_port {
|
|
|
2548
2562
|
.and_then(|agents| agents.keys().next().cloned())
|
|
2549
2563
|
.map(Value::String)
|
|
2550
2564
|
.unwrap_or(Value::Null);
|
|
2551
|
-
crate::state::
|
|
2565
|
+
crate::state::repository::StateRepository::new(workspace)
|
|
2566
|
+
.save(
|
|
2567
|
+
crate::state::repository::StateWriteIntent::IdleAck {
|
|
2568
|
+
team_key: Some(&team),
|
|
2569
|
+
},
|
|
2570
|
+
&state,
|
|
2571
|
+
)
|
|
2552
2572
|
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
2553
2573
|
crate::event_log::EventLog::new(workspace)
|
|
2554
2574
|
.write(
|
|
@@ -3553,7 +3573,13 @@ pub mod lifecycle_port {
|
|
|
3553
3573
|
return Ok(());
|
|
3554
3574
|
};
|
|
3555
3575
|
let promoted = crate::state::projection::project_top_level_view(&raw, next_key);
|
|
3556
|
-
crate::state::
|
|
3576
|
+
crate::state::repository::StateRepository::new(workspace).save(
|
|
3577
|
+
crate::state::repository::StateWriteIntent::PromoteLiveSiblingAfterShutdown {
|
|
3578
|
+
stopped_team_key: stopped_key,
|
|
3579
|
+
promoted_team_key: next_key,
|
|
3580
|
+
},
|
|
3581
|
+
&promoted,
|
|
3582
|
+
)?;
|
|
3557
3583
|
Ok(())
|
|
3558
3584
|
}
|
|
3559
3585
|
|
|
@@ -3966,14 +3992,6 @@ pub mod diagnose_port {
|
|
|
3966
3992
|
format!("{:012x}", now & 0xffffffffffff)
|
|
3967
3993
|
}
|
|
3968
3994
|
|
|
3969
|
-
fn read_runtime_state(workspace: &Path) -> Value {
|
|
3970
|
-
let path = workspace.join(".team").join("runtime").join("state.json");
|
|
3971
|
-
std::fs::read_to_string(path)
|
|
3972
|
-
.ok()
|
|
3973
|
-
.and_then(|s| serde_json::from_str(&s).ok())
|
|
3974
|
-
.unwrap_or_else(|| json!({}))
|
|
3975
|
-
}
|
|
3976
|
-
|
|
3977
3995
|
fn which_path(binary: &str) -> Option<String> {
|
|
3978
3996
|
let path = std::env::var_os("PATH")?;
|
|
3979
3997
|
for dir in std::env::split_paths(&path) {
|
|
@@ -505,57 +505,20 @@ enum ParsedTarget {
|
|
|
505
505
|
}
|
|
506
506
|
|
|
507
507
|
fn parse_named_address(raw_name: &str) -> Result<ParsedNamedAddress, NamedAddressError> {
|
|
508
|
-
let
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
if workspace.trim().is_empty() || rest.trim().is_empty() {
|
|
514
|
-
return Err(name_invalid(
|
|
515
|
-
"workspace-qualified name must include workspace and target",
|
|
516
|
-
));
|
|
517
|
-
}
|
|
518
|
-
(Some(PathBuf::from(workspace)), rest.trim())
|
|
519
|
-
} else {
|
|
520
|
-
(None, raw)
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
if name.contains("//") {
|
|
524
|
-
return Err(name_invalid("name contains an empty path segment"));
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
let target = if name.contains('/') {
|
|
528
|
-
let parts = name.split('/').collect::<Vec<_>>();
|
|
529
|
-
if parts.len() != 2 || !valid_component(parts[0]) || !valid_component(parts[1]) {
|
|
530
|
-
return Err(name_invalid("expected <team>/<agent> or <team>/leader"));
|
|
508
|
+
let parsed = crate::messaging::parse_logical_address(raw_name).map_err(name_invalid)?;
|
|
509
|
+
let target = match parsed.target {
|
|
510
|
+
crate::messaging::LogicalAddressTarget::Worker(agent) => ParsedTarget::BareAgent(agent),
|
|
511
|
+
crate::messaging::LogicalAddressTarget::TeamEntity { team, entity } => {
|
|
512
|
+
ParsedTarget::TeamEntity { team, entity }
|
|
531
513
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
entity: parts[1].to_string(),
|
|
514
|
+
crate::messaging::LogicalAddressTarget::SessionWindow { session, window } => {
|
|
515
|
+
ParsedTarget::SessionWindow { session, window }
|
|
535
516
|
}
|
|
536
|
-
} else if name.contains(':') {
|
|
537
|
-
let parts = name.split(':').collect::<Vec<_>>();
|
|
538
|
-
if parts.len() != 2 || parts[0].trim().is_empty() || parts[1].trim().is_empty() {
|
|
539
|
-
return Err(name_invalid("expected <session>:<window>"));
|
|
540
|
-
}
|
|
541
|
-
ParsedTarget::SessionWindow {
|
|
542
|
-
session: parts[0].to_string(),
|
|
543
|
-
window: parts[1].to_string(),
|
|
544
|
-
}
|
|
545
|
-
} else {
|
|
546
|
-
if !valid_component(name) {
|
|
547
|
-
return Err(name_invalid("expected a non-empty agent id"));
|
|
548
|
-
}
|
|
549
|
-
ParsedTarget::BareAgent(name.to_string())
|
|
550
517
|
};
|
|
551
|
-
Ok(ParsedNamedAddress {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
!raw.trim().is_empty()
|
|
556
|
-
&& !raw.contains(char::is_whitespace)
|
|
557
|
-
&& !raw.contains('/')
|
|
558
|
-
&& !raw.contains(':')
|
|
518
|
+
Ok(ParsedNamedAddress {
|
|
519
|
+
workspace: parsed.workspace,
|
|
520
|
+
target,
|
|
521
|
+
})
|
|
559
522
|
}
|
|
560
523
|
|
|
561
524
|
fn resolve_workspace(
|
|
@@ -680,12 +643,38 @@ fn resolve_worker(
|
|
|
680
643
|
.with_scoped_suggestions_for_agent(team_entry, team, agent, parsed));
|
|
681
644
|
}
|
|
682
645
|
};
|
|
683
|
-
let session = string_field(team_entry, "session_name")
|
|
684
|
-
|
|
646
|
+
let Some(session) = string_field(team_entry, "session_name") else {
|
|
647
|
+
return Ok(resolved_worker_without_live(
|
|
648
|
+
sender_workspace,
|
|
649
|
+
target_workspace,
|
|
650
|
+
team,
|
|
651
|
+
agent,
|
|
652
|
+
parsed,
|
|
653
|
+
agent_entry,
|
|
654
|
+
None,
|
|
655
|
+
agent,
|
|
656
|
+
transport.tmux_endpoint(),
|
|
657
|
+
));
|
|
658
|
+
};
|
|
685
659
|
let window = string_field(agent_entry, "window")
|
|
686
660
|
.or_else(|| string_field(agent_entry, "window_name"))
|
|
687
661
|
.unwrap_or(agent);
|
|
688
|
-
let targets = list_targets(transport)
|
|
662
|
+
let targets = match list_targets(transport) {
|
|
663
|
+
Ok(targets) => targets,
|
|
664
|
+
Err(_) => {
|
|
665
|
+
return Ok(resolved_worker_without_live(
|
|
666
|
+
sender_workspace,
|
|
667
|
+
target_workspace,
|
|
668
|
+
team,
|
|
669
|
+
agent,
|
|
670
|
+
parsed,
|
|
671
|
+
agent_entry,
|
|
672
|
+
Some(session),
|
|
673
|
+
window,
|
|
674
|
+
transport.tmux_endpoint(),
|
|
675
|
+
));
|
|
676
|
+
}
|
|
677
|
+
};
|
|
689
678
|
let matches = matching_session_window(&targets, session, window);
|
|
690
679
|
match matches.len() {
|
|
691
680
|
1 => {
|
|
@@ -720,10 +709,14 @@ fn resolve_worker(
|
|
|
720
709
|
warning,
|
|
721
710
|
})
|
|
722
711
|
}
|
|
723
|
-
0 =>
|
|
712
|
+
0 => Ok(resolved_worker_without_live(
|
|
713
|
+
sender_workspace,
|
|
714
|
+
target_workspace,
|
|
724
715
|
team,
|
|
725
716
|
agent,
|
|
726
|
-
|
|
717
|
+
parsed,
|
|
718
|
+
agent_entry,
|
|
719
|
+
Some(session),
|
|
727
720
|
window,
|
|
728
721
|
transport.tmux_endpoint(),
|
|
729
722
|
)),
|
|
@@ -747,6 +740,42 @@ fn resolve_worker(
|
|
|
747
740
|
}
|
|
748
741
|
}
|
|
749
742
|
|
|
743
|
+
#[allow(clippy::too_many_arguments)]
|
|
744
|
+
fn resolved_worker_without_live(
|
|
745
|
+
sender_workspace: &Path,
|
|
746
|
+
target_workspace: &Path,
|
|
747
|
+
team: &str,
|
|
748
|
+
agent: &str,
|
|
749
|
+
parsed: &ParsedNamedAddress,
|
|
750
|
+
agent_entry: &Value,
|
|
751
|
+
session: Option<&str>,
|
|
752
|
+
window: &str,
|
|
753
|
+
tmux_endpoint: Option<String>,
|
|
754
|
+
) -> ResolvedNamedAddress {
|
|
755
|
+
let state_pane_id = string_field(agent_entry, "pane_id").map(str::to_string);
|
|
756
|
+
ResolvedNamedAddress {
|
|
757
|
+
raw_name: parsed.display_name(),
|
|
758
|
+
target_kind: NamedTargetKind::Worker,
|
|
759
|
+
sender_workspace: sender_workspace.to_path_buf(),
|
|
760
|
+
target_workspace: target_workspace.to_path_buf(),
|
|
761
|
+
team_key: Some(team.to_string()),
|
|
762
|
+
agent_id: Some(agent.to_string()),
|
|
763
|
+
pane_id: state_pane_id.clone().unwrap_or_default(),
|
|
764
|
+
session_name: session.map(str::to_string),
|
|
765
|
+
window_name: Some(window.to_string()),
|
|
766
|
+
tmux_endpoint,
|
|
767
|
+
transport_kind: Some("direct_tmux".to_string()),
|
|
768
|
+
app_server: None,
|
|
769
|
+
state_pane_id,
|
|
770
|
+
state_pane_stale: true,
|
|
771
|
+
agent_status: string_field(agent_entry, "status").map(str::to_string),
|
|
772
|
+
warning: Some(
|
|
773
|
+
"agent has no live pane; message will be persisted for standard delivery recovery"
|
|
774
|
+
.to_string(),
|
|
775
|
+
),
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
750
779
|
fn resolve_bare_agent(
|
|
751
780
|
sender_workspace: &Path,
|
|
752
781
|
target_workspace: &Path,
|