@donggui/core 1.5.4-donggui.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 +9 -0
- package/dist/es/agent/agent.mjs +709 -0
- package/dist/es/agent/agent.mjs.map +1 -0
- package/dist/es/agent/common.mjs +0 -0
- package/dist/es/agent/execution-session.mjs +41 -0
- package/dist/es/agent/execution-session.mjs.map +1 -0
- package/dist/es/agent/index.mjs +6 -0
- package/dist/es/agent/task-builder.mjs +330 -0
- package/dist/es/agent/task-builder.mjs.map +1 -0
- package/dist/es/agent/task-cache.mjs +186 -0
- package/dist/es/agent/task-cache.mjs.map +1 -0
- package/dist/es/agent/tasks.mjs +422 -0
- package/dist/es/agent/tasks.mjs.map +1 -0
- package/dist/es/agent/ui-utils.mjs +91 -0
- package/dist/es/agent/ui-utils.mjs.map +1 -0
- package/dist/es/agent/utils.mjs +198 -0
- package/dist/es/agent/utils.mjs.map +1 -0
- package/dist/es/ai-model/auto-glm/actions.mjs +224 -0
- package/dist/es/ai-model/auto-glm/actions.mjs.map +1 -0
- package/dist/es/ai-model/auto-glm/index.mjs +6 -0
- package/dist/es/ai-model/auto-glm/parser.mjs +239 -0
- package/dist/es/ai-model/auto-glm/parser.mjs.map +1 -0
- package/dist/es/ai-model/auto-glm/planning.mjs +71 -0
- package/dist/es/ai-model/auto-glm/planning.mjs.map +1 -0
- package/dist/es/ai-model/auto-glm/prompt.mjs +222 -0
- package/dist/es/ai-model/auto-glm/prompt.mjs.map +1 -0
- package/dist/es/ai-model/auto-glm/util.mjs +9 -0
- package/dist/es/ai-model/auto-glm/util.mjs.map +1 -0
- package/dist/es/ai-model/conversation-history.mjs +195 -0
- package/dist/es/ai-model/conversation-history.mjs.map +1 -0
- package/dist/es/ai-model/index.mjs +11 -0
- package/dist/es/ai-model/inspect.mjs +386 -0
- package/dist/es/ai-model/inspect.mjs.map +1 -0
- package/dist/es/ai-model/llm-planning.mjs +233 -0
- package/dist/es/ai-model/llm-planning.mjs.map +1 -0
- package/dist/es/ai-model/prompt/common.mjs +7 -0
- package/dist/es/ai-model/prompt/common.mjs.map +1 -0
- package/dist/es/ai-model/prompt/describe.mjs +66 -0
- package/dist/es/ai-model/prompt/describe.mjs.map +1 -0
- package/dist/es/ai-model/prompt/extraction.mjs +129 -0
- package/dist/es/ai-model/prompt/extraction.mjs.map +1 -0
- package/dist/es/ai-model/prompt/llm-locator.mjs +51 -0
- package/dist/es/ai-model/prompt/llm-locator.mjs.map +1 -0
- package/dist/es/ai-model/prompt/llm-planning.mjs +364 -0
- package/dist/es/ai-model/prompt/llm-planning.mjs.map +1 -0
- package/dist/es/ai-model/prompt/llm-section-locator.mjs +44 -0
- package/dist/es/ai-model/prompt/llm-section-locator.mjs.map +1 -0
- package/dist/es/ai-model/prompt/order-sensitive-judge.mjs +35 -0
- package/dist/es/ai-model/prompt/order-sensitive-judge.mjs.map +1 -0
- package/dist/es/ai-model/prompt/playwright-generator.mjs +117 -0
- package/dist/es/ai-model/prompt/playwright-generator.mjs.map +1 -0
- package/dist/es/ai-model/prompt/ui-tars-planning.mjs +36 -0
- package/dist/es/ai-model/prompt/ui-tars-planning.mjs.map +1 -0
- package/dist/es/ai-model/prompt/util.mjs +59 -0
- package/dist/es/ai-model/prompt/util.mjs.map +1 -0
- package/dist/es/ai-model/prompt/yaml-generator.mjs +219 -0
- package/dist/es/ai-model/prompt/yaml-generator.mjs.map +1 -0
- package/dist/es/ai-model/service-caller/index.mjs +466 -0
- package/dist/es/ai-model/service-caller/index.mjs.map +1 -0
- package/dist/es/ai-model/ui-tars-planning.mjs +249 -0
- package/dist/es/ai-model/ui-tars-planning.mjs.map +1 -0
- package/dist/es/common.mjs +371 -0
- package/dist/es/common.mjs.map +1 -0
- package/dist/es/device/device-options.mjs +0 -0
- package/dist/es/device/index.mjs +300 -0
- package/dist/es/device/index.mjs.map +1 -0
- package/dist/es/dump/html-utils.mjs +211 -0
- package/dist/es/dump/html-utils.mjs.map +1 -0
- package/dist/es/dump/image-restoration.mjs +43 -0
- package/dist/es/dump/image-restoration.mjs.map +1 -0
- package/dist/es/dump/index.mjs +3 -0
- package/dist/es/index.mjs +15 -0
- package/dist/es/index.mjs.map +1 -0
- package/dist/es/report-generator.mjs +134 -0
- package/dist/es/report-generator.mjs.map +1 -0
- package/dist/es/report.mjs +111 -0
- package/dist/es/report.mjs.map +1 -0
- package/dist/es/screenshot-item.mjs +105 -0
- package/dist/es/screenshot-item.mjs.map +1 -0
- package/dist/es/service/index.mjs +256 -0
- package/dist/es/service/index.mjs.map +1 -0
- package/dist/es/service/utils.mjs +15 -0
- package/dist/es/service/utils.mjs.map +1 -0
- package/dist/es/skill/index.mjs +38 -0
- package/dist/es/skill/index.mjs.map +1 -0
- package/dist/es/task-runner.mjs +258 -0
- package/dist/es/task-runner.mjs.map +1 -0
- package/dist/es/task-timing.mjs +12 -0
- package/dist/es/task-timing.mjs.map +1 -0
- package/dist/es/tree.mjs +13 -0
- package/dist/es/tree.mjs.map +1 -0
- package/dist/es/types.mjs +196 -0
- package/dist/es/types.mjs.map +1 -0
- package/dist/es/utils.mjs +218 -0
- package/dist/es/utils.mjs.map +1 -0
- package/dist/es/yaml/builder.mjs +13 -0
- package/dist/es/yaml/builder.mjs.map +1 -0
- package/dist/es/yaml/index.mjs +4 -0
- package/dist/es/yaml/player.mjs +418 -0
- package/dist/es/yaml/player.mjs.map +1 -0
- package/dist/es/yaml/utils.mjs +73 -0
- package/dist/es/yaml/utils.mjs.map +1 -0
- package/dist/es/yaml.mjs +0 -0
- package/dist/lib/agent/agent.js +757 -0
- package/dist/lib/agent/agent.js.map +1 -0
- package/dist/lib/agent/common.js +5 -0
- package/dist/lib/agent/execution-session.js +75 -0
- package/dist/lib/agent/execution-session.js.map +1 -0
- package/dist/lib/agent/index.js +81 -0
- package/dist/lib/agent/index.js.map +1 -0
- package/dist/lib/agent/task-builder.js +367 -0
- package/dist/lib/agent/task-builder.js.map +1 -0
- package/dist/lib/agent/task-cache.js +238 -0
- package/dist/lib/agent/task-cache.js.map +1 -0
- package/dist/lib/agent/tasks.js +465 -0
- package/dist/lib/agent/tasks.js.map +1 -0
- package/dist/lib/agent/ui-utils.js +143 -0
- package/dist/lib/agent/ui-utils.js.map +1 -0
- package/dist/lib/agent/utils.js +275 -0
- package/dist/lib/agent/utils.js.map +1 -0
- package/dist/lib/ai-model/auto-glm/actions.js +258 -0
- package/dist/lib/ai-model/auto-glm/actions.js.map +1 -0
- package/dist/lib/ai-model/auto-glm/index.js +66 -0
- package/dist/lib/ai-model/auto-glm/index.js.map +1 -0
- package/dist/lib/ai-model/auto-glm/parser.js +282 -0
- package/dist/lib/ai-model/auto-glm/parser.js.map +1 -0
- package/dist/lib/ai-model/auto-glm/planning.js +105 -0
- package/dist/lib/ai-model/auto-glm/planning.js.map +1 -0
- package/dist/lib/ai-model/auto-glm/prompt.js +259 -0
- package/dist/lib/ai-model/auto-glm/prompt.js.map +1 -0
- package/dist/lib/ai-model/auto-glm/util.js +46 -0
- package/dist/lib/ai-model/auto-glm/util.js.map +1 -0
- package/dist/lib/ai-model/conversation-history.js +229 -0
- package/dist/lib/ai-model/conversation-history.js.map +1 -0
- package/dist/lib/ai-model/index.js +125 -0
- package/dist/lib/ai-model/index.js.map +1 -0
- package/dist/lib/ai-model/inspect.js +429 -0
- package/dist/lib/ai-model/inspect.js.map +1 -0
- package/dist/lib/ai-model/llm-planning.js +270 -0
- package/dist/lib/ai-model/llm-planning.js.map +1 -0
- package/dist/lib/ai-model/prompt/common.js +41 -0
- package/dist/lib/ai-model/prompt/common.js.map +1 -0
- package/dist/lib/ai-model/prompt/describe.js +100 -0
- package/dist/lib/ai-model/prompt/describe.js.map +1 -0
- package/dist/lib/ai-model/prompt/extraction.js +169 -0
- package/dist/lib/ai-model/prompt/extraction.js.map +1 -0
- package/dist/lib/ai-model/prompt/llm-locator.js +88 -0
- package/dist/lib/ai-model/prompt/llm-locator.js.map +1 -0
- package/dist/lib/ai-model/prompt/llm-planning.js +401 -0
- package/dist/lib/ai-model/prompt/llm-planning.js.map +1 -0
- package/dist/lib/ai-model/prompt/llm-section-locator.js +81 -0
- package/dist/lib/ai-model/prompt/llm-section-locator.js.map +1 -0
- package/dist/lib/ai-model/prompt/order-sensitive-judge.js +72 -0
- package/dist/lib/ai-model/prompt/order-sensitive-judge.js.map +1 -0
- package/dist/lib/ai-model/prompt/playwright-generator.js +178 -0
- package/dist/lib/ai-model/prompt/playwright-generator.js.map +1 -0
- package/dist/lib/ai-model/prompt/ui-tars-planning.js +73 -0
- package/dist/lib/ai-model/prompt/ui-tars-planning.js.map +1 -0
- package/dist/lib/ai-model/prompt/util.js +105 -0
- package/dist/lib/ai-model/prompt/util.js.map +1 -0
- package/dist/lib/ai-model/prompt/yaml-generator.js +280 -0
- package/dist/lib/ai-model/prompt/yaml-generator.js.map +1 -0
- package/dist/lib/ai-model/service-caller/index.js +531 -0
- package/dist/lib/ai-model/service-caller/index.js.map +1 -0
- package/dist/lib/ai-model/ui-tars-planning.js +283 -0
- package/dist/lib/ai-model/ui-tars-planning.js.map +1 -0
- package/dist/lib/common.js +480 -0
- package/dist/lib/common.js.map +1 -0
- package/dist/lib/device/device-options.js +20 -0
- package/dist/lib/device/device-options.js.map +1 -0
- package/dist/lib/device/index.js +418 -0
- package/dist/lib/device/index.js.map +1 -0
- package/dist/lib/dump/html-utils.js +281 -0
- package/dist/lib/dump/html-utils.js.map +1 -0
- package/dist/lib/dump/image-restoration.js +77 -0
- package/dist/lib/dump/image-restoration.js.map +1 -0
- package/dist/lib/dump/index.js +60 -0
- package/dist/lib/dump/index.js.map +1 -0
- package/dist/lib/index.js +146 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/report-generator.js +172 -0
- package/dist/lib/report-generator.js.map +1 -0
- package/dist/lib/report.js +145 -0
- package/dist/lib/report.js.map +1 -0
- package/dist/lib/screenshot-item.js +139 -0
- package/dist/lib/screenshot-item.js.map +1 -0
- package/dist/lib/service/index.js +290 -0
- package/dist/lib/service/index.js.map +1 -0
- package/dist/lib/service/utils.js +49 -0
- package/dist/lib/service/utils.js.map +1 -0
- package/dist/lib/skill/index.js +72 -0
- package/dist/lib/skill/index.js.map +1 -0
- package/dist/lib/task-runner.js +295 -0
- package/dist/lib/task-runner.js.map +1 -0
- package/dist/lib/task-timing.js +46 -0
- package/dist/lib/task-timing.js.map +1 -0
- package/dist/lib/tree.js +53 -0
- package/dist/lib/tree.js.map +1 -0
- package/dist/lib/types.js +285 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/utils.js +297 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/yaml/builder.js +57 -0
- package/dist/lib/yaml/builder.js.map +1 -0
- package/dist/lib/yaml/index.js +81 -0
- package/dist/lib/yaml/index.js.map +1 -0
- package/dist/lib/yaml/player.js +452 -0
- package/dist/lib/yaml/player.js.map +1 -0
- package/dist/lib/yaml/utils.js +126 -0
- package/dist/lib/yaml/utils.js.map +1 -0
- package/dist/lib/yaml.js +20 -0
- package/dist/lib/yaml.js.map +1 -0
- package/dist/types/agent/agent.d.ts +190 -0
- package/dist/types/agent/common.d.ts +0 -0
- package/dist/types/agent/execution-session.d.ts +36 -0
- package/dist/types/agent/index.d.ts +10 -0
- package/dist/types/agent/task-builder.d.ts +34 -0
- package/dist/types/agent/task-cache.d.ts +48 -0
- package/dist/types/agent/tasks.d.ts +70 -0
- package/dist/types/agent/ui-utils.d.ts +14 -0
- package/dist/types/agent/utils.d.ts +29 -0
- package/dist/types/ai-model/auto-glm/actions.d.ts +77 -0
- package/dist/types/ai-model/auto-glm/index.d.ts +6 -0
- package/dist/types/ai-model/auto-glm/parser.d.ts +18 -0
- package/dist/types/ai-model/auto-glm/planning.d.ts +10 -0
- package/dist/types/ai-model/auto-glm/prompt.d.ts +27 -0
- package/dist/types/ai-model/auto-glm/util.d.ts +13 -0
- package/dist/types/ai-model/conversation-history.d.ts +105 -0
- package/dist/types/ai-model/index.d.ts +14 -0
- package/dist/types/ai-model/inspect.d.ts +58 -0
- package/dist/types/ai-model/llm-planning.d.ts +19 -0
- package/dist/types/ai-model/prompt/common.d.ts +2 -0
- package/dist/types/ai-model/prompt/describe.d.ts +1 -0
- package/dist/types/ai-model/prompt/extraction.d.ts +7 -0
- package/dist/types/ai-model/prompt/llm-locator.d.ts +3 -0
- package/dist/types/ai-model/prompt/llm-planning.d.ts +10 -0
- package/dist/types/ai-model/prompt/llm-section-locator.d.ts +3 -0
- package/dist/types/ai-model/prompt/order-sensitive-judge.d.ts +2 -0
- package/dist/types/ai-model/prompt/playwright-generator.d.ts +26 -0
- package/dist/types/ai-model/prompt/ui-tars-planning.d.ts +2 -0
- package/dist/types/ai-model/prompt/util.d.ts +33 -0
- package/dist/types/ai-model/prompt/yaml-generator.d.ts +100 -0
- package/dist/types/ai-model/service-caller/index.d.ts +49 -0
- package/dist/types/ai-model/ui-tars-planning.d.ts +72 -0
- package/dist/types/common.d.ts +288 -0
- package/dist/types/device/device-options.d.ts +142 -0
- package/dist/types/device/index.d.ts +2315 -0
- package/dist/types/dump/html-utils.d.ts +52 -0
- package/dist/types/dump/image-restoration.d.ts +6 -0
- package/dist/types/dump/index.d.ts +5 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/report-generator.d.ts +48 -0
- package/dist/types/report.d.ts +15 -0
- package/dist/types/screenshot-item.d.ts +66 -0
- package/dist/types/service/index.d.ts +23 -0
- package/dist/types/service/utils.d.ts +2 -0
- package/dist/types/skill/index.d.ts +25 -0
- package/dist/types/task-runner.d.ts +48 -0
- package/dist/types/task-timing.d.ts +8 -0
- package/dist/types/tree.d.ts +4 -0
- package/dist/types/types.d.ts +645 -0
- package/dist/types/utils.d.ts +40 -0
- package/dist/types/yaml/builder.d.ts +2 -0
- package/dist/types/yaml/index.d.ts +4 -0
- package/dist/types/yaml/player.d.ts +34 -0
- package/dist/types/yaml/utils.d.ts +9 -0
- package/dist/types/yaml.d.ts +203 -0
- package/package.json +111 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import node_assert from "node:assert";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { isDeepStrictEqual } from "node:util";
|
|
5
|
+
import { getMidsceneRunSubDir } from "@midscene/shared/common";
|
|
6
|
+
import { MIDSCENE_CACHE_MAX_FILENAME_LENGTH, globalConfigManager } from "@midscene/shared/env";
|
|
7
|
+
import { getDebug } from "@midscene/shared/logger";
|
|
8
|
+
import { generateHashId, ifInBrowser, ifInWorker, replaceIllegalPathCharsAndSpace } from "@midscene/shared/utils";
|
|
9
|
+
import js_yaml from "js-yaml";
|
|
10
|
+
import semver from "semver";
|
|
11
|
+
import { getMidsceneVersion } from "./utils.mjs";
|
|
12
|
+
function _define_property(obj, key, value) {
|
|
13
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
14
|
+
value: value,
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true
|
|
18
|
+
});
|
|
19
|
+
else obj[key] = value;
|
|
20
|
+
return obj;
|
|
21
|
+
}
|
|
22
|
+
const DEFAULT_CACHE_MAX_FILENAME_LENGTH = 200;
|
|
23
|
+
const debug = getDebug('cache');
|
|
24
|
+
const lowestSupportedMidsceneVersion = '0.16.10';
|
|
25
|
+
const cacheFileExt = '.cache.yaml';
|
|
26
|
+
class TaskCache {
|
|
27
|
+
matchCache(prompt, type) {
|
|
28
|
+
if (!this.isCacheResultUsed) return;
|
|
29
|
+
const promptStr = 'string' == typeof prompt ? prompt : JSON.stringify(prompt);
|
|
30
|
+
for(let i = 0; i < this.cacheOriginalLength; i++){
|
|
31
|
+
const item = this.cache.caches[i];
|
|
32
|
+
const key = `${type}:${promptStr}:${i}`;
|
|
33
|
+
if (item.type === type && isDeepStrictEqual(item.prompt, prompt) && !this.matchedCacheIndices.has(key)) {
|
|
34
|
+
if ('locate' === item.type) {
|
|
35
|
+
const locateItem = item;
|
|
36
|
+
if (!locateItem.cache && Array.isArray(locateItem.xpaths)) locateItem.cache = {
|
|
37
|
+
xpaths: locateItem.xpaths
|
|
38
|
+
};
|
|
39
|
+
if ('xpaths' in locateItem) locateItem.xpaths = void 0;
|
|
40
|
+
}
|
|
41
|
+
this.matchedCacheIndices.add(key);
|
|
42
|
+
debug('cache found and marked as used, type: %s, prompt: %s, index: %d', type, prompt, i);
|
|
43
|
+
return {
|
|
44
|
+
cacheContent: item,
|
|
45
|
+
updateFn: (cb)=>{
|
|
46
|
+
debug('will call updateFn to update cache, type: %s, prompt: %s, index: %d', type, prompt, i);
|
|
47
|
+
cb(item);
|
|
48
|
+
if (this.readOnlyMode) return void debug('read-only mode, cache updated in memory but not flushed to file');
|
|
49
|
+
debug('cache updated, will flush to file, type: %s, prompt: %s, index: %d', type, prompt, i);
|
|
50
|
+
this.flushCacheToFile();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
debug('no unused cache found, type: %s, prompt: %s', type, prompt);
|
|
56
|
+
}
|
|
57
|
+
matchPlanCache(prompt) {
|
|
58
|
+
return this.matchCache(prompt, 'plan');
|
|
59
|
+
}
|
|
60
|
+
matchLocateCache(prompt) {
|
|
61
|
+
return this.matchCache(prompt, 'locate');
|
|
62
|
+
}
|
|
63
|
+
appendCache(cache) {
|
|
64
|
+
debug('will append cache', cache);
|
|
65
|
+
this.cache.caches.push(cache);
|
|
66
|
+
if (this.readOnlyMode) return void debug('read-only mode, cache appended to memory but not flushed to file');
|
|
67
|
+
this.flushCacheToFile();
|
|
68
|
+
}
|
|
69
|
+
loadCacheFromFile() {
|
|
70
|
+
const cacheFile = this.cacheFilePath;
|
|
71
|
+
node_assert(cacheFile, 'cache file path is required');
|
|
72
|
+
if (!existsSync(cacheFile)) return void debug('no cache file found, path: %s', cacheFile);
|
|
73
|
+
const jsonTypeCacheFile = cacheFile.replace(cacheFileExt, '.json');
|
|
74
|
+
if (existsSync(jsonTypeCacheFile) && this.isCacheResultUsed) return void console.warn(`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}.`);
|
|
75
|
+
try {
|
|
76
|
+
const data = readFileSync(cacheFile, 'utf8');
|
|
77
|
+
const jsonData = js_yaml.load(data);
|
|
78
|
+
const version = getMidsceneVersion();
|
|
79
|
+
if (!version) return void debug('no midscene version info, will not read cache from file');
|
|
80
|
+
if (semver.lt(jsonData.midsceneVersion, lowestSupportedMidsceneVersion) && !jsonData.midsceneVersion.includes('beta')) return void console.warn(`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.\nPlease delete the existing cache and rebuild it. Sorry for the inconvenience.\ncache file: ${cacheFile}`);
|
|
81
|
+
debug('cache loaded from file, path: %s, cache version: %s, record length: %s', cacheFile, jsonData.midsceneVersion, jsonData.caches.length);
|
|
82
|
+
jsonData.midsceneVersion = getMidsceneVersion();
|
|
83
|
+
return jsonData;
|
|
84
|
+
} catch (err) {
|
|
85
|
+
debug('cache file exists but load failed, path: %s, error: %s', cacheFile, err);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
flushCacheToFile(options) {
|
|
90
|
+
const version = getMidsceneVersion();
|
|
91
|
+
if (!version) return void debug('no midscene version info, will not write cache to file');
|
|
92
|
+
if (!this.cacheFilePath) return void debug('no cache file path, will not write cache to file');
|
|
93
|
+
if (options?.cleanUnused) if (this.isCacheResultUsed) {
|
|
94
|
+
const originalLength = this.cache.caches.length;
|
|
95
|
+
const usedIndices = new Set();
|
|
96
|
+
for (const key of this.matchedCacheIndices){
|
|
97
|
+
const parts = key.split(':');
|
|
98
|
+
const index = Number.parseInt(parts[parts.length - 1], 10);
|
|
99
|
+
if (!Number.isNaN(index)) usedIndices.add(index);
|
|
100
|
+
}
|
|
101
|
+
this.cache.caches = this.cache.caches.filter((_, index)=>{
|
|
102
|
+
const isUsed = usedIndices.has(index);
|
|
103
|
+
const isNew = index >= this.cacheOriginalLength;
|
|
104
|
+
return isUsed || isNew;
|
|
105
|
+
});
|
|
106
|
+
const removedCount = originalLength - this.cache.caches.length;
|
|
107
|
+
removedCount > 0 ? debug('cleaned %d unused cache record(s)', removedCount) : debug('no unused cache to clean');
|
|
108
|
+
} else debug('skip cleaning: cache is not used for reading');
|
|
109
|
+
try {
|
|
110
|
+
const dir = dirname(this.cacheFilePath);
|
|
111
|
+
if (!existsSync(dir)) {
|
|
112
|
+
mkdirSync(dir, {
|
|
113
|
+
recursive: true
|
|
114
|
+
});
|
|
115
|
+
debug('created cache directory: %s', dir);
|
|
116
|
+
}
|
|
117
|
+
const sortedCaches = [
|
|
118
|
+
...this.cache.caches
|
|
119
|
+
].sort((a, b)=>{
|
|
120
|
+
if ('plan' === a.type && 'locate' === b.type) return -1;
|
|
121
|
+
if ('locate' === a.type && 'plan' === b.type) return 1;
|
|
122
|
+
return 0;
|
|
123
|
+
});
|
|
124
|
+
const cacheToWrite = {
|
|
125
|
+
...this.cache,
|
|
126
|
+
caches: sortedCaches
|
|
127
|
+
};
|
|
128
|
+
const yamlData = js_yaml.dump(cacheToWrite, {
|
|
129
|
+
lineWidth: -1
|
|
130
|
+
});
|
|
131
|
+
writeFileSync(this.cacheFilePath, yamlData);
|
|
132
|
+
debug('cache flushed to file: %s', this.cacheFilePath);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
debug('write cache to file failed, path: %s, error: %s', this.cacheFilePath, err);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
updateOrAppendCacheRecord(newRecord, cachedRecord) {
|
|
138
|
+
if (cachedRecord) if ('plan' === newRecord.type) cachedRecord.updateFn((cache)=>{
|
|
139
|
+
cache.yamlWorkflow = newRecord.yamlWorkflow;
|
|
140
|
+
});
|
|
141
|
+
else cachedRecord.updateFn((cache)=>{
|
|
142
|
+
const locateCache = cache;
|
|
143
|
+
locateCache.cache = newRecord.cache;
|
|
144
|
+
if ('xpaths' in locateCache) locateCache.xpaths = void 0;
|
|
145
|
+
});
|
|
146
|
+
else this.appendCache(newRecord);
|
|
147
|
+
}
|
|
148
|
+
constructor(cacheId, isCacheResultUsed, cacheFilePath, options = {}){
|
|
149
|
+
_define_property(this, "cacheId", void 0);
|
|
150
|
+
_define_property(this, "cacheFilePath", void 0);
|
|
151
|
+
_define_property(this, "cache", void 0);
|
|
152
|
+
_define_property(this, "isCacheResultUsed", void 0);
|
|
153
|
+
_define_property(this, "cacheOriginalLength", void 0);
|
|
154
|
+
_define_property(this, "readOnlyMode", void 0);
|
|
155
|
+
_define_property(this, "writeOnlyMode", void 0);
|
|
156
|
+
_define_property(this, "matchedCacheIndices", new Set());
|
|
157
|
+
node_assert(cacheId, 'cacheId is required');
|
|
158
|
+
let safeCacheId = replaceIllegalPathCharsAndSpace(cacheId);
|
|
159
|
+
const cacheMaxFilenameLength = globalConfigManager.getEnvConfigValueAsNumber(MIDSCENE_CACHE_MAX_FILENAME_LENGTH) ?? DEFAULT_CACHE_MAX_FILENAME_LENGTH;
|
|
160
|
+
if (Buffer.byteLength(safeCacheId, 'utf8') > cacheMaxFilenameLength) {
|
|
161
|
+
const prefix = safeCacheId.slice(0, 32);
|
|
162
|
+
const hash = generateHashId(void 0, safeCacheId);
|
|
163
|
+
safeCacheId = `${prefix}-${hash}`;
|
|
164
|
+
}
|
|
165
|
+
this.cacheId = safeCacheId;
|
|
166
|
+
this.cacheFilePath = ifInBrowser || ifInWorker ? void 0 : cacheFilePath || join(getMidsceneRunSubDir('cache'), `${this.cacheId}${cacheFileExt}`);
|
|
167
|
+
const readOnlyMode = Boolean(options?.readOnly);
|
|
168
|
+
const writeOnlyMode = Boolean(options?.writeOnly);
|
|
169
|
+
if (readOnlyMode && writeOnlyMode) throw new Error('TaskCache cannot be both read-only and write-only');
|
|
170
|
+
this.isCacheResultUsed = writeOnlyMode ? false : isCacheResultUsed;
|
|
171
|
+
this.readOnlyMode = readOnlyMode;
|
|
172
|
+
this.writeOnlyMode = writeOnlyMode;
|
|
173
|
+
let cacheContent;
|
|
174
|
+
if (this.cacheFilePath && !this.writeOnlyMode) cacheContent = this.loadCacheFromFile();
|
|
175
|
+
if (!cacheContent) cacheContent = {
|
|
176
|
+
midsceneVersion: getMidsceneVersion(),
|
|
177
|
+
cacheId: this.cacheId,
|
|
178
|
+
caches: []
|
|
179
|
+
};
|
|
180
|
+
this.cache = cacheContent;
|
|
181
|
+
this.cacheOriginalLength = this.isCacheResultUsed ? this.cache.caches.length : 0;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
export { TaskCache, cacheFileExt, debug };
|
|
185
|
+
|
|
186
|
+
//# sourceMappingURL=task-cache.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent/task-cache.mjs","sources":["../../../src/agent/task-cache.ts"],"sourcesContent":["import assert from 'node:assert';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { isDeepStrictEqual } from 'node:util';\nimport type { TUserPrompt } from '@/ai-model';\nimport type { ElementCacheFeature } from '@/types';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n MIDSCENE_CACHE_MAX_FILENAME_LENGTH,\n globalConfigManager,\n} from '@midscene/shared/env';\nimport { getDebug } from '@midscene/shared/logger';\nimport { ifInBrowser, ifInWorker } from '@midscene/shared/utils';\nimport { generateHashId } from '@midscene/shared/utils';\nimport { replaceIllegalPathCharsAndSpace } from '@midscene/shared/utils';\nimport yaml from 'js-yaml';\nimport semver from 'semver';\nimport { getMidsceneVersion } from './utils';\n\nconst DEFAULT_CACHE_MAX_FILENAME_LENGTH = 200;\n\nexport const debug = getDebug('cache');\n\nexport interface PlanningCache {\n type: 'plan';\n prompt: string;\n yamlWorkflow: string;\n}\n\nexport interface LocateCache {\n type: 'locate';\n prompt: TUserPrompt;\n cache?: ElementCacheFeature;\n /** @deprecated kept for backward compatibility */\n xpaths?: string[];\n}\n\nexport interface MatchCacheResult<T extends PlanningCache | LocateCache> {\n cacheContent: T;\n updateFn: (cb: (cache: T) => void) => void;\n}\n\nexport type CacheFileContent = {\n midsceneVersion: string;\n cacheId: string;\n caches: Array<PlanningCache | LocateCache>;\n};\n\nconst lowestSupportedMidsceneVersion = '0.16.10';\nexport const cacheFileExt = '.cache.yaml';\n\nexport class TaskCache {\n cacheId: string;\n\n cacheFilePath?: string;\n\n cache: CacheFileContent;\n\n isCacheResultUsed: boolean; // a flag to indicate if the cache result should be used\n cacheOriginalLength: number;\n\n readOnlyMode: boolean; // a flag to indicate if the cache is in read-only mode\n\n writeOnlyMode: boolean; // a flag to indicate if the cache is in write-only mode\n\n private matchedCacheIndices: Set<string> = new Set(); // Track matched records\n\n constructor(\n cacheId: string,\n isCacheResultUsed: boolean,\n cacheFilePath?: string,\n options: { readOnly?: boolean; writeOnly?: boolean } = {},\n ) {\n assert(cacheId, 'cacheId is required');\n let safeCacheId = replaceIllegalPathCharsAndSpace(cacheId);\n const cacheMaxFilenameLength =\n globalConfigManager.getEnvConfigValueAsNumber(\n MIDSCENE_CACHE_MAX_FILENAME_LENGTH,\n ) ?? DEFAULT_CACHE_MAX_FILENAME_LENGTH;\n if (Buffer.byteLength(safeCacheId, 'utf8') > cacheMaxFilenameLength) {\n const prefix = safeCacheId.slice(0, 32);\n const hash = generateHashId(undefined, safeCacheId);\n safeCacheId = `${prefix}-${hash}`;\n }\n this.cacheId = safeCacheId;\n\n this.cacheFilePath =\n ifInBrowser || ifInWorker\n ? undefined\n : cacheFilePath ||\n join(getMidsceneRunSubDir('cache'), `${this.cacheId}${cacheFileExt}`);\n const readOnlyMode = Boolean(options?.readOnly);\n const writeOnlyMode = Boolean(options?.writeOnly);\n\n if (readOnlyMode && writeOnlyMode) {\n throw new Error('TaskCache cannot be both read-only and write-only');\n }\n\n this.isCacheResultUsed = writeOnlyMode ? false : isCacheResultUsed;\n this.readOnlyMode = readOnlyMode;\n this.writeOnlyMode = writeOnlyMode;\n\n let cacheContent;\n if (this.cacheFilePath && !this.writeOnlyMode) {\n cacheContent = this.loadCacheFromFile();\n }\n if (!cacheContent) {\n cacheContent = {\n midsceneVersion: getMidsceneVersion(),\n cacheId: this.cacheId,\n caches: [],\n };\n }\n this.cache = cacheContent;\n this.cacheOriginalLength = this.isCacheResultUsed\n ? this.cache.caches.length\n : 0;\n }\n\n matchCache(\n prompt: TUserPrompt,\n type: 'plan' | 'locate',\n ): MatchCacheResult<PlanningCache | LocateCache> | undefined {\n if (!this.isCacheResultUsed) {\n return undefined;\n }\n // Find the first unused matching cache\n const promptStr =\n typeof prompt === 'string' ? prompt : JSON.stringify(prompt);\n for (let i = 0; i < this.cacheOriginalLength; i++) {\n const item = this.cache.caches[i];\n const key = `${type}:${promptStr}:${i}`;\n if (\n item.type === type &&\n isDeepStrictEqual(item.prompt, prompt) &&\n !this.matchedCacheIndices.has(key)\n ) {\n if (item.type === 'locate') {\n const locateItem = item as LocateCache;\n if (!locateItem.cache && Array.isArray(locateItem.xpaths)) {\n locateItem.cache = { xpaths: locateItem.xpaths };\n }\n if ('xpaths' in locateItem) {\n locateItem.xpaths = undefined;\n }\n }\n this.matchedCacheIndices.add(key);\n debug(\n 'cache found and marked as used, type: %s, prompt: %s, index: %d',\n type,\n prompt,\n i,\n );\n return {\n cacheContent: item,\n updateFn: (cb: (cache: PlanningCache | LocateCache) => void) => {\n debug(\n 'will call updateFn to update cache, type: %s, prompt: %s, index: %d',\n type,\n prompt,\n i,\n );\n cb(item);\n\n if (this.readOnlyMode) {\n debug(\n 'read-only mode, cache updated in memory but not flushed to file',\n );\n return;\n }\n\n debug(\n 'cache updated, will flush to file, type: %s, prompt: %s, index: %d',\n type,\n prompt,\n i,\n );\n this.flushCacheToFile();\n },\n };\n }\n }\n debug('no unused cache found, type: %s, prompt: %s', type, prompt);\n return undefined;\n }\n\n matchPlanCache(prompt: string): MatchCacheResult<PlanningCache> | undefined {\n return this.matchCache(prompt, 'plan') as\n | MatchCacheResult<PlanningCache>\n | undefined;\n }\n\n matchLocateCache(\n prompt: TUserPrompt,\n ): MatchCacheResult<LocateCache> | undefined {\n return this.matchCache(prompt, 'locate') as\n | MatchCacheResult<LocateCache>\n | undefined;\n }\n\n appendCache(cache: PlanningCache | LocateCache) {\n debug('will append cache', cache);\n this.cache.caches.push(cache);\n\n if (this.readOnlyMode) {\n debug('read-only mode, cache appended to memory but not flushed to file');\n return;\n }\n\n this.flushCacheToFile();\n }\n\n loadCacheFromFile() {\n const cacheFile = this.cacheFilePath;\n assert(cacheFile, 'cache file path is required');\n\n if (!existsSync(cacheFile)) {\n debug('no cache file found, path: %s', cacheFile);\n return undefined;\n }\n\n // detect old cache file\n const jsonTypeCacheFile = cacheFile.replace(cacheFileExt, '.json');\n if (existsSync(jsonTypeCacheFile) && this.isCacheResultUsed) {\n console.warn(\n `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}.`,\n );\n return undefined;\n }\n\n try {\n const data = readFileSync(cacheFile, 'utf8');\n const jsonData = yaml.load(data) as CacheFileContent;\n\n const version = getMidsceneVersion();\n if (!version) {\n debug('no midscene version info, will not read cache from file');\n return undefined;\n }\n\n if (\n semver.lt(jsonData.midsceneVersion, lowestSupportedMidsceneVersion) &&\n !jsonData.midsceneVersion.includes('beta') // for internal test\n ) {\n console.warn(\n `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.\\nPlease delete the existing cache and rebuild it. Sorry for the inconvenience.\\ncache file: ${cacheFile}`,\n );\n return undefined;\n }\n\n debug(\n 'cache loaded from file, path: %s, cache version: %s, record length: %s',\n cacheFile,\n jsonData.midsceneVersion,\n jsonData.caches.length,\n );\n jsonData.midsceneVersion = getMidsceneVersion(); // update the version\n return jsonData;\n } catch (err) {\n debug(\n 'cache file exists but load failed, path: %s, error: %s',\n cacheFile,\n err,\n );\n return undefined;\n }\n }\n\n flushCacheToFile(options?: { cleanUnused?: boolean }) {\n const version = getMidsceneVersion();\n if (!version) {\n debug('no midscene version info, will not write cache to file');\n return;\n }\n\n if (!this.cacheFilePath) {\n debug('no cache file path, will not write cache to file');\n return;\n }\n\n // Clean unused caches if requested\n if (options?.cleanUnused) {\n // Skip cleaning in write-only mode or when cache is not used\n if (this.isCacheResultUsed) {\n const originalLength = this.cache.caches.length;\n\n // Collect indices of used caches\n const usedIndices = new Set<number>();\n for (const key of this.matchedCacheIndices) {\n // key format: \"type:prompt:index\"\n const parts = key.split(':');\n const index = Number.parseInt(parts[parts.length - 1], 10);\n if (!Number.isNaN(index)) {\n usedIndices.add(index);\n }\n }\n\n // Filter: keep used caches and newly added caches\n this.cache.caches = this.cache.caches.filter((_, index) => {\n const isUsed = usedIndices.has(index);\n const isNew = index >= this.cacheOriginalLength;\n return isUsed || isNew;\n });\n\n const removedCount = originalLength - this.cache.caches.length;\n if (removedCount > 0) {\n debug('cleaned %d unused cache record(s)', removedCount);\n } else {\n debug('no unused cache to clean');\n }\n } else {\n debug('skip cleaning: cache is not used for reading');\n }\n }\n\n try {\n const dir = dirname(this.cacheFilePath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n debug('created cache directory: %s', dir);\n }\n\n // Sort caches to ensure plan entries come before locate entries for better readability\n // Create a sorted copy for writing to disk while keeping in-memory order unchanged\n const sortedCaches = [...this.cache.caches].sort((a, b) => {\n if (a.type === 'plan' && b.type === 'locate') return -1;\n if (a.type === 'locate' && b.type === 'plan') return 1;\n return 0;\n });\n\n const cacheToWrite = {\n ...this.cache,\n caches: sortedCaches,\n };\n\n const yamlData = yaml.dump(cacheToWrite, { lineWidth: -1 });\n writeFileSync(this.cacheFilePath, yamlData);\n debug('cache flushed to file: %s', this.cacheFilePath);\n } catch (err) {\n debug(\n 'write cache to file failed, path: %s, error: %s',\n this.cacheFilePath,\n err,\n );\n }\n }\n\n updateOrAppendCacheRecord(\n newRecord: PlanningCache | LocateCache,\n cachedRecord?: MatchCacheResult<PlanningCache | LocateCache>,\n ) {\n if (cachedRecord) {\n // update existing record\n if (newRecord.type === 'plan') {\n cachedRecord.updateFn((cache) => {\n (cache as PlanningCache).yamlWorkflow = newRecord.yamlWorkflow;\n });\n } else {\n cachedRecord.updateFn((cache) => {\n const locateCache = cache as LocateCache;\n locateCache.cache = newRecord.cache;\n if ('xpaths' in locateCache) {\n locateCache.xpaths = undefined;\n }\n });\n }\n } else {\n this.appendCache(newRecord);\n }\n }\n}\n"],"names":["DEFAULT_CACHE_MAX_FILENAME_LENGTH","debug","getDebug","lowestSupportedMidsceneVersion","cacheFileExt","TaskCache","prompt","type","promptStr","JSON","i","item","key","isDeepStrictEqual","locateItem","Array","undefined","cb","cache","cacheFile","assert","existsSync","jsonTypeCacheFile","console","data","readFileSync","jsonData","yaml","version","getMidsceneVersion","semver","err","options","originalLength","usedIndices","Set","parts","index","Number","_","isUsed","isNew","removedCount","dir","dirname","mkdirSync","sortedCaches","a","b","cacheToWrite","yamlData","writeFileSync","newRecord","cachedRecord","locateCache","cacheId","isCacheResultUsed","cacheFilePath","safeCacheId","replaceIllegalPathCharsAndSpace","cacheMaxFilenameLength","globalConfigManager","MIDSCENE_CACHE_MAX_FILENAME_LENGTH","Buffer","prefix","hash","generateHashId","ifInBrowser","ifInWorker","join","getMidsceneRunSubDir","readOnlyMode","Boolean","writeOnlyMode","Error","cacheContent"],"mappings":"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"}
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { AIResponseParseError, ConversationHistory, autoGLMPlanning, plan, uiTarsPlanning } from "../ai-model/index.mjs";
|
|
2
|
+
import { isAutoGLM, isUITars } from "../ai-model/auto-glm/util.mjs";
|
|
3
|
+
import { getReadableTimeString } from "../common.mjs";
|
|
4
|
+
import { TaskExecutionError } from "../task-runner.mjs";
|
|
5
|
+
import { ServiceError } from "../types.mjs";
|
|
6
|
+
import { getCurrentTime } from "@midscene/shared/env";
|
|
7
|
+
import { getDebug } from "@midscene/shared/logger";
|
|
8
|
+
import { assert } from "@midscene/shared/utils";
|
|
9
|
+
import { ExecutionSession } from "./execution-session.mjs";
|
|
10
|
+
import { TaskBuilder, locatePlanForLocate } from "./task-builder.mjs";
|
|
11
|
+
import { setTimingFieldOnce } from "../task-timing.mjs";
|
|
12
|
+
import { descriptionOfTree } from "@midscene/shared/extractor";
|
|
13
|
+
import { taskTitleStr } from "./ui-utils.mjs";
|
|
14
|
+
import { parsePrompt } from "./utils.mjs";
|
|
15
|
+
function _define_property(obj, key, value) {
|
|
16
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
17
|
+
value: value,
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
writable: true
|
|
21
|
+
});
|
|
22
|
+
else obj[key] = value;
|
|
23
|
+
return obj;
|
|
24
|
+
}
|
|
25
|
+
const debug = getDebug('device-task-executor');
|
|
26
|
+
const maxErrorCountAllowedInOnePlanningLoop = 5;
|
|
27
|
+
class TaskExecutor {
|
|
28
|
+
get page() {
|
|
29
|
+
return this.interface;
|
|
30
|
+
}
|
|
31
|
+
createExecutionSession(title, options) {
|
|
32
|
+
return new ExecutionSession(title, ()=>Promise.resolve(this.service.contextRetrieverFn()), {
|
|
33
|
+
onTaskStart: this.onTaskStartCallback,
|
|
34
|
+
tasks: options?.tasks,
|
|
35
|
+
onTaskUpdate: this.hooks?.onTaskUpdate
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
getActionSpace() {
|
|
39
|
+
return this.providedActionSpace;
|
|
40
|
+
}
|
|
41
|
+
async getTimeString(format) {
|
|
42
|
+
const timestamp = await getCurrentTime(this.interface, this.useDeviceTimestamp);
|
|
43
|
+
return getReadableTimeString(format, timestamp);
|
|
44
|
+
}
|
|
45
|
+
async convertPlanToExecutable(plans, modelConfigForPlanning, modelConfigForDefaultIntent, options) {
|
|
46
|
+
return this.taskBuilder.build(plans, modelConfigForPlanning, modelConfigForDefaultIntent, options);
|
|
47
|
+
}
|
|
48
|
+
async loadYamlFlowAsPlanning(userInstruction, yamlString) {
|
|
49
|
+
const session = this.createExecutionSession(taskTitleStr('Act', userInstruction));
|
|
50
|
+
const task = {
|
|
51
|
+
type: 'Planning',
|
|
52
|
+
subType: 'LoadYaml',
|
|
53
|
+
param: {
|
|
54
|
+
userInstruction
|
|
55
|
+
},
|
|
56
|
+
executor: async (param, executorContext)=>{
|
|
57
|
+
const { uiContext } = executorContext;
|
|
58
|
+
assert(uiContext, 'uiContext is required for Planning task');
|
|
59
|
+
return {
|
|
60
|
+
output: {
|
|
61
|
+
actions: [],
|
|
62
|
+
shouldContinuePlanning: false,
|
|
63
|
+
log: '',
|
|
64
|
+
yamlString
|
|
65
|
+
},
|
|
66
|
+
cache: {
|
|
67
|
+
hit: true
|
|
68
|
+
},
|
|
69
|
+
hitBy: {
|
|
70
|
+
from: 'Cache',
|
|
71
|
+
context: {
|
|
72
|
+
yamlString
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const runner = session.getRunner();
|
|
79
|
+
await session.appendAndRun(task);
|
|
80
|
+
return {
|
|
81
|
+
runner
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async runPlans(title, plans, modelConfigForPlanning, modelConfigForDefaultIntent) {
|
|
85
|
+
const session = this.createExecutionSession(title);
|
|
86
|
+
const { tasks } = await this.convertPlanToExecutable(plans, modelConfigForPlanning, modelConfigForDefaultIntent);
|
|
87
|
+
const runner = session.getRunner();
|
|
88
|
+
const result = await session.appendAndRun(tasks);
|
|
89
|
+
const { output } = result ?? {};
|
|
90
|
+
return {
|
|
91
|
+
output,
|
|
92
|
+
runner
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async action(userPrompt, modelConfigForPlanning, modelConfigForDefaultIntent, includeBboxInPlanning, aiActContext, cacheable, replanningCycleLimitOverride, imagesIncludeCount, deepThink, fileChooserAccept, deepLocate, abortSignal) {
|
|
96
|
+
return withFileChooser(this.interface, fileChooserAccept, async ()=>this.runAction(userPrompt, modelConfigForPlanning, modelConfigForDefaultIntent, includeBboxInPlanning, aiActContext, cacheable, replanningCycleLimitOverride, imagesIncludeCount, deepThink, deepLocate, abortSignal));
|
|
97
|
+
}
|
|
98
|
+
async runAction(userPrompt, modelConfigForPlanning, modelConfigForDefaultIntent, includeBboxInPlanning, aiActContext, cacheable, replanningCycleLimitOverride, imagesIncludeCount, deepThink, deepLocate, abortSignal) {
|
|
99
|
+
this.conversationHistory.reset();
|
|
100
|
+
const session = this.createExecutionSession(taskTitleStr('Act', userPrompt));
|
|
101
|
+
const runner = session.getRunner();
|
|
102
|
+
let replanCount = 0;
|
|
103
|
+
const yamlFlow = [];
|
|
104
|
+
const replanningCycleLimit = replanningCycleLimitOverride ?? this.replanningCycleLimit;
|
|
105
|
+
assert(void 0 !== replanningCycleLimit, 'replanningCycleLimit is required for TaskExecutor.action');
|
|
106
|
+
let errorCountInOnePlanningLoop = 0;
|
|
107
|
+
let outputString;
|
|
108
|
+
while(true){
|
|
109
|
+
if (abortSignal?.aborted) return session.appendErrorPlan(`Task aborted: ${abortSignal.reason || 'abort signal received'}`);
|
|
110
|
+
const subGoalStatus = this.conversationHistory.subGoalsToText() || void 0;
|
|
111
|
+
const memoriesStatus = this.conversationHistory.memoriesToText() || void 0;
|
|
112
|
+
const result = await session.appendAndRun({
|
|
113
|
+
type: 'Planning',
|
|
114
|
+
subType: 'Plan',
|
|
115
|
+
param: {
|
|
116
|
+
userInstruction: userPrompt,
|
|
117
|
+
aiActContext,
|
|
118
|
+
imagesIncludeCount,
|
|
119
|
+
deepThink,
|
|
120
|
+
...subGoalStatus ? {
|
|
121
|
+
subGoalStatus
|
|
122
|
+
} : {},
|
|
123
|
+
...memoriesStatus ? {
|
|
124
|
+
memoriesStatus
|
|
125
|
+
} : {}
|
|
126
|
+
},
|
|
127
|
+
executor: async (param, executorContext)=>{
|
|
128
|
+
const { uiContext } = executorContext;
|
|
129
|
+
assert(uiContext, 'uiContext is required for Planning task');
|
|
130
|
+
const { modelFamily } = modelConfigForPlanning;
|
|
131
|
+
const timing = executorContext.task.timing;
|
|
132
|
+
const actionSpace = this.getActionSpace();
|
|
133
|
+
debug('actionSpace for this interface is:', actionSpace.map((action)=>action.name).join(', '));
|
|
134
|
+
assert(Array.isArray(actionSpace), 'actionSpace must be an array');
|
|
135
|
+
if (0 === actionSpace.length) console.warn(`ActionSpace for ${this.interface.interfaceType} is empty. This may lead to unexpected behavior.`);
|
|
136
|
+
const planImpl = isUITars(modelFamily) ? uiTarsPlanning : isAutoGLM(modelFamily) ? autoGLMPlanning : plan;
|
|
137
|
+
let planResult;
|
|
138
|
+
try {
|
|
139
|
+
setTimingFieldOnce(timing, 'callAiStart');
|
|
140
|
+
planResult = await planImpl(param.userInstruction, {
|
|
141
|
+
context: uiContext,
|
|
142
|
+
actionContext: param.aiActContext,
|
|
143
|
+
interfaceType: this.interface.interfaceType,
|
|
144
|
+
actionSpace,
|
|
145
|
+
modelConfig: modelConfigForPlanning,
|
|
146
|
+
conversationHistory: this.conversationHistory,
|
|
147
|
+
includeBbox: includeBboxInPlanning,
|
|
148
|
+
imagesIncludeCount,
|
|
149
|
+
deepThink,
|
|
150
|
+
abortSignal
|
|
151
|
+
});
|
|
152
|
+
} catch (planError) {
|
|
153
|
+
if (planError instanceof AIResponseParseError) {
|
|
154
|
+
executorContext.task.usage = planError.usage;
|
|
155
|
+
executorContext.task.log = {
|
|
156
|
+
...executorContext.task.log || {},
|
|
157
|
+
rawResponse: planError.rawResponse
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
throw planError;
|
|
161
|
+
} finally{
|
|
162
|
+
setTimingFieldOnce(timing, 'callAiEnd');
|
|
163
|
+
}
|
|
164
|
+
debug('planResult', JSON.stringify(planResult, null, 2));
|
|
165
|
+
const { actions, thought, log, memory, error, usage, rawResponse, reasoning_content, finalizeSuccess, finalizeMessage, updateSubGoals, markFinishedIndexes } = planResult;
|
|
166
|
+
outputString = finalizeMessage;
|
|
167
|
+
executorContext.task.log = {
|
|
168
|
+
...executorContext.task.log || {},
|
|
169
|
+
rawResponse
|
|
170
|
+
};
|
|
171
|
+
executorContext.task.usage = usage;
|
|
172
|
+
executorContext.task.reasoning_content = reasoning_content;
|
|
173
|
+
executorContext.task.output = {
|
|
174
|
+
actions: actions || [],
|
|
175
|
+
log,
|
|
176
|
+
thought,
|
|
177
|
+
memory,
|
|
178
|
+
yamlFlow: planResult.yamlFlow,
|
|
179
|
+
output: finalizeMessage,
|
|
180
|
+
shouldContinuePlanning: planResult.shouldContinuePlanning,
|
|
181
|
+
updateSubGoals,
|
|
182
|
+
markFinishedIndexes
|
|
183
|
+
};
|
|
184
|
+
executorContext.uiContext = uiContext;
|
|
185
|
+
assert(!error, `Failed to continue: ${error}\n${log || ''}`);
|
|
186
|
+
if (false === finalizeSuccess) assert(false, `Task failed: ${finalizeMessage || 'No error message provided'}\n${log || ''}`);
|
|
187
|
+
return {
|
|
188
|
+
cache: {
|
|
189
|
+
hit: false
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}, {
|
|
194
|
+
allowWhenError: true
|
|
195
|
+
});
|
|
196
|
+
const planResult = result?.output;
|
|
197
|
+
const plans = planResult?.actions || [];
|
|
198
|
+
yamlFlow.push(...planResult?.yamlFlow || []);
|
|
199
|
+
let executables;
|
|
200
|
+
try {
|
|
201
|
+
executables = await this.convertPlanToExecutable(plans, modelConfigForPlanning, modelConfigForDefaultIntent, {
|
|
202
|
+
cacheable,
|
|
203
|
+
deepLocate,
|
|
204
|
+
abortSignal
|
|
205
|
+
});
|
|
206
|
+
} catch (error) {
|
|
207
|
+
return session.appendErrorPlan(`Error converting plans to executable tasks: ${error}, plans: ${JSON.stringify(plans)}`);
|
|
208
|
+
}
|
|
209
|
+
if (this.conversationHistory.pendingFeedbackMessage) console.warn('unconsumed pending feedback message detected, this may lead to unexpected planning result:', this.conversationHistory.pendingFeedbackMessage);
|
|
210
|
+
const initialTimeString = await this.getTimeString();
|
|
211
|
+
this.conversationHistory.pendingFeedbackMessage += `Current time: ${initialTimeString}`;
|
|
212
|
+
try {
|
|
213
|
+
await session.appendAndRun(executables.tasks);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
errorCountInOnePlanningLoop++;
|
|
216
|
+
const timeString = await this.getTimeString();
|
|
217
|
+
this.conversationHistory.pendingFeedbackMessage = `Time: ${timeString}, Error executing running tasks: ${error?.message || String(error)}`;
|
|
218
|
+
debug('error when executing running tasks, but continue to run if it is not too many errors:', error instanceof Error ? error.message : String(error), 'current error count in one planning loop:', errorCountInOnePlanningLoop);
|
|
219
|
+
}
|
|
220
|
+
if (errorCountInOnePlanningLoop > maxErrorCountAllowedInOnePlanningLoop) return session.appendErrorPlan('Too many errors in one planning loop');
|
|
221
|
+
if (abortSignal?.aborted) return session.appendErrorPlan(`Task aborted: ${abortSignal.reason || 'abort signal received'}`);
|
|
222
|
+
if (!planResult?.shouldContinuePlanning) break;
|
|
223
|
+
++replanCount;
|
|
224
|
+
if (replanCount > replanningCycleLimit) {
|
|
225
|
+
const errorMsg = `Replanned ${replanningCycleLimit} times, exceeding the limit. Please configure a larger value for replanningCycleLimit (or use MIDSCENE_REPLANNING_CYCLE_LIMIT) to handle more complex tasks.`;
|
|
226
|
+
return session.appendErrorPlan(errorMsg);
|
|
227
|
+
}
|
|
228
|
+
if (!this.conversationHistory.pendingFeedbackMessage) {
|
|
229
|
+
const timeString = await this.getTimeString();
|
|
230
|
+
this.conversationHistory.pendingFeedbackMessage = `Time: ${timeString}, I have finished the action previously planned.`;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
output: {
|
|
235
|
+
yamlFlow,
|
|
236
|
+
output: outputString
|
|
237
|
+
},
|
|
238
|
+
runner
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
createTypeQueryTask(type, demand, modelConfig, opt, multimodalPrompt) {
|
|
242
|
+
const queryTask = {
|
|
243
|
+
type: 'Insight',
|
|
244
|
+
subType: type,
|
|
245
|
+
param: {
|
|
246
|
+
dataDemand: multimodalPrompt ? {
|
|
247
|
+
demand,
|
|
248
|
+
multimodalPrompt
|
|
249
|
+
} : demand
|
|
250
|
+
},
|
|
251
|
+
executor: async (param, taskContext)=>{
|
|
252
|
+
const { task } = taskContext;
|
|
253
|
+
let queryDump;
|
|
254
|
+
const applyDump = (dump)=>{
|
|
255
|
+
queryDump = dump;
|
|
256
|
+
task.log = {
|
|
257
|
+
dump,
|
|
258
|
+
rawResponse: dump.taskInfo?.rawResponse
|
|
259
|
+
};
|
|
260
|
+
task.usage = dump.taskInfo?.usage;
|
|
261
|
+
if (dump.taskInfo?.reasoning_content) task.reasoning_content = dump.taskInfo.reasoning_content;
|
|
262
|
+
};
|
|
263
|
+
const uiContext = taskContext.uiContext;
|
|
264
|
+
assert(uiContext, 'uiContext is required for Query task');
|
|
265
|
+
const ifTypeRestricted = 'Query' !== type;
|
|
266
|
+
let demandInput = demand;
|
|
267
|
+
let keyOfResult = 'result';
|
|
268
|
+
if (ifTypeRestricted && ('Assert' === type || 'WaitFor' === type)) {
|
|
269
|
+
keyOfResult = 'StatementIsTruthy';
|
|
270
|
+
const booleanPrompt = 'Assert' === type ? `Boolean, whether the following statement is true: ${demand}` : `Boolean, the user wants to do some 'wait for' operation, please check whether the following statement is true: ${demand}`;
|
|
271
|
+
demandInput = {
|
|
272
|
+
[keyOfResult]: booleanPrompt
|
|
273
|
+
};
|
|
274
|
+
} else if (ifTypeRestricted) demandInput = {
|
|
275
|
+
[keyOfResult]: `${type}, ${demand}`
|
|
276
|
+
};
|
|
277
|
+
let extractResult;
|
|
278
|
+
let extraPageDescription = '';
|
|
279
|
+
if (opt?.domIncluded && this.interface.getElementsNodeTree) {
|
|
280
|
+
debug('appending tree info for page');
|
|
281
|
+
const tree = await this.interface.getElementsNodeTree();
|
|
282
|
+
extraPageDescription = await descriptionOfTree(tree, 200, false, opt?.domIncluded === 'visible-only');
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
extractResult = await this.service.extract(demandInput, modelConfig, opt, extraPageDescription, multimodalPrompt, uiContext);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (error instanceof ServiceError) applyDump(error.dump);
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
const { data, thought, dump } = extractResult;
|
|
291
|
+
applyDump(dump);
|
|
292
|
+
let outputResult = data;
|
|
293
|
+
if (ifTypeRestricted) if ('string' == typeof data) outputResult = data;
|
|
294
|
+
else if ('WaitFor' === type) outputResult = null == data ? false : data[keyOfResult];
|
|
295
|
+
else if (null == data) outputResult = null;
|
|
296
|
+
else {
|
|
297
|
+
assert(data?.[keyOfResult] !== void 0, 'No result in query data');
|
|
298
|
+
outputResult = data[keyOfResult];
|
|
299
|
+
}
|
|
300
|
+
if ('Assert' === type && !outputResult) {
|
|
301
|
+
task.thought = thought;
|
|
302
|
+
throw new Error(`Assertion failed: ${thought}`);
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
output: outputResult,
|
|
306
|
+
log: queryDump,
|
|
307
|
+
thought
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
return queryTask;
|
|
312
|
+
}
|
|
313
|
+
async createTypeQueryExecution(type, demand, modelConfig, opt, multimodalPrompt) {
|
|
314
|
+
const session = this.createExecutionSession(taskTitleStr(type, 'string' == typeof demand ? demand : JSON.stringify(demand)));
|
|
315
|
+
const queryTask = await this.createTypeQueryTask(type, demand, modelConfig, opt, multimodalPrompt);
|
|
316
|
+
const runner = session.getRunner();
|
|
317
|
+
const result = await session.appendAndRun(queryTask);
|
|
318
|
+
if (!result) throw new Error('result of taskExecutor.flush() is undefined in function createTypeQueryTask');
|
|
319
|
+
const { output, thought } = result;
|
|
320
|
+
return {
|
|
321
|
+
output,
|
|
322
|
+
thought,
|
|
323
|
+
runner
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
async waitFor(assertion, opt, modelConfig) {
|
|
327
|
+
const { textPrompt, multimodalPrompt } = parsePrompt(assertion);
|
|
328
|
+
const description = `waitFor: ${textPrompt}`;
|
|
329
|
+
const session = this.createExecutionSession(taskTitleStr('WaitFor', description));
|
|
330
|
+
const runner = session.getRunner();
|
|
331
|
+
const { timeoutMs, checkIntervalMs, domIncluded, screenshotIncluded, ...restOpt } = opt;
|
|
332
|
+
const serviceExtractOpt = {
|
|
333
|
+
domIncluded,
|
|
334
|
+
screenshotIncluded,
|
|
335
|
+
...restOpt
|
|
336
|
+
};
|
|
337
|
+
assert(assertion, 'No assertion for waitFor');
|
|
338
|
+
assert(timeoutMs, 'No timeoutMs for waitFor');
|
|
339
|
+
assert(checkIntervalMs, 'No checkIntervalMs for waitFor');
|
|
340
|
+
assert(checkIntervalMs <= timeoutMs, `wrong config for waitFor: checkIntervalMs must be less than timeoutMs, config: {checkIntervalMs: ${checkIntervalMs}, timeoutMs: ${timeoutMs}}`);
|
|
341
|
+
const overallStartTime = Date.now();
|
|
342
|
+
let lastCheckStart = overallStartTime;
|
|
343
|
+
let errorThought = '';
|
|
344
|
+
while(lastCheckStart - overallStartTime <= timeoutMs){
|
|
345
|
+
const currentCheckStart = Date.now();
|
|
346
|
+
lastCheckStart = currentCheckStart;
|
|
347
|
+
const queryTask = await this.createTypeQueryTask('WaitFor', textPrompt, modelConfig, serviceExtractOpt, multimodalPrompt);
|
|
348
|
+
const result = await session.appendAndRun(queryTask);
|
|
349
|
+
if (result?.output) return {
|
|
350
|
+
output: void 0,
|
|
351
|
+
runner
|
|
352
|
+
};
|
|
353
|
+
errorThought = result?.thought || !result && `No result from assertion: ${textPrompt}` || `unknown error when waiting for assertion: ${textPrompt}`;
|
|
354
|
+
const now = Date.now();
|
|
355
|
+
if (now - currentCheckStart < checkIntervalMs) {
|
|
356
|
+
const elapsed = now - currentCheckStart;
|
|
357
|
+
const timeRemaining = checkIntervalMs - elapsed;
|
|
358
|
+
const thought = `Check interval is ${checkIntervalMs}ms, ${elapsed}ms elapsed since last check, sleeping for ${timeRemaining}ms`;
|
|
359
|
+
const { tasks: sleepTasks } = await this.convertPlanToExecutable([
|
|
360
|
+
{
|
|
361
|
+
type: 'Sleep',
|
|
362
|
+
param: {
|
|
363
|
+
timeMs: timeRemaining
|
|
364
|
+
},
|
|
365
|
+
thought
|
|
366
|
+
}
|
|
367
|
+
], modelConfig, modelConfig);
|
|
368
|
+
if (sleepTasks[0]) await session.appendAndRun(sleepTasks[0]);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return session.appendErrorPlan(`waitFor timeout: ${errorThought}`);
|
|
372
|
+
}
|
|
373
|
+
constructor(interfaceInstance, service, opts){
|
|
374
|
+
_define_property(this, "interface", void 0);
|
|
375
|
+
_define_property(this, "service", void 0);
|
|
376
|
+
_define_property(this, "taskCache", void 0);
|
|
377
|
+
_define_property(this, "providedActionSpace", void 0);
|
|
378
|
+
_define_property(this, "taskBuilder", void 0);
|
|
379
|
+
_define_property(this, "conversationHistory", void 0);
|
|
380
|
+
_define_property(this, "onTaskStartCallback", void 0);
|
|
381
|
+
_define_property(this, "hooks", void 0);
|
|
382
|
+
_define_property(this, "replanningCycleLimit", void 0);
|
|
383
|
+
_define_property(this, "waitAfterAction", void 0);
|
|
384
|
+
_define_property(this, "useDeviceTimestamp", void 0);
|
|
385
|
+
this.interface = interfaceInstance;
|
|
386
|
+
this.service = service;
|
|
387
|
+
this.taskCache = opts.taskCache;
|
|
388
|
+
this.onTaskStartCallback = opts?.onTaskStart;
|
|
389
|
+
this.replanningCycleLimit = opts.replanningCycleLimit;
|
|
390
|
+
this.waitAfterAction = opts.waitAfterAction;
|
|
391
|
+
this.useDeviceTimestamp = opts.useDeviceTimestamp;
|
|
392
|
+
this.hooks = opts.hooks;
|
|
393
|
+
this.conversationHistory = new ConversationHistory();
|
|
394
|
+
this.providedActionSpace = opts.actionSpace;
|
|
395
|
+
this.taskBuilder = new TaskBuilder({
|
|
396
|
+
interfaceInstance,
|
|
397
|
+
service,
|
|
398
|
+
taskCache: opts.taskCache,
|
|
399
|
+
actionSpace: this.getActionSpace(),
|
|
400
|
+
waitAfterAction: opts.waitAfterAction
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
async function withFileChooser(interfaceInstance, fileChooserAccept, action) {
|
|
405
|
+
if (!fileChooserAccept?.length) return action();
|
|
406
|
+
if (!interfaceInstance.registerFileChooserListener) throw new Error(`File upload is not supported on ${interfaceInstance.interfaceType}`);
|
|
407
|
+
const handler = async (chooser)=>{
|
|
408
|
+
await chooser.accept(fileChooserAccept);
|
|
409
|
+
};
|
|
410
|
+
const { dispose, getError } = await interfaceInstance.registerFileChooserListener(handler);
|
|
411
|
+
try {
|
|
412
|
+
const result = await action();
|
|
413
|
+
const error = getError();
|
|
414
|
+
if (error) throw error;
|
|
415
|
+
return result;
|
|
416
|
+
} finally{
|
|
417
|
+
dispose();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
export { TaskExecutionError, TaskExecutor, locatePlanForLocate, withFileChooser };
|
|
421
|
+
|
|
422
|
+
//# sourceMappingURL=tasks.mjs.map
|