@ticktockbent/charlotte 0.4.2 → 0.5.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/CHANGELOG.md +43 -0
- package/README.md +104 -18
- package/dist/browser/browser-manager.d.ts +1 -1
- package/dist/browser/browser-manager.d.ts.map +1 -1
- package/dist/browser/browser-manager.js +8 -4
- package/dist/browser/browser-manager.js.map +1 -1
- package/dist/browser/cdp-session.d.ts +9 -1
- package/dist/browser/cdp-session.d.ts.map +1 -1
- package/dist/browser/cdp-session.js +32 -10
- package/dist/browser/cdp-session.js.map +1 -1
- package/dist/browser/page-manager.d.ts +16 -0
- package/dist/browser/page-manager.d.ts.map +1 -1
- package/dist/browser/page-manager.js +97 -29
- package/dist/browser/page-manager.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +20 -10
- package/dist/cli.js.map +1 -1
- package/dist/dev/auditor.d.ts.map +1 -1
- package/dist/dev/auditor.js +15 -36
- package/dist/dev/auditor.js.map +1 -1
- package/dist/dev/dev-mode-state.d.ts.map +1 -1
- package/dist/dev/dev-mode-state.js +14 -4
- package/dist/dev/dev-mode-state.js.map +1 -1
- package/dist/dev/static-server.d.ts.map +1 -1
- package/dist/dev/static-server.js +6 -2
- package/dist/dev/static-server.js.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/renderer/accessibility-extractor.d.ts +3 -1
- package/dist/renderer/accessibility-extractor.d.ts.map +1 -1
- package/dist/renderer/accessibility-extractor.js +9 -7
- package/dist/renderer/accessibility-extractor.js.map +1 -1
- package/dist/renderer/content-extractor.d.ts.map +1 -1
- package/dist/renderer/content-extractor.js +7 -1
- package/dist/renderer/content-extractor.js.map +1 -1
- package/dist/renderer/dom-path.d.ts.map +1 -1
- package/dist/renderer/dom-path.js.map +1 -1
- package/dist/renderer/element-id-generator.d.ts +4 -1
- package/dist/renderer/element-id-generator.d.ts.map +1 -1
- package/dist/renderer/element-id-generator.js +12 -1
- package/dist/renderer/element-id-generator.js.map +1 -1
- package/dist/renderer/frame-discovery.d.ts +23 -0
- package/dist/renderer/frame-discovery.d.ts.map +1 -0
- package/dist/renderer/frame-discovery.js +107 -0
- package/dist/renderer/frame-discovery.js.map +1 -0
- package/dist/renderer/interactive-extractor.d.ts +1 -1
- package/dist/renderer/interactive-extractor.d.ts.map +1 -1
- package/dist/renderer/interactive-extractor.js +9 -9
- package/dist/renderer/interactive-extractor.js.map +1 -1
- package/dist/renderer/layout-extractor.d.ts +4 -1
- package/dist/renderer/layout-extractor.d.ts.map +1 -1
- package/dist/renderer/layout-extractor.js +19 -3
- package/dist/renderer/layout-extractor.js.map +1 -1
- package/dist/renderer/renderer-pipeline.d.ts +26 -1
- package/dist/renderer/renderer-pipeline.d.ts.map +1 -1
- package/dist/renderer/renderer-pipeline.js +174 -20
- package/dist/renderer/renderer-pipeline.js.map +1 -1
- package/dist/renderer/structural-tree-extractor.d.ts +7 -0
- package/dist/renderer/structural-tree-extractor.d.ts.map +1 -0
- package/dist/renderer/structural-tree-extractor.js +245 -0
- package/dist/renderer/structural-tree-extractor.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +3 -4
- package/dist/server.js.map +1 -1
- package/dist/state/artifact-store.d.ts.map +1 -1
- package/dist/state/artifact-store.js +1 -2
- package/dist/state/artifact-store.js.map +1 -1
- package/dist/state/differ.d.ts.map +1 -1
- package/dist/state/differ.js.map +1 -1
- package/dist/tools/dev-mode.d.ts.map +1 -1
- package/dist/tools/dev-mode.js +4 -16
- package/dist/tools/dev-mode.js.map +1 -1
- package/dist/tools/dialog.d.ts.map +1 -1
- package/dist/tools/dialog.js +2 -4
- package/dist/tools/dialog.js.map +1 -1
- package/dist/tools/evaluate.d.ts.map +1 -1
- package/dist/tools/evaluate.js +1 -4
- package/dist/tools/evaluate.js.map +1 -1
- package/dist/tools/interaction.d.ts.map +1 -1
- package/dist/tools/interaction.js +30 -57
- package/dist/tools/interaction.js.map +1 -1
- package/dist/tools/meta-tool.d.ts.map +1 -1
- package/dist/tools/meta-tool.js.map +1 -1
- package/dist/tools/monitoring.d.ts.map +1 -1
- package/dist/tools/monitoring.js +33 -15
- package/dist/tools/monitoring.js.map +1 -1
- package/dist/tools/navigation.d.ts.map +1 -1
- package/dist/tools/navigation.js +20 -16
- package/dist/tools/navigation.js.map +1 -1
- package/dist/tools/observation.d.ts.map +1 -1
- package/dist/tools/observation.js +50 -13
- package/dist/tools/observation.js.map +1 -1
- package/dist/tools/session.d.ts.map +1 -1
- package/dist/tools/session.js +35 -24
- package/dist/tools/session.js.map +1 -1
- package/dist/tools/tool-groups.d.ts.map +1 -1
- package/dist/tools/tool-groups.js +5 -21
- package/dist/tools/tool-groups.js.map +1 -1
- package/dist/tools/tool-helpers.d.ts +43 -5
- package/dist/tools/tool-helpers.d.ts.map +1 -1
- package/dist/tools/tool-helpers.js +104 -2
- package/dist/tools/tool-helpers.js.map +1 -1
- package/dist/types/config.d.ts +6 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +2 -0
- package/dist/types/config.js.map +1 -1
- package/dist/types/page-representation.d.ts +16 -0
- package/dist/types/page-representation.d.ts.map +1 -1
- package/dist/utils/wait.js +1 -1
- package/dist/utils/wait.js.map +1 -1
- package/package.json +24 -8
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,49 @@ All notable changes to Charlotte will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.1] - 2026-03-14
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Popup and target="_blank" tab capture** — Clicks on `target="_blank"` links and `window.open()` calls were silently lost because PageManager had no `popup` event handler. New tabs are now auto-captured via `page.on("popup")`, auto-cleaned when pages close themselves, and surfaced as `opened_tabs` in tool responses using single-consumption semantics. Fixes #103, #98.
|
|
12
|
+
- **Contributor issue templates** — Bug report, feature request, and tool request templates added to the repository. Community links added to README. (#102)
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Renamed AXIOM to ASM across Charlotte site (#100).
|
|
17
|
+
- Bumped hono dependency (#99).
|
|
18
|
+
|
|
19
|
+
## [0.5.0] - 2026-03-09
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **Iframe content extraction** — Child frames are now discovered and their content (interactive elements, content summaries, full text) is merged into the parent page representation. Configurable depth limit (default 3). Iframe interactive elements are included in the `interactive` array and `interactive_summary`. Closes #23.
|
|
24
|
+
- **Structural tree view** — `charlotte:observe` now accepts a `view` parameter with `"tree"` and `"tree-labeled"` modes that render the page as a hierarchical tree with indentation, replacing the flat JSON representation. Tree-labeled mode annotates interactive elements with their IDs for direct use.
|
|
25
|
+
- **File output for large responses** — `charlotte:observe` and `charlotte:screenshot` accept an `output_file` parameter to write results to disk instead of returning inline, reducing token consumption for large pages. Relative paths resolve against `output_dir` (configurable via `charlotte:configure` or `--output-dir` CLI flag). Closes GAP-13, #16.
|
|
26
|
+
- **Screenshot artifact management** — `charlotte:screenshots` (list), `charlotte:screenshot_get` (retrieve), `charlotte:screenshot_delete` (remove) tools for managing persistent screenshot files. `charlotte:screenshot` gains a `save` parameter for persistence.
|
|
27
|
+
- **Code quality tooling** — ESLint, Prettier, and coverage configuration added to the project.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **`wait_for` JS evaluation** — Replaced `new Function("return " + expr)` with CDP `Runtime.evaluate`, fixing multi-statement JS conditions that silently returned `undefined` due to ASI. Now consistent with `charlotte:evaluate`. Fixes #73.
|
|
32
|
+
- **Browser reconnection race** — `getBrowser()` now calls `ensureConnected()` to auto-recover instead of throwing immediately. `ensureConnected()` verifies browser health after awaiting concurrent launches. Fixes #83.
|
|
33
|
+
- **Renderer pipeline resilience** — Malformed AX properties no longer crash accessibility extraction (#86). Content extraction skips failed nodes instead of aborting (#79). Recursive frame traversal catches errors per-frame (#74). Batch layout extraction uses `Promise.allSettled()` for partial failure tolerance (#77).
|
|
34
|
+
- **Event listener cleanup** — `closeTab()` now explicitly removes all page event listeners before `page.close()` to prevent memory leaks across tab cycles. Fixes #89.
|
|
35
|
+
- **Dialog handler error handling** — Dialog event handler wrapped in try/catch to prevent unhandled promise rejections when dialog is already dismissed. Fixes #75.
|
|
36
|
+
- **Dev mode shutdown resilience** — `DevModeState.stopAll()` catches errors per substep so a file watcher or static server failure doesn't prevent browser cleanup. Fixes #80.
|
|
37
|
+
- **Form field matching null guard** — `resolveId()` null return no longer produces false-positive form field matches. Fixes #76.
|
|
38
|
+
- **Landmark ID collision** — Main-frame landmarks now pass explicit `"main"` frameId for consistent hash input, preventing rare cross-frame ID collisions. Fixes #82.
|
|
39
|
+
- **CLI argument parsing** — `--output-dir=`, `--profile=`, and `--tools=` flags now use `substring(indexOf("=") + 1)` instead of `split("=")[1]`, preserving paths containing `=`. Fixes #70.
|
|
40
|
+
- **Zod bounds validation** — Added `.min()`/`.max()` constraints to `quality` (1-100), viewport `width`/`height` (>=1), and key `delay` (>=0). Fixes #81.
|
|
41
|
+
- **Test cleanup** — File output integration test no longer leaks artifact store temp directory. Fixes #71.
|
|
42
|
+
- **File output security** — Path traversal prevention, mkdir-before-validation fix, and CLI `output-dir` initialization hardened. Fixes security issues from #16 review.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- README rewritten with problem-first opening, expanded MCP client setup configs (Cursor, Windsurf, VS Code, Cline, Amp), and updated tool counts.
|
|
47
|
+
- npm package description and keywords updated for discoverability.
|
|
48
|
+
- Site meta descriptions updated to lead with token-efficiency comparison.
|
|
49
|
+
|
|
7
50
|
## [0.4.2] - 2026-03-06
|
|
8
51
|
|
|
9
52
|
### Added
|
package/README.md
CHANGED
|
@@ -2,17 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
**The Web, Readable.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Your AI agent spends 60,000 tokens just to look at a web page. Charlotte does it in 336.
|
|
6
|
+
|
|
7
|
+
Charlotte is an MCP server that gives AI agents structured, token-efficient access to the web.
|
|
8
|
+
Instead of dumping the full accessibility tree on every call, Charlotte returns only what
|
|
9
|
+
the agent needs: a compact page summary on arrival, targeted queries for specific elements,
|
|
10
|
+
and full detail only when explicitly requested. The result is 25-182x less data per page
|
|
11
|
+
compared to [Playwright MCP](https://github.com/anthropics/playwright-mcp), saving thousands of dollars across production workloads.
|
|
6
12
|
|
|
7
13
|
## Why Charlotte?
|
|
8
14
|
|
|
9
15
|
Most browser MCP servers dump the entire accessibility tree on every call — a flat text blob that can exceed a million characters on content-heavy pages. Agents pay for all of it whether they need it or not.
|
|
10
16
|
|
|
11
|
-
Charlotte
|
|
17
|
+
Charlotte decomposes each page into a typed, structured representation — landmarks, headings, interactive elements, forms, content summaries — and lets agents control how much they receive with three detail levels. When an agent navigates to a new page, it gets a compact orientation (336 characters for Hacker News) instead of the full element dump (61,000+ characters). When it needs specifics, it asks for them.
|
|
12
18
|
|
|
13
19
|
### Benchmarks
|
|
14
20
|
|
|
15
|
-
Charlotte v0.
|
|
21
|
+
Charlotte v0.5.1 vs Playwright MCP, measured by characters returned per tool call on real websites:
|
|
16
22
|
|
|
17
23
|
**Navigation** (first contact with a page):
|
|
18
24
|
|
|
@@ -33,7 +39,7 @@ Charlotte's `navigate` returns minimal detail by default — landmarks, headings
|
|
|
33
39
|
| browse (default) | 23 | ~3,900 | **~47%** |
|
|
34
40
|
| core | 7 | 1,677 | **~77%** |
|
|
35
41
|
|
|
36
|
-
Tool definitions are sent on every API round-trip. With the default `browse` profile, Charlotte carries ~47% less definition overhead than loading all
|
|
42
|
+
Tool definitions are sent on every API round-trip. With the default `browse` profile, Charlotte carries ~47% less definition overhead than loading all tools. Over a 20-call browsing session, that's **~38% fewer total tokens**. See the [profile benchmark report](docs/charlotte-profile-benchmark-report.md) for full results.
|
|
37
43
|
|
|
38
44
|
**The workflow difference:** Playwright agents receive 61K+ characters every time they look at Hacker News, whether they're reading headlines or looking for a login button. Charlotte agents get 336 characters on arrival, call `find({ type: "link", text: "login" })` to get exactly what they need, and never pay for the rest.
|
|
39
45
|
|
|
@@ -63,7 +69,7 @@ Agents receive landmarks, headings, interactive elements with typed metadata, bo
|
|
|
63
69
|
|
|
64
70
|
**Navigation** — `navigate`, `back`, `forward`, `reload`
|
|
65
71
|
|
|
66
|
-
**Observation** — `observe` (3 detail levels), `find` (spatial + semantic search, CSS selector mode), `screenshot`, `diff` (structural comparison against snapshots)
|
|
72
|
+
**Observation** — `observe` (3 detail levels, structural tree view), `find` (spatial + semantic search, CSS selector mode), `screenshot` (with persistent artifact management), `screenshots`, `screenshot_get`, `screenshot_delete`, `diff` (structural comparison against snapshots)
|
|
67
73
|
|
|
68
74
|
**Interaction** — `click`, `click_at` (coordinate-based), `type`, `select`, `toggle`, `submit`, `scroll`, `hover`, `drag`, `key` (single/sequence with element targeting), `wait_for` (async condition polling), `upload` (file input), `dialog` (accept/dismiss JS dialogs)
|
|
69
75
|
|
|
@@ -77,7 +83,7 @@ Agents receive landmarks, headings, interactive elements with typed metadata, bo
|
|
|
77
83
|
|
|
78
84
|
## Tool Profiles
|
|
79
85
|
|
|
80
|
-
Charlotte ships 42 tools, but most workflows only need a subset. Startup profiles control which tools load into the agent's context, reducing definition overhead by up to 77%.
|
|
86
|
+
Charlotte ships 42 tools (41 registered + the `charlotte:tools` meta-tool), but most workflows only need a subset. Startup profiles control which tools load into the agent's context, reducing definition overhead by up to 77%.
|
|
81
87
|
|
|
82
88
|
```bash
|
|
83
89
|
charlotte --profile browse # 23 tools (default) — navigate, observe, interact, tabs
|
|
@@ -150,7 +156,9 @@ npm start
|
|
|
150
156
|
|
|
151
157
|
### MCP Client Configuration
|
|
152
158
|
|
|
153
|
-
|
|
159
|
+
#### Claude Code
|
|
160
|
+
|
|
161
|
+
Create `.mcp.json` in your project root:
|
|
154
162
|
|
|
155
163
|
```json
|
|
156
164
|
{
|
|
@@ -165,7 +173,85 @@ Add Charlotte to your MCP client configuration. For Claude Code, create `.mcp.js
|
|
|
165
173
|
}
|
|
166
174
|
```
|
|
167
175
|
|
|
168
|
-
|
|
176
|
+
#### Claude Desktop
|
|
177
|
+
|
|
178
|
+
Add to `claude_desktop_config.json`:
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"mcpServers": {
|
|
183
|
+
"charlotte": {
|
|
184
|
+
"command": "npx",
|
|
185
|
+
"args": ["@ticktockbent/charlotte"]
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### Cursor
|
|
192
|
+
|
|
193
|
+
Add to `.cursor/mcp.json`:
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"mcpServers": {
|
|
198
|
+
"charlotte": {
|
|
199
|
+
"command": "npx",
|
|
200
|
+
"args": ["@ticktockbent/charlotte"]
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
#### Windsurf
|
|
207
|
+
|
|
208
|
+
Add to `~/.codeium/windsurf/mcp_config.json`:
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"mcpServers": {
|
|
213
|
+
"charlotte": {
|
|
214
|
+
"command": "npx",
|
|
215
|
+
"args": ["@ticktockbent/charlotte"]
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### VS Code (Copilot)
|
|
222
|
+
|
|
223
|
+
Add to `.vscode/mcp.json`:
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"servers": {
|
|
228
|
+
"charlotte": {
|
|
229
|
+
"type": "stdio",
|
|
230
|
+
"command": "npx",
|
|
231
|
+
"args": ["@ticktockbent/charlotte"]
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### Cline
|
|
238
|
+
|
|
239
|
+
Add to Cline MCP settings (via the Cline sidebar > MCP Servers > Configure):
|
|
240
|
+
|
|
241
|
+
```json
|
|
242
|
+
{
|
|
243
|
+
"mcpServers": {
|
|
244
|
+
"charlotte": {
|
|
245
|
+
"command": "npx",
|
|
246
|
+
"args": ["@ticktockbent/charlotte"]
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
#### Amp
|
|
253
|
+
|
|
254
|
+
Add to `~/.amp/settings.json`:
|
|
169
255
|
|
|
170
256
|
```json
|
|
171
257
|
{
|
|
@@ -347,20 +433,18 @@ All tools go through `renderActivePage()` which handles snapshots, reload events
|
|
|
347
433
|
|
|
348
434
|
## Sandbox
|
|
349
435
|
|
|
350
|
-
Charlotte includes a test website in `tests/sandbox/` that exercises all
|
|
436
|
+
Charlotte includes a test website in `tests/sandbox/` that exercises all tools without touching the public internet. Serve it locally with:
|
|
351
437
|
|
|
352
438
|
```
|
|
353
439
|
dev_serve({ path: "tests/sandbox" })
|
|
354
440
|
```
|
|
355
441
|
|
|
356
|
-
|
|
442
|
+
Five pages cover navigation, forms, interactive elements, popups, delayed content, scroll containers, and more. See [docs/sandbox.md](docs/sandbox.md) for the full page reference and a tool-by-tool exercise checklist.
|
|
357
443
|
|
|
358
444
|
## Known Issues
|
|
359
445
|
|
|
360
446
|
**Tool naming convention** — Charlotte uses `:` as a namespace separator in tool names (e.g., `charlotte:navigate`, `charlotte:observe`). MCP SDK v1.26.0+ logs validation warnings for this character, as the emerging [SEP standard](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/986) restricts tool names to `[A-Za-z0-9_.-]`. This does not affect functionality — all tools work correctly — but produces stderr warnings on server startup. Will be addressed in a future release to comply with the SEP standard.
|
|
361
447
|
|
|
362
|
-
**Iframe content not captured** — Charlotte reads the main frame's accessibility tree only. Content inside iframes (same-origin or cross-origin) is not included in the page representation. See the Roadmap for planned iframe support.
|
|
363
|
-
|
|
364
448
|
**Shadow DOM** — Open shadow DOM works transparently. Chromium's accessibility tree pierces open shadow boundaries, so web components (e.g., GitHub's `<relative-time>`, `<tool-tip>`) render their content into Charlotte's representation without special handling. Closed shadow roots are opaque to the accessibility tree and will not be captured.
|
|
365
449
|
|
|
366
450
|
## Roadmap
|
|
@@ -379,20 +463,14 @@ Four pages cover navigation, forms, interactive elements, delayed content, scrol
|
|
|
379
463
|
|
|
380
464
|
**Configuration File** — Support a `--config` CLI argument to load settings from a JSON file, simplifying repeatable setups and CI/CD integration.
|
|
381
465
|
|
|
382
|
-
**File Output** — Add an optional `filename` parameter to `screenshot`, `observe`, and future monitoring tools so large responses can be written to disk instead of returned inline, reducing token consumption.
|
|
383
|
-
|
|
384
466
|
**Full Device Emulation** — Extend `charlotte:viewport` to accept named devices (e.g., "iPhone 15") and configure user agent, touch support, and device pixel ratio via CDP, not just viewport dimensions.
|
|
385
467
|
|
|
386
468
|
### Feature Roadmap
|
|
387
469
|
|
|
388
|
-
**Screenshot Artifacts** — Save screenshots as persistent file artifacts rather than only returning inline data, enabling agents to reference and manage captured images across sessions.
|
|
389
|
-
|
|
390
470
|
**Video Recording** — Record interactions as video, capturing the full sequence of agent-driven navigation and manipulation for debugging, documentation, and review.
|
|
391
471
|
|
|
392
472
|
**ARM64 Docker Images** — Add `linux/arm64` platform support to the Docker publish workflow for native performance on Apple Silicon Macs and ARM servers.
|
|
393
473
|
|
|
394
|
-
**Iframe Content Extraction** — Traverse child frames via CDP to include iframe content in the page representation. Currently, Charlotte only reads the main frame's accessibility tree; same-origin and cross-origin iframe content is invisible.
|
|
395
|
-
|
|
396
474
|
See [docs/playwright-mcp-gap-analysis.md](docs/playwright-mcp-gap-analysis.md) for the full gap analysis against Playwright MCP, including lower-priority items (vision tools, testing/verification, tracing, transport, security) and areas where Charlotte has advantages.
|
|
397
475
|
|
|
398
476
|
## Full Specification
|
|
@@ -403,6 +481,14 @@ See [docs/CHARLOTTE_SPEC.md](docs/CHARLOTTE_SPEC.md) for the complete specificat
|
|
|
403
481
|
|
|
404
482
|
[MIT](LICENSE)
|
|
405
483
|
|
|
484
|
+
## Community
|
|
485
|
+
|
|
486
|
+
- Open a [bug report](https://github.com/TickTockBent/charlotte/issues/new?template=bug_report.md) for reproducible defects, regressions, or MCP-client-specific problems.
|
|
487
|
+
- Open a [feature request](https://github.com/TickTockBent/charlotte/issues/new?template=feature_request.md) for workflow improvements or new capabilities.
|
|
488
|
+
- Open a [tool request](https://github.com/TickTockBent/charlotte/issues/new?template=tool_request.md) if you want to propose a new tool, parameter surface, or profile placement.
|
|
489
|
+
- Browse [open issues](https://github.com/TickTockBent/charlotte/issues) to find current work and discussion.
|
|
490
|
+
- Check the planned [good first issue filter](https://github.com/TickTockBent/charlotte/issues?q=is%3Aopen+label%3A%22good+first+issue%22) as maintainers tag starter-friendly tasks.
|
|
491
|
+
|
|
406
492
|
## Contributing
|
|
407
493
|
|
|
408
494
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-manager.d.ts","sourceRoot":"","sources":["../../src/browser/browser-manager.ts"],"names":[],"mappings":"AAAA,OAAkB,EAAE,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAInF,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,SAAS,CAA8B;IAEzC,MAAM,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;YAetC,QAAQ;IAchB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"browser-manager.d.ts","sourceRoot":"","sources":["../../src/browser/browser-manager.ts"],"names":[],"mappings":"AAAA,OAAkB,EAAE,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAInF,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,SAAS,CAA8B;IAEzC,MAAM,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;YAetC,QAAQ;IAchB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IA2BhC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQtB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAW9B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAK9B,WAAW,IAAI,OAAO;CAGvB"}
|
|
@@ -36,6 +36,10 @@ export class BrowserManager {
|
|
|
36
36
|
// Prevent concurrent relaunch attempts
|
|
37
37
|
if (this.launching) {
|
|
38
38
|
await this.launching;
|
|
39
|
+
// Verify the concurrent launch actually succeeded
|
|
40
|
+
if (!this.browser || !this.browser.connected) {
|
|
41
|
+
throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, "Browser launch failed during concurrent reconnection attempt.");
|
|
42
|
+
}
|
|
39
43
|
return;
|
|
40
44
|
}
|
|
41
45
|
logger.info("Browser not connected, relaunching");
|
|
@@ -54,15 +58,15 @@ export class BrowserManager {
|
|
|
54
58
|
this.browser = null;
|
|
55
59
|
}
|
|
56
60
|
}
|
|
57
|
-
getBrowser() {
|
|
61
|
+
async getBrowser() {
|
|
62
|
+
await this.ensureConnected();
|
|
58
63
|
if (!this.browser || !this.browser.connected) {
|
|
59
|
-
throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, "Browser is not connected
|
|
64
|
+
throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, "Browser is not connected after ensureConnected().");
|
|
60
65
|
}
|
|
61
66
|
return this.browser;
|
|
62
67
|
}
|
|
63
68
|
async newPage() {
|
|
64
|
-
await this.
|
|
65
|
-
const browser = this.getBrowser();
|
|
69
|
+
const browser = await this.getBrowser();
|
|
66
70
|
return browser.newPage();
|
|
67
71
|
}
|
|
68
72
|
isConnected() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-manager.js","sourceRoot":"","sources":["../../src/browser/browser-manager.ts"],"names":[],"mappings":"AAAA,OAAO,SAA0D,MAAM,WAAW,CAAC;AACnF,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExE,MAAM,OAAO,cAAc;IACjB,OAAO,GAAmB,IAAI,CAAC;IAC/B,aAAa,GAAkB,EAAE,CAAC;IAClC,SAAS,GAAyB,IAAI,CAAC;IAE/C,KAAK,CAAC,MAAM,CAAC,OAAuB;QAClC,IAAI,CAAC,aAAa,GAAG;YACnB,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE;gBACJ,cAAc;gBACd,0BAA0B;gBAC1B,eAAe;gBACf,yBAAyB;aAC1B;YACD,GAAG,OAAO;SACX,CAAC;QAEF,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;IAEO,KAAK,CAAC,QAAQ;QACpB,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAClC,IAAI,CAAC,OAAO,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAE1D,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,cAAc,EAAE,GAAG,EAAE;YACnC,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;YAClD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE;YAC/B,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,GAAG;SACjC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,uCAAuC;QACvC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,MAAM,IAAI,CAAC,SAAS,CAAC;YACrB,OAAO;QACT,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;QAClD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QACjC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,SAAS,CAAC;QACvB,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACxB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;YAChC,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAC3B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,CAAC;IACH,CAAC;IAED,UAAU;
|
|
1
|
+
{"version":3,"file":"browser-manager.js","sourceRoot":"","sources":["../../src/browser/browser-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import type { Page, CDPSession } from "puppeteer";
|
|
1
|
+
import type { Page, CDPSession, Frame } from "puppeteer";
|
|
2
2
|
export declare class CDPSessionManager {
|
|
3
3
|
private sessions;
|
|
4
|
+
private frameSessions;
|
|
4
5
|
getSession(page: Page): Promise<CDPSession>;
|
|
6
|
+
/**
|
|
7
|
+
* Get or create a CDP session for a child frame.
|
|
8
|
+
* Uses the frame's own client for out-of-process (cross-origin) frames.
|
|
9
|
+
*/
|
|
10
|
+
getFrameSession(frame: Frame): Promise<CDPSession>;
|
|
11
|
+
/** Extract the CDP frame ID from a Puppeteer Frame. */
|
|
12
|
+
getFrameId(frame: Frame): string;
|
|
5
13
|
private enableDomains;
|
|
6
14
|
}
|
|
7
15
|
//# sourceMappingURL=cdp-session.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdp-session.d.ts","sourceRoot":"","sources":["../../src/browser/cdp-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"cdp-session.d.ts","sourceRoot":"","sources":["../../src/browser/cdp-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAQzD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAA4C;IAC5D,OAAO,CAAC,aAAa,CAAiC;IAEhD,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC;IAajD;;;OAGG;IACG,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC;IAiBxD,uDAAuD;IACvD,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM;YAMlB,aAAa;CAc5B"}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { logger } from "../utils/logger.js";
|
|
2
|
-
const REQUIRED_DOMAINS = [
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
"CSS",
|
|
6
|
-
"Page",
|
|
7
|
-
"Network",
|
|
8
|
-
];
|
|
2
|
+
const REQUIRED_DOMAINS = ["Accessibility", "DOM", "CSS", "Page", "Network"];
|
|
3
|
+
/** Domains needed for iframe frame sessions (subset — no Page/Network). */
|
|
4
|
+
const FRAME_DOMAINS = ["Accessibility", "DOM", "CSS"];
|
|
9
5
|
export class CDPSessionManager {
|
|
10
6
|
sessions = new WeakMap();
|
|
7
|
+
frameSessions = new Map();
|
|
11
8
|
async getSession(page) {
|
|
12
9
|
const existing = this.sessions.get(page);
|
|
13
10
|
if (existing) {
|
|
@@ -15,12 +12,37 @@ export class CDPSessionManager {
|
|
|
15
12
|
}
|
|
16
13
|
logger.debug("Creating new CDP session");
|
|
17
14
|
const session = await page.createCDPSession();
|
|
18
|
-
await this.enableDomains(session);
|
|
15
|
+
await this.enableDomains(session, REQUIRED_DOMAINS);
|
|
19
16
|
this.sessions.set(page, session);
|
|
20
17
|
return session;
|
|
21
18
|
}
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Get or create a CDP session for a child frame.
|
|
21
|
+
* Uses the frame's own client for out-of-process (cross-origin) frames.
|
|
22
|
+
*/
|
|
23
|
+
async getFrameSession(frame) {
|
|
24
|
+
const frameId = this.getFrameId(frame);
|
|
25
|
+
const existing = this.frameSessions.get(frameId);
|
|
26
|
+
if (existing) {
|
|
27
|
+
return existing;
|
|
28
|
+
}
|
|
29
|
+
logger.debug("Creating CDP session for frame", { frameId, url: frame.url() });
|
|
30
|
+
// CdpFrame exposes .client which is the CDP session for that frame's target.
|
|
31
|
+
// This is a Puppeteer internal (tested with puppeteer 24.x). If Puppeteer
|
|
32
|
+
// changes the internal API, this will need updating.
|
|
33
|
+
const session = frame.client;
|
|
34
|
+
await this.enableDomains(session, FRAME_DOMAINS);
|
|
35
|
+
this.frameSessions.set(frameId, session);
|
|
36
|
+
return session;
|
|
37
|
+
}
|
|
38
|
+
/** Extract the CDP frame ID from a Puppeteer Frame. */
|
|
39
|
+
getFrameId(frame) {
|
|
40
|
+
// Puppeteer Frame exposes _id as the CDP frame ID.
|
|
41
|
+
// This is a Puppeteer internal (tested with puppeteer 24.x).
|
|
42
|
+
return frame._id;
|
|
43
|
+
}
|
|
44
|
+
async enableDomains(session, domains) {
|
|
45
|
+
for (const domain of domains) {
|
|
24
46
|
try {
|
|
25
47
|
await session.send(`${domain}.enable`);
|
|
26
48
|
logger.debug(`Enabled CDP domain: ${domain}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdp-session.js","sourceRoot":"","sources":["../../src/browser/cdp-session.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,MAAM,gBAAgB,GAAG
|
|
1
|
+
{"version":3,"file":"cdp-session.js","sourceRoot":"","sources":["../../src/browser/cdp-session.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,MAAM,gBAAgB,GAAG,CAAC,eAAe,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAU,CAAC;AAErF,2EAA2E;AAC3E,MAAM,aAAa,GAAG,CAAC,eAAe,EAAE,KAAK,EAAE,KAAK,CAAU,CAAC;AAE/D,MAAM,OAAO,iBAAiB;IACpB,QAAQ,GAA8B,IAAI,OAAO,EAAE,CAAC;IACpD,aAAa,GAAG,IAAI,GAAG,EAAsB,CAAC;IAEtD,KAAK,CAAC,UAAU,CAAC,IAAU;QACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC9C,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;QACpD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACjC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,eAAe,CAAC,KAAY;QAChC,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACjD,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,MAAM,CAAC,KAAK,CAAC,gCAAgC,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC9E,6EAA6E;QAC7E,0EAA0E;QAC1E,qDAAqD;QACrD,MAAM,OAAO,GAAI,KAAa,CAAC,MAAoB,CAAC;QACpD,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QACjD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACzC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,uDAAuD;IACvD,UAAU,CAAC,KAAY;QACrB,mDAAmD;QACnD,6DAA6D;QAC7D,OAAQ,KAAa,CAAC,GAAa,CAAC;IACtC,CAAC;IAEO,KAAK,CAAC,aAAa,CACzB,OAAmB,EACnB,OAA0B;QAE1B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,SAAgB,CAAC,CAAC;gBAC9C,MAAM,CAAC,KAAK,CAAC,uBAAuB,MAAM,EAAE,CAAC,CAAC;YAChD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,8CAA8C;gBAC9C,MAAM,CAAC,KAAK,CAAC,gCAAgC,MAAM,EAAE,EAAE,KAAK,CAAC,CAAC;YAChE,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
|
@@ -25,7 +25,23 @@ export declare class PageManager {
|
|
|
25
25
|
private pages;
|
|
26
26
|
private activeTabId;
|
|
27
27
|
private config;
|
|
28
|
+
/** Tab IDs of pages opened by popups since the last drain. */
|
|
29
|
+
private newTabQueue;
|
|
28
30
|
constructor(config?: CharlotteConfig);
|
|
31
|
+
/**
|
|
32
|
+
* Wire up event listeners on a managed page: console, network, dialog,
|
|
33
|
+
* framenavigated, popup, and close. Shared by openTab() and the popup handler.
|
|
34
|
+
*/
|
|
35
|
+
private wirePageListeners;
|
|
36
|
+
/**
|
|
37
|
+
* Register a popup page as a managed tab. Called by the popup event handler.
|
|
38
|
+
*/
|
|
39
|
+
private registerPopupPage;
|
|
40
|
+
/**
|
|
41
|
+
* Drain the new-tab queue. Returns tab IDs of pages opened by popups since
|
|
42
|
+
* the last call, then clears the queue (single-consumption semantics).
|
|
43
|
+
*/
|
|
44
|
+
consumeNewTabs(): string[];
|
|
29
45
|
openTab(browserManager: BrowserManager, url?: string): Promise<string>;
|
|
30
46
|
switchTab(tabId: string): Promise<Page>;
|
|
31
47
|
closeTab(tabId: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-manager.d.ts","sourceRoot":"","sources":["../../src/browser/page-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAErE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAI1D,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAoBD,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,MAAM,CAAkB;
|
|
1
|
+
{"version":3,"file":"page-manager.d.ts","sourceRoot":"","sources":["../../src/browser/page-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAErE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAI1D,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAoBD,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,MAAM,CAAkB;IAChC,8DAA8D;IAC9D,OAAO,CAAC,WAAW,CAAgB;gBAEvB,MAAM,CAAC,EAAE,eAAe;IAKpC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IA6FzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAkBzB;;;OAGG;IACH,cAAc,IAAI,MAAM,EAAE;IAOpB,OAAO,CAAC,cAAc,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA0BtE,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBtC,QAAQ,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAapC,aAAa,IAAI,IAAI;IAmBrB,cAAc,IAAI,MAAM;IAOxB,+EAA+E;IAC/E,gBAAgB,IAAI,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAS1D,uFAAuF;IACvF,gBAAgB,IAAI,KAAK,CAAC;QACxB,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IASF,iEAAiE;IACjE,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE;IAUpD,wDAAwD;IACxD,kBAAkB,IAAI,cAAc,EAAE;IAOtC,oBAAoB,IAAI,IAAI;IAQ5B,oBAAoB,IAAI,IAAI;IAQ5B,WAAW,IAAI,IAAI;IASnB,oBAAoB,IAAI,aAAa,GAAG,IAAI;IAM5C,gBAAgB,IAAI,MAAM,GAAG,IAAI;IAMjC,kBAAkB,IAAI,IAAI;IAS1B,QAAQ,IAAI,OAAO;CAGpB"}
|
|
@@ -11,21 +11,18 @@ export class PageManager {
|
|
|
11
11
|
pages = new Map();
|
|
12
12
|
activeTabId = null;
|
|
13
13
|
config;
|
|
14
|
+
/** Tab IDs of pages opened by popups since the last drain. */
|
|
15
|
+
newTabQueue = [];
|
|
14
16
|
constructor(config) {
|
|
15
17
|
// Accept optional config; callers without config get a permissive default
|
|
16
18
|
this.config = config ?? createDefaultConfig();
|
|
17
19
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
consoleMessages: [],
|
|
25
|
-
networkRequests: [],
|
|
26
|
-
pendingDialog: null,
|
|
27
|
-
pendingDialogInfo: null,
|
|
28
|
-
};
|
|
20
|
+
/**
|
|
21
|
+
* Wire up event listeners on a managed page: console, network, dialog,
|
|
22
|
+
* framenavigated, popup, and close. Shared by openTab() and the popup handler.
|
|
23
|
+
*/
|
|
24
|
+
wirePageListeners(managedPage) {
|
|
25
|
+
const { page, id: tabId } = managedPage;
|
|
29
26
|
// Collect all console messages
|
|
30
27
|
page.on("console", (msg) => {
|
|
31
28
|
if (managedPage.consoleMessages.length >= MAX_CONSOLE_MESSAGES) {
|
|
@@ -53,26 +50,32 @@ export class PageManager {
|
|
|
53
50
|
});
|
|
54
51
|
// Handle JavaScript dialogs (alert, confirm, prompt, beforeunload)
|
|
55
52
|
page.on("dialog", async (dialog) => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
53
|
+
try {
|
|
54
|
+
const dialogType = dialog.type();
|
|
55
|
+
const autoDismiss = this.config.dialogAutoDismiss;
|
|
56
|
+
logger.info("Dialog appeared", { tabId, type: dialogType, message: dialog.message() });
|
|
57
|
+
// Auto-dismiss logic
|
|
58
|
+
if (autoDismiss === "accept_all" ||
|
|
59
|
+
(autoDismiss === "accept_alerts" && dialogType === "alert")) {
|
|
60
|
+
await dialog.accept();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (autoDismiss === "dismiss_all") {
|
|
64
|
+
await dialog.dismiss();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Queue for manual handling
|
|
68
|
+
managedPage.pendingDialog = dialog;
|
|
69
|
+
managedPage.pendingDialogInfo = {
|
|
70
|
+
type: dialogType,
|
|
71
|
+
message: dialog.message(),
|
|
72
|
+
...(dialogType === "prompt" ? { default_value: dialog.defaultValue() } : {}),
|
|
73
|
+
timestamp: new Date().toISOString(),
|
|
74
|
+
};
|
|
63
75
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return;
|
|
76
|
+
catch (error) {
|
|
77
|
+
logger.warn("Dialog handler failed", { tabId, error });
|
|
67
78
|
}
|
|
68
|
-
// Queue for manual handling
|
|
69
|
-
managedPage.pendingDialog = dialog;
|
|
70
|
-
managedPage.pendingDialogInfo = {
|
|
71
|
-
type: dialogType,
|
|
72
|
-
message: dialog.message(),
|
|
73
|
-
...(dialogType === "prompt" ? { default_value: dialog.defaultValue() } : {}),
|
|
74
|
-
timestamp: new Date().toISOString(),
|
|
75
|
-
};
|
|
76
79
|
});
|
|
77
80
|
// Clear stale dialog references on main-frame navigation only.
|
|
78
81
|
// Subframe navigations (iframes, ads, embeds) must not wipe dialog state.
|
|
@@ -82,6 +85,65 @@ export class PageManager {
|
|
|
82
85
|
managedPage.pendingDialogInfo = null;
|
|
83
86
|
}
|
|
84
87
|
});
|
|
88
|
+
// Capture popups (target="_blank" links, window.open()) as managed tabs
|
|
89
|
+
page.on("popup", (popupPage) => {
|
|
90
|
+
if (popupPage) {
|
|
91
|
+
this.registerPopupPage(popupPage);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
// Auto-clean when a page closes itself (window.close(), site-initiated)
|
|
95
|
+
page.on("close", () => {
|
|
96
|
+
if (this.pages.has(tabId)) {
|
|
97
|
+
this.pages.delete(tabId);
|
|
98
|
+
logger.info(`Tab ${tabId} closed by page`);
|
|
99
|
+
if (this.activeTabId === tabId) {
|
|
100
|
+
const remaining = this.pages.keys().next();
|
|
101
|
+
this.activeTabId = remaining.done ? null : remaining.value;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Register a popup page as a managed tab. Called by the popup event handler.
|
|
108
|
+
*/
|
|
109
|
+
registerPopupPage(popupPage) {
|
|
110
|
+
const popupTabId = generateTabId();
|
|
111
|
+
const managedPopup = {
|
|
112
|
+
id: popupTabId,
|
|
113
|
+
page: popupPage,
|
|
114
|
+
consoleMessages: [],
|
|
115
|
+
networkRequests: [],
|
|
116
|
+
pendingDialog: null,
|
|
117
|
+
pendingDialogInfo: null,
|
|
118
|
+
};
|
|
119
|
+
this.wirePageListeners(managedPopup);
|
|
120
|
+
this.pages.set(popupTabId, managedPopup);
|
|
121
|
+
this.newTabQueue.push(popupTabId);
|
|
122
|
+
logger.info(`Captured popup as ${popupTabId}`, { url: popupPage.url() });
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Drain the new-tab queue. Returns tab IDs of pages opened by popups since
|
|
126
|
+
* the last call, then clears the queue (single-consumption semantics).
|
|
127
|
+
*/
|
|
128
|
+
consumeNewTabs() {
|
|
129
|
+
if (this.newTabQueue.length === 0)
|
|
130
|
+
return [];
|
|
131
|
+
const tabs = [...this.newTabQueue];
|
|
132
|
+
this.newTabQueue = [];
|
|
133
|
+
return tabs;
|
|
134
|
+
}
|
|
135
|
+
async openTab(browserManager, url) {
|
|
136
|
+
const page = await browserManager.newPage();
|
|
137
|
+
const tabId = generateTabId();
|
|
138
|
+
const managedPage = {
|
|
139
|
+
id: tabId,
|
|
140
|
+
page,
|
|
141
|
+
consoleMessages: [],
|
|
142
|
+
networkRequests: [],
|
|
143
|
+
pendingDialog: null,
|
|
144
|
+
pendingDialogInfo: null,
|
|
145
|
+
};
|
|
146
|
+
this.wirePageListeners(managedPage);
|
|
85
147
|
this.pages.set(tabId, managedPage);
|
|
86
148
|
this.activeTabId = tabId;
|
|
87
149
|
if (url) {
|
|
@@ -104,6 +166,12 @@ export class PageManager {
|
|
|
104
166
|
if (!managedPage) {
|
|
105
167
|
throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, `Tab '${tabId}' not found`);
|
|
106
168
|
}
|
|
169
|
+
managedPage.page.removeAllListeners("console");
|
|
170
|
+
managedPage.page.removeAllListeners("response");
|
|
171
|
+
managedPage.page.removeAllListeners("dialog");
|
|
172
|
+
managedPage.page.removeAllListeners("framenavigated");
|
|
173
|
+
managedPage.page.removeAllListeners("popup");
|
|
174
|
+
managedPage.page.removeAllListeners("close");
|
|
107
175
|
await managedPage.page.close();
|
|
108
176
|
this.pages.delete(tabId);
|
|
109
177
|
if (this.activeTabId === tabId) {
|