From 8ed54448c271973829e1680d68fc7dc67d0a69d8 Mon Sep 17 00:00:00 2001 From: Rocky Kwok Date: Wed, 24 Jun 2026 15:40:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=89=93=E5=BC=80=20browser=5Fact?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/gateway/routers/browser_context.py | 15 +++++-- offline-backend-20260512/backend/config.yaml | 15 +++++++ .../backend/extensions_config.json | 41 ++++++++++++++----- .../harness/deerflow/browser_context/tasks.py | 33 ++++++++++++++- .../backend/tests/test_browser_act.py | 17 ++++++++ 5 files changed, 106 insertions(+), 15 deletions(-) diff --git a/offline-backend-20260512/backend/app/gateway/routers/browser_context.py b/offline-backend-20260512/backend/app/gateway/routers/browser_context.py index 3a2237c0..e5572783 100644 --- a/offline-backend-20260512/backend/app/gateway/routers/browser_context.py +++ b/offline-backend-20260512/backend/app/gateway/routers/browser_context.py @@ -27,7 +27,7 @@ from app.gateway.routers.thread_runs import RunCreateRequest from app.gateway.routers.threads import ThreadCreateRequest, create_thread from app.gateway.routers.uploads import _make_file_sandbox_writable from app.gateway.services import start_run -from deerflow.browser_context.tasks import claim_next_browser_task, complete_browser_task, prune_browser_tasks +from deerflow.browser_context.tasks import claim_next_browser_task, complete_browser_task, prune_browser_tasks, tasks_dir from deerflow.config.app_config import AppConfig from deerflow.config.browser_context_config import BrowserContextConfig from deerflow.runtime import serialize_channel_values @@ -307,10 +307,19 @@ async def claim_browser_context_task( config: AppConfig = Depends(get_config), ) -> BrowserContextTaskNextResponse: settings = _require_enabled(config) + effective_user_id = get_effective_user_id() + effective_worker_id = worker_id or "chrome-extension" + logger.info( + "Browser task next poll user_id=%s worker_id=%s tasks_dir=%s client=%s", + effective_user_id, + effective_worker_id, + tasks_dir(), + getattr(request.client, "host", None), + ) prune_browser_tasks() task = claim_next_browser_task( - user_id=get_effective_user_id(), - worker_id=worker_id or "chrome-extension", + user_id=effective_user_id, + worker_id=effective_worker_id, lease_seconds=settings.task_lease_seconds, ) if task is None: diff --git a/offline-backend-20260512/backend/config.yaml b/offline-backend-20260512/backend/config.yaml index 01729927..b1951857 100644 --- a/offline-backend-20260512/backend/config.yaml +++ b/offline-backend-20260512/backend/config.yaml @@ -171,6 +171,11 @@ vision: # prompt: 请详细描述这张图片的内容。 # max_tokens: 2048 +# browser_act 相关配置 +browser_context: + enabled: true + actions_enabled: true + # 上传文件识别配置。 # - auto_convert_documents: true 时,上传的 PDF / Word / Excel / PPT 会在服务端自动转换为 # Markdown(.md),agent 通过 read_file / grep 读取其内容 —— 即“识别”文档内容。 @@ -268,6 +273,16 @@ tools: group: bash use: deerflow.sandbox.tools:bash_tool + # browser_act 相关配置 + - name: browser_fetch_page + group: web + use: deerflow.tools.builtins.browser_context_tool:browser_fetch_page_tool + + # browser_act 相关配置 + - name: browser_act + group: web + use: deerflow.tools.builtins.browser_act_tool:browser_act_tool + sandbox: use: deerflow.sandbox.local:LocalSandboxProvider allow_host_bash: true diff --git a/offline-backend-20260512/backend/extensions_config.json b/offline-backend-20260512/backend/extensions_config.json index 522e4597..8d7e27a0 100644 --- a/offline-backend-20260512/backend/extensions_config.json +++ b/offline-backend-20260512/backend/extensions_config.json @@ -1,14 +1,35 @@ { "mcpServers": {}, "skills": { - "web-research": { "enabled": true }, - "deep-search": { "enabled": true }, - "code-retrieval": { "enabled": true }, - "news-briefing": { "enabled": true }, - "workspace-search": { "enabled": true }, - "policy-knowledge": { "enabled": false }, - "html-page-builder": { "enabled": true }, - "html-charts": { "enabled": true }, - "html-multipage": { "enabled": true } + "web-research": { + "enabled": true + }, + "deep-search": { + "enabled": true + }, + "code-retrieval": { + "enabled": true + }, + "news-briefing": { + "enabled": true + }, + "workspace-search": { + "enabled": true + }, + "policy-knowledge": { + "enabled": false + }, + "html-page-builder": { + "enabled": true + }, + "html-charts": { + "enabled": true + }, + "html-multipage": { + "enabled": true + }, + "browser_act": { + "enabled": true + } } -} +} \ No newline at end of file diff --git a/offline-backend-20260512/backend/packages/harness/deerflow/browser_context/tasks.py b/offline-backend-20260512/backend/packages/harness/deerflow/browser_context/tasks.py index 9a8d2f51..0d814f6f 100644 --- a/offline-backend-20260512/backend/packages/harness/deerflow/browser_context/tasks.py +++ b/offline-backend-20260512/backend/packages/harness/deerflow/browser_context/tasks.py @@ -193,16 +193,22 @@ def claim_next_browser_task( user_id = str(user_id or "default") worker_id = (worker_id or "chrome-extension").strip()[:128] or "chrome-extension" lease_seconds = max(10, int(lease_seconds)) + queue_dir = tasks_dir() with _LOCK: - paths = sorted(tasks_dir().glob("*.json"), key=lambda p: p.stat().st_mtime) + paths = sorted(queue_dir.glob("*.json"), key=lambda p: p.stat().st_mtime) + matching_user = 0 + matching_status_counts: dict[str, int] = {} for path in paths: data = _read_task(path) if not data or str(data.get("user_id") or "") != user_id: continue + matching_user += 1 data = _expire_if_needed(path, data, now) status = data.get("status") + status_key = str(status or "unknown") + matching_status_counts[status_key] = matching_status_counts.get(status_key, 0) + 1 if status == "running": updated_at = _parse_iso(data.get("updated_at")) or now if (now - updated_at).total_seconds() <= lease_seconds: @@ -220,8 +226,32 @@ def claim_next_browser_task( data["claimed_at"] = _iso(now) data["updated_at"] = _iso(now) _write_task(path, data) + logger.info( + "Browser task claim scan user_id=%s worker_id=%s tasks_dir=%s total_files=%d matching_user=%d " + "matching_statuses=%s claimed_task_id=%s claimed_kind=%s", + user_id, + worker_id, + queue_dir, + len(paths), + matching_user, + matching_status_counts, + data.get("id"), + data.get("kind") or "fetch", + ) return _public_task(data) + logger.info( + "Browser task claim scan user_id=%s worker_id=%s tasks_dir=%s total_files=%d matching_user=%d " + "matching_statuses=%s claimed_task_id=%s claimed_kind=%s", + user_id, + worker_id, + queue_dir, + len(paths), + matching_user, + matching_status_counts, + None, + None, + ) return None @@ -300,4 +330,3 @@ async def wait_for_browser_task_result( if time.monotonic() >= deadline: return task await asyncio.sleep(interval) - diff --git a/offline-backend-20260512/backend/tests/test_browser_act.py b/offline-backend-20260512/backend/tests/test_browser_act.py index 3dee216a..fd160bc2 100644 --- a/offline-backend-20260512/backend/tests/test_browser_act.py +++ b/offline-backend-20260512/backend/tests/test_browser_act.py @@ -3,6 +3,7 @@ from __future__ import annotations import asyncio +import logging import pytest @@ -76,6 +77,22 @@ def test_fetch_task_default_kind_is_fetch(): assert claimed["action"] is None +def test_claim_next_browser_task_logs_claim_diagnostics(caplog): + task_queue.create_browser_task(user_id="u1", url="https://example.com/1") + task_queue.create_browser_task(user_id="u2", url="https://example.com/2") + + with caplog.at_level(logging.INFO, logger=task_queue.__name__): + claimed = task_queue.claim_next_browser_task(user_id="u1", worker_id="worker-1") + + assert claimed is not None + assert "Browser task claim scan" in caplog.text + assert "user_id=u1" in caplog.text + assert "worker_id=worker-1" in caplog.text + assert "total_files=2" in caplog.text + assert "matching_user=1" in caplog.text + assert "claimed_task_id=" in caplog.text + + # --- tool validation --------------------------------------------------------