@deeeed/metamask-harness 0.79.0 → 0.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +1 -1
- package/adapters/extension/ensure-browser.sh +2 -2
- package/adapters/extension/inject.mjs +0 -1
- package/adapters/extension/launch-browser.cjs +39 -613
- package/adapters/extension/readiness.mjs +2 -5
- package/adapters/extension/reattach.sh +2 -2
- package/adapters/extension/stamp-runtime-title.cjs +1 -1
- package/adapters/manifest.json +2 -34
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +57 -1358
- package/adapters/mobile/bridge-runtime/teaching.cjs +20 -0
- package/adapters/mobile/inject.sh +16 -0
- package/adapters/mobile/launch-console-forwarder.cjs +1 -2
- package/adapters/mobile/open-device.sh +2 -2
- package/adapters/mobile/reload-app.mjs +4 -4
- package/adapters/mobile/reset-app-data.sh +1 -1
- package/adapters/mobile/start-console-forwarder.sh +3 -2
- package/adapters/mobile/start-metro.sh +17 -12
- package/adapters/mobile/wait-for-bridge.cjs +2 -2
- package/adapters/shared/harness-source-fingerprint.mjs +13 -8
- package/adapters/shared/open-debug.mjs +2 -2
- package/adapters/shared/sync-wallet-fixture.sh +8 -5
- package/adapters/terminal/cleanup.mjs +2 -1
- package/adapters/terminal/inject.mjs +2 -1
- package/bin/mm-harness +9 -28
- package/dist/adapters/checkout-files.js +16 -0
- package/dist/adapters/console-capture.js +14 -1
- package/dist/adapters/core/actions.js +4 -3
- package/dist/adapters/core/overlay.js +1 -1
- package/dist/adapters/core/readiness.js +28 -1
- package/dist/adapters/core/run.js +2 -2
- package/dist/adapters/core/surface.js +16 -1
- package/dist/adapters/extension/browser-pid.js +56 -0
- package/dist/adapters/extension/console-capture.js +1 -1
- package/dist/adapters/extension/doctor-checks.js +1 -1
- package/dist/adapters/extension/ensure-ready.js +5 -4
- package/dist/adapters/extension/extension-id.js +1 -1
- package/dist/adapters/extension/feature-flags.js +1 -1
- package/dist/adapters/extension/fixtures.js +1 -2
- package/dist/adapters/extension/launch.js +4 -5
- package/dist/adapters/extension/launched-browser.js +1 -2
- package/dist/adapters/extension/network-observer.js +1 -1
- package/dist/adapters/extension/overlay.js +1 -1
- package/dist/adapters/extension/product-config.js +1 -1
- package/dist/adapters/extension/readiness.js +1 -1
- package/dist/adapters/extension/reload.js +1 -1
- package/dist/adapters/extension/run.js +24 -10
- package/dist/adapters/extension/runtime-decision.js +2 -1
- package/dist/adapters/extension/runtime.js +9 -5
- package/dist/adapters/extension/surface.js +15 -2
- package/dist/adapters/extension/verify.js +2 -2
- package/dist/adapters/harness-freshness.js +2 -1
- package/dist/adapters/mobile/android-gfxinfo-performance.js +1 -3
- package/dist/adapters/mobile/app-identity.js +10 -0
- package/dist/adapters/mobile/doctor-checks.js +1 -1
- package/dist/adapters/mobile/fixtures.js +1 -1
- package/dist/adapters/mobile/launch.js +27 -28
- package/dist/adapters/mobile/lifecycle.js +1 -1
- package/dist/adapters/mobile/metro-env.js +9 -59
- package/dist/adapters/mobile/performance-observer.js +1 -1
- package/dist/adapters/mobile/prepare-target.js +1 -1
- package/dist/adapters/mobile/prepare.js +3 -4
- package/dist/adapters/mobile/provision.js +1 -1
- package/dist/adapters/mobile/readiness.js +1 -1
- package/dist/adapters/mobile/recording-target.js +15 -0
- package/dist/adapters/mobile/release-artifact-state.js +1 -1
- package/dist/adapters/mobile/reload.js +1 -1
- package/dist/adapters/mobile/run.js +8 -7
- package/dist/adapters/mobile/runtime-decision.js +2 -4
- package/dist/adapters/mobile/source-freshness.js +22 -126
- package/dist/adapters/mobile/surface.js +26 -3
- package/dist/adapters/mobile/ui.js +16 -5
- package/dist/adapters/mobile/verify.js +2 -3
- package/dist/adapters/performance/cdp-trace.js +1 -1
- package/dist/adapters/performance/device-platform.js +1 -1
- package/dist/adapters/slot-ports.js +1 -1
- package/dist/adapters/surface.js +1 -0
- package/dist/adapters/terminal/console-capture.js +1 -1
- package/dist/adapters/terminal/launch.js +1 -1
- package/dist/adapters/terminal/run.js +25 -2
- package/dist/adapters/terminal/surface.js +7 -1
- package/dist/adapters.js +8 -4
- package/dist/advisor-policy.js +1 -1
- package/dist/checkout-view.js +1 -2
- package/dist/cli.js +28 -15
- package/dist/command-contract.js +1 -26
- package/dist/commands/call.js +10 -24
- package/dist/commands/check.js +3 -4
- package/dist/commands/checklist.js +1 -1
- package/dist/commands/completion-candidates.js +2 -10
- package/dist/commands/config.js +2 -2
- package/dist/commands/core-readiness.js +12 -7
- package/dist/commands/debug.js +3 -3
- package/dist/commands/device-target.js +5 -5
- package/dist/commands/doctor.js +2 -14
- package/dist/commands/domain.js +2 -2
- package/dist/commands/ensure-ready.js +1 -7
- package/dist/commands/fixtures.js +5 -6
- package/dist/commands/help.js +3 -4
- package/dist/commands/launch/device.js +10 -17
- package/dist/commands/launch/extension.js +2 -8
- package/dist/commands/launch/mobile.js +1 -1
- package/dist/commands/list-executables.js +1 -3
- package/dist/commands/logs.js +3 -3
- package/dist/commands/manifest.js +1 -12
- package/dist/commands/mobile-device-view.js +4 -2
- package/dist/commands/overlay.js +110 -0
- package/dist/commands/pin.js +1 -1
- package/dist/commands/pr-body.js +2 -2
- package/dist/commands/prepare.js +4 -9
- package/dist/commands/provision.js +3 -12
- package/dist/commands/recipe-advice.js +1 -2
- package/dist/commands/recipe-quality.js +2 -3
- package/dist/commands/resolve-extension.js +1 -7
- package/dist/commands/review.js +4 -4
- package/dist/commands/run-engine.js +12 -51
- package/dist/commands/run.js +4 -25
- package/dist/commands/runtime-decision.js +1 -6
- package/dist/commands/runtime-health.js +1 -7
- package/dist/commands/runtime-launch.js +1 -13
- package/dist/commands/self-test.js +2 -6
- package/dist/commands/status-watch.js +1 -2
- package/dist/commands/status.js +2 -5
- package/dist/commands/task-init.js +2 -3
- package/dist/commands/tutorial.js +2 -2
- package/dist/commands/update.js +1 -1
- package/dist/completions-cache.js +1 -1
- package/dist/console-classifier.js +19 -0
- package/dist/doctor.js +2 -3
- package/dist/funding-execution-context.js +15 -1
- package/dist/host.js +32 -0
- package/dist/index.js +1 -6
- package/dist/live-tsx.js +49 -0
- package/dist/mm-harness-cli.js +2 -4
- package/dist/network-observation.js +4 -4
- package/dist/paths.js +34 -64
- package/dist/performance-observation.js +2 -2
- package/dist/progress.js +1 -1
- package/dist/recipe-assistance-context.js +1 -3
- package/dist/recipe-security.js +2 -59
- package/dist/review/knowledge.js +2 -2
- package/dist/runner.js +5 -3
- package/dist/runtime-context.js +1 -1
- package/dist/task-view.js +2 -3
- package/docs/CONTRIBUTING.md +1 -1
- package/docs/cli-contract.md +2 -2
- package/docs/feature-guard.md +90 -0
- package/library/README.md +3 -1
- package/library/actions/core/perps/_controller.mjs +153 -130
- package/library/actions/core/perps/quote_fees.mjs +58 -0
- package/library/actions/core/wallet/_fixture-accounts.mjs +69 -0
- package/library/actions/core/wallet/list_accounts.mjs +14 -57
- package/library/actions/extension/feature_flags/state.mjs +1 -1
- package/library/actions/extension/platform/cdp.mjs +2 -8
- package/library/actions/harness-exports.mjs +1 -6
- package/library/actions/mobile/app/network-control.mjs +6 -8
- package/library/actions/mobile/perps/prepare_local_snapshot_endpoint.mjs +1 -1
- package/library/actions/mobile/platform/bridge.mjs +8 -8
- package/library/actions/mobile/platform/native-session-name.mjs +21 -1
- package/library/actions/mobile/platform/native-session.mjs +3 -4
- package/library/actions/mobile/platform/observe-ui.mjs +12 -6
- package/library/actions/mobile/ui/native-navigation.mjs +4 -3
- package/library/actions/mobile/wallet/native-ui.mjs +3 -2
- package/library/actions/shared/analytics/_adapter.mjs +1 -1
- package/library/actions/shared/console-findings.mjs +1 -1
- package/library/actions/terminal/platform/runtime.mjs +2 -1
- package/library/manifests/core.action-manifest.json +83 -13
- package/package.json +8 -9
- package/adapters/extension/lib/slot-title.cjs +0 -292
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +0 -472
- package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +0 -125
- package/adapters/mobile/bridge-runtime/lib/cdp-broker.cjs +0 -1055
- package/adapters/mobile/bridge-runtime/lib/cdp-eval.cjs +0 -110
- package/adapters/mobile/bridge-runtime/lib/config.cjs +0 -61
- package/adapters/mobile/bridge-runtime/lib/console-format.cjs +0 -53
- package/adapters/mobile/bridge-runtime/lib/devtools-proxy.cjs +0 -177
- package/adapters/mobile/bridge-runtime/lib/issue-capture.cjs +0 -446
- package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +0 -84
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +0 -403
- package/adapters/mobile/bridge-runtime/lib/ws-client.cjs +0 -116
- package/adapters/mobile/coalesce-metro-log.cjs +0 -24
- package/adapters/mobile/launch-metro.cjs +0 -77
- package/adapters/mobile/metro-config.cjs +0 -98
- package/adapters/mobile/metro-log-generation.cjs +0 -106
- package/dist/adapters/mobile/frame-metrics.js +0 -118
- package/dist/adapters/mobile/video-recorder.js +0 -289
- package/dist/artifact-files.js +0 -92
- package/dist/checkout-lock.js +0 -149
- package/dist/cli-color.js +0 -98
- package/dist/command-journal.js +0 -308
- package/dist/commands/last.js +0 -60
- package/dist/commands/launch/index.js +0 -109
- package/dist/commands/parse-args.js +0 -279
- package/dist/commands/reload.js +0 -41
- package/dist/commands/run-report.js +0 -241
- package/dist/commands/shared.js +0 -323
- package/dist/commands/stop.js +0 -60
- package/dist/devices.js +0 -100
- package/dist/execution-provenance.js +0 -332
- package/dist/harness.js +0 -451
- package/dist/heal-bounds.js +0 -137
- package/dist/json-stream.js +0 -60
- package/dist/leaf-invoke.js +0 -19
- package/dist/library-provenance.js +0 -33
- package/dist/live-adapter-contract.js +0 -380
- package/dist/recording-target.js +0 -120
- package/dist/run-diagnostics.js +0 -364
- package/dist/run-recording.js +0 -481
- package/dist/runtime-proof.js +0 -90
- package/library/actions/mobile/platform/tool-paths.mjs +0 -123
package/dist/run-diagnostics.js
DELETED
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { createHash } from "node:crypto";
|
|
4
|
-
import { getAdapterSurface } from "./adapters/surface.js";
|
|
5
|
-
import {
|
|
6
|
-
collectorOwnerMarker,
|
|
7
|
-
collectorStatus
|
|
8
|
-
} from "../library/actions/shared/console-collector.mjs";
|
|
9
|
-
import {
|
|
10
|
-
allowlistMatch,
|
|
11
|
-
classifyConsoleRecord,
|
|
12
|
-
consoleRecords,
|
|
13
|
-
libraryRootsFromEnv,
|
|
14
|
-
loadConsoleAllowlist,
|
|
15
|
-
messageSignature
|
|
16
|
-
} from "../library/actions/shared/console-findings.mjs";
|
|
17
|
-
const MAX_CAPTURE_BYTES = 512 * 1024;
|
|
18
|
-
const MAX_FINDINGS = 20;
|
|
19
|
-
const MAX_PREVIEW_CHARS = 320;
|
|
20
|
-
const RAW_APP_LOG_ARTIFACT = "diagnostics-app-log.txt";
|
|
21
|
-
function readRunDiagnosticsDocument(diagnosticsPath) {
|
|
22
|
-
if (typeof diagnosticsPath !== "string" || diagnosticsPath.length === 0) return null;
|
|
23
|
-
try {
|
|
24
|
-
const document = JSON.parse(fs.readFileSync(diagnosticsPath, "utf8"));
|
|
25
|
-
return isRunDiagnosticsDocument(document) ? document : null;
|
|
26
|
-
} catch {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
function formatRunDiagnosticsForHuman(diagnostics, adapter) {
|
|
31
|
-
if (!diagnostics) {
|
|
32
|
-
return [getAdapterSurface(adapter).headless ? `N/A \u2014 ${adapter.charAt(0).toUpperCase()}${adapter.slice(1)} is headless and has no application log source.` : "UNAVAILABLE \u2014 application diagnostics could not be collected for this run."];
|
|
33
|
-
}
|
|
34
|
-
const status = diagnostics.status === "clean" ? "CLEAN" : diagnostics.status === "review" ? "REVIEW" : "UNAVAILABLE";
|
|
35
|
-
const lines = [`${status} \u2014 ${diagnostics.note}`];
|
|
36
|
-
for (const finding of diagnostics.findings) {
|
|
37
|
-
const count = finding.count > 1 ? ` \xD7${finding.count}` : "";
|
|
38
|
-
const source = finding.source ? ` [${finding.source}]` : "";
|
|
39
|
-
const allowed = finding.allowlisted ? ` (allowlisted: ${finding.allowlisted.reason})` : "";
|
|
40
|
-
lines.push(`${finding.level.toUpperCase()}${count}${source} \u2014 ${finding.preview}${allowed}`);
|
|
41
|
-
}
|
|
42
|
-
return lines;
|
|
43
|
-
}
|
|
44
|
-
async function beginRunDiagnostics(adapter, projectRoot) {
|
|
45
|
-
const surface = getAdapterSurface(adapter);
|
|
46
|
-
const source = surface.appLogSource(projectRoot);
|
|
47
|
-
if (!source) return null;
|
|
48
|
-
const diagnostics = surface.diagnostics;
|
|
49
|
-
const issueBuffer = diagnostics?.issueBuffer?.arm(projectRoot) ? { adapter, projectRoot } : void 0;
|
|
50
|
-
let capture;
|
|
51
|
-
if (diagnostics?.console) {
|
|
52
|
-
const collector = diagnostics.console;
|
|
53
|
-
const startError = await collector.start(projectRoot).then(() => void 0, (error) => error instanceof Error ? error.message : String(error));
|
|
54
|
-
const { extensionLog, pageLog, pidFile } = collector.files(projectRoot);
|
|
55
|
-
const cdpPort = collector.cdpPort();
|
|
56
|
-
capture = { adapter, projectRoot, extensionLog, pageLog, pidFile, ...cdpPort ? { cdpPort } : {}, ...startError ? { startError } : {} };
|
|
57
|
-
}
|
|
58
|
-
const walletLogPath = diagnostics?.walletLog?.(projectRoot) ?? null;
|
|
59
|
-
const walletStat = walletLogPath ? safeStat(walletLogPath) : null;
|
|
60
|
-
const stat = safeStat(source.path);
|
|
61
|
-
return {
|
|
62
|
-
source,
|
|
63
|
-
offset: stat?.size ?? 0,
|
|
64
|
-
...stat ? { inode: stat.ino } : {},
|
|
65
|
-
...issueBuffer ? { issueBuffer } : {},
|
|
66
|
-
...capture ? { capture } : {},
|
|
67
|
-
...walletLogPath ? { walletLog: { path: walletLogPath, offset: walletStat?.size ?? 0, ...walletStat ? { inode: walletStat.ino } : {} } } : {}
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
async function verifyConsoleCapture(capture) {
|
|
71
|
-
if (capture.startError) return { verified: false, detail: `the console collector did not start: ${capture.startError}` };
|
|
72
|
-
if (!capture.cdpPort) return { verified: false, detail: "no CDP port for the console collector" };
|
|
73
|
-
const status = collectorStatus({
|
|
74
|
-
pidFile: capture.pidFile,
|
|
75
|
-
extensionLog: capture.extensionLog,
|
|
76
|
-
marker: collectorOwnerMarker(path.dirname(capture.pidFile)),
|
|
77
|
-
cdpPort: capture.cdpPort
|
|
78
|
-
});
|
|
79
|
-
if (!status.ok) return { verified: false, detail: status.detail };
|
|
80
|
-
const control = await getAdapterSurface(capture.adapter).diagnostics.console.verifyControl({
|
|
81
|
-
projectRoot: capture.projectRoot,
|
|
82
|
-
cdpPort: capture.cdpPort,
|
|
83
|
-
extensionLog: capture.extensionLog,
|
|
84
|
-
pageLog: capture.pageLog,
|
|
85
|
-
pidFile: capture.pidFile
|
|
86
|
-
});
|
|
87
|
-
return { verified: control.ok, detail: control.ok ? `${status.detail}; ${control.detail}` : control.detail };
|
|
88
|
-
}
|
|
89
|
-
function isRunDiagnosticsDocument(value) {
|
|
90
|
-
if (!isRecord(value) || value.schemaVersion !== 1 || value.scope !== "recipe-run-application") return false;
|
|
91
|
-
if (value.status !== "clean" && value.status !== "review" && value.status !== "unavailable") return false;
|
|
92
|
-
return typeof value.note === "string" && Array.isArray(value.findings);
|
|
93
|
-
}
|
|
94
|
-
async function finishRunDiagnostics(baseline, result) {
|
|
95
|
-
if (!baseline) return result;
|
|
96
|
-
try {
|
|
97
|
-
const bufferedIssues = baseline.issueBuffer ? getAdapterSurface(baseline.issueBuffer.adapter).diagnostics?.issueBuffer?.collect(baseline.issueBuffer.projectRoot) : void 0;
|
|
98
|
-
const allowlist = loadConsoleAllowlist(recipeLibraryRoots(result.summaryPath));
|
|
99
|
-
const capture = baseline.capture ? await verifyConsoleCapture(baseline.capture) : void 0;
|
|
100
|
-
const { diagnostics, rawAppLog } = collectRunDiagnosticsCapture(baseline, bufferedIssues, allowlist, capture);
|
|
101
|
-
const artifactsDir = path.dirname(result.summaryPath);
|
|
102
|
-
const diagnosticsPath = path.join(artifactsDir, "diagnostics.json");
|
|
103
|
-
fs.writeFileSync(diagnosticsPath, `${JSON.stringify(diagnostics, null, 2)}
|
|
104
|
-
`);
|
|
105
|
-
fs.writeFileSync(
|
|
106
|
-
path.join(artifactsDir, RAW_APP_LOG_ARTIFACT),
|
|
107
|
-
redactDiagnosticText(rawAppLog.toString("utf8"))
|
|
108
|
-
);
|
|
109
|
-
indexDiagnosticArtifact(result.artifactManifestPath);
|
|
110
|
-
indexDiagnosticSummary(result.summaryPath, diagnostics);
|
|
111
|
-
return {
|
|
112
|
-
...result,
|
|
113
|
-
diagnosticsPath,
|
|
114
|
-
sideFindings: {
|
|
115
|
-
status: diagnostics.status,
|
|
116
|
-
note: diagnostics.note,
|
|
117
|
-
counts: diagnostics.counts
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
} catch (error) {
|
|
121
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
122
|
-
console.warn(`WARN: run diagnostics were unavailable: ${detail}`);
|
|
123
|
-
return {
|
|
124
|
-
...result,
|
|
125
|
-
sideFindings: {
|
|
126
|
-
status: "unavailable",
|
|
127
|
-
note: "Run diagnostics could not be collected; the recipe result is unchanged.",
|
|
128
|
-
counts: { total: 0, warning: 0, error: 0, exception: 0 }
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
function collectRunDiagnostics(baseline, bufferedIssues, allowlist = null, capture) {
|
|
134
|
-
return collectRunDiagnosticsCapture(baseline, bufferedIssues, allowlist, capture).diagnostics;
|
|
135
|
-
}
|
|
136
|
-
function collectRunDiagnosticsCapture(baseline, bufferedIssues, allowlist = null, capture) {
|
|
137
|
-
const stat = safeStat(baseline.source.path);
|
|
138
|
-
const source = {
|
|
139
|
-
label: baseline.source.label,
|
|
140
|
-
path: baseline.source.path,
|
|
141
|
-
startOffset: baseline.offset,
|
|
142
|
-
endOffset: stat?.size ?? baseline.offset,
|
|
143
|
-
inode: void 0,
|
|
144
|
-
bytesRead: 0,
|
|
145
|
-
truncated: false,
|
|
146
|
-
inAppBuffer: bufferedIssues === void 0 ? "n/a" : bufferedIssues === null ? "unavailable" : "collected",
|
|
147
|
-
rawArtifact: RAW_APP_LOG_ARTIFACT,
|
|
148
|
-
redacted: true
|
|
149
|
-
};
|
|
150
|
-
let rawAppLog = Buffer.alloc(0);
|
|
151
|
-
if (stat) {
|
|
152
|
-
const sameFile = baseline.inode === void 0 || baseline.inode === stat.ino;
|
|
153
|
-
const startOffset = sameFile && stat.size >= baseline.offset ? baseline.offset : 0;
|
|
154
|
-
const available = Math.max(0, stat.size - startOffset);
|
|
155
|
-
const bytesToRead = Math.min(available, MAX_CAPTURE_BYTES);
|
|
156
|
-
source.startOffset = startOffset;
|
|
157
|
-
source.endOffset = stat.size;
|
|
158
|
-
source.inode = stat.ino;
|
|
159
|
-
source.bytesRead = bytesToRead;
|
|
160
|
-
source.truncated = available > MAX_CAPTURE_BYTES;
|
|
161
|
-
if (bytesToRead > 0) rawAppLog = readSlice(baseline.source.path, startOffset, bytesToRead);
|
|
162
|
-
if (source.truncated) rawAppLog = rawAppLog.subarray(0, rawAppLog.lastIndexOf(10) + 1);
|
|
163
|
-
}
|
|
164
|
-
const text = rawAppLog.toString("utf8");
|
|
165
|
-
const lineFindings = consoleRecords(text).map(classifyRecord).filter((finding) => finding !== null);
|
|
166
|
-
applyConsoleAllowlist(lineFindings, allowlist);
|
|
167
|
-
const allFindings = dedupeFindings(
|
|
168
|
-
[
|
|
169
|
-
...lineFindings,
|
|
170
|
-
...(bufferedIssues ?? []).map(classifyBufferedIssue).filter((finding) => finding !== null),
|
|
171
|
-
...baseline.walletLog ? walletLogFindings(baseline.walletLog) : []
|
|
172
|
-
]
|
|
173
|
-
);
|
|
174
|
-
const applied = allFindings.filter((finding) => finding.level === "info").length;
|
|
175
|
-
allFindings.sort((a, b) => Number(a.level === "info") - Number(b.level === "info"));
|
|
176
|
-
const findings = allFindings.slice(0, MAX_FINDINGS).map(stripInternal);
|
|
177
|
-
const counts = countFindings(allFindings);
|
|
178
|
-
const omittedFindingCount = Math.max(0, allFindings.length - findings.length);
|
|
179
|
-
const captureFailed = capture !== void 0 && !capture.verified;
|
|
180
|
-
const status = counts.total > 0 ? "review" : !captureFailed && !source.truncated && (stat || bufferedIssues !== void 0 && bufferedIssues !== null) ? "clean" : "unavailable";
|
|
181
|
-
const captureNote = (captureFailed ? ` Console capture was not verified: ${capture.detail}.` : "") + (source.truncated ? ` The run's log exceeded ${MAX_CAPTURE_BYTES} bytes; only its start was checked.` : "");
|
|
182
|
-
const note = (counts.total > 0 ? `Observed ${counts.total} distinct application warning/error event(s) during the recipe run; showing ${findings.length} preview(s) and omitting ${omittedFindingCount}. Relation to the task is not determined.` : status === "clean" ? "No application warnings or errors were emitted during the recipe run." : "Application diagnostics were unavailable for this run.") + captureNote;
|
|
183
|
-
return { diagnostics: {
|
|
184
|
-
schemaVersion: 1,
|
|
185
|
-
scope: "recipe-run-application",
|
|
186
|
-
status,
|
|
187
|
-
nonBlocking: true,
|
|
188
|
-
note,
|
|
189
|
-
source,
|
|
190
|
-
counts,
|
|
191
|
-
...allowlist ? { allowlist: { entries: allowlist.entries.length, applied, problems: allowlist.problems } } : {},
|
|
192
|
-
...capture ? { capture } : {},
|
|
193
|
-
displayedFindingCount: findings.length,
|
|
194
|
-
omittedFindingCount,
|
|
195
|
-
findings
|
|
196
|
-
}, rawAppLog };
|
|
197
|
-
}
|
|
198
|
-
function classifyRecord(record) {
|
|
199
|
-
const classified = classifyConsoleRecord(record);
|
|
200
|
-
if (!classified) return null;
|
|
201
|
-
const line = record.line;
|
|
202
|
-
const signature = messageSignature(redactDiagnosticText(classified.message));
|
|
203
|
-
return {
|
|
204
|
-
...makeFinding(classified.level, line.trim(), `${classified.source}|${signature}`),
|
|
205
|
-
source: classified.source,
|
|
206
|
-
signature: redactPreview(signature),
|
|
207
|
-
firstFrame: classified.firstFrame ? redactPreview(classified.firstFrame) : null,
|
|
208
|
-
event: classified.key
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
function walletLogFindings(walletLog) {
|
|
212
|
-
const stat = safeStat(walletLog.path);
|
|
213
|
-
if (!stat) return [];
|
|
214
|
-
const sameFile = walletLog.inode === void 0 || walletLog.inode === stat.ino;
|
|
215
|
-
const start = sameFile && stat.size >= walletLog.offset ? walletLog.offset : 0;
|
|
216
|
-
const findings = [];
|
|
217
|
-
for (const line of readSlice(walletLog.path, start, stat.size - start).toString("utf8").split("\n")) {
|
|
218
|
-
let entry;
|
|
219
|
-
try {
|
|
220
|
-
entry = JSON.parse(line);
|
|
221
|
-
} catch {
|
|
222
|
-
continue;
|
|
223
|
-
}
|
|
224
|
-
if (!isRecord(entry)) continue;
|
|
225
|
-
let text = null;
|
|
226
|
-
if (entry.kind === "blocked-mainnet" && entry.probe !== true) text = `blocked-mainnet: ${String(entry.transport ?? "request")} to ${String(entry.url ?? "?")}`;
|
|
227
|
-
else if (entry.kind === "refused-mainnet") text = `refused-mainnet: ${String(entry.method ?? "?")} (${String(entry.reason ?? "?")})`;
|
|
228
|
-
else if (entry.kind === "unattributed") text = `unattributed wallet ${entry.binding === "request" ? "request" : "log entry"}${typeof entry.method === "string" ? ` (${entry.method})` : ""}`;
|
|
229
|
-
if (text) findings.push({ ...makeFinding("error", text, `wallet|${text}`), source: "wallet", signature: text, firstFrame: null });
|
|
230
|
-
}
|
|
231
|
-
return findings;
|
|
232
|
-
}
|
|
233
|
-
function classifyBufferedIssue(value) {
|
|
234
|
-
if (!isRecord(value) || typeof value.text !== "string") return null;
|
|
235
|
-
const rawLevel = String(value.level ?? "").toLowerCase();
|
|
236
|
-
const level = rawLevel === "warn" || rawLevel === "warning" ? "warning" : rawLevel === "error" ? "error" : rawLevel === "exception" ? "exception" : null;
|
|
237
|
-
return level ? makeFinding(level, value.text) : null;
|
|
238
|
-
}
|
|
239
|
-
function makeFinding(level, text, groupKey) {
|
|
240
|
-
const preview = redactPreview(text.trim());
|
|
241
|
-
const identity = groupKey ?? preview.replace(/\b\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?\b/gu, "[TIME]").replace(/\[(?:sw|page:[^\]]+|console:[^\]]+)\]/gu, "[APP]").replace(/^(?:(?:WARN(?:ING)?|ERROR|EXCEPTION|\[TIME\]|\[APP\])\s*)+/u, "");
|
|
242
|
-
return {
|
|
243
|
-
level,
|
|
244
|
-
fingerprint: createHash("sha256").update(`${level}|${identity}`).digest("hex").slice(0, 12),
|
|
245
|
-
preview
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
function applyConsoleAllowlist(findings, allowlist) {
|
|
249
|
-
if (!allowlist) return;
|
|
250
|
-
for (const finding of findings) {
|
|
251
|
-
if (!finding.event) continue;
|
|
252
|
-
const match = allowlistMatch({ source: finding.source, key: finding.event }, allowlist);
|
|
253
|
-
if (!match) continue;
|
|
254
|
-
finding.allowlisted = { reason: match.reason, entry: match.index, file: match.file, ...match.temporary ? { temporary: true } : {} };
|
|
255
|
-
finding.level = "info";
|
|
256
|
-
finding.fingerprint = createHash("sha256").update(`${finding.fingerprint}|info|${match.file}#${match.index}`).digest("hex").slice(0, 12);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
function redactPreview(value) {
|
|
260
|
-
return redactDiagnosticText(value).slice(0, MAX_PREVIEW_CHARS);
|
|
261
|
-
}
|
|
262
|
-
function redactDiagnosticText(value) {
|
|
263
|
-
return value.replace(/\b(Bearer)\s+\S+/giu, "$1 [REDACTED]").replace(/(["'])(api[-_]?key|password|passphrase|mnemonic|seed(?:Phrase)?|privateKey|secret|token|authorization|vault)\1\s*:\s*(?:"[^"]*"|'[^']*'|[^,\s}\]]+)/giu, '$1$2$1:"[REDACTED]"').replace(/\b(api[-_]?key|password|passphrase|mnemonic|seed(?:Phrase)?|privateKey|secret|token|authorization|vault)\b\s*[:=]\s*(?:"[^"]*"|'[^']*'|\S+)/giu, "$1=[REDACTED]").replace(/\b(?:0x)?[a-f0-9]{64,}\b/giu, "[REDACTED_HEX]").replace(/(https?:\/\/[^\s?]+)\?\S+/giu, "$1?[REDACTED_QUERY]");
|
|
264
|
-
}
|
|
265
|
-
function dedupeFindings(findings) {
|
|
266
|
-
const byFingerprint = /* @__PURE__ */ new Map();
|
|
267
|
-
for (const finding of findings) {
|
|
268
|
-
const existing = byFingerprint.get(finding.fingerprint);
|
|
269
|
-
if (existing) existing.count += 1;
|
|
270
|
-
else byFingerprint.set(finding.fingerprint, { ...finding, count: 1 });
|
|
271
|
-
}
|
|
272
|
-
return [...byFingerprint.values()];
|
|
273
|
-
}
|
|
274
|
-
function countFindings(findings) {
|
|
275
|
-
const counts = { total: 0, warning: 0, error: 0, exception: 0, info: 0 };
|
|
276
|
-
for (const finding of findings) {
|
|
277
|
-
counts[finding.level] += 1;
|
|
278
|
-
if (finding.level !== "info") counts.total += 1;
|
|
279
|
-
}
|
|
280
|
-
return counts;
|
|
281
|
-
}
|
|
282
|
-
function stripInternal(finding) {
|
|
283
|
-
const { event: _event, ...rest } = finding;
|
|
284
|
-
return rest;
|
|
285
|
-
}
|
|
286
|
-
function readSlice(filePath, offset, length) {
|
|
287
|
-
const fd = fs.openSync(filePath, "r");
|
|
288
|
-
try {
|
|
289
|
-
const buffer = Buffer.alloc(length);
|
|
290
|
-
const bytesRead = fs.readSync(fd, buffer, 0, length, offset);
|
|
291
|
-
return buffer.subarray(0, bytesRead);
|
|
292
|
-
} finally {
|
|
293
|
-
fs.closeSync(fd);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
function safeStat(filePath) {
|
|
297
|
-
try {
|
|
298
|
-
return fs.statSync(filePath);
|
|
299
|
-
} catch {
|
|
300
|
-
return null;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
function indexDiagnosticArtifact(manifestPath) {
|
|
304
|
-
const manifest = readJsonRecord(manifestPath);
|
|
305
|
-
if (!manifest) return;
|
|
306
|
-
const artifacts = Array.isArray(manifest.artifacts) ? manifest.artifacts : [];
|
|
307
|
-
manifest.artifacts = [
|
|
308
|
-
...artifacts.filter((artifact) => !isRecord(artifact) || !["diagnostics.json", RAW_APP_LOG_ARTIFACT].includes(String(artifact.path))),
|
|
309
|
-
{
|
|
310
|
-
path: "diagnostics.json",
|
|
311
|
-
type: "json",
|
|
312
|
-
label: "Run-scoped application diagnostics",
|
|
313
|
-
category: "diagnostic"
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
path: RAW_APP_LOG_ARTIFACT,
|
|
317
|
-
type: "log",
|
|
318
|
-
label: "Redacted run-scoped application log slice",
|
|
319
|
-
category: "diagnostic"
|
|
320
|
-
}
|
|
321
|
-
];
|
|
322
|
-
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
323
|
-
`);
|
|
324
|
-
}
|
|
325
|
-
function indexDiagnosticSummary(summaryPath, diagnostics) {
|
|
326
|
-
const summary = readJsonRecord(summaryPath);
|
|
327
|
-
if (!summary) return;
|
|
328
|
-
summary.sideFindings = {
|
|
329
|
-
status: diagnostics.status,
|
|
330
|
-
nonBlocking: true,
|
|
331
|
-
counts: diagnostics.counts,
|
|
332
|
-
diagnosticsPath: "diagnostics.json",
|
|
333
|
-
appLogPath: RAW_APP_LOG_ARTIFACT,
|
|
334
|
-
appLogRedacted: true
|
|
335
|
-
};
|
|
336
|
-
fs.writeFileSync(summaryPath, `${JSON.stringify(summary, null, 2)}
|
|
337
|
-
`);
|
|
338
|
-
}
|
|
339
|
-
function recipeLibraryRoots(summaryPath) {
|
|
340
|
-
const summary = readJsonRecord(summaryPath);
|
|
341
|
-
const libraries = isRecord(summary?.recipeLibraries) ? summary.recipeLibraries : null;
|
|
342
|
-
const sources = Array.isArray(libraries?.sources) ? libraries.sources : [];
|
|
343
|
-
const roots = sources.map((source) => isRecord(source) && typeof source.root === "string" ? source.root : null).filter((root) => Boolean(root));
|
|
344
|
-
return roots.length ? roots : libraryRootsFromEnv();
|
|
345
|
-
}
|
|
346
|
-
function readJsonRecord(filePath) {
|
|
347
|
-
try {
|
|
348
|
-
const value = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
349
|
-
return isRecord(value) ? value : null;
|
|
350
|
-
} catch {
|
|
351
|
-
return null;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
function isRecord(value) {
|
|
355
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
356
|
-
}
|
|
357
|
-
export {
|
|
358
|
-
beginRunDiagnostics,
|
|
359
|
-
collectRunDiagnostics,
|
|
360
|
-
finishRunDiagnostics,
|
|
361
|
-
formatRunDiagnosticsForHuman,
|
|
362
|
-
readRunDiagnosticsDocument,
|
|
363
|
-
verifyConsoleCapture
|
|
364
|
-
};
|