refactor(service): unify submit bootstrap target resolution
Use page context, deterministic plans, and direct-skill metadata as the service-owned bootstrap target precedence so callback-host startup no longer relies on line-loss text matching or the old request-url helper. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::browser::ws_backend::WsBrowserBackend;
|
||||
use crate::browser::{BrowserBackend, PipeBrowserBackend};
|
||||
use crate::config::SgClawSettings;
|
||||
use crate::pipe::{BrowserMessage, BrowserPipeTool, PipeError, Transport};
|
||||
|
||||
pub use task_runner::{
|
||||
@@ -22,13 +23,27 @@ fn browser_backend_for_submit<T: Transport + 'static>(
|
||||
request: &SubmitTaskRequest,
|
||||
) -> Result<Arc<dyn BrowserBackend>, PipeError> {
|
||||
if let Some(browser_ws_url) = configured_browser_ws_url(context) {
|
||||
let settings = context.load_sgclaw_settings()?.unwrap_or(
|
||||
SgClawSettings::from_legacy_deepseek_fields(
|
||||
"test-key".to_string(),
|
||||
"https://example.invalid".to_string(),
|
||||
"test-model".to_string(),
|
||||
None,
|
||||
)
|
||||
.map_err(|err| PipeError::Protocol(err.to_string()))?,
|
||||
);
|
||||
let bootstrap_target = crate::service::server::resolve_submit_bootstrap_target(
|
||||
request,
|
||||
context.workspace_root(),
|
||||
&settings,
|
||||
);
|
||||
return Ok(Arc::new(
|
||||
WsBrowserBackend::new(
|
||||
Arc::new(crate::service::browser_ws_client::ServiceWsClient::connect(
|
||||
&browser_ws_url,
|
||||
)?),
|
||||
browser_tool.mac_policy().clone(),
|
||||
crate::service::browser_ws_client::initial_request_url_for_submit_task(request),
|
||||
bootstrap_target.request_url,
|
||||
)
|
||||
.with_response_timeout(browser_tool.response_timeout()),
|
||||
));
|
||||
|
||||
@@ -68,6 +68,10 @@ impl AgentRuntimeContext {
|
||||
self.config_path.as_deref()
|
||||
}
|
||||
|
||||
pub fn workspace_root(&self) -> &Path {
|
||||
&self.workspace_root
|
||||
}
|
||||
|
||||
fn settings_source_label(&self) -> String {
|
||||
match &self.config_path {
|
||||
Some(path) if path.exists() => path.display().to_string(),
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use reqwest::Url;
|
||||
use serde::Deserialize;
|
||||
use serde_json::{Map, Value};
|
||||
use zeroclaw::skills::{load_skills_from_directory, SkillTool};
|
||||
|
||||
@@ -12,6 +14,12 @@ use crate::compat::runtime::CompatTaskContext;
|
||||
use crate::config::SgClawSettings;
|
||||
use crate::pipe::{BrowserPipeTool, PipeError, Transport};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct DirectSubmitBootstrapMetadata {
|
||||
pub bootstrap_url: String,
|
||||
pub expected_domain: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DirectSubmitOutcome {
|
||||
pub success: bool,
|
||||
@@ -111,6 +119,32 @@ pub fn execute_browser_script_skill_raw_output_with_browser_backend(
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_direct_submit_bootstrap_metadata(
|
||||
configured_tool: &str,
|
||||
workspace_root: &Path,
|
||||
settings: &SgClawSettings,
|
||||
) -> Result<Option<DirectSubmitBootstrapMetadata>, PipeError> {
|
||||
let (tool, skill_root) = resolve_browser_script_skill(configured_tool, workspace_root, settings)?;
|
||||
let manifest_path = skill_root.join("SKILL.toml");
|
||||
let Ok(manifest) = fs::read_to_string(&manifest_path) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Ok(parsed) = toml::from_str::<DirectSubmitSkillManifest>(&manifest) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let metadata = parsed
|
||||
.tools
|
||||
.into_iter()
|
||||
.find(|candidate| candidate.name == tool.name)
|
||||
.and_then(|candidate| candidate.metadata)
|
||||
.and_then(|metadata| {
|
||||
normalize_bootstrap_metadata(metadata.bootstrap_url, metadata.expected_domain)
|
||||
});
|
||||
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
fn resolve_browser_script_skill(
|
||||
configured_tool: &str,
|
||||
workspace_root: &Path,
|
||||
@@ -306,6 +340,50 @@ fn count_summary_rows(counts: Option<&Value>, sections: Option<&Value>) -> usize
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct DirectSubmitSkillManifest {
|
||||
#[serde(default)]
|
||||
tools: Vec<DirectSubmitSkillManifestTool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct DirectSubmitSkillManifestTool {
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
metadata: Option<DirectSubmitToolMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct DirectSubmitToolMetadata {
|
||||
#[serde(default)]
|
||||
bootstrap_url: Option<String>,
|
||||
#[serde(default)]
|
||||
expected_domain: Option<String>,
|
||||
}
|
||||
|
||||
fn normalize_bootstrap_metadata(
|
||||
bootstrap_url: Option<String>,
|
||||
expected_domain: Option<String>,
|
||||
) -> Option<DirectSubmitBootstrapMetadata> {
|
||||
let bootstrap_url = bootstrap_url
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())?;
|
||||
let parsed = Url::parse(bootstrap_url).ok()?;
|
||||
if parsed.scheme().is_empty() || parsed.host_str().is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(DirectSubmitBootstrapMetadata {
|
||||
bootstrap_url: parsed.to_string(),
|
||||
expected_domain: expected_domain
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToString::to_string),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_configured_tool_name(configured_tool: &str) -> Result<(&str, &str), PipeError> {
|
||||
let (skill_name, tool_name) = configured_tool.split_once('.').ok_or_else(|| {
|
||||
PipeError::Protocol(format!(
|
||||
|
||||
@@ -18,7 +18,7 @@ pub use protocol::{ClientMessage, ConfigUpdatePayload, ServiceMessage};
|
||||
pub use server::{ServiceEventSink, ServiceSession};
|
||||
|
||||
pub(crate) mod browser_ws_client {
|
||||
pub(crate) use super::server::{initial_request_url_for_submit_task, ServiceWsClient};
|
||||
pub(crate) use super::server::ServiceWsClient;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use reqwest::Url;
|
||||
#[cfg(test)]
|
||||
use reqwest::blocking::Client;
|
||||
#[cfg(test)]
|
||||
@@ -28,6 +29,7 @@ use crate::browser::bridge_transport::BridgeActionTransport;
|
||||
use crate::browser::{BrowserBackend, BrowserCallbackBackend};
|
||||
#[cfg(test)]
|
||||
use crate::browser::BridgeBrowserBackend;
|
||||
use crate::config::SgClawSettings;
|
||||
use crate::pipe::{AgentMessage, BrowserMessage, PipeError, Transport};
|
||||
#[cfg(test)]
|
||||
use crate::pipe::Timing;
|
||||
@@ -329,7 +331,21 @@ pub(crate) fn serve_client(
|
||||
// Lazily create and cache the browser callback host. On first
|
||||
// task it opens the helper page; subsequent tasks reuse it.
|
||||
if cached_host.is_none() {
|
||||
let bootstrap_url = initial_request_url_for_submit_task(&request);
|
||||
let settings = context.load_sgclaw_settings()?.unwrap_or(
|
||||
SgClawSettings::from_legacy_deepseek_fields(
|
||||
"test-key".to_string(),
|
||||
"https://example.invalid".to_string(),
|
||||
"test-model".to_string(),
|
||||
None,
|
||||
)
|
||||
.map_err(|err| PipeError::Protocol(err.to_string()))?,
|
||||
);
|
||||
let bootstrap_target = resolve_submit_bootstrap_target(
|
||||
&request,
|
||||
context.workspace_root(),
|
||||
&settings,
|
||||
);
|
||||
let bootstrap_url = bootstrap_target.request_url;
|
||||
match LiveBrowserCallbackHost::start_with_browser_ws_url(
|
||||
browser_ws_url,
|
||||
&bootstrap_url,
|
||||
@@ -419,15 +435,82 @@ pub(crate) fn serve_client(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn initial_request_url_for_submit_task(request: &crate::agent::SubmitTaskRequest) -> String {
|
||||
request
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct SubmitBootstrapTarget {
|
||||
pub request_url: String,
|
||||
pub expected_domain: Option<String>,
|
||||
pub source: BootstrapTargetSource,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum BootstrapTargetSource {
|
||||
PageContext,
|
||||
DeterministicPlan,
|
||||
SkillConfig,
|
||||
Fallback,
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_submit_bootstrap_target(
|
||||
request: &crate::agent::SubmitTaskRequest,
|
||||
workspace_root: &Path,
|
||||
settings: &SgClawSettings,
|
||||
) -> SubmitBootstrapTarget {
|
||||
if let Some(page_url) = request
|
||||
.page_url
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToString::to_string)
|
||||
.or_else(|| derive_request_url_from_instruction(&request.instruction))
|
||||
.unwrap_or_else(|| "about:blank".to_string())
|
||||
{
|
||||
return SubmitBootstrapTarget {
|
||||
request_url: page_url.to_string(),
|
||||
expected_domain: Url::parse(page_url)
|
||||
.ok()
|
||||
.and_then(|url| url.domain().map(ToString::to_string)),
|
||||
source: BootstrapTargetSource::PageContext,
|
||||
};
|
||||
}
|
||||
|
||||
if let crate::compat::deterministic_submit::DeterministicSubmitDecision::Execute(plan) =
|
||||
crate::compat::deterministic_submit::decide_deterministic_submit(
|
||||
&request.instruction,
|
||||
request.page_url.as_deref(),
|
||||
request.page_title.as_deref(),
|
||||
)
|
||||
{
|
||||
return SubmitBootstrapTarget {
|
||||
request_url: plan.target_url.clone(),
|
||||
expected_domain: Some(plan.expected_domain.clone()),
|
||||
source: BootstrapTargetSource::DeterministicPlan,
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(configured_tool) = settings
|
||||
.direct_submit_skill
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
if let Ok(Some(metadata)) =
|
||||
crate::compat::direct_skill_runtime::resolve_direct_submit_bootstrap_metadata(
|
||||
configured_tool,
|
||||
workspace_root,
|
||||
settings,
|
||||
)
|
||||
{
|
||||
return SubmitBootstrapTarget {
|
||||
request_url: metadata.bootstrap_url,
|
||||
expected_domain: metadata.expected_domain,
|
||||
source: BootstrapTargetSource::SkillConfig,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
SubmitBootstrapTarget {
|
||||
request_url: derive_request_url_from_instruction(&request.instruction)
|
||||
.unwrap_or_else(|| "about:blank".to_string()),
|
||||
expected_domain: None,
|
||||
source: BootstrapTargetSource::Fallback,
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_request_url_from_instruction(instruction: &str) -> Option<String> {
|
||||
@@ -457,12 +540,6 @@ fn derive_request_url_from_instruction(instruction: &str) -> Option<String> {
|
||||
return Some("https://zhuanlan.zhihu.com".to_string());
|
||||
}
|
||||
|
||||
// 台区线损相关
|
||||
// TODO: 临时方案,后续应从 skill 配置或 deterministic_submit 解析结果中获取
|
||||
if instruction.contains("线损") || instruction.contains("lineloss") {
|
||||
return Some("http://20.76.57.61:18080".to_string());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@@ -834,16 +911,77 @@ fn write_http_json_response(stream: &mut impl std::io::Write, status: &str, body
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use std::net::TcpListener;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::thread;
|
||||
|
||||
use serde_json::json;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::agent::SubmitTaskRequest;
|
||||
use crate::browser::BrowserBackend;
|
||||
use crate::pipe::Action;
|
||||
|
||||
fn service_test_settings(
|
||||
skills_dir: Option<PathBuf>,
|
||||
direct_submit_skill: Option<&str>,
|
||||
) -> SgClawSettings {
|
||||
let mut settings = SgClawSettings::from_legacy_deepseek_fields(
|
||||
"test-key".to_string(),
|
||||
"https://example.invalid".to_string(),
|
||||
"test-model".to_string(),
|
||||
skills_dir,
|
||||
)
|
||||
.expect("settings");
|
||||
settings.direct_submit_skill = direct_submit_skill.map(ToString::to_string);
|
||||
settings
|
||||
}
|
||||
|
||||
fn staged_skill_staging_dir() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../claw/claw/skills/skill_staging/skills")
|
||||
.canonicalize()
|
||||
.expect("staged skills dir")
|
||||
}
|
||||
|
||||
fn temp_direct_submit_skill_root(bootstrap_url: &str) -> PathBuf {
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
"sgclaw-bootstrap-target-skill-root-{}",
|
||||
Uuid::new_v4()
|
||||
));
|
||||
let skill_dir = root.join("fault-details-report");
|
||||
let script_dir = skill_dir.join("scripts");
|
||||
fs::create_dir_all(&script_dir).expect("create script dir");
|
||||
fs::write(
|
||||
skill_dir.join("SKILL.toml"),
|
||||
format!(
|
||||
r#"[skill]
|
||||
name = "fault-details-report"
|
||||
description = "Collect 95598 fault detail data via browser eval."
|
||||
version = "0.1.0"
|
||||
|
||||
[[tools]]
|
||||
name = "collect_fault_details"
|
||||
description = "Collect structured fault detail rows for a specific period."
|
||||
kind = "browser_script"
|
||||
command = "scripts/collect_fault_details.js"
|
||||
|
||||
[tools.metadata]
|
||||
bootstrap_url = "{bootstrap_url}"
|
||||
expected_domain = "95598.sgcc.com.cn"
|
||||
"#
|
||||
),
|
||||
)
|
||||
.expect("write skill manifest");
|
||||
fs::write(
|
||||
script_dir.join("collect_fault_details.js"),
|
||||
"return { ok: true };\n",
|
||||
)
|
||||
.expect("write skill script");
|
||||
root
|
||||
}
|
||||
|
||||
fn service_test_policy() -> MacPolicy {
|
||||
MacPolicy::from_json_str(
|
||||
r#"{
|
||||
@@ -859,56 +997,165 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initial_request_url_prefers_submit_task_page_url() {
|
||||
fn page_context_bootstrap_target_wins_over_deterministic_and_skill_fallback() {
|
||||
let request = SubmitTaskRequest {
|
||||
instruction: "打开知乎热榜".to_string(),
|
||||
page_url: Some(" https://www.zhihu.com/ ".to_string()),
|
||||
instruction: "兰州公司 台区线损大数据 月累计线损率统计分析。。。".to_string(),
|
||||
page_url: Some(" https://already-open.example.com/page ".to_string()),
|
||||
..SubmitTaskRequest::default()
|
||||
};
|
||||
let settings = SgClawSettings::from_legacy_deepseek_fields(
|
||||
"test-key".to_string(),
|
||||
"https://example.invalid".to_string(),
|
||||
"test-model".to_string(),
|
||||
None,
|
||||
)
|
||||
.expect("settings");
|
||||
|
||||
assert_eq!(
|
||||
initial_request_url_for_submit_task(&request),
|
||||
"https://www.zhihu.com/"
|
||||
);
|
||||
let target = resolve_submit_bootstrap_target(&request, Path::new("."), &settings);
|
||||
|
||||
assert_eq!(target.request_url, "https://already-open.example.com/page");
|
||||
assert_eq!(target.expected_domain.as_deref(), Some("already-open.example.com"));
|
||||
assert_eq!(target.source, BootstrapTargetSource::PageContext);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initial_request_url_falls_back_to_zhihu_origin_for_hotlist_routes() {
|
||||
fn whitespace_page_url_does_not_short_circuit_bootstrap_fallback() {
|
||||
let request = SubmitTaskRequest {
|
||||
instruction: "打开知乎热榜,获取前10条数据,并导出 Excel".to_string(),
|
||||
page_url: Some(" ".to_string()),
|
||||
..SubmitTaskRequest::default()
|
||||
};
|
||||
let settings = SgClawSettings::from_legacy_deepseek_fields(
|
||||
"test-key".to_string(),
|
||||
"https://example.invalid".to_string(),
|
||||
"test-model".to_string(),
|
||||
None,
|
||||
)
|
||||
.expect("settings");
|
||||
|
||||
assert_eq!(
|
||||
initial_request_url_for_submit_task(&request),
|
||||
"https://www.zhihu.com"
|
||||
);
|
||||
let target = resolve_submit_bootstrap_target(&request, Path::new("."), &settings);
|
||||
|
||||
assert_eq!(target.request_url, "https://www.zhihu.com");
|
||||
assert_eq!(target.source, BootstrapTargetSource::Fallback);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initial_request_url_falls_back_to_zhihu_origin_for_generated_article_publish_routes() {
|
||||
let request = SubmitTaskRequest {
|
||||
instruction: "在知乎自动发表一篇名称为人工智能技能大全".to_string(),
|
||||
..SubmitTaskRequest::default()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
initial_request_url_for_submit_task(&request),
|
||||
"https://www.zhihu.com"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initial_request_url_falls_back_to_lineloss_origin_for_lineloss_instructions() {
|
||||
fn deterministic_bootstrap_target_uses_plan_target_url() {
|
||||
let request = SubmitTaskRequest {
|
||||
instruction: "兰州公司 台区线损大数据 月累计线损率统计分析。。。".to_string(),
|
||||
..SubmitTaskRequest::default()
|
||||
};
|
||||
let settings = service_test_settings(None, None);
|
||||
|
||||
let target = resolve_submit_bootstrap_target(&request, Path::new("."), &settings);
|
||||
|
||||
assert_eq!(
|
||||
initial_request_url_for_submit_task(&request),
|
||||
"http://20.76.57.61:18080"
|
||||
target.request_url,
|
||||
"http://20.76.57.61:18080/gsllys/tqLinelossStatis/tqQualifyRateMonitor"
|
||||
);
|
||||
assert_eq!(target.expected_domain.as_deref(), Some("20.76.57.61"));
|
||||
assert_eq!(target.source, BootstrapTargetSource::DeterministicPlan);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skill_metadata_bootstrap_url_is_used_when_no_page_context_or_plan_exists() {
|
||||
let request = SubmitTaskRequest {
|
||||
instruction: "请采集 2026-03 的故障明细并返回结果".to_string(),
|
||||
..SubmitTaskRequest::default()
|
||||
};
|
||||
let settings = service_test_settings(
|
||||
Some(staged_skill_staging_dir()),
|
||||
Some("fault-details-report.collect_fault_details"),
|
||||
);
|
||||
|
||||
let target = resolve_submit_bootstrap_target(&request, Path::new("."), &settings);
|
||||
|
||||
assert_eq!(target.request_url, "https://95598.sgcc.com.cn/");
|
||||
assert_eq!(target.expected_domain.as_deref(), Some("95598.sgcc.com.cn"));
|
||||
assert_eq!(target.source, BootstrapTargetSource::SkillConfig);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_skill_bootstrap_url_falls_back_to_about_blank() {
|
||||
let request = SubmitTaskRequest {
|
||||
instruction: "请采集 2026-03 的故障明细并返回结果".to_string(),
|
||||
..SubmitTaskRequest::default()
|
||||
};
|
||||
let skills_dir = temp_direct_submit_skill_root("not-a-valid-absolute-url");
|
||||
let settings = service_test_settings(
|
||||
Some(skills_dir.clone()),
|
||||
Some("fault-details-report.collect_fault_details"),
|
||||
);
|
||||
|
||||
let target = resolve_submit_bootstrap_target(&request, Path::new("."), &settings);
|
||||
|
||||
assert_eq!(target.request_url, "about:blank");
|
||||
assert_eq!(target.expected_domain, None);
|
||||
assert_eq!(target.source, BootstrapTargetSource::Fallback);
|
||||
|
||||
let _ = fs::remove_dir_all(skills_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bootstrap_target_precedence_matrix_covers_page_context_deterministic_skill_and_fallback() {
|
||||
let page_request = SubmitTaskRequest {
|
||||
instruction: "兰州公司 台区线损大数据 月累计线损率统计分析。。。".to_string(),
|
||||
page_url: Some(" https://already-open.example.com/page ".to_string()),
|
||||
..SubmitTaskRequest::default()
|
||||
};
|
||||
let page_settings = service_test_settings(
|
||||
Some(staged_skill_staging_dir()),
|
||||
Some("fault-details-report.collect_fault_details"),
|
||||
);
|
||||
let page_target =
|
||||
resolve_submit_bootstrap_target(&page_request, Path::new("."), &page_settings);
|
||||
assert_eq!(page_target.request_url, "https://already-open.example.com/page");
|
||||
assert_eq!(page_target.source, BootstrapTargetSource::PageContext);
|
||||
|
||||
let deterministic_request = SubmitTaskRequest {
|
||||
instruction: "兰州公司 台区线损大数据 月累计线损率统计分析。。。".to_string(),
|
||||
..SubmitTaskRequest::default()
|
||||
};
|
||||
let deterministic_settings = service_test_settings(
|
||||
Some(staged_skill_staging_dir()),
|
||||
Some("fault-details-report.collect_fault_details"),
|
||||
);
|
||||
let deterministic_target = resolve_submit_bootstrap_target(
|
||||
&deterministic_request,
|
||||
Path::new("."),
|
||||
&deterministic_settings,
|
||||
);
|
||||
assert_eq!(
|
||||
deterministic_target.request_url,
|
||||
"http://20.76.57.61:18080/gsllys/tqLinelossStatis/tqQualifyRateMonitor"
|
||||
);
|
||||
assert_eq!(
|
||||
deterministic_target.source,
|
||||
BootstrapTargetSource::DeterministicPlan
|
||||
);
|
||||
|
||||
let skill_request = SubmitTaskRequest {
|
||||
instruction: "请采集 2026-03 的故障明细并返回结果".to_string(),
|
||||
..SubmitTaskRequest::default()
|
||||
};
|
||||
let skill_settings = service_test_settings(
|
||||
Some(staged_skill_staging_dir()),
|
||||
Some("fault-details-report.collect_fault_details"),
|
||||
);
|
||||
let skill_target =
|
||||
resolve_submit_bootstrap_target(&skill_request, Path::new("."), &skill_settings);
|
||||
assert_eq!(skill_target.request_url, "https://95598.sgcc.com.cn/");
|
||||
assert_eq!(skill_target.source, BootstrapTargetSource::SkillConfig);
|
||||
|
||||
let fallback_request = SubmitTaskRequest {
|
||||
instruction: "完全不相关的普通问题".to_string(),
|
||||
..SubmitTaskRequest::default()
|
||||
};
|
||||
let fallback_settings = service_test_settings(None, None);
|
||||
let fallback_target =
|
||||
resolve_submit_bootstrap_target(&fallback_request, Path::new("."), &fallback_settings);
|
||||
assert_eq!(fallback_target.request_url, "about:blank");
|
||||
assert_eq!(fallback_target.source, BootstrapTargetSource::Fallback);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user