@team-agent/installer 0.5.77 → 0.5.78

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 (83) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +161 -29
  4. package/crates/team-agent/src/cli/diagnose.rs +333 -68
  5. package/crates/team-agent/src/cli/emit.rs +24 -8
  6. package/crates/team-agent/src/cli/mod.rs +543 -98
  7. package/crates/team-agent/src/cli/models.rs +362 -32
  8. package/crates/team-agent/src/cli/named_address.rs +19 -5
  9. package/crates/team-agent/src/cli/profile.rs +35 -4
  10. package/crates/team-agent/src/cli/spec.rs +7 -2
  11. package/crates/team-agent/src/cli/status_port/compact.rs +12 -8
  12. package/crates/team-agent/src/cli/status_port/snapshot.rs +15 -12
  13. package/crates/team-agent/src/cli/status_port/store.rs +1 -1
  14. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +62 -8
  15. package/crates/team-agent/src/cli/tests/named_address.rs +70 -0
  16. package/crates/team-agent/src/cli/tests/verb_profile.rs +81 -6
  17. package/crates/team-agent/src/cli/types.rs +1 -0
  18. package/crates/team-agent/src/leader/lease.rs +492 -33
  19. package/crates/team-agent/src/leader/owner_bind.rs +40 -12
  20. package/crates/team-agent/src/leader/rediscover/tests.rs +65 -0
  21. package/crates/team-agent/src/leader/rediscover.rs +36 -6
  22. package/crates/team-agent/src/leader/start.rs +151 -14
  23. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +15 -9
  24. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +28 -227
  25. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +0 -18
  26. package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +94 -227
  27. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +59 -10
  28. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +65 -18
  29. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +1449 -282
  30. package/crates/team-agent/src/lifecycle/launch/readiness.rs +349 -4
  31. package/crates/team-agent/src/lifecycle/launch/spawn.rs +11 -11
  32. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +35 -3
  33. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +16 -0
  34. package/crates/team-agent/src/lifecycle/launch.rs +30 -7
  35. package/crates/team-agent/src/lifecycle/profile_launch.rs +37 -3
  36. package/crates/team-agent/src/lifecycle/restart/agent.rs +112 -100
  37. package/crates/team-agent/src/lifecycle/restart/common.rs +82 -52
  38. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +996 -66
  39. package/crates/team-agent/src/lifecycle/restart.rs +2 -2
  40. package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +3 -0
  41. package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +15 -0
  42. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +5 -0
  43. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +11 -13
  44. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +119 -4
  45. package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +12 -0
  46. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +22 -0
  47. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +12 -0
  48. package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +265 -198
  49. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +14 -30
  50. package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +3 -0
  51. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +2 -5
  52. package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +10 -0
  53. package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +58 -35
  54. package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +10 -0
  55. package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +9 -0
  56. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +3 -2
  57. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +0 -168
  58. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +145 -212
  59. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +16 -2
  60. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +54 -9
  61. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +78 -0
  62. package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +3 -0
  63. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +51 -10
  64. package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +50 -7
  65. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +137 -0
  66. package/crates/team-agent/src/lifecycle/types.rs +8 -0
  67. package/crates/team-agent/src/mcp_server/wire.rs +140 -11
  68. package/crates/team-agent/src/messaging/delivery.rs +67 -8
  69. package/crates/team-agent/src/messaging/leader_channel.rs +56 -0
  70. package/crates/team-agent/src/messaging/tests/runtime.rs +10 -2
  71. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +2 -5
  72. package/crates/team-agent/src/state/persist.rs +97 -1
  73. package/crates/team-agent/src/state/repository.rs +29 -0
  74. package/crates/team-agent/src/test_support/models_timeout_fixture.rs +43 -21
  75. package/crates/team-agent/src/tmux_backend/tests.rs +121 -4
  76. package/crates/team-agent/src/tmux_backend.rs +208 -118
  77. package/crates/team-agent/src/transport.rs +72 -0
  78. package/crates/team-agent/src/transport_factory.rs +53 -0
  79. package/package.json +4 -5
  80. package/skills/team-agent/SKILL.md +45 -17
  81. package/skills/team-agent/command-coverage.json +4 -29
  82. package/npm/check-skill-links.mjs +0 -88
  83. package/skills/team-agent/references/team-agent-operator.md +0 -672
@@ -1,16 +1,33 @@
1
1
  use std::fs::OpenOptions;
