Files
pikasTech-unidesk/scripts/src/hwlab-node-web-observe-analyzer-io-source.ts
T

856 lines
40 KiB
TypeScript

// SPEC: PJ2026-01040111 long-running Workbench observation.
// Responsibility: Analyzer artifact IO, YAML-derived config parsing, JSONL tailing, focus windows, and sample compaction source fragment.
export function nodeWebObserveAnalyzerIoSource(): string {
return String.raw`async function readJson(file) {
try { return JSON.parse(await readFile(file, "utf8")); } catch { return null; }
}
async function readCommandState(rootDir) {
const buckets = {};
for (const bucket of ["pending", "processing", "done", "failed", "abandoned"]) {
buckets[bucket] = await readCommandBucket(path.join(rootDir, "commands", bucket), bucket);
}
return {
pendingCount: buckets.pending.count,
processingCount: buckets.processing.count,
doneCount: buckets.done.count,
failedCount: buckets.failed.count,
abandonedCount: buckets.abandoned.count,
pending: buckets.pending.items,
processing: buckets.processing.items,
failed: buckets.failed.items.slice(0, 12),
abandoned: buckets.abandoned.items.slice(0, 20),
summary: {
backlogCount: buckets.pending.count + buckets.processing.count,
oldestPendingAgeSeconds: buckets.pending.oldestAgeSeconds,
oldestProcessingAgeSeconds: buckets.processing.oldestAgeSeconds,
valuesRedacted: true
},
valuesRedacted: true
};
}
async function readCommandBucket(dir, bucket) {
let names = [];
try {
names = (await readdir(dir)).filter((name) => name.endsWith(".json")).sort();
} catch (error) {
if (!error || error.code !== "ENOENT") jsonlReadIssues.push({ file: path.basename(dir), kind: "command-dir-read-error", code: error && error.code ? String(error.code) : null, errorMessage: limitText(error && error.message ? error.message : String(error), 240) });
return { bucket, count: 0, oldestAgeSeconds: null, items: [] };
}
const items = [];
for (const name of names.slice(0, 200)) {
const file = path.join(dir, name);
const parsed = await readJson(file) || {};
let meta = null;
try { meta = await stat(file); } catch {}
const timestamp = parsed.createdAt || parsed.abandonedAt || parsed.completedAt || parsed.failedAt || (meta ? meta.mtime.toISOString() : null);
const timestampMs = Date.parse(String(timestamp || ""));
items.push({
bucket,
id: parsed.id || parsed.commandId || name.replace(/[.]json$/u, ""),
type: parsed.type || parsed.command?.type || null,
createdAt: parsed.createdAt || null,
completedAt: parsed.completedAt || null,
failedAt: parsed.failedAt || null,
abandonedAt: parsed.abandonedAt || null,
reason: parsed.reason || parsed.error?.message || null,
ageSeconds: Number.isFinite(timestampMs) ? Math.max(0, Math.round((Date.now() - timestampMs) / 1000)) : null,
mtime: meta ? meta.mtime.toISOString() : null,
valuesRedacted: true
});
}
const ages = items.map((item) => item.ageSeconds).filter((value) => Number.isFinite(value));
return { bucket, count: names.length, oldestAgeSeconds: ages.length > 0 ? Math.max(...ages) : null, items };
}
function buildToolFindings({ manifest, heartbeat, commandState }) {
const findings = [];
const diagnostics = heartbeatDiagnostics(manifest, heartbeat);
const startupReadiness = toolStartupReadiness(manifest, heartbeat);
if (startupReadiness !== null) {
findings.push({
id: "tool-target-page-not-ready",
severity: "red",
summary: "web-probe observe target page did not reach the expected Workbench app shell; treat this as probe setup/runtime access blocker before interpreting business turns or frontend hotspots",
count: 1,
reason: startupReadiness.reason,
rootCause: startupReadiness.reason === "nav-access-denied" ? "web_probe_target_nav_access_denied" : startupReadiness.reason === "workbench-blank-document" ? "web_probe_target_blank_workbench_document" : "web_probe_target_page_not_ready",
rootCauseStatus: "confirmed-from-startup-readiness",
rootCauseConfidence: "high",
nextAction: "Fix the selected node/lane Web access or Workbench shell loading first, then rerun observe start and performanceCapture; do not treat an empty performance report from this run as a business performance result.",
readiness: startupReadiness,
valuesRedacted: true
});
}
if (diagnostics.heartbeatStale) {
findings.push({
id: "tool-runner-heartbeat-stale",
severity: "red",
summary: "web-probe observe runner heartbeat is stale; treat this as observer tooling failure, not Workbench behavior",
count: 1,
diagnostics,
valuesRedacted: true
});
}
if ((commandState?.pendingCount ?? 0) > 0 || (commandState?.processingCount ?? 0) > 0) {
findings.push({
id: "tool-pending-commands-unconsumed",
severity: "red",
summary: "web-probe observe has pending/processing control commands that were not consumed by the runner",
count: (commandState?.pendingCount ?? 0) + (commandState?.processingCount ?? 0),
pending: (commandState?.pending ?? []).slice(0, 12),
processing: (commandState?.processing ?? []).slice(0, 12),
valuesRedacted: true
});
}
if ((commandState?.abandonedCount ?? 0) > 0) {
findings.push({
id: "tool-commands-abandoned",
severity: "info",
summary: "web-probe observe force-stop abandoned queued commands; do not interpret these as Workbench command failures",
count: commandState.abandonedCount,
commands: (commandState.abandoned ?? []).slice(0, 20),
valuesRedacted: true
});
}
if (heartbeat?.forceStop || manifest?.forceStop) {
findings.push({
id: "tool-runner-force-stopped",
severity: "info",
summary: "web-probe observe runner was stopped by the CLI outside the command queue",
count: 1,
forceStop: heartbeat?.forceStop || manifest?.forceStop,
valuesRedacted: true
});
}
return findings;
}
function toolStartupReadiness(manifest, heartbeat) {
const candidates = [
heartbeat?.error?.navigationReadiness,
heartbeat?.error?.details?.readiness,
heartbeat?.error?.details?.readinessAfterWait,
heartbeat?.error?.details?.readinessBeforeClick,
manifest?.error?.navigationReadiness,
manifest?.error?.details?.readiness,
manifest?.error?.details?.readinessAfterWait,
manifest?.error?.details?.readinessBeforeClick,
];
for (const candidate of candidates) {
const readiness = candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
if (!readiness) continue;
const snapshot = readiness.snapshot && typeof readiness.snapshot === "object" && !Array.isArray(readiness.snapshot) ? readiness.snapshot : readiness;
const reason = typeof readiness.reason === "string" && readiness.reason ? readiness.reason : typeof snapshot.reason === "string" && snapshot.reason ? snapshot.reason : null;
if (reason === null) continue;
return {
reason,
path: typeof snapshot.path === "string" ? snapshot.path : null,
search: typeof snapshot.search === "string" ? snapshot.search : null,
blockedReason: typeof snapshot.blockedReason === "string" ? snapshot.blockedReason : null,
readyState: typeof snapshot.readyState === "string" ? snapshot.readyState : null,
workbenchShellVisible: snapshot.workbenchShellVisible === true,
sessionCreatePresent: snapshot.sessionCreatePresent === true,
sessionCreateVisible: snapshot.sessionCreateVisible === true,
sessionRailPresent: snapshot.sessionRailPresent === true,
commandInputPresent: snapshot.commandInputPresent === true,
activeTabPresent: snapshot.activeTabPresent === true,
loginVisible: snapshot.loginVisible === true,
bodyTextBytes: Number.isFinite(Number(snapshot.bodyTextBytes)) ? Number(snapshot.bodyTextBytes) : null,
bodyTextHash: typeof snapshot.bodyTextHash === "string" ? snapshot.bodyTextHash : null,
valuesRedacted: true
};
}
return null;
}
function heartbeatDiagnostics(manifest, heartbeat) {
const status = String(heartbeat?.status || manifest?.status || "");
const terminal = /^(completed|failed|force-stopped|stopped|abandoned)$/u.test(status);
const sampleIntervalMs = Number(manifest?.sampling?.sampleIntervalMs) || 5000;
const staleAfterMs = Math.max(60000, sampleIntervalMs * 3);
const updatedAt = heartbeat?.updatedAt || heartbeat?.lastSampleAt || null;
const updatedMs = Date.parse(String(updatedAt || ""));
const ageSeconds = Number.isFinite(updatedMs) ? Math.max(0, Math.round((Date.now() - updatedMs) / 1000)) : null;
const heartbeatStale = !terminal && (!Number.isFinite(updatedMs) || Date.now() - updatedMs > staleAfterMs);
return {
status: status || null,
terminal,
updatedAt,
heartbeatAgeSeconds: ageSeconds,
heartbeatStale,
heartbeatStaleAfterSeconds: Math.round(staleAfterMs / 1000),
sampleSeq: heartbeat?.sampleSeq ?? null,
commandSeq: heartbeat?.commandSeq ?? null,
activeCommandId: heartbeat?.activeCommandId ?? null,
valuesRedacted: true
};
}
function safeArchivePrefix(value) {
const text = String(value || "").trim();
if (!text) return "";
if (!/^[A-Za-z0-9_.-]+$/u.test(text) || text.includes("..")) throw new Error("unsafe archive prefix: " + text);
return text;
}
function positiveNumber(value, fallback) {
const parsed = Number(value);
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
}
function requiredPositiveThreshold(raw, key) {
const parsed = Number(raw?.[key]);
if (!Number.isFinite(parsed) || parsed <= 0) {
throw new Error("UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON requires positive " + key + "; configure config/hwlab-node-lanes.yaml webProbe.alertThresholds");
}
return parsed;
}
function parseAlertThresholds(value) {
if (!value) {
throw new Error("UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON is required; configure config/hwlab-node-lanes.yaml webProbe.alertThresholds for the selected node/lane");
}
const raw = (() => {
try { return JSON.parse(value); } catch (error) { throw new Error("UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON is invalid JSON: " + (error instanceof Error ? error.message : String(error))); }
})();
const sessionRailFallbackRatio = requiredPositiveThreshold(raw, "sessionRailFallbackRatio");
if (sessionRailFallbackRatio > 1) {
throw new Error("UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON sessionRailFallbackRatio must be <= 1");
}
return {
sameOriginApiSlowMs: requiredPositiveThreshold(raw, "sameOriginApiSlowMs"),
partialApiSlowMs: requiredPositiveThreshold(raw, "partialApiSlowMs"),
longLivedStreamOpenSlowMs: requiredPositiveThreshold(raw, "longLivedStreamOpenSlowMs"),
visibleLoadingSlowMs: requiredPositiveThreshold(raw, "visibleLoadingSlowMs"),
turnTimingSampleSlackSeconds: requiredPositiveThreshold(raw, "turnTimingSampleSlackSeconds"),
turnElapsedSevereTimeoutSeconds: requiredPositiveThreshold(raw, "turnElapsedSevereTimeoutSeconds"),
domEvaluateTimeoutRedCount: requiredPositiveThreshold(raw, "domEvaluateTimeoutRedCount"),
domEvaluateTimeoutRedWindowMs: requiredPositiveThreshold(raw, "domEvaluateTimeoutRedWindowMs"),
screenshotTimeoutRedCount: requiredPositiveThreshold(raw, "screenshotTimeoutRedCount"),
pageErrorRedCount: requiredPositiveThreshold(raw, "pageErrorRedCount"),
longTaskRedMs: requiredPositiveThreshold(raw, "longTaskRedMs"),
longAnimationFrameRedMs: requiredPositiveThreshold(raw, "longAnimationFrameRedMs"),
eventLoopGapRedMs: requiredPositiveThreshold(raw, "eventLoopGapRedMs"),
browserProcessSampleIntervalMs: requiredPositiveThreshold(raw, "browserProcessSampleIntervalMs"),
requestRateBucketMs: requiredPositiveThreshold(raw, "requestRateBucketMs"),
requestRateTotalRedPerMinute: requiredPositiveThreshold(raw, "requestRateTotalRedPerMinute"),
requestRatePageRedPerMinute: requiredPositiveThreshold(raw, "requestRatePageRedPerMinute"),
requestRateApiPathRedPerMinute: requiredPositiveThreshold(raw, "requestRateApiPathRedPerMinute"),
browserTotalRssRedMb: requiredPositiveThreshold(raw, "browserTotalRssRedMb"),
browserProcessRssRedMb: requiredPositiveThreshold(raw, "browserProcessRssRedMb"),
browserRssGrowthRedMb: requiredPositiveThreshold(raw, "browserRssGrowthRedMb"),
browserRssGrowthWindowMs: requiredPositiveThreshold(raw, "browserRssGrowthWindowMs"),
playwrightResponsivenessRedMs: requiredPositiveThreshold(raw, "playwrightResponsivenessRedMs"),
playwrightResponsivenessTimeoutRedCount: requiredPositiveThreshold(raw, "playwrightResponsivenessTimeoutRedCount"),
cdpMetricsTimeoutRedCount: requiredPositiveThreshold(raw, "cdpMetricsTimeoutRedCount"),
uncommandedStateChangeCommandWindowMs: requiredPositiveThreshold(raw, "uncommandedStateChangeCommandWindowMs"),
scrollJumpCommandWindowMs: requiredPositiveThreshold(raw, "scrollJumpCommandWindowMs"),
scrollJumpFromY: requiredPositiveThreshold(raw, "scrollJumpFromY"),
scrollJumpToY: requiredPositiveThreshold(raw, "scrollJumpToY"),
sessionRailFallbackRatio,
crossPageProjectionDivergenceRedMs: positiveNumber(raw.crossPageProjectionDivergenceRedMs, requiredPositiveThreshold(raw, "visibleLoadingSlowMs")),
source: "yaml-env",
};
}
function parseBrowserFreezePolicy(value) {
if (!value) {
throw new Error("UNIDESK_WEB_OBSERVE_BROWSER_FREEZE_POLICY_JSON is required; configure config/hwlab-node-lanes.yaml webProbe.browserFreezePolicy for the selected node/lane");
}
const raw = (() => {
try { return JSON.parse(value); } catch (error) { throw new Error("UNIDESK_WEB_OBSERVE_BROWSER_FREEZE_POLICY_JSON is invalid JSON: " + (error instanceof Error ? error.message : String(error))); }
})();
const memory = requiredPolicyRecord(raw, "memory", "webProbe.browserFreezePolicy");
const responsiveness = requiredPolicyRecord(raw, "responsiveness", "webProbe.browserFreezePolicy");
const cdp = requiredPolicyRecord(raw, "cdp", "webProbe.browserFreezePolicy");
const kill = requiredPolicyRecord(raw, "kill", "webProbe.browserFreezePolicy");
return {
enabled: requiredPolicyBoolean(raw, "enabled", "webProbe.browserFreezePolicy"),
blockerWindowMs: requiredPolicyPositiveNumber(raw, "blockerWindowMs", "webProbe.browserFreezePolicy"),
memory: {
totalRssBlockerMb: requiredPolicyPositiveNumber(memory, "totalRssBlockerMb", "webProbe.browserFreezePolicy.memory"),
processRssBlockerMb: requiredPolicyPositiveNumber(memory, "processRssBlockerMb", "webProbe.browserFreezePolicy.memory"),
growthBlockerMb: requiredPolicyPositiveNumber(memory, "growthBlockerMb", "webProbe.browserFreezePolicy.memory"),
},
responsiveness: {
latencyBlockerMs: requiredPolicyPositiveNumber(responsiveness, "latencyBlockerMs", "webProbe.browserFreezePolicy.responsiveness"),
eventBlockerCount: requiredPolicyPositiveNumber(responsiveness, "eventBlockerCount", "webProbe.browserFreezePolicy.responsiveness"),
},
cdp: {
metricsTimeoutBlockerCount: requiredPolicyPositiveNumber(cdp, "metricsTimeoutBlockerCount", "webProbe.browserFreezePolicy.cdp"),
},
kill: {
enabled: requiredPolicyBoolean(kill, "enabled", "webProbe.browserFreezePolicy.kill"),
gracefulSignal: requiredPolicySignal(kill, "gracefulSignal", "webProbe.browserFreezePolicy.kill", "SIGTERM"),
forceSignal: requiredPolicySignal(kill, "forceSignal", "webProbe.browserFreezePolicy.kill", "SIGKILL"),
graceMs: requiredPolicyIntegerInRange(kill, "graceMs", "webProbe.browserFreezePolicy.kill", 1, 120000),
pollIntervalMs: requiredPolicyIntegerInRange(kill, "pollIntervalMs", "webProbe.browserFreezePolicy.kill", 1, 10000),
exitCode: requiredPolicyIntegerInRange(kill, "exitCode", "webProbe.browserFreezePolicy.kill", 1, 125),
},
source: "yaml-env",
valuesRedacted: true,
};
}
function requiredPolicyRecord(raw, key, pathLabel) {
const value = raw?.[key];
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new Error("UNIDESK_WEB_OBSERVE_BROWSER_FREEZE_POLICY_JSON requires object " + pathLabel + "." + key);
}
return value;
}
function requiredPolicyBoolean(raw, key, pathLabel) {
const value = raw?.[key];
if (typeof value !== "boolean") {
throw new Error("UNIDESK_WEB_OBSERVE_BROWSER_FREEZE_POLICY_JSON requires boolean " + pathLabel + "." + key);
}
return value;
}
function requiredPolicyPositiveNumber(raw, key, pathLabel) {
const value = Number(raw?.[key]);
if (!Number.isFinite(value) || value <= 0) {
throw new Error("UNIDESK_WEB_OBSERVE_BROWSER_FREEZE_POLICY_JSON requires positive number " + pathLabel + "." + key);
}
return value;
}
function requiredPolicyIntegerInRange(raw, key, pathLabel, min, max) {
const value = Number(raw?.[key]);
if (!Number.isInteger(value) || value < min || value > max) {
throw new Error("UNIDESK_WEB_OBSERVE_BROWSER_FREEZE_POLICY_JSON requires integer " + pathLabel + "." + key + " between " + min + " and " + max);
}
return value;
}
function requiredPolicySignal(raw, key, pathLabel, expected) {
const value = String(raw?.[key] || "");
if (value !== expected) {
throw new Error("UNIDESK_WEB_OBSERVE_BROWSER_FREEZE_POLICY_JSON requires " + pathLabel + "." + key + "=" + expected);
}
return value;
}
function parseProjectManagementConfig(value) {
if (!value || value === "null") {
return {
enabled: false,
targetPaths: [],
readinessSelectors: [],
naturalApiPathPrefixes: [],
commandAllowlist: [],
launchRoute: "",
slowApiBudgetMs: 0,
source: "yaml-env",
valuesRedacted: true
};
}
const raw = (() => {
try { return JSON.parse(value); } catch (error) { throw new Error("UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON is invalid JSON: " + (error instanceof Error ? error.message : String(error))); }
})();
if (raw?.enabled !== true && raw?.enabled !== false) throw new Error("UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON requires boolean enabled");
if (raw.enabled !== true) return { enabled: false, targetPaths: [], readinessSelectors: [], naturalApiPathPrefixes: [], commandAllowlist: [], launchRoute: "", slowApiBudgetMs: 0, source: "yaml-env", valuesRedacted: true };
const stringList = (key) => {
const list = raw?.[key];
if (!Array.isArray(list) || list.some((item) => typeof item !== "string" || item.length === 0)) throw new Error("UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON requires string[] " + key + "; configure config/hwlab-node-lanes.yaml webProbe.projectManagement");
return list;
};
const slowApiBudgetMs = Number(raw?.slowApiBudgetMs);
if (!Number.isFinite(slowApiBudgetMs) || slowApiBudgetMs <= 0) throw new Error("UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON requires positive slowApiBudgetMs");
const launchRoute = String(raw.launchRoute || "");
if (!launchRoute.startsWith("/")) throw new Error("UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON launchRoute must be an absolute path");
return {
enabled: true,
targetPaths: stringList("targetPaths"),
readinessSelectors: stringList("readinessSelectors"),
naturalApiPathPrefixes: stringList("naturalApiPathPrefixes"),
commandAllowlist: stringList("commandAllowlist"),
launchRoute,
slowApiBudgetMs,
source: "yaml-env",
valuesRedacted: true
};
}
async function readJsonl(file, options = {}) {
const tailLimit = Number.isFinite(Number(options.tail)) && Number(options.tail) > 0 ? Math.floor(Number(options.tail)) : 0;
if (tailLimit > 0) return readJsonlTail(file, tailLimit, options);
const rows = [];
try {
const input = createReadStream(file, { encoding: "utf8" });
const lines = createInterface({ input, crlfDelay: Infinity });
let lineNo = 0;
for await (const rawLine of lines) {
lineNo += 1;
const line = String(rawLine || "").trim();
if (!line) continue;
try {
const parsed = JSON.parse(line);
rows.push(typeof options.compact === "function" ? options.compact(parsed) : parsed);
} catch (error) {
const item = { parseError: true, lineNo, rawHash: sha256(line), errorMessage: limitText(error && error.message ? error.message : String(error), 240) };
rows.push(item);
if (jsonlReadIssues.length < 50) jsonlReadIssues.push({ file: path.basename(file), kind: "parse-error", lineNo, rawHash: item.rawHash, errorMessage: item.errorMessage });
}
}
return rows;
} catch (error) {
if (error && error.code === "ENOENT") return [];
if (jsonlReadIssues.length < 50) jsonlReadIssues.push({ file: path.basename(file), kind: "read-error", code: error && error.code ? String(error.code) : null, errorMessage: limitText(error && error.message ? error.message : String(error), 240) });
return [];
}
}
async function readJsonlTail(file, limit, options = {}) {
try {
const lines = await readTailLines(file, limit);
const rows = [];
let lineNo = 0;
for (const rawLine of lines) {
lineNo += 1;
const line = String(rawLine || "").trim();
if (!line) continue;
try {
const parsed = JSON.parse(line);
rows.push(typeof options.compact === "function" ? options.compact(parsed) : parsed);
} catch (error) {
const item = { parseError: true, lineNo, tail: true, rawHash: sha256(line), errorMessage: limitText(error && error.message ? error.message : String(error), 240) };
rows.push(item);
if (jsonlReadIssues.length < 50) jsonlReadIssues.push({ file: path.basename(file), kind: "parse-error", lineNo, tail: true, rawHash: item.rawHash, errorMessage: item.errorMessage });
}
}
return rows;
} catch (error) {
if (error && error.code === "ENOENT") return [];
if (jsonlReadIssues.length < 50) jsonlReadIssues.push({ file: path.basename(file), kind: "read-error", tail: true, code: error && error.code ? String(error.code) : null, errorMessage: limitText(error && error.message ? error.message : String(error), 240) });
return [];
}
}
async function readTailLines(file, limit) {
const info = await stat(file);
if (!info.size || limit <= 0) return [];
const chunkSize = 64 * 1024;
const maxBytes = Math.max(32 * 1024 * 1024, Math.min(256 * 1024 * 1024, limit * 512 * 1024));
const chunks = [];
let position = info.size;
let readBytes = 0;
let newlineCount = 0;
while (position > 0 && newlineCount <= limit && readBytes < maxBytes) {
const readSize = Math.min(chunkSize, position, maxBytes - readBytes);
position -= readSize;
const chunk = await readFileSlice(file, position, readSize);
chunks.unshift(chunk);
readBytes += chunk.length;
for (let index = 0; index < chunk.length; index += 1) {
if (chunk[index] === 10) newlineCount += 1;
}
}
if (position > 0 && newlineCount <= limit && jsonlReadIssues.length < 50) {
jsonlReadIssues.push({ file: path.basename(file), kind: "tail-scan-truncated", limit, readBytes, fileBytes: info.size });
}
const text = Buffer.concat(chunks).toString("utf8");
let lines = text.split(/\r?\n/u);
if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
if (position > 0 && lines.length > 0) lines.shift();
return lines.slice(-limit);
}
async function readFileSlice(file, start, length) {
const chunks = [];
await new Promise((resolve, reject) => {
const stream = createReadStream(file, { start, end: start + length - 1 });
stream.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
stream.on("error", reject);
stream.on("end", resolve);
});
return Buffer.concat(chunks);
}
function sampleTimeWindow(samples, paddingMs) {
const times = samples
.map((item) => Date.parse(String(item && item.ts || "")))
.filter((value) => Number.isFinite(value));
if (times.length === 0) return { startMs: null, endMs: null, startAt: null, endAt: null, paddingMs };
const startMs = Math.max(0, Math.min(...times) - Math.max(0, paddingMs || 0));
const endMs = Math.max(...times) + Math.max(0, paddingMs || 0);
return { startMs, endMs, startAt: new Date(startMs).toISOString(), endAt: new Date(endMs).toISOString(), paddingMs };
}
function filterRowsByTimeWindow(rows, window) {
if (!window || !Number.isFinite(window.startMs) || !Number.isFinite(window.endMs)) return rows;
return rows.filter((item) => {
const value = item && (item.ts || item.observedAt || item.startedAt || item.finishedAt || item.createdAt || item.updatedAt);
const ms = Date.parse(String(value || ""));
if (!Number.isFinite(ms)) return true;
return ms >= window.startMs && ms <= window.endMs;
});
}
function analysisFocusFromControl(control) {
const completedNewSessions = (Array.isArray(control) ? control : [])
.filter((item) => item?.type === "newSession" && item?.phase === "completed" && Number.isFinite(Date.parse(String(item.ts || ""))))
.sort((a, b) => Date.parse(String(a.ts || "")) - Date.parse(String(b.ts || "")));
const latest = completedNewSessions.at(-1) ?? null;
if (!latest) return { mode: "all", reason: "no-new-session-command", startAt: null, startMs: null, commandId: null, valuesRedacted: true };
const startMs = Date.parse(String(latest.ts));
return {
mode: "after-new-session",
reason: "latest-completed-new-session",
startAt: new Date(startMs).toISOString(),
startMs,
commandId: latest.commandId ?? null,
valuesRedacted: true
};
}
function applyAnalysisFocus(rows, focus, graceMs = 0) {
if (!focus || !Number.isFinite(focus.startMs)) return rows;
const minMs = focus.startMs - Math.max(0, Number(graceMs) || 0);
return (Array.isArray(rows) ? rows : []).filter((item) => {
const value = item && (item.ts || item.observedAt || item.startedAt || item.finishedAt || item.createdAt || item.updatedAt);
const ms = Date.parse(String(value || ""));
return Number.isFinite(ms) ? ms >= minMs : true;
});
}
function analysisControlWindow(sampleWindow, focus, graceMs = 0) {
if (!sampleWindow || !Number.isFinite(sampleWindow.endMs)) return sampleWindow;
if (!focus || !Number.isFinite(focus.startMs)) return sampleWindow;
const startMs = Math.max(0, Math.min(Number(sampleWindow.startMs ?? focus.startMs), focus.startMs - Math.max(0, Number(graceMs) || 0)));
return {
...sampleWindow,
startMs,
startAt: new Date(startMs).toISOString()
};
}
function compactSampleForAnalysis(sample) {
if (!sample || typeof sample !== "object") return sample;
return {
seq: sample.seq ?? null,
ts: sample.ts ?? null,
reason: sample.reason ?? null,
sampleGroupSeq: sample.sampleGroupSeq ?? null,
pageId: sample.pageId ?? null,
pageRole: sample.pageRole ?? null,
commandId: sample.commandId ?? null,
observerInitiated: sample.observerInitiated ?? null,
url: sample.url ?? null,
path: sample.path ?? null,
title: sample.title ?? null,
routeSessionId: sample.routeSessionId ?? null,
activeSessionId: sample.activeSessionId ?? null,
messages: compactDomItems(sample.messages),
traceRows: compactDomItems(sample.traceRows),
loadings: compactLoadingItems(sample.loadings),
sessionRail: compactSessionRail(sample.sessionRail),
turns: compactDomItems(sample.turns),
diagnostics: compactDomItems(sample.diagnostics),
composer: compactComposer(sample.composer),
projectManagement: compactProjectManagementSample(sample.projectManagement),
pageProvenance: compactSamplePageProvenance(sample.pageProvenance),
performance: compactPerformanceItems(sample.performance)
};
}
function compactProjectManagementSample(value) {
if (!value || typeof value !== "object") return null;
return {
pageKind: value.pageKind ?? null,
configuredPath: value.configuredPath === true,
rootVisible: value.rootVisible === true,
mdtodoVisible: value.mdtodoVisible === true,
sourceCount: value.sourceCount ?? null,
fileCount: value.fileCount ?? null,
taskCount: value.taskCount ?? null,
taskRefMissingCount: value.taskRefMissingCount ?? null,
selectedSourceId: value.selectedSourceId ?? null,
selectedFileRef: value.selectedFileRef ?? null,
selectedTaskRef: value.selectedTaskRef ?? null,
selectedTaskStatus: value.selectedTaskStatus ?? null,
sourceSelectVisible: value.sourceSelectVisible === true,
fileSelectVisible: value.fileSelectVisible === true,
sourceConfigVisible: value.sourceConfigVisible === true,
taskEditorVisible: value.taskEditorVisible === true,
taskBodyVisible: value.taskBodyVisible === true,
taskBody: value.taskBody ?? null,
reportLinkCount: value.reportLinkCount ?? 0,
reportPreviewVisible: value.reportPreviewVisible === true,
reportPreview: value.reportPreview ?? null,
reportFullscreenVisible: value.reportFullscreenVisible === true,
newTaskDraftVisible: value.newTaskDraftVisible === true,
taskStatusCounts: value.taskStatusCounts && typeof value.taskStatusCounts === "object" ? value.taskStatusCounts : {},
launchButtonVisible: value.launchButtonVisible === true,
launchButtonEnabled: value.launchButtonEnabled === true,
launchButtonText: value.launchButtonText ?? null,
blockerCount: value.blockerCount ?? 0,
blockers: Array.isArray(value.blockers) ? value.blockers.slice(0, 6) : [],
paneGaps: Array.isArray(value.paneGaps) ? value.paneGaps.slice(0, 8).map((item) => ({
name: item?.name ?? null,
visible: item?.visible === true,
widthPx: item?.widthPx ?? null,
heightPx: item?.heightPx ?? null,
bottomGapPx: item?.bottomGapPx ?? null,
bottomGapRatio: item?.bottomGapRatio ?? null,
contentNodeCount: item?.contentNodeCount ?? null,
valuesRedacted: true
})) : [],
workbenchLinkCount: value.workbenchLinkCount ?? 0,
valuesRedacted: true
};
}
function compactSessionRail(value) {
if (!value || typeof value !== "object") return null;
const items = Array.isArray(value.items) ? value.items.slice(0, 80).map((item) => ({
index: item?.index ?? null,
tag: item?.tag ?? null,
testId: item?.testId ?? null,
role: item?.role ?? null,
active: item?.active === true,
status: item?.status ?? null,
dataStatus: item?.dataStatus ?? null,
running: item?.running === true,
dataRunning: item?.dataRunning ?? null,
ariaBusy: item?.ariaBusy ?? null,
sessionIdPrefix: item?.sessionIdPrefix ?? (item?.sessionId ? String(item.sessionId).slice(0, 12) : null),
fallbackTitle: item?.fallbackTitle === true,
titlePreview: limitText(String(item?.titlePreview || item?.titleText || ""), 180),
titleHash: item?.titleHash ?? sha256(String(item?.titlePreview || item?.titleText || "")),
titleBytes: item?.titleBytes ?? null,
})) : [];
const fallbackItems = Array.isArray(value.fallbackItems)
? value.fallbackItems.slice(0, 20).map((item) => ({
index: item?.index ?? null,
active: item?.active === true,
sessionIdPrefix: item?.sessionIdPrefix ?? (item?.sessionId ? String(item.sessionId).slice(0, 12) : null),
titlePreview: limitText(String(item?.titlePreview || item?.titleText || ""), 180),
titleHash: item?.titleHash ?? sha256(String(item?.titlePreview || item?.titleText || "")),
}))
: items.filter((item) => item.fallbackTitle).slice(0, 20);
const visibleCount = Number(value.visibleCount ?? items.length);
const fallbackTitleCount = Number(value.fallbackTitleCount ?? fallbackItems.length);
return {
visibleCount: Number.isFinite(visibleCount) ? visibleCount : items.length,
fallbackTitleCount: Number.isFinite(fallbackTitleCount) ? fallbackTitleCount : fallbackItems.length,
fallbackTitleRatio: Number.isFinite(Number(value.fallbackTitleRatio)) ? Number(value.fallbackTitleRatio) : (items.length > 0 ? Number((fallbackItems.length / items.length).toFixed(4)) : 0),
activeItem: items.find((item) => item.active) || null,
items,
fallbackItems,
valuesRedacted: true
};
}
function compactComposer(value) {
if (!value || typeof value !== "object") return null;
return {
inputPresent: value.inputPresent === true,
inputDisabled: value.inputDisabled === true,
warningPresent: value.warningPresent === true,
submitPresent: value.submitPresent === true,
submitDisabled: value.submitDisabled === true,
submitAction: value.submitAction ?? null,
activeStatus: value.activeStatus ?? null,
valuesRedacted: true
};
}
function compactLoadingItems(items) {
if (!Array.isArray(items)) return [];
return items.map((item) => {
if (!item || typeof item !== "object") return item;
const rawText = String(item.text ?? item.textPreview ?? "");
return {
index: item.index ?? null,
tag: item.tag ?? null,
testId: item.testId ?? null,
role: item.role ?? null,
ownerKind: item.ownerKind ?? null,
ownerKey: item.ownerKey ?? null,
ownerLabel: item.ownerLabel ?? null,
owner: item.owner && typeof item.owner === "object" ? {
tag: item.owner.tag ?? null,
testId: item.owner.testId ?? null,
role: item.owner.role ?? null,
id: item.owner.id ?? null,
className: item.owner.className ?? null,
status: item.owner.status ?? null,
sessionId: item.owner.sessionId ?? null,
messageId: item.owner.messageId ?? null,
traceId: item.owner.traceId ?? null,
ariaLabel: item.owner.ariaLabel ?? null,
} : null,
text: limitText(rawText, 400),
textPreview: limitText(String(item.textPreview ?? rawText), 240),
textHash: item.textHash ?? sha256(rawText),
textBytes: item.textBytes ?? Buffer.byteLength(rawText),
ownerTextHash: item.ownerTextHash ?? null,
ownerTextPreview: item.ownerTextPreview ? limitText(item.ownerTextPreview, 240) : null,
};
});
}
function compactDomItems(items) {
if (!Array.isArray(items)) return [];
return items.map(compactDomItem);
}
function compactDomItem(item) {
if (!item || typeof item !== "object") return item;
const rawText = String(item.text ?? item.textPreview ?? "");
const preview = String(item.textPreview ?? limitText(rawText, 240));
const hasBodyTextPresent = Object.prototype.hasOwnProperty.call(item, "bodyTextPresent");
const hasFinalResponsePresent = Object.prototype.hasOwnProperty.call(item, "finalResponsePresent");
return {
index: item.index ?? null,
tag: item.tag ?? null,
testId: item.testId ?? null,
role: item.role ?? null,
dataRole: item.dataRole ?? null,
status: item.status ?? null,
sessionId: item.sessionId ?? null,
messageId: item.messageId ?? null,
traceId: item.traceId ?? null,
turnId: item.turnId ?? null,
projectedSeq: Number.isFinite(Number(item.projectedSeq)) ? Number(item.projectedSeq) : null,
sourceSeq: Number.isFinite(Number(item.sourceSeq)) ? Number(item.sourceSeq) : null,
eventSeq: Number.isFinite(Number(item.eventSeq)) ? Number(item.eventSeq) : null,
eventTimestamp: item.eventTimestamp ?? null,
eventTimeText: item.eventTimeText ?? null,
eventKind: item.eventKind ?? null,
durationText: item.durationText ?? null,
activityText: item.activityText ?? null,
bodyTextSource: item.bodyTextSource ?? null,
bodyTextCandidateCount: Number.isFinite(Number(item.bodyTextCandidateCount)) ? Number(item.bodyTextCandidateCount) : null,
bodyTextPresent: hasBodyTextPresent ? item.bodyTextPresent === true : null,
bodyTextPreview: item.bodyTextPreview ? limitText(item.bodyTextPreview, 600) : undefined,
bodyTextHash: item.bodyTextHash ?? null,
bodyTextBytes: Number.isFinite(Number(item.bodyTextBytes)) ? Number(item.bodyTextBytes) : null,
finalResponsePresent: hasFinalResponsePresent ? item.finalResponsePresent === true : null,
finalResponseTextSource: item.finalResponseTextSource ?? null,
finalResponseCandidateCount: Number.isFinite(Number(item.finalResponseCandidateCount)) ? Number(item.finalResponseCandidateCount) : null,
finalResponseTextPreview: item.finalResponseTextPreview ? limitText(item.finalResponseTextPreview, 600) : undefined,
finalResponseTextHash: item.finalResponseTextHash ?? null,
finalResponseTextBytes: Number.isFinite(Number(item.finalResponseTextBytes)) ? Number(item.finalResponseTextBytes) : null,
className: item.className ?? null,
diagnosticCode: item.diagnosticCode ?? null,
source: item.source ?? null,
sources: Array.isArray(item.sources) ? item.sources.slice(0, 8) : undefined,
text: limitText(rawText, 4000),
textPreview: limitText(preview, 600),
textHash: item.textHash ?? sha256(rawText),
textBytes: item.textBytes ?? Buffer.byteLength(rawText)
};
}
function compactPerformanceItems(items) {
if (!Array.isArray(items)) return [];
return items.map((item) => ({
name: item?.name ?? null,
initiatorType: item?.initiatorType ?? null,
startTime: item?.startTime ?? null,
duration: item?.duration ?? null,
workerStart: item?.workerStart ?? null,
redirectStart: item?.redirectStart ?? null,
redirectEnd: item?.redirectEnd ?? null,
fetchStart: item?.fetchStart ?? null,
domainLookupStart: item?.domainLookupStart ?? null,
domainLookupEnd: item?.domainLookupEnd ?? null,
connectStart: item?.connectStart ?? null,
connectEnd: item?.connectEnd ?? null,
secureConnectionStart: item?.secureConnectionStart ?? null,
requestStart: item?.requestStart ?? null,
responseStart: item?.responseStart ?? null,
responseEnd: item?.responseEnd ?? null,
transferSize: item?.transferSize ?? null,
encodedBodySize: item?.encodedBodySize ?? null,
decodedBodySize: item?.decodedBodySize ?? null,
nextHopProtocol: item?.nextHopProtocol ?? null,
responseStatus: Number.isFinite(Number(item?.responseStatus)) ? Number(item.responseStatus) : null
}));
}
function compactLoadingMetricsForOutput(value) {
if (!value || typeof value !== "object") return null;
return {
summary: value.summary ?? null,
longestSegments: Array.isArray(value.segments) ? value.segments.slice(0, 8) : [],
owners: Array.isArray(value.owners) ? value.owners.slice(0, 8) : [],
timeline: Array.isArray(value.timeline) ? value.timeline.slice(-12) : [],
valuesRedacted: true
};
}
function compactSessionRailTitleMetricsForOutput(value) {
if (!value || typeof value !== "object") return null;
return {
summary: value.summary ?? null,
samples: Array.isArray(value.samples) ? value.samples.slice(0, 12) : [],
examples: Array.isArray(value.examples) ? value.examples.slice(0, 12) : [],
timeline: Array.isArray(value.timeline) ? value.timeline.slice(-12) : [],
valuesRedacted: true
};
}
function compactWorkbenchTurnStateTriadForOutput(value) {
if (!value || typeof value !== "object") return null;
return {
summary: value.summary ?? null,
drilldown: value.drilldown ?? null,
invalidFullTriads: Array.isArray(value.invalidFullTriads) ? value.invalidFullTriads.slice(0, 8) : [],
cardFinalResponseMismatches: Array.isArray(value.cardFinalResponseMismatches) ? value.cardFinalResponseMismatches.slice(0, 8) : [],
collectorMissingRows: Array.isArray(value.collectorMissingRows) ? value.collectorMissingRows.slice(0, 8) : [],
valuesRedacted: true
};
}
function compactSamplePageProvenance(value) {
if (!value || typeof value !== "object") return null;
return {
pageLoadSeq: value.pageLoadSeq ?? null,
reason: value.reason ?? null,
observedAt: value.observedAt ?? null,
urlPath: value.urlPath ?? null,
documentReadyState: value.documentReadyState ?? null,
timeOrigin: value.timeOrigin ?? null,
httpStatus: value.httpStatus ?? null,
assetFingerprint: value.assetFingerprint ?? null,
scriptCount: value.scriptCount ?? null,
stylesheetCount: value.stylesheetCount ?? null,
metaCount: value.metaCount ?? null,
scripts: Array.isArray(value.scripts) ? value.scripts.slice(0, 20) : [],
stylesheets: Array.isArray(value.stylesheets) ? value.stylesheets.slice(0, 20) : [],
error: value.error ?? null,
valuesRedacted: true
};
}
function summarizeCommandFailures(control) {
return control.filter((item) => item?.phase === "failed").map((item) => {
const detail = item?.detail && typeof item.detail === "object" ? item.detail : {};
const error = detail?.error && typeof detail.error === "object" ? detail.error : detail;
return {
ts: item.ts ?? null,
commandId: item.commandId ?? null,
type: item.type ?? item.input?.type ?? null,
source: item.source ?? null,
durationMs: detail.durationMs ?? null,
beforePath: urlPath(detail.beforeUrl || item.beforeUrl),
afterPath: urlPath(detail.afterUrl || item.afterUrl),
name: error?.name ?? null,
message: limitText(error?.message ?? detail?.message ?? "", 240),
failureKind: error?.failureKind ?? detail?.failureKind ?? null,
failureSampleOk: detail?.failureSample?.ok === true,
sampleSeq: detail?.failureSample?.sampleSeq ?? null,
valuesRedacted: true
};
});
}
`;
}