// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. web-observe-scripts module for scripts/src/hwlab-node-impl.ts.

// Moved mechanically from scripts/src/hwlab-node-impl.ts:10515-11200 for #903.

// SPEC: PJ2026-01060505 Workbench Performance draft-2026-06-17-p0.
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-25-p0-web-probe-sentinel.
// Responsibility: YAML-first node/lane operations, including Workbench observability control commands.
import { createHash, randomBytes } from "node:crypto";
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { repoRoot, rootPath, type Config } from "../config";
import { runCommand, type CommandResult } from "../command";
import { resolveCliChildJsonCommandResult } from "../cli-child-json-recovery";
import { startJob } from "../jobs";
import { classifySshTcpPoolFailure } from "../ssh";
import { HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, hwlabNodeControlPlaneInfraHelp, runHwlabNodeControlPlaneInfra } from "../hwlab-node-control-plane";
import { hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, hwlabRuntimeLaneSpecForNode, hwlabRuntimeNodeIds, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec, type HwlabRuntimeObservabilityRecordingRuleSpec, type HwlabRuntimeObservabilitySpec, type HwlabRuntimeObservabilityWarningAlertSpec, type HwlabRuntimePublicExposureSpec, type HwlabRuntimeWebProbeAlertThresholdsSpec, type HwlabRuntimeWebProbeProjectManagementSpec } from "../hwlab-node-lanes";
import { nodeWebProbeScriptRunnerSource } from "../hwlab-node-web-probe-runner-source";
import { nodeWebObserveAnalyzerSource } from "../hwlab-node-web-observe-analyzer-source";
import { nodeWebObserveRunnerSource } from "../hwlab-node-web-observe-runner-source";
import { nodeWebObserveCollectViewNodeScript, parseNodeWebProbeObserveCollectView, type NodeWebProbeObserveCollectView } from "../hwlab-node-web-observe-collect";
import { withWebObserveCollectRendered, withWebObserveCommandRendered, withWebObserveStatusRendered } from "../hwlab-node-web-observe-render";
import { buildWebObserveWrapperForObserveOptions, webObserveWrapperStateDirFromStatus } from "../hwlab-node-web-observe-wrapper";
import { renderWebObserveWrapperContract } from "../hwlab-node-web-observe-wrapper-render";
import { runWebProbeSentinelCommand, type WebProbeSentinelOptions } from "../hwlab-node-web-sentinel-cicd";
import { hwlabNodeHelp, hwlabNodeObservabilityHelp, hwlabNodeWebProbeHelp } from "../hwlab-node-help";
import { compactWebProbeResult, compactWebProbeScriptResult } from "../hwlab-node-web-probe-summary";
import { runWebProbeMemoryGuard, webProbeBrowserEvidenceEnvAssignments, webProbeGuardedLaunchShell } from "../hwlab-node-web-probe-memory-guard";
import { nodeObservabilityRecordingRuleExpression, nodeObservabilityRecordingRuleSummaries, nodeObservabilityWarningAlertExpression, nodeObservabilityWarningAlertSummaries } from "../hwlab-node-observability-promql";
import { runDelegatedHwlabNodeCommand, type DelegatedNodeDomain } from "../hwlab-node-transport";
import { runWebProbeRemoteArtifactJob } from "../web-probe-remote-artifact";
import type { RenderedCliResult } from "../output";

import type { BootstrapAdminPasswordMaterial, NodeWebProbeScriptOptions, RuntimeSecretSpec } from "./entry";
import type { NodeWebProbeHostProxyEnv } from "./web-probe-observe";
import { isSafeWebProbeScriptArtifactPath, isSafeWebProbeScriptReportPath, isSafeWebProbeScriptRunDir, runTransWorkspaceStdinScript } from "./public-exposure";
import { compactCommandResultRedacted, nullableRecord, redactKnownSecrets, shellQuote } from "./utils";
import { renderWebProbeScriptResult } from "./web-observe-render";
import { nodeWebProbeHostProxyEnv } from "./web-probe-observe";
import { webProbeOriginSummary } from "./web-probe-origin";

export type WebProbeScriptAuthenticationProfile = {
  readonly mode: "none";
} | {
  readonly mode: "json-post-session-cookie";
  readonly loginPath: string;
  readonly usernameField: string;
  readonly passwordField: string;
  readonly cookieNames: readonly string[];
} | {
  readonly mode: "ui-form-memory";
  readonly loginPath: string;
  readonly usernameSelector: string;
  readonly passwordSelector: string;
  readonly submitSelector: string;
  readonly authenticatedPath: string;
  readonly authenticatedSelector: string;
} | {
  readonly mode: "basic-auth";
  readonly loginPath: string;
  readonly authenticatedSelector: string;
};

export interface WebProbeScriptCredentialMaterial {
  readonly username: string;
  readonly password: string;
}

const hwlabWebProbeAuthentication: WebProbeScriptAuthenticationProfile = {
  mode: "json-post-session-cookie",
  loginPath: "/auth/login",
  usernameField: "username",
  passwordField: "password",
  cookieNames: ["hwlab_session"],
};