2
- use std::io::{Read, Write};
3
- use std::os::unix::net::{UnixListener, UnixStream};
2
+ use std::io::Write;
4
3
  use std::path::{Path, PathBuf};
5
4
  use std::process::{Command, Stdio};
6
- use std::time::Duration;
5
+ use std::time::{Duration, Instant};
7
6
 
8
7
  const CHILD_FLAG: &str = "--team-agent-models-timeout-child";
9
8
  const LIST_MODELS_FLAG: &str = "--list-models";
10
9
  const RECEIPT_ENV: &str = "TEAM_AGENT_MODELS_TIMEOUT_RECEIPT";
10
+ const STAGE_RECEIPT_ENV: &str = "TEAM_AGENT_MODELS_TIMEOUT_STAGE_RECEIPT";
11
11
  const MODE_ENV: &str = "TEAM_AGENT_MODELS_TIMEOUT_MODE";
12
- const READY_SOCKET_ENV: &str = "TEAM_AGENT_MODELS_TIMEOUT_READY_SOCKET";
13
12
  const STDOUT_MARKER: &[u8] = b"__team_agent_models_timeout_descendant_stdout_v1__\n";
13
+ const PARENT_HANDOFF_TIMEOUT: Duration = Duration::from_secs(1);
14
+
15
+ // Stage values are UNIX epoch milliseconds so the test process and helper use
16
+ // one directly comparable clock. Stage writes are best-effort diagnostics and
17
+ // never change the fixture's production-facing exit semantics.
18
+ fn append_stage(key: &str) {
19
+ let Some(path) = std::env::var_os(STAGE_RECEIPT_ENV) else {
20
+ return;
21
+ };
22
+ let Some(milliseconds) = std::time::SystemTime::now()
23
+ .duration_since(std::time::UNIX_EPOCH)
24
+ .ok()
25
+ .map(|duration| duration.as_millis())
26
+ else {
27
+ return;
28
+ };
29
+ let _ = append_receipt(Path::new(&path), key, milliseconds);
30
+ }
14
31
 
15
32
  fn receipt_path() -> PathBuf {
16
33
  std::env::var_os(RECEIPT_ENV)
@@ -27,14 +44,29 @@ fn append_receipt(path: &Path, key: &str, value: impl std::fmt::Display) -> Resu
27
44
  writeln!(receipt, "{key}={value}").map_err(|_| ())
28
45
  }
29
46
 
