@team-agent/installer 0.5.105 → 0.5.107

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 (46) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/README.md +5 -3
  4. package/README.zh.md +4 -3
  5. package/crates/team-agent/src/cli/adapters.rs +15 -1
  6. package/crates/team-agent/src/cli/emit.rs +33 -13
  7. package/crates/team-agent/src/cli/mod.rs +148 -21
  8. package/crates/team-agent/src/cli/status_port/brief.rs +62 -3
  9. package/crates/team-agent/src/cli/tests/base.rs +112 -225
  10. package/crates/team-agent/src/cli/tests/run_delegation.rs +4 -2
  11. package/crates/team-agent/src/cli/tests/status_send.rs +2 -2
  12. package/crates/team-agent/src/compiler/tests.rs +46 -59
  13. package/crates/team-agent/src/leader/tests/byte_findings.rs +1 -14
  14. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +57 -173
  15. package/crates/team-agent/src/lifecycle/launch/pi_mcp_extension.js +226 -0
  16. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +0 -37
  17. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +8 -3
  18. package/crates/team-agent/src/lifecycle/restart/agent.rs +44 -0
  19. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +24 -17
  20. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +22 -9
  21. package/crates/team-agent/src/lifecycle/tests/fixtures/pi_native_mcp_host.mjs +71 -0
  22. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +43 -66
  23. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +71 -392
  24. package/crates/team-agent/src/lifecycle/tests/lifecycle_lock.rs +0 -5
  25. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +2 -361
  26. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +1 -16
  27. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +20 -112
  28. package/crates/team-agent/src/lifecycle/tests/pi_fork_session_inheritance_red.rs +1 -12
  29. package/crates/team-agent/src/lifecycle/tests/pi_native_mcp_capability_red.rs +546 -0
  30. package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +6 -2
  31. package/crates/team-agent/src/lifecycle/tests.rs +1 -0
  32. package/crates/team-agent/src/lifecycle/worker_command_context.rs +11 -54
  33. package/crates/team-agent/src/messaging/tests/wave2.rs +2 -302
  34. package/crates/team-agent/src/model/name_similarity.rs +52 -67
  35. package/crates/team-agent/src/model/task_graph.rs +37 -124
  36. package/crates/team-agent/src/model/yaml/tests.rs +46 -165
  37. package/crates/team-agent/src/provider/tests/adapter.rs +0 -20
  38. package/crates/team-agent/src/provider/tests/classify.rs +106 -198
  39. package/crates/team-agent/src/state/projection.rs +15 -0
  40. package/crates/team-agent/src/tmux_backend/tests.rs +315 -501
  41. package/crates/team-agent/src/tmux_backend.rs +110 -0
  42. package/crates/team-agent/src/transport/test_support.rs +35 -0
  43. package/crates/team-agent/src/transport/tests/wire.rs +32 -58
  44. package/crates/team-agent/src/transport.rs +26 -12
  45. package/package.json +4 -4
  46. package/skills/team-agent/references/recovery-runbook.md +2 -2
@@ -74,6 +74,7 @@ use crate::transport::{
74
74
  };
75
75
 
76
76
  pub const PANE_BINDING_NONCE_METADATA_KEY: &str = "TEAM_AGENT_PANE_BINDING_NONCE";
77
+ pub(crate) const KEEPALIVE_WINDOW_NAME: &str = "[team-agent keepalive]";
77
78
  const TMUX_PANE_BINDING_NONCE_OPTION: &str = "@team_agent_pane_binding_nonce";
78
79
  const TMUX_SESSION_OWNER_WORKSPACE_OPTION: &str = "@team_agent_owner_workspace";
79
80
  const TMUX_SESSION_OWNER_TEAM_OPTION: &str = "@team_agent_owner_team";
@@ -3875,6 +3876,26 @@ impl Transport for TmuxBackend {
3875
3876
  Ok(output.stdout.trim().parse::<u8>().ok())
3876
3877
  }
3877
3878
 
