Skip to content

Commit d4d30a7

Browse files
m13vclaude
andcommitted
feat: add element verification to open_application and navigate_browser
- Add verify_element_exists, verify_element_not_exists, verify_timeout_ms to OpenApplicationArgs - Add verification fields to NavigateBrowserArgs - Make verify_timeout_ms optional (Option<u64>) across all tools for backwards compatibility - Add post-action verification logic to both open_application and navigate_browser implementations - All verification timeouts default to 2000ms if not specified All tools now have consistent optional verification fields with sensible defaults. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 0f1ca88 commit d4d30a7

File tree

2 files changed

+157
-5
lines changed

2 files changed

+157
-5
lines changed

crates/terminator-mcp-agent/src/server.rs

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2419,7 +2419,7 @@ Note: Curly brace format (e.g., '{Tab}') is more reliable than plain format (e.g
24192419
// POST-ACTION VERIFICATION
24202420
let verify_exists = args.verify_element_exists.clone();
24212421
let verify_not_exists = args.verify_element_not_exists.clone();
2422-
let verify_timeout_ms = args.verify_timeout_ms;
2422+
let verify_timeout_ms = args.verify_timeout_ms.unwrap_or(2000);
24232423

24242424
let skip_verification = verify_exists.is_empty() && verify_not_exists.is_empty();
24252425

@@ -4567,6 +4567,67 @@ Set include_logs: true to capture stdout/stderr output. Default is false for cle
45674567
)
45684568
.await;
45694569

4570+
// POST-ACTION VERIFICATION
4571+
if !args.verify_element_exists.is_empty() || !args.verify_element_not_exists.is_empty() {
4572+
let verify_exists_opt = if args.verify_element_exists.is_empty() {
4573+
None
4574+
} else {
4575+
Some(args.verify_element_exists.as_str())
4576+
};
4577+
let verify_not_exists_opt = if args.verify_element_not_exists.is_empty() {
4578+
None
4579+
} else {
4580+
Some(args.verify_element_not_exists.as_str())
4581+
};
4582+
4583+
match crate::helpers::verify_post_action(
4584+
&self.desktop,
4585+
&ui_element,
4586+
verify_exists_opt,
4587+
verify_not_exists_opt,
4588+
args.verify_timeout_ms.unwrap_or(2000),
4589+
&args.url,
4590+
)
4591+
.await
4592+
{
4593+
Ok(verification_result) => {
4594+
tracing::info!(
4595+
"[navigate_browser] Verification passed: method={}, details={}",
4596+
verification_result.method,
4597+
verification_result.details
4598+
);
4599+
span.set_attribute("verification.passed", "true".to_string());
4600+
span.set_attribute("verification.method", verification_result.method.clone());
4601+
span.set_attribute(
4602+
"verification.elapsed_ms",
4603+
verification_result.elapsed_ms.to_string(),
4604+
);
4605+
4606+
let verification_json = json!({
4607+
"passed": verification_result.passed,
4608+
"method": verification_result.method,
4609+
"details": verification_result.details,
4610+
"elapsed_ms": verification_result.elapsed_ms,
4611+
"timestamp": chrono::Utc::now().to_rfc3339(),
4612+
});
4613+
4614+
if let Some(obj) = result_json.as_object_mut() {
4615+
obj.insert("verification".to_string(), verification_json);
4616+
}
4617+
}
4618+
Err(e) => {
4619+
tracing::error!("[navigate_browser] Verification failed: {}", e);
4620+
span.set_attribute("verification.passed", "false".to_string());
4621+
span.set_status(false, Some("Verification failed"));
4622+
span.end();
4623+
return Err(McpError::internal_error(
4624+
format!("Post-action verification failed: {e}"),
4625+
None,
4626+
));
4627+
}
4628+
}
4629+
}
4630+
45704631
self.restore_window_management(should_restore).await;
45714632

45724633
span.set_status(true, None);
@@ -4660,6 +4721,67 @@ Set include_logs: true to capture stdout/stderr output. Default is false for cle
46604721
}
46614722
}
46624723

