@sensaiorg/adapter-android 0.1.0 → 0.1.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"android-adapter.d.ts","sourceRoot":"","sources":["../src/android-adapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,EACjB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAGtE,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,cAAe,YAAW,gBAAgB;IACrD,QAAQ,CAAC,QAAQ,EAAG,SAAS,CAAU;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAUzC;IAEF,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAoB;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IAGxD,QAAQ,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,CAAQ;IACvC,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAQ;IAC1C,QAAQ,CAAC,WAAW,EAAE,oBAAoB,GAAG,IAAI,CAAQ;IACzD,QAAQ,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAQ;IACjD,QAAQ,CAAC,WAAW,EAAE,oBAAoB,GAAG,IAAI,CAAQ;IACzD,QAAQ,CAAC,UAAU,EAAE,mBAAmB,GAAG,IAAI,CAAQ;IACvD,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAQ;gBAEvC,MAAM,GAAE,oBAAyB;IAiBvC,UAAU,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAsB7C,QAAQ,IAAI,IAAI;IAIhB,WAAW,IAAI,OAAO;IAItB;;;OAGG;IACH,oBAAoB,IAAI,iBAAiB;IAIzC;;;OAGG;IACH,qBAAqB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;CAM/C"}
1
+ {"version":3,"file":"android-adapter.d.ts","sourceRoot":"","sources":["../src/android-adapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,EACjB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAGtE,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,cAAe,YAAW,gBAAgB;IACrD,QAAQ,CAAC,QAAQ,EAAG,SAAS,CAAU;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAUzC;IAEF,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAoB;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IAGxD,QAAQ,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,CAAQ;IACvC,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAQ;IAC1C,QAAQ,CAAC,WAAW,EAAE,oBAAoB,GAAG,IAAI,CAAQ;IACzD,QAAQ,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAQ;IACjD,QAAQ,CAAC,WAAW,EAAE,oBAAoB,GAAG,IAAI,CAAQ;IACzD,QAAQ,CAAC,UAAU,EAAE,mBAAmB,GAAG,IAAI,CAAQ;IACvD,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAQ;gBAEvC,MAAM,GAAE,oBAAyB;IAiBvC,UAAU,IAAI,OAAO,CAAC,gBAAgB,CAAC;IA+B7C,QAAQ,IAAI,IAAI;IAIhB,WAAW,IAAI,OAAO;IAItB;;;OAGG;IACH,oBAAoB,IAAI,iBAAiB;IAIzC;;;OAGG;IACH,qBAAqB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;CAM/C"}
@@ -45,10 +45,21 @@ class AndroidAdapter {
45
45
  }
46
46
  async initialize() {
47
47
  const status = await this.connectionManager.initialize();
48
- // Update capabilities if agent is available
48
+ // Update capabilities based on agent-reported capabilities (if available)
49
49
  if (status.agent) {
50
- this.capabilities.network = true;
51
- this.capabilities.appState = true;
50
+ const agentCaps = this.connectionManager.getAgentCapabilities();
51
+ if (agentCaps) {
52
+ // Agent explicitly reports what it supports
53
+ if (agentCaps.network)
54
+ this.capabilities.network = true;
55
+ if (agentCaps.reactNative)
56
+ this.capabilities.appState = true;
57
+ }
58
+ else {
59
+ // Agent connected but doesn't support capabilities RPC — assume Phase 2 defaults
60
+ this.capabilities.network = true;
61
+ this.capabilities.appState = true;
62
+ }
52
63
  }
53
64
  return {
54
65
  platform: "android",
@@ -58,6 +69,7 @@ class AndroidAdapter {
58
69
  details: {
59
70
  adb: status.adb,
60
71
  agent: status.agent,
72
+ agentCapabilities: this.connectionManager.getAgentCapabilities(),
61
73
  targetPackage: this.config.targetPackage,
62
74
  },
63
75
  };
@@ -38,7 +38,7 @@ function registerAccessibilityTools(server, cm) {
38
38
  // Try the agent first for a richer accessibility tree
39
39
  if (cm.agent.isConnected()) {
40
40
  try {
41
- const result = await cm.agent.call("getAccessibilityTree");
41
+ const result = await cm.agent.call("ui.allWindows");
42
42
  return {
43
43
  content: [{ type: "text", text: JSON.stringify(result) }],
44
44
  };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-management.d.ts","sourceRoot":"","sources":["../../src/tools/agent-management.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AA8C5E,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CAqM3F"}
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ /**
3
+ * Agent Management Tools - Install the SensAI agent APK and query connection status.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerAgentManagementTools = registerAgentManagementTools;
7
+ const node_fs_1 = require("node:fs");
8
+ const node_path_1 = require("node:path");
9
+ const zod_1 = require("zod");
10
+ /** Package name for the SensAI on-device agent. */
11
+ const AGENT_PACKAGE = "com.sensai.agent";
12
+ /** Service component to start after install. */
13
+ const AGENT_SERVICE = `${AGENT_PACKAGE}/.DebugAgentService`;
14
+ /** Accessibility service component for emulators. */
15
+ const ACCESSIBILITY_SERVICE = `${AGENT_PACKAGE}/.accessibility.UITreeExtractor`;
16
+ /**
17
+ * Resolve the agent APK path. Checks (in order):
18
+ * 1. SENSAI_AGENT_APK environment variable
19
+ * 2. android-agent/app/build/outputs/apk/debug/app-debug.apk relative to repo root
20
+ */
21
+ function resolveApkPath() {
22
+ if (process.env.SENSAI_AGENT_APK) {
23
+ const envPath = (0, node_path_1.resolve)(process.env.SENSAI_AGENT_APK);
24
+ return (0, node_fs_1.existsSync)(envPath) ? envPath : null;
25
+ }
26
+ // Walk up from this file to find the repo root (contains android-agent/)
27
+ // __dirname is packages/adapter-android/dist/tools (or src/tools in dev)
28
+ const repoRoot = (0, node_path_1.resolve)(__dirname, "..", "..", "..", "..");
29
+ const defaultPath = (0, node_path_1.resolve)(repoRoot, "android-agent", "app", "build", "outputs", "apk", "debug", "app-debug.apk");
30
+ return (0, node_fs_1.existsSync)(defaultPath) ? defaultPath : null;
31
+ }
32
+ /**
33
+ * Detect whether the device serial looks like an emulator.
34
+ */
35
+ function isEmulatorDevice(serial) {
36
+ return serial.startsWith("emulator-") || serial.includes("localhost:");
37
+ }
38
+ function registerAgentManagementTools(server, cm) {
39
+ // ---------- install_agent ----------
40
+ server.tool("install_agent", "Install the SensAI on-device agent APK via ADB. Starts the agent service, " +
41
+ "enables accessibility on emulators, sets up port forwarding, and attempts connection.", {
42
+ apkPath: zod_1.z
43
+ .string()
44
+ .optional()
45
+ .describe("Explicit path to the agent APK. If omitted, checks SENSAI_AGENT_APK env var " +
46
+ "then the default Gradle build output."),
47
+ forceReinstall: zod_1.z
48
+ .boolean()
49
+ .optional()
50
+ .describe("Pass -r flag to adb install to replace existing install (default: true)"),
51
+ }, async (params) => {
52
+ const steps = [];
53
+ try {
54
+ // 1. Locate APK
55
+ const apkPath = params.apkPath ? (0, node_path_1.resolve)(params.apkPath) : resolveApkPath();
56
+ if (!apkPath || !(0, node_fs_1.existsSync)(apkPath)) {
57
+ return {
58
+ content: [
59
+ {
60
+ type: "text",
61
+ text: JSON.stringify({
62
+ success: false,
63
+ error: "Agent APK not found",
64
+ searchedPaths: [
65
+ params.apkPath ?? "(not specified)",
66
+ process.env.SENSAI_AGENT_APK ?? "(env not set)",
67
+ "android-agent/app/build/outputs/apk/debug/app-debug.apk",
68
+ ],
69
+ hint: "Build the agent with: cd android-agent && ./gradlew assembleDebug",
70
+ }),
71
+ },
72
+ ],
73
+ isError: true,
74
+ };
75
+ }
76
+ steps.push(`APK found: ${apkPath}`);
77
+ // 2. Install APK
78
+ const installArgs = params.forceReinstall === false ? ["install", apkPath] : ["install", "-r", apkPath];
79
+ const installOutput = await cm.adb.exec(installArgs, 60_000);
80
+ steps.push(`Install: ${installOutput.trim()}`);
81
+ // 3. Start the agent foreground service
82
+ try {
83
+ await cm.adb.shell(`am start-foreground-service -n ${AGENT_SERVICE}`);
84
+ steps.push("Agent service started");
85
+ }
86
+ catch (err) {
87
+ steps.push(`Service start warning: ${err instanceof Error ? err.message : String(err)}`);
88
+ }
89
+ // 4. On emulators, auto-enable accessibility service
90
+ const status = cm.getStatus();
91
+ if (isEmulatorDevice(status.device)) {
92
+ try {
93
+ await cm.adb.shell(`settings put secure enabled_accessibility_services ${ACCESSIBILITY_SERVICE}`);
94
+ steps.push("Accessibility service enabled (emulator)");
95
+ }
96
+ catch (err) {
97
+ steps.push(`Accessibility warning: ${err instanceof Error ? err.message : String(err)}`);
98
+ }
99
+ }
100
+ else {
101
+ steps.push("Physical device detected — enable accessibility manually in Settings > Accessibility");
102
+ }
103
+ // 5. Set up port forwarding
104
+ try {
105
+ await cm.adb.forward(status.agentPort, status.agentPort);
106
+ steps.push(`Port forward: tcp:${status.agentPort} -> tcp:${status.agentPort}`);
107
+ }
108
+ catch (err) {
109
+ steps.push(`Port forward warning: ${err instanceof Error ? err.message : String(err)}`);
110
+ }
111
+ // 6. Attempt connection (give the service a moment to start)
112
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
113
+ let agentConnected = false;
114
+ try {
115
+ await cm.agent.connect();
116
+ agentConnected = cm.agent.isConnected();
117
+ steps.push(agentConnected ? "Agent connected" : "Agent not responding yet");
118
+ }
119
+ catch {
120
+ steps.push("Agent not responding yet — it may need a few seconds to initialize");
121
+ }
122
+ return {
123
+ content: [
124
+ {
125
+ type: "text",
126
+ text: JSON.stringify({
127
+ success: true,
128
+ agentConnected,
129
+ steps,
130
+ }),
131
+ },
132
+ ],
133
+ };
134
+ }
135
+ catch (err) {
136
+ return {
137
+ content: [
138
+ {
139
+ type: "text",
140
+ text: JSON.stringify({
141
+ success: false,
142
+ error: err instanceof Error ? err.message : String(err),
143
+ steps,
144
+ }),
145
+ },
146
+ ],
147
+ isError: true,
148
+ };
149
+ }
150
+ });
151
+ // ---------- agent_status ----------
152
+ server.tool("agent_status", "Report current SensAI agent connection state, capabilities, port forwarding, and device info.", {}, async () => {
153
+ try {
154
+ const connStatus = cm.getStatus();
155
+ const capabilities = cm.getAgentCapabilities();
156
+ // Check if the agent package is installed on the device
157
+ let installed = false;
158
+ try {
159
+ const pmOutput = await cm.adb.shell(`pm list packages ${AGENT_PACKAGE}`);
160
+ installed = pmOutput.includes(AGENT_PACKAGE);
161
+ }
162
+ catch {
163
+ // pm command failed — can't determine install status
164
+ }
165
+ // Gather device info if ADB is connected
166
+ let deviceInfo = null;
167
+ if (connStatus.adb) {
168
+ try {
169
+ deviceInfo = await cm.adb.getDeviceInfo();
170
+ }
171
+ catch {
172
+ // Best-effort
173
+ }
174
+ }
175
+ return {
176
+ content: [
177
+ {
178
+ type: "text",
179
+ text: JSON.stringify({
180
+ agent: {
181
+ connected: connStatus.agent,
182
+ installed,
183
+ capabilities,
184
+ },
185
+ connection: {
186
+ adb: connStatus.adb,
187
+ device: connStatus.device,
188
+ agentPort: connStatus.agentPort,
189
+ isEmulator: isEmulatorDevice(connStatus.device),
190
+ },
191
+ deviceInfo,
192
+ }),
193
+ },
194
+ ],
195
+ };
196
+ }
197
+ catch (err) {
198
+ return {
199
+ content: [
200
+ {
201
+ type: "text",
202
+ text: `Agent status error: ${err instanceof Error ? err.message : String(err)}`,
203
+ },
204
+ ],
205
+ isError: true,
206
+ };
207
+ }
208
+ });
209
+ }
210
+ //# sourceMappingURL=agent-management.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-state.d.ts","sourceRoot":"","sources":["../../src/tools/app-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAe5E,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CAqKpF"}
1
+ {"version":3,"file":"app-state.d.ts","sourceRoot":"","sources":["../../src/tools/app-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAe5E,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CAgOpF"}
@@ -35,16 +35,73 @@ function registerAppStateTools(server, cm) {
35
35
  }, async (params) => {
36
36
  const categories = (params.categories ?? [...STATE_CATEGORIES]);
37
37
  const targetPackage = process.env.TARGET_PACKAGE ?? "com.emudebug.target";
38
- // Phase 2: agent provides full app state
38
+ // Phase 2: agent provides full app state via specific RPC calls
39
39
  if (cm.agent.isConnected()) {
40
40
  try {
41
- const result = await cm.agent.call("getAppState", {
42
- categories,
43
- asyncStorageKeys: params.asyncStorageKeys,
44
- queryCacheFilter: params.queryCacheFilter,
45
- });
41
+ const agentState = {};
42
+ if (categories.includes("asyncStorage")) {
43
+ agentState.asyncStorage = await cm.agent.call("rn.asyncStorage", {
44
+ keys: params.asyncStorageKeys,
45
+ });
46
+ }
47
+ if (categories.includes("queryCache")) {
48
+ agentState.queryCache = await cm.agent.call("rn.queryCache", {
49
+ filter: params.queryCacheFilter,
50
+ });
51
+ }
52
+ if (categories.includes("featureFlags") || categories.includes("theme")) {
53
+ // Feature flags and theme are typically stored in Redux or Context
54
+ try {
55
+ const reduxState = await cm.agent.call("rn.reduxState");
56
+ if (categories.includes("featureFlags")) {
57
+ agentState.featureFlags = reduxState;
58
+ }
59
+ if (categories.includes("theme")) {
60
+ agentState.theme = reduxState;
61
+ }
62
+ }
63
+ catch {
64
+ // Redux may not be available
65
+ if (categories.includes("featureFlags")) {
66
+ agentState.featureFlags = { note: "Redux state not available. App may use Context instead." };
67
+ }
68
+ if (categories.includes("theme")) {
69
+ agentState.theme = { note: "Redux state not available. App may use Context instead." };
70
+ }
71
+ }
72
+ }
73
+ if (categories.includes("navigation")) {
74
+ try {
75
+ agentState.navigation = await cm.agent.call("rn.context", {
76
+ name: "NavigationContainer",
77
+ });
78
+ }
79
+ catch {
80
+ agentState.navigation = { note: "Navigation context not found." };
81
+ }
82
+ }
83
+ if (categories.includes("auth")) {
84
+ try {
85
+ agentState.auth = await cm.agent.call("rn.context", {
86
+ name: "AuthContext",
87
+ });
88
+ }
89
+ catch {
90
+ agentState.auth = { note: "Auth context not found." };
91
+ }
92
+ }
93
+ if (categories.includes("offlineQueue")) {
94
+ try {
95
+ agentState.offlineQueue = await cm.agent.call("rn.asyncStorage", {
96
+ keys: ["offlineQueue"],
97
+ });
98
+ }
99
+ catch {
100
+ agentState.offlineQueue = { note: "Offline queue not found in AsyncStorage." };
101
+ }
102
+ }
46
103
  return {
47
- content: [{ type: "text", text: JSON.stringify(result) }],
104
+ content: [{ type: "text", text: JSON.stringify(agentState) }],
48
105
  };
49
106
  }
50
107
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAe5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CAc/E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAgB5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CAe/E"}
@@ -17,8 +17,9 @@ const diagnose_js_1 = require("./diagnose.js");
17
17
  const hot_reload_js_1 = require("./hot-reload.js");
18
18
  const smart_actions_js_1 = require("./smart-actions.js");
19
19
  const recording_js_1 = require("./recording.js");
20
+ const agent_management_js_1 = require("./agent-management.js");
20
21
  /**
21
- * Register all 26 EmuDebug tools with the MCP server.
22
+ * Register all 28 SensAI tools with the MCP server.
22
23
  *
23
24
  * Tools registered:
24
25
  * 1. get_ui_tree - UI hierarchy as JSON
@@ -47,6 +48,8 @@ const recording_js_1 = require("./recording.js");
47
48
  * 24. assert_screen - Quick screen assertions
48
49
  * 25. open_deep_link - Open app via deep link URL
49
50
  * 26. take_screenshot - Capture screen as base64 PNG
51
+ * 27. install_agent - Install SensAI agent APK via ADB
52
+ * 28. agent_status - Report agent connection state and capabilities
50
53
  */
51
54
  function registerAllTools(server, cm) {
52
55
  (0, ui_tree_js_1.registerUiTreeTools)(server, cm); // 3 tools: get_ui_tree, get_screen_text, get_element_details
@@ -62,5 +65,6 @@ function registerAllTools(server, cm) {
62
65
  (0, hot_reload_js_1.registerHotReloadTools)(server, cm); // 1 tool: hot_reload
63
66
  (0, smart_actions_js_1.registerSmartActionTools)(server, cm); // 7 tools: wait_for_text, scroll_to_text, fill_form, tap_and_wait, assert_screen, open_deep_link, take_screenshot
64
67
  (0, recording_js_1.registerRecordingTools)(server, cm); // 2 tools: start_recording, stop_recording
68
+ (0, agent_management_js_1.registerAgentManagementTools)(server, cm); // 2 tools: install_agent, agent_status
65
69
  }
66
70
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"interaction.d.ts","sourceRoot":"","sources":["../../src/tools/interaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAmE5E,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CA+WvF"}
1
+ {"version":3,"file":"interaction.d.ts","sourceRoot":"","sources":["../../src/tools/interaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAmE5E,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CA2cvF"}
@@ -132,6 +132,50 @@ function registerInteractionTools(server, cm) {
132
132
  }
133
133
  // Invalidate cache — screen will change after tap
134
134
  cm.uiCache.invalidate();
135
+ // Try agent first if connected (accessibility-based tap is more reliable)
136
+ if (cm.agent.isConnected()) {
137
+ try {
138
+ // Use text-based click if we have a text selector, otherwise coordinate-based
139
+ if (params.text && !params.x) {
140
+ const result = await cm.agent.call("ui.click", { text: params.text, resourceId: params.resourceId, contentDescription: params.contentDescription });
141
+ return {
142
+ content: [
143
+ {
144
+ type: "text",
145
+ text: JSON.stringify({
146
+ success: true,
147
+ tapped: { x: tapX, y: tapY },
148
+ matchedBy: matchInfo,
149
+ via: "agent (ui.click)",
150
+ agentResult: result,
151
+ }),
152
+ },
153
+ ],
154
+ };
155
+ }
156
+ else {
157
+ const result = await cm.agent.call("ui.clickAt", { x: tapX, y: tapY });
158
+ return {
159
+ content: [
160
+ {
161
+ type: "text",
162
+ text: JSON.stringify({
163
+ success: true,
164
+ tapped: { x: tapX, y: tapY },
165
+ matchedBy: matchInfo,
166
+ via: "agent (ui.clickAt)",
167
+ agentResult: result,
168
+ }),
169
+ },
170
+ ],
171
+ };
172
+ }
173
+ }
174
+ catch {
175
+ // Fall through to ADB
176
+ }
177
+ }
178
+ // ADB fallback
135
179
  await cm.adb.shell(`input tap ${tapX} ${tapY}`);
136
180
  return {
137
181
  content: [
@@ -141,6 +185,7 @@ function registerInteractionTools(server, cm) {
141
185
  success: true,
142
186
  tapped: { x: tapX, y: tapY },
143
187
  matchedBy: matchInfo,
188
+ via: "adb",
144
189
  }),
145
190
  },
146
191
  ],
@@ -166,6 +211,29 @@ function registerInteractionTools(server, cm) {
166
211
  clearFirst: zod_1.z.boolean().optional().describe("Clear the field before typing (default: false)"),
167
212
  }, async (params) => {
168
213
  try {
214
+ // Invalidate cache — screen content changes after typing
215
+ cm.uiCache.invalidate();
216
+ // Try agent first if connected (handles Unicode and special chars better)
217
+ if (cm.agent.isConnected()) {
218
+ try {
219
+ const result = await cm.agent.call("ui.type", {
220
+ text: params.text,
221
+ clearFirst: params.clearFirst ?? false,
222
+ });
223
+ return {
224
+ content: [
225
+ {
226
+ type: "text",
227
+ text: JSON.stringify({ ok: true, via: "agent (ui.type)", agentResult: result }),
228
+ },
229
+ ],
230
+ };
231
+ }
232
+ catch {
233
+ // Fall through to ADB
234
+ }
235
+ }
236
+ // ADB fallback
169
237
  // Optionally clear existing text
170
238
  if (params.clearFirst) {
171
239
  // Most reliable approach for React Native TextInput:
@@ -190,14 +258,12 @@ function registerInteractionTools(server, cm) {
190
258
  .replace(/\)/g, "\\)")
191
259
  .replace(/\$/g, "\\$")
192
260
  .replace(/`/g, "\\`");
193
- // Invalidate cache — screen content changes after typing
194
- cm.uiCache.invalidate();
195
261
  await cm.adb.shell(`input text ${escaped}`);
196
262
  return {
197
263
  content: [
198
264
  {
199
265
  type: "text",
200
- text: JSON.stringify({ ok: true }),
266
+ text: JSON.stringify({ ok: true, via: "adb" }),
201
267
  },
202
268
  ],
203
269
  };
@@ -228,12 +294,36 @@ function registerInteractionTools(server, cm) {
228
294
  const duration = params.durationMs ?? 300;
229
295
  // Invalidate cache — screen will change after swipe
230
296
  cm.uiCache.invalidate();
297
+ // Try agent first if connected
298
+ if (cm.agent.isConnected()) {
299
+ try {
300
+ const result = await cm.agent.call("ui.swipe", {
301
+ startX: params.startX,
302
+ startY: params.startY,
303
+ endX: params.endX,
304
+ endY: params.endY,
305
+ durationMs: duration,
306
+ });
307
+ return {
308
+ content: [
309
+ {
310
+ type: "text",
311
+ text: JSON.stringify({ ok: true, via: "agent (ui.swipe)", agentResult: result }),
312
+ },
313
+ ],
314
+ };
315
+ }
316
+ catch {
317
+ // Fall through to ADB
318
+ }
319
+ }
320
+ // ADB fallback
231
321
  await cm.adb.shell(`input swipe ${params.startX} ${params.startY} ${params.endX} ${params.endY} ${duration}`);
232
322
  return {
233
323
  content: [
234
324
  {
235
325
  type: "text",
236
- text: JSON.stringify({ ok: true }),
326
+ text: JSON.stringify({ ok: true, via: "adb" }),
237
327
  },
238
328
  ],
239
329
  };
@@ -1 +1 @@
1
- {"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../src/tools/network.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAG5E,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CAiInF"}
1
+ {"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../src/tools/network.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAG5E,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CA8InF"}
@@ -29,13 +29,27 @@ function registerNetworkTools(server, cm) {
29
29
  // Phase 2: full network interception via agent
30
30
  if (cm.agent.isConnected()) {
31
31
  try {
32
- const result = await cm.agent.call("getNetwork", {
33
- mode: params.mode,
34
- urlFilter: params.urlFilter,
35
- statusFilter: params.statusFilter,
36
- maxEntries,
37
- includeBody: params.includeBody ?? false,
38
- });
32
+ let result;
33
+ if (params.mode === "capture_start") {
34
+ // Install the network interceptor to begin capture
35
+ result = await cm.agent.call("network.install");
36
+ }
37
+ else if (params.mode === "capture_stop") {
38
+ // Retrieve captured requests and stats
39
+ const requests = await cm.agent.call("network.requests", {
40
+ urlFilter: params.urlFilter,
41
+ limit: maxEntries,
42
+ });
43
+ const stats = await cm.agent.call("network.stats");
44
+ result = { requests, stats };
45
+ }
46
+ else {
47
+ // "history" mode - get buffered requests
48
+ result = await cm.agent.call("network.buffer", {
49
+ urlFilter: params.urlFilter,
50
+ limit: maxEntries,
51
+ });
52
+ }
39
53
  return {
40
54
  content: [{ type: "text", text: JSON.stringify(result) }],
41
55
  };
@@ -1 +1 @@
1
- {"version":3,"file":"performance.d.ts","sourceRoot":"","sources":["../../src/tools/performance.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAgC5E,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CA8HvF"}
1
+ {"version":3,"file":"performance.d.ts","sourceRoot":"","sources":["../../src/tools/performance.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAgC5E,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CAuLvF"}
@@ -43,13 +43,64 @@ function registerPerformanceTools(server, cm) {
43
43
  // Phase 2: agent provides rich metrics
44
44
  if (cm.agent.isConnected()) {
45
45
  try {
46
- const result = await cm.agent.call("getPerformance", {
47
- metrics,
48
- durationSec,
49
- });
50
- return {
51
- content: [{ type: "text", text: JSON.stringify(result) }],
52
- };
46
+ const agentResult = {};
47
+ if (metrics.includes("memory")) {
48
+ agentResult.memory = await cm.agent.call("perf.memory");
49
+ }
50
+ if (metrics.includes("fps")) {
51
+ // Start frame monitor, wait for sampling, then get stats
52
+ await cm.agent.call("perf.startFrameMonitor");
53
+ await new Promise((r) => setTimeout(r, durationSec * 1000));
54
+ agentResult.fps = await cm.agent.call("perf.frameStats");
55
+ await cm.agent.call("perf.stopFrameMonitor");
56
+ }
57
+ if (metrics.includes("cpu")) {
58
+ // No dedicated agent method for CPU - will fall through to ADB below
59
+ }
60
+ if (metrics.includes("bridge")) {
61
+ // Bridge metrics via RN bridge interceptor
62
+ try {
63
+ agentResult.bridge = await cm.agent.call("rn.bridgeCalls", { limit: 50 });
64
+ }
65
+ catch {
66
+ agentResult.bridge = { note: "Bridge interceptor not installed. Call rn.installBridge first." };
67
+ }
68
+ }
69
+ // If we got at least some metrics from the agent, return them
70
+ // For CPU, fall through to ADB section below and merge
71
+ if (Object.keys(agentResult).length > 0 && !metrics.includes("cpu")) {
72
+ return {
73
+ content: [{ type: "text", text: JSON.stringify(agentResult) }],
74
+ };
75
+ }
76
+ // If CPU is requested, we need ADB for that - merge agent results into ADB path
77
+ if (metrics.includes("cpu") && Object.keys(agentResult).length > 0) {
78
+ // Fall through to ADB for CPU, but pre-populate result with agent data
79
+ const result = { ...agentResult };
80
+ // CPU via top (single snapshot)
81
+ try {
82
+ const raw = await cm.adb.shell(`top -b -n 1 -d ${durationSec} | grep -i "${targetPackage}"`);
83
+ const lines = raw.trim().split("\n").filter(Boolean);
84
+ const cpuEntries = lines.map((line) => {
85
+ const parts = line.trim().split(/\s+/);
86
+ return {
87
+ pid: parts[0],
88
+ cpu: parts.length > 8 ? parts[8] : "?",
89
+ mem: parts.length > 9 ? parts[9] : "?",
90
+ raw: line.trim(),
91
+ };
92
+ });
93
+ result.cpu = { processes: cpuEntries };
94
+ }
95
+ catch (err) {
96
+ result.cpu = {
97
+ error: `Failed to get CPU info: ${err instanceof Error ? err.message : String(err)}`,
98
+ };
99
+ }
100
+ return {
101
+ content: [{ type: "text", text: JSON.stringify(result) }],
102
+ };
103
+ }
53
104
  }
54
105
  catch {
55
106
  // Fall through to ADB
@@ -1 +1 @@
1
- {"version":3,"file":"rn-tools.d.ts","sourceRoot":"","sources":["../../src/tools/rn-tools.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAoB5E,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CAgH9E"}
1
+ {"version":3,"file":"rn-tools.d.ts","sourceRoot":"","sources":["../../src/tools/rn-tools.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAoB5E,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CA4G9E"}