fix: anchor sentinel observe session summaries (#924)
Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
@@ -46,6 +46,20 @@ function tsMs(value){const ms=Date.parse(String(value||'')); return Number.isFin
|
||||
function sessionIdFromUrl(value){const match=String(value||'').match(/\\/workbench\\/sessions\\/(ses_[A-Za-z0-9_-]+)/u); return match?match[1]:null}
|
||||
function commandSessionId(item){return item?.sessionId||item?.detail?.sessionId||item?.input?.sessionId||sessionIdFromUrl(item?.afterUrl)||sessionIdFromUrl(item?.detail?.afterUrl)||null}
|
||||
function commandTraceId(item){return item?.traceId||item?.detail?.chatSubmit?.traceId||item?.detail?.traceId||item?.input?.traceId||null}
|
||||
function completedNewSessionIdsBefore(ts){
|
||||
const limit=tsMs(ts);
|
||||
return control
|
||||
.filter((item)=>item.type==='newSession'&&item.phase==='completed')
|
||||
.filter((item)=>limit===null||tsMs(item.ts)===null||tsMs(item.ts)<=limit)
|
||||
.map(commandSessionId)
|
||||
.filter(Boolean);
|
||||
}
|
||||
function authoritativeSessionIdForPrompts(prompts){
|
||||
const firstPromptTs=prompts[0]?.firstTs||null;
|
||||
const newSessionIds=completedNewSessionIdsBefore(firstPromptTs);
|
||||
const promptSessionIds=unique(prompts.map((item)=>item.sessionId));
|
||||
return newSessionIds.slice(-1)[0]||promptSessionIds[0]||null;
|
||||
}
|
||||
function promptCommands(){
|
||||
const map=new Map();
|
||||
for(const item of control){
|
||||
@@ -55,8 +69,7 @@ function promptCommands(){
|
||||
map.set(id,{...existing,...item,input:{...(existing.input||{}),...(item.input||{})},sessionId:existing.sessionId||commandSessionId(item),traceId:existing.traceId||commandTraceId(item),firstTs:existing.firstTs||item.ts,lastTs:item.ts});
|
||||
}
|
||||
const prompts=Array.from(map.values()).filter((item)=>tsMs(item.firstTs)!==null).sort((a,b)=>tsMs(a.firstTs)-tsMs(b.firstTs));
|
||||
const latestSession=samples.slice().reverse().find((sample)=>sample.routeSessionId||sample.activeSessionId);
|
||||
const sessionId=latestSession?.routeSessionId||latestSession?.activeSessionId||null;
|
||||
const sessionId=authoritativeSessionIdForPrompts(prompts);
|
||||
if(!sessionId) return prompts;
|
||||
const scoped=prompts.filter((item)=>item.sessionId===sessionId);
|
||||
return scoped.length>0?scoped:prompts;
|
||||
@@ -264,12 +277,16 @@ function turnSummaryRows(){
|
||||
const texts=textsFor(segment,traceId);
|
||||
const elapsedValues=texts.map(parseElapsed).filter((value)=>value!==null); const recentValues=texts.map(parseRecent).filter((value)=>value!==null);
|
||||
const marks=unique(segmentControls.map((item)=>item.type==='cancel'?'cancel':item.type==='steer'?'steer':null)).join(',')||'-';
|
||||
return {round:index+1,commandId:prompt.commandId||null,commandType:prompt.type||null,userPreview:user.preview,userHash:user.textHash,userBytes:user.textBytes,traceId,traceIds:unique([traceId,...selected.columns.map((column)=>column.traceId),...traceIdsFromSamples(segment)]).slice(0,8),status:statusFor(segment,traceId),elapsedSeconds:elapsedValues.length?Math.max(...elapsedValues):null,recentUpdateSeconds:recentValues.length?Math.max(...recentValues):null,marks,firstSeq:segment[0]?.seq??primaryColumn?.firstSeq??null,lastSeq:traceSample?.seq??primaryColumn?.lastSeq??segment[segment.length-1]?.seq??null,lastTs:traceSample?.ts??segment[segment.length-1]?.ts??null,finalResponse:finalResponseFor(segment,traceId,user),valuesRedacted:true};
|
||||
return {round:index+1,commandId:prompt.commandId||null,commandType:prompt.type||null,sessionId:prompt.sessionId||null,userPreview:user.preview,userHash:user.textHash,userBytes:user.textBytes,traceId,traceIds:unique([traceId,...selected.columns.map((column)=>column.traceId),...traceIdsFromSamples(segment)]).slice(0,8),status:statusFor(segment,traceId),elapsedSeconds:elapsedValues.length?Math.max(...elapsedValues):null,recentUpdateSeconds:recentValues.length?Math.max(...recentValues):null,marks,firstSeq:segment[0]?.seq??primaryColumn?.firstSeq??null,lastSeq:traceSample?.seq??primaryColumn?.lastSeq??segment[segment.length-1]?.seq??null,lastTs:traceSample?.ts??segment[segment.length-1]?.ts??null,finalResponse:finalResponseFor(segment,traceId,user),valuesRedacted:true};
|
||||
});
|
||||
}
|
||||
function pad(value,width){const text=short(value,width); return text+Array(Math.max(0,width-text.length)+1).join(' ')}
|
||||
function renderTurnSummary(rows){
|
||||
const title='Workbench Session '+(samples[samples.length-1]?.routeSessionId||samples[samples.length-1]?.activeSessionId||'-')+' observer '+(manifest.jobId||'-');
|
||||
const prompts=promptCommands();
|
||||
const anchorSessionId=authoritativeSessionIdForPrompts(prompts)||rows.find((row)=>row.sessionId)?.sessionId||samples[0]?.routeSessionId||samples[0]?.activeSessionId||samples[samples.length-1]?.routeSessionId||samples[samples.length-1]?.activeSessionId||'-';
|
||||
const observedSessions=unique(samples.flatMap((sample)=>[sample.routeSessionId,sample.activeSessionId]));
|
||||
const driftSuffix=observedSessions.length>1?' sessionDrift='+observedSessions.length:'';
|
||||
const title='Workbench Session '+anchorSessionId+' observer '+(manifest.jobId||'-')+driftSuffix;
|
||||
const header='轮次 用户消息 Trace 状态 耗时/最近 标记 Final Response';
|
||||
const body=rows.map((row)=>pad(row.round,5)+' '+pad((row.userHash?row.userHash.slice(0,18)+' ':'')+row.userPreview,32)+' '+pad(row.traceId||'-',12)+' '+pad(row.status,10)+' '+pad(fmtDuration(row.elapsedSeconds)+'/'+(row.recentUpdateSeconds===null?'-':String(row.recentUpdateSeconds)+'s'),13)+' '+pad(row.marks,11)+' '+short(row.finalResponse?.preview||'(空内容)',80)).join('\\n');
|
||||
return [title,'=======================================================',header,body||'(空内容)','', 'NEXT', ' detail: bun scripts/cli.ts hwlab nodes web-probe observe collect '+(manifest.jobId||'<observer>')+' --view trace-frame --trace-id <traceId> --sample-seq <seq>'].join('\\n');
|
||||
|
||||
@@ -16,16 +16,40 @@ async function cancelRunningTurn() {
|
||||
await page.keyboard.press("Backspace").catch(() => {});
|
||||
}
|
||||
}
|
||||
const cancelSelectors = [
|
||||
'button:has-text("取消")',
|
||||
'button:has-text("Cancel")',
|
||||
'[data-testid*="cancel" i]',
|
||||
'[aria-label*="cancel" i]',
|
||||
'[aria-label*="取消"]'
|
||||
].join(", ");
|
||||
const primaryButton = page.locator('#command-submit, [data-testid="command-submit"], [data-testid="composer-submit"], [data-testid="send-command"]').last();
|
||||
const button = await primaryButton.isVisible().catch(() => false)
|
||||
? primaryButton
|
||||
: page.locator([
|
||||
'button:has-text("取消")',
|
||||
'button:has-text("Cancel")',
|
||||
'[data-testid*="cancel" i]',
|
||||
'[aria-label*="cancel" i]',
|
||||
'[aria-label*="取消"]'
|
||||
].join(", ")).last();
|
||||
const explicitCancelButton = page.locator(cancelSelectors).last();
|
||||
let button = null;
|
||||
if (await primaryButton.isVisible().catch(() => false)) {
|
||||
const primaryLooksCancelable = await primaryButton.evaluate((element) => {
|
||||
const parts = [
|
||||
element.textContent || "",
|
||||
element.getAttribute("aria-label") || "",
|
||||
element.getAttribute("title") || "",
|
||||
element.getAttribute("data-testid") || "",
|
||||
element.getAttribute("data-state") || "",
|
||||
element.getAttribute("data-action") || "",
|
||||
element.className || ""
|
||||
];
|
||||
return /cancel|取消|停止|stop|abort/i.test(parts.join(" "));
|
||||
}).catch(() => false);
|
||||
if (primaryLooksCancelable) button = primaryButton;
|
||||
}
|
||||
if (button === null && await explicitCancelButton.isVisible().catch(() => false)) button = explicitCancelButton;
|
||||
if (button === null) {
|
||||
return {
|
||||
beforeUrl,
|
||||
afterUrl: currentPageUrl(),
|
||||
cancelSubmit: { status: null, statusText: null, skipped: true, reason: "cancel-button-not-visible", responseObserved: false },
|
||||
pageId
|
||||
};
|
||||
}
|
||||
await button.waitFor({ state: "visible", timeout: 15000 });
|
||||
const cancelResponsePromise = page.waitForResponse((response) => {
|
||||
const request = response.request();
|
||||
|
||||
Reference in New Issue
Block a user