export function nodeWebObserveStatusNodeScript(tailLines: number, node: string, lane: string, commandId: string | null = null): string {
  return `node -e ${shellQuote(`
	const fs=require('fs'),path=require('path');
	const dir=process.env.state_dir||process.argv[1];
	const node=process.argv[2];
	const lane=process.argv[3];
	const exactCommandId=process.argv[4]||'';
	const tailN=Math.min(${tailLines},3);
	const readJson=(name)=>{try{return JSON.parse(fs.readFileSync(path.join(dir,name),'utf8'))}catch{return null}};
	const readJsonPath=(file)=>{try{return JSON.parse(fs.readFileSync(file,'utf8'))}catch{return null}};
	const tailJsonl=(name)=>{try{const file=path.join(dir,name); const st=fs.statSync(file); const maxBytes=Math.min(st.size,8*1024*1024); const fd=fs.openSync(file,'r'); try{const buf=Buffer.alloc(maxBytes); fs.readSync(fd,buf,0,maxBytes,st.size-maxBytes); const lines=buf.toString('utf8').split(/\\r?\\n/).filter(Boolean); if(st.size>maxBytes&&lines.length>0) lines.shift(); return lines.slice(-tailN).map(line=>{try{return JSON.parse(line)}catch{return {parseError:true, rawTail:line.slice(-500)}}});}finally{fs.closeSync(fd)}}catch{return []}};
	const short=(value)=>String(value||'').slice(0,160);
	const compactReadiness=(value)=>{const raw=value&&typeof value==='object'?value:null; const snapshot=raw&&raw.snapshot&&typeof raw.snapshot==='object'?raw.snapshot:raw; return raw?{reason:short(raw.reason||snapshot.reason,80),path:short(snapshot.path,120),search:short(snapshot.search,120),blockedReason:short(snapshot.blockedReason,80),readyState:short(snapshot.readyState,24),workbenchShellVisible:snapshot.workbenchShellVisible===true,sessionCreatePresent:snapshot.sessionCreatePresent===true,sessionCreateVisible:snapshot.sessionCreateVisible===true,sessionRailPresent:snapshot.sessionRailPresent===true,sessionRailCollapsed:snapshot.sessionRailCollapsed??null,commandInputPresent:snapshot.commandInputPresent===true,activeTabPresent:snapshot.activeTabPresent===true,loginVisible:snapshot.loginVisible===true,bodyTextBytes:snapshot.bodyTextBytes??null,bodyTextHash:short(snapshot.bodyTextHash,80),valuesRedacted:true}:null};
	const errorReadiness=(error)=>{const details=error&&error.details&&typeof error.details==='object'?error.details:null; return compactReadiness((error&&error.navigationReadiness)||details&&details.readiness||details&&details.readinessAfterWait||details&&details.readinessBeforeClick)};
	const compactManifest=(item)=>item?{jobId:item.jobId,status:item.status,specRef:item.specRef,baseUrl:item.baseUrl,targetPath:item.targetPath,network:item.network,pageAuthority:item.pageAuthority,sampling:item.sampling,safety:item.safety,startedAt:item.startedAt,completedAt:item.completedAt,error:item.error?{message:short(item.error.message),readiness:errorReadiness(item.error),auth:item.error.auth?{lastRetryLabel:item.error.auth.lastRetryLabel||null,retryExhausted:item.error.auth.retryExhausted===true,lastError:short(item.error.auth.lastError||'')}:null}:null}:null;
	const compactAuth=(auth)=>auth?{phase:auth.phase||null,lastRetryLabel:auth.lastRetryLabel||null,retryAttempt:auth.retryAttempt??null,retryMaxAttempts:auth.retryMaxAttempts??null,retryDelayMs:auth.retryDelayMs??null,lastStatus:auth.lastStatus??null,lastStatusText:auth.lastStatusText||null,retryable:auth.retryable??null,cookiePresent:auth.cookiePresent??null,retryExhausted:auth.retryExhausted===true,lastError:short(auth.lastError||'')}:null;
	const compactHeartbeat=(item,diag)=>item?{ok:item.ok,jobId:item.jobId,pid:item.pid,stateDir:item.stateDir,status:item.status,pageId:item.pageId,baseUrl:item.baseUrl,currentUrl:item.currentUrl,sampleSeq:item.sampleSeq,commandSeq:item.commandSeq,activeCommandId:item.activeCommandId,auth:compactAuth(item.auth),updatedAt:item.updatedAt,uptimeMs:item.uptimeMs,ageSeconds:diag.heartbeatAgeSeconds,stale:diag.heartbeatStale,staleAfterSeconds:diag.heartbeatStaleAfterSeconds,effectiveLiveness:diag.effectiveLiveness,error:item.error?{message:short(item.error.message),readiness:errorReadiness(item.error),auth:compactAuth(item.error.auth)}:null}:null;
	const retryLabel=(detail)=>detail&&detail.auth?detail.auth.lastRetryLabel||'':detail&&detail.result?detail.result.lastRetryLabel||'':detail&&detail.error&&detail.error.auth?detail.error.auth.lastRetryLabel||'':'';
	const detailText=(detail)=>{const readiness=detail&&detail.error?errorReadiness(detail.error):null; return detail&&detail.error?short((detail.error.message||'')+(readiness&&readiness.reason?' readiness='+readiness.reason:'')+(readiness&&readiness.blockedReason?' blocked='+readiness.blockedReason:'')+(detail.error.auth&&detail.error.auth.lastError?' '+detail.error.auth.lastError:'')):detail&&detail.result?short([detail.result.statusText,detail.result.retryExhausted?'retry-exhausted':'',detail.result.degradedReason?'degraded='+detail.result.degradedReason:''].filter(Boolean).join(' ')):''};
	const compactControl=(item)=>({ts:item.ts,seq:item.seq,phase:item.phase,type:item.type,commandId:item.commandId,durationMs:item.detail&&item.detail.durationMs||null,retry:retryLabel(item.detail),detail:detailText(item.detail)});
	const compactSample=(item)=>({seq:item.seq,ts:item.ts,path:item.path,routeSessionId:item.routeSessionId||null,activeSessionId:item.activeSessionId||null,messageCount:Array.isArray(item.messages)?item.messages.length:0,traceRowCount:Array.isArray(item.traceRows)?item.traceRows.length:0,loadingCount:Array.isArray(item.loadings)?item.loadings.length:0,loadingOwners:Array.isArray(item.loadings)?Array.from(new Set(item.loadings.map((loading)=>loading.ownerLabel||loading.ownerKind||loading.ownerKey||'loading'))).slice(0,4):[],sessionRail:item.sessionRail?{visibleCount:item.sessionRail.visibleCount??null,fallbackTitleCount:item.sessionRail.fallbackTitleCount??null,fallbackTitleRatio:item.sessionRail.fallbackTitleRatio??null,fallbackItems:Array.isArray(item.sessionRail.fallbackItems)?item.sessionRail.fallbackItems.slice(0,4).map((fallback)=>({sessionIdPrefix:fallback.sessionIdPrefix??null,titlePreview:fallback.titlePreview??null,active:fallback.active===true})):[]}:null});
	const compactNetwork=(item)=>({ts:item.ts,type:item.type,method:item.method,url:short(item.url),status:item.status||null,failure:item.failure?short(item.failure):null});
	const commandDir=(name)=>path.join(dir,'commands',name);
	const compactReplayEvidence=(value)=>{
	  const item=value&&typeof value==='object'?value:null;if(!item)return{};
	  const layers=item.layerCounts&&typeof item.layerCounts==='object'?item.layerCounts:null;
	  const server=layers&&layers.server&&typeof layers.server==='object'?layers.server:null;
	  const client=layers&&layers.client&&typeof layers.client==='object'?layers.client:null;
	  const layerDom=item.layerDom&&typeof item.layerDom==='object'?item.layerDom:null;
	  const layerStable=item.layerDomStabilization&&typeof item.layerDomStabilization==='object'?item.layerDomStabilization:null;
	  const trace=item.traceTimeline&&typeof item.traceTimeline==='object'?item.traceTimeline:null;
	  const raw=item.rawHwlabEventWindow&&typeof item.rawHwlabEventWindow==='object'?item.rawHwlabEventWindow:null;
	  const rawCounts=raw&&raw.counts&&typeof raw.counts==='object'?raw.counts:null;
	  return{
	    phase:item.phase||null,code:item.code||null,
	    layerCounts:layers?{server:server?{scanned:server.scanned??null,matched:server.matched??null,delivered:server.delivered??null}:null,client:client?{received:client.received??null,decoded:client.decoded??null,applied:client.applied??null}:null,valuesRedacted:true}:null,
	    layerDom:layerDom?{matchCount:layerDom.matchCount??null,visibleCount:layerDom.visibleCount??null,connectedCount:layerDom.connectedCount??null,textPresent:layerDom.textPresent??null,textBytes:layerDom.textBytes??null,textHash:layerDom.textHash||null,textPreview:short(layerDom.textPreview),valuesRedacted:true}:null,
	    layerDomStabilization:layerStable?{stable:layerStable.stable??null,timedOut:layerStable.timedOut??null,sampleCount:layerStable.sampleCount??null,elapsedMs:layerStable.elapsedMs??null,stableQuietMs:layerStable.stableQuietMs??null,valuesRedacted:true}:null,
	    traceTimeline:trace?{contract:trace.contract||null,authorityAvailable:trace.authorityAvailable??null,rowCount:trace.rowCount??null,sourceAuthorityRowCount:trace.sourceAuthorityRowCount??null,projectedAuthorityRowCount:trace.projectedAuthorityRowCount??null,readableSourceRowCount:trace.readableSourceRowCount??null,valuesRedacted:true}:null,
	    rawHwlabEventWindow:raw?{available:raw.available??null,contract:raw.contract||null,reason:raw.reason||null,samePage:raw.samePage??null,productEventsPath:raw.productEventsPath||null,productEventSourceRequestCount:raw.productEventSourceRequestCount??null,sourceContractPresent:raw.sourceContractPresent??null,unfilteredContractPresent:raw.unfilteredContractPresent??null,textBytes:raw.textBytes??null,textHash:raw.textHash||null,textPresent:raw.textPresent??null,counts:rawCounts?{received:rawCounts.received??null,retained:rawCounts.retained??null,decoded:rawCounts.decoded??null,rejected:rawCounts.rejected??null,evicted:rawCounts.evicted??null,bytes:rawCounts.bytes??null}:null,valuesRedacted:true}:null
	  };
	};
	const compactExistingSessionRefreshEvidence=(value)=>{
	  const item=value&&typeof value==='object'?value:null;if(!item)return{};
	  const replay=item.refreshReplay&&typeof item.refreshReplay==='object'?item.refreshReplay:null;
	  const counts=replay&&replay.counts&&typeof replay.counts==='object'?replay.counts:null;
	  const product=item.productSse&&typeof item.productSse==='object'?item.productSse:null;
	  const before=item.before&&typeof item.before==='object'?item.before:null;
	  const after=item.after&&typeof item.after==='object'?item.after:null;
	  const failure=item.failure&&typeof item.failure==='object'?item.failure:null;
	  const streamFailure=failure&&failure.streamFailure&&typeof failure.streamFailure==='object'?failure.streamFailure:item.streamFailure&&typeof item.streamFailure==='object'?item.streamFailure:null;
	  const compactDom=(dom)=>dom?{messageCount:dom.messageCount??null,traceIds:Array.isArray(dom.traceIds)?dom.traceIds.slice(0,4):[],missingIdentityCount:Array.isArray(dom.missingIdentityRows)?dom.missingIdentityRows.length:null,duplicateIdentityCount:Array.isArray(dom.duplicateIdentities)?dom.duplicateIdentities.length:null,valuesRedacted:true}:null;
	  return{
	    before:compactDom(before),after:compactDom(after),
	    refreshReplay:replay?{phase:replay.phase||null,topic:replay.topic||null,topicPartitions:Array.isArray(replay.topicPartitions)?replay.topicPartitions.slice(0,8):[],barrier:Array.isArray(replay.barrier)?replay.barrier.slice(0,8).map((entry)=>({partition:entry?.partition??null,endOffset:entry?.endOffset||null})):[],counts:counts?{matched:counts.matched??null,replayed:counts.replayed??null,buffered:counts.buffered??null,bufferedDelivered:counts.bufferedDelivered??null,liveDelivered:counts.liveDelivered??null,deduplicated:counts.deduplicated??null}:null,valuesRedacted:true}:null,
	    productSse:product?{requestCount:product.requestCount??null,browserSourceCount:product.browserSourceCount??null,openCount:product.openCount??null,connectedEventCount:product.connectedEventCount??null,businessEventCount:product.businessEventCount??null,typedFailureCount:product.typedFailureCount??null,livePhase:product.livePhase??null,valuesRedacted:true}:null,
	    failure:failure||streamFailure?{phase:failure&&failure.phase||item.phase||null,code:failure&&failure.code||item.code||null,streamPhase:failure&&failure.streamPhase||streamFailure&&streamFailure.phase||null,streamCode:failure&&failure.streamCode||streamFailure&&streamFailure.code||null,message:short(streamFailure&&streamFailure.message||failure&&failure.error&&failure.error.message||'',200),valuesRedacted:true}:null
	  };
	};
	const compactTraceSnapshot=(value)=>{const item=value&&typeof value==='object'?value:null;if(!item)return null;const disclosure=item.disclosure&&typeof item.disclosure==='object'?item.disclosure:null;return{status:item.status||null,traceId:item.traceId||null,disclosure:disclosure?{available:disclosure.available??null,open:disclosure.open??null,eventCount:disclosure.eventCount??null,readableRowCount:disclosure.readableRowCount??null,renderedRowCount:disclosure.renderedRowCount??null,rowWindowed:disclosure.rowWindowed??null}:null,rowCount:item.rowCount??null,readableRowCount:item.readableRowCount??null,sourceAuthorityRowCount:item.sourceAuthorityRowCount??null,readableSourceRowCount:item.readableSourceRowCount??null,projectedAuthorityRowCount:item.projectedAuthorityRowCount??null,rowSetHash:item.rowSetHash||null,valuesRedacted:true};};
	const compactTracePhase=(value)=>{const item=value&&typeof value==='object'?value:null;return item?{observed:item.observed??null,stable:item.stable??null,reason:item.reason||null,status:item.status||null,elapsedMs:item.elapsedMs??null,sampleCount:item.sampleCount??null,snapshot:compactTraceSnapshot(item.snapshot),valuesRedacted:true}:null;};
	const compactTraceReadabilityEvidence=(value)=>{const item=value&&typeof value==='object'?value:null;if(!item)return{};const scope=item.scope&&typeof item.scope==='object'?item.scope:null;const disclosure=item.disclosure&&typeof item.disclosure==='object'?item.disclosure:null;const retention=item.runningRetention&&typeof item.runningRetention==='object'?item.runningRetention:null;return{startedDuringRunning:item.startedDuringRunning??null,forcedExpansion:item.forcedExpansion??null,scope:scope?{selector:short(scope.selector),conversationCount:scope.conversationCount??null,productCardCount:scope.productCardCount??null,debugPanelCountInsideConversation:scope.debugPanelCountInsideConversation??null,isolatedDebugExcluded:scope.isolatedDebugExcluded??null,valuesRedacted:true}:null,disclosure:disclosure?{openBefore:disclosure.openBefore??null,openAfterExpansion:disclosure.openAfterExpansion??null,openAfterTerminal:disclosure.openAfterTerminal??null,autoReadable:disclosure.autoReadable??null,valuesRedacted:true}:null,running:compactTracePhase(item.running),terminalArrival:compactTracePhase(item.terminalArrival),terminal:compactTracePhase(item.terminal),runningRetention:retention?{mode:retention.mode||null,passed:retention.passed??null,retainedRowCount:retention.retainedRowCount??null,runningEventCount:retention.runningEventCount??null,terminalEventCount:retention.terminalEventCount??null,runningSourceSeqMin:retention.runningSourceSeqMin??null,runningSourceSeqMax:retention.runningSourceSeqMax??null,terminalSourceSeqMin:retention.terminalSourceSeqMin??null,terminalSourceSeqMax:retention.terminalSourceSeqMax??null,valuesRedacted:true}:null,retainedRunningRowCount:item.retainedRunningRowCount??null,failures:Array.isArray(item.failures)?item.failures.slice(0,8).map((failure)=>({code:failure&&failure.code||null,message:short(failure&&failure.message),valuesRedacted:true})):null};};
	const compactScreenshot=(value)=>value&&typeof value==='object'?{path:value.path||null,sha256:value.sha256||null,available:value.available??null,valuesRedacted:true}:null;
	const compactPerformanceCapture=(value)=>{const item=value&&typeof value==='object'?value:null;const artifact=item&&item.artifact&&typeof item.artifact==='object'?item.artifact:null;if(!item&&!artifact)return{};return{captureId:item&&item.captureId||artifact&&artifact.captureId||null,durationMs:item&&item.durationMs!==undefined?item.durationMs:artifact&&artifact.durationMs!==undefined?artifact.durationMs:null,profilePath:artifact&&artifact.path||null,profileSha256:artifact&&artifact.sha256||null,reportPath:artifact&&artifact.summaryPath||null,reportSha256:artifact&&artifact.summarySha256||null,valuesRedacted:true};};
	const commandSummary=(name)=>{
	  const root=commandDir(name); let entries=[];
	  try{entries=fs.readdirSync(root).filter((item)=>item.endsWith('.json')).sort();}catch{}
	  const items=entries.slice(0,12).map((entry)=>{const file=path.join(root,entry); const parsed=readJsonPath(file)||{}; let stat=null; try{stat=fs.statSync(file)}catch{}; const createdAt=parsed.createdAt||parsed.abandonedAt||parsed.completedAt||parsed.failedAt||null; const createdMs=Date.parse(String(createdAt||'')); const ageSeconds=Number.isFinite(createdMs)?Math.max(0,Math.round((Date.now()-createdMs)/1000)):null; return {id:parsed.id||parsed.commandId||entry.replace(/[.]json$/,''),type:parsed.type||null,createdAt,ageSeconds,mtime:stat?stat.mtime.toISOString():null};});
	  const ages=items.map((item)=>item.ageSeconds).filter((value)=>Number.isFinite(value));
	  return {count:entries.length,oldestAgeSeconds:ages.length?Math.max(...ages):null,items};
	};
	const exactCommand=()=>{
	  if(!exactCommandId)return null;
	  for(const bucket of ['done','failed','abandoned','processing','pending']){
	    const parsed=readJsonPath(path.join(commandDir(bucket),exactCommandId+'.json'));
	    if(!parsed)continue;
	    const result=parsed.result&&typeof parsed.result==='object'?parsed.result:null;
	    const error=parsed.error&&typeof parsed.error==='object'?parsed.error:null;
	    return {commandId:exactCommandId,phase:bucket,ok:parsed.ok??null,type:parsed.type||null,createdAt:parsed.createdAt||null,completedAt:parsed.completedAt||null,failedAt:parsed.failedAt||null,result:result?{ok:result.ok??null,status:result.status||null,sessionId:result.sessionId||null,traceId:result.traceId||null,traceIds:Array.isArray(result.traceIds)?result.traceIds.slice(0,4):[],runIds:Array.isArray(result.runIds)?result.runIds.slice(0,4):[],commandIds:Array.isArray(result.commandIds)?result.commandIds.slice(0,4):[],topic:result.topic||null,groupId:result.groupId||null,groupPrefix:result.groupPrefix||null,receivedCount:result.receivedCount??null,appliedCount:result.appliedCount??null,terminalStatus:result.terminalStatus||null,...compactReplayEvidence(result),...(parsed.type==='validateExistingSessionRefresh'?compactExistingSessionRefreshEvidence(result):{}),...compactTraceReadabilityEvidence(result),warmRunnerReused:result.warmRunnerReused??null,forbiddenRequestCount:result.forbiddenRequestCount??null,replayedEventCount:result.replayedEventCount??null,reportPath:result.reportPath||null,reportSha256:result.reportSha256||null,screenshot:compactScreenshot(result.screenshot),...(parsed.type==='performanceCapture'?compactPerformanceCapture(result):{profile:result.profile||null}),valuesRedacted:true}:null,error:error?{name:error.name||null,message:short(error.message,200),details:error.details&&typeof error.details==='object'?{...compactReplayEvidence(error.details),...(parsed.type==='validateExistingSessionRefresh'?compactExistingSessionRefreshEvidence(error.details):{}),...compactTraceReadabilityEvidence(error.details),sessionId:error.details.sessionId||null,traceId:error.details.traceId||null,traceIds:Array.isArray(error.details.traceIds)?error.details.traceIds.slice(0,4):[],topic:error.details.topic||null,groupId:error.details.groupId||null,receivedCount:error.details.receivedCount??null,appliedCount:error.details.appliedCount??null,terminalStatus:error.details.terminalStatus||null,forbiddenRequestCount:error.details.forbiddenRequestCount??null,reportPath:error.details.reportPath||null,reportSha256:error.details.reportSha256||null,screenshot:compactScreenshot(error.details.screenshot),...(parsed.type==='performanceCapture'?compactPerformanceCapture(error.details):{profile:error.details.profile||null}),valuesRedacted:true}:null}:null,valuesRedacted:true};
	  }
	  return {commandId:exactCommandId,phase:'not-found',ok:false,valuesRedacted:true};
	};
	const pidText=fs.existsSync(path.join(dir,'pid'))?fs.readFileSync(path.join(dir,'pid'),'utf8').trim():null;
	let alive=false; if(pidText){try{process.kill(Number(pidText),0); alive=true}catch{}}
	const manifest=readJson('manifest.json');
	const heartbeat=readJson('heartbeat.json');
	const terminal=/^(completed|failed|force-stopped|stopped|abandoned)$/u.test(String((heartbeat&&heartbeat.status)||(manifest&&manifest.status)||''));
	const sampleIntervalMs=Number(manifest&&manifest.sampling&&manifest.sampling.sampleIntervalMs)||5000;
	const staleAfterMs=Math.max(60000,sampleIntervalMs*3);
	const updatedRaw=heartbeat&&(heartbeat.updatedAt||heartbeat.lastSampleAt)||null;
	const updatedMs=Date.parse(String(updatedRaw||''));
	const heartbeatAgeSeconds=Number.isFinite(updatedMs)?Math.max(0,Math.round((Date.now()-updatedMs)/1000)):null;
	const heartbeatStale=alive&&!terminal&&(!Number.isFinite(updatedMs)||Date.now()-updatedMs>staleAfterMs);
	const commandsPending=commandSummary('pending');
	const commandsProcessing=commandSummary('processing');
	const commandsAbandoned=commandSummary('abandoned');
	const commandsFailed=commandSummary('failed');
	const diagnostics={heartbeatAgeSeconds,heartbeatStale,heartbeatStaleAfterSeconds:Math.round(staleAfterMs/1000),heartbeatUpdatedAt:updatedRaw,terminal,processAlive:alive,effectiveLiveness:heartbeatStale?'stale':alive?'alive':'not-running',commandBacklog:commandsPending.count+commandsProcessing.count,oldestPendingAgeSeconds:commandsPending.oldestAgeSeconds,oldestProcessingAgeSeconds:commandsProcessing.oldestAgeSeconds,valuesRedacted:true};
	const commands={pendingCount:commandsPending.count,processingCount:commandsProcessing.count,abandonedCount:commandsAbandoned.count,failedCount:commandsFailed.count,pending:commandsPending.items,processing:commandsProcessing.items,abandoned:commandsAbandoned.items.slice(0,6),failed:commandsFailed.items.slice(0,6),valuesRedacted:true};
	console.log(JSON.stringify({ok:true,command:'web-probe-observe status',stateDir:dir,pid:pidText?Number(pidText):null,processAlive:alive,manifest:compactManifest(manifest),heartbeat:compactHeartbeat(heartbeat,diagnostics),diagnostics,commands,exactCommand:exactCommand(),tails:{control:tailJsonl('control.jsonl').map(compactControl),samples:tailJsonl('samples.jsonl').map(compactSample),network:tailJsonl('network.jsonl').map(compactNetwork)},next:{command:'bun scripts/cli.ts web-probe observe command --node '+node+' --lane '+lane+' --state-dir '+dir+' --type mark --label checkpoint',stop:'bun scripts/cli.ts web-probe observe stop --node '+node+' --lane '+lane+' --state-dir '+dir,forceStop:'bun scripts/cli.ts web-probe observe stop --node '+node+' --lane '+lane+' --state-dir '+dir+' --force',analyze:'bun scripts/cli.ts web-probe observe analyze --node '+node+' --lane '+lane+' --state-dir '+dir},valuesRedacted:true}));
	`)} "$state_dir" ${shellQuote(node)} ${shellQuote(lane)} ${shellQuote(commandId ?? "")}`;
}

