// SPEC: PJ2026-010401080313 Workbench 实时权威。 // 职责:在持久 Web observer 中验证产品 Workbench Trace 的可读性。 export function nodeWebObserveRunnerTraceReadabilitySource(): string { return String.raw` function parseWorkbenchTraceReadabilityConfig(raw) { if (!raw) return null; let parsed; try { parsed = JSON.parse(raw); } catch (error) { throw new Error("UNIDESK_WEB_OBSERVE_WORKBENCH_TRACE_READABILITY_JSON 不是合法 JSON:" + (error instanceof Error ? error.message : String(error))); } if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { throw new Error("UNIDESK_WEB_OBSERVE_WORKBENCH_TRACE_READABILITY_JSON 必须是对象"); } if (typeof parsed.enabled !== "boolean") throw new Error("Workbench Trace 可读性 enabled 必须由 owning YAML 显式声明为布尔值"); const positiveInteger = (key) => { const value = Number(parsed[key]); if (!Number.isInteger(value) || value <= 0) throw new Error("Workbench Trace 可读性 " + key + " 必须是 owning YAML 中的正整数"); return value; }; return { enabled: parsed.enabled, runningCardTimeoutMs: positiveInteger("runningCardTimeoutMs"), disclosureActionTimeoutMs: positiveInteger("disclosureActionTimeoutMs"), disclosureOpenTimeoutMs: positiveInteger("disclosureOpenTimeoutMs"), runningReadableTimeoutMs: positiveInteger("runningReadableTimeoutMs"), terminalTimeoutMs: positiveInteger("terminalTimeoutMs"), domStableQuietMs: positiveInteger("domStableQuietMs"), domStableTimeoutMs: positiveInteger("domStableTimeoutMs"), pollIntervalMs: positiveInteger("pollIntervalMs"), valuesRedacted: true }; } function workbenchTraceReadabilityTerminalStatus(value) { return ["completed", "failed", "blocked", "timeout", "canceled", "cancelled", "stale", "thread-resume-failed"] .includes(String(value || "").trim().toLowerCase().replace(/_/gu, "-")); } async function validateWorkbenchTraceReadability(command) { const config = workbenchTraceReadability; if (!config || config.enabled !== true) throw new Error("owning YAML 未启用 validateWorkbenchTraceReadability"); const reportDir = path.join(stateDir, "artifacts", "workbench-trace-readability", safeId(command.id)); const report = { ok: false, contract: "web-probe-workbench-trace-readability-v1", commandId: command.id, origin, expected: { runningCardTimeoutMs: config.runningCardTimeoutMs, disclosureActionTimeoutMs: config.disclosureActionTimeoutMs, disclosureOpenTimeoutMs: config.disclosureOpenTimeoutMs, runningReadableTimeoutMs: config.runningReadableTimeoutMs, terminalTimeoutMs: config.terminalTimeoutMs, domStableQuietMs: config.domStableQuietMs, domStableTimeoutMs: config.domStableTimeoutMs, pollIntervalMs: config.pollIntervalMs, scope: "#conversation-list product WorkbenchMessageCard", sequenceAuthority: "source", valuesRedacted: true }, startedAt: new Date().toISOString(), valuesRedacted: true }; try { const controlRecovery = await ensureControlPageResponsiveForCommand("validateWorkbenchTraceReadability"); Object.assign(report, { controlRecovery }); const currentPath = safeUrlPath(currentPageUrl()) || ""; if (!isWorkbenchPathname(currentPath)) throw new Error("validateWorkbenchTraceReadability 必须在原 Workbench 页面执行"); const session = await workbenchSessionSnapshot(); const sessionId = session?.activeSessionId || session?.routeSessionId || routeSessionIdFromUrl(currentPageUrl()); if (!sessionId) throw new Error("validateWorkbenchTraceReadability 需要当前已有 Workbench session"); Object.assign(report, { sessionId }); const target = await workbenchTraceReadabilityWaitForCurrentProductCard( config.runningCardTimeoutMs, config.pollIntervalMs ); Object.assign(report, { traceId: target.traceId, scope: target.scope }); const before = await workbenchTraceReadabilitySnapshot(target.card, target.traceId); const startedDuringRunning = !workbenchTraceReadabilityTerminalStatus(before.status); const openBefore = before.disclosure.open === true; let forcedExpansion = false; Object.assign(report, { before, startedDuringRunning, forcedExpansion, disclosure: { openBefore, openAfterExpansion: null, openAfterTerminal: null, autoReadable: false, valuesRedacted: true } }); if (!openBefore) { const summary = target.card.locator("details.trace-disclosure > summary.trace-disclosure-summary").first(); await summary.waitFor({ state: "visible", timeout: config.disclosureActionTimeoutMs }); await summary.click({ timeout: config.disclosureActionTimeoutMs }); forcedExpansion = true; report.forcedExpansion = true; } const afterExpansion = await workbenchTraceReadabilityWaitForDisclosureOpen( target.card, target.traceId, config.disclosureOpenTimeoutMs, config.pollIntervalMs ); report.disclosure.openAfterExpansion = afterExpansion.disclosure?.open ?? null; const running = startedDuringRunning ? await workbenchTraceReadabilityWaitForReadableRunning( target.card, target.traceId, config.runningReadableTimeoutMs, config.pollIntervalMs ) : { observed: false, reason: "command-started-after-terminal", snapshot: before, valuesRedacted: true }; Object.assign(report, { running }); report.disclosure.autoReadable = openBefore && (startedDuringRunning ? running.observed === true : before.rowCount > 0 && before.sourceAuthorityRowCount > 0 && before.readableSourceRowCount > 0); const terminalArrival = await workbenchTraceReadabilityWaitForTerminal( target.card, target.traceId, config.terminalTimeoutMs, config.pollIntervalMs ); Object.assign(report, { terminalArrival }); const terminal = await workbenchTraceReadabilityWaitForStableTerminal( target.card, target.traceId, config.domStableQuietMs, config.domStableTimeoutMs, config.pollIntervalMs ); Object.assign(report, { terminal }); report.disclosure.openAfterTerminal = terminal.snapshot?.disclosure?.open ?? null; const runningRetention = workbenchTraceReadabilityRetention(running.snapshot, terminal.snapshot); const retainedRunningRowCount = runningRetention.retainedRowCount; Object.assign(report, { runningRetention, retainedRunningRowCount }); const screenshot = await captureScreenshot("workbench-trace-readability-" + safeId(command.id)); const evidence = { sessionId, traceId: target.traceId, scope: target.scope, startedDuringRunning, forcedExpansion, disclosure: { openBefore, openAfterExpansion: afterExpansion.disclosure?.open ?? null, openAfterTerminal: terminal.snapshot?.disclosure?.open ?? null, autoReadable: openBefore && (startedDuringRunning ? running.observed === true : before.rowCount > 0 && before.sourceAuthorityRowCount > 0 && before.readableSourceRowCount > 0), valuesRedacted: true }, running, terminalArrival, terminal, runningRetention, retainedRunningRowCount, screenshot: { path: screenshot.path, sha256: screenshot.sha256, valuesRedacted: true }, controlRecovery, valuesRedacted: true }; Object.assign(report, evidence); const failures = workbenchTraceReadabilityFailures(evidence); report.failures = failures; report.ok = failures.length === 0; report.status = failures.length === 0 ? "passed" : "failed"; report.completedAt = new Date().toISOString(); const reportArtifact = await workbenchTraceReadabilityWriteReport(reportDir, report); report.reportPath = reportArtifact.path; report.reportSha256 = reportArtifact.sha256; if (failures.length > 0) { throw workbenchTraceReadabilityEvidenceError( "Workbench 产品 Trace 可读性验证失败:" + failures.map((failure) => failure.code).join(","), report ); } await appendJsonl(files.control, eventRecord("workbench-trace-readability-validated", { sessionId, traceId: report.traceId, disclosure: report.disclosure, running: report.running, terminal: report.terminal, runningRetention: report.runningRetention, retainedRunningRowCount, reportPath: report.reportPath, reportSha256: report.reportSha256, screenshot: report.screenshot, valuesRedacted: true })); return { ok: true, status: "passed", sessionId, traceId: report.traceId, scope: report.scope, disclosure: report.disclosure, running: report.running, terminalArrival: report.terminalArrival, terminal: report.terminal, runningRetention: report.runningRetention, retainedRunningRowCount, reportPath: report.reportPath, reportSha256: report.reportSha256, screenshot: report.screenshot, valuesRedacted: true }; } catch (error) { const wrapped = error instanceof Error ? error : new Error(String(error)); const partial = wrapped.details && typeof wrapped.details === "object" && !Array.isArray(wrapped.details) ? wrapped.details : {}; for (const key of ["sessionId", "traceId", "scope", "before", "disclosure", "running", "terminalArrival", "terminal", "runningRetention", "retainedRunningRowCount", "failures"]) { if ((report[key] === undefined || report[key] === null) && partial[key] !== undefined && partial[key] !== null) report[key] = partial[key]; } if (!report.completedAt) { Object.assign(report, { ok: false, status: "failed", completedAt: new Date().toISOString(), failure: errorSummary(error), valuesRedacted: true }); } if (!Array.isArray(report.failures)) { report.failures = [{ code: "probe_exception", message: wrapped.message, valuesRedacted: true }]; } if (!report.screenshot) { report.screenshot = await captureScreenshot("workbench-trace-readability-failed-" + safeId(command.id)) .then((screenshot) => ({ path: screenshot.path, sha256: screenshot.sha256, valuesRedacted: true })) .catch((screenshotError) => ({ available: false, error: errorSummary(screenshotError), valuesRedacted: true })); } const existingReportPath = wrapped.details?.reportPath || report.reportPath || null; const existingReportSha256 = wrapped.details?.reportSha256 || report.reportSha256 || null; const reportArtifact = existingReportPath && existingReportSha256 ? null : await workbenchTraceReadabilityWriteReport(reportDir, report).catch(() => null); wrapped.details = { ...(wrapped.details || {}), sessionId: report.sessionId || wrapped.details?.sessionId || null, traceId: report.traceId || wrapped.details?.traceId || null, scope: report.scope || wrapped.details?.scope || null, before: report.before || wrapped.details?.before || null, disclosure: report.disclosure || wrapped.details?.disclosure || null, running: report.running || wrapped.details?.running || null, terminalArrival: report.terminalArrival || wrapped.details?.terminalArrival || null, terminal: report.terminal || wrapped.details?.terminal || null, runningRetention: report.runningRetention || wrapped.details?.runningRetention || null, retainedRunningRowCount: report.retainedRunningRowCount ?? wrapped.details?.retainedRunningRowCount ?? null, failures: report.failures || wrapped.details?.failures || null, screenshot: report.screenshot || wrapped.details?.screenshot || null, reportPath: existingReportPath || reportArtifact?.path || null, reportSha256: existingReportSha256 || reportArtifact?.sha256 || null, valuesRedacted: true }; throw wrapped; } } async function workbenchTraceReadabilityWaitForCurrentProductCard(timeoutMs, pollIntervalMs) { const deadline = Date.now() + timeoutMs; let lastScope = null; while (Date.now() <= deadline) { const conversation = page.locator("#conversation-list").first(); const conversationCount = await page.locator("#conversation-list").count(); const cards = conversation.locator(':scope > .message-card[data-role="agent"][data-trace-id]'); const productCardCount = conversationCount === 1 ? await cards.count() : 0; const debugPanelCountInsideConversation = conversationCount === 1 ? await conversation.locator('[data-testid="workbench-kafka-debug-panel"]').count() : 0; lastScope = { selector: '#conversation-list > .message-card[data-role="agent"][data-trace-id]', conversationCount, productCardCount, debugPanelCountInsideConversation, isolatedDebugExcluded: debugPanelCountInsideConversation === 0, valuesRedacted: true }; if (conversationCount === 1 && productCardCount > 0) { const runningTraceIds = await cards.evaluateAll((elements) => elements .filter((element) => { const timeline = element.querySelector(":scope > .trace-timeline"); const status = String(element.getAttribute("data-status") || timeline?.getAttribute("data-status") || "").trim().toLowerCase(); return status === "running" || status === "pending"; }) .map((element) => String(element.getAttribute("data-trace-id") || "").trim()) .filter((traceId) => /^trc_[A-Za-z0-9_-]+$/u.test(traceId))); const traceId = runningTraceIds.at(-1) || null; if (traceId) { const card = conversation.locator(':scope > .message-card[data-role="agent"][data-trace-id="' + traceId + '"]').first(); const stableCardCount = await card.count(); const timelineCount = stableCardCount === 1 ? await card.locator(":scope > .trace-timeline").count() : 0; if (stableCardCount === 1 && timelineCount === 1) return { card, traceId, scope: lastScope }; } } await interruptibleRunnerSleep(pollIntervalMs); } const error = new Error("在 owning YAML 指定的 " + timeoutMs + "ms 内未找到当前新运行中的产品 Trace 卡片"); error.details = { scope: lastScope, valuesRedacted: true }; throw error; } async function workbenchTraceReadabilityWaitForDisclosureOpen(card, traceId, timeoutMs, pollIntervalMs) { const deadline = Date.now() + timeoutMs; let latest = await workbenchTraceReadabilitySnapshot(card, traceId); while (Date.now() <= deadline) { latest = await workbenchTraceReadabilitySnapshot(card, traceId); if (latest.disclosure.open === true) return latest; await interruptibleRunnerSleep(pollIntervalMs); } return latest; } async function workbenchTraceReadabilitySnapshot(card, expectedTraceId) { const raw = await card.evaluate((root) => { const timeline = root.querySelector(":scope > .trace-timeline"); const disclosure = timeline?.querySelector(":scope > details.trace-disclosure"); const rows = disclosure ? Array.from(disclosure.querySelectorAll('[data-testid="trace-render-row"]')) : []; const normalized = (value) => String(value || "").replace(/\s+/gu, " ").trim(); return { connected: root.isConnected, status: root.getAttribute("data-status") || timeline?.getAttribute("data-status") || null, traceId: root.getAttribute("data-trace-id") || timeline?.getAttribute("data-trace-id") || null, disclosure: { available: Boolean(disclosure), open: disclosure instanceof HTMLDetailsElement ? disclosure.open : null, eventCount: disclosure?.getAttribute("data-event-count") || null, readableRowCount: disclosure?.getAttribute("data-readable-row-count") || null, renderedRowCount: disclosure?.getAttribute("data-rendered-row-count") || null, rowWindowed: disclosure?.getAttribute("data-row-windowed") || null }, rows: rows.map((row) => ({ seq: row.getAttribute("data-event-seq"), authority: row.getAttribute("data-event-seq-authority"), kind: row.getAttribute("data-event-kind"), status: row.getAttribute("data-event-status"), terminal: row.getAttribute("data-terminal") === "true", text: normalized(row.textContent) })) }; }); if (raw.connected !== true || raw.traceId !== expectedTraceId) { const error = new Error("产品 Trace 卡片身份在验证期间发生变化"); error.details = { traceId: expectedTraceId, observedTraceId: raw.traceId || null, connected: raw.connected === true, valuesRedacted: true }; throw error; } const rows = raw.rows.map((row) => ({ seq: row.seq, authority: row.authority, kind: row.kind, status: row.status, terminal: row.terminal, identity: [row.authority || "-", row.seq || "-", row.kind || "-"].join(":"), textPresent: row.text.length > 0, textBytes: Buffer.byteLength(row.text), textHash: sha256Text(row.text), valuesRedacted: true })); const sourceRows = rows.filter((row) => row.authority === "source"); const projectedRows = rows.filter((row) => row.authority === "projected"); const sourceSeqs = sourceRows .map((row) => Number(row.seq)) .filter((value) => Number.isInteger(value)); const integerAttribute = (value) => { const parsed = Number(value); return Number.isInteger(parsed) && parsed >= 0 ? parsed : null; }; return { connected: raw.connected, status: raw.status, traceId: raw.traceId, disclosure: { ...raw.disclosure, eventCount: integerAttribute(raw.disclosure.eventCount), readableRowCount: integerAttribute(raw.disclosure.readableRowCount), renderedRowCount: integerAttribute(raw.disclosure.renderedRowCount), rowWindowed: raw.disclosure.rowWindowed === "true" ? true : raw.disclosure.rowWindowed === "false" ? false : null }, rowCount: rows.length, readableRowCount: rows.filter((row) => row.textPresent).length, sourceAuthorityRowCount: sourceRows.length, readableSourceRowCount: sourceRows.filter((row) => row.textPresent).length, projectedAuthorityRowCount: projectedRows.length, sourceSeqMin: sourceSeqs.length > 0 ? Math.min(...sourceSeqs) : null, sourceSeqMax: sourceSeqs.length > 0 ? Math.max(...sourceSeqs) : null, rowIdentities: rows.map((row) => row.identity), rows: rows.slice(-12), rowSetHash: sha256Text(rows.map((row) => row.identity + ":" + row.textHash).join("|")), valuesRedacted: true }; } async function workbenchTraceReadabilityWaitForReadableRunning(card, traceId, timeoutMs, pollIntervalMs) { const startedAtMs = Date.now(); const deadline = startedAtMs + timeoutMs; let latest = await workbenchTraceReadabilitySnapshot(card, traceId); while (Date.now() <= deadline) { latest = await workbenchTraceReadabilitySnapshot(card, traceId); if (!workbenchTraceReadabilityTerminalStatus(latest.status) && latest.disclosure.open === true && latest.rowCount > 0 && latest.sourceAuthorityRowCount > 0 && latest.readableSourceRowCount > 0) { return { observed: true, elapsedMs: Date.now() - startedAtMs, snapshot: latest, valuesRedacted: true }; } if (workbenchTraceReadabilityTerminalStatus(latest.status)) { return { observed: false, reason: "terminal-before-readable-running-sample", elapsedMs: Date.now() - startedAtMs, snapshot: latest, valuesRedacted: true }; } await interruptibleRunnerSleep(pollIntervalMs); } return { observed: false, reason: "readable-running-timeout", elapsedMs: Date.now() - startedAtMs, snapshot: latest, valuesRedacted: true }; } async function workbenchTraceReadabilityWaitForTerminal(card, traceId, timeoutMs, pollIntervalMs) { const startedAtMs = Date.now(); const deadline = startedAtMs + timeoutMs; let latest = await workbenchTraceReadabilitySnapshot(card, traceId); while (Date.now() <= deadline) { latest = await workbenchTraceReadabilitySnapshot(card, traceId); if (workbenchTraceReadabilityTerminalStatus(latest.status)) { return { observed: true, status: latest.status, elapsedMs: Date.now() - startedAtMs, snapshot: latest, valuesRedacted: true }; } await interruptibleRunnerSleep(pollIntervalMs); } return { observed: false, status: latest.status, elapsedMs: Date.now() - startedAtMs, snapshot: latest, valuesRedacted: true }; } async function workbenchTraceReadabilityWaitForStableTerminal(card, traceId, quietMs, timeoutMs, pollIntervalMs) { const startedAtMs = Date.now(); const deadline = startedAtMs + timeoutMs; let stableSinceMs = null; let previousSignature = null; let latest = await workbenchTraceReadabilitySnapshot(card, traceId); let sampleCount = 0; while (Date.now() <= deadline) { latest = await workbenchTraceReadabilitySnapshot(card, traceId); sampleCount += 1; const signature = [latest.status, latest.disclosure.open, latest.rowCount, latest.sourceAuthorityRowCount, latest.rowSetHash].join("|"); const ready = workbenchTraceReadabilityTerminalStatus(latest.status) && latest.disclosure.open === true && latest.rowCount > 0 && latest.sourceAuthorityRowCount > 0 && latest.readableSourceRowCount > 0; if (ready && signature === previousSignature) { stableSinceMs ??= Date.now(); if (Date.now() - stableSinceMs >= quietMs) { return { observed: true, stable: true, sampleCount, elapsedMs: Date.now() - startedAtMs, snapshot: latest, valuesRedacted: true }; } } else { stableSinceMs = null; } previousSignature = signature; await interruptibleRunnerSleep(pollIntervalMs); } return { observed: workbenchTraceReadabilityTerminalStatus(latest.status), stable: false, sampleCount, elapsedMs: Date.now() - startedAtMs, snapshot: latest, valuesRedacted: true }; } function workbenchTraceReadabilityRetention(running, terminal) { const runningRowIds = new Set(running?.rowIdentities || []); const terminalRowIds = new Set(terminal?.rowIdentities || []); const retainedRowCount = [...runningRowIds].filter((identity) => terminalRowIds.has(identity)).length; const concreteRunningRowIds = [...runningRowIds].filter((identity) => !identity.endsWith(":trace-noise-summary")); const retainedConcreteRowCount = concreteRunningRowIds.filter((identity) => terminalRowIds.has(identity)).length; const summaryOnly = runningRowIds.size > 0 && concreteRunningRowIds.length === 0; const windowed = terminal?.disclosure?.rowWindowed === true; const runningEventCount = running?.disclosure?.eventCount; const terminalEventCount = terminal?.disclosure?.eventCount; const countsAdvance = Number.isInteger(runningEventCount) && Number.isInteger(terminalEventCount) && terminalEventCount >= runningEventCount; const sequenceWindowAdvances = Number.isInteger(running?.sourceSeqMin) && Number.isInteger(running?.sourceSeqMax) && Number.isInteger(terminal?.sourceSeqMin) && Number.isInteger(terminal?.sourceSeqMax) && terminal.sourceSeqMin >= running.sourceSeqMin && terminal.sourceSeqMax >= running.sourceSeqMax; if (!windowed && !summaryOnly) { return { mode: "identity-overlap", passed: retainedConcreteRowCount > 0, retainedRowCount, retainedConcreteRowCount, valuesRedacted: true }; } return { mode: windowed ? "window-progress" : "summary-progress", passed: countsAdvance && sequenceWindowAdvances, retainedRowCount, retainedConcreteRowCount, runningEventCount: runningEventCount ?? null, terminalEventCount: terminalEventCount ?? null, runningSourceSeqMin: running?.sourceSeqMin ?? null, runningSourceSeqMax: running?.sourceSeqMax ?? null, terminalSourceSeqMin: terminal?.sourceSeqMin ?? null, terminalSourceSeqMax: terminal?.sourceSeqMax ?? null, valuesRedacted: true }; } function workbenchTraceReadabilityFailures(evidence) { const failures = []; const add = (code, message) => failures.push({ code, message, valuesRedacted: true }); if (evidence.scope?.conversationCount !== 1 || evidence.scope?.debugPanelCountInsideConversation !== 0) { add("product_trace_scope_invalid", "产品 Trace 作用域必须只有一个 #conversation-list,且不得包含隔离调试面板。"); } if (evidence.startedDuringRunning !== true || evidence.running?.observed !== true) { add("running_phase_not_observed", "命令未在当前 turn 运行期间观测到可读的产品 Trace。"); } const running = evidence.running?.snapshot; if (evidence.running?.observed === true && (running?.sourceAuthorityRowCount !== running?.rowCount || running?.projectedAuthorityRowCount !== 0)) { add("running_source_authority_missing", "运行中的产品 Trace 行必须全部使用 source sequence authority。"); } if (evidence.disclosure?.openBefore !== true || evidence.disclosure?.autoReadable !== true) { add("trace_not_auto_expanded", "产品 Trace 在命令主动展开前没有默认保持可读。"); } if (evidence.terminalArrival?.observed !== true) add("terminal_not_observed", "当前产品 Trace 未在 YAML 超时内进入终态。"); const terminal = evidence.terminal?.snapshot; if (evidence.terminal?.stable !== true || terminal?.disclosure?.open !== true || terminal?.rowCount <= 0) { add("terminal_trace_not_retained", "产品 Trace 行在终态后没有保留在展开的详情中。"); } if (terminal?.sourceAuthorityRowCount <= 0 || terminal?.readableSourceRowCount <= 0 || terminal?.sourceAuthorityRowCount !== terminal?.rowCount || terminal?.projectedAuthorityRowCount !== 0) { add("source_authority_missing", "终态产品 Trace 必须展示可读的 source-authority 行,且不得出现 projected-authority 行。"); } if (evidence.running?.observed === true && evidence.runningRetention?.passed !== true) { add("running_rows_not_retained", "运行期 Trace 未通过身份重叠或窗口前进合同证明终态保留。"); } return failures; } function workbenchTraceReadabilityEvidenceError(message, evidence) { const error = new Error(message); error.details = { sessionId: evidence?.sessionId || null, traceId: evidence?.traceId || null, scope: evidence?.scope || null, disclosure: evidence?.disclosure || null, running: evidence?.running || null, terminalArrival: evidence?.terminalArrival || null, terminal: evidence?.terminal || null, runningRetention: evidence?.runningRetention || null, retainedRunningRowCount: evidence?.retainedRunningRowCount ?? null, failures: evidence?.failures || null, screenshot: evidence?.screenshot || null, reportPath: evidence?.reportPath || null, reportSha256: evidence?.reportSha256 || null, valuesRedacted: true }; return error; } async function workbenchTraceReadabilityWriteReport(reportDir, report) { await mkdir(reportDir, { recursive: true, mode: 0o700 }); const file = path.join(reportDir, "report.json"); await writeFile(file, JSON.stringify(sanitize(report), null, 2) + "\n", { mode: 0o600 }); const meta = await fileMeta(file); const artifact = { kind: "workbench-trace-readability-report", path: file, byteCount: meta.byteCount, sha256: meta.sha256, commandId: activeCommandId, valuesRedacted: true }; await appendJsonl(files.artifacts, { ts: new Date().toISOString(), ...artifact }); return artifact; } `; }