@moxxy/plugin-browser 0.27.0
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 -0
- package/dist/browser-session.d.ts +43 -0
- package/dist/browser-session.d.ts.map +1 -0
- package/dist/browser-session.js +500 -0
- package/dist/browser-session.js.map +1 -0
- package/dist/browser-surface.d.ts +3 -0
- package/dist/browser-surface.d.ts.map +1 -0
- package/dist/browser-surface.js +255 -0
- package/dist/browser-surface.js.map +1 -0
- package/dist/html-extract.d.ts +20 -0
- package/dist/html-extract.d.ts.map +1 -0
- package/dist/html-extract.js +122 -0
- package/dist/html-extract.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/sidecar/dispatch.d.ts +18 -0
- package/dist/sidecar/dispatch.d.ts.map +1 -0
- package/dist/sidecar/dispatch.js +294 -0
- package/dist/sidecar/dispatch.js.map +1 -0
- package/dist/sidecar/install.d.ts +37 -0
- package/dist/sidecar/install.d.ts.map +1 -0
- package/dist/sidecar/install.js +242 -0
- package/dist/sidecar/install.js.map +1 -0
- package/dist/sidecar/types.d.ts +97 -0
- package/dist/sidecar/types.d.ts.map +1 -0
- package/dist/sidecar/types.js +20 -0
- package/dist/sidecar/types.js.map +1 -0
- package/dist/sidecar.d.ts +31 -0
- package/dist/sidecar.d.ts.map +1 -0
- package/dist/sidecar.js +165 -0
- package/dist/sidecar.js.map +1 -0
- package/dist/ssrf-guard.d.ts +43 -0
- package/dist/ssrf-guard.d.ts.map +1 -0
- package/dist/ssrf-guard.js +164 -0
- package/dist/ssrf-guard.js.map +1 -0
- package/dist/web-fetch.d.ts +23 -0
- package/dist/web-fetch.d.ts.map +1 -0
- package/dist/web-fetch.js +253 -0
- package/dist/web-fetch.js.map +1 -0
- package/package.json +74 -0
- package/src/browser-session.test.ts +333 -0
- package/src/browser-session.ts +567 -0
- package/src/browser-surface.test.ts +367 -0
- package/src/browser-surface.ts +275 -0
- package/src/html-extract.ts +152 -0
- package/src/index.ts +35 -0
- package/src/sidecar/dispatch.test.ts +313 -0
- package/src/sidecar/dispatch.ts +314 -0
- package/src/sidecar/install.ts +283 -0
- package/src/sidecar/types.test.ts +26 -0
- package/src/sidecar/types.ts +114 -0
- package/src/sidecar.test.ts +57 -0
- package/src/sidecar.ts +167 -0
- package/src/ssrf-guard.test.ts +109 -0
- package/src/ssrf-guard.ts +165 -0
- package/src/web-fetch.test.ts +305 -0
- package/src/web-fetch.ts +311 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { defineSurface } from '@moxxy/sdk';
|
|
2
|
+
import { browserSidecarCall, closeBrowserSidecar, resolveBrowserInstallRoot, sidecarErrorKind, } from './browser-session.js';
|
|
3
|
+
import { installPlaywrightPackage } from './sidecar/install.js';
|
|
4
|
+
/**
|
|
5
|
+
* The `browser` surface: a live, in-window view of the SAME Playwright page the
|
|
6
|
+
* `browser_session` tool drives. We "stream" the page by polling a JPEG frame
|
|
7
|
+
* (`frame` sidecar method) a few times a second and forwarding it as a
|
|
8
|
+
* `surface.data` payload; the user's clicks/keys/scroll/navigate are proxied
|
|
9
|
+
* back onto the page via the sidecar's coordinate-based input methods. So the
|
|
10
|
+
* agent and the user operate ONE shared page — agent navigations show up in the
|
|
11
|
+
* pane, and the user can take over.
|
|
12
|
+
*
|
|
13
|
+
* Why polling and not a CDP `Page.startScreencast` push: the screencast only
|
|
14
|
+
* emits on visual change, so a freshly-opened (blank / static / headless) page
|
|
15
|
+
* produces no frames at all — the pane sat on "Loading…" forever with the
|
|
16
|
+
* underlying error swallowed. A screenshot poll always yields a frame (even a
|
|
17
|
+
* blank one), so the view comes up reliably and a real launch/install failure
|
|
18
|
+
* surfaces as a status line instead of an indefinite spinner. Polling rides the
|
|
19
|
+
* existing sidecar RPC surface and works on every Playwright browser, not just
|
|
20
|
+
* Chromium.
|
|
21
|
+
*/
|
|
22
|
+
const FRAME_INTERVAL_MS = 300;
|
|
23
|
+
/** Consecutive `frame` failures (with no frame ever seen) before we stop
|
|
24
|
+
* assuming "still launching" and show the error. ~6 × 300ms ≈ 1.8s grace. */
|
|
25
|
+
const FAIL_GRACE = 6;
|
|
26
|
+
/** Validate a sidecar `frame` reply before emitting it: a partial / altered
|
|
27
|
+
* reply must be treated as a failed tick (so FAIL_GRACE eventually surfaces a
|
|
28
|
+
* status) rather than streamed to subscribers as `{base64: undefined, …}`. */
|
|
29
|
+
function isFrame(v) {
|
|
30
|
+
if (!v || typeof v !== 'object')
|
|
31
|
+
return false;
|
|
32
|
+
const f = v;
|
|
33
|
+
return typeof f.base64 === 'string' && typeof f.mediaType === 'string' && typeof f.url === 'string';
|
|
34
|
+
}
|
|
35
|
+
export function buildBrowserSurface(deps) {
|
|
36
|
+
return defineSurface({
|
|
37
|
+
kind: 'browser',
|
|
38
|
+
description: "A live view of the agent's browser; click, type, and navigate.",
|
|
39
|
+
open: () => {
|
|
40
|
+
const dataSubs = new Set();
|
|
41
|
+
let last = null;
|
|
42
|
+
let timer = null;
|
|
43
|
+
let inFlight = false;
|
|
44
|
+
let fails = 0;
|
|
45
|
+
// Set once we detect the `playwright` npm package isn't installed. Polling
|
|
46
|
+
// pauses (no point retrying an import that will keep failing) and the pane
|
|
47
|
+
// shows an Install affordance; an `install` input clears it.
|
|
48
|
+
let needsInstall = false;
|
|
49
|
+
let installing = false;
|
|
50
|
+
const emit = (payload) => {
|
|
51
|
+
for (const cb of dataSubs)
|
|
52
|
+
cb(payload);
|
|
53
|
+
};
|
|
54
|
+
const stopPolling = () => {
|
|
55
|
+
if (timer)
|
|
56
|
+
clearInterval(timer);
|
|
57
|
+
timer = null;
|
|
58
|
+
};
|
|
59
|
+
const startPolling = () => {
|
|
60
|
+
if (timer)
|
|
61
|
+
return;
|
|
62
|
+
void tick();
|
|
63
|
+
timer = setInterval(() => void tick(), FRAME_INTERVAL_MS);
|
|
64
|
+
};
|
|
65
|
+
const tick = async () => {
|
|
66
|
+
if (inFlight || needsInstall || installing)
|
|
67
|
+
return; // don't pile up / retry a known-missing dep
|
|
68
|
+
inFlight = true;
|
|
69
|
+
try {
|
|
70
|
+
const reply = await browserSidecarCall('frame', {}, deps);
|
|
71
|
+
if (!isFrame(reply))
|
|
72
|
+
throw new Error('malformed frame reply from sidecar');
|
|
73
|
+
last = reply;
|
|
74
|
+
fails = 0;
|
|
75
|
+
emit({ type: 'frame', base64: reply.base64, mime: reply.mediaType, url: reply.url });
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
// The `playwright` npm package is simply absent — recoverable. Pause
|
|
79
|
+
// polling and ask the user (the download is ~200MB) rather than spin on
|
|
80
|
+
// a failing import or dump a raw "not installed" error.
|
|
81
|
+
if (sidecarErrorKind(err) === 'needs-install') {
|
|
82
|
+
needsInstall = true;
|
|
83
|
+
stopPolling();
|
|
84
|
+
emit({
|
|
85
|
+
type: 'status',
|
|
86
|
+
needsInstall: true,
|
|
87
|
+
text: 'The browser engine (Playwright) is not installed. It is a one-time ~200MB download.',
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// The first failures are usually the browser still launching (or a
|
|
92
|
+
// one-time binary install). Only surface a hard error once it's
|
|
93
|
+
// clearly not transient, so the user isn't left on a silent spinner.
|
|
94
|
+
// Fire EXACTLY on the FAIL_GRACE-th consecutive failure (a successful
|
|
95
|
+
// frame resets `fails` to 0), so the status is emitted once per
|
|
96
|
+
// failure streak rather than every tick. We surface it whether or not
|
|
97
|
+
// a prior frame exists: with no frame yet the page never came up;
|
|
98
|
+
// with a prior frame the page later died and the pane would otherwise
|
|
99
|
+
// sit frozen on a stale screenshot with no hint the live view is dead.
|
|
100
|
+
if (++fails === FAIL_GRACE) {
|
|
101
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
102
|
+
emit({
|
|
103
|
+
type: 'status',
|
|
104
|
+
text: last ? `Browser disconnected: ${message}` : `Browser unavailable: ${message}`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
inFlight = false;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
// After an interaction, grab a frame now and once more shortly after, so a
|
|
113
|
+
// click/keypress that kicks off async work (a navigation, a menu opening,
|
|
114
|
+
// an animation) shows up promptly instead of waiting for the next poll.
|
|
115
|
+
const bump = () => {
|
|
116
|
+
void tick();
|
|
117
|
+
setTimeout(() => void tick(), 140);
|
|
118
|
+
};
|
|
119
|
+
// Kick an immediate frame (launches the browser), then poll.
|
|
120
|
+
startPolling();
|
|
121
|
+
const runInstall = async () => {
|
|
122
|
+
if (installing)
|
|
123
|
+
return;
|
|
124
|
+
installing = true;
|
|
125
|
+
stopPolling();
|
|
126
|
+
emit({ type: 'status', text: 'Installing browser engine… (one-time, ~200MB)' });
|
|
127
|
+
try {
|
|
128
|
+
await installPlaywrightPackage({
|
|
129
|
+
rootDir: resolveBrowserInstallRoot(deps),
|
|
130
|
+
onProgress: (line) => emit({ type: 'status', text: line }),
|
|
131
|
+
});
|
|
132
|
+
// The sidecar cached its failed `import('playwright')`; drop it so the
|
|
133
|
+
// next frame call respawns a fresh sidecar that imports the now-present
|
|
134
|
+
// package.
|
|
135
|
+
await closeBrowserSidecar();
|
|
136
|
+
needsInstall = false;
|
|
137
|
+
fails = 0;
|
|
138
|
+
emit({ type: 'status', text: 'Installed. Starting browser…' });
|
|
139
|
+
startPolling();
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
143
|
+
emit({ type: 'status', needsInstall: true, text: `Install failed: ${message}` });
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
installing = false;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
return {
|
|
150
|
+
id: 'browser',
|
|
151
|
+
kind: 'browser',
|
|
152
|
+
onData: (cb) => {
|
|
153
|
+
dataSubs.add(cb);
|
|
154
|
+
return () => dataSubs.delete(cb);
|
|
155
|
+
},
|
|
156
|
+
snapshot: () => needsInstall
|
|
157
|
+
? {
|
|
158
|
+
type: 'status',
|
|
159
|
+
needsInstall: true,
|
|
160
|
+
text: 'The browser engine (Playwright) is not installed. It is a one-time ~200MB download.',
|
|
161
|
+
}
|
|
162
|
+
: last
|
|
163
|
+
? { type: 'frame', base64: last.base64, mime: last.mediaType, url: last.url }
|
|
164
|
+
: { type: 'status', text: 'Starting browser…' },
|
|
165
|
+
resize: async (size) => {
|
|
166
|
+
// Match the page viewport to the pane so the live view fills the
|
|
167
|
+
// container (no letterboxing) and click coords map 1:1.
|
|
168
|
+
if (!size.width || !size.height)
|
|
169
|
+
return;
|
|
170
|
+
await browserSidecarCall('setviewport', { width: size.width, height: size.height }, deps).catch(() => undefined);
|
|
171
|
+
void tick();
|
|
172
|
+
},
|
|
173
|
+
input: async (msg) => {
|
|
174
|
+
if (msg.type === 'install') {
|
|
175
|
+
await runInstall();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const vw = last?.width ?? 1280;
|
|
179
|
+
const vh = last?.height ?? 720;
|
|
180
|
+
if (msg.type === 'navigate' && typeof msg.url === 'string') {
|
|
181
|
+
// The sidecar's goto re-runs the SSRF guard (loopback/private/
|
|
182
|
+
// metadata blocked), so a hostile URL never navigates. Unlike the
|
|
183
|
+
// pointer/key proxies below, a navigate rejection is user-meaningful
|
|
184
|
+
// (bad URL format, SSRF block) — surface it as a status line instead
|
|
185
|
+
// of swallowing it, so the user knows why the address bar did nothing.
|
|
186
|
+
try {
|
|
187
|
+
await browserSidecarCall('goto', { url: msg.url }, deps);
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
const text = err instanceof Error ? err.message : String(err);
|
|
191
|
+
emit({ type: 'status', text });
|
|
192
|
+
}
|
|
193
|
+
bump();
|
|
194
|
+
}
|
|
195
|
+
else if ((msg.type === 'click' || msg.type === 'dblclick') &&
|
|
196
|
+
typeof msg.fx === 'number' &&
|
|
197
|
+
typeof msg.fy === 'number') {
|
|
198
|
+
const count = msg.type === 'dblclick' ? 2 : 1;
|
|
199
|
+
await browserSidecarCall('mouse', { x: msg.fx * vw, y: msg.fy * vh, count }, deps).catch(() => undefined);
|
|
200
|
+
bump();
|
|
201
|
+
}
|
|
202
|
+
else if (msg.type === 'move' && typeof msg.fx === 'number' && typeof msg.fy === 'number') {
|
|
203
|
+
// Hover — drive the pointer so :hover styles/tooltips render. A single
|
|
204
|
+
// follow-up frame (not a bump) keeps the cost down at move frequency.
|
|
205
|
+
await browserSidecarCall('mousemove', { x: msg.fx * vw, y: msg.fy * vh }, deps).catch(() => undefined);
|
|
206
|
+
void tick();
|
|
207
|
+
}
|
|
208
|
+
else if (msg.type === 'capture' &&
|
|
209
|
+
typeof msg.fx === 'number' &&
|
|
210
|
+
typeof msg.fy === 'number' &&
|
|
211
|
+
typeof msg.fw === 'number' &&
|
|
212
|
+
typeof msg.fh === 'number') {
|
|
213
|
+
// Sharp PNG of the dragged region → handed back to the pane, which
|
|
214
|
+
// attaches it to the chat composer (the user then describes the change).
|
|
215
|
+
const shot = (await browserSidecarCall('capture', { x: msg.fx * vw, y: msg.fy * vh, width: msg.fw * vw, height: msg.fh * vh }, deps).catch(() => null));
|
|
216
|
+
// Validate before emitting so a partial/altered reply doesn't hand
|
|
217
|
+
// the composer a {base64: undefined} attachment.
|
|
218
|
+
if (shot &&
|
|
219
|
+
typeof shot === 'object' &&
|
|
220
|
+
typeof shot.base64 === 'string' &&
|
|
221
|
+
typeof shot.mediaType === 'string') {
|
|
222
|
+
const s = shot;
|
|
223
|
+
emit({ type: 'captured', base64: s.base64, mediaType: s.mediaType });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else if (msg.type === 'zoom' && typeof msg.factor === 'number') {
|
|
227
|
+
await browserSidecarCall('zoom', { factor: msg.factor }, deps).catch(() => undefined);
|
|
228
|
+
bump();
|
|
229
|
+
}
|
|
230
|
+
else if (msg.type === 'key' && typeof msg.key === 'string') {
|
|
231
|
+
await browserSidecarCall('key', { key: msg.key }, deps).catch(() => undefined);
|
|
232
|
+
bump();
|
|
233
|
+
}
|
|
234
|
+
else if (msg.type === 'scroll' && typeof msg.dy === 'number') {
|
|
235
|
+
await browserSidecarCall('scroll', { dy: msg.dy }, deps).catch(() => undefined);
|
|
236
|
+
bump();
|
|
237
|
+
}
|
|
238
|
+
else if (msg.type === 'back' || msg.type === 'forward' || msg.type === 'reload') {
|
|
239
|
+
await browserSidecarCall(msg.type, {}, deps).catch(() => undefined);
|
|
240
|
+
bump();
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
close: () => {
|
|
244
|
+
if (timer)
|
|
245
|
+
clearInterval(timer);
|
|
246
|
+
timer = null;
|
|
247
|
+
dataSubs.clear();
|
|
248
|
+
// The underlying page stays alive for the agent's browser_session tool;
|
|
249
|
+
// it's torn down on session shutdown (closeBrowserSidecar).
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=browser-surface.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-surface.js","sourceRoot":"","sources":["../src/browser-surface.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal HTML → text / markdown extractors used by `web_fetch`. Not a
|
|
3
|
+
* full DOM parser: regex-based, intentionally limited. For stricter
|
|
4
|
+
* extraction use the markdown converter with a selector or upgrade to
|
|
5
|
+
* browser_session.
|
|
6
|
+
*/
|
|
7
|
+
export interface ExtractOptions {
|
|
8
|
+
selector?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Minimal HTML → plain text. Strips <script>, <style>, comments, and tags.
|
|
12
|
+
* Collapses whitespace. Decodes the common HTML entities.
|
|
13
|
+
*/
|
|
14
|
+
export declare function htmlToPlainText(html: string, opts?: ExtractOptions): string;
|
|
15
|
+
/**
|
|
16
|
+
* Minimal HTML → markdown. Maps headings, lists, links, code blocks, and
|
|
17
|
+
* paragraphs. Falls through to plain-text rules for unknown structure.
|
|
18
|
+
*/
|
|
19
|
+
export declare function htmlToMarkdown(html: string, opts?: ExtractOptions): string;
|
|
20
|
+
//# sourceMappingURL=html-extract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-extract.d.ts","sourceRoot":"","sources":["../src/html-extract.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAyBD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,MAAM,CAU/E;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,MAAM,CAwC9E"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal HTML → text / markdown extractors used by `web_fetch`. Not a
|
|
3
|
+
* full DOM parser: regex-based, intentionally limited. For stricter
|
|
4
|
+
* extraction use the markdown converter with a selector or upgrade to
|
|
5
|
+
* browser_session.
|
|
6
|
+
*/
|
|
7
|
+
const COMMENT_RE = /<!--[\s\S]*?-->/g;
|
|
8
|
+
const SCRIPT_RE = /<script\b[^>]*>[\s\S]*?<\/script>/gi;
|
|
9
|
+
const STYLE_RE = /<style\b[^>]*>[\s\S]*?<\/style>/gi;
|
|
10
|
+
/**
|
|
11
|
+
* Hard ceiling on the HTML fed to the regex extractors. web_fetch caps the raw
|
|
12
|
+
* body at up to 20MB (maxBytes), but the lazy `[\s\S]*?` passes here run
|
|
13
|
+
* SYNCHRONOUSLY on the runner event loop — a multi-MB adversarial page with
|
|
14
|
+
* many unmatched open tags could block it. Truncating to a generous 4MB bounds
|
|
15
|
+
* that worst case while still covering virtually every real article.
|
|
16
|
+
*/
|
|
17
|
+
const MAX_EXTRACT_INPUT = 4 * 1024 * 1024;
|
|
18
|
+
/** Bound the regex-extractor input so a hostile, oversized page can't stall the
|
|
19
|
+
* event loop. Cuts at a tag boundary when one is near the limit to avoid
|
|
20
|
+
* splitting an entity/tag mid-token. */
|
|
21
|
+
function capInput(html) {
|
|
22
|
+
if (html.length <= MAX_EXTRACT_INPUT)
|
|
23
|
+
return html;
|
|
24
|
+
const slice = html.slice(0, MAX_EXTRACT_INPUT);
|
|
25
|
+
const lastLt = slice.lastIndexOf('<');
|
|
26
|
+
return lastLt > MAX_EXTRACT_INPUT - 1024 ? slice.slice(0, lastLt) : slice;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Minimal HTML → plain text. Strips <script>, <style>, comments, and tags.
|
|
30
|
+
* Collapses whitespace. Decodes the common HTML entities.
|
|
31
|
+
*/
|
|
32
|
+
export function htmlToPlainText(html, opts = {}) {
|
|
33
|
+
let body = sliceBySelector(capInput(html), opts.selector);
|
|
34
|
+
body = body.replace(COMMENT_RE, '');
|
|
35
|
+
body = body.replace(SCRIPT_RE, '');
|
|
36
|
+
body = body.replace(STYLE_RE, '');
|
|
37
|
+
body = body.replace(/<br\b[^>]*>/gi, '\n');
|
|
38
|
+
body = body.replace(/<\/(p|div|li|tr|h[1-6])>/gi, '\n');
|
|
39
|
+
body = body.replace(/<[^>]+>/g, '');
|
|
40
|
+
body = decodeEntities(body);
|
|
41
|
+
return collapseWhitespace(body);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Minimal HTML → markdown. Maps headings, lists, links, code blocks, and
|
|
45
|
+
* paragraphs. Falls through to plain-text rules for unknown structure.
|
|
46
|
+
*/
|
|
47
|
+
export function htmlToMarkdown(html, opts = {}) {
|
|
48
|
+
let body = sliceBySelector(capInput(html), opts.selector);
|
|
49
|
+
body = body.replace(COMMENT_RE, '');
|
|
50
|
+
body = body.replace(SCRIPT_RE, '');
|
|
51
|
+
body = body.replace(STYLE_RE, '');
|
|
52
|
+
// headings
|
|
53
|
+
body = body.replace(/<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1>/gi, (_, lvl, inner) => `\n\n${'#'.repeat(Number(lvl))} ${stripTags(inner).trim()}\n\n`);
|
|
54
|
+
// links: keep text + url. Accept double-quoted, single-quoted, and unquoted
|
|
55
|
+
// hrefs — real-world HTML uses all three, and matching only "double" silently
|
|
56
|
+
// dropped the URL (the link text survived via the later tag-strip).
|
|
57
|
+
body = body.replace(/<a\b[^>]*\bhref=(?:"([^"]+)"|'([^']+)'|([^\s>]+))[^>]*>([\s\S]*?)<\/a>/gi, (_, dq, sq, uq, inner) => `[${stripTags(inner).trim()}](${dq ?? sq ?? uq})`);
|
|
58
|
+
// code
|
|
59
|
+
body = body.replace(/<pre\b[^>]*>([\s\S]*?)<\/pre>/gi, (_, inner) => `\n\n\`\`\`\n${stripTags(inner)}\n\`\`\`\n\n`);
|
|
60
|
+
body = body.replace(/<code\b[^>]*>([\s\S]*?)<\/code>/gi, (_, inner) => `\`${stripTags(inner)}\``);
|
|
61
|
+
// lists
|
|
62
|
+
body = body.replace(/<li\b[^>]*>([\s\S]*?)<\/li>/gi, (_, inner) => `\n- ${stripTags(inner).trim()}`);
|
|
63
|
+
body = body.replace(/<\/?(ul|ol)\b[^>]*>/gi, '\n');
|
|
64
|
+
// line breaks
|
|
65
|
+
body = body.replace(/<br\b[^>]*>/gi, '\n');
|
|
66
|
+
body = body.replace(/<\/?p\b[^>]*>/gi, '\n\n');
|
|
67
|
+
body = body.replace(/<[^>]+>/g, '');
|
|
68
|
+
body = decodeEntities(body);
|
|
69
|
+
return collapseWhitespace(body);
|
|
70
|
+
}
|
|
71
|
+
function sliceBySelector(html, selector) {
|
|
72
|
+
if (!selector)
|
|
73
|
+
return html;
|
|
74
|
+
const slice = extractFirstTagBlock(html, selector);
|
|
75
|
+
return slice ?? html;
|
|
76
|
+
}
|
|
77
|
+
function collapseWhitespace(s) {
|
|
78
|
+
return s.replace(/[ \t]+/g, ' ').replace(/\n[ \t]+/g, '\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
79
|
+
}
|
|
80
|
+
function stripTags(s) {
|
|
81
|
+
return s.replace(/<[^>]+>/g, '');
|
|
82
|
+
}
|
|
83
|
+
function decodeEntities(s) {
|
|
84
|
+
const map = {
|
|
85
|
+
'&': '&',
|
|
86
|
+
'<': '<',
|
|
87
|
+
'>': '>',
|
|
88
|
+
'"': '"',
|
|
89
|
+
''': "'",
|
|
90
|
+
''': "'",
|
|
91
|
+
' ': ' ',
|
|
92
|
+
};
|
|
93
|
+
return s.replace(/&[a-zA-Z]+;|&#\d+;/g, (m) => {
|
|
94
|
+
if (m in map)
|
|
95
|
+
return map[m];
|
|
96
|
+
const numMatch = /^&#(\d+);$/.exec(m);
|
|
97
|
+
if (numMatch)
|
|
98
|
+
return String.fromCharCode(Number(numMatch[1]));
|
|
99
|
+
return m;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Pull the first <tag>...</tag> block (or self-closing tag with id="x") whose
|
|
104
|
+
* tag name OR id matches `selector`. Very limited — supports `tagName` and
|
|
105
|
+
* `#id` only. For richer querying upgrade to browser_session.
|
|
106
|
+
*/
|
|
107
|
+
function extractFirstTagBlock(html, selector) {
|
|
108
|
+
if (selector.startsWith('#')) {
|
|
109
|
+
const id = selector.slice(1);
|
|
110
|
+
const re = new RegExp(`<([a-z][a-z0-9-]*)\\b[^>]*\\bid=["']${escapeReSelector(id)}["'][^>]*>([\\s\\S]*?)<\\/\\1>`, 'i');
|
|
111
|
+
const match = re.exec(html);
|
|
112
|
+
return match ? match[2] : null;
|
|
113
|
+
}
|
|
114
|
+
const tag = selector.toLowerCase();
|
|
115
|
+
const re = new RegExp(`<${tag}\\b[^>]*>([\\s\\S]*?)<\\/${tag}>`, 'i');
|
|
116
|
+
const match = re.exec(html);
|
|
117
|
+
return match ? match[1] : null;
|
|
118
|
+
}
|
|
119
|
+
function escapeReSelector(s) {
|
|
120
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=html-extract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-extract.js","sourceRoot":"","sources":["../src/html-extract.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type BrowserSessionDeps } from './browser-session.js';
|
|
2
|
+
export { webFetchTool, htmlToPlainText, htmlToMarkdown } from './web-fetch.js';
|
|
3
|
+
export { buildBrowserSessionTool, browserSidecarCall, closeBrowserSidecar, type BrowserSessionDeps, type SidecarStream, } from './browser-session.js';
|
|
4
|
+
export { buildBrowserSurface } from './browser-surface.js';
|
|
5
|
+
export interface BuildBrowserPluginOptions extends BrowserSessionDeps {
|
|
6
|
+
}
|
|
7
|
+
export declare function buildBrowserPlugin(opts?: BuildBrowserPluginOptions): import("@moxxy/sdk").Plugin;
|
|
8
|
+
export declare const browserPlugin: import("@moxxy/sdk").Plugin;
|
|
9
|
+
export default browserPlugin;
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgD,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG7G,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,kBAAkB,EACvB,KAAK,aAAa,GACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,WAAW,yBAA0B,SAAQ,kBAAkB;CAAG;AAExE,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,yBAA8B,+BAatE;AAED,eAAO,MAAM,aAAa,6BAAuB,CAAC;AAElD,eAAe,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { definePlugin } from '@moxxy/sdk';
|
|
2
|
+
import { webFetchTool } from './web-fetch.js';
|
|
3
|
+
import { buildBrowserSessionTool, closeBrowserSidecar } from './browser-session.js';
|
|
4
|
+
import { buildBrowserSurface } from './browser-surface.js';
|
|
5
|
+
export { webFetchTool, htmlToPlainText, htmlToMarkdown } from './web-fetch.js';
|
|
6
|
+
export { buildBrowserSessionTool, browserSidecarCall, closeBrowserSidecar, } from './browser-session.js';
|
|
7
|
+
export { buildBrowserSurface } from './browser-surface.js';
|
|
8
|
+
export function buildBrowserPlugin(opts = {}) {
|
|
9
|
+
return definePlugin({
|
|
10
|
+
name: '@moxxy/plugin-browser',
|
|
11
|
+
version: '0.0.0',
|
|
12
|
+
tools: [webFetchTool, buildBrowserSessionTool(opts)],
|
|
13
|
+
surfaces: [buildBrowserSurface(opts)],
|
|
14
|
+
hooks: {
|
|
15
|
+
onShutdown: async () => {
|
|
16
|
+
// Make sure the sidecar process exits with the session.
|
|
17
|
+
await closeBrowserSidecar();
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export const browserPlugin = buildBrowserPlugin();
|
|
23
|
+
export default browserPlugin;
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAA2B,MAAM,sBAAsB,CAAC;AAC7G,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,GAGpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAI3D,MAAM,UAAU,kBAAkB,CAAC,OAAkC,EAAE;IACrE,OAAO,YAAY,CAAC;QAClB,IAAI,EAAE,uBAAuB;QAC7B,OAAO,EAAE,OAAO;QAChB,KAAK,EAAE,CAAC,YAAY,EAAE,uBAAuB,CAAC,IAAI,CAAC,CAAC;QACpD,QAAQ,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;QACrC,KAAK,EAAE;YACL,UAAU,EAAE,KAAK,IAAI,EAAE;gBACrB,wDAAwD;gBACxD,MAAM,mBAAmB,EAAE,CAAC;YAC9B,CAAC;SACF;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,kBAAkB,EAAE,CAAC;AAElD,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-RPC dispatch table for the sidecar. Each `method` here corresponds
|
|
3
|
+
* one-to-one with the wire-format methods documented in `sidecar.ts`.
|
|
4
|
+
*/
|
|
5
|
+
import { type PlaywrightHandle, type Reply, type Req } from './types.js';
|
|
6
|
+
export interface SidecarState {
|
|
7
|
+
handle: PlaywrightHandle | null;
|
|
8
|
+
/**
|
|
9
|
+
* Set after a successful auto-install of browser binaries so the next
|
|
10
|
+
* tool result can carry a `notice` letting the user/model know the
|
|
11
|
+
* one-time download happened. Cleared once the notice has been
|
|
12
|
+
* delivered (handed to the reply once, then forgotten).
|
|
13
|
+
*/
|
|
14
|
+
pendingInstallNotice: string | null;
|
|
15
|
+
}
|
|
16
|
+
export declare function teardown(state: SidecarState): Promise<void>;
|
|
17
|
+
export declare function dispatch(state: SidecarState, req: Req): Promise<Reply>;
|
|
18
|
+
//# sourceMappingURL=dispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/sidecar/dispatch.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAKL,KAAK,gBAAgB,EACrB,KAAK,KAAK,EACV,KAAK,GAAG,EACT,MAAM,YAAY,CAAC;AAepB,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAChC;;;;;OAKG;IACH,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAgBD,wBAAsB,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CASjE;AAED,wBAAsB,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAU5E"}
|