diff --git a/third_party/homeagent-sdk/example/browser/plg.json b/third_party/homeagent-sdk/example/browser/plg.json index d831afa..9da4a74 100644 --- a/third_party/homeagent-sdk/example/browser/plg.json +++ b/third_party/homeagent-sdk/example/browser/plg.json @@ -2,7 +2,7 @@ "name": "browser", "name_zh": "浏览器", "name_en": "Browser", - "version": "2.2.0", + "version": "2.2.1", "description": "统一浏览器插件:搜索、HTTP抓取(quick)、无头渲染(normal)、交互式浏览器(interactive/CDP)", "author": "HomeAgent", "entry": "plugin.so", diff --git a/third_party/homeagent-sdk/example/browser/plugin.go b/third_party/homeagent-sdk/example/browser/plugin.go index d55d96d..09b41ff 100644 --- a/third_party/homeagent-sdk/example/browser/plugin.go +++ b/third_party/homeagent-sdk/example/browser/plugin.go @@ -707,6 +707,9 @@ func (p *Plugin) fetchWithChromium(rawURL string, maxChars int) (interface{}, er }, nil } +// handleRender 无头渲染 JS 页面并提取文本(normal 模式)。 +// 主路径走共享浏览器后端:开临时标签页(带全机登录态)→ 渲染 → 取 text → 关标签页; +// 后端不可用时 failback 到独立 chromium --dump-dom(无登录态,仅保功能)。 func (p *Plugin) handleRender(args map[string]interface{}) (interface{}, error) { rawURL := readArg(args, "url", "") if rawURL == "" { @@ -716,32 +719,69 @@ func (p *Plugin) handleRender(args map[string]interface{}) (interface{}, error) return errResult(err.Error()), nil } waitSec := int64(readArg(args, "wait", float64(0))) - if waitSec > 0 { - time.Sleep(time.Duration(waitSec) * time.Second) + + var title, html string + rendered := false + + ok, needInstall, _ := p.ensureBackend() + if ok { + remoteCtx, remoteCancel := chromedp.NewRemoteAllocator(context.Background(), cdpEndpoint) + defer remoteCancel() + tabCtx, tabCancel := chromedp.NewContext(remoteCtx) + defer tabCancel() + actions := []chromedp.Action{ + chromedp.Navigate(rawURL), + chromedp.WaitReady("body"), + } + if waitSec > 0 { + actions = append(actions, chromedp.Sleep(time.Duration(waitSec)*time.Second)) + } + actions = append(actions, + chromedp.Title(&title), + chromedp.OuterHTML("html", &html), + ) + // 整体限时 30s,防慢页拖死工具 + rctx, rcancel := context.WithTimeout(tabCtx, 30*time.Second) + defer rcancel() + if err := chromedp.Run(rctx, actions...); err == nil { + rendered = true + } else { + log.Printf("[%s] render via backend failed (%v), fallback to dump-dom", p.name, err) + } + } else if needInstall { + return map[string]interface{}{ + "error": "browser backend not installed", + "need_install": true, + "guide": "调用 browser_install 安装共享后端;或重试本工具自动降级为独立 chromium 渲染(不带登录态)", + }, nil } - var html string - chromiumPath := "/usr/local/bin/chromium" - if _, err := os.Stat(chromiumPath); err == nil { + + if !rendered { + chromiumPath := "/usr/local/bin/chromium" + if _, err := os.Stat(chromiumPath); err != nil { + if _, e2 := exec.LookPath("chromium"); e2 == nil { + chromiumPath = "chromium" + } else { + return errResult("no chromium available"), nil + } + } var out bytes.Buffer cmd := exec.Command(chromiumPath, "--headless", "--disable-gpu", "--no-sandbox", "--dump-dom", rawURL) cmd.Stdout = &out - if err := cmd.Run(); err != nil { - return errResult("chromium: " + err.Error()), nil + done := make(chan error, 1) + go func() { done <- cmd.Run() }() + select { + case err := <-done: + if err != nil { + return errResult("chromium: " + err.Error()), nil + } + case <-time.After(30 * time.Second): + cmd.Process.Kill() + return errResult("chromium dump-dom timeout (30s)"), nil } html = out.String() - } else { - resp, err := http.Get(rawURL) - if err != nil { - return errResult("http get: " + err.Error()), nil - } - defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) - html = string(body) - } - title := "" - if m := regexp.MustCompile(`