agent-scenario-loop 0.1.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/LICENSE +21 -0
- package/README.md +119 -0
- package/app/profile-session.ts +812 -0
- package/core/config-template.json +41 -0
- package/dist/core/agent-summary.d.ts +15 -0
- package/dist/core/agent-summary.js +177 -0
- package/dist/core/artifact-contract.d.ts +151 -0
- package/dist/core/artifact-contract.js +897 -0
- package/dist/core/artifact-layout.d.ts +56 -0
- package/dist/core/artifact-layout.js +61 -0
- package/dist/core/artifact-writer.d.ts +44 -0
- package/dist/core/artifact-writer.js +55 -0
- package/dist/core/comparison.d.ts +133 -0
- package/dist/core/comparison.js +294 -0
- package/dist/core/evidence-interpreter.d.ts +28 -0
- package/dist/core/evidence-interpreter.js +69 -0
- package/dist/core/execution-plan.d.ts +44 -0
- package/dist/core/execution-plan.js +95 -0
- package/dist/core/planner.d.ts +132 -0
- package/dist/core/planner.js +812 -0
- package/dist/core/ports.d.ts +198 -0
- package/dist/core/ports.js +146 -0
- package/dist/core/run-index.d.ts +62 -0
- package/dist/core/run-index.js +143 -0
- package/dist/core/schema-validator.d.ts +86 -0
- package/dist/core/schema-validator.js +407 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +27 -0
- package/dist/runner/agent-device-driver.d.ts +126 -0
- package/dist/runner/agent-device-driver.js +168 -0
- package/dist/runner/agent-device.d.ts +295 -0
- package/dist/runner/agent-device.js +1271 -0
- package/dist/runner/android-adb-driver.d.ts +175 -0
- package/dist/runner/android-adb-driver.js +399 -0
- package/dist/runner/android-adb.d.ts +254 -0
- package/dist/runner/android-adb.js +1618 -0
- package/dist/runner/argent-driver.d.ts +183 -0
- package/dist/runner/argent-driver.js +297 -0
- package/dist/runner/argent.d.ts +349 -0
- package/dist/runner/argent.js +1211 -0
- package/dist/runner/check-plan.d.ts +45 -0
- package/dist/runner/check-plan.js +210 -0
- package/dist/runner/cli.d.ts +20 -0
- package/dist/runner/cli.js +23 -0
- package/dist/runner/compare-latest.d.ts +99 -0
- package/dist/runner/compare-latest.js +233 -0
- package/dist/runner/compare.d.ts +58 -0
- package/dist/runner/compare.js +157 -0
- package/dist/runner/demo-loop.d.ts +45 -0
- package/dist/runner/demo-loop.js +170 -0
- package/dist/runner/example-android-live.d.ts +137 -0
- package/dist/runner/example-android-live.js +454 -0
- package/dist/runner/example-ios-live.d.ts +137 -0
- package/dist/runner/example-ios-live.js +471 -0
- package/dist/runner/host-doctor.d.ts +131 -0
- package/dist/runner/host-doctor.js +628 -0
- package/dist/runner/init-project.d.ts +88 -0
- package/dist/runner/init-project.js +263 -0
- package/dist/runner/ios-simctl-driver.d.ts +69 -0
- package/dist/runner/ios-simctl-driver.js +97 -0
- package/dist/runner/ios-simctl.d.ts +254 -0
- package/dist/runner/ios-simctl.js +1415 -0
- package/dist/runner/live-android.d.ts +137 -0
- package/dist/runner/live-android.js +539 -0
- package/dist/runner/live-comparison.d.ts +67 -0
- package/dist/runner/live-comparison.js +147 -0
- package/dist/runner/live-ios.d.ts +137 -0
- package/dist/runner/live-ios.js +460 -0
- package/dist/runner/live-proof-summary.d.ts +263 -0
- package/dist/runner/live-proof-summary.js +465 -0
- package/dist/runner/live-proof.d.ts +467 -0
- package/dist/runner/live-proof.js +920 -0
- package/dist/runner/local-env.d.ts +64 -0
- package/dist/runner/local-env.js +155 -0
- package/dist/runner/profile-android.d.ts +82 -0
- package/dist/runner/profile-android.js +671 -0
- package/dist/runner/profile-ios.d.ts +108 -0
- package/dist/runner/profile-ios.js +532 -0
- package/dist/runner/profile-mobile.d.ts +254 -0
- package/dist/runner/profile-mobile.js +1307 -0
- package/dist/runner/validate-project.d.ts +273 -0
- package/dist/runner/validate-project.js +1501 -0
- package/docs/adapters.md +145 -0
- package/docs/api.md +94 -0
- package/docs/authoring.md +196 -0
- package/docs/concepts.md +136 -0
- package/docs/consumer-rehearsal.md +115 -0
- package/docs/contracts.md +267 -0
- package/docs/live-proofs.md +270 -0
- package/docs/principles.md +46 -0
- package/examples/event-logs/app-startup-baseline.log +4 -0
- package/examples/event-logs/app-startup-current.log +4 -0
- package/examples/minimal-app/README.md +70 -0
- package/examples/mobile-app/README.md +302 -0
- package/examples/mobile-app/app.json +22 -0
- package/examples/mobile-app/asl/package-scripts.json +32 -0
- package/examples/mobile-app/asl.config.json +37 -0
- package/examples/mobile-app/event-logs/android-app-startup.log +4 -0
- package/examples/mobile-app/event-logs/android-open-close-cycle.log +12 -0
- package/examples/mobile-app/event-logs/android-scroll-settle.log +12 -0
- package/examples/mobile-app/event-logs/app-startup.log +4 -0
- package/examples/mobile-app/event-logs/open-close-cycle.log +12 -0
- package/examples/mobile-app/event-logs/scroll-settle.log +12 -0
- package/examples/mobile-app/index.ts +20 -0
- package/examples/mobile-app/metro.config.js +20 -0
- package/examples/mobile-app/package.json +62 -0
- package/examples/mobile-app/patches/expo-modules-jsi@56.0.10.patch +19 -0
- package/examples/mobile-app/plugins/with-ios-build-compat.js +271 -0
- package/examples/mobile-app/pnpm-lock.yaml +4440 -0
- package/examples/mobile-app/runner-manifests/evidence-provider.json +79 -0
- package/examples/mobile-app/runner-manifests/primary-runner.json +19 -0
- package/examples/mobile-app/scenarios/android/app-startup-video.json +73 -0
- package/examples/mobile-app/scenarios/android/app-startup.json +44 -0
- package/examples/mobile-app/scenarios/android/open-close-cycle.json +54 -0
- package/examples/mobile-app/scenarios/android/scroll-settle.json +49 -0
- package/examples/mobile-app/scenarios/ios/app-startup.json +44 -0
- package/examples/mobile-app/scenarios/ios/open-close-cycle.json +54 -0
- package/examples/mobile-app/scenarios/ios/scroll-settle.json +49 -0
- package/examples/mobile-app/scenarios/mobile/app-startup.json +91 -0
- package/examples/mobile-app/scenarios/mobile/open-close-cycle.json +160 -0
- package/examples/mobile-app/scenarios/mobile/scroll-settle.json +148 -0
- package/examples/mobile-app/scripts/asl-capture-accessibility-provider.mjs +112 -0
- package/examples/mobile-app/scripts/asl-capture-profiler-provider.mjs +127 -0
- package/examples/mobile-app/src/devtools/profile-session.ts +7 -0
- package/examples/mobile-app/src/example-screen.tsx +322 -0
- package/examples/mobile-app/tsconfig.json +16 -0
- package/examples/mobile-app/tsconfig.typecheck.json +13 -0
- package/examples/runners/README.md +44 -0
- package/examples/runners/adb-android.json +25 -0
- package/examples/runners/agent-device-android.json +27 -0
- package/examples/runners/agent-device-ios.json +27 -0
- package/examples/runners/argent-android.json +32 -0
- package/examples/runners/argent-ios.json +32 -0
- package/examples/runners/argent-react-profiler-provider.json +15 -0
- package/examples/runners/axe-accessibility-provider.json +24 -0
- package/examples/runners/manual-log-ingest.json +9 -0
- package/examples/runners/rozenite-profiler-provider.json +9 -0
- package/examples/runners/script-accessibility-provider.json +24 -0
- package/examples/runners/script-memory-provider.json +24 -0
- package/examples/runners/script-network-provider.json +24 -0
- package/examples/runners/script-profiler-provider.json +30 -0
- package/examples/runners/xcodebuildmcp-ios.json +29 -0
- package/examples/scenarios/ios/app-startup.json +28 -0
- package/examples/scenarios/ios/open-close-cycle.json +35 -0
- package/examples/scenarios/mobile/app-startup.json +72 -0
- package/examples/scenarios/mobile/media-open-close.json +141 -0
- package/examples/scenarios/mobile/open-close-cycle.json +135 -0
- package/examples/scenarios/mobile/scroll-settle.json +106 -0
- package/package.json +240 -0
- package/schemas/budget-verdict.schema.json +115 -0
- package/schemas/causal-run.schema.json +279 -0
- package/schemas/comparison.schema.json +196 -0
- package/schemas/health.schema.json +108 -0
- package/schemas/live-proof-set.schema.json +195 -0
- package/schemas/live-proof.schema.json +413 -0
- package/schemas/manifest.schema.json +204 -0
- package/schemas/metrics.schema.json +137 -0
- package/schemas/project-validation.schema.json +343 -0
- package/schemas/runner-capabilities.schema.json +217 -0
- package/schemas/scenario.schema.json +400 -0
- package/schemas/verdict.schema.json +88 -0
- package/templates/evidence-provider.json +83 -0
- package/templates/gitignore-snippet +9 -0
- package/templates/integration-readme.md +125 -0
- package/templates/mobile-scenario.json +133 -0
- package/templates/package-scripts.json +32 -0
- package/templates/primary-runner.json +19 -0
- package/templates/project.config.json +37 -0
- package/templates/scripts/asl-capture-accessibility-provider.mjs +112 -0
- package/templates/scripts/asl-capture-profiler-provider.mjs +127 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.assertAggregatePassed = assertAggregatePassed;
|
|
5
|
+
exports.formatResult = formatResult;
|
|
6
|
+
exports.assertNoRegressedComparisons = assertNoRegressedComparisons;
|
|
7
|
+
exports.buildLiveRunId = buildLiveRunId;
|
|
8
|
+
exports.buildSkippedInteractionProofs = buildSkippedInteractionProofs;
|
|
9
|
+
exports.buildInteractionComparisonLane = buildInteractionComparisonLane;
|
|
10
|
+
exports.isTrustedProfileRun = isTrustedProfileRun;
|
|
11
|
+
exports.main = main;
|
|
12
|
+
exports.normalizeRunSuffix = normalizeRunSuffix;
|
|
13
|
+
exports.resolveAndroidSerial = resolveAndroidSerial;
|
|
14
|
+
exports.runExampleAndroidLiveProof = runExampleAndroidLiveProof;
|
|
15
|
+
exports.usage = usage;
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
const { hasHelpFlag, writeUsage } = require('./cli');
|
|
19
|
+
const { parsePositiveInteger, parseArgs, runAndroidAdbPreflight, } = require('./android-adb');
|
|
20
|
+
const { compareLiveProfilesToLatest, isEnabledFlag, } = require('./live-comparison');
|
|
21
|
+
const { writeLiveProofSummary } = require('./live-proof-summary');
|
|
22
|
+
const { runAgentDeviceCapture } = require('./agent-device');
|
|
23
|
+
const { parseBaseArgs: parseArgentBaseArgs, runArgentCapture } = require('./argent');
|
|
24
|
+
const { loadAslLocalEnv, readStringArgOrEnv } = require('./local-env');
|
|
25
|
+
const { runProfileAndroid } = require('./profile-android');
|
|
26
|
+
const EXAMPLE_PROFILES = [
|
|
27
|
+
{
|
|
28
|
+
label: 'startup',
|
|
29
|
+
runId: 'android-live-startup',
|
|
30
|
+
scenario: 'app-startup.json',
|
|
31
|
+
scenarioId: 'app-startup',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
label: 'open-close',
|
|
35
|
+
runId: 'android-live-open-close',
|
|
36
|
+
scenario: 'open-close-cycle.json',
|
|
37
|
+
scenarioId: 'open-close-cycle',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
label: 'scroll',
|
|
41
|
+
runId: 'android-live-scroll',
|
|
42
|
+
scenario: 'scroll-settle.json',
|
|
43
|
+
scenarioId: 'scroll-settle',
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
const DEFAULT_REACT_NATIVE_DEBUG_HOST = 'localhost:8097';
|
|
47
|
+
/**
|
|
48
|
+
* Prints CLI usage.
|
|
49
|
+
*
|
|
50
|
+
* @param {{write: (message: string) => unknown}} [output]
|
|
51
|
+
* @returns {void}
|
|
52
|
+
*/
|
|
53
|
+
function usage(output = process.stderr) {
|
|
54
|
+
writeUsage([
|
|
55
|
+
'Usage: asl-example-android-live [--config <path>] [--out <dir>] [--package <name>] [--serial <device>] [--react-native-debug-host <host:port>] [--run-suffix <label>] [--compare-latest] [--fail-on-regression] [--agent-device-proof] [--argent-proof]',
|
|
56
|
+
'',
|
|
57
|
+
'Runs the packaged example Android live proof: adb preflight, startup, open-close, and scroll-settle.',
|
|
58
|
+
'The example app must already be installed and reachable on an online Android emulator or device.',
|
|
59
|
+
`By default, the runner sets the app React Native debug host to ${DEFAULT_REACT_NATIVE_DEBUG_HOST} for the isolated Metro server.`,
|
|
60
|
+
'Use --run-suffix to preserve multiple live proof artifact sets without changing deterministic default run ids.',
|
|
61
|
+
'Use --compare-latest to compare each passed scenario against the latest trusted prior run under the artifact root.',
|
|
62
|
+
'Use --fail-on-regression with --compare-latest to exit nonzero after writing evidence when any comparison regressed.',
|
|
63
|
+
'Use --agent-device-proof to attach the shared startup UI assertion through agent-device; pass --agent-device-session-mode bind when a named session should still receive the configured serial.',
|
|
64
|
+
'Use --argent-proof to attach the shared startup UI assertion through Argent; set ASL_ARGENT_BIN and ASL_ARGENT_BASE_ARGS for non-global installs.',
|
|
65
|
+
], output);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resolves the package root for source and built CLI execution.
|
|
69
|
+
*
|
|
70
|
+
* @returns {string}
|
|
71
|
+
*/
|
|
72
|
+
function defaultPackageRoot() {
|
|
73
|
+
return path.resolve(__dirname, '..', '..');
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Reads a JSON object from disk.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} filePath
|
|
79
|
+
* @returns {Record<string, unknown>}
|
|
80
|
+
*/
|
|
81
|
+
function readJson(filePath) {
|
|
82
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Reads a nested object property from unknown JSON.
|
|
86
|
+
*
|
|
87
|
+
* @param {Record<string, unknown>} value
|
|
88
|
+
* @param {string} key
|
|
89
|
+
* @returns {Record<string, unknown> | null}
|
|
90
|
+
*/
|
|
91
|
+
function readObjectProperty(value, key) {
|
|
92
|
+
const property = value[key];
|
|
93
|
+
return property && typeof property === 'object' && !Array.isArray(property)
|
|
94
|
+
? property
|
|
95
|
+
: null;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Resolves the Android package name from explicit input or ASL config.
|
|
99
|
+
*
|
|
100
|
+
* @param {{args: CliArgs, config: Record<string, unknown>}} options
|
|
101
|
+
* @returns {string | null}
|
|
102
|
+
*/
|
|
103
|
+
function resolveAndroidPackageName({ args, config, }) {
|
|
104
|
+
if (typeof args.package === 'string') {
|
|
105
|
+
return args.package;
|
|
106
|
+
}
|
|
107
|
+
const app = readObjectProperty(config, 'app');
|
|
108
|
+
return typeof app?.androidPackage === 'string' ? app.androidPackage : null;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Resolves the Android target used by every example proof lane.
|
|
112
|
+
*
|
|
113
|
+
* @param {CliArgs} args
|
|
114
|
+
* @returns {string}
|
|
115
|
+
*/
|
|
116
|
+
function resolveAndroidSerial(args) {
|
|
117
|
+
return readStringArgOrEnv(args.serial, ['ASL_ANDROID_SERIAL', 'ASL_EXAMPLE_ANDROID_SERIAL']) ?? 'emulator-5554';
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Converts a caller-provided run suffix into a path-safe run-id segment.
|
|
121
|
+
*
|
|
122
|
+
* @param {unknown} value
|
|
123
|
+
* @returns {string | null}
|
|
124
|
+
*/
|
|
125
|
+
function normalizeRunSuffix(value) {
|
|
126
|
+
if (typeof value !== 'string') {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const normalized = value
|
|
130
|
+
.trim()
|
|
131
|
+
.toLowerCase()
|
|
132
|
+
.replace(/[^a-z0-9._-]+/gu, '-')
|
|
133
|
+
.replace(/^-+|-+$/gu, '')
|
|
134
|
+
.slice(0, 64);
|
|
135
|
+
return normalized.length > 0 ? normalized : null;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Applies the optional run suffix while preserving deterministic defaults.
|
|
139
|
+
*
|
|
140
|
+
* @param {string} baseRunId
|
|
141
|
+
* @param {string | null} suffix
|
|
142
|
+
* @returns {string}
|
|
143
|
+
*/
|
|
144
|
+
function buildLiveRunId(baseRunId, suffix) {
|
|
145
|
+
return suffix ? `${baseRunId}-${suffix}` : baseRunId;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Builds the comparison lane suffix for enabled interaction proofs.
|
|
149
|
+
*
|
|
150
|
+
* @param {string[]} runnerIds
|
|
151
|
+
* @returns {string}
|
|
152
|
+
*/
|
|
153
|
+
function buildInteractionComparisonLane(runnerIds) {
|
|
154
|
+
return runnerIds.length > 0
|
|
155
|
+
? `example-android-live+${runnerIds.join('+')}`
|
|
156
|
+
: 'example-android-live';
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Reports whether profile evidence is healthy enough to trust sidecar proofs and comparisons.
|
|
160
|
+
*
|
|
161
|
+
* @param {{health: Record<string, unknown>, verdict: Record<string, unknown>}} run
|
|
162
|
+
* @returns {boolean}
|
|
163
|
+
*/
|
|
164
|
+
function isTrustedProfileRun({ health, verdict, }) {
|
|
165
|
+
return health.healthStatus === 'passed' && verdict.verdictStatus === 'passed';
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Builds skipped sidecar pointers for requested example runners when the profile batch failed.
|
|
169
|
+
*
|
|
170
|
+
* @param {{failedProfiles: AndroidLiveProfile[], requestedRunners: string[], runIdsByRunner: Record<string, string>}} options
|
|
171
|
+
* @returns {AndroidSkippedInteractionProof[]}
|
|
172
|
+
*/
|
|
173
|
+
function buildSkippedInteractionProofs({ failedProfiles, requestedRunners, runIdsByRunner, }) {
|
|
174
|
+
if (failedProfiles.length === 0) {
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
const failedSummary = failedProfiles
|
|
178
|
+
.map((profile) => `${profile.label}=health:${profile.healthStatus ?? 'unknown'}/verdict:${profile.verdictStatus ?? 'unknown'}`)
|
|
179
|
+
.join(', ');
|
|
180
|
+
const reason = `Profile batch failed (${failedSummary}); sidecar interaction proof was skipped because aggregate runner evidence would not be trustworthy.`;
|
|
181
|
+
const labelsByRunner = {
|
|
182
|
+
'agent-device': 'startup-ui',
|
|
183
|
+
argent: 'startup-ui-argent',
|
|
184
|
+
};
|
|
185
|
+
return requestedRunners.map((runnerId) => ({
|
|
186
|
+
label: labelsByRunner[runnerId] ?? `startup-ui-${runnerId}`,
|
|
187
|
+
nextAction: {
|
|
188
|
+
code: 'fix_profile_gate',
|
|
189
|
+
summary: 'Inspect the failed profile health and verdict before rerunning sidecar interaction proofs.',
|
|
190
|
+
},
|
|
191
|
+
reason,
|
|
192
|
+
runId: runIdsByRunner[runnerId] ?? `android-${runnerId}-startup`,
|
|
193
|
+
runnerId,
|
|
194
|
+
scenarioId: 'app-startup',
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Throws after aggregate proof writing when the live proof itself failed.
|
|
199
|
+
*
|
|
200
|
+
* @param {AndroidLiveProofResult} result
|
|
201
|
+
* @returns {void}
|
|
202
|
+
*/
|
|
203
|
+
function assertAggregatePassed(result) {
|
|
204
|
+
const proof = readJson(result.aggregateSummary.liveProofPath);
|
|
205
|
+
if (proof.status === 'passed') {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
throw new Error(`Android example live proof failed. Inspect ${result.aggregateSummary.summaryPath}.`);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Throws after aggregate proof writing when fail-on-regression should gate the run.
|
|
212
|
+
*
|
|
213
|
+
* @param {RegressionGateOptions} options
|
|
214
|
+
* @returns {void}
|
|
215
|
+
*/
|
|
216
|
+
function assertNoRegressedComparisons({ platformLabel, result, }) {
|
|
217
|
+
const regressed = result.comparisons.filter((comparison) => comparison.status === 'worse');
|
|
218
|
+
if (regressed.length === 0) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const labels = regressed.map((comparison) => comparison.label).join(', ');
|
|
222
|
+
throw new Error(`${platformLabel} example live proof found regressed comparison(s): ${labels}. Inspect ${result.aggregateSummary.summaryPath}.`);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Runs adb preflight plus all canonical Android example live profiles.
|
|
226
|
+
*
|
|
227
|
+
* @param {CliArgs} args
|
|
228
|
+
* @param {AndroidLiveProofOptions} [options]
|
|
229
|
+
* @returns {Promise<AndroidLiveProofResult>}
|
|
230
|
+
*/
|
|
231
|
+
async function runExampleAndroidLiveProof(args, options = {}) {
|
|
232
|
+
const packageRoot = options.packageRoot ?? defaultPackageRoot();
|
|
233
|
+
const exampleRoot = path.join(packageRoot, 'examples', 'mobile-app');
|
|
234
|
+
const configPath = typeof args.config === 'string'
|
|
235
|
+
? path.resolve(args.config)
|
|
236
|
+
: path.join(exampleRoot, 'asl.config.json');
|
|
237
|
+
const outputDir = typeof args.out === 'string'
|
|
238
|
+
? path.resolve(args.out)
|
|
239
|
+
: path.resolve('artifacts/example-mobile-app/android');
|
|
240
|
+
const config = readJson(configPath);
|
|
241
|
+
const packageName = resolveAndroidPackageName({ args, config });
|
|
242
|
+
const serial = resolveAndroidSerial(args);
|
|
243
|
+
const agentDeviceSession = readStringArgOrEnv(args['agent-device-session'], [
|
|
244
|
+
'ASL_ANDROID_AGENT_DEVICE_SESSION',
|
|
245
|
+
'ASL_EXAMPLE_ANDROID_AGENT_DEVICE_SESSION',
|
|
246
|
+
]);
|
|
247
|
+
const agentDeviceSessionMode = readStringArgOrEnv(args['agent-device-session-mode'], [
|
|
248
|
+
'ASL_ANDROID_AGENT_DEVICE_SESSION_MODE',
|
|
249
|
+
'ASL_EXAMPLE_ANDROID_AGENT_DEVICE_SESSION_MODE',
|
|
250
|
+
]);
|
|
251
|
+
const runSuffix = normalizeRunSuffix(args['run-suffix']);
|
|
252
|
+
const aggregateRunId = buildLiveRunId('android-live-proof', runSuffix);
|
|
253
|
+
const preflightRunId = buildLiveRunId('android-live-preflight', runSuffix);
|
|
254
|
+
const agentDeviceRunId = buildLiveRunId('android-agent-device-startup', runSuffix);
|
|
255
|
+
const argentRunId = buildLiveRunId('android-argent-startup', runSuffix);
|
|
256
|
+
const enabledInteractionRunners = [
|
|
257
|
+
...(isEnabledFlag(args['agent-device-proof']) ? ['agent-device'] : []),
|
|
258
|
+
...(isEnabledFlag(args['argent-proof']) ? ['argent'] : []),
|
|
259
|
+
];
|
|
260
|
+
const comparisonLane = buildInteractionComparisonLane(enabledInteractionRunners);
|
|
261
|
+
const runIdsByRunner = {
|
|
262
|
+
'agent-device': agentDeviceRunId,
|
|
263
|
+
argent: argentRunId,
|
|
264
|
+
};
|
|
265
|
+
const preflightDir = path.join(outputDir, '_preflight', preflightRunId);
|
|
266
|
+
const reactNativeDebugHost = typeof args['react-native-debug-host'] === 'string'
|
|
267
|
+
? args['react-native-debug-host']
|
|
268
|
+
: DEFAULT_REACT_NATIVE_DEBUG_HOST;
|
|
269
|
+
const preflight = await runAndroidAdbPreflight({
|
|
270
|
+
...(typeof args.adb === 'string' ? { adbPath: args.adb } : {}),
|
|
271
|
+
...(options.delay ? { delay: options.delay } : {}),
|
|
272
|
+
...(options.executor ? { executor: options.executor } : {}),
|
|
273
|
+
outputDir: preflightDir,
|
|
274
|
+
packageName,
|
|
275
|
+
reactNativeDebugHost,
|
|
276
|
+
runId: preflightRunId,
|
|
277
|
+
serial,
|
|
278
|
+
});
|
|
279
|
+
if (preflight.health.healthStatus !== 'passed') {
|
|
280
|
+
throw new Error(`Android live proof preflight failed; inspect ${preflight.runDir}/agent-summary.md.`);
|
|
281
|
+
}
|
|
282
|
+
const interactionProofs = [];
|
|
283
|
+
const profiles = [];
|
|
284
|
+
const failedProfiles = [];
|
|
285
|
+
for (const profile of EXAMPLE_PROFILES) {
|
|
286
|
+
const profileRunId = buildLiveRunId(profile.runId, runSuffix);
|
|
287
|
+
const result = await runProfileAndroid({
|
|
288
|
+
...(typeof args.adb === 'string' ? { adb: args.adb } : {}),
|
|
289
|
+
'adb-capture': true,
|
|
290
|
+
'clear-logcat': true,
|
|
291
|
+
config: configPath,
|
|
292
|
+
'command-wait-ms': typeof args['command-wait-ms'] === 'string' ? args['command-wait-ms'] : '250',
|
|
293
|
+
launch: true,
|
|
294
|
+
'launch-wait-ms': typeof args['launch-wait-ms'] === 'string' ? args['launch-wait-ms'] : '1500',
|
|
295
|
+
'logcat-lines': typeof args['logcat-lines'] === 'string' ? args['logcat-lines'] : '1000',
|
|
296
|
+
out: outputDir,
|
|
297
|
+
...(packageName ? { package: packageName } : {}),
|
|
298
|
+
'profile-session': true,
|
|
299
|
+
'react-native-debug-host': reactNativeDebugHost,
|
|
300
|
+
'run-id': profileRunId,
|
|
301
|
+
scenario: path.join(exampleRoot, 'scenarios', 'mobile', profile.scenario),
|
|
302
|
+
serial,
|
|
303
|
+
'wait-ms': typeof args['wait-ms'] === 'string' ? args['wait-ms'] : '1000',
|
|
304
|
+
}, {
|
|
305
|
+
comparisonLane,
|
|
306
|
+
...(options.delay ? { delay: options.delay } : {}),
|
|
307
|
+
...(options.executor ? { executor: options.executor } : {}),
|
|
308
|
+
});
|
|
309
|
+
const profilePointer = {
|
|
310
|
+
healthStatus: typeof result.health.healthStatus === 'string' ? result.health.healthStatus : 'unknown',
|
|
311
|
+
label: profile.label,
|
|
312
|
+
runDir: result.runDir,
|
|
313
|
+
runId: profileRunId,
|
|
314
|
+
scenario: profile.scenario,
|
|
315
|
+
scenarioId: profile.scenarioId,
|
|
316
|
+
verdictStatus: typeof result.verdict.verdictStatus === 'string' ? result.verdict.verdictStatus : 'unknown',
|
|
317
|
+
};
|
|
318
|
+
profiles.push(profilePointer);
|
|
319
|
+
if (!isTrustedProfileRun({ health: result.health, verdict: result.verdict })) {
|
|
320
|
+
failedProfiles.push(profilePointer);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const skippedInteractionProofs = buildSkippedInteractionProofs({
|
|
324
|
+
failedProfiles,
|
|
325
|
+
requestedRunners: enabledInteractionRunners,
|
|
326
|
+
runIdsByRunner,
|
|
327
|
+
});
|
|
328
|
+
const profileBatchTrusted = failedProfiles.length === 0;
|
|
329
|
+
if (profileBatchTrusted && isEnabledFlag(args['agent-device-proof'])) {
|
|
330
|
+
const agentDeviceCapture = await runAgentDeviceCapture({
|
|
331
|
+
...(typeof args['agent-device'] === 'string' ? { agentDevicePath: args['agent-device'] } : {}),
|
|
332
|
+
app: packageName,
|
|
333
|
+
...(options.agentDeviceExecutor ? { executor: options.agentDeviceExecutor } : {}),
|
|
334
|
+
open: true,
|
|
335
|
+
outputDir: path.join(outputDir, '_agent-device-captures', agentDeviceRunId),
|
|
336
|
+
platform: 'android',
|
|
337
|
+
runId: agentDeviceRunId,
|
|
338
|
+
scenario: readJson(path.join(exampleRoot, 'scenarios', 'mobile', 'app-startup.json')),
|
|
339
|
+
serial,
|
|
340
|
+
...(agentDeviceSession ? { session: agentDeviceSession } : {}),
|
|
341
|
+
...(agentDeviceSessionMode
|
|
342
|
+
? { sessionMode: agentDeviceSessionMode }
|
|
343
|
+
: {}),
|
|
344
|
+
waitMs: parsePositiveInteger(args['agent-device-wait-ms'], 1000),
|
|
345
|
+
});
|
|
346
|
+
interactionProofs.push({
|
|
347
|
+
label: 'startup-ui',
|
|
348
|
+
runDir: agentDeviceCapture.runDir,
|
|
349
|
+
runId: agentDeviceRunId,
|
|
350
|
+
runnerId: 'agent-device',
|
|
351
|
+
scenarioId: 'app-startup',
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
if (profileBatchTrusted && isEnabledFlag(args['argent-proof'])) {
|
|
355
|
+
const argentBaseArgs = parseArgentBaseArgs(process.env.ASL_ARGENT_BASE_ARGS);
|
|
356
|
+
const argentCapture = await runArgentCapture({
|
|
357
|
+
app: packageName,
|
|
358
|
+
argentCommand: process.env.ASL_ARGENT_BIN || 'argent',
|
|
359
|
+
...(argentBaseArgs ? { baseArgs: argentBaseArgs } : {}),
|
|
360
|
+
commandTimeoutMs: parsePositiveInteger(process.env.ASL_ARGENT_COMMAND_TIMEOUT_MS, 60_000),
|
|
361
|
+
deviceId: serial,
|
|
362
|
+
...(options.delay ? { delay: options.delay } : {}),
|
|
363
|
+
...(options.argentExecutor ? { executor: options.argentExecutor } : {}),
|
|
364
|
+
outputDir: path.join(outputDir, '_argent-captures', argentRunId),
|
|
365
|
+
platform: 'android',
|
|
366
|
+
runId: argentRunId,
|
|
367
|
+
scenario: readJson(path.join(exampleRoot, 'scenarios', 'mobile', 'app-startup.json')),
|
|
368
|
+
});
|
|
369
|
+
interactionProofs.push({
|
|
370
|
+
label: 'startup-ui-argent',
|
|
371
|
+
runDir: argentCapture.runDir,
|
|
372
|
+
runId: argentRunId,
|
|
373
|
+
runnerId: 'argent',
|
|
374
|
+
scenarioId: 'app-startup',
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
const comparisons = profileBatchTrusted && isEnabledFlag(args['compare-latest'])
|
|
378
|
+
? await compareLiveProfilesToLatest({ outputDir, profiles })
|
|
379
|
+
: [];
|
|
380
|
+
const aggregateSummary = await writeLiveProofSummary({
|
|
381
|
+
comparisons,
|
|
382
|
+
interactionProofs,
|
|
383
|
+
outputDir,
|
|
384
|
+
platform: 'android',
|
|
385
|
+
preflightDir: preflight.runDir,
|
|
386
|
+
preflightRunId,
|
|
387
|
+
profiles,
|
|
388
|
+
runId: aggregateRunId,
|
|
389
|
+
skippedInteractionProofs,
|
|
390
|
+
});
|
|
391
|
+
const result = {
|
|
392
|
+
aggregateSummary,
|
|
393
|
+
comparisons,
|
|
394
|
+
interactionProofs,
|
|
395
|
+
outputDir,
|
|
396
|
+
preflightDir: preflight.runDir,
|
|
397
|
+
profiles,
|
|
398
|
+
skippedInteractionProofs,
|
|
399
|
+
};
|
|
400
|
+
if (isEnabledFlag(args['fail-on-regression'])) {
|
|
401
|
+
assertNoRegressedComparisons({
|
|
402
|
+
platformLabel: 'Android',
|
|
403
|
+
result,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
assertAggregatePassed(result);
|
|
407
|
+
return result;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Formats the live proof result for humans and agents.
|
|
411
|
+
*
|
|
412
|
+
* @param {AndroidLiveProofResult} result
|
|
413
|
+
* @returns {string}
|
|
414
|
+
*/
|
|
415
|
+
function formatResult(result) {
|
|
416
|
+
return [
|
|
417
|
+
'Android example live proof passed.',
|
|
418
|
+
`Live proof: ${result.aggregateSummary.summaryPath}`,
|
|
419
|
+
`Preflight: ${result.preflightDir}/agent-summary.md`,
|
|
420
|
+
...result.profiles.map((profile) => (`${profile.label}: ${profile.runDir}/agent-summary.md`)),
|
|
421
|
+
...result.interactionProofs.map((proof) => (`${proof.label}: ${proof.runDir}/agent-summary.md`)),
|
|
422
|
+
...(result.comparisons.length > 0
|
|
423
|
+
? [
|
|
424
|
+
'Comparisons:',
|
|
425
|
+
...result.comparisons.map((comparison) => (comparison.status === 'skipped'
|
|
426
|
+
? `${comparison.label}: skipped - ${comparison.reason}`
|
|
427
|
+
: `${comparison.label}: ${comparison.status} ${comparison.summaryPath}`)),
|
|
428
|
+
]
|
|
429
|
+
: []),
|
|
430
|
+
`Artifact root: ${result.outputDir}`,
|
|
431
|
+
].join('\n');
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Runs the example Android live proof CLI.
|
|
435
|
+
*
|
|
436
|
+
* @returns {Promise<void>}
|
|
437
|
+
*/
|
|
438
|
+
async function main() {
|
|
439
|
+
const argv = process.argv.slice(2);
|
|
440
|
+
if (hasHelpFlag(argv)) {
|
|
441
|
+
usage(process.stdout);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
loadAslLocalEnv();
|
|
445
|
+
const args = parseArgs(argv);
|
|
446
|
+
const result = await runExampleAndroidLiveProof(args);
|
|
447
|
+
process.stdout.write(`${formatResult(result)}\n`);
|
|
448
|
+
}
|
|
449
|
+
if (require.main === module) {
|
|
450
|
+
main().catch((error) => {
|
|
451
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
452
|
+
process.exitCode = 1;
|
|
453
|
+
});
|
|
454
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
type CliArgs = import('./ios-simctl').CliArgs;
|
|
3
|
+
type LiveComparisonResult = import('./live-comparison').LiveComparisonResult;
|
|
4
|
+
type LiveProofSummaryResult = import('./live-proof-summary').LiveProofSummaryResult;
|
|
5
|
+
type IosLiveProofOptions = {
|
|
6
|
+
agentDeviceExecutor?: import('./agent-device').CommandExecutor;
|
|
7
|
+
argentExecutor?: import('./argent').CommandExecutor;
|
|
8
|
+
delay?: (ms: number) => Promise<void>;
|
|
9
|
+
executor?: import('./ios-simctl').CommandExecutor;
|
|
10
|
+
packageRoot?: string;
|
|
11
|
+
};
|
|
12
|
+
type IosInteractionProof = {
|
|
13
|
+
label: string;
|
|
14
|
+
runDir: string;
|
|
15
|
+
runId: string;
|
|
16
|
+
runnerId: string;
|
|
17
|
+
scenarioId: string;
|
|
18
|
+
};
|
|
19
|
+
type IosSkippedInteractionProof = import('./live-proof-summary').LiveProofSkippedInteractionProofPointer;
|
|
20
|
+
type IosLiveProfile = {
|
|
21
|
+
healthStatus?: string;
|
|
22
|
+
label: string;
|
|
23
|
+
runDir: string;
|
|
24
|
+
runId: string;
|
|
25
|
+
scenario: string;
|
|
26
|
+
scenarioId: string;
|
|
27
|
+
verdictStatus?: string;
|
|
28
|
+
};
|
|
29
|
+
type IosLiveProofResult = {
|
|
30
|
+
aggregateSummary: LiveProofSummaryResult;
|
|
31
|
+
comparisons: LiveComparisonResult[];
|
|
32
|
+
interactionProofs: IosInteractionProof[];
|
|
33
|
+
outputDir: string;
|
|
34
|
+
preflightDir: string;
|
|
35
|
+
profiles: IosLiveProfile[];
|
|
36
|
+
skippedInteractionProofs: IosSkippedInteractionProof[];
|
|
37
|
+
};
|
|
38
|
+
type RegressionGateOptions = {
|
|
39
|
+
platformLabel: string;
|
|
40
|
+
result: IosLiveProofResult;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Prints CLI usage.
|
|
44
|
+
*
|
|
45
|
+
* @param {{write: (message: string) => unknown}} [output]
|
|
46
|
+
* @returns {void}
|
|
47
|
+
*/
|
|
48
|
+
declare function usage(output?: {
|
|
49
|
+
write: (message: string) => unknown;
|
|
50
|
+
}): void;
|
|
51
|
+
/**
|
|
52
|
+
* Resolves the simulator target used by every iOS proof lane.
|
|
53
|
+
*
|
|
54
|
+
* @param {CliArgs} args
|
|
55
|
+
* @returns {string}
|
|
56
|
+
*/
|
|
57
|
+
declare function resolveIosDeviceId(args: CliArgs): string;
|
|
58
|
+
/**
|
|
59
|
+
* Converts a caller-provided run suffix into a path-safe run-id segment.
|
|
60
|
+
*
|
|
61
|
+
* @param {unknown} value
|
|
62
|
+
* @returns {string | null}
|
|
63
|
+
*/
|
|
64
|
+
declare function normalizeRunSuffix(value: unknown): string | null;
|
|
65
|
+
/**
|
|
66
|
+
* Applies the optional run suffix while preserving deterministic defaults.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} baseRunId
|
|
69
|
+
* @param {string | null} suffix
|
|
70
|
+
* @returns {string}
|
|
71
|
+
*/
|
|
72
|
+
declare function buildLiveRunId(baseRunId: string, suffix: string | null): string;
|
|
73
|
+
/**
|
|
74
|
+
* Builds the comparison lane suffix for enabled interaction proofs.
|
|
75
|
+
*
|
|
76
|
+
* @param {string[]} runnerIds
|
|
77
|
+
* @returns {string}
|
|
78
|
+
*/
|
|
79
|
+
declare function buildInteractionComparisonLane(runnerIds: string[]): string;
|
|
80
|
+
/**
|
|
81
|
+
* Reports whether profile evidence is healthy enough to trust sidecar proofs and comparisons.
|
|
82
|
+
*
|
|
83
|
+
* @param {{health: Record<string, unknown>, verdict: Record<string, unknown>}} run
|
|
84
|
+
* @returns {boolean}
|
|
85
|
+
*/
|
|
86
|
+
declare function isTrustedProfileRun({ health, verdict, }: {
|
|
87
|
+
health: Record<string, unknown>;
|
|
88
|
+
verdict: Record<string, unknown>;
|
|
89
|
+
}): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Builds skipped sidecar pointers for requested example runners when the profile batch failed.
|
|
92
|
+
*
|
|
93
|
+
* @param {{failedProfiles: IosLiveProfile[], requestedRunners: string[], runIdsByRunner: Record<string, string>}} options
|
|
94
|
+
* @returns {IosSkippedInteractionProof[]}
|
|
95
|
+
*/
|
|
96
|
+
declare function buildSkippedInteractionProofs({ failedProfiles, requestedRunners, runIdsByRunner, }: {
|
|
97
|
+
failedProfiles: IosLiveProfile[];
|
|
98
|
+
requestedRunners: string[];
|
|
99
|
+
runIdsByRunner: Record<string, string>;
|
|
100
|
+
}): IosSkippedInteractionProof[];
|
|
101
|
+
/**
|
|
102
|
+
* Throws after aggregate proof writing when the live proof itself failed.
|
|
103
|
+
*
|
|
104
|
+
* @param {IosLiveProofResult} result
|
|
105
|
+
* @returns {void}
|
|
106
|
+
*/
|
|
107
|
+
declare function assertAggregatePassed(result: IosLiveProofResult): void;
|
|
108
|
+
/**
|
|
109
|
+
* Throws after aggregate proof writing when fail-on-regression should gate the run.
|
|
110
|
+
*
|
|
111
|
+
* @param {RegressionGateOptions} options
|
|
112
|
+
* @returns {void}
|
|
113
|
+
*/
|
|
114
|
+
declare function assertNoRegressedComparisons({ platformLabel, result, }: RegressionGateOptions): void;
|
|
115
|
+
/**
|
|
116
|
+
* Runs simctl preflight plus all canonical iOS example live profiles.
|
|
117
|
+
*
|
|
118
|
+
* @param {CliArgs} args
|
|
119
|
+
* @param {IosLiveProofOptions} [options]
|
|
120
|
+
* @returns {Promise<IosLiveProofResult>}
|
|
121
|
+
*/
|
|
122
|
+
declare function runExampleIosLiveProof(args: CliArgs, options?: IosLiveProofOptions): Promise<IosLiveProofResult>;
|
|
123
|
+
/**
|
|
124
|
+
* Formats the live proof result for humans and agents.
|
|
125
|
+
*
|
|
126
|
+
* @param {IosLiveProofResult} result
|
|
127
|
+
* @returns {string}
|
|
128
|
+
*/
|
|
129
|
+
declare function formatResult(result: IosLiveProofResult): string;
|
|
130
|
+
/**
|
|
131
|
+
* Runs the example iOS live proof CLI.
|
|
132
|
+
*
|
|
133
|
+
* @returns {Promise<void>}
|
|
134
|
+
*/
|
|
135
|
+
declare function main(): Promise<void>;
|
|
136
|
+
export { assertAggregatePassed, buildLiveRunId, formatResult, assertNoRegressedComparisons, buildSkippedInteractionProofs, buildInteractionComparisonLane, isTrustedProfileRun, main, normalizeRunSuffix, resolveIosDeviceId, runExampleIosLiveProof, usage, };
|
|
137
|
+
export type { IosLiveProofOptions, IosInteractionProof, IosLiveProofResult, IosLiveProfile, };
|