Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ including an endpoint-free local WebGPU option with the tested LFM2.5 2.6B
preset and an experimental custom Hugging Face ONNX repository option —
see the [full catalog](docs/providers-and-models.md#extended-provider-catalog).

For a local or bring-your-own provider, the per-provider **Share queries for
research** switch remains off by default. When enabled, it now shares a
bounded, content-free diagnostic timeline for that provider's model attempts
(including failed runs) alongside the existing scrubbed prompt/response share.
The timeline includes tool names, outcomes, error codes, and timings, but not
tool arguments, page content, or screenshots. If a run fails before a normal
generation share, its bounded model-facing request and final blocker accompany
the diagnostic record. No second sharing switch is
required; turning the existing switch off also purges queued diagnostics.

## Features

- **Reads any page** — text, links, forms, tables, PDFs, and interactive
Expand Down
46 changes: 38 additions & 8 deletions docs/privacy-and-data-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ inference stay on-device.
The user chooses their provider in Settings. Options include:

- **WebBrain Compass**: requests go through `api.webbrain.one`; selected interactions may be retained and used for evaluation, improvement, fine-tuning, and training while Help Improve WebBrain is enabled
- **Bring-your-own cloud providers**: OpenAI, Anthropic, Google Gemini, Mistral, DeepSeek, xAI, Groq, OpenRouter, etc. — requests go directly to the provider using the user's credentials and are never collected by WebBrain
- **Bring-your-own cloud providers**: OpenAI, Anthropic, Google Gemini, Mistral, DeepSeek, xAI, Groq, OpenRouter, etc. — requests go directly to the provider using the user's credentials; WebBrain receives a separate research copy only if the user enables that provider's **Share queries for research** switch
- **Local model runtimes**: llama.cpp, Ollama, LM Studio, Jan, vLLM, SGLang,
LocalAI, GPT4All, and Unsloth Studio — inference requests stay on the user's
machine when Studio is configured with its loopback URL
Expand All @@ -57,7 +57,7 @@ The user chooses their provider in Settings. Options include:
local gateway, but the gateway may forward the request context to an upstream
account. Its configuration and privacy policy determine where data goes.

Local-model and bring-your-own API requests are never collected by WebBrain. WebBrain Compass requests are processed and may be retained as described below.
Local-model and bring-your-own API requests do not pass through WebBrain Compass. A separate, per-provider research-sharing switch is off by default; when enabled, bounded copies and diagnostic metadata are sent to WebBrain as described below. WebBrain Compass requests are processed and may be retained separately.

### Optional research escalation to ChatGPT

Expand Down Expand Up @@ -173,8 +173,9 @@ routed through an OpenRouter workspace where content logging is disabled. This
does not prevent the minimal metadata-only operational logging required to
provide the service, enforce quotas, prevent abuse, maintain security, or debug
failures. Requests sent to local models or directly to providers using the
user's own credentials never pass through WebBrain Compass and are never eligible
for WebBrain training.
user's own credentials never pass through WebBrain Compass for inference. They
are not collected by WebBrain unless the user separately opts in to that
provider's research sharing.

For eligible completed generations, MySQL is WebBrain's canonical store. The
service strips media, compresses the request/response payload, encrypts it with
Expand All @@ -197,6 +198,30 @@ selected for improvement are retained for no longer than 12 months before
deletion or de-identification. De-identified datasets may be retained for up to
5 years for model development, evaluation, security, and reproducibility.

### Voluntary external/local provider research sharing

**Share queries for research** is off by default for each local or bring-your-own
provider. If the user turns it on, WebBrain sends a bounded copy of that
provider's model-facing request and response to the Compass improvement service.
It also sends a bounded diagnostic timeline for model-attempt runs, including
failed ones: steps, tool names, outcomes, error codes, and timings. A failed run
may include its bounded model-facing request and displayed blocker even when no
response was produced. Screenshot and other binary bytes are stripped; raw tool
arguments and results are not included in the diagnostic timeline. Text in the
shared conversation can still contain sensitive personal information after
truncation and automated de-identification, so users should not enable this
switch for content they do not want to share.

The extension records a local trace for an opted-in run even if the separate
Record traces switch is off. The upload projects only metadata from that trace,
including when the user independently selected local lossless tracing. Research
shares use a durable, revocable local outbox and are retried after temporary
delivery failures; the provider's live sharing consent is checked before each
send. The Compass service admits these records only under explicit
share-session consent, de-identifies and encrypts them, and applies its
improvement-data retention rules. They are not counted as Compass inference
requests.

---

## What Stays in the Browser
Expand All @@ -216,7 +241,10 @@ the stored copies are not separately synced to WebBrain.
### Trace Recorder

When enabled (Settings → Display → "Record traces"), every agent run is written
to the local `webbrain_traces` IndexedDB database in one of two privacy tiers:
to the local `webbrain_traces` IndexedDB database in one of two privacy tiers.
An external/local provider with **Share queries for research** enabled also
records its run locally for the bounded diagnostic upload, even when this
separate Record traces setting is off:

- **Default metadata-only tier.** The `runs` store keeps run identifiers and
lineage, model/provider identifiers, token and event totals, timestamps,
Expand Down Expand Up @@ -400,8 +428,9 @@ support this path.

## Telemetry / Analytics

The extension does not include an analytics SDK, crash-reporting SDK, or a
separate product-telemetry endpoint. When WebBrain Compass is selected, the model
The extension does not include an analytics SDK or crash-reporting SDK. Opt-in
research sharing uses separate improvement endpoints; it is not general product
telemetry. When WebBrain Compass is selected, the model
request itself goes to `api.webbrain.one` and is subject to the Compass data-use
terms above. Operational request metadata is retained separately for quota,
security, abuse prevention, and debugging.
Expand All @@ -415,6 +444,7 @@ The only outbound HTTP requests are:
6. **User memory extraction calls** (only if auto-learn is enabled; sent to the configured LLM provider after a completed turn)
7. **Encrypted Cloud Sync calls** to `https://api.webbrain.one/v1/sync` (only after a subscriber explicitly enables sync; vault content is encrypted before upload)
8. **Slash-driven tab/screen recording** creates no outbound traffic (the .webm is saved to the Downloads folder via `chrome.downloads.download`)
9. **Voluntary research shares** to `https://api.webbrain.one/v1/improvement/generations` and `/v1/improvement/diagnostic-traces` (only for a local or bring-your-own provider with its separate sharing switch enabled)

The `webRequest` API shortcut observer is on by default and does not
create outbound requests; it observes replay metadata for requests
Expand Down Expand Up @@ -646,7 +676,7 @@ CDP capture → JPEG/PNG data URL
| Provider selection | Choose which LLM receives the data, or run locally |
| Provider prompt/tool tier | Choose Compact, Mid, or Full tool exposure for non-cloud providers |
| Ask / Act / Dev mode | Choose read-only, normal action, or developer/page-inspection mode |
| Tracing toggle | Prevents any trace data from being stored |
| Tracing toggle | Controls ordinary local trace recording; a separately opted-in provider research share records a run for metadata-only diagnostic upload even when this toggle is off |
| Screenshot fallback | Controls whether page images are sent to the LLM |
| Auto-screenshot mode | Controls how frequently viewport captures are sent |
| Strict secret handling | Keeps credentials out of assistant text and completion summaries: an instruction to the model, plus exact-match redaction in cloud runs of anything it typed, sent, or read from a labelled field |
Expand Down
5 changes: 4 additions & 1 deletion docs/security-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,10 @@ persistent setting, which remains active until the user turns it off.
## Trace Data Isolation

The trace recorder (`trace/recorder.js`) writes to IndexedDB on the user's
machine only when explicitly enabled (Settings → Display → "Record traces").
machine when explicitly enabled (Settings → Display → "Record traces") or
when a local/bring-your-own provider's separate **Share queries for research**
switch is enabled for that run. The latter forces a local record so a bounded,
content-free diagnostic timeline can be uploaded under that explicit consent.
The default tier is metadata-only: run records omit user and final assistant
text; event records keep allowlisted counts, timings, usage, status/error codes,
tool names/outcome status, and screenshot markers while omitting raw model
Expand Down
39 changes: 32 additions & 7 deletions src/chrome/src/agent/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ import { isPdfHandlerTabUrl, pdfUrlFromTabUrl } from './pdf-extraction.js';
import { normalizePdfOcrResult, PDF_OCR_SYSTEM_PROMPT } from './pdf-ocr.js';
import * as trace from '../trace/recorder.js';
import { buildTerminalRuntimeEvent, enqueueCloudRuntimeEvent, flushCloudRuntimeOutbox } from '../trace/cloud-runtime-outbox.js';
import { buildShareGenerationItem, enqueueShareGeneration, flushShareOutbox, purgeShareGenerations } from '../trace/webbrain-share-outbox.js';
import { buildShareGenerationItem, buildShareDiagnosticItem, enqueueShareGeneration, enqueueShareDiagnostic, flushShareOutbox, purgeShareGenerations } from '../trace/webbrain-share-outbox.js';
import { normalizeRuntimeTraceConfig } from '../trace/runtime-config.js';
import { tracesToMarkdown } from './trace-export.js';
import { hcaptchaParamError } from './captcha-hcaptcha-providers.js';
Expand Down Expand Up @@ -19512,7 +19512,11 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
// passes the origin ids so the trace can attribute the derived run.
parentRunId: runOptions?.parentRunId || null,
parentSessionId: runOptions?.parentSessionId || null,
force: runOptions?.cloudRun === true,
// Research consent also records a local run when Tracing is off. The
// share builder projects metadata before upload even if a separate
// local lossless-trace preference is enabled.
force: runOptions?.cloudRun === true || (provider?.config?.shareQueriesForResearch === true
&& String(provider?.config?.providerName || '').toLowerCase() !== 'webbrain-cloud'),
});
} catch {
this.pendingAdapterMatchTraces.delete(tabId);
Expand Down Expand Up @@ -19611,11 +19615,6 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
}
} catch {}
}
// Retry delivery of previously queued voluntary shares on every run end,
// mirroring the Compass runtime outbox pattern. Revoked entries are
// purged first so opt-out is honored immediately before delivery.
try { await this._purgeRevokedShareGenerations(); } catch {}
void flushShareOutbox(shareTransport, (entry) => this._shareEntryConsented(entry));
if (runId) {
await this._flushAdapterMatchTraceRun(runId);
try {
Expand All @@ -19624,9 +19623,35 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
await this._persistNow(tabId);
}
} catch {}
if (shareTransport && provider?.config?.shareQueriesForResearch === true
&& String(provider?.config?.providerName || '').toLowerCase() !== 'webbrain-cloud') {
try {
const sessionId = this._shareSessionId(this.conversationIds.get(tabId) || null);
if (sessionId) {
const item = buildShareDiagnosticItem({
runId,
events: await trace.getRunEvents(runId),
status,
model: provider?.model,
mode,
browserTarget: 'chrome',
extensionVersion: chrome.runtime.getManifest().version || '',
provider: String(provider?.config?.providerName || '').toLowerCase(),
provider_name: String(provider?.config?.label || provider?.name || ''),
provider_id: String(provider?.config?._providerId || ''),
messages: Array.isArray(shareRequest) ? shareRequest : null,
finalContent,
});
if (item) await enqueueShareDiagnostic({ session_id: sessionId, ...item });
}
} catch {}
}
this.currentRunId.delete(tabId);
this.adapterMatchTraceKeys.delete(runId);
}
// Both generation and diagnostic records use the same revocable outbox.
try { await this._purgeRevokedShareGenerations(); } catch {}
void flushShareOutbox(shareTransport, (entry) => this._shareEntryConsented(entry));
// Stash before deleting so an app-owned trusted continuation (Continue
// after max_steps) can reuse the same task's proofs. Independent tasks
// mint fresh at the next _startTraceRun and discard the stash there, so
Expand Down
25 changes: 25 additions & 0 deletions src/chrome/src/providers/openai.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,31 @@ export class OpenAICompatibleProvider extends BaseLLMProvider {
}
}