export function nodeWebObserveForceStopNodeScript(reason: string, forcedByCommandId: string): string {
  return `node -e ${shellQuote(`
	const fs=require('fs'),path=require('path'),crypto=require('crypto');
	const dir=process.argv[1],reason=process.argv[2],forcedByCommandId=process.argv[3];
	const now=new Date().toISOString();
	const short=(value,max=160)=>value===undefined||value===null?null:String(value).replace(/\\s+/g,' ').slice(0,max);
	const shaText=(value)=>'sha256:'+crypto.createHash('sha256').update(String(value||'')).digest('hex');
	const readJson=(file)=>{try{return JSON.parse(fs.readFileSync(file,'utf8'))}catch{return null}};
	const writeJson=(file,value)=>{fs.mkdirSync(path.dirname(file),{recursive:true,mode:0o700}); fs.writeFileSync(file,JSON.stringify(value,null,2)+'\\n',{mode:0o600});};
	const appendJsonl=(file,value)=>{fs.appendFileSync(file,JSON.stringify(value)+'\\n',{mode:0o600});};
	const summarizeCommand=(command,id)=>({id,type:command&&command.type||null,createdAt:command&&command.createdAt||null,source:command&&command.source||null,path:command&&command.path||null,label:command&&command.label||null,sessionId:command&&command.sessionId||null,provider:command&&command.provider||null,textHash:typeof(command&&command.text)==='string'?shaText(command.text):null,textBytes:typeof(command&&command.text)==='string'?Buffer.byteLength(command.text):null,valuesRedacted:true});
	const moveCommands=(bucket)=>{
	  const source=path.join(dir,'commands',bucket); let names=[];
	  try{names=fs.readdirSync(source).filter((name)=>name.endsWith('.json')).sort();}catch{}
	  const moved=[];
	  for(const name of names){
	    const file=path.join(source,name);
	    const parsed=readJson(file)||{};
	    const id=String(parsed.id||parsed.commandId||name.replace(/[.]json$/,'')).replace(/[^A-Za-z0-9_.-]/g,'-')||name.replace(/[.]json$/,'');
	    const record={ok:false,status:'abandoned',commandId:id,type:parsed.type||null,abandonedAt:now,reason,sourceBucket:bucket,forcedByCommandId,command:summarizeCommand(parsed,id),valuesRedacted:true};
	    writeJson(path.join(dir,'commands','abandoned',id+'.json'),record);
	    try{fs.unlinkSync(file)}catch{}
	    appendJsonl(path.join(dir,'control.jsonl'),{ts:now,seq:null,phase:'abandoned',type:parsed.type||null,commandId:id,detail:{reason,sourceBucket:bucket,forcedByCommandId,valuesRedacted:true},valuesRedacted:true});
	    moved.push({id,type:parsed.type||null,sourceBucket:bucket,createdAt:parsed.createdAt||null});
	  }
	  return moved;
	};
	const pidFile=path.join(dir,'pid');
	const pidText=fs.existsSync(pidFile)?fs.readFileSync(pidFile,'utf8').trim():'';
	const pid=Number(pidText);
	const alive=()=>Number.isFinite(pid)&&pid>0?(()=>{try{process.kill(pid,0); return true;}catch{return false;}})():false;
	const sleep=(ms)=>Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,ms);
	const aliveBefore=alive();
	let termSent=false,killSent=false;
	if(aliveBefore){try{process.kill(pid,'SIGTERM'); termSent=true;}catch{}}
	for(let i=0;i<10&&alive();i+=1) sleep(250);
	if(alive()){try{process.kill(pid,'SIGKILL'); killSent=true;}catch{}}
	for(let i=0;i<8&&alive();i+=1) sleep(250);
	const pending=moveCommands('pending');
	const processing=moveCommands('processing');
	const aliveAfter=alive();
	const manifestPath=path.join(dir,'manifest.json');
	const heartbeatPath=path.join(dir,'heartbeat.json');
	const manifest=readJson(manifestPath)||{};
	const heartbeat=readJson(heartbeatPath)||{};
	writeJson(manifestPath,{...manifest,ok:false,status:'force-stopped',forceStoppedAt:now,forceStop:{reason,forcedByCommandId,pid:Number.isFinite(pid)?pid:null,termSent,killSent,aliveBefore,aliveAfter,pendingAbandoned:pending.length,processingAbandoned:processing.length,valuesRedacted:true}});
	writeJson(heartbeatPath,{...heartbeat,ok:false,status:'force-stopped',updatedAt:now,forceStoppedAt:now,forceStop:{reason,forcedByCommandId,pid:Number.isFinite(pid)?pid:null,termSent,killSent,aliveBefore,aliveAfter,pendingAbandoned:pending.length,processingAbandoned:processing.length,valuesRedacted:true}});
	appendJsonl(path.join(dir,'control.jsonl'),{ts:now,seq:null,phase:'completed',type:'forceStop',commandId:forcedByCommandId,detail:{reason,pid:Number.isFinite(pid)?pid:null,termSent,killSent,aliveBefore,aliveAfter,pendingAbandoned:pending.length,processingAbandoned:processing.length,valuesRedacted:true},valuesRedacted:true});
	console.log(JSON.stringify({ok:!aliveAfter,command:'web-probe-observe force-stop',stateDir:dir,forced:true,reason,forcedByCommandId,pid:Number.isFinite(pid)?pid:null,termSent,killSent,aliveBefore,aliveAfter,pendingAbandoned:pending.length,processingAbandoned:processing.length,abandoned:[...pending,...processing].slice(0,20),valuesRedacted:true}));
	`)} "$state_dir" ${shellQuote(reason)} ${shellQuote(forcedByCommandId)}`;
}

