agent-scenario-loop 0.1.3 → 0.1.4

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.
Files changed (47) hide show
  1. package/app/profile-session.ts +263 -17
  2. package/dist/core/artifact-contract.d.ts +6 -4
  3. package/dist/core/artifact-contract.js +164 -15
  4. package/dist/core/schema-validator.d.ts +1 -0
  5. package/dist/core/schema-validator.js +1 -0
  6. package/dist/runner/android-adb-driver.d.ts +7 -2
  7. package/dist/runner/android-adb-driver.js +7 -1
  8. package/dist/runner/android-adb.d.ts +40 -5
  9. package/dist/runner/android-adb.js +1046 -664
  10. package/dist/runner/ios-simctl.d.ts +1 -0
  11. package/dist/runner/ios-simctl.js +1 -0
  12. package/dist/runner/profile-android.d.ts +11 -1
  13. package/dist/runner/profile-android.js +230 -16
  14. package/dist/runner/profile-ios.d.ts +3 -2
  15. package/dist/runner/profile-ios.js +223 -20
  16. package/dist/runner/profile-mobile.d.ts +31 -3
  17. package/dist/runner/profile-mobile.js +793 -20
  18. package/dist/runner/validate-project.js +3 -0
  19. package/dist/scripts/consumer-rehearsal.d.ts +119 -0
  20. package/dist/scripts/consumer-rehearsal.js +757 -0
  21. package/dist/scripts/downstream-local-package-gate.d.ts +2 -0
  22. package/dist/scripts/downstream-local-package-gate.js +264 -0
  23. package/dist/scripts/package-smoke.d.ts +96 -0
  24. package/dist/scripts/package-smoke.js +2282 -0
  25. package/dist/scripts/release-readiness.d.ts +2 -0
  26. package/dist/scripts/release-readiness.js +520 -0
  27. package/docs/adapters.md +3 -1
  28. package/docs/api.md +2 -2
  29. package/docs/authoring.md +34 -2
  30. package/docs/consumer-rehearsal.md +27 -1
  31. package/docs/contracts.md +16 -2
  32. package/docs/live-proofs.md +5 -3
  33. package/examples/mobile-app/runner-manifests/evidence-provider.json +3 -3
  34. package/examples/mobile-app/scripts/asl-capture-profiler-provider.mjs +25 -0
  35. package/examples/runners/README.md +3 -3
  36. package/examples/runners/axe-accessibility-provider.json +2 -2
  37. package/examples/runners/script-accessibility-provider.json +2 -2
  38. package/examples/runners/script-memory-provider.json +2 -2
  39. package/examples/runners/script-network-provider.json +2 -2
  40. package/examples/runners/script-profiler-provider.json +2 -2
  41. package/package.json +11 -3
  42. package/schemas/manifest.schema.json +73 -3
  43. package/schemas/profiler.schema.json +243 -0
  44. package/schemas/runner-capabilities.schema.json +8 -2
  45. package/schemas/scenario.schema.json +18 -2
  46. package/templates/evidence-provider.json +3 -3
  47. package/templates/scripts/asl-capture-profiler-provider.mjs +20 -0
@@ -50,6 +50,7 @@ type IosProfileSessionStorageCommand = {
50
50
  sequence?: number;
51
51
  timestamp?: number;
52
52
  waitForMilestone?: string;
53
+ waitMs?: number;
53
54
  waitTimeoutMs?: number;
54
55
  };
55
56
  type IosProfileSessionStorageSeed = {
@@ -462,6 +462,7 @@ async function seedProfileSessionStorage({ bundleId, commands = [], dataContaine
462
462
  ...(typeof profileCommand.sequence === 'number' ? { sequence: profileCommand.sequence } : {}),
463
463
  timestamp: typeof profileCommand.timestamp === 'number' ? profileCommand.timestamp : startedAt + index + 1,
464
464
  ...(typeof profileCommand.waitForMilestone === 'string' ? { waitForMilestone: profileCommand.waitForMilestone } : {}),
465
+ ...(typeof profileCommand.waitMs === 'number' ? { waitMs: profileCommand.waitMs } : {}),
465
466
  ...(typeof profileCommand.waitTimeoutMs === 'number' ? { waitTimeoutMs: profileCommand.waitTimeoutMs } : {}),
466
467
  }));
467
468
  manifest[profileStorageKeys.session] = JSON.stringify(session);
