@team-agent/installer 0.5.42 → 0.5.44

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 (156) 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 +126 -16
  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 +51 -56
  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/db/message_store.rs +138 -30
  51. package/crates/team-agent/src/db/migration.rs +249 -61
  52. package/crates/team-agent/src/db/schema.rs +303 -82
  53. package/crates/team-agent/src/diagnose/comms.rs +9 -2
  54. package/crates/team-agent/src/diagnose/mod.rs +1 -3
  55. package/crates/team-agent/src/diagnose/orphans.rs +79 -61
  56. package/crates/team-agent/src/event_log.rs +70 -16
  57. package/crates/team-agent/src/layout/manager.rs +15 -4
  58. package/crates/team-agent/src/layout/mod.rs +4 -4
  59. package/crates/team-agent/src/layout/overlay.rs +10 -3
  60. package/crates/team-agent/src/layout/placement.rs +5 -1
  61. package/crates/team-agent/src/layout/recovery.rs +4 -2
  62. package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
  63. package/crates/team-agent/src/layout/sessions.rs +17 -9
  64. package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
  65. package/crates/team-agent/src/layout/worker_env.rs +87 -19
  66. package/crates/team-agent/src/leader/helpers.rs +7 -1
  67. package/crates/team-agent/src/leader/lease.rs +199 -89
  68. package/crates/team-agent/src/leader/owner_bind.rs +55 -22
  69. package/crates/team-agent/src/leader/provider_attribution.rs +25 -6
  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/registry.rs +1 -1
  73. package/crates/team-agent/src/leader/start.rs +75 -54
  74. package/crates/team-agent/src/leader/takeover.rs +46 -11
  75. package/crates/team-agent/src/leader/tests/basics.rs +320 -167
  76. package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
  77. package/crates/team-agent/src/leader/tests/identity.rs +428 -356
  78. package/crates/team-agent/src/leader/tests/idle.rs +285 -254
  79. package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
  80. package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
  81. package/crates/team-agent/src/leader/tests/mod.rs +115 -99
  82. package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
  83. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
  84. package/crates/team-agent/src/lib.rs +4 -4
  85. package/crates/team-agent/src/lifecycle/display.rs +7 -3
  86. package/crates/team-agent/src/lifecycle/launch.rs +55 -15
  87. package/crates/team-agent/src/lifecycle/mod.rs +9 -1
  88. package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
  89. package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
  90. package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
  91. package/crates/team-agent/src/lifecycle/restart/common.rs +6 -2
  92. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
  93. package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
  94. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
  95. package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
  96. package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
  97. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
  98. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
  99. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
  100. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
  101. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
  102. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +3 -0
  103. package/crates/team-agent/src/lifecycle/tests.rs +2 -2
  104. package/crates/team-agent/src/main.rs +4 -4
  105. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
  106. package/crates/team-agent/src/mcp_server/mod.rs +11 -2
  107. package/crates/team-agent/src/mcp_server/types.rs +14 -3
  108. package/crates/team-agent/src/mcp_server/wire.rs +230 -68
  109. package/crates/team-agent/src/messaging/delivery.rs +20 -18
  110. package/crates/team-agent/src/messaging/helpers.rs +25 -4
  111. package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
  112. package/crates/team-agent/src/messaging/mod.rs +2 -3
  113. package/crates/team-agent/src/messaging/selftest.rs +46 -26
  114. package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
  115. package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
  116. package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
  117. package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
  118. package/crates/team-agent/src/messaging/trust.rs +25 -2
  119. package/crates/team-agent/src/messaging/watchers.rs +37 -9
  120. package/crates/team-agent/src/model/enums.rs +65 -16
  121. package/crates/team-agent/src/model/ids.rs +12 -3
  122. package/crates/team-agent/src/model/paths.rs +28 -7
  123. package/crates/team-agent/src/model/permissions.rs +176 -33
  124. package/crates/team-agent/src/model/routing.rs +66 -20
  125. package/crates/team-agent/src/model/spec.rs +365 -69
  126. package/crates/team-agent/src/model/task_graph.rs +36 -9
  127. package/crates/team-agent/src/model/yaml/tests.rs +24 -6
  128. package/crates/team-agent/src/model/yaml.rs +7 -6
  129. package/crates/team-agent/src/packaging/install.rs +23 -9
  130. package/crates/team-agent/src/packaging/migrate.rs +5 -7
  131. package/crates/team-agent/src/packaging/mod.rs +9 -1
  132. package/crates/team-agent/src/packaging/repair.rs +13 -6
  133. package/crates/team-agent/src/packaging/tests.rs +63 -16
  134. package/crates/team-agent/src/packaging/types.rs +22 -7
  135. package/crates/team-agent/src/platform/argv.rs +4 -1
  136. package/crates/team-agent/src/platform/file_lock.rs +22 -8
  137. package/crates/team-agent/src/platform/process.rs +54 -24
  138. package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
  139. package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
  140. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
  141. package/crates/team-agent/src/provider/classify.rs +127 -42
  142. package/crates/team-agent/src/provider/faults.rs +17 -5
  143. package/crates/team-agent/src/provider/helpers.rs +6 -5
  144. package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
  145. package/crates/team-agent/src/state/persist.rs +28 -0
  146. package/crates/team-agent/src/state/repository.rs +8 -3
  147. package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
  148. package/crates/team-agent/src/tmux_backend.rs +80 -44
  149. package/crates/team-agent/src/topology.rs +40 -20
  150. package/crates/team-agent/src/transport/test_support.rs +20 -23
  151. package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
  152. package/crates/team-agent/src/transport/tests/mod.rs +178 -187
  153. package/crates/team-agent/src/transport/tests/wire.rs +561 -525
  154. package/crates/team-agent/src/transport.rs +12 -17
  155. package/crates/team-agent/src/transport_factory.rs +29 -14
  156. 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,
