agent-scenario-loop 0.1.3 → 0.1.5
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/app/profile-session.ts +263 -17
- package/dist/core/artifact-contract.d.ts +6 -4
- package/dist/core/artifact-contract.js +164 -15
- package/dist/core/artifact-layout.d.ts +2 -0
- package/dist/core/artifact-layout.js +2 -0
- package/dist/core/planner.js +4 -3
- package/dist/core/schema-validator.d.ts +1 -0
- package/dist/core/schema-validator.js +1 -0
- package/dist/runner/android-adb-driver.d.ts +7 -2
- package/dist/runner/android-adb-driver.js +7 -1
- package/dist/runner/android-adb.d.ts +40 -5
- package/dist/runner/android-adb.js +1046 -664
- package/dist/runner/ios-simctl.d.ts +1 -0
- package/dist/runner/ios-simctl.js +1 -0
- package/dist/runner/profile-android.d.ts +11 -1
- package/dist/runner/profile-android.js +266 -25
- package/dist/runner/profile-ios.d.ts +3 -2
- package/dist/runner/profile-ios.js +252 -22
- package/dist/runner/profile-mobile.d.ts +63 -4
- package/dist/runner/profile-mobile.js +1002 -20
- package/dist/runner/validate-project.js +3 -0
- package/dist/scripts/consumer-rehearsal.d.ts +127 -0
- package/dist/scripts/consumer-rehearsal.js +774 -0
- package/dist/scripts/downstream-local-package-gate.d.ts +2 -0
- package/dist/scripts/downstream-local-package-gate.js +264 -0
- package/dist/scripts/package-smoke.d.ts +104 -0
- package/dist/scripts/package-smoke.js +2304 -0
- package/dist/scripts/release-check.d.ts +47 -0
- package/dist/scripts/release-check.js +117 -0
- package/dist/scripts/release-readiness.d.ts +2 -0
- package/dist/scripts/release-readiness.js +539 -0
- package/docs/adapters.md +3 -1
- package/docs/api.md +2 -2
- package/docs/authoring.md +34 -2
- package/docs/consumer-rehearsal.md +33 -1
- package/docs/contracts.md +16 -2
- package/docs/live-proofs.md +12 -4
- package/examples/mobile-app/runner-manifests/evidence-provider.json +3 -3
- package/examples/mobile-app/scripts/asl-capture-profiler-provider.mjs +25 -0
- package/examples/runners/README.md +3 -3
- package/examples/runners/axe-accessibility-provider.json +2 -2
- package/examples/runners/script-accessibility-provider.json +2 -2
- package/examples/runners/script-memory-provider.json +2 -2
- package/examples/runners/script-network-provider.json +2 -2
- package/examples/runners/script-profiler-provider.json +2 -2
- package/package.json +12 -4
- package/schemas/manifest.schema.json +73 -3
- package/schemas/profiler.schema.json +243 -0
- package/schemas/runner-capabilities.schema.json +8 -2
- package/schemas/scenario.schema.json +18 -2
- package/templates/evidence-provider.json +3 -3
- package/templates/scripts/asl-capture-profiler-provider.mjs +20 -0
|
@@ -18,7 +18,7 @@ const fs = require('node:fs');
|
|
|
18
18
|
const path = require('node:path');
|
|
19
19
|
const { hasHelpFlag } = require('./cli');
|
|
20
20
|
const { buildScenarioExecutionPlan } = require('../core/execution-plan');
|
|
21
|
-
const { buildProfileHealth, buildProfileVerdict, buildVerdictBudgetChecks, parseArgs, readScalarArg, runProfileCli, runProfileMobile, usage, } = require('./profile-mobile');
|
|
21
|
+
const { buildProfileHealth, buildProfileVerdict, buildVerdictBudgetChecks, parseArgs, readScalarArg, resolveArtifactRoot, resolveProfileScenarioName, runProfileCompatibilityPreflight, runProfileCli, runProfileMobile, usage, } = require('./profile-mobile');
|
|
22
22
|
exports.buildProfileHealth = buildProfileHealth;
|
|
23
23
|
exports.buildProfileVerdict = buildProfileVerdict;
|
|
24
24
|
exports.buildVerdictBudgetChecks = buildVerdictBudgetChecks;
|
|
@@ -28,7 +28,10 @@ const { runIosSimctlCapture } = require('./ios-simctl');
|
|
|
28
28
|
const { runAgentDeviceCapture } = require('./agent-device');
|
|
29
29
|
const { loadAslLocalEnv, readStringArgOrEnv } = require('./local-env');
|
|
30
30
|
const PROFILE_SESSION_CAPTURE_BOOTSTRAP_MS = 1000;
|
|
31
|
-
const
|
|
31
|
+
const PROFILE_SESSION_CAPTURE_COMMAND_OVERHEAD_MS = 250;
|
|
32
|
+
const PROFILE_SESSION_CAPTURE_BUFFER_MIN_MS = 2000;
|
|
33
|
+
const PROFILE_SESSION_CAPTURE_BUFFER_RATIO = 0.2;
|
|
34
|
+
const PROFILE_SESSION_CAPTURE_MAX_MS = 10 * 60 * 1000;
|
|
32
35
|
const DEFAULT_IOS_PROFILE_SESSION_STORAGE_KEY = 'agent-scenario-loop.profile-session.1';
|
|
33
36
|
const DEFAULT_IOS_PROFILE_COMMAND_STORAGE_KEY = 'agent-scenario-loop.profile-commands.1';
|
|
34
37
|
const DEFAULT_IOS_PROFILE_EVENT_STORAGE_KEY = 'agent-scenario-loop.profile-events.1';
|
|
@@ -49,6 +52,17 @@ const MANIFEST_LIFECYCLE_PHASES = new Set([
|
|
|
49
52
|
'reboot',
|
|
50
53
|
'relaunch',
|
|
51
54
|
]);
|
|
55
|
+
const IOS_PROFILE_RUNNER_CAPABILITIES = {
|
|
56
|
+
schemaVersion: '1.0.0',
|
|
57
|
+
runnerId: 'ios-simctl-profile-runner',
|
|
58
|
+
kind: 'primary',
|
|
59
|
+
platforms: ['ios'],
|
|
60
|
+
capabilities: ['launch', 'sessionControl', 'command', 'logCapture', 'artifactWrite'],
|
|
61
|
+
driverActions: ['tap', 'scroll', 'assertVisible', 'inspectTree', 'screenshot', 'readLogs'],
|
|
62
|
+
artifactOutputs: ['logs', 'signals', 'screenshot', 'uiTree'],
|
|
63
|
+
uiContexts: ['app'],
|
|
64
|
+
lifecycle: ['prepare', 'launch', 'startSession', 'executeStep', 'waitForTruthEvent', 'captureEvidence', 'stopSession', 'finalize'],
|
|
65
|
+
};
|
|
52
66
|
/**
|
|
53
67
|
* Reads and parses a JSON object from disk.
|
|
54
68
|
*
|
|
@@ -171,7 +185,7 @@ function resolveIosConflictingBundleIds(config) {
|
|
|
171
185
|
* @param {{config: Record<string, unknown>, action: 'start' | 'command', scenario: string, runId: string, command?: string}} options
|
|
172
186
|
* @returns {string}
|
|
173
187
|
*/
|
|
174
|
-
function buildProfileSessionUrl({ action, command, commandId, config, queueId, runId, scenario, sequence, waitForMilestone, waitTimeoutMs, }) {
|
|
188
|
+
function buildProfileSessionUrl({ action, command, commandId, config, queueId, runId, scenario, sequence, waitForMilestone, waitMs, waitTimeoutMs, }) {
|
|
175
189
|
const scheme = typeof config.app?.profileSessionScheme === 'string'
|
|
176
190
|
? config.app.profileSessionScheme
|
|
177
191
|
: typeof config.app?.scheme === 'string'
|
|
@@ -192,6 +206,9 @@ function buildProfileSessionUrl({ action, command, commandId, config, queueId, r
|
|
|
192
206
|
if (waitForMilestone) {
|
|
193
207
|
params.set('waitForMilestone', waitForMilestone);
|
|
194
208
|
}
|
|
209
|
+
if (typeof waitMs === 'number') {
|
|
210
|
+
params.set('waitMs', String(waitMs));
|
|
211
|
+
}
|
|
195
212
|
if (typeof waitTimeoutMs === 'number') {
|
|
196
213
|
params.set('waitTimeoutMs', String(waitTimeoutMs));
|
|
197
214
|
}
|
|
@@ -215,24 +232,49 @@ function readStepWaitMs(step) {
|
|
|
215
232
|
return readPositiveInteger(step.timeoutMs, 0);
|
|
216
233
|
}
|
|
217
234
|
/**
|
|
218
|
-
*
|
|
235
|
+
* Reads wait time from a profile-session command.
|
|
236
|
+
*
|
|
237
|
+
* @param {IosSimctlProfileCommand} command
|
|
238
|
+
* @returns {number}
|
|
239
|
+
*/
|
|
240
|
+
function readProfileCommandWindowMs(command) {
|
|
241
|
+
return readPositiveInteger(command.waitMs, 0) +
|
|
242
|
+
readPositiveInteger(command.waitTimeoutMs, 0) +
|
|
243
|
+
PROFILE_SESSION_CAPTURE_COMMAND_OVERHEAD_MS;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Reads execution-plan waits that are not already attached to a profile-session command.
|
|
219
247
|
*
|
|
220
248
|
* @param {Record<string, unknown>} scenario
|
|
221
249
|
* @returns {number}
|
|
222
250
|
*/
|
|
223
|
-
function
|
|
251
|
+
function readUnattachedExecutionWaitMs(scenario) {
|
|
224
252
|
const executionPlan = buildScenarioExecutionPlan(scenario);
|
|
225
253
|
const iterations = readScenarioIterationCount(scenario);
|
|
226
|
-
const perIterationWaitMs = executionPlan.steps.reduce((total, step) => {
|
|
227
|
-
if (step.
|
|
228
|
-
return total
|
|
254
|
+
const perIterationWaitMs = executionPlan.steps.reduce((total, step, index) => {
|
|
255
|
+
if (step.portMethod !== 'waitForTruthEvent') {
|
|
256
|
+
return total;
|
|
229
257
|
}
|
|
230
|
-
|
|
231
|
-
|
|
258
|
+
const previousStep = executionPlan.steps[index - 1];
|
|
259
|
+
if (previousStep?.portMethod === 'executeStep') {
|
|
260
|
+
return total;
|
|
232
261
|
}
|
|
233
|
-
return total;
|
|
262
|
+
return total + readPositiveInteger(step.timeoutMs, 0);
|
|
234
263
|
}, 0);
|
|
235
|
-
|
|
264
|
+
return perIterationWaitMs * iterations;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Derives a storage-backed profile capture window from scenario waits, command gates, and cycles.
|
|
268
|
+
*
|
|
269
|
+
* @param {Record<string, unknown>} scenario
|
|
270
|
+
* @returns {number}
|
|
271
|
+
*/
|
|
272
|
+
function deriveProfileSessionCaptureWaitMs(scenario) {
|
|
273
|
+
const commands = resolveIosSimctlProfileCommands(scenario);
|
|
274
|
+
const commandWindowMs = commands.reduce((total, command) => total + readProfileCommandWindowMs(command), 0);
|
|
275
|
+
const executionWindowMs = commandWindowMs + readUnattachedExecutionWaitMs(scenario);
|
|
276
|
+
const bufferMs = Math.max(PROFILE_SESSION_CAPTURE_BUFFER_MIN_MS, Math.ceil(executionWindowMs * PROFILE_SESSION_CAPTURE_BUFFER_RATIO));
|
|
277
|
+
const derivedWaitMs = PROFILE_SESSION_CAPTURE_BOOTSTRAP_MS + executionWindowMs + bufferMs;
|
|
236
278
|
return Math.min(Math.max(derivedWaitMs, PROFILE_SESSION_CAPTURE_BOOTSTRAP_MS), PROFILE_SESSION_CAPTURE_MAX_MS);
|
|
237
279
|
}
|
|
238
280
|
/**
|
|
@@ -271,16 +313,182 @@ function resolveExecutionPlanProfileCommands(scenario) {
|
|
|
271
313
|
waitMs: readStepWaitMs(step),
|
|
272
314
|
...(nextStep?.portMethod === 'waitForTruthEvent' && typeof nextStep.milestone === 'string'
|
|
273
315
|
? {
|
|
274
|
-
waitForMilestone: nextStep.milestone,
|
|
316
|
+
waitForMilestone: resolveMilestoneEventName(scenario, nextStep.milestone),
|
|
275
317
|
waitTimeoutMs: readPositiveInteger(nextStep.timeoutMs, 0),
|
|
276
318
|
}
|
|
277
319
|
: {}),
|
|
278
320
|
});
|
|
279
321
|
}
|
|
280
|
-
return
|
|
322
|
+
return expandProfileCommandCycles(scenario, commands, repeat);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Returns true when a command is part of the setup prefix that establishes app readiness before repeated cycle work.
|
|
326
|
+
*
|
|
327
|
+
* @param {Record<string, unknown>} scenario
|
|
328
|
+
* @param {IosSimctlProfileCommand} command
|
|
329
|
+
* @returns {boolean}
|
|
330
|
+
*/
|
|
331
|
+
function isReadinessSetupProfileCommand(scenario, command) {
|
|
332
|
+
if (typeof command.waitForMilestone !== 'string') {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
const readyEvent = resolveScenarioReadinessEvent(scenario);
|
|
336
|
+
return typeof readyEvent === 'string' && command.waitForMilestone === readyEvent;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Reads a string id list from scenario cycles metadata.
|
|
340
|
+
*
|
|
341
|
+
* @param {unknown} value
|
|
342
|
+
* @returns {Set<string>}
|
|
343
|
+
*/
|
|
344
|
+
function readCycleStepIdSet(value) {
|
|
345
|
+
return new Set(Array.isArray(value) ? value.filter((entry) => typeof entry === 'string') : []);
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Resolves the milestone ids that represent measured cycle boundaries.
|
|
349
|
+
*
|
|
350
|
+
* @param {Record<string, unknown>} scenario
|
|
351
|
+
* @returns {Set<string>}
|
|
352
|
+
*/
|
|
353
|
+
function resolveMeasuredCycleMilestoneEvents(scenario) {
|
|
354
|
+
const milestones = new Set();
|
|
355
|
+
for (const budget of Array.isArray(scenario.budgets) ? scenario.budgets : []) {
|
|
356
|
+
if (!budget || typeof budget !== 'object' || budget.source !== 'milestone') {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (typeof budget.fromMilestone === 'string') {
|
|
360
|
+
milestones.add(resolveMilestoneEventName(scenario, budget.fromMilestone));
|
|
361
|
+
}
|
|
362
|
+
if (typeof budget.toMilestone === 'string') {
|
|
363
|
+
milestones.add(resolveMilestoneEventName(scenario, budget.toMilestone));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return milestones;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Resolves how many leading commands are setup-only before repeated cycle work.
|
|
370
|
+
*
|
|
371
|
+
* @param {Record<string, unknown>} scenario
|
|
372
|
+
* @param {IosSimctlProfileCommand[]} commands
|
|
373
|
+
* @returns {number}
|
|
374
|
+
*/
|
|
375
|
+
function resolveSetupCommandCount(scenario, commands) {
|
|
376
|
+
const explicitSetupStepIds = readCycleStepIdSet(scenario.cycles?.setupStepIds);
|
|
377
|
+
if (explicitSetupStepIds.size > 0) {
|
|
378
|
+
let count = 0;
|
|
379
|
+
for (const command of commands) {
|
|
380
|
+
if (!command.commandId || !explicitSetupStepIds.has(command.commandId)) {
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
count += 1;
|
|
384
|
+
}
|
|
385
|
+
return count;
|
|
386
|
+
}
|
|
387
|
+
const explicitBodyStepIds = readCycleStepIdSet(scenario.cycles?.bodyStepIds);
|
|
388
|
+
if (explicitBodyStepIds.size > 0) {
|
|
389
|
+
const firstBodyIndex = commands.findIndex((command) => (typeof command.commandId === 'string' && explicitBodyStepIds.has(command.commandId)));
|
|
390
|
+
return firstBodyIndex > 0 ? firstBodyIndex : 0;
|
|
391
|
+
}
|
|
392
|
+
let readinessSetupCommandCount = 0;
|
|
393
|
+
for (const command of commands) {
|
|
394
|
+
if (!isReadinessSetupProfileCommand(scenario, command)) {
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
readinessSetupCommandCount += 1;
|
|
398
|
+
}
|
|
399
|
+
if (readinessSetupCommandCount > 0) {
|
|
400
|
+
return readinessSetupCommandCount;
|
|
401
|
+
}
|
|
402
|
+
const measuredMilestones = resolveMeasuredCycleMilestoneEvents(scenario);
|
|
403
|
+
if (measuredMilestones.size === 0) {
|
|
404
|
+
return 0;
|
|
405
|
+
}
|
|
406
|
+
const firstMeasuredCommandIndex = commands.findIndex((command) => (typeof command.waitForMilestone === 'string' && measuredMilestones.has(command.waitForMilestone)));
|
|
407
|
+
return firstMeasuredCommandIndex > 0 ? firstMeasuredCommandIndex : 0;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Expands commands so setup/readiness commands execute once while cycle-body commands repeat.
|
|
411
|
+
*
|
|
412
|
+
* @param {Record<string, unknown>} scenario
|
|
413
|
+
* @param {IosSimctlProfileCommand[]} commands
|
|
414
|
+
* @param {number} repeat
|
|
415
|
+
* @returns {IosSimctlProfileCommand[]}
|
|
416
|
+
*/
|
|
417
|
+
function expandProfileCommandCycles(scenario, commands, repeat) {
|
|
418
|
+
const setupCommandCount = resolveSetupCommandCount(scenario, commands);
|
|
419
|
+
const setupCommands = commands.slice(0, setupCommandCount);
|
|
420
|
+
const cycleCommands = commands.slice(setupCommandCount);
|
|
421
|
+
const expandedCommands = cycleCommands.length === 0
|
|
422
|
+
? setupCommands
|
|
423
|
+
: [
|
|
424
|
+
...setupCommands,
|
|
425
|
+
...Array.from({ length: repeat }).flatMap(() => cycleCommands),
|
|
426
|
+
];
|
|
427
|
+
return expandedCommands.map((command, index) => ({
|
|
281
428
|
...command,
|
|
282
|
-
sequence:
|
|
283
|
-
}))
|
|
429
|
+
sequence: index + 1,
|
|
430
|
+
}));
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Resolves a portable milestone id to the app truth event that releases command sequencing.
|
|
434
|
+
*
|
|
435
|
+
* @param {Record<string, unknown>} scenario
|
|
436
|
+
* @param {string} milestone
|
|
437
|
+
* @returns {string}
|
|
438
|
+
*/
|
|
439
|
+
function resolveMilestoneEventName(scenario, milestone) {
|
|
440
|
+
const milestoneEntry = Array.isArray(scenario.milestones)
|
|
441
|
+
? scenario.milestones.find((entry) => entry?.id === milestone)
|
|
442
|
+
: undefined;
|
|
443
|
+
if (typeof milestoneEntry?.event === 'string' && milestoneEntry.event.length > 0) {
|
|
444
|
+
return milestoneEntry.event;
|
|
445
|
+
}
|
|
446
|
+
const metricEvent = scenario.metricEvents?.[milestone];
|
|
447
|
+
return typeof metricEvent === 'string' && metricEvent.length > 0 ? metricEvent : milestone;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Resolves the scenario truth event that represents initial app readiness.
|
|
451
|
+
*
|
|
452
|
+
* @param {Record<string, unknown>} scenario
|
|
453
|
+
* @returns {string | null}
|
|
454
|
+
*/
|
|
455
|
+
function resolveScenarioReadinessEvent(scenario) {
|
|
456
|
+
const explicitReadyEvent = scenario.truthEvents?.ready?.event;
|
|
457
|
+
if (typeof explicitReadyEvent === 'string' && explicitReadyEvent.length > 0) {
|
|
458
|
+
return explicitReadyEvent;
|
|
459
|
+
}
|
|
460
|
+
const milestoneEntry = Array.isArray(scenario.milestones)
|
|
461
|
+
? scenario.milestones.find((entry) => (String(entry?.event ?? '').includes('ready')))
|
|
462
|
+
: undefined;
|
|
463
|
+
return typeof milestoneEntry?.event === 'string' && milestoneEntry.event.length > 0
|
|
464
|
+
? milestoneEntry.event
|
|
465
|
+
: null;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Applies wait gates from the normalized execution plan to platform-declared commands.
|
|
469
|
+
*
|
|
470
|
+
* @param {Record<string, unknown>} scenario
|
|
471
|
+
* @param {IosSimctlProfileCommand[]} commands
|
|
472
|
+
* @returns {IosSimctlProfileCommand[]}
|
|
473
|
+
*/
|
|
474
|
+
function applyExecutionPlanCommandGates(scenario, commands) {
|
|
475
|
+
const planCommands = resolveExecutionPlanProfileCommands(scenario);
|
|
476
|
+
if (planCommands.length === 0) {
|
|
477
|
+
return commands;
|
|
478
|
+
}
|
|
479
|
+
return commands.map((command, index) => {
|
|
480
|
+
const planCommand = planCommands[index];
|
|
481
|
+
if (!planCommand || typeof planCommand.waitForMilestone !== 'string' || typeof command.waitForMilestone === 'string') {
|
|
482
|
+
return command;
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
...command,
|
|
486
|
+
waitForMilestone: planCommand.waitForMilestone,
|
|
487
|
+
...(typeof command.waitTimeoutMs === 'number'
|
|
488
|
+
? {}
|
|
489
|
+
: { waitTimeoutMs: readPositiveInteger(planCommand.waitTimeoutMs, 0) }),
|
|
490
|
+
};
|
|
491
|
+
});
|
|
284
492
|
}
|
|
285
493
|
/**
|
|
286
494
|
* Expands scenario-declared iOS commands for a simctl capture profile session.
|
|
@@ -316,7 +524,7 @@ function resolveIosSimctlProfileCommands(scenario) {
|
|
|
316
524
|
});
|
|
317
525
|
}
|
|
318
526
|
}
|
|
319
|
-
return commands;
|
|
527
|
+
return applyExecutionPlanCommandGates(scenario, commands);
|
|
320
528
|
}
|
|
321
529
|
/**
|
|
322
530
|
* Returns true when the scenario asks iOS simctl capture to preserve a screenshot.
|
|
@@ -411,6 +619,23 @@ async function runProfileIos(args, options = {}) {
|
|
|
411
619
|
const config = readJson(path.resolve(args.config));
|
|
412
620
|
const scenario = readJson(path.resolve(args.scenario));
|
|
413
621
|
const runId = typeof args['run-id'] === 'string' ? args['run-id'] : createRunId();
|
|
622
|
+
const scenarioName = resolveProfileScenarioName({ scenario, scenarioPath: path.resolve(args.scenario) });
|
|
623
|
+
const artifactRoot = resolveArtifactRoot({
|
|
624
|
+
args,
|
|
625
|
+
config,
|
|
626
|
+
configPath: path.resolve(args.config),
|
|
627
|
+
platform: 'ios',
|
|
628
|
+
});
|
|
629
|
+
await runProfileCompatibilityPreflight({
|
|
630
|
+
args,
|
|
631
|
+
artifactRoot,
|
|
632
|
+
platform: 'ios',
|
|
633
|
+
primaryRunner: IOS_PROFILE_RUNNER_CAPABILITIES,
|
|
634
|
+
runDir: path.join(artifactRoot, scenarioName, runId),
|
|
635
|
+
runId,
|
|
636
|
+
scenario,
|
|
637
|
+
scenarioName,
|
|
638
|
+
});
|
|
414
639
|
const profileSessionEnabled = isEnabled(args['profile-session']);
|
|
415
640
|
const profileSessionStorageEnabled = isEnabled(args['profile-session-storage']);
|
|
416
641
|
const profileSessionStorageKey = readStringArgOrEnv(args['ios-profile-session-storage-key'], [
|
|
@@ -441,7 +666,6 @@ async function runProfileIos(args, options = {}) {
|
|
|
441
666
|
'ASL_IOS_DEV_CLIENT_WAIT_MS',
|
|
442
667
|
'ASL_EXAMPLE_IOS_DEV_CLIENT_WAIT_MS',
|
|
443
668
|
]), 1000);
|
|
444
|
-
const scenarioName = typeof scenario.name === 'string' ? scenario.name : path.basename(args.scenario, '.json');
|
|
445
669
|
const profileSessionCommands = profileSessionEnabled ? resolveIosSimctlProfileCommands(scenario) : [];
|
|
446
670
|
const iosDevClientDeepLinks = iosDevClientUrl
|
|
447
671
|
? [
|
|
@@ -476,6 +700,7 @@ async function runProfileIos(args, options = {}) {
|
|
|
476
700
|
...(typeof profileCommand.queueId === 'string' ? { queueId: profileCommand.queueId } : {}),
|
|
477
701
|
...(typeof profileCommand.sequence === 'number' ? { sequence: profileCommand.sequence } : {}),
|
|
478
702
|
...(typeof profileCommand.waitForMilestone === 'string' ? { waitForMilestone: profileCommand.waitForMilestone } : {}),
|
|
703
|
+
...(typeof profileCommand.waitMs === 'number' ? { waitMs: profileCommand.waitMs } : {}),
|
|
479
704
|
...(typeof profileCommand.waitTimeoutMs === 'number' ? { waitTimeoutMs: profileCommand.waitTimeoutMs } : {}),
|
|
480
705
|
}),
|
|
481
706
|
waitMs: profileCommand.waitMs,
|
|
@@ -514,6 +739,7 @@ async function runProfileIos(args, options = {}) {
|
|
|
514
739
|
...(typeof profileCommand.queueId === 'string' ? { queueId: profileCommand.queueId } : {}),
|
|
515
740
|
...(typeof profileCommand.sequence === 'number' ? { sequence: profileCommand.sequence } : {}),
|
|
516
741
|
...(typeof profileCommand.waitForMilestone === 'string' ? { waitForMilestone: profileCommand.waitForMilestone } : {}),
|
|
742
|
+
...(typeof profileCommand.waitMs === 'number' ? { waitMs: profileCommand.waitMs } : {}),
|
|
517
743
|
...(typeof profileCommand.waitTimeoutMs === 'number' ? { waitTimeoutMs: profileCommand.waitTimeoutMs } : {}),
|
|
518
744
|
})),
|
|
519
745
|
runId,
|
|
@@ -580,7 +806,11 @@ async function runProfileIos(args, options = {}) {
|
|
|
580
806
|
: baseProfileArgs;
|
|
581
807
|
const lifecyclePhase = resolveManifestLifecyclePhase(args);
|
|
582
808
|
const environmentSource = agentDeviceCapture ? 'agent-device' : 'simctl';
|
|
583
|
-
const
|
|
809
|
+
const copiedSimctlLogArtifact = simctlCapture &&
|
|
810
|
+
!fs.existsSync(path.join(simctlCapture.runDir, 'raw', 'ios-profile-events.log')) &&
|
|
811
|
+
fs.existsSync(path.join(simctlCapture.runDir, 'raw', 'ios-simctl-log.txt'))
|
|
812
|
+
? 'raw/ios-simctl-log.txt'
|
|
813
|
+
: undefined;
|
|
584
814
|
return runProfileMobile(profileArgs, {
|
|
585
815
|
commandTransport: agentDeviceCapture
|
|
586
816
|
? 'agent-device'
|
|
@@ -596,13 +826,13 @@ async function runProfileIos(args, options = {}) {
|
|
|
596
826
|
value: 'foreground',
|
|
597
827
|
evidence: 'asserted',
|
|
598
828
|
source: environmentSource,
|
|
599
|
-
artifact:
|
|
829
|
+
...(copiedSimctlLogArtifact ? { artifact: copiedSimctlLogArtifact } : {}),
|
|
600
830
|
},
|
|
601
831
|
lifecyclePhase: {
|
|
602
832
|
value: 'foreground',
|
|
603
833
|
evidence: 'asserted',
|
|
604
834
|
source: environmentSource,
|
|
605
|
-
artifact:
|
|
835
|
+
...(copiedSimctlLogArtifact ? { artifact: copiedSimctlLogArtifact } : {}),
|
|
606
836
|
},
|
|
607
837
|
},
|
|
608
838
|
environmentPreconditions: {
|
|
@@ -615,7 +845,7 @@ async function runProfileIos(args, options = {}) {
|
|
|
615
845
|
value: lifecyclePhase,
|
|
616
846
|
evidence: 'asserted',
|
|
617
847
|
source: environmentSource,
|
|
618
|
-
artifact:
|
|
848
|
+
...(copiedSimctlLogArtifact ? { artifact: copiedSimctlLogArtifact } : {}),
|
|
619
849
|
},
|
|
620
850
|
},
|
|
621
851
|
interactionDriver: agentDeviceCapture ? 'agent-device' : 'ios-simctl',
|
|
@@ -10,6 +10,7 @@ type CliArgs = {
|
|
|
10
10
|
events?: string | boolean;
|
|
11
11
|
out?: string | boolean;
|
|
12
12
|
provider?: CliArgValue;
|
|
13
|
+
'profile-session-entries'?: string | boolean;
|
|
13
14
|
'run-id'?: string | boolean;
|
|
14
15
|
signal?: CliArgValue;
|
|
15
16
|
[key: string]: CliArgValue | undefined;
|
|
@@ -19,6 +20,16 @@ type ProfileRunResult = {
|
|
|
19
20
|
health: Record<string, unknown>;
|
|
20
21
|
verdict: Record<string, unknown>;
|
|
21
22
|
};
|
|
23
|
+
type CompatibilityPreflightOptions = {
|
|
24
|
+
args: CliArgs;
|
|
25
|
+
artifactRoot: string;
|
|
26
|
+
platform: ProfilePlatform;
|
|
27
|
+
primaryRunner: Record<string, unknown>;
|
|
28
|
+
runDir: string;
|
|
29
|
+
runId: string;
|
|
30
|
+
scenario: Record<string, unknown>;
|
|
31
|
+
scenarioName: string;
|
|
32
|
+
};
|
|
22
33
|
type ProfilePlatform = 'android' | 'ios';
|
|
23
34
|
type ProfileMobileOptions = {
|
|
24
35
|
commandTransport?: string;
|
|
@@ -35,6 +46,25 @@ type ProviderEvidenceKind = 'accessibility' | 'logs' | 'profiler';
|
|
|
35
46
|
type SignalEvidenceKind = 'js' | 'memory' | 'network';
|
|
36
47
|
type EvidenceChannel = 'capture' | 'provider' | 'signal';
|
|
37
48
|
type EvidenceKind = CaptureEvidenceKind | ProviderEvidenceKind | SignalEvidenceKind;
|
|
49
|
+
type DiagnosticStatus = 'captured' | 'not_requested' | 'not_supported' | 'unavailable' | 'failed' | 'skipped' | 'missing';
|
|
50
|
+
type DiagnosticKind = EvidenceKind | 'logs';
|
|
51
|
+
type DiagnosticInventoryEntry = {
|
|
52
|
+
kind: DiagnosticKind;
|
|
53
|
+
status: DiagnosticStatus;
|
|
54
|
+
required: boolean;
|
|
55
|
+
name?: string;
|
|
56
|
+
provider?: string;
|
|
57
|
+
runnerId?: string;
|
|
58
|
+
path?: string;
|
|
59
|
+
reason?: string;
|
|
60
|
+
nextAction?: string;
|
|
61
|
+
sidecarRoot?: string;
|
|
62
|
+
evidenceDependency?: {
|
|
63
|
+
kind: string;
|
|
64
|
+
root?: 'run' | 'sidecar';
|
|
65
|
+
path: string;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
38
68
|
type EvidenceAttachment = {
|
|
39
69
|
channel: EvidenceChannel;
|
|
40
70
|
completenessStatus: 'complete';
|
|
@@ -43,6 +73,7 @@ type EvidenceAttachment = {
|
|
|
43
73
|
kind: EvidenceKind;
|
|
44
74
|
manifestPath: string;
|
|
45
75
|
redactionStatus: 'not-redacted';
|
|
76
|
+
required: boolean;
|
|
46
77
|
sha256: string;
|
|
47
78
|
sourcePath: string;
|
|
48
79
|
sourceFileName: string;
|
|
@@ -54,6 +85,8 @@ type EvidenceAttachmentInput = {
|
|
|
54
85
|
destinationPath: string;
|
|
55
86
|
kind: EvidenceKind;
|
|
56
87
|
manifestPath: string;
|
|
88
|
+
providerId?: string;
|
|
89
|
+
required?: boolean;
|
|
57
90
|
sourcePath: string;
|
|
58
91
|
};
|
|
59
92
|
type AttachedEvidence = {
|
|
@@ -70,6 +103,7 @@ type ProviderCommandOutput = {
|
|
|
70
103
|
channel: EvidenceChannel;
|
|
71
104
|
kind: EvidenceKind;
|
|
72
105
|
path: string;
|
|
106
|
+
required?: boolean;
|
|
73
107
|
};
|
|
74
108
|
type ProviderCommand = {
|
|
75
109
|
args?: string[];
|
|
@@ -78,7 +112,7 @@ type ProviderCommand = {
|
|
|
78
112
|
env?: Record<string, string>;
|
|
79
113
|
id: string;
|
|
80
114
|
outputs: ProviderCommandOutput[];
|
|
81
|
-
phase: 'prepare' | 'startWindow' | 'capture' | 'stopWindow' | 'finalize';
|
|
115
|
+
phase: 'prepare' | 'startWindow' | 'capture' | 'stopWindow' | 'afterCapture' | 'postRun' | 'finalize';
|
|
82
116
|
};
|
|
83
117
|
type ProviderCommandFailure = {
|
|
84
118
|
commandId: string;
|
|
@@ -139,13 +173,18 @@ declare function resolveAttachedEvidence({ args, layout, providerInputs, }: {
|
|
|
139
173
|
/**
|
|
140
174
|
* Builds scenario health from profile metrics.
|
|
141
175
|
*
|
|
142
|
-
* @param {{scenario: Record<string, unknown>, runId: string, metrics: Record<string, unknown>}} options
|
|
176
|
+
* @param {{scenario: Record<string, unknown>, runId: string, metrics: Record<string, unknown>, diagnostics?: DiagnosticInventoryEntry[], profileEventCount?: number, profileSessionEntryCount?: number, commandTransport?: string, sessionEntries?: Record<string, unknown>[]}} options
|
|
143
177
|
* @returns {Record<string, unknown>}
|
|
144
178
|
*/
|
|
145
|
-
declare function buildProfileHealth({ scenario, runId, metrics, }: {
|
|
179
|
+
declare function buildProfileHealth({ scenario, runId, metrics, diagnostics, profileEventCount, profileSessionEntryCount, commandTransport, sessionEntries, }: {
|
|
146
180
|
scenario: Record<string, any>;
|
|
147
181
|
runId: string;
|
|
148
182
|
metrics: Record<string, any>;
|
|
183
|
+
diagnostics?: DiagnosticInventoryEntry[];
|
|
184
|
+
profileEventCount?: number;
|
|
185
|
+
profileSessionEntryCount?: number;
|
|
186
|
+
commandTransport?: string;
|
|
187
|
+
sessionEntries?: Record<string, any>[];
|
|
149
188
|
}): Record<string, unknown>;
|
|
150
189
|
/**
|
|
151
190
|
* Builds failed scenario health from evidence-provider command failures.
|
|
@@ -231,6 +270,26 @@ declare function resolveEventLogPath({ args, platform }: {
|
|
|
231
270
|
args: CliArgs;
|
|
232
271
|
platform: ProfilePlatform;
|
|
233
272
|
}): string | null;
|
|
273
|
+
/**
|
|
274
|
+
* Resolves the profile scenario name from modern or legacy scenario identity fields.
|
|
275
|
+
*
|
|
276
|
+
* @param {{scenario: Record<string, unknown>, scenarioPath: string}} options
|
|
277
|
+
* @returns {string}
|
|
278
|
+
*/
|
|
279
|
+
declare function resolveProfileScenarioName({ scenario, scenarioPath, }: {
|
|
280
|
+
scenario: Record<string, unknown>;
|
|
281
|
+
scenarioPath: string;
|
|
282
|
+
}): string;
|
|
283
|
+
/**
|
|
284
|
+
* Runs planner compatibility before a live profile capture starts.
|
|
285
|
+
*
|
|
286
|
+
* Failed compatibility writes classified artifacts in the profile run folder so
|
|
287
|
+
* agents can stop before adb, simctl, or provider work consumes runtime time.
|
|
288
|
+
*
|
|
289
|
+
* @param {CompatibilityPreflightOptions} options
|
|
290
|
+
* @returns {Promise<void>}
|
|
291
|
+
*/
|
|
292
|
+
declare function runProfileCompatibilityPreflight({ args, artifactRoot, platform, primaryRunner, runDir, runId, scenario, scenarioName, }: CompatibilityPreflightOptions): Promise<void>;
|
|
234
293
|
/**
|
|
235
294
|
* Creates a stable fingerprint for the scenario contract used by one run.
|
|
236
295
|
*
|
|
@@ -258,5 +317,5 @@ declare function runProfileCli({ argv, binaryName, defaultDriver, platform, }: {
|
|
|
258
317
|
defaultDriver: string;
|
|
259
318
|
platform: ProfilePlatform;
|
|
260
319
|
}): Promise<void>;
|
|
261
|
-
export { buildProfileHealth, buildProviderCommandFailureHealth, buildProfileVerdict, buildVerdictBudgetChecks, parseArgs, buildEvidenceAttachmentManifest, readScalarArg, resolveAppId, resolveArtifactRoot, resolveAttachedEvidence, resolveComparisonLane, resolveEventLogPath, resolveInteractionDriver, runProfileCli, runProfileMobile, hashScenarioContract, usage, };
|
|
320
|
+
export { buildProfileHealth, buildProviderCommandFailureHealth, buildProfileVerdict, buildVerdictBudgetChecks, parseArgs, buildEvidenceAttachmentManifest, readScalarArg, resolveAppId, resolveArtifactRoot, resolveAttachedEvidence, resolveComparisonLane, resolveEventLogPath, resolveInteractionDriver, resolveProfileScenarioName, runProfileCompatibilityPreflight, runProfileCli, runProfileMobile, hashScenarioContract, usage, };
|
|
262
321
|
export type { CliArgs, ProfileMobileOptions, ProfilePlatform, ProfileRunResult, };
|