@loadmill/droid-cua 2.18.0 → 2.21.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/README.md +52 -0
- package/build/index.js +109 -4
- package/build/src/browser/playwright/welcome-page.js +20 -0
- package/build/src/cli/aws-device-farm-app-upload.js +76 -0
- package/build/src/cli/cloud-status-line.js +98 -0
- package/build/src/cli/headless-runner.js +8 -7
- package/build/src/cli/headless-target.js +57 -22
- package/build/src/commands/loadmill.js +8 -1
- package/build/src/core/execution-engine.js +68 -0
- package/build/src/core/report/run-log-model.js +6 -0
- package/build/src/device/cloud/aws-device-farm/adapter.js +388 -0
- package/build/src/device/cloud/aws-device-farm/upload-cache.js +42 -0
- package/build/src/device/cloud/cloud-appium-client.js +2 -1
- package/build/src/device/cloud/connection.js +71 -14
- package/build/src/device/cloud/progress.js +24 -0
- package/build/src/device/cloud/registry.js +2 -1
- package/build/src/device/connection.js +3 -2
- package/build/src/device/loadmill.js +11 -0
- package/build/src/integrations/loadmill/executor.js +14 -6
- package/build/src/integrations/loadmill/interpreter.js +4 -4
- package/build/src/modes/execution-mode.js +16 -0
- package/package.json +4 -1
|
@@ -57,13 +57,13 @@ ${futureScript}`
|
|
|
57
57
|
}
|
|
58
58
|
];
|
|
59
59
|
}
|
|
60
|
-
async function selectRelevantParameterNames({ openAiClient, command, flowName, futureInstructions, parameters, }) {
|
|
60
|
+
async function selectRelevantParameterNames({ openAiClient, helperModel, command, flowName, futureInstructions, parameters, }) {
|
|
61
61
|
if (!openAiClient || !Array.isArray(parameters) || parameters.length === 0) {
|
|
62
62
|
return [];
|
|
63
63
|
}
|
|
64
64
|
try {
|
|
65
65
|
const response = await openAiClient.chat.completions.create({
|
|
66
|
-
model: LOADMILL_MODEL_ALIASES.DEFAULT_AGENT_MODEL,
|
|
66
|
+
model: helperModel || LOADMILL_MODEL_ALIASES.DEFAULT_AGENT_MODEL,
|
|
67
67
|
messages: buildParameterSelectionMessages({
|
|
68
68
|
command,
|
|
69
69
|
flowName,
|
|
@@ -98,7 +98,7 @@ function findSelectedSuiteFlowRun(finalResult, selectedFlow) {
|
|
|
98
98
|
}
|
|
99
99
|
return flowRuns.find((flowRun) => flowRun?.testSuiteFlowId === selectedFlow?.id) || null;
|
|
100
100
|
}
|
|
101
|
-
async function buildRuntimeParameterContext({ loadmillClient, finalResult, selectedFlow, command, flowName, futureInstructions, openAiClient, }) {
|
|
101
|
+
async function buildRuntimeParameterContext({ loadmillClient, finalResult, selectedFlow, command, flowName, futureInstructions, openAiClient, helperModel, }) {
|
|
102
102
|
const selectedSuiteFlowRun = findSelectedSuiteFlowRun(finalResult, selectedFlow);
|
|
103
103
|
if (!selectedSuiteFlowRun?.id || typeof loadmillClient.getTestSuiteFlowRun !== "function") {
|
|
104
104
|
return null;
|
|
@@ -112,6 +112,7 @@ async function buildRuntimeParameterContext({ loadmillClient, finalResult, selec
|
|
|
112
112
|
flowName,
|
|
113
113
|
futureInstructions,
|
|
114
114
|
parameters,
|
|
115
|
+
helperModel,
|
|
115
116
|
});
|
|
116
117
|
const candidates = selectLoadmillParameterCandidates(parameters, selectedParameterNames);
|
|
117
118
|
const note = renderLoadmillParameterContextNote(candidates);
|
|
@@ -198,7 +199,7 @@ async function pollForCompletion(runId, loadmillClient, options = {}) {
|
|
|
198
199
|
* @returns {Promise<{success: boolean, error?: string, result?: Object}>}
|
|
199
200
|
*/
|
|
200
201
|
export async function executeLoadmillCommand(userInput, options = {}) {
|
|
201
|
-
const { onProgress = () => { }, loadmillClient = createLoadmillApiClient(), openAiClient = null, futureInstructions = [], pollTimeoutMs, pollIntervalMs, now, sleepFn, } = options;
|
|
202
|
+
const { onProgress = () => { }, loadmillClient = createLoadmillApiClient(), openAiClient = null, helperModel = null, futureInstructions = [], pollTimeoutMs, pollIntervalMs, now, sleepFn, } = options;
|
|
202
203
|
// Check for API token
|
|
203
204
|
if (!loadmillClient.getApiToken()) {
|
|
204
205
|
return {
|
|
@@ -215,7 +216,10 @@ export async function executeLoadmillCommand(userInput, options = {}) {
|
|
|
215
216
|
try {
|
|
216
217
|
// Step 1: Interpret the command
|
|
217
218
|
onProgress({ step: "interpreting", message: "Interpreting command..." });
|
|
218
|
-
const interpreted = await interpretLoadmillCommand(userInput, {
|
|
219
|
+
const interpreted = await interpretLoadmillCommand(userInput, {
|
|
220
|
+
client: openAiClient,
|
|
221
|
+
...(helperModel ? { model: helperModel } : {}),
|
|
222
|
+
});
|
|
219
223
|
logger.debug("Loadmill command interpreted", interpreted);
|
|
220
224
|
// Step 2: Search for flows
|
|
221
225
|
onProgress({ step: "searching", message: `Searching for flows matching "${interpreted.searchQuery}"...` });
|
|
@@ -229,7 +233,10 @@ export async function executeLoadmillCommand(userInput, options = {}) {
|
|
|
229
233
|
}
|
|
230
234
|
// Step 3: Select best match
|
|
231
235
|
onProgress({ step: "selecting", message: `Found ${flows.length} flow(s). Selecting best match...` });
|
|
232
|
-
const { selectedFlow, confidence } = await selectBestFlow(flows, interpreted.searchQuery, {
|
|
236
|
+
const { selectedFlow, confidence } = await selectBestFlow(flows, interpreted.searchQuery, {
|
|
237
|
+
client: openAiClient,
|
|
238
|
+
...(helperModel ? { model: helperModel } : {}),
|
|
239
|
+
});
|
|
233
240
|
if (!selectedFlow) {
|
|
234
241
|
return {
|
|
235
242
|
success: false,
|
|
@@ -272,6 +279,7 @@ export async function executeLoadmillCommand(userInput, options = {}) {
|
|
|
272
279
|
flowName,
|
|
273
280
|
futureInstructions,
|
|
274
281
|
openAiClient,
|
|
282
|
+
helperModel,
|
|
275
283
|
})
|
|
276
284
|
: null;
|
|
277
285
|
return {
|
|
@@ -8,9 +8,9 @@ import { buildLoadmillCommandInterpretationMessages, buildLoadmillFlowSelectionM
|
|
|
8
8
|
* @param {string} userInput - Natural language command
|
|
9
9
|
* @returns {Promise<{searchQuery: string, parameters: Object}>}
|
|
10
10
|
*/
|
|
11
|
-
export async function interpretLoadmillCommand(userInput, { client }) {
|
|
11
|
+
export async function interpretLoadmillCommand(userInput, { client, model = LOADMILL_MODEL_ALIASES.FAST }) {
|
|
12
12
|
const response = await client.chat.completions.create({
|
|
13
|
-
model
|
|
13
|
+
model,
|
|
14
14
|
messages: buildLoadmillCommandInterpretationMessages(userInput),
|
|
15
15
|
response_format: { type: "json_object" }
|
|
16
16
|
});
|
|
@@ -27,7 +27,7 @@ export async function interpretLoadmillCommand(userInput, { client }) {
|
|
|
27
27
|
* @param {string} originalQuery - Original user query
|
|
28
28
|
* @returns {Promise<{selectedFlow: Object|null, confidence: number}>}
|
|
29
29
|
*/
|
|
30
|
-
export async function selectBestFlow(flows, originalQuery, { client }) {
|
|
30
|
+
export async function selectBestFlow(flows, originalQuery, { client, model = LOADMILL_MODEL_ALIASES.FAST }) {
|
|
31
31
|
// Ensure flows is an array
|
|
32
32
|
if (!flows || !Array.isArray(flows) || flows.length === 0) {
|
|
33
33
|
return { selectedFlow: null, confidence: 0 };
|
|
@@ -42,7 +42,7 @@ export async function selectBestFlow(flows, originalQuery, { client }) {
|
|
|
42
42
|
return `${i + 1}. ID: ${f.id}, Name: "${name}"${suite ? `, Suite: "${suite}"` : ""}`;
|
|
43
43
|
}).join("\n");
|
|
44
44
|
const response = await client.chat.completions.create({
|
|
45
|
-
model
|
|
45
|
+
model,
|
|
46
46
|
messages: buildLoadmillFlowSelectionMessages(originalQuery, flowList),
|
|
47
47
|
response_format: { type: "json_object" }
|
|
48
48
|
});
|
|
@@ -145,6 +145,19 @@ export class ExecutionMode {
|
|
|
145
145
|
});
|
|
146
146
|
return { success: false, error: 'Stopped by user' };
|
|
147
147
|
}
|
|
148
|
+
if (runContext.waitIfPaused) {
|
|
149
|
+
const pauseResult = await runContext.waitIfPaused({
|
|
150
|
+
boundary: 'before_instruction',
|
|
151
|
+
refreshScreenOnResume: false,
|
|
152
|
+
stepContext,
|
|
153
|
+
});
|
|
154
|
+
if (pauseResult?.status === 'stopped' || this.shouldStop) {
|
|
155
|
+
this.emit(addOutput, 'info', 'Test execution stopped by user.', runContext, stepContext, {
|
|
156
|
+
eventType: 'system_message'
|
|
157
|
+
});
|
|
158
|
+
return { success: false, error: 'Stopped by user' };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
148
161
|
const instruction = this.instructions[i];
|
|
149
162
|
this.emit(addOutput, 'user', instruction, runContext, stepContext, {
|
|
150
163
|
eventType: 'instruction_started',
|
|
@@ -256,6 +269,9 @@ export class ExecutionMode {
|
|
|
256
269
|
return this.shouldStop;
|
|
257
270
|
};
|
|
258
271
|
const { responseId: newResponseId, summary: turnSummary } = await this.engine.runFullTurn(response, trackAction, instructionContext, stepContext, { currentScreenshot });
|
|
272
|
+
if (turnSummary.stopped || this.shouldStop) {
|
|
273
|
+
return { success: false, error: "Stopped by user" };
|
|
274
|
+
}
|
|
259
275
|
if (turnSummary.assertionStatus === "passed") {
|
|
260
276
|
this.stats.assertionsPassed++;
|
|
261
277
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loadmill/droid-cua",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.21.0",
|
|
4
4
|
"description": "AI-powered Android testing agent using OpenAI's computer-use model and ADB",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
"bench:report:web": "node scripts/benchmark-report-web.js",
|
|
23
23
|
"execution:profile": "node scripts/execution-profile.js",
|
|
24
24
|
"test": "node --test unit-tests/*.test.js",
|
|
25
|
+
"test:aws-device-farm:android": "node integration-tests/aws-device-farm-android.test.js",
|
|
26
|
+
"test:aws-device-farm:ios": "node integration-tests/aws-device-farm-ios.test.js",
|
|
25
27
|
"build": "tsc",
|
|
26
28
|
"clean": "rm -rf build",
|
|
27
29
|
"typecheck": "tsc --noEmit",
|
|
@@ -58,6 +60,7 @@
|
|
|
58
60
|
"author": "Loadmill",
|
|
59
61
|
"license": "UNLICENSED",
|
|
60
62
|
"dependencies": {
|
|
63
|
+
"@aws-sdk/client-device-farm": "^3.1065.0",
|
|
61
64
|
"dotenv": "^16.4.7",
|
|
62
65
|
"ink": "^4.4.1",
|
|
63
66
|
"ink-spinner": "^5.0.0",
|