mobile-debug-mcp 0.35.0 → 0.37.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.
Files changed (247) hide show
  1. package/.cursor-plugin/marketplace.json +1 -1
  2. package/.github/workflows/ci.yml +1 -1
  3. package/README.md +96 -24
  4. package/agent-plugin/.antigravity-plugin/README.md +48 -0
  5. package/agent-plugin/.antigravity-plugin/plugin.json +18 -0
  6. package/agent-plugin/.claude-plugin/plugin.json +2 -2
  7. package/agent-plugin/.codex-plugin/plugin.json +1 -1
  8. package/agent-plugin/.cursor-plugin/plugin.json +2 -2
  9. package/agent-plugin/.mcp.json +1 -1
  10. package/agent-plugin/README.md +37 -8
  11. package/agent-plugin/cursor/.cursor-plugin/plugin.json +23 -0
  12. package/agent-plugin/cursor/LICENSE +201 -0
  13. package/agent-plugin/cursor/README.md +9 -0
  14. package/agent-plugin/cursor/skills/mobile-debug-mcp/SKILL.md +261 -0
  15. package/agent-plugin/mcp.json +1 -1
  16. package/agent-plugin/mcp_config.json +13 -0
  17. package/agent-plugin/plugin.json +1 -1
  18. package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +46 -18
  19. package/dist/contracts/device-readiness.js +1 -0
  20. package/dist/debugger/guard.js +4 -4
  21. package/dist/debugger/ios-worker.js +62 -0
  22. package/dist/debugger/ios.js +57 -0
  23. package/dist/debugger/sessions.js +33 -16
  24. package/dist/debugger/worker.js +6 -4
  25. package/dist/interact/index.js +12 -2
  26. package/dist/interact/waits/ui-change-wait.js +9 -2
  27. package/dist/interact/waits/ui-wait.js +7 -3
  28. package/dist/manage/index.js +3 -0
  29. package/dist/manage/ios.js +2 -0
  30. package/dist/observe/device-readiness.js +116 -0
  31. package/dist/observe/evidence.js +31 -0
  32. package/dist/observe/ios-trace/artifact.js +63 -0
  33. package/dist/observe/ios-trace/capabilities.js +86 -0
  34. package/dist/observe/ios-trace/native.js +93 -0
  35. package/dist/observe/ios-trace/notification.js +35 -0
  36. package/dist/observe/ios-trace/process.js +98 -0
  37. package/dist/observe/ios-trace/sessions.js +246 -0
  38. package/dist/observe/readiness-service.js +22 -10
  39. package/dist/observe/readiness.js +34 -4
  40. package/dist/observe/screenshot-encoding.js +4 -0
  41. package/dist/observe/snapshot-collection.js +7 -2
  42. package/dist/observe/system-trace-error.js +7 -0
  43. package/dist/observe/system-trace-router.js +81 -0
  44. package/dist/observe/system-trace.js +4 -8
  45. package/dist/server/definitions/manage.js +3 -2
  46. package/dist/server/handlers/action-execution.js +23 -14
  47. package/dist/server/handlers/compact-interaction.js +106 -0
  48. package/dist/server/handlers/compact-state.js +47 -0
  49. package/dist/server/handlers/debugger-handlers.js +27 -8
  50. package/dist/server/handlers/diagnostic.js +12 -4
  51. package/dist/server/handlers/input.js +2 -0
  52. package/dist/server/handlers/interact.js +2 -0
  53. package/dist/server/handlers/interaction-diagnostics.js +62 -0
  54. package/dist/server/handlers/journey.js +18 -20
  55. package/dist/server/handlers/observe.js +2 -1
  56. package/dist/server/handlers/tool-handlers.js +28 -14
  57. package/dist/server/schemas/debugger-schema.js +10 -7
  58. package/dist/server/schemas/interaction-diagnostics.js +9 -0
  59. package/dist/server/schemas/system-trace-schema.js +14 -9
  60. package/dist/server/tool-definitions.js +9 -4
  61. package/dist/server-core.js +5 -2
  62. package/dist/server.js +5 -5
  63. package/dist/system/android.js +19 -15
  64. package/dist/system/index.js +2 -1
  65. package/dist/utils/android/utils.js +7 -16
  66. package/dist/utils/app-installation.js +77 -0
  67. package/dist/utils/benchmark-metrics.js +43 -0
  68. package/dist/utils/exec.js +4 -0
  69. package/dist/utils/ios/utils.js +7 -11
  70. package/dist/utils/resolve-device.js +3 -2
  71. package/dist/utils/timing.js +3 -3
  72. package/docs/CHANGELOG.md +48 -1
  73. package/docs/agent-plugin-installation-verification.md +39 -0
  74. package/docs/benchmarks/README.md +1 -0
  75. package/docs/benchmarks/end-to-end-latency/README.md +98 -0
  76. package/docs/benchmarks/end-to-end-latency/android-snapshot-pair.json +6532 -0
  77. package/docs/benchmarks/end-to-end-latency/android-snapshot-pair.md +30 -0
  78. package/docs/benchmarks/end-to-end-latency/baseline-attempt-1.json +15901 -0
  79. package/docs/benchmarks/end-to-end-latency/baseline-attempt-1.md +14 -0
  80. package/docs/benchmarks/end-to-end-latency/baseline.json +233843 -0
  81. package/docs/benchmarks/end-to-end-latency/baseline.md +21 -0
  82. package/docs/benchmarks/end-to-end-latency/investigation.md +128 -0
  83. package/docs/benchmarks/end-to-end-latency/ios-smoke.json +84 -0
  84. package/docs/benchmarks/end-to-end-latency/ios-smoke.md +9 -0
  85. package/docs/benchmarks/end-to-end-latency/smoke.json +19601 -0
  86. package/docs/benchmarks/end-to-end-latency/smoke.md +21 -0
  87. package/docs/code-structure.md +3 -1
  88. package/docs/specs/031-ios-system-traces/intent.md +38 -0
  89. package/docs/specs/031-ios-system-traces/investigation.md +92 -0
  90. package/docs/specs/031-ios-system-traces/late-run-failure-evidence.json +32 -0
  91. package/docs/specs/031-ios-system-traces/plan.md +166 -0
  92. package/docs/specs/031-ios-system-traces/pr102-monitor-evidence.json +22 -0
  93. package/docs/specs/031-ios-system-traces/simulator-evidence.json +47 -0
  94. package/docs/specs/031-ios-system-traces/spec.md +135 -0
  95. package/docs/specs/032-ios-runtime-debugger/evidence.json +176 -0
  96. package/docs/specs/032-ios-runtime-debugger/intent.md +38 -0
  97. package/docs/specs/032-ios-runtime-debugger/plan.md +95 -0
  98. package/docs/specs/032-ios-runtime-debugger/report.md +205 -0
  99. package/docs/specs/032-ios-runtime-debugger/spec.md +153 -0
  100. package/docs/specs/033-device-readiness-blockers/android-evidence.json +437 -0
  101. package/docs/specs/033-device-readiness-blockers/android-physical-evidence.json +28 -0
  102. package/docs/specs/033-device-readiness-blockers/android-physical-power-evidence.json +29 -0
  103. package/docs/specs/033-device-readiness-blockers/intent.md +41 -0
  104. package/docs/specs/033-device-readiness-blockers/ios-investigation.md +55 -0
  105. package/docs/specs/033-device-readiness-blockers/ios-probe-evidence.json +24 -0
  106. package/docs/specs/033-device-readiness-blockers/ios-tool-evidence.json +73 -0
  107. package/docs/specs/033-device-readiness-blockers/plan.md +249 -0
  108. package/docs/specs/033-device-readiness-blockers/spec.md +134 -0
  109. package/docs/specs/034-app-installation-status/intent.md +41 -0
  110. package/docs/specs/034-app-installation-status/native-evidence.json +654 -0
  111. package/docs/specs/034-app-installation-status/plan.md +73 -0
  112. package/docs/specs/034-app-installation-status/spec.md +114 -0
  113. package/docs/specs/035-compact-interaction-results/baseline.md +45 -0
  114. package/docs/specs/035-compact-interaction-results/intent.md +38 -0
  115. package/docs/specs/035-compact-interaction-results/plan.md +43 -0
  116. package/docs/specs/035-compact-interaction-results/spec.md +118 -0
  117. package/docs/specs/035-compact-interaction-results/verification.md +111 -0
  118. package/docs/specs/036-end-to-end-interaction-latency/android-snapshot-amendment.md +60 -0
  119. package/docs/specs/036-end-to-end-interaction-latency/android-snapshot-plan.md +32 -0
  120. package/docs/specs/036-end-to-end-interaction-latency/intent.md +37 -0
  121. package/docs/specs/036-end-to-end-interaction-latency/plan.md +59 -0
  122. package/docs/specs/036-end-to-end-interaction-latency/spec.md +126 -0
  123. package/docs/specs/036-end-to-end-interaction-latency/verification.md +87 -0
  124. package/docs/tools/TOOLS.md +3 -2
  125. package/docs/tools/compact-interactions.md +56 -0
  126. package/docs/tools/correlated-evidence.md +6 -0
  127. package/docs/tools/debugger.md +45 -5
  128. package/docs/tools/interact.md +17 -8
  129. package/docs/tools/manage.md +10 -2
  130. package/docs/tools/observe.md +42 -2
  131. package/docs/tools/system-traces.md +81 -14
  132. package/docs/tools/system.md +6 -0
  133. package/package.json +2 -1
  134. package/scripts/agent-plugin.mjs +55 -5
  135. package/src/contracts/device-readiness.ts +8 -0
  136. package/src/debugger/android.ts +1 -1
  137. package/src/debugger/guard.ts +5 -5
  138. package/src/debugger/ios-worker.ts +49 -0
  139. package/src/debugger/ios.ts +45 -0
  140. package/src/debugger/lldb/breakpoints.py +51 -0
  141. package/src/debugger/lldb/inspection.py +109 -0
  142. package/src/debugger/lldb/worker.py +203 -0
  143. package/src/debugger/sessions.ts +24 -12
  144. package/src/debugger/worker.ts +7 -4
  145. package/src/interact/index.ts +13 -2
  146. package/src/interact/waits/ui-change-wait.ts +7 -2
  147. package/src/interact/waits/ui-wait.ts +6 -3
  148. package/src/manage/index.ts +2 -0
  149. package/src/manage/ios.ts +2 -0
  150. package/src/observe/device-readiness.ts +96 -0
  151. package/src/observe/evidence.ts +30 -1
  152. package/src/observe/ios-trace/artifact.ts +55 -0
  153. package/src/observe/ios-trace/capabilities.ts +74 -0
  154. package/src/observe/ios-trace/native.ts +72 -0
  155. package/src/observe/ios-trace/notification.ts +30 -0
  156. package/src/observe/ios-trace/process.ts +82 -0
  157. package/src/observe/ios-trace/sessions.ts +206 -0
  158. package/src/observe/readiness-service.ts +22 -10
  159. package/src/observe/readiness.ts +37 -4
  160. package/src/observe/screenshot-encoding.ts +4 -0
  161. package/src/observe/snapshot-collection.ts +7 -2
  162. package/src/observe/system-trace-error.ts +3 -0
  163. package/src/observe/system-trace-router.ts +61 -0
  164. package/src/observe/system-trace.ts +4 -4
  165. package/src/server/common.ts +2 -2
  166. package/src/server/definitions/manage.ts +3 -2
  167. package/src/server/handlers/action-execution.ts +22 -14
  168. package/src/server/handlers/compact-interaction.ts +91 -0
  169. package/src/server/handlers/compact-state.ts +43 -0
  170. package/src/server/handlers/debugger-handlers.ts +28 -9
  171. package/src/server/handlers/diagnostic.ts +11 -4
  172. package/src/server/handlers/input.ts +2 -0
  173. package/src/server/handlers/interact.ts +2 -0
  174. package/src/server/handlers/interaction-diagnostics.ts +50 -0
  175. package/src/server/handlers/journey.ts +15 -17
  176. package/src/server/handlers/observe.ts +2 -1
  177. package/src/server/handlers/payload.ts +2 -1
  178. package/src/server/handlers/tool-handlers.ts +29 -14
  179. package/src/server/schemas/debugger-schema.ts +10 -7
  180. package/src/server/schemas/interaction-diagnostics.ts +10 -0
  181. package/src/server/schemas/system-trace-schema.ts +15 -9
  182. package/src/server/tool-definitions.ts +8 -4
  183. package/src/server-core.ts +5 -2
  184. package/src/server.ts +4 -4
  185. package/src/system/android.ts +18 -11
  186. package/src/system/index.ts +2 -1
  187. package/src/types.ts +2 -0
  188. package/src/utils/android/utils.ts +8 -10
  189. package/src/utils/app-installation.ts +73 -0
  190. package/src/utils/benchmark-metrics.ts +42 -0
  191. package/src/utils/exec.ts +5 -0
  192. package/src/utils/ios/utils.ts +7 -10
  193. package/src/utils/resolve-device.ts +4 -2
  194. package/src/utils/timing.ts +3 -2
  195. package/test/device/automated/debugger/runtime.android.test.ts +5 -0
  196. package/test/device/automated/debugger/runtime.ios.test.ts +227 -0
  197. package/test/device/automated/interact/compact-results.smoke.ts +58 -0
  198. package/test/device/automated/latency/end-to-end-environment.ts +61 -0
  199. package/test/device/automated/latency/end-to-end-report.ts +90 -0
  200. package/test/device/automated/latency/end-to-end-session.ts +98 -0
  201. package/test/device/automated/latency/end-to-end.ts +149 -0
  202. package/test/device/automated/latency/owned-transport.ts +81 -0
  203. package/test/device/automated/latency/snapshot-pair.ts +143 -0
  204. package/test/device/automated/latency-ios/benchmark.ts +126 -0
  205. package/test/device/automated/observe/correlated-evidence.smoke.ts +7 -5
  206. package/test/device/automated/observe/device-readiness-cleanup.ts +14 -0
  207. package/test/device/automated/observe/device-readiness.android.smoke.ts +78 -0
  208. package/test/device/automated/observe/ios-system-trace.smoke.ts +119 -0
  209. package/test/device/automated/observe/system-trace.smoke.ts +2 -2
  210. package/test/fixtures/contracts/tool-definitions.json +243 -47
  211. package/test/fixtures/ios-debugger-app/Main.swift +95 -0
  212. package/test/fixtures/ios-debugger-app/README.md +27 -0
  213. package/test/fixtures/ios-debugger-app/build.ts +21 -0
  214. package/test/fixtures/latency-journey-app/build.ts +3 -3
  215. package/test/helpers/ios-trace-shutdown.ts +29 -0
  216. package/test/unit/debugger/handle-guards.test.ts +89 -0
  217. package/test/unit/debugger/ios-preparation.test.ts +57 -0
  218. package/test/unit/debugger/ios.test.ts +104 -0
  219. package/test/unit/debugger/lldb-worker.py +166 -0
  220. package/test/unit/debugger/lldb-worker.test.ts +10 -0
  221. package/test/unit/interact/ui-change-readiness-interruption.test.ts +37 -0
  222. package/test/unit/observe/capture_debug_snapshot.test.ts +66 -12
  223. package/test/unit/observe/device-readiness-cleanup.test.ts +24 -0
  224. package/test/unit/observe/device-readiness.test.ts +97 -0
  225. package/test/unit/observe/ios-trace-native.test.ts +90 -0
  226. package/test/unit/observe/ios-trace-sessions.test.ts +154 -0
  227. package/test/unit/observe/readiness-retained-blockers.test.ts +40 -0
  228. package/test/unit/observe/readiness.test.ts +1 -1
  229. package/test/unit/observe/system-trace-router.test.ts +49 -0
  230. package/test/unit/quality/agent-plugin-export.test.ts +38 -0
  231. package/test/unit/server/compact-interaction.test.ts +87 -0
  232. package/test/unit/server/compact-journey-evidence.test.ts +40 -0
  233. package/test/unit/server/compact-review-regressions.test.ts +78 -0
  234. package/test/unit/server/conditional_actions.test.ts +7 -3
  235. package/test/unit/server/correlated-evidence.test.ts +18 -6
  236. package/test/unit/server/end-to-end-report.test.ts +46 -0
  237. package/test/unit/server/input-device-readiness.test.ts +55 -0
  238. package/test/unit/server/interaction-diagnostics.test.ts +45 -0
  239. package/test/unit/server/owned-benchmark-transport.test.ts +32 -0
  240. package/test/unit/server/readiness-retained-deadline.test.ts +32 -0
  241. package/test/unit/server/readiness.test.ts +24 -1
  242. package/test/unit/server/run_journey.test.ts +2 -1
  243. package/test/unit/server/selector_tap_target.test.ts +2 -1
  244. package/test/unit/server/system-trace.test.ts +18 -7
  245. package/test/unit/utils/app-installation.test.ts +78 -0
  246. package/test/unit/utils/benchmark-metrics.test.ts +36 -0
  247. package/test/unit/utils/resolve_device.test.ts +11 -0
