@reclaimprotocol/agent 0.1.23 → 0.1.24

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.
Files changed (152) hide show
  1. package/README.md +55 -20
  2. package/lib/authoring/backend.d.ts +5 -4
  3. package/lib/authoring/tools.js +30 -30
  4. package/lib/cdp/browser-proxy.d.ts +78 -0
  5. package/lib/cdp/browser-proxy.js +186 -0
  6. package/lib/cdp/capture-engine.js +8 -7
  7. package/lib/cdp/client.d.ts +10 -0
  8. package/lib/cdp/geo.d.ts +10 -0
  9. package/lib/cdp/geo.js +36 -0
  10. package/lib/cdp/index.d.ts +1 -0
  11. package/lib/cdp/index.js +1 -0
  12. package/lib/live/cloudflared.d.ts +15 -0
  13. package/lib/live/cloudflared.js +268 -0
  14. package/lib/live/page.d.ts +20 -0
  15. package/lib/live/page.js +134 -0
  16. package/lib/live/tunnel.d.ts +23 -0
  17. package/lib/live/tunnel.js +120 -0
  18. package/lib/live/viewer.d.ts +28 -0
  19. package/lib/live/viewer.js +232 -0
  20. package/lib/mcp/api/operations.gen.js +34 -26
  21. package/lib/mcp/api/to-zod.d.ts +1 -1
  22. package/lib/mcp/api/to-zod.js +1 -1
  23. package/lib/mcp/api/tools.js +1 -1
  24. package/lib/mcp/instructions.d.ts +15 -2
  25. package/lib/mcp/instructions.js +101 -38
  26. package/lib/mcp/start.js +4 -3
  27. package/lib/mcp/tools/agent/analyze.js +5 -1
  28. package/lib/mcp/tools/agent/attach.d.ts +8 -1
  29. package/lib/mcp/tools/agent/attach.js +166 -22
  30. package/lib/mcp/tools/agent/build.d.ts +13 -8
  31. package/lib/mcp/tools/agent/build.js +22 -11
  32. package/lib/mcp/tools/agent/capture.js +5 -1
  33. package/lib/mcp/tools/agent/eval.js +3 -1
  34. package/lib/mcp/tools/agent/find.js +6 -5
  35. package/lib/mcp/tools/agent/how-it-works.d.ts +15 -7
  36. package/lib/mcp/tools/agent/how-it-works.js +57 -17
  37. package/lib/mcp/tools/agent/live-view.d.ts +21 -0
  38. package/lib/mcp/tools/agent/live-view.js +153 -0
  39. package/lib/mcp/tools/agent/pod-proof.d.ts +8 -0
  40. package/lib/mcp/tools/agent/pod-proof.js +64 -0
  41. package/lib/mcp/tools/agent/propose.js +4 -2
  42. package/lib/mcp/tools/agent/proxy.d.ts +3 -0
  43. package/lib/mcp/tools/agent/proxy.js +199 -0
  44. package/lib/mcp/tools/agent/publish.js +47 -77
  45. package/lib/mcp/tools/agent/session.d.ts +2 -1
  46. package/lib/mcp/tools/agent/session.js +26 -7
  47. package/lib/mcp/tools/authenticate/authenticate.js +26 -13
  48. package/lib/mcp/tools/mode/mode.d.ts +1 -1
  49. package/lib/mcp/tools/mode/mode.js +10 -22
  50. package/lib/old/client.d.ts +1 -1
  51. package/lib/old/client.js +1 -1
  52. package/lib/old/instructions.d.ts +9 -1
  53. package/lib/old/instructions.js +4 -6
  54. package/lib/old/login-cdp.d.ts +2 -2
  55. package/lib/old/login-cdp.js +46 -6
  56. package/lib/old/to-register.d.ts +3 -2
  57. package/lib/old/to-register.js +1 -1
  58. package/lib/old/tools/authenticate.js +9 -1
  59. package/lib/old/tools/get-provider-info.js +6 -5
  60. package/lib/old/tools/issue-app-credentials.js +3 -2
  61. package/lib/old/tools/publish.d.ts +1 -1
  62. package/lib/old/tools/publish.js +90 -131
  63. package/lib/old/tools/update-provider-metadata.d.ts +1 -1
  64. package/lib/old/tools/update-provider-metadata.js +1 -1
  65. package/lib/old/tools/verify-sdk-setup.js +1 -1
  66. package/lib/proof/attestor.d.ts +3 -3
  67. package/lib/proof/debug-response-dump.js +4 -4
  68. package/lib/proof/index.d.ts +1 -0
  69. package/lib/proof/index.js +1 -0
  70. package/lib/proof/page-replay.d.ts +1 -1
  71. package/lib/proof/page-replay.js +1 -1
  72. package/lib/proof/pod-prover.d.ts +47 -0
  73. package/lib/proof/pod-prover.js +155 -0
  74. package/lib/provider/dom-xpath.js +11 -9
  75. package/lib/provider/draft.d.ts +10 -7
  76. package/lib/provider/draft.js +11 -9
  77. package/lib/provider/injection-doc.d.ts +23 -1
  78. package/lib/provider/injection-doc.js +34 -21
  79. package/lib/provider/match-redaction-warning.d.ts +2 -2
  80. package/lib/provider/match-redaction-warning.js +3 -3
  81. package/lib/provider/matcher.js +13 -12
  82. package/lib/provider/secrets.d.ts +3 -3
  83. package/lib/provider/secrets.js +3 -3
  84. package/lib/provider/to-version.d.ts +7 -6
  85. package/lib/provider/to-version.js +3 -3
  86. package/lib/tools/registry.d.ts +5 -5
  87. package/lib/tools/registry.js +5 -5
  88. package/package.json +3 -3
  89. package/samples/demo-server/README.md +7 -6
  90. package/skill/SKILL.builder.md +356 -189
  91. package/skill/SKILL.common.md +323 -97
  92. package/skill/SKILL.old.md +340 -155
  93. package/skill/builder-mode.md +58 -17
  94. package/skill/hash-validation.md +170 -0
  95. package/skill/interception.md +235 -0
  96. package/skill/js-sdk-integration.md +13 -7
  97. package/skill/old-mode.md +60 -21
  98. package/skill/user-script.md +61 -0
  99. package/src/authoring/backend.ts +5 -4
  100. package/src/authoring/tools.ts +31 -30
  101. package/src/cdp/capture-engine.ts +8 -7
  102. package/src/cdp/client.ts +8 -0
  103. package/src/cdp/geo.ts +52 -0
  104. package/src/cdp/index.ts +1 -0
  105. package/src/consts.ts +1 -0
  106. package/src/live/cloudflared.ts +348 -0
  107. package/src/live/page.ts +145 -0
  108. package/src/live/tunnel.ts +157 -0
  109. package/src/live/viewer.ts +293 -0
  110. package/src/mcp/api/operations.gen.ts +34 -26
  111. package/src/mcp/api/to-zod.ts +1 -1
  112. package/src/mcp/api/tools.ts +1 -1
  113. package/src/mcp/instructions.ts +121 -48
  114. package/src/mcp/start.ts +4 -3
  115. package/src/mcp/tools/agent/analyze.ts +5 -1
  116. package/src/mcp/tools/agent/attach.ts +194 -22
  117. package/src/mcp/tools/agent/build.ts +26 -15
  118. package/src/mcp/tools/agent/capture.ts +5 -1
  119. package/src/mcp/tools/agent/eval.ts +4 -1
  120. package/src/mcp/tools/agent/find.ts +6 -5
  121. package/src/mcp/tools/agent/how-it-works.ts +62 -21
  122. package/src/mcp/tools/agent/live-view.ts +198 -0
  123. package/src/mcp/tools/agent/pod-proof.ts +90 -0
  124. package/src/mcp/tools/agent/propose.ts +4 -2
  125. package/src/mcp/tools/agent/publish.ts +52 -77
  126. package/src/mcp/tools/agent/session.ts +26 -5
  127. package/src/mcp/tools/authenticate/authenticate.ts +28 -13
  128. package/src/mcp/tools/mode/mode.ts +10 -22
  129. package/src/old/client.ts +2 -2
  130. package/src/old/instructions.ts +4 -10
  131. package/src/old/login-cdp.ts +50 -6
  132. package/src/old/to-register.ts +3 -2
  133. package/src/old/tools/authenticate.ts +9 -1
  134. package/src/old/tools/get-provider-info.ts +6 -5
  135. package/src/old/tools/issue-app-credentials.ts +3 -2
  136. package/src/old/tools/publish.ts +95 -131
  137. package/src/old/tools/update-provider-metadata.ts +1 -1
  138. package/src/old/tools/verify-sdk-setup.ts +1 -1
  139. package/src/proof/attestor.ts +3 -3
  140. package/src/proof/debug-response-dump.ts +4 -4
  141. package/src/proof/index.ts +8 -0
  142. package/src/proof/page-replay.ts +1 -1
  143. package/src/proof/pod-prover.ts +231 -0
  144. package/src/provider/dom-xpath.ts +11 -9
  145. package/src/provider/draft.ts +13 -10
  146. package/src/provider/injection-doc.ts +37 -24
  147. package/src/provider/match-redaction-warning.ts +3 -3
  148. package/src/provider/matcher.ts +13 -12
  149. package/src/provider/secrets.ts +3 -3
  150. package/src/provider/to-version.ts +11 -9
  151. package/src/tools/registry.ts +5 -5
  152. package/skill/injection-api.md +0 -364
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @reclaimprotocol/agent
2
2
 
