@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
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
connectCdp,
|
|
7
7
|
connectCdpUrl,
|
|
8
8
|
} from '../../../cdp/client.ts'
|
|
9
|
+
import { suggestCountryFromIp } from '../../../cdp/geo.ts'
|
|
9
10
|
import {
|
|
10
11
|
defaultProfileDir,
|
|
11
12
|
launchChrome,
|
|
@@ -24,6 +25,13 @@ export interface AttachState {
|
|
|
24
25
|
liveViewUrl?: string
|
|
25
26
|
browserSessionId?: string
|
|
26
27
|
orgId?: string
|
|
28
|
+
/** The Popcorn pod's own TEE api base (builder mode only) — lets
|
|
29
|
+
* `run_proof_via_pod` prove through the SAME enclave that holds the
|
|
30
|
+
* browser's TLS session (and its country-routed egress), instead of the
|
|
31
|
+
* local in-process attestor SDK. */
|
|
32
|
+
apiUrl?: string
|
|
33
|
+
/** The exit country this remote browser was allocated with, if any. */
|
|
34
|
+
countryCode?: string
|
|
27
35
|
disposeRemote?: () => Promise<void>
|
|
28
36
|
}
|
|
29
37
|
|
|
@@ -40,6 +48,99 @@ interface AttachArgs {
|
|
|
40
48
|
orgId?: string
|
|
41
49
|
cdpUrl?: string
|
|
42
50
|
liveViewUrl?: string
|
|
51
|
+
/** Builder mode: the country the remote browser should browse from. */
|
|
52
|
+
countryCode?: string
|
|
53
|
+
/** Builder mode: allocate without geo-routing, skipping the country
|
|
54
|
+
* prompt. Geolocation is optional — this is how the caller says so. */
|
|
55
|
+
skipCountry?: boolean
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The browser sources, written once and used twice: rendered into this tool's
|
|
60
|
+
* DESCRIPTION (so an agent can put the choice to the developer straight from
|
|
61
|
+
* the tool list, without a discovery round-trip) and returned verbatim as
|
|
62
|
+
* structured `options` when the tool is called without a `mode` anyway.
|
|
63
|
+
*
|
|
64
|
+
* Being the single source for both is the point — an option list that a model
|
|
65
|
+
* reads in the description but that disagrees with what the tool returns is
|
|
66
|
+
* worse than either alone.
|
|
67
|
+
*
|
|
68
|
+
* `liveView` is here for the same reason. How a developer watches the browser
|
|
69
|
+
* differs per mode — Popcorn hands back a hosted URL, a local Chrome needs
|
|
70
|
+
* `share_browser_view` to tunnel one — and nobody discovers that by guessing.
|
|
71
|
+
* Advertising it alongside the mode is the only place it lands BEFORE the
|
|
72
|
+
* choice is made.
|
|
73
|
+
*/
|
|
74
|
+
const BROWSER_OPTIONS = [
|
|
75
|
+
{
|
|
76
|
+
mode: 'dedicated',
|
|
77
|
+
label: 'Local Chrome, launched for you',
|
|
78
|
+
detail:
|
|
79
|
+
'Launches Chrome with a dedicated profile under '
|
|
80
|
+
+ '~/.reclaim/chrome-profile. Easiest local option; nothing to set up.',
|
|
81
|
+
requires: [],
|
|
82
|
+
liveView:
|
|
83
|
+
'On this machine only, until you share it: share_browser_view puts the '
|
|
84
|
+
+ 'tab behind a public URL (needs cloudflared) so someone elsewhere can '
|
|
85
|
+
+ 'watch, and by default click and type.',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
mode: 'attach',
|
|
89
|
+
label: 'Local Chrome the developer is already running',
|
|
90
|
+
detail:
|
|
91
|
+
'Connects to an existing Chrome debugging port. That Chrome must have '
|
|
92
|
+
+ 'been started with BOTH --remote-debugging-port AND a non-default '
|
|
93
|
+
+ '--user-data-dir: since Chrome 136 the debug flag is silently ignored '
|
|
94
|
+
+ 'on the default profile.',
|
|
95
|
+
requires: ['port (default 9222)'],
|
|
96
|
+
liveView:
|
|
97
|
+
'On this machine only, until you share it: share_browser_view puts the '
|
|
98
|
+
+ 'tab behind a public URL (needs cloudflared) so someone elsewhere can '
|
|
99
|
+
+ 'watch, and by default click and type.',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
mode: 'builder',
|
|
103
|
+
label: 'Remote Builder browser (Popcorn), with a shareable live view',
|
|
104
|
+
detail:
|
|
105
|
+
'Quota-accounted remote browser; returns a liveViewUrl the developer '
|
|
106
|
+
+ 'can open or share. The only way to browse a site from a SPECIFIC '
|
|
107
|
+
+ 'COUNTRY — its whole egress is routed there. Geolocation is '
|
|
108
|
+
+ 'optional: '
|
|
109
|
+
+ 'omit countryCode to be offered a suggestion, or pass skipCountry '
|
|
110
|
+
+ 'to allocate without geo-routing.',
|
|
111
|
+
requires: [
|
|
112
|
+
'Builder authentication',
|
|
113
|
+
'orgId (auto-resolved when you have only one)',
|
|
114
|
+
],
|
|
115
|
+
liveView:
|
|
116
|
+
'Included — this call returns a hosted liveViewUrl straight from '
|
|
117
|
+
+ 'Popcorn. Nothing to start, and share_browser_view does not apply.',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
mode: 'custom',
|
|
121
|
+
label: 'A CDP endpoint the developer supplies',
|
|
122
|
+
detail:
|
|
123
|
+
'Connects to any ws:// or wss:// CDP websocket. The URL is treated as '
|
|
124
|
+
+ 'a credential — it is never logged, persisted, or returned.',
|
|
125
|
+
requires: ['cdpUrl'],
|
|
126
|
+
liveView:
|
|
127
|
+
'Shareable on request: share_browser_view puts the tab behind a public '
|
|
128
|
+
+ 'URL (needs cloudflared). The developer\'s own cdpUrl is never '
|
|
129
|
+
+ 'exposed by it.',
|
|
130
|
+
},
|
|
131
|
+
] as const
|
|
132
|
+
|
|
133
|
+
/** Flatten {@link BROWSER_OPTIONS} into the tool description. */
|
|
134
|
+
function renderBrowserOptions(): string {
|
|
135
|
+
return BROWSER_OPTIONS
|
|
136
|
+
.map((option) => {
|
|
137
|
+
const needs = option.requires.length
|
|
138
|
+
? ` Needs: ${option.requires.join('; ')}.`
|
|
139
|
+
: ''
|
|
140
|
+
return `(${option.mode}) ${option.label}. ${option.detail}${needs}`
|
|
141
|
+
+ ` Live view: ${option.liveView}`
|
|
142
|
+
})
|
|
143
|
+
.join(' ')
|
|
43
144
|
}
|
|
44
145
|
|
|
45
146
|
interface AttachDependencies {
|
|
@@ -55,28 +156,27 @@ const DEFAULT_DEPENDENCIES: AttachDependencies = {
|
|
|
55
156
|
export function attachTool(
|
|
56
157
|
state: AttachRef,
|
|
57
158
|
builderClient?: ReclaimClient,
|
|
58
|
-
name: 'attach_browser' | 'attach_chrome' = 'attach_browser',
|
|
59
159
|
beforeReplace?: () => Promise<void>,
|
|
60
160
|
dependencies: AttachDependencies = DEFAULT_DEPENDENCIES,
|
|
61
161
|
): RegisteredTool {
|
|
62
162
|
return defineTool<AttachArgs>(
|
|
63
163
|
{
|
|
64
|
-
name,
|
|
164
|
+
name: 'attach_browser',
|
|
65
165
|
description:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
166
|
+
'Connect the provider-authoring tools to a browser. ASK THE '
|
|
167
|
+
+ 'DEVELOPER which of the four options below they want, then call '
|
|
168
|
+
+ 'this ONCE with their chosen `mode` — never choose for them, and '
|
|
169
|
+
+ 'never assume a previous session\'s choice still applies. You do '
|
|
170
|
+
+ 'not need a discovery call: the options are listed here. '
|
|
171
|
+
+ '(Calling with no `mode` returns the same list as structured '
|
|
172
|
+
+ '`options`, if you\'d rather relay it verbatim.) '
|
|
173
|
+
+ 'When you put the choice to them, INCLUDE each option\'s live-view '
|
|
174
|
+
+ 'line: how they watch the browser differs per mode, and nobody '
|
|
175
|
+
+ 'discovers share_browser_view on their own. '
|
|
176
|
+
+ 'OPTIONS — ' + renderBrowserOptions() + ' '
|
|
177
|
+
+ 'Builder and custom CDP credentials are held in memory and are '
|
|
178
|
+
+ 'never returned. '
|
|
179
|
+
+ 'More: how_it_works({ topic: "browser" }).',
|
|
80
180
|
inputSchema: {
|
|
81
181
|
type: 'object',
|
|
82
182
|
properties: {
|
|
@@ -93,21 +193,45 @@ export function attachTool(
|
|
|
93
193
|
orgId: { type: 'string', format: 'uuid' },
|
|
94
194
|
cdpUrl: { type: 'string', maxLength: 4000 },
|
|
95
195
|
liveViewUrl: { type: 'string', format: 'uri', maxLength: 4000 },
|
|
196
|
+
countryCode: {
|
|
197
|
+
type: 'string',
|
|
198
|
+
pattern: '^[A-Za-z]{2}$',
|
|
199
|
+
description:
|
|
200
|
+
'Builder mode only. ISO 3166-1 alpha-2 country the remote '
|
|
201
|
+
+ 'browser browses from, fixed for the whole session. Omit to '
|
|
202
|
+
+ 'get a suggested country back (needsChoice) instead of '
|
|
203
|
+
+ 'allocating a browser.',
|
|
204
|
+
},
|
|
205
|
+
skipCountry: {
|
|
206
|
+
type: 'boolean',
|
|
207
|
+
description:
|
|
208
|
+
'Builder mode only. Allocate without geo-routing, skipping '
|
|
209
|
+
+ 'the country prompt. Pass this once the developer has said '
|
|
210
|
+
+ 'they don\'t need a specific country.',
|
|
211
|
+
},
|
|
96
212
|
},
|
|
97
213
|
},
|
|
98
214
|
},
|
|
99
215
|
async(parsed) => {
|
|
100
216
|
const persisted = state.config.read()?.browser_agent
|
|
101
|
-
|
|
217
|
+
// Deliberately NOT falling back to the persisted mode: which browser
|
|
218
|
+
// to drive is the developer's call every time (a remote one costs
|
|
219
|
+
// quota, a local one exposes their own profile), and silently reusing
|
|
220
|
+
// last session's answer takes that decision away from them. The
|
|
221
|
+
// persisted choice is offered back as a default to suggest, not
|
|
222
|
+
// applied.
|
|
223
|
+
const mode = parsed.mode ?? parsed.attachMode
|
|
102
224
|
if(!mode) {
|
|
103
225
|
return {
|
|
104
226
|
needsChoice: true,
|
|
105
227
|
message:
|
|
106
|
-
'
|
|
107
|
-
+ '
|
|
108
|
-
+ '
|
|
109
|
-
|
|
110
|
-
|
|
228
|
+
'Ask the developer which browser to use, then call '
|
|
229
|
+
+ 'attach_browser again with their chosen `mode`. Present all '
|
|
230
|
+
+ 'the options below — do not pick one for them.',
|
|
231
|
+
options: BROWSER_OPTIONS,
|
|
232
|
+
...(persisted?.attachMode
|
|
233
|
+
? { previouslyUsed: persisted.attachMode }
|
|
234
|
+
: {}),
|
|
111
235
|
configPath: defaultConfigPath(),
|
|
112
236
|
}
|
|
113
237
|
}
|
|
@@ -146,6 +270,49 @@ export function attachTool(
|
|
|
146
270
|
orgId = orgs[0].id
|
|
147
271
|
}
|
|
148
272
|
|
|
273
|
+
// A remote browser's exit country is fixed for the whole session —
|
|
274
|
+
// Popcorn applies it at allocation and it can't be changed later. So
|
|
275
|
+
// confirm it with the developer BEFORE spending a quota-accounted
|
|
276
|
+
// browser on the wrong one, the same way orgId is confirmed above.
|
|
277
|
+
// Geo-routing is OPTIONAL though, so this asks rather than demands:
|
|
278
|
+
// `skipCountry` is the "no particular country" answer, and it must be
|
|
279
|
+
// as reachable as naming one.
|
|
280
|
+
if(mode === 'builder' && !parsed.countryCode && !parsed.skipCountry) {
|
|
281
|
+
const suggestion = await suggestCountryFromIp()
|
|
282
|
+
return {
|
|
283
|
+
needsChoice: true,
|
|
284
|
+
message:
|
|
285
|
+
'Ask the developer which country this browser should browse '
|
|
286
|
+
+ 'from — its egress is routed through that country for the '
|
|
287
|
+
+ 'whole session and cannot be changed afterwards. Geolocation '
|
|
288
|
+
+ 'is optional; offer all three answers below. Nothing has been '
|
|
289
|
+
+ 'allocated yet, so this costs nothing.',
|
|
290
|
+
choices: [
|
|
291
|
+
{
|
|
292
|
+
answer: 'The suggested country',
|
|
293
|
+
howToCall: `attach_browser({ mode: "builder", countryCode: "${
|
|
294
|
+
suggestion.countryCode}" })`,
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
answer: 'A specific country the site must be seen from',
|
|
298
|
+
howToCall:
|
|
299
|
+
'attach_browser({ mode: "builder", countryCode: "<ISO '
|
|
300
|
+
+ '3166-1 alpha-2>" })',
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
answer: 'No particular country',
|
|
304
|
+
howToCall:
|
|
305
|
+
'attach_browser({ mode: "builder", skipCountry: true })',
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
suggestedCountryCode: suggestion.countryCode,
|
|
309
|
+
suggestionSource: 'caller-ip',
|
|
310
|
+
...(suggestion.country ? { country: suggestion.country } : {}),
|
|
311
|
+
...(suggestion.region ? { region: suggestion.region } : {}),
|
|
312
|
+
...(suggestion.city ? { city: suggestion.city } : {}),
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
149
316
|
const next = await connect(
|
|
150
317
|
mode, { ...parsed, orgId }, persisted, builderClient, dependencies,
|
|
151
318
|
)
|
|
@@ -172,6 +339,7 @@ export function attachTool(
|
|
|
172
339
|
? { browserSessionId: next.browserSessionId }
|
|
173
340
|
: {}),
|
|
174
341
|
...(next.liveViewUrl ? { liveViewUrl: next.liveViewUrl } : {}),
|
|
342
|
+
...(next.countryCode ? { countryCode: next.countryCode } : {}),
|
|
175
343
|
}
|
|
176
344
|
},
|
|
177
345
|
)
|
|
@@ -197,8 +365,10 @@ async function connect(
|
|
|
197
365
|
if(mode === 'builder') {
|
|
198
366
|
assert(builderClient, badRequest('Builder browser service is unavailable'))
|
|
199
367
|
assert(parsed.orgId, badRequest('orgId is required for builder mode'))
|
|
368
|
+
const countryCode = parsed.countryCode?.trim().toUpperCase()
|
|
200
369
|
const { data } = await builderClient.call('CreateAgentBrowserSession', {
|
|
201
370
|
params: { orgId: parsed.orgId },
|
|
371
|
+
body: countryCode ? { countryCode } : {},
|
|
202
372
|
})
|
|
203
373
|
let handles: CdpHandles
|
|
204
374
|
try {
|
|
@@ -219,6 +389,8 @@ async function connect(
|
|
|
219
389
|
liveViewUrl: data.liveViewUrl,
|
|
220
390
|
browserSessionId: data.id,
|
|
221
391
|
orgId: parsed.orgId,
|
|
392
|
+
...(data.apiUrl ? { apiUrl: data.apiUrl } : {}),
|
|
393
|
+
...(countryCode ? { countryCode } : {}),
|
|
222
394
|
disposeRemote: async() => {
|
|
223
395
|
await builderClient.call('DisposeAgentBrowserSession', {
|
|
224
396
|
params: {
|
|
@@ -19,6 +19,8 @@ import { type AttachState, attachTool } from './attach.ts'
|
|
|
19
19
|
import { type CaptureStore, captureTools, resetCaptures } from './capture.ts'
|
|
20
20
|
import { listTabsTool } from './eval.ts'
|
|
21
21
|
import { howItWorksTool } from './how-it-works.ts'
|
|
22
|
+
import { type LiveShareRef, liveViewTools } from './live-view.ts'
|
|
23
|
+
import { podProofTools } from './pod-proof.ts'
|
|
22
24
|
import { createVersionFromCaptureTool } from './publish.ts'
|
|
23
25
|
import { sessionTools } from './session.ts'
|
|
24
26
|
import { sharedAuthoringTools } from './shared.ts'
|
|
@@ -26,26 +28,31 @@ import { sharedAuthoringTools } from './shared.ts'
|
|
|
26
28
|
export interface AgentState {
|
|
27
29
|
attach: { current?: AttachState, config: ConfigStore }
|
|
28
30
|
captures: CaptureStore
|
|
31
|
+
/** The one public live view, when `share_browser_view` has opened it. */
|
|
32
|
+
liveShare: LiveShareRef
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
/** Exported so a mode (
|
|
32
|
-
* can build the attach/capture state up front and share the SAME
|
|
33
|
-
* with `buildAgentTools` — letting that publish tool see the live
|
|
34
|
-
* state (
|
|
35
|
+
/** Exported so a mode (for example, old-devtools) that supplies its own
|
|
36
|
+
* publish tool can build the attach/capture state up front and share the SAME
|
|
37
|
+
* instance with `buildAgentTools` — letting that publish tool see the live
|
|
38
|
+
* attach state (for example, to remind the caller about a still-open browser
|
|
39
|
+
* session). */
|
|
35
40
|
export function buildAgentState(): AgentState {
|
|
36
41
|
return {
|
|
37
42
|
attach: { config: new ConfigStore() },
|
|
38
43
|
captures: { sessions: new Map() },
|
|
44
|
+
liveShare: {},
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
/** Build the flat list of `RegisteredTool` entries, ready for
|
|
43
49
|
* `registerToolList`.
|
|
44
50
|
* Pass `client` to include the builder `create_provider_version_from_capture`;
|
|
45
|
-
* omit it (
|
|
46
|
-
* capture/proof tools — old mode supplies its own publish
|
|
47
|
-
* Pass `externalState` to share attach/capture state with tools
|
|
48
|
-
* elsewhere (
|
|
51
|
+
* omit it (for example, OLD-devtools mode) to register only the
|
|
52
|
+
* backend-independent capture/proof tools — old mode supplies its own publish
|
|
53
|
+
* tool. Pass `externalState` to share attach/capture state with tools
|
|
54
|
+
* registered elsewhere (for example, old mode's publish tool) instead of
|
|
55
|
+
* building a fresh one. */
|
|
49
56
|
export function buildAgentTools(
|
|
50
57
|
client?: ReclaimClient,
|
|
51
58
|
builderClient: ReclaimClient | undefined = client,
|
|
@@ -58,18 +65,22 @@ export function buildAgentTools(
|
|
|
58
65
|
// client. No `client` ⇒ old-devtools mode (see `mcp/start.ts`).
|
|
59
66
|
tools.push(howItWorksTool(!client))
|
|
60
67
|
const beforeReplace = () => resetCaptures(state.captures)
|
|
61
|
-
tools.push(attachTool(
|
|
62
|
-
state.attach, builderClient, 'attach_browser', beforeReplace,
|
|
63
|
-
))
|
|
64
|
-
tools.push(attachTool(
|
|
65
|
-
state.attach, builderClient, 'attach_chrome', beforeReplace,
|
|
66
|
-
))
|
|
68
|
+
tools.push(attachTool(state.attach, builderClient, beforeReplace))
|
|
67
69
|
tools.push(listTabsTool(state.attach))
|
|
68
70
|
for(const t of captureTools(state.attach, state.captures)) {
|
|
69
71
|
tools.push(t)
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
for(const t of sessionTools(state.attach, state.captures)) {
|
|
74
|
+
for(const t of sessionTools(state.attach, state.captures, state.liveShare)) {
|
|
75
|
+
tools.push(t)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Local-CDP only: a builder-mode browser already has a hosted live view.
|
|
79
|
+
for(const t of liveViewTools(state.attach, state.captures, state.liveShare)) {
|
|
80
|
+
tools.push(t)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for(const t of podProofTools(state.attach, state.captures)) {
|
|
73
84
|
tools.push(t)
|
|
74
85
|
}
|
|
75
86
|
|
|
@@ -93,7 +93,11 @@ export function captureTools(
|
|
|
93
93
|
{
|
|
94
94
|
name: 'get_request',
|
|
95
95
|
description:
|
|
96
|
-
'Return full headers
|
|
96
|
+
'Return the full headers and bodies of one captured request. Use it '
|
|
97
|
+
+ 'to inspect a candidate that find_requests_containing ranked. '
|
|
98
|
+
+ 'Unlike every other tool here, the headers are RAW — cookie and '
|
|
99
|
+
+ 'authorization values are visible. Read them; never repeat them to '
|
|
100
|
+
+ 'the developer or write them to a file.',
|
|
97
101
|
inputSchema: {
|
|
98
102
|
type: 'object',
|
|
99
103
|
properties: {
|
|
@@ -10,7 +10,10 @@ export function listTabsTool(attachRef: {
|
|
|
10
10
|
return defineTool<Record<string, never>>(
|
|
11
11
|
{
|
|
12
12
|
name: 'list_tabs',
|
|
13
|
-
description:
|
|
13
|
+
description:
|
|
14
|
+
'List the open tabs in the attached browser, with their tabId, title '
|
|
15
|
+
+ 'and URL. Call it after attach_browser to pick the tabId to pass to '
|
|
16
|
+
+ 'start_capture.',
|
|
14
17
|
inputSchema: { type: 'object', properties: {} },
|
|
15
18
|
},
|
|
16
19
|
async() => {
|
|
@@ -22,9 +22,10 @@ export function findTool(resolveBackend: ResolveBackend): RegisteredTool {
|
|
|
22
22
|
+ 'URL/JSON/HTML-escaped, …). Returns a ranked candidate list with '
|
|
23
23
|
+ 'the matched variant, score, and a short snippet — used to locate '
|
|
24
24
|
+ 'the request that returns a target. Also finds SPLIT matches: when '
|
|
25
|
-
+ '`value` is multi-word (
|
|
26
|
-
+ 'every word SEPARATELY (a JSON profile API with
|
|
27
|
-
+ 'distinct fields) is returned with
|
|
25
|
+
+ '`value` is multi-word (for example, a full name), a response '
|
|
26
|
+
+ 'that carries every word SEPARATELY (a JSON profile API with '
|
|
27
|
+
+ 'first and last name in distinct fields) is returned with '
|
|
28
|
+
+ '`splitMatch:true` — prove those '
|
|
28
29
|
+ 'per-field (a redaction per part) rather than as one string. '
|
|
29
30
|
+ 'Server-side; bodies stay here.',
|
|
30
31
|
inputSchema: objectSchema(
|
|
@@ -77,8 +78,8 @@ export function findTool(resolveBackend: ResolveBackend): RegisteredTool {
|
|
|
77
78
|
continue
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
// Split match: every word present separately (
|
|
81
|
-
// in distinct JSON fields). Requires ≥2 tokens so it's specific.
|
|
81
|
+
// Split match: every word present separately (for example, first/last
|
|
82
|
+
// name in distinct JSON fields). Requires ≥2 tokens so it's specific.
|
|
82
83
|
if(tokens.length >= 2 && tokens.every((t) => body.includes(t))) {
|
|
83
84
|
ranked.push({
|
|
84
85
|
...publicRequest(r),
|
|
@@ -1,32 +1,73 @@
|
|
|
1
|
-
import {
|
|
2
|
-
PROVIDER_GUIDE_BUILDER,
|
|
3
|
-
PROVIDER_GUIDE_OLD,
|
|
4
|
-
} from '../../instructions.ts'
|
|
1
|
+
import { guideFor } from '../../instructions.ts'
|
|
5
2
|
import { defineTool, type RegisteredTool } from '../../server.ts'
|
|
6
3
|
|
|
7
4
|
/**
|
|
8
|
-
* Serves the
|
|
9
|
-
* always-on `instructions` are a terse tool-flow reference; this tool
|
|
10
|
-
* the deep version
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
5
|
+
* Serves the provider-authoring guide on demand, ONE TOPIC AT A TIME. The
|
|
6
|
+
* server's always-on `instructions` are a terse tool-flow reference; this tool
|
|
7
|
+
* is the deep version, so any MCP client — not just Claude Code, which alone
|
|
8
|
+
* auto-loads skills — can pull it.
|
|
9
|
+
*
|
|
10
|
+
* Topic-scoped rather than all-at-once because the full guide is ~7k tokens:
|
|
11
|
+
* paying that to answer "how do I mark a header secret?" crowds out the actual
|
|
12
|
+
* task, which is exactly what makes an agent slow and prone to inventing
|
|
13
|
+
* details. Every response carries the topic index, so a caller that guessed
|
|
14
|
+
* wrong (or called with no topic at all) always learns what else is available
|
|
15
|
+
* without a second round-trip.
|
|
16
|
+
*
|
|
17
|
+
* `oldMode` selects the backend-specific guide, so the agent is never told to
|
|
18
|
+
* call auth/publish tools that aren't registered in its mode. Stateless:
|
|
19
|
+
* callable before `attach_browser`.
|
|
15
20
|
*/
|
|
16
21
|
export function howItWorksTool(oldMode: boolean): RegisteredTool {
|
|
17
|
-
const guide = oldMode
|
|
18
|
-
|
|
22
|
+
const guide = guideFor(oldMode)
|
|
23
|
+
const names = [...guide.topics.keys()]
|
|
24
|
+
|
|
25
|
+
return defineTool<{ topic?: string }>(
|
|
19
26
|
{
|
|
20
27
|
name: 'how_it_works',
|
|
21
28
|
description:
|
|
22
|
-
'
|
|
23
|
-
+ '
|
|
24
|
-
+ '
|
|
25
|
-
+ '
|
|
26
|
-
+ '
|
|
27
|
-
+ '
|
|
28
|
-
|
|
29
|
+
'Reclaim provider-authoring guide for THIS server, by topic. Call it '
|
|
30
|
+
+ 'with no arguments for the overview plus the topic index, then again '
|
|
31
|
+
+ 'for whichever topic you need. Topics: '
|
|
32
|
+
+ names.join(', ')
|
|
33
|
+
+ '. Use `all` for the entire guide at once (large — prefer a topic). '
|
|
34
|
+
+ 'Read the relevant topic BEFORE acting unless you are already fluent '
|
|
35
|
+
+ 'in that part of the flow.',
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
topic: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
// Deliberately NOT an `enum`: schema validation runs before the
|
|
42
|
+
// handler, so an enum turns a mistyped topic into a validation
|
|
43
|
+
// error instead of a response carrying the real topic list. A
|
|
44
|
+
// plain string lets the tool answer the mistake usefully.
|
|
45
|
+
description:
|
|
46
|
+
'Which section to return: ' + names.join(', ')
|
|
47
|
+
+ ', or `all`. Omit for the overview + topic index. An '
|
|
48
|
+
+ 'unrecognized value comes back with the list, not an error.',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
async({ topic }) => {
|
|
54
|
+
if(topic === 'all') {
|
|
55
|
+
return { topic: 'all', topics: guide.index, guide: guide.full }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const requested = topic ?? 'overview'
|
|
59
|
+
const section = guide.topics.get(requested)
|
|
60
|
+
if(!section) {
|
|
61
|
+
// Not an error: hand back the index so the caller self-corrects in
|
|
62
|
+
// the same turn rather than retrying blind.
|
|
63
|
+
return {
|
|
64
|
+
error: `Unknown topic "${requested}".`,
|
|
65
|
+
topics: guide.index,
|
|
66
|
+
guide: guide.topics.get('overview'),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { topic: requested, topics: guide.index, guide: section }
|
|
29
71
|
},
|
|
30
|
-
async() => ({ guide }),
|
|
31
72
|
)
|
|
32
73
|
}
|