mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
fix(runtime): fix early control return and update lifecycle
This commit is contained in:
@@ -7,6 +7,17 @@
|
||||
"session_id"
|
||||
],
|
||||
"properties": {
|
||||
"completion_criteria": {
|
||||
"description": "Optional explicit success detector. Navigation by itself is not a completion signal; these criteria must match before the tool can auto-complete without the user clicking Done.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/HelpCompletionCriteria"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"prompt": {
|
||||
"description": "Message shown to the user explaining what to do.",
|
||||
"type": "string"
|
||||
@@ -50,6 +61,78 @@
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"HelpCompletionCondition": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"selector_exists": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"selector_missing": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"text_exists": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"text_missing": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"url_contains": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"url_matches": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"HelpCompletionCriteria": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"all": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/HelpCompletionCondition"
|
||||
}
|
||||
},
|
||||
"any": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/HelpCompletionCondition"
|
||||
}
|
||||
},
|
||||
"stable_for_ms": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"HelpTarget": {
|
||||
"description": "One component the user is asked to look at / interact with. Exactly one of `ref` / `selector` should be supplied (mirrors the click/fill convention). When neither matches a live element the overlay still shows the prompt and reports the miss via [`ResolvedTarget`].",
|
||||
"type": "object",
|
||||
|
||||
@@ -7,6 +7,16 @@
|
||||
"tab_id"
|
||||
],
|
||||
"properties": {
|
||||
"completed_by": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/HelpCompletedBy"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"note": {
|
||||
"type": [
|
||||
"string",
|
||||
@@ -31,10 +41,16 @@
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"HelpCompletedBy": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"system"
|
||||
]
|
||||
},
|
||||
"HelpOutcome": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "User completed the step and clicked Continue.",
|
||||
"description": "User completed the step and clicked Done / return control.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"continued"
|
||||
@@ -55,7 +71,14 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "The page navigated while waiting (full reload or SPA URL change).",
|
||||
"description": "Explicit success criteria matched while the user had control.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"completed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "The page navigated while waiting (full reload or SPA URL change). Deprecated: navigation alone should not end a help request.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"navigated"
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
//! Lets an agent pause and ask the human to complete an in-page step
|
||||
//! (captcha, login, confirmation). The extension brings the target tab
|
||||
//! to the foreground, highlights the requested components, and blocks
|
||||
//! until the user clicks Continue / Cancel (or the call times out).
|
||||
//! until the user clicks Done / Cancel, explicit completion criteria match,
|
||||
//! or the call times out.
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -41,6 +42,11 @@ pub struct RequestHelpParams {
|
||||
/// Optional components to scroll to + flash-highlight.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub targets: Option<Vec<HelpTarget>>,
|
||||
/// Optional explicit success detector. Navigation by itself is not a
|
||||
/// completion signal; these criteria must match before the tool can
|
||||
/// auto-complete without the user clicking Done.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub completion_criteria: Option<HelpCompletionCriteria>,
|
||||
/// Hard upper bound on the wait. Defaults to 300000 (5 min),
|
||||
/// enforced daemon-side via the generic tool timeout.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -48,16 +54,46 @@ pub struct RequestHelpParams {
|
||||
pub timeout_ms: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct HelpCompletionCriteria {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub any: Option<Vec<HelpCompletionCondition>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub all: Option<Vec<HelpCompletionCondition>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[schemars(range(min = 0))]
|
||||
pub stable_for_ms: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct HelpCompletionCondition {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub url_contains: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub url_matches: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub selector_exists: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub selector_missing: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub text_exists: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub text_missing: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum HelpOutcome {
|
||||
/// User completed the step and clicked Continue.
|
||||
/// User completed the step and clicked Done / return control.
|
||||
Continued,
|
||||
/// User declined / aborted via the overlay's Cancel button.
|
||||
Cancelled,
|
||||
/// The wait expired before the user acted.
|
||||
TimedOut,
|
||||
/// Explicit success criteria matched while the user had control.
|
||||
Completed,
|
||||
/// The page navigated while waiting (full reload or SPA URL change).
|
||||
/// Deprecated: navigation alone should not end a help request.
|
||||
Navigated,
|
||||
}
|
||||
|
||||
@@ -67,11 +103,18 @@ impl HelpOutcome {
|
||||
Self::Continued => "continued",
|
||||
Self::Cancelled => "cancelled",
|
||||
Self::TimedOut => "timed_out",
|
||||
Self::Completed => "completed",
|
||||
Self::Navigated => "navigated",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum HelpCompletedBy {
|
||||
System,
|
||||
}
|
||||
|
||||
/// Per-target resolution status echoed back so the agent can tell which
|
||||
/// `ref` / `selector` actually matched a live element.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
@@ -87,6 +130,8 @@ pub struct ResolvedTarget {
|
||||
pub struct RequestHelpResult {
|
||||
pub outcome: HelpOutcome,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub completed_by: Option<HelpCompletedBy>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub note: Option<String>,
|
||||
pub tab_id: i64,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -123,6 +168,7 @@ mod tests {
|
||||
prompt: "log in".into(),
|
||||
title: None,
|
||||
targets: None,
|
||||
completion_criteria: None,
|
||||
timeout_ms: None,
|
||||
};
|
||||
let v = serde_json::to_value(&p).unwrap();
|
||||
@@ -140,6 +186,10 @@ mod tests {
|
||||
serde_json::to_value(HelpOutcome::TimedOut).unwrap(),
|
||||
json!("timed_out")
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(HelpOutcome::Completed).unwrap(),
|
||||
json!("completed")
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(HelpOutcome::Continued).unwrap(),
|
||||
json!("continued")
|
||||
@@ -158,6 +208,7 @@ mod tests {
|
||||
fn result_round_trips() {
|
||||
let r = RequestHelpResult {
|
||||
outcome: HelpOutcome::Continued,
|
||||
completed_by: None,
|
||||
note: Some("done".into()),
|
||||
tab_id: 7,
|
||||
resolved_targets: Some(vec![ResolvedTarget {
|
||||
|
||||
Reference in New Issue
Block a user