@deeeed/metamask-harness 0.78.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 +55 -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/ensure-runner-deps.sh +2 -2
- package/adapters/shared/harness-source-fingerprint.mjs +13 -8
- package/adapters/shared/open-debug.mjs +2 -2
- package/adapters/shared/runner-deps-ready.mjs +1 -1
- package/adapters/shared/sync-wallet-fixture.sh +8 -5
- package/adapters/terminal/cleanup.mjs +2 -1
- package/adapters/terminal/inject.mjs +2 -1
- package/adapters/terminal/lib/cdp-client.mjs +1 -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 +3 -3
- package/dist/adapters/core/surface.js +22 -5
- 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 +3 -2
- package/dist/adapters/extension/runtime.js +9 -5
- package/dist/adapters/extension/surface.js +20 -5
- 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 +4 -5
- 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 +9 -8
- package/dist/adapters/mobile/runtime-decision.js +5 -7
- package/dist/adapters/mobile/source-freshness.js +22 -126
- package/dist/adapters/mobile/surface.js +31 -6
- 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/registry.js +2 -14
- 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 +12 -4
- 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 +3 -9
- 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 +6 -11
- 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/discovery.js +1 -1
- package/dist/doctor.js +3 -4
- 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 +48 -78
- package/dist/performance-observation.js +2 -2
- package/dist/progress.js +1 -1
- package/dist/recipe-assistance-context.js +2 -4
- package/dist/recipe-security.js +2 -59
- package/dist/review/knowledge.js +3 -3
- package/dist/runner.js +5 -3
- package/dist/runtime-context.js +1 -1
- package/dist/task-view.js +3 -4
- package/docs/CONTRIBUTING.md +3 -3
- 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 -8
- 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
|
@@ -1,446 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('node:fs');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const { Buffer } = require('node:buffer');
|
|
6
|
-
|
|
7
|
-
// Shape of a single captured issue:
|
|
8
|
-
// { level, channel, source, targetRole, targetUrl, text, timestamp, allowlistMatch }
|
|
9
|
-
// level: 'warning' | 'error' | 'exception' | 'other'
|
|
10
|
-
// channel: 'console' | 'exception' | 'metro'
|
|
11
|
-
// source: 'app' | 'metro-log'
|
|
12
|
-
|
|
13
|
-
const LEVEL_WARNING = 'warning';
|
|
14
|
-
const LEVEL_ERROR = 'error';
|
|
15
|
-
const LEVEL_EXCEPTION = 'exception';
|
|
16
|
-
const LEVEL_OTHER = 'other';
|
|
17
|
-
|
|
18
|
-
const REVIEW_LEVELS = new Set([LEVEL_WARNING, LEVEL_ERROR, LEVEL_EXCEPTION]);
|
|
19
|
-
const TOP_ISSUES_LIMIT = 4;
|
|
20
|
-
|
|
21
|
-
const METRO_PATTERNS = [
|
|
22
|
-
{ level: LEVEL_EXCEPTION, re: /\b(Uncaught|unhandledRejection|FATAL EXCEPTION|UnhandledPromiseRejection)\b/i },
|
|
23
|
-
{ level: LEVEL_ERROR, re: /(^|\s)(\[error\]|ERROR |Error:)/i },
|
|
24
|
-
{ level: LEVEL_WARNING, re: /(^|\s)(\[warn\]|WARN |Warning:)/i },
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
function classifyMetroLine(line) {
|
|
28
|
-
if (!line) {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
const trimmed = line.trim();
|
|
32
|
-
if (!trimmed) {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
for (const { level, re } of METRO_PATTERNS) {
|
|
36
|
-
if (re.test(trimmed)) {
|
|
37
|
-
return { level, text: trimmed };
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function captureFromMetro(logPath, startOffset) {
|
|
44
|
-
const issues = [];
|
|
45
|
-
if (!logPath || !fs.existsSync(logPath)) {
|
|
46
|
-
return issues;
|
|
47
|
-
}
|
|
48
|
-
let stat;
|
|
49
|
-
try {
|
|
50
|
-
stat = fs.statSync(logPath);
|
|
51
|
-
} catch {
|
|
52
|
-
return issues;
|
|
53
|
-
}
|
|
54
|
-
const start = Math.max(0, Math.min(startOffset || 0, stat.size));
|
|
55
|
-
if (stat.size <= start) {
|
|
56
|
-
return issues;
|
|
57
|
-
}
|
|
58
|
-
let slice = '';
|
|
59
|
-
try {
|
|
60
|
-
const fd = fs.openSync(logPath, 'r');
|
|
61
|
-
try {
|
|
62
|
-
const len = stat.size - start;
|
|
63
|
-
const buf = Buffer.alloc(len);
|
|
64
|
-
fs.readSync(fd, buf, 0, len, start);
|
|
65
|
-
slice = buf.toString('utf8');
|
|
66
|
-
} finally {
|
|
67
|
-
fs.closeSync(fd);
|
|
68
|
-
}
|
|
69
|
-
} catch {
|
|
70
|
-
return issues;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const lines = slice.split(/\r?\n/);
|
|
74
|
-
for (const line of lines) {
|
|
75
|
-
const classification = classifyMetroLine(line);
|
|
76
|
-
if (!classification) {
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
issues.push({
|
|
80
|
-
level: classification.level,
|
|
81
|
-
channel: 'metro',
|
|
82
|
-
source: 'metro-log',
|
|
83
|
-
targetRole: 'app',
|
|
84
|
-
targetUrl: 'metro://local',
|
|
85
|
-
text: classification.text.slice(0, 2000),
|
|
86
|
-
timestamp: new Date().toISOString(),
|
|
87
|
-
allowlistMatch: null,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
return issues;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function normalizeAppBufferEntries(entries) {
|
|
94
|
-
if (!Array.isArray(entries)) {
|
|
95
|
-
return [];
|
|
96
|
-
}
|
|
97
|
-
return entries
|
|
98
|
-
.map((entry) => {
|
|
99
|
-
if (!entry || typeof entry !== 'object') {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
const rawLevel = String(entry.level || '').toLowerCase();
|
|
103
|
-
let level = LEVEL_OTHER;
|
|
104
|
-
if (rawLevel === 'warn' || rawLevel === 'warning') {
|
|
105
|
-
level = LEVEL_WARNING;
|
|
106
|
-
} else if (rawLevel === 'error') {
|
|
107
|
-
level = LEVEL_ERROR;
|
|
108
|
-
} else if (rawLevel === 'exception') {
|
|
109
|
-
level = LEVEL_EXCEPTION;
|
|
110
|
-
}
|
|
111
|
-
const text = String(entry.text || '').slice(0, 2000);
|
|
112
|
-
if (!text) {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
return {
|
|
116
|
-
level,
|
|
117
|
-
channel: level === LEVEL_EXCEPTION ? 'exception' : 'console',
|
|
118
|
-
source: 'app',
|
|
119
|
-
targetRole: 'app',
|
|
120
|
-
targetUrl: 'hermes://metamask',
|
|
121
|
-
text,
|
|
122
|
-
timestamp: entry.t ? new Date(Number(entry.t)).toISOString() : new Date().toISOString(),
|
|
123
|
-
allowlistMatch: null,
|
|
124
|
-
};
|
|
125
|
-
})
|
|
126
|
-
.filter(Boolean);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function dedupeIssues(issues) {
|
|
130
|
-
const seen = new Map();
|
|
131
|
-
for (const issue of issues) {
|
|
132
|
-
const key = `${issue.level}|${issue.text.slice(0, 300)}`;
|
|
133
|
-
const prior = seen.get(key);
|
|
134
|
-
if (prior) {
|
|
135
|
-
prior.count += 1;
|
|
136
|
-
// Prefer the in-app capture over the Metro mirror when both channels
|
|
137
|
-
// report the same logical issue text.
|
|
138
|
-
if (prior.source !== 'app' && issue.source === 'app') {
|
|
139
|
-
Object.assign(prior, issue, { count: prior.count });
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
seen.set(key, { ...issue, count: 1 });
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return Array.from(seen.values());
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function applyAllowlist(issues, allowlist) {
|
|
149
|
-
const unexpected = [];
|
|
150
|
-
const informational = [];
|
|
151
|
-
const rules = Array.isArray(allowlist) ? allowlist : [];
|
|
152
|
-
for (const issue of issues) {
|
|
153
|
-
let match = null;
|
|
154
|
-
for (const rule of rules) {
|
|
155
|
-
if (rule && rule.level && rule.level !== issue.level) {
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
if (rule && rule.textMatch) {
|
|
159
|
-
try {
|
|
160
|
-
const re = new RegExp(rule.textMatch);
|
|
161
|
-
if (re.test(issue.text)) {
|
|
162
|
-
match = rule.reason || rule.textMatch;
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
} catch {
|
|
166
|
-
if (issue.text.includes(rule.textMatch)) {
|
|
167
|
-
match = rule.reason || rule.textMatch;
|
|
168
|
-
break;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
if (match) {
|
|
174
|
-
informational.push({ ...issue, allowlistMatch: match });
|
|
175
|
-
} else if (REVIEW_LEVELS.has(issue.level)) {
|
|
176
|
-
unexpected.push(issue);
|
|
177
|
-
} else {
|
|
178
|
-
informational.push(issue);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return { unexpected, informational };
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function countByLevel(issues) {
|
|
185
|
-
const counts = { total: 0, warning: 0, error: 0, exception: 0, other: 0 };
|
|
186
|
-
for (const issue of issues) {
|
|
187
|
-
counts.total += 1;
|
|
188
|
-
if (counts[issue.level] !== undefined) {
|
|
189
|
-
counts[issue.level] += 1;
|
|
190
|
-
} else {
|
|
191
|
-
counts.other += 1;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return counts;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function matchesFailOn(issue, failOn) {
|
|
198
|
-
if (!failOn || typeof failOn !== 'object') {
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
const levels = Array.isArray(failOn.levels) ? failOn.levels : [];
|
|
202
|
-
const textMatches = Array.isArray(failOn.textMatches) ? failOn.textMatches : [];
|
|
203
|
-
if (levels.length && !levels.includes(issue.level)) {
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
if (!textMatches.length) {
|
|
207
|
-
return levels.length > 0;
|
|
208
|
-
}
|
|
209
|
-
for (const pattern of textMatches) {
|
|
210
|
-
try {
|
|
211
|
-
if (new RegExp(pattern).test(issue.text)) {
|
|
212
|
-
return true;
|
|
213
|
-
}
|
|
214
|
-
} catch {
|
|
215
|
-
if (issue.text.includes(pattern)) {
|
|
216
|
-
return true;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function computeReview(unexpected, informational, failOn, artifactFiles) {
|
|
224
|
-
const observedDeduped = dedupeIssues(unexpected);
|
|
225
|
-
const observed = countByLevel(observedDeduped);
|
|
226
|
-
const gatingIssues = observedDeduped.filter((issue) => matchesFailOn(issue, failOn));
|
|
227
|
-
const gating = countByLevel(gatingIssues);
|
|
228
|
-
const infoCount = Array.isArray(informational) ? informational.length : 0;
|
|
229
|
-
|
|
230
|
-
let status = 'clean';
|
|
231
|
-
let note = 'No unexpected warnings, errors, or exceptions observed.';
|
|
232
|
-
if (gating.total > 0) {
|
|
233
|
-
status = 'gating';
|
|
234
|
-
note = `Observed ${gating.total} unexpected warning/error/exception event(s) matching fail_on_unexpected. Recipe marked as failing.`;
|
|
235
|
-
} else if (observed.total > 0) {
|
|
236
|
-
status = 'review';
|
|
237
|
-
note = `Observed ${observed.total} unexpected warning/error/exception event(s) during validation. Relation to the recipe or current change is not determined; review the artifacts.`;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const ranked = observedDeduped.slice().sort((a, b) => {
|
|
241
|
-
if (b.count !== a.count) {
|
|
242
|
-
return b.count - a.count;
|
|
243
|
-
}
|
|
244
|
-
return (a.text || '').localeCompare(b.text || '');
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
const topIssues = ranked.slice(0, TOP_ISSUES_LIMIT).map((issue) => ({
|
|
248
|
-
level: issue.level,
|
|
249
|
-
channel: issue.channel,
|
|
250
|
-
source: issue.source,
|
|
251
|
-
targetRole: issue.targetRole,
|
|
252
|
-
targetUrl: issue.targetUrl,
|
|
253
|
-
text: issue.text,
|
|
254
|
-
allowlistMatch: issue.allowlistMatch,
|
|
255
|
-
}));
|
|
256
|
-
|
|
257
|
-
return {
|
|
258
|
-
status,
|
|
259
|
-
note,
|
|
260
|
-
observed,
|
|
261
|
-
gating,
|
|
262
|
-
informational: { total: infoCount },
|
|
263
|
-
topIssues,
|
|
264
|
-
artifactFiles: artifactFiles || {},
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function renderMarkdown(review) {
|
|
269
|
-
const { status, note, observed, gating, informational, topIssues, artifactFiles } = review;
|
|
270
|
-
const lines = [];
|
|
271
|
-
lines.push('# Recipe Issue Review');
|
|
272
|
-
lines.push('');
|
|
273
|
-
lines.push(`Status: ${status}`);
|
|
274
|
-
lines.push('');
|
|
275
|
-
lines.push(note);
|
|
276
|
-
lines.push('');
|
|
277
|
-
lines.push('Observed:');
|
|
278
|
-
lines.push(`- warnings: ${observed.warning || 0}`);
|
|
279
|
-
lines.push(`- errors: ${observed.error || 0}`);
|
|
280
|
-
lines.push(`- exceptions: ${observed.exception || 0}`);
|
|
281
|
-
lines.push(`- total: ${observed.total || 0}`);
|
|
282
|
-
lines.push('');
|
|
283
|
-
lines.push('Gating:');
|
|
284
|
-
lines.push(`- warnings: ${gating.warning || 0}`);
|
|
285
|
-
lines.push(`- errors: ${gating.error || 0}`);
|
|
286
|
-
lines.push(`- exceptions: ${gating.exception || 0}`);
|
|
287
|
-
lines.push(`- total: ${gating.total || 0}`);
|
|
288
|
-
lines.push('');
|
|
289
|
-
lines.push(`Informational-only events: ${(informational && informational.total) || 0}`);
|
|
290
|
-
lines.push('');
|
|
291
|
-
if (topIssues && topIssues.length) {
|
|
292
|
-
lines.push('Top issues:');
|
|
293
|
-
for (const issue of topIssues) {
|
|
294
|
-
const levelLabel = String(issue.level || 'other').toUpperCase();
|
|
295
|
-
lines.push(`- [${levelLabel}] ${issue.source}: ${issue.text}`);
|
|
296
|
-
}
|
|
297
|
-
lines.push('');
|
|
298
|
-
}
|
|
299
|
-
if (artifactFiles && Object.keys(artifactFiles).length) {
|
|
300
|
-
lines.push('Artifacts:');
|
|
301
|
-
for (const key of ['allIssues', 'consoleWarnings', 'consoleErrors', 'runtimeExceptions']) {
|
|
302
|
-
if (artifactFiles[key]) {
|
|
303
|
-
lines.push(`- ${artifactFiles[key]}`);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
lines.push('');
|
|
307
|
-
}
|
|
308
|
-
return lines.join('\n');
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function writeJsonArtifact(filePath, data) {
|
|
312
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
313
|
-
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function writeArtifacts(runDir, { unexpected, informational, review }) {
|
|
317
|
-
const outDir = path.resolve(runDir);
|
|
318
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
319
|
-
|
|
320
|
-
const all = [...(unexpected || []), ...(informational || [])];
|
|
321
|
-
|
|
322
|
-
const paths = {
|
|
323
|
-
allIssues: path.join(outDir, 'agentic-issues.json'),
|
|
324
|
-
consoleWarnings: path.join(outDir, 'console-warnings.json'),
|
|
325
|
-
consoleErrors: path.join(outDir, 'console-errors.json'),
|
|
326
|
-
runtimeExceptions: path.join(outDir, 'runtime-exceptions.json'),
|
|
327
|
-
reviewJson: path.join(outDir, 'agentic-issues-review.json'),
|
|
328
|
-
reviewMd: path.join(outDir, 'agentic-issues-review.md'),
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
writeJsonArtifact(paths.allIssues, all);
|
|
332
|
-
writeJsonArtifact(
|
|
333
|
-
paths.consoleWarnings,
|
|
334
|
-
all.filter((i) => i.level === LEVEL_WARNING),
|
|
335
|
-
);
|
|
336
|
-
writeJsonArtifact(
|
|
337
|
-
paths.consoleErrors,
|
|
338
|
-
all.filter((i) => i.level === LEVEL_ERROR),
|
|
339
|
-
);
|
|
340
|
-
writeJsonArtifact(
|
|
341
|
-
paths.runtimeExceptions,
|
|
342
|
-
all.filter((i) => i.level === LEVEL_EXCEPTION),
|
|
343
|
-
);
|
|
344
|
-
|
|
345
|
-
const reviewWithPaths = { ...review, artifactFiles: paths };
|
|
346
|
-
writeJsonArtifact(paths.reviewJson, reviewWithPaths);
|
|
347
|
-
fs.writeFileSync(paths.reviewMd, renderMarkdown(reviewWithPaths));
|
|
348
|
-
|
|
349
|
-
return paths;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Installed into the Hermes runtime via Runtime.evaluate. Returns a string
|
|
353
|
-
// so callers can embed it directly into eval expressions. Keeps the hook
|
|
354
|
-
// idempotent and bounded (500-entry cap).
|
|
355
|
-
function buildArmSnippet() {
|
|
356
|
-
return `(() => {
|
|
357
|
-
var g = globalThis;
|
|
358
|
-
if (g.__AGENTIC_ISSUES_INSTALLED__) { return { installed: true, reason: 'already-installed' }; }
|
|
359
|
-
g.__AGENTIC_ISSUES__ = [];
|
|
360
|
-
g.__AGENTIC_ISSUES_CAP__ = 500;
|
|
361
|
-
var push = function (entry) {
|
|
362
|
-
try {
|
|
363
|
-
if (g.__AGENTIC_ISSUES__.length < g.__AGENTIC_ISSUES_CAP__) {
|
|
364
|
-
g.__AGENTIC_ISSUES__.push(entry);
|
|
365
|
-
}
|
|
366
|
-
} catch (_e) {}
|
|
367
|
-
};
|
|
368
|
-
['warn', 'error'].forEach(function (k) {
|
|
369
|
-
var orig = console[k] && console[k].bind(console);
|
|
370
|
-
if (!orig) return;
|
|
371
|
-
console[k] = function () {
|
|
372
|
-
var args = Array.prototype.slice.call(arguments);
|
|
373
|
-
try {
|
|
374
|
-
var text = args.map(function (a) {
|
|
375
|
-
if (a && a.stack) return String(a.stack);
|
|
376
|
-
if (typeof a === 'string') return a;
|
|
377
|
-
try { return JSON.stringify(a); } catch (_e) { return String(a); }
|
|
378
|
-
}).join(' ');
|
|
379
|
-
push({ t: Date.now(), level: k, text: text });
|
|
380
|
-
} catch (_e) {}
|
|
381
|
-
return orig.apply(console, args);
|
|
382
|
-
};
|
|
383
|
-
});
|
|
384
|
-
if (typeof g.addEventListener === 'function') {
|
|
385
|
-
try {
|
|
386
|
-
g.addEventListener('error', function (e) {
|
|
387
|
-
var err = e && (e.error || e.message);
|
|
388
|
-
var text = err && err.stack ? err.stack : String(err || e);
|
|
389
|
-
push({ t: Date.now(), level: 'exception', text: text });
|
|
390
|
-
});
|
|
391
|
-
g.addEventListener('unhandledrejection', function (e) {
|
|
392
|
-
var r = e && e.reason;
|
|
393
|
-
var text = r && r.stack ? r.stack : String(r || e);
|
|
394
|
-
push({ t: Date.now(), level: 'exception', text: text });
|
|
395
|
-
});
|
|
396
|
-
} catch (_e) {}
|
|
397
|
-
}
|
|
398
|
-
var ep = g.ErrorUtils;
|
|
399
|
-
if (ep && typeof ep.setGlobalHandler === 'function') {
|
|
400
|
-
try {
|
|
401
|
-
var priorHandler = typeof ep.getGlobalHandler === 'function' ? ep.getGlobalHandler() : null;
|
|
402
|
-
ep.setGlobalHandler(function (err, isFatal) {
|
|
403
|
-
try {
|
|
404
|
-
var text = err && err.stack ? err.stack : String(err);
|
|
405
|
-
push({ t: Date.now(), level: 'exception', text: (isFatal ? '[FATAL] ' : '') + text });
|
|
406
|
-
} catch (_e) {}
|
|
407
|
-
if (typeof priorHandler === 'function') {
|
|
408
|
-
try { priorHandler(err, isFatal); } catch (_e) {}
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
} catch (_e) {}
|
|
412
|
-
}
|
|
413
|
-
g.__AGENTIC_ISSUES_INSTALLED__ = true;
|
|
414
|
-
return { installed: true };
|
|
415
|
-
})()`;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function buildCollectSnippet() {
|
|
419
|
-
return `(() => {
|
|
420
|
-
var g = globalThis;
|
|
421
|
-
var buf = g.__AGENTIC_ISSUES__ || [];
|
|
422
|
-
var snapshot = buf.slice();
|
|
423
|
-
g.__AGENTIC_ISSUES__ = [];
|
|
424
|
-
return { count: snapshot.length, entries: snapshot };
|
|
425
|
-
})()`;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
module.exports = {
|
|
429
|
-
LEVEL_WARNING,
|
|
430
|
-
LEVEL_ERROR,
|
|
431
|
-
LEVEL_EXCEPTION,
|
|
432
|
-
LEVEL_OTHER,
|
|
433
|
-
TOP_ISSUES_LIMIT,
|
|
434
|
-
applyAllowlist,
|
|
435
|
-
buildArmSnippet,
|
|
436
|
-
buildCollectSnippet,
|
|
437
|
-
captureFromMetro,
|
|
438
|
-
classifyMetroLine,
|
|
439
|
-
computeReview,
|
|
440
|
-
countByLevel,
|
|
441
|
-
dedupeIssues,
|
|
442
|
-
matchesFailOn,
|
|
443
|
-
normalizeAppBufferEntries,
|
|
444
|
-
renderMarkdown,
|
|
445
|
-
writeArtifacts,
|
|
446
|
-
};
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Boundary-safe matcher for CDP bridge targets, shared by wait-for-bridge and the
|
|
4
|
-
// launch ready-path confirm. A launch (or its confirm) may only accept a target
|
|
5
|
-
// belonging to the REQUESTED platform — and the requested device when one is pinned.
|
|
6
|
-
//
|
|
7
|
-
// The requested platform is authoritative and is passed explicitly
|
|
8
|
-
// (WAIT_FOR_BRIDGE_PLATFORM), so it wins over env inference. On a dual-platform slot
|
|
9
|
-
// the other platform's device identity is ambiently injected (a slot-context
|
|
10
|
-
// IOS_SIMULATOR while launching android, or vice versa); without an explicit platform
|
|
11
|
-
// the matcher would fall back to that ambient identity and confirm against the wrong
|
|
12
|
-
// platform's target. Binding the platform up front closes that corner.
|
|
13
|
-
|
|
14
|
-
function matchesBridgeTarget(target, env) {
|
|
15
|
-
const e = env || process.env;
|
|
16
|
-
if (!target || typeof target !== 'object') return false;
|
|
17
|
-
|
|
18
|
-
const requirePlatform = e.WAIT_FOR_BRIDGE_PLATFORM || '';
|
|
19
|
-
// Requested platform is authoritative: never accept the other platform's target,
|
|
20
|
-
// whatever device env is ambiently present.
|
|
21
|
-
if (requirePlatform && target.platform !== requirePlatform) return false;
|
|
22
|
-
|
|
23
|
-
const androidName = e.ANDROID_TARGET_DEVICE_NAME || e.ANDROID_DEVICE || '';
|
|
24
|
-
const adbSerial = e.ADB_SERIAL || e.ANDROID_SERIAL || '';
|
|
25
|
-
const iosSimulator = e.IOS_SIMULATOR || '';
|
|
26
|
-
|
|
27
|
-
// Android device pin applies only when android is requested (or nothing constrains
|
|
28
|
-
// the platform); it must never redirect a platform-bound iOS confirm — a slot-context
|
|
29
|
-
// ADB_SERIAL while launching iOS must not reject every iOS target. Symmetric with the
|
|
30
|
-
// iOS-simulator branch below.
|
|
31
|
-
if ((adbSerial || androidName) && (!requirePlatform || requirePlatform === 'android')) {
|
|
32
|
-
// Require android + boundary-safe device-name match.
|
|
33
|
-
if (target.platform !== 'android') return false;
|
|
34
|
-
if (!androidName) return true;
|
|
35
|
-
const deviceName = String(target.deviceName || '');
|
|
36
|
-
return deviceName === androidName || deviceName.startsWith(`${androidName} -`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// iOS simulator pin applies only when iOS is requested (or nothing constrains the
|
|
40
|
-
// platform); it must never redirect a platform-bound android confirm.
|
|
41
|
-
if (iosSimulator && (!requirePlatform || requirePlatform === 'ios')) {
|
|
42
|
-
return target.platform !== 'android' && target.deviceName === iosSimulator;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// No device pin: the platform gate above is the only constraint.
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// True when at least one answering target matches the request AND carries a route
|
|
50
|
-
// (an in-app agentic bridge is live), not merely a registered debug target.
|
|
51
|
-
function hasMatchingRoute(value, env) {
|
|
52
|
-
const targets = Array.isArray(value) ? value : [value];
|
|
53
|
-
return targets.some(
|
|
54
|
-
(t) => matchesBridgeTarget(t, env) && t && t.agenticPresent === true && Boolean(t.route),
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Human summary of the targets that answered (platform / deviceName), for teaching on
|
|
59
|
-
// a timeout — e.g. an iOS target answering an android launch.
|
|
60
|
-
function describeTargets(value) {
|
|
61
|
-
const targets = (Array.isArray(value) ? value : [value]).filter((t) => t && typeof t === 'object');
|
|
62
|
-
if (!targets.length) return 'none';
|
|
63
|
-
return targets
|
|
64
|
-
.map((t) => `${t.platform || '?'}${t.deviceName ? `/'${t.deviceName}'` : ''}${t.route ? '' : ' (no route)'}`)
|
|
65
|
-
.join(', ');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Human summary of what the run is pinned to, for the same teaching line.
|
|
69
|
-
function describeRequested(env) {
|
|
70
|
-
const e = env || process.env;
|
|
71
|
-
const platform = e.WAIT_FOR_BRIDGE_PLATFORM || '';
|
|
72
|
-
const androidName = e.ANDROID_TARGET_DEVICE_NAME || e.ANDROID_DEVICE || '';
|
|
73
|
-
const adbSerial = e.ADB_SERIAL || e.ANDROID_SERIAL || '';
|
|
74
|
-
const iosSimulator = e.IOS_SIMULATOR || '';
|
|
75
|
-
// Explicit requested platform wins over device-env inference: an iOS-bound confirm
|
|
76
|
-
// with an ambient ADB_SERIAL is still iOS.
|
|
77
|
-
if (platform === 'android') return `android${androidName ? ` / ${androidName}` : ''}`;
|
|
78
|
-
if (platform === 'ios') return `ios${iosSimulator ? ` / ${iosSimulator}` : ''}`;
|
|
79
|
-
if (adbSerial || androidName) return `android${androidName ? ` / ${androidName}` : ''}`;
|
|
80
|
-
if (iosSimulator) return `ios${iosSimulator ? ` / ${iosSimulator}` : ''}`;
|
|
81
|
-
return 'any platform';
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
module.exports = { matchesBridgeTarget, hasMatchingRoute, describeTargets, describeRequested };
|