@team-agent/installer 0.5.41 → 0.5.43

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 (152) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +129 -54
  4. package/crates/team-agent/src/cli/diagnose.rs +1 -2
  5. package/crates/team-agent/src/cli/emit.rs +1 -7
  6. package/crates/team-agent/src/cli/helpers.rs +3 -1
  7. package/crates/team-agent/src/cli/leader.rs +2 -1
  8. package/crates/team-agent/src/cli/mod.rs +27 -7
  9. package/crates/team-agent/src/cli/named_address.rs +14 -5
  10. package/crates/team-agent/src/cli/profile.rs +19 -7
  11. package/crates/team-agent/src/cli/send.rs +9 -3
  12. package/crates/team-agent/src/cli/status.rs +8 -30
  13. package/crates/team-agent/src/cli/status_port.rs +1341 -1272
  14. package/crates/team-agent/src/cli/tests/base.rs +738 -660
  15. package/crates/team-agent/src/cli/tests/compile.rs +45 -18
  16. package/crates/team-agent/src/cli/tests/divergence.rs +462 -444
  17. package/crates/team-agent/src/cli/tests/lane_c.rs +365 -282
  18. package/crates/team-agent/src/cli/tests/leader_watch.rs +356 -329
  19. package/crates/team-agent/src/cli/tests/main_preserved.rs +672 -564
  20. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +284 -224
  21. package/crates/team-agent/src/cli/tests/mod.rs +17 -7
  22. package/crates/team-agent/src/cli/tests/named_address.rs +8 -2
  23. package/crates/team-agent/src/cli/tests/peer_allow.rs +10 -2
  24. package/crates/team-agent/src/cli/tests/run_delegation.rs +314 -273
  25. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +26 -15
  26. package/crates/team-agent/src/cli/tests/status_send.rs +707 -629
  27. package/crates/team-agent/src/cli/tests/verb_install_skill.rs +20 -4
  28. package/crates/team-agent/src/cli/tests/verb_profile.rs +46 -17
  29. package/crates/team-agent/src/cli/tests/verb_validate.rs +15 -3
  30. package/crates/team-agent/src/codex_app_server.rs +2 -5
  31. package/crates/team-agent/src/compiler/tests.rs +139 -33
  32. package/crates/team-agent/src/compiler.rs +55 -22
  33. package/crates/team-agent/src/conpty/backend.rs +23 -33
  34. package/crates/team-agent/src/coordinator/backoff.rs +2 -7
  35. package/crates/team-agent/src/coordinator/conpty_shim.rs +55 -67
  36. package/crates/team-agent/src/coordinator/health.rs +46 -31
  37. package/crates/team-agent/src/coordinator/mod.rs +3 -3
  38. package/crates/team-agent/src/coordinator/orphan.rs +22 -10
  39. package/crates/team-agent/src/coordinator/steps/abnormal.rs +47 -52
  40. package/crates/team-agent/src/coordinator/tests/abnormal.rs +55 -19
  41. package/crates/team-agent/src/coordinator/tests/basics.rs +179 -41
  42. package/crates/team-agent/src/coordinator/tests/daemon.rs +53 -13
  43. package/crates/team-agent/src/coordinator/tests/health_sync.rs +78 -19
  44. package/crates/team-agent/src/coordinator/tests/main_preserved.rs +61 -11
  45. package/crates/team-agent/src/coordinator/tests/mod.rs +33 -39
  46. package/crates/team-agent/src/coordinator/tests/spine.rs +52 -12
  47. package/crates/team-agent/src/coordinator/tests/takeover.rs +73 -15
  48. package/crates/team-agent/src/coordinator/tests/tick_core.rs +50 -15
  49. package/crates/team-agent/src/coordinator/tests/watch.rs +74 -20
  50. package/crates/team-agent/src/coordinator/tick.rs +19 -1
  51. package/crates/team-agent/src/db/message_store.rs +138 -30
  52. package/crates/team-agent/src/db/migration.rs +249 -61
  53. package/crates/team-agent/src/db/schema.rs +303 -82
  54. package/crates/team-agent/src/diagnose/comms.rs +9 -2
  55. package/crates/team-agent/src/diagnose/mod.rs +1 -3
  56. package/crates/team-agent/src/diagnose/orphans.rs +79 -61
  57. package/crates/team-agent/src/event_log.rs +70 -16
  58. package/crates/team-agent/src/layout/manager.rs +15 -4
  59. package/crates/team-agent/src/layout/mod.rs +4 -4
  60. package/crates/team-agent/src/layout/overlay.rs +10 -3
  61. package/crates/team-agent/src/layout/placement.rs +5 -1
  62. package/crates/team-agent/src/layout/recovery.rs +4 -2
  63. package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
  64. package/crates/team-agent/src/layout/sessions.rs +17 -9
  65. package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
  66. package/crates/team-agent/src/layout/worker_env.rs +52 -19
  67. package/crates/team-agent/src/leader/helpers.rs +7 -1
  68. package/crates/team-agent/src/leader/lease.rs +195 -82
  69. package/crates/team-agent/src/leader/owner_bind.rs +55 -22
  70. package/crates/team-agent/src/leader/rediscover/tests.rs +88 -24
  71. package/crates/team-agent/src/leader/rediscover.rs +74 -25
  72. package/crates/team-agent/src/leader/start.rs +75 -54
  73. package/crates/team-agent/src/leader/takeover.rs +46 -11
  74. package/crates/team-agent/src/leader/tests/basics.rs +320 -167
  75. package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
  76. package/crates/team-agent/src/leader/tests/identity.rs +428 -356
  77. package/crates/team-agent/src/leader/tests/idle.rs +285 -254
  78. package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
  79. package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
  80. package/crates/team-agent/src/leader/tests/mod.rs +115 -99
  81. package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
  82. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
  83. package/crates/team-agent/src/lib.rs +4 -4
  84. package/crates/team-agent/src/lifecycle/display.rs +7 -3
  85. package/crates/team-agent/src/lifecycle/launch.rs +6 -1
  86. package/crates/team-agent/src/lifecycle/mod.rs +9 -1
  87. package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
  88. package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
  89. package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
  90. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
  91. package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
  92. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
  93. package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
  94. package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
  95. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
  96. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
  97. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
  98. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
  99. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
  100. package/crates/team-agent/src/lifecycle/tests.rs +2 -2
  101. package/crates/team-agent/src/main.rs +4 -4
  102. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
  103. package/crates/team-agent/src/mcp_server/mod.rs +11 -2
  104. package/crates/team-agent/src/mcp_server/types.rs +14 -3
  105. package/crates/team-agent/src/mcp_server/wire.rs +230 -68
  106. package/crates/team-agent/src/messaging/delivery.rs +20 -18
  107. package/crates/team-agent/src/messaging/helpers.rs +25 -4
  108. package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
  109. package/crates/team-agent/src/messaging/mod.rs +2 -3
  110. package/crates/team-agent/src/messaging/selftest.rs +46 -26
  111. package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
  112. package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
  113. package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
  114. package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
  115. package/crates/team-agent/src/messaging/trust.rs +25 -2
  116. package/crates/team-agent/src/messaging/watchers.rs +37 -9
  117. package/crates/team-agent/src/model/enums.rs +65 -16
  118. package/crates/team-agent/src/model/ids.rs +12 -3
  119. package/crates/team-agent/src/model/paths.rs +28 -7
  120. package/crates/team-agent/src/model/permissions.rs +176 -33
  121. package/crates/team-agent/src/model/routing.rs +66 -20
  122. package/crates/team-agent/src/model/spec.rs +365 -69
  123. package/crates/team-agent/src/model/task_graph.rs +36 -9
  124. package/crates/team-agent/src/model/yaml/tests.rs +24 -6
  125. package/crates/team-agent/src/model/yaml.rs +7 -6
  126. package/crates/team-agent/src/packaging/install.rs +23 -9
  127. package/crates/team-agent/src/packaging/migrate.rs +5 -7
  128. package/crates/team-agent/src/packaging/mod.rs +9 -1
  129. package/crates/team-agent/src/packaging/repair.rs +13 -6
  130. package/crates/team-agent/src/packaging/tests.rs +63 -16
  131. package/crates/team-agent/src/packaging/types.rs +22 -7
  132. package/crates/team-agent/src/platform/argv.rs +4 -1
  133. package/crates/team-agent/src/platform/file_lock.rs +22 -8
  134. package/crates/team-agent/src/platform/process.rs +21 -22
  135. package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
  136. package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
  137. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
  138. package/crates/team-agent/src/provider/classify.rs +127 -42
  139. package/crates/team-agent/src/provider/faults.rs +17 -5
  140. package/crates/team-agent/src/provider/helpers.rs +6 -5
  141. package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
  142. package/crates/team-agent/src/state/repository.rs +27 -14
  143. package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
  144. package/crates/team-agent/src/tmux_backend.rs +80 -44
  145. package/crates/team-agent/src/topology.rs +27 -20
  146. package/crates/team-agent/src/transport/test_support.rs +20 -23
  147. package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
  148. package/crates/team-agent/src/transport/tests/mod.rs +178 -187
  149. package/crates/team-agent/src/transport/tests/wire.rs +561 -525
  150. package/crates/team-agent/src/transport.rs +12 -17
  151. package/crates/team-agent/src/transport_factory.rs +29 -14
  152. package/package.json +4 -4
