@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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +129 -54
- package/crates/team-agent/src/cli/diagnose.rs +1 -2
- package/crates/team-agent/src/cli/emit.rs +1 -7
- package/crates/team-agent/src/cli/helpers.rs +3 -1
- package/crates/team-agent/src/cli/leader.rs +2 -1
- package/crates/team-agent/src/cli/mod.rs +126 -16
- package/crates/team-agent/src/cli/named_address.rs +14 -5
- package/crates/team-agent/src/cli/profile.rs +19 -7
- package/crates/team-agent/src/cli/send.rs +9 -3
- package/crates/team-agent/src/cli/status.rs +8 -30
- package/crates/team-agent/src/cli/status_port.rs +1341 -1272
- package/crates/team-agent/src/cli/tests/base.rs +738 -660
- package/crates/team-agent/src/cli/tests/compile.rs +45 -18
- package/crates/team-agent/src/cli/tests/divergence.rs +462 -444
- package/crates/team-agent/src/cli/tests/lane_c.rs +365 -282
- package/crates/team-agent/src/cli/tests/leader_watch.rs +356 -329
- package/crates/team-agent/src/cli/tests/main_preserved.rs +672 -564
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +284 -224
- package/crates/team-agent/src/cli/tests/mod.rs +17 -7
- package/crates/team-agent/src/cli/tests/named_address.rs +8 -2
- package/crates/team-agent/src/cli/tests/peer_allow.rs +10 -2
- package/crates/team-agent/src/cli/tests/run_delegation.rs +314 -273
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +26 -15
- package/crates/team-agent/src/cli/tests/status_send.rs +707 -629
- package/crates/team-agent/src/cli/tests/verb_install_skill.rs +20 -4
- package/crates/team-agent/src/cli/tests/verb_profile.rs +46 -17
- package/crates/team-agent/src/cli/tests/verb_validate.rs +15 -3
- package/crates/team-agent/src/codex_app_server.rs +2 -5
- package/crates/team-agent/src/compiler/tests.rs +139 -33
- package/crates/team-agent/src/compiler.rs +55 -22
- package/crates/team-agent/src/conpty/backend.rs +23 -33
- package/crates/team-agent/src/coordinator/backoff.rs +2 -7
- package/crates/team-agent/src/coordinator/conpty_shim.rs +55 -67
- package/crates/team-agent/src/coordinator/health.rs +46 -31
- package/crates/team-agent/src/coordinator/mod.rs +3 -3
- package/crates/team-agent/src/coordinator/orphan.rs +22 -10
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +51 -56
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +55 -19
- package/crates/team-agent/src/coordinator/tests/basics.rs +179 -41
- package/crates/team-agent/src/coordinator/tests/daemon.rs +53 -13
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +78 -19
- package/crates/team-agent/src/coordinator/tests/main_preserved.rs +61 -11
- package/crates/team-agent/src/coordinator/tests/mod.rs +33 -39
- package/crates/team-agent/src/coordinator/tests/spine.rs +52 -12
- package/crates/team-agent/src/coordinator/tests/takeover.rs +73 -15
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +50 -15
- package/crates/team-agent/src/coordinator/tests/watch.rs +74 -20
- package/crates/team-agent/src/db/message_store.rs +138 -30
- package/crates/team-agent/src/db/migration.rs +249 -61
- package/crates/team-agent/src/db/schema.rs +303 -82
- package/crates/team-agent/src/diagnose/comms.rs +9 -2
- package/crates/team-agent/src/diagnose/mod.rs +1 -3
- package/crates/team-agent/src/diagnose/orphans.rs +79 -61
- package/crates/team-agent/src/event_log.rs +70 -16
- package/crates/team-agent/src/layout/manager.rs +15 -4
- package/crates/team-agent/src/layout/mod.rs +4 -4
- package/crates/team-agent/src/layout/overlay.rs +10 -3
- package/crates/team-agent/src/layout/placement.rs +5 -1
- package/crates/team-agent/src/layout/recovery.rs +4 -2
- package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
- package/crates/team-agent/src/layout/sessions.rs +17 -9
- package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
- package/crates/team-agent/src/layout/worker_env.rs +87 -19
- package/crates/team-agent/src/leader/helpers.rs +7 -1
- package/crates/team-agent/src/leader/lease.rs +199 -89
- package/crates/team-agent/src/leader/owner_bind.rs +55 -22
- package/crates/team-agent/src/leader/provider_attribution.rs +25 -6
- package/crates/team-agent/src/leader/rediscover/tests.rs +88 -24
- package/crates/team-agent/src/leader/rediscover.rs +74 -25
- package/crates/team-agent/src/leader/registry.rs +1 -1
- package/crates/team-agent/src/leader/start.rs +75 -54
- package/crates/team-agent/src/leader/takeover.rs +46 -11
- package/crates/team-agent/src/leader/tests/basics.rs +320 -167
- package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
- package/crates/team-agent/src/leader/tests/identity.rs +428 -356
- package/crates/team-agent/src/leader/tests/idle.rs +285 -254
- package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
- package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
- package/crates/team-agent/src/leader/tests/mod.rs +115 -99
- package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
- package/crates/team-agent/src/lib.rs +4 -4
- package/crates/team-agent/src/lifecycle/display.rs +7 -3
- package/crates/team-agent/src/lifecycle/launch.rs +55 -15
- package/crates/team-agent/src/lifecycle/mod.rs +9 -1
- package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
- package/crates/team-agent/src/lifecycle/restart/common.rs +6 -2
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
- package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests.rs +2 -2
- package/crates/team-agent/src/main.rs +4 -4
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
- package/crates/team-agent/src/mcp_server/mod.rs +11 -2
- package/crates/team-agent/src/mcp_server/types.rs +14 -3
- package/crates/team-agent/src/mcp_server/wire.rs +230 -68
- package/crates/team-agent/src/messaging/delivery.rs +20 -18
- package/crates/team-agent/src/messaging/helpers.rs +25 -4
- package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
- package/crates/team-agent/src/messaging/mod.rs +2 -3
- package/crates/team-agent/src/messaging/selftest.rs +46 -26
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
- package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
- package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
- package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
- package/crates/team-agent/src/messaging/trust.rs +25 -2
- package/crates/team-agent/src/messaging/watchers.rs +37 -9
- package/crates/team-agent/src/model/enums.rs +65 -16
- package/crates/team-agent/src/model/ids.rs +12 -3
- package/crates/team-agent/src/model/paths.rs +28 -7
- package/crates/team-agent/src/model/permissions.rs +176 -33
- package/crates/team-agent/src/model/routing.rs +66 -20
- package/crates/team-agent/src/model/spec.rs +365 -69
- package/crates/team-agent/src/model/task_graph.rs +36 -9
- package/crates/team-agent/src/model/yaml/tests.rs +24 -6
- package/crates/team-agent/src/model/yaml.rs +7 -6
- package/crates/team-agent/src/packaging/install.rs +23 -9
- package/crates/team-agent/src/packaging/migrate.rs +5 -7
- package/crates/team-agent/src/packaging/mod.rs +9 -1
- package/crates/team-agent/src/packaging/repair.rs +13 -6
- package/crates/team-agent/src/packaging/tests.rs +63 -16
- package/crates/team-agent/src/packaging/types.rs +22 -7
- package/crates/team-agent/src/platform/argv.rs +4 -1
- package/crates/team-agent/src/platform/file_lock.rs +22 -8
- package/crates/team-agent/src/platform/process.rs +54 -24
- package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
- package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
- package/crates/team-agent/src/provider/classify.rs +127 -42
- package/crates/team-agent/src/provider/faults.rs +17 -5
- package/crates/team-agent/src/provider/helpers.rs +6 -5
- package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
- package/crates/team-agent/src/state/persist.rs +28 -0
- package/crates/team-agent/src/state/repository.rs +8 -3
- package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
- package/crates/team-agent/src/tmux_backend.rs +80 -44
- package/crates/team-agent/src/topology.rs +40 -20
- package/crates/team-agent/src/transport/test_support.rs +20 -23
- package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
- package/crates/team-agent/src/transport/tests/mod.rs +178 -187
- package/crates/team-agent/src/transport/tests/wire.rs +561 -525
- package/crates/team-agent/src/transport.rs +12 -17
- package/crates/team-agent/src/transport_factory.rs +29 -14
- 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(
|
|
156
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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!(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
104
|
-
|
|
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(
|
|
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((
|
|
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((
|
|
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()
|
|
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!(
|
|
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!(
|
|
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(
|
|
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!(
|
|
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!(
|
|
387
|
-
|
|
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!(
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
assert_eq!(
|
|
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
|
|
449
|
-
|
|
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
|
|
462
|
-
|
|
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
|
|
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(
|
|
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!(
|
|
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(
|