@pi-unipi/trajectory 2.10.2 → 2.12.0

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/src/ui.ts CHANGED
@@ -5,6 +5,8 @@ export const TRAJECTORY_HTML = `<!doctype html>
5
5
  <button id="duration" class="toggle" type="button" aria-pressed="false">◷ Duration</button>
6
6
  <button id="turns" class="toggle" type="button" aria-pressed="false">Turns</button>
7
7
  <button id="calls" class="toggle" type="button" aria-pressed="false">Calls</button>
8
+ <button id="unipi-only" class="toggle" type="button" aria-pressed="false">UniPi</button>
9
+ <button id="violations" class="toggle" type="button" aria-pressed="false">Violations</button>
8
10
  <span class="spacer"></span><span id="connection" class="connection">UniPi · Live</span>
9
11
  <button id="theme" class="theme-toggle" type="button" aria-label="Toggle dark theme" title="Toggle theme">☾</button>
10
12
  <label class="search"><span>⌕</span><input id="search" type="search" aria-label="Search trajectory" placeholder="Search trajectory"></label>
@@ -15,7 +17,7 @@ export const TRAJECTORY_HTML = `<!doctype html>
15
17
  <script type="module" src="/app.js"></script></body></html>`;
16
18
 
17
19
  export const TRAJECTORY_CSS = `
