// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. redaction module for scripts/src/platform-infra-sub2api-codex.ts. // Moved mechanically from scripts/src/platform-infra-sub2api-codex.ts:2035-2700 for #903. import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; import type { UniDeskConfig } from "../config"; import { rootPath } from "../config"; import type { RenderedCliResult } from "../output"; import { applyPk01CaddyBlock, prepareFrpcSecret, renderFrpcManifest, type PublicServiceExposure, type PublicServiceTarget } from "../platform-infra-public-service"; import { shortSha256Fingerprint } from "../platform-infra-ops-library"; import { codexPoolSentinelSummary, codexPoolSentinelRuntimeImage, readCodexPoolSentinelConfig, renderCodexPoolSentinelManifest, type CodexPoolSentinelConfig, type CodexPoolSentinelProfileSecret, } from "../platform-infra-sub2api-codex-sentinel"; import { parseEnvFile, readTextFile, redactRepoPath, requiredEnvValue } from "../secrets"; import { runSshCommandCapture, type SshCaptureResult } from "../ssh"; import type { CodexPoolConfig, CodexPoolRuntimeTarget, CodexProfile, CodexTempUnschedulablePolicy } from "./types"; import { desiredAccountCapacityTotal } from "./accounts"; import { fingerprint, isRecord } from "./config-utils"; import { manualBindingSourcePlan, targetPublicExposureSummary } from "./public-exposure"; export function readAuthAPIKey(authPath: string): { apiKey: string | null; shape: string } { if (!existsSync(authPath)) return { apiKey: null, shape: "missing" }; const parsed = JSON.parse(readFileSync(authPath, "utf8")) as unknown; if (!isRecord(parsed)) return { apiKey: null, shape: "non-object" }; const value = parsed.OPENAI_API_KEY; const shape = value === null ? "null" : Array.isArray(value) ? "array" : typeof value; if (typeof value === "string" && value.length > 0) return { apiKey: value, shape }; return { apiKey: null, shape }; } export function uniqueAccountName(profile: string, seen: Set): string { const normalized = profile .toLowerCase() .replace(/[^a-z0-9._-]+/gu, "-") .replace(/^-+|-+$/gu, "") || "default"; let candidate = `unidesk-codex-${normalized}`; let counter = 2; while (seen.has(candidate)) { candidate = `unidesk-codex-${normalized}-${counter}`; counter += 1; } seen.add(candidate); return candidate; } export function redactProfile(profile: CodexProfile): Record { return { profile: profile.profile, accountName: profile.accountName, configFile: profile.configFile, authFile: profile.authFile, provider: profile.provider || null, baseUrl: profile.baseUrl || null, wireApi: profile.wireApi, model: profile.model, envKey: profile.envKey, apiKeySource: profile.apiKeySource, openaiResponsesWebSocketsV2Mode: profile.openaiResponsesWebSocketsV2Mode, upstreamUserAgent: profile.upstreamUserAgent, trustUpstream: profile.trustUpstream, sentinelProtect: profile.sentinelProtect, priority: profile.priority, capacity: profile.capacity, loadFactor: profile.loadFactor, tempUnschedulable: tempUnschedulableSummary(profile.tempUnschedulable), apiKeyPresent: profile.apiKey !== null && profile.apiKey.length > 0, apiKeyBytes: profile.apiKey === null ? 0 : Buffer.byteLength(profile.apiKey, "utf8"), apiKeyFingerprint: profile.apiKey === null ? null : fingerprint(profile.apiKey), authOpenAIKeyShape: profile.authOpenAIKeyShape, ok: profile.ok, error: profile.error, valuesPrinted: false, }; } export function compactProfile(profile: CodexProfile): Record { return { profile: profile.profile, accountName: profile.accountName, provider: profile.provider || null, model: profile.model, priority: profile.priority, trustUpstream: profile.trustUpstream, sentinelProtectEnabled: profile.sentinelProtect.enabled, sentinelProtectConsecutiveFailures: profile.sentinelProtect.enabled ? profile.sentinelProtect.consecutiveFailures : undefined, capacity: profile.capacity, loadFactor: profile.loadFactor, tempUnschedulableEnabled: profile.tempUnschedulable.enabled, tempUnschedulableRuleCount: profile.tempUnschedulable.rules.length, apiKeyPresent: profile.apiKey !== null && profile.apiKey.length > 0, ok: profile.ok, error: profile.error, valuesPrinted: false, }; } export function renderSub2ApiTempUnschedulableCredentials(policy: CodexTempUnschedulablePolicy): Record { if (!policy.enabled) return {}; return { temp_unschedulable_enabled: policy.enabled, temp_unschedulable_rules: policy.rules.map((rule) => ({ error_code: rule.statusCode, keywords: [...rule.keywords], duration_minutes: rule.durationMinutes, description: rule.description ?? "", })), }; } export function tempUnschedulableSummary(policy: CodexTempUnschedulablePolicy): Record { return { enabled: policy.enabled, ruleCount: policy.rules.length, statusCodes: policy.rules.map((rule) => rule.statusCode), }; } export function codexPoolConfigSummary(pool: CodexPoolConfig, target: CodexPoolRuntimeTarget): Record { const accountCapacityTotal = desiredAccountCapacityTotal(pool); return { version: pool.version, kind: pool.kind, metadata: pool.metadata, groupName: pool.groupName, groupDescription: pool.groupDescription, apiKeyName: pool.apiKeyName, apiKeySecretName: pool.apiKeySecretName, apiKeySecretKey: pool.apiKeySecretKey, adminEmailDefault: pool.adminEmailDefault, minOwnerBalanceUsd: pool.minOwnerBalanceUsd, minOwnerConcurrency: pool.minOwnerConcurrency, minOwnerConcurrencySource: pool.minOwnerConcurrencySource, accountCapacityTotal, defaultAccountPriority: pool.defaultAccountPriority, defaultAccountCapacity: pool.defaultAccountCapacity, defaultAccountLoadFactor: pool.defaultAccountLoadFactor, defaultTempUnschedulable: tempUnschedulableSummary(pool.defaultTempUnschedulable), defaultSentinelProtect: pool.defaultSentinelProtect, profileCount: pool.profiles.length, manualAccounts: { bindingSources: pool.manualAccounts.bindingSources.items.map(manualBindingSourcePlan), protectedCount: pool.manualAccounts.protected.length, protected: pool.manualAccounts.protected, controlPolicy: "manual accounts are not created, credential-updated, pruned, probed, or frozen by UniDesk codex-pool sync/sentinel; optional proxy_id and pool group membership bindings are narrow YAML-controlled exceptions", }, publicExposure: targetPublicExposureSummary(target), localCodex: pool.localCodex, sentinel: codexPoolSentinelSummary(pool.sentinel), disclosure: { full: "bun scripts/cli.ts platform-infra sub2api codex-pool plan --full", }, valuesPrinted: false, }; } export function compactText(value: unknown, limit = 260): string { if (typeof value !== "string") return ""; return value.length > limit ? `${value.slice(0, limit)}…` : value; } export function recordArray(value: unknown): Record[] { return Array.isArray(value) ? value.filter(isRecord) : []; } export function pickSummaryFields(item: Record, keys: string[]): Record { const result: Record = {}; for (const key of keys) { if (item[key] !== undefined) result[key] = item[key]; } return result; } export function compactStatusBlock(block: unknown, keys: string[]): Record | null { if (!isRecord(block)) return null; const items = recordArray(block.items); const attentionItems = items .filter((item) => item.ok === false) .map((item) => pickSummaryFields(item, keys)); return { ok: block.ok, desired: block.desired, missing: block.missing, mismatched: block.mismatched, totals: block.totals, itemCount: items.length, attentionItems, valuesPrinted: false, }; } export function compactTempUnschedulableStatus(block: unknown): Record | null { if (!isRecord(block)) return null; const items = recordArray(block.items); const compactItems = items.map((item) => { const result = pickSummaryFields(item, [ "accountName", "accountId", "status", "schedulable", "tempUnschedulableUntil", "tempUnschedulableSet", "ok", ]); if (item.tempUnschedulableReasonPreview !== undefined) { result.tempUnschedulableReason = tempUnschedulableReasonSummary(item.tempUnschedulableReasonPreview); } return result; }); const frozenItems = compactItems.filter((item) => item.tempUnschedulableSet === true); const focusedFrozenItems = uniqueByAccountName([ ...frozenItems.filter((item) => isRecord(item.tempUnschedulableReason) && item.tempUnschedulableReason.statusCode === 400), ...frozenItems.filter((item) => item.schedulable === false), ...frozenItems, ]).slice(0, 6); return { ok: block.ok, desired: block.desired, enabledCount: Array.isArray(block.enabled) ? block.enabled.length : undefined, missing: block.missing, mismatched: block.mismatched, itemCount: compactItems.length, frozenCount: frozenItems.length, frozenShown: focusedFrozenItems.length, frozen: focusedFrozenItems.map((item) => pickSummaryFields(item, [ "accountName", "accountId", "schedulable", "tempUnschedulableUntil", "tempUnschedulableReason", ])), manuallyUnschedulable: compactItems .filter((item) => item.schedulable === false && item.tempUnschedulableSet !== true) .map((item) => pickSummaryFields(item, ["accountName", "accountId", "status", "schedulable"])), valuesPrinted: false, }; } export function compactManualAccounts(block: unknown): Record | null { if (!isRecord(block)) return null; const items = recordArray(block.items).map((item) => pickSummaryFields(item, [ "accountName", "reason", "ok", "exists", "accountId", "status", "schedulable", "inYamlProfiles", "runtimeMarkedUnideskManaged", "proxyBinding", "groupBinding", "controlPolicy", ])); const proxySync = isRecord(block.proxySync) ? { ok: block.proxySync.ok, itemCount: block.proxySync.itemCount, items: recordArray(block.proxySync.items).map((item) => pickSummaryFields(item, [ "accountName", "accountId", "enabled", "ok", "action", "proxyAction", "expectedProxyName", "proxyId", "runtimeProxyId", "runtimeProxyName", "bindingAligned", "controlPolicy", ])), valuesPrinted: false, } : undefined; const groupSync = isRecord(block.groupSync) ? { ok: block.groupSync.ok, itemCount: block.groupSync.itemCount, items: recordArray(block.groupSync.items).map((item) => pickSummaryFields(item, [ "accountName", "accountId", "enabled", "ok", "action", "source", "poolGroupName", "poolGroupId", "bindingAligned", "controlPolicy", ])), valuesPrinted: false, } : undefined; return { ok: block.ok, protectedCount: block.protectedCount, items, proxySync, groupSync, valuesPrinted: false, }; } export function tempUnschedulableReasonSummary(value: unknown): Record { const reason = compactText(value, 180); const statusCodeMatch = reason.match(/"status_code":(\d{3})/u) ?? reason.match(/OpenAI\s+(\d{3})/u) ?? reason.match(/\((\d{3})\)/u); const keywordMatch = reason.match(/"matched_keyword":"([^"]+)"/u); const summary: Record = { statusCode: statusCodeMatch ? Number(statusCodeMatch[1]) : undefined, matchedKeyword: keywordMatch ? keywordMatch[1] : undefined, }; if (!statusCodeMatch || !keywordMatch) summary.preview = compactText(reason, 120); return summary; } export function uniqueByAccountName(items: Record[]): Record[] { const seen = new Set(); const result: Record[] = []; for (const item of items) { const key = item.accountName ?? item.accountId; if (seen.has(key)) continue; seen.add(key); result.push(item); } return result; } export function compactRuntimeCapability(block: unknown): unknown { if (!isRecord(block)) return block; const probe = isRecord(block.probe) ? block.probe : {}; const logEvidence = isRecord(probe.logEvidence) ? probe.logEvidence : {}; const accountState = isRecord(probe.accountState) ? probe.accountState : (isRecord(probe.badAccountState) ? probe.badAccountState : {}); const resources = isRecord(block.resources) ? block.resources : {}; const requirement = isRecord(block.requirement) ? block.requirement : {}; return { ok: block.ok, required: block.required, capability: block.capability, outcome: block.outcome, requirement: Object.keys(requirement).length === 0 ? undefined : { statusCode: requirement.statusCode, representativeKeyword: requirement.representativeKeyword, durationMinutes: requirement.durationMinutes, sourceAccountName: requirement.sourceAccountName, }, requestEvidence: Object.keys(probe).length === 0 ? undefined : { requestId: probe.requestId, durationMs: probe.durationMs, httpStatus: probe.httpStatus, responseOk: probe.responseOk, accountState: Object.keys(accountState).length === 0 ? undefined : { accountId: accountState.accountId, status: accountState.status, schedulable: accountState.schedulable, tempUnschedulableUntil: accountState.tempUnschedulableUntil, tempUnschedulableSet: accountState.tempUnschedulableSet, tempUnschedulableReasonPreview: compactText(accountState.tempUnschedulableReasonPreview, 180), }, logEvidence: Object.keys(logEvidence).length === 0 ? undefined : { matchedLogLineCount: logEvidence.matchedLogLineCount, failovers: logEvidence.failovers, final: logEvidence.final, }, bodyPreview: probe.responseOk === false ? compactText(probe.bodyPreview, 240) : undefined, }, resources: Object.keys(resources).length === 0 ? undefined : { groupId: resources.groupId, accountId: resources.accountId, badAccountId: resources.badAccountId, goodAccountId: resources.goodAccountId, apiKeyId: resources.apiKeyId, valuesPrinted: false, }, message: block.message, valuesPrinted: false, }; } export function compactGatewayModels(block: unknown): unknown { if (!isRecord(block)) return block; return pickSummaryFields(block, [ "ok", "httpStatus", "modelCount", "method", "serviceDns", "valuesPrinted", ]); } export function compactGatewayResponses(block: unknown): unknown { if (!isRecord(block)) return block; const evidence = isRecord(block.evidence) ? block.evidence : {}; return { ok: block.ok, degraded: block.degraded, outcome: block.outcome, httpStatus: block.httpStatus, method: block.method, model: block.model, requestId: block.requestId, durationMs: block.durationMs, outputTextPreview: block.outputTextPreview, evidence: Object.keys(evidence).length === 0 ? undefined : { matchedLogLineCount: evidence.matchedLogLineCount, failoverCount: Array.isArray(evidence.failovers) ? evidence.failovers.length : undefined, failovers: Array.isArray(evidence.failovers) ? evidence.failovers.slice(-5) : undefined, final: evidence.final, }, valuesPrinted: false, }; } export function compactGatewayResponsesRecent(block: unknown): unknown { if (!isRecord(block)) return block; return { ok: block.ok, degraded: block.degraded, window: block.window, tailLines: block.tailLines, failoverCount: block.failoverCount, forwardFailureCount: block.forwardFailureCount, finalErrorCount: block.finalErrorCount, slowFinalErrorCount: block.slowFinalErrorCount, contextCanceledCount: block.contextCanceledCount, ignoredProbeNoiseCount: block.ignoredProbeNoiseCount, failoverBudgetExhaustedCount: Array.isArray(block.failoverBudgetExhausted) ? block.failoverBudgetExhausted.length : undefined, failoverBudgetExhausted: Array.isArray(block.failoverBudgetExhausted) ? block.failoverBudgetExhausted.slice(-3).reverse() : undefined, recentFailovers: Array.isArray(block.recentFailovers) ? block.recentFailovers.slice(-4).reverse() : undefined, recentForwardFailures: Array.isArray(block.recentForwardFailures) ? block.recentForwardFailures.slice(-4).reverse().map((item) => ({ ...item, errorPreview: compactText(item.errorPreview, 220), })) : undefined, recentFinalErrors: Array.isArray(block.recentFinalErrors) ? block.recentFinalErrors.slice(-3).reverse() : undefined, recentSlowFinalErrors: Array.isArray(block.recentSlowFinalErrors) ? block.recentSlowFinalErrors.slice(-3).reverse() : undefined, recentContextCanceled: Array.isArray(block.recentContextCanceled) ? block.recentContextCanceled.slice(-3).reverse() : undefined, logsExitCode: block.logsExitCode, valuesPrinted: false, }; } export function compactGatewayCompactRecent(block: unknown): unknown { if (!isRecord(block)) return block; return { ok: block.ok, degraded: block.degraded, window: block.window, tailLines: block.tailLines, failureCount: block.failureCount, successCount: block.successCount, failoverCount: block.failoverCount, finalErrorCount: block.finalErrorCount, contextCanceledCount: block.contextCanceledCount, recentFailures: Array.isArray(block.recentFailures) ? block.recentFailures.slice(-2).reverse() : undefined, recentSuccesses: Array.isArray(block.recentSuccesses) ? block.recentSuccesses.slice(-2).reverse() : undefined, recentFailovers: Array.isArray(block.recentFailovers) ? block.recentFailovers.slice(-4).reverse() : undefined, recentFinalErrors: Array.isArray(block.recentFinalErrors) ? block.recentFinalErrors.slice(-2).reverse() : undefined, recentContextCanceled: Array.isArray(block.recentContextCanceled) ? block.recentContextCanceled.slice(-2).reverse() : undefined, logsExitCode: block.logsExitCode, valuesPrinted: false, }; } export function compactSentinelErrorDetails(value: unknown): Record | undefined { if (!isRecord(value)) return undefined; const body = isRecord(value.body) ? value.body : {}; const openaiError = isRecord(value.openaiError) ? value.openaiError : {}; return { kind: value.kind, statusCode: value.statusCode, code: value.code ?? body.code ?? openaiError.code, type: value.type ?? body.type ?? openaiError.type, bodyHash: value.bodyHash, valuesPrinted: false, }; } export function compactSentinelQuarantine(item: unknown): Record { if (!isRecord(item)) return {}; const protect = isRecord(item.sentinelProtect) ? item.sentinelProtect : {}; return { accountName: item.accountName, until: item.until, applied: item.applied, reason: item.reason, failureKind: item.failureKind, intervalMinutes: item.intervalMinutes, sentinelProtect: Object.keys(protect).length > 0 ? { enabled: protect.enabled, decision: protect.decision, failureCount: protect.failureCount, threshold: protect.threshold, } : undefined, error: compactSentinelErrorDetails(item.errorDetails), }; } export function compactSentinelRecentAccount(item: unknown): Record { if (!isRecord(item)) return {}; const action = isRecord(item.action) ? item.action : {}; const error = compactSentinelErrorDetails(item.errorDetails); const protect = isRecord(item.sentinelProtect) ? item.sentinelProtect : {}; return { accountName: item.accountName, lastProbeAt: item.lastProbeAt, lastStatus: item.lastStatus, nextProbeAfter: item.nextProbeAfter, ok: item.ok, purpose: item.purpose, httpStatus: item.httpStatus, durationMs: item.durationMs, markerMatched: item.markerMatched, sentinelProtect: Object.keys(protect).length > 0 ? { enabled: protect.enabled, decision: protect.decision, protected: protect.protected, failureCount: protect.failureCount, threshold: protect.threshold, } : undefined, failureKind: item.failureKind, requestShape: item.requestShape, action: Object.keys(action).length > 0 ? { taken: action.taken, type: action.type, } : undefined, errorStatusCode: error?.statusCode, errorCode: error?.code, errorBodyHash: error?.bodyHash, }; } export function compactSentinelLastRun(value: unknown): Record | undefined { if (!isRecord(value)) return undefined; return { at: value.at, monitorEnabled: value.monitorEnabled, actionsEnabled: value.actionsEnabled, profileCount: value.profileCount, selected: value.selected, okCount: value.okCount, mismatchCount: value.mismatchCount, markerMismatchCount: value.markerMismatchCount, transportFailureCount: value.transportFailureCount, actionsTaken: value.actionsTaken, gatewayFailureMonitor: value.gatewayFailureMonitor, selection: value.selection, reconcileCount: Array.isArray(value.reconcile) ? value.reconcile.length : undefined, }; } export function compactSentinelStatus(block: unknown): unknown { if (!isRecord(block)) return block; const runtime = isRecord(block.runtime) ? block.runtime : block; const desired = isRecord(runtime.desired) ? runtime.desired : {}; const cronJob = isRecord(runtime.cronJob) ? runtime.cronJob : {}; const secret = isRecord(runtime.secret) ? runtime.secret : {}; const configMap = isRecord(runtime.configMap) ? runtime.configMap : {}; const state = isRecord(runtime.state) ? runtime.state : {}; const freezeReassert = isRecord(block.freezeReassert) ? block.freezeReassert : {}; const qualityGatePrepare = isRecord(block.qualityGatePrepare) ? block.qualityGatePrepare : {}; const qualityGate = isRecord(block.qualityGate) ? block.qualityGate : {}; const quarantined = recordArray(state.quarantined).map(compactSentinelQuarantine); const recentAccounts = recordArray(state.recentAccounts).map(compactSentinelRecentAccount); const recentAttention = recentAccounts.filter((item) => item.ok === false || isRecord(item.action) && item.action.taken === true); const recentHealthy = recentAccounts .filter((item) => item.ok === true) .slice(-3) .map((item) => pickSummaryFields(item, ["accountName", "lastProbeAt", "lastStatus", "nextProbeAfter", "ok", "httpStatus", "markerMatched"])); return { ok: block.ok, action: block.action, desired: { monitorEnabled: desired.monitorEnabled, actionsEnabled: desired.actionsEnabled, schedule: desired.schedule, cronJobName: desired.cronJobName, configMapName: desired.configMapName, credentialsSecretName: desired.credentialsSecretName, stateConfigMapName: desired.stateConfigMapName, }, cronJob: { exists: cronJob.exists, schedule: cronJob.schedule, suspend: cronJob.suspend, lastScheduleTime: cronJob.lastScheduleTime, active: cronJob.active, error: cronJob.error, }, secret: { exists: secret.exists, profileSecretPresent: secret.profileSecretPresent, valuesPrinted: false, error: secret.error, }, configMap: { exists: configMap.exists, configPresent: configMap.configPresent, runnerPresent: configMap.runnerPresent, error: configMap.error, }, state: { exists: state.exists, accountCount: state.accountCount, quarantinedCount: state.quarantinedCount, quarantinedShown: Math.min(quarantined.length, 3), quarantined: quarantined.slice(-3), recentAccountCount: recentAccounts.length, recentAttention: uniqueByAccountName(recentAttention.slice(-3)), recentHealthy, lastRun: compactSentinelLastRun(state.lastRun), error: state.error, }, freezeReassert: Object.keys(freezeReassert).length > 0 ? { ok: freezeReassert.ok, skipped: freezeReassert.skipped, reason: freezeReassert.reason, itemCount: freezeReassert.itemCount, attentionItems: freezeReassert.attentionItems, } : undefined, pendingProbePrepare: Object.keys(qualityGatePrepare).length > 0 ? { ok: qualityGatePrepare.ok, skipped: qualityGatePrepare.skipped, reason: qualityGatePrepare.reason, changedCount: qualityGatePrepare.changedCount, fingerprintOnlyCount: qualityGatePrepare.fingerprintOnlyCount, pendingOnly: qualityGatePrepare.pendingOnly, items: qualityGatePrepare.items, } : undefined, pendingProbe: Object.keys(qualityGate).length > 0 ? { ok: qualityGate.ok, skipped: qualityGate.skipped, reason: qualityGate.reason, changedCount: qualityGate.changedCount, fingerprintOnlyCount: qualityGate.fingerprintOnlyCount, clampedCount: qualityGate.clampedCount, items: qualityGate.items, clampedItems: qualityGate.clampedItems, } : undefined, valuesPrinted: false, }; } export function compactSentinelProbeResult(parsed: Record | null): Record | null { if (parsed === null) return null; const probe = isRecord(parsed.probe) ? parsed.probe : {}; const summary = isRecord(probe.summary) ? probe.summary : {}; const state = isRecord(parsed.sentinelState) ? parsed.sentinelState : {}; return { ok: parsed.ok, mode: parsed.mode, namespace: parsed.namespace, job: parsed.job, requestedAccounts: parsed.requestedAccounts, summary: { at: summary.at, monitorEnabled: summary.monitorEnabled, actionsEnabled: summary.actionsEnabled, selected: summary.selected, okCount: summary.okCount, mismatchCount: summary.mismatchCount, markerMismatchCount: summary.markerMismatchCount, transportFailureCount: summary.transportFailureCount, actionsTaken: summary.actionsTaken, gatewayFailureMonitor: summary.gatewayFailureMonitor, selection: summary.selection, }, results: recordArray(probe.results).map((item) => pickSummaryFields(item, [ "accountName", "purpose", "ok", "markerMatched", "sentinelProtect", "httpStatus", "durationMs", "usage", "outputHash", "outputPreview", "responseBodyHash", "responseBodyPreview", "error", "errorDetails", "failureKind", "sdk", "requestShape", ])), actions: recordArray(probe.actions).map((item) => pickSummaryFields(item, [ "accountName", "taken", "type", "error", ])), sentinelState: { quarantined: state.quarantined, recentAccounts: state.recentAccounts, lastRun: state.lastRun, }, valuesPrinted: false, }; }