@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,79 +1,52 @@
|
|
|
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, type Tunnel } from '../../../live/tunnel.ts'
|
|
17
|
-
import { LOGGER } from '../../../logger.ts'
|
|
18
15
|
import { defineTool, type RegisteredTool } from '../../server.ts'
|
|
19
16
|
import { type AttachState, SHAREABLE_MODES } from './attach.ts'
|
|
20
17
|
import { ensureLiveViewUrl } from './live-view-url.ts'
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
/** The public URL handed to whoever is watching. */
|
|
25
|
-
url: string
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface LiveShareRef {
|
|
29
|
-
current?: LiveShare
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
type TunnelStarter = typeof startTunnel
|
|
33
|
-
|
|
34
|
-
/** Tear down whatever share is running. Safe when none is. Exported so the
|
|
35
|
-
* session tools can call it from `dispose_browser` / `reset_session`. */
|
|
36
|
-
export async function stopLiveShare(ref: LiveShareRef): Promise<boolean> {
|
|
37
|
-
const share = ref.current
|
|
38
|
-
if(!share) {
|
|
39
|
-
return false
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
ref.current = undefined
|
|
43
|
-
// Only the tunnel is ours to close. The browser and its viewer belong to the
|
|
44
|
-
// container, which keeps running until the browser is disposed.
|
|
45
|
-
await share.tunnel.stop().catch((err) => {
|
|
46
|
-
LOGGER.warn({ err }, 'live view: tunnel stop failed')
|
|
47
|
-
})
|
|
48
|
-
return true
|
|
49
|
-
}
|
|
19
|
+
/** Quick-tunnel hostnames only: `https://<words>.trycloudflare.com`. */
|
|
20
|
+
const QUICK_TUNNEL_HOST = /\.trycloudflare\.com$/i
|
|
50
21
|
|
|
51
22
|
export function liveViewTools(
|
|
52
23
|
attachRef: { current?: AttachState },
|
|
53
|
-
shareRef: LiveShareRef,
|
|
54
|
-
openTunnel: TunnelStarter = startTunnel,
|
|
55
24
|
): RegisteredTool[] {
|
|
56
25
|
return [
|
|
57
|
-
defineTool<
|
|
26
|
+
defineTool<{ publicUrl?: string }>(
|
|
58
27
|
{
|
|
59
28
|
name: 'share_browser_view',
|
|
60
29
|
description:
|
|
61
|
-
'
|
|
62
|
-
+ '
|
|
63
|
-
+ '
|
|
64
|
-
+ '
|
|
65
|
-
+ '
|
|
66
|
-
+ '
|
|
67
|
-
+ '
|
|
68
|
-
+ '
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
30
|
+
'Shares an attached `container` browser with someone who is not at '
|
|
31
|
+
+ 'this machine. Step 1: call with no arguments to get the local '
|
|
32
|
+
+ 'viewer URL and the `cloudflared` command to run yourself (this '
|
|
33
|
+
+ 'server does not run it). Step 2: after cloudflared prints its '
|
|
34
|
+
+ '`https://*.trycloudflare.com` URL, call again with it as '
|
|
35
|
+
+ '`publicUrl` to get the link to relay. Treat the link as a '
|
|
36
|
+
+ 'credential. For `builder`, use the `liveViewUrl` from '
|
|
37
|
+
+ '`attach_browser`.',
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
publicUrl: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
description: 'The `https://*.trycloudflare.com` URL that '
|
|
44
|
+
+ 'cloudflared printed.',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
75
48
|
},
|
|
76
|
-
async() => {
|
|
49
|
+
async(parsed) => {
|
|
77
50
|
const attached = attachRef.current
|
|
78
51
|
assert(attached, badRequest(
|
|
79
52
|
'No browser attached. Call attach_browser first.',
|
|
@@ -92,79 +65,66 @@ export function liveViewTools(
|
|
|
92
65
|
+ '"builder" (hosted, no install, chargeable)')
|
|
93
66
|
+ '.',
|
|
94
67
|
))
|
|
95
|
-
assert(!shareRef.current, conflict(
|
|
96
|
-
'A live view is already running at '
|
|
97
|
-
+ `${shareRef.current?.url}. Call stop_browser_view first, or `
|
|
98
|
-
+ 'reuse that URL.',
|
|
99
|
-
))
|
|
100
68
|
|
|
101
69
|
// The container publishes its viewer on loopback and its full CDP
|
|
102
|
-
// port beside it. Only the VIEWER
|
|
103
|
-
// unauthenticated, total control of the browser
|
|
104
|
-
// public URL would hand that to anyone with the link.
|
|
70
|
+
// port beside it. Only the VIEWER may be exposed: the CDP port is
|
|
71
|
+
// unauthenticated, total control of the browser.
|
|
105
72
|
const viewerPort = attached.viewerPort
|
|
106
73
|
assert(viewerPort, badRequest(
|
|
107
74
|
'This browser reports no live-view port. Re-attach with mode '
|
|
108
75
|
+ '"container".',
|
|
109
76
|
))
|
|
110
77
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
78
|
+
if(parsed.publicUrl) {
|
|
79
|
+
const publicUrl = parseQuickTunnelUrl(parsed.publicUrl)
|
|
80
|
+
return {
|
|
81
|
+
url: ensureLiveViewUrl(publicUrl.origin),
|
|
82
|
+
_notes: [
|
|
83
|
+
'Relay `url` to the developer verbatim, on its own line.',
|
|
84
|
+
'The URL is a credential: it needs no login, and anyone with '
|
|
85
|
+
+ 'it can see this browser and act in it as the signed-in user.',
|
|
86
|
+
'It shows the whole browser, so a sign-in that opens a '
|
|
87
|
+
+ 'separate window is visible too.',
|
|
88
|
+
'Opening it on a phone lays the page out for that phone, with '
|
|
89
|
+
+ 'touch, pinch-zoom and the soft keyboard.',
|
|
90
|
+
'When they are done, stop the cloudflared process you started. '
|
|
91
|
+
+ 'The browser keeps running until `dispose_browser`.',
|
|
92
|
+
],
|
|
93
|
+
}
|
|
119
94
|
}
|
|
120
|
-
shareRef.current = share
|
|
121
|
-
LOGGER.info({ mode: attached.mode }, 'live view shared')
|
|
122
95
|
|
|
96
|
+
const localUrl = `http://localhost:${viewerPort}`
|
|
123
97
|
return {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
: {
|
|
128
|
-
propagating: true,
|
|
129
|
-
cloudflaredLog: share.tunnel.cloudflaredLog,
|
|
130
|
-
}),
|
|
131
|
-
...(share.tunnel.binary.source === 'downloaded'
|
|
132
|
-
? { cloudflaredInstalled: true }
|
|
133
|
-
: {}),
|
|
98
|
+
localViewerUrl: ensureLiveViewUrl(localUrl),
|
|
99
|
+
port: viewerPort,
|
|
100
|
+
command: `cloudflared tunnel --url ${localUrl}`,
|
|
134
101
|
_notes: [
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
'
|
|
145
|
-
'
|
|
146
|
-
+ 'can see this browser and act in it as the signed-in user.',
|
|
147
|
-
'It shows the whole browser, so a sign-in that opens a separate '
|
|
148
|
-
+ 'window is visible too — unlike a single-tab stream.',
|
|
149
|
-
'Opening it on a phone lays the page out for that phone, with '
|
|
150
|
-
+ 'touch, pinch-zoom and the soft keyboard.',
|
|
151
|
-
'Call stop_browser_view when they are done. That closes the '
|
|
152
|
-
+ 'tunnel only — the browser keeps running.',
|
|
102
|
+
'Run `command` yourself in a background process. If `cloudflared` '
|
|
103
|
+
+ 'is not installed, tell the developer and ask them to install '
|
|
104
|
+
+ 'it (https://developers.cloudflare.com/cloudflare-one/'
|
|
105
|
+
+ 'connections/connect-networks/downloads/) — do not install it '
|
|
106
|
+
+ 'without their approval.',
|
|
107
|
+
'Read its output for a line containing '
|
|
108
|
+
+ '`https://<words>.trycloudflare.com`. That is the public URL.',
|
|
109
|
+
'Call share_browser_view again with that value as `publicUrl` '
|
|
110
|
+
+ 'to get the link to relay. Never guess or reconstruct the '
|
|
111
|
+
+ 'hostname.',
|
|
112
|
+
'Expose only this port. Never tunnel the browser\'s CDP port.',
|
|
153
113
|
],
|
|
154
114
|
}
|
|
155
115
|
},
|
|
156
116
|
),
|
|
157
|
-
defineTool<Record<string, never>>(
|
|
158
|
-
{
|
|
159
|
-
name: 'stop_browser_view',
|
|
160
|
-
description:
|
|
161
|
-
'Stops the public tunnel opened by share_browser_view; the URL stops '
|
|
162
|
-
+ 'resolving. Call it when the viewer is done. The browser stays '
|
|
163
|
-
+ 'attached, its local viewer keeps serving, and capture continues. '
|
|
164
|
-
+ 'Returns `stopped: false` when no public share is active.',
|
|
165
|
-
inputSchema: { type: 'object', properties: {} },
|
|
166
|
-
},
|
|
167
|
-
async() => ({ stopped: await stopLiveShare(shareRef) }),
|
|
168
|
-
),
|
|
169
117
|
]
|
|
170
118
|
}
|
|
119
|
+
|
|
120
|
+
function parseQuickTunnelUrl(value: string): URL {
|
|
121
|
+
const url = URL.parse(value)
|
|
122
|
+
assert(
|
|
123
|
+
url?.protocol === 'https:' && QUICK_TUNNEL_HOST.test(url.hostname),
|
|
124
|
+
badRequest(
|
|
125
|
+
'publicUrl must be the https://*.trycloudflare.com URL printed by '
|
|
126
|
+
+ 'cloudflared.',
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
return url
|
|
130
|
+
}
|
|
@@ -17,29 +17,13 @@ export function proposeTool(resolveBackend: ResolveBackend): RegisteredTool {
|
|
|
17
17
|
{
|
|
18
18
|
name: 'propose_provider',
|
|
19
19
|
description:
|
|
20
|
-
'
|
|
21
|
-
+ '
|
|
22
|
-
+ '
|
|
23
|
-
+ '
|
|
24
|
-
+ '
|
|
25
|
-
+ '
|
|
26
|
-
+ '
|
|
27
|
-
+ 'its last alphanumeric token, lowercased, to form the param key '
|
|
28
|
-
+ '(for example, "employer_name" → "name") — pick names that stay '
|
|
29
|
-
+ 'distinct '
|
|
30
|
-
+ 'after shortening ("firstName"/"surname", not "first_name"/'
|
|
31
|
-
+ '"last_name"). Pass `shortenNames: false` to use each `name` '
|
|
32
|
-
+ 'VERBATIM as the param key instead — it becomes both a regex '
|
|
33
|
-
+ 'capture-group name and a paramValues key, so it must be a plain '
|
|
34
|
-
+ 'identifier (letters/digits/underscore, not starting with a '
|
|
35
|
-
+ 'digit); we recommend lower_snake_case (for example, '
|
|
36
|
-
+ '"employer_name"), '
|
|
37
|
-
+ 'but you choose the exact name. For a parameter used only to build a '
|
|
38
|
-
+ 'later claim request, preserve a `REQ_` prefix (body, header, or '
|
|
39
|
-
+ 'intermediate value) or `URL_` prefix (path or query value) by '
|
|
40
|
-
+ 'setting `shortenNames: false`. The prefix hides that internal '
|
|
41
|
-
+ 'parameter from the data-point UI; it remains available to named '
|
|
42
|
-
+ 'regexes and `{{param}}` substitutions.',
|
|
20
|
+
'Drafts a provider from one authenticated captured request and '
|
|
21
|
+
+ 'keeps captured secrets server-side. Use `targets` for every value '
|
|
22
|
+
+ 'available in the same response; the result contains a `draftId` '
|
|
23
|
+
+ 'and secret-free recipe. Set `shortenNames: false` to preserve an '
|
|
24
|
+
+ 'explicit parameter name, including a hidden `REQ_` or `URL_` '
|
|
25
|
+
+ 'prefix. For matching and naming guidance, call '
|
|
26
|
+
+ 'how_it_works({ topic: "draft" }) and topic `params`.',
|
|
43
27
|
inputSchema: objectSchema(
|
|
44
28
|
{
|
|
45
29
|
requestId: { type: 'string' },
|
|
@@ -137,6 +137,75 @@ const ALLOWED_JS_REQUEST_SCHEMA: JSONSchema.Schema = {
|
|
|
137
137
|
required: ['url', 'method'],
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
const BROWSER_REQUEST_ACTION_SCHEMA: JSONSchema.Schema = {
|
|
141
|
+
type: 'object',
|
|
142
|
+
description:
|
|
143
|
+
'Portal-only authenticated browser action. The page supplies the action ' +
|
|
144
|
+
'id and declared primitive inputs; this object fixes the HTTPS origin, ' +
|
|
145
|
+
'request template, and returned fields.',
|
|
146
|
+
properties: {
|
|
147
|
+
id: { type: 'string', pattern: '^[A-Za-z][A-Za-z0-9_-]{0,99}$' },
|
|
148
|
+
origin: { type: 'string', pattern: '^https://[^/?#]+$' },
|
|
149
|
+
path: { type: 'string', pattern: '^/(?:$|[^/\\\\][^\\\\]*)$' },
|
|
150
|
+
pathTemplate: {
|
|
151
|
+
type: 'string',
|
|
152
|
+
pattern: '^/(?:$|[^/\\\\][^\\\\]*)$',
|
|
153
|
+
description:
|
|
154
|
+
'Origin-relative path/query template using `{{name}}` inputs.',
|
|
155
|
+
},
|
|
156
|
+
method: {
|
|
157
|
+
type: 'string',
|
|
158
|
+
enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
|
|
159
|
+
},
|
|
160
|
+
transport: { type: 'string', enum: ['fetch', 'navigate'] },
|
|
161
|
+
userAgent: {
|
|
162
|
+
type: 'string',
|
|
163
|
+
minLength: 1,
|
|
164
|
+
maxLength: 300,
|
|
165
|
+
pattern: '^[^\\r\\n]+$',
|
|
166
|
+
description:
|
|
167
|
+
'Browser User-Agent applied before the isolated action page navigates.',
|
|
168
|
+
},
|
|
169
|
+
omitOriginHeader: {
|
|
170
|
+
type: 'boolean',
|
|
171
|
+
description:
|
|
172
|
+
'Remove Chromium\'s automatic Origin header from a fetch action. ' +
|
|
173
|
+
'Unsupported for navigate actions.',
|
|
174
|
+
},
|
|
175
|
+
headers: {
|
|
176
|
+
type: 'object',
|
|
177
|
+
additionalProperties: { type: 'string' },
|
|
178
|
+
},
|
|
179
|
+
headersTemplate: {
|
|
180
|
+
type: 'object',
|
|
181
|
+
description: 'Header values may interpolate declared `{{name}}` inputs.',
|
|
182
|
+
additionalProperties: { type: 'string' },
|
|
183
|
+
},
|
|
184
|
+
bodyTemplate: {
|
|
185
|
+
type: 'string',
|
|
186
|
+
description: 'JSON template using declared `{{name}}` inputs.',
|
|
187
|
+
},
|
|
188
|
+
cookieValueHeader: {
|
|
189
|
+
type: 'object',
|
|
190
|
+
properties: {
|
|
191
|
+
name: { type: 'string' },
|
|
192
|
+
cookieName: { type: 'string' },
|
|
193
|
+
},
|
|
194
|
+
required: ['name', 'cookieName'],
|
|
195
|
+
},
|
|
196
|
+
includeBody: { type: 'boolean' },
|
|
197
|
+
inputKeys: {
|
|
198
|
+
type: 'array',
|
|
199
|
+
items: { type: 'string', pattern: '^[A-Za-z][A-Za-z0-9_]{0,99}$' },
|
|
200
|
+
},
|
|
201
|
+
responseFields: {
|
|
202
|
+
type: 'array',
|
|
203
|
+
items: { type: 'string', pattern: '^[A-Za-z][A-Za-z0-9_]{0,99}$' },
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
required: ['id', 'origin', 'path', 'method'],
|
|
207
|
+
}
|
|
208
|
+
|
|
140
209
|
interface PublishArgs {
|
|
141
210
|
providerId: string
|
|
142
211
|
/** Single-provider back-compat input; normalized to `[provider]`. */
|
|
@@ -150,6 +219,9 @@ interface PublishArgs {
|
|
|
150
219
|
requiredContext?: CreateProviderVersionRequest['requiredContext']
|
|
151
220
|
jsUserScripts?: string
|
|
152
221
|
allowedJsRequests?: AllowedJsRequest[]
|
|
222
|
+
browserRequestActions?: NonNullable<
|
|
223
|
+
CreateProviderVersionRequest['webSettings']['browserRequestActions']
|
|
224
|
+
>
|
|
153
225
|
interceptorOptions?: {
|
|
154
226
|
interceptorType: 'HAWKEYE' | 'MSWJS' | 'CDP'
|
|
155
227
|
interceptorSettings?: string
|
|
@@ -165,24 +237,13 @@ export function createVersionFromCaptureTool(
|
|
|
165
237
|
{
|
|
166
238
|
name: 'create_provider_version_from_capture',
|
|
167
239
|
description:
|
|
168
|
-
'
|
|
169
|
-
'
|
|
170
|
-
'propose_provider
|
|
171
|
-
'
|
|
172
|
-
'
|
|
173
|
-
'
|
|
174
|
-
'
|
|
175
|
-
'creates a new immutable version; never edit an existing version. ' +
|
|
176
|
-
'Omit `version` and `bump` for a patch bump, pass `bump` for a ' +
|
|
177
|
-
'major/minor/patch bump, or pass the exact higher `version` the user ' +
|
|
178
|
-
'directed. The version lands on the ' +
|
|
179
|
-
'draft branch: call `publish_provider_version` (PRIVATE) or ' +
|
|
180
|
-
'`submit_provider_version_for_review` (PUBLIC) to go live. ' +
|
|
181
|
-
'`requiredContext` is derived from any {{context.<name>}} ' +
|
|
182
|
-
'placeholders and returned on the created version — relay it to the ' +
|
|
183
|
-
'dev verbatim, since a session missing those fields is a 400. Always ' +
|
|
184
|
-
'read the response\'s `_notes`. ' +
|
|
185
|
-
'Full walkthrough: how_it_works({ topic: "publish" }).',
|
|
240
|
+
'Creates a new immutable provider version after `run_proof` succeeds. '
|
|
241
|
+
+ 'Pass exactly one `provider` or `providers` value returned by '
|
|
242
|
+
+ '`propose_provider`; do not rebuild the recipe. The default is a '
|
|
243
|
+
+ 'patch bump, or use `bump` or an explicitly requested higher '
|
|
244
|
+
+ '`version`. The new version remains a draft until published or '
|
|
245
|
+
+ 'submitted for review. Read `_notes` and '
|
|
246
|
+
+ 'how_it_works({ topic: "publish" }).',
|
|
186
247
|
inputSchema: {
|
|
187
248
|
type: 'object',
|
|
188
249
|
properties: {
|
|
@@ -264,6 +325,15 @@ export function createVersionFromCaptureTool(
|
|
|
264
325
|
ALLOWED_REQUESTS_HINT,
|
|
265
326
|
items: ALLOWED_JS_REQUEST_SCHEMA,
|
|
266
327
|
},
|
|
328
|
+
browserRequestActions: {
|
|
329
|
+
type: 'array',
|
|
330
|
+
description:
|
|
331
|
+
'Portal-only actions available to jsUserScripts through ' +
|
|
332
|
+
'window.Reclaim.browserRequest. Use these for bounded ' +
|
|
333
|
+
'same-origin requests that need the browser cookie jar. ' +
|
|
334
|
+
'Other Verification Clients currently ignore them.',
|
|
335
|
+
items: BROWSER_REQUEST_ACTION_SCHEMA,
|
|
336
|
+
},
|
|
267
337
|
interceptorOptions: {
|
|
268
338
|
type: 'object',
|
|
269
339
|
description:
|
|
@@ -328,6 +398,7 @@ export function createVersionFromCaptureTool(
|
|
|
328
398
|
requiredContext: args.requiredContext,
|
|
329
399
|
jsUserScripts: args.jsUserScripts,
|
|
330
400
|
allowedJsRequests: args.allowedJsRequests,
|
|
401
|
+
browserRequestActions: args.browserRequestActions,
|
|
331
402
|
interceptorOptions: args.interceptorOptions,
|
|
332
403
|
})
|
|
333
404
|
const result = await client.call('CreateProviderVersion', {
|
|
@@ -2,12 +2,10 @@ import type { ConfigStore } from '../../../config/store.ts'
|
|
|
2
2
|
import { defineTool, type RegisteredTool } from '../../server.ts'
|
|
3
3
|
import { type AttachState, disposeAttached } from './attach.ts'
|
|
4
4
|
import { type CaptureStore, resetCaptures } from './capture.ts'
|
|
5
|
-
import { type LiveShareRef, stopLiveShare } from './live-view.ts'
|
|
6
5
|
|
|
7
6
|
export function sessionTools(
|
|
8
7
|
attachRef: { current?: AttachState, config: ConfigStore },
|
|
9
8
|
captures: CaptureStore,
|
|
10
|
-
liveShare: LiveShareRef,
|
|
11
9
|
): RegisteredTool[] {
|
|
12
10
|
return [
|
|
13
11
|
defineTool<Record<string, never>>(
|
|
@@ -29,13 +27,6 @@ export function sessionTools(
|
|
|
29
27
|
orgId: attachRef.current?.orgId,
|
|
30
28
|
captures: [...captures.sessions.keys()],
|
|
31
29
|
activeTab: captures.activeTab,
|
|
32
|
-
// The public share, when one is open — so a caller that lost the
|
|
33
|
-
// URL can recover it instead of opening a second tunnel.
|
|
34
|
-
...(liveShare.current
|
|
35
|
-
? {
|
|
36
|
-
sharedView: { url: liveShare.current.url },
|
|
37
|
-
}
|
|
38
|
-
: {}),
|
|
39
30
|
}),
|
|
40
31
|
),
|
|
41
32
|
defineTool<Record<string, never>>(
|
|
@@ -44,12 +35,11 @@ export function sessionTools(
|
|
|
44
35
|
description:
|
|
45
36
|
'Stop captures, disconnect the current browser, and immediately '
|
|
46
37
|
+ 'delete a Builder-hosted Popcorn session. Custom CDP sessions '
|
|
47
|
-
+ 'are disconnected but never deleted by Reclaim.
|
|
48
|
-
+ 'public live view opened by share_browser_view.',
|
|
38
|
+
+ 'are disconnected but never deleted by Reclaim.',
|
|
49
39
|
inputSchema: { type: 'object', properties: {} },
|
|
50
40
|
},
|
|
51
41
|
async() => {
|
|
52
|
-
await resetAll(attachRef, captures
|
|
42
|
+
await resetAll(attachRef, captures)
|
|
53
43
|
return { disposed: true }
|
|
54
44
|
},
|
|
55
45
|
),
|
|
@@ -58,12 +48,11 @@ export function sessionTools(
|
|
|
58
48
|
name: 'reset_session',
|
|
59
49
|
description:
|
|
60
50
|
'Stop all captures and clear capture state — drops every '
|
|
61
|
-
+ 'session\'s drafts and their in-memory secrets,
|
|
62
|
-
+ 'public live view opened by share_browser_view.',
|
|
51
|
+
+ 'session\'s drafts and their in-memory secrets.',
|
|
63
52
|
inputSchema: { type: 'object', properties: {} },
|
|
64
53
|
},
|
|
65
54
|
async() => {
|
|
66
|
-
await resetAll(attachRef, captures
|
|
55
|
+
await resetAll(attachRef, captures)
|
|
67
56
|
return { reset: true }
|
|
68
57
|
},
|
|
69
58
|
),
|
|
@@ -71,23 +60,17 @@ export function sessionTools(
|
|
|
71
60
|
}
|
|
72
61
|
|
|
73
62
|
/**
|
|
74
|
-
* Release everything a session holds: the
|
|
75
|
-
* browser itself.
|
|
63
|
+
* Release everything a session holds: the captures and the browser itself.
|
|
76
64
|
*
|
|
77
65
|
* Exported because a session can end WITHOUT anyone calling `dispose_browser` —
|
|
78
66
|
* the developer closes their agent, the client kills the server, the transport
|
|
79
67
|
* drops. Nothing runs on its own then, so `start.ts` runs this. Without it a
|
|
80
|
-
* container keeps running
|
|
81
|
-
* the session that created it.
|
|
68
|
+
* container keeps running.
|
|
82
69
|
*/
|
|
83
70
|
export async function resetAll(
|
|
84
71
|
attachRef: { current?: AttachState, config: ConfigStore },
|
|
85
72
|
captures: CaptureStore,
|
|
86
|
-
liveShare: LiveShareRef,
|
|
87
73
|
) {
|
|
88
|
-
// Before the captures: a public URL must never outlive the browser it points
|
|
89
|
-
// at.
|
|
90
|
-
await stopLiveShare(liveShare)
|
|
91
74
|
await resetCaptures(captures)
|
|
92
75
|
await disposeAttached(attachRef)
|
|
93
76
|
}
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createReclaim,
|
|
3
2
|
decryptCallback,
|
|
4
3
|
type DecryptionKey,
|
|
4
|
+
ReclaimVerification,
|
|
5
5
|
} from '@reclaimprotocol/client'
|
|
6
6
|
import assert from 'node:assert'
|
|
7
7
|
import { apiUrl, orgSecret } from '../../../consts.ts'
|
|
8
8
|
import { defineTool, type RegisteredTool } from '../../server.ts'
|
|
9
9
|
import { resolveDecryptionKey } from '../authenticate/decrypt-key.ts'
|
|
10
10
|
|
|
11
|
-
/** Statuses that can carry a
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
/** Statuses that can carry a Builder-managed terminal result. */
|
|
12
|
+
const TERMINAL = new Set([
|
|
13
|
+
'success',
|
|
14
|
+
'rejected',
|
|
15
|
+
'error',
|
|
16
|
+
'cancelled',
|
|
17
|
+
'expired',
|
|
18
|
+
])
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
21
|
* Resolve the org secret used to authenticate verification reads. Auth is a
|
|
@@ -37,27 +42,25 @@ function resolveOrgSecret(): string {
|
|
|
37
42
|
* plaintext signed JWS (when the org's encryption is off) or an ECIES
|
|
38
43
|
* ciphertext that we decrypt here with the optional eth key
|
|
39
44
|
* (`RECLAIM_DECRYPT_KEY` or the `reclaim-decrypt.key` hex file). The Builder
|
|
40
|
-
*
|
|
45
|
+
* creates, signs, optionally encrypts, and stores the delivery payload.
|
|
46
|
+
* Decryption, when needed, happens here.
|
|
41
47
|
*/
|
|
42
48
|
export function verificationResultTool(): RegisteredTool {
|
|
43
|
-
return defineTool<{
|
|
49
|
+
return defineTool<{ reclaimSessionId: string }>(
|
|
44
50
|
{
|
|
45
51
|
name: 'get_verification_result',
|
|
46
52
|
description:
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'act only on result entries where `valid` is true; `ready` means a ' +
|
|
54
|
-
'payload was read, not that verification succeeded. Use ' +
|
|
55
|
-
'list_verification_events to diagnose an unfinished session.',
|
|
53
|
+
'Fetches and verifies a terminal Builder result for one session. '
|
|
54
|
+
+ 'Requires `RECLAIM_ORG_SECRET`; encrypted deliveries also require '
|
|
55
|
+
+ 'the matching ETH decryption key. Returns `ready: false` until a '
|
|
56
|
+
+ 'payload is available. Invalid results fail verification and are not '
|
|
57
|
+
+ 'returned. Use `list_verification_events` to diagnose '
|
|
58
|
+
+ 'an unfinished session.',
|
|
56
59
|
inputSchema: {
|
|
57
60
|
type: 'object',
|
|
58
|
-
required: ['
|
|
61
|
+
required: ['reclaimSessionId'],
|
|
59
62
|
properties: {
|
|
60
|
-
|
|
63
|
+
reclaimSessionId: {
|
|
61
64
|
type: 'string',
|
|
62
65
|
description:
|
|
63
66
|
'The verification session id from create_verification_session.',
|
|
@@ -71,36 +74,36 @@ export function verificationResultTool(): RegisteredTool {
|
|
|
71
74
|
const decryptionKey: DecryptionKey | undefined =
|
|
72
75
|
await resolveDecryptionKey()
|
|
73
76
|
|
|
74
|
-
const reclaim =
|
|
77
|
+
const reclaim = ReclaimVerification.create({
|
|
75
78
|
baseUrl: apiUrl(),
|
|
76
79
|
orgSecret: secret,
|
|
77
80
|
})
|
|
78
81
|
|
|
79
82
|
const sessionRes = await reclaim.api('GetVerificationSession', {
|
|
80
|
-
params: { sessionId: args.
|
|
83
|
+
params: { sessionId: args.reclaimSessionId },
|
|
81
84
|
})
|
|
82
85
|
const status = sessionRes.data.status
|
|
83
86
|
|
|
84
87
|
if(!TERMINAL.has(status)) {
|
|
85
|
-
return {
|
|
88
|
+
return { reclaimSessionId: args.reclaimSessionId, status, ready: false }
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
const resultRes = await reclaim.api('GetVerificationResult', {
|
|
89
|
-
params: { sessionId: args.
|
|
92
|
+
params: { sessionId: args.reclaimSessionId },
|
|
90
93
|
})
|
|
91
94
|
const { items } = resultRes.data
|
|
92
95
|
|
|
93
96
|
if(!items.length) {
|
|
94
97
|
return {
|
|
95
|
-
|
|
98
|
+
reclaimSessionId: args.reclaimSessionId,
|
|
96
99
|
status,
|
|
97
100
|
ready: false,
|
|
98
101
|
note: 'Session is terminal but no result has been submitted yet.',
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
104
|
|
|
102
|
-
// Each item's `payload` is an ECIES ciphertext
|
|
103
|
-
//
|
|
105
|
+
// Each item's `payload` is an ECIES ciphertext encrypted to the org's
|
|
106
|
+
// public key, or the plaintext signed JWS. `decryptCallback`
|
|
104
107
|
// branches on the body shape; an encrypted delivery with no key throws
|
|
105
108
|
// a clear error, which we surface per-subscription rather than skip.
|
|
106
109
|
const results: unknown[] = []
|
|
@@ -116,12 +119,10 @@ export function verificationResultTool(): RegisteredTool {
|
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
const outcome = await reclaim.results.verify(jws, {
|
|
119
|
-
|
|
122
|
+
reclaimSessionId: args.reclaimSessionId,
|
|
120
123
|
})
|
|
121
124
|
results.push({
|
|
122
125
|
subscriptionId: item.subscriptionId,
|
|
123
|
-
valid: outcome.valid,
|
|
124
|
-
reason: outcome.reason,
|
|
125
126
|
payload: outcome.payload,
|
|
126
127
|
proofs: outcome.proofs,
|
|
127
128
|
})
|
|
@@ -129,7 +130,7 @@ export function verificationResultTool(): RegisteredTool {
|
|
|
129
130
|
|
|
130
131
|
if(!results.length) {
|
|
131
132
|
return {
|
|
132
|
-
|
|
133
|
+
reclaimSessionId: args.reclaimSessionId,
|
|
133
134
|
status,
|
|
134
135
|
ready: false,
|
|
135
136
|
note:
|
|
@@ -141,7 +142,7 @@ export function verificationResultTool(): RegisteredTool {
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
return {
|
|
144
|
-
|
|
145
|
+
reclaimSessionId: args.reclaimSessionId,
|
|
145
146
|
status,
|
|
146
147
|
ready: true,
|
|
147
148
|
results,
|
package/src/old/client.ts
CHANGED
|
@@ -47,9 +47,9 @@ export interface RegisterProviderBody {
|
|
|
47
47
|
* controller's destructure + its `providerConfig` write, both of which
|
|
48
48
|
* include it alongside `pageTitle`/`userAgent`. */
|
|
49
49
|
stepsToFollow?: string
|
|
50
|
-
/** Interception mechanism (`NONE`
|
|
50
|
+
/** Interception mechanism (`NONE` disables interception;
|
|
51
51
|
* `HAWKEYE`/`MSWJS`/`XHOOK` = JS-context interception; `CDP` = browser-
|
|
52
|
-
* level).
|
|
52
|
+
* level). The column defaults to `HAWKEYE` when omitted — unlike
|
|
53
53
|
* `stepsToFollow`, and so on. above, whether the register endpoint actually
|
|
54
54
|
* PERSISTS this field is UNVERIFIED (no source access to confirm); sent
|
|
55
55
|
* here on the chance it does, but the publish tool always follows up
|