@donggui/core 1.5.4-donggui.5 → 1.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/agent/agent.mjs +1 -1
- package/dist/es/agent/agent.mjs.map +1 -1
- package/dist/es/agent/cache-adapter.mjs +0 -0
- package/dist/es/agent/task-builder.mjs +2 -2
- package/dist/es/agent/task-builder.mjs.map +1 -1
- package/dist/es/agent/task-cache.mjs +93 -44
- package/dist/es/agent/task-cache.mjs.map +1 -1
- package/dist/es/agent/utils.mjs +1 -1
- package/dist/es/ai-model/prompt/llm-planning.mjs +357 -153
- package/dist/es/ai-model/prompt/llm-planning.mjs.map +1 -1
- package/dist/es/ai-model/service-caller/codex-app-server.mjs +584 -0
- package/dist/es/ai-model/service-caller/codex-app-server.mjs.map +1 -0
- package/dist/es/ai-model/service-caller/index.mjs +2 -0
- package/dist/es/ai-model/service-caller/index.mjs.map +1 -1
- package/dist/es/device/index.mjs +1 -1
- package/dist/es/device/index.mjs.map +1 -1
- package/dist/es/utils.mjs +2 -2
- package/dist/lib/agent/agent.js +1 -1
- package/dist/lib/agent/agent.js.map +1 -1
- package/dist/lib/agent/cache-adapter.js +20 -0
- package/dist/lib/agent/cache-adapter.js.map +1 -0
- package/dist/lib/agent/task-builder.js +2 -2
- package/dist/lib/agent/task-builder.js.map +1 -1
- package/dist/lib/agent/task-cache.js +93 -44
- package/dist/lib/agent/task-cache.js.map +1 -1
- package/dist/lib/agent/utils.js +1 -1
- package/dist/lib/ai-model/prompt/llm-planning.js +357 -153
- package/dist/lib/ai-model/prompt/llm-planning.js.map +1 -1
- package/dist/lib/ai-model/service-caller/codex-app-server.js +633 -0
- package/dist/lib/ai-model/service-caller/codex-app-server.js.map +1 -0
- package/dist/lib/ai-model/service-caller/index.js +2 -0
- package/dist/lib/ai-model/service-caller/index.js.map +1 -1
- package/dist/lib/device/index.js +1 -1
- package/dist/lib/device/index.js.map +1 -1
- package/dist/lib/utils.js +2 -2
- package/dist/types/agent/cache-adapter.d.ts +32 -0
- package/dist/types/agent/index.d.ts +2 -0
- package/dist/types/agent/task-cache.d.ts +16 -7
- package/dist/types/ai-model/service-caller/codex-app-server.d.ts +46 -0
- package/package.json +3 -3
|
@@ -24,8 +24,15 @@ const debug = getDebug('cache');
|
|
|
24
24
|
const lowestSupportedMidsceneVersion = '0.16.10';
|
|
25
25
|
const cacheFileExt = '.cache.yaml';
|
|
26
26
|
class TaskCache {
|
|
27
|
-
matchCache(prompt, type) {
|
|
27
|
+
async matchCache(prompt, type) {
|
|
28
28
|
if (!this.isCacheResultUsed) return;
|
|
29
|
+
if (this.cacheAdapter && 0 === this.cache.caches.length) {
|
|
30
|
+
const loadedCache = await this.loadCacheFromAdapter();
|
|
31
|
+
if (loadedCache) {
|
|
32
|
+
this.cache = loadedCache;
|
|
33
|
+
this.cacheOriginalLength = loadedCache.caches.length;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
29
36
|
const promptStr = 'string' == typeof prompt ? prompt : JSON.stringify(prompt);
|
|
30
37
|
for(let i = 0; i < this.cacheOriginalLength; i++){
|
|
31
38
|
const item = this.cache.caches[i];
|
|
@@ -42,29 +49,48 @@ class TaskCache {
|
|
|
42
49
|
debug('cache found and marked as used, type: %s, prompt: %s, index: %d', type, prompt, i);
|
|
43
50
|
return {
|
|
44
51
|
cacheContent: item,
|
|
45
|
-
updateFn: (cb)=>{
|
|
52
|
+
updateFn: async (cb, options)=>{
|
|
46
53
|
debug('will call updateFn to update cache, type: %s, prompt: %s, index: %d', type, prompt, i);
|
|
47
54
|
cb(item);
|
|
48
|
-
if (this.readOnlyMode) return void debug('read-only mode, cache updated in memory but not flushed
|
|
49
|
-
debug('cache updated, will flush
|
|
50
|
-
this.
|
|
55
|
+
if (this.readOnlyMode) return void debug('read-only mode, cache updated in memory but not flushed');
|
|
56
|
+
debug('cache updated, will flush, type: %s, prompt: %s, index: %d', type, prompt, i);
|
|
57
|
+
await this.flushCache(options);
|
|
51
58
|
}
|
|
52
59
|
};
|
|
53
60
|
}
|
|
54
61
|
}
|
|
55
62
|
debug('no unused cache found, type: %s, prompt: %s', type, prompt);
|
|
56
63
|
}
|
|
57
|
-
matchPlanCache(prompt) {
|
|
64
|
+
async matchPlanCache(prompt) {
|
|
58
65
|
return this.matchCache(prompt, 'plan');
|
|
59
66
|
}
|
|
60
|
-
matchLocateCache(prompt) {
|
|
67
|
+
async matchLocateCache(prompt) {
|
|
61
68
|
return this.matchCache(prompt, 'locate');
|
|
62
69
|
}
|
|
63
|
-
appendCache(cache) {
|
|
70
|
+
async appendCache(cache) {
|
|
64
71
|
debug('will append cache', cache);
|
|
65
72
|
this.cache.caches.push(cache);
|
|
66
|
-
if (this.readOnlyMode) return void debug('read-only mode, cache appended to memory but not flushed
|
|
67
|
-
this.
|
|
73
|
+
if (this.readOnlyMode) return void debug('read-only mode, cache appended to memory but not flushed');
|
|
74
|
+
await this.flushCache();
|
|
75
|
+
}
|
|
76
|
+
async loadCache() {
|
|
77
|
+
if (this.cacheAdapter) return this.loadCacheFromAdapter();
|
|
78
|
+
return this.loadCacheFromFile();
|
|
79
|
+
}
|
|
80
|
+
async loadCacheFromAdapter() {
|
|
81
|
+
try {
|
|
82
|
+
const content = await this.cacheAdapter.get(this.cacheId);
|
|
83
|
+
if (!content) return void debug('no cache found from adapter, cacheId: %s', this.cacheId);
|
|
84
|
+
const version = getMidsceneVersion();
|
|
85
|
+
if (!version) return void debug('no midscene version info, will not read cache from adapter');
|
|
86
|
+
if (semver.lt(content.midsceneVersion, lowestSupportedMidsceneVersion) && !content.midsceneVersion.includes('beta')) return void console.warn(`You are using an old version of Midscene cache, and we cannot match any info from it.\ncacheId: ${this.cacheId}`);
|
|
87
|
+
debug('cache loaded from adapter, cacheId: %s, cache version: %s, record length: %s', this.cacheId, content.midsceneVersion, content.caches.length);
|
|
88
|
+
content.midsceneVersion = getMidsceneVersion();
|
|
89
|
+
return content;
|
|
90
|
+
} catch (err) {
|
|
91
|
+
debug('load cache from adapter failed, cacheId: %s, error: %s', this.cacheId, err);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
68
94
|
}
|
|
69
95
|
loadCacheFromFile() {
|
|
70
96
|
const cacheFile = this.cacheFilePath;
|
|
@@ -86,26 +112,26 @@ class TaskCache {
|
|
|
86
112
|
return;
|
|
87
113
|
}
|
|
88
114
|
}
|
|
115
|
+
async flushCache(options) {
|
|
116
|
+
if (this.cacheAdapter) return this.flushCacheToAdapter(options);
|
|
117
|
+
return this.flushCacheToFile(options);
|
|
118
|
+
}
|
|
119
|
+
async flushCacheToAdapter(options) {
|
|
120
|
+
const version = getMidsceneVersion();
|
|
121
|
+
if (!version) return void debug('no midscene version info, will not write cache to adapter');
|
|
122
|
+
const cacheToWrite = this.prepareCacheToWrite(options);
|
|
123
|
+
try {
|
|
124
|
+
await this.cacheAdapter.set(this.cacheId, cacheToWrite, void 0, options?.ttl);
|
|
125
|
+
debug('cache flushed to adapter, cacheId: %s', this.cacheId);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
debug('write cache to adapter failed, cacheId: %s, error: %s', this.cacheId, err);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
89
130
|
flushCacheToFile(options) {
|
|
90
131
|
const version = getMidsceneVersion();
|
|
91
132
|
if (!version) return void debug('no midscene version info, will not write cache to file');
|
|
92
133
|
if (!this.cacheFilePath) return void debug('no cache file path, will not write cache to file');
|
|
93
|
-
|
|
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');
|
|
134
|
+
const cacheToWrite = this.prepareCacheToWrite(options);
|
|
109
135
|
try {
|
|
110
136
|
const dir = dirname(this.cacheFilePath);
|
|
111
137
|
if (!existsSync(dir)) {
|
|
@@ -114,17 +140,6 @@ class TaskCache {
|
|
|
114
140
|
});
|
|
115
141
|
debug('created cache directory: %s', dir);
|
|
116
142
|
}
|
|
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
143
|
const yamlData = js_yaml.dump(cacheToWrite, {
|
|
129
144
|
lineWidth: -1
|
|
130
145
|
});
|
|
@@ -134,18 +149,49 @@ class TaskCache {
|
|
|
134
149
|
debug('write cache to file failed, path: %s, error: %s', this.cacheFilePath, err);
|
|
135
150
|
}
|
|
136
151
|
}
|
|
137
|
-
|
|
138
|
-
|
|
152
|
+
prepareCacheToWrite(options) {
|
|
153
|
+
let caches = [
|
|
154
|
+
...this.cache.caches
|
|
155
|
+
];
|
|
156
|
+
if (options?.cleanUnused) if (this.isCacheResultUsed) {
|
|
157
|
+
const usedIndices = new Set();
|
|
158
|
+
for (const key of this.matchedCacheIndices){
|
|
159
|
+
const parts = key.split(':');
|
|
160
|
+
const index = Number.parseInt(parts[parts.length - 1], 10);
|
|
161
|
+
if (!Number.isNaN(index)) usedIndices.add(index);
|
|
162
|
+
}
|
|
163
|
+
caches = caches.filter((_, index)=>{
|
|
164
|
+
const isUsed = usedIndices.has(index);
|
|
165
|
+
const isNew = index >= this.cacheOriginalLength;
|
|
166
|
+
return isUsed || isNew;
|
|
167
|
+
});
|
|
168
|
+
const removedCount = this.cache.caches.length - caches.length;
|
|
169
|
+
removedCount > 0 ? debug('cleaned %d unused cache record(s)', removedCount) : debug('no unused cache to clean');
|
|
170
|
+
} else debug('skip cleaning: cache is not used for reading');
|
|
171
|
+
const sortedCaches = [
|
|
172
|
+
...caches
|
|
173
|
+
].sort((a, b)=>{
|
|
174
|
+
if ('plan' === a.type && 'locate' === b.type) return -1;
|
|
175
|
+
if ('locate' === a.type && 'plan' === b.type) return 1;
|
|
176
|
+
return 0;
|
|
177
|
+
});
|
|
178
|
+
return {
|
|
179
|
+
...this.cache,
|
|
180
|
+
caches: sortedCaches
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
async updateOrAppendCacheRecord(newRecord, cachedRecord) {
|
|
184
|
+
if (cachedRecord) if ('plan' === newRecord.type) await cachedRecord.updateFn((cache)=>{
|
|
139
185
|
cache.yamlWorkflow = newRecord.yamlWorkflow;
|
|
140
186
|
});
|
|
141
|
-
else cachedRecord.updateFn((cache)=>{
|
|
187
|
+
else await cachedRecord.updateFn((cache)=>{
|
|
142
188
|
const locateCache = cache;
|
|
143
189
|
locateCache.cache = newRecord.cache;
|
|
144
190
|
if ('xpaths' in locateCache) locateCache.xpaths = void 0;
|
|
145
191
|
});
|
|
146
|
-
else this.appendCache(newRecord);
|
|
192
|
+
else await this.appendCache(newRecord);
|
|
147
193
|
}
|
|
148
|
-
constructor(cacheId, isCacheResultUsed,
|
|
194
|
+
constructor(cacheId, isCacheResultUsed, cacheAdapterOrPath, options = {}){
|
|
149
195
|
_define_property(this, "cacheId", void 0);
|
|
150
196
|
_define_property(this, "cacheFilePath", void 0);
|
|
151
197
|
_define_property(this, "cache", void 0);
|
|
@@ -154,6 +200,7 @@ class TaskCache {
|
|
|
154
200
|
_define_property(this, "readOnlyMode", void 0);
|
|
155
201
|
_define_property(this, "writeOnlyMode", void 0);
|
|
156
202
|
_define_property(this, "matchedCacheIndices", new Set());
|
|
203
|
+
_define_property(this, "cacheAdapter", void 0);
|
|
157
204
|
node_assert(cacheId, 'cacheId is required');
|
|
158
205
|
let safeCacheId = replaceIllegalPathCharsAndSpace(cacheId);
|
|
159
206
|
const cacheMaxFilenameLength = globalConfigManager.getEnvConfigValueAsNumber(MIDSCENE_CACHE_MAX_FILENAME_LENGTH) ?? DEFAULT_CACHE_MAX_FILENAME_LENGTH;
|
|
@@ -163,15 +210,17 @@ class TaskCache {
|
|
|
163
210
|
safeCacheId = `${prefix}-${hash}`;
|
|
164
211
|
}
|
|
165
212
|
this.cacheId = safeCacheId;
|
|
166
|
-
this.cacheFilePath = ifInBrowser || ifInWorker ? void 0 : cacheFilePath || join(getMidsceneRunSubDir('cache'), `${this.cacheId}${cacheFileExt}`);
|
|
167
213
|
const readOnlyMode = Boolean(options?.readOnly);
|
|
168
214
|
const writeOnlyMode = Boolean(options?.writeOnly);
|
|
169
215
|
if (readOnlyMode && writeOnlyMode) throw new Error('TaskCache cannot be both read-only and write-only');
|
|
170
216
|
this.isCacheResultUsed = writeOnlyMode ? false : isCacheResultUsed;
|
|
171
217
|
this.readOnlyMode = readOnlyMode;
|
|
172
218
|
this.writeOnlyMode = writeOnlyMode;
|
|
219
|
+
if (cacheAdapterOrPath) if ('string' == typeof cacheAdapterOrPath) this.cacheFilePath = ifInBrowser || ifInWorker ? void 0 : cacheAdapterOrPath;
|
|
220
|
+
else this.cacheAdapter = cacheAdapterOrPath;
|
|
221
|
+
if (!this.cacheAdapter) this.cacheFilePath = ifInBrowser || ifInWorker ? void 0 : join(getMidsceneRunSubDir('cache'), `${this.cacheId}${cacheFileExt}`);
|
|
173
222
|
let cacheContent;
|
|
174
|
-
if (
|
|
223
|
+
if (!this.writeOnlyMode) cacheContent = this.loadCacheFromFile();
|
|
175
224
|
if (!cacheContent) cacheContent = {
|
|
176
225
|
midsceneVersion: getMidsceneVersion(),
|
|
177
226
|
cacheId: this.cacheId,
|
|
@@ -1 +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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"}
|
|
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 type { CacheAdapter } from './cache-adapter';\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: (\n cb: (cache: T) => void,\n options?: { ttl?: number },\n ) => Promise<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;\n\n cacheOriginalLength: number;\n\n readOnlyMode: boolean;\n\n writeOnlyMode: boolean;\n\n private matchedCacheIndices: Set<string> = new Set();\n\n private cacheAdapter?: CacheAdapter;\n\n constructor(\n cacheId: string,\n isCacheResultUsed: boolean,\n cacheAdapterOrPath?: CacheAdapter | 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 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 if (cacheAdapterOrPath) {\n if (typeof cacheAdapterOrPath === 'string') {\n this.cacheFilePath =\n ifInBrowser || ifInWorker ? undefined : cacheAdapterOrPath;\n } else {\n this.cacheAdapter = cacheAdapterOrPath;\n }\n }\n\n if (!this.cacheAdapter) {\n this.cacheFilePath =\n ifInBrowser || ifInWorker\n ? undefined\n : join(\n getMidsceneRunSubDir('cache'),\n `${this.cacheId}${cacheFileExt}`,\n );\n }\n\n let cacheContent;\n if (!this.writeOnlyMode) {\n // 同步加载缓存,对于文件系统缓存是同步的,对于适配器缓存会返回undefined\n // 实际的异步加载会在需要时进行\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 async matchCache(\n prompt: TUserPrompt,\n type: 'plan' | 'locate',\n ): Promise<MatchCacheResult<PlanningCache | LocateCache> | undefined> {\n if (!this.isCacheResultUsed) {\n return undefined;\n }\n\n // 如果有缓存适配器且缓存未加载,尝试异步加载\n if (this.cacheAdapter && this.cache.caches.length === 0) {\n const loadedCache = await this.loadCacheFromAdapter();\n if (loadedCache) {\n this.cache = loadedCache;\n this.cacheOriginalLength = loadedCache.caches.length;\n }\n }\n\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: async (\n cb: (cache: PlanningCache | LocateCache) => void,\n options?: { ttl?: number },\n ) => {\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('read-only mode, cache updated in memory but not flushed');\n return;\n }\n\n debug(\n 'cache updated, will flush, type: %s, prompt: %s, index: %d',\n type,\n prompt,\n i,\n );\n await this.flushCache(options);\n },\n };\n }\n }\n debug('no unused cache found, type: %s, prompt: %s', type, prompt);\n return undefined;\n }\n\n async matchPlanCache(\n prompt: string,\n ): Promise<MatchCacheResult<PlanningCache> | undefined> {\n return this.matchCache(prompt, 'plan') as Promise<\n MatchCacheResult<PlanningCache> | undefined\n >;\n }\n\n async matchLocateCache(\n prompt: TUserPrompt,\n ): Promise<MatchCacheResult<LocateCache> | undefined> {\n return this.matchCache(prompt, 'locate') as Promise<\n MatchCacheResult<LocateCache> | undefined\n >;\n }\n\n async 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');\n return;\n }\n\n await this.flushCache();\n }\n\n private async loadCache(): Promise<CacheFileContent | undefined> {\n if (this.cacheAdapter) {\n return this.loadCacheFromAdapter();\n }\n return this.loadCacheFromFile();\n }\n\n private async loadCacheFromAdapter(): Promise<CacheFileContent | undefined> {\n try {\n const content = await this.cacheAdapter!.get(this.cacheId);\n if (!content) {\n debug('no cache found from adapter, cacheId: %s', this.cacheId);\n return undefined;\n }\n\n const version = getMidsceneVersion();\n if (!version) {\n debug('no midscene version info, will not read cache from adapter');\n return undefined;\n }\n\n if (\n semver.lt(content.midsceneVersion, lowestSupportedMidsceneVersion) &&\n !content.midsceneVersion.includes('beta')\n ) {\n console.warn(\n `You are using an old version of Midscene cache, and we cannot match any info from it.\\ncacheId: ${this.cacheId}`,\n );\n return undefined;\n }\n\n debug(\n 'cache loaded from adapter, cacheId: %s, cache version: %s, record length: %s',\n this.cacheId,\n content.midsceneVersion,\n content.caches.length,\n );\n content.midsceneVersion = getMidsceneVersion();\n return content;\n } catch (err) {\n debug(\n 'load cache from adapter failed, cacheId: %s, error: %s',\n this.cacheId,\n err,\n );\n return undefined;\n }\n }\n\n loadCacheFromFile(): CacheFileContent | undefined {\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 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')\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();\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 private async flushCache(options?: { cleanUnused?: boolean; ttl?: number }) {\n if (this.cacheAdapter) {\n return this.flushCacheToAdapter(options);\n }\n return this.flushCacheToFile(options);\n }\n\n private async flushCacheToAdapter(options?: {\n cleanUnused?: boolean;\n ttl?: number;\n }) {\n const version = getMidsceneVersion();\n if (!version) {\n debug('no midscene version info, will not write cache to adapter');\n return;\n }\n\n const cacheToWrite = this.prepareCacheToWrite(options);\n\n try {\n await this.cacheAdapter!.set(\n this.cacheId,\n cacheToWrite,\n undefined,\n options?.ttl,\n );\n debug('cache flushed to adapter, cacheId: %s', this.cacheId);\n } catch (err) {\n debug(\n 'write cache to adapter failed, cacheId: %s, error: %s',\n this.cacheId,\n err,\n );\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 const cacheToWrite = this.prepareCacheToWrite(options);\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 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 private prepareCacheToWrite(options?: {\n cleanUnused?: boolean;\n }): CacheFileContent {\n let caches = [...this.cache.caches];\n\n if (options?.cleanUnused) {\n if (this.isCacheResultUsed) {\n const usedIndices = new Set<number>();\n for (const key of this.matchedCacheIndices) {\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 caches = caches.filter((_, index) => {\n const isUsed = usedIndices.has(index);\n const isNew = index >= this.cacheOriginalLength;\n return isUsed || isNew;\n });\n\n const removedCount = this.cache.caches.length - 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 const sortedCaches = [...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 return {\n ...this.cache,\n caches: sortedCaches,\n };\n }\n\n async updateOrAppendCacheRecord(\n newRecord: PlanningCache | LocateCache,\n cachedRecord?: MatchCacheResult<PlanningCache | LocateCache>,\n ) {\n if (cachedRecord) {\n if (newRecord.type === 'plan') {\n await cachedRecord.updateFn((cache) => {\n (cache as PlanningCache).yamlWorkflow = newRecord.yamlWorkflow;\n });\n } else {\n await 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 await this.appendCache(newRecord);\n }\n }\n}\n"],"names":["DEFAULT_CACHE_MAX_FILENAME_LENGTH","debug","getDebug","lowestSupportedMidsceneVersion","cacheFileExt","TaskCache","prompt","type","loadedCache","promptStr","JSON","i","item","key","isDeepStrictEqual","locateItem","Array","undefined","cb","options","cache","content","version","getMidsceneVersion","semver","console","err","cacheFile","assert","existsSync","jsonTypeCacheFile","data","readFileSync","jsonData","yaml","cacheToWrite","dir","dirname","mkdirSync","yamlData","writeFileSync","caches","usedIndices","Set","parts","index","Number","_","isUsed","isNew","removedCount","sortedCaches","a","b","newRecord","cachedRecord","locateCache","cacheId","isCacheResultUsed","cacheAdapterOrPath","safeCacheId","replaceIllegalPathCharsAndSpace","cacheMaxFilenameLength","globalConfigManager","MIDSCENE_CACHE_MAX_FILENAME_LENGTH","Buffer","prefix","hash","generateHashId","readOnlyMode","Boolean","writeOnlyMode","Error","ifInBrowser","ifInWorker","join","getMidsceneRunSubDir","cacheContent"],"mappings":"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"}
|
package/dist/es/agent/utils.mjs
CHANGED
|
@@ -152,7 +152,7 @@ async function matchElementFromCache(context, cacheEntry, cachePrompt, cacheable
|
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
-
const getMidsceneVersion = ()=>"1.5.
|
|
155
|
+
const getMidsceneVersion = ()=>"1.5.6";
|
|
156
156
|
const parsePrompt = (prompt)=>{
|
|
157
157
|
if ('string' == typeof prompt) return {
|
|
158
158
|
textPrompt: prompt,
|