项目文件夹

文件
2026-07-13 12:28:42 +08:00

2059 行
73 KiB
JavaScript

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
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');