@@ -0,0 +1,77 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { execCmd } from './exec.js';
3
+ import { remainingBudget } from './operation-budget.js';
4
+ export function validAppId(appId) { return /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/.test(appId); }
5
+ export function installationContext(deviceId, appId, scope) {
6
+ return { status: 'unknown', appId: appId ?? null, deviceId, scope, checkedAt: null, reason: appId === undefined ? 'APP_ID_NOT_PROVIDED' : 'CHECK_FAILED' };
7
+ }
8
+ export function installationFields(appInstallation) {
9
+ return { appInstallation, ...(appInstallation.status === 'unknown' ? {} : { appInstalled: appInstallation.status === 'installed' }) };
10
+ }
11
+ export async function probeInstallation(command, deviceId, appId, scope, available = true) {
12
+ const result = installationContext(deviceId, appId, scope);
13
+ if (appId === undefined || !validAppId(appId))
14
+ return result;
15
+ if (!available)
16
+ return { ...result, reason: 'DEVICE_UNAVAILABLE' };
17
+ try {
18
+ return await runProbe(command, result);
19
+ }
20
+ catch {
21
+ return { ...result, checkedAt: new Date().toISOString(), reason: 'CHECK_FAILED' };
22
+ }
23
+ }
24
+ async function runProbe(command, context) {
25
+ const android = context.scope === 'android-current-user';
26
+ const started = performance.now();
27
+ const timeout = remainingBudget(5000);
28
+ const args = android ? ['-s', context.deviceId, 'shell', 'pm', 'path', context.appId] : ['simctl', 'listapps', context.deviceId];
29
+ const output = await execCmd(command, args, { timeout });
30
+ const result = { ...context, checkedAt: new Date().toISOString() };
31
+ if (output.exitCode !== 0) {
32
+ if (android && !output.stdout && !output.stderr)
33
+ return confirmAndroidAbsence(command, result, Math.max(1, timeout - (performance.now() - started)));
34
+ return { ...result, reason: 'CHECK_FAILED' };
35
+ }
36
+ let status;
37
+ if (android) {
38
+ const lines = output.stdout.trim().split('\n').filter(Boolean);
39
+ if (lines.some(line => !/^package:\/\S+$/.test(line.trim())))
40
+ return { ...result, reason: 'UNRECOGNIZED_RESPONSE' };
41
+ status = lines.length ? 'installed' : 'absent';
42
+ }
43
+ else {
44
+ try {
45
+ const budget = remainingBudget(Math.max(1, Math.floor(timeout - (performance.now() - started))));
46
+ if (performance.now() - started >= timeout)
47
+ return { ...result, reason: 'CHECK_FAILED' };
48
+ const json = await new Promise((resolve, reject) => {
49
+ const child = execFile('/usr/bin/plutil', ['-convert', 'json', '-o', '-', '--', '-'], { timeout: budget, killSignal: 'SIGKILL', maxBuffer: 16 * 1024 * 1024 }, (error, stdout) => error ? reject(error) : resolve(stdout));
50
+ child.stdin?.on('error', reject);
51
+ child.stdin?.end(output.stdout);
52
+ });
53
+ const apps = JSON.parse(json);
54
+ if (!apps || typeof apps !== 'object' || Array.isArray(apps))
55
+ return { ...result, reason: 'UNRECOGNIZED_RESPONSE' };
56
+ if (Object.values(apps).some(value => !value || typeof value !== 'object' || Array.isArray(value)))
57
+ return { ...result, reason: 'UNRECOGNIZED_RESPONSE' };
58
+ status = Object.hasOwn(apps, context.appId) ? 'installed' : 'absent';
59
+ }
60
+ catch {
61
+ return { ...result, reason: 'UNRECOGNIZED_RESPONSE' };
62
+ }
63
+ }
64
+ const { reason: _reason, ...confirmed } = result;
65
+ return { ...confirmed, status };
66
+ }
67
+ async function confirmAndroidAbsence(command, context, timeout) {
68
+ const output = await execCmd(command, ['-s', context.deviceId, 'shell', 'pm', 'list', 'packages', context.appId], { timeout });
69
+ const result = { ...context, checkedAt: new Date().toISOString() };
70
+ if (output.exitCode !== 0)
71
+ return { ...result, reason: 'CHECK_FAILED' };
72
+ const lines = output.stdout.split('\n').map(line => line.trim()).filter(Boolean);
73
+ if (lines.some(line => !/^package:[A-Za-z0-9_.-]+$/.test(line)))
74
+ return { ...result, reason: 'UNRECOGNIZED_RESPONSE' };
75
+ const { reason: _reason, ...confirmed } = result;
76
+ return { ...confirmed, status: lines.includes(`package:${context.appId}`) ? 'installed' : 'absent' };
77
+ }
@@ -0,0 +1,43 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { randomUUID } from 'node:crypto';
3
+ const measurement = new AsyncLocalStorage();
4
+ // Private benchmark diagnostics: no arguments, device IDs or response contents.
5
+ export function recordBenchmarkSpan(stage, duration) {
6
+ const current = measurement.getStore();
7
+ if (!current)
8
+ return;
9
+ const end = performance.now() - current.started;
10
+ current.spans.push({ stage, start_ms: Math.max(0, end - duration), end_ms: end });
11
+ }
12
+ export async function benchmarkSubprocess(operation) {
13
+ return benchmarkStage('subprocess_ms', operation);
14
+ }
15
+ export async function benchmarkStage(stage, operation) {
16
+ if (!measurement.getStore())
17
+ return operation();
18
+ const started = performance.now();
19
+ try {
20
+ return await operation();
21
+ }
22
+ finally {
23
+ recordBenchmarkSpan(stage, performance.now() - started);
24
+ }
25
+ }
26
+ export async function withBenchmarkRequest(tool, operation) {
27
+ if (process.env.MOBILE_DEBUG_MCP_LATENCY_METRICS !== '1')
28
+ return operation();
29
+ const current = { id: randomUUID(), started: performance.now(), spans: [] };
30
+ return measurement.run(current, async () => {
31
+ let completed = false;
32
+ try {
33
+ const result = await operation();
34
+ completed = true;
35
+ return result;
36
+ }
37
+ finally {
38
+ process.stderr.write(`${JSON.stringify({ event: 'latency_metric', request_id: current.id,
39
+ tool, handler_completed: completed, elapsed_ms: performance.now() - current.started,
40
+ spans: current.spans })}\n`);
41
+ }
42
+ });
43
+ }
@@ -1,7 +1,11 @@
1
1
  import { checkDebuggerGuard } from '../debugger/guard.js';
