@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/lib/server.js
CHANGED
|
@@ -43,16 +43,19 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
43
43
|
InteractParamsValidationError: ()=>InteractParamsValidationError,
|
|
44
44
|
buildInteractParams: ()=>buildInteractParams
|
|
45
45
|
});
|
|
46
|
+
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
46
47
|
const external_node_fs_namespaceObject = require("node:fs");
|
|
47
48
|
const external_node_path_namespaceObject = require("node:path");
|
|
48
49
|
const external_node_url_namespaceObject = require("node:url");
|
|
49
50
|
const core_namespaceObject = require("@midscene/core");
|
|
50
51
|
const utils_namespaceObject = require("@midscene/core/utils");
|
|
52
|
+
const common_namespaceObject = require("@midscene/shared/common");
|
|
51
53
|
const constants_namespaceObject = require("@midscene/shared/constants");
|
|
52
54
|
const env_namespaceObject = require("@midscene/shared/env");
|
|
53
55
|
const extractor_namespaceObject = require("@midscene/shared/extractor");
|
|
54
56
|
const img_namespaceObject = require("@midscene/shared/img");
|
|
55
57
|
const logger_namespaceObject = require("@midscene/shared/logger");
|
|
58
|
+
const recorder_namespaceObject = require("@midscene/shared/recorder");
|
|
56
59
|
const shared_utils_namespaceObject = require("@midscene/shared/utils");
|
|
57
60
|
const external_express_namespaceObject = require("express");
|
|
58
61
|
var external_express_default = /*#__PURE__*/ __webpack_require__.n(external_express_namespaceObject);
|
|
@@ -73,6 +76,216 @@ function _define_property(obj, key, value) {
|
|
|
73
76
|
}
|
|
74
77
|
const defaultPort = constants_namespaceObject.PLAYGROUND_SERVER_PORT;
|
|
75
78
|
const RECORDER_CAPTURE_AFTER_INTERACT_DELAY_MS = 250;
|
|
79
|
+
const RECORDER_AI_DESCRIBE_AFTER_INTERACT_TIMEOUT_MS = 30000;
|
|
80
|
+
const RECORDER_AI_DESCRIBE_SCREENSHOT_DUMP_DIR = 'recorder-ai-describe-screenshots';
|
|
81
|
+
function extractBase64Payload(value) {
|
|
82
|
+
const dataUrlMatch = value.match(/^data:([^;,]+)?(?:;[^,]*)?,([\s\S]*)$/);
|
|
83
|
+
if (dataUrlMatch) return {
|
|
84
|
+
mimeType: dataUrlMatch[1],
|
|
85
|
+
base64: dataUrlMatch[2] || ''
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
base64: value
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function estimateBase64Bytes(value) {
|
|
92
|
+
if (!value) return;
|
|
93
|
+
const { base64 } = extractBase64Payload(value);
|
|
94
|
+
const padding = base64.endsWith('==') ? 2 : base64.endsWith('=') ? 1 : 0;
|
|
95
|
+
return Math.max(0, Math.floor(3 * base64.length / 4) - padding);
|
|
96
|
+
}
|
|
97
|
+
function buildRecorderRawPayloadSummary(rawPayload) {
|
|
98
|
+
if (!rawPayload) return;
|
|
99
|
+
const allowedKeys = [
|
|
100
|
+
'actionType',
|
|
101
|
+
'x',
|
|
102
|
+
'y',
|
|
103
|
+
'endX',
|
|
104
|
+
'endY',
|
|
105
|
+
'duration',
|
|
106
|
+
'direction',
|
|
107
|
+
'scrollType',
|
|
108
|
+
'distance',
|
|
109
|
+
'keyName',
|
|
110
|
+
'mode'
|
|
111
|
+
];
|
|
112
|
+
const summary = {};
|
|
113
|
+
for (const key of allowedKeys)if (void 0 !== rawPayload[key]) summary[key] = rawPayload[key];
|
|
114
|
+
const value = rawPayload.value;
|
|
115
|
+
if ('string' == typeof value) summary.valueLength = value.length;
|
|
116
|
+
return Object.keys(summary).length > 0 ? summary : void 0;
|
|
117
|
+
}
|
|
118
|
+
function buildRecorderEventSummary(event) {
|
|
119
|
+
return {
|
|
120
|
+
hashId: event.hashId,
|
|
121
|
+
mergedHashIds: event.mergedHashIds,
|
|
122
|
+
type: event.type,
|
|
123
|
+
source: event.source,
|
|
124
|
+
actionType: event.actionType,
|
|
125
|
+
timestamp: event.timestamp,
|
|
126
|
+
url: event.url,
|
|
127
|
+
title: event.title,
|
|
128
|
+
valueLength: 'string' == typeof event.value ? event.value.length : void 0,
|
|
129
|
+
rawPayloadSummary: buildRecorderRawPayloadSummary(event.rawPayload),
|
|
130
|
+
elementRect: event.elementRect,
|
|
131
|
+
pageInfo: event.pageInfo
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function shouldVerifyRecorderAiDescribeEvent(event) {
|
|
135
|
+
return 'scroll' !== event.type;
|
|
136
|
+
}
|
|
137
|
+
function formatRecorderAiDescribeScreenshotPathParts() {
|
|
138
|
+
const date = new Date();
|
|
139
|
+
const localDate = Number.isNaN(date.getTime()) ? new Date() : date;
|
|
140
|
+
const pad = (value, length = 2)=>String(value).padStart(length, '0');
|
|
141
|
+
const datePart = [
|
|
142
|
+
localDate.getFullYear(),
|
|
143
|
+
pad(localDate.getMonth() + 1),
|
|
144
|
+
pad(localDate.getDate())
|
|
145
|
+
].join('-');
|
|
146
|
+
const timePart = [
|
|
147
|
+
pad(localDate.getHours()),
|
|
148
|
+
pad(localDate.getMinutes()),
|
|
149
|
+
pad(localDate.getSeconds()),
|
|
150
|
+
pad(localDate.getMilliseconds(), 3)
|
|
151
|
+
].join('-');
|
|
152
|
+
const hourPart = timePart.slice(0, 2) || 'unknown-hour';
|
|
153
|
+
return {
|
|
154
|
+
datePart,
|
|
155
|
+
hourPart,
|
|
156
|
+
filePrefix: `${timePart}_${(0, external_node_crypto_namespaceObject.randomUUID)()}`
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function assertPathInsideDirectory(rootDir, targetPath) {
|
|
160
|
+
const resolvedRoot = (0, external_node_path_namespaceObject.resolve)(rootDir);
|
|
161
|
+
const resolvedTarget = (0, external_node_path_namespaceObject.resolve)(targetPath);
|
|
162
|
+
if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(`${resolvedRoot}${external_node_path_namespaceObject.sep}`)) throw new Error(`Refusing to write recorder dump outside ${resolvedRoot}`);
|
|
163
|
+
return resolvedTarget;
|
|
164
|
+
}
|
|
165
|
+
function writeRecorderAiDescribeScreenshot(imageBase64, suffix) {
|
|
166
|
+
const { base64, mimeType } = extractBase64Payload(imageBase64);
|
|
167
|
+
const bytes = Buffer.from(base64, 'base64');
|
|
168
|
+
const sha256 = (0, external_node_crypto_namespaceObject.createHash)('sha256').update(bytes).digest('hex');
|
|
169
|
+
const extension = mimeType?.includes('jpeg') ? 'jpg' : 'png';
|
|
170
|
+
const pathParts = formatRecorderAiDescribeScreenshotPathParts();
|
|
171
|
+
const dumpRoot = (0, external_node_path_namespaceObject.resolve)((0, common_namespaceObject.getMidsceneRunSubDir)('dump'), RECORDER_AI_DESCRIBE_SCREENSHOT_DUMP_DIR);
|
|
172
|
+
const dumpDir = assertPathInsideDirectory(dumpRoot, (0, external_node_path_namespaceObject.join)(dumpRoot, pathParts.datePart, pathParts.hourPart));
|
|
173
|
+
(0, external_node_fs_namespaceObject.mkdirSync)(dumpDir, {
|
|
174
|
+
recursive: true
|
|
175
|
+
});
|
|
176
|
+
const safeSuffix = 'annotated' === suffix ? 'annotated' : 'raw';
|
|
177
|
+
const fileName = `${pathParts.filePrefix}_${safeSuffix}.${extension}`;
|
|
178
|
+
const filePath = assertPathInsideDirectory(dumpRoot, (0, external_node_path_namespaceObject.join)(dumpDir, fileName));
|
|
179
|
+
if (!(0, external_node_fs_namespaceObject.existsSync)(filePath)) (0, external_node_fs_namespaceObject.writeFileSync)(filePath, bytes);
|
|
180
|
+
return {
|
|
181
|
+
path: filePath,
|
|
182
|
+
sha256,
|
|
183
|
+
bytes: bytes.byteLength,
|
|
184
|
+
mimeType
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function calculateRecorderScreenshotAnnotation(event, imageSize, verifyResult) {
|
|
188
|
+
const pageWidth = event.pageInfo?.width;
|
|
189
|
+
const pageHeight = event.pageInfo?.height;
|
|
190
|
+
const scaleX = pageWidth ? imageSize.width / pageWidth : void 0;
|
|
191
|
+
const scaleY = pageHeight ? imageSize.height / pageHeight : void 0;
|
|
192
|
+
const mapX = (value)=>scaleX ? value * scaleX : value;
|
|
193
|
+
const mapY = (value)=>scaleY ? value * scaleY : value;
|
|
194
|
+
const logicalPoint = 'number' == typeof event.elementRect?.x && 'number' == typeof event.elementRect?.y ? [
|
|
195
|
+
event.elementRect.x,
|
|
196
|
+
event.elementRect.y
|
|
197
|
+
] : void 0;
|
|
198
|
+
const locateRect = verifyResult?.rect;
|
|
199
|
+
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 ? {
|
|
200
|
+
left: mapX(event.elementRect.left),
|
|
201
|
+
top: mapY(event.elementRect.top),
|
|
202
|
+
width: mapX(event.elementRect.width),
|
|
203
|
+
height: mapY(event.elementRect.height)
|
|
204
|
+
} : void 0;
|
|
205
|
+
if (!logicalPoint && !sourceTargetRect && !locateRect) return;
|
|
206
|
+
const screenshotPoint = logicalPoint && scaleX && scaleY ? [
|
|
207
|
+
logicalPoint[0] * scaleX,
|
|
208
|
+
logicalPoint[1] * scaleY
|
|
209
|
+
] : void 0;
|
|
210
|
+
const pointTargetRect = screenshotPoint && !sourceTargetRect ? {
|
|
211
|
+
left: Math.max(0, screenshotPoint[0] - 6),
|
|
212
|
+
top: Math.max(0, screenshotPoint[1] - 6),
|
|
213
|
+
width: 12,
|
|
214
|
+
height: 12
|
|
215
|
+
} : void 0;
|
|
216
|
+
const center = verifyResult?.center || (locateRect ? [
|
|
217
|
+
locateRect.left + locateRect.width / 2,
|
|
218
|
+
locateRect.top + locateRect.height / 2
|
|
219
|
+
] : void 0);
|
|
220
|
+
const centerDelta = screenshotPoint && center ? {
|
|
221
|
+
x: center[0] - screenshotPoint[0],
|
|
222
|
+
y: center[1] - screenshotPoint[1],
|
|
223
|
+
distance: Math.hypot(center[0] - screenshotPoint[0], center[1] - screenshotPoint[1])
|
|
224
|
+
} : void 0;
|
|
225
|
+
const distanceOutsideRect = screenshotPoint && locateRect ? (()=>{
|
|
226
|
+
const right = locateRect.left + locateRect.width;
|
|
227
|
+
const bottom = locateRect.top + locateRect.height;
|
|
228
|
+
const x = screenshotPoint[0] < locateRect.left ? locateRect.left - screenshotPoint[0] : screenshotPoint[0] > right ? screenshotPoint[0] - right : 0;
|
|
229
|
+
const y = screenshotPoint[1] < locateRect.top ? locateRect.top - screenshotPoint[1] : screenshotPoint[1] > bottom ? screenshotPoint[1] - bottom : 0;
|
|
230
|
+
return {
|
|
231
|
+
x,
|
|
232
|
+
y,
|
|
233
|
+
distance: Math.hypot(x, y)
|
|
234
|
+
};
|
|
235
|
+
})() : void 0;
|
|
236
|
+
return {
|
|
237
|
+
inputPoint: screenshotPoint ? {
|
|
238
|
+
logical: logicalPoint,
|
|
239
|
+
screenshot: screenshotPoint
|
|
240
|
+
} : void 0,
|
|
241
|
+
sourceTargetRect: sourceTargetRect || pointTargetRect,
|
|
242
|
+
locateRect,
|
|
243
|
+
centerDelta,
|
|
244
|
+
distanceOutsideRect
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
async function persistRecorderAiDescribeScreenshot(event, eventScreenshot, verifyResult) {
|
|
248
|
+
if (!eventScreenshot) return;
|
|
249
|
+
const screenshotRef = writeRecorderAiDescribeScreenshot(eventScreenshot, 'raw');
|
|
250
|
+
let annotatedScreenshotRef;
|
|
251
|
+
let screenshotAnnotation;
|
|
252
|
+
let annotatedScreenshotPersistError;
|
|
253
|
+
try {
|
|
254
|
+
const imageSize = await (0, img_namespaceObject.imageInfoOfBase64)(eventScreenshot);
|
|
255
|
+
screenshotAnnotation = calculateRecorderScreenshotAnnotation(event, imageSize, verifyResult);
|
|
256
|
+
const annotatedRects = [
|
|
257
|
+
screenshotAnnotation?.sourceTargetRect,
|
|
258
|
+
screenshotAnnotation?.locateRect
|
|
259
|
+
].filter((rect)=>Boolean(rect));
|
|
260
|
+
if (annotatedRects.length > 0) {
|
|
261
|
+
const annotatedScreenshot = await (0, img_namespaceObject.annotateRects)(eventScreenshot, annotatedRects);
|
|
262
|
+
annotatedScreenshotRef = writeRecorderAiDescribeScreenshot(annotatedScreenshot, 'annotated');
|
|
263
|
+
}
|
|
264
|
+
} catch (error) {
|
|
265
|
+
annotatedScreenshotPersistError = error instanceof Error ? error.message : String(error);
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
screenshotRef,
|
|
269
|
+
annotatedScreenshotRef,
|
|
270
|
+
screenshotAnnotation,
|
|
271
|
+
annotatedScreenshotPersistError
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function createRecorderAiDescribeTraceBase(event, eventScreenshot) {
|
|
275
|
+
return {
|
|
276
|
+
traceId: `${event.hashId || 'recorder-event'}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
|
|
277
|
+
eventHashId: event.hashId,
|
|
278
|
+
eventType: event.type,
|
|
279
|
+
actionType: event.actionType,
|
|
280
|
+
eventSummary: buildRecorderEventSummary(event),
|
|
281
|
+
point: 'number' == typeof event.elementRect?.x && 'number' == typeof event.elementRect?.y ? [
|
|
282
|
+
event.elementRect.x,
|
|
283
|
+
event.elementRect.y
|
|
284
|
+
] : void 0,
|
|
285
|
+
pageInfo: event.pageInfo,
|
|
286
|
+
screenshotBytes: estimateBase64Bytes(eventScreenshot)
|
|
287
|
+
};
|
|
288
|
+
}
|
|
76
289
|
function serializeAiConfigSignature(aiConfig) {
|
|
77
290
|
return JSON.stringify(Object.entries(aiConfig).sort(([leftKey], [rightKey])=>leftKey.localeCompare(rightKey)));
|
|
78
291
|
}
|
|
@@ -236,6 +449,74 @@ function buildInteractParams(actionType, body) {
|
|
|
236
449
|
const { actionType: _omit, ...passthrough } = body;
|
|
237
450
|
return passthrough;
|
|
238
451
|
}
|
|
452
|
+
function getRecorderSemanticActionType(actionType) {
|
|
453
|
+
switch(actionType){
|
|
454
|
+
case 'Tap':
|
|
455
|
+
case 'DoubleClick':
|
|
456
|
+
case 'LongPress':
|
|
457
|
+
case 'RightClick':
|
|
458
|
+
return 'click';
|
|
459
|
+
case 'Swipe':
|
|
460
|
+
case 'DragAndDrop':
|
|
461
|
+
return 'drag';
|
|
462
|
+
case 'Input':
|
|
463
|
+
return 'input';
|
|
464
|
+
case 'KeyboardPress':
|
|
465
|
+
return 'keydown';
|
|
466
|
+
case 'Scroll':
|
|
467
|
+
return 'scroll';
|
|
468
|
+
case 'GoBack':
|
|
469
|
+
case 'GoForward':
|
|
470
|
+
case 'Reload':
|
|
471
|
+
case 'Stop':
|
|
472
|
+
case 'InitialNavigation':
|
|
473
|
+
case 'NavigationChanged':
|
|
474
|
+
return 'navigation';
|
|
475
|
+
default:
|
|
476
|
+
return 'click';
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
function buildRecorderSemanticAction(actionType, payload, url) {
|
|
480
|
+
return {
|
|
481
|
+
type: getRecorderSemanticActionType(actionType),
|
|
482
|
+
actionType,
|
|
483
|
+
value: 'string' == typeof payload.value ? payload.value : 'string' == typeof payload.keyName ? payload.keyName : void 0,
|
|
484
|
+
url
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function buildReadyRecorderSemantic(source, event, elementDescription, confidence, extra) {
|
|
488
|
+
return {
|
|
489
|
+
source,
|
|
490
|
+
status: 'ready',
|
|
491
|
+
elementDescription,
|
|
492
|
+
replayInstruction: (0, recorder_namespaceObject.buildMidsceneRecorderReplayInstruction)(event, elementDescription),
|
|
493
|
+
actionSummary: (0, recorder_namespaceObject.buildMidsceneRecorderActionSummary)(event, elementDescription),
|
|
494
|
+
confidence,
|
|
495
|
+
...extra
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
function buildFailedAiDescribeRecorderSemantic(error, extra) {
|
|
499
|
+
return {
|
|
500
|
+
source: 'aiDescribe',
|
|
501
|
+
status: 'failed',
|
|
502
|
+
error: error instanceof Error ? error.message : String(error),
|
|
503
|
+
...extra
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
function withTimeout(promise, timeoutMs, message) {
|
|
507
|
+
let timeout;
|
|
508
|
+
const timeoutPromise = new Promise((_resolve, reject)=>{
|
|
509
|
+
timeout = setTimeout(()=>{
|
|
510
|
+
reject(new Error(message));
|
|
511
|
+
}, timeoutMs);
|
|
512
|
+
});
|
|
513
|
+
return Promise.race([
|
|
514
|
+
promise,
|
|
515
|
+
timeoutPromise
|
|
516
|
+
]).finally(()=>{
|
|
517
|
+
if (timeout) clearTimeout(timeout);
|
|
518
|
+
});
|
|
519
|
+
}
|
|
239
520
|
function createManualExecutorContext(actionType, param) {
|
|
240
521
|
const task = {
|
|
241
522
|
type: 'Action Space',
|
|
@@ -439,9 +720,14 @@ class PlaygroundServer {
|
|
|
439
720
|
resetRecorderState() {
|
|
440
721
|
this._recorderSessionId = null;
|
|
441
722
|
this._recorderEvents = [];
|
|
723
|
+
this._recorderEventQueue = Promise.resolve();
|
|
724
|
+
this._studioPreviewRecorderLastTargetPoint = void 0;
|
|
442
725
|
this._studioPreviewRecorderLastScreenshot = void 0;
|
|
443
726
|
this._studioPreviewRecorderLastPageState = void 0;
|
|
444
727
|
}
|
|
728
|
+
async waitForRecorderIdle() {
|
|
729
|
+
await this._recorderEventQueue;
|
|
730
|
+
}
|
|
445
731
|
canRecordStudioPreviewInteractions() {
|
|
446
732
|
const agent = this._activeConnection.agent;
|
|
447
733
|
if (!agent) return false;
|
|
@@ -525,6 +811,13 @@ class PlaygroundServer {
|
|
|
525
811
|
pageState: this._studioPreviewRecorderLastPageState || await this.getActiveRecorderPageState()
|
|
526
812
|
};
|
|
527
813
|
}
|
|
814
|
+
captureCachedRecorderSnapshotBeforeInteract() {
|
|
815
|
+
if (!this._recorderSessionId || !this._studioPreviewRecorderLastPageState || this._recorderPendingCaptures > 0) return;
|
|
816
|
+
return {
|
|
817
|
+
screenshot: this._mjpegHandler.getLastFrameBase64() || this._studioPreviewRecorderLastScreenshot,
|
|
818
|
+
pageState: this._studioPreviewRecorderLastPageState
|
|
819
|
+
};
|
|
820
|
+
}
|
|
528
821
|
async startStudioPreviewRecorder(sessionId) {
|
|
529
822
|
this._recorderSessionId = sessionId;
|
|
530
823
|
this._studioPreviewRecorderLastScreenshot = await this.takeRecorderScreenshot();
|
|
@@ -533,7 +826,7 @@ class PlaygroundServer {
|
|
|
533
826
|
const initialNavigationEvent = this.buildStudioPreviewInitialNavigationEvent(initialPageState, this._studioPreviewRecorderLastScreenshot);
|
|
534
827
|
if (initialNavigationEvent) this._recorderEvents.push(initialNavigationEvent);
|
|
535
828
|
}
|
|
536
|
-
async storeStudioPreviewRecorderEvent(payload, snapshotBefore) {
|
|
829
|
+
async storeStudioPreviewRecorderEvent(payload, snapshotBefore, agent) {
|
|
537
830
|
if (!this._recorderSessionId) return;
|
|
538
831
|
const before = snapshotBefore || await this.captureRecorderSnapshotBeforeInteract();
|
|
539
832
|
const screenshotBefore = before?.screenshot;
|
|
@@ -558,19 +851,26 @@ class PlaygroundServer {
|
|
|
558
851
|
this._studioPreviewRecorderLastPageState = pageStateAfter;
|
|
559
852
|
return;
|
|
560
853
|
}
|
|
561
|
-
this._recorderEvents.push(event);
|
|
562
854
|
const navigationEvent = this.buildStudioPreviewNavigationChangeEvent(payload, before?.pageState, pageStateAfter, screenshotAfter);
|
|
563
|
-
if (navigationEvent) this._recorderEvents.push(navigationEvent);
|
|
564
855
|
this._studioPreviewRecorderLastScreenshot = screenshotAfter;
|
|
565
856
|
this._studioPreviewRecorderLastPageState = pageStateAfter;
|
|
857
|
+
this.queueStudioPreviewRecorderEventAppend(event, navigationEvent);
|
|
566
858
|
}
|
|
567
859
|
async buildStudioPreviewRecorderEvent(payload, pageState, screenshotBefore, screenshotAfter) {
|
|
568
860
|
const actionType = 'string' == typeof payload.actionType ? payload.actionType : void 0;
|
|
569
861
|
if (!actionType) return null;
|
|
570
862
|
const { pageInfo, url, title } = pageState;
|
|
571
863
|
const timestamp = Date.now();
|
|
572
|
-
const
|
|
573
|
-
const
|
|
864
|
+
const payloadX = 'number' == typeof payload.x ? payload.x : void 0;
|
|
865
|
+
const payloadY = 'number' == typeof payload.y ? payload.y : void 0;
|
|
866
|
+
const canReuseLastTargetPoint = 'Input' === actionType || 'KeyboardPress' === actionType;
|
|
867
|
+
const inheritedTargetPoint = canReuseLastTargetPoint && (void 0 === payloadX || void 0 === payloadY) ? this._studioPreviewRecorderLastTargetPoint : void 0;
|
|
868
|
+
const x = payloadX ?? inheritedTargetPoint?.x;
|
|
869
|
+
const y = payloadY ?? inheritedTargetPoint?.y;
|
|
870
|
+
if (void 0 !== payloadX && void 0 !== payloadY) this._studioPreviewRecorderLastTargetPoint = {
|
|
871
|
+
x: payloadX,
|
|
872
|
+
y: payloadY
|
|
873
|
+
};
|
|
574
874
|
const endX = 'number' == typeof payload.endX ? payload.endX : void 0;
|
|
575
875
|
const endY = 'number' == typeof payload.endY ? payload.endY : void 0;
|
|
576
876
|
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;
|
|
@@ -588,8 +888,10 @@ class PlaygroundServer {
|
|
|
588
888
|
screenshotBefore,
|
|
589
889
|
screenshotAfter,
|
|
590
890
|
screenshotWithBox: screenshotWithMarker,
|
|
591
|
-
|
|
592
|
-
|
|
891
|
+
semantic: {
|
|
892
|
+
source: 'aiDescribe',
|
|
893
|
+
status: 'pending'
|
|
894
|
+
},
|
|
593
895
|
timestamp,
|
|
594
896
|
hashId: `studio-preview-${actionType}-${timestamp}-${Math.random().toString(36).slice(2, 8)}`
|
|
595
897
|
};
|
|
@@ -667,17 +969,18 @@ class PlaygroundServer {
|
|
|
667
969
|
case 'GoForward':
|
|
668
970
|
case 'Reload':
|
|
669
971
|
case 'Stop':
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
972
|
+
{
|
|
973
|
+
const elementDescription = url || actionType;
|
|
974
|
+
const semanticEvent = buildRecorderSemanticAction(actionType, {
|
|
975
|
+
value: actionType
|
|
976
|
+
}, url);
|
|
977
|
+
return {
|
|
978
|
+
...base,
|
|
979
|
+
type: 'navigation',
|
|
980
|
+
value: actionType,
|
|
981
|
+
semantic: buildReadyRecorderSemantic('heuristic', semanticEvent, elementDescription, url ? 'high' : 'medium')
|
|
982
|
+
};
|
|
983
|
+
}
|
|
681
984
|
default:
|
|
682
985
|
return {
|
|
683
986
|
...base,
|
|
@@ -686,6 +989,190 @@ class PlaygroundServer {
|
|
|
686
989
|
};
|
|
687
990
|
}
|
|
688
991
|
}
|
|
992
|
+
async enrichStudioPreviewRecorderEventWithAiDescribe(event, agent) {
|
|
993
|
+
const startedAt = new Date();
|
|
994
|
+
const startedAtMs = Date.now();
|
|
995
|
+
const eventScreenshot = this.getRecorderAiDescribeScreenshot(event);
|
|
996
|
+
const traceBase = createRecorderAiDescribeTraceBase(event, eventScreenshot);
|
|
997
|
+
const finishTrace = async (status, extra = {})=>{
|
|
998
|
+
let screenshotRef;
|
|
999
|
+
let annotatedScreenshotRef;
|
|
1000
|
+
let screenshotAnnotation;
|
|
1001
|
+
let screenshotPersistError;
|
|
1002
|
+
let annotatedScreenshotPersistError;
|
|
1003
|
+
if ('failed' === status) try {
|
|
1004
|
+
const screenshotDump = await persistRecorderAiDescribeScreenshot(event, eventScreenshot, extra.verifyResult);
|
|
1005
|
+
screenshotRef = screenshotDump?.screenshotRef;
|
|
1006
|
+
annotatedScreenshotRef = screenshotDump?.annotatedScreenshotRef;
|
|
1007
|
+
screenshotAnnotation = screenshotDump?.screenshotAnnotation;
|
|
1008
|
+
annotatedScreenshotPersistError = screenshotDump?.annotatedScreenshotPersistError;
|
|
1009
|
+
} catch (error) {
|
|
1010
|
+
screenshotPersistError = error instanceof Error ? error.message : String(error);
|
|
1011
|
+
}
|
|
1012
|
+
return {
|
|
1013
|
+
...traceBase,
|
|
1014
|
+
...extra,
|
|
1015
|
+
screenshotRef,
|
|
1016
|
+
annotatedScreenshotRef,
|
|
1017
|
+
screenshotAnnotation,
|
|
1018
|
+
screenshotPersistError,
|
|
1019
|
+
annotatedScreenshotPersistError,
|
|
1020
|
+
status,
|
|
1021
|
+
startedAt: startedAt.toISOString(),
|
|
1022
|
+
durationMs: Date.now() - startedAtMs
|
|
1023
|
+
};
|
|
1024
|
+
};
|
|
1025
|
+
if ('navigation' === event.type || 'setViewport' === event.type) {
|
|
1026
|
+
const error = 'aiDescribe skipped because the event type does not target a UI element.';
|
|
1027
|
+
const trace = await finishTrace('failed', {
|
|
1028
|
+
error
|
|
1029
|
+
});
|
|
1030
|
+
debugInteract('recorder aiDescribe trace:', trace);
|
|
1031
|
+
return {
|
|
1032
|
+
event: {
|
|
1033
|
+
...event,
|
|
1034
|
+
semantic: buildFailedAiDescribeRecorderSemantic(error)
|
|
1035
|
+
},
|
|
1036
|
+
trace
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
if ('function' != typeof agent?.describeElementAtPoint) {
|
|
1040
|
+
const error = 'Active agent does not support describeElementAtPoint.';
|
|
1041
|
+
const trace = await finishTrace('failed', {
|
|
1042
|
+
error
|
|
1043
|
+
});
|
|
1044
|
+
debugInteract('recorder aiDescribe trace:', trace);
|
|
1045
|
+
return {
|
|
1046
|
+
event: {
|
|
1047
|
+
...event,
|
|
1048
|
+
semantic: buildFailedAiDescribeRecorderSemantic(error)
|
|
1049
|
+
},
|
|
1050
|
+
trace
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
const x = event.elementRect?.x;
|
|
1054
|
+
const y = event.elementRect?.y;
|
|
1055
|
+
let modelCallDurationMs;
|
|
1056
|
+
let modelCallStartedAt;
|
|
1057
|
+
let elementDescription;
|
|
1058
|
+
let deepLocate;
|
|
1059
|
+
let verifyResult;
|
|
1060
|
+
try {
|
|
1061
|
+
if ('number' != typeof x || 'number' != typeof y) throw new Error('Skipped aiDescribe because the recorder event has no stable point target.');
|
|
1062
|
+
if (!eventScreenshot) throw new Error('Skipped aiDescribe because the recorder event has no screenshot.');
|
|
1063
|
+
if (!event.pageInfo?.width || !event.pageInfo?.height) throw new Error('Skipped aiDescribe because the recorder event has no pageInfo for coordinate mapping.');
|
|
1064
|
+
const verifyPrompt = shouldVerifyRecorderAiDescribeEvent(event);
|
|
1065
|
+
modelCallStartedAt = Date.now();
|
|
1066
|
+
const describeResult = await withTimeout(agent.describeElementAtPoint([
|
|
1067
|
+
x,
|
|
1068
|
+
y
|
|
1069
|
+
], {
|
|
1070
|
+
verifyPrompt,
|
|
1071
|
+
screenshotBase64: eventScreenshot,
|
|
1072
|
+
coordinateSpace: 'logical',
|
|
1073
|
+
logicalSize: event.pageInfo,
|
|
1074
|
+
onProgress: (progress)=>{
|
|
1075
|
+
elementDescription = progress.prompt?.trim() || elementDescription;
|
|
1076
|
+
deepLocate = progress.deepLocate;
|
|
1077
|
+
verifyResult = verifyPrompt ? progress.verifyResult : void 0;
|
|
1078
|
+
}
|
|
1079
|
+
}), RECORDER_AI_DESCRIBE_AFTER_INTERACT_TIMEOUT_MS, 'Timed out while analyzing recorder event with aiDescribe.');
|
|
1080
|
+
modelCallDurationMs = Date.now() - modelCallStartedAt;
|
|
1081
|
+
elementDescription = describeResult.prompt?.trim();
|
|
1082
|
+
deepLocate = describeResult.deepLocate;
|
|
1083
|
+
verifyResult = verifyPrompt ? describeResult.verifyResult : void 0;
|
|
1084
|
+
if (!elementDescription) throw new Error("aiDescribe returned an empty element description.");
|
|
1085
|
+
if (verifyResult && false === verifyResult.pass) throw new Error('aiDescribe verification failed.');
|
|
1086
|
+
const semanticAction = buildRecorderSemanticAction(event.actionType || event.type, event.rawPayload || {}, event.url);
|
|
1087
|
+
const trace = await finishTrace('ready', {
|
|
1088
|
+
modelCallDurationMs,
|
|
1089
|
+
elementDescription,
|
|
1090
|
+
verifyPassed: verifyResult?.pass,
|
|
1091
|
+
centerDistance: verifyResult?.centerDistance,
|
|
1092
|
+
verifyResult
|
|
1093
|
+
});
|
|
1094
|
+
debugInteract('recorder aiDescribe trace:', trace);
|
|
1095
|
+
return {
|
|
1096
|
+
event: {
|
|
1097
|
+
...event,
|
|
1098
|
+
semantic: buildReadyRecorderSemantic('aiDescribe', semanticAction, elementDescription, verifyResult?.pass ? 'high' : 'medium', {
|
|
1099
|
+
aiDescribe: {
|
|
1100
|
+
verifyPrompt,
|
|
1101
|
+
verifyPassed: verifyResult?.pass,
|
|
1102
|
+
deepLocate,
|
|
1103
|
+
centerDistance: verifyResult?.centerDistance,
|
|
1104
|
+
expectedCenter: [
|
|
1105
|
+
x,
|
|
1106
|
+
y
|
|
1107
|
+
],
|
|
1108
|
+
actualCenter: verifyResult?.center
|
|
1109
|
+
}
|
|
1110
|
+
})
|
|
1111
|
+
},
|
|
1112
|
+
trace
|
|
1113
|
+
};
|
|
1114
|
+
} catch (error) {
|
|
1115
|
+
const reportedError = verifyResult?.pass === false ? new Error('aiDescribe verification failed.') : error;
|
|
1116
|
+
if (void 0 === modelCallDurationMs && modelCallStartedAt) modelCallDurationMs = Date.now() - modelCallStartedAt;
|
|
1117
|
+
debugInteract('canonical recorder aiDescribe failed:', reportedError);
|
|
1118
|
+
const trace = await finishTrace('failed', {
|
|
1119
|
+
error: reportedError instanceof Error ? reportedError.message : String(reportedError),
|
|
1120
|
+
modelCallDurationMs,
|
|
1121
|
+
elementDescription,
|
|
1122
|
+
verifyPassed: verifyResult?.pass,
|
|
1123
|
+
centerDistance: verifyResult?.centerDistance,
|
|
1124
|
+
verifyResult
|
|
1125
|
+
});
|
|
1126
|
+
debugInteract('recorder aiDescribe trace:', trace);
|
|
1127
|
+
const aiDescribeDetails = verifyResult || trace.annotatedScreenshotRef ? {
|
|
1128
|
+
aiDescribe: {
|
|
1129
|
+
verifyPrompt: true,
|
|
1130
|
+
verifyPassed: verifyResult?.pass,
|
|
1131
|
+
deepLocate,
|
|
1132
|
+
centerDistance: verifyResult?.centerDistance,
|
|
1133
|
+
expectedCenter: 'number' == typeof x && 'number' == typeof y ? [
|
|
1134
|
+
x,
|
|
1135
|
+
y
|
|
1136
|
+
] : void 0,
|
|
1137
|
+
actualCenter: verifyResult?.center,
|
|
1138
|
+
annotatedScreenshotPath: trace.annotatedScreenshotRef?.path
|
|
1139
|
+
}
|
|
1140
|
+
} : void 0;
|
|
1141
|
+
return {
|
|
1142
|
+
event: {
|
|
1143
|
+
...event,
|
|
1144
|
+
semantic: buildFailedAiDescribeRecorderSemantic(reportedError, aiDescribeDetails)
|
|
1145
|
+
},
|
|
1146
|
+
trace
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
getRecorderAiDescribeScreenshot(event) {
|
|
1151
|
+
if ('click' === event.type || 'input' === event.type || 'keydown' === event.type || 'drag' === event.type) return event.screenshotBefore || event.screenshotAfter;
|
|
1152
|
+
return event.screenshotAfter || event.screenshotBefore;
|
|
1153
|
+
}
|
|
1154
|
+
queueStudioPreviewRecorderEventAppend(event, navigationEvent) {
|
|
1155
|
+
const sessionId = this._recorderSessionId;
|
|
1156
|
+
if (!sessionId) return;
|
|
1157
|
+
this._recorderEvents.push(event);
|
|
1158
|
+
if (navigationEvent) this._recorderEvents.push(navigationEvent);
|
|
1159
|
+
}
|
|
1160
|
+
queueStudioPreviewRecorderEvent(payload, snapshotBefore, agent) {
|
|
1161
|
+
const sessionId = this._recorderSessionId;
|
|
1162
|
+
if (!sessionId) return;
|
|
1163
|
+
this._recorderPendingCaptures++;
|
|
1164
|
+
const queuedTask = this._recorderEventQueue.catch(()=>void 0).then(async ()=>{
|
|
1165
|
+
try {
|
|
1166
|
+
if (!this._recorderSessionId || this._recorderSessionId !== sessionId) return;
|
|
1167
|
+
await this.storeStudioPreviewRecorderEvent(payload, snapshotBefore, agent);
|
|
1168
|
+
} finally{
|
|
1169
|
+
this._recorderPendingCaptures = Math.max(0, this._recorderPendingCaptures - 1);
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
this._recorderEventQueue = queuedTask.catch((error)=>{
|
|
1173
|
+
debugInteract('async recorder event capture failed:', error);
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
689
1176
|
buildStudioPreviewNavigationChangeEvent(payload, pageStateBefore, pageStateAfter, screenshotAfter) {
|
|
690
1177
|
const actionType = 'string' == typeof payload.actionType ? payload.actionType : void 0;
|
|
691
1178
|
if (!actionType || BROWSER_CHROME_NAVIGATION_ACTIONS.has(actionType)) return null;
|
|
@@ -693,6 +1180,9 @@ class PlaygroundServer {
|
|
|
693
1180
|
const afterUrl = pageStateAfter.url;
|
|
694
1181
|
if (!afterUrl || beforeUrl === afterUrl) return null;
|
|
695
1182
|
const timestamp = Date.now();
|
|
1183
|
+
const semanticEvent = buildRecorderSemanticAction('NavigationChanged', {
|
|
1184
|
+
value: afterUrl
|
|
1185
|
+
}, afterUrl);
|
|
696
1186
|
return {
|
|
697
1187
|
source: 'studio-preview',
|
|
698
1188
|
type: 'navigation',
|
|
@@ -708,12 +1198,7 @@ class PlaygroundServer {
|
|
|
708
1198
|
value: afterUrl,
|
|
709
1199
|
screenshotBefore: screenshotAfter,
|
|
710
1200
|
screenshotAfter,
|
|
711
|
-
|
|
712
|
-
replayInstruction: `Wait for navigation to complete at \`${afterUrl}\`.`,
|
|
713
|
-
actionSummary: `Wait for navigation to complete at ${afterUrl}`,
|
|
714
|
-
semanticConfidence: 'high',
|
|
715
|
-
descriptionLoading: false,
|
|
716
|
-
descriptionSource: 'fallback',
|
|
1201
|
+
semantic: buildReadyRecorderSemantic('heuristic', semanticEvent, afterUrl, 'high'),
|
|
717
1202
|
timestamp,
|
|
718
1203
|
hashId: `studio-preview-navigation-${timestamp}-${Math.random().toString(36).slice(2, 8)}`
|
|
719
1204
|
};
|
|
@@ -721,6 +1206,9 @@ class PlaygroundServer {
|
|
|
721
1206
|
buildStudioPreviewInitialNavigationEvent(pageState, screenshot) {
|
|
722
1207
|
if (!pageState.url) return null;
|
|
723
1208
|
const timestamp = Date.now();
|
|
1209
|
+
const semanticEvent = buildRecorderSemanticAction('InitialNavigation', {
|
|
1210
|
+
value: pageState.url
|
|
1211
|
+
}, pageState.url);
|
|
724
1212
|
return {
|
|
725
1213
|
source: 'studio-preview',
|
|
726
1214
|
type: 'navigation',
|
|
@@ -735,12 +1223,7 @@ class PlaygroundServer {
|
|
|
735
1223
|
value: pageState.url,
|
|
736
1224
|
screenshotBefore: screenshot,
|
|
737
1225
|
screenshotAfter: screenshot,
|
|
738
|
-
|
|
739
|
-
replayInstruction: `Navigate to \`${pageState.url}\`.`,
|
|
740
|
-
actionSummary: `Navigate to ${pageState.url}`,
|
|
741
|
-
semanticConfidence: 'high',
|
|
742
|
-
descriptionLoading: false,
|
|
743
|
-
descriptionSource: 'fallback',
|
|
1226
|
+
semantic: buildReadyRecorderSemantic('heuristic', semanticEvent, pageState.url, 'high'),
|
|
744
1227
|
timestamp,
|
|
745
1228
|
hashId: `studio-preview-initial-navigation-${timestamp}-${Math.random().toString(36).slice(2, 8)}`
|
|
746
1229
|
};
|
|
@@ -1082,7 +1565,7 @@ class PlaygroundServer {
|
|
|
1082
1565
|
error: error instanceof Error ? error.message : 'No active session'
|
|
1083
1566
|
});
|
|
1084
1567
|
}
|
|
1085
|
-
const { type, prompt, params, requestId, deepLocate, deepThink, screenshotIncluded, domIncluded, deviceOptions } = req.body;
|
|
1568
|
+
const { type, prompt, params, requestId, deepLocate, deepThink, screenshotIncluded, domIncluded, deviceOptions, reportDisplay } = req.body;
|
|
1086
1569
|
if (!type) return res.status(400).json({
|
|
1087
1570
|
error: 'type is required'
|
|
1088
1571
|
});
|
|
@@ -1131,6 +1614,7 @@ class PlaygroundServer {
|
|
|
1131
1614
|
};
|
|
1132
1615
|
const startTime = Date.now();
|
|
1133
1616
|
try {
|
|
1617
|
+
agent.resetDump?.();
|
|
1134
1618
|
await this._activeConnection.executionHooks?.beforeExecute?.();
|
|
1135
1619
|
const actionSpace = agent.interface.actionSpace();
|
|
1136
1620
|
const value = {
|
|
@@ -1143,7 +1627,8 @@ class PlaygroundServer {
|
|
|
1143
1627
|
deepThink,
|
|
1144
1628
|
screenshotIncluded,
|
|
1145
1629
|
domIncluded,
|
|
1146
|
-
deviceOptions
|
|
1630
|
+
deviceOptions,
|
|
1631
|
+
reportDisplay
|
|
1147
1632
|
});
|
|
1148
1633
|
} catch (error) {
|
|
1149
1634
|
response.error = (0, external_common_js_namespaceObject.formatErrorMessage)(error);
|
|
@@ -1160,7 +1645,7 @@ class PlaygroundServer {
|
|
|
1160
1645
|
});
|
|
1161
1646
|
if (dumpString) {
|
|
1162
1647
|
const groupedDump = core_namespaceObject.ReportActionDump.fromSerializedString(dumpString);
|
|
1163
|
-
response.dump = groupedDump
|
|
1648
|
+
response.dump = groupedDump;
|
|
1164
1649
|
} else response.dump = null;
|
|
1165
1650
|
response.reportHTML = agent.reportHTMLString({
|
|
1166
1651
|
inlineScreenshots: true
|
|
@@ -1200,7 +1685,7 @@ class PlaygroundServer {
|
|
|
1200
1685
|
});
|
|
1201
1686
|
if (dumpString) {
|
|
1202
1687
|
const groupedDump = core_namespaceObject.ReportActionDump.fromSerializedString(dumpString);
|
|
1203
|
-
dump = groupedDump
|
|
1688
|
+
dump = groupedDump;
|
|
1204
1689
|
}
|
|
1205
1690
|
reportHTML = agent.reportHTMLString?.({
|
|
1206
1691
|
inlineScreenshots: true
|
|
@@ -1271,6 +1756,7 @@ class PlaygroundServer {
|
|
|
1271
1756
|
res.json(await this.getRecorderCapabilities());
|
|
1272
1757
|
});
|
|
1273
1758
|
this._app.post('/recorder/stop', async (_req, res)=>{
|
|
1759
|
+
await this.waitForRecorderIdle();
|
|
1274
1760
|
this._recorderSessionId = null;
|
|
1275
1761
|
this._studioPreviewRecorderLastScreenshot = void 0;
|
|
1276
1762
|
this._studioPreviewRecorderLastPageState = void 0;
|
|
@@ -1286,6 +1772,38 @@ class PlaygroundServer {
|
|
|
1286
1772
|
nextIndex: this._recorderEvents.length
|
|
1287
1773
|
});
|
|
1288
1774
|
});
|
|
1775
|
+
this._app.post('/recorder/describe-event', async (req, res)=>{
|
|
1776
|
+
const event = req.body?.event;
|
|
1777
|
+
if (!event || 'object' != typeof event) return res.status(400).json({
|
|
1778
|
+
ok: false,
|
|
1779
|
+
error: 'event is required'
|
|
1780
|
+
});
|
|
1781
|
+
try {
|
|
1782
|
+
const agent = this.getActiveAgentOrThrow();
|
|
1783
|
+
const { event: describedEvent, trace } = await this.enrichStudioPreviewRecorderEventWithAiDescribe(event, agent);
|
|
1784
|
+
res.json({
|
|
1785
|
+
ok: true,
|
|
1786
|
+
event: describedEvent,
|
|
1787
|
+
trace
|
|
1788
|
+
});
|
|
1789
|
+
} catch (error) {
|
|
1790
|
+
const startedAt = new Date();
|
|
1791
|
+
const traceBase = createRecorderAiDescribeTraceBase(event);
|
|
1792
|
+
const trace = {
|
|
1793
|
+
...traceBase,
|
|
1794
|
+
status: 'failed',
|
|
1795
|
+
startedAt: startedAt.toISOString(),
|
|
1796
|
+
durationMs: 0,
|
|
1797
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1798
|
+
};
|
|
1799
|
+
debugInteract('recorder aiDescribe trace:', trace);
|
|
1800
|
+
res.status(500).json({
|
|
1801
|
+
ok: false,
|
|
1802
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1803
|
+
trace
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
});
|
|
1289
1807
|
this._app.get('/screenshot', async (_req, res)=>{
|
|
1290
1808
|
try {
|
|
1291
1809
|
let agent = this.getActiveAgentOrThrow();
|
|
@@ -1386,7 +1904,7 @@ class PlaygroundServer {
|
|
|
1386
1904
|
recorderActive: Boolean(this._recorderSessionId),
|
|
1387
1905
|
hasInputPrimitives: Boolean(agent.interface.inputPrimitives)
|
|
1388
1906
|
});
|
|
1389
|
-
const recorderSnapshotBefore =
|
|
1907
|
+
const recorderSnapshotBefore = this.captureCachedRecorderSnapshotBeforeInteract();
|
|
1390
1908
|
const inputPrimitives = agent.interface.inputPrimitives;
|
|
1391
1909
|
if (inputPrimitives) {
|
|
1392
1910
|
await (0, external_pointer_dispatch_js_namespaceObject.dispatchPointer)(inputPrimitives, req.body ?? {}, ()=>agent.interface.size());
|
|
@@ -1394,12 +1912,12 @@ class PlaygroundServer {
|
|
|
1394
1912
|
payload: summarizeInteractPayload(req.body ?? {}),
|
|
1395
1913
|
elapsedMs: Date.now() - interactStartedAt
|
|
1396
1914
|
});
|
|
1397
|
-
|
|
1915
|
+
res.json({});
|
|
1916
|
+
this.queueStudioPreviewRecorderEvent(req.body ?? {}, recorderSnapshotBefore, agent);
|
|
1398
1917
|
debugInteract('manual interact completed %o', {
|
|
1399
1918
|
payload: summarizeInteractPayload(req.body ?? {}),
|
|
1400
1919
|
elapsedMs: Date.now() - interactStartedAt
|
|
1401
1920
|
});
|
|
1402
|
-
res.json({});
|
|
1403
1921
|
return;
|
|
1404
1922
|
}
|
|
1405
1923
|
if (!this.findInteractAction(agent, actionType) && !this.canRunBrowserChromeInteractAction(agent, actionType)) return res.status(404).json({
|
|
@@ -1411,12 +1929,12 @@ class PlaygroundServer {
|
|
|
1411
1929
|
payload: summarizeInteractPayload(req.body ?? {}),
|
|
1412
1930
|
elapsedMs: Date.now() - interactStartedAt
|
|
1413
1931
|
});
|
|
1414
|
-
|
|
1932
|
+
res.json({});
|
|
1933
|
+
this.queueStudioPreviewRecorderEvent(req.body ?? {}, recorderSnapshotBefore, agent);
|
|
1415
1934
|
debugInteract('manual interact completed %o', {
|
|
1416
1935
|
payload: summarizeInteractPayload(req.body ?? {}),
|
|
1417
1936
|
elapsedMs: Date.now() - interactStartedAt
|
|
1418
1937
|
});
|
|
1419
|
-
res.json({});
|
|
1420
1938
|
} catch (error) {
|
|
1421
1939
|
if (error instanceof external_pointer_dispatch_js_namespaceObject.PointerInputError) return res.status(error.statusCode).json({
|
|
1422
1940
|
error: error.message
|
|
@@ -1605,6 +2123,9 @@ class PlaygroundServer {
|
|
|
1605
2123
|
_define_property(this, "_baseSidecars", void 0);
|
|
1606
2124
|
_define_property(this, "_recorderSessionId", null);
|
|
1607
2125
|
_define_property(this, "_recorderEvents", []);
|
|
2126
|
+
_define_property(this, "_recorderEventQueue", Promise.resolve());
|
|
2127
|
+
_define_property(this, "_recorderPendingCaptures", 0);
|
|
2128
|
+
_define_property(this, "_studioPreviewRecorderLastTargetPoint", void 0);
|
|
1608
2129
|
_define_property(this, "_studioPreviewRecorderLastScreenshot", void 0);
|
|
1609
2130
|
_define_property(this, "_studioPreviewRecorderLastPageState", void 0);
|
|
1610
2131
|
_define_property(this, "_activeConnection", {
|