misoai-web 1.6.0 → 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 (59) hide show
  1. package/dist/es/agent.js +105 -50
  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 +107 -52
  6. package/dist/es/bridge-mode.js.map +1 -1
  7. package/dist/es/chrome-extension.js +106 -51
  8. package/dist/es/chrome-extension.js.map +1 -1
  9. package/dist/es/index.js +106 -51
  10. package/dist/es/index.js.map +1 -1
  11. package/dist/es/midscene-playground.js +108 -53
  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 +105 -50
  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 +106 -51
  20. package/dist/es/playwright.js.map +1 -1
  21. package/dist/es/puppeteer-agent-launcher.js +105 -50
  22. package/dist/es/puppeteer-agent-launcher.js.map +1 -1
  23. package/dist/es/puppeteer.js +105 -50
  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 +105 -50
  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 +107 -52
  34. package/dist/lib/bridge-mode.js.map +1 -1
  35. package/dist/lib/chrome-extension.js +106 -51
  36. package/dist/lib/chrome-extension.js.map +1 -1
  37. package/dist/lib/index.js +106 -51
  38. package/dist/lib/index.js.map +1 -1
  39. package/dist/lib/midscene-playground.js +108 -53
  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 +105 -50
  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 +106 -51
  48. package/dist/lib/playwright.js.map +1 -1
  49. package/dist/lib/puppeteer-agent-launcher.js +105 -50
  50. package/dist/lib/puppeteer-agent-launcher.js.map +1 -1
  51. package/dist/lib/puppeteer.js +105 -50
  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/index.d.ts +1 -1
  58. package/dist/types/playwright.d.ts +1 -1
  59. package/package.json +18 -54
@@ -4,6 +4,7 @@ import { existsSync, readFileSync, writeFileSync } from "fs";
4
4
  import { join } from "path";
5
5
 
6
6
  // src/common/utils.ts
7
+ import dayjs from "dayjs";
7
8
  import { elementByPositionWithElementInfo } from "misoai-core/ai-model";
8
9
  import { uploadTestInfoToServer } from "misoai-core/utils";
9
10
  import { MIDSCENE_REPORT_TAG_NAME, getAIConfig } from "misoai-shared/env";
@@ -15,17 +16,16 @@ import {
15
16
  } from "misoai-shared/extractor";
16
17
  import { resizeImgBase64 } from "misoai-shared/img";
17
18
  import { assert, logMsg, uuid } from "misoai-shared/utils";
18
- import dayjs from "dayjs";
19
19
  var ERROR_CODE_NOT_IMPLEMENTED_AS_DESIGNED = "NOT_IMPLEMENTED_AS_DESIGNED";
20
20
 
21
21
  // src/playground/server.ts
22
+ import cors from "cors";
23
+ import dotenv from "dotenv";
24
+ import express from "express";
22
25
  import { getTmpDir } from "misoai-core/utils";
23
26
  import { PLAYGROUND_SERVER_PORT } from "misoai-shared/constants";
24
27
  import { overrideAIConfig } from "misoai-shared/env";
25
28
  import { ifInBrowser } from "misoai-shared/utils";
26
- import cors from "cors";
27
- import dotenv from "dotenv";
28
- import express from "express";
29
29
  var defaultPort = PLAYGROUND_SERVER_PORT;
