2024-08-04 08:28:19 +08:00
|
|
|
import assert from 'node:assert';
|
|
|
|
import type { WebPage } from '@/common/page';
|
2024-07-23 16:25:11 +08:00
|
|
|
import Insight, {
|
2024-08-04 08:28:19 +08:00
|
|
|
type AIElementParseResponse,
|
|
|
|
type DumpSubscriber,
|
|
|
|
type ExecutionRecorderItem,
|
|
|
|
type ExecutionTaskActionApply,
|
|
|
|
type ExecutionTaskApply,
|
|
|
|
type ExecutionTaskInsightLocateApply,
|
|
|
|
type ExecutionTaskInsightQueryApply,
|
|
|
|
type ExecutionTaskPlanningApply,
|
2024-07-23 16:25:11 +08:00
|
|
|
Executor,
|
2024-08-21 14:43:35 +08:00
|
|
|
plan,
|
2024-08-06 10:00:25 +08:00
|
|
|
type InsightAssertionResponse,
|
2024-08-04 08:28:19 +08:00
|
|
|
type InsightDump,
|
|
|
|
type InsightExtractParam,
|
|
|
|
type PlanningAction,
|
2024-08-06 10:00:25 +08:00
|
|
|
type PlanningActionParamAssert,
|
2024-08-04 08:28:19 +08:00
|
|
|
type PlanningActionParamHover,
|
|
|
|
type PlanningActionParamInputOrKeyPress,
|
|
|
|
type PlanningActionParamScroll,
|
2024-08-07 20:03:13 +08:00
|
|
|
type PlanningActionParamSleep,
|
2024-08-04 08:28:19 +08:00
|
|
|
type PlanningActionParamTap,
|
2024-08-21 14:43:35 +08:00
|
|
|
type PlanningActionParamWaitFor,
|
|
|
|
type PlanningActionParamError,
|
2024-07-23 16:25:11 +08:00
|
|
|
} from '@midscene/core';
|
2024-09-23 10:57:19 +08:00
|
|
|
import { sleep } from '@midscene/core/utils';
|
2024-08-26 18:50:33 +08:00
|
|
|
import { base64Encoded } from '@midscene/shared/img';
|
2024-09-05 20:05:19 +08:00
|
|
|
import type { KeyInput } from 'puppeteer';
|
|
|
|
import type { ElementInfo } from '../extractor';
|
2024-08-04 08:28:19 +08:00
|
|
|
import type { WebElementInfo } from '../web-element';
|
2024-09-06 17:19:35 +08:00
|
|
|
import { TaskCache } from './task-cache';
|
2024-08-04 08:28:19 +08:00
|
|
|
import { type WebUIContext, parseContextFromWebPage } from './utils';
|
2024-07-23 16:25:11 +08:00
|
|
|
|
2024-08-10 07:57:15 +08:00
|
|
|
interface ExecutionResult<OutputType = any> {
|
|
|
|
output: OutputType;
|
|
|
|
executor: Executor;
|
|
|
|
}
|
|
|
|
|
2024-07-28 08:49:57 +08:00
|
|
|
export class PageTaskExecutor {
|
|
|
|
page: WebPage;
|
2024-07-23 16:25:11 +08:00
|
|
|
|
2024-08-01 15:46:40 +08:00
|
|
|
insight: Insight<WebElementInfo, WebUIContext>;
|
2024-07-23 16:25:11 +08:00
|
|
|
|
2024-08-01 15:46:40 +08:00
|
|
|
taskCache: TaskCache;
|
|
|
|
|
2024-09-06 17:19:35 +08:00
|
|
|
constructor(page: WebPage, opts: { cacheId: string | undefined }) {
|
2024-07-23 16:25:11 +08:00
|
|
|
this.page = page;
|
2024-08-01 15:46:40 +08:00
|
|
|
this.insight = new Insight<WebElementInfo, WebUIContext>(async () => {
|
2024-07-28 08:49:57 +08:00
|
|
|
return await parseContextFromWebPage(page);
|
2024-07-23 16:25:11 +08:00
|
|
|
});
|
2024-09-06 17:19:35 +08:00
|
|
|
|
|
|
|
this.taskCache = new TaskCache({
|
|
|
|
fileName: opts?.cacheId,
|
|
|
|
});
|
2024-07-23 16:25:11 +08:00
|
|
|
}
|
|
|
|
|
2024-07-25 10:47:02 +08:00
|
|
|
private async recordScreenshot(timing: ExecutionRecorderItem['timing']) {
|
2024-09-23 10:57:19 +08:00
|
|
|
const file = await this.page.screenshot();
|
2024-07-23 16:25:11 +08:00
|
|
|
const item: ExecutionRecorderItem = {
|
|
|
|
type: 'screenshot',
|
|
|
|
ts: Date.now(),
|
2024-09-23 10:57:19 +08:00
|
|
|
screenshot: base64Encoded(file as string),
|
2024-07-23 16:25:11 +08:00
|
|
|
timing,
|
|
|
|
};
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
2024-08-04 08:28:19 +08:00
|
|
|
private wrapExecutorWithScreenshot(
|
|
|
|
taskApply: ExecutionTaskApply,
|
|
|
|
): ExecutionTaskApply {
|
2024-07-23 16:25:11 +08:00
|
|
|
const taskWithScreenshot: ExecutionTaskApply = {
|
|
|
|
...taskApply,
|
|
|
|
executor: async (param, context, ...args) => {
|
|
|
|
const recorder: ExecutionRecorderItem[] = [];
|
|
|
|
const { task } = context;
|
|
|
|
// set the recorder before executor in case of error
|
|
|
|
task.recorder = recorder;
|
2024-07-25 10:47:02 +08:00
|
|
|
const shot = await this.recordScreenshot(`before ${task.type}`);
|
2024-07-23 16:25:11 +08:00
|
|
|
recorder.push(shot);
|
|
|
|
const result = await taskApply.executor(param, context, ...args);
|
|
|
|
if (taskApply.type === 'Action') {
|
|
|
|
await sleep(1000);
|
2024-07-25 10:47:02 +08:00
|
|
|
const shot2 = await this.recordScreenshot('after Action');
|
2024-07-23 16:25:11 +08:00
|
|
|
recorder.push(shot2);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
return taskWithScreenshot;
|
|
|
|
}
|
|
|
|
|
2024-09-06 17:19:35 +08:00
|
|
|
private async convertPlanToExecutable(
|
|
|
|
plans: PlanningAction[],
|
|
|
|
cacheGroup?: ReturnType<TaskCache['getCacheGroupByPrompt']>,
|
|
|
|
) {
|
2024-07-23 16:25:11 +08:00
|
|
|
const tasks: ExecutionTaskApply[] = plans
|
|
|
|
.map((plan) => {
|
2024-07-25 13:40:46 +08:00
|
|
|
if (plan.type === 'Locate') {
|
|
|
|
const taskFind: ExecutionTaskInsightLocateApply = {
|
2024-07-23 16:25:11 +08:00
|
|
|
type: 'Insight',
|
2024-07-25 13:40:46 +08:00
|
|
|
subType: 'Locate',
|
2024-08-01 16:07:58 +08:00
|
|
|
param: plan.param,
|
2024-08-28 19:21:32 +08:00
|
|
|
executor: async (param, taskContext) => {
|
|
|
|
const { task } = taskContext;
|
2024-07-23 16:25:11 +08:00
|
|
|
let insightDump: InsightDump | undefined;
|
|
|
|
const dumpCollector: DumpSubscriber = (dump) => {
|
|
|
|
insightDump = dump;
|
|
|
|
};
|
|
|
|
this.insight.onceDumpUpdatedFn = dumpCollector;
|
2024-08-01 15:46:40 +08:00
|
|
|
const pageContext = await this.insight.contextRetrieverFn();
|
2024-09-06 17:19:35 +08:00
|
|
|
const locateCache = cacheGroup?.readCache(
|
2024-08-04 08:28:19 +08:00
|
|
|
pageContext,
|
|
|
|
'locate',
|
|
|
|
param.prompt,
|
|
|
|
);
|
2024-08-01 15:46:40 +08:00
|
|
|
let locateResult: AIElementParseResponse | undefined;
|
2024-08-09 21:37:41 +08:00
|
|
|
const callAI = this.insight.aiVendorFn;
|
2024-08-01 15:46:40 +08:00
|
|
|
const element = await this.insight.locate(param.prompt, {
|
2024-10-12 12:09:25 +08:00
|
|
|
quickAnswer: plan.quickAnswer,
|
2024-08-09 21:37:41 +08:00
|
|
|
callAI: async (...message: any) => {
|
2024-08-01 15:46:40 +08:00
|
|
|
if (locateCache) {
|
|
|
|
locateResult = locateCache;
|
|
|
|
return Promise.resolve(locateCache);
|
|
|
|
}
|
2024-08-09 21:37:41 +08:00
|
|
|
locateResult = await callAI(...message);
|
|
|
|
assert(locateResult);
|
2024-08-01 15:46:40 +08:00
|
|
|
return locateResult;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
if (locateResult) {
|
2024-09-06 17:19:35 +08:00
|
|
|
cacheGroup?.saveCache({
|
2024-08-01 15:46:40 +08:00
|
|
|
type: 'locate',
|
|
|
|
pageContext: {
|
|
|
|
url: pageContext.url,
|
|
|
|
size: pageContext.size,
|
|
|
|
},
|
|
|
|
prompt: param.prompt,
|
|
|
|
response: locateResult,
|
|
|
|
});
|
|
|
|
}
|
2024-08-28 19:21:32 +08:00
|
|
|
if (!element) {
|
|
|
|
task.log = {
|
|
|
|
dump: insightDump,
|
|
|
|
};
|
|
|
|
throw new Error(`Element not found: ${param.prompt}`);
|
|
|
|
}
|
|
|
|
|
2024-07-23 16:25:11 +08:00
|
|
|
return {
|
|
|
|
output: {
|
|
|
|
element,
|
|
|
|
},
|
|
|
|
log: {
|
|
|
|
dump: insightDump,
|
|
|
|
},
|
2024-08-01 15:46:40 +08:00
|
|
|
cache: {
|
2024-09-10 14:29:01 +08:00
|
|
|
hit: Boolean(locateCache),
|
2024-08-01 15:46:40 +08:00
|
|
|
},
|
2024-07-23 16:25:11 +08:00
|
|
|
};
|
|
|
|
},
|
|
|
|
};
|
|
|
|
return taskFind;
|
2024-08-04 08:28:19 +08:00
|
|
|
}
|
2024-08-21 14:43:35 +08:00
|
|
|
if (plan.type === 'Assert' || plan.type === 'AssertWithoutThrow') {
|
2024-08-06 10:00:25 +08:00
|
|
|
const assertPlan = plan as PlanningAction<PlanningActionParamAssert>;
|
|
|
|
const taskAssert: ExecutionTaskApply = {
|
|
|
|
type: 'Insight',
|
|
|
|
subType: 'Assert',
|
|
|
|
param: assertPlan.param,
|
2024-08-10 07:57:15 +08:00
|
|
|
executor: async (param, taskContext) => {
|
|
|
|
const { task } = taskContext;
|
2024-08-06 10:00:25 +08:00
|
|
|
let insightDump: InsightDump | undefined;
|
|
|
|
const dumpCollector: DumpSubscriber = (dump) => {
|
|
|
|
insightDump = dump;
|
|
|
|
};
|
|
|
|
this.insight.onceDumpUpdatedFn = dumpCollector;
|
|
|
|
const assertion = await this.insight.assert(
|
|
|
|
assertPlan.param.assertion,
|
|
|
|
);
|
|
|
|
|
2024-08-10 07:57:15 +08:00
|
|
|
if (!assertion.pass) {
|
2024-08-21 14:43:35 +08:00
|
|
|
if (plan.type === 'Assert') {
|
|
|
|
task.output = assertion;
|
|
|
|
task.log = {
|
|
|
|
dump: insightDump,
|
|
|
|
};
|
|
|
|
throw new Error(
|
|
|
|
assertion.thought || 'Assertion failed without reason',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
task.error = assertion.thought;
|
2024-08-10 07:57:15 +08:00
|
|
|
}
|
|
|
|
|
2024-08-06 10:00:25 +08:00
|
|
|
return {
|
|
|
|
output: assertion,
|
|
|
|
log: {
|
|
|
|
dump: insightDump,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
},
|
|
|
|
};
|
|
|
|
return taskAssert;
|
|
|
|
}
|
2024-08-04 08:28:19 +08:00
|
|
|
if (plan.type === 'Input') {
|
|
|
|
const taskActionInput: ExecutionTaskActionApply<PlanningActionParamInputOrKeyPress> =
|
|
|
|
{
|
|
|
|
type: 'Action',
|
|
|
|
subType: 'Input',
|
|
|
|
param: plan.param,
|
|
|
|
executor: async (taskParam, { element }) => {
|
|
|
|
if (element) {
|
2024-09-05 20:05:19 +08:00
|
|
|
await this.page.clearInput(element as ElementInfo);
|
|
|
|
|
|
|
|
if (taskParam.value === '') {
|
|
|
|
return;
|
2024-08-28 19:31:59 +08:00
|
|
|
}
|
2024-09-05 20:05:19 +08:00
|
|
|
|
|
|
|
await this.page.keyboard.type(taskParam.value);
|
2024-08-04 08:28:19 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
};
|
2024-07-23 16:25:11 +08:00
|
|
|
return taskActionInput;
|
2024-08-04 08:28:19 +08:00
|
|
|
}
|
|
|
|
if (plan.type === 'KeyboardPress') {
|
|
|
|
const taskActionKeyboardPress: ExecutionTaskActionApply<PlanningActionParamInputOrKeyPress> =
|
|
|
|
{
|
|
|
|
type: 'Action',
|
|
|
|
subType: 'KeyboardPress',
|
|
|
|
param: plan.param,
|
|
|
|
executor: async (taskParam) => {
|
|
|
|
assert(taskParam.value, 'No key to press');
|
|
|
|
await this.page.keyboard.press(taskParam.value as KeyInput);
|
|
|
|
},
|
|
|
|
};
|
2024-07-23 16:25:11 +08:00
|
|
|
return taskActionKeyboardPress;
|
2024-08-04 08:28:19 +08:00
|
|
|
}
|
|
|
|
if (plan.type === 'Tap') {
|
|
|
|
const taskActionTap: ExecutionTaskActionApply<PlanningActionParamTap> =
|
|
|
|
{
|
|
|
|
type: 'Action',
|
|
|
|
subType: 'Tap',
|
|
|
|
executor: async (param, { element }) => {
|
|
|
|
assert(element, 'Element not found, cannot tap');
|
|
|
|
await this.page.mouse.click(
|
|
|
|
element.center[0],
|
|
|
|
element.center[1],
|
|
|
|
);
|
|
|
|
},
|
|
|
|
};
|
2024-07-23 16:25:11 +08:00
|
|
|
return taskActionTap;
|
2024-08-04 08:28:19 +08:00
|
|
|
}
|
|
|
|
if (plan.type === 'Hover') {
|
|
|
|
const taskActionHover: ExecutionTaskActionApply<PlanningActionParamHover> =
|
|
|
|
{
|
|
|
|
type: 'Action',
|
|
|
|
subType: 'Hover',
|
|
|
|
executor: async (param, { element }) => {
|
|
|
|
assert(element, 'Element not found, cannot hover');
|
|
|
|
await this.page.mouse.move(
|
|
|
|
element.center[0],
|
|
|
|
element.center[1],
|
|
|
|
);
|
|
|
|
},
|
|
|
|
};
|
2024-07-23 16:25:11 +08:00
|
|
|
return taskActionHover;
|
2024-08-04 08:28:19 +08:00
|
|
|
}
|
|
|
|
if (plan.type === 'Scroll') {
|
|
|
|
const taskActionScroll: ExecutionTaskActionApply<PlanningActionParamScroll> =
|
|
|
|
{
|
|
|
|
type: 'Action',
|
|
|
|
subType: 'Scroll',
|
|
|
|
param: plan.param,
|
|
|
|
executor: async (taskParam) => {
|
|
|
|
const scrollToEventName = taskParam.scrollType;
|
2024-07-23 16:25:11 +08:00
|
|
|
|
2024-08-04 08:28:19 +08:00
|
|
|
switch (scrollToEventName) {
|
2024-09-05 20:05:19 +08:00
|
|
|
case 'scrollUntilTop':
|
|
|
|
await this.page.scrollUntilTop();
|
2024-08-04 08:28:19 +08:00
|
|
|
break;
|
2024-09-05 20:05:19 +08:00
|
|
|
case 'scrollUntilBottom':
|
|
|
|
await this.page.scrollUntilBottom();
|
2024-08-04 08:28:19 +08:00
|
|
|
break;
|
2024-09-05 20:05:19 +08:00
|
|
|
case 'scrollUpOneScreen':
|
|
|
|
await this.page.scrollUpOneScreen();
|
2024-08-04 08:28:19 +08:00
|
|
|
break;
|
2024-09-05 20:05:19 +08:00
|
|
|
case 'scrollDownOneScreen':
|
|
|
|
await this.page.scrollDownOneScreen();
|
2024-08-04 08:28:19 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
console.error(
|
|
|
|
'Unknown scroll event type:',
|
|
|
|
scrollToEventName,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
};
|
2024-07-23 16:25:11 +08:00
|
|
|
return taskActionScroll;
|
2024-08-04 08:28:19 +08:00
|
|
|
}
|
2024-08-07 20:03:13 +08:00
|
|
|
if (plan.type === 'Sleep') {
|
|
|
|
const taskActionSleep: ExecutionTaskActionApply<PlanningActionParamSleep> =
|
|
|
|
{
|
|
|
|
type: 'Action',
|
|
|
|
subType: 'Sleep',
|
|
|
|
param: plan.param,
|
|
|
|
executor: async (taskParam) => {
|
2024-08-28 19:21:32 +08:00
|
|
|
await sleep(taskParam.timeMs || 3000);
|
2024-08-07 20:03:13 +08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
return taskActionSleep;
|
|
|
|
}
|
2024-08-04 08:28:19 +08:00
|
|
|
if (plan.type === 'Error') {
|
2024-08-21 14:43:35 +08:00
|
|
|
const taskActionError: ExecutionTaskActionApply<PlanningActionParamError> =
|
|
|
|
{
|
|
|
|
type: 'Action',
|
|
|
|
subType: 'Error',
|
|
|
|
param: plan.param,
|
|
|
|
executor: async (taskParam) => {
|
|
|
|
assert(
|
|
|
|
taskParam.thought,
|
|
|
|
'An error occurred, but no thought provided',
|
|
|
|
);
|
|
|
|
throw new Error(taskParam.thought);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
return taskActionError;
|
2024-07-23 16:25:11 +08:00
|
|
|
}
|
2024-08-21 14:43:35 +08:00
|
|
|
|
2024-08-04 08:28:19 +08:00
|
|
|
throw new Error(`Unknown or Unsupported task type: ${plan.type}`);
|
2024-07-23 16:25:11 +08:00
|
|
|
})
|
|
|
|
.map((task: ExecutionTaskApply) => {
|
|
|
|
return this.wrapExecutorWithScreenshot(task);
|
|
|
|
});
|
|
|
|
|
|
|
|
return tasks;
|
|
|
|
}
|
|
|
|
|
2024-08-04 08:28:19 +08:00
|
|
|
async action(
|
|
|
|
userPrompt: string /* , actionInfo?: { actionType?: EventActions[number]['action'] } */,
|
2024-08-10 07:57:15 +08:00
|
|
|
): Promise<ExecutionResult> {
|
2024-08-01 15:46:40 +08:00
|
|
|
const taskExecutor = new Executor(userPrompt);
|
2024-09-06 17:19:35 +08:00
|
|
|
const cacheGroup = this.taskCache.getCacheGroupByPrompt(userPrompt);
|
2024-07-23 16:25:11 +08:00
|
|
|
let plans: PlanningAction[] = [];
|
|
|
|
const planningTask: ExecutionTaskPlanningApply = {
|
|
|
|
type: 'Planning',
|
|
|
|
param: {
|
|
|
|
userPrompt,
|
|
|
|
},
|
2024-08-01 15:46:40 +08:00
|
|
|
executor: async (param) => {
|
|
|
|
const pageContext = await this.insight.contextRetrieverFn();
|
|
|
|
let planResult: { plans: PlanningAction[] };
|
2024-09-06 17:19:35 +08:00
|
|
|
const planCache = cacheGroup.readCache(pageContext, 'plan', userPrompt);
|
2024-08-01 15:46:40 +08:00
|
|
|
if (planCache) {
|
|
|
|
planResult = planCache;
|
|
|
|
} else {
|
|
|
|
planResult = await plan(param.userPrompt, {
|
|
|
|
context: pageContext,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-07-23 16:25:11 +08:00
|
|
|
assert(planResult.plans.length > 0, 'No plans found');
|
|
|
|
// eslint-disable-next-line prefer-destructuring
|
|
|
|
plans = planResult.plans;
|
2024-08-01 15:46:40 +08:00
|
|
|
|
2024-09-06 17:19:35 +08:00
|
|
|
cacheGroup.saveCache({
|
2024-08-01 15:46:40 +08:00
|
|
|
type: 'plan',
|
|
|
|
pageContext: {
|
|
|
|
url: pageContext.url,
|
|
|
|
size: pageContext.size,
|
|
|
|
},
|
|
|
|
prompt: userPrompt,
|
|
|
|
response: planResult,
|
|
|
|
});
|
2024-07-23 16:25:11 +08:00
|
|
|
return {
|
|
|
|
output: planResult,
|
2024-08-01 15:46:40 +08:00
|
|
|
cache: {
|
2024-08-02 13:58:15 +08:00
|
|
|
hit: Boolean(planCache),
|
2024-08-01 15:46:40 +08:00
|
|
|
},
|
2024-07-23 16:25:11 +08:00
|
|
|
};
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2024-08-10 07:57:15 +08:00
|
|
|
// plan
|
|
|
|
await taskExecutor.append(this.wrapExecutorWithScreenshot(planningTask));
|
|
|
|
let output = await taskExecutor.flush();
|
|
|
|
if (taskExecutor.isInErrorState()) {
|
|
|
|
return {
|
|
|
|
output,
|
|
|
|
executor: taskExecutor,
|
|
|
|
};
|
|
|
|
}
|
2024-07-23 16:25:11 +08:00
|
|
|
|
2024-08-10 07:57:15 +08:00
|
|
|
// append tasks
|
2024-09-06 17:19:35 +08:00
|
|
|
const executables = await this.convertPlanToExecutable(plans, cacheGroup);
|
2024-08-10 07:57:15 +08:00
|
|
|
await taskExecutor.append(executables);
|
2024-07-23 16:25:11 +08:00
|
|
|
|
2024-08-10 07:57:15 +08:00
|
|
|
// flush actions
|
|
|
|
output = await taskExecutor.flush();
|
|
|
|
return {
|
|
|
|
output,
|
|
|
|
executor: taskExecutor,
|
|
|
|
};
|
2024-07-23 16:25:11 +08:00
|
|
|
}
|
2024-07-25 10:47:02 +08:00
|
|
|
|
2024-08-10 07:57:15 +08:00
|
|
|
async query(demand: InsightExtractParam): Promise<ExecutionResult> {
|
|
|
|
const description =
|
|
|
|
typeof demand === 'string' ? demand : JSON.stringify(demand);
|
2024-08-01 15:46:40 +08:00
|
|
|
const taskExecutor = new Executor(description);
|
2024-07-25 10:47:02 +08:00
|
|
|
const queryTask: ExecutionTaskInsightQueryApply = {
|
|
|
|
type: 'Insight',
|
2024-07-25 13:40:46 +08:00
|
|
|
subType: 'Query',
|
2024-07-25 10:47:02 +08:00
|
|
|
param: {
|
|
|
|
dataDemand: demand,
|
|
|
|
},
|
|
|
|
executor: async (param) => {
|
|
|
|
let insightDump: InsightDump | undefined;
|
|
|
|
const dumpCollector: DumpSubscriber = (dump) => {
|
|
|
|
insightDump = dump;
|
|
|
|
};
|
|
|
|
this.insight.onceDumpUpdatedFn = dumpCollector;
|
2024-08-10 07:57:15 +08:00
|
|
|
const data = await this.insight.extract<any>(param.dataDemand);
|
2024-07-25 10:47:02 +08:00
|
|
|
return {
|
|
|
|
output: data,
|
|
|
|
log: { dump: insightDump },
|
|
|
|
};
|
|
|
|
},
|
|
|
|
};
|
2024-08-10 07:57:15 +08:00
|
|
|
|
|
|
|
await taskExecutor.append(this.wrapExecutorWithScreenshot(queryTask));
|
|
|
|
const output = await taskExecutor.flush();
|
|
|
|
return {
|
|
|
|
output,
|
|
|
|
executor: taskExecutor,
|
|
|
|
};
|
2024-07-25 10:47:02 +08:00
|
|
|
}
|
2024-08-06 10:00:25 +08:00
|
|
|
|
2024-08-10 07:57:15 +08:00
|
|
|
async assert(
|
|
|
|
assertion: string,
|
|
|
|
): Promise<ExecutionResult<InsightAssertionResponse>> {
|
2024-08-21 14:43:35 +08:00
|
|
|
const description = `assert: ${assertion}`;
|
2024-08-06 10:00:25 +08:00
|
|
|
const taskExecutor = new Executor(description);
|
|
|
|
const assertionPlan: PlanningAction<PlanningActionParamAssert> = {
|
|
|
|
type: 'Assert',
|
|
|
|
param: {
|
|
|
|
assertion,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
const assertTask = await this.convertPlanToExecutable([assertionPlan]);
|
|
|
|
|
|
|
|
await taskExecutor.append(this.wrapExecutorWithScreenshot(assertTask[0]));
|
2024-08-10 07:57:15 +08:00
|
|
|
const output: InsightAssertionResponse = await taskExecutor.flush();
|
2024-08-06 10:00:25 +08:00
|
|
|
|
2024-08-10 07:57:15 +08:00
|
|
|
return {
|
|
|
|
output,
|
|
|
|
executor: taskExecutor,
|
|
|
|
};
|
2024-08-06 10:00:25 +08:00
|
|
|
}
|
2024-08-21 14:43:35 +08:00
|
|
|
|
|
|
|
async waitFor(
|
|
|
|
assertion: string,
|
|
|
|
opt: PlanningActionParamWaitFor,
|
|
|
|
): Promise<ExecutionResult<void>> {
|
|
|
|
const description = `waitFor: ${assertion}`;
|
|
|
|
const taskExecutor = new Executor(description);
|
|
|
|
const { timeoutMs, checkIntervalMs } = opt;
|
|
|
|
|
|
|
|
assert(assertion, 'No assertion for waitFor');
|
|
|
|
assert(timeoutMs, 'No timeoutMs for waitFor');
|
|
|
|
assert(checkIntervalMs, 'No checkIntervalMs for waitFor');
|
|
|
|
|
|
|
|
const overallStartTime = Date.now();
|
|
|
|
let startTime = Date.now();
|
|
|
|
let errorThought = '';
|
|
|
|
while (Date.now() - overallStartTime < timeoutMs) {
|
|
|
|
startTime = Date.now();
|
|
|
|
const assertPlan: PlanningAction<PlanningActionParamAssert> = {
|
|
|
|
type: 'AssertWithoutThrow',
|
|
|
|
param: {
|
|
|
|
assertion,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
const assertTask = await this.convertPlanToExecutable([assertPlan]);
|
|
|
|
await taskExecutor.append(this.wrapExecutorWithScreenshot(assertTask[0]));
|
|
|
|
const output: InsightAssertionResponse = await taskExecutor.flush();
|
|
|
|
|
2024-10-12 12:09:25 +08:00
|
|
|
if (output?.pass) {
|
2024-08-21 14:43:35 +08:00
|
|
|
return {
|
|
|
|
output: undefined,
|
|
|
|
executor: taskExecutor,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-10-12 12:09:25 +08:00
|
|
|
errorThought = output?.thought || 'unknown error';
|
2024-08-21 14:43:35 +08:00
|
|
|
const now = Date.now();
|
|
|
|
if (now - startTime < checkIntervalMs) {
|
|
|
|
const timeRemaining = checkIntervalMs - (now - startTime);
|
|
|
|
const sleepPlan: PlanningAction<PlanningActionParamSleep> = {
|
|
|
|
type: 'Sleep',
|
|
|
|
param: {
|
|
|
|
timeMs: timeRemaining,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
const sleepTask = await this.convertPlanToExecutable([sleepPlan]);
|
|
|
|
await taskExecutor.append(
|
|
|
|
this.wrapExecutorWithScreenshot(sleepTask[0]),
|
|
|
|
);
|
|
|
|
await taskExecutor.flush();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// throw an error using taskExecutor
|
|
|
|
const errorPlan: PlanningAction<PlanningActionParamError> = {
|
|
|
|
type: 'Error',
|
|
|
|
param: {
|
|
|
|
thought: `waitFor timeout: ${errorThought}`,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
const errorTask = await this.convertPlanToExecutable([errorPlan]);
|
|
|
|
await taskExecutor.append(errorTask[0]);
|
|
|
|
await taskExecutor.flush();
|
|
|
|
return {
|
|
|
|
output: undefined,
|
|
|
|
executor: taskExecutor,
|
|
|
|
};
|
|
|
|
}
|
2024-07-23 16:25:11 +08:00
|
|
|
}
|