@agent-inspect/viewer 6.4.0 → 6.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -21,7 +21,7 @@ interface ViewerServerInfo {
21
21
  declare function createViewerServer(options?: ViewerServerOptions): Server;
22
22
  declare function startViewerServer(options?: ViewerServerOptions): Promise<ViewerServerInfo>;
23
23
 
24
- declare const viewerIndexHtml = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <title>AgentInspect Viewer</title>\n <style>\n body { font-family: system-ui, sans-serif; margin: 1.5rem; line-height: 1.4; }\n h1 { font-size: 1.25rem; }\n pre { background: #f4f4f5; padding: 1rem; overflow: auto; max-height: 50vh; }\n a { color: #0b57d0; }\n .muted { color: #666; }\n table { border-collapse: collapse; width: 100%; margin: 1rem 0; }\n th, td { border: 1px solid #ddd; padding: 0.5rem; text-align: left; vertical-align: top; }\n th { background: #f6f6f6; }\n .fail { color: #b42318; font-weight: 600; }\n .pass { color: #027a48; font-weight: 600; }\n section { margin: 1.5rem 0; }\n </style>\n</head>\n<body>\n <h1>AgentInspect local viewer</h1>\n <p class=\"muted\">Read-only. JSONL on disk remains canonical.</p>\n <p id=\"nav\"></p>\n <div id=\"content\"><pre id=\"out\">Loading\u2026</pre></div>\n <script>\n const params = new URLSearchParams(location.search);\n const mode = params.get(\"mode\") || \"traces\";\n\n function renderSuite(data) {\n const rows = data.cases.map((c) =>\n '<tr><td>' + c.id + '</td><td class=\"' + c.status + '\">' + c.status +\n '</td><td>' + (c.message || '') + '</td><td>' + c.toolPath.join(' \u2192 ') +\n '</td><td>' + c.observations.map((o) => o.name + ':' + o.status).join(', ') + '</td></tr>'\n ).join('');\n const failed = data.cases.filter((c) => c.status !== 'pass');\n const detail = failed.map((c) => {\n let html = '<h3>Case ' + c.id + '</h3><ul>';\n if (c.failureDiff) html += '<li>Diff errors: ' + c.failureDiff.summary.errors + '</li>';\n if (c.timeline) html += '<li>Timeline steps: ' + c.timeline.entries.length + '</li>';\n html += '<li>Diagnostics: ' + c.diagnostics.map((d) => d.message).join('; ') + '</li></ul>';\n return html;\n }).join('');\n return '<section><h2>Suite: ' + data.suiteName + ' (' + data.status + ')</h2>' +\n '<p>Passed ' + data.summary.passed + ', failed ' + data.summary.failed + '</p>' +\n '<table><thead><tr><th>Case</th><th>Status</th><th>Message</th><th>Tool path</th><th>Observations</th></tr></thead><tbody>' +\n rows + '</tbody></table>' +\n '<section><h2>Failure detail</h2>' + (detail || '<p class=\"pass\">No failures</p>') + '</section>' +\n '<section><h2>CI artifacts</h2><p>' + (data.ciArtifactsDir || 'n/a') + '</p></section>' +\n '<section><h2>Bundle export</h2><p>' + data.bundleExportHint + '</p></section>';\n }\n\n async function load() {\n const nav = document.getElementById(\"nav\");\n const out = document.getElementById(\"out\");\n const content = document.getElementById(\"content\");\n if (mode === \"suite\") {\n nav.innerHTML = '<a href=\"/api/suite\">/api/suite</a>';\n const data = await fetch(\"/api/suite\").then((r) => r.json());\n content.innerHTML = renderSuite(data);\n return;\n }\n if (mode === \"workspace\") {\n nav.innerHTML = '<a href=\"/api/workspace\">/api/workspace</a>';\n const data = await fetch(\"/api/workspace\").then((r) => r.json());\n content.innerHTML = '<section><h2>Workspace: ' + (data.project || 'workspace') + '</h2>' +\n '<p>Runs: ' + data.runs.length + '</p><pre>' + JSON.stringify(data, null, 2) + '</pre></section>';\n return;\n }\n nav.innerHTML = '<a href=\"/api/traces\">/api/traces</a> \u00B7 <a href=\"/api/sessions\">/api/sessions</a>';\n const data = await fetch(\"/api/traces\").then((r) => r.json());\n out.textContent = JSON.stringify(data, null, 2);\n }\n load().catch((err) => {\n document.getElementById(\"out\").textContent = String(err);\n });\n </script>\n</body>\n</html>\n";
24
+ declare const viewerIndexHtml = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <title>AgentInspect Viewer</title>\n <style>\n body { font-family: system-ui, sans-serif; margin: 1.5rem; line-height: 1.4; }\n h1 { font-size: 1.25rem; }\n pre { background: #f4f4f5; padding: 1rem; overflow: auto; max-height: 50vh; }\n a { color: #0b57d0; }\n .muted { color: #666; }\n table { border-collapse: collapse; width: 100%; margin: 1rem 0; }\n th, td { border: 1px solid #ddd; padding: 0.5rem; text-align: left; vertical-align: top; }\n th { background: #f6f6f6; }\n .fail { color: #b42318; font-weight: 600; }\n .pass { color: #027a48; font-weight: 600; }\n section { margin: 1.5rem 0; }\n </style>\n</head>\n<body>\n <h1>AgentInspect local viewer</h1>\n <p class=\"muted\">Read-only. JSONL on disk remains canonical.</p>\n <p id=\"nav\"></p>\n <div id=\"content\"><pre id=\"out\">Loading\u2026</pre></div>\n <script>\n const params = new URLSearchParams(location.search);\n const mode = params.get(\"mode\") || \"traces\";\n\n function escapeHtml(value) {\n return String(value ?? \"\")\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"'\", \"&#39;\");\n }\n\n function renderSuite(data) {\n const rows = data.cases.map((c) =>\n '<tr><td>' + escapeHtml(c.id) + '</td><td class=\"' + escapeHtml(c.status) + '\">' + escapeHtml(c.status) +\n '</td><td>' + escapeHtml(c.message || '') + '</td><td>' + escapeHtml(c.toolPath.join(' \u2192 ')) +\n '</td><td>' + escapeHtml(c.observations.map((o) => o.name + ':' + o.status).join(', ')) + '</td></tr>'\n ).join('');\n const failed = data.cases.filter((c) => c.status !== 'pass');\n const detail = failed.map((c) => {\n let html = '<h3>Case ' + escapeHtml(c.id) + '</h3><ul>';\n if (c.failureDiff) html += '<li>Diff errors: ' + escapeHtml(c.failureDiff.summary.errors) + '</li>';\n if (c.timeline) html += '<li>Timeline steps: ' + escapeHtml(c.timeline.entries.length) + '</li>';\n html += '<li>Diagnostics: ' + escapeHtml(c.diagnostics.map((d) => d.message).join('; ')) + '</li></ul>';\n return html;\n }).join('');\n return '<section><h2>Suite: ' + escapeHtml(data.suiteName) + ' (' + escapeHtml(data.status) + ')</h2>' +\n '<p>Passed ' + escapeHtml(data.summary.passed) + ', failed ' + escapeHtml(data.summary.failed) + '</p>' +\n '<table><thead><tr><th>Case</th><th>Status</th><th>Message</th><th>Tool path</th><th>Observations</th></tr></thead><tbody>' +\n rows + '</tbody></table>' +\n '<section><h2>Failure detail</h2>' + (detail || '<p class=\"pass\">No failures</p>') + '</section>' +\n '<section><h2>CI artifacts</h2><p>' + escapeHtml(data.ciArtifactsDir || 'n/a') + '</p></section>' +\n '<section><h2>Bundle export</h2><p>' + escapeHtml(data.bundleExportHint) + '</p></section>';\n }\n\n async function load() {\n const nav = document.getElementById(\"nav\");\n const out = document.getElementById(\"out\");\n const content = document.getElementById(\"content\");\n if (mode === \"suite\") {\n nav.innerHTML = '<a href=\"/api/suite\">/api/suite</a>';\n const data = await fetch(\"/api/suite\").then((r) => r.json());\n content.innerHTML = renderSuite(data);\n return;\n }\n if (mode === \"workspace\") {\n nav.innerHTML = '<a href=\"/api/workspace\">/api/workspace</a>';\n const data = await fetch(\"/api/workspace\").then((r) => r.json());\n content.innerHTML = '<section><h2>Workspace: ' + escapeHtml(data.project || 'workspace') + '</h2>' +\n '<p>Runs: ' + escapeHtml(data.runs.length) + '</p><pre>' + escapeHtml(JSON.stringify(data, null, 2)) + '</pre></section>';\n return;\n }\n nav.innerHTML = '<a href=\"/api/traces\">/api/traces</a> \u00B7 <a href=\"/api/sessions\">/api/sessions</a>';\n const data = await fetch(\"/api/traces\").then((r) => r.json());\n out.textContent = JSON.stringify(data, null, 2);\n }\n load().catch((err) => {\n document.getElementById(\"out\").textContent = String(err);\n });\n </script>\n</body>\n</html>\n";
25
25
 
26
26
  /**
27
27
  * Discriminator for what kind of work a {@link Step} represents.
package/dist/index.d.ts CHANGED
@@ -21,7 +21,7 @@ interface ViewerServerInfo {
21
21
  declare function createViewerServer(options?: ViewerServerOptions): Server;
22
22
  declare function startViewerServer(options?: ViewerServerOptions): Promise<ViewerServerInfo>;
23
23
 
24
- declare const viewerIndexHtml = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <title>AgentInspect Viewer</title>\n <style>\n body { font-family: system-ui, sans-serif; margin: 1.5rem; line-height: 1.4; }\n h1 { font-size: 1.25rem; }\n pre { background: #f4f4f5; padding: 1rem; overflow: auto; max-height: 50vh; }\n a { color: #0b57d0; }\n .muted { color: #666; }\n table { border-collapse: collapse; width: 100%; margin: 1rem 0; }\n th, td { border: 1px solid #ddd; padding: 0.5rem; text-align: left; vertical-align: top; }\n th { background: #f6f6f6; }\n .fail { color: #b42318; font-weight: 600; }\n .pass { color: #027a48; font-weight: 600; }\n section { margin: 1.5rem 0; }\n </style>\n</head>\n<body>\n <h1>AgentInspect local viewer</h1>\n <p class=\"muted\">Read-only. JSONL on disk remains canonical.</p>\n <p id=\"nav\"></p>\n <div id=\"content\"><pre id=\"out\">Loading\u2026</pre></div>\n <script>\n const params = new URLSearchParams(location.search);\n const mode = params.get(\"mode\") || \"traces\";\n\n function renderSuite(data) {\n const rows = data.cases.map((c) =>\n '<tr><td>' + c.id + '</td><td class=\"' + c.status + '\">' + c.status +\n '</td><td>' + (c.message || '') + '</td><td>' + c.toolPath.join(' \u2192 ') +\n '</td><td>' + c.observations.map((o) => o.name + ':' + o.status).join(', ') + '</td></tr>'\n ).join('');\n const failed = data.cases.filter((c) => c.status !== 'pass');\n const detail = failed.map((c) => {\n let html = '<h3>Case ' + c.id + '</h3><ul>';\n if (c.failureDiff) html += '<li>Diff errors: ' + c.failureDiff.summary.errors + '</li>';\n if (c.timeline) html += '<li>Timeline steps: ' + c.timeline.entries.length + '</li>';\n html += '<li>Diagnostics: ' + c.diagnostics.map((d) => d.message).join('; ') + '</li></ul>';\n return html;\n }).join('');\n return '<section><h2>Suite: ' + data.suiteName + ' (' + data.status + ')</h2>' +\n '<p>Passed ' + data.summary.passed + ', failed ' + data.summary.failed + '</p>' +\n '<table><thead><tr><th>Case</th><th>Status</th><th>Message</th><th>Tool path</th><th>Observations</th></tr></thead><tbody>' +\n rows + '</tbody></table>' +\n '<section><h2>Failure detail</h2>' + (detail || '<p class=\"pass\">No failures</p>') + '</section>' +\n '<section><h2>CI artifacts</h2><p>' + (data.ciArtifactsDir || 'n/a') + '</p></section>' +\n '<section><h2>Bundle export</h2><p>' + data.bundleExportHint + '</p></section>';\n }\n\n async function load() {\n const nav = document.getElementById(\"nav\");\n const out = document.getElementById(\"out\");\n const content = document.getElementById(\"content\");\n if (mode === \"suite\") {\n nav.innerHTML = '<a href=\"/api/suite\">/api/suite</a>';\n const data = await fetch(\"/api/suite\").then((r) => r.json());\n content.innerHTML = renderSuite(data);\n return;\n }\n if (mode === \"workspace\") {\n nav.innerHTML = '<a href=\"/api/workspace\">/api/workspace</a>';\n const data = await fetch(\"/api/workspace\").then((r) => r.json());\n content.innerHTML = '<section><h2>Workspace: ' + (data.project || 'workspace') + '</h2>' +\n '<p>Runs: ' + data.runs.length + '</p><pre>' + JSON.stringify(data, null, 2) + '</pre></section>';\n return;\n }\n nav.innerHTML = '<a href=\"/api/traces\">/api/traces</a> \u00B7 <a href=\"/api/sessions\">/api/sessions</a>';\n const data = await fetch(\"/api/traces\").then((r) => r.json());\n out.textContent = JSON.stringify(data, null, 2);\n }\n load().catch((err) => {\n document.getElementById(\"out\").textContent = String(err);\n });\n </script>\n</body>\n</html>\n";
24
+ declare const viewerIndexHtml = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <title>AgentInspect Viewer</title>\n <style>\n body { font-family: system-ui, sans-serif; margin: 1.5rem; line-height: 1.4; }\n h1 { font-size: 1.25rem; }\n pre { background: #f4f4f5; padding: 1rem; overflow: auto; max-height: 50vh; }\n a { color: #0b57d0; }\n .muted { color: #666; }\n table { border-collapse: collapse; width: 100%; margin: 1rem 0; }\n th, td { border: 1px solid #ddd; padding: 0.5rem; text-align: left; vertical-align: top; }\n th { background: #f6f6f6; }\n .fail { color: #b42318; font-weight: 600; }\n .pass { color: #027a48; font-weight: 600; }\n section { margin: 1.5rem 0; }\n </style>\n</head>\n<body>\n <h1>AgentInspect local viewer</h1>\n <p class=\"muted\">Read-only. JSONL on disk remains canonical.</p>\n <p id=\"nav\"></p>\n <div id=\"content\"><pre id=\"out\">Loading\u2026</pre></div>\n <script>\n const params = new URLSearchParams(location.search);\n const mode = params.get(\"mode\") || \"traces\";\n\n function escapeHtml(value) {\n return String(value ?? \"\")\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"'\", \"&#39;\");\n }\n\n function renderSuite(data) {\n const rows = data.cases.map((c) =>\n '<tr><td>' + escapeHtml(c.id) + '</td><td class=\"' + escapeHtml(c.status) + '\">' + escapeHtml(c.status) +\n '</td><td>' + escapeHtml(c.message || '') + '</td><td>' + escapeHtml(c.toolPath.join(' \u2192 ')) +\n '</td><td>' + escapeHtml(c.observations.map((o) => o.name + ':' + o.status).join(', ')) + '</td></tr>'\n ).join('');\n const failed = data.cases.filter((c) => c.status !== 'pass');\n const detail = failed.map((c) => {\n let html = '<h3>Case ' + escapeHtml(c.id) + '</h3><ul>';\n if (c.failureDiff) html += '<li>Diff errors: ' + escapeHtml(c.failureDiff.summary.errors) + '</li>';\n if (c.timeline) html += '<li>Timeline steps: ' + escapeHtml(c.timeline.entries.length) + '</li>';\n html += '<li>Diagnostics: ' + escapeHtml(c.diagnostics.map((d) => d.message).join('; ')) + '</li></ul>';\n return html;\n }).join('');\n return '<section><h2>Suite: ' + escapeHtml(data.suiteName) + ' (' + escapeHtml(data.status) + ')</h2>' +\n '<p>Passed ' + escapeHtml(data.summary.passed) + ', failed ' + escapeHtml(data.summary.failed) + '</p>' +\n '<table><thead><tr><th>Case</th><th>Status</th><th>Message</th><th>Tool path</th><th>Observations</th></tr></thead><tbody>' +\n rows + '</tbody></table>' +\n '<section><h2>Failure detail</h2>' + (detail || '<p class=\"pass\">No failures</p>') + '</section>' +\n '<section><h2>CI artifacts</h2><p>' + escapeHtml(data.ciArtifactsDir || 'n/a') + '</p></section>' +\n '<section><h2>Bundle export</h2><p>' + escapeHtml(data.bundleExportHint) + '</p></section>';\n }\n\n async function load() {\n const nav = document.getElementById(\"nav\");\n const out = document.getElementById(\"out\");\n const content = document.getElementById(\"content\");\n if (mode === \"suite\") {\n nav.innerHTML = '<a href=\"/api/suite\">/api/suite</a>';\n const data = await fetch(\"/api/suite\").then((r) => r.json());\n content.innerHTML = renderSuite(data);\n return;\n }\n if (mode === \"workspace\") {\n nav.innerHTML = '<a href=\"/api/workspace\">/api/workspace</a>';\n const data = await fetch(\"/api/workspace\").then((r) => r.json());\n content.innerHTML = '<section><h2>Workspace: ' + escapeHtml(data.project || 'workspace') + '</h2>' +\n '<p>Runs: ' + escapeHtml(data.runs.length) + '</p><pre>' + escapeHtml(JSON.stringify(data, null, 2)) + '</pre></section>';\n return;\n }\n nav.innerHTML = '<a href=\"/api/traces\">/api/traces</a> \u00B7 <a href=\"/api/sessions\">/api/sessions</a>';\n const data = await fetch(\"/api/traces\").then((r) => r.json());\n out.textContent = JSON.stringify(data, null, 2);\n }\n load().catch((err) => {\n document.getElementById(\"out\").textContent = String(err);\n });\n </script>\n</body>\n</html>\n";
25
25
 
26
26
  /**
27
27
  * Discriminator for what kind of work a {@link Step} represents.
package/dist/index.mjs CHANGED
@@ -4591,8 +4591,9 @@ async function runSuite(options = {}) {
4591
4591
  skipped: cases.filter((item) => item.status === "skipped").length
4592
4592
  };
4593
4593
  const finishedAt = new Date(options.nowMs ?? Date.now()).toISOString();
4594
- const ok = summary.failed === 0 && summary.errors === 0;
4595
- const status = summary.errors > 0 ? "error" : summary.failed > 0 || !ok ? "fail" : "pass";
4594
+ const allSkipped = cases.length > 0 && summary.skipped === cases.length;
4595
+ const ok = summary.failed === 0 && summary.errors === 0 && !allSkipped;
4596
+ const status = summary.errors > 0 ? "error" : allSkipped ? "fail" : summary.failed > 0 || !ok ? "fail" : "pass";
4596
4597
  return {
4597
4598
  ok,
4598
4599
  status,
@@ -4652,27 +4653,36 @@ var viewerIndexHtml = `<!DOCTYPE html>
4652
4653
  const params = new URLSearchParams(location.search);
4653
4654
  const mode = params.get("mode") || "traces";
4654
4655
 
4656
+ function escapeHtml(value) {
4657
+ return String(value ?? "")
4658
+ .replaceAll("&", "&amp;")
4659
+ .replaceAll("<", "&lt;")
4660
+ .replaceAll(">", "&gt;")
4661
+ .replaceAll('"', "&quot;")
4662
+ .replaceAll("'", "&#39;");
4663
+ }
4664
+
4655
4665
  function renderSuite(data) {
4656
4666
  const rows = data.cases.map((c) =>
4657
- '<tr><td>' + c.id + '</td><td class="' + c.status + '">' + c.status +
4658
- '</td><td>' + (c.message || '') + '</td><td>' + c.toolPath.join(' \u2192 ') +
4659
- '</td><td>' + c.observations.map((o) => o.name + ':' + o.status).join(', ') + '</td></tr>'
4667
+ '<tr><td>' + escapeHtml(c.id) + '</td><td class="' + escapeHtml(c.status) + '">' + escapeHtml(c.status) +
4668
+ '</td><td>' + escapeHtml(c.message || '') + '</td><td>' + escapeHtml(c.toolPath.join(' \u2192 ')) +
4669
+ '</td><td>' + escapeHtml(c.observations.map((o) => o.name + ':' + o.status).join(', ')) + '</td></tr>'
4660
4670
  ).join('');
4661
4671
  const failed = data.cases.filter((c) => c.status !== 'pass');
4662
4672
  const detail = failed.map((c) => {
4663
- let html = '<h3>Case ' + c.id + '</h3><ul>';
4664
- if (c.failureDiff) html += '<li>Diff errors: ' + c.failureDiff.summary.errors + '</li>';
4665
- if (c.timeline) html += '<li>Timeline steps: ' + c.timeline.entries.length + '</li>';
4666
- html += '<li>Diagnostics: ' + c.diagnostics.map((d) => d.message).join('; ') + '</li></ul>';
4673
+ let html = '<h3>Case ' + escapeHtml(c.id) + '</h3><ul>';
4674
+ if (c.failureDiff) html += '<li>Diff errors: ' + escapeHtml(c.failureDiff.summary.errors) + '</li>';
4675
+ if (c.timeline) html += '<li>Timeline steps: ' + escapeHtml(c.timeline.entries.length) + '</li>';
4676
+ html += '<li>Diagnostics: ' + escapeHtml(c.diagnostics.map((d) => d.message).join('; ')) + '</li></ul>';
4667
4677
  return html;
4668
4678
  }).join('');
4669
- return '<section><h2>Suite: ' + data.suiteName + ' (' + data.status + ')</h2>' +
4670
- '<p>Passed ' + data.summary.passed + ', failed ' + data.summary.failed + '</p>' +
4679
+ return '<section><h2>Suite: ' + escapeHtml(data.suiteName) + ' (' + escapeHtml(data.status) + ')</h2>' +
4680
+ '<p>Passed ' + escapeHtml(data.summary.passed) + ', failed ' + escapeHtml(data.summary.failed) + '</p>' +
4671
4681
  '<table><thead><tr><th>Case</th><th>Status</th><th>Message</th><th>Tool path</th><th>Observations</th></tr></thead><tbody>' +
4672
4682
  rows + '</tbody></table>' +
4673
4683
  '<section><h2>Failure detail</h2>' + (detail || '<p class="pass">No failures</p>') + '</section>' +
4674
- '<section><h2>CI artifacts</h2><p>' + (data.ciArtifactsDir || 'n/a') + '</p></section>' +
4675
- '<section><h2>Bundle export</h2><p>' + data.bundleExportHint + '</p></section>';
4684
+ '<section><h2>CI artifacts</h2><p>' + escapeHtml(data.ciArtifactsDir || 'n/a') + '</p></section>' +
4685
+ '<section><h2>Bundle export</h2><p>' + escapeHtml(data.bundleExportHint) + '</p></section>';
4676
4686
  }
4677
4687
 
4678
4688
  async function load() {
@@ -4688,8 +4698,8 @@ var viewerIndexHtml = `<!DOCTYPE html>
4688
4698
  if (mode === "workspace") {
4689
4699
  nav.innerHTML = '<a href="/api/workspace">/api/workspace</a>';
4690
4700
  const data = await fetch("/api/workspace").then((r) => r.json());
4691
- content.innerHTML = '<section><h2>Workspace: ' + (data.project || 'workspace') + '</h2>' +
4692
- '<p>Runs: ' + data.runs.length + '</p><pre>' + JSON.stringify(data, null, 2) + '</pre></section>';
4701
+ content.innerHTML = '<section><h2>Workspace: ' + escapeHtml(data.project || 'workspace') + '</h2>' +
4702
+ '<p>Runs: ' + escapeHtml(data.runs.length) + '</p><pre>' + escapeHtml(JSON.stringify(data, null, 2)) + '</pre></section>';
4693
4703
  return;
4694
4704
  }
4695
4705
  nav.innerHTML = '<a href="/api/traces">/api/traces</a> \xB7 <a href="/api/sessions">/api/sessions</a>';