@@ -101,6 +99,7 @@ mod unix_impl {
101
99
  //! `cli/mod.rs`, `coordinator/backoff.rs`, `mcp_server/wire.rs`,
102
100
  //! `lifecycle/restart/agent.rs`) with zero behavioral drift.
103
101
  use super::*;
102
+ use std::process::Command;
104
103
 
105
104
  pub fn current_parent_pid() -> Option<u32> {
106
105
  // Byte-equivalent to `mcp_server/wire.rs:319` and
@@ -152,8 +151,38 @@ mod unix_impl {
152
151
  Ok(Vec::new())
153
152
  }
154
153
 
155
- pub fn process_tree(_root: u32) -> Result<Vec<u32>, io::Error> {
156
- Ok(Vec::new())
154
+ pub fn process_tree(root: u32) -> Result<Vec<u32>, io::Error> {
155
+ let output = Command::new("ps").args(["-axo", "pid=,ppid="]).output()?;
156
+ if !output.status.success() {
157
+ return Err(io::Error::new(
158
+ io::ErrorKind::Other,
159
+ "ps_parent exited unsuccessfully",
160
+ ));
161
+ }
162
+ let pairs = String::from_utf8_lossy(&output.stdout)
163
+ .lines()
164
+ .filter_map(|line| {
165
+ let mut parts = line.split_whitespace();
166
+ let pid = parts.next()?.parse::<u32>().ok()?;
167
+ let ppid = parts.next()?.parse::<u32>().ok()?;
168
+ Some((pid, ppid))
169
+ })
170
+ .collect::<Vec<_>>();
171
+ let mut out = Vec::new();
172
+ collect_child_pids(root, &pairs, &mut out);
173
+ out.push(root);
174
+ out.sort_unstable();
175
+ out.dedup();
176
+ Ok(out)
177
+ }
178
+
179
+ fn collect_child_pids(parent: u32, pairs: &[(u32, u32)], out: &mut Vec<u32>) {
180
+ for (pid, ppid) in pairs {
181
+ if *ppid == parent && !out.contains(pid) {
182
+ out.push(*pid);
183
+ collect_child_pids(*pid, pairs, out);
184
+ }
185
+ }
157
186
  }
158
187
 
159
188
  /// Send a SIGTERM (`TerminateGraceful`) or SIGKILL
@@ -166,10 +195,7 @@ mod unix_impl {
166
195
  /// downgrade path); the `AlreadyGone` case is preserved when
167
196
  /// `kill()` returns ESRCH (the target was reaped between check
168
197
  /// and send).
169
- pub fn terminate_pid(
170
- pid: u32,
171
- kind: SignalKind,
172
- ) -> Result<TerminationOutcome, io::Error> {
198
+ pub fn terminate_pid(pid: u32, kind: SignalKind) -> Result<TerminationOutcome, io::Error> {
173
199
  let signal = match kind {
174
200
  SignalKind::TerminateGraceful => libc::SIGTERM,
175
201
  SignalKind::TerminateForce => libc::SIGKILL,
@@ -275,12 +301,16 @@ mod windows_impl {
275
301
  while result.is_ok() {
276
302
  if entry.th32ProcessID == my_pid {
277
303
  let ppid = entry.th32ParentProcessID;
278
- unsafe { let _ = windows::Win32::Foundation::CloseHandle(snapshot); };
304
+ unsafe {
305
+ let _ = windows::Win32::Foundation::CloseHandle(snapshot);
306
+ };
279
307
  return Some(ppid);
280
308
  }
281
309
  result = unsafe { Process32NextW(snapshot, &mut entry) };
282
310
  }
283
- unsafe { let _ = windows::Win32::Foundation::CloseHandle(snapshot); };
311
+ unsafe {
312
+ let _ = windows::Win32::Foundation::CloseHandle(snapshot);
313
+ };
284
314
  None
285
315
  }
286
316
 
@@ -327,7 +357,9 @@ mod windows_impl {
327
357
  };
328
358
  let mut exit_code: u32 = 0;
329
359
  let result = unsafe { GetExitCodeProcess(handle, &mut exit_code) };
330
- unsafe { let _ = CloseHandle(handle); };
360
+ unsafe {
361
+ let _ = CloseHandle(handle);
362
+ };
331
363
  result.map_err(|e| io::Error::from_raw_os_error(e.code().0 as i32))?;
332
364
  // `STILL_ACTIVE` (0x103) — the process is still running.
333
365
  if exit_code == STILL_ACTIVE.0 as u32 {
@@ -358,14 +390,9 @@ mod windows_impl {
358
390
  Ok(Vec::new())
359
391
  }
360
392
 
361
- pub fn terminate_pid(
362
- pid: u32,
363
- kind: SignalKind,
364
- ) -> Result<TerminationOutcome, io::Error> {
393
+ pub fn terminate_pid(pid: u32, kind: SignalKind) -> Result<TerminationOutcome, io::Error> {
365
394
  use windows::Win32::Foundation::CloseHandle;
366
- use windows::Win32::System::Threading::{
367
- OpenProcess, TerminateProcess, PROCESS_TERMINATE,
368
- };
395
+ use windows::Win32::System::Threading::{OpenProcess, TerminateProcess, PROCESS_TERMINATE};
369
396
  // CR C-6 anchor: Windows has no SIGTERM analogue for a
370
397
  // non-console child. `TerminateGraceful` cannot be honored;
371
398
  // we still perform the physical `TerminateProcess` (design
@@ -394,7 +421,9 @@ mod windows_impl {
394
421
  }
395
422
  };
396
423
  let result = unsafe { TerminateProcess(handle, 1) };
397
- unsafe { let _ = CloseHandle(handle); };
424
+ unsafe {
425
+ let _ = CloseHandle(handle);
426
+ };
398
427
  result.map_err(|e| io::Error::from_raw_os_error(e.code().0 as i32))?;
399
428
  Ok(match kind {
400
429
  SignalKind::TerminateGraceful => TerminationOutcome::ForceOnly {
@@ -444,7 +473,10 @@ mod tests {
444
473
  // Windows (Toolhelp32 snapshot) return the process's parent
445
474
  // pid. A `None` here on either platform is a regression.
446
475
  let ppid = current_parent_pid();
447
- assert!(ppid.is_some(), "current_parent_pid must return Some on both unix and windows");
476
+ assert!(
477
+ ppid.is_some(),
478
+ "current_parent_pid must return Some on both unix and windows"
479
+ );
448
480
  assert!(ppid.unwrap() > 0);
449
481
  }
450
482
 
@@ -544,9 +576,7 @@ mod tests {
544
576
  // pattern-match without cfg.
545
577
  assert_ne!(SignalKind::TerminateGraceful, SignalKind::TerminateForce);
546
578
  let requested = TerminationOutcome::Requested;
547
- let force_only = TerminationOutcome::ForceOnly {
548
- reason: "test",
549
- };
579
+ let force_only = TerminationOutcome::ForceOnly { reason: "test" };
550
580
  let already_gone = TerminationOutcome::AlreadyGone;
551
581
  assert_ne!(requested, force_only);
552
582
  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(