Files
pikasTech-unidesk/scripts/src/hwlab-node-web-observe-collect.ts
T

648 lines
60 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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<prompts.length?tsMs(prompts[index+1].firstTs):Infinity;
return samples.filter((sample)=>{const ms=tsMs(sample.ts); return ms!==null&&ms>=start&&ms<end});
}
function controlsFor(index,prompts){
const start=tsMs(prompts[index]?.firstTs); const end=index+1<prompts.length?tsMs(prompts[index+1].firstTs):Infinity;
return control.filter((item)=>{const ms=tsMs(item.ts); return ms!==null&&ms>=start&&ms<end});
}
function firstTraceId(text){const match=String(text||'').match(/\\btrc_[A-Za-z0-9_-]+\\b/u); return match?match[0]:null}
function itemTraceId(item){return item?.traceId||firstTraceId(textOf(item))||null}
function entryGroups(sample){
return [
...(Array.isArray(sample.turns)?sample.turns.map((item)=>({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||'<observer>')+' --view trace-frame --trace-id <traceId> --sample-seq <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??'<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&&ms<window.fromMs) return false;
if(window.toMs!==null&&ms>window.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||'<observer>';
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 <traceId> --sample-seq <seq>');
lines.push(' command window: bun scripts/cli.ts web-probe observe collect '+id+' --view timeline --command-id <commandId>');
lines.push(' raw control grep: bun scripts/cli.ts web-probe observe collect '+id+' --view files --file control.jsonl --grep <commandId>');
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()||'<target>';
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"`;
}