3879
+ fn pane_is_dead(&self, pane: &PaneId) -> Result<Option<bool>, TransportError> {
3880
+ let argv = self.tmux_argv(&[
3881
+ "tmux".to_string(),
3882
+ "display-message".to_string(),
3883
+ "-p".to_string(),
3884
+ "-t".to_string(),
3885
+ pane.as_str().to_string(),
3886
+ "#{pane_dead}".to_string(),
3887
+ ]);
3888
+ let output = self.runner.run(&argv)?;
3889
+ if !output.success {
3890
+ return Ok(None);
3891
+ }
3892
+ Ok(match output.stdout.trim() {
3893
+ "1" => Some(true),
3894
+ "0" => Some(false),
3895
+ _ => None,
3896
+ })
3897
+ }
3898
+
3878
3899
  fn liveness(&self, pane: &PaneId) -> Result<PaneLiveness, TransportError> {
3879
3900
  let argv = self.tmux_argv(&[
3880
3901
  "tmux".to_string(),
@@ -4087,6 +4108,95 @@ impl Transport for TmuxBackend {
4087
4108
  self.run_ok(&argv)
4088
4109
  }
4089
4110
 
4111
+ fn preserve_session_before_stop(
4112
+ &self,
4113
+ session: &SessionName,
4114
+ targets: &[Target],
4115
+ ) -> Result<(), TransportError> {
4116
+ let list_argv = self.tmux_argv(&[
4117
+ "tmux".to_string(),
4118
+ "list-panes".to_string(),
4119
+ "-s".to_string(),
4120
+ "-t".to_string(),
4121
+ format!("={}", session.as_str()),
4122
+ "-F".to_string(),
4123
+ "#{pane_id}\t#{window_name}".to_string(),
4124
+ ]);
4125
+ let output = self.runner.run(&list_argv)?;
4126
+ if !output.success {
4127
+ return Err(subprocess_error(list_argv, output));
4128
+ }
4129
+ for line in output.stdout.lines() {
4130
+ let Some((pane, window)) = line.split_once('\t') else {
4131
+ return Err(TransportError::Subprocess {
4132
+ argv: list_argv,
4133
+ code: output.code,
4134
+ stderr: "tmux returned an invalid session pane listing".to_string(),
4135
+ });
4136
+ };
4137
+ if !targets.iter().any(|target| match target {
4138
+ Target::Pane(id) => id.as_str() == pane,
4139
+ Target::SessionWindow {
4140
+ session: owner,
4141
+ window: name,
4142
+ } => owner == session && name.as_str() == window,
4143
+ }) {
4144
+ return Ok(());
4145
+ }
4146
+ }
4147
+ // An empty listing cannot prove that this is the last worker.
4148
+ if output.stdout.trim().is_empty() {
4149
+ return Err(TransportError::TargetNotFound {
4150
+ target: session.as_str().to_string(),
4151
+ });
4152
+ }
4153
+ // Use a separate, non-worker window: remain-on-exit does not protect a
4154
+ // pane explicitly deleted by kill-pane. Configure by the new pane ID,
4155
+ // not a window name that could resolve to another same-named window.
4156
+ let spawn_argv = tmux_spawn_argv(
4157
+ &SessionName::new(format!("={}", session.as_str())),
4158
+ &WindowName::new(KEEPALIVE_WINDOW_NAME),
4159
+ "exec sleep 86400",
4160
+ false,
4161
+ );
4162
+ let spawned = self.run_spawn(&spawn_argv)?;
4163
+ let pane = spawned.stdout.trim();
4164
+ if !pane.starts_with('%')
4165
+ || pane.len() == 1
4166
+ || !pane[1..].chars().all(|ch| ch.is_ascii_digit())
4167
+ {
4168
+ return Err(TransportError::Subprocess {
4169
+ argv: self.tmux_argv(&spawn_argv),
4170
+ code: spawned.code,
4171
+ stderr: "tmux keepalive spawn returned no valid pane id".to_string(),
4172
+ });
4173
+ }
4174
+ let result = self
4175
+ .run_ok(&[
4176
+ "tmux".to_string(),
4177
+ "set-window-option".to_string(),
4178
+ "-t".to_string(),
4179
+ pane.to_string(),
4180
+ "remain-on-exit".to_string(),
4181
+ "on".to_string(),
4182
+ ])
4183
+ .and_then(|()| {
4184
+ self.run_ok(&[
4185
+ "tmux".to_string(),
4186
+ "respawn-pane".to_string(),
4187
+ "-k".to_string(),
4188
+ "-t".to_string(),
4189
+ pane.to_string(),
4190
+ "exit 0".to_string(),
4191
+ ])
4192
+ });
4193
+ if result.is_err() {
4194
+ // The worker has not been killed yet; remove only our new anchor.
4195
+ let _ = self.kill_pane(&PaneId::new(pane));
4196
+ }
4197
+ result
4198
+ }
4199
+
4090
4200
  fn kill_server(&self) -> Result<(), TransportError> {
4091
4201
  TmuxBackend::kill_server(self);
4092
4202
  Ok(())
@@ -29,6 +29,7 @@ struct OfflineState {
29
29
  target_snapshots: VecDeque<Vec<PaneInfo>>,
30
30
  windows: Vec<WindowName>,
31
31
  pane_presence: BTreeMap<String, bool>,
32
+ dead_panes: BTreeMap<String, bool>,
32
33
  spawn_failures: BTreeMap<String, String>,
33
34
  spawned_panes_addressable: bool,
34
35
  liveness: BTreeMap<String, PaneLiveness>,
@@ -67,6 +68,7 @@ impl Default for OfflineState {
67
68
  target_snapshots: VecDeque::new(),
68
69
  windows: Vec::new(),
69
70
  pane_presence: BTreeMap::new(),
71
+ dead_panes: BTreeMap::new(),
70
72
  spawn_failures: BTreeMap::new(),
71
73
  spawned_panes_addressable: true,
72
74
  liveness: BTreeMap::new(),
@@ -144,6 +146,13 @@ impl OfflineTransport {
144
146
  self
145
147
  }
146
148
 
149
+ pub fn with_pane_dead(self, pane: impl Into<String>, dead: bool) -> Self {
150
+ self.with_state(|state| {
151
+ state.dead_panes.insert(pane.into(), dead);
152
+ });
153
+ self
154
+ }
155
+
147
156
  pub fn with_spawn_failure(self, window: impl Into<String>, error: impl Into<String>) -> Self {
148
157
  self.with_state(|state| {
149
158
  state.spawn_failures.insert(window.into(), error.into());
@@ -523,6 +532,13 @@ impl Transport for OfflineTransport {
523
532
  Ok(self.with_state(|state| state.pane_current_commands.get(&pane_id).cloned()))
524
533
  }
525
534
 
535
+ fn pane_is_dead(&self, pane: &PaneId) -> Result<Option<bool>, TransportError> {
536
+ Ok(self.with_state(|state| {
537
+ state.calls.push("pane_is_dead");
538
+ state.dead_panes.get(pane.as_str()).copied()
539
+ }))
540
+ }
541
+
526
542
  fn liveness(&self, pane: &PaneId) -> Result<PaneLiveness, TransportError> {
527
543
  Ok(self.with_state(|state| {
528
544
  state.calls.push("liveness");
@@ -582,6 +598,25 @@ impl Transport for OfflineTransport {
582
598
  Ok(self.with_state(|state| state.session_owner.clone()))
583
599
  }
584
600
 
601
+ fn set_session_owner_with_generation(
602
+ &self,
603
+ _session: &SessionName,
604
+ workspace: &Path,
605
+ team: &str,
606
+ generation: &str,
607
+ ) -> Result<(), TransportError> {
608
+ self.with_state(|state| {
609
+ state.calls.push("set_session_owner_with_generation");
610
+ state.session_owner = Some(super::SessionOwner {
611
+ workspace: workspace.canonicalize().unwrap_or_else(|_| workspace.to_path_buf())
612
+ .to_string_lossy().to_string(),
613
+ team: team.to_string(),
614
+ generation: generation.to_string(),
615
+ });
616
+ });
617
+ Ok(())
618
+ }
619
+
585
620
  fn list_windows(&self, _session: &SessionName) -> Result<Vec<WindowName>, TransportError> {
586
621
  Ok(self.with_state(|state| {
587
622
  state.calls.push("list_windows");
@@ -195,32 +195,6 @@ fn session_window_name_transparent_bytes() {
195
195
  // contracts-rust-native §2: spawn 返回的稳定 Target,后续 inject/capture 命中同进程。
196
196
  // ════════════════════════════════════════════════════════════════════════
197
197
 
198
- #[test]
199
- fn target_pane_and_session_window_are_distinct_addressings() {
200
- // 两种合法寻址不可混传;同名 PaneId vs SessionWindow 不相等。
201
- let p = Target::Pane(PaneId::new("%7"));
202
- let sw = Target::SessionWindow {
203
- session: SessionName::new("team-sess"),
204
- window: WindowName::new("%7"),
205
- };
206
- assert_ne!(p, sw);
207
- // Pane 寻址按 PaneId 字节相等。
208
- assert_eq!(
209
- Target::Pane(PaneId::new("%7")),
210
- Target::Pane(PaneId::new("%7"))
211
- );
212
- // SessionWindow 按 (session,window) 对相等;任一不同即不等。
213
- let a = Target::SessionWindow {
214
- session: SessionName::new("s"),
215
- window: WindowName::new("w"),
216
- };
217
- let b = Target::SessionWindow {
218
- session: SessionName::new("s"),
219
- window: WindowName::new("w2"),
220
- };
221
- assert_ne!(a, b);
222
- }
223
-
224
198
  #[test]
225
199
  fn spawn_first_returns_stable_addressable_target_then_reachable() {
226
200
  // contracts-rust-native: spawn_first 返回稳定可寻址 Target,且交回的 pane_id 能
@@ -331,16 +305,6 @@ fn spawn_into_then_list_targets_enumerates_it() {
331
305
  // 会卡 trust prompt)。golden /tmp/transport_golden.py: empty_inject_*。
332
306
  // ════════════════════════════════════════════════════════════════════════
333
307
 
334
- #[test]
335
- fn inject_payload_empty_is_typed_distinct_from_empty_text() {
336
- // 类型上把空文本与含字符文本分流(InjectPayload::Empty != Text("..."))。
337
- assert_ne!(InjectPayload::Empty, InjectPayload::Text(String::new()));
338
- assert_eq!(
339
- InjectPayload::Text("hi".into()),
340
- InjectPayload::Text("hi".into())
341
- );
342
- }
343
-
344
308
  #[test]
345
309
  fn inject_empty_payload_reports_empty_text_send_keys_and_turn_not_required() {
346
310
  // golden empty_inject_report:
@@ -366,16 +330,45 @@ fn inject_empty_payload_reports_empty_text_send_keys_and_turn_not_required() {
366
330
  assert_eq!(report.attempts, 1);
367
331
  }
368
332
 
333
+ #[cfg(unix)]
334
+ #[test]
335
+ #[ignore = "requires native tmux 3.5+ and python3; owns an isolated socket/session"]
336
+ fn submit_key_protocol_native_tmux_is_return_in_legacy_and_extended_modes() {
337
+ let fixture = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
338
+ .join("tests/fixtures/submit_key_protocol_probe.py");
339
+ let output = std::process::Command::new("python3")
340
+ .arg(fixture)
341
+ .args([
342
+ "--submit-key",
343
+ crate::transport::tmux_submit_key_name(Key::Enter),
344
+ ])
345
+ .output()
346
+ .expect("run isolated raw-PTY submit diagnostic");
347
+ assert!(
348
+ output.status.success(),
349
+ "production submit key is not Return: stdout={} stderr={}",
350
+ String::from_utf8_lossy(&output.stdout),
351
+ String::from_utf8_lossy(&output.stderr),
352
+ );
353
+ assert!(String::from_utf8_lossy(&output.stdout)
354
+ .contains("ProductionSubmitReturn=verified-three-modes"));
355
+ }
356
+
369
357
  #[test]
370
358
  fn empty_inject_is_single_direct_send_keys_never_buffer() {
371
359
  // 命令构造 golden(STEP-9):空文本禁走 buffer,只发一条 send-keys 提交键
372
360
  // (tmux_io.py:42 —— tmux 拒空 buffer 会卡 trust prompt)。
373
- // golden empty_inject_calls 仅 1 条:send-keys -t %7 C-m(锁定阳性 CR)。
361
+ // Return 与 Ctrl-M 只在 legacy 模式等价;提交必须保持逻辑 Enter。
374
362
  assert_eq!(
375
363
  tmux_empty_inject_argv(&PaneId::new("%7"), Key::Enter),
376
- vec!["tmux", "send-keys", "-t", "%7", "C-m"]
364
+ vec!["tmux", "send-keys", "-t", "%7", "Enter"]
377
365
  );
378
- assert_eq!(crate::transport::tmux_submit_key_name(Key::Enter), "C-m");
366
+ assert_eq!(
367
+ crate::transport::tmux_send_submit_argv(&PaneId::new("%7"), Key::Enter),
368
+ vec!["tmux", "send-keys", "-t", "%7", "Enter"]
369
+ );
370
+ assert_eq!(crate::transport::tmux_submit_key_name(Key::Enter), "Enter");
371
+ assert_eq!(crate::transport::tmux_submit_key_name(Key::Down), "Down");
379
372
  }
380
373
 
381
374
  #[test]
@@ -479,18 +472,6 @@ fn p2_inject_large_text_switches_to_load_buffer_stdin_at_16k() {
479
472
  // contracts-rust-native test_key_enum_translation_parity / cancel_mode_noop。
480
473
  // ════════════════════════════════════════════════════════════════════════
481
474
 
482
- #[test]
483
- fn key_enum_variants_are_distinct_and_copy() {
484
- // Enter/Up/Down/Left/Right/Char/CtrlC/CancelMode 互不相等;Char 携字符。
485
- assert_ne!(Key::Enter, Key::CtrlC);
486
- assert_ne!(Key::Up, Key::Down);
487
- assert_ne!(Key::Char('1'), Key::Char('2'));
488
- assert_eq!(Key::Char('3'), Key::Char('3'));
489
- // Copy:可按值传两次。
490
- let k = Key::Enter;
491
- let _ = (k, k);
492
- }
493
-
494
475
  #[test]
495
476
  fn send_keys_sequence_routes_through_transport() {
496
477
  // send_keys 接受 &[Key];RED via stub。
@@ -552,13 +533,6 @@ fn cancel_mode_is_noop_on_non_tmux_backends() {
552
533
  // tmux `-S -<N>`(Tail)/ `-S -`(Full);DELIVERY_CAPTURE_LINES=40 是默认 tail。
553
534
  // ════════════════════════════════════════════════════════════════════════
554
535
 
555
- #[test]
556
- fn capture_range_tail_and_full_distinct() {
557
- assert_ne!(CaptureRange::Tail(40), CaptureRange::Full);
558
- assert_ne!(CaptureRange::Tail(40), CaptureRange::Tail(30));
559
- assert_eq!(CaptureRange::Tail(40), CaptureRange::Tail(40));
560
- }
561
-
562
536
  #[test]
563
537
  fn capture_tail_records_range_in_captured_text() {
564
538
  // CapturedText 携带它抓取的 range + 出口规范化文本。
@@ -13,7 +13,7 @@
13
13
  //! - name: tmux_inject_text_argv
14
14
  //! what: 文本注入三步 argv(装 buffer→paste→删 buffer)的唯一构造点
15
15
  //! - name: tmux_send_submit_argv
16
- //! what: 提交键 argv 的唯一构造点,Enter 锁定为 C-m
16
+ //! what: 提交键 argv 的唯一构造点,Enter 保持逻辑 Return 而非 Ctrl-M
17
17
  //! - name: tmux_capture_argv
18
18
  //! what: capture-pane argv 的唯一构造点
19
19
  //! - name: tmux_query_argv
@@ -994,6 +994,12 @@ pub trait Transport: Send + Sync {
994
994
  Ok(None)
995
995
  }
996
996
 
997
+ /// Native dead-pane observation. Only Some(true) proves no live pane
998
+ /// process; a missing/unsupported sample must remain unknown.
999
+ fn pane_is_dead(&self, _pane: &PaneId) -> Result<Option<bool>, TransportError> {
1000
+ Ok(None)
1001
+ }
1002
+
997
1003
  /// Cheap direct pane existence check when a backend can prove it. `Ok(None)`
998
1004
  /// preserves the existing Unknown boundary.
999
1005
  fn has_pane(&self, pane: &PaneId) -> Result<Option<bool>, TransportError> {
@@ -1085,6 +1091,17 @@ pub trait Transport: Send + Sync {
1085
1091
 
1086
1092
  // —— LIFECYCLE(SL)——
1087
1093
 
1094
+ /// Before stopping a seat, preserve its session if removing these targets
1095
+ /// would otherwise destroy it. This is not called by team shutdown/restart.
1096
+ /// Backends whose sessions outlive their panes need no special handling.
1097
+ fn preserve_session_before_stop(
1098
+ &self,
1099
+ _session: &SessionName,
1100
+ _targets: &[Target],
1101
+ ) -> Result<(), TransportError> {
1102
+ Ok(())
1103
+ }
1104
+
1088
1105
  fn kill_server(&self) -> Result<(), TransportError> {
1089
1106
  Ok(())
1090
1107
  }
@@ -1199,21 +1216,18 @@ pub fn tmux_send_keys_argv(pane: &PaneId, keys: &[Key]) -> Vec<String> {
1199
1216
 
1200
1217
  /// 投递提交键的 tmux 拼写。
1201
1218
  ///
1202
- /// ledger.p0enter 锁定对:`C-j`/LF 5/5 不成回合,`C-m`/CR 5/5 成回合。
1203
- /// `send-keys Enter` 在 raw PTY 上也是 `0d`,但提交路径只发锁定阳性名
1204
- /// `C-m`,避免落到 LF 族。菜单 `send_keys([Down, Enter])` 不走这里。
1219
+ /// `Enter` 必须保持逻辑 Return,不能替换成控制组合 `C-m`。
1220
+ /// legacy PTY 中两者都是 CR,但启用 extended keys 后 `C-m` 会成为
1221
+ /// CSI-u / modifyOtherKeys Ctrl-M,Pi 不把它当提交;`Enter` 仍发送 CR。
1205
1222
  /// ---
1206
- /// purpose: 投递提交键的 tmux 拼写:Enter 锁定为 C-m(CR),其余键回落通用翻译
1223
+ /// purpose: 投递提交键复用通用逻辑键翻译,避免协议相关的控制键别名
1207
1224
  /// params:
1208
- /// key: 提交键;只有 Enter 有专门拼写
1209
- /// returns: 提交用键名;非 Enter 与 tmux_key_name 同值(含空串情形)
1225
+ /// key: 逻辑提交键
1226
+ /// returns: 与 tmux_key_name 同值(含空串情形)
1210
1227
  /// boundary: 只管拼写,不管何时提交、提交是否成功;不发送、不验证
1211
1228
  /// ---
1212
1229
  pub fn tmux_submit_key_name(key: Key) -> &'static str {
1213
- match key {
1214
- Key::Enter => "C-m",
1215
- other => tmux_key_name(other),
1216
- }
1230
+ tmux_key_name(key)
1217
1231
  }
1218
1232
 
1219
1233
  /// 投递提交:`send-keys -t <pane> <submit-spelling>`。
@@ -1478,7 +1492,7 @@ pub fn tmux_inject_text_argv(
1478
1492
 
1479
1493
  /// 空文本 inject:纯 `send-keys -t <target> <submit_key>`,**禁** buffer 路径
1480
1494
  /// (tmux 拒空 buffer 会卡 trust prompt;tmux_io.py:42)。
1481
- /// `Key::Enter` 在此译成锁定阳性 `C-m`,不是菜单用的 `Enter`。
1495
+ /// `Key::Enter` 保持逻辑 `Enter`,不会在 extended keys 下误发 Ctrl-M。
1482
1496
  /// ---
1483
1497
  /// purpose: 空载荷注入(唤醒/ping)的 argv:只发提交键,不碰 buffer
1484
1498
  /// params:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-agent/installer",
3
- "version": "0.5.105",
3
+ "version": "0.5.107",
4
4
  "description": "npx installer for Team Agent",
5
5
  "keywords": [
6
6
  "codex",
@@ -20,9 +20,9 @@
20
20
  "team-agent-installer": "npm/install.mjs"
21
21
  },
22
22
  "optionalDependencies": {
23
- "@team-agent/cli-darwin-arm64": "0.5.105",
24
- "@team-agent/cli-darwin-x64": "0.5.105",
25
- "@team-agent/cli-linux-x64": "0.5.105"
23
+ "@team-agent/cli-darwin-arm64": "0.5.107",
24
+ "@team-agent/cli-darwin-x64": "0.5.107",
25
+ "@team-agent/cli-linux-x64": "0.5.107"
26
26
  },
27
27
  "scripts": {
28
28
  "postinstall": "node npm/bincheck.mjs",
@@ -54,7 +54,7 @@ Diagnosis:
54
54
 
55
55
  ```bash
56
56
  team-agent status --workspace "$WORKSPACE" --json --detail
57
- team-agent collect --workspace "$WORKSPACE" --json
57
+ team-agent inbox "$AGENT_ID" --workspace "$WORKSPACE" --team "$TEAM" -n 3 --json
58
58
  ```
59
59
 
60
60
  What to look for:
@@ -167,7 +167,7 @@ Validate:
167
167
 
168
168
  ```bash
169
169
  team-agent status --workspace "$WORKSPACE" --json --detail
170
- team-agent collect --workspace "$WORKSPACE" --json
170
+ team-agent inbox "$AGENT_ID" --workspace "$WORKSPACE" --team "$TEAM" -n 3 --json
171
171
  ```
172
172
 
173
173
  The expected result is `status="restarted"` or an equivalent running projection without duplicate-session errors. Do not treat a partial failure on one worker as permission to reset the whole team.