@midscene/playground 1.9.6 → 1.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/adapters/local-execution.mjs +3 -3
- package/dist/es/adapters/local-execution.mjs.map +1 -1
- package/dist/es/adapters/remote-execution.mjs +35 -0
- package/dist/es/adapters/remote-execution.mjs.map +1 -1
- package/dist/es/common.mjs +30 -3
- package/dist/es/common.mjs.map +1 -1
- package/dist/es/index.browser.mjs.map +1 -1
- package/dist/es/mjpeg-hub.mjs +3 -0
- package/dist/es/mjpeg-hub.mjs.map +1 -1
- package/dist/es/mjpeg-stream-handler.mjs +6 -0
- package/dist/es/mjpeg-stream-handler.mjs.map +1 -1
- package/dist/es/platform.mjs.map +1 -1
- package/dist/es/sdk/index.mjs +7 -0
- package/dist/es/sdk/index.mjs.map +1 -1
- package/dist/es/server.mjs +563 -42
- package/dist/es/server.mjs.map +1 -1
- package/dist/lib/adapters/local-execution.js +3 -3
- package/dist/lib/adapters/local-execution.js.map +1 -1
- package/dist/lib/adapters/remote-execution.js +35 -0
- package/dist/lib/adapters/remote-execution.js.map +1 -1
- package/dist/lib/common.js +30 -3
- package/dist/lib/common.js.map +1 -1
- package/dist/lib/index.browser.js.map +1 -1
- package/dist/lib/mjpeg-hub.js +3 -0
- package/dist/lib/mjpeg-hub.js.map +1 -1
- package/dist/lib/mjpeg-stream-handler.js +6 -0
- package/dist/lib/mjpeg-stream-handler.js.map +1 -1
- package/dist/lib/platform.js.map +1 -1
- package/dist/lib/sdk/index.js +7 -0
- package/dist/lib/sdk/index.js.map +1 -1
- package/dist/lib/server.js +560 -39
- package/dist/lib/server.js.map +1 -1
- package/dist/types/adapters/local-execution.d.ts +3 -2
- package/dist/types/adapters/remote-execution.d.ts +2 -1
- package/dist/types/index.browser.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/mjpeg-hub.d.ts +1 -0
- package/dist/types/mjpeg-stream-handler.d.ts +2 -0
- package/dist/types/platform.d.ts +104 -0
- package/dist/types/sdk/index.d.ts +3 -2
- package/dist/types/server.d.ts +9 -0
- package/dist/types/types.d.ts +5 -0
- package/package.json +3 -3
- package/static/index.html +1 -1
- package/static/static/css/{index.ab28104a.css → index.1293237f.css} +2 -2
- package/static/static/css/{index.ab28104a.css.map → index.1293237f.css.map} +1 -1
- package/static/static/js/{596.5426be9e.js → 905.8d12588e.js} +17 -17
- package/static/static/js/905.8d12588e.js.map +1 -0
- package/static/static/js/index.50e06ed5.js +948 -0
- package/static/static/js/index.50e06ed5.js.map +1 -0
- package/static/static/js/596.5426be9e.js.map +0 -1
- package/static/static/js/index.134d5099.js +0 -940
- package/static/static/js/index.134d5099.js.map +0 -1
- /package/static/static/js/{596.5426be9e.js.LICENSE.txt → 905.8d12588e.js.LICENSE.txt} +0 -0
- /package/static/static/js/{index.134d5099.js.LICENSE.txt → index.50e06ed5.js.LICENSE.txt} +0 -0
package/dist/es/server.mjs
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve as external_node_path_resolve, sep } from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { ReportActionDump, runConnectivityTest } from "@midscene/core";
|
|
5
6
|
import { getTmpDir, sleep } from "@midscene/core/utils";
|
|
7
|
+
import { getMidsceneRunSubDir } from "@midscene/shared/common";
|
|
6
8
|
import { PLAYGROUND_SERVER_PORT } from "@midscene/shared/constants";
|
|
7
9
|
import { globalModelConfigManager, overrideAIConfig } from "@midscene/shared/env";
|
|
8
10
|
import { generateElementByPoint } from "@midscene/shared/extractor";
|
|
9
|
-
import { compositePointMarkerImg } from "@midscene/shared/img";
|
|
11
|
+
import { annotateRects, compositePointMarkerImg, imageInfoOfBase64 } from "@midscene/shared/img";
|
|
10
12
|
import { getDebug } from "@midscene/shared/logger";
|
|
13
|
+
import { buildMidsceneRecorderActionSummary, buildMidsceneRecorderReplayInstruction } from "@midscene/shared/recorder";
|
|
11
14
|
import { uuid as utils_uuid } from "@midscene/shared/utils";
|
|
12
15
|
import express from "express";
|
|
13
16
|
import { executeAction, formatErrorMessage } from "./common.mjs";
|
|
@@ -27,6 +30,216 @@ function _define_property(obj, key, value) {
|
|
|
27
30
|
}
|
|
28
31
|
const defaultPort = PLAYGROUND_SERVER_PORT;
|
|
29
32
|
const RECORDER_CAPTURE_AFTER_INTERACT_DELAY_MS = 250;
|
|
33
|
+
const RECORDER_AI_DESCRIBE_AFTER_INTERACT_TIMEOUT_MS = 30000;
|
|
34
|
+
const RECORDER_AI_DESCRIBE_SCREENSHOT_DUMP_DIR = 'recorder-ai-describe-screenshots';
|
|
35
|
+
function extractBase64Payload(value) {
|
|
36
|
+
const dataUrlMatch = value.match(/^data:([^;,]+)?(?:;[^,]*)?,([\s\S]*)$/);
|
|
37
|
+
if (dataUrlMatch) return {
|
|
38
|
+
mimeType: dataUrlMatch[1],
|
|
39
|
+
base64: dataUrlMatch[2] || ''
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
base64: value
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function estimateBase64Bytes(value) {
|
|
46
|
+
if (!value) return;
|
|
47
|
+
const { base64 } = extractBase64Payload(value);
|
|
48
|
+
const padding = base64.endsWith('==') ? 2 : base64.endsWith('=') ? 1 : 0;
|
|
49
|
+
return Math.max(0, Math.floor(3 * base64.length / 4) - padding);
|
|
50
|
+
}
|
|
51
|
+
function buildRecorderRawPayloadSummary(rawPayload) {
|
|
52
|
+
if (!rawPayload) return;
|
|
53
|
+
const allowedKeys = [
|
|
54
|
+
'actionType',
|
|
55
|
+
'x',
|
|
56
|
+
'y',
|
|
57
|
+
'endX',
|
|
58
|
+
'endY',
|
|
59
|
+
'duration',
|
|
60
|
+
'direction',
|
|
61
|
+
'scrollType',
|
|
62
|
+
'distance',
|
|
63
|
+
'keyName',
|
|
64
|
+
'mode'
|
|
65
|
+
];
|
|
66
|
+
const summary = {};
|
|
67
|
+
for (const key of allowedKeys)if (void 0 !== rawPayload[key]) summary[key] = rawPayload[key];
|
|
68
|
+
const value = rawPayload.value;
|
|
69
|
+
if ('string' == typeof value) summary.valueLength = value.length;
|
|
70
|
+
return Object.keys(summary).length > 0 ? summary : void 0;
|
|
71
|
+
}
|
|
72
|
+
function buildRecorderEventSummary(event) {
|
|
73
|
+
return {
|
|
74
|
+
hashId: event.hashId,
|
|
75
|
+
mergedHashIds: event.mergedHashIds,
|
|
76
|
+
type: event.type,
|
|
77
|
+
source: event.source,
|
|
78
|
+
actionType: event.actionType,
|
|
79
|
+
timestamp: event.timestamp,
|
|
80
|
+
url: event.url,
|
|
81
|
+
title: event.title,
|
|
82
|
+
valueLength: 'string' == typeof event.value ? event.value.length : void 0,
|
|
83
|
+
rawPayloadSummary: buildRecorderRawPayloadSummary(event.rawPayload),
|
|
84
|
+
elementRect: event.elementRect,
|
|
85
|
+
pageInfo: event.pageInfo
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function shouldVerifyRecorderAiDescribeEvent(event) {
|
|
89
|
+
return 'scroll' !== event.type;
|
|
90
|
+
}
|
|
91
|
+
function formatRecorderAiDescribeScreenshotPathParts() {
|
|
92
|
+
const date = new Date();
|
|
93
|
+
const localDate = Number.isNaN(date.getTime()) ? new Date() : date;
|
|
94
|
+
const pad = (value, length = 2)=>String(value).padStart(length, '0');
|
|
95
|
+
const datePart = [
|
|
96
|
+
localDate.getFullYear(),
|
|
97
|
+
pad(localDate.getMonth() + 1),
|
|
98
|
+
pad(localDate.getDate())
|
|
99
|
+
].join('-');
|
|
100
|
+
const timePart = [
|
|
101
|
+
pad(localDate.getHours()),
|
|
102
|
+
pad(localDate.getMinutes()),
|
|
103
|
+
pad(localDate.getSeconds()),
|
|
104
|
+
pad(localDate.getMilliseconds(), 3)
|
|
105
|
+
].join('-');
|
|
106
|
+
const hourPart = timePart.slice(0, 2) || 'unknown-hour';
|
|
107
|
+
return {
|
|
108
|
+
datePart,
|
|
109
|
+
hourPart,
|
|
110
|
+
filePrefix: `${timePart}_${randomUUID()}`
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function assertPathInsideDirectory(rootDir, targetPath) {
|
|
114
|
+
const resolvedRoot = external_node_path_resolve(rootDir);
|
|
115
|
+
const resolvedTarget = external_node_path_resolve(targetPath);
|
|
116
|
+
if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(`${resolvedRoot}${sep}`)) throw new Error(`Refusing to write recorder dump outside ${resolvedRoot}`);
|
|
117
|
+
return resolvedTarget;
|
|
118
|
+
}
|
|
119
|
+
function writeRecorderAiDescribeScreenshot(imageBase64, suffix) {
|
|
120
|
+
const { base64, mimeType } = extractBase64Payload(imageBase64);
|
|
121
|
+
const bytes = Buffer.from(base64, 'base64');
|
|
122
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
123
|
+
const extension = mimeType?.includes('jpeg') ? 'jpg' : 'png';
|
|
124
|
+
const pathParts = formatRecorderAiDescribeScreenshotPathParts();
|
|
125
|
+
const dumpRoot = external_node_path_resolve(getMidsceneRunSubDir('dump'), RECORDER_AI_DESCRIBE_SCREENSHOT_DUMP_DIR);
|
|
126
|
+
const dumpDir = assertPathInsideDirectory(dumpRoot, join(dumpRoot, pathParts.datePart, pathParts.hourPart));
|
|
127
|
+
mkdirSync(dumpDir, {
|
|
128
|
+
recursive: true
|
|
129
|
+
});
|
|
130
|
+
const safeSuffix = 'annotated' === suffix ? 'annotated' : 'raw';
|
|
131
|
+
const fileName = `${pathParts.filePrefix}_${safeSuffix}.${extension}`;
|
|
132
|
+
const filePath = assertPathInsideDirectory(dumpRoot, join(dumpDir, fileName));
|
|
133
|
+
if (!existsSync(filePath)) writeFileSync(filePath, bytes);
|
|
134
|
+
return {
|
|
135
|
+
path: filePath,
|
|
136
|
+
sha256,
|
|
137
|
+
bytes: bytes.byteLength,
|
|
138
|
+
mimeType
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function calculateRecorderScreenshotAnnotation(event, imageSize, verifyResult) {
|
|
142
|
+
const pageWidth = event.pageInfo?.width;
|
|
143
|
+
const pageHeight = event.pageInfo?.height;
|
|
144
|
+
const scaleX = pageWidth ? imageSize.width / pageWidth : void 0;
|
|
145
|
+
const scaleY = pageHeight ? imageSize.height / pageHeight : void 0;
|
|
146
|
+
const mapX = (value)=>scaleX ? value * scaleX : value;
|
|
147
|
+
const mapY = (value)=>scaleY ? value * scaleY : value;
|
|
148
|
+
const logicalPoint = 'number' == typeof event.elementRect?.x && 'number' == typeof event.elementRect?.y ? [
|
|
149
|
+
event.elementRect.x,
|
|
150
|
+
event.elementRect.y
|
|
151
|
+
] : void 0;
|
|
152
|
+
const locateRect = verifyResult?.rect;
|
|
153
|
+
const sourceTargetRect = 'number' == typeof event.elementRect?.left && 'number' == typeof event.elementRect?.top && 'number' == typeof event.elementRect?.width && 'number' == typeof event.elementRect?.height && event.elementRect.width > 0 && event.elementRect.height > 0 ? {
|
|
154
|
+
left: mapX(event.elementRect.left),
|
|
155
|
+
top: mapY(event.elementRect.top),
|
|
156
|
+
width: mapX(event.elementRect.width),
|
|
157
|
+
height: mapY(event.elementRect.height)
|
|
158
|
+
} : void 0;
|
|
159
|
+
if (!logicalPoint && !sourceTargetRect && !locateRect) return;
|
|
160
|
+
const screenshotPoint = logicalPoint && scaleX && scaleY ? [
|
|
161
|
+
logicalPoint[0] * scaleX,
|
|
162
|
+
logicalPoint[1] * scaleY
|
|
163
|
+
] : void 0;
|
|
164
|
+
const pointTargetRect = screenshotPoint && !sourceTargetRect ? {
|
|
165
|
+
left: Math.max(0, screenshotPoint[0] - 6),
|
|
166
|
+
top: Math.max(0, screenshotPoint[1] - 6),
|
|
167
|
+
width: 12,
|
|
168
|
+
height: 12
|
|
169
|
+
} : void 0;
|
|
170
|
+
const center = verifyResult?.center || (locateRect ? [
|
|
171
|
+
locateRect.left + locateRect.width / 2,
|
|
172
|
+
locateRect.top + locateRect.height / 2
|
|
173
|
+
] : void 0);
|
|
174
|
+
const centerDelta = screenshotPoint && center ? {
|
|
175
|
+
x: center[0] - screenshotPoint[0],
|
|
176
|
+
y: center[1] - screenshotPoint[1],
|
|
177
|
+
distance: Math.hypot(center[0] - screenshotPoint[0], center[1] - screenshotPoint[1])
|
|
178
|
+
} : void 0;
|
|
179
|
+
const distanceOutsideRect = screenshotPoint && locateRect ? (()=>{
|
|
180
|
+
const right = locateRect.left + locateRect.width;
|
|
181
|
+
const bottom = locateRect.top + locateRect.height;
|
|
182
|
+
const x = screenshotPoint[0] < locateRect.left ? locateRect.left - screenshotPoint[0] : screenshotPoint[0] > right ? screenshotPoint[0] - right : 0;
|
|
183
|
+
const y = screenshotPoint[1] < locateRect.top ? locateRect.top - screenshotPoint[1] : screenshotPoint[1] > bottom ? screenshotPoint[1] - bottom : 0;
|
|
184
|
+
return {
|
|
185
|
+
x,
|
|
186
|
+
y,
|
|
187
|
+
distance: Math.hypot(x, y)
|
|
188
|
+
};
|
|
189
|
+
})() : void 0;
|
|
190
|
+
return {
|
|
191
|
+
inputPoint: screenshotPoint ? {
|
|
192
|
+
logical: logicalPoint,
|
|
193
|
+
screenshot: screenshotPoint
|
|
194
|
+
} : void 0,
|
|
195
|
+
sourceTargetRect: sourceTargetRect || pointTargetRect,
|
|
196
|
+
locateRect,
|
|
197
|
+
centerDelta,
|
|
198
|
+
distanceOutsideRect
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
async function persistRecorderAiDescribeScreenshot(event, eventScreenshot, verifyResult) {
|
|
202
|
+
if (!eventScreenshot) return;
|
|
203
|
+
const screenshotRef = writeRecorderAiDescribeScreenshot(eventScreenshot, 'raw');
|
|
204
|
+
let annotatedScreenshotRef;
|
|
205
|
+
let screenshotAnnotation;
|
|
206
|
+
let annotatedScreenshotPersistError;
|
|
207
|
+
try {
|
|
208
|
+
const imageSize = await imageInfoOfBase64(eventScreenshot);
|
|
209
|
+
screenshotAnnotation = calculateRecorderScreenshotAnnotation(event, imageSize, verifyResult);
|
|
210
|
+
const annotatedRects = [
|
|
211
|
+
screenshotAnnotation?.sourceTargetRect,
|
|
212
|
+
screenshotAnnotation?.locateRect
|
|
213
|
+
].filter((rect)=>Boolean(rect));
|
|
214
|
+
if (annotatedRects.length > 0) {
|
|
215
|
+
const annotatedScreenshot = await annotateRects(eventScreenshot, annotatedRects);
|
|
216
|
+
annotatedScreenshotRef = writeRecorderAiDescribeScreenshot(annotatedScreenshot, 'annotated');
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
annotatedScreenshotPersistError = error instanceof Error ? error.message : String(error);
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
screenshotRef,
|
|
223
|
+
annotatedScreenshotRef,
|
|
224
|
+
screenshotAnnotation,
|
|
225
|
+
annotatedScreenshotPersistError
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function createRecorderAiDescribeTraceBase(event, eventScreenshot) {
|
|
229
|
+
return {
|
|
230
|
+
traceId: `${event.hashId || 'recorder-event'}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
|
|
231
|
+
eventHashId: event.hashId,
|
|
232
|
+
eventType: event.type,
|
|
233
|
+
actionType: event.actionType,
|
|
234
|
+
eventSummary: buildRecorderEventSummary(event),
|
|
235
|
+
point: 'number' == typeof event.elementRect?.x && 'number' == typeof event.elementRect?.y ? [
|
|
236
|
+
event.elementRect.x,
|
|
237
|
+
event.elementRect.y
|
|
238
|
+
] : void 0,
|
|
239
|
+
pageInfo: event.pageInfo,
|
|
240
|
+
screenshotBytes: estimateBase64Bytes(eventScreenshot)
|
|
241
|
+
};
|
|
242
|
+
}
|
|
30
243
|
function serializeAiConfigSignature(aiConfig) {
|
|
31
244
|
return JSON.stringify(Object.entries(aiConfig).sort(([leftKey], [rightKey])=>leftKey.localeCompare(rightKey)));
|
|
32
245
|
}
|
|
@@ -190,6 +403,74 @@ function buildInteractParams(actionType, body) {
|
|
|
190
403
|
const { actionType: _omit, ...passthrough } = body;
|
|
191
404
|
return passthrough;
|
|
192
405
|
}
|
|
406
|
+
function getRecorderSemanticActionType(actionType) {
|
|
407
|
+
switch(actionType){
|
|
408
|
+
case 'Tap':
|
|
409
|
+
case 'DoubleClick':
|
|
410
|
+
case 'LongPress':
|
|
411
|
+
case 'RightClick':
|
|
412
|
+
return 'click';
|
|
413
|
+
case 'Swipe':
|
|
414
|
+
case 'DragAndDrop':
|
|
415
|
+
return 'drag';
|
|
416
|
+
case 'Input':
|
|
417
|
+
return 'input';
|
|
418
|
+
case 'KeyboardPress':
|
|
419
|
+
return 'keydown';
|
|
420
|
+
case 'Scroll':
|
|
421
|
+
return 'scroll';
|
|
422
|
+
case 'GoBack':
|
|
423
|
+
case 'GoForward':
|
|
424
|
+
case 'Reload':
|
|
425
|
+
case 'Stop':
|
|
426
|
+
case 'InitialNavigation':
|
|
427
|
+
case 'NavigationChanged':
|
|
428
|
+
return 'navigation';
|
|
429
|
+
default:
|
|
430
|
+
return 'click';
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function buildRecorderSemanticAction(actionType, payload, url) {
|
|
434
|
+
return {
|
|
435
|
+
type: getRecorderSemanticActionType(actionType),
|
|
436
|
+
actionType,
|
|
437
|
+
value: 'string' == typeof payload.value ? payload.value : 'string' == typeof payload.keyName ? payload.keyName : void 0,
|
|
438
|
+
url
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
function buildReadyRecorderSemantic(source, event, elementDescription, confidence, extra) {
|
|
442
|
+
return {
|
|
443
|
+
source,
|
|
444
|
+
status: 'ready',
|
|
445
|
+
elementDescription,
|
|
446
|
+
replayInstruction: buildMidsceneRecorderReplayInstruction(event, elementDescription),
|
|
447
|
+
actionSummary: buildMidsceneRecorderActionSummary(event, elementDescription),
|
|
448
|
+
confidence,
|
|
449
|
+
...extra
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
function buildFailedAiDescribeRecorderSemantic(error, extra) {
|
|
453
|
+
return {
|
|
454
|
+
source: 'aiDescribe',
|
|
455
|
+
status: 'failed',
|
|
456
|
+
error: error instanceof Error ? error.message : String(error),
|
|
457
|
+
...extra
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function withTimeout(promise, timeoutMs, message) {
|
|
461
|
+
let timeout;
|
|
462
|
+
const timeoutPromise = new Promise((_resolve, reject)=>{
|
|
463
|
+
timeout = setTimeout(()=>{
|
|
464
|
+
reject(new Error(message));
|
|
465
|
+
}, timeoutMs);
|
|
466
|
+
});
|
|
467
|
+
return Promise.race([
|
|
468
|
+
promise,
|
|
469
|
+
timeoutPromise
|
|
470
|
+
]).finally(()=>{
|
|
471
|
+
if (timeout) clearTimeout(timeout);
|
|
472
|
+
});
|
|
473
|
+
}
|
|
193
474
|
function createManualExecutorContext(actionType, param) {
|
|
194
475
|
const task = {
|
|
195
476
|
type: 'Action Space',
|
|
@@ -393,9 +674,14 @@ class PlaygroundServer {
|
|
|
393
674
|
resetRecorderState() {
|
|
394
675
|
this._recorderSessionId = null;
|
|
395
676
|
this._recorderEvents = [];
|
|
677
|
+
this._recorderEventQueue = Promise.resolve();
|
|
678
|
+
this._studioPreviewRecorderLastTargetPoint = void 0;
|
|
396
679
|
this._studioPreviewRecorderLastScreenshot = void 0;
|
|
397
680
|
this._studioPreviewRecorderLastPageState = void 0;
|
|
398
681
|
}
|
|
682
|
+
async waitForRecorderIdle() {
|
|
683
|
+
await this._recorderEventQueue;
|
|
684
|
+
}
|
|
399
685
|
canRecordStudioPreviewInteractions() {
|
|
400
686
|
const agent = this._activeConnection.agent;
|
|
401
687
|
if (!agent) return false;
|
|
@@ -479,6 +765,13 @@ class PlaygroundServer {
|
|
|
479
765
|
pageState: this._studioPreviewRecorderLastPageState || await this.getActiveRecorderPageState()
|
|
480
766
|
};
|
|
481
767
|
}
|
|
768
|
+
captureCachedRecorderSnapshotBeforeInteract() {
|
|
769
|
+
if (!this._recorderSessionId || !this._studioPreviewRecorderLastPageState || this._recorderPendingCaptures > 0) return;
|
|
770
|
+
return {
|
|
771
|
+
screenshot: this._mjpegHandler.getLastFrameBase64() || this._studioPreviewRecorderLastScreenshot,
|
|
772
|
+
pageState: this._studioPreviewRecorderLastPageState
|
|
773
|
+
};
|
|
774
|
+
}
|
|
482
775
|
async startStudioPreviewRecorder(sessionId) {
|
|
483
776
|
this._recorderSessionId = sessionId;
|
|
484
777
|
this._studioPreviewRecorderLastScreenshot = await this.takeRecorderScreenshot();
|
|
@@ -487,7 +780,7 @@ class PlaygroundServer {
|
|
|
487
780
|
const initialNavigationEvent = this.buildStudioPreviewInitialNavigationEvent(initialPageState, this._studioPreviewRecorderLastScreenshot);
|
|
488
781
|
if (initialNavigationEvent) this._recorderEvents.push(initialNavigationEvent);
|
|
489
782
|
}
|
|
490
|
-
async storeStudioPreviewRecorderEvent(payload, snapshotBefore) {
|
|
783
|
+
async storeStudioPreviewRecorderEvent(payload, snapshotBefore, agent) {
|
|
491
784
|
if (!this._recorderSessionId) return;
|
|
492
785
|
const before = snapshotBefore || await this.captureRecorderSnapshotBeforeInteract();
|
|
493
786
|
const screenshotBefore = before?.screenshot;
|
|
@@ -512,19 +805,26 @@ class PlaygroundServer {
|
|
|
512
805
|
this._studioPreviewRecorderLastPageState = pageStateAfter;
|
|
513
806
|
return;
|
|
514
807
|
}
|
|
515
|
-
this._recorderEvents.push(event);
|
|
516
808
|
const navigationEvent = this.buildStudioPreviewNavigationChangeEvent(payload, before?.pageState, pageStateAfter, screenshotAfter);
|
|
517
|
-
if (navigationEvent) this._recorderEvents.push(navigationEvent);
|
|
518
809
|
this._studioPreviewRecorderLastScreenshot = screenshotAfter;
|
|
519
810
|
this._studioPreviewRecorderLastPageState = pageStateAfter;
|
|
811
|
+
this.queueStudioPreviewRecorderEventAppend(event, navigationEvent);
|
|
520
812
|
}
|
|
521
813
|
async buildStudioPreviewRecorderEvent(payload, pageState, screenshotBefore, screenshotAfter) {
|
|
522
814
|
const actionType = 'string' == typeof payload.actionType ? payload.actionType : void 0;
|
|
523
815
|
if (!actionType) return null;
|
|
524
816
|
const { pageInfo, url, title } = pageState;
|
|
525
817
|
const timestamp = Date.now();
|
|
526
|
-
const
|
|
527
|
-
const
|
|
818
|
+
const payloadX = 'number' == typeof payload.x ? payload.x : void 0;
|
|
819
|
+
const payloadY = 'number' == typeof payload.y ? payload.y : void 0;
|
|
820
|
+
const canReuseLastTargetPoint = 'Input' === actionType || 'KeyboardPress' === actionType;
|
|
821
|
+
const inheritedTargetPoint = canReuseLastTargetPoint && (void 0 === payloadX || void 0 === payloadY) ? this._studioPreviewRecorderLastTargetPoint : void 0;
|
|
822
|
+
const x = payloadX ?? inheritedTargetPoint?.x;
|
|
823
|
+
const y = payloadY ?? inheritedTargetPoint?.y;
|
|
824
|
+
if (void 0 !== payloadX && void 0 !== payloadY) this._studioPreviewRecorderLastTargetPoint = {
|
|
825
|
+
x: payloadX,
|
|
826
|
+
y: payloadY
|
|
827
|
+
};
|
|
528
828
|
const endX = 'number' == typeof payload.endX ? payload.endX : void 0;
|
|
529
829
|
const endY = 'number' == typeof payload.endY ? payload.endY : void 0;
|
|
530
830
|
const dragDescription = void 0 !== x && void 0 !== y && void 0 !== endX && void 0 !== endY ? `${Math.round(x)},${Math.round(y)} -> ${Math.round(endX)},${Math.round(endY)}` : void 0;
|
|
@@ -542,8 +842,10 @@ class PlaygroundServer {
|
|
|
542
842
|
screenshotBefore,
|
|
543
843
|
screenshotAfter,
|
|
544
844
|
screenshotWithBox: screenshotWithMarker,
|
|
545
|
-
|
|
546
|
-
|
|
845
|
+
semantic: {
|
|
846
|
+
source: 'aiDescribe',
|
|
847
|
+
status: 'pending'
|
|
848
|
+
},
|
|
547
849
|
timestamp,
|
|
548
850
|
hashId: `studio-preview-${actionType}-${timestamp}-${Math.random().toString(36).slice(2, 8)}`
|
|
549
851
|
};
|
|
@@ -621,17 +923,18 @@ class PlaygroundServer {
|
|
|
621
923
|
case 'GoForward':
|
|
622
924
|
case 'Reload':
|
|
623
925
|
case 'Stop':
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
926
|
+
{
|
|
927
|
+
const elementDescription = url || actionType;
|
|
928
|
+
const semanticEvent = buildRecorderSemanticAction(actionType, {
|
|
929
|
+
value: actionType
|
|
930
|
+
}, url);
|
|
931
|
+
return {
|
|
932
|
+
...base,
|
|
933
|
+
type: 'navigation',
|
|
934
|
+
value: actionType,
|
|
935
|
+
semantic: buildReadyRecorderSemantic('heuristic', semanticEvent, elementDescription, url ? 'high' : 'medium')
|
|
936
|
+
};
|
|
937
|
+
}
|
|
635
938
|
default:
|
|
636
939
|
return {
|
|
637
940
|
...base,
|
|
@@ -640,6 +943,190 @@ class PlaygroundServer {
|
|
|
640
943
|
};
|
|
641
944
|
}
|
|
642
945
|
}
|
|
946
|
+
async enrichStudioPreviewRecorderEventWithAiDescribe(event, agent) {
|
|
947
|
+
const startedAt = new Date();
|
|
948
|
+
const startedAtMs = Date.now();
|
|
949
|
+
const eventScreenshot = this.getRecorderAiDescribeScreenshot(event);
|
|
950
|
+
const traceBase = createRecorderAiDescribeTraceBase(event, eventScreenshot);
|
|
951
|
+
const finishTrace = async (status, extra = {})=>{
|
|
952
|
+
let screenshotRef;
|
|
953
|
+
let annotatedScreenshotRef;
|
|
954
|
+
let screenshotAnnotation;
|
|
955
|
+
let screenshotPersistError;
|
|
956
|
+
let annotatedScreenshotPersistError;
|
|
957
|
+
if ('failed' === status) try {
|
|
958
|
+
const screenshotDump = await persistRecorderAiDescribeScreenshot(event, eventScreenshot, extra.verifyResult);
|
|
959
|
+
screenshotRef = screenshotDump?.screenshotRef;
|
|
960
|
+
annotatedScreenshotRef = screenshotDump?.annotatedScreenshotRef;
|
|
961
|
+
screenshotAnnotation = screenshotDump?.screenshotAnnotation;
|
|
962
|
+
annotatedScreenshotPersistError = screenshotDump?.annotatedScreenshotPersistError;
|
|
963
|
+
} catch (error) {
|
|
964
|
+
screenshotPersistError = error instanceof Error ? error.message : String(error);
|
|
965
|
+
}
|
|
966
|
+
return {
|
|
967
|
+
...traceBase,
|
|
968
|
+
...extra,
|
|
969
|
+
screenshotRef,
|
|
970
|
+
annotatedScreenshotRef,
|
|
971
|
+
screenshotAnnotation,
|
|
972
|
+
screenshotPersistError,
|
|
973
|
+
annotatedScreenshotPersistError,
|
|
974
|
+
status,
|
|
975
|
+
startedAt: startedAt.toISOString(),
|
|
976
|
+
durationMs: Date.now() - startedAtMs
|
|
977
|
+
};
|
|
978
|
+
};
|
|
979
|
+
if ('navigation' === event.type || 'setViewport' === event.type) {
|
|
980
|
+
const error = 'aiDescribe skipped because the event type does not target a UI element.';
|
|
981
|
+
const trace = await finishTrace('failed', {
|
|
982
|
+
error
|
|
983
|
+
});
|
|
984
|
+
debugInteract('recorder aiDescribe trace:', trace);
|
|
985
|
+
return {
|
|
986
|
+
event: {
|
|
987
|
+
...event,
|
|
988
|
+
semantic: buildFailedAiDescribeRecorderSemantic(error)
|
|
989
|
+
},
|
|
990
|
+
trace
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
if ('function' != typeof agent?.describeElementAtPoint) {
|
|
994
|
+
const error = 'Active agent does not support describeElementAtPoint.';
|
|
995
|
+
const trace = await finishTrace('failed', {
|
|
996
|
+
error
|
|
997
|
+
});
|
|
998
|
+
debugInteract('recorder aiDescribe trace:', trace);
|
|
999
|
+
return {
|
|
1000
|
+
event: {
|
|
1001
|
+
...event,
|
|
1002
|
+
semantic: buildFailedAiDescribeRecorderSemantic(error)
|
|
1003
|
+
},
|
|
1004
|
+
trace
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
const x = event.elementRect?.x;
|
|
1008
|
+
const y = event.elementRect?.y;
|
|
1009
|
+
let modelCallDurationMs;
|
|
1010
|
+
let modelCallStartedAt;
|
|
1011
|
+
let elementDescription;
|
|
1012
|
+
let deepLocate;
|
|
1013
|
+
let verifyResult;
|
|
1014
|
+
try {
|
|
1015
|
+
if ('number' != typeof x || 'number' != typeof y) throw new Error('Skipped aiDescribe because the recorder event has no stable point target.');
|
|
1016
|
+
if (!eventScreenshot) throw new Error('Skipped aiDescribe because the recorder event has no screenshot.');
|
|
1017
|
+
if (!event.pageInfo?.width || !event.pageInfo?.height) throw new Error('Skipped aiDescribe because the recorder event has no pageInfo for coordinate mapping.');
|
|
1018
|
+
const verifyPrompt = shouldVerifyRecorderAiDescribeEvent(event);
|
|
1019
|
+
modelCallStartedAt = Date.now();
|
|
1020
|
+
const describeResult = await withTimeout(agent.describeElementAtPoint([
|
|
1021
|
+
x,
|
|
1022
|
+
y
|
|
1023
|
+
], {
|
|
1024
|
+
verifyPrompt,
|
|
1025
|
+
screenshotBase64: eventScreenshot,
|
|
1026
|
+
coordinateSpace: 'logical',
|
|
1027
|
+
logicalSize: event.pageInfo,
|
|
1028
|
+
onProgress: (progress)=>{
|
|
1029
|
+
elementDescription = progress.prompt?.trim() || elementDescription;
|
|
1030
|
+
deepLocate = progress.deepLocate;
|
|
1031
|
+
verifyResult = verifyPrompt ? progress.verifyResult : void 0;
|
|
1032
|
+
}
|
|
1033
|
+
}), RECORDER_AI_DESCRIBE_AFTER_INTERACT_TIMEOUT_MS, 'Timed out while analyzing recorder event with aiDescribe.');
|
|
1034
|
+
modelCallDurationMs = Date.now() - modelCallStartedAt;
|
|
1035
|
+
elementDescription = describeResult.prompt?.trim();
|
|
1036
|
+
deepLocate = describeResult.deepLocate;
|
|
1037
|
+
verifyResult = verifyPrompt ? describeResult.verifyResult : void 0;
|
|
1038
|
+
if (!elementDescription) throw new Error("aiDescribe returned an empty element description.");
|
|
1039
|
+
if (verifyResult && false === verifyResult.pass) throw new Error('aiDescribe verification failed.');
|
|
1040
|
+
const semanticAction = buildRecorderSemanticAction(event.actionType || event.type, event.rawPayload || {}, event.url);
|
|
1041
|
+
const trace = await finishTrace('ready', {
|
|
1042
|
+
modelCallDurationMs,
|
|
1043
|
+
elementDescription,
|
|
1044
|
+
verifyPassed: verifyResult?.pass,
|
|
1045
|
+
centerDistance: verifyResult?.centerDistance,
|
|
1046
|
+
verifyResult
|
|
1047
|
+
});
|
|
1048
|
+
debugInteract('recorder aiDescribe trace:', trace);
|
|
1049
|
+
return {
|
|
1050
|
+
event: {
|
|
1051
|
+
...event,
|
|
1052
|
+
semantic: buildReadyRecorderSemantic('aiDescribe', semanticAction, elementDescription, verifyResult?.pass ? 'high' : 'medium', {
|
|
1053
|
+
aiDescribe: {
|
|
1054
|
+
verifyPrompt,
|
|
1055
|
+
verifyPassed: verifyResult?.pass,
|
|
1056
|
+
deepLocate,
|
|
1057
|
+
centerDistance: verifyResult?.centerDistance,
|
|
1058
|
+
expectedCenter: [
|
|
1059
|
+
x,
|
|
1060
|
+
y
|
|
1061
|
+
],
|
|
1062
|
+
actualCenter: verifyResult?.center
|
|
1063
|
+
}
|
|
1064
|
+
})
|
|
1065
|
+
},
|
|
1066
|
+
trace
|
|
1067
|
+
};
|
|
1068
|
+
} catch (error) {
|
|
1069
|
+
const reportedError = verifyResult?.pass === false ? new Error('aiDescribe verification failed.') : error;
|
|
1070
|
+
if (void 0 === modelCallDurationMs && modelCallStartedAt) modelCallDurationMs = Date.now() - modelCallStartedAt;
|
|
1071
|
+
debugInteract('canonical recorder aiDescribe failed:', reportedError);
|
|
1072
|
+
const trace = await finishTrace('failed', {
|
|
1073
|
+
error: reportedError instanceof Error ? reportedError.message : String(reportedError),
|
|
1074
|
+
modelCallDurationMs,
|
|
1075
|
+
elementDescription,
|
|
1076
|
+
verifyPassed: verifyResult?.pass,
|
|
1077
|
+
centerDistance: verifyResult?.centerDistance,
|
|
1078
|
+
verifyResult
|
|
1079
|
+
});
|
|
1080
|
+
debugInteract('recorder aiDescribe trace:', trace);
|
|
1081
|
+
const aiDescribeDetails = verifyResult || trace.annotatedScreenshotRef ? {
|
|
1082
|
+
aiDescribe: {
|
|
1083
|
+
verifyPrompt: true,
|
|
1084
|
+
verifyPassed: verifyResult?.pass,
|
|
1085
|
+
deepLocate,
|
|
1086
|
+
centerDistance: verifyResult?.centerDistance,
|
|
1087
|
+
expectedCenter: 'number' == typeof x && 'number' == typeof y ? [
|
|
1088
|
+
x,
|
|
1089
|
+
y
|
|
1090
|
+
] : void 0,
|
|
1091
|
+
actualCenter: verifyResult?.center,
|
|
1092
|
+
annotatedScreenshotPath: trace.annotatedScreenshotRef?.path
|
|
1093
|
+
}
|
|
1094
|
+
} : void 0;
|
|
1095
|
+
return {
|
|
1096
|
+
event: {
|
|
1097
|
+
...event,
|
|
1098
|
+
semantic: buildFailedAiDescribeRecorderSemantic(reportedError, aiDescribeDetails)
|
|
1099
|
+
},
|
|
1100
|
+
trace
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
getRecorderAiDescribeScreenshot(event) {
|
|
1105
|
+
if ('click' === event.type || 'input' === event.type || 'keydown' === event.type || 'drag' === event.type) return event.screenshotBefore || event.screenshotAfter;
|
|
1106
|
+
return event.screenshotAfter || event.screenshotBefore;
|
|
1107
|
+
}
|
|
1108
|
+
queueStudioPreviewRecorderEventAppend(event, navigationEvent) {
|
|
1109
|
+
const sessionId = this._recorderSessionId;
|
|
1110
|
+
if (!sessionId) return;
|
|
1111
|
+
this._recorderEvents.push(event);
|
|
1112
|
+
if (navigationEvent) this._recorderEvents.push(navigationEvent);
|
|
1113
|
+
}
|
|
1114
|
+
queueStudioPreviewRecorderEvent(payload, snapshotBefore, agent) {
|
|
1115
|
+
const sessionId = this._recorderSessionId;
|
|
1116
|
+
if (!sessionId) return;
|
|
1117
|
+
this._recorderPendingCaptures++;
|
|
1118
|
+
const queuedTask = this._recorderEventQueue.catch(()=>void 0).then(async ()=>{
|
|
1119
|
+
try {
|
|
1120
|
+
if (!this._recorderSessionId || this._recorderSessionId !== sessionId) return;
|
|
1121
|
+
await this.storeStudioPreviewRecorderEvent(payload, snapshotBefore, agent);
|
|
1122
|
+
} finally{
|
|
1123
|
+
this._recorderPendingCaptures = Math.max(0, this._recorderPendingCaptures - 1);
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
this._recorderEventQueue = queuedTask.catch((error)=>{
|
|
1127
|
+
debugInteract('async recorder event capture failed:', error);
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
643
1130
|
buildStudioPreviewNavigationChangeEvent(payload, pageStateBefore, pageStateAfter, screenshotAfter) {
|
|
644
1131
|
const actionType = 'string' == typeof payload.actionType ? payload.actionType : void 0;
|
|
645
1132
|
if (!actionType || BROWSER_CHROME_NAVIGATION_ACTIONS.has(actionType)) return null;
|
|
@@ -647,6 +1134,9 @@ class PlaygroundServer {
|
|
|
647
1134
|
const afterUrl = pageStateAfter.url;
|
|
648
1135
|
if (!afterUrl || beforeUrl === afterUrl) return null;
|
|
649
1136
|
const timestamp = Date.now();
|
|
1137
|
+
const semanticEvent = buildRecorderSemanticAction('NavigationChanged', {
|
|
1138
|
+
value: afterUrl
|
|
1139
|
+
}, afterUrl);
|
|
650
1140
|
return {
|
|
651
1141
|
source: 'studio-preview',
|
|
652
1142
|
type: 'navigation',
|
|
@@ -662,12 +1152,7 @@ class PlaygroundServer {
|
|
|
662
1152
|
value: afterUrl,
|
|
663
1153
|
screenshotBefore: screenshotAfter,
|
|
664
1154
|
screenshotAfter,
|
|
665
|
-
|
|
666
|
-
replayInstruction: `Wait for navigation to complete at \`${afterUrl}\`.`,
|
|
667
|
-
actionSummary: `Wait for navigation to complete at ${afterUrl}`,
|
|
668
|
-
semanticConfidence: 'high',
|
|
669
|
-
descriptionLoading: false,
|
|
670
|
-
descriptionSource: 'fallback',
|
|
1155
|
+
semantic: buildReadyRecorderSemantic('heuristic', semanticEvent, afterUrl, 'high'),
|
|
671
1156
|
timestamp,
|
|
672
1157
|
hashId: `studio-preview-navigation-${timestamp}-${Math.random().toString(36).slice(2, 8)}`
|
|
673
1158
|
};
|
|
@@ -675,6 +1160,9 @@ class PlaygroundServer {
|
|
|
675
1160
|
buildStudioPreviewInitialNavigationEvent(pageState, screenshot) {
|
|
676
1161
|
if (!pageState.url) return null;
|
|
677
1162
|
const timestamp = Date.now();
|
|
1163
|
+
const semanticEvent = buildRecorderSemanticAction('InitialNavigation', {
|
|
1164
|
+
value: pageState.url
|
|
1165
|
+
}, pageState.url);
|
|
678
1166
|
return {
|
|
679
1167
|
source: 'studio-preview',
|
|
680
1168
|
type: 'navigation',
|
|
@@ -689,12 +1177,7 @@ class PlaygroundServer {
|
|
|
689
1177
|
value: pageState.url,
|
|
690
1178
|
screenshotBefore: screenshot,
|
|
691
1179
|
screenshotAfter: screenshot,
|
|
692
|
-
|
|
693
|
-
replayInstruction: `Navigate to \`${pageState.url}\`.`,
|
|
694
|
-
actionSummary: `Navigate to ${pageState.url}`,
|
|
695
|
-
semanticConfidence: 'high',
|
|
696
|
-
descriptionLoading: false,
|
|
697
|
-
descriptionSource: 'fallback',
|
|
1180
|
+
semantic: buildReadyRecorderSemantic('heuristic', semanticEvent, pageState.url, 'high'),
|
|
698
1181
|
timestamp,
|
|
699
1182
|
hashId: `studio-preview-initial-navigation-${timestamp}-${Math.random().toString(36).slice(2, 8)}`
|
|
700
1183
|
};
|
|
@@ -1036,7 +1519,7 @@ class PlaygroundServer {
|
|
|
1036
1519
|
error: error instanceof Error ? error.message : 'No active session'
|
|
1037
1520
|
});
|
|
1038
1521
|
}
|
|
1039
|
-
const { type, prompt, params, requestId, deepLocate, deepThink, screenshotIncluded, domIncluded, deviceOptions } = req.body;
|
|
1522
|
+
const { type, prompt, params, requestId, deepLocate, deepThink, screenshotIncluded, domIncluded, deviceOptions, reportDisplay } = req.body;
|
|
1040
1523
|
if (!type) return res.status(400).json({
|
|
1041
1524
|
error: 'type is required'
|
|
1042
1525
|
});
|
|
@@ -1085,6 +1568,7 @@ class PlaygroundServer {
|
|
|
1085
1568
|
};
|
|
1086
1569
|
const startTime = Date.now();
|
|
1087
1570
|
try {
|
|
1571
|
+
agent.resetDump?.();
|
|
1088
1572
|
await this._activeConnection.executionHooks?.beforeExecute?.();
|
|
1089
1573
|
const actionSpace = agent.interface.actionSpace();
|
|
1090
1574
|
const value = {
|
|
@@ -1097,7 +1581,8 @@ class PlaygroundServer {
|
|
|
1097
1581
|
deepThink,
|
|
1098
1582
|
screenshotIncluded,
|
|
1099
1583
|
domIncluded,
|
|
1100
|
-
deviceOptions
|
|
1584
|
+
deviceOptions,
|
|
1585
|
+
reportDisplay
|
|
1101
1586
|
});
|
|
1102
1587
|
} catch (error) {
|
|
1103
1588
|
response.error = formatErrorMessage(error);
|
|
@@ -1114,7 +1599,7 @@ class PlaygroundServer {
|
|
|
1114
1599
|
});
|
|
1115
1600
|
if (dumpString) {
|
|
1116
1601
|
const groupedDump = ReportActionDump.fromSerializedString(dumpString);
|
|
1117
|
-
response.dump = groupedDump
|
|
1602
|
+
response.dump = groupedDump;
|
|
1118
1603
|
} else response.dump = null;
|
|
1119
1604
|
response.reportHTML = agent.reportHTMLString({
|
|
1120
1605
|
inlineScreenshots: true
|
|
@@ -1154,7 +1639,7 @@ class PlaygroundServer {
|
|
|
1154
1639
|
});
|
|
1155
1640
|
if (dumpString) {
|
|
1156
1641
|
const groupedDump = ReportActionDump.fromSerializedString(dumpString);
|
|
1157
|
-
dump = groupedDump
|
|
1642
|
+
dump = groupedDump;
|
|
1158
1643
|
}
|
|
1159
1644
|
reportHTML = agent.reportHTMLString?.({
|
|
1160
1645
|
inlineScreenshots: true
|
|
@@ -1225,6 +1710,7 @@ class PlaygroundServer {
|
|
|
1225
1710
|
res.json(await this.getRecorderCapabilities());
|
|
1226
1711
|
});
|
|
1227
1712
|
this._app.post('/recorder/stop', async (_req, res)=>{
|
|
1713
|
+
await this.waitForRecorderIdle();
|
|
1228
1714
|
this._recorderSessionId = null;
|
|
1229
1715
|
this._studioPreviewRecorderLastScreenshot = void 0;
|
|
1230
1716
|
this._studioPreviewRecorderLastPageState = void 0;
|
|
@@ -1240,6 +1726,38 @@ class PlaygroundServer {
|
|
|
1240
1726
|
nextIndex: this._recorderEvents.length
|
|
1241
1727
|
});
|
|
1242
1728
|
});
|
|
1729
|
+
this._app.post('/recorder/describe-event', async (req, res)=>{
|
|
1730
|
+
const event = req.body?.event;
|
|
1731
|
+
if (!event || 'object' != typeof event) return res.status(400).json({
|
|
1732
|
+
ok: false,
|
|
1733
|
+
error: 'event is required'
|
|
1734
|
+
});
|
|
1735
|
+
try {
|
|
1736
|
+
const agent = this.getActiveAgentOrThrow();
|
|
1737
|
+
const { event: describedEvent, trace } = await this.enrichStudioPreviewRecorderEventWithAiDescribe(event, agent);
|
|
1738
|
+
res.json({
|
|
1739
|
+
ok: true,
|
|
1740
|
+
event: describedEvent,
|
|
1741
|
+
trace
|
|
1742
|
+
});
|
|
1743
|
+
} catch (error) {
|
|
1744
|
+
const startedAt = new Date();
|
|
1745
|
+
const traceBase = createRecorderAiDescribeTraceBase(event);
|
|
1746
|
+
const trace = {
|
|
1747
|
+
...traceBase,
|
|
1748
|
+
status: 'failed',
|
|
1749
|
+
startedAt: startedAt.toISOString(),
|
|
1750
|
+
durationMs: 0,
|
|
1751
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1752
|
+
};
|
|
1753
|
+
debugInteract('recorder aiDescribe trace:', trace);
|
|
1754
|
+
res.status(500).json({
|
|
1755
|
+
ok: false,
|
|
1756
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1757
|
+
trace
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
});
|
|
1243
1761
|
this._app.get('/screenshot', async (_req, res)=>{
|
|
1244
1762
|
try {
|
|
1245
1763
|
let agent = this.getActiveAgentOrThrow();
|
|
@@ -1340,7 +1858,7 @@ class PlaygroundServer {
|
|
|
1340
1858
|
recorderActive: Boolean(this._recorderSessionId),
|
|
1341
1859
|
hasInputPrimitives: Boolean(agent.interface.inputPrimitives)
|
|
1342
1860
|
});
|
|
1343
|
-
const recorderSnapshotBefore =
|
|
1861
|
+
const recorderSnapshotBefore = this.captureCachedRecorderSnapshotBeforeInteract();
|
|
1344
1862
|
const inputPrimitives = agent.interface.inputPrimitives;
|
|
1345
1863
|
if (inputPrimitives) {
|
|
1346
1864
|
await dispatchPointer(inputPrimitives, req.body ?? {}, ()=>agent.interface.size());
|
|
@@ -1348,12 +1866,12 @@ class PlaygroundServer {
|
|
|
1348
1866
|
payload: summarizeInteractPayload(req.body ?? {}),
|
|
1349
1867
|
elapsedMs: Date.now() - interactStartedAt
|
|
1350
1868
|
});
|
|
1351
|
-
|
|
1869
|
+
res.json({});
|
|
1870
|
+
this.queueStudioPreviewRecorderEvent(req.body ?? {}, recorderSnapshotBefore, agent);
|
|
1352
1871
|
debugInteract('manual interact completed %o', {
|
|
1353
1872
|
payload: summarizeInteractPayload(req.body ?? {}),
|
|
1354
1873
|
elapsedMs: Date.now() - interactStartedAt
|
|
1355
1874
|
});
|
|
1356
|
-
res.json({});
|
|
1357
1875
|
return;
|
|
1358
1876
|
}
|
|
1359
1877
|
if (!this.findInteractAction(agent, actionType) && !this.canRunBrowserChromeInteractAction(agent, actionType)) return res.status(404).json({
|
|
@@ -1365,12 +1883,12 @@ class PlaygroundServer {
|
|
|
1365
1883
|
payload: summarizeInteractPayload(req.body ?? {}),
|
|
1366
1884
|
elapsedMs: Date.now() - interactStartedAt
|
|
1367
1885
|
});
|
|
1368
|
-
|
|
1886
|
+
res.json({});
|
|
1887
|
+
this.queueStudioPreviewRecorderEvent(req.body ?? {}, recorderSnapshotBefore, agent);
|
|
1369
1888
|
debugInteract('manual interact completed %o', {
|
|
1370
1889
|
payload: summarizeInteractPayload(req.body ?? {}),
|
|
1371
1890
|
elapsedMs: Date.now() - interactStartedAt
|
|
1372
1891
|
});
|
|
1373
|
-
res.json({});
|
|
1374
1892
|
} catch (error) {
|
|
1375
1893
|
if (error instanceof PointerInputError) return res.status(error.statusCode).json({
|
|
1376
1894
|
error: error.message
|
|
@@ -1559,6 +2077,9 @@ class PlaygroundServer {
|
|
|
1559
2077
|
_define_property(this, "_baseSidecars", void 0);
|
|
1560
2078
|
_define_property(this, "_recorderSessionId", null);
|
|
1561
2079
|
_define_property(this, "_recorderEvents", []);
|
|
2080
|
+
_define_property(this, "_recorderEventQueue", Promise.resolve());
|
|
2081
|
+
_define_property(this, "_recorderPendingCaptures", 0);
|
|
2082
|
+
_define_property(this, "_studioPreviewRecorderLastTargetPoint", void 0);
|
|
1562
2083
|
_define_property(this, "_studioPreviewRecorderLastScreenshot", void 0);
|
|
1563
2084
|
_define_property(this, "_studioPreviewRecorderLastPageState", void 0);
|
|
1564
2085
|
_define_property(this, "_activeConnection", {
|