export function nodeWebObserveCollectNodeScript(maxFiles: number, collectFile: string | null, collectFinding: string | null, collectGrep: string | null): string {
  return `node -e ${shellQuote(`
const fs=require('fs'),path=require('path'),crypto=require('crypto');
const dir=process.argv[1]; const findingFilter=process.argv[3]||''; let selected=(process.argv[2]||'') || (findingFilter ? 'analysis/report.json' : ''); const grepText=process.argv[4]||''; const maxFiles=${maxFiles}; const maxReadBytes=64*1024; const maxJsonlTailBytes=1024*1024; const maxTextPreviewBytes=4096; const maxGrepContextLines=24; const maxGrepLineText=180;
const shaFile=(file)=>'sha256:'+crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
const shaText=(value)=>'sha256:'+crypto.createHash('sha256').update(String(value||'')).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 grepLines=(text,pattern)=>{
  if(!pattern) return null;
  const needle=String(pattern).toLowerCase();
  const lines=String(text||'').split(/\\r?\\n/);
  const contexts=[]; let matchCount=0; const emitted=new Set();
  for(let index=0; index<lines.length; index++){
    if(!lines[index].toLowerCase().includes(needle)) continue;
    matchCount++;
    for(let offset=-2; offset<=2; offset++){
      const lineIndex=index+offset;
      if(lineIndex<0||lineIndex>=lines.length) continue;
      if(emitted.has(lineIndex)) continue;
      emitted.add(lineIndex);
      contexts.push({lineNo:lineIndex+1,match:offset===0,text:short(lines[lineIndex],maxGrepLineText)});
      if(contexts.length>=maxGrepContextLines) break;
    }
    if(contexts.length>=maxGrepContextLines) break;
  }
  return {patternSha256:shaText(pattern),matchCount,returnedLineCount:contexts.length,truncated:matchCount>0&&contexts.length>=maxGrepContextLines,lines:contexts,valuesRedacted:true};
};
const slimObject=(value,maxKeys=24)=>{
  if(!value||typeof value!=='object'||Array.isArray(value)) return value??null;
  const out={};
  for(const key of Object.keys(value).slice(0,maxKeys)){
    const item=value[key];
    if(item===null||item===undefined||typeof item==='number'||typeof item==='boolean'||typeof item==='string') out[key]=short(item,160);
    else if(Array.isArray(item)) out[key]={arrayLength:item.length};
    else out[key]={keys:Object.keys(item).slice(0,12)};
  }
  return out;
};
const sensitiveKey=(key)=>/token|secret|password|passwd|authorization|cookie|api[-_]?key|session/i.test(String(key||''));
const slimJsonValue=(value,depth=0)=>{
  if(value===null||value===undefined) return value??null;
  if(typeof value==='number'||typeof value==='boolean') return value;
  if(typeof value==='string') return short(value,360);
  if(depth>=4) return Array.isArray(value)?{arrayLength:value.length}:{keys:Object.keys(value||{}).slice(0,20)};
  if(Array.isArray(value)) return value.slice(0,12).map((item)=>slimJsonValue(item,depth+1)).concat(value.length>12?[{omitted:value.length-12}]:[]);
  if(typeof value==='object'){
    const out={};
    const keys=Object.keys(value).slice(0,40);
    for(const key of keys) out[key]=sensitiveKey(key)?'[redacted]':slimJsonValue(value[key],depth+1);
    const omitted=Object.keys(value).length-keys.length;
    if(omitted>0) out.__omittedKeys=omitted;
    return out;
  }
  return short(value,180);
};
const slimFinding=(item)=>{
  if(!item||typeof item!=='object') return null;
  const samples=Array.isArray(item.samples)?item.samples.length:null;
  const groups=Array.isArray(item.groups)?item.groups.length:null;
  const evidence=item.evidence&&typeof item.evidence==='object'?slimObject(item.evidence,10):null;
  return {
    kind:short(item.kind||item.code||item.id,80),
    severity:short(item.severity||item.level,24),
    count:item.count??item.sampleCount??null,
    summary:short(item.summary||item.message,240),
    rootCause:short(item.rootCause,140),
    rootCauseStatus:short(item.rootCauseStatus,90),
    rootCauseConfidence:short(item.rootCauseConfidence,40),
    nextAction:short(item.nextAction,240),
    evidenceSummary:evidence?short(JSON.stringify(evidence),240):null,
    sampleRows:samples,
    groupRows:groups
  };
};
const firstArray=(...values)=>values.find((value)=>Array.isArray(value))||[];
const compactMetric=(value)=>value===undefined||value===null?undefined:(typeof value==='object'?short(JSON.stringify(slimObject(value,8)),160):short(value,160));
const compactProjectionPair=(messageCount,traceCount)=>messageCount===undefined&&traceCount===undefined?undefined:'msg='+String(messageCount??'-')+' trace='+String(traceCount??'-');
const firstNestedRef=(value)=>value&&typeof value==='object'?(value.control&&typeof value.control==='object'?value.control:(value.observer&&typeof value.observer==='object'?value.observer:null)):null;
const compactTraceList=(value)=>{
  if(Array.isArray(value)) return value.length>0?value.slice(0,4).map((item)=>String(item)).join(','):undefined;
  if(value===undefined||value===null||value==='') return undefined;
  return String(value);
};
const traceIdsFromRows=(rows)=>{
  if(!Array.isArray(rows)) return undefined;
  const ids=[];
  for(const row of rows){
    if(!row||typeof row!=='object') continue;
    const value=row.traceId??row.businessTraceId??row.id;
    if(value&&!ids.includes(String(value))) ids.push(String(value));
    if(ids.length>=4) break;
  }
  return ids.length>0?ids:undefined;
};
const messageStatusesFromRows=(rows)=>{
  if(!Array.isArray(rows)) return undefined;
  const values=[];
  for(const row of rows){
    if(!row||typeof row!=='object') continue;
    const status=row.status??row.dataStatus??row.messageStatus;
    const trace=row.traceId??row.businessTraceId;
    const text=[status,trace].filter(Boolean).join(':');
    if(text&&!values.includes(text)) values.push(String(text));
    if(values.length>=4) break;
  }
  return values.length>0?values:undefined;
};
const slimFindingSample=(value)=>{
  if(!value||typeof value!=='object') return null;
  const refValue=firstNestedRef(value)||{};
  const controlCount=compactProjectionPair(value.controlMessageCount,value.controlTraceRowCount)??value.controlValue??value.beforeMessageCount??value.beforeValue;
  const observerCount=compactProjectionPair(value.observerMessageCount,value.observerTraceRowCount)??value.observerValue??value.afterMessageCount??value.afterValue;
  const trace=compactTraceList(value.traceIds)??compactTraceList(value.missingTraceIdsInObserver)??compactTraceList(value.controlTraceIds)??compactTraceList(value.observerTraceIds)??value.traceId??refValue.traceId;
  return {
    seq:value.seq??value.sampleSeq??value.fromSeq??value.controlSeq??refValue.seq??null,
    ts:short(value.ts??value.sampleTs??value.fromTs??value.controlTs??refValue.ts,32),
    diffKind:short(value.diffKind??value.kind??value.type??value.metric??value.anomaly,48),
    fromValue:value.fromValue??value.beforeValue??null,
    toValue:value.toValue??value.afterValue??null,
    fromDigest:short(value.fromDigest,80),
    toDigest:short(value.toDigest,80),
    previousSeq:value.previousSeq??null,
    previousTs:short(value.previousTs,32),
    delta:value.delta??null,
    sampleDeltaSeconds:value.sampleDeltaSeconds??null,
    allowedIncreaseSeconds:value.allowedIncreaseSeconds??null,
    excessiveIncreaseSeconds:value.excessiveIncreaseSeconds??null,
    promptIndex:value.promptIndex??null,
    event:short(value.event??value.anomaly??(value.steerUsed===true?'steer':Array.isArray(value.submitModes)?value.submitModes.join(','):undefined),48),
    expectedPattern:short(value.expectedPattern,96),
    control:compactMetric(controlCount??(value.fromMessageCount!==undefined||value.fromTraceRowCount!==undefined?compactProjectionPair(value.fromMessageCount,value.fromTraceRowCount):undefined)),
    observer:compactMetric(observerCount??(value.toMessageCount!==undefined||value.toTraceRowCount!==undefined?compactProjectionPair(value.toMessageCount,value.toTraceRowCount):undefined)),
    sessionId:short(value.sessionId??value.routeSessionId??value.activeSessionId??value.controlSessionId??value.observerSessionId??refValue.routeSessionId??refValue.activeSessionId,64),
    path:short(value.path??value.urlPath??value.url??value.controlPath??value.observerPath??refValue.url,120),
    trace:short(trace,120),
    detail:short(value.detail??(value.durationText!==undefined||value.activityText!==undefined||value.textPreview!==undefined?['duration='+String(value.durationText??'-'),'activity='+String(value.activityText??'-'),'status='+String(value.status??'-'),'preview='+String(value.textPreview??'-')].join(' '):undefined)??(Array.isArray(value.submitModes)||Array.isArray(value.responseStatuses)?['modes='+String(Array.isArray(value.submitModes)?value.submitModes.join(','):'-'),'statuses='+String(Array.isArray(value.responseStatuses)?value.responseStatuses.join(','):'-'),'failure='+String(value.failureKind??'-')].join(' '):undefined)??(value.fromDigest||value.toDigest?'digest '+String(value.fromDigest??'-')+' -> '+String(value.toDigest??'-'):undefined)??(value.messageTextDigestDiff?'messageDigest '+String(value.controlMessageDigest??'-')+' != '+String(value.observerMessageDigest??'-'):undefined)??value.summary??value.message??value.preview??value.expectedPattern,180),
    valuesRedacted:true
  };
};
const slimFindingDetail=(parsed,id)=>{
  if(!id||!parsed||typeof parsed!=='object'||!Array.isArray(parsed.findings)) return null;
  const finding=parsed.findings.find((item)=>item&&typeof item==='object'&&String(item.id??item.kind??item.code??'')===id);
  if(!finding) return {id,found:false,samples:[],valuesRedacted:true};
  const sampleSource=Array.isArray(finding.samples)?'samples':Array.isArray(finding.groups)?'groups':Array.isArray(finding.examples)?'examples':Array.isArray(finding.rows)?'rows':Array.isArray(finding.rounds)?'rounds':'none';
  const samples=firstArray(finding.samples,finding.groups,finding.examples,finding.rows,finding.rounds).slice(0,4).map(slimFindingSample).filter(Boolean);
  return {id:short(finding.id??finding.kind??finding.code,80),found:true,severity:short(finding.severity??finding.level,24),count:finding.count??finding.sampleCount??samples.length,summary:short(finding.summary??finding.message,180),sampleSource,samples,valuesRedacted:true};
};
const slimRunnerError=(item)=>item&&typeof item==='object'?{ts:short(item.ts||item.at,32),type:short(item.type,48),retry:item.retry||item.lastRetryLabel||null,retryExhausted:item.retryExhausted===true,attemptCount:item.attemptCount??(Array.isArray(item.attempts)?item.attempts.length:null),lastFailureKind:short(item.lastFailureKind||item.failureKind,80),lastReadinessReason:short(item.lastReadinessReason,80),message:short(item.message||item.lastError,240)}:null;
const collectFallbackCandidates=(requested)=>{
  const candidates=[];
  if(findingFilter&&requested!=='analysis/report.json') candidates.push('analysis/report.json');
  if(requested==='analysis/report.json'||findingFilter||requested.startsWith('analysis/')){
    try{
      const analysisDir=path.join(dir,'analysis');
      const names=fs.readdirSync(analysisDir).filter((name)=>name.endsWith('.json')).map((name)=>({name,mtime:fs.statSync(path.join(analysisDir,name)).mtimeMs})).sort((a,b)=>b.mtime-a.mtime);
      for(const item of names) candidates.push('analysis/'+item.name);
    }catch{}
  }
  return [...new Set(candidates)].filter((item)=>item&&item!==requested&&safeRel(item));
};
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??(Array.isArray(value.messages)?value.messages.length:undefined)??snapshot?.messageCount,
    messageStatuses:short(compactTraceList(messageStatusesFromRows(value.messages)),160),
    traceIds:short(compactTraceList(value.traceIds??traceIdsFromRows(value.traceRows)),160),
    traceEventCount:value.traceEventCount??snapshot?.traceEventCount,
    traceRowCount:value.traceRowCount??(Array.isArray(value.traceRows)?value.traceRows.length:undefined)??snapshot?.traceRowCount,
    loadingCount:value.loadingCount??(Array.isArray(value.loadings)?value.loadings.length:undefined)??snapshot?.loadingCount,
    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){
  const requested=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);}
  let file=path.join(dir,selected); let relative=path.relative(dir,file);
  if(relative.startsWith('..')||path.isAbsolute(relative)){console.log(JSON.stringify({ok:false,command:'web-probe-observe collect',stateDir:dir,mode:'file',reason:'file-outside-state-dir',file:selected,valuesRedacted:true},null,2)); process.exit(2);}
  let fallbackReason=null;
  let fallbackCandidates=[];
  if(!fs.existsSync(file)||!fs.statSync(file).isFile()){
    fallbackCandidates=collectFallbackCandidates(selected);
    const fallback=fallbackCandidates.find((item)=>{const candidate=path.join(dir,item); return fs.existsSync(candidate)&&fs.statSync(candidate).isFile();});
    if(fallback){fallbackReason='requested-file-missing'; selected=fallback; file=path.join(dir,selected); relative=path.relative(dir,file);}
  }
  if(!fs.existsSync(file)||!fs.statSync(file).isFile()){console.log(JSON.stringify({ok:false,command:'web-probe-observe collect',stateDir:dir,mode:'file',reason:'file-not-found',requestedFile:requested,resolvedFile:selected,file:selected,fallbackCandidates:(fallbackCandidates.length>0?fallbackCandidates:collectFallbackCandidates(selected)).slice(0,8),valuesRedacted:true},null,2)); process.exit(1);}
  const st=fs.statSync(file); const raw=fs.readFileSync(file); const truncated=raw.length>maxReadBytes; const isJsonl=selected.endsWith('.jsonl'); const isJson=selected.endsWith('.json');
  const tailReadBytes=isJsonl?maxJsonlTailBytes:maxReadBytes;
  const text=(isJsonl?raw.slice(Math.max(0,raw.length-tailReadBytes)):raw.slice(0,Math.min(raw.length,maxReadBytes))).toString('utf8');
  const fullTextForGrep=grepText?raw.toString('utf8'):'';
  const grep=grepText?grepLines(fullTextForGrep,grepText):null;
  const lines=text.split(/\\r?\\n/).filter(Boolean);
  if(isJsonl&&truncated&&lines.length>0) lines.shift();
  let jsonSummary=null; let jsonContent=null;
  if(isJson){try{const parsed=JSON.parse(raw.toString('utf8')); const inferredFindingFilter=findingFilter||(grepText&&Array.isArray(parsed&&parsed.findings)&&parsed.findings.some((item)=>item&&typeof item==='object'&&String(item.id??item.kind??item.code??'')===grepText)?grepText:null); jsonContent=raw.length<=4096?slimJsonValue(parsed):null; jsonSummary={topLevelKeys:parsed&&typeof parsed==='object'&&!Array.isArray(parsed)?Object.keys(parsed).slice(0,24):[],counts:slimObject(parsed&&parsed.counts,16),sampleMetrics:slimObject(parsed&&parsed.sampleMetrics,16),runtimeAlerts:slimObject(parsed&&parsed.runtimeAlerts,16),pagePerformance:slimObject(parsed&&parsed.pagePerformance,16),runnerErrors:Array.isArray(parsed&&parsed.runnerErrors)?parsed.runnerErrors.slice(-4).map(slimRunnerError).filter(Boolean):[],findings:Array.isArray(parsed&&parsed.findings)?parsed.findings.slice(0,6).map(slimFinding).filter(Boolean):[],findingDetail:slimFindingDetail(parsed,inferredFindingFilter)}}catch(error){jsonSummary={parseError:String(error&&error.message||error).slice(0,160)}}}
  const jsonlTail=isJsonl&&!grep?lines.slice(-8).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',requestedFile:requested,resolvedFile:selected,fallbackReason,file:{path:file,relative,requestedRelative:requested,resolvedRelative:selected,fallbackReason,byteCount:st.size,sha256:shaFile(file),truncated,content:isJsonl||isJson||jsonSummary||grep?undefined:text.slice(0,maxTextPreviewBytes),contentTruncated:!isJsonl&&!isJson&&!jsonSummary&&!grep&&text.length>maxTextPreviewBytes,jsonlTail,jsonSummary,jsonContent,grep,lineCount:lines.length},valuesRedacted:true}));
  process.exit(0);
}
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 selectedFiles=out.slice(0,maxFiles);
const files=selectedFiles.slice(0,24).map(file=>{const st=fs.statSync(file); return {relative:path.relative(dir,file),byteCount:st.size,sha256:shaFile(file)}});
const totalBytes=selectedFiles.reduce((sum,file)=>sum+fs.statSync(file).size,0);
console.log(JSON.stringify({ok:true,command:'web-probe-observe collect',stateDir:dir,mode:'list',view:'files',fileCount:selectedFiles.length,listedFileCount:files.length,omittedFileCount:Math.max(0,selectedFiles.length-files.length),totalBytes,files,valuesRedacted:true}));
`)} "$state_dir" ${shellQuote(collectFile ?? "")} ${shellQuote(collectFinding ?? "")} ${shellQuote(collectGrep ?? "")}`;
}

