@midscene/web 0.19.1 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/es/agent.js +299 -247
  2. package/dist/es/agent.js.map +1 -1
  3. package/dist/es/bridge-mode-browser.js +3 -3
  4. package/dist/es/bridge-mode.js +301 -249
  5. package/dist/es/bridge-mode.js.map +1 -1
  6. package/dist/es/chrome-extension.js +342 -290
  7. package/dist/es/chrome-extension.js.map +1 -1
  8. package/dist/es/index.js +307 -247
  9. package/dist/es/index.js.map +1 -1
  10. package/dist/es/midscene-playground.js +341 -289
  11. package/dist/es/midscene-playground.js.map +1 -1
  12. package/dist/es/midscene-server.js +25 -12
  13. package/dist/es/midscene-server.js.map +1 -1
  14. package/dist/es/playground.js +341 -289
  15. package/dist/es/playground.js.map +1 -1
  16. package/dist/es/playwright-report.js +14 -1
  17. package/dist/es/playwright-report.js.map +1 -1
  18. package/dist/es/playwright-reporter.js +14 -1
  19. package/dist/es/playwright-reporter.js.map +1 -1
  20. package/dist/es/playwright.js +307 -247
  21. package/dist/es/playwright.js.map +1 -1
  22. package/dist/es/puppeteer-agent-launcher.js +299 -247
  23. package/dist/es/puppeteer-agent-launcher.js.map +1 -1
  24. package/dist/es/puppeteer.js +299 -247
  25. package/dist/es/puppeteer.js.map +1 -1
  26. package/dist/es/utils.js +42 -8
  27. package/dist/es/utils.js.map +1 -1
  28. package/dist/es/yaml.js +11 -4
  29. package/dist/es/yaml.js.map +1 -1
  30. package/dist/lib/agent.js +308 -256
  31. package/dist/lib/agent.js.map +1 -1
  32. package/dist/lib/bridge-mode-browser.js +3 -3
  33. package/dist/lib/bridge-mode.js +310 -258
  34. package/dist/lib/bridge-mode.js.map +1 -1
  35. package/dist/lib/chrome-extension.js +355 -303
  36. package/dist/lib/chrome-extension.js.map +1 -1
  37. package/dist/lib/index.js +316 -256
  38. package/dist/lib/index.js.map +1 -1
  39. package/dist/lib/midscene-playground.js +354 -302
  40. package/dist/lib/midscene-playground.js.map +1 -1
  41. package/dist/lib/midscene-server.js +28 -15
  42. package/dist/lib/midscene-server.js.map +1 -1
  43. package/dist/lib/playground.js +354 -302
  44. package/dist/lib/playground.js.map +1 -1
  45. package/dist/lib/playwright-report.js +20 -7
  46. package/dist/lib/playwright-report.js.map +1 -1
  47. package/dist/lib/playwright-reporter.js +20 -7
  48. package/dist/lib/playwright-reporter.js.map +1 -1
  49. package/dist/lib/playwright.js +316 -256
  50. package/dist/lib/playwright.js.map +1 -1
  51. package/dist/lib/puppeteer-agent-launcher.js +308 -256
  52. package/dist/lib/puppeteer-agent-launcher.js.map +1 -1
  53. package/dist/lib/puppeteer.js +308 -256
  54. package/dist/lib/puppeteer.js.map +1 -1
  55. package/dist/lib/utils.js +48 -13
  56. package/dist/lib/utils.js.map +1 -1
  57. package/dist/lib/yaml.js +11 -4
  58. package/dist/lib/yaml.js.map +1 -1
  59. package/dist/types/agent.d.ts +6 -102
  60. package/dist/types/bridge-mode-browser.d.ts +3 -2
  61. package/dist/types/bridge-mode.d.ts +4 -4
  62. package/dist/types/{browser-5dbb4bfb.d.ts → browser-118d886d.d.ts} +1 -1
  63. package/dist/types/chrome-extension.d.ts +2 -2
  64. package/dist/types/index.d.ts +1 -1
  65. package/dist/types/midscene-server.d.ts +2 -2
  66. package/dist/types/{page-90e9f9a7.d.ts → page-471361cd.d.ts} +102 -3
  67. package/dist/types/playground.d.ts +2 -2
  68. package/dist/types/playwright.d.ts +6 -2
  69. package/dist/types/puppeteer-agent-launcher.d.ts +1 -1
  70. package/dist/types/puppeteer.d.ts +3 -3
  71. package/dist/types/utils.d.ts +2 -1
  72. package/dist/types/yaml.d.ts +1 -1
  73. package/package.json +3 -3
package/dist/es/index.js CHANGED
@@ -149,7 +149,7 @@ var ScriptPlayer = class {
149
149
  domIncluded: numberTask.domIncluded,
150
150
  screenshotIncluded: numberTask.screenshotIncluded
151
151
  };
