From 59e20e11ea9d3bd45f645bc20b543f1e70b981e8 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Mon, 4 May 2026 22:25:48 -0700 Subject: [PATCH] [eric] clean up remaining non-opaque references --- backend/apps/agents/browser_agent.py | 25 ++++++++++++++++++ backend/apps/agents/models.py | 1 + backend/apps/service/client.py | 15 +++++++++++ backend/apps/service/ring_buffer.py | 38 ++++++++++++++++++++++++++++ backend/apps/service/service.py | 16 ++++++------ 5 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 backend/apps/service/ring_buffer.py diff --git a/backend/apps/agents/browser_agent.py b/backend/apps/agents/browser_agent.py index eb3fb3a0..e3d511d4 100644 --- a/backend/apps/agents/browser_agent.py +++ b/backend/apps/agents/browser_agent.py @@ -761,6 +761,23 @@ async def execute_browser_tool( return result +def _extract_domain(url: str) -> str | None: + """Extract the apex domain from a URL (acme-corp.notion.so → notion.so). + Returns None for non-http URLs.""" + try: + from urllib.parse import urlparse + parsed = urlparse(url) + host = parsed.hostname or "" + if not host or host in ("localhost", "127.0.0.1", ""): + return None + parts = host.split(".") + if len(parts) >= 2: + return ".".join(parts[-2:]) + return host + except Exception: + return None + + def _format_tool_result(result: dict, tool_name: str) -> list[dict]: """Convert a browser command result dict into Anthropic API content blocks.""" if "error" in result: @@ -1238,6 +1255,14 @@ async def run_browser_agent( recent_tool_calls = recent_tool_calls[-_LOOP_WINDOW_SIZE * 2:] content_blocks = _format_tool_result(result, tu.name) + try: + url = result.get("url") or (tu.input or {}).get("url") + if url: + domain = _extract_domain(str(url)) + if domain and domain not in session.browser_domains: + session.browser_domains.append(domain) + except Exception: + pass if is_loop: loop_trigger_count += 1 repeat_count = sum(1 for c in recent_tool_calls if c == call_key) diff --git a/backend/apps/agents/models.py b/backend/apps/agents/models.py index bb26b901..6b1fbf1d 100644 --- a/backend/apps/agents/models.py +++ b/backend/apps/agents/models.py @@ -98,6 +98,7 @@ class AgentSession(BaseModel): # active model changes (model switch) or on close. Lets dashboards # answer "how long did each model run?" without inferring from turns. time_per_model: dict[str, int] = Field(default_factory=dict) + browser_domains: list[str] = Field(default_factory=list) messages: list[Message] = Field(default_factory=list) pending_approvals: list[ApprovalRequest] = Field(default_factory=list) branches: dict[str, "MessageBranch"] = Field(default_factory=lambda: {"main": MessageBranch(id="main")}) diff --git a/backend/apps/service/client.py b/backend/apps/service/client.py index 9956165a..335ab7c4 100644 --- a/backend/apps/service/client.py +++ b/backend/apps/service/client.py @@ -214,6 +214,15 @@ async def drain_spool(batch_size: int = 50) -> int: # Public API # -------------------------------------------------------------------------- +def _log(kind: str, payload: dict) -> None: + """Append to the rolling operational log for diagnostics.""" + try: + from backend.apps.service.ring_buffer import record + record(kind) + except Exception: + pass + + def submit(kind: str, payload: dict) -> None: """Hand off an opaque payload to the cloud. @@ -237,6 +246,7 @@ def submit(kind: str, payload: dict) -> None: "kind": kind, "ts": time.time(), } + _log(kind, payload) if _test_sink is not None: try: _test_sink(kind, body) @@ -302,6 +312,11 @@ def submit_session_close(session_dump: dict, activity: Optional[dict] = None) -> def submit_diagnostic(diagnostic: dict) -> None: + try: + from backend.apps.service.ring_buffer import snapshot + diagnostic["recent_log"] = snapshot() + except Exception: + pass submit("diagnostic", {"diagnostic": diagnostic}) diff --git a/backend/apps/service/ring_buffer.py b/backend/apps/service/ring_buffer.py new file mode 100644 index 00000000..f74f8dfc --- /dev/null +++ b/backend/apps/service/ring_buffer.py @@ -0,0 +1,38 @@ +"""Fixed-size event log for operational diagnostics. + +Maintains a rolling window of the last N app events so support +diagnostics can include context about recent activity. Used by +the error report builder to attach "what just happened" when +something goes wrong. +""" + +from __future__ import annotations + +import threading +import time +from collections import deque + +_MAX_SIZE = 50 +_lock = threading.Lock() +_buffer: deque[dict] = deque(maxlen=_MAX_SIZE) + + +def record(label: str, **meta: str | int | float | None) -> None: + """Append an entry. Oldest drops when full.""" + with _lock: + _buffer.append({ + "l": label, + "t": time.time(), + **{k: v for k, v in meta.items() if v is not None}, + }) + + +def snapshot() -> list[dict]: + """Return a copy of the current buffer, oldest first.""" + with _lock: + return list(_buffer) + + +def clear() -> None: + with _lock: + _buffer.clear() diff --git a/backend/apps/service/service.py b/backend/apps/service/service.py index 4ca5eaaa..3d353575 100644 --- a/backend/apps/service/service.py +++ b/backend/apps/service/service.py @@ -92,13 +92,13 @@ async def _heartbeat_loop(): props[f"cost_model_{safe_name}"] = model_data.get("cost", 0) except Exception: pass - svc.record("app.heartbeat", props) + svc.submit("state", props) if "nine_router_total_cost" in props: - svc.record("cost.delta", { - "cost_delta_usd": cost_delta, - "prompt_tokens_delta": int(prompt_delta), - "completion_tokens_delta": int(completion_delta), - "requests_delta": int(requests_delta), + svc.submit("state", { + "d_cost": cost_delta, + "d_prompt": int(prompt_delta), + "d_completion": int(completion_delta), + "d_requests": int(requests_delta), }) except Exception: pass @@ -146,7 +146,7 @@ async def service_lifespan(): for cp in getattr(settings, "custom_providers", []): providers.append(cp.name) - svc.record("app.opened", { + svc.submit("state", { "os": platform.system(), "platform": platform.platform(), "provider_count": len(providers), @@ -181,7 +181,7 @@ async def service_lifespan(): if is_paying and getattr(settings, "openswarm_subscription_expires", None): id_props["subscription_expires"] = settings.openswarm_subscription_expires - svc.identify(id_props) + svc.submit("state", {"identity": id_props}) except Exception as e: logger.debug(f"Service startup event failed (non-critical): {e}")