export function nodeWebObserveWaitCommandShell(commandId: string, waitMs: number): string {
  if (waitMs <= 0) {
    return [
      `printf '{"ok":true,"queued":true,"commandId":%s,"stateDir":%s}\\n' ${shellQuote(JSON.stringify(commandId))} "$(node -e "console.log(JSON.stringify(process.argv[1]))" "$state_dir")"`,
    ].join("\n");
  }
  const waitSeconds = Math.max(1, Math.ceil(waitMs / 1000));
  const printCommandSummary = `node -e ${shellQuote(`
const fs=require('fs');
const file=process.argv[1];
const phase=process.argv[2];
const parsed=JSON.parse(fs.readFileSync(file,'utf8'));
const result=parsed&&parsed.result&&typeof parsed.result==='object'?parsed.result:null;
const artifact=result&&result.artifact&&typeof result.artifact==='object'?result.artifact:null;
const summary={ok:parsed.ok!==false,queued:false,commandId:parsed.commandId||parsed.id||null,type:parsed.type||null,status:phase,completedAt:parsed.completedAt||null,failedAt:parsed.failedAt||null,result:result?{ok:result.ok??null,status:result.status||null,captureId:result.captureId||artifact&&artifact.captureId||null,durationMs:result.durationMs!==undefined?result.durationMs:artifact&&artifact.durationMs!==undefined?artifact.durationMs:null,profilePath:artifact&&artifact.path||null,profileSha256:artifact&&artifact.sha256||null,reportPath:artifact&&artifact.summaryPath||result.reportPath||null,reportSha256:artifact&&artifact.summarySha256||result.reportSha256||null,valuesRedacted:true}:null,error:parsed.error?{name:parsed.error.name||null,message:String(parsed.error.message||'').replace(/\\s+/g,' ').slice(0,200),valuesRedacted:true}:null,valuesRedacted:true};
console.log(JSON.stringify(summary));
`)}`;
  return [
    `command_id=${shellQuote(commandId)}`,
    `deadline=$(( $(date +%s) + ${waitSeconds} ))`,
    "while [ \"$(date +%s)\" -le \"$deadline\" ]; do",
    `  if [ -f "$state_dir/commands/done/\${command_id}.json" ]; then ${printCommandSummary} "$state_dir/commands/done/\${command_id}.json" done; exit 0; fi`,
    `  if [ -f "$state_dir/commands/failed/\${command_id}.json" ]; then ${printCommandSummary} "$state_dir/commands/failed/\${command_id}.json" failed; exit 2; fi`,
    "  sleep 1",
    "done",
    "printf '{\"ok\":true,\"queued\":true,\"waitTimedOut\":true,\"commandId\":\"%s\",\"stateDir\":\"%s\"}\\n' \"$command_id\" \"$state_dir\"",
  ].join("\n");
}

export function commandSummaryForOutput(payload: Record<string, unknown>): Record<string, unknown> {
  const text = typeof payload.text === "string" ? payload.text : null;
  const secondText = typeof payload.secondText === "string" ? payload.secondText : null;
  const title = typeof payload.title === "string" ? payload.title : null;
  const body = typeof payload.body === "string" ? payload.body : null;
  const opaque = (value: unknown) => typeof value === "string" && value.length > 0
    ? {
      hash: `sha256:${createHash("sha256").update(value).digest("hex")}`,
      preview: value.length <= 18 ? value : `${value.slice(0, 10)}...${value.slice(-5)}`,
      bytes: Buffer.byteLength(value),
    }
    : null;
  return {
    id: payload.id,
    type: payload.type,
    path: payload.path ?? null,
    label: payload.label ?? null,
    sessionId: payload.sessionId ?? null,
    provider: payload.provider ?? null,
    profile: payload.profile ?? null,
    durationMs: payload.durationMs ?? null,
    sourceId: opaque(payload.sourceId),
    fileRef: opaque(payload.fileRef),
    taskRef: opaque(payload.taskRef),
    taskId: payload.taskId ?? null,
    titleHash: title === null ? null : `sha256:${createHash("sha256").update(title).digest("hex")}`,
    titleBytes: title === null ? null : Buffer.byteLength(title),
    bodyHash: body === null ? null : `sha256:${createHash("sha256").update(body).digest("hex")}`,
    bodyBytes: body === null ? null : Buffer.byteLength(body),
    status: payload.status ?? null,
    hwpodId: opaque(payload.hwpodId),
    nodeId: opaque(payload.nodeId),
    root: opaque(payload.root),
    textHash: text === null ? null : `sha256:${createHash("sha256").update(text).digest("hex")}`,
    textBytes: text === null ? null : Buffer.byteLength(text),
    secondTextHash: secondText === null ? null : `sha256:${createHash("sha256").update(secondText).digest("hex")}`,
    secondTextBytes: secondText === null ? null : Buffer.byteLength(secondText),
    valuesRedacted: true,
  };
}

