From 6a58a06bd12220bb08207798be7e43f54b5beb8a Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Mon, 22 Jun 2026 22:49:25 +0800 Subject: [PATCH] fix: bound web probe collect file output (#686) Co-authored-by: Codex --- scripts/src/hwlab-node-impl.ts | 63 +++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/scripts/src/hwlab-node-impl.ts b/scripts/src/hwlab-node-impl.ts index 87fffc8c..77835646 100644 --- a/scripts/src/hwlab-node-impl.ts +++ b/scripts/src/hwlab-node-impl.ts @@ -7753,13 +7753,15 @@ function runNodeWebProbeObserveCollect(options: NodeWebProbeObserveOptions, spec const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, options.commandTimeoutSeconds); const collect = parseJsonObject(result.stdout); return withWebObserveCollectRendered({ - ok: result.exitCode === 0 && collect?.ok !== false, - status: result.exitCode === 0 ? "collected" : "blocked", + ok: result.exitCode === 0 && collect !== null && collect.ok !== false, + status: result.exitCode === 0 && collect !== null ? "collected" : "blocked", command: webObserveCommandLabel("collect", options), id: webObserveIdFromOptions(options), node: options.node, lane: options.lane, workspace: spec.workspace, + requestedFile: options.collectFile, + degradedReason: collect === null ? "collect-json-parse-failed" : null, collect, result: collect === null ? compactCommandResultWithStdoutTail(result) : compactCommandResult(result), valuesRedacted: true, @@ -7776,10 +7778,10 @@ function withWebObserveCollectRendered(value: Record): Rendered } function renderWebObserveCollectTable(value: Record): string { - const collect = record(value.collect); + const collect = nullableRecord(value.collect); const result = record(value.result); - const file = record(collect.file); - const files = Array.isArray(collect.files) ? collect.files.map(record).filter((item): item is Record => item !== null).slice(0, 20) : []; + const file = record(collect?.file); + const files = Array.isArray(collect?.files) ? collect.files.map(record).filter((item): item is Record => item !== null).slice(0, 20) : []; const jsonlTail = Array.isArray(file.jsonlTail) ? file.jsonlTail.map(record).filter((item): item is Record => item !== null).slice(-8) : []; const jsonSummary = record(file.jsonSummary); const jsonRunnerErrors = Array.isArray(jsonSummary.runnerErrors) ? jsonSummary.runnerErrors.map(record).filter((item): item is Record => item !== null).slice(-8) : []; @@ -7791,11 +7793,29 @@ function renderWebObserveCollectTable(value: Record): string { webObserveText(value.id), webObserveText(value.node), webObserveText(value.lane), - webObserveText(collect.mode ?? "files"), - webObserveShort(webObserveText(collect.stateDir), 88), + webObserveText(collect?.mode ?? "files"), + webObserveShort(webObserveText(collect?.stateDir), 88), ]]), "", ]; + if (collect === null) { + lines.push( + "Collect command:", + webObserveTable(["REQUESTED", "REASON", "EXIT", "TIMED_OUT", "STDOUT_BYTES", "STDOUT_TAIL", "STDERR"], [[ + webObserveShort(webObserveText(value.requestedFile), 64), + webObserveShort(webObserveText(value.degradedReason), 40), + webObserveText(result.exitCode), + webObserveText(result.timedOut), + webObserveText(result.stdoutBytes), + webObserveShort(webObserveText(result.stdoutTail), 160), + webObserveShort(webObserveText(result.stderr), 160), + ]]), + "", + "Disclosure:", + " collect stdout was not valid JSON; fix the collect command or rerun with a narrower --file after the root cause is visible.", + ); + return lines.join("\n"); + } if (collect.mode === "file") { lines.push( "File:", @@ -9214,6 +9234,33 @@ const fs=require('fs'),path=require('path'),crypto=require('crypto'); const dir=process.argv[1]; const selected=process.argv[2]||''; const maxFiles=${maxFiles}; const maxReadBytes=64*1024; const shaFile=(file)=>'sha256:'+crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'); const safeRel=(value)=>Boolean(value)&&!path.isAbsolute(value)&&!value.includes('..')&&!value.includes('\\\\')&&value.split('/').every((part)=>part&&part!=='.'&&part!=='..'); +const short=(value,max=180)=>value===undefined||value===null?undefined:String(value).replace(/\\s+/g,' ').slice(0,max); +const slimJsonl=(value)=>{ + if(!value||typeof value!=='object'||Array.isArray(value)) return {value:short(value)}; + const readiness=value.readiness&&typeof value.readiness==='object'?value.readiness:null; + const snapshot=value.snapshot&&typeof value.snapshot==='object'?value.snapshot:null; + const error=value.error&&typeof value.error==='object'?value.error:null; + return { + ts:short(value.ts||value.timestamp||value.at,32), + seq:value.seq, + type:short(value.type,48), + phase:short(value.phase,32), + commandId:short(value.commandId,40), + commandType:short(value.commandType||value.command?.type,40), + pageRole:short(value.pageRole,20), + path:short(value.path||value.routePath||snapshot?.path,80), + routeSessionId:short(value.routeSessionId||snapshot?.routeSessionId,48), + activeSessionId:short(value.activeSessionId||snapshot?.activeSessionId,48), + messageCount:value.messageCount??snapshot?.messageCount, + traceEventCount:value.traceEventCount??snapshot?.traceEventCount, + method:short(value.method,12), + status:value.status, + url:short(value.url,120), + failure:short(value.failure||value.failureText||value.errorText,120), + readiness:readiness?{ok:readiness.ok,reason:short(readiness.reason,80),path:short(readiness.path,80),domReady:readiness.domReady,workbenchShellVisible:readiness.workbenchShellVisible,sessionCreateVisible:readiness.sessionCreateVisible,commandInputVisible:readiness.commandInputVisible}:undefined, + message:short(value.message||error?.message||value.text,180) + }; +}; if(selected){ if(!safeRel(selected)){console.log(JSON.stringify({ok:false,command:'web-probe-observe collect',stateDir:dir,mode:'file',reason:'unsafe-file',file:selected,valuesRedacted:true},null,2)); process.exit(2);} const file=path.join(dir,selected); const relative=path.relative(dir,file); @@ -9223,7 +9270,7 @@ if(selected){ const lines=text.split(/\\r?\\n/).filter(Boolean); const isJsonl=selected.endsWith('.jsonl'); let jsonSummary=null; if(selected.endsWith('.json')&&!truncated){try{const parsed=JSON.parse(text); jsonSummary={topLevelKeys:parsed&&typeof parsed==='object'&&!Array.isArray(parsed)?Object.keys(parsed).slice(0,40):[],counts:parsed&&parsed.counts||null,runnerErrors:Array.isArray(parsed&&parsed.runnerErrors)?parsed.runnerErrors.slice(-8):[],findings:Array.isArray(parsed&&parsed.findings)?parsed.findings.slice(0,8):[]}}catch(error){jsonSummary={parseError:String(error&&error.message||error).slice(0,160)}}} - const jsonlTail=isJsonl?lines.slice(-20).map((line,index)=>{try{return JSON.parse(line)}catch(error){return {parseError:true,index,lineTail:line.slice(-500),error:String(error&&error.message||error).slice(0,160)}}}):null; + const jsonlTail=isJsonl?lines.slice(-20).map((line,index)=>{try{return slimJsonl(JSON.parse(line))}catch(error){return {parseError:true,index,lineTail:line.slice(-240),error:String(error&&error.message||error).slice(0,160)}}}):null; console.log(JSON.stringify({ok:true,command:'web-probe-observe collect',stateDir:dir,mode:'file',file:{path:file,relative,byteCount:st.size,sha256:shaFile(file),truncated,content:isJsonl||jsonSummary?undefined:text,jsonlTail,jsonSummary,lineCount:lines.length},valuesRedacted:true},null,2)); process.exit(0); }