@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.
- package/dist/android-adapter.d.ts.map +1 -1
- package/dist/android-adapter.js +15 -3
- package/dist/tools/accessibility.js +1 -1
- package/dist/tools/agent-management.d.ts.map +1 -0
- package/dist/tools/agent-management.js +210 -0
- package/dist/tools/app-state.d.ts.map +1 -1
- package/dist/tools/app-state.js +64 -7
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +5 -1
- package/dist/tools/interaction.d.ts.map +1 -1
- package/dist/tools/interaction.js +94 -4
- package/dist/tools/network.d.ts.map +1 -1
- package/dist/tools/network.js +21 -7
- package/dist/tools/performance.d.ts.map +1 -1
- package/dist/tools/performance.js +58 -7
- package/dist/tools/rn-tools.d.ts.map +1 -1
- package/dist/tools/rn-tools.js +5 -9
- package/dist/tools/ui-tree.js +1 -1
- package/dist/transport/connection-manager.d.ts.map +1 -1
- package/dist/transport/connection-manager.js +23 -1
- package/package.json +2 -2
- package/src/android-adapter.ts +13 -4
- package/src/tools/accessibility.ts +1 -1
- package/src/tools/agent-management.ts +252 -0
- package/src/tools/app-state.ts +66 -7
- package/src/tools/index.ts +5 -1
- package/src/tools/interaction.ts +96 -4
- package/src/tools/network.ts +20 -7
- package/src/tools/performance.ts +64 -7
- package/src/tools/rn-tools.ts +5 -9
- package/src/tools/ui-tree.ts +1 -1
- package/src/transport/connection-manager.ts +34 -1
|
@@ -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,
|
|
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"}
|
package/dist/android-adapter.js
CHANGED
|
@@ -45,10 +45,21 @@ class AndroidAdapter {
|
|
|
45
45
|
}
|
|
46
46
|
async initialize() {
|
|
47
47
|
const status = await this.connectionManager.initialize();
|
|
48
|
-
// Update capabilities
|
|
48
|
+
// Update capabilities based on agent-reported capabilities (if available)
|
|
49
49
|
if (status.agent) {
|
|
50
|
-
this.
|
|
51
|
-
|
|
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("
|
|
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,
|
|
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"}
|
package/dist/tools/app-state.js
CHANGED
|
@@ -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
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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(
|
|
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;
|
|
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"}
|
package/dist/tools/index.js
CHANGED
|
@@ -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
|
|
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,
|
|
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,
|
|
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"}
|
package/dist/tools/network.js
CHANGED
|
@@ -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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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,
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
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,
|
|
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"}
|