export function isSafeWebObserveStateDir(value: string): boolean {
  return value.length > 0
    && !value.includes("\0")
    && !value.includes("..")
    && /^\.state\/web-observe\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/\d{4}\/\d{2}\/\d{2}\/[A-Za-z0-9_.TZ-]+_[A-Za-z0-9_.-]+_webobs-[A-Za-z0-9_.-]+$/u.test(value);
}

export function isSafeWebObserveCollectFile(value: string): boolean {
  return value.length > 0
    && value.length <= 240
    && !value.includes("\0")
    && !value.includes("..")
    && !value.startsWith("/")
    && !value.includes("\\")
    && value.split("/").every((part) => part.length > 0 && part !== "." && part !== "..")
    && /^[A-Za-z0-9_.\/-]+$/u.test(value);
}

export function isSafeWebObserveTraceId(value: string): boolean {
  return /^trc_[A-Za-z0-9_-]{6,120}$/u.test(value);
}

export function isSafeWebObserveFindingId(value: string): boolean {
  return value.length > 0
    && value.length <= 120
    && !value.includes("\0")
    && /^[A-Za-z0-9_.:-]+$/u.test(value);
}

export function isSafeWebObserveArchivePrefix(value: string): boolean {
  return value.length > 0
    && value.length <= 120
    && !value.includes("\0")
    && !value.includes("..")
    && /^[A-Za-z0-9_.-]+$/u.test(value);
}

export function isSafeWebObserveJobId(value: string): boolean {
  return /^webobs-[A-Za-z0-9_.-]+$/u.test(value);
}

export function safeWebObserveSegment(value: string): string {
  return value.replace(/[^A-Za-z0-9_.-]+/gu, "-").replace(/^-+|-+$/gu, "") || "item";
}

