@reclaimprotocol/agent 0.1.28 → 0.1.31
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 +28 -9
- package/lib/consts.d.ts +5 -5
- package/lib/consts.js +7 -7
- package/lib/live/tunnel-watchdog.d.ts +1 -0
- package/lib/live/tunnel-watchdog.js +66 -0
- package/lib/live/tunnel.d.ts +30 -7
- package/lib/live/tunnel.js +217 -40
- package/lib/mcp/api/operations.gen.d.ts +12 -0
- package/lib/mcp/api/operations.gen.js +537 -59
- package/lib/mcp/api/tools.js +27 -5
- package/lib/mcp/instructions.js +25 -32
- package/lib/mcp/remap_errors.js +6 -0
- package/lib/mcp/server.js +2 -8
- package/lib/mcp/start.js +3 -5
- package/lib/mcp/success-response.d.ts +5 -0
- package/lib/mcp/success-response.js +21 -0
- package/lib/mcp/tools/agent/attach.js +9 -66
- package/lib/mcp/tools/agent/build.d.ts +0 -3
- package/lib/mcp/tools/agent/build.js +3 -4
- package/lib/mcp/tools/agent/how-it-works.d.ts +5 -15
- package/lib/mcp/tools/agent/how-it-works.js +9 -22
- package/lib/mcp/tools/agent/live-view.d.ts +1 -15
- package/lib/mcp/tools/agent/live-view.js +71 -96
- package/lib/mcp/tools/agent/propose.js +7 -23
- package/lib/mcp/tools/agent/publish.js +80 -18
- package/lib/mcp/tools/agent/session.d.ts +4 -7
- package/lib/mcp/tools/agent/session.js +8 -23
- package/lib/mcp/tools/verification/result.d.ts +2 -1
- package/lib/mcp/tools/verification/result.js +29 -28
- package/lib/old/client.d.ts +2 -2
- package/lib/old/flag.d.ts +2 -5
- package/lib/old/flag.js +2 -5
- package/lib/old/instructions.d.ts +1 -1
- package/lib/old/instructions.js +1 -1
- package/lib/old/to-register.d.ts +4 -3
- package/lib/old/tools/publish.js +11 -14
- package/lib/proof/attestor-auth.d.ts +9 -1
- package/lib/proof/attestor-auth.js +15 -2
- package/lib/proof/attestor.d.ts +2 -2
- package/lib/proof/attestor.js +20 -2
- package/lib/proof/pod-prover.js +1 -1
- package/lib/proof/proof-core.d.ts +4 -0
- package/lib/proof/proof-core.js +20 -0
- package/lib/provider/injection-doc.d.ts +3 -2
- package/lib/provider/injection-doc.js +12 -10
- package/lib/provider/matcher.js +19 -15
- package/lib/provider/to-version.d.ts +3 -0
- package/lib/provider/to-version.js +2 -1
- package/package.json +3 -3
- package/skill/SKILL.builder.md +32 -85
- package/skill/SKILL.common.md +38 -22
- package/skill/SKILL.old.md +17 -3
- package/skill/builder-mode.md +37 -4
- package/skill/community-contributions.md +12 -3
- package/skill/hash-validation.md +9 -9
- package/skill/interception.md +39 -46
- package/skill/old-mode.md +13 -0
- package/skill/user-script.md +24 -8
- package/skill/verification-client-integration.md +130 -0
- package/src/consts.ts +9 -7
- package/src/mcp/api/operations.gen.ts +539 -59
- package/src/mcp/api/tools.ts +38 -7
- package/src/mcp/instructions.ts +35 -32
- package/src/mcp/remap_errors.ts +6 -0
- package/src/mcp/server.ts +2 -8
- package/src/mcp/start.ts +3 -5
- package/src/mcp/success-response.ts +26 -0
- package/src/mcp/tools/agent/attach.ts +9 -71
- package/src/mcp/tools/agent/build.ts +4 -7
- package/src/mcp/tools/agent/how-it-works.ts +9 -22
- package/src/mcp/tools/agent/live-view.ts +76 -116
- package/src/mcp/tools/agent/propose.ts +7 -23
- package/src/mcp/tools/agent/publish.ts +89 -18
- package/src/mcp/tools/agent/session.ts +6 -23
- package/src/mcp/tools/verification/result.ts +30 -29
- package/src/old/client.ts +2 -2
- package/src/old/flag.ts +2 -5
- package/src/old/instructions.ts +1 -1
- package/src/old/to-register.ts +4 -3
- package/src/old/tools/publish.ts +16 -14
- package/src/proof/attestor-auth.ts +18 -1
- package/src/proof/attestor.ts +24 -3
- package/src/proof/pod-prover.ts +1 -1
- package/src/proof/proof-core.ts +28 -0
- package/src/provider/injection-doc.ts +12 -10
- package/src/provider/matcher.ts +25 -17
- package/src/provider/to-version.ts +7 -1
- package/src/live/cloudflared.ts +0 -348
- package/src/live/tunnel.ts +0 -254
|
@@ -1,57 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `share_browser_view`
|
|
3
|
-
* browser's built-in viewer behind a public URL so someone
|
|
4
|
-
* machine can watch it and optionally drive it.
|
|
2
|
+
* `share_browser_view` — gives the calling agent what it needs to put the
|
|
3
|
+
* attached container browser's built-in viewer behind a public URL, so someone
|
|
4
|
+
* who is not at this machine can watch it and optionally drive it.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The server never runs, downloads or resolves cloudflared: the agent runs it
|
|
7
|
+
* itself and passes the resulting `trycloudflare.com` URL back to get the link
|
|
8
|
+
* to hand out.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `stop_browser_view`, `dispose_browser`, or `reset_session`.
|
|
10
|
+
* Scoped to `container` mode on purpose. A `builder`-mode browser already has
|
|
11
|
+
* a hosted live view at `attach_browser`'s `liveViewUrl`.
|
|
13
12
|
*/
|
|
14
|
-
import { badRequest
|
|
13
|
+
import { badRequest } from '@hapi/boom';
|
|
15
14
|
import assert from 'node:assert';
|
|
16
|
-
import { startTunnel } from "../../../live/tunnel.js";
|
|
17
|
-
import { LOGGER } from "../../../logger.js";
|
|
18
15
|
import { defineTool } from "../../server.js";
|
|
19
16
|
import { SHAREABLE_MODES } from "./attach.js";
|
|
20
17
|
import { ensureLiveViewUrl } from "./live-view-url.js";
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
export
|
|
24
|
-
const share = ref.current;
|
|
25
|
-
if (!share) {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
ref.current = undefined;
|
|
29
|
-
// Only the tunnel is ours to close. The browser and its viewer belong to the
|
|
30
|
-
// container, which keeps running until the browser is disposed.
|
|
31
|
-
await share.tunnel.stop().catch((err) => {
|
|
32
|
-
LOGGER.warn({ err }, 'live view: tunnel stop failed');
|
|
33
|
-
});
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
export function liveViewTools(attachRef, shareRef, openTunnel = startTunnel) {
|
|
18
|
+
/** Quick-tunnel hostnames only: `https://<words>.trycloudflare.com`. */
|
|
19
|
+
const QUICK_TUNNEL_HOST = /\.trycloudflare\.com$/i;
|
|
20
|
+
export function liveViewTools(attachRef) {
|
|
37
21
|
return [
|
|
38
22
|
defineTool({
|
|
39
23
|
name: 'share_browser_view',
|
|
40
|
-
description: '
|
|
41
|
-
+ '
|
|
42
|
-
+ '
|
|
43
|
-
+ '
|
|
44
|
-
+ '
|
|
45
|
-
+ '
|
|
46
|
-
+ '
|
|
47
|
-
+ '
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
24
|
+
description: 'Shares an attached `container` browser with someone who is not at '
|
|
25
|
+
+ 'this machine. Step 1: call with no arguments to get the local '
|
|
26
|
+
+ 'viewer URL and the `cloudflared` command to run yourself (this '
|
|
27
|
+
+ 'server does not run it). Step 2: after cloudflared prints its '
|
|
28
|
+
+ '`https://*.trycloudflare.com` URL, call again with it as '
|
|
29
|
+
+ '`publicUrl` to get the link to relay. Treat the link as a '
|
|
30
|
+
+ 'credential. For `builder`, use the `liveViewUrl` from '
|
|
31
|
+
+ '`attach_browser`.',
|
|
32
|
+
inputSchema: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
publicUrl: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: 'The `https://*.trycloudflare.com` URL that '
|
|
38
|
+
+ 'cloudflared printed.',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
}, async (parsed) => {
|
|
55
43
|
const attached = attachRef.current;
|
|
56
44
|
assert(attached, badRequest('No browser attached. Call attach_browser first.'));
|
|
57
45
|
const hostedViewUrl = attached.liveViewUrl
|
|
@@ -66,67 +54,54 @@ export function liveViewTools(attachRef, shareRef, openTunnel = startTunnel) {
|
|
|
66
54
|
+ 'Reclaim runtime in Docker — free, needs Docker) or '
|
|
67
55
|
+ '"builder" (hosted, no install, chargeable)')
|
|
68
56
|
+ '.'));
|
|
69
|
-
assert(!shareRef.current, conflict('A live view is already running at '
|
|
70
|
-
+ `${shareRef.current?.url}. Call stop_browser_view first, or `
|
|
71
|
-
+ 'reuse that URL.'));
|
|
72
57
|
// The container publishes its viewer on loopback and its full CDP
|
|
73
|
-
// port beside it. Only the VIEWER
|
|
74
|
-
// unauthenticated, total control of the browser
|
|
75
|
-
// public URL would hand that to anyone with the link.
|
|
58
|
+
// port beside it. Only the VIEWER may be exposed: the CDP port is
|
|
59
|
+
// unauthenticated, total control of the browser.
|
|
76
60
|
const viewerPort = attached.viewerPort;
|
|
77
61
|
assert(viewerPort, badRequest('This browser reports no live-view port. Re-attach with mode '
|
|
78
62
|
+ '"container".'));
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
63
|
+
if (parsed.publicUrl) {
|
|
64
|
+
const publicUrl = parseQuickTunnelUrl(parsed.publicUrl);
|
|
65
|
+
return {
|
|
66
|
+
url: ensureLiveViewUrl(publicUrl.origin),
|
|
67
|
+
_notes: [
|
|
68
|
+
'Relay `url` to the developer verbatim, on its own line.',
|
|
69
|
+
'The URL is a credential: it needs no login, and anyone with '
|
|
70
|
+
+ 'it can see this browser and act in it as the signed-in user.',
|
|
71
|
+
'It shows the whole browser, so a sign-in that opens a '
|
|
72
|
+
+ 'separate window is visible too.',
|
|
73
|
+
'Opening it on a phone lays the page out for that phone, with '
|
|
74
|
+
+ 'touch, pinch-zoom and the soft keyboard.',
|
|
75
|
+
'When they are done, stop the cloudflared process you started. '
|
|
76
|
+
+ 'The browser keeps running until `dispose_browser`.',
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const localUrl = `http://localhost:${viewerPort}`;
|
|
90
81
|
return {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
: {
|
|
95
|
-
propagating: true,
|
|
96
|
-
cloudflaredLog: share.tunnel.cloudflaredLog,
|
|
97
|
-
}),
|
|
98
|
-
...(share.tunnel.binary.source === 'downloaded'
|
|
99
|
-
? { cloudflaredInstalled: true }
|
|
100
|
-
: {}),
|
|
82
|
+
localViewerUrl: ensureLiveViewUrl(localUrl),
|
|
83
|
+
port: viewerPort,
|
|
84
|
+
command: `cloudflared tunnel --url ${localUrl}`,
|
|
101
85
|
_notes: [
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
'
|
|
113
|
-
+ 'can see this browser and act in it as the signed-in user.',
|
|
114
|
-
'It shows the whole browser, so a sign-in that opens a separate '
|
|
115
|
-
+ 'window is visible too — unlike a single-tab stream.',
|
|
116
|
-
'Opening it on a phone lays the page out for that phone, with '
|
|
117
|
-
+ 'touch, pinch-zoom and the soft keyboard.',
|
|
118
|
-
'Call stop_browser_view when they are done. That closes the '
|
|
119
|
-
+ 'tunnel only — the browser keeps running.',
|
|
86
|
+
'Run `command` yourself in a background process. If `cloudflared` '
|
|
87
|
+
+ 'is not installed, tell the developer and ask them to install '
|
|
88
|
+
+ 'it (https://developers.cloudflare.com/cloudflare-one/'
|
|
89
|
+
+ 'connections/connect-networks/downloads/) — do not install it '
|
|
90
|
+
+ 'without their approval.',
|
|
91
|
+
'Read its output for a line containing '
|
|
92
|
+
+ '`https://<words>.trycloudflare.com`. That is the public URL.',
|
|
93
|
+
'Call share_browser_view again with that value as `publicUrl` '
|
|
94
|
+
+ 'to get the link to relay. Never guess or reconstruct the '
|
|
95
|
+
+ 'hostname.',
|
|
96
|
+
'Expose only this port. Never tunnel the browser\'s CDP port.',
|
|
120
97
|
],
|
|
121
98
|
};
|
|
122
99
|
}),
|
|
123
|
-
defineTool({
|
|
124
|
-
name: 'stop_browser_view',
|
|
125
|
-
description: 'Stops the public tunnel opened by share_browser_view; the URL stops '
|
|
126
|
-
+ 'resolving. Call it when the viewer is done. The browser stays '
|
|
127
|
-
+ 'attached, its local viewer keeps serving, and capture continues. '
|
|
128
|
-
+ 'Returns `stopped: false` when no public share is active.',
|
|
129
|
-
inputSchema: { type: 'object', properties: {} },
|
|
130
|
-
}, async () => ({ stopped: await stopLiveShare(shareRef) })),
|
|
131
100
|
];
|
|
132
101
|
}
|
|
102
|
+
function parseQuickTunnelUrl(value) {
|
|
103
|
+
const url = URL.parse(value);
|
|
104
|
+
assert(url?.protocol === 'https:' && QUICK_TUNNEL_HOST.test(url.hostname), badRequest('publicUrl must be the https://*.trycloudflare.com URL printed by '
|
|
105
|
+
+ 'cloudflared.'));
|
|
106
|
+
return url;
|
|
107
|
+
}
|
|
@@ -13,29 +13,13 @@ import { defineTool } from "../../../tools/registry.js";
|
|
|
13
13
|
export function proposeTool(resolveBackend) {
|
|
14
14
|
return defineTool({
|
|
15
15
|
name: 'propose_provider',
|
|
16
|
-
description: '
|
|
17
|
-
+ '
|
|
18
|
-
+ '
|
|
19
|
-
+ '
|
|
20
|
-
+ '
|
|
21
|
-
+ '
|
|
22
|
-
+ '
|
|
23
|
-
+ 'its last alphanumeric token, lowercased, to form the param key '
|
|
24
|
-
+ '(for example, "employer_name" → "name") — pick names that stay '
|
|
25
|
-
+ 'distinct '
|
|
26
|
-
+ 'after shortening ("firstName"/"surname", not "first_name"/'
|
|
27
|
-
+ '"last_name"). Pass `shortenNames: false` to use each `name` '
|
|
28
|
-
+ 'VERBATIM as the param key instead — it becomes both a regex '
|
|
29
|
-
+ 'capture-group name and a paramValues key, so it must be a plain '
|
|
30
|
-
+ 'identifier (letters/digits/underscore, not starting with a '
|
|
31
|
-
+ 'digit); we recommend lower_snake_case (for example, '
|
|
32
|
-
+ '"employer_name"), '
|
|
33
|
-
+ 'but you choose the exact name. For a parameter used only to build a '
|
|
34
|
-
+ 'later claim request, preserve a `REQ_` prefix (body, header, or '
|
|
35
|
-
+ 'intermediate value) or `URL_` prefix (path or query value) by '
|
|
36
|
-
+ 'setting `shortenNames: false`. The prefix hides that internal '
|
|
37
|
-
+ 'parameter from the data-point UI; it remains available to named '
|
|
38
|
-
+ 'regexes and `{{param}}` substitutions.',
|
|
16
|
+
description: 'Drafts a provider from one authenticated captured request and '
|
|
17
|
+
+ 'keeps captured secrets server-side. Use `targets` for every value '
|
|
18
|
+
+ 'available in the same response; the result contains a `draftId` '
|
|
19
|
+
+ 'and secret-free recipe. Set `shortenNames: false` to preserve an '
|
|
20
|
+
+ 'explicit parameter name, including a hidden `REQ_` or `URL_` '
|
|
21
|
+
+ 'prefix. For matching and naming guidance, call '
|
|
22
|
+
+ 'how_it_works({ topic: "draft" }) and topic `params`.',
|
|
39
23
|
inputSchema: objectSchema({
|
|
40
24
|
requestId: { type: 'string' },
|
|
41
25
|
name: { type: 'string' },
|
|
@@ -101,27 +101,80 @@ const ALLOWED_JS_REQUEST_SCHEMA = {
|
|
|
101
101
|
},
|
|
102
102
|
required: ['url', 'method'],
|
|
103
103
|
};
|
|
104
|
+
const BROWSER_REQUEST_ACTION_SCHEMA = {
|
|
105
|
+
type: 'object',
|
|
106
|
+
description: 'Portal-only authenticated browser action. The page supplies the action ' +
|
|
107
|
+
'id and declared primitive inputs; this object fixes the HTTPS origin, ' +
|
|
108
|
+
'request template, and returned fields.',
|
|
109
|
+
properties: {
|
|
110
|
+
id: { type: 'string', pattern: '^[A-Za-z][A-Za-z0-9_-]{0,99}$' },
|
|
111
|
+
origin: { type: 'string', pattern: '^https://[^/?#]+$' },
|
|
112
|
+
path: { type: 'string', pattern: '^/(?:$|[^/\\\\][^\\\\]*)$' },
|
|
113
|
+
pathTemplate: {
|
|
114
|
+
type: 'string',
|
|
115
|
+
pattern: '^/(?:$|[^/\\\\][^\\\\]*)$',
|
|
116
|
+
description: 'Origin-relative path/query template using `{{name}}` inputs.',
|
|
117
|
+
},
|
|
118
|
+
method: {
|
|
119
|
+
type: 'string',
|
|
120
|
+
enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
|
|
121
|
+
},
|
|
122
|
+
transport: { type: 'string', enum: ['fetch', 'navigate'] },
|
|
123
|
+
userAgent: {
|
|
124
|
+
type: 'string',
|
|
125
|
+
minLength: 1,
|
|
126
|
+
maxLength: 300,
|
|
127
|
+
pattern: '^[^\\r\\n]+$',
|
|
128
|
+
description: 'Browser User-Agent applied before the isolated action page navigates.',
|
|
129
|
+
},
|
|
130
|
+
omitOriginHeader: {
|
|
131
|
+
type: 'boolean',
|
|
132
|
+
description: 'Remove Chromium\'s automatic Origin header from a fetch action. ' +
|
|
133
|
+
'Unsupported for navigate actions.',
|
|
134
|
+
},
|
|
135
|
+
headers: {
|
|
136
|
+
type: 'object',
|
|
137
|
+
additionalProperties: { type: 'string' },
|
|
138
|
+
},
|
|
139
|
+
headersTemplate: {
|
|
140
|
+
type: 'object',
|
|
141
|
+
description: 'Header values may interpolate declared `{{name}}` inputs.',
|
|
142
|
+
additionalProperties: { type: 'string' },
|
|
143
|
+
},
|
|
144
|
+
bodyTemplate: {
|
|
145
|
+
type: 'string',
|
|
146
|
+
description: 'JSON template using declared `{{name}}` inputs.',
|
|
147
|
+
},
|
|
148
|
+
cookieValueHeader: {
|
|
149
|
+
type: 'object',
|
|
150
|
+
properties: {
|
|
151
|
+
name: { type: 'string' },
|
|
152
|
+
cookieName: { type: 'string' },
|
|
153
|
+
},
|
|
154
|
+
required: ['name', 'cookieName'],
|
|
155
|
+
},
|
|
156
|
+
includeBody: { type: 'boolean' },
|
|
157
|
+
inputKeys: {
|
|
158
|
+
type: 'array',
|
|
159
|
+
items: { type: 'string', pattern: '^[A-Za-z][A-Za-z0-9_]{0,99}$' },
|
|
160
|
+
},
|
|
161
|
+
responseFields: {
|
|
162
|
+
type: 'array',
|
|
163
|
+
items: { type: 'string', pattern: '^[A-Za-z][A-Za-z0-9_]{0,99}$' },
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
required: ['id', 'origin', 'path', 'method'],
|
|
167
|
+
};
|
|
104
168
|
export function createVersionFromCaptureTool(client, attachRef) {
|
|
105
169
|
return defineTool({
|
|
106
170
|
name: 'create_provider_version_from_capture',
|
|
107
|
-
description: '
|
|
108
|
-
'
|
|
109
|
-
'propose_provider
|
|
110
|
-
'
|
|
111
|
-
'
|
|
112
|
-
'
|
|
113
|
-
'
|
|
114
|
-
'creates a new immutable version; never edit an existing version. ' +
|
|
115
|
-
'Omit `version` and `bump` for a patch bump, pass `bump` for a ' +
|
|
116
|
-
'major/minor/patch bump, or pass the exact higher `version` the user ' +
|
|
117
|
-
'directed. The version lands on the ' +
|
|
118
|
-
'draft branch: call `publish_provider_version` (PRIVATE) or ' +
|
|
119
|
-
'`submit_provider_version_for_review` (PUBLIC) to go live. ' +
|
|
120
|
-
'`requiredContext` is derived from any {{context.<name>}} ' +
|
|
121
|
-
'placeholders and returned on the created version — relay it to the ' +
|
|
122
|
-
'dev verbatim, since a session missing those fields is a 400. Always ' +
|
|
123
|
-
'read the response\'s `_notes`. ' +
|
|
124
|
-
'Full walkthrough: how_it_works({ topic: "publish" }).',
|
|
171
|
+
description: 'Creates a new immutable provider version after `run_proof` succeeds. '
|
|
172
|
+
+ 'Pass exactly one `provider` or `providers` value returned by '
|
|
173
|
+
+ '`propose_provider`; do not rebuild the recipe. The default is a '
|
|
174
|
+
+ 'patch bump, or use `bump` or an explicitly requested higher '
|
|
175
|
+
+ '`version`. The new version remains a draft until published or '
|
|
176
|
+
+ 'submitted for review. Read `_notes` and '
|
|
177
|
+
+ 'how_it_works({ topic: "publish" }).',
|
|
125
178
|
inputSchema: {
|
|
126
179
|
type: 'object',
|
|
127
180
|
properties: {
|
|
@@ -195,6 +248,14 @@ export function createVersionFromCaptureTool(client, attachRef) {
|
|
|
195
248
|
ALLOWED_REQUESTS_HINT,
|
|
196
249
|
items: ALLOWED_JS_REQUEST_SCHEMA,
|
|
197
250
|
},
|
|
251
|
+
browserRequestActions: {
|
|
252
|
+
type: 'array',
|
|
253
|
+
description: 'Portal-only actions available to jsUserScripts through ' +
|
|
254
|
+
'window.Reclaim.browserRequest. Use these for bounded ' +
|
|
255
|
+
'same-origin requests that need the browser cookie jar. ' +
|
|
256
|
+
'Other Verification Clients currently ignore them.',
|
|
257
|
+
items: BROWSER_REQUEST_ACTION_SCHEMA,
|
|
258
|
+
},
|
|
198
259
|
interceptorOptions: {
|
|
199
260
|
type: 'object',
|
|
200
261
|
description: 'In-app interception settings, stored as ' +
|
|
@@ -247,6 +308,7 @@ export function createVersionFromCaptureTool(client, attachRef) {
|
|
|
247
308
|
requiredContext: args.requiredContext,
|
|
248
309
|
jsUserScripts: args.jsUserScripts,
|
|
249
310
|
allowedJsRequests: args.allowedJsRequests,
|
|
311
|
+
browserRequestActions: args.browserRequestActions,
|
|
250
312
|
interceptorOptions: args.interceptorOptions,
|
|
251
313
|
});
|
|
252
314
|
const result = await client.call('CreateProviderVersion', {
|
|
@@ -2,22 +2,19 @@ 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';
|
|
6
5
|
export declare function sessionTools(attachRef: {
|
|
7
6
|
current?: AttachState;
|
|
8
7
|
config: ConfigStore;
|
|
9
|
-
}, captures: CaptureStore
|
|
8
|
+
}, captures: CaptureStore): RegisteredTool[];
|
|
10
9
|
/**
|
|
11
|
-
* Release everything a session holds: the
|
|
12
|
-
* browser itself.
|
|
10
|
+
* Release everything a session holds: the captures and the browser itself.
|
|
13
11
|
*
|
|
14
12
|
* Exported because a session can end WITHOUT anyone calling `dispose_browser` —
|
|
15
13
|
* the developer closes their agent, the client kills the server, the transport
|
|
16
14
|
* drops. Nothing runs on its own then, so `start.ts` runs this. Without it a
|
|
17
|
-
* container keeps running
|
|
18
|
-
* the session that created it.
|
|
15
|
+
* container keeps running.
|
|
19
16
|
*/
|
|
20
17
|
export declare function resetAll(attachRef: {
|
|
21
18
|
current?: AttachState;
|
|
22
19
|
config: ConfigStore;
|
|
23
|
-
}, captures: CaptureStore
|
|
20
|
+
}, captures: CaptureStore): Promise<void>;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { defineTool } from "../../server.js";
|
|
2
2
|
import { disposeAttached } from "./attach.js";
|
|
3
3
|
import { resetCaptures } from "./capture.js";
|
|
4
|
-
|
|
5
|
-
export function sessionTools(attachRef, captures, liveShare) {
|
|
4
|
+
export function sessionTools(attachRef, captures) {
|
|
6
5
|
return [
|
|
7
6
|
defineTool({
|
|
8
7
|
name: 'get_session',
|
|
@@ -20,51 +19,37 @@ export function sessionTools(attachRef, captures, liveShare) {
|
|
|
20
19
|
orgId: attachRef.current?.orgId,
|
|
21
20
|
captures: [...captures.sessions.keys()],
|
|
22
21
|
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: { url: liveShare.current.url },
|
|
28
|
-
}
|
|
29
|
-
: {}),
|
|
30
22
|
})),
|
|
31
23
|
defineTool({
|
|
32
24
|
name: 'dispose_browser',
|
|
33
25
|
description: 'Stop captures, disconnect the current browser, and immediately '
|
|
34
26
|
+ 'delete a Builder-hosted Popcorn session. Custom CDP sessions '
|
|
35
|
-
+ 'are disconnected but never deleted by Reclaim.
|
|
36
|
-
+ 'public live view opened by share_browser_view.',
|
|
27
|
+
+ 'are disconnected but never deleted by Reclaim.',
|
|
37
28
|
inputSchema: { type: 'object', properties: {} },
|
|
38
29
|
}, async () => {
|
|
39
|
-
await resetAll(attachRef, captures
|
|
30
|
+
await resetAll(attachRef, captures);
|
|
40
31
|
return { disposed: true };
|
|
41
32
|
}),
|
|
42
33
|
defineTool({
|
|
43
34
|
name: 'reset_session',
|
|
44
35
|
description: 'Stop all captures and clear capture state — drops every '
|
|
45
|
-
+ 'session\'s drafts and their in-memory secrets,
|
|
46
|
-
+ 'public live view opened by share_browser_view.',
|
|
36
|
+
+ 'session\'s drafts and their in-memory secrets.',
|
|
47
37
|
inputSchema: { type: 'object', properties: {} },
|
|
48
38
|
}, async () => {
|
|
49
|
-
await resetAll(attachRef, captures
|
|
39
|
+
await resetAll(attachRef, captures);
|
|
50
40
|
return { reset: true };
|
|
51
41
|
}),
|
|
52
42
|
];
|
|
53
43
|
}
|
|
54
44
|
/**
|
|
55
|
-
* Release everything a session holds: the
|
|
56
|
-
* browser itself.
|
|
45
|
+
* Release everything a session holds: the captures and the browser itself.
|
|
57
46
|
*
|
|
58
47
|
* Exported because a session can end WITHOUT anyone calling `dispose_browser` —
|
|
59
48
|
* the developer closes their agent, the client kills the server, the transport
|
|
60
49
|
* drops. Nothing runs on its own then, so `start.ts` runs this. Without it a
|
|
61
|
-
* container keeps running
|
|
62
|
-
* the session that created it.
|
|
50
|
+
* container keeps running.
|
|
63
51
|
*/
|
|
64
|
-
export async function resetAll(attachRef, captures
|
|
65
|
-
// Before the captures: a public URL must never outlive the browser it points
|
|
66
|
-
// at.
|
|
67
|
-
await stopLiveShare(liveShare);
|
|
52
|
+
export async function resetAll(attachRef, captures) {
|
|
68
53
|
await resetCaptures(captures);
|
|
69
54
|
await disposeAttached(attachRef);
|
|
70
55
|
}
|
|
@@ -6,6 +6,7 @@ import { type RegisteredTool } from '../../server.ts';
|
|
|
6
6
|
* plaintext signed JWS (when the org's encryption is off) or an ECIES
|
|
7
7
|
* ciphertext that we decrypt here with the optional eth key
|
|
8
8
|
* (`RECLAIM_DECRYPT_KEY` or the `reclaim-decrypt.key` hex file). The Builder
|
|
9
|
-
*
|
|
9
|
+
* creates, signs, optionally encrypts, and stores the delivery payload.
|
|
10
|
+
* Decryption, when needed, happens here.
|
|
10
11
|
*/
|
|
11
12
|
export declare function verificationResultTool(): RegisteredTool;
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { decryptCallback, ReclaimVerification, } from '@reclaimprotocol/client';
|
|
2
2
|
import assert from 'node:assert';
|
|
3
3
|
import { apiUrl, orgSecret } from "../../../consts.js";
|
|
4
4
|
import { defineTool } from "../../server.js";
|
|
5
5
|
import { resolveDecryptionKey } from "../authenticate/decrypt-key.js";
|
|
6
|
-
/** Statuses that can carry a
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
/** Statuses that can carry a Builder-managed terminal result. */
|
|
7
|
+
const TERMINAL = new Set([
|
|
8
|
+
'success',
|
|
9
|
+
'rejected',
|
|
10
|
+
'error',
|
|
11
|
+
'cancelled',
|
|
12
|
+
'expired',
|
|
13
|
+
]);
|
|
9
14
|
/**
|
|
10
15
|
* Resolve the org secret used to authenticate verification reads. Auth is a
|
|
11
16
|
* single per-org bearer secret (`rorg_…`), not an eth signature — read it from
|
|
@@ -25,25 +30,23 @@ function resolveOrgSecret() {
|
|
|
25
30
|
* plaintext signed JWS (when the org's encryption is off) or an ECIES
|
|
26
31
|
* ciphertext that we decrypt here with the optional eth key
|
|
27
32
|
* (`RECLAIM_DECRYPT_KEY` or the `reclaim-decrypt.key` hex file). The Builder
|
|
28
|
-
*
|
|
33
|
+
* creates, signs, optionally encrypts, and stores the delivery payload.
|
|
34
|
+
* Decryption, when needed, happens here.
|
|
29
35
|
*/
|
|
30
36
|
export function verificationResultTool() {
|
|
31
37
|
return defineTool({
|
|
32
38
|
name: 'get_verification_result',
|
|
33
|
-
description: '
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'act only on result entries where `valid` is true; `ready` means a ' +
|
|
40
|
-
'payload was read, not that verification succeeded. Use ' +
|
|
41
|
-
'list_verification_events to diagnose an unfinished session.',
|
|
39
|
+
description: 'Fetches and verifies a terminal Builder result for one session. '
|
|
40
|
+
+ 'Requires `RECLAIM_ORG_SECRET`; encrypted deliveries also require '
|
|
41
|
+
+ 'the matching ETH decryption key. Returns `ready: false` until a '
|
|
42
|
+
+ 'payload is available. Invalid results fail verification and are not '
|
|
43
|
+
+ 'returned. Use `list_verification_events` to diagnose '
|
|
44
|
+
+ 'an unfinished session.',
|
|
42
45
|
inputSchema: {
|
|
43
46
|
type: 'object',
|
|
44
|
-
required: ['
|
|
47
|
+
required: ['reclaimSessionId'],
|
|
45
48
|
properties: {
|
|
46
|
-
|
|
49
|
+
reclaimSessionId: {
|
|
47
50
|
type: 'string',
|
|
48
51
|
description: 'The verification session id from create_verification_session.',
|
|
49
52
|
},
|
|
@@ -53,31 +56,31 @@ export function verificationResultTool() {
|
|
|
53
56
|
const secret = resolveOrgSecret();
|
|
54
57
|
// Optional eth decryption key — absent ⇒ plaintext-only.
|
|
55
58
|
const decryptionKey = await resolveDecryptionKey();
|
|
56
|
-
const reclaim =
|
|
59
|
+
const reclaim = ReclaimVerification.create({
|
|
57
60
|
baseUrl: apiUrl(),
|
|
58
61
|
orgSecret: secret,
|
|
59
62
|
});
|
|
60
63
|
const sessionRes = await reclaim.api('GetVerificationSession', {
|
|
61
|
-
params: { sessionId: args.
|
|
64
|
+
params: { sessionId: args.reclaimSessionId },
|
|
62
65
|
});
|
|
63
66
|
const status = sessionRes.data.status;
|
|
64
67
|
if (!TERMINAL.has(status)) {
|
|
65
|
-
return {
|
|
68
|
+
return { reclaimSessionId: args.reclaimSessionId, status, ready: false };
|
|
66
69
|
}
|
|
67
70
|
const resultRes = await reclaim.api('GetVerificationResult', {
|
|
68
|
-
params: { sessionId: args.
|
|
71
|
+
params: { sessionId: args.reclaimSessionId },
|
|
69
72
|
});
|
|
70
73
|
const { items } = resultRes.data;
|
|
71
74
|
if (!items.length) {
|
|
72
75
|
return {
|
|
73
|
-
|
|
76
|
+
reclaimSessionId: args.reclaimSessionId,
|
|
74
77
|
status,
|
|
75
78
|
ready: false,
|
|
76
79
|
note: 'Session is terminal but no result has been submitted yet.',
|
|
77
80
|
};
|
|
78
81
|
}
|
|
79
|
-
// Each item's `payload` is an ECIES ciphertext
|
|
80
|
-
//
|
|
82
|
+
// Each item's `payload` is an ECIES ciphertext encrypted to the org's
|
|
83
|
+
// public key, or the plaintext signed JWS. `decryptCallback`
|
|
81
84
|
// branches on the body shape; an encrypted delivery with no key throws
|
|
82
85
|
// a clear error, which we surface per-subscription rather than skip.
|
|
83
86
|
const results = [];
|
|
@@ -93,19 +96,17 @@ export function verificationResultTool() {
|
|
|
93
96
|
continue;
|
|
94
97
|
}
|
|
95
98
|
const outcome = await reclaim.results.verify(jws, {
|
|
96
|
-
|
|
99
|
+
reclaimSessionId: args.reclaimSessionId,
|
|
97
100
|
});
|
|
98
101
|
results.push({
|
|
99
102
|
subscriptionId: item.subscriptionId,
|
|
100
|
-
valid: outcome.valid,
|
|
101
|
-
reason: outcome.reason,
|
|
102
103
|
payload: outcome.payload,
|
|
103
104
|
proofs: outcome.proofs,
|
|
104
105
|
});
|
|
105
106
|
}
|
|
106
107
|
if (!results.length) {
|
|
107
108
|
return {
|
|
108
|
-
|
|
109
|
+
reclaimSessionId: args.reclaimSessionId,
|
|
109
110
|
status,
|
|
110
111
|
ready: false,
|
|
111
112
|
note: 'A result was stored, but no payload could be read — it is ' +
|
|
@@ -115,7 +116,7 @@ export function verificationResultTool() {
|
|
|
115
116
|
};
|
|
116
117
|
}
|
|
117
118
|
return {
|
|
118
|
-
|
|
119
|
+
reclaimSessionId: args.reclaimSessionId,
|
|
119
120
|
status,
|
|
120
121
|
ready: true,
|
|
121
122
|
results,
|
package/lib/old/client.d.ts
CHANGED
|
@@ -48,9 +48,9 @@ export interface RegisterProviderBody {
|
|
|
48
48
|
* controller's destructure + its `providerConfig` write, both of which
|
|
49
49
|
* include it alongside `pageTitle`/`userAgent`. */
|
|
50
50
|
stepsToFollow?: string;
|
|
51
|
-
/** Interception mechanism (`NONE`
|
|
51
|
+
/** Interception mechanism (`NONE` disables interception;
|
|
52
52
|
* `HAWKEYE`/`MSWJS`/`XHOOK` = JS-context interception; `CDP` = browser-
|
|
53
|
-
* level).
|
|
53
|
+
* level). The column defaults to `HAWKEYE` when omitted — unlike
|
|
54
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
|
package/lib/old/flag.d.ts
CHANGED
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
* `USE_OLD_DEVTOOLS` — single source of truth for which backend the MCP
|
|
3
3
|
* server targets.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* EXPLICITLY by setting `USE_OLD_DEVTOOLS=false`. Anything else — unset,
|
|
8
|
-
* empty, `true`, `1`, a typo — keeps old mode on, so a forgotten env var
|
|
9
|
-
* can never silently route a developer at the half-built builder backend.
|
|
5
|
+
* Builder is the default. Only `true` or `1` explicitly selects old devtools;
|
|
6
|
+
* an unset value or typo stays on Builder.
|
|
10
7
|
*
|
|
11
8
|
* The actual env read lives in `../consts.ts` (the single env-access module);
|
|
12
9
|
* this widely-imported path re-exports it for back-compat.
|