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,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
type CliArgs = import('./android-adb').CliArgs;
|
|
3
|
+
type AndroidGenericLiveOptions = {
|
|
4
|
+
agentDeviceExecutor?: import('./agent-device').CommandExecutor;
|
|
5
|
+
argentExecutor?: import('./argent').CommandExecutor;
|
|
6
|
+
delay?: (ms: number) => Promise<void>;
|
|
7
|
+
executor?: import('./android-adb').CommandExecutor;
|
|
8
|
+
};
|
|
9
|
+
type AndroidGenericLiveResult = {
|
|
10
|
+
aggregateSummary: import('./live-proof-summary').LiveProofSummaryResult;
|
|
11
|
+
outputDir: string;
|
|
12
|
+
preflightDir: string;
|
|
13
|
+
profileDir: string;
|
|
14
|
+
};
|
|
15
|
+
type SkippedInteractionProof = import('./live-proof-summary').LiveProofSkippedInteractionProofPointer;
|
|
16
|
+
/**
|
|
17
|
+
* Prints CLI usage.
|
|
18
|
+
*
|
|
19
|
+
* @param {{write: (message: string) => unknown}} [output]
|
|
20
|
+
* @returns {void}
|
|
21
|
+
*/
|
|
22
|
+
declare function usage(output?: {
|
|
23
|
+
write: (message: string) => unknown;
|
|
24
|
+
}): void;
|
|
25
|
+
/**
|
|
26
|
+
* Reads a JSON object from disk.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} filePath
|
|
29
|
+
* @returns {Record<string, unknown>}
|
|
30
|
+
*/
|
|
31
|
+
declare function readJson(filePath: string): Record<string, unknown>;
|
|
32
|
+
/**
|
|
33
|
+
* Resolves a stable scenario id from a scenario file.
|
|
34
|
+
*
|
|
35
|
+
* @param {{scenario: Record<string, unknown>, scenarioPath: string}} options
|
|
36
|
+
* @returns {string}
|
|
37
|
+
*/
|
|
38
|
+
declare function resolveScenarioId({ scenario, scenarioPath, }: {
|
|
39
|
+
scenario: Record<string, unknown>;
|
|
40
|
+
scenarioPath: string;
|
|
41
|
+
}): string;
|
|
42
|
+
/**
|
|
43
|
+
* Resolves the Android package name from explicit input or ASL config.
|
|
44
|
+
*
|
|
45
|
+
* @param {{args: CliArgs, config: Record<string, unknown>}} options
|
|
46
|
+
* @returns {string | null}
|
|
47
|
+
*/
|
|
48
|
+
declare function resolveAndroidPackageName({ args, config, }: {
|
|
49
|
+
args: CliArgs;
|
|
50
|
+
config: Record<string, unknown>;
|
|
51
|
+
}): string | null;
|
|
52
|
+
/**
|
|
53
|
+
* Converts an optional run suffix into a path-safe segment.
|
|
54
|
+
*
|
|
55
|
+
* @param {unknown} value
|
|
56
|
+
* @returns {string | null}
|
|
57
|
+
*/
|
|
58
|
+
declare function normalizeRunSuffix(value: unknown): string | null;
|
|
59
|
+
/**
|
|
60
|
+
* Applies an optional suffix to deterministic run ids.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} baseRunId
|
|
63
|
+
* @param {string | null} suffix
|
|
64
|
+
* @returns {string}
|
|
65
|
+
*/
|
|
66
|
+
declare function buildRunId(baseRunId: string, suffix: string | null): string;
|
|
67
|
+
/**
|
|
68
|
+
* Throws if a profile or interaction proof failed before aggregate writing.
|
|
69
|
+
*
|
|
70
|
+
* @param {{kind: string, runDir: string, health: Record<string, unknown>, verdict?: Record<string, unknown>}} options
|
|
71
|
+
* @returns {void}
|
|
72
|
+
*/
|
|
73
|
+
declare function assertPassedRun({ health, kind, runDir, verdict, }: {
|
|
74
|
+
health: Record<string, unknown>;
|
|
75
|
+
kind: string;
|
|
76
|
+
runDir: string;
|
|
77
|
+
verdict?: Record<string, unknown>;
|
|
78
|
+
}): void;
|
|
79
|
+
/**
|
|
80
|
+
* Reports whether profile evidence is trusted enough to run sidecar interaction proofs.
|
|
81
|
+
*
|
|
82
|
+
* @param {{health: Record<string, unknown>, verdict: Record<string, unknown>}} run
|
|
83
|
+
* @returns {boolean}
|
|
84
|
+
*/
|
|
85
|
+
declare function isTrustedProfileRun({ health, verdict, }: {
|
|
86
|
+
health: Record<string, unknown>;
|
|
87
|
+
verdict: Record<string, unknown>;
|
|
88
|
+
}): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Builds skipped sidecar pointers for requested runners when the profile gate failed.
|
|
91
|
+
*
|
|
92
|
+
* @param {{requestedRunners: string[], runIdsByRunner: Record<string, string>, scenarioId: string, profileHealthStatus: unknown, profileVerdictStatus: unknown}} options
|
|
93
|
+
* @returns {SkippedInteractionProof[]}
|
|
94
|
+
*/
|
|
95
|
+
declare function buildSkippedInteractionProofs({ profileHealthStatus, profileVerdictStatus, requestedRunners, runIdsByRunner, scenarioId, }: {
|
|
96
|
+
profileHealthStatus: unknown;
|
|
97
|
+
profileVerdictStatus: unknown;
|
|
98
|
+
requestedRunners: string[];
|
|
99
|
+
runIdsByRunner: Record<string, string>;
|
|
100
|
+
scenarioId: string;
|
|
101
|
+
}): SkippedInteractionProof[];
|
|
102
|
+
/**
|
|
103
|
+
* Throws after aggregate writing when the live proof itself failed.
|
|
104
|
+
*
|
|
105
|
+
* @param {AndroidGenericLiveResult} result
|
|
106
|
+
* @returns {void}
|
|
107
|
+
*/
|
|
108
|
+
declare function assertAggregatePassed(result: AndroidGenericLiveResult): void;
|
|
109
|
+
/**
|
|
110
|
+
* Throws after aggregate proof writing when fail-on-regression should gate the run.
|
|
111
|
+
*
|
|
112
|
+
* @param {{result: AndroidGenericLiveResult, comparisons: Array<{label: string, status: string}>}} options
|
|
113
|
+
* @returns {void}
|
|
114
|
+
*/
|
|
115
|
+
declare function assertNoRegressedComparisons({ comparisons, result, }: {
|
|
116
|
+
comparisons: Array<{
|
|
117
|
+
label: string;
|
|
118
|
+
status: string;
|
|
119
|
+
}>;
|
|
120
|
+
result: AndroidGenericLiveResult;
|
|
121
|
+
}): void;
|
|
122
|
+
/**
|
|
123
|
+
* Runs a generic one-scenario Android live proof.
|
|
124
|
+
*
|
|
125
|
+
* @param {CliArgs} args
|
|
126
|
+
* @param {AndroidGenericLiveOptions} [options]
|
|
127
|
+
* @returns {Promise<AndroidGenericLiveResult>}
|
|
128
|
+
*/
|
|
129
|
+
declare function runAndroidLiveProof(args: CliArgs, options?: AndroidGenericLiveOptions): Promise<AndroidGenericLiveResult>;
|
|
130
|
+
/**
|
|
131
|
+
* Runs the generic Android live CLI.
|
|
132
|
+
*
|
|
133
|
+
* @returns {Promise<void>}
|
|
134
|
+
*/
|
|
135
|
+
declare function main(): Promise<void>;
|
|
136
|
+
export { assertNoRegressedComparisons, assertAggregatePassed, assertPassedRun, buildRunId, buildSkippedInteractionProofs, isTrustedProfileRun, main, normalizeRunSuffix, readJson, resolveAndroidPackageName, resolveScenarioId, runAndroidLiveProof, usage, };
|
|
137
|
+
export type { AndroidGenericLiveOptions, AndroidGenericLiveResult, };
|
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.assertNoRegressedComparisons = assertNoRegressedComparisons;
|
|
5
|
+
exports.assertAggregatePassed = assertAggregatePassed;
|
|
6
|
+
exports.assertPassedRun = assertPassedRun;
|
|
7
|
+
exports.buildRunId = buildRunId;
|
|
8
|
+
exports.buildSkippedInteractionProofs = buildSkippedInteractionProofs;
|
|
9
|
+
exports.isTrustedProfileRun = isTrustedProfileRun;
|
|
10
|
+
exports.main = main;
|
|
11
|
+
exports.normalizeRunSuffix = normalizeRunSuffix;
|
|
12
|
+
exports.readJson = readJson;
|
|
13
|
+
exports.resolveAndroidPackageName = resolveAndroidPackageName;
|
|
14
|
+
exports.resolveScenarioId = resolveScenarioId;
|
|
15
|
+
exports.runAndroidLiveProof = runAndroidLiveProof;
|
|
16
|
+
exports.usage = usage;
|
|
17
|
+
const fs = require('node:fs');
|
|
18
|
+
const path = require('node:path');
|
|
19
|
+
const { writeJsonArtifact } = require('../core/artifact-writer');
|
|
20
|
+
const { SCHEMAS } = require('../core/schema-validator');
|
|
21
|
+
const { hasHelpFlag, writeUsage } = require('./cli');
|
|
22
|
+
const { execFileCommand, parseArgs, parsePositiveInteger, runAndroidAdbPreflight } = require('./android-adb');
|
|
23
|
+
const { compareLiveProfilesToLatest, isEnabledFlag } = require('./live-comparison');
|
|
24
|
+
const { writeLiveProofSummary } = require('./live-proof-summary');
|
|
25
|
+
const { runAgentDeviceCapture } = require('./agent-device');
|
|
26
|
+
const { parseBaseArgs: parseArgentBaseArgs, runArgentCapture } = require('./argent');
|
|
27
|
+
const { loadAslLocalEnv, readStringArgOrEnv } = require('./local-env');
|
|
28
|
+
const { runProfileAndroid } = require('./profile-android');
|
|
29
|
+
/**
|
|
30
|
+
* Prints CLI usage.
|
|
31
|
+
*
|
|
32
|
+
* @param {{write: (message: string) => unknown}} [output]
|
|
33
|
+
* @returns {void}
|
|
34
|
+
*/
|
|
35
|
+
function usage(output = process.stderr) {
|
|
36
|
+
writeUsage([
|
|
37
|
+
'Usage: asl-live-android --config <path> --scenario <path> [--out <dir>] [--package <name>] [--serial <device>] [--run-id <id>] [--run-suffix <label>] [--compare-latest] [--fail-on-regression] [--agent-device-proof] [--argent-proof]',
|
|
38
|
+
'',
|
|
39
|
+
'Runs one generic Android live proof: adb preflight, profile-session adb capture, optional sidecars, optional latest-trusted comparison, and aggregate live-proof artifacts.',
|
|
40
|
+
'Use --android-dev-client-url <url> [--android-dev-client-wait-ms <ms>] [--android-dev-client-ready-pattern <pattern>] for Expo dev-client shells that must open a Metro session before profile-session deep links.',
|
|
41
|
+
'Use --android-profile-session-storage [--android-profile-session-storage-key <key>] [--android-profile-command-storage-key <key>] when app startup control must be delivered through Android AsyncStorage.',
|
|
42
|
+
'Use --agent-device-proof to attach scenario-declared portable driver actions through agent-device; pass --agent-device-session-mode bind when a named session should still receive the configured serial.',
|
|
43
|
+
'Use --argent-proof to attach scenario-declared Argent-compatible driver actions; set ASL_ARGENT_BIN and ASL_ARGENT_BASE_ARGS for non-global installs.',
|
|
44
|
+
], output);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Reads a JSON object from disk.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} filePath
|
|
50
|
+
* @returns {Record<string, unknown>}
|
|
51
|
+
*/
|
|
52
|
+
function readJson(filePath) {
|
|
53
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Reads a nested object property from unknown JSON.
|
|
57
|
+
*
|
|
58
|
+
* @param {Record<string, unknown>} value
|
|
59
|
+
* @param {string} key
|
|
60
|
+
* @returns {Record<string, unknown> | null}
|
|
61
|
+
*/
|
|
62
|
+
function readObjectProperty(value, key) {
|
|
63
|
+
const property = value[key];
|
|
64
|
+
return property && typeof property === 'object' && !Array.isArray(property)
|
|
65
|
+
? property
|
|
66
|
+
: null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolves a stable scenario id from a scenario file.
|
|
70
|
+
*
|
|
71
|
+
* @param {{scenario: Record<string, unknown>, scenarioPath: string}} options
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/
|
|
74
|
+
function resolveScenarioId({ scenario, scenarioPath, }) {
|
|
75
|
+
return typeof scenario.id === 'string' && scenario.id.length > 0
|
|
76
|
+
? scenario.id
|
|
77
|
+
: path.basename(scenarioPath, '.json');
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Resolves the Android package name from explicit input or ASL config.
|
|
81
|
+
*
|
|
82
|
+
* @param {{args: CliArgs, config: Record<string, unknown>}} options
|
|
83
|
+
* @returns {string | null}
|
|
84
|
+
*/
|
|
85
|
+
function resolveAndroidPackageName({ args, config, }) {
|
|
86
|
+
if (typeof args.package === 'string') {
|
|
87
|
+
return args.package;
|
|
88
|
+
}
|
|
89
|
+
const envPackage = readStringArgOrEnv(undefined, ['ASL_ANDROID_APP_ID', 'ASL_EXAMPLE_ANDROID_APP_ID']);
|
|
90
|
+
if (envPackage) {
|
|
91
|
+
return envPackage;
|
|
92
|
+
}
|
|
93
|
+
const app = readObjectProperty(config, 'app');
|
|
94
|
+
return typeof app?.androidPackage === 'string' ? app.androidPackage : null;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Converts an optional run suffix into a path-safe segment.
|
|
98
|
+
*
|
|
99
|
+
* @param {unknown} value
|
|
100
|
+
* @returns {string | null}
|
|
101
|
+
*/
|
|
102
|
+
function normalizeRunSuffix(value) {
|
|
103
|
+
if (typeof value !== 'string') {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const normalized = value
|
|
107
|
+
.trim()
|
|
108
|
+
.toLowerCase()
|
|
109
|
+
.replace(/[^a-z0-9._-]+/gu, '-')
|
|
110
|
+
.replace(/^-+|-+$/gu, '')
|
|
111
|
+
.slice(0, 64);
|
|
112
|
+
return normalized.length > 0 ? normalized : null;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Applies an optional suffix to deterministic run ids.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} baseRunId
|
|
118
|
+
* @param {string | null} suffix
|
|
119
|
+
* @returns {string}
|
|
120
|
+
*/
|
|
121
|
+
function buildRunId(baseRunId, suffix) {
|
|
122
|
+
return suffix ? `${baseRunId}-${suffix}` : baseRunId;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Throws if a profile or interaction proof failed before aggregate writing.
|
|
126
|
+
*
|
|
127
|
+
* @param {{kind: string, runDir: string, health: Record<string, unknown>, verdict?: Record<string, unknown>}} options
|
|
128
|
+
* @returns {void}
|
|
129
|
+
*/
|
|
130
|
+
function assertPassedRun({ health, kind, runDir, verdict, }) {
|
|
131
|
+
if (health.healthStatus === 'passed' && (!verdict || verdict.verdictStatus === 'passed')) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
throw new Error([
|
|
135
|
+
`Android live proof failed for ${kind}.`,
|
|
136
|
+
`Health: ${health.healthStatus ?? 'unknown'}.`,
|
|
137
|
+
...(verdict ? [`Verdict: ${verdict.verdictStatus ?? 'unknown'}.`] : []),
|
|
138
|
+
`Inspect ${runDir}/agent-summary.md.`,
|
|
139
|
+
].join(' '));
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Reports whether profile evidence is trusted enough to run sidecar interaction proofs.
|
|
143
|
+
*
|
|
144
|
+
* @param {{health: Record<string, unknown>, verdict: Record<string, unknown>}} run
|
|
145
|
+
* @returns {boolean}
|
|
146
|
+
*/
|
|
147
|
+
function isTrustedProfileRun({ health, verdict, }) {
|
|
148
|
+
return health.healthStatus === 'passed' && verdict.verdictStatus === 'passed';
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Builds skipped sidecar pointers for requested runners when the profile gate failed.
|
|
152
|
+
*
|
|
153
|
+
* @param {{requestedRunners: string[], runIdsByRunner: Record<string, string>, scenarioId: string, profileHealthStatus: unknown, profileVerdictStatus: unknown}} options
|
|
154
|
+
* @returns {SkippedInteractionProof[]}
|
|
155
|
+
*/
|
|
156
|
+
function buildSkippedInteractionProofs({ profileHealthStatus, profileVerdictStatus, requestedRunners, runIdsByRunner, scenarioId, }) {
|
|
157
|
+
const reason = `Profile gate failed with health=${String(profileHealthStatus ?? 'unknown')} verdict=${String(profileVerdictStatus ?? 'unknown')}; sidecar interaction proof was skipped because timing and runner evidence would not be trustworthy.`;
|
|
158
|
+
return requestedRunners.map((runnerId) => ({
|
|
159
|
+
label: `interaction-${runnerId}`,
|
|
160
|
+
nextAction: {
|
|
161
|
+
code: 'fix_profile_gate',
|
|
162
|
+
summary: 'Inspect the profile health and verdict before rerunning sidecar interaction proofs.',
|
|
163
|
+
},
|
|
164
|
+
reason,
|
|
165
|
+
runId: runIdsByRunner[runnerId] ?? `${scenarioId}-android-${runnerId}`,
|
|
166
|
+
runnerId,
|
|
167
|
+
scenarioId,
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Builds adb arguments with an optional device serial prefix.
|
|
172
|
+
*
|
|
173
|
+
* @param {string | null} serial
|
|
174
|
+
* @param {string[]} args
|
|
175
|
+
* @returns {string[]}
|
|
176
|
+
*/
|
|
177
|
+
function buildAndroidRunnerAdbArgs(serial, args) {
|
|
178
|
+
return serial ? ['-s', serial, ...args] : args;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Formats an adb command result as raw diagnostic evidence.
|
|
182
|
+
*
|
|
183
|
+
* @param {import('./android-adb').CommandResult} result
|
|
184
|
+
* @returns {string}
|
|
185
|
+
*/
|
|
186
|
+
function formatAndroidRunnerCommandResult(result) {
|
|
187
|
+
return [
|
|
188
|
+
`$ ${result.command} ${result.args.join(' ')}`,
|
|
189
|
+
`exitCode=${result.exitCode}`,
|
|
190
|
+
result.stdout.trimEnd(),
|
|
191
|
+
result.stderr.trimEnd(),
|
|
192
|
+
].filter(Boolean).join('\n');
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Detects the known Argent Android instrumentation-helper crash signature.
|
|
196
|
+
*
|
|
197
|
+
* @param {string} text
|
|
198
|
+
* @returns {boolean}
|
|
199
|
+
*/
|
|
200
|
+
function hasArgentAndroidDevtoolsCrash(text) {
|
|
201
|
+
return /(?:Process:\s*com\.argent\.androiddevtools|Crash of app com\.argent\.androiddevtools|FATAL EXCEPTION:\s*argent-androiddevtools)/u.test(text);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Runs one adb diagnostic command through the live proof executor seam.
|
|
205
|
+
*
|
|
206
|
+
* @param {{adbPath: string, args: string[], executor?: import('./android-adb').CommandExecutor}} options
|
|
207
|
+
* @returns {Promise<import('./android-adb').CommandResult>}
|
|
208
|
+
*/
|
|
209
|
+
async function runAndroidRunnerAdbCommand({ adbPath, args, executor, }) {
|
|
210
|
+
return executor ? executor(adbPath, args) : execFileCommand(adbPath, args);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Attaches runner-side logcat evidence to an Argent Android sidecar proof.
|
|
214
|
+
*
|
|
215
|
+
* The Android live proof already owns adb, so this preserves Argent helper
|
|
216
|
+
* instability without making the standalone Argent runner depend on adb.
|
|
217
|
+
*
|
|
218
|
+
* @param {{adbPath: string, capture: import('./argent').ArgentCaptureResult, executor?: import('./android-adb').CommandExecutor, serial: string | null}} options
|
|
219
|
+
* @returns {Promise<void>}
|
|
220
|
+
*/
|
|
221
|
+
async function attachArgentAndroidRunnerDiagnostics({ adbPath, capture, executor, serial, }) {
|
|
222
|
+
const logcatResult = await runAndroidRunnerAdbCommand({
|
|
223
|
+
adbPath,
|
|
224
|
+
args: buildAndroidRunnerAdbArgs(serial, ['logcat', '-d', '-v', 'time', '-t', '400']),
|
|
225
|
+
...(executor ? { executor } : {}),
|
|
226
|
+
});
|
|
227
|
+
const rawDir = path.join(capture.runDir, 'raw');
|
|
228
|
+
await fs.promises.mkdir(rawDir, { recursive: true });
|
|
229
|
+
await fs.promises.writeFile(path.join(rawDir, 'adb-runner-logcat-after-argent.txt'), `${formatAndroidRunnerCommandResult(logcatResult)}\n`, 'utf8');
|
|
230
|
+
const rawOutput = `${logcatResult.stdout}\n${logcatResult.stderr}`;
|
|
231
|
+
if (!hasArgentAndroidDevtoolsCrash(rawOutput)) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const healthPath = path.join(capture.runDir, 'health.json');
|
|
235
|
+
const health = JSON.parse(fs.readFileSync(healthPath, 'utf8'));
|
|
236
|
+
health.checks = Array.isArray(health.checks) ? health.checks : [];
|
|
237
|
+
health.checks.push({
|
|
238
|
+
name: 'argent_android_helper_crash',
|
|
239
|
+
status: 'warning',
|
|
240
|
+
source: 'runner',
|
|
241
|
+
code: 'argent_android_helper_crash',
|
|
242
|
+
message: 'Argent Android helper crashed after producing sidecar evidence.',
|
|
243
|
+
metadata: {
|
|
244
|
+
nextAction: 'Inspect raw/adb-runner-logcat-after-argent.txt and rerun Argent if sidecar evidence becomes flaky.',
|
|
245
|
+
nextActionCode: 'inspect_argent_android_helper_crash',
|
|
246
|
+
rawPath: 'raw/adb-runner-logcat-after-argent.txt',
|
|
247
|
+
runnerProcess: 'com.argent.androiddevtools',
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
await writeJsonArtifact({
|
|
251
|
+
filePath: healthPath,
|
|
252
|
+
value: health,
|
|
253
|
+
schema: SCHEMAS.health,
|
|
254
|
+
label: 'Argent health artifact',
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Throws after aggregate writing when the live proof itself failed.
|
|
259
|
+
*
|
|
260
|
+
* @param {AndroidGenericLiveResult} result
|
|
261
|
+
* @returns {void}
|
|
262
|
+
*/
|
|
263
|
+
function assertAggregatePassed(result) {
|
|
264
|
+
const proof = readJson(result.aggregateSummary.liveProofPath);
|
|
265
|
+
if (proof.status === 'passed') {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
throw new Error(`Android live proof failed. Inspect ${result.aggregateSummary.summaryPath}.`);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Throws after aggregate proof writing when fail-on-regression should gate the run.
|
|
272
|
+
*
|
|
273
|
+
* @param {{result: AndroidGenericLiveResult, comparisons: Array<{label: string, status: string}>}} options
|
|
274
|
+
* @returns {void}
|
|
275
|
+
*/
|
|
276
|
+
function assertNoRegressedComparisons({ comparisons, result, }) {
|
|
277
|
+
const regressed = comparisons.filter((comparison) => comparison.status === 'worse');
|
|
278
|
+
if (regressed.length === 0) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
throw new Error(`Android live proof found regressed comparison(s): ${regressed.map((comparison) => comparison.label).join(', ')}. Inspect ${result.aggregateSummary.summaryPath}.`);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Runs a generic one-scenario Android live proof.
|
|
285
|
+
*
|
|
286
|
+
* @param {CliArgs} args
|
|
287
|
+
* @param {AndroidGenericLiveOptions} [options]
|
|
288
|
+
* @returns {Promise<AndroidGenericLiveResult>}
|
|
289
|
+
*/
|
|
290
|
+
async function runAndroidLiveProof(args, options = {}) {
|
|
291
|
+
if (typeof args.config !== 'string' || typeof args.scenario !== 'string') {
|
|
292
|
+
throw new Error('Both --config and --scenario are required.');
|
|
293
|
+
}
|
|
294
|
+
const configPath = path.resolve(args.config);
|
|
295
|
+
const scenarioPath = path.resolve(args.scenario);
|
|
296
|
+
const config = readJson(configPath);
|
|
297
|
+
const scenario = readJson(scenarioPath);
|
|
298
|
+
const scenarioId = resolveScenarioId({ scenario, scenarioPath });
|
|
299
|
+
const packageName = resolveAndroidPackageName({ args, config });
|
|
300
|
+
const serial = readStringArgOrEnv(args.serial, ['ASL_ANDROID_SERIAL', 'ASL_EXAMPLE_ANDROID_SERIAL']);
|
|
301
|
+
const reactNativeDebugHost = readStringArgOrEnv(args['react-native-debug-host'], [
|
|
302
|
+
'ASL_ANDROID_REACT_NATIVE_DEBUG_HOST',
|
|
303
|
+
'ASL_EXAMPLE_ANDROID_DEBUG_HOST',
|
|
304
|
+
]);
|
|
305
|
+
const androidDevClientUrl = readStringArgOrEnv(args['android-dev-client-url'], [
|
|
306
|
+
'ASL_ANDROID_DEV_CLIENT_URL',
|
|
307
|
+
'ASL_EXAMPLE_ANDROID_DEV_CLIENT_URL',
|
|
308
|
+
]);
|
|
309
|
+
const androidDevClientWaitMs = readStringArgOrEnv(args['android-dev-client-wait-ms'], [
|
|
310
|
+
'ASL_ANDROID_DEV_CLIENT_WAIT_MS',
|
|
311
|
+
'ASL_EXAMPLE_ANDROID_DEV_CLIENT_WAIT_MS',
|
|
312
|
+
]);
|
|
313
|
+
const androidDevClientReadyPattern = readStringArgOrEnv(args['android-dev-client-ready-pattern'], [
|
|
314
|
+
'ASL_ANDROID_DEV_CLIENT_READY_PATTERN',
|
|
315
|
+
'ASL_EXAMPLE_ANDROID_DEV_CLIENT_READY_PATTERN',
|
|
316
|
+
]);
|
|
317
|
+
const androidDevClientReadyQuietMs = readStringArgOrEnv(args['android-dev-client-ready-quiet-ms'], [
|
|
318
|
+
'ASL_ANDROID_DEV_CLIENT_READY_QUIET_MS',
|
|
319
|
+
'ASL_EXAMPLE_ANDROID_DEV_CLIENT_READY_QUIET_MS',
|
|
320
|
+
]);
|
|
321
|
+
const androidDevClientReadyTimeoutMs = readStringArgOrEnv(args['android-dev-client-ready-timeout-ms'], [
|
|
322
|
+
'ASL_ANDROID_DEV_CLIENT_READY_TIMEOUT_MS',
|
|
323
|
+
'ASL_EXAMPLE_ANDROID_DEV_CLIENT_READY_TIMEOUT_MS',
|
|
324
|
+
]);
|
|
325
|
+
const androidProfileSessionStorage = readStringArgOrEnv(args['android-profile-session-storage'], [
|
|
326
|
+
'ASL_ANDROID_PROFILE_SESSION_STORAGE',
|
|
327
|
+
'ASL_EXAMPLE_ANDROID_PROFILE_SESSION_STORAGE',
|
|
328
|
+
]);
|
|
329
|
+
const androidProfileSessionStorageKey = readStringArgOrEnv(args['android-profile-session-storage-key'], [
|
|
330
|
+
'ASL_ANDROID_PROFILE_SESSION_STORAGE_KEY',
|
|
331
|
+
'ASL_EXAMPLE_ANDROID_PROFILE_SESSION_STORAGE_KEY',
|
|
332
|
+
]);
|
|
333
|
+
const androidProfileCommandStorageKey = readStringArgOrEnv(args['android-profile-command-storage-key'], [
|
|
334
|
+
'ASL_ANDROID_PROFILE_COMMAND_STORAGE_KEY',
|
|
335
|
+
'ASL_EXAMPLE_ANDROID_PROFILE_COMMAND_STORAGE_KEY',
|
|
336
|
+
]);
|
|
337
|
+
const agentDeviceSession = readStringArgOrEnv(args['agent-device-session'], [
|
|
338
|
+
'ASL_ANDROID_AGENT_DEVICE_SESSION',
|
|
339
|
+
'ASL_EXAMPLE_ANDROID_AGENT_DEVICE_SESSION',
|
|
340
|
+
]);
|
|
341
|
+
const agentDeviceSessionMode = readStringArgOrEnv(args['agent-device-session-mode'], [
|
|
342
|
+
'ASL_ANDROID_AGENT_DEVICE_SESSION_MODE',
|
|
343
|
+
'ASL_EXAMPLE_ANDROID_AGENT_DEVICE_SESSION_MODE',
|
|
344
|
+
]);
|
|
345
|
+
const outputDir = typeof args.out === 'string' ? path.resolve(args.out) : path.resolve('artifacts/asl/android-live');
|
|
346
|
+
const runSuffix = normalizeRunSuffix(args['run-suffix']);
|
|
347
|
+
const aggregateRunId = buildRunId(typeof args['run-id'] === 'string' ? args['run-id'] : 'android-live-proof', runSuffix);
|
|
348
|
+
const preflightRunId = buildRunId(`${scenarioId}-android-preflight`, runSuffix);
|
|
349
|
+
const profileRunId = buildRunId(`${scenarioId}-android-live`, runSuffix);
|
|
350
|
+
const agentDeviceRunId = buildRunId(`${scenarioId}-android-agent-device`, runSuffix);
|
|
351
|
+
const argentRunId = buildRunId(`${scenarioId}-android-argent`, runSuffix);
|
|
352
|
+
const enabledInteractionRunners = [
|
|
353
|
+
...(isEnabledFlag(args['agent-device-proof']) ? ['agent-device'] : []),
|
|
354
|
+
...(isEnabledFlag(args['argent-proof']) ? ['argent'] : []),
|
|
355
|
+
];
|
|
356
|
+
const runIdsByRunner = {
|
|
357
|
+
'agent-device': agentDeviceRunId,
|
|
358
|
+
argent: argentRunId,
|
|
359
|
+
};
|
|
360
|
+
const comparisonLane = enabledInteractionRunners.length > 0
|
|
361
|
+
? `${scenarioId}-android-live+${enabledInteractionRunners.join('+')}`
|
|
362
|
+
: `${scenarioId}-android-live`;
|
|
363
|
+
const preflightDir = path.join(outputDir, '_preflight', preflightRunId);
|
|
364
|
+
const preflight = await runAndroidAdbPreflight({
|
|
365
|
+
...(typeof args.adb === 'string' ? { adbPath: args.adb } : {}),
|
|
366
|
+
...(options.delay ? { delay: options.delay } : {}),
|
|
367
|
+
...(options.executor ? { executor: options.executor } : {}),
|
|
368
|
+
outputDir: preflightDir,
|
|
369
|
+
packageName,
|
|
370
|
+
...(reactNativeDebugHost ? { reactNativeDebugHost } : {}),
|
|
371
|
+
runId: preflightRunId,
|
|
372
|
+
...(serial ? { serial } : {}),
|
|
373
|
+
});
|
|
374
|
+
if (preflight.health.healthStatus !== 'passed') {
|
|
375
|
+
throw new Error(`Android live proof preflight failed; inspect ${preflight.runDir}/agent-summary.md.`);
|
|
376
|
+
}
|
|
377
|
+
const interactionProofs = [];
|
|
378
|
+
const profile = await runProfileAndroid({
|
|
379
|
+
...(typeof args.adb === 'string' ? { adb: args.adb } : {}),
|
|
380
|
+
'adb-capture': true,
|
|
381
|
+
'clear-logcat': true,
|
|
382
|
+
config: configPath,
|
|
383
|
+
'command-wait-ms': typeof args['command-wait-ms'] === 'string' ? args['command-wait-ms'] : '250',
|
|
384
|
+
launch: true,
|
|
385
|
+
'launch-wait-ms': typeof args['launch-wait-ms'] === 'string' ? args['launch-wait-ms'] : '1500',
|
|
386
|
+
'logcat-lines': typeof args['logcat-lines'] === 'string' ? args['logcat-lines'] : '1000',
|
|
387
|
+
out: outputDir,
|
|
388
|
+
...(packageName ? { package: packageName } : {}),
|
|
389
|
+
'profile-session': true,
|
|
390
|
+
...(reactNativeDebugHost ? { 'react-native-debug-host': reactNativeDebugHost } : {}),
|
|
391
|
+
...(androidDevClientUrl ? { 'android-dev-client-url': androidDevClientUrl } : {}),
|
|
392
|
+
...(androidDevClientWaitMs ? { 'android-dev-client-wait-ms': androidDevClientWaitMs } : {}),
|
|
393
|
+
...(androidDevClientReadyPattern ? { 'android-dev-client-ready-pattern': androidDevClientReadyPattern } : {}),
|
|
394
|
+
...(androidDevClientReadyQuietMs ? { 'android-dev-client-ready-quiet-ms': androidDevClientReadyQuietMs } : {}),
|
|
395
|
+
...(androidDevClientReadyTimeoutMs ? { 'android-dev-client-ready-timeout-ms': androidDevClientReadyTimeoutMs } : {}),
|
|
396
|
+
...(isEnabledFlag(args['android-profile-session-storage']) || androidProfileSessionStorage === 'true'
|
|
397
|
+
? { 'android-profile-session-storage': true }
|
|
398
|
+
: {}),
|
|
399
|
+
...(androidProfileSessionStorageKey ? { 'android-profile-session-storage-key': androidProfileSessionStorageKey } : {}),
|
|
400
|
+
...(androidProfileCommandStorageKey ? { 'android-profile-command-storage-key': androidProfileCommandStorageKey } : {}),
|
|
401
|
+
'run-id': profileRunId,
|
|
402
|
+
scenario: scenarioPath,
|
|
403
|
+
...(serial ? { serial } : {}),
|
|
404
|
+
...(typeof args['wait-ms'] === 'string' ? { 'wait-ms': args['wait-ms'] } : {}),
|
|
405
|
+
}, {
|
|
406
|
+
comparisonLane,
|
|
407
|
+
...(options.delay ? { delay: options.delay } : {}),
|
|
408
|
+
...(options.executor ? { executor: options.executor } : {}),
|
|
409
|
+
});
|
|
410
|
+
const profileTrusted = isTrustedProfileRun({ health: profile.health, verdict: profile.verdict });
|
|
411
|
+
let skippedInteractionProofs = [];
|
|
412
|
+
if (!profileTrusted) {
|
|
413
|
+
skippedInteractionProofs = buildSkippedInteractionProofs({
|
|
414
|
+
profileHealthStatus: profile.health.healthStatus,
|
|
415
|
+
profileVerdictStatus: profile.verdict.verdictStatus,
|
|
416
|
+
requestedRunners: enabledInteractionRunners,
|
|
417
|
+
runIdsByRunner,
|
|
418
|
+
scenarioId,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
if (profileTrusted && isEnabledFlag(args['agent-device-proof'])) {
|
|
422
|
+
const capture = await runAgentDeviceCapture({
|
|
423
|
+
...(typeof args['agent-device'] === 'string' ? { agentDevicePath: args['agent-device'] } : {}),
|
|
424
|
+
app: packageName,
|
|
425
|
+
...(options.agentDeviceExecutor ? { executor: options.agentDeviceExecutor } : {}),
|
|
426
|
+
open: true,
|
|
427
|
+
outputDir: path.join(outputDir, '_agent-device-captures', agentDeviceRunId),
|
|
428
|
+
platform: 'android',
|
|
429
|
+
runId: agentDeviceRunId,
|
|
430
|
+
scenario,
|
|
431
|
+
...(serial ? { serial } : {}),
|
|
432
|
+
...(agentDeviceSession ? { session: agentDeviceSession } : {}),
|
|
433
|
+
...(agentDeviceSessionMode
|
|
434
|
+
? { sessionMode: agentDeviceSessionMode }
|
|
435
|
+
: {}),
|
|
436
|
+
waitMs: parsePositiveInteger(args['agent-device-wait-ms'], 1000),
|
|
437
|
+
});
|
|
438
|
+
interactionProofs.push({
|
|
439
|
+
label: 'interaction-agent-device',
|
|
440
|
+
runDir: capture.runDir,
|
|
441
|
+
runId: agentDeviceRunId,
|
|
442
|
+
runnerId: 'agent-device',
|
|
443
|
+
scenarioId,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
if (profileTrusted && isEnabledFlag(args['argent-proof'])) {
|
|
447
|
+
const argentBaseArgs = parseArgentBaseArgs(process.env.ASL_ARGENT_BASE_ARGS);
|
|
448
|
+
const adbPath = typeof args.adb === 'string' ? args.adb : 'adb';
|
|
449
|
+
await runAndroidRunnerAdbCommand({
|
|
450
|
+
adbPath,
|
|
451
|
+
args: buildAndroidRunnerAdbArgs(serial ?? null, ['logcat', '-c']),
|
|
452
|
+
...(options.executor ? { executor: options.executor } : {}),
|
|
453
|
+
});
|
|
454
|
+
const capture = await runArgentCapture({
|
|
455
|
+
app: packageName,
|
|
456
|
+
argentCommand: process.env.ASL_ARGENT_BIN || 'argent',
|
|
457
|
+
...(argentBaseArgs ? { baseArgs: argentBaseArgs } : {}),
|
|
458
|
+
commandTimeoutMs: parsePositiveInteger(process.env.ASL_ARGENT_COMMAND_TIMEOUT_MS, 60_000),
|
|
459
|
+
deviceId: serial ?? 'emulator-5554',
|
|
460
|
+
...(options.delay ? { delay: options.delay } : {}),
|
|
461
|
+
...(options.argentExecutor ? { executor: options.argentExecutor } : {}),
|
|
462
|
+
outputDir: path.join(outputDir, '_argent-captures', argentRunId),
|
|
463
|
+
platform: 'android',
|
|
464
|
+
runId: argentRunId,
|
|
465
|
+
scenario,
|
|
466
|
+
});
|
|
467
|
+
await attachArgentAndroidRunnerDiagnostics({
|
|
468
|
+
adbPath,
|
|
469
|
+
capture,
|
|
470
|
+
...(options.executor ? { executor: options.executor } : {}),
|
|
471
|
+
serial: serial ?? null,
|
|
472
|
+
});
|
|
473
|
+
interactionProofs.push({
|
|
474
|
+
label: 'interaction-argent',
|
|
475
|
+
runDir: capture.runDir,
|
|
476
|
+
runId: argentRunId,
|
|
477
|
+
runnerId: 'argent',
|
|
478
|
+
scenarioId,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
const profiles = [{
|
|
482
|
+
label: scenarioId,
|
|
483
|
+
runDir: profile.runDir,
|
|
484
|
+
runId: profileRunId,
|
|
485
|
+
scenarioId,
|
|
486
|
+
}];
|
|
487
|
+
const comparisons = profileTrusted && isEnabledFlag(args['compare-latest'])
|
|
488
|
+
? await compareLiveProfilesToLatest({ outputDir, profiles })
|
|
489
|
+
: [];
|
|
490
|
+
const aggregateSummary = await writeLiveProofSummary({
|
|
491
|
+
comparisons,
|
|
492
|
+
interactionProofs,
|
|
493
|
+
outputDir,
|
|
494
|
+
platform: 'android',
|
|
495
|
+
preflightDir: preflight.runDir,
|
|
496
|
+
preflightRunId,
|
|
497
|
+
profiles,
|
|
498
|
+
runId: aggregateRunId,
|
|
499
|
+
skippedInteractionProofs,
|
|
500
|
+
});
|
|
501
|
+
const result = {
|
|
502
|
+
aggregateSummary,
|
|
503
|
+
outputDir,
|
|
504
|
+
preflightDir: preflight.runDir,
|
|
505
|
+
profileDir: profile.runDir,
|
|
506
|
+
};
|
|
507
|
+
if (isEnabledFlag(args['fail-on-regression'])) {
|
|
508
|
+
assertNoRegressedComparisons({ comparisons, result });
|
|
509
|
+
}
|
|
510
|
+
assertAggregatePassed(result);
|
|
511
|
+
return result;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Runs the generic Android live CLI.
|
|
515
|
+
*
|
|
516
|
+
* @returns {Promise<void>}
|
|
517
|
+
*/
|
|
518
|
+
async function main() {
|
|
519
|
+
const argv = process.argv.slice(2);
|
|
520
|
+
if (hasHelpFlag(argv)) {
|
|
521
|
+
usage(process.stdout);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
loadAslLocalEnv();
|
|
525
|
+
const args = parseArgs(argv);
|
|
526
|
+
if (typeof args.config !== 'string' || typeof args.scenario !== 'string') {
|
|
527
|
+
usage();
|
|
528
|
+
process.exitCode = 1;
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const result = await runAndroidLiveProof(args);
|
|
532
|
+
process.stdout.write(`${result.aggregateSummary.summaryPath}\n`);
|
|
533
|
+
}
|
|
534
|
+
if (require.main === module) {
|
|
535
|
+
main().catch((error) => {
|
|
536
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
537
|
+
process.exitCode = 1;
|
|
538
|
+
});
|
|
539
|
+
}
|