export function safeWebObserveTargetSegment(value: string): string {
  const segment = value.replace(/^https?:\/\//u, "").replace(/[^A-Za-z0-9_.-]+/gu, "-").replace(/^-+|-+$/gu, "");
  return segment.slice(0, 48) || "workbench";
}

export function runNodeWebProbeScript(
  options: NodeWebProbeScriptOptions,
  spec: HwlabRuntimeLaneSpec,
  secretSpec: RuntimeSecretSpec,
  material: BootstrapAdminPasswordMaterial,
  credential: Record<string, unknown>,
): Record<string, unknown> {
  return runManagedNodeWebProbeScript(
    options,
    spec,
    {
      username: material.username ?? secretSpec.bootstrapAdminUsername,
      password: material.password ?? "",
    },
    credential,
    options.authentication === "none" ? { mode: "none" } : hwlabWebProbeAuthentication,
  );
}

export function runManagedNodeWebProbeScript(
  options: NodeWebProbeScriptOptions,
  spec: HwlabRuntimeLaneSpec,
  material: WebProbeScriptCredentialMaterial,
  credential: Record<string, unknown>,
  authentication: WebProbeScriptAuthenticationProfile,
): Record<string, unknown> {
  const commandLabel = options.commandLabel ?? `web-probe script --node ${options.node} --lane ${options.lane}${webProbeSemanticOriginArgs(options)}`;
  const commandGovernance = webProbeScriptCommandGovernance(options);
  const webProbeProxy = nodeWebProbeHostProxyEnv(spec, options.browserProxyMode);
  const memoryGuard = runWebProbeMemoryGuard(spec, "manual-start");
  if (memoryGuard.status !== "allowed") return memoryGuard;
  const script = nodeWebProbeScriptRemoteShell(options, spec, material, authentication, webProbeProxy, spec.webProbe?.playwrightBrowsersPath);
  const localArtifactDir = rootPath(".state/web-probe-script-artifacts");
  const asyncJob = options.commandTimeoutSeconds > 55
    ? (() => {
        mkdirSync(localArtifactDir, { recursive: true });
        return runWebProbeRemoteArtifactJob({
          route: `${options.node}:${spec.workspace}`,
          localDir: localArtifactDir,
          waitTimeoutMs: options.commandTimeoutSeconds * 1000,
          commandTimeoutMs: options.commandTimeoutSeconds * 1000,
          inactivityTimeoutMs: options.commandTimeoutSeconds * 1000,
          runIdPrefix: `web-probe-script-${options.node.toLowerCase()}-${options.lane}`,
          stdoutTailBytes: 1_000,
        }, script);
      })()
    : null;
  const result = asyncJob?.result ?? runTransWorkspaceStdinScript(options.node, spec.workspace, script, options.commandTimeoutSeconds);
  const asyncTransport = nullableRecord(asyncJob?.transport);
  const asyncRemote = nullableRecord(asyncTransport?.remote);
  const asyncArtifacts = Array.isArray(asyncTransport?.artifacts) ? asyncTransport.artifacts.map(nullableRecord).filter((item): item is Record<string, unknown> => item !== null) : [];
  const asyncReportArtifact = asyncArtifacts.find((item) => typeof item.remotePath === "string" && item.remotePath.endsWith("/web-probe-script-report.json")) ?? null;
  const reportResult: CommandResult = asyncJob === null ? result : {
    ...result,
    exitCode: typeof asyncRemote?.exitCode === "number" ? asyncRemote.exitCode : result.exitCode,
    stdout: typeof asyncRemote?.stdoutTail === "string" ? asyncRemote.stdoutTail : "",
    stderr: typeof asyncRemote?.stderrTail === "string" ? asyncRemote.stderrTail : "",
    timedOut: false,
  };
  const commandTimedOut = result.timedOut || result.exitCode === 124;
  const runPaths = webProbeScriptRunPathsFromStderr(reportResult.stderr);
  let recoveredReport: WebProbeScriptReportReadResult | null = null;
  const stdoutResolution = resolveCliChildJsonCommandResult({
    result: reportResult,
    requestedStdoutType: "web-probe script report JSON",
    acceptParsed: isWebProbeScriptReportContract,
    forceArtifactFallbackReason: commandTimedOut
      ? "remote-command-timeout"
      : asyncJob !== null ? "remote-artifact-job-report" : null,
    artifactFallback: {
      path: runPaths.reportPath,
      nextCommand: runPaths.reportPath === null ? null : `trans ${options.node}:${spec.workspace} cat ${shellQuote(runPaths.reportPath)}`,
      read: () => {
        const localReportPath = typeof asyncReportArtifact?.localPath === "string" ? asyncReportArtifact.localPath : null;
        if (localReportPath !== null) {
          try {
            const localReportBytes = readFileSync(localReportPath);
            const localReport = JSON.parse(localReportBytes.toString("utf8")) as unknown;
            const parsedLocalReport = nullableRecord(localReport);
            if (parsedLocalReport !== null && isWebProbeScriptReportContract(parsedLocalReport)) {
              return {
                ok: true,
                value: withRecoveredWebProbeReportArtifact(parsedLocalReport, {
                  kind: "json",
                  path: localReportPath,
                  byteCount: localReportBytes.length,
                  sha256: `sha256:${createHash("sha256").update(localReportBytes).digest("hex")}`,
                }),
                path: localReportPath,
                reason: "remote-artifact-download",
              };
            }
          } catch {
            // Fall through to the bounded remote report reader.
          }
        }
        recoveredReport = readNodeWebProbeScriptReport(options, spec, runPaths.reportPath);
        if (recoveredReport?.report) return { ok: true, value: recoveredReport.report, path: recoveredReport.path, reason: recoveredReport.source };
        return { ok: false, reason: recoveredReport?.degradedReason ?? "web-probe-report-artifact-missing", path: runPaths.reportPath };
      },
    },
  });
  const stdoutReport = stdoutResolution.source === "stdout" || stdoutResolution.source === "dump" ? stdoutResolution.parsed : null;
  const recoveredArtifacts = asyncJob !== null ? {
    source: "remote-artifact-download",
    degradedReason: asyncTransport?.ok === true ? null : "web-probe-remote-artifact-failed",
    result,
    artifacts: {
      runDir: asyncRemote?.remoteDir ?? null,
      count: asyncArtifacts.length,
      items: asyncArtifacts.map((item) => ({
        kind: typeof item.remotePath === "string" && item.remotePath.endsWith(".json") ? "json" : "screenshot",
        path: item.localPath ?? item.remotePath ?? null,
        remotePath: item.remotePath ?? null,
        byteCount: item.bytes ?? item.manifestBytes ?? null,
        sha256: item.sha256 ?? item.manifestSha256 ?? null,
        verified: item.verified ?? null,
      })),
    },
    screenshots: [],
  } : stdoutResolution.parsed === null || commandTimedOut ? readNodeWebProbeScriptArtifacts(options, spec, runPaths.runDir) : null;
  const parsedReport = withDownloadedWebProbeArtifacts(stdoutResolution.parsed, asyncArtifacts);
  const report = compactWebProbeScriptResult(parsedReport);
  const passed = result.exitCode === 0 && report?.ok === true;
  const summary = nullableRecord(report?.summary);
  const stdoutBytes = Buffer.byteLength(reportResult.stdout, "utf8");
  const outputFailureKind = parsedReport === null
    ? commandTimedOut
      ? "web-probe-command-timeout"
      : stringOrNullValue(stdoutResolution.diagnostics.fallbackReason)
        ?? (stdoutBytes > 64 * 1024
          ? "web-probe-output-too-large"
          : "web-probe-report-parse-failed")
    : null;
  const degradedReason = commandTimedOut
    ? "web-probe-command-timeout"
    : typeof summary?.degradedReason === "string"
    ? summary.degradedReason
    : typeof report?.failureKind === "string"
      ? report.failureKind
      : outputFailureKind
        ? outputFailureKind
      : commandTimedOut
        ? "web-probe-command-timeout"
        : null;
  const failureKind = commandTimedOut
    ? "web-probe-command-timeout"
    : typeof summary?.failureKind === "string"
    ? summary.failureKind
    : typeof report?.failureKind === "string"
      ? report.failureKind
      : outputFailureKind;
  const effectiveSummary = summary !== null ? {
    ...summary,
    transportTimedOut: commandTimedOut,
    recoveredFrom: stdoutReport !== null ? stdoutResolution.source : recoveredReport?.source ?? null,
    stdoutRecovery: stdoutResolution.diagnostics,
  } : (outputFailureKind === null ? null : {
    ok: false,
    status: "blocked",
    degradedReason: outputFailureKind,
    failureKind: outputFailureKind,
    failedCondition: outputFailureKind === "web-probe-output-too-large"
      ? "remote web-probe stdout exceeded the bounded summary parser input"
      : outputFailureKind === "web-probe-command-timeout"
        ? "remote web-probe command timed out before stdout returned a parseable report"
      : "remote web-probe stdout did not contain a parseable JSON report",
    runDir: runPaths.runDir,
    reportPath: runPaths.reportPath,
    reportLoad: recoveredReport === null ? null : {
      source: recoveredReport.source,
      path: recoveredReport.path,
      degradedReason: recoveredReport.degradedReason,
      result: recoveredReport.result === null ? null : compactCommandResultRedacted(recoveredReport.result, [material.password ?? ""]),
    },
    screenshots: recoveredArtifacts?.screenshots ?? [],
    artifacts: recoveredArtifacts?.artifacts ?? null,
	    stdoutBytes,
	    stdoutRecovery: stdoutResolution.diagnostics,
	    exitCode: result.exitCode,
    stderrTail: result.stderr.trim().slice(-2000),
    valuesRedacted: true,
  });
  const issueEvidence = nullableRecord(report?.issueEvidence) ?? nullableRecord(effectiveSummary?.issueEvidence);
  const compactResult = compactCommandResultRedacted(asyncJob === null ? result : { ...result, stdout: "", stderr: "" }, [material.password ?? ""]);
  if (outputFailureKind !== null) {
    compactResult.stdoutTail = redactKnownSecrets(reportResult.stdout.slice(-2000), [material.password ?? ""]);
    compactResult.stderrTail = redactKnownSecrets(reportResult.stderr.slice(-2000), [material.password ?? ""]);
  }
  return renderWebProbeScriptResult({
    commandPromotionHint: commandGovernance.commandPromotionHint,
    ok: passed,
    status: passed ? "pass" : "blocked",
    command: commandLabel,
    node: options.node,
    lane: options.lane,
    workspace: spec.workspace,
    url: options.url,
    origin: webProbeOriginSummary(options),
    network: webProbeProxy.summary,
    credential,
    scriptSource: options.scriptSource,
    degradedReason,
    failureKind,
    summary: effectiveSummary,
    issueEvidence,
    probe: report,
    reportLoad: stdoutReport !== null || stdoutResolution.source === "artifact" ? { source: asyncJob === null ? stdoutResolution.source : "remote-artifact-download", path: asyncReportArtifact?.localPath ?? report?.reportPath ?? null, degradedReason: null } : recoveredReport === null ? null : {
      source: recoveredReport.source,
      path: recoveredReport.path,
      degradedReason: recoveredReport.degradedReason,
      result: recoveredReport.result === null ? null : compactCommandResultRedacted(recoveredReport.result, [material.password ?? ""]),
      stdoutRecovery: recoveredReport.stdoutRecovery ?? null,
    },
    stdoutRecovery: stdoutResolution.diagnostics,
    warnings: commandGovernance.warnings,
    hints: commandGovernance.hints,
    preferredCommands: commandGovernance.preferredCommands,
    recoveredArtifacts: recoveredArtifacts === null ? null : {
      source: recoveredArtifacts.source,
      degradedReason: recoveredArtifacts.degradedReason,
      result: recoveredArtifacts.result === null ? null : compactCommandResultRedacted(recoveredArtifacts.result, [material.password ?? ""]),
      artifacts: recoveredArtifacts.artifacts,
    },
    result: compactResult,
    valuesRedacted: true,
  });
}

function withDownloadedWebProbeArtifacts(
  report: Record<string, unknown> | null,
  downloaded: readonly Record<string, unknown>[],
): Record<string, unknown> | null {
  if (report === null || downloaded.length === 0) return report;
  const byRemotePath = new Map<string, Record<string, unknown>>(downloaded
    .filter((item) => typeof item.remotePath === "string" && typeof item.localPath === "string")
    .map((item) => [String(item.remotePath), item] as const));
  const artifacts = nullableRecord(report.artifacts) ?? {};
  const items = Array.isArray(artifacts.items) ? artifacts.items.map((value) => {
    const item = nullableRecord(value);
    if (item === null || typeof item.path !== "string") return value;
    const downloadedItem = byRemotePath.get(item.path);
    if (downloadedItem === undefined) return value;
    return {
      ...item,
      remotePath: item.path,
      path: downloadedItem.localPath,
      byteCount: downloadedItem.bytes ?? item.byteCount ?? null,
      sha256: typeof downloadedItem.sha256 === "string" ? `sha256:${downloadedItem.sha256.replace(/^sha256:/u, "")}` : item.sha256 ?? null,
      verified: downloadedItem.verified ?? null,
    };
  }) : [];
  return {
    ...report,
    reportPath: typeof report.reportPath === "string" && byRemotePath.has(report.reportPath)
      ? byRemotePath.get(report.reportPath)?.localPath ?? report.reportPath
      : report.reportPath ?? null,
    artifacts: { ...artifacts, items },
  };
}

export function webProbeScriptCommandGovernance(options: NodeWebProbeScriptOptions): {
  commandPromotionHint: Record<string, unknown>;
  warnings: Record<string, unknown>[];
  hints: string[];
  preferredCommands: Record<string, string>;
} {
  return {
    commandPromotionHint: webProbeScriptCommandPromotionHint(options),
    warnings: webProbeScriptGovernanceWarnings(options),
    hints: webProbeScriptGovernanceHints(options),
    preferredCommands: webProbeScriptPreferredCommands(options),
  };
}

export function webProbeScriptCommandPromotionHint(options: NodeWebProbeScriptOptions): Record<string, unknown> {
  const repoOwnedTypedCommand = options.suppressAdHocWarning === true;
  const currentCommand = options.commandLabel ?? `web-probe script --node ${options.node} --lane ${options.lane}${webProbeSemanticOriginArgs(options)}`;
  return {
    schemaVersion: "unidesk.web-probe.command-promotion.v1",
    code: "prefer_repo_owned_typed_command",
    promotionRequiredBeforeRepeat: !repoOwnedTypedCommand,
    repoOwnedTypedCommand,
    sourceKind: options.scriptSource.kind,
    currentCommand,
    preferredArtifact: "repo-owned-typed-command",
    preferredSurface: "web-probe observe command or a dedicated web-probe subcommand",
    action: repoOwnedTypedCommand ? "reuse-repo-owned-typed-command" : "promote-before-repeat",
    message: repoOwnedTypedCommand
      ? "This probe is already a repo-owned typed command; reuse its public command instead of copying the generated script."
      : "Before repeating this one-off script, promote the workflow to a repo-owned typed web-probe command.",
    valuesRedacted: true,
  };
}

export function webProbeScriptGovernanceWarnings(options: NodeWebProbeScriptOptions): Record<string, unknown>[] {
  if (options.suppressAdHocWarning === true) return [];
  return [{
    code: "web_probe_script_ad_hoc_only",
    severity: "warning",
    requiredAction: "promote-before-repeat",
    preferredArtifact: "repo-owned-typed-command",
    preferredSurface: "web-probe observe command or a dedicated web-probe subcommand",
    temporaryScriptScope: "one-off-exploration-only",
    message: "web-probe script is a one-off exploration escape hatch; before repeating it, promote the workflow to a repo-owned typed command instead of rerunning temporary scripts.",
    node: options.node,
    lane: options.lane,
    valuesRedacted: true,
  }];
}

export function webProbeScriptGovernanceHints(options: NodeWebProbeScriptOptions): string[] {
  if (options.generatedHints !== undefined) return options.generatedHints;
  return [
    "Before rerunning this script, promote the workflow to a repo-owned typed `web-probe` command so auth, artifacts, output bounds and evidence contracts remain reusable.",
    "Prefer `web-probe observe start` plus `web-probe observe command` for interactive flows; use `observe collect/analyze` for repeated evidence reads.",
    `For this target, start with: bun scripts/cli.ts web-probe observe start --node ${options.node} --lane ${options.lane}${webProbeSemanticOriginArgs(options)} --target-path /projects/mdtodo`,
  ];
}

export function webProbeScriptPreferredCommands(options: NodeWebProbeScriptOptions): Record<string, string> {
  if (options.generatedPreferredCommands !== undefined) return options.generatedPreferredCommands;
  return {
    typedCommandCatalog: "bun scripts/cli.ts web-probe --help",
    startObserver: `bun scripts/cli.ts web-probe observe start --node ${options.node} --lane ${options.lane}${webProbeSemanticOriginArgs(options)} --target-path /projects/mdtodo`,
    mdtodoSummary: "bun scripts/cli.ts web-probe observe collect <observerId> --view project-mdtodo-summary",
    analyze: "bun scripts/cli.ts web-probe observe analyze <observerId>",
  };
}

function webProbeSemanticOriginArgs(options: Pick<NodeWebProbeScriptOptions, "originName">): string {
  return options.originName === "internal" || options.originName === "public" || options.originName === "native" ? ` --origin ${options.originName}` : "";
}

export function nodeWebProbeScriptRemoteShell(
  options: NodeWebProbeScriptOptions,
  spec: HwlabRuntimeLaneSpec,
  material: WebProbeScriptCredentialMaterial,
  authentication: WebProbeScriptAuthenticationProfile,
  webProbeProxy: NodeWebProbeHostProxyEnv,
  playwrightBrowsersPath: string | undefined,
): string {
  const userScriptB64 = Buffer.from(options.scriptText, "utf8").toString("base64");
  const userScriptB64Body = userScriptB64.match(/.{1,76}/gu)?.join("\n") ?? userScriptB64;
  const runnerB64 = Buffer.from(nodeWebProbeScriptRunnerSource(), "utf8").toString("base64");
  const runnerB64Body = runnerB64.match(/.{1,76}/gu)?.join("\n") ?? runnerB64;
  return [
    "set -eu",
    'run_root="${UNIDESK_WEB_PROBE_ARTIFACT_REMOTE_DIR:-.state/web-probe-script}"',
    "mkdir -p \"$run_root\"",
    "run_dir=$(mktemp -d \"$run_root/run.XXXXXX\")",
    "report_file=\"$run_dir/web-probe-script-report.json\"",
    "browser_stage_file=\"$run_dir/browser-stage.txt\"",
    "export UNIDESK_WEB_PROBE_BROWSER_STAGE_FILE=\"$browser_stage_file\"",
    "chmod 700 \"$run_dir\"",
    "printf 'UNIDESK_WEB_PROBE_RUN_DIR=%s\\n' \"$run_dir\" >&2",
    "printf 'UNIDESK_WEB_PROBE_REPORT_PATH=%s\\n' \"$report_file\" >&2",
    "user_script=\"$run_dir/user-script.mjs\"",
    "user_script_b64=\"$run_dir/user-script.mjs.b64\"",
    "runner=\"$run_dir/runner.mjs\"",
    "runner_b64=\"$run_dir/runner.mjs.b64\"",
    "cat >\"$user_script_b64\" <<'UNIDESK_WEB_PROBE_USER_SCRIPT_B64'",
    userScriptB64Body,
    "UNIDESK_WEB_PROBE_USER_SCRIPT_B64",
    "cat >\"$runner_b64\" <<'UNIDESK_WEB_PROBE_RUNNER_B64'",
    runnerB64Body,
    "UNIDESK_WEB_PROBE_RUNNER_B64",
    "node -e \"const fs=require('fs'); fs.writeFileSync(process.argv[1], Buffer.from(fs.readFileSync(process.argv[2], 'utf8').replace(/\\s+/g, ''), 'base64'))\" \"$user_script\" \"$user_script_b64\"",
    "node -e \"const fs=require('fs'); fs.writeFileSync(process.argv[1], Buffer.from(fs.readFileSync(process.argv[2], 'utf8').replace(/\\s+/g, ''), 'base64'))\" \"$runner\" \"$runner_b64\"",
    "rm -f \"$user_script_b64\" \"$runner_b64\"",
    "export run_dir user_script runner",
    webProbeGuardedLaunchShell(spec, "manual-start", [
      ...webProbeProxy.envAssignments,
      ...webProbeBrowserEvidenceEnvAssignments(spec),
      ...(playwrightBrowsersPath === undefined ? [] : [
        `PLAYWRIGHT_BROWSERS_PATH=${shellQuote(playwrightBrowsersPath)}`,
      ]),
      `HWLAB_WEB_BASE_URL=${shellQuote(options.url)}`,
      `HWLAB_WEB_USER=${shellQuote(material.username)}`,
      `HWLAB_WEB_PASS=${shellQuote(material.password)}`,
      `UNIDESK_WEB_PROBE_AUTH_PROFILE=${shellQuote(JSON.stringify(authentication))}`,
      "UNIDESK_WEB_PROBE_RUN_DIR=\"$run_dir\"",
      "UNIDESK_WEB_PROBE_USER_SCRIPT=\"$user_script\"",
      `UNIDESK_WEB_PROBE_TIMEOUT_MS=${shellQuote(String(options.timeoutMs))}`,
      `UNIDESK_WEB_PROBE_VIEWPORT=${shellQuote(options.viewport)}`,
      `UNIDESK_WEB_PROBE_BROWSER_PROXY_MODE=${shellQuote(options.browserProxyMode)}`,
      "node \"$runner\"",
    ].join(" ")),
  ].join("\n");
}

export function webProbeScriptRunPathsFromStderr(stderr: string): { runDir: string | null; reportPath: string | null } {
  const runDir = lineValueFromText(stderr, "UNIDESK_WEB_PROBE_RUN_DIR");
  const markedReportPath = lineValueFromText(stderr, "UNIDESK_WEB_PROBE_REPORT_PATH");
  const reportPath = markedReportPath ?? (runDir === null ? null : `${runDir.replace(/\/$/u, "")}/web-probe-script-report.json`);
  return {
    runDir: isSafeWebProbeScriptRunDir(runDir) ? runDir : null,
    reportPath: isSafeWebProbeScriptReportPath(reportPath) ? reportPath : null,
  };
}

export function lineValueFromText(text: string, name: string): string | null {
  const pattern = new RegExp(`^${name}=([^\\r\\n]+)$`, "mu");
  const match = text.match(pattern);
  return match ? match[1].trim() : null;
}

interface WebProbeScriptReportReadResult {
  source: string;
  report: Record<string, unknown> | null;
  result: CommandResult | null;
  degradedReason: string | null;
  path: string | null;
  stdoutRecovery?: Record<string, unknown> | null;
}

export function withRecoveredWebProbeReportArtifact(
  report: Record<string, unknown>,
  artifactValue: unknown,
): Record<string, unknown> {
  const artifact = nullableRecord(artifactValue);
  if (artifact === null || typeof artifact.path !== "string" || typeof artifact.sha256 !== "string") return report;
  const artifactSummary = nullableRecord(report.artifacts) ?? {};
  const existingItems = Array.isArray(artifactSummary.items)
    ? artifactSummary.items.filter((item) => nullableRecord(item)?.path !== artifact.path)
    : [];
  return {
    ...report,
    reportPath: artifact.path,
    reportSha256: artifact.sha256,
    artifacts: {
      ...artifactSummary,
      items: [...existingItems.slice(-7), artifact],
    },
  };
}

export function readNodeWebProbeScriptReport(
  options: NodeWebProbeScriptOptions,
  spec: HwlabRuntimeLaneSpec,
  reportPath: string | null,
): WebProbeScriptReportReadResult | null {
  if (!reportPath) return null;
  if (!isSafeWebProbeScriptReportPath(reportPath)) return { source: "unsafe-path", report: null, result: null, degradedReason: "web-probe-report-path-invalid", path: reportPath };
  const script = [
    "set -eu",
    `test -f ${shellQuote(reportPath)}`,
    `node - ${shellQuote(reportPath)} <<'NODE'`,
    "const fs = require('fs');",
    "const { createHash } = require('crypto');",
    "const reportPath = process.argv[2];",
    "const reportBytes = fs.readFileSync(reportPath);",
    "const recoveredReportArtifact = { kind: 'json', path: reportPath, byteCount: reportBytes.length, sha256: 'sha256:' + createHash('sha256').update(reportBytes).digest('hex') };",
    "const report = JSON.parse(reportBytes.toString('utf8'));",
    "function rec(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : {}; }",
      "function compact(value, depth = 0) {",
      "  if (value === null || value === undefined) return value ?? null;",
      "  if (typeof value === 'string') return value.replace(/\\s+/gu, ' ').trim().slice(0, 120);",
      "  if (typeof value === 'number' || typeof value === 'boolean') return value;",
      "  if (depth >= 3) return '[max-depth]';",
      "  if (Array.isArray(value)) return value.slice(0, 4).map((item) => compact(item, depth + 1));",
      "  if (typeof value === 'object') {",
      "    const out = {};",
      "    for (const [key, nested] of Object.entries(value).slice(0, 8)) out[key] = compact(nested, depth + 1);",
      "    return out;",
      "  }",
      "  return String(value).slice(0, 240);",
      "}",
      "function compactSummary(value) {",
      "  const summary = rec(value);",
      "  return {",
      "    ok: summary.ok === true,",
      "    status: typeof summary.status === 'string' ? summary.status : null,",
      "    degradedReason: typeof summary.degradedReason === 'string' ? summary.degradedReason : null,",
      "    failureKind: typeof summary.failureKind === 'string' ? summary.failureKind : null,",
      "    failedCondition: typeof summary.failedCondition === 'string' ? compact(summary.failedCondition) : null,",
      "    nextAction: typeof summary.nextAction === 'string' ? compact(summary.nextAction) : null,",
      "    baseUrl: typeof summary.baseUrl === 'string' ? summary.baseUrl : null,",
      "    finalUrl: typeof summary.finalUrl === 'string' ? summary.finalUrl : null,",
      "    lastUrl: typeof summary.lastUrl === 'string' ? summary.lastUrl : null,",
      "    scriptSha256: typeof summary.scriptSha256 === 'string' ? summary.scriptSha256 : null,",
      "    runDir: typeof summary.runDir === 'string' ? summary.runDir : null,",
      "    reportPath: typeof summary.reportPath === 'string' ? summary.reportPath : null,",
      "    reportSha256: typeof summary.reportSha256 === 'string' ? summary.reportSha256 : null,",
      "    lastScreenshot: compact(summary.lastScreenshot),",
      "    screenshots: compact(summary.screenshots),",
      "    apiMatrix: compact(summary.apiMatrix),",
      "    stepCount: typeof summary.stepCount === 'number' ? summary.stepCount : null,",
      "    lastStep: compact(summary.lastStep),",
      "    issueEvidence: compact(summary.issueEvidence),",
      "    valuesRedacted: true,",
      "  };",
      "}",
      "const scriptBlock = rec(report.script);",
      "const compactReport = {",
      "  ok: report.ok === true,",
      "  status: typeof report.status === 'string' ? report.status : null,",
      "  summary: compactSummary(report.summary),",
    "  issueEvidence: compact(report.issueEvidence ?? rec(report.summary).issueEvidence),",
    "  baseUrl: typeof report.baseUrl === 'string' ? report.baseUrl : null,",
    "  finalUrl: typeof report.finalUrl === 'string' ? report.finalUrl : null,",
    "  lastUrl: typeof report.lastUrl === 'string' ? report.lastUrl : null,",
    "  scriptSha256: typeof report.scriptSha256 === 'string' ? report.scriptSha256 : null,",
    "  runDir: typeof report.runDir === 'string' ? report.runDir : null,",
    "  reportPath: typeof report.reportPath === 'string' ? report.reportPath : reportPath,",
    "  reportSha256: typeof report.reportSha256 === 'string' ? report.reportSha256 : null,",
    "  auth: compact(report.auth),",
    "  script: { ok: scriptBlock.ok === true, result: compact(scriptBlock.result), stepCount: Array.isArray(scriptBlock.steps) ? scriptBlock.steps.length : null },",
    "  steps: Array.isArray(report.steps) ? report.steps.slice(-5).map((item) => compact(item)) : [],",
    "  failureKind: typeof report.failureKind === 'string' ? report.failureKind : null,",
    "  guidance: typeof report.guidance === 'string' ? report.guidance : null,",
    "  lastScreenshot: compact(report.lastScreenshot),",
    "  readiness: compact(report.readiness),",
    "  artifacts: compact(report.artifacts),",
    "  error: typeof report.error === 'string' ? report.error : null,",
    "  errorMessage: typeof report.errorMessage === 'string' ? report.errorMessage : null,",
    "  safety: compact(report.safety),",
    "  recoveredReportArtifact,",
    "  valuesRedacted: true,",
    "};",
    "console.log(JSON.stringify(compactReport));",
    "NODE",
  ].join("\n");
  const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, 55);
  if (result.exitCode !== 0 || result.timedOut) {
    return { source: "report-file", report: null, result, degradedReason: result.timedOut ? "web-probe-command-timeout" : "web-probe-report-read-failed", path: reportPath };
  }
  const reportResolution = resolveCliChildJsonCommandResult({
    result,
    requestedStdoutType: "web-probe script report artifact compact JSON",
    acceptParsed: isWebProbeScriptReportContract,
  });
  const parsedReport = reportResolution.parsed;
  const report = parsedReport === null
    ? null
    : withRecoveredWebProbeReportArtifact(parsedReport, parsedReport.recoveredReportArtifact);
  return {
    source: "report-file",
    report,
    result,
    degradedReason: report === null ? stringOrNullValue(reportResolution.diagnostics.fallbackReason) ?? "web-probe-report-parse-failed" : null,
    path: reportPath,
    stdoutRecovery: reportResolution.diagnostics,
  };
}

