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,407 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SchemaValidationError = exports.SCHEMAS = void 0;
|
|
4
|
+
exports.assertValidJson = assertValidJson;
|
|
5
|
+
exports.formatValidationErrorMessage = formatValidationErrorMessage;
|
|
6
|
+
exports.validateJson = validateJson;
|
|
7
|
+
const fs = require('node:fs');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
/**
|
|
10
|
+
* Error thrown when a manifest or generated artifact does not satisfy its schema.
|
|
11
|
+
*/
|
|
12
|
+
class SchemaValidationError extends Error {
|
|
13
|
+
label;
|
|
14
|
+
errors;
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} label
|
|
17
|
+
* @param {{code: string, path: string, message: string}[]} errors
|
|
18
|
+
*/
|
|
19
|
+
constructor(label, errors) {
|
|
20
|
+
super(formatValidationErrorMessage(label, errors));
|
|
21
|
+
this.name = 'SchemaValidationError';
|
|
22
|
+
this.label = label;
|
|
23
|
+
this.errors = errors;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.SchemaValidationError = SchemaValidationError;
|
|
27
|
+
/**
|
|
28
|
+
* Reads and parses a JSON file.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} filePath
|
|
31
|
+
* @returns {unknown}
|
|
32
|
+
*/
|
|
33
|
+
function readJson(filePath) {
|
|
34
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Loads a schema from the repo-local `schemas` directory.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} relativePath
|
|
40
|
+
* @returns {Record<string, unknown>}
|
|
41
|
+
*/
|
|
42
|
+
function loadSchema(relativePath) {
|
|
43
|
+
const sourcePath = path.join(__dirname, '..', 'schemas', relativePath);
|
|
44
|
+
const compiledPath = path.join(__dirname, '..', '..', 'schemas', relativePath);
|
|
45
|
+
const schemaPath = fs.existsSync(sourcePath) ? sourcePath : compiledPath;
|
|
46
|
+
return readJson(schemaPath);
|
|
47
|
+
}
|
|
48
|
+
const SCHEMAS = {
|
|
49
|
+
budgetVerdict: loadSchema('budget-verdict.schema.json'),
|
|
50
|
+
causalRun: loadSchema('causal-run.schema.json'),
|
|
51
|
+
comparison: loadSchema('comparison.schema.json'),
|
|
52
|
+
health: loadSchema('health.schema.json'),
|
|
53
|
+
liveProof: loadSchema('live-proof.schema.json'),
|
|
54
|
+
liveProofSet: loadSchema('live-proof-set.schema.json'),
|
|
55
|
+
manifest: loadSchema('manifest.schema.json'),
|
|
56
|
+
metrics: loadSchema('metrics.schema.json'),
|
|
57
|
+
projectValidation: loadSchema('project-validation.schema.json'),
|
|
58
|
+
scenario: loadSchema('scenario.schema.json'),
|
|
59
|
+
runnerCapabilities: loadSchema('runner-capabilities.schema.json'),
|
|
60
|
+
verdict: loadSchema('verdict.schema.json'),
|
|
61
|
+
};
|
|
62
|
+
exports.SCHEMAS = SCHEMAS;
|
|
63
|
+
/**
|
|
64
|
+
* Formats a validation path as a JSONPath-like string.
|
|
65
|
+
*
|
|
66
|
+
* @param {(string | number)[]} pathSegments
|
|
67
|
+
* @returns {string}
|
|
68
|
+
*/
|
|
69
|
+
function formatPath(pathSegments) {
|
|
70
|
+
if (pathSegments.length === 0) {
|
|
71
|
+
return '$';
|
|
72
|
+
}
|
|
73
|
+
return pathSegments.reduce((result, segment) => {
|
|
74
|
+
if (typeof segment === 'number') {
|
|
75
|
+
return `${result}[${segment}]`;
|
|
76
|
+
}
|
|
77
|
+
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(segment)) {
|
|
78
|
+
return `${result}.${segment}`;
|
|
79
|
+
}
|
|
80
|
+
return `${result}[${JSON.stringify(segment)}]`;
|
|
81
|
+
}, '$');
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Stringifies object values with stable key ordering for `uniqueItems` checks.
|
|
85
|
+
*
|
|
86
|
+
* @param {unknown} value
|
|
87
|
+
* @returns {string | undefined}
|
|
88
|
+
*/
|
|
89
|
+
function stableStringify(value) {
|
|
90
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
91
|
+
return JSON.stringify(value);
|
|
92
|
+
}
|
|
93
|
+
return JSON.stringify(Object.keys(value)
|
|
94
|
+
.sort()
|
|
95
|
+
.reduce((result, key) => {
|
|
96
|
+
const source = value;
|
|
97
|
+
result[key] = source[key];
|
|
98
|
+
return result;
|
|
99
|
+
}, {}));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Formats enum values for validation error messages.
|
|
103
|
+
*
|
|
104
|
+
* @param {unknown[]} values
|
|
105
|
+
* @returns {string}
|
|
106
|
+
*/
|
|
107
|
+
function describeAllowedValues(values) {
|
|
108
|
+
return values.map((value) => JSON.stringify(value)).join(', ');
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Resolves local JSON Schema references such as `#/$defs/capability`.
|
|
112
|
+
*
|
|
113
|
+
* @param {Record<string, unknown>} rootSchema
|
|
114
|
+
* @param {string} ref
|
|
115
|
+
* @returns {Record<string, unknown>}
|
|
116
|
+
*/
|
|
117
|
+
function resolveLocalRef(rootSchema, ref) {
|
|
118
|
+
if (!ref.startsWith('#/')) {
|
|
119
|
+
throw new Error(`Only local schema refs are supported: ${ref}`);
|
|
120
|
+
}
|
|
121
|
+
return ref
|
|
122
|
+
.slice(2)
|
|
123
|
+
.split('/')
|
|
124
|
+
.reduce((current, token) => {
|
|
125
|
+
const key = token.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
126
|
+
if (!current || typeof current !== 'object' || !(key in current)) {
|
|
127
|
+
throw new Error(`Schema ref could not be resolved: ${ref}`);
|
|
128
|
+
}
|
|
129
|
+
return current[key];
|
|
130
|
+
}, rootSchema);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Returns true when a value satisfies a schema without recording validation errors.
|
|
134
|
+
*
|
|
135
|
+
* @param {unknown} value
|
|
136
|
+
* @param {Record<string, unknown>} schema
|
|
137
|
+
* @param {Record<string, unknown>} rootSchema
|
|
138
|
+
* @returns {boolean}
|
|
139
|
+
*/
|
|
140
|
+
function matchesSchema(value, schema, rootSchema) {
|
|
141
|
+
return validateSchema(value, schema, rootSchema, [], []).length === 0;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Checks a JavaScript value against one JSON Schema primitive type.
|
|
145
|
+
*
|
|
146
|
+
* @param {unknown} value
|
|
147
|
+
* @param {string} type
|
|
148
|
+
* @returns {boolean}
|
|
149
|
+
*/
|
|
150
|
+
function isType(value, type) {
|
|
151
|
+
if (type === 'null') {
|
|
152
|
+
return value === null;
|
|
153
|
+
}
|
|
154
|
+
if (type === 'array') {
|
|
155
|
+
return Array.isArray(value);
|
|
156
|
+
}
|
|
157
|
+
if (type === 'object') {
|
|
158
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
159
|
+
}
|
|
160
|
+
if (type === 'integer') {
|
|
161
|
+
return Number.isInteger(value);
|
|
162
|
+
}
|
|
163
|
+
if (type === 'number') {
|
|
164
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
165
|
+
}
|
|
166
|
+
return typeof value === type;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Validates the `type` keyword and records one error on mismatch.
|
|
170
|
+
*
|
|
171
|
+
* @param {{value: unknown, expectedType: string | string[], pathSegments: (string | number)[], errors: {code: string, path: string, message: string}[]}} options
|
|
172
|
+
* @returns {boolean}
|
|
173
|
+
*/
|
|
174
|
+
function validateType({ value, expectedType, pathSegments, errors, }) {
|
|
175
|
+
const expectedTypes = Array.isArray(expectedType) ? expectedType : [expectedType];
|
|
176
|
+
if (!expectedTypes.some((type) => isType(value, type))) {
|
|
177
|
+
errors.push({
|
|
178
|
+
code: 'invalid_type',
|
|
179
|
+
path: formatPath(pathSegments),
|
|
180
|
+
message: `Expected ${expectedTypes.join(' or ')}.`,
|
|
181
|
+
});
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Validates the JSON Schema subset used by this package's public contracts.
|
|
188
|
+
*
|
|
189
|
+
* @param {unknown} value
|
|
190
|
+
* @param {Record<string, unknown>} schema
|
|
191
|
+
* @param {Record<string, unknown>} rootSchema
|
|
192
|
+
* @param {(string | number)[]} [pathSegments]
|
|
193
|
+
* @param {{code: string, path: string, message: string}[]} [errors]
|
|
194
|
+
* @returns {{code: string, path: string, message: string}[]}
|
|
195
|
+
*/
|
|
196
|
+
function validateSchema(value, schema, rootSchema, pathSegments = [], errors = []) {
|
|
197
|
+
if (!schema || typeof schema !== 'object') {
|
|
198
|
+
return errors;
|
|
199
|
+
}
|
|
200
|
+
if (schema.$ref) {
|
|
201
|
+
validateSchema(value, resolveLocalRef(rootSchema, schema.$ref), rootSchema, pathSegments, errors);
|
|
202
|
+
}
|
|
203
|
+
for (const childSchema of schema.allOf ?? []) {
|
|
204
|
+
validateSchema(value, childSchema, rootSchema, pathSegments, errors);
|
|
205
|
+
}
|
|
206
|
+
if ('const' in schema && !Object.is(schema.const, value)) {
|
|
207
|
+
errors.push({
|
|
208
|
+
code: 'invalid_const',
|
|
209
|
+
path: formatPath(pathSegments),
|
|
210
|
+
message: `Expected ${JSON.stringify(schema.const)}.`,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (schema.if && matchesSchema(value, schema.if, rootSchema)) {
|
|
214
|
+
if (schema.then) {
|
|
215
|
+
validateSchema(value, schema.then, rootSchema, pathSegments, errors);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else if (schema.if && schema.else) {
|
|
219
|
+
validateSchema(value, schema.else, rootSchema, pathSegments, errors);
|
|
220
|
+
}
|
|
221
|
+
if (schema.not && matchesSchema(value, schema.not, rootSchema)) {
|
|
222
|
+
errors.push({
|
|
223
|
+
code: 'schema_not',
|
|
224
|
+
path: formatPath(pathSegments),
|
|
225
|
+
message: schema.not.description ?? 'Value must not match the disallowed schema.',
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
if (schema.type && !validateType({ value, expectedType: schema.type, pathSegments, errors })) {
|
|
229
|
+
return errors;
|
|
230
|
+
}
|
|
231
|
+
if (schema.enum && !schema.enum.some((allowed) => Object.is(allowed, value))) {
|
|
232
|
+
errors.push({
|
|
233
|
+
code: 'invalid_enum',
|
|
234
|
+
path: formatPath(pathSegments),
|
|
235
|
+
message: `Expected one of ${describeAllowedValues(schema.enum)}.`,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (typeof schema.pattern === 'string' && typeof value === 'string') {
|
|
239
|
+
const regex = new RegExp(schema.pattern, 'u');
|
|
240
|
+
if (!regex.test(value)) {
|
|
241
|
+
errors.push({
|
|
242
|
+
code: 'invalid_pattern',
|
|
243
|
+
path: formatPath(pathSegments),
|
|
244
|
+
message: `Expected value to match ${schema.pattern}.`,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (typeof schema.minLength === 'number' && typeof value === 'string' && value.length < schema.minLength) {
|
|
249
|
+
errors.push({
|
|
250
|
+
code: 'too_short',
|
|
251
|
+
path: formatPath(pathSegments),
|
|
252
|
+
message: `Expected at least ${schema.minLength} character(s).`,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
if (typeof schema.minimum === 'number' && typeof value === 'number' && value < schema.minimum) {
|
|
256
|
+
errors.push({
|
|
257
|
+
code: 'below_minimum',
|
|
258
|
+
path: formatPath(pathSegments),
|
|
259
|
+
message: `Expected value to be >= ${schema.minimum}.`,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (Array.isArray(value)) {
|
|
263
|
+
validateArray(value, schema, rootSchema, pathSegments, errors);
|
|
264
|
+
}
|
|
265
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
266
|
+
validateObject(value, schema, rootSchema, pathSegments, errors);
|
|
267
|
+
}
|
|
268
|
+
return errors;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Validates array-specific schema keywords.
|
|
272
|
+
*
|
|
273
|
+
* @param {unknown[]} value
|
|
274
|
+
* @param {Record<string, unknown>} schema
|
|
275
|
+
* @param {Record<string, unknown>} rootSchema
|
|
276
|
+
* @param {(string | number)[]} pathSegments
|
|
277
|
+
* @param {{code: string, path: string, message: string}[]} errors
|
|
278
|
+
* @returns {void}
|
|
279
|
+
*/
|
|
280
|
+
function validateArray(value, schema, rootSchema, pathSegments, errors) {
|
|
281
|
+
if (typeof schema.minItems === 'number' && value.length < schema.minItems) {
|
|
282
|
+
errors.push({
|
|
283
|
+
code: 'too_few_items',
|
|
284
|
+
path: formatPath(pathSegments),
|
|
285
|
+
message: `Expected at least ${schema.minItems} item(s).`,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
if (schema.uniqueItems === true) {
|
|
289
|
+
const seen = new Set();
|
|
290
|
+
for (const item of value) {
|
|
291
|
+
const key = stableStringify(item);
|
|
292
|
+
if (seen.has(key)) {
|
|
293
|
+
errors.push({
|
|
294
|
+
code: 'duplicate_item',
|
|
295
|
+
path: formatPath(pathSegments),
|
|
296
|
+
message: 'Expected array items to be unique.',
|
|
297
|
+
});
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
seen.add(key);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (schema.items) {
|
|
304
|
+
const itemSchema = schema.items;
|
|
305
|
+
value.forEach((item, index) => validateSchema(item, itemSchema, rootSchema, [...pathSegments, index], errors));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Validates object-specific schema keywords.
|
|
310
|
+
*
|
|
311
|
+
* @param {Record<string, unknown>} value
|
|
312
|
+
* @param {Record<string, unknown>} schema
|
|
313
|
+
* @param {Record<string, unknown>} rootSchema
|
|
314
|
+
* @param {(string | number)[]} pathSegments
|
|
315
|
+
* @param {{code: string, path: string, message: string}[]} errors
|
|
316
|
+
* @returns {void}
|
|
317
|
+
*/
|
|
318
|
+
function validateObject(value, schema, rootSchema, pathSegments, errors) {
|
|
319
|
+
if (typeof schema.minProperties === 'number' && Object.keys(value).length < schema.minProperties) {
|
|
320
|
+
errors.push({
|
|
321
|
+
code: 'too_few_properties',
|
|
322
|
+
path: formatPath(pathSegments),
|
|
323
|
+
message: `Expected at least ${schema.minProperties} properties.`,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
for (const requiredKey of schema.required ?? []) {
|
|
327
|
+
if (!(requiredKey in value)) {
|
|
328
|
+
errors.push({
|
|
329
|
+
code: 'missing_required_property',
|
|
330
|
+
path: formatPath([...pathSegments, requiredKey]),
|
|
331
|
+
message: `Missing required property \`${requiredKey}\`.`,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const definedProperties = schema.properties ?? {};
|
|
336
|
+
for (const [key, propertySchema] of Object.entries(definedProperties)) {
|
|
337
|
+
if (key in value) {
|
|
338
|
+
validateSchema(value[key], propertySchema, rootSchema, [...pathSegments, key], errors);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
for (const key of Object.keys(value)) {
|
|
342
|
+
if (key in definedProperties) {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (schema.additionalProperties === false) {
|
|
346
|
+
errors.push({
|
|
347
|
+
code: 'additional_property',
|
|
348
|
+
path: formatPath([...pathSegments, key]),
|
|
349
|
+
message: `Unexpected property \`${key}\`.`,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
else if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
|
|
353
|
+
validateSchema(value[key], schema.additionalProperties, rootSchema, [...pathSegments, key], errors);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Validates a value against a schema and returns structured errors instead of throwing.
|
|
359
|
+
*
|
|
360
|
+
* @param {unknown} value
|
|
361
|
+
* @param {Record<string, unknown>} schema
|
|
362
|
+
* @param {string} [label]
|
|
363
|
+
* @returns {{valid: boolean, errors: {code: string, path: string, message: string}[], message: string}}
|
|
364
|
+
*/
|
|
365
|
+
function validateJson(value, schema, label = 'JSON document') {
|
|
366
|
+
const errors = validateSchema(value, schema, schema);
|
|
367
|
+
if (errors.length > 0) {
|
|
368
|
+
return {
|
|
369
|
+
valid: false,
|
|
370
|
+
errors,
|
|
371
|
+
message: formatValidationErrorMessage(label, errors),
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
valid: true,
|
|
376
|
+
errors: [],
|
|
377
|
+
message: '',
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Validates a value against a schema, throwing `SchemaValidationError` on failure.
|
|
382
|
+
*
|
|
383
|
+
* @param {unknown} value
|
|
384
|
+
* @param {Record<string, unknown>} schema
|
|
385
|
+
* @param {string} label
|
|
386
|
+
* @returns {unknown}
|
|
387
|
+
*/
|
|
388
|
+
function assertValidJson(value, schema, label) {
|
|
389
|
+
const result = validateJson(value, schema, label);
|
|
390
|
+
if (!result.valid) {
|
|
391
|
+
throw new SchemaValidationError(label, result.errors);
|
|
392
|
+
}
|
|
393
|
+
return value;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Formats schema errors as a CLI-readable message.
|
|
397
|
+
*
|
|
398
|
+
* @param {string} label
|
|
399
|
+
* @param {{path: string, message: string}[]} errors
|
|
400
|
+
* @returns {string}
|
|
401
|
+
*/
|
|
402
|
+
function formatValidationErrorMessage(label, errors) {
|
|
403
|
+
return [
|
|
404
|
+
`${label} failed schema validation:`,
|
|
405
|
+
...errors.map((error) => `- ${error.path}: ${error.message}`),
|
|
406
|
+
].join('\n');
|
|
407
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './core/agent-summary';
|
|
2
|
+
export * from './core/artifact-contract';
|
|
3
|
+
export * from './core/artifact-layout';
|
|
4
|
+
export * from './core/artifact-writer';
|
|
5
|
+
export * from './core/comparison';
|
|
6
|
+
export * from './core/evidence-interpreter';
|
|
7
|
+
export * from './core/execution-plan';
|
|
8
|
+
export * from './core/planner';
|
|
9
|
+
export * from './core/ports';
|
|
10
|
+
export * from './core/run-index';
|
|
11
|
+
export * from './core/schema-validator';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./core/agent-summary"), exports);
|
|
18
|
+
__exportStar(require("./core/artifact-contract"), exports);
|
|
19
|
+
__exportStar(require("./core/artifact-layout"), exports);
|
|
20
|
+
__exportStar(require("./core/artifact-writer"), exports);
|
|
21
|
+
__exportStar(require("./core/comparison"), exports);
|
|
22
|
+
__exportStar(require("./core/evidence-interpreter"), exports);
|
|
23
|
+
__exportStar(require("./core/execution-plan"), exports);
|
|
24
|
+
__exportStar(require("./core/planner"), exports);
|
|
25
|
+
__exportStar(require("./core/ports"), exports);
|
|
26
|
+
__exportStar(require("./core/run-index"), exports);
|
|
27
|
+
__exportStar(require("./core/schema-validator"), exports);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
type AgentDevicePlatform = 'android' | 'apple' | 'ios' | 'linux' | 'macos';
|
|
2
|
+
type AgentDeviceCommandResult = {
|
|
3
|
+
action: string;
|
|
4
|
+
args: string[];
|
|
5
|
+
capturePath?: string;
|
|
6
|
+
command: string;
|
|
7
|
+
exitCode: number;
|
|
8
|
+
rawFileName: string;
|
|
9
|
+
stderr: string;
|
|
10
|
+
stdout: string;
|
|
11
|
+
};
|
|
12
|
+
type AgentDeviceCommandExecutor = (command: string, args: string[]) => Promise<{
|
|
13
|
+
args: string[];
|
|
14
|
+
command: string;
|
|
15
|
+
exitCode: number;
|
|
16
|
+
stderr: string;
|
|
17
|
+
stdout: string;
|
|
18
|
+
}>;
|
|
19
|
+
type AgentDeviceDriverOptions = {
|
|
20
|
+
agentDevicePath: string;
|
|
21
|
+
device?: string;
|
|
22
|
+
executor: AgentDeviceCommandExecutor;
|
|
23
|
+
extraArgs?: string[];
|
|
24
|
+
json?: boolean;
|
|
25
|
+
platform: AgentDevicePlatform;
|
|
26
|
+
serial?: string;
|
|
27
|
+
session?: string;
|
|
28
|
+
target?: 'desktop' | 'mobile' | 'tv';
|
|
29
|
+
udid?: string;
|
|
30
|
+
};
|
|
31
|
+
type AgentDeviceSelector = {
|
|
32
|
+
kind: string;
|
|
33
|
+
match?: string;
|
|
34
|
+
value: string;
|
|
35
|
+
};
|
|
36
|
+
type AgentDeviceAlertAction = 'accept' | 'dismiss' | 'get' | 'wait';
|
|
37
|
+
type AgentDeviceAlertOptions = {
|
|
38
|
+
action?: AgentDeviceAlertAction;
|
|
39
|
+
rawFileName?: string;
|
|
40
|
+
timeoutMs?: number;
|
|
41
|
+
};
|
|
42
|
+
type AgentDeviceAssertVisibleOptions = {
|
|
43
|
+
rawFileName?: string;
|
|
44
|
+
selector: AgentDeviceSelector;
|
|
45
|
+
};
|
|
46
|
+
type AgentDeviceInspectTreeOptions = {
|
|
47
|
+
interactive?: boolean;
|
|
48
|
+
rawFileName?: string;
|
|
49
|
+
};
|
|
50
|
+
type AgentDeviceOpenOptions = {
|
|
51
|
+
appOrUrl: string;
|
|
52
|
+
rawFileName?: string;
|
|
53
|
+
url?: string;
|
|
54
|
+
};
|
|
55
|
+
type AgentDeviceReadLogsOptions = {
|
|
56
|
+
rawFileName?: string;
|
|
57
|
+
};
|
|
58
|
+
type AgentDeviceScreenshotOptions = {
|
|
59
|
+
outputPath: string;
|
|
60
|
+
rawFileName?: string;
|
|
61
|
+
};
|
|
62
|
+
type AgentDeviceScrollOptions = {
|
|
63
|
+
amount?: string;
|
|
64
|
+
direction?: string;
|
|
65
|
+
durationMs?: number;
|
|
66
|
+
endX?: number;
|
|
67
|
+
endY?: number;
|
|
68
|
+
pixels?: number;
|
|
69
|
+
rawFileName?: string;
|
|
70
|
+
startX?: number;
|
|
71
|
+
startY?: number;
|
|
72
|
+
};
|
|
73
|
+
type AgentDeviceTapOptions = {
|
|
74
|
+
rawFileName?: string;
|
|
75
|
+
ref?: string;
|
|
76
|
+
selector?: AgentDeviceSelector;
|
|
77
|
+
x?: number;
|
|
78
|
+
y?: number;
|
|
79
|
+
};
|
|
80
|
+
type AgentDeviceDriver = {
|
|
81
|
+
alert: (options?: AgentDeviceAlertOptions) => Promise<AgentDeviceCommandResult>;
|
|
82
|
+
assertVisible: (options: AgentDeviceAssertVisibleOptions) => Promise<AgentDeviceCommandResult>;
|
|
83
|
+
close: (app: string) => Promise<AgentDeviceCommandResult>;
|
|
84
|
+
inspectTree: (options?: AgentDeviceInspectTreeOptions) => Promise<AgentDeviceCommandResult>;
|
|
85
|
+
open: (options: AgentDeviceOpenOptions) => Promise<AgentDeviceCommandResult>;
|
|
86
|
+
readLogs: (options?: AgentDeviceReadLogsOptions) => Promise<AgentDeviceCommandResult>;
|
|
87
|
+
screenshot: (options: AgentDeviceScreenshotOptions) => Promise<AgentDeviceCommandResult>;
|
|
88
|
+
scroll: (options?: AgentDeviceScrollOptions) => Promise<AgentDeviceCommandResult>;
|
|
89
|
+
tap: (options: AgentDeviceTapOptions) => Promise<AgentDeviceCommandResult>;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Builds global CLI flags shared by all agent-device driver actions.
|
|
93
|
+
*
|
|
94
|
+
* @param {AgentDeviceDriverOptions} options
|
|
95
|
+
* @returns {string[]}
|
|
96
|
+
*/
|
|
97
|
+
declare function buildAgentDeviceGlobalArgs(options: AgentDeviceDriverOptions): string[];
|
|
98
|
+
/**
|
|
99
|
+
* Formats one portable selector as an agent-device selector expression.
|
|
100
|
+
*
|
|
101
|
+
* @param {AgentDeviceSelector} selector
|
|
102
|
+
* @returns {string}
|
|
103
|
+
*/
|
|
104
|
+
declare function formatAgentDeviceSelector(selector: AgentDeviceSelector): string;
|
|
105
|
+
/**
|
|
106
|
+
* Combines stdout and stderr into the raw evidence text written by callers.
|
|
107
|
+
*
|
|
108
|
+
* @param {{stdout: string, stderr: string}} result
|
|
109
|
+
* @returns {string}
|
|
110
|
+
*/
|
|
111
|
+
declare function formatAgentDeviceRawOutput(result: {
|
|
112
|
+
stdout: string;
|
|
113
|
+
stderr: string;
|
|
114
|
+
}): string;
|
|
115
|
+
/**
|
|
116
|
+
* Creates an agent-device-backed driver for portable mobile actions.
|
|
117
|
+
*
|
|
118
|
+
* The adapter shells out through an injected executor so consumers can choose
|
|
119
|
+
* local, remote, or test doubles without making agent-device a package dependency.
|
|
120
|
+
*
|
|
121
|
+
* @param {AgentDeviceDriverOptions} options
|
|
122
|
+
* @returns {AgentDeviceDriver}
|
|
123
|
+
*/
|
|
124
|
+
declare function createAgentDeviceDriver(options: AgentDeviceDriverOptions): AgentDeviceDriver;
|
|
125
|
+
export { buildAgentDeviceGlobalArgs, createAgentDeviceDriver, formatAgentDeviceRawOutput, formatAgentDeviceSelector, };
|
|
126
|
+
export type { AgentDeviceAlertAction, AgentDeviceAlertOptions, AgentDeviceAssertVisibleOptions, AgentDeviceCommandExecutor, AgentDeviceCommandResult, AgentDeviceDriver, AgentDeviceDriverOptions, AgentDeviceInspectTreeOptions, AgentDeviceOpenOptions, AgentDevicePlatform, AgentDeviceReadLogsOptions, AgentDeviceScreenshotOptions, AgentDeviceScrollOptions, AgentDeviceSelector, AgentDeviceTapOptions, };
|