misoai-web 1.5.9 → 1.6.1

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 (60) hide show
  1. package/dist/es/agent.js +158 -54
  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-browser.js.map +1 -1
  5. package/dist/es/bridge-mode.js +160 -56
  6. package/dist/es/bridge-mode.js.map +1 -1
  7. package/dist/es/chrome-extension.js +159 -55
  8. package/dist/es/chrome-extension.js.map +1 -1
  9. package/dist/es/index.js +159 -55
  10. package/dist/es/index.js.map +1 -1
  11. package/dist/es/midscene-playground.js +161 -57
  12. package/dist/es/midscene-playground.js.map +1 -1
  13. package/dist/es/midscene-server.js +4 -4
  14. package/dist/es/midscene-server.js.map +1 -1
  15. package/dist/es/playground.js +158 -54
  16. package/dist/es/playground.js.map +1 -1
  17. package/dist/es/playwright-report.js +1 -1
  18. package/dist/es/playwright-report.js.map +1 -1
  19. package/dist/es/playwright.js +159 -55
  20. package/dist/es/playwright.js.map +1 -1
  21. package/dist/es/puppeteer-agent-launcher.js +158 -54
  22. package/dist/es/puppeteer-agent-launcher.js.map +1 -1
  23. package/dist/es/puppeteer.js +158 -54
  24. package/dist/es/puppeteer.js.map +1 -1
  25. package/dist/es/utils.js +1 -1
  26. package/dist/es/utils.js.map +1 -1
  27. package/dist/es/yaml.js +1 -1
  28. package/dist/es/yaml.js.map +1 -1
  29. package/dist/lib/agent.js +158 -54
  30. package/dist/lib/agent.js.map +1 -1
  31. package/dist/lib/bridge-mode-browser.js +3 -3
  32. package/dist/lib/bridge-mode-browser.js.map +1 -1
  33. package/dist/lib/bridge-mode.js +160 -56
  34. package/dist/lib/bridge-mode.js.map +1 -1
  35. package/dist/lib/chrome-extension.js +159 -55
  36. package/dist/lib/chrome-extension.js.map +1 -1
  37. package/dist/lib/index.js +159 -55
  38. package/dist/lib/index.js.map +1 -1
  39. package/dist/lib/midscene-playground.js +161 -57
  40. package/dist/lib/midscene-playground.js.map +1 -1
  41. package/dist/lib/midscene-server.js +4 -4
  42. package/dist/lib/midscene-server.js.map +1 -1
  43. package/dist/lib/playground.js +158 -54
  44. package/dist/lib/playground.js.map +1 -1
  45. package/dist/lib/playwright-report.js +1 -1
  46. package/dist/lib/playwright-report.js.map +1 -1
  47. package/dist/lib/playwright.js +159 -55
  48. package/dist/lib/playwright.js.map +1 -1
  49. package/dist/lib/puppeteer-agent-launcher.js +158 -54
  50. package/dist/lib/puppeteer-agent-launcher.js.map +1 -1
  51. package/dist/lib/puppeteer.js +158 -54
  52. package/dist/lib/puppeteer.js.map +1 -1
  53. package/dist/lib/utils.js +1 -1
  54. package/dist/lib/utils.js.map +1 -1
  55. package/dist/lib/yaml.js +1 -1
  56. package/dist/lib/yaml.js.map +1 -1
  57. package/dist/types/agent.d.ts +8 -1
  58. package/dist/types/index.d.ts +1 -1
  59. package/dist/types/playwright.d.ts +1 -1
  60. package/package.json +18 -54