async sendShareDiagnostic(sessionId, diagnostic, { timeoutMs = 4000 } = {}) {
if (String(this.config.providerName || '').toLowerCase() !== 'webbrain-cloud') {
return { ok: false, retryable: false, status: 0 };
}
const controller = typeof AbortController === 'function' ? new AbortController() : null;
const timer = controller ? setTimeout(() => controller.abort(), Math.max(250, timeoutMs)) : null;
try {
const response = await fetchWithFallback(`${this.baseUrl}/improvement/diagnostic-traces`, {
method: 'POST',
headers: this._headers({ helpImprove: '1' }),
body: JSON.stringify({ session_id: String(sessionId || ''), diagnostic }),
...(controller ? { signal: controller.signal } : {}),
});
if (response.ok) return { ok: true, retryable: false, status: response.status };
try { await response.text(); } catch {}
// Keep the durable outbox entry while an older Cloud deployment lacks
// this newer endpoint; it will be retried after the server rolls out.
return { ok: false, retryable: response.status === 404 || response.status === 408 || response.status === 429 || response.status >= 500, status: response.status };
} catch {
return { ok: false, retryable: true, status: 0 };
} finally {
if (timer != null) clearTimeout(timer);
}
}

/**
* Newer OpenAI models (gpt-5 and the o-series) reject `max_tokens` and any
* non-default `temperature`, requiring `max_completion_tokens`. Detected by
Expand Down
Loading
Loading