30
30
  var errorHandler = (err, req, res, next) => {
31
31
  console.error(err);
@@ -1 +1 @@
1
- {"version":3,"mappings":"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","names":["uuid","port"],"ignoreList":[],"sources":["../../src/playground/server.ts","../../src/common/utils.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport type { Server } from 'node:http';\nimport { join } from 'node:path';\nimport { ERROR_CODE_NOT_IMPLEMENTED_AS_DESIGNED } from '@/common/utils';\nimport { getTmpDir } from 'misoai-core/utils';\nimport { PLAYGROUND_SERVER_PORT } from 'misoai-shared/constants';\nimport { overrideAIConfig } from 'misoai-shared/env';\nimport { ifInBrowser } from 'misoai-shared/utils';\nimport cors from 'cors';\nimport dotenv from 'dotenv';\nimport express from 'express';\nimport type { PageAgent } from '../common/agent';\nimport type { AbstractPage } from '../page';\n\nconst defaultPort = PLAYGROUND_SERVER_PORT;\n// const staticPath = join(__dirname, '../../static');\n\nconst errorHandler = (err: any, req: any, res: any, next: any) => {\n console.error(err);\n res.status(500).json({\n error: err.message,\n });\n};\n\nconst setup = async () => {\n if (!ifInBrowser) {\n const { parsed } = dotenv.config();\n\n if (parsed) {\n overrideAIConfig(parsed);\n }\n }\n};\n\nexport default class PlaygroundServer {\n app: express.Application;\n tmpDir: string;\n server?: Server;\n port?: number | null;\n pageClass: new (\n ...args: any[]\n ) => AbstractPage;\n agentClass: new (\n ...args: any[]\n ) => PageAgent;\n staticPath?: string;\n taskProgressTips: Record<string, string>;\n\n constructor(\n pageClass: new (...args: any[]) => AbstractPage,\n agentClass: new (...args: any[]) => PageAgent,\n staticPath?: string,\n ) {\n this.app = express();\n this.tmpDir = getTmpDir()!;\n this.pageClass = pageClass;\n this.agentClass = agentClass;\n this.staticPath = staticPath;\n this.taskProgressTips = {};\n setup();\n }\n\n filePathForUuid(uuid: string) {\n return join(this.tmpDir, `${uuid}.json`);\n }\n\n saveContextFile(uuid: string, context: string) {\n const tmpFile = this.filePathForUuid(uuid);\n console.log(`save context file: ${tmpFile}`);\n writeFileSync(tmpFile, context);\n return tmpFile;\n }\n\n async launch(port?: number) {\n this.port = port || defaultPort;\n this.app.use(errorHandler);\n\n this.app.use(\n cors({\n origin: '*',\n credentials: true,\n }),\n );\n\n this.app.get('/status', cors(), async (req, res) => {\n // const modelName = g\n res.send({\n status: 'ok',\n });\n });\n\n // this.app.get('/playground/:uuid', async (req, res) => {\n // res.sendFile(join(staticPath, 'index.html'));\n // });\n\n this.app.get('/context/:uuid', async (req, res) => {\n const { uuid } = req.params;\n const contextFile = this.filePathForUuid(uuid);\n\n if (!existsSync(contextFile)) {\n return res.status(404).json({\n error: 'Context not found',\n });\n }\n\n const context = readFileSync(contextFile, 'utf8');\n res.json({\n context,\n });\n });\n\n this.app.get('/task-progress/:requestId', cors(), async (req, res) => {\n const { requestId } = req.params;\n res.json({\n tip: this.taskProgressTips[requestId] || '',\n });\n });\n\n // -------------------------\n // actions from report file\n this.app.post(\n '/playground-with-context',\n express.json({ limit: '50mb' }),\n async (req, res) => {\n const context = req.body.context;\n\n if (!context) {\n return res.status(400).json({\n error: 'context is required',\n });\n }\n\n const uuid = randomUUID();\n this.saveContextFile(uuid, context);\n return res.json({\n location: `/playground/${uuid}`,\n uuid,\n });\n },\n );\n\n this.app.post(\n '/execute',\n express.json({ limit: '30mb' }),\n async (req, res) => {\n const { context, type, prompt, requestId, deepThink } = req.body;\n\n if (!context) {\n return res.status(400).json({\n error: 'context is required',\n });\n }\n\n if (!type) {\n return res.status(400).json({\n error: 'type is required',\n });\n }\n\n if (!prompt) {\n return res.status(400).json({\n error: 'prompt is required',\n });\n }\n\n // build an agent with context\n const page = new this.pageClass(context);\n const agent = new this.agentClass(page);\n\n if (requestId) {\n this.taskProgressTips[requestId] = '';\n\n agent.onTaskStartTip = (tip: string) => {\n this.taskProgressTips[requestId] = tip;\n };\n }\n\n const response: {\n result: any;\n dump: string | null;\n error: string | null;\n reportHTML: string | null;\n requestId?: string;\n } = {\n result: null,\n dump: null,\n error: null,\n reportHTML: null,\n requestId,\n };\n\n const startTime = Date.now();\n try {\n if (type === 'aiQuery') {\n response.result = await agent.aiQuery(prompt);\n } else if (type === 'aiAction') {\n response.result = await agent.aiAction(prompt);\n } else if (type === 'aiAssert') {\n response.result = await agent.aiAssert(prompt, undefined, {\n keepRawResponse: true,\n });\n } else if (type === 'aiTap') {\n response.result = await agent.aiTap(prompt, {\n deepThink,\n });\n } else {\n response.error = `Unknown type: ${type}`;\n }\n } catch (error: any) {\n if (!error.message.includes(ERROR_CODE_NOT_IMPLEMENTED_AS_DESIGNED)) {\n response.error = error.message;\n }\n }\n\n try {\n response.dump = JSON.parse(agent.dumpDataString());\n response.reportHTML = agent.reportHTMLString() || null;\n\n agent.writeOutActionDumps();\n } catch (error: any) {\n console.error(\n `write out dump failed: requestId: ${requestId}, ${error.message}`,\n );\n }\n\n res.send(response);\n const timeCost = Date.now() - startTime;\n\n if (response.error) {\n console.error(\n `handle request failed after ${timeCost}ms: requestId: ${requestId}, ${response.error}`,\n );\n } else {\n console.log(\n `handle request done after ${timeCost}ms: requestId: ${requestId}`,\n );\n }\n },\n );\n\n this.app.post(\n '/config',\n express.json({ limit: '1mb' }),\n async (req, res) => {\n const { aiConfig } = req.body;\n\n if (!aiConfig || typeof aiConfig !== 'object') {\n return res.status(400).json({\n error: 'aiConfig is required and must be an object',\n });\n }\n\n try {\n overrideAIConfig(aiConfig);\n\n return res.json({\n status: 'ok',\n message: 'AI config updated successfully',\n });\n } catch (error: any) {\n console.error(`Failed to update AI config: ${error.message}`);\n return res.status(500).json({\n error: `Failed to update AI config: ${error.message}`,\n });\n }\n },\n );\n\n // Set up static file serving after all API routes are defined\n if (this.staticPath) {\n this.app.get('/', (req, res) => {\n // compatible with windows\n res.redirect('/index.html');\n });\n\n this.app.get('*', (req, res) => {\n const requestedPath = join(this.staticPath!, req.path);\n if (existsSync(requestedPath)) {\n res.sendFile(requestedPath);\n } else {\n res.sendFile(join(this.staticPath!, 'index.html'));\n }\n });\n }\n\n return new Promise((resolve, reject) => {\n const port = this.port;\n this.server = this.app.listen(port, () => {\n resolve(this);\n });\n });\n }\n\n close() {\n // close the server\n if (this.server) {\n return this.server.close();\n }\n }\n}\n","import type { StaticPage } from '@/playground';\nimport type {\n BaseElement,\n ElementTreeNode,\n PlanningLocateParam,\n PlaywrightParserOpt,\n UIContext,\n} from 'misoai-core';\nimport { elementByPositionWithElementInfo } from 'misoai-core/ai-model';\nimport { uploadTestInfoToServer } from 'misoai-core/utils';\nimport { MIDSCENE_REPORT_TAG_NAME, getAIConfig } from 'misoai-shared/env';\nimport type { ElementInfo } from 'misoai-shared/extractor';\nimport {\n generateElementByPosition,\n getNodeFromCacheList,\n traverseTree,\n treeToList,\n} from 'misoai-shared/extractor';\nimport { resizeImgBase64 } from 'misoai-shared/img';\nimport type { DebugFunction } from 'misoai-shared/logger';\nimport { assert, logMsg, uuid } from 'misoai-shared/utils';\nimport dayjs from 'dayjs';\nimport type { Page as PlaywrightPage } from 'playwright';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport { WebElementInfo } from '../web-element';\nimport type { WebPage } from './page';\n\nexport type WebUIContext = UIContext<WebElementInfo> & {\n url: string;\n};\n\nexport async function parseContextFromWebPage(\n page: WebPage,\n _opt?: PlaywrightParserOpt,\n): Promise<WebUIContext> {\n assert(page, 'page is required');\n if ((page as StaticPage)._forceUsePageContext) {\n return await (page as any)._forceUsePageContext();\n }\n const url = await page.url();\n uploadTestInfoToServer({ testUrl: url });\n\n let screenshotBase64: string;\n let tree: ElementTreeNode<ElementInfo>;\n\n await Promise.all([\n page.screenshotBase64().then((base64) => {\n screenshotBase64 = base64;\n }),\n page.getElementsNodeTree().then(async (treeRoot) => {\n tree = treeRoot;\n }),\n ]);\n\n const webTree = traverseTree(tree!, (elementInfo) => {\n const { rect, id, content, attributes, locator, indexId, isVisible } =\n elementInfo;\n return new WebElementInfo({\n rect,\n locator,\n id,\n content,\n attributes,\n indexId,\n isVisible,\n });\n });\n\n assert(screenshotBase64!, 'screenshotBase64 is required');\n\n const elementsInfo = treeToList(webTree);\n const size = await page.size();\n\n if (size.dpr && size.dpr > 1) {\n // console.time('resizeImgBase64');\n screenshotBase64 = await resizeImgBase64(screenshotBase64, {\n width: size.width,\n height: size.height,\n });\n // console.timeEnd('resizeImgBase64');\n }\n\n return {\n content: elementsInfo!,\n tree: webTree,\n size,\n screenshotBase64: screenshotBase64!,\n url,\n };\n}\n\nexport function reportFileName(tag = 'web') {\n const reportTagName = getAIConfig(MIDSCENE_REPORT_TAG_NAME);\n const dateTimeInFileName = dayjs().format('YYYY-MM-DD_HH-mm-ss');\n // ensure uniqueness at the same time\n const uniqueId = uuid().substring(0, 8);\n return `${reportTagName || tag}-${dateTimeInFileName}-${uniqueId}`;\n}\n\nexport function printReportMsg(filepath: string) {\n logMsg(`Midscene - report file updated: ${filepath}`);\n}\n\n/**\n * Get the current execution file name\n * @returns The name of the current execution file\n */\nexport function getCurrentExecutionFile(trace?: string): string | false {\n const error = new Error();\n const stackTrace = trace || error.stack;\n const pkgDir = process.cwd() || '';\n if (stackTrace) {\n const stackLines = stackTrace.split('\\n');\n for (const line of stackLines) {\n if (\n line.includes('.spec.') ||\n line.includes('.test.') ||\n line.includes('.ts') ||\n line.includes('.js')\n ) {\n const match = line.match(/(?:at\\s+)?(.*?\\.(?:spec|test)\\.[jt]s)/);\n if (match?.[1]) {\n const targetFileName = match[1]\n .replace(pkgDir, '')\n .trim()\n .replace('at ', '');\n return targetFileName;\n }\n }\n }\n }\n return false;\n}\n\nconst testFileIndex = new Map<string, number>();\n\nexport function generateCacheId(fileName?: string): string {\n let taskFile = fileName || getCurrentExecutionFile();\n if (!taskFile) {\n taskFile = uuid();\n console.warn(\n 'Midscene - using random UUID for cache id. Cache may be invalid.',\n );\n }\n\n if (testFileIndex.has(taskFile)) {\n const currentIndex = testFileIndex.get(taskFile);\n if (currentIndex !== undefined) {\n testFileIndex.set(taskFile, currentIndex + 1);\n }\n } else {\n testFileIndex.set(taskFile, 1);\n }\n return `${taskFile}-${testFileIndex.get(taskFile)}`;\n}\n\nexport const ERROR_CODE_NOT_IMPLEMENTED_AS_DESIGNED =\n 'NOT_IMPLEMENTED_AS_DESIGNED';\n\nexport function replaceIllegalPathCharsAndSpace(str: string) {\n // Only replace characters that are illegal in filenames, but preserve path separators\n return str.replace(/[:*?\"<>| ]/g, '-');\n}\n\nexport function forceClosePopup(\n page: PuppeteerPage | PlaywrightPage,\n debug: DebugFunction,\n) {\n page.on('popup', async (popup) => {\n if (!popup) {\n console.warn('got a popup event, but the popup is not ready yet, skip');\n return;\n }\n const url = await (popup as PuppeteerPage).url();\n console.log(`Popup opened: ${url}`);\n if (!(popup as PuppeteerPage).isClosed()) {\n try {\n await (popup as PuppeteerPage).close(); // Close the newly opened TAB\n } catch (error) {\n debug(`failed to close popup ${url}, error: ${error}`);\n }\n } else {\n debug(`popup is already closed, skip close ${url}`);\n }\n\n if (!page.isClosed()) {\n try {\n await page.goto(url);\n } catch (error) {\n debug(`failed to goto ${url}, error: ${error}`);\n }\n } else {\n debug(`page is already closed, skip goto ${url}`);\n }\n });\n}\n\nexport function matchElementFromPlan(\n planLocateParam: PlanningLocateParam,\n tree: ElementTreeNode<BaseElement>,\n) {\n if (!planLocateParam) {\n return undefined;\n }\n if (planLocateParam.id) {\n return getNodeFromCacheList(planLocateParam.id);\n }\n\n if (planLocateParam.bbox) {\n const centerPosition = {\n x: Math.floor((planLocateParam.bbox[0] + planLocateParam.bbox[2]) / 2),\n y: Math.floor((planLocateParam.bbox[1] + planLocateParam.bbox[3]) / 2),\n };\n let element = elementByPositionWithElementInfo(tree, centerPosition);\n\n if (!element) {\n element = generateElementByPosition(centerPosition) as BaseElement;\n }\n\n return element;\n }\n\n return undefined;\n}\n"]}
1
+ {"version":3,"mappings":"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","names":["uuid","port"],"ignoreList":[],"sources":["../../src/playground/server.ts","../../src/common/utils.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport type { Server } from 'node:http';\nimport { join } from 'node:path';\nimport { ERROR_CODE_NOT_IMPLEMENTED_AS_DESIGNED } from '@/common/utils';\nimport cors from 'cors';\nimport dotenv from 'dotenv';\nimport express from 'express';\nimport { getTmpDir } from 'misoai-core/utils';\nimport { PLAYGROUND_SERVER_PORT } from 'misoai-shared/constants';\nimport { overrideAIConfig } from 'misoai-shared/env';\nimport { ifInBrowser } from 'misoai-shared/utils';\nimport type { PageAgent } from '../common/agent';\nimport type { AbstractPage } from '../page';\n\nconst defaultPort = PLAYGROUND_SERVER_PORT;\n// const staticPath = join(__dirname, '../../static');\n\nconst errorHandler = (err: any, req: any, res: any, next: any) => {\n console.error(err);\n res.status(500).json({\n error: err.message,\n });\n};\n\nconst setup = async () => {\n if (!ifInBrowser) {\n const { parsed } = dotenv.config();\n\n if (parsed) {\n overrideAIConfig(parsed);\n }\n }\n};\n\nexport default class PlaygroundServer {\n app: express.Application;\n tmpDir: string;\n server?: Server;\n port?: number | null;\n pageClass: new (\n ...args: any[]\n ) => AbstractPage;\n agentClass: new (\n ...args: any[]\n ) => PageAgent;\n staticPath?: string;\n taskProgressTips: Record<string, string>;\n\n constructor(\n pageClass: new (...args: any[]) => AbstractPage,\n agentClass: new (...args: any[]) => PageAgent,\n staticPath?: string,\n ) {\n this.app = express();\n this.tmpDir = getTmpDir()!;\n this.pageClass = pageClass;\n this.agentClass = agentClass;\n this.staticPath = staticPath;\n this.taskProgressTips = {};\n setup();\n }\n\n filePathForUuid(uuid: string) {\n return join(this.tmpDir, `${uuid}.json`);\n }\n\n saveContextFile(uuid: string, context: string) {\n const tmpFile = this.filePathForUuid(uuid);\n console.log(`save context file: ${tmpFile}`);\n writeFileSync(tmpFile, context);\n return tmpFile;\n }\n\n async launch(port?: number) {\n this.port = port || defaultPort;\n this.app.use(errorHandler);\n\n this.app.use(\n cors({\n origin: '*',\n credentials: true,\n }),\n );\n\n this.app.get('/status', cors(), async (req, res) => {\n // const modelName = g\n res.send({\n status: 'ok',\n });\n });\n\n // this.app.get('/playground/:uuid', async (req, res) => {\n // res.sendFile(join(staticPath, 'index.html'));\n // });\n\n this.app.get('/context/:uuid', async (req, res) => {\n const { uuid } = req.params;\n const contextFile = this.filePathForUuid(uuid);\n\n if (!existsSync(contextFile)) {\n return res.status(404).json({\n error: 'Context not found',\n });\n }\n\n const context = readFileSync(contextFile, 'utf8');\n res.json({\n context,\n });\n });\n\n this.app.get('/task-progress/:requestId', cors(), async (req, res) => {\n const { requestId } = req.params;\n res.json({\n tip: this.taskProgressTips[requestId] || '',\n });\n });\n\n // -------------------------\n // actions from report file\n this.app.post(\n '/playground-with-context',\n express.json({ limit: '50mb' }),\n async (req, res) => {\n const context = req.body.context;\n\n if (!context) {\n return res.status(400).json({\n error: 'context is required',\n });\n }\n\n const uuid = randomUUID();\n this.saveContextFile(uuid, context);\n return res.json({\n location: `/playground/${uuid}`,\n uuid,\n });\n },\n );\n\n this.app.post(\n '/execute',\n express.json({ limit: '30mb' }),\n async (req, res) => {\n const { context, type, prompt, requestId, deepThink } = req.body;\n\n if (!context) {\n return res.status(400).json({\n error: 'context is required',\n });\n }\n\n if (!type) {\n return res.status(400).json({\n error: 'type is required',\n });\n }\n\n if (!prompt) {\n return res.status(400).json({\n error: 'prompt is required',\n });\n }\n\n // build an agent with context\n const page = new this.pageClass(context);\n const agent = new this.agentClass(page);\n\n if (requestId) {\n this.taskProgressTips[requestId] = '';\n\n agent.onTaskStartTip = (tip: string) => {\n this.taskProgressTips[requestId] = tip;\n };\n }\n\n const response: {\n result: any;\n dump: string | null;\n error: string | null;\n reportHTML: string | null;\n requestId?: string;\n } = {\n result: null,\n dump: null,\n error: null,\n reportHTML: null,\n requestId,\n };\n\n const startTime = Date.now();\n try {\n if (type === 'aiQuery') {\n response.result = await agent.aiQuery(prompt);\n } else if (type === 'aiAction') {\n response.result = await agent.aiAction(prompt);\n } else if (type === 'aiAssert') {\n response.result = await agent.aiAssert(prompt, undefined, {\n keepRawResponse: true,\n });\n } else if (type === 'aiTap') {\n response.result = await agent.aiTap(prompt, {\n deepThink,\n });\n } else {\n response.error = `Unknown type: ${type}`;\n }\n } catch (error: any) {\n if (!error.message.includes(ERROR_CODE_NOT_IMPLEMENTED_AS_DESIGNED)) {\n response.error = error.message;\n }\n }\n\n try {\n response.dump = JSON.parse(agent.dumpDataString());\n response.reportHTML = agent.reportHTMLString() || null;\n\n agent.writeOutActionDumps();\n } catch (error: any) {\n console.error(\n `write out dump failed: requestId: ${requestId}, ${error.message}`,\n );\n }\n\n res.send(response);\n const timeCost = Date.now() - startTime;\n\n if (response.error) {\n console.error(\n `handle request failed after ${timeCost}ms: requestId: ${requestId}, ${response.error}`,\n );\n } else {\n console.log(\n `handle request done after ${timeCost}ms: requestId: ${requestId}`,\n );\n }\n },\n );\n\n this.app.post(\n '/config',\n express.json({ limit: '1mb' }),\n async (req, res) => {\n const { aiConfig } = req.body;\n\n if (!aiConfig || typeof aiConfig !== 'object') {\n return res.status(400).json({\n error: 'aiConfig is required and must be an object',\n });\n }\n\n try {\n overrideAIConfig(aiConfig);\n\n return res.json({\n status: 'ok',\n message: 'AI config updated successfully',\n });\n } catch (error: any) {\n console.error(`Failed to update AI config: ${error.message}`);\n return res.status(500).json({\n error: `Failed to update AI config: ${error.message}`,\n });\n }\n },\n );\n\n // Set up static file serving after all API routes are defined\n if (this.staticPath) {\n this.app.get('/', (req, res) => {\n // compatible with windows\n res.redirect('/index.html');\n });\n\n this.app.get('*', (req, res) => {\n const requestedPath = join(this.staticPath!, req.path);\n if (existsSync(requestedPath)) {\n res.sendFile(requestedPath);\n } else {\n res.sendFile(join(this.staticPath!, 'index.html'));\n }\n });\n }\n\n return new Promise((resolve, reject) => {\n const port = this.port;\n this.server = this.app.listen(port, () => {\n resolve(this);\n });\n });\n }\n\n close() {\n // close the server\n if (this.server) {\n return this.server.close();\n }\n }\n}\n","import type { StaticPage } from '@/playground';\nimport dayjs from 'dayjs';\nimport type {\n BaseElement,\n ElementTreeNode,\n PlanningLocateParam,\n PlaywrightParserOpt,\n UIContext,\n} from 'misoai-core';\nimport { elementByPositionWithElementInfo } from 'misoai-core/ai-model';\nimport { uploadTestInfoToServer } from 'misoai-core/utils';\nimport { MIDSCENE_REPORT_TAG_NAME, getAIConfig } from 'misoai-shared/env';\nimport type { ElementInfo } from 'misoai-shared/extractor';\nimport {\n generateElementByPosition,\n getNodeFromCacheList,\n traverseTree,\n treeToList,\n} from 'misoai-shared/extractor';\nimport { resizeImgBase64 } from 'misoai-shared/img';\nimport type { DebugFunction } from 'misoai-shared/logger';\nimport { assert, logMsg, uuid } from 'misoai-shared/utils';\nimport type { Page as PlaywrightPage } from 'playwright';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport { WebElementInfo } from '../web-element';\nimport type { WebPage } from './page';\n\nexport type WebUIContext = UIContext<WebElementInfo> & {\n url: string;\n};\n\nexport async function parseContextFromWebPage(\n page: WebPage,\n _opt?: PlaywrightParserOpt,\n): Promise<WebUIContext> {\n assert(page, 'page is required');\n if ((page as StaticPage)._forceUsePageContext) {\n return await (page as any)._forceUsePageContext();\n }\n const url = await page.url();\n uploadTestInfoToServer({ testUrl: url });\n\n let screenshotBase64: string;\n let tree: ElementTreeNode<ElementInfo>;\n\n await Promise.all([\n page.screenshotBase64().then((base64) => {\n screenshotBase64 = base64;\n }),\n page.getElementsNodeTree().then(async (treeRoot) => {\n tree = treeRoot;\n }),\n ]);\n\n const webTree = traverseTree(tree!, (elementInfo) => {\n const { rect, id, content, attributes, locator, indexId, isVisible } =\n elementInfo;\n return new WebElementInfo({\n rect,\n locator,\n id,\n content,\n attributes,\n indexId,\n isVisible,\n });\n });\n\n assert(screenshotBase64!, 'screenshotBase64 is required');\n\n const elementsInfo = treeToList(webTree);\n const size = await page.size();\n\n if (size.dpr && size.dpr > 1) {\n // console.time('resizeImgBase64');\n screenshotBase64 = await resizeImgBase64(screenshotBase64, {\n width: size.width,\n height: size.height,\n });\n // console.timeEnd('resizeImgBase64');\n }\n\n return {\n content: elementsInfo!,\n tree: webTree,\n size,\n screenshotBase64: screenshotBase64!,\n url,\n };\n}\n\nexport function reportFileName(tag = 'web') {\n const reportTagName = getAIConfig(MIDSCENE_REPORT_TAG_NAME);\n const dateTimeInFileName = dayjs().format('YYYY-MM-DD_HH-mm-ss');\n // ensure uniqueness at the same time\n const uniqueId = uuid().substring(0, 8);\n return `${reportTagName || tag}-${dateTimeInFileName}-${uniqueId}`;\n}\n\nexport function printReportMsg(filepath: string) {\n logMsg(`Midscene - report file updated: ${filepath}`);\n}\n\n/**\n * Get the current execution file name\n * @returns The name of the current execution file\n */\nexport function getCurrentExecutionFile(trace?: string): string | false {\n const error = new Error();\n const stackTrace = trace || error.stack;\n const pkgDir = process.cwd() || '';\n if (stackTrace) {\n const stackLines = stackTrace.split('\\n');\n for (const line of stackLines) {\n if (\n line.includes('.spec.') ||\n line.includes('.test.') ||\n line.includes('.ts') ||\n line.includes('.js')\n ) {\n const match = line.match(/(?:at\\s+)?(.*?\\.(?:spec|test)\\.[jt]s)/);\n if (match?.[1]) {\n const targetFileName = match[1]\n .replace(pkgDir, '')\n .trim()\n .replace('at ', '');\n return targetFileName;\n }\n }\n }\n }\n return false;\n}\n\nconst testFileIndex = new Map<string, number>();\n\nexport function generateCacheId(fileName?: string): string {\n let taskFile = fileName || getCurrentExecutionFile();\n if (!taskFile) {\n taskFile = uuid();\n console.warn(\n 'Midscene - using random UUID for cache id. Cache may be invalid.',\n );\n }\n\n if (testFileIndex.has(taskFile)) {\n const currentIndex = testFileIndex.get(taskFile);\n if (currentIndex !== undefined) {\n testFileIndex.set(taskFile, currentIndex + 1);\n }\n } else {\n testFileIndex.set(taskFile, 1);\n }\n return `${taskFile}-${testFileIndex.get(taskFile)}`;\n}\n\nexport const ERROR_CODE_NOT_IMPLEMENTED_AS_DESIGNED =\n 'NOT_IMPLEMENTED_AS_DESIGNED';\n\nexport function replaceIllegalPathCharsAndSpace(str: string) {\n // Only replace characters that are illegal in filenames, but preserve path separators\n return str.replace(/[:*?\"<>| ]/g, '-');\n}\n\nexport function forceClosePopup(\n page: PuppeteerPage | PlaywrightPage,\n debug: DebugFunction,\n) {\n page.on('popup', async (popup) => {\n if (!popup) {\n console.warn('got a popup event, but the popup is not ready yet, skip');\n return;\n }\n const url = await (popup as PuppeteerPage).url();\n console.log(`Popup opened: ${url}`);\n if (!(popup as PuppeteerPage).isClosed()) {\n try {\n await (popup as PuppeteerPage).close(); // Close the newly opened TAB\n } catch (error) {\n debug(`failed to close popup ${url}, error: ${error}`);\n }\n } else {\n debug(`popup is already closed, skip close ${url}`);\n }\n\n if (!page.isClosed()) {\n try {\n await page.goto(url);\n } catch (error) {\n debug(`failed to goto ${url}, error: ${error}`);\n }\n } else {\n debug(`page is already closed, skip goto ${url}`);\n }\n });\n}\n\nexport function matchElementFromPlan(\n planLocateParam: PlanningLocateParam,\n tree: ElementTreeNode<BaseElement>,\n) {\n if (!planLocateParam) {\n return undefined;\n }\n if (planLocateParam.id) {\n return getNodeFromCacheList(planLocateParam.id);\n }\n\n if (planLocateParam.bbox) {\n const centerPosition = {\n x: Math.floor((planLocateParam.bbox[0] + planLocateParam.bbox[2]) / 2),\n y: Math.floor((planLocateParam.bbox[1] + planLocateParam.bbox[3]) / 2),\n };\n let element = elementByPositionWithElementInfo(tree, centerPosition);\n\n if (!element) {\n element = generateElementByPosition(centerPosition) as BaseElement;\n }\n\n return element;\n }\n\n return undefined;\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  // src/common/utils.ts
2
+ import dayjs from "dayjs";
2
3
  import { elementByPositionWithElementInfo } from "misoai-core/ai-model";
3
4
  import { uploadTestInfoToServer } from "misoai-core/utils";
4
5
  import { MIDSCENE_REPORT_TAG_NAME, getAIConfig } from "misoai-shared/env";
@@ -10,7 +11,6 @@ import {
10
11
  } from "misoai-shared/extractor";
11
12
  import { resizeImgBase64 } from "misoai-shared/img";
12
13
  import { assert, logMsg, uuid } from "misoai-shared/utils";
13
- import dayjs from "dayjs";
14
14
 
15
15
  // src/web-element.ts
16
16
  var WebElementInfo = class {
@@ -429,8 +429,8 @@ var ScriptPlayer = class {
429
429
  import yaml from "js-yaml";
430
430
 
431
431
  // src/yaml/utils.ts
432
- import { assert as assert3 } from "misoai-shared/utils";
433
432
  import yaml2 from "js-yaml";
433
+ import { assert as assert3 } from "misoai-shared/utils";
434
434
  function interpolateEnvVars(content) {
435
435
  return content.replace(/\$\{([^}]+)\}/g, (_, envVar) => {
436
436
  const value = process.env[envVar.trim()];
@@ -621,8 +621,12 @@ var WorkflowMemory = class {
621
621
  const workflow = this.workflows.get(workflowId) || this.createEmptyWorkflowData(workflowId);
622
622
  workflow.memory = [...memory];
623
623
  workflow.metadata.totalSteps = workflow.steps.length;
624
- workflow.metadata.completedSteps = workflow.steps.filter((s) => s.status === "completed").length;
625
- workflow.metadata.failedSteps = workflow.steps.filter((s) => s.status === "failed").length;
624
+ workflow.metadata.completedSteps = workflow.steps.filter(
625
+ (s) => s.status === "completed"
626
+ ).length;
627
+ workflow.metadata.failedSteps = workflow.steps.filter(
628
+ (s) => s.status === "failed"
629
+ ).length;
626
630
  this.workflows.set(workflowId, workflow);
627
631
  this.enforceRetentionPolicy();
628
632
  }
@@ -633,7 +637,9 @@ var WorkflowMemory = class {
633
637
  const workflow = this.workflows.get(workflowId) || this.createEmptyWorkflowData(workflowId);
634
638
  workflow.context = { ...workflow.context, ...context };
635
639
  if (context.currentStep) {
636
- const existingStep = workflow.steps.find((s) => s.stepName === context.currentStep);
640
+ const existingStep = workflow.steps.find(
641
+ (s) => s.stepName === context.currentStep
642
+ );
637
643
  if (!existingStep) {
638
644
  workflow.steps.push({
639
645
  stepId: `step_${workflow.steps.length + 1}`,
@@ -678,7 +684,9 @@ var WorkflowMemory = class {
678
684
  enforceRetentionPolicy() {
679
685
  const maxWorkflows = 10;
680
686
  if (this.workflows.size > maxWorkflows) {
681
- const sortedWorkflows = Array.from(this.workflows.entries()).sort(([, a], [, b]) => (b.metadata.endTime || b.metadata.startTime) - (a.metadata.endTime || a.metadata.startTime));
687
+ const sortedWorkflows = Array.from(this.workflows.entries()).sort(
688
+ ([, a], [, b]) => (b.metadata.endTime || b.metadata.startTime) - (a.metadata.endTime || a.metadata.startTime)
689
+ );
682
690
  const toDelete = sortedWorkflows.slice(maxWorkflows);
683
691
  toDelete.forEach(([workflowId]) => this.workflows.delete(workflowId));
684
692
  }
@@ -1417,7 +1425,9 @@ var PageTaskExecutor = class {
1417
1425
  */
1418
1426
  getPersistentExecutor() {
1419
1427
  if (!this.persistentExecutor || this.persistentExecutor.status === "error") {
1420
- const previousMemory = this.workflowMemory.getWorkflowMemory(this.sessionContext.workflowId);
1428
+ const previousMemory = this.workflowMemory.getWorkflowMemory(
1429
+ this.sessionContext.workflowId
1430
+ );
1421
1431
  this.persistentExecutor = new Executor("Persistent Task Executor", {
1422
1432
  onTaskStart: this.onTaskStartCallback,
1423
1433
  initialMemory: previousMemory
@@ -1446,7 +1456,9 @@ var PageTaskExecutor = class {
1446
1456
  if (this.persistentExecutor) {
1447
1457
  this.persistentExecutor.clearMemory();
1448
1458
  }
1449
- this.workflowMemory.clearWorkflow(this.sessionContext.workflowId || "default");
1459
+ this.workflowMemory.clearWorkflow(
1460
+ this.sessionContext.workflowId || "default"
1461
+ );
1450
1462
  }
1451
1463
  /**
1452
1464
  * Mevcut hafızayı döndürür
@@ -1458,7 +1470,9 @@ var PageTaskExecutor = class {
1458
1470
  * İş akışı hafızasını döndürür
1459
1471
  */
1460
1472
  getWorkflowMemory() {
1461
- return this.workflowMemory.getWorkflowData(this.sessionContext.workflowId || "default");
1473
+ return this.workflowMemory.getWorkflowData(
1474
+ this.sessionContext.workflowId || "default"
1475
+ );
1462
1476
  }
1463
1477
  /**
1464
1478
  * Hafıza istatistiklerini döndürür
@@ -1466,7 +1480,13 @@ var PageTaskExecutor = class {
1466
1480
  getMemoryStats() {
1467
1481
  return this.persistentExecutor?.getMemoryStats() || {
1468
1482
  totalItems: 0,
1469
- analytics: { totalTasks: 0, memoryHits: 0, memoryMisses: 0, averageMemorySize: 0, memoryEffectiveness: 0 },
1483
+ analytics: {
1484
+ totalTasks: 0,
1485
+ memoryHits: 0,
1486
+ memoryMisses: 0,
1487
+ averageMemorySize: 0,
1488
+ memoryEffectiveness: 0
1489
+ },
1470
1490
  config: this.memoryConfig
1471
1491
  };
1472
1492
  }
@@ -1502,11 +1522,14 @@ var PageTaskExecutor = class {
1502
1522
  let taskExecutor;
1503
1523
  if (useMemory) {
1504
1524
  taskExecutor = this.getPersistentExecutor();
1505
- this.workflowMemory.updateWorkflowContext({
1506
- currentStep: title,
1507
- pageInfo: this.sessionContext.pageInfo,
1508
- timestamp: Date.now()
1509
- }, this.sessionContext.workflowId || "default");
1525
+ this.workflowMemory.updateWorkflowContext(
1526
+ {
1527
+ currentStep: title,
1528
+ pageInfo: this.sessionContext.pageInfo,
1529
+ timestamp: Date.now()
1530
+ },
1531
+ this.sessionContext.workflowId || "default"
1532
+ );
1510
1533
  } else {
1511
1534
  taskExecutor = new Executor(title, {
1512
1535
  onTaskStart: this.onTaskStartCallback
@@ -1872,14 +1895,19 @@ var PageTaskExecutor = class {
1872
1895
  */
1873
1896
  addToMemory(memoryItem) {
1874
1897
  if (!this.persistentExecutor || this.persistentExecutor.status === "error") {
1875
- const previousMemory = this.workflowMemory.getWorkflowMemory(this.sessionContext.workflowId);
1898
+ const previousMemory = this.workflowMemory.getWorkflowMemory(
1899
+ this.sessionContext.workflowId
1900
+ );
1876
1901
  this.persistentExecutor = new Executor("Persistent Task Executor", {
1877
1902
  onTaskStart: this.onTaskStartCallback,
1878
1903
  initialMemory: previousMemory
1879
1904
  });
1880
1905
  }
1881
1906
  this.persistentExecutor.memoryStore?.add(memoryItem);
1882
- this.persistentExecutor.memoryAnalytics?.recordMemoryOperation("add", memoryItem);
1907
+ this.persistentExecutor.memoryAnalytics?.recordMemoryOperation(
1908
+ "add",
1909
+ memoryItem
1910
+ );
1883
1911
  }
1884
1912
  };
1885
1913
 
@@ -1968,14 +1996,14 @@ function buildPlans(type, locateParam, param) {
1968
1996
  import assert6 from "assert";
1969
1997
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
1970
1998
  import { dirname as dirname2, join as join2 } from "path";
1999
+ import yaml3 from "js-yaml";
1971
2000
  import { getMidsceneRunSubDir as getMidsceneRunSubDir2 } from "misoai-shared/common";
1972
2001
  import { getDebug as getDebug3 } from "misoai-shared/logger";
1973
2002
  import { ifInBrowser as ifInBrowser2 } from "misoai-shared/utils";
1974
- import yaml3 from "js-yaml";
1975
2003
  import semver from "semver";
1976
2004
 
1977
2005
  // package.json
1978
- var version = "1.6.0";
2006
+ var version = "1.6.1";
1979
2007
 
1980
2008
  // src/common/task-cache.ts
1981
2009
  var debug3 = getDebug3("cache");
@@ -2270,22 +2298,28 @@ var PageAgent = class {
2270
2298
  const allThoughts = executor.tasks.filter((task) => task.thought).map((task) => task.thought);
2271
2299
  const allLocates = executor.tasks.filter((task) => task.locate).map((task) => task.locate);
2272
2300
  const allPlans = executor.tasks.filter((task) => task.param?.plans).map((task) => task.param?.plans);
2273
- const planningTasks = executor.tasks.filter((task) => task.type === "Planning");
2274
- const insightTasks = executor.tasks.filter((task) => task.type === "Insight");
2301
+ const planningTasks = executor.tasks.filter(
2302
+ (task) => task.type === "Planning"
2303
+ );
2304
+ const insightTasks = executor.tasks.filter(
2305
+ (task) => task.type === "Insight"
2306
+ );
2275
2307
  const actionTasks = executor.tasks.filter((task) => task.type === "Action");
2276
2308
  const planning = planningTasks.length > 0 ? {
2277
2309
  type: "Planning",
2278
- description: `Planning for task execution`,
2310
+ description: "Planning for task execution",
2279
2311
  steps: planningTasks.map((task) => task.thought || "Planning step")
2280
2312
  } : void 0;
2281
2313
  const insight = insightTasks.length > 0 ? {
2282
2314
  type: "Insight",
2283
- description: `Insight for task execution`,
2284
- elements: insightTasks.map((task) => task.thought || "Insight element")
2315
+ description: "Insight for task execution",
2316
+ elements: insightTasks.map(
2317
+ (task) => task.thought || "Insight element"
2318
+ )
2285
2319
  } : void 0;
2286
2320
  const action = actionTasks.length > 0 ? {
2287
2321
  type: "Action",
2288
- description: `Action for task execution`,
2322
+ description: "Action for task execution",
2289
2323
  result: lastTask?.output
2290
2324
  } : void 0;
2291
2325
  const actionDetails = executor.tasks.map((task) => ({
@@ -2619,7 +2653,10 @@ ${memoryContext}` : void 0;
2619
2653
  }
2620
2654
  const memoryContext = this.getMemoryAsContext();
2621
2655
  const assertionWithContext = currentUrl ? `For the page at URL "${currentUrl}", ${assertion}` : assertion;
2622
- const { output, executor } = await this.taskExecutor.assert(assertionWithContext, memoryContext);
2656
+ const { output, executor } = await this.taskExecutor.assert(
2657
+ assertionWithContext,
2658
+ memoryContext
2659
+ );
2623
2660
  const metadata = this.afterTaskRunning(executor, true);
2624
2661
  if (output && opt?.keepRawResponse) {
2625
2662
  return {
@@ -2658,7 +2695,10 @@ A complex CAPTCHA typically has one or more of these characteristics:
2658
2695
  Return only "complex" or "simple" based on your analysis.
2659
2696
  `;
2660
2697
  const complexityMsgs = [
2661
- { role: "system", content: "You are an AI assistant that analyzes screenshots to determine CAPTCHA complexity." },
2698
+ {
2699
+ role: "system",
2700
+ content: "You are an AI assistant that analyzes screenshots to determine CAPTCHA complexity."
2701
+ },
2662
2702
  {
2663
2703
  role: "user",
2664
2704
  content: [
@@ -2682,7 +2722,12 @@ Return only "complex" or "simple" based on your analysis.
2682
2722
  );
2683
2723
  const responseText = typeof complexityResult.content === "string" ? complexityResult.content.toLowerCase() : JSON.stringify(complexityResult.content).toLowerCase();
2684
2724
  shouldUseDeepThink = responseText.includes("complex");
2685
- debug4("CAPTCHA complexity analysis:", responseText, "Using deep think:", shouldUseDeepThink);
2725
+ debug4(
2726
+ "CAPTCHA complexity analysis:",
2727
+ responseText,
2728
+ "Using deep think:",
2729
+ shouldUseDeepThink
2730
+ );
2686
2731
  } catch (error) {
2687
2732
  debug4("Failed to analyze CAPTCHA complexity:", error);
2688
2733
  }
@@ -2699,7 +2744,9 @@ Return only "complex" or "simple" based on your analysis.
2699
2744
  await this.aiTap(action.target, { deepThink: shouldUseDeepThink });
2700
2745
  } else if (action.type === "input" && action.value) {
2701
2746
  if (action.target) {
2702
- await this.aiInput(action.value, action.target, { deepThink: shouldUseDeepThink });
2747
+ await this.aiInput(action.value, action.target, {
2748
+ deepThink: shouldUseDeepThink
2749
+ });
2703
2750
  }
2704
2751
  } else if (action.type === "verify" && action.target) {
2705
2752
  await this.aiTap(action.target, { deepThink: shouldUseDeepThink });
@@ -2711,7 +2758,9 @@ Return only "complex" or "simple" based on your analysis.
2711
2758
  if (action.coordinates) {
2712
2759
  const x = action.coordinates[0];
2713
2760
  const y = action.coordinates[1];
2714
- await this.aiTap(`element at coordinates (${x}, ${y})`, { deepThink: shouldUseDeepThink });
2761
+ await this.aiTap(`element at coordinates (${x}, ${y})`, {
2762
+ deepThink: shouldUseDeepThink
2763
+ });
2715
2764
  } else if (action.target) {
2716
2765
  await this.aiTap(action.target, { deepThink: shouldUseDeepThink });
2717
2766
  }
@@ -2863,25 +2912,27 @@ ${errors}`);
2863
2912
  const executionDump = {
2864
2913
  name: screenshotTitle,
2865
2914
  description: content,
2866
- tasks: [{
2867
- type: "Screenshot",
2868
- subType: "log",
2869
- status: "finished",
2870
- executor: null,
2871
- param: {
2872
- title: screenshotTitle,
2873
- content
2874
- },
2875
- output: {
2876
- screenshot
2877
- },
2878
- thought: `Logged screenshot: ${screenshotTitle}`,
2879
- timing: {
2880
- start: Date.now(),
2881
- end: Date.now(),
2882
- cost: 0
2915
+ tasks: [
2916
+ {
2917
+ type: "Screenshot",
2918
+ subType: "log",
2919
+ status: "finished",
2920
+ executor: null,
2921
+ param: {
2922
+ title: screenshotTitle,
2923
+ content
2924
+ },
2925
+ output: {
2926
+ screenshot
2927
+ },
2928
+ thought: `Logged screenshot: ${screenshotTitle}`,
2929
+ timing: {
2930
+ start: Date.now(),
2931
+ end: Date.now(),
2932
+ cost: 0
2933
+ }
2883
2934
  }
2884
- }],
2935
+ ],
2885
2936
  sdkVersion: "1.0.0",
2886
2937
  logTime: Date.now(),
2887
2938
  model_name: "screenshot"
@@ -2933,7 +2984,9 @@ ${errors}`);
2933
2984
  totalTasks: stats.analytics.totalTasks,
2934
2985
  memoryHits: stats.analytics.memoryHits,
2935
2986
  memoryMisses: stats.analytics.memoryMisses,
2936
- memoryEffectiveness: Math.round(stats.analytics.memoryEffectiveness * 100),
2987
+ memoryEffectiveness: Math.round(
2988
+ stats.analytics.memoryEffectiveness * 100
2989
+ ),
2937
2990
  averageMemorySize: Math.round(stats.analytics.averageMemorySize * 100) / 100
2938
2991
  },
2939
2992
  config: stats.config,
@@ -2997,7 +3050,9 @@ ${errors}`);
2997
3050
  calculateSuccessRate(memory) {
2998
3051
  if (memory.length === 0)
2999
3052
  return 0;
3000
- const successCount = memory.filter((item) => item.metadata?.success !== false).length;
3053
+ const successCount = memory.filter(
3054
+ (item) => item.metadata?.success !== false
3055
+ ).length;
3001
3056
  return Math.round(successCount / memory.length * 100);
3002
3057
  }
3003
3058
  calculateAverageExecutionTime(memory) {