@anweat/dsh-browser 0.1.11 → 0.1.13-alpha.1
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/LICENSE +21 -21
- package/README.md +382 -356
- package/cordis.patch.yml +26 -26
- package/lib/approval-policy.js +25 -1
- package/lib/approval-policy.js.map +1 -1
- package/lib/automation-assets-rpc.js +47 -6
- package/lib/automation-assets-rpc.js.map +1 -1
- package/lib/browser-service.d.ts +23 -1
- package/lib/browser-service.js +123 -58
- package/lib/browser-service.js.map +1 -1
- package/lib/client/automation-assets-client.d.ts +1 -1
- package/lib/client/automation-assets-client.js +1 -1
- package/lib/client/automation-assets-client.js.map +1 -1
- package/lib/client/context-types.d.ts +1 -1
- package/lib/client/form.d.ts +2 -1
- package/lib/client/form.js.map +1 -1
- package/lib/client/styles.js +12 -12
- package/lib/client.js +1 -1
- package/lib/client.js.map +1 -1
- package/lib/freedom.d.ts +1 -1
- package/lib/freedom.js +3 -0
- package/lib/freedom.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/scripts.js +27 -27
- package/lib/tools.js +69 -0
- package/lib/tools.js.map +1 -1
- package/package.json +150 -133
- package/scripts/check-client-bundle.mjs +15 -15
- package/scripts/clean-lib.mjs +3 -3
- package/scripts/install-browser.mjs +18 -18
package/cordis.patch.yml
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
# dsh-browser bundle patch — installs the browser runtime as one more row over
|
|
2
|
-
# the active profile. Install with:
|
|
3
|
-
# dsh plugin --profile <name> add @anweat/dsh-browser
|
|
4
|
-
# then boot the profile (the web profile has HMR disabled, so a restart applies it).
|
|
5
|
-
#
|
|
6
|
-
# Login state: prefer named `authProfiles` with explicit `allowedDomains` (see
|
|
7
|
-
# README). `storageStatePath` remains as a legacy global fallback.
|
|
8
|
-
- insert:
|
|
9
|
-
- id: browser
|
|
10
|
-
name: '@anweat/dsh-browser'
|
|
11
|
-
config:
|
|
12
|
-
automationMode: standard
|
|
13
|
-
browserRuntime: playwright
|
|
14
|
-
channel: chromium
|
|
15
|
-
headless: true
|
|
16
|
-
opencliEnabled: true
|
|
17
|
-
usagePolicy:
|
|
18
|
-
minDelayMs: 750
|
|
19
|
-
maxConcurrency: 2
|
|
20
|
-
burst: 3
|
|
21
|
-
maxPagesPerRun: 20
|
|
22
|
-
maxDepth: 2
|
|
23
|
-
retryLimit: 2
|
|
24
|
-
backoffBaseMs: 1000
|
|
25
|
-
cooldownMs: 30000
|
|
26
|
-
verbose: false
|
|
1
|
+
# dsh-browser bundle patch — installs the browser runtime as one more row over
|
|
2
|
+
# the active profile. Install with:
|
|
3
|
+
# dsh plugin --profile <name> add @anweat/dsh-browser
|
|
4
|
+
# then boot the profile (the web profile has HMR disabled, so a restart applies it).
|
|
5
|
+
#
|
|
6
|
+
# Login state: prefer named `authProfiles` with explicit `allowedDomains` (see
|
|
7
|
+
# README). `storageStatePath` remains as a legacy global fallback.
|
|
8
|
+
- insert:
|
|
9
|
+
- id: browser
|
|
10
|
+
name: '@anweat/dsh-browser'
|
|
11
|
+
config:
|
|
12
|
+
automationMode: standard
|
|
13
|
+
browserRuntime: playwright
|
|
14
|
+
channel: chromium
|
|
15
|
+
headless: true
|
|
16
|
+
opencliEnabled: true
|
|
17
|
+
usagePolicy:
|
|
18
|
+
minDelayMs: 750
|
|
19
|
+
maxConcurrency: 2
|
|
20
|
+
burst: 3
|
|
21
|
+
maxPagesPerRun: 20
|
|
22
|
+
maxDepth: 2
|
|
23
|
+
retryLimit: 2
|
|
24
|
+
backoffBaseMs: 1000
|
|
25
|
+
cooldownMs: 30000
|
|
26
|
+
verbose: false
|
package/lib/approval-policy.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { recipeNeedsApproval } from "./automation.js";
|
|
3
3
|
import { validateUserscript } from "./scripts.js";
|
|
4
4
|
import { isBrowserToolExposed } from "./freedom.js";
|
|
5
|
-
const DIRECT_INTERACTIONS = new Set(['browser_click', 'browser_type', 'browser_scroll']);
|
|
5
|
+
const DIRECT_INTERACTIONS = new Set(['browser_click', 'browser_type', 'browser_hover', 'browser_scroll']);
|
|
6
6
|
const WEB_LOCAL_MUTATIONS = new Set(['web_cache_clear']);
|
|
7
7
|
export function browserPolicyDecision(name, args, mode = 'standard') {
|
|
8
8
|
if (!isBrowserToolExposed(name, mode) && name.startsWith('browser_')) {
|
|
@@ -21,6 +21,30 @@ export function browserPolicyDecision(name, args, mode = 'standard') {
|
|
|
21
21
|
return { kind: 'allow' };
|
|
22
22
|
return { kind: 'ask', reason: 'Install Playwright Chromium into the shared browser cache' };
|
|
23
23
|
}
|
|
24
|
+
if (name === 'browser_set_files') {
|
|
25
|
+
const files = Array.isArray(args?.files)
|
|
26
|
+
? args.files.filter(value => typeof value === 'string')
|
|
27
|
+
: [];
|
|
28
|
+
if (files.length === 0)
|
|
29
|
+
return { kind: 'deny', reason: 'browser_set_files requires one or more absolute file paths' };
|
|
30
|
+
if (mode === 'read-only')
|
|
31
|
+
return { kind: 'deny', reason: `Local file upload is disabled by automationMode=${mode}` };
|
|
32
|
+
if (mode === 'unrestricted')
|
|
33
|
+
return { kind: 'allow' };
|
|
34
|
+
return { kind: 'ask', reason: 'Read local files and expose them to the current website upload control: ' + files.slice(0, 5).join(', ') };
|
|
35
|
+
}
|
|
36
|
+
if (name === 'browser_evaluate') {
|
|
37
|
+
const expression = args?.expression;
|
|
38
|
+
if (typeof expression !== 'string' || !expression.trim())
|
|
39
|
+
return { kind: 'deny', reason: 'browser_evaluate requires a JavaScript expression' };
|
|
40
|
+
if (expression.length > 20_000)
|
|
41
|
+
return { kind: 'deny', reason: 'browser_evaluate expression exceeds 20,000 characters' };
|
|
42
|
+
if (mode === 'read-only')
|
|
43
|
+
return { kind: 'deny', reason: `Page JavaScript execution is disabled by automationMode=${mode}` };
|
|
44
|
+
if (mode === 'unrestricted')
|
|
45
|
+
return { kind: 'allow' };
|
|
46
|
+
return { kind: 'ask', reason: 'Run JavaScript with the current page origin and login state; it may access page storage, non-HttpOnly cookies, and browser-permitted network APIs' };
|
|
47
|
+
}
|
|
24
48
|
if (name === 'browser_userscript_run') {
|
|
25
49
|
const input = args;
|
|
26
50
|
if (typeof input.source !== 'string' || typeof input.url !== 'string')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval-policy.js","sourceRoot":"","sources":["../src/approval-policy.ts"],"names":[],"mappings":"AAAA,iFAAiF;AAEjF,OAAO,EAAE,mBAAmB,EAA0B,MAAM,iBAAiB,CAAA;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,EAAE,oBAAoB,EAAuB,MAAM,cAAc,CAAA;AAOxE,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,eAAe,EAAE,cAAc,EAAE,gBAAgB,CAAC,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"approval-policy.js","sourceRoot":"","sources":["../src/approval-policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
/** Loopback-only Host RPC for the automation asset review UI. */
|
|
2
2
|
import { executeAutomationAsset } from "./automation-execution.js";
|
|
3
|
-
const CHANNEL = '/
|
|
3
|
+
const CHANNEL = '/api';
|
|
4
|
+
const PREFIX = 'dsh-browser-assets';
|
|
5
|
+
const ENDPOINTS = ['snapshot', 'get', 'save', 'summarize', 'dismiss', 'validate', 'test', 'status'];
|
|
6
|
+
function failure(rpcId, message) {
|
|
7
|
+
return Response.json({
|
|
8
|
+
type: 'server-response',
|
|
9
|
+
rpcId,
|
|
10
|
+
result: { ok: false, error: { code: 'gateway/bad-request', message, details: {} } },
|
|
11
|
+
});
|
|
12
|
+
}
|
|
4
13
|
function record(payload) {
|
|
5
14
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload))
|
|
6
15
|
throw new Error('request payload must be an object');
|
|
@@ -17,7 +26,7 @@ function stringField(payload, name) {
|
|
|
17
26
|
export function registerAutomationAssetRpc(ctx, store, service) {
|
|
18
27
|
ctx.inject(['connection'], (connectionCtx) => {
|
|
19
28
|
const connection = connectionCtx.connection;
|
|
20
|
-
const
|
|
29
|
+
const handler = async (endpoint, rawPayload) => {
|
|
21
30
|
try {
|
|
22
31
|
const payload = record(rawPayload);
|
|
23
32
|
let value;
|
|
@@ -49,15 +58,47 @@ export function registerAutomationAssetRpc(ctx, store, service) {
|
|
|
49
58
|
case 'status':
|
|
50
59
|
value = store.setStatus(stringField(payload, 'id'), stringField(payload, 'status'));
|
|
51
60
|
break;
|
|
52
|
-
default: return { ok: false, error: { code: 'not-found', message: `unknown automation asset endpoint: ${endpoint}
|
|
61
|
+
default: return { ok: false, error: { code: 'not-found', message: `unknown automation asset endpoint: ${endpoint}`, details: {} } };
|
|
53
62
|
}
|
|
54
63
|
return { ok: true, value };
|
|
55
64
|
}
|
|
56
65
|
catch (error) {
|
|
57
|
-
return { ok: false, error: { code: 'bad-request', message: String(error instanceof Error ? error.message : error).slice(0, 500) } };
|
|
66
|
+
return { ok: false, error: { code: 'bad-request', message: String(error instanceof Error ? error.message : error).slice(0, 500), details: {} } };
|
|
58
67
|
}
|
|
59
|
-
}
|
|
60
|
-
|
|
68
|
+
};
|
|
69
|
+
for (const endpoint of ENDPOINTS) {
|
|
70
|
+
const method = `${PREFIX}/${endpoint}`;
|
|
71
|
+
connectionCtx.effect(() => connection.fetch.register({
|
|
72
|
+
path: `${CHANNEL}/${method}`,
|
|
73
|
+
methods: ['POST'],
|
|
74
|
+
requestBody: 'buffered',
|
|
75
|
+
fetch: async (request) => {
|
|
76
|
+
let body;
|
|
77
|
+
try {
|
|
78
|
+
body = await request.json();
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return new Response('body is not JSON', { status: 400 });
|
|
82
|
+
}
|
|
83
|
+
const rpcId = typeof body === 'object' && body !== null
|
|
84
|
+
&& typeof body.rpcId === 'string'
|
|
85
|
+
? body.rpcId
|
|
86
|
+
: 'invalid-request';
|
|
87
|
+
if (typeof body !== 'object' || body === null
|
|
88
|
+
|| body.type !== 'client-request'
|
|
89
|
+
|| body.method !== method) {
|
|
90
|
+
return failure(rpcId, 'invalid RPC request');
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const result = await handler(endpoint, body.payload, request.signal);
|
|
94
|
+
return Response.json({ type: 'server-response', rpcId, result });
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
return failure(rpcId, String(error instanceof Error ? error.message : error).slice(0, 500));
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
}), `dsh-browser: ${method} RPC route`);
|
|
101
|
+
}
|
|
61
102
|
});
|
|
62
103
|
}
|
|
63
104
|
//# sourceMappingURL=automation-assets-rpc.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"automation-assets-rpc.js","sourceRoot":"","sources":["../src/automation-assets-rpc.ts"],"names":[],"mappings":"AAAA,iEAAiE;
|
|
1
|
+
{"version":3,"file":"automation-assets-rpc.js","sourceRoot":"","sources":["../src/automation-assets-rpc.ts"],"names":[],"mappings":"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"}
|
package/lib/browser-service.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* web-search-pro's former PlaywrightManager (rules-aware page extraction and
|
|
10
10
|
* platform search-page list extraction run in the page itself).
|
|
11
11
|
* - opencli(...) runs the bundled @jackwener/opencli (no global CLI).
|
|
12
|
-
* - The interactive surface (open/click/type/scroll/read/screenshot/closePage)
|
|
12
|
+
* - The interactive surface (open/click/type/hover/setFiles/evaluate/scroll/read/screenshot/closePage)
|
|
13
13
|
* drives ONE persistent context+page, giving the model multi-step browsing.
|
|
14
14
|
*
|
|
15
15
|
* The page-side extractors are raw JS strings, not closures: tsx/esbuild would
|
|
@@ -72,6 +72,16 @@ export interface ScriptRunResult {
|
|
|
72
72
|
resultJson: string;
|
|
73
73
|
truncated: boolean;
|
|
74
74
|
}
|
|
75
|
+
export interface EvaluateResult {
|
|
76
|
+
url: string;
|
|
77
|
+
resultJson: string;
|
|
78
|
+
truncated: boolean;
|
|
79
|
+
capabilities: string[];
|
|
80
|
+
warnings: string[];
|
|
81
|
+
}
|
|
82
|
+
export interface FileUploadResult extends InteractiveState {
|
|
83
|
+
files: string[];
|
|
84
|
+
}
|
|
75
85
|
export interface CrawlPage {
|
|
76
86
|
url: string;
|
|
77
87
|
title: string;
|
|
@@ -110,6 +120,8 @@ export interface BrowserStatus {
|
|
|
110
120
|
directInteractionPolicy: 'deny' | 'ask' | 'allow';
|
|
111
121
|
mutatingRecipePolicy: 'deny' | 'ask' | 'allow';
|
|
112
122
|
externalUserscriptPolicy: 'deny' | 'ask' | 'allow';
|
|
123
|
+
pageEvaluatePolicy: 'deny' | 'ask' | 'allow';
|
|
124
|
+
fileUploadPolicy: 'deny' | 'ask' | 'allow';
|
|
113
125
|
opencliRunPolicy: 'deny' | 'ask' | 'allow';
|
|
114
126
|
chromiumInstalled: boolean;
|
|
115
127
|
chromiumExecutablePath?: string;
|
|
@@ -225,6 +237,16 @@ export declare class BrowserService {
|
|
|
225
237
|
type(selector: string, text: string, opts?: {
|
|
226
238
|
timeoutMs?: number;
|
|
227
239
|
}): Promise<InteractiveState>;
|
|
240
|
+
hover(selector: string, opts?: {
|
|
241
|
+
timeoutMs?: number;
|
|
242
|
+
waitMs?: number;
|
|
243
|
+
}): Promise<InteractiveState>;
|
|
244
|
+
setFiles(selector: string, files: readonly string[], opts?: {
|
|
245
|
+
timeoutMs?: number;
|
|
246
|
+
}): Promise<FileUploadResult>;
|
|
247
|
+
evaluate(expression: string, opts?: {
|
|
248
|
+
timeoutMs?: number;
|
|
249
|
+
}): Promise<EvaluateResult>;
|
|
228
250
|
scroll(deltaY: number, opts?: {
|
|
229
251
|
waitMs?: number;
|
|
230
252
|
}): Promise<InteractiveState>;
|
package/lib/browser-service.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* web-search-pro's former PlaywrightManager (rules-aware page extraction and
|
|
10
10
|
* platform search-page list extraction run in the page itself).
|
|
11
11
|
* - opencli(...) runs the bundled @jackwener/opencli (no global CLI).
|
|
12
|
-
* - The interactive surface (open/click/type/scroll/read/screenshot/closePage)
|
|
12
|
+
* - The interactive surface (open/click/type/hover/setFiles/evaluate/scroll/read/screenshot/closePage)
|
|
13
13
|
* drives ONE persistent context+page, giving the model multi-step browsing.
|
|
14
14
|
*
|
|
15
15
|
* The page-side extractors are raw JS strings, not closures: tsx/esbuild would
|
|
@@ -30,66 +30,66 @@ import { configuredBrowserTools } from "./freedom.js";
|
|
|
30
30
|
import { filterOpencliCatalog, parseOpencliCatalog } from "./opencli-catalog.js";
|
|
31
31
|
import { UsageGovernor } from "./usage-policy.js";
|
|
32
32
|
/** Rules-aware content extractor (runs in the page). */
|
|
33
|
-
const EXTRACTOR_FN = `(ruleList) => {
|
|
34
|
-
const doc = document
|
|
35
|
-
const title = doc.title ? doc.title.trim() : ''
|
|
36
|
-
let host = ''
|
|
37
|
-
try { host = location.hostname } catch (e) {}
|
|
38
|
-
const norm = (h) => { const x = h.toLowerCase(); return x.startsWith('www.') ? x.slice(4) : x }
|
|
39
|
-
let rule = null
|
|
40
|
-
for (const r of ruleList) {
|
|
41
|
-
const rh = norm(r.hostname)
|
|
42
|
-
if (host === rh || host.endsWith('.' + rh)) rule = r
|
|
43
|
-
}
|
|
44
|
-
const pick = (selectors) => {
|
|
45
|
-
for (const sel of selectors) {
|
|
46
|
-
try {
|
|
47
|
-
const el = doc.querySelector(sel)
|
|
48
|
-
if (el && (el.textContent || '').trim().length > 40) return el
|
|
49
|
-
} catch (e) {}
|
|
50
|
-
}
|
|
51
|
-
return null
|
|
52
|
-
}
|
|
53
|
-
const content = rule ? pick(rule.contentSelectors) : (pick(['article', 'main', '[role="main"]']) || doc.body)
|
|
54
|
-
if (content && rule && rule.removeSelectors) {
|
|
55
|
-
for (const sel of rule.removeSelectors) {
|
|
56
|
-
try { content.querySelectorAll(sel).forEach((el) => el.remove()) } catch (e) {}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
const text = content ? (content.innerText || content.textContent || '') : ''
|
|
60
|
-
return { title, text, html: doc.documentElement.outerHTML.slice(0, 2000000), usedRule: rule ? rule.hostname : null }
|
|
33
|
+
const EXTRACTOR_FN = `(ruleList) => {
|
|
34
|
+
const doc = document
|
|
35
|
+
const title = doc.title ? doc.title.trim() : ''
|
|
36
|
+
let host = ''
|
|
37
|
+
try { host = location.hostname } catch (e) {}
|
|
38
|
+
const norm = (h) => { const x = h.toLowerCase(); return x.startsWith('www.') ? x.slice(4) : x }
|
|
39
|
+
let rule = null
|
|
40
|
+
for (const r of ruleList) {
|
|
41
|
+
const rh = norm(r.hostname)
|
|
42
|
+
if (host === rh || host.endsWith('.' + rh)) rule = r
|
|
43
|
+
}
|
|
44
|
+
const pick = (selectors) => {
|
|
45
|
+
for (const sel of selectors) {
|
|
46
|
+
try {
|
|
47
|
+
const el = doc.querySelector(sel)
|
|
48
|
+
if (el && (el.textContent || '').trim().length > 40) return el
|
|
49
|
+
} catch (e) {}
|
|
50
|
+
}
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
const content = rule ? pick(rule.contentSelectors) : (pick(['article', 'main', '[role="main"]']) || doc.body)
|
|
54
|
+
if (content && rule && rule.removeSelectors) {
|
|
55
|
+
for (const sel of rule.removeSelectors) {
|
|
56
|
+
try { content.querySelectorAll(sel).forEach((el) => el.remove()) } catch (e) {}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const text = content ? (content.innerText || content.textContent || '') : ''
|
|
60
|
+
return { title, text, html: doc.documentElement.outerHTML.slice(0, 2000000), usedRule: rule ? rule.hostname : null }
|
|
61
61
|
}`;
|
|
62
62
|
/** Platform search-page list extractor (runs in the page). */
|
|
63
|
-
const LIST_EXTRACTOR = `(spec) => {
|
|
64
|
-
const items = []
|
|
65
|
-
const nodes = document.querySelectorAll(spec.item)
|
|
66
|
-
for (let i = 0; i < nodes.length && items.length < 20; i++) {
|
|
67
|
-
const el = nodes[i]
|
|
68
|
-
const titleEl = spec.title ? el.querySelector(spec.title) : null
|
|
69
|
-
const linkEl = spec.link ? el.querySelector(spec.link) : null
|
|
70
|
-
const textEl = spec.text ? el.querySelector(spec.text) : null
|
|
71
|
-
const title = (titleEl ? titleEl.textContent : el.textContent || '').trim().replace(/\\s+/g, ' ')
|
|
72
|
-
let url = linkEl ? (linkEl.href || linkEl.getAttribute('href') || '') : ''
|
|
73
|
-
if (url && url.startsWith('/')) url = location.origin + url
|
|
74
|
-
if (!url && linkEl === null && titleEl) { const a = titleEl.closest ? titleEl.closest('a') : null; if (a) url = a.href }
|
|
75
|
-
const snippet = textEl ? (textEl.textContent || '').trim().replace(/\\s+/g, ' ').slice(0, 300) : ''
|
|
76
|
-
if (!url || !title || title.length < 2) continue
|
|
77
|
-
items.push({ url, title, snippet })
|
|
78
|
-
}
|
|
79
|
-
return items
|
|
63
|
+
const LIST_EXTRACTOR = `(spec) => {
|
|
64
|
+
const items = []
|
|
65
|
+
const nodes = document.querySelectorAll(spec.item)
|
|
66
|
+
for (let i = 0; i < nodes.length && items.length < 20; i++) {
|
|
67
|
+
const el = nodes[i]
|
|
68
|
+
const titleEl = spec.title ? el.querySelector(spec.title) : null
|
|
69
|
+
const linkEl = spec.link ? el.querySelector(spec.link) : null
|
|
70
|
+
const textEl = spec.text ? el.querySelector(spec.text) : null
|
|
71
|
+
const title = (titleEl ? titleEl.textContent : el.textContent || '').trim().replace(/\\s+/g, ' ')
|
|
72
|
+
let url = linkEl ? (linkEl.href || linkEl.getAttribute('href') || '') : ''
|
|
73
|
+
if (url && url.startsWith('/')) url = location.origin + url
|
|
74
|
+
if (!url && linkEl === null && titleEl) { const a = titleEl.closest ? titleEl.closest('a') : null; if (a) url = a.href }
|
|
75
|
+
const snippet = textEl ? (textEl.textContent || '').trim().replace(/\\s+/g, ' ').slice(0, 300) : ''
|
|
76
|
+
if (!url || !title || title.length < 2) continue
|
|
77
|
+
items.push({ url, title, snippet })
|
|
78
|
+
}
|
|
79
|
+
return items
|
|
80
80
|
}`;
|
|
81
|
-
const CRAWL_EXTRACTOR = `(maxChars) => {
|
|
82
|
-
const root = document.querySelector('article, main, [role="main"]') || document.body
|
|
83
|
-
const text = ((root && (root.innerText || root.textContent)) || '').replace(/\\n{3,}/g, '\\n\\n').trim().slice(0, maxChars)
|
|
84
|
-
const links = []
|
|
85
|
-
for (const anchor of document.querySelectorAll('a[href]')) {
|
|
86
|
-
try {
|
|
87
|
-
const url = new URL(anchor.href, location.href)
|
|
88
|
-
if ((url.protocol === 'http:' || url.protocol === 'https:') && !links.includes(url.href)) links.push(url.href)
|
|
89
|
-
if (links.length >= 500) break
|
|
90
|
-
} catch (e) {}
|
|
91
|
-
}
|
|
92
|
-
return { title: document.title || '', text, links }
|
|
81
|
+
const CRAWL_EXTRACTOR = `(maxChars) => {
|
|
82
|
+
const root = document.querySelector('article, main, [role="main"]') || document.body
|
|
83
|
+
const text = ((root && (root.innerText || root.textContent)) || '').replace(/\\n{3,}/g, '\\n\\n').trim().slice(0, maxChars)
|
|
84
|
+
const links = []
|
|
85
|
+
for (const anchor of document.querySelectorAll('a[href]')) {
|
|
86
|
+
try {
|
|
87
|
+
const url = new URL(anchor.href, location.href)
|
|
88
|
+
if ((url.protocol === 'http:' || url.protocol === 'https:') && !links.includes(url.href)) links.push(url.href)
|
|
89
|
+
if (links.length >= 500) break
|
|
90
|
+
} catch (e) {}
|
|
91
|
+
}
|
|
92
|
+
return { title: document.title || '', text, links }
|
|
93
93
|
}`;
|
|
94
94
|
function uid() {
|
|
95
95
|
return crypto.randomUUID();
|
|
@@ -611,6 +611,69 @@ export class BrowserService {
|
|
|
611
611
|
await page.fill(selector, text);
|
|
612
612
|
return this.readState(page, false);
|
|
613
613
|
}
|
|
614
|
+
async hover(selector, opts = {}) {
|
|
615
|
+
const page = await this.ensureActivePage();
|
|
616
|
+
const timeoutMs = opts.timeoutMs ?? 15_000;
|
|
617
|
+
const waitMs = Math.min(Math.max(opts.waitMs ?? 300, 0), timeoutMs);
|
|
618
|
+
await page.waitForSelector(selector, { timeout: timeoutMs });
|
|
619
|
+
await page.hover(selector);
|
|
620
|
+
await page.waitForTimeout(waitMs);
|
|
621
|
+
return this.readState(page, true);
|
|
622
|
+
}
|
|
623
|
+
async setFiles(selector, files, opts = {}) {
|
|
624
|
+
if (files.length === 0 || files.length > 20)
|
|
625
|
+
throw new Error('browser_set_files requires 1 to 20 files');
|
|
626
|
+
const resolved = files.map(file => {
|
|
627
|
+
if (!path.isAbsolute(file))
|
|
628
|
+
throw new Error('browser_set_files requires absolute file paths: ' + file);
|
|
629
|
+
const real = fs.realpathSync(file);
|
|
630
|
+
if (!fs.statSync(real).isFile())
|
|
631
|
+
throw new Error('browser_set_files path is not a file: ' + file);
|
|
632
|
+
return real;
|
|
633
|
+
});
|
|
634
|
+
const totalBytes = resolved.reduce((total, file) => total + fs.statSync(file).size, 0);
|
|
635
|
+
if (totalBytes > 512 * 1024 * 1024)
|
|
636
|
+
throw new Error('browser_set_files total upload size exceeds 512 MiB');
|
|
637
|
+
const page = await this.ensureActivePage();
|
|
638
|
+
await page.waitForSelector(selector, { timeout: opts.timeoutMs ?? 15_000 });
|
|
639
|
+
await page.setInputFiles(selector, resolved);
|
|
640
|
+
return { ...await this.readState(page, true), files: resolved.map(file => path.basename(file)) };
|
|
641
|
+
}
|
|
642
|
+
async evaluate(expression, opts = {}) {
|
|
643
|
+
const source = expression.trim();
|
|
644
|
+
if (!source)
|
|
645
|
+
throw new Error('browser_evaluate requires a JavaScript expression');
|
|
646
|
+
if (source.length > 20_000)
|
|
647
|
+
throw new Error('browser_evaluate expression exceeds 20,000 characters');
|
|
648
|
+
const page = await this.ensureActivePage();
|
|
649
|
+
const timeoutMs = Math.min(Math.max(opts.timeoutMs ?? 15_000, 1_000), 30_000);
|
|
650
|
+
let timer;
|
|
651
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
652
|
+
timer = setTimeout(() => {
|
|
653
|
+
void this.closePage();
|
|
654
|
+
reject(new Error('browser_evaluate timed out after ' + timeoutMs + 'ms; the active page was closed'));
|
|
655
|
+
}, timeoutMs);
|
|
656
|
+
});
|
|
657
|
+
try {
|
|
658
|
+
const script = `(async () => {\nconst value = await (${source}\n);\nconst json = JSON.stringify(value);\nif (json === undefined) throw new Error('expression result is not JSON-serializable');\nreturn { resultJson: json.slice(0, 100000), truncated: json.length > 100000 };\n})()`;
|
|
659
|
+
const result = await Promise.race([page.evaluate(script), timeout]);
|
|
660
|
+
return {
|
|
661
|
+
url: page.url(),
|
|
662
|
+
resultJson: result.resultJson,
|
|
663
|
+
truncated: result.truncated,
|
|
664
|
+
capabilities: ['dom', 'page-javascript', 'page-network', 'page-storage'],
|
|
665
|
+
warnings: [
|
|
666
|
+
'The expression runs with the current page origin and login state. It can mutate the page, access non-HttpOnly cookies and browser storage, and issue requests allowed by the browser.',
|
|
667
|
+
'Use this capability according to the target site rules and applicable requirements. The caller/operator is responsible for that decision; dsh-browser only executes approved browser operations.',
|
|
668
|
+
'The expression has no Node.js or direct host-filesystem access. Downloads are not persisted or returned by this tool.',
|
|
669
|
+
],
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
finally {
|
|
673
|
+
if (timer)
|
|
674
|
+
clearTimeout(timer);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
614
677
|
async scroll(deltaY, opts = {}) {
|
|
615
678
|
const page = await this.ensureActivePage();
|
|
616
679
|
await page.mouse?.wheel(0, deltaY || 2000).catch(() => { });
|
|
@@ -711,6 +774,8 @@ export class BrowserService {
|
|
|
711
774
|
directInteractionPolicy: !this.config.enabled || this.config.automationMode === 'read-only' ? 'deny' : this.config.automationMode === 'standard' ? 'ask' : 'allow',
|
|
712
775
|
mutatingRecipePolicy: !this.config.enabled || this.config.automationMode === 'read-only' ? 'deny' : this.config.automationMode === 'standard' ? 'ask' : 'allow',
|
|
713
776
|
externalUserscriptPolicy: !this.config.enabled || this.config.automationMode === 'read-only' ? 'deny' : this.config.automationMode === 'unrestricted' ? 'allow' : 'ask',
|
|
777
|
+
pageEvaluatePolicy: !this.config.enabled || this.config.automationMode === 'read-only' ? 'deny' : this.config.automationMode === 'unrestricted' ? 'allow' : 'ask',
|
|
778
|
+
fileUploadPolicy: !this.config.enabled || this.config.automationMode === 'read-only' ? 'deny' : this.config.automationMode === 'unrestricted' ? 'allow' : 'ask',
|
|
714
779
|
opencliRunPolicy: !this.config.enabled || this.config.automationMode === 'read-only' ? 'deny' : this.config.automationMode === 'unrestricted' ? 'allow' : 'ask',
|
|
715
780
|
chromiumInstalled,
|
|
716
781
|
...chromiumExecutablePath ? { chromiumExecutablePath } : {},
|