152
- assert(prompt, "missing prompt for number");
152
+ assert(prompt, "missing prompt for aiNumber");
153
153
  assert(
154
154
  typeof prompt === "string",
155
155
  "prompt for number must be a string"
@@ -163,7 +163,7 @@ var ScriptPlayer = class {
163
163
  domIncluded: stringTask.domIncluded,
164
164
  screenshotIncluded: stringTask.screenshotIncluded
165
165
  };
166
- assert(prompt, "missing prompt for string");
166
+ assert(prompt, "missing prompt for aiNumber");
167
167
  assert(
168
168
  typeof prompt === "string",
169
169
  "prompt for string must be a string"
@@ -177,13 +177,20 @@ var ScriptPlayer = class {
177
177
  domIncluded: booleanTask.domIncluded,
178
178
  screenshotIncluded: booleanTask.screenshotIncluded
179
179
  };
180
- assert(prompt, "missing prompt for boolean");
180
+ assert(prompt, "missing prompt for aiBoolean");
181
181
  assert(
182
182
  typeof prompt === "string",
183
183
  "prompt for boolean must be a string"
184
184
  );
185
185
  const booleanResult = await agent.aiBoolean(prompt, options);
186
186
  this.setResult(booleanTask.name, booleanResult);
187
+ } else if ("aiAsk" in flowItem) {
188
+ const askTask = flowItem;
189
+ const prompt = askTask.aiAsk;
190
+ assert(prompt, "missing prompt for aiAsk");
191
+ assert(typeof prompt === "string", "prompt for aiAsk must be a string");
192
+ const askResult = await agent.aiAsk(prompt);
193
+ this.setResult(askTask.name, askResult);
187
194
  } else if ("aiLocate" in flowItem) {
188
195
  const locateTask = flowItem;
189
196
  const prompt = locateTask.aiLocate;
@@ -192,7 +199,7 @@ var ScriptPlayer = class {
192
199
  typeof prompt === "string",
193
200
  "prompt for aiLocate must be a string"
194
201
  );
195
- const locateResult = await agent.aiLocate(prompt);
202
+ const locateResult = await agent.aiLocate(prompt, locateTask);
196
203
  this.setResult(locateTask.name, locateResult);
197
204
  } else if ("aiWaitFor" in flowItem) {
198
205
  const waitForTask = flowItem;
@@ -418,8 +425,8 @@ import {
418
425
  } from "@midscene/core/ai-model";
419
426
  import { sleep } from "@midscene/core/utils";
420
427
  import { NodeType } from "@midscene/shared/constants";
421
- import { getDebug } from "@midscene/shared/logger";
422
- import { assert as assert4 } from "@midscene/shared/utils";
428
+ import { getDebug as getDebug2 } from "@midscene/shared/logger";
429
+ import { assert as assert5 } from "@midscene/shared/utils";
423
430
 
424
431
  // src/common/ui-utils.ts
425
432
  function typeStr(task) {
@@ -504,7 +511,7 @@ import {
504
511
  traverseTree
505
512
  } from "@midscene/shared/extractor";
506
513
  import { resizeImgBase64 } from "@midscene/shared/img";
507
- import { assert as assert3, logMsg, uuid } from "@midscene/shared/utils";
514
+ import { assert as assert4, logMsg, uuid } from "@midscene/shared/utils";
508
515
  import dayjs from "dayjs";
509
516
 
510
517
  // src/web-element.ts
@@ -532,9 +539,184 @@ var WebElementInfo = class {
532
539
  }
533
540
  };
534
541
 
542
+ // src/common/task-cache.ts
543
+ import assert3 from "assert";
544
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
545
+ import { dirname as dirname2, join as join2 } from "path";
546
+ import { getMidsceneRunSubDir as getMidsceneRunSubDir2 } from "@midscene/shared/common";
547
+ import { getDebug } from "@midscene/shared/logger";
548
+ import { ifInBrowser as ifInBrowser2 } from "@midscene/shared/utils";
549
+ import yaml3 from "js-yaml";
550
+ import semver from "semver";
551
+
552
+ // package.json
553
+ var version = "0.20.0";
554
+
555
+ // src/common/task-cache.ts
556
+ var debug = getDebug("cache");
557
+ var lowestSupportedMidsceneVersion = "0.16.10";
558
+ var cacheFileExt = ".cache.yaml";
559
+ var TaskCache = class {
560
+ // Track matched records
561
+ constructor(cacheId, isCacheResultUsed, cacheFilePath) {
562
+ this.matchedCacheIndices = /* @__PURE__ */ new Set();
563
+ assert3(cacheId, "cacheId is required");
564
+ this.cacheId = replaceIllegalPathCharsAndSpace(cacheId);
565
+ this.cacheFilePath = ifInBrowser2 ? void 0 : cacheFilePath || join2(getMidsceneRunSubDir2("cache"), `${this.cacheId}${cacheFileExt}`);
566
+ this.isCacheResultUsed = isCacheResultUsed;
567
+ let cacheContent;
568
+ if (this.cacheFilePath) {
569
+ cacheContent = this.loadCacheFromFile();
570
+ }
571
+ if (!cacheContent) {
572
+ cacheContent = {
573
+ midsceneVersion: version,
574
+ cacheId: this.cacheId,
575
+ caches: []
576
+ };
577
+ }
578
+ this.cache = cacheContent;
579
+ this.cacheOriginalLength = this.cache.caches.length;
580
+ }
581
+ matchCache(prompt, type) {
582
+ for (let i = 0; i < this.cacheOriginalLength; i++) {
583
+ const item = this.cache.caches[i];
584
+ const key = `${type}:${prompt}:${i}`;
585
+ if (item.type === type && item.prompt === prompt && !this.matchedCacheIndices.has(key)) {
586
+ this.matchedCacheIndices.add(key);
587
+ debug(
588
+ "cache found and marked as used, type: %s, prompt: %s, index: %d",
589
+ type,
590
+ prompt,
591
+ i
592
+ );
593
+ return {
594
+ cacheContent: item,
595
+ updateFn: (cb) => {
596
+ debug(
597
+ "will call updateFn to update cache, type: %s, prompt: %s, index: %d",
598
+ type,
599
+ prompt,
600
+ i
601
+ );
602
+ cb(item);
603
+ debug(
604
+ "cache updated, will flush to file, type: %s, prompt: %s, index: %d",
605
+ type,
606
+ prompt,
607
+ i
608
+ );
609
+ this.flushCacheToFile();
610
+ }
611
+ };
612
+ }
613
+ }
614
+ debug("no unused cache found, type: %s, prompt: %s", type, prompt);
615
+ return void 0;
616
+ }
617
+ matchPlanCache(prompt) {
618
+ return this.matchCache(prompt, "plan");
619
+ }
620
+ matchLocateCache(prompt) {
621
+ return this.matchCache(prompt, "locate");
622
+ }
623
+ appendCache(cache) {
624
+ debug("will append cache", cache);
625
+ this.cache.caches.push(cache);
626
+ this.flushCacheToFile();
627
+ }
628
+ loadCacheFromFile() {
629
+ const cacheFile = this.cacheFilePath;
630
+ assert3(cacheFile, "cache file path is required");
631
+ if (!existsSync2(cacheFile)) {
632
+ debug("no cache file found, path: %s", cacheFile);
633
+ return void 0;
634
+ }
635
+ const jsonTypeCacheFile = cacheFile.replace(cacheFileExt, ".json");
636
+ if (existsSync2(jsonTypeCacheFile) && this.isCacheResultUsed) {
637
+ console.warn(
638
+ `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}.`
639
+ );
640
+ return void 0;
641
+ }
642
+ try {
643
+ const data = readFileSync(cacheFile, "utf8");
644
+ const jsonData = yaml3.load(data);
645
+ if (!version) {
646
+ debug("no midscene version info, will not read cache from file");
647
+ return void 0;
648
+ }
649
+ if (semver.lt(jsonData.midsceneVersion, lowestSupportedMidsceneVersion) && !jsonData.midsceneVersion.includes("beta")) {
650
+ console.warn(
651
+ `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.
652
+ Please delete the existing cache and rebuild it. Sorry for the inconvenience.
653
+ cache file: ${cacheFile}`
654
+ );
655
+ return void 0;
656
+ }
657
+ debug(
658
+ "cache loaded from file, path: %s, cache version: %s, record length: %s",
659
+ cacheFile,
660
+ jsonData.midsceneVersion,
661
+ jsonData.caches.length
662
+ );
663
+ jsonData.midsceneVersion = version;
664
+ return jsonData;
665
+ } catch (err) {
666
+ debug(
667
+ "cache file exists but load failed, path: %s, error: %s",
668
+ cacheFile,
669
+ err
670
+ );
671
+ return void 0;
672
+ }
673
+ }
674
+ flushCacheToFile() {
675
+ if (!version) {
676
+ debug("no midscene version info, will not write cache to file");
677
+ return;
678
+ }
679
+ if (!this.cacheFilePath) {
680
+ debug("no cache file path, will not write cache to file");
681
+ return;
682
+ }
683
+ try {
684
+ const dir = dirname2(this.cacheFilePath);
685
+ if (!existsSync2(dir)) {
686
+ mkdirSync2(dir, { recursive: true });
687
+ debug("created cache directory: %s", dir);
688
+ }
689
+ const yamlData = yaml3.dump(this.cache);
690
+ writeFileSync2(this.cacheFilePath, yamlData);
691
+ debug("cache flushed to file: %s", this.cacheFilePath);
692
+ } catch (err) {
693
+ debug(
694
+ "write cache to file failed, path: %s, error: %s",
695
+ this.cacheFilePath,
696
+ err
697
+ );
698
+ }
699
+ }
700
+ updateOrAppendCacheRecord(newRecord, cachedRecord) {
701
+ if (cachedRecord) {
702
+ if (newRecord.type === "plan") {
703
+ cachedRecord.updateFn((cache) => {
704
+ cache.yamlWorkflow = newRecord.yamlWorkflow;
705
+ });
706
+ } else {
707
+ cachedRecord.updateFn((cache) => {
708
+ cache.xpaths = newRecord.xpaths;
709
+ });
710
+ }
711
+ } else {
712
+ this.appendCache(newRecord);
713
+ }
714
+ }
715
+ };
716
+
535
717
  // src/common/utils.ts
536
718
  async function parseContextFromWebPage(page, _opt) {
537
- assert3(page, "page is required");
719
+ assert4(page, "page is required");
538
720
  if (page._forceUsePageContext) {
539
721
  return await page._forceUsePageContext();
540
722
  }
@@ -561,7 +743,7 @@ async function parseContextFromWebPage(page, _opt) {
561
743
  isVisible
562
744
  });
563
745
  });
