From 1c9ab207efe5a1d90578f17cabd9ee7e2665ffd5 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Mon, 6 Jul 2026 21:43:15 -0700 Subject: [PATCH] chore: bumped version to 0.0.31 --- VERSION | 2 +- surfsense_backend/alembic/env.py | 6 +- .../171_add_runs_and_tool_output_spills.py | 4 +- .../subagents/builtins/mcp_discovery/agent.py | 4 +- .../tools/get_connected_accounts.py | 3 +- .../subagents/mcp_tools/index.py | 4 +- .../subagents/shared/run_reader.py | 18 ++- surfsense_backend/app/app.py | 8 +- .../app/capabilities/core/access/rest.py | 5 +- .../app/capabilities/core/runs.py | 4 +- .../google_maps/reviews/executor.py | 11 +- .../google_maps/scrape/executor.py | 8 +- .../google_search/scrape/executor.py | 7 +- .../capabilities/reddit/scrape/executor.py | 8 +- .../app/capabilities/web/crawl/executor.py | 4 +- .../app/capabilities/web/crawl/schemas.py | 3 +- .../capabilities/youtube/comments/executor.py | 14 +- .../capabilities/youtube/scrape/executor.py | 11 +- surfsense_backend/app/config/__init__.py | 4 +- surfsense_backend/app/db.py | 4 +- .../platforms/google_search/fetch.py | 8 +- .../platforms/google_search/parsers.py | 10 +- .../proprietary/platforms/reddit/scraper.py | 11 +- .../platforms/reddit/url_resolver.py | 4 +- .../platforms/youtube/subtitles.py | 4 +- .../app/proprietary/web_crawler/connector.py | 8 +- .../proprietary/web_crawler/site_crawler.py | 4 +- .../app/proprietary/web_crawler/url_policy.py | 4 +- .../app/services/mcp_oauth/registry.py | 4 +- .../app/services/wallet_credit.py | 7 +- .../app/tasks/celery_tasks/document_tasks.py | 1 - surfsense_backend/app/utils/crawl/contacts.py | 69 +++++++-- surfsense_backend/pyproject.toml | 2 +- .../scripts/check_migration_flow.py | 1 + .../check_zero_publication_bootstrap.py | 4 +- .../scripts/e2e_google_search.py | 121 +++++++++++----- .../scripts/e2e_reddit_scraper.py | 4 +- surfsense_backend/tests/e2e/fakes/chat_llm.py | 4 +- .../test_web_search_delegation.py | 8 +- .../test_mcp_discovery_migration.py | 19 ++- .../capabilities/access/test_rest_router.py | 8 +- .../reddit/scrape/test_executor.py | 4 +- .../tests/unit/capabilities/test_billing.py | 4 +- .../unit/capabilities/test_run_truncation.py | 50 +++++-- .../capabilities/web/crawl/test_executor.py | 6 +- .../capabilities/web/crawl/test_schemas.py | 12 +- .../platforms/google_search/test_skeleton.py | 9 +- .../unit/platforms/reddit/test_parsers.py | 31 +++- .../web_crawler/test_site_crawler.py | 4 +- .../web_crawler/test_url_policy.py | 7 +- .../services/test_web_crawl_credit_service.py | 2 - .../utils/proxy/test_dataimpulse_provider.py | 4 +- surfsense_backend/uv.lock | 134 +++++++++++++++++- surfsense_browser_extension/package.json | 2 +- surfsense_desktop/package.json | 2 +- surfsense_web/package.json | 2 +- 56 files changed, 520 insertions(+), 190 deletions(-) diff --git a/VERSION b/VERSION index f092e2be2..d788d4335 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.30 +0.0.31 diff --git a/surfsense_backend/alembic/env.py b/surfsense_backend/alembic/env.py index ce20133c6..f2130892a 100644 --- a/surfsense_backend/alembic/env.py +++ b/surfsense_backend/alembic/env.py @@ -5,12 +5,12 @@ import sys from logging.config import fileConfig import sqlalchemy as sa -from alembic.script import ScriptDirectory from sqlalchemy import pool from sqlalchemy.engine import Connection from sqlalchemy.ext.asyncio import async_engine_from_config from alembic import context +from alembic.script import ScriptDirectory # Ensure the app directory is in the Python path # This allows Alembic to find your models @@ -81,9 +81,7 @@ def _fast_forward_fresh_db(connection: Connection) -> bool: step rather than resurrecting the replay. """ for table in ("documents", "searchspaces", BOOTSTRAP_MARKER_TABLE): - if connection.execute( - sa.text("SELECT to_regclass(:t)"), {"t": table} - ).scalar(): + if connection.execute(sa.text("SELECT to_regclass(:t)"), {"t": table}).scalar(): return False if connection.execute(sa.text("SELECT to_regclass('alembic_version')")).scalar(): current = connection.execute( diff --git a/surfsense_backend/alembic/versions/171_add_runs_and_tool_output_spills.py b/surfsense_backend/alembic/versions/171_add_runs_and_tool_output_spills.py index 34c3628eb..2511ef3f9 100644 --- a/surfsense_backend/alembic/versions/171_add_runs_and_tool_output_spills.py +++ b/surfsense_backend/alembic/versions/171_add_runs_and_tool_output_spills.py @@ -41,9 +41,7 @@ def upgrade() -> None: ); """ ) - op.execute( - "CREATE INDEX IF NOT EXISTS ix_runs_workspace_id ON runs (workspace_id)" - ) + op.execute("CREATE INDEX IF NOT EXISTS ix_runs_workspace_id ON runs (workspace_id)") op.execute("CREATE INDEX IF NOT EXISTS ix_runs_user_id ON runs (user_id)") op.execute("CREATE INDEX IF NOT EXISTS ix_runs_capability ON runs (capability)") op.execute("CREATE INDEX IF NOT EXISTS ix_runs_created_at ON runs (created_at)") diff --git a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/mcp_discovery/agent.py b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/mcp_discovery/agent.py index 3a4b45319..f4cc503c3 100644 --- a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/mcp_discovery/agent.py +++ b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/mcp_discovery/agent.py @@ -50,9 +50,7 @@ def _augment_allowlist_for_collision_prefixes( continue original = meta.get("mcp_original_tool_name") if original in trusted_names and tool.name not in trusted_names: - alias_rules.append( - Rule(permission=tool.name, pattern="*", action="allow") - ) + alias_rules.append(Rule(permission=tool.name, pattern="*", action="allow")) if not alias_rules: return dependencies diff --git a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/mcp_discovery/tools/get_connected_accounts.py b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/mcp_discovery/tools/get_connected_accounts.py index 0f98b5f24..292530229 100644 --- a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/mcp_discovery/tools/get_connected_accounts.py +++ b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/mcp_discovery/tools/get_connected_accounts.py @@ -85,8 +85,7 @@ def create_get_connected_accounts_tool(*, workspace_id: int) -> BaseTool: # ``server_config`` presence == the connector produces agent # tools. Native rows without it are connected for indexing # only and need a reconnect via MCP. - "usable_in_chat": isinstance(cfg, dict) - and "server_config" in cfg, + "usable_in_chat": isinstance(cfg, dict) and "server_config" in cfg, "account": account_meta, } ) diff --git a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/mcp_tools/index.py b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/mcp_tools/index.py index eae0105ac..6359e9842 100644 --- a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/mcp_tools/index.py +++ b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/mcp_tools/index.py @@ -83,7 +83,9 @@ def resolve_tool_name_collisions( or original_name, "mcp_collision_prefixed": True, } - resolved.append(tool.model_copy(update={"name": new_name, "metadata": new_meta})) + resolved.append( + tool.model_copy(update={"name": new_name, "metadata": new_meta}) + ) return resolved diff --git a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/shared/run_reader.py b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/shared/run_reader.py index 7ef03a68c..2b2c57cc4 100644 --- a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/shared/run_reader.py +++ b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/shared/run_reader.py @@ -128,7 +128,7 @@ def _cell(value: Any) -> str: """Render one CSV cell: scalars as-is, nested structures as compact JSON.""" if value is None: return "" - if isinstance(value, (dict, list)): + if isinstance(value, dict | list): return json.dumps(value, ensure_ascii=False, default=str) return str(value) @@ -230,9 +230,7 @@ def build_run_reader_tools(*, workspace_id: int) -> list[BaseTool]: count = min(max(1, limit), _MAX_LIMIT) window = lines[start : start + count] if not window: - return ( - f"No lines at offset {start} (total {len(lines)} lines in {ref})." - ) + return f"No lines at offset {start} (total {len(lines)} lines in {ref})." window_body = "\n".join(window) start_char = max(0, char_offset) if start_char >= len(window_body) > 0: @@ -328,10 +326,14 @@ def build_run_reader_tools(*, workspace_id: int) -> list[BaseTool]: records = _rows_from_body(body, rows) if include_pattern: inc = _build_matcher(include_pattern.strip()) - records = [r for r in records if inc(" ".join(map(_cell, r.values()))) is not None] + records = [ + r for r in records if inc(" ".join(map(_cell, r.values()))) is not None + ] if exclude_pattern: exc = _build_matcher(exclude_pattern.strip()) - records = [r for r in records if exc(" ".join(map(_cell, r.values()))) is None] + records = [ + r for r in records if exc(" ".join(map(_cell, r.values()))) is None + ] if not records: return ( f"Error: no rows to export from {ref} " @@ -353,7 +355,9 @@ def build_run_reader_tools(*, workspace_id: int) -> list[BaseTool]: preview_lines = csv_text.split("\n")[:4] truncated_note = ( - f" (capped at {_EXPORT_MAX_ROWS} rows)" if row_count >= _EXPORT_MAX_ROWS else "" + f" (capped at {_EXPORT_MAX_ROWS} rows)" + if row_count >= _EXPORT_MAX_ROWS + else "" ) return ( f"Exported {row_count} rows{truncated_note} to {final_path} " diff --git a/surfsense_backend/app/app.py b/surfsense_backend/app/app.py index f5618fdf0..dde2ce7fa 100644 --- a/surfsense_backend/app/app.py +++ b/surfsense_backend/app/app.py @@ -627,9 +627,13 @@ async def _sweep_stale_scraper_runs() -> None: async with async_session_maker() as session: swept = await fail_stale_running_runs(session) if swept: - logger.info("[startup] Marked %d stale running scraper run(s) as error", swept) + logger.info( + "[startup] Marked %d stale running scraper run(s) as error", swept + ) except Exception: - logger.warning("[startup] Stale scraper-run sweep failed (non-fatal)", exc_info=True) + logger.warning( + "[startup] Stale scraper-run sweep failed (non-fatal)", exc_info=True + ) @asynccontextmanager diff --git a/surfsense_backend/app/capabilities/core/access/rest.py b/surfsense_backend/app/capabilities/core/access/rest.py index 59e8bc8ca..328a7c5a1 100644 --- a/surfsense_backend/app/capabilities/core/access/rest.py +++ b/surfsense_backend/app/capabilities/core/access/rest.py @@ -209,7 +209,10 @@ async def _execute_async_run( raise except (SurfSenseError, HTTPException) as exc: await _finalize_async( - run_id, status="error", error=str(exc), started=started, + run_id, + status="error", + error=str(exc), + started=started, progress=reporter.coarse, ) _publish_finished(run_id, "error", error=str(exc)) diff --git a/surfsense_backend/app/capabilities/core/runs.py b/surfsense_backend/app/capabilities/core/runs.py index 5ad9edbc5..161ec0bf7 100644 --- a/surfsense_backend/app/capabilities/core/runs.py +++ b/surfsense_backend/app/capabilities/core/runs.py @@ -291,7 +291,9 @@ async def record_spill( return None -async def _maybe_cleanup(session: AsyncSession, table: str, retention_days: int) -> None: +async def _maybe_cleanup( + session: AsyncSession, table: str, retention_days: int +) -> None: """Delete a bounded batch of expired rows on ~1% of inserts.""" if random.random() >= _CLEANUP_SAMPLE_RATE: return diff --git a/surfsense_backend/app/capabilities/google_maps/reviews/executor.py b/surfsense_backend/app/capabilities/google_maps/reviews/executor.py index e7edd1dd8..c6bec3775 100644 --- a/surfsense_backend/app/capabilities/google_maps/reviews/executor.py +++ b/surfsense_backend/app/capabilities/google_maps/reviews/executor.py @@ -30,14 +30,21 @@ def build_reviews_executor(scrape_fn: ReviewsFn | None = None) -> Executor: reviewsStartDate=payload.start_date, language=payload.language, ) - emit_progress("starting", "Fetching Google Maps reviews", total=payload.max_reviews, unit="review") + emit_progress( + "starting", + "Fetching Google Maps reviews", + total=payload.max_reviews, + unit="review", + ) try: items = await scrape_fn(actor_input) except SignInRequiredError as exc: raise ForbiddenError( f"Google sign in required: {exc}", code="GOOGLE_SIGNIN_REQUIRED" ) from exc - emit_progress("done", f"Scraped {len(items)} review(s)", current=len(items), unit="review") + emit_progress( + "done", f"Scraped {len(items)} review(s)", current=len(items), unit="review" + ) return ReviewsOutput(items=items) return execute diff --git a/surfsense_backend/app/capabilities/google_maps/scrape/executor.py b/surfsense_backend/app/capabilities/google_maps/scrape/executor.py index 73519b41c..12b5c25bf 100644 --- a/surfsense_backend/app/capabilities/google_maps/scrape/executor.py +++ b/surfsense_backend/app/capabilities/google_maps/scrape/executor.py @@ -33,14 +33,18 @@ def build_scrape_executor(scrape_fn: ScrapeFn | None = None) -> Executor: maxReviews=payload.max_reviews, maxImages=payload.max_images, ) - emit_progress("starting", "Searching Google Maps", total=payload.max_places, unit="place") + emit_progress( + "starting", "Searching Google Maps", total=payload.max_places, unit="place" + ) try: items = await scrape_fn(actor_input) except SignInRequiredError as exc: raise ForbiddenError( f"Google sign in required: {exc}", code="GOOGLE_SIGNIN_REQUIRED" ) from exc - emit_progress("done", f"Scraped {len(items)} place(s)", current=len(items), unit="place") + emit_progress( + "done", f"Scraped {len(items)} place(s)", current=len(items), unit="place" + ) return ScrapeOutput(items=items) return execute diff --git a/surfsense_backend/app/capabilities/google_search/scrape/executor.py b/surfsense_backend/app/capabilities/google_search/scrape/executor.py index 593ec6240..022f1eac6 100644 --- a/surfsense_backend/app/capabilities/google_search/scrape/executor.py +++ b/surfsense_backend/app/capabilities/google_search/scrape/executor.py @@ -43,7 +43,12 @@ def build_scrape_executor(scrape_fn: ScrapeFn | None = None) -> Executor: unit="page", ) items = await scrape_fn(actor_input, limit=_MAX_SERP_ITEMS) - emit_progress("done", f"Scraped {len(items)} SERP page(s)", current=len(items), unit="page") + emit_progress( + "done", + f"Scraped {len(items)} SERP page(s)", + current=len(items), + unit="page", + ) return ScrapeOutput(items=items) return execute diff --git a/surfsense_backend/app/capabilities/reddit/scrape/executor.py b/surfsense_backend/app/capabilities/reddit/scrape/executor.py index a3fdc8557..da72c8bcf 100644 --- a/surfsense_backend/app/capabilities/reddit/scrape/executor.py +++ b/surfsense_backend/app/capabilities/reddit/scrape/executor.py @@ -36,7 +36,9 @@ def build_scrape_executor(scrape_fn: ScrapeFn | None = None) -> Executor: postDateLimit=payload.post_date_limit, commentDateLimit=payload.comment_date_limit, ) - emit_progress("starting", "Resolving Reddit targets", total=payload.max_items, unit="item") + emit_progress( + "starting", "Resolving Reddit targets", total=payload.max_items, unit="item" + ) try: items = await scrape_fn(actor_input, limit=payload.max_items) except RedditAccessBlockedError as exc: @@ -46,7 +48,9 @@ def build_scrape_executor(scrape_fn: ScrapeFn | None = None) -> Executor: f"Reddit refused anonymous access: {exc}", code="REDDIT_ACCESS_BLOCKED", ) from exc - emit_progress("done", f"Scraped {len(items)} item(s)", current=len(items), unit="item") + emit_progress( + "done", f"Scraped {len(items)} item(s)", current=len(items), unit="item" + ) return ScrapeOutput(items=items) return execute diff --git a/surfsense_backend/app/capabilities/web/crawl/executor.py b/surfsense_backend/app/capabilities/web/crawl/executor.py index 069486b67..75d2f9c48 100644 --- a/surfsense_backend/app/capabilities/web/crawl/executor.py +++ b/surfsense_backend/app/capabilities/web/crawl/executor.py @@ -60,7 +60,9 @@ def build_crawl_executor(engine: WebCrawlerConnector | None = None) -> Executor: unit="page", ) items = [_to_item(page, payload.maxLength) for page in pages] - emit_progress("done", f"Crawled {len(items)} page(s)", current=len(items), unit="page") + emit_progress( + "done", f"Crawled {len(items)} page(s)", current=len(items), unit="page" + ) return CrawlOutput( items=items, contacts=_aggregate_contacts(items), diff --git a/surfsense_backend/app/capabilities/web/crawl/schemas.py b/surfsense_backend/app/capabilities/web/crawl/schemas.py index 6894bd8e9..d6c9c38a3 100644 --- a/surfsense_backend/app/capabilities/web/crawl/schemas.py +++ b/surfsense_backend/app/capabilities/web/crawl/schemas.py @@ -165,7 +165,8 @@ class CrawlItem(BaseModel): default=None, description="Crawl provenance (loaded URL, depth, referrer)." ) markdown: str | None = Field( - default=None, description="Cleaned page content as markdown (null unless success)." + default=None, + description="Cleaned page content as markdown (null unless success).", ) metadata: dict[str, str] | None = Field( default=None, description="Page metadata such as title and description." diff --git a/surfsense_backend/app/capabilities/youtube/comments/executor.py b/surfsense_backend/app/capabilities/youtube/comments/executor.py index c311a7a60..a682f118d 100644 --- a/surfsense_backend/app/capabilities/youtube/comments/executor.py +++ b/surfsense_backend/app/capabilities/youtube/comments/executor.py @@ -25,9 +25,19 @@ def build_comments_executor(scrape_fn: CommentsFn | None = None) -> Executor: maxComments=payload.max_comments, sortCommentsBy=payload.sort_by, ) - emit_progress("starting", "Fetching YouTube comments", total=payload.max_comments, unit="comment") + emit_progress( + "starting", + "Fetching YouTube comments", + total=payload.max_comments, + unit="comment", + ) items = await scrape_fn(actor_input) - emit_progress("done", f"Scraped {len(items)} comment(s)", current=len(items), unit="comment") + emit_progress( + "done", + f"Scraped {len(items)} comment(s)", + current=len(items), + unit="comment", + ) return CommentsOutput(items=items) return execute diff --git a/surfsense_backend/app/capabilities/youtube/scrape/executor.py b/surfsense_backend/app/capabilities/youtube/scrape/executor.py index 0e7539005..1a1ce175d 100644 --- a/surfsense_backend/app/capabilities/youtube/scrape/executor.py +++ b/surfsense_backend/app/capabilities/youtube/scrape/executor.py @@ -31,9 +31,16 @@ def build_scrape_executor(scrape_fn: ScrapeFn | None = None) -> Executor: downloadSubtitles=payload.download_subtitles, subtitlesLanguage=payload.subtitles_language, ) - emit_progress("starting", "Resolving YouTube targets", total=payload.max_results, unit="video") + emit_progress( + "starting", + "Resolving YouTube targets", + total=payload.max_results, + unit="video", + ) items = await scrape_fn(actor_input) - emit_progress("done", f"Scraped {len(items)} video(s)", current=len(items), unit="video") + emit_progress( + "done", f"Scraped {len(items)} video(s)", current=len(items), unit="video" + ) return ScrapeOutput(items=items) return execute diff --git a/surfsense_backend/app/config/__init__.py b/surfsense_backend/app/config/__init__.py index 68ea93de6..5d8e96881 100644 --- a/surfsense_backend/app/config/__init__.py +++ b/surfsense_backend/app/config/__init__.py @@ -713,9 +713,7 @@ class Config: YOUTUBE_MICROS_PER_VIDEO = int(os.getenv("YOUTUBE_MICROS_PER_VIDEO", "2500")) # Kept separate from the video rate so comments can be re-tuned toward the # cheaper per-comment market ($0.40-2.00/1k) without touching video pricing. - YOUTUBE_MICROS_PER_COMMENT = int( - os.getenv("YOUTUBE_MICROS_PER_COMMENT", "1500") - ) + YOUTUBE_MICROS_PER_COMMENT = int(os.getenv("YOUTUBE_MICROS_PER_COMMENT", "1500")) # Low-balance WARNING threshold (micro-USD). Surfaced by the quota service # so the UI can nudge the user to top up / enable auto-reload. $0.50. diff --git a/surfsense_backend/app/db.py b/surfsense_backend/app/db.py index b44ec2e13..380a8b2bf 100644 --- a/surfsense_backend/app/db.py +++ b/surfsense_backend/app/db.py @@ -2795,9 +2795,7 @@ class Run(Base, TimestampMixin): __tablename__ = "runs" __allow_unmapped__ = True - __table_args__ = ( - Index("ix_runs_workspace_created", "workspace_id", "created_at"), - ) + __table_args__ = (Index("ix_runs_workspace_created", "workspace_id", "created_at"),) id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) workspace_id = Column( diff --git a/surfsense_backend/app/proprietary/platforms/google_search/fetch.py b/surfsense_backend/app/proprietary/platforms/google_search/fetch.py index c969d6bc4..68cc90d26 100644 --- a/surfsense_backend/app/proprietary/platforms/google_search/fetch.py +++ b/surfsense_backend/app/proprietary/platforms/google_search/fetch.py @@ -31,6 +31,7 @@ if we add another sticky vendor is to extend ``_STICKY_HOSTS``. from __future__ import annotations import asyncio +import contextlib import logging import random import sys @@ -158,8 +159,7 @@ async def _vet_fresh_ip(url: str, base: str) -> str | None: return proxy if await _precheck(url, proxy) else None tasks = [ - asyncio.create_task(vet(_sticky_variant(base))) - for _ in range(_VET_CONCURRENCY) + asyncio.create_task(vet(_sticky_variant(base))) for _ in range(_VET_CONCURRENCY) ] winner: str | None = None for fut in asyncio.as_completed(tasks): @@ -306,10 +306,8 @@ async def _drop_session_on_loop(mobile: bool) -> None: async with _session_lock: session = _sessions.pop(mobile, None) if session is not None: - try: + with contextlib.suppress(Exception): # already dead; nothing to salvage await session.close() - except Exception: # already dead; nothing to salvage - pass async def _drop_session(mobile: bool) -> None: diff --git a/surfsense_backend/app/proprietary/platforms/google_search/parsers.py b/surfsense_backend/app/proprietary/platforms/google_search/parsers.py index e74fb4549..04e278079 100644 --- a/surfsense_backend/app/proprietary/platforms/google_search/parsers.py +++ b/surfsense_backend/app/proprietary/platforms/google_search/parsers.py @@ -218,7 +218,9 @@ def parse_organic(doc: Adaptor, *, include_icons: bool = False) -> list[OrganicR return results -def parse_paid_results(doc: Adaptor, *, include_icons: bool = False) -> list[PaidResult]: +def parse_paid_results( + doc: Adaptor, *, include_icons: bool = False +) -> list[PaidResult]: """Text ads (``div[data-text-ad]``), covering the top and bottom ad blocks. Fields mirror an organic result: the heading is the title, the ad's anchor @@ -239,7 +241,11 @@ def parse_paid_results(doc: Adaptor, *, include_icons: bool = False) -> list[Pai description = None for cand in block.css(".Va3FIb"): text = _text(cand) - if text and text != title and (description is None or len(text) > len(description)): + if ( + text + and text != title + and (description is None or len(text) > len(description)) + ): description = text slot = block.attrib.get("data-ta-slot-pos") ads.append( diff --git a/surfsense_backend/app/proprietary/platforms/reddit/scraper.py b/surfsense_backend/app/proprietary/platforms/reddit/scraper.py index 0af12d5e5..a14e1c13f 100644 --- a/surfsense_backend/app/proprietary/platforms/reddit/scraper.py +++ b/surfsense_backend/app/proprietary/platforms/reddit/scraper.py @@ -291,8 +291,8 @@ async def _user_flow( date_limit=input_model.postDateLimit, ): # A user listing mixes posts (t3) and comments (t1); a post has a title. - parsed = parse_post(data) if data.get("title") is not None else parse_comment( - data + parsed = ( + parse_post(data) if data.get("title") is not None else parse_comment(data) ) item = _emit(parsed, include_nsfw=input_model.includeNSFW) if item is not None: @@ -401,8 +401,7 @@ async def iter_reddit( continue resolved.append(r) jobs = [ - _dispatch(r, input_model) - for r in _capped_targets(resolved, input_model) + _dispatch(r, input_model) for r in _capped_targets(resolved, input_model) ] async for item in fan_out(jobs): yield item @@ -449,9 +448,7 @@ async def scrape_reddit( results: list[dict[str, Any]] = [] async for item in iter_reddit(input_model): results.append(item) - emit_progress( - "scraping", current=len(results), total=limit, unit="item" - ) + emit_progress("scraping", current=len(results), total=limit, unit="item") if limit is not None and len(results) >= limit: break return results diff --git a/surfsense_backend/app/proprietary/platforms/reddit/url_resolver.py b/surfsense_backend/app/proprietary/platforms/reddit/url_resolver.py index 0af80b524..8490d9de6 100644 --- a/surfsense_backend/app/proprietary/platforms/reddit/url_resolver.py +++ b/surfsense_backend/app/proprietary/platforms/reddit/url_resolver.py @@ -28,9 +28,7 @@ _REDDIT_HOSTS = frozenset( ) # Listing sorts that can appear as a trailing subreddit path segment. -_SUBREDDIT_SORTS = frozenset( - {"hot", "new", "top", "rising", "controversial", "best"} -) +_SUBREDDIT_SORTS = frozenset({"hot", "new", "top", "rising", "controversial", "best"}) _USER_CONTENT = frozenset({"overview", "submitted", "comments"}) diff --git a/surfsense_backend/app/proprietary/platforms/youtube/subtitles.py b/surfsense_backend/app/proprietary/platforms/youtube/subtitles.py index 31cb672f0..c249d7d6c 100644 --- a/surfsense_backend/app/proprietary/platforms/youtube/subtitles.py +++ b/surfsense_backend/app/proprietary/platforms/youtube/subtitles.py @@ -69,9 +69,7 @@ def _fetch_subtitles_sync( api = _build_client() try: transcript_list = api.list(video_id) - transcript = _select_transcript( - transcript_list, language, prefer_generated - ) + transcript = _select_transcript(transcript_list, language, prefer_generated) fetched = transcript.fetch() break except RequestBlocked: # covers IpBlocked; rotate to a fresh exit IP diff --git a/surfsense_backend/app/proprietary/web_crawler/connector.py b/surfsense_backend/app/proprietary/web_crawler/connector.py index c44340334..76118b118 100644 --- a/surfsense_backend/app/proprietary/web_crawler/connector.py +++ b/surfsense_backend/app/proprietary/web_crawler/connector.py @@ -91,7 +91,9 @@ _CURRENCY_AMOUNT_RE = re.compile( re.IGNORECASE, ) -_STRIP_XPATH = "//script | //style | //noscript | //template | //svg | //iframe | //head" +_STRIP_XPATH = ( + "//script | //style | //noscript | //template | //svg | //iframe | //head" +) def _visible_text(raw_html: str) -> str: @@ -278,9 +280,7 @@ class WebCrawlerConnector: else: reached_without_content = True errors.append("Scrapling static: empty extraction") - self._log_tier_outcome( - "scrapling-static", url, tier_start, "empty" - ) + self._log_tier_outcome("scrapling-static", url, tier_start, "empty") except Exception as exc: errors.append(f"Scrapling static: {exc!s}") self._log_tier_outcome( diff --git a/surfsense_backend/app/proprietary/web_crawler/site_crawler.py b/surfsense_backend/app/proprietary/web_crawler/site_crawler.py index 0fef706a9..546e74aee 100644 --- a/surfsense_backend/app/proprietary/web_crawler/site_crawler.py +++ b/surfsense_backend/app/proprietary/web_crawler/site_crawler.py @@ -156,9 +156,7 @@ class _SiteSpider(Spider): # ladder + proxy rotation; never let the spider re-fetch on top of that. return False - async def parse( - self, response: Response - ) -> AsyncGenerator[Request | None, None]: + async def parse(self, response: Response) -> AsyncGenerator[Request | None, None]: outcome: CrawlOutcome = response._outcome # type: ignore[attr-defined] depth: int = response.meta.get("_depth", 0) referrer: str | None = response.meta.get("_referrer") diff --git a/surfsense_backend/app/proprietary/web_crawler/url_policy.py b/surfsense_backend/app/proprietary/web_crawler/url_policy.py index 46f74c8c3..db07d6aa9 100644 --- a/surfsense_backend/app/proprietary/web_crawler/url_policy.py +++ b/surfsense_backend/app/proprietary/web_crawler/url_policy.py @@ -73,9 +73,7 @@ def _anchor_context(anchor: Any) -> str: return "" -def extract_link_records( - page_html: str | None, base_url: str -) -> list[dict[str, str]]: +def extract_link_records(page_html: str | None, base_url: str) -> list[dict[str, str]]: """Structured ```` inventory: ``{url, text, context, rel, kind}`` per target. ``kind`` is one of ``internal`` (same site as ``base_url``), ``external``, diff --git a/surfsense_backend/app/services/mcp_oauth/registry.py b/surfsense_backend/app/services/mcp_oauth/registry.py index f4cfabfd0..c61aedfd6 100644 --- a/surfsense_backend/app/services/mcp_oauth/registry.py +++ b/surfsense_backend/app/services/mcp_oauth/registry.py @@ -218,9 +218,7 @@ MCP_SERVICES: dict[str, MCPServiceConfig] = { "create-pages", "update-page", ], - readonly_tools=frozenset( - {"notion-search", "notion-fetch", "search", "fetch"} - ), + readonly_tools=frozenset({"notion-search", "notion-fetch", "search", "fetch"}), account_metadata_keys=["workspace_name"], ), "confluence": MCPServiceConfig( diff --git a/surfsense_backend/app/services/wallet_credit.py b/surfsense_backend/app/services/wallet_credit.py index 218dd777f..cfac2a335 100644 --- a/surfsense_backend/app/services/wallet_credit.py +++ b/surfsense_backend/app/services/wallet_credit.py @@ -22,7 +22,12 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.services.etl_credit_service import InsufficientCreditsError -__all__ = ["InsufficientCreditsError", "apply_debit", "check_balance", "spendable_micros"] +__all__ = [ + "InsufficientCreditsError", + "apply_debit", + "check_balance", + "spendable_micros", +] async def spendable_micros(session: AsyncSession, user_id: str | UUID) -> int: diff --git a/surfsense_backend/app/tasks/celery_tasks/document_tasks.py b/surfsense_backend/app/tasks/celery_tasks/document_tasks.py index 3e26050ef..e2ca5345e 100644 --- a/surfsense_backend/app/tasks/celery_tasks/document_tasks.py +++ b/surfsense_backend/app/tasks/celery_tasks/document_tasks.py @@ -1393,4 +1393,3 @@ async def _index_uploaded_folder_files_async( heartbeat_task.cancel() if notification_id is not None: _stop_heartbeat(notification_id) - diff --git a/surfsense_backend/app/utils/crawl/contacts.py b/surfsense_backend/app/utils/crawl/contacts.py index b487b10f1..872db33d9 100644 --- a/surfsense_backend/app/utils/crawl/contacts.py +++ b/surfsense_backend/app/utils/crawl/contacts.py @@ -87,9 +87,30 @@ _NOISE_EMAIL_DOMAINS = frozenset( # or version-pinned dep (``react@18.2.0.js``) matches the email shape. _ASSET_TLDS = frozenset( { - "png", "jpg", "jpeg", "gif", "svg", "webp", "ico", "bmp", - "css", "js", "mjs", "cjs", "ts", "map", "json", "xml", - "woff", "woff2", "ttf", "eot", "otf", "php", "html", "htm", + "png", + "jpg", + "jpeg", + "gif", + "svg", + "webp", + "ico", + "bmp", + "css", + "js", + "mjs", + "cjs", + "ts", + "map", + "json", + "xml", + "woff", + "woff2", + "ttf", + "eot", + "otf", + "php", + "html", + "htm", } ) @@ -100,19 +121,47 @@ _EMAIL_RE = re.compile(r"[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}") # common locals like hello/info/contact/support. _PLACEHOLDER_EMAIL_LOCALS = frozenset( { - "youremail", "yourname", "youraddress", "myemail", "email", "name", - "user", "username", "someone", "somebody", "johndoe", "janedoe", - "firstname", "lastname", "firstnamelastname", "firstlast", - "test", "example", "sample", "placeholder", + "youremail", + "yourname", + "youraddress", + "myemail", + "email", + "name", + "user", + "username", + "someone", + "somebody", + "johndoe", + "janedoe", + "firstname", + "lastname", + "firstnamelastname", + "firstlast", + "test", + "example", + "sample", + "placeholder", } ) # Placeholder profile handles left in site templates ("github.com/username"). _PLACEHOLDER_SOCIAL_SEGMENTS = frozenset( { - "username", "yourusername", "yourhandle", "handle", "user", - "profile", "yourprofile", "yourname", "yourpage", "pagename", - "youraccount", "account", "example", "placeholder", "yourcompany", + "username", + "yourusername", + "yourhandle", + "handle", + "user", + "profile", + "yourprofile", + "yourname", + "yourpage", + "pagename", + "youraccount", + "account", + "example", + "placeholder", + "yourcompany", } ) diff --git a/surfsense_backend/pyproject.toml b/surfsense_backend/pyproject.toml index 002c527c0..2faa2be17 100644 --- a/surfsense_backend/pyproject.toml +++ b/surfsense_backend/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "surf-new-backend" -version = "0.0.30" +version = "0.0.31" description = "SurfSense Backend" requires-python = ">=3.12" dependencies = [ diff --git a/surfsense_backend/scripts/check_migration_flow.py b/surfsense_backend/scripts/check_migration_flow.py index 155e5ac84..18587f836 100644 --- a/surfsense_backend/scripts/check_migration_flow.py +++ b/surfsense_backend/scripts/check_migration_flow.py @@ -94,6 +94,7 @@ async def set_version(version: str | None) -> None: async def assert_at_head() -> None: import asyncpg + from alembic.script import ScriptDirectory head = ScriptDirectory(str(BACKEND_DIR / "alembic")).get_current_head() diff --git a/surfsense_backend/scripts/check_zero_publication_bootstrap.py b/surfsense_backend/scripts/check_zero_publication_bootstrap.py index e8956992a..3738901c1 100644 --- a/surfsense_backend/scripts/check_zero_publication_bootstrap.py +++ b/surfsense_backend/scripts/check_zero_publication_bootstrap.py @@ -40,7 +40,9 @@ async def main() -> None: await admin.execute(f'DROP DATABASE IF EXISTS "{SCRATCH_DB}" WITH (FORCE)') await admin.close() - print("OK: ensure_publication creates and verifies on a create_all DB, idempotently.") + print( + "OK: ensure_publication creates and verifies on a create_all DB, idempotently." + ) asyncio.run(main()) diff --git a/surfsense_backend/scripts/e2e_google_search.py b/surfsense_backend/scripts/e2e_google_search.py index acf062819..9c3c02fcf 100644 --- a/surfsense_backend/scripts/e2e_google_search.py +++ b/surfsense_backend/scripts/e2e_google_search.py @@ -28,7 +28,9 @@ sys.path.insert(0, str(_ROOT)) load_dotenv(_ROOT / ".env") logging.basicConfig(level=logging.WARNING) -logging.getLogger("app.proprietary.platforms.google_search.scraper").setLevel(logging.INFO) +logging.getLogger("app.proprietary.platforms.google_search.scraper").setLevel( + logging.INFO +) from app.proprietary.platforms.google_search import ( # noqa: E402 GoogleSearchScrapeInput, @@ -41,15 +43,19 @@ async def run_ai_mode(label: str, *, queries: str) -> None: print(f"\n=== {label} ===") t0 = time.perf_counter() inp = GoogleSearchScrapeInput( - queries=queries, countryCode="us", languageCode="en", + queries=queries, + countryCode="us", + languageCode="en", aiModeSearch={"enableAiMode": True}, ) items = await scrape_serps(inp, limit=2) ai_items = [i for i in items if i["aiModeResult"]] assert ai_items, f"{label}: no aiModeResult item emitted" res = ai_items[0]["aiModeResult"] - print(f" text={len(res['text'])} chars, sources={len(res['sources'])} " - f"({time.perf_counter()-t0:.0f}s)") + print( + f" text={len(res['text'])} chars, sources={len(res['sources'])} " + f"({time.perf_counter() - t0:.0f}s)" + ) print(f" {res['text'][:130]!r}") for s in res["sources"][:3]: print(f" src: {(s['title'] or '')[:60]!r}") @@ -59,9 +65,16 @@ async def run_ai_mode(label: str, *, queries: str) -> None: async def run( - label: str, *, expect_ads=False, expect_products=False, expect_paa_answers=False, - expect_sitelinks=False, expect_aio=False, expect_device=None, - expect_icons=False, **kwargs + label: str, + *, + expect_ads=False, + expect_products=False, + expect_paa_answers=False, + expect_sitelinks=False, + expect_aio=False, + expect_device=None, + expect_icons=False, + **kwargs, ) -> None: print(f"\n=== {label} ===") t0 = time.perf_counter() @@ -72,18 +85,24 @@ async def run( paa_answered = [p for p in it["peopleAlsoAsk"] if p["answer"]] sitelinked = [o for o in it["organicResults"] if o["siteLinks"]] print(f" term={it['searchQuery']['term']!r} resultsTotal={it['resultsTotal']}") - print(f" organic={len(it['organicResults'])} paidResults={len(it['paidResults'])} " - f"paidProducts={len(it['paidProducts'])} related={len(it['relatedQueries'])} " - f"suggested={len(it['suggestedResults'])} " - f"paa={len(it['peopleAlsoAsk'])} (answered={len(paa_answered)}) " - f"({time.perf_counter()-t0:.0f}s)") + print( + f" organic={len(it['organicResults'])} paidResults={len(it['paidResults'])} " + f"paidProducts={len(it['paidProducts'])} related={len(it['relatedQueries'])} " + f"suggested={len(it['suggestedResults'])} " + f"paa={len(it['peopleAlsoAsk'])} (answered={len(paa_answered)}) " + f"({time.perf_counter() - t0:.0f}s)" + ) for o in sitelinked[:2]: - print(f" [sitelinks on #{o['position']}] " - + ", ".join(s["title"] for s in o["siteLinks"][:5])) + print( + f" [sitelinks on #{o['position']}] " + + ", ".join(s["title"] for s in o["siteLinks"][:5]) + ) aio = it["aiOverview"] if aio: - print(f" [aiOverview] content={len(aio['content'])} chars, " - f"sources={len(aio['sources'])}") + print( + f" [aiOverview] content={len(aio['content'])} chars, " + f"sources={len(aio['sources'])}" + ) print(f" {aio['content'][:110]!r}") for s in aio["sources"][:3]: print(f" src: {(s['title'] or '')[:55]!r}") @@ -113,10 +132,15 @@ async def run( f"{label}: device={it['searchQuery']['device']}" ) if expect_icons: - iconed = [o for o in it["organicResults"] - if (o["icon"] or "").startswith("data:image")] - print(f" [icons] {len(iconed)}/{len(it['organicResults'])} organic " - f"carry a base64 favicon") + iconed = [ + o + for o in it["organicResults"] + if (o["icon"] or "").startswith("data:image") + ] + print( + f" [icons] {len(iconed)}/{len(it['organicResults'])} organic " + f"carry a base64 favicon" + ) assert iconed, f"{label}: expected base64 icons on organic results" @@ -124,24 +148,47 @@ _CASES = { "plain": lambda: run("plain query", queries="python asyncio tutorial"), "site": lambda: run("site: filter", queries="machine learning", site="arxiv.org"), "ads": lambda: run("text ads", queries="car insurance quotes", expect_ads=True), - "products": lambda: run("product ads", queries="buy running shoes", expect_products=True), - "focus": lambda: run("focusOnPaidAds (commercial)", queries="car insurance quotes", - focusOnPaidAds=True, expect_ads=True), - "focus-neg": lambda: run("focusOnPaidAds (non-commercial, retries capped)", - queries="python asyncio tutorial", focusOnPaidAds=True), - "paa": lambda: run("people also ask", queries="what is seo", expect_paa_answers=True), - "sitelinks": lambda: run("sitelinks + suggested (brand query)", queries="amazon", - expect_sitelinks=True), + "products": lambda: run( + "product ads", queries="buy running shoes", expect_products=True + ), + "focus": lambda: run( + "focusOnPaidAds (commercial)", + queries="car insurance quotes", + focusOnPaidAds=True, + expect_ads=True, + ), + "focus-neg": lambda: run( + "focusOnPaidAds (non-commercial, retries capped)", + queries="python asyncio tutorial", + focusOnPaidAds=True, + ), + "paa": lambda: run( + "people also ask", queries="what is seo", expect_paa_answers=True + ), + "sitelinks": lambda: run( + "sitelinks + suggested (brand query)", queries="amazon", expect_sitelinks=True + ), "aio": lambda: run("AI Overview", queries="benefits of green tea", expect_aio=True), - "mobile": lambda: run("mobile layout (mobileResults)", queries="best seo tools", - mobileResults=True, expect_device="MOBILE"), - "unfiltered": lambda: run("includeUnfilteredResults (filter=0)", - queries="python asyncio tutorial", - includeUnfilteredResults=True), - "icons": lambda: run("includeIcons (base64 favicons)", queries="github", - includeIcons=True, expect_icons=True), - "aimode": lambda: run_ai_mode("Google AI Mode (udm=50)", - queries="what is quantum computing"), + "mobile": lambda: run( + "mobile layout (mobileResults)", + queries="best seo tools", + mobileResults=True, + expect_device="MOBILE", + ), + "unfiltered": lambda: run( + "includeUnfilteredResults (filter=0)", + queries="python asyncio tutorial", + includeUnfilteredResults=True, + ), + "icons": lambda: run( + "includeIcons (base64 favicons)", + queries="github", + includeIcons=True, + expect_icons=True, + ), + "aimode": lambda: run_ai_mode( + "Google AI Mode (udm=50)", queries="what is quantum computing" + ), } diff --git a/surfsense_backend/scripts/e2e_reddit_scraper.py b/surfsense_backend/scripts/e2e_reddit_scraper.py index 941f009aa..7bedc0812 100644 --- a/surfsense_backend/scripts/e2e_reddit_scraper.py +++ b/surfsense_backend/scripts/e2e_reddit_scraper.py @@ -182,9 +182,7 @@ async def step5_dump_fixtures() -> bool: wrote.append("sample_post.json") # A single comment thing, for the comment-mapping fixture. comment_kids = children(post[1]) if len(post) > 1 else [] - first_comment = next( - (c for c in comment_kids if c.get("kind") == "t1"), None - ) + first_comment = next((c for c in comment_kids if c.get("kind") == "t1"), None) if first_comment: (_FIXTURE_DIR / "sample_comment.json").write_text( json.dumps(first_comment), encoding="utf-8" diff --git a/surfsense_backend/tests/e2e/fakes/chat_llm.py b/surfsense_backend/tests/e2e/fakes/chat_llm.py index 4e6be0995..6c8d2429e 100644 --- a/surfsense_backend/tests/e2e/fakes/chat_llm.py +++ b/surfsense_backend/tests/e2e/fakes/chat_llm.py @@ -562,8 +562,8 @@ class FakeChatLLM(BaseChatModel): # Marker unique to a connector subagent's prompt: the main agent must # delegate via ``task``; only the subagent has connector tools registered. - in_connector_subagent = ( - "connected-apps specialist" in _messages_to_text(messages) + in_connector_subagent = "connected-apps specialist" in _messages_to_text( + messages ) # Main agent: delegate live-tool connector work to its subagent (which diff --git a/surfsense_backend/tests/integration/agents/multi_agent_chat/test_web_search_delegation.py b/surfsense_backend/tests/integration/agents/multi_agent_chat/test_web_search_delegation.py index 04b09af7d..70d624089 100644 --- a/surfsense_backend/tests/integration/agents/multi_agent_chat/test_web_search_delegation.py +++ b/surfsense_backend/tests/integration/agents/multi_agent_chat/test_web_search_delegation.py @@ -29,9 +29,7 @@ def _last_ai_text(messages: list) -> str | None: @pytest.mark.asyncio -async def test_web_query_delegates_to_google_search( - db_session, db_user, db_workspace -): +async def test_web_query_delegates_to_google_search(db_session, db_user, db_workspace): """A web-search query routes through ``task(google_search)`` and resumes. Scripted sequence (the fake model is shared and consumed in order across the @@ -76,9 +74,7 @@ async def test_web_query_delegates_to_google_search( ) task_tool_messages = [ - m - for m in result["messages"] - if isinstance(m, ToolMessage) and m.name == "task" + m for m in result["messages"] if isinstance(m, ToolMessage) and m.name == "task" ] assert task_tool_messages, "web query did not delegate through the task tool" assert _last_ai_text(result["messages"]) == ( diff --git a/surfsense_backend/tests/unit/agents/multi_agent_chat/test_mcp_discovery_migration.py b/surfsense_backend/tests/unit/agents/multi_agent_chat/test_mcp_discovery_migration.py index 5205e363a..428b2f24f 100644 --- a/surfsense_backend/tests/unit/agents/multi_agent_chat/test_mcp_discovery_migration.py +++ b/surfsense_backend/tests/unit/agents/multi_agent_chat/test_mcp_discovery_migration.py @@ -62,13 +62,15 @@ def _tool(name: str, metadata: dict) -> StructuredTool: def test_all_mcp_connectors_route_to_discovery(): for connector_type in _MCP_ROUTED: assert ( - CONNECTOR_TYPE_TO_CONNECTOR_AGENT_MAPS[connector_type] - == MCP_DISCOVERY_NAME + CONNECTOR_TYPE_TO_CONNECTOR_AGENT_MAPS[connector_type] == MCP_DISCOVERY_NAME ), connector_type def test_file_connectors_keep_native_routes(): - assert CONNECTOR_TYPE_TO_CONNECTOR_AGENT_MAPS["GOOGLE_DRIVE_CONNECTOR"] == "google_drive" + assert ( + CONNECTOR_TYPE_TO_CONNECTOR_AGENT_MAPS["GOOGLE_DRIVE_CONNECTOR"] + == "google_drive" + ) assert CONNECTOR_TYPE_TO_CONNECTOR_AGENT_MAPS["DROPBOX_CONNECTOR"] == "dropbox" assert CONNECTOR_TYPE_TO_CONNECTOR_AGENT_MAPS["ONEDRIVE_CONNECTOR"] == "onedrive" @@ -87,7 +89,9 @@ def test_discovery_gating_is_any_of(): def test_discovery_gating_tokens_match_routed_connectors(): - assert SUBAGENT_TO_REQUIRED_CONNECTOR_MAP[MCP_DISCOVERY_NAME] == frozenset(_MCP_ROUTED) + assert SUBAGENT_TO_REQUIRED_CONNECTOR_MAP[MCP_DISCOVERY_NAME] == frozenset( + _MCP_ROUTED + ) def test_legacy_aliases_resolve_to_a_live_subagent(): @@ -104,7 +108,12 @@ def test_collision_only_prefixes_shared_names(): _tool("search", {"mcp_connector_id": 2, "mcp_transport": "http"}), _tool("list_bases", {"mcp_connector_id": 2, "mcp_transport": "http"}), ] - resolved = {t.name: t for t in resolve_tool_name_collisions(tools, {1: "NOTION_CONNECTOR", 2: "AIRTABLE_CONNECTOR"})} + resolved = { + t.name: t + for t in resolve_tool_name_collisions( + tools, {1: "NOTION_CONNECTOR", 2: "AIRTABLE_CONNECTOR"} + ) + } # The unique tool keeps its bare name (trusted_tools / history stay valid). assert "list_bases" in resolved diff --git a/surfsense_backend/tests/unit/capabilities/access/test_rest_router.py b/surfsense_backend/tests/unit/capabilities/access/test_rest_router.py index b0e64032f..a406f29bd 100644 --- a/surfsense_backend/tests/unit/capabilities/access/test_rest_router.py +++ b/surfsense_backend/tests/unit/capabilities/access/test_rest_router.py @@ -423,8 +423,12 @@ async def test_run_events_replays_buffer_then_finishes(monkeypatch): row = _fake_run_row(status="running") raw = str(row.id) - run_event_bus.publish(raw, {"type": "run.progress", "phase": "scraping", "current": 1}) - run_event_bus.publish(raw, {"type": "run.finished", "status": "success", "item_count": 2}) + run_event_bus.publish( + raw, {"type": "run.progress", "phase": "scraping", "current": 1} + ) + run_event_bus.publish( + raw, {"type": "run.finished", "status": "success", "item_count": 2} + ) app = _build_app_with_rows(monkeypatch, [row]) try: diff --git a/surfsense_backend/tests/unit/capabilities/reddit/scrape/test_executor.py b/surfsense_backend/tests/unit/capabilities/reddit/scrape/test_executor.py index ca302f82f..257b974a0 100644 --- a/surfsense_backend/tests/unit/capabilities/reddit/scrape/test_executor.py +++ b/surfsense_backend/tests/unit/capabilities/reddit/scrape/test_executor.py @@ -43,7 +43,9 @@ async def test_maps_urls_to_start_urls_and_wraps_items(): assert out.items[0].dataType == "post" (actor_input, _limit) = scraper.calls[0] - assert [u.url for u in actor_input.startUrls] == ["https://www.reddit.com/r/python/"] + assert [u.url for u in actor_input.startUrls] == [ + "https://www.reddit.com/r/python/" + ] assert actor_input.searches == [] diff --git a/surfsense_backend/tests/unit/capabilities/test_billing.py b/surfsense_backend/tests/unit/capabilities/test_billing.py index 28021af47..36d9a2978 100644 --- a/surfsense_backend/tests/unit/capabilities/test_billing.py +++ b/surfsense_backend/tests/unit/capabilities/test_billing.py @@ -233,7 +233,9 @@ async def test_gate_reserves_worst_case_captcha_when_solving_enabled(monkeypatch with pytest.raises(InsufficientCreditsError): await gate_capability( - CrawlInput(startUrls=["https://a.com"]), BillingUnit.WEB_CRAWL, _ctx(session) + CrawlInput(startUrls=["https://a.com"]), + BillingUnit.WEB_CRAWL, + _ctx(session), ) diff --git a/surfsense_backend/tests/unit/capabilities/test_run_truncation.py b/surfsense_backend/tests/unit/capabilities/test_run_truncation.py index 43f1519be..5723c2ff2 100644 --- a/surfsense_backend/tests/unit/capabilities/test_run_truncation.py +++ b/surfsense_backend/tests/unit/capabilities/test_run_truncation.py @@ -64,7 +64,9 @@ def test_preview_is_char_budgeted_and_references_run(): per_item = "y" * 500 items = [f'{{"i": {i}, "v": "{per_item}"}}' for i in range(500)] body = "\n".join(items) - serialized = SerializedOutput(text=body, item_count=len(items), char_count=len(body)) + serialized = SerializedOutput( + text=body, item_count=len(items), char_count=len(body) + ) preview = _build_preview(serialized, run_id="abc") @@ -143,8 +145,11 @@ async def test_read_run_paginates(monkeypatch): read_run, _ = _tools() out = await read_run.ainvoke( - {"ref": "run_" + "0" * 8 + "-0000-0000-0000-000000000000", - "offset": 2, "limit": 3} + { + "ref": "run_" + "0" * 8 + "-0000-0000-0000-000000000000", + "offset": 2, + "limit": 3, + } ) assert "item_2" in out and "item_3" in out and "item_4" in out assert "item_0" not in out and "item_5" not in out @@ -184,8 +189,7 @@ async def test_search_run_excerpts_huge_matched_line(monkeypatch): _, search_run = _tools() out = await search_run.ainvoke( - {"ref": "run_" + "0" * 8 + "-0000-0000-0000-000000000000", - "pattern": "NEEDLE"} + {"ref": "run_" + "0" * 8 + "-0000-0000-0000-000000000000", "pattern": "NEEDLE"} ) assert "NEEDLE" in out assert "match at char 100000" in out @@ -215,8 +219,10 @@ async def test_search_run_matches(monkeypatch): _patch_session(monkeypatch, _BODY, []) _, search_run = _tools() out = await search_run.ainvoke( - {"ref": "spill_" + "0" * 8 + "-0000-0000-0000-000000000000", - "pattern": "item_7"} + { + "ref": "spill_" + "0" * 8 + "-0000-0000-0000-000000000000", + "pattern": "item_7", + } ) assert "item_7" in out assert "item_1" not in out.split("item_7")[0] @@ -232,14 +238,30 @@ _CRAWL_BODY = "\n".join( "url": "https://x.com/team/", "status": "success", "links": [ - {"url": "https://x.com/author/jane/", "text": "Jane Doe", - "context": "Jane Doe General Partner", "kind": "internal"}, - {"url": "https://x.com/author/bob/", "text": "Bob Roe", - "context": "Bob Roe Operations", "kind": "internal"}, + { + "url": "https://x.com/author/jane/", + "text": "Jane Doe", + "context": "Jane Doe General Partner", + "kind": "internal", + }, + { + "url": "https://x.com/author/bob/", + "text": "Bob Roe", + "context": "Bob Roe Operations", + "kind": "internal", + }, # Duplicate of Jane (nav + card) — must dedupe. - {"url": "https://x.com/author/jane/", "text": "Jane Doe", - "context": "Jane Doe General Partner", "kind": "internal"}, - {"url": "https://x.com/about/", "text": "About", "kind": "internal"}, + { + "url": "https://x.com/author/jane/", + "text": "Jane Doe", + "context": "Jane Doe General Partner", + "kind": "internal", + }, + { + "url": "https://x.com/about/", + "text": "About", + "kind": "internal", + }, ], } ), diff --git a/surfsense_backend/tests/unit/capabilities/web/crawl/test_executor.py b/surfsense_backend/tests/unit/capabilities/web/crawl/test_executor.py index d26846f93..6294cf4f1 100644 --- a/surfsense_backend/tests/unit/capabilities/web/crawl/test_executor.py +++ b/surfsense_backend/tests/unit/capabilities/web/crawl/test_executor.py @@ -101,7 +101,11 @@ async def test_aggregated_contacts_carry_provenance_and_site_wide_flag() -> None class _ContactsEngine: async def crawl_url(self, url: str) -> CrawlOutcome: socials = [footer] + ([person] if url.endswith("/about") else []) - links = ["https://e.com/about", "https://e.com/blog"] if url == "https://e.com/" else [] + links = ( + ["https://e.com/about", "https://e.com/blog"] + if url == "https://e.com/" + else [] + ) return CrawlOutcome( status=_SUCCESS, result={ diff --git a/surfsense_backend/tests/unit/capabilities/web/crawl/test_schemas.py b/surfsense_backend/tests/unit/capabilities/web/crawl/test_schemas.py index 9e7503851..f7b9a2b39 100644 --- a/surfsense_backend/tests/unit/capabilities/web/crawl/test_schemas.py +++ b/surfsense_backend/tests/unit/capabilities/web/crawl/test_schemas.py @@ -43,18 +43,20 @@ def test_estimated_units_for_single_url_is_seed_count() -> None: def test_estimated_units_for_spider_is_max_pages() -> None: - model = CrawlInput( - startUrls=["https://a.com"], maxCrawlDepth=2, maxCrawlPages=25 - ) + model = CrawlInput(startUrls=["https://a.com"], maxCrawlDepth=2, maxCrawlPages=25) assert model.estimated_units == 25 def test_billable_units_counts_only_successes() -> None: out = CrawlOutput( items=[ - CrawlItem(url="a", status="success", crawl=CrawlMeta(loadedUrl="a", depth=0)), + CrawlItem( + url="a", status="success", crawl=CrawlMeta(loadedUrl="a", depth=0) + ), CrawlItem(url="b", status="empty", crawl=CrawlMeta(loadedUrl="b", depth=1)), - CrawlItem(url="c", status="failed", crawl=CrawlMeta(loadedUrl="c", depth=1)), + CrawlItem( + url="c", status="failed", crawl=CrawlMeta(loadedUrl="c", depth=1) + ), ] ) assert out.billable_units == 1 diff --git a/surfsense_backend/tests/unit/platforms/google_search/test_skeleton.py b/surfsense_backend/tests/unit/platforms/google_search/test_skeleton.py index 2025089c5..41a960a0e 100644 --- a/surfsense_backend/tests/unit/platforms/google_search/test_skeleton.py +++ b/surfsense_backend/tests/unit/platforms/google_search/test_skeleton.py @@ -274,8 +274,13 @@ def test_parse_serp_extracts_all_blocks(): assert prod.prices == ["$24.99", "$30"] # Related searches exclude the numeric pagination anchor (a.fl). - assert [r.title for r in item.relatedQueries] == ["easy apple pie", "apple pie recipe"] - assert item.relatedQueries[0].url == "https://www.google.com/search?q=easy+apple+pie" + assert [r.title for r in item.relatedQueries] == [ + "easy apple pie", + "apple pie recipe", + ] + assert ( + item.relatedQueries[0].url == "https://www.google.com/search?q=easy+apple+pie" + ) # suggestedResults are the related queries re-shaped with type/position. assert [(s.position, s.title, s.type) for s in item.suggestedResults] == [ diff --git a/surfsense_backend/tests/unit/platforms/reddit/test_parsers.py b/surfsense_backend/tests/unit/platforms/reddit/test_parsers.py index b61479305..77f1312a4 100644 --- a/surfsense_backend/tests/unit/platforms/reddit/test_parsers.py +++ b/surfsense_backend/tests/unit/platforms/reddit/test_parsers.py @@ -95,8 +95,15 @@ def test_flatten_comments_counts_replies_and_stops_at_more(): "kind": "Listing", "data": { "children": [ - {"kind": "t1", "data": {"name": "t1_2", "id": "2", - "body": "reply", "replies": ""}}, + { + "kind": "t1", + "data": { + "name": "t1_2", + "id": "2", + "body": "reply", + "replies": "", + }, + }, {"kind": "more", "data": {}}, # stub -> ignored ] }, @@ -112,8 +119,10 @@ def test_flatten_comments_counts_replies_and_stops_at_more(): def test_flatten_comments_honors_max(): tree = [ - {"kind": "t1", "data": {"name": f"t1_{i}", "id": str(i), "body": "x", - "replies": ""}} + { + "kind": "t1", + "data": {"name": f"t1_{i}", "id": str(i), "body": "x", "replies": ""}, + } for i in range(5) ] assert len(flatten_comments(tree, max_comments=2)) == 2 @@ -128,9 +137,17 @@ def test_children_and_after(): def test_parse_community_maps_members(): - thing = {"kind": "t5", "data": {"name": "t5_s", "id": "s", - "display_name": "py", "display_name_prefixed": "r/py", - "subscribers": 1234, "url": "/r/py/"}} + thing = { + "kind": "t5", + "data": { + "name": "t5_s", + "id": "s", + "display_name": "py", + "display_name_prefixed": "r/py", + "subscribers": 1234, + "url": "/r/py/", + }, + } item = parse_community(thing) assert item["dataType"] == "community" assert item["numberOfMembers"] == 1234 diff --git a/surfsense_backend/tests/unit/proprietary/web_crawler/test_site_crawler.py b/surfsense_backend/tests/unit/proprietary/web_crawler/test_site_crawler.py index cbb12d43b..1f8f64d54 100644 --- a/surfsense_backend/tests/unit/proprietary/web_crawler/test_site_crawler.py +++ b/surfsense_backend/tests/unit/proprietary/web_crawler/test_site_crawler.py @@ -125,9 +125,7 @@ async def test_dedupes_on_canonical_url() -> None: } ) - await crawl_site( - engine, ["https://e.com/"], max_crawl_depth=3, max_crawl_pages=10 - ) + await crawl_site(engine, ["https://e.com/"], max_crawl_depth=3, max_crawl_pages=10) assert engine.calls.count("https://e.com/a") == 1 assert engine.calls.count("https://e.com/") == 1 diff --git a/surfsense_backend/tests/unit/proprietary/web_crawler/test_url_policy.py b/surfsense_backend/tests/unit/proprietary/web_crawler/test_url_policy.py index 9ff7fec26..2c209ce16 100644 --- a/surfsense_backend/tests/unit/proprietary/web_crawler/test_url_policy.py +++ b/surfsense_backend/tests/unit/proprietary/web_crawler/test_url_policy.py @@ -116,7 +116,12 @@ def test_extract_link_records_dedupes_keeping_first_nonempty_text() -> None: html = 'Pricing' records = extract_link_records(html, "https://example.com/") assert records == [ - {"url": "https://example.com/p", "text": "Pricing", "rel": "", "kind": "internal"} + { + "url": "https://example.com/p", + "text": "Pricing", + "rel": "", + "kind": "internal", + } ] diff --git a/surfsense_backend/tests/unit/services/test_web_crawl_credit_service.py b/surfsense_backend/tests/unit/services/test_web_crawl_credit_service.py index 7c7cd0e40..ef806cfeb 100644 --- a/surfsense_backend/tests/unit/services/test_web_crawl_credit_service.py +++ b/surfsense_backend/tests/unit/services/test_web_crawl_credit_service.py @@ -241,5 +241,3 @@ class TestCheckBalance: session, _user = _make_session(balance_micros=0) await WebCrawlCreditService(session).check_balance(_USER_ID, 0) session.execute.assert_not_called() - - diff --git a/surfsense_backend/tests/unit/utils/proxy/test_dataimpulse_provider.py b/surfsense_backend/tests/unit/utils/proxy/test_dataimpulse_provider.py index e2d96e0e1..af1288046 100644 --- a/surfsense_backend/tests/unit/utils/proxy/test_dataimpulse_provider.py +++ b/surfsense_backend/tests/unit/utils/proxy/test_dataimpulse_provider.py @@ -47,9 +47,7 @@ def test_location_stops_at_next_param(monkeypatch: pytest.MonkeyPatch) -> None: def test_no_country_suffix_yields_empty_location( monkeypatch: pytest.MonkeyPatch, ) -> None: - monkeypatch.setattr( - Config, "PROXY_URL", "http://tok:secret@gw.dataimpulse.com:823" - ) + monkeypatch.setattr(Config, "PROXY_URL", "http://tok:secret@gw.dataimpulse.com:823") assert DataImpulseProvider().get_location() == "" diff --git a/surfsense_backend/uv.lock b/surfsense_backend/uv.lock index c46b8d4fc..a5b492fc8 100644 --- a/surfsense_backend/uv.lock +++ b/surfsense_backend/uv.lock @@ -33,6 +33,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version == '3.13.*' and sys_platform == 'linux' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra == 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -64,6 +67,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version < '3.13' and sys_platform == 'linux' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra == 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -95,6 +101,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version >= '3.14' and sys_platform == 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra == 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -126,6 +135,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version >= '3.14' and sys_platform == 'emscripten' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra == 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -157,6 +169,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra == 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -188,6 +203,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version == '3.13.*' and sys_platform == 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra == 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -219,6 +237,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version == '3.13.*' and sys_platform == 'emscripten' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra == 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -250,6 +271,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra == 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -281,6 +305,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version < '3.13' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra == 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -312,6 +339,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version < '3.13' and sys_platform == 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra == 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -353,6 +383,10 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version >= '3.14' and sys_platform == 'linux' and extra != 'extra-16-surf-new-backend-cpu' and extra == 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -384,6 +418,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version == '3.13.*' and sys_platform == 'linux' and extra != 'extra-16-surf-new-backend-cpu' and extra == 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -415,6 +452,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version < '3.13' and sys_platform == 'linux' and extra != 'extra-16-surf-new-backend-cpu' and extra == 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -446,6 +486,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version >= '3.14' and sys_platform == 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra == 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -477,6 +520,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version >= '3.14' and sys_platform == 'emscripten' and extra != 'extra-16-surf-new-backend-cpu' and extra == 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -508,6 +554,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra == 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -539,6 +588,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version == '3.13.*' and sys_platform == 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra == 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -570,6 +622,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version == '3.13.*' and sys_platform == 'emscripten' and extra != 'extra-16-surf-new-backend-cpu' and extra == 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -601,6 +656,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra == 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -632,6 +690,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version < '3.13' and sys_platform != 'linux' and sys_platform != 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra == 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -663,6 +724,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version < '3.13' and sys_platform == 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra == 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -704,6 +768,10 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version >= '3.14' and sys_platform == 'linux' and extra == 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -735,6 +803,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version == '3.13.*' and sys_platform == 'linux' and extra == 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -766,6 +837,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version < '3.13' and sys_platform == 'linux' and extra == 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -797,6 +871,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version >= '3.14' and sys_platform == 'win32' and extra == 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -828,6 +905,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version >= '3.14' and sys_platform == 'emscripten' and extra == 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -859,6 +939,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -890,6 +973,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version == '3.13.*' and sys_platform == 'win32' and extra == 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -921,6 +1007,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version == '3.13.*' and sys_platform == 'emscripten' and extra == 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -952,6 +1041,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -983,6 +1075,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version < '3.13' and sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -1014,6 +1109,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version < '3.13' and sys_platform == 'win32' and extra == 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -1055,6 +1153,10 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version >= '3.14' and sys_platform == 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -1086,6 +1188,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version >= '3.14' and sys_platform == 'emscripten' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -1147,6 +1252,12 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -1178,6 +1289,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version == '3.13.*' and sys_platform == 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -1209,6 +1323,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version == '3.13.*' and sys_platform == 'emscripten' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -1270,6 +1387,12 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -1331,6 +1454,12 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version < '3.13' and sys_platform != 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", "python_version < '0'", "python_version < '0'", @@ -1362,6 +1491,9 @@ resolution-markers = [ "python_version < '0'", "python_version < '0'", "python_version < '0'", + "python_version < '0'", + "python_version < '0'", + "python_version < '0'", "python_full_version < '3.13' and sys_platform == 'win32' and extra != 'extra-16-surf-new-backend-cpu' and extra != 'extra-16-surf-new-backend-cu126' and extra != 'extra-16-surf-new-backend-cu128'", ] conflicts = [[ @@ -10221,7 +10353,7 @@ wheels = [ [[package]] name = "surf-new-backend" -version = "0.0.30" +version = "0.0.31" source = { editable = "." } dependencies = [ { name = "alembic" }, diff --git a/surfsense_browser_extension/package.json b/surfsense_browser_extension/package.json index 728909d06..8dda0d82f 100644 --- a/surfsense_browser_extension/package.json +++ b/surfsense_browser_extension/package.json @@ -1,7 +1,7 @@ { "name": "surfsense_browser_extension", "displayName": "Surfsense Browser Extension", - "version": "0.0.30", + "version": "0.0.31", "description": "Extension to collect Browsing History for SurfSense.", "author": "https://github.com/MODSetter", "engines": { diff --git a/surfsense_desktop/package.json b/surfsense_desktop/package.json index f5915a875..f12b9722c 100644 --- a/surfsense_desktop/package.json +++ b/surfsense_desktop/package.json @@ -1,7 +1,7 @@ { "name": "surfsense-desktop", "productName": "SurfSense", - "version": "0.0.30", + "version": "0.0.31", "description": "SurfSense Desktop App", "main": "dist/main.js", "scripts": { diff --git a/surfsense_web/package.json b/surfsense_web/package.json index 0d4113937..efa04b4f6 100644 --- a/surfsense_web/package.json +++ b/surfsense_web/package.json @@ -1,6 +1,6 @@ { "name": "surfsense_web", - "version": "0.0.30", + "version": "0.0.31", "private": true, "packageManager": "pnpm@10.26.0", "description": "SurfSense Frontend",