@reclaimprotocol/agent 0.1.23 → 0.1.25
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 +56 -20
- package/lib/authoring/backend.d.ts +5 -4
- package/lib/authoring/tools.js +30 -30
- package/lib/cdp/capture-engine.js +8 -7
- package/lib/cdp/claims.d.ts +21 -0
- package/lib/cdp/claims.js +129 -0
- package/lib/cdp/container.d.ts +85 -0
- package/lib/cdp/container.js +481 -0
- package/lib/cdp/geo.d.ts +10 -0
- package/lib/cdp/geo.js +36 -0
- package/lib/cdp/index.d.ts +1 -0
- package/lib/cdp/index.js +1 -0
- package/lib/live/cloudflared.d.ts +15 -0
- package/lib/live/cloudflared.js +268 -0
- package/lib/live/tunnel.d.ts +26 -0
- package/lib/live/tunnel.js +160 -0
- package/lib/mcp/api/operations.gen.js +34 -26
- package/lib/mcp/api/to-zod.d.ts +1 -1
- package/lib/mcp/api/to-zod.js +1 -1
- package/lib/mcp/api/tools.js +1 -1
- package/lib/mcp/instructions.d.ts +15 -2
- package/lib/mcp/instructions.js +102 -38
- package/lib/mcp/start.js +62 -6
- package/lib/mcp/tools/agent/analyze.js +5 -1
- package/lib/mcp/tools/agent/attach.d.ts +29 -2
- package/lib/mcp/tools/agent/attach.js +310 -24
- package/lib/mcp/tools/agent/build.d.ts +18 -8
- package/lib/mcp/tools/agent/build.js +30 -12
- package/lib/mcp/tools/agent/capture.js +5 -1
- package/lib/mcp/tools/agent/eval.js +3 -1
- package/lib/mcp/tools/agent/find.js +6 -5
- package/lib/mcp/tools/agent/how-it-works.d.ts +15 -7
- package/lib/mcp/tools/agent/how-it-works.js +57 -17
- package/lib/mcp/tools/agent/index.d.ts +1 -2
- package/lib/mcp/tools/agent/index.js +1 -1
- package/lib/mcp/tools/agent/live-view.d.ts +17 -0
- package/lib/mcp/tools/agent/live-view.js +146 -0
- package/lib/mcp/tools/agent/pod-proof.d.ts +8 -0
- package/lib/mcp/tools/agent/pod-proof.js +64 -0
- package/lib/mcp/tools/agent/propose.js +4 -2
- package/lib/mcp/tools/agent/publish.js +47 -77
- package/lib/mcp/tools/agent/session.d.ts +16 -1
- package/lib/mcp/tools/agent/session.js +32 -7
- package/lib/mcp/tools/authenticate/authenticate.js +26 -13
- package/lib/mcp/tools/mode/mode.d.ts +1 -1
- package/lib/mcp/tools/mode/mode.js +10 -22
- package/lib/old/client.d.ts +1 -1
- package/lib/old/client.js +1 -1
- package/lib/old/instructions.d.ts +9 -1
- package/lib/old/instructions.js +4 -6
- package/lib/old/login-cdp.d.ts +2 -2
- package/lib/old/login-cdp.js +46 -6
- package/lib/old/to-register.d.ts +3 -2
- package/lib/old/to-register.js +1 -1
- package/lib/old/tools/authenticate.js +9 -1
- package/lib/old/tools/get-provider-info.js +6 -5
- package/lib/old/tools/issue-app-credentials.js +3 -2
- package/lib/old/tools/publish.d.ts +1 -1
- package/lib/old/tools/publish.js +90 -131
- package/lib/old/tools/update-provider-metadata.d.ts +1 -1
- package/lib/old/tools/update-provider-metadata.js +1 -1
- package/lib/old/tools/verify-sdk-setup.js +1 -1
- package/lib/proof/attestor.d.ts +3 -3
- package/lib/proof/debug-response-dump.js +4 -4
- package/lib/proof/index.d.ts +1 -0
- package/lib/proof/index.js +1 -0
- package/lib/proof/page-replay.d.ts +1 -1
- package/lib/proof/page-replay.js +1 -1
- package/lib/proof/pod-prover.d.ts +47 -0
- package/lib/proof/pod-prover.js +155 -0
- package/lib/provider/dom-xpath.js +11 -9
- package/lib/provider/draft.d.ts +10 -7
- package/lib/provider/draft.js +11 -9
- package/lib/provider/injection-doc.d.ts +23 -1
- package/lib/provider/injection-doc.js +34 -21
- package/lib/provider/match-redaction-warning.d.ts +2 -2
- package/lib/provider/match-redaction-warning.js +3 -3
- package/lib/provider/matcher.js +13 -12
- package/lib/provider/secrets.d.ts +3 -3
- package/lib/provider/secrets.js +3 -3
- package/lib/provider/to-version.d.ts +7 -6
- package/lib/provider/to-version.js +3 -3
- package/lib/tools/registry.d.ts +5 -5
- package/lib/tools/registry.js +5 -5
- package/package.json +3 -3
- package/samples/demo-server/README.md +7 -6
- package/skill/SKILL.builder.md +356 -189
- package/skill/SKILL.common.md +400 -97
- package/skill/SKILL.old.md +340 -155
- package/skill/builder-mode.md +60 -17
- package/skill/hash-validation.md +170 -0
- package/skill/interception.md +236 -0
- package/skill/js-sdk-integration.md +13 -7
- package/skill/old-mode.md +62 -21
- package/skill/user-script.md +61 -0
- package/src/authoring/backend.ts +5 -4
- package/src/authoring/tools.ts +31 -30
- package/src/cdp/capture-engine.ts +8 -7
- package/src/cdp/claims.ts +153 -0
- package/src/cdp/container.ts +593 -0
- package/src/cdp/geo.ts +52 -0
- package/src/cdp/index.ts +1 -0
- package/src/consts.ts +1 -0
- package/src/live/cloudflared.ts +348 -0
- package/src/live/tunnel.ts +208 -0
- package/src/mcp/api/operations.gen.ts +34 -26
- package/src/mcp/api/to-zod.ts +1 -1
- package/src/mcp/api/tools.ts +1 -1
- package/src/mcp/instructions.ts +122 -48
- package/src/mcp/start.ts +69 -5
- package/src/mcp/tools/agent/analyze.ts +5 -1
- package/src/mcp/tools/agent/attach.ts +367 -26
- package/src/mcp/tools/agent/build.ts +35 -16
- package/src/mcp/tools/agent/capture.ts +5 -1
- package/src/mcp/tools/agent/eval.ts +4 -1
- package/src/mcp/tools/agent/find.ts +6 -5
- package/src/mcp/tools/agent/how-it-works.ts +62 -21
- package/src/mcp/tools/agent/index.ts +6 -2
- package/src/mcp/tools/agent/live-view.ts +182 -0
- package/src/mcp/tools/agent/pod-proof.ts +90 -0
- package/src/mcp/tools/agent/propose.ts +4 -2
- package/src/mcp/tools/agent/publish.ts +52 -77
- package/src/mcp/tools/agent/session.ts +33 -6
- package/src/mcp/tools/authenticate/authenticate.ts +28 -13
- package/src/mcp/tools/mode/mode.ts +10 -22
- package/src/old/client.ts +2 -2
- package/src/old/instructions.ts +4 -10
- package/src/old/login-cdp.ts +50 -6
- package/src/old/to-register.ts +3 -2
- package/src/old/tools/authenticate.ts +9 -1
- package/src/old/tools/get-provider-info.ts +6 -5
- package/src/old/tools/issue-app-credentials.ts +3 -2
- package/src/old/tools/publish.ts +95 -131
- package/src/old/tools/update-provider-metadata.ts +1 -1
- package/src/old/tools/verify-sdk-setup.ts +1 -1
- package/src/proof/attestor.ts +3 -3
- package/src/proof/debug-response-dump.ts +4 -4
- package/src/proof/index.ts +8 -0
- package/src/proof/page-replay.ts +1 -1
- package/src/proof/pod-prover.ts +231 -0
- package/src/provider/dom-xpath.ts +11 -9
- package/src/provider/draft.ts +13 -10
- package/src/provider/injection-doc.ts +37 -24
- package/src/provider/match-redaction-warning.ts +3 -3
- package/src/provider/matcher.ts +13 -12
- package/src/provider/secrets.ts +3 -3
- package/src/provider/to-version.ts +11 -9
- package/src/tools/registry.ts +5 -5
- 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
|
|
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,14 @@ This package **is** the Reclaim MCP server (bin: `reclaim-mcp-server`). It depen
|
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
|
-
**Prerequisites:**
|
|
11
|
+
**Prerequisites:**
|
|
12
12
|
|
|
13
|
-
|
|
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
|
+
- **Docker** — only for `attach_browser`'s `container` mode, which runs the Reclaim browser runtime (the same image the hosted browser uses) and gives you a link you can send to whoever holds the account. It is the free way to get that link; the hosted remote browser is the other, and it is chargeable. Not needed if you are doing the work yourself. The image is amd64, so Apple Silicon runs it translated. OrbStack handles that itself; Docker Desktop needs Rosetta enabled. `RECLAIM_BROWSER_RUNTIME_IMAGE` overrides the pinned digest.
|
|
16
|
+
- **`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.
|
|
17
|
+
|
|
18
|
+
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
19
|
|
|
15
20
|
It runs as a **stdio MCP server**. Point any MCP-capable agent at this command:
|
|
16
21
|
|
|
@@ -48,24 +53,24 @@ See [Backends](#backends) for builder mode (`USE_OLD_DEVTOOLS=false`) and the fu
|
|
|
48
53
|
|
|
49
54
|
### Provider-authoring guide
|
|
50
55
|
|
|
51
|
-
The MCP server is all you need — it ships a `how_it_works` tool that
|
|
56
|
+
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
57
|
|
|
53
58
|
## Using it from your agent
|
|
54
59
|
|
|
55
60
|
Tip: have your agent call the `how_it_works` tool first so it follows the full authoring flow.
|
|
56
61
|
|
|
57
|
-
Then
|
|
62
|
+
Then ask in plain language. For example:
|
|
58
63
|
|
|
59
64
|
> "Use Reclaim to create a provider that proves my GitHub follower count."
|
|
60
65
|
|
|
61
|
-
|
|
66
|
+
Or more loosely:
|
|
62
67
|
|
|
63
68
|
> "I want to verify Github follow count"
|
|
64
69
|
|
|
65
70
|
The agent orchestrates the tools for you:
|
|
66
71
|
|
|
67
72
|
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
|
|
73
|
+
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
74
|
3. **`start_capture` → `find_requests_containing` → `get_request`** — records network traffic and locates the request carrying your value.
|
|
70
75
|
4. **`propose_provider`** — drafts the provider (a `contains` match + `jsonPath`/`xPath`/`regex` redaction), keeping any secret headers server-side.
|
|
71
76
|
5. **`replay_request` → `run_proof`** — pre-flights the draft, then produces a verified zkTLS proof via the attestor.
|
|
@@ -75,7 +80,7 @@ Your only manual steps are signing in when the browser windows open and confirmi
|
|
|
75
80
|
|
|
76
81
|
## How it works (guide tool)
|
|
77
82
|
|
|
78
|
-
The
|
|
83
|
+
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
84
|
|
|
80
85
|
## Provider-authoring tools
|
|
81
86
|
|
|
@@ -83,20 +88,20 @@ These capture/synthesis/proof tools are backend-independent and registered in bo
|
|
|
83
88
|
|
|
84
89
|
| Tool | Purpose |
|
|
85
90
|
| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
86
|
-
| `attach_browser` | Connect
|
|
87
|
-
| `attach_chrome` | Backward-compatible alias for `attach_browser` |
|
|
91
|
+
| `attach_browser` | Connect to a browser. Grouped by use case: working alone → local Chrome (launched, or the one already open); someone else has to sign in → the Reclaim runtime in Docker (free) or a hosted remote browser (chargeable). The tool description asks which case applies before listing anything, so an agent can put the choice to the developer and connect in one call |
|
|
88
92
|
| `list_tabs` | List open Chrome tabs |
|
|
89
93
|
| `navigate`, `wait_for_page`, `eval_in_page` | Drive the tab; poll for login completion |
|
|
90
94
|
| `start_capture`, `stop_capture` | Network capture lifecycle |
|
|
91
95
|
| `list_requests`, `get_request` | Inspect captured traffic |
|
|
92
96
|
| `find_requests_containing` | Deterministic search for a target value across captured responses |
|
|
93
|
-
| `analyze_request_constraints` | Flag concerns
|
|
97
|
+
| `analyze_request_constraints` | Flag concerns: signed request, CSRF dependency, bot challenge, short-lived token, GraphQL |
|
|
94
98
|
| `propose_provider` | Synthesize a provider — `contains` match + `jsonPath`/`xPath`/`regex` redaction |
|
|
95
99
|
| `replay_request` | Pre-flight replay via Node `fetch` (no attestor); also used for auth-bound check by omitting `secretParamsRef` |
|
|
96
100
|
| `run_proof` | End-to-end zkTLS proof through the attestor SDK |
|
|
97
101
|
| `create_provider_version_from_capture` | Publish a drafted provider to the backend |
|
|
98
102
|
| `authenticate_builder` | In old-devtools mode only, sign in to Builder for remote-browser allocation |
|
|
99
103
|
| `list_builder_organizations` | In old-devtools mode only, list organization ids for a quota-accounted Builder browser |
|
|
104
|
+
| `share_browser_view`, `stop_browser_view` | Put the `container` browser's own live view behind a public HTTPS URL (cloudflared quick tunnel), so someone elsewhere can watch it and act in it — from a phone if they like. Container mode only: a `builder` browser already returns a hosted `liveViewUrl`, and a plain local browser has no view to share |
|
|
100
105
|
| `dispose_browser` | Stop captures, disconnect CDP, and immediately release a Builder-hosted browser |
|
|
101
106
|
| `get_session`, `reset_session` | Session state |
|
|
102
107
|
|
|
@@ -114,7 +119,7 @@ Relevant env vars: `RECLAIM_OLD_API_URL` (default `https://devapi.reclaimprotoco
|
|
|
114
119
|
|
|
115
120
|
## Local development (from source)
|
|
116
121
|
|
|
117
|
-
For working on the package inside the monorepo (consumers should use [Install](#install) instead). A workspace install
|
|
122
|
+
For working on the package inside the monorepo (consumers should use [Install](#install) instead). A workspace install only fetches dependencies:
|
|
118
123
|
|
|
119
124
|
```bash
|
|
120
125
|
npm install
|
|
@@ -128,14 +133,18 @@ npm run download:zk-files --workspace=packages/agent
|
|
|
128
133
|
|
|
129
134
|
## Platform notes
|
|
130
135
|
|
|
131
|
-
- **Node
|
|
136
|
+
- **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.
|
|
137
|
+
|
|
138
|
+
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.
|
|
139
|
+
|
|
140
|
+
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
141
|
- **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
142
|
|
|
134
143
|
## Tests
|
|
135
144
|
|
|
136
|
-
`npm test` runs the unit
|
|
145
|
+
`npm test` runs the unit and integration suite. Chrome isn't needed.
|
|
137
146
|
|
|
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
|
|
147
|
+
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
148
|
|
|
140
149
|
```bash
|
|
141
150
|
# bash / macOS / Linux
|
|
@@ -145,11 +154,11 @@ RECLAIM_AGENT_E2E=1 npm test --workspace=packages/agent
|
|
|
145
154
|
$env:RECLAIM_AGENT_E2E=1; npm test --workspace=packages/agent
|
|
146
155
|
```
|
|
147
156
|
|
|
148
|
-
|
|
157
|
+
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
158
|
|
|
150
|
-
##
|
|
159
|
+
## Disable the browser tools
|
|
151
160
|
|
|
152
|
-
The MCP server registers the provider-authoring (Chrome
|
|
161
|
+
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
162
|
|
|
154
163
|
```bash
|
|
155
164
|
RECLAIM_AGENT_DISABLED=1
|
|
@@ -203,8 +212,35 @@ claude mcp add reclaim -- node "$(pwd)/packages/agent/lib/mcp-bin.js"
|
|
|
203
212
|
}
|
|
204
213
|
```
|
|
205
214
|
|
|
206
|
-
To target a specific backend while developing, add an `env` block (Claude Code / `.mcp.json`) or `environment` (opencode) —
|
|
215
|
+
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
216
|
|
|
208
217
|
### Authoring guide
|
|
209
218
|
|
|
210
|
-
|
|
219
|
+
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.
|
|
220
|
+
|
|
221
|
+
| file | what it is |
|
|
222
|
+
| --- | --- |
|
|
223
|
+
| `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. |
|
|
224
|
+
| `SKILL.builder.md`, `SKILL.old.md` | The authoring guide per backend, split into `<!-- topic: name -->` blocks. Each block is one `how_it_works` topic. |
|
|
225
|
+
| `SKILL.common.md` | Prose shared by both guides, in `<!-- section: name -->` blocks. A guide splices one in with `<!-- common: name -->`. |
|
|
226
|
+
| `user-script.md`, `interception.md`, `hash-validation.md` | Backend-independent topics, served as-is to both modes. |
|
|
227
|
+
| `js-sdk-integration.md` | Served by the old-devtools `get_js_sdk_integration_guide` tool, not by `how_it_works`. |
|
|
228
|
+
|
|
229
|
+
Two mechanics to know when editing:
|
|
230
|
+
|
|
231
|
+
- `%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.
|
|
232
|
+
- 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.
|
|
233
|
+
|
|
234
|
+
**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.
|
|
235
|
+
|
|
236
|
+
### Writing conventions for `skill/` and tool descriptions
|
|
237
|
+
|
|
238
|
+
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):
|
|
239
|
+
|
|
240
|
+
- **Assume the model is smart.** Only write what it can't infer. Every token in a topic competes with the task.
|
|
241
|
+
- **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.
|
|
242
|
+
- **One term per concept.** "provider", not "verification" or "recipe". "developer", not "dev", "user", or "operator". "user script", not "injection".
|
|
243
|
+
- **Reserve capitals for hard rules.** NEVER, MUST, ONLY, and NOT earn all caps; use bold for ordinary emphasis.
|
|
244
|
+
- **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.".
|
|
245
|
+
- **Every tool description says what the tool does and when to call it**, plus any constraint the model can't discover by calling it.
|
|
246
|
+
- **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
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
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
|
-
*
|
|
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
|
*/
|
package/lib/authoring/tools.js
CHANGED
|
@@ -80,7 +80,9 @@ export function authoringTools(resolveBackend) {
|
|
|
80
80
|
return [
|
|
81
81
|
defineTool({
|
|
82
82
|
name: 'navigate',
|
|
83
|
-
description: 'Navigate the
|
|
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
|
-
+ '
|
|
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
|
|
108
|
-
+ 'elapses
|
|
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
|
|
126
|
-
+ '
|
|
127
|
-
+ '
|
|
128
|
-
+ '
|
|
129
|
-
+ '
|
|
130
|
-
+ '
|
|
131
|
-
+ '
|
|
132
|
-
+ '
|
|
133
|
-
+
|
|
134
|
-
+ '
|
|
135
|
-
+ '`navigateUrl`
|
|
136
|
-
+ '
|
|
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 (
|
|
181
|
-
+ 'window.__reclaimUserScriptResult via
|
|
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 (
|
|
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 (
|
|
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
|
|
@@ -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 (
|
|
60
|
-
* MIME at response time. `text/html`, `application/json`, and
|
|
61
|
-
* are NOT here — those are the document/API shapes recipes
|
|
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 (
|
|
169
|
-
// or never seen. Discard any buffered response extra-info so a
|
|
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 (
|
|
229
|
+
// some content-types (for example, websocket). Skip.
|
|
229
230
|
}
|
|
230
231
|
}
|
|
231
232
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Whether a process is still running. Signal 0 checks without delivering. */
|
|
2
|
+
export declare function processAlive(pid: number): boolean;
|
|
3
|
+
/** Slots with at least one live claimant, so a caller can avoid them. */
|
|
4
|
+
export declare function busySlots(): Set<string>;
|
|
5
|
+
/**
|
|
6
|
+
* Slots THIS process already holds.
|
|
7
|
+
*
|
|
8
|
+
* Its own claim must not push it onto a new slot: re-attaching in one session
|
|
9
|
+
* should come back to the browser it is already running, not start a second.
|
|
10
|
+
*/
|
|
11
|
+
export declare function ownSlots(): Set<string>;
|
|
12
|
+
/** Take a claim on `slot`. Returns the token to release it with. */
|
|
13
|
+
export declare function claim(slot: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Give up this process's claim.
|
|
16
|
+
*
|
|
17
|
+
* Returns true when nobody is left, which is the caller's signal to stop the
|
|
18
|
+
* container. Returns false while another live session still holds it — stopping
|
|
19
|
+
* then would pull the browser out from under them.
|
|
20
|
+
*/
|
|
21
|
+
export declare function release(slot: string, token: string): boolean;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which sessions are using which browser container.
|
|
3
|
+
*
|
|
4
|
+
* A container is worth sharing: about 2 GB of RAM and 7 to 17 seconds to start,
|
|
5
|
+
* so a developer running one session after another should get the same one back
|
|
6
|
+
* rather than pay for a new one each time.
|
|
7
|
+
*
|
|
8
|
+
* It can only be shared SEQUENTIALLY, and the profile is why. Every container
|
|
9
|
+
* mounts a host directory as Chromium's `user-data-dir`, and the runtime
|
|
10
|
+
* deletes `SingletonLock` on boot — so a second container on the same profile
|
|
11
|
+
* is a second writer on one cookie jar. Chromium does not support that, and the
|
|
12
|
+
* failure is silent corruption of the thing most worth keeping: the login.
|
|
13
|
+
*
|
|
14
|
+
* So each container gets a SLOT. A slot has one profile and at most one live
|
|
15
|
+
* claimant. A session adopts the first slot nobody holds; when the last claim
|
|
16
|
+
* on a slot goes, the container stops.
|
|
17
|
+
*
|
|
18
|
+
* State lives in a file because each MCP session is its own OS process, with
|
|
19
|
+
* no shared memory to count in. Claims are process ids, and liveness is a
|
|
20
|
+
* signal-0 check — the same test the orphan reaper uses. It fails the harmless
|
|
21
|
+
* way: a recycled pid keeps a container alive rather than pulling one out from
|
|
22
|
+
* under a running session.
|
|
23
|
+
*/
|
|
24
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
25
|
+
import { dirname, join } from 'node:path';
|
|
26
|
+
import { LOGGER } from "../logger.js";
|
|
27
|
+
import { reclaimHomeDirPath } from "../paths.js";
|
|
28
|
+
/** Distinguishes claims made by this same process. */
|
|
29
|
+
let claimSeq = 0;
|
|
30
|
+
/** The pid a token belongs to, or 0 when it is malformed. */
|
|
31
|
+
function tokenPid(token) {
|
|
32
|
+
const pid = Number(token.split('.')[0]);
|
|
33
|
+
return Number.isInteger(pid) ? pid : 0;
|
|
34
|
+
}
|
|
35
|
+
function claimsPath() {
|
|
36
|
+
return join(reclaimHomeDirPath(), 'browser-runtime-claims.json');
|
|
37
|
+
}
|
|
38
|
+
function read() {
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(readFileSync(claimsPath(), 'utf8'));
|
|
41
|
+
// Drop claims whose process is gone: that is what makes a slot free again
|
|
42
|
+
// after a session was killed without cleaning up.
|
|
43
|
+
const live = {};
|
|
44
|
+
for (const [slot, tokens] of Object.entries(parsed)) {
|
|
45
|
+
const alive = (Array.isArray(tokens) ? tokens : [])
|
|
46
|
+
.filter((token) => processAlive(tokenPid(token)));
|
|
47
|
+
if (alive.length) {
|
|
48
|
+
live[slot] = alive;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return live;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Missing or unreadable: nobody has claimed anything.
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function write(claims) {
|
|
59
|
+
const path = claimsPath();
|
|
60
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
61
|
+
// Write then rename, so a reader never sees a half-written file. Two sessions
|
|
62
|
+
// racing here means last-writer-wins on a file that is pruned on every read,
|
|
63
|
+
// which costs at most one redundant container.
|
|
64
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
65
|
+
writeFileSync(temp, `${JSON.stringify(claims, null, '\t')}\n`);
|
|
66
|
+
renameSync(temp, path);
|
|
67
|
+
}
|
|
68
|
+
/** Whether a process is still running. Signal 0 checks without delivering. */
|
|
69
|
+
export function processAlive(pid) {
|
|
70
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
process.kill(pid, 0);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
// EPERM means it exists but belongs to another user — alive either way.
|
|
79
|
+
return err.code === 'EPERM';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Slots with at least one live claimant, so a caller can avoid them. */
|
|
83
|
+
export function busySlots() {
|
|
84
|
+
return new Set(Object.keys(read()));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Slots THIS process already holds.
|
|
88
|
+
*
|
|
89
|
+
* Its own claim must not push it onto a new slot: re-attaching in one session
|
|
90
|
+
* should come back to the browser it is already running, not start a second.
|
|
91
|
+
*/
|
|
92
|
+
export function ownSlots() {
|
|
93
|
+
const mine = new Set();
|
|
94
|
+
for (const [slot, tokens] of Object.entries(read())) {
|
|
95
|
+
if (tokens.some((token) => tokenPid(token) === process.pid)) {
|
|
96
|
+
mine.add(slot);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return mine;
|
|
100
|
+
}
|
|
101
|
+
/** Take a claim on `slot`. Returns the token to release it with. */
|
|
102
|
+
export function claim(slot) {
|
|
103
|
+
const token = `${process.pid}.${++claimSeq}`;
|
|
104
|
+
const claims = read();
|
|
105
|
+
claims[slot] = [...claims[slot] ?? [], token];
|
|
106
|
+
write(claims);
|
|
107
|
+
LOGGER.debug({ slot, held: claims[slot] }, 'claimed a browser slot');
|
|
108
|
+
return token;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Give up this process's claim.
|
|
112
|
+
*
|
|
113
|
+
* Returns true when nobody is left, which is the caller's signal to stop the
|
|
114
|
+
* container. Returns false while another live session still holds it — stopping
|
|
115
|
+
* then would pull the browser out from under them.
|
|
116
|
+
*/
|
|
117
|
+
export function release(slot, token) {
|
|
118
|
+
const claims = read();
|
|
119
|
+
const remaining = (claims[slot] ?? []).filter((held) => held !== token);
|
|
120
|
+
if (remaining.length) {
|
|
121
|
+
claims[slot] = remaining;
|
|
122
|
+
write(claims);
|
|
123
|
+
LOGGER.debug({ slot, remaining }, 'released a claim; others still hold it');
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
delete claims[slot];
|
|
127
|
+
write(claims);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pinned runtime image.
|
|
3
|
+
*
|
|
4
|
+
* A digest, not a tag. Upstream publishes 40-char git SHAs plus moving
|
|
5
|
+
* `main`/`latest` aliases and no semver at all, and its own docs say to
|
|
6
|
+
* "prefer immutable digests". Pinning one means an agent release cannot have
|
|
7
|
+
* the browser changed underneath it.
|
|
8
|
+
*
|
|
9
|
+
* Digest of `latest` as of 2026-08-22, built from popcorn-oss
|
|
10
|
+
* `4df751c25c7b329699033957b93f6486837e4acb`.
|
|
11
|
+
*/
|
|
12
|
+
export declare const BROWSER_RUNTIME_IMAGE: string;
|
|
13
|
+
export interface ContainerBrowser {
|
|
14
|
+
/** Local viewer origin — what `share_browser_view` tunnels. */
|
|
15
|
+
viewerUrl: string;
|
|
16
|
+
/** Browser-level CDP websocket, for `connectCdpUrl`. */
|
|
17
|
+
cdpUrl: string;
|
|
18
|
+
/** Loopback port the viewer is published on. */
|
|
19
|
+
viewerPort: number;
|
|
20
|
+
/** Docker's name for this container. */
|
|
21
|
+
name: string;
|
|
22
|
+
/** Whether the image had to be pulled first — worth telling the developer. */
|
|
23
|
+
pulled: boolean;
|
|
24
|
+
/** Which slot this browser occupies, for diagnostics. */
|
|
25
|
+
slot: number;
|
|
26
|
+
stop: () => Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export interface ContainerOptions {
|
|
29
|
+
/** Loopback port for the viewer. Allocated when omitted. */
|
|
30
|
+
viewerPort?: number;
|
|
31
|
+
/** Loopback port for full CDP. Allocated when omitted. */
|
|
32
|
+
cdpPort?: number;
|
|
33
|
+
/** Override the pinned image, e.g. a local `build.sh` output. */
|
|
34
|
+
image?: string;
|
|
35
|
+
/** Framebuffer size, which is also the browser window's size. */
|
|
36
|
+
width?: number;
|
|
37
|
+
height?: number;
|
|
38
|
+
/** Page to open on start. */
|
|
39
|
+
url?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Where the container's Chromium profile lives on the host.
|
|
43
|
+
*
|
|
44
|
+
* Mounted, unlike the reference dev script, for two reasons. Provider authoring
|
|
45
|
+
* means signing in once and capturing an authenticated request, which a profile
|
|
46
|
+
* that dies with the container makes impossible. And the profile holds the
|
|
47
|
+
* persisted fingerprint identity — canvas/audio seed, timezone, locale — which
|
|
48
|
+
* upstream warns must stay stable, since "flipping any of them against the same
|
|
49
|
+
* cookie jar is itself a bot signal".
|
|
50
|
+
*/
|
|
51
|
+
export declare function containerProfileDir(slot?: number): string;
|
|
52
|
+
/** Docker CLI present AND its daemon reachable. */
|
|
53
|
+
export declare function dockerAvailable(): Promise<boolean>;
|
|
54
|
+
/** Message for the case we cannot fix for them. */
|
|
55
|
+
export declare function dockerMissingMessage(): string;
|
|
56
|
+
/**
|
|
57
|
+
* The `docker run` argv.
|
|
58
|
+
*
|
|
59
|
+
* A pure function because every flag here is load bearing and none of it is
|
|
60
|
+
* obvious from the outside: which ports are loopback-only, that the image is
|
|
61
|
+
* digest-pinned, that the profile is mounted. Tests assert on this directly
|
|
62
|
+
* rather than trying to observe a running container.
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildRunArgs(opts: ContainerOptions & {
|
|
65
|
+
viewerPort: number;
|
|
66
|
+
cdpPort: number;
|
|
67
|
+
}, image: string, name: string, profileDir: string): string[];
|
|
68
|
+
/**
|
|
69
|
+
* Start a browser container and wait until it is actually serving.
|
|
70
|
+
*
|
|
71
|
+
* Ports are allocated per call, so concurrent sessions never land on the same
|
|
72
|
+
* container. Pass `viewerPort` to pin one — re-attaching with the same port
|
|
73
|
+
* reuses the container that is already there.
|
|
74
|
+
*/
|
|
75
|
+
export declare function startContainer(options?: ContainerOptions): Promise<ContainerBrowser>;
|
|
76
|
+
/**
|
|
77
|
+
* Remove containers whose owning process is gone.
|
|
78
|
+
*
|
|
79
|
+
* Shutdown cleanup covers an orderly exit, but a SIGKILL or a crash runs
|
|
80
|
+
* nothing — and a 1.3 GB container with a live browser in it should not outlive
|
|
81
|
+
* the session that made it. Reaping at startup catches those, and the owner-PID
|
|
82
|
+
* label keeps it safe: a container whose owner still runs belongs to another
|
|
83
|
+
* session and is left alone.
|
|
84
|
+
*/
|
|
85
|
+
export declare function reapOrphanedContainers(): Promise<string[]>;
|