misoai-web 1.0.1 → 1.0.3
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/LICENSE +21 -0
- package/README.md +8 -8
- package/bin/midscene-playground +2 -2
- package/package.json +23 -24
- package/dist/es/agent.js +0 -2451
- package/dist/es/agent.js.map +0 -1
- package/dist/es/bridge-mode-browser.js +0 -908
- package/dist/es/bridge-mode-browser.js.map +0 -1
- package/dist/es/bridge-mode.js +0 -2812
- package/dist/es/bridge-mode.js.map +0 -1
- package/dist/es/chrome-extension.js +0 -3152
- package/dist/es/chrome-extension.js.map +0 -1
- package/dist/es/index.js +0 -3052
- package/dist/es/index.js.map +0 -1
- package/dist/es/midscene-playground.js +0 -2781
- package/dist/es/midscene-playground.js.map +0 -1
- package/dist/es/midscene-server.js +0 -247
- package/dist/es/midscene-server.js.map +0 -1
- package/dist/es/playground.js +0 -2552
- package/dist/es/playground.js.map +0 -1
- package/dist/es/playwright-report.js +0 -120
- package/dist/es/playwright-report.js.map +0 -1
- package/dist/es/playwright.js +0 -2997
- package/dist/es/playwright.js.map +0 -1
- package/dist/es/puppeteer-agent-launcher.js +0 -2947
- package/dist/es/puppeteer-agent-launcher.js.map +0 -1
- package/dist/es/puppeteer.js +0 -2794
- package/dist/es/puppeteer.js.map +0 -1
- package/dist/es/ui-utils.js +0 -106
- package/dist/es/ui-utils.js.map +0 -1
- package/dist/es/utils.js +0 -197
- package/dist/es/utils.js.map +0 -1
- package/dist/es/yaml.js +0 -333
- package/dist/es/yaml.js.map +0 -1
- package/dist/lib/agent.js +0 -2466
- package/dist/lib/agent.js.map +0 -1
- package/dist/lib/bridge-mode-browser.js +0 -942
- package/dist/lib/bridge-mode-browser.js.map +0 -1
- package/dist/lib/bridge-mode.js +0 -2832
- package/dist/lib/bridge-mode.js.map +0 -1
- package/dist/lib/chrome-extension.js +0 -3169
- package/dist/lib/chrome-extension.js.map +0 -1
- package/dist/lib/index.js +0 -3071
- package/dist/lib/index.js.map +0 -1
- package/dist/lib/midscene-playground.js +0 -2785
- package/dist/lib/midscene-playground.js.map +0 -1
- package/dist/lib/midscene-server.js +0 -273
- package/dist/lib/midscene-server.js.map +0 -1
- package/dist/lib/playground.js +0 -2571
- package/dist/lib/playground.js.map +0 -1
- package/dist/lib/playwright-report.js +0 -148
- package/dist/lib/playwright-report.js.map +0 -1
- package/dist/lib/playwright.js +0 -3017
- package/dist/lib/playwright.js.map +0 -1
- package/dist/lib/puppeteer-agent-launcher.js +0 -2963
- package/dist/lib/puppeteer-agent-launcher.js.map +0 -1
- package/dist/lib/puppeteer.js +0 -2808
- package/dist/lib/puppeteer.js.map +0 -1
- package/dist/lib/ui-utils.js +0 -137
- package/dist/lib/ui-utils.js.map +0 -1
- package/dist/lib/utils.js +0 -235
- package/dist/lib/utils.js.map +0 -1
- package/dist/lib/yaml.js +0 -372
- package/dist/lib/yaml.js.map +0 -1
- package/dist/types/agent.d.ts +0 -254
- package/dist/types/bridge-mode-browser.d.ts +0 -9
- package/dist/types/bridge-mode.d.ts +0 -40
- package/dist/types/browser-d447695b.d.ts +0 -37
- package/dist/types/chrome-extension.d.ts +0 -18
- package/dist/types/index.d.ts +0 -16
- package/dist/types/midscene-playground.d.ts +0 -2
- package/dist/types/midscene-server.d.ts +0 -31
- package/dist/types/page-b8ada1f3.d.ts +0 -322
- package/dist/types/playground.d.ts +0 -17
- package/dist/types/playwright-report.d.ts +0 -11
- package/dist/types/playwright.d.ts +0 -87
- package/dist/types/puppeteer-agent-launcher.d.ts +0 -40
- package/dist/types/puppeteer.d.ts +0 -17
- package/dist/types/ui-utils.d.ts +0 -14
- package/dist/types/utils-badc824e.d.ts +0 -34
- package/dist/types/utils.d.ts +0 -8
- package/dist/types/yaml.d.ts +0 -15
@@ -1,2947 +0,0 @@
|
|
1
|
-
// src/puppeteer/agent-launcher.ts
|
2
|
-
import { readFileSync as readFileSync2 } from "fs";
|
3
|
-
import { getDebug as getDebug7 } from "misoai-shared/logger";
|
4
|
-
import { assert as assert9 } from "misoai-shared/utils";
|
5
|
-
|
6
|
-
// src/common/agent.ts
|
7
|
-
import { Insight } from "misoai-core";
|
8
|
-
import yaml4 from "js-yaml";
|
9
|
-
|
10
|
-
// src/yaml/player.ts
|
11
|
-
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
12
|
-
import { dirname, join, resolve } from "path";
|
13
|
-
import { assert, ifInBrowser } from "misoai-shared/utils";
|
14
|
-
import { getMidsceneRunSubDir } from "misoai-shared/common";
|
15
|
-
var ScriptPlayer = class {
|
16
|
-
constructor(script, setupAgent, onTaskStatusChange) {
|
17
|
-
this.script = script;
|
18
|
-
this.setupAgent = setupAgent;
|
19
|
-
this.onTaskStatusChange = onTaskStatusChange;
|
20
|
-
this.taskStatusList = [];
|
21
|
-
this.status = "init";
|
22
|
-
this.unnamedResultIndex = 0;
|
23
|
-
this.pageAgent = null;
|
24
|
-
this.result = {};
|
25
|
-
if (ifInBrowser) {
|
26
|
-
this.output = void 0;
|
27
|
-
} else if (script.target?.output) {
|
28
|
-
this.output = resolve(process.cwd(), script.target.output);
|
29
|
-
} else {
|
30
|
-
this.output = join(getMidsceneRunSubDir("output"), `${process.pid}.json`);
|
31
|
-
}
|
32
|
-
this.taskStatusList = (script.tasks || []).map((task, taskIndex) => ({
|
33
|
-
...task,
|
34
|
-
index: taskIndex,
|
35
|
-
status: "init",
|
36
|
-
totalSteps: task.flow?.length || 0
|
37
|
-
}));
|
38
|
-
}
|
39
|
-
setResult(key, value) {
|
40
|
-
const keyToUse = key || this.unnamedResultIndex++;
|
41
|
-
if (this.result[keyToUse]) {
|
42
|
-
console.warn(`result key ${keyToUse} already exists, will overwrite`);
|
43
|
-
}
|
44
|
-
this.result[keyToUse] = value;
|
45
|
-
this.flushResult();
|
46
|
-
}
|
47
|
-
setPlayerStatus(status, error) {
|
48
|
-
this.status = status;
|
49
|
-
this.errorInSetup = error;
|
50
|
-
}
|
51
|
-
notifyCurrentTaskStatusChange(taskIndex) {
|
52
|
-
const taskIndexToNotify = typeof taskIndex === "number" ? taskIndex : this.currentTaskIndex;
|
53
|
-
if (typeof taskIndexToNotify !== "number") {
|
54
|
-
return;
|
55
|
-
}
|
56
|
-
const taskStatus = this.taskStatusList[taskIndexToNotify];
|
57
|
-
if (this.onTaskStatusChange) {
|
58
|
-
this.onTaskStatusChange(taskStatus);
|
59
|
-
}
|
60
|
-
}
|
61
|
-
async setTaskStatus(index, statusValue, error) {
|
62
|
-
this.taskStatusList[index].status = statusValue;
|
63
|
-
if (error) {
|
64
|
-
this.taskStatusList[index].error = error;
|
65
|
-
}
|
66
|
-
this.notifyCurrentTaskStatusChange(index);
|
67
|
-
}
|
68
|
-
setTaskIndex(taskIndex) {
|
69
|
-
this.currentTaskIndex = taskIndex;
|
70
|
-
}
|
71
|
-
flushResult() {
|
72
|
-
if (Object.keys(this.result).length && this.output) {
|
73
|
-
const output = resolve(process.cwd(), this.output);
|
74
|
-
const outputDir = dirname(output);
|
75
|
-
if (!existsSync(outputDir)) {
|
76
|
-
mkdirSync(outputDir, { recursive: true });
|
77
|
-
}
|
78
|
-
writeFileSync(output, JSON.stringify(this.result, void 0, 2));
|
79
|
-
}
|
80
|
-
}
|
81
|
-
async playTask(taskStatus, agent) {
|
82
|
-
const { flow } = taskStatus;
|
83
|
-
assert(flow, "missing flow in task");
|
84
|
-
for (const flowItemIndex in flow) {
|
85
|
-
const currentStep = Number.parseInt(flowItemIndex, 10);
|
86
|
-
taskStatus.currentStep = currentStep;
|
87
|
-
const flowItem = flow[flowItemIndex];
|
88
|
-
if ("aiAction" in flowItem || "ai" in flowItem) {
|
89
|
-
const actionTask = flowItem;
|
90
|
-
const prompt = actionTask.aiAction || actionTask.ai;
|
91
|
-
assert(prompt, "missing prompt for ai (aiAction)");
|
92
|
-
assert(
|
93
|
-
typeof prompt === "string",
|
94
|
-
"prompt for aiAction must be a string"
|
95
|
-
);
|
96
|
-
await agent.aiAction(prompt);
|
97
|
-
} else if ("aiAssert" in flowItem) {
|
98
|
-
const assertTask = flowItem;
|
99
|
-
const prompt = assertTask.aiAssert;
|
100
|
-
assert(prompt, "missing prompt for aiAssert");
|
101
|
-
assert(
|
102
|
-
typeof prompt === "string",
|
103
|
-
"prompt for aiAssert must be a string"
|
104
|
-
);
|
105
|
-
await agent.aiAssert(prompt);
|
106
|
-
} else if ("aiQuery" in flowItem) {
|
107
|
-
const queryTask = flowItem;
|
108
|
-
const prompt = queryTask.aiQuery;
|
109
|
-
assert(prompt, "missing prompt for aiQuery");
|
110
|
-
assert(
|
111
|
-
typeof prompt === "string",
|
112
|
-
"prompt for aiQuery must be a string"
|
113
|
-
);
|
114
|
-
const queryResult = await agent.aiQuery(prompt);
|
115
|
-
this.setResult(queryTask.name, queryResult);
|
116
|
-
} else if ("aiNumber" in flowItem) {
|
117
|
-
const numberTask = flowItem;
|
118
|
-
const prompt = numberTask.aiNumber;
|
119
|
-
assert(prompt, "missing prompt for number");
|
120
|
-
assert(
|
121
|
-
typeof prompt === "string",
|
122
|
-
"prompt for number must be a string"
|
123
|
-
);
|
124
|
-
const numberResult = await agent.aiNumber(prompt);
|
125
|
-
this.setResult(numberTask.name, numberResult);
|
126
|
-
} else if ("aiString" in flowItem) {
|
127
|
-
const stringTask = flowItem;
|
128
|
-
const prompt = stringTask.aiString;
|
129
|
-
assert(prompt, "missing prompt for string");
|
130
|
-
assert(
|
131
|
-
typeof prompt === "string",
|
132
|
-
"prompt for string must be a string"
|
133
|
-
);
|
134
|
-
const stringResult = await agent.aiString(prompt);
|
135
|
-
this.setResult(stringTask.name, stringResult);
|
136
|
-
} else if ("aiBoolean" in flowItem) {
|
137
|
-
const booleanTask = flowItem;
|
138
|
-
const prompt = booleanTask.aiBoolean;
|
139
|
-
assert(prompt, "missing prompt for boolean");
|
140
|
-
assert(
|
141
|
-
typeof prompt === "string",
|
142
|
-
"prompt for boolean must be a string"
|
143
|
-
);
|
144
|
-
const booleanResult = await agent.aiBoolean(prompt);
|
145
|
-
this.setResult(booleanTask.name, booleanResult);
|
146
|
-
} else if ("aiLocate" in flowItem) {
|
147
|
-
const locateTask = flowItem;
|
148
|
-
const prompt = locateTask.aiLocate;
|
149
|
-
assert(prompt, "missing prompt for aiLocate");
|
150
|
-
assert(
|
151
|
-
typeof prompt === "string",
|
152
|
-
"prompt for aiLocate must be a string"
|
153
|
-
);
|
154
|
-
const locateResult = await agent.aiLocate(prompt);
|
155
|
-
this.setResult(locateTask.name, locateResult);
|
156
|
-
} else if ("aiWaitFor" in flowItem) {
|
157
|
-
const waitForTask = flowItem;
|
158
|
-
const prompt = waitForTask.aiWaitFor;
|
159
|
-
assert(prompt, "missing prompt for aiWaitFor");
|
160
|
-
assert(
|
161
|
-
typeof prompt === "string",
|
162
|
-
"prompt for aiWaitFor must be a string"
|
163
|
-
);
|
164
|
-
const timeout = waitForTask.timeout;
|
165
|
-
await agent.aiWaitFor(prompt, { timeoutMs: timeout });
|
166
|
-
} else if ("sleep" in flowItem) {
|
167
|
-
const sleepTask = flowItem;
|
168
|
-
const ms = sleepTask.sleep;
|
169
|
-
let msNumber = ms;
|
170
|
-
if (typeof ms === "string") {
|
171
|
-
msNumber = Number.parseInt(ms, 10);
|
172
|
-
}
|
173
|
-
assert(
|
174
|
-
msNumber && msNumber > 0,
|
175
|
-
`ms for sleep must be greater than 0, but got ${ms}`
|
176
|
-
);
|
177
|
-
await new Promise((resolve2) => setTimeout(resolve2, msNumber));
|
178
|
-
} else if ("aiTap" in flowItem) {
|
179
|
-
const tapTask = flowItem;
|
180
|
-
await agent.aiTap(tapTask.aiTap, tapTask);
|
181
|
-
} else if ("aiHover" in flowItem) {
|
182
|
-
const hoverTask = flowItem;
|
183
|
-
await agent.aiHover(hoverTask.aiHover, hoverTask);
|
184
|
-
} else if ("aiInput" in flowItem) {
|
185
|
-
const inputTask = flowItem;
|
186
|
-
await agent.aiInput(inputTask.aiInput, inputTask.locate, inputTask);
|
187
|
-
} else if ("aiKeyboardPress" in flowItem) {
|
188
|
-
const keyboardPressTask = flowItem;
|
189
|
-
await agent.aiKeyboardPress(
|
190
|
-
keyboardPressTask.aiKeyboardPress,
|
191
|
-
keyboardPressTask.locate,
|
192
|
-
keyboardPressTask
|
193
|
-
);
|
194
|
-
} else if ("aiScroll" in flowItem) {
|
195
|
-
const scrollTask = flowItem;
|
196
|
-
await agent.aiScroll(scrollTask, scrollTask.locate, scrollTask);
|
197
|
-
} else if ("javascript" in flowItem) {
|
198
|
-
const evaluateJavaScriptTask = flowItem;
|
199
|
-
const result = await agent.evaluateJavaScript(
|
200
|
-
evaluateJavaScriptTask.javascript
|
201
|
-
);
|
202
|
-
this.setResult(evaluateJavaScriptTask.name, result);
|
203
|
-
} else {
|
204
|
-
throw new Error(`unknown flowItem: ${JSON.stringify(flowItem)}`);
|
205
|
-
}
|
206
|
-
}
|
207
|
-
this.reportFile = agent.reportFile;
|
208
|
-
}
|
209
|
-
async run() {
|
210
|
-
const { target, web, android, tasks } = this.script;
|
211
|
-
const webEnv = web || target;
|
212
|
-
const androidEnv = android;
|
213
|
-
const platform = webEnv || androidEnv;
|
214
|
-
this.setPlayerStatus("running");
|
215
|
-
let agent = null;
|
216
|
-
let freeFn = [];
|
217
|
-
try {
|
218
|
-
const { agent: newAgent, freeFn: newFreeFn } = await this.setupAgent(
|
219
|
-
platform
|
220
|
-
);
|
221
|
-
agent = newAgent;
|
222
|
-
const originalOnTaskStartTip = agent.onTaskStartTip;
|
223
|
-
agent.onTaskStartTip = (tip) => {
|
224
|
-
if (this.status === "running") {
|
225
|
-
this.agentStatusTip = tip;
|
226
|
-
}
|
227
|
-
originalOnTaskStartTip?.(tip);
|
228
|
-
};
|
229
|
-
freeFn = [
|
230
|
-
...newFreeFn || [],
|
231
|
-
{
|
232
|
-
name: "restore-agent-onTaskStartTip",
|
233
|
-
fn: () => {
|
234
|
-
if (agent) {
|
235
|
-
agent.onTaskStartTip = originalOnTaskStartTip;
|
236
|
-
}
|
237
|
-
}
|
238
|
-
}
|
239
|
-
];
|
240
|
-
} catch (e) {
|
241
|
-
this.setPlayerStatus("error", e);
|
242
|
-
return;
|
243
|
-
}
|
244
|
-
this.pageAgent = agent;
|
245
|
-
let taskIndex = 0;
|
246
|
-
this.setPlayerStatus("running");
|
247
|
-
let errorFlag = false;
|
248
|
-
while (taskIndex < tasks.length) {
|
249
|
-
const taskStatus = this.taskStatusList[taskIndex];
|
250
|
-
this.setTaskStatus(taskIndex, "running");
|
251
|
-
this.setTaskIndex(taskIndex);
|
252
|
-
try {
|
253
|
-
await this.playTask(taskStatus, this.pageAgent);
|
254
|
-
this.setTaskStatus(taskIndex, "done");
|
255
|
-
} catch (e) {
|
256
|
-
this.setTaskStatus(taskIndex, "error", e);
|
257
|
-
if (taskStatus.continueOnError) {
|
258
|
-
} else {
|
259
|
-
this.reportFile = agent.reportFile;
|
260
|
-
errorFlag = true;
|
261
|
-
break;
|
262
|
-
}
|
263
|
-
}
|
264
|
-
this.reportFile = agent.reportFile;
|
265
|
-
taskIndex++;
|
266
|
-
}
|
267
|
-
if (errorFlag) {
|
268
|
-
this.setPlayerStatus("error");
|
269
|
-
} else {
|
270
|
-
this.setPlayerStatus("done");
|
271
|
-
}
|
272
|
-
this.agentStatusTip = "";
|
273
|
-
for (const fn of freeFn) {
|
274
|
-
try {
|
275
|
-
await fn.fn();
|
276
|
-
} catch (e) {
|
277
|
-
}
|
278
|
-
}
|
279
|
-
}
|
280
|
-
};
|
281
|
-
|
282
|
-
// src/yaml/builder.ts
|
283
|
-
import yaml from "js-yaml";
|
284
|
-
|
285
|
-
// src/yaml/utils.ts
|
286
|
-
import { assert as assert2 } from "misoai-shared/utils";
|
287
|
-
import yaml2 from "js-yaml";
|
288
|
-
function interpolateEnvVars(content) {
|
289
|
-
return content.replace(/\$\{([^}]+)\}/g, (_, envVar) => {
|
290
|
-
const value = process.env[envVar.trim()];
|
291
|
-
if (value === void 0) {
|
292
|
-
throw new Error(`Environment variable "${envVar.trim()}" is not defined`);
|
293
|
-
}
|
294
|
-
return value;
|
295
|
-
});
|
296
|
-
}
|
297
|
-
function parseYamlScript(content, filePath, ignoreCheckingTarget) {
|
298
|
-
const interpolatedContent = interpolateEnvVars(content);
|
299
|
-
const obj = yaml2.load(interpolatedContent);
|
300
|
-
const pathTip = filePath ? `, failed to load ${filePath}` : "";
|
301
|
-
const android = typeof obj.android !== "undefined" ? Object.assign({}, obj.android || {}) : void 0;
|
302
|
-
const webConfig = obj.web || obj.target;
|
303
|
-
const web = typeof webConfig !== "undefined" ? Object.assign({}, webConfig || {}) : void 0;
|
304
|
-
if (!ignoreCheckingTarget) {
|
305
|
-
assert2(
|
306
|
-
web || android,
|
307
|
-
`at least one of "target", "web", or "android" properties is required in yaml script${pathTip}`
|
308
|
-
);
|
309
|
-
assert2(
|
310
|
-
web && !android || !web && android,
|
311
|
-
`only one of "target", "web", or "android" properties is allowed in yaml script${pathTip}`
|
312
|
-
);
|
313
|
-
if (web || android) {
|
314
|
-
assert2(
|
315
|
-
typeof web === "object" || typeof android === "object",
|
316
|
-
`property "target/web/android" must be an object${pathTip}`
|
317
|
-
);
|
318
|
-
}
|
319
|
-
}
|
320
|
-
assert2(obj.tasks, `property "tasks" is required in yaml script ${pathTip}`);
|
321
|
-
assert2(
|
322
|
-
Array.isArray(obj.tasks),
|
323
|
-
`property "tasks" must be an array in yaml script, but got ${obj.tasks}`
|
324
|
-
);
|
325
|
-
return obj;
|
326
|
-
}
|
327
|
-
|
328
|
-
// src/common/agent.ts
|
329
|
-
import {
|
330
|
-
groupedActionDumpFileExt,
|
331
|
-
reportHTMLContent,
|
332
|
-
stringifyDumpData,
|
333
|
-
writeLogFile
|
334
|
-
} from "misoai-core/utils";
|
335
|
-
import {
|
336
|
-
DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT,
|
337
|
-
DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT
|
338
|
-
} from "misoai-shared/constants";
|
339
|
-
import { getAIConfigInBoolean, vlLocateMode } from "misoai-shared/env";
|
340
|
-
import { getDebug as getDebug4 } from "misoai-shared/logger";
|
341
|
-
import { assert as assert7 } from "misoai-shared/utils";
|
342
|
-
|
343
|
-
// src/common/tasks.ts
|
344
|
-
import {
|
345
|
-
Executor,
|
346
|
-
plan
|
347
|
-
} from "misoai-core";
|
348
|
-
import {
|
349
|
-
elementByPositionWithElementInfo as elementByPositionWithElementInfo2,
|
350
|
-
resizeImageForUiTars,
|
351
|
-
vlmPlanning
|
352
|
-
} from "misoai-core/ai-model";
|
353
|
-
import { sleep } from "misoai-core/utils";
|
354
|
-
import { NodeType } from "misoai-shared/constants";
|
355
|
-
import { getElementInfosScriptContent } from "misoai-shared/fs";
|
356
|
-
import { getDebug } from "misoai-shared/logger";
|
357
|
-
import { assert as assert4 } from "misoai-shared/utils";
|
358
|
-
|
359
|
-
// src/common/ui-utils.ts
|
360
|
-
function typeStr(task) {
|
361
|
-
return task.subType && task.subType !== "Plan" ? `${task.type} / ${task.subType || ""}` : task.type;
|
362
|
-
}
|
363
|
-
function getKeyCommands(value) {
|
364
|
-
const keys = Array.isArray(value) ? value : [value];
|
365
|
-
return keys.reduce((acc, k) => {
|
366
|
-
const includeMeta = keys.includes("Meta") || keys.includes("Control");
|
367
|
-
if (includeMeta && (k === "a" || k === "A")) {
|
368
|
-
return acc.concat([{ key: k, command: "SelectAll" }]);
|
369
|
-
}
|
370
|
-
if (includeMeta && (k === "c" || k === "C")) {
|
371
|
-
return acc.concat([{ key: k, command: "Copy" }]);
|
372
|
-
}
|
373
|
-
if (includeMeta && (k === "v" || k === "V")) {
|
374
|
-
return acc.concat([{ key: k, command: "Paste" }]);
|
375
|
-
}
|
376
|
-
return acc.concat([{ key: k }]);
|
377
|
-
}, []);
|
378
|
-
}
|
379
|
-
function locateParamStr(locate) {
|
380
|
-
if (!locate) {
|
381
|
-
return "";
|
382
|
-
}
|
383
|
-
if (typeof locate === "string") {
|
384
|
-
return locate;
|
385
|
-
}
|
386
|
-
return locate.prompt;
|
387
|
-
}
|
388
|
-
function scrollParamStr(scrollParam) {
|
389
|
-
if (!scrollParam) {
|
390
|
-
return "";
|
391
|
-
}
|
392
|
-
return `${scrollParam.direction || "down"}, ${scrollParam.scrollType || "once"}, ${scrollParam.distance || "distance-not-set"}`;
|
393
|
-
}
|
394
|
-
function taskTitleStr(type, prompt) {
|
395
|
-
if (prompt) {
|
396
|
-
return `${type} - ${prompt}`;
|
397
|
-
}
|
398
|
-
return type;
|
399
|
-
}
|
400
|
-
function paramStr(task) {
|
401
|
-
let value;
|
402
|
-
if (task.type === "Planning") {
|
403
|
-
value = task?.param?.userInstruction;
|
404
|
-
}
|
405
|
-
if (task.type === "Insight") {
|
406
|
-
value = task?.param?.prompt || task?.param?.id || task?.param?.dataDemand || task?.param?.assertion;
|
407
|
-
}
|
408
|
-
if (task.type === "Action") {
|
409
|
-
const locate = task?.locate;
|
410
|
-
const locateStr = locate ? locateParamStr(locate) : "";
|
411
|
-
value = task.thought || "";
|
412
|
-
if (typeof task?.param?.timeMs === "number") {
|
413
|
-
value = `${task?.param?.timeMs}ms`;
|
414
|
-
} else if (typeof task?.param?.scrollType === "string") {
|
415
|
-
value = scrollParamStr(task?.param);
|
416
|
-
} else if (typeof task?.param?.value !== "undefined") {
|
417
|
-
value = task?.param?.value;
|
418
|
-
}
|
419
|
-
if (locateStr) {
|
420
|
-
if (value) {
|
421
|
-
value = `${locateStr} - ${value}`;
|
422
|
-
} else {
|
423
|
-
value = locateStr;
|
424
|
-
}
|
425
|
-
}
|
426
|
-
}
|
427
|
-
if (typeof value === "undefined")
|
428
|
-
return "";
|
429
|
-
return typeof value === "string" ? value : JSON.stringify(value, void 0, 2);
|
430
|
-
}
|
431
|
-
|
432
|
-
// src/common/utils.ts
|
433
|
-
import { elementByPositionWithElementInfo } from "misoai-core/ai-model";
|
434
|
-
import { uploadTestInfoToServer } from "misoai-core/utils";
|
435
|
-
import { MIDSCENE_REPORT_TAG_NAME, getAIConfig } from "misoai-shared/env";
|
436
|
-
import {
|
437
|
-
generateElementByPosition,
|
438
|
-
getNodeFromCacheList,
|
439
|
-
traverseTree,
|
440
|
-
treeToList
|
441
|
-
} from "misoai-shared/extractor";
|
442
|
-
import { resizeImgBase64 } from "misoai-shared/img";
|
443
|
-
import { assert as assert3, logMsg, uuid } from "misoai-shared/utils";
|
444
|
-
import dayjs from "dayjs";
|
445
|
-
|
446
|
-
// src/web-element.ts
|
447
|
-
var WebElementInfo = class {
|
448
|
-
constructor({
|
449
|
-
content,
|
450
|
-
rect,
|
451
|
-
// page,
|
452
|
-
locator,
|
453
|
-
id,
|
454
|
-
attributes,
|
455
|
-
indexId,
|
456
|
-
xpaths
|
457
|
-
}) {
|
458
|
-
this.content = content;
|
459
|
-
this.rect = rect;
|
460
|
-
this.center = [
|
461
|
-
Math.floor(rect.left + rect.width / 2),
|
462
|
-
Math.floor(rect.top + rect.height / 2)
|
463
|
-
];
|
464
|
-
this.locator = locator;
|
465
|
-
this.id = id;
|
466
|
-
this.attributes = attributes;
|
467
|
-
this.indexId = indexId;
|
468
|
-
this.xpaths = xpaths;
|
469
|
-
}
|
470
|
-
};
|
471
|
-
|
472
|
-
// src/common/utils.ts
|
473
|
-
async function parseContextFromWebPage(page, _opt) {
|
474
|
-
assert3(page, "page is required");
|
475
|
-
if (page._forceUsePageContext) {
|
476
|
-
return await page._forceUsePageContext();
|
477
|
-
}
|
478
|
-
const url = await page.url();
|
479
|
-
uploadTestInfoToServer({ testUrl: url });
|
480
|
-
let screenshotBase64;
|
481
|
-
let tree;
|
482
|
-
await Promise.all([
|
483
|
-
page.screenshotBase64().then((base64) => {
|
484
|
-
screenshotBase64 = base64;
|
485
|
-
}),
|
486
|
-
page.getElementsNodeTree().then(async (treeRoot) => {
|
487
|
-
tree = treeRoot;
|
488
|
-
})
|
489
|
-
]);
|
490
|
-
const webTree = traverseTree(tree, (elementInfo) => {
|
491
|
-
const { rect, id, content, attributes, locator, indexId } = elementInfo;
|
492
|
-
return new WebElementInfo({
|
493
|
-
rect,
|
494
|
-
locator,
|
495
|
-
id,
|
496
|
-
content,
|
497
|
-
attributes,
|
498
|
-
indexId
|
499
|
-
});
|
500
|
-
});
|
501
|
-
assert3(screenshotBase64, "screenshotBase64 is required");
|
502
|
-
const elementsInfo = treeToList(webTree);
|
503
|
-
const size = await page.size();
|
504
|
-
if (size.dpr && size.dpr > 1) {
|
505
|
-
screenshotBase64 = await resizeImgBase64(screenshotBase64, {
|
506
|
-
width: size.width,
|
507
|
-
height: size.height
|
508
|
-
});
|
509
|
-
}
|
510
|
-
return {
|
511
|
-
content: elementsInfo,
|
512
|
-
tree: webTree,
|
513
|
-
size,
|
514
|
-
screenshotBase64,
|
515
|
-
url
|
516
|
-
};
|
517
|
-
}
|
518
|
-
function reportFileName(tag = "web") {
|
519
|
-
const reportTagName = getAIConfig(MIDSCENE_REPORT_TAG_NAME);
|
520
|
-
const dateTimeInFileName = dayjs().format("YYYY-MM-DD_HH-mm-ss");
|
521
|
-
const uniqueId = uuid().substring(0, 8);
|
522
|
-
return `${reportTagName || tag}-${dateTimeInFileName}-${uniqueId}`;
|
523
|
-
}
|
524
|
-
function printReportMsg(filepath) {
|
525
|
-
logMsg(`Midscene - report file updated: ${filepath}`);
|
526
|
-
}
|
527
|
-
function replaceIllegalPathCharsAndSpace(str) {
|
528
|
-
return str.replace(/[/\\:*?"<>| ]/g, "-");
|
529
|
-
}
|
530
|
-
function forceClosePopup(page, debug6) {
|
531
|
-
page.on("popup", async (popup) => {
|
532
|
-
if (!popup) {
|
533
|
-
console.warn("got a popup event, but the popup is not ready yet, skip");
|
534
|
-
return;
|
535
|
-
}
|
536
|
-
const url = await popup.url();
|
537
|
-
console.log(`Popup opened: ${url}`);
|
538
|
-
if (!popup.isClosed()) {
|
539
|
-
try {
|
540
|
-
await popup.close();
|
541
|
-
} catch (error) {
|
542
|
-
debug6(`failed to close popup ${url}, error: ${error}`);
|
543
|
-
}
|
544
|
-
} else {
|
545
|
-
debug6(`popup is already closed, skip close ${url}`);
|
546
|
-
}
|
547
|
-
if (!page.isClosed()) {
|
548
|
-
try {
|
549
|
-
await page.goto(url);
|
550
|
-
} catch (error) {
|
551
|
-
debug6(`failed to goto ${url}, error: ${error}`);
|
552
|
-
}
|
553
|
-
} else {
|
554
|
-
debug6(`page is already closed, skip goto ${url}`);
|
555
|
-
}
|
556
|
-
});
|
557
|
-
}
|
558
|
-
function matchElementFromPlan(planLocateParam, tree) {
|
559
|
-
if (!planLocateParam) {
|
560
|
-
return void 0;
|
561
|
-
}
|
562
|
-
if (planLocateParam.id) {
|
563
|
-
return getNodeFromCacheList(planLocateParam.id);
|
564
|
-
}
|
565
|
-
if (planLocateParam.bbox) {
|
566
|
-
const centerPosition = {
|
567
|
-
x: Math.floor((planLocateParam.bbox[0] + planLocateParam.bbox[2]) / 2),
|
568
|
-
y: Math.floor((planLocateParam.bbox[1] + planLocateParam.bbox[3]) / 2)
|
569
|
-
};
|
570
|
-
let element = elementByPositionWithElementInfo(tree, centerPosition);
|
571
|
-
if (!element) {
|
572
|
-
element = generateElementByPosition(centerPosition);
|
573
|
-
}
|
574
|
-
return element;
|
575
|
-
}
|
576
|
-
return void 0;
|
577
|
-
}
|
578
|
-
|
579
|
-
// src/common/tasks.ts
|
580
|
-
var debug = getDebug("page-task-executor");
|
581
|
-
var replanningCountLimit = 10;
|
582
|
-
var isAndroidPage = (page) => {
|
583
|
-
return page.pageType === "android";
|
584
|
-
};
|
585
|
-
var PageTaskExecutor = class {
|
586
|
-
constructor(page, insight, opts) {
|
587
|
-
this.conversationHistory = [];
|
588
|
-
this.page = page;
|
589
|
-
this.insight = insight;
|
590
|
-
this.taskCache = opts.taskCache;
|
591
|
-
this.onTaskStartCallback = opts?.onTaskStart;
|
592
|
-
}
|
593
|
-
async recordScreenshot(timing) {
|
594
|
-
const base64 = await this.page.screenshotBase64();
|
595
|
-
const item = {
|
596
|
-
type: "screenshot",
|
597
|
-
ts: Date.now(),
|
598
|
-
screenshot: base64,
|
599
|
-
timing
|
600
|
-
};
|
601
|
-
return item;
|
602
|
-
}
|
603
|
-
async getElementXpath(pageContext, element) {
|
604
|
-
let elementId = element?.id;
|
605
|
-
if (element?.attributes?.nodeType === NodeType.POSITION) {
|
606
|
-
await this.insight.contextRetrieverFn("locate");
|
607
|
-
const info = elementByPositionWithElementInfo2(
|
608
|
-
pageContext.tree,
|
609
|
-
{
|
610
|
-
x: element.center[0],
|
611
|
-
y: element.center[1]
|
612
|
-
},
|
613
|
-
{
|
614
|
-
requireStrictDistance: false,
|
615
|
-
filterPositionElements: true
|
616
|
-
}
|
617
|
-
);
|
618
|
-
if (info?.id) {
|
619
|
-
elementId = info.id;
|
620
|
-
}
|
621
|
-
}
|
622
|
-
if (!elementId) {
|
623
|
-
return void 0;
|
624
|
-
}
|
625
|
-
try {
|
626
|
-
const elementInfosScriptContent = getElementInfosScriptContent();
|
627
|
-
const result = await this.page.evaluateJavaScript?.(
|
628
|
-
`${elementInfosScriptContent}midscene_element_inspector.getXpathsById('${elementId}')`
|
629
|
-
);
|
630
|
-
return result;
|
631
|
-
} catch (error) {
|
632
|
-
debug("getXpathsById error: ", error);
|
633
|
-
}
|
634
|
-
}
|
635
|
-
prependExecutorWithScreenshot(taskApply, appendAfterExecution = false) {
|
636
|
-
const taskWithScreenshot = {
|
637
|
-
...taskApply,
|
638
|
-
executor: async (param, context, ...args) => {
|
639
|
-
const recorder = [];
|
640
|
-
const { task } = context;
|
641
|
-
task.recorder = recorder;
|
642
|
-
const shot = await this.recordScreenshot(`before ${task.type}`);
|
643
|
-
recorder.push(shot);
|
644
|
-
const result = await taskApply.executor(param, context, ...args);
|
645
|
-
if (taskApply.type === "Action") {
|
646
|
-
await Promise.all([
|
647
|
-
(async () => {
|
648
|
-
await sleep(100);
|
649
|
-
if (this.page.waitUntilNetworkIdle) {
|
650
|
-
try {
|
651
|
-
await this.page.waitUntilNetworkIdle();
|
652
|
-
} catch (error) {
|
653
|
-
}
|
654
|
-
}
|
655
|
-
})(),
|
656
|
-
sleep(200)
|
657
|
-
]);
|
658
|
-
}
|
659
|
-
if (appendAfterExecution) {
|
660
|
-
const shot2 = await this.recordScreenshot("after Action");
|
661
|
-
recorder.push(shot2);
|
662
|
-
}
|
663
|
-
return result;
|
664
|
-
}
|
665
|
-
};
|
666
|
-
return taskWithScreenshot;
|
667
|
-
}
|
668
|
-
async convertPlanToExecutable(plans) {
|
669
|
-
const tasks = [];
|
670
|
-
plans.forEach((plan2) => {
|
671
|
-
if (plan2.type === "Locate") {
|
672
|
-
if (plan2.locate === null || plan2.locate?.id === null || plan2.locate?.id === "null") {
|
673
|
-
return;
|
674
|
-
}
|
675
|
-
const taskFind = {
|
676
|
-
type: "Insight",
|
677
|
-
subType: "Locate",
|
678
|
-
param: plan2.locate || void 0,
|
679
|
-
thought: plan2.thought,
|
680
|
-
locate: plan2.locate,
|
681
|
-
executor: async (param, taskContext) => {
|
682
|
-
const { task } = taskContext;
|
683
|
-
assert4(
|
684
|
-
param?.prompt || param?.id || param?.bbox,
|
685
|
-
"No prompt or id or position or bbox to locate"
|
686
|
-
);
|
687
|
-
let insightDump;
|
688
|
-
let usage;
|
689
|
-
const dumpCollector = (dump) => {
|
690
|
-
insightDump = dump;
|
691
|
-
usage = dump?.taskInfo?.usage;
|
692
|
-
task.log = {
|
693
|
-
dump: insightDump
|
694
|
-
};
|
695
|
-
task.usage = usage;
|
696
|
-
};
|
697
|
-
this.insight.onceDumpUpdatedFn = dumpCollector;
|
698
|
-
const shotTime = Date.now();
|
699
|
-
const pageContext = await this.insight.contextRetrieverFn("locate");
|
700
|
-
task.pageContext = pageContext;
|
701
|
-
const recordItem = {
|
702
|
-
type: "screenshot",
|
703
|
-
ts: shotTime,
|
704
|
-
screenshot: pageContext.screenshotBase64,
|
705
|
-
timing: "before locate"
|
706
|
-
};
|
707
|
-
task.recorder = [recordItem];
|
708
|
-
let cacheHitFlag = false;
|
709
|
-
const cachePrompt = param.prompt;
|
710
|
-
const locateCacheRecord = this.taskCache?.matchLocateCache(cachePrompt);
|
711
|
-
const xpaths = locateCacheRecord?.cacheContent?.xpaths;
|
712
|
-
let elementFromCache = null;
|
713
|
-
try {
|
714
|
-
if (xpaths?.length && this.taskCache?.isCacheResultUsed && param?.cacheable !== false) {
|
715
|
-
const elementInfosScriptContent = getElementInfosScriptContent();
|
716
|
-
const element2 = await this.page.evaluateJavaScript?.(
|
717
|
-
`${elementInfosScriptContent}midscene_element_inspector.getElementInfoByXpath('${xpaths[0]}')`
|
718
|
-
);
|
719
|
-
if (element2?.id) {
|
720
|
-
elementFromCache = element2;
|
721
|
-
debug("cache hit, prompt: %s", cachePrompt);
|
722
|
-
cacheHitFlag = true;
|
723
|
-
debug(
|
724
|
-
"found a new new element with same xpath, xpath: %s, id: %s",
|
725
|
-
xpaths[0],
|
726
|
-
element2?.id
|
727
|
-
);
|
728
|
-
}
|
729
|
-
}
|
730
|
-
} catch (error) {
|
731
|
-
debug("get element info by xpath error: ", error);
|
732
|
-
}
|
733
|
-
const startTime = Date.now();
|
734
|
-
const element = elementFromCache || // try to match element from cache
|
735
|
-
matchElementFromPlan(param, pageContext.tree) || // try to match element from plan
|
736
|
-
(await this.insight.locate(param, {
|
737
|
-
context: pageContext
|
738
|
-
})).element;
|
739
|
-
const aiCost = Date.now() - startTime;
|
740
|
-
if (element && this.taskCache && !cacheHitFlag && param?.cacheable !== false) {
|
741
|
-
const elementXpaths = await this.getElementXpath(
|
742
|
-
pageContext,
|
743
|
-
element
|
744
|
-
);
|
745
|
-
if (elementXpaths) {
|
746
|
-
this.taskCache.updateOrAppendCacheRecord(
|
747
|
-
{
|
748
|
-
type: "locate",
|
749
|
-
prompt: cachePrompt,
|
750
|
-
xpaths: elementXpaths
|
751
|
-
},
|
752
|
-
locateCacheRecord
|
753
|
-
);
|
754
|
-
} else {
|
755
|
-
debug("no xpaths found, will not update cache", cachePrompt);
|
756
|
-
}
|
757
|
-
}
|
758
|
-
if (!element) {
|
759
|
-
throw new Error(`Element not found: ${param.prompt}`);
|
760
|
-
}
|
761
|
-
return {
|
762
|
-
output: {
|
763
|
-
element
|
764
|
-
},
|
765
|
-
pageContext,
|
766
|
-
cache: {
|
767
|
-
hit: cacheHitFlag
|
768
|
-
},
|
769
|
-
aiCost
|
770
|
-
};
|
771
|
-
}
|
772
|
-
};
|
773
|
-
tasks.push(taskFind);
|
774
|
-
} else if (plan2.type === "Assert" || plan2.type === "AssertWithoutThrow") {
|
775
|
-
const assertPlan = plan2;
|
776
|
-
const taskAssert = {
|
777
|
-
type: "Insight",
|
778
|
-
subType: "Assert",
|
779
|
-
param: assertPlan.param,
|
780
|
-
thought: assertPlan.thought,
|
781
|
-
locate: assertPlan.locate,
|
782
|
-
executor: async (param, taskContext) => {
|
783
|
-
const { task } = taskContext;
|
784
|
-
let insightDump;
|
785
|
-
const dumpCollector = (dump) => {
|
786
|
-
insightDump = dump;
|
787
|
-
};
|
788
|
-
this.insight.onceDumpUpdatedFn = dumpCollector;
|
789
|
-
const assertion = await this.insight.assert(
|
790
|
-
assertPlan.param.assertion
|
791
|
-
);
|
792
|
-
if (!assertion.pass) {
|
793
|
-
if (plan2.type === "Assert") {
|
794
|
-
task.output = assertion;
|
795
|
-
task.log = {
|
796
|
-
dump: insightDump
|
797
|
-
};
|
798
|
-
throw new Error(
|
799
|
-
assertion.thought || "Assertion failed without reason"
|
800
|
-
);
|
801
|
-
}
|
802
|
-
task.error = assertion.thought;
|
803
|
-
}
|
804
|
-
return {
|
805
|
-
output: assertion,
|
806
|
-
log: {
|
807
|
-
dump: insightDump
|
808
|
-
},
|
809
|
-
usage: assertion.usage
|
810
|
-
};
|
811
|
-
}
|
812
|
-
};
|
813
|
-
tasks.push(taskAssert);
|
814
|
-
} else if (plan2.type === "Input") {
|
815
|
-
const taskActionInput = {
|
816
|
-
type: "Action",
|
817
|
-
subType: "Input",
|
818
|
-
param: plan2.param,
|
819
|
-
thought: plan2.thought,
|
820
|
-
locate: plan2.locate,
|
821
|
-
executor: async (taskParam, { element }) => {
|
822
|
-
if (element) {
|
823
|
-
await this.page.clearInput(element);
|
824
|
-
if (!taskParam || !taskParam.value) {
|
825
|
-
return;
|
826
|
-
}
|
827
|
-
await this.page.keyboard.type(taskParam.value);
|
828
|
-
} else {
|
829
|
-
await this.page.keyboard.type(taskParam.value);
|
830
|
-
}
|
831
|
-
}
|
832
|
-
};
|
833
|
-
tasks.push(taskActionInput);
|
834
|
-
} else if (plan2.type === "KeyboardPress") {
|
835
|
-
const taskActionKeyboardPress = {
|
836
|
-
type: "Action",
|
837
|
-
subType: "KeyboardPress",
|
838
|
-
param: plan2.param,
|
839
|
-
thought: plan2.thought,
|
840
|
-
locate: plan2.locate,
|
841
|
-
executor: async (taskParam) => {
|
842
|
-
const keys = getKeyCommands(taskParam.value);
|
843
|
-
await this.page.keyboard.press(keys);
|
844
|
-
}
|
845
|
-
};
|
846
|
-
tasks.push(taskActionKeyboardPress);
|
847
|
-
} else if (plan2.type === "Tap") {
|
848
|
-
const taskActionTap = {
|
849
|
-
type: "Action",
|
850
|
-
subType: "Tap",
|
851
|
-
thought: plan2.thought,
|
852
|
-
locate: plan2.locate,
|
853
|
-
executor: async (param, { element }) => {
|
854
|
-
assert4(element, "Element not found, cannot tap");
|
855
|
-
await this.page.mouse.click(element.center[0], element.center[1]);
|
856
|
-
}
|
857
|
-
};
|
858
|
-
tasks.push(taskActionTap);
|
859
|
-
} else if (plan2.type === "Drag") {
|
860
|
-
const taskActionDrag = {
|
861
|
-
type: "Action",
|
862
|
-
subType: "Drag",
|
863
|
-
param: plan2.param,
|
864
|
-
thought: plan2.thought,
|
865
|
-
locate: plan2.locate,
|
866
|
-
executor: async (taskParam) => {
|
867
|
-
assert4(
|
868
|
-
taskParam?.start_box && taskParam?.end_box,
|
869
|
-
"No start_box or end_box to drag"
|
870
|
-
);
|
871
|
-
await this.page.mouse.drag(taskParam.start_box, taskParam.end_box);
|
872
|
-
}
|
873
|
-
};
|
874
|
-
tasks.push(taskActionDrag);
|
875
|
-
} else if (plan2.type === "Hover") {
|
876
|
-
const taskActionHover = {
|
877
|
-
type: "Action",
|
878
|
-
subType: "Hover",
|
879
|
-
thought: plan2.thought,
|
880
|
-
locate: plan2.locate,
|
881
|
-
executor: async (param, { element }) => {
|
882
|
-
assert4(element, "Element not found, cannot hover");
|
883
|
-
await this.page.mouse.move(element.center[0], element.center[1]);
|
884
|
-
}
|
885
|
-
};
|
886
|
-
tasks.push(taskActionHover);
|
887
|
-
} else if (plan2.type === "Scroll") {
|
888
|
-
const taskActionScroll = {
|
889
|
-
type: "Action",
|
890
|
-
subType: "Scroll",
|
891
|
-
param: plan2.param,
|
892
|
-
thought: plan2.thought,
|
893
|
-
locate: plan2.locate,
|
894
|
-
executor: async (taskParam, { element }) => {
|
895
|
-
const startingPoint = element ? {
|
896
|
-
left: element.center[0],
|
897
|
-
top: element.center[1]
|
898
|
-
} : void 0;
|
899
|
-
const scrollToEventName = taskParam?.scrollType;
|
900
|
-
if (scrollToEventName === "untilTop") {
|
901
|
-
await this.page.scrollUntilTop(startingPoint);
|
902
|
-
} else if (scrollToEventName === "untilBottom") {
|
903
|
-
await this.page.scrollUntilBottom(startingPoint);
|
904
|
-
} else if (scrollToEventName === "untilRight") {
|
905
|
-
await this.page.scrollUntilRight(startingPoint);
|
906
|
-
} else if (scrollToEventName === "untilLeft") {
|
907
|
-
await this.page.scrollUntilLeft(startingPoint);
|
908
|
-
} else if (scrollToEventName === "once" || !scrollToEventName) {
|
909
|
-
if (taskParam?.direction === "down" || !taskParam || !taskParam.direction) {
|
910
|
-
await this.page.scrollDown(
|
911
|
-
taskParam?.distance || void 0,
|
912
|
-
startingPoint
|
913
|
-
);
|
914
|
-
} else if (taskParam.direction === "up") {
|
915
|
-
await this.page.scrollUp(
|
916
|
-
taskParam.distance || void 0,
|
917
|
-
startingPoint
|
918
|
-
);
|
919
|
-
} else if (taskParam.direction === "left") {
|
920
|
-
await this.page.scrollLeft(
|
921
|
-
taskParam.distance || void 0,
|
922
|
-
startingPoint
|
923
|
-
);
|
924
|
-
} else if (taskParam.direction === "right") {
|
925
|
-
await this.page.scrollRight(
|
926
|
-
taskParam.distance || void 0,
|
927
|
-
startingPoint
|
928
|
-
);
|
929
|
-
} else {
|
930
|
-
throw new Error(
|
931
|
-
`Unknown scroll direction: ${taskParam.direction}`
|
932
|
-
);
|
933
|
-
}
|
934
|
-
await sleep(500);
|
935
|
-
} else {
|
936
|
-
throw new Error(
|
937
|
-
`Unknown scroll event type: ${scrollToEventName}, taskParam: ${JSON.stringify(
|
938
|
-
taskParam
|
939
|
-
)}`
|
940
|
-
);
|
941
|
-
}
|
942
|
-
}
|
943
|
-
};
|
944
|
-
tasks.push(taskActionScroll);
|
945
|
-
} else if (plan2.type === "Sleep") {
|
946
|
-
const taskActionSleep = {
|
947
|
-
type: "Action",
|
948
|
-
subType: "Sleep",
|
949
|
-
param: plan2.param,
|
950
|
-
thought: plan2.thought,
|
951
|
-
locate: plan2.locate,
|
952
|
-
executor: async (taskParam) => {
|
953
|
-
await sleep(taskParam?.timeMs || 3e3);
|
954
|
-
}
|
955
|
-
};
|
956
|
-
tasks.push(taskActionSleep);
|
957
|
-
} else if (plan2.type === "Error") {
|
958
|
-
const taskActionError = {
|
959
|
-
type: "Action",
|
960
|
-
subType: "Error",
|
961
|
-
param: plan2.param,
|
962
|
-
thought: plan2.thought || plan2.param?.thought,
|
963
|
-
locate: plan2.locate,
|
964
|
-
executor: async () => {
|
965
|
-
throw new Error(
|
966
|
-
plan2?.thought || plan2.param?.thought || "error without thought"
|
967
|
-
);
|
968
|
-
}
|
969
|
-
};
|
970
|
-
tasks.push(taskActionError);
|
971
|
-
} else if (plan2.type === "ExpectedFalsyCondition") {
|
972
|
-
const taskActionFalsyConditionStatement = {
|
973
|
-
type: "Action",
|
974
|
-
subType: "ExpectedFalsyCondition",
|
975
|
-
param: null,
|
976
|
-
thought: plan2.param?.reason,
|
977
|
-
locate: plan2.locate,
|
978
|
-
executor: async () => {
|
979
|
-
}
|
980
|
-
};
|
981
|
-
tasks.push(taskActionFalsyConditionStatement);
|
982
|
-
} else if (plan2.type === "Finished") {
|
983
|
-
const taskActionFinished = {
|
984
|
-
type: "Action",
|
985
|
-
subType: "Finished",
|
986
|
-
param: null,
|
987
|
-
thought: plan2.thought,
|
988
|
-
locate: plan2.locate,
|
989
|
-
executor: async (param) => {
|
990
|
-
}
|
991
|
-
};
|
992
|
-
tasks.push(taskActionFinished);
|
993
|
-
} else if (plan2.type === "AndroidHomeButton") {
|
994
|
-
const taskActionAndroidHomeButton = {
|
995
|
-
type: "Action",
|
996
|
-
subType: "AndroidHomeButton",
|
997
|
-
param: null,
|
998
|
-
thought: plan2.thought,
|
999
|
-
locate: plan2.locate,
|
1000
|
-
executor: async (param) => {
|
1001
|
-
assert4(
|
1002
|
-
isAndroidPage(this.page),
|
1003
|
-
"Cannot use home button on non-Android devices"
|
1004
|
-
);
|
1005
|
-
await this.page.home();
|
1006
|
-
}
|
1007
|
-
};
|
1008
|
-
tasks.push(taskActionAndroidHomeButton);
|
1009
|
-
} else if (plan2.type === "AndroidBackButton") {
|
1010
|
-
const taskActionAndroidBackButton = {
|
1011
|
-
type: "Action",
|
1012
|
-
subType: "AndroidBackButton",
|
1013
|
-
param: null,
|
1014
|
-
thought: plan2.thought,
|
1015
|
-
locate: plan2.locate,
|
1016
|
-
executor: async (param) => {
|
1017
|
-
assert4(
|
1018
|
-
isAndroidPage(this.page),
|
1019
|
-
"Cannot use back button on non-Android devices"
|
1020
|
-
);
|
1021
|
-
await this.page.back();
|
1022
|
-
}
|
1023
|
-
};
|
1024
|
-
tasks.push(taskActionAndroidBackButton);
|
1025
|
-
} else if (plan2.type === "AndroidRecentAppsButton") {
|
1026
|
-
const taskActionAndroidRecentAppsButton = {
|
1027
|
-
type: "Action",
|
1028
|
-
subType: "AndroidRecentAppsButton",
|
1029
|
-
param: null,
|
1030
|
-
thought: plan2.thought,
|
1031
|
-
locate: plan2.locate,
|
1032
|
-
executor: async (param) => {
|
1033
|
-
assert4(
|
1034
|
-
isAndroidPage(this.page),
|
1035
|
-
"Cannot use recent apps button on non-Android devices"
|
1036
|
-
);
|
1037
|
-
await this.page.recentApps();
|
1038
|
-
}
|
1039
|
-
};
|
1040
|
-
tasks.push(taskActionAndroidRecentAppsButton);
|
1041
|
-
} else {
|
1042
|
-
throw new Error(`Unknown or unsupported task type: ${plan2.type}`);
|
1043
|
-
}
|
1044
|
-
});
|
1045
|
-
const wrappedTasks = tasks.map(
|
1046
|
-
(task, index) => {
|
1047
|
-
if (task.type === "Action") {
|
1048
|
-
return this.prependExecutorWithScreenshot(
|
1049
|
-
task,
|
1050
|
-
index === tasks.length - 1
|
1051
|
-
);
|
1052
|
-
}
|
1053
|
-
return task;
|
1054
|
-
}
|
1055
|
-
);
|
1056
|
-
return {
|
1057
|
-
tasks: wrappedTasks
|
1058
|
-
};
|
1059
|
-
}
|
1060
|
-
async setupPlanningContext(executorContext) {
|
1061
|
-
const shotTime = Date.now();
|
1062
|
-
const pageContext = await this.insight.contextRetrieverFn("locate");
|
1063
|
-
const recordItem = {
|
1064
|
-
type: "screenshot",
|
1065
|
-
ts: shotTime,
|
1066
|
-
screenshot: pageContext.screenshotBase64,
|
1067
|
-
timing: "before planning"
|
1068
|
-
};
|
1069
|
-
executorContext.task.recorder = [recordItem];
|
1070
|
-
executorContext.task.pageContext = pageContext;
|
1071
|
-
return {
|
1072
|
-
pageContext
|
1073
|
-
};
|
1074
|
-
}
|
1075
|
-
async loadYamlFlowAsPlanning(userInstruction, yamlString) {
|
1076
|
-
const taskExecutor = new Executor(taskTitleStr("Action", userInstruction), {
|
1077
|
-
onTaskStart: this.onTaskStartCallback
|
1078
|
-
});
|
1079
|
-
const task = {
|
1080
|
-
type: "Planning",
|
1081
|
-
subType: "LoadYaml",
|
1082
|
-
locate: null,
|
1083
|
-
param: {
|
1084
|
-
userInstruction
|
1085
|
-
},
|
1086
|
-
executor: async (param, executorContext) => {
|
1087
|
-
await this.setupPlanningContext(executorContext);
|
1088
|
-
return {
|
1089
|
-
output: {
|
1090
|
-
actions: [],
|
1091
|
-
more_actions_needed_by_instruction: false,
|
1092
|
-
log: "",
|
1093
|
-
yamlString
|
1094
|
-
},
|
1095
|
-
cache: {
|
1096
|
-
hit: true
|
1097
|
-
}
|
1098
|
-
};
|
1099
|
-
}
|
1100
|
-
};
|
1101
|
-
await taskExecutor.append(task);
|
1102
|
-
await taskExecutor.flush();
|
1103
|
-
return {
|
1104
|
-
executor: taskExecutor
|
1105
|
-
};
|
1106
|
-
}
|
1107
|
-
planningTaskFromPrompt(userInstruction, log, actionContext) {
|
1108
|
-
const task = {
|
1109
|
-
type: "Planning",
|
1110
|
-
subType: "Plan",
|
1111
|
-
locate: null,
|
1112
|
-
param: {
|
1113
|
-
userInstruction,
|
1114
|
-
log
|
1115
|
-
},
|
1116
|
-
executor: async (param, executorContext) => {
|
1117
|
-
const startTime = Date.now();
|
1118
|
-
const { pageContext } = await this.setupPlanningContext(executorContext);
|
1119
|
-
const planResult = await plan(param.userInstruction, {
|
1120
|
-
context: pageContext,
|
1121
|
-
log: param.log,
|
1122
|
-
actionContext,
|
1123
|
-
pageType: this.page.pageType
|
1124
|
-
});
|
1125
|
-
const {
|
1126
|
-
actions,
|
1127
|
-
log: log2,
|
1128
|
-
more_actions_needed_by_instruction,
|
1129
|
-
error,
|
1130
|
-
usage,
|
1131
|
-
rawResponse,
|
1132
|
-
sleep: sleep3
|
1133
|
-
} = planResult;
|
1134
|
-
executorContext.task.log = {
|
1135
|
-
rawResponse
|
1136
|
-
};
|
1137
|
-
executorContext.task.usage = usage;
|
1138
|
-
let stopCollecting = false;
|
1139
|
-
let bboxCollected = false;
|
1140
|
-
let planParsingError = "";
|
1141
|
-
const finalActions = (actions || []).reduce(
|
1142
|
-
(acc, planningAction) => {
|
1143
|
-
if (stopCollecting) {
|
1144
|
-
return acc;
|
1145
|
-
}
|
1146
|
-
if (planningAction.locate) {
|
1147
|
-
if (bboxCollected && planningAction.locate.bbox) {
|
1148
|
-
delete planningAction.locate.bbox;
|
1149
|
-
}
|
1150
|
-
if (planningAction.locate.bbox) {
|
1151
|
-
bboxCollected = true;
|
1152
|
-
}
|
1153
|
-
acc.push({
|
1154
|
-
type: "Locate",
|
1155
|
-
locate: planningAction.locate,
|
1156
|
-
param: null,
|
1157
|
-
thought: planningAction.locate.prompt
|
1158
|
-
});
|
1159
|
-
} else if (["Tap", "Hover", "Input"].includes(planningAction.type)) {
|
1160
|
-
planParsingError = `invalid planning response: ${JSON.stringify(planningAction)}`;
|
1161
|
-
stopCollecting = true;
|
1162
|
-
return acc;
|
1163
|
-
}
|
1164
|
-
acc.push(planningAction);
|
1165
|
-
return acc;
|
1166
|
-
},
|
1167
|
-
[]
|
1168
|
-
);
|
1169
|
-
if (sleep3) {
|
1170
|
-
const timeNow = Date.now();
|
1171
|
-
const timeRemaining = sleep3 - (timeNow - startTime);
|
1172
|
-
if (timeRemaining > 0) {
|
1173
|
-
finalActions.push({
|
1174
|
-
type: "Sleep",
|
1175
|
-
param: {
|
1176
|
-
timeMs: timeRemaining
|
1177
|
-
},
|
1178
|
-
locate: null
|
1179
|
-
});
|
1180
|
-
}
|
1181
|
-
}
|
1182
|
-
if (finalActions.length === 0) {
|
1183
|
-
assert4(
|
1184
|
-
!more_actions_needed_by_instruction || sleep3,
|
1185
|
-
error ? `Failed to plan: ${error}` : planParsingError || "No plan found"
|
1186
|
-
);
|
1187
|
-
}
|
1188
|
-
return {
|
1189
|
-
output: {
|
1190
|
-
actions: finalActions,
|
1191
|
-
more_actions_needed_by_instruction,
|
1192
|
-
log: log2,
|
1193
|
-
yamlFlow: planResult.yamlFlow
|
1194
|
-
},
|
1195
|
-
cache: {
|
1196
|
-
hit: false
|
1197
|
-
},
|
1198
|
-
pageContext
|
1199
|
-
};
|
1200
|
-
}
|
1201
|
-
};
|
1202
|
-
return task;
|
1203
|
-
}
|
1204
|
-
planningTaskToGoal(userInstruction) {
|
1205
|
-
const task = {
|
1206
|
-
type: "Planning",
|
1207
|
-
subType: "Plan",
|
1208
|
-
locate: null,
|
1209
|
-
param: {
|
1210
|
-
userInstruction
|
1211
|
-
},
|
1212
|
-
executor: async (param, executorContext) => {
|
1213
|
-
const { pageContext } = await this.setupPlanningContext(executorContext);
|
1214
|
-
const imagePayload = await resizeImageForUiTars(
|
1215
|
-
pageContext.screenshotBase64,
|
1216
|
-
pageContext.size
|
1217
|
-
);
|
1218
|
-
this.appendConversationHistory({
|
1219
|
-
role: "user",
|
1220
|
-
content: [
|
1221
|
-
{
|
1222
|
-
type: "image_url",
|
1223
|
-
image_url: {
|
1224
|
-
url: imagePayload
|
1225
|
-
}
|
1226
|
-
}
|
1227
|
-
]
|
1228
|
-
});
|
1229
|
-
const startTime = Date.now();
|
1230
|
-
const planResult = await vlmPlanning({
|
1231
|
-
userInstruction: param.userInstruction,
|
1232
|
-
conversationHistory: this.conversationHistory,
|
1233
|
-
size: pageContext.size
|
1234
|
-
});
|
1235
|
-
const aiCost = Date.now() - startTime;
|
1236
|
-
const { actions, action_summary } = planResult;
|
1237
|
-
this.appendConversationHistory({
|
1238
|
-
role: "assistant",
|
1239
|
-
content: action_summary
|
1240
|
-
});
|
1241
|
-
return {
|
1242
|
-
output: {
|
1243
|
-
actions,
|
1244
|
-
thought: actions[0]?.thought,
|
1245
|
-
actionType: actions[0].type,
|
1246
|
-
more_actions_needed_by_instruction: true,
|
1247
|
-
log: "",
|
1248
|
-
yamlFlow: planResult.yamlFlow
|
1249
|
-
},
|
1250
|
-
cache: {
|
1251
|
-
hit: false
|
1252
|
-
},
|
1253
|
-
aiCost
|
1254
|
-
};
|
1255
|
-
}
|
1256
|
-
};
|
1257
|
-
return task;
|
1258
|
-
}
|
1259
|
-
async runPlans(title, plans) {
|
1260
|
-
const taskExecutor = new Executor(title, {
|
1261
|
-
onTaskStart: this.onTaskStartCallback
|
1262
|
-
});
|
1263
|
-
const { tasks } = await this.convertPlanToExecutable(plans);
|
1264
|
-
await taskExecutor.append(tasks);
|
1265
|
-
const result = await taskExecutor.flush();
|
1266
|
-
return {
|
1267
|
-
output: result,
|
1268
|
-
executor: taskExecutor
|
1269
|
-
};
|
1270
|
-
}
|
1271
|
-
async action(userPrompt, actionContext) {
|
1272
|
-
const taskExecutor = new Executor(taskTitleStr("Action", userPrompt), {
|
1273
|
-
onTaskStart: this.onTaskStartCallback
|
1274
|
-
});
|
1275
|
-
let planningTask = this.planningTaskFromPrompt(userPrompt, void 0, actionContext);
|
1276
|
-
let replanCount = 0;
|
1277
|
-
const logList = [];
|
1278
|
-
const yamlFlow = [];
|
1279
|
-
while (planningTask) {
|
1280
|
-
if (replanCount > replanningCountLimit) {
|
1281
|
-
const errorMsg = "Replanning too many times, please split the task into multiple steps";
|
1282
|
-
return this.appendErrorPlan(taskExecutor, errorMsg);
|
1283
|
-
}
|
1284
|
-
await taskExecutor.append(planningTask);
|
1285
|
-
const planResult = await taskExecutor.flush();
|
1286
|
-
if (taskExecutor.isInErrorState()) {
|
1287
|
-
return {
|
1288
|
-
output: planResult,
|
1289
|
-
executor: taskExecutor
|
1290
|
-
};
|
1291
|
-
}
|
1292
|
-
const plans = planResult.actions || [];
|
1293
|
-
yamlFlow.push(...planResult.yamlFlow || []);
|
1294
|
-
let executables;
|
1295
|
-
try {
|
1296
|
-
executables = await this.convertPlanToExecutable(plans);
|
1297
|
-
taskExecutor.append(executables.tasks);
|
1298
|
-
} catch (error) {
|
1299
|
-
return this.appendErrorPlan(
|
1300
|
-
taskExecutor,
|
1301
|
-
`Error converting plans to executable tasks: ${error}, plans: ${JSON.stringify(
|
1302
|
-
plans
|
1303
|
-
)}`
|
1304
|
-
);
|
1305
|
-
}
|
1306
|
-
await taskExecutor.flush();
|
1307
|
-
if (taskExecutor.isInErrorState()) {
|
1308
|
-
return {
|
1309
|
-
output: void 0,
|
1310
|
-
executor: taskExecutor
|
1311
|
-
};
|
1312
|
-
}
|
1313
|
-
if (planResult?.log) {
|
1314
|
-
logList.push(planResult.log);
|
1315
|
-
}
|
1316
|
-
if (!planResult.more_actions_needed_by_instruction) {
|
1317
|
-
planningTask = null;
|
1318
|
-
break;
|
1319
|
-
}
|
1320
|
-
planningTask = this.planningTaskFromPrompt(
|
1321
|
-
userPrompt,
|
1322
|
-
logList.length > 0 ? `- ${logList.join("\n- ")}` : void 0,
|
1323
|
-
actionContext
|
1324
|
-
);
|
1325
|
-
replanCount++;
|
1326
|
-
}
|
1327
|
-
return {
|
1328
|
-
output: {
|
1329
|
-
yamlFlow
|
1330
|
-
},
|
1331
|
-
executor: taskExecutor
|
1332
|
-
};
|
1333
|
-
}
|
1334
|
-
async actionToGoal(userPrompt) {
|
1335
|
-
const taskExecutor = new Executor(taskTitleStr("Action", userPrompt), {
|
1336
|
-
onTaskStart: this.onTaskStartCallback
|
1337
|
-
});
|
1338
|
-
this.conversationHistory = [];
|
1339
|
-
const isCompleted = false;
|
1340
|
-
let currentActionNumber = 0;
|
1341
|
-
const maxActionNumber = 40;
|
1342
|
-
const yamlFlow = [];
|
1343
|
-
while (!isCompleted && currentActionNumber < maxActionNumber) {
|
1344
|
-
currentActionNumber++;
|
1345
|
-
const planningTask = this.planningTaskToGoal(userPrompt);
|
1346
|
-
await taskExecutor.append(planningTask);
|
1347
|
-
const output = await taskExecutor.flush();
|
1348
|
-
if (taskExecutor.isInErrorState()) {
|
1349
|
-
return {
|
1350
|
-
output: void 0,
|
1351
|
-
executor: taskExecutor
|
1352
|
-
};
|
1353
|
-
}
|
1354
|
-
const plans = output.actions;
|
1355
|
-
yamlFlow.push(...output.yamlFlow || []);
|
1356
|
-
let executables;
|
1357
|
-
try {
|
1358
|
-
executables = await this.convertPlanToExecutable(plans);
|
1359
|
-
taskExecutor.append(executables.tasks);
|
1360
|
-
} catch (error) {
|
1361
|
-
return this.appendErrorPlan(
|
1362
|
-
taskExecutor,
|
1363
|
-
`Error converting plans to executable tasks: ${error}, plans: ${JSON.stringify(
|
1364
|
-
plans
|
1365
|
-
)}`
|
1366
|
-
);
|
1367
|
-
}
|
1368
|
-
await taskExecutor.flush();
|
1369
|
-
if (taskExecutor.isInErrorState()) {
|
1370
|
-
return {
|
1371
|
-
output: void 0,
|
1372
|
-
executor: taskExecutor
|
1373
|
-
};
|
1374
|
-
}
|
1375
|
-
if (plans[0].type === "Finished") {
|
1376
|
-
break;
|
1377
|
-
}
|
1378
|
-
}
|
1379
|
-
return {
|
1380
|
-
output: {
|
1381
|
-
yamlFlow
|
1382
|
-
},
|
1383
|
-
executor: taskExecutor
|
1384
|
-
};
|
1385
|
-
}
|
1386
|
-
async createTypeQueryTask(type, demand) {
|
1387
|
-
const taskExecutor = new Executor(
|
1388
|
-
taskTitleStr(
|
1389
|
-
type,
|
1390
|
-
typeof demand === "string" ? demand : JSON.stringify(demand)
|
1391
|
-
),
|
1392
|
-
{
|
1393
|
-
onTaskStart: this.onTaskStartCallback
|
1394
|
-
}
|
1395
|
-
);
|
1396
|
-
const queryTask = {
|
1397
|
-
type: "Insight",
|
1398
|
-
subType: type,
|
1399
|
-
locate: null,
|
1400
|
-
param: {
|
1401
|
-
dataDemand: demand
|
1402
|
-
// for user param presentation in report right sidebar
|
1403
|
-
},
|
1404
|
-
executor: async (param) => {
|
1405
|
-
let insightDump;
|
1406
|
-
const dumpCollector = (dump) => {
|
1407
|
-
insightDump = dump;
|
1408
|
-
};
|
1409
|
-
this.insight.onceDumpUpdatedFn = dumpCollector;
|
1410
|
-
const ifTypeRestricted = type !== "Query";
|
1411
|
-
let demandInput = demand;
|
1412
|
-
if (ifTypeRestricted) {
|
1413
|
-
demandInput = {
|
1414
|
-
result: `${type}, ${demand}`
|
1415
|
-
};
|
1416
|
-
}
|
1417
|
-
const { data, usage } = await this.insight.extract(demandInput);
|
1418
|
-
let outputResult = data;
|
1419
|
-
if (ifTypeRestricted) {
|
1420
|
-
assert4(data?.result !== void 0, "No result in query data");
|
1421
|
-
outputResult = data.result;
|
1422
|
-
}
|
1423
|
-
return {
|
1424
|
-
output: outputResult,
|
1425
|
-
log: { dump: insightDump },
|
1426
|
-
usage
|
1427
|
-
};
|
1428
|
-
}
|
1429
|
-
};
|
1430
|
-
await taskExecutor.append(this.prependExecutorWithScreenshot(queryTask));
|
1431
|
-
const output = await taskExecutor.flush();
|
1432
|
-
return {
|
1433
|
-
output,
|
1434
|
-
executor: taskExecutor
|
1435
|
-
};
|
1436
|
-
}
|
1437
|
-
async query(demand) {
|
1438
|
-
return this.createTypeQueryTask("Query", demand);
|
1439
|
-
}
|
1440
|
-
async boolean(prompt) {
|
1441
|
-
return this.createTypeQueryTask("Boolean", prompt);
|
1442
|
-
}
|
1443
|
-
async number(prompt) {
|
1444
|
-
return this.createTypeQueryTask("Number", prompt);
|
1445
|
-
}
|
1446
|
-
async string(prompt) {
|
1447
|
-
return this.createTypeQueryTask("String", prompt);
|
1448
|
-
}
|
1449
|
-
async assert(assertion) {
|
1450
|
-
const description = `assert: ${assertion}`;
|
1451
|
-
const taskExecutor = new Executor(taskTitleStr("Assert", description), {
|
1452
|
-
onTaskStart: this.onTaskStartCallback
|
1453
|
-
});
|
1454
|
-
const assertionPlan = {
|
1455
|
-
type: "Assert",
|
1456
|
-
param: {
|
1457
|
-
assertion
|
1458
|
-
},
|
1459
|
-
locate: null
|
1460
|
-
};
|
1461
|
-
const { tasks } = await this.convertPlanToExecutable([assertionPlan]);
|
1462
|
-
await taskExecutor.append(this.prependExecutorWithScreenshot(tasks[0]));
|
1463
|
-
const output = await taskExecutor.flush();
|
1464
|
-
return {
|
1465
|
-
output,
|
1466
|
-
executor: taskExecutor
|
1467
|
-
};
|
1468
|
-
}
|
1469
|
-
/**
|
1470
|
-
* Append a message to the conversation history
|
1471
|
-
* For user messages with images:
|
1472
|
-
* - Keep max 4 user image messages in history
|
1473
|
-
* - Remove oldest user image message when limit reached
|
1474
|
-
* For assistant messages:
|
1475
|
-
* - Simply append to history
|
1476
|
-
* @param conversationHistory Message to append
|
1477
|
-
*/
|
1478
|
-
appendConversationHistory(conversationHistory) {
|
1479
|
-
if (conversationHistory.role === "user") {
|
1480
|
-
const userImgItems = this.conversationHistory.filter(
|
1481
|
-
(item) => item.role === "user"
|
1482
|
-
);
|
1483
|
-
if (userImgItems.length >= 4 && conversationHistory.role === "user") {
|
1484
|
-
const firstUserImgIndex = this.conversationHistory.findIndex(
|
1485
|
-
(item) => item.role === "user"
|
1486
|
-
);
|
1487
|
-
if (firstUserImgIndex >= 0) {
|
1488
|
-
this.conversationHistory.splice(firstUserImgIndex, 1);
|
1489
|
-
}
|
1490
|
-
}
|
1491
|
-
}
|
1492
|
-
this.conversationHistory.push(conversationHistory);
|
1493
|
-
}
|
1494
|
-
async appendErrorPlan(taskExecutor, errorMsg) {
|
1495
|
-
const errorPlan = {
|
1496
|
-
type: "Error",
|
1497
|
-
param: {
|
1498
|
-
thought: errorMsg
|
1499
|
-
},
|
1500
|
-
locate: null
|
1501
|
-
};
|
1502
|
-
const { tasks } = await this.convertPlanToExecutable([errorPlan]);
|
1503
|
-
await taskExecutor.append(this.prependExecutorWithScreenshot(tasks[0]));
|
1504
|
-
await taskExecutor.flush();
|
1505
|
-
return {
|
1506
|
-
output: void 0,
|
1507
|
-
executor: taskExecutor
|
1508
|
-
};
|
1509
|
-
}
|
1510
|
-
async waitFor(assertion, opt) {
|
1511
|
-
const description = `waitFor: ${assertion}`;
|
1512
|
-
const taskExecutor = new Executor(taskTitleStr("WaitFor", description), {
|
1513
|
-
onTaskStart: this.onTaskStartCallback
|
1514
|
-
});
|
1515
|
-
const { timeoutMs, checkIntervalMs } = opt;
|
1516
|
-
assert4(assertion, "No assertion for waitFor");
|
1517
|
-
assert4(timeoutMs, "No timeoutMs for waitFor");
|
1518
|
-
assert4(checkIntervalMs, "No checkIntervalMs for waitFor");
|
1519
|
-
const overallStartTime = Date.now();
|
1520
|
-
let startTime = Date.now();
|
1521
|
-
let errorThought = "";
|
1522
|
-
while (Date.now() - overallStartTime < timeoutMs) {
|
1523
|
-
startTime = Date.now();
|
1524
|
-
const assertPlan = {
|
1525
|
-
type: "AssertWithoutThrow",
|
1526
|
-
param: {
|
1527
|
-
assertion
|
1528
|
-
},
|
1529
|
-
locate: null
|
1530
|
-
};
|
1531
|
-
const { tasks: assertTasks } = await this.convertPlanToExecutable([
|
1532
|
-
assertPlan
|
1533
|
-
]);
|
1534
|
-
await taskExecutor.append(
|
1535
|
-
this.prependExecutorWithScreenshot(assertTasks[0])
|
1536
|
-
);
|
1537
|
-
const output = await taskExecutor.flush();
|
1538
|
-
if (output?.pass) {
|
1539
|
-
return {
|
1540
|
-
output: void 0,
|
1541
|
-
executor: taskExecutor
|
1542
|
-
};
|
1543
|
-
}
|
1544
|
-
errorThought = output?.thought || `unknown error when waiting for assertion: ${assertion}`;
|
1545
|
-
const now = Date.now();
|
1546
|
-
if (now - startTime < checkIntervalMs) {
|
1547
|
-
const timeRemaining = checkIntervalMs - (now - startTime);
|
1548
|
-
const sleepPlan = {
|
1549
|
-
type: "Sleep",
|
1550
|
-
param: {
|
1551
|
-
timeMs: timeRemaining
|
1552
|
-
},
|
1553
|
-
locate: null
|
1554
|
-
};
|
1555
|
-
const { tasks: sleepTasks } = await this.convertPlanToExecutable([
|
1556
|
-
sleepPlan
|
1557
|
-
]);
|
1558
|
-
await taskExecutor.append(
|
1559
|
-
this.prependExecutorWithScreenshot(sleepTasks[0])
|
1560
|
-
);
|
1561
|
-
await taskExecutor.flush();
|
1562
|
-
}
|
1563
|
-
}
|
1564
|
-
return this.appendErrorPlan(
|
1565
|
-
taskExecutor,
|
1566
|
-
`waitFor timeout: ${errorThought}`
|
1567
|
-
);
|
1568
|
-
}
|
1569
|
-
};
|
1570
|
-
|
1571
|
-
// src/common/plan-builder.ts
|
1572
|
-
import { getDebug as getDebug2 } from "misoai-shared/logger";
|
1573
|
-
import { assert as assert5 } from "misoai-shared/utils";
|
1574
|
-
var debug2 = getDebug2("plan-builder");
|
1575
|
-
function buildPlans(type, locateParam, param) {
|
1576
|
-
let returnPlans = [];
|
1577
|
-
const locatePlan = locateParam ? {
|
1578
|
-
type: "Locate",
|
1579
|
-
locate: locateParam,
|
1580
|
-
param: locateParam,
|
1581
|
-
thought: ""
|
1582
|
-
} : null;
|
1583
|
-
if (type === "Tap" || type === "Hover") {
|
1584
|
-
assert5(locateParam, `missing locate info for action "${type}"`);
|
1585
|
-
assert5(locatePlan, `missing locate info for action "${type}"`);
|
1586
|
-
const tapPlan = {
|
1587
|
-
type,
|
1588
|
-
param: null,
|
1589
|
-
thought: "",
|
1590
|
-
locate: locateParam
|
1591
|
-
};
|
1592
|
-
returnPlans = [locatePlan, tapPlan];
|
1593
|
-
}
|
1594
|
-
if (type === "Input" || type === "KeyboardPress") {
|
1595
|
-
if (type === "Input") {
|
1596
|
-
assert5(locateParam, `missing locate info for action "${type}"`);
|
1597
|
-
}
|
1598
|
-
assert5(param, `missing param for action "${type}"`);
|
1599
|
-
const inputPlan = {
|
1600
|
-
type,
|
1601
|
-
param,
|
1602
|
-
thought: "",
|
1603
|
-
locate: locateParam
|
1604
|
-
};
|
1605
|
-
if (locatePlan) {
|
1606
|
-
returnPlans = [locatePlan, inputPlan];
|
1607
|
-
} else {
|
1608
|
-
returnPlans = [inputPlan];
|
1609
|
-
}
|
1610
|
-
}
|
1611
|
-
if (type === "Scroll") {
|
1612
|
-
assert5(param, `missing param for action "${type}"`);
|
1613
|
-
const scrollPlan = {
|
1614
|
-
type,
|
1615
|
-
param,
|
1616
|
-
thought: "",
|
1617
|
-
locate: locateParam
|
1618
|
-
};
|
1619
|
-
if (locatePlan) {
|
1620
|
-
returnPlans = [locatePlan, scrollPlan];
|
1621
|
-
} else {
|
1622
|
-
returnPlans = [scrollPlan];
|
1623
|
-
}
|
1624
|
-
}
|
1625
|
-
if (type === "Sleep") {
|
1626
|
-
assert5(param, `missing param for action "${type}"`);
|
1627
|
-
const sleepPlan = {
|
1628
|
-
type,
|
1629
|
-
param,
|
1630
|
-
thought: "",
|
1631
|
-
locate: null
|
1632
|
-
};
|
1633
|
-
returnPlans = [sleepPlan];
|
1634
|
-
}
|
1635
|
-
if (type === "Locate") {
|
1636
|
-
assert5(locateParam, `missing locate info for action "${type}"`);
|
1637
|
-
const locatePlan2 = {
|
1638
|
-
type,
|
1639
|
-
param: locateParam,
|
1640
|
-
locate: locateParam,
|
1641
|
-
thought: ""
|
1642
|
-
};
|
1643
|
-
returnPlans = [locatePlan2];
|
1644
|
-
}
|
1645
|
-
if (returnPlans) {
|
1646
|
-
debug2("buildPlans", returnPlans);
|
1647
|
-
return returnPlans;
|
1648
|
-
}
|
1649
|
-
throw new Error(`Not supported type: ${type}`);
|
1650
|
-
}
|
1651
|
-
|
1652
|
-
// src/common/task-cache.ts
|
1653
|
-
import assert6 from "assert";
|
1654
|
-
import { existsSync as existsSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
|
1655
|
-
import { join as join2 } from "path";
|
1656
|
-
import { getMidsceneRunSubDir as getMidsceneRunSubDir2 } from "misoai-shared/common";
|
1657
|
-
import { getDebug as getDebug3 } from "misoai-shared/logger";
|
1658
|
-
import { ifInBrowser as ifInBrowser2 } from "misoai-shared/utils";
|
1659
|
-
import yaml3 from "js-yaml";
|
1660
|
-
import semver from "semver";
|
1661
|
-
|
1662
|
-
// package.json
|
1663
|
-
var version = "1.0.0";
|
1664
|
-
|
1665
|
-
// src/common/task-cache.ts
|
1666
|
-
var debug3 = getDebug3("cache");
|
1667
|
-
var lowestSupportedMidsceneVersion = "0.16.10";
|
1668
|
-
var cacheFileExt = ".cache.yaml";
|
1669
|
-
var TaskCache = class {
|
1670
|
-
// Track matched records
|
1671
|
-
constructor(cacheId, isCacheResultUsed, cacheFilePath) {
|
1672
|
-
this.matchedCacheIndices = /* @__PURE__ */ new Set();
|
1673
|
-
assert6(cacheId, "cacheId is required");
|
1674
|
-
this.cacheId = replaceIllegalPathCharsAndSpace(cacheId);
|
1675
|
-
this.cacheFilePath = ifInBrowser2 ? void 0 : cacheFilePath || join2(getMidsceneRunSubDir2("cache"), `${this.cacheId}${cacheFileExt}`);
|
1676
|
-
this.isCacheResultUsed = isCacheResultUsed;
|
1677
|
-
let cacheContent;
|
1678
|
-
if (this.cacheFilePath) {
|
1679
|
-
cacheContent = this.loadCacheFromFile();
|
1680
|
-
}
|
1681
|
-
if (!cacheContent) {
|
1682
|
-
cacheContent = {
|
1683
|
-
midsceneVersion: version,
|
1684
|
-
cacheId: this.cacheId,
|
1685
|
-
caches: []
|
1686
|
-
};
|
1687
|
-
}
|
1688
|
-
this.cache = cacheContent;
|
1689
|
-
this.cacheOriginalLength = this.cache.caches.length;
|
1690
|
-
}
|
1691
|
-
matchCache(prompt, type) {
|
1692
|
-
for (let i = 0; i < this.cacheOriginalLength; i++) {
|
1693
|
-
const item = this.cache.caches[i];
|
1694
|
-
const key = `${type}:${prompt}:${i}`;
|
1695
|
-
if (item.type === type && item.prompt === prompt && !this.matchedCacheIndices.has(key)) {
|
1696
|
-
this.matchedCacheIndices.add(key);
|
1697
|
-
debug3(
|
1698
|
-
"cache found and marked as used, type: %s, prompt: %s, index: %d",
|
1699
|
-
type,
|
1700
|
-
prompt,
|
1701
|
-
i
|
1702
|
-
);
|
1703
|
-
return {
|
1704
|
-
cacheContent: item,
|
1705
|
-
updateFn: (cb) => {
|
1706
|
-
debug3(
|
1707
|
-
"will call updateFn to update cache, type: %s, prompt: %s, index: %d",
|
1708
|
-
type,
|
1709
|
-
prompt,
|
1710
|
-
i
|
1711
|
-
);
|
1712
|
-
cb(item);
|
1713
|
-
debug3(
|
1714
|
-
"cache updated, will flush to file, type: %s, prompt: %s, index: %d",
|
1715
|
-
type,
|
1716
|
-
prompt,
|
1717
|
-
i
|
1718
|
-
);
|
1719
|
-
this.flushCacheToFile();
|
1720
|
-
}
|
1721
|
-
};
|
1722
|
-
}
|
1723
|
-
}
|
1724
|
-
debug3("no unused cache found, type: %s, prompt: %s", type, prompt);
|
1725
|
-
return void 0;
|
1726
|
-
}
|
1727
|
-
matchPlanCache(prompt) {
|
1728
|
-
return this.matchCache(prompt, "plan");
|
1729
|
-
}
|
1730
|
-
matchLocateCache(prompt) {
|
1731
|
-
return this.matchCache(prompt, "locate");
|
1732
|
-
}
|
1733
|
-
appendCache(cache) {
|
1734
|
-
debug3("will append cache", cache);
|
1735
|
-
this.cache.caches.push(cache);
|
1736
|
-
this.flushCacheToFile();
|
1737
|
-
}
|
1738
|
-
loadCacheFromFile() {
|
1739
|
-
const cacheFile = this.cacheFilePath;
|
1740
|
-
assert6(cacheFile, "cache file path is required");
|
1741
|
-
if (!existsSync2(cacheFile)) {
|
1742
|
-
debug3("no cache file found, path: %s", cacheFile);
|
1743
|
-
return void 0;
|
1744
|
-
}
|
1745
|
-
const jsonTypeCacheFile = cacheFile.replace(cacheFileExt, ".json");
|
1746
|
-
if (existsSync2(jsonTypeCacheFile) && this.isCacheResultUsed) {
|
1747
|
-
console.warn(
|
1748
|
-
`An outdated cache file from an earlier version of Midscene has been detected. Since version 0.17, we have implemented an improved caching strategy. Please delete the old file located at: ${jsonTypeCacheFile}.`
|
1749
|
-
);
|
1750
|
-
return void 0;
|
1751
|
-
}
|
1752
|
-
try {
|
1753
|
-
const data = readFileSync(cacheFile, "utf8");
|
1754
|
-
const jsonData = yaml3.load(data);
|
1755
|
-
if (!version) {
|
1756
|
-
debug3("no midscene version info, will not read cache from file");
|
1757
|
-
return void 0;
|
1758
|
-
}
|
1759
|
-
if (semver.lt(jsonData.midsceneVersion, lowestSupportedMidsceneVersion) && !jsonData.midsceneVersion.includes("beta")) {
|
1760
|
-
console.warn(
|
1761
|
-
`You are using an old version of Midscene cache file, and we cannot match any info from it. Starting from Midscene v0.17, we changed our strategy to use xpath for cache info, providing better performance.
|
1762
|
-
Please delete the existing cache and rebuild it. Sorry for the inconvenience.
|
1763
|
-
cache file: ${cacheFile}`
|
1764
|
-
);
|
1765
|
-
return void 0;
|
1766
|
-
}
|
1767
|
-
debug3(
|
1768
|
-
"cache loaded from file, path: %s, cache version: %s, record length: %s",
|
1769
|
-
cacheFile,
|
1770
|
-
jsonData.midsceneVersion,
|
1771
|
-
jsonData.caches.length
|
1772
|
-
);
|
1773
|
-
jsonData.midsceneVersion = version;
|
1774
|
-
return jsonData;
|
1775
|
-
} catch (err) {
|
1776
|
-
debug3(
|
1777
|
-
"cache file exists but load failed, path: %s, error: %s",
|
1778
|
-
cacheFile,
|
1779
|
-
err
|
1780
|
-
);
|
1781
|
-
return void 0;
|
1782
|
-
}
|
1783
|
-
}
|
1784
|
-
flushCacheToFile() {
|
1785
|
-
if (!version) {
|
1786
|
-
debug3("no midscene version info, will not write cache to file");
|
1787
|
-
return;
|
1788
|
-
}
|
1789
|
-
if (!this.cacheFilePath) {
|
1790
|
-
debug3("no cache file path, will not write cache to file");
|
1791
|
-
return;
|
1792
|
-
}
|
1793
|
-
try {
|
1794
|
-
const yamlData = yaml3.dump(this.cache);
|
1795
|
-
writeFileSync2(this.cacheFilePath, yamlData);
|
1796
|
-
} catch (err) {
|
1797
|
-
debug3(
|
1798
|
-
"write cache to file failed, path: %s, error: %s",
|
1799
|
-
this.cacheFilePath,
|
1800
|
-
err
|
1801
|
-
);
|
1802
|
-
}
|
1803
|
-
}
|
1804
|
-
updateOrAppendCacheRecord(newRecord, cachedRecord) {
|
1805
|
-
if (cachedRecord) {
|
1806
|
-
if (newRecord.type === "plan") {
|
1807
|
-
cachedRecord.updateFn((cache) => {
|
1808
|
-
cache.yamlWorkflow = newRecord.yamlWorkflow;
|
1809
|
-
});
|
1810
|
-
} else {
|
1811
|
-
cachedRecord.updateFn((cache) => {
|
1812
|
-
cache.xpaths = newRecord.xpaths;
|
1813
|
-
});
|
1814
|
-
}
|
1815
|
-
} else {
|
1816
|
-
this.appendCache(newRecord);
|
1817
|
-
}
|
1818
|
-
}
|
1819
|
-
};
|
1820
|
-
|
1821
|
-
// src/common/agent.ts
|
1822
|
-
var debug4 = getDebug4("web-integration");
|
1823
|
-
var distanceOfTwoPoints = (p1, p2) => {
|
1824
|
-
const [x1, y1] = p1;
|
1825
|
-
const [x2, y2] = p2;
|
1826
|
-
return Math.round(Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2));
|
1827
|
-
};
|
1828
|
-
var includedInRect = (point, rect) => {
|
1829
|
-
const [x, y] = point;
|
1830
|
-
const { left, top, width, height } = rect;
|
1831
|
-
return x >= left && x <= left + width && y >= top && y <= top + height;
|
1832
|
-
};
|
1833
|
-
var PageAgent = class {
|
1834
|
-
constructor(page, opts) {
|
1835
|
-
/**
|
1836
|
-
* If true, the agent will not perform any actions
|
1837
|
-
*/
|
1838
|
-
this.dryMode = false;
|
1839
|
-
this.page = page;
|
1840
|
-
this.opts = Object.assign(
|
1841
|
-
{
|
1842
|
-
generateReport: true,
|
1843
|
-
autoPrintReportMsg: true,
|
1844
|
-
groupName: "Midscene Report",
|
1845
|
-
groupDescription: ""
|
1846
|
-
},
|
1847
|
-
opts || {}
|
1848
|
-
);
|
1849
|
-
if (this.page.pageType === "puppeteer" || this.page.pageType === "playwright") {
|
1850
|
-
this.page.waitForNavigationTimeout = this.opts.waitForNavigationTimeout || DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT;
|
1851
|
-
this.page.waitForNetworkIdleTimeout = this.opts.waitForNetworkIdleTimeout || DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT;
|
1852
|
-
}
|
1853
|
-
this.onTaskStartTip = this.opts.onTaskStartTip;
|
1854
|
-
this.insight = new Insight(
|
1855
|
-
async (action) => {
|
1856
|
-
return this.getUIContext(action);
|
1857
|
-
}
|
1858
|
-
);
|
1859
|
-
if (opts?.cacheId && this.page.pageType !== "android") {
|
1860
|
-
this.taskCache = new TaskCache(
|
1861
|
-
opts.cacheId,
|
1862
|
-
getAIConfigInBoolean("MIDSCENE_CACHE")
|
1863
|
-
// if we should use cache to match the element
|
1864
|
-
);
|
1865
|
-
}
|
1866
|
-
this.taskExecutor = new PageTaskExecutor(this.page, this.insight, {
|
1867
|
-
taskCache: this.taskCache,
|
1868
|
-
onTaskStart: this.callbackOnTaskStartTip.bind(this)
|
1869
|
-
});
|
1870
|
-
this.dump = this.resetDump();
|
1871
|
-
this.reportFileName = reportFileName(
|
1872
|
-
opts?.testId || this.page.pageType || "web"
|
1873
|
-
);
|
1874
|
-
}
|
1875
|
-
async getUIContext(action) {
|
1876
|
-
if (action && (action === "extract" || action === "assert" || action === "captcha")) {
|
1877
|
-
return await parseContextFromWebPage(this.page, {
|
1878
|
-
ignoreMarker: true
|
1879
|
-
});
|
1880
|
-
}
|
1881
|
-
return await parseContextFromWebPage(this.page, {
|
1882
|
-
ignoreMarker: !!vlLocateMode()
|
1883
|
-
});
|
1884
|
-
}
|
1885
|
-
// Helper method to call the insight.captcha method
|
1886
|
-
async _callInsightCaptcha(options) {
|
1887
|
-
const context = await this.getUIContext();
|
1888
|
-
if (this.page.url) {
|
1889
|
-
const url = await this.page.url();
|
1890
|
-
context.url = url;
|
1891
|
-
}
|
1892
|
-
return this.insight.captcha(context, options);
|
1893
|
-
}
|
1894
|
-
async setAIActionContext(prompt) {
|
1895
|
-
this.opts.aiActionContext = prompt;
|
1896
|
-
}
|
1897
|
-
resetDump() {
|
1898
|
-
this.dump = {
|
1899
|
-
groupName: this.opts.groupName,
|
1900
|
-
groupDescription: this.opts.groupDescription,
|
1901
|
-
executions: []
|
1902
|
-
};
|
1903
|
-
return this.dump;
|
1904
|
-
}
|
1905
|
-
appendExecutionDump(execution) {
|
1906
|
-
const currentDump = this.dump;
|
1907
|
-
currentDump.executions.push(execution);
|
1908
|
-
}
|
1909
|
-
dumpDataString() {
|
1910
|
-
this.dump.groupName = this.opts.groupName;
|
1911
|
-
this.dump.groupDescription = this.opts.groupDescription;
|
1912
|
-
return stringifyDumpData(this.dump);
|
1913
|
-
}
|
1914
|
-
reportHTMLString() {
|
1915
|
-
return reportHTMLContent(this.dumpDataString());
|
1916
|
-
}
|
1917
|
-
writeOutActionDumps() {
|
1918
|
-
const { generateReport, autoPrintReportMsg } = this.opts;
|
1919
|
-
this.reportFile = writeLogFile({
|
1920
|
-
fileName: this.reportFileName,
|
1921
|
-
fileExt: groupedActionDumpFileExt,
|
1922
|
-
fileContent: this.dumpDataString(),
|
1923
|
-
type: "dump",
|
1924
|
-
generateReport
|
1925
|
-
});
|
1926
|
-
debug4("writeOutActionDumps", this.reportFile);
|
1927
|
-
if (generateReport && autoPrintReportMsg && this.reportFile) {
|
1928
|
-
printReportMsg(this.reportFile);
|
1929
|
-
}
|
1930
|
-
}
|
1931
|
-
async callbackOnTaskStartTip(task) {
|
1932
|
-
const param = paramStr(task);
|
1933
|
-
const tip = param ? `${typeStr(task)} - ${param}` : typeStr(task);
|
1934
|
-
if (this.onTaskStartTip) {
|
1935
|
-
await this.onTaskStartTip(tip);
|
1936
|
-
}
|
1937
|
-
}
|
1938
|
-
afterTaskRunning(executor, doNotThrowError = false) {
|
1939
|
-
this.appendExecutionDump(executor.dump());
|
1940
|
-
this.writeOutActionDumps();
|
1941
|
-
if (executor.isInErrorState() && !doNotThrowError) {
|
1942
|
-
const errorTask = executor.latestErrorTask();
|
1943
|
-
throw new Error(`${errorTask?.error}`);
|
1944
|
-
}
|
1945
|
-
const lastTask = executor.tasks[executor.tasks.length - 1];
|
1946
|
-
const allThoughts = executor.tasks.filter((task) => task.thought).map((task) => task.thought);
|
1947
|
-
const allLocates = executor.tasks.filter((task) => task.locate).map((task) => task.locate);
|
1948
|
-
const allPlans = executor.tasks.filter((task) => task.param?.plans).map((task) => task.param?.plans);
|
1949
|
-
const planningTasks = executor.tasks.filter((task) => task.type === "Planning");
|
1950
|
-
const insightTasks = executor.tasks.filter((task) => task.type === "Insight");
|
1951
|
-
const actionTasks = executor.tasks.filter((task) => task.type === "Action");
|
1952
|
-
const planning = planningTasks.length > 0 ? {
|
1953
|
-
type: "Planning",
|
1954
|
-
description: `Planning for task execution`,
|
1955
|
-
steps: planningTasks.map((task) => task.thought || "Planning step")
|
1956
|
-
} : void 0;
|
1957
|
-
const insight = insightTasks.length > 0 ? {
|
1958
|
-
type: "Insight",
|
1959
|
-
description: `Insight for task execution`,
|
1960
|
-
elements: insightTasks.map((task) => task.thought || "Insight element")
|
1961
|
-
} : void 0;
|
1962
|
-
const action = actionTasks.length > 0 ? {
|
1963
|
-
type: "Action",
|
1964
|
-
description: `Action for task execution`,
|
1965
|
-
result: lastTask?.output
|
1966
|
-
} : void 0;
|
1967
|
-
const actionDetails = executor.tasks.map((task) => ({
|
1968
|
-
type: task.type,
|
1969
|
-
subType: task.subType,
|
1970
|
-
status: task.status,
|
1971
|
-
thought: task.thought
|
1972
|
-
}));
|
1973
|
-
const metadata = {
|
1974
|
-
status: lastTask?.status,
|
1975
|
-
start: lastTask?.timing?.start,
|
1976
|
-
end: lastTask?.timing?.end,
|
1977
|
-
totalTime: lastTask?.timing?.cost,
|
1978
|
-
cache: lastTask?.cache,
|
1979
|
-
usage: lastTask?.usage,
|
1980
|
-
thought: allThoughts.length > 0 ? allThoughts.join("\n") : lastTask?.thought,
|
1981
|
-
locate: allLocates.length > 0 ? allLocates : lastTask?.locate,
|
1982
|
-
plan: allPlans.length > 0 ? allPlans : lastTask?.param?.plans,
|
1983
|
-
// Add planning, insight, and action information
|
1984
|
-
planning,
|
1985
|
-
insight,
|
1986
|
-
action,
|
1987
|
-
actionDetails,
|
1988
|
-
// Include raw tasks for debugging
|
1989
|
-
tasks: executor.tasks.map((task) => ({
|
1990
|
-
type: task.type,
|
1991
|
-
subType: task.subType,
|
1992
|
-
status: task.status,
|
1993
|
-
thought: task.thought,
|
1994
|
-
locate: task.locate,
|
1995
|
-
timing: task.timing,
|
1996
|
-
usage: task.usage,
|
1997
|
-
cache: task.cache,
|
1998
|
-
error: task.error
|
1999
|
-
}))
|
2000
|
-
};
|
2001
|
-
return metadata;
|
2002
|
-
}
|
2003
|
-
buildDetailedLocateParam(locatePrompt, opt) {
|
2004
|
-
assert7(locatePrompt, "missing locate prompt");
|
2005
|
-
if (typeof opt === "object") {
|
2006
|
-
const prompt = opt.prompt || locatePrompt;
|
2007
|
-
const deepThink = opt.deepThink || false;
|
2008
|
-
const cacheable = opt.cacheable || true;
|
2009
|
-
return {
|
2010
|
-
prompt,
|
2011
|
-
deepThink,
|
2012
|
-
cacheable
|
2013
|
-
};
|
2014
|
-
}
|
2015
|
-
return {
|
2016
|
-
prompt: locatePrompt
|
2017
|
-
};
|
2018
|
-
}
|
2019
|
-
async aiTap(locatePrompt, opt) {
|
2020
|
-
const detailedLocateParam = this.buildDetailedLocateParam(
|
2021
|
-
locatePrompt,
|
2022
|
-
opt
|
2023
|
-
);
|
2024
|
-
const plans = buildPlans("Tap", detailedLocateParam);
|
2025
|
-
const { executor, output } = await this.taskExecutor.runPlans(
|
2026
|
-
taskTitleStr("Tap", locateParamStr(detailedLocateParam)),
|
2027
|
-
plans
|
2028
|
-
);
|
2029
|
-
const metadata = this.afterTaskRunning(executor);
|
2030
|
-
return {
|
2031
|
-
result: output,
|
2032
|
-
metadata
|
2033
|
-
};
|
2034
|
-
}
|
2035
|
-
async aiHover(locatePrompt, opt) {
|
2036
|
-
const detailedLocateParam = this.buildDetailedLocateParam(
|
2037
|
-
locatePrompt,
|
2038
|
-
opt
|
2039
|
-
);
|
2040
|
-
const plans = buildPlans("Hover", detailedLocateParam);
|
2041
|
-
const { executor, output } = await this.taskExecutor.runPlans(
|
2042
|
-
taskTitleStr("Hover", locateParamStr(detailedLocateParam)),
|
2043
|
-
plans
|
2044
|
-
);
|
2045
|
-
const metadata = this.afterTaskRunning(executor);
|
2046
|
-
return {
|
2047
|
-
result: output,
|
2048
|
-
metadata
|
2049
|
-
};
|
2050
|
-
}
|
2051
|
-
async aiInput(value, locatePrompt, opt) {
|
2052
|
-
assert7(
|
2053
|
-
typeof value === "string",
|
2054
|
-
"input value must be a string, use empty string if you want to clear the input"
|
2055
|
-
);
|
2056
|
-
assert7(locatePrompt, "missing locate prompt for input");
|
2057
|
-
const detailedLocateParam = this.buildDetailedLocateParam(
|
2058
|
-
locatePrompt,
|
2059
|
-
opt
|
2060
|
-
);
|
2061
|
-
const plans = buildPlans("Input", detailedLocateParam, {
|
2062
|
-
value
|
2063
|
-
});
|
2064
|
-
const { executor, output } = await this.taskExecutor.runPlans(
|
2065
|
-
taskTitleStr("Input", locateParamStr(detailedLocateParam)),
|
2066
|
-
plans
|
2067
|
-
);
|
2068
|
-
const metadata = this.afterTaskRunning(executor);
|
2069
|
-
return {
|
2070
|
-
result: output,
|
2071
|
-
metadata
|
2072
|
-
};
|
2073
|
-
}
|
2074
|
-
async aiKeyboardPress(keyName, locatePrompt, opt) {
|
2075
|
-
assert7(keyName, "missing keyName for keyboard press");
|
2076
|
-
const detailedLocateParam = locatePrompt ? this.buildDetailedLocateParam(locatePrompt, opt) : void 0;
|
2077
|
-
const plans = buildPlans("KeyboardPress", detailedLocateParam, {
|
2078
|
-
value: keyName
|
2079
|
-
});
|
2080
|
-
const { executor, output } = await this.taskExecutor.runPlans(
|
2081
|
-
taskTitleStr("KeyboardPress", locateParamStr(detailedLocateParam)),
|
2082
|
-
plans
|
2083
|
-
);
|
2084
|
-
const metadata = this.afterTaskRunning(executor);
|
2085
|
-
return {
|
2086
|
-
result: output,
|
2087
|
-
metadata
|
2088
|
-
};
|
2089
|
-
}
|
2090
|
-
async aiScroll(scrollParam, locatePrompt, opt) {
|
2091
|
-
const detailedLocateParam = locatePrompt ? this.buildDetailedLocateParam(locatePrompt, opt) : void 0;
|
2092
|
-
const plans = buildPlans("Scroll", detailedLocateParam, scrollParam);
|
2093
|
-
const paramInTitle = locatePrompt ? `${locateParamStr(detailedLocateParam)} - ${scrollParamStr(scrollParam)}` : scrollParamStr(scrollParam);
|
2094
|
-
const { executor, output } = await this.taskExecutor.runPlans(
|
2095
|
-
taskTitleStr("Scroll", paramInTitle),
|
2096
|
-
plans
|
2097
|
-
);
|
2098
|
-
const metadata = this.afterTaskRunning(executor);
|
2099
|
-
return {
|
2100
|
-
result: output,
|
2101
|
-
metadata
|
2102
|
-
};
|
2103
|
-
}
|
2104
|
-
async aiAction(taskPrompt, opt) {
|
2105
|
-
const cacheable = opt?.cacheable;
|
2106
|
-
const isVlmUiTars = vlLocateMode() === "vlm-ui-tars";
|
2107
|
-
const matchedCache = isVlmUiTars || cacheable === false ? void 0 : this.taskCache?.matchPlanCache(taskPrompt);
|
2108
|
-
if (matchedCache && this.taskCache?.isCacheResultUsed) {
|
2109
|
-
const { executor: executor2 } = await this.taskExecutor.loadYamlFlowAsPlanning(
|
2110
|
-
taskPrompt,
|
2111
|
-
matchedCache.cacheContent?.yamlWorkflow
|
2112
|
-
);
|
2113
|
-
const metadata2 = this.afterTaskRunning(executor2);
|
2114
|
-
debug4("matched cache, will call .runYaml to run the action");
|
2115
|
-
const yaml5 = matchedCache.cacheContent?.yamlWorkflow;
|
2116
|
-
const result = await this.runYaml(yaml5);
|
2117
|
-
return {
|
2118
|
-
result: result.result,
|
2119
|
-
metadata: metadata2
|
2120
|
-
};
|
2121
|
-
}
|
2122
|
-
const { output, executor } = await (isVlmUiTars ? this.taskExecutor.actionToGoal(taskPrompt) : this.taskExecutor.action(taskPrompt, this.opts.aiActionContext));
|
2123
|
-
if (this.taskCache && output?.yamlFlow && cacheable !== false) {
|
2124
|
-
const yamlContent = {
|
2125
|
-
tasks: [
|
2126
|
-
{
|
2127
|
-
name: taskPrompt,
|
2128
|
-
flow: output.yamlFlow
|
2129
|
-
}
|
2130
|
-
]
|
2131
|
-
};
|
2132
|
-
const yamlFlowStr = yaml4.dump(yamlContent);
|
2133
|
-
this.taskCache.updateOrAppendCacheRecord(
|
2134
|
-
{
|
2135
|
-
type: "plan",
|
2136
|
-
prompt: taskPrompt,
|
2137
|
-
yamlWorkflow: yamlFlowStr
|
2138
|
-
},
|
2139
|
-
matchedCache
|
2140
|
-
);
|
2141
|
-
}
|
2142
|
-
const metadata = this.afterTaskRunning(executor);
|
2143
|
-
return {
|
2144
|
-
result: output,
|
2145
|
-
metadata
|
2146
|
-
};
|
2147
|
-
}
|
2148
|
-
async aiQuery(demand) {
|
2149
|
-
const { output, executor } = await this.taskExecutor.query(demand);
|
2150
|
-
const metadata = this.afterTaskRunning(executor);
|
2151
|
-
return {
|
2152
|
-
result: output,
|
2153
|
-
metadata
|
2154
|
-
};
|
2155
|
-
}
|
2156
|
-
async aiBoolean(prompt) {
|
2157
|
-
const { output, executor } = await this.taskExecutor.boolean(prompt);
|
2158
|
-
const metadata = this.afterTaskRunning(executor);
|
2159
|
-
return {
|
2160
|
-
result: output,
|
2161
|
-
metadata
|
2162
|
-
};
|
2163
|
-
}
|
2164
|
-
async aiNumber(prompt) {
|
2165
|
-
const { output, executor } = await this.taskExecutor.number(prompt);
|
2166
|
-
const metadata = this.afterTaskRunning(executor);
|
2167
|
-
return {
|
2168
|
-
result: output,
|
2169
|
-
metadata
|
2170
|
-
};
|
2171
|
-
}
|
2172
|
-
async aiString(prompt) {
|
2173
|
-
const { output, executor } = await this.taskExecutor.string(prompt);
|
2174
|
-
const metadata = this.afterTaskRunning(executor);
|
2175
|
-
return {
|
2176
|
-
result: output,
|
2177
|
-
metadata
|
2178
|
-
};
|
2179
|
-
}
|
2180
|
-
async describeElementAtPoint(center, opt) {
|
2181
|
-
const { verifyPrompt = true, retryLimit = 3 } = opt || {};
|
2182
|
-
let success = false;
|
2183
|
-
let retryCount = 0;
|
2184
|
-
let resultPrompt = "";
|
2185
|
-
let deepThink = opt?.deepThink || false;
|
2186
|
-
let verifyResult;
|
2187
|
-
while (!success && retryCount < retryLimit) {
|
2188
|
-
if (retryCount >= 2) {
|
2189
|
-
deepThink = true;
|
2190
|
-
}
|
2191
|
-
debug4(
|
2192
|
-
"aiDescribe",
|
2193
|
-
center,
|
2194
|
-
"verifyPrompt",
|
2195
|
-
verifyPrompt,
|
2196
|
-
"retryCount",
|
2197
|
-
retryCount,
|
2198
|
-
"deepThink",
|
2199
|
-
deepThink
|
2200
|
-
);
|
2201
|
-
const text = await this.insight.describe(center, { deepThink });
|
2202
|
-
debug4("aiDescribe text", text);
|
2203
|
-
assert7(text.description, `failed to describe element at [${center}]`);
|
2204
|
-
resultPrompt = text.description;
|
2205
|
-
verifyResult = await this.verifyLocator(
|
2206
|
-
resultPrompt,
|
2207
|
-
deepThink ? { deepThink: true } : void 0,
|
2208
|
-
center,
|
2209
|
-
opt
|
2210
|
-
);
|
2211
|
-
if (verifyResult.pass) {
|
2212
|
-
success = true;
|
2213
|
-
} else {
|
2214
|
-
retryCount++;
|
2215
|
-
}
|
2216
|
-
}
|
2217
|
-
return {
|
2218
|
-
prompt: resultPrompt,
|
2219
|
-
deepThink,
|
2220
|
-
verifyResult
|
2221
|
-
};
|
2222
|
-
}
|
2223
|
-
async verifyLocator(prompt, locateOpt, expectCenter, verifyLocateOption) {
|
2224
|
-
debug4("verifyLocator", prompt, locateOpt, expectCenter, verifyLocateOption);
|
2225
|
-
const locateResult = await this.aiLocate(prompt, locateOpt);
|
2226
|
-
const { center: verifyCenter, rect: verifyRect } = locateResult.result;
|
2227
|
-
const distance = distanceOfTwoPoints(expectCenter, verifyCenter);
|
2228
|
-
const included = includedInRect(expectCenter, verifyRect);
|
2229
|
-
const pass = distance <= (verifyLocateOption?.centerDistanceThreshold || 20) || included;
|
2230
|
-
const verifyResult = {
|
2231
|
-
pass,
|
2232
|
-
rect: verifyRect,
|
2233
|
-
center: verifyCenter,
|
2234
|
-
centerDistance: distance
|
2235
|
-
};
|
2236
|
-
debug4("aiDescribe verifyResult", verifyResult);
|
2237
|
-
return verifyResult;
|
2238
|
-
}
|
2239
|
-
async aiLocate(prompt, opt) {
|
2240
|
-
const detailedLocateParam = this.buildDetailedLocateParam(prompt, opt);
|
2241
|
-
const plans = buildPlans("Locate", detailedLocateParam);
|
2242
|
-
const { executor, output } = await this.taskExecutor.runPlans(
|
2243
|
-
taskTitleStr("Locate", locateParamStr(detailedLocateParam)),
|
2244
|
-
plans
|
2245
|
-
);
|
2246
|
-
const metadata = this.afterTaskRunning(executor);
|
2247
|
-
const { element } = output;
|
2248
|
-
const result = {
|
2249
|
-
rect: element?.rect,
|
2250
|
-
center: element?.center
|
2251
|
-
};
|
2252
|
-
return {
|
2253
|
-
result,
|
2254
|
-
metadata
|
2255
|
-
};
|
2256
|
-
}
|
2257
|
-
async aiAssert(assertion, msg, opt) {
|
2258
|
-
let currentUrl = "";
|
2259
|
-
if (this.page.url) {
|
2260
|
-
try {
|
2261
|
-
currentUrl = await this.page.url();
|
2262
|
-
} catch (e) {
|
2263
|
-
}
|
2264
|
-
}
|
2265
|
-
const assertionWithContext = currentUrl ? `For the page at URL "${currentUrl}", ${assertion}` : assertion;
|
2266
|
-
const { output, executor } = await this.taskExecutor.assert(assertionWithContext);
|
2267
|
-
const metadata = this.afterTaskRunning(executor, true);
|
2268
|
-
if (output && opt?.keepRawResponse) {
|
2269
|
-
return {
|
2270
|
-
result: output,
|
2271
|
-
metadata
|
2272
|
-
};
|
2273
|
-
}
|
2274
|
-
if (!output?.pass) {
|
2275
|
-
const errMsg = msg || `Assertion failed: ${assertion}`;
|
2276
|
-
const reasonMsg = `Reason: ${output?.thought || executor.latestErrorTask()?.error || "(no_reason)"}`;
|
2277
|
-
throw new Error(`${errMsg}
|
2278
|
-
${reasonMsg}`);
|
2279
|
-
}
|
2280
|
-
return {
|
2281
|
-
result: true,
|
2282
|
-
metadata
|
2283
|
-
};
|
2284
|
-
}
|
2285
|
-
async aiCaptcha(options) {
|
2286
|
-
const { deepThink = false, autoDetectComplexity = true } = options || {};
|
2287
|
-
let shouldUseDeepThink = deepThink;
|
2288
|
-
if (autoDetectComplexity && !deepThink) {
|
2289
|
-
const context = await this.getUIContext();
|
2290
|
-
const { screenshotBase64 } = context;
|
2291
|
-
try {
|
2292
|
-
const complexityAnalysisPrompt = `
|
2293
|
-
Analyze this screenshot and determine if it contains a complex CAPTCHA that would benefit from deep thinking.
|
2294
|
-
A complex CAPTCHA typically has one or more of these characteristics:
|
2295
|
-
- Distorted or overlapping text that is hard to read
|
2296
|
-
- Multiple images that need to be selected based on a specific criteria
|
2297
|
-
- Puzzles that require spatial reasoning
|
2298
|
-
- Multiple steps or verification methods
|
2299
|
-
- Small or hard-to-distinguish elements
|
2300
|
-
|
2301
|
-
Return only "complex" or "simple" based on your analysis.
|
2302
|
-
`;
|
2303
|
-
const complexityMsgs = [
|
2304
|
-
{ role: "system", content: "You are an AI assistant that analyzes screenshots to determine CAPTCHA complexity." },
|
2305
|
-
{
|
2306
|
-
role: "user",
|
2307
|
-
content: [
|
2308
|
-
{
|
2309
|
-
type: "image_url",
|
2310
|
-
image_url: {
|
2311
|
-
url: screenshotBase64,
|
2312
|
-
detail: "high"
|
2313
|
-
}
|
2314
|
-
},
|
2315
|
-
{
|
2316
|
-
type: "text",
|
2317
|
-
text: complexityAnalysisPrompt
|
2318
|
-
}
|
2319
|
-
]
|
2320
|
-
}
|
2321
|
-
];
|
2322
|
-
const complexityResult = await this.insight.aiVendorFn(
|
2323
|
-
complexityMsgs,
|
2324
|
-
{ type: "extract_data" }
|
2325
|
-
);
|
2326
|
-
const responseText = typeof complexityResult.content === "string" ? complexityResult.content.toLowerCase() : JSON.stringify(complexityResult.content).toLowerCase();
|
2327
|
-
shouldUseDeepThink = responseText.includes("complex");
|
2328
|
-
debug4("CAPTCHA complexity analysis:", responseText, "Using deep think:", shouldUseDeepThink);
|
2329
|
-
} catch (error) {
|
2330
|
-
debug4("Failed to analyze CAPTCHA complexity:", error);
|
2331
|
-
}
|
2332
|
-
}
|
2333
|
-
const captchaResponse = await this._callInsightCaptcha({
|
2334
|
-
deepThink: shouldUseDeepThink
|
2335
|
-
});
|
2336
|
-
const captchaResult = captchaResponse.content;
|
2337
|
-
const usage = captchaResponse.usage;
|
2338
|
-
const actualDeepThink = captchaResponse.deepThink || false;
|
2339
|
-
if (captchaResult.captchaType === "text") {
|
2340
|
-
for (const action of captchaResult.actions) {
|
2341
|
-
if (action.type === "click" && action.target) {
|
2342
|
-
await this.aiTap(action.target, { deepThink: shouldUseDeepThink });
|
2343
|
-
} else if (action.type === "input" && action.value) {
|
2344
|
-
if (action.target) {
|
2345
|
-
await this.aiInput(action.value, action.target, { deepThink: shouldUseDeepThink });
|
2346
|
-
}
|
2347
|
-
} else if (action.type === "verify" && action.target) {
|
2348
|
-
await this.aiTap(action.target, { deepThink: shouldUseDeepThink });
|
2349
|
-
}
|
2350
|
-
}
|
2351
|
-
} else if (captchaResult.captchaType === "image") {
|
2352
|
-
for (const action of captchaResult.actions) {
|
2353
|
-
if (action.type === "click") {
|
2354
|
-
if (action.coordinates) {
|
2355
|
-
const x = action.coordinates[0];
|
2356
|
-
const y = action.coordinates[1];
|
2357
|
-
await this.aiTap(`element at coordinates (${x}, ${y})`, { deepThink: shouldUseDeepThink });
|
2358
|
-
} else if (action.target) {
|
2359
|
-
await this.aiTap(action.target, { deepThink: shouldUseDeepThink });
|
2360
|
-
}
|
2361
|
-
} else if (action.type === "verify" && action.target) {
|
2362
|
-
await this.aiTap(action.target, { deepThink: shouldUseDeepThink });
|
2363
|
-
}
|
2364
|
-
}
|
2365
|
-
}
|
2366
|
-
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
2367
|
-
const metadata = {
|
2368
|
-
status: "finished",
|
2369
|
-
usage,
|
2370
|
-
thought: captchaResult.thought
|
2371
|
-
};
|
2372
|
-
metadata.deepThink = actualDeepThink;
|
2373
|
-
if (autoDetectComplexity && !deepThink) {
|
2374
|
-
metadata.autoDetectedComplexity = shouldUseDeepThink;
|
2375
|
-
}
|
2376
|
-
return {
|
2377
|
-
result: captchaResult,
|
2378
|
-
metadata
|
2379
|
-
};
|
2380
|
-
}
|
2381
|
-
async aiWaitFor(assertion, opt) {
|
2382
|
-
const startTime = Date.now();
|
2383
|
-
const { executor } = await this.taskExecutor.waitFor(assertion, {
|
2384
|
-
timeoutMs: opt?.timeoutMs || 15 * 1e3,
|
2385
|
-
checkIntervalMs: opt?.checkIntervalMs || 3 * 1e3,
|
2386
|
-
assertion
|
2387
|
-
});
|
2388
|
-
const metadata = {
|
2389
|
-
status: executor.isInErrorState() ? "failed" : "finished",
|
2390
|
-
start: startTime,
|
2391
|
-
end: Date.now(),
|
2392
|
-
totalTime: Date.now() - startTime,
|
2393
|
-
thought: executor.latestErrorTask()?.thought,
|
2394
|
-
actionDetails: executor.tasks.map((task) => ({
|
2395
|
-
type: task.type,
|
2396
|
-
subType: task.subType,
|
2397
|
-
status: task.status,
|
2398
|
-
thought: task.thought
|
2399
|
-
}))
|
2400
|
-
};
|
2401
|
-
this.appendExecutionDump(executor.dump());
|
2402
|
-
this.writeOutActionDumps();
|
2403
|
-
if (executor.isInErrorState()) {
|
2404
|
-
const errorTask = executor.latestErrorTask();
|
2405
|
-
throw new Error(`${errorTask?.error}
|
2406
|
-
${errorTask?.errorStack}`);
|
2407
|
-
}
|
2408
|
-
return {
|
2409
|
-
result: true,
|
2410
|
-
// Successfully waited
|
2411
|
-
metadata
|
2412
|
-
};
|
2413
|
-
}
|
2414
|
-
async ai(taskPrompt, type = "action", options) {
|
2415
|
-
if (type === "action") {
|
2416
|
-
return this.aiAction(taskPrompt);
|
2417
|
-
}
|
2418
|
-
if (type === "query") {
|
2419
|
-
return this.aiQuery(taskPrompt);
|
2420
|
-
}
|
2421
|
-
if (type === "assert") {
|
2422
|
-
return this.aiAssert(taskPrompt);
|
2423
|
-
}
|
2424
|
-
if (type === "tap") {
|
2425
|
-
return this.aiTap(taskPrompt, options);
|
2426
|
-
}
|
2427
|
-
if (type === "captcha") {
|
2428
|
-
return this.aiCaptcha(options);
|
2429
|
-
}
|
2430
|
-
throw new Error(
|
2431
|
-
`Unknown type: ${type}, only support 'action', 'query', 'assert', 'tap', 'captcha'`
|
2432
|
-
);
|
2433
|
-
}
|
2434
|
-
async runYaml(yamlScriptContent) {
|
2435
|
-
const startTime = Date.now();
|
2436
|
-
const script = parseYamlScript(yamlScriptContent, "yaml", true);
|
2437
|
-
const player = new ScriptPlayer(script, async () => {
|
2438
|
-
return { agent: this, freeFn: [] };
|
2439
|
-
});
|
2440
|
-
await player.run();
|
2441
|
-
const endTime = Date.now();
|
2442
|
-
const metadata = {
|
2443
|
-
status: player.status,
|
2444
|
-
start: startTime,
|
2445
|
-
end: endTime,
|
2446
|
-
totalTime: endTime - startTime,
|
2447
|
-
tasks: player.taskStatusList.map((task) => ({
|
2448
|
-
type: "yaml-task",
|
2449
|
-
subType: task.name,
|
2450
|
-
status: task.status,
|
2451
|
-
error: task.error?.message
|
2452
|
-
}))
|
2453
|
-
};
|
2454
|
-
if (player.status === "error") {
|
2455
|
-
const errors = player.taskStatusList.filter((task) => task.status === "error").map((task) => {
|
2456
|
-
return `task - ${task.name}: ${task.error?.message}`;
|
2457
|
-
}).join("\n");
|
2458
|
-
throw new Error(`Error(s) occurred in running yaml script:
|
2459
|
-
${errors}`);
|
2460
|
-
}
|
2461
|
-
return {
|
2462
|
-
result: player.result,
|
2463
|
-
metadata
|
2464
|
-
};
|
2465
|
-
}
|
2466
|
-
async evaluateJavaScript(script) {
|
2467
|
-
assert7(
|
2468
|
-
this.page.evaluateJavaScript,
|
2469
|
-
"evaluateJavaScript is not supported in current agent"
|
2470
|
-
);
|
2471
|
-
if (this.page.evaluateJavaScript) {
|
2472
|
-
return this.page.evaluateJavaScript(script);
|
2473
|
-
}
|
2474
|
-
throw new Error("evaluateJavaScript is not supported in current agent");
|
2475
|
-
}
|
2476
|
-
async destroy() {
|
2477
|
-
await this.page.destroy();
|
2478
|
-
}
|
2479
|
-
};
|
2480
|
-
|
2481
|
-
// src/puppeteer/index.ts
|
2482
|
-
import { getDebug as getDebug6 } from "misoai-shared/logger";
|
2483
|
-
|
2484
|
-
// src/puppeteer/page.ts
|
2485
|
-
import {
|
2486
|
-
DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT as DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT3,
|
2487
|
-
DEFAULT_WAIT_FOR_NETWORK_IDLE_CONCURRENCY,
|
2488
|
-
DEFAULT_WAIT_FOR_NETWORK_IDLE_TIME,
|
2489
|
-
DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT as DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT2
|
2490
|
-
} from "misoai-shared/constants";
|
2491
|
-
|
2492
|
-
// src/puppeteer/base-page.ts
|
2493
|
-
import { sleep as sleep2 } from "misoai-core/utils";
|
2494
|
-
import { DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT as DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT2 } from "misoai-shared/constants";
|
2495
|
-
import { treeToList as treeToList2 } from "misoai-shared/extractor";
|
2496
|
-
import { getExtraReturnLogic } from "misoai-shared/fs";
|
2497
|
-
import { getDebug as getDebug5 } from "misoai-shared/logger";
|
2498
|
-
import { assert as assert8 } from "misoai-shared/utils";
|
2499
|
-
var debugPage = getDebug5("web:page");
|
2500
|
-
var Page = class {
|
2501
|
-
constructor(underlyingPage, pageType, opts) {
|
2502
|
-
this.everMoved = false;
|
2503
|
-
this.underlyingPage = underlyingPage;
|
2504
|
-
this.pageType = pageType;
|
2505
|
-
this.waitForNavigationTimeout = opts?.waitForNavigationTimeout || DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT2;
|
2506
|
-
}
|
2507
|
-
async evaluate(pageFunction, arg) {
|
2508
|
-
let result;
|
2509
|
-
debugPage("evaluate function begin");
|
2510
|
-
if (this.pageType === "puppeteer") {
|
2511
|
-
result = await this.underlyingPage.evaluate(
|
2512
|
-
pageFunction,
|
2513
|
-
arg
|
2514
|
-
);
|
2515
|
-
} else {
|
2516
|
-
result = await this.underlyingPage.evaluate(
|
2517
|
-
pageFunction,
|
2518
|
-
arg
|
2519
|
-
);
|
2520
|
-
}
|
2521
|
-
debugPage("evaluate function end");
|
2522
|
-
return result;
|
2523
|
-
}
|
2524
|
-
async evaluateJavaScript(script) {
|
2525
|
-
return this.evaluate(script);
|
2526
|
-
}
|
2527
|
-
async waitForNavigation() {
|
2528
|
-
if (this.pageType === "puppeteer" || this.pageType === "playwright") {
|
2529
|
-
debugPage("waitForNavigation begin");
|
2530
|
-
debugPage(`waitForNavigation timeout: ${this.waitForNavigationTimeout}`);
|
2531
|
-
try {
|
2532
|
-
await this.underlyingPage.waitForSelector("html", {
|
2533
|
-
timeout: this.waitForNavigationTimeout
|
2534
|
-
});
|
2535
|
-
} catch (error) {
|
2536
|
-
console.warn(
|
2537
|
-
"[midscene:warning] Waiting for the navigation has timed out, but Midscene will continue execution. Please check https://midscenejs.com/faq.html#customize-the-network-timeout for more information on customizing the network timeout"
|
2538
|
-
);
|
2539
|
-
}
|
2540
|
-
debugPage("waitForNavigation end");
|
2541
|
-
}
|
2542
|
-
}
|
2543
|
-
// @deprecated
|
2544
|
-
async getElementsInfo() {
|
2545
|
-
await this.waitForNavigation();
|
2546
|
-
debugPage("getElementsInfo begin");
|
2547
|
-
const tree = await this.getElementsNodeTree();
|
2548
|
-
debugPage("getElementsInfo end");
|
2549
|
-
return treeToList2(tree);
|
2550
|
-
}
|
2551
|
-
async getElementsNodeTree() {
|
2552
|
-
await this.waitForNavigation();
|
2553
|
-
const scripts = await getExtraReturnLogic(true);
|
2554
|
-
assert8(scripts, "scripts should be set before writing report in browser");
|
2555
|
-
const captureElementSnapshot = await this.evaluate(scripts);
|
2556
|
-
return captureElementSnapshot;
|
2557
|
-
}
|
2558
|
-
async size() {
|
2559
|
-
if (this.viewportSize)
|
2560
|
-
return this.viewportSize;
|
2561
|
-
const sizeInfo = await this.evaluate(() => {
|
2562
|
-
return {
|
2563
|
-
width: document.documentElement.clientWidth,
|
2564
|
-
height: document.documentElement.clientHeight,
|
2565
|
-
dpr: window.devicePixelRatio
|
2566
|
-
};
|
2567
|
-
});
|
2568
|
-
this.viewportSize = sizeInfo;
|
2569
|
-
return sizeInfo;
|
2570
|
-
}
|
2571
|
-
async screenshotBase64() {
|
2572
|
-
const imgType = "jpeg";
|
2573
|
-
const quality = 90;
|
2574
|
-
await this.waitForNavigation();
|
2575
|
-
debugPage("screenshotBase64 begin");
|
2576
|
-
let base64;
|
2577
|
-
if (this.pageType === "puppeteer") {
|
2578
|
-
const result = await this.underlyingPage.screenshot({
|
2579
|
-
type: imgType,
|
2580
|
-
quality,
|
2581
|
-
encoding: "base64"
|
2582
|
-
});
|
2583
|
-
base64 = `data:image/jpeg;base64,${result}`;
|
2584
|
-
} else if (this.pageType === "playwright") {
|
2585
|
-
const buffer = await this.underlyingPage.screenshot({
|
2586
|
-
type: imgType,
|
2587
|
-
quality,
|
2588
|
-
timeout: 10 * 1e3
|
2589
|
-
});
|
2590
|
-
base64 = `data:image/jpeg;base64,${buffer.toString("base64")}`;
|
2591
|
-
} else {
|
2592
|
-
throw new Error("Unsupported page type for screenshot");
|
2593
|
-
}
|
2594
|
-
debugPage("screenshotBase64 end");
|
2595
|
-
return base64;
|
2596
|
-
}
|
2597
|
-
async url() {
|
2598
|
-
return this.underlyingPage.url();
|
2599
|
-
}
|
2600
|
-
get mouse() {
|
2601
|
-
return {
|
2602
|
-
click: async (x, y, options) => {
|
2603
|
-
await this.mouse.move(x, y);
|
2604
|
-
this.underlyingPage.mouse.click(x, y, {
|
2605
|
-
button: options?.button || "left",
|
2606
|
-
count: options?.count || 1
|
2607
|
-
});
|
2608
|
-
},
|
2609
|
-
wheel: async (deltaX, deltaY) => {
|
2610
|
-
if (this.pageType === "puppeteer") {
|
2611
|
-
await this.underlyingPage.mouse.wheel({
|
2612
|
-
deltaX,
|
2613
|
-
deltaY
|
2614
|
-
});
|
2615
|
-
} else if (this.pageType === "playwright") {
|
2616
|
-
await this.underlyingPage.mouse.wheel(
|
2617
|
-
deltaX,
|
2618
|
-
deltaY
|
2619
|
-
);
|
2620
|
-
}
|
2621
|
-
},
|
2622
|
-
move: async (x, y) => {
|
2623
|
-
this.everMoved = true;
|
2624
|
-
return this.underlyingPage.mouse.move(x, y);
|
2625
|
-
},
|
2626
|
-
drag: async (from, to) => {
|
2627
|
-
if (this.pageType === "puppeteer") {
|
2628
|
-
await this.underlyingPage.mouse.drag(
|
2629
|
-
{
|
2630
|
-
x: from.x,
|
2631
|
-
y: from.y
|
2632
|
-
},
|
2633
|
-
{
|
2634
|
-
x: to.x,
|
2635
|
-
y: to.y
|
2636
|
-
}
|
2637
|
-
);
|
2638
|
-
} else if (this.pageType === "playwright") {
|
2639
|
-
await this.underlyingPage.mouse.move(
|
2640
|
-
from.x,
|
2641
|
-
from.y
|
2642
|
-
);
|
2643
|
-
await this.underlyingPage.mouse.down();
|
2644
|
-
await this.underlyingPage.mouse.move(to.x, to.y);
|
2645
|
-
await this.underlyingPage.mouse.up();
|
2646
|
-
}
|
2647
|
-
}
|
2648
|
-
};
|
2649
|
-
}
|
2650
|
-
get keyboard() {
|
2651
|
-
return {
|
2652
|
-
type: async (text) => this.underlyingPage.keyboard.type(text, { delay: 80 }),
|
2653
|
-
press: async (action) => {
|
2654
|
-
const keys = Array.isArray(action) ? action : [action];
|
2655
|
-
for (const k of keys) {
|
2656
|
-
const commands = k.command ? [k.command] : [];
|
2657
|
-
await this.underlyingPage.keyboard.down(k.key, { commands });
|
2658
|
-
}
|
2659
|
-
for (const k of [...keys].reverse()) {
|
2660
|
-
await this.underlyingPage.keyboard.up(k.key);
|
2661
|
-
}
|
2662
|
-
},
|
2663
|
-
down: async (key) => {
|
2664
|
-
this.underlyingPage.keyboard.down(key);
|
2665
|
-
},
|
2666
|
-
up: async (key) => {
|
2667
|
-
this.underlyingPage.keyboard.up(key);
|
2668
|
-
}
|
2669
|
-
};
|
2670
|
-
}
|
2671
|
-
async clearInput(element) {
|
2672
|
-
if (!element) {
|
2673
|
-
console.warn("No element to clear input");
|
2674
|
-
return;
|
2675
|
-
}
|
2676
|
-
const isMac = process.platform === "darwin";
|
2677
|
-
if (isMac) {
|
2678
|
-
if (this.pageType === "puppeteer") {
|
2679
|
-
await this.mouse.click(element.center[0], element.center[1], {
|
2680
|
-
count: 3
|
2681
|
-
});
|
2682
|
-
} else {
|
2683
|
-
await this.mouse.click(element.center[0], element.center[1]);
|
2684
|
-
await this.underlyingPage.keyboard.down("Meta");
|
2685
|
-
await this.underlyingPage.keyboard.press("a");
|
2686
|
-
await this.underlyingPage.keyboard.up("Meta");
|
2687
|
-
}
|
2688
|
-
} else {
|
2689
|
-
await this.mouse.click(element.center[0], element.center[1]);
|
2690
|
-
await this.underlyingPage.keyboard.down("Control");
|
2691
|
-
await this.underlyingPage.keyboard.press("a");
|
2692
|
-
await this.underlyingPage.keyboard.up("Control");
|
2693
|
-
}
|
2694
|
-
await sleep2(100);
|
2695
|
-
await this.keyboard.press([{ key: "Backspace" }]);
|
2696
|
-
}
|
2697
|
-
async moveToPointBeforeScroll(point) {
|
2698
|
-
if (point) {
|
2699
|
-
await this.mouse.move(point.left, point.top);
|
2700
|
-
} else if (!this.everMoved) {
|
2701
|
-
const size = await this.size();
|
2702
|
-
const targetX = Math.floor(size.width / 2);
|
2703
|
-
const targetY = Math.floor(size.height / 2);
|
2704
|
-
await this.mouse.move(targetX, targetY);
|
2705
|
-
}
|
2706
|
-
}
|
2707
|
-
async scrollUntilTop(startingPoint) {
|
2708
|
-
await this.moveToPointBeforeScroll(startingPoint);
|
2709
|
-
return this.mouse.wheel(0, -9999999);
|
2710
|
-
}
|
2711
|
-
async scrollUntilBottom(startingPoint) {
|
2712
|
-
await this.moveToPointBeforeScroll(startingPoint);
|
2713
|
-
return this.mouse.wheel(0, 9999999);
|
2714
|
-
}
|
2715
|
-
async scrollUntilLeft(startingPoint) {
|
2716
|
-
await this.moveToPointBeforeScroll(startingPoint);
|
2717
|
-
return this.mouse.wheel(-9999999, 0);
|
2718
|
-
}
|
2719
|
-
async scrollUntilRight(startingPoint) {
|
2720
|
-
await this.moveToPointBeforeScroll(startingPoint);
|
2721
|
-
return this.mouse.wheel(9999999, 0);
|
2722
|
-
}
|
2723
|
-
async scrollUp(distance, startingPoint) {
|
2724
|
-
const innerHeight = await this.evaluate(() => window.innerHeight);
|
2725
|
-
const scrollDistance = distance || innerHeight * 0.7;
|
2726
|
-
await this.moveToPointBeforeScroll(startingPoint);
|
2727
|
-
return this.mouse.wheel(0, -scrollDistance);
|
2728
|
-
}
|
2729
|
-
async scrollDown(distance, startingPoint) {
|
2730
|
-
const innerHeight = await this.evaluate(() => window.innerHeight);
|
2731
|
-
const scrollDistance = distance || innerHeight * 0.7;
|
2732
|
-
await this.moveToPointBeforeScroll(startingPoint);
|
2733
|
-
return this.mouse.wheel(0, scrollDistance);
|
2734
|
-
}
|
2735
|
-
async scrollLeft(distance, startingPoint) {
|
2736
|
-
const innerWidth = await this.evaluate(() => window.innerWidth);
|
2737
|
-
const scrollDistance = distance || innerWidth * 0.7;
|
2738
|
-
await this.moveToPointBeforeScroll(startingPoint);
|
2739
|
-
return this.mouse.wheel(-scrollDistance, 0);
|
2740
|
-
}
|
2741
|
-
async scrollRight(distance, startingPoint) {
|
2742
|
-
const innerWidth = await this.evaluate(() => window.innerWidth);
|
2743
|
-
const scrollDistance = distance || innerWidth * 0.7;
|
2744
|
-
await this.moveToPointBeforeScroll(startingPoint);
|
2745
|
-
return this.mouse.wheel(scrollDistance, 0);
|
2746
|
-
}
|
2747
|
-
async navigate(url) {
|
2748
|
-
if (this.pageType === "puppeteer") {
|
2749
|
-
await this.underlyingPage.goto(url);
|
2750
|
-
} else if (this.pageType === "playwright") {
|
2751
|
-
await this.underlyingPage.goto(url);
|
2752
|
-
} else {
|
2753
|
-
throw new Error("Unsupported page type for navigate");
|
2754
|
-
}
|
2755
|
-
}
|
2756
|
-
async destroy() {
|
2757
|
-
}
|
2758
|
-
};
|
2759
|
-
|
2760
|
-
// src/puppeteer/page.ts
|
2761
|
-
var WebPage = class extends Page {
|
2762
|
-
constructor(page, opts) {
|
2763
|
-
super(page, "puppeteer");
|
2764
|
-
const {
|
2765
|
-
waitForNavigationTimeout = DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT3,
|
2766
|
-
waitForNetworkIdleTimeout = DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT2
|
2767
|
-
} = opts ?? {};
|
2768
|
-
this.waitForNavigationTimeout = waitForNavigationTimeout;
|
2769
|
-
this.waitForNetworkIdleTimeout = waitForNetworkIdleTimeout;
|
2770
|
-
}
|
2771
|
-
async waitUntilNetworkIdle(options) {
|
2772
|
-
await this.underlyingPage.waitForNetworkIdle({
|
2773
|
-
idleTime: options?.idleTime || DEFAULT_WAIT_FOR_NETWORK_IDLE_TIME,
|
2774
|
-
concurrency: options?.concurrency || DEFAULT_WAIT_FOR_NETWORK_IDLE_CONCURRENCY,
|
2775
|
-
timeout: options?.timeout || this.waitForNetworkIdleTimeout
|
2776
|
-
});
|
2777
|
-
}
|
2778
|
-
};
|
2779
|
-
|
2780
|
-
// src/puppeteer/index.ts
|
2781
|
-
import { overrideAIConfig } from "misoai-shared/env";
|
2782
|
-
var debug5 = getDebug6("puppeteer:agent");
|
2783
|
-
var PuppeteerAgent = class extends PageAgent {
|
2784
|
-
constructor(page, opts) {
|
2785
|
-
const webPage = new WebPage(page);
|
2786
|
-
super(webPage, opts);
|
2787
|
-
const { forceSameTabNavigation = true } = opts ?? {};
|
2788
|
-
if (forceSameTabNavigation) {
|
2789
|
-
forceClosePopup(page, debug5);
|
2790
|
-
}
|
2791
|
-
}
|
2792
|
-
};
|
2793
|
-
|
2794
|
-
// src/puppeteer/agent-launcher.ts
|
2795
|
-
import { DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT as DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT3 } from "misoai-shared/constants";
|
2796
|
-
import puppeteer from "puppeteer";
|
2797
|
-
var defaultUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36";
|
2798
|
-
var defaultViewportWidth = 1440;
|
2799
|
-
var defaultViewportHeight = 768;
|
2800
|
-
var defaultViewportScale = process.platform === "darwin" ? 2 : 1;
|
2801
|
-
var defaultWaitForNetworkIdleTimeout = DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT3;
|
2802
|
-
var launcherDebug = getDebug7("puppeteer:launcher");
|
2803
|
-
async function launchPuppeteerPage(target, preference) {
|
2804
|
-
assert9(target.url, "url is required");
|
2805
|
-
const freeFn = [];
|
2806
|
-
const ua = target.userAgent || defaultUA;
|
2807
|
-
let width = defaultViewportWidth;
|
2808
|
-
let preferMaximizedWindow = true;
|
2809
|
-
if (target.viewportWidth) {
|
2810
|
-
preferMaximizedWindow = false;
|
2811
|
-
assert9(
|
2812
|
-
typeof target.viewportWidth === "number",
|
2813
|
-
"viewportWidth must be a number"
|
2814
|
-
);
|
2815
|
-
width = Number.parseInt(target.viewportWidth, 10);
|
2816
|
-
assert9(width > 0, `viewportWidth must be greater than 0, but got ${width}`);
|
2817
|
-
}
|
2818
|
-
let height = defaultViewportHeight;
|
2819
|
-
if (target.viewportHeight) {
|
2820
|
-
preferMaximizedWindow = false;
|
2821
|
-
assert9(
|
2822
|
-
typeof target.viewportHeight === "number",
|
2823
|
-
"viewportHeight must be a number"
|
2824
|
-
);
|
2825
|
-
height = Number.parseInt(target.viewportHeight, 10);
|
2826
|
-
assert9(
|
2827
|
-
height > 0,
|
2828
|
-
`viewportHeight must be greater than 0, but got ${height}`
|
2829
|
-
);
|
2830
|
-
}
|
2831
|
-
let dpr = defaultViewportScale;
|
2832
|
-
if (target.viewportScale) {
|
2833
|
-
preferMaximizedWindow = false;
|
2834
|
-
assert9(
|
2835
|
-
typeof target.viewportScale === "number",
|
2836
|
-
"viewportScale must be a number"
|
2837
|
-
);
|
2838
|
-
dpr = Number.parseInt(target.viewportScale, 10);
|
2839
|
-
assert9(dpr > 0, `viewportScale must be greater than 0, but got ${dpr}`);
|
2840
|
-
}
|
2841
|
-
const viewportConfig = {
|
2842
|
-
width,
|
2843
|
-
height,
|
2844
|
-
deviceScaleFactor: dpr
|
2845
|
-
};
|
2846
|
-
const headed = preference?.headed || preference?.keepWindow;
|
2847
|
-
preferMaximizedWindow = preferMaximizedWindow && !!headed;
|
2848
|
-
if (headed && process.env.CI === "1") {
|
2849
|
-
console.warn(
|
2850
|
-
"you are probably running headed mode in CI, this will usually fail."
|
2851
|
-
);
|
2852
|
-
}
|
2853
|
-
const isWindows = process.platform === "win32";
|
2854
|
-
const args = [
|
2855
|
-
...isWindows ? [] : ["--no-sandbox", "--disable-setuid-sandbox"],
|
2856
|
-
"--disable-features=HttpsFirstBalancedModeAutoEnable",
|
2857
|
-
"--disable-features=PasswordLeakDetection",
|
2858
|
-
"--disable-save-password-bubble",
|
2859
|
-
`--user-agent="${ua}"`,
|
2860
|
-
preferMaximizedWindow ? "--start-maximized" : `--window-size=${width},${height + 200}`
|
2861
|
-
// add 200px for the address bar
|
2862
|
-
];
|
2863
|
-
launcherDebug(
|
2864
|
-
"launching browser with viewport, headed",
|
2865
|
-
headed,
|
2866
|
-
"viewport",
|
2867
|
-
viewportConfig,
|
2868
|
-
"args",
|
2869
|
-
args,
|
2870
|
-
"preference",
|
2871
|
-
preference
|
2872
|
-
);
|
2873
|
-
const browser = await puppeteer.launch({
|
2874
|
-
headless: !headed,
|
2875
|
-
defaultViewport: viewportConfig,
|
2876
|
-
args,
|
2877
|
-
acceptInsecureCerts: target.acceptInsecureCerts
|
2878
|
-
});
|
2879
|
-
freeFn.push({
|
2880
|
-
name: "puppeteer_browser",
|
2881
|
-
fn: () => {
|
2882
|
-
if (!preference?.keepWindow) {
|
2883
|
-
if (isWindows) {
|
2884
|
-
setTimeout(() => {
|
2885
|
-
browser.close();
|
2886
|
-
}, 800);
|
2887
|
-
} else {
|
2888
|
-
browser.close();
|
2889
|
-
}
|
2890
|
-
}
|
2891
|
-
}
|
2892
|
-
});
|
2893
|
-
const pages = await browser.pages();
|
2894
|
-
const page = pages[0];
|
2895
|
-
if (target.cookie) {
|
2896
|
-
const cookieFileContent = readFileSync2(target.cookie, "utf-8");
|
2897
|
-
await page.setCookie(...JSON.parse(cookieFileContent));
|
2898
|
-
}
|
2899
|
-
const waitForNetworkIdleTimeout = typeof target.waitForNetworkIdle?.timeout === "number" ? target.waitForNetworkIdle.timeout : defaultWaitForNetworkIdleTimeout;
|
2900
|
-
try {
|
2901
|
-
launcherDebug("goto", target.url);
|
2902
|
-
await page.goto(target.url);
|
2903
|
-
if (waitForNetworkIdleTimeout > 0) {
|
2904
|
-
launcherDebug("waitForNetworkIdle", waitForNetworkIdleTimeout);
|
2905
|
-
await page.waitForNetworkIdle({
|
2906
|
-
timeout: waitForNetworkIdleTimeout
|
2907
|
-
});
|
2908
|
-
}
|
2909
|
-
} catch (e) {
|
2910
|
-
if (typeof target.waitForNetworkIdle?.continueOnNetworkIdleError === "boolean" && !target.waitForNetworkIdle?.continueOnNetworkIdleError) {
|
2911
|
-
const newError = new Error(`failed to wait for network idle: ${e}`, {
|
2912
|
-
cause: e
|
2913
|
-
});
|
2914
|
-
throw newError;
|
2915
|
-
}
|
2916
|
-
const newMessage = `failed to wait for network idle after ${waitForNetworkIdleTimeout}ms, but the script will continue.`;
|
2917
|
-
console.warn(newMessage);
|
2918
|
-
}
|
2919
|
-
return { page, freeFn };
|
2920
|
-
}
|
2921
|
-
async function puppeteerAgentForTarget(target, preference) {
|
2922
|
-
const { page, freeFn } = await launchPuppeteerPage(target, preference);
|
2923
|
-
const agent = new PuppeteerAgent(page, {
|
2924
|
-
autoPrintReportMsg: false,
|
2925
|
-
testId: preference?.testId,
|
2926
|
-
cacheId: preference?.cacheId,
|
2927
|
-
aiActionContext: target.aiActionContext,
|
2928
|
-
forceSameTabNavigation: typeof target.forceSameTabNavigation !== "undefined" ? target.forceSameTabNavigation : true
|
2929
|
-
// true for default in yaml script
|
2930
|
-
});
|
2931
|
-
freeFn.push({
|
2932
|
-
name: "midscene_puppeteer_agent",
|
2933
|
-
fn: () => agent.destroy()
|
2934
|
-
});
|
2935
|
-
return { agent, freeFn };
|
2936
|
-
}
|
2937
|
-
export {
|
2938
|
-
defaultUA,
|
2939
|
-
defaultViewportHeight,
|
2940
|
-
defaultViewportScale,
|
2941
|
-
defaultViewportWidth,
|
2942
|
-
defaultWaitForNetworkIdleTimeout,
|
2943
|
-
launchPuppeteerPage,
|
2944
|
-
puppeteerAgentForTarget
|
2945
|
-
};
|
2946
|
-
|
2947
|
-
//# sourceMappingURL=puppeteer-agent-launcher.js.map
|