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
package/app/profile-session.ts
CHANGED
|
@@ -21,6 +21,7 @@ export type ProfileSessionCommand = {
|
|
|
21
21
|
source?: 'deeplink' | 'storage';
|
|
22
22
|
timestamp: number;
|
|
23
23
|
waitForMilestone?: string;
|
|
24
|
+
waitMs?: number;
|
|
24
25
|
waitTimeoutMs?: number;
|
|
25
26
|
};
|
|
26
27
|
|
|
@@ -67,6 +68,7 @@ type StoredProfileSessionEntry = {
|
|
|
67
68
|
scenario: string;
|
|
68
69
|
runId: string;
|
|
69
70
|
timestamp: number;
|
|
71
|
+
atMs?: number;
|
|
70
72
|
startedAt?: number;
|
|
71
73
|
stoppedAt?: number;
|
|
72
74
|
command?: string;
|
|
@@ -79,10 +81,22 @@ type StoredProfileSessionEntry = {
|
|
|
79
81
|
source?: 'deeplink' | 'storage';
|
|
80
82
|
status?: 'received' | 'queued' | 'delivered' | 'completed' | 'skipped';
|
|
81
83
|
waitForMilestone?: string;
|
|
84
|
+
waitMs?: number;
|
|
82
85
|
waitTimeoutMs?: number;
|
|
83
86
|
};
|
|
84
87
|
|
|
85
88
|
type StoredProfileSignals = Record<ProfileSignalKind, Record<string, unknown>>;
|
|
89
|
+
type ProfileCommandMilestoneGate = {
|
|
90
|
+
commandId?: string;
|
|
91
|
+
id: string;
|
|
92
|
+
milestone: string;
|
|
93
|
+
queueId?: string;
|
|
94
|
+
runId?: string;
|
|
95
|
+
scenario?: string;
|
|
96
|
+
sequence?: number;
|
|
97
|
+
timeoutId?: ReturnType<typeof setTimeout>;
|
|
98
|
+
waitMs?: number;
|
|
99
|
+
};
|
|
86
100
|
|
|
87
101
|
const INITIAL_STATE: ProfileSessionState = {
|
|
88
102
|
active: false,
|
|
@@ -111,9 +125,15 @@ const listeners = new Set<() => void>();
|
|
|
111
125
|
const profileCommandListeners = new Set<(command: ProfileSessionCommand) => void>();
|
|
112
126
|
const profileCommandTargetHandlers = new Map<string, () => void>();
|
|
113
127
|
const pendingProfileCommands: ProfileSessionCommand[] = [];
|
|
128
|
+
const sequencedProfileCommands: ProfileSessionCommand[] = [];
|
|
129
|
+
const observedProfileEvents: StoredProfileEvent[] = [];
|
|
114
130
|
const processedProfileCommandIds = new Set<string>();
|
|
115
131
|
let lastProfileCommandSignature: string | null = null;
|
|
116
132
|
let lastProfileCommandTimestamp = 0;
|
|
133
|
+
let profileCommandMilestoneGate: ProfileCommandMilestoneGate | null = null;
|
|
134
|
+
let profileCommandProcessingScheduled = false;
|
|
135
|
+
let profileCommandProcessingTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
136
|
+
let profileCommandProcessingAvailableAt = 0;
|
|
117
137
|
|
|
118
138
|
function writeProfileLog(line: string) {
|
|
119
139
|
if (Platform.OS === 'ios') {
|
|
@@ -205,6 +225,7 @@ function appendStoredProfileEvent(event: StoredProfileEvent) {
|
|
|
205
225
|
}
|
|
206
226
|
|
|
207
227
|
function resetStoredProfileArtifacts() {
|
|
228
|
+
observedProfileEvents.length = 0;
|
|
208
229
|
queueProfileStorageMutation(async () => {
|
|
209
230
|
await Promise.all([
|
|
210
231
|
AsyncStorage.removeItem(PROFILE_EVENT_STORAGE_KEY),
|
|
@@ -216,6 +237,9 @@ function resetStoredProfileArtifacts() {
|
|
|
216
237
|
|
|
217
238
|
function clearPendingProfileCommands() {
|
|
218
239
|
pendingProfileCommands.length = 0;
|
|
240
|
+
sequencedProfileCommands.length = 0;
|
|
241
|
+
clearProfileCommandMilestoneGate();
|
|
242
|
+
clearProfileCommandProcessingSchedule();
|
|
219
243
|
queueProfileStorageMutation(async () => {
|
|
220
244
|
await AsyncStorage.removeItem(PROFILE_COMMAND_STORAGE_KEY);
|
|
221
245
|
});
|
|
@@ -299,7 +323,21 @@ export function isProfileSessionFresh(
|
|
|
299
323
|
}
|
|
300
324
|
|
|
301
325
|
function logProfileSession(kind: 'start' | 'stop' | 'command', payload: Record<string, unknown>) {
|
|
302
|
-
|
|
326
|
+
const timestamp = Date.now();
|
|
327
|
+
const sessionStartedAt = readProfileSessionStartedAt(profileSessionState);
|
|
328
|
+
const atMs =
|
|
329
|
+
typeof payload.atMs === 'number' && Number.isFinite(payload.atMs)
|
|
330
|
+
? payload.atMs
|
|
331
|
+
: sessionStartedAt !== null
|
|
332
|
+
? Math.max(0, timestamp - sessionStartedAt)
|
|
333
|
+
: undefined;
|
|
334
|
+
const logPayload = {
|
|
335
|
+
kind,
|
|
336
|
+
...payload,
|
|
337
|
+
timestamp,
|
|
338
|
+
...(atMs !== undefined ? { atMs } : {}),
|
|
339
|
+
};
|
|
340
|
+
writeProfileLog(buildLogLine('profile-session', logPayload));
|
|
303
341
|
|
|
304
342
|
const scenario = typeof payload.scenario === 'string' ? payload.scenario : null;
|
|
305
343
|
const runId = typeof payload.runId === 'string' ? payload.runId : null;
|
|
@@ -307,12 +345,12 @@ function logProfileSession(kind: 'start' | 'stop' | 'command', payload: Record<s
|
|
|
307
345
|
return;
|
|
308
346
|
}
|
|
309
347
|
|
|
310
|
-
const timestamp = Date.now();
|
|
311
348
|
const entry: StoredProfileSessionEntry = {
|
|
312
349
|
kind,
|
|
313
350
|
scenario,
|
|
314
351
|
runId,
|
|
315
352
|
timestamp,
|
|
353
|
+
...(atMs !== undefined ? { atMs } : {}),
|
|
316
354
|
};
|
|
317
355
|
|
|
318
356
|
if (kind === 'start' && typeof payload.startedAt === 'number') {
|
|
@@ -362,6 +400,9 @@ function logProfileSession(kind: 'start' | 'stop' | 'command', payload: Record<s
|
|
|
362
400
|
if (typeof payload.waitForMilestone === 'string') {
|
|
363
401
|
entry.waitForMilestone = payload.waitForMilestone;
|
|
364
402
|
}
|
|
403
|
+
if (typeof payload.waitMs === 'number') {
|
|
404
|
+
entry.waitMs = payload.waitMs;
|
|
405
|
+
}
|
|
365
406
|
if (typeof payload.waitTimeoutMs === 'number') {
|
|
366
407
|
entry.waitTimeoutMs = payload.waitTimeoutMs;
|
|
367
408
|
}
|
|
@@ -379,6 +420,7 @@ function getProfileSessionRoute(url: string): {
|
|
|
379
420
|
queueId?: string;
|
|
380
421
|
sequence?: number;
|
|
381
422
|
waitForMilestone?: string;
|
|
423
|
+
waitMs?: number;
|
|
382
424
|
waitTimeoutMs?: number;
|
|
383
425
|
} | null {
|
|
384
426
|
const parsed = ExpoLinking.parse(url);
|
|
@@ -415,8 +457,12 @@ function getProfileSessionRoute(url: string): {
|
|
|
415
457
|
typeof parsed.queryParams?.waitTimeoutMs === 'string' && Number.isInteger(Number(parsed.queryParams.waitTimeoutMs))
|
|
416
458
|
? Number(parsed.queryParams.waitTimeoutMs)
|
|
417
459
|
: undefined;
|
|
460
|
+
const waitMs =
|
|
461
|
+
typeof parsed.queryParams?.waitMs === 'string' && Number.isInteger(Number(parsed.queryParams.waitMs))
|
|
462
|
+
? Number(parsed.queryParams.waitMs)
|
|
463
|
+
: undefined;
|
|
418
464
|
|
|
419
|
-
return { action, scenario, runId, command, commandId, queueId, sequence, waitForMilestone, waitTimeoutMs };
|
|
465
|
+
return { action, scenario, runId, command, commandId, queueId, sequence, waitForMilestone, waitMs, waitTimeoutMs };
|
|
420
466
|
}
|
|
421
467
|
|
|
422
468
|
function queuePendingProfileCommand(command: ProfileSessionCommand) {
|
|
@@ -508,6 +554,132 @@ function markProfileCommandIdProcessed(command: ProfileSessionCommand) {
|
|
|
508
554
|
}
|
|
509
555
|
}
|
|
510
556
|
|
|
557
|
+
function compareProfileCommands(left: ProfileSessionCommand, right: ProfileSessionCommand): number {
|
|
558
|
+
const leftSequence = typeof left.sequence === 'number' ? left.sequence : Number.POSITIVE_INFINITY;
|
|
559
|
+
const rightSequence = typeof right.sequence === 'number' ? right.sequence : Number.POSITIVE_INFINITY;
|
|
560
|
+
if (leftSequence !== rightSequence) {
|
|
561
|
+
return leftSequence - rightSequence;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return left.timestamp - right.timestamp;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function shouldQueueProfileCommand(command: ProfileSessionCommand): boolean {
|
|
568
|
+
return !hasProcessedProfileCommandId(command) &&
|
|
569
|
+
!sequencedProfileCommands.some((queuedCommand) => queuedCommand.id === command.id);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function buildProfileCommandMilestoneGate(command: ProfileSessionCommand): ProfileCommandMilestoneGate | null {
|
|
573
|
+
if (typeof command.waitForMilestone !== 'string' || command.waitForMilestone.length === 0) {
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return {
|
|
578
|
+
id: command.id,
|
|
579
|
+
milestone: command.waitForMilestone,
|
|
580
|
+
...(typeof command.commandId === 'string' ? { commandId: command.commandId } : {}),
|
|
581
|
+
...(typeof command.queueId === 'string' ? { queueId: command.queueId } : {}),
|
|
582
|
+
...(typeof command.runId === 'string' ? { runId: command.runId } : {}),
|
|
583
|
+
...(typeof command.scenario === 'string' ? { scenario: command.scenario } : {}),
|
|
584
|
+
...(typeof command.sequence === 'number' ? { sequence: command.sequence } : {}),
|
|
585
|
+
...(typeof command.waitMs === 'number' && command.waitMs > 0 ? { waitMs: command.waitMs } : {}),
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function hasObservedProfileCommandMilestone(command: ProfileSessionCommand): boolean {
|
|
590
|
+
if (typeof command.waitForMilestone !== 'string' || command.waitForMilestone.length === 0) {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
if (typeof command.sequence === 'number' && command.sequence > 1) {
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return observedProfileEvents.some((eventPayload) => (
|
|
598
|
+
eventPayload.event === command.waitForMilestone &&
|
|
599
|
+
(!command.runId || eventPayload.runId === command.runId) &&
|
|
600
|
+
(!command.scenario || eventPayload.scenario === command.scenario)
|
|
601
|
+
));
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function clearProfileCommandMilestoneGate() {
|
|
605
|
+
if (profileCommandMilestoneGate?.timeoutId) {
|
|
606
|
+
clearTimeout(profileCommandMilestoneGate.timeoutId);
|
|
607
|
+
}
|
|
608
|
+
profileCommandMilestoneGate = null;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function clearProfileCommandProcessingSchedule() {
|
|
612
|
+
if (profileCommandProcessingTimeoutId) {
|
|
613
|
+
clearTimeout(profileCommandProcessingTimeoutId);
|
|
614
|
+
}
|
|
615
|
+
profileCommandProcessingTimeoutId = null;
|
|
616
|
+
profileCommandProcessingScheduled = false;
|
|
617
|
+
profileCommandProcessingAvailableAt = 0;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function startProfileCommandMilestoneTimeout(command: ProfileSessionCommand) {
|
|
621
|
+
if (
|
|
622
|
+
!profileCommandMilestoneGate ||
|
|
623
|
+
typeof command.waitTimeoutMs !== 'number' ||
|
|
624
|
+
command.waitTimeoutMs <= 0
|
|
625
|
+
) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
profileCommandMilestoneGate.timeoutId = setTimeout(() => {
|
|
630
|
+
if (!profileCommandMilestoneGate || profileCommandMilestoneGate.id !== command.id) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
logProfileSession('command', {
|
|
635
|
+
...command,
|
|
636
|
+
status: 'skipped',
|
|
637
|
+
reason: 'wait-for-milestone-timeout',
|
|
638
|
+
});
|
|
639
|
+
clearProfileCommandMilestoneGate();
|
|
640
|
+
processSequencedProfileCommands();
|
|
641
|
+
}, command.waitTimeoutMs);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function scheduleProfileCommandProcessing(waitMs = 0) {
|
|
645
|
+
const availableAt = waitMs > 0 ? Date.now() + waitMs : 0;
|
|
646
|
+
if (availableAt > profileCommandProcessingAvailableAt) {
|
|
647
|
+
profileCommandProcessingAvailableAt = availableAt;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const delayMs = Math.max(0, profileCommandProcessingAvailableAt - Date.now());
|
|
651
|
+
if (profileCommandProcessingTimeoutId) {
|
|
652
|
+
clearTimeout(profileCommandProcessingTimeoutId);
|
|
653
|
+
profileCommandProcessingTimeoutId = null;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
profileCommandProcessingScheduled = true;
|
|
657
|
+
const run = () => {
|
|
658
|
+
profileCommandProcessingTimeoutId = null;
|
|
659
|
+
const remainingMs = Math.max(0, profileCommandProcessingAvailableAt - Date.now());
|
|
660
|
+
if (remainingMs > 0) {
|
|
661
|
+
profileCommandProcessingScheduled = false;
|
|
662
|
+
scheduleProfileCommandProcessing(remainingMs);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
profileCommandProcessingAvailableAt = 0;
|
|
666
|
+
profileCommandProcessingScheduled = false;
|
|
667
|
+
processSequencedProfileCommands();
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
if (delayMs > 0) {
|
|
671
|
+
profileCommandProcessingTimeoutId = setTimeout(run, delayMs);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (typeof queueMicrotask === 'function') {
|
|
676
|
+
queueMicrotask(run);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
Promise.resolve().then(run);
|
|
681
|
+
}
|
|
682
|
+
|
|
511
683
|
function notifyProfileCommandListeners(command: ProfileSessionCommand) {
|
|
512
684
|
const commandTimestamp = Number.isFinite(command.timestamp) ? command.timestamp : Date.now();
|
|
513
685
|
if (shouldSkipProfileCommandForDuplicateWindow(command, commandTimestamp)) {
|
|
@@ -552,6 +724,82 @@ function notifyProfileCommandListeners(command: ProfileSessionCommand) {
|
|
|
552
724
|
});
|
|
553
725
|
}
|
|
554
726
|
|
|
727
|
+
function processSequencedProfileCommands() {
|
|
728
|
+
if (profileCommandProcessingScheduled) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const remainingMs = Math.max(0, profileCommandProcessingAvailableAt - Date.now());
|
|
732
|
+
if (remainingMs > 0) {
|
|
733
|
+
scheduleProfileCommandProcessing(remainingMs);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
if (profileCommandMilestoneGate) {
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
while (sequencedProfileCommands.length > 0) {
|
|
741
|
+
const command = sequencedProfileCommands.shift();
|
|
742
|
+
if (!command || hasProcessedProfileCommandId(command)) {
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
markProfileCommandIdProcessed(command);
|
|
747
|
+
const nextGate = hasObservedProfileCommandMilestone(command)
|
|
748
|
+
? null
|
|
749
|
+
: buildProfileCommandMilestoneGate(command);
|
|
750
|
+
profileCommandMilestoneGate = nextGate;
|
|
751
|
+
logProfileSession('command', {
|
|
752
|
+
...command,
|
|
753
|
+
status: 'received',
|
|
754
|
+
});
|
|
755
|
+
startProfileCommandMilestoneTimeout(command);
|
|
756
|
+
notifyProfileCommandListeners(command);
|
|
757
|
+
|
|
758
|
+
if (profileCommandMilestoneGate) {
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (typeof command.waitMs === 'number' && command.waitMs > 0) {
|
|
762
|
+
scheduleProfileCommandProcessing(command.waitMs);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function enqueueSequencedProfileCommands(commands: ProfileSessionCommand[]) {
|
|
769
|
+
const nextCommands = commands.filter(shouldQueueProfileCommand);
|
|
770
|
+
if (nextCommands.length === 0) {
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
sequencedProfileCommands.push(...nextCommands);
|
|
775
|
+
sequencedProfileCommands.sort(compareProfileCommands);
|
|
776
|
+
processSequencedProfileCommands();
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function releaseProfileCommandMilestoneGate(eventPayload: StoredProfileEvent) {
|
|
780
|
+
if (!profileCommandMilestoneGate || profileCommandMilestoneGate.milestone !== eventPayload.event) {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
if (
|
|
784
|
+
profileCommandMilestoneGate.runId &&
|
|
785
|
+
profileCommandMilestoneGate.runId !== eventPayload.runId
|
|
786
|
+
) {
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
if (
|
|
790
|
+
profileCommandMilestoneGate.scenario &&
|
|
791
|
+
profileCommandMilestoneGate.scenario !== eventPayload.scenario
|
|
792
|
+
) {
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const waitMs = typeof profileCommandMilestoneGate.waitMs === 'number'
|
|
797
|
+
? profileCommandMilestoneGate.waitMs
|
|
798
|
+
: 0;
|
|
799
|
+
clearProfileCommandMilestoneGate();
|
|
800
|
+
scheduleProfileCommandProcessing(waitMs);
|
|
801
|
+
}
|
|
802
|
+
|
|
555
803
|
function flushPendingProfileCommands(listener: (command: ProfileSessionCommand) => void) {
|
|
556
804
|
if (pendingProfileCommands.length === 0) {
|
|
557
805
|
return;
|
|
@@ -649,13 +897,10 @@ export function applyProfileSessionUrl(url: string | null | undefined): boolean
|
|
|
649
897
|
source: 'deeplink' as const,
|
|
650
898
|
timestamp,
|
|
651
899
|
...(route.waitForMilestone ? { waitForMilestone: route.waitForMilestone } : {}),
|
|
900
|
+
...(typeof route.waitMs === 'number' ? { waitMs: route.waitMs } : {}),
|
|
652
901
|
...(typeof route.waitTimeoutMs === 'number' ? { waitTimeoutMs: route.waitTimeoutMs } : {}),
|
|
653
902
|
};
|
|
654
|
-
|
|
655
|
-
...command,
|
|
656
|
-
status: 'received',
|
|
657
|
-
});
|
|
658
|
-
notifyProfileCommandListeners(command);
|
|
903
|
+
enqueueSequencedProfileCommands([command]);
|
|
659
904
|
return true;
|
|
660
905
|
}
|
|
661
906
|
|
|
@@ -698,7 +943,12 @@ export function emitProfileEvent(event: string, metadata?: ProfileEventMetadata)
|
|
|
698
943
|
};
|
|
699
944
|
|
|
700
945
|
writeProfileLog(buildLogLine('profile-event', eventPayload));
|
|
946
|
+
observedProfileEvents.push(eventPayload);
|
|
947
|
+
while (observedProfileEvents.length > MAX_STORED_PROFILE_EVENTS) {
|
|
948
|
+
observedProfileEvents.shift();
|
|
949
|
+
}
|
|
701
950
|
appendStoredProfileEvent(eventPayload);
|
|
951
|
+
releaseProfileCommandMilestoneGate(eventPayload);
|
|
702
952
|
}
|
|
703
953
|
|
|
704
954
|
/**
|
|
@@ -838,7 +1088,7 @@ export function useProfileSessionBootstrap(): void {
|
|
|
838
1088
|
return;
|
|
839
1089
|
}
|
|
840
1090
|
|
|
841
|
-
|
|
1091
|
+
const nextCommands: ProfileSessionCommand[] = [];
|
|
842
1092
|
for (const command of storedCommands) {
|
|
843
1093
|
if (
|
|
844
1094
|
!command ||
|
|
@@ -851,8 +1101,7 @@ export function useProfileSessionBootstrap(): void {
|
|
|
851
1101
|
|
|
852
1102
|
if (
|
|
853
1103
|
command.scenario !== activeSession.scenario ||
|
|
854
|
-
command.runId !== activeSession.runId
|
|
855
|
-
(typeof activeSession.startedAt === 'number' && command.timestamp < activeSession.startedAt)
|
|
1104
|
+
command.runId !== activeSession.runId
|
|
856
1105
|
) {
|
|
857
1106
|
continue;
|
|
858
1107
|
}
|
|
@@ -866,13 +1115,10 @@ export function useProfileSessionBootstrap(): void {
|
|
|
866
1115
|
continue;
|
|
867
1116
|
}
|
|
868
1117
|
|
|
869
|
-
|
|
870
|
-
logProfileSession('command', {
|
|
871
|
-
...storageCommand,
|
|
872
|
-
status: 'received',
|
|
873
|
-
});
|
|
874
|
-
notifyProfileCommandListeners(storageCommand);
|
|
1118
|
+
nextCommands.push(storageCommand);
|
|
875
1119
|
}
|
|
1120
|
+
|
|
1121
|
+
enqueueSequencedProfileCommands(nextCommands);
|
|
876
1122
|
};
|
|
877
1123
|
|
|
878
1124
|
syncStoredProfileState()
|
|
@@ -56,10 +56,10 @@ declare function extractProfileSessionEntries(logText: string, filters?: {
|
|
|
56
56
|
/**
|
|
57
57
|
* Builds timing metrics from app-emitted profile events.
|
|
58
58
|
*
|
|
59
|
-
* @param {{scenario: string, runId: string, events: Record<string, unknown>[], expectedIterations: number, timeoutCount?: number, artifacts?: Record<string, unknown>, cycleEventNames?: Record<string, string> | null, budgets?: Record<string, unknown> | null}} options
|
|
59
|
+
* @param {{scenario: string, runId: string, events: Record<string, unknown>[], expectedIterations: number, timeoutCount?: number, artifacts?: Record<string, unknown>, cycleEventNames?: Record<string, string> | null, milestoneEventsPerIteration?: number, budgets?: Record<string, unknown> | null}} options
|
|
60
60
|
* @returns {Record<string, unknown>}
|
|
61
61
|
*/
|
|
62
|
-
declare function buildMetricsFromProfileEvents({ scenario, runId, events, expectedIterations, timeoutCount, artifacts, cycleEventNames, budgets, }: {
|
|
62
|
+
declare function buildMetricsFromProfileEvents({ scenario, runId, events, expectedIterations, timeoutCount, artifacts, cycleEventNames, milestoneEventsPerIteration, budgets, }: {
|
|
63
63
|
scenario: string;
|
|
64
64
|
runId: string;
|
|
65
65
|
events: ProfileEvent[];
|
|
@@ -67,17 +67,19 @@ declare function buildMetricsFromProfileEvents({ scenario, runId, events, expect
|
|
|
67
67
|
timeoutCount?: number;
|
|
68
68
|
artifacts?: ArtifactRecord;
|
|
69
69
|
cycleEventNames?: ArtifactRecord | null;
|
|
70
|
+
milestoneEventsPerIteration?: number;
|
|
70
71
|
budgets?: ArtifactRecord | null;
|
|
71
72
|
}): ArtifactRecord;
|
|
72
73
|
/**
|
|
73
74
|
* Evaluates configured profile budgets against generated metrics.
|
|
74
75
|
*
|
|
75
|
-
* @param {{metrics: Record<string, unknown>, budgets?: Record<string, unknown> | null}} options
|
|
76
|
+
* @param {{metrics: Record<string, unknown>, budgets?: Record<string, unknown> | null, extraChecks?: BudgetCheck[]}} options
|
|
76
77
|
* @returns {Record<string, unknown> | null}
|
|
77
78
|
*/
|
|
78
|
-
declare function evaluateProfileBudgets({ metrics, budgets }: {
|
|
79
|
+
declare function evaluateProfileBudgets({ metrics, budgets, extraChecks, }: {
|
|
79
80
|
metrics: ArtifactRecord;
|
|
80
81
|
budgets?: ArtifactRecord | null;
|
|
82
|
+
extraChecks?: BudgetCheck[];
|
|
81
83
|
}): ArtifactRecord | null;
|
|
82
84
|
/**
|
|
83
85
|
* Recursively sorts object keys and array values for stable JSON artifacts.
|