mobile-debug-mcp 0.30.1 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -5
- package/dist/interact/index.js +546 -133
- package/dist/manage/ios.js +1 -1
- package/dist/server/tool-definitions.js +13 -9
- package/dist/server/tool-handlers.js +4 -4
- package/dist/server-core.js +1 -1
- package/dist/system/index.js +485 -48
- package/dist/system/ios.js +15 -7
- package/dist/utils/android/utils.js +4 -4
- package/dist/utils/cli/idb/check-idb.js +10 -3
- package/dist/utils/cli/idb/idb-helper.js +47 -13
- package/dist/utils/ios/utils.js +5 -9
- package/docs/CHANGELOG.md +6 -0
- package/docs/ROADMAP.md +88 -212
- package/docs/specs/baseline-spec-v0.md +1 -1
- package/docs/specs-deltas/015-adjustable-control-support.md +541 -0
- package/docs/specs-deltas/016-environment-tools.md +533 -0
- package/docs/tools/TOOLS.md +2 -0
- package/docs/tools/manage.md +4 -2
- package/docs/tools/system.md +9 -2
- 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/manage/ios.ts +1 -1
- 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/system/index.ts +572 -50
- package/src/system/ios.ts +14 -7
- package/src/types.ts +2 -0
- package/src/utils/android/utils.ts +3 -3
- package/src/utils/cli/idb/check-idb.ts +11 -2
- package/src/utils/cli/idb/idb-helper.ts +49 -12
- package/src/utils/ios/utils.ts +4 -8
- package/test/unit/interact/adjust_control.test.ts +586 -33
- package/test/unit/interact/tap_element.test.ts +1 -1
- package/test/unit/server/contract.test.ts +4 -0
- package/test/unit/system/get_system_status.test.ts +3 -0
- package/test/unit/system/system_status.test.ts +40 -0
- package/test/unit/utils/idb_health.test.ts +74 -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/src/interact/index.ts
CHANGED
|
@@ -53,6 +53,10 @@ interface UiElement {
|
|
|
53
53
|
test_tag?: string | null
|
|
54
54
|
selector?: { value: string | null, confidence: { score: number, reason: string } | null } | null
|
|
55
55
|
semantic?: UIElementSemanticMetadata | null
|
|
56
|
+
actions?: string[] | null
|
|
57
|
+
accessibilityActions?: string[] | null
|
|
58
|
+
runtimeActions?: string[] | null
|
|
59
|
+
platformActions?: string[] | null
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
interface ResolvedUiElementContext {
|
|
@@ -101,6 +105,15 @@ interface RankedResolutionCandidate {
|
|
|
101
105
|
interactable: boolean
|
|
102
106
|
}
|
|
103
107
|
|
|
108
|
+
interface InferredLabeledSliderState {
|
|
109
|
+
current: number
|
|
110
|
+
min: number
|
|
111
|
+
max: number
|
|
112
|
+
value_range: { min: number; max: number }
|
|
113
|
+
touch_bounds: [number, number, number, number]
|
|
114
|
+
source: 'nearby_labels'
|
|
115
|
+
}
|
|
116
|
+
|
|
104
117
|
function buildObservationTrace({
|
|
105
118
|
actionType,
|
|
106
119
|
stage,
|
|
@@ -719,13 +732,217 @@ export class ToolsInteract {
|
|
|
719
732
|
if (!el?.state) return null
|
|
720
733
|
const stateValue = el.state[property as keyof UIElementState]
|
|
721
734
|
if (typeof stateValue === 'number' && Number.isFinite(stateValue)) return stateValue
|
|
735
|
+
const parsedStateValue = ToolsInteract._parseNumericControlValue(stateValue)
|
|
736
|
+
if (parsedStateValue !== null) return parsedStateValue
|
|
722
737
|
if (property === 'value' || property === 'raw_value') {
|
|
723
738
|
const fallback = el.state.raw_value ?? el.state.value
|
|
724
739
|
if (typeof fallback === 'number' && Number.isFinite(fallback)) return fallback
|
|
740
|
+
return ToolsInteract._parseNumericControlValue(fallback)
|
|
741
|
+
}
|
|
742
|
+
return null
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
private static _parseNumericControlValue(value: unknown): number | null {
|
|
746
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value
|
|
747
|
+
if (typeof value !== 'string') return null
|
|
748
|
+
const trimmed = value.trim()
|
|
749
|
+
if (!trimmed) return null
|
|
750
|
+
|
|
751
|
+
const percent = trimmed.match(/^([-+]?\d+(?:\.\d+)?)\s*%$/)
|
|
752
|
+
if (percent) return Number(percent[1])
|
|
753
|
+
|
|
754
|
+
const fraction = trimmed.match(/^([-+]?\d+(?:\.\d+)?)\s+of\s+[-+]?\d+(?:\.\d+)?$/i)
|
|
755
|
+
if (fraction) return Number(fraction[1])
|
|
756
|
+
|
|
757
|
+
const plain = Number(trimmed)
|
|
758
|
+
if (Number.isFinite(plain)) return plain
|
|
759
|
+
|
|
760
|
+
const trailing = trimmed.match(/,\s*([-+]?\d+(?:\.\d+)?)$/)
|
|
761
|
+
if (trailing) return Number(trailing[1])
|
|
762
|
+
|
|
763
|
+
return null
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
private static _readNumericControlRange(el: UiElement | null): { min: number; max: number } | null {
|
|
767
|
+
const min = typeof el?.state?.value_range?.min === 'number' ? el.state.value_range.min : null
|
|
768
|
+
const max = typeof el?.state?.value_range?.max === 'number' ? el.state.value_range.max : null
|
|
769
|
+
if (min !== null && max !== null && Number.isFinite(min) && Number.isFinite(max) && max > min) {
|
|
770
|
+
return { min, max }
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const value = el?.state?.value ?? el?.state?.raw_value
|
|
774
|
+
if (typeof value === 'string') {
|
|
775
|
+
const fraction = value.trim().match(/^[-+]?\d+(?:\.\d+)?\s+of\s+([-+]?\d+(?:\.\d+)?)$/i)
|
|
776
|
+
if (fraction) {
|
|
777
|
+
const parsedMax = Number(fraction[1])
|
|
778
|
+
if (Number.isFinite(parsedMax) && parsedMax > 0) return { min: 0, max: parsedMax }
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return null
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
private static _readControlStep(el: UiElement | null): number | null {
|
|
786
|
+
const candidates = [
|
|
787
|
+
el?.state?.step,
|
|
788
|
+
el?.state?.value_range?.step
|
|
789
|
+
]
|
|
790
|
+
for (const candidate of candidates) {
|
|
791
|
+
if (typeof candidate === 'number' && Number.isFinite(candidate) && candidate > 0) return candidate
|
|
725
792
|
}
|
|
726
793
|
return null
|
|
727
794
|
}
|
|
728
795
|
|
|
796
|
+
private static _quantizeTargetValue(targetValue: number, min: number, max: number, step: number | null): number {
|
|
797
|
+
if (!step) return targetValue
|
|
798
|
+
const quantized = min + (Math.round((targetValue - min) / step) * step)
|
|
799
|
+
const bounded = Math.max(min, Math.min(max, quantized))
|
|
800
|
+
const precision = ToolsInteract._decimalPrecisionForStep(step)
|
|
801
|
+
return Number(bounded.toFixed(precision))
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
private static _decimalPrecisionForStep(step: number): number {
|
|
805
|
+
const normalized = String(step).toLowerCase()
|
|
806
|
+
const [coefficient, exponentPart] = normalized.split('e')
|
|
807
|
+
const coefficientDecimals = coefficient.includes('.')
|
|
808
|
+
? coefficient.split('.')[1]?.length ?? 0
|
|
809
|
+
: 0
|
|
810
|
+
const exponent = exponentPart !== undefined ? Number(exponentPart) : 0
|
|
811
|
+
const precision = exponent < 0
|
|
812
|
+
? coefficientDecimals + Math.abs(exponent)
|
|
813
|
+
: Math.max(0, coefficientDecimals - exponent)
|
|
814
|
+
return Math.max(0, Math.min(12, precision))
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
private static _defaultAdjustTolerance(range: { min: number; max: number } | null, step: number | null): number {
|
|
818
|
+
if (step) return 0
|
|
819
|
+
if (!range) return 0
|
|
820
|
+
return Math.max(0, 0.01 * Math.abs(range.max - range.min))
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
private static _runtimeActionSet(el: UiElement | null): Set<string> {
|
|
824
|
+
const values = [
|
|
825
|
+
...(Array.isArray(el?.actions) ? el!.actions! : []),
|
|
826
|
+
...(Array.isArray(el?.accessibilityActions) ? el!.accessibilityActions! : []),
|
|
827
|
+
...(Array.isArray(el?.runtimeActions) ? el!.runtimeActions! : []),
|
|
828
|
+
...(Array.isArray(el?.platformActions) ? el!.platformActions! : [])
|
|
829
|
+
]
|
|
830
|
+
return new Set(values.map((value) => ToolsInteract._normalize(value)).filter(Boolean))
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
private static _hasDirectAdjustmentAction(el: UiElement | null): boolean {
|
|
834
|
+
const actions = ToolsInteract._runtimeActionSet(el)
|
|
835
|
+
return [
|
|
836
|
+
'action_set_progress',
|
|
837
|
+
'set_progress',
|
|
838
|
+
'setprogress',
|
|
839
|
+
'semanticsactions.setprogress',
|
|
840
|
+
'set_value',
|
|
841
|
+
'setvalue',
|
|
842
|
+
'direct_set_value'
|
|
843
|
+
].some((action) => actions.has(action))
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
private static _adjustmentDirections(el: UiElement | null): { increment: boolean; decrement: boolean } {
|
|
847
|
+
const actions = ToolsInteract._runtimeActionSet(el)
|
|
848
|
+
return {
|
|
849
|
+
increment: ['increment', 'accessibility_increment', 'action_increment'].some((action) => actions.has(action)),
|
|
850
|
+
decrement: ['decrement', 'accessibility_decrement', 'action_decrement'].some((action) => actions.has(action))
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
private static _textFromElement(el: UiElement | null): string {
|
|
855
|
+
return String(el?.text ?? el?.contentDescription ?? el?.contentDesc ?? el?.accessibilityLabel ?? el?.value ?? '').trim()
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
private static _parseLeadingNumber(text: string): number | null {
|
|
859
|
+
const match = text.trim().match(/^([-+]?\d+(?:\.\d+)?)(?:\s*[a-zA-Z%]+)?$/)
|
|
860
|
+
if (!match) return null
|
|
861
|
+
const value = Number(match[1])
|
|
862
|
+
return Number.isFinite(value) ? value : null
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
private static _inferLabeledSliderState(elements: UiElement[], track: UiElement | null): InferredLabeledSliderState | null {
|
|
866
|
+
const trackBounds = ToolsInteract._normalizeBounds(track?.bounds)
|
|
867
|
+
if (!track || !trackBounds) return null
|
|
868
|
+
const type = ToolsInteract._normalize(track.type ?? track.class ?? '')
|
|
869
|
+
const role = ToolsInteract._normalize(track.role ?? '')
|
|
870
|
+
const width = trackBounds[2] - trackBounds[0]
|
|
871
|
+
const height = trackBounds[3] - trackBounds[1]
|
|
872
|
+
const wideTrackLike = width >= 240 && height <= 220
|
|
873
|
+
const knownTrackLike = /slider|seekbar|range/.test(type) || /slider|range/.test(role)
|
|
874
|
+
if (!wideTrackLike && !knownTrackLike) return null
|
|
875
|
+
|
|
876
|
+
const trackTop = trackBounds[1]
|
|
877
|
+
const trackBottom = trackBounds[3]
|
|
878
|
+
const trackCenterX = (trackBounds[0] + trackBounds[2]) / 2
|
|
879
|
+
const labels = elements
|
|
880
|
+
.map((el, index) => {
|
|
881
|
+
const bounds = ToolsInteract._normalizeBounds(el.bounds)
|
|
882
|
+
const text = ToolsInteract._textFromElement(el)
|
|
883
|
+
const value = ToolsInteract._parseLeadingNumber(text)
|
|
884
|
+
return bounds && text && value !== null
|
|
885
|
+
? {
|
|
886
|
+
index,
|
|
887
|
+
el,
|
|
888
|
+
text,
|
|
889
|
+
value,
|
|
890
|
+
bounds,
|
|
891
|
+
centerX: (bounds[0] + bounds[2]) / 2,
|
|
892
|
+
centerY: (bounds[1] + bounds[3]) / 2,
|
|
893
|
+
hasUnit: /[a-zA-Z%]/.test(text)
|
|
894
|
+
}
|
|
895
|
+
: null
|
|
896
|
+
})
|
|
897
|
+
.filter((label): label is NonNullable<typeof label> => !!label)
|
|
898
|
+
|
|
899
|
+
const rangeLabels = labels
|
|
900
|
+
.filter((label) => {
|
|
901
|
+
const verticallyNearTrack = label.centerY >= trackTop - 520 && label.centerY <= trackBottom + 260
|
|
902
|
+
const horizontallyNearTrack = label.centerX >= trackBounds[0] - 120 && label.centerX <= trackBounds[2] + 120
|
|
903
|
+
return verticallyNearTrack && horizontallyNearTrack && label.hasUnit
|
|
904
|
+
})
|
|
905
|
+
.sort((a, b) => a.centerX - b.centerX)
|
|
906
|
+
|
|
907
|
+
if (rangeLabels.length < 2) return null
|
|
908
|
+
const minLabel = rangeLabels[0]
|
|
909
|
+
const maxLabel = rangeLabels[rangeLabels.length - 1]
|
|
910
|
+
if (!minLabel || !maxLabel || maxLabel.value <= minLabel.value) return null
|
|
911
|
+
|
|
912
|
+
const currentCandidates = labels
|
|
913
|
+
.filter((label) => {
|
|
914
|
+
if (label.index === minLabel.index || label.index === maxLabel.index) return false
|
|
915
|
+
if (label.value < minLabel.value || label.value > maxLabel.value) return false
|
|
916
|
+
if (label.centerY < trackTop - 950 || label.centerY > minLabel.centerY - 20) return false
|
|
917
|
+
return label.centerX >= trackCenterX - 80
|
|
918
|
+
})
|
|
919
|
+
.sort((a, b) => {
|
|
920
|
+
const yDistance = Math.abs((trackTop - 420) - a.centerY) - Math.abs((trackTop - 420) - b.centerY)
|
|
921
|
+
if (yDistance !== 0) return yDistance
|
|
922
|
+
return b.centerX - a.centerX
|
|
923
|
+
})
|
|
924
|
+
|
|
925
|
+
const currentLabel = currentCandidates[0]
|
|
926
|
+
if (!currentLabel) return null
|
|
927
|
+
|
|
928
|
+
const candidateBelowRangeLabels = minLabel.centerY < trackTop && maxLabel.centerY < trackTop
|
|
929
|
+
const labelDerivedBounds: [number, number, number, number] = [
|
|
930
|
+
Math.min(minLabel.bounds[0], trackBounds[0]),
|
|
931
|
+
Math.max(currentLabel.bounds[3] + 12, Math.min(minLabel.bounds[1], maxLabel.bounds[1]) - 160),
|
|
932
|
+
Math.max(maxLabel.bounds[2], trackBounds[2]),
|
|
933
|
+
Math.max(currentLabel.bounds[3] + 80, Math.min(minLabel.bounds[1], maxLabel.bounds[1]) - 32)
|
|
934
|
+
]
|
|
935
|
+
|
|
936
|
+
return {
|
|
937
|
+
current: currentLabel.value,
|
|
938
|
+
min: minLabel.value,
|
|
939
|
+
max: maxLabel.value,
|
|
940
|
+
value_range: { min: minLabel.value, max: maxLabel.value },
|
|
941
|
+
touch_bounds: candidateBelowRangeLabels ? labelDerivedBounds : trackBounds,
|
|
942
|
+
source: 'nearby_labels'
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
729
946
|
private static _buildControlPoint(bounds: [number, number, number, number], ratio: number, axis: 'horizontal' | 'vertical') {
|
|
730
947
|
const clampedRatio = Math.max(0, Math.min(1, ratio))
|
|
731
948
|
const [left, top, right, bottom] = bounds
|
|
@@ -1080,8 +1297,8 @@ export class ToolsInteract {
|
|
|
1080
1297
|
element_id,
|
|
1081
1298
|
property = 'value',
|
|
1082
1299
|
targetValue,
|
|
1083
|
-
tolerance
|
|
1084
|
-
maxAttempts
|
|
1300
|
+
tolerance,
|
|
1301
|
+
maxAttempts,
|
|
1085
1302
|
platform,
|
|
1086
1303
|
deviceId
|
|
1087
1304
|
}: {
|
|
@@ -1096,8 +1313,10 @@ export class ToolsInteract {
|
|
|
1096
1313
|
}): Promise<AdjustControlResponse> {
|
|
1097
1314
|
const actionType = 'adjust_control'
|
|
1098
1315
|
const targetSelector = selector ?? (element_id ? { elementId: element_id } : null)
|
|
1099
|
-
const
|
|
1100
|
-
const attemptsLimit =
|
|
1316
|
+
const requestedTolerance = typeof tolerance === 'number' && Number.isFinite(tolerance) ? Math.max(0, tolerance) : null
|
|
1317
|
+
const attemptsLimit = maxAttempts === undefined || maxAttempts === null
|
|
1318
|
+
? 5
|
|
1319
|
+
: Math.max(1, Math.floor(Number(maxAttempts) || 1))
|
|
1101
1320
|
const sourcePlatform: 'android' | 'ios' = platform || 'android'
|
|
1102
1321
|
let resolvedPlatform = sourcePlatform
|
|
1103
1322
|
let resolvedDeviceId = deviceId
|
|
@@ -1148,6 +1367,7 @@ export class ToolsInteract {
|
|
|
1148
1367
|
retry_depth: attempts
|
|
1149
1368
|
})
|
|
1150
1369
|
}
|
|
1370
|
+
const responseTolerance = requestedTolerance ?? 0
|
|
1151
1371
|
const base = buildActionExecutionResult({
|
|
1152
1372
|
actionType,
|
|
1153
1373
|
sourceModule: 'interact',
|
|
@@ -1160,7 +1380,7 @@ export class ToolsInteract {
|
|
|
1160
1380
|
failure: { failureCode, retryable },
|
|
1161
1381
|
details: {
|
|
1162
1382
|
target_value: targetValue,
|
|
1163
|
-
tolerance:
|
|
1383
|
+
tolerance: responseTolerance,
|
|
1164
1384
|
property,
|
|
1165
1385
|
attempts,
|
|
1166
1386
|
adjustment_mode: adjustmentMode,
|
|
@@ -1177,7 +1397,7 @@ export class ToolsInteract {
|
|
|
1177
1397
|
target_state: {
|
|
1178
1398
|
property,
|
|
1179
1399
|
target_value: targetValue,
|
|
1180
|
-
tolerance:
|
|
1400
|
+
tolerance: responseTolerance
|
|
1181
1401
|
},
|
|
1182
1402
|
actual_state: actualState,
|
|
1183
1403
|
within_tolerance: false,
|
|
@@ -1295,8 +1515,8 @@ export class ToolsInteract {
|
|
|
1295
1515
|
return null
|
|
1296
1516
|
}
|
|
1297
1517
|
|
|
1298
|
-
if (!selector && !element_id) {
|
|
1299
|
-
return buildFailure('ELEMENT_NOT_FOUND', 'selector or element_id is required', null, undefined, null, 0, 'gesture', false)
|
|
1518
|
+
if ((!selector && !element_id) || (selector && element_id)) {
|
|
1519
|
+
return buildFailure('ELEMENT_NOT_FOUND', 'exactly one of selector or element_id is required', null, undefined, null, 0, 'gesture', false)
|
|
1300
1520
|
}
|
|
1301
1521
|
|
|
1302
1522
|
if (selector && !element_id) {
|
|
@@ -1358,8 +1578,10 @@ export class ToolsInteract {
|
|
|
1358
1578
|
let currentDevice: any = undefined
|
|
1359
1579
|
let attemptCount = 0
|
|
1360
1580
|
let cachedResolvedMatch: { el: UiElement, idx: number } | null = null
|
|
1581
|
+
let coordinateTapsAwaitingFreshGesture = false
|
|
1361
1582
|
|
|
1362
|
-
|
|
1583
|
+
adjustmentAttempts:
|
|
1584
|
+
for (let attempt = 0; attempt < attemptsLimit && attemptCount < attemptsLimit; attempt++) {
|
|
1363
1585
|
const resolved: {
|
|
1364
1586
|
tree: any
|
|
1365
1587
|
device: any
|
|
@@ -1400,11 +1622,29 @@ export class ToolsInteract {
|
|
|
1400
1622
|
)
|
|
1401
1623
|
}
|
|
1402
1624
|
|
|
1403
|
-
const
|
|
1404
|
-
|
|
1405
|
-
|
|
1625
|
+
const elementsForInference = Array.isArray(resolved.tree?.elements)
|
|
1626
|
+
? resolved.tree.elements as UiElement[]
|
|
1627
|
+
: cachedResolvedMatch
|
|
1628
|
+
? [cachedResolvedMatch.el]
|
|
1629
|
+
: [currentEl]
|
|
1630
|
+
const inferredSliderState = ToolsInteract._inferLabeledSliderState(elementsForInference, currentEl)
|
|
1631
|
+
const nativeBounds = ToolsInteract._normalizeBounds(currentEl.bounds)
|
|
1632
|
+
const bounds = inferredSliderState?.touch_bounds ?? nativeBounds
|
|
1633
|
+
const valueRange = ToolsInteract._readNumericControlRange(currentEl) ?? inferredSliderState?.value_range ?? null
|
|
1634
|
+
const step = ToolsInteract._readControlStep(currentEl)
|
|
1635
|
+
const normalizedTargetValue = valueRange
|
|
1636
|
+
? ToolsInteract._quantizeTargetValue(targetValue, valueRange.min, valueRange.max, step)
|
|
1637
|
+
: targetValue
|
|
1638
|
+
const effectiveTolerance = requestedTolerance ?? ToolsInteract._defaultAdjustTolerance(valueRange, step)
|
|
1639
|
+
const currentValue = ToolsInteract._readNumericControlValue(currentEl, property) ?? inferredSliderState?.current ?? null
|
|
1406
1640
|
const actualState = currentValue !== null
|
|
1407
|
-
? {
|
|
1641
|
+
? {
|
|
1642
|
+
property,
|
|
1643
|
+
value: currentValue,
|
|
1644
|
+
raw_value: typeof currentEl.state?.raw_value === 'number'
|
|
1645
|
+
? currentEl.state.raw_value
|
|
1646
|
+
: inferredSliderState?.current
|
|
1647
|
+
}
|
|
1408
1648
|
: null
|
|
1409
1649
|
|
|
1410
1650
|
lastObservedState = actualState
|
|
@@ -1421,10 +1661,23 @@ export class ToolsInteract {
|
|
|
1421
1661
|
return buildFailure('ELEMENT_NOT_INTERACTABLE', 'adjust_control currently supports numeric value and raw_value properties only', resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false)
|
|
1422
1662
|
}
|
|
1423
1663
|
|
|
1424
|
-
|
|
1664
|
+
const isAdjustableControl = ToolsInteract._isAdjustableControl(currentEl) || !!inferredSliderState
|
|
1665
|
+
if (!isAdjustableControl) {
|
|
1666
|
+
return buildFailure('ELEMENT_NOT_INTERACTABLE', 'target is not an adjustable control', resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false)
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
if (currentValue === null) {
|
|
1670
|
+
return buildFailure('UNKNOWN', 'current numeric value is unavailable', resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false)
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
if (valueRange && (targetValue < valueRange.min || targetValue > valueRange.max)) {
|
|
1674
|
+
return buildFailure('CONTROL_CONVERGENCE_FAILED', `targetValue ${targetValue} is outside the control range ${valueRange.min}..${valueRange.max}`, resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false)
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
if (currentValue !== null && Math.abs(currentValue - normalizedTargetValue) <= effectiveTolerance) {
|
|
1425
1678
|
recordTraceStep('verify', 'success', {
|
|
1426
1679
|
property,
|
|
1427
|
-
target_value:
|
|
1680
|
+
target_value: normalizedTargetValue,
|
|
1428
1681
|
actual_state: actualState,
|
|
1429
1682
|
reason: 'control already within tolerance'
|
|
1430
1683
|
})
|
|
@@ -1439,8 +1692,8 @@ export class ToolsInteract {
|
|
|
1439
1692
|
uiFingerprintBefore: fingerprintBefore,
|
|
1440
1693
|
uiFingerprintAfter,
|
|
1441
1694
|
details: {
|
|
1442
|
-
target_value:
|
|
1443
|
-
tolerance:
|
|
1695
|
+
target_value: normalizedTargetValue,
|
|
1696
|
+
tolerance: effectiveTolerance,
|
|
1444
1697
|
property,
|
|
1445
1698
|
attempts: attemptCount,
|
|
1446
1699
|
adjustment_mode: 'semantic',
|
|
@@ -1456,8 +1709,8 @@ export class ToolsInteract {
|
|
|
1456
1709
|
...base,
|
|
1457
1710
|
target_state: {
|
|
1458
1711
|
property,
|
|
1459
|
-
target_value:
|
|
1460
|
-
tolerance:
|
|
1712
|
+
target_value: normalizedTargetValue,
|
|
1713
|
+
tolerance: effectiveTolerance
|
|
1461
1714
|
},
|
|
1462
1715
|
actual_state: actualState,
|
|
1463
1716
|
within_tolerance: true,
|
|
@@ -1467,50 +1720,48 @@ export class ToolsInteract {
|
|
|
1467
1720
|
}
|
|
1468
1721
|
}
|
|
1469
1722
|
|
|
1470
|
-
if (!ToolsInteract._isAdjustableControl(currentEl)) {
|
|
1471
|
-
return buildFailure('ELEMENT_NOT_INTERACTABLE', 'target is not an adjustable control', resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false)
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
1723
|
if (!bounds) {
|
|
1475
1724
|
return buildFailure('ELEMENT_NOT_INTERACTABLE', 'adjustable control has no bounds', resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false)
|
|
1476
1725
|
}
|
|
1477
1726
|
|
|
1478
|
-
const min = typeof valueRange?.min === 'number' ? valueRange.min : null
|
|
1479
|
-
const max = typeof valueRange?.max === 'number' ? valueRange.max : null
|
|
1480
|
-
if (min === null || max === null || !Number.isFinite(min) || !Number.isFinite(max) || max <= min) {
|
|
1481
|
-
return buildFailure('ELEMENT_NOT_INTERACTABLE', 'value_range unavailable', resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false)
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
if (targetValue < min || targetValue > max) {
|
|
1485
|
-
return buildFailure('UNKNOWN', `targetValue ${targetValue} is outside the control range ${min}..${max}`, resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false)
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
const axis = ToolsInteract._controlAxis(currentEl, bounds)
|
|
1489
|
-
const targetPoint = ToolsInteract._buildConservativeControlPoint(bounds, targetValue, currentValue, min, max, axis)
|
|
1490
|
-
const currentPoint = currentValue !== null
|
|
1491
|
-
? ToolsInteract._buildControlPoint(bounds, (currentValue - min) / (max - min), axis)
|
|
1492
|
-
: ToolsInteract._buildControlPoint(bounds, 0.5, axis)
|
|
1493
|
-
const probePoints = ToolsInteract._buildAdjustmentProbePoints(bounds, targetValue, currentValue, min, max, axis)
|
|
1494
|
-
|
|
1495
1727
|
const runVerification = async (): Promise<{
|
|
1496
1728
|
verification: any
|
|
1497
1729
|
observedState: { property: string; value: number | null; raw_value?: number | null } | null
|
|
1498
1730
|
withinTolerance: boolean
|
|
1499
1731
|
}> => {
|
|
1732
|
+
if (inferredSliderState) {
|
|
1733
|
+
const tree = await ToolsObserve.getUITreeHandler({ platform: resolvedPlatform, deviceId: resolvedDeviceId }) as any
|
|
1734
|
+
const elements = Array.isArray(tree?.elements) ? tree.elements as UiElement[] : []
|
|
1735
|
+
const refreshed = ToolsInteract._inferLabeledSliderState(elements, currentEl)
|
|
1736
|
+
const observedValue = refreshed?.current ?? null
|
|
1737
|
+
const observedState = observedValue !== null
|
|
1738
|
+
? { property, value: observedValue, raw_value: observedValue }
|
|
1739
|
+
: actualState
|
|
1740
|
+
|
|
1741
|
+
return {
|
|
1742
|
+
verification: {
|
|
1743
|
+
success: observedValue !== null && Math.abs(observedValue - normalizedTargetValue) <= effectiveTolerance,
|
|
1744
|
+
observed_state: observedState,
|
|
1745
|
+
reason: observedValue !== null
|
|
1746
|
+
? 'visible labeled value read from nearby slider labels'
|
|
1747
|
+
: 'visible labeled value unavailable'
|
|
1748
|
+
},
|
|
1749
|
+
observedState,
|
|
1750
|
+
withinTolerance: observedValue !== null && Math.abs(observedValue - normalizedTargetValue) <= effectiveTolerance
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1500
1754
|
const verification = await ToolsInteract.expectStateHandler({
|
|
1501
1755
|
element_id: resolvedTarget?.elementId ?? element_id,
|
|
1502
1756
|
selector: selector ?? undefined,
|
|
1503
1757
|
property,
|
|
1504
|
-
expected:
|
|
1758
|
+
expected: normalizedTargetValue,
|
|
1505
1759
|
platform: resolvedPlatform,
|
|
1506
1760
|
deviceId: resolvedDeviceId
|
|
1507
1761
|
}) as any
|
|
1508
1762
|
|
|
1509
|
-
const observedValue =
|
|
1510
|
-
|
|
1511
|
-
: typeof verification?.observed_state?.raw_value === 'number'
|
|
1512
|
-
? verification.observed_state.raw_value
|
|
1513
|
-
: null
|
|
1763
|
+
const observedValue = ToolsInteract._parseNumericControlValue(verification?.observed_state?.value) ??
|
|
1764
|
+
ToolsInteract._parseNumericControlValue(verification?.observed_state?.raw_value)
|
|
1514
1765
|
const observedState = observedValue !== null
|
|
1515
1766
|
? {
|
|
1516
1767
|
property,
|
|
@@ -1522,7 +1773,7 @@ export class ToolsInteract {
|
|
|
1522
1773
|
return {
|
|
1523
1774
|
verification,
|
|
1524
1775
|
observedState,
|
|
1525
|
-
withinTolerance: observedValue !== null && Math.abs(observedValue -
|
|
1776
|
+
withinTolerance: observedValue !== null && Math.abs(observedValue - normalizedTargetValue) <= effectiveTolerance
|
|
1526
1777
|
}
|
|
1527
1778
|
}
|
|
1528
1779
|
|
|
@@ -1531,86 +1782,276 @@ export class ToolsInteract {
|
|
|
1531
1782
|
let verification: any = null
|
|
1532
1783
|
let verificationResult: any = { verification: null, observedState: actualState, withinTolerance: false }
|
|
1533
1784
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1785
|
+
const directActionSupported = ToolsInteract._hasDirectAdjustmentAction(currentEl)
|
|
1786
|
+
if (directActionSupported && attemptCount < attemptsLimit) {
|
|
1787
|
+
const { interact, resolved: actionResolved } = await ToolsInteract.getInteractionService(resolvedPlatform, resolvedDeviceId)
|
|
1788
|
+
if (typeof interact.setAdjustableValue === 'function') {
|
|
1789
|
+
lastAdjustmentMode = 'semantic'
|
|
1790
|
+
recordTraceStep('execute', 'retry', {
|
|
1791
|
+
attempt: attemptCount + 1,
|
|
1792
|
+
mode: 'semantic',
|
|
1793
|
+
strategy: 'direct_set_value',
|
|
1794
|
+
target_value: normalizedTargetValue
|
|
1795
|
+
})
|
|
1796
|
+
const actionResult = await interact.setAdjustableValue({
|
|
1797
|
+
deviceId: actionResolved.id,
|
|
1798
|
+
target: resolvedTarget,
|
|
1799
|
+
property,
|
|
1800
|
+
value: normalizedTargetValue
|
|
1801
|
+
})
|
|
1802
|
+
attemptCount++
|
|
1803
|
+
actionDevice = actionResult?.device ?? actionDevice
|
|
1804
|
+
|
|
1805
|
+
if (actionResult?.success !== false) {
|
|
1806
|
+
verificationResult = await runVerification()
|
|
1807
|
+
observedState = verificationResult.observedState
|
|
1808
|
+
lastObservedState = observedState
|
|
1809
|
+
recordTraceStep('verify', verificationResult.withinTolerance ? 'success' : 'retry', {
|
|
1810
|
+
attempt: attemptCount,
|
|
1811
|
+
property,
|
|
1812
|
+
target_value: normalizedTargetValue,
|
|
1813
|
+
actual_state: observedState,
|
|
1814
|
+
reason: verificationResult.verification?.reason ?? 'direct adjustment did not converge yet'
|
|
1815
|
+
})
|
|
1816
|
+
|
|
1817
|
+
if (verificationResult.withinTolerance) {
|
|
1818
|
+
const uiFingerprintAfter = await ToolsInteract._captureFingerprint(resolvedPlatform, resolvedDeviceId)
|
|
1819
|
+
const base = buildActionExecutionResult({
|
|
1820
|
+
actionType,
|
|
1821
|
+
sourceModule: 'interact',
|
|
1822
|
+
device: actionDevice ?? currentDevice,
|
|
1823
|
+
selector: targetSelector,
|
|
1824
|
+
resolved: resolvedTarget,
|
|
1825
|
+
success: true,
|
|
1826
|
+
uiFingerprintBefore: fingerprintBefore,
|
|
1827
|
+
uiFingerprintAfter,
|
|
1828
|
+
details: {
|
|
1829
|
+
target_value: normalizedTargetValue,
|
|
1830
|
+
tolerance: effectiveTolerance,
|
|
1831
|
+
property,
|
|
1832
|
+
attempts: attemptCount,
|
|
1833
|
+
adjustment_mode: lastAdjustmentMode,
|
|
1834
|
+
actual_state: observedState,
|
|
1835
|
+
converged: true,
|
|
1836
|
+
within_tolerance: true,
|
|
1837
|
+
reason: verificationResult.verification?.reason ?? 'control converged to target value'
|
|
1838
|
+
},
|
|
1839
|
+
traceSteps
|
|
1840
|
+
}) as AdjustControlResponse
|
|
1841
|
+
|
|
1842
|
+
return {
|
|
1843
|
+
...base,
|
|
1844
|
+
target_state: {
|
|
1845
|
+
property,
|
|
1846
|
+
target_value: normalizedTargetValue,
|
|
1847
|
+
tolerance: effectiveTolerance
|
|
1848
|
+
},
|
|
1849
|
+
actual_state: observedState,
|
|
1850
|
+
within_tolerance: true,
|
|
1851
|
+
converged: true,
|
|
1852
|
+
attempts: attemptCount,
|
|
1853
|
+
adjustment_mode: lastAdjustmentMode
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
cachedResolvedMatch = null
|
|
1858
|
+
if (attemptCount < attemptsLimit) {
|
|
1859
|
+
continue
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1550
1864
|
|
|
1551
|
-
|
|
1865
|
+
const directions = ToolsInteract._adjustmentDirections(currentEl)
|
|
1866
|
+
if ((directions.increment || directions.decrement) && attemptCount < attemptsLimit) {
|
|
1867
|
+
const { interact, resolved: actionResolved } = await ToolsInteract.getInteractionService(resolvedPlatform, resolvedDeviceId)
|
|
1868
|
+
const adjustByDirection = typeof interact.adjustAccessibleValue === 'function'
|
|
1869
|
+
? interact.adjustAccessibleValue.bind(interact)
|
|
1870
|
+
: null
|
|
1871
|
+
while (adjustByDirection && attemptCount < attemptsLimit) {
|
|
1872
|
+
const direction = normalizedTargetValue > (observedState?.value ?? currentValue) ? 'increment' : 'decrement'
|
|
1873
|
+
if ((direction === 'increment' && !directions.increment) || (direction === 'decrement' && !directions.decrement)) break
|
|
1874
|
+
lastAdjustmentMode = 'semantic'
|
|
1552
1875
|
recordTraceStep('execute', 'retry', {
|
|
1876
|
+
attempt: attemptCount + 1,
|
|
1877
|
+
mode: 'semantic',
|
|
1878
|
+
strategy: 'accessibility_increment_decrement',
|
|
1879
|
+
direction
|
|
1880
|
+
})
|
|
1881
|
+
const actionResult = await adjustByDirection({
|
|
1882
|
+
deviceId: actionResolved.id,
|
|
1883
|
+
target: resolvedTarget,
|
|
1884
|
+
property,
|
|
1885
|
+
direction
|
|
1886
|
+
})
|
|
1887
|
+
attemptCount++
|
|
1888
|
+
actionDevice = actionResult?.device ?? actionDevice
|
|
1889
|
+
if (actionResult?.success === false) break
|
|
1890
|
+
|
|
1891
|
+
verificationResult = await runVerification()
|
|
1892
|
+
observedState = verificationResult.observedState
|
|
1893
|
+
lastObservedState = observedState
|
|
1894
|
+
recordTraceStep('verify', verificationResult.withinTolerance ? 'success' : 'retry', {
|
|
1553
1895
|
attempt: attemptCount,
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1896
|
+
property,
|
|
1897
|
+
target_value: normalizedTargetValue,
|
|
1898
|
+
actual_state: observedState,
|
|
1899
|
+
reason: verificationResult.verification?.reason ?? 'incremental adjustment did not converge yet'
|
|
1557
1900
|
})
|
|
1558
|
-
|
|
1901
|
+
|
|
1902
|
+
if (verificationResult.withinTolerance) {
|
|
1903
|
+
const uiFingerprintAfter = await ToolsInteract._captureFingerprint(resolvedPlatform, resolvedDeviceId)
|
|
1904
|
+
const base = buildActionExecutionResult({
|
|
1905
|
+
actionType,
|
|
1906
|
+
sourceModule: 'interact',
|
|
1907
|
+
device: actionDevice ?? currentDevice,
|
|
1908
|
+
selector: targetSelector,
|
|
1909
|
+
resolved: resolvedTarget,
|
|
1910
|
+
success: true,
|
|
1911
|
+
uiFingerprintBefore: fingerprintBefore,
|
|
1912
|
+
uiFingerprintAfter,
|
|
1913
|
+
details: {
|
|
1914
|
+
target_value: normalizedTargetValue,
|
|
1915
|
+
tolerance: effectiveTolerance,
|
|
1916
|
+
property,
|
|
1917
|
+
attempts: attemptCount,
|
|
1918
|
+
adjustment_mode: lastAdjustmentMode,
|
|
1919
|
+
actual_state: observedState,
|
|
1920
|
+
converged: true,
|
|
1921
|
+
within_tolerance: true,
|
|
1922
|
+
reason: verificationResult.verification?.reason ?? 'control converged to target value'
|
|
1923
|
+
},
|
|
1924
|
+
traceSteps
|
|
1925
|
+
}) as AdjustControlResponse
|
|
1926
|
+
|
|
1927
|
+
return {
|
|
1928
|
+
...base,
|
|
1929
|
+
target_state: {
|
|
1930
|
+
property,
|
|
1931
|
+
target_value: normalizedTargetValue,
|
|
1932
|
+
tolerance: effectiveTolerance
|
|
1933
|
+
},
|
|
1934
|
+
actual_state: observedState,
|
|
1935
|
+
within_tolerance: true,
|
|
1936
|
+
converged: true,
|
|
1937
|
+
attempts: attemptCount,
|
|
1938
|
+
adjustment_mode: lastAdjustmentMode
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
cachedResolvedMatch = null
|
|
1943
|
+
if (attemptCount < attemptsLimit) {
|
|
1944
|
+
continue adjustmentAttempts
|
|
1945
|
+
}
|
|
1559
1946
|
}
|
|
1947
|
+
}
|
|
1560
1948
|
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
recordTraceStep('verify', verificationResult.withinTolerance ? 'success' : 'retry', {
|
|
1565
|
-
attempt: attemptCount,
|
|
1566
|
-
property,
|
|
1567
|
-
target_value: targetValue,
|
|
1568
|
-
actual_state: observedState,
|
|
1569
|
-
reason: verificationResult.verification?.reason ?? 'control did not converge yet'
|
|
1570
|
-
})
|
|
1949
|
+
if (!bounds) {
|
|
1950
|
+
return buildFailure('ELEMENT_NOT_INTERACTABLE', 'adjustable control has no bounds', resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false)
|
|
1951
|
+
}
|
|
1571
1952
|
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1953
|
+
if (!valueRange) {
|
|
1954
|
+
return buildFailure('UNKNOWN', 'value_range unavailable', resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false)
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
const min = valueRange.min
|
|
1958
|
+
const max = valueRange.max
|
|
1959
|
+
const axis = ToolsInteract._controlAxis(currentEl, bounds)
|
|
1960
|
+
const targetPoint = ToolsInteract._buildConservativeControlPoint(bounds, normalizedTargetValue, currentValue, min, max, axis)
|
|
1961
|
+
const currentPoint = ToolsInteract._buildControlPoint(bounds, (currentValue - min) / (max - min), axis)
|
|
1962
|
+
if (!coordinateTapsAwaitingFreshGesture) {
|
|
1963
|
+
const probePoints = inferredSliderState
|
|
1964
|
+
? [targetPoint]
|
|
1965
|
+
: ToolsInteract._buildAdjustmentProbePoints(bounds, normalizedTargetValue, currentValue, min, max, axis)
|
|
1966
|
+
|
|
1967
|
+
for (let i = 0; i < probePoints.length && attemptCount < attemptsLimit; i++) {
|
|
1968
|
+
const probePoint = probePoints[i]
|
|
1969
|
+
lastAdjustmentMode = 'coordinate'
|
|
1970
|
+
recordTraceStep('execute', 'retry', {
|
|
1971
|
+
attempt: attemptCount + 1,
|
|
1972
|
+
mode: 'coordinate',
|
|
1973
|
+
point: probePoint
|
|
1974
|
+
})
|
|
1975
|
+
const actionResult = await ToolsInteract.tapHandler({
|
|
1976
|
+
platform: resolvedPlatform,
|
|
1977
|
+
x: probePoint.x,
|
|
1978
|
+
y: probePoint.y,
|
|
1979
|
+
deviceId: resolvedDeviceId
|
|
1980
|
+
})
|
|
1981
|
+
attemptCount++
|
|
1982
|
+
actionDevice = actionResult.device ?? actionDevice
|
|
1983
|
+
|
|
1984
|
+
if (!actionResult.success) {
|
|
1985
|
+
recordTraceStep('execute', 'retry', {
|
|
1986
|
+
attempt: attemptCount,
|
|
1987
|
+
mode: 'coordinate',
|
|
1988
|
+
point: probePoint,
|
|
1989
|
+
success: false
|
|
1990
|
+
})
|
|
1991
|
+
continue
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
verificationResult = await runVerification()
|
|
1995
|
+
observedState = verificationResult.observedState
|
|
1996
|
+
lastObservedState = observedState
|
|
1997
|
+
recordTraceStep('verify', verificationResult.withinTolerance ? 'success' : 'retry', {
|
|
1998
|
+
attempt: attemptCount,
|
|
1999
|
+
property,
|
|
2000
|
+
target_value: normalizedTargetValue,
|
|
2001
|
+
actual_state: observedState,
|
|
2002
|
+
reason: verificationResult.verification?.reason ?? 'control did not converge yet'
|
|
2003
|
+
})
|
|
2004
|
+
|
|
2005
|
+
if (verificationResult.withinTolerance) {
|
|
2006
|
+
const uiFingerprintAfter = await ToolsInteract._captureFingerprint(resolvedPlatform, resolvedDeviceId)
|
|
2007
|
+
const base = buildActionExecutionResult({
|
|
2008
|
+
actionType,
|
|
2009
|
+
sourceModule: 'interact',
|
|
2010
|
+
device: actionDevice ?? currentDevice,
|
|
2011
|
+
selector: targetSelector,
|
|
2012
|
+
resolved: resolvedTarget,
|
|
2013
|
+
success: true,
|
|
2014
|
+
uiFingerprintBefore: fingerprintBefore,
|
|
2015
|
+
uiFingerprintAfter,
|
|
2016
|
+
details: {
|
|
2017
|
+
target_value: normalizedTargetValue,
|
|
2018
|
+
tolerance: effectiveTolerance,
|
|
2019
|
+
property,
|
|
2020
|
+
attempts: attemptCount,
|
|
2021
|
+
adjustment_mode: lastAdjustmentMode,
|
|
2022
|
+
actual_state: observedState,
|
|
2023
|
+
converged: true,
|
|
2024
|
+
within_tolerance: true,
|
|
2025
|
+
reason: verificationResult.verification?.reason ?? 'control converged to target value'
|
|
2026
|
+
},
|
|
2027
|
+
traceSteps
|
|
2028
|
+
}) as AdjustControlResponse
|
|
2029
|
+
|
|
2030
|
+
return {
|
|
2031
|
+
...base,
|
|
2032
|
+
target_state: {
|
|
2033
|
+
property,
|
|
2034
|
+
target_value: normalizedTargetValue,
|
|
2035
|
+
tolerance: effectiveTolerance
|
|
2036
|
+
},
|
|
1589
2037
|
actual_state: observedState,
|
|
1590
|
-
converged: true,
|
|
1591
2038
|
within_tolerance: true,
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
2039
|
+
converged: true,
|
|
2040
|
+
attempts: attemptCount,
|
|
2041
|
+
adjustment_mode: lastAdjustmentMode
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
1596
2044
|
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
target_value: targetValue,
|
|
1602
|
-
tolerance: normalizedTolerance
|
|
1603
|
-
},
|
|
1604
|
-
actual_state: observedState,
|
|
1605
|
-
within_tolerance: true,
|
|
1606
|
-
converged: true,
|
|
1607
|
-
attempts: attemptCount,
|
|
1608
|
-
adjustment_mode: lastAdjustmentMode
|
|
2045
|
+
coordinateTapsAwaitingFreshGesture = true
|
|
2046
|
+
cachedResolvedMatch = null
|
|
2047
|
+
if (attemptCount < attemptsLimit) {
|
|
2048
|
+
continue adjustmentAttempts
|
|
1609
2049
|
}
|
|
1610
2050
|
}
|
|
1611
2051
|
}
|
|
1612
2052
|
|
|
1613
|
-
if (
|
|
2053
|
+
if (attemptCount < attemptsLimit) {
|
|
2054
|
+
coordinateTapsAwaitingFreshGesture = false
|
|
1614
2055
|
lastAdjustmentMode = 'gesture'
|
|
1615
2056
|
recordTraceStep('execute', 'retry', {
|
|
1616
2057
|
attempt: attemptCount + 1,
|
|
@@ -1624,7 +2065,7 @@ export class ToolsInteract {
|
|
|
1624
2065
|
y1: currentPoint.y,
|
|
1625
2066
|
x2: targetPoint.x,
|
|
1626
2067
|
y2: targetPoint.y,
|
|
1627
|
-
duration: 220,
|
|
2068
|
+
duration: inferredSliderState ? 520 : 220,
|
|
1628
2069
|
deviceId: resolvedDeviceId
|
|
1629
2070
|
})
|
|
1630
2071
|
attemptCount++
|
|
@@ -1646,7 +2087,7 @@ export class ToolsInteract {
|
|
|
1646
2087
|
recordTraceStep('verify', verificationResult.withinTolerance ? 'success' : 'retry', {
|
|
1647
2088
|
attempt: attemptCount,
|
|
1648
2089
|
property,
|
|
1649
|
-
target_value:
|
|
2090
|
+
target_value: normalizedTargetValue,
|
|
1650
2091
|
actual_state: observedState,
|
|
1651
2092
|
reason: verificationResult.verification?.reason ?? 'gesture adjustment did not converge yet'
|
|
1652
2093
|
})
|
|
@@ -1663,8 +2104,8 @@ export class ToolsInteract {
|
|
|
1663
2104
|
uiFingerprintBefore: fingerprintBefore,
|
|
1664
2105
|
uiFingerprintAfter,
|
|
1665
2106
|
details: {
|
|
1666
|
-
target_value:
|
|
1667
|
-
tolerance:
|
|
2107
|
+
target_value: normalizedTargetValue,
|
|
2108
|
+
tolerance: effectiveTolerance,
|
|
1668
2109
|
property,
|
|
1669
2110
|
attempts: attemptCount,
|
|
1670
2111
|
adjustment_mode: lastAdjustmentMode,
|
|
@@ -1680,8 +2121,8 @@ export class ToolsInteract {
|
|
|
1680
2121
|
...base,
|
|
1681
2122
|
target_state: {
|
|
1682
2123
|
property,
|
|
1683
|
-
target_value:
|
|
1684
|
-
tolerance:
|
|
2124
|
+
target_value: normalizedTargetValue,
|
|
2125
|
+
tolerance: effectiveTolerance
|
|
1685
2126
|
},
|
|
1686
2127
|
actual_state: observedState,
|
|
1687
2128
|
within_tolerance: true,
|
|
@@ -1699,7 +2140,7 @@ export class ToolsInteract {
|
|
|
1699
2140
|
recordTraceStep('verify', 'success', {
|
|
1700
2141
|
attempt: attemptCount,
|
|
1701
2142
|
property,
|
|
1702
|
-
target_value:
|
|
2143
|
+
target_value: normalizedTargetValue,
|
|
1703
2144
|
actual_state: observedState,
|
|
1704
2145
|
reason: verification?.reason ?? 'control converged to target value'
|
|
1705
2146
|
})
|
|
@@ -1714,8 +2155,8 @@ export class ToolsInteract {
|
|
|
1714
2155
|
uiFingerprintBefore: fingerprintBefore,
|
|
1715
2156
|
uiFingerprintAfter,
|
|
1716
2157
|
details: {
|
|
1717
|
-
target_value:
|
|
1718
|
-
tolerance:
|
|
2158
|
+
target_value: normalizedTargetValue,
|
|
2159
|
+
tolerance: effectiveTolerance,
|
|
1719
2160
|
property,
|
|
1720
2161
|
attempts: attemptCount,
|
|
1721
2162
|
adjustment_mode: lastAdjustmentMode,
|
|
@@ -1731,8 +2172,8 @@ export class ToolsInteract {
|
|
|
1731
2172
|
...base,
|
|
1732
2173
|
target_state: {
|
|
1733
2174
|
property,
|
|
1734
|
-
target_value:
|
|
1735
|
-
tolerance:
|
|
2175
|
+
target_value: normalizedTargetValue,
|
|
2176
|
+
tolerance: effectiveTolerance
|
|
1736
2177
|
},
|
|
1737
2178
|
actual_state: observedState,
|
|
1738
2179
|
within_tolerance: true,
|
|
@@ -1742,23 +2183,25 @@ export class ToolsInteract {
|
|
|
1742
2183
|
}
|
|
1743
2184
|
}
|
|
1744
2185
|
|
|
1745
|
-
cachedResolvedMatch =
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
...
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
2186
|
+
cachedResolvedMatch = inferredSliderState
|
|
2187
|
+
? null
|
|
2188
|
+
: {
|
|
2189
|
+
el: {
|
|
2190
|
+
...currentEl,
|
|
2191
|
+
state: {
|
|
2192
|
+
...(currentEl.state ?? null),
|
|
2193
|
+
...(observedState ? {
|
|
2194
|
+
[observedState.property]: observedState.value,
|
|
2195
|
+
raw_value: observedState.raw_value ?? observedState.value
|
|
2196
|
+
} : {})
|
|
2197
|
+
}
|
|
2198
|
+
},
|
|
2199
|
+
idx: resolved.match.idx
|
|
2200
|
+
}
|
|
1758
2201
|
}
|
|
1759
2202
|
|
|
1760
2203
|
const uiFingerprintAfter = await ToolsInteract._captureFingerprint(resolvedPlatform, resolvedDeviceId)
|
|
1761
|
-
return buildFailure('
|
|
2204
|
+
return buildFailure('CONTROL_CONVERGENCE_FAILED', 'control did not converge within the allotted attempts', resolvedTarget, currentDevice, lastObservedState, attemptCount, lastAdjustmentMode, true, uiFingerprintAfter)
|
|
1762
2205
|
}
|
|
1763
2206
|
|
|
1764
2207
|
static async swipeHandler({ platform = 'android', x1, y1, x2, y2, duration, deviceId }: { platform?: 'android' | 'ios', x1: number, y1: number, x2: number, y2: number, duration: number, deviceId?: string }) {
|