Commit graph

9 commits

Author SHA1 Message Date
Thierry CH.
1f4069541f refactor(backend): move plugins/ into backend/, run by server/ 2026-10-23 17:49:08 +02:00
DESKTOP-RTLN3BA\$punk
907ec1cc42 feat(plugin-host): count each plugin's tool calls per license, day, tool and outcome
host/usage_meter/ counts only tools/call requests to /<id>/mcp: per UTC day, Keygen license id (or unlicensed), plugin, tool and outcome (ok, tool_error, refused, cancelled, error), with calls and total milliseconds, and each license's first day with a call. It sits outside the license check so refusals count, reads only the tool's name, keeps a name the plugin lacks as unknown, and never keeps the arguments, the key or the thread's token.

Counts are written to SQLite (USAGE_DB_PATH) every minute and at shutdown, off the event loop; a failing meter is logged once and never fails a call. Once a day, rows older than 13 months are forgotten and, with POSTHOG_API_KEY, yesterday's totals go to PostHog as personless plugin_usage_daily and plugin_licences_daily events with no license id; python -m host.usage_meter.rollup --day sends one by hand. With USAGE_ADMIN_TOKEN, /admin/usage/first-seen and /admin/usage/daily serve the records to a bearer token; admin is reserved as a plugin id.
2026-10-21 16:34:16 -07:00
DESKTOP-RTLN3BA\$punk
b249438b69 Merge remote-tracking branch 'origin/dev' into perf/plugins-speed
# Conflicts:
#	docs/architecture/plugin-host.md
#	plugins/remote/host/app.py
#	plugins/remote/proprietary/license_check/tests/test_license_access.py
2026-10-14 05:59:47 -07:00
DESKTOP-RTLN3BA\$punk
2fb720cd03 feat(plugin-host): plugin lifespans, one license cache, and results the agent reads
- A plugin's server.py may expose a lifespan: the host enters it at startup,
  warm-ups run in the background, and scraping/lifecycle.py closes the shared
  sessions, browsers and API clients once at shutdown.
- One license cache per process, stale-while-revalidate, instead of one per
  plugin: the app's 5-minute check no longer pays a Keygen round trip per
  plugin. Refusals and the 24 h grace work as before.
- Kept API clients for Keygen, SearXNG and the captcha solver, which now polls
  every second.
- mcp_server/results.py: tool_result puts each item in the text as one
  compact JSON line, within 32 KB. opencode reads only a result's text when
  there is any, so the agent never saw prices, page text or reviews. A request
  with x-surfsense-text-only: 1 gets no structured copy.
- mcp_server/progress.py: stage progress, so a long call never sits silent.
- GET and DELETE on /<id>/mcp answer 405: the stateless host has no stream to
  offer, and each GET held an idle SSE connection open per plugin.
2026-10-14 05:56:11 -07:00
Anish Sarkar
464041694c fix(plugins): a paid plugin's license verdict shows at once, from the plugin host
Removing, importing or replacing a license, opening Plugins with a stale
verdict, and a turn after one all ask the plugin host's new GET /license,
once for every paid plugin, and apply its answer: a refused license holds
the plugin out of @ and the agent, and a valid one brings it back. The host
stays the only judge; the license file only says when to ask, and no answer
keeps every verdict. A paid plugin's refusal in a turn is named as its
license, not a sign-in. Contract 2 gains the route.
2026-10-14 16:48:28 +05:30
Anish Sarkar
b84da18757 docs(plugins): fold the built remote plugins into architecture, keep only what is not built as the proposal
The core/ and remote/ proposals described work that has shipped. What they
said that the architecture docs lacked now lives there: the MCP client's
rules, what the gateway does today, what a plugin can reach, adding a plugin
to the host, who changes proprietary/, and the backend's copy of the
scrapers. proposals/plugins/README.md now holds only what is not built: the
chat router step, jobs and scaling on the host, organisation policy, later
MCP features and the open questions. Bundles stay deferred, with their
registry entry moved in. ADRs 0051, 0053 and 0054 are accepted, and links
into the deleted files point at the architecture docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:47:47 +05:30
Anish Sarkar
d8aa0f5305 feat(plugins): serve plugins without sessions, and let Stop end their calls on any copy
The plugin host serves MCP without sessions, so any copy answers any request
and Azure can add copies on load. opencode cancels with a message no such
copy can match, so each turn gives plugins on the plugin host a token for
the thread, and the app's Stop calls the host's POST /cancel with it and the
license; Redis passes it to the copy running the call, whose response ends
with Request cancelled. PLUGINS limits a container to some plugins, as one
split out of the shared container runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:26:53 +05:30
Anish Sarkar
1ea87694da feat(plugins): list each scraper as its own plugin on the plugin host
The first-party list holds ten scraper plugins, from Google Search to Indeed,
each its own MCP server at https://plugins.surfsense.com/<id>/mcp. The host
serves a folder under its id with underscores as hyphens, since a package
cannot hold a hyphen, reserves scraping/ for the code they will share, and
lints plugins/remote/ with ruff's ASYNC rules, since its plugins share each
process's event loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:04:22 +05:30
Anish Sarkar
8d85b6f544 feat(plugins): serve SurfSense's plugins from one host, a paid one behind the license
plugins/remote/host mounts each plugin folder's MCP server at /<id>/mcp and
answers /health; without proprietary/ it serves only the Apache-2.0 plugins.
A plugin's access hook runs before its server sees a request, and
proprietary/license_check applies contract 2 with Keygen's validate-key: a
five-minute cache, a day's grace while Keygen is down, and 401, 403 or 503
otherwise. The check is license_check/, since license/ beside LICENSE is one
path on macOS and Windows. CI checks plugins/remote/ on its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:40:00 +05:30