feat: add mdtodo web-probe commands (#898)
Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
@@ -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" | "project-summary";
|
||||
export type NodeWebProbeObserveCollectView = "files" | "turn-summary" | "trace-frame" | "project-summary" | "project-mdtodo-summary";
|
||||
|
||||
export function parseNodeWebProbeObserveCollectView(value: string): NodeWebProbeObserveCollectView {
|
||||
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}`);
|
||||
if (value === "files" || value === "turn-summary" || value === "trace-frame" || value === "project-summary" || value === "project-mdtodo-summary") return value;
|
||||
throw new Error(`web-probe observe collect --view must be files, turn-summary, trace-frame, project-summary, or project-mdtodo-summary; got ${value}`);
|
||||
}
|
||||
|
||||
export function nodeWebObserveCollectViewNodeScript(input: {
|
||||
@@ -322,11 +322,20 @@ 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 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 launches=control.filter((item)=>item.type==='launchWorkbenchFromTask'&&(item.phase==='completed'||item.phase==='failed')).map((item)=>{
|
||||
const projectCommandTypes=new Set(['gotoProjectMdtodo','openMdtodoSourceConfig','configureMdtodoHwpodSource','probeMdtodoSource','reindexMdtodoSource','selectProjectSource','selectMdtodoSource','selectMdtodoFile','selectMdtodoTask','expandMdtodoTask','editMdtodoTaskTitle','editMdtodoTaskBody','toggleMdtodoTaskStatus','addMdtodoRootTask','addMdtodoSubTask','continueMdtodoTask','deleteMdtodoTask','launchWorkbenchFromTask','launchWorkbenchFromMdtodo']);
|
||||
const mutationTypes=new Set(['configureMdtodoHwpodSource','probeMdtodoSource','reindexMdtodoSource','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,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,taskHash:detail.selectedTask?.hash??detail.projectBeforeClick?.selectedTaskRef?.hash??null,message:error.message?short(error.message,160):null,valuesRedacted:true};
|
||||
@@ -334,8 +343,8 @@ function projectSummaryFromSamples(){
|
||||
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};
|
||||
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,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,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,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);
|
||||
@@ -344,11 +353,17 @@ function targetNodeFromStateDir(){
|
||||
}
|
||||
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'];
|
||||
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||'-'),'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)+' 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||'-'));
|
||||
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>';
|
||||
@@ -361,9 +376,9 @@ function renderProjectSummary(project){
|
||||
return lines.join('\\n');
|
||||
}
|
||||
const rows=turnSummaryRows();
|
||||
if(view==='project-summary'){
|
||||
if(view==='project-summary'||view==='project-mdtodo-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}));
|
||||
console.log(JSON.stringify({ok:true,command:'web-probe-observe collect',view,stateDir:dir,summary:project.summary,sampleRowCount:project.sampleRows.length,commandCount:project.commands.length,mutationCount:project.mutations.length,launchCount:project.launches.length,findingCount:project.findings.length,commands:project.commands,mutations:project.mutations,launches:project.launches,renderedText:renderProjectSummary(project),sourceFiles:['samples.jsonl','control.jsonl','analysis/report.json'],valuesRedacted:true}));
|
||||
process.exit(0);
|
||||
}
|
||||
if(view==='turn-summary'){
|
||||
|
||||
Reference in New Issue
Block a user