@@ -35,6 +35,16 @@ declare function resolveProfileSessionCaptureWaitMs({ args, profileSessionEnable
35
35
  profileSessionEnabled: boolean;
36
36
  scenario: Record<string, any>;
37
37
  }): number;
38
+ /**
39
+ * Derives a bounded logcat tail large enough to keep command-session evidence.
40
+ *
41
+ * @param {{commands: AndroidAdbProfileCommand[], profileSessionEnabled: boolean}} options
42
+ * @returns {number}
43
+ */
44
+ declare function deriveProfileSessionLogcatLines({ commands, profileSessionEnabled, }: {
45
+ commands: AndroidAdbProfileCommand[];
46
+ profileSessionEnabled: boolean;
47
+ }): number;
38
48
  /**
39
49
  * Expands normalized scenario evidence steps into Android adb driver actions.
40
50
  *
@@ -84,4 +94,4 @@ declare function runProfileAndroid(args: import('./profile-mobile').CliArgs, opt
84
94
  * @returns {Promise<void>}
85
95
  */
86
96
  declare function main(): Promise<void>;
87
- export { deriveProfileSessionCaptureWaitMs, main, parseArgs, resolveAndroidAdbProfileCommands, resolveAndroidAdbDriverSteps, resolveProfileSessionCaptureWaitMs, readAndroidAdbVideoCapturePath, validateAndroidAdbDriverSteps, runProfileAndroid, summarizeFailedAndroidChecks, usage, };
97
+ export { deriveProfileSessionCaptureWaitMs, deriveProfileSessionLogcatLines, main, parseArgs, resolveAndroidAdbProfileCommands, resolveAndroidAdbDriverSteps, resolveProfileSessionCaptureWaitMs, readAndroidAdbVideoCapturePath, validateAndroidAdbDriverSteps, runProfileAndroid, summarizeFailedAndroidChecks, usage, };
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.usage = exports.parseArgs = void 0;
5
5
  exports.deriveProfileSessionCaptureWaitMs = deriveProfileSessionCaptureWaitMs;
6
+ exports.deriveProfileSessionLogcatLines = deriveProfileSessionLogcatLines;
6
7
  exports.main = main;
7
8
  exports.resolveAndroidAdbProfileCommands = resolveAndroidAdbProfileCommands;
8
9
  exports.resolveAndroidAdbDriverSteps = resolveAndroidAdbDriverSteps;
@@ -24,8 +25,12 @@ const { runAgentDeviceCapture } = require('./agent-device');
24
25
  const { loadAslLocalEnv, readStringArgOrEnv } = require('./local-env');
25
26
  const PROFILE_SESSION_CAPTURE_BOOTSTRAP_MS = 1000;
26
27
  const PROFILE_SESSION_CAPTURE_MAX_MS = 120000;
28
+ const PROFILE_SESSION_LOGCAT_MIN_LINES = 1000;
29
+ const PROFILE_SESSION_LOGCAT_MAX_LINES = 20000;
30
+ const PROFILE_SESSION_LOGCAT_LINES_PER_COMMAND = 300;
27
31
  const DEFAULT_ANDROID_PROFILE_SESSION_STORAGE_KEY = 'agent-scenario-loop.profile-session.1';
28
32
  const DEFAULT_ANDROID_PROFILE_COMMAND_STORAGE_KEY = 'agent-scenario-loop.profile-commands.1';
