mobile-debug-mcp 0.32.4 → 0.34.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/.agents/plugins/marketplace.json +20 -0
- package/.claude-plugin/marketplace.json +14 -0
- package/.cursor-plugin/marketplace.json +16 -0
- package/.github/copilot-instructions.md +7 -0
- package/.github/workflows/ci.yml +21 -0
- package/AGENTS.md +21 -10
- package/README.md +69 -4
- package/agent-plugin/.claude-plugin/plugin.json +20 -0
- package/agent-plugin/.codex-plugin/plugin.json +34 -0
- package/agent-plugin/.cursor-plugin/plugin.json +23 -0
- package/agent-plugin/.mcp.json +14 -0
- package/agent-plugin/README.md +9 -3
- package/agent-plugin/assets/mobile-debug-tools.svg +28 -0
- package/agent-plugin/mcp.json +1 -1
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +113 -1
- package/dist/debugger/android.js +78 -0
- package/dist/debugger/guard.js +30 -0
- package/dist/debugger/sessions.js +262 -0
- package/dist/debugger/worker.js +165 -0
- package/dist/interact/android.js +10 -4
- package/dist/interact/conditional-action.js +68 -0
- package/dist/interact/index.js +70 -14
- package/dist/interact/ios.js +14 -8
- package/dist/manage/android.js +4 -3
- package/dist/manage/ios.js +3 -2
- package/dist/observe/android.js +25 -4
- package/dist/observe/evidence.js +280 -0
- package/dist/observe/index.js +33 -6
- package/dist/observe/ios.js +25 -6
- package/dist/observe/readiness-service.js +124 -0
- package/dist/observe/readiness.js +88 -0
- package/dist/observe/recording.js +165 -0
- package/dist/observe/snapshot-metadata.js +10 -2
- package/dist/observe/system-trace-android.js +135 -0
- package/dist/observe/system-trace.js +285 -0
- package/dist/server/common.js +9 -0
- package/dist/server/condition-schema.js +32 -0
- package/dist/server/debugger-handlers.js +38 -0
- package/dist/server/debugger-schema.js +42 -0
- package/dist/server/journey-schema.js +4 -2
- package/dist/server/readiness-schema.js +12 -0
- package/dist/server/system-trace-schema.js +17 -0
- package/dist/server/tool-definitions.js +51 -0
- package/dist/server/tool-handlers.js +294 -105
- package/dist/server-core.js +3 -3
- package/dist/server.js +34 -0
- package/dist/system/index.js +5 -4
- package/dist/utils/exec.js +2 -0
- package/dist/utils/ios/utils.js +4 -4
- package/dist/utils/java.js +14 -12
- package/dist/utils/operation-budget.js +26 -2
- package/dist/utils/resolve-device.js +9 -0
- package/docs/CHANGELOG.md +15 -0
- package/docs/specs/020-conditional-actions/intent.md +62 -0
- package/docs/specs/020-conditional-actions/plan.md +214 -0
- package/docs/specs/020-conditional-actions/spec.md +301 -0
- package/docs/specs/021-app-readiness-signals/intent.md +40 -0
- package/docs/specs/021-app-readiness-signals/plan.md +102 -0
- package/docs/specs/021-app-readiness-signals/spec.md +144 -0
- package/docs/specs/022-correlated-evidence/device-evidence.json +60 -0
- package/docs/specs/022-correlated-evidence/intent.md +37 -0
- package/docs/specs/022-correlated-evidence/modul8-evidence.json +1062 -0
- package/docs/specs/022-correlated-evidence/modul8-selector-fix-evidence.json +336 -0
- package/docs/specs/022-correlated-evidence/plan.md +171 -0
- package/docs/specs/022-correlated-evidence/spec.md +123 -0
- package/docs/specs/023-android-system-traces/device-evidence.json +38 -0
- package/docs/specs/023-android-system-traces/emulator-recheck.json +38 -0
- package/docs/specs/023-android-system-traces/intent.md +40 -0
- package/docs/specs/023-android-system-traces/plan.md +117 -0
- package/docs/specs/023-android-system-traces/spec.md +120 -0
- package/docs/specs/024-android-heap-dump-capture/intent.md +39 -0
- package/docs/specs/025-android-runtime-debugger/intent.md +40 -0
- package/docs/specs/025-android-runtime-debugger/plan.md +104 -0
- package/docs/specs/025-android-runtime-debugger/spec.md +182 -0
- package/docs/specs/process.md +213 -0
- package/docs/tools/TOOLS.md +5 -0
- package/docs/tools/debugger.md +104 -0
- package/docs/tools/evidence.md +82 -0
- package/docs/tools/interact.md +125 -2
- package/docs/tools/observe.md +70 -0
- package/docs/tools/system-traces.md +126 -0
- package/package.json +1 -1
- package/scripts/agent-plugin.mjs +68 -8
- package/src/debugger/android.ts +65 -0
- package/src/debugger/guard.ts +26 -0
- package/src/debugger/sessions.ts +168 -0
- package/src/debugger/worker.ts +122 -0
- package/src/interact/android.ts +10 -4
- package/src/interact/conditional-action.ts +75 -0
- package/src/interact/index.ts +57 -28
- package/src/interact/ios.ts +14 -8
- package/src/manage/android.ts +4 -3
- package/src/manage/ios.ts +3 -2
- package/src/observe/android.ts +33 -6
- package/src/observe/evidence.ts +262 -0
- package/src/observe/index.ts +32 -8
- package/src/observe/ios.ts +32 -8
- package/src/observe/readiness-service.ts +94 -0
- package/src/observe/readiness.ts +84 -0
- package/src/observe/snapshot-metadata.ts +12 -1
- package/src/observe/system-trace-android.ts +133 -0
- package/src/observe/system-trace.ts +218 -0
- package/src/server/common.ts +7 -0
- package/src/server/condition-schema.ts +32 -0
- package/src/server/debugger-handlers.ts +29 -0
- package/src/server/debugger-schema.ts +37 -0
- package/src/server/journey-schema.ts +4 -2
- package/src/server/readiness-schema.ts +12 -0
- package/src/server/system-trace-schema.ts +17 -0
- package/src/server/tool-definitions.ts +49 -0
- package/src/server/tool-handlers.ts +182 -21
- package/src/server-core.ts +3 -3
- package/src/server.ts +24 -0
- package/src/system/index.ts +7 -5
- package/src/types.ts +30 -1
- package/src/utils/exec.ts +2 -0
- package/src/utils/ios/utils.ts +4 -4
- package/src/utils/java.ts +13 -12
- package/src/utils/operation-budget.ts +24 -2
- package/src/utils/resolve-device.ts +10 -0
- package/test/device/automated/debugger/runtime.android.test.ts +203 -0
- package/test/device/automated/observe/correlated-evidence.smoke.ts +70 -0
- package/test/device/automated/observe/system-trace.smoke.ts +104 -0
- package/test/device/manual/interact/conditional_actions.manual.ts +95 -0
- package/test/device/manual/interact/modul8_selector_tap.manual.ts +63 -0
- package/test/device/manual/interact/readiness.manual.ts +99 -0
- package/test/fixtures/debugger-app/MainActivity.java +46 -0
- package/test/fixtures/debugger-app/NoLocals.java +7 -0
- package/test/fixtures/debugger-app/README.md +13 -0
- package/test/fixtures/debugger-app/build.ts +29 -0
- package/test/fixtures/readiness-app/MainActivity.java +23 -0
- package/test/fixtures/readiness-app/README.md +12 -0
- package/test/fixtures/readiness-app/build.ts +37 -0
- package/test/fixtures/readiness-app/main.m +27 -0
- package/test/helpers/perfetto.ts +59 -0
- package/test/unit/debugger/guards.test.ts +46 -0
- package/test/unit/debugger/native.test.ts +33 -0
- package/test/unit/debugger/sessions.test.ts +83 -0
- package/test/unit/observe/android_tree_acquisition.test.ts +2 -0
- package/test/unit/observe/capture_debug_snapshot.test.ts +42 -32
- package/test/unit/observe/compact_tree_error.test.ts +24 -0
- package/test/unit/observe/conditional_snapshots.test.ts +33 -0
- package/test/unit/observe/correlated-evidence.test.ts +128 -0
- package/test/unit/observe/ios_tree_acquisition.test.ts +2 -0
- package/test/unit/observe/readiness.test.ts +41 -0
- package/test/unit/observe/readiness_native.test.ts +68 -0
- package/test/unit/observe/system-trace-android.test.ts +52 -0
- package/test/unit/observe/system-trace.test.ts +166 -0
- package/test/unit/server/conditional_actions.test.ts +299 -0
- package/test/unit/server/conditional_schema.test.ts +59 -0
- package/test/unit/server/correlated-evidence.test.ts +194 -0
- package/test/unit/server/readiness.test.ts +170 -0
- package/test/unit/server/selector_tap_target.test.ts +79 -0
- package/test/unit/server/system-trace.test.ts +64 -0
- package/test/unit/server/tap_selector.test.ts +2 -0
- package/worker/DebuggerWorker.java +160 -0
- package/skills/mobile-debug-tools-usage/SKILL.md +0 -96
- /package/{skills → .agents/skills}/README.md +0 -0
- /package/{skills → .agents/skills}/mcp-builder/SKILL.md +0 -0
- /package/{skills → .agents/skills}/mcp-builder/references/build-flags.md +0 -0
- /package/{skills → .agents/skills}/mcp-builder/references/diagnostics-schema.md +0 -0
- /package/{skills → .agents/skills}/mcp-builder/references/toolchain-details.md +0 -0
- /package/{skills → .agents/skills}/spec-review/SKILL.md +0 -0
- /package/{skills → .agents/skills}/spec-review/references/spec-review-checklist.md +0 -0
- /package/{skills → .agents/skills}/spec-review/references/spec-review-template.md +0 -0
- /package/{skills → .agents/skills}/test-authoring/SKILL.md +0 -0
- /package/{skills → .agents/skills}/test-authoring/references/repo-test-layout.md +0 -0
- /package/{skills → .agents/skills}/test-authoring/references/test-authoring-workflow.md +0 -0
- /package/{skills → .agents/skills}/test-authoring/references/test-quality-checklist.md +0 -0
package/dist/server-core.js
CHANGED
|
@@ -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.
|
|
9
|
+
version: '0.34.0'
|
|
10
10
|
};
|
|
11
11
|
export function createServer() {
|
|
12
12
|
const server = new Server(serverInfo, {
|
|
@@ -27,9 +27,9 @@ export function createServer() {
|
|
|
27
27
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
28
28
|
tools: toolDefinitions
|
|
29
29
|
}));
|
|
30
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
30
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
31
31
|
const { name, arguments: args } = request.params;
|
|
32
|
-
return handleToolCall(name, args);
|
|
32
|
+
return handleToolCall(name, args, extra.signal);
|
|
33
33
|
});
|
|
34
34
|
return server;
|
|
35
35
|
}
|
package/dist/server.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { debuggerSessions } from './debugger/sessions.js';
|
|
3
|
+
import { systemTraces } from './observe/system-trace.js';
|
|
2
4
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
5
|
import { createServer } from './server-core.js';
|
|
4
6
|
import { getSystemStatus } from './system/index.js';
|
|
@@ -9,6 +11,38 @@ if (process.env.MOBILE_DEBUG_MCP_STARTUP_HEALTHCHECK === '1') {
|
|
|
9
11
|
}).catch((e) => console.warn('[startup] healthcheck failed:', e instanceof Error ? e.message : String(e)));
|
|
10
12
|
}
|
|
11
13
|
const transport = new StdioServerTransport();
|
|
14
|
+
let shuttingDown = false;
|
|
15
|
+
async function shutdown() {
|
|
16
|
+
if (shuttingDown)
|
|
17
|
+
return;
|
|
18
|
+
shuttingDown = true;
|
|
19
|
+
const deadline = setTimeout(() => { console.error('System trace shutdown cleanup timed out; owned device captures remain duration-bounded.'); process.exit(1); }, 10000);
|
|
20
|
+
let exitCode = 0;
|
|
21
|
+
try {
|
|
22
|
+
const [remaining, debuggers] = await Promise.all([systemTraces.shutdown(), debuggerSessions.shutdown()]);
|
|
23
|
+
if (debuggers.length) {
|
|
24
|
+
exitCode = 1;
|
|
25
|
+
console.error('Debugger shutdown cleanup incomplete', debuggers.map(s => s.cleanup));
|
|
26
|
+
}
|
|
27
|
+
if (remaining.length) {
|
|
28
|
+
exitCode = 1;
|
|
29
|
+
console.error('System trace shutdown cleanup incomplete:', remaining.map(s => ({ trace_id: s.trace_id, device: s.device, cleanup: s.cleanup })));
|
|
30
|
+
}
|
|
31
|
+
await server.close();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
exitCode = 1;
|
|
35
|
+
console.error('Server shutdown cleanup failed.');
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
clearTimeout(deadline);
|
|
39
|
+
process.exit(exitCode);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
server.onclose = () => { void shutdown(); };
|
|
43
|
+
process.stdin.on('end', () => { void shutdown(); });
|
|
44
|
+
process.on('SIGINT', () => { void shutdown(); });
|
|
45
|
+
process.on('SIGTERM', () => { void shutdown(); });
|
|
12
46
|
async function main() {
|
|
13
47
|
await server.connect(transport);
|
|
14
48
|
}
|
package/dist/system/index.js
CHANGED
|
@@ -221,7 +221,7 @@ function buildAndroidPlatform(android, hostSupported) {
|
|
|
221
221
|
},
|
|
222
222
|
providers: {
|
|
223
223
|
adb: { available: adbAvailable, selected: true, tool: 'adb' },
|
|
224
|
-
jdwp: { available:
|
|
224
|
+
jdwp: { available: false, selected: false, tool: 'adb', probeRequired: 'get_debugger_capabilities' }
|
|
225
225
|
},
|
|
226
226
|
capabilities: {
|
|
227
227
|
device_discovery: {
|
|
@@ -255,9 +255,10 @@ function buildAndroidPlatform(android, hostSupported) {
|
|
|
255
255
|
blockingFailure: logAvailable ? null : adbFailure ?? envFailure
|
|
256
256
|
},
|
|
257
257
|
debug: {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
258
|
+
probeRequired: 'get_debugger_capabilities',
|
|
259
|
+
availability: false,
|
|
260
|
+
selectedProvider: null,
|
|
261
|
+
blockingFailure: deviceFailure ?? adbFailure ?? envFailure
|
|
261
262
|
}
|
|
262
263
|
},
|
|
263
264
|
devices: deviceEntries,
|
package/dist/utils/exec.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { checkDebuggerGuard } from '../debugger/guard.js';
|
|
1
2
|
import { remainingBudget } from './operation-budget.js';
|
|
2
3
|
import { spawn } from 'child_process';
|
|
3
4
|
export async function execCmd(cmd, args, opts = {}) {
|
|
4
5
|
const { env, cwd, shell } = opts;
|
|
5
6
|
const timeout = remainingBudget(opts.timeout ?? 0);
|
|
6
7
|
return new Promise((resolve, reject) => {
|
|
8
|
+
checkDebuggerGuard();
|
|
7
9
|
const child = spawn(cmd, args, { env: { ...process.env, ...(env || {}) }, cwd, shell });
|
|
8
10
|
let stdout = '';
|
|
9
11
|
let stderr = '';
|
package/dist/utils/ios/utils.js
CHANGED
|
@@ -75,7 +75,7 @@ export async function isIDBInstalled() {
|
|
|
75
75
|
if (!cmd)
|
|
76
76
|
return false;
|
|
77
77
|
try {
|
|
78
|
-
execSync(`${cmd} list-targets --json`, { stdio: ['ignore', 'pipe', 'pipe'], timeout: 5000 });
|
|
78
|
+
execSync(`${cmd} list-targets --json`, { stdio: ['ignore', 'pipe', 'pipe'], timeout: remainingBudget(5000), killSignal: 'SIGKILL' });
|
|
79
79
|
return true;
|
|
80
80
|
}
|
|
81
81
|
catch (e) {
|
|
@@ -197,7 +197,7 @@ export async function findAppBundle(dir) {
|
|
|
197
197
|
export async function getIOSDeviceMetadata(deviceId = "booted") {
|
|
198
198
|
return new Promise((resolve) => {
|
|
199
199
|
// If deviceId is provided (and not "booted"), attempt to find that device among booted simulators.
|
|
200
|
-
execFile(getXcrunCmd(), ['simctl', 'list', 'devices', 'booted', '--json'], (err, stdout) => {
|
|
200
|
+
execFile(getXcrunCmd(), ['simctl', 'list', 'devices', 'booted', '--json'], { timeout: remainingBudget(), killSignal: 'SIGKILL' }, (err, stdout) => {
|
|
201
201
|
const fallback = {
|
|
202
202
|
platform: "ios",
|
|
203
203
|
id: deviceId,
|
|
@@ -240,7 +240,7 @@ export async function getIOSDeviceMetadata(deviceId = "booted") {
|
|
|
240
240
|
export async function listIOSDevices(appId) {
|
|
241
241
|
return new Promise((resolve) => {
|
|
242
242
|
// Query all devices and separately query booted devices to mark them
|
|
243
|
-
execFile(getXcrunCmd(), ['simctl', 'list', 'devices', '--json'], (err, stdout) => {
|
|
243
|
+
execFile(getXcrunCmd(), ['simctl', 'list', 'devices', '--json'], { timeout: remainingBudget(), killSignal: 'SIGKILL' }, (err, stdout) => {
|
|
244
244
|
if (err || !stdout)
|
|
245
245
|
return resolve([]);
|
|
246
246
|
try {
|
|
@@ -249,7 +249,7 @@ export async function listIOSDevices(appId) {
|
|
|
249
249
|
const out = [];
|
|
250
250
|
const checks = [];
|
|
251
251
|
// Get booted devices set
|
|
252
|
-
execFile(getXcrunCmd(), ['simctl', 'list', 'devices', 'booted', '--json'], (err2, stdout2) => {
|
|
252
|
+
execFile(getXcrunCmd(), ['simctl', 'list', 'devices', 'booted', '--json'], { timeout: remainingBudget(), killSignal: 'SIGKILL' }, (err2, stdout2) => {
|
|
253
253
|
const bootedSet = new Set();
|
|
254
254
|
if (!err2 && stdout2) {
|
|
255
255
|
try {
|
package/dist/utils/java.js
CHANGED
|
@@ -25,17 +25,19 @@ function javaVersionOf(javaBin) {
|
|
|
25
25
|
return undefined;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
export async function detectJavaHome() {
|
|
28
|
+
export async function detectJavaHome(options = {}) {
|
|
29
|
+
const debug = (...args) => { if (!options.quiet)
|
|
30
|
+
console.debug(...args); };
|
|
29
31
|
try {
|
|
30
32
|
// 1) Honor explicit ANDROID_STUDIO_JDK env (highest priority)
|
|
31
33
|
const envStudio = process.env.ANDROID_STUDIO_JDK || process.env.ANDROID_STUDIO_JBR;
|
|
32
34
|
if (envStudio && existsSync(path.join(envStudio, 'bin', 'java'))) {
|
|
33
35
|
const v = javaVersionOf(path.join(envStudio, 'bin', 'java'));
|
|
34
36
|
if (isJavaVersionAcceptable(v)) {
|
|
35
|
-
|
|
37
|
+
debug('[java.detect] Using ANDROID_STUDIO_JDK from env:', envStudio);
|
|
36
38
|
return envStudio;
|
|
37
39
|
}
|
|
38
|
-
|
|
40
|
+
debug('[java.detect] ANDROID_STUDIO_JDK present but java -version did not match expected versions');
|
|
39
41
|
}
|
|
40
42
|
// 2) Android Studio JBR candidates (prefer these over JAVA_HOME)
|
|
41
43
|
const jbrCandidates = [
|
|
@@ -53,7 +55,7 @@ export async function detectJavaHome() {
|
|
|
53
55
|
if (existsSync(javaBin)) {
|
|
54
56
|
const v = javaVersionOf(javaBin);
|
|
55
57
|
if (isJavaVersionAcceptable(v)) {
|
|
56
|
-
|
|
58
|
+
debug('[java.detect] Found Android Studio JBR at:', p);
|
|
57
59
|
return p;
|
|
58
60
|
}
|
|
59
61
|
}
|
|
@@ -64,13 +66,13 @@ export async function detectJavaHome() {
|
|
|
64
66
|
const javaBin = path.join(process.env.JAVA_HOME, 'bin', 'java');
|
|
65
67
|
const v = javaVersionOf(javaBin);
|
|
66
68
|
if (isJavaVersionAcceptable(v)) {
|
|
67
|
-
|
|
69
|
+
debug('[java.detect] Using JAVA_HOME from env:', process.env.JAVA_HOME);
|
|
68
70
|
return process.env.JAVA_HOME;
|
|
69
71
|
}
|
|
70
|
-
|
|
72
|
+
debug('[java.detect] Existing JAVA_HOME does not appear to be acceptable Java (17/21), will search');
|
|
71
73
|
}
|
|
72
74
|
catch {
|
|
73
|
-
|
|
75
|
+
debug('[java.detect] Failed to validate existing JAVA_HOME, searching for JDK');
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
78
|
// 4) macOS explicit path for JDK 17
|
|
@@ -84,7 +86,7 @@ export async function detectJavaHome() {
|
|
|
84
86
|
return out17;
|
|
85
87
|
}
|
|
86
88
|
catch (e) {
|
|
87
|
-
|
|
89
|
+
debug('[java.detect] /usr/libexec/java_home -v 17 failed: ' + String(e));
|
|
88
90
|
}
|
|
89
91
|
try {
|
|
90
92
|
const out21 = execSync('/usr/libexec/java_home -v 21', { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
|
|
@@ -92,7 +94,7 @@ export async function detectJavaHome() {
|
|
|
92
94
|
return out21;
|
|
93
95
|
}
|
|
94
96
|
catch (e) {
|
|
95
|
-
|
|
97
|
+
debug('[java.detect] /usr/libexec/java_home -v 21 failed: ' + String(e));
|
|
96
98
|
}
|
|
97
99
|
// 6) macOS common JDK locations
|
|
98
100
|
try {
|
|
@@ -105,7 +107,7 @@ export async function detectJavaHome() {
|
|
|
105
107
|
}
|
|
106
108
|
}
|
|
107
109
|
catch (e) {
|
|
108
|
-
|
|
110
|
+
debug('[java.detect] listing /Library/Java/JavaVirtualMachines failed: ' + String(e));
|
|
109
111
|
}
|
|
110
112
|
// 7) Linux locations
|
|
111
113
|
const linuxCandidates = [
|
|
@@ -122,12 +124,12 @@ export async function detectJavaHome() {
|
|
|
122
124
|
return p;
|
|
123
125
|
}
|
|
124
126
|
catch (e) {
|
|
125
|
-
|
|
127
|
+
debug(`[java.detect] checking linux candidate ${p} failed: ${String(e)}`);
|
|
126
128
|
}
|
|
127
129
|
}
|
|
128
130
|
}
|
|
129
131
|
catch (e) {
|
|
130
|
-
|
|
132
|
+
debug('[java.detect] error detecting java home:', e instanceof Error ? e.message : String(e));
|
|
131
133
|
}
|
|
132
134
|
return undefined;
|
|
133
135
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { checkDebuggerGuard } from '../debugger/guard.js';
|
|
1
2
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
3
|
const budgets = new AsyncLocalStorage();
|
|
3
4
|
export function hasFiniteBudget() { return Number.isFinite(budgets.getStore()?.deadline); }
|
|
@@ -8,14 +9,37 @@ export function runWithBudget(timeoutMs, operation) {
|
|
|
8
9
|
}
|
|
9
10
|
export function remainingBudget(fallback = 0) {
|
|
10
11
|
const budget = budgets.getStore();
|
|
11
|
-
|
|
12
|
+
let deadline = budget?.deadline ?? Infinity;
|
|
13
|
+
for (let ancestor = budget?.parent; ancestor; ancestor = ancestor.parent)
|
|
14
|
+
deadline = Math.min(deadline, ancestor.deadline);
|
|
15
|
+
if (!Number.isFinite(deadline))
|
|
12
16
|
return fallback;
|
|
13
|
-
const remaining = Math.floor(
|
|
17
|
+
const remaining = Math.floor(deadline - performance.now());
|
|
14
18
|
if (remaining <= 0)
|
|
15
19
|
throw new Error('ACTION_TIMEOUT');
|
|
16
20
|
return fallback > 0 ? Math.min(fallback, remaining) : remaining;
|
|
17
21
|
}
|
|
22
|
+
// Pause only this action's budget. Any enclosing operation still bounds the read.
|
|
23
|
+
export async function runWithSeparateBudget(timeoutMs, operation) {
|
|
24
|
+
remainingBudget();
|
|
25
|
+
const action = budgets.getStore();
|
|
26
|
+
const started = performance.now();
|
|
27
|
+
const parent = action?.parent;
|
|
28
|
+
const budget = { deadline: Math.min(parent?.deadline ?? Infinity, started + timeoutMs), dispatchStarted: false, parent };
|
|
29
|
+
try {
|
|
30
|
+
return await budgets.run(budget, async () => {
|
|
31
|
+
const result = await operation();
|
|
32
|
+
remainingBudget();
|
|
33
|
+
return result;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
if (action)
|
|
38
|
+
action.deadline += performance.now() - started;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
18
41
|
export function markDispatchStarted() {
|
|
42
|
+
checkDebuggerGuard(true);
|
|
19
43
|
remainingBudget();
|
|
20
44
|
let budget = budgets.getStore();
|
|
21
45
|
while (budget) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { checkDebuggerGuard } from '../debugger/guard.js';
|
|
2
|
+
import { bindEvidenceDevice } from '../observe/evidence.js';
|
|
1
3
|
import { retainConnectedDeviceSnapshots } from '../observe/snapshot-metadata.js';
|
|
2
4
|
import { measure } from './timing.js';
|
|
3
5
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
@@ -37,9 +39,16 @@ export async function listDevices(platform, appId) {
|
|
|
37
39
|
}
|
|
38
40
|
const deviceContext = new AsyncLocalStorage();
|
|
39
41
|
export function withResolvedDevice(device, operation) {
|
|
42
|
+
bindEvidenceDevice(device);
|
|
40
43
|
return deviceContext.run(device, operation);
|
|
41
44
|
}
|
|
42
45
|
export async function resolveTargetDevice(opts) {
|
|
46
|
+
checkDebuggerGuard();
|
|
47
|
+
const device = await resolveDevice(opts);
|
|
48
|
+
bindEvidenceDevice(device);
|
|
49
|
+
return device;
|
|
50
|
+
}
|
|
51
|
+
async function resolveDevice(opts) {
|
|
43
52
|
const { platform, appId, prefer, deviceId } = opts;
|
|
44
53
|
const current = deviceContext.getStore();
|
|
45
54
|
if (current && current.platform === platform && (!deviceId || current.id === deviceId))
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the **Mobile Debug MCP** project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
## [0.34.0]
|
|
8
|
+
- Added optional Android JDI runtime debugging: explicit device/process attach, source breakpoints/events, bounded stacks and shallow locals, stepping/resume, stale-reference rejection, pause-aware UI guards/readiness, expiry and owned detach/cleanup. Requires a host JDK 17+ and a running debuggable app; evaluation, mutation, native/coroutine and iOS debugging remain unsupported.
|
|
9
|
+
- Added optional bounded Android Perfetto capture with capability/start/status/stop/cancel tools, explicit journey trace linkage, local artifacts, process-ownership checks and retryable cleanup. iOS tracing and automatic trace analysis remain unsupported.
|
|
10
|
+
- Made readiness unit-test polling time deterministic while retaining exact sample-count and timeout assertions, eliminating host timer-rounding flakes.
|
|
11
|
+
- Isolated snapshot unit tests from physical device discovery and native handlers so they run without connected devices. Readiness deadline expiry now retains the last valid observation instead of intermittently resetting loading to unknown.
|
|
12
|
+
|
|
13
|
+
## [0.33.0]
|
|
14
|
+
- Fixed selector taps timing out before one normal Android UI-tree acquisition: a default single lookup now uses the action budget (5 seconds). Selector taps with exists/visible waits resolve actionable containers consistently with clickable waits; hidden/disabled labels remain protected. Acquisition timeouts retain `ACTION_TIMEOUT` instead of reporting a missing element.
|
|
15
|
+
- Added correlated action/observation evidence, explicit `actionRef`, phase/acquisition timestamps, conservative freshness, and bounded metadata history. Journey steps share correlation IDs; snapshot components and stream reads retain their actual device scope. Device log times remain explicitly incomparable with host dispatch times; tracing is unsupported.
|
|
16
|
+
- Added evidence-backed foreground UI readiness observations, bounded readiness waits, and opt-in `requireReady` for existing UI waits. Tree responses include single-sample readiness without extra reads. Unresponsive/debugger-paused signals are explicitly unsupported; iOS probes and metadata reads now respect active operation budgets.
|
|
17
|
+
- Added optional conditional input to `tap`, `tap_element`, `swipe`, Android `type_text`/`press_back`, and journey tap steps. A fresh snapshot check at native dispatch blocks stale or uncheckable expectations and returns condition evidence, including fresh trees in compact failures.
|
|
18
|
+
- Added process-scoped `snapshot_session_id` metadata to UI-tree responses so conditional expectations cannot be reused across server restarts. Successful repeated observations refresh retained revision history timestamps.
|
|
19
|
+
|
|
5
20
|
## [0.32.4]
|
|
6
21
|
- Removed the optional Android UI-tree bridge runtime and its APK build tooling. Android UI-tree reads use `uiautomator dump` through ADB.
|
|
7
22
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Intent: conditional actions
|
|
2
|
+
|
|
3
|
+
Originator: repository maintainer (user in the current conversation).
|
|
4
|
+
Date: 2026-10-29.
|
|
5
|
+
Status: approved.
|
|
6
|
+
|
|
7
|
+
## Original problem
|
|
8
|
+
|
|
9
|
+
“Conditional actions: Check expected state immediately before acting; stop and
|
|
10
|
+
return fresh evidence if it changed.”
|
|
11
|
+
|
|
12
|
+
Source: user request in the current conversation, following adoption of the
|
|
13
|
+
repository development process.
|
|
14
|
+
|
|
15
|
+
## Desired outcome
|
|
16
|
+
|
|
17
|
+
An MCP client can make an action conditional on an expected UI state. The server
|
|
18
|
+
checks that expectation against a fresh observation of the same target device
|
|
19
|
+
immediately before dispatching the action. If the expectation no longer holds,
|
|
20
|
+
the server does not dispatch the action and returns fresh evidence so the client
|
|
21
|
+
can reassess its next step.
|
|
22
|
+
|
|
23
|
+
Affected users and systems: MCP clients/agents, interaction tool dispatch,
|
|
24
|
+
state observation, and Android/iOS adapters for supported actions.
|
|
25
|
+
|
|
26
|
+
## Observable success criteria
|
|
27
|
+
|
|
28
|
+
- A matching expectation allows the requested action to proceed normally.
|
|
29
|
+
- A mismatching expectation prevents dispatch and clearly reports that the
|
|
30
|
+
action was not performed, together with the newly observed state.
|
|
31
|
+
- If the expected state cannot be checked reliably, the action is not dispatched;
|
|
32
|
+
the response explains the observation failure without inventing evidence.
|
|
33
|
+
- The check and action target the same device.
|
|
34
|
+
- A mismatch does not automatically retry the action or adopt the new state.
|
|
35
|
+
- Existing calls without a condition preserve their behaviour.
|
|
36
|
+
|
|
37
|
+
## Constraints and exclusions
|
|
38
|
+
|
|
39
|
+
- Reuse existing action, observation, and response conventions where suitable.
|
|
40
|
+
- Define precise supported conditions, actions, evidence, and platform coverage
|
|
41
|
+
in the spec after approval of this intent.
|
|
42
|
+
- A pre-dispatch check cannot guarantee the UI remains unchanged between the
|
|
43
|
+
observation and native input delivery; do not claim atomic device execution.
|
|
44
|
+
- Automatic replanning, rollback, and post-action success verification are
|
|
45
|
+
outside this feature's scope.
|
|
46
|
+
- Preserve unrelated checkout changes.
|
|
47
|
+
|
|
48
|
+
## Assumptions and open questions for specification
|
|
49
|
+
|
|
50
|
+
- Proposed compatibility approach: conditions are optional on supported actions.
|
|
51
|
+
- Determine the initial action set, including whether journey steps are covered.
|
|
52
|
+
- Determine whether expectations use screen identity, element properties,
|
|
53
|
+
snapshot revisions, or a supported combination.
|
|
54
|
+
- Define fresh mismatch evidence and how it is preserved in compact responses.
|
|
55
|
+
- Existing screen fingerprints deliberately omit some changing values; assess
|
|
56
|
+
their suitability rather than assuming they detect every relevant state change.
|
|
57
|
+
|
|
58
|
+
## Approval
|
|
59
|
+
|
|
60
|
+
Approved by the originator in the current conversation on 2026-10-29: “yes”,
|
|
61
|
+
in direct response to the complete intent summary and linked artifact. Approval
|
|
62
|
+
covers the captured intent; specification approval remains a separate gate.
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Plan: conditional actions
|
|
2
|
+
|
|
3
|
+
Date: 2026-10-29. Status: complete — implementation and required verification passed.
|
|
4
|
+
Intent: [intent.md](intent.md). Specification: [spec.md](spec.md).
|
|
5
|
+
Both approved by the originator's separate “yes” replies in this conversation;
|
|
6
|
+
the latest approval authorises implementation of spec 020.
|
|
7
|
+
|
|
8
|
+
## Implementation sequence
|
|
9
|
+
|
|
10
|
+
- [x] Inspect action wrapping, native input, observation/history, budgets, tests.
|
|
11
|
+
- [x] Add session metadata and retained successful-baseline lookup without changing
|
|
12
|
+
existing revision semantics or growing history.
|
|
13
|
+
- [x] Add strict condition schema, supported-tool validation, and journey tap wiring.
|
|
14
|
+
- [x] Add a request-scoped asynchronous pre-dispatch guard, pin the device, and
|
|
15
|
+
isolate the observation budget from the action budget (respecting outer budgets).
|
|
16
|
+
- [x] Integrate at Android/iOS input spawn boundaries; retain failure evidence
|
|
17
|
+
through adapter errors; defer guarded network-window reset until dispatch.
|
|
18
|
+
- [x] Preserve condition evidence and non-delivery semantics in compact/debug
|
|
19
|
+
responses, including journey stop and not-checked cases.
|
|
20
|
+
- [x] Add deterministic unit/adapter regression coverage and device journeys.
|
|
21
|
+
- [x] Update public docs/changelog and packaged agent usage guidance; sync/check plugin.
|
|
22
|
+
- [x] Run focused checks, maintainer verification, device suite, and focused device journeys.
|
|
23
|
+
- [x] Review changed code/contracts/callers and record results and limits.
|
|
24
|
+
|
|
25
|
+
## Expected affected files
|
|
26
|
+
|
|
27
|
+
- `src/observe/snapshot-metadata.ts`, `src/observe/index.ts`, `src/types.ts`.
|
|
28
|
+
- New shared condition schema/guard modules under `src/` as responsibility requires.
|
|
29
|
+
- `src/utils/operation-budget.ts`, Android/iOS input adapters.
|
|
30
|
+
- `src/server/tool-definitions.ts`, `tool-handlers.ts`, `journey-schema.ts`, `common.ts`.
|
|
31
|
+
- Unit tests under `test/unit/server/`, `test/unit/observe/`, and adapter fixtures.
|
|
32
|
+
- Focused reusable device helper under `test/device/manual/` if explicit simulator
|
|
33
|
+
selection/state setup makes it unsuitable for automatic generic suite execution.
|
|
34
|
+
- `docs/tools/interact.md`, `observe.md`, `docs/CHANGELOG.md`, `README.md`, and
|
|
35
|
+
`agent-plugin/skills/mobile-debug-mcp/SKILL.md`.
|
|
36
|
+
|
|
37
|
+
## Verification mapping
|
|
38
|
+
|
|
39
|
+
| Spec criteria | Verification |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| 1, 5, 8 | Real Android/iOS adapter calls using fake binaries; all supported actions, verification modes and response modes; unguarded compatibility tests |
|
|
42
|
+
| 2, 3 | Mutable hierarchy fixture changes text/bounds/enabled/state and changes during target resolution/adapter preparation; assert zero native input and exact final tree |
|
|
43
|
+
| 4 | Session, history expiry/eviction, device mismatch, error/malformed observation, timeout including late completion and child cleanup |
|
|
44
|
+
| 6 | Journey with earlier passed step, blocked conditional tap, later not-run step; both response modes |
|
|
45
|
+
| 7 | Runtime invalid-input matrix and advertised schemas; unsupported tools/steps rejected before any side effects |
|
|
46
|
+
| 9 | Overlapping guarded/unguarded calls and network-window reset spy; condition budget distinct from input budget; outer-budget expiry |
|
|
47
|
+
| 10 | Documentation and focused review against actual implementation |
|
|
48
|
+
|
|
49
|
+
Commands: focused test files via existing `tsx` runtime, then `npm run verify`,
|
|
50
|
+
`npm run test:device`, and focused device helper against built `dist/` modules.
|
|
51
|
+
Runtime: repository supports Node >=18; record actual Node/toolchain with results.
|
|
52
|
+
No new dependency, migration, helper APK, publication, or service is needed.
|
|
53
|
+
|
|
54
|
+
## Device routes and readiness
|
|
55
|
+
|
|
56
|
+
Available during spec reconnaissance: Android 16 `emulator-5554` and booted iOS
|
|
57
|
+
26.4 iPhone Air `F72108FF-C8D6-4C1E-980E-1AE5E5CCD27B`. The physical Pixel is
|
|
58
|
+
excluded from mutation checks. Tool access and fresh trees work on both simulators.
|
|
59
|
+
Before device runs, inspect current state and choose safe system controls; avoid
|
|
60
|
+
interacting with the open Modul8 subscription flow. Use an already-installed
|
|
61
|
+
system Settings app on Android/iOS where possible; no accounts or entitlements.
|
|
62
|
+
|
|
63
|
+
For each platform on the newly built server:
|
|
64
|
+
|
|
65
|
+
1. Open system Settings, observe its tree and session/revision, repeat to check a
|
|
66
|
+
stable baseline, then perform a guarded safe navigation tap and assert destination.
|
|
67
|
+
2. Observe the baseline, navigate to another Settings screen, then send a guarded
|
|
68
|
+
action with the old expectation; assert non-delivery and returned current tree.
|
|
69
|
+
Confirm the blocked action has no effect using a fresh state assertion.
|
|
70
|
+
3. Run a journey with an earlier successful wait/assert, an old-revision guarded
|
|
71
|
+
tap, and a later action. Confirm the tap fails, later step is not run, and state
|
|
72
|
+
stays unchanged. Repeat relevant compact/debug evidence checks.
|
|
73
|
+
|
|
74
|
+
Exact selectors depend on the current simulator OS tree and will be recorded
|
|
75
|
+
with results. Restore original foreground apps where practical; do not alter
|
|
76
|
+
settings, app data, or subscriptions. Owner of local prerequisites: current task.
|
|
77
|
+
Unavailable/timeout and last-moment preparation races use controlled fake binaries;
|
|
78
|
+
no forced device disconnect or privileged settings change is needed. This is
|
|
79
|
+
reported as simulated error-path evidence, separate from real-device passes.
|
|
80
|
+
|
|
81
|
+
## Results
|
|
82
|
+
|
|
83
|
+
Implementation is present. Node.js v23.11.0 was used locally (the supported
|
|
84
|
+
minimum is >=18; CI's Node 18 run has not been executed locally).
|
|
85
|
+
|
|
86
|
+
### Automated checks
|
|
87
|
+
|
|
88
|
+
- Initial `npm run verify`: PASS, including lint, build, unit tests, plugin check.
|
|
89
|
+
- Focused conditional action regression: PASS using production Android/iOS input
|
|
90
|
+
adapters and controlled fake native binaries. Coverage includes all supported
|
|
91
|
+
actions, each verification/response mode, late target-preparation changes,
|
|
92
|
+
mismatches, unavailable/malformed observations, session/history/device rejection,
|
|
93
|
+
request isolation, native input failure, and journey stop. Complete native
|
|
94
|
+
observation/input paths also pass with fake ADB/idb, including child-close
|
|
95
|
+
evidence for condition timeouts. This is simulated platform evidence.
|
|
96
|
+
- Focused schema parity and retention tests: PASS. Advertised JSON schemas are
|
|
97
|
+
exercised using the existing MCP SDK's validator, alongside runtime schemas.
|
|
98
|
+
All elements contribute to revision checks beyond the bounded delta history.
|
|
99
|
+
- `npm run sync:agent-plugin`: PASS; no generated manifest differences.
|
|
100
|
+
- Final `npm run verify`: PASS (lint, build, 59 unit-test files, plugin consistency).
|
|
101
|
+
Log: `/tmp/mobile-debug-conditional-verify-final.log`.
|
|
102
|
+
- Initial `npm run test:device`: FAIL at iOS UI-tree smoke (`Invalid UI tree resolution`) while the original simulator was booting.
|
|
103
|
+
Android screenshot/log/tree wrappers passed. iOS screenshot wrapper passed;
|
|
104
|
+
the iOS log wrapper also reported pass but its underlying log retrieval printed
|
|
105
|
+
`getpwuid_r` / unavailable log-store errors, so this is not evidence of usable
|
|
106
|
+
iOS logs. These wrappers use automatic device selection; focused tests below
|
|
107
|
+
pin their simulator explicitly. Full log: `/tmp/mobile-debug-conditional-device-suite.log`.
|
|
108
|
+
|
|
109
|
+
- Final `npm run test:device`: PASS after the replacement iPhone 17 Pro finished
|
|
110
|
+
booting. All six Android/iOS screenshot, log, and tree smoke wrappers passed,
|
|
111
|
+
without the earlier iOS log-store diagnostics. Log:
|
|
112
|
+
`/tmp/mobile-debug-conditional-device-suite-final.log`.
|
|
113
|
+
- Final documentation relative-link checks and `git diff --check`: PASS.
|
|
114
|
+
- Final lint after the device helper's selector/start-state correction: PASS.
|
|
115
|
+
|
|
116
|
+
### Focused Android emulator evidence
|
|
117
|
+
|
|
118
|
+
Command (against the built server over MCP stdio, no mocked tool handlers):
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
node --import tsx test/device/manual/interact/conditional_actions.manual.ts \
|
|
122
|
+
--platform android --id emulator-5554 \
|
|
123
|
+
--target-text 'Network & internet' --destination-text Internet \
|
|
124
|
+
--output /tmp/mobile-debug-conditional-android.json
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Device: Android 16 sdk_gphone64_arm64, `emulator-5554`.
|
|
128
|
+
Build: local `dist/server.js`, handshake `mobile-debug-mcp` 0.32.4, with
|
|
129
|
+
`expectedState` confirmed in its advertised tool schema. This is the working-tree
|
|
130
|
+
build, not proof of a published release.
|
|
131
|
+
Starting state: system Settings root, with Network & internet visible.
|
|
132
|
+
|
|
133
|
+
| Journey | Actions / expected outcome | Observed outcome | Result |
|
|
134
|
+
| --- | --- | --- | --- |
|
|
135
|
+
| Matching snapshot | Observe Settings; conditional coordinate tap Network & internet | `matched`, delivered once; fresh tree contains Internet | PASS |
|
|
136
|
+
| Stale snapshot, compact | On network page, attempt safe back-navigation tap with root expectation | `PRECONDITION_FAILED`, not delivered; compact tree contains Internet; fresh tree confirms network page remains | PASS |
|
|
137
|
+
| Stale snapshot, debug | Repeat guarded back navigation with root expectation | Same non-delivery and unchanged destination, full debug evidence | PASS |
|
|
138
|
+
| Journey stop, compact | Successful wait for Internet, conditional back tap with root expectation, later tap | Earlier wait passed, guarded step failed with fresh tree, later step `not_run`; destination remained | PASS |
|
|
139
|
+
| Journey stop, debug | Same sequence in debug mode | Same stop and preserved evidence | PASS |
|
|
140
|
+
|
|
141
|
+
Full structured evidence: `/tmp/mobile-debug-conditional-android.json`.
|
|
142
|
+
The five scenarios were rerun successfully against the final built server after
|
|
143
|
+
the compact-error and metadata-validity fixes and the helper selector correction.
|
|
144
|
+
The helper is reproducible and requires a deliberately prepared safe system
|
|
145
|
+
Settings screen. No setting or subscription was changed. The original Modul8
|
|
146
|
+
dev app was brought back to the foreground after checks.
|
|
147
|
+
|
|
148
|
+
### Focused iOS simulator evidence
|
|
149
|
+
|
|
150
|
+
The original iPhone Air simulator stalled during boot and was closed/rebooted by
|
|
151
|
+
the user. The replacement iPhone 17 Pro became responsive; read-only screenshot
|
|
152
|
+
and UI-tree checks confirmed Home and then Settings before running input checks.
|
|
153
|
+
No boot/restart/reset commands were issued by the agent.
|
|
154
|
+
|
|
155
|
+
Command:
|
|
156
|
+
|
|
157
|
+
```sh
|
|
158
|
+
node --import tsx test/device/manual/interact/conditional_actions.manual.ts \
|
|
159
|
+
--platform ios --id F9243BAF-62A8-4990-8A99-17F527F56FDA \
|
|
160
|
+
--target-text General --destination-text About \
|
|
161
|
+
--output /tmp/mobile-debug-conditional-ios.json
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Device: iOS 26.4 iPhone 17 Pro simulator,
|
|
165
|
+
`F9243BAF-62A8-4990-8A99-17F527F56FDA`.
|
|
166
|
+
Build: the same local `dist/server.js` 0.32.4 build and MCP stdio route as Android,
|
|
167
|
+
with condition support confirmed in tool discovery.
|
|
168
|
+
Starting state: Settings root with General visible, About absent.
|
|
169
|
+
|
|
170
|
+
| Journey | Actions / expected outcome | Observed outcome | Result |
|
|
171
|
+
| --- | --- | --- | --- |
|
|
172
|
+
| Matching snapshot | Observe Settings; conditional coordinate tap General | `matched`, delivered; fresh tree contains About | PASS |
|
|
173
|
+
| Stale snapshot, compact | Attempt safe back-navigation tap with Settings-root expectation | `PRECONDITION_FAILED`, not delivered; compact fresh tree contains About; new observation confirms General stays visible | PASS |
|
|
174
|
+
| Stale snapshot, debug | Same guarded back navigation | Same non-delivery and preserved full condition tree | PASS |
|
|
175
|
+
| Journey stop, compact | Successful wait for About, stale conditional tap on `resource_id: BackButton`, later back tap | Wait passed; condition failed; later step `not_run`; General remained visible | PASS |
|
|
176
|
+
| Journey stop, debug | Same sequence in debug mode | Same stop and fresh condition evidence | PASS |
|
|
177
|
+
|
|
178
|
+
Structured evidence: `/tmp/mobile-debug-conditional-ios.json`.
|
|
179
|
+
An initial helper attempt selected the application container sharing the Settings
|
|
180
|
+
label and correctly stopped with `ELEMENT_NOT_INTERACTABLE` / `not_checked`;
|
|
181
|
+
this was not counted as a conditional-journey pass. The helper now prefers the
|
|
182
|
+
unique native resource ID and requires the destination to be absent from its
|
|
183
|
+
starting screen. A subsequent incorrect-start attempt was also discarded.
|
|
184
|
+
The final five-pass run above starts at the confirmed root and uses BackButton.
|
|
185
|
+
|
|
186
|
+
Cleanup: only navigation was exercised. No setting, permission, account, or app
|
|
187
|
+
data was changed. Home is restored after the smoke-suite rerun.
|
|
188
|
+
|
|
189
|
+
### Focused quality review
|
|
190
|
+
|
|
191
|
+
Reviewed the shared condition schema and advertised definitions, action wrapper
|
|
192
|
+
and journey dispatch, Android/iOS native input boundaries, observation metadata
|
|
193
|
+
and history, budget ancestors, compact serialization, error/recovery mapping,
|
|
194
|
+
and associated tests/callers. The guard runs after adapter preparation, and
|
|
195
|
+
blocked conditions replace generic adapter failures without inventing an execute
|
|
196
|
+
trace or allowing recovery. Condition observation budgets pause only the current
|
|
197
|
+
action; enclosing deadlines still apply. Tests check no late input after timeout
|
|
198
|
+
and native observation child closure. No new framework or dependency was added.
|
|
199
|
+
|
|
200
|
+
Review findings fixed:
|
|
201
|
+
|
|
202
|
+
- Journey light-verification response overlay must preserve `not_requested` when
|
|
203
|
+
its conditional tap did not dispatch.
|
|
204
|
+
- Compact tree responses must retain observation errors instead of looking like
|
|
205
|
+
successful deltas carrying an old revision/session. Added a regression test.
|
|
206
|
+
- Repeated successful observations refresh retained revision timestamps, so a
|
|
207
|
+
recently observed stable screen does not yield an immediately unusable baseline.
|
|
208
|
+
|
|
209
|
+
Limits: live simulator checks cover conditional taps and journey stops; conditional
|
|
210
|
+
swipes and Android typing/back use production-adapter tests with fake native
|
|
211
|
+
tools. Physical devices were not used for mutation checks. UI normalization, the observation-to-OS-input race, and concurrent external
|
|
212
|
+
input remain explicit spec limits. No mutation testing was performed. No tests
|
|
213
|
+
or validation rules were weakened. Existing uncommitted process/skill relocation
|
|
214
|
+
work was preserved. No package was published and nothing was committed/pushed.
|