agentic-browser 2.1.0 → 2.2.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/README.md +4 -2
- package/dist/cli/index.mjs +14 -7
- package/dist/index.mjs +1 -1
- package/dist/mcp/index.mjs +55 -11
- package/dist/{runtime-CEvs2yzE.mjs → runtime-CRkUsvhV.mjs} +483 -65
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,7 +113,7 @@ agentic-browser agent start --user-agent "MyBot/1.0"
|
|
|
113
113
|
agentic-browser agent status
|
|
114
114
|
agentic-browser agent navigate https://example.com
|
|
115
115
|
agentic-browser agent click "#login"
|
|
116
|
-
agentic-browser agent content --mode
|
|
116
|
+
agentic-browser agent content --mode summary
|
|
117
117
|
agentic-browser agent content --mode html --selector main
|
|
118
118
|
agentic-browser agent elements
|
|
119
119
|
agentic-browser agent elements --roles button,link --limit 20
|
|
@@ -156,6 +156,8 @@ MCP responses are compact — `visible`, `actions`, and `tagName` are omitted to
|
|
|
156
156
|
|
|
157
157
|
When an element lives inside an open shadow root or a same-origin iframe, discovery returns a composed locator using `>>>` to cross boundaries, for example `iframe[name="checkout"] >>> button[aria-label="Pay"]`. The same locator string works with `agent click`, `agent type`, MCP `browser_interact`, and fallback selectors.
|
|
158
158
|
|
|
159
|
+
`agent content` and MCP `browser_get_content` now default to `summary`, which returns a low-token overview with `title`, `headings`, `landmarks`, `primaryActions`, `inputs`, `alerts`, and iframe metadata. Use `a11y` when you need the deeper accessibility structure, and `text` or `html` only when you need raw content.
|
|
160
|
+
|
|
159
161
|
````
|
|
160
162
|
|
|
161
163
|
## MCP Server
|
|
@@ -225,8 +227,8 @@ More `interact` actions:
|
|
|
225
227
|
### 4. Read Page Content
|
|
226
228
|
|
|
227
229
|
```bash
|
|
230
|
+
agentic-browser page:content <sessionId> --mode summary
|
|
228
231
|
agentic-browser page:content <sessionId> --mode title
|
|
229
|
-
agentic-browser page:content <sessionId> --mode text
|
|
230
232
|
agentic-browser page:content <sessionId> --mode html --selector main
|
|
231
233
|
```
|
|
232
234
|
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { r as createCliRuntime } from "../runtime-
|
|
2
|
+
import { r as createCliRuntime } from "../runtime-CRkUsvhV.mjs";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import crypto from "node:crypto";
|
|
@@ -151,7 +151,8 @@ async function agentContent(runtime, input) {
|
|
|
151
151
|
action: "content",
|
|
152
152
|
...await runtime.api.getContent(sessionId, {
|
|
153
153
|
mode: input.mode,
|
|
154
|
-
selector: input.selector
|
|
154
|
+
selector: input.selector,
|
|
155
|
+
maxChars: input.maxChars
|
|
155
156
|
})
|
|
156
157
|
};
|
|
157
158
|
}
|
|
@@ -249,7 +250,8 @@ async function runCommand(runtime, input) {
|
|
|
249
250
|
async function runPageContent(runtime, input) {
|
|
250
251
|
return await runtime.api.getContent(input.sessionId, {
|
|
251
252
|
mode: input.mode,
|
|
252
|
-
selector: input.selector
|
|
253
|
+
selector: input.selector,
|
|
254
|
+
maxChars: input.maxChars
|
|
253
255
|
});
|
|
254
256
|
}
|
|
255
257
|
//#endregion
|
|
@@ -322,11 +324,12 @@ async function main() {
|
|
|
322
324
|
});
|
|
323
325
|
console.log(JSON.stringify(result));
|
|
324
326
|
});
|
|
325
|
-
program.command("page:content").argument("<sessionId>").option("--mode <mode>", "title|text|html|a11y", "
|
|
327
|
+
program.command("page:content").argument("<sessionId>").option("--mode <mode>", "title|text|html|a11y|summary", "summary").option("--selector <selector>", "optional CSS selector").option("--max-chars <n>", "max characters for text/html/a11y modes").action(async (sessionId, options) => {
|
|
326
328
|
const result = await runPageContent(runtime, {
|
|
327
329
|
sessionId,
|
|
328
330
|
mode: options.mode,
|
|
329
|
-
selector: options.selector
|
|
331
|
+
selector: options.selector,
|
|
332
|
+
maxChars: options.maxChars ? Number.parseInt(options.maxChars, 10) : void 0
|
|
330
333
|
});
|
|
331
334
|
console.log(JSON.stringify(result));
|
|
332
335
|
});
|
|
@@ -470,8 +473,12 @@ async function main() {
|
|
|
470
473
|
const result = await agentTerminate(runtime);
|
|
471
474
|
console.log(JSON.stringify(result));
|
|
472
475
|
});
|
|
473
|
-
agent.command("content").option("--mode <mode>", "title|text|html|a11y", "
|
|
474
|
-
const result = await agentContent(runtime,
|
|
476
|
+
agent.command("content").option("--mode <mode>", "title|text|html|a11y|summary", "summary").option("--selector <selector>", "optional CSS selector").option("--max-chars <n>", "max characters for text/html/a11y modes").action(async (options) => {
|
|
477
|
+
const result = await agentContent(runtime, {
|
|
478
|
+
mode: options.mode,
|
|
479
|
+
selector: options.selector,
|
|
480
|
+
maxChars: options.maxChars ? Number.parseInt(options.maxChars, 10) : void 0
|
|
481
|
+
});
|
|
475
482
|
console.log(JSON.stringify(result));
|
|
476
483
|
});
|
|
477
484
|
agent.command("elements").description("List interactive elements on the current page").option("--roles <roles>", "comma-separated roles filter", (v) => v.split(",")).option("--visible-only", "only visible elements", true).option("--no-visible-only", "include hidden elements").option("--limit <n>", "max elements", "50").option("--selector <selector>", "scope to CSS selector subtree").action(async (options) => {
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as createMockAgenticBrowserCore, n as createAgenticBrowserCore, t as AgenticBrowserCore } from "./runtime-
|
|
2
|
+
import { i as createMockAgenticBrowserCore, n as createAgenticBrowserCore, t as AgenticBrowserCore } from "./runtime-CRkUsvhV.mjs";
|
|
3
3
|
export { AgenticBrowserCore, createAgenticBrowserCore, createMockAgenticBrowserCore };
|
package/dist/mcp/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as createAgenticBrowserCore } from "../runtime-
|
|
2
|
+
import { n as createAgenticBrowserCore } from "../runtime-CRkUsvhV.mjs";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -22,10 +22,27 @@ function truncateContent(content, limit) {
|
|
|
22
22
|
};
|
|
23
23
|
const originalLength = content.length;
|
|
24
24
|
return {
|
|
25
|
-
content: content.slice(0, limit) + `\n\n[Truncated
|
|
25
|
+
content: content.slice(0, limit) + `\n\n[Truncated - showing first ${limit} of ${originalLength} characters. Use a CSS selector to scope the content, or use mode="summary" for a lower-token overview.]`,
|
|
26
26
|
truncated: true
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
function resolveCompactedContent(result, limit) {
|
|
30
|
+
if (result.truncated) return {
|
|
31
|
+
content: result.content,
|
|
32
|
+
truncated: true
|
|
33
|
+
};
|
|
34
|
+
return truncateContent(result.content, limit);
|
|
35
|
+
}
|
|
36
|
+
function parseStructuredSummary(result) {
|
|
37
|
+
if (result.structuredContent) return result.structuredContent;
|
|
38
|
+
if (!result.content) return;
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(result.content);
|
|
41
|
+
return parsed && typeof parsed === "object" ? parsed : void 0;
|
|
42
|
+
} catch {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
29
46
|
function summarizeTextContent(content) {
|
|
30
47
|
const lines = uniqueLines(content);
|
|
31
48
|
return {
|
|
@@ -68,9 +85,23 @@ function compactPageContent(result, maxChars) {
|
|
|
68
85
|
text: 8e3,
|
|
69
86
|
a11y: 1e4,
|
|
70
87
|
html: 4e3,
|
|
71
|
-
title: void 0
|
|
88
|
+
title: void 0,
|
|
89
|
+
summary: void 0
|
|
72
90
|
}[result.mode];
|
|
73
|
-
|
|
91
|
+
if (result.mode === "summary") {
|
|
92
|
+
const structured = parseStructuredSummary(result);
|
|
93
|
+
if (structured) return {
|
|
94
|
+
mode: "summary",
|
|
95
|
+
...structured,
|
|
96
|
+
truncated: false
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
mode: "summary",
|
|
100
|
+
content: result.content,
|
|
101
|
+
truncated: false
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const truncated = resolveCompactedContent(result, limit);
|
|
74
105
|
if (result.mode === "title") return {
|
|
75
106
|
...result,
|
|
76
107
|
truncated: false
|
|
@@ -81,6 +112,7 @@ function compactPageContent(result, maxChars) {
|
|
|
81
112
|
mode: result.mode,
|
|
82
113
|
content: truncated.content,
|
|
83
114
|
truncated: truncated.truncated,
|
|
115
|
+
originalLength: result.originalLength,
|
|
84
116
|
summaryLines: summary.summaryLines,
|
|
85
117
|
previewLines: summary.previewLines
|
|
86
118
|
};
|
|
@@ -91,6 +123,7 @@ function compactPageContent(result, maxChars) {
|
|
|
91
123
|
mode: result.mode,
|
|
92
124
|
content: truncated.content,
|
|
93
125
|
truncated: truncated.truncated,
|
|
126
|
+
originalLength: result.originalLength,
|
|
94
127
|
headings: summary.headings,
|
|
95
128
|
landmarks: summary.landmarks,
|
|
96
129
|
actions: summary.actions,
|
|
@@ -101,7 +134,8 @@ function compactPageContent(result, maxChars) {
|
|
|
101
134
|
return {
|
|
102
135
|
mode: result.mode,
|
|
103
136
|
content: truncated.content,
|
|
104
|
-
truncated: truncated.truncated
|
|
137
|
+
truncated: truncated.truncated,
|
|
138
|
+
originalLength: result.originalLength
|
|
105
139
|
};
|
|
106
140
|
}
|
|
107
141
|
function summarizeElements(elements) {
|
|
@@ -159,6 +193,13 @@ function compactMemoryResults(results) {
|
|
|
159
193
|
//#region src/mcp/index.ts
|
|
160
194
|
let core;
|
|
161
195
|
let activeSessionId;
|
|
196
|
+
const DEFAULT_MCP_MAX_CHARS = {
|
|
197
|
+
summary: void 0,
|
|
198
|
+
title: void 0,
|
|
199
|
+
text: 8e3,
|
|
200
|
+
a11y: 1e4,
|
|
201
|
+
html: 4e3
|
|
202
|
+
};
|
|
162
203
|
function getCore() {
|
|
163
204
|
if (!core) core = createAgenticBrowserCore();
|
|
164
205
|
return core;
|
|
@@ -265,26 +306,29 @@ server.tool("browser_interact", "Interact with a page element or perform browser
|
|
|
265
306
|
text: JSON.stringify(compact)
|
|
266
307
|
}] };
|
|
267
308
|
});
|
|
268
|
-
server.tool("browser_get_content", "Get the current page content. Modes: \"text\" (readable text), \"a11y\" (accessibility tree
|
|
309
|
+
server.tool("browser_get_content", "Get the current page content. Modes: \"summary\" (recommended low-token overview with headings, actions, inputs, alerts, and iframe awareness), \"text\" (readable text), \"a11y\" (accessibility tree for deeper structure), \"title\" (page title only), \"html\" (raw HTML). A session is auto-started if needed.", {
|
|
269
310
|
mode: z.enum([
|
|
270
311
|
"title",
|
|
271
312
|
"text",
|
|
272
313
|
"html",
|
|
273
|
-
"a11y"
|
|
274
|
-
|
|
314
|
+
"a11y",
|
|
315
|
+
"summary"
|
|
316
|
+
]).default("summary").describe("Content extraction mode"),
|
|
275
317
|
selector: z.string().optional().describe("CSS selector to scope content (e.g. \"main\", \"#content\")"),
|
|
276
|
-
maxChars: z.number().optional().describe("Maximum characters to return (default:
|
|
318
|
+
maxChars: z.number().optional().describe("Maximum characters to return (default: 8000 for text, 10000 for a11y, 4000 for html, ignored for summary/title). Use a CSS selector to scope content instead of raising this limit."),
|
|
277
319
|
sessionId: z.string().optional().describe("Session ID (auto-resolved if omitted)")
|
|
278
320
|
}, async ({ mode, selector, maxChars, sessionId }) => {
|
|
279
321
|
const sid = await resolveSession(sessionId);
|
|
322
|
+
const effectiveMaxChars = maxChars ?? DEFAULT_MCP_MAX_CHARS[mode];
|
|
280
323
|
const result = await getCore().getPageContent({
|
|
281
324
|
sessionId: sid,
|
|
282
325
|
mode,
|
|
283
|
-
selector
|
|
326
|
+
selector,
|
|
327
|
+
maxChars: effectiveMaxChars
|
|
284
328
|
});
|
|
285
329
|
return { content: [{
|
|
286
330
|
type: "text",
|
|
287
|
-
text: JSON.stringify(compactPageContent(result
|
|
331
|
+
text: JSON.stringify(compactPageContent(result))
|
|
288
332
|
}] };
|
|
289
333
|
});
|
|
290
334
|
server.tool("browser_get_elements", "Discover all interactive elements on the current page (buttons, links, inputs, etc.). Returns CSS selectors and fallbackSelectors you can use with browser_interact. Pass fallbackSelectors to browser_interact for automatic retry when the primary selector breaks. Actions are derived from role: link/button/custom→click, input/textarea/contenteditable→click+type+press, select→click+select, checkbox/radio→toggle. A session is auto-started if needed.", {
|
|
@@ -5,8 +5,8 @@ import net from "node:net";
|
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import WebSocket, { WebSocketServer } from "ws";
|
|
8
|
-
import { URL as URL$1, fileURLToPath } from "node:url";
|
|
9
8
|
import crypto from "node:crypto";
|
|
9
|
+
import { URL as URL$1 } from "node:url";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
//#region src/session/chrome-launcher.ts
|
|
12
12
|
const CANDIDATES = [
|
|
@@ -22,16 +22,6 @@ function discoverChrome(explicitPath) {
|
|
|
22
22
|
return found;
|
|
23
23
|
}
|
|
24
24
|
//#endregion
|
|
25
|
-
//#region src/session/extension-loader.ts
|
|
26
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
-
function loadControlExtension() {
|
|
28
|
-
const packageRoot = path.resolve(__dirname, "..");
|
|
29
|
-
return {
|
|
30
|
-
extensionPath: path.resolve(packageRoot, "extension"),
|
|
31
|
-
loadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
//#endregion
|
|
35
25
|
//#region src/session/browser-controller.ts
|
|
36
26
|
var CdpConnection = class CdpConnection {
|
|
37
27
|
nextId = 0;
|
|
@@ -274,6 +264,7 @@ function resolveDefaultProfileDir() {
|
|
|
274
264
|
if (platform === "win32") return path.join(process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local"), "Google", "Chrome", "User Data");
|
|
275
265
|
return path.join(home, ".config", "google-chrome");
|
|
276
266
|
}
|
|
267
|
+
const CONTENT_TRUNCATION_HINT = "Use a CSS selector to scope the content, or use mode=\"summary\" for a lower-token overview.";
|
|
277
268
|
const LOCATOR_RUNTIME_HELPERS = String.raw`
|
|
278
269
|
const LOCATOR_SEPARATOR = ' >>> ';
|
|
279
270
|
|
|
@@ -482,6 +473,168 @@ ${LOCATOR_RUNTIME_HELPERS}
|
|
|
482
473
|
${body}
|
|
483
474
|
})()`;
|
|
484
475
|
}
|
|
476
|
+
const SPECIAL_KEY_PAYLOADS = new Map([
|
|
477
|
+
["Enter", {
|
|
478
|
+
key: "Enter",
|
|
479
|
+
code: "Enter",
|
|
480
|
+
windowsVirtualKeyCode: 13,
|
|
481
|
+
nativeVirtualKeyCode: 13,
|
|
482
|
+
text: "\r",
|
|
483
|
+
unmodifiedText: "\r"
|
|
484
|
+
}],
|
|
485
|
+
["Tab", {
|
|
486
|
+
key: "Tab",
|
|
487
|
+
code: "Tab",
|
|
488
|
+
windowsVirtualKeyCode: 9,
|
|
489
|
+
nativeVirtualKeyCode: 9
|
|
490
|
+
}],
|
|
491
|
+
["Escape", {
|
|
492
|
+
key: "Escape",
|
|
493
|
+
code: "Escape",
|
|
494
|
+
windowsVirtualKeyCode: 27,
|
|
495
|
+
nativeVirtualKeyCode: 27
|
|
496
|
+
}],
|
|
497
|
+
["Backspace", {
|
|
498
|
+
key: "Backspace",
|
|
499
|
+
code: "Backspace",
|
|
500
|
+
windowsVirtualKeyCode: 8,
|
|
501
|
+
nativeVirtualKeyCode: 8
|
|
502
|
+
}],
|
|
503
|
+
["Delete", {
|
|
504
|
+
key: "Delete",
|
|
505
|
+
code: "Delete",
|
|
506
|
+
windowsVirtualKeyCode: 46,
|
|
507
|
+
nativeVirtualKeyCode: 46
|
|
508
|
+
}],
|
|
509
|
+
["Space", {
|
|
510
|
+
key: " ",
|
|
511
|
+
code: "Space",
|
|
512
|
+
windowsVirtualKeyCode: 32,
|
|
513
|
+
nativeVirtualKeyCode: 32,
|
|
514
|
+
text: " ",
|
|
515
|
+
unmodifiedText: " "
|
|
516
|
+
}],
|
|
517
|
+
[" ", {
|
|
518
|
+
key: " ",
|
|
519
|
+
code: "Space",
|
|
520
|
+
windowsVirtualKeyCode: 32,
|
|
521
|
+
nativeVirtualKeyCode: 32,
|
|
522
|
+
text: " ",
|
|
523
|
+
unmodifiedText: " "
|
|
524
|
+
}],
|
|
525
|
+
["ArrowUp", {
|
|
526
|
+
key: "ArrowUp",
|
|
527
|
+
code: "ArrowUp",
|
|
528
|
+
windowsVirtualKeyCode: 38,
|
|
529
|
+
nativeVirtualKeyCode: 38
|
|
530
|
+
}],
|
|
531
|
+
["ArrowDown", {
|
|
532
|
+
key: "ArrowDown",
|
|
533
|
+
code: "ArrowDown",
|
|
534
|
+
windowsVirtualKeyCode: 40,
|
|
535
|
+
nativeVirtualKeyCode: 40
|
|
536
|
+
}],
|
|
537
|
+
["ArrowLeft", {
|
|
538
|
+
key: "ArrowLeft",
|
|
539
|
+
code: "ArrowLeft",
|
|
540
|
+
windowsVirtualKeyCode: 37,
|
|
541
|
+
nativeVirtualKeyCode: 37
|
|
542
|
+
}],
|
|
543
|
+
["ArrowRight", {
|
|
544
|
+
key: "ArrowRight",
|
|
545
|
+
code: "ArrowRight",
|
|
546
|
+
windowsVirtualKeyCode: 39,
|
|
547
|
+
nativeVirtualKeyCode: 39
|
|
548
|
+
}],
|
|
549
|
+
["Home", {
|
|
550
|
+
key: "Home",
|
|
551
|
+
code: "Home",
|
|
552
|
+
windowsVirtualKeyCode: 36,
|
|
553
|
+
nativeVirtualKeyCode: 36
|
|
554
|
+
}],
|
|
555
|
+
["End", {
|
|
556
|
+
key: "End",
|
|
557
|
+
code: "End",
|
|
558
|
+
windowsVirtualKeyCode: 35,
|
|
559
|
+
nativeVirtualKeyCode: 35
|
|
560
|
+
}],
|
|
561
|
+
["PageUp", {
|
|
562
|
+
key: "PageUp",
|
|
563
|
+
code: "PageUp",
|
|
564
|
+
windowsVirtualKeyCode: 33,
|
|
565
|
+
nativeVirtualKeyCode: 33
|
|
566
|
+
}],
|
|
567
|
+
["PageDown", {
|
|
568
|
+
key: "PageDown",
|
|
569
|
+
code: "PageDown",
|
|
570
|
+
windowsVirtualKeyCode: 34,
|
|
571
|
+
nativeVirtualKeyCode: 34
|
|
572
|
+
}]
|
|
573
|
+
]);
|
|
574
|
+
function appendTruncationNotice(content, limit, originalLength) {
|
|
575
|
+
return content + `\n\n[Truncated - showing first ${limit} of ${originalLength} characters. ${CONTENT_TRUNCATION_HINT}]`;
|
|
576
|
+
}
|
|
577
|
+
function applyContentLimit(content, limit, originalLength = content.length) {
|
|
578
|
+
if (!limit || originalLength <= limit) return {
|
|
579
|
+
content,
|
|
580
|
+
truncated: false
|
|
581
|
+
};
|
|
582
|
+
return {
|
|
583
|
+
content: appendTruncationNotice(content.slice(0, limit), limit, originalLength),
|
|
584
|
+
truncated: true,
|
|
585
|
+
originalLength
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
function normalizeRuntimeContentPayload(value, limit) {
|
|
589
|
+
if (typeof value === "string") {
|
|
590
|
+
const limited = applyContentLimit(value, limit);
|
|
591
|
+
return {
|
|
592
|
+
content: limited.content,
|
|
593
|
+
truncated: limited.truncated || void 0,
|
|
594
|
+
originalLength: limited.originalLength
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
const content = value?.content ?? "";
|
|
598
|
+
if (value?.truncated) {
|
|
599
|
+
const originalLength = value.originalLength ?? content.length;
|
|
600
|
+
const effectiveLimit = limit && limit > 0 ? limit : content.length;
|
|
601
|
+
return {
|
|
602
|
+
content: content.includes("[Truncated - showing first") || effectiveLimit === 0 ? content : appendTruncationNotice(content, effectiveLimit, originalLength),
|
|
603
|
+
truncated: true,
|
|
604
|
+
originalLength
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
const limited = applyContentLimit(content, limit, value?.originalLength ?? content.length);
|
|
608
|
+
return {
|
|
609
|
+
content: limited.content,
|
|
610
|
+
truncated: limited.truncated || void 0,
|
|
611
|
+
originalLength: limited.originalLength
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
function buildKeyDispatchPayload(inputKey) {
|
|
615
|
+
const key = inputKey || "Enter";
|
|
616
|
+
const special = SPECIAL_KEY_PAYLOADS.get(key);
|
|
617
|
+
if (special) return { ...special };
|
|
618
|
+
if (key.length === 1) {
|
|
619
|
+
const upper = key.toUpperCase();
|
|
620
|
+
const code = key >= "a" && key <= "z" ? `Key${upper}` : key >= "A" && key <= "Z" ? `Key${key}` : key >= "0" && key <= "9" ? `Digit${key}` : "Unidentified";
|
|
621
|
+
const keyCode = upper.charCodeAt(0);
|
|
622
|
+
return {
|
|
623
|
+
key,
|
|
624
|
+
code,
|
|
625
|
+
windowsVirtualKeyCode: keyCode,
|
|
626
|
+
nativeVirtualKeyCode: keyCode,
|
|
627
|
+
text: key,
|
|
628
|
+
unmodifiedText: key
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
return {
|
|
632
|
+
key,
|
|
633
|
+
code: key,
|
|
634
|
+
windowsVirtualKeyCode: 0,
|
|
635
|
+
nativeVirtualKeyCode: 0
|
|
636
|
+
};
|
|
637
|
+
}
|
|
485
638
|
var ChromeCdpBrowserController = class {
|
|
486
639
|
connections = /* @__PURE__ */ new Map();
|
|
487
640
|
cleanupInterval;
|
|
@@ -564,7 +717,6 @@ var ChromeCdpBrowserController = class {
|
|
|
564
717
|
async launch(sessionId, options) {
|
|
565
718
|
const { executablePath: explicitPath, userProfileDir, headless, userAgent } = options ?? {};
|
|
566
719
|
const executablePath = discoverChrome(explicitPath);
|
|
567
|
-
const extension = loadControlExtension();
|
|
568
720
|
let profileDir;
|
|
569
721
|
if (userProfileDir === "default") profileDir = resolveDefaultProfileDir();
|
|
570
722
|
else if (userProfileDir) profileDir = userProfileDir;
|
|
@@ -572,23 +724,7 @@ var ChromeCdpBrowserController = class {
|
|
|
572
724
|
fs.mkdirSync(profileDir, { recursive: true });
|
|
573
725
|
const lockFile = path.join(profileDir, "SingletonLock");
|
|
574
726
|
if (fs.existsSync(lockFile)) throw new Error(`Chrome profile is already in use (lock file exists: ${lockFile}). Quit the running Chrome instance first, or use --cdp-url to connect to it instead.`);
|
|
575
|
-
const launchAttempts = headless ? [{
|
|
576
|
-
withExtension: false,
|
|
577
|
-
headless: true
|
|
578
|
-
}] : [
|
|
579
|
-
{
|
|
580
|
-
withExtension: true,
|
|
581
|
-
headless: false
|
|
582
|
-
},
|
|
583
|
-
{
|
|
584
|
-
withExtension: false,
|
|
585
|
-
headless: false
|
|
586
|
-
},
|
|
587
|
-
{
|
|
588
|
-
withExtension: false,
|
|
589
|
-
headless: true
|
|
590
|
-
}
|
|
591
|
-
];
|
|
727
|
+
const launchAttempts = headless ? [{ headless: true }] : [{ headless: false }, { headless: true }];
|
|
592
728
|
let lastError;
|
|
593
729
|
for (const attempt of launchAttempts) {
|
|
594
730
|
const port = await getFreePort();
|
|
@@ -598,7 +734,6 @@ var ChromeCdpBrowserController = class {
|
|
|
598
734
|
"--no-first-run",
|
|
599
735
|
"--no-default-browser-check"
|
|
600
736
|
];
|
|
601
|
-
if (attempt.withExtension) args.push(`--disable-extensions-except=${extension.extensionPath}`, `--load-extension=${extension.extensionPath}`);
|
|
602
737
|
if (attempt.headless) args.push("--headless=new");
|
|
603
738
|
args.push("about:blank");
|
|
604
739
|
const child = spawn(executablePath, args, {
|
|
@@ -782,6 +917,42 @@ var ChromeCdpBrowserController = class {
|
|
|
782
917
|
clickCount: 1
|
|
783
918
|
});
|
|
784
919
|
}
|
|
920
|
+
async focusTarget(conn, payload) {
|
|
921
|
+
if (!payload.selector) return;
|
|
922
|
+
const expression = buildLocatorRuntimeExpression("payload", payload, `
|
|
923
|
+
const resolved = resolveWithFallbacks(payload.selector, payload.fallbackSelectors);
|
|
924
|
+
const el = resolved.element;
|
|
925
|
+
ensureInView(el);
|
|
926
|
+
if (typeof el.focus === 'function') {
|
|
927
|
+
el.focus();
|
|
928
|
+
}
|
|
929
|
+
return true;
|
|
930
|
+
`);
|
|
931
|
+
await conn.send("Runtime.evaluate", {
|
|
932
|
+
expression,
|
|
933
|
+
returnByValue: true,
|
|
934
|
+
awaitPromise: true
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
async dispatchKeyPress(conn, keyInput) {
|
|
938
|
+
const payload = buildKeyDispatchPayload(keyInput ?? "Enter");
|
|
939
|
+
await conn.send("Input.dispatchKeyEvent", {
|
|
940
|
+
type: payload.text ? "keyDown" : "rawKeyDown",
|
|
941
|
+
key: payload.key,
|
|
942
|
+
code: payload.code,
|
|
943
|
+
windowsVirtualKeyCode: payload.windowsVirtualKeyCode,
|
|
944
|
+
nativeVirtualKeyCode: payload.nativeVirtualKeyCode,
|
|
945
|
+
text: payload.text,
|
|
946
|
+
unmodifiedText: payload.unmodifiedText
|
|
947
|
+
});
|
|
948
|
+
await conn.send("Input.dispatchKeyEvent", {
|
|
949
|
+
type: "keyUp",
|
|
950
|
+
key: payload.key,
|
|
951
|
+
code: payload.code,
|
|
952
|
+
windowsVirtualKeyCode: payload.windowsVirtualKeyCode,
|
|
953
|
+
nativeVirtualKeyCode: payload.nativeVirtualKeyCode
|
|
954
|
+
});
|
|
955
|
+
}
|
|
785
956
|
async interact(targetWsUrl, payload) {
|
|
786
957
|
if (payload.action === "goBack") return await this.withRetry(targetWsUrl, async (conn) => {
|
|
787
958
|
await this.ensureEnabled(targetWsUrl);
|
|
@@ -820,7 +991,7 @@ var ChromeCdpBrowserController = class {
|
|
|
820
991
|
return "page refreshed";
|
|
821
992
|
});
|
|
822
993
|
if (payload.action === "dialog") return await this.handleDialogAction(targetWsUrl, payload);
|
|
823
|
-
if (payload.action === "click" || payload.action === "hover" || payload.action === "type") return await this.withRetry(targetWsUrl, async (conn) => {
|
|
994
|
+
if (payload.action === "click" || payload.action === "hover" || payload.action === "type" || payload.action === "press") return await this.withRetry(targetWsUrl, async (conn) => {
|
|
824
995
|
await this.ensureEnabled(targetWsUrl);
|
|
825
996
|
if (payload.action === "click") {
|
|
826
997
|
const point = await this.preparePointerTarget(conn, payload);
|
|
@@ -843,18 +1014,15 @@ var ChromeCdpBrowserController = class {
|
|
|
843
1014
|
});
|
|
844
1015
|
return "hovered";
|
|
845
1016
|
}
|
|
1017
|
+
if (payload.action === "press") {
|
|
1018
|
+
await this.focusTarget(conn, payload);
|
|
1019
|
+
await this.dispatchKeyPress(conn, payload.key);
|
|
1020
|
+
return "pressed";
|
|
1021
|
+
}
|
|
846
1022
|
if (await this.prepareTypeTarget(conn, payload) === "ready") await conn.send("Input.insertText", { text: payload.text ?? "" });
|
|
847
1023
|
return "typed";
|
|
848
1024
|
});
|
|
849
1025
|
const expression = buildLocatorRuntimeExpression("payload", payload, `
|
|
850
|
-
if (payload.action === 'press') {
|
|
851
|
-
const target = deepActiveElement();
|
|
852
|
-
if (!target) throw new Error('No active element to press key on');
|
|
853
|
-
const key = payload.key ?? 'Enter';
|
|
854
|
-
target.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true }));
|
|
855
|
-
target.dispatchEvent(new KeyboardEvent('keyup', { key, bubbles: true }));
|
|
856
|
-
return 'pressed';
|
|
857
|
-
}
|
|
858
1026
|
if (payload.action === 'waitFor') {
|
|
859
1027
|
const timeout = payload.timeoutMs ?? 2000;
|
|
860
1028
|
const started = Date.now();
|
|
@@ -877,10 +1045,10 @@ var ChromeCdpBrowserController = class {
|
|
|
877
1045
|
if (payload.action === 'scroll') {
|
|
878
1046
|
if (payload.selector) {
|
|
879
1047
|
const el = resolveWithFallbacks(payload.selector, payload.fallbackSelectors).element;
|
|
880
|
-
el.scrollBy({ left: payload.scrollX ?? 0, top: payload.scrollY ?? 0
|
|
1048
|
+
el.scrollBy({ left: payload.scrollX ?? 0, top: payload.scrollY ?? 0 });
|
|
881
1049
|
return 'scrolled element';
|
|
882
1050
|
}
|
|
883
|
-
window.scrollBy({ left: payload.scrollX ?? 0, top: payload.scrollY ?? 0
|
|
1051
|
+
window.scrollBy({ left: payload.scrollX ?? 0, top: payload.scrollY ?? 0 });
|
|
884
1052
|
return 'scrolled page';
|
|
885
1053
|
}
|
|
886
1054
|
if (payload.action === 'select') {
|
|
@@ -909,37 +1077,234 @@ var ChromeCdpBrowserController = class {
|
|
|
909
1077
|
});
|
|
910
1078
|
}
|
|
911
1079
|
async getContent(targetWsUrl, options) {
|
|
912
|
-
if (options.mode === "
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1080
|
+
if (options.mode === "summary") return await this.getSummaryContent(targetWsUrl, options);
|
|
1081
|
+
if (options.mode === "a11y") {
|
|
1082
|
+
const limited = normalizeRuntimeContentPayload(await this.getAccessibilityTree(targetWsUrl, options.selector), options.maxChars);
|
|
1083
|
+
return {
|
|
1084
|
+
mode: "a11y",
|
|
1085
|
+
content: limited.content,
|
|
1086
|
+
truncated: limited.truncated,
|
|
1087
|
+
originalLength: limited.originalLength
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
916
1090
|
const expression = buildLocatorRuntimeExpression("options", options, `
|
|
917
|
-
|
|
1091
|
+
const limit =
|
|
1092
|
+
typeof options.maxChars === 'number' && Number.isFinite(options.maxChars) && options.maxChars > 0
|
|
1093
|
+
? Math.floor(options.maxChars)
|
|
1094
|
+
: null;
|
|
1095
|
+
|
|
1096
|
+
function serializeContent(content) {
|
|
1097
|
+
const normalized = typeof content === 'string' ? content : String(content ?? '');
|
|
1098
|
+
const originalLength = normalized.length;
|
|
1099
|
+
if (limit !== null && originalLength > limit) {
|
|
1100
|
+
return { content: normalized.slice(0, limit), truncated: true, originalLength };
|
|
1101
|
+
}
|
|
1102
|
+
return { content: normalized, truncated: false, originalLength };
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
if (options.mode === 'title') return serializeContent(document.title ?? '');
|
|
918
1106
|
if (options.mode === 'html') {
|
|
919
1107
|
if (options.selector) {
|
|
920
1108
|
const el = resolveLocator(options.selector);
|
|
921
|
-
return el ? el.outerHTML : '';
|
|
1109
|
+
return serializeContent(el ? el.outerHTML : '');
|
|
922
1110
|
}
|
|
923
|
-
return document.documentElement?.outerHTML ?? '';
|
|
1111
|
+
return serializeContent(document.documentElement?.outerHTML ?? '');
|
|
924
1112
|
}
|
|
925
1113
|
if (options.selector) {
|
|
926
1114
|
const el = resolveLocator(options.selector);
|
|
927
|
-
return el ? el.innerText ?? '' : '';
|
|
1115
|
+
return serializeContent(el ? el.innerText ?? '' : '');
|
|
928
1116
|
}
|
|
929
|
-
return document.body?.innerText ?? '';
|
|
1117
|
+
return serializeContent(document.body?.innerText ?? '');
|
|
930
1118
|
`);
|
|
931
1119
|
return await this.withRetry(targetWsUrl, async (conn) => {
|
|
932
1120
|
await this.ensureEnabled(targetWsUrl);
|
|
933
|
-
const
|
|
1121
|
+
const limited = normalizeRuntimeContentPayload((await conn.send("Runtime.evaluate", {
|
|
934
1122
|
expression,
|
|
935
|
-
returnByValue: true
|
|
936
|
-
|
|
1123
|
+
returnByValue: true,
|
|
1124
|
+
awaitPromise: true
|
|
1125
|
+
})).result.value, options.maxChars);
|
|
937
1126
|
return {
|
|
938
1127
|
mode: options.mode,
|
|
939
|
-
content
|
|
1128
|
+
content: limited.content,
|
|
1129
|
+
truncated: limited.truncated,
|
|
1130
|
+
originalLength: limited.originalLength
|
|
940
1131
|
};
|
|
941
1132
|
});
|
|
942
1133
|
}
|
|
1134
|
+
summarizeInteractiveElements(result) {
|
|
1135
|
+
const primaryActions = [];
|
|
1136
|
+
const inputs = [];
|
|
1137
|
+
for (const element of result.elements) {
|
|
1138
|
+
if (primaryActions.length < 8 && (element.role === "button" || element.role === "link" || element.role === "custom")) primaryActions.push({
|
|
1139
|
+
role: element.role,
|
|
1140
|
+
text: element.text,
|
|
1141
|
+
selector: element.selector,
|
|
1142
|
+
fallbackSelectors: element.fallbackSelectors,
|
|
1143
|
+
href: element.href
|
|
1144
|
+
});
|
|
1145
|
+
if (inputs.length < 8 && (element.role === "input" || element.role === "textarea" || element.role === "select" || element.role === "checkbox" || element.role === "radio" || element.role === "contenteditable")) inputs.push({
|
|
1146
|
+
role: element.role,
|
|
1147
|
+
text: element.text,
|
|
1148
|
+
selector: element.selector,
|
|
1149
|
+
fallbackSelectors: element.fallbackSelectors,
|
|
1150
|
+
inputType: element.inputType,
|
|
1151
|
+
placeholder: element.placeholder,
|
|
1152
|
+
ariaLabel: element.ariaLabel
|
|
1153
|
+
});
|
|
1154
|
+
if (primaryActions.length >= 8 && inputs.length >= 8) break;
|
|
1155
|
+
}
|
|
1156
|
+
return {
|
|
1157
|
+
primaryActions,
|
|
1158
|
+
inputs
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
async getSummaryContent(targetWsUrl, options) {
|
|
1162
|
+
const expression = buildLocatorRuntimeExpression("options", options, `
|
|
1163
|
+
const scopeSelector = options.selector;
|
|
1164
|
+
let scopeRoot = document.body ?? document.documentElement;
|
|
1165
|
+
if (scopeSelector) {
|
|
1166
|
+
try {
|
|
1167
|
+
scopeRoot = resolveLocator(scopeSelector) ?? scopeRoot;
|
|
1168
|
+
} catch {
|
|
1169
|
+
scopeRoot = scopeRoot ?? document.documentElement;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
function escapeAttr(value) {
|
|
1174
|
+
return String(value ?? '').replace(/\\\\/g, '\\\\\\\\').replace(/"/g, '\\\\"');
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function uniqueValues(values, limit) {
|
|
1178
|
+
const seen = new Set();
|
|
1179
|
+
const results = [];
|
|
1180
|
+
for (const value of values) {
|
|
1181
|
+
const normalized = String(value ?? '').replace(/\\s+/g, ' ').trim();
|
|
1182
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
1183
|
+
seen.add(normalized);
|
|
1184
|
+
results.push(normalized.slice(0, 160));
|
|
1185
|
+
if (results.length >= limit) break;
|
|
1186
|
+
}
|
|
1187
|
+
return results;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
function textOf(el) {
|
|
1191
|
+
if (!el) return '';
|
|
1192
|
+
const ariaLabel = el.getAttribute && el.getAttribute('aria-label');
|
|
1193
|
+
if (ariaLabel) return ariaLabel;
|
|
1194
|
+
const text = el.innerText || el.textContent || '';
|
|
1195
|
+
return text.replace(/\\s+/g, ' ').trim();
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function frameSelector(frame) {
|
|
1199
|
+
if (frame.id) return '#' + CSS.escape(frame.id);
|
|
1200
|
+
const name = frame.getAttribute('name');
|
|
1201
|
+
if (name) return 'iframe[name="' + escapeAttr(name) + '"]';
|
|
1202
|
+
const title = frame.getAttribute('title');
|
|
1203
|
+
if (title) return 'iframe[title="' + escapeAttr(title) + '"]';
|
|
1204
|
+
const src = frame.getAttribute('src');
|
|
1205
|
+
if (src) return 'iframe[src="' + escapeAttr(src) + '"]';
|
|
1206
|
+
const parent = frame.parentElement;
|
|
1207
|
+
if (!parent) return 'iframe';
|
|
1208
|
+
const siblings = Array.from(parent.children).filter((candidate) => candidate.tagName === frame.tagName);
|
|
1209
|
+
if (siblings.length === 1) return 'iframe';
|
|
1210
|
+
return 'iframe:nth-of-type(' + (siblings.indexOf(frame) + 1) + ')';
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
const headingSelectors = 'h1,h2,h3,h4,h5,h6,[role="heading"]';
|
|
1214
|
+
const landmarkSelectors = [
|
|
1215
|
+
'main',
|
|
1216
|
+
'nav',
|
|
1217
|
+
'aside',
|
|
1218
|
+
'header',
|
|
1219
|
+
'footer',
|
|
1220
|
+
'[role="main"]',
|
|
1221
|
+
'[role="navigation"]',
|
|
1222
|
+
'[role="banner"]',
|
|
1223
|
+
'[role="complementary"]',
|
|
1224
|
+
'[role="contentinfo"]',
|
|
1225
|
+
'[role="search"]'
|
|
1226
|
+
].join(',');
|
|
1227
|
+
const alertSelectors = '[role="alert"], [aria-live="assertive"], dialog[open], [aria-modal="true"]';
|
|
1228
|
+
|
|
1229
|
+
const headings = uniqueValues(
|
|
1230
|
+
Array.from(scopeRoot.querySelectorAll(headingSelectors)).map((el) => textOf(el)),
|
|
1231
|
+
8
|
|
1232
|
+
);
|
|
1233
|
+
const landmarks = uniqueValues(
|
|
1234
|
+
Array.from(scopeRoot.querySelectorAll(landmarkSelectors)).map((el) => {
|
|
1235
|
+
const role = el.getAttribute('role') || el.tagName.toLowerCase();
|
|
1236
|
+
const label = textOf(el);
|
|
1237
|
+
return label ? role + ' "' + label.slice(0, 80) + '"' : role;
|
|
1238
|
+
}),
|
|
1239
|
+
8
|
|
1240
|
+
);
|
|
1241
|
+
const alerts = uniqueValues(
|
|
1242
|
+
Array.from(scopeRoot.querySelectorAll(alertSelectors)).map((el) => textOf(el)),
|
|
1243
|
+
6
|
|
1244
|
+
);
|
|
1245
|
+
|
|
1246
|
+
const allFrames = Array.from(scopeRoot.querySelectorAll('iframe'));
|
|
1247
|
+
const frames = allFrames.slice(0, 8).map((frame) => {
|
|
1248
|
+
let sameOrigin = false;
|
|
1249
|
+
try {
|
|
1250
|
+
sameOrigin = !!frame.contentDocument;
|
|
1251
|
+
} catch {
|
|
1252
|
+
sameOrigin = false;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
return {
|
|
1256
|
+
selector: frameSelector(frame),
|
|
1257
|
+
name: frame.getAttribute('name') || undefined,
|
|
1258
|
+
title: frame.getAttribute('title') || undefined,
|
|
1259
|
+
src: frame.getAttribute('src') || frame.src || undefined,
|
|
1260
|
+
sameOrigin,
|
|
1261
|
+
};
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1264
|
+
return {
|
|
1265
|
+
url: window.location.href,
|
|
1266
|
+
title: document.title ?? '',
|
|
1267
|
+
headings,
|
|
1268
|
+
landmarks,
|
|
1269
|
+
alerts,
|
|
1270
|
+
frames,
|
|
1271
|
+
crossOriginFrameCount: allFrames.filter((frame) => {
|
|
1272
|
+
try {
|
|
1273
|
+
return !frame.contentDocument;
|
|
1274
|
+
} catch {
|
|
1275
|
+
return true;
|
|
1276
|
+
}
|
|
1277
|
+
}).length,
|
|
1278
|
+
hasMoreFrames: allFrames.length > frames.length,
|
|
1279
|
+
scopedToSelector: scopeSelector || undefined,
|
|
1280
|
+
};
|
|
1281
|
+
`);
|
|
1282
|
+
const pageSummary = await this.withRetry(targetWsUrl, async (conn) => {
|
|
1283
|
+
await this.ensureEnabled(targetWsUrl);
|
|
1284
|
+
return (await conn.send("Runtime.evaluate", {
|
|
1285
|
+
expression,
|
|
1286
|
+
returnByValue: true,
|
|
1287
|
+
awaitPromise: true
|
|
1288
|
+
})).result.value ?? {};
|
|
1289
|
+
});
|
|
1290
|
+
const elements = await this.getInteractiveElements(targetWsUrl, {
|
|
1291
|
+
selector: options.selector,
|
|
1292
|
+
visibleOnly: true,
|
|
1293
|
+
limit: 24
|
|
1294
|
+
});
|
|
1295
|
+
const elementSummary = this.summarizeInteractiveElements(elements);
|
|
1296
|
+
return {
|
|
1297
|
+
mode: "summary",
|
|
1298
|
+
content: "",
|
|
1299
|
+
structuredContent: {
|
|
1300
|
+
mode: "summary",
|
|
1301
|
+
...pageSummary,
|
|
1302
|
+
...elementSummary,
|
|
1303
|
+
totalInteractiveElements: elements.totalFound,
|
|
1304
|
+
truncatedInteractiveElements: elements.truncated
|
|
1305
|
+
}
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
943
1308
|
formatAccessibilityTree(nodes) {
|
|
944
1309
|
const childrenMap = /* @__PURE__ */ new Map();
|
|
945
1310
|
const nodeMap = /* @__PURE__ */ new Map();
|
|
@@ -1616,17 +1981,29 @@ var MockBrowserController = class {
|
|
|
1616
1981
|
async getContent(cdpUrl, options) {
|
|
1617
1982
|
const page = this.pages.get(cdpUrl);
|
|
1618
1983
|
if (!page) throw new Error("mock page missing");
|
|
1619
|
-
if (options.mode === "
|
|
1620
|
-
mode: "
|
|
1621
|
-
content:
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1984
|
+
if (options.mode === "summary") return {
|
|
1985
|
+
mode: "summary",
|
|
1986
|
+
content: "",
|
|
1987
|
+
structuredContent: {
|
|
1988
|
+
mode: "summary",
|
|
1989
|
+
url: page.url,
|
|
1990
|
+
title: page.title,
|
|
1991
|
+
headings: [page.title],
|
|
1992
|
+
landmarks: [],
|
|
1993
|
+
alerts: [],
|
|
1994
|
+
frames: [],
|
|
1995
|
+
primaryActions: [],
|
|
1996
|
+
inputs: [],
|
|
1997
|
+
totalInteractiveElements: 0,
|
|
1998
|
+
truncatedInteractiveElements: false
|
|
1999
|
+
}
|
|
1626
2000
|
};
|
|
2001
|
+
const limited = applyContentLimit(options.mode === "title" ? page.title : options.mode === "html" ? page.html : page.text, options.maxChars);
|
|
1627
2002
|
return {
|
|
1628
|
-
mode:
|
|
1629
|
-
content:
|
|
2003
|
+
mode: options.mode,
|
|
2004
|
+
content: limited.content,
|
|
2005
|
+
truncated: limited.truncated || void 0,
|
|
2006
|
+
originalLength: limited.originalLength
|
|
1630
2007
|
};
|
|
1631
2008
|
}
|
|
1632
2009
|
async getInteractiveElements(_targetWsUrl, _options) {
|
|
@@ -2217,16 +2594,26 @@ const MAX_EVENTS_PER_SESSION = 500;
|
|
|
2217
2594
|
var EventStore = class {
|
|
2218
2595
|
events = /* @__PURE__ */ new Map();
|
|
2219
2596
|
filePath;
|
|
2597
|
+
bufferedLines = [];
|
|
2598
|
+
flushTimer;
|
|
2599
|
+
flushInFlight;
|
|
2220
2600
|
constructor(baseDir) {
|
|
2221
2601
|
fs.mkdirSync(baseDir, { recursive: true });
|
|
2222
2602
|
this.filePath = path.join(baseDir, "session-events.log");
|
|
2603
|
+
process.once("beforeExit", this.flushSync);
|
|
2604
|
+
process.once("exit", this.flushSync);
|
|
2223
2605
|
}
|
|
2224
2606
|
append(event) {
|
|
2225
2607
|
let existing = this.events.get(event.sessionId) ?? [];
|
|
2226
2608
|
existing.push(event);
|
|
2227
2609
|
if (existing.length > MAX_EVENTS_PER_SESSION) existing = existing.slice(-MAX_EVENTS_PER_SESSION);
|
|
2228
2610
|
this.events.set(event.sessionId, existing);
|
|
2229
|
-
|
|
2611
|
+
this.bufferedLines.push(`${JSON.stringify(event)}\n`);
|
|
2612
|
+
if (this.bufferedLines.length >= 20) {
|
|
2613
|
+
this.flushSoon(0);
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
this.flushSoon(25);
|
|
2230
2617
|
}
|
|
2231
2618
|
list(sessionId, limit = 100) {
|
|
2232
2619
|
const entries = this.events.get(sessionId) ?? [];
|
|
@@ -2235,6 +2622,36 @@ var EventStore = class {
|
|
|
2235
2622
|
clear(sessionId) {
|
|
2236
2623
|
this.events.delete(sessionId);
|
|
2237
2624
|
}
|
|
2625
|
+
flushSoon(delayMs) {
|
|
2626
|
+
if (this.flushInFlight) return this.flushInFlight;
|
|
2627
|
+
if (this.flushTimer) return Promise.resolve();
|
|
2628
|
+
this.flushTimer = setTimeout(() => {
|
|
2629
|
+
this.flushTimer = void 0;
|
|
2630
|
+
this.flushAsync();
|
|
2631
|
+
}, delayMs);
|
|
2632
|
+
this.flushTimer.unref();
|
|
2633
|
+
return Promise.resolve();
|
|
2634
|
+
}
|
|
2635
|
+
async flushAsync() {
|
|
2636
|
+
if (this.flushInFlight || this.bufferedLines.length === 0) return;
|
|
2637
|
+
const chunk = this.bufferedLines.splice(0, this.bufferedLines.length).join("");
|
|
2638
|
+
this.flushInFlight = fs.promises.appendFile(this.filePath, chunk, "utf8").catch(() => {}).finally(() => {
|
|
2639
|
+
this.flushInFlight = void 0;
|
|
2640
|
+
if (this.bufferedLines.length > 0) this.flushSoon(0);
|
|
2641
|
+
});
|
|
2642
|
+
await this.flushInFlight;
|
|
2643
|
+
}
|
|
2644
|
+
flushSync = () => {
|
|
2645
|
+
if (this.flushTimer) {
|
|
2646
|
+
clearTimeout(this.flushTimer);
|
|
2647
|
+
this.flushTimer = void 0;
|
|
2648
|
+
}
|
|
2649
|
+
if (this.bufferedLines.length === 0) return;
|
|
2650
|
+
const chunk = this.bufferedLines.splice(0, this.bufferedLines.length).join("");
|
|
2651
|
+
try {
|
|
2652
|
+
fs.appendFileSync(this.filePath, chunk, "utf8");
|
|
2653
|
+
} catch {}
|
|
2654
|
+
};
|
|
2238
2655
|
};
|
|
2239
2656
|
//#endregion
|
|
2240
2657
|
//#region src/auth/session-token.ts
|
|
@@ -2876,7 +3293,8 @@ var AgenticBrowserCore = class {
|
|
|
2876
3293
|
async getPageContent(input) {
|
|
2877
3294
|
return await this.api.getContent(input.sessionId, {
|
|
2878
3295
|
mode: input.mode,
|
|
2879
|
-
selector: input.selector
|
|
3296
|
+
selector: input.selector,
|
|
3297
|
+
maxChars: input.maxChars
|
|
2880
3298
|
});
|
|
2881
3299
|
}
|
|
2882
3300
|
async getInteractiveElements(input) {
|