47
+ fn wait_for_parent_handoff(receipt: &Path) {
48
+ let expected = format!("child_pid={}", std::process::id());
49
+ let deadline = Instant::now() + PARENT_HANDOFF_TIMEOUT;
50
+ loop {
51
+ if std::fs::read_to_string(receipt)
52
+ .ok()
53
+ .is_some_and(|text| text.lines().any(|line| line == expected))
54
+ {
55
+ return;
56
+ }
57
+ if Instant::now() >= deadline {
58
+ std::process::exit(5);
59
+ }
60
+ std::thread::sleep(Duration::from_millis(1));
61
+ }
62
+ }
63
+
30
64
  fn child_main(args: &[String]) {
31
65
  if args.len() != 1 || args[0] != CHILD_FLAG {
32
66
  std::process::exit(5);
33
67
  }
34
68
  let receipt = receipt_path();
35
- let socket = std::env::var_os(READY_SOCKET_ENV)
36
- .map(PathBuf::from)
37
- .unwrap_or_else(|| std::process::exit(5));
69
+ wait_for_parent_handoff(&receipt);
38
70
 
39
71
  let mut stdout = std::io::stdout();
40
72
  if stdout.write_all(STDOUT_MARKER).is_err() || stdout.flush().is_err() {
@@ -48,10 +80,6 @@ fn child_main(args: &[String]) {
48
80
  std::process::exit(5);
49
81
  }
50
82
 
51
- let mut ready = UnixStream::connect(socket).unwrap_or_else(|_| std::process::exit(5));
52
- if ready.write_all(b"1").is_err() {
53
- std::process::exit(5);
54
- }
55
83
  std::thread::sleep(Duration::from_secs(2));
56
84
  }
57
85
 
@@ -86,10 +114,8 @@ fn main() {
86
114
  Ok(_) | Err(std::env::VarError::NotUnicode(_)) => std::process::exit(2),
87
115
  }
88
116
 
117
+ append_stage("parent_entry_ms");
89
118
  let receipt = receipt_path();
90
- let socket = PathBuf::from(format!("{}.sock", receipt.display()));
91
- let _ = std::fs::remove_file(&socket);
92
- let listener = UnixListener::bind(&socket).unwrap_or_else(|_| std::process::exit(4));
93
119
  if append_receipt(&receipt, "parent_started_once", 1).is_err()
94
120
  || append_receipt(&receipt, "parent_argv_exact", 1).is_err()
95
121
  || append_receipt(&receipt, "parent_pid", std::process::id()).is_err()
@@ -99,24 +125,20 @@ fn main() {
99
125
  }
100
126
 
101
127
  let executable = std::env::current_exe().unwrap_or_else(|_| std::process::exit(4));
128
+ append_stage("parent_before_child_spawn_ms");
102
129
  let child = Command::new(executable)
103
130
  .arg(CHILD_FLAG)
104
131
  .env(RECEIPT_ENV, &receipt)
105
- .env(READY_SOCKET_ENV, &socket)
106
132
  .stdin(Stdio::null())
107
133
  .stdout(Stdio::inherit())
108
134
  .stderr(Stdio::null())
109
135
  .spawn()
110
136
  .unwrap_or_else(|_| std::process::exit(4));
111
- let (mut ready, _) = listener.accept().unwrap_or_else(|_| std::process::exit(4));
112
- let mut byte = [0_u8; 1];
113
- if ready.read_exact(&mut byte).is_err() || byte != [b'1'] {
114
- std::process::exit(4);
115
- }
137
+ append_stage("parent_after_child_spawn_ms");
116
138
  if append_receipt(&receipt, "child_pid", child.id()).is_err() {
117
139
  std::process::exit(4);
118
140
  }
119
- let _ = std::fs::remove_file(&socket);
141
+ append_stage("parent_before_exit_ms");
120
142
  // The parent exits successfully without waiting; the child retains the
121
143
  // inherited stdout pipe and self-exits after the real reader deadline.
122
144
  }
@@ -28,10 +28,10 @@ use crate::layout::worker_env::{
28
28
  use crate::model::enums::{PaneLiveness, Provider};
29
29
  use crate::transport::{
30
30
  command_basename, normalize_capture, tmux_capture_argv, tmux_query_argv, tmux_send_keys_argv,
31
- tmux_spawn_argv, tmux_submit_key_name, AttachOutcome, CaptureRange, CaptureSampleOutcome,
32
- InjectPayload, InjectStage, InjectVerification, InputSurfaceProbe, Key, PaneField, PaneId,
33
- QueryOutcome, SessionName, SetEnvOutcome, SubmitVerification, Target, Transport,
34
- TransportError, TurnVerification, WindowName,
31
+ tmux_spawn_argv, tmux_submit_key_name, AttachOutcome, BusySignalKind, CaptureRange,
32
+ CaptureSampleOutcome, InjectPayload, InjectStage, InjectVerification, InputSurfaceProbe, Key,
33
+ PaneField, PaneId, QueryOutcome, SessionName, SetEnvOutcome, SubmitConsumptionReason,
34
+ SubmitVerification, Target, Transport, TransportError, TurnVerification, WindowName,
35
35
  };
36
36
 
37
37
  type RecordedArgv = Arc<Mutex<Vec<Vec<String>>>>;
@@ -1051,6 +1051,15 @@ fn inject_text_runs_buffer_paste_submit_sequence_and_reports_submit() {
1051
1051
  SubmitVerification::EnterSentWithoutPlaceholderCheck
1052
1052
  );
1053
1053
  assert_eq!(report.turn_verification, TurnVerification::NotYetObserved);
1054
+ let diagnostics = report.submit_diagnostics.expect("diagnostics");
1055
+ assert_eq!(
1056
+ diagnostics.consumption_reason,
1057
+ SubmitConsumptionReason::NoMarker
1058
+ );
1059
+ assert_eq!(diagnostics.busy_signal_kind, None);
1060
+ assert_eq!(diagnostics.current_marker_in_bottom_15, None);
1061
+ assert_eq!(diagnostics.paste_identity_in_composer, None);
1062
+ assert_eq!(diagnostics.consumption_from_capture_result, None);
1054
1063
  }
1055
1064
 
1056
1065
  #[test]