@@ -53,9 +53,7 @@ pub enum TerminationOutcome {
53
53
  /// Windows downgraded `TerminateGraceful` to `TerminateProcess`
54
54
  /// (no grace period). Includes a machine-readable reason so
55
55
  /// callers can log it in the audit event.
56
- ForceOnly {
57
- reason: &'static str,
58
- },
56
+ ForceOnly { reason: &'static str },
59
57
  /// The pid/group was already gone by the time the call resolved.
60
58
  /// Not an error.
61
59
  AlreadyGone,
@@ -166,10 +164,7 @@ mod unix_impl {
166
164
  /// downgrade path); the `AlreadyGone` case is preserved when
167
165
  /// `kill()` returns ESRCH (the target was reaped between check
168
166
  /// and send).
169
- pub fn terminate_pid(
170
- pid: u32,
171
- kind: SignalKind,
172
- ) -> Result<TerminationOutcome, io::Error> {
167
+ pub fn terminate_pid(pid: u32, kind: SignalKind) -> Result<TerminationOutcome, io::Error> {
173
168
  let signal = match kind {
174
169
  SignalKind::TerminateGraceful => libc::SIGTERM,
175
170
  SignalKind::TerminateForce => libc::SIGKILL,
@@ -275,12 +270,16 @@ mod windows_impl {
275
270
  while result.is_ok() {
276
271
  if entry.th32ProcessID == my_pid {
277
272
  let ppid = entry.th32ParentProcessID;
278
- unsafe { let _ = windows::Win32::Foundation::CloseHandle(snapshot); };
273
+ unsafe {
274
+ let _ = windows::Win32::Foundation::CloseHandle(snapshot);
275
+ };
279
276
  return Some(ppid);
280
277
  }
281
278
  result = unsafe { Process32NextW(snapshot, &mut entry) };
282
279
  }
283
- unsafe { let _ = windows::Win32::Foundation::CloseHandle(snapshot); };
280
+ unsafe {
281
+ let _ = windows::Win32::Foundation::CloseHandle(snapshot);
282
+ };
284
283
  None
285
284
  }
286
285
 
@@ -327,7 +326,9 @@ mod windows_impl {
327
326
  };
328
327
  let mut exit_code: u32 = 0;
329
328
  let result = unsafe { GetExitCodeProcess(handle, &mut exit_code) };
330
- unsafe { let _ = CloseHandle(handle); };
329
+ unsafe {
330
+ let _ = CloseHandle(handle);
331
+ };
331
332
  result.map_err(|e| io::Error::from_raw_os_error(e.code().0 as i32))?;
332
333
  // `STILL_ACTIVE` (0x103) — the process is still running.
333
334
  if exit_code == STILL_ACTIVE.0 as u32 {
@@ -358,14 +359,9 @@ mod windows_impl {
358
359
  Ok(Vec::new())
359
360
  }
360
361
 
361
- pub fn terminate_pid(
362
- pid: u32,
363
- kind: SignalKind,
364
- ) -> Result<TerminationOutcome, io::Error> {
362
+ pub fn terminate_pid(pid: u32, kind: SignalKind) -> Result<TerminationOutcome, io::Error> {
365
363
  use windows::Win32::Foundation::CloseHandle;
366
- use windows::Win32::System::Threading::{
367
- OpenProcess, TerminateProcess, PROCESS_TERMINATE,
368
- };
364
+ use windows::Win32::System::Threading::{OpenProcess, TerminateProcess, PROCESS_TERMINATE};
369
365
  // CR C-6 anchor: Windows has no SIGTERM analogue for a
370
366
  // non-console child. `TerminateGraceful` cannot be honored;
371
367
  // we still perform the physical `TerminateProcess` (design
@@ -394,7 +390,9 @@ mod windows_impl {
394
390
  }
395
391
  };
396
392
  let result = unsafe { TerminateProcess(handle, 1) };
397
- unsafe { let _ = CloseHandle(handle); };
393
+ unsafe {
394
+ let _ = CloseHandle(handle);
395
+ };
398
396
  result.map_err(|e| io::Error::from_raw_os_error(e.code().0 as i32))?;
399
397
  Ok(match kind {
400
398
  SignalKind::TerminateGraceful => TerminationOutcome::ForceOnly {
@@ -444,7 +442,10 @@ mod tests {
444
442
  // Windows (Toolhelp32 snapshot) return the process's parent
445
443
  // pid. A `None` here on either platform is a regression.
446
444
  let ppid = current_parent_pid();
447
- assert!(ppid.is_some(), "current_parent_pid must return Some on both unix and windows");
445
+ assert!(
446
+ ppid.is_some(),
447
+ "current_parent_pid must return Some on both unix and windows"
448
+ );
448
449
  assert!(ppid.unwrap() > 0);
449
450
  }
450
451
 
@@ -544,9 +545,7 @@ mod tests {
544
545
  // pattern-match without cfg.
545
546
  assert_ne!(SignalKind::TerminateGraceful, SignalKind::TerminateForce);
546
547
  let requested = TerminationOutcome::Requested;
547
- let force_only = TerminationOutcome::ForceOnly {
548
- reason: "test",
549
- };
548
+ let force_only = TerminationOutcome::ForceOnly { reason: "test" };
550
549
  let already_gone = TerminationOutcome::AlreadyGone;
551
550
  assert_ne!(requested, force_only);
552
551
  assert_ne!(requested, already_gone);
@@ -8,9 +8,7 @@
8
8
  //! helpers now live under `provider/session_scan/claude.rs`.
9
9
 
10
10
  use crate::model::enums::AuthMode;
11
- use crate::provider::adapter::{
12
- next_session_token, prompt_needs_native_mcp, BasicProviderAdapter,
13
- };
11
+ use crate::provider::adapter::{next_session_token, prompt_needs_native_mcp, BasicProviderAdapter};
14
12
  use crate::provider::{McpConfig, ProviderAdapter, ProviderError};
15
13
 
16
14
  pub(crate) fn claude_launch_command(
@@ -49,7 +49,11 @@ pub fn active_approval_control_index(lines: &[&str]) -> Option<usize> {
49
49
  .rev()
50
50
  .find(|(_, line)| is_control_line(&clean_line(line)))
51
51
  .map(|(idx, _)| idx)?;
52
- if lines.iter().skip(idx + 1).any(|line| !clean_line(line).is_empty()) {
52
+ if lines
53
+ .iter()
54
+ .skip(idx + 1)
55
+ .any(|line| !clean_line(line).is_empty())
56
+ {
53
57
  return None;
54
58
  }
55
59
  Some(idx)
@@ -61,15 +65,39 @@ pub fn extract_approval_prompt(agent_id: &str, capture: &str) -> Option<Approval
61
65
 
62
66
  if let Some((matched_idx, prompt, server, tool)) = explicit_mcp_prompt(&lines, control_idx) {
63
67
  let choices = approval_choices_between(&lines, matched_idx, control_idx);
64
- return Some(approval(agent_id, ApprovalKind::McpTool, prompt, choices, Some(server), Some(tool), None));
68
+ return Some(approval(
69
+ agent_id,
70
+ ApprovalKind::McpTool,
71
+ prompt,
72
+ choices,
73
+ Some(server),
74
+ Some(tool),
75
+ None,
76
+ ));
65
77
  }
66
78
  if let Some((matched_idx, prompt, server, tool)) = short_mcp_prompt(&lines, control_idx) {
67
79
  let choices = approval_choices_between(&lines, matched_idx, control_idx);
68
- return Some(approval(agent_id, ApprovalKind::McpTool, prompt, choices, Some(server), Some(tool), None));
80
+ return Some(approval(
81
+ agent_id,
82
+ ApprovalKind::McpTool,
83
+ prompt,
84
+ choices,
85
+ Some(server),
86
+ Some(tool),
87
+ None,
88
+ ));
69
89
  }
70
90
  if let Some((matched_idx, prompt, command)) = command_prompt(&lines, control_idx) {
71
91
  let choices = approval_choices_between(&lines, matched_idx, control_idx);
72
- return Some(approval(agent_id, ApprovalKind::Command, prompt, choices, None, None, Some(command)));
92
+ return Some(approval(
93
+ agent_id,
94
+ ApprovalKind::Command,
95
+ prompt,
96
+ choices,
97
+ None,
98
+ None,
99
+ Some(command),
100
+ ));
73
101
  }
74
102
  let choices = approval_choices_between(&lines, 0, control_idx);
75
103
  Some(approval(
@@ -84,7 +112,11 @@ pub fn extract_approval_prompt(agent_id: &str, capture: &str) -> Option<Approval
84
112
  }
85
113
 
86
114
  pub fn choose_internal_mcp_approval_choice(prompt: &ApprovalPrompt) -> String {
87
- if prompt.choices.iter().any(|choice| choice == "Allow for this session") {
115
+ if prompt
116
+ .choices
117
+ .iter()
118
+ .any(|choice| choice == "Allow for this session")
119
+ {
88
120
  return "Allow for this session".to_string();
89
121
  }
90
122
  for choice in &prompt.choices {
@@ -100,8 +132,16 @@ pub fn choose_internal_mcp_approval_choice(prompt: &ApprovalPrompt) -> String {
100
132
  "Allow for this session".to_string()
101
133
  }
102
134
 
103
- pub fn approval_choice_keys(prompt: &ApprovalPrompt, capture: &str, target_choice: &str) -> Vec<String> {
104
- let Some(target_index) = prompt.choices.iter().position(|choice| choice == target_choice) else {
135
+ pub fn approval_choice_keys(
136
+ prompt: &ApprovalPrompt,
137
+ capture: &str,
138
+ target_choice: &str,
139
+ ) -> Vec<String> {
140
+ let Some(target_index) = prompt
141
+ .choices
142
+ .iter()
143
+ .position(|choice| choice == target_choice)
144
+ else {
105
145
  return vec!["Down".to_string(), "Enter".to_string()];
106
146
  };
107
147
  let Some(active) = active_choice_index(capture) else {
@@ -145,7 +185,10 @@ fn is_control_line(line: &str) -> bool {
145
185
  || (lower.contains("esc to cancel") && lower.contains("tab to amend"))
146
186
  }
147
187
 
148
- fn explicit_mcp_prompt(lines: &[&str], control_idx: usize) -> Option<(usize, String, String, String)> {
188
+ fn explicit_mcp_prompt(
189
+ lines: &[&str],
190
+ control_idx: usize,
191
+ ) -> Option<(usize, String, String, String)> {
149
192
  for (idx, raw) in lines.iter().enumerate().take(control_idx).rev() {
150
193
  let line = clean_line(raw);
151
194
  let prefix = "Allow the ";
@@ -171,10 +214,20 @@ fn short_mcp_prompt(lines: &[&str], control_idx: usize) -> Option<(usize, String
171
214
  continue;
172
215
  }
173
216
  if let Some(tool) = team_orchestrator_tool(&line, '-') {
174
- return Some((idx, format!("team_orchestrator - {tool}"), "team_orchestrator".to_string(), tool));
217
+ return Some((
218
+ idx,
219
+ format!("team_orchestrator - {tool}"),
220
+ "team_orchestrator".to_string(),
221
+ tool,
222
+ ));
175
223
  }
176
224
  if let Some(tool) = team_orchestrator_tool(&line, '.') {
177
- return Some((idx, format!("team_orchestrator - {tool}"), "team_orchestrator".to_string(), tool));
225
+ return Some((
226
+ idx,
227
+ format!("team_orchestrator - {tool}"),
228
+ "team_orchestrator".to_string(),
229
+ tool,
230
+ ));
178
231
  }
179
232
  }
180
233
  None
@@ -192,7 +245,12 @@ fn team_orchestrator_tool(line: &str, separator: char) -> Option<String> {
192
245
  .chars()
193
246
  .take_while(|ch| ch.is_ascii_alphanumeric() || *ch == '_')
194
247
  .collect();
195
- if tool.is_empty() || !tool.chars().next().is_some_and(|ch| ch.is_ascii_alphabetic() || ch == '_') {
248
+ if tool.is_empty()
249
+ || !tool
250
+ .chars()
251
+ .next()
252
+ .is_some_and(|ch| ch.is_ascii_alphabetic() || ch == '_')
253
+ {
196
254
  None
197
255
  } else {
198
256
  Some(tool)
@@ -300,7 +358,10 @@ mod tests {
300
358
  assert_eq!(prompt.kind, ApprovalKind::McpTool);
301
359
  assert_eq!(prompt.server.as_deref(), Some("team_orchestrator"));
302
360
  assert_eq!(prompt.tool.as_deref(), Some("send_message"));
303
- assert_eq!(prompt.prompt, "Allow the team_orchestrator MCP server to run tool \"send_message\"?");
361
+ assert_eq!(
362
+ prompt.prompt,
363
+ "Allow the team_orchestrator MCP server to run tool \"send_message\"?"
364
+ );
304
365
  assert_eq!(prompt.choices, vec!["Allow for this session", "Deny"]);
305
366
  }
306
367
 
@@ -338,7 +399,10 @@ mod tests {
338
399
  .unwrap();
339
400
  assert_eq!(prompt.kind, ApprovalKind::Command);
340
401
  assert_eq!(prompt.command.as_deref(), Some("Bash(echo hi)"));
341
- assert_eq!(prompt.prompt, "Would you like to run the following command?");
402
+ assert_eq!(
403
+ prompt.prompt,
404
+ "Would you like to run the following command?"
405
+ );
342
406
  }
343
407
 
344
408
  #[test]
@@ -348,7 +412,10 @@ mod tests {
348
412
  None
349
413
  );
350
414
  assert_eq!(
351
- extract_approval_prompt("agent-x", "MCP tool: team_orchestrator - send_message requires approval\n 1. Allow\n"),
415
+ extract_approval_prompt(
416
+ "agent-x",
417
+ "MCP tool: team_orchestrator - send_message requires approval\n 1. Allow\n"
418
+ ),
352
419
  None
353
420
  );
354
421
  }
@@ -381,10 +448,20 @@ mod tests {
381
448
  None,
382
449
  None,
383
450
  );
384
- assert_eq!(choose_internal_mcp_approval_choice(&prompt), "Allow for this session");
451
+ assert_eq!(
452
+ choose_internal_mcp_approval_choice(&prompt),
453
+ "Allow for this session"
454
+ );
385
455
  prompt.choices = vec!["Maybe".to_string()];
386
- assert_eq!(choose_internal_mcp_approval_choice(&prompt), "Allow for this session");
387
- prompt.choices = vec!["Maybe".to_string(), "Yes, and don't ask again for this command".to_string(), "Allow".to_string()];
456
+ assert_eq!(
457
+ choose_internal_mcp_approval_choice(&prompt),
458
+ "Allow for this session"
459
+ );
460
+ prompt.choices = vec![
461
+ "Maybe".to_string(),
462
+ "Yes, and don't ask again for this command".to_string(),
463
+ "Allow".to_string(),
464
+ ];
388
465
  assert_eq!(
389
466
  choose_internal_mcp_approval_choice(&prompt),
390
467
  "Yes, and don't ask again for this command"
@@ -402,11 +479,26 @@ mod tests {
402
479
  None,
403
480
  None,
404
481
  );
405
- assert_eq!(approval_choice_keys(&prompt, "❯ 1. Allow\n", "Missing"), vec!["Down", "Enter"]);
406
- assert_eq!(approval_choice_keys(&prompt, " 1. Allow\n 2. Deny\n", "Deny"), vec!["2", "Enter"]);
407
- assert_eq!(approval_choice_keys(&prompt, "❯ 1. Allow\n 2. Deny\n", "Deny"), vec!["Down", "Enter"]);
408
- assert_eq!(approval_choice_keys(&prompt, " 1. Allow\n❯ 2. Deny\n", "Allow"), vec!["Up", "Enter"]);
409
- assert_eq!(approval_choice_keys(&prompt, "❯ 1. Allow\n 2. Deny\n", "Allow"), vec!["Enter"]);
482
+ assert_eq!(
483
+ approval_choice_keys(&prompt, "1. Allow\n", "Missing"),
484
+ vec!["Down", "Enter"]
485
+ );
486
+ assert_eq!(
487
+ approval_choice_keys(&prompt, " 1. Allow\n 2. Deny\n", "Deny"),
488
+ vec!["2", "Enter"]
489
+ );
490
+ assert_eq!(
491
+ approval_choice_keys(&prompt, "❯ 1. Allow\n 2. Deny\n", "Deny"),
492
+ vec!["Down", "Enter"]
493
+ );
494
+ assert_eq!(
495
+ approval_choice_keys(&prompt, " 1. Allow\n❯ 2. Deny\n", "Allow"),
496
+ vec!["Up", "Enter"]
497
+ );
498
+ assert_eq!(
499
+ approval_choice_keys(&prompt, "❯ 1. Allow\n 2. Deny\n", "Allow"),
500
+ vec!["Enter"]
501
+ );
410
502
  }
411
503
 
412
504
  #[test]
@@ -445,8 +537,16 @@ mod tests {
445
537
  None,
446
538
  )
447
539
  .to_ordered_value();
448
- let keys: Vec<&str> = mcp.as_object().unwrap().keys().map(String::as_str).collect();
449
- assert_eq!(keys, vec!["agent_id", "state", "kind", "tool", "prompt", "choices"]);
540
+ let keys: Vec<&str> = mcp
541
+ .as_object()
542
+ .unwrap()
543
+ .keys()
544
+ .map(String::as_str)
545
+ .collect();
546
+ assert_eq!(
547
+ keys,
548
+ vec!["agent_id", "state", "kind", "tool", "prompt", "choices"]
549
+ );
450
550
 
451
551
  let command = approval(
452
552
  "agent-x",
@@ -458,8 +558,16 @@ mod tests {
458
558
  Some("Bash(echo hi)".to_string()),
459
559
  )
460
560
  .to_ordered_value();
461
- let keys: Vec<&str> = command.as_object().unwrap().keys().map(String::as_str).collect();
462
- assert_eq!(keys, vec!["agent_id", "state", "kind", "command", "prompt", "choices"]);
561
+ let keys: Vec<&str> = command
562
+ .as_object()
563
+ .unwrap()
564
+ .keys()
565
+ .map(String::as_str)
566
+ .collect();
567
+ assert_eq!(
568
+ keys,
569
+ vec!["agent_id", "state", "kind", "command", "prompt", "choices"]
570
+ );
463
571
  }
464
572
 
465
573
  #[test]
@@ -13,7 +13,10 @@ pub fn runtime_mcp_tool_allowlisted(tool: &str) -> bool {
13
13
 
14
14
  pub fn runtime_mcp_prompt_allowlisted(prompt: &ApprovalPrompt) -> bool {
15
15
  prompt.server.as_deref() == Some(RUNTIME_MCP_APPROVAL_SERVER)
16
- && prompt.tool.as_deref().is_some_and(runtime_mcp_tool_allowlisted)
16
+ && prompt
17
+ .tool
18
+ .as_deref()
19
+ .is_some_and(runtime_mcp_tool_allowlisted)
17
20
  }
18
21
 
19
22
  /// Issue 1 (Round 3b gate review §6): Team-Agent control-plane MCP tools
@@ -126,7 +129,11 @@ pub fn awaiting_human_confirm_reason(
126
129
  }
127
130
  }
128
131
 
129
- pub fn approval_prompt_fingerprint(team: &str, agent_id: &str, prompt: &ApprovalPrompt) -> ApprovalFingerprint {
132
+ pub fn approval_prompt_fingerprint(
133
+ team: &str,
134
+ agent_id: &str,
135
+ prompt: &ApprovalPrompt,
136
+ ) -> ApprovalFingerprint {
130
137
  let mut hasher = Sha256::new();
131
138
  hasher.update(team.as_bytes());
132
139
  hasher.update([0]);
@@ -161,7 +168,10 @@ pub fn awaiting_human_confirm_dedupe_key(
161
168
  agent_id: &str,
162
169
  fingerprint: &ApprovalFingerprint,
163
170
  ) -> String {
164
- format!("awaiting_human_confirm:{team}:{agent_id}:{}", fingerprint.as_str())
171
+ format!(
172
+ "awaiting_human_confirm:{team}:{agent_id}:{}",
173
+ fingerprint.as_str()
174
+ )
165
175
  }
166
176
 
167
177
  pub fn awaiting_human_confirm_fact(
@@ -3,8 +3,8 @@
3
3
 
4
4
  use super::helpers::parse_jsonl_records;
5
5
  use super::types::{
6
- ClassifyResult, ClassifySource, FactKind, FaultFact, NoPingReason, ProcessLiveness, ProviderError,
7
- RemindResult, Signature, TurnId, TurnState,
6
+ ClassifyResult, ClassifySource, FactKind, FaultFact, NoPingReason, ProcessLiveness,
7
+ ProviderError, RemindResult, Signature, TurnId, TurnState,
8
8
  };
9
9
  use super::Provider;
10
10
 
@@ -116,14 +116,20 @@ fn collect_background_open_ids(value: &serde_json::Value, out: &mut Vec<String>)
116
116
  match value {
117
117
  serde_json::Value::String(s) => {
118
118
  for piece in s.split(MARKER).skip(1) {
119
- let id: String = piece.chars().take_while(|c| !c.is_whitespace() && *c != '\n').collect();
119
+ let id: String = piece
120
+ .chars()
121
+ .take_while(|c| !c.is_whitespace() && *c != '\n')
122
+ .collect();
120
123
  if !id.is_empty() {
121
124
  out.push(id);
122
125
  }
123
126
  }
124
127
  }
125
128
  serde_json::Value::Object(map) => {
126
- if let Some(id) = map.get("backgroundTaskId").and_then(serde_json::Value::as_str) {
129
+ if let Some(id) = map
130
+ .get("backgroundTaskId")
131
+ .and_then(serde_json::Value::as_str)
132
+ {
127
133
  if !id.is_empty() {
128
134
  out.push(id.to_string());
129
135
  }
@@ -147,9 +153,15 @@ fn collect_background_close_ids(value: &serde_json::Value, out: &mut Vec<String>
147
153
  // Form B: <task-notification> wrapper in a string with a status tag.
148
154
  if let serde_json::Value::Object(map) = value {
149
155
  if let Some(bash) = map.get("bashOutput").and_then(serde_json::Value::as_object) {
150
- let status = bash.get("status").and_then(serde_json::Value::as_str).unwrap_or("");
156
+ let status = bash
157
+ .get("status")
158
+ .and_then(serde_json::Value::as_str)
159
+ .unwrap_or("");
151
160
  if matches!(status, "completed" | "failed" | "killed") {
152
- if let Some(id) = bash.get("backgroundTaskId").and_then(serde_json::Value::as_str) {
161
+ if let Some(id) = bash
162
+ .get("backgroundTaskId")
163
+ .and_then(serde_json::Value::as_str)
164
+ {
153
165
  if !id.is_empty() {
154
166
  out.push(id.to_string());
155
167
  }
@@ -236,37 +248,90 @@ pub fn evaluate_takeover_reminder(
236
248
  }
237
249
  }
238
250
  TurnState::Working => {
239
- return Ok(remind(false, NoPingReason::Node(TurnState::Working), Vec::new(), None));
251
+ return Ok(remind(
252
+ false,
253
+ NoPingReason::Node(TurnState::Working),
254
+ Vec::new(),
255
+ None,
256
+ ));
240
257
  }
241
258
  TurnState::BlockedOnHuman => {
242
- return Ok(remind(false, NoPingReason::Node(TurnState::BlockedOnHuman), Vec::new(), None));
259
+ return Ok(remind(
260
+ false,
261
+ NoPingReason::Node(TurnState::BlockedOnHuman),
262
+ Vec::new(),
263
+ None,
264
+ ));
243
265
  }
244
266
  TurnState::Abnormal => {
245
- return Ok(remind(false, NoPingReason::Node(TurnState::Abnormal), Vec::new(), None));
267
+ return Ok(remind(
268
+ false,
269
+ NoPingReason::Node(TurnState::Abnormal),
270
+ Vec::new(),
271
+ None,
272
+ ));
246
273
  }
247
274
  TurnState::Unknown => {
248
- return Ok(remind(false, NoPingReason::Node(TurnState::Unknown), Vec::new(), None));
275
+ return Ok(remind(
276
+ false,
277
+ NoPingReason::Node(TurnState::Unknown),
278
+ Vec::new(),
279
+ None,
280
+ ));
249
281
  }
250
282
  }
251
283
  }
252
284
 
253
285
  let Some(ms) = monitor_state else {
254
- return Ok(remind(false, NoPingReason::NotArmedNoWorkerTurn, Vec::new(), None));
286
+ return Ok(remind(
287
+ false,
288
+ NoPingReason::NotArmedNoWorkerTurn,
289
+ Vec::new(),
290
+ None,
291
+ ));
255
292
  };
256
- if ms.get("opened_worker_turn_since_ack").and_then(serde_json::Value::as_bool) != Some(true) {
257
- return Ok(remind(false, NoPingReason::NotArmedNoWorkerTurn, Vec::new(), None));
293
+ if ms
294
+ .get("opened_worker_turn_since_ack")
295
+ .and_then(serde_json::Value::as_bool)
296
+ != Some(true)
297
+ {
298
+ return Ok(remind(
299
+ false,
300
+ NoPingReason::NotArmedNoWorkerTurn,
301
+ Vec::new(),
302
+ None,
303
+ ));
258
304
  }
259
305
  if ms.get("suppressed").and_then(serde_json::Value::as_bool) == Some(true) {
260
306
  return Ok(remind(false, NoPingReason::Acknowledged, Vec::new(), None));
261
307
  }
262
308
  let Some(all_idle_since) = ms.get("all_idle_since").and_then(serde_json::Value::as_f64) else {
263
- return Ok(remind(false, NoPingReason::NotArmedNoWorkerTurn, Vec::new(), None));
309
+ return Ok(remind(
310
+ false,
311
+ NoPingReason::NotArmedNoWorkerTurn,
312
+ Vec::new(),
313
+ None,
314
+ ));
264
315
  };
265
- if ms.get("pinged_for_episode").and_then(serde_json::Value::as_f64) == Some(all_idle_since) {
266
- return Ok(remind(false, NoPingReason::AlreadyPingedThisEpisode, Vec::new(), None));
316
+ if ms
317
+ .get("pinged_for_episode")
318
+ .and_then(serde_json::Value::as_f64)
319
+ == Some(all_idle_since)
320
+ {
321
+ return Ok(remind(
322
+ false,
323
+ NoPingReason::AlreadyPingedThisEpisode,
324
+ Vec::new(),
325
+ None,
326
+ ));
267
327
  }
268
328
  if now_monotonic - all_idle_since < debounce_seconds {
269
- return Ok(remind(false, NoPingReason::DebounceActive, Vec::new(), None));
329
+ return Ok(remind(
330
+ false,
331
+ NoPingReason::DebounceActive,
332
+ Vec::new(),
333
+ None,
334
+ ));
270
335
  }
271
336
  Ok(remind(
272
337
  true,
@@ -301,7 +366,10 @@ fn classify_result(
301
366
  }
302
367
  }
303
368
 
304
- fn extract_lifecycle_facts(provider: Provider, records: &[serde_json::Value]) -> Vec<LifecycleFact> {
369
+ fn extract_lifecycle_facts(
370
+ provider: Provider,
371
+ records: &[serde_json::Value],
372
+ ) -> Vec<LifecycleFact> {
305
373
  records
306
374
  .iter()
307
375
  .filter_map(|record| match provider {
@@ -328,7 +396,9 @@ fn codex_latest_explicit_error_fact(record: &serde_json::Value) -> Option<FaultF
328
396
  }
329
397
  Some(FaultFact::new(
330
398
  Signature::new("turn_failed"),
331
- turn.get("id").and_then(serde_json::Value::as_str).map(TurnId::new),
399
+ turn.get("id")
400
+ .and_then(serde_json::Value::as_str)
401
+ .map(TurnId::new),
332
402
  FactKind::Failed,
333
403
  ))
334
404
  }
@@ -431,29 +501,43 @@ fn claude_lifecycle_fact(record: &serde_json::Value) -> Option<LifecycleFact> {
431
501
  ));
432
502
  }
433
503
  if record_type == Some("assistant") {
434
- let turn_id = record.get("requestId").and_then(serde_json::Value::as_str).map(TurnId::new);
504
+ let turn_id = record
505
+ .get("requestId")
506
+ .and_then(serde_json::Value::as_str)
507
+ .map(TurnId::new);
435
508
  let Some(stop_reason) = record
436
509
  .get("message")
437
510
  .and_then(|m| m.get("stop_reason"))
438
- .and_then(serde_json::Value::as_str) else {
439
- if record
440
- .get("message")
441
- .and_then(|m| m.get("content"))
442
- .and_then(serde_json::Value::as_array)
443
- .is_some()
444
- {
445
- return Some(lifecycle(
446
- FactKind::TurnOpen,
447
- turn_id,
448
- "assistant_in_flight",
449
- vec!["assistant_in_flight".to_string()],
450
- ));
451
- }
452
- return None;
453
- };
511
+ .and_then(serde_json::Value::as_str)
512
+ else {
513
+ if record
514
+ .get("message")
515
+ .and_then(|m| m.get("content"))
516
+ .and_then(serde_json::Value::as_array)
517
+ .is_some()
518
+ {
519
+ return Some(lifecycle(
520
+ FactKind::TurnOpen,
521
+ turn_id,
522
+ "assistant_in_flight",
523
+ vec!["assistant_in_flight".to_string()],
524
+ ));
525
+ }
526
+ return None;
527
+ };
454
528
  return match stop_reason {
455
- "tool_use" => Some(lifecycle(FactKind::TurnOpen, turn_id, "open_turn", Vec::new())),
456
- "end_turn" => Some(lifecycle(FactKind::TurnComplete, turn_id, "end_turn", Vec::new())),
529
+ "tool_use" => Some(lifecycle(
530
+ FactKind::TurnOpen,
531
+ turn_id,
532
+ "open_turn",
533
+ Vec::new(),
534
+ )),
535
+ "end_turn" => Some(lifecycle(
536
+ FactKind::TurnComplete,
537
+ turn_id,
538
+ "end_turn",
539
+ Vec::new(),
540
+ )),
457
541
  "stop_sequence" => Some(lifecycle(
458
542
  FactKind::TurnComplete,
459
543
  turn_id,
@@ -466,7 +550,10 @@ fn claude_lifecycle_fact(record: &serde_json::Value) -> Option<LifecycleFact> {
466
550
  if record_type == Some("user") && claude_user_interrupted(record) {
467
551
  return Some(lifecycle(
468
552
  FactKind::Interrupted,
469
- record.get("uuid").and_then(serde_json::Value::as_str).map(TurnId::new),
553
+ record
554
+ .get("uuid")
555
+ .and_then(serde_json::Value::as_str)
556
+ .map(TurnId::new),
470
557
  "user_interrupt",
471
558
  vec!["interrupted".to_string()],
472
559
  ));
@@ -585,9 +672,7 @@ fn claude_user_interrupted(record: &serde_json::Value) -> bool {
585
672
  .is_some_and(|items| {
586
673
  items.iter().any(|item| {
587
674
  item.get("type").and_then(serde_json::Value::as_str) == Some("text")
588
- && item
589
- .get("text")
590
- .and_then(serde_json::Value::as_str)
675
+ && item.get("text").and_then(serde_json::Value::as_str)
591
676
  == Some("[Request interrupted by user]")
592
677
  })
593
678
  })