@pi-unipi/trajectory 2.10.2 → 2.11.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/README.md +23 -7
- package/index.ts +23 -3
- package/package.json +1 -1
- package/src/capture.ts +150 -30
- package/src/prefix-integrity.ts +213 -0
- package/src/project.ts +57 -5
- package/src/server.ts +10 -3
- package/src/telemetry.ts +79 -18
- package/src/tracer.ts +401 -0
- package/src/ui.ts +14 -12
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=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[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?'
|
|
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
|
|
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,
|
|
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
|
`;
|