// SPEC: PJ2026-01040111 长程观测 draft-2026-06-20-p0-passive-web-probe-observer. // Responsibility: Offline CLI view renderers for HWLAB web-probe observe artifacts. import { shellQuote } from "./ssh"; export type NodeWebProbeObserveCollectView = "files" | "turn-summary" | "trace-frame" | "timeline" | "project-summary" | "project-mdtodo-summary"; export function parseNodeWebProbeObserveCollectView(value: string): NodeWebProbeObserveCollectView { if (value === "files" || value === "turn-summary" || value === "trace-frame" || value === "timeline" || value === "project-summary" || value === "project-mdtodo-summary") return value; throw new Error(`web-probe observe collect --view must be files, turn-summary, trace-frame, timeline, project-summary, or project-mdtodo-summary; got ${value}`); } export function nodeWebObserveCollectViewNodeScript(input: { maxFiles: number; view: NodeWebProbeObserveCollectView; traceId: string | null; sampleSeq: number | null; timestamp: string | null; turn: number | null; commandId: string | null; windowMs: number | null; }): string { const requestedSampleSeq = input.sampleSeq === null ? "null" : String(input.sampleSeq); const requestedTurn = input.turn === null ? "null" : String(input.turn); const requestedWindowMs = input.windowMs === null ? "null" : String(input.windowMs); return `node -e ${shellQuote(` const fs=require('fs'),path=require('path'),crypto=require('crypto'); const dir=process.argv[1]; const maxFiles=${input.maxFiles}; const view=${JSON.stringify(input.view)}; const requestedTraceId=${JSON.stringify(input.traceId)}; const requestedSampleSeq=${requestedSampleSeq}; const requestedTimestamp=${JSON.stringify(input.timestamp)}; const requestedTurn=${requestedTurn}; const requestedCommandId=${JSON.stringify(input.commandId)}; const requestedWindowMs=${requestedWindowMs}; const sha=(value)=>'sha256:'+crypto.createHash('sha256').update(String(value||'')).digest('hex'); const short=(value,limit=96)=>{const text=String(value||'').replace(/\\s+/gu,' ').trim(); return text.length>limit?text.slice(0,Math.max(1,limit-1))+'...':text;}; const textOf=(value)=>String(value?.text||value?.textPreview||value?.preview||''); const readJson=(rel)=>{try{return JSON.parse(fs.readFileSync(path.join(dir,rel),'utf8'))}catch{return null}}; const readJsonl=(rel)=>{try{return fs.readFileSync(path.join(dir,rel),'utf8').split(/\\r?\\n/u).filter(Boolean).map((line)=>{try{return JSON.parse(line)}catch{return {parseError:true,rawHash:sha(line)}}})}catch{return []}}; const out=[]; const walk=(p)=>{for(const ent of fs.readdirSync(p,{withFileTypes:true})){const full=path.join(p,ent.name); if(ent.isDirectory()) walk(full); else out.push(full); if(out.length>=maxFiles) return;}}; walk(dir); const files=out.slice(0,maxFiles).map(file=>{const st=fs.statSync(file); const hash=crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'); return {path:file,relative:path.relative(dir,file),byteCount:st.size,sha256:'sha256:'+hash}}); const totalBytes=files.reduce((sum,item)=>sum+item.byteCount,0); if(view==='files'){ console.log(JSON.stringify({ok:true,command:'web-probe-observe collect',view,stateDir:dir,fileCount:files.length,totalBytes,files,valuesRedacted:true},null,2)); process.exit(0); } const samples=readJsonl('samples.jsonl'); const control=readJsonl('control.jsonl'); const manifest=readJson('manifest.json')||{}; const report=readJson('analysis/report.json')||{}; function unique(values){return Array.from(new Set(values.filter(Boolean)));} function numOrNull(value){const n=Number(value); return Number.isFinite(n)?n:null} function promptIndexOf(value){const n=Number(value); return Number.isInteger(n)&&n>0?n:null} function tsMs(value){const ms=Date.parse(String(value||'')); return Number.isFinite(ms)?ms:null} 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){ if(item.type!=='sendPrompt'||(item.phase!=='started'&&item.phase!=='completed'&&item.phase!=='failed')) continue; const id=item.commandId||item.seq||String(map.size+1); const existing=map.get(id)||{}; 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 sessionId=authoritativeSessionIdForPrompts(prompts); if(!sessionId) return prompts; const scoped=prompts.filter((item)=>item.sessionId===sessionId); return scoped.length>0?scoped:prompts; } function segmentFor(index,prompts){ const start=tsMs(prompts[index]?.firstTs); const end=index+1{const ms=tsMs(sample.ts); return ms!==null&&ms>=start&&ms{const ms=tsMs(item.ts); return ms!==null&&ms>=start&&ms({group:'turn',item})):[]), ...(Array.isArray(sample.traceRows)?sample.traceRows.map((item)=>({group:'traceRow',item})):[]), ...(Array.isArray(sample.messages)?sample.messages.map((item)=>({group:'message',item})):[]), ]; } function traceIdStats(items){ const map=new Map(); for(const sample of items){ const seq=numOrNull(sample.seq); for(const entry of entryGroups(sample)){ const id=itemTraceId(entry.item); if(!id) continue; const current=map.get(id)||{traceId:id,count:0,firstSeq:null,lastSeq:null,terminalCount:0}; current.count+=1; current.firstSeq=current.firstSeq===null?seq:seq===null?current.firstSeq:Math.min(current.firstSeq,seq); current.lastSeq=current.lastSeq===null?seq:seq===null?current.lastSeq:Math.max(current.lastSeq,seq); if(terminalText(textOf(entry.item))||String(entry.item?.status||'').match(/complete|fail|cancel|terminal/iu)) current.terminalCount+=1; map.set(id,current); } } return Array.from(map.values()).sort((a,b)=>(a.firstSeq??0)-(b.firstSeq??0)||String(a.traceId).localeCompare(String(b.traceId))); } function traceIdsFromSamples(items){return traceIdStats(items).map((item)=>item.traceId)} function collectReportTurnColumns(value,out=[]){ if(!value||typeof value!=='object') return out; if(Array.isArray(value)){for(const item of value) collectReportTurnColumns(item,out); return out;} if(Array.isArray(value.turnColumns)){ for(const item of value.turnColumns){ if(!item||typeof item!=='object') continue; const traceId=String(item.traceId||'').trim(); if(!traceId) continue; out.push({...item,traceId,promptIndex:promptIndexOf(item.promptIndex),lastPromptIndex:promptIndexOf(item.lastPromptIndex),firstSeq:numOrNull(item.firstSeq),lastSeq:numOrNull(item.lastSeq)}); } } for(const key of ['sampleMetrics','analysis','analysisWindow','recentWindow','windows','recent','archive','summary']){ if(value[key]&&value[key]!==value) collectReportTurnColumns(value[key],out); } return out; } const reportTurnColumns=(()=>{const seen=new Set(); const rows=[]; for(const item of collectReportTurnColumns(report)){const key=[item.traceId,item.promptIndex,item.lastPromptIndex,item.firstSeq,item.lastSeq,item.source,item.pageRole,item.messageId].join('|'); if(seen.has(key)) continue; seen.add(key); rows.push(item);} return rows.sort((a,b)=>(a.firstSeq??0)-(b.firstSeq??0)||(a.promptIndex??9999)-(b.promptIndex??9999));})(); function columnOverlapsSegment(column,segment){ if(segment.length===0) return true; const first=numOrNull(segment[0]?.seq); const last=numOrNull(segment[segment.length-1]?.seq); if(first===null||last===null||column.firstSeq===null||column.lastSeq===null) return true; return column.lastSeq>=first&&column.firstSeq<=last; } function reportColumnsForPrompt(index,segment){ const promptIndex=index+1; const segmentIds=new Set(traceIdsFromSamples(segment)); return reportTurnColumns .filter((column)=>(column.promptIndex===promptIndex||column.lastPromptIndex===promptIndex)&&columnOverlapsSegment(column,segment)) .filter((column)=>segmentIds.size===0||segmentIds.has(column.traceId)||column.source==='turn') .sort((a,b)=>(a.firstSeq??0)-(b.firstSeq??0)||(a.lastSeq??0)-(b.lastSeq??0)||String(a.label||'').localeCompare(String(b.label||''))); } function choosePrimaryTraceId(index,segment,prompt=null){ const promptTraceId=commandTraceId(prompt)||prompt?.traceId||null; if(promptTraceId){ const segmentIds=new Set(traceIdsFromSamples(segment)); if(segmentIds.size===0||segmentIds.has(promptTraceId)) return {traceId:promptTraceId,columns:reportColumnsForPrompt(index,segment)}; } const columns=reportColumnsForPrompt(index,segment); const promptIndex=index+1; const promptColumns=columns.filter((column)=>column.promptIndex===promptIndex); const preferred=promptColumns.length>0?promptColumns:columns; if(preferred.length>0) return {traceId:preferred[0].traceId,columns}; const stats=traceIdStats(segment).sort((a,b)=>(b.lastSeq??0)-(a.lastSeq??0)||(b.firstSeq??0)-(a.firstSeq??0)); return {traceId:stats[0]?.traceId||null,columns:[]}; } function samplesForTrace(items,traceId,maxSeq=null){ if(!traceId) return items; return items.filter((sample)=>{ const seq=numOrNull(sample.seq); return (maxSeq===null||seq===null||seq<=maxSeq)&&traceIdsFromSamples([sample]).includes(traceId); }); } function latestSampleForTrace(items,traceId,maxSeq=null){ return samplesForTrace(items,traceId,maxSeq).slice(-1)[0]||null; } function sampleHasTerminalForTrace(sample,traceId){ if(!sample) return false; const status=statusFor([sample],traceId); if(terminalFinalStatus(status)) return true; const final=finalResponseFor([sample],traceId); if(final&&!final.empty) return true; return traceEntries([sample],traceId).some((entry)=>terminalText(entry.text)||terminalFinalStatus(entry.item?.status)); } function bestTraceFrameSample(traceId,maxSeq=null){ if(!traceId) return null; const matched=samples.filter((sample)=>traceIdsFromSamples([sample]).includes(traceId)); if(matched.length===0) return null; const terminal=matched.filter((sample)=>sampleHasTerminalForTrace(sample,traceId)).slice(-1)[0]; if(terminal) return terminal; if(maxSeq!==null){ const before=matched.filter((sample)=>{const seq=numOrNull(sample.seq); return seq===null||seq<=maxSeq;}).slice(-1)[0]; if(before) return before; } return matched.slice(-1)[0]||null; } function traceEntries(items,traceId){ const entries=[]; for(const sample of items){ for(const entry of entryGroups(sample)){ const text=textOf(entry.item); const id=itemTraceId(entry.item); if(!traceId||id===traceId||text.includes(traceId)) entries.push({...entry,sample,text}); } } return entries; } function textsFor(items,traceId=null){ const entries=traceId?traceEntries(items,traceId):items.flatMap((sample)=>entryGroups(sample).map((entry)=>({...entry,sample,text:textOf(entry.item)}))); return entries.length>0?entries.map((entry)=>entry.text):[]; } function parseElapsed(text){ const values=[]; const raw=String(text||''); for(const m of raw.matchAll(/(?:耗时|总耗时|total\\s*=?)\\s*(?:(\\d+)\\s*(?:小时|h)\\s*)?(?:(\\d+)\\s*(?:分|分钟|m)\\s*)?(?:(\\d+)\\s*(?:秒|s))?/giu)){ const total=Number(m[1]||0)*3600+Number(m[2]||0)*60+Number(m[3]||0); if(total>0) values.push(total); } for(const m of raw.matchAll(/\\b(?:total|elapsed)\\s*=\\s*(?:(\\d{1,2}):)?(\\d{1,2}):(\\d{2})\\b/giu)){ values.push(Number(m[1]||0)*3600+Number(m[2]||0)*60+Number(m[3]||0)); } return values.length?Math.max(...values):null; } function parseRecent(text){const m=String(text||'').match(/最近\\s*(\\d+)\\s*(秒|分钟|分|小时)前/u); if(!m)return null; const n=Number(m[1]); return m[2]==='小时'?n*3600:m[2]==='秒'?n:n*60} function fmtDuration(seconds){if(seconds===null||seconds===undefined||!Number.isFinite(Number(seconds)))return '-'; const value=Math.max(0,Math.round(Number(seconds))); const h=Math.floor(value/3600), m=Math.floor((value%3600)/60), s=value%60; return (h>0?String(h).padStart(2,'0')+':':'')+String(m).padStart(2,'0')+':'+String(s).padStart(2,'0')} function terminalText(text){return /轮次完成|轮次失败|轮次取消|已记录|final response|sealed final response|turn completed|turn failed|turn canceled|completed|failed|canceled|cancelled|terminal/iu.test(String(text||''))} function normalizeLifecycleStatus(value){ const raw=String(value||'').trim().toLowerCase(); if(/^(canceled|cancelled)$/u.test(raw))return 'canceled'; if(/^(failed|failure|error)$/u.test(raw))return 'failed'; if(/^(completed|complete|succeeded|success|terminal)$/u.test(raw))return 'completed'; if(/^(running|admitting|queued|pending|in_progress|in-progress)$/u.test(raw))return 'running'; return null; } function statusFor(items,traceId=null){ const matchedEntries=traceId?traceEntries(items,traceId):items.flatMap((sample)=>entryGroups(sample).map((entry)=>({...entry,sample,text:textOf(entry.item)}))); const lastTurn=matchedEntries.filter((entry)=>entry.group==='turn').slice(-1)[0]?.item||null; const turnStatus=normalizeLifecycleStatus(lastTurn?.status); if(turnStatus)return turnStatus; const lastMessage=matchedEntries.filter((entry)=>entry.group==='message').slice(-1)[0]?.item||null; const messageStatus=normalizeLifecycleStatus(lastMessage?.status); if(messageStatus)return messageStatus; return 'unknown'; } function userMessageFor(items,prompt){ const hash=prompt?.input?.textHash||null; for(const sample of items){ for(const message of Array.isArray(sample.messages)?sample.messages:[]){ if(hash&&message.textHash===hash)return {preview:short(textOf(message),90),textHash:message.textHash||hash,textBytes:message.textBytes??prompt?.input?.textBytes??null}; } } return {preview:short(prompt?.input?.textPreview||'',90)||'(hash only)',textHash:hash,textBytes:prompt?.input?.textBytes??null}; } function promptFailedBeforeTrace(prompt,segment){ if(prompt?.phase!=='failed')return false; if(commandTraceId(prompt)||prompt?.traceId)return false; const hash=prompt?.input?.textHash||null; if(hash){ for(const sample of segment){ for(const message of Array.isArray(sample.messages)?sample.messages:[]){ if(message?.textHash===hash)return false; } } } return true; } function cleanFinalResponseText(text){ const raw=String(text||'').trim(); if(!raw) return ''; if(/^Code Agent\\s*(?:running|completed|failed|canceled|cancelled)?$/iu.test(raw)) return ''; if(/^耗时\\s*\\d+\\s*(?:秒|分钟|分|小时)$/u.test(raw)) return ''; if(/^(completed|failed|canceled|cancelled)!?$/iu.test(raw)) return ''; if(/^!+$/u.test(raw)) return ''; if(/Code Agent\\s*耗时[\\s\\S]*运行记录/iu.test(raw)) return ''; if(/^sent\\s+当前 AgentRun 请求已取消/u.test(raw)) return ''; if(/^(admitted|run|ok|error)\\s+/iu.test(raw)) return ''; if(/^(AgentRun|Code Agent turn was durably admitted|commandExecution|runner-job|command -v|node --version|npm --version|cd |mkdir -p|npm install|npx )/iu.test(raw)) return ''; if(/(?:^|\\s)轮次(完成|失败|取消)(?总耗时/iu.test(raw)) return ''; if(/^(轮次完成|轮次失败|轮次取消|已记录)$/u.test(raw.replace(/\\s+/gu,' ').trim())) return ''; return raw; } function emptyFinalResponse(){return {preview:'(空内容)',textHash:null,textBytes:0,empty:true}} function terminalFinalStatus(value){return /^(completed|failed|canceled|cancelled|terminal)$/u.test(String(value||'').trim().toLowerCase())} function entryRole(entry){return String(entry.item?.role||entry.item?.dataRole||entry.item?.authorRole||entry.item?.messageRole||entry.item?.speaker||'').toLowerCase()} function finalCandidateText(entry){ const explicit=textOf(entry.item?.finalResponse); if(explicit) return explicit; if(entry.group==='message') return entry.text; if(entry.group==='traceRow'&&/助手消息|assistant\\s*message/iu.test(entry.text)){ return String(entry.text||'').replace(/^.*?(?:助手消息|assistant\\s*message)\\s*/iu,''); } return ''; } function sameAsUserPrompt(entry,text,user){ if(!user) return false; if(user.textHash&&entry.item?.textHash===user.textHash) return true; const userPrefix=short(user.preview||'',48).replace(/\\s+/gu,' ').trim().slice(0,32); const textPrefix=String(text||'').replace(/\\s+/gu,' ').trim().slice(0,32); return userPrefix.length>=18&&textPrefix===userPrefix; } function finalResponseFor(items,traceId,user=null){ if(!terminalFinalStatus(statusFor(items,traceId))) return emptyFinalResponse(); const scoped=traceId?traceEntries(items,traceId):items.flatMap((sample)=>entryGroups(sample).map((entry)=>({...entry,sample,text:textOf(entry.item)}))); const unscopedMessages=traceId?items.flatMap((sample)=>entryGroups(sample) .filter((entry)=>entry.group==='message'&&!itemTraceId(entry.item)) .map((entry)=>({...entry,sample,text:textOf(entry.item)}))):[]; const candidates=[...scoped,...unscopedMessages].slice().reverse(); for(const entry of candidates){ const role=entryRole(entry); const entryStatus=String(entry.item?.status||''); if(entry.group==='message'&&role&&!/assistant|agent|system/u.test(role)) continue; if(/用户|user/iu.test(role)||/用户|user/iu.test(entryStatus)) continue; const status=String(entry.item?.status||'').toLowerCase(); const text=cleanFinalResponseText(finalCandidateText(entry)); if(!text) continue; if(/^第\\d+轮\\/\\d+[::]?\\s*请/u.test(text)||/^请(执行|帮|回答|修复|调查|用|不要|直接)/u.test(text)) continue; if(sameAsUserPrompt(entry,text,user)) continue; if(status.includes('running')&&!terminalText(entry.text)&&entry.group==='turn')continue; const preview=short(text,180); if(preview)return {preview,textHash:entry.item?.textHash||sha(text),textBytes:entry.item?.textBytes??Buffer.byteLength(text),empty:false}; } return emptyFinalResponse(); } function turnSummaryRows(){ const prompts=promptCommands(); if(prompts.length===0){ const allTraceIds=traceIdsFromSamples(samples); return [{round:0,commandId:null,commandType:null,userPreview:'(无 sendPrompt control 记录)',userHash:null,userBytes:null,traceId:allTraceIds[0]||null,status:statusFor(samples,allTraceIds[0]||null),elapsedSeconds:null,recentUpdateSeconds:null,marks:'-',firstSeq:samples[0]?.seq??null,lastSeq:samples[samples.length-1]?.seq??null,lastTs:samples[samples.length-1]?.ts??null,finalResponse:finalResponseFor(samples,allTraceIds[0]||null),valuesRedacted:true}]; } return prompts.map((prompt,index)=>{ const segment=segmentFor(index,prompts); const segmentControls=controlsFor(index,prompts); const failedBeforeTrace=promptFailedBeforeTrace(prompt,segment); const selected=failedBeforeTrace?{traceId:null,columns:[]}:choosePrimaryTraceId(index,segment,prompt); const traceId=selected.traceId; const user=userMessageFor(segment,prompt); const primaryColumn=selected.columns.find((column)=>column.traceId===traceId)||null; const traceSample=latestSampleForTrace(segment,traceId,primaryColumn?.lastSeq??null); 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),failedBeforeTrace?'command-failed':null]).join(',')||'-'; 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:failedBeforeTrace?[]:unique([traceId,...selected.columns.map((column)=>column.traceId),...traceIdsFromSamples(segment)]).slice(0,8),status:failedBeforeTrace?'command-failed':statusFor(segment,traceId),elapsedSeconds:failedBeforeTrace?null:(elapsedValues.length?Math.max(...elapsedValues):null),recentUpdateSeconds:failedBeforeTrace?null:(recentValues.length?Math.max(...recentValues):null),marks,firstSeq:segment[0]?.seq??primaryColumn?.firstSeq??null,lastSeq:failedBeforeTrace?(segment[segment.length-1]?.seq??null):(traceSample?.seq??primaryColumn?.lastSeq??segment[segment.length-1]?.seq??null),lastTs:failedBeforeTrace?(segment[segment.length-1]?.ts??null):(traceSample?.ts??segment[segment.length-1]?.ts??null),finalResponse:failedBeforeTrace?emptyFinalResponse():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 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 web-probe observe collect '+(manifest.jobId||'')+' --view trace-frame --trace-id --sample-seq '].join('\\n'); } function diagnosticRows(label,items,limit=6){ const selected=items.slice(-limit); const skipped=Math.max(0,items.length-selected.length); const prefix=skipped>0?'last '+selected.length+'/'+items.length:items.length+''; return selected.map((item,index)=>{ const traceId=itemTraceId(item)||item.traceId||'-'; const status=String(item?.status||item?.kind||item?.type||'-'); return label+' '+prefix+' #'+(skipped+index+1)+' trace='+short(traceId,22)+' status='+short(status,18)+' text='+short(textOf(item),120); }); } function traceFrameDiagnosticLines(sample,traceId,matchedTraceRows){ const allTraceRows=Array.isArray(sample.traceRows)?sample.traceRows:[]; const allTurns=Array.isArray(sample.turns)?sample.turns:[]; const allMessages=Array.isArray(sample.messages)?sample.messages:[]; const allIds=unique([...allTraceRows,...allTurns,...allMessages].map((item)=>itemTraceId(item)||item?.traceId||firstTraceId(textOf(item)))).slice(0,12); return [ 'TRACE DIAGNOSTIC', 'pageRole='+(sample.pageRole||'-')+' pageId='+(sample.pageId||'-')+' traceRows='+allTraceRows.length+' turns='+allTurns.length+' messages='+allMessages.length, 'requestedTraceId='+(traceId||'-')+' matchedTraceRows='+matchedTraceRows.length+' sampleTraceIds='+(allIds.join(',')||'-'), ...diagnosticRows('traceRow',allTraceRows), ...diagnosticRows('turn',allTurns,4), ...diagnosticRows('message',allMessages,4), ]; } function selectSample(rows){ if(requestedSampleSeq!==null){const exact=samples.find((sample)=>Number(sample.seq)===requestedSampleSeq); if(exact)return exact;} if(requestedTimestamp){const target=tsMs(requestedTimestamp); if(target!==null){const before=samples.filter((sample)=>{const ms=tsMs(sample.ts); return ms!==null&&ms<=target}).slice(-1)[0]; if(before)return before;}} const requestedRow=requestedTurn!==null?rows[requestedTurn-1]:null; if(requestedRow?.traceId){const byTrace=bestTraceFrameSample(requestedRow.traceId,numOrNull(requestedRow.lastSeq)); if(byTrace)return byTrace;} if(requestedRow&&requestedRow.lastSeq!==null&&requestedRow.lastSeq!==undefined){const byTurn=samples.find((sample)=>Number(sample.seq)===Number(requestedRow.lastSeq)); if(byTurn)return byTurn;} if(requestedTraceId){const byTrace=bestTraceFrameSample(requestedTraceId); if(byTrace)return byTrace;} return samples[samples.length-1]||null; } function renderTraceFrame(sample,rows){ if(!sample)return {ok:false,renderedText:'TRACE_FRAME_BLOCKER\\nno sample matched --sample-seq/--timestamp/--trace-id/--turn',blocker:'sample-not-found'}; const requestedRow=requestedTurn!==null?rows[requestedTurn-1]:null; const sampleTraceIds=traceIdsFromSamples([sample]); const traceId=requestedTraceId||requestedRow?.traceId||rows.find((row)=>row.lastSeq===sample.seq)?.traceId||sampleTraceIds[0]||null; const traceRows=(Array.isArray(sample.traceRows)?sample.traceRows:[]).filter((row)=>!traceId||row.traceId===traceId||!row.traceId||textOf(row).includes(traceId)); const turns=(Array.isArray(sample.turns)?sample.turns:[]).filter((turn)=>!traceId||turn.traceId===traceId||textOf(turn).includes(traceId)); const texts=[...turns.map(textOf),...traceRows.map(textOf)]; const elapsed=Math.max(-1,...texts.map(parseElapsed).filter((value)=>value!==null)); const recent=Math.max(-1,...texts.map(parseRecent).filter((value)=>value!==null)); const status=statusFor([sample],traceId); const finalResponse=finalResponseFor([sample],traceId); const visibleTraceRows=traceRows.slice(-24); const rowLines=visibleTraceRows.map((row,index)=>{const text=textOf(row); return short((row.status?row.status+' ':'')+text,180)||('row#'+index+' '+(row.textHash||'-'));}); if(traceRows.length>visibleTraceRows.length) rowLines.unshift('(已省略 '+(traceRows.length-visibleTraceRows.length)+' 条较早 trace rows;需要原始数据请看 samples.jsonl)'); const terminalProjectionVisible=status==='completed'&&finalResponse&&!finalResponse.empty&&turns.some((turn)=>String(turn?.status||'').toLowerCase()==='completed'||/completed|轮次完成|final response/iu.test(textOf(turn))); if(rowLines.length===0&&terminalProjectionVisible) rowLines.push('(trace rows collapsed; terminal turn and Final Response are visible)'); const missingRows=rowLines.length===0; const laterTerminalSample=traceId?samples.filter((candidate)=>Number(candidate.seq??0)>Number(sample.seq??0)&&traceIdsFromSamples([candidate]).includes(traceId)&&sampleHasTerminalForTrace(candidate,traceId)).slice(-1)[0]||null:null; const sampleNotice=laterTerminalSample?['TRACE SAMPLE NOTICE','current sample is non-terminal but a later terminal sample exists: seq='+(laterTerminalSample.seq??'-')+' ts='+(laterTerminalSample.ts||'-')+'; rerun with --sample-seq '+(laterTerminalSample.seq??'')+' or omit explicit sample selection.']:[]; const bodyRows=missingRows?['(无 trace rows;这是 blocker,不能当业务通过证据)',...traceFrameDiagnosticLines(sample,traceId,traceRows)]:rowLines; const rendered=['Code Agent 耗时 '+(elapsed>=0?fmtDuration(elapsed):'-')+' 最近 '+(recent>=0?String(recent)+' 秒前':'-')+' ('+status+')','=======================================================','sample seq='+(sample.seq??'-')+' ts='+(sample.ts||'-')+' traceId='+(traceId||'-')+' routeSession='+(sample.routeSessionId||'-')+' activeSession='+(sample.activeSessionId||'-'),...sampleNotice,...bodyRows,'==========================','Final Response',finalResponse.preview||'(空内容)'].join('\\n'); return {ok:!missingRows,renderedText:rendered,blocker:missingRows?'trace-rows-missing':null,sampleSeq:sample.seq??null,traceId,finalResponse,laterTerminalSample:laterTerminalSample?{seq:laterTerminalSample.seq??null,ts:laterTerminalSample.ts??null}:null,traceDiagnostic:missingRows?{pageRole:sample.pageRole||null,pageId:sample.pageId||null,traceRows:Array.isArray(sample.traceRows)?sample.traceRows.length:0,turns:Array.isArray(sample.turns)?sample.turns.length:0,messages:Array.isArray(sample.messages)?sample.messages.length:0,sampleTraceIds:traceIdsFromSamples([sample]).slice(0,12)}:null,valuesRedacted:true}; } function firstPresent(values){ for(const value of values){ if(value!==null&&value!==undefined&&String(value)!=='') return value; } return null; } function commandIdOf(item){return item?.commandId||item?.id||item?.detail?.commandId||item?.input?.commandId||null} function controlSessionId(item){ const detail=item?.detail&&typeof item.detail==='object'?item.detail:{}; return commandSessionId(item)||detail.sessionId||detail.result?.sessionId||detail.observer?.sessionId||null; } function controlTraceId(item){ const detail=item?.detail&&typeof item.detail==='object'?item.detail:{}; return commandTraceId(item)||detail.traceId||detail.chatSubmit?.traceId||detail.result?.traceId||detail.result?.chatSubmit?.traceId||null; } function safeListJsonFiles(rel){ const root=path.join(dir,rel); let entries=[]; try{entries=fs.readdirSync(root,{withFileTypes:true});}catch{return [];} return entries.filter((entry)=>entry.isFile()&&entry.name.endsWith('.json')).map((entry)=>rel+'/'+entry.name); } function readCommandFiles(){ const buckets=['pending','processing','done','failed','abandoned']; const rows=[]; for(const bucket of buckets){ for(const rel of safeListJsonFiles('commands/'+bucket)){ const data=readJson(rel)||{}; let stat=null; try{stat=fs.statSync(path.join(dir,rel));}catch{} const name=path.basename(rel); rows.push({bucket,relative:rel,name,data,mtime:stat?stat.mtime.toISOString():null,valuesRedacted:true}); } } return rows.sort((a,b)=>(tsMs(commandFileTs(a))??0)-(tsMs(commandFileTs(b))??0)||a.relative.localeCompare(b.relative)); } function commandFileId(row){return row?.data?.id||row?.data?.commandId||String(row?.name||'').replace(/[.]json$/u,'')||null} function commandFileType(row){return row?.data?.type||row?.data?.commandType||row?.data?.input?.type||row?.data?.result?.type||null} function commandFileTs(row){return firstPresent([row?.data?.createdAt,row?.data?.completedAt,row?.data?.failedAt,row?.data?.abandonedAt,row?.data?.ts,row?.mtime])} function commandFileSessionId(row){ const data=row?.data||{}; const result=data.result&&typeof data.result==='object'?data.result:{}; const command=data.command&&typeof data.command==='object'?data.command:{}; return data.sessionId||data.input?.sessionId||command.sessionId||result.sessionId||result.observer?.sessionId||sessionIdFromUrl(result.afterUrl)||null; } function commandFileTraceId(row){ const data=row?.data||{}; const result=data.result&&typeof data.result==='object'?data.result:{}; const error=data.error&&typeof data.error==='object'?data.error:{}; return data.traceId||data.input?.traceId||result.traceId||result.chatSubmit?.traceId||error.traceId||null; } function commandFileSummary(row){ const data=row?.data||{}; const result=data.result&&typeof data.result==='object'?data.result:{}; const error=data.error&&typeof data.error==='object'?data.error:{}; const parts=['file='+short(row.relative,48),'ok='+String(data.ok===true),'bucket='+row.bucket]; const duration=firstPresent([result.durationMs,result.observer?.durationMs,error.durationMs]); if(duration!==null) parts.push('durationMs='+String(duration)); const status=firstPresent([result.status,result.phase,error.name,error.failureKind]); if(status!==null) parts.push('status='+short(status,32)); const message=firstPresent([error.message,result.message,result.error?.message]); if(message!==null) parts.push('message='+short(message,120)); return parts.join(' '); } function controlSummary(item){ const detail=item?.detail&&typeof item.detail==='object'?item.detail:{}; const input=item?.input&&typeof item.input==='object'?item.input:{}; const error=detail.error&&typeof detail.error==='object'?detail.error:null; const parts=[]; if(input.textHash||input.hash) parts.push('textHash='+short(input.textHash||input.hash,24)); if(input.textBytes!==undefined) parts.push('textBytes='+String(input.textBytes)); if(detail.durationMs!==undefined) parts.push('durationMs='+String(detail.durationMs)); if(detail.retry||detail.retryLabel) parts.push('retry='+short(detail.retry||detail.retryLabel,24)); if(error?.message) parts.push('error='+short(error.message,120)); const afterPath=pathOnly(item?.afterUrl||detail.afterUrl); if(afterPath) parts.push('afterPath='+short(afterPath,64)); return parts.join(' ')||'-'; } function sampleSummary(sample){ const traceIds=traceIdsFromSamples([sample]).slice(0,4); const counts='turns='+(Array.isArray(sample.turns)?sample.turns.length:0)+' traceRows='+(Array.isArray(sample.traceRows)?sample.traceRows.length:0)+' messages='+(Array.isArray(sample.messages)?sample.messages.length:0); const pathText=sample.path||pathOnly(sample.url)||pathOnly(sample.currentUrl)||'-'; return 'path='+short(pathText,64)+' pageRole='+short(sample.pageRole||'-',18)+' '+counts+' traceIds='+(traceIds.join(',')||'-'); } function isoOrNull(ms){return ms===null||ms===Infinity||ms===-Infinity?null:new Date(ms).toISOString()} function timelineAnchor(rows,commandFiles){ if(requestedCommandId){ const controls=control.filter((item)=>String(commandIdOf(item)||'')===requestedCommandId); const files=commandFiles.filter((item)=>String(commandFileId(item)||'')===requestedCommandId); const times=[...controls.map((item)=>tsMs(item.ts)),...files.map((item)=>tsMs(commandFileTs(item)))].filter((value)=>value!==null); const start=times.length?Math.min(...times):null; const end=times.length?Math.max(...times):null; return {mode:'command-id',value:requestedCommandId,startMs:start,endMs:end,anchorMs:end??start,matchedControlCount:controls.length,matchedCommandFileCount:files.length,valuesRedacted:true}; } if(requestedTurn!==null&&rows[requestedTurn-1]){ const row=rows[requestedTurn-1]; const segment=samples.filter((sample)=>{const seq=numOrNull(sample.seq); return seq!==null&&row.firstSeq!==null&&row.lastSeq!==null&&seq>=Number(row.firstSeq)&&seq<=Number(row.lastSeq);}); const times=[tsMs(row.lastTs),...segment.map((sample)=>tsMs(sample.ts))].filter((value)=>value!==null); const start=times.length?Math.min(...times):null; const end=times.length?Math.max(...times):null; return {mode:'turn',value:String(requestedTurn),commandId:row.commandId||null,traceId:row.traceId||null,startMs:start,endMs:end,anchorMs:end??start,valuesRedacted:true}; } if(requestedTraceId){ const matched=samples.filter((sample)=>traceIdsFromSamples([sample]).includes(requestedTraceId)); const times=matched.map((sample)=>tsMs(sample.ts)).filter((value)=>value!==null); const start=times.length?Math.min(...times):null; const end=times.length?Math.max(...times):null; return {mode:'trace-id',value:requestedTraceId,startMs:start,endMs:end,anchorMs:end??start,matchedSampleCount:matched.length,valuesRedacted:true}; } if(requestedSampleSeq!==null){ const sample=samples.find((item)=>Number(item.seq)===requestedSampleSeq)||null; const anchorMs=tsMs(sample?.ts); return {mode:'sample-seq',value:String(requestedSampleSeq),startMs:anchorMs,endMs:anchorMs,anchorMs,valuesRedacted:true}; } if(requestedTimestamp){ const anchorMs=tsMs(requestedTimestamp); return {mode:'timestamp',value:requestedTimestamp,startMs:anchorMs,endMs:anchorMs,anchorMs,valuesRedacted:true}; } const latestTimes=[...samples.map((sample)=>tsMs(sample.ts)),...control.map((item)=>tsMs(item.ts)),...commandFiles.map((item)=>tsMs(commandFileTs(item)))].filter((value)=>value!==null); const anchorMs=latestTimes.length?Math.max(...latestTimes):null; return {mode:'latest',value:null,startMs:anchorMs,endMs:anchorMs,anchorMs,valuesRedacted:true}; } function timelineWindow(anchor){ const windowMs=requestedWindowMs??120000; if(anchor.startMs!==null&&anchor.endMs!==null&&anchor.startMs!==anchor.endMs){ return {fromMs:Math.max(0,anchor.startMs-1000),toMs:anchor.endMs+1000,windowMs,anchorMode:anchor.mode,valuesRedacted:true}; } if(anchor.anchorMs!==null){ return {fromMs:Math.max(0,anchor.anchorMs-windowMs),toMs:anchor.anchorMs+windowMs,windowMs,anchorMode:anchor.mode,valuesRedacted:true}; } return {fromMs:null,toMs:null,windowMs,anchorMode:anchor.mode,valuesRedacted:true}; } function inTimelineWindow(ts,window){ const ms=tsMs(ts); if(ms===null) return window.fromMs===null&&window.toMs===null; if(window.fromMs!==null&&mswindow.toMs) return false; return true; } function timelineEventRows(anchor,window,commandFiles){ const commandId=anchor.mode==='command-id'?anchor.value:null; const matchedControl=control.filter((item)=>(commandId&&String(commandIdOf(item)||'')===commandId)||inTimelineWindow(item.ts,window)); const matchedSamples=samples.filter((sample)=>inTimelineWindow(sample.ts,window)); const matchedCommands=commandFiles.filter((item)=>(commandId&&String(commandFileId(item)||'')===commandId)||inTimelineWindow(commandFileTs(item),window)); const rows=[ ...matchedControl.map((item)=>({ts:item.ts||null,seq:item.seq??null,kind:'control',phase:item.phase||'-',type:item.type||'-',commandId:commandIdOf(item),sessionId:controlSessionId(item),traceId:controlTraceId(item),summary:controlSummary(item),valuesRedacted:true})), ...matchedSamples.map((sample)=>({ts:sample.ts||null,seq:sample.seq??null,kind:'sample',phase:'sample',type:sample.reason||sample.pageRole||'-',commandId:sample.commandId||null,sessionId:sample.routeSessionId||sample.activeSessionId||null,traceId:traceIdsFromSamples([sample])[0]||null,summary:sampleSummary(sample),valuesRedacted:true})), ...matchedCommands.map((item)=>({ts:commandFileTs(item),seq:null,kind:'command-file',phase:item.bucket,type:commandFileType(item)||'-',commandId:commandFileId(item),sessionId:commandFileSessionId(item),traceId:commandFileTraceId(item),summary:commandFileSummary(item),valuesRedacted:true})), ].filter((item)=>item.ts!==null||item.commandId!==null) .sort((a,b)=>(tsMs(a.ts)??0)-(tsMs(b.ts)??0)||String(a.kind).localeCompare(String(b.kind))||String(a.commandId||'').localeCompare(String(b.commandId||''))); const cap=12; let visible=rows; if(rows.length>cap){ const evidence=rows.filter((row)=>row.kind!=='sample'); const sampleRows=rows.filter((row)=>row.kind==='sample'); visible=evidence.length>=cap?evidence.slice(evidence.length-cap):[...evidence,...sampleRows.slice(sampleRows.length-(cap-evidence.length))] .sort((a,b)=>(tsMs(a.ts)??0)-(tsMs(b.ts)??0)||String(a.kind).localeCompare(String(b.kind))||String(a.commandId||'').localeCompare(String(b.commandId||''))); } return {rows:visible,omittedRowCount:Math.max(0,rows.length-visible.length),matchedControlCount:matchedControl.length,matchedSampleCount:matchedSamples.length,matchedCommandFileCount:matchedCommands.length,valuesRedacted:true}; } function renderTimeline(anchor,window,eventRows){ const lines=['Observe artifact timeline '+(manifest.jobId||'-'),'=======================================================']; lines.push('stateDir='+dir); lines.push('anchor='+anchor.mode+' value='+(anchor.value||'-')+' from='+(isoOrNull(window.fromMs)||'-')+' to='+(isoOrNull(window.toMs)||'-')+' windowMs='+String(window.windowMs)); lines.push('counts control='+String(eventRows.matchedControlCount)+' samples='+String(eventRows.matchedSampleCount)+' commandFiles='+String(eventRows.matchedCommandFileCount)+' shown='+String(eventRows.rows.length)+' omitted='+String(eventRows.omittedRowCount)); lines.push('', 'TIMELINE'); lines.push(pad('TS',24)+' '+pad('SEQ',5)+' '+pad('KIND',12)+' '+pad('PHASE',12)+' '+pad('TYPE',24)+' '+pad('COMMAND',28)+' '+pad('SESSION',18)+' '+pad('TRACE',18)+' SUMMARY'); if(eventRows.rows.length===0) lines.push('(no artifact rows matched; widen --window-ms or choose another --command-id/--timestamp/--turn)'); for(const row of eventRows.rows){ lines.push(pad(row.ts||'-',24)+' '+pad(row.seq??'-',5)+' '+pad(row.kind,12)+' '+pad(row.phase,12)+' '+pad(row.type,24)+' '+pad(row.commandId||'-',28)+' '+pad(row.sessionId||'-',18)+' '+pad(row.traceId||'-',18)+' '+short(row.summary,100)); } const id=manifest.jobId||''; lines.push('', 'NEXT'); lines.push(' turn summary: bun scripts/cli.ts web-probe observe collect '+id+' --view turn-summary'); lines.push(' trace frame: bun scripts/cli.ts web-probe observe collect '+id+' --view trace-frame --trace-id --sample-seq '); lines.push(' command window: bun scripts/cli.ts web-probe observe collect '+id+' --view timeline --command-id '); lines.push(' raw control grep: bun scripts/cli.ts web-probe observe collect '+id+' --view files --file control.jsonl --grep '); lines.push('', 'DISCLOSURE valuesRedacted=true valuesPrinted=false source=existing observe artifacts'); return lines.join('\\n'); } function timelineView(rows){ const commandFiles=readCommandFiles(); const anchor=timelineAnchor(rows,commandFiles); const window=timelineWindow(anchor); const eventRows=timelineEventRows(anchor,window,commandFiles); const timelineRows=eventRows.rows.map((row)=>({ts:row.ts?short(row.ts,32):null,seq:row.seq??null,kind:row.kind,phase:short(row.phase,24),type:short(row.type,40),commandId:row.commandId?short(row.commandId,64):null,sessionId:row.sessionId?short(row.sessionId,64):null,traceId:row.traceId?short(row.traceId,64):null,summary:short(row.summary,140),valuesRedacted:true})); return {anchor:{...anchor,from:isoOrNull(anchor.startMs),to:isoOrNull(anchor.endMs),startMs:undefined,endMs:undefined,anchorMs:undefined},window:{from:isoOrNull(window.fromMs),to:isoOrNull(window.toMs),windowMs:window.windowMs,anchorMode:window.anchorMode,valuesRedacted:true},counts:{control:control.length,samples:samples.length,commandFiles:commandFiles.length,matchedControl:eventRows.matchedControlCount,matchedSamples:eventRows.matchedSampleCount,matchedCommandFiles:eventRows.matchedCommandFileCount,shown:timelineRows.length,omitted:eventRows.omittedRowCount,valuesRedacted:true},timelineRows,renderedText:renderTimeline(anchor,window,eventRows),valuesRedacted:true}; } function pathOnly(value){try{return value?new URL(String(value),'http://x').pathname:null}catch{return null}} function projectSummaryFromSamples(){ const projectSamples=samples.filter((sample)=>sample?.projectManagement&&typeof sample.projectManagement==='object'); const latest=projectSamples[projectSamples.length-1]||null; const latestProject=latest?.projectManagement||null; const projectCommandTypes=new Set(['gotoProjectMdtodo','openMdtodoSourceConfig','closeMdtodoSourceConfig','configureMdtodoHwpodSource','probeMdtodoSource','reindexMdtodoSource','selectProjectSource','selectMdtodoSource','selectMdtodoFile','selectMdtodoTask','expandMdtodoTask','openMdtodoReportPreview','toggleMdtodoReportFullscreen','editMdtodoTaskInline','editMdtodoTaskTitle','editMdtodoTaskBody','toggleMdtodoTaskStatus','addMdtodoRootTask','addMdtodoSubTask','continueMdtodoTask','deleteMdtodoTask','launchWorkbenchFromTask','launchWorkbenchFromMdtodo']); const mutationTypes=new Set(['configureMdtodoHwpodSource','probeMdtodoSource','reindexMdtodoSource','editMdtodoTaskInline','editMdtodoTaskTitle','editMdtodoTaskBody','toggleMdtodoTaskStatus','addMdtodoRootTask','addMdtodoSubTask','continueMdtodoTask','deleteMdtodoTask']); const commandRows=control.filter((item)=>projectCommandTypes.has(item.type)&&(item.phase==='completed'||item.phase==='failed')).map((item)=>{ const detail=item.detail&&typeof item.detail==='object'?item.detail:{}; const error=detail.error&&typeof detail.error==='object'?detail.error:{}; return {ts:item.ts||null,phase:item.phase||null,type:item.type||null,commandId:item.commandId||null,afterPath:pathOnly(item.afterUrl),selectedTaskHash:detail.selectedTask?.hash??detail.projectBeforeClick?.selectedTaskRef?.hash??detail.afterProject?.selectedTaskRef?.hash??null,status:detail.launchStatus??error.details?.launchStatus??null,chatObserved:detail.chatObserved??error.details?.chatObserved??null,workbenchMessageCount:detail.workbenchSnapshot?.messageCount??error.details?.workbenchSnapshot?.messageCount??null,workbenchTraceRowCount:detail.workbenchSnapshot?.traceRowCount??error.details?.workbenchSnapshot?.traceRowCount??null,message:error.message?short(error.message,160):null,valuesRedacted:true}; }); const mutations=commandRows.filter((item)=>mutationTypes.has(item.type)); const launches=control.filter((item)=>(item.type==='launchWorkbenchFromTask'||item.type==='launchWorkbenchFromMdtodo')&&(item.phase==='completed'||item.phase==='failed')).map((item)=>{ const detail=item.detail&&typeof item.detail==='object'?item.detail:{}; const error=detail.error&&typeof detail.error==='object'?detail.error:{}; return {ts:item.ts||null,phase:item.phase||null,commandId:item.commandId||null,status:detail.launchStatus??error.details?.launchStatus??null,sessionId:detail.sessionId??error.details?.sessionId??null,workbenchUrl:detail.workbenchUrl??error.details?.workbenchUrl??null,otelTraceId:detail.otelTraceId??error.details?.otelTraceId??null,chatObserved:detail.chatObserved??error.details?.chatObserved??null,chatStatus:detail.chatStatus??error.details?.chatStatus??null,chatTraceId:detail.chatTraceId??error.details?.chatTraceId??null,workbenchMessageCount:detail.workbenchSnapshot?.messageCount??error.details?.workbenchSnapshot?.messageCount??null,workbenchTraceRowCount:detail.workbenchSnapshot?.traceRowCount??error.details?.workbenchSnapshot?.traceRowCount??null,taskHash:detail.selectedTask?.hash??detail.projectBeforeClick?.selectedTaskRef?.hash??null,message:error.message?short(error.message,160):null,valuesRedacted:true}; }); const findings=Array.isArray(report.findings)?report.findings.filter((item)=>String(item?.id||item?.kind||'').match(/project-management|mdtodo|workbench-launch/u)).slice(0,20):[]; const summary=report.projectManagement?.summary||{}; const mdtodoSampleCount=projectSamples.filter((sample)=>sample.projectManagement?.pageKind==='project-management-mdtodo').length; const derived={enabled:summary.enabled===true||projectSamples.length>0,projectSampleCount:Math.max(Number(summary.projectSampleCount??0),projectSamples.length),mdtodoSampleCount:Math.max(Number(summary.mdtodoSampleCount??0),mdtodoSampleCount),latestPageKind:summary.latestPageKind??latestProject?.pageKind??null,latestPath:summary.latestPath??latest?.path??null,latestSeq:summary.latestSeq??latest?.seq??null,latestTs:summary.latestTs??latest?.ts??null,latestSourceCount:summary.latestSourceCount??latestProject?.sourceCount??null,latestFileCount:summary.latestFileCount??latestProject?.fileCount??null,latestTaskCount:summary.latestTaskCount??latestProject?.taskCount??null,latestSelectedTaskRefHash:summary.latestSelectedTaskRefHash??latestProject?.selectedTaskRef?.hash??null,latestSelectedTaskRefPreview:summary.latestSelectedTaskRefPreview??latestProject?.selectedTaskRef?.preview??null,latestSelectedFileLabelPreview:summary.latestSelectedFileLabelPreview??latestProject?.selectedFileLabel?.textPreview??null,latestSelectedFileLabelLooksDirect:summary.latestSelectedFileLabelLooksDirect??latestProject?.selectedFileLabelLooksDirect??null,selectedTaskBodyVisibleSamples:summary.selectedTaskBodyVisibleSamples??projectSamples.filter((sample)=>sample.projectManagement?.taskBodyVisible===true).length,reportLinkVisibleSamples:summary.reportLinkVisibleSamples??projectSamples.filter((sample)=>Number(sample.projectManagement?.reportLinkCount??0)>0).length,reportPreviewVisibleSamples:summary.reportPreviewVisibleSamples??projectSamples.filter((sample)=>sample.projectManagement?.reportPreviewVisible===true).length,reportFullscreenVisibleSamples:summary.reportFullscreenVisibleSamples??projectSamples.filter((sample)=>sample.projectManagement?.reportFullscreenVisible===true).length,severePaneGapSampleCount:summary.severePaneGapSampleCount??0,ignoredPaneGapSampleCount:summary.ignoredPaneGapSampleCount??0,projectCommandCount:commandRows.length,mutationCommandCount:mutations.length,mutationFailureCount:mutations.filter((item)=>item.phase==='failed').length,launchCommandCount:summary.launchCommandCount??launches.length,launchSuccessCount:summary.launchSuccessCount??launches.filter((item)=>Number(item.status)>=200&&Number(item.status)<300).length,launchFailureCount:summary.launchFailureCount??launches.filter((item)=>item.phase==='failed'||Number(item.status)>=400).length,launchNonEmptyCount:summary.launchNonEmptyCount??launches.filter((item)=>item.chatObserved===true&&(Number(item.workbenchMessageCount??0)>0||Number(item.workbenchTraceRowCount??0)>0)).length,launchEmptyCount:summary.launchEmptyCount??launches.filter((item)=>item.chatObserved!==true||(Number(item.workbenchMessageCount??0)===0&&Number(item.workbenchTraceRowCount??0)===0)).length,launchWithOtelTraceHeaderCount:summary.launchWithOtelTraceHeaderCount??launches.filter((item)=>item.otelTraceId).length,projectApiResponseCount:summary.projectApiResponseCount??null,projectApiFailureCount:summary.projectApiFailureCount??null,projectApiRequestFailedCount:summary.projectApiRequestFailedCount??null,projectApiSlowPathCount:summary.projectApiSlowPathCount??null,valuesRedacted:true}; return {summary:derived,commands:commandRows.slice(-24),mutations:mutations.slice(-16),launches:launches.slice(-12),findings,sampleRows:projectSamples.slice(-12).map((sample)=>({seq:sample.seq??null,ts:sample.ts??null,pageRole:sample.pageRole??null,path:sample.path??null,pageKind:sample.projectManagement?.pageKind??null,sourceCount:sample.projectManagement?.sourceCount??null,fileCount:sample.projectManagement?.fileCount??null,taskCount:sample.projectManagement?.taskCount??null,selectedFileLabelPreview:sample.projectManagement?.selectedFileLabel?.textPreview??null,selectedFileLabelLooksDirect:sample.projectManagement?.selectedFileLabelLooksDirect??null,selectedTaskRefHash:sample.projectManagement?.selectedTaskRef?.hash??null,selectedTaskStatus:sample.projectManagement?.selectedTaskStatus??null,taskBodyVisible:sample.projectManagement?.taskBodyVisible===true,taskBodyBytes:sample.projectManagement?.taskBody?.textBytes??0,reportLinkCount:sample.projectManagement?.reportLinkCount??0,reportPreviewVisible:sample.projectManagement?.reportPreviewVisible===true,reportPreviewBytes:sample.projectManagement?.reportPreview?.textBytes??0,reportFullscreenVisible:sample.projectManagement?.reportFullscreenVisible===true,launchButtonEnabled:sample.projectManagement?.launchButtonEnabled===true,workbenchLinkCount:sample.projectManagement?.workbenchLinkCount??0,valuesRedacted:true})),valuesRedacted:true}; } function targetNodeFromStateDir(){ const parts=String(dir||'').split(/[\\\\/]+/u); const index=parts.lastIndexOf('web-observe'); return index>=0&&parts[index+1]?parts[index+1]:null; } function renderProjectSummary(project){ const s=project.summary||{}; const lines=['Project MDTODO observer '+(manifest.jobId||'-'),'=======================================================','enabled='+String(s.enabled===true)+' samples='+String(s.projectSampleCount??0)+' mdtodo='+String(s.mdtodoSampleCount??0)+' latest='+String(s.latestPageKind||'-')+' path='+String(s.latestPath||'-'),'counts source='+String(s.latestSourceCount??'-')+' file='+String(s.latestFileCount??'-')+' task='+String(s.latestTaskCount??'-')+' selectedTask='+String(s.latestSelectedTaskRefHash||'-'),'fileLabel='+short(s.latestSelectedFileLabelPreview||'-',80)+' direct='+String(s.latestSelectedFileLabelLooksDirect??'-')+' bodyVisibleSamples='+String(s.selectedTaskBodyVisibleSamples??'-')+' reportPreviewSamples='+String(s.reportPreviewVisibleSamples??'-')+' reportFullscreenSamples='+String(s.reportFullscreenVisibleSamples??'-'),'paneGap actionable='+String(s.severePaneGapSampleCount??0)+' ignoredInitialEmptyDetail='+String(s.ignoredPaneGapSampleCount??0),'commands='+String(s.projectCommandCount??0)+' mutations='+String(s.mutationCommandCount??0)+' mutationFailures='+String(s.mutationFailureCount??0),'launch commands='+String(s.launchCommandCount??0)+' success='+String(s.launchSuccessCount??0)+' failure='+String(s.launchFailureCount??0)+' nonEmpty='+String(s.launchNonEmptyCount??'-')+' empty='+String(s.launchEmptyCount??'-')+' otelTraceHeader='+String(s.launchWithOtelTraceHeaderCount??0),'api responses='+String(s.projectApiResponseCount??'-')+' failures='+String(s.projectApiFailureCount??'-')+'/'+String(s.projectApiRequestFailedCount??'-')+' slowPaths='+String(s.projectApiSlowPathCount??'-'),'','Recent samples']; for(const row of project.sampleRows.slice(-12)) lines.push('#'+String(row.seq??'-')+' '+String(row.ts||'-')+' '+String(row.pageRole||'-')+' '+String(row.pageKind||'-')+' src='+String(row.sourceCount??'-')+' files='+String(row.fileCount??'-')+' tasks='+String(row.taskCount??'-')+' fileLabel='+short(row.selectedFileLabelPreview||'-',50)+' body='+String(row.taskBodyVisible===true)+' report='+String(row.reportPreviewVisible===true)+' fullscreen='+String(row.reportFullscreenVisible===true)+' selected='+String(row.selectedTaskRefHash||'-')+' launch='+String(row.launchButtonEnabled===true)+' links='+String(row.workbenchLinkCount??0)); lines.push('','Launches'); if(project.launches.length===0) lines.push('-'); for(const item of project.launches.slice(-12)) lines.push(String(item.ts||'-')+' '+String(item.phase||'-')+' status='+String(item.status??'-')+' session='+String(item.sessionId||'-')+' chat='+String(item.chatObserved===true)+' chatStatus='+String(item.chatStatus??'-')+' msg='+String(item.workbenchMessageCount??'-')+' traceRows='+String(item.workbenchTraceRowCount??'-')+' otel='+String(item.otelTraceId||'-')+' task='+String(item.taskHash||'-')); lines.push('','MDTODO commands'); if(project.commands.length===0) lines.push('-'); for(const item of project.commands.slice(-24)) lines.push(String(item.ts||'-')+' '+String(item.phase||'-')+' '+String(item.type||'-')+' path='+String(item.afterPath||'-')+' task='+String(item.selectedTaskHash||'-')+' status='+String(item.status??'-')+(item.message?' message='+short(item.message,120):'')); lines.push('','MDTODO mutations'); if(project.mutations.length===0) lines.push('-'); for(const item of project.mutations.slice(-16)) lines.push(String(item.ts||'-')+' '+String(item.phase||'-')+' '+String(item.type||'-')+' task='+String(item.selectedTaskHash||'-')+' status='+String(item.status??'-')+(item.message?' message='+short(item.message,120):'')); const otelLaunches=project.launches.filter((item)=>item.otelTraceId).slice(-4); if(otelLaunches.length>0){ const target=targetNodeFromStateDir()||''; lines.push('','OTel trace drill-down'); for(const item of otelLaunches) lines.push('bun scripts/cli.ts platform-infra observability trace --target '+target+' --trace-id '+String(item.otelTraceId)); } lines.push('','Findings'); if(project.findings.length===0) lines.push('-'); for(const item of project.findings.slice(0,12)) lines.push(String(item.severity||'-')+': '+String(item.id||item.kind||'-')+' count='+String(item.count??'-')+' '+short(item.summary||item.message||'',140)); return lines.join('\\n'); } const rows=turnSummaryRows(); if(view==='project-summary'||view==='project-mdtodo-summary'){ const project=projectSummaryFromSamples(); const projectSummary={...project.summary,latestSelectedTaskRefPreview:short(project.summary?.latestSelectedTaskRefPreview,80)}; const projectSampleRows=project.sampleRows.slice(-4).map((item)=>({seq:item.seq??null,ts:short(item.ts,24),pageRole:short(item.pageRole,18),path:short(item.path,36),pageKind:short(item.pageKind,28),sourceCount:item.sourceCount??null,fileCount:item.fileCount??null,taskCount:item.taskCount??null,selectedFileLabelPreview:short(item.selectedFileLabelPreview,80),selectedFileLabelLooksDirect:item.selectedFileLabelLooksDirect??null,selectedTaskRefHash:short(item.selectedTaskRefHash,24),selectedTaskStatus:short(item.selectedTaskStatus,20),taskBodyVisible:item.taskBodyVisible===true,taskBodyBytes:item.taskBodyBytes??0,reportLinkCount:item.reportLinkCount??0,reportPreviewVisible:item.reportPreviewVisible===true,reportPreviewBytes:item.reportPreviewBytes??0,reportFullscreenVisible:item.reportFullscreenVisible===true,launchButtonEnabled:item.launchButtonEnabled===true,workbenchLinkCount:item.workbenchLinkCount??0,valuesRedacted:true})); const compactCommand=(item)=>({ts:short(item.ts,24),phase:item.phase??null,type:short(item.type,28),commandId:short(item.commandId,24),afterPath:short(item.afterPath,36),selectedTaskHash:short(item.selectedTaskHash,24),status:item.status??null,message:item.message?short(item.message,80):null,valuesRedacted:true}); const projectCommands=project.commands.slice(-14).map(compactCommand); const projectMutations=project.mutations.slice(-10).map(compactCommand); const projectLaunches=project.launches.slice(-4).map((item)=>({ts:short(item.ts,24),phase:item.phase??null,commandId:short(item.commandId,24),status:item.status??null,sessionId:short(item.sessionId,28),workbenchUrl:short(item.workbenchUrl,52),otelTraceId:short(item.otelTraceId,28),chatObserved:item.chatObserved??null,chatStatus:item.chatStatus??null,chatTraceId:short(item.chatTraceId,28),workbenchMessageCount:item.workbenchMessageCount??null,workbenchTraceRowCount:item.workbenchTraceRowCount??null,taskHash:short(item.taskHash,24),message:item.message?short(item.message,80):null,valuesRedacted:true})); const projectFindings=project.findings.slice(0,4).map((item)=>({severity:item.severity??item.level??null,id:short(item.id??item.kind??item.code,48),count:item.count??item.sampleCount??null,summary:short(item.summary??item.message,96),valuesRedacted:true})); console.log(JSON.stringify({ok:true,command:'web-probe-observe collect',view,stateDir:dir,summary:projectSummary,sampleRowCount:project.sampleRows.length,commandCount:project.commands.length,mutationCount:project.mutations.length,launchCount:project.launches.length,findingCount:project.findings.length,sampleRows:projectSampleRows,commands:projectCommands,mutations:projectMutations,launches:projectLaunches,findings:projectFindings,sourceFiles:['samples.jsonl','control.jsonl','analysis/report.json'],valuesRedacted:true})); process.exit(0); } if(view==='turn-summary'){ console.log(JSON.stringify({ok:true,command:'web-probe-observe collect',view,stateDir:dir,turnCount:rows.length,rows:rows.slice(0,80),renderedText:renderTurnSummary(rows),sourceFiles:['samples.jsonl','control.jsonl','analysis/report.json'],valuesRedacted:true},null,2)); process.exit(0); } if(view==='timeline'){ const timeline=timelineView(rows); console.log(JSON.stringify({ok:true,command:'web-probe-observe collect',view,stateDir:dir,...timeline,sourceFiles:['samples.jsonl','control.jsonl','commands/pending/*.json','commands/processing/*.json','commands/done/*.json','commands/failed/*.json','commands/abandoned/*.json'],valuesRedacted:true})); process.exit(0); } const frame=renderTraceFrame(selectSample(rows),rows); console.log(JSON.stringify({ok:frame.ok!==false,command:'web-probe-observe collect',view,stateDir:dir,...frame,sourceFiles:['samples.jsonl','control.jsonl','analysis/report.json'],valuesRedacted:true},null,2)); `)} "$state_dir"`; }