@team-agent/installer 0.5.0 → 0.5.2
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 +118 -9
- package/Cargo.toml +2 -2
- package/crates/team-agent/Cargo.toml +1 -0
- package/crates/team-agent/src/app_server_test_support.rs +258 -0
- package/crates/team-agent/src/cli/adapters.rs +32 -3
- package/crates/team-agent/src/cli/attach_app_server_leader.rs +16 -0
- package/crates/team-agent/src/cli/diagnose.rs +14 -5
- package/crates/team-agent/src/cli/emit.rs +111 -3
- package/crates/team-agent/src/cli/mod.rs +72 -25
- package/crates/team-agent/src/cli/named_address.rs +975 -0
- package/crates/team-agent/src/cli/send.rs +200 -1
- package/crates/team-agent/src/cli/status_port.rs +44 -6
- package/crates/team-agent/src/cli/tests/leader_watch.rs +1 -0
- package/crates/team-agent/src/cli/tests/mod.rs +1 -0
- package/crates/team-agent/src/cli/tests/named_address.rs +586 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +2 -0
- package/crates/team-agent/src/cli/tests/status_send.rs +1 -0
- package/crates/team-agent/src/cli/types.rs +21 -0
- package/crates/team-agent/src/codex_app_server.rs +488 -0
- package/crates/team-agent/src/conpty/backend.rs +710 -0
- package/crates/team-agent/src/conpty/mod.rs +32 -0
- package/crates/team-agent/src/coordinator/backoff.rs +45 -6
- package/crates/team-agent/src/diagnose/orphans.rs +11 -3
- package/crates/team-agent/src/leader/lease.rs +144 -7
- package/crates/team-agent/src/leader/owner_bind.rs +5 -2
- package/crates/team-agent/src/leader/start.rs +18 -5
- package/crates/team-agent/src/leader/tests/lease_api.rs +172 -0
- package/crates/team-agent/src/lib.rs +23 -0
- package/crates/team-agent/src/lifecycle/launch.rs +127 -3
- package/crates/team-agent/src/lifecycle/restart/common.rs +70 -0
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +7 -0
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +90 -0
- package/crates/team-agent/src/messaging/delivery.rs +329 -9
- package/crates/team-agent/src/messaging/leader_receiver.rs +17 -138
- package/crates/team-agent/src/messaging/mod.rs +2 -2
- package/crates/team-agent/src/messaging/results.rs +78 -21
- package/crates/team-agent/src/messaging/tests/runtime.rs +232 -0
- package/crates/team-agent/src/tmux_backend.rs +31 -0
- package/crates/team-agent/src/transport_factory.rs +632 -0
- package/package.json +4 -4
|
@@ -112,6 +112,10 @@ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliE
|
|
|
112
112
|
"claim-leader" => cmd_claim_leader(&claim_leader_args(args, cwd)).map(emit_result),
|
|
113
113
|
// Real dispatch: `cmd_attach_leader` writes the `leader_receiver` binding.
|
|
114
114
|
"attach-leader" => cmd_attach_leader(&attach_leader_args(args, cwd)?).map(emit_result),
|
|
115
|
+
"attach-app-server-leader" => {
|
|
116
|
+
cmd_attach_app_server_leader(&attach_app_server_leader_args(args, cwd)?)
|
|
117
|
+
.map(emit_result)
|
|
118
|
+
}
|
|
115
119
|
"identity" => cmd_identity(&identity_args(args, cwd)).map(emit_result),
|
|
116
120
|
"approvals" => cmd_approvals(&approvals_args(args, cwd)).map(emit_result),
|
|
117
121
|
"inbox" => cmd_inbox(&inbox_args(args, cwd)?).map(emit_result),
|
|
@@ -167,6 +171,7 @@ const DISPATCH_COMMANDS: &[&str] = &[
|
|
|
167
171
|
"takeover",
|
|
168
172
|
"claim-leader",
|
|
169
173
|
"attach-leader",
|
|
174
|
+
"attach-app-server-leader",
|
|
170
175
|
"identity",
|
|
171
176
|
"approvals",
|
|
172
177
|
"inbox",
|
|
@@ -210,6 +215,23 @@ fn is_known_subcommand(command: &str) -> bool {
|
|
|
210
215
|
DISPATCH_COMMANDS.contains(&command) || SPEC_ONLY_HELP_COMMANDS.contains(&command)
|
|
211
216
|
}
|
|
212
217
|
|
|
218
|
+
/// Test-only public accessor for `command_help` — allows integration
|
|
219
|
+
/// tests to grep the help copy without depending on internal parser
|
|
220
|
+
/// machinery.
|
|
221
|
+
pub fn __test_command_help(command: Option<&str>) -> String {
|
|
222
|
+
command_help(command)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/// Test-only public accessor for `quick_start_args` — allows
|
|
226
|
+
/// integration tests to exercise the parser without going through
|
|
227
|
+
/// stdio + the full `main` entrypoint.
|
|
228
|
+
pub fn __test_quick_start_args(
|
|
229
|
+
args: &[String],
|
|
230
|
+
cwd: &std::path::Path,
|
|
231
|
+
) -> Result<crate::cli::types::QuickStartArgs, crate::cli::CliError> {
|
|
232
|
+
quick_start_args(args, cwd)
|
|
233
|
+
}
|
|
234
|
+
|
|
213
235
|
fn command_help(command: Option<&str>) -> String {
|
|
214
236
|
match command {
|
|
215
237
|
None => {
|
|
@@ -222,10 +244,10 @@ fn command_help(command: Option<&str>) -> String {
|
|
|
222
244
|
)
|
|
223
245
|
}
|
|
224
246
|
Some("init") => "usage: team-agent init [--workspace WORKSPACE] [--force] [--json]".to_string(),
|
|
225
|
-
Some("quick-start") => "usage: team-agent quick-start [TEAMDIR] [--workspace WORKSPACE] [--name NAME] [--team-id TEAM|--team TEAM] [--yes] [--no-display] [--json]\n\ndefaults: display_backend=adaptive; set display_backend: none in TEAM.md or pass --no-display to use one worker window per agent.".to_string(),
|
|
247
|
+
Some("quick-start") => "usage: team-agent quick-start [TEAMDIR] [--workspace WORKSPACE] [--name NAME] [--team-id TEAM|--team TEAM] [--yes] [--no-display] [--backend tmux|conpty] [--json]\n\ndefaults: display_backend=adaptive; set display_backend: none in TEAM.md or pass --no-display to use one worker window per agent.\n\n--backend selects the worker transport (Phase 1d Batch 2): tmux (default on POSIX; unchanged behavior), conpty (Windows-native ConPTY worker transport; requires the shim binary and Windows host).".to_string(),
|
|
226
248
|
Some("start") => "usage: team-agent start [TEAMDIR] [--yes] [--fresh] [--json]".to_string(),
|
|
227
249
|
Some("compile") => "usage: team-agent compile --team TEAM [--out FILE] [--json]".to_string(),
|
|
228
|
-
Some("send") => "usage: team-agent send TARGET MESSAGE... [--workspace WORKSPACE] [--team TEAM] [--targets AGENTS] [--task TASK] [--sender SENDER] [--watch-result] [--requires-ack|--no-ack] [--no-wait] [--timeout SECONDS] [--confirm-human] [--message-id ID] [--json]".to_string(),
|
|
250
|
+
Some("send") => "usage: team-agent send TARGET MESSAGE... [--workspace WORKSPACE] [--team TEAM] [--targets AGENTS] [--to-name NAME] [--pane PANE] [--task TASK] [--sender SENDER] [--watch-result] [--requires-ack|--no-ack] [--no-wait] [--timeout SECONDS] [--confirm-human] [--message-id ID] [--json]\n\nMVP: name-based cross-workspace addressing assumes trusted local caller; no auth gate.".to_string(),
|
|
229
251
|
Some("fallback-send-leader") => "usage: team-agent fallback-send-leader --workspace WORKSPACE --team TEAM --sender AGENT --message-id ID --content TEXT --primary-error ERROR [--task TASK] [--json]\n\nEmergency one-shot fallback only after a leader-bound MCP send transport failure; restart-agent after use.".to_string(),
|
|
230
252
|
Some("fallback-report-result") => "usage: team-agent fallback-report-result --workspace WORKSPACE --team TEAM --agent-id AGENT --task-id TASK --result-json JSON --primary-error ERROR [--json]\n\nEmergency one-shot fallback only after a report_result MCP transport failure; persists the result DB row, then restart-agent after use.".to_string(),
|
|
231
253
|
Some("allow-peer-talk") => "usage: team-agent allow-peer-talk A B [--workspace WORKSPACE] [--json]".to_string(),
|
|
@@ -247,6 +269,7 @@ fn command_help(command: Option<&str>) -> String {
|
|
|
247
269
|
Some("takeover") => "usage: team-agent takeover [--workspace WORKSPACE] [--team TEAM] [--confirm] [--json]".to_string(),
|
|
248
270
|
Some("claim-leader") => "usage: team-agent claim-leader [--workspace WORKSPACE] [--team TEAM] [--confirm] [--json]".to_string(),
|
|
249
271
|
Some("attach-leader") => "usage: team-agent attach-leader [--workspace WORKSPACE] [--team TEAM] [--pane PANE] [--provider PROVIDER] [--confirm] [--json]".to_string(),
|
|
272
|
+
Some("attach-app-server-leader") => "usage: team-agent attach-app-server-leader [--workspace WORKSPACE] [--team TEAM] --socket unix:///path.sock --thread-id THREAD_ID [--json]".to_string(),
|
|
250
273
|
Some("identity") => "usage: team-agent identity [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
|
|
251
274
|
Some("approvals") => "usage: team-agent approvals [AGENT] [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
|
|
252
275
|
Some("inbox") => "usage: team-agent inbox AGENT [--workspace WORKSPACE] [--team TEAM] [--limit N] [--since CURSOR] [--json]".to_string(),
|
|
@@ -668,13 +691,20 @@ struct ParsedArgs {
|
|
|
668
691
|
out: Option<PathBuf>,
|
|
669
692
|
auth_mode: Option<String>,
|
|
670
693
|
pane: Option<String>,
|
|
694
|
+
to_name: Option<String>,
|
|
671
695
|
provider: Option<String>,
|
|
696
|
+
socket: Option<String>,
|
|
697
|
+
thread_id: Option<String>,
|
|
672
698
|
message_id: Option<String>,
|
|
673
699
|
content: Option<String>,
|
|
674
700
|
primary_error: Option<String>,
|
|
675
701
|
agent_id: Option<String>,
|
|
676
702
|
task_id: Option<String>,
|
|
677
703
|
result_json: Option<String>,
|
|
704
|
+
/// 0.5.x Phase 1d Batch 2: quick-start `--backend <tmux|conpty>`.
|
|
705
|
+
/// Raw string (validated at the quick-start builder); the factory
|
|
706
|
+
/// enforces literal semantics.
|
|
707
|
+
backend: Option<String>,
|
|
678
708
|
}
|
|
679
709
|
|
|
680
710
|
fn parse_args(args: &[String]) -> ParsedArgs {
|
|
@@ -716,6 +746,7 @@ fn parse_args(args: &[String]) -> ParsedArgs {
|
|
|
716
746
|
}
|
|
717
747
|
"--force" => parsed.force = true,
|
|
718
748
|
"--no-display" => parsed.no_display = true,
|
|
749
|
+
"--backend" => parsed.backend = next_arg(args, &mut i),
|
|
719
750
|
"--discard-session" => parsed.discard_session = true,
|
|
720
751
|
"--role-file" => parsed.role_file = next_arg(args, &mut i),
|
|
721
752
|
"--as" => parsed.as_agent = next_arg(args, &mut i),
|
|
@@ -750,7 +781,10 @@ fn parse_args(args: &[String]) -> ParsedArgs {
|
|
|
750
781
|
"--out" => parsed.out = next_arg(args, &mut i).map(PathBuf::from),
|
|
751
782
|
"--auth-mode" => parsed.auth_mode = next_arg(args, &mut i),
|
|
752
783
|
"--pane" => parsed.pane = next_arg(args, &mut i),
|
|
784
|
+
"--to-name" => parsed.to_name = next_arg(args, &mut i),
|
|
753
785
|
"--provider" => parsed.provider = next_arg(args, &mut i),
|
|
786
|
+
"--socket" => parsed.socket = next_arg(args, &mut i),
|
|
787
|
+
"--thread-id" => parsed.thread_id = next_arg(args, &mut i),
|
|
754
788
|
"--message-id" => parsed.message_id = next_arg(args, &mut i),
|
|
755
789
|
"--content" => parsed.content = next_arg(args, &mut i),
|
|
756
790
|
"--primary-error" => parsed.primary_error = next_arg(args, &mut i),
|
|
@@ -762,9 +796,18 @@ fn parse_args(args: &[String]) -> ParsedArgs {
|
|
|
762
796
|
other if other.starts_with("--pane=") => {
|
|
763
797
|
parsed.pane = Some(other.trim_start_matches("--pane=").to_string());
|
|
764
798
|
}
|
|
799
|
+
other if other.starts_with("--to-name=") => {
|
|
800
|
+
parsed.to_name = Some(other.trim_start_matches("--to-name=").to_string());
|
|
801
|
+
}
|
|
765
802
|
other if other.starts_with("--provider=") => {
|
|
766
803
|
parsed.provider = Some(other.trim_start_matches("--provider=").to_string());
|
|
767
804
|
}
|
|
805
|
+
other if other.starts_with("--socket=") => {
|
|
806
|
+
parsed.socket = Some(other.trim_start_matches("--socket=").to_string());
|
|
807
|
+
}
|
|
808
|
+
other if other.starts_with("--thread-id=") => {
|
|
809
|
+
parsed.thread_id = Some(other.trim_start_matches("--thread-id=").to_string());
|
|
810
|
+
}
|
|
768
811
|
other if other.starts_with('-') => {}
|
|
769
812
|
other => parsed.positionals.push(other.to_string()),
|
|
770
813
|
}
|
|
@@ -828,6 +871,20 @@ fn quick_start_args(args: &[String], cwd: &Path) -> Result<QuickStartArgs, CliEr
|
|
|
828
871
|
} else {
|
|
829
872
|
workspace.join(agents_dir)
|
|
830
873
|
};
|
|
874
|
+
// 0.5.x Phase 1d Batch 2: validate the `--backend` literal up-front
|
|
875
|
+
// so users get a fast, actionable error instead of a downstream
|
|
876
|
+
// factory refusal. Accept the same literals as the factory
|
|
877
|
+
// `RequestedTransportBackend::parse_literal`.
|
|
878
|
+
if let Some(literal) = parsed.backend.as_deref() {
|
|
879
|
+
let normalized = literal.trim().to_ascii_lowercase();
|
|
880
|
+
if normalized != "tmux" && normalized != "conpty" {
|
|
881
|
+
return Err(CliError::Usage(format!(
|
|
882
|
+
"--backend must be `tmux` or `conpty`, got {literal:?}. \
|
|
883
|
+
`pty` is not a supported literal in Phase 1d (design \
|
|
884
|
+
§Non-Goals + CR C-1 ②)."
|
|
885
|
+
)));
|
|
886
|
+
}
|
|
887
|
+
}
|
|
831
888
|
Ok(QuickStartArgs {
|
|
832
889
|
workspace,
|
|
833
890
|
agents_dir,
|
|
@@ -836,6 +893,7 @@ fn quick_start_args(args: &[String], cwd: &Path) -> Result<QuickStartArgs, CliEr
|
|
|
836
893
|
yes: parsed.yes,
|
|
837
894
|
no_display: parsed.no_display,
|
|
838
895
|
json: parsed.json,
|
|
896
|
+
backend: parsed.backend,
|
|
839
897
|
})
|
|
840
898
|
}
|
|
841
899
|
|
|
@@ -875,7 +933,7 @@ fn resolve_cli_path(cwd: &Path, path: &Path) -> PathBuf {
|
|
|
875
933
|
|
|
876
934
|
fn send_args(args: &[String], cwd: &Path) -> Result<SendArgs, CliError> {
|
|
877
935
|
let parsed = parse_args(args);
|
|
878
|
-
let target = if parsed.targets.is_some() || parsed.pane.is_some() {
|
|
936
|
+
let target = if parsed.targets.is_some() || parsed.pane.is_some() || parsed.to_name.is_some() {
|
|
879
937
|
None
|
|
880
938
|
} else {
|
|
881
939
|
parsed.positionals.first().cloned()
|
|
@@ -903,6 +961,7 @@ fn send_args(args: &[String], cwd: &Path) -> Result<SendArgs, CliError> {
|
|
|
903
961
|
json: parsed.json,
|
|
904
962
|
message_id: parsed.message_id,
|
|
905
963
|
pane: parsed.pane.clone(),
|
|
964
|
+
to_name: parsed.to_name.clone(),
|
|
906
965
|
})
|
|
907
966
|
}
|
|
908
967
|
|
|
@@ -1073,6 +1132,26 @@ fn attach_leader_args(args: &[String], cwd: &Path) -> Result<AttachLeaderArgs, C
|
|
|
1073
1132
|
})
|
|
1074
1133
|
}
|
|
1075
1134
|
|
|
1135
|
+
fn attach_app_server_leader_args(
|
|
1136
|
+
args: &[String],
|
|
1137
|
+
cwd: &Path,
|
|
1138
|
+
) -> Result<AttachAppServerLeaderArgs, CliError> {
|
|
1139
|
+
let parsed = parse_args(args);
|
|
1140
|
+
Ok(AttachAppServerLeaderArgs {
|
|
1141
|
+
workspace: workspace(&parsed, cwd),
|
|
1142
|
+
team: parsed.team,
|
|
1143
|
+
socket: parsed
|
|
1144
|
+
.socket
|
|
1145
|
+
.filter(|socket| !socket.is_empty())
|
|
1146
|
+
.ok_or_else(|| CliError::Usage("missing --socket".to_string()))?,
|
|
1147
|
+
thread_id: parsed
|
|
1148
|
+
.thread_id
|
|
1149
|
+
.filter(|thread_id| !thread_id.is_empty())
|
|
1150
|
+
.ok_or_else(|| CliError::Usage("missing --thread-id".to_string()))?,
|
|
1151
|
+
json: parsed.json,
|
|
1152
|
+
})
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1076
1155
|
fn identity_args(args: &[String], cwd: &Path) -> IdentityArgs {
|
|
1077
1156
|
let parsed = parse_args(args);
|
|
1078
1157
|
IdentityArgs {
|
|
@@ -1602,6 +1681,8 @@ mod tests {
|
|
|
1602
1681
|
"--workspace",
|
|
1603
1682
|
"--team",
|
|
1604
1683
|
"--targets",
|
|
1684
|
+
"--to-name",
|
|
1685
|
+
"--pane",
|
|
1605
1686
|
"--watch-result",
|
|
1606
1687
|
"--timeout",
|
|
1607
1688
|
"--json",
|
|
@@ -1781,6 +1862,33 @@ mod tests {
|
|
|
1781
1862
|
let _ = std::fs::remove_dir_all(&cwd);
|
|
1782
1863
|
}
|
|
1783
1864
|
|
|
1865
|
+
#[test]
|
|
1866
|
+
fn send_to_name_positionals_are_message_not_target() {
|
|
1867
|
+
let cwd = tmp_workspace();
|
|
1868
|
+
let args = send_args(&cli_argv(&["--to-name", "team-a/qa", "hello"]), &cwd).unwrap();
|
|
1869
|
+
assert_eq!(args.to_name.as_deref(), Some("team-a/qa"));
|
|
1870
|
+
assert_eq!(args.target, None);
|
|
1871
|
+
assert_eq!(args.targets, None);
|
|
1872
|
+
assert_eq!(args.message, vec!["hello".to_string()]);
|
|
1873
|
+
|
|
1874
|
+
let args = send_args(
|
|
1875
|
+
&cli_argv(&["--to-name=team-a/qa", "multi", "word", "message"]),
|
|
1876
|
+
&cwd,
|
|
1877
|
+
)
|
|
1878
|
+
.unwrap();
|
|
1879
|
+
assert_eq!(args.target, None);
|
|
1880
|
+
assert_eq!(
|
|
1881
|
+
args.message,
|
|
1882
|
+
vec![
|
|
1883
|
+
"multi".to_string(),
|
|
1884
|
+
"word".to_string(),
|
|
1885
|
+
"message".to_string()
|
|
1886
|
+
]
|
|
1887
|
+
);
|
|
1888
|
+
|
|
1889
|
+
let _ = std::fs::remove_dir_all(&cwd);
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1784
1892
|
#[test]
|
|
1785
1893
|
fn send_pane_still_rejects_to_and_empty_message() {
|
|
1786
1894
|
let cwd = tmp_workspace();
|
|
@@ -53,19 +53,23 @@ pub(crate) const SEND_REMINDER: &str = "Message delivered. Wait for the worker t
|
|
|
53
53
|
pub(crate) const STATUS_REMINDER: &str = "To wait for results use --watch-result or team-agent collect. Do not capture-pane worker terminals.";
|
|
54
54
|
|
|
55
55
|
pub mod adapters;
|
|
56
|
+
pub mod attach_app_server_leader;
|
|
56
57
|
pub mod diagnose;
|
|
57
58
|
pub mod emit;
|
|
58
59
|
pub mod helpers;
|
|
59
60
|
pub mod leader;
|
|
61
|
+
pub mod named_address;
|
|
60
62
|
pub mod profile;
|
|
61
63
|
pub mod send;
|
|
62
64
|
pub mod status;
|
|
63
65
|
pub mod types;
|
|
64
66
|
|
|
65
67
|
pub use adapters::*;
|
|
68
|
+
pub use attach_app_server_leader::*;
|
|
66
69
|
pub use diagnose::*;
|
|
67
70
|
pub use emit::*;
|
|
68
71
|
pub use leader::*;
|
|
72
|
+
pub use named_address::*;
|
|
69
73
|
pub use profile::*;
|
|
70
74
|
pub use send::*;
|
|
71
75
|
pub use status::*;
|
|
@@ -118,14 +122,16 @@ pub mod lifecycle_port {
|
|
|
118
122
|
team_id: Option<&str>,
|
|
119
123
|
yes: bool,
|
|
120
124
|
open_display: bool,
|
|
125
|
+
backend: Option<&str>,
|
|
121
126
|
) -> Result<Value, CliError> {
|
|
122
|
-
match crate::lifecycle::
|
|
127
|
+
match crate::lifecycle::quick_start_in_workspace_with_display_and_backend(
|
|
123
128
|
workspace,
|
|
124
129
|
agents_dir,
|
|
125
130
|
name,
|
|
126
131
|
yes,
|
|
127
132
|
team_id,
|
|
128
133
|
open_display,
|
|
134
|
+
backend,
|
|
129
135
|
) {
|
|
130
136
|
Ok(report) => Ok(quick_start_value(report)),
|
|
131
137
|
Err(e) => Ok(error_value(e)),
|
|
@@ -190,16 +196,40 @@ pub mod lifecycle_port {
|
|
|
190
196
|
crate::tmux_backend::attach_command_for_workspace(cwd, session, window.as_str())
|
|
191
197
|
}
|
|
192
198
|
/// `runtime.shutdown`(`cmd_shutdown`)。
|
|
199
|
+
///
|
|
200
|
+
/// 0.5.x Phase 1d Batch 5: the workspace-transport branch now routes
|
|
201
|
+
/// through `transport_factory::resolve_read_only_transport` so a
|
|
202
|
+
/// conpty team's shutdown reaches the shim rather than a
|
|
203
|
+
/// no-op tmux backend. Legacy tmux endpoint literal in state still
|
|
204
|
+
/// takes the `shutdown_transport_for_endpoint` tmux channel helper
|
|
205
|
+
/// path (intentional — this is the "attached explicit tmux
|
|
206
|
+
/// endpoint" branch and stays tmux-typed).
|
|
207
|
+
///
|
|
208
|
+
/// `shutdown_with_transport_and_state` is generic over `&dyn
|
|
209
|
+
/// Transport`, so we hand the boxed factory backend down as
|
|
210
|
+
/// `&*box`. Factory refusal falls back to the workspace tmux
|
|
211
|
+
/// backend byte-equivalent to today so daemon liveness paths
|
|
212
|
+
/// don't crash.
|
|
193
213
|
pub fn shutdown(workspace: &Path, keep_logs: bool, team: Option<&str>) -> Result<Value, CliError> {
|
|
194
214
|
let run_ws = crate::model::paths::canonical_run_workspace(workspace)
|
|
195
215
|
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
196
216
|
let state = shutdown_state_for_team(&run_ws, team)?;
|
|
197
|
-
|
|
198
|
-
shutdown_transport_for_endpoint(endpoint)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
217
|
+
if let Some(endpoint) = legacy_worker_tmux_endpoint(&state) {
|
|
218
|
+
let transport = shutdown_transport_for_endpoint(endpoint);
|
|
219
|
+
return shutdown_with_transport_and_state(
|
|
220
|
+
workspace, keep_logs, team, &transport, Some(state),
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
let boxed: Box<dyn crate::transport::Transport> =
|
|
224
|
+
match crate::transport_factory::resolve_read_only_transport(
|
|
225
|
+
&run_ws,
|
|
226
|
+
Some(&state),
|
|
227
|
+
crate::transport_factory::TransportPurpose::Shutdown,
|
|
228
|
+
) {
|
|
229
|
+
Ok(r) => r.backend,
|
|
230
|
+
Err(_) => Box::new(shutdown_workspace_transport(&run_ws)),
|
|
231
|
+
};
|
|
232
|
+
shutdown_with_transport_and_state(workspace, keep_logs, team, boxed.as_ref(), Some(state))
|
|
203
233
|
}
|
|
204
234
|
|
|
205
235
|
/// E12 ①:从 state 锚 pane_id(leader_receiver/team_owner,top+teams)映射到其所在 session
|
|
@@ -1189,24 +1219,41 @@ pub mod lifecycle_port {
|
|
|
1189
1219
|
}
|
|
1190
1220
|
}
|
|
1191
1221
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1222
|
+
// 0.5.x Phase 1d Batch 5 (design §Batch 5 point 4): a ConPTY
|
|
1223
|
+
// team's residual check MUST NOT read the workspace tmux socket
|
|
1224
|
+
// + shared default tmux socket as evidence for/against ConPTY
|
|
1225
|
+
// residual. Those probes are meaningless for a shim-owned pane
|
|
1226
|
+
// universe: they will always return `has_session=false` (no
|
|
1227
|
+
// tmux session exists) which would look like "no residual" but
|
|
1228
|
+
// gives zero honest information about whether the shim / child
|
|
1229
|
+
// panes have actually been reaped. Skip the tmux fallback
|
|
1230
|
+
// probes when the primary transport is ConPTY; the primary
|
|
1231
|
+
// transport check above already covered the honest question
|
|
1232
|
+
// ("does the shim still have this session?").
|
|
1233
|
+
let primary_is_conpty = matches!(
|
|
1234
|
+
transport.kind(),
|
|
1235
|
+
crate::transport::BackendKind::ConPty
|
|
1236
|
+
);
|
|
1237
|
+
if !primary_is_conpty {
|
|
1238
|
+
let workspace_transport = shutdown_workspace_transport(workspace);
|
|
1239
|
+
match crate::transport::Transport::has_session(&workspace_transport, session) {
|
|
1240
|
+
Ok(true) => residual = true,
|
|
1241
|
+
Ok(false) => {}
|
|
1242
|
+
Err(err) if tmux_absent_error(&err.to_string()) => {}
|
|
1243
|
+
Err(err) => {
|
|
1244
|
+
error.get_or_insert_with(|| err.to_string());
|
|
1245
|
+
residual = true;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
let default_transport = crate::tmux_backend::TmuxBackend::new();
|
|
1249
|
+
match crate::transport::Transport::has_session(&default_transport, session) {
|
|
1250
|
+
Ok(true) => residual = true,
|
|
1251
|
+
Ok(false) => {}
|
|
1252
|
+
Err(err) if tmux_absent_error(&err.to_string()) => {}
|
|
1253
|
+
Err(err) => {
|
|
1254
|
+
error.get_or_insert_with(|| err.to_string());
|
|
1255
|
+
residual = true;
|
|
1256
|
+
}
|
|
1210
1257
|
}
|
|
1211
1258
|
}
|
|
1212
1259
|
let sessions = if residual {
|