Skip to content

Commit 2f28680

Browse files
committed
Address /export-with-traces review: scrub, scope, and UX
Make tool-chain export safer and clearer: conversation-scoped listRuns, resilient JSON serialization, event-load failure signals, distinct empty/ success copy, busy-state locale strings, and wiring tests for both browsers.
1 parent acd1182 commit 2f28680

43 files changed

Lines changed: 430 additions & 89 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/chrome/src/agent/agent.js

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6320,24 +6320,58 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
63206320
await this._hydrate(tabId);
63216321
const conversationId = this.conversationIds.get(tabId);
63226322
if (!conversationId) return { ok: true, markdown: null, turnCount: 0, reason: 'no-conversation' };
6323+
// Cap matching runs for this conversation only (not a global newest-N).
6324+
const RUN_LIMIT = 500;
63236325
let runs;
6326+
let truncated = false;
63246327
try {
6325-
const all = await trace.listRuns();
6326-
runs = all
6328+
const matched = await trace.listRuns({ limit: RUN_LIMIT, conversationId });
6329+
truncated = matched.length >= RUN_LIMIT;
6330+
runs = matched
63276331
.filter((r) => r && r.conversationId === conversationId)
63286332
.sort((a, b) => (a.startedAt || 0) - (b.startedAt || 0));
63296333
} catch (e) {
63306334
return { ok: false, error: String((e && e.message) || e) };
63316335
}
63326336
if (runs.length === 0) return { ok: true, markdown: null, turnCount: 0, reason: 'no-traces' };
63336337
const withEvents = [];
6338+
let failedEventLoads = 0;
63346339
for (const run of runs) {
63356340
let events = [];
6336-
try { events = await trace.getRunEvents(run.runId); } catch (e) { events = []; }
6341+
try {
6342+
events = await trace.getRunEvents(run.runId);
6343+
} catch (e) {
6344+
events = [];
6345+
failedEventLoads += 1;
6346+
}
63376347
withEvents.push({ run, events });
63386348
}
6339-
const { markdown, turnCount, toolCount } = tracesToMarkdown(withEvents);
6340-
return { ok: true, markdown, turnCount, toolCount };
6349+
if (failedEventLoads === runs.length) {
6350+
return { ok: false, error: 'Could not read any trace events for this conversation.' };
6351+
}
6352+
const notes = [];
6353+
if (failedEventLoads > 0) {
6354+
notes.push(`${failedEventLoads} of ${runs.length} turn(s) could not load their event log.`);
6355+
}
6356+
if (truncated) {
6357+
notes.push(`Export limited to the ${RUN_LIMIT} most recent traced turns for this conversation.`);
6358+
}
6359+
let markdown;
6360+
let turnCount;
6361+
let toolCount;
6362+
try {
6363+
({ markdown, turnCount, toolCount } = tracesToMarkdown(withEvents, { notes }));
6364+
} catch (e) {
6365+
return { ok: false, error: String((e && e.message) || e) };
6366+
}
6367+
return {
6368+
ok: true,
6369+
markdown,
6370+
turnCount,
6371+
toolCount,
6372+
partial: failedEventLoads > 0,
6373+
truncated,
6374+
};
63416375
}
63426376

63436377
_isAgentInjectedUserContent(content) {

src/chrome/src/agent/trace-export.js

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,23 @@ function fencedBlock(content) {
4747
return `${fence}${info}\n${body}\n${fence}`;
4848
}
4949

50+
// IndexedDB can retain values that JSON.stringify rejects (circular / bigint /
51+
// sparse). Never throw mid-export — fall back to a readable marker.
52+
function safeJsonStringify(value) {
53+
try {
54+
return JSON.stringify(value);
55+
} catch {
56+
try {
57+
return String(value);
58+
} catch {
59+
return '(unserializable)';
60+
}
61+
}
62+
}
63+
5064
function stringifyArgs(args) {
5165
if (args == null) return '';
52-
const s = typeof args === 'string' ? args : JSON.stringify(args);
66+
const s = typeof args === 'string' ? args : safeJsonStringify(args);
5367
return truncate(oneLine(s), ARGS_LIMIT);
5468
}
5569

@@ -64,11 +78,14 @@ function renderResult(result) {
6478
};
6579
}
6680
const failed = typeof result === 'object' ? (result.success === false || !!result.error) : false;
67-
const s = typeof result === 'string' ? result : JSON.stringify(result);
81+
const s = typeof result === 'string' ? result : safeJsonStringify(result);
6882
return { text: truncate(oneLine(s), RESULT_LIMIT), failed };
6983
}
7084