4724+
// POST-ACTION VERIFICATION
4725+
if !args.verify_element_exists.is_empty() || !args.verify_element_not_exists.is_empty() {
4726+
let verify_exists_opt = if args.verify_element_exists.is_empty() {
4727+
None
4728+
} else {
4729+
Some(args.verify_element_exists.as_str())
4730+
};
4731+
let verify_not_exists_opt = if args.verify_element_not_exists.is_empty() {
4732+
None
4733+
} else {
4734+
Some(args.verify_element_not_exists.as_str())
4735+
};
4736+
4737+
match crate::helpers::verify_post_action(
4738+
&self.desktop,
4739+
&ui_element,
4740+
verify_exists_opt,
4741+
verify_not_exists_opt,
4742+
args.verify_timeout_ms.unwrap_or(2000),
4743+
&args.app_name,
4744+
)
4745+
.await
4746+
{
4747+
Ok(verification_result) => {
4748+
tracing::info!(
4749+
"[open_application] Verification passed: method={}, details={}",
4750+
verification_result.method,
4751+
verification_result.details
4752+
);
4753+
span.set_attribute("verification.passed", "true".to_string());
4754+
span.set_attribute("verification.method", verification_result.method.clone());
4755+
span.set_attribute(
4756+
"verification.elapsed_ms",
4757+
verification_result.elapsed_ms.to_string(),
4758+
);
4759+
4760+
let verification_json = json!({
4761+
"passed": verification_result.passed,
4762+
"method": verification_result.method,
4763+
"details": verification_result.details,
4764+
"elapsed_ms": verification_result.elapsed_ms,
4765+
"timestamp": chrono::Utc::now().to_rfc3339(),
4766+
});
4767+
4768+
if let Some(obj) = result_json.as_object_mut() {
4769+
obj.insert("verification".to_string(), verification_json);
4770+
}
4771+
}
4772+
Err(e) => {
4773+
tracing::error!("[open_application] Verification failed: {}", e);
4774+
span.set_attribute("verification.passed", "false".to_string());
4775+
span.set_status(false, Some("Verification failed"));
4776+
span.end();
4777+
return Err(McpError::internal_error(
4778+
format!("Post-action verification failed: {e}"),
4779+
None,
4780+
));
4781+
}
4782+
}
4783+
}
4784+
46634785
// Restore windows if we did window management
46644786
self.restore_window_management(should_restore).await;
46654787

crates/terminator-mcp-agent/src/utils.rs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,8 @@ pub struct ActionOptions {
171171
pub verify_element_not_exists: String,
172172

173173
#[schemars(
174-
description = "Timeout in milliseconds for post-action verification. The system will poll until verification passes or timeout is reached. Defaults to 2000ms."
174+
description = "Timeout in milliseconds for post-action verification. The system will poll until verification passes or timeout is reached. Defaults to 2000ms if not specified."
175175
)]
176-
#[serde(default)]
177176
pub verify_timeout_ms: Option<u64>,
178177
}
179178

@@ -596,9 +595,9 @@ pub struct GlobalKeyArgs {
596595
pub verify_element_not_exists: String,
597596

598597
#[schemars(
599-
description = "REQUIRED: Timeout in milliseconds for post-action verification. The system will poll until verification passes or timeout is reached."
598+
description = "Timeout in milliseconds for post-action verification. The system will poll until verification passes or timeout is reached. Defaults to 2000ms if not specified."
600599
)]
601-
pub verify_timeout_ms: u64,
600+
pub verify_timeout_ms: Option<u64>,
602601

603602
#[serde(flatten)]
604603
pub tree: TreeOptions,
@@ -781,6 +780,22 @@ pub struct NavigateBrowserArgs {
781780
description = "Browser process name (e.g., 'chrome', 'msedge', 'firefox'). Will start the browser if not running."
782781
)]
783782
pub process: String,
783+
784+
#[schemars(
785+
description = "REQUIRED: Selector that should exist after navigation completes. Used to verify the page loaded successfully. Use empty string \"\" to skip this check."
786+
)]
787+
pub verify_element_exists: String,
788+
789+
#[schemars(
790+
description = "REQUIRED: Selector that should NOT exist after navigation completes. Use empty string \"\" to skip this check."
791+
)]
792+
pub verify_element_not_exists: String,
793+
794+
#[schemars(
795+
description = "Timeout in milliseconds for post-action verification. The system will poll until verification passes or timeout is reached. Defaults to 2000ms if not specified."
796+
)]
797+
pub verify_timeout_ms: Option<u64>,
798+
784799
#[serde(flatten)]
785800
pub tree: TreeOptions,
786801
#[serde(flatten)]
@@ -820,6 +835,21 @@ pub struct OpenApplicationArgs {
820835
#[schemars(description = "Name of the application to open")]
821836
pub app_name: String,
822837

838+
#[schemars(
839+
description = "REQUIRED: Selector that should exist after the application opens. Used to verify the app loaded successfully (e.g., 'process:notepad|role:Document'). Use empty string \"\" to skip this check."
840+
)]
841+
pub verify_element_exists: String,
842+
843+
#[schemars(
844+
description = "REQUIRED: Selector that should NOT exist after the application opens. Use empty string \"\" to skip this check."
845+
)]
846+
pub verify_element_not_exists: String,
847+
848+
#[schemars(
849+
description = "Timeout in milliseconds for post-action verification. The system will poll until verification passes or timeout is reached. Defaults to 2000ms if not specified."
850+
)]
851+
pub verify_timeout_ms: Option<u64>,
852+
823853
#[serde(flatten)]
824854
pub monitor: MonitorScreenshotOptions,
825855

0 commit comments

Comments
 (0)