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.
- package/.cursor-plugin/marketplace.json +1 -1
- package/.github/workflows/ci.yml +1 -1
- package/README.md +96 -24
- package/agent-plugin/.antigravity-plugin/README.md +48 -0
- package/agent-plugin/.antigravity-plugin/plugin.json +18 -0
- package/agent-plugin/.claude-plugin/plugin.json +2 -2
- package/agent-plugin/.codex-plugin/plugin.json +1 -1
- package/agent-plugin/.cursor-plugin/plugin.json +2 -2
- package/agent-plugin/.mcp.json +1 -1
- package/agent-plugin/README.md +37 -8
- package/agent-plugin/cursor/.cursor-plugin/plugin.json +23 -0
- package/agent-plugin/cursor/LICENSE +201 -0
- package/agent-plugin/cursor/README.md +9 -0
- package/agent-plugin/cursor/skills/mobile-debug-mcp/SKILL.md +261 -0
- package/agent-plugin/mcp.json +1 -1
- package/agent-plugin/mcp_config.json +13 -0
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +46 -18
- package/dist/contracts/device-readiness.js +1 -0
- package/dist/debugger/guard.js +4 -4
- package/dist/debugger/ios-worker.js +62 -0
- package/dist/debugger/ios.js +57 -0
- package/dist/debugger/sessions.js +33 -16
- package/dist/debugger/worker.js +6 -4
- package/dist/interact/index.js +12 -2
- package/dist/interact/waits/ui-change-wait.js +9 -2
- package/dist/interact/waits/ui-wait.js +7 -3
- package/dist/manage/index.js +3 -0
- package/dist/manage/ios.js +2 -0
- package/dist/observe/device-readiness.js +116 -0
- package/dist/observe/evidence.js +31 -0
- package/dist/observe/ios-trace/artifact.js +63 -0
- package/dist/observe/ios-trace/capabilities.js +86 -0
- package/dist/observe/ios-trace/native.js +93 -0
- package/dist/observe/ios-trace/notification.js +35 -0
- package/dist/observe/ios-trace/process.js +98 -0
- package/dist/observe/ios-trace/sessions.js +246 -0
- package/dist/observe/readiness-service.js +22 -10
- package/dist/observe/readiness.js +34 -4
- package/dist/observe/screenshot-encoding.js +4 -0
- package/dist/observe/snapshot-collection.js +7 -2
- package/dist/observe/system-trace-error.js +7 -0
- package/dist/observe/system-trace-router.js +81 -0
- package/dist/observe/system-trace.js +4 -8
- package/dist/server/definitions/manage.js +3 -2
- package/dist/server/handlers/action-execution.js +23 -14
- package/dist/server/handlers/compact-interaction.js +106 -0
- package/dist/server/handlers/compact-state.js +47 -0
- package/dist/server/handlers/debugger-handlers.js +27 -8
- package/dist/server/handlers/diagnostic.js +12 -4
- package/dist/server/handlers/input.js +2 -0
- package/dist/server/handlers/interact.js +2 -0
- package/dist/server/handlers/interaction-diagnostics.js +62 -0
- package/dist/server/handlers/journey.js +18 -20
- package/dist/server/handlers/observe.js +2 -1
- package/dist/server/handlers/tool-handlers.js +28 -14
- package/dist/server/schemas/debugger-schema.js +10 -7
- package/dist/server/schemas/interaction-diagnostics.js +9 -0
- package/dist/server/schemas/system-trace-schema.js +14 -9
- package/dist/server/tool-definitions.js +9 -4
- package/dist/server-core.js +5 -2
- package/dist/server.js +5 -5
- package/dist/system/android.js +19 -15
- package/dist/system/index.js +2 -1
- package/dist/utils/android/utils.js +7 -16
- package/dist/utils/app-installation.js +77 -0
- package/dist/utils/benchmark-metrics.js +43 -0
- package/dist/utils/exec.js +4 -0
- package/dist/utils/ios/utils.js +7 -11
- package/dist/utils/resolve-device.js +3 -2
- package/dist/utils/timing.js +3 -3
- package/docs/CHANGELOG.md +48 -1
- package/docs/agent-plugin-installation-verification.md +39 -0
- package/docs/benchmarks/README.md +1 -0
- package/docs/benchmarks/end-to-end-latency/README.md +98 -0
- package/docs/benchmarks/end-to-end-latency/android-snapshot-pair.json +6532 -0
- package/docs/benchmarks/end-to-end-latency/android-snapshot-pair.md +30 -0
- package/docs/benchmarks/end-to-end-latency/baseline-attempt-1.json +15901 -0
- package/docs/benchmarks/end-to-end-latency/baseline-attempt-1.md +14 -0
- package/docs/benchmarks/end-to-end-latency/baseline.json +233843 -0
- package/docs/benchmarks/end-to-end-latency/baseline.md +21 -0
- package/docs/benchmarks/end-to-end-latency/investigation.md +128 -0
- package/docs/benchmarks/end-to-end-latency/ios-smoke.json +84 -0
- package/docs/benchmarks/end-to-end-latency/ios-smoke.md +9 -0
- package/docs/benchmarks/end-to-end-latency/smoke.json +19601 -0
- package/docs/benchmarks/end-to-end-latency/smoke.md +21 -0
- package/docs/code-structure.md +3 -1
- package/docs/specs/031-ios-system-traces/intent.md +38 -0
- package/docs/specs/031-ios-system-traces/investigation.md +92 -0
- package/docs/specs/031-ios-system-traces/late-run-failure-evidence.json +32 -0
- package/docs/specs/031-ios-system-traces/plan.md +166 -0
- package/docs/specs/031-ios-system-traces/pr102-monitor-evidence.json +22 -0
- package/docs/specs/031-ios-system-traces/simulator-evidence.json +47 -0
- package/docs/specs/031-ios-system-traces/spec.md +135 -0
- package/docs/specs/032-ios-runtime-debugger/evidence.json +176 -0
- package/docs/specs/032-ios-runtime-debugger/intent.md +38 -0
- package/docs/specs/032-ios-runtime-debugger/plan.md +95 -0
- package/docs/specs/032-ios-runtime-debugger/report.md +205 -0
- package/docs/specs/032-ios-runtime-debugger/spec.md +153 -0
- package/docs/specs/033-device-readiness-blockers/android-evidence.json +437 -0
- package/docs/specs/033-device-readiness-blockers/android-physical-evidence.json +28 -0
- package/docs/specs/033-device-readiness-blockers/android-physical-power-evidence.json +29 -0
- package/docs/specs/033-device-readiness-blockers/intent.md +41 -0
- package/docs/specs/033-device-readiness-blockers/ios-investigation.md +55 -0
- package/docs/specs/033-device-readiness-blockers/ios-probe-evidence.json +24 -0
- package/docs/specs/033-device-readiness-blockers/ios-tool-evidence.json +73 -0
- package/docs/specs/033-device-readiness-blockers/plan.md +249 -0
- package/docs/specs/033-device-readiness-blockers/spec.md +134 -0
- package/docs/specs/034-app-installation-status/intent.md +41 -0
- package/docs/specs/034-app-installation-status/native-evidence.json +654 -0
- package/docs/specs/034-app-installation-status/plan.md +73 -0
- package/docs/specs/034-app-installation-status/spec.md +114 -0
- package/docs/specs/035-compact-interaction-results/baseline.md +45 -0
- package/docs/specs/035-compact-interaction-results/intent.md +38 -0
- package/docs/specs/035-compact-interaction-results/plan.md +43 -0
- package/docs/specs/035-compact-interaction-results/spec.md +118 -0
- package/docs/specs/035-compact-interaction-results/verification.md +111 -0
- package/docs/specs/036-end-to-end-interaction-latency/android-snapshot-amendment.md +60 -0
- package/docs/specs/036-end-to-end-interaction-latency/android-snapshot-plan.md +32 -0
- package/docs/specs/036-end-to-end-interaction-latency/intent.md +37 -0
- package/docs/specs/036-end-to-end-interaction-latency/plan.md +59 -0
- package/docs/specs/036-end-to-end-interaction-latency/spec.md +126 -0
- package/docs/specs/036-end-to-end-interaction-latency/verification.md +87 -0
- package/docs/tools/TOOLS.md +3 -2
- package/docs/tools/compact-interactions.md +56 -0
- package/docs/tools/correlated-evidence.md +6 -0
- package/docs/tools/debugger.md +45 -5
- package/docs/tools/interact.md +17 -8
- package/docs/tools/manage.md +10 -2
- package/docs/tools/observe.md +42 -2
- package/docs/tools/system-traces.md +81 -14
- package/docs/tools/system.md +6 -0
- package/package.json +2 -1
- package/scripts/agent-plugin.mjs +55 -5
- package/src/contracts/device-readiness.ts +8 -0
- package/src/debugger/android.ts +1 -1
- package/src/debugger/guard.ts +5 -5
- package/src/debugger/ios-worker.ts +49 -0
- package/src/debugger/ios.ts +45 -0
- package/src/debugger/lldb/breakpoints.py +51 -0
- package/src/debugger/lldb/inspection.py +109 -0
- package/src/debugger/lldb/worker.py +203 -0
- package/src/debugger/sessions.ts +24 -12
- package/src/debugger/worker.ts +7 -4
- package/src/interact/index.ts +13 -2
- package/src/interact/waits/ui-change-wait.ts +7 -2
- package/src/interact/waits/ui-wait.ts +6 -3
- package/src/manage/index.ts +2 -0
- package/src/manage/ios.ts +2 -0
- package/src/observe/device-readiness.ts +96 -0
- package/src/observe/evidence.ts +30 -1
- package/src/observe/ios-trace/artifact.ts +55 -0
- package/src/observe/ios-trace/capabilities.ts +74 -0
- package/src/observe/ios-trace/native.ts +72 -0
- package/src/observe/ios-trace/notification.ts +30 -0
- package/src/observe/ios-trace/process.ts +82 -0
- package/src/observe/ios-trace/sessions.ts +206 -0
- package/src/observe/readiness-service.ts +22 -10
- package/src/observe/readiness.ts +37 -4
- package/src/observe/screenshot-encoding.ts +4 -0
- package/src/observe/snapshot-collection.ts +7 -2
- package/src/observe/system-trace-error.ts +3 -0
- package/src/observe/system-trace-router.ts +61 -0
- package/src/observe/system-trace.ts +4 -4
- package/src/server/common.ts +2 -2
- package/src/server/definitions/manage.ts +3 -2
- package/src/server/handlers/action-execution.ts +22 -14
- package/src/server/handlers/compact-interaction.ts +91 -0
- package/src/server/handlers/compact-state.ts +43 -0
- package/src/server/handlers/debugger-handlers.ts +28 -9
- package/src/server/handlers/diagnostic.ts +11 -4
- package/src/server/handlers/input.ts +2 -0
- package/src/server/handlers/interact.ts +2 -0
- package/src/server/handlers/interaction-diagnostics.ts +50 -0
- package/src/server/handlers/journey.ts +15 -17
- package/src/server/handlers/observe.ts +2 -1
- package/src/server/handlers/payload.ts +2 -1
- package/src/server/handlers/tool-handlers.ts +29 -14
- package/src/server/schemas/debugger-schema.ts +10 -7
- package/src/server/schemas/interaction-diagnostics.ts +10 -0
- package/src/server/schemas/system-trace-schema.ts +15 -9
- package/src/server/tool-definitions.ts +8 -4
- package/src/server-core.ts +5 -2
- package/src/server.ts +4 -4
- package/src/system/android.ts +18 -11
- package/src/system/index.ts +2 -1
- package/src/types.ts +2 -0
- package/src/utils/android/utils.ts +8 -10
- package/src/utils/app-installation.ts +73 -0
- package/src/utils/benchmark-metrics.ts +42 -0
- package/src/utils/exec.ts +5 -0
- package/src/utils/ios/utils.ts +7 -10
- package/src/utils/resolve-device.ts +4 -2
- package/src/utils/timing.ts +3 -2
- package/test/device/automated/debugger/runtime.android.test.ts +5 -0
- package/test/device/automated/debugger/runtime.ios.test.ts +227 -0
- package/test/device/automated/interact/compact-results.smoke.ts +58 -0
- package/test/device/automated/latency/end-to-end-environment.ts +61 -0
- package/test/device/automated/latency/end-to-end-report.ts +90 -0
- package/test/device/automated/latency/end-to-end-session.ts +98 -0
- package/test/device/automated/latency/end-to-end.ts +149 -0
- package/test/device/automated/latency/owned-transport.ts +81 -0
- package/test/device/automated/latency/snapshot-pair.ts +143 -0
- package/test/device/automated/latency-ios/benchmark.ts +126 -0
- package/test/device/automated/observe/correlated-evidence.smoke.ts +7 -5
- package/test/device/automated/observe/device-readiness-cleanup.ts +14 -0
- package/test/device/automated/observe/device-readiness.android.smoke.ts +78 -0
- package/test/device/automated/observe/ios-system-trace.smoke.ts +119 -0
- package/test/device/automated/observe/system-trace.smoke.ts +2 -2
- package/test/fixtures/contracts/tool-definitions.json +243 -47
- package/test/fixtures/ios-debugger-app/Main.swift +95 -0
- package/test/fixtures/ios-debugger-app/README.md +27 -0
- package/test/fixtures/ios-debugger-app/build.ts +21 -0
- package/test/fixtures/latency-journey-app/build.ts +3 -3
- package/test/helpers/ios-trace-shutdown.ts +29 -0
- package/test/unit/debugger/handle-guards.test.ts +89 -0
- package/test/unit/debugger/ios-preparation.test.ts +57 -0
- package/test/unit/debugger/ios.test.ts +104 -0
- package/test/unit/debugger/lldb-worker.py +166 -0
- package/test/unit/debugger/lldb-worker.test.ts +10 -0
- package/test/unit/interact/ui-change-readiness-interruption.test.ts +37 -0
- package/test/unit/observe/capture_debug_snapshot.test.ts +66 -12
- package/test/unit/observe/device-readiness-cleanup.test.ts +24 -0
- package/test/unit/observe/device-readiness.test.ts +97 -0
- package/test/unit/observe/ios-trace-native.test.ts +90 -0
- package/test/unit/observe/ios-trace-sessions.test.ts +154 -0
- package/test/unit/observe/readiness-retained-blockers.test.ts +40 -0
- package/test/unit/observe/readiness.test.ts +1 -1
- package/test/unit/observe/system-trace-router.test.ts +49 -0
- package/test/unit/quality/agent-plugin-export.test.ts +38 -0
- package/test/unit/server/compact-interaction.test.ts +87 -0
- package/test/unit/server/compact-journey-evidence.test.ts +40 -0
- package/test/unit/server/compact-review-regressions.test.ts +78 -0
- package/test/unit/server/conditional_actions.test.ts +7 -3
- package/test/unit/server/correlated-evidence.test.ts +18 -6
- package/test/unit/server/end-to-end-report.test.ts +46 -0
- package/test/unit/server/input-device-readiness.test.ts +55 -0
- package/test/unit/server/interaction-diagnostics.test.ts +45 -0
- package/test/unit/server/owned-benchmark-transport.test.ts +32 -0
- package/test/unit/server/readiness-retained-deadline.test.ts +32 -0
- package/test/unit/server/readiness.test.ts +24 -1
- package/test/unit/server/run_journey.test.ts +2 -1
- package/test/unit/server/selector_tap_target.test.ts +2 -1
- package/test/unit/server/system-trace.test.ts +18 -7
- package/test/unit/utils/app-installation.test.ts +78 -0
- package/test/unit/utils/benchmark-metrics.test.ts +36 -0
- 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
|
+
}
|
package/dist/utils/exec.js
CHANGED
|
@@ -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) => {
|
package/dist/utils/ios/utils.js
CHANGED
|
@@ -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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
}
|
package/dist/utils/timing.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
##
|
|
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.
|