2
2
  import { remainingBudget } from './operation-budget.js';
3
3
  import { spawn } from 'child_process';
4
+ import { benchmarkSubprocess } from './benchmark-metrics.js';
4
5
  export async function execCmd(cmd, args, opts = {}) {
6
+ return benchmarkSubprocess(() => execute(cmd, args, opts));
7
+ }
8
+ async function execute(cmd, args, opts) {
5
9
  const { env, cwd, shell } = opts;
6
10
  const timeout = remainingBudget(opts.timeout ?? 0);
7
11
  return new Promise((resolve, reject) => {
@@ -1,3 +1,5 @@
1
+ import { installationFields, probeInstallation } from '../app-installation.js';
2
+ import { checkDebuggerGuard } from '../../debugger/guard.js';
1
3
  import { remainingBudget } from '../operation-budget.js';
2
4
  import { execFile, spawn, execSync, spawnSync } from "child_process";
3
5
  import { promises as fsPromises } from 'fs';
@@ -94,6 +96,7 @@ export function execCommand(args, deviceId = "booted") {
94
96
  return new Promise((resolve, reject) => {
95
97
  // Use spawn for better stream control and consistency with Android implementation
96
98
  const budgetMs = remainingBudget();
99
+ checkDebuggerGuard();
97
100
  const child = spawn(getXcrunCmd(), args);
98
101
  let stdout = '';
99
102
  let stderr = '';
@@ -135,6 +138,7 @@ export function execCommandWithDiagnostics(args, deviceId = "booted") {
135
138
  const DEFAULT_XCRUN_LOG_TIMEOUT = parseInt(process.env.MCP_XCRUN_LOG_TIMEOUT || '', 10) || 30000;
136
139
  const DEFAULT_XCRUN_CMD_TIMEOUT = parseInt(process.env.MCP_XCRUN_TIMEOUT || '', 10) || 60000;
137
140
  const timeoutMs = budgetMs || (args.includes('log') ? DEFAULT_XCRUN_LOG_TIMEOUT : DEFAULT_XCRUN_CMD_TIMEOUT);
141
+ checkDebuggerGuard();
138
142
  const res = spawnSync(getXcrunCmd(), args, { encoding: 'utf8', timeout: timeoutMs });
139
143
  const runResult = {
140
144
  exitCode: typeof res.status === 'number' ? res.status : null,
@@ -276,17 +280,9 @@ export async function listIOSDevices(appId) {
276
280
  simulator: true,
277
281
  booted: bootedSet.has(device.udid)
278
282
  };
279
- if (appId) {
280
- // check if installed
281
- const p = execCommand(['simctl', 'get_app_container', device.udid, appId, 'data'], device.udid)
282
- .then(() => { info.appInstalled = true; })
283
- .catch(() => { info.appInstalled = false; })
284
- .then(() => { out.push(info); });
285
- checks.push(p);
286
- }
287
- else {
288
- out.push(info);
289
- }
283
+ out.push(info);
284
+ checks.push(probeInstallation(getXcrunCmd(), device.udid, appId, 'ios-simulator', info.booted === true)
285
+ .then(check => { Object.assign(info, installationFields(check)); }));
290
286
  }
291
287
  }
292
288
  Promise.all(checks).then(() => resolve(out)).catch(() => resolve(out));
@@ -38,6 +38,7 @@ export async function listDevices(platform, appId) {
38
38
  return iosDeviceLister(appId);
39
39
  }
40
40
  const deviceContext = new AsyncLocalStorage();
41
+ export function currentResolvedDevice() { return deviceContext.getStore(); }
41
42
  export function withResolvedDevice(device, operation) {
42
43
  bindEvidenceDevice(device);
43
44
  return deviceContext.run(device, operation);
@@ -75,7 +76,7 @@ function selectDevice(devices, platform, appId, prefer) {
75
76
  if (prefer === "emulator")
76
77
  candidates = candidates.filter(d => d.simulator);
77
78
  if (appId) {
78
- const installed = candidates.filter(d => d.appInstalled);
79
+ const installed = candidates.filter(d => d.appInstallation ? d.appInstallation.status === 'installed' && d.appInstallation.appId === appId && d.appInstallation.deviceId === d.id : d.appInstalled === true);
79
80
  if (installed.length > 0)
80
81
  candidates = installed;
81
82
  }
@@ -101,7 +102,7 @@ function selectDevice(devices, platform, appId, prefer) {
101
102
  if (candidates.length > 1 && parseNumericVersion(candidates[0].osVersion) > parseNumericVersion(candidates[1].osVersion)) {
102
103
  return candidates[0];
103
104
  }
104
- const list = candidates.map(d => ({ id: d.id, platform: d.platform, osVersion: d.osVersion, model: d.model, simulator: d.simulator, appInstalled: d.appInstalled }));
105
+ const list = candidates.map(d => ({ id: d.id, platform: d.platform, osVersion: d.osVersion, model: d.model, simulator: d.simulator, appInstalled: d.appInstalled, appInstallation: d.appInstallation }));
105
106
  const err = Object.assign(new Error(`Multiple matching devices found: ${JSON.stringify(list, null, 2)}`), { devices: list });
106
107
  throw err;
107
108
  }
@@ -1,7 +1,9 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { recordBenchmarkSpan } from './benchmark-metrics.js';
2
3
  const timings = new AsyncLocalStorage();
3
4
  export function withTiming(values, operation) { return timings.run(values, operation); }
4
5
  export function recordTiming(component, milliseconds) {
6
+ recordBenchmarkSpan(component, milliseconds);
5
7
  const values = timings.getStore();
6
8
  if (values)
7
9
  values[component] = (values[component] ?? 0) + milliseconds;
@@ -12,8 +14,6 @@ export async function measure(component, operation) {
12
14
  return await operation();
13
15
  }
14
16
  finally {
15
- const values = timings.getStore();
16
- if (values)
17
- values[component] = (values[component] ?? 0) + performance.now() - start;
17
+ recordTiming(component, performance.now() - start);
18
18
  }
19
19
  }
package/docs/CHANGELOG.md CHANGED
@@ -8,7 +8,54 @@ and [tool reference](tools/TOOLS.md) for the current checkout.
8
8
 
9
9
  All notable changes to the **Mobile Debug MCP** project will be documented in this file.
10
10
 
11
- ## Unreleased
11
+ ## [0.37.0]
12
+
13
+ - Updated the agent plugin to `0.0.7`, pinned to MCP `0.37.0`.
14
+
15
+ - Android debug snapshots use one fresh UI tree for the returned hierarchy and
16
+ screen fingerprint, removing a duplicate acquisition.
17
+
18
+ - Add an end-to-end latency benchmark with repeated mode comparisons, visible
19
+ failed attempts, source/build identity, and opt-in timing-only request and
20
+ subprocess diagnostics. Public tool contracts and verification are unchanged.
21
+
22
+ - Compact interaction/journey results now summarize observations, readiness, conditions and state deltas within explicit byte budgets. Detailed original results are available through session-local `get_interaction_diagnostics` references without replaying input. Debug output and response-mode defaults are unchanged.
23
+
24
+ - Report device-scoped app installation as installed, absent or unknown. Publish
25
+ `list_devices.appId`; omit legacy installation booleans for inconclusive or
26
+ multi-device system checks instead of reporting false absence.
27
+
28
+ - Clean up discarded iOS trace files after a recorder-stop failure when process
29
+ exit is confirmed, while retaining the failure and protecting live recordings.
30
+
31
+ - Add timestamped device-readiness evidence to launch/interaction results and
32
+ readiness waits, preserving dispatch outcomes and compact/journey evidence.
33
+ Android power/keyguard blockers prevent readiness success; iOS reports
34
+ per-signal unknown/unsupported evidence. No automatic wake or unlock.
35
+
36
+ ## [0.36.0]
37
+
38
+ - Updated the agent plugin to `0.0.6`, pinned to MCP `0.36.0`.
39
+
40
+ - Add an optional iOS simulator LLDB backend to the existing debugger tools,
41
+ including exact source-file breakpoints, bounded static inspection, stepping,
42
+ platform-scoped pause guards and owned cleanup. A separate Swift concurrency
43
+ fixture checks inspection around await, cancellation and recovery; verification
44
+ coverage is recorded in specification 032. Physical iOS devices remain unsupported.
45
+
46
+ - Preserve iOS trace capture through isolated monitoring command failures; require
47
+ three consecutive non-definitive failures before discarding, while identity and
48
+ storage failures remain immediate and capture expiry still reconciles.
49
+
50
+ - Add optional iOS simulator Time Profiler capture through Instruments, with explicit
51
+ device/app targeting, recording-start notification, local `.trace` validation,
52
+ journey evidence linkage and owned cleanup. Physical devices and other templates
53
+ remain unsupported in the initial matrix. Android tracing retains its preset.
54
+
55
+
56
+ - Added plugin `0.0.5` Antigravity compatibility and a standalone distribution
57
+ export. Generated a Cursor-native package for its supported path variables,
58
+ and checked/refined installation instructions for all four clients.
12
59
 
13
60
  ## [0.35.0]
14
61
 
@@ -0,0 +1,39 @@
1
+ # Agent plugin installation verification
2
+
3
+ Checked 2026-11-02 for plugin 0.0.5 and published MCP 0.35.0. This record separates
4
+ command/schema checks from installation and live device evidence.
5
+
6
+ | Client | Evidence | Limit |
7
+ | --- | --- | --- |
8
+ | Codex CLI 0.166.0-alpha.5.1 | Marketplace-add and plugin-add syntax confirmed with installed CLI help and official docs; catalog paths checked by repository verification | No new Codex installation or agent journey performed |
9
+ | Claude Code 2.1.285 | Native strict validation passes for the marketplace, compatibility manifest and complete plugin folder; shell command syntax confirmed | No new Claude Code installation or agent journey performed |
10
+ | Cursor | Native package and catalog generated from the canonical skill; uses documented CURSOR_PLUGIN_ROOT; local-copy instructions preserve hidden files | Host loading in Cursor is unrun |
11
+ | Antigravity CLI 1.4.12 | Standalone export passes strict validation with zero warnings; local install discovers one skill and one MCP server | An agent print-mode attempt was denied by plan-mode MCP policy; a narrowly permitted read-only attempt timed out, so no agent tool-call success is claimed |
12
+
13
+ The Antigravity export contains native `plugin.json`, `mcp_config.json`, README,
14
+ license, logo and the shared skill. It excludes foreign client manifests and
15
+ Finder metadata. Export destinations must be new directories. Regression coverage
16
+ checks inventory, canonical skill/license bytes and refusal to overwrite existing
17
+ files. Cursor's generated skill/license are checked against their canonical
18
+ sources by `npm run check:agent-plugin`.
19
+
20
+ A separate SDK test launched the exact exported npx command from an expanded
21
+ PLUGIN_DATA working directory. It connected to published MCP 0.35.0, discovered
22
+ 52 tools, selected emulator-5554 (Android 16/API 36), launched Settings and asserted
23
+ a fresh UI tree contained a visible Settings-owned resource. **PASS**; this is an
24
+ exported-config/device check, not an Antigravity agent interaction. Log:
25
+ `/tmp/antigravity-published-mcp-check.log`. The emulator remains running.
26
+
27
+ `npm run verify` **PASS**, all 91 unit files, including the exporter regression,
28
+ lint/quality/architecture, build and plugin consistency. Native Antigravity strict
29
+ validation and Claude strict validation also **PASS**. Markdown links and diff
30
+ whitespace were checked. No production server code or dependencies changed.
31
+ GitHub/local distribution does not require the optional Marketplace intake form;
32
+ no application was submitted.
33
+
34
+ ## Primary documentation
35
+
36
+ - [OpenAI plugin packaging](https://developers.openai.com/plugins/build/plugins)
37
+ - [Claude Code installation](https://code.claude.com/docs/en/discover-plugins)
38
+ - [Cursor plugins and local testing](https://cursor.com/docs/plugins)
39
+ - [Antigravity plugin building and validation](https://antigravity.google/docs/build-plugins)
@@ -14,5 +14,6 @@ iOS latency.
14
14
  | --- | --- | --- | --- |
15
15
  | Interaction latency | [Run the benchmark](interaction-latency/README.md) | [Android measurements](interaction-latency/final.md), [iOS interaction validation](interaction-latency/ios-validation.md) | [018](../specs/018-interaction-latency-and-batched-journeys/spec.md) |
16
16
  | UI-tree observation | [Run the paired benchmark](ui-tree-performance/README.md) | [Comparison and limitations](ui-tree-performance/comparison.md) | [019](../specs/019-ui-tree-observation-performance/spec.md) |
17
+ | End-to-end interaction latency | [Run the benchmarks](end-to-end-latency/README.md) | [Provisional Android results and iOS blocker](end-to-end-latency/investigation.md) | [036](../specs/036-end-to-end-interaction-latency/spec.md) |
17
18
 
18
19
  Fixture instructions: [latency journey app](../../test/fixtures/latency-journey-app/README.md).
@@ -0,0 +1,98 @@
1
+ # End-to-end interaction latency
2
+
3
+ [Benchmark index](../README.md) · [Specification](../../specs/036-end-to-end-interaction-latency/spec.md)
4
+
5
+ This investigation reuses the Spec 018 Android fixture and Spec 019 tree metrics.
6
+ It does not replace their historical results or optimization acceptance gates.
7
+ Requires Node >=22, Java supporting `--release 8`, Android platform 36 and
8
+ build-tools 36.0.0. Start an Android emulator, then run from the repository root:
9
+
10
+ ```sh
11
+ npm run verify
12
+ LATENCY_DEVICE_ID=emulator-5554 MCP_ADB_TIMEOUT=30000 npx tsx test/device/automated/latency/end-to-end.ts --smoke
13
+ LATENCY_DEVICE_ID=emulator-5554 MCP_ADB_TIMEOUT=30000 npx tsx test/device/automated/latency/end-to-end.ts
14
+ ```
15
+
16
+ Only explicitly named Android emulators are accepted; physical phones are not
17
+ selected. Existing device resolution can enumerate other attached devices;
18
+ the report records discovery counts without serials. No display configuration is changed. The dedicated fixture is built,
19
+ installed, and reset; an installation created by this run is removed on exit.
20
+ A pre-existing fixture installation is left installed, with reset fixture data.
21
+ The harness owns isolated POSIX server process groups, closes them on exit and
22
+ interrupt, and verifies their termination. An unsafe timeout stops the run;
23
+ semantic failures are retained before the next explicitly recorded reset.
24
+
25
+ The smoke uses one attempt per case and writes `smoke.json`/`smoke.md`; it is not
26
+ performance evidence. The full run writes `baseline.json`/`baseline.md` after
27
+ each attempt, preserving partial results on failure/interruption. It performs
28
+ three warm-ups and ten measured attempts per case, reversing mode order every
29
+ round. Do not run other device workloads concurrently.
30
+
31
+ `baseline-attempt-1.json`/`.md` retain the first failed warm-up run: the emulator
32
+ disappeared, the next reset failed, and fixture cleanup could not be confirmed.
33
+ No measured samples were produced. Both owned server groups exited. The retry
34
+ restores the same AVD headlessly using `emulator -avd Pixel_9_Pro -port 5554
35
+ -no-window -no-snapshot-save`; it does not wipe data. This startup configuration
36
+ is distinct from the earlier development smoke and failed attempt.
37
+
38
+ Cases compare the original standalone handle flow and four full/light ×
39
+ debug/compact journeys, retaining all six destination assertions. Screenshot,
40
+ UI tree and screenshot/log-free snapshot calls are separate cases on the known
41
+ fixture home screen, with instrumentation on and off. Light/compact has existing
42
+ assertion-based observation sharing; mode differences can therefore include
43
+ acquisition counts as well as response construction costs. They are not a pure
44
+ serialization microbenchmark.
45
+
46
+ Raw reports include source/build/harness/fixture hashes, dirty paths, environment,
47
+ request order, byte counts, timing-only events, setup/startup durations and
48
+ cleanup. Device serials, raw responses and unrestricted stderr are not retained.
49
+ Source/build/harness changes during measurement invalidate the run. Externally
50
+ run builds and validation suites are separate from MCP request time. This fixture
51
+ uses offline SDK tools rather than Gradle. Device application work inside requests
52
+ is included; it is not established as pure MCP overhead.
53
+
54
+ Percentiles use nearest rank and successful attempts only; failures remain in
55
+ raw records and fail the run. Warm-up failures are counted separately. Ten samples
56
+ provide a preliminary p95, not a robust tail estimate. Inclusive component spans
57
+ overlap and must not be summed. Handler residual subtracts their interval union;
58
+ client-minus-handler includes scheduling, transport and uninstrumented work.
59
+ Existing serialization spans cover selected response construction, not all SDK
60
+ wire serialization. Tree event stages expose acquisition, retry and parse costs.
61
+ Subprocess spans cover `execCmd`; direct platform spawns can remain in residuals.
62
+
63
+ Compare observation `on`/`off` results to assess probe overhead; these include
64
+ normal device variability and are not automatically a causal overhead estimate.
65
+ `missing_request_metrics` is expected for `off` cases only. Instrumentation uses
66
+ `MOBILE_DEBUG_MCP_LATENCY_METRICS=1` and the existing
67
+ `MOBILE_DEBUG_MCP_UI_TREE_METRICS=1` only in instrumented benchmark servers.
68
+ Normal MCP contracts and diagnostic output are unchanged.
69
+
70
+ For standalone iOS observations, use an isolated booted simulator with working
71
+ idb discovery and English Settings. The runner confirms the Settings root
72
+ before each timed request and records preparation time separately.
73
+
74
+ ```sh
75
+ IOS_LATENCY_DEVICE_ID=YOUR_SIMULATOR_ID npx tsx test/device/automated/latency-ios/benchmark.ts --smoke
76
+ IOS_LATENCY_DEVICE_ID=YOUR_SIMULATOR_ID npx tsx test/device/automated/latency-ios/benchmark.ts
77
+ ```
78
+
79
+ Equivalent iOS fixture journeys depend on [#89](https://github.com/clivejefferies/mobile-debug-tools/issues/89).
80
+ See the [investigation report](investigation.md) for the Android measurements
81
+ and the observed iOS setup blocker.
82
+
83
+ For a focused [Android snapshot comparison](android-snapshot-pair.md), build
84
+ the pre-change checkout and preserve its emitted `dist` directory and source
85
+ hash. The preserved build needs its ESM `package.json` and the same installed
86
+ dependencies. Build the candidate in the current checkout, then run:
87
+
88
+ ```sh
89
+ LATENCY_DEVICE_ID=emulator-5554 \
90
+ SNAPSHOT_BASELINE_SERVER=/absolute/path/to/baseline/dist/server.js \
91
+ SNAPSHOT_BASELINE_SOURCE_SHA=BASELINE_SOURCE_SHA256 \
92
+ MCP_ADB_TIMEOUT=30000 \
93
+ node --import tsx test/device/automated/latency/snapshot-pair.ts
94
+ ```
95
+
96
+ Use the `treeHash` helper in `end-to-end-environment.ts` to hash baseline `src`
97
+ before editing it. This command writes `android-snapshot-pair.json` with the
98
+ attempts and acceptance result.