@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.
Files changed (55) hide show
  1. package/Cargo.lock +120 -9
  2. package/Cargo.toml +2 -2
  3. package/crates/team-agent/Cargo.toml +21 -0
  4. package/crates/team-agent/src/app_server_test_support.rs +258 -0
  5. package/crates/team-agent/src/cli/adapters.rs +32 -3
  6. package/crates/team-agent/src/cli/attach_app_server_leader.rs +16 -0
  7. package/crates/team-agent/src/cli/diagnose.rs +14 -5
  8. package/crates/team-agent/src/cli/emit.rs +79 -1
  9. package/crates/team-agent/src/cli/mod.rs +190 -80
  10. package/crates/team-agent/src/cli/named_address.rs +111 -0
  11. package/crates/team-agent/src/cli/send.rs +98 -3
  12. package/crates/team-agent/src/cli/status_port.rs +44 -6
  13. package/crates/team-agent/src/cli/tests/named_address.rs +135 -0
  14. package/crates/team-agent/src/cli/tests/run_delegation.rs +2 -0
  15. package/crates/team-agent/src/cli/types.rs +17 -0
  16. package/crates/team-agent/src/codex_app_server.rs +549 -0
  17. package/crates/team-agent/src/conpty/backend.rs +822 -0
  18. package/crates/team-agent/src/conpty/mod.rs +32 -0
  19. package/crates/team-agent/src/coordinator/backoff.rs +132 -7
  20. package/crates/team-agent/src/coordinator/conpty_shim.rs +730 -0
  21. package/crates/team-agent/src/coordinator/health.rs +97 -11
  22. package/crates/team-agent/src/coordinator/mod.rs +8 -0
  23. package/crates/team-agent/src/coordinator/tests/daemon.rs +2 -1
  24. package/crates/team-agent/src/coordinator/tests/tick_core.rs +6 -0
  25. package/crates/team-agent/src/diagnose/orphans.rs +29 -10
  26. package/crates/team-agent/src/leader/lease.rs +144 -7
  27. package/crates/team-agent/src/leader/owner_bind.rs +5 -2
  28. package/crates/team-agent/src/leader/provider_attribution.rs +19 -34
  29. package/crates/team-agent/src/leader/start.rs +18 -5
  30. package/crates/team-agent/src/leader/tests/lease_api.rs +179 -0
  31. package/crates/team-agent/src/lib.rs +36 -0
  32. package/crates/team-agent/src/lifecycle/launch.rs +207 -94
  33. package/crates/team-agent/src/lifecycle/lock.rs +40 -33
  34. package/crates/team-agent/src/lifecycle/restart/agent.rs +10 -18
  35. package/crates/team-agent/src/lifecycle/restart/common.rs +70 -0
  36. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +7 -0
  37. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +7 -0
  38. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +63 -0
  39. package/crates/team-agent/src/mcp_server/wire.rs +6 -7
  40. package/crates/team-agent/src/messaging/delivery.rs +329 -9
  41. package/crates/team-agent/src/messaging/leader_receiver.rs +17 -138
  42. package/crates/team-agent/src/messaging/mod.rs +2 -2
  43. package/crates/team-agent/src/messaging/results.rs +78 -21
  44. package/crates/team-agent/src/messaging/tests/runtime.rs +237 -0
  45. package/crates/team-agent/src/packaging/tests.rs +41 -6
  46. package/crates/team-agent/src/packaging/types.rs +31 -3
  47. package/crates/team-agent/src/platform/argv.rs +324 -0
  48. package/crates/team-agent/src/platform/errors.rs +95 -0
  49. package/crates/team-agent/src/platform/file_lock.rs +418 -0
  50. package/crates/team-agent/src/platform/mod.rs +66 -0
  51. package/crates/team-agent/src/platform/process.rs +555 -0
  52. package/crates/team-agent/src/state/persist.rs +205 -25
  53. package/crates/team-agent/src/tmux_backend.rs +94 -13
  54. package/crates/team-agent/src/transport_factory.rs +751 -0
  55. package/package.json +4 -4
