mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 08:17:40 +00:00
refactor(browser): reuse shared route context in agent act routes
This commit is contained in:
@@ -7,10 +7,9 @@ import {
|
|||||||
parseClickModifiers,
|
parseClickModifiers,
|
||||||
} from "./agent.act.shared.js";
|
} from "./agent.act.shared.js";
|
||||||
import {
|
import {
|
||||||
handleRouteError,
|
|
||||||
readBody,
|
readBody,
|
||||||
requirePwAi,
|
resolveTargetIdFromBody,
|
||||||
resolveProfileContext,
|
withPlaywrightRouteContext,
|
||||||
SELECTOR_UNSUPPORTED_MESSAGE,
|
SELECTOR_UNSUPPORTED_MESSAGE,
|
||||||
} from "./agent.shared.js";
|
} from "./agent.shared.js";
|
||||||
import {
|
import {
|
||||||
@@ -27,28 +26,24 @@ export function registerBrowserAgentActRoutes(
|
|||||||
ctx: BrowserRouteContext,
|
ctx: BrowserRouteContext,
|
||||||
) {
|
) {
|
||||||
app.post("/act", async (req, res) => {
|
app.post("/act", async (req, res) => {
|
||||||
const profileCtx = resolveProfileContext(req, res, ctx);
|
|
||||||
if (!profileCtx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const body = readBody(req);
|
const body = readBody(req);
|
||||||
const kindRaw = toStringOrEmpty(body.kind);
|
const kindRaw = toStringOrEmpty(body.kind);
|
||||||
if (!isActKind(kindRaw)) {
|
if (!isActKind(kindRaw)) {
|
||||||
return jsonError(res, 400, "kind is required");
|
return jsonError(res, 400, "kind is required");
|
||||||
}
|
}
|
||||||
const kind: ActKind = kindRaw;
|
const kind: ActKind = kindRaw;
|
||||||
const targetId = toStringOrEmpty(body.targetId) || undefined;
|
const targetId = resolveTargetIdFromBody(body);
|
||||||
if (Object.hasOwn(body, "selector") && kind !== "wait") {
|
if (Object.hasOwn(body, "selector") && kind !== "wait") {
|
||||||
return jsonError(res, 400, SELECTOR_UNSUPPORTED_MESSAGE);
|
return jsonError(res, 400, SELECTOR_UNSUPPORTED_MESSAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
await withPlaywrightRouteContext({
|
||||||
const tab = await profileCtx.ensureTabAvailable(targetId);
|
req,
|
||||||
const cdpUrl = profileCtx.profile.cdpUrl;
|
res,
|
||||||
const pw = await requirePwAi(res, `act:${kind}`);
|
ctx,
|
||||||
if (!pw) {
|
targetId,
|
||||||
return;
|
feature: `act:${kind}`,
|
||||||
}
|
run: async ({ cdpUrl, tab, pw }) => {
|
||||||
const evaluateEnabled = ctx.state().resolved.evaluateEnabled;
|
const evaluateEnabled = ctx.state().resolved.evaluateEnabled;
|
||||||
|
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
@@ -339,18 +334,13 @@ export function registerBrowserAgentActRoutes(
|
|||||||
return jsonError(res, 400, "unsupported kind");
|
return jsonError(res, 400, "unsupported kind");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
},
|
||||||
handleRouteError(ctx, res, err);
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/hooks/file-chooser", async (req, res) => {
|
app.post("/hooks/file-chooser", async (req, res) => {
|
||||||
const profileCtx = resolveProfileContext(req, res, ctx);
|
|
||||||
if (!profileCtx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const body = readBody(req);
|
const body = readBody(req);
|
||||||
const targetId = toStringOrEmpty(body.targetId) || undefined;
|
const targetId = resolveTargetIdFromBody(body);
|
||||||
const ref = toStringOrEmpty(body.ref) || undefined;
|
const ref = toStringOrEmpty(body.ref) || undefined;
|
||||||
const inputRef = toStringOrEmpty(body.inputRef) || undefined;
|
const inputRef = toStringOrEmpty(body.inputRef) || undefined;
|
||||||
const element = toStringOrEmpty(body.element) || undefined;
|
const element = toStringOrEmpty(body.element) || undefined;
|
||||||
@@ -359,7 +349,14 @@ export function registerBrowserAgentActRoutes(
|
|||||||
if (!paths.length) {
|
if (!paths.length) {
|
||||||
return jsonError(res, 400, "paths are required");
|
return jsonError(res, 400, "paths are required");
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
|
await withPlaywrightRouteContext({
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
ctx,
|
||||||
|
targetId,
|
||||||
|
feature: "file chooser hook",
|
||||||
|
run: async ({ cdpUrl, tab, pw }) => {
|
||||||
const uploadPathsResult = resolvePathsWithinRoot({
|
const uploadPathsResult = resolvePathsWithinRoot({
|
||||||
rootDir: DEFAULT_UPLOAD_DIR,
|
rootDir: DEFAULT_UPLOAD_DIR,
|
||||||
requestedPaths: paths,
|
requestedPaths: paths,
|
||||||
@@ -371,17 +368,12 @@ export function registerBrowserAgentActRoutes(
|
|||||||
}
|
}
|
||||||
const resolvedPaths = uploadPathsResult.paths;
|
const resolvedPaths = uploadPathsResult.paths;
|
||||||
|
|
||||||
const tab = await profileCtx.ensureTabAvailable(targetId);
|
|
||||||
const pw = await requirePwAi(res, "file chooser hook");
|
|
||||||
if (!pw) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (inputRef || element) {
|
if (inputRef || element) {
|
||||||
if (ref) {
|
if (ref) {
|
||||||
return jsonError(res, 400, "ref cannot be combined with inputRef/element");
|
return jsonError(res, 400, "ref cannot be combined with inputRef/element");
|
||||||
}
|
}
|
||||||
await pw.setInputFilesViaPlaywright({
|
await pw.setInputFilesViaPlaywright({
|
||||||
cdpUrl: profileCtx.profile.cdpUrl,
|
cdpUrl,
|
||||||
targetId: tab.targetId,
|
targetId: tab.targetId,
|
||||||
inputRef,
|
inputRef,
|
||||||
element,
|
element,
|
||||||
@@ -389,72 +381,66 @@ export function registerBrowserAgentActRoutes(
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await pw.armFileUploadViaPlaywright({
|
await pw.armFileUploadViaPlaywright({
|
||||||
cdpUrl: profileCtx.profile.cdpUrl,
|
cdpUrl,
|
||||||
targetId: tab.targetId,
|
targetId: tab.targetId,
|
||||||
paths: resolvedPaths,
|
paths: resolvedPaths,
|
||||||
timeoutMs: timeoutMs ?? undefined,
|
timeoutMs: timeoutMs ?? undefined,
|
||||||
});
|
});
|
||||||
if (ref) {
|
if (ref) {
|
||||||
await pw.clickViaPlaywright({
|
await pw.clickViaPlaywright({
|
||||||
cdpUrl: profileCtx.profile.cdpUrl,
|
cdpUrl,
|
||||||
targetId: tab.targetId,
|
targetId: tab.targetId,
|
||||||
ref,
|
ref,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
} catch (err) {
|
},
|
||||||
handleRouteError(ctx, res, err);
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/hooks/dialog", async (req, res) => {
|
app.post("/hooks/dialog", async (req, res) => {
|
||||||
const profileCtx = resolveProfileContext(req, res, ctx);
|
|
||||||
if (!profileCtx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const body = readBody(req);
|
const body = readBody(req);
|
||||||
const targetId = toStringOrEmpty(body.targetId) || undefined;
|
const targetId = resolveTargetIdFromBody(body);
|
||||||
const accept = toBoolean(body.accept);
|
const accept = toBoolean(body.accept);
|
||||||
const promptText = toStringOrEmpty(body.promptText) || undefined;
|
const promptText = toStringOrEmpty(body.promptText) || undefined;
|
||||||
const timeoutMs = toNumber(body.timeoutMs);
|
const timeoutMs = toNumber(body.timeoutMs);
|
||||||
if (accept === undefined) {
|
if (accept === undefined) {
|
||||||
return jsonError(res, 400, "accept is required");
|
return jsonError(res, 400, "accept is required");
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
const tab = await profileCtx.ensureTabAvailable(targetId);
|
await withPlaywrightRouteContext({
|
||||||
const pw = await requirePwAi(res, "dialog hook");
|
req,
|
||||||
if (!pw) {
|
res,
|
||||||
return;
|
ctx,
|
||||||
}
|
targetId,
|
||||||
|
feature: "dialog hook",
|
||||||
|
run: async ({ cdpUrl, tab, pw }) => {
|
||||||
await pw.armDialogViaPlaywright({
|
await pw.armDialogViaPlaywright({
|
||||||
cdpUrl: profileCtx.profile.cdpUrl,
|
cdpUrl,
|
||||||
targetId: tab.targetId,
|
targetId: tab.targetId,
|
||||||
accept,
|
accept,
|
||||||
promptText,
|
promptText,
|
||||||
timeoutMs: timeoutMs ?? undefined,
|
timeoutMs: timeoutMs ?? undefined,
|
||||||
});
|
});
|
||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
} catch (err) {
|
},
|
||||||
handleRouteError(ctx, res, err);
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/wait/download", async (req, res) => {
|
app.post("/wait/download", async (req, res) => {
|
||||||
const profileCtx = resolveProfileContext(req, res, ctx);
|
|
||||||
if (!profileCtx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const body = readBody(req);
|
const body = readBody(req);
|
||||||
const targetId = toStringOrEmpty(body.targetId) || undefined;
|
const targetId = resolveTargetIdFromBody(body);
|
||||||
const out = toStringOrEmpty(body.path) || "";
|
const out = toStringOrEmpty(body.path) || "";
|
||||||
const timeoutMs = toNumber(body.timeoutMs);
|
const timeoutMs = toNumber(body.timeoutMs);
|
||||||
try {
|
|
||||||
const tab = await profileCtx.ensureTabAvailable(targetId);
|
await withPlaywrightRouteContext({
|
||||||
const pw = await requirePwAi(res, "wait for download");
|
req,
|
||||||
if (!pw) {
|
res,
|
||||||
return;
|
ctx,
|
||||||
}
|
targetId,
|
||||||
|
feature: "wait for download",
|
||||||
|
run: async ({ cdpUrl, tab, pw }) => {
|
||||||
let downloadPath: string | undefined;
|
let downloadPath: string | undefined;
|
||||||
if (out.trim()) {
|
if (out.trim()) {
|
||||||
const downloadPathResult = resolvePathWithinRoot({
|
const downloadPathResult = resolvePathWithinRoot({
|
||||||
@@ -469,24 +455,19 @@ export function registerBrowserAgentActRoutes(
|
|||||||
downloadPath = downloadPathResult.path;
|
downloadPath = downloadPathResult.path;
|
||||||
}
|
}
|
||||||
const result = await pw.waitForDownloadViaPlaywright({
|
const result = await pw.waitForDownloadViaPlaywright({
|
||||||
cdpUrl: profileCtx.profile.cdpUrl,
|
cdpUrl,
|
||||||
targetId: tab.targetId,
|
targetId: tab.targetId,
|
||||||
path: downloadPath,
|
path: downloadPath,
|
||||||
timeoutMs: timeoutMs ?? undefined,
|
timeoutMs: timeoutMs ?? undefined,
|
||||||
});
|
});
|
||||||
res.json({ ok: true, targetId: tab.targetId, download: result });
|
res.json({ ok: true, targetId: tab.targetId, download: result });
|
||||||
} catch (err) {
|
},
|
||||||
handleRouteError(ctx, res, err);
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/download", async (req, res) => {
|
app.post("/download", async (req, res) => {
|
||||||
const profileCtx = resolveProfileContext(req, res, ctx);
|
|
||||||
if (!profileCtx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const body = readBody(req);
|
const body = readBody(req);
|
||||||
const targetId = toStringOrEmpty(body.targetId) || undefined;
|
const targetId = resolveTargetIdFromBody(body);
|
||||||
const ref = toStringOrEmpty(body.ref);
|
const ref = toStringOrEmpty(body.ref);
|
||||||
const out = toStringOrEmpty(body.path);
|
const out = toStringOrEmpty(body.path);
|
||||||
const timeoutMs = toNumber(body.timeoutMs);
|
const timeoutMs = toNumber(body.timeoutMs);
|
||||||
@@ -496,7 +477,14 @@ export function registerBrowserAgentActRoutes(
|
|||||||
if (!out) {
|
if (!out) {
|
||||||
return jsonError(res, 400, "path is required");
|
return jsonError(res, 400, "path is required");
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
|
await withPlaywrightRouteContext({
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
ctx,
|
||||||
|
targetId,
|
||||||
|
feature: "download",
|
||||||
|
run: async ({ cdpUrl, tab, pw }) => {
|
||||||
const downloadPathResult = resolvePathWithinRoot({
|
const downloadPathResult = resolvePathWithinRoot({
|
||||||
rootDir: DEFAULT_DOWNLOAD_DIR,
|
rootDir: DEFAULT_DOWNLOAD_DIR,
|
||||||
requestedPath: out,
|
requestedPath: out,
|
||||||
@@ -506,81 +494,69 @@ export function registerBrowserAgentActRoutes(
|
|||||||
res.status(400).json({ error: downloadPathResult.error });
|
res.status(400).json({ error: downloadPathResult.error });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const tab = await profileCtx.ensureTabAvailable(targetId);
|
|
||||||
const pw = await requirePwAi(res, "download");
|
|
||||||
if (!pw) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const result = await pw.downloadViaPlaywright({
|
const result = await pw.downloadViaPlaywright({
|
||||||
cdpUrl: profileCtx.profile.cdpUrl,
|
cdpUrl,
|
||||||
targetId: tab.targetId,
|
targetId: tab.targetId,
|
||||||
ref,
|
ref,
|
||||||
path: downloadPathResult.path,
|
path: downloadPathResult.path,
|
||||||
timeoutMs: timeoutMs ?? undefined,
|
timeoutMs: timeoutMs ?? undefined,
|
||||||
});
|
});
|
||||||
res.json({ ok: true, targetId: tab.targetId, download: result });
|
res.json({ ok: true, targetId: tab.targetId, download: result });
|
||||||
} catch (err) {
|
},
|
||||||
handleRouteError(ctx, res, err);
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/response/body", async (req, res) => {
|
app.post("/response/body", async (req, res) => {
|
||||||
const profileCtx = resolveProfileContext(req, res, ctx);
|
|
||||||
if (!profileCtx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const body = readBody(req);
|
const body = readBody(req);
|
||||||
const targetId = toStringOrEmpty(body.targetId) || undefined;
|
const targetId = resolveTargetIdFromBody(body);
|
||||||
const url = toStringOrEmpty(body.url);
|
const url = toStringOrEmpty(body.url);
|
||||||
const timeoutMs = toNumber(body.timeoutMs);
|
const timeoutMs = toNumber(body.timeoutMs);
|
||||||
const maxChars = toNumber(body.maxChars);
|
const maxChars = toNumber(body.maxChars);
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return jsonError(res, 400, "url is required");
|
return jsonError(res, 400, "url is required");
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
const tab = await profileCtx.ensureTabAvailable(targetId);
|
await withPlaywrightRouteContext({
|
||||||
const pw = await requirePwAi(res, "response body");
|
req,
|
||||||
if (!pw) {
|
res,
|
||||||
return;
|
ctx,
|
||||||
}
|
targetId,
|
||||||
|
feature: "response body",
|
||||||
|
run: async ({ cdpUrl, tab, pw }) => {
|
||||||
const result = await pw.responseBodyViaPlaywright({
|
const result = await pw.responseBodyViaPlaywright({
|
||||||
cdpUrl: profileCtx.profile.cdpUrl,
|
cdpUrl,
|
||||||
targetId: tab.targetId,
|
targetId: tab.targetId,
|
||||||
url,
|
url,
|
||||||
timeoutMs: timeoutMs ?? undefined,
|
timeoutMs: timeoutMs ?? undefined,
|
||||||
maxChars: maxChars ?? undefined,
|
maxChars: maxChars ?? undefined,
|
||||||
});
|
});
|
||||||
res.json({ ok: true, targetId: tab.targetId, response: result });
|
res.json({ ok: true, targetId: tab.targetId, response: result });
|
||||||
} catch (err) {
|
},
|
||||||
handleRouteError(ctx, res, err);
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/highlight", async (req, res) => {
|
app.post("/highlight", async (req, res) => {
|
||||||
const profileCtx = resolveProfileContext(req, res, ctx);
|
|
||||||
if (!profileCtx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const body = readBody(req);
|
const body = readBody(req);
|
||||||
const targetId = toStringOrEmpty(body.targetId) || undefined;
|
const targetId = resolveTargetIdFromBody(body);
|
||||||
const ref = toStringOrEmpty(body.ref);
|
const ref = toStringOrEmpty(body.ref);
|
||||||
if (!ref) {
|
if (!ref) {
|
||||||
return jsonError(res, 400, "ref is required");
|
return jsonError(res, 400, "ref is required");
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
const tab = await profileCtx.ensureTabAvailable(targetId);
|
await withPlaywrightRouteContext({
|
||||||
const pw = await requirePwAi(res, "highlight");
|
req,
|
||||||
if (!pw) {
|
res,
|
||||||
return;
|
ctx,
|
||||||
}
|
targetId,
|
||||||
|
feature: "highlight",
|
||||||
|
run: async ({ cdpUrl, tab, pw }) => {
|
||||||
await pw.highlightViaPlaywright({
|
await pw.highlightViaPlaywright({
|
||||||
cdpUrl: profileCtx.profile.cdpUrl,
|
cdpUrl,
|
||||||
targetId: tab.targetId,
|
targetId: tab.targetId,
|
||||||
ref,
|
ref,
|
||||||
});
|
});
|
||||||
res.json({ ok: true, targetId: tab.targetId });
|
res.json({ ok: true, targetId: tab.targetId });
|
||||||
} catch (err) {
|
},
|
||||||
handleRouteError(ctx, res, err);
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user