fix(growth,hands,kernel,desktop): Phase 1 用户可感知修复 — 6 项断链修复
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

Phase 1 修复内容:
1. Hand 执行前端字段映射 — instance_id → runId,修复 Hand 状态追踪
2. Heartbeat 痛点感知 — PAIN_POINTS_CACHE + VikingStorage 持久化 + 未解决痛点检查
3. Browser Hand 委托消息 — pending_execution → delegated_to_frontend + 中文摘要
4. 跨会话记忆检索增强 — 扩展 IdentityRecall 模式 26→43 + 弱身份信号检测 + 低结果 fallback
5. Twitter Hand 凭据持久化 — SetCredentials action + 文件持久化 + 启动恢复
6. Browser 测试修复 — 适配新的 delegated_to_frontend 响应格式

验证: cargo check  | cargo test 912 PASS  | tsc --noEmit 
This commit is contained in:
iven
2026-04-21 10:18:25 +08:00
parent 2f5e9f1755
commit 9a2611d122
10 changed files with 435 additions and 165 deletions

View File

@@ -117,6 +117,56 @@ pub enum BrowserAction {
},
}
impl BrowserAction {
pub fn action_name(&self) -> &'static str {
match self {
BrowserAction::Navigate { .. } => "navigate",
BrowserAction::Click { .. } => "click",
BrowserAction::Type { .. } => "type",
BrowserAction::Select { .. } => "select",
BrowserAction::Scrape { .. } => "scrape",
BrowserAction::Screenshot { .. } => "screenshot",
BrowserAction::FillForm { .. } => "fill_form",
BrowserAction::Wait { .. } => "wait",
BrowserAction::Execute { .. } => "execute",
BrowserAction::GetSource => "get_source",
BrowserAction::GetUrl => "get_url",
BrowserAction::GetTitle => "get_title",
BrowserAction::Scroll { .. } => "scroll",
BrowserAction::Back => "back",
BrowserAction::Forward => "forward",
BrowserAction::Refresh => "refresh",
BrowserAction::Hover { .. } => "hover",
BrowserAction::PressKey { .. } => "press_key",
BrowserAction::Upload { .. } => "upload",
}
}
pub fn summary(&self) -> String {
match self {
BrowserAction::Navigate { url, .. } => format!("导航到 {}", url),
BrowserAction::Click { selector, .. } => format!("点击 {}", selector),
BrowserAction::Type { selector, text, .. } => format!("{} 输入 {}", selector, text),
BrowserAction::Select { selector, value } => format!("{} 选择 {}", selector, value),
BrowserAction::Scrape { selectors, .. } => format!("抓取 {} 个选择器", selectors.len()),
BrowserAction::Screenshot { .. } => "截图".to_string(),
BrowserAction::FillForm { fields, .. } => format!("填写 {} 个字段", fields.len()),
BrowserAction::Wait { selector, .. } => format!("等待 {}", selector),
BrowserAction::Execute { .. } => "执行脚本".to_string(),
BrowserAction::GetSource => "获取页面源码".to_string(),
BrowserAction::GetUrl => "获取当前URL".to_string(),
BrowserAction::GetTitle => "获取页面标题".to_string(),
BrowserAction::Scroll { x, y, .. } => format!("滚动到 ({},{})", x, y),
BrowserAction::Back => "后退".to_string(),
BrowserAction::Forward => "前进".to_string(),
BrowserAction::Refresh => "刷新".to_string(),
BrowserAction::Hover { selector } => format!("悬停 {}", selector),
BrowserAction::PressKey { key } => format!("按键 {}", key),
BrowserAction::Upload { selector, .. } => format!("上传文件到 {}", selector),
}
}
}
/// Form field definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FormField {
@@ -202,151 +252,18 @@ impl Hand for BrowserHand {
Err(e) => return Ok(HandResult::error(format!("Invalid action: {}", e))),
};
// Execute based on action type
// Note: Actual browser operations are handled via Tauri commands
// This Hand provides a structured interface for the runtime
match action {
BrowserAction::Navigate { url, wait_for } => {
Ok(HandResult::success(serde_json::json!({
"action": "navigate",
"url": url,
"wait_for": wait_for,
"status": "pending_execution"
})))
}
BrowserAction::Click { selector, wait_ms } => {
Ok(HandResult::success(serde_json::json!({
"action": "click",
"selector": selector,
"wait_ms": wait_ms,
"status": "pending_execution"
})))
}
BrowserAction::Type { selector, text, clear_first } => {
Ok(HandResult::success(serde_json::json!({
"action": "type",
"selector": selector,
"text": text,
"clear_first": clear_first,
"status": "pending_execution"
})))
}
BrowserAction::Scrape { selectors, wait_for } => {
Ok(HandResult::success(serde_json::json!({
"action": "scrape",
"selectors": selectors,
"wait_for": wait_for,
"status": "pending_execution"
})))
}
BrowserAction::Screenshot { selector, full_page } => {
Ok(HandResult::success(serde_json::json!({
"action": "screenshot",
"selector": selector,
"full_page": full_page,
"status": "pending_execution"
})))
}
BrowserAction::FillForm { fields, submit_selector } => {
Ok(HandResult::success(serde_json::json!({
"action": "fill_form",
"fields": fields,
"submit_selector": submit_selector,
"status": "pending_execution"
})))
}
BrowserAction::Wait { selector, timeout_ms } => {
Ok(HandResult::success(serde_json::json!({
"action": "wait",
"selector": selector,
"timeout_ms": timeout_ms,
"status": "pending_execution"
})))
}
BrowserAction::Execute { script, args } => {
Ok(HandResult::success(serde_json::json!({
"action": "execute",
"script": script,
"args": args,
"status": "pending_execution"
})))
}
BrowserAction::GetSource => {
Ok(HandResult::success(serde_json::json!({
"action": "get_source",
"status": "pending_execution"
})))
}
BrowserAction::GetUrl => {
Ok(HandResult::success(serde_json::json!({
"action": "get_url",
"status": "pending_execution"
})))
}
BrowserAction::GetTitle => {
Ok(HandResult::success(serde_json::json!({
"action": "get_title",
"status": "pending_execution"
})))
}
BrowserAction::Scroll { x, y, selector } => {
Ok(HandResult::success(serde_json::json!({
"action": "scroll",
"x": x,
"y": y,
"selector": selector,
"status": "pending_execution"
})))
}
BrowserAction::Back => {
Ok(HandResult::success(serde_json::json!({
"action": "back",
"status": "pending_execution"
})))
}
BrowserAction::Forward => {
Ok(HandResult::success(serde_json::json!({
"action": "forward",
"status": "pending_execution"
})))
}
BrowserAction::Refresh => {
Ok(HandResult::success(serde_json::json!({
"action": "refresh",
"status": "pending_execution"
})))
}
BrowserAction::Hover { selector } => {
Ok(HandResult::success(serde_json::json!({
"action": "hover",
"selector": selector,
"status": "pending_execution"
})))
}
BrowserAction::PressKey { key } => {
Ok(HandResult::success(serde_json::json!({
"action": "press_key",
"key": key,
"status": "pending_execution"
})))
}
BrowserAction::Upload { selector, file_path } => {
Ok(HandResult::success(serde_json::json!({
"action": "upload",
"selector": selector,
"file_path": file_path,
"status": "pending_execution"
})))
}
BrowserAction::Select { selector, value } => {
Ok(HandResult::success(serde_json::json!({
"action": "select",
"selector": selector,
"value": value,
"status": "pending_execution"
})))
}
}
// Browser automation executes on the frontend via BrowserHandCard.
// Return the parsed action with a clear message so the LLM can inform
// the user and the frontend can pick it up via Tauri events.
let action_type = action.action_name();
let summary = action.summary();
Ok(HandResult::success(serde_json::json!({
"action": action_type,
"status": "delegated_to_frontend",
"message": format!("浏览器操作「{}」已委托给前端执行。请在 HandsPanel 中查看执行结果。", summary),
"details": format!("{} — 需要 WebDriver 会话,由前端 BrowserHandCard 管理。", summary),
})))
}
fn is_dependency_available(&self, dep: &str) -> bool {
@@ -600,7 +517,7 @@ mod tests {
let result = hand.execute(&ctx, action_json).await.expect("execute");
assert!(result.success);
assert_eq!(result.output["action"], "navigate");
assert_eq!(result.output["url"], "https://example.com");
assert_eq!(result.output["status"], "delegated_to_frontend");
}
#[tokio::test]