33
+ const DEFAULT_ANDROID_DEV_CLIENT_READY_PATTERN = 'Running "main"';
29
34
  const MANIFEST_LIFECYCLE_PHASES = new Set([
30
35
  'cold-launch',
31
36
  'warm-launch',
@@ -150,7 +155,7 @@ function resolveAndroidPackageName({ args, config, }) {
150
155
  * @param {{config: Record<string, unknown>, action: 'start' | 'command', scenario: string, runId: string, command?: string}} options
151
156
  * @returns {string}
152
157
  */
153
- function buildProfileSessionUrl({ action, command, commandId, config, queueId, runId, scenario, sequence, waitForMilestone, waitTimeoutMs, }) {
158
+ function buildProfileSessionUrl({ action, command, commandId, config, queueId, runId, scenario, sequence, waitForMilestone, waitMs, waitTimeoutMs, }) {
154
159
  const scheme = typeof config.app?.profileSessionScheme === 'string'
155
160
  ? config.app.profileSessionScheme
156
161
  : typeof config.app?.scheme === 'string'
@@ -171,6 +176,9 @@ function buildProfileSessionUrl({ action, command, commandId, config, queueId, r
171
176
  if (waitForMilestone) {
172
177
  params.set('waitForMilestone', waitForMilestone);
173
178
  }
179
+ if (typeof waitMs === 'number') {
180
+ params.set('waitMs', String(waitMs));
181
+ }
174
182
  if (typeof waitTimeoutMs === 'number') {
175
183
  params.set('waitTimeoutMs', String(waitTimeoutMs));
176
184
  }
@@ -184,11 +192,10 @@ function buildProfileSessionUrl({ action, command, commandId, config, queueId, r
184
192
  * @returns {import('./android-adb').AndroidAsyncStorageWrite[]}
185
193
  */
186
194
  function buildProfileSessionStorageWrites({ commands, commandStorageKey, commandWaitMs, runId, scenario, sessionStorageKey, }) {
187
- const timestampBase = Date.now();
188
195
  const storedCommands = commands.map((profileCommand, index) => {
189
- const timestamp = timestampBase + index + 1;
196
+ const timestampPlaceholder = `${ANDROID_DEVICE_EPOCH_MS_PLACEHOLDER}+${index + 1}`;
190
197
  return {
191
- id: `${timestamp}-${scenario}-${profileCommand.command}`,
198
+ id: `${scenario}-${index + 1}-${profileCommand.command}`,
192
199
  scenario,
193
200
  runId,
194
201
  command: profileCommand.command,
@@ -196,8 +203,9 @@ function buildProfileSessionStorageWrites({ commands, commandStorageKey, command
196
203
  ...(typeof profileCommand.sequence === 'number' ? { sequence: profileCommand.sequence } : {}),
197
204
  ...(typeof profileCommand.queueId === 'string' ? { queueId: profileCommand.queueId } : {}),
198
205
  ...(typeof profileCommand.waitForMilestone === 'string' ? { waitForMilestone: profileCommand.waitForMilestone } : {}),
206
+ ...(typeof profileCommand.waitMs === 'number' ? { waitMs: profileCommand.waitMs } : {}),
199
207
  ...(typeof profileCommand.waitTimeoutMs === 'number' ? { waitTimeoutMs: profileCommand.waitTimeoutMs } : {}),
200
- timestamp,
208
+ timestamp: timestampPlaceholder,
201
209
  };
202
210
  });
203
211
  const commandWaitMsTotal = commands.reduce((total, profileCommand) => (total + (typeof profileCommand.waitMs === 'number' && profileCommand.waitMs > 0 ? profileCommand.waitMs : 0)), 0);
@@ -274,6 +282,32 @@ function resolveProfileSessionCaptureWaitMs({ args, profileSessionEnabled, scena
274
282
  }
275
283
  return profileSessionEnabled ? deriveProfileSessionCaptureWaitMs(scenario) : 0;
276
284
  }
285
+ /**
286
+ * Derives a bounded logcat tail large enough to keep command-session evidence.
287
+ *
288
+ * @param {{commands: AndroidAdbProfileCommand[], profileSessionEnabled: boolean}} options
289
+ * @returns {number}
290
+ */
291
+ function deriveProfileSessionLogcatLines({ commands, profileSessionEnabled, }) {
292
+ if (!profileSessionEnabled || commands.length === 0) {
293
+ return PROFILE_SESSION_LOGCAT_MIN_LINES;
294
+ }
295
+ const derivedLines = PROFILE_SESSION_LOGCAT_MIN_LINES + (commands.length * PROFILE_SESSION_LOGCAT_LINES_PER_COMMAND);
296
+ return Math.min(Math.max(derivedLines, PROFILE_SESSION_LOGCAT_MIN_LINES), PROFILE_SESSION_LOGCAT_MAX_LINES);
297
+ }
298
+ /**
299
+ * Resolves Android logcat capture lines, keeping explicit CLI input authoritative.
300
+ *
301
+ * @param {{args: import('./profile-mobile').CliArgs, commands: AndroidAdbProfileCommand[], profileSessionEnabled: boolean}} options
302
+ * @returns {number}
303
+ */
304
+ function resolveProfileSessionLogcatLines({ args, commands, profileSessionEnabled, }) {
305
+ const explicitLogcatLines = readScalarArg(args['logcat-lines']);
306
+ if (explicitLogcatLines !== undefined) {
307
+ return parsePositiveInteger(explicitLogcatLines, PROFILE_SESSION_LOGCAT_MIN_LINES);
308
+ }
309
+ return deriveProfileSessionLogcatLines({ commands, profileSessionEnabled });
310
+ }
277
311
  /**
278
312
  * Reads Android adb adapter metadata from a normalized scenario step.
279
313
  *
@@ -341,16 +375,182 @@ function resolveExecutionPlanProfileCommands(scenario) {
341
375
  waitMs: readStepWaitMs(step),
342
376
  ...(nextStep?.portMethod === 'waitForTruthEvent' && typeof nextStep.milestone === 'string'
343
377
  ? {
344
- waitForMilestone: nextStep.milestone,
378
+ waitForMilestone: resolveMilestoneEventName(scenario, nextStep.milestone),
345
379
  waitTimeoutMs: readPositiveInteger(nextStep.timeoutMs, 0),
346
380
  }
347
381
  : {}),
348
382
  });
349
383
  }
350
- return Array.from({ length: repeat }).flatMap((_, iteration) => commands.map((command, commandIndex) => ({
384
+ return expandProfileCommandCycles(scenario, commands, repeat);
385
+ }
386
+ /**
387
+ * Returns true when a command is part of the setup prefix that establishes app readiness before repeated cycle work.
388
+ *
389
+ * @param {Record<string, unknown>} scenario
390
+ * @param {AndroidAdbProfileCommand} command
391
+ * @returns {boolean}
392
+ */
393
+ function isReadinessSetupProfileCommand(scenario, command) {
394
+ if (typeof command.waitForMilestone !== 'string') {
395
+ return false;
396
+ }
397
+ const readyEvent = resolveScenarioReadinessEvent(scenario);
398
+ return typeof readyEvent === 'string' && command.waitForMilestone === readyEvent;
399
+ }
400
+ /**
401
+ * Reads a string id list from scenario cycles metadata.
402
+ *
403
+ * @param {unknown} value
404
+ * @returns {Set<string>}
405
+ */
406
+ function readCycleStepIdSet(value) {
407
+ return new Set(Array.isArray(value) ? value.filter((entry) => typeof entry === 'string') : []);
408
+ }
409
+ /**
410
+ * Resolves the milestone ids that represent measured cycle boundaries.
411
+ *
412
+ * @param {Record<string, unknown>} scenario
413
+ * @returns {Set<string>}
414
+ */
415
+ function resolveMeasuredCycleMilestoneEvents(scenario) {
416
+ const milestones = new Set();
417
+ for (const budget of Array.isArray(scenario.budgets) ? scenario.budgets : []) {
418
+ if (!budget || typeof budget !== 'object' || budget.source !== 'milestone') {
419
+ continue;
420
+ }
421
+ if (typeof budget.fromMilestone === 'string') {
422
+ milestones.add(resolveMilestoneEventName(scenario, budget.fromMilestone));
423
+ }
424
+ if (typeof budget.toMilestone === 'string') {
425
+ milestones.add(resolveMilestoneEventName(scenario, budget.toMilestone));
426
+ }
427
+ }
428
+ return milestones;
429
+ }
430
+ /**
431
+ * Resolves how many leading commands are setup-only before repeated cycle work.
432
+ *
433
+ * @param {Record<string, unknown>} scenario
434
+ * @param {AndroidAdbProfileCommand[]} commands
435
+ * @returns {number}
436
+ */
437
+ function resolveSetupCommandCount(scenario, commands) {
438
+ const explicitSetupStepIds = readCycleStepIdSet(scenario.cycles?.setupStepIds);
439
+ if (explicitSetupStepIds.size > 0) {
440
+ let count = 0;
441
+ for (const command of commands) {
442
+ if (!command.commandId || !explicitSetupStepIds.has(command.commandId)) {
443
+ break;
444
+ }
445
+ count += 1;
446
+ }
447
+ return count;
448
+ }
449
+ const explicitBodyStepIds = readCycleStepIdSet(scenario.cycles?.bodyStepIds);
450
+ if (explicitBodyStepIds.size > 0) {
451
+ const firstBodyIndex = commands.findIndex((command) => (typeof command.commandId === 'string' && explicitBodyStepIds.has(command.commandId)));
452
+ return firstBodyIndex > 0 ? firstBodyIndex : 0;
453
+ }
454
+ let readinessSetupCommandCount = 0;
455
+ for (const command of commands) {
456
+ if (!isReadinessSetupProfileCommand(scenario, command)) {
457
+ break;
458
+ }
459
+ readinessSetupCommandCount += 1;
460
+ }
461
+ if (readinessSetupCommandCount > 0) {
462
+ return readinessSetupCommandCount;
463
+ }
464
+ const measuredMilestones = resolveMeasuredCycleMilestoneEvents(scenario);
465
+ if (measuredMilestones.size === 0) {
466
+ return 0;
467
+ }
468
+ const firstMeasuredCommandIndex = commands.findIndex((command) => (typeof command.waitForMilestone === 'string' && measuredMilestones.has(command.waitForMilestone)));
469
+ return firstMeasuredCommandIndex > 0 ? firstMeasuredCommandIndex : 0;
470
+ }
471
+ /**
472
+ * Expands commands so setup/readiness commands execute once while cycle-body commands repeat.
473
+ *
474
+ * @param {Record<string, unknown>} scenario
475
+ * @param {AndroidAdbProfileCommand[]} commands
476
+ * @param {number} repeat
477
+ * @returns {AndroidAdbProfileCommand[]}
478
+ */
479
+ function expandProfileCommandCycles(scenario, commands, repeat) {
480
+ const setupCommandCount = resolveSetupCommandCount(scenario, commands);
481
+ const setupCommands = commands.slice(0, setupCommandCount);
482
+ const cycleCommands = commands.slice(setupCommandCount);
483
+ const expandedCommands = cycleCommands.length === 0
484
+ ? setupCommands
485
+ : [
486
+ ...setupCommands,
487
+ ...Array.from({ length: repeat }).flatMap(() => cycleCommands),
488
+ ];
489
+ return expandedCommands.map((command, index) => ({
351
490
  ...command,
352
- sequence: (iteration * commands.length) + commandIndex + 1,
353
- })));
491
+ sequence: index + 1,
492
+ }));
493
+ }
494
+ /**
495
+ * Resolves a portable milestone id to the app truth event that releases command sequencing.
496
+ *
497
+ * @param {Record<string, unknown>} scenario
498
+ * @param {string} milestone
499
+ * @returns {string}
500
+ */
501
+ function resolveMilestoneEventName(scenario, milestone) {
502
+ const milestoneEntry = Array.isArray(scenario.milestones)
503
+ ? scenario.milestones.find((entry) => entry?.id === milestone)
504
+ : undefined;
505
+ if (typeof milestoneEntry?.event === 'string' && milestoneEntry.event.length > 0) {
506
+ return milestoneEntry.event;
507
+ }
508
+ const metricEvent = scenario.metricEvents?.[milestone];
509
+ return typeof metricEvent === 'string' && metricEvent.length > 0 ? metricEvent : milestone;
510
+ }
511
+ /**
512
+ * Resolves the scenario truth event that represents initial app readiness.
513
+ *
514
+ * @param {Record<string, unknown>} scenario
515
+ * @returns {string | null}
516
+ */
517
+ function resolveScenarioReadinessEvent(scenario) {
518
+ const explicitReadyEvent = scenario.truthEvents?.ready?.event;
519
+ if (typeof explicitReadyEvent === 'string' && explicitReadyEvent.length > 0) {
520
+ return explicitReadyEvent;
521
+ }
522
+ const milestoneEntry = Array.isArray(scenario.milestones)
523
+ ? scenario.milestones.find((entry) => (String(entry?.event ?? '').includes('ready')))
524
+ : undefined;
525
+ return typeof milestoneEntry?.event === 'string' && milestoneEntry.event.length > 0
526
+ ? milestoneEntry.event
527
+ : null;
528
+ }
529
+ /**
530
+ * Applies wait gates from the normalized execution plan to platform-declared commands.
531
+ *
532
+ * @param {Record<string, unknown>} scenario
533
+ * @param {AndroidAdbProfileCommand[]} commands
534
+ * @returns {AndroidAdbProfileCommand[]}
535
+ */
536
+ function applyExecutionPlanCommandGates(scenario, commands) {
537
+ const planCommands = resolveExecutionPlanProfileCommands(scenario);
538
+ if (planCommands.length === 0) {
539
+ return commands;
540
+ }
541
+ return commands.map((command, index) => {
542
+ const planCommand = planCommands[index];
543
+ if (!planCommand || typeof planCommand.waitForMilestone !== 'string' || typeof command.waitForMilestone === 'string') {
544
+ return command;
545
+ }
546
+ return {
547
+ ...command,
548
+ waitForMilestone: planCommand.waitForMilestone,
549
+ ...(typeof command.waitTimeoutMs === 'number'
550
+ ? {}
551
+ : { waitTimeoutMs: readPositiveInteger(planCommand.waitTimeoutMs, 0) }),
552
+ };
553
+ });
354
554
  }
355
555
  /**
356
556
  * Expands normalized scenario evidence steps into Android adb driver actions.
@@ -478,7 +678,7 @@ function resolveAndroidAdbProfileCommands(scenario) {
478
678
  });
479
679
  }
480
680
  }
481
- return commands;
681
+ return applyExecutionPlanCommandGates(scenario, commands);
482
682
  }
483
683
  /**
484
684
  * Summarizes failed adb capture checks for CLI errors.
@@ -573,10 +773,14 @@ async function runProfileAndroid(args, options = {}) {
573
773
  'ASL_ANDROID_DEV_CLIENT_WAIT_MS',
574
774
  'ASL_EXAMPLE_ANDROID_DEV_CLIENT_WAIT_MS',
575
775
  ]), 1000);
576
- const androidDevClientReadyPattern = readStringArgOrEnv(args['android-dev-client-ready-pattern'], [
776
+ const configuredAndroidDevClientReadyPattern = readStringArgOrEnv(args['android-dev-client-ready-pattern'], [
577
777
  'ASL_ANDROID_DEV_CLIENT_READY_PATTERN',
578
778
  'ASL_EXAMPLE_ANDROID_DEV_CLIENT_READY_PATTERN',
579
779
  ]);
780
+ const androidDevClientReadyPattern = configuredAndroidDevClientReadyPattern
781
+ ?? (androidDevClientUrl && profileSessionEnabled && profileSessionStorageEnabled
782
+ ? DEFAULT_ANDROID_DEV_CLIENT_READY_PATTERN
783
+ : undefined);
580
784
  const androidDevClientReadyQuietMs = parsePositiveInteger(readStringArgOrEnv(args['android-dev-client-ready-quiet-ms'], [
581
785
  'ASL_ANDROID_DEV_CLIENT_READY_QUIET_MS',
582
786
  'ASL_EXAMPLE_ANDROID_DEV_CLIENT_READY_QUIET_MS',
@@ -585,6 +789,10 @@ async function runProfileAndroid(args, options = {}) {
585
789
  'ASL_ANDROID_DEV_CLIENT_READY_TIMEOUT_MS',
586
790
  'ASL_EXAMPLE_ANDROID_DEV_CLIENT_READY_TIMEOUT_MS',
587
791
  ]), 60000);
792
+ const adbCommandTimeoutMs = parsePositiveInteger(readStringArgOrEnv(args['adb-command-timeout-ms'], [
793
+ 'ASL_ANDROID_ADB_COMMAND_TIMEOUT_MS',
794
+ 'ASL_EXAMPLE_ANDROID_ADB_COMMAND_TIMEOUT_MS',
795
+ ]), 30000);
588
796
  const scenarioName = typeof scenario.name === 'string' ? scenario.name : path.basename(args.scenario, '.json');
589
797
  const driverSteps = adbCaptureEnabled ? resolveAndroidAdbDriverSteps(scenario) : [];
590
798
  if (adbCaptureEnabled) {
@@ -619,6 +827,7 @@ async function runProfileAndroid(args, options = {}) {
619
827
  ...(typeof profileCommand.queueId === 'string' ? { queueId: profileCommand.queueId } : {}),
620
828
  ...(typeof profileCommand.sequence === 'number' ? { sequence: profileCommand.sequence } : {}),
621
829
  ...(typeof profileCommand.waitForMilestone === 'string' ? { waitForMilestone: profileCommand.waitForMilestone } : {}),
830
+ ...(typeof profileCommand.waitMs === 'number' ? { waitMs: profileCommand.waitMs } : {}),
622
831
  ...(typeof profileCommand.waitTimeoutMs === 'number' ? { waitTimeoutMs: profileCommand.waitTimeoutMs } : {}),
623
832
  }),
624
833
  waitMs: profileCommand.waitMs,
@@ -652,13 +861,18 @@ async function runProfileAndroid(args, options = {}) {
652
861
  ...(typeof args.adb === 'string' ? { adbPath: args.adb } : {}),
653
862
  captureLogcat: true,
654
863
  clearLogcat: isEnabled(args['clear-logcat']),
864
+ commandTimeoutMs: adbCommandTimeoutMs,
655
865
  deepLinks: profileSessionDeepLinks,
656
866
  ...(options.delay ? { delay: options.delay } : {}),
657
867
  ...(options.executor ? { executor: options.executor } : {}),
658
868
  driverSteps,
659
869
  launch: isEnabled(args.launch),
660
870
  launchWaitMs: parsePositiveInteger(readScalarArg(args['launch-wait-ms']), 0),
661
- logcatLines: parsePositiveInteger(readScalarArg(args['logcat-lines']), 1000),
871
+ logcatLines: resolveProfileSessionLogcatLines({
872
+ args,
873
+ commands: profileSessionCommands,
874
+ profileSessionEnabled,
875
+ }),
662
876
  outputDir: resolveAdbCaptureOutputDir({ args, runId }),
663
877
  packageName: resolveAndroidPackageName({ args, config }),
664
878
  ...(typeof args['react-native-debug-host'] === 'string'
@@ -724,7 +938,7 @@ async function runProfileAndroid(args, options = {}) {
724
938
  : baseProfileArgs;
725
939
  const lifecyclePhase = resolveManifestLifecyclePhase(args);
726
940
  const environmentSource = agentDeviceCapture ? 'agent-device' : 'adb';
727
- const lifecycleArtifact = adbCapture ? 'raw/adb-logcat.txt' : 'raw/interaction.log';
941
+ const copiedAdbLogArtifact = adbCapture ? 'raw/adb-logcat.txt' : undefined;
728
942
  return runProfileMobile(profileArgs, {
729
943
  commandTransport: profileSessionStorageEnabled
730
944
  ? 'profile-session-storage'
@@ -740,13 +954,13 @@ async function runProfileAndroid(args, options = {}) {
740
954
  value: 'foreground',
741
955
  evidence: 'asserted',
742
956
  source: environmentSource,
743
- artifact: lifecycleArtifact,
957
+ ...(copiedAdbLogArtifact ? { artifact: copiedAdbLogArtifact } : {}),
744
958
  },
745
959
  lifecyclePhase: {
746
960
  value: 'foreground',
747
961
  evidence: 'asserted',
748
962
  source: environmentSource,
749
- artifact: lifecycleArtifact,
963
+ ...(copiedAdbLogArtifact ? { artifact: copiedAdbLogArtifact } : {}),
750
964
  },
751
965
  },
752
966
  environmentPreconditions: {
@@ -759,7 +973,7 @@ async function runProfileAndroid(args, options = {}) {
759
973
  value: lifecyclePhase,
760
974
  evidence: 'asserted',
761
975
  source: environmentSource,
762
- artifact: lifecycleArtifact,
976
+ ...(copiedAdbLogArtifact ? { artifact: copiedAdbLogArtifact } : {}),
763
977
  },
764
978
  },
765
979
  interactionDriver: agentDeviceCapture ? 'agent-device' : 'adb-logcat',
@@ -49,7 +49,7 @@ declare function resolveIosConflictingBundleIds(config: Record<string, any>): st
49
49
  * @param {{config: Record<string, unknown>, action: 'start' | 'command', scenario: string, runId: string, command?: string}} options
50
50
  * @returns {string}
51
51
  */
52
- declare function buildProfileSessionUrl({ action, command, commandId, config, queueId, runId, scenario, sequence, waitForMilestone, waitTimeoutMs, }: {
52
+ declare function buildProfileSessionUrl({ action, command, commandId, config, queueId, runId, scenario, sequence, waitForMilestone, waitMs, waitTimeoutMs, }: {
53
53
  action: 'start' | 'command';
54
54
  command?: string;
55
55
  commandId?: string;
@@ -59,10 +59,11 @@ declare function buildProfileSessionUrl({ action, command, commandId, config, qu
59
59
  scenario: string;
60
60
  sequence?: number;
61
61
  waitForMilestone?: string;
62
+ waitMs?: number;
62
63
  waitTimeoutMs?: number;
63
64
  }): string;
64
65
  /**
65
- * Derives a storage-backed profile capture window from scenario waits and cycles.
66
+ * Derives a storage-backed profile capture window from scenario waits, command gates, and cycles.
66
67
  *
67
68
  * @param {Record<string, unknown>} scenario
68
69
  * @returns {number}