Files
2026-07-13 12:28:42 +08:00

2059 lines
73 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Tiktoken } from 'https://esm.sh/js-tiktoken/lite';
const { createApp } = window.Vue;
const MODEL_TO_ENCODING = {
'gpt-4.1': 'o200k_base',
'gpt-4o': 'o200k_base',
'gpt-4o-mini': 'o200k_base',
o1: 'o200k_base',
o3: 'o200k_base',
'o3-mini': 'o200k_base',
'gpt-4-turbo': 'cl100k_base',
'gpt-4': 'cl100k_base',
'gpt-3.5-turbo': 'cl100k_base'
};
const encodingCache = new Map();
function safeJsonParse(text) {
try {
return JSON.parse(text);
} catch (error) {
return null;
}
}
function shortText(value, limit = 220) {
const clean = String(value || '').replace(/\s+/g, ' ').trim();
if (!clean) {
return '';
}
return clean.length > limit ? clean.slice(0, limit - 3) + '...' : clean;
}
function shortMultiline(value, limit = 440) {
const clean = String(value || '').trim();
if (!clean) {
return '';
}
return clean.length > limit ? clean.slice(0, limit - 3) + '...' : clean;
}
function numberFormat(value) {
if (value === null || value === undefined || value === '') {
return 'n/a';
}
const number = Number(value);
if (!Number.isFinite(number)) {
return String(value);
}
return new Intl.NumberFormat().format(number);
}
function formatTimestamp(value) {
if (!value) {
return 'n/a';
}
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
return String(value);
}
return date.toLocaleString();
}
function parseArguments(raw) {
if (!raw) {
return {};
}
if (typeof raw === 'object') {
return raw;
}
const parsed = safeJsonParse(raw);
return parsed && typeof parsed === 'object' ? parsed : {};
}
function primaryCommand(raw) {
const input = String(raw || '').trim();
if (!input) {
return 'none';
}
const pieces = input.split(/&&|\|\|/).map((piece) => piece.trim()).filter(Boolean);
const target = pieces.find((piece) => !/^cd\s+/.test(piece)) || pieces[0] || input;
if (target.startsWith('python - <<')) {
return 'python-heredoc';
}
if (target.startsWith('printf ')) {
return 'printf';
}
const match = target.match(/^([A-Za-z0-9._/-]+)/);
return match ? match[1] : shortText(target, 40);
}
function parseExitCode(text) {
const match = String(text || '').match(/(?:Return code:|Process exited with code|<exited with exit code)\s*(-?\d+)/i);
return match ? Number(match[1]) : null;
}
function parseSessionId(text) {
const match = String(text || '').match(/session(?:\s+ID)?\s+(\d+)/i);
return match ? match[1] : '';
}
function countBy(items, selector) {
const counts = new Map();
for (const item of items) {
const key = selector(item);
if (!key) {
continue;
}
counts.set(key, (counts.get(key) || 0) + 1);
}
return Array.from(counts.entries())
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
.map(([label, value]) => ({ label, value }));
}
function toBarRows(rows, limit = 6) {
const subset = rows.slice(0, limit);
const max = subset.reduce((value, row) => Math.max(value, row.value), 0) || 1;
return subset.map((row) => ({
label: row.label,
value: row.value,
width: Math.max(8, Math.round((row.value / max) * 100))
}));
}
function tokenRowsFromTotals(tokens) {
if (!tokens) {
return [{ label: 'availability', value: 'n/a' }];
}
return [
{ label: 'input', value: numberFormat(tokens.input_tokens) },
{ label: 'output', value: numberFormat(tokens.output_tokens) },
{ label: 'reasoning', value: numberFormat(tokens.reasoning_output_tokens) },
{ label: 'cached', value: numberFormat(tokens.cached_input_tokens) },
{ label: 'total', value: numberFormat(tokens.total_tokens) }
];
}
function extractTrajectoryUsage(text, fileName) {
const parsed = safeJsonParse(String(text || '').trim());
if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.messages)) {
throw new Error('Attachment must be a valid trajectory.json file.');
}
let latestUsage = null;
parsed.messages.forEach((message) => {
const usage = message && message.extra && message.extra.usage;
const cumulative = usage && usage.cumulative_response;
if (cumulative && typeof cumulative.total_tokens === 'number') {
latestUsage = cumulative;
}
});
if (!latestUsage) {
throw new Error('trajectory.json does not contain messages[].extra.usage.cumulative_response.');
}
return {
fileName,
exactTokens: latestUsage,
tokenRows: tokenRowsFromTotals(latestUsage),
tokenSourceLabel: 'Exact tokens from attached trajectory.json.'
};
}
function applyTrajectoryCompanion(trace, companion) {
return Object.assign({}, trace, {
exactTokens: companion.exactTokens,
tokenRows: companion.tokenRows,
tokenSourceLabel: companion.tokenSourceLabel,
tokenModeLabel: 'exact',
companionFileName: companion.fileName
});
}
function stripTrajectoryCompanion(trace) {
const exactTokens = trace.baseExactTokens || null;
return Object.assign({}, trace, {
exactTokens,
tokenRows: exactTokens ? tokenRowsFromTotals(exactTokens) : [{ label: 'availability', value: 'estimate pending' }],
tokenSourceLabel: trace.baseTokenSourceLabel,
tokenModeLabel: exactTokens ? 'exact' : 'estimate',
companionFileName: ''
});
}
function statusFromExit(exitCode, sessionId, output, done) {
if (exitCode === 0) {
return { label: 'ok', css: 'good' };
}
if (exitCode !== null && exitCode !== 0) {
return { label: 'error', css: 'bad' };
}
if (done === true) {
return { label: 'done', css: 'good' };
}
if (sessionId || /process running/i.test(String(output || ''))) {
return { label: 'streaming', css: 'warn' };
}
return { label: 'unknown', css: '' };
}
function splitConcatenatedJsonObjects(text) {
const source = String(text || '');
const segments = [];
let start = -1;
let depth = 0;
let inString = false;
let escaped = false;
for (let index = 0; index < source.length; index += 1) {
const char = source[index];
if (start === -1) {
if (char === '{') {
start = index;
depth = 1;
inString = false;
escaped = false;
}
continue;
}
if (inString) {
if (escaped) {
escaped = false;
} else if (char === '\\') {
escaped = true;
} else if (char === '"') {
inString = false;
}
continue;
}
if (char === '"') {
inString = true;
continue;
}
if (char === '{') {
depth += 1;
continue;
}
if (char === '}') {
depth -= 1;
if (depth === 0) {
segments.push(source.slice(start, index + 1));
start = -1;
}
}
}
return segments.map((segment) => safeJsonParse(segment)).filter(Boolean);
}
function getTokenizerSelection(value) {
const [mode, name] = String(value || 'model:o3').split(':');
const encoding = mode === 'model' ? MODEL_TO_ENCODING[name] || 'o200k_base' : name || 'o200k_base';
return { mode, name: name || 'o3', encoding };
}
async function loadEncoder(encoding) {
if (encodingCache.has(encoding)) {
return encodingCache.get(encoding);
}
const controller = new AbortController();
const timeoutId = window.setTimeout(() => controller.abort(), 8000);
let response;
try {
response = await fetch(`https://tiktoken.pages.dev/js/${encoding}.json`, {
signal: controller.signal
});
} finally {
window.clearTimeout(timeoutId);
}
if (!response.ok) {
throw new Error(`Failed to load ${encoding} ranks from CDN`);
}
const ranks = await response.json();
const encoder = new Tiktoken(ranks);
encodingCache.set(encoding, encoder);
return encoder;
}
function quickTokenEstimate(text) {
const source = String(text || '');
if (!source.trim()) {
return 0;
}
return Math.max(1, Math.ceil(source.length / 4));
}
function mergeEstimateParts(parts) {
const grouped = new Map();
for (const part of parts || []) {
if (!part || !String(part.text || '').trim()) {
continue;
}
const key = part.label || 'other';
if (!grouped.has(key)) {
grouped.set(key, []);
}
grouped.get(key).push(String(part.text));
}
return Array.from(grouped.entries()).map(([label, texts]) => ({
label,
text: texts.join('\n\n')
}));
}
function contentToText(content) {
if (typeof content === 'string') {
return content;
}
if (Array.isArray(content)) {
return content.map((part) => {
if (typeof part === 'string') {
return part;
}
if (part && typeof part === 'object') {
if (typeof part.text === 'string') {
return part.text;
}
if (typeof part.content === 'string') {
return part.content;
}
}
return part ? JSON.stringify(part) : '';
}).filter(Boolean).join('\n\n');
}
return content ? JSON.stringify(content) : '';
}
function normalizeObservation(message) {
const extra = message && message.extra ? message.extra : {};
if (extra.observation && typeof extra.observation === 'object') {
return {
output: String(extra.observation.command_output || ''),
exitCode: typeof extra.observation.returncode === 'number' ? extra.observation.returncode : parseExitCode(extra.observation.command_output || ''),
command: String(extra.observation.command || ''),
success: extra.observation.success
};
}
const text = contentToText(message.content);
if (!/^Observation:/i.test(text)) {
return null;
}
const outputMatch = text.match(/Command output:\n([\s\S]*)$/i);
const commandMatch = text.match(/Command:\s*([^\n]+)/i);
return {
output: outputMatch ? outputMatch[1].trim() : text,
exitCode: parseExitCode(text),
command: commandMatch ? commandMatch[1].trim() : '',
success: /Status:\s*ok/i.test(text)
};
}
function stripSessionMarkup(text) {
return String(text || '')
.replace(/<details>/gi, '')
.replace(/<\/details>/gi, '')
.replace(/<summary>.*?<\/summary>/gis, '')
.replace(/<sub>.*?<\/sub>/gis, '')
.replace(/^---$/gm, '')
.trim();
}
function extractCodeFences(text) {
const fences = [];
const regex = /(^|\n)(`{3,})([^\n]*)\n([\s\S]*?)\n\2(?=\n|$)/g;
let match = regex.exec(text);
while (match) {
fences.push({
fence: match[2],
language: (match[3] || '').trim(),
content: match[4]
});
match = regex.exec(text);
}
return fences;
}
function normalizeCodeLanguage(language) {
const value = String(language || '').trim().toLowerCase();
if (!value) {
return '';
}
if (value === 'sh' || value === 'shell' || value === 'zsh') {
return 'bash';
}
if (value === 'py') {
return 'python';
}
if (value === 'patch') {
return 'diff';
}
return value;
}
function looksLikeShell(text) {
return /(^|\n)\s*(\$ |(?:cd|ls|pwd|cat|rg|sed|find|git|npm|pnpm|yarn|uv|python|python3|bash|sh|chmod|cp|mv|rm)\b)/m.test(String(text || ''));
}
function inferCodeLanguage(text, fallback = '') {
const normalizedFallback = normalizeCodeLanguage(fallback);
const source = String(text || '').trim();
if (!source) {
return normalizedFallback || 'plaintext';
}
if (normalizedFallback) {
return normalizedFallback;
}
if (/^\*\*\* Begin Patch/m.test(source) || /^\*\*\* (Add|Update|Delete) File:/m.test(source)) {
return 'diff';
}
if (looksLikePython(source)) {
return 'python';
}
if (safeJsonParse(source) !== null) {
return 'json';
}
if (looksLikeShell(source)) {
return 'bash';
}
return 'plaintext';
}
function makeTextSection(text, label = '') {
return { type: 'text', label, text: String(text || '').trim() };
}
function makeCodeSection(text, language = '', label = '') {
return {
type: 'code',
label,
language: inferCodeLanguage(text, language),
text: String(text || '').trim()
};
}
function buildRichTextSections(text, options = {}) {
const source = String(text || '').trim();
if (!source) {
return [];
}
if (options.forceCode) {
return [makeCodeSection(source, options.language || '', options.codeLabel || 'Code')];
}
const sections = [];
const regex = /(^|\n)(`{3,})([^\n]*)\n([\s\S]*?)\n\2(?=\n|$)/g;
let cursor = 0;
let match = regex.exec(source);
while (match) {
const start = match.index + match[1].length;
const before = source.slice(cursor, start).trim();
if (before) {
sections.push(makeTextSection(before, sections.length ? '' : (options.textLabel || '')));
}
const language = normalizeCodeLanguage(match[3]);
const content = String(match[4] || '').trim();
if (content) {
sections.push(makeCodeSection(content, language || options.language || '', options.codeLabel || 'Code'));
}
cursor = regex.lastIndex;
match = regex.exec(source);
}
const tail = source.slice(cursor).trim();
if (tail) {
sections.push(makeTextSection(tail, sections.length ? '' : (options.textLabel || '')));
}
if (!sections.length) {
if (options.detectCode && (options.language || looksLikePython(source) || looksLikeShell(source) || safeJsonParse(source) !== null)) {
return [makeCodeSection(source, options.language || '', options.codeLabel || 'Code')];
}
return [makeTextSection(source, options.textLabel || '')];
}
return sections;
}
function buildCommandSections(command, channel, outputText = '', outputLabel = 'Output') {
const sections = [];
const commandText = String(command || '').trim();
const resultText = String(outputText || '').trim();
if (commandText) {
sections.push(makeCodeSection(commandText, inferCodeLanguage(commandText, channel), 'Command'));
}
if (resultText) {
sections.push(makeCodeSection(resultText, inferCodeLanguage(resultText), outputLabel));
}
return sections;
}
function createDetailEntry({
id,
kind,
kindLabel,
summary = '',
timeLabel = 'n/a',
chips = [],
sections = []
}) {
return {
id,
kind,
kindLabel: kindLabel || kind.replace(/_/g, ' '),
summary,
timeLabel,
chips: uniqueStrings(chips),
sections: sections.filter((section) => section && String(section.text || '').trim())
};
}
function extractBoldSummary(text) {
const match = String(text || '').match(/\*\*([^*\n][\s\S]*?)\*\*/);
return match ? shortText(match[1].trim(), 140) : '';
}
function extractPatchTargets(patchText) {
const targets = [];
const regex = /^\*\*\*\s+(Add|Update|Delete) File:\s+(.+)$/gm;
let match = regex.exec(String(patchText || ''));
while (match) {
targets.push({ action: match[1], path: match[2].trim() });
match = regex.exec(String(patchText || ''));
}
return targets;
}
function summarizePatchTargets(targets) {
if (!targets.length) {
return 'apply_patch';
}
const first = targets[0];
const suffix = targets.length > 1 ? ' +' + (targets.length - 1) + ' more' : '';
return 'apply_patch ' + first.action + ' ' + first.path + suffix;
}
function extractLargeOutputPath(text) {
const match = String(text || '').match(/Saved to:\s*(\/\S+)/);
return match ? match[1] : '';
}
function extractSessionMetadata(text) {
const source = String(text || '');
const readValue = (label) => {
const match = source.match(new RegExp('^> - \\*\\*' + label + ':\\*\\*\\s+(.+?)\\s*$', 'm'));
return match ? match[1].replace(/`/g, '').trim() : '';
};
return {
sessionId: readValue('Session ID'),
started: readValue('Started'),
duration: readValue('Duration'),
exported: readValue('Exported')
};
}
function looksLikePython(text) {
return /(from\s+[A-Za-z0-9_\.]+\s+import\s+|import\s+[A-Za-z0-9_\.]+|async\s+def\s+|def\s+[A-Za-z_][A-Za-z0-9_]*\s*\(|class\s+[A-Za-z_][A-Za-z0-9_]*\s*[(:]|asyncio\.run\(|await\s+[A-Za-z_][A-Za-z0-9_\.]*\(|if\s+__name__\s*==\s*['"]__main__['"])/m.test(String(text || ''));
}
function extractPythonHeredocs(text) {
const blocks = [];
const regex = /(?:^|\n)[^\n]*\bpython(?:3(?:\.\d+)?)?\s+-\s*<<['"]?([A-Za-z_][A-Za-z0-9_]*)['"]?\n([\s\S]*?)\n\1(?=\n|$)/g;
let match = regex.exec(String(text || ''));
while (match) {
const body = String(match[2] || '').trim();
if (body && looksLikePython(body)) {
blocks.push({ title: 'python heredoc', text: body, sourceLabel: 'python heredoc' });
}
match = regex.exec(String(text || ''));
}
return blocks;
}
function extractPythonFromPatch(text) {
const lines = String(text || '').split(/\r?\n/);
const blocks = [];
let active = null;
const flush = () => {
if (!active) {
return;
}
const body = active.lines.join('\n').replace(/^\n+|\n+$/g, '');
if (body && looksLikePython(body)) {
blocks.push({
title: active.path,
text: body,
sourceLabel: active.action.toLowerCase() + ' patch'
});
}
active = null;
};
lines.forEach((line) => {
const fileMatch = line.match(/^\*\*\*\s+(Add|Update|Delete) File:\s+(.+)$/);
if (fileMatch) {
flush();
const action = fileMatch[1];
const path = fileMatch[2].trim();
active = path.endsWith('.py') && action !== 'Delete' ? { action, path, lines: [] } : null;
return;
}
if (!active) {
return;
}
if (/^\*\*\* End Patch$/.test(line)) {
flush();
return;
}
if (line.startsWith('@@') || line.startsWith('-')) {
return;
}
if (line.startsWith('+')) {
active.lines.push(line.slice(1));
return;
}
active.lines.push(line);
});
flush();
return blocks;
}
function extractPythonBlocks(text) {
const blocks = [];
const rawText = String(text || '').trim();
extractCodeFences(text).forEach((fence) => {
const language = String(fence.language || '').toLowerCase();
const content = String(fence.content || '').trim();
if (!content) {
return;
}
if (language === 'python' || language === 'py' || (!language && looksLikePython(content))) {
blocks.push({ title: 'python fence', text: content, sourceLabel: language || 'python fence' });
}
});
blocks.push(...extractPythonHeredocs(text));
blocks.push(...extractPythonFromPatch(text));
if (rawText && looksLikePython(rawText)) {
blocks.push({ title: 'python snippet', text: rawText, sourceLabel: 'python snippet' });
}
const seen = new Set();
return blocks.filter((block) => {
const key = String(block.text || '').trim();
if (!key || seen.has(key)) {
return false;
}
seen.add(key);
return true;
});
}
function uniqueStrings(values) {
return Array.from(new Set((values || []).filter(Boolean)));
}
function appendPythonScripts(target, seen, text, options = {}) {
extractPythonBlocks(text).forEach((block) => {
const scriptText = String(block.text || '').trim();
if (!scriptText || seen.has(scriptText)) {
return;
}
seen.add(scriptText);
const order = target.length + 1;
target.push({
id: (options.idPrefix || 'python-script') + '-' + order,
order,
title: block.title || options.title || 'Python script ' + order,
text: scriptText,
sections: [makeCodeSection(scriptText, 'python', 'Python')],
timeLabel: options.timeLabel || 'n/a',
sourceLabel: options.sourceLabel || block.sourceLabel || '',
chips: uniqueStrings([options.sourceLabel || block.sourceLabel || 'python'].concat(options.chips || []))
});
});
}
function extractActionPayload(record, action = null) {
const source = record && typeof record === 'object' ? record : {};
const actionSource = action && typeof action === 'object' ? action : {};
const bashCommand = String(source.bash_command || actionSource.bash_command || '').trim();
if (bashCommand) {
return {
text: bashCommand,
field: 'bash_command',
channel: 'bash_command',
estimateLabel: 'bash_command fields'
};
}
const pythonCode = String(source.python_code || actionSource.python_code || '').trim();
if (pythonCode) {
return {
text: pythonCode,
field: 'python_code',
channel: 'python_code',
estimateLabel: 'python_code fields'
};
}
const genericCommand = String(source.command || actionSource.command || '').trim();
if (genericCommand) {
const looksPython = looksLikePython(genericCommand);
return {
text: genericCommand,
field: 'command',
channel: looksPython ? 'python_code' : 'command',
estimateLabel: looksPython ? 'python_code fields' : 'command fields'
};
}
return {
text: '',
field: '',
channel: '',
estimateLabel: ''
};
}
function extractModelToolPayload(args, fallbackCommand) {
if (typeof args === 'string' && args.trim()) {
return args.trim();
}
if (!args || typeof args !== 'object') {
return String(fallbackCommand || '').trim();
}
for (const key of ['input', 'cmd', 'command', 'bash_command']) {
if (typeof args[key] === 'string' && args[key].trim()) {
return args[key].trim();
}
}
const picked = {};
['query', 'path', 'pattern', 'skill', 'filePath', 'old_path', 'new_path'].forEach((key) => {
if (typeof args[key] === 'string' && args[key].trim()) {
picked[key] = args[key].trim();
}
});
if (Array.isArray(args.urls) && args.urls.length) {
picked.urls = args.urls;
}
return Object.keys(picked).length ? JSON.stringify(picked, null, 2) : String(fallbackCommand || '').trim();
}
function extractFirstPath(text) {
const match = String(text || '').match(/\/(?:[^\s`*]|\\ )+/);
return match ? match[0].replace(/\\ /g, ' ') : '';
}
function parseMarkdownSections(text) {
const lines = String(text || '').split(/\r?\n/);
const sections = [];
let pendingTime = '';
let index = 0;
while (index < lines.length) {
const line = lines[index];
const timeMatch = line.match(/^<sub>.*?⏱️\s*([^<]+)<\/sub>$/);
if (timeMatch) {
pendingTime = timeMatch[1].trim();
index += 1;
continue;
}
const headingMatch = line.match(/^###\s+(️ Info|👤 User|💬 Copilot|✅\s+`([^`]+)`)\s*$/);
if (!headingMatch) {
index += 1;
continue;
}
const tool = headingMatch[2] || '';
let type = 'info';
if (tool) {
type = 'tool';
} else if (/User/.test(headingMatch[1])) {
type = 'user';
} else if (/Copilot/.test(headingMatch[1])) {
type = 'copilot';
}
let cursor = index + 1;
const bodyLines = [];
while (cursor < lines.length) {
if (lines[cursor].match(/^<sub>.*?⏱️/)) {
break;
}
if (lines[cursor].match(/^###\s+(️ Info|👤 User|💬 Copilot|✅\s+`[^`]+`)\s*$/)) {
break;
}
bodyLines.push(lines[cursor]);
cursor += 1;
}
sections.push({ type, tool, timeLabel: pendingTime || 'n/a', body: bodyLines.join('\n').trim() });
pendingTime = '';
index = cursor;
}
return sections;
}
function parseSessionToolSection(section, side, commandOrder, eventOrder) {
const body = section.body || '';
const fences = extractCodeFences(body);
const plainBody = stripSessionMarkup(body);
const title = extractBoldSummary(plainBody);
const argsFence = fences.find((fence) => fence.language === 'json');
const parsedArgs = argsFence ? safeJsonParse(argsFence.content.trim()) : null;
let command = '';
let output = '';
let exitCode = null;
let extraChips = [];
let payloadText = '';
if (section.tool === 'bash') {
const commandFence = fences.find((fence) => /^\$\s/m.test(fence.content));
const outputFenceIndex = commandFence ? fences.indexOf(commandFence) + 1 : -1;
command = commandFence ? commandFence.content.replace(/^\$\s?/, '').trim() : '';
output = outputFenceIndex > 0 && fences[outputFenceIndex] ? fences[outputFenceIndex].content.trim() : plainBody;
exitCode = parseExitCode(output);
const savedPath = extractLargeOutputPath(output);
if (title) {
extraChips.push(title);
}
if (savedPath) {
extraChips.push('saved output');
}
payloadText = command;
} else if (section.tool === 'apply_patch') {
const patchValue = typeof parsedArgs === 'string' ? parsedArgs : parsedArgs && typeof parsedArgs.input === 'string' ? parsedArgs.input : '';
const patchTargets = extractPatchTargets(patchValue);
command = patchValue ? ('apply_patch\n' + patchValue.trim()) : 'apply_patch';
output = fences.length > 1 ? fences[fences.length - 1].content.trim() : plainBody;
extraChips = [summarizePatchTargets(patchTargets)].concat(patchTargets.slice(0, 3).map((target) => target.action.toLowerCase()));
payloadText = patchValue;
} else if (section.tool === 'view') {
const path = extractFirstPath(plainBody);
command = path ? 'view ' + path : 'view';
output = fences.length ? fences[fences.length - 1].content.trim() : plainBody;
if (title) {
extraChips.push(title);
}
payloadText = extractModelToolPayload(parsedArgs, command);
} else if (section.tool === 'glob') {
const pattern = fences.length ? fences[0].content.trim() : shortText(plainBody, 120);
command = pattern ? 'glob ' + pattern : 'glob';
output = fences.length > 1 ? fences[1].content.trim() : plainBody;
if (title) {
extraChips.push(title);
}
payloadText = extractModelToolPayload(parsedArgs, command);
} else if (section.tool === 'skill') {
const argsText = parsedArgs ? JSON.stringify(parsedArgs) : shortText(plainBody, 160);
command = 'skill ' + shortText(argsText, 80);
output = fences.length ? fences[fences.length - 1].content.trim() : plainBody;
if (title) {
extraChips.push(title);
}
payloadText = extractModelToolPayload(parsedArgs, command);
} else {
const path = extractFirstPath(plainBody);
command = section.tool + (path ? ' ' + path : '');
output = fences.length ? fences[fences.length - 1].content.trim() : plainBody;
if (title) {
extraChips.push(title);
}
payloadText = extractModelToolPayload(parsedArgs, command);
}
if (!payloadText) {
payloadText = extractModelToolPayload(parsedArgs, command);
}
if (title && section.tool === 'apply_patch') {
output = (title + '\n' + output).trim();
}
const state = statusFromExit(exitCode, '', output, false);
const pythonScripts = [];
appendPythonScripts(pythonScripts, new Set(), payloadText, {
idPrefix: side + '-session-python-' + commandOrder,
timeLabel: section.timeLabel,
sourceLabel: section.tool,
chips: [section.tool]
});
return {
run: {
id: side + '-session-command-' + commandOrder,
order: commandOrder,
command,
primaryCommand: primaryCommand(command || section.tool),
channel: section.tool,
timeLabel: section.timeLabel,
exitCode,
outputPreview: shortMultiline(output, 520),
outputText: output,
status: state.label,
statusClass: state.css,
sections: buildCommandSections(command, section.tool, output)
},
events: [
{
id: side + '-session-event-' + eventOrder,
kind: 'tool_call',
summary: section.tool === 'apply_patch' ? (command || plainBody) : shortMultiline(command || plainBody, 460),
timeLabel: section.timeLabel,
chips: [section.tool, primaryCommand(command || section.tool)].concat(extraChips)
},
{
id: side + '-session-event-' + (eventOrder + 1),
kind: 'tool_result',
summary: shortMultiline(output || plainBody, 460),
timeLabel: section.timeLabel,
chips: [section.tool].concat(exitCode !== null ? ['exit ' + exitCode] : []).concat(extractLargeOutputPath(output) ? ['saved output'] : [])
}
],
detailEntries: [
createDetailEntry({
id: side + '-session-detail-tool-' + commandOrder,
kind: 'tool',
kindLabel: 'Tool',
summary: section.tool === 'apply_patch'
? (command || plainBody)
: shortMultiline(command || output || plainBody, 460),
timeLabel: section.timeLabel,
chips: [section.tool, primaryCommand(command || section.tool)]
.concat(extraChips)
.concat(exitCode !== null ? ['exit ' + exitCode] : [])
.concat(extractLargeOutputPath(output) ? ['saved output'] : []),
sections: buildCommandSections(command || payloadText, section.tool, output || plainBody)
})
],
estimateParts: [
title ? { label: 'tool titles', text: title } : null,
parsedArgs ? { label: 'tool arguments', text: typeof parsedArgs === 'string' ? parsedArgs : JSON.stringify(parsedArgs, null, 2) } : null,
command ? { label: 'tool commands', text: command } : null,
output ? { label: 'tool outputs', text: output } : null
].filter(Boolean),
pythonScripts
};
}
function detectFormat(text) {
const source = String(text || '').trim();
if (!source) {
return null;
}
if (/^#\s+🤖\s+Copilot CLI Session/m.test(source) || /^###\s+💬\s+Copilot/m.test(source)) {
return 'copilot_session_md';
}
if (source[0] === '{' || source[0] === '[') {
const parsed = safeJsonParse(source);
if (parsed && typeof parsed === 'object' && Array.isArray(parsed.messages) && parsed.info) {
return 'trajectory_json';
}
}
const lines = source.split(/\r?\n/).filter(Boolean);
if (!lines.length) {
return null;
}
const first = safeJsonParse(lines[0]);
if (!first || typeof first !== 'object') {
return null;
}
if (first.timestamp && first.type) {
return 'codex_jsonl';
}
if (first.event === 'raw_text' && typeof first.raw_text === 'string') {
return 'raw_response_jsonl';
}
return null;
}
function finalizeTrace(base) {
const commandFamilies = toBarRows(countBy(base.commandRuns, (run) => run.primaryCommand));
const defaultChannelFamilies = toBarRows(countBy(base.events, (event) => event.kind));
const exactTokens = base.exactTokens || null;
const tokenSourceLabel = base.tokenSourceLabel || 'Estimated from extracted trace text.';
return {
side: base.side,
kind: base.kind,
formatLabel: base.formatLabel,
description: base.description,
fileName: base.fileName,
filePath: base.filePath,
taskPrompt: base.taskPrompt,
events: base.events,
thoughtEntries: base.thoughtEntries,
commandRuns: base.commandRuns,
finalResponses: base.finalResponses,
pythonScripts: base.pythonScripts || [],
detailEntries: base.detailEntries || [],
metrics: base.metrics,
commandFamilies,
channelFamilies: base.channelFamilies || defaultChannelFamilies,
exactTokens,
baseExactTokens: exactTokens,
tokenRows: exactTokens ? tokenRowsFromTotals(exactTokens) : [{ label: 'availability', value: 'estimate pending' }],
tokenSourceLabel,
baseTokenSourceLabel: tokenSourceLabel,
tokenModeLabel: exactTokens ? 'exact' : 'estimate',
tokenEstimateParts: mergeEstimateParts(base.tokenEstimateParts || []),
countedFieldLabels: base.countedFieldLabels || [],
tokenBasisRows: [],
tokenEstimateTotal: null,
tokenEstimateError: '',
tokenSelectionLabel: '',
companionFileName: '',
summary: {
thoughts: base.thoughtEntries.length,
commands: base.commandRuns.length,
commandRuns: base.commandRuns.length,
pythonScripts: (base.pythonScripts || []).length,
finalResponses: base.finalResponses.length,
events: base.events.length
}
};
}
function normalizeCodex(text, fileName, filePath, side) {
const lines = text.split(/\r?\n/).filter(Boolean);
const entries = [];
const callById = new Map();
const runByCallId = new Map();
const runBySession = new Map();
const commandRuns = [];
const thoughtEntries = [];
const finalResponses = [];
const pythonScripts = [];
const pythonSeen = new Set();
const events = [];
const detailEntries = [];
const estimateParts = [];
let latestTokens = null;
let thoughtOrder = 0;
let commandOrder = 0;
let userPrompt = '';
lines.forEach((line) => {
const item = safeJsonParse(line);
if (!item || typeof item !== 'object') {
return;
}
entries.push(item);
if (item.type === 'event_msg' && item.payload && item.payload.type === 'token_count') {
latestTokens = item.payload.info ? item.payload.info.total_token_usage : null;
}
});
entries.forEach((item, index) => {
const id = side + '-codex-' + index;
const timeLabel = formatTimestamp(item.timestamp || '');
if (item.type === 'event_msg' && item.payload && item.payload.type === 'user_message') {
const summary = shortMultiline(item.payload.message, 360);
if (!userPrompt) {
userPrompt = summary;
}
estimateParts.push({ label: 'user messages', text: String(item.payload.message || '') });
events.push({ id, kind: 'user_message', summary, timeLabel, chips: ['user'] });
detailEntries.push(createDetailEntry({
id: id + '-detail',
kind: 'user_message',
kindLabel: 'User',
summary,
timeLabel,
chips: ['user'],
sections: buildRichTextSections(item.payload.message, { textLabel: 'Prompt' })
}));
return;
}
if (item.type === 'event_msg' && item.payload && item.payload.type === 'agent_message') {
thoughtOrder += 1;
const textBody = shortMultiline(item.payload.message, 520);
thoughtEntries.push({
id,
order: thoughtOrder,
text: textBody,
timeLabel,
chips: [item.payload.phase || 'commentary'],
sections: buildRichTextSections(item.payload.message, { textLabel: 'Reasoning', detectCode: true })
});
estimateParts.push({ label: 'agent messages', text: String(item.payload.message || '') });
appendPythonScripts(pythonScripts, pythonSeen, item.payload.message, {
idPrefix: side + '-codex-python',
timeLabel,
sourceLabel: 'agent message',
chips: [item.payload.phase || 'commentary']
});
events.push({ id, kind: 'thought', summary: textBody, timeLabel, chips: [item.payload.phase || 'commentary'] });
detailEntries.push(createDetailEntry({
id: id + '-detail',
kind: 'thought',
kindLabel: 'Thought',
summary: textBody,
timeLabel,
chips: [item.payload.phase || 'commentary'],
sections: buildRichTextSections(item.payload.message, { textLabel: 'Reasoning', detectCode: true })
}));
if (/final|answer|result/i.test(item.payload.phase || '') && textBody) {
finalResponses.push({ id: id + '-final', order: finalResponses.length + 1, text: textBody, timeLabel });
}
return;
}
if (item.type === 'event_msg' && item.payload && item.payload.type === 'token_count') {
const total = item.payload.info && item.payload.info.total_token_usage ? item.payload.info.total_token_usage : {};
events.push({
id,
kind: 'token_snapshot',
summary: 'total=' + numberFormat(total.total_tokens) + ', input=' + numberFormat(total.input_tokens) + ', output=' + numberFormat(total.output_tokens),
timeLabel,
chips: ['tokens']
});
return;
}
if (item.type === 'response_item' && item.payload && item.payload.type === 'function_call') {
const args = parseArguments(item.payload.arguments);
const tool = item.payload.name || 'unknown';
const command = args.cmd || args.command || args.bash_command || '';
const payloadText = extractModelToolPayload(args, command);
callById.set(item.payload.call_id, { tool, args, command });
if (command) {
commandOrder += 1;
const run = {
id: item.payload.call_id,
order: commandOrder,
command,
primaryCommand: primaryCommand(command),
channel: tool,
timeLabel,
exitCode: null,
outputPreview: '',
sessionId: '',
status: 'started',
statusClass: '',
outputText: '',
sections: buildCommandSections(command, tool)
};
commandRuns.push(run);
runByCallId.set(item.payload.call_id, run);
}
if (payloadText) {
estimateParts.push({ label: 'model-emitted tool payloads', text: payloadText });
appendPythonScripts(pythonScripts, pythonSeen, payloadText, {
idPrefix: side + '-codex-python',
timeLabel,
sourceLabel: tool,
chips: [tool]
});
}
events.push({
id,
kind: 'tool_call',
summary: command ? shortMultiline(command, 420) : shortText(JSON.stringify(args), 240),
timeLabel,
chips: command ? [tool, primaryCommand(command)] : [tool]
});
detailEntries.push(createDetailEntry({
id: id + '-detail',
kind: 'tool_call',
kindLabel: 'Tool Call',
summary: command ? shortMultiline(command, 420) : shortText(JSON.stringify(args), 240),
timeLabel,
chips: command ? [tool, primaryCommand(command)] : [tool],
sections: command
? buildCommandSections(command, tool)
: [makeCodeSection(JSON.stringify(args, null, 2), 'json', 'Arguments')]
}));
return;
}
if (item.type === 'response_item' && item.payload && item.payload.type === 'function_call_output') {
const call = callById.get(item.payload.call_id) || {};
const outputText = Array.isArray(item.payload.output) ? JSON.stringify(item.payload.output) : String(item.payload.output || '');
const exitCode = parseExitCode(outputText);
const sessionId = parseSessionId(outputText);
let run = runByCallId.get(item.payload.call_id) || null;
if (!run && call.tool === 'write_stdin' && call.args && call.args.session_id) {
run = runBySession.get(String(call.args.session_id)) || null;
}
if (run) {
if (sessionId && !run.sessionId) {
run.sessionId = sessionId;
runBySession.set(sessionId, run);
}
if (exitCode !== null) {
run.exitCode = exitCode;
}
if (outputText) {
run.outputPreview = shortMultiline(outputText, 520);
run.outputText = outputText;
run.sections = buildCommandSections(run.command, run.channel, outputText);
}
const state = statusFromExit(run.exitCode, sessionId, outputText, false);
run.status = state.label;
run.statusClass = state.css;
}
if (outputText) {
estimateParts.push({ label: 'tool outputs', text: outputText });
}
events.push({
id,
kind: 'tool_result',
summary: shortMultiline(outputText, 460),
timeLabel,
chips: [call.tool || 'tool_result'].concat(exitCode !== null ? ['exit ' + exitCode] : sessionId ? ['session ' + sessionId] : [])
});
detailEntries.push(createDetailEntry({
id: id + '-detail',
kind: 'tool_result',
kindLabel: 'Tool Result',
summary: shortMultiline(outputText, 460),
timeLabel,
chips: [call.tool || 'tool_result'].concat(exitCode !== null ? ['exit ' + exitCode] : sessionId ? ['session ' + sessionId] : []),
sections: [makeCodeSection(outputText, inferCodeLanguage(outputText), 'Output')]
}));
}
});
commandRuns.forEach((run) => {
if (run.status === 'started') {
const state = statusFromExit(run.exitCode, '', run.outputPreview, false);
run.status = state.label;
run.statusClass = state.css;
}
});
const channelFamilies = toBarRows([
{ label: 'thought entries', value: thoughtEntries.length },
{ label: 'command runs', value: commandRuns.length },
{ label: 'tool results', value: events.filter((event) => event.kind === 'tool_result').length },
{ label: 'token snapshots', value: events.filter((event) => event.kind === 'token_snapshot').length },
{ label: 'final responses', value: finalResponses.length }
]);
return finalizeTrace({
side,
kind: 'codex_jsonl',
formatLabel: 'Codex JSONL',
description: 'Codex CLI session JSONL with user, agent, tool, and token events.',
fileName,
filePath,
taskPrompt: userPrompt,
events,
thoughtEntries,
commandRuns,
finalResponses,
pythonScripts,
detailEntries,
exactTokens: latestTokens,
tokenSourceLabel: latestTokens
? 'Exact tokens from token_count snapshot.'
: 'Estimated from extracted trace text.',
tokenEstimateParts: estimateParts,
countedFieldLabels: [
'event_msg.user_message.message',
'event_msg.agent_message.message',
'response_item.function_call.arguments.(cmd|command|bash_command)',
'response_item.function_call_output.output'
],
metrics: [
{ label: 'Events', value: numberFormat(events.length), sub: 'normalized event records' },
{ label: 'Thoughts', value: numberFormat(thoughtEntries.length), sub: 'agent commentary entries' },
{ label: 'Commands', value: numberFormat(commandRuns.length), sub: 'shell commands reconstructed from tool calls' },
{ label: 'Token source', value: latestTokens ? 'exact' : 'estimate', sub: latestTokens ? 'token_count snapshot present' : 'fallback to tokenizer estimate' }
],
channelFamilies
});
}
function normalizeRawResponses(text, fileName, filePath, side) {
const lines = text.split(/\r?\n/).filter(Boolean);
const events = [];
const thoughtEntries = [];
const commandRuns = [];
const finalResponses = [];
const pythonScripts = [];
const pythonSeen = new Set();
const detailEntries = [];
const estimateParts = [];
let thoughtOrder = 0;
let commandOrder = 0;
let finalOrder = 0;
let extractedFrames = 0;
let emptyFrames = 0;
let taskPrompt = '';
lines.forEach((line, lineIndex) => {
const outer = safeJsonParse(line);
if (!outer || typeof outer !== 'object') {
return;
}
const timeLabel = formatTimestamp(outer.timestamp || '');
if (outer.event !== 'raw_text') {
return;
}
const frames = splitConcatenatedJsonObjects(outer.raw_text);
frames.forEach((frame, frameIndex) => {
const idBase = side + '-raw-' + lineIndex + '-' + frameIndex;
const actionPayload = extractActionPayload(frame);
const hasThought = !!String(frame.thought || '').trim();
const hasCommand = !!actionPayload.text;
const hasFinal = !!String(frame.final_response || '').trim();
const frameSections = [];
const frameChips = ['attempt ' + (outer.attempt || 'n/a')];
if (!hasThought && !hasCommand && !hasFinal) {
emptyFrames += 1;
return;
}
extractedFrames += 1;
if (hasThought) {
thoughtOrder += 1;
const textBody = shortMultiline(frame.thought, 520);
thoughtEntries.push({
id: idBase + '-thought',
order: thoughtOrder,
text: textBody,
timeLabel,
chips: ['attempt ' + (outer.attempt || 'n/a')],
sections: buildRichTextSections(frame.thought, { textLabel: 'Reasoning', detectCode: true })
});
estimateParts.push({ label: 'thought fields', text: String(frame.thought || '') });
appendPythonScripts(pythonScripts, pythonSeen, frame.thought, {
idPrefix: side + '-raw-python',
timeLabel,
sourceLabel: 'thought field',
chips: ['attempt ' + (outer.attempt || 'n/a')]
});
events.push({ id: idBase + '-thought-event', kind: 'thought', summary: textBody, timeLabel, chips: ['attempt ' + (outer.attempt || 'n/a')] });
if (!taskPrompt && /task|search|goal/i.test(textBody)) {
taskPrompt = textBody;
}
frameSections.push(...buildRichTextSections(frame.thought, { textLabel: 'Reasoning', detectCode: true }));
}
if (hasCommand) {
commandOrder += 1;
const command = actionPayload.text;
const state = statusFromExit(null, '', '', frame.done === true);
commandRuns.push({
id: idBase + '-command',
order: commandOrder,
command,
primaryCommand: primaryCommand(command),
channel: actionPayload.channel,
timeLabel,
exitCode: null,
outputPreview: hasFinal ? shortMultiline(frame.final_response, 320) : '',
outputText: hasFinal ? String(frame.final_response || '').trim() : '',
status: state.label,
statusClass: state.css,
sections: buildCommandSections(command, actionPayload.channel, hasFinal ? String(frame.final_response || '') : '', hasFinal ? 'Result' : 'Output')
});
estimateParts.push({ label: actionPayload.estimateLabel, text: command });
appendPythonScripts(pythonScripts, pythonSeen, command, {
idPrefix: side + '-raw-python',
timeLabel,
sourceLabel: actionPayload.field || actionPayload.channel || 'command',
chips: ['attempt ' + (outer.attempt || 'n/a')]
});
events.push({ id: idBase + '-command-event', kind: 'command', summary: shortMultiline(command, 460), timeLabel, chips: ['attempt ' + (outer.attempt || 'n/a'), primaryCommand(command)] });
frameSections.push(makeCodeSection(command, actionPayload.channel, 'Command'));
frameChips.push(primaryCommand(command));
}
if (hasFinal) {
finalOrder += 1;
const responseText = shortMultiline(frame.final_response, 520);
finalResponses.push({ id: idBase + '-final', order: finalOrder, text: responseText, timeLabel });
estimateParts.push({ label: 'final_response fields', text: String(frame.final_response || '') });
appendPythonScripts(pythonScripts, pythonSeen, frame.final_response, {
idPrefix: side + '-raw-python',
timeLabel,
sourceLabel: 'final_response',
chips: ['done=' + Boolean(frame.done)]
});
events.push({ id: idBase + '-final-event', kind: 'final_response', summary: responseText, timeLabel, chips: ['done=' + Boolean(frame.done)] });
frameSections.push(...buildRichTextSections(frame.final_response, { textLabel: 'Response', detectCode: true }));
frameChips.push('done=' + Boolean(frame.done));
}
detailEntries.push(createDetailEntry({
id: idBase + '-frame-detail',
kind: 'frame',
kindLabel: 'Step',
summary: shortMultiline(
actionPayload.text || String(frame.final_response || '') || String(frame.thought || ''),
460
),
timeLabel,
chips: frameChips,
sections: frameSections
}));
});
});
const channelFamilies = toBarRows([
{ label: 'thought entries', value: thoughtEntries.length },
{ label: 'command frames', value: commandRuns.length },
{ label: 'final responses', value: finalResponses.length },
{ label: 'outer lines', value: lines.length },
{ label: 'empty frames dropped', value: emptyFrames }
]);
return finalizeTrace({
side,
kind: 'raw_response_jsonl',
formatLabel: 'Webwright Responses JSONL',
description: 'Webwright raw_responses.jsonl with embedded thought, bash, and final-response frames.',
fileName,
filePath,
taskPrompt,
events,
thoughtEntries,
commandRuns,
finalResponses,
pythonScripts,
detailEntries,
tokenSourceLabel: 'Estimated from extracted trace text.',
tokenEstimateParts: estimateParts,
countedFieldLabels: ['raw_text frame.thought', 'raw_text frame.bash_command', 'raw_text frame.python_code', 'raw_text frame.final_response'],
metrics: [
{ label: 'Outer lines', value: numberFormat(lines.length), sub: 'top-level raw_text records' },
{ label: 'Frames', value: numberFormat(extractedFrames), sub: 'embedded JSON frames extracted from raw_text' },
{ label: 'Thoughts', value: numberFormat(thoughtEntries.length), sub: 'inner thought strings' },
{ label: 'Commands', value: numberFormat(commandRuns.length), sub: 'inner action strings' }
],
channelFamilies
});
}
function normalizeTrajectory(text, fileName, filePath, side) {
const parsed = safeJsonParse(text);
if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.messages)) {
throw new Error('Invalid trajectory.json structure');
}
const events = [];
const thoughtEntries = [];
const commandRuns = [];
const finalResponses = [];
const pythonScripts = [];
const pythonSeen = new Set();
const detailEntries = [];
const estimateParts = [];
let latestTokens = null;
let taskPrompt = '';
let thoughtOrder = 0;
let commandOrder = 0;
let finalOrder = 0;
let pendingRun = null;
parsed.messages.forEach((message, index) => {
const id = side + '-trajectory-' + index;
const timeLabel = 'step ' + (index + 1);
const role = message.role || 'unknown';
const contentText = contentToText(message.content);
if (role === 'user') {
const observation = normalizeObservation(message);
if (observation && pendingRun) {
if (observation.command && !pendingRun.command) {
pendingRun.command = observation.command;
pendingRun.primaryCommand = primaryCommand(observation.command);
}
pendingRun.exitCode = observation.exitCode;
pendingRun.outputPreview = shortMultiline(observation.output, 520);
pendingRun.outputText = observation.output;
const state = statusFromExit(observation.exitCode, '', observation.output, false);
pendingRun.status = state.label;
pendingRun.statusClass = state.css;
pendingRun.sections = buildCommandSections(pendingRun.command, pendingRun.channel, observation.output);
estimateParts.push({ label: 'observation outputs', text: observation.output });
events.push({ id, kind: 'observation', summary: shortMultiline(observation.output, 460), timeLabel, chips: observation.exitCode !== null ? ['exit ' + observation.exitCode] : ['observation'] });
detailEntries.push(createDetailEntry({
id: id + '-observation-detail',
kind: 'observation',
kindLabel: 'Observation',
summary: shortMultiline(observation.output, 460),
timeLabel,
chips: observation.exitCode !== null ? ['exit ' + observation.exitCode] : ['observation'],
sections: [makeCodeSection(observation.output, inferCodeLanguage(observation.output), 'Output')]
}));
} else if (contentText) {
if (!taskPrompt) {
taskPrompt = shortMultiline(contentText, 360);
}
estimateParts.push({ label: 'user messages', text: contentText });
events.push({ id, kind: 'user_message', summary: shortMultiline(contentText, 420), timeLabel, chips: ['user'] });
detailEntries.push(createDetailEntry({
id: id + '-detail',
kind: 'user_message',
kindLabel: 'User',
summary: shortMultiline(contentText, 420),
timeLabel,
chips: ['user'],
sections: buildRichTextSections(contentText, { textLabel: 'Prompt' })
}));
}
return;
}
if (role !== 'assistant') {
return;
}
const extra = message.extra || {};
const raw = extra.raw_response && typeof extra.raw_response === 'object' ? extra.raw_response : {};
const usage = extra.usage && typeof extra.usage === 'object' ? (extra.usage.cumulative_response || extra.usage.last_response || extra.usage) : null;
if (usage && typeof usage.total_tokens === 'number') {
latestTokens = usage;
}
const thoughtText = String(raw.thought || contentText || '').trim();
const action = Array.isArray(extra.actions) && extra.actions.length ? extra.actions[0] : null;
const actionPayload = extractActionPayload(raw, action);
const commandText = actionPayload.text;
const finalText = String(raw.final_response || '').trim();
const done = raw.done === true || extra.done === true;
if (thoughtText) {
thoughtOrder += 1;
estimateParts.push({ label: 'assistant thoughts', text: thoughtText });
appendPythonScripts(pythonScripts, pythonSeen, thoughtText, {
idPrefix: side + '-trajectory-python',
timeLabel,
sourceLabel: 'assistant thought',
chips: [done ? 'done' : 'assistant']
});
thoughtEntries.push({
id: id + '-thought',
order: thoughtOrder,
text: shortMultiline(thoughtText, 520),
timeLabel,
chips: [done ? 'done' : 'assistant'],
sections: buildRichTextSections(thoughtText, { textLabel: 'Reasoning', detectCode: true })
});
events.push({ id: id + '-thought-event', kind: 'thought', summary: shortMultiline(thoughtText, 460), timeLabel, chips: [done ? 'done' : 'assistant'] });
detailEntries.push(createDetailEntry({
id: id + '-thought-detail',
kind: 'thought',
kindLabel: 'Thought',
summary: shortMultiline(thoughtText, 460),
timeLabel,
chips: [done ? 'done' : 'assistant'],
sections: buildRichTextSections(thoughtText, { textLabel: 'Reasoning', detectCode: true })
}));
}
if (commandText) {
commandOrder += 1;
const run = {
id: id + '-command',
order: commandOrder,
command: commandText,
primaryCommand: primaryCommand(commandText),
channel: actionPayload.channel,
timeLabel,
exitCode: null,
outputPreview: '',
outputText: '',
status: 'started',
statusClass: '',
sections: buildCommandSections(commandText, actionPayload.channel)
};
pendingRun = run;
commandRuns.push(run);
estimateParts.push({ label: actionPayload.estimateLabel || 'assistant commands', text: commandText });
appendPythonScripts(pythonScripts, pythonSeen, commandText, {
idPrefix: side + '-trajectory-python',
timeLabel,
sourceLabel: actionPayload.field || actionPayload.channel || 'assistant command',
chips: ['assistant', primaryCommand(commandText)]
});
events.push({ id: id + '-command-event', kind: 'command', summary: shortMultiline(commandText, 460), timeLabel, chips: ['assistant', primaryCommand(commandText)] });
detailEntries.push(createDetailEntry({
id: id + '-command-detail',
kind: 'command',
kindLabel: 'Command',
summary: shortMultiline(commandText, 460),
timeLabel,
chips: ['assistant', primaryCommand(commandText)],
sections: buildCommandSections(commandText, actionPayload.channel)
}));
}
if (finalText) {
finalOrder += 1;
estimateParts.push({ label: 'assistant final responses', text: finalText });
appendPythonScripts(pythonScripts, pythonSeen, finalText, {
idPrefix: side + '-trajectory-python',
timeLabel,
sourceLabel: 'assistant final response',
chips: ['done=' + done]
});
finalResponses.push({ id: id + '-final', order: finalOrder, text: shortMultiline(finalText, 520), timeLabel });
events.push({ id: id + '-final-event', kind: 'final_response', summary: shortMultiline(finalText, 460), timeLabel, chips: ['done=' + done] });
detailEntries.push(createDetailEntry({
id: id + '-final-detail',
kind: 'final_response',
kindLabel: 'Final Response',
summary: shortMultiline(finalText, 460),
timeLabel,
chips: ['done=' + done],
sections: buildRichTextSections(finalText, { textLabel: 'Response', detectCode: true })
}));
}
});
commandRuns.forEach((run) => {
if (run.status === 'started') {
const state = statusFromExit(run.exitCode, '', run.outputPreview, false);
run.status = state.label;
run.statusClass = state.css;
}
});
return finalizeTrace({
side,
kind: 'trajectory_json',
formatLabel: 'Trajectory JSON',
description: 'Webwright trajectory.json transcript with assistant actions, observations, and usage snapshots.',
fileName,
filePath,
taskPrompt,
events,
thoughtEntries,
commandRuns,
finalResponses,
pythonScripts,
detailEntries,
exactTokens: latestTokens,
tokenSourceLabel: latestTokens
? 'Exact tokens from usage snapshot.'
: 'Estimated from extracted trace text.',
tokenEstimateParts: estimateParts,
countedFieldLabels: [
'messages[].content',
'messages[].extra.raw_response.thought',
'messages[].extra.raw_response.bash_command',
'messages[].extra.raw_response.python_code',
'messages[].extra.raw_response.final_response',
'messages[].extra.observation.command_output'
],
metrics: [
{ label: 'Messages', value: numberFormat(parsed.messages.length), sub: 'top-level transcript turns' },
{ label: 'Thoughts', value: numberFormat(thoughtEntries.length), sub: 'assistant reasoning entries' },
{ label: 'Commands', value: numberFormat(commandRuns.length), sub: 'assistant action commands' },
{ label: 'API calls', value: numberFormat(parsed.info && parsed.info.api_calls), sub: 'info.api_calls from trajectory metadata' }
]
});
}
function normalizeCopilotSessionMarkdown(text, fileName, filePath, side) {
const sections = parseMarkdownSections(text);
const metadata = extractSessionMetadata(text);
const events = [];
const thoughtEntries = [];
const commandRuns = [];
const finalResponses = [];
const pythonScripts = [];
const pythonSeen = new Set();
const detailEntries = [];
const estimateParts = [];
let taskPrompt = '';
let thoughtOrder = 0;
let commandOrder = 0;
let eventOrder = 0;
if (metadata.sessionId || metadata.duration || metadata.started || metadata.exported) {
const summary = [
metadata.sessionId ? 'session ' + metadata.sessionId : '',
metadata.started ? 'started ' + metadata.started : '',
metadata.duration ? 'duration ' + metadata.duration : '',
metadata.exported ? 'exported ' + metadata.exported : ''
].filter(Boolean).join(' | ');
events.push({
id: side + '-session-meta',
kind: 'info',
summary,
timeLabel: 'session',
chips: ['session metadata']
});
eventOrder += 1;
}
sections.forEach((section) => {
if (section.type === 'user') {
const clean = stripSessionMarkup(section.body);
const textBody = shortMultiline(clean, 520);
if (!taskPrompt && textBody) {
taskPrompt = textBody;
}
if (clean) {
estimateParts.push({ label: 'user sections', text: clean });
events.push({ id: side + '-session-user-' + eventOrder, kind: 'user_message', summary: textBody, timeLabel: section.timeLabel, chips: ['user'] });
detailEntries.push(createDetailEntry({
id: side + '-session-user-detail-' + eventOrder,
kind: 'user_message',
kindLabel: 'User',
summary: textBody,
timeLabel: section.timeLabel,
chips: ['user'],
sections: buildRichTextSections(clean, { textLabel: 'Prompt', detectCode: true })
}));
eventOrder += 1;
}
return;
}
if (section.type === 'copilot') {
const clean = stripSessionMarkup(section.body);
if (!clean) {
return;
}
thoughtOrder += 1;
estimateParts.push({ label: 'copilot sections', text: clean });
appendPythonScripts(pythonScripts, pythonSeen, clean, {
idPrefix: side + '-session-python',
timeLabel: section.timeLabel,
sourceLabel: 'copilot section',
chips: ['copilot']
});
thoughtEntries.push({
id: side + '-session-thought-' + thoughtOrder,
order: thoughtOrder,
text: shortMultiline(clean, 520),
timeLabel: section.timeLabel,
chips: ['copilot'],
sections: buildRichTextSections(clean, { textLabel: 'Narrative', detectCode: true })
});
events.push({ id: side + '-session-copilot-' + eventOrder, kind: 'thought', summary: shortMultiline(clean, 460), timeLabel: section.timeLabel, chips: ['copilot'] });
detailEntries.push(createDetailEntry({
id: side + '-session-copilot-detail-' + eventOrder,
kind: 'thought',
kindLabel: 'Copilot',
summary: shortMultiline(clean, 460),
timeLabel: section.timeLabel,
chips: ['copilot'],
sections: buildRichTextSections(clean, { textLabel: 'Narrative', detectCode: true })
}));
eventOrder += 1;
if (/final|done|complete|result/i.test(clean)) {
finalResponses.push({ id: side + '-session-final-' + finalResponses.length, order: finalResponses.length + 1, text: shortMultiline(clean, 520), timeLabel: section.timeLabel });
}
return;
}
if (section.type === 'tool') {
commandOrder += 1;
const parsed = parseSessionToolSection(section, side, commandOrder, eventOrder);
commandRuns.push(parsed.run);
parsed.events.forEach((event) => events.push(event));
parsed.detailEntries.forEach((entry) => detailEntries.push(entry));
estimateParts.push(...parsed.estimateParts);
parsed.pythonScripts.forEach((script) => {
if (pythonSeen.has(script.text)) {
return;
}
pythonSeen.add(script.text);
pythonScripts.push(Object.assign({}, script, {
id: side + '-session-python-' + (pythonScripts.length + 1),
order: pythonScripts.length + 1
}));
});
eventOrder += parsed.events.length;
return;
}
const infoText = stripSessionMarkup(section.body);
if (infoText) {
events.push({ id: side + '-session-info-' + eventOrder, kind: 'info', summary: shortMultiline(infoText, 420), timeLabel: section.timeLabel, chips: ['info'] });
eventOrder += 1;
}
});
const channelFamilies = toBarRows([
{ label: 'user sections', value: events.filter((event) => event.kind === 'user_message').length },
{ label: 'copilot sections', value: thoughtEntries.length },
{ label: 'tool sections', value: commandRuns.length },
{ label: 'info sections', value: events.filter((event) => event.kind === 'info').length }
]);
return finalizeTrace({
side,
kind: 'copilot_session_md',
formatLabel: 'Copilot Session MD',
description: 'GitHub Copilot CLI markdown session export with user, copilot, and tool sections.',
fileName,
filePath,
taskPrompt,
events,
thoughtEntries,
commandRuns,
finalResponses,
pythonScripts,
detailEntries,
tokenSourceLabel: 'Estimated from extracted trace text.',
tokenEstimateParts: estimateParts,
countedFieldLabels: ['session header metadata', '### User body text', '### Copilot body text', 'tool titles', '### ✅ tool Arguments blocks', '### ✅ tool commands / outputs'],
metrics: [
{ label: 'Sections', value: numberFormat(sections.length), sub: 'top-level markdown sections' },
{ label: 'Thoughts', value: numberFormat(thoughtEntries.length), sub: 'copilot narrative blocks' },
{ label: 'Commands', value: numberFormat(commandRuns.length), sub: 'tool sections mapped to comparable command rows' },
{ label: 'Finals', value: numberFormat(finalResponses.length), sub: 'copilot blocks that look like terminal summaries' },
{ label: 'Session duration', value: metadata.duration || 'n/a', sub: metadata.started ? 'started ' + metadata.started : 'header metadata if present' }
],
channelFamilies
});
}
function normalizeTrace(text, fileName, filePath, side) {
const format = detectFormat(text);
if (!format) {
throw new Error('Supported inputs: Codex JSONL, Webwright Responses JSONL (raw_responses.jsonl), trajectory.json, and Copilot session markdown.');
}
if (format === 'codex_jsonl') {
return normalizeCodex(text, fileName, filePath, side);
}
if (format === 'raw_response_jsonl') {
return normalizeRawResponses(text, fileName, filePath, side);
}
if (format === 'trajectory_json') {
return normalizeTrajectory(text, fileName, filePath, side);
}
return normalizeCopilotSessionMarkdown(text, fileName, filePath, side);
}
const app = createApp({
data() {
return {
activeView: 'summary',
traceTab: 'python',
dragTarget: null,
left: null,
right: null,
errors: { left: '', right: '' },
attachmentErrors: { left: '', right: '' },
tokenTarget: 'model:o3',
tokenizerStatus: 'Ready.'
};
},
computed: {
slots() {
return [
{ key: 'left', title: 'Trace A', subtitle: 'Load Codex JSONL, Webwright Responses JSONL (raw_responses.jsonl), trajectory.json, or Copilot session markdown on this side.' },
{ key: 'right', title: 'Trace B', subtitle: 'Load a second trace and compare thoughts, commands, token source, and extracted outputs.' }
];
},
tokenizerOptions() {
return [
{ value: 'model:o3', label: 'o3' },
{ value: 'model:o3-mini', label: 'o3-mini' },
{ value: 'model:gpt-4o', label: 'gpt-4o' },
{ value: 'encoding:o200k_base', label: 'o200k_base' },
{ value: 'encoding:cl100k_base', label: 'cl100k_base' }
];
},
bothLoaded() {
return !!(this.left && this.right);
},
hasAnyTrace() {
return !!(this.left || this.right);
},
loadedTraces() {
return [this.left, this.right].filter(Boolean);
},
leftTrace() {
return this.left;
},
rightTrace() {
return this.right;
},
detailTabs() {
return [
{ label: 'Python Scripts', value: 'python' },
{ label: 'Thoughts', value: 'thoughts' },
{ label: 'Commands', value: 'commands' },
{ label: 'All', value: 'all' }
];
},
comparisonRows() {
if (!this.bothLoaded) {
return [];
}
return [
{ label: 'Format', left: this.left.formatLabel, right: this.right.formatLabel },
{ label: 'Thought entries', left: numberFormat(this.left.thoughtEntries.length), right: numberFormat(this.right.thoughtEntries.length) },
{ label: 'Command runs', left: numberFormat(this.left.commandRuns.length), right: numberFormat(this.right.commandRuns.length) },
{ label: 'Final responses', left: numberFormat(this.left.finalResponses.length), right: numberFormat(this.right.finalResponses.length) },
{ label: 'Python scripts', left: numberFormat(this.left.pythonScripts.length), right: numberFormat(this.right.pythonScripts.length) },
{ label: 'Token total', left: this.tokenTotalDisplay(this.left), right: this.tokenTotalDisplay(this.right) },
{ label: 'Token source', left: this.left.tokenModeLabel, right: this.right.tokenModeLabel },
{ label: 'Counted fields', left: this.left.countedFieldLabels.join(', '), right: this.right.countedFieldLabels.join(', ') },
{
label: 'Top command families',
left: this.left.commandFamilies.map((row) => row.label + ' ' + row.value).join(', '),
right: this.right.commandFamilies.map((row) => row.label + ' ' + row.value).join(', ')
},
{ label: 'Task hint', left: this.left.taskPrompt || 'n/a', right: this.right.taskPrompt || 'n/a' }
];
},
tokenizerSelectionLabel() {
const selection = getTokenizerSelection(this.tokenTarget);
return selection.mode === 'model' ? selection.name + ' -> ' + selection.encoding : selection.encoding;
}
},
watch: {
tokenTarget() {
this.refreshAllTokens();
}
},
mounted() {
this.highlightRenderedCode();
},
updated() {
this.highlightRenderedCode();
},
methods: {
codeClass(language) {
return 'language-' + (normalizeCodeLanguage(language) || 'plaintext');
},
shouldShowEntrySummary(entry) {
if (!entry || !String(entry.summary || '').trim()) {
return false;
}
if (entry.kind === 'frame' && Array.isArray(entry.sections) && entry.sections.length) {
return false;
}
const sections = Array.isArray(entry.sections) ? entry.sections : [];
if (sections.length !== 1) {
return true;
}
const onlySection = sections[0];
if (!onlySection || onlySection.type !== 'text') {
return true;
}
return shortMultiline(onlySection.text, 460) !== shortMultiline(entry.summary, 460);
},
traceBySide(side) {
return this[side];
},
errorBySide(side) {
return this.errors[side];
},
attachmentErrorBySide(side) {
return this.attachmentErrors[side];
},
clearTrace(side) {
this[side] = null;
this.errors[side] = '';
this.attachmentErrors[side] = '';
},
clearCompanion(side) {
const trace = this[side];
if (!trace || trace.kind !== 'raw_response_jsonl') {
return;
}
this.attachmentErrors[side] = '';
this[side] = stripTrajectoryCompanion(trace);
},
tokenTotalDisplay(trace) {
if (!trace) {
return 'n/a';
}
if (trace.exactTokens && typeof trace.exactTokens.total_tokens === 'number') {
return 'Exact tokens: ' + numberFormat(trace.exactTokens.total_tokens);
}
if (typeof trace.tokenEstimateTotal === 'number') {
return 'Est. output tokens: ~' + numberFormat(trace.tokenEstimateTotal);
}
if (trace.tokenEstimateError) {
return 'Est. output tokens failed';
}
return 'Estimating output tokens...';
},
tokenNote(trace) {
if (!trace) {
return '';
}
return trace.tokenSourceLabel;
},
async handleFileInput(side, event) {
const file = event.target.files && event.target.files[0];
if (!file) {
return;
}
await this.loadFile(side, file);
event.target.value = '';
},
async handleDrop(side, event) {
this.dragTarget = null;
const file = event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0];
if (!file) {
return;
}
await this.loadFile(side, file);
},
async handleCompanionInput(side, event) {
const file = event.target.files && event.target.files[0];
if (!file) {
return;
}
this.attachmentErrors[side] = '';
try {
const trace = this[side];
if (!trace || trace.kind !== 'raw_response_jsonl') {
throw new Error('Attach trajectory.json only to a Webwright Responses JSONL trace (raw_responses.jsonl).');
}
const text = await file.text();
const companion = extractTrajectoryUsage(text, file.name);
const updatedTrace = applyTrajectoryCompanion(trace, companion);
this[side] = updatedTrace;
this.enrichTraceTokens(updatedTrace);
} catch (error) {
this.attachmentErrors[side] = error && error.message ? error.message : 'Failed to attach trajectory.json';
} finally {
event.target.value = '';
}
},
async enrichTraceTokens(trace) {
const selection = getTokenizerSelection(this.tokenTarget);
trace.tokenSelectionLabel = selection.mode === 'model' ? selection.name + ' / ' + selection.encoding : selection.encoding;
trace.tokenEstimateError = '';
if (!trace.tokenEstimateParts.length) {
trace.tokenBasisRows = [];
if (!trace.exactTokens) {
trace.tokenRows = [{ label: 'availability', value: 'no text fields to estimate' }];
}
return trace;
}
const quickBasisRows = trace.tokenEstimateParts
.map((part) => ({ label: part.label, value: quickTokenEstimate(part.text) }))
.filter((row) => row.value > 0);
trace.tokenEstimateTotal = quickBasisRows.reduce((sum, row) => sum + row.value, 0);
if (!trace.exactTokens) {
trace.tokenRows = [
{ label: 'basis', value: 'estimated' },
{ label: 'encoding', value: selection.encoding + ' pending' },
{ label: 'est. output tokens', value: '~' + numberFormat(trace.tokenEstimateTotal) }
];
}
try {
this.tokenizerStatus = 'Counting tokens with ' + selection.encoding + '...';
const encoder = await loadEncoder(selection.encoding);
const basisRows = trace.tokenEstimateParts.map((part) => ({ label: part.label, value: encoder.encode(part.text).length })).filter((row) => row.value > 0);
trace.tokenBasisRows = toBarRows(basisRows, 8);
trace.tokenEstimateTotal = basisRows.reduce((sum, row) => sum + row.value, 0);
trace.tokenModeLabel = trace.exactTokens ? 'exact' : 'estimate';
trace.tokenRows = trace.exactTokens
? tokenRowsFromTotals(trace.exactTokens)
: [
{ label: 'basis', value: 'estimated' },
{ label: 'encoding', value: selection.encoding },
{ label: 'est. output tokens', value: '~' + numberFormat(trace.tokenEstimateTotal) }
];
this.tokenizerStatus = 'Ready.';
} catch (error) {
trace.tokenEstimateError = error && error.message ? error.message : 'Failed to estimate tokens';
trace.tokenBasisRows = toBarRows(quickBasisRows, 8);
if (!trace.exactTokens) {
trace.tokenRows = [
{ label: 'basis', value: 'estimated' },
{ label: 'encoding', value: 'quick fallback' },
{ label: 'est. output tokens', value: '~' + numberFormat(trace.tokenEstimateTotal) }
];
}
this.tokenizerStatus = trace.tokenEstimateError + '. Showing quick estimate.';
}
return trace;
},
async refreshAllTokens() {
const traces = [this.left, this.right].filter(Boolean);
for (const trace of traces) {
await this.enrichTraceTokens(trace);
}
},
async loadFile(side, file) {
this.errors[side] = '';
this.attachmentErrors[side] = '';
try {
const text = await file.text();
const trace = normalizeTrace(text, file.name, file.path || '', side);
this[side] = trace;
this.enrichTraceTokens(trace);
} catch (error) {
this[side] = null;
this.errors[side] = error && error.message ? error.message : 'Failed to parse file';
}
},
filteredEvents(trace) {
if (!trace) {
return [];
}
return trace.events.filter((event) => event.kind !== 'token_snapshot');
},
highlightRenderedCode() {
if (!window.hljs) {
return;
}
this.$nextTick(() => {
document.querySelectorAll('#app pre code').forEach((node) => {
if (node.dataset.highlighted === 'yes') {
delete node.dataset.highlighted;
}
window.hljs.highlightElement(node);
});
});
}
}
});
window.__traceJsonlCompareVm = app.mount('#app');