feat: add project-management web-probe observe

This commit is contained in:
Codex
2026-06-25 11:39:53 +00:00
parent 8d26e6fbc3
commit 80351ce5d2
9 changed files with 1070 additions and 23 deletions
+133 -7
View File
@@ -8,7 +8,7 @@ import { runCommand, type CommandResult } from "./command";
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 } from "./hwlab-node-lanes";
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";
@@ -64,7 +64,7 @@ interface NodeWebProbeScriptOptions {
}
type NodeWebProbeObserveAction = "start" | "status" | "command" | "stop" | "collect" | "analyze";
type NodeWebProbeObserveCommandType = "login" | "preflight" | "goto" | "newSession" | "sendPrompt" | "steer" | "cancel" | "selectProvider" | "clickSession" | "screenshot" | "mark" | "stop";
type NodeWebProbeObserveCommandType = "login" | "preflight" | "goto" | "newSession" | "sendPrompt" | "steer" | "cancel" | "selectProvider" | "clickSession" | "selectProjectSource" | "selectMdtodoFile" | "selectMdtodoTask" | "launchWorkbenchFromTask" | "screenshot" | "mark" | "stop";
interface NodeWebProbeObserveOptions {
action: "observe";
@@ -104,6 +104,9 @@ interface NodeWebProbeObserveOptions {
commandLabel: string | null;
commandSessionId: string | null;
commandProvider: string | null;
commandSourceId: string | null;
commandFileRef: string | null;
commandTaskRef: string | null;
}
type NodeWebProbeOptions = NodeWebProbeRunOptions | NodeWebProbeScriptOptions | NodeWebProbeObserveOptions;
@@ -7385,6 +7388,9 @@ function parseNodeWebProbeObserveOptions(
"--label",
"--session-id",
"--provider",
"--source-id",
"--file-ref",
"--task-ref",
]), new Set(["--force", "--full", "--text-stdin"]));
const commandTypeRaw = optionValue(args, "--type") ?? null;
const commandType = commandTypeRaw === null ? null : parseNodeWebProbeObserveCommandType(commandTypeRaw);
@@ -7428,6 +7434,12 @@ function parseNodeWebProbeObserveOptions(
throw new Error("web-probe observe command accepts either --text or --text-stdin, not both");
}
const commandText = commandTextFromStdin ? readFileSync(0, "utf8") : commandTextOption;
const commandSourceId = optionValue(args, "--source-id") ?? null;
const commandFileRef = optionValue(args, "--file-ref") ?? null;
const commandTaskRef = optionValue(args, "--task-ref") ?? null;
for (const [label, value] of [["--source-id", commandSourceId], ["--file-ref", commandFileRef], ["--task-ref", commandTaskRef]] as const) {
if (value !== null && (value.includes("\0") || value.length > 500)) throw new Error(`unsafe web-probe observe ${label}: expected 1-500 non-NUL chars`);
}
return {
action: "observe",
observeAction: observeActionRaw,
@@ -7466,6 +7478,9 @@ function parseNodeWebProbeObserveOptions(
commandLabel: optionValue(args, "--label") ?? null,
commandSessionId: optionValue(args, "--session-id") ?? null,
commandProvider: optionValue(args, "--provider") ?? null,
commandSourceId,
commandFileRef,
commandTaskRef,
};
}
@@ -7480,11 +7495,15 @@ function parseNodeWebProbeObserveCommandType(value: string): NodeWebProbeObserve
|| value === "cancel"
|| value === "selectProvider"
|| value === "clickSession"
|| value === "selectProjectSource"
|| value === "selectMdtodoFile"
|| value === "selectMdtodoTask"
|| value === "launchWorkbenchFromTask"
|| value === "screenshot"
|| value === "mark"
|| value === "stop"
) return value;
throw new Error(`web-probe observe command --type must be login, preflight, goto, newSession, sendPrompt, steer, cancel, selectProvider, clickSession, screenshot, mark, or stop; got ${value}`);
throw new Error(`web-probe observe command --type must be login, preflight, goto, newSession, sendPrompt, steer, cancel, selectProvider, clickSession, selectProjectSource, selectMdtodoFile, selectMdtodoTask, launchWorkbenchFromTask, screenshot, mark, or stop; got ${value}`);
}
function parseWebProbeBrowserProxyMode(value: string | undefined): WebProbeBrowserProxyMode {
@@ -7843,6 +7862,10 @@ function nodeWebProbeAlertThresholds(spec: HwlabRuntimeLaneSpec): HwlabRuntimeWe
return thresholds;
}
function nodeWebProbeProjectManagementConfig(spec: HwlabRuntimeLaneSpec): HwlabRuntimeWebProbeProjectManagementSpec | null {
return spec.webProbe?.projectManagement ?? null;
}
interface NodeWebProbeHostProxyEnv {
readonly envAssignments: string[];
readonly summary: Record<string, unknown>;
@@ -7999,6 +8022,7 @@ function runNodeWebProbeObserveStart(
const runnerB64Body = runnerB64.match(/.{1,76}/gu)?.join("\n") ?? runnerB64;
const webProbeProxy = nodeWebProbeHostProxyEnv(spec, options.browserProxyMode);
const alertThresholds = nodeWebProbeAlertThresholds(spec);
const projectManagement = nodeWebProbeProjectManagementConfig(spec);
const runnerEnvAssignments = [
...webProbeProxy.envAssignments,
`HWLAB_WEB_BASE_URL=${shellQuote(options.url)}`,
@@ -8014,6 +8038,7 @@ function runNodeWebProbeObserveStart(
`UNIDESK_WEB_OBSERVE_VIEWPORT=${shellQuote(options.viewport)}`,
`UNIDESK_WEB_OBSERVE_BROWSER_PROXY_MODE=${shellQuote(options.browserProxyMode)}`,
`UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON=${shellQuote(JSON.stringify(alertThresholds))}`,
`UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON=${shellQuote(JSON.stringify(projectManagement))}`,
].join(" ");
const script = [
"set -eu",
@@ -8062,6 +8087,7 @@ function runNodeWebProbeObserveStart(
url: options.url,
network: webProbeProxy.summary,
alertThresholds,
projectManagement,
targetPath: options.targetPath,
id: observerId,
credential,
@@ -8149,6 +8175,9 @@ function runNodeWebProbeObserveCommand(options: NodeWebProbeObserveOptions, spec
label: options.commandLabel,
sessionId: options.commandSessionId,
provider: options.commandProvider,
sourceId: options.commandSourceId,
fileRef: options.commandFileRef,
taskRef: options.commandTaskRef,
};
const preStopStatus = options.force && stopCommand
? readNodeWebProbeObserveRemoteStatus(options, spec, 1, Math.min(options.commandTimeoutSeconds, 30))
@@ -8279,6 +8308,7 @@ function runNodeWebProbeObserveCollect(options: NodeWebProbeObserveOptions, spec
function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec: HwlabRuntimeLaneSpec): Record<string, unknown> | RenderedCliResult {
const analyzerB64 = Buffer.from(nodeWebObserveAnalyzerSource(), "utf8").toString("base64");
const alertThresholds = nodeWebProbeAlertThresholds(spec);
const projectManagement = nodeWebProbeProjectManagementConfig(spec);
const script = [
"set -eu",
nodeWebObserveResolveStateDirShell(options),
@@ -8297,7 +8327,8 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
`UNIDESK_WEB_OBSERVE_ANALYZE_ARCHIVE_PREFIX=${shellQuote(options.analyzeArchivePrefix ?? "")}`,
`UNIDESK_WEB_OBSERVE_ANALYZE_TAIL_SAMPLES=${shellQuote(options.analyzeTailSamples === null ? "" : String(options.analyzeTailSamples))}`,
`UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON=${shellQuote(JSON.stringify(alertThresholds))}`,
"UNIDESK_WEB_OBSERVE_STATE_DIR=\"$state_dir\" UNIDESK_WEB_OBSERVE_ANALYZE_ARCHIVE_PREFIX=\"$UNIDESK_WEB_OBSERVE_ANALYZE_ARCHIVE_PREFIX\" UNIDESK_WEB_OBSERVE_ANALYZE_TAIL_SAMPLES=\"$UNIDESK_WEB_OBSERVE_ANALYZE_TAIL_SAMPLES\" UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON=\"$UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON\" node \"$analyzer\" >\"$analysis_stdout\" 2>\"$analysis_stderr\"",
`UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON=${shellQuote(JSON.stringify(projectManagement))}`,
"UNIDESK_WEB_OBSERVE_STATE_DIR=\"$state_dir\" UNIDESK_WEB_OBSERVE_ANALYZE_ARCHIVE_PREFIX=\"$UNIDESK_WEB_OBSERVE_ANALYZE_ARCHIVE_PREFIX\" UNIDESK_WEB_OBSERVE_ANALYZE_TAIL_SAMPLES=\"$UNIDESK_WEB_OBSERVE_ANALYZE_TAIL_SAMPLES\" UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON=\"$UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON\" UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON=\"$UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON\" node \"$analyzer\" >\"$analysis_stdout\" 2>\"$analysis_stderr\"",
"analyzer_exit=$?",
"set -e",
"report_json=\"$state_dir/analysis/report.json\"",
@@ -8319,7 +8350,7 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
"const readJsonlTail = (path, limit) => readText(path).split(/\\r?\\n/).filter(Boolean).slice(-limit).map((line) => { try { return JSON.parse(line); } catch { return null; } }).filter(Boolean);",
"const mergeArrays = (...values) => { const out = []; const seen = new Set(); for (const value of values) { if (!Array.isArray(value)) continue; for (const item of value) { const key = JSON.stringify([item?.id ?? item?.kind ?? item?.code ?? item?.columnLabel ?? item?.traceId ?? null, item?.path ?? item?.urlPath ?? null, item?.method ?? null, item?.status ?? null, item?.summary ?? item?.message ?? item?.fromSeq ?? item?.firstAt ?? null, item?.toSeq ?? item?.lastAt ?? null]); if (seen.has(key)) continue; seen.add(key); out.push(item); } } return out; };",
"const clip = (value, limit = 160) => value === null || value === undefined ? null : String(value).slice(0, limit);",
"const findingRank = (item) => { const id = String(item?.id ?? item?.kind ?? item?.code ?? ''); if (id === 'observer-command-failed') return 0; if (id === 'page-performance-slow-same-origin-api') return 1; if (id === 'session-rail-title-fallback-majority') return 2; if (id.startsWith('turn-timing-total-elapsed')) return 3; if (id.startsWith('turn-timing-recent-update')) return 4; if (id.includes('runtime-execution') || id.includes('prompt-chat-submit-failed')) return 5; return 10; };",
"const findingRank = (item) => { const id = String(item?.id ?? item?.kind ?? item?.code ?? ''); if (id.startsWith('project-management-') || id.startsWith('mdtodo-') || id === 'workbench-launch-button-unavailable') return 0; if (id === 'observer-command-failed') return 0.2; if (id === 'page-performance-slow-same-origin-api') return 1; if (id === 'session-rail-title-fallback-majority') return 2; if (id.startsWith('turn-timing-total-elapsed')) return 3; if (id.startsWith('turn-timing-recent-update')) return 4; if (id.includes('runtime-execution') || id.includes('prompt-chat-submit-failed')) return 5; return 10; };",
"const severityRank = (item) => { const severity = String(item?.severity ?? item?.level ?? '').toLowerCase(); if (severity === 'red') return 0; if (severity === 'amber' || severity === 'warning') return 1; if (severity === 'info') return 3; return 2; };",
"const sortFindings = (items) => (Array.isArray(items) ? items : []).slice().sort((a, b) => findingRank(a) - findingRank(b) || severityRank(a) - severityRank(b));",
"const stdoutJson = readJson(stdoutPath);",
@@ -8332,6 +8363,7 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
"const slimSlowSample = (item) => { const v = objectOrNull(item) || {}; return { ts: v.ts ?? null, seq: v.seq ?? null, path: clip(v.path ?? v.rawPath, 96), initiatorType: clip(v.initiatorType, 24), durationMs: v.durationMs ?? null, requestToResponseStartMs: v.requestToResponseStartMs ?? v.streamOpenMs ?? null, responseTransferMs: v.responseTransferMs ?? null, nextHopProtocol: clip(v.nextHopProtocol, 24), timingStatus: clip(v.timingStatus, 16), serverTimingNames: Array.isArray(v.serverTimingNames) ? v.serverTimingNames.slice(0, 4).map((x) => clip(x, 32)) : [], otelTraceId: clip(v.otelTraceId, 32) }; };",
"const slimSlowApi = (item) => { const v = objectOrNull(item) || {}; return { path: clip(v.path ?? v.route, 96), route: clip(v.route ?? v.path, 96), sampleCount: v.sampleCount ?? null, p95Ms: v.p95Ms ?? v.p95 ?? null, maxMs: v.maxMs ?? v.max ?? null, budgetMs: v.budgetMs ?? null, overBudgetCount: v.overBudgetCount ?? null, overFiveSecondCount: v.overFiveSecondCount ?? null, slowSamples: Array.isArray(v.slowSamples) ? v.slowSamples.slice(0, 3).map(slimSlowSample) : [] }; };",
"const slimFinding = (item) => { const v = objectOrNull(item) || {}; return { kind: clip(v.kind ?? v.id ?? v.code, 48), code: clip(v.code ?? v.id ?? v.kind, 48), severity: clip(v.severity ?? v.level, 24), level: clip(v.level ?? v.severity, 24), count: v.count ?? v.sampleCount ?? null, sampleCount: v.sampleCount ?? v.count ?? null, summary: clip(v.summary ?? v.message, 180), message: clip(v.message ?? v.summary, 180) }; };",
"const slimProjectManagement = (value) => { const v = objectOrNull(value); if (!v) return null; const s = objectOrNull(v.summary) || v; return { summary: { enabled: s.enabled === true, projectSampleCount: s.projectSampleCount ?? null, mdtodoSampleCount: s.mdtodoSampleCount ?? null, latestPageKind: clip(s.latestPageKind, 48), latestPath: clip(s.latestPath, 96), latestSourceCount: s.latestSourceCount ?? null, latestFileCount: s.latestFileCount ?? null, latestTaskCount: s.latestTaskCount ?? null, latestSelectedTaskRefHash: clip(s.latestSelectedTaskRefHash, 80), launchCommandCount: s.launchCommandCount ?? null, launchSuccessCount: s.launchSuccessCount ?? null, launchFailureCount: s.launchFailureCount ?? null, launchWithOtelTraceHeaderCount: s.launchWithOtelTraceHeaderCount ?? null, projectApiResponseCount: s.projectApiResponseCount ?? null, projectApiFailureCount: s.projectApiFailureCount ?? null, projectApiSlowPathCount: s.projectApiSlowPathCount ?? null, slowApiBudgetMs: s.slowApiBudgetMs ?? null }, commands: takeTail(v.commands, 8).map((item) => { const row = objectOrNull(item) || {}; return { ts: row.ts ?? null, phase: clip(row.phase, 16), type: clip(row.type, 32), commandId: clip(row.commandId, 80), launchStatus: row.launchStatus ?? null, sessionId: clip(row.sessionId, 80), workbenchUrl: clip(row.workbenchUrl, 120), otelTraceId: clip(row.otelTraceId, 32), selectedTaskRefHash: clip(row.selectedTaskRefHash, 80) }; }), samples: takeTail(v.samples, 8).map((item) => { const row = objectOrNull(item) || {}; return { seq: row.seq ?? null, ts: row.ts ?? null, pageRole: clip(row.pageRole, 24), path: clip(row.path, 96), pageKind: clip(row.pageKind, 48), sourceCount: row.sourceCount ?? null, fileCount: row.fileCount ?? null, taskCount: row.taskCount ?? null, selectedTaskRefHash: clip(row.selectedTaskRefHash, 80), launchButtonEnabled: row.launchButtonEnabled === true, workbenchLinkCount: row.workbenchLinkCount ?? null }; }), projectApiByPath: takeHead(v.projectApiByPath, 8).map(slimNetworkGroup), valuesRedacted: true }; };",
"const slimDomGroup = (item) => { const v = objectOrNull(item) || {}; return { count: v.count ?? null, firstAt: v.firstAt ?? null, lastAt: v.lastAt ?? null, text: clip(v.text ?? v.preview, 180) }; };",
"const slimNetworkGroup = (item) => { const v = objectOrNull(item) || {}; return { count: v.count ?? null, method: clip(v.method, 12), status: v.status ?? null, path: clip(v.path ?? v.urlPath, 96), firstAt: v.firstAt ?? null, lastAt: v.lastAt ?? null, promptIndexes: Array.isArray(v.promptIndexes) ? v.promptIndexes.slice(0, 6) : [], failureKinds: Array.isArray(v.failureKinds) ? v.failureKinds.slice(0, 4).map((x) => clip(x, 48)) : [] }; };",
"const slimDomSample = (item) => { const v = objectOrNull(item) || {}; return { seq: v.seq ?? null, ts: v.ts ?? null, source: clip(v.source, 32), diagnosticCode: clip(v.diagnosticCode, 48), traceId: clip(v.traceId, 64), httpStatus: v.httpStatus ?? null, idleSeconds: v.idleSeconds ?? null, waitingFor: clip(v.waitingFor, 48), lastEventLabel: clip(v.lastEventLabel, 80), text: clip(v.text ?? v.preview, 180) }; };",
@@ -8400,6 +8432,7 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
"const commandFailuresFromFindings = [...allFindingsForCommands, ...archiveRedFindings].flatMap((item) => Array.isArray(item?.commands) ? item.commands : []);",
"const srcPromptNetwork = objectOrNull(source?.promptNetwork);",
"const promptNetwork = srcPromptNetwork ? { promptSegments: srcPromptNetwork.promptSegments ?? null } : null;",
"const projectManagement = slimProjectManagement(source?.projectManagement || fullSource?.projectManagement);",
"const runnerErrorsFromJsonl = readJsonlTail(reportJsonPath.replace(/\\/analysis\\/report\\.json$/u, '/errors.jsonl'), 8).filter((item) => item?.type === 'runner-error').map(slimRunnerErrorFromJsonl);",
"const compact = source ? {",
" ok: analyzerExit === 0,",
@@ -8410,6 +8443,7 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
" sampleMetrics: metrics,",
" runtimeAlerts,",
" pagePerformance,",
" projectManagement,",
" promptNetwork,",
" pagePerformanceSlowApi: takeHead(sourceSlowApi, 4).map(slimSlowApi),",
" archivePagePerformanceSlowApi: takeHead(archiveSlowApi, 8).map(slimSlowApi),",
@@ -8498,6 +8532,7 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
" } : null,",
" runtimeAlerts: compact.runtimeAlerts ?? null,",
" pagePerformance: compact.pagePerformance ?? null,",
" projectManagement: compact.projectManagement ?? null,",
" promptNetwork: compact.promptNetwork ?? null,",
" toolFindings: Array.isArray(compact.toolFindings) ? compact.toolFindings.slice(0, 8) : [],",
" commandState: compact.commandState ?? null,",
@@ -8561,6 +8596,7 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
" } : null,",
" runtimeAlerts: compact.runtimeAlerts ?? null,",
" pagePerformance: compact.pagePerformance ?? null,",
" projectManagement: compact.projectManagement ? { summary: compact.projectManagement.summary ?? compact.projectManagement, samples: Array.isArray(compact.projectManagement.samples) ? compact.projectManagement.samples.slice(-4) : [], commands: Array.isArray(compact.projectManagement.commands) ? compact.projectManagement.commands.slice(-4) : [], launchCommands: Array.isArray(compact.projectManagement.launchCommands) ? compact.projectManagement.launchCommands.slice(-4) : [], projectApiByPath: Array.isArray(compact.projectManagement.projectApiByPath) ? compact.projectManagement.projectApiByPath.slice(0, 4) : [], slowProjectApiPerformance: Array.isArray(compact.projectManagement.slowProjectApiPerformance) ? compact.projectManagement.slowProjectApiPerformance.slice(0, 4) : [], valuesRedacted: true } : null,",
" toolFindings: Array.isArray(compact.toolFindings) ? compact.toolFindings.slice(0, 8) : [],",
" commandState: compact.commandState ?? null,",
" findings: Array.isArray(compact.findings) ? compact.findings.slice(0, 4) : [],",
@@ -8601,6 +8637,7 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
" } : null,",
" runtimeAlerts: compact.runtimeAlerts ?? null,",
" pagePerformance: compact.pagePerformance ?? null,",
" projectManagement: compact.projectManagement ? { summary: compact.projectManagement.summary ?? compact.projectManagement, samples: Array.isArray(compact.projectManagement.samples) ? compact.projectManagement.samples.slice(-3) : [], commands: Array.isArray(compact.projectManagement.commands) ? compact.projectManagement.commands.slice(-3) : [], launchCommands: Array.isArray(compact.projectManagement.launchCommands) ? compact.projectManagement.launchCommands.slice(-3) : [], projectApiByPath: Array.isArray(compact.projectManagement.projectApiByPath) ? compact.projectManagement.projectApiByPath.slice(0, 3) : [], slowProjectApiPerformance: Array.isArray(compact.projectManagement.slowProjectApiPerformance) ? compact.projectManagement.slowProjectApiPerformance.slice(0, 2) : [], valuesRedacted: true } : null,",
" toolFindings: Array.isArray(compact.toolFindings) ? compact.toolFindings.slice(0, 8) : [],",
" commandState: compact.commandState ?? null,",
" findings: Array.isArray(compact.findings) ? compact.findings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, summary: clip(item.summary ?? item.message, 120) })) : [],",
@@ -8624,7 +8661,7 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
" output = compactOutput(ultratiny);",
" }",
" if (Buffer.byteLength(output, 'utf8') > compactStdoutLimitBytes) {",
" output = compactOutput({ ok: compact.ok, counts: compact.counts ?? null, jsonlScope: compact.jsonlScope ?? null, analysisWindow: compact.analysisWindow ?? null, archiveSummary: compact.archiveSummary ? { redFindingCount: compact.archiveSummary.redFindingCount ?? null, findingCount: compact.archiveSummary.findingCount ?? null, sampleCount: compact.archiveSummary.sampleMetrics?.sampleCount ?? null, slowPathCount: compact.archiveSummary.pagePerformance?.slowPathCount ?? null } : null, toolFindings: Array.isArray(compact.toolFindings) ? compact.toolFindings.slice(0, 8) : [], commandState: compact.commandState ? { pendingCount: compact.commandState.pendingCount ?? null, processingCount: compact.commandState.processingCount ?? null, abandonedCount: compact.commandState.abandonedCount ?? null, failedCount: compact.commandState.failedCount ?? null } : null, findings: Array.isArray(compact.findings) ? compact.findings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, summary: clip(item.summary ?? item.message, 120) })) : [], archiveRedFindings: Array.isArray(compact.archiveRedFindings) ? compact.archiveRedFindings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, summary: clip(item.summary ?? item.message, 120) })) : [], commandFailures: Array.isArray(compact.commandFailures) ? compact.commandFailures.slice(-3).map((item) => ({ ts: item.ts ?? null, type: item.type ?? null, commandId: item.commandId ?? null, durationMs: item.durationMs ?? null, sampleSeq: item.sampleSeq ?? null, beforePath: item.beforePath ?? null, afterPath: item.afterPath ?? null, message: clip(item.message ?? item.failureKind ?? item.name, 120) })) : [], archivePagePerformanceSlowApi: Array.isArray(compact.archivePagePerformanceSlowApi) ? compact.archivePagePerformanceSlowApi.slice(0, 4).map((item) => ({ path: item.path ?? item.route ?? null, sampleCount: item.sampleCount ?? null, p95Ms: item.p95Ms ?? null, maxMs: item.maxMs ?? null, overFiveSecondCount: item.overFiveSecondCount ?? null })) : [], reportJsonPath: compact.reportJsonPath ?? reportJsonPath, reportJsonSha256: compact.reportJsonSha256 ?? sha256(reportJsonPath), reportMdPath: compact.reportMdPath ?? reportMdPath, reportMdSha256: compact.reportMdSha256 ?? sha256(reportMdPath), analyzer: { ...(compact.analyzer ?? {}), compactStdoutLimited: true, ultratiny: true, hardFallback: true, compactStdoutLimitBytes }, valuesRedacted: true });",
" output = compactOutput({ ok: compact.ok, counts: compact.counts ?? null, jsonlScope: compact.jsonlScope ?? null, analysisWindow: compact.analysisWindow ?? null, archiveSummary: compact.archiveSummary ? { redFindingCount: compact.archiveSummary.redFindingCount ?? null, findingCount: compact.archiveSummary.findingCount ?? null, sampleCount: compact.archiveSummary.sampleMetrics?.sampleCount ?? null, slowPathCount: compact.archiveSummary.pagePerformance?.slowPathCount ?? null } : null, projectManagement: compact.projectManagement ? { summary: compact.projectManagement.summary ?? compact.projectManagement, samples: Array.isArray(compact.projectManagement.samples) ? compact.projectManagement.samples.slice(-2) : [], commands: Array.isArray(compact.projectManagement.commands) ? compact.projectManagement.commands.slice(-2) : [], launchCommands: Array.isArray(compact.projectManagement.launchCommands) ? compact.projectManagement.launchCommands.slice(-2) : [], projectApiByPath: Array.isArray(compact.projectManagement.projectApiByPath) ? compact.projectManagement.projectApiByPath.slice(0, 2) : [], valuesRedacted: true } : null, toolFindings: Array.isArray(compact.toolFindings) ? compact.toolFindings.slice(0, 8) : [], commandState: compact.commandState ? { pendingCount: compact.commandState.pendingCount ?? null, processingCount: compact.commandState.processingCount ?? null, abandonedCount: compact.commandState.abandonedCount ?? null, failedCount: compact.commandState.failedCount ?? null } : null, findings: Array.isArray(compact.findings) ? compact.findings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, summary: clip(item.summary ?? item.message, 120) })) : [], archiveRedFindings: Array.isArray(compact.archiveRedFindings) ? compact.archiveRedFindings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, summary: clip(item.summary ?? item.message, 120) })) : [], commandFailures: Array.isArray(compact.commandFailures) ? compact.commandFailures.slice(-3).map((item) => ({ ts: item.ts ?? null, type: item.type ?? null, commandId: item.commandId ?? null, durationMs: item.durationMs ?? null, sampleSeq: item.sampleSeq ?? null, beforePath: item.beforePath ?? null, afterPath: item.afterPath ?? null, message: clip(item.message ?? item.failureKind ?? item.name, 120) })) : [], archivePagePerformanceSlowApi: Array.isArray(compact.archivePagePerformanceSlowApi) ? compact.archivePagePerformanceSlowApi.slice(0, 4).map((item) => ({ path: item.path ?? item.route ?? null, sampleCount: item.sampleCount ?? null, p95Ms: item.p95Ms ?? null, maxMs: item.maxMs ?? null, overFiveSecondCount: item.overFiveSecondCount ?? null })) : [], reportJsonPath: compact.reportJsonPath ?? reportJsonPath, reportJsonSha256: compact.reportJsonSha256 ?? sha256(reportJsonPath), reportMdPath: compact.reportMdPath ?? reportMdPath, reportMdSha256: compact.reportMdSha256 ?? sha256(reportMdPath), analyzer: { ...(compact.analyzer ?? {}), compactStdoutLimited: true, ultratiny: true, hardFallback: true, compactStdoutLimitBytes }, valuesRedacted: true });",
" }",
" }",
"}",
@@ -8750,7 +8787,7 @@ function recoverWebObserveAnalyzeFromArtifacts(options: NodeWebProbeObserveOptio
"const archiveSummary = objectOrNull(source.archiveSummary) || {};",
"const archiveSampleMetrics = objectOrNull(archiveSummary.sampleMetrics) || objectOrNull(source.sampleMetrics?.summary) || objectOrNull(srcMetrics.summary) || {};",
"const slowApis = arr(source.pagePerformanceSlowApi).length > 0 ? arr(source.pagePerformanceSlowApi) : arr(pagePerformance.sameOriginApiByPath).filter((item) => Number(item?.overBudgetCount ?? item?.overFiveSecondCount ?? 0) > 0);",
"const compact = { ok: source.ok === true, command: source.command ?? 'web-probe-observe analyze', stateDir: source.stateDir ?? stateDir, jsonlScope: source.jsonlScope ?? null, alertThresholds: source.alertThresholds ?? null, counts: source.counts ?? null, archiveSummary: { ...archiveSummary, sampleMetrics: archiveSampleMetrics, pagePerformance: objectOrNull(archiveSummary.pagePerformance) || objectOrNull(pagePerformance.summary) || {}, runtimeAlerts: objectOrNull(archiveSummary.runtimeAlerts) || objectOrNull(runtimeAlerts.summary) || {}, redFindings: arr(archiveSummary.redFindings).slice(0, 12).map(slimFinding) }, analysisWindow: source.analysisWindow ?? objectOrNull(recent.summary), sampleMetrics: compactMetrics(srcMetrics), pageProvenance: objectOrNull(source.pageProvenance?.summary) || source.pageProvenance ?? null, pagePerformance: objectOrNull(pagePerformance.summary) || pagePerformance, promptNetwork: objectOrNull(promptNetwork.summary) || promptNetwork, runtimeAlerts: objectOrNull(runtimeAlerts.summary) || runtimeAlerts, runnerErrors: arr(source.runnerErrors).slice(-8), commandFailures: arr(source.commandFailures).slice(-8), commandState: objectOrNull(source.commandState) || null, toolFindings: arr(source.toolFindings).slice(0, 8).map(slimFinding), httpErrorGroups: arr(source.httpErrorGroups ?? runtimeAlerts.networkHttpErrorsByPath).slice(0, 8).map(slimGroup), requestFailedGroups: arr(source.requestFailedGroups ?? runtimeAlerts.networkRequestFailedByPath).slice(0, 8).map(slimGroup), domDiagnosticGroups: arr(source.domDiagnosticGroups ?? runtimeAlerts.domDiagnosticsByText).slice(0, 5).map(slimGroup), domDiagnosticSamples: arr(source.domDiagnosticSamples ?? runtimeAlerts.domDiagnostics).slice(0, 8).map(slimGroup), consoleAlertGroups: arr(source.consoleAlertGroups ?? runtimeAlerts.consoleAlertsByPath).slice(0, 8).map(slimGroup), consoleAlertSamples: arr(source.consoleAlertSamples ?? runtimeAlerts.consoleAlerts).slice(0, 8).map(slimGroup), turnTimingRecentUpdateJumps: arr(source.turnTimingRecentUpdateJumps ?? srcMetrics.turnTimingRecentUpdateSawtoothJumps).slice(0, 8), turnTimingElapsedZeroResets: arr(source.turnTimingElapsedZeroResets ?? srcMetrics.turnTimingElapsedZeroResets).slice(0, 8), turnTimingTotalElapsedForwardJumps: arr(source.turnTimingTotalElapsedForwardJumps ?? srcMetrics.turnTimingTotalElapsedForwardJumps).slice(0, 8), pagePerformanceSlowApi: slowApis.slice(0, 8).map(slimSlowApi), archivePagePerformanceSlowApi: arr(source.archivePagePerformanceSlowApi).slice(0, 8).map(slimSlowApi), pagePerformancePartialApi: arr(source.pagePerformancePartialApi).slice(0, 8), pagePerformanceSseStreams: arr(source.pagePerformanceSseStreams).slice(0, 8), findings: arr(source.findings).slice(0, 12).map(slimFinding), archiveRedFindings: arr(source.archiveRedFindings ?? archiveSummary.redFindings).slice(0, 12).map(slimFinding), reportJsonPath: source.reportJsonPath ?? reportJsonPath, reportJsonSha256: source.reportJsonSha256 ?? sha256(reportJsonPath), reportMdPath: source.reportMdPath ?? reportMdPath, reportMdSha256: source.reportMdSha256 ?? sha256(reportMdPath), analyzer: { ...(objectOrNull(source.analyzer) || {}), recoveredFrom: 'analysis-artifact-after-transport-timeout', stdoutPath, stderrPath, stdoutBytes: statSize(stdoutPath), stderrBytes: statSize(stderrPath), reportJsonBytes: statSize(reportJsonPath), reportMdBytes: statSize(reportMdPath), transportExitCode: Number(transportExitRaw), transportTimedOut: transportTimedOutRaw === 'true', valuesRedacted: true }, valuesRedacted: true };",
"const compact = { ok: source.ok === true, command: source.command ?? 'web-probe-observe analyze', stateDir: source.stateDir ?? stateDir, jsonlScope: source.jsonlScope ?? null, alertThresholds: source.alertThresholds ?? null, counts: source.counts ?? null, archiveSummary: { ...archiveSummary, sampleMetrics: archiveSampleMetrics, pagePerformance: objectOrNull(archiveSummary.pagePerformance) || objectOrNull(pagePerformance.summary) || {}, runtimeAlerts: objectOrNull(archiveSummary.runtimeAlerts) || objectOrNull(runtimeAlerts.summary) || {}, redFindings: arr(archiveSummary.redFindings).slice(0, 12).map(slimFinding) }, analysisWindow: source.analysisWindow ?? objectOrNull(recent.summary), sampleMetrics: compactMetrics(srcMetrics), pageProvenance: objectOrNull(source.pageProvenance?.summary) || source.pageProvenance ?? null, pagePerformance: objectOrNull(pagePerformance.summary) || pagePerformance, projectManagement: objectOrNull(source.projectManagement) || null, promptNetwork: objectOrNull(promptNetwork.summary) || promptNetwork, runtimeAlerts: objectOrNull(runtimeAlerts.summary) || runtimeAlerts, runnerErrors: arr(source.runnerErrors).slice(-8), commandFailures: arr(source.commandFailures).slice(-8), commandState: objectOrNull(source.commandState) || null, toolFindings: arr(source.toolFindings).slice(0, 8).map(slimFinding), httpErrorGroups: arr(source.httpErrorGroups ?? runtimeAlerts.networkHttpErrorsByPath).slice(0, 8).map(slimGroup), requestFailedGroups: arr(source.requestFailedGroups ?? runtimeAlerts.networkRequestFailedByPath).slice(0, 8).map(slimGroup), domDiagnosticGroups: arr(source.domDiagnosticGroups ?? runtimeAlerts.domDiagnosticsByText).slice(0, 5).map(slimGroup), domDiagnosticSamples: arr(source.domDiagnosticSamples ?? runtimeAlerts.domDiagnostics).slice(0, 8).map(slimGroup), consoleAlertGroups: arr(source.consoleAlertGroups ?? runtimeAlerts.consoleAlertsByPath).slice(0, 8).map(slimGroup), consoleAlertSamples: arr(source.consoleAlertSamples ?? runtimeAlerts.consoleAlerts).slice(0, 8).map(slimGroup), turnTimingRecentUpdateJumps: arr(source.turnTimingRecentUpdateJumps ?? srcMetrics.turnTimingRecentUpdateSawtoothJumps).slice(0, 8), turnTimingElapsedZeroResets: arr(source.turnTimingElapsedZeroResets ?? srcMetrics.turnTimingElapsedZeroResets).slice(0, 8), turnTimingTotalElapsedForwardJumps: arr(source.turnTimingTotalElapsedForwardJumps ?? srcMetrics.turnTimingTotalElapsedForwardJumps).slice(0, 8), pagePerformanceSlowApi: slowApis.slice(0, 8).map(slimSlowApi), archivePagePerformanceSlowApi: arr(source.archivePagePerformanceSlowApi).slice(0, 8).map(slimSlowApi), pagePerformancePartialApi: arr(source.pagePerformancePartialApi).slice(0, 8), pagePerformanceSseStreams: arr(source.pagePerformanceSseStreams).slice(0, 8), findings: arr(source.findings).slice(0, 12).map(slimFinding), archiveRedFindings: arr(source.archiveRedFindings ?? archiveSummary.redFindings).slice(0, 12).map(slimFinding), reportJsonPath: source.reportJsonPath ?? reportJsonPath, reportJsonSha256: source.reportJsonSha256 ?? sha256(reportJsonPath), reportMdPath: source.reportMdPath ?? reportMdPath, reportMdSha256: source.reportMdSha256 ?? sha256(reportMdPath), analyzer: { ...(objectOrNull(source.analyzer) || {}), recoveredFrom: 'analysis-artifact-after-transport-timeout', stdoutPath, stderrPath, stdoutBytes: statSize(stdoutPath), stderrBytes: statSize(stderrPath), reportJsonBytes: statSize(reportJsonPath), reportMdBytes: statSize(reportMdPath), transportExitCode: Number(transportExitRaw), transportTimedOut: transportTimedOutRaw === 'true', valuesRedacted: true }, valuesRedacted: true };",
"console.log(JSON.stringify(compact));",
"UNIDESK_WEB_OBSERVE_RECOVER_ANALYZE_ARTIFACT",
].join("\n");
@@ -8840,6 +8877,15 @@ function renderWebObserveAnalyzeTable(value: Record<string, unknown>): string {
const runtimeAlerts = record(analysis?.runtimeAlerts);
const pagePerformance = record(analysis?.pagePerformance);
const pagePerformanceSummary = record(pagePerformance?.summary);
const projectManagement = record(analysis?.projectManagement) ?? record(value.projectManagement);
const projectManagementSummary = record(projectManagement?.summary) ?? projectManagement;
const projectManagementCommandsSource = Array.isArray(projectManagement?.launchCommands) && projectManagement.launchCommands.length > 0
? projectManagement.launchCommands
: projectManagement?.commands;
const projectManagementCommands = webObserveArray(projectManagementCommandsSource).map(record).filter((item): item is Record<string, unknown> => item !== null).slice(-8);
const projectManagementSamples = webObserveArray(projectManagement?.samples).map(record).filter((item): item is Record<string, unknown> => item !== null).slice(-8);
const projectManagementApiByPath = webObserveArray(projectManagement?.projectApiByPath).map(record).filter((item): item is Record<string, unknown> => item !== null).slice(0, 8);
const projectManagementSlowApis = webObserveArray(projectManagement?.slowProjectApiPerformance).map(record).filter((item): item is Record<string, unknown> => item !== null).slice(0, 8);
const alertThresholds = record(analysis?.alertThresholds ?? pagePerformanceSummary?.alertThresholds ?? value.alertThresholds);
const budgetLabel = (rawValue: unknown): string => {
const parsed = Number(rawValue);
@@ -8848,6 +8894,7 @@ function renderWebObserveAnalyzeTable(value: Record<string, unknown>): string {
return ms % 1000 === 0 ? `${Math.round(ms / 1000)}s` : `${ms}ms`;
};
const sameOriginApiBudgetLabel = budgetLabel(alertThresholds?.sameOriginApiSlowMs ?? pagePerformanceSummary?.budgetMs);
const projectManagementApiBudgetLabel = budgetLabel(projectManagementSummary?.slowApiBudgetMs ?? alertThresholds?.sameOriginApiSlowMs ?? pagePerformanceSummary?.budgetMs);
const partialApiBudgetLabel = budgetLabel(alertThresholds?.partialApiSlowMs);
const streamOpenBudgetLabel = budgetLabel(alertThresholds?.longLivedStreamOpenSlowMs);
const loadingBudgetLabel = budgetLabel(alertThresholds?.visibleLoadingSlowMs);
@@ -8993,6 +9040,47 @@ function renderWebObserveAnalyzeTable(value: Record<string, unknown>): string {
webObserveShort(webObserveText(item.lastAt ?? item.firstAt), 24),
webObserveShort(webObserveArray(item.failureKinds).join(",") || "-", 40),
]);
const projectManagementCommandRows = projectManagementCommands.map((item) => [
webObserveShort(webObserveText(item.ts), 24),
webObserveShort(webObserveText(item.phase), 12),
webObserveShort(webObserveText(item.type), 26),
webObserveText(item.launchStatus ?? item.status),
webObserveShort(webObserveText(item.sessionId), 24),
webObserveShort(webObserveText(item.otelTraceId), 18),
webObserveShort(webObserveText(item.selectedTaskRefHash ?? item.taskHash), 18),
webObserveShort(webObserveText(item.workbenchUrl ?? item.afterPath), 44),
webObserveShort(webObserveText(item.message ?? item.errorMessageHash), 72),
]);
const projectManagementSampleRows = projectManagementSamples.map((item) => [
webObserveText(item.seq),
webObserveShort(webObserveText(item.ts), 24),
webObserveShort(webObserveText(item.pageRole), 12),
webObserveShort(webObserveText(item.pageKind), 28),
webObserveShort(webObserveText(item.path), 36),
webObserveText(item.sourceCount),
webObserveText(item.fileCount),
webObserveText(item.taskCount),
webObserveShort(webObserveText(item.selectedTaskRefHash), 18),
webObserveText(item.launchButtonEnabled),
webObserveText(item.workbenchLinkCount),
]);
const projectManagementApiRows = projectManagementApiByPath.map((item) => [
webObserveText(item.count ?? item.sampleCount),
webObserveShort(webObserveText(item.type), 12),
webObserveText(item.method),
webObserveText(item.status),
webObserveShort(webObserveText(item.path ?? item.urlPath), 52),
webObserveShort(webObserveText(item.lastAt ?? item.firstAt), 24),
webObserveShort(webObserveArray(item.failureKinds).join(",") || "-", 40),
]);
const projectManagementSlowRows = projectManagementSlowApis.map((item) => [
webObserveShort(webObserveText(item.path ?? item.route), 52),
webObserveText(item.sampleCount),
webObserveText(item.p95Ms ?? item.p95),
webObserveText(item.maxMs ?? item.max),
webObserveText(item.overBudgetCount ?? item.overFiveSecondCount),
webObserveShort(webObserveArray(item.slowSamples).map((sample) => webObserveText(record(sample)?.otelTraceId)).filter((text) => text !== "-").join(",") || "-", 36),
]);
const lines = [
`hwlab nodes web-probe observe analyze (${webObserveText(value.status)})`,
"",
@@ -9047,6 +9135,34 @@ function renderWebObserveAnalyzeTable(value: Record<string, unknown>): string {
["md", webObserveShort(webObserveText(analysis?.reportMdPath), 96), webObserveShort(webObserveText(analysis?.reportMdSha256), 24)],
]),
"",
"Project management:",
webObserveTable(["ENABLED", "SAMPLES", "MDTODO", "LATEST", "SRC", "FILES", "TASKS", "SELECTED_TASK", "LAUNCH", "OTEL", "API", `SLOW>${projectManagementApiBudgetLabel}`], [[
webObserveText(projectManagementSummary?.enabled),
webObserveText(projectManagementSummary?.projectSampleCount),
webObserveText(projectManagementSummary?.mdtodoSampleCount),
webObserveShort([webObserveText(projectManagementSummary?.latestPageKind), webObserveText(projectManagementSummary?.latestPath)].filter((part) => part !== "-" && part !== "").join(" "), 52),
webObserveText(projectManagementSummary?.latestSourceCount),
webObserveText(projectManagementSummary?.latestFileCount),
webObserveText(projectManagementSummary?.latestTaskCount),
webObserveShort(webObserveText(projectManagementSummary?.latestSelectedTaskRefHash), 18),
`ok=${webObserveText(projectManagementSummary?.launchSuccessCount)} fail=${webObserveText(projectManagementSummary?.launchFailureCount)} total=${webObserveText(projectManagementSummary?.launchCommandCount)}`,
`${webObserveText(projectManagementSummary?.launchWithOtelTraceHeaderCount)}/${webObserveText(projectManagementSummary?.launchSuccessCount)}`,
`resp=${webObserveText(projectManagementSummary?.projectApiResponseCount)} fail=${webObserveText(projectManagementSummary?.projectApiFailureCount ?? 0)}/${webObserveText(projectManagementSummary?.projectApiRequestFailedCount ?? 0)}`,
webObserveText(projectManagementSummary?.projectApiSlowPathCount),
]]),
"",
"Project management samples:",
webObserveTable(["SEQ", "TS", "ROLE", "KIND", "PATH", "SRC", "FILES", "TASKS", "SELECTED", "LAUNCH", "LINKS"], projectManagementSampleRows.length > 0 ? projectManagementSampleRows : [["-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-"]]),
"",
"Project management commands:",
webObserveTable(["TS", "PHASE", "TYPE", "STATUS", "SESSION", "OTEL", "TASK", "URL", "MESSAGE"], projectManagementCommandRows.length > 0 ? projectManagementCommandRows : [["-", "-", "-", "-", "-", "-", "-", "-", "-"]]),
"",
"Project management API:",
webObserveTable(["COUNT", "TYPE", "METHOD", "STATUS", "PATH", "LAST", "FAILURE"], projectManagementApiRows.length > 0 ? projectManagementApiRows : [["-", "-", "-", "-", "-", "-", "-"]]),
"",
`Project management slow API (>${projectManagementApiBudgetLabel}):`,
webObserveTable(["PATH", "SAMPLES", "P95", "MAX", "OVER_BUDGET", "OTEL"], projectManagementSlowRows.length > 0 ? projectManagementSlowRows : [["-", "-", "-", "-", "-", "-"]]),
"",
"Turn timing:",
webObserveTable(["ROUNDS", "TURNS", "ROWS", "NON_MONO", "ELAPSED_DEC", "ZERO_RESET", "ELAPSED_JUMP", "TERMINAL_GROWTH", "RECENT_JUMP", "MAX_RECENT_STEP"], [[
webObserveText(roundCount),
@@ -10477,6 +10593,13 @@ function nodeWebObserveWaitCommandShell(commandId: string, waitMs: number): stri
function commandSummaryForOutput(payload: Record<string, unknown>): Record<string, unknown> {
const text = typeof payload.text === "string" ? payload.text : 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,
@@ -10484,6 +10607,9 @@ function commandSummaryForOutput(payload: Record<string, unknown>): Record<strin
label: payload.label ?? null,
sessionId: payload.sessionId ?? null,
provider: payload.provider ?? null,
sourceId: opaque(payload.sourceId),
fileRef: opaque(payload.fileRef),
taskRef: opaque(payload.taskRef),
textHash: text === null ? null : `sha256:${createHash("sha256").update(text).digest("hex")}`,
textBytes: text === null ? null : Buffer.byteLength(text),
valuesRedacted: true,