mobile-debug-mcp 0.30.1 → 0.30.2
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/dist/interact/index.js +546 -133
- package/dist/server/tool-definitions.js +13 -9
- package/dist/server/tool-handlers.js +4 -4
- package/dist/server-core.js +1 -1
- package/docs/CHANGELOG.md +3 -0
- package/docs/ROADMAP.md +85 -209
- package/docs/specs-deltas/015-adjustable-control-support.md +541 -0
- package/package.json +1 -1
- package/skills/spec-review/SKILL.md +52 -0
- package/skills/spec-review/references/spec-review-checklist.md +13 -0
- package/skills/spec-review/references/spec-review-template.md +28 -0
- package/src/interact/index.ts +581 -138
- package/src/server/tool-definitions.ts +13 -9
- package/src/server/tool-handlers.ts +4 -4
- package/src/server-core.ts +1 -1
- package/src/types.ts +2 -0
- package/test/unit/interact/adjust_control.test.ts +586 -33
- package/test/unit/server/contract.test.ts +4 -0
- package/skills/rfc-review/SKILL.md +0 -52
- package/skills/rfc-review/references/rfc-review-checklist.md +0 -12
- package/skills/rfc-review/references/rfc-review-template.md +0 -28
- /package/docs/{rfcs → specs-deltas}/001-state-verification.md +0 -0
- /package/docs/{rfcs → specs-deltas}/002-richer-element-identity +0 -0
- /package/docs/{rfcs → specs-deltas}/003-wait-and-synchronization-reliability.md +0 -0
- /package/docs/{rfcs → specs-deltas}/004-action-verification-routing.md +0 -0
- /package/docs/{rfcs → specs-deltas}/005-unified-action-execution-and-verification-model.md +0 -0
- /package/docs/{rfcs → specs-deltas}/006-runtime-action-instrumentation-and-binding-layer.md +0 -0
- /package/docs/{rfcs → specs-deltas}/007-actionability-resolution-and-executable-target-selection.md +0 -0
- /package/docs/{rfcs → specs-deltas}/008-adjustable-control-support-and-semantic-value-manipulation.md +0 -0
- /package/docs/{rfcs → specs-deltas}/009-semantic-control-modeling-for-custom-and-composite-controls.md +0 -0
- /package/docs/{rfcs → specs-deltas}/010-verification-stabilization-and-temporal-convergence.md +0 -0
- /package/docs/{rfcs → specs-deltas}/011-recovery-and-replanning-for-failed-or-ambiguous-interaction-flows.md +0 -0
- /package/docs/{rfcs → specs-deltas}/011.1-recovery-contract-types-and-runtime-wiring-spec.md +0 -0
- /package/docs/{rfcs → specs-deltas}/012-action-trace-and-xecution-observability.md +0 -0
- /package/docs/{rfcs → specs-deltas}/013-wait-and-synchronization-reliability.md +0 -0
- /package/docs/{rfcs → specs-deltas}/014-actionability-resolution.md +0 -0
package/dist/interact/index.js
CHANGED
|
@@ -528,13 +528,214 @@ export class ToolsInteract {
|
|
|
528
528
|
const stateValue = el.state[property];
|
|
529
529
|
if (typeof stateValue === 'number' && Number.isFinite(stateValue))
|
|
530
530
|
return stateValue;
|
|
531
|
+
const parsedStateValue = ToolsInteract._parseNumericControlValue(stateValue);
|
|
532
|
+
if (parsedStateValue !== null)
|
|
533
|
+
return parsedStateValue;
|
|
531
534
|
if (property === 'value' || property === 'raw_value') {
|
|
532
535
|
const fallback = el.state.raw_value ?? el.state.value;
|
|
533
536
|
if (typeof fallback === 'number' && Number.isFinite(fallback))
|
|
534
537
|
return fallback;
|
|
538
|
+
return ToolsInteract._parseNumericControlValue(fallback);
|
|
535
539
|
}
|
|
536
540
|
return null;
|
|
537
541
|
}
|
|
542
|
+
static _parseNumericControlValue(value) {
|
|
543
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
544
|
+
return value;
|
|
545
|
+
if (typeof value !== 'string')
|
|
546
|
+
return null;
|
|
547
|
+
const trimmed = value.trim();
|
|
548
|
+
if (!trimmed)
|
|
549
|
+
return null;
|
|
550
|
+
const percent = trimmed.match(/^([-+]?\d+(?:\.\d+)?)\s*%$/);
|
|
551
|
+
if (percent)
|
|
552
|
+
return Number(percent[1]);
|
|
553
|
+
const fraction = trimmed.match(/^([-+]?\d+(?:\.\d+)?)\s+of\s+[-+]?\d+(?:\.\d+)?$/i);
|
|
554
|
+
if (fraction)
|
|
555
|
+
return Number(fraction[1]);
|
|
556
|
+
const plain = Number(trimmed);
|
|
557
|
+
if (Number.isFinite(plain))
|
|
558
|
+
return plain;
|
|
559
|
+
const trailing = trimmed.match(/,\s*([-+]?\d+(?:\.\d+)?)$/);
|
|
560
|
+
if (trailing)
|
|
561
|
+
return Number(trailing[1]);
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
static _readNumericControlRange(el) {
|
|
565
|
+
const min = typeof el?.state?.value_range?.min === 'number' ? el.state.value_range.min : null;
|
|
566
|
+
const max = typeof el?.state?.value_range?.max === 'number' ? el.state.value_range.max : null;
|
|
567
|
+
if (min !== null && max !== null && Number.isFinite(min) && Number.isFinite(max) && max > min) {
|
|
568
|
+
return { min, max };
|
|
569
|
+
}
|
|
570
|
+
const value = el?.state?.value ?? el?.state?.raw_value;
|
|
571
|
+
if (typeof value === 'string') {
|
|
572
|
+
const fraction = value.trim().match(/^[-+]?\d+(?:\.\d+)?\s+of\s+([-+]?\d+(?:\.\d+)?)$/i);
|
|
573
|
+
if (fraction) {
|
|
574
|
+
const parsedMax = Number(fraction[1]);
|
|
575
|
+
if (Number.isFinite(parsedMax) && parsedMax > 0)
|
|
576
|
+
return { min: 0, max: parsedMax };
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
static _readControlStep(el) {
|
|
582
|
+
const candidates = [
|
|
583
|
+
el?.state?.step,
|
|
584
|
+
el?.state?.value_range?.step
|
|
585
|
+
];
|
|
586
|
+
for (const candidate of candidates) {
|
|
587
|
+
if (typeof candidate === 'number' && Number.isFinite(candidate) && candidate > 0)
|
|
588
|
+
return candidate;
|
|
589
|
+
}
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
static _quantizeTargetValue(targetValue, min, max, step) {
|
|
593
|
+
if (!step)
|
|
594
|
+
return targetValue;
|
|
595
|
+
const quantized = min + (Math.round((targetValue - min) / step) * step);
|
|
596
|
+
const bounded = Math.max(min, Math.min(max, quantized));
|
|
597
|
+
const precision = ToolsInteract._decimalPrecisionForStep(step);
|
|
598
|
+
return Number(bounded.toFixed(precision));
|
|
599
|
+
}
|
|
600
|
+
static _decimalPrecisionForStep(step) {
|
|
601
|
+
const normalized = String(step).toLowerCase();
|
|
602
|
+
const [coefficient, exponentPart] = normalized.split('e');
|
|
603
|
+
const coefficientDecimals = coefficient.includes('.')
|
|
604
|
+
? coefficient.split('.')[1]?.length ?? 0
|
|
605
|
+
: 0;
|
|
606
|
+
const exponent = exponentPart !== undefined ? Number(exponentPart) : 0;
|
|
607
|
+
const precision = exponent < 0
|
|
608
|
+
? coefficientDecimals + Math.abs(exponent)
|
|
609
|
+
: Math.max(0, coefficientDecimals - exponent);
|
|
610
|
+
return Math.max(0, Math.min(12, precision));
|
|
611
|
+
}
|
|
612
|
+
static _defaultAdjustTolerance(range, step) {
|
|
613
|
+
if (step)
|
|
614
|
+
return 0;
|
|
615
|
+
if (!range)
|
|
616
|
+
return 0;
|
|
617
|
+
return Math.max(0, 0.01 * Math.abs(range.max - range.min));
|
|
618
|
+
}
|
|
619
|
+
static _runtimeActionSet(el) {
|
|
620
|
+
const values = [
|
|
621
|
+
...(Array.isArray(el?.actions) ? el.actions : []),
|
|
622
|
+
...(Array.isArray(el?.accessibilityActions) ? el.accessibilityActions : []),
|
|
623
|
+
...(Array.isArray(el?.runtimeActions) ? el.runtimeActions : []),
|
|
624
|
+
...(Array.isArray(el?.platformActions) ? el.platformActions : [])
|
|
625
|
+
];
|
|
626
|
+
return new Set(values.map((value) => ToolsInteract._normalize(value)).filter(Boolean));
|
|
627
|
+
}
|
|
628
|
+
static _hasDirectAdjustmentAction(el) {
|
|
629
|
+
const actions = ToolsInteract._runtimeActionSet(el);
|
|
630
|
+
return [
|
|
631
|
+
'action_set_progress',
|
|
632
|
+
'set_progress',
|
|
633
|
+
'setprogress',
|
|
634
|
+
'semanticsactions.setprogress',
|
|
635
|
+
'set_value',
|
|
636
|
+
'setvalue',
|
|
637
|
+
'direct_set_value'
|
|
638
|
+
].some((action) => actions.has(action));
|
|
639
|
+
}
|
|
640
|
+
static _adjustmentDirections(el) {
|
|
641
|
+
const actions = ToolsInteract._runtimeActionSet(el);
|
|
642
|
+
return {
|
|
643
|
+
increment: ['increment', 'accessibility_increment', 'action_increment'].some((action) => actions.has(action)),
|
|
644
|
+
decrement: ['decrement', 'accessibility_decrement', 'action_decrement'].some((action) => actions.has(action))
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
static _textFromElement(el) {
|
|
648
|
+
return String(el?.text ?? el?.contentDescription ?? el?.contentDesc ?? el?.accessibilityLabel ?? el?.value ?? '').trim();
|
|
649
|
+
}
|
|
650
|
+
static _parseLeadingNumber(text) {
|
|
651
|
+
const match = text.trim().match(/^([-+]?\d+(?:\.\d+)?)(?:\s*[a-zA-Z%]+)?$/);
|
|
652
|
+
if (!match)
|
|
653
|
+
return null;
|
|
654
|
+
const value = Number(match[1]);
|
|
655
|
+
return Number.isFinite(value) ? value : null;
|
|
656
|
+
}
|
|
657
|
+
static _inferLabeledSliderState(elements, track) {
|
|
658
|
+
const trackBounds = ToolsInteract._normalizeBounds(track?.bounds);
|
|
659
|
+
if (!track || !trackBounds)
|
|
660
|
+
return null;
|
|
661
|
+
const type = ToolsInteract._normalize(track.type ?? track.class ?? '');
|
|
662
|
+
const role = ToolsInteract._normalize(track.role ?? '');
|
|
663
|
+
const width = trackBounds[2] - trackBounds[0];
|
|
664
|
+
const height = trackBounds[3] - trackBounds[1];
|
|
665
|
+
const wideTrackLike = width >= 240 && height <= 220;
|
|
666
|
+
const knownTrackLike = /slider|seekbar|range/.test(type) || /slider|range/.test(role);
|
|
667
|
+
if (!wideTrackLike && !knownTrackLike)
|
|
668
|
+
return null;
|
|
669
|
+
const trackTop = trackBounds[1];
|
|
670
|
+
const trackBottom = trackBounds[3];
|
|
671
|
+
const trackCenterX = (trackBounds[0] + trackBounds[2]) / 2;
|
|
672
|
+
const labels = elements
|
|
673
|
+
.map((el, index) => {
|
|
674
|
+
const bounds = ToolsInteract._normalizeBounds(el.bounds);
|
|
675
|
+
const text = ToolsInteract._textFromElement(el);
|
|
676
|
+
const value = ToolsInteract._parseLeadingNumber(text);
|
|
677
|
+
return bounds && text && value !== null
|
|
678
|
+
? {
|
|
679
|
+
index,
|
|
680
|
+
el,
|
|
681
|
+
text,
|
|
682
|
+
value,
|
|
683
|
+
bounds,
|
|
684
|
+
centerX: (bounds[0] + bounds[2]) / 2,
|
|
685
|
+
centerY: (bounds[1] + bounds[3]) / 2,
|
|
686
|
+
hasUnit: /[a-zA-Z%]/.test(text)
|
|
687
|
+
}
|
|
688
|
+
: null;
|
|
689
|
+
})
|
|
690
|
+
.filter((label) => !!label);
|
|
691
|
+
const rangeLabels = labels
|
|
692
|
+
.filter((label) => {
|
|
693
|
+
const verticallyNearTrack = label.centerY >= trackTop - 520 && label.centerY <= trackBottom + 260;
|
|
694
|
+
const horizontallyNearTrack = label.centerX >= trackBounds[0] - 120 && label.centerX <= trackBounds[2] + 120;
|
|
695
|
+
return verticallyNearTrack && horizontallyNearTrack && label.hasUnit;
|
|
696
|
+
})
|
|
697
|
+
.sort((a, b) => a.centerX - b.centerX);
|
|
698
|
+
if (rangeLabels.length < 2)
|
|
699
|
+
return null;
|
|
700
|
+
const minLabel = rangeLabels[0];
|
|
701
|
+
const maxLabel = rangeLabels[rangeLabels.length - 1];
|
|
702
|
+
if (!minLabel || !maxLabel || maxLabel.value <= minLabel.value)
|
|
703
|
+
return null;
|
|
704
|
+
const currentCandidates = labels
|
|
705
|
+
.filter((label) => {
|
|
706
|
+
if (label.index === minLabel.index || label.index === maxLabel.index)
|
|
707
|
+
return false;
|
|
708
|
+
if (label.value < minLabel.value || label.value > maxLabel.value)
|
|
709
|
+
return false;
|
|
710
|
+
if (label.centerY < trackTop - 950 || label.centerY > minLabel.centerY - 20)
|
|
711
|
+
return false;
|
|
712
|
+
return label.centerX >= trackCenterX - 80;
|
|
713
|
+
})
|
|
714
|
+
.sort((a, b) => {
|
|
715
|
+
const yDistance = Math.abs((trackTop - 420) - a.centerY) - Math.abs((trackTop - 420) - b.centerY);
|
|
716
|
+
if (yDistance !== 0)
|
|
717
|
+
return yDistance;
|
|
718
|
+
return b.centerX - a.centerX;
|
|
719
|
+
});
|
|
720
|
+
const currentLabel = currentCandidates[0];
|
|
721
|
+
if (!currentLabel)
|
|
722
|
+
return null;
|
|
723
|
+
const candidateBelowRangeLabels = minLabel.centerY < trackTop && maxLabel.centerY < trackTop;
|
|
724
|
+
const labelDerivedBounds = [
|
|
725
|
+
Math.min(minLabel.bounds[0], trackBounds[0]),
|
|
726
|
+
Math.max(currentLabel.bounds[3] + 12, Math.min(minLabel.bounds[1], maxLabel.bounds[1]) - 160),
|
|
727
|
+
Math.max(maxLabel.bounds[2], trackBounds[2]),
|
|
728
|
+
Math.max(currentLabel.bounds[3] + 80, Math.min(minLabel.bounds[1], maxLabel.bounds[1]) - 32)
|
|
729
|
+
];
|
|
730
|
+
return {
|
|
731
|
+
current: currentLabel.value,
|
|
732
|
+
min: minLabel.value,
|
|
733
|
+
max: maxLabel.value,
|
|
734
|
+
value_range: { min: minLabel.value, max: maxLabel.value },
|
|
735
|
+
touch_bounds: candidateBelowRangeLabels ? labelDerivedBounds : trackBounds,
|
|
736
|
+
source: 'nearby_labels'
|
|
737
|
+
};
|
|
738
|
+
}
|
|
538
739
|
static _buildControlPoint(bounds, ratio, axis) {
|
|
539
740
|
const clampedRatio = Math.max(0, Math.min(1, ratio));
|
|
540
741
|
const [left, top, right, bottom] = bounds;
|
|
@@ -797,11 +998,13 @@ export class ToolsInteract {
|
|
|
797
998
|
sourceModule: 'interact'
|
|
798
999
|
});
|
|
799
1000
|
}
|
|
800
|
-
static async adjustControlHandler({ selector, element_id, property = 'value', targetValue, tolerance
|
|
1001
|
+
static async adjustControlHandler({ selector, element_id, property = 'value', targetValue, tolerance, maxAttempts, platform, deviceId }) {
|
|
801
1002
|
const actionType = 'adjust_control';
|
|
802
1003
|
const targetSelector = selector ?? (element_id ? { elementId: element_id } : null);
|
|
803
|
-
const
|
|
804
|
-
const attemptsLimit =
|
|
1004
|
+
const requestedTolerance = typeof tolerance === 'number' && Number.isFinite(tolerance) ? Math.max(0, tolerance) : null;
|
|
1005
|
+
const attemptsLimit = maxAttempts === undefined || maxAttempts === null
|
|
1006
|
+
? 5
|
|
1007
|
+
: Math.max(1, Math.floor(Number(maxAttempts) || 1));
|
|
805
1008
|
const sourcePlatform = platform || 'android';
|
|
806
1009
|
let resolvedPlatform = sourcePlatform;
|
|
807
1010
|
let resolvedDeviceId = deviceId;
|
|
@@ -835,6 +1038,7 @@ export class ToolsInteract {
|
|
|
835
1038
|
retry_depth: attempts
|
|
836
1039
|
});
|
|
837
1040
|
}
|
|
1041
|
+
const responseTolerance = requestedTolerance ?? 0;
|
|
838
1042
|
const base = buildActionExecutionResult({
|
|
839
1043
|
actionType,
|
|
840
1044
|
sourceModule: 'interact',
|
|
@@ -847,7 +1051,7 @@ export class ToolsInteract {
|
|
|
847
1051
|
failure: { failureCode, retryable },
|
|
848
1052
|
details: {
|
|
849
1053
|
target_value: targetValue,
|
|
850
|
-
tolerance:
|
|
1054
|
+
tolerance: responseTolerance,
|
|
851
1055
|
property,
|
|
852
1056
|
attempts,
|
|
853
1057
|
adjustment_mode: adjustmentMode,
|
|
@@ -863,7 +1067,7 @@ export class ToolsInteract {
|
|
|
863
1067
|
target_state: {
|
|
864
1068
|
property,
|
|
865
1069
|
target_value: targetValue,
|
|
866
|
-
tolerance:
|
|
1070
|
+
tolerance: responseTolerance
|
|
867
1071
|
},
|
|
868
1072
|
actual_state: actualState,
|
|
869
1073
|
within_tolerance: false,
|
|
@@ -955,8 +1159,8 @@ export class ToolsInteract {
|
|
|
955
1159
|
}
|
|
956
1160
|
return null;
|
|
957
1161
|
};
|
|
958
|
-
if (!selector && !element_id) {
|
|
959
|
-
return buildFailure('ELEMENT_NOT_FOUND', 'selector or element_id is required', null, undefined, null, 0, 'gesture', false);
|
|
1162
|
+
if ((!selector && !element_id) || (selector && element_id)) {
|
|
1163
|
+
return buildFailure('ELEMENT_NOT_FOUND', 'exactly one of selector or element_id is required', null, undefined, null, 0, 'gesture', false);
|
|
960
1164
|
}
|
|
961
1165
|
if (selector && !element_id) {
|
|
962
1166
|
const waitResult = await ToolsInteract.waitForUIHandler({
|
|
@@ -995,7 +1199,8 @@ export class ToolsInteract {
|
|
|
995
1199
|
let currentDevice = undefined;
|
|
996
1200
|
let attemptCount = 0;
|
|
997
1201
|
let cachedResolvedMatch = null;
|
|
998
|
-
|
|
1202
|
+
let coordinateTapsAwaitingFreshGesture = false;
|
|
1203
|
+
adjustmentAttempts: for (let attempt = 0; attempt < attemptsLimit && attemptCount < attemptsLimit; attempt++) {
|
|
999
1204
|
const resolved = cachedResolvedMatch
|
|
1000
1205
|
? {
|
|
1001
1206
|
tree: null,
|
|
@@ -1015,11 +1220,29 @@ export class ToolsInteract {
|
|
|
1015
1220
|
if (!adjustableActionability.actionable) {
|
|
1016
1221
|
return buildFailure(adjustableActionability.failureCode ?? 'ELEMENT_NOT_INTERACTABLE', adjustableActionability.reason ?? 'adjustable control is not actionable', resolvedTarget, currentDevice, lastObservedState, attemptCount, lastAdjustmentMode, true);
|
|
1017
1222
|
}
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
|
|
1223
|
+
const elementsForInference = Array.isArray(resolved.tree?.elements)
|
|
1224
|
+
? resolved.tree.elements
|
|
1225
|
+
: cachedResolvedMatch
|
|
1226
|
+
? [cachedResolvedMatch.el]
|
|
1227
|
+
: [currentEl];
|
|
1228
|
+
const inferredSliderState = ToolsInteract._inferLabeledSliderState(elementsForInference, currentEl);
|
|
1229
|
+
const nativeBounds = ToolsInteract._normalizeBounds(currentEl.bounds);
|
|
1230
|
+
const bounds = inferredSliderState?.touch_bounds ?? nativeBounds;
|
|
1231
|
+
const valueRange = ToolsInteract._readNumericControlRange(currentEl) ?? inferredSliderState?.value_range ?? null;
|
|
1232
|
+
const step = ToolsInteract._readControlStep(currentEl);
|
|
1233
|
+
const normalizedTargetValue = valueRange
|
|
1234
|
+
? ToolsInteract._quantizeTargetValue(targetValue, valueRange.min, valueRange.max, step)
|
|
1235
|
+
: targetValue;
|
|
1236
|
+
const effectiveTolerance = requestedTolerance ?? ToolsInteract._defaultAdjustTolerance(valueRange, step);
|
|
1237
|
+
const currentValue = ToolsInteract._readNumericControlValue(currentEl, property) ?? inferredSliderState?.current ?? null;
|
|
1021
1238
|
const actualState = currentValue !== null
|
|
1022
|
-
? {
|
|
1239
|
+
? {
|
|
1240
|
+
property,
|
|
1241
|
+
value: currentValue,
|
|
1242
|
+
raw_value: typeof currentEl.state?.raw_value === 'number'
|
|
1243
|
+
? currentEl.state.raw_value
|
|
1244
|
+
: inferredSliderState?.current
|
|
1245
|
+
}
|
|
1023
1246
|
: null;
|
|
1024
1247
|
lastObservedState = actualState;
|
|
1025
1248
|
if (!traceSteps.some((step) => step.stage === 'resolve')) {
|
|
@@ -1032,10 +1255,20 @@ export class ToolsInteract {
|
|
|
1032
1255
|
if (property !== 'value' && property !== 'raw_value') {
|
|
1033
1256
|
return buildFailure('ELEMENT_NOT_INTERACTABLE', 'adjust_control currently supports numeric value and raw_value properties only', resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false);
|
|
1034
1257
|
}
|
|
1035
|
-
|
|
1258
|
+
const isAdjustableControl = ToolsInteract._isAdjustableControl(currentEl) || !!inferredSliderState;
|
|
1259
|
+
if (!isAdjustableControl) {
|
|
1260
|
+
return buildFailure('ELEMENT_NOT_INTERACTABLE', 'target is not an adjustable control', resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false);
|
|
1261
|
+
}
|
|
1262
|
+
if (currentValue === null) {
|
|
1263
|
+
return buildFailure('UNKNOWN', 'current numeric value is unavailable', resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false);
|
|
1264
|
+
}
|
|
1265
|
+
if (valueRange && (targetValue < valueRange.min || targetValue > valueRange.max)) {
|
|
1266
|
+
return buildFailure('CONTROL_CONVERGENCE_FAILED', `targetValue ${targetValue} is outside the control range ${valueRange.min}..${valueRange.max}`, resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false);
|
|
1267
|
+
}
|
|
1268
|
+
if (currentValue !== null && Math.abs(currentValue - normalizedTargetValue) <= effectiveTolerance) {
|
|
1036
1269
|
recordTraceStep('verify', 'success', {
|
|
1037
1270
|
property,
|
|
1038
|
-
target_value:
|
|
1271
|
+
target_value: normalizedTargetValue,
|
|
1039
1272
|
actual_state: actualState,
|
|
1040
1273
|
reason: 'control already within tolerance'
|
|
1041
1274
|
});
|
|
@@ -1050,8 +1283,8 @@ export class ToolsInteract {
|
|
|
1050
1283
|
uiFingerprintBefore: fingerprintBefore,
|
|
1051
1284
|
uiFingerprintAfter,
|
|
1052
1285
|
details: {
|
|
1053
|
-
target_value:
|
|
1054
|
-
tolerance:
|
|
1286
|
+
target_value: normalizedTargetValue,
|
|
1287
|
+
tolerance: effectiveTolerance,
|
|
1055
1288
|
property,
|
|
1056
1289
|
attempts: attemptCount,
|
|
1057
1290
|
adjustment_mode: 'semantic',
|
|
@@ -1066,8 +1299,8 @@ export class ToolsInteract {
|
|
|
1066
1299
|
...base,
|
|
1067
1300
|
target_state: {
|
|
1068
1301
|
property,
|
|
1069
|
-
target_value:
|
|
1070
|
-
tolerance:
|
|
1302
|
+
target_value: normalizedTargetValue,
|
|
1303
|
+
tolerance: effectiveTolerance
|
|
1071
1304
|
},
|
|
1072
1305
|
actual_state: actualState,
|
|
1073
1306
|
within_tolerance: true,
|
|
@@ -1076,40 +1309,40 @@ export class ToolsInteract {
|
|
|
1076
1309
|
adjustment_mode: 'semantic'
|
|
1077
1310
|
};
|
|
1078
1311
|
}
|
|
1079
|
-
if (!ToolsInteract._isAdjustableControl(currentEl)) {
|
|
1080
|
-
return buildFailure('ELEMENT_NOT_INTERACTABLE', 'target is not an adjustable control', resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false);
|
|
1081
|
-
}
|
|
1082
1312
|
if (!bounds) {
|
|
1083
1313
|
return buildFailure('ELEMENT_NOT_INTERACTABLE', 'adjustable control has no bounds', resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false);
|
|
1084
1314
|
}
|
|
1085
|
-
const min = typeof valueRange?.min === 'number' ? valueRange.min : null;
|
|
1086
|
-
const max = typeof valueRange?.max === 'number' ? valueRange.max : null;
|
|
1087
|
-
if (min === null || max === null || !Number.isFinite(min) || !Number.isFinite(max) || max <= min) {
|
|
1088
|
-
return buildFailure('ELEMENT_NOT_INTERACTABLE', 'value_range unavailable', resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false);
|
|
1089
|
-
}
|
|
1090
|
-
if (targetValue < min || targetValue > max) {
|
|
1091
|
-
return buildFailure('UNKNOWN', `targetValue ${targetValue} is outside the control range ${min}..${max}`, resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false);
|
|
1092
|
-
}
|
|
1093
|
-
const axis = ToolsInteract._controlAxis(currentEl, bounds);
|
|
1094
|
-
const targetPoint = ToolsInteract._buildConservativeControlPoint(bounds, targetValue, currentValue, min, max, axis);
|
|
1095
|
-
const currentPoint = currentValue !== null
|
|
1096
|
-
? ToolsInteract._buildControlPoint(bounds, (currentValue - min) / (max - min), axis)
|
|
1097
|
-
: ToolsInteract._buildControlPoint(bounds, 0.5, axis);
|
|
1098
|
-
const probePoints = ToolsInteract._buildAdjustmentProbePoints(bounds, targetValue, currentValue, min, max, axis);
|
|
1099
1315
|
const runVerification = async () => {
|
|
1316
|
+
if (inferredSliderState) {
|
|
1317
|
+
const tree = await ToolsObserve.getUITreeHandler({ platform: resolvedPlatform, deviceId: resolvedDeviceId });
|
|
1318
|
+
const elements = Array.isArray(tree?.elements) ? tree.elements : [];
|
|
1319
|
+
const refreshed = ToolsInteract._inferLabeledSliderState(elements, currentEl);
|
|
1320
|
+
const observedValue = refreshed?.current ?? null;
|
|
1321
|
+
const observedState = observedValue !== null
|
|
1322
|
+
? { property, value: observedValue, raw_value: observedValue }
|
|
1323
|
+
: actualState;
|
|
1324
|
+
return {
|
|
1325
|
+
verification: {
|
|
1326
|
+
success: observedValue !== null && Math.abs(observedValue - normalizedTargetValue) <= effectiveTolerance,
|
|
1327
|
+
observed_state: observedState,
|
|
1328
|
+
reason: observedValue !== null
|
|
1329
|
+
? 'visible labeled value read from nearby slider labels'
|
|
1330
|
+
: 'visible labeled value unavailable'
|
|
1331
|
+
},
|
|
1332
|
+
observedState,
|
|
1333
|
+
withinTolerance: observedValue !== null && Math.abs(observedValue - normalizedTargetValue) <= effectiveTolerance
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1100
1336
|
const verification = await ToolsInteract.expectStateHandler({
|
|
1101
1337
|
element_id: resolvedTarget?.elementId ?? element_id,
|
|
1102
1338
|
selector: selector ?? undefined,
|
|
1103
1339
|
property,
|
|
1104
|
-
expected:
|
|
1340
|
+
expected: normalizedTargetValue,
|
|
1105
1341
|
platform: resolvedPlatform,
|
|
1106
1342
|
deviceId: resolvedDeviceId
|
|
1107
1343
|
});
|
|
1108
|
-
const observedValue =
|
|
1109
|
-
|
|
1110
|
-
: typeof verification?.observed_state?.raw_value === 'number'
|
|
1111
|
-
? verification.observed_state.raw_value
|
|
1112
|
-
: null;
|
|
1344
|
+
const observedValue = ToolsInteract._parseNumericControlValue(verification?.observed_state?.value) ??
|
|
1345
|
+
ToolsInteract._parseNumericControlValue(verification?.observed_state?.raw_value);
|
|
1113
1346
|
const observedState = observedValue !== null
|
|
1114
1347
|
? {
|
|
1115
1348
|
property,
|
|
@@ -1120,88 +1353,266 @@ export class ToolsInteract {
|
|
|
1120
1353
|
return {
|
|
1121
1354
|
verification,
|
|
1122
1355
|
observedState,
|
|
1123
|
-
withinTolerance: observedValue !== null && Math.abs(observedValue -
|
|
1356
|
+
withinTolerance: observedValue !== null && Math.abs(observedValue - normalizedTargetValue) <= effectiveTolerance
|
|
1124
1357
|
};
|
|
1125
1358
|
};
|
|
1126
1359
|
let actionDevice = currentDevice;
|
|
1127
1360
|
let observedState = actualState;
|
|
1128
1361
|
let verification = null;
|
|
1129
1362
|
let verificationResult = { verification: null, observedState: actualState, withinTolerance: false };
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
mode: 'coordinate',
|
|
1136
|
-
point: probePoint
|
|
1137
|
-
});
|
|
1138
|
-
const actionResult = await ToolsInteract.tapHandler({
|
|
1139
|
-
platform: resolvedPlatform,
|
|
1140
|
-
x: probePoint.x,
|
|
1141
|
-
y: probePoint.y,
|
|
1142
|
-
deviceId: resolvedDeviceId
|
|
1143
|
-
});
|
|
1144
|
-
attemptCount++;
|
|
1145
|
-
actionDevice = actionResult.device ?? actionDevice;
|
|
1146
|
-
if (!actionResult.success) {
|
|
1363
|
+
const directActionSupported = ToolsInteract._hasDirectAdjustmentAction(currentEl);
|
|
1364
|
+
if (directActionSupported && attemptCount < attemptsLimit) {
|
|
1365
|
+
const { interact, resolved: actionResolved } = await ToolsInteract.getInteractionService(resolvedPlatform, resolvedDeviceId);
|
|
1366
|
+
if (typeof interact.setAdjustableValue === 'function') {
|
|
1367
|
+
lastAdjustmentMode = 'semantic';
|
|
1147
1368
|
recordTraceStep('execute', 'retry', {
|
|
1148
|
-
attempt: attemptCount,
|
|
1149
|
-
mode: '
|
|
1150
|
-
|
|
1151
|
-
|
|
1369
|
+
attempt: attemptCount + 1,
|
|
1370
|
+
mode: 'semantic',
|
|
1371
|
+
strategy: 'direct_set_value',
|
|
1372
|
+
target_value: normalizedTargetValue
|
|
1152
1373
|
});
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
const base = buildActionExecutionResult({
|
|
1168
|
-
actionType,
|
|
1169
|
-
sourceModule: 'interact',
|
|
1170
|
-
device: actionDevice ?? currentDevice,
|
|
1171
|
-
selector: targetSelector,
|
|
1172
|
-
resolved: resolvedTarget,
|
|
1173
|
-
success: true,
|
|
1174
|
-
uiFingerprintBefore: fingerprintBefore,
|
|
1175
|
-
uiFingerprintAfter,
|
|
1176
|
-
details: {
|
|
1177
|
-
target_value: targetValue,
|
|
1178
|
-
tolerance: normalizedTolerance,
|
|
1374
|
+
const actionResult = await interact.setAdjustableValue({
|
|
1375
|
+
deviceId: actionResolved.id,
|
|
1376
|
+
target: resolvedTarget,
|
|
1377
|
+
property,
|
|
1378
|
+
value: normalizedTargetValue
|
|
1379
|
+
});
|
|
1380
|
+
attemptCount++;
|
|
1381
|
+
actionDevice = actionResult?.device ?? actionDevice;
|
|
1382
|
+
if (actionResult?.success !== false) {
|
|
1383
|
+
verificationResult = await runVerification();
|
|
1384
|
+
observedState = verificationResult.observedState;
|
|
1385
|
+
lastObservedState = observedState;
|
|
1386
|
+
recordTraceStep('verify', verificationResult.withinTolerance ? 'success' : 'retry', {
|
|
1387
|
+
attempt: attemptCount,
|
|
1179
1388
|
property,
|
|
1180
|
-
|
|
1181
|
-
|
|
1389
|
+
target_value: normalizedTargetValue,
|
|
1390
|
+
actual_state: observedState,
|
|
1391
|
+
reason: verificationResult.verification?.reason ?? 'direct adjustment did not converge yet'
|
|
1392
|
+
});
|
|
1393
|
+
if (verificationResult.withinTolerance) {
|
|
1394
|
+
const uiFingerprintAfter = await ToolsInteract._captureFingerprint(resolvedPlatform, resolvedDeviceId);
|
|
1395
|
+
const base = buildActionExecutionResult({
|
|
1396
|
+
actionType,
|
|
1397
|
+
sourceModule: 'interact',
|
|
1398
|
+
device: actionDevice ?? currentDevice,
|
|
1399
|
+
selector: targetSelector,
|
|
1400
|
+
resolved: resolvedTarget,
|
|
1401
|
+
success: true,
|
|
1402
|
+
uiFingerprintBefore: fingerprintBefore,
|
|
1403
|
+
uiFingerprintAfter,
|
|
1404
|
+
details: {
|
|
1405
|
+
target_value: normalizedTargetValue,
|
|
1406
|
+
tolerance: effectiveTolerance,
|
|
1407
|
+
property,
|
|
1408
|
+
attempts: attemptCount,
|
|
1409
|
+
adjustment_mode: lastAdjustmentMode,
|
|
1410
|
+
actual_state: observedState,
|
|
1411
|
+
converged: true,
|
|
1412
|
+
within_tolerance: true,
|
|
1413
|
+
reason: verificationResult.verification?.reason ?? 'control converged to target value'
|
|
1414
|
+
},
|
|
1415
|
+
traceSteps
|
|
1416
|
+
});
|
|
1417
|
+
return {
|
|
1418
|
+
...base,
|
|
1419
|
+
target_state: {
|
|
1420
|
+
property,
|
|
1421
|
+
target_value: normalizedTargetValue,
|
|
1422
|
+
tolerance: effectiveTolerance
|
|
1423
|
+
},
|
|
1424
|
+
actual_state: observedState,
|
|
1425
|
+
within_tolerance: true,
|
|
1426
|
+
converged: true,
|
|
1427
|
+
attempts: attemptCount,
|
|
1428
|
+
adjustment_mode: lastAdjustmentMode
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
cachedResolvedMatch = null;
|
|
1432
|
+
if (attemptCount < attemptsLimit) {
|
|
1433
|
+
continue;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
const directions = ToolsInteract._adjustmentDirections(currentEl);
|
|
1439
|
+
if ((directions.increment || directions.decrement) && attemptCount < attemptsLimit) {
|
|
1440
|
+
const { interact, resolved: actionResolved } = await ToolsInteract.getInteractionService(resolvedPlatform, resolvedDeviceId);
|
|
1441
|
+
const adjustByDirection = typeof interact.adjustAccessibleValue === 'function'
|
|
1442
|
+
? interact.adjustAccessibleValue.bind(interact)
|
|
1443
|
+
: null;
|
|
1444
|
+
while (adjustByDirection && attemptCount < attemptsLimit) {
|
|
1445
|
+
const direction = normalizedTargetValue > (observedState?.value ?? currentValue) ? 'increment' : 'decrement';
|
|
1446
|
+
if ((direction === 'increment' && !directions.increment) || (direction === 'decrement' && !directions.decrement))
|
|
1447
|
+
break;
|
|
1448
|
+
lastAdjustmentMode = 'semantic';
|
|
1449
|
+
recordTraceStep('execute', 'retry', {
|
|
1450
|
+
attempt: attemptCount + 1,
|
|
1451
|
+
mode: 'semantic',
|
|
1452
|
+
strategy: 'accessibility_increment_decrement',
|
|
1453
|
+
direction
|
|
1454
|
+
});
|
|
1455
|
+
const actionResult = await adjustByDirection({
|
|
1456
|
+
deviceId: actionResolved.id,
|
|
1457
|
+
target: resolvedTarget,
|
|
1458
|
+
property,
|
|
1459
|
+
direction
|
|
1460
|
+
});
|
|
1461
|
+
attemptCount++;
|
|
1462
|
+
actionDevice = actionResult?.device ?? actionDevice;
|
|
1463
|
+
if (actionResult?.success === false)
|
|
1464
|
+
break;
|
|
1465
|
+
verificationResult = await runVerification();
|
|
1466
|
+
observedState = verificationResult.observedState;
|
|
1467
|
+
lastObservedState = observedState;
|
|
1468
|
+
recordTraceStep('verify', verificationResult.withinTolerance ? 'success' : 'retry', {
|
|
1469
|
+
attempt: attemptCount,
|
|
1470
|
+
property,
|
|
1471
|
+
target_value: normalizedTargetValue,
|
|
1472
|
+
actual_state: observedState,
|
|
1473
|
+
reason: verificationResult.verification?.reason ?? 'incremental adjustment did not converge yet'
|
|
1474
|
+
});
|
|
1475
|
+
if (verificationResult.withinTolerance) {
|
|
1476
|
+
const uiFingerprintAfter = await ToolsInteract._captureFingerprint(resolvedPlatform, resolvedDeviceId);
|
|
1477
|
+
const base = buildActionExecutionResult({
|
|
1478
|
+
actionType,
|
|
1479
|
+
sourceModule: 'interact',
|
|
1480
|
+
device: actionDevice ?? currentDevice,
|
|
1481
|
+
selector: targetSelector,
|
|
1482
|
+
resolved: resolvedTarget,
|
|
1483
|
+
success: true,
|
|
1484
|
+
uiFingerprintBefore: fingerprintBefore,
|
|
1485
|
+
uiFingerprintAfter,
|
|
1486
|
+
details: {
|
|
1487
|
+
target_value: normalizedTargetValue,
|
|
1488
|
+
tolerance: effectiveTolerance,
|
|
1489
|
+
property,
|
|
1490
|
+
attempts: attemptCount,
|
|
1491
|
+
adjustment_mode: lastAdjustmentMode,
|
|
1492
|
+
actual_state: observedState,
|
|
1493
|
+
converged: true,
|
|
1494
|
+
within_tolerance: true,
|
|
1495
|
+
reason: verificationResult.verification?.reason ?? 'control converged to target value'
|
|
1496
|
+
},
|
|
1497
|
+
traceSteps
|
|
1498
|
+
});
|
|
1499
|
+
return {
|
|
1500
|
+
...base,
|
|
1501
|
+
target_state: {
|
|
1502
|
+
property,
|
|
1503
|
+
target_value: normalizedTargetValue,
|
|
1504
|
+
tolerance: effectiveTolerance
|
|
1505
|
+
},
|
|
1182
1506
|
actual_state: observedState,
|
|
1183
|
-
converged: true,
|
|
1184
1507
|
within_tolerance: true,
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1508
|
+
converged: true,
|
|
1509
|
+
attempts: attemptCount,
|
|
1510
|
+
adjustment_mode: lastAdjustmentMode
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
cachedResolvedMatch = null;
|
|
1514
|
+
if (attemptCount < attemptsLimit) {
|
|
1515
|
+
continue adjustmentAttempts;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
if (!bounds) {
|
|
1520
|
+
return buildFailure('ELEMENT_NOT_INTERACTABLE', 'adjustable control has no bounds', resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false);
|
|
1521
|
+
}
|
|
1522
|
+
if (!valueRange) {
|
|
1523
|
+
return buildFailure('UNKNOWN', 'value_range unavailable', resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false);
|
|
1524
|
+
}
|
|
1525
|
+
const min = valueRange.min;
|
|
1526
|
+
const max = valueRange.max;
|
|
1527
|
+
const axis = ToolsInteract._controlAxis(currentEl, bounds);
|
|
1528
|
+
const targetPoint = ToolsInteract._buildConservativeControlPoint(bounds, normalizedTargetValue, currentValue, min, max, axis);
|
|
1529
|
+
const currentPoint = ToolsInteract._buildControlPoint(bounds, (currentValue - min) / (max - min), axis);
|
|
1530
|
+
if (!coordinateTapsAwaitingFreshGesture) {
|
|
1531
|
+
const probePoints = inferredSliderState
|
|
1532
|
+
? [targetPoint]
|
|
1533
|
+
: ToolsInteract._buildAdjustmentProbePoints(bounds, normalizedTargetValue, currentValue, min, max, axis);
|
|
1534
|
+
for (let i = 0; i < probePoints.length && attemptCount < attemptsLimit; i++) {
|
|
1535
|
+
const probePoint = probePoints[i];
|
|
1536
|
+
lastAdjustmentMode = 'coordinate';
|
|
1537
|
+
recordTraceStep('execute', 'retry', {
|
|
1538
|
+
attempt: attemptCount + 1,
|
|
1539
|
+
mode: 'coordinate',
|
|
1540
|
+
point: probePoint
|
|
1188
1541
|
});
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1542
|
+
const actionResult = await ToolsInteract.tapHandler({
|
|
1543
|
+
platform: resolvedPlatform,
|
|
1544
|
+
x: probePoint.x,
|
|
1545
|
+
y: probePoint.y,
|
|
1546
|
+
deviceId: resolvedDeviceId
|
|
1547
|
+
});
|
|
1548
|
+
attemptCount++;
|
|
1549
|
+
actionDevice = actionResult.device ?? actionDevice;
|
|
1550
|
+
if (!actionResult.success) {
|
|
1551
|
+
recordTraceStep('execute', 'retry', {
|
|
1552
|
+
attempt: attemptCount,
|
|
1553
|
+
mode: 'coordinate',
|
|
1554
|
+
point: probePoint,
|
|
1555
|
+
success: false
|
|
1556
|
+
});
|
|
1557
|
+
continue;
|
|
1558
|
+
}
|
|
1559
|
+
verificationResult = await runVerification();
|
|
1560
|
+
observedState = verificationResult.observedState;
|
|
1561
|
+
lastObservedState = observedState;
|
|
1562
|
+
recordTraceStep('verify', verificationResult.withinTolerance ? 'success' : 'retry', {
|
|
1563
|
+
attempt: attemptCount,
|
|
1564
|
+
property,
|
|
1565
|
+
target_value: normalizedTargetValue,
|
|
1196
1566
|
actual_state: observedState,
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1567
|
+
reason: verificationResult.verification?.reason ?? 'control did not converge yet'
|
|
1568
|
+
});
|
|
1569
|
+
if (verificationResult.withinTolerance) {
|
|
1570
|
+
const uiFingerprintAfter = await ToolsInteract._captureFingerprint(resolvedPlatform, resolvedDeviceId);
|
|
1571
|
+
const base = buildActionExecutionResult({
|
|
1572
|
+
actionType,
|
|
1573
|
+
sourceModule: 'interact',
|
|
1574
|
+
device: actionDevice ?? currentDevice,
|
|
1575
|
+
selector: targetSelector,
|
|
1576
|
+
resolved: resolvedTarget,
|
|
1577
|
+
success: true,
|
|
1578
|
+
uiFingerprintBefore: fingerprintBefore,
|
|
1579
|
+
uiFingerprintAfter,
|
|
1580
|
+
details: {
|
|
1581
|
+
target_value: normalizedTargetValue,
|
|
1582
|
+
tolerance: effectiveTolerance,
|
|
1583
|
+
property,
|
|
1584
|
+
attempts: attemptCount,
|
|
1585
|
+
adjustment_mode: lastAdjustmentMode,
|
|
1586
|
+
actual_state: observedState,
|
|
1587
|
+
converged: true,
|
|
1588
|
+
within_tolerance: true,
|
|
1589
|
+
reason: verificationResult.verification?.reason ?? 'control converged to target value'
|
|
1590
|
+
},
|
|
1591
|
+
traceSteps
|
|
1592
|
+
});
|
|
1593
|
+
return {
|
|
1594
|
+
...base,
|
|
1595
|
+
target_state: {
|
|
1596
|
+
property,
|
|
1597
|
+
target_value: normalizedTargetValue,
|
|
1598
|
+
tolerance: effectiveTolerance
|
|
1599
|
+
},
|
|
1600
|
+
actual_state: observedState,
|
|
1601
|
+
within_tolerance: true,
|
|
1602
|
+
converged: true,
|
|
1603
|
+
attempts: attemptCount,
|
|
1604
|
+
adjustment_mode: lastAdjustmentMode
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
coordinateTapsAwaitingFreshGesture = true;
|
|
1608
|
+
cachedResolvedMatch = null;
|
|
1609
|
+
if (attemptCount < attemptsLimit) {
|
|
1610
|
+
continue adjustmentAttempts;
|
|
1611
|
+
}
|
|
1202
1612
|
}
|
|
1203
1613
|
}
|
|
1204
|
-
if (
|
|
1614
|
+
if (attemptCount < attemptsLimit) {
|
|
1615
|
+
coordinateTapsAwaitingFreshGesture = false;
|
|
1205
1616
|
lastAdjustmentMode = 'gesture';
|
|
1206
1617
|
recordTraceStep('execute', 'retry', {
|
|
1207
1618
|
attempt: attemptCount + 1,
|
|
@@ -1215,7 +1626,7 @@ export class ToolsInteract {
|
|
|
1215
1626
|
y1: currentPoint.y,
|
|
1216
1627
|
x2: targetPoint.x,
|
|
1217
1628
|
y2: targetPoint.y,
|
|
1218
|
-
duration: 220,
|
|
1629
|
+
duration: inferredSliderState ? 520 : 220,
|
|
1219
1630
|
deviceId: resolvedDeviceId
|
|
1220
1631
|
});
|
|
1221
1632
|
attemptCount++;
|
|
@@ -1236,7 +1647,7 @@ export class ToolsInteract {
|
|
|
1236
1647
|
recordTraceStep('verify', verificationResult.withinTolerance ? 'success' : 'retry', {
|
|
1237
1648
|
attempt: attemptCount,
|
|
1238
1649
|
property,
|
|
1239
|
-
target_value:
|
|
1650
|
+
target_value: normalizedTargetValue,
|
|
1240
1651
|
actual_state: observedState,
|
|
1241
1652
|
reason: verificationResult.verification?.reason ?? 'gesture adjustment did not converge yet'
|
|
1242
1653
|
});
|
|
@@ -1252,8 +1663,8 @@ export class ToolsInteract {
|
|
|
1252
1663
|
uiFingerprintBefore: fingerprintBefore,
|
|
1253
1664
|
uiFingerprintAfter,
|
|
1254
1665
|
details: {
|
|
1255
|
-
target_value:
|
|
1256
|
-
tolerance:
|
|
1666
|
+
target_value: normalizedTargetValue,
|
|
1667
|
+
tolerance: effectiveTolerance,
|
|
1257
1668
|
property,
|
|
1258
1669
|
attempts: attemptCount,
|
|
1259
1670
|
adjustment_mode: lastAdjustmentMode,
|
|
@@ -1268,8 +1679,8 @@ export class ToolsInteract {
|
|
|
1268
1679
|
...base,
|
|
1269
1680
|
target_state: {
|
|
1270
1681
|
property,
|
|
1271
|
-
target_value:
|
|
1272
|
-
tolerance:
|
|
1682
|
+
target_value: normalizedTargetValue,
|
|
1683
|
+
tolerance: effectiveTolerance
|
|
1273
1684
|
},
|
|
1274
1685
|
actual_state: observedState,
|
|
1275
1686
|
within_tolerance: true,
|
|
@@ -1285,7 +1696,7 @@ export class ToolsInteract {
|
|
|
1285
1696
|
recordTraceStep('verify', 'success', {
|
|
1286
1697
|
attempt: attemptCount,
|
|
1287
1698
|
property,
|
|
1288
|
-
target_value:
|
|
1699
|
+
target_value: normalizedTargetValue,
|
|
1289
1700
|
actual_state: observedState,
|
|
1290
1701
|
reason: verification?.reason ?? 'control converged to target value'
|
|
1291
1702
|
});
|
|
@@ -1300,8 +1711,8 @@ export class ToolsInteract {
|
|
|
1300
1711
|
uiFingerprintBefore: fingerprintBefore,
|
|
1301
1712
|
uiFingerprintAfter,
|
|
1302
1713
|
details: {
|
|
1303
|
-
target_value:
|
|
1304
|
-
tolerance:
|
|
1714
|
+
target_value: normalizedTargetValue,
|
|
1715
|
+
tolerance: effectiveTolerance,
|
|
1305
1716
|
property,
|
|
1306
1717
|
attempts: attemptCount,
|
|
1307
1718
|
adjustment_mode: lastAdjustmentMode,
|
|
@@ -1316,8 +1727,8 @@ export class ToolsInteract {
|
|
|
1316
1727
|
...base,
|
|
1317
1728
|
target_state: {
|
|
1318
1729
|
property,
|
|
1319
|
-
target_value:
|
|
1320
|
-
tolerance:
|
|
1730
|
+
target_value: normalizedTargetValue,
|
|
1731
|
+
tolerance: effectiveTolerance
|
|
1321
1732
|
},
|
|
1322
1733
|
actual_state: observedState,
|
|
1323
1734
|
within_tolerance: true,
|
|
@@ -1326,22 +1737,24 @@ export class ToolsInteract {
|
|
|
1326
1737
|
adjustment_mode: lastAdjustmentMode
|
|
1327
1738
|
};
|
|
1328
1739
|
}
|
|
1329
|
-
cachedResolvedMatch =
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
...
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1740
|
+
cachedResolvedMatch = inferredSliderState
|
|
1741
|
+
? null
|
|
1742
|
+
: {
|
|
1743
|
+
el: {
|
|
1744
|
+
...currentEl,
|
|
1745
|
+
state: {
|
|
1746
|
+
...(currentEl.state ?? null),
|
|
1747
|
+
...(observedState ? {
|
|
1748
|
+
[observedState.property]: observedState.value,
|
|
1749
|
+
raw_value: observedState.raw_value ?? observedState.value
|
|
1750
|
+
} : {})
|
|
1751
|
+
}
|
|
1752
|
+
},
|
|
1753
|
+
idx: resolved.match.idx
|
|
1754
|
+
};
|
|
1342
1755
|
}
|
|
1343
1756
|
const uiFingerprintAfter = await ToolsInteract._captureFingerprint(resolvedPlatform, resolvedDeviceId);
|
|
1344
|
-
return buildFailure('
|
|
1757
|
+
return buildFailure('CONTROL_CONVERGENCE_FAILED', 'control did not converge within the allotted attempts', resolvedTarget, currentDevice, lastObservedState, attemptCount, lastAdjustmentMode, true, uiFingerprintAfter);
|
|
1345
1758
|
}
|
|
1346
1759
|
static async swipeHandler({ platform = 'android', x1, y1, x2, y2, duration, deviceId }) {
|
|
1347
1760
|
const { interact, resolved } = await ToolsInteract.getInteractionService(platform, deviceId);
|