@@ -311,8 +311,8 @@ var ScriptPlayer = class {
311
311
  import yaml from "js-yaml";
312
312
 
313
313
  // src/yaml/utils.ts
314
- import { assert as assert2 } from "misoai-shared/utils";
315
314
  import yaml2 from "js-yaml";
315
+ import { assert as assert2 } from "misoai-shared/utils";
316
316
  function interpolateEnvVars(content) {
317
317
  return content.replace(/\$\{([^}]+)\}/g, (_, envVar) => {
318
318
  const value = process.env[envVar.trim()];
@@ -473,6 +473,7 @@ function paramStr(task) {
473
473
  }
474
474
 
475
475
  // src/common/utils.ts
476
+ import dayjs from "dayjs";
476
477
  import { elementByPositionWithElementInfo } from "misoai-core/ai-model";
477
478
  import { uploadTestInfoToServer } from "misoai-core/utils";
478
479
  import { MIDSCENE_REPORT_TAG_NAME, getAIConfig } from "misoai-shared/env";
@@ -484,7 +485,6 @@ import {
484
485
  } from "misoai-shared/extractor";
485
486
  import { resizeImgBase64 } from "misoai-shared/img";
486
487
  import { assert as assert3, logMsg, uuid } from "misoai-shared/utils";
487
- import dayjs from "dayjs";
488
488
 
489
489
  // src/web-element.ts
490
490
  var WebElementInfo = class {
@@ -653,8 +653,12 @@ var WorkflowMemory = class {
653
653
  const workflow = this.workflows.get(workflowId) || this.createEmptyWorkflowData(workflowId);
654
654
  workflow.memory = [...memory];
655
655
  workflow.metadata.totalSteps = workflow.steps.length;
656
- workflow.metadata.completedSteps = workflow.steps.filter((s) => s.status === "completed").length;
657
- workflow.metadata.failedSteps = workflow.steps.filter((s) => s.status === "failed").length;
656
+ workflow.metadata.completedSteps = workflow.steps.filter(
657
+ (s) => s.status === "completed"
658
+ ).length;
659
+ workflow.metadata.failedSteps = workflow.steps.filter(
660
+ (s) => s.status === "failed"
661
+ ).length;
658
662
  this.workflows.set(workflowId, workflow);
659
663
  this.enforceRetentionPolicy();
660
664
  }
@@ -665,7 +669,9 @@ var WorkflowMemory = class {
665
669
  const workflow = this.workflows.get(workflowId) || this.createEmptyWorkflowData(workflowId);
666
670
  workflow.context = { ...workflow.context, ...context };
667
671
  if (context.currentStep) {
668
- const existingStep = workflow.steps.find((s) => s.stepName === context.currentStep);
672
+ const existingStep = workflow.steps.find(
673
+ (s) => s.stepName === context.currentStep
674
+ );
669
675
  if (!existingStep) {
670
676
  workflow.steps.push({
671
677
  stepId: `step_${workflow.steps.length + 1}`,
@@ -710,7 +716,9 @@ var WorkflowMemory = class {
710
716
  enforceRetentionPolicy() {
711
717
  const maxWorkflows = 10;
712
718
  if (this.workflows.size > maxWorkflows) {
713
- const sortedWorkflows = Array.from(this.workflows.entries()).sort(([, a], [, b]) => (b.metadata.endTime || b.metadata.startTime) - (a.metadata.endTime || a.metadata.startTime));
719
+ const sortedWorkflows = Array.from(this.workflows.entries()).sort(
720
+ ([, a], [, b]) => (b.metadata.endTime || b.metadata.startTime) - (a.metadata.endTime || a.metadata.startTime)
721
+ );
714
722
  const toDelete = sortedWorkflows.slice(maxWorkflows);
715
723
  toDelete.forEach(([workflowId]) => this.workflows.delete(workflowId));
716
724
  }
@@ -1449,7 +1457,9 @@ var PageTaskExecutor = class {
1449
1457
  */
1450
1458
  getPersistentExecutor() {
1451
1459
  if (!this.persistentExecutor || this.persistentExecutor.status === "error") {
1452
- const previousMemory = this.workflowMemory.getWorkflowMemory(this.sessionContext.workflowId);
1460
+ const previousMemory = this.workflowMemory.getWorkflowMemory(
1461
+ this.sessionContext.workflowId
1462
+ );
1453
1463
  this.persistentExecutor = new Executor("Persistent Task Executor", {
1454
1464
  onTaskStart: this.onTaskStartCallback,
1455
1465
  initialMemory: previousMemory
@@ -1478,7 +1488,9 @@ var PageTaskExecutor = class {
1478
1488
  if (this.persistentExecutor) {
1479
1489
  this.persistentExecutor.clearMemory();
1480
1490
  }
1481
- this.workflowMemory.clearWorkflow(this.sessionContext.workflowId || "default");
1491
+ this.workflowMemory.clearWorkflow(
1492
+ this.sessionContext.workflowId || "default"
1493
+ );
1482
1494
  }
1483
1495
  /**
1484
1496
  * Mevcut hafızayı döndürür
@@ -1490,7 +1502,9 @@ var PageTaskExecutor = class {
1490
1502
  * İş akışı hafızasını döndürür
1491
1503
  */
1492
1504
  getWorkflowMemory() {
1493
- return this.workflowMemory.getWorkflowData(this.sessionContext.workflowId || "default");
1505
+ return this.workflowMemory.getWorkflowData(
1506
+ this.sessionContext.workflowId || "default"
1507
+ );
1494
1508
  }
1495
1509
  /**
1496
1510
  * Hafıza istatistiklerini döndürür
@@ -1498,7 +1512,13 @@ var PageTaskExecutor = class {
1498
1512
  getMemoryStats() {
1499
1513
  return this.persistentExecutor?.getMemoryStats() || {
1500
1514
  totalItems: 0,
1501
- analytics: { totalTasks: 0, memoryHits: 0, memoryMisses: 0, averageMemorySize: 0, memoryEffectiveness: 0 },
1515
+ analytics: {
1516
+ totalTasks: 0,
1517
+ memoryHits: 0,
1518
+ memoryMisses: 0,
1519
+ averageMemorySize: 0,
1520
+ memoryEffectiveness: 0
1521
+ },
1502
1522
  config: this.memoryConfig
1503
1523
  };
1504
1524
  }
@@ -1534,11 +1554,14 @@ var PageTaskExecutor = class {
1534
1554
  let taskExecutor;
1535
1555
  if (useMemory) {
1536
1556
  taskExecutor = this.getPersistentExecutor();
1537
- this.workflowMemory.updateWorkflowContext({
1538
- currentStep: title,
1539
- pageInfo: this.sessionContext.pageInfo,
1540
- timestamp: Date.now()
1541
- }, this.sessionContext.workflowId || "default");
1557
+ this.workflowMemory.updateWorkflowContext(
1558
+ {
1559
+ currentStep: title,
1560
+ pageInfo: this.sessionContext.pageInfo,
1561
+ timestamp: Date.now()
1562
+ },
1563
+ this.sessionContext.workflowId || "default"
1564
+ );
1542
1565
  } else {
1543
1566
  taskExecutor = new Executor(title, {
1544
1567
  onTaskStart: this.onTaskStartCallback
@@ -1836,9 +1859,15 @@ var PageTaskExecutor = class {
1836
1859
  }
1837
1860
  async waitFor(assertion, opt) {
1838
1861
  const description = `waitFor: ${assertion}`;
1839
- const taskExecutor = new Executor(taskTitleStr("WaitFor", description), {
1840
- onTaskStart: this.onTaskStartCallback
1841
- });
1862
+ const useMemory = true;
1863
+ let taskExecutor;
1864
+ if (useMemory) {
1865
+ taskExecutor = this.getPersistentExecutor();
1866
+ } else {
1867
+ taskExecutor = new Executor(taskTitleStr("WaitFor", description), {
1868
+ onTaskStart: this.onTaskStartCallback
1869
+ });
1870
+ }
1842
1871
  const { timeoutMs, checkIntervalMs } = opt;
1843
1872
  assert4(assertion, "No assertion for waitFor");
1844
1873
  assert4(timeoutMs, "No timeoutMs for waitFor");
@@ -1893,6 +1922,25 @@ var PageTaskExecutor = class {
1893
1922
  `waitFor timeout: ${errorThought}`
1894
1923
  );
1895
1924
  }
1925
+ /**
1926
+ * Hafızaya yeni bir öğe ekler
1927
+ */
1928
+ addToMemory(memoryItem) {
1929
+ if (!this.persistentExecutor || this.persistentExecutor.status === "error") {
1930
+ const previousMemory = this.workflowMemory.getWorkflowMemory(
1931
+ this.sessionContext.workflowId
1932
+ );
1933
+ this.persistentExecutor = new Executor("Persistent Task Executor", {
1934
+ onTaskStart: this.onTaskStartCallback,
1935
+ initialMemory: previousMemory
1936
+ });
1937
+ }
1938
+ this.persistentExecutor.memoryStore?.add(memoryItem);
1939
+ this.persistentExecutor.memoryAnalytics?.recordMemoryOperation(
1940
+ "add",
1941
+ memoryItem
1942
+ );
1943
+ }
1896
1944
  };
1897
1945
 
1898
1946
  // src/common/plan-builder.ts
@@ -1980,14 +2028,14 @@ function buildPlans(type, locateParam, param) {
1980
2028
  import assert6 from "assert";
1981
2029
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
1982
2030
  import { dirname as dirname2, join as join2 } from "path";
2031
+ import yaml3 from "js-yaml";
1983
2032
  import { getMidsceneRunSubDir as getMidsceneRunSubDir2 } from "misoai-shared/common";
1984
2033
  import { getDebug as getDebug3 } from "misoai-shared/logger";
1985
2034
  import { ifInBrowser as ifInBrowser2 } from "misoai-shared/utils";
1986
- import yaml3 from "js-yaml";
1987
2035
  import semver from "semver";
1988
2036
 
1989
2037
  // package.json
1990
- var version = "1.5.8";
2038
+ var version = "1.6.1";
1991
2039
 
1992
2040
  // src/common/task-cache.ts
1993
2041
  var debug3 = getDebug3("cache");
@@ -2198,7 +2246,10 @@ var PageAgent = class {
2198
2246
  }
2199
2247
  this.taskExecutor = new PageTaskExecutor(this.page, this.insight, {
2200
2248
  taskCache: this.taskCache,
2201
- onTaskStart: this.callbackOnTaskStartTip.bind(this)
2249
+ onTaskStart: this.callbackOnTaskStartTip.bind(this),
2250
+ memoryConfig: opts?.memoryConfig,
2251
+ sessionId: opts?.sessionId,
2252
+ workflowId: opts?.workflowId
2202
2253
  });
2203
2254
  this.dump = this.resetDump();
2204
2255
  this.reportFileName = reportFileName(
@@ -2279,22 +2330,28 @@ var PageAgent = class {
2279
2330
  const allThoughts = executor.tasks.filter((task) => task.thought).map((task) => task.thought);
2280
2331
  const allLocates = executor.tasks.filter((task) => task.locate).map((task) => task.locate);
2281
2332
  const allPlans = executor.tasks.filter((task) => task.param?.plans).map((task) => task.param?.plans);
2282
- const planningTasks = executor.tasks.filter((task) => task.type === "Planning");
2283
- const insightTasks = executor.tasks.filter((task) => task.type === "Insight");
2333
+ const planningTasks = executor.tasks.filter(
2334
+ (task) => task.type === "Planning"
2335
+ );
2336
+ const insightTasks = executor.tasks.filter(
2337
+ (task) => task.type === "Insight"
2338
+ );
2284
2339
  const actionTasks = executor.tasks.filter((task) => task.type === "Action");
2285
2340
  const planning = planningTasks.length > 0 ? {
2286
2341
  type: "Planning",
2287
- description: `Planning for task execution`,
2342
+ description: "Planning for task execution",
2288
2343
  steps: planningTasks.map((task) => task.thought || "Planning step")
2289
2344
  } : void 0;
2290
2345
  const insight = insightTasks.length > 0 ? {
2291
2346
  type: "Insight",
2292
- description: `Insight for task execution`,
2293
- elements: insightTasks.map((task) => task.thought || "Insight element")
2347
+ description: "Insight for task execution",
2348
+ elements: insightTasks.map(
2349
+ (task) => task.thought || "Insight element"
2350
+ )
2294
2351
  } : void 0;
2295
2352
  const action = actionTasks.length > 0 ? {
2296
2353
  type: "Action",
2297
- description: `Action for task execution`,
2354
+ description: "Action for task execution",
2298
2355
  result: lastTask?.output
2299
2356
  } : void 0;
2300
2357
  const actionDetails = executor.tasks.map((task) => ({
@@ -2628,7 +2685,10 @@ ${memoryContext}` : void 0;
2628
2685
  }
2629
2686
  const memoryContext = this.getMemoryAsContext();
2630
2687
  const assertionWithContext = currentUrl ? `For the page at URL "${currentUrl}", ${assertion}` : assertion;
2631
- const { output, executor } = await this.taskExecutor.assert(assertionWithContext, memoryContext);
2688
+ const { output, executor } = await this.taskExecutor.assert(
2689
+ assertionWithContext,
2690
+ memoryContext
2691
+ );
2632
2692
  const metadata = this.afterTaskRunning(executor, true);
2633
2693
  if (output && opt?.keepRawResponse) {
2634
2694
  return {
@@ -2649,6 +2709,7 @@ ${reasonMsg}`);
2649
2709
  }
2650
2710
  async aiCaptcha(options) {
2651
2711
  const { deepThink = false, autoDetectComplexity = true } = options || {};
2712
+ const memoryContext = this.getMemoryAsContext();
2652
2713
  let shouldUseDeepThink = deepThink;
2653
2714
  if (autoDetectComplexity && !deepThink) {
2654
2715
  const context = await this.getUIContext();
@@ -2666,7 +2727,10 @@ A complex CAPTCHA typically has one or more of these characteristics:
2666
2727
  Return only "complex" or "simple" based on your analysis.
2667
2728
  `;
2668
2729
  const complexityMsgs = [
2669
- { role: "system", content: "You are an AI assistant that analyzes screenshots to determine CAPTCHA complexity." },
2730
+ {
2731
+ role: "system",
2732
+ content: "You are an AI assistant that analyzes screenshots to determine CAPTCHA complexity."
2733
+ },
2670
2734
  {
2671
2735
  role: "user",
2672
2736
  content: [
@@ -2690,7 +2754,12 @@ Return only "complex" or "simple" based on your analysis.
2690
2754
  );
2691
2755
  const responseText = typeof complexityResult.content === "string" ? complexityResult.content.toLowerCase() : JSON.stringify(complexityResult.content).toLowerCase();
2692
2756
  shouldUseDeepThink = responseText.includes("complex");
2693
- debug4("CAPTCHA complexity analysis:", responseText, "Using deep think:", shouldUseDeepThink);
2757
+ debug4(
2758
+ "CAPTCHA complexity analysis:",
2759
+ responseText,
2760
+ "Using deep think:",
2761
+ shouldUseDeepThink
2762
+ );
2694
2763
  } catch (error) {
2695
2764
  debug4("Failed to analyze CAPTCHA complexity:", error);
2696
2765
  }
@@ -2707,7 +2776,9 @@ Return only "complex" or "simple" based on your analysis.
2707
2776
  await this.aiTap(action.target, { deepThink: shouldUseDeepThink });
2708
2777
  } else if (action.type === "input" && action.value) {
2709
2778
  if (action.target) {
2710
- await this.aiInput(action.value, action.target, { deepThink: shouldUseDeepThink });
2779
+ await this.aiInput(action.value, action.target, {
2780
+ deepThink: shouldUseDeepThink
2781
+ });
2711
2782
  }
2712
2783
  } else if (action.type === "verify" && action.target) {
2713
2784
  await this.aiTap(action.target, { deepThink: shouldUseDeepThink });
@@ -2719,7 +2790,9 @@ Return only "complex" or "simple" based on your analysis.
2719
2790
  if (action.coordinates) {
2720
2791
  const x = action.coordinates[0];
2721
2792
  const y = action.coordinates[1];
2722
- await this.aiTap(`element at coordinates (${x}, ${y})`, { deepThink: shouldUseDeepThink });
2793
+ await this.aiTap(`element at coordinates (${x}, ${y})`, {
2794
+ deepThink: shouldUseDeepThink
2795
+ });
2723
2796
  } else if (action.target) {
2724
2797
  await this.aiTap(action.target, { deepThink: shouldUseDeepThink });
2725
2798
  }
@@ -2729,6 +2802,26 @@ Return only "complex" or "simple" based on your analysis.
2729
2802
  }
2730
2803
  }
2731
2804
  await new Promise((resolve2) => setTimeout(resolve2, 3e3));
2805
+ const captchaMemoryItem = {
2806
+ id: `captcha_${Date.now()}`,
2807
+ timestamp: Date.now(),
2808
+ taskType: "Action",
2809
+ summary: `Solved ${captchaResult.captchaType} CAPTCHA: ${captchaResult.thought}`,
2810
+ context: {
2811
+ url: await this.page.url?.() || "",
2812
+ captchaType: captchaResult.captchaType,
2813
+ actions: captchaResult.actions,
2814
+ deepThink: actualDeepThink
2815
+ },
2816
+ metadata: {
2817
+ executionTime: Date.now() - Date.now(),
2818
+ // Will be updated
2819
+ success: true,
2820
+ confidence: 0.9
2821
+ },
2822
+ tags: ["captcha", "action", captchaResult.captchaType]
2823
+ };
2824
+ this.taskExecutor.addToMemory(captchaMemoryItem);
2732
2825
  const metadata = {
2733
2826
  status: "finished",
2734
2827
  usage,
@@ -2745,10 +2838,15 @@ Return only "complex" or "simple" based on your analysis.
2745
2838
  }
2746
2839
  async aiWaitFor(assertion, opt) {
2747
2840
  const startTime = Date.now();
2748
- const { executor } = await this.taskExecutor.waitFor(assertion, {
2841
+ const memoryContext = this.getMemoryAsContext();
2842
+ const assertionWithContext = memoryContext ? `${assertion}
2843
+
2844
+ Previous workflow steps:
2845
+ ${memoryContext}` : assertion;
2846
+ const { executor } = await this.taskExecutor.waitFor(assertionWithContext, {
2749
2847
  timeoutMs: opt?.timeoutMs || 15 * 1e3,
2750
2848
  checkIntervalMs: opt?.checkIntervalMs || 3 * 1e3,
2751
- assertion
2849
+ assertion: assertionWithContext
2752
2850
  });
2753
2851
  const metadata = {
2754
2852
  status: executor.isInErrorState() ? "failed" : "finished",
@@ -2846,25 +2944,27 @@ ${errors}`);
2846
2944
  const executionDump = {
2847
2945
  name: screenshotTitle,
2848
2946
  description: content,
2849
- tasks: [{
2850
- type: "Screenshot",
2851
- subType: "log",
2852
- status: "finished",
2853
- executor: null,
2854
- param: {
2855
- title: screenshotTitle,
2856
- content
2857
- },
2858
- output: {
2859
- screenshot
2860
- },
2861
- thought: `Logged screenshot: ${screenshotTitle}`,
2862
- timing: {
2863
- start: Date.now(),
2864
- end: Date.now(),
2865
- cost: 0
2947
+ tasks: [
2948
+ {
2949
+ type: "Screenshot",
2950
+ subType: "log",
2951
+ status: "finished",
2952
+ executor: null,
2953
+ param: {
2954
+ title: screenshotTitle,
2955
+ content
2956
+ },
2957
+ output: {
2958
+ screenshot
2959
+ },
2960
+ thought: `Logged screenshot: ${screenshotTitle}`,
2961
+ timing: {
2962
+ start: Date.now(),
2963
+ end: Date.now(),
2964
+ cost: 0
2965
+ }
2866
2966
  }
2867
- }],
2967
+ ],
2868
2968
  sdkVersion: "1.0.0",
2869
2969
  logTime: Date.now(),
2870
2970
  model_name: "screenshot"
@@ -2916,7 +3016,9 @@ ${errors}`);
2916
3016
  totalTasks: stats.analytics.totalTasks,
2917
3017
  memoryHits: stats.analytics.memoryHits,
2918
3018
  memoryMisses: stats.analytics.memoryMisses,
2919
- memoryEffectiveness: Math.round(stats.analytics.memoryEffectiveness * 100),
3019
+ memoryEffectiveness: Math.round(
3020
+ stats.analytics.memoryEffectiveness * 100
3021
+ ),
2920
3022
  averageMemorySize: Math.round(stats.analytics.averageMemorySize * 100) / 100
2921
3023
  },
2922
3024
  config: stats.config,
@@ -2980,7 +3082,9 @@ ${errors}`);
2980
3082
  calculateSuccessRate(memory) {
2981
3083
  if (memory.length === 0)
2982
3084
  return 0;
2983
- const successCount = memory.filter((item) => item.metadata?.success !== false).length;
3085
+ const successCount = memory.filter(
3086
+ (item) => item.metadata?.success !== false
3087
+ ).length;
2984
3088
  return Math.round(successCount / memory.length * 100);
2985
3089
  }
2986
3090
  calculateAverageExecutionTime(memory) {