3
- Reclaim Protocol lets your app verify **data points** about its users — a follower count, an account balance, an order history. This package gives your AI coding agent the tools to **find, build, test and fix the _data verification providers_** (the recipes that verify a data point) by inspecting and replaying captured requests, checking sessions, evaluate proofs, publishing updates, and much more.
3
+ Reclaim Protocol lets your app verify **data points** about its users — a follower count, an account balance, an order history. This package gives your AI coding agent the tools to find, build, test, and fix **data verification providers**: the recipes that verify a data point. With it, your agent inspects and replays captured requests, checks verification sessions, evaluates proofs, and publishes provider updates.
4
4
 
5
5
  Under the hood it's Chrome-CDP + zkTLS provider-authoring tooling: it captures network traffic from a browser, synthesizes provider definitions from it (`contains` matchers + `jsonPath` / `xPath` / `regex` redactions), runs a local replay diagnostic, and produces a verified attestor proof via `@reclaimprotocol/attestor-core`.
6
6
 
@@ -8,9 +8,13 @@ This package **is** the Reclaim MCP server (bin: `reclaim-mcp-server`). It depen
8
8
 
9
9
  ## Install
10
10
 
11
- **Prerequisites:** **Node ≥ 20** — **works best on a current LTS release (22 or 24)** — and a local Chrome / Chromium / Edge (used for capture and the dashboard login). The first time you call `run_proof`, the agent downloads the ZK circuit files it needs (~280 MB) — so allow a moment for that first proof. The download is deferred to first proof on purpose (not an install step), so the MCP server itself starts immediately.
11
+ **Prerequisites:**
12
12
 
13
- > **Any Node ≥ 20 works, including non-LTS lines like 25** however an LTS line (22 or 24) is *recommended*.
13
+ - **Node 20 or later.** Any line works, including non-LTS ones such as 25, but a current LTS line (22 or 24) is recommended.
14
+ - **A local Chrome, Chromium, or Edge**, used for capture and for the dashboard login.
15
+ - **`cloudflared`** — not required up front. `share_browser_view` uses an existing install if one is on `PATH`, and otherwise downloads it once to `~/.reclaim/bin` on first share (never at install time). Set `RECLAIM_CLOUDFLARED_PATH` to point at your own copy.
16
+
17
+ The first time you call `run_proof`, the agent downloads the ZK circuit files it needs (about 280 MB), so allow a moment for that first proof. The download is deferred to the first proof rather than run at install time, so the MCP server itself starts immediately.
14
18
 
15
19
  It runs as a **stdio MCP server**. Point any MCP-capable agent at this command:
16
20
 
