mobile-debug-mcp 0.27.0 → 0.28.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.
@@ -5,7 +5,26 @@ export { AndroidInteract, iOSInteract };
5
5
  import { resolveTargetDevice } from '../utils/resolve-device.js';
6
6
  import { ToolsObserve } from '../observe/index.js';
7
7
  import { computeSnapshotSignature } from '../observe/snapshot-metadata.js';
8
- import { buildActionExecutionResult } from '../server/common.js';
8
+ import { buildActionExecutionResult, createTraceStep, nextActionId } from '../server/common.js';
9
+ function buildObservationTrace({ actionType, stage, success, attempts, metadata }) {
10
+ const now = Date.now();
11
+ const actionId = nextActionId(actionType, now);
12
+ const steps = [
13
+ createTraceStep({
14
+ stage,
15
+ timestamp: now,
16
+ result: success ? 'success' : 'failure',
17
+ attemptIndex: 0,
18
+ metadata
19
+ })
20
+ ];
21
+ return {
22
+ action_id: actionId,
23
+ steps,
24
+ final_outcome: success ? 'success' : 'failure',
25
+ attempts: Math.max(1, Math.floor(attempts || 1))
26
+ };
27
+ }
9
28
  export class ToolsInteract {
10
29
  static _maxResolvedUiElements = 256;
11
30
  static _uiChangeKinds = ['hierarchy_diff', 'text_change', 'state_change'];
@@ -533,7 +552,33 @@ export class ToolsInteract {
533
552
  let resolvedDeviceId = deviceId;
534
553
  const fingerprintBefore = await ToolsInteract._captureFingerprint(resolvedPlatform, resolvedDeviceId);
535
554
  let semanticFallbackElement = null;
555
+ const traceSteps = [];
556
+ let traceAttemptIndex = 0;
557
+ const recordTraceStep = (stage, result, metadata) => {
558
+ traceSteps.push(createTraceStep({
559
+ stage,
560
+ timestamp: Date.now(),
561
+ result,
562
+ attemptIndex: traceAttemptIndex++,
563
+ metadata
564
+ }));
565
+ };
536
566
  const buildFailure = (failureCode, reason, resolved, device, actualState, attempts, adjustmentMode = 'gesture', retryable = false, uiFingerprintAfter = null) => {
567
+ if (!traceSteps.some((step) => step.stage === 'resolve')) {
568
+ recordTraceStep('resolve', 'failure', {
569
+ reason,
570
+ failure_code: failureCode
571
+ });
572
+ }
573
+ if (!traceSteps.some((step) => step.stage === 'recover')) {
574
+ recordTraceStep('recover', retryable ? 'retry' : 'failure', {
575
+ reason,
576
+ failure_code: failureCode,
577
+ retry_allowed: retryable,
578
+ recovery_attempts: attempts,
579
+ retry_depth: attempts
580
+ });
581
+ }
537
582
  const base = buildActionExecutionResult({
538
583
  actionType,
539
584
  sourceModule: 'interact',
@@ -554,7 +599,8 @@ export class ToolsInteract {
554
599
  converged: false,
555
600
  within_tolerance: false,
556
601
  reason
557
- }
602
+ },
603
+ traceSteps
558
604
  });
559
605
  return {
560
606
  ...base,
@@ -716,10 +762,23 @@ export class ToolsInteract {
716
762
  ? { property, value: currentValue, raw_value: typeof currentEl.state?.raw_value === 'number' ? currentEl.state.raw_value : undefined }
717
763
  : null;
718
764
  lastObservedState = actualState;
765
+ if (!traceSteps.some((step) => step.stage === 'resolve')) {
766
+ recordTraceStep('resolve', 'success', {
767
+ resolved_target: resolvedTarget,
768
+ current_value: currentValue,
769
+ adjustment_mode: lastAdjustmentMode
770
+ });
771
+ }
719
772
  if (property !== 'value' && property !== 'raw_value') {
720
773
  return buildFailure('ELEMENT_NOT_INTERACTABLE', 'adjust_control currently supports numeric value and raw_value properties only', resolvedTarget, currentDevice, actualState, attemptCount, lastAdjustmentMode, false);
721
774
  }
722
775
  if (currentValue !== null && Math.abs(currentValue - targetValue) <= normalizedTolerance) {
776
+ recordTraceStep('verify', 'success', {
777
+ property,
778
+ target_value: targetValue,
779
+ actual_state: actualState,
780
+ reason: 'control already within tolerance'
781
+ });
723
782
  const uiFingerprintAfter = await ToolsInteract._captureFingerprint(resolvedPlatform, resolvedDeviceId);
724
783
  const base = buildActionExecutionResult({
725
784
  actionType,
@@ -740,7 +799,8 @@ export class ToolsInteract {
740
799
  converged: true,
741
800
  within_tolerance: true,
742
801
  reason: 'control already within tolerance'
743
- }
802
+ },
803
+ traceSteps
744
804
  });
745
805
  return {
746
806
  ...base,
@@ -810,6 +870,11 @@ export class ToolsInteract {
810
870
  for (let i = 0; i < probePoints.length; i++) {
811
871
  const probePoint = probePoints[i];
812
872
  lastAdjustmentMode = 'coordinate';
873
+ recordTraceStep('execute', 'retry', {
874
+ attempt: attemptCount + 1,
875
+ mode: 'coordinate',
876
+ point: probePoint
877
+ });
813
878
  const actionResult = await ToolsInteract.tapHandler({
814
879
  platform: resolvedPlatform,
815
880
  x: probePoint.x,
@@ -819,11 +884,24 @@ export class ToolsInteract {
819
884
  attemptCount++;
820
885
  actionDevice = actionResult.device ?? actionDevice;
821
886
  if (!actionResult.success) {
887
+ recordTraceStep('execute', 'retry', {
888
+ attempt: attemptCount,
889
+ mode: 'coordinate',
890
+ point: probePoint,
891
+ success: false
892
+ });
822
893
  continue;
823
894
  }
824
895
  verificationResult = await runVerification();
825
896
  observedState = verificationResult.observedState;
826
897
  lastObservedState = observedState;
898
+ recordTraceStep('verify', verificationResult.withinTolerance ? 'success' : 'retry', {
899
+ attempt: attemptCount,
900
+ property,
901
+ target_value: targetValue,
902
+ actual_state: observedState,
903
+ reason: verificationResult.verification?.reason ?? 'control did not converge yet'
904
+ });
827
905
  if (verificationResult.withinTolerance) {
828
906
  const uiFingerprintAfter = await ToolsInteract._captureFingerprint(resolvedPlatform, resolvedDeviceId);
829
907
  const base = buildActionExecutionResult({
@@ -845,7 +923,8 @@ export class ToolsInteract {
845
923
  converged: true,
846
924
  within_tolerance: true,
847
925
  reason: verificationResult.verification?.reason ?? 'control converged to target value'
848
- }
926
+ },
927
+ traceSteps
849
928
  });
850
929
  return {
851
930
  ...base,
@@ -864,6 +943,12 @@ export class ToolsInteract {
864
943
  }
865
944
  if (currentValue !== null) {
866
945
  lastAdjustmentMode = 'gesture';
946
+ recordTraceStep('execute', 'retry', {
947
+ attempt: attemptCount + 1,
948
+ mode: 'gesture',
949
+ start: currentPoint,
950
+ end: targetPoint
951
+ });
867
952
  const fallbackActionResult = await ToolsInteract.swipeHandler({
868
953
  platform: resolvedPlatform,
869
954
  x1: currentPoint.x,
@@ -875,12 +960,26 @@ export class ToolsInteract {
875
960
  });
876
961
  attemptCount++;
877
962
  if (!fallbackActionResult.success) {
963
+ recordTraceStep('execute', 'failure', {
964
+ attempt: attemptCount,
965
+ mode: 'gesture',
966
+ start: currentPoint,
967
+ end: targetPoint,
968
+ success: false
969
+ });
878
970
  return buildFailure('UNKNOWN', fallbackActionResult.error ?? 'adjustment gesture failed', resolvedTarget, fallbackActionResult.device ?? actionDevice, observedState ?? actualState, attemptCount, lastAdjustmentMode, false);
879
971
  }
880
972
  actionDevice = fallbackActionResult.device ?? actionDevice;
881
973
  verificationResult = await runVerification();
882
974
  observedState = verificationResult.observedState;
883
975
  lastObservedState = observedState;
976
+ recordTraceStep('verify', verificationResult.withinTolerance ? 'success' : 'retry', {
977
+ attempt: attemptCount,
978
+ property,
979
+ target_value: targetValue,
980
+ actual_state: observedState,
981
+ reason: verificationResult.verification?.reason ?? 'gesture adjustment did not converge yet'
982
+ });
884
983
  if (verificationResult.withinTolerance) {
885
984
  const uiFingerprintAfter = await ToolsInteract._captureFingerprint(resolvedPlatform, resolvedDeviceId);
886
985
  const base = buildActionExecutionResult({
@@ -902,7 +1001,8 @@ export class ToolsInteract {
902
1001
  converged: true,
903
1002
  within_tolerance: true,
904
1003
  reason: verificationResult.verification?.reason ?? 'control converged to target value'
905
- }
1004
+ },
1005
+ traceSteps
906
1006
  });
907
1007
  return {
908
1008
  ...base,
@@ -922,6 +1022,13 @@ export class ToolsInteract {
922
1022
  verification = verificationResult.verification;
923
1023
  lastObservedState = observedState;
924
1024
  if (verificationResult.withinTolerance) {
1025
+ recordTraceStep('verify', 'success', {
1026
+ attempt: attemptCount,
1027
+ property,
1028
+ target_value: targetValue,
1029
+ actual_state: observedState,
1030
+ reason: verification?.reason ?? 'control converged to target value'
1031
+ });
925
1032
  const uiFingerprintAfter = await ToolsInteract._captureFingerprint(resolvedPlatform, resolvedDeviceId);
926
1033
  const base = buildActionExecutionResult({
927
1034
  actionType,
@@ -942,7 +1049,8 @@ export class ToolsInteract {
942
1049
  converged: true,
943
1050
  within_tolerance: true,
944
1051
  reason: verification?.reason ?? 'control converged to target value'
945
- }
1052
+ },
1053
+ traceSteps
946
1054
  });
947
1055
  return {
948
1056
  ...base,
@@ -1716,7 +1824,24 @@ export class ToolsInteract {
1716
1824
  basis,
1717
1825
  matched: success,
1718
1826
  reason
1719
- }
1827
+ },
1828
+ trace: buildObservationTrace({
1829
+ actionType: 'expect_screen',
1830
+ stage: 'verify',
1831
+ success,
1832
+ attempts: 1,
1833
+ metadata: {
1834
+ expected_screen: expectedScreen,
1835
+ observed_screen: {
1836
+ fingerprint: observedScreen.fingerprint,
1837
+ screen: observedScreenLabel
1838
+ },
1839
+ comparison: {
1840
+ basis,
1841
+ matched: success
1842
+ }
1843
+ }
1844
+ })
1720
1845
  };
1721
1846
  }
1722
1847
  static async expectElementVisibleHandler({ selector, element_id, timeout_ms = 5000, poll_interval_ms = 300, platform, deviceId }) {
@@ -1770,7 +1895,18 @@ export class ToolsInteract {
1770
1895
  semantic: result.element.semantic ?? null
1771
1896
  }
1772
1897
  },
1773
- reason: 'selector is visible'
1898
+ reason: 'selector is visible',
1899
+ trace: buildObservationTrace({
1900
+ actionType: 'expect_element_visible',
1901
+ stage: 'verify',
1902
+ success: true,
1903
+ attempts: 1,
1904
+ metadata: {
1905
+ selector,
1906
+ element_id: result.element.elementId ?? element_id ?? null,
1907
+ status: result.status
1908
+ }
1909
+ })
1774
1910
  };
1775
1911
  }
1776
1912
  const errorCode = result?.error?.code === 'INTERNAL_ERROR' ? 'UNKNOWN' : 'TIMEOUT';
@@ -1788,7 +1924,19 @@ export class ToolsInteract {
1788
1924
  },
1789
1925
  reason: result?.error?.message ?? 'selector is not visible',
1790
1926
  failure_code: errorCode,
1791
- retryable: errorCode === 'TIMEOUT'
1927
+ retryable: errorCode === 'TIMEOUT',
1928
+ trace: buildObservationTrace({
1929
+ actionType: 'expect_element_visible',
1930
+ stage: 'verify',
1931
+ success: false,
1932
+ attempts: 1,
1933
+ metadata: {
1934
+ selector,
1935
+ element_id: element_id ?? null,
1936
+ status: result?.status ?? null,
1937
+ reason: result?.error?.message ?? 'selector is not visible'
1938
+ }
1939
+ })
1792
1940
  };
1793
1941
  }
1794
1942
  static async expectStateHandler({ selector, element_id, property, expected, platform, deviceId, stabilization_window_ms = 1000, stable_observation_count = 2, snapshot_stale_threshold_ms = 500, poll_interval_ms = 150 }) {
@@ -1809,6 +1957,24 @@ export class ToolsInteract {
1809
1957
  let lastObservedValue = null;
1810
1958
  let lastRawValue = null;
1811
1959
  let lastResolvedElementId = element_id ?? null;
1960
+ const traceSteps = [];
1961
+ let traceAttemptIndex = 0;
1962
+ let resolveRecorded = false;
1963
+ const recordTraceStep = (stage, result, metadata) => {
1964
+ traceSteps.push(createTraceStep({
1965
+ stage,
1966
+ timestamp: Date.now(),
1967
+ result,
1968
+ attemptIndex: traceAttemptIndex++,
1969
+ metadata
1970
+ }));
1971
+ };
1972
+ const buildStateTrace = (outcome) => ({
1973
+ action_id: nextActionId('expect_state', Date.now()),
1974
+ steps: traceSteps,
1975
+ final_outcome: outcome,
1976
+ attempts
1977
+ });
1812
1978
  while (Date.now() <= deadline) {
1813
1979
  attempts++;
1814
1980
  const tree = await ToolsObserve.getUITreeHandler({ platform, deviceId });
@@ -1832,6 +1998,19 @@ export class ToolsInteract {
1832
1998
  lastReason = 'element not found';
1833
1999
  lastFailureCode = 'ELEMENT_NOT_FOUND';
1834
2000
  stableCount = 0;
2001
+ recordTraceStep('resolve', 'retry', {
2002
+ selector: selector ?? null,
2003
+ element_id: lastResolvedElementId,
2004
+ matched: false,
2005
+ reason: lastReason,
2006
+ attempt: attempts
2007
+ });
2008
+ recordTraceStep('stabilize', 'retry', {
2009
+ stabilization_attempts: attempts,
2010
+ stable_observation_count: stableCount,
2011
+ snapshot_freshness_ms: treeAgeMs,
2012
+ reason: lastReason
2013
+ });
1835
2014
  await sleep(pollDelay);
1836
2015
  continue;
1837
2016
  }
@@ -1842,9 +2021,31 @@ export class ToolsInteract {
1842
2021
  lastReason = 'stale snapshot';
1843
2022
  lastFailureCode = 'UNKNOWN';
1844
2023
  stableCount = 0;
2024
+ recordTraceStep('resolve', 'retry', {
2025
+ selector: selector ?? null,
2026
+ element_id: lastResolvedElementId,
2027
+ matched: true,
2028
+ reason: lastReason,
2029
+ attempt: attempts
2030
+ });
2031
+ recordTraceStep('stabilize', 'retry', {
2032
+ stabilization_attempts: attempts,
2033
+ stable_observation_count: stableCount,
2034
+ snapshot_freshness_ms: treeAgeMs,
2035
+ reason: lastReason
2036
+ });
1845
2037
  await sleep(pollDelay);
1846
2038
  continue;
1847
2039
  }
2040
+ if (!resolveRecorded) {
2041
+ recordTraceStep('resolve', 'success', {
2042
+ selector: selector ?? null,
2043
+ element_id: lastResolvedElementId,
2044
+ matched: true,
2045
+ reason: 'element resolved'
2046
+ });
2047
+ resolveRecorded = true;
2048
+ }
1848
2049
  const observedState = matched.el.state ?? null;
1849
2050
  const actual = observedState?.[property] ?? null;
1850
2051
  let success = false;
@@ -1964,6 +2165,18 @@ export class ToolsInteract {
1964
2165
  }
1965
2166
  if (success) {
1966
2167
  stableCount++;
2168
+ recordTraceStep('stabilize', 'success', {
2169
+ stabilization_attempts: attempts,
2170
+ stable_observation_count: stableCount,
2171
+ snapshot_freshness_ms: treeAgeMs,
2172
+ reason
2173
+ });
2174
+ recordTraceStep('verify', 'success', {
2175
+ property,
2176
+ expected,
2177
+ observed_value: observedValue,
2178
+ reason
2179
+ });
1967
2180
  if (stableCount >= stableTarget) {
1968
2181
  return {
1969
2182
  success: true,
@@ -1980,7 +2193,8 @@ export class ToolsInteract {
1980
2193
  stabilization_attempts: attempts,
1981
2194
  stabilization_window_ms: Date.now() - start,
1982
2195
  stable_observation_count: stableCount,
1983
- snapshot_freshness_ms: treeAgeMs ?? undefined
2196
+ snapshot_freshness_ms: treeAgeMs ?? undefined,
2197
+ trace: buildStateTrace('success')
1984
2198
  };
1985
2199
  }
1986
2200
  }
@@ -1988,6 +2202,18 @@ export class ToolsInteract {
1988
2202
  stableCount = 0;
1989
2203
  lastReason = reason || lastReason;
1990
2204
  lastFailureCode = 'UNKNOWN';
2205
+ recordTraceStep('stabilize', 'retry', {
2206
+ stabilization_attempts: attempts,
2207
+ stable_observation_count: stableCount,
2208
+ snapshot_freshness_ms: treeAgeMs,
2209
+ reason: lastReason
2210
+ });
2211
+ recordTraceStep('verify', 'retry', {
2212
+ property,
2213
+ expected,
2214
+ observed_value: observedValue,
2215
+ reason: lastReason
2216
+ });
1991
2217
  }
1992
2218
  if (!success) {
1993
2219
  lastObservedValue = observedValue;
@@ -2008,7 +2234,8 @@ export class ToolsInteract {
2008
2234
  },
2009
2235
  reason: lastReason,
2010
2236
  failure_code: lastFailureCode,
2011
- retryable: true
2237
+ retryable: true,
2238
+ trace: buildStateTrace('failure')
2012
2239
  };
2013
2240
  }
2014
2241
  static async waitForUICore({ type = 'ui', query, timeoutMs = 30000, pollIntervalMs = 300, includeSnapshotOnFailure = true, match = 'present', stability_ms = 700, observationDelayMs = 0, platform, deviceId }) {
@@ -71,6 +71,119 @@ export async function captureActionFingerprint(platform, deviceId) {
71
71
  return null;
72
72
  }
73
73
  }
74
+ export function createTraceStep({ stage, timestamp, result, attemptIndex, cycleId, metadata }) {
75
+ return {
76
+ stage,
77
+ timestamp,
78
+ result,
79
+ attempt_index: attemptIndex,
80
+ ...(cycleId !== undefined ? { cycle_id: cycleId } : {}),
81
+ ...(metadata ? { metadata } : {})
82
+ };
83
+ }
84
+ export function buildActionTrace({ actionId, actionType, sourceModule, selector, resolved, success, failure, details, recovery, attempts = 1, steps }) {
85
+ if (steps && steps.length > 0) {
86
+ return {
87
+ action_id: actionId,
88
+ steps,
89
+ final_outcome: success ? 'success' : 'failure',
90
+ attempts: Math.max(1, Math.floor(attempts || steps.length))
91
+ };
92
+ }
93
+ const start = Date.now();
94
+ const builtSteps = [];
95
+ let attemptIndex = 0;
96
+ const totalAttempts = Math.max(1, Math.floor(attempts || 1));
97
+ if (selector || resolved) {
98
+ const stageResult = resolved ? 'success' : 'failure';
99
+ builtSteps.push(createTraceStep({
100
+ stage: 'resolve',
101
+ timestamp: start,
102
+ result: stageResult,
103
+ attemptIndex: attemptIndex++,
104
+ metadata: {
105
+ action_type: actionType,
106
+ source_module: sourceModule,
107
+ selector: selector ?? null,
108
+ resolved: resolved ? normalizeResolvedTarget(resolved) : null
109
+ }
110
+ }));
111
+ }
112
+ builtSteps.push(createTraceStep({
113
+ stage: 'execute',
114
+ timestamp: Date.now(),
115
+ result: success ? 'success' : 'failure',
116
+ attemptIndex: attemptIndex++,
117
+ metadata: {
118
+ action_type: actionType,
119
+ source_module: sourceModule,
120
+ ...(failure ? { failure_code: failure.failureCode, retryable: failure.retryable } : {})
121
+ }
122
+ }));
123
+ const hasStabilizeDetails = Boolean(details && (Object.prototype.hasOwnProperty.call(details, 'stabilization_attempts') ||
124
+ Object.prototype.hasOwnProperty.call(details, 'stable_observation_count') ||
125
+ Object.prototype.hasOwnProperty.call(details, 'snapshot_freshness_ms')));
126
+ const hasVerifyDetails = Boolean(details && (Object.prototype.hasOwnProperty.call(details, 'within_tolerance') ||
127
+ Object.prototype.hasOwnProperty.call(details, 'converged') ||
128
+ Object.prototype.hasOwnProperty.call(details, 'observed_state')));
129
+ if (hasStabilizeDetails) {
130
+ builtSteps.push(createTraceStep({
131
+ stage: 'stabilize',
132
+ timestamp: Date.now(),
133
+ result: success ? 'success' : 'failure',
134
+ attemptIndex: attemptIndex++,
135
+ metadata: {
136
+ stabilization_attempts: details?.stabilization_attempts ?? null,
137
+ stable_observation_count: details?.stable_observation_count ?? null,
138
+ snapshot_freshness_ms: details?.snapshot_freshness_ms ?? null
139
+ }
140
+ }));
141
+ }
142
+ if (hasVerifyDetails) {
143
+ builtSteps.push(createTraceStep({
144
+ stage: 'verify',
145
+ timestamp: Date.now(),
146
+ result: success ? 'success' : 'failure',
147
+ attemptIndex: attemptIndex++,
148
+ metadata: {
149
+ within_tolerance: details?.within_tolerance ?? null,
150
+ converged: details?.converged ?? null,
151
+ actual_state: details?.actual_state ?? null,
152
+ reason: details?.reason ?? null
153
+ }
154
+ }));
155
+ }
156
+ if (failure) {
157
+ builtSteps.push(createTraceStep({
158
+ stage: 'recover',
159
+ timestamp: Date.now(),
160
+ result: failure.retryable ? 'retry' : 'failure',
161
+ attemptIndex: attemptIndex++,
162
+ metadata: {
163
+ failure_class: recovery?.failure_class ?? mapFailureCodeToFailureClass(failure.failureCode),
164
+ runtime_code: failure.failureCode,
165
+ retry_allowed: failure.retryable,
166
+ recovery_attempts: recovery?.recovery_attempts ?? 0,
167
+ retry_depth: recovery?.retry_depth ?? 0
168
+ }
169
+ }));
170
+ }
171
+ if (!builtSteps.length) {
172
+ builtSteps.push(createTraceStep({
173
+ stage: 'execute',
174
+ timestamp: start,
175
+ result: success ? 'success' : 'failure',
176
+ attemptIndex: 0,
177
+ metadata: { action_type: actionType, source_module: sourceModule }
178
+ }));
179
+ }
180
+ return {
181
+ action_id: actionId,
182
+ steps: builtSteps,
183
+ final_outcome: success ? 'success' : 'failure',
184
+ attempts: totalAttempts
185
+ };
186
+ }
74
187
  export function normalizeResolvedTarget(value = null) {
75
188
  if (!value)
76
189
  return null;
@@ -144,11 +257,16 @@ function buildRecoveryState(failureCode, retryable) {
144
257
  retry_allowed: retryable
145
258
  };
146
259
  }
147
- export function buildActionExecutionResult({ actionType, device, selector, resolved, success, uiFingerprintBefore, uiFingerprintAfter, failure, details, sourceModule }) {
260
+ export function buildActionExecutionResult({ actionType, device, selector, resolved, success, uiFingerprintBefore, uiFingerprintAfter, failure, details, sourceModule, traceSteps }) {
148
261
  const timestampMs = Date.now();
149
262
  const timestamp = new Date(timestampMs).toISOString();
263
+ const actionId = nextActionId(actionType, timestampMs);
264
+ const recoveryState = failure ? buildRecoveryState(failure.failureCode, failure.retryable) : undefined;
265
+ const attempts = typeof details?.attempts === 'number' && Number.isFinite(details.attempts)
266
+ ? Math.max(1, Math.floor(details.attempts))
267
+ : 1;
150
268
  return {
151
- action_id: nextActionId(actionType, timestampMs),
269
+ action_id: actionId,
152
270
  timestamp,
153
271
  action_type: actionType,
154
272
  lifecycle_state: determineActionLifecycleState({ success, failure }),
@@ -160,7 +278,20 @@ export function buildActionExecutionResult({ actionType, device, selector, resol
160
278
  },
161
279
  success,
162
280
  ...(failure ? { failure_code: failure.failureCode, retryable: failure.retryable } : {}),
163
- ...(failure ? { recovery: buildRecoveryState(failure.failureCode, failure.retryable) } : {}),
281
+ ...(recoveryState ? { recovery: recoveryState } : {}),
282
+ trace: buildActionTrace({
283
+ actionId,
284
+ actionType,
285
+ sourceModule,
286
+ selector,
287
+ resolved,
288
+ success,
289
+ failure,
290
+ details,
291
+ recovery: recoveryState,
292
+ attempts,
293
+ steps: traceSteps
294
+ }),
164
295
  ui_fingerprint_before: uiFingerprintBefore,
165
296
  ui_fingerprint_after: uiFingerprintAfter,
166
297
  ...(details ? { details } : {})
@@ -6,7 +6,7 @@ import { handleToolCall } from './server/tool-handlers.js';
6
6
  export { wrapResponse, toolDefinitions, handleToolCall };
7
7
  export const serverInfo = {
8
8
  name: 'mobile-debug-mcp',
9
- version: '0.27.0'
9
+ version: '0.28.0'
10
10
  };
11
11
  export function createServer() {
12
12
  const server = new Server(serverInfo, {
package/docs/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  All notable changes to the **Mobile Debug MCP** project will be documented in this file.
4
4
 
5
+ ## [0.28.0]
6
+ - Added structured execution trace model for all actions within the MCP runtime. It provides visibility into resolution, execution, verification, stabilization, and recovery stages.
7
+
5
8
  ## [0.27.0]
6
9
  - defines a structured recovery and replanning model for UI interaction failures, enabling the system to respond to execution uncertainty with bounded, deterministic recovery strategies.
7
10