diff --git a/docs/privacy-and-data-flow.md b/docs/privacy-and-data-flow.md index 473c9fa47..d08c622b1 100644 --- a/docs/privacy-and-data-flow.md +++ b/docs/privacy-and-data-flow.md @@ -185,10 +185,10 @@ the stored copies are not separately synced to WebBrain. When enabled (Settings → Display → "Record traces"), every agent run is written to an IndexedDB database (`webbrain_traces`): - **`runs` store**: model, provider, token totals, timestamps, user message, final content -- **`events` store**: per-step LLM request provenance, model responses, and tool calls with args and results. Request provenance contains counts, controlled prompt/mode labels, and declared prompt/tool policy revisions; it neither duplicates nor fingerprints raw system prompts, message text, tool schemas, or tool names. +- **`events` store**: per-step LLM request provenance, model responses, and tool calls with args and results. By default, request provenance contains counts, controlled prompt/mode labels, and declared prompt/tool policy revisions; it neither duplicates nor fingerprints raw system prompts, message text, tool schemas, or tool names. Users can explicitly enable the lossless debug tier in Settings → Display; those runs are visibly marked and retain bounded request messages and tool schemas for debugging. Both Markdown and JSON exports mask credential-shaped values for lossless runs before writing a file. - **`shots` store**: screenshot blobs -The Traces page (`ui/traces.html`) reads from local IndexedDB only. Export produces a JSON blob saved to the user's Downloads folder. **No trace data ever leaves the browser.** +The Traces page (`ui/traces.html`) reads from local IndexedDB only. Export produces a JSON blob saved to the user's Downloads folder. **No trace data ever leaves the browser.** Lossless recording is off by default and uses per-request/result bounds; treat a lossless trace as sensitive even though exported credentials are masked. Each run also records an allowlisted effective runtime snapshot (including mode and prompt tier). Trace Markdown surfaces that snapshot and the privacy-safe diff --git a/src/chrome/src/agent/trace-export.js b/src/chrome/src/agent/trace-export.js index 0716016db..452b85a94 100644 --- a/src/chrome/src/agent/trace-export.js +++ b/src/chrome/src/agent/trace-export.js @@ -19,12 +19,81 @@ */ import { isKnownKind } from '../trace/event-model.js'; +import { isSensitiveCloudKey } from '../cloud-runs.js'; const ARGS_LIMIT = 300; const RESULT_LIMIT = 600; +const LOSSILESS_MESSAGE_PREVIEW_LIMIT = 2000; const FOOTER = '_Screenshot pixels and vision descriptions are omitted here — see the Traces page for the complete record._'; const UNKNOWN_EVENTS_NOTE = (n) => `_Note: ${n} unknown event(s) skipped._`; +// Credential masking for the opt-in lossless tier. Exports of lossless runs +// contain real request content, so obvious secret shapes are masked before +// they reach a Markdown file — the same spirit as the strict-redaction path +// used elsewhere, kept pure and browser-neutral here. +const SECRET_PATTERNS = [ + /\bsk-[A-Za-z0-9_-]{8,}/g, + /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi, +]; + +function maskSecrets(text) { + let out = String(text ?? ''); + for (const pattern of SECRET_PATTERNS) out = out.replace(pattern, '[redacted]'); + out = out + .replace(/\bBearer\s+[^\s,;]+/gi, 'Bearer [redacted]') + .replace(/((?:^|[^a-zA-Z0-9_])["']?(?:authorization|cookie|password|passwd|passphrase|passcode|pincode|(?:verification|confirmation|security|auth|email|twofactor|2fa|mfa|onetime|recovery)[_ -]?code|secret|credential|private[_ -]?key|api[_ -]?key|(?:access|refresh)[_ -]?token|client[_ -]?secret|token|access[_ -]?key[_ -]?id|secret[_ -]?access[_ -]?key|otp)["']?\s*[:=]\s*)(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,;}\]]+)/gi, '$1[redacted]') + .replace(/([?&](?:api[_-]?key|access[_-]?token|token|key)=)[^&\s]+/gi, '$1[redacted]'); + return out; +} + +function redactExportValue(value, key = '') { + if (isSensitiveCloudKey(key)) return '[redacted]'; + if (typeof value === 'string') return maskSecrets(value); + if (Array.isArray(value)) return value.map(item => redactExportValue(item)); + if (!value || typeof value !== 'object') return value; + return Object.fromEntries(Object.entries(value).map(([childKey, item]) => [childKey, redactExportValue(item, childKey)])); +} + +export function sanitizeTraceExport(payload) { + return payload?.run?.lossless === true ? redactExportValue(payload) : payload; +} + +// Lossless requests carry the full message/tool shape. Render a bounded, +// masked preview per message so the export stays readable without dumping +// every token of a 500 KB request. +function renderLosslessRequest(messages, tools) { + if (messages?._truncated === true) { + const total = Number(messages.length) || 0; + const head = truncate(oneLine(maskSecrets(messages.head || '')), LOSSILESS_MESSAGE_PREVIEW_LIMIT); + const toolNames = Array.isArray(messages.toolNames) ? messages.toolNames : []; + const lines = [`request truncated (${humanSize(total)} total): ${head || '(head unavailable)'}`]; + if (toolNames.length) lines.push(`tools: ${toolNames.join(', ')}`); + return `\n${lines.join('\n')}`; + } + const list = Array.isArray(messages) ? messages : []; + if (!list.length) return ' (empty request log)'; + const lines = []; + for (const message of list.slice(0, 12)) { + const role = oneLine(message?.role || '?'); + const content = maskSecrets( + typeof message?.content === 'string' + ? message.content + : (Array.isArray(message.content) + ? message.content.map(block => block?.text || block?.image_url?.url || '').join(' ') + : ''), + ); + const body = content ? truncate(oneLine(content), LOSSILESS_MESSAGE_PREVIEW_LIMIT) : '(no text)'; + lines.push(`**${role}:** ${body}`); + } + if (list.length > 12) lines.push(`… +${list.length - 12} more message(s) omitted`); + if (Array.isArray(tools) && tools.length) { + lines.push(`tools: ${tools.map(tool => oneLine(tool?.function?.name || '?')).join(', ')}`); + } else if (tools?._truncated === true && Array.isArray(tools.toolNames) && tools.toolNames.length) { + lines.push(`tools: ${tools.toolNames.join(', ')}`); + } + return `\n${lines.join('\n')}`; +} + function oneLine(t) { return String(t ?? '').replace(/\s+/g, ' ').trim(); } function humanSize(n) { return n >= 1024 ? `${(n / 1024).toFixed(1)}kb` : `${n}b`; } @@ -202,7 +271,7 @@ export function tracesToMarkdown(runsWithEvents, { Number.isFinite(d.imageBlockCount) ? `${d.imageBlockCount} image block${d.imageBlockCount === 1 ? '' : 's'}` : '', Number.isFinite(d.documentBlockCount) ? `${d.documentBlockCount} document block${d.documentBlockCount === 1 ? '' : 's'}` : '', ].filter(Boolean).join(' · '); - md += `- 🧠 Model request: ${Number(d.messageCount) || 0} messages · ${Number(d.toolsCount) || 0} tools${media ? ` · ${media}` : ''}${renderLocalWikipediaRag(d.localWikipediaRag)}${renderPromptProvenance(d.promptProvenance)}\n`; + md += `- 🧠 Model request: ${Number(d.messageCount) || 0} messages · ${Number(d.toolsCount) || 0} tools${media ? ` · ${media}` : ''}${renderLocalWikipediaRag(d.localWikipediaRag)}${renderPromptProvenance(d.promptProvenance)}${d.lossless === true ? renderLosslessRequest(d.messages, d.tools) : ''}\n`; } else if (ev.kind === 'llm_response') { const content = String(d.content || '').trim(); if (!content) continue; diff --git a/src/chrome/src/cloud-runs.js b/src/chrome/src/cloud-runs.js index 43874d753..1a1ab19fb 100644 --- a/src/chrome/src/cloud-runs.js +++ b/src/chrome/src/cloud-runs.js @@ -14,7 +14,7 @@ const CLOUD_PERSIST_BYTES_LIMIT = 4 * 1024 * 1024; const TERMINAL_STATUSES = new Set(['completed', 'failed', 'aborted']); // Suffix match on normalized keys (non-alnum stripped). Avoid bare `pin` as a // suffix — it over-matches `spin`, `mapPin`, etc. Short exact keys live in the set. -const SENSITIVE_CLOUD_KEY = /(?:authorization|cookie|password|passwd|passphrase|passcode|pincode|(?:verification|confirmation|security|auth|email|twofactor|2fa|mfa|onetime)code|secret|credential|privatekey|apikey|token|accesskeyid|secretaccesskey)$/i; +const SENSITIVE_CLOUD_KEY = /(?:authorization|cookie|password|passwd|passphrase|passcode|pincode|(?:verification|confirmation|security|auth|email|twofactor|2fa|mfa|onetime|recovery)code|secret|credential|privatekey|apikey|token|accesskeyid|secretaccesskey)$/i; const SENSITIVE_CLOUD_KEY_EXACT = new Set(['code', 'pin', 'otp', 'cvv', 'cvc', 'ssn']); const LARGE_IMAGE_KEY = /(?:attachimage|screenshot|image|imagedata|dataurl)$/i; const CLOUD_TEXT_ENTRY_TOOLS = new Set(['set_field', 'type_ax', 'type_text', 'iframe_type']); @@ -81,7 +81,7 @@ export function normalizeCloudBridgeUrl(value = DEFAULT_CLOUD_BRIDGE_URL) { return url.href; } -function isSensitiveCloudKey(key) { +export function isSensitiveCloudKey(key) { const normalizedKey = normalizedCloudKey(key); if (!normalizedKey) return false; return SENSITIVE_CLOUD_KEY.test(normalizedKey) || SENSITIVE_CLOUD_KEY_EXACT.has(normalizedKey); diff --git a/src/chrome/src/trace/recorder.js b/src/chrome/src/trace/recorder.js index 6450c6f0d..e22111048 100644 --- a/src/chrome/src/trace/recorder.js +++ b/src/chrome/src/trace/recorder.js @@ -27,6 +27,14 @@ import { const DB_NAME = 'webbrain_traces'; const DB_VERSION = 2; +// Lossless tier bounds: tool results up to 200 KB verbatim, request payloads +// up to 500 KB before the head is clamped with a truncation marker. Keeps an +// opt-in debugging tier from exhausting IndexedDB on a single long run. +const LOSSILESS_RESULT_CAP = 200_000; +const LOSSILESS_REQUEST_CAP = 500_000; +const LOSSILESS_RUN_CAP = 5_000_000; +const LOSSILESS_TOTAL_CAP = 50_000_000; + let _dbPromise = null; function openDB() { if (_dbPromise) return _dbPromise; @@ -89,6 +97,16 @@ async function tracingEnabled() { } catch { return false; } } +// Opt-in lossless tier: same event pipeline, full request payloads instead of +// content-free provenance. Read once per run at startRun; never per event. +async function losslessTraceEnabled() { + try { + if (typeof indexedDB === 'undefined') return false; + const { losslessTrace } = await chrome.storage.local.get(['losslessTrace']); + return losslessTrace === true; + } catch { return false; } +} + async function isForcedTraceRun(runId) { if (!runId) return false; if (_runState.get(runId)?.forced === true) return true; @@ -105,6 +123,17 @@ async function tracingEnabledForRun(runId) { return (await tracingEnabled()) || (await isForcedTraceRun(runId)); } +// Restore per-run flags after SW eviction from the durable run record so the +// lossless tier decision survives a worker restart mid-run. +async function peekRunFlags(db, runId) { + try { + const record = await promisifyReq( + tx(db, ['runs'], 'readonly').objectStore('runs').get(runId), + ); + return { forced: record?.forced === true, lossless: record?.lossless === true, losslessBytes: record?.losslessBytes || 0 }; + } catch { return { forced: false, lossless: false, losslessBytes: 0 }; } +} + // ----- Per-run state (held in memory on the service worker) ------------------ // // A run lives only as long as its processMessage() call. If the SW gets @@ -143,6 +172,60 @@ async function _peekSeq(db, runId) { return result; } +const _runStateLoads = new Map(); + +async function _ensureRunState(runId, db = null) { + if (!runId) return null; + const existing = _runState.get(runId); + if (existing) return existing; + const pending = _runStateLoads.get(runId); + if (pending) return pending; + const load = (async () => { + try { + const resolvedDb = db || await openDB(); + const seq = await _peekSeq(resolvedDb, runId); + const flags = await peekRunFlags(resolvedDb, runId); + const state = { seq, forced: flags.forced, lossless: flags.lossless, losslessBytes: Number(flags.losslessBytes) || 0 }; + _runState.set(runId, state); + return state; + } catch { return null; } + })(); + _runStateLoads.set(runId, load); + try { return await load; } + finally { if (_runStateLoads.get(runId) === load) _runStateLoads.delete(runId); } +} + +function boundedToolNames(tools) { + return Array.isArray(tools) + ? tools.slice(0, 100).map(tool => String(tool?.function?.name || '?').slice(0, 120)) + : []; +} + +function clampLosslessRequest(messages, tools) { + const request = { messages: messages ?? null, tools: tools ?? null }; + let serialized; + try { serialized = JSON.stringify(request); } catch { serialized = null; } + if (!serialized) { + return { + messages: { _truncated: true, length: null, head: '(unserializable request)', toolNames: boundedToolNames(tools) }, + tools: null, + }; + } + if (serialized.length <= LOSSILESS_REQUEST_CAP) return { messages, tools }; + // Keep one bounded head of the combined request. Tool names remain visible + // even when the head ends before the dynamic tool catalog. + const headCap = Math.max(1, LOSSILESS_REQUEST_CAP - 2048); + return { + messages: { + _truncated: true, + length: serialized.length, + head: serialized.slice(0, headCap), + toolNames: boundedToolNames(tools), + }, + tools: null, + }; +} + function _repairRunInTransaction(db, runId, { now, staleAfterMs }) { return new Promise((resolve, reject) => { const transaction = tx(db, ['runs', 'events'], 'readwrite'); @@ -213,6 +296,9 @@ export async function startRun(meta = {}) { const db = await openDB(); const runId = meta.runId || `run_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; const lineage = normalizeRunHeader(meta) || {}; + // Tier is decided once per run: explicit caller override wins, otherwise + // the opt-in setting. Never forced for local runs by default. + const lossless = meta.lossless === true || await losslessTraceEnabled(); const record = { runId, // Stable per-conversation id so the Traces UI can group sibling runs @@ -240,6 +326,7 @@ export async function startRun(meta = {}) { tabTitle: meta.tabTitle || '', mode: meta.mode || 'act', attachments: normalizeTraceAttachments(meta.attachments), + ...(lossless ? { lossless: true, losslessBytes: 0 } : {}), forced, stepCount: 0, totalInputTokens: 0, @@ -247,7 +334,7 @@ export async function startRun(meta = {}) { finalContent: null, }; await promisifyReq(tx(db, ['runs']).objectStore('runs').put(record)); - _runState.set(runId, { seq: 0, model: record.model, providerId: record.providerId, forced }); + _runState.set(runId, { seq: 0, model: record.model, providerId: record.providerId, forced, lossless, losslessBytes: 0 }); return runId; } catch (e) { console.warn('[trace] startRun failed:', e); @@ -259,13 +346,10 @@ async function _appendEventNow(runId, kind, data) { if (!(await tracingEnabledForRun(runId))) return; try { const db = await openDB(); - if (!_runState.has(runId)) { - // Recover from SW eviction. - const seq = await _peekSeq(db, runId); - _runState.set(runId, { seq, forced: await isForcedTraceRun(runId) }); - } + const state = await _ensureRunState(runId, db); + const resolvedData = typeof data === 'function' ? data(state) : data; const seq = _newSeq(runId); - const ev = makeEvent(runId, seq, kind, data); + const ev = makeEvent(runId, seq, kind, resolvedData); if (!ev) { // Unknown kind or unserializable data: skip the write and surface the // bug at recording time instead of storing a ghost event. @@ -273,33 +357,107 @@ async function _appendEventNow(runId, kind, data) { return null; } await promisifyReq(tx(db, ['events']).objectStore('events').put(ev)); + if (state?.lossless === true && (kind === 'llm_request' || kind === 'tool')) { + let bytes = 0; + try { bytes = JSON.stringify(resolvedData).length; } catch {} + state.losslessBytes = (state.losslessBytes || 0) + bytes; + const run = await promisifyReq(tx(db, ['runs']).objectStore('runs').get(runId)); + if (run?.lossless === true) { + run.losslessBytes = state.losslessBytes; + await promisifyReq(tx(db, ['runs']).objectStore('runs').put(run)); + await evictOldestLosslessRuns(runId, bytes); + } + } return seq; } catch (e) { console.warn('[trace] appendEvent failed:', e); } } +// Retain the active run and evict completed lossless runs oldest first when +// their aggregate recorded payload crosses the global budget. A cached running +// total keeps under-budget writes free of any store-wide scan; the scan itself +// walks every run rather than a newest-N window so old lossless runs still +// count toward the budget and stay evictable. +let _losslessTotalEstimate = null; + +async function _scanLosslessTotal() { + const db = await openDB(); + let total = 0; + await new Promise((resolve) => { + const req = tx(db, ['runs'], 'readonly').objectStore('runs').openCursor(); + req.onsuccess = () => { + const c = req.result; + if (!c) return resolve(); + if (c.value?.lossless === true) total += Number(c.value.losslessBytes) || 0; + c.continue(); + }; + req.onerror = () => resolve(); + }); + _losslessTotalEstimate = total; + return total; +} + +async function evictOldestLosslessRuns(activeRunId, addedBytes = 0) { + if (_losslessTotalEstimate === null) { + // First lossless write of this worker lifetime: learn the true total. + // The persisted run record already includes this write's bytes. + await _scanLosslessTotal(); + } else { + _losslessTotalEstimate += addedBytes; + if (_losslessTotalEstimate <= LOSSILESS_TOTAL_CAP) return; + } + const db = await openDB(); + const runs = []; + await new Promise((resolve) => { + const req = tx(db, ['runs'], 'readonly').objectStore('runs').openCursor(); + req.onsuccess = () => { + const c = req.result; + if (!c) return resolve(); + if (c.value?.lossless === true) runs.push(c.value); + c.continue(); + }; + req.onerror = () => resolve(); + }); + let total = runs.reduce((sum, run) => sum + (Number(run.losslessBytes) || 0), 0); + const candidates = runs + .filter(run => run.runId !== activeRunId && run.status !== 'running') + .sort((a, b) => (a.startedAt || 0) - (b.startedAt || 0)); + for (const run of candidates) { + if (total <= LOSSILESS_TOTAL_CAP) break; + await deleteRun(run.runId); + total -= Number(run.losslessBytes) || 0; + } + _losslessTotalEstimate = total; +} + function _appendEvent(runId, kind, data) { return _queueRunWrite(runId, () => _appendEventNow(runId, kind, data)); } export function recordLLMRequest(runId, step, payload, provenanceInput = null) { - // Never persist full prompts, message text, tool schemas, or tool names here. + // Lossless tier (opt-in): persist the request's full message/tool shape for + // deep debugging and request reconstruction. Clamped so one oversized + // request cannot exhaust IndexedDB; the marker mirrors tool-result + // truncation ({ _truncated, length, head }). + return _appendEvent(runId, 'llm_request', (state) => { + if (state?.lossless === true && provenanceInput) { + if ((state.losslessBytes || 0) >= LOSSILESS_RUN_CAP) { + let length = null; + try { length = JSON.stringify({ messages: provenanceInput.messages || null, tools: provenanceInput.tools || null }).length; } catch {} + return { step, ...payload, lossless: true, messages: { _truncated: true, length, head: '(per-run lossless budget reached)', toolNames: boundedToolNames(provenanceInput.tools) }, tools: null }; + } + const { messages, tools } = clampLosslessRequest(provenanceInput.messages || null, provenanceInput.tools || null); + return { step, ...payload, lossless: true, messages, tools }; + } + // Default tier: never persist full prompts, message text, tool schemas, or + // tool names here. // The optional fourth argument is reduced to content-free provenance only. - let promptProvenance = null; - if (provenanceInput) { - try { - promptProvenance = buildPromptTraceProvenance( - provenanceInput.messages, - provenanceInput.tools, - provenanceInput.runtimeMode, - ); - } catch { /* provenance must never break a model request */ } - } - return _appendEvent(runId, 'llm_request', { - step, - ...payload, - ...(promptProvenance ? { promptProvenance } : {}), + let promptProvenance = null; + if (provenanceInput) { + try { promptProvenance = buildPromptTraceProvenance(provenanceInput.messages, provenanceInput.tools, provenanceInput.runtimeMode); } catch {} + } + return { step, ...payload, ...(promptProvenance ? { promptProvenance } : {}) }; }); } @@ -325,21 +483,21 @@ export function recordLLMResponse(runId, step, { content, toolCalls, usage, late export function recordToolCall(runId, step, { name, args, result, latencyMs }) { // Truncate very large tool results (a11y trees can be huge). Keep the first - // 20KB verbatim and note the truncation — plenty for debugging flow, and - // the model response still has the full thing in context anyway. - let shortResult = result; - try { - const s = typeof result === 'string' ? result : JSON.stringify(result); - if (s && s.length > 20_000) { - shortResult = { _truncated: true, length: s.length, head: s.slice(0, 20_000) }; + // 20KB verbatim by default — plenty for debugging flow — and 200KB in the + // opt-in lossless tier; note the truncation either way. + return _appendEvent(runId, 'tool', (state) => { + if (state?.lossless === true && (state.losslessBytes || 0) >= LOSSILESS_RUN_CAP) { + let length = null; + try { const s = typeof result === 'string' ? result : JSON.stringify(result); length = s ? s.length : 0; } catch {} + return { step, name, args: args || null, result: { _truncated: true, length, head: '(per-run lossless budget reached)' }, latencyMs: latencyMs || null }; } - } catch {} - return _appendEvent(runId, 'tool', { - step, - name, - args: args || null, - result: shortResult, - latencyMs: latencyMs || null, + const cap = state?.lossless === true ? LOSSILESS_RESULT_CAP : 20_000; + let shortResult = result; + try { + const s = typeof result === 'string' ? result : JSON.stringify(result); + if (s && s.length > cap) shortResult = { _truncated: true, length: s.length, head: s.slice(0, cap) }; + } catch {} + return { step, name, args: args || null, result: shortResult, latencyMs: latencyMs || null }; }); } @@ -349,10 +507,7 @@ export function recordScreenshot(runId, step, dataUrl, caption = '') { if (!dataUrl) return; try { const db = await openDB(); - if (!_runState.has(runId)) { - const seq = await _peekSeq(db, runId); - _runState.set(runId, { seq, forced: await isForcedTraceRun(runId) }); - } + await _ensureRunState(runId, db); const seq = _newSeq(runId); // Decode data URL to a Blob so IDB stores raw bytes (no base64 overhead). let blob = null; @@ -478,10 +633,7 @@ export function recordLLMRetry(runId, step, { delayMs = 0, code = 'UNKNOWN' } = if (!(await tracingEnabledForRun(runId))) return; try { const db = await openDB(); - if (!_runState.has(runId)) { - const seq = await _peekSeq(db, runId); - _runState.set(runId, { seq, forced: await isForcedTraceRun(runId) }); - } + await _ensureRunState(runId, db); const attempt = (await _retryCount(db, runId, step)) + 1; return _appendEventNow(runId, 'note', { step, @@ -684,6 +836,7 @@ export async function deleteRun(runId) { } export async function clearAllRuns() { + _losslessTotalEstimate = null; const db = await openDB(); const t = tx(db, ['runs', 'events', 'shots']); await promisifyReq(t.objectStore('runs').clear()); diff --git a/src/chrome/src/trace/runtime-config.js b/src/chrome/src/trace/runtime-config.js index 660fd6773..659b90e39 100644 --- a/src/chrome/src/trace/runtime-config.js +++ b/src/chrome/src/trace/runtime-config.js @@ -17,6 +17,7 @@ const BOOLEAN_FIELDS = Object.freeze([ 'selection_grounded', 'standalone_chat_profile', 'standalone_webgpu_profile', + 'lossless_trace', ]); // Bounds keep the payload sane, not to re-validate settings: each range is a diff --git a/src/chrome/src/ui/locales/ar.js b/src/chrome/src/ui/locales/ar.js index 8085d44e1..d4b10bf24 100644 --- a/src/chrome/src/ui/locales/ar.js +++ b/src/chrome/src/ui/locales/ar.js @@ -267,6 +267,8 @@ export default { 'st.display.auto_screenshot.every_step': 'كل خطوة', 'st.display.tracing.label': 'تسجيل التتبّعات (لمقارنة النماذج)', 'st.display.tracing.desc_html': 'حفظ كل تشغيل (طلبات LLM، الاستجابات، استدعاءات الأدوات، لقطات الشاشة) في IndexedDB المحلية حتى تتمكّن من فحص النماذج ومقارنتها جنبًا إلى جنب. يُفتح في علامة تبويب «التتبّعات» منفصلة. مُعطَّل افتراضيًا لأنه يضيف عمليات كتابة على القرص في كل خطوة. افتح صفحة التتبّعات →', + 'st.display.lossless_tracing.label': 'تسجيل تفاصيل الطلب الكاملة (طبقة تصحيح الأخطاء)', + 'st.display.lossless_tracing.desc_html': 'يحفظ أيضًا المطالبات والرسائل ومخططات الأدوات الكاملة لكل طلب نموذج لغوي بدلاً من الملخصات الخالية من المحتوى، ويحتفظ بما يصل إلى 200 كيلوبايت من كل نتيجة أداة. يتيح تصحيح الأخطاء العميق وإعادة بناء الطلبات. معطّل افتراضيًا لأنه يكتب محتوى محادثتك محليًا ويستهلك مساحة أكبر بكثير. يتطلب تفعيل تسجيل التتبعات.', 'st.display.allow_local_network.label': 'السماح للوكيل بالوصول إلى الشبكة المحلية', 'st.display.allow_local_network.desc': 'يسمح لأداة fetch_url للوكيل بالوصول إلى عناوين loopback (127.0.0.1، localhost) وعناوين RFC1918 الخاصة (10.x، 192.168.x، 172.16–31.x). مفيد لخوادم التطوير المحلية، وأدوات الإنترانت، وواجهات API ذاتية الاستضافة. مُعطَّل افتراضيًا — عند تعطيله لا تستطيع صفحات حقن الـ prompt دفع الوكيل لفحص شبكتك المحلية. تظل عناوين بيانات السحابة (169.254.169.254) وأسماء *.internal/*.local محظورة حتى عند التفعيل.', 'st.display.notify_sound.label': 'صوت الإكمال', @@ -346,6 +348,8 @@ export default { 'st.profile.security_html': 'الأمان — اقرأ هذا:
', 'tr.title': 'تتبّعات WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} عملية تشغيل', 'tr.run': '{n} عملية تشغيل', 'tr.btn.refresh': '↻ تحديث', diff --git a/src/chrome/src/ui/locales/bn.js b/src/chrome/src/ui/locales/bn.js index 3c90af87b..c9de0f2f4 100644 --- a/src/chrome/src/ui/locales/bn.js +++ b/src/chrome/src/ui/locales/bn.js @@ -603,6 +603,8 @@ export default { 'st.display.auto_screenshot.every_step': "প্রতিটি পদক্ষেপ", 'st.display.tracing.label': "রেকর্ড ট্রেস (মডেল তুলনা জন্য)", 'st.display.tracing.desc_html': "স্থানীয় IndexedDB-তে প্রতিটি রান (LLM অনুরোধ, প্রতিক্রিয়া, টুল কল, স্ক্রিনশট) চালিয়ে যান যাতে আপনি পাশাপাশি মডেলগুলি পরিদর্শন এবং তুলনা করতে পারেন। একটি পৃথক ট্রেস ট্যাবে খোলে। ডিফল্টরূপে বন্ধ কারণ এটি প্রতি ধাপে ডিস্ক রাইট যোগ করে। ট্রেস পৃষ্ঠা খুলুন →", + 'st.display.lossless_tracing.label': 'সম্পূর্ণ অনুরোধের বিবরণ রেকর্ড করুন (ডিবাগ স্তর)', + 'st.display.lossless_tracing.desc_html': 'প্রতিটি LLM অনুরোধের সম্পূর্ণ প্রম্পট, বার্তা এবং টুল স্কিমা বিষয়বস্তুহীন সারাংশের পরিবর্তে সংরক্ষণ করে এবং প্রতিটি টুল ফলাফলের ২০০ KB পর্যন্ত রাখে। গভীর ডিবাগিং এবং অনুরোধ পুনর্নির্মাণ সক্ষম করে। ডিফল্টভাবে বন্ধ কারণ এটি আপনার কথোপকথনের বিষয়বস্তু স্থানীয়ভাবে লেখে এবং অনেক বেশি ডিস্ক ব্যবহার করে। ট্রেস রেকর্ডিং চালু থাকা প্রয়োজন।', 'st.display.cost_session_limit.label': "মেঘ খরচ অধিবেশন ভাতা", 'st.display.cost_session_limit.desc': "স্টপ ক্লাউড এবং OpenRouter এই সেশনটি রিপোর্ট করা বা আনুমানিক খরচ ভাতা পৌঁছানোর পরে অন্য একটি প্রদত্ত মডেল কলের আগে চলে। ডিফল্ট $10।", 'st.display.cost_total_limit.label': "ক্লাউড খরচ মোট ভাতা", @@ -935,6 +937,8 @@ export default { // --- Traces page ------------------------------------------------------- 'tr.title': "WebBrain ট্রেস", + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': "{n} চলে", 'tr.run': "{n} রান", 'tr.btn.refresh': "↻ রিফ্রেশ করুন", diff --git a/src/chrome/src/ui/locales/de.js b/src/chrome/src/ui/locales/de.js index 07e936798..be3234b6f 100644 --- a/src/chrome/src/ui/locales/de.js +++ b/src/chrome/src/ui/locales/de.js @@ -598,6 +598,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Bei jedem Schritt', 'st.display.tracing.label': 'Traces aufzeichnen (zum Modellvergleich)', 'st.display.tracing.desc_html': 'Speichert jeden Durchlauf in der lokalen IndexedDB, damit Sie Modelle seitenweise inspizieren und vergleichen können. Traces öffnen →', + 'st.display.lossless_tracing.label': 'Vollständige Anforderungsdetails aufzeichnen (Debug-Stufe)', + 'st.display.lossless_tracing.desc_html': 'Speichert zusätzlich die vollständigen Prompts, Nachrichten und Tool-Schemata jeder LLM-Anfrage statt inhaltsloser Zusammenfassungen und behält bis zu 200 KB jedes Tool-Ergebnisses. Ermöglicht tiefes Debugging und die Rekonstruktion von Anfragen. Standardmäßig aus, weil es Ihren Gesprächsinhalt lokal schreibt und deutlich mehr Speicherplatz benötigt. Erfordert aktivierte Trace-Aufzeichnung.', 'st.display.cost_session_limit.label': 'Cloud-Kosten-Sitzungskontingent', 'st.display.cost_session_limit.desc': 'Stoppt Cloud- und OpenRouter-Durchläufe vor einem weiteren kostenpflichtigen Modellaufruf, wenn diese Sitzung das Kostenkontingent erreicht.', 'st.display.cost_total_limit.label': 'Cloud-Kosten-Gesamtkontingent', @@ -933,6 +935,8 @@ export default { 'hist.confirm_clear_all': 'ALLE gespeicherten Gespräche löschen? Dies kann nicht rückgängig gemacht werden.', // --- Traces --- 'tr.title': 'WebBrain Traces', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} Durchläufe', 'tr.run': '{n} Durchlauf', 'tr.btn.refresh': '↻ Aktualisieren', diff --git a/src/chrome/src/ui/locales/en.js b/src/chrome/src/ui/locales/en.js index 2637fab55..743475f20 100644 --- a/src/chrome/src/ui/locales/en.js +++ b/src/chrome/src/ui/locales/en.js @@ -604,6 +604,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Every step', 'st.display.tracing.label': 'Record traces (for model comparison)', 'st.display.tracing.desc_html': 'Persist every run (LLM requests, responses, tool calls, screenshots) into local IndexedDB so you can inspect and compare models side-by-side. Opens in a separate Traces tab. Off by default because it adds disk writes per step. Open Traces page →', + 'st.display.lossless_tracing.label': 'Record full request details (debug tier)', + 'st.display.lossless_tracing.desc_html': 'Also persist the full prompts, messages, and tool schemas of each LLM request instead of content-free summaries, and keep up to 200 KB of each tool result. Enables deep debugging and request reconstruction. Off by default because it writes your conversation content locally and uses much more disk. Requires Record traces to be on.', 'st.display.cost_session_limit.label': 'Cloud cost session allowance', 'st.display.cost_session_limit.desc': 'Stops eligible cloud and router runs before another paid model call once this session reaches the reported or estimated cost allowance. Default $10.', 'st.display.cost_total_limit.label': 'Cloud cost total allowance', @@ -939,6 +941,8 @@ export default { // --- Traces page ------------------------------------------------------- 'tr.title': 'WebBrain Traces', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} runs', 'tr.run': '{n} run', 'tr.btn.refresh': '↻ Refresh', diff --git a/src/chrome/src/ui/locales/es.js b/src/chrome/src/ui/locales/es.js index dd709ffab..6a72cc899 100644 --- a/src/chrome/src/ui/locales/es.js +++ b/src/chrome/src/ui/locales/es.js @@ -267,6 +267,8 @@ export default { 'st.display.auto_screenshot.every_step': 'En cada paso', 'st.display.tracing.label': 'Registrar trazas (para comparar modelos)', 'st.display.tracing.desc_html': 'Guarda cada ejecución (peticiones al LLM, respuestas, llamadas a herramientas, capturas) en IndexedDB local para que puedas inspeccionar y comparar modelos lado a lado. Se abre en una pestaña de Trazas aparte. Desactivado por defecto porque añade escrituras en disco por paso. Abrir página de Trazas →', + 'st.display.lossless_tracing.label': 'Grabar detalles completos de la solicitud (nivel de depuración)', + 'st.display.lossless_tracing.desc_html': 'También guarda los prompts, mensajes y esquemas de herramientas completos de cada solicitud LLM en lugar de resúmenes sin contenido, y conserva hasta 200 KB de cada resultado de herramienta. Permite depuración profunda y reconstrucción de solicitudes. Desactivado por defecto porque escribe el contenido de tu conversación localmente y usa mucho más disco. Requiere que Grabar trazas esté activado.', 'st.display.allow_local_network.label': 'Permitir al agente acceder a la red local', 'st.display.allow_local_network.desc': 'Permite que la herramienta fetch_url del agente alcance direcciones loopback (127.0.0.1, localhost) y privadas/RFC1918 (10.x, 192.168.x, 172.16–31.x). Útil para servidores de desarrollo locales, herramientas de intranet o APIs auto-alojadas. Desactivado por defecto — cuando está desactivado, páginas con inyección de prompt no pueden dirigir al agente a sondear tu red local. Las direcciones de metadatos en la nube (169.254.169.254) y los nombres de host *.internal/*.local siguen bloqueados incluso cuando está activado.', 'st.display.notify_sound.label': 'Sonido al terminar', @@ -346,6 +348,8 @@ export default { 'st.profile.security_html': 'Seguridad — léelo:
', 'tr.title': 'Trazas de WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} ejecuciones', 'tr.run': '{n} ejecución', 'tr.btn.refresh': '↻ Actualizar', diff --git a/src/chrome/src/ui/locales/fa.js b/src/chrome/src/ui/locales/fa.js index e16de6d75..71745128b 100644 --- a/src/chrome/src/ui/locales/fa.js +++ b/src/chrome/src/ui/locales/fa.js @@ -603,6 +603,8 @@ export default { 'st.display.auto_screenshot.every_step': "هر قدم", 'st.display.tracing.label': "ثبت ردیابی (برای مقایسه مدل)", 'st.display.tracing.desc_html': "هر اجرا (درخواست‌های LLM، پاسخ‌ها، تماس‌های ابزار، تصاویر صفحه) را در IndexedDB محلی ادامه دهید تا بتوانید مدل‌ها را در کنار یکدیگر بررسی و مقایسه کنید. در یک برگه Traces جداگانه باز می شود. به‌طور پیش‌فرض خاموش است زیرا نوشته‌های دیسک را در هر مرحله اضافه می‌کند. باز کردن صفحه Traces →", + 'st.display.lossless_tracing.label': 'ثبت جزئیات کامل درخواست (لایه اشکالزدایی)', + 'st.display.lossless_tracing.desc_html': 'همچنین پرامپتها، پیامها و ساختار ابزارهای کامل هر درخواست LLM را بهجای خلاصههای بدون محتوا ذخیره میکند و تا ۲۰۰ کیلوبایت از هر نتیجه ابزار را نگه میدارد. اشکالزدایی عمیق و بازسازی درخواست را ممکن میسازد. بهصورت پیشفرض خاموش است زیرا محتوای گفتوگوی شما را محلی مینویسد و فضای دیسک بسیار بیشتری مصرف میکند. نیازمند فعال بودن ضبط ردگیری است.', 'st.display.cost_session_limit.label': "کمک هزینه جلسه هزینه ابر", 'st.display.cost_session_limit.desc': "هنگامی که این جلسه به میزان هزینه گزارش شده یا تخمین زده شده رسید، ابر را متوقف می کند و OpenRouter قبل از تماس مدل پولی دیگر اجرا می شود. پیش فرض 10 دلار.", 'st.display.cost_total_limit.label': "هزینه کل کمک هزینه", @@ -935,6 +937,8 @@ export default { // --- Traces page ------------------------------------------------------- 'tr.title': "WebBrain Traces", + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': "{n} اجرا می شود", 'tr.run': "اجرای {n}", 'tr.btn.refresh': "↻ تازه کردن", diff --git a/src/chrome/src/ui/locales/fr.js b/src/chrome/src/ui/locales/fr.js index 1f509b56b..e0e3427d8 100644 --- a/src/chrome/src/ui/locales/fr.js +++ b/src/chrome/src/ui/locales/fr.js @@ -267,6 +267,8 @@ export default { 'st.display.auto_screenshot.every_step': 'À chaque étape', 'st.display.tracing.label': 'Enregistrer les traces (pour comparer des modèles)', 'st.display.tracing.desc_html': 'Conserve chaque exécution (requêtes LLM, réponses, appels d\'outils, captures) dans IndexedDB local pour pouvoir inspecter et comparer les modèles côte à côte. S\'ouvre dans un onglet Traces distinct. Désactivé par défaut car cela ajoute des écritures disque à chaque étape. Ouvrir la page Traces →', + 'st.display.lossless_tracing.label': 'Enregistrer les détails complets des requêtes (niveau débogage)', + 'st.display.lossless_tracing.desc_html': 'Conserve aussi les prompts, messages et schémas d\'outils complets de chaque requête LLM au lieu de résumés sans contenu, et garde jusqu\'à 200 Ko de chaque résultat d\'outil. Permet un débogage approfondi et la reconstruction des requêtes. Désactivé par défaut car cela écrit localement le contenu de votre conversation et utilise beaucoup plus d\'espace disque. Nécessite que l\'enregistrement des traces soit activé.', 'st.display.allow_local_network.label': 'Autoriser l\'agent à accéder au réseau local', 'st.display.allow_local_network.desc': 'Permet à l\'outil fetch_url de l\'agent d\'atteindre les adresses loopback (127.0.0.1, localhost) et privées/RFC1918 (10.x, 192.168.x, 172.16–31.x). Utile pour les serveurs de dev locaux, outils intranet ou API auto-hébergées. Désactivé par défaut — quand désactivé, des pages avec injection de prompt ne peuvent pas pousser l\'agent à sonder votre réseau local. Les adresses de méta-données cloud (169.254.169.254) et les noms d\'hôte *.internal/*.local restent bloqués même quand activé.', 'st.display.notify_sound.label': 'Son de fin', @@ -346,6 +348,8 @@ export default { 'st.profile.security_html': 'Sécurité — à lire :
', 'tr.title': 'Traces WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} exécutions', 'tr.run': '{n} exécution', 'tr.btn.refresh': '↻ Actualiser', diff --git a/src/chrome/src/ui/locales/he.js b/src/chrome/src/ui/locales/he.js index 8e1fa03fd..b0b6fcaf1 100644 --- a/src/chrome/src/ui/locales/he.js +++ b/src/chrome/src/ui/locales/he.js @@ -573,6 +573,8 @@ export default { "st.display.auto_screenshot.every_step": "כל צעד", "st.display.tracing.label": "הקלטת עקבות (להשוואת מודלים)", "st.display.tracing.desc_html": "תתמיד בכל ריצה (LLM בקשות, תגובות, קריאות לכלים, צילומי מסך) לתוך IndexedDB המקומי כדי שתוכל לבדוק ולהשוות מודלים זה לצד זה. נפתח בלשונית Traces נפרדת. כבוי כברירת מחדל מכיוון שהוא מוסיף כתיבה בדיסק בכל שלב. פתח את דף העקבות →", + "st.display.lossless_tracing.label": "רישום פרטי בקשה מלאים (שכבת דיבאג)", + "st.display.lossless_tracing.desc_html": "שומר גם את ההנחיות, ההודעות והמבנים המלאים של כל בקשת LLM במקום סיכומים חסרי תוכן, ומחזיק עד 200KB מכל תוצאת כלי. מאפשר ניפוי שגיאות מעמיק ושחזור בקשות. כבוי כברירת מחדל משום שהוא כותב את תוכן השיחה שלך מקומית ומשתמש בהרבה יותר מקום. דורש שהקלטת מעקב תהיה פעילה.", "st.display.cost_session_limit.label": "קצבת הפגישה בעלות ענן", "st.display.cost_session_limit.desc": "מפסיק ענן ו OpenRouter פועל לפני שיחת מודל בתשלום נוספת ברגע שההפעלה הזו מגיעה לקצבת העלות המדווחת או המשוערת. ברירת מחדל 10 $.", "st.display.cost_total_limit.label": "הקצבה כוללת של עלות ענן", @@ -817,6 +819,8 @@ export default { "hist.confirm_delete": "למחוק את השיחה השמורה הזו?", "hist.confirm_clear_all": "למחוק את כל השיחות השמורות? לא ניתן לבטל פעולה זו.", "tr.title": "WebBrain עקבות", + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', "tr.runs": "{n} פועל", "tr.run": "{n} לָרוּץ", "tr.btn.refresh": "↻ רענן", diff --git a/src/chrome/src/ui/locales/hi.js b/src/chrome/src/ui/locales/hi.js index c9f292b6a..853970814 100644 --- a/src/chrome/src/ui/locales/hi.js +++ b/src/chrome/src/ui/locales/hi.js @@ -603,6 +603,8 @@ export default { 'st.display.auto_screenshot.every_step': "हर कदम", 'st.display.tracing.label': "रिकॉर्ड निशान (मॉडल तुलना के लिए)", 'st.display.tracing.desc_html': "प्रत्येक रन (एलएलएम अनुरोध, प्रतिक्रिया, टूल कॉल, स्क्रीनशॉट) को स्थानीय IndexedDB में जारी रखें ताकि आप साथ-साथ मॉडलों का निरीक्षण और तुलना कर सकें। एक अलग ट्रेसेस टैब में खुलता है। डिफ़ॉल्ट रूप से बंद है क्योंकि यह प्रति चरण डिस्क राइट जोड़ता है। ट्रेस पेज खोलें →", + 'st.display.lossless_tracing.label': 'पूर्ण अनुरोध विवरण रिकॉर्ड करें (डीबग स्तर)', + 'st.display.lossless_tracing.desc_html': 'प्रत्येक LLM अनुरोध के पूर्ण प्रॉम्प्ट, संदेश और टूल स्कीमा को बिना सामग्री वाले सारांशों के बजाय सहेजता है, और प्रत्येक टूल परिणाम का 200 KB तक रखता है। गहन डीबगिंग और अनुरोध पुनर्निर्माण सक्षम करता है। डिफ़ॉल्ट रूप से बंद क्योंकि यह आपकी बातचीत की सामग्री को स्थानीय रूप से लिखता है और बहुत अधिक डिस्क उपयोग करता है। ट्रेस रिकॉर्डिंग चालू होना आवश्यक है।', 'st.display.cost_session_limit.label': "क्लाउड लागत सत्र भत्ता", 'st.display.cost_session_limit.desc': "एक बार जब यह सत्र रिपोर्ट की गई या अनुमानित लागत भत्ता तक पहुंच जाता है, तो क्लाउड रुक जाता है और OpenRouter किसी अन्य भुगतान मॉडल कॉल से पहले चलता है। डिफ़ॉल्ट $10.", 'st.display.cost_total_limit.label': "क्लाउड लागत कुल भत्ता", @@ -935,6 +937,8 @@ export default { // --- Traces page ------------------------------------------------------- 'tr.title': "WebBrain निशान", + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': "{n} चलता है", 'tr.run': "{n} चलाएँ", 'tr.btn.refresh': "↻ ताज़ा करें", diff --git a/src/chrome/src/ui/locales/id.js b/src/chrome/src/ui/locales/id.js index 9b33c516d..303dda0d4 100644 --- a/src/chrome/src/ui/locales/id.js +++ b/src/chrome/src/ui/locales/id.js @@ -267,6 +267,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Setiap langkah', 'st.display.tracing.label': 'Rekam jejak (untuk perbandingan model)', 'st.display.tracing.desc_html': 'Simpan setiap proses (permintaan LLM, respons, pemanggilan alat, tangkapan layar) ke IndexedDB lokal sehingga Anda dapat memeriksa dan membandingkan model berdampingan. Membuka tab Jejak terpisah. Nonaktif secara bawaan karena menambah penulisan disk per langkah. Buka halaman Jejak →', + 'st.display.lossless_tracing.label': 'Rekam detail lengkap permintaan (tingkat debug)', + 'st.display.lossless_tracing.desc_html': 'Juga menyimpan prompt, pesan, dan skema alat lengkap dari setiap permintaan LLM alih-alih ringkasan tanpa konten, dan menyimpan hingga 200 KB setiap hasil alat. Memungkinkan debug mendalam dan rekonstruksi permintaan. Nonaktif secara default karena menulis konten percakapan Anda secara lokal dan memakai disk jauh lebih banyak. Memerlukan perekaman trace aktif.', 'st.display.allow_local_network.label': 'Izinkan agen mengakses jaringan lokal', 'st.display.allow_local_network.desc': 'Memungkinkan alat fetch_url agen menjangkau loopback (127.0.0.1, localhost) dan alamat privat/RFC1918 (10.x, 192.168.x, 172.16–31.x). Berguna untuk server pengembangan lokal, alat intranet, atau API yang di-host sendiri. Nonaktif secara bawaan — saat nonaktif, halaman yang disisipi prompt-injection tidak dapat menyuruh agen memindai jaringan lokal Anda. Alamat metadata cloud (169.254.169.254) dan nama host *.internal/*.local tetap diblokir bahkan saat diaktifkan.', 'st.display.notify_sound.label': 'Suara penyelesaian', @@ -346,6 +348,8 @@ export default { 'st.profile.security_html': 'Keamanan — baca ini:
', 'tr.title': 'Jejak WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} proses', 'tr.run': '{n} proses', 'tr.btn.refresh': '↻ Segarkan', diff --git a/src/chrome/src/ui/locales/ja.js b/src/chrome/src/ui/locales/ja.js index fade1fc42..aa7f5ff62 100644 --- a/src/chrome/src/ui/locales/ja.js +++ b/src/chrome/src/ui/locales/ja.js @@ -267,6 +267,8 @@ export default { 'st.display.auto_screenshot.every_step': '毎ステップ', 'st.display.tracing.label': 'トレースを記録(モデル比較用)', 'st.display.tracing.desc_html': 'すべての実行(LLM リクエスト、レスポンス、ツール呼び出し、スクリーンショット)をローカルの IndexedDB に保存し、モデルを並べて検査・比較できるようにします。別の「トレース」タブで開きます。毎ステップで書き込みが増えるためデフォルトはオフです。トレースページを開く →', + 'st.display.lossless_tracing.label': 'リクエストの完全な詳細を記録(デバッグ層)', + 'st.display.lossless_tracing.desc_html': '各LLMリクエストの完全なプロンプト・メッセージ・ツールスキーマを、内容を含まない要約の代わりに保存し、各ツール結果を最大200KB保持します。深いデバッグとリクエスト再構築が可能になります。会話内容をローカルに書き込み、ディスク消費が大幅に増えるため、既定ではオフです。トレース記録の有効化が必要です。', 'st.display.allow_local_network.label': 'エージェントにローカルネットワークへのアクセスを許可', 'st.display.allow_local_network.desc': 'エージェントの fetch_url ツールがループバック(127.0.0.1、localhost)やプライベート/RFC1918 アドレス(10.x、192.168.x、172.16–31.x)に届くようになります。ローカル開発サーバー、社内ツール、セルフホスト API に便利です。デフォルトはオフ — オフのときは prompt 注入されたページがエージェントにローカルネットワークの探索をさせることはできません。クラウドメタデータアドレス(169.254.169.254)と *.internal/*.local ホスト名はオンでもブロックされたままです。', 'st.display.notify_sound.label': '完了サウンド', @@ -346,6 +348,8 @@ export default { 'st.profile.security_html': 'セキュリティ — 必ずお読みください:
', 'tr.title': 'WebBrain トレース', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} 件の実行', 'tr.run': '{n} 件の実行', 'tr.btn.refresh': '↻ 更新', diff --git a/src/chrome/src/ui/locales/ko.js b/src/chrome/src/ui/locales/ko.js index e22cc0431..97f1cab42 100644 --- a/src/chrome/src/ui/locales/ko.js +++ b/src/chrome/src/ui/locales/ko.js @@ -267,6 +267,8 @@ export default { 'st.display.auto_screenshot.every_step': '매 단계', 'st.display.tracing.label': '트레이스 기록 (모델 비교용)', 'st.display.tracing.desc_html': '모든 실행(LLM 요청, 응답, 도구 호출, 스크린샷)을 로컬 IndexedDB에 보관하여 모델을 나란히 검사하고 비교할 수 있게 합니다. 별도의 트레이스 탭에서 열립니다. 매 단계마다 디스크 쓰기가 늘어나므로 기본값은 꺼짐입니다. 트레이스 페이지 열기 →', + 'st.display.lossless_tracing.label': '요청 전체 세부정보 기록(디버그 계층)', + 'st.display.lossless_tracing.desc_html': '각 LLM 요청의 전체 프롬프트·메시지·도구 스키마를 내용 없는 요약 대신 저장하고, 각 도구 결과를 최대 200KB까지 보관합니다. 심층 디버깅과 요청 재구성이 가능합니다. 대화 내용을 로컬에 기록하고 디스크를 훨씬 더 사용하므로 기본적으로 꺼져 있습니다. 트레이스 기록이 켜져 있어야 합니다.', 'st.display.allow_local_network.label': '에이전트의 로컬 네트워크 접근 허용', 'st.display.allow_local_network.desc': '에이전트의 fetch_url 도구가 루프백(127.0.0.1, localhost)과 사설/RFC1918 주소(10.x, 192.168.x, 172.16–31.x)에 접근할 수 있게 합니다. 로컬 개발 서버, 인트라넷 도구, 자체 호스팅 API에 유용합니다. 기본값은 끔 — 꺼져 있으면 prompt 주입된 페이지가 에이전트로 하여금 로컬 네트워크를 탐색하게 만들 수 없습니다. 클라우드 메타데이터 주소(169.254.169.254)와 *.internal/*.local 호스트명은 켜진 상태에서도 차단됩니다.', 'st.display.notify_sound.label': '완료 알림음', @@ -346,6 +348,8 @@ export default { 'st.profile.security_html': '보안 — 반드시 읽어주세요:
', 'tr.title': 'WebBrain 트레이스', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '실행 {n}건', 'tr.run': '실행 {n}건', 'tr.btn.refresh': '↻ 새로고침', diff --git a/src/chrome/src/ui/locales/ms.js b/src/chrome/src/ui/locales/ms.js index f35a12ff6..672580296 100644 --- a/src/chrome/src/ui/locales/ms.js +++ b/src/chrome/src/ui/locales/ms.js @@ -267,6 +267,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Setiap langkah', 'st.display.tracing.label': 'Rakam jejak (untuk perbandingan model)', 'st.display.tracing.desc_html': 'Simpan setiap larian (permintaan LLM, respons, panggilan alat, tangkapan skrin) ke dalam IndexedDB tempatan supaya anda boleh memeriksa dan membandingkan model secara bersebelahan. Dibuka dalam tab Jejak yang berasingan. Dimatikan secara lalai kerana ia menambah penulisan cakera pada setiap langkah. Buka halaman Jejak →', + 'st.display.lossless_tracing.label': 'Rekod butiran penuh permintaan (peringkat debug)', + 'st.display.lossless_tracing.desc_html': 'Juga menyimpan prompt, mesej, dan skema alatan penuh bagi setiap permintaan LLM dan bukannya ringkasan tanpa kandungan, serta mengekalkan sehingga 200 KB bagi setiap hasil alatan. Membolehkan penyahpepijatan mendalam dan pembinaan semula permintaan. Dimatikan secara lalai kerana menulis kandungan perbualan anda secara setempat dan menggunakan lebih banyak cakera. Memerlukan rakaman jejak dihidupkan.', 'st.display.allow_local_network.label': 'Benarkan ejen mengakses rangkaian tempatan', 'st.display.allow_local_network.desc': 'Membenarkan alat fetch_url ejen mencapai loopback (127.0.0.1, localhost) dan alamat peribadi/RFC1918 (10.x, 192.168.x, 172.16–31.x). Berguna untuk pelayan pembangunan tempatan, alat intranet, atau API yang dihos sendiri. Dimatikan secara lalai — apabila dimatikan, halaman yang disuntik prompt-injection tidak boleh mendorong ejen untuk meneliti rangkaian tempatan anda. Alamat metadata awan (169.254.169.254) dan nama hos *.internal/*.local kekal disekat walaupun dihidupkan.', 'st.display.notify_sound.label': 'Bunyi selesai', @@ -346,6 +348,8 @@ export default { 'st.profile.security_html': 'Keselamatan — sila baca:
', 'tr.title': 'Jejak WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} larian', 'tr.run': '{n} larian', 'tr.btn.refresh': '↻ Muat semula', diff --git a/src/chrome/src/ui/locales/nl.js b/src/chrome/src/ui/locales/nl.js index 6dae32f5a..d4b13eb18 100644 --- a/src/chrome/src/ui/locales/nl.js +++ b/src/chrome/src/ui/locales/nl.js @@ -580,6 +580,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Elke stap', 'st.display.tracing.label': 'Traces opslaan (voor modelvergelijking)', 'st.display.tracing.desc_html': 'Sla elke uitvoering op in lokale IndexedDB... Traces openen →', + 'st.display.lossless_tracing.label': 'Volledige verzoekdetails opnemen (debuglaag)', + 'st.display.lossless_tracing.desc_html': 'Bewaart ook de volledige prompts, berichten en tool-schema\'s van elk LLM-verzoek in plaats van inhoudsloze samenvattingen, en houdt tot 200 KB van elk toolresultaat. Maakt diepgaand debuggen en reconstructie van verzoeken mogelijk. Standaard uit omdat het de inhoud van uw gesprek lokaal opslaat en veel meer schijfruimte gebruikt. Vereist dat tracerecordering aan staat.', 'st.display.cost_session_limit.label': 'Cloudkostensessietegoed', 'st.display.cost_session_limit.desc': 'Stopt cloud- en OpenRouter-uitvoeringen voordat nog een betaald modelaanroep plaatsvindt...', 'st.display.cost_total_limit.label': 'Cloudkostentotaaltegoed', @@ -835,6 +837,8 @@ export default { 'hist.confirm_delete': 'Dit opgeslagen gesprek verwijderen?', 'hist.confirm_clear_all': 'Alle opgeslagen gesprekken verwijderen? Dit kan niet ongedaan worden gemaakt.', 'tr.title': 'WebBrain Traces', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} uitvoeringen', 'tr.run': '{n} uitvoering', 'tr.btn.refresh': '↻ Vernieuwen', diff --git a/src/chrome/src/ui/locales/pl.js b/src/chrome/src/ui/locales/pl.js index c2ef1ab6d..867b42d86 100644 --- a/src/chrome/src/ui/locales/pl.js +++ b/src/chrome/src/ui/locales/pl.js @@ -419,6 +419,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Przy każdym kroku', 'st.display.tracing.label': 'Rejestruj ślady (do porównywania modeli)', 'st.display.tracing.desc_html': 'Zapisuj każde wykonanie (żądania LLM, odpowiedzi, wywołania narzędzi, zrzuty ekranu) w lokalnej bazie IndexedDB, aby móc je analizować i porównywać modele obok siebie. Otwiera się w osobnej karcie Ślady. Domyślnie wyłączone, ponieważ dodaje zapisy na dysk przy każdym kroku. Otwórz stronę Ślady →', + 'st.display.lossless_tracing.label': 'Zapisuj pełne szczegóły żądań (poziom debugowania)', + 'st.display.lossless_tracing.desc_html': 'Zapisuje również pełne prompty, wiadomości i schematy narzędzi każdego żądania LLM zamiast podsumowań bez treści oraz przechowuje do 200 KB każdego wyniku narzędzia. Umożliwia głębokie debugowanie i rekonstrukcję żądań. Wyłączone domyślnie, ponieważ zapisuje treść rozmowy lokalnie i zużywa znacznie więcej miejsca. Wymaga włączonego zapisu śladów.', 'st.display.cost_session_limit.label': 'Limit kosztów chmury na sesję', 'st.display.cost_session_limit.desc': 'Zatrzymuje wykonania w chmurze i OpenRouter przed kolejnym płatnym wywołaniem modelu, gdy ta sesja osiągnie zgłoszony lub szacowany limit kosztów. Domyślnie 10 USD.', 'st.display.cost_total_limit.label': 'Całkowity limit kosztów chmury', @@ -586,6 +588,8 @@ export default { 'st.captcha.balance_fail': 'Niepowodzenie: {error}', 'st.captcha.security_html': 'Uwaga: klucz API jest przechowywany w postaci jawnej w lokalnej pamięci przeglądarki. CapSolver obciąża Twoje konto za każde rozwiązanie; agent wywoła go tylko, gdy CAPTCHA faktycznie zablokuje krok (maksymalnie raz na napotkanie — nie ponawia po niepowodzeniu). Regulaminy niektórych witryn zabraniają automatycznego rozwiązywania CAPTCHA; kieruj się własnym osądem.', 'tr.title': 'Ślady WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} wykonań', 'tr.run': '{n} wykonanie', 'tr.btn.refresh': '↻ Odśwież', diff --git a/src/chrome/src/ui/locales/pt.js b/src/chrome/src/ui/locales/pt.js index b96b206d7..ac655167e 100644 --- a/src/chrome/src/ui/locales/pt.js +++ b/src/chrome/src/ui/locales/pt.js @@ -603,6 +603,8 @@ export default { 'st.display.auto_screenshot.every_step': "Cada passo", 'st.display.tracing.label': "Registrar rastreamentos (para comparação de modelos)", 'st.display.tracing.desc_html': "Persista cada execução (solicitações LLM, respostas, chamadas de ferramentas, capturas de tela) no IndexedDB local para que você possa inspecionar e comparar modelos lado a lado. Abre em uma guia Traces separada. Desativado por padrão porque adiciona gravações de disco por etapa. Abra a página Traces →", + 'st.display.lossless_tracing.label': 'Registrar detalhes completos da solicitação (nível de depuração)', + 'st.display.lossless_tracing.desc_html': 'Também persiste os prompts, mensagens e esquemas de ferramentas completos de cada solicitação LLM em vez de resumos sem conteúdo, e mantém até 200 KB de cada resultado de ferramenta. Permite depuração profunda e reconstrução de solicitações. Desativado por padrão porque grava o conteúdo da sua conversa localmente e usa muito mais disco. Exige que o registro de rastros esteja ativado.', 'st.display.cost_session_limit.label': "Subsídio de sessão de custo na nuvem", 'st.display.cost_session_limit.desc': "Interrompe a nuvem e OpenRouter é executado antes de outra chamada de modelo pago quando esta sessão atinge o limite de custo relatado ou estimado. Padrão $ 10.", 'st.display.cost_total_limit.label': "Permissão total de custos de nuvem", @@ -935,6 +937,8 @@ export default { // --- Traces page ------------------------------------------------------- 'tr.title': "WebBrain Rastreamentos", + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': "{n} é executado", 'tr.run': "{n} executado", 'tr.btn.refresh': "↻ Atualizar", diff --git a/src/chrome/src/ui/locales/ru.js b/src/chrome/src/ui/locales/ru.js index f46355c82..966ddc615 100644 --- a/src/chrome/src/ui/locales/ru.js +++ b/src/chrome/src/ui/locales/ru.js @@ -267,6 +267,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Каждый шаг', 'st.display.tracing.label': 'Записывать трассы (для сравнения моделей)', 'st.display.tracing.desc_html': 'Сохранять каждый запуск (LLM-запросы, ответы, вызовы инструментов, скриншоты) в локальную IndexedDB, чтобы можно было разбирать и сравнивать модели бок о бок. Открывается в отдельной вкладке «Трассы». По умолчанию выключено, потому что добавляет запись на диск на каждом шаге. Открыть страницу трасс →', + 'st.display.lossless_tracing.label': 'Записывать полные детали запросов (отладочный уровень)', + 'st.display.lossless_tracing.desc_html': 'Также сохраняет полные промпты, сообщения и схемы инструментов каждого LLM-запроса вместо кратких сводок без содержимого, и хранит до 200 КБ каждого результата инструмента. Позволяет глубокую отладку и восстановление запросов. Выключено по умолчанию, так как записывает содержимое вашего диалога локально и занимает заметно больше места. Требует включённой записи трасс.', 'st.display.allow_local_network.label': 'Разрешить агенту доступ к локальной сети', 'st.display.allow_local_network.desc': 'Разрешает инструменту fetch_url агента обращаться к loopback (127.0.0.1, localhost) и частным адресам RFC1918 (10.x, 192.168.x, 172.16–31.x). Полезно для локальных серверов разработки, интранет-инструментов и self-hosted API. По умолчанию выключено — когда выключено, страницы с prompt-инъекцией не смогут заставить агента сканировать вашу локальную сеть. Адреса метаданных облака (169.254.169.254) и имена *.internal/*.local остаются заблокированы даже когда включено.', 'st.display.notify_sound.label': 'Звук завершения', @@ -346,6 +348,8 @@ export default { 'st.profile.security_html': 'Безопасность — прочтите:
', 'tr.title': 'Трассы WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': 'запусков: {n}', 'tr.run': 'запуск: {n}', 'tr.btn.refresh': '↻ Обновить', diff --git a/src/chrome/src/ui/locales/th.js b/src/chrome/src/ui/locales/th.js index 3d64315ce..84808b18c 100644 --- a/src/chrome/src/ui/locales/th.js +++ b/src/chrome/src/ui/locales/th.js @@ -267,6 +267,8 @@ export default { 'st.display.auto_screenshot.every_step': 'ทุกขั้น', 'st.display.tracing.label': 'บันทึกการติดตาม (เพื่อเปรียบเทียบโมเดล)', 'st.display.tracing.desc_html': 'จัดเก็บทุกการทำงาน (คำขอ LLM, การตอบกลับ, การเรียกใช้เครื่องมือ, ภาพหน้าจอ) ลงใน IndexedDB ในเครื่อง เพื่อให้คุณตรวจสอบและเปรียบเทียบโมเดลแบบเคียงข้างกันได้ จะเปิดในแท็บ Traces แยกต่างหาก ปิดเป็นค่าเริ่มต้นเพราะเพิ่มการเขียนดิสก์ในทุกขั้น เปิดหน้า Traces →', + 'st.display.lossless_tracing.label': 'บันทึกรายละเอียดคำขอทั้งหมด (ระดับดีบัก)', + 'st.display.lossless_tracing.desc_html': 'จัดเก็บพรอมต์ ข้อความ และโครงสร้างเครื่องมือแบบเต็มของแต่ละคำขอ LLM แทนสรุปที่ไม่มีเนื้อหา และเก็บผลลัพธ์เครื่องมือแต่ละรายการสูงสุด 200 KB เปิดใช้งานการดีบักเชิงลึกและการสร้างคำขอใหม่ ปิดโดยค่าเริ่มต้นเพราะเขียนเนื้อหาสนทนาของคุณในเครื่องและใช้พื้นที่ดิสก์มากกว่ามาก ต้องเปิดการบันทึกเทรซ', 'st.display.allow_local_network.label': 'อนุญาตให้เอเจนต์เข้าถึงเครือข่ายภายใน', 'st.display.allow_local_network.desc': 'ให้เครื่องมือ fetch_url ของเอเจนต์เข้าถึง loopback (127.0.0.1, localhost) และที่อยู่ส่วนตัว/RFC1918 (10.x, 192.168.x, 172.16–31.x) มีประโยชน์สำหรับเซิร์ฟเวอร์พัฒนาภายใน เครื่องมืออินทราเน็ต หรือ API ที่โฮสต์เอง ปิดเป็นค่าเริ่มต้น — เมื่อปิด หน้าเว็บที่ถูกแทรก prompt-injection ไม่สามารถสั่งเอเจนต์ให้สำรวจเครือข่ายภายในของคุณได้ ส่วนที่อยู่เมตาดาตาคลาวด์ (169.254.169.254) และโฮสต์ *.internal/*.local จะยังถูกบล็อกแม้จะเปิดไว้', 'st.display.notify_sound.label': 'เสียงเมื่อทำเสร็จ', @@ -346,6 +348,8 @@ export default { 'st.profile.security_html': 'ความปลอดภัย — โปรดอ่าน:
', 'tr.title': 'Traces ของ WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} ครั้ง', 'tr.run': '{n} ครั้ง', 'tr.btn.refresh': '↻ รีเฟรช', diff --git a/src/chrome/src/ui/locales/tl.js b/src/chrome/src/ui/locales/tl.js index d0f5cf24b..19e65b444 100644 --- a/src/chrome/src/ui/locales/tl.js +++ b/src/chrome/src/ui/locales/tl.js @@ -267,6 +267,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Bawat hakbang', 'st.display.tracing.label': 'I-record ang mga trace (para sa paghahambing ng modelo)', 'st.display.tracing.desc_html': 'I-save ang bawat run (mga LLM request, response, tool call, screenshot) sa lokal na IndexedDB para maaari mong suriin at ihambing ang mga modelo nang magkatabi. Bumubukas sa hiwalay na Traces tab. Naka-off bilang default dahil nagdadagdag ito ng disk writes kada hakbang. Buksan ang Traces page →', + 'st.display.lossless_tracing.label': 'Itala ang buong detalye ng kahilingan (antas ng debug)', + 'st.display.lossless_tracing.desc_html': 'Iniimbak din ang buong prompt, mensahe, at schema ng tool ng bawat LLM request sa halip na buod na walang nilalaman, at nagpapanatili ng hanggang 200 KB ng bawat resulta ng tool. Nagbibigay-daan sa malalim na pag-debug at muling pagbuo ng request. Naka-off bilang default dahil isinusulat nito ang nilalaman ng iyong usapan nang lokal at gumagamit ng mas malaking disk. Nangangailangan ng naka-on na pag-record ng trace.', 'st.display.allow_local_network.label': 'Pahintulutan ang ahente na ma-access ang lokal na network', 'st.display.allow_local_network.desc': 'Hinahayaan ang fetch_url tool ng ahente na maabot ang loopback (127.0.0.1, localhost) at mga private/RFC1918 address (10.x, 192.168.x, 172.16–31.x). Mainam para sa mga lokal na dev server, intranet tool, o self-hosted API. Naka-off bilang default — kapag naka-off, hindi mapipilit ng mga pahina na may prompt-injection ang ahente na sumiyasat sa iyong lokal na network. Mananatiling naka-block ang mga cloud-metadata address (169.254.169.254) at mga *.internal/*.local hostname kahit naka-on ito.', 'st.display.notify_sound.label': 'Tunog kapag tapos na', @@ -346,6 +348,8 @@ export default { 'st.profile.security_html': 'Seguridad — basahin ito:
', 'tr.title': 'Mga Trace ng WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} run', 'tr.run': '{n} run', 'tr.btn.refresh': '↻ I-refresh', diff --git a/src/chrome/src/ui/locales/tr.js b/src/chrome/src/ui/locales/tr.js index bf3114b83..8a4b396e9 100644 --- a/src/chrome/src/ui/locales/tr.js +++ b/src/chrome/src/ui/locales/tr.js @@ -303,6 +303,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Her adımda', 'st.display.tracing.label': 'İzleri kaydet (model karşılaştırması için)', 'st.display.tracing.desc_html': 'Her çalışmayı (LLM istekleri, yanıtlar, araç çağrıları, ekran görüntüleri) yerel IndexedDB\'de saklar, böylece modelleri yan yana inceleyip karşılaştırabilirsin. Ayrı bir İzler sekmesinde açılır. Her adımda diske yazdığı için varsayılan olarak kapalıdır. İzler sayfasını aç →', + 'st.display.lossless_tracing.label': 'Tüm istek ayrıntılarını kaydet (hata ayıklama katmanı)', + 'st.display.lossless_tracing.desc_html': 'Her LLM isteğinin tam prompt, mesaj ve araç şemalarını içeriksiz özetler yerine de kalıcı olarak kaydeder ve her araç sonucundan en fazla 200 KB tutar. Derin hata ayıklama ve istek yeniden yapılandırmasını sağlar. Varsayılan olarak kapalıdır çünkü konuşma içeriğinizi yerel olarak yazar ve çok daha fazla disk kullanır. İz kaydının açık olmasını gerektirir.', 'st.display.allow_local_network.label': 'Aracının yerel ağa erişmesine izin ver', 'st.display.allow_local_network.desc': 'Aracının fetch_url aracının loopback (127.0.0.1, localhost) ve özel/RFC1918 adreslerine (10.x, 192.168.x, 172.16–31.x) ulaşmasına izin verir. Yerel geliştirme sunucuları, intranet araçları veya kendi sunucundaki API\'lar için kullanışlıdır. Varsayılan olarak kapalıdır — kapalıyken, prompt-injection içeren sayfalar aracıyı yerel ağını taramaya yönlendiremez. Bulut meta-veri adresleri (169.254.169.254) ve *.internal/*.local ana makine adları açıkken bile engellenir.', 'st.display.notify_sound.label': 'Tamamlama sesi', @@ -385,6 +387,8 @@ export default { 'st.profile.security_html': 'Güvenlik — lütfen oku:
', 'tr.title': 'WebBrain İzleri', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} çalışma', 'tr.run': '{n} çalışma', 'tr.btn.refresh': '↻ Yenile', diff --git a/src/chrome/src/ui/locales/uk.js b/src/chrome/src/ui/locales/uk.js index 245427618..427ba54cd 100644 --- a/src/chrome/src/ui/locales/uk.js +++ b/src/chrome/src/ui/locales/uk.js @@ -267,6 +267,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Кожен крок', 'st.display.tracing.label': 'Записувати траси (для порівняння моделей)', 'st.display.tracing.desc_html': 'Зберігати кожен запуск (LLM-запити, відповіді, виклики інструментів, скриншоти) у локальну IndexedDB, щоб переглядати й порівнювати моделі поряд. Відкривається в окремій вкладці «Траси». За замовчуванням вимкнено, бо додає запис на диск на кожному кроці. Відкрити сторінку трас →', + 'st.display.lossless_tracing.label': 'Записувати повні деталі запитів (рівень налагодження)', + 'st.display.lossless_tracing.desc_html': 'Також зберігає повні промпти, повідомлення та схеми інструментів кожного LLM-запиту замість стислих зведень без вмісту, та тримає до 200 КБ кожного результату інструмента. Дає змогу глибокого налагодження та відтворення запитів. Вимкнено за замовчуванням, бо записує вміст вашої розмови локально та займає значно більше диска. Потребує увімкненого запису трас.', 'st.display.allow_local_network.label': 'Дозволити агенту доступ до локальної мережі', 'st.display.allow_local_network.desc': 'Дозволяє інструменту fetch_url агента звертатися до loopback (127.0.0.1, localhost) та приватних адрес RFC1918 (10.x, 192.168.x, 172.16–31.x). Корисно для локальних серверів розробки, інтранет-інструментів і self-hosted API. За замовчуванням вимкнено — коли вимкнено, сторінки з prompt-ін\'єкцією не зможуть змусити агента сканувати вашу локальну мережу. Адреси метаданих хмари (169.254.169.254) та імена *.internal/*.local залишаються заблокованими навіть коли ввімкнено.', 'st.display.notify_sound.label': 'Звук завершення', @@ -346,6 +348,8 @@ export default { 'st.profile.security_html': 'Безпека — прочитайте:
', 'tr.title': 'Траси WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': 'запусків: {n}', 'tr.run': 'запуск: {n}', 'tr.btn.refresh': '↻ Оновити', diff --git a/src/chrome/src/ui/locales/vi.js b/src/chrome/src/ui/locales/vi.js index 849073f4f..3eeb7f28b 100644 --- a/src/chrome/src/ui/locales/vi.js +++ b/src/chrome/src/ui/locales/vi.js @@ -603,6 +603,8 @@ export default { 'st.display.auto_screenshot.every_step': "Mỗi bước", 'st.display.tracing.label': "Ghi lại dấu vết (để so sánh mô hình)", 'st.display.tracing.desc_html': "Duy trì mọi lần chạy (yêu cầu LLM, phản hồi, lệnh gọi công cụ, ảnh chụp màn hình) vào IndexedDB cục bộ để bạn có thể kiểm tra và so sánh các mô hình song song. Mở trong tab Dấu vết riêng biệt. Tắt theo mặc định vì nó thêm ghi vào đĩa mỗi bước. Mở trang Dấu vết →", + 'st.display.lossless_tracing.label': 'Ghi lại chi tiết đầy đủ của yêu cầu (lớp gỡ lỗi)', + 'st.display.lossless_tracing.desc_html': 'Cũng lưu trữ đầy đủ prompt, tin nhắn và cấu trúc công cụ của mỗi yêu cầu LLM thay vì bản tóm tắt không nội dung, đồng thời giữ tối đa 200 KB mỗi kết quả công cụ. Cho phép gỡ lỗi sâu và dựng lại yêu cầu. Tắt theo mặc định vì ghi nội dung hội thoại cục bộ và tốn nhiều dung lượng hơn. Yêu cầu bật tính năng ghi trace.', 'st.display.cost_session_limit.label': "Phụ cấp phiên chi phí đám mây", 'st.display.cost_session_limit.desc': "Dừng đám mây và OpenRouter chạy trước một lệnh gọi mô hình trả phí khác sau khi phiên này đạt đến mức chi phí được báo cáo hoặc ước tính. Mặc định là 10$.", 'st.display.cost_total_limit.label': "Tổng phụ cấp chi phí đám mây", @@ -935,6 +937,8 @@ export default { // --- Traces page ------------------------------------------------------- 'tr.title': "Dấu vết WebBrain", + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': "{n} chạy", 'tr.run': "Chạy {n}", 'tr.btn.refresh': "↻ Làm mới", diff --git a/src/chrome/src/ui/locales/zh.js b/src/chrome/src/ui/locales/zh.js index fdc0f90bb..5437cf450 100644 --- a/src/chrome/src/ui/locales/zh.js +++ b/src/chrome/src/ui/locales/zh.js @@ -267,6 +267,8 @@ export default { 'st.display.auto_screenshot.every_step': '每一步', 'st.display.tracing.label': '记录轨迹(用于模型对比)', 'st.display.tracing.desc_html': '将每次运行(LLM 请求、响应、工具调用、截图)持久化到本地 IndexedDB,以便并排查看和对比模型。在独立的轨迹页打开。因为每步都会写盘,默认关闭。打开轨迹页 →', + 'st.display.lossless_tracing.label': '记录完整请求详情(调试档)', + 'st.display.lossless_tracing.desc_html': '同时持久化每次 LLM 请求的完整提示词、消息与工具结构(而非无内容摘要),并保留每个工具结果最多 200 KB。支持深度调试与请求重建。默认关闭,因为会在本地写入对话内容并占用更多磁盘。需要先开启记录轨迹。', 'st.display.allow_local_network.label': '允许代理访问本地网络', 'st.display.allow_local_network.desc': '允许代理的 fetch_url 工具访问回环(127.0.0.1、localhost)和私有/RFC1918 地址(10.x、192.168.x、172.16–31.x)。用于本地开发服务器、内网工具或自托管 API。默认关闭 — 关闭时,含 prompt 注入的页面无法引导代理探测你的本地网络。即使开启,云元数据地址(169.254.169.254)和 *.internal/*.local 主机名仍然会被阻止。', 'st.display.notify_sound.label': '完成提示音', @@ -346,6 +348,8 @@ export default { 'st.profile.security_html': '安全提示 — 请阅读:
', 'tr.title': 'WebBrain 轨迹', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} 次运行', 'tr.run': '{n} 次运行', 'tr.btn.refresh': '↻ 刷新', diff --git a/src/chrome/src/ui/settings.html b/src/chrome/src/ui/settings.html index fa0aac093..5689de20d 100644 --- a/src/chrome/src/ui/settings.html +++ b/src/chrome/src/ui/settings.html @@ -1507,6 +1507,16 @@

+
+
+
+
+
+ +
diff --git a/src/chrome/src/ui/settings.js b/src/chrome/src/ui/settings.js index f95636112..1c36ee631 100644 --- a/src/chrome/src/ui/settings.js +++ b/src/chrome/src/ui/settings.js @@ -108,6 +108,7 @@ const planReviewConfidenceRow = document.getElementById('row-plan-review-confide const notifySoundToggle = document.getElementById('toggle-notify-sound'); const completionConfettiToggle = document.getElementById('toggle-completion-confetti'); const tracingToggle = document.getElementById('toggle-tracing'); +const losslessTracingToggle = document.getElementById('toggle-lossless-tracing'); const strictSecretToggle = document.getElementById('toggle-strict-secret'); const allowLocalNetworkToggle = document.getElementById('toggle-allow-local-network'); const cloudBridgeToggle = document.getElementById('toggle-cloud-bridge'); @@ -698,7 +699,7 @@ async function init() { chrome.storage.local.remove(['authToken', 'authEmail', 'authDefaultModel']).catch(() => {}); // Load display settings - const stored = await chrome.storage.local.get(['verboseMode', 'selectionShortcutEnabled', AUTO_GROUP_TABS_KEY, 'helpImproveWebBrain', 'screenshotFallback', 'maxAgentSteps', 'autoScreenshot', 'useSiteAdapters', 'researchEscalationEnabled', 'researchEscalationEngine', 'voiceInputEnabled', 'alwaysAllowApiMutations', 'apiMutationObserverEnabled', 'webMcpEnabled', 'openaiAskStreamingEnabled', 'planBeforeActMode', 'planBeforeAct', 'planReviewMode', 'planReviewConfidenceThreshold', DOWNLOAD_DIRECTORY_STORAGE_KEY, 'notifySound', 'completionConfetti', 'tracingEnabled', 'strictSecretMode', 'agentAllowLocalNetwork', CLOUD_BRIDGE_ENABLED_KEY, CLOUD_BRIDGE_URL_KEY, 'scheduledTasksEnabled', 'scheduledRequireConsequentialConfirmation', 'providerFilter', 'requestTimeoutMs', 'clarifyTimeoutSec', 'clarifyTimeoutSemanticsV2', 'costAllowanceSessionUsd', 'costAllowanceTotalUsd', 'meteredProviderCostSpentUsd', 'screenshotRedaction', 'imageDetail', 'maxScreenshotsPerTurn', 'maxImageDimension']); + const stored = await chrome.storage.local.get(['verboseMode', 'selectionShortcutEnabled', AUTO_GROUP_TABS_KEY, 'helpImproveWebBrain', 'screenshotFallback', 'maxAgentSteps', 'autoScreenshot', 'useSiteAdapters', 'researchEscalationEnabled', 'researchEscalationEngine', 'voiceInputEnabled', 'alwaysAllowApiMutations', 'apiMutationObserverEnabled', 'webMcpEnabled', 'openaiAskStreamingEnabled', 'planBeforeActMode', 'planBeforeAct', 'planReviewMode', 'planReviewConfidenceThreshold', DOWNLOAD_DIRECTORY_STORAGE_KEY, 'notifySound', 'completionConfetti', 'tracingEnabled', 'losslessTrace', 'strictSecretMode', 'agentAllowLocalNetwork', CLOUD_BRIDGE_ENABLED_KEY, CLOUD_BRIDGE_URL_KEY, 'scheduledTasksEnabled', 'scheduledRequireConsequentialConfirmation', 'providerFilter', 'requestTimeoutMs', 'clarifyTimeoutSec', 'clarifyTimeoutSemanticsV2', 'costAllowanceSessionUsd', 'costAllowanceTotalUsd', 'meteredProviderCostSpentUsd', 'screenshotRedaction', 'imageDetail', 'maxScreenshotsPerTurn', 'maxImageDimension']); if (typeof stored.providerFilter === 'string' && ['all','active','local','cloud','router'].includes(stored.providerFilter)) { providerFilter = stored.providerFilter; } @@ -770,6 +771,10 @@ async function init() { notifySoundToggle.checked = stored.notifySound ?? true; // on by default completionConfettiToggle.checked = stored.completionConfetti ?? true; // on by default tracingToggle.checked = stored.tracingEnabled === true; + losslessTracingToggle.checked = stored.losslessTrace === true; + // Lossless recording only means something when tracing is on; mirror the + // disabled state so the disclosure reads honestly. + losslessTracingToggle.disabled = tracingToggle.checked !== true; const sessionLimit = normalizeCostAmount(stored.costAllowanceSessionUsd); const totalLimit = normalizeCostAmount(stored.costAllowanceTotalUsd); const totalSpent = normalizeCostAmount(stored.meteredProviderCostSpentUsd, 0); @@ -1459,6 +1464,15 @@ completionConfettiToggle.addEventListener('change', async () => { tracingToggle.addEventListener('change', async () => { await chrome.storage.local.set({ tracingEnabled: tracingToggle.checked }).catch(() => {}); + losslessTracingToggle.disabled = tracingToggle.checked !== true; + if (!tracingToggle.checked) { + losslessTracingToggle.checked = false; + await chrome.storage.local.set({ losslessTrace: false }).catch(() => {}); + } +}); + +losslessTracingToggle.addEventListener('change', async () => { + await chrome.storage.local.set({ losslessTrace: losslessTracingToggle.checked }).catch(() => {}); }); costSessionLimitInput?.addEventListener('change', async () => { diff --git a/src/chrome/src/ui/traces.html b/src/chrome/src/ui/traces.html index d1e70c0bf..ae592a176 100644 --- a/src/chrome/src/ui/traces.html +++ b/src/chrome/src/ui/traces.html @@ -126,6 +126,17 @@ text-overflow: ellipsis; margin-bottom: 3px; } + .lossless-badge { + display: inline-block; + margin-left: 6px; + padding: 1px 4px; + border: 1px solid rgba(255,152,0,0.55); + border-radius: 4px; + color: var(--warning); + font-size: 9px; + font-weight: 600; + vertical-align: 1px; + } .run-item .run-meta { font-size: 10px; color: var(--text2); @@ -196,6 +207,15 @@ margin-bottom: 14px; flex-wrap: wrap; } + .lossless-warning { + margin: 0 0 14px; + padding: 9px 12px; + border-left: 3px solid var(--warning); + background: rgba(255,152,0,0.12); + color: var(--warning); + border-radius: 0 6px 6px 0; + font-size: 12px; + } /* Costly-failure highlight — runs ≥ $0.50 with empty finalContent. */ .cost-warn { color: #ff9d4d; diff --git a/src/chrome/src/ui/traces.js b/src/chrome/src/ui/traces.js index 6f0c9ad3b..c948b6ee9 100644 --- a/src/chrome/src/ui/traces.js +++ b/src/chrome/src/ui/traces.js @@ -8,6 +8,7 @@ import { deleteRun, clearAllRuns, repairStaleRuns, } from '../trace/recorder.js'; import { isKnownKind, isIgnorableKind } from '../trace/event-model.js'; +import { sanitizeTraceExport } from '../agent/trace-export.js'; import { t } from './i18n.js'; import { escapeHtml, escapeAttr } from './utils.js'; @@ -121,6 +122,9 @@ function renderList() { compareIds.includes(r.runId) ? 'compare' : '', ].filter(Boolean).join(' '); const title = r.userMessage || t('tr.no_task'); + const losslessBadge = r.lossless === true + ? `${escapeHtml(t('tr.lossless.badge'))}` + : ''; const siblings = siblingsOf(r); const convChip = siblings.length > 1 ? `🧵 ${siblings.length}` @@ -131,7 +135,7 @@ function renderList() { const costClass = isCostlyFailure ? 'cost-warn' : ''; return `
-
${escapeHtml(title.slice(0, 120))}${convChip}
+
${escapeHtml(title.slice(0, 120))}${losslessBadge}${convChip}
${escapeHtml(r.model || '?')} ${escapeHtml(r.providerId || '')} @@ -271,6 +275,7 @@ async function buildRunView(run, events, compact, objectUrls = new Set()) { ${escapeHtml(t('tr.outtokens.label'))} ${(run.totalOutputTokens || 0).toLocaleString()} ${formatCost(run.totalCost) ? `${escapeHtml(t('tr.cost.label'))} ${escapeHtml(formatCost(run.totalCost))}` : ''}
+ ${run.lossless === true ? `` : ''} ${renderConversationPanel(run, compact)}
${escapeHtml(run.userMessage || '')}
${run.finalContent ? `
${escapeHtml(t('tr.final_label'))} ${escapeHtml(run.finalContent)}
` : ''} @@ -541,7 +546,7 @@ document.getElementById('btn-export').addEventListener('click', async () => { exportedByWebBrainVersion: chrome.runtime.getManifest().version || '', schema: 'webbrain-trace/1', }; - const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' }); + const blob = new Blob([JSON.stringify(sanitizeTraceExport(payload), null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; diff --git a/src/firefox/src/agent/trace-export.js b/src/firefox/src/agent/trace-export.js index 0716016db..c738cbd50 100644 --- a/src/firefox/src/agent/trace-export.js +++ b/src/firefox/src/agent/trace-export.js @@ -22,9 +22,84 @@ import { isKnownKind } from '../trace/event-model.js'; const ARGS_LIMIT = 300; const RESULT_LIMIT = 600; +const LOSSILESS_MESSAGE_PREVIEW_LIMIT = 2000; const FOOTER = '_Screenshot pixels and vision descriptions are omitted here — see the Traces page for the complete record._'; const UNKNOWN_EVENTS_NOTE = (n) => `_Note: ${n} unknown event(s) skipped._`; +// Credential masking for the opt-in lossless tier. Exports of lossless runs +// contain real request content, so obvious secret shapes are masked before +// they reach a Markdown file — the same spirit as the strict-redaction path +// used elsewhere, kept pure and browser-neutral here. +const SECRET_PATTERNS = [ + /\bsk-[A-Za-z0-9_-]{8,}/g, + /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi, +]; +const SENSITIVE_TRACE_KEY = /(?:authorization|cookie|password|passwd|passphrase|passcode|pincode|(?:verification|confirmation|security|auth|email|twofactor|2fa|mfa|onetime|recovery)code|secret|credential|privatekey|apikey|token|accesskeyid|secretaccesskey)$/i; +const SENSITIVE_TRACE_KEY_EXACT = new Set(['code', 'pin', 'otp', 'cvv', 'cvc', 'ssn']); + +function isSensitiveTraceKey(key) { + const normalized = String(key || '').replace(/[^a-z0-9]/gi, ''); + return SENSITIVE_TRACE_KEY.test(normalized) || SENSITIVE_TRACE_KEY_EXACT.has(normalized); +} + +function maskSecrets(text) { + let out = String(text ?? ''); + for (const pattern of SECRET_PATTERNS) out = out.replace(pattern, '[redacted]'); + out = out + .replace(/\bBearer\s+[^\s,;]+/gi, 'Bearer [redacted]') + .replace(/((?:^|[^a-zA-Z0-9_])["']?(?:authorization|cookie|password|passwd|passphrase|passcode|pincode|(?:verification|confirmation|security|auth|email|twofactor|2fa|mfa|onetime|recovery)[_ -]?code|secret|credential|private[_ -]?key|api[_ -]?key|(?:access|refresh)[_ -]?token|client[_ -]?secret|token|access[_ -]?key[_ -]?id|secret[_ -]?access[_ -]?key|otp)["']?\s*[:=]\s*)(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,;}\]]+)/gi, '$1[redacted]') + .replace(/([?&](?:api[_-]?key|access[_-]?token|token|key)=)[^&\s]+/gi, '$1[redacted]'); + return out; +} + +function redactExportValue(value, key = '') { + if (isSensitiveTraceKey(key)) return '[redacted]'; + if (typeof value === 'string') return maskSecrets(value); + if (Array.isArray(value)) return value.map(item => redactExportValue(item)); + if (!value || typeof value !== 'object') return value; + return Object.fromEntries(Object.entries(value).map(([childKey, item]) => [childKey, redactExportValue(item, childKey)])); +} + +export function sanitizeTraceExport(payload) { + return payload?.run?.lossless === true ? redactExportValue(payload) : payload; +} + +// Lossless requests carry the full message/tool shape. Render a bounded, +// masked preview per message so the export stays readable without dumping +// every token of a 500 KB request. +function renderLosslessRequest(messages, tools) { + if (messages?._truncated === true) { + const total = Number(messages.length) || 0; + const head = truncate(oneLine(maskSecrets(messages.head || '')), LOSSILESS_MESSAGE_PREVIEW_LIMIT); + const toolNames = Array.isArray(messages.toolNames) ? messages.toolNames : []; + const lines = [`request truncated (${humanSize(total)} total): ${head || '(head unavailable)'}`]; + if (toolNames.length) lines.push(`tools: ${toolNames.join(', ')}`); + return `\n${lines.join('\n')}`; + } + const list = Array.isArray(messages) ? messages : []; + if (!list.length) return ' (empty request log)'; + const lines = []; + for (const message of list.slice(0, 12)) { + const role = oneLine(message?.role || '?'); + const content = maskSecrets( + typeof message?.content === 'string' + ? message.content + : (Array.isArray(message.content) + ? message.content.map(block => block?.text || block?.image_url?.url || '').join(' ') + : ''), + ); + const body = content ? truncate(oneLine(content), LOSSILESS_MESSAGE_PREVIEW_LIMIT) : '(no text)'; + lines.push(`**${role}:** ${body}`); + } + if (list.length > 12) lines.push(`… +${list.length - 12} more message(s) omitted`); + if (Array.isArray(tools) && tools.length) { + lines.push(`tools: ${tools.map(tool => oneLine(tool?.function?.name || '?')).join(', ')}`); + } else if (tools?._truncated === true && Array.isArray(tools.toolNames) && tools.toolNames.length) { + lines.push(`tools: ${tools.toolNames.join(', ')}`); + } + return `\n${lines.join('\n')}`; +} + function oneLine(t) { return String(t ?? '').replace(/\s+/g, ' ').trim(); } function humanSize(n) { return n >= 1024 ? `${(n / 1024).toFixed(1)}kb` : `${n}b`; } @@ -202,7 +277,7 @@ export function tracesToMarkdown(runsWithEvents, { Number.isFinite(d.imageBlockCount) ? `${d.imageBlockCount} image block${d.imageBlockCount === 1 ? '' : 's'}` : '', Number.isFinite(d.documentBlockCount) ? `${d.documentBlockCount} document block${d.documentBlockCount === 1 ? '' : 's'}` : '', ].filter(Boolean).join(' · '); - md += `- 🧠 Model request: ${Number(d.messageCount) || 0} messages · ${Number(d.toolsCount) || 0} tools${media ? ` · ${media}` : ''}${renderLocalWikipediaRag(d.localWikipediaRag)}${renderPromptProvenance(d.promptProvenance)}\n`; + md += `- 🧠 Model request: ${Number(d.messageCount) || 0} messages · ${Number(d.toolsCount) || 0} tools${media ? ` · ${media}` : ''}${renderLocalWikipediaRag(d.localWikipediaRag)}${renderPromptProvenance(d.promptProvenance)}${d.lossless === true ? renderLosslessRequest(d.messages, d.tools) : ''}\n`; } else if (ev.kind === 'llm_response') { const content = String(d.content || '').trim(); if (!content) continue; diff --git a/src/firefox/src/trace/recorder.js b/src/firefox/src/trace/recorder.js index 98f244ef5..f86ec22f7 100644 --- a/src/firefox/src/trace/recorder.js +++ b/src/firefox/src/trace/recorder.js @@ -27,6 +27,14 @@ import { const DB_NAME = 'webbrain_traces'; const DB_VERSION = 2; +// Lossless tier bounds: tool results up to 200 KB verbatim, request payloads +// up to 500 KB before the head is clamped with a truncation marker. Keeps an +// opt-in debugging tier from exhausting IndexedDB on a single long run. +const LOSSILESS_RESULT_CAP = 200_000; +const LOSSILESS_REQUEST_CAP = 500_000; +const LOSSILESS_RUN_CAP = 5_000_000; +const LOSSILESS_TOTAL_CAP = 50_000_000; + let _dbPromise = null; function openDB() { if (_dbPromise) return _dbPromise; @@ -90,6 +98,17 @@ async function tracingEnabled() { } catch { return false; } } +// Opt-in lossless tier: same event pipeline, full request payloads instead of +// content-free provenance. Read once per run at startRun; never per event. +async function losslessTraceEnabled() { + try { + if (typeof indexedDB === 'undefined') return false; + const storageApi = (typeof browser !== 'undefined' ? browser : chrome).storage.local; + const { losslessTrace } = await storageApi.get(['losslessTrace']); + return losslessTrace === true; + } catch { return false; } +} + // ----- Per-run state (held in memory on the service worker) ------------------ // // A run lives only as long as its processMessage() call. If the SW gets @@ -128,6 +147,71 @@ async function _peekSeq(db, runId) { return result; } +// Restore per-run flags after worker eviction from the durable run record so +// the lossless tier decision survives a worker restart mid-run. +async function peekRunFlags(db, runId) { + try { + const record = await promisifyReq( + tx(db, ['runs'], 'readonly').objectStore('runs').get(runId), + ); + return { lossless: record?.lossless === true, losslessBytes: record?.losslessBytes || 0 }; + } catch { return { lossless: false, losslessBytes: 0 }; } +} + +const _runStateLoads = new Map(); + +async function _ensureRunState(runId, db = null) { + if (!runId) return null; + const existing = _runState.get(runId); + if (existing) return existing; + const pending = _runStateLoads.get(runId); + if (pending) return pending; + const load = (async () => { + try { + const resolvedDb = db || await openDB(); + const seq = await _peekSeq(resolvedDb, runId); + const flags = await peekRunFlags(resolvedDb, runId); + const state = { seq, lossless: flags.lossless, losslessBytes: Number(flags.losslessBytes) || 0 }; + _runState.set(runId, state); + return state; + } catch { return null; } + })(); + _runStateLoads.set(runId, load); + try { return await load; } + finally { if (_runStateLoads.get(runId) === load) _runStateLoads.delete(runId); } +} + +function boundedToolNames(tools) { + return Array.isArray(tools) + ? tools.slice(0, 100).map(tool => String(tool?.function?.name || '?').slice(0, 120)) + : []; +} + +function clampLosslessRequest(messages, tools) { + const request = { messages: messages ?? null, tools: tools ?? null }; + let serialized; + try { serialized = JSON.stringify(request); } catch { serialized = null; } + if (!serialized) { + return { + messages: { _truncated: true, length: null, head: '(unserializable request)', toolNames: boundedToolNames(tools) }, + tools: null, + }; + } + if (serialized.length <= LOSSILESS_REQUEST_CAP) return { messages, tools }; + // Keep one bounded head of the combined request. Tool names remain visible + // even when the head ends before the dynamic tool catalog. + const headCap = Math.max(1, LOSSILESS_REQUEST_CAP - 2048); + return { + messages: { + _truncated: true, + length: serialized.length, + head: serialized.slice(0, headCap), + toolNames: boundedToolNames(tools), + }, + tools: null, + }; +} + function _repairRunInTransaction(db, runId, { now, staleAfterMs }) { return new Promise((resolve, reject) => { const transaction = tx(db, ['runs', 'events'], 'readwrite'); @@ -197,6 +281,9 @@ export async function startRun(meta) { const db = await openDB(); const runId = meta.runId || `run_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; const lineage = normalizeRunHeader(meta) || {}; + // Tier is decided once per run: explicit caller override wins, otherwise + // the opt-in setting. Never forced for local runs by default. + const lossless = meta.lossless === true || await losslessTraceEnabled(); const record = { runId, // Stable per-conversation id so the Traces UI can group sibling runs @@ -224,13 +311,14 @@ export async function startRun(meta) { tabTitle: meta.tabTitle || '', mode: meta.mode || 'act', attachments: normalizeTraceAttachments(meta.attachments), + ...(lossless ? { lossless: true, losslessBytes: 0 } : {}), stepCount: 0, totalInputTokens: 0, totalOutputTokens: 0, finalContent: null, }; await promisifyReq(tx(db, ['runs']).objectStore('runs').put(record)); - _runState.set(runId, { seq: 0, model: record.model, providerId: record.providerId }); + _runState.set(runId, { seq: 0, model: record.model, providerId: record.providerId, lossless, losslessBytes: 0 }); return runId; } catch (e) { console.warn('[trace] startRun failed:', e); @@ -242,13 +330,10 @@ async function _appendEventNow(runId, kind, data) { if (!(await tracingEnabled())) return; try { const db = await openDB(); - if (!_runState.has(runId)) { - // Recover from SW eviction. - const seq = await _peekSeq(db, runId); - _runState.set(runId, { seq }); - } + const state = await _ensureRunState(runId, db); + const resolvedData = typeof data === 'function' ? data(state) : data; const seq = _newSeq(runId); - const ev = makeEvent(runId, seq, kind, data); + const ev = makeEvent(runId, seq, kind, resolvedData); if (!ev) { // Unknown kind or unserializable data: skip the write and surface the // bug at recording time instead of storing a ghost event. @@ -256,33 +341,106 @@ async function _appendEventNow(runId, kind, data) { return null; } await promisifyReq(tx(db, ['events']).objectStore('events').put(ev)); + if (state?.lossless === true && (kind === 'llm_request' || kind === 'tool')) { + let bytes = 0; + try { bytes = JSON.stringify(resolvedData).length; } catch {} + state.losslessBytes = (state.losslessBytes || 0) + bytes; + const run = await promisifyReq(tx(db, ['runs']).objectStore('runs').get(runId)); + if (run?.lossless === true) { + run.losslessBytes = state.losslessBytes; + await promisifyReq(tx(db, ['runs']).objectStore('runs').put(run)); + await evictOldestLosslessRuns(runId, bytes); + } + } return seq; } catch (e) { console.warn('[trace] appendEvent failed:', e); } } +// Retain the active run and evict completed lossless runs oldest first when +// their aggregate recorded payload crosses the global budget. A cached running +// total keeps under-budget writes free of any store-wide scan; the scan itself +// walks every run rather than a newest-N window so old lossless runs still +// count toward the budget and stay evictable. +let _losslessTotalEstimate = null; + +async function _scanLosslessTotal() { + const db = await openDB(); + let total = 0; + await new Promise((resolve) => { + const req = tx(db, ['runs'], 'readonly').objectStore('runs').openCursor(); + req.onsuccess = () => { + const c = req.result; + if (!c) return resolve(); + if (c.value?.lossless === true) total += Number(c.value.losslessBytes) || 0; + c.continue(); + }; + req.onerror = () => resolve(); + }); + _losslessTotalEstimate = total; + return total; +} + +async function evictOldestLosslessRuns(activeRunId, addedBytes = 0) { + if (_losslessTotalEstimate === null) { + // First lossless write of this worker lifetime: learn the true total. + // The persisted run record already includes this write's bytes. + await _scanLosslessTotal(); + } else { + _losslessTotalEstimate += addedBytes; + if (_losslessTotalEstimate <= LOSSILESS_TOTAL_CAP) return; + } + const db = await openDB(); + const runs = []; + await new Promise((resolve) => { + const req = tx(db, ['runs'], 'readonly').objectStore('runs').openCursor(); + req.onsuccess = () => { + const c = req.result; + if (!c) return resolve(); + if (c.value?.lossless === true) runs.push(c.value); + c.continue(); + }; + req.onerror = () => resolve(); + }); + let total = runs.reduce((sum, run) => sum + (Number(run.losslessBytes) || 0), 0); + const candidates = runs + .filter(run => run.runId !== activeRunId && run.status !== 'running') + .sort((a, b) => (a.startedAt || 0) - (b.startedAt || 0)); + for (const run of candidates) { + if (total <= LOSSILESS_TOTAL_CAP) break; + await deleteRun(run.runId); + total -= Number(run.losslessBytes) || 0; + } + _losslessTotalEstimate = total; +} + function _appendEvent(runId, kind, data) { return _queueRunWrite(runId, () => _appendEventNow(runId, kind, data)); } export function recordLLMRequest(runId, step, payload, provenanceInput = null) { - // Never persist full prompts, message text, tool schemas, or tool names here. + // Lossless tier (opt-in): persist the request's full message/tool shape for + // deep debugging and request reconstruction. Clamp oversized requests so + // one request cannot exhaust IndexedDB. + return _appendEvent(runId, 'llm_request', (state) => { + if (state?.lossless === true && provenanceInput) { + if ((state.losslessBytes || 0) >= LOSSILESS_RUN_CAP) { + let length = null; + try { length = JSON.stringify({ messages: provenanceInput.messages || null, tools: provenanceInput.tools || null }).length; } catch {} + return { step, ...payload, lossless: true, messages: { _truncated: true, length, head: '(per-run lossless budget reached)', toolNames: boundedToolNames(provenanceInput.tools) }, tools: null }; + } + const { messages, tools } = clampLosslessRequest(provenanceInput.messages || null, provenanceInput.tools || null); + return { step, ...payload, lossless: true, messages, tools }; + } + // Default tier: never persist full prompts, message text, tool schemas, or + // tool names here. // The optional fourth argument is reduced to content-free provenance only. - let promptProvenance = null; - if (provenanceInput) { - try { - promptProvenance = buildPromptTraceProvenance( - provenanceInput.messages, - provenanceInput.tools, - provenanceInput.runtimeMode, - ); - } catch { /* provenance must never break a model request */ } - } - return _appendEvent(runId, 'llm_request', { - step, - ...payload, - ...(promptProvenance ? { promptProvenance } : {}), + let promptProvenance = null; + if (provenanceInput) { + try { promptProvenance = buildPromptTraceProvenance(provenanceInput.messages, provenanceInput.tools, provenanceInput.runtimeMode); } catch {} + } + return { step, ...payload, ...(promptProvenance ? { promptProvenance } : {}) }; }); } @@ -308,21 +466,21 @@ export function recordLLMResponse(runId, step, { content, toolCalls, usage, late export function recordToolCall(runId, step, { name, args, result, latencyMs }) { // Truncate very large tool results (a11y trees can be huge). Keep the first - // 20KB verbatim and note the truncation — plenty for debugging flow, and - // the model response still has the full thing in context anyway. - let shortResult = result; - try { - const s = typeof result === 'string' ? result : JSON.stringify(result); - if (s && s.length > 20_000) { - shortResult = { _truncated: true, length: s.length, head: s.slice(0, 20_000) }; + // 20KB verbatim by default — plenty for debugging flow — and 200KB in the + // opt-in lossless tier; note the truncation either way. + return _appendEvent(runId, 'tool', (state) => { + if (state?.lossless === true && (state.losslessBytes || 0) >= LOSSILESS_RUN_CAP) { + let length = null; + try { const s = typeof result === 'string' ? result : JSON.stringify(result); length = s ? s.length : 0; } catch {} + return { step, name, args: args || null, result: { _truncated: true, length, head: '(per-run lossless budget reached)' }, latencyMs: latencyMs || null }; } - } catch {} - return _appendEvent(runId, 'tool', { - step, - name, - args: args || null, - result: shortResult, - latencyMs: latencyMs || null, + const cap = state?.lossless === true ? LOSSILESS_RESULT_CAP : 20_000; + let shortResult = result; + try { + const s = typeof result === 'string' ? result : JSON.stringify(result); + if (s && s.length > cap) shortResult = { _truncated: true, length: s.length, head: s.slice(0, cap) }; + } catch {} + return { step, name, args: args || null, result: shortResult, latencyMs: latencyMs || null }; }); } @@ -332,10 +490,7 @@ export function recordScreenshot(runId, step, dataUrl, caption = '') { if (!dataUrl) return; try { const db = await openDB(); - if (!_runState.has(runId)) { - const seq = await _peekSeq(db, runId); - _runState.set(runId, { seq }); - } + await _ensureRunState(runId, db); const seq = _newSeq(runId); // Decode data URL to a Blob so IDB stores raw bytes (no base64 overhead). let blob = null; @@ -461,10 +616,7 @@ export function recordLLMRetry(runId, step, { delayMs = 0, code = 'UNKNOWN' } = if (!(await tracingEnabled())) return; try { const db = await openDB(); - if (!_runState.has(runId)) { - const seq = await _peekSeq(db, runId); - _runState.set(runId, { seq }); - } + await _ensureRunState(runId, db); const attempt = (await _retryCount(db, runId, step)) + 1; return _appendEventNow(runId, 'note', { step, @@ -666,6 +818,7 @@ export async function deleteRun(runId) { } export async function clearAllRuns() { + _losslessTotalEstimate = null; const db = await openDB(); const t = tx(db, ['runs', 'events', 'shots']); await promisifyReq(t.objectStore('runs').clear()); diff --git a/src/firefox/src/trace/runtime-config.js b/src/firefox/src/trace/runtime-config.js index 660fd6773..659b90e39 100644 --- a/src/firefox/src/trace/runtime-config.js +++ b/src/firefox/src/trace/runtime-config.js @@ -17,6 +17,7 @@ const BOOLEAN_FIELDS = Object.freeze([ 'selection_grounded', 'standalone_chat_profile', 'standalone_webgpu_profile', + 'lossless_trace', ]); // Bounds keep the payload sane, not to re-validate settings: each range is a diff --git a/src/firefox/src/ui/locales/ar.js b/src/firefox/src/ui/locales/ar.js index a53297f31..8aae01c0e 100644 --- a/src/firefox/src/ui/locales/ar.js +++ b/src/firefox/src/ui/locales/ar.js @@ -259,6 +259,8 @@ export default { 'st.display.auto_screenshot.every_step': 'كل خطوة', 'st.display.tracing.label': 'تسجيل التتبّعات (لمقارنة النماذج)', 'st.display.tracing.desc_html': 'حفظ كل تشغيل (طلبات LLM، الاستجابات، استدعاءات الأدوات، لقطات الشاشة) في IndexedDB المحلية حتى تتمكّن من فحص النماذج ومقارنتها جنبًا إلى جنب. يُفتح في علامة تبويب «التتبّعات» منفصلة. مُعطَّل افتراضيًا لأنه يضيف عمليات كتابة على القرص في كل خطوة. افتح صفحة التتبّعات →', + 'st.display.lossless_tracing.label': 'تسجيل تفاصيل الطلب الكاملة (طبقة تصحيح الأخطاء)', + 'st.display.lossless_tracing.desc_html': 'يحفظ أيضًا المطالبات والرسائل ومخططات الأدوات الكاملة لكل طلب نموذج لغوي بدلاً من الملخصات الخالية من المحتوى، ويحتفظ بما يصل إلى 200 كيلوبايت من كل نتيجة أداة. يتيح تصحيح الأخطاء العميق وإعادة بناء الطلبات. معطّل افتراضيًا لأنه يكتب محتوى محادثتك محليًا ويستهلك مساحة أكبر بكثير. يتطلب تفعيل تسجيل التتبعات.', 'st.display.allow_local_network.label': 'السماح للوكيل بالوصول إلى الشبكة المحلية', 'st.display.allow_local_network.desc': 'يسمح لأداة fetch_url للوكيل بالوصول إلى عناوين loopback (127.0.0.1، localhost) وعناوين RFC1918 الخاصة (10.x، 192.168.x، 172.16–31.x). مفيد لخوادم التطوير المحلية، وأدوات الإنترانت، وواجهات API ذاتية الاستضافة. مُعطَّل افتراضيًا — عند تعطيله لا تستطيع صفحات حقن الـ prompt دفع الوكيل لفحص شبكتك المحلية. تظل عناوين بيانات السحابة (169.254.169.254) وأسماء *.internal/*.local محظورة حتى عند التفعيل.', 'st.display.notify_sound.label': 'صوت الإكمال', @@ -331,6 +333,8 @@ export default { 'st.profile.security_html': 'الأمان — اقرأ هذا:
  • النص الذي تُدخله هنا يُخزَّن بصيغة نصية واضحة في التخزين المحلي للمتصفح. لا يُرسَل إلى مشروع WebBrain — لكنه يُرسَل إلى مزوّد LLM الذي ضبطته في كل دور، كجزء من prompt النظام.
  • لا تضع كلمات مرور حسابات مهمة هنا (Google، Apple، iCloud، البنوك، SSO العمل، البريد الرئيسي). هذه الحسابات يجب أن تستخدم المصادقة الثنائية، ولا يجدر بك تسليمها لوكيل أصلًا.
  • الاستخدام المقصود هو كلمة مرور للاستعمال لمرة واحدة تعيد استخدامها لتسجيلات منخفضة الأهمية (النشرات البريدية، التجارب المجانية، حسابات المنتديات).
  • إذا اختُرق هذا الملف الشخصي للمتصفح يومًا، فإن مهاجمًا يملك الوصول إلى القرص قادر على قراءة هذا النص. أبقِه في حدّه الأدنى.
', 'tr.title': 'تتبّعات WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} عملية تشغيل', 'tr.run': '{n} عملية تشغيل', 'tr.btn.refresh': '↻ تحديث', diff --git a/src/firefox/src/ui/locales/bn.js b/src/firefox/src/ui/locales/bn.js index 4c64ff4fd..7f7c4326e 100644 --- a/src/firefox/src/ui/locales/bn.js +++ b/src/firefox/src/ui/locales/bn.js @@ -589,6 +589,8 @@ export default { 'st.display.auto_screenshot.every_step': "প্রতিটি পদক্ষেপ", 'st.display.tracing.label': "রেকর্ড ট্রেস (মডেল তুলনা জন্য)", 'st.display.tracing.desc_html': "স্থানীয় IndexedDB-তে প্রতিটি রান (LLM অনুরোধ, প্রতিক্রিয়া, টুল কল, স্ক্রিনশট) চালিয়ে যান যাতে আপনি পাশাপাশি মডেলগুলি পরিদর্শন এবং তুলনা করতে পারেন। একটি পৃথক ট্রেস ট্যাবে খোলে। ডিফল্টরূপে বন্ধ কারণ এটি প্রতি ধাপে ডিস্ক রাইট যোগ করে। ট্রেস পৃষ্ঠা খুলুন →", + 'st.display.lossless_tracing.label': 'সম্পূর্ণ অনুরোধের বিবরণ রেকর্ড করুন (ডিবাগ স্তর)', + 'st.display.lossless_tracing.desc_html': 'প্রতিটি LLM অনুরোধের সম্পূর্ণ প্রম্পট, বার্তা এবং টুল স্কিমা বিষয়বস্তুহীন সারাংশের পরিবর্তে সংরক্ষণ করে এবং প্রতিটি টুল ফলাফলের ২০০ KB পর্যন্ত রাখে। গভীর ডিবাগিং এবং অনুরোধ পুনর্নির্মাণ সক্ষম করে। ডিফল্টভাবে বন্ধ কারণ এটি আপনার কথোপকথনের বিষয়বস্তু স্থানীয়ভাবে লেখে এবং অনেক বেশি ডিস্ক ব্যবহার করে। ট্রেস রেকর্ডিং চালু থাকা প্রয়োজন।', 'st.display.cost_session_limit.label': "মেঘ খরচ অধিবেশন ভাতা", 'st.display.cost_session_limit.desc': "স্টপ ক্লাউড এবং OpenRouter এই সেশনটি রিপোর্ট করা বা আনুমানিক খরচ ভাতা পৌঁছানোর পরে অন্য একটি প্রদত্ত মডেল কলের আগে চলে। ডিফল্ট $10।", 'st.display.cost_total_limit.label': "ক্লাউড খরচ মোট ভাতা", @@ -914,6 +916,8 @@ export default { // --- Traces page ------------------------------------------------------- 'tr.title': "WebBrain ট্রেস", + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': "{n} চলে", 'tr.run': "{n} রান", 'tr.btn.refresh': "↻ রিফ্রেশ করুন", diff --git a/src/firefox/src/ui/locales/de.js b/src/firefox/src/ui/locales/de.js index cd9921dd9..3869750f8 100644 --- a/src/firefox/src/ui/locales/de.js +++ b/src/firefox/src/ui/locales/de.js @@ -584,6 +584,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Bei jedem Schritt', 'st.display.tracing.label': 'Traces aufzeichnen (zum Modellvergleich)', 'st.display.tracing.desc_html': 'Speichert jeden Durchlauf in der lokalen IndexedDB, damit Sie Modelle seitenweise inspizieren und vergleichen können. Traces öffnen →', + 'st.display.lossless_tracing.label': 'Vollständige Anforderungsdetails aufzeichnen (Debug-Stufe)', + 'st.display.lossless_tracing.desc_html': 'Speichert zusätzlich die vollständigen Prompts, Nachrichten und Tool-Schemata jeder LLM-Anfrage statt inhaltsloser Zusammenfassungen und behält bis zu 200 KB jedes Tool-Ergebnisses. Ermöglicht tiefes Debugging und die Rekonstruktion von Anfragen. Standardmäßig aus, weil es Ihren Gesprächsinhalt lokal schreibt und deutlich mehr Speicherplatz benötigt. Erfordert aktivierte Trace-Aufzeichnung.', 'st.display.cost_session_limit.label': 'Cloud-Kosten-Sitzungskontingent', 'st.display.cost_session_limit.desc': 'Stoppt Cloud- und OpenRouter-Durchläufe vor einem weiteren kostenpflichtigen Modellaufruf, wenn diese Sitzung das Kostenkontingent erreicht.', 'st.display.cost_total_limit.label': 'Cloud-Kosten-Gesamtkontingent', @@ -912,6 +914,8 @@ export default { 'hist.confirm_clear_all': 'ALLE gespeicherten Gespräche löschen? Dies kann nicht rückgängig gemacht werden.', // --- Traces --- 'tr.title': 'WebBrain Traces', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} Durchläufe', 'tr.run': '{n} Durchlauf', 'tr.btn.refresh': '↻ Aktualisieren', diff --git a/src/firefox/src/ui/locales/en.js b/src/firefox/src/ui/locales/en.js index 79ce7eef7..cb76e4751 100644 --- a/src/firefox/src/ui/locales/en.js +++ b/src/firefox/src/ui/locales/en.js @@ -590,6 +590,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Every step', 'st.display.tracing.label': 'Record traces (for model comparison)', 'st.display.tracing.desc_html': 'Persist every run (LLM requests, responses, tool calls, screenshots) into local IndexedDB so you can inspect and compare models side-by-side. Opens in a separate Traces tab. Off by default because it adds disk writes per step. Open Traces page →', + 'st.display.lossless_tracing.label': 'Record full request details (debug tier)', + 'st.display.lossless_tracing.desc_html': 'Also persist the full prompts, messages, and tool schemas of each LLM request instead of content-free summaries, and keep up to 200 KB of each tool result. Enables deep debugging and request reconstruction. Off by default because it writes your conversation content locally and uses much more disk. Requires Record traces to be on.', 'st.display.cost_session_limit.label': 'Cloud cost session allowance', 'st.display.cost_session_limit.desc': 'Stops eligible cloud and router runs before another paid model call once this session reaches the reported or estimated cost allowance. Default $10.', 'st.display.cost_total_limit.label': 'Cloud cost total allowance', @@ -916,6 +918,8 @@ export default { // --- Traces page ------------------------------------------------------- 'tr.title': 'WebBrain Traces', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} runs', 'tr.run': '{n} run', 'tr.btn.refresh': '↻ Refresh', diff --git a/src/firefox/src/ui/locales/es.js b/src/firefox/src/ui/locales/es.js index c9d93dd7d..696e8e87b 100644 --- a/src/firefox/src/ui/locales/es.js +++ b/src/firefox/src/ui/locales/es.js @@ -259,6 +259,8 @@ export default { 'st.display.auto_screenshot.every_step': 'En cada paso', 'st.display.tracing.label': 'Registrar trazas (para comparar modelos)', 'st.display.tracing.desc_html': 'Guarda cada ejecución (peticiones al LLM, respuestas, llamadas a herramientas, capturas) en IndexedDB local para que puedas inspeccionar y comparar modelos lado a lado. Se abre en una pestaña de Trazas aparte. Desactivado por defecto porque añade escrituras en disco por paso. Abrir página de Trazas →', + 'st.display.lossless_tracing.label': 'Grabar detalles completos de la solicitud (nivel de depuración)', + 'st.display.lossless_tracing.desc_html': 'También guarda los prompts, mensajes y esquemas de herramientas completos de cada solicitud LLM en lugar de resúmenes sin contenido, y conserva hasta 200 KB de cada resultado de herramienta. Permite depuración profunda y reconstrucción de solicitudes. Desactivado por defecto porque escribe el contenido de tu conversación localmente y usa mucho más disco. Requiere que Grabar trazas esté activado.', 'st.display.allow_local_network.label': 'Permitir al agente acceder a la red local', 'st.display.allow_local_network.desc': 'Permite que la herramienta fetch_url del agente alcance direcciones loopback (127.0.0.1, localhost) y privadas/RFC1918 (10.x, 192.168.x, 172.16–31.x). Útil para servidores de desarrollo locales, herramientas de intranet o APIs auto-alojadas. Desactivado por defecto — cuando está desactivado, páginas con inyección de prompt no pueden dirigir al agente a sondear tu red local. Las direcciones de metadatos en la nube (169.254.169.254) y los nombres de host *.internal/*.local siguen bloqueados incluso cuando está activado.', 'st.display.notify_sound.label': 'Sonido al terminar', @@ -331,6 +333,8 @@ export default { 'st.profile.security_html': 'Seguridad — léelo:
  • El texto que introduces aquí se guarda en texto plano en el almacenamiento local del navegador. No se envía al proyecto WebBrain — pero se envía al proveedor de LLM que hayas configurado en cada turno, como parte del prompt del sistema.
  • No pongas aquí contraseñas de cuentas importantes (Google, Apple, iCloud, banca, SSO de trabajo, correo principal). Esas cuentas deberían usar 2FA y no deberías entregárselas a un agente.
  • Una contraseña desechable que reutilizas para registros poco importantes (boletines, pruebas gratuitas, cuentas de foro) es el uso previsto.
  • Si este perfil del navegador alguna vez se ve comprometido, un atacante con acceso al disco podrá leer este texto. Mantenlo al mínimo.
', 'tr.title': 'Trazas de WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} ejecuciones', 'tr.run': '{n} ejecución', 'tr.btn.refresh': '↻ Actualizar', diff --git a/src/firefox/src/ui/locales/fa.js b/src/firefox/src/ui/locales/fa.js index 9e0b011e9..b3eaab2d5 100644 --- a/src/firefox/src/ui/locales/fa.js +++ b/src/firefox/src/ui/locales/fa.js @@ -589,6 +589,8 @@ export default { 'st.display.auto_screenshot.every_step': "هر قدم", 'st.display.tracing.label': "ثبت ردیابی (برای مقایسه مدل)", 'st.display.tracing.desc_html': "هر اجرا (درخواست‌های LLM، پاسخ‌ها، تماس‌های ابزار، تصاویر صفحه) را در IndexedDB محلی ادامه دهید تا بتوانید مدل‌ها را در کنار یکدیگر بررسی و مقایسه کنید. در یک برگه Traces جداگانه باز می شود. به‌طور پیش‌فرض خاموش است زیرا نوشته‌های دیسک را در هر مرحله اضافه می‌کند. باز کردن صفحه Traces →", + 'st.display.lossless_tracing.label': 'ثبت جزئیات کامل درخواست (لایه اشکالزدایی)', + 'st.display.lossless_tracing.desc_html': 'همچنین پرامپتها، پیامها و ساختار ابزارهای کامل هر درخواست LLM را بهجای خلاصههای بدون محتوا ذخیره میکند و تا ۲۰۰ کیلوبایت از هر نتیجه ابزار را نگه میدارد. اشکالزدایی عمیق و بازسازی درخواست را ممکن میسازد. بهصورت پیشفرض خاموش است زیرا محتوای گفتوگوی شما را محلی مینویسد و فضای دیسک بسیار بیشتری مصرف میکند. نیازمند فعال بودن ضبط ردگیری است.', 'st.display.cost_session_limit.label': "کمک هزینه جلسه هزینه ابر", 'st.display.cost_session_limit.desc': "هنگامی که این جلسه به میزان هزینه گزارش شده یا تخمین زده شده رسید، ابر را متوقف می کند و OpenRouter قبل از تماس مدل پولی دیگر اجرا می شود. پیش فرض 10 دلار.", 'st.display.cost_total_limit.label': "هزینه کل کمک هزینه", @@ -914,6 +916,8 @@ export default { // --- Traces page ------------------------------------------------------- 'tr.title': "WebBrain Traces", + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': "{n} اجرا می شود", 'tr.run': "اجرای {n}", 'tr.btn.refresh': "↻ تازه کردن", diff --git a/src/firefox/src/ui/locales/fr.js b/src/firefox/src/ui/locales/fr.js index 5b73c4115..459bca649 100644 --- a/src/firefox/src/ui/locales/fr.js +++ b/src/firefox/src/ui/locales/fr.js @@ -259,6 +259,8 @@ export default { 'st.display.auto_screenshot.every_step': 'À chaque étape', 'st.display.tracing.label': 'Enregistrer les traces (pour comparer des modèles)', 'st.display.tracing.desc_html': 'Conserve chaque exécution (requêtes LLM, réponses, appels d\'outils, captures) dans IndexedDB local pour pouvoir inspecter et comparer les modèles côte à côte. S\'ouvre dans un onglet Traces distinct. Désactivé par défaut car cela ajoute des écritures disque à chaque étape. Ouvrir la page Traces →', + 'st.display.lossless_tracing.label': 'Enregistrer les détails complets des requêtes (niveau débogage)', + 'st.display.lossless_tracing.desc_html': 'Conserve aussi les prompts, messages et schémas d\'outils complets de chaque requête LLM au lieu de résumés sans contenu, et garde jusqu\'à 200 Ko de chaque résultat d\'outil. Permet un débogage approfondi et la reconstruction des requêtes. Désactivé par défaut car cela écrit localement le contenu de votre conversation et utilise beaucoup plus d\'espace disque. Nécessite que l\'enregistrement des traces soit activé.', 'st.display.allow_local_network.label': 'Autoriser l\'agent à accéder au réseau local', 'st.display.allow_local_network.desc': 'Permet à l\'outil fetch_url de l\'agent d\'atteindre les adresses loopback (127.0.0.1, localhost) et privées/RFC1918 (10.x, 192.168.x, 172.16–31.x). Utile pour les serveurs de dev locaux, outils intranet ou API auto-hébergées. Désactivé par défaut — quand désactivé, des pages avec injection de prompt ne peuvent pas pousser l\'agent à sonder votre réseau local. Les adresses de méta-données cloud (169.254.169.254) et les noms d\'hôte *.internal/*.local restent bloqués même quand activé.', 'st.display.notify_sound.label': 'Son de fin', @@ -331,6 +333,8 @@ export default { 'st.profile.security_html': 'Sécurité — à lire :
  • Le texte que vous saisissez ici est stocké en clair dans le stockage local de votre navigateur. Il n\'est pas transmis au projet WebBrain — mais il est envoyé au fournisseur de LLM que vous avez configuré à chaque tour, dans le cadre du prompt système.
  • Ne mettez pas ici les mots de passe de comptes importants (Google, Apple, iCloud, banque, SSO professionnel, e-mail principal). Ces comptes devraient utiliser la 2FA et vous ne devriez pas avoir à les confier à un agent.
  • Un mot de passe jetable que vous réutilisez pour des inscriptions peu sensibles (newsletters, essais gratuits, comptes de forum) est l\'usage prévu.
  • Si ce profil de navigateur est un jour compromis, un attaquant ayant accès au disque pourra lire ce texte. Gardez-le au minimum.
', 'tr.title': 'Traces WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} exécutions', 'tr.run': '{n} exécution', 'tr.btn.refresh': '↻ Actualiser', diff --git a/src/firefox/src/ui/locales/he.js b/src/firefox/src/ui/locales/he.js index 9fe0a30d9..288ae62d4 100644 --- a/src/firefox/src/ui/locales/he.js +++ b/src/firefox/src/ui/locales/he.js @@ -552,6 +552,8 @@ export default { "st.display.auto_screenshot.every_step": "כל צעד", "st.display.tracing.label": "הקלטת עקבות (להשוואת מודלים)", "st.display.tracing.desc_html": "תתמיד בכל ריצה (LLM בקשות, תגובות, קריאות לכלים, צילומי מסך) לתוך IndexedDB המקומי כדי שתוכל לבדוק ולהשוות מודלים זה לצד זה. נפתח בלשונית Traces נפרדת. כבוי כברירת מחדל מכיוון שהוא מוסיף כתיבה בדיסק בכל שלב. פתח את דף העקבות →", + "st.display.lossless_tracing.label": "רישום פרטי בקשה מלאים (שכבת דיבאג)", + "st.display.lossless_tracing.desc_html": "שומר גם את ההנחיות, ההודעות והמבנים המלאים של כל בקשת LLM במקום סיכומים חסרי תוכן, ומחזיק עד 200KB מכל תוצאת כלי. מאפשר ניפוי שגיאות מעמיק ושחזור בקשות. כבוי כברירת מחדל משום שהוא כותב את תוכן השיחה שלך מקומית ומשתמש בהרבה יותר מקום. דורש שהקלטת מעקב תהיה פעילה.", "st.display.cost_session_limit.label": "קצבת הפגישה בעלות ענן", "st.display.cost_session_limit.desc": "מפסיק ענן ו OpenRouter פועל לפני שיחת מודל בתשלום נוספת ברגע שההפעלה הזו מגיעה לקצבת העלות המדווחת או המשוערת. ברירת מחדל 10 $.", "st.display.cost_total_limit.label": "הקצבה כוללת של עלות ענן", @@ -789,6 +791,8 @@ export default { "hist.confirm_delete": "למחוק את השיחה השמורה הזו?", "hist.confirm_clear_all": "למחוק את כל השיחות השמורות? לא ניתן לבטל פעולה זו.", "tr.title": "WebBrain עקבות", + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', "tr.runs": "{n} פועל", "tr.run": "{n} לָרוּץ", "tr.btn.refresh": "↻ רענן", diff --git a/src/firefox/src/ui/locales/hi.js b/src/firefox/src/ui/locales/hi.js index 41393336d..96074bdbc 100644 --- a/src/firefox/src/ui/locales/hi.js +++ b/src/firefox/src/ui/locales/hi.js @@ -589,6 +589,8 @@ export default { 'st.display.auto_screenshot.every_step': "हर कदम", 'st.display.tracing.label': "रिकॉर्ड निशान (मॉडल तुलना के लिए)", 'st.display.tracing.desc_html': "प्रत्येक रन (एलएलएम अनुरोध, प्रतिक्रिया, टूल कॉल, स्क्रीनशॉट) को स्थानीय IndexedDB में जारी रखें ताकि आप साथ-साथ मॉडलों का निरीक्षण और तुलना कर सकें। एक अलग ट्रेसेस टैब में खुलता है। डिफ़ॉल्ट रूप से बंद है क्योंकि यह प्रति चरण डिस्क राइट जोड़ता है। ट्रेस पेज खोलें →", + 'st.display.lossless_tracing.label': 'पूर्ण अनुरोध विवरण रिकॉर्ड करें (डीबग स्तर)', + 'st.display.lossless_tracing.desc_html': 'प्रत्येक LLM अनुरोध के पूर्ण प्रॉम्प्ट, संदेश और टूल स्कीमा को बिना सामग्री वाले सारांशों के बजाय सहेजता है, और प्रत्येक टूल परिणाम का 200 KB तक रखता है। गहन डीबगिंग और अनुरोध पुनर्निर्माण सक्षम करता है। डिफ़ॉल्ट रूप से बंद क्योंकि यह आपकी बातचीत की सामग्री को स्थानीय रूप से लिखता है और बहुत अधिक डिस्क उपयोग करता है। ट्रेस रिकॉर्डिंग चालू होना आवश्यक है।', 'st.display.cost_session_limit.label': "क्लाउड लागत सत्र भत्ता", 'st.display.cost_session_limit.desc': "एक बार जब यह सत्र रिपोर्ट की गई या अनुमानित लागत भत्ता तक पहुंच जाता है, तो क्लाउड रुक जाता है और OpenRouter किसी अन्य भुगतान मॉडल कॉल से पहले चलता है। डिफ़ॉल्ट $10.", 'st.display.cost_total_limit.label': "क्लाउड लागत कुल भत्ता", @@ -914,6 +916,8 @@ export default { // --- Traces page ------------------------------------------------------- 'tr.title': "WebBrain निशान", + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': "{n} चलता है", 'tr.run': "{n} चलाएँ", 'tr.btn.refresh': "↻ ताज़ा करें", diff --git a/src/firefox/src/ui/locales/id.js b/src/firefox/src/ui/locales/id.js index b3964a5e1..f386c8e17 100644 --- a/src/firefox/src/ui/locales/id.js +++ b/src/firefox/src/ui/locales/id.js @@ -259,6 +259,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Setiap langkah', 'st.display.tracing.label': 'Rekam jejak (untuk perbandingan model)', 'st.display.tracing.desc_html': 'Simpan setiap proses (permintaan LLM, respons, pemanggilan alat, tangkapan layar) ke IndexedDB lokal sehingga Anda dapat memeriksa dan membandingkan model berdampingan. Membuka tab Jejak terpisah. Nonaktif secara bawaan karena menambah penulisan disk per langkah. Buka halaman Jejak →', + 'st.display.lossless_tracing.label': 'Rekam detail lengkap permintaan (tingkat debug)', + 'st.display.lossless_tracing.desc_html': 'Juga menyimpan prompt, pesan, dan skema alat lengkap dari setiap permintaan LLM alih-alih ringkasan tanpa konten, dan menyimpan hingga 200 KB setiap hasil alat. Memungkinkan debug mendalam dan rekonstruksi permintaan. Nonaktif secara default karena menulis konten percakapan Anda secara lokal dan memakai disk jauh lebih banyak. Memerlukan perekaman trace aktif.', 'st.display.allow_local_network.label': 'Izinkan agen mengakses jaringan lokal', 'st.display.allow_local_network.desc': 'Memungkinkan alat fetch_url agen menjangkau loopback (127.0.0.1, localhost) dan alamat privat/RFC1918 (10.x, 192.168.x, 172.16–31.x). Berguna untuk server pengembangan lokal, alat intranet, atau API yang di-host sendiri. Nonaktif secara bawaan — saat nonaktif, halaman yang disisipi prompt-injection tidak dapat menyuruh agen memindai jaringan lokal Anda. Alamat metadata cloud (169.254.169.254) dan nama host *.internal/*.local tetap diblokir bahkan saat diaktifkan.', 'st.display.notify_sound.label': 'Suara penyelesaian', @@ -331,6 +333,8 @@ export default { 'st.profile.security_html': 'Keamanan — baca ini:
  • Teks yang Anda masukkan di sini disimpan dalam bentuk teks biasa di penyimpanan lokal peramban Anda. Teks ini tidak dikirim ke proyek WebBrain — tetapi dikirim ke penyedia LLM yang Anda konfigurasikan pada setiap giliran, sebagai bagian dari prompt sistem.
  • Jangan masukkan kata sandi untuk akun penting di sini (Google, Apple, iCloud, perbankan, SSO kantor, email utama). Akun-akun itu seharusnya memakai 2FA dan tidak perlu Anda serahkan ke agen.
  • Kasus penggunaan yang dimaksud adalah kata sandi sekali pakai yang Anda gunakan kembali untuk pendaftaran situs berisiko rendah (newsletter, uji coba gratis, akun forum).
  • Jika profil peramban ini suatu saat disusupi, penyerang yang memiliki akses disk dapat membaca teks ini. Buat seminimal mungkin.
', 'tr.title': 'Jejak WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} proses', 'tr.run': '{n} proses', 'tr.btn.refresh': '↻ Segarkan', diff --git a/src/firefox/src/ui/locales/ja.js b/src/firefox/src/ui/locales/ja.js index 9874799b1..79e963dbf 100644 --- a/src/firefox/src/ui/locales/ja.js +++ b/src/firefox/src/ui/locales/ja.js @@ -259,6 +259,8 @@ export default { 'st.display.auto_screenshot.every_step': '毎ステップ', 'st.display.tracing.label': 'トレースを記録(モデル比較用)', 'st.display.tracing.desc_html': 'すべての実行(LLM リクエスト、レスポンス、ツール呼び出し、スクリーンショット)をローカルの IndexedDB に保存し、モデルを並べて検査・比較できるようにします。別の「トレース」タブで開きます。毎ステップで書き込みが増えるためデフォルトはオフです。トレースページを開く →', + 'st.display.lossless_tracing.label': 'リクエストの完全な詳細を記録(デバッグ層)', + 'st.display.lossless_tracing.desc_html': '各LLMリクエストの完全なプロンプト・メッセージ・ツールスキーマを、内容を含まない要約の代わりに保存し、各ツール結果を最大200KB保持します。深いデバッグとリクエスト再構築が可能になります。会話内容をローカルに書き込み、ディスク消費が大幅に増えるため、既定ではオフです。トレース記録の有効化が必要です。', 'st.display.allow_local_network.label': 'エージェントにローカルネットワークへのアクセスを許可', 'st.display.allow_local_network.desc': 'エージェントの fetch_url ツールがループバック(127.0.0.1、localhost)やプライベート/RFC1918 アドレス(10.x、192.168.x、172.16–31.x)に届くようになります。ローカル開発サーバー、社内ツール、セルフホスト API に便利です。デフォルトはオフ — オフのときは prompt 注入されたページがエージェントにローカルネットワークの探索をさせることはできません。クラウドメタデータアドレス(169.254.169.254)と *.internal/*.local ホスト名はオンでもブロックされたままです。', 'st.display.notify_sound.label': '完了サウンド', @@ -331,6 +333,8 @@ export default { 'st.profile.security_html': 'セキュリティ — 必ずお読みください:
  • ここに入力したテキストはブラウザのローカルストレージに平文で保存されます。WebBrain プロジェクトへは送信されませんが、設定済みの LLM プロバイダーへはシステム prompt の一部として毎ターン送信されます
  • 重要アカウント(Google、Apple、iCloud、銀行、社内 SSO、メインのメール)のパスワードは絶対にここに入れないでください。それらは 2FA を有効にすべきで、そもそもエージェントに渡すべきではありません。
  • 意図された使い方は、低リスクの登録(メルマガ、無料体験、フォーラム)で使い回す使い捨てパスワードです。
  • このブラウザプロファイルが侵害された場合、ディスクにアクセスできる攻撃者はこのテキストを読み取れます。最小限にとどめてください。
', 'tr.title': 'WebBrain トレース', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} 件の実行', 'tr.run': '{n} 件の実行', 'tr.btn.refresh': '↻ 更新', diff --git a/src/firefox/src/ui/locales/ko.js b/src/firefox/src/ui/locales/ko.js index 649fb200c..9e1c109a7 100644 --- a/src/firefox/src/ui/locales/ko.js +++ b/src/firefox/src/ui/locales/ko.js @@ -259,6 +259,8 @@ export default { 'st.display.auto_screenshot.every_step': '매 단계', 'st.display.tracing.label': '트레이스 기록 (모델 비교용)', 'st.display.tracing.desc_html': '모든 실행(LLM 요청, 응답, 도구 호출, 스크린샷)을 로컬 IndexedDB에 보관하여 모델을 나란히 검사하고 비교할 수 있게 합니다. 별도의 트레이스 탭에서 열립니다. 매 단계마다 디스크 쓰기가 늘어나므로 기본값은 꺼짐입니다. 트레이스 페이지 열기 →', + 'st.display.lossless_tracing.label': '요청 전체 세부정보 기록(디버그 계층)', + 'st.display.lossless_tracing.desc_html': '각 LLM 요청의 전체 프롬프트·메시지·도구 스키마를 내용 없는 요약 대신 저장하고, 각 도구 결과를 최대 200KB까지 보관합니다. 심층 디버깅과 요청 재구성이 가능합니다. 대화 내용을 로컬에 기록하고 디스크를 훨씬 더 사용하므로 기본적으로 꺼져 있습니다. 트레이스 기록이 켜져 있어야 합니다.', 'st.display.allow_local_network.label': '에이전트의 로컬 네트워크 접근 허용', 'st.display.allow_local_network.desc': '에이전트의 fetch_url 도구가 루프백(127.0.0.1, localhost)과 사설/RFC1918 주소(10.x, 192.168.x, 172.16–31.x)에 접근할 수 있게 합니다. 로컬 개발 서버, 인트라넷 도구, 자체 호스팅 API에 유용합니다. 기본값은 끔 — 꺼져 있으면 prompt 주입된 페이지가 에이전트로 하여금 로컬 네트워크를 탐색하게 만들 수 없습니다. 클라우드 메타데이터 주소(169.254.169.254)와 *.internal/*.local 호스트명은 켜진 상태에서도 차단됩니다.', 'st.display.notify_sound.label': '완료 알림음', @@ -331,6 +333,8 @@ export default { 'st.profile.security_html': '보안 — 반드시 읽어주세요:
  • 여기에 입력한 텍스트는 브라우저 로컬 저장소에 평문으로 저장됩니다. WebBrain 프로젝트로는 전송되지 않지만, 설정한 LLM 제공자에게는 시스템 prompt의 일부로 매 턴 전송됩니다.
  • 중요한 계정(Google, Apple, iCloud, 은행, 회사 SSO, 주 이메일)의 비밀번호는 여기에 넣지 마세요. 이런 계정은 2FA를 사용해야 하며 애초에 에이전트에게 넘길 일이 없어야 합니다.
  • 의도된 사용 사례는 저위험 사이트 가입(뉴스레터, 무료 체험, 포럼 계정)에 재사용하는 일회용 비밀번호입니다.
  • 이 브라우저 프로필이 침해되면 디스크에 접근할 수 있는 공격자가 이 텍스트를 읽을 수 있습니다. 최소한으로 유지하세요.
', 'tr.title': 'WebBrain 트레이스', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '실행 {n}건', 'tr.run': '실행 {n}건', 'tr.btn.refresh': '↻ 새로고침', diff --git a/src/firefox/src/ui/locales/ms.js b/src/firefox/src/ui/locales/ms.js index a6fe1d01c..b9f8495c5 100644 --- a/src/firefox/src/ui/locales/ms.js +++ b/src/firefox/src/ui/locales/ms.js @@ -259,6 +259,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Setiap langkah', 'st.display.tracing.label': 'Rakam jejak (untuk perbandingan model)', 'st.display.tracing.desc_html': 'Simpan setiap larian (permintaan LLM, respons, panggilan alat, tangkapan skrin) ke dalam IndexedDB tempatan supaya anda boleh memeriksa dan membandingkan model secara bersebelahan. Dibuka dalam tab Jejak yang berasingan. Dimatikan secara lalai kerana ia menambah penulisan cakera pada setiap langkah. Buka halaman Jejak →', + 'st.display.lossless_tracing.label': 'Rekod butiran penuh permintaan (peringkat debug)', + 'st.display.lossless_tracing.desc_html': 'Juga menyimpan prompt, mesej, dan skema alatan penuh bagi setiap permintaan LLM dan bukannya ringkasan tanpa kandungan, serta mengekalkan sehingga 200 KB bagi setiap hasil alatan. Membolehkan penyahpepijatan mendalam dan pembinaan semula permintaan. Dimatikan secara lalai kerana menulis kandungan perbualan anda secara setempat dan menggunakan lebih banyak cakera. Memerlukan rakaman jejak dihidupkan.', 'st.display.allow_local_network.label': 'Benarkan ejen mengakses rangkaian tempatan', 'st.display.allow_local_network.desc': 'Membenarkan alat fetch_url ejen mencapai loopback (127.0.0.1, localhost) dan alamat peribadi/RFC1918 (10.x, 192.168.x, 172.16–31.x). Berguna untuk pelayan pembangunan tempatan, alat intranet, atau API yang dihos sendiri. Dimatikan secara lalai — apabila dimatikan, halaman yang disuntik prompt-injection tidak boleh mendorong ejen untuk meneliti rangkaian tempatan anda. Alamat metadata awan (169.254.169.254) dan nama hos *.internal/*.local kekal disekat walaupun dihidupkan.', 'st.display.notify_sound.label': 'Bunyi selesai', @@ -331,6 +333,8 @@ export default { 'st.profile.security_html': 'Keselamatan — sila baca:
  • Teks yang anda masukkan di sini disimpan sebagai teks biasa dalam storan tempatan pelayar anda. Ia tidak dihantar kepada projek WebBrain — tetapi dihantar kepada pembekal LLM yang anda konfigurasikan pada setiap giliran, sebagai sebahagian daripada system prompt.
  • Jangan letakkan kata laluan untuk akaun penting di sini (Google, Apple, iCloud, perbankan, SSO kerja, e-mel utama). Akaun-akaun itu sepatutnya menggunakan 2FA dan anda sememangnya tidak perlu menyerahkannya kepada ejen.
  • Kes penggunaan yang dimaksudkan ialah kata laluan pakai buang yang anda guna semula untuk pendaftaran laman berisiko rendah (surat berita, percubaan percuma, akaun forum).
  • Jika profil pelayar ini diceroboh, penyerang yang mempunyai akses cakera boleh membaca teks ini. Kekalkan ia seminimum mungkin.
', 'tr.title': 'Jejak WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} larian', 'tr.run': '{n} larian', 'tr.btn.refresh': '↻ Muat semula', diff --git a/src/firefox/src/ui/locales/nl.js b/src/firefox/src/ui/locales/nl.js index bb625241f..9aabf530b 100644 --- a/src/firefox/src/ui/locales/nl.js +++ b/src/firefox/src/ui/locales/nl.js @@ -566,6 +566,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Elke stap', 'st.display.tracing.label': 'Traces opslaan (voor modelvergelijking)', 'st.display.tracing.desc_html': 'Sla elke uitvoering op in lokale IndexedDB... Traces openen →', + 'st.display.lossless_tracing.label': 'Volledige verzoekdetails opnemen (debuglaag)', + 'st.display.lossless_tracing.desc_html': 'Bewaart ook de volledige prompts, berichten en tool-schema\'s van elk LLM-verzoek in plaats van inhoudsloze samenvattingen, en houdt tot 200 KB van elk toolresultaat. Maakt diepgaand debuggen en reconstructie van verzoeken mogelijk. Standaard uit omdat het de inhoud van uw gesprek lokaal opslaat en veel meer schijfruimte gebruikt. Vereist dat tracerecordering aan staat.', 'st.display.cost_session_limit.label': 'Cloudkostensessietegoed', 'st.display.cost_session_limit.desc': 'Stopt cloud- en OpenRouter-uitvoeringen voordat nog een betaald modelaanroep plaatsvindt...', 'st.display.cost_total_limit.label': 'Cloudkostentotaaltegoed', @@ -814,6 +816,8 @@ export default { 'hist.confirm_delete': 'Dit opgeslagen gesprek verwijderen?', 'hist.confirm_clear_all': 'Alle opgeslagen gesprekken verwijderen? Dit kan niet ongedaan worden gemaakt.', 'tr.title': 'WebBrain Traces', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} uitvoeringen', 'tr.run': '{n} uitvoering', 'tr.btn.refresh': '↻ Vernieuwen', diff --git a/src/firefox/src/ui/locales/pl.js b/src/firefox/src/ui/locales/pl.js index b95c3cfd7..4978e4258 100644 --- a/src/firefox/src/ui/locales/pl.js +++ b/src/firefox/src/ui/locales/pl.js @@ -410,6 +410,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Przy każdym kroku', 'st.display.tracing.label': 'Rejestruj ślady (do porównywania modeli)', 'st.display.tracing.desc_html': 'Zapisuj każde wykonanie (żądania LLM, odpowiedzi, wywołania narzędzi, zrzuty ekranu) w lokalnej bazie IndexedDB, aby móc je analizować i porównywać modele obok siebie. Otwiera się w osobnej karcie Ślady. Domyślnie wyłączone, ponieważ dodaje zapisy na dysk przy każdym kroku. Otwórz stronę Ślady →', + 'st.display.lossless_tracing.label': 'Zapisuj pełne szczegóły żądań (poziom debugowania)', + 'st.display.lossless_tracing.desc_html': 'Zapisuje również pełne prompty, wiadomości i schematy narzędzi każdego żądania LLM zamiast podsumowań bez treści oraz przechowuje do 200 KB każdego wyniku narzędzia. Umożliwia głębokie debugowanie i rekonstrukcję żądań. Wyłączone domyślnie, ponieważ zapisuje treść rozmowy lokalnie i zużywa znacznie więcej miejsca. Wymaga włączonego zapisu śladów.', 'st.display.cost_session_limit.label': 'Limit kosztów chmury na sesję', 'st.display.cost_session_limit.desc': 'Zatrzymuje wykonania w chmurze i OpenRouter przed kolejnym płatnym wywołaniem modelu, gdy ta sesja osiągnie zgłoszony lub szacowany limit kosztów. Domyślnie 10 USD.', 'st.display.cost_total_limit.label': 'Całkowity limit kosztów chmury', @@ -570,6 +572,8 @@ export default { 'st.captcha.balance_fail': 'Niepowodzenie: {error}', 'st.captcha.security_html': 'Uwaga: klucz API jest przechowywany w postaci jawnej w lokalnej pamięci przeglądarki. CapSolver obciąża Twoje konto za każde rozwiązanie; agent wywoła go tylko, gdy CAPTCHA faktycznie zablokuje krok (maksymalnie raz na napotkanie — nie ponawia po niepowodzeniu). Regulaminy niektórych witryn zabraniają automatycznego rozwiązywania CAPTCHA; kieruj się własnym osądem.', 'tr.title': 'Ślady WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} wykonań', 'tr.run': '{n} wykonanie', 'tr.btn.refresh': '↻ Odśwież', diff --git a/src/firefox/src/ui/locales/pt.js b/src/firefox/src/ui/locales/pt.js index c509832a8..88f97aff7 100644 --- a/src/firefox/src/ui/locales/pt.js +++ b/src/firefox/src/ui/locales/pt.js @@ -589,6 +589,8 @@ export default { 'st.display.auto_screenshot.every_step': "Cada passo", 'st.display.tracing.label': "Registrar rastreamentos (para comparação de modelos)", 'st.display.tracing.desc_html': "Persista cada execução (solicitações LLM, respostas, chamadas de ferramentas, capturas de tela) no IndexedDB local para que você possa inspecionar e comparar modelos lado a lado. Abre em uma guia Traces separada. Desativado por padrão porque adiciona gravações de disco por etapa. Abra a página Traces →", + 'st.display.lossless_tracing.label': 'Registrar detalhes completos da solicitação (nível de depuração)', + 'st.display.lossless_tracing.desc_html': 'Também persiste os prompts, mensagens e esquemas de ferramentas completos de cada solicitação LLM em vez de resumos sem conteúdo, e mantém até 200 KB de cada resultado de ferramenta. Permite depuração profunda e reconstrução de solicitações. Desativado por padrão porque grava o conteúdo da sua conversa localmente e usa muito mais disco. Exige que o registro de rastros esteja ativado.', 'st.display.cost_session_limit.label': "Subsídio de sessão de custo na nuvem", 'st.display.cost_session_limit.desc': "Interrompe a nuvem e OpenRouter é executado antes de outra chamada de modelo pago quando esta sessão atinge o limite de custo relatado ou estimado. Padrão $ 10.", 'st.display.cost_total_limit.label': "Permissão total de custos de nuvem", @@ -914,6 +916,8 @@ export default { // --- Traces page ------------------------------------------------------- 'tr.title': "WebBrain Rastreamentos", + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': "{n} é executado", 'tr.run': "{n} executado", 'tr.btn.refresh': "↻ Atualizar", diff --git a/src/firefox/src/ui/locales/ru.js b/src/firefox/src/ui/locales/ru.js index d03080b06..a2c558263 100644 --- a/src/firefox/src/ui/locales/ru.js +++ b/src/firefox/src/ui/locales/ru.js @@ -259,6 +259,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Каждый шаг', 'st.display.tracing.label': 'Записывать трассы (для сравнения моделей)', 'st.display.tracing.desc_html': 'Сохранять каждый запуск (LLM-запросы, ответы, вызовы инструментов, скриншоты) в локальную IndexedDB, чтобы можно было разбирать и сравнивать модели бок о бок. Открывается в отдельной вкладке «Трассы». По умолчанию выключено, потому что добавляет запись на диск на каждом шаге. Открыть страницу трасс →', + 'st.display.lossless_tracing.label': 'Записывать полные детали запросов (отладочный уровень)', + 'st.display.lossless_tracing.desc_html': 'Также сохраняет полные промпты, сообщения и схемы инструментов каждого LLM-запроса вместо кратких сводок без содержимого, и хранит до 200 КБ каждого результата инструмента. Позволяет глубокую отладку и восстановление запросов. Выключено по умолчанию, так как записывает содержимое вашего диалога локально и занимает заметно больше места. Требует включённой записи трасс.', 'st.display.allow_local_network.label': 'Разрешить агенту доступ к локальной сети', 'st.display.allow_local_network.desc': 'Разрешает инструменту fetch_url агента обращаться к loopback (127.0.0.1, localhost) и частным адресам RFC1918 (10.x, 192.168.x, 172.16–31.x). Полезно для локальных серверов разработки, интранет-инструментов и self-hosted API. По умолчанию выключено — когда выключено, страницы с prompt-инъекцией не смогут заставить агента сканировать вашу локальную сеть. Адреса метаданных облака (169.254.169.254) и имена *.internal/*.local остаются заблокированы даже когда включено.', 'st.display.notify_sound.label': 'Звук завершения', @@ -331,6 +333,8 @@ export default { 'st.profile.security_html': 'Безопасность — прочтите:
  • Введённый здесь текст хранится в открытом виде в локальном хранилище браузера. Он не отправляется в проект WebBrain — но отправляется на каждом ходу как часть системного prompt тому LLM-провайдеру, которого вы настроили.
  • Не размещайте здесь пароли от важных аккаунтов (Google, Apple, iCloud, банк, рабочий SSO, основная почта). Эти аккаунты должны быть с 2FA, и их в любом случае не стоит передавать агенту.
  • Целевой сценарий — одноразовый пароль, который вы повторно используете для регистраций с низкими ставками (рассылки, бесплатные пробные периоды, аккаунты на форумах).
  • Если этот профиль браузера будет скомпрометирован, атакующий с доступом к диску сможет прочитать этот текст. Держите его минимальным.
', 'tr.title': 'Трассы WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': 'запусков: {n}', 'tr.run': 'запуск: {n}', 'tr.btn.refresh': '↻ Обновить', diff --git a/src/firefox/src/ui/locales/th.js b/src/firefox/src/ui/locales/th.js index 78cc80afe..f8e11b0de 100644 --- a/src/firefox/src/ui/locales/th.js +++ b/src/firefox/src/ui/locales/th.js @@ -259,6 +259,8 @@ export default { 'st.display.auto_screenshot.every_step': 'ทุกขั้น', 'st.display.tracing.label': 'บันทึกการติดตาม (เพื่อเปรียบเทียบโมเดล)', 'st.display.tracing.desc_html': 'จัดเก็บทุกการทำงาน (คำขอ LLM, การตอบกลับ, การเรียกใช้เครื่องมือ, ภาพหน้าจอ) ลงใน IndexedDB ในเครื่อง เพื่อให้คุณตรวจสอบและเปรียบเทียบโมเดลแบบเคียงข้างกันได้ จะเปิดในแท็บ Traces แยกต่างหาก ปิดเป็นค่าเริ่มต้นเพราะเพิ่มการเขียนดิสก์ในทุกขั้น เปิดหน้า Traces →', + 'st.display.lossless_tracing.label': 'บันทึกรายละเอียดคำขอทั้งหมด (ระดับดีบัก)', + 'st.display.lossless_tracing.desc_html': 'จัดเก็บพรอมต์ ข้อความ และโครงสร้างเครื่องมือแบบเต็มของแต่ละคำขอ LLM แทนสรุปที่ไม่มีเนื้อหา และเก็บผลลัพธ์เครื่องมือแต่ละรายการสูงสุด 200 KB เปิดใช้งานการดีบักเชิงลึกและการสร้างคำขอใหม่ ปิดโดยค่าเริ่มต้นเพราะเขียนเนื้อหาสนทนาของคุณในเครื่องและใช้พื้นที่ดิสก์มากกว่ามาก ต้องเปิดการบันทึกเทรซ', 'st.display.allow_local_network.label': 'อนุญาตให้เอเจนต์เข้าถึงเครือข่ายภายใน', 'st.display.allow_local_network.desc': 'ให้เครื่องมือ fetch_url ของเอเจนต์เข้าถึง loopback (127.0.0.1, localhost) และที่อยู่ส่วนตัว/RFC1918 (10.x, 192.168.x, 172.16–31.x) มีประโยชน์สำหรับเซิร์ฟเวอร์พัฒนาภายใน เครื่องมืออินทราเน็ต หรือ API ที่โฮสต์เอง ปิดเป็นค่าเริ่มต้น — เมื่อปิด หน้าเว็บที่ถูกแทรก prompt-injection ไม่สามารถสั่งเอเจนต์ให้สำรวจเครือข่ายภายในของคุณได้ ส่วนที่อยู่เมตาดาตาคลาวด์ (169.254.169.254) และโฮสต์ *.internal/*.local จะยังถูกบล็อกแม้จะเปิดไว้', 'st.display.notify_sound.label': 'เสียงเมื่อทำเสร็จ', @@ -331,6 +333,8 @@ export default { 'st.profile.security_html': 'ความปลอดภัย — โปรดอ่าน:
  • ข้อความที่คุณกรอกที่นี่ถูกเก็บ เป็นข้อความธรรมดา ใน local storage ของเบราว์เซอร์ มัน จะไม่ ถูกส่งไปยังโปรเจกต์ WebBrain — แต่ จะถูกส่ง ไปยังผู้ให้บริการ LLM ที่คุณตั้งค่าไว้ในทุกเทิร์น ในฐานะส่วนหนึ่งของ system prompt
  • อย่าใส่รหัสผ่านของบัญชีสำคัญ ที่นี่ (Google, Apple, iCloud, ธนาคาร, SSO ที่ทำงาน, อีเมลหลัก) บัญชีเหล่านั้นควรใช้ 2FA และไม่ควรมอบให้เอเจนต์อยู่แล้ว
  • กรณีใช้งานที่ตั้งใจคือ รหัสผ่านใช้แล้วทิ้ง ที่คุณใช้ซ้ำสำหรับการสมัครเว็บไซต์เสี่ยงต่ำ (จดหมายข่าว ทดลองใช้ฟรี บัญชีฟอรัม)
  • หากโปรไฟล์เบราว์เซอร์นี้ถูกบุกรุก ผู้โจมตีที่เข้าถึงดิสก์ได้จะอ่านข้อความนี้ได้ ควรเก็บให้น้อยที่สุด
', 'tr.title': 'Traces ของ WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} ครั้ง', 'tr.run': '{n} ครั้ง', 'tr.btn.refresh': '↻ รีเฟรช', diff --git a/src/firefox/src/ui/locales/tl.js b/src/firefox/src/ui/locales/tl.js index bf21dd525..1af16e68e 100644 --- a/src/firefox/src/ui/locales/tl.js +++ b/src/firefox/src/ui/locales/tl.js @@ -259,6 +259,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Bawat hakbang', 'st.display.tracing.label': 'I-record ang mga trace (para sa paghahambing ng modelo)', 'st.display.tracing.desc_html': 'I-save ang bawat run (mga LLM request, response, tool call, screenshot) sa lokal na IndexedDB para maaari mong suriin at ihambing ang mga modelo nang magkatabi. Bumubukas sa hiwalay na Traces tab. Naka-off bilang default dahil nagdadagdag ito ng disk writes kada hakbang. Buksan ang Traces page →', + 'st.display.lossless_tracing.label': 'Itala ang buong detalye ng kahilingan (antas ng debug)', + 'st.display.lossless_tracing.desc_html': 'Iniimbak din ang buong prompt, mensahe, at schema ng tool ng bawat LLM request sa halip na buod na walang nilalaman, at nagpapanatili ng hanggang 200 KB ng bawat resulta ng tool. Nagbibigay-daan sa malalim na pag-debug at muling pagbuo ng request. Naka-off bilang default dahil isinusulat nito ang nilalaman ng iyong usapan nang lokal at gumagamit ng mas malaking disk. Nangangailangan ng naka-on na pag-record ng trace.', 'st.display.allow_local_network.label': 'Pahintulutan ang ahente na ma-access ang lokal na network', 'st.display.allow_local_network.desc': 'Hinahayaan ang fetch_url tool ng ahente na maabot ang loopback (127.0.0.1, localhost) at mga private/RFC1918 address (10.x, 192.168.x, 172.16–31.x). Mainam para sa mga lokal na dev server, intranet tool, o self-hosted API. Naka-off bilang default — kapag naka-off, hindi mapipilit ng mga pahina na may prompt-injection ang ahente na sumiyasat sa iyong lokal na network. Mananatiling naka-block ang mga cloud-metadata address (169.254.169.254) at mga *.internal/*.local hostname kahit naka-on ito.', 'st.display.notify_sound.label': 'Tunog kapag tapos na', @@ -331,6 +333,8 @@ export default { 'st.profile.security_html': 'Seguridad — basahin ito:
  • Ang teksto na inilagay mo dito ay iniimbak bilang plain text sa local storage ng iyong browser. Hindi ito ipinapadala sa proyektong WebBrain — ngunit ipinapadala ito sa kung anong LLM provider ang iyong na-configure sa bawat turn, bilang bahagi ng system prompt.
  • Huwag maglagay ng mga password para sa mahahalagang account dito (Google, Apple, iCloud, banking, work SSO, pangunahing email). Dapat gumamit ng 2FA ang mga account na iyon at hindi mo naman dapat ipinapasa sa isang ahente.
  • Ang inilaang gamit ay isahang-gamit na password na ginagamit mo sa mga low-stakes na site signup (newsletter, libreng trial, forum account).
  • Kung sakaling ma-compromise ang browser profile na ito, ang isang attacker na may disk access ay makakabasa ng tekstong ito. Panatilihin itong minimal.
', 'tr.title': 'Mga Trace ng WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} run', 'tr.run': '{n} run', 'tr.btn.refresh': '↻ I-refresh', diff --git a/src/firefox/src/ui/locales/tr.js b/src/firefox/src/ui/locales/tr.js index 147eb2a25..40f24934e 100644 --- a/src/firefox/src/ui/locales/tr.js +++ b/src/firefox/src/ui/locales/tr.js @@ -295,6 +295,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Her adımda', 'st.display.tracing.label': 'İzleri kaydet (model karşılaştırması için)', 'st.display.tracing.desc_html': 'Her çalışmayı (LLM istekleri, yanıtlar, araç çağrıları, ekran görüntüleri) yerel IndexedDB\'de saklar, böylece modelleri yan yana inceleyip karşılaştırabilirsin. Ayrı bir İzler sekmesinde açılır. Her adımda diske yazdığı için varsayılan olarak kapalıdır. İzler sayfasını aç →', + 'st.display.lossless_tracing.label': 'Tüm istek ayrıntılarını kaydet (hata ayıklama katmanı)', + 'st.display.lossless_tracing.desc_html': 'Her LLM isteğinin tam prompt, mesaj ve araç şemalarını içeriksiz özetler yerine de kalıcı olarak kaydeder ve her araç sonucundan en fazla 200 KB tutar. Derin hata ayıklama ve istek yeniden yapılandırmasını sağlar. Varsayılan olarak kapalıdır çünkü konuşma içeriğinizi yerel olarak yazar ve çok daha fazla disk kullanır. İz kaydının açık olmasını gerektirir.', 'st.display.allow_local_network.label': 'Aracının yerel ağa erişmesine izin ver', 'st.display.allow_local_network.desc': 'Aracının fetch_url aracının loopback (127.0.0.1, localhost) ve özel/RFC1918 adreslerine (10.x, 192.168.x, 172.16–31.x) ulaşmasına izin verir. Yerel geliştirme sunucuları, intranet araçları veya kendi sunucundaki API\'lar için kullanışlıdır. Varsayılan olarak kapalıdır — kapalıyken, prompt-injection içeren sayfalar aracıyı yerel ağını taramaya yönlendiremez. Bulut meta-veri adresleri (169.254.169.254) ve *.internal/*.local ana makine adları açıkken bile engellenir.', 'st.display.notify_sound.label': 'Tamamlama sesi', @@ -370,6 +372,8 @@ export default { 'st.profile.security_html': 'Güvenlik — lütfen oku:
  • Buraya yazdığın metin tarayıcının yerel depolamasında düz metin olarak saklanır. WebBrain projesine iletilmez — fakat yapılandırdığın LLM sağlayıcısına her turda sistem isteminin bir parçası olarak gönderilir.
  • Önemli hesapların parolalarını buraya koyma (Google, Apple, iCloud, bankacılık, iş SSO\'su, birincil e-posta). Bu hesaplar 2FA kullanmalı ve zaten bir aracıya teslim edilmemeli.
  • Düşük riskli site kayıtları (bültenler, ücretsiz denemeler, forum hesapları) için kullandığın tek kullanımlık bir parola hedeflenen kullanım durumudur.
  • Bu tarayıcı profili ele geçirilirse, diske erişebilen bir saldırgan bu metni okuyabilir. Asgaride tut.
', 'tr.title': 'WebBrain İzleri', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} çalışma', 'tr.run': '{n} çalışma', 'tr.btn.refresh': '↻ Yenile', diff --git a/src/firefox/src/ui/locales/uk.js b/src/firefox/src/ui/locales/uk.js index 0cb6dba84..662cf659b 100644 --- a/src/firefox/src/ui/locales/uk.js +++ b/src/firefox/src/ui/locales/uk.js @@ -259,6 +259,8 @@ export default { 'st.display.auto_screenshot.every_step': 'Кожен крок', 'st.display.tracing.label': 'Записувати траси (для порівняння моделей)', 'st.display.tracing.desc_html': 'Зберігати кожен запуск (LLM-запити, відповіді, виклики інструментів, скриншоти) у локальну IndexedDB, щоб переглядати й порівнювати моделі поряд. Відкривається в окремій вкладці «Траси». За замовчуванням вимкнено, бо додає запис на диск на кожному кроці. Відкрити сторінку трас →', + 'st.display.lossless_tracing.label': 'Записувати повні деталі запитів (рівень налагодження)', + 'st.display.lossless_tracing.desc_html': 'Також зберігає повні промпти, повідомлення та схеми інструментів кожного LLM-запиту замість стислих зведень без вмісту, та тримає до 200 КБ кожного результату інструмента. Дає змогу глибокого налагодження та відтворення запитів. Вимкнено за замовчуванням, бо записує вміст вашої розмови локально та займає значно більше диска. Потребує увімкненого запису трас.', 'st.display.allow_local_network.label': 'Дозволити агенту доступ до локальної мережі', 'st.display.allow_local_network.desc': 'Дозволяє інструменту fetch_url агента звертатися до loopback (127.0.0.1, localhost) та приватних адрес RFC1918 (10.x, 192.168.x, 172.16–31.x). Корисно для локальних серверів розробки, інтранет-інструментів і self-hosted API. За замовчуванням вимкнено — коли вимкнено, сторінки з prompt-ін\'єкцією не зможуть змусити агента сканувати вашу локальну мережу. Адреси метаданих хмари (169.254.169.254) та імена *.internal/*.local залишаються заблокованими навіть коли ввімкнено.', 'st.display.notify_sound.label': 'Звук завершення', @@ -331,6 +333,8 @@ export default { 'st.profile.security_html': 'Безпека — прочитайте:
  • Введений тут текст зберігається у відкритому вигляді в локальному сховищі браузера. Він не надсилається проєкту WebBrain — але надсилається на кожному ходу як частина системного prompt тому LLM-провайдеру, якого ви налаштували.
  • Не вводьте сюди паролі від важливих акаунтів (Google, Apple, iCloud, банк, робочий SSO, основна пошта). Ці акаунти мають мати 2FA, і їх у будь-якому разі не варто передавати агенту.
  • Цільовий сценарій — одноразовий пароль, який ви повторно використовуєте для реєстрацій із низькими ставками (розсилки, безкоштовні пробні версії, акаунти на форумах).
  • Якщо цей профіль браузера колись буде скомпрометовано, зловмисник із доступом до диска зможе прочитати цей текст. Тримайте його мінімальним.
', 'tr.title': 'Траси WebBrain', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': 'запусків: {n}', 'tr.run': 'запуск: {n}', 'tr.btn.refresh': '↻ Оновити', diff --git a/src/firefox/src/ui/locales/vi.js b/src/firefox/src/ui/locales/vi.js index d048b043e..4d4709ca3 100644 --- a/src/firefox/src/ui/locales/vi.js +++ b/src/firefox/src/ui/locales/vi.js @@ -589,6 +589,8 @@ export default { 'st.display.auto_screenshot.every_step': "Mỗi bước", 'st.display.tracing.label': "Ghi lại dấu vết (để so sánh mô hình)", 'st.display.tracing.desc_html': "Duy trì mọi lần chạy (yêu cầu LLM, phản hồi, lệnh gọi công cụ, ảnh chụp màn hình) vào IndexedDB cục bộ để bạn có thể kiểm tra và so sánh các mô hình song song. Mở trong tab Dấu vết riêng biệt. Tắt theo mặc định vì nó thêm ghi vào đĩa mỗi bước. Mở trang Dấu vết →", + 'st.display.lossless_tracing.label': 'Ghi lại chi tiết đầy đủ của yêu cầu (lớp gỡ lỗi)', + 'st.display.lossless_tracing.desc_html': 'Cũng lưu trữ đầy đủ prompt, tin nhắn và cấu trúc công cụ của mỗi yêu cầu LLM thay vì bản tóm tắt không nội dung, đồng thời giữ tối đa 200 KB mỗi kết quả công cụ. Cho phép gỡ lỗi sâu và dựng lại yêu cầu. Tắt theo mặc định vì ghi nội dung hội thoại cục bộ và tốn nhiều dung lượng hơn. Yêu cầu bật tính năng ghi trace.', 'st.display.cost_session_limit.label': "Phụ cấp phiên chi phí đám mây", 'st.display.cost_session_limit.desc': "Dừng đám mây và OpenRouter chạy trước một lệnh gọi mô hình trả phí khác sau khi phiên này đạt đến mức chi phí được báo cáo hoặc ước tính. Mặc định là 10$.", 'st.display.cost_total_limit.label': "Tổng phụ cấp chi phí đám mây", @@ -914,6 +916,8 @@ export default { // --- Traces page ------------------------------------------------------- 'tr.title': "Dấu vết WebBrain", + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': "{n} chạy", 'tr.run': "Chạy {n}", 'tr.btn.refresh': "↻ Làm mới", diff --git a/src/firefox/src/ui/locales/zh.js b/src/firefox/src/ui/locales/zh.js index a78b073cc..81e525e12 100644 --- a/src/firefox/src/ui/locales/zh.js +++ b/src/firefox/src/ui/locales/zh.js @@ -259,6 +259,8 @@ export default { 'st.display.auto_screenshot.every_step': '每一步', 'st.display.tracing.label': '记录轨迹(用于模型对比)', 'st.display.tracing.desc_html': '将每次运行(LLM 请求、响应、工具调用、截图)持久化到本地 IndexedDB,以便并排查看和对比模型。在独立的轨迹页打开。因为每步都会写盘,默认关闭。打开轨迹页 →', + 'st.display.lossless_tracing.label': '记录完整请求详情(调试档)', + 'st.display.lossless_tracing.desc_html': '同时持久化每次 LLM 请求的完整提示词、消息与工具结构(而非无内容摘要),并保留每个工具结果最多 200 KB。支持深度调试与请求重建。默认关闭,因为会在本地写入对话内容并占用更多磁盘。需要先开启记录轨迹。', 'st.display.allow_local_network.label': '允许代理访问本地网络', 'st.display.allow_local_network.desc': '允许代理的 fetch_url 工具访问回环(127.0.0.1、localhost)和私有/RFC1918 地址(10.x、192.168.x、172.16–31.x)。用于本地开发服务器、内网工具或自托管 API。默认关闭 — 关闭时,含 prompt 注入的页面无法引导代理探测你的本地网络。即使开启,云元数据地址(169.254.169.254)和 *.internal/*.local 主机名仍然会被阻止。', 'st.display.notify_sound.label': '完成提示音', @@ -331,6 +333,8 @@ export default { 'st.profile.security_html': '安全提示 — 请阅读:
  • 你在这里输入的文本以明文保存在浏览器的本地存储中。它不会被发送到 WebBrain 项目 — 但在每一轮作为系统提示的一部分发送给你所配置的 LLM 提供商。
  • 不要在这里放重要账户的密码(Google、Apple、iCloud、银行、工作 SSO、主邮箱)。这些账户应启用 2FA,也本就不该交给代理。
  • 适合此处的是你用于低风险注册(时事通讯、免费试用、论坛账户)的一次性密码
  • 如果此浏览器配置文件被攻破,能访问磁盘的攻击者就能读到此文本。请尽量精简。
', 'tr.title': 'WebBrain 轨迹', + 'tr.lossless.badge': 'Sensitive debug tier', + 'tr.lossless.warning': 'This run contains full request content. Review it for secrets before exporting or sharing.', 'tr.runs': '{n} 次运行', 'tr.run': '{n} 次运行', 'tr.btn.refresh': '↻ 刷新', diff --git a/src/firefox/src/ui/settings.html b/src/firefox/src/ui/settings.html index 784c852bf..cd56cb4a0 100644 --- a/src/firefox/src/ui/settings.html +++ b/src/firefox/src/ui/settings.html @@ -1350,6 +1350,16 @@

+
+
+
+
+
+ +
diff --git a/src/firefox/src/ui/settings.js b/src/firefox/src/ui/settings.js index 531738d9e..6a87b7b20 100644 --- a/src/firefox/src/ui/settings.js +++ b/src/firefox/src/ui/settings.js @@ -101,6 +101,7 @@ const planReviewConfidenceRow = document.getElementById('row-plan-review-confide const notifySoundToggle = document.getElementById('toggle-notify-sound'); const completionConfettiToggle = document.getElementById('toggle-completion-confetti'); const tracingToggle = document.getElementById('toggle-tracing'); +const losslessTracingToggle = document.getElementById('toggle-lossless-tracing'); const strictSecretToggle = document.getElementById('toggle-strict-secret'); const allowLocalNetworkToggle = document.getElementById('toggle-allow-local-network'); const scheduledTasksToggle = document.getElementById('toggle-scheduled-tasks'); @@ -469,7 +470,7 @@ async function init() { browser.storage.local.remove(['authToken', 'authEmail', 'authDefaultModel']).catch(() => {}); // Load display settings - const stored = await browser.storage.local.get(['verboseMode', 'selectionShortcutEnabled', AUTO_GROUP_TABS_KEY, 'helpImproveWebBrain', 'screenshotFallback', 'maxAgentSteps', 'autoScreenshot', 'useSiteAdapters', 'researchEscalationEnabled', 'researchEscalationEngine', 'voiceInputEnabled', 'alwaysAllowApiMutations', 'apiMutationObserverEnabled', 'openaiAskStreamingEnabled', 'planBeforeActMode', 'planBeforeAct', 'planReviewMode', 'planReviewConfidenceThreshold', DOWNLOAD_DIRECTORY_STORAGE_KEY, 'notifySound', 'completionConfetti', 'tracingEnabled', 'strictSecretMode', 'agentAllowLocalNetwork', 'scheduledTasksEnabled', 'scheduledRequireConsequentialConfirmation', 'providerFilter', 'requestTimeoutMs', 'clarifyTimeoutSec', 'clarifyTimeoutSemanticsV2', 'costAllowanceSessionUsd', 'costAllowanceTotalUsd', 'meteredProviderCostSpentUsd', 'screenshotRedaction', 'imageDetail', 'maxScreenshotsPerTurn', 'maxImageDimension']); + const stored = await browser.storage.local.get(['verboseMode', 'selectionShortcutEnabled', AUTO_GROUP_TABS_KEY, 'helpImproveWebBrain', 'screenshotFallback', 'maxAgentSteps', 'autoScreenshot', 'useSiteAdapters', 'researchEscalationEnabled', 'researchEscalationEngine', 'voiceInputEnabled', 'alwaysAllowApiMutations', 'apiMutationObserverEnabled', 'openaiAskStreamingEnabled', 'planBeforeActMode', 'planBeforeAct', 'planReviewMode', 'planReviewConfidenceThreshold', DOWNLOAD_DIRECTORY_STORAGE_KEY, 'notifySound', 'completionConfetti', 'tracingEnabled', 'losslessTrace', 'strictSecretMode', 'agentAllowLocalNetwork', 'scheduledTasksEnabled', 'scheduledRequireConsequentialConfirmation', 'providerFilter', 'requestTimeoutMs', 'clarifyTimeoutSec', 'clarifyTimeoutSemanticsV2', 'costAllowanceSessionUsd', 'costAllowanceTotalUsd', 'meteredProviderCostSpentUsd', 'screenshotRedaction', 'imageDetail', 'maxScreenshotsPerTurn', 'maxImageDimension']); if (typeof stored.providerFilter === 'string' && ['all','active','local','cloud','router'].includes(stored.providerFilter)) { providerFilter = stored.providerFilter; } @@ -535,6 +536,12 @@ async function init() { if (notifySoundToggle) notifySoundToggle.checked = stored.notifySound ?? true; if (completionConfettiToggle) completionConfettiToggle.checked = stored.completionConfetti ?? true; if (tracingToggle) tracingToggle.checked = stored.tracingEnabled === true; + if (losslessTracingToggle) { + losslessTracingToggle.checked = stored.losslessTrace === true; + // Lossless recording only means something when tracing is on; mirror the + // disabled state so the disclosure reads honestly. + losslessTracingToggle.disabled = tracingToggle?.checked !== true; + } const sessionLimit = normalizeCostAmount(stored.costAllowanceSessionUsd); const totalLimit = normalizeCostAmount(stored.costAllowanceTotalUsd); const totalSpent = normalizeCostAmount(stored.meteredProviderCostSpentUsd, 0); @@ -1206,6 +1213,17 @@ completionConfettiToggle?.addEventListener('change', async () => { tracingToggle?.addEventListener('change', async () => { await browser.storage.local.set({ tracingEnabled: tracingToggle.checked }).catch(() => {}); + if (losslessTracingToggle) { + losslessTracingToggle.disabled = tracingToggle.checked !== true; + if (!tracingToggle.checked) { + losslessTracingToggle.checked = false; + await browser.storage.local.set({ losslessTrace: false }).catch(() => {}); + } + } +}); + +losslessTracingToggle?.addEventListener('change', async () => { + await browser.storage.local.set({ losslessTrace: losslessTracingToggle.checked }).catch(() => {}); }); costSessionLimitInput?.addEventListener('change', async () => { diff --git a/src/firefox/src/ui/traces.html b/src/firefox/src/ui/traces.html index d1e70c0bf..ae592a176 100644 --- a/src/firefox/src/ui/traces.html +++ b/src/firefox/src/ui/traces.html @@ -126,6 +126,17 @@ text-overflow: ellipsis; margin-bottom: 3px; } + .lossless-badge { + display: inline-block; + margin-left: 6px; + padding: 1px 4px; + border: 1px solid rgba(255,152,0,0.55); + border-radius: 4px; + color: var(--warning); + font-size: 9px; + font-weight: 600; + vertical-align: 1px; + } .run-item .run-meta { font-size: 10px; color: var(--text2); @@ -196,6 +207,15 @@ margin-bottom: 14px; flex-wrap: wrap; } + .lossless-warning { + margin: 0 0 14px; + padding: 9px 12px; + border-left: 3px solid var(--warning); + background: rgba(255,152,0,0.12); + color: var(--warning); + border-radius: 0 6px 6px 0; + font-size: 12px; + } /* Costly-failure highlight — runs ≥ $0.50 with empty finalContent. */ .cost-warn { color: #ff9d4d; diff --git a/src/firefox/src/ui/traces.js b/src/firefox/src/ui/traces.js index 22bde8324..bafa8fc33 100644 --- a/src/firefox/src/ui/traces.js +++ b/src/firefox/src/ui/traces.js @@ -8,6 +8,7 @@ import { deleteRun, clearAllRuns, repairStaleRuns, } from '../trace/recorder.js'; import { isKnownKind, isIgnorableKind } from '../trace/event-model.js'; +import { sanitizeTraceExport } from '../agent/trace-export.js'; import { t } from './i18n.js'; import { escapeHtml, escapeAttr } from './utils.js'; @@ -121,6 +122,9 @@ function renderList() { compareIds.includes(r.runId) ? 'compare' : '', ].filter(Boolean).join(' '); const title = r.userMessage || t('tr.no_task'); + const losslessBadge = r.lossless === true + ? `${escapeHtml(t('tr.lossless.badge'))}` + : ''; const siblings = siblingsOf(r); const convChip = siblings.length > 1 ? `🧵 ${siblings.length}` @@ -131,7 +135,7 @@ function renderList() { const costClass = isCostlyFailure ? 'cost-warn' : ''; return `
-
${escapeHtml(title.slice(0, 120))}${convChip}
+
${escapeHtml(title.slice(0, 120))}${losslessBadge}${convChip}
${escapeHtml(r.model || '?')} ${escapeHtml(r.providerId || '')} @@ -271,6 +275,7 @@ async function buildRunView(run, events, compact, objectUrls = new Set()) { ${escapeHtml(t('tr.outtokens.label'))} ${(run.totalOutputTokens || 0).toLocaleString()} ${formatCost(run.totalCost) ? `${escapeHtml(t('tr.cost.label'))} ${escapeHtml(formatCost(run.totalCost))}` : ''}
+ ${run.lossless === true ? `` : ''} ${renderConversationPanel(run, compact)}
${escapeHtml(run.userMessage || '')}
${run.finalContent ? `
${escapeHtml(t('tr.final_label'))} ${escapeHtml(run.finalContent)}
` : ''} @@ -541,7 +546,7 @@ document.getElementById('btn-export').addEventListener('click', async () => { exportedByWebBrainVersion: browser.runtime.getManifest().version || '', schema: 'webbrain-trace/1', }; - const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' }); + const blob = new Blob([JSON.stringify(sanitizeTraceExport(payload), null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; diff --git a/test/run.js b/test/run.js index 10b7656b7..c7111226c 100644 --- a/test/run.js +++ b/test/run.js @@ -286,10 +286,10 @@ const { ); // trace-export.js is pure ESM — the /export --traces serializer, tested here. -const { tracesToMarkdown } = await import( +const { tracesToMarkdown, sanitizeTraceExport } = await import( 'file://' + path.join(ROOT, 'src/chrome/src/agent/trace-export.js').replace(/\\/g, '/') ); -const { tracesToMarkdown: tracesToMarkdownFx } = await import( +const { tracesToMarkdown: tracesToMarkdownFx, sanitizeTraceExport: sanitizeTraceExportFx } = await import( 'file://' + path.join(ROOT, 'src/firefox/src/agent/trace-export.js').replace(/\\/g, '/') ); const { webbrainTraceToAtif } = await import( @@ -8425,7 +8425,7 @@ test('trace recorder: writes go through the event model and stamp the format ver const recorderSource = fs.readFileSync(path.join(ROOT, `src/${browser}/src/trace/recorder.js`), 'utf8'); assert.match(recorderSource, /import \{ TRACE_FORMAT_VERSION, makeEvent \} from '\.\/event-model\.js';/, `${browser}: recorder does not import the event model`); assert.match(recorderSource, /traceFormatVersion: TRACE_FORMAT_VERSION/, `${browser}: run record does not stamp the format version`); - assert.match(recorderSource, /const ev = makeEvent\(runId, seq, kind, data\);/, `${browser}: event writes bypass the envelope`); + assert.match(recorderSource, /const resolvedData = typeof data === 'function' \? data\(state\) : data;[\s\S]*?const ev = makeEvent\(runId, seq, kind, resolvedData\);/, `${browser}: queued event writes bypass the envelope`); assert.match(recorderSource, /dropped invalid event/, `${browser}: invalid-event drop is not surfaced`); assert.match(recorderSource, /const marker = makeEvent\(runId, seq, 'screenshot'/, `${browser}: screenshot marker bypasses the envelope`); } @@ -9113,6 +9113,169 @@ test('trace run header: mirrors are identical and browser-neutral', () => { assert.doesNotMatch(chromeSource, /indexedDB|storage\./, 'run header must not touch storage'); }); +// trace lossless tier — opt-in full-request recording +// ──────────────────────────────────────────────────────────────────────── + +console.log('\ntrace lossless tier'); + +const RUNTIME_CONFIG_CH = await import('file://' + path.join(ROOT, 'src/chrome/src/trace/runtime-config.js').replace(/\\/g, '/')); + +test('trace lossless tier: runtime-config accepts the boolean and defaults stay off', () => { + const normalized = RUNTIME_CONFIG_CH.normalizeRuntimeTraceConfig({ lossless_trace: true }); + assert.equal(normalized.lossless_trace, true, 'lossless_trace boolean not allowlisted'); + const off = RUNTIME_CONFIG_CH.normalizeRuntimeTraceConfig({ lossless_trace: false }); + assert.equal(off.lossless_trace, false, 'lossless_trace false should survive'); + const junk = RUNTIME_CONFIG_CH.normalizeRuntimeTraceConfig({ lossless_trace: 'yes' }); + assert.equal(junk.lossless_trace, undefined, 'non-boolean lossless_trace must be rejected'); +}); + +test('trace lossless tier: recorder branches on the tier and clamps payloads', () => { + for (const browser of ['chrome', 'firefox']) { + const recorderSource = fs.readFileSync(path.join(ROOT, `src/${browser}/src/trace/recorder.js`), 'utf8'); + assert.match(recorderSource, /async function losslessTraceEnabled\(\)/, `${browser}: losslessTraceEnabled missing`); + assert.match(recorderSource, /const lossless = meta\.lossless === true \|\| await losslessTraceEnabled\(\);/, `${browser}: tier decision missing in startRun`); + assert.match(recorderSource, /\.\.\.\(lossless \? \{ lossless: true, losslessBytes: 0 \} : \{\}\)/, `${browser}: run record does not stamp the tier`); + assert.match(recorderSource, /const LOSSILESS_RESULT_CAP = 200_000;/, `${browser}: lossless result cap missing`); + assert.match(recorderSource, /const LOSSILESS_REQUEST_CAP = 500_000;/, `${browser}: lossless request cap missing`); + assert.match(recorderSource, /_appendEvent\(runId, 'llm_request', \(state\)[\s\S]*?state\?\.lossless === true && provenanceInput/, `${browser}: request lossless branch missing`); + assert.match(recorderSource, /const cap = state\?\.lossless === true \? LOSSILESS_RESULT_CAP : 20_000;/, `${browser}: tool-result cap does not branch on tier`); + assert.match(recorderSource, /peekRunFlags|lossless: record\?\.lossless === true|lossless = record\?\.lossless === true/, `${browser}: SW-eviction recovery does not restore the tier`); + assert.match(recorderSource, /async function _ensureRunState\(runId(?:, db = null)?\)/, `${browser}: recorder has no shared SW-recovery state loader`); + assert.match(recorderSource, /function recordLLMRequest[\s\S]*?_appendEvent\(runId, 'llm_request', \(state\)[\s\S]*?state\?\.lossless === true/, `${browser}: request recovery is not serialized inside the write queue`); + assert.match(recorderSource, /function recordToolCall[\s\S]*?_appendEvent\(runId, 'tool', \(state\)[\s\S]*?state\?\.lossless === true/, `${browser}: tool recovery is not serialized inside the write queue`); + assert.match(recorderSource, /\.\.\.\(lossless \? \{ lossless: true, losslessBytes: 0 \} : \{\}\)/, `${browser}: default run records still serialize a false lossless field`); + assert.match(recorderSource, /LOSSILESS_TOOLS_CAP|clampLosslessRequest|tools: \{ _truncated/, `${browser}: lossless tool schemas are not independently bounded`); + assert.match(recorderSource, /evictOldestLosslessRuns[\s\S]*?status !== 'running'[\s\S]*?sort\(\(a, b\) => \(a\.startedAt \|\| 0\) - \(b\.startedAt \|\| 0\)\)[\s\S]*?await deleteRun\(run\.runId\)/, `${browser}: lossless runs are not evicted oldest-first`); + assert.match(recorderSource, /_losslessTotalEstimate \+= addedBytes;[\s\S]*?if \(_losslessTotalEstimate <= LOSSILESS_TOTAL_CAP\) return;/, `${browser}: eviction scan is not gated by a cached running total`); + assert.match(recorderSource, /async function _scanLosslessTotal\(\)[\s\S]*?objectStore\('runs'\)\.openCursor\(\)/, `${browser}: lossless total must scan every run, not a newest-N window`); + assert.match(recorderSource, /clearAllRuns\(\) \{[\s\S]*?_losslessTotalEstimate = null;/, `${browser}: clearAllRuns does not reset the lossless total cache`); + assert.doesNotMatch(recorderSource, /length: 0, head: '\(per-run lossless budget reached\)'/, `${browser}: budget-reached markers lost the true payload length`); + // Default tier must keep the content-free provenance path. + assert.match(recorderSource, /buildPromptTraceProvenance\(/, `${browser}: default tier lost its provenance reduction`); + } +}); + +test('trace lossless tier: settings UI wires the toggle and the disclosure', () => { + for (const browser of ['chrome', 'firefox']) { + const settingsHtml = fs.readFileSync(path.join(ROOT, `src/${browser}/src/ui/settings.html`), 'utf8'); + const settingsJs = fs.readFileSync(path.join(ROOT, `src/${browser}/src/ui/settings.js`), 'utf8'); + const enLocale = fs.readFileSync(path.join(ROOT, `src/${browser}/src/ui/locales/en.js`), 'utf8'); + assert.match(settingsHtml, /id="toggle-lossless-tracing"/, `${browser}: settings HTML toggle missing`); + assert.match(settingsHtml, /st\.display\.lossless_tracing\.label/, `${browser}: settings HTML label key missing`); + assert.match(settingsJs, /losslessTracingToggle\.checked = stored\.losslessTrace === true/, `${browser}: settings JS does not load the key`); + assert.match(settingsJs, /storage\.local\.set\(\{ losslessTrace: losslessTracingToggle\.checked \}\)/, `${browser}: settings JS does not persist the key`); + assert.match(settingsJs, /losslessTracingToggle\.disabled = tracingToggle\.checked !== true/, `${browser}: lossless toggle is not gated on tracing`); + assert.match(enLocale, /st\.display\.lossless_tracing\.label/, `${browser}: en locale label missing`); + assert.match(enLocale, /writes your conversation content locally/, `${browser}: en locale disclosure missing`); + } +}); + +test('trace lossless tier: export renders masked request previews only for lossless runs', () => { + const losslessRun = { + runId: 'r_lossless', userMessage: 'hello', status: 'done', webbrainVersion: '', + }; + const losslessEvents = [ + { + runId: 'r_lossless', seq: 1, ts: 1, kind: 'llm_request', + data: { + messageCount: 2, toolsCount: 1, lossless: true, + messages: [ + { role: 'system', content: 'You are WebBrain.' }, + { role: 'user', content: '{"password":"hunter2abc", "api_key":"value12345"} api_key=sk-secret1234567890 and Bearer tok1234567890' }, + ], + tools: [{ function: { name: 'click' } }], + }, + }, + ]; + for (const [label, serialize] of [['chrome', tracesToMarkdown], ['firefox', tracesToMarkdownFx]]) { + const { markdown } = serialize([{ run: losslessRun, events: losslessEvents }]); + assert.match(markdown, /\*\*user:\*\*/, `${label}: lossless message preview missing`); + assert.match(markdown, /tools: click/, `${label}: lossless tool list missing`); + assert.doesNotMatch(markdown, /sk-secret1234567890|tok1234567890/, `${label}: lossless export leaked secrets`); + assert.match(markdown, /\[redacted\]/, `${label}: lossless export has no redaction marker`); + + const truncated = serialize([{ run: losslessRun, events: [{ + kind: 'llm_request', + data: { + messageCount: 1, toolsCount: 2, lossless: true, + messages: { _truncated: true, length: 750000, head: '{"messages":[{"role":"user","content":"partial"}' , toolNames: ['click', 'type_text'] }, + tools: null, + }, + }] }]).markdown; + assert.match(truncated, /truncated/i, `${label}: truncated request marker was rendered as empty`); + assert.match(truncated, /partial/, `${label}: truncated request head was omitted`); + assert.match(truncated, /tools: click, type_text/, `${label}: truncated request tool names were omitted`); + } + // Default tier: no message preview even when a request payload exists. + const defaultRun = { + runId: 'r_default', userMessage: 'hello', status: 'done', webbrainVersion: '', + }; + const defaultEvents = [ + { + runId: 'r_default', seq: 1, ts: 1, kind: 'llm_request', + data: { messageCount: 2, toolsCount: 0, promptProvenance: { systemPromptVariant: 'ask' } }, + }, + ]; + const { markdown } = tracesToMarkdown([{ run: defaultRun, events: defaultEvents }]); + assert.doesNotMatch(markdown, /\*\*system:\*\*|\*\*user:\*\*/, 'default tier export must not render messages'); +}); + +test('trace lossless tier: exports redact the complete credential-key catalog', () => { + const sentinel = { + refresh_token: 'refresh-sentinel', + client_secret: 'client-secret-sentinel', + private_key: 'private-key-sentinel', + otp: 'otp-sentinel', + recovery_code: 'recovery-sentinel', + }; + for (const [label, serialize] of [['chrome', tracesToMarkdown], ['firefox', tracesToMarkdownFx]]) { + const { markdown } = serialize([{ run: { runId: label, status: 'done' }, events: [{ + kind: 'llm_request', + data: { lossless: true, messages: [{ role: 'user', content: JSON.stringify(sentinel) }], tools: [] }, + }] }]); + for (const value of Object.values(sentinel)) { + assert.doesNotMatch(markdown, new RegExp(value), `${label}: Markdown leaked ${value}`); + } + } +}); + +test('trace lossless tier: JSON exports redact lossless event credentials only', () => { + const payload = { + run: { runId: 'lossless-json', lossless: true }, + events: [ + { data: { messages: [{ content: '{"refresh_token":"refresh-sentinel","private_key":"private-sentinel"}' }] } }, + { data: { name: 'fill_form', args: { recovery_code: 'recovery-sentinel', api_key: 'api-sentinel', notes: 'plain-sentinel' } } }, + ], + }; + for (const [label, sanitize] of [['chrome', sanitizeTraceExport], ['firefox', sanitizeTraceExportFx]]) { + const exported = JSON.stringify(sanitize(payload)); + assert.doesNotMatch(exported, /refresh-sentinel|private-sentinel/, `${label}: JSON export leaked a credential`); + assert.doesNotMatch(exported, /recovery-sentinel|api-sentinel/, `${label}: JSON export leaked a credential stored under a sensitive args key`); + assert.match(exported, /\[redacted\]/, `${label}: JSON export did not mark credentials`); + assert.match(exported, /plain-sentinel/, `${label}: JSON export redacted a non-sensitive key`); + } +}); + +test('trace lossless tier: default-tier privacy guard survives in both builds', () => { + const chromeRecorder = fs.readFileSync(path.join(ROOT, 'src/chrome/src/trace/recorder.js'), 'utf8'); + const firefoxRecorder = fs.readFileSync(path.join(ROOT, 'src/firefox/src/trace/recorder.js'), 'utf8'); + assert.match(chromeRecorder, /Default tier: never persist full prompts, message text, tool schemas, or[\s\S]*?tool names here\./, 'chrome default tier lost its privacy comment guard'); + assert.match(firefoxRecorder, /Default tier: never persist full prompts, message text, tool schemas, or[\s\S]*?tool names here\./, 'firefox default tier lost its privacy comment guard'); +}); + +test('trace lossless tier: Traces UI identifies sensitive runs before export', () => { + for (const browser of ['chrome', 'firefox']) { + const traces = fs.readFileSync(path.join(ROOT, `src/${browser}/src/ui/traces.js`), 'utf8'); + const html = fs.readFileSync(path.join(ROOT, `src/${browser}/src/ui/traces.html`), 'utf8'); + const locale = fs.readFileSync(path.join(ROOT, `src/${browser}/src/ui/locales/en.js`), 'utf8'); + assert.match(traces, /run\.lossless === true/, `${browser}: run list does not surface lossless traces`); + assert.match(traces, /tr\.lossless\.warning/, `${browser}: selected lossless run has no export warning`); + assert.match(html, /lossless-badge|lossless-warning/, `${browser}: no lossless trace styling exists`); + assert.match(locale, /tr\.lossless\.badge/, `${browser}: lossless badge locale missing`); + assert.match(locale, /tr\.lossless\.warning/, `${browser}: lossless warning locale missing`); + } +}); + test('trace recorder: DB v2 adds lineage indexes and startRun stores lineage fields', () => { for (const browser of ['chrome', 'firefox']) { const recorderSource = fs.readFileSync(path.join(ROOT, `src/${browser}/src/trace/recorder.js`), 'utf8'); @@ -19338,7 +19501,9 @@ test('cloud runs force trace capture without changing the interactive opt-in def assert.match(agentSource, /force:\s*runOptions\?\.cloudRun === true/); assert.match(recorderSource, /if \(!forced && !\(await tracingEnabled\(\)\)\) return null/); assert.match(recorderSource, /tracingEnabledForRun\(runId\)/); - assert.match(recorderSource, /forced:\s*await isForcedTraceRun\(runId\)/); + // The forced flag is restored from the durable run record after SW eviction + // (peekRunFlags), keeping forced-capture semantics without an in-memory map. + assert.match(recorderSource, /forced: flags\.forced, lossless: flags\.lossless/); }); test('cloud trace keeps CAPTCHA frame/vendor diagnostics after the rolling update window drops the event', async () => {