@@ -48,24 +52,24 @@ See [Backends](#backends) for builder mode (`USE_OLD_DEVTOOLS=false`) and the fu
48
52
 
49
53
  ### Provider-authoring guide
50
54
 
51
- The MCP server is all you need — it ships a `how_it_works` tool that returns the full capture → prove → publish guide (login detection, choosing auth-bound endpoints, OPRF decisions, troubleshooting). Any MCP client can call it on demand; there's nothing extra to install.
55
+ The MCP server is all you need — it ships a `how_it_works` tool that serves the capture → prove → publish guide **one topic at a time** (`overview`, `auth`, `browser`, `capture`, `draft`, `params`, `prove`, `publish`, `discover`, `user-script`, `interception`, `hash-validation`, `credentials`, `never-do`, `troubleshooting`). Call it with no arguments for the overview plus the topic index; every response repeats that index, so an agent that guessed wrong self-corrects without a second round-trip. Any MCP client can call it on demand; there's nothing extra to install.
52
56
 
53
57
  ## Using it from your agent
54
58
 
55
59
  Tip: have your agent call the `how_it_works` tool first so it follows the full authoring flow.
56
60
 
57
- Then just ask in plain language, e.g.:
61
+ Then ask in plain language. For example:
58
62
 
59
63
  > "Use Reclaim to create a provider that proves my GitHub follower count."
60
64
 
61
- or you can even ask it like:
65
+ Or more loosely:
62
66
 
63
67
  > "I want to verify Github follow count"
64
68
 
65
69
  The agent orchestrates the tools for you:
66
70
 
67
71
  1. **`reclaim_authenticate`** — a Chrome window opens the Reclaim dashboard; you sign in. The token is captured automatically and the tab closes. (Or it reuses a still-valid cached identity.)
68
- 2. **`attach_browser` + `navigate`** — uses local Chrome, a Builder-hosted remote browser, or a custom CDP websocket; **`wait_for_page`** waits while the operator logs in.
72
+ 2. **`attach_browser` + `navigate`** — uses local Chrome, a Builder-hosted remote browser, or a custom CDP websocket; **`wait_for_page`** waits while you log in.
69
73
  3. **`start_capture` → `find_requests_containing` → `get_request`** — records network traffic and locates the request carrying your value.
70
74
  4. **`propose_provider`** — drafts the provider (a `contains` match + `jsonPath`/`xPath`/`regex` redaction), keeping any secret headers server-side.
71
75
  5. **`replay_request` → `run_proof`** — pre-flights the draft, then produces a verified zkTLS proof via the attestor.
@@ -75,7 +79,7 @@ Your only manual steps are signing in when the browser windows open and confirmi
75
79
 
76
80
  ## How it works (guide tool)
77
81
 
78
- The full, mode-aware authoring guide (`skill/SKILL.builder.md` / `skill/SKILL.old.md`, one per backend) is served on demand by the **`how_it_works`** MCP tool — call it from any MCP client (Claude Code, Cursor, opencode, Antigravity, …) to get the deep capture → prove → publish workflow, decision frameworks, and troubleshooting. The server's always-on `instructions` cover the essentials; `how_it_works` is the in-depth version, with nothing to install.
82
+ The mode-aware authoring guide (`skill/SKILL.builder.md` / `skill/SKILL.old.md`, one per backend) is served on demand by the **`how_it_works`** MCP tool — call it from any MCP client (Claude Code, Cursor, opencode, Antigravity, …) to get the deep capture → prove → publish workflow, decision frameworks, and troubleshooting. It is served per topic rather than all at once, so pulling one answer costs a fraction of the guide. The server's always-on `instructions` cover the essentials; `how_it_works` is the in-depth version, with nothing to install.
79
83
 
80
84
  ## Provider-authoring tools
81
85
 
@@ -83,20 +87,20 @@ These capture/synthesis/proof tools are backend-independent and registered in bo
83
87
 
84
88
  | Tool | Purpose |
85
89
  | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
86
- | `attach_browser` | Connect via local Chrome, Builder-hosted Popcorn with a shareable live view, or a custom CDP websocket |
87
- | `attach_chrome` | Backward-compatible alias for `attach_browser` |
90
+ | `attach_browser` | Connect through local Chrome, Builder-hosted Popcorn, or a custom CDP websocket. All four options — including how each one is watched live — are spelled out in the tool description, so an agent can put the choice to the developer and connect in one call |
88
91
  | `list_tabs` | List open Chrome tabs |
89
92
  | `navigate`, `wait_for_page`, `eval_in_page` | Drive the tab; poll for login completion |
90
93
  | `start_capture`, `stop_capture` | Network capture lifecycle |
91
94
  | `list_requests`, `get_request` | Inspect captured traffic |
92
95
  | `find_requests_containing` | Deterministic search for a target value across captured responses |
93
- | `analyze_request_constraints` | Flag concerns (signed, csrf, graphql, etc.) |
96
+ | `analyze_request_constraints` | Flag concerns: signed request, CSRF dependency, bot challenge, short-lived token, GraphQL |
94
97
  | `propose_provider` | Synthesize a provider — `contains` match + `jsonPath`/`xPath`/`regex` redaction |
95
98
  | `replay_request` | Pre-flight replay via Node `fetch` (no attestor); also used for auth-bound check by omitting `secretParamsRef` |
96
99
  | `run_proof` | End-to-end zkTLS proof through the attestor SDK |
97
100
  | `create_provider_version_from_capture` | Publish a drafted provider to the backend |
98
101
  | `authenticate_builder` | In old-devtools mode only, sign in to Builder for remote-browser allocation |
99
102
  | `list_builder_organizations` | In old-devtools mode only, list organization ids for a quota-accounted Builder browser |
103
+ | `share_browser_view`, `stop_browser_view` | Put a LOCAL attached tab behind a public HTTPS URL (cloudflared quick tunnel) so someone elsewhere can watch it and, by default, click and type in it. Local CDP modes only — a `builder` browser already returns a hosted `liveViewUrl` |
100
104
  | `dispose_browser` | Stop captures, disconnect CDP, and immediately release a Builder-hosted browser |
101
105
  | `get_session`, `reset_session` | Session state |
102
106
 
@@ -114,7 +118,7 @@ Relevant env vars: `RECLAIM_OLD_API_URL` (default `https://devapi.reclaimprotoco
114
118
 
115
119
  ## Local development (from source)
116
120
 
117
- For working on the package inside the monorepo (consumers should use [Install](#install) instead). A workspace install just fetches dependencies:
121
+ For working on the package inside the monorepo (consumers should use [Install](#install) instead). A workspace install only fetches dependencies:
118
122
 
119
123
  ```bash
120
124
  npm install
@@ -128,14 +132,18 @@ npm run download:zk-files --workspace=packages/agent
128
132
 
129
133
  ## Platform notes
130
134
 
131
- - **Node >=20** is required to run the published package (declared in `engines`) — it ships compiled `lib/*.js`, so no TypeScript type-stripping is needed at runtime. Any line ≥ 20 works, including non-LTS like 25: the one native addon that used to break this, `re2`, is an **optional** dependency as of `@reclaimprotocol/attestor-core` 5.0.8 and degrades to native `RegExp` when its binary is absent. (The other native dep, `koffi`, is N-API — ABI-independent, all-platform prebuilts — so it was never version-sensitive.) An LTS line (22/24) is recommended so `re2` is present for faster, ReDoS-safe matching. Working on the package **from a checkout** additionally needs **Node >=22.18**, because the test suite and dev workflow run bare `.ts` files directly and unflagged TypeScript type-stripping landed in 22.18.
135
+ - **Node 20 or later** is required to run the published package (declared in `engines`). The package ships compiled `lib/*.js`, so no TypeScript type-stripping is needed at runtime.
136
+
137
+ Any line works, including non-LTS ones such as 25. The one native addon that used to break this, `re2`, became an **optional** dependency in `@reclaimprotocol/attestor-core` 5.0.8 and degrades to the native `RegExp` when its binary is absent. The other native dependency, `koffi`, is N-API, so it ships ABI-independent prebuilts for every platform and was never version-sensitive. An LTS line (22 or 24) is still recommended, because `re2` is present there and gives faster, ReDoS-safe matching.
138
+
139
+ Working on the package **from a checkout** additionally needs **Node 22.18 or later**: the test suite and the dev workflow run bare `.ts` files directly, and unflagged TypeScript type-stripping landed in 22.18.
132
140
  - **Chrome 136+ silently disables `--remote-debugging-port`** when launched against the default user profile. `dedicated` mode (the default) sidesteps this by always passing `--user-data-dir=~/.reclaim/chrome-profile`.
133
141
 
134
142
  ## Tests
135
143
 
136
- `npm test` runs the unit + integration suite (no Chrome needed).
144
+ `npm test` runs the unit and integration suite. Chrome isn't needed.
137
145
 
138
- The Chrome CDP end-to-end test at `tests/e2e-chrome.test.ts` is gated behind an env var. It launches headless Chrome with a dedicated temp profile, captures a navigation against a local fixture server, and asserts the synthesiser finds the right request. To run it:
146
+ The Chrome CDP end-to-end test at `tests/e2e-chrome.test.ts` is gated behind an env var. It launches headless Chrome with a dedicated temp profile, captures a navigation against a local fixture server, and asserts the synthesizer finds the right request. To run it:
139
147
 
140
148
  ```bash
141
149
  # bash / macOS / Linux
@@ -145,11 +153,11 @@ RECLAIM_AGENT_E2E=1 npm test --workspace=packages/agent
145
153
  $env:RECLAIM_AGENT_E2E=1; npm test --workspace=packages/agent
146
154
  ```
147
155
 
148
- Requires Chrome / Chromium / Edge installed somewhere `findChromeBinary` can locate (or `RECLAIM_AGENT_CHROME_PATH` set). The test self-skips if no binary is found.
156
+ The test requires Chrome, Chromium, or Edge installed somewhere `findChromeBinary` can locate it, or `RECLAIM_AGENT_CHROME_PATH` pointing at the binary. It self-skips when no binary is found.
149
157
 
150
- ## Disabling
158
+ ## Disable the browser tools
151
159
 
152
- The MCP server registers the provider-authoring (Chrome/CDP) tools by default in both modes. To disable them (e.g. in hosted deployments where no local Chrome is available):
160
+ The MCP server registers the provider-authoring (Chrome and CDP) tools by default in both modes. To disable them — for example, in a hosted deployment with no local Chrome — set:
153
161
 
154
162
  ```bash
155
163
  RECLAIM_AGENT_DISABLED=1
@@ -203,8 +211,35 @@ claude mcp add reclaim -- node "$(pwd)/packages/agent/lib/mcp-bin.js"
203
211
  }
204
212
  ```
205
213
 
206
- To target a specific backend while developing, add an `env` block (Claude Code / `.mcp.json`) or `environment` (opencode) — e.g. `"USE_OLD_DEVTOOLS": "false"` for builder mode. See [Backends](#backends).
214
+ To target a specific backend while developing, add an `env` block (Claude Code / `.mcp.json`) or `environment` (opencode) — for example, `"USE_OLD_DEVTOOLS": "false"` for builder mode. See [Backends](#backends).
207
215
 
208
216
  ### Authoring guide
209
217
 
210
- `skill/SKILL.builder.md` and `skill/SKILL.old.md` are the sources the `how_it_works` tool serves — one per backend, so an agent is never shown tools its mode doesn't have. The server picks by `USE_OLD_DEVTOOLS`. Edit the relevant file and rebuild (`npm run build`) to update what the tool returns.
218
+ Everything the `how_it_works` tool serves lives in `skill/` as markdown — edit it and rebuild (`npm run build`) to change what the tool returns.
219
+
220
+ | file | what it is |
221
+ | --- | --- |
222
+ | `builder-mode.md`, `old-mode.md` | The server's **always-on** `instructions`, one per backend. Every client pays for these on every turn — keep them short and keep the depth in a topic. |
223
+ | `SKILL.builder.md`, `SKILL.old.md` | The authoring guide per backend, split into `<!-- topic: name -->` blocks. Each block is one `how_it_works` topic. |
224
+ | `SKILL.common.md` | Prose shared by both guides, in `<!-- section: name -->` blocks. A guide splices one in with `<!-- common: name -->`. |
225
+ | `user-script.md`, `interception.md`, `hash-validation.md` | Backend-independent topics, served as-is to both modes. |
226
+ | `js-sdk-integration.md` | Served by the old-devtools `get_js_sdk_integration_guide` tool, not by `how_it_works`. |
227
+
228
+ Two mechanics to know when editing:
229
+
230
+ - `%CTX%` expands to the mode's consumer-param namespace (`context.` on builder, `context_` on old-devtools). It is usually the *only* difference between the two modes, which is what lets a block stay in `SKILL.common.md` instead of being duplicated and drifting.
231
+ - Every topic needs a one-line entry in `TOPIC_SUMMARIES` (`src/mcp/instructions.ts`) — it's what the tool advertises in its index. Assembly asserts on a missing section or summary, so a typo fails at server start rather than silently serving an empty topic.
232
+
233
+ **Do not inline docs into a tool's input schema.** A schema description is sent to the model on every request; the injection guide that used to live in the publish tool's `jsUserScripts` field was about half the entire old-devtools tool surface on its own. Put the prose in a topic and leave a pointer.
234
+
235
+ ### Writing conventions for `skill/` and tool descriptions
236
+
237
+ These follow the [Agent Skills best practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) and the [Google developer documentation style guide](https://developers.google.com/style):
238
+
239
+ - **Assume the model is smart.** Only write what it can't infer. Every token in a topic competes with the task.
240
+ - **Keep each topic focused and one level deep.** A topic links to other topics by name; it never chains through a third file. Give any topic over 100 lines a `## Contents` list at the top, so a partial read still shows its full scope.
241
+ - **One term per concept.** "provider", not "verification" or "recipe". "developer", not "dev", "user", or "operator". "user script", not "injection".
242
+ - **Reserve capitals for hard rules.** NEVER, MUST, ONLY, and NOT earn all caps; use bold for ordinary emphasis.
243
+ - **Write in American English, in the second person, in the present tense.** Spell out "for example" and "that is" rather than "e.g." and "i.e.".
244
+ - **Every tool description says what the tool does and when to call it**, plus any constraint the model can't discover by calling it.
245
+ - **No time-sensitive statements.** Pin facts to a version or a named behavior, not to a date.
@@ -56,9 +56,10 @@ export interface AuthoringBackend {
56
56
  /** This session's drafts (propose → replay/run_proof). */
57
57
  readonly drafts: DraftStore;
58
58
  /** Prove a drafted recipe. Local: attestor SDK (rich claim view). Remote: TEE
59
- * `/reclaim/prove` (lean, secret-free summary). Returns the environment's
60
- * display shape. A backend may record the proven recipe internally (e.g.
61
- * the cloud publish step) — that side effect is the backend's, not ours. */
59
+ * `/reclaim/prove` (lean, secret-free summary). Returns the environment's
60
+ * display shape. A backend may record the proven recipe internally (for
61
+ * example, the cloud publish step) — that side effect is the backend's, not
62
+ * ours. */
62
63
  prove(draft: Draft, opts?: ProveOptions): Promise<unknown>;
63
64
  /**
64
65
  * Validate/replay a drafted recipe before proving. Both local and remote
@@ -67,7 +68,7 @@ export interface AuthoringBackend {
67
68
  * always reflects the real browser session. With `withoutSecrets` it is
68
69
  * the AUTH-BOUND check: re-issue anonymously (`credentials: 'omit'`, no
69
70
  * secret headers) and report whether the values are STILL extractable —
70
- * i.e. the endpoint is public. Without it, validate extraction for the
71
+ * that is, the endpoint is public. Without it, validate extraction for the
71
72
  * authenticated request. The shape is environment-specific and passed
72
73
  * straight through as the tool's result.
73
74
  */
@@ -80,7 +80,9 @@ export function authoringTools(resolveBackend) {
80
80
  return [
81
81
  defineTool({
82
82
  name: 'navigate',
83
- description: 'Navigate the browser tab to a URL, waiting for load.',
83
+ description: 'Navigate the captured tab to a URL and wait for its load event. '
84
+ + 'Returns the URL the tab actually landed on, so you can detect a '
85
+ + 'redirect to a login page.',
84
86
  inputSchema: objectSchema({ url: { type: 'string' }, timeoutMs: { type: 'number' } }, ['url']),
85
87
  }, async (args) => {
86
88
  const backend = resolveBackend(args);
@@ -98,14 +100,18 @@ export function authoringTools(resolveBackend) {
98
100
  }),
99
101
  defineTool({
100
102
  name: 'eval_in_page',
101
- description: 'Evaluate a JavaScript expression in the page and return its value '
102
- + '(use to read the displayed value, check login state, etc.).',
103
+ description: 'Evaluate a JavaScript expression in the page and return its value. '
104
+ + 'Use it to read the value being proven out of the DOM, to check '
105
+ + 'login state, or to trigger the target request (for example, '
106
+ + '`location.reload()`).',
103
107
  inputSchema: objectSchema({ expression: { type: 'string' } }, ['expression']),
104
108
  }, (args) => evalInPage(resolveBackend(args), String(args.expression))),
105
109
  defineTool({
106
110
  name: 'wait_for_page',
107
- description: 'Poll a JavaScript expression until it is truthy or the timeout '
108
- + 'elapses (e.g. wait for the user to finish logging in).',
111
+ description: 'Poll a JavaScript expression once a second until it is truthy or '
112
+ + 'the timeout elapses. Use it to wait for content to appear. To '
113
+ + 'wait for a login, use wait_for_login instead — page JavaScript '
114
+ + 'cannot see the httpOnly session cookie.',
109
115
  inputSchema: objectSchema({ expression: { type: 'string' }, timeoutMs: { type: 'number' } }, ['expression']),
110
116
  }, async (args) => {
111
117
  const backend = resolveBackend(args);
@@ -122,26 +128,18 @@ export function authoringTools(resolveBackend) {
122
128
  }),
123
129
  defineTool({
124
130
  name: 'test_user_script',
125
- description: 'Dry-run a USER SCRIPT — the same thing as the `jsUserScripts` / '
126
- + '`customInjection` field on a published provider: plain browser '
127
- + 'JS that runs before every page load, used to drive navigation '
128
- + 'for data gated behind login/routing — in the LIVE attached tab, '
129
- + 'BEFORE publishing. Installs `script` via CDP so it runs on the '
130
- + 'next navigation exactly like the real field would, then reports '
131
- + 'whether it ran and any errors it threw. Catches bugs (e.g. a '
132
- + 'listener registered with setTimeout that never fires because the '
133
- + "page was already past 'load' by the time it ran) before they "
134
- + 'surface in a real end-user session instead of after. If '
135
- + '`navigateUrl` is given, navigates there after installing, then '
136
- + 'removes the script again once the result is read (self-cleaning '
137
- + '— safe to call repeatedly). Without `navigateUrl` the script is '
138
- + 'left installed for you to navigate yourself (any way you like) '
139
- + 'then re-call this tool, or read '
140
- + '`window.__reclaimUserScriptResult` via eval_in_page — in that '
141
- + 'manual mode it is NOT auto-removed and keeps running on every '
142
- + 'later navigation until a follow-up call with `navigateUrl` '
143
- + 'removes it, so avoid stacking several manual-mode calls in a '
144
- + 'row.',
131
+ description: 'Dry-run a USER SCRIPT (the `jsUserScripts` / `customInjection` '
132
+ + 'field) in the LIVE attached tab, BEFORE publishing. Installs '
133
+ + '`script` via CDP so it runs on the next navigation exactly as '
134
+ + 'the real field would, then reports whether it ran and what it '
135
+ + 'threw. With `navigateUrl`, it navigates there and removes the '
136
+ + 'script once the result is read — self-cleaning, safe to repeat. '
137
+ + 'WITHOUT `navigateUrl` the script stays installed (navigate '
138
+ + 'yourself, then re-call this tool or read '
139
+ + '`window.__reclaimUserScriptResult` via eval_in_page) and keeps '
140
+ + 'running on every later navigation until a call WITH '
141
+ + '`navigateUrl` removes it — so don\'t stack manual-mode calls. '
142
+ + 'Guide: how_it_works({ topic: "user-script" }).',
145
143
  inputSchema: objectSchema({
146
144
  script: { type: 'string' },
147
145
  navigateUrl: { type: 'string' },
@@ -177,8 +175,9 @@ export function authoringTools(resolveBackend) {
177
175
  note: 'Installed for the NEXT navigation only — it did not run on '
178
176
  + 'the current page, and stays installed (runs on every later '
179
177
  + 'navigation too) until a follow-up call with navigateUrl '
180
- + 'removes it. Navigate now (e.g. the navigate tool), then read '
181
- + 'window.__reclaimUserScriptResult via eval_in_page, or call '
178
+ + 'removes it. Navigate now (for example, with the navigate '
179
+ + 'tool), then read window.__reclaimUserScriptResult via '
180
+ + 'eval_in_page, or call '
182
181
  + 'test_user_script again with navigateUrl.',
183
182
  };
184
183
  }
@@ -216,7 +215,8 @@ export function authoringTools(resolveBackend) {
216
215
  description: 'List the NAMES of cookies currently set for the page (values are '
217
216
  + 'NEVER returned). The most reliable, language- and DOM-independent '
218
217
  + 'login signal: after the user logs in, an auth/session cookie '
219
- + 'appears (e.g. session/sid/token/auth/jwt/_user/logged_in). '
218
+ + 'appears (for example, session, sid, token, auth, jwt, _user, '
219
+ + 'logged_in). '
220
220
  + 'Includes httpOnly cookies the page JS cannot see.',
221
221
  inputSchema: objectSchema({}),
222
222
  }, async (args) => {
@@ -257,8 +257,8 @@ export function authoringTools(resolveBackend) {
257
257
  'userFirstVisit', 'cf_', '_dd_', 'bcookie', 'bscookie', 'lidc',
258
258
  ].join('|') + ')', 'i');
259
259
  // A real session/auth cookie name. Deliberately NOT bare `token` — a
260
- // challenge/CSRF token (e.g. LinkedIn `chp_token`) contains it and is
261
- // set BEFORE auth completes. Any new httpOnly cookie is also a
260
+ // challenge/CSRF token (for example, LinkedIn `chp_token`) contains it
261
+ // and is set BEFORE auth completes. Any new httpOnly cookie is also a
262
262
  // candidate (server-set session cookies are usually httpOnly).
263
263
  const SESSION = /sess|sid|jwt|_session|auth|logged.?in|remember|li_at/i;
264
264
  // Auth/challenge URL paths — while the page is still on one, login is
@@ -0,0 +1,78 @@
1
+ /** The minimal CDP surface this module needs. Both the agent's `CdpTabClient`
2
+ * and an app-side session-scoped wrapper around `chrome-remote-interface`'s
3
+ * `CDP.Client` already satisfy this. */
4
+ export interface CdpProxySender {
5
+ send: (method: string, params?: object) => Promise<unknown>;
6
+ subscribe: <T = unknown>(method: string, listener: (params: T) => void) => () => void;
7
+ }
8
+ export interface BrowserProxyCredentials {
9
+ host: string;
10
+ port: number;
11
+ username: string;
12
+ password: string;
13
+ }
14
+ export interface TemplatedProxyUsernameOptions {
15
+ /** Vendor's env-configured base, e.g.
16
+ * `brd-customer-<id>-zone-<zone>-country-{{COUNTRY_LOCATION}}`. */
17
+ template: string;
18
+ countryCode: string;
19
+ /** Sticky-session key — the same value must be used for every request
20
+ * that should exit through the same proxy IP. */
21
+ proxySessionId: string;
22
+ }
23
+ /** Substitute `{{COUNTRY_LOCATION}}` in the vendor's username template (or
24
+ * append `-country-<cc>` if the template has no placeholder), then append
25
+ * the sticky-session suffix. */
26
+ export declare function buildTemplatedProxyUsername(opts: TemplatedProxyUsernameOptions): string;
27
+ /** A short alphanumeric sticky-session id suggestion for a proxy vendor's
28
+ * session placeholder (e.g. Bright Data's `-session-<id>` suffix) —
29
+ * deliberately not `captureId`/`sessionId` (long UUIDs, wrong shape for
30
+ * most vendors' conventions and for the attestor's own `proxySessionId`
31
+ * constraint: smallcase alphanumeric, 8-14 chars). Only a suggestion — the
32
+ * caller's own `proxySessionId`, when given, is used instead. */
33
+ export declare function generateProxySessionId(): string;
34
+ /** Parse a `PROXY_SERVER`-style `host:port` string. Throws on a malformed
35
+ * value — a bad env var should fail loudly, not silently proxy nowhere. */
36
+ export declare function parseProxyServer(server: string): {
37
+ host: string;
38
+ port: number;
39
+ };
40
+ /**
41
+ * Route the browser's network traffic through `host:port` via the Popcorn
42
+ * extension's page API. Never throws — a `false` return means the extension
43
+ * couldn't be reached (e.g. this isn't a Popcorn browser); callers still get
44
+ * proxy auth wired up by {@link setupProxyAuthViaCdp} independently, since a
45
+ * caller-supplied proxy might already be configured another way.
46
+ */
47
+ export declare function configureBrowserProxyViaExtension(sender: CdpProxySender, config: BrowserProxyCredentials): Promise<boolean>;
48
+ /** Clear the extension-configured proxy, routing the browser direct again. */
49
+ export declare function clearBrowserProxyViaExtension(sender: CdpProxySender): Promise<boolean>;
50
+ /**
51
+ * Answer the proxy's Basic-auth challenge over CDP `Fetch`, since Popcorn's
52
+ * extension only sets host/port routing (not credentials — the proxy vendor
53
+ * still challenges the browser for them the normal way). Self-disables once
54
+ * the first challenge is answered (or after a timeout with none), rather
55
+ * than intercepting every request for the session's whole lifetime.
56
+ */
57
+ export declare function setupProxyAuthViaCdp(sender: CdpProxySender, credentials: Pick<BrowserProxyCredentials, 'username' | 'password'>): Promise<void>;
58
+ export interface ApplyBrowserProxyOptions extends BrowserProxyCredentials {
59
+ /** Defaults to true — proxy routing only affects new connections, so
60
+ * existing page state must reload to actually go through it. */
61
+ reload?: boolean;
62
+ }
63
+ export interface ApplyBrowserProxyResult {
64
+ /** False means the extension wasn't reachable (not a Popcorn browser, or
65
+ * it isn't installed) — auth handling was still armed regardless, in
66
+ * case the proxy is configured another way. */
67
+ appliedViaExtension: boolean;
68
+ }
69
+ /** Configure routing + auth + reload, in the right order: routing must be
70
+ * live before the reload navigates, and auth must be armed before that
71
+ * navigation's requests go out. */
72
+ export declare function applyBrowserProxy(sender: CdpProxySender, opts: ApplyBrowserProxyOptions): Promise<ApplyBrowserProxyResult>;
73
+ /** Clear proxy routing and reload so subsequent requests go direct again. */
74
+ export declare function clearBrowserProxy(sender: CdpProxySender, opts?: {
75
+ reload?: boolean;
76
+ }): Promise<{
77
+ cleared: boolean;
78
+ }>;
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Native browser-level proxy for a Popcorn remote browser session.
3
+ *
4
+ * Routes the browser's OWN network traffic (not the attestor's zkTLS egress —
5
+ * that's `geoLocation`/`useProxy` on `ProviderWebSettings`) through a
6
+ * user-supplied proxy. Applied via the Popcorn kiosk image's `window.__pcn`
7
+ * page API rather than a Playwright/CDP launch arg or a new browser context —
8
+ * both of those would spawn a browser outside the pod's `--kiosk` window,
9
+ * breaking the live-view stream this session is already being watched
10
+ * through. Proxy auth (username/password) is answered separately over CDP's
11
+ * `Fetch` domain, since Popcorn's extension only sets host/port routing.
12
+ *
13
+ * Shared between the app's verification runner and the agent's MCP tools —
14
+ * both drive a Popcorn browser over `chrome-remote-interface`, just through
15
+ * different wrapper types, so this module only depends on the minimal
16
+ * {@link CdpProxySender} shape rather than either package's CDP client.
17
+ */
18
+ import { randomInt } from 'node:crypto';
19
+ import { setTimeout as delay } from 'node:timers/promises';
20
+ import { LOGGER } from "../logger.js";
21
+ /** Substitute `{{COUNTRY_LOCATION}}` in the vendor's username template (or
22
+ * append `-country-<cc>` if the template has no placeholder), then append
23
+ * the sticky-session suffix. */
24
+ export function buildTemplatedProxyUsername(opts) {
25
+ const { template, countryCode, proxySessionId } = opts;
26
+ const cc = countryCode.toLowerCase();
27
+ const base = template.includes('{{COUNTRY_LOCATION}}')
28
+ ? template.replaceAll('{{COUNTRY_LOCATION}}', cc)
29
+ : `${template}-country-${cc}`;
30
+ return `${base}-session-${proxySessionId}`;
31
+ }
32
+ const PROXY_SESSION_ID_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789';
33
+ const PROXY_SESSION_ID_LENGTH = 10;
34
+ /** A short alphanumeric sticky-session id suggestion for a proxy vendor's
35
+ * session placeholder (e.g. Bright Data's `-session-<id>` suffix) —
36
+ * deliberately not `captureId`/`sessionId` (long UUIDs, wrong shape for
37
+ * most vendors' conventions and for the attestor's own `proxySessionId`
38
+ * constraint: smallcase alphanumeric, 8-14 chars). Only a suggestion — the
39
+ * caller's own `proxySessionId`, when given, is used instead. */
40
+ export function generateProxySessionId() {
41
+ let out = '';
42
+ for (let i = 0; i < PROXY_SESSION_ID_LENGTH; i++) {
43
+ out += PROXY_SESSION_ID_CHARS[randomInt(PROXY_SESSION_ID_CHARS.length)];
44
+ }
45
+ return out;
46
+ }
47
+ /** Parse a `PROXY_SERVER`-style `host:port` string. Throws on a malformed
48
+ * value — a bad env var should fail loudly, not silently proxy nowhere. */
49
+ export function parseProxyServer(server) {
50
+ const match = /^([^:/]+):(\d+)$/.exec(server.trim());
51
+ if (!match) {
52
+ throw new Error(`PROXY_SERVER must be "host:port", got "${server}"`);
53
+ }
54
+ return { host: match[1], port: Number(match[2]) };
55
+ }
56
+ const PCN_READY_TIMEOUT_MS = 5000;
57
+ const PCN_READY_POLL_MS = 100;
58
+ /** Chrome caches a proxy credential per host:port after the first successful
59
+ * challenge, so this only needs to cover the FIRST request. If no challenge
60
+ * ever arrives (proxy doesn't require auth), don't leave `Fetch` enabled —
61
+ * it would otherwise hold every request pending on this handler forever. */
62
+ const PROXY_AUTH_SAFETY_TIMEOUT_MS = 15000;
63
+ async function isExtensionReady(sender) {
64
+ const { result } = await sender.send('Runtime.evaluate', {
65
+ expression: 'Boolean(window.__pcn && window.__pcn.ready)',
66
+ returnByValue: true,
67
+ });
68
+ return result?.value === true;
69
+ }
70
+ async function waitForExtensionReady(sender) {
71
+ const deadline = Date.now() + PCN_READY_TIMEOUT_MS;
72
+ while (Date.now() < deadline) {
73
+ if (await isExtensionReady(sender)) {
74
+ return true;
75
+ }
76
+ await delay(PCN_READY_POLL_MS);
77
+ }
78
+ return false;
79
+ }
80
+ /**
81
+ * Route the browser's network traffic through `host:port` via the Popcorn
82
+ * extension's page API. Never throws — a `false` return means the extension
83
+ * couldn't be reached (e.g. this isn't a Popcorn browser); callers still get
84
+ * proxy auth wired up by {@link setupProxyAuthViaCdp} independently, since a
85
+ * caller-supplied proxy might already be configured another way.
86
+ */
87
+ export async function configureBrowserProxyViaExtension(sender, config) {
88
+ try {
89
+ if (!await isExtensionReady(sender)) {
90
+ // Force the content script to (re-)run so `window.__pcn` appears.
91
+ await sender.send('Page.navigate', { url: 'about:blank' });
92
+ if (!await waitForExtensionReady(sender)) {
93
+ LOGGER.warn('popcorn proxy extension not ready; skipping __pcn.set');
94
+ return false;
95
+ }
96
+ }
97
+ const { result } = await sender.send('Runtime.evaluate', {
98
+ expression: `(async () => {
99
+ try { await window.__pcn.set(${JSON.stringify(config)}); return true }
100
+ catch(e) { return false }
101
+ })()`,
102
+ awaitPromise: true,
103
+ returnByValue: true,
104
+ });
105
+ return result?.value === true;
106
+ }
107
+ catch (err) {
108
+ LOGGER.warn({ err }, 'configureBrowserProxyViaExtension failed');
109
+ return false;
110
+ }
111
+ }
112
+ /** Clear the extension-configured proxy, routing the browser direct again. */
113
+ export async function clearBrowserProxyViaExtension(sender) {
114
+ try {
115
+ const { result } = await sender.send('Runtime.evaluate', {
116
+ expression: `(async () => {
117
+ try { await window.__pcn.clear(); return true }
118
+ catch(e) { return false }
119
+ })()`,
120
+ awaitPromise: true,
121
+ returnByValue: true,
122
+ });
123
+ return result?.value === true;
124
+ }
125
+ catch (err) {
126
+ LOGGER.warn({ err }, 'clearBrowserProxyViaExtension failed');
127
+ return false;
128
+ }
129
+ }
130
+ /**
131
+ * Answer the proxy's Basic-auth challenge over CDP `Fetch`, since Popcorn's
132
+ * extension only sets host/port routing (not credentials — the proxy vendor
133
+ * still challenges the browser for them the normal way). Self-disables once
134
+ * the first challenge is answered (or after a timeout with none), rather
135
+ * than intercepting every request for the session's whole lifetime.
136
+ */
137
+ export async function setupProxyAuthViaCdp(sender, credentials) {
138
+ await sender.send('Fetch.enable', { handleAuthRequests: true });
139
+ let settled = false;
140
+ const finish = () => {
141
+ if (settled) {
142
+ return;
143
+ }
144
+ settled = true;
145
+ unsubAuth();
146
+ unsubPaused();
147
+ sender.send('Fetch.disable').catch(() => { });
148
+ };
149
+ const unsubAuth = sender.subscribe('Fetch.authRequired', (params) => {
150
+ sender.send('Fetch.continueWithAuth', {
151
+ requestId: params.requestId,
152
+ authChallengeResponse: {
153
+ response: 'ProvideCredentials',
154
+ username: credentials.username,
155
+ password: credentials.password,
156
+ },
157
+ }).then(finish).catch(() => { });
158
+ });
159
+ // Requests still need releasing while `Fetch` is enabled, whether or not
160
+ // they end up challenged. Fire-and-forget: serializing behind auth
161
+ // negotiation would stall unrelated requests.
162
+ const unsubPaused = sender.subscribe('Fetch.requestPaused', (params) => {
163
+ sender.send('Fetch.continueRequest', { requestId: params.requestId })
164
+ .catch(() => { });
165
+ });
166
+ setTimeout(finish, PROXY_AUTH_SAFETY_TIMEOUT_MS);
167
+ }
168
+ /** Configure routing + auth + reload, in the right order: routing must be
169
+ * live before the reload navigates, and auth must be armed before that
170
+ * navigation's requests go out. */
171
+ export async function applyBrowserProxy(sender, opts) {
172
+ const appliedViaExtension = await configureBrowserProxyViaExtension(sender, opts);
173
+ await setupProxyAuthViaCdp(sender, opts);
174
+ if (opts.reload !== false) {
175
+ await sender.send('Page.reload', {});
176
+ }
177
+ return { appliedViaExtension };
178
+ }
179
+ /** Clear proxy routing and reload so subsequent requests go direct again. */
180
+ export async function clearBrowserProxy(sender, opts = {}) {
181
+ const cleared = await clearBrowserProxyViaExtension(sender);
182
+ if (opts.reload !== false) {
183
+ await sender.send('Page.reload', {});
184
+ }
185
+ return { cleared };
186
+ }
@@ -56,9 +56,10 @@ export function isStaticAsset(url) {
56
56
  return STATIC_ASSET_EXTENSIONS.has(base.slice(dot + 1).toLowerCase());
57
57
  }
58
58
  /** Response Content-Types for static assets. The URL-extension filter misses
59
- * extension-less bundler/CDN URLs (e.g. `/assets/3b8f1c`), so we also drop by
60
- * MIME at response time. `text/html`, `application/json`, and `text/plain`
61
- * are NOT here — those are the document/API shapes recipes match. */
59
+ * extension-less bundler/CDN URLs (for example, `/assets/3b8f1c`), so we also
60
+ * drop by MIME at response time. `text/html`, `application/json`, and
61
+ * `text/plain` are NOT here — those are the document/API shapes recipes
62
+ * match. */
62
63
  const STATIC_ASSET_MIME_EXACT = new Set([
63
64
  'text/css',
64
65
  'application/javascript', 'text/javascript', 'application/x-javascript',
@@ -165,9 +166,9 @@ export function createCaptureEngine(opts) {
165
166
  function onResponseReceived(p) {
166
167
  const f = inFlight.get(p.requestId);
167
168
  if (!f) {
168
- // No in-flight entry → the request was skipped (e.g. a static asset)
169
- // or never seen. Discard any buffered response extra-info so a skipped
170
- // asset can't leave a dangling entry.
169
+ // No in-flight entry → the request was skipped (for example, a static
170
+ // asset) or never seen. Discard any buffered response extra-info so a
171
+ // skipped asset can't leave a dangling entry.
171
172
  pendingResponseExtra.delete(p.requestId);
172
173
  return;
173
174
  }
@@ -225,7 +226,7 @@ export function createCaptureEngine(opts) {
225
226
  }
226
227
  catch {
227
228
  // Body might be unavailable for
228
- // some content-types (e.g. websocket). Skip.
229
+ // some content-types (for example, websocket). Skip.
229
230
  }
230
231
  }
231
232
  }
@@ -19,6 +19,16 @@ interface CdpTabEventMap {
19
19
  'Network.loadingFinished': {
20
20
  requestId: string;
21
21
  };
22
+ /** Screencast frames, for the live view (`live/viewer.ts`). Each carries a
23
+ * `sessionId` that MUST be acked with `Page.screencastFrameAck`, or CDP
24
+ * stops sending. */
25
+ 'Page.screencastFrame': {
26
+ data: string;
27
+ sessionId: number;
28
+ metadata?: {
29
+ timestamp?: number;
30
+ };
31
+ };
22
32
  }
23
33
  export interface CdpTabClient {
24
34
  send: (method: string, params?: object) => Promise<unknown>;