function isWebProbeScriptReportContract(value: Record<string, unknown>): boolean {
  return value.ok === true
    || value.ok === false
    || typeof value.status === "string"
    || typeof value.reportPath === "string"
    || typeof value.error === "string"
    || typeof value.failureKind === "string"
    || Object.keys(nullableRecord(value.summary) ?? {}).length > 0
    || Object.keys(nullableRecord(value.script) ?? {}).length > 0
    || Array.isArray(value.steps);
}

function stringOrNullValue(value: unknown): string | null {
  return typeof value === "string" && value.length > 0 ? value : null;
}

export function readNodeWebProbeScriptArtifacts(
  options: NodeWebProbeScriptOptions,
  spec: HwlabRuntimeLaneSpec,
  runDir: string | null,
): { source: string; artifacts: Record<string, unknown> | null; screenshots: Record<string, unknown>[]; result: CommandResult | null; degradedReason: string | null } | null {
  if (!runDir) return null;
  if (!isSafeWebProbeScriptRunDir(runDir)) return { source: "unsafe-path", artifacts: null, screenshots: [], result: null, degradedReason: "web-probe-run-dir-invalid" };
  const script = [
    "set -eu",
    `test -d ${shellQuote(runDir)}`,
    `find ${shellQuote(runDir)} -maxdepth 1 -type f \\( -name '*.png' -o -name '*.json' \\) -printf '%p\\t%s\\n' | tail -n 30`,
  ].join("\n");
  const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, 55);
  if (result.exitCode !== 0 || result.timedOut) {
    return { source: "run-dir", artifacts: null, screenshots: [], result, degradedReason: result.timedOut ? "web-probe-command-timeout" : "web-probe-artifact-list-failed" };
  }
  const items = result.stdout.split(/\r?\n/u)
    .map((line) => line.trim())
    .filter(Boolean)
    .map((line) => {
      const [path, sizeText] = line.split("\t");
      const byteCount = Number(sizeText);
      return {
        kind: path.endsWith(".png") ? "screenshot" : "json",
        path,
        byteCount: Number.isFinite(byteCount) ? byteCount : null,
      };
    })
    .filter((item) => isSafeWebProbeScriptArtifactPath(String(item.path)));
  const screenshots = items.filter((item) => item.kind === "screenshot");
  return {
    source: "run-dir",
    artifacts: { runDir, count: items.length, items },
    screenshots,
    result,
    degradedReason: null,
  };
}
