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
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const conditionalTools = new Set(['tap', 'tap_element', 'swipe', 'type_text', 'press_back']);
|
|
3
|
+
export const expectedStateSchema = z.object({
|
|
4
|
+
snapshotSessionId: z.string().min(1),
|
|
5
|
+
snapshotRevision: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
6
|
+
timeoutMs: z.number().int().min(100).max(60000).optional()
|
|
7
|
+
}).strict();
|
|
8
|
+
export const expectedStateJson = {
|
|
9
|
+
type: 'object', additionalProperties: false,
|
|
10
|
+
properties: {
|
|
11
|
+
snapshotSessionId: { type: 'string', minLength: 1 },
|
|
12
|
+
snapshotRevision: { type: 'integer', minimum: 1, maximum: Number.MAX_SAFE_INTEGER },
|
|
13
|
+
timeoutMs: { type: 'integer', minimum: 100, maximum: 60000, default: 15000 }
|
|
14
|
+
},
|
|
15
|
+
required: ['snapshotSessionId', 'snapshotRevision']
|
|
16
|
+
};
|
|
17
|
+
export function validateExpectedState(name, args) {
|
|
18
|
+
if (args.expectedState === undefined)
|
|
19
|
+
return undefined;
|
|
20
|
+
if (!conditionalTools.has(name))
|
|
21
|
+
throw new Error('INVALID_ARGUMENT: expectedState is not supported on this tool');
|
|
22
|
+
const parsed = expectedStateSchema.safeParse(args.expectedState);
|
|
23
|
+
if (!parsed.success)
|
|
24
|
+
throw new Error('INVALID_ARGUMENT: invalid expectedState');
|
|
25
|
+
if ((args.platform !== 'android' && args.platform !== 'ios') ||
|
|
26
|
+
typeof args.deviceId !== 'string' || !args.deviceId.trim() ||
|
|
27
|
+
['booted', 'default'].includes(args.deviceId))
|
|
28
|
+
throw new Error('INVALID_ARGUMENT: expectedState requires an explicit platform and concrete deviceId');
|
|
29
|
+
if (['type_text', 'press_back'].includes(name) && args.platform !== 'android')
|
|
30
|
+
throw new Error('INVALID_ARGUMENT: this action supports only Android');
|
|
31
|
+
return parsed.data;
|
|
32
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { debuggerContracts } from './debugger-schema.js';
|
|
2
|
+
import { debuggerSessions, debuggerCapabilities } from '../debugger/sessions.js';
|
|
3
|
+
import { DebuggerError, withDebuggerBudget } from '../debugger/worker.js';
|
|
4
|
+
import { wrapResponse } from './common.js';
|
|
5
|
+
function failure(code, summary = {}) {
|
|
6
|
+
return { ...wrapResponse({ debug_session_id: null, device: null, target: null, state: null,
|
|
7
|
+
captured_at_ms: Date.now(), capabilities: debuggerCapabilities, limitations: [], cleanup: null,
|
|
8
|
+
...summary, success: false, error: { code, message: code, retryable: false } }), isError: true };
|
|
9
|
+
}
|
|
10
|
+
export async function handleDebugger(name, args, signal) {
|
|
11
|
+
const parsed = debuggerContracts[name].schema.safeParse(args);
|
|
12
|
+
if (!parsed.success)
|
|
13
|
+
return failure('INVALID_ARGUMENT');
|
|
14
|
+
const a = parsed.data;
|
|
15
|
+
const command = debuggerContracts[name].command;
|
|
16
|
+
try {
|
|
17
|
+
if (signal?.aborted)
|
|
18
|
+
throw new DebuggerError('DEBUGGER_CANCELLED');
|
|
19
|
+
const result = await withDebuggerBudget(a.timeoutMs, signal, async () => command === 'capabilities' ? await debuggerSessions.capabilities(a.deviceId, a.appId, a.processId) :
|
|
20
|
+
command === 'attach' ? await debuggerSessions.attach(a.deviceId, a.appId, a.processId, a.timeoutMs) :
|
|
21
|
+
await debuggerSessions.command(a.debugSessionId, command, { ...a, signal: command === 'events' ? signal : undefined }, a.timeoutMs));
|
|
22
|
+
if (signal?.aborted && command === 'capabilities')
|
|
23
|
+
throw new DebuggerError('DEBUGGER_CANCELLED');
|
|
24
|
+
return { ...wrapResponse(result), ...(!result.success ? { isError: true } : {}) };
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
const code = e instanceof DebuggerError ? e.code : 'DEBUGGER_WORKER_FAILED';
|
|
28
|
+
let summary = {};
|
|
29
|
+
if (a.debugSessionId) {
|
|
30
|
+
try {
|
|
31
|
+
summary = debuggerSessions.summary(a.debugSessionId);
|
|
32
|
+
}
|
|
33
|
+
catch { /* Unknown session */ }
|
|
34
|
+
}
|
|
35
|
+
return failure(code, { debug_session_id: a.debugSessionId ?? null,
|
|
36
|
+
device: a.deviceId ? { platform: 'android', id: a.deviceId } : null, ...summary });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const text = z.string().min(1).max(512);
|
|
3
|
+
const integer = z.number().int().safe().positive();
|
|
4
|
+
const timeout = z.number().int().min(100).max(30000);
|
|
5
|
+
const target = { platform: z.literal('android'), deviceId: text, appId: text.regex(/^[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/), processId: integer.optional() };
|
|
6
|
+
const session = { debugSessionId: text };
|
|
7
|
+
const stop = { ...session, stopId: text };
|
|
8
|
+
const specs = {
|
|
9
|
+
get_debugger_capabilities: { fields: target, command: 'capabilities', description: 'Probe optional Android JDK/JDWP prerequisites and matching running debuggable processes; does not attach.' },
|
|
10
|
+
attach_debugger: { fields: target, command: 'attach', description: 'Attach an owned Android JDI session. Requires an explicit device and debuggable app; choose processId when ambiguous. Stops suspend all app threads. Auto-detach: 60s paused, 5m idle, 30m total.' },
|
|
11
|
+
get_debugger_state: { fields: session, command: 'state', description: 'Read debugger state, current stop and breakpoint/cleanup status.' },
|
|
12
|
+
set_debugger_breakpoint: { fields: { ...session, className: text.regex(/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/), line: integer }, command: 'set', description: 'Set an exact binary-class and executable-source-line breakpoint; unloaded classes remain pending. No silent line relocation.' },
|
|
13
|
+
remove_debugger_breakpoint: { fields: { ...session, breakpointId: text }, command: 'remove', description: 'Remove an owned breakpoint; repeated removal succeeds.' },
|
|
14
|
+
wait_for_debugger_event: { fields: { ...session, afterSequence: z.number().int().safe().min(0).optional() }, command: 'events', description: 'Wait for bounded debugger events after a sequence cursor. Empty timeout does not affect execution; overflow is explicit.' },
|
|
15
|
+
get_debugger_stack: { fields: { ...stop, threadId: text, offset: z.number().int().min(0).max(999).optional(), limit: z.number().int().min(1).max(100).optional() }, command: 'stack', description: 'Read bounded stack frames for a stopped thread; IDs expire on resume.' },
|
|
16
|
+
get_debugger_locals: { fields: { ...stop, frameId: text }, command: 'locals', description: 'Read at most 100 shallow locals. Sensitive values returned only on request; strings truncate at 256 characters. No evaluation or field traversal.' },
|
|
17
|
+
step_debugger: { fields: { ...stop, threadId: text, kind: z.enum(['into', 'over', 'out']) }, command: 'step', description: 'Acknowledge one-shot line stepping and resume. Wait for a subsequent stop event separately.' },
|
|
18
|
+
resume_debugger: { fields: stop, command: 'resume', description: 'Resume owned debugger suspension and immediately expire stopped references.' },
|
|
19
|
+
detach_debugger: { fields: session, command: 'detach', description: 'Dispose the owned debugger connection, release owned suspension and remove its exact forward. Idempotent; retries incomplete cleanup.' }
|
|
20
|
+
};
|
|
21
|
+
// Derive JSON schemas and runtime validation from the same small Zod field vocabulary.
|
|
22
|
+
function jsonField(schema) {
|
|
23
|
+
if (schema instanceof z.ZodOptional)
|
|
24
|
+
return jsonField(schema.unwrap());
|
|
25
|
+
if (schema instanceof z.ZodLiteral)
|
|
26
|
+
return { type: 'string', enum: [schema.value] };
|
|
27
|
+
if (schema instanceof z.ZodEnum)
|
|
28
|
+
return { type: 'string', enum: schema.options };
|
|
29
|
+
if (schema instanceof z.ZodString)
|
|
30
|
+
return { type: 'string', minLength: 1, maxLength: 512, ...Object.assign({}, ...schema._def.checks.filter(c => c.kind === 'regex').map(c => ({ pattern: c.regex.source }))) };
|
|
31
|
+
if (schema instanceof z.ZodNumber)
|
|
32
|
+
return { type: 'integer', minimum: schema.minValue, maximum: schema.maxValue };
|
|
33
|
+
throw new Error('Unsupported debugger field schema');
|
|
34
|
+
}
|
|
35
|
+
export const debuggerContracts = Object.fromEntries(Object.entries(specs).map(([name, spec]) => [name, {
|
|
36
|
+
...spec, schema: z.object({ ...spec.fields, timeoutMs: timeout.default(name === 'wait_for_debugger_event' ? 1000 : 10000) }).strict()
|
|
37
|
+
}]));
|
|
38
|
+
export const debuggerTools = Object.entries(debuggerContracts).map(([name, spec]) => ({ name, description: spec.description,
|
|
39
|
+
inputSchema: { type: 'object', additionalProperties: false,
|
|
40
|
+
required: Object.entries(spec.fields).filter(([, field]) => !field.isOptional()).map(([key]) => key),
|
|
41
|
+
properties: { ...Object.fromEntries(Object.entries(spec.fields).map(([key, field]) => [key, jsonField(field)])), timeoutMs: { type: 'integer', minimum: 100, maximum: 30000, default: name === 'wait_for_debugger_event' ? 1000 : 10000 } } }
|
|
42
|
+
}));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { expectedStateSchema, expectedStateJson } from './condition-schema.js';
|
|
2
3
|
const selector = z.object({
|
|
3
4
|
text: z.string().min(1).optional(), resource_id: z.string().min(1).optional(),
|
|
4
5
|
accessibility_id: z.string().min(1).optional(), contains: z.boolean().optional()
|
|
@@ -22,7 +23,7 @@ const assertion = z.union([
|
|
|
22
23
|
const id = z.string().min(1);
|
|
23
24
|
const step = z.union([
|
|
24
25
|
z.object({ id, type: z.literal('start_app'), appId: z.string().min(1), verificationMode: verificationMode.optional() }).strict(),
|
|
25
|
-
z.object({ id, type: z.literal('tap'), ...target, waitFor: waitFor.optional(), verificationMode: verificationMode.optional() }).strict().refine(exactlyOneTarget),
|
|
26
|
+
z.object({ id, type: z.literal('tap'), ...target, waitFor: waitFor.optional(), verificationMode: verificationMode.optional(), expectedState: expectedStateSchema.optional() }).strict().refine(exactlyOneTarget),
|
|
26
27
|
z.object({ id, type: z.literal('wait'), selector, condition: z.enum(['exists', 'not_exists', 'visible', 'clickable']).optional(), timeoutMs, pollIntervalMs }).strict(),
|
|
27
28
|
z.object({ id, type: z.literal('assert'), assertion, timeoutMs, pollIntervalMs }).strict()
|
|
28
29
|
]);
|
|
@@ -31,6 +32,7 @@ export const tapElementControlsSchema = z.object({
|
|
|
31
32
|
}).strict().refine(value => Number(!!value.elementId) + Number(!!value.selector) === 1 && (!value.elementId || !value.waitFor));
|
|
32
33
|
export const journeySchema = z.object({
|
|
33
34
|
platform: z.enum(['android', 'ios']), deviceId: z.string().min(1).optional(),
|
|
35
|
+
systemTraceId: z.string().uuid().optional(),
|
|
34
36
|
responseMode: z.enum(['compact', 'debug']).default('compact'), captureOnFailure: z.boolean().default(false),
|
|
35
37
|
defaults: z.object({ verificationMode: verificationMode.optional(), actionTimeoutMs: z.number().int().finite().optional(), verificationTimeoutMs: z.number().int().finite().optional() }).strict().optional(),
|
|
36
38
|
steps: z.array(step).min(1).max(50)
|
|
@@ -46,7 +48,7 @@ const objectJson = (properties, required, extra = {}) => ({ type: 'object', addi
|
|
|
46
48
|
export const journeyStepsJson = {
|
|
47
49
|
type: 'array', minItems: 1, maxItems: 50, items: { oneOf: [
|
|
48
50
|
objectJson({ id: { type: 'string', minLength: 1 }, type: { const: 'start_app' }, appId: { type: 'string', minLength: 1 }, verificationMode: modeJson }, ['id', 'type', 'appId']),
|
|
49
|
-
objectJson({ id: { type: 'string', minLength: 1 }, type: { const: 'tap' }, ...targetJson, verificationMode: modeJson,
|
|
51
|
+
objectJson({ id: { type: 'string', minLength: 1 }, type: { const: 'tap' }, ...targetJson, verificationMode: modeJson, expectedState: expectedStateJson,
|
|
50
52
|
waitFor: objectJson({ ...intervalJson, timeoutMs: { type: 'integer', minimum: 100, maximum: 10000 }, condition: { enum: ['exists', 'visible', 'clickable'] }, match: objectJson({ index: { type: 'integer', minimum: 0 } }, []) }, [])
|
|
51
53
|
}, ['id', 'type'], { oneOf: oneTargetJson }),
|
|
52
54
|
objectJson({ id: { type: 'string', minLength: 1 }, type: { const: 'wait' }, selector: selectorJson, condition: { enum: ['exists', 'not_exists', 'visible', 'clickable'] }, ...intervalJson }, ['id', 'type', 'selector']),
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const timeout = z.number().int().min(100).max(60000);
|
|
3
|
+
export const readinessSchema = z.object({
|
|
4
|
+
platform: z.enum(['android', 'ios']), deviceId: z.string().min(1).optional(),
|
|
5
|
+
timeoutMs: timeout.optional(), stabilityMs: z.number().int().min(100).max(10000).optional()
|
|
6
|
+
}).strict();
|
|
7
|
+
export const readinessWaitSchema = readinessSchema.extend({ pollIntervalMs: z.number().int().min(100).max(1000).optional() }).strict();
|
|
8
|
+
export const readinessPropertiesJson = {
|
|
9
|
+
platform: { type: 'string', enum: ['android', 'ios'] }, deviceId: { type: 'string', minLength: 1 },
|
|
10
|
+
timeoutMs: { type: 'integer', minimum: 100, maximum: 60000 },
|
|
11
|
+
stabilityMs: { type: 'integer', minimum: 100, maximum: 10000, default: 500 }
|
|
12
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const target = { platform: z.enum(['android', 'ios']), deviceId: z.string().min(1).optional() };
|
|
3
|
+
export const traceTargetSchema = z.object(target).strict();
|
|
4
|
+
export const traceStartSchema = z.object({ ...target, durationMs: z.number().int().min(1000).max(120000).default(30000) }).strict();
|
|
5
|
+
export const traceControlSchema = z.object({ traceId: z.string().uuid() }).strict();
|
|
6
|
+
const properties = { platform: { type: 'string', enum: ['android', 'ios'] }, deviceId: { type: 'string', minLength: 1 } };
|
|
7
|
+
export const systemTraceTools = [
|
|
8
|
+
{ name: 'get_system_trace_capabilities', description: 'Probe Android Perfetto prerequisites without recording. Device-wide system tracing is optional; iOS is unsupported.',
|
|
9
|
+
inputSchema: { type: 'object', additionalProperties: false, required: ['platform'], properties } },
|
|
10
|
+
{ name: 'start_system_trace', description: 'Start bounded Android Perfetto recording, returning an owned trace_id. Run an existing journey with systemTraceId, then stop to retrieve a local artifact or cancel to discard. Device-wide traces may contain sensitive system annotations; no upload.',
|
|
11
|
+
inputSchema: { type: 'object', additionalProperties: false, required: ['platform'], properties: { ...properties, durationMs: { type: 'integer', minimum: 1000, maximum: 120000, default: 30000 } } } },
|
|
12
|
+
...['get_system_trace_status', 'stop_system_trace', 'cancel_system_trace'].map(name => ({ name,
|
|
13
|
+
description: name === 'get_system_trace_status' ? 'Reconcile an owned trace and retrieve its state; finalise duration-completed capture.' :
|
|
14
|
+
name === 'stop_system_trace' ? 'Stop owned capture gracefully and retrieve a local Perfetto artifact. Retry after a transfer/cleanup failure.' : 'Stop owned capture and discard partial files. Already completed local artifacts are retained.',
|
|
15
|
+
inputSchema: { type: 'object', additionalProperties: false, required: ['traceId'], properties: { traceId: { type: 'string', format: 'uuid' } } }
|
|
16
|
+
}))
|
|
17
|
+
];
|
|
@@ -1,5 +1,20 @@
|
|
|
1
|
+
import { debuggerTools } from './debugger-schema.js';
|
|
2
|
+
import { systemTraceTools } from './system-trace-schema.js';
|
|
1
3
|
import { journeyStepsJson } from './journey-schema.js';
|
|
4
|
+
import { conditionalTools, expectedStateJson } from './condition-schema.js';
|
|
5
|
+
import { readinessPropertiesJson } from './readiness-schema.js';
|
|
2
6
|
export const toolDefinitions = [
|
|
7
|
+
...debuggerTools,
|
|
8
|
+
...systemTraceTools,
|
|
9
|
+
...['get_app_readiness', 'wait_for_app_ready'].map(name => ({
|
|
10
|
+
name,
|
|
11
|
+
description: `${name === 'get_app_readiness' ? 'Observe readiness with at most two fresh UI-tree samples.' : 'Wait within a single bounded budget for settled UI evidence.'} Reports loading, transitioning, settled or unknown with evidence and limitations. Managed Android debugger pauses are reported from debugger evidence; unresponsive detection remains unsupported. Settled normalized foreground UI is not proof of app identity, responsiveness or backend completion.`,
|
|
12
|
+
inputSchema: { type: 'object', additionalProperties: false, required: ['platform'], properties: {
|
|
13
|
+
...readinessPropertiesJson,
|
|
14
|
+
timeoutMs: { ...readinessPropertiesJson.timeoutMs, default: name === 'get_app_readiness' ? 15000 : 30000 },
|
|
15
|
+
...(name === 'wait_for_app_ready' ? { pollIntervalMs: { type: 'integer', minimum: 100, maximum: 1000, default: 500 } } : {})
|
|
16
|
+
} }
|
|
17
|
+
})),
|
|
3
18
|
{
|
|
4
19
|
name: 'start_app',
|
|
5
20
|
description: `Purpose:
|
|
@@ -1128,6 +1143,29 @@ Returns { requests: [], count: 0 } when no credible network signals are found.`,
|
|
|
1128
1143
|
];
|
|
1129
1144
|
// Keep the additive execution controls consistent across all action tools.
|
|
1130
1145
|
for (const tool of toolDefinitions) {
|
|
1146
|
+
if (['wait_for_ui', 'wait_for_ui_change'].includes(tool.name)) {
|
|
1147
|
+
tool.inputSchema.properties.requireReady = { type: 'boolean', default: false };
|
|
1148
|
+
tool.description += '\nSet requireReady=true to require both the existing predicate and settled UI readiness, using the same fresh samples and one timeout budget. The readiness summary explains uncertainty. Default predicate semantics are unchanged.';
|
|
1149
|
+
}
|
|
1150
|
+
if (tool.name === 'get_ui_tree')
|
|
1151
|
+
tool.description += '\nSuccessful observations include snapshot_session_id and snapshot_revision for same-device conditional input. Compact errors preserve the error and cannot authorize an expectation.';
|
|
1152
|
+
if (tool.name === 'run_journey')
|
|
1153
|
+
tool.description += '\nTap steps may include expectedState from a successful same-device get_ui_tree. A failed condition blocks that input, retains fresh condition evidence even in compact mode, and stops subsequent steps. Conditions are not journey defaults or supported on other step types.';
|
|
1154
|
+
if (conditionalTools.has(tool.name)) {
|
|
1155
|
+
tool.inputSchema.properties.expectedState = expectedStateJson;
|
|
1156
|
+
tool.inputSchema.properties.platform ??= { type: 'string', enum: ['android'] };
|
|
1157
|
+
tool.inputSchema.allOf = [...(tool.inputSchema.allOf ?? []), {
|
|
1158
|
+
if: { required: ['expectedState'] },
|
|
1159
|
+
then: { required: ['platform', 'deviceId'], properties: {
|
|
1160
|
+
platform: { enum: ['type_text', 'press_back'].includes(tool.name) ? ['android'] : ['android', 'ios'] },
|
|
1161
|
+
deviceId: { type: 'string', minLength: 1, pattern: '\\S', not: { enum: ['booted', 'default'] } }
|
|
1162
|
+
} }
|
|
1163
|
+
}];
|
|
1164
|
+
tool.description += '\n\nConditional input: optionally supply expectedState { snapshotSessionId, snapshotRevision, timeoutMs? } from a successful get_ui_tree on the same explicit platform/deviceId. A fresh normalized UI-tree check runs immediately before input, even with verification disabled. PRECONDITION_FAILED or PRECONDITION_UNAVAILABLE means no input was dispatched; inspect condition evidence and re-observe before deciding what to do next. Compact failures include the fresh UI tree. This is not atomic with OS input delivery.';
|
|
1165
|
+
}
|
|
1166
|
+
else {
|
|
1167
|
+
tool.inputSchema.allOf = [...(tool.inputSchema.allOf ?? []), { not: { required: ['expectedState'] } }];
|
|
1168
|
+
}
|
|
1131
1169
|
if (['start_app', 'restart_app', 'tap', 'tap_element', 'swipe', 'scroll_to_element', 'type_text', 'press_back'].includes(tool.name)) {
|
|
1132
1170
|
Object.assign(tool.inputSchema.properties, {
|
|
1133
1171
|
verificationMode: { type: 'string', enum: ['none', 'light', 'full'], default: 'full' },
|
|
@@ -1141,6 +1179,7 @@ for (const tool of toolDefinitions) {
|
|
|
1141
1179
|
if (tool.name === 'run_journey')
|
|
1142
1180
|
Object.assign(tool.inputSchema.properties, {
|
|
1143
1181
|
steps: journeyStepsJson,
|
|
1182
|
+
systemTraceId: { type: 'string', format: 'uuid', description: 'Explicit active same-device system trace. Invalid references prevent all journey input. Capture lifetime is controlled separately.' },
|
|
1144
1183
|
captureOnFailure: { type: 'boolean', default: false },
|
|
1145
1184
|
defaults: { type: 'object', additionalProperties: false, properties: {
|
|
1146
1185
|
verificationMode: { type: 'string', enum: ['none', 'light', 'full'] },
|
|
@@ -1148,3 +1187,15 @@ for (const tool of toolDefinitions) {
|
|
|
1148
1187
|
} }
|
|
1149
1188
|
});
|
|
1150
1189
|
}
|
|
1190
|
+
// Correlation is explicit; an omitted reference creates an independent observation.
|
|
1191
|
+
for (const tool of toolDefinitions) {
|
|
1192
|
+
if (['capture_screenshot', 'get_ui_tree', 'get_screen_fingerprint', 'capture_debug_snapshot', 'get_logs', 'read_log_stream'].includes(tool.name)) {
|
|
1193
|
+
Object.assign(tool.inputSchema.properties, { actionRef: {
|
|
1194
|
+
type: 'object', additionalProperties: false, required: ['sessionId', 'actionId'], properties: {
|
|
1195
|
+
sessionId: { type: 'string', minLength: 1, maxLength: 128 },
|
|
1196
|
+
actionId: { type: 'string', minLength: 1, maxLength: 128 }
|
|
1197
|
+
}
|
|
1198
|
+
} });
|
|
1199
|
+
tool.description += '\nReturns evidence metadata with acquisition intervals and conservative freshness. Optional actionRef links a known same-session/device action; unavailable references collect independently. Device log times are not comparable with host dispatch times.';
|
|
1200
|
+
}
|
|
1201
|
+
}
|