@agenticmail/enterprise 0.5.77 → 0.5.79
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/chunk-7RNT4O5T.js +15198 -0
- package/dist/chunk-AGFOJCSB.js +2191 -0
- package/dist/chunk-CYABMD5B.js +2191 -0
- package/dist/chunk-F4GSFCM3.js +898 -0
- package/dist/chunk-GINZ56GG.js +15035 -0
- package/dist/chunk-NRKB2KGD.js +898 -0
- package/dist/chunk-PZA7YOJE.js +898 -0
- package/dist/chunk-Q3V7VZFQ.js +2191 -0
- package/dist/chunk-RRFB6G6M.js +15198 -0
- package/dist/chunk-VX3VFMVB.js +409 -0
- package/dist/cli.js +1 -1
- package/dist/dashboard/pages/agent-detail.js +491 -2
- package/dist/index.js +4 -3
- package/dist/pw-ai-KPETTB25.js +2212 -0
- package/dist/routes-2T2ZNH3D.js +6642 -0
- package/dist/routes-PDHMCIXU.js +6676 -0
- package/dist/runtime-5ZJYB5PY.js +47 -0
- package/dist/runtime-7HW4GX5L.js +48 -0
- package/dist/runtime-XXDCZZIK.js +48 -0
- package/dist/server-FMP4BFGW.js +12 -0
- package/dist/server-JRHDUNII.js +12 -0
- package/dist/server-QPIMKFK4.js +12 -0
- package/dist/setup-NPFIX7LF.js +20 -0
- package/dist/setup-O5FPRLK4.js +20 -0
- package/dist/setup-S4Z4PPIJ.js +20 -0
- package/package.json +15 -2
- package/src/agent-tools/common.ts +25 -0
- package/src/agent-tools/index.ts +3 -0
- package/src/agent-tools/schema/typebox.ts +25 -0
- package/src/agent-tools/tools/browser-tool.schema.ts +112 -0
- package/src/agent-tools/tools/browser-tool.ts +388 -0
- package/src/agent-tools/tools/gateway.ts +126 -0
- package/src/agent-tools/tools/nodes-utils.ts +80 -0
- package/src/browser/bridge-auth-registry.ts +34 -0
- package/src/browser/bridge-server.ts +93 -0
- package/src/browser/cdp.helpers.ts +180 -0
- package/src/browser/cdp.ts +466 -0
- package/src/browser/chrome.executables.ts +625 -0
- package/src/browser/chrome.profile-decoration.ts +198 -0
- package/src/browser/chrome.ts +349 -0
- package/src/browser/client-actions-core.ts +259 -0
- package/src/browser/client-actions-observe.ts +184 -0
- package/src/browser/client-actions-state.ts +284 -0
- package/src/browser/client-actions-types.ts +16 -0
- package/src/browser/client-actions-url.ts +11 -0
- package/src/browser/client-actions.ts +4 -0
- package/src/browser/client-fetch.ts +253 -0
- package/src/browser/client.ts +337 -0
- package/src/browser/config.ts +296 -0
- package/src/browser/constants.ts +8 -0
- package/src/browser/control-auth.ts +94 -0
- package/src/browser/control-service.ts +81 -0
- package/src/browser/csrf.ts +87 -0
- package/src/browser/enterprise-compat.ts +518 -0
- package/src/browser/extension-relay.ts +834 -0
- package/src/browser/http-auth.ts +63 -0
- package/src/browser/navigation-guard.ts +50 -0
- package/src/browser/paths.ts +49 -0
- package/src/browser/profiles-service.ts +187 -0
- package/src/browser/profiles.ts +113 -0
- package/src/browser/proxy-files.ts +41 -0
- package/src/browser/pw-ai-module.ts +52 -0
- package/src/browser/pw-ai-state.ts +9 -0
- package/src/browser/pw-ai.ts +65 -0
- package/src/browser/pw-role-snapshot.ts +434 -0
- package/src/browser/pw-session.ts +810 -0
- package/src/browser/pw-tools-core.activity.ts +68 -0
- package/src/browser/pw-tools-core.downloads.ts +281 -0
- package/src/browser/pw-tools-core.interactions.ts +646 -0
- package/src/browser/pw-tools-core.responses.ts +124 -0
- package/src/browser/pw-tools-core.shared.ts +70 -0
- package/src/browser/pw-tools-core.snapshot.ts +213 -0
- package/src/browser/pw-tools-core.state.ts +209 -0
- package/src/browser/pw-tools-core.storage.ts +128 -0
- package/src/browser/pw-tools-core.trace.ts +37 -0
- package/src/browser/pw-tools-core.ts +8 -0
- package/src/browser/resolved-config-refresh.ts +59 -0
- package/src/browser/routes/agent.act.shared.ts +52 -0
- package/src/browser/routes/agent.act.ts +575 -0
- package/src/browser/routes/agent.debug.ts +149 -0
- package/src/browser/routes/agent.shared.ts +143 -0
- package/src/browser/routes/agent.snapshot.ts +333 -0
- package/src/browser/routes/agent.storage.ts +451 -0
- package/src/browser/routes/agent.ts +13 -0
- package/src/browser/routes/basic.ts +202 -0
- package/src/browser/routes/dispatcher.ts +126 -0
- package/src/browser/routes/index.ts +11 -0
- package/src/browser/routes/path-output.ts +1 -0
- package/src/browser/routes/tabs.ts +217 -0
- package/src/browser/routes/types.ts +26 -0
- package/src/browser/routes/utils.ts +73 -0
- package/src/browser/screenshot.ts +54 -0
- package/src/browser/server-context.ts +688 -0
- package/src/browser/server-context.types.ts +65 -0
- package/src/browser/server-lifecycle.ts +48 -0
- package/src/browser/server-middleware.ts +37 -0
- package/src/browser/server.ts +110 -0
- package/src/browser/target-id.ts +30 -0
- package/src/browser/trash.ts +21 -0
- package/src/dashboard/pages/agent-detail.js +491 -2
- package/src/engine/agent-routes.ts +246 -0
- package/src/security/external-content.ts +299 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
import type { BrowserFormField } from "../client-actions-core.js";
|
|
2
|
+
import type { BrowserRouteContext } from "../server-context.js";
|
|
3
|
+
import {
|
|
4
|
+
type ActKind,
|
|
5
|
+
isActKind,
|
|
6
|
+
parseClickButton,
|
|
7
|
+
parseClickModifiers,
|
|
8
|
+
} from "./agent.act.shared.js";
|
|
9
|
+
import {
|
|
10
|
+
readBody,
|
|
11
|
+
resolveTargetIdFromBody,
|
|
12
|
+
withPlaywrightRouteContext,
|
|
13
|
+
SELECTOR_UNSUPPORTED_MESSAGE,
|
|
14
|
+
} from "./agent.shared.js";
|
|
15
|
+
import {
|
|
16
|
+
DEFAULT_DOWNLOAD_DIR,
|
|
17
|
+
DEFAULT_UPLOAD_DIR,
|
|
18
|
+
resolvePathWithinRoot,
|
|
19
|
+
resolvePathsWithinRoot,
|
|
20
|
+
} from "./path-output.js";
|
|
21
|
+
import type { BrowserResponse, BrowserRouteRegistrar } from "./types.js";
|
|
22
|
+
import { jsonError, toBoolean, toNumber, toStringArray, toStringOrEmpty } from "./utils.js";
|
|
23
|
+
|
|
24
|
+
function resolveDownloadPathOrRespond(res: BrowserResponse, requestedPath: string): string | null {
|
|
25
|
+
const downloadPathResult = resolvePathWithinRoot({
|
|
26
|
+
rootDir: DEFAULT_DOWNLOAD_DIR,
|
|
27
|
+
requestedPath,
|
|
28
|
+
scopeLabel: "downloads directory",
|
|
29
|
+
});
|
|
30
|
+
if (!downloadPathResult.ok) {
|
|
31
|
+
res.status(400).json({ error: downloadPathResult.error });
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return downloadPathResult.path;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function buildDownloadRequestBase(cdpUrl: string, targetId: string, timeoutMs: number | undefined) {
|
|
38
|
+
return {
|
|
39
|
+
cdpUrl,
|
|
40
|
+
targetId,
|
|
41
|
+
timeoutMs: timeoutMs ?? undefined,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function respondWithDownloadResult(res: BrowserResponse, targetId: string, result: unknown) {
|
|
46
|
+
res.json({ ok: true, targetId, download: result });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function registerBrowserAgentActRoutes(
|
|
50
|
+
app: BrowserRouteRegistrar,
|
|
51
|
+
ctx: BrowserRouteContext,
|
|
52
|
+
) {
|
|
53
|
+
app.post("/act", async (req, res) => {
|
|
54
|
+
const body = readBody(req);
|
|
55
|
+
const kindRaw = toStringOrEmpty(body.kind);
|
|
56
|
+
if (!isActKind(kindRaw)) {
|
|
57
|
+
return jsonError(res, 400, "kind is required");
|
|
58
|
+
}
|
|
59
|
+
const kind: ActKind = kindRaw;
|
|
60
|
+
const targetId = resolveTargetIdFromBody(body);
|
|
61
|
+
if (Object.hasOwn(body, "selector") && kind !== "wait") {
|
|
62
|
+
return jsonError(res, 400, SELECTOR_UNSUPPORTED_MESSAGE);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await withPlaywrightRouteContext({
|
|
66
|
+
req,
|
|
67
|
+
res,
|
|
68
|
+
ctx,
|
|
69
|
+
targetId,
|
|
70
|
+
feature: `act:${kind}`,
|
|
71
|
+
run: async ({ cdpUrl, tab, pw }) => {
|
|
72
|
+
const evaluateEnabled = ctx.state().resolved.evaluateEnabled;
|
|
73
|
+
|
|
74
|
+
switch (kind) {
|
|
75
|
+
case "click": {
|
|
76
|
+
const ref = toStringOrEmpty(body.ref);
|
|
77
|
+
if (!ref) {
|
|
78
|
+
return jsonError(res, 400, "ref is required");
|
|
79
|
+
}
|
|
80
|
+
const doubleClick = toBoolean(body.doubleClick) ?? false;
|
|
81
|
+
const timeoutMs = toNumber(body.timeoutMs);
|
|
82
|
+
const buttonRaw = toStringOrEmpty(body.button) || "";
|
|
83
|
+
const button = buttonRaw ? parseClickButton(buttonRaw) : undefined;
|
|
84
|
+
if (buttonRaw && !button) {
|
|
85
|
+
return jsonError(res, 400, "button must be left|right|middle");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const modifiersRaw = toStringArray(body.modifiers) ?? [];
|
|
89
|
+
const parsedModifiers = parseClickModifiers(modifiersRaw);
|
|
90
|
+
if (parsedModifiers.error) {
|
|
91
|
+
return jsonError(res, 400, parsedModifiers.error);
|
|
92
|
+
}
|
|
93
|
+
const modifiers = parsedModifiers.modifiers;
|
|
94
|
+
const clickRequest: Parameters<typeof pw.clickViaPlaywright>[0] = {
|
|
95
|
+
cdpUrl,
|
|
96
|
+
targetId: tab.targetId,
|
|
97
|
+
ref,
|
|
98
|
+
doubleClick,
|
|
99
|
+
};
|
|
100
|
+
if (button) {
|
|
101
|
+
clickRequest.button = button;
|
|
102
|
+
}
|
|
103
|
+
if (modifiers) {
|
|
104
|
+
clickRequest.modifiers = modifiers;
|
|
105
|
+
}
|
|
106
|
+
if (timeoutMs) {
|
|
107
|
+
clickRequest.timeoutMs = timeoutMs;
|
|
108
|
+
}
|
|
109
|
+
await pw.clickViaPlaywright(clickRequest);
|
|
110
|
+
return res.json({ ok: true, targetId: tab.targetId, url: tab.url });
|
|
111
|
+
}
|
|
112
|
+
case "type": {
|
|
113
|
+
const ref = toStringOrEmpty(body.ref);
|
|
114
|
+
if (!ref) {
|
|
115
|
+
return jsonError(res, 400, "ref is required");
|
|
116
|
+
}
|
|
117
|
+
if (typeof body.text !== "string") {
|
|
118
|
+
return jsonError(res, 400, "text is required");
|
|
119
|
+
}
|
|
120
|
+
const text = body.text;
|
|
121
|
+
const submit = toBoolean(body.submit) ?? false;
|
|
122
|
+
const slowly = toBoolean(body.slowly) ?? false;
|
|
123
|
+
const timeoutMs = toNumber(body.timeoutMs);
|
|
124
|
+
const typeRequest: Parameters<typeof pw.typeViaPlaywright>[0] = {
|
|
125
|
+
cdpUrl,
|
|
126
|
+
targetId: tab.targetId,
|
|
127
|
+
ref,
|
|
128
|
+
text,
|
|
129
|
+
submit,
|
|
130
|
+
slowly,
|
|
131
|
+
};
|
|
132
|
+
if (timeoutMs) {
|
|
133
|
+
typeRequest.timeoutMs = timeoutMs;
|
|
134
|
+
}
|
|
135
|
+
await pw.typeViaPlaywright(typeRequest);
|
|
136
|
+
return res.json({ ok: true, targetId: tab.targetId });
|
|
137
|
+
}
|
|
138
|
+
case "press": {
|
|
139
|
+
const key = toStringOrEmpty(body.key);
|
|
140
|
+
if (!key) {
|
|
141
|
+
return jsonError(res, 400, "key is required");
|
|
142
|
+
}
|
|
143
|
+
const delayMs = toNumber(body.delayMs);
|
|
144
|
+
await pw.pressKeyViaPlaywright({
|
|
145
|
+
cdpUrl,
|
|
146
|
+
targetId: tab.targetId,
|
|
147
|
+
key,
|
|
148
|
+
delayMs: delayMs ?? undefined,
|
|
149
|
+
});
|
|
150
|
+
return res.json({ ok: true, targetId: tab.targetId });
|
|
151
|
+
}
|
|
152
|
+
case "hover": {
|
|
153
|
+
const ref = toStringOrEmpty(body.ref);
|
|
154
|
+
if (!ref) {
|
|
155
|
+
return jsonError(res, 400, "ref is required");
|
|
156
|
+
}
|
|
157
|
+
const timeoutMs = toNumber(body.timeoutMs);
|
|
158
|
+
await pw.hoverViaPlaywright({
|
|
159
|
+
cdpUrl,
|
|
160
|
+
targetId: tab.targetId,
|
|
161
|
+
ref,
|
|
162
|
+
timeoutMs: timeoutMs ?? undefined,
|
|
163
|
+
});
|
|
164
|
+
return res.json({ ok: true, targetId: tab.targetId });
|
|
165
|
+
}
|
|
166
|
+
case "scrollIntoView": {
|
|
167
|
+
const ref = toStringOrEmpty(body.ref);
|
|
168
|
+
if (!ref) {
|
|
169
|
+
return jsonError(res, 400, "ref is required");
|
|
170
|
+
}
|
|
171
|
+
const timeoutMs = toNumber(body.timeoutMs);
|
|
172
|
+
const scrollRequest: Parameters<typeof pw.scrollIntoViewViaPlaywright>[0] = {
|
|
173
|
+
cdpUrl,
|
|
174
|
+
targetId: tab.targetId,
|
|
175
|
+
ref,
|
|
176
|
+
};
|
|
177
|
+
if (timeoutMs) {
|
|
178
|
+
scrollRequest.timeoutMs = timeoutMs;
|
|
179
|
+
}
|
|
180
|
+
await pw.scrollIntoViewViaPlaywright(scrollRequest);
|
|
181
|
+
return res.json({ ok: true, targetId: tab.targetId });
|
|
182
|
+
}
|
|
183
|
+
case "drag": {
|
|
184
|
+
const startRef = toStringOrEmpty(body.startRef);
|
|
185
|
+
const endRef = toStringOrEmpty(body.endRef);
|
|
186
|
+
if (!startRef || !endRef) {
|
|
187
|
+
return jsonError(res, 400, "startRef and endRef are required");
|
|
188
|
+
}
|
|
189
|
+
const timeoutMs = toNumber(body.timeoutMs);
|
|
190
|
+
await pw.dragViaPlaywright({
|
|
191
|
+
cdpUrl,
|
|
192
|
+
targetId: tab.targetId,
|
|
193
|
+
startRef,
|
|
194
|
+
endRef,
|
|
195
|
+
timeoutMs: timeoutMs ?? undefined,
|
|
196
|
+
});
|
|
197
|
+
return res.json({ ok: true, targetId: tab.targetId });
|
|
198
|
+
}
|
|
199
|
+
case "select": {
|
|
200
|
+
const ref = toStringOrEmpty(body.ref);
|
|
201
|
+
const values = toStringArray(body.values);
|
|
202
|
+
if (!ref || !values?.length) {
|
|
203
|
+
return jsonError(res, 400, "ref and values are required");
|
|
204
|
+
}
|
|
205
|
+
const timeoutMs = toNumber(body.timeoutMs);
|
|
206
|
+
await pw.selectOptionViaPlaywright({
|
|
207
|
+
cdpUrl,
|
|
208
|
+
targetId: tab.targetId,
|
|
209
|
+
ref,
|
|
210
|
+
values,
|
|
211
|
+
timeoutMs: timeoutMs ?? undefined,
|
|
212
|
+
});
|
|
213
|
+
return res.json({ ok: true, targetId: tab.targetId });
|
|
214
|
+
}
|
|
215
|
+
case "fill": {
|
|
216
|
+
const rawFields = Array.isArray(body.fields) ? body.fields : [];
|
|
217
|
+
const fields = rawFields
|
|
218
|
+
.map((field) => {
|
|
219
|
+
if (!field || typeof field !== "object") {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
const rec = field as Record<string, unknown>;
|
|
223
|
+
const ref = toStringOrEmpty(rec.ref);
|
|
224
|
+
const type = toStringOrEmpty(rec.type);
|
|
225
|
+
if (!ref || !type) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
const value =
|
|
229
|
+
typeof rec.value === "string" ||
|
|
230
|
+
typeof rec.value === "number" ||
|
|
231
|
+
typeof rec.value === "boolean"
|
|
232
|
+
? rec.value
|
|
233
|
+
: undefined;
|
|
234
|
+
const parsed: BrowserFormField =
|
|
235
|
+
value === undefined ? { ref, type } : { ref, type, value };
|
|
236
|
+
return parsed;
|
|
237
|
+
})
|
|
238
|
+
.filter((field): field is BrowserFormField => field !== null);
|
|
239
|
+
if (!fields.length) {
|
|
240
|
+
return jsonError(res, 400, "fields are required");
|
|
241
|
+
}
|
|
242
|
+
const timeoutMs = toNumber(body.timeoutMs);
|
|
243
|
+
await pw.fillFormViaPlaywright({
|
|
244
|
+
cdpUrl,
|
|
245
|
+
targetId: tab.targetId,
|
|
246
|
+
fields,
|
|
247
|
+
timeoutMs: timeoutMs ?? undefined,
|
|
248
|
+
});
|
|
249
|
+
return res.json({ ok: true, targetId: tab.targetId });
|
|
250
|
+
}
|
|
251
|
+
case "resize": {
|
|
252
|
+
const width = toNumber(body.width);
|
|
253
|
+
const height = toNumber(body.height);
|
|
254
|
+
if (!width || !height) {
|
|
255
|
+
return jsonError(res, 400, "width and height are required");
|
|
256
|
+
}
|
|
257
|
+
await pw.resizeViewportViaPlaywright({
|
|
258
|
+
cdpUrl,
|
|
259
|
+
targetId: tab.targetId,
|
|
260
|
+
width,
|
|
261
|
+
height,
|
|
262
|
+
});
|
|
263
|
+
return res.json({ ok: true, targetId: tab.targetId, url: tab.url });
|
|
264
|
+
}
|
|
265
|
+
case "wait": {
|
|
266
|
+
const timeMs = toNumber(body.timeMs);
|
|
267
|
+
const text = toStringOrEmpty(body.text) || undefined;
|
|
268
|
+
const textGone = toStringOrEmpty(body.textGone) || undefined;
|
|
269
|
+
const selector = toStringOrEmpty(body.selector) || undefined;
|
|
270
|
+
const url = toStringOrEmpty(body.url) || undefined;
|
|
271
|
+
const loadStateRaw = toStringOrEmpty(body.loadState);
|
|
272
|
+
const loadState =
|
|
273
|
+
loadStateRaw === "load" ||
|
|
274
|
+
loadStateRaw === "domcontentloaded" ||
|
|
275
|
+
loadStateRaw === "networkidle"
|
|
276
|
+
? loadStateRaw
|
|
277
|
+
: undefined;
|
|
278
|
+
const fn = toStringOrEmpty(body.fn) || undefined;
|
|
279
|
+
const timeoutMs = toNumber(body.timeoutMs) ?? undefined;
|
|
280
|
+
if (fn && !evaluateEnabled) {
|
|
281
|
+
return jsonError(
|
|
282
|
+
res,
|
|
283
|
+
403,
|
|
284
|
+
[
|
|
285
|
+
"wait --fn is disabled by config (browser.evaluateEnabled=false).",
|
|
286
|
+
"Docs: /gateway/configuration#browser-openclaw-managed-browser",
|
|
287
|
+
].join("\n"),
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (
|
|
291
|
+
timeMs === undefined &&
|
|
292
|
+
!text &&
|
|
293
|
+
!textGone &&
|
|
294
|
+
!selector &&
|
|
295
|
+
!url &&
|
|
296
|
+
!loadState &&
|
|
297
|
+
!fn
|
|
298
|
+
) {
|
|
299
|
+
return jsonError(
|
|
300
|
+
res,
|
|
301
|
+
400,
|
|
302
|
+
"wait requires at least one of: timeMs, text, textGone, selector, url, loadState, fn",
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
await pw.waitForViaPlaywright({
|
|
306
|
+
cdpUrl,
|
|
307
|
+
targetId: tab.targetId,
|
|
308
|
+
timeMs,
|
|
309
|
+
text,
|
|
310
|
+
textGone,
|
|
311
|
+
selector,
|
|
312
|
+
url,
|
|
313
|
+
loadState,
|
|
314
|
+
fn,
|
|
315
|
+
timeoutMs,
|
|
316
|
+
});
|
|
317
|
+
return res.json({ ok: true, targetId: tab.targetId });
|
|
318
|
+
}
|
|
319
|
+
case "evaluate": {
|
|
320
|
+
if (!evaluateEnabled) {
|
|
321
|
+
return jsonError(
|
|
322
|
+
res,
|
|
323
|
+
403,
|
|
324
|
+
[
|
|
325
|
+
"act:evaluate is disabled by config (browser.evaluateEnabled=false).",
|
|
326
|
+
"Docs: /gateway/configuration#browser-openclaw-managed-browser",
|
|
327
|
+
].join("\n"),
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
const fn = toStringOrEmpty(body.fn);
|
|
331
|
+
if (!fn) {
|
|
332
|
+
return jsonError(res, 400, "fn is required");
|
|
333
|
+
}
|
|
334
|
+
const ref = toStringOrEmpty(body.ref) || undefined;
|
|
335
|
+
const evalTimeoutMs = toNumber(body.timeoutMs);
|
|
336
|
+
const evalRequest: Parameters<typeof pw.evaluateViaPlaywright>[0] = {
|
|
337
|
+
cdpUrl,
|
|
338
|
+
targetId: tab.targetId,
|
|
339
|
+
fn,
|
|
340
|
+
ref,
|
|
341
|
+
signal: req.signal,
|
|
342
|
+
};
|
|
343
|
+
if (evalTimeoutMs !== undefined) {
|
|
344
|
+
evalRequest.timeoutMs = evalTimeoutMs;
|
|
345
|
+
}
|
|
346
|
+
const result = await pw.evaluateViaPlaywright(evalRequest);
|
|
347
|
+
return res.json({
|
|
348
|
+
ok: true,
|
|
349
|
+
targetId: tab.targetId,
|
|
350
|
+
url: tab.url,
|
|
351
|
+
result,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
case "close": {
|
|
355
|
+
await pw.closePageViaPlaywright({ cdpUrl, targetId: tab.targetId });
|
|
356
|
+
return res.json({ ok: true, targetId: tab.targetId });
|
|
357
|
+
}
|
|
358
|
+
default: {
|
|
359
|
+
return jsonError(res, 400, "unsupported kind");
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
app.post("/hooks/file-chooser", async (req, res) => {
|
|
367
|
+
const body = readBody(req);
|
|
368
|
+
const targetId = resolveTargetIdFromBody(body);
|
|
369
|
+
const ref = toStringOrEmpty(body.ref) || undefined;
|
|
370
|
+
const inputRef = toStringOrEmpty(body.inputRef) || undefined;
|
|
371
|
+
const element = toStringOrEmpty(body.element) || undefined;
|
|
372
|
+
const paths = toStringArray(body.paths) ?? [];
|
|
373
|
+
const timeoutMs = toNumber(body.timeoutMs);
|
|
374
|
+
if (!paths.length) {
|
|
375
|
+
return jsonError(res, 400, "paths are required");
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
await withPlaywrightRouteContext({
|
|
379
|
+
req,
|
|
380
|
+
res,
|
|
381
|
+
ctx,
|
|
382
|
+
targetId,
|
|
383
|
+
feature: "file chooser hook",
|
|
384
|
+
run: async ({ cdpUrl, tab, pw }) => {
|
|
385
|
+
const uploadPathsResult = resolvePathsWithinRoot({
|
|
386
|
+
rootDir: DEFAULT_UPLOAD_DIR,
|
|
387
|
+
requestedPaths: paths,
|
|
388
|
+
scopeLabel: `uploads directory (${DEFAULT_UPLOAD_DIR})`,
|
|
389
|
+
});
|
|
390
|
+
if (!uploadPathsResult.ok) {
|
|
391
|
+
res.status(400).json({ error: uploadPathsResult.error });
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const resolvedPaths = uploadPathsResult.paths;
|
|
395
|
+
|
|
396
|
+
if (inputRef || element) {
|
|
397
|
+
if (ref) {
|
|
398
|
+
return jsonError(res, 400, "ref cannot be combined with inputRef/element");
|
|
399
|
+
}
|
|
400
|
+
await pw.setInputFilesViaPlaywright({
|
|
401
|
+
cdpUrl,
|
|
402
|
+
targetId: tab.targetId,
|
|
403
|
+
inputRef,
|
|
404
|
+
element,
|
|
405
|
+
paths: resolvedPaths,
|
|
406
|
+
});
|
|
407
|
+
} else {
|
|
408
|
+
await pw.armFileUploadViaPlaywright({
|
|
409
|
+
cdpUrl,
|
|
410
|
+
targetId: tab.targetId,
|
|
411
|
+
paths: resolvedPaths,
|
|
412
|
+
timeoutMs: timeoutMs ?? undefined,
|
|
413
|
+
});
|
|
414
|
+
if (ref) {
|
|
415
|
+
await pw.clickViaPlaywright({
|
|
416
|
+
cdpUrl,
|
|
417
|
+
targetId: tab.targetId,
|
|
418
|
+
ref,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
res.json({ ok: true });
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
app.post("/hooks/dialog", async (req, res) => {
|
|
428
|
+
const body = readBody(req);
|
|
429
|
+
const targetId = resolveTargetIdFromBody(body);
|
|
430
|
+
const accept = toBoolean(body.accept);
|
|
431
|
+
const promptText = toStringOrEmpty(body.promptText) || undefined;
|
|
432
|
+
const timeoutMs = toNumber(body.timeoutMs);
|
|
433
|
+
if (accept === undefined) {
|
|
434
|
+
return jsonError(res, 400, "accept is required");
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
await withPlaywrightRouteContext({
|
|
438
|
+
req,
|
|
439
|
+
res,
|
|
440
|
+
ctx,
|
|
441
|
+
targetId,
|
|
442
|
+
feature: "dialog hook",
|
|
443
|
+
run: async ({ cdpUrl, tab, pw }) => {
|
|
444
|
+
await pw.armDialogViaPlaywright({
|
|
445
|
+
cdpUrl,
|
|
446
|
+
targetId: tab.targetId,
|
|
447
|
+
accept,
|
|
448
|
+
promptText,
|
|
449
|
+
timeoutMs: timeoutMs ?? undefined,
|
|
450
|
+
});
|
|
451
|
+
res.json({ ok: true });
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
app.post("/wait/download", async (req, res) => {
|
|
457
|
+
const body = readBody(req);
|
|
458
|
+
const targetId = resolveTargetIdFromBody(body);
|
|
459
|
+
const out = toStringOrEmpty(body.path) || "";
|
|
460
|
+
const timeoutMs = toNumber(body.timeoutMs);
|
|
461
|
+
|
|
462
|
+
await withPlaywrightRouteContext({
|
|
463
|
+
req,
|
|
464
|
+
res,
|
|
465
|
+
ctx,
|
|
466
|
+
targetId,
|
|
467
|
+
feature: "wait for download",
|
|
468
|
+
run: async ({ cdpUrl, tab, pw }) => {
|
|
469
|
+
let downloadPath: string | undefined;
|
|
470
|
+
if (out.trim()) {
|
|
471
|
+
const resolvedDownloadPath = resolveDownloadPathOrRespond(res, out);
|
|
472
|
+
if (!resolvedDownloadPath) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
downloadPath = resolvedDownloadPath;
|
|
476
|
+
}
|
|
477
|
+
const requestBase = buildDownloadRequestBase(cdpUrl, tab.targetId, timeoutMs);
|
|
478
|
+
const result = await pw.waitForDownloadViaPlaywright({
|
|
479
|
+
...requestBase,
|
|
480
|
+
path: downloadPath,
|
|
481
|
+
});
|
|
482
|
+
respondWithDownloadResult(res, tab.targetId, result);
|
|
483
|
+
},
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
app.post("/download", async (req, res) => {
|
|
488
|
+
const body = readBody(req);
|
|
489
|
+
const targetId = resolveTargetIdFromBody(body);
|
|
490
|
+
const ref = toStringOrEmpty(body.ref);
|
|
491
|
+
const out = toStringOrEmpty(body.path);
|
|
492
|
+
const timeoutMs = toNumber(body.timeoutMs);
|
|
493
|
+
if (!ref) {
|
|
494
|
+
return jsonError(res, 400, "ref is required");
|
|
495
|
+
}
|
|
496
|
+
if (!out) {
|
|
497
|
+
return jsonError(res, 400, "path is required");
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
await withPlaywrightRouteContext({
|
|
501
|
+
req,
|
|
502
|
+
res,
|
|
503
|
+
ctx,
|
|
504
|
+
targetId,
|
|
505
|
+
feature: "download",
|
|
506
|
+
run: async ({ cdpUrl, tab, pw }) => {
|
|
507
|
+
const downloadPath = resolveDownloadPathOrRespond(res, out);
|
|
508
|
+
if (!downloadPath) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
const requestBase = buildDownloadRequestBase(cdpUrl, tab.targetId, timeoutMs);
|
|
512
|
+
const result = await pw.downloadViaPlaywright({
|
|
513
|
+
...requestBase,
|
|
514
|
+
ref,
|
|
515
|
+
path: downloadPath,
|
|
516
|
+
});
|
|
517
|
+
respondWithDownloadResult(res, tab.targetId, result);
|
|
518
|
+
},
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
app.post("/response/body", async (req, res) => {
|
|
523
|
+
const body = readBody(req);
|
|
524
|
+
const targetId = resolveTargetIdFromBody(body);
|
|
525
|
+
const url = toStringOrEmpty(body.url);
|
|
526
|
+
const timeoutMs = toNumber(body.timeoutMs);
|
|
527
|
+
const maxChars = toNumber(body.maxChars);
|
|
528
|
+
if (!url) {
|
|
529
|
+
return jsonError(res, 400, "url is required");
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
await withPlaywrightRouteContext({
|
|
533
|
+
req,
|
|
534
|
+
res,
|
|
535
|
+
ctx,
|
|
536
|
+
targetId,
|
|
537
|
+
feature: "response body",
|
|
538
|
+
run: async ({ cdpUrl, tab, pw }) => {
|
|
539
|
+
const result = await pw.responseBodyViaPlaywright({
|
|
540
|
+
cdpUrl,
|
|
541
|
+
targetId: tab.targetId,
|
|
542
|
+
url,
|
|
543
|
+
timeoutMs: timeoutMs ?? undefined,
|
|
544
|
+
maxChars: maxChars ?? undefined,
|
|
545
|
+
});
|
|
546
|
+
res.json({ ok: true, targetId: tab.targetId, response: result });
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
app.post("/highlight", async (req, res) => {
|
|
552
|
+
const body = readBody(req);
|
|
553
|
+
const targetId = resolveTargetIdFromBody(body);
|
|
554
|
+
const ref = toStringOrEmpty(body.ref);
|
|
555
|
+
if (!ref) {
|
|
556
|
+
return jsonError(res, 400, "ref is required");
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
await withPlaywrightRouteContext({
|
|
560
|
+
req,
|
|
561
|
+
res,
|
|
562
|
+
ctx,
|
|
563
|
+
targetId,
|
|
564
|
+
feature: "highlight",
|
|
565
|
+
run: async ({ cdpUrl, tab, pw }) => {
|
|
566
|
+
await pw.highlightViaPlaywright({
|
|
567
|
+
cdpUrl,
|
|
568
|
+
targetId: tab.targetId,
|
|
569
|
+
ref,
|
|
570
|
+
});
|
|
571
|
+
res.json({ ok: true, targetId: tab.targetId });
|
|
572
|
+
},
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
}
|