package/Cargo.lock CHANGED
@@ -91,7 +91,16 @@ dependencies = [
91
91
  "js-sys",
92
92
  "num-traits",
93
93
  "wasm-bindgen",
94
- "windows-link",
94
+ "windows-link 0.2.1",
95
+ ]
96
+
97
+ [[package]]
98
+ name = "conpty-transport"
99
+ version = "0.1.0"
100
+ dependencies = [
101
+ "serde",
102
+ "serde_json",
103
+ "windows",
95
104
  ]
96
105
 
97
106
  [[package]]
@@ -244,7 +253,7 @@ dependencies = [
244
253
  "js-sys",
245
254
  "log",
246
255
  "wasm-bindgen",
247
- "windows-core",
256
+ "windows-core 0.62.2",
248
257
  ]
249
258
 
250
259
  [[package]]
@@ -357,7 +366,7 @@ dependencies = [
357
366
  "libc",
358
367
  "redox_syscall",
359
368
  "smallvec",
360
- "windows-link",
369
+ "windows-link 0.2.1",
361
370
  ]
362
371
 
363
372
  [[package]]
@@ -566,10 +575,11 @@ dependencies = [
566
575
 
567
576
  [[package]]
568
577
  name = "team-agent"
569
- version = "0.5.1"
578
+ version = "0.5.3"
570
579
  dependencies = [
571
580
  "anyhow",
572
581
  "chrono",
582
+ "conpty-transport",
573
583
  "libc",
574
584
  "regex",
575
585
  "rusqlite",
@@ -578,6 +588,7 @@ dependencies = [
578
588
  "serial_test",
579
589
  "sha2",
580
590
  "thiserror",
591
+ "windows",
581
592
  ]
582
593
 
583
594
  [[package]]
@@ -669,6 +680,17 @@ dependencies = [
669
680
  "unicode-ident",
670
681
  ]
671
682
 
683
+ [[package]]
684
+ name = "win-conpty-phase0"
685
+ version = "0.1.0"
686
+ dependencies = [
687
+ "anyhow",
688
+ "conpty-transport",
689
+ "serde",
690
+ "serde_json",
691
+ "windows",
692
+ ]
693
+
672
694
  [[package]]
673
695
  name = "winapi"
674
696
  version = "0.3.9"
@@ -691,6 +713,41 @@ version = "0.4.0"
691
713
  source = "registry+https://github.com/rust-lang/crates.io-index"
692
714
  checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
693
715
 
716
+ [[package]]
717
+ name = "windows"
718
+ version = "0.61.3"
719
+ source = "registry+https://github.com/rust-lang/crates.io-index"
720
+ checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
721
+ dependencies = [
722
+ "windows-collections",
723
+ "windows-core 0.61.2",
724
+ "windows-future",
725
+ "windows-link 0.1.3",
726
+ "windows-numerics",
727
+ ]
728
+
729
+ [[package]]
730
+ name = "windows-collections"
731
+ version = "0.2.0"
732
+ source = "registry+https://github.com/rust-lang/crates.io-index"
733
+ checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
734
+ dependencies = [
735
+ "windows-core 0.61.2",
736
+ ]
737
+
738
+ [[package]]
739
+ name = "windows-core"
740
+ version = "0.61.2"
741
+ source = "registry+https://github.com/rust-lang/crates.io-index"
742
+ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
743
+ dependencies = [
744
+ "windows-implement",
745
+ "windows-interface",
746
+ "windows-link 0.1.3",
747
+ "windows-result 0.3.4",
748
+ "windows-strings 0.4.2",
749
+ ]
750
+
694
751
  [[package]]
695
752
  name = "windows-core"
696
753
  version = "0.62.2"
@@ -699,9 +756,20 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
699
756
  dependencies = [
700
757
  "windows-implement",
701
758
  "windows-interface",
702
- "windows-link",
703
- "windows-result",
704
- "windows-strings",
759
+ "windows-link 0.2.1",
760
+ "windows-result 0.4.1",
761
+ "windows-strings 0.5.1",
762
+ ]
763
+
764
+ [[package]]
765
+ name = "windows-future"
766
+ version = "0.2.1"
767
+ source = "registry+https://github.com/rust-lang/crates.io-index"
768
+ checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
769
+ dependencies = [
770
+ "windows-core 0.61.2",
771
+ "windows-link 0.1.3",
772
+ "windows-threading",
705
773
  ]
706
774
 
707
775
  [[package]]
@@ -726,19 +794,53 @@ dependencies = [
726
794
  "syn",
727
795
  ]
728
796
 
797
+ [[package]]
798
+ name = "windows-link"
799
+ version = "0.1.3"
800
+ source = "registry+https://github.com/rust-lang/crates.io-index"
801
+ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
802
+
729
803
  [[package]]
730
804
  name = "windows-link"
731
805
  version = "0.2.1"
732
806
  source = "registry+https://github.com/rust-lang/crates.io-index"
733
807
  checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
734
808
 
809
+ [[package]]
810
+ name = "windows-numerics"
811
+ version = "0.2.0"
812
+ source = "registry+https://github.com/rust-lang/crates.io-index"
813
+ checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
814
+ dependencies = [
815
+ "windows-core 0.61.2",
816
+ "windows-link 0.1.3",
817
+ ]
818
+
819
+ [[package]]
820
+ name = "windows-result"
821
+ version = "0.3.4"
822
+ source = "registry+https://github.com/rust-lang/crates.io-index"
823
+ checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
824
+ dependencies = [
825
+ "windows-link 0.1.3",
826
+ ]
827
+
735
828
  [[package]]
736
829
  name = "windows-result"
737
830
  version = "0.4.1"
738
831
  source = "registry+https://github.com/rust-lang/crates.io-index"
739
832
  checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
740
833
  dependencies = [
741
- "windows-link",
834
+ "windows-link 0.2.1",
835
+ ]
836
+
837
+ [[package]]
838
+ name = "windows-strings"
839
+ version = "0.4.2"
840
+ source = "registry+https://github.com/rust-lang/crates.io-index"
841
+ checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
842
+ dependencies = [
843
+ "windows-link 0.1.3",
742
844
  ]
743
845
 
744
846
  [[package]]
@@ -747,7 +849,16 @@ version = "0.5.1"
747
849
  source = "registry+https://github.com/rust-lang/crates.io-index"
748
850
  checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
749
851
  dependencies = [
750
- "windows-link",
852
+ "windows-link 0.2.1",
853
+ ]
854
+
855
+ [[package]]
856
+ name = "windows-threading"
857
+ version = "0.1.0"
858
+ source = "registry+https://github.com/rust-lang/crates.io-index"
859
+ checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
860
+ dependencies = [
861
+ "windows-link 0.1.3",
751
862
  ]
752
863
 
753
864
  [[package]]
package/Cargo.toml CHANGED
@@ -5,11 +5,11 @@
5
5
  # 等抽成独立成员 crate(届时各自顶上 §10 的 deny 锁 + cargo-deny ban)。
6
6
  [workspace]
7
7
  resolver = "2"
8
- members = ["crates/team-agent"]
8
+ members = ["crates/team-agent", "crates/win-conpty-phase0", "crates/conpty-transport"]
9
9
 
10
10
  [workspace.package]
11
11
  edition = "2021"
12
- version = "0.5.1"
12
+ version = "0.5.3"
13
13
  license = "AGPL-3.0"
14
14
  rust-version = "1.95"
15
15
 
@@ -16,6 +16,7 @@ path = "src/main.rs"
16
16
  # step 2 (model) 已落地: serde/serde_json/thiserror/sha2。
17
17
  # rusqlite 仍留 workspace 调色板,step 3 (db) 再加(且需联网先 fetch)。
18
18
  [dependencies]
19
+ conpty-transport = { path = "../conpty-transport" }
19
20
  anyhow.workspace = true
20
21
  thiserror.workspace = true
21
22
  serde.workspace = true
@@ -26,6 +27,26 @@ chrono.workspace = true
26
27
  libc.workspace = true
27
28
  rusqlite.workspace = true # step 3 db(bundled SQLite,静态链接)
28
29
 
30
+ # 0.5.x Windows portability Batch 2/3: uses the same `windows = "0.61"`
31
+ # crate as `win-conpty-phase0` so the workspace shares a single
32
+ # Windows-API crate + already-locked Cargo.lock version.
33
+ #
34
+ # - Batch 2 (`platform::file_lock`): `Win32_Storage_FileSystem`
35
+ # (`LockFileEx`/`UnlockFileEx`) + `Win32_System_IO::OVERLAPPED`.
36
+ # - Batch 3 (`platform::process`): `Win32_System_Threading`
37
+ # (`OpenProcess`, `TerminateProcess`, `GetExitCodeProcess`,
38
+ # `GetCurrentProcessId`) + `Win32_System_Diagnostics_ToolHelp`
39
+ # (Toolhelp snapshot for parent-pid discovery).
40
+ [target.'cfg(windows)'.dependencies.windows]
41
+ version = "0.61"
42
+ features = [
43
+ "Win32_Foundation",
44
+ "Win32_Storage_FileSystem",
45
+ "Win32_System_IO",
46
+ "Win32_System_Threading",
47
+ "Win32_System_Diagnostics_ToolHelp",
48
+ ]
49
+
29
50
  [dev-dependencies]
30
51
  serial_test = { version = "3", features = ["file_locks"] }
31
52
 
@@ -0,0 +1,258 @@
1
+ use serde_json::{json, Value};
2
+ use std::io::{Read, Write};
3
+ use std::os::unix::net::{UnixListener, UnixStream};
4
+ use std::path::PathBuf;
5
+ use std::sync::{
6
+ atomic::{AtomicBool, Ordering},
7
+ Arc, Mutex,
8
+ };
9
+ use std::thread::JoinHandle;
10
+ use std::time::Duration;
11
+
12
+ #[derive(Clone)]
13
+ pub(crate) struct FakeAppServerScript {
14
+ pub user_agent: Option<String>,
15
+ pub thread_id: String,
16
+ pub session_id: String,
17
+ pub cwd: String,
18
+ pub resume_error: Option<String>,
19
+ pub turn_error: Option<String>,
20
+ pub turn_status: String,
21
+ }
22
+
23
+ impl FakeAppServerScript {
24
+ pub(crate) fn happy(thread_id: &str, session_id: &str, cwd: &str) -> Self {
25
+ Self {
26
+ user_agent: Some("codex-appserver-team-agent-test/0.139.0".to_string()),
27
+ thread_id: thread_id.to_string(),
28
+ session_id: session_id.to_string(),
29
+ cwd: cwd.to_string(),
30
+ resume_error: None,
31
+ turn_error: None,
32
+ turn_status: "inProgress".to_string(),
33
+ }
34
+ }
35
+ }
36
+
37
+ pub(crate) struct FakeAppServer {
38
+ endpoint: String,
39
+ path: PathBuf,
40
+ received_turns: Arc<Mutex<Vec<Value>>>,
41
+ running: Arc<AtomicBool>,
42
+ handle: Option<JoinHandle<()>>,
43
+ }
44
+
45
+ impl FakeAppServer {
46
+ pub(crate) fn start(tag: &str, script: FakeAppServerScript) -> Self {
47
+ static NEXT_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
48
+ let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
49
+ let path = PathBuf::from(format!("/tmp/taas-{}-{id}-{tag}.sock", std::process::id()));
50
+ let _ = std::fs::remove_file(&path);
51
+ let listener = UnixListener::bind(&path).unwrap();
52
+ listener.set_nonblocking(true).unwrap();
53
+ let received_turns = Arc::new(Mutex::new(Vec::new()));
54
+ let running = Arc::new(AtomicBool::new(true));
55
+ let thread_turns = Arc::clone(&received_turns);
56
+ let thread_running = Arc::clone(&running);
57
+ let thread_path = path.clone();
58
+ let handle = std::thread::spawn(move || {
59
+ while thread_running.load(Ordering::Relaxed) {
60
+ match listener.accept() {
61
+ Ok((mut stream, _)) => {
62
+ let _ = handle_connection(&mut stream, &script, &thread_turns);
63
+ }
64
+ Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {
65
+ std::thread::sleep(Duration::from_millis(10));
66
+ }
67
+ Err(_) => break,
68
+ }
69
+ }
70
+ let _ = std::fs::remove_file(thread_path);
71
+ });
72
+ Self {
73
+ endpoint: format!("unix://{}", path.display()),
74
+ path,
75
+ received_turns,
76
+ running,
77
+ handle: Some(handle),
78
+ }
79
+ }
80
+
81
+ pub(crate) fn endpoint(&self) -> &str {
82
+ &self.endpoint
83
+ }
84
+
85
+ pub(crate) fn path(&self) -> &PathBuf {
86
+ &self.path
87
+ }
88
+
89
+ pub(crate) fn received_turns(&self) -> Vec<Value> {
90
+ self.received_turns.lock().unwrap().clone()
91
+ }
92
+ }
93
+
94
+ impl Drop for FakeAppServer {
95
+ fn drop(&mut self) {
96
+ self.running.store(false, Ordering::Relaxed);
97
+ let _ = UnixStream::connect(&self.path);
98
+ if let Some(handle) = self.handle.take() {
99
+ let _ = handle.join();
100
+ }
101
+ let _ = std::fs::remove_file(&self.path);
102
+ }
103
+ }
104
+
105
+ fn handle_connection(
106
+ stream: &mut UnixStream,
107
+ script: &FakeAppServerScript,
108
+ turns: &Arc<Mutex<Vec<Value>>>,
109
+ ) -> std::io::Result<()> {
110
+ stream.set_nonblocking(false)?;
111
+ stream.set_read_timeout(Some(Duration::from_secs(3)))?;
112
+ stream.set_write_timeout(Some(Duration::from_secs(3)))?;
113
+ read_http_upgrade(stream)?;
114
+ stream.write_all(
115
+ b"HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: test\r\n\r\n",
116
+ )?;
117
+ while let Some(frame) = read_ws_text(stream)? {
118
+ let value: Value = match serde_json::from_str(&frame) {
119
+ Ok(value) => value,
120
+ Err(_) => continue,
121
+ };
122
+ let method = value.get("method").and_then(Value::as_str).unwrap_or("");
123
+ let id = value.get("id").cloned().unwrap_or(Value::Null);
124
+ match method {
125
+ "initialize" => {
126
+ let mut result = serde_json::Map::new();
127
+ result.insert("codexHome".to_string(), json!("/tmp/codex-home"));
128
+ result.insert("platformFamily".to_string(), json!("unix"));
129
+ result.insert("platformOs".to_string(), json!("macos"));
130
+ if let Some(user_agent) = &script.user_agent {
131
+ result.insert("userAgent".to_string(), json!(user_agent));
132
+ }
133
+ write_ws_text(
134
+ stream,
135
+ &json!({"id": id, "result": Value::Object(result)}).to_string(),
136
+ )?;
137
+ }
138
+ "initialized" => {}
139
+ "thread/resume" => {
140
+ if let Some(message) = &script.resume_error {
141
+ write_ws_text(
142
+ stream,
143
+ &json!({"id": id, "error": {"code": -32600, "message": message}})
144
+ .to_string(),
145
+ )?;
146
+ } else {
147
+ write_ws_text(
148
+ stream,
149
+ &json!({
150
+ "id": id,
151
+ "result": {
152
+ "cwd": script.cwd,
153
+ "thread": {
154
+ "id": script.thread_id,
155
+ "sessionId": script.session_id,
156
+ "cwd": script.cwd,
157
+ "ephemeral": false
158
+ }
159
+ }
160
+ })
161
+ .to_string(),
162
+ )?;
163
+ }
164
+ }
165
+ "turn/start" => {
166
+ turns.lock().unwrap().push(value.clone());
167
+ if let Some(message) = &script.turn_error {
168
+ write_ws_text(
169
+ stream,
170
+ &json!({"id": id, "error": {"code": -32600, "message": message}})
171
+ .to_string(),
172
+ )?;
173
+ } else {
174
+ write_ws_text(
175
+ stream,
176
+ &json!({
177
+ "id": id,
178
+ "result": {
179
+ "turn": {"id": "turn-test", "status": script.turn_status}
180
+ }
181
+ })
182
+ .to_string(),
183
+ )?;
184
+ }
185
+ }
186
+ _ => {}
187
+ }
188
+ }
189
+ Ok(())
190
+ }
191
+
192
+ fn read_http_upgrade(stream: &mut UnixStream) -> std::io::Result<()> {
193
+ let mut data = Vec::new();
194
+ let mut buf = [0u8; 1];
195
+ while !data.ends_with(b"\r\n\r\n") {
196
+ stream.read_exact(&mut buf)?;
197
+ data.push(buf[0]);
198
+ }
199
+ Ok(())
200
+ }
201
+
202
+ fn read_ws_text(stream: &mut UnixStream) -> std::io::Result<Option<String>> {
203
+ let mut header = [0u8; 2];
204
+ match stream.read_exact(&mut header) {
205
+ Ok(()) => {}
206
+ Err(err)
207
+ if matches!(
208
+ err.kind(),
209
+ std::io::ErrorKind::UnexpectedEof
210
+ | std::io::ErrorKind::WouldBlock
211
+ | std::io::ErrorKind::TimedOut
212
+ ) =>
213
+ {
214
+ return Ok(None);
215
+ }
216
+ Err(err) => return Err(err),
217
+ }
218
+ let opcode = header[0] & 0x0f;
219
+ if opcode == 0x8 {
220
+ return Ok(None);
221
+ }
222
+ let masked = header[1] & 0x80 != 0;
223
+ let mut len = u64::from(header[1] & 0x7f);
224
+ if len == 126 {
225
+ let mut ext = [0u8; 2];
226
+ stream.read_exact(&mut ext)?;
227
+ len = u64::from(u16::from_be_bytes(ext));
228
+ } else if len == 127 {
229
+ let mut ext = [0u8; 8];
230
+ stream.read_exact(&mut ext)?;
231
+ len = u64::from_be_bytes(ext);
232
+ }
233
+ let mut mask = [0u8; 4];
234
+ if masked {
235
+ stream.read_exact(&mut mask)?;
236
+ }
237
+ let mut payload = vec![0u8; usize::try_from(len).unwrap_or(0)];
238
+ stream.read_exact(&mut payload)?;
239
+ if masked {
240
+ for (idx, byte) in payload.iter_mut().enumerate() {
241
+ *byte ^= mask[idx % 4];
242
+ }
243
+ }
244
+ Ok(Some(String::from_utf8_lossy(&payload).to_string()))
245
+ }
246
+
247
+ fn write_ws_text(stream: &mut UnixStream, text: &str) -> std::io::Result<()> {
248
+ let payload = text.as_bytes();
249
+ let mut frame = vec![0x81];
250
+ if payload.len() < 126 {
251
+ frame.push(payload.len() as u8);
252
+ } else {
253
+ frame.push(126);
254
+ frame.extend_from_slice(&(payload.len() as u16).to_be_bytes());
255
+ }
256
+ frame.extend_from_slice(payload);
257
+ stream.write_all(&frame)
258
+ }
@@ -107,6 +107,7 @@ pub fn cmd_quick_start(args: &QuickStartArgs) -> Result<CmdResult, CliError> {
107
107
  args.team_id.as_deref(),
108
108
  args.yes,
109
109
  !args.no_display,
110
+ args.backend.as_deref(),
110
111
  )?;
111
112
  let readiness = value.get("readiness").and_then(Value::as_object);
112
113
  let all_resumable_have_session = readiness
@@ -716,8 +717,22 @@ pub fn cmd_diagnose(args: &DiagnoseArgs) -> Result<CmdResult, CliError> {
716
717
  .map_err(|e| CliError::Runtime(e.to_string()))?;
717
718
  let state = selected.state;
718
719
  let event_log = selected.run_workspace.join(".team").join("logs").join("events.jsonl");
719
- let backend = crate::tmux_backend::TmuxBackend::for_workspace(&selected.run_workspace);
720
- let (issues, suggested_repairs) = diagnose_runtime(&state, &backend);
720
+ // 0.5.x Phase 1d Batch 3: factory-routed diagnose backend so
721
+ // conpty teams get accurate `has_session` / capture from the shim
722
+ // rather than a workspace tmux fallback (which would always miss
723
+ // the ConPTY pane universe).
724
+ let backend: Box<dyn crate::transport::Transport> =
725
+ match crate::transport_factory::resolve_read_only_transport(
726
+ &selected.run_workspace,
727
+ Some(&state),
728
+ crate::transport_factory::TransportPurpose::Diagnose,
729
+ ) {
730
+ Ok(r) => r.backend,
731
+ Err(_) => Box::new(crate::tmux_backend::TmuxBackend::for_workspace(
732
+ &selected.run_workspace,
733
+ )),
734
+ };
735
+ let (issues, suggested_repairs) = diagnose_runtime(&state, backend.as_ref());
721
736
  let ok = issues.as_array().is_some_and(Vec::is_empty);
722
737
  Ok(CmdResult::from_json(
723
738
  json!({
@@ -801,7 +816,21 @@ pub fn cmd_peek(args: &PeekArgs) -> Result<CmdResult, CliError> {
801
816
  let Some((session, window, target)) = agent_pane_id(&state, &args.agent, agent_state) else {
802
817
  return Ok(peek_unavailable(&args.agent, args.json));
803
818
  };
804
- let backend = crate::tmux_backend::TmuxBackend::for_workspace(&args.workspace);
819
+ // 0.5.x Phase 1d Batch 3: factory-routed peek backend so conpty
820
+ // teams peek from the shim scrollback rather than a workspace
821
+ // tmux capture that would always be empty. Tmux teams keep
822
+ // byte-equivalent behavior.
823
+ let backend: Box<dyn crate::transport::Transport> =
824
+ match crate::transport_factory::resolve_read_only_transport(
825
+ &args.workspace,
826
+ Some(&state),
827
+ crate::transport_factory::TransportPurpose::Status,
828
+ ) {
829
+ Ok(r) => r.backend,
830
+ Err(_) => Box::new(crate::tmux_backend::TmuxBackend::for_workspace(
831
+ &args.workspace,
832
+ )),
833
+ };
805
834
  let windows = backend
806
835
  .list_windows(&crate::transport::SessionName::new(session.clone()))
807
836
  .map_err(|e| CliError::Runtime(e.to_string()))?;
@@ -0,0 +1,16 @@
1
+ use super::*;
2
+
3
+ /// `team-agent attach-app-server-leader` writes a typed codex_app_server
4
+ /// leader_receiver through the leader lease primitive.
5
+ pub fn cmd_attach_app_server_leader(
6
+ args: &AttachAppServerLeaderArgs,
7
+ ) -> Result<CmdResult, CliError> {
8
+ let value = crate::leader::attach_app_server_leader(
9
+ &args.workspace,
10
+ args.team.as_deref(),
11
+ &args.socket,
12
+ &args.thread_id,
13
+ )
14
+ .map_err(|error| CliError::Runtime(error.to_string()))?;
15
+ Ok(CmdResult::from_json(value, args.json))
16
+ }
@@ -483,14 +483,23 @@ fn inject_tmux_session_present(workspace: &std::path::Path, state: &mut Value) {
483
483
  return;
484
484
  };
485
485
  let session_name_owned = session_name.to_string();
486
- let selection = crate::tmux_backend::tmux_backend_for_runtime_state_or_workspace(
486
+ // 0.5.x Phase 1d Batch 3: factory-routed read path. conpty state
487
+ // hits the shim; tmux state hits the persisted endpoint (byte-
488
+ // equivalent). Diagnose no longer reports `tmux_session_missing`
489
+ // for a conpty team just because it can't find a tmux session on
490
+ // the workspace socket (design §Batch 3 Verification anchor).
491
+ let resolved = crate::transport_factory::resolve_read_only_transport(
487
492
  workspace,
488
493
  Some(state),
494
+ crate::transport_factory::TransportPurpose::Diagnose,
489
495
  );
490
- let present = selection
491
- .backend
492
- .has_session(&crate::transport::SessionName::new(session_name_owned))
493
- .unwrap_or(false);
496
+ let present = match resolved {
497
+ Ok(r) => r
498
+ .backend
499
+ .has_session(&crate::transport::SessionName::new(session_name_owned))
500
+ .unwrap_or(false),
501
+ Err(_) => false,
502
+ };
494
503
  if let Value::Object(map) = state {
495
504
  map.insert("tmux_session_present".to_string(), Value::Bool(present));
496
505
  }