18
- :root{color-scheme:light;--bg:#f7f8fa;--panel:#fff;--line:#e4e6eb;--muted:#74777d;--text:#202124;--blue:#3977ef;--user:#4d8bf5;--model:#9b70df;--tool:#e39b45;--error:#dc4b4b;font:13px Inter,ui-sans-serif,system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text)}*{box-sizing:border-box}body{margin:0;overflow:hidden}button,input{font:inherit}.product-nav{height:34px;display:flex;align-items:center;gap:20px;padding:0 20px;border-bottom:1px solid var(--line);background:var(--panel);font-size:12px}.product-nav strong{font-weight:600}.product-nav span{height:34px;display:flex;align-items:center;border-bottom:2px solid var(--blue);color:var(--blue)}.toolbar{height:32px;display:flex;align-items:center;gap:10px;padding:0 20px;background:var(--panel);border-bottom:1px solid var(--line)}button{border:1px solid var(--line);background:#fff;border-radius:4px;color:#50535a;padding:3px 8px;cursor:pointer}.toggle{border-color:transparent;background:transparent;padding:2px}.toggle.active{background:#edf3ff;border-color:#b9cdf7;color:#245ec8}.spacer{flex:1}.connection{font-size:11px;color:var(--muted)}.connection.live{color:#27865b}.search{display:flex;align-items:center;gap:6px;width:min(280px,32vw);border:1px solid var(--line);background:#fafbfc;border-radius:6px;padding:5px 9px}.search input{width:100%;border:0;outline:0;background:transparent}.timeline{height:84px;display:grid;grid-template-columns:56px 1fr;background:var(--panel);border-bottom:1px solid var(--line)}.lane-labels{position:relative;border-right:1px solid var(--line);color:var(--muted);font-size:10px;line-height:1}.lane-labels span{position:absolute;right:5px;height:12px}.lane-labels span:nth-child(1){top:12px}.lane-labels span:nth-child(2){top:35px}.lane-labels span:nth-child(3){top:58px}.timeline-track{position:relative;overflow:hidden;cursor:crosshair}.span{position:absolute;height:12px;min-width:2px;border-radius:1px;opacity:.9;transition:opacity .1s,filter .1s}.span:hover,.span.selected{opacity:1;filter:brightness(.92);z-index:3}.span.user{top:12px;background:var(--user)}.span.assistant{top:35px;background:var(--model)}.span.tool{top:58px;background:var(--tool)}.span.compaction,.span.branch{top:35px;background:#8b919d}.span.error{background:var(--error)}.range{position:absolute;inset:0 auto 0 0;background:#3977ef1c;border-left:1px solid var(--blue);border-right:1px solid var(--blue);pointer-events:none;z-index:2}.workspace{height:calc(100vh - 150px);display:grid;grid-template-columns:minmax(360px,1fr) minmax(340px,38%)}.workspace.details-closed{grid-template-columns:1fr}.ledger-pane{overflow:auto;background:var(--panel)}table{width:100%;border-collapse:collapse;table-layout:fixed}tr{border-bottom:1px solid #eceef1;cursor:pointer}tr.turn-start{border-top:3px solid #cfd3da}tr:hover{background:#f6f8fc}tr.selected{background:#edf3ff}tr.filtered,tr.timeline-filtered{display:none}td{height:30px;vertical-align:middle;padding:0 8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.event-column{width:172px}.event{position:relative;overflow:visible!important;color:#555a63;text-align:right;padding-left:76px!important}.event::before{content:'•';position:absolute;left:13px;color:#81858d}.request-boundary{position:absolute;top:3px;left:20px;display:inline-grid;align-items:center;height:14px;max-width:68px;padding:0 5px;border:1px solid var(--line);border-radius:2px;background:var(--panel);color:var(--muted);font:9px/12px ui-monospace,SFMono-Regular,Menlo,monospace;white-space:nowrap}.content{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.badge{display:inline-flex;align-items:center;gap:5px;font-size:10px;font-weight:700;letter-spacing:.045em}.badge.user{color:var(--user)}.badge.assistant{color:var(--model)}.badge.tool{color:#b87322}.badge.compaction,.badge.branch{color:#6f7580}.request{display:inline-block;margin-right:6px;padding:2px 5px;border:1px solid #d7d9df;border-radius:4px;color:#666;font-weight:500;letter-spacing:0}.location{display:none}.content{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.4}.arrow{color:#a3a6ad;margin:0 6px}.result{color:#656a73}.error-text{color:var(--error)}.empty{display:none;padding:50px;text-align:center;color:var(--muted)}.empty.visible{display:block}.details{border-left:1px solid var(--line);background:var(--panel);overflow:hidden}.details>header{height:56px;display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-bottom:1px solid var(--line)}.details>header div{display:flex;flex-direction:column;gap:3px}.details small{color:var(--muted)}#detail-close{font-size:18px;border:0}.tabs{display:flex;gap:2px;padding:8px 12px 0;border-bottom:1px solid var(--line);overflow:auto}.tabs button{border:0;border-bottom:2px solid transparent;border-radius:0;background:none;padding:8px}.tabs button.active{color:var(--blue);border-bottom-color:var(--blue)}.detail-body{height:calc(100% - 101px);overflow:auto;padding:15px}.summary{display:grid;grid-template-columns:120px 1fr;gap:8px 12px;margin:0}.summary dt{color:var(--muted)}.summary dd{margin:0;overflow-wrap:anywhere}.payload{margin:0;padding:13px;background:#f5f6f8;border:1px solid #e5e7eb;border-radius:6px;white-space:pre-wrap;overflow-wrap:anywhere;font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace}.folded{display:none}@media(max-width:760px){.workspace{grid-template-columns:1fr}.details{position:absolute;inset:150px 0 0 10%;z-index:10;box-shadow:-8px 0 24px #0002}.event-column{width:156px}.timeline{grid-template-columns:50px 1fr}.request-boundary{left:18px}.search{width:160px}.product-nav{padding:0 10px}.toolbar{padding:0 10px}}
20
+ :root{color-scheme:light;--bg:#f7f8fa;--panel:#fff;--line:#e4e6eb;--muted:#74777d;--text:#202124;--blue:#3977ef;--user:#4d8bf5;--model:#9b70df;--tool:#e39b45;--error:#dc4b4b;font:13px Inter,ui-sans-serif,system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text)}*{box-sizing:border-box}body{margin:0;overflow:hidden}button,input{font:inherit}.product-nav{height:34px;display:flex;align-items:center;gap:20px;padding:0 20px;border-bottom:1px solid var(--line);background:var(--panel);font-size:12px}.product-nav strong{font-weight:600}.product-nav span{height:34px;display:flex;align-items:center;border-bottom:2px solid var(--blue);color:var(--blue)}.toolbar{height:32px;display:flex;align-items:center;gap:10px;padding:0 20px;background:var(--panel);border-bottom:1px solid var(--line)}button{border:1px solid var(--line);background:#fff;border-radius:4px;color:#50535a;padding:3px 8px;cursor:pointer}.toggle{border-color:transparent;background:transparent;padding:2px}.toggle.active{background:#edf3ff;border-color:#b9cdf7;color:#245ec8}.spacer{flex:1}.connection{font-size:11px;color:var(--muted)}.connection.live{color:#27865b}.search{display:flex;align-items:center;gap:6px;width:min(280px,32vw);border:1px solid var(--line);background:#fafbfc;border-radius:6px;padding:5px 9px}.search input{width:100%;border:0;outline:0;background:transparent}.timeline{height:84px;display:grid;grid-template-columns:56px 1fr;background:var(--panel);border-bottom:1px solid var(--line)}.lane-labels{position:relative;border-right:1px solid var(--line);color:var(--muted);font-size:10px;line-height:1}.lane-labels span{position:absolute;right:5px;height:12px}.lane-labels span:nth-child(1){top:12px}.lane-labels span:nth-child(2){top:35px}.lane-labels span:nth-child(3){top:58px}.timeline-track{position:relative;overflow:hidden;cursor:crosshair}.span{position:absolute;height:12px;min-width:2px;border-radius:1px;opacity:.9;transition:opacity .1s,filter .1s}.span:hover,.span.selected{opacity:1;filter:brightness(.92);z-index:3}.span.user{top:12px;background:var(--user)}.span.assistant{top:35px;background:var(--model)}.span.tool{top:58px;background:var(--tool)}.span.system,.span.hook,.span.unipi,.span.prefix,.span.compaction,.span.branch{top:35px;background:#8b919d}.span.prefix{background:#2f9d68}.span.prefix.error{background:var(--error)}.span.error{background:var(--error)}.range{position:absolute;inset:0 auto 0 0;background:#3977ef1c;border-left:1px solid var(--blue);border-right:1px solid var(--blue);pointer-events:none;z-index:2}.workspace{height:calc(100vh - 150px);display:grid;grid-template-columns:minmax(360px,1fr) minmax(340px,38%)}.workspace.details-closed{grid-template-columns:1fr}.ledger-pane{overflow:auto;background:var(--panel)}table{width:100%;border-collapse:collapse;table-layout:fixed}tr{border-bottom:1px solid #eceef1;cursor:pointer}tr.turn-start{border-top:3px solid #cfd3da}tr:hover{background:#f6f8fc}tr.selected{background:#edf3ff}tr.filtered,tr.timeline-filtered{display:none}td{height:30px;vertical-align:middle;padding:0 8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.event-column{width:172px}.event{position:relative;overflow:visible!important;color:#555a63;text-align:right;padding-left:76px!important}.event::before{content:'•';position:absolute;left:13px;color:#81858d}.request-boundary{position:absolute;top:3px;left:20px;display:inline-grid;align-items:center;height:14px;max-width:68px;padding:0 5px;border:1px solid var(--line);border-radius:2px;background:var(--panel);color:var(--muted);font:9px/12px ui-monospace,SFMono-Regular,Menlo,monospace;white-space:nowrap}.content{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.badge{display:inline-flex;align-items:center;gap:5px;font-size:10px;font-weight:700;letter-spacing:.045em}.badge.user{color:var(--user)}.badge.assistant{color:var(--model)}.badge.tool{color:#b87322}.badge.system,.badge.hook,.badge.unipi,.badge.prefix,.badge.compaction,.badge.branch{color:#6f7580}.badge.prefix{color:#27865b}.badge.prefix.error-text{color:var(--error)}.request{display:inline-block;margin-right:6px;padding:2px 5px;border:1px solid #d7d9df;border-radius:4px;color:#666;font-weight:500;letter-spacing:0}.location{display:none}.content{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.4}.arrow{color:#a3a6ad;margin:0 6px}.result{color:#656a73}.error-text{color:var(--error)}.empty{display:none;padding:50px;text-align:center;color:var(--muted)}.empty.visible{display:block}.details{border-left:1px solid var(--line);background:var(--panel);overflow:hidden}.details>header{height:56px;display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-bottom:1px solid var(--line)}.details>header div{display:flex;flex-direction:column;gap:3px}.details small{color:var(--muted)}#detail-close{font-size:18px;border:0}.tabs{display:flex;gap:2px;padding:8px 12px 0;border-bottom:1px solid var(--line);overflow:auto}.tabs button{border:0;border-bottom:2px solid transparent;border-radius:0;background:none;padding:8px}.tabs button.active{color:var(--blue);border-bottom-color:var(--blue)}.detail-body{height:calc(100% - 101px);overflow:auto;padding:15px}.summary{display:grid;grid-template-columns:120px 1fr;gap:8px 12px;margin:0}.summary dt{color:var(--muted)}.summary dd{margin:0;overflow-wrap:anywhere}.payload{margin:0;padding:13px;background:#f5f6f8;border:1px solid #e5e7eb;border-radius:6px;white-space:pre-wrap;overflow-wrap:anywhere;font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace}.folded{display:none}@media(max-width:760px){.workspace{grid-template-columns:1fr}.details{position:absolute;inset:150px 0 0 10%;z-index:10;box-shadow:-8px 0 24px #0002}.event-column{width:156px}.timeline{grid-template-columns:50px 1fr}.request-boundary{left:18px}.search{width:160px}.product-nav{padding:0 10px}.toolbar{padding:0 10px}}
19
21
  `;
20
22
 
21
23
  export const TRAJECTORY_THEME_CSS = `
@@ -36,8 +38,8 @@ html,body{background:var(--background);color:var(--foreground);font-family:var(-
36
38
  .product-nav,.toolbar{background:var(--card);border-color:var(--border)}.toolbar{height:32px;margin:0;padding:0 20px;border-width:0 0 1px;border-style:solid;border-radius:0;box-shadow:none}
37
39
  button{background:var(--card);color:var(--card-foreground);border-color:var(--border);border-radius:var(--radius);transition:transform .12s,box-shadow .12s,background .12s}button:hover{background:color-mix(in oklch,var(--muted) 42%,var(--card));box-shadow:var(--shadow-sm)}button:active{transform:translateY(1px)}button:focus-visible,input:focus-visible,.timeline-track:focus-visible{outline:2px solid var(--ring);outline-offset:2px}
38
40
  .toggle.active{background:var(--primary);border-color:color-mix(in oklch,var(--primary) 72%,var(--border));color:var(--primary-foreground)}.theme-toggle{width:32px;height:32px;padding:0;font-size:16px}.connection.live{color:color-mix(in oklch,var(--secondary) 80%,var(--foreground))}.search{background:color-mix(in oklch,var(--card) 78%,var(--background));border-color:var(--input);border-radius:var(--radius)}.search input{color:var(--foreground)}
39
- .timeline{height:84px;margin:0;background:var(--card);border-width:0 0 1px;border-style:solid;border-color:var(--border);border-radius:0;box-shadow:none}.timeline-track{border-left-color:var(--border);border-radius:0;background:transparent}.lane-labels,.location,.details small,.summary dt{color:var(--muted-foreground)}.span.user{background:var(--trajectory-user)}.span.assistant{background:var(--trajectory-model)}.span.tool{background:var(--trajectory-tool)}.span.compaction,.span.branch{background:var(--muted)}.range{background:color-mix(in oklch,var(--secondary) 20%,transparent);border-color:var(--secondary)}
40
- .workspace{height:calc(100vh - 150px);gap:0;padding:0}.ledger-pane,.details{background:var(--card);border:0;border-radius:0;box-shadow:none;overflow:auto}.details{border-left:1px solid var(--border);box-shadow:var(--shadow-lg)}tr{border-bottom-color:color-mix(in oklch,var(--border) 72%,transparent)}tr.turn-start{border-top-color:var(--secondary)}tr:hover{background:color-mix(in oklch,var(--muted) 24%,transparent)}tr.selected{background:color-mix(in oklch,var(--secondary) 18%,transparent)}.content{font-family:var(--font-mono)}.request{background:color-mix(in oklch,var(--muted) 26%,transparent);border-color:var(--border);color:var(--muted-foreground)}.badge.user{color:var(--trajectory-user)}.badge.assistant{padding:2px 5px;border-radius:3px;background:color-mix(in oklch,var(--trajectory-model) 18%,transparent);color:var(--trajectory-model)}.badge.tool{color:var(--trajectory-tool)}.result{color:var(--muted-foreground)}.error-text{color:var(--destructive)}
41
+ .timeline{height:84px;margin:0;background:var(--card);border-width:0 0 1px;border-style:solid;border-color:var(--border);border-radius:0;box-shadow:none}.timeline-track{border-left-color:var(--border);border-radius:0;background:transparent}.lane-labels,.location,.details small,.summary dt{color:var(--muted-foreground)}.span.user{background:var(--trajectory-user)}.span.assistant{background:var(--trajectory-model)}.span.tool{background:var(--trajectory-tool)}.span.system,.span.hook,.span.unipi,.span.compaction,.span.branch{background:var(--muted)}.span.prefix{background:color-mix(in oklch,var(--secondary) 78%,#22a06b)}.range{background:color-mix(in oklch,var(--secondary) 20%,transparent);border-color:var(--secondary)}
42
+ .workspace{height:calc(100vh - 150px);gap:0;padding:0}.ledger-pane,.details{background:var(--card);border:0;border-radius:0;box-shadow:none;overflow:auto}.details{border-left:1px solid var(--border);box-shadow:var(--shadow-lg)}tr{border-bottom-color:color-mix(in oklch,var(--border) 72%,transparent)}tr.turn-start{border-top-color:var(--secondary)}tr:hover{background:color-mix(in oklch,var(--muted) 24%,transparent)}tr.selected{background:color-mix(in oklch,var(--secondary) 18%,transparent)}.content{font-family:var(--font-mono)}.request{background:color-mix(in oklch,var(--muted) 26%,transparent);border-color:var(--border);color:var(--muted-foreground)}.badge.user{color:var(--trajectory-user)}.badge.assistant{padding:2px 5px;border-radius:3px;background:color-mix(in oklch,var(--trajectory-model) 18%,transparent);color:var(--trajectory-model)}.badge.tool{color:var(--trajectory-tool)}.badge.system,.badge.hook,.badge.unipi{color:var(--muted-foreground)}.badge.prefix{color:color-mix(in oklch,var(--secondary) 70%,#17834f)}.result{color:var(--muted-foreground)}.error-text{color:var(--destructive)}
41
43
  .details>header,.tabs{border-color:var(--border)}.tabs button.active{color:var(--secondary);border-bottom-color:var(--secondary)}.payload{font-family:var(--font-mono);background:color-mix(in oklch,var(--background) 70%,var(--card));border-color:var(--border)}
42
44
  @media(max-width:760px){.toolbar{margin:0}.timeline{margin:0}.workspace{height:calc(100vh - 150px);padding:0}.details{inset:150px 0 0 6%;border-radius:0}.connection{display:none}}
43
45
  `;
@@ -45,23 +47,23 @@ button{background:var(--card);color:var(--card-foreground);border-color:var(--bo
45
47
  export const TRAJECTORY_APP = String.raw`
46
48
  const q=s=>document.querySelector(s), ledger=q('#ledger'), empty=q('#empty'), details=q('#details'), workspace=q('.workspace'), track=q('#timeline-track');
47
49
  const savedTheme=localStorage.getItem('unipi-trajectory-theme');const prefersDark=matchMedia('(prefers-color-scheme: dark)').matches;document.documentElement.classList.toggle('dark',savedTheme?savedTheme==='dark':prefersDark);
48
- let snapshot={records:[]}, selected=null, activeTab='summary', revision='', collapseTurns=false, collapseCalls=false, timelineRange=null, dragStart=null, actualDuration=false;
50
+ let snapshot={records:[]}, selected=null, activeTab='summary', revision='', collapseTurns=false, collapseCalls=false, unipiOnly=false, violationsOnly=false, timelineRange=null, dragStart=null, actualDuration=false;
49
51
  const esc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
50
52
  const stringify=v=>typeof v==='string'?v:JSON.stringify(v??null,null,2);
51
53
  const fmtTime=n=>n?new Date(n).toLocaleTimeString(undefined,{hour:'2-digit',minute:'2-digit',second:'2-digit',fractionalSecondDigits:3}):'';
52
54
  const fmtDuration=n=>n==null?'':n<1000?Math.round(n)+' ms':(n/1000).toFixed(n<10000?2:1)+' s';
53
- const location=r=>r.turn==null?'Between turns':'Turn '+r.turn+(r.step==null?'':' · Step '+r.step);
55
+ const location=r=>r.kind==='system'?'Agent run':r.kind==='unipi'?(r.package||'UniPi')+(r.surface?' · '+r.surface:''):r.kind==='prefix'?'Epoch '+(r.epoch??'?')+(r.request?.requestId!=null?' · Request '+r.request.requestId:''):r.kind==='hook'&&r.request?.requestId!=null?'Request '+r.request.requestId+(r.turn==null?'':' · Turn '+r.turn):r.turn==null?'Session':'Turn '+r.turn+(r.step==null?'':' · Step '+r.step);
54
56
  function has(v){if(v==null)return false;if(typeof v==='string')return v.trim().length>0;if(Array.isArray(v))return v.length>0;if(typeof v==='object')return Object.keys(v).length>0;return true}
55
- function tabs(r){const values=r.kind==='tool'?[['summary',r],['payload',r.input],['result',r.output],['timing',r.timing]]:r.kind==='assistant'?[['summary',r],['output',r.output],['thinking',r.thinking],['request',r.request],['tools',r.tools],['usage',r.usage],['timing',r.timing]]:r.kind==='user'?[['summary',r],['input',r.input]]:[['summary',r],['output',r.output],['usage',r.usage]];return values.filter(([tab,value])=>tab==='summary'||has(value)).map(([tab])=>tab)}
56
- function detailValue(r,tab){if(tab==='payload'||tab==='input')return r.input;if(tab==='result'||tab==='output')return r.output;if(tab==='thinking')return r.thinking;if(tab==='request')return r.request;if(tab==='tools')return r.tools;if(tab==='usage')return r.usage;if(tab==='timing')return r.timing;return null}
57
- function summary(r){const rows=[['Status',r.isError?'Error':r.durationMs==null&&r.kind==='tool'?'Running':'Complete'],['Started',fmtTime(r.timestamp)],['Duration',fmtDuration(r.durationMs)],['TTFT',fmtDuration(r.timing?.ttftMs)],['Decoding',fmtDuration(r.timing?.decodingMs)],['Provider',r.provider],['Model',r.model],['Stop reason',r.stopReason],['Tool',r.toolName],['Call ID',r.toolCallId],['Input tokens',r.usage?.input],['Cached tokens',r.usage?.cacheRead],['Cache created',r.usage?.cacheWrite],['Output tokens',r.usage?.output],['Reasoning tokens',r.usage?.reasoning],['Total tokens',r.usage?.totalTokens],['Cost',r.usage?.cost==null?undefined:'$'+r.usage.cost.toFixed(6)],['Tokens before compaction',r.tokensBefore]].filter(x=>has(x[1]));return '<dl class="summary">'+rows.map(x=>'<dt>'+esc(x[0])+'</dt><dd>'+esc(x[1])+'</dd>').join('')+'</dl>'}
57
+ function tabs(r){const values=r.kind==='tool'?[['summary',r],['payload',r.input],['result',r.output],['timing',r.timing]]:r.kind==='assistant'?[['summary',r],['output',r.output],['thinking',r.thinking],['request',r.request],['tools',r.tools],['usage',r.usage],['timing',r.timing]]:r.kind==='user'?[['summary',r],['input',r.input]]:r.kind==='system'?[['summary',r],['prompt',r.output]]:r.kind==='unipi'?[['summary',r],['trace',r.data]]:r.kind==='prefix'?[['summary',r],['integrity',r.data],['attribution',r.attribution]]:[['summary',r],['output',r.output],['usage',r.usage]];return values.filter(([tab,value])=>tab==='summary'||has(value)).map(([tab])=>tab)}
58
+ function detailValue(r,tab){if(tab==='payload'||tab==='input')return r.input;if(tab==='result'||tab==='output'||tab==='prompt')return r.output;if(tab==='thinking')return r.thinking;if(tab==='request')return r.request;if(tab==='tools')return r.tools;if(tab==='usage')return r.usage;if(tab==='timing')return r.timing;if(tab==='details'||tab==='event'||tab==='trace'||tab==='integrity')return r.data;if(tab==='attribution')return r.attribution;return null}
59
+ function summary(r){const rows=[['Status',r.isError?'Error':r.durationMs==null&&r.kind==='tool'?'Running':'Complete'],['Started',fmtTime(r.timestamp)],['Event',r.data?.name],['Package',r.package],['Surface',r.surface],['Phase',r.phase],['Verdict',r.verdict],['Epoch',r.epoch],['Request',r.request?.requestId],['Duration',fmtDuration(r.durationMs)],['TTFT',fmtDuration(r.timing?.ttftMs)],['Decoding',fmtDuration(r.timing?.decodingMs)],['Provider',r.provider],['Model',r.model],['Stop reason',r.stopReason],['Tool',r.toolName],['Call ID',r.toolCallId],['Input tokens',r.usage?.input],['Cached tokens',r.usage?.cacheRead],['Cache created',r.usage?.cacheWrite],['Output tokens',r.usage?.output],['Reasoning tokens',r.usage?.reasoning],['Total tokens',r.usage?.totalTokens],['Cost',r.usage?.cost==null?undefined:'$'+r.usage.cost.toFixed(6)],['Tokens before compaction',r.tokensBefore]].filter(x=>has(x[1]));return '<dl class="summary">'+rows.map(x=>'<dt>'+esc(x[0])+'</dt><dd>'+esc(x[1])+'</dd>').join('')+'</dl>'}
58
60
  function showDetails(r){selected=r.id;details.hidden=false;workspace.classList.remove('details-closed');q('#detail-kind').textContent=r.title;q('#detail-location').textContent=location(r);const available=tabs(r);if(!available.includes(activeTab))activeTab='summary';q('#detail-tabs').innerHTML=available.map(t=>'<button role="tab" data-tab="'+t+'" class="'+(t===activeTab?'active':'')+'">'+esc(t[0].toUpperCase()+t.slice(1))+'</button>').join('');q('#detail-tabs').querySelectorAll('button').forEach(b=>b.onclick=()=>{activeTab=b.dataset.tab;showDetails(r)});q('#detail-body').innerHTML=activeTab==='summary'?summary(r):'<pre class="payload">'+esc(stringify(detailValue(r,activeTab)))+'</pre>';renderRows()}
59
- function renderRows(){const query=q('#search').value.toLowerCase();let lastTurn=null,request=0;ledger.innerHTML='';for(const r of snapshot.records){if(r.kind==='assistant')request++;const hay=JSON.stringify(r).toLowerCase();const filtered=query&&!hay.includes(query);const timelineFiltered=timelineRange&&(r.timestamp+(r.durationMs??0)<timelineRange[0]||r.timestamp>timelineRange[1]);const foldTurn=collapseTurns&&r.turn!=null&&r.turn===lastTurn&&r.kind!=='user';const foldCall=collapseCalls&&r.kind==='tool';const tr=document.createElement('tr');tr.dataset.id=r.id;tr.dataset.kind=r.kind;tr.className=[r.turn!==lastTurn&&r.turn!=null?'turn-start':'',selected===r.id?'selected':'',filtered?'filtered':'',timelineFiltered?'timeline-filtered':'',(foldTurn||foldCall)?'folded':''].filter(Boolean).join(' ');const req=r.kind==='assistant'?'<span class="request-boundary">Request #'+request+'</span>':'';const result=r.kind==='tool'&&r.output!=null?'<span class="arrow">→</span><span class="result '+(r.isError?'error-text':'')+'">'+esc((typeof r.output==='string'?r.output:JSON.stringify(r.output)).replace(/\s+/g,' ').slice(0,180))+'</span>':'';tr.innerHTML='<td class="event">'+req+'<span class="badge '+r.kind+'">'+esc(r.title)+'</span><span class="location">'+esc(location(r))+'</span></td><td class="content">'+esc(r.preview||'')+result+'</td>';tr.onclick=()=>showDetails(r);ledger.append(tr);if(r.turn!=null)lastTurn=r.turn}empty.classList.toggle('visible',snapshot.records.length===0)}
61
+ function renderRows(){const query=q('#search').value.toLowerCase();let lastTurn=null,request=0;ledger.innerHTML='';for(const r of snapshot.records){if(r.kind==='assistant')request++;const hay=JSON.stringify(r).toLowerCase();const filtered=query&&!hay.includes(query)||unipiOnly&&!['unipi','prefix'].includes(r.kind)||violationsOnly&&!(r.kind==='prefix'&&r.verdict==='violation');const timelineFiltered=timelineRange&&(r.timestamp+(r.durationMs??0)<timelineRange[0]||r.timestamp>timelineRange[1]);const foldTurn=collapseTurns&&r.turn!=null&&r.turn===lastTurn&&r.kind!=='user';const foldCall=collapseCalls&&r.kind==='tool';const tr=document.createElement('tr');tr.dataset.id=r.id;tr.dataset.kind=r.kind;tr.className=[r.turn!==lastTurn&&r.turn!=null?'turn-start':'',selected===r.id?'selected':'',filtered?'filtered':'',timelineFiltered?'timeline-filtered':'',(foldTurn||foldCall)?'folded':''].filter(Boolean).join(' ');const req=r.kind==='assistant'?'<span class="request-boundary">Request #'+request+'</span>':'';const result=r.kind==='tool'&&r.output!=null?'<span class="arrow">→</span><span class="result '+(r.isError?'error-text':'')+'">'+esc((typeof r.output==='string'?r.output:JSON.stringify(r.output)).replace(/\s+/g,' ').slice(0,180))+'</span>':'';tr.innerHTML='<td class="event">'+req+'<span class="badge '+r.kind+(r.isError?' error-text':'')+'">'+esc(r.title)+'</span><span class="location">'+esc(location(r))+'</span></td><td class="content">'+esc(r.preview||'')+result+'</td>';tr.onclick=()=>showDetails(r);ledger.append(tr);if(r.turn!=null)lastTurn=r.turn}empty.classList.toggle('visible',snapshot.records.length===0)}
60
62
  function timelineModel(){const records=snapshot.records;if(!records.length)return null;if(!actualDuration)return{min:0,max:records.length,spans:records.map((r,index)=>({r,start:index,end:index+1}))};const ordered=records.map((r,index)=>({r,index,start:r.timestamp,end:r.timestamp+Math.max(0,r.durationMs??0)})).sort((a,b)=>a.start-b.start||a.index-b.index);const offsets=new Map;let removed=0,covered=null;for(const span of ordered){if(covered!=null&&span.start>covered)removed+=span.start-covered;offsets.set(span.r.id,removed);covered=covered==null?span.end:Math.max(covered,span.end)}const spans=records.map(r=>({r,start:r.timestamp-(offsets.get(r.id)??0),end:r.timestamp+Math.max(0,r.durationMs??0)-(offsets.get(r.id)??0)}));const min=Math.min(...spans.map(x=>x.start)),rawMax=Math.max(...spans.map(x=>x.end));return{min,max:Math.max(min+1,rawMax),spans}}
61
63
  function renderTimeline(){track.innerHTML='';const d=timelineModel();if(!d)return;for(const {r,start,end} of d.spans){const s=document.createElement('span');s.className='span '+r.kind+(r.isError?' error':'')+(selected===r.id?' selected':'');s.style.left=((start-d.min)/(d.max-d.min)*100)+'%';s.style.width=actualDuration?Math.max(.13,((end-start)/(d.max-d.min)*100))+'%':'max(2px,calc('+(100/snapshot.records.length)+'% - 2px))';s.title=r.title+(fmtTime(r.timestamp)?'\n'+fmtTime(r.timestamp):'')+(fmtDuration(r.durationMs)?' · '+fmtDuration(r.durationMs):'');s.onclick=e=>{e.stopPropagation();showDetails(r)};track.append(s)}if(timelineRange){const range=document.createElement('span');range.className='range';range.style.left=((timelineRange[0]-d.min)/(d.max-d.min)*100)+'%';range.style.width=((timelineRange[1]-timelineRange[0])/(d.max-d.min)*100)+'%';track.append(range)}}
62
64
  function render(){q('#connection').textContent='UniPi · Live';q('#connection').className='connection live';renderRows();renderTimeline();if(selected){const r=snapshot.records.find(x=>x.id===selected);if(r)showDetails(r)}}
63
- async function update(){try{const response=await fetch('/api/snapshot',{cache:'no-store'});if(!response.ok)throw Error(response.status);const data=await response.json(), next=JSON.stringify(data);if(next!==revision){revision=next;snapshot=data;render()}}catch(error){q('#connection').textContent='UniPi';q('#connection').className='connection'}}
65
+ async function update(){try{const response=await fetch('/api/snapshot',{cache:'no-store'});if(!response.ok)throw Error(response.status);const next=response.headers.get('etag')||'';if(next&&next===revision)return;const data=await response.json();revision=next;snapshot=data;render()}catch(error){q('#connection').textContent='UniPi';q('#connection').className='connection'}}
64
66
  function syncThemeButton(){const dark=document.documentElement.classList.contains('dark');q('#theme').textContent=dark?'☀':'☾';q('#theme').setAttribute('aria-label',dark?'Use light theme':'Use dark theme')}syncThemeButton();q('#theme').onclick=()=>{const dark=document.documentElement.classList.toggle('dark');localStorage.setItem('unipi-trajectory-theme',dark?'dark':'light');syncThemeButton()};
65
- q('#detail-close').onclick=()=>{selected=null;details.hidden=true;workspace.classList.add('details-closed');renderRows();renderTimeline()};q('#search').oninput=renderRows;
66
- function timelinePoint(e){const d=timelineModel(),box=track.getBoundingClientRect();return d?d.min+Math.max(0,Math.min(1,(e.clientX-box.left)/box.width))*(d.max-d.min):0}track.onpointerdown=e=>{if(e.button===2){timelineRange=null;renderRows();renderTimeline();return}if(e.button!==0)return;dragStart=timelinePoint(e);track.setPointerCapture?.(e.pointerId)};track.onpointerup=e=>{if(dragStart==null)return;const end=timelinePoint(e);timelineRange=[Math.min(dragStart,end),Math.max(dragStart,end)];dragStart=null;renderRows();renderTimeline()};track.oncontextmenu=e=>e.preventDefault();track.onkeydown=e=>{if(e.key==='Escape'){timelineRange=null;renderRows();renderTimeline()}};q('#turns').onclick=e=>{collapseTurns=!collapseTurns;e.currentTarget.classList.toggle('active',collapseTurns);e.currentTarget.setAttribute('aria-pressed',collapseTurns);renderRows()};q('#calls').onclick=e=>{collapseCalls=!collapseCalls;e.currentTarget.classList.toggle('active',collapseCalls);e.currentTarget.setAttribute('aria-pressed',collapseCalls);renderRows()};q('#duration').onclick=e=>{actualDuration=!actualDuration;e.currentTarget.classList.toggle('active',actualDuration);e.currentTarget.setAttribute('aria-pressed',actualDuration);timelineRange=null;renderRows();renderTimeline()};workspace.classList.add('details-closed');update();setInterval(update,500);
67
+ q('#detail-close').onclick=()=>{selected=null;details.hidden=true;workspace.classList.add('details-closed');renderRows();renderTimeline()};let searchTimer;q('#search').oninput=()=>{clearTimeout(searchTimer);searchTimer=setTimeout(renderRows,120)};
68
+ function timelinePoint(e){const d=timelineModel(),box=track.getBoundingClientRect();return d?d.min+Math.max(0,Math.min(1,(e.clientX-box.left)/box.width))*(d.max-d.min):0}track.onpointerdown=e=>{if(e.button===2){timelineRange=null;renderRows();renderTimeline();return}if(e.button!==0)return;dragStart=timelinePoint(e);track.setPointerCapture?.(e.pointerId)};track.onpointerup=e=>{if(dragStart==null)return;const end=timelinePoint(e);timelineRange=[Math.min(dragStart,end),Math.max(dragStart,end)];dragStart=null;renderRows();renderTimeline()};track.oncontextmenu=e=>e.preventDefault();track.onkeydown=e=>{if(e.key==='Escape'){timelineRange=null;renderRows();renderTimeline()}};q('#turns').onclick=e=>{collapseTurns=!collapseTurns;e.currentTarget.classList.toggle('active',collapseTurns);e.currentTarget.setAttribute('aria-pressed',collapseTurns);renderRows()};q('#calls').onclick=e=>{collapseCalls=!collapseCalls;e.currentTarget.classList.toggle('active',collapseCalls);e.currentTarget.setAttribute('aria-pressed',collapseCalls);renderRows()};q('#unipi-only').onclick=e=>{unipiOnly=!unipiOnly;e.currentTarget.classList.toggle('active',unipiOnly);e.currentTarget.setAttribute('aria-pressed',unipiOnly);renderRows()};q('#violations').onclick=e=>{violationsOnly=!violationsOnly;e.currentTarget.classList.toggle('active',violationsOnly);e.currentTarget.setAttribute('aria-pressed',violationsOnly);renderRows()};q('#duration').onclick=e=>{actualDuration=!actualDuration;e.currentTarget.classList.toggle('active',actualDuration);e.currentTarget.setAttribute('aria-pressed',actualDuration);timelineRange=null;renderRows();renderTimeline()};workspace.classList.add('details-closed');update();setInterval(update,1500);
67
69
  `;