feat(worklog): show Transparent Stream event timestamps (#5700)

This commit is contained in:
Rod Boev committed 2026-07-07 01:05:59 -04:00
1 parent ad7d0abf44
commit 815d06f10f
3 files changed
+1319 -9

No files matched your search

+17 -1
View File
@@ -4109,6 +4109,15 @@ body.resizing .sidebar{transition:none!important;}
font-size:11.5px;
order:3;
}
.transparent-event-time{
flex-shrink:0;
order:4;
color:var(--muted);
font-variant-numeric:tabular-nums;
opacity:.55;
white-space:nowrap;
font-size:10px;
}
.transparent-event-row .thinking-card-label{
display:inline;
flex:0 0 auto;
@@ -4133,7 +4142,7 @@ body.resizing .sidebar{transition:none!important;}
}
.transparent-event-status{
flex-shrink:0;
order:4;
order:5;
color:var(--muted);
opacity:.64;
font-size:10px;
@@ -4143,6 +4152,13 @@ body.resizing .sidebar{transition:none!important;}
border-radius:6px;
background:transparent;
}
.transparent-event-row .thinking-card-btn-row{
order:10;
flex-shrink:0;
display:inline-flex;
align-items:center;
gap:6px;
}
/* Failed / running / interrupted must be legible — a failure in a long trace was
previously invisible (only a muted .64 badge), a regression vs the old card's
accent. (Trifecta finding V2.) */
+224 -8
View File
@@ -5568,6 +5568,108 @@ function _activityClockLabel(ts){
if(!Number.isFinite(stamp)||stamp<=0)return'';
try{return new Date(stamp*1000).toLocaleTimeString([], {hour:'numeric',minute:'2-digit'});}catch(_){return'';}
}
function _timestampSeconds(value){
if(value===undefined||value===null||value==='') return null;
if(value instanceof Date){
const stamp=value.getTime()/1000;
return Number.isFinite(stamp)&&stamp>0?stamp:null;
}
const numeric=Number(value);
if(Number.isFinite(numeric)&&numeric>0){
const stamp=numeric>1e12?numeric/1000:numeric;
return Number.isFinite(stamp)&&stamp>0?stamp:null;
}
if(typeof value==='string'){
const text=value.trim();
if(!text||/^[+-]?(?:\d+\.?\d*|\.\d+)$/.test(text)) return null;
const parsed=Date.parse(text);
if(Number.isFinite(parsed)&&parsed>0) return parsed/1000;
}
return null;
}
function _firstValidTimestampSeconds(...values){
for(const value of values){
const stamp=_timestampSeconds(value);
if(stamp) return stamp;
}
return null;
}
function _transparentEventTimestampSeconds(row, opts){
opts=opts||{};
for(const key of ['ts','timestamp','created_at']){
const stamp=_timestampSeconds(opts[key]);
if(stamp) return stamp;
}
const toolCall=opts.toolCall||row&&row._tcData||null;
if(toolCall&&typeof toolCall==='object'){
for(const key of ['ts','timestamp','created_at','started_at','completed_at']){
const stamp=_timestampSeconds(toolCall[key]);
if(stamp) return stamp;
}
}
if(row&&typeof row.getAttribute==='function'){
for(const key of ['data-event-at','data-activity-at']){
const stamp=_timestampSeconds(row.getAttribute(key));
if(stamp) return stamp;
}
}
if(opts.live===true) return _activityNowSeconds();
return null;
}
function _syncTransparentEventTimestamp(row, header, opts){
if(!row||!header) return null;
opts=opts||{};
const live=opts.live===true||row.getAttribute&&(
row.getAttribute('data-live-tid')==='1'||
row.getAttribute('data-live-thinking')==='1'||
row.getAttribute('data-live-assistant')==='1'||
row.getAttribute('data-live-stream-owned')==='1'
);
const explicitTs=_firstValidTimestampSeconds(opts.ts, opts.timestamp, opts.created_at);
const toolCall=opts.toolCall||row&&row._tcData||null;
const toolTs=toolCall&&typeof toolCall==='object'
? _firstValidTimestampSeconds(
toolCall.ts,
toolCall.timestamp,
toolCall.created_at,
toolCall.started_at,
toolCall.completed_at
)
: null;
const attrTs=row&&typeof row.getAttribute==='function'
? _firstValidTimestampSeconds(
row.getAttribute('data-event-at'),
row.getAttribute('data-activity-at')
)
: null;
const ts=explicitTs||toolTs||attrTs||(live?_activityNowSeconds():null);
const label=ts?_activityClockLabel(ts):'';
let timeEl=header.querySelector('.transparent-event-time');
if(!label){
if(timeEl) timeEl.remove();
row.removeAttribute('data-event-at');
row.removeAttribute('data-event-at-source');
return null;
}
const source=explicitTs||toolTs||attrTs?'event':'live';
if(!timeEl){
timeEl=document.createElement('span');
timeEl.className='transparent-event-time';
}
timeEl.textContent=label;
timeEl.setAttribute('data-event-at',String(ts));
timeEl.setAttribute('data-event-at-source',source);
row.setAttribute('data-event-at',String(ts));
row.setAttribute('data-event-at-source',source);
const anchor=header.querySelector('.transparent-event-status,.thinking-card-btn-row,.tool-card-toggle,.thinking-card-toggle');
if(timeEl.parentNode!==header){
if(anchor&&anchor.parentNode===header) header.insertBefore(timeEl,anchor);
else header.appendChild(timeEl);
}else if(anchor&&timeEl.nextSibling!==anchor){
header.insertBefore(timeEl,anchor);
}
return timeEl;
}
function _activityStatusNode({kind='info',label='',detail='',status='done',ts=null,id=''}){
const row=document.createElement('div');
row.className=`agent-activity-status agent-activity-status-${kind} agent-activity-status-${status}`;
@@ -10651,6 +10753,7 @@ function _decorateTransparentEventRow(row, opts){
else detail.appendChild(modes);
detail.setAttribute('data-transparent-detail-mode','full');
}
if(typeof _syncTransparentEventTimestamp==='function') _syncTransparentEventTimestamp(row, header, {toolCall:tc, ts:opts.ts, live:opts.live===true});
_wireTransparentHeaderToggle(header);
_attachCopyButton(header);
}
@@ -10691,6 +10794,7 @@ function _decorateTransparentEventRow(row, opts){
}else if(preview){
preview.remove();
}
if(typeof _syncTransparentEventTimestamp==='function') _syncTransparentEventTimestamp(row, header, {ts:opts.ts, live:opts.live===true});
_wireTransparentHeaderToggle(header);
_attachCopyButton(header);
}
@@ -11277,6 +11381,22 @@ function _anchorSceneIsSettledSuccessfulCompression(row, settled){
function _anchorSceneToolCallFromRow(row, opts){
const tool=(row&&row.tool&&typeof row.tool==='object')?row.tool:{};
const payload=(row&&row.payload&&typeof row.payload==='object')?row.payload:{};
const timestampSeconds=typeof _timestampSeconds==='function'?_timestampSeconds:function(value){
const stamp=Number(value);
return Number.isFinite(stamp)&&stamp>0?(stamp>1e12?stamp/1000:stamp):null;
};
const firstValidTimestampSeconds=typeof _firstValidTimestampSeconds==='function'
? _firstValidTimestampSeconds
: function(...values){
for(const value of values){
const stamp=timestampSeconds(value);
if(stamp) return stamp;
}
return null;
};
const rowTs=typeof _anchorSceneRowTimestampSeconds==='function'
? _anchorSceneRowTimestampSeconds(row)
: firstValidTimestampSeconds(row&&row.created_at, row&&row.timestamp, row&&row.ts, row&&row.started_at, row&&row.completed_at);
const id=tool.id||row.tool_call_id||payload.tid||payload.id||payload.tool_call_id||payload.tool_use_id||payload.call_id||'';
const settled=!!(opts&&opts.settled);
return {
@@ -11291,11 +11411,34 @@ function _anchorSceneToolCallFromRow(row, opts){
done:settled?true:(tool.done!==null&&tool.done!==undefined?tool.done:(row.status!=='running'&&row.status!=='pending')),
is_error:!!(tool.is_error||payload.is_error||row.status==='error'||row.status==='failed'),
duration:tool.duration||payload.duration||payload.duration_seconds,
started_at:tool.started_at||payload.started_at,
started_at:firstValidTimestampSeconds(tool.started_at, payload.started_at, rowTs),
created_at:firstValidTimestampSeconds(tool.created_at, payload.created_at, rowTs),
timestamp:firstValidTimestampSeconds(tool.timestamp, payload.timestamp, rowTs),
ts:firstValidTimestampSeconds(
tool.ts,
payload.ts,
tool.timestamp,
payload.timestamp,
tool.created_at,
payload.created_at,
rowTs
),
tid:id,
id,
};
}
function _anchorSceneRowTimestampSeconds(row){
if(!row) return null;
const timestampSeconds=typeof _timestampSeconds==='function'?_timestampSeconds:function(value){
const stamp=Number(value);
return Number.isFinite(stamp)&&stamp>0?(stamp>1e12?stamp/1000:stamp):null;
};
for(const key of ['created_at','timestamp','ts','started_at','completed_at']){
const stamp=timestampSeconds(row[key]);
if(stamp) return stamp;
}
return null;
}
function _anchorSceneNodeForRow(row, opts){
const settled=!!(opts&&opts.settled);
if(!row) return null;
@@ -11374,6 +11517,7 @@ function _anchorSceneTransparentNodeForRow(row, opts){
const live=!!(opts&&opts.live);
if(!row) return null;
let node=null;
const eventTs=typeof _anchorSceneRowTimestampSeconds==='function'?_anchorSceneRowTimestampSeconds(row):null;
const meta={
segmentSeq:row.segment_seq||row.segmentSeq||'',
burstId:row.activity_burst_id||row.burst_id||row.burstId||'',
@@ -11403,7 +11547,9 @@ function _anchorSceneTransparentNodeForRow(row, opts){
type:'thinking',
text,
preview:text,
ts:eventTs,
...meta,
live,
});
}else if(row.role==='tool'){
const toolCall=_anchorSceneToolCallFromRow(row,{settled});
@@ -11412,13 +11558,16 @@ function _anchorSceneTransparentNodeForRow(row, opts){
name:toolCall&&toolCall.name,
status:_transparentToolStatus(toolCall,settled),
toolCall,
ts:eventTs,
...meta,
live,
});
}else{
node=_anchorSceneNodeForRow(row,{settled});
node=_decorateTransparentEventRow(node,{
type:String(row.role||'activity'),
...meta,
live,
});
}
if(!node) return null;
@@ -11733,6 +11882,7 @@ function _renderLiveAnchorActivitySceneTransparent(streamId, scene, opts){
const liveFooter=blocks.querySelector('#liveRunStatus');
const renderedRows=[];
for(const row of rows){
const rowEventTs=typeof _anchorSceneRowTimestampSeconds==='function'?_anchorSceneRowTimestampSeconds(row):null;
const node=_anchorSceneTransparentNodeForRow(row,{
live:true,
settled:false,
@@ -11743,7 +11893,9 @@ function _renderLiveAnchorActivitySceneTransparent(streamId, scene, opts){
const key = _transparentLiveRowKey(node, activeStreamId);
const existing = key ? preserveByKey.get(key) : null;
const renderedNode = existing && _transparentLiveRowsCompatible(existing, node)
? _refreshTransparentLiveRow(existing, node)
? _refreshTransparentLiveRow(existing, node, {
preserveEventAt:!rowEventTs&&existing.getAttribute?existing.getAttribute('data-event-at'):null,
})
: node;
if(existing) preserveByKey.delete(key);
if(!renderedNode) continue;
@@ -11866,7 +12018,19 @@ function _refreshTransparentThinkingLiveRow(existing, node){
const nodePreview = node.querySelector('.transparent-event-thinking-preview');
const previewText = nodePreview ? String(nodePreview.textContent || '') : nextText;
if(typeof _decorateTransparentEventRow === 'function'){
_decorateTransparentEventRow(existing,{type:'thinking',text:nextText,preview:previewText});
const nodeStampSource = node.getAttribute ? String(node.getAttribute('data-event-at-source') || '') : '';
const existingStamp = existing.getAttribute ? existing.getAttribute('data-event-at') : null;
const nodeStamp = node.getAttribute ? node.getAttribute('data-event-at') : null;
const nextStamp = nodeStampSource === 'event'
? (nodeStamp || existingStamp)
: (existingStamp || nodeStamp);
_decorateTransparentEventRow(existing,{
type:'thinking',
text:nextText,
preview:previewText,
ts:nextStamp||undefined,
live:true,
});
}
return true;
}
@@ -11945,7 +12109,8 @@ function _refreshTransparentFadeProseRow(existing, node, preservedState){
return existing;
}
function _refreshTransparentLiveRow(existing, node){
function _refreshTransparentLiveRow(existing, node, opts){
opts=opts||{};
if(!existing || !node || !existing.getAttribute) return node;
if(existing===node) return existing;
const preservedState = _transparentLiveRowInteractiveState(existing);
@@ -11977,6 +12142,10 @@ function _refreshTransparentLiveRow(existing, node){
const htmlChanged = existing.innerHTML !== newHtml;
if(htmlChanged) existing.innerHTML = newHtml;
_rehydrateTransparentLiveRow(existing, node, preservedState);
if(opts.preserveEventAt){
const header = existing.querySelector ? existing.querySelector('.tool-card-header,.thinking-card-header') : null;
if(header) _syncTransparentEventTimestamp(existing, header, {ts:opts.preserveEventAt, live:false});
}
return existing;
}
function _renderLiveAnchorActivitySceneForStream(streamId, sessionId, opts){
@@ -13607,6 +13776,9 @@ function _transparentStreamOrderedParts(message){
if(!message||message.role!=='assistant'||message._live||!Array.isArray(message.content)) return null;
if(message._anchor_activity_scene) return null;
const ordered=[];
const messageTs=typeof _firstValidTimestampSeconds==='function'
? _firstValidTimestampSeconds(message._ts, message.timestamp, message.created_at)
: (message._ts||message.timestamp||message.created_at);
let hasText=false;
let hasTool=false;
for(const part of message.content){
@@ -13626,6 +13798,10 @@ function _transparentStreamOrderedParts(message){
toolUseId,
name:part.name||'tool',
input:(part.input&&typeof part.input==='object')?part.input:{},
ts:part.ts,
timestamp:part.timestamp,
created_at:part.created_at,
message_ts:messageTs,
});
hasTool=true;
}
@@ -13671,11 +13847,29 @@ function _collectToolResultSnippetsByTid(messages){
}
return resultsByTid;
}
function _transparentOrderedToolCall(part, rawIdx, toolCallsByTid, resultsByTid, persistedByTid){
function _transparentOrderedToolCall(part, rawIdx, toolCallsByTid, resultsByTid, persistedByTid, messageTs){
const tid=String(part&&part.toolUseId||'').trim();
const firstValidTimestampSeconds=typeof _firstValidTimestampSeconds==='function'
? _firstValidTimestampSeconds
: function(...values){
for(const value of values){
const stamp=Number(value);
if(Number.isFinite(stamp)&&stamp>0) return stamp>1e12?stamp/1000:stamp;
}
return null;
};
const messageStamp=firstValidTimestampSeconds(messageTs, part&&part.message_ts);
const partStamp=firstValidTimestampSeconds(part&&part.ts, part&&part.timestamp, part&&part.created_at);
const liveTool=tid&&toolCallsByTid&&toolCallsByTid.get(tid);
if(liveTool){
const next={...liveTool};
const hasEventStamp=firstValidTimestampSeconds(next.ts, next.timestamp, next.created_at, next.started_at, next.completed_at);
const fallbackStamp=partStamp||messageStamp;
if(!hasEventStamp&&fallbackStamp){
next.ts=fallbackStamp;
next.timestamp=fallbackStamp;
next.created_at=fallbackStamp;
}
const liveSnip=(resultsByTid&&resultsByTid[tid])||(persistedByTid&&persistedByTid[tid])||'';
if(liveSnip){
const patchSnippet=_cliPatchSnippetFromArgs(next.name||part.name||'tool', next.args||part.input||{});
@@ -13689,6 +13883,8 @@ function _transparentOrderedToolCall(part, rawIdx, toolCallsByTid, resultsByTid,
const args=(part&&part.input&&typeof part.input==='object')?part.input:{};
const patchSnippet=_cliPatchSnippetFromArgs(name,args);
const resultSnippet=(resultsByTid&&tid&&resultsByTid[tid])||(persistedByTid&&tid&&persistedByTid[tid])||'';
const fallbackStamp=partStamp||messageStamp;
const primaryStamp=firstValidTimestampSeconds(part&&part.ts, part&&part.timestamp, part&&part.created_at, fallbackStamp);
return {
name,
tid,
@@ -13698,6 +13894,9 @@ function _transparentOrderedToolCall(part, rawIdx, toolCallsByTid, resultsByTid,
snippet:_cliToolCardSnippet(resultSnippet,patchSnippet),
is_diff:_cliToolCardHasDiffSnippet(resultSnippet,patchSnippet),
done:true,
ts:primaryStamp||undefined,
timestamp:primaryStamp||undefined,
created_at:primaryStamp||undefined,
};
}
function _assistantTurnAnchorSettledFinalAnswer(message, content, context){
@@ -14386,7 +14585,7 @@ function renderMessages(options){
orderedTransparentParts.forEach((part, partIdx)=>{
if(!part) return;
if(part.kind==='tool'){
const toolCall=_transparentOrderedToolCall(part, rawIdx, transparentOrderedToolCallsByTid, transparentToolResultsByTid, transparentPersistedSnippetByTid);
const toolCall=_transparentOrderedToolCall(part, rawIdx, transparentOrderedToolCallsByTid, transparentToolResultsByTid, transparentPersistedSnippetByTid);
const toolRow=_decorateTransparentEventRow(buildToolCard(toolCall),{
type:'tool',
name:toolCall&&toolCall.name,
@@ -14926,11 +15125,13 @@ function renderMessages(options){
// position). Keyed by the assistant turn element. (Trifecta finding O-Bug1.)
const transparentSeenThinking=new Map();
for(const entry of activityOrder){
const {aIdx,segmentSeq,burstId,cards,thinkingIdx,includeAnchorReason}=entry;
const sourceMsg=aIdx>=0?S.messages[aIdx]:null;
const event={
...entry,
thinkingText:entry.thinkingIdx!==null?assistantThinking.get(entry.thinkingIdx):'',
ts:sourceMsg&&((sourceMsg._ts!==undefined&&sourceMsg._ts!==null)?sourceMsg._ts:sourceMsg.timestamp),
thinkingText:thinkingIdx!==null?assistantThinking.get(thinkingIdx):'',
};
const {aIdx,segmentSeq,burstId,cards}=event;
if(aIdx<assistantIdxs[0]) continue;
const anchorRow=_assistantAnchorForActivity(aIdx,segmentSeq,burstId);
if(!anchorRow) continue;
@@ -14964,6 +15165,7 @@ function renderMessages(options){
type:'thinking',
text:event.thinkingText,
preview:event.thinkingText,
ts:event.ts,
segmentSeq,
burstId,
});
@@ -14978,6 +15180,7 @@ function renderMessages(options){
name:event.toolCall&&event.toolCall.name,
status:_transparentToolStatus(event.toolCall,true),
toolCall:event.toolCall,
ts:event.ts,
segmentSeq,
burstId,
});
@@ -16115,11 +16318,14 @@ function appendLiveToolCard(tc){
if(tid){
const existing=inner.querySelector(`.transparent-event-row[data-live-tid="${CSS.escape(tid)}"],.tool-card-row[data-live-tid="${CSS.escape(tid)}"]`);
if(existing){
const replacementTs=_transparentEventTimestampSeconds(existing,{toolCall:tc});
const replacement=_decorateTransparentEventRow(buildToolCard(tc),{
type:'tool',
name:tc&&tc.name,
status:_transparentToolStatus(tc),
toolCall:tc,
ts:replacementTs,
live:true,
segmentSeq:effectiveSegmentSeq,
burstId,
});
@@ -16155,6 +16361,7 @@ function appendLiveToolCard(tc){
name:tc&&tc.name,
status:_transparentToolStatus(tc),
toolCall:tc,
live:true,
segmentSeq:effectiveSegmentSeq,
burstId,
});
@@ -17271,10 +17478,19 @@ function appendThinking(text='', options){
}
row.id='thinkingRow';
row.setAttribute('data-thinking-active','1');
const existingEventAt=row.getAttribute('data-event-at');
const nextTs=_firstValidTimestampSeconds(
options.ts,
options.timestamp,
options.created_at,
existingEventAt
);
_decorateTransparentEventRow(row,{
type:'thinking',
text:clean,
preview:clean,
ts:nextTs||undefined,
live:true,
segmentSeq,
burstId,
});
File diff suppressed because it is too large. Load diff