feat: add project-management web-probe observe

This commit is contained in:
Codex
2026-06-25 11:39:53 +00:00
parent 8d26e6fbc3
commit 80351ce5d2
9 changed files with 1070 additions and 23 deletions
+46 -3
View File
@@ -2,11 +2,11 @@
// Responsibility: Offline CLI view renderers for HWLAB web-probe observe artifacts.
import { shellQuote } from "./ssh";
export type NodeWebProbeObserveCollectView = "files" | "turn-summary" | "trace-frame";
export type NodeWebProbeObserveCollectView = "files" | "turn-summary" | "trace-frame" | "project-summary";
export function parseNodeWebProbeObserveCollectView(value: string): NodeWebProbeObserveCollectView {
if (value === "files" || value === "turn-summary" || value === "trace-frame") return value;
throw new Error(`web-probe observe collect --view must be files, turn-summary, or trace-frame; got ${value}`);
if (value === "files" || value === "turn-summary" || value === "trace-frame" || value === "project-summary") return value;
throw new Error(`web-probe observe collect --view must be files, turn-summary, trace-frame, or project-summary; got ${value}`);
}
export function nodeWebObserveCollectViewNodeScript(input: {
@@ -322,7 +322,50 @@ function renderTraceFrame(sample,rows){
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||'-'),...bodyRows,'==========================','Final Response',finalResponse.preview||'(空内容)'].join('\\n');
return {ok:!missingRows,renderedText:rendered,blocker:missingRows?'trace-rows-missing':null,sampleSeq:sample.seq??null,traceId,finalResponse,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 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 launches=control.filter((item)=>item.type==='launchWorkbenchFromTask'&&(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,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,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,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,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,selectedTaskRefHash:sample.projectManagement?.selectedTaskRef?.hash??null,selectedTaskStatus:sample.projectManagement?.selectedTaskStatus??null,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 management 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||'-'),'launch commands='+String(s.launchCommandCount??0)+' success='+String(s.launchSuccessCount??0)+' failure='+String(s.launchFailureCount??0)+' 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??'-')+' 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||'-')+' otel='+String(item.otelTraceId||'-')+' task='+String(item.taskHash||'-'));
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'){
const project=projectSummaryFromSamples();
console.log(JSON.stringify({ok:true,command:'web-probe-observe collect',view,stateDir:dir,summary:project.summary,sampleRowCount:project.sampleRows.length,launchCount:project.launches.length,findingCount:project.findings.length,renderedText:renderProjectSummary(project),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);