@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.
- package/README.md +55 -20
- package/lib/authoring/backend.d.ts +5 -4
- package/lib/authoring/tools.js +30 -30
- package/lib/cdp/browser-proxy.d.ts +78 -0
- package/lib/cdp/browser-proxy.js +186 -0
- package/lib/cdp/capture-engine.js +8 -7
- package/lib/cdp/client.d.ts +10 -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/page.d.ts +20 -0
- package/lib/live/page.js +134 -0
- package/lib/live/tunnel.d.ts +23 -0
- package/lib/live/tunnel.js +120 -0
- package/lib/live/viewer.d.ts +28 -0
- package/lib/live/viewer.js +232 -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 +101 -38
- package/lib/mcp/start.js +4 -3
- package/lib/mcp/tools/agent/analyze.js +5 -1
- package/lib/mcp/tools/agent/attach.d.ts +8 -1
- package/lib/mcp/tools/agent/attach.js +166 -22
- package/lib/mcp/tools/agent/build.d.ts +13 -8
- package/lib/mcp/tools/agent/build.js +22 -11
- 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/live-view.d.ts +21 -0
- package/lib/mcp/tools/agent/live-view.js +153 -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/proxy.d.ts +3 -0
- package/lib/mcp/tools/agent/proxy.js +199 -0
- package/lib/mcp/tools/agent/publish.js +47 -77
- package/lib/mcp/tools/agent/session.d.ts +2 -1
- package/lib/mcp/tools/agent/session.js +26 -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 +323 -97
- package/skill/SKILL.old.md +340 -155
- package/skill/builder-mode.md +58 -17
- package/skill/hash-validation.md +170 -0
- package/skill/interception.md +235 -0
- package/skill/js-sdk-integration.md +13 -7
- package/skill/old-mode.md +60 -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/client.ts +8 -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/page.ts +145 -0
- package/src/live/tunnel.ts +157 -0
- package/src/live/viewer.ts +293 -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 +121 -48
- package/src/mcp/start.ts +4 -3
- package/src/mcp/tools/agent/analyze.ts +5 -1
- package/src/mcp/tools/agent/attach.ts +194 -22
- package/src/mcp/tools/agent/build.ts +26 -15
- 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/live-view.ts +198 -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 +26 -5
- 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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ALLOWED_REQUESTS_HINT, USER_SCRIPT_HINT, } from "../../../provider/injection-doc.js";
|
|
2
2
|
import { countsFor, matchRedactionWarnings, } from "../../../provider/match-redaction-warning.js";
|
|
3
3
|
import { nextVersion, providerToCreateVersionRequest, } from "../../../provider/to-version.js";
|
|
4
4
|
import { defineTool } from "../../server.js";
|
|
@@ -62,7 +62,8 @@ const REQUEST_DRAFT_PROPERTIES = {
|
|
|
62
62
|
const PROVIDER_SCHEMA = {
|
|
63
63
|
type: 'object',
|
|
64
64
|
description: 'A `provider` object from propose_provider, passed through verbatim — ' +
|
|
65
|
-
'the tool builds the version body (the requests[] entry
|
|
65
|
+
'the tool builds the version body (the requests[] entry and so on) ' +
|
|
66
|
+
'for you.',
|
|
66
67
|
properties: {
|
|
67
68
|
name: { type: 'string', description: 'Verification name.' },
|
|
68
69
|
...REQUEST_DRAFT_PROPERTIES,
|
|
@@ -73,7 +74,7 @@ const PROVIDER_SCHEMA = {
|
|
|
73
74
|
},
|
|
74
75
|
paramValues: {
|
|
75
76
|
type: 'object',
|
|
76
|
-
description: 'Real values for the {{param}} placeholders,
|
|
77
|
+
description: 'Real values for the {{param}} placeholders, for example, ' +
|
|
77
78
|
'{ "username": "Syed" }.',
|
|
78
79
|
},
|
|
79
80
|
},
|
|
@@ -87,8 +88,8 @@ const ALLOWED_JS_REQUEST_SCHEMA = {
|
|
|
87
88
|
...REQUEST_DRAFT_PROPERTIES,
|
|
88
89
|
multiple: {
|
|
89
90
|
type: 'boolean',
|
|
90
|
-
description: 'Allow the template to match many requests (
|
|
91
|
-
'Default true.',
|
|
91
|
+
description: 'Allow the template to match many requests (for example, ' +
|
|
92
|
+
'pagination). Default true.',
|
|
92
93
|
},
|
|
93
94
|
required: {
|
|
94
95
|
type: 'boolean',
|
|
@@ -100,36 +101,21 @@ const ALLOWED_JS_REQUEST_SCHEMA = {
|
|
|
100
101
|
export function createVersionFromCaptureTool(client, attachRef) {
|
|
101
102
|
return defineTool({
|
|
102
103
|
name: 'create_provider_version_from_capture',
|
|
103
|
-
description: 'Persist drafted ReclaimProvider(s) as a provider version
|
|
104
|
-
'
|
|
105
|
-
'
|
|
106
|
-
'
|
|
107
|
-
'
|
|
108
|
-
'
|
|
109
|
-
'
|
|
110
|
-
'
|
|
111
|
-
'
|
|
112
|
-
'
|
|
113
|
-
'page-injection script that drives the page to where the request ' +
|
|
114
|
-
'fires) and/or `allowedJsRequests` (extra requests the script may ' +
|
|
115
|
-
'fire, e.g. pagination). ' +
|
|
116
|
-
'Omit `version` to have the tool auto-select the next available ' +
|
|
117
|
-
'version (it lists existing versions and bumps past the highest). ' +
|
|
118
|
-
'The version lands on the draft branch — to go live, call ' +
|
|
119
|
-
'`publish_provider_version` (PRIVATE) or ' +
|
|
120
|
-
'`submit_provider_version_for_review` (PUBLIC) afterwards. ' +
|
|
104
|
+
description: 'Persist drafted ReclaimProvider(s) as a provider version, after ' +
|
|
105
|
+
'run_proof succeeds. Pass the `provider` object(s) from ' +
|
|
106
|
+
'propose_provider VERBATIM — this tool builds the version body ' +
|
|
107
|
+
'(requests[], webSettings, …) for you; do NOT hand-construct that ' +
|
|
108
|
+
'shape or call create_provider_version directly. Exactly one of ' +
|
|
109
|
+
'`provider` (one request) or `providers` (a multi-request provider — ' +
|
|
110
|
+
'every entry is required at verification time). Omit `version` to ' +
|
|
111
|
+
'auto-bump past the highest existing one. The version lands on the ' +
|
|
112
|
+
'draft branch: call `publish_provider_version` (PRIVATE) or ' +
|
|
113
|
+
'`submit_provider_version_for_review` (PUBLIC) to go live. ' +
|
|
121
114
|
'`requiredContext` is derived from any {{context.<name>}} ' +
|
|
122
|
-
'placeholders
|
|
123
|
-
'
|
|
124
|
-
'
|
|
125
|
-
'
|
|
126
|
-
'field is a 400 at session create). ' +
|
|
127
|
-
'Refine types/descriptions via `update_provider_version` while ' +
|
|
128
|
-
'still on draft. ' +
|
|
129
|
-
'On success, if a browser is still attached, the response\'s ' +
|
|
130
|
-
'`_notes` reminds you to confirm with the developer before ' +
|
|
131
|
-
'calling `dispose_browser` — never close it unprompted, they ' +
|
|
132
|
-
'may want it for further edits.',
|
|
115
|
+
'placeholders and returned on the created version — relay it to the ' +
|
|
116
|
+
'dev verbatim, since a session missing those fields is a 400. Always ' +
|
|
117
|
+
'read the response\'s `_notes`. ' +
|
|
118
|
+
'Full walkthrough: how_it_works({ topic: "publish" }).',
|
|
133
119
|
inputSchema: {
|
|
134
120
|
type: 'object',
|
|
135
121
|
properties: {
|
|
@@ -149,12 +135,14 @@ export function createVersionFromCaptureTool(client, attachRef) {
|
|
|
149
135
|
initialUrl: {
|
|
150
136
|
type: 'string',
|
|
151
137
|
description: 'The website URL the user landed on to trigger the request ' +
|
|
152
|
-
'(
|
|
153
|
-
'flow opens for end-users — NOT the API URL
|
|
138
|
+
'(for example, https://github.com/Syed). The URL the ' +
|
|
139
|
+
'verification flow opens for end-users — NOT the API URL ' +
|
|
140
|
+
'inside the provider.',
|
|
154
141
|
},
|
|
155
142
|
notes: {
|
|
156
143
|
type: 'string',
|
|
157
|
-
description: 'Short change note (
|
|
144
|
+
description: 'Short change note (for example, "Initial draft from ' +
|
|
145
|
+
'browser capture").',
|
|
158
146
|
},
|
|
159
147
|
version: {
|
|
160
148
|
type: 'object',
|
|
@@ -169,44 +157,21 @@ export function createVersionFromCaptureTool(client, attachRef) {
|
|
|
169
157
|
},
|
|
170
158
|
jsUserScripts: {
|
|
171
159
|
type: 'string',
|
|
172
|
-
description:
|
|
173
|
-
'
|
|
174
|
-
'
|
|
175
|
-
'
|
|
176
|
-
'
|
|
177
|
-
'
|
|
178
|
-
'verification client installs this to run before every page ' +
|
|
179
|
-
'load (before navigating to initialUrl), matching old-devtool ' +
|
|
180
|
-
'customInjection; window.Reclaim itself is not yet exposed ' +
|
|
181
|
-
'there, so prefer plain browser JS. Guide:\n\n' +
|
|
182
|
-
INJECTION_GUIDE,
|
|
160
|
+
description: USER_SCRIPT_HINT +
|
|
161
|
+
' Stored as webSettings.jsUserScripts; the Builder ' +
|
|
162
|
+
'verification client runs it before every page load ' +
|
|
163
|
+
'(including before navigating to initialUrl), matching ' +
|
|
164
|
+
'old-devtools customInjection. window.Reclaim is not exposed ' +
|
|
165
|
+
'there yet, so prefer plain browser JS.',
|
|
183
166
|
},
|
|
184
167
|
allowedJsRequests: {
|
|
185
168
|
type: 'array',
|
|
186
|
-
description: 'Extra requests the
|
|
187
|
-
'
|
|
188
|
-
'
|
|
189
|
-
'
|
|
190
|
-
'
|
|
191
|
-
|
|
192
|
-
'different field name): this whole entry (url, method, body, ' +
|
|
193
|
-
'responseMatches, responseRedactions) feeds a client-side ' +
|
|
194
|
-
'hash-equality check, and `url` is hashed VERBATIM — no regex/ ' +
|
|
195
|
-
'template resolution, even with `urlType: REGEX` or ' +
|
|
196
|
-
'`TEMPLATE`. If the real captured URL contains ANY per-session ' +
|
|
197
|
-
'part (a tracking nonce, CSRF token, cache-busting timestamp), ' +
|
|
198
|
-
'no declared spec can EVER hash-match a real proof — populating' +
|
|
199
|
-
' this for such a provider makes verifyProof reject every real ' +
|
|
200
|
-
'proof, which is worse than leaving it empty. Diff the same ' +
|
|
201
|
-
'endpoint\'s captured URL across two separate sessions before ' +
|
|
202
|
-
'wiring this up. If provider-hash validation keeps causing ' +
|
|
203
|
-
'problems (unstable url, or any other persistent mismatch), ' +
|
|
204
|
-
'the fallback on the verifying side is `verifyProof(proof, { ' +
|
|
205
|
-
'dangerouslyDisableContentValidation: true })` — trusts only ' +
|
|
206
|
-
'the attestor\'s signature, skips the hash-equality check ' +
|
|
207
|
-
'entirely; see the "Provider-hash validation" section of the ' +
|
|
208
|
-
'injection script\'s guide for the full mechanics and when to ' +
|
|
209
|
-
'reach for it.',
|
|
169
|
+
description: 'Extra requests the user script is allowed to fire (for ' +
|
|
170
|
+
'example, paginated API calls), as ' +
|
|
171
|
+
'RequestSelectionTemplates. ' +
|
|
172
|
+
'`multiple`/`required` default to true. Old-devtools calls the ' +
|
|
173
|
+
'same concept `allowedInjectedRequestData`. ' +
|
|
174
|
+
ALLOWED_REQUESTS_HINT,
|
|
210
175
|
items: ALLOWED_JS_REQUEST_SCHEMA,
|
|
211
176
|
},
|
|
212
177
|
interceptorOptions: {
|
|
@@ -214,11 +179,7 @@ export function createVersionFromCaptureTool(client, attachRef) {
|
|
|
214
179
|
description: 'In-app interception settings, stored as ' +
|
|
215
180
|
'webSettings.clientOptions.inapp.interceptorOptions — only ' +
|
|
216
181
|
'needed for a genuine live-interception provider (a real ' +
|
|
217
|
-
'HAWKEYE/MSWJS script, not the standard capture→replay flow).
|
|
218
|
-
'Document replay (webSettings.clientOptions.inapp' +
|
|
219
|
-
'.interceptorOptions.isDocumentRequestReplayEnabled) is ' +
|
|
220
|
-
'ALWAYS published disabled regardless of this field — there ' +
|
|
221
|
-
'is no way to enable it here.',
|
|
182
|
+
'HAWKEYE/MSWJS script, not the standard capture→replay flow).',
|
|
222
183
|
properties: {
|
|
223
184
|
interceptorType: {
|
|
224
185
|
type: 'string',
|
|
@@ -229,6 +190,15 @@ export function createVersionFromCaptureTool(client, attachRef) {
|
|
|
229
190
|
type: 'string',
|
|
230
191
|
description: 'Free-form JSON config for the chosen interceptor.',
|
|
231
192
|
},
|
|
193
|
+
isDocumentRequestReplayEnabled: {
|
|
194
|
+
type: 'boolean',
|
|
195
|
+
description: 'Whether the page\'s own document/navigation request is ' +
|
|
196
|
+
'genuinely replayed through the interceptor, rather than ' +
|
|
197
|
+
'constructed locally. Defaults to `false` (disabled) when ' +
|
|
198
|
+
'omitted. Only meaningful for HAWKEYE/MSWJS — pass `true` ' +
|
|
199
|
+
'when the target value is embedded in the page\'s own HTML ' +
|
|
200
|
+
'rather than a separate API call. Inert for CDP.',
|
|
201
|
+
},
|
|
232
202
|
},
|
|
233
203
|
required: ['interceptorType'],
|
|
234
204
|
},
|
|
@@ -2,7 +2,8 @@ import type { ConfigStore } from '../../../config/store.ts';
|
|
|
2
2
|
import { type RegisteredTool } from '../../server.ts';
|
|
3
3
|
import { type AttachState } from './attach.ts';
|
|
4
4
|
import { type CaptureStore } from './capture.ts';
|
|
5
|
+
import { type LiveShareRef } from './live-view.ts';
|
|
5
6
|
export declare function sessionTools(attachRef: {
|
|
6
7
|
current?: AttachState;
|
|
7
8
|
config: ConfigStore;
|
|
8
|
-
}, captures: CaptureStore): RegisteredTool[];
|
|
9
|
+
}, captures: CaptureStore, liveShare: LiveShareRef): RegisteredTool[];
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { defineTool } from "../../server.js";
|
|
2
2
|
import { disposeAttached } from "./attach.js";
|
|
3
3
|
import { resetCaptures } from "./capture.js";
|
|
4
|
-
|
|
4
|
+
import { stopLiveShare } from "./live-view.js";
|
|
5
|
+
export function sessionTools(attachRef, captures, liveShare) {
|
|
5
6
|
return [
|
|
6
7
|
defineTool({
|
|
7
8
|
name: 'get_session',
|
|
8
|
-
description: '
|
|
9
|
+
description: 'Report the current attach state, the live captureIds, and the '
|
|
10
|
+
+ 'persisted browser config. Call it to check whether a browser is '
|
|
11
|
+
+ 'still attached before reattaching, or to recover a captureId.',
|
|
9
12
|
inputSchema: { type: 'object', properties: {} },
|
|
10
13
|
}, async () => ({
|
|
11
14
|
config: attachRef.config.read()?.browser_agent,
|
|
@@ -17,29 +20,45 @@ export function sessionTools(attachRef, captures) {
|
|
|
17
20
|
orgId: attachRef.current?.orgId,
|
|
18
21
|
captures: [...captures.sessions.keys()],
|
|
19
22
|
activeTab: captures.activeTab,
|
|
23
|
+
// The public share, when one is open — so a caller that lost the
|
|
24
|
+
// URL can recover it instead of opening a second tunnel.
|
|
25
|
+
...(liveShare.current
|
|
26
|
+
? {
|
|
27
|
+
sharedView: {
|
|
28
|
+
url: liveShare.current.url,
|
|
29
|
+
interactive: liveShare.current.interactive,
|
|
30
|
+
viewers: liveShare.current.view.viewers(),
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
: {}),
|
|
20
34
|
})),
|
|
21
35
|
defineTool({
|
|
22
36
|
name: 'dispose_browser',
|
|
23
37
|
description: 'Stop captures, disconnect the current browser, and immediately '
|
|
24
38
|
+ 'delete a Builder-hosted Popcorn session. Custom CDP sessions '
|
|
25
|
-
+ 'are disconnected but never deleted by Reclaim.'
|
|
39
|
+
+ 'are disconnected but never deleted by Reclaim. Also closes a '
|
|
40
|
+
+ 'public live view opened by share_browser_view.',
|
|
26
41
|
inputSchema: { type: 'object', properties: {} },
|
|
27
42
|
}, async () => {
|
|
28
|
-
await resetAll(attachRef, captures);
|
|
43
|
+
await resetAll(attachRef, captures, liveShare);
|
|
29
44
|
return { disposed: true };
|
|
30
45
|
}),
|
|
31
46
|
defineTool({
|
|
32
47
|
name: 'reset_session',
|
|
33
48
|
description: 'Stop all captures and clear capture state — drops every '
|
|
34
|
-
+ 'session\'s drafts and their in-memory secrets
|
|
49
|
+
+ 'session\'s drafts and their in-memory secrets, and closes a '
|
|
50
|
+
+ 'public live view opened by share_browser_view.',
|
|
35
51
|
inputSchema: { type: 'object', properties: {} },
|
|
36
52
|
}, async () => {
|
|
37
|
-
await resetAll(attachRef, captures);
|
|
53
|
+
await resetAll(attachRef, captures, liveShare);
|
|
38
54
|
return { reset: true };
|
|
39
55
|
}),
|
|
40
56
|
];
|
|
41
57
|
}
|
|
42
|
-
async function resetAll(attachRef, captures) {
|
|
58
|
+
async function resetAll(attachRef, captures, liveShare) {
|
|
59
|
+
// Before the captures: the live view holds a tab session of its own, and a
|
|
60
|
+
// public URL must never outlive the browser it points at.
|
|
61
|
+
await stopLiveShare(liveShare);
|
|
43
62
|
await resetCaptures(captures);
|
|
44
63
|
await disposeAttached(attachRef);
|
|
45
64
|
}
|
|
@@ -9,17 +9,12 @@ export function authenticateTool(client, name = 'reclaim_authenticate') {
|
|
|
9
9
|
return defineTool({
|
|
10
10
|
name,
|
|
11
11
|
description: (name === 'authenticate_builder'
|
|
12
|
-
? '
|
|
13
|
-
+ '
|
|
14
|
-
+ '
|
|
15
|
-
+ '
|
|
16
|
-
+ '
|
|
17
|
-
+ '
|
|
18
|
-
+ '(list_builder_organizations — old-devtools has no generated '
|
|
19
|
-
+ 'org tool of its own). It does NOT authorize '
|
|
20
|
-
+ 'create_provider_version_from_capture or get_me_providers, '
|
|
21
|
-
+ 'which need their OWN, separate login via the old-devtools '
|
|
22
|
-
+ 'reclaim_authenticate tool. Check both with get_devtools_mode. '
|
|
12
|
+
? 'OLD-DEVTOOLS MODE ONLY, and narrow: it authorizes exactly two '
|
|
13
|
+
+ 'Builder-exclusive features — allocating a remote browser '
|
|
14
|
+
+ '(attach_browser mode="builder") and list_builder_organizations. '
|
|
15
|
+
+ 'It does NOT authorize create_provider_version_from_capture or '
|
|
16
|
+
+ 'get_me_providers; those need the separate old-devtools login, '
|
|
17
|
+
+ 'reclaim_authenticate. Check both with get_devtools_mode. '
|
|
23
18
|
: '')
|
|
24
19
|
+ 'Authorize the CLI / MCP to make authenticated API calls. Opens '
|
|
25
20
|
+ 'the '
|
|
@@ -28,8 +23,9 @@ export function authenticateTool(client, name = 'reclaim_authenticate') {
|
|
|
28
23
|
'the user to sign in and approve. On success the access token is ' +
|
|
29
24
|
'cached in ~/.reclaim/config.json and activated for this session.\n\n' +
|
|
30
25
|
'Call with no arguments. This bearer token authenticates ' +
|
|
31
|
-
'dashboard/org-management calls (
|
|
32
|
-
'secret). Programmatic verification calls use a
|
|
26
|
+
'dashboard/org-management calls (for example, list_orgs, or ' +
|
|
27
|
+
'issuing an org secret). Programmatic verification calls use a ' +
|
|
28
|
+
'separate org secret ' +
|
|
33
29
|
'(rorg_…, set as RECLAIM_ORG_SECRET).',
|
|
34
30
|
inputSchema: { type: 'object', properties: {} },
|
|
35
31
|
}, async () => runOAuthLoopback(client));
|
|
@@ -63,6 +59,16 @@ openBrowserFn = openBrowser) {
|
|
|
63
59
|
const codeVerifier = oauth.randomPKCECodeVerifier();
|
|
64
60
|
const codeChallenge = await oauth.calculatePKCECodeChallenge(codeVerifier);
|
|
65
61
|
const state = oauth.randomState();
|
|
62
|
+
// openid-client/oauth4webapi refuse any non-HTTPS issuer by default
|
|
63
|
+
// ("only requests to HTTPS are allowed"). Better Auth's own dev default
|
|
64
|
+
// (`canonicalOrigin()`, packages/app/src/consts.ts) is plain
|
|
65
|
+
// `http://localhost:4001` — the server side has no HTTPS requirement at
|
|
66
|
+
// all — so a local `RECLAIM_API_URL=http://localhost:...` would otherwise
|
|
67
|
+
// always fail here. Loosen it ONLY for loopback hosts, matching what the
|
|
68
|
+
// server already trusts (`authTrustedOrigins()` defaults to
|
|
69
|
+
// localhost:4000/4001 outside production); a non-loopback http:// origin
|
|
70
|
+
// still gets the strict check.
|
|
71
|
+
const isLoopbackAuthBase = ['localhost', '127.0.0.1', '[::1]'].includes(new URL(authBase).hostname);
|
|
66
72
|
// Bind the callback port first: the client is registered for this exact
|
|
67
73
|
// loopback redirect.
|
|
68
74
|
const callback = await listenForCallback();
|
|
@@ -93,6 +99,13 @@ openBrowserFn = openBrowser) {
|
|
|
93
99
|
// `authorizationCodeGrant` fails instead (confirmed by hitting
|
|
94
100
|
// exactly that when this only checked method === 'POST').
|
|
95
101
|
[oauth.customFetch]: _customFetchForOauth,
|
|
102
|
+
// Applies to the `Configuration` this call returns, so the later
|
|
103
|
+
// `buildAuthorizationUrl`/`authorizationCodeGrant` calls below (which
|
|
104
|
+
// reuse that same `config`) stop enforcing HTTPS too — no separate
|
|
105
|
+
// opt-in needed for them.
|
|
106
|
+
...(isLoopbackAuthBase
|
|
107
|
+
? { execute: [oauth.allowInsecureRequests] }
|
|
108
|
+
: {}),
|
|
96
109
|
});
|
|
97
110
|
openBrowserFn(oauth.buildAuthorizationUrl(config, {
|
|
98
111
|
redirect_uri: callback.redirectUri,
|
|
@@ -7,7 +7,7 @@ import { type RegisteredTool } from '../../server.ts';
|
|
|
7
7
|
* `~/.reclaim/config.json` and applied the NEXT time the server starts —
|
|
8
8
|
* the registered tool set, server instructions, and backend client are all
|
|
9
9
|
* fixed at connect time, so the switch requires a restart/reconnect. Follows
|
|
10
|
-
* the `
|
|
10
|
+
* the `attach_browser` persist-through-config pattern.
|
|
11
11
|
*
|
|
12
12
|
* Registered in BOTH modes (so the user can switch either direction) and
|
|
13
13
|
* outside `buildAgentTools` (so it survives `RECLAIM_AGENT_DISABLED=1`). An
|
|
@@ -20,7 +20,7 @@ function hasValidBuilderSession(config) {
|
|
|
20
20
|
* `~/.reclaim/config.json` and applied the NEXT time the server starts —
|
|
21
21
|
* the registered tool set, server instructions, and backend client are all
|
|
22
22
|
* fixed at connect time, so the switch requires a restart/reconnect. Follows
|
|
23
|
-
* the `
|
|
23
|
+
* the `attach_browser` persist-through-config pattern.
|
|
24
24
|
*
|
|
25
25
|
* Registered in BOTH modes (so the user can switch either direction) and
|
|
26
26
|
* outside `buildAgentTools` (so it survives `RECLAIM_AGENT_DISABLED=1`). An
|
|
@@ -61,27 +61,15 @@ export function buildModeTools(config, currentOldMode) {
|
|
|
61
61
|
}),
|
|
62
62
|
defineTool({
|
|
63
63
|
name: 'get_devtools_mode',
|
|
64
|
-
description: 'Report which backend this
|
|
65
|
-
+ '"
|
|
66
|
-
+ '
|
|
67
|
-
+ '
|
|
68
|
-
+ '
|
|
69
|
-
+ '
|
|
70
|
-
+ '
|
|
71
|
-
+ '
|
|
72
|
-
+ '
|
|
73
|
-
+ '(list_orgs) and remote-browser allocation. In OLD-DEVTOOLS '
|
|
74
|
-
+ 'mode it is a SEPARATE, additional login (authenticate_builder) '
|
|
75
|
-
+ 'needed only for two Builder-exclusive features: remote-browser '
|
|
76
|
-
+ '(Popcorn) allocation and org listing (list_builder_organizations '
|
|
77
|
-
+ '— old-devtools has no generated org tool of its own). '
|
|
78
|
-
+ 'oldDevtoolsAuthenticated (the OLD-mode reclaim_authenticate) is '
|
|
79
|
-
+ 'required separately for create_provider_version_from_capture '
|
|
80
|
-
+ 'and get_me_providers, and only exists/matters in old-devtools '
|
|
81
|
-
+ 'mode. Both are presence checks, not '
|
|
82
|
-
+ 'validity checks — a stale old-devtools token still reports '
|
|
83
|
-
+ 'true here; a real publish/list call will surface staleness '
|
|
84
|
-
+ 'with a clear error pointing back at reclaim_authenticate.',
|
|
64
|
+
description: 'Report which backend this server targets ("old" devtools or '
|
|
65
|
+
+ '"builder"), WHY (a USE_OLD_DEVTOOLS env override, the persisted '
|
|
66
|
+
+ 'config, or the default), and which mode is persisted for the '
|
|
67
|
+
+ 'next start. Change it with set_devtools_mode. Also reports auth '
|
|
68
|
+
+ 'status for both login systems: builderAuthenticated and '
|
|
69
|
+
+ 'oldDevtoolsAuthenticated. Both are PRESENCE checks, not validity '
|
|
70
|
+
+ 'checks — a stale token still reports true; a real call surfaces '
|
|
71
|
+
+ 'the staleness with an error naming the tool to re-run. Which '
|
|
72
|
+
+ 'login authorizes what: how_it_works({ topic: "auth" }).',
|
|
85
73
|
inputSchema: { type: 'object', properties: {} },
|
|
86
74
|
}, async () => {
|
|
87
75
|
const persisted = config.read()?.mode;
|
package/lib/old/client.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ export interface RegisterProviderBody {
|
|
|
51
51
|
/** Interception mechanism (`NONE` = standard replay flow, the default;
|
|
52
52
|
* `HAWKEYE`/`MSWJS`/`XHOOK` = JS-context interception; `CDP` = browser-
|
|
53
53
|
* level). Column default is `HAWKEYE` when omitted entirely — unlike
|
|
54
|
-
* `stepsToFollow
|
|
54
|
+
* `stepsToFollow`, and so on. above, whether the register endpoint actually
|
|
55
55
|
* PERSISTS this field is UNVERIFIED (no source access to confirm); sent
|
|
56
56
|
* here on the chance it does, but the publish tool always follows up
|
|
57
57
|
* with a config (add-version) call regardless, which is confirmed to
|
package/lib/old/client.js
CHANGED
|
@@ -209,7 +209,7 @@ _ReclaimOldClient_identity = new WeakMap(), _ReclaimOldClient_fetch = new WeakMa
|
|
|
209
209
|
// An identity ALREADY SET (as opposed to `#requireIdentity`'s "never
|
|
210
210
|
// authenticated" case, which never reaches the backend) plus either a
|
|
211
211
|
// 401/403 OR a message that itself names token verification means the
|
|
212
|
-
// backend rejected a token we had —
|
|
212
|
+
// backend rejected a token we had — that is, it's stale/invalid, not merely
|
|
213
213
|
// absent. The message check matters because this backend does NOT
|
|
214
214
|
// reliably use 401/403 for it — confirmed live: an actually-expired
|
|
215
215
|
// Firebase token surfaced as "Error while verifying token" with a
|
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* MCP server instructions shown to the LLM when running in OLD-devtools mode
|
|
3
|
+
* (`USE_OLD_DEVTOOLS` unset or not `false`). Describes the REDUCED tool
|
|
4
|
+
* surface — there are no org/credential-link/review flows and no generated
|
|
5
|
+
* API tools here. Both modes' instructions load together in
|
|
6
|
+
* `mcp/instructions` (alongside the topic-split authoring guide); re-exported
|
|
7
|
+
* here so old-mode callers keep importing from their own module.
|
|
8
|
+
*/
|
|
9
|
+
export { OLD_MODE_INSTRUCTIONS } from '../mcp/instructions.ts';
|
package/lib/old/instructions.js
CHANGED
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
* MCP server instructions shown to the LLM when running in OLD-devtools mode
|
|
3
3
|
* (`USE_OLD_DEVTOOLS` unset or not `false`). Describes the REDUCED tool
|
|
4
4
|
* surface — there are no org/credential-link/review flows and no generated
|
|
5
|
-
* API tools here.
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
5
|
+
* API tools here. Both modes' instructions load together in
|
|
6
|
+
* `mcp/instructions` (alongside the topic-split authoring guide); re-exported
|
|
7
|
+
* here so old-mode callers keep importing from their own module.
|
|
8
8
|
*/
|
|
9
|
-
|
|
10
|
-
import { fileURLToPath } from 'node:url';
|
|
11
|
-
export const OLD_MODE_INSTRUCTIONS = readFileSync(fileURLToPath(new URL('../../skill/old-mode.md', import.meta.url)), 'utf8').trim();
|
|
9
|
+
export { OLD_MODE_INSTRUCTIONS } from "../mcp/instructions.js";
|
package/lib/old/login-cdp.d.ts
CHANGED
|
@@ -34,8 +34,8 @@ export interface CdpLoginResult {
|
|
|
34
34
|
* finish signing in, read the resulting Firebase ID token from page storage,
|
|
35
35
|
* then clean up. If we had to launch a dedicated Chrome for this, we close the
|
|
36
36
|
* whole browser afterwards; if we attached to a Chrome that was already up
|
|
37
|
-
* (
|
|
38
|
-
* disrupt it.
|
|
37
|
+
* (for example, the capture flow's), we only close the tab we opened so we
|
|
38
|
+
* don't disrupt it.
|
|
39
39
|
*/
|
|
40
40
|
export declare function captureFirebaseTokenViaCdp(opts: {
|
|
41
41
|
loginUrl?: string;
|
package/lib/old/login-cdp.js
CHANGED
|
@@ -83,12 +83,18 @@ export const EXTRACT_TOKEN_JS = `(async () => {
|
|
|
83
83
|
* finish signing in, read the resulting Firebase ID token from page storage,
|
|
84
84
|
* then clean up. If we had to launch a dedicated Chrome for this, we close the
|
|
85
85
|
* whole browser afterwards; if we attached to a Chrome that was already up
|
|
86
|
-
* (
|
|
87
|
-
* disrupt it.
|
|
86
|
+
* (for example, the capture flow's), we only close the tab we opened so we
|
|
87
|
+
* don't disrupt it.
|
|
88
88
|
*/
|
|
89
89
|
export async function captureFirebaseTokenViaCdp(opts) {
|
|
90
90
|
const loginUrl = opts.loginUrl || oldLoginUrl() || DEFAULT_LOGIN_URL;
|
|
91
|
-
|
|
91
|
+
// 6 minutes, not 3. On a FRESH Chrome profile the dashboard is a cold load
|
|
92
|
+
// of an ~8.6 MB uncompressed bundle before the login form is even
|
|
93
|
+
// interactive — tens of seconds on a slow link — and only then does the
|
|
94
|
+
// developer start typing credentials and a 2FA code. The old 3-minute
|
|
95
|
+
// budget was routinely spent before they could finish, and the retry that
|
|
96
|
+
// followed looked like a 6-minute hang with no output.
|
|
97
|
+
const timeoutMs = opts.timeoutMs ?? 360000;
|
|
92
98
|
const pollMs = opts.pollMs ?? 1000;
|
|
93
99
|
const cfg = new ConfigStore().read()?.browser_agent;
|
|
94
100
|
const mode = cfg?.attachMode ?? 'dedicated';
|
|
@@ -102,7 +108,7 @@ export async function captureFirebaseTokenViaCdp(opts) {
|
|
|
102
108
|
if (mode !== 'dedicated') {
|
|
103
109
|
throw new Error(`attach mode: nothing answering CDP on port ${port}. Launch Chrome ` +
|
|
104
110
|
`with --remote-debugging-port=${port} AND a non-default ` +
|
|
105
|
-
'--user-data-dir, or switch to dedicated mode (
|
|
111
|
+
'--user-data-dir, or switch to dedicated mode (attach_browser).');
|
|
106
112
|
}
|
|
107
113
|
launchChrome({ port, profileDir: defaultProfileDir() });
|
|
108
114
|
await waitForCdpReady(port, 10000);
|
|
@@ -114,6 +120,10 @@ export async function captureFirebaseTokenViaCdp(opts) {
|
|
|
114
120
|
try {
|
|
115
121
|
const session = await handles.attachToTab(targetId);
|
|
116
122
|
const deadline = Date.now() + timeoutMs;
|
|
123
|
+
// Tracks how far the page got, so a timeout can say WHICH thing stalled:
|
|
124
|
+
// a bundle that never finished downloading is a different problem from a
|
|
125
|
+
// developer who walked away mid-sign-in.
|
|
126
|
+
let sawInteractivePage = false;
|
|
117
127
|
while (Date.now() < deadline) {
|
|
118
128
|
const result = await session.send('Runtime.evaluate', {
|
|
119
129
|
expression: EXTRACT_TOKEN_JS,
|
|
@@ -124,10 +134,21 @@ export async function captureFirebaseTokenViaCdp(opts) {
|
|
|
124
134
|
if (typeof value === 'string' && value.length > 0) {
|
|
125
135
|
return { token: value, closedBrowser: launchedByUs };
|
|
126
136
|
}
|
|
137
|
+
if (!sawInteractivePage) {
|
|
138
|
+
sawInteractivePage = await pageIsInteractive(session);
|
|
139
|
+
}
|
|
127
140
|
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
128
141
|
}
|
|
129
|
-
|
|
130
|
-
|
|
142
|
+
const waited = Math.round(timeoutMs / 1000);
|
|
143
|
+
throw new Error(`Timed out after ${waited}s waiting for login. `
|
|
144
|
+
+ (sawInteractivePage
|
|
145
|
+
? 'The page finished loading, so sign-in was never completed — '
|
|
146
|
+
+ 'ask the developer to finish signing in at the opened window, '
|
|
147
|
+
+ 'then re-run reclaim_authenticate.'
|
|
148
|
+
: 'The page never became interactive, so the dashboard was still '
|
|
149
|
+
+ 'loading the whole time. On a fresh Chrome profile its bundle '
|
|
150
|
+
+ 'is a large cold download. Retry (the profile is now warm), or '
|
|
151
|
+
+ 'pass a bigger timeoutMs.'));
|
|
131
152
|
}
|
|
132
153
|
finally {
|
|
133
154
|
// Best-effort cleanup — never mask the real result/error.
|
|
@@ -145,6 +166,25 @@ export async function captureFirebaseTokenViaCdp(opts) {
|
|
|
145
166
|
await handles.close().catch(() => { });
|
|
146
167
|
}
|
|
147
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Whether the login page has reached a usable state. `readyState` alone is not
|
|
171
|
+
* enough for a single-bundle SPA: the document is 'complete' while the app is
|
|
172
|
+
* still parsing, and nothing is clickable yet. Requiring a rendered root as
|
|
173
|
+
* well is what distinguishes "still loading" from "waiting for the human".
|
|
174
|
+
*/
|
|
175
|
+
async function pageIsInteractive(session) {
|
|
176
|
+
try {
|
|
177
|
+
const result = await session.send('Runtime.evaluate', {
|
|
178
|
+
expression: 'document.readyState === "complete" '
|
|
179
|
+
+ '&& !!document.body && document.body.innerText.trim().length > 0',
|
|
180
|
+
returnByValue: true,
|
|
181
|
+
});
|
|
182
|
+
return runtimeResultValue(result) === true;
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
148
188
|
function runtimeResultValue(value) {
|
|
149
189
|
if (typeof value !== 'object' || !value || !('result' in value)) {
|
|
150
190
|
return undefined;
|
package/lib/old/to-register.d.ts
CHANGED
|
@@ -26,7 +26,8 @@ export interface OldProviderOpts {
|
|
|
26
26
|
/** Page-injection script — the devtools name for the builder's
|
|
27
27
|
* `webSettings.jsUserScripts`. Top-level on register, nested under
|
|
28
28
|
* `providerConfig` on config (add-version). The backend rejects a few
|
|
29
|
-
* deprecated injection APIs (
|
|
29
|
+
* deprecated injection APIs (for example, `window.payloadData`) with a
|
|
30
|
+
* 400. */
|
|
30
31
|
customInjection?: string;
|
|
31
32
|
/** Accepted at register too (unlike the four fields below, which the
|
|
32
33
|
* backend only accepts on config/add-version). */
|
|
@@ -129,7 +130,7 @@ export interface SemverTriplet {
|
|
|
129
130
|
/**
|
|
130
131
|
* Pull `{major,minor,patch}` triplets out of a `/versions` response, tolerant
|
|
131
132
|
* of the exact envelope (array, `{ providerVersions: [...] }`, `{ items }`,
|
|
132
|
-
|
|
133
|
+
*, and so on). Each entry's `version` is the triplet.
|
|
133
134
|
*/
|
|
134
135
|
export declare function extractVersions(resp: unknown): SemverTriplet[];
|
|
135
136
|
/**
|
package/lib/old/to-register.js
CHANGED
|
@@ -184,7 +184,7 @@ export function providerToConfigBody(providers, opts, version, versionInfo) {
|
|
|
184
184
|
/**
|
|
185
185
|
* Pull `{major,minor,patch}` triplets out of a `/versions` response, tolerant
|
|
186
186
|
* of the exact envelope (array, `{ providerVersions: [...] }`, `{ items }`,
|
|
187
|
-
|
|
187
|
+
*, and so on). Each entry's `version` is the triplet.
|
|
188
188
|
*/
|
|
189
189
|
export function extractVersions(resp) {
|
|
190
190
|
const list = pickArray(resp);
|
|
@@ -36,6 +36,11 @@ export function authenticateTool(client) {
|
|
|
36
36
|
'and closes the tab. A previously stored identity that is still ' +
|
|
37
37
|
'valid is reused first (pass `useBrowser: true` to force a fresh ' +
|
|
38
38
|
'login). \n\n' +
|
|
39
|
+
'EXPECT THIS TO TAKE A WHILE ON A FIRST RUN: a fresh Chrome ' +
|
|
40
|
+
'profile downloads the dashboard cold, so tell the developer it is ' +
|
|
41
|
+
'loading and that the window is waiting for them — do not re-run ' +
|
|
42
|
+
'the tool while it is still waiting. It polls for up to 6 minutes ' +
|
|
43
|
+
'and reports whether the page ever became interactive.\n\n' +
|
|
39
44
|
'Manual overrides:\n' +
|
|
40
45
|
'- `token`: paste a Firebase ID token (skips the browser).\n' +
|
|
41
46
|
'- `ethAddress`: an `eth:0x…` / `0x…` uid for an already-provisioned ' +
|
|
@@ -61,7 +66,10 @@ export function authenticateTool(client) {
|
|
|
61
66
|
},
|
|
62
67
|
timeoutMs: {
|
|
63
68
|
type: 'number',
|
|
64
|
-
description: 'How long to wait for sign-in, in ms (default
|
|
69
|
+
description: 'How long to wait for sign-in, in ms (default 360000, i.e. 6 '
|
|
70
|
+
+ 'minutes). A fresh Chrome profile loads the dashboard cold, '
|
|
71
|
+
+ 'which can take tens of seconds before the form is usable — '
|
|
72
|
+
+ 'that time comes out of this budget.',
|
|
65
73
|
},
|
|
66
74
|
useBrowser: {
|
|
67
75
|
type: 'boolean',
|