564
- assert3(screenshotBase64, "screenshotBase64 is required");
746
+ assert4(screenshotBase64, "screenshotBase64 is required");
565
747
  const size = await page.size();
566
748
  if (size.dpr && size.dpr > 1) {
567
749
  screenshotBase64 = await resizeImgBase64(screenshotBase64, {
@@ -636,6 +818,28 @@ function matchElementFromPlan(planLocateParam, tree) {
636
818
  }
637
819
  return void 0;
638
820
  }
821
+ async function matchElementFromCache(taskExecutor, xpaths, cachePrompt, cacheable) {
822
+ try {
823
+ if (xpaths?.length && taskExecutor.taskCache?.isCacheResultUsed && cacheable !== false) {
824
+ for (let i = 0; i < xpaths.length; i++) {
825
+ const element = await taskExecutor.page.getElementInfoByXpath(
826
+ xpaths[i]
827
+ );
828
+ if (element?.id) {
829
+ debug("cache hit, prompt: %s", cachePrompt);
830
+ debug(
831
+ "found a new new element with same xpath, xpath: %s, id: %s",
832
+ xpaths[i],
833
+ element?.id
834
+ );
835
+ return element;
836
+ }
837
+ }
838
+ }
839
+ } catch (error) {
840
+ debug("get element info by xpath error: ", error);
841
+ }
842
+ }
639
843
  function trimContextByViewport(execution) {
640
844
  function filterVisibleTree(node) {
641
845
  if (!node)
@@ -674,7 +878,7 @@ function trimContextByViewport(execution) {
674
878
  }
675
879
 
676
880
  // src/common/tasks.ts
677
- var debug = getDebug("page-task-executor");
881
+ var debug2 = getDebug2("page-task-executor");
678
882
  var replanningCountLimit = 10;
679
883
  var isAndroidPage = (page) => {
680
884
  return page.pageType === "android";
@@ -715,7 +919,7 @@ var PageTaskExecutor = class {
715
919
  if (info?.id) {
716
920
  elementId = info.id;
717
921
  } else {
718
- debug(
922
+ debug2(
719
923
  "no element id found for position node, will not update cache",
720
924
  element
721
925
  );
@@ -728,7 +932,7 @@ var PageTaskExecutor = class {
728
932
  const result = await this.page.getXpathsById(elementId);
729
933
  return result;
730
934
  } catch (error) {
731
- debug("getXpathsById error: ", error);
935
+ debug2("getXpathsById error: ", error);
732
936
  }
733
937
  }
734
938
  prependExecutorWithScreenshot(taskApply, appendAfterExecution = false) {
@@ -782,7 +986,7 @@ var PageTaskExecutor = class {
782
986
  locate: plan2.locate,
783
987
  executor: async (param, taskContext) => {
784
988
  const { task } = taskContext;
785
- assert4(
989
+ assert5(
786
990
  param?.prompt || param?.id || param?.bbox,
787
991
  "No prompt or id or position or bbox to locate"
788
992
  );
@@ -807,39 +1011,29 @@ var PageTaskExecutor = class {
807
1011
  timing: "before Insight"
808
1012
  };
809
1013
  task.recorder = [recordItem];
810
- let cacheHitFlag = false;
1014
+ const elementFromXpath = param.xpath ? await this.page.getElementInfoByXpath(param.xpath) : void 0;
1015
+ const userExpectedPathHitFlag = !!elementFromXpath;
811
1016
  const cachePrompt = param.prompt;
812
1017
  const locateCacheRecord = this.taskCache?.matchLocateCache(cachePrompt);
813
1018
  const xpaths = locateCacheRecord?.cacheContent?.xpaths;
814
- let elementFromCache = null;
815
- try {
816
- if (xpaths?.length && this.taskCache?.isCacheResultUsed && param?.cacheable !== false) {
817
- for (let i = 0; i < xpaths.length; i++) {
818
- const element2 = await this.page.getElementInfoByXpath(
819
- xpaths[i]
820
- );
821
- if (element2?.id) {
822
- elementFromCache = element2;
823
- debug("cache hit, prompt: %s", cachePrompt);
824
- cacheHitFlag = true;
825
- debug(
826
- "found a new new element with same xpath, xpath: %s, id: %s",
827
- xpaths[i],
828
- element2?.id
829
- );
830
- break;
831
- }
832
- }
833
- }
834
- } catch (error) {
835
- debug("get element info by xpath error: ", error);
836
- }
837
- const startTime = Date.now();
838
- const element = elementFromCache || // try to match element from cache
839
- matchElementFromPlan(param, pageContext.tree) || // try to match element from plan
840
- (await this.insight.locate(param, {
1019
+ const elementFromCache = userExpectedPathHitFlag ? null : await matchElementFromCache(
1020
+ this,
1021
+ xpaths,
1022
+ cachePrompt,
1023
+ param.cacheable
1024
+ );
1025
+ const cacheHitFlag = !!elementFromCache;
1026
+ const elementFromPlan = !userExpectedPathHitFlag && !cacheHitFlag ? matchElementFromPlan(param, pageContext.tree) : void 0;
1027
+ const planHitFlag = !!elementFromPlan;
1028
+ const elementFromAiLocate = !userExpectedPathHitFlag && !cacheHitFlag && !planHitFlag ? (await this.insight.locate(param, {
1029
+ // fallback to ai locate
841
1030
  context: pageContext
842
- })).element;
1031
+ })).element : void 0;
1032
+ const aiLocateHitFlag = !!elementFromAiLocate;
1033
+ const element = elementFromXpath || // highest priority
1034
+ elementFromCache || // second priority
1035
+ elementFromPlan || // third priority
1036
+ elementFromAiLocate;
843
1037
  let currentXpaths;
844
1038
  if (element && this.taskCache && !cacheHitFlag && param?.cacheable !== false) {
845
1039
  const elementXpaths = await this.getElementXpath(
@@ -857,7 +1051,7 @@ var PageTaskExecutor = class {
857
1051
  locateCacheRecord
858
1052
  );
859
1053
  } else {
860
- debug(
1054
+ debug2(
861
1055
  "no xpaths found, will not update cache",
862
1056
  cachePrompt,
863
1057
  elementXpaths
@@ -867,16 +1061,44 @@ var PageTaskExecutor = class {
867
1061
  if (!element) {
868
1062
  throw new Error(`Element not found: ${param.prompt}`);
869
1063
  }
1064
+ let hitBy;
1065
+ if (userExpectedPathHitFlag) {
1066
+ hitBy = {
1067
+ from: "User expected path",
1068
+ context: {
1069
+ xpath: param.xpath
1070
+ }
1071
+ };
1072
+ } else if (cacheHitFlag) {
1073
+ hitBy = {
1074
+ from: "Cache",
1075
+ context: {
1076
+ xpathsFromCache: xpaths,
1077
+ xpathsToSave: currentXpaths
1078
+ }
1079
+ };
1080
+ } else if (planHitFlag) {
1081
+ hitBy = {
1082
+ from: "Planning",
1083
+ context: {
1084
+ id: elementFromPlan?.id,
1085
+ bbox: elementFromPlan?.bbox
1086
+ }
1087
+ };
1088
+ } else if (aiLocateHitFlag) {
1089
+ hitBy = {
1090
+ from: "AI model",
1091
+ context: {
1092
+ prompt: param.prompt
1093
+ }
1094
+ };
1095
+ }
870
1096
  return {
871
1097
  output: {
872
1098
  element
873
1099
  },
874
1100
  pageContext,
875
- cache: {
876
- hit: cacheHitFlag,
877
- originalXpaths: xpaths,
878
- currentXpaths
879
- }
1101
+ hitBy
880
1102
  };
881
1103
  }
882
1104
  };
@@ -972,7 +1194,7 @@ var PageTaskExecutor = class {
972
1194
  thought: plan2.thought,
973
1195
  locate: plan2.locate,
974
1196
  executor: async (param, { element }) => {
975
- assert4(element, "Element not found, cannot tap");
1197
+ assert5(element, "Element not found, cannot tap");
976
1198
  await this.page.mouse.click(element.center[0], element.center[1]);
977
1199
  }
978
1200
  };
@@ -984,7 +1206,7 @@ var PageTaskExecutor = class {
984
1206
  thought: plan2.thought,
985
1207
  locate: plan2.locate,
986
1208
  executor: async (param, { element }) => {
987
- assert4(element, "Element not found, cannot right click");
1209
+ assert5(element, "Element not found, cannot right click");
988
1210
  await this.page.mouse.click(
989
1211
  element.center[0],
990
1212
  element.center[1],
@@ -1001,7 +1223,7 @@ var PageTaskExecutor = class {
1001
1223
  thought: plan2.thought,
1002
1224
  locate: plan2.locate,
1003
1225
  executor: async (taskParam) => {
1004
- assert4(
1226
+ assert5(
1005
1227
  taskParam?.start_box && taskParam?.end_box,
1006
1228
  "No start_box or end_box to drag"
1007
1229
  );
@@ -1016,7 +1238,7 @@ var PageTaskExecutor = class {
1016
1238
  thought: plan2.thought,
1017
1239
  locate: plan2.locate,
1018
1240
  executor: async (param, { element }) => {
1019
- assert4(element, "Element not found, cannot hover");
1241
+ assert5(element, "Element not found, cannot hover");
1020
1242
  await this.page.mouse.move(element.center[0], element.center[1]);
1021
1243
  }
1022
1244
  };
@@ -1135,7 +1357,7 @@ var PageTaskExecutor = class {
1135
1357
  thought: plan2.thought,
1136
1358
  locate: plan2.locate,
1137
1359
  executor: async (param) => {
1138
- assert4(
1360
+ assert5(
1139
1361
  isAndroidPage(this.page),
1140
1362
  "Cannot use home button on non-Android devices"
1141
1363
  );
@@ -1151,7 +1373,7 @@ var PageTaskExecutor = class {
1151
1373
  thought: plan2.thought,
1152
1374
  locate: plan2.locate,
1153
1375
  executor: async (param) => {
1154
- assert4(
1376
+ assert5(
1155
1377
  isAndroidPage(this.page),
1156
1378
  "Cannot use back button on non-Android devices"
1157
1379
  );
@@ -1167,7 +1389,7 @@ var PageTaskExecutor = class {
1167
1389
  thought: plan2.thought,
1168
1390
  locate: plan2.locate,
1169
1391
  executor: async (param) => {
1170
- assert4(
1392
+ assert5(
1171
1393
  isAndroidPage(this.page),
1172
1394
  "Cannot use recent apps button on non-Android devices"
1173
1395
  );
@@ -1318,7 +1540,7 @@ var PageTaskExecutor = class {
1318
1540
  }
1319
1541
  }
1320
1542
  if (finalActions.length === 0) {
1321
- assert4(
1543
+ assert5(
1322
1544
  !more_actions_needed_by_instruction || sleep3,
1323
1545
  error ? `Failed to plan: ${error}` : planParsingError || "No plan found"
1324
1546
  );
@@ -1556,7 +1778,7 @@ var PageTaskExecutor = class {
1556
1778
  );
1557
1779
  let outputResult = data;
1558
1780
  if (ifTypeRestricted) {
1559
- assert4(data?.result !== void 0, "No result in query data");
1781
+ assert5(data?.result !== void 0, "No result in query data");
1560
1782
  outputResult = data.result;
1561
1783
  }
1562
1784
  return {
@@ -1652,9 +1874,9 @@ var PageTaskExecutor = class {
1652
1874
  onTaskStart: this.onTaskStartCallback
1653
1875
  });
1654
1876
  const { timeoutMs, checkIntervalMs } = opt;
1655
- assert4(assertion, "No assertion for waitFor");
1656
- assert4(timeoutMs, "No timeoutMs for waitFor");
1657
- assert4(checkIntervalMs, "No checkIntervalMs for waitFor");
1877
+ assert5(assertion, "No assertion for waitFor");
1878
+ assert5(timeoutMs, "No timeoutMs for waitFor");
1879
+ assert5(checkIntervalMs, "No checkIntervalMs for waitFor");
1658
1880
  const overallStartTime = Date.now();
1659
1881
  let startTime = Date.now();
1660
1882
  let errorThought = "";
@@ -1708,9 +1930,9 @@ var PageTaskExecutor = class {
1708
1930
  };
1709
1931
 
1710
1932
  // src/common/plan-builder.ts
1711
- import { getDebug as getDebug2 } from "@midscene/shared/logger";
1712
- import { assert as assert5 } from "@midscene/shared/utils";
1713
- var debug2 = getDebug2("plan-builder");
1933
+ import { getDebug as getDebug3 } from "@midscene/shared/logger";
1934
+ import { assert as assert6 } from "@midscene/shared/utils";
1935
+ var debug3 = getDebug3("plan-builder");
1714
1936
  function buildPlans(type, locateParam, param) {
1715
1937
  let returnPlans = [];
1716
1938
  const locatePlan = locateParam ? {
@@ -1720,8 +1942,8 @@ function buildPlans(type, locateParam, param) {
1720
1942
  thought: ""
1721
1943
  } : null;
1722
1944
  if (type === "Tap" || type === "Hover" || type === "RightClick") {
1723
- assert5(locateParam, `missing locate info for action "${type}"`);
1724
- assert5(locatePlan, `missing locate info for action "${type}"`);
1945
+ assert6(locateParam, `missing locate info for action "${type}"`);
1946
+ assert6(locatePlan, `missing locate info for action "${type}"`);
1725
1947
  const tapPlan = {
1726
1948
  type,
1727
1949
  param: null,
@@ -1732,9 +1954,9 @@ function buildPlans(type, locateParam, param) {
1732
1954
  }
1733
1955
  if (type === "Input" || type === "KeyboardPress") {
1734
1956
  if (type === "Input") {
1735
- assert5(locateParam, `missing locate info for action "${type}"`);
1957
+ assert6(locateParam, `missing locate info for action "${type}"`);
1736
1958
  }
1737
- assert5(param, `missing param for action "${type}"`);
1959
+ assert6(param, `missing param for action "${type}"`);
1738
1960
  const inputPlan = {
1739
1961
  type,
1740
1962
  param,
@@ -1748,7 +1970,7 @@ function buildPlans(type, locateParam, param) {
1748
1970
  }
1749
1971
  }
1750
1972
  if (type === "Scroll") {
1751
- assert5(param, `missing param for action "${type}"`);
1973
+ assert6(param, `missing param for action "${type}"`);
1752
1974
  const scrollPlan = {
1753
1975
  type,
1754
1976
  param,
@@ -1762,7 +1984,7 @@ function buildPlans(type, locateParam, param) {
1762
1984
  }
1763
1985
  }
1764
1986
  if (type === "Sleep") {
1765
- assert5(param, `missing param for action "${type}"`);
1987
+ assert6(param, `missing param for action "${type}"`);
1766
1988
  const sleepPlan = {
1767
1989
  type,
1768
1990
  param,
@@ -1772,7 +1994,7 @@ function buildPlans(type, locateParam, param) {
1772
1994
  returnPlans = [sleepPlan];
1773
1995
  }
1774
1996
  if (type === "Locate") {
1775
- assert5(locateParam, `missing locate info for action "${type}"`);
1997
+ assert6(locateParam, `missing locate info for action "${type}"`);
1776
1998
  const locatePlan2 = {
1777
1999
  type,
1778
2000
  param: locateParam,
@@ -1782,187 +2004,12 @@ function buildPlans(type, locateParam, param) {
1782
2004
  returnPlans = [locatePlan2];
1783
2005
  }
1784
2006
  if (returnPlans) {
1785
- debug2("buildPlans", returnPlans);
2007
+ debug3("buildPlans", returnPlans);
1786
2008
  return returnPlans;
1787
2009
  }
1788
2010
  throw new Error(`Not supported type: ${type}`);
1789
2011
  }
1790
2012
 
1791
- // src/common/task-cache.ts
1792
- import assert6 from "assert";
1793
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
1794
- import { dirname as dirname2, join as join2 } from "path";
1795
- import { getMidsceneRunSubDir as getMidsceneRunSubDir2 } from "@midscene/shared/common";
1796
- import { getDebug as getDebug3 } from "@midscene/shared/logger";
1797
- import { ifInBrowser as ifInBrowser2 } from "@midscene/shared/utils";
1798
- import yaml3 from "js-yaml";
1799
- import semver from "semver";
1800
-
1801
- // package.json
1802
- var version = "0.19.1";
1803
-
1804
- // src/common/task-cache.ts
1805
- var debug3 = getDebug3("cache");
1806
- var lowestSupportedMidsceneVersion = "0.16.10";
1807
- var cacheFileExt = ".cache.yaml";
1808
- var TaskCache = class {
1809
- // Track matched records
1810
- constructor(cacheId, isCacheResultUsed, cacheFilePath) {
1811
- this.matchedCacheIndices = /* @__PURE__ */ new Set();
1812
- assert6(cacheId, "cacheId is required");
1813
- this.cacheId = replaceIllegalPathCharsAndSpace(cacheId);
1814
- this.cacheFilePath = ifInBrowser2 ? void 0 : cacheFilePath || join2(getMidsceneRunSubDir2("cache"), `${this.cacheId}${cacheFileExt}`);
1815
- this.isCacheResultUsed = isCacheResultUsed;
1816
- let cacheContent;
1817
- if (this.cacheFilePath) {
1818
- cacheContent = this.loadCacheFromFile();
1819
- }
1820
- if (!cacheContent) {
1821
- cacheContent = {
1822
- midsceneVersion: version,
1823
- cacheId: this.cacheId,
1824
- caches: []
1825
- };
1826
- }
1827
- this.cache = cacheContent;
1828
- this.cacheOriginalLength = this.cache.caches.length;
1829
- }
1830
- matchCache(prompt, type) {
1831
- for (let i = 0; i < this.cacheOriginalLength; i++) {
1832
- const item = this.cache.caches[i];
1833
- const key = `${type}:${prompt}:${i}`;
1834
- if (item.type === type && item.prompt === prompt && !this.matchedCacheIndices.has(key)) {
1835
- this.matchedCacheIndices.add(key);
1836
- debug3(
1837
- "cache found and marked as used, type: %s, prompt: %s, index: %d",
1838
- type,
1839
- prompt,
1840
- i
1841
- );
1842
- return {
1843
- cacheContent: item,
1844
- updateFn: (cb) => {
1845
- debug3(
1846
- "will call updateFn to update cache, type: %s, prompt: %s, index: %d",
1847
- type,
1848
- prompt,
1849
- i
1850
- );
1851
- cb(item);
1852
- debug3(
1853
- "cache updated, will flush to file, type: %s, prompt: %s, index: %d",
1854
- type,
1855
- prompt,
1856
- i
1857
- );
1858
- this.flushCacheToFile();
1859
- }
1860
- };
1861
- }
1862
- }
1863
- debug3("no unused cache found, type: %s, prompt: %s", type, prompt);
1864
- return void 0;
1865
- }
1866
- matchPlanCache(prompt) {
1867
- return this.matchCache(prompt, "plan");
1868
- }
1869
- matchLocateCache(prompt) {
1870
- return this.matchCache(prompt, "locate");
1871
- }
1872
- appendCache(cache) {
1873
- debug3("will append cache", cache);
1874
- this.cache.caches.push(cache);
1875
- this.flushCacheToFile();
1876
- }
1877
- loadCacheFromFile() {
1878
- const cacheFile = this.cacheFilePath;
1879
- assert6(cacheFile, "cache file path is required");
1880
- if (!existsSync2(cacheFile)) {
1881
- debug3("no cache file found, path: %s", cacheFile);
1882
- return void 0;
1883
- }
1884
- const jsonTypeCacheFile = cacheFile.replace(cacheFileExt, ".json");
1885
- if (existsSync2(jsonTypeCacheFile) && this.isCacheResultUsed) {
1886
- console.warn(
1887
- `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}.`
1888
- );
1889
- return void 0;
1890
- }
1891
- try {
1892
- const data = readFileSync(cacheFile, "utf8");
1893
- const jsonData = yaml3.load(data);
1894
- if (!version) {
1895
- debug3("no midscene version info, will not read cache from file");
1896
- return void 0;
1897
- }
1898
- if (semver.lt(jsonData.midsceneVersion, lowestSupportedMidsceneVersion) && !jsonData.midsceneVersion.includes("beta")) {
1899
- console.warn(
1900
- `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.
1901
- Please delete the existing cache and rebuild it. Sorry for the inconvenience.
1902
- cache file: ${cacheFile}`
1903
- );
1904
- return void 0;
1905
- }
1906
- debug3(
1907
- "cache loaded from file, path: %s, cache version: %s, record length: %s",
1908
- cacheFile,
1909
- jsonData.midsceneVersion,
1910
- jsonData.caches.length
1911
- );
1912
- jsonData.midsceneVersion = version;
1913
- return jsonData;
1914
- } catch (err) {
1915
- debug3(
1916
- "cache file exists but load failed, path: %s, error: %s",
1917
- cacheFile,
1918
- err
1919
- );
1920
- return void 0;
1921
- }
1922
- }
1923
- flushCacheToFile() {
1924
- if (!version) {
1925
- debug3("no midscene version info, will not write cache to file");
1926
- return;
1927
- }
1928
- if (!this.cacheFilePath) {
1929
- debug3("no cache file path, will not write cache to file");
1930
- return;
1931
- }
1932
- try {
1933
- const dir = dirname2(this.cacheFilePath);
1934
- if (!existsSync2(dir)) {
1935
- mkdirSync2(dir, { recursive: true });
1936
- debug3("created cache directory: %s", dir);
1937
- }
1938
- const yamlData = yaml3.dump(this.cache);
1939
- writeFileSync2(this.cacheFilePath, yamlData);
1940
- debug3("cache flushed to file: %s", this.cacheFilePath);
1941
- } catch (err) {
1942
- debug3(
1943
- "write cache to file failed, path: %s, error: %s",
1944
- this.cacheFilePath,
1945
- err
1946
- );
1947
- }
1948
- }
1949
- updateOrAppendCacheRecord(newRecord, cachedRecord) {
1950
- if (cachedRecord) {
1951
- if (newRecord.type === "plan") {
1952
- cachedRecord.updateFn((cache) => {
1953
- cache.yamlWorkflow = newRecord.yamlWorkflow;
1954
- });
1955
- } else {
1956
- cachedRecord.updateFn((cache) => {
1957
- cache.xpaths = newRecord.xpaths;
1958
- });
1959
- }
1960
- } else {
1961
- this.appendCache(newRecord);
1962
- }
1963
- }
1964
- };
1965
-
1966
2013
  // src/common/agent.ts
1967
2014
  var debug4 = getDebug4("web-integration");
1968
2015
  var distanceOfTwoPoints = (p1, p2) => {
@@ -2091,10 +2138,12 @@ ${errorTask?.errorStack}`);
2091
2138
  const prompt = opt.prompt ?? locatePrompt;
2092
2139
  const deepThink = opt.deepThink ?? false;
2093
2140
  const cacheable = opt.cacheable ?? true;
2141
+ const xpath = opt.xpath;
2094
2142
  return {
2095
2143
  prompt,
2096
2144
  deepThink,
2097
- cacheable
2145
+ cacheable,
2146
+ xpath
2098
2147
  };
2099
2148
  }
2100
2149
  return {
@@ -2252,6 +2301,9 @@ ${errorTask?.errorStack}`);
2252
2301
  this.afterTaskRunning(executor);
2253
2302
  return output;
2254
2303
  }
2304
+ async aiAsk(prompt, opt = defaultInsightExtractOption) {
2305
+ return this.aiString(prompt, opt);
2306
+ }
2255
2307
  async describeElementAtPoint(center, opt) {
2256
2308
  const { verifyPrompt = true, retryLimit = 3 } = opt || {};
2257
2309
  let success = false;
@@ -2977,6 +3029,14 @@ var PlaywrightAiFixture = (options) => {
2977
3029
  use,
2978
3030
  aiActionType: "aiBoolean"
2979
3031
  });
3032
+ },
3033
+ aiAsk: async ({ page }, use, testInfo) => {
3034
+ await generateAiFunction({
3035
+ page,
3036
+ testInfo,
3037
+ use,
3038
+ aiActionType: "aiAsk"
3039
+ });
2980
3040
  }
2981
3041
  };
2982
3042
  };