@team-agent/installer 0.5.1 → 0.5.3
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 +120 -9
- package/Cargo.toml +2 -2
- package/crates/team-agent/Cargo.toml +21 -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 +79 -1
- package/crates/team-agent/src/cli/mod.rs +190 -80
- package/crates/team-agent/src/cli/named_address.rs +111 -0
- package/crates/team-agent/src/cli/send.rs +98 -3
- package/crates/team-agent/src/cli/status_port.rs +44 -6
- package/crates/team-agent/src/cli/tests/named_address.rs +135 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +2 -0
- package/crates/team-agent/src/cli/types.rs +17 -0
- package/crates/team-agent/src/codex_app_server.rs +549 -0
- package/crates/team-agent/src/conpty/backend.rs +822 -0
- package/crates/team-agent/src/conpty/mod.rs +32 -0
- package/crates/team-agent/src/coordinator/backoff.rs +132 -7
- package/crates/team-agent/src/coordinator/conpty_shim.rs +730 -0
- package/crates/team-agent/src/coordinator/health.rs +97 -11
- package/crates/team-agent/src/coordinator/mod.rs +8 -0
- package/crates/team-agent/src/coordinator/tests/daemon.rs +2 -1
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +6 -0
- package/crates/team-agent/src/diagnose/orphans.rs +29 -10
- 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/provider_attribution.rs +19 -34
- package/crates/team-agent/src/leader/start.rs +18 -5
- package/crates/team-agent/src/leader/tests/lease_api.rs +179 -0
- package/crates/team-agent/src/lib.rs +36 -0
- package/crates/team-agent/src/lifecycle/launch.rs +207 -94
- package/crates/team-agent/src/lifecycle/lock.rs +40 -33
- package/crates/team-agent/src/lifecycle/restart/agent.rs +10 -18
- package/crates/team-agent/src/lifecycle/restart/common.rs +70 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +7 -0
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +7 -0
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +63 -0
- package/crates/team-agent/src/mcp_server/wire.rs +6 -7
- 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 +237 -0
- package/crates/team-agent/src/packaging/tests.rs +41 -6
- package/crates/team-agent/src/packaging/types.rs +31 -3
- package/crates/team-agent/src/platform/argv.rs +324 -0
- package/crates/team-agent/src/platform/errors.rs +95 -0
- package/crates/team-agent/src/platform/file_lock.rs +418 -0
- package/crates/team-agent/src/platform/mod.rs +66 -0
- package/crates/team-agent/src/platform/process.rs +555 -0
- package/crates/team-agent/src/state/persist.rs +205 -25
- package/crates/team-agent/src/tmux_backend.rs +94 -13
- package/crates/team-agent/src/transport_factory.rs +751 -0
- package/package.json +4 -4
|
@@ -146,7 +146,14 @@ fn errno_name(errno: Option<i32>) -> Option<&'static str> {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
/// `runtime.py:_runtime_lock` 的 flock 版(RAII;Drop 释放)。state-save 不发锁事件。
|
|
149
|
-
///
|
|
149
|
+
///
|
|
150
|
+
/// 0.5.x Windows portability Batch 2: migrated to
|
|
151
|
+
/// `crate::platform::file_lock::{try_lock_once_nonblocking, unlock}` so
|
|
152
|
+
/// the same polling loop + timeout + `StateError::Locked(name)` shape
|
|
153
|
+
/// works on both Unix (`flock`) and Windows (`LockFileEx`) — 1:1
|
|
154
|
+
/// semantic mapping. The Batch 0 non-Unix `not_yet_implemented`
|
|
155
|
+
/// fallback is now removed (CR C-2 fallback burn-down; grep guard
|
|
156
|
+
/// `platform_fallback_burndown_batch0.rs` flipped in this batch).
|
|
150
157
|
struct RuntimeLock {
|
|
151
158
|
#[allow(dead_code)]
|
|
152
159
|
file: std::fs::File,
|
|
@@ -160,42 +167,37 @@ impl RuntimeLock {
|
|
|
160
167
|
}
|
|
161
168
|
let file = std::fs::OpenOptions::new()
|
|
162
169
|
.create(true)
|
|
170
|
+
.read(true)
|
|
163
171
|
.write(true)
|
|
164
172
|
.truncate(false)
|
|
165
173
|
.open(&lock_path)?;
|
|
166
|
-
|
|
167
|
-
{
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return Ok(Self { file });
|
|
174
|
+
let start = Instant::now();
|
|
175
|
+
loop {
|
|
176
|
+
match crate::platform::file_lock::try_lock_once_nonblocking(&file) {
|
|
177
|
+
Ok(true) => return Ok(Self { file }),
|
|
178
|
+
Ok(false) => {
|
|
179
|
+
if start.elapsed().as_secs_f64() >= timeout {
|
|
180
|
+
return Err(StateError::Locked(name.to_string()));
|
|
181
|
+
}
|
|
182
|
+
std::thread::sleep(Duration::from_millis(50));
|
|
176
183
|
}
|
|
177
|
-
|
|
178
|
-
|
|
184
|
+
Err(e) => {
|
|
185
|
+
// Byte-preserving: a real I/O error surfaces as a
|
|
186
|
+
// `StateError::Io` (via `From<io::Error>`), same as
|
|
187
|
+
// the current inline code would if `file.open()`
|
|
188
|
+
// failed. The lock-would-block case took the
|
|
189
|
+
// Ok(false) arm above.
|
|
190
|
+
return Err(StateError::from(e));
|
|
179
191
|
}
|
|
180
|
-
std::thread::sleep(Duration::from_millis(50));
|
|
181
192
|
}
|
|
182
193
|
}
|
|
183
|
-
#[cfg(not(unix))]
|
|
184
|
-
{
|
|
185
|
-
let _ = timeout;
|
|
186
|
-
Err(StateError::Locked(format!(
|
|
187
|
-
"{name} (runtime lock not yet implemented on non-unix)"
|
|
188
|
-
)))
|
|
189
|
-
}
|
|
190
194
|
}
|
|
191
195
|
}
|
|
192
196
|
|
|
193
|
-
#[cfg(unix)]
|
|
194
197
|
impl Drop for RuntimeLock {
|
|
195
198
|
fn drop(&mut self) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
unsafe { libc::flock(self.file.as_raw_fd(), libc::LOCK_UN) };
|
|
199
|
+
// Best-effort unlock. OS releases on handle close if this fails.
|
|
200
|
+
let _ = crate::platform::file_lock::unlock(&self.file);
|
|
199
201
|
}
|
|
200
202
|
}
|
|
201
203
|
|
|
@@ -451,6 +453,18 @@ fn apply_persist_merge_contract(
|
|
|
451
453
|
// teams.<key> entry-scoped preservation at :370 remains so legacy
|
|
452
454
|
// teams.<key>.team_owner entries still survive a save that omits
|
|
453
455
|
// them.
|
|
456
|
+
|
|
457
|
+
// 0.5.x Windows portability Batch 7 F6 (leader msg_700a016675d0):
|
|
458
|
+
// preserve `state.transport.shim` across saves. The coordinator
|
|
459
|
+
// writes this block when it spawns `windows-shim.exe` during
|
|
460
|
+
// quick-start (see `coordinator::conpty_shim::finalize`).
|
|
461
|
+
// Downstream saves inside the same quick-start (leader-persist,
|
|
462
|
+
// worker-persist) construct their `incoming` state from an
|
|
463
|
+
// in-memory copy that predates the shim-spawn, so without this
|
|
464
|
+
// preserve step the shim block is silently dropped. That
|
|
465
|
+
// clobber broke Batch 6 CHECK 6 (`shutdown` couldn't route to
|
|
466
|
+
// the shim pid because `state.transport.shim.pid` was gone).
|
|
467
|
+
preserve_transport_shim(incoming, latest);
|
|
454
468
|
}
|
|
455
469
|
|
|
456
470
|
let latest_teams = latest.get("teams").and_then(Value::as_object);
|
|
@@ -478,6 +492,47 @@ fn apply_persist_merge_contract(
|
|
|
478
492
|
Ok(())
|
|
479
493
|
}
|
|
480
494
|
|
|
495
|
+
/// 0.5.x Windows portability Batch 7 F6: copy `latest.transport.shim`
|
|
496
|
+
/// into `incoming` if the incoming save omitted it. The coordinator's
|
|
497
|
+
/// `conpty_shim::finalize` writes `state.transport.shim = {pid,
|
|
498
|
+
/// pipe_name, pipe_ready}` after Hello succeeds. If a subsequent save
|
|
499
|
+
/// in the same quick-start starts from a stale in-memory `Value` that
|
|
500
|
+
/// predates the finalize, this preserve step keeps the on-disk shim
|
|
501
|
+
/// block alive.
|
|
502
|
+
///
|
|
503
|
+
/// Only runs when the top-level projection matches (same active team
|
|
504
|
+
/// key + same session name) — no risk of leaking a shim block into
|
|
505
|
+
/// an unrelated projection.
|
|
506
|
+
///
|
|
507
|
+
/// Windows-relevant behavior; on Unix `transport.shim` is never
|
|
508
|
+
/// written, so this fn is a no-op (both branches see `None`).
|
|
509
|
+
fn preserve_transport_shim(incoming: &mut Value, latest: &Value) {
|
|
510
|
+
// The shim block only matters when `latest` has one AND
|
|
511
|
+
// `incoming` doesn't (or `incoming.transport` doesn't exist).
|
|
512
|
+
let Some(latest_shim) = latest
|
|
513
|
+
.get("transport")
|
|
514
|
+
.and_then(|t| t.get("shim"))
|
|
515
|
+
.cloned()
|
|
516
|
+
else {
|
|
517
|
+
return;
|
|
518
|
+
};
|
|
519
|
+
let Some(incoming_obj) = incoming.as_object_mut() else {
|
|
520
|
+
return;
|
|
521
|
+
};
|
|
522
|
+
let transport = incoming_obj
|
|
523
|
+
.entry("transport".to_string())
|
|
524
|
+
.or_insert_with(|| serde_json::json!({}));
|
|
525
|
+
let Some(transport_obj) = transport.as_object_mut() else {
|
|
526
|
+
return;
|
|
527
|
+
};
|
|
528
|
+
// If the incoming already has a shim block, respect it (the
|
|
529
|
+
// caller has authoritative newer state — e.g. shim was rotated).
|
|
530
|
+
if transport_obj.contains_key("shim") {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
transport_obj.insert("shim".to_string(), latest_shim);
|
|
534
|
+
}
|
|
535
|
+
|
|
481
536
|
fn should_skip_capture_backfill(
|
|
482
537
|
current_team_key: Option<&str>,
|
|
483
538
|
skip_team_key: Option<&str>,
|
|
@@ -1696,6 +1751,131 @@ mod tests {
|
|
|
1696
1751
|
);
|
|
1697
1752
|
}
|
|
1698
1753
|
|
|
1754
|
+
// 0.5.x Windows portability Batch 7 F6 (leader msg_700a016675d0):
|
|
1755
|
+
// `state.transport.shim` must survive a downstream save whose
|
|
1756
|
+
// `incoming` was constructed from a stale in-memory Value.
|
|
1757
|
+
#[test]
|
|
1758
|
+
fn transport_shim_block_is_preserved_across_merge() {
|
|
1759
|
+
let ws = temp_ws();
|
|
1760
|
+
// Disk state has the shim block (coordinator wrote it after
|
|
1761
|
+
// Hello succeeded).
|
|
1762
|
+
write_state(
|
|
1763
|
+
&ws,
|
|
1764
|
+
&json!({
|
|
1765
|
+
"session_name": "team-a",
|
|
1766
|
+
"active_team_key": "team-a",
|
|
1767
|
+
"agents": {},
|
|
1768
|
+
"transport": {
|
|
1769
|
+
"kind": "conpty",
|
|
1770
|
+
"shim": { "pid": 4242, "pipe_name": r"\\.\pipe\team-agent-conpty-abc-team-a", "pipe_ready": true }
|
|
1771
|
+
}
|
|
1772
|
+
}),
|
|
1773
|
+
);
|
|
1774
|
+
// Incoming save (leader-persist or worker-persist) doesn't
|
|
1775
|
+
// know about the shim block.
|
|
1776
|
+
let incoming = json!({
|
|
1777
|
+
"session_name": "team-a",
|
|
1778
|
+
"active_team_key": "team-a",
|
|
1779
|
+
"agents": {},
|
|
1780
|
+
"transport": {
|
|
1781
|
+
"kind": "conpty"
|
|
1782
|
+
}
|
|
1783
|
+
});
|
|
1784
|
+
save_runtime_state(&ws, &incoming).unwrap();
|
|
1785
|
+
let saved = read_state(&ws);
|
|
1786
|
+
// Shim block must survive the merge.
|
|
1787
|
+
assert_eq!(
|
|
1788
|
+
saved.pointer("/transport/shim/pid").and_then(Value::as_u64),
|
|
1789
|
+
Some(4242),
|
|
1790
|
+
"shim.pid must be preserved: {saved}"
|
|
1791
|
+
);
|
|
1792
|
+
assert_eq!(
|
|
1793
|
+
saved
|
|
1794
|
+
.pointer("/transport/shim/pipe_name")
|
|
1795
|
+
.and_then(Value::as_str),
|
|
1796
|
+
Some(r"\\.\pipe\team-agent-conpty-abc-team-a"),
|
|
1797
|
+
);
|
|
1798
|
+
assert_eq!(
|
|
1799
|
+
saved
|
|
1800
|
+
.pointer("/transport/shim/pipe_ready")
|
|
1801
|
+
.and_then(Value::as_bool),
|
|
1802
|
+
Some(true),
|
|
1803
|
+
);
|
|
1804
|
+
// And the incoming's `transport.kind` still wins.
|
|
1805
|
+
assert_eq!(
|
|
1806
|
+
saved.pointer("/transport/kind").and_then(Value::as_str),
|
|
1807
|
+
Some("conpty"),
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
#[test]
|
|
1812
|
+
fn transport_shim_incoming_authoritative_overrides_latest() {
|
|
1813
|
+
// If the caller has a NEWER shim block (e.g. rotation), it
|
|
1814
|
+
// wins — preserve_transport_shim only fills in the gap.
|
|
1815
|
+
let ws = temp_ws();
|
|
1816
|
+
write_state(
|
|
1817
|
+
&ws,
|
|
1818
|
+
&json!({
|
|
1819
|
+
"session_name": "team-a",
|
|
1820
|
+
"active_team_key": "team-a",
|
|
1821
|
+
"agents": {},
|
|
1822
|
+
"transport": {
|
|
1823
|
+
"kind": "conpty",
|
|
1824
|
+
"shim": { "pid": 1000, "pipe_name": "old", "pipe_ready": true }
|
|
1825
|
+
}
|
|
1826
|
+
}),
|
|
1827
|
+
);
|
|
1828
|
+
let incoming = json!({
|
|
1829
|
+
"session_name": "team-a",
|
|
1830
|
+
"active_team_key": "team-a",
|
|
1831
|
+
"agents": {},
|
|
1832
|
+
"transport": {
|
|
1833
|
+
"kind": "conpty",
|
|
1834
|
+
"shim": { "pid": 9999, "pipe_name": "new", "pipe_ready": true }
|
|
1835
|
+
}
|
|
1836
|
+
});
|
|
1837
|
+
save_runtime_state(&ws, &incoming).unwrap();
|
|
1838
|
+
let saved = read_state(&ws);
|
|
1839
|
+
assert_eq!(
|
|
1840
|
+
saved.pointer("/transport/shim/pid").and_then(Value::as_u64),
|
|
1841
|
+
Some(9999),
|
|
1842
|
+
"incoming shim block must win when both sides have one"
|
|
1843
|
+
);
|
|
1844
|
+
assert_eq!(
|
|
1845
|
+
saved
|
|
1846
|
+
.pointer("/transport/shim/pipe_name")
|
|
1847
|
+
.and_then(Value::as_str),
|
|
1848
|
+
Some("new"),
|
|
1849
|
+
);
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
#[test]
|
|
1853
|
+
fn transport_shim_no_leak_when_latest_lacks_block() {
|
|
1854
|
+
// If disk has no shim block, save must not synthesize one.
|
|
1855
|
+
let ws = temp_ws();
|
|
1856
|
+
write_state(
|
|
1857
|
+
&ws,
|
|
1858
|
+
&json!({
|
|
1859
|
+
"session_name": "team-a",
|
|
1860
|
+
"active_team_key": "team-a",
|
|
1861
|
+
"agents": {},
|
|
1862
|
+
"transport": {"kind": "tmux"}
|
|
1863
|
+
}),
|
|
1864
|
+
);
|
|
1865
|
+
let incoming = json!({
|
|
1866
|
+
"session_name": "team-a",
|
|
1867
|
+
"active_team_key": "team-a",
|
|
1868
|
+
"agents": {},
|
|
1869
|
+
"transport": {"kind": "tmux"}
|
|
1870
|
+
});
|
|
1871
|
+
save_runtime_state(&ws, &incoming).unwrap();
|
|
1872
|
+
let saved = read_state(&ws);
|
|
1873
|
+
assert!(
|
|
1874
|
+
saved.pointer("/transport/shim").is_none(),
|
|
1875
|
+
"shim must not appear when neither side had one: {saved}"
|
|
1876
|
+
);
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1699
1879
|
#[test]
|
|
1700
1880
|
fn projection_paths_do_not_cross_backfill_topology() {
|
|
1701
1881
|
let ws = temp_ws();
|
|
@@ -18,6 +18,13 @@
|
|
|
18
18
|
use std::collections::BTreeMap;
|
|
19
19
|
use std::hash::{Hash, Hasher};
|
|
20
20
|
use std::io::{Read, Write};
|
|
21
|
+
// 0.5.x Windows portability Batch 1: gate the sole Unix API surface
|
|
22
|
+
// in this concrete tmux backend (FileTypeExt::is_socket + libc::geteuid
|
|
23
|
+
// tmux-socket-root derivation). The rest of the file is Command::new
|
|
24
|
+
// "tmux" shellouts that compile on Windows but return runtime errors
|
|
25
|
+
// honestly (tmux binary absent → typed subprocess error).
|
|
26
|
+
// Truth source: `.team/artifacts/0.5.x-windows-portability-survey-design.md` §Batch 1.
|
|
27
|
+
#[cfg(unix)]
|
|
21
28
|
use std::os::unix::fs::FileTypeExt;
|
|
22
29
|
use std::path::{Path, PathBuf};
|
|
23
30
|
use std::process::Stdio;
|
|
@@ -430,6 +437,37 @@ fn runtime_tmux_endpoint_from_state(
|
|
|
430
437
|
/// stable FNV-1a hash over the canonicalized path. AF_UNIX `sun_path` is ~104 chars and the socket
|
|
431
438
|
/// lives at `/tmp/tmux-<uid>/<name>`, so we must NOT use the (~88-char) session name. §10: a
|
|
432
439
|
/// canonicalize failure falls back to the raw path (never panics).
|
|
440
|
+
/// Public re-export of the crate-private canonical workspace hash used
|
|
441
|
+
/// by the tmux short-socket derivation. `transport_factory` uses this
|
|
442
|
+
/// same hash for the ConPTY `workspace_hash` so both backends see
|
|
443
|
+
/// identical workspace identity. Adding a wrapper (not calling
|
|
444
|
+
/// `socket_name_for_workspace` directly outside this file) keeps the
|
|
445
|
+
/// existing internal API stable.
|
|
446
|
+
pub fn workspace_short_hash_pub(workspace: &Path) -> String {
|
|
447
|
+
// The tmux short-socket name is `ta-<12 hex>`; the workspace-hash
|
|
448
|
+
// used by the ConPTY workspace identity is the raw 12-hex portion
|
|
449
|
+
// (no `ta-` prefix), which is what the shim/pipe name derivation
|
|
450
|
+
// expects. Reuse the exact same hash so drift is impossible.
|
|
451
|
+
let name = socket_name_for_workspace(workspace);
|
|
452
|
+
name.strip_prefix("ta-").unwrap_or(&name).to_string()
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/// Public re-export of the crate-private `runtime_tmux_endpoint_from_state`
|
|
456
|
+
/// probe. `transport_factory` uses this to spot the legacy tmux endpoint
|
|
457
|
+
/// in `state` without duplicating the field-precedence logic.
|
|
458
|
+
pub fn runtime_tmux_endpoint_from_state_pub(
|
|
459
|
+
state: Option<&serde_json::Value>,
|
|
460
|
+
) -> Option<(String, &'static str)> {
|
|
461
|
+
runtime_tmux_endpoint_from_state(state).map(|(endpoint, source)| {
|
|
462
|
+
let src = match source {
|
|
463
|
+
RuntimeTmuxEndpointSource::StateTmuxEndpoint => "state.tmux_endpoint",
|
|
464
|
+
RuntimeTmuxEndpointSource::StateTmuxSocket => "state.tmux_socket",
|
|
465
|
+
RuntimeTmuxEndpointSource::WorkspaceFallback => "workspace_fallback",
|
|
466
|
+
};
|
|
467
|
+
(endpoint.to_string(), src)
|
|
468
|
+
})
|
|
469
|
+
}
|
|
470
|
+
|
|
433
471
|
pub(crate) fn socket_name_for_workspace(workspace: &Path) -> String {
|
|
434
472
|
let canonical = workspace
|
|
435
473
|
.canonicalize()
|
|
@@ -458,10 +496,24 @@ pub(crate) fn socket_path_for_name(socket_name: &str) -> Option<PathBuf> {
|
|
|
458
496
|
if let Some(existing) = existing_socket_path_for_name(socket_name) {
|
|
459
497
|
return Some(existing);
|
|
460
498
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
499
|
+
// Batch 1: tmux uses `/tmp/tmux-<uid>/` on Unix; on Windows this
|
|
500
|
+
// helper is dead code (Command::new "tmux" fails at spawn time
|
|
501
|
+
// before this path is dereferenced). N38 typed unsupported honored
|
|
502
|
+
// at the shellout boundary.
|
|
503
|
+
#[cfg(unix)]
|
|
504
|
+
{
|
|
505
|
+
let uid = unsafe { libc::geteuid() };
|
|
506
|
+
let default_root = PathBuf::from(format!("/tmp/tmux-{uid}"));
|
|
507
|
+
let default_root = default_root.canonicalize().unwrap_or(default_root);
|
|
508
|
+
Some(default_root.join(socket_name))
|
|
509
|
+
}
|
|
510
|
+
#[cfg(not(unix))]
|
|
511
|
+
{
|
|
512
|
+
// Windows: tmux socket-root derivation is meaningless. Return
|
|
513
|
+
// None so the caller sees the honest "no such socket" branch.
|
|
514
|
+
let _ = socket_name;
|
|
515
|
+
None
|
|
516
|
+
}
|
|
465
517
|
}
|
|
466
518
|
|
|
467
519
|
fn existing_socket_path_for_name(socket_name: &str) -> Option<PathBuf> {
|
|
@@ -551,14 +603,25 @@ pub(crate) fn attach_commands_for_windows<'a>(
|
|
|
551
603
|
}
|
|
552
604
|
|
|
553
605
|
pub(crate) fn tmux_socket_roots() -> Vec<PathBuf> {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
606
|
+
// Batch 1: `/tmp/tmux-<uid>` root enumeration is Unix-only tmux
|
|
607
|
+
// convention. On Windows return empty so the caller loops zero
|
|
608
|
+
// times (honest "no tmux socket roots" — tmux is not deployed on
|
|
609
|
+
// native Windows without WSL, and WSL is out of scope).
|
|
610
|
+
#[cfg(unix)]
|
|
611
|
+
{
|
|
612
|
+
let uid = unsafe { libc::geteuid() };
|
|
613
|
+
let mut roots = vec![PathBuf::from(format!("/tmp/tmux-{uid}"))];
|
|
614
|
+
if let Some(tmpdir) = std::env::var_os("TMPDIR") {
|
|
615
|
+
roots.push(PathBuf::from(tmpdir).join(format!("tmux-{uid}")));
|
|
616
|
+
}
|
|
617
|
+
roots.sort();
|
|
618
|
+
roots.dedup();
|
|
619
|
+
roots
|
|
620
|
+
}
|
|
621
|
+
#[cfg(not(unix))]
|
|
622
|
+
{
|
|
623
|
+
Vec::new()
|
|
558
624
|
}
|
|
559
|
-
roots.sort();
|
|
560
|
-
roots.dedup();
|
|
561
|
-
roots
|
|
562
625
|
}
|
|
563
626
|
|
|
564
627
|
pub(crate) fn tmux_socket_endpoints() -> Vec<String> {
|
|
@@ -571,7 +634,20 @@ pub(crate) fn tmux_socket_endpoints() -> Vec<String> {
|
|
|
571
634
|
let Ok(file_type) = entry.file_type() else {
|
|
572
635
|
continue;
|
|
573
636
|
};
|
|
574
|
-
|
|
637
|
+
// Batch 1: `FileTypeExt::is_socket` is Unix-only. Dead code
|
|
638
|
+
// on Windows because `tmux_socket_roots()` returns empty
|
|
639
|
+
// there (no /tmp/tmux-<uid> convention). We still cfg the
|
|
640
|
+
// method call so `cargo check --target x86_64-pc-windows-msvc`
|
|
641
|
+
// sees no `std::os::unix::fs::FileTypeExt` reference.
|
|
642
|
+
#[cfg(unix)]
|
|
643
|
+
{
|
|
644
|
+
if !file_type.is_socket() {
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
#[cfg(not(unix))]
|
|
649
|
+
{
|
|
650
|
+
let _ = file_type;
|
|
575
651
|
continue;
|
|
576
652
|
}
|
|
577
653
|
let path = entry.path();
|
|
@@ -2218,5 +2294,10 @@ fn non_empty(raw: &str) -> Option<&str> {
|
|
|
2218
2294
|
}
|
|
2219
2295
|
}
|
|
2220
2296
|
|
|
2221
|
-
|
|
2297
|
+
// 0.5.x Windows portability Batch 5: the `tmux_backend/tests.rs`
|
|
2298
|
+
// module uses `std::os::unix::net::UnixListener` for its mock
|
|
2299
|
+
// runner + verifies Unix-specific socket-root derivation. Since the
|
|
2300
|
+
// tmux backend itself only functions on Unix (design § Route B —
|
|
2301
|
+
// tmux is a Unix concept), the test module stays Unix-only.
|
|
2302
|
+
#[cfg(all(test, unix))]
|
|
2222
2303
|
mod tests;
|