@@ -1409,10 +1418,18 @@ fn e46_post_submit_matched_token_without_scroll_is_unverified() {
1409
1418
  "token still in composer and no busy must report 没开跑, not unknown/started"
1410
1419
  );
1411
1420
  let diagnostics = report.submit_diagnostics.expect("diagnostics");
1421
+ assert_eq!(diagnostics.busy_signal_kind, None);
1422
+ assert_eq!(diagnostics.current_marker_in_bottom_15, Some(true));
1423
+ assert_eq!(diagnostics.paste_identity_in_composer, None);
1424
+ assert_eq!(diagnostics.consumption_from_capture_result, Some(false));
1412
1425
  assert!(
1413
1426
  diagnostics.attempts_detail.iter().any(|obs| obs.matched),
1414
1427
  "the unverified verdict must still record that paste landed"
1415
1428
  );
1429
+ assert_eq!(
1430
+ diagnostics.consumption_reason,
1431
+ SubmitConsumptionReason::Unverified
1432
+ );
1416
1433
  }
1417
1434
 
1418
1435
  #[test]
@@ -1452,6 +1469,56 @@ fn e46_unconsumed_token_with_live_busy_state_is_treated_as_processing() {
1452
1469
  "busy-state capture should be recorded in attempts_detail: {:?}",
1453
1470
  diagnostics.attempts_detail
1454
1471
  );
1472
+ assert_eq!(
1473
+ diagnostics.consumption_reason,
1474
+ SubmitConsumptionReason::FallbackBusy
1475
+ );
1476
+ assert_eq!(diagnostics.before_busy_signal_kind, None);
1477
+ assert_eq!(diagnostics.busy_signal_kind, Some(BusySignalKind::Working));
1478
+ assert_eq!(diagnostics.busy_line_from_bottom, Some(0));
1479
+ assert_eq!(diagnostics.current_marker_in_bottom_15, Some(true));
1480
+ assert_eq!(diagnostics.paste_identity_in_composer, None);
1481
+ assert_eq!(diagnostics.consumption_from_capture_result, Some(false));
1482
+ }
1483
+
1484
+ #[test]
1485
+ fn e46_capture_error_clears_previous_consumption_observation() {
1486
+ let token_text = "Team Agent message from leader:\n\nhi\n\n[team-agent-token:msg_capture_error]";
1487
+ let busy_tail = format!("{token_text}\n● Working (1s · esc to interrupt)\n");
1488
+ let visible = ok(token_text);
1489
+ let queued = vec![
1490
+ MockResp::Out(ok("")), // set-buffer
1491
+ MockResp::Out(ok("")), // paste-buffer
1492
+ MockResp::Out(ok("")), // delete-buffer
1493
+ MockResp::Out(visible.clone()), // pasted-content prompt: continue
1494
+ MockResp::Out(visible), // pre-submit token visibility
1495
+ MockResp::Out(ok("")), // Escape
1496
+ MockResp::Out(ok("")), // Enter
1497
+ MockResp::Out(ok(&busy_tail)), // first post-Enter capture: busy
1498
+ // Default capture error forces the next poll/fallback/resend reads to fail.
1499
+ ];
1500
+ let (be, _rec) = backend_with(MockResp::Io(std::io::ErrorKind::Other), queued);
1501
+ let report = be
1502
+ .inject(
1503
+ &Target::Pane(PaneId::new("%7")),
1504
+ &InjectPayload::Text(token_text.to_string()),
1505
+ Key::Enter,
1506
+ true,
1507
+ )
1508
+ .expect("inject runs through capture failure");
1509
+ let diagnostics = report.submit_diagnostics.expect("diagnostics");
1510
+ assert_eq!(
1511
+ report.submit_verification,
1512
+ SubmitVerification::SubmitConsumptionUnverified
1513
+ );
1514
+ assert_eq!(diagnostics.consumption_reason, SubmitConsumptionReason::Unverified);
1515
+ assert!(diagnostics.capture_err_count > 0);
1516
+ assert_eq!(diagnostics.before_busy_signal_kind, None);
1517
+ assert_eq!(diagnostics.busy_signal_kind, None);
1518
+ assert_eq!(diagnostics.busy_line_from_bottom, None);
1519
+ assert_eq!(diagnostics.current_marker_in_bottom_15, None);
1520
+ assert_eq!(diagnostics.paste_identity_in_composer, None);
1521
+ assert_eq!(diagnostics.consumption_from_capture_result, None);
1455
1522
  }
1456
1523
 
1457
1524
  /// NeverSeen + 无占位符:空 pane 不得再把「token 不在底部」写成 consumed。
@@ -1521,6 +1588,14 @@ fn e46_inject_text_with_token_consumed_after_enter_keeps_enter_sent_without_plac
1521
1588
  hold (provider_submit_verification_red.rs:113-159). Got {:?}",
1522
1589
  report.submit_verification
1523
1590
  );
1591
+ let diagnostics = report.submit_diagnostics.expect("diagnostics");
1592
+ assert_eq!(
1593
+ diagnostics.consumption_reason,
1594
+ SubmitConsumptionReason::ConsumptionFromCapture
1595
+ );
1596
+ assert_eq!(diagnostics.busy_signal_kind, None);
1597
+ assert_eq!(diagnostics.current_marker_in_bottom_15, Some(false));
1598
+ assert_eq!(diagnostics.consumption_from_capture_result, Some(true));
1524
1599
  }
1525
1600
 
1526
1601
  /// **E46 RED-3 (resend-to-cap, no double-submit)**: when the FIRST Enter
@@ -3289,6 +3364,15 @@ fn cursor_single_enter_busy_transcript_placeholder_does_not_retry() {
3289
3364
  "busy after first Enter is consumption for cursor; got {:?}",
3290
3365
  report.submit_verification
3291
3366
  );
3367
+ let diagnostics = report.submit_diagnostics.expect("diagnostics");
3368
+ assert_eq!(
3369
+ diagnostics.consumption_reason,
3370
+ SubmitConsumptionReason::CursorPollingBusy
3371
+ );
3372
+ assert_eq!(diagnostics.busy_signal_kind, Some(BusySignalKind::Working));
3373
+ assert!(diagnostics.busy_line_from_bottom.is_some());
3374
+ assert!(diagnostics.current_marker_in_bottom_15.is_some());
3375
+ assert!(diagnostics.consumption_from_capture_result.is_some());
3292
3376
  let calls = rec.lock().unwrap().clone();
3293
3377
  assert_eq!(
3294
3378
  count_submit_enters(&calls),
@@ -3570,6 +3654,39 @@ fn grok_fold_worker_text_payload_polls_consumption() {
3570
3654
  );
3571
3655
  }
3572
3656
 
3657
+ #[test]
3658
+ fn provider_busy_signal_match_preserves_category_priority_and_tail_boundary() {
3659
+ let cases = [
3660
+ ("working", BusySignalKind::Working),
3661
+ ("thinking", BusySignalKind::Thinking),
3662
+ ("processing", BusySignalKind::Processing),
3663
+ ("esc to interrupt", BusySignalKind::EscToInterrupt),
3664
+ ("⠋", BusySignalKind::SpinnerGlyph),
3665
+ ("working ⠋", BusySignalKind::Working),
3666
+ ];
3667
+ for (text, expected) in cases {
3668
+ let matched = super::provider_busy_signal_match(text).expect("busy match");
3669
+ assert_eq!(matched.kind, expected, "text={text:?}");
3670
+ assert_eq!(matched.line_from_bottom, 0, "text={text:?}");
3671
+ assert!(super::provider_busy_signal_in_tail(text));
3672
+ }
3673
+
3674
+ let inside_bottom_15 = std::iter::once("working".to_string())
3675
+ .chain((0..14).map(|index| format!("tail-{index}")))
3676
+ .collect::<Vec<_>>()
3677
+ .join("\n");
3678
+ let matched = super::provider_busy_signal_match(&inside_bottom_15).expect("15th line");
3679
+ assert_eq!(matched.kind, BusySignalKind::Working);
3680
+ assert_eq!(matched.line_from_bottom, 14);
3681
+
3682
+ let outside_bottom_15 = std::iter::once("working".to_string())
3683
+ .chain((0..15).map(|index| format!("tail-{index}")))
3684
+ .collect::<Vec<_>>()
3685
+ .join("\n");
3686
+ assert!(super::provider_busy_signal_match(&outside_bottom_15).is_none());
3687
+ assert!(!super::provider_busy_signal_in_tail(&outside_bottom_15));
3688
+ }
3689
+
3573
3690
  const G4_TOKEN: &str = "[team-agent-token:msg_g4]";
3574
3691
 
3575
3692
  #[test]