71-
export function tracesToMarkdown(runsWithEvents, { title = 'WebBrain Conversation — tool chain' } = {}) {
85+
export function tracesToMarkdown(runsWithEvents, {
86+
title = 'WebBrain Conversation — tool chain',
87+
notes = [],
88+
} = {}) {
7289
const runs = Array.isArray(runsWithEvents) ? runsWithEvents : [];
7390
let md = `# ${title}\n\n`;
7491
let turnCount = 0;
@@ -109,6 +126,10 @@ export function tracesToMarkdown(runsWithEvents, { title = 'WebBrain Conversatio
109126
md += '\n';
110127
}
111128

129+
for (const note of Array.isArray(notes) ? notes : []) {
130+
const line = oneLine(note);
131+
if (line) md += `_Note: ${line}_\n`;
132+
}
112133
md += `${FOOTER}\n`;
113134
return { markdown: md, turnCount, toolCount };
114135
}

src/chrome/src/trace/recorder.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -301,19 +301,24 @@ export async function endRun(runId, { status = 'done', finalContent = null } = {
301301

302302
// ----- Reader API (used by traces.html) --------------------------------------
303303

304-
export async function listRuns({ limit = 500 } = {}) {
304+
export async function listRuns({ limit = 500, conversationId = null } = {}) {
305305
const db = await openDB();
306306
const idx = tx(db, ['runs'], 'readonly').objectStore('runs').index('startedAt');
307307
const out = [];
308-
await new Promise((resolve) => {
308+
// When conversationId is set, only matching runs count toward `limit`, so a
309+
// chat's tool-chain export is not starved by unrelated newer runs.
310+
await new Promise((resolve, reject) => {
309311
const req = idx.openCursor(null, 'prev');
310312
req.onsuccess = () => {
311313
const c = req.result;
312314
if (!c || out.length >= limit) return resolve();
313-
out.push(c.value);
315+
const row = c.value;
316+
if (!conversationId || row?.conversationId === conversationId) {
317+
out.push(row);
318+
}
314319
c.continue();
315320
};
316-
req.onerror = () => resolve();
321+
req.onerror = () => reject(req.error || new Error('listRuns failed'));
317322
});
318323
return out;
319324
}
@@ -327,15 +332,15 @@ export async function getRunEvents(runId) {
327332
const db = await openDB();
328333
const idx = tx(db, ['events'], 'readonly').objectStore('events').index('runId');
329334
const out = [];
330-
await new Promise((resolve) => {
335+
await new Promise((resolve, reject) => {
331336
const req = idx.openCursor(IDBKeyRange.only(runId));
332337
req.onsuccess = () => {
333338
const c = req.result;
334339
if (!c) return resolve();
335340
out.push(c.value);
336341
c.continue();
337342
};
338-
req.onerror = () => resolve();
343+
req.onerror = () => reject(req.error || new Error('getRunEvents failed'));
339344
});
340345
out.sort((a, b) => a.seq - b.seq);
341346
return out;

src/chrome/src/ui/locales/ar.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ export default {
551551
'sp.plan.cancelled': 'تم إلغاء الخطة.',
552552
'sp.plan.expired': 'لم تعد هذه الخطة قيد المراجعة — تم إلغاء التشغيل.',
553553
'sp.plan.awaiting_review': 'وافق على الخطة أعلاه أو ألغها قبل إرسال رسالة أخرى.',
554-
'sp.slash.busy_only_oob': 'تُضاف الرسائل إلى قائمة الانتظار بينما يكون WebBrain مشغولًا. يمكن فقط لـ /help و /check-progress و /show-scratchpad و /show-memory و /list-schedules و /dangerously-skip-permissions و /screenshot و /export و /verbose العمل فورًا كأوامر slash.',
554+
'sp.slash.busy_only_oob': 'تُضاف الرسائل إلى قائمة الانتظار بينما يكون WebBrain مشغولًا. يمكن فقط لـ /help و /check-progress و /show-scratchpad و /show-memory و /list-schedules و /dangerously-skip-permissions و /screenshot و /export و /export-with-traces و /verbose العمل فورًا كأوامر slash.',
555555
'tool.go_back': 'العودة للخلف',
556556
'tool.go_forward': 'التقدم للأمام',
557557
'st.display.search.placeholder': 'البحث في الإعدادات العامة',
@@ -707,4 +707,8 @@ export default {
707707
"sp.slash.export_traces": "تصدير سلسلة الأدوات (التتبعات)",
708708
"sp.export_traces.none": "لا توجد تتبعات لهذه المحادثة. فعّل «تسجيل التتبعات» في الإعدادات ثم أعد التشغيل.",
709709
"sp.export_traces.error": "تعذّر تصدير التتبعات.",
710+
"sp.export_traces.done": "تم تصدير سلسلة الأدوات.",
711+
"sp.export_traces.no_conversation": "لا يوجد ما يُصدَّر بعد — ابدأ محادثة أولًا.",
712+
"sp.export_traces.partial": "تم تصدير سلسلة الأدوات، لكن تعذّر قراءة بعض أحداث الجولات.",
713+
"sp.export_traces.truncated": "تم تصدير سلسلة الأدوات. قد تُفقد الجولات الأقدم إذا كان لهذه المحادثة كثير من التتبعات.",
710714
};

src/chrome/src/ui/locales/en.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ export default {
250250
'sp.permissions.disabled_html': '⚠️ <strong>Permission prompts are OFF.</strong> WebBrain will click, type, navigate, run JavaScript, upload, download, record, and schedule future work without asking first. Re-enable this from Settings → Permissions → Ask before consequential actions.',
251251

252252
'sp.help_html': '<strong>Slash Commands</strong><br><code>/help</code> — Show this list<br><code>/schedule</code> — Create a scheduled task<br><code>/list-schedules</code> — Show scheduled tasks<br><code>/check-progress</code> — Show current progress ledger<br><code>/show-scratchpad</code> — Show current scratchpad<br><code>/edit-scratchpad &lt;text&gt;</code> — Append text to the current scratchpad<br><code>/clear-scratchpad</code> — Clear the current scratchpad<br><code>/remember &lt;text&gt;</code> — Save a user preference to memory<br><code>/show-memory</code> — Show saved user memory<br><code>/forget-memory &lt;id&gt;</code> — Forget a saved memory<br><code>/allow-api</code> — Allow API mutations for this conversation<br><code>/dangerously-skip-permissions</code> — Disable permission prompts globally<br><code>/compact</code> — Compact this conversation context<br><code>/verbose</code> — Toggle verbose/compact tool display<br><code>/reset</code> — Clear conversation<br><code>/screenshot</code> — Capture current tab<br><code>/full-page-screenshot</code> — Capture the full page (Chrome only)<br><code>/record</code> — Start recording the current tab<br><code>/record --transcribe</code> — Record and save a Whisper transcript after stop<br><code>/record-full-screen</code> — Record a screen or window (Chrome only)<br><code>/record-full-screen --transcribe</code> — Record screen/window and save a transcript<br><code>/export</code> — Download conversation as Markdown<br><code>/export-with-traces</code> — Export the tool chain (traces)<br><code>/profile</code> — Toggle profile auto-fill<br><code>/vision</code> — Toggle vision mode on active provider<br><code>/ask</code> — Switch to Ask mode<br><code>/act</code> — Switch to Act mode<br><code>/dev</code> — Switch to Dev mode<br><br><strong>Keyboard Shortcuts</strong><br><code>Ctrl/Cmd+/</code> — Focus the input<br><code>Ctrl/Cmd+Shift+A</code> — Switch to Ask mode<br><code>Ctrl/Cmd+Shift+X</code> — Switch to Act mode<br><code>Ctrl/Cmd+Shift+D</code> — Switch to Dev mode<br><code>Escape</code> — Stop the active run<br><code>Escape</code> twice — Stop an active recording',
253-
'sp.slash.busy_only_oob': 'Messages are queued while WebBrain is busy. Only /help, /check-progress, /show-scratchpad, /show-memory, /list-schedules, /dangerously-skip-permissions, /screenshot, /export, and /verbose can run immediately as slash commands.',
253+
'sp.slash.busy_only_oob': 'Messages are queued while WebBrain is busy. Only /help, /check-progress, /show-scratchpad, /show-memory, /list-schedules, /dangerously-skip-permissions, /screenshot, /export, /export-with-traces, and /verbose can run immediately as slash commands.',
254254
'sp.compact.nothing_to_compact': 'Nothing to compact yet — there is not enough older context.',
255255
'sp.compact.busy': 'Cannot compact while a run is in progress — wait for it to finish.',
256256
'sp.compact.failed': 'Context compaction failed: {error}',
@@ -710,4 +710,8 @@ export default {
710710
"sp.slash.export_traces": "Export the tool chain (traces)",
711711
"sp.export_traces.none": "No traces for this conversation. Enable Record traces in Settings, then run again.",
712712
"sp.export_traces.error": "Couldn't export traces.",
713+
"sp.export_traces.done": "Tool chain exported.",
714+
"sp.export_traces.no_conversation": "Nothing to export yet — start a conversation first.",
715+
"sp.export_traces.partial": "Tool chain exported, but some turn events could not be read.",
716+
"sp.export_traces.truncated": "Tool chain exported. Older turns may be missing if this conversation has many traced runs.",
713717
};

src/chrome/src/ui/locales/es.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ export default {
551551
'sp.plan.cancelled': 'Plan cancelado.',
552552
'sp.plan.expired': 'Este plan ya no está en espera de revisión — la ejecución fue cancelada.',
553553
'sp.plan.awaiting_review': 'Aprueba o cancela el plan anterior antes de enviar otro mensaje.',
554-
'sp.slash.busy_only_oob': 'Los mensajes se ponen en cola mientras WebBrain está ocupado. Solo /help, /check-progress, /show-scratchpad, /show-memory, /list-schedules, /dangerously-skip-permissions, /screenshot, /export y /verbose pueden ejecutarse de inmediato como comandos slash.',
554+
'sp.slash.busy_only_oob': 'Los mensajes se ponen en cola mientras WebBrain está ocupado. Solo /help, /check-progress, /show-scratchpad, /show-memory, /list-schedules, /dangerously-skip-permissions, /screenshot, /export, /export-with-traces y /verbose pueden ejecutarse de inmediato como comandos slash.',
555555
'tool.go_back': 'Volviendo atrás',
556556
'tool.go_forward': 'Avanzando',
557557
'st.display.search.placeholder': 'Buscar en ajustes generales',
@@ -707,4 +707,8 @@ export default {
707707
"sp.slash.export_traces": "Exportar la cadena de herramientas (trazas)",
708708
"sp.export_traces.none": "No hay trazas para esta conversación. Activa «Registrar trazas» en Ajustes y vuelve a ejecutar.",
709709
"sp.export_traces.error": "No se pudieron exportar las trazas.",
710+
"sp.export_traces.done": "Cadena de herramientas exportada.",
711+
"sp.export_traces.no_conversation": "Aún no hay nada que exportar: inicia una conversación primero.",
712+
"sp.export_traces.partial": "Cadena de herramientas exportada, pero no se pudieron leer algunos eventos de turno.",
713+
"sp.export_traces.truncated": "Cadena de herramientas exportada. Puede faltar turnos antiguos si esta conversación tiene muchas trazas.",
710714
};

src/chrome/src/ui/locales/fr.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ export default {
551551
'sp.plan.cancelled': 'Plan annulé.',
552552
'sp.plan.expired': 'Ce plan n\'est plus en attente de révision — l\'exécution a été annulée.',
553553
'sp.plan.awaiting_review': 'Approuvez ou annulez le plan ci-dessus avant d\'envoyer un autre message.',
554-
'sp.slash.busy_only_oob': 'Les messages sont mis en file d\'attente pendant que WebBrain est occupé. Seuls /help, /check-progress, /show-scratchpad, /show-memory, /list-schedules, /dangerously-skip-permissions, /screenshot, /export et /verbose peuvent s\'exécuter immédiatement comme commandes slash.',
554+
'sp.slash.busy_only_oob': 'Les messages sont mis en file d\'attente pendant que WebBrain est occupé. Seuls /help, /check-progress, /show-scratchpad, /show-memory, /list-schedules, /dangerously-skip-permissions, /screenshot, /export, /export-with-traces et /verbose peuvent s\'exécuter immédiatement comme commandes slash.',
555555
'tool.go_back': 'Revenir en arrière',
556556
'tool.go_forward': 'Aller en avant',
557557
'st.display.search.placeholder': 'Rechercher dans les paramètres généraux',
@@ -707,4 +707,8 @@ export default {
707707
"sp.slash.export_traces": "Exporter la chaîne d'outils (traces)",
708708
"sp.export_traces.none": "Aucune trace pour cette conversation. Activez « Enregistrer les traces » dans les paramètres, puis relancez.",
709709
"sp.export_traces.error": "Impossible d'exporter les traces.",
710+
"sp.export_traces.done": "Chaîne d'outils exportée.",
711+
"sp.export_traces.no_conversation": "Rien à exporter pour l'instant — commencez une conversation d'abord.",
712+
"sp.export_traces.partial": "Chaîne d'outils exportée, mais certains événements de tour n'ont pas pu être lus.",
713+
"sp.export_traces.truncated": "Chaîne d'outils exportée. Des tours plus anciens peuvent manquer si cette conversation a beaucoup de traces.",
710714
};

0 commit comments

Comments
 (0)