@reclaimprotocol/agent 0.1.40 → 0.1.42

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.
Files changed (44) hide show
  1. package/README.md +32 -0
  2. package/lib/authoring/tools.js +4 -1
  3. package/lib/config/store.d.ts +7 -0
  4. package/lib/consts.d.ts +5 -0
  5. package/lib/consts.js +10 -0
  6. package/lib/mcp/api/operations.gen.js +6 -2
  7. package/lib/mcp/api/tools.js +4 -0
  8. package/lib/mcp/server.js +6 -1
  9. package/lib/mcp/start.js +27 -1
  10. package/lib/mcp/tools/agent/proof.js +4 -2
  11. package/lib/mcp/tools/agent/propose.js +7 -0
  12. package/lib/mcp/tools/agent/publish.js +4 -1
  13. package/lib/mcp/tools/agent/replay.js +3 -1
  14. package/lib/mcp/tools/agent/start-integration.d.ts +2 -0
  15. package/lib/mcp/tools/agent/start-integration.js +7 -0
  16. package/lib/mcp/tools/authenticate/authenticate.js +3 -0
  17. package/lib/telemetry/index.d.ts +9 -0
  18. package/lib/telemetry/index.js +13 -0
  19. package/lib/telemetry/telemetry.d.ts +54 -0
  20. package/lib/telemetry/telemetry.js +175 -0
  21. package/lib/telemetry/tool-call.d.ts +24 -0
  22. package/lib/telemetry/tool-call.js +102 -0
  23. package/package.json +2 -2
  24. package/skill/SKILL.builder.md +5 -0
  25. package/skill/SKILL.common.md +34 -3
  26. package/skill/SKILL.old.md +5 -0
  27. package/skill/builder-mode.md +1 -0
  28. package/skill/old-mode.md +1 -0
  29. package/src/authoring/tools.ts +4 -1
  30. package/src/config/store.ts +4 -0
  31. package/src/consts.ts +11 -0
  32. package/src/mcp/api/operations.gen.ts +6 -2
  33. package/src/mcp/api/tools.ts +4 -0
  34. package/src/mcp/server.ts +6 -1
  35. package/src/mcp/start.ts +34 -0
  36. package/src/mcp/tools/agent/proof.ts +4 -2
  37. package/src/mcp/tools/agent/propose.ts +8 -0
  38. package/src/mcp/tools/agent/publish.ts +4 -1
  39. package/src/mcp/tools/agent/replay.ts +3 -1
  40. package/src/mcp/tools/agent/start-integration.ts +10 -0
  41. package/src/mcp/tools/authenticate/authenticate.ts +3 -0
  42. package/src/telemetry/index.ts +18 -0
  43. package/src/telemetry/telemetry.ts +206 -0
  44. package/src/telemetry/tool-call.ts +115 -0
@@ -0,0 +1,102 @@
1
+ import { isProblemError } from '@reclaimprotocol/client/api';
2
+ import { currentTelemetry } from "./index.js";
3
+ const GOAL_MAX = 300;
4
+ const USER_REQUEST_MAX = 500;
5
+ const DOMAIN_MAX = 200;
6
+ const DATA_POINTS_MAX = 20;
7
+ const DATA_POINT_MAX = 200;
8
+ /**
9
+ * Times a tool call and records it. Call `end()` once when the call
10
+ * finishes. It never throws, so it can't change the outcome of the call.
11
+ */
12
+ export function startToolCall(tool, args) {
13
+ const startedAt = Date.now();
14
+ return {
15
+ end(err) {
16
+ try {
17
+ const telemetry = currentTelemetry();
18
+ if (!telemetry) {
19
+ return;
20
+ }
21
+ const input = allowlistedInput(tool, args);
22
+ telemetry.track('tool_call', {
23
+ tool,
24
+ ok: err === undefined,
25
+ ...(err === undefined ? {} : { errorCode: errorCode(err) }),
26
+ durationMs: Date.now() - startedAt,
27
+ mode: telemetry.mode,
28
+ ...(input ? { input } : {}),
29
+ });
30
+ }
31
+ catch {
32
+ // Telemetry must never affect a tool call.
33
+ }
34
+ },
35
+ };
36
+ }
37
+ /**
38
+ * A category for the error, such as `http_401`. It's never the message,
39
+ * because messages can contain URLs, tokens, or file paths.
40
+ */
41
+ export function errorCode(err) {
42
+ if (isProblemError(err)) {
43
+ return `http_${err.output.statusCode}`;
44
+ }
45
+ if (isRecord(err) && err.isBoom === true && isRecord(err.output)) {
46
+ return `http_${String(err.output.statusCode)}`;
47
+ }
48
+ if (err instanceof Error) {
49
+ if (err.name === 'ZodError') {
50
+ return 'invalid_input';
51
+ }
52
+ if (err.name === 'TimeoutError' || err.name === 'AbortError') {
53
+ return 'timeout';
54
+ }
55
+ }
56
+ return 'error';
57
+ }
58
+ /**
59
+ * The only arguments Reclaim records, and only for these tools. They are the
60
+ * developer's own description of what they want to build, which tells us
61
+ * which providers to add. Every other argument is dropped here, so a new tool
62
+ * records nothing until you add it to this list on purpose.
63
+ */
64
+ export function allowlistedInput(tool, args) {
65
+ if (!isRecord(args)) {
66
+ return undefined;
67
+ }
68
+ switch (tool) {
69
+ case 'start_integration':
70
+ return compact({
71
+ goal: cut(args.goal, GOAL_MAX),
72
+ userRequest: cut(args.userRequest, USER_REQUEST_MAX),
73
+ });
74
+ case 'propose_provider':
75
+ return compact({ userRequest: cut(args.userRequest, USER_REQUEST_MAX) });
76
+ case 'discover_resolve': {
77
+ const query = isRecord(args.query) ? args.query : {};
78
+ const dataPoints = Array.isArray(query.dataPoints)
79
+ ? query.dataPoints
80
+ .slice(0, DATA_POINTS_MAX)
81
+ .map((p) => cut(p, DATA_POINT_MAX))
82
+ .filter((p) => p !== undefined)
83
+ : undefined;
84
+ return compact({
85
+ domain: cut(query.domain, DOMAIN_MAX),
86
+ dataPoints: dataPoints?.length ? dataPoints : undefined,
87
+ });
88
+ }
89
+ default:
90
+ return undefined;
91
+ }
92
+ }
93
+ function cut(value, max) {
94
+ return typeof value === 'string' && value ? value.slice(0, max) : undefined;
95
+ }
96
+ /** Returns the object, or undefined when every field is undefined. */
97
+ function compact(obj) {
98
+ return Object.values(obj).some((v) => v !== undefined) ? obj : undefined;
99
+ }
100
+ function isRecord(value) {
101
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
102
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reclaimprotocol/agent",
3
3
  "title": "Reclaim Agent",
4
- "version": "0.1.40",
4
+ "version": "0.1.42",
5
5
  "description": "Reclaim Protocol tools for AI coding agents. Reclaim lets your app verify data points about its users, such as a follower count, an account balance, or an order history. This package is the Reclaim MCP server: it gives your agent the tools to find, build, test, and fix data verification providers by capturing and replaying browser requests, drafting provider recipes, running zkTLS proofs, and publishing provider versions.",
6
6
  "type": "module",
7
7
  "main": "./lib/index.js",
@@ -84,7 +84,7 @@
84
84
  "@noble/hashes": "^2.2.0",
85
85
  "@puppeteer/browsers": "3.2.4",
86
86
  "@reclaimprotocol/attestor-core": "^5.1.6",
87
- "@reclaimprotocol/client": "0.1.40",
87
+ "@reclaimprotocol/client": "0.1.42",
88
88
  "@reclaimprotocol/tls": "^0.1.3",
89
89
  "chrome-remote-interface": "^0.33.0",
90
90
  "domhandler": "^6.0.0",
@@ -46,6 +46,11 @@ or asynchronous question tool for a blocking question.
46
46
  `webSettings.jsUserScripts`) → `publish_provider_version` or
47
47
  `submit_provider_version_for_review`. → topic `publish`
48
48
 
49
+ Replay or proof blocked with 403 after one retry and one correction? Stop
50
+ retrying and go straight to step 11: publish the draft unproven from the
51
+ captured request, then ask the developer to try a real verification. → topic
52
+ `prove`, "publish from the capture".
53
+
49
54
  Data points on different requests? Run steps 7-10 once per request, then
50
55
  publish every proven draft in one call. See topic `publish`.
51
56
 
@@ -409,9 +409,37 @@ supplies are context params.
409
409
  often after `replay_request` succeeded): the site blocks clients that
410
410
  aren't a real browser (bot protection or TLS fingerprint). Copying
411
411
  headers or cookies won't help, and `run_proof_via_pod` doesn't change the
412
- TLS fingerprint. Try a different endpoint that returns the value (a JSON
413
- API the page calls), or stop and report. Run `check_matchers` first so a
414
- recipe bug isn't mistaken for blocking.
412
+ TLS fingerprint. Run `check_matchers` first so a recipe bug isn't
413
+ mistaken for blocking, then try ONE other endpoint that returns the value
414
+ (a JSON API the page calls). Blocked there too → publish from the
415
+ capture, below.
416
+
417
+ **Replay or proof keeps returning 403 (or another block): publish from
418
+ the capture.** A 403 from `replay_request` or `run_proof` says the site
419
+ defends against the authoring path. It does not say the recipe is wrong,
420
+ and a real verification runs inside the end user's own browser session,
421
+ which those defenses usually let through. Retry budget: one retry of the
422
+ same call, one correction (another endpoint, a `geoLocation` pin, or a
423
+ matcher fix via `check_matchers`), and one retry after the correction.
424
+ Past that, STOP. Don't re-capture, re-propose, or re-prove the same
425
+ request, and don't wait for the developer to tell you to stop. Instead:
426
+
427
+ 1. Say plainly that replay/proof is blocked and that you are publishing
428
+ the version UNPROVEN, from the request you intercepted. Never say it
429
+ was proven.
430
+ 2. Publish the draft as topic `publish` says
431
+ (`create_provider_version_from_capture`, then go live). The draft
432
+ holds the intercepted request, selectors, and params; that is all the
433
+ version needs.
434
+ 3. Ask the developer to try it with a real verification. Builder mode:
435
+ `create_verification_session` and put its URL in the message. Legacy
436
+ devtools mode: the "try it" option in topic `publish`. End your turn
437
+ there.
438
+ 4. When they return, check the session the usual way
439
+ (`get_verification_session` / `list_verification_events`, or the
440
+ devtools equivalent). Passed → done; it was only the authoring path
441
+ that was blocked. Failed with the same block → the site blocks
442
+ verification too; say so and look for a different endpoint.
415
443
 
416
444
  **Do not auto-retry with a different `attestorUrl`.** There is one live
417
445
  public attestor; backup hostnames (`attestor-2.*`, and so on) **do not
@@ -431,6 +459,9 @@ supplies are context params.
431
459
  never repeat them.
432
460
  - Never call `run_proof` before `replay_request` succeeds. Attestor calls are
433
461
  expensive.
462
+ - Never loop a blocked replay or proof. A 403 that survives the retry budget
463
+ in `prove` means publish from the capture, unproven, and hand the developer
464
+ a real verification to try.
434
465
  - Never pretend a proof succeeded when `verified: false`. Show the error and
435
466
  iterate.
436
467
  - Never write a literal value into `responseMatches.value` — that bakes the
@@ -28,6 +28,11 @@ topic before a step you aren't already fluent in.
28
28
  → topics `prove`, `credentials`
29
29
  - [ ] 10. `create_provider_version_from_capture`. → topic `publish`
30
30
 
31
+ Replay or proof blocked with 403 after one retry and one correction? Stop
32
+ retrying and go straight to step 10: publish the draft unproven from the
33
+ captured request, then ask the developer to try a real verification. → topic
34
+ `prove`, "publish from the capture".
35
+
31
36
  Multi-request provider? Run steps 6–9 once per request, then publish every
32
37
  proven draft in one call. See topic `publish`.
33
38
 
@@ -16,6 +16,7 @@ Provider-authoring and verification tools for the Builder backend.
16
16
  - Call `attach_browser()` with no arguments. Don't ask which browser to use.
17
17
  - Propose only the data points the claim needs.
18
18
  - Provider versions are immutable: every change creates a new version.
19
+ - A 403 (or similar block) from `replay_request` or `run_proof` that survives one retry and one correction is not a reason to keep retrying. Publish the version from the captured request, say it is unproven, and ask the developer to try a real verification (topic `prove`).
19
20
 
20
21
  ## Asking the developer
21
22
 
package/skill/old-mode.md CHANGED
@@ -9,6 +9,7 @@ Provider-authoring tools for the old devtools backend. The tool surface is reduc
9
9
  - A provider found with `get_me_providers` or `get_provider_info` is published and usable as-is. Don't capture, replay, or prove it unless the developer asks.
10
10
  - Before using a provider, found or just created, ask once how they want to use it (try it, integrate it, or build a new app), unless they already said. Topic `publish` has the options.
11
11
  - Call `attach_browser()` with no arguments. Don't ask which browser to use.
12
+ - A 403 (or similar block) from `replay_request` or `run_proof` that survives one retry and one correction is not a reason to keep retrying. Publish the version from the captured request, say it is unproven, and ask the developer to try it (topic `prove`).
12
13
 
13
14
  ## Asking the developer
14
15
 
@@ -521,7 +521,10 @@ export function authoringTools(
521
521
  + 'of the site, not a bug in the draft. HTTP 403/406/429/503 or a '
522
522
  + 'challenge page from the attestor while the replay succeeded '
523
523
  + 'means the site blocks non-browser clients: copying headers or '
524
- + 'cookies will not help, so try a different endpoint or stop.',
524
+ + 'cookies will not help. Try one other endpoint; if that is '
525
+ + 'blocked too, publish the version from the capture and have '
526
+ + 'the developer try a real verification '
527
+ + '(how_it_works({ topic: "prove" })). Do not keep retrying.',
525
528
  inputSchema: objectSchema({ draftId: { type: 'string' } }, ['draftId']),
526
529
  annotations: { openWorldHint: true, destructiveHint: false },
527
530
  },
@@ -27,6 +27,9 @@ export interface ReclaimAgentConfig {
27
27
  * - `old_devtools` — externally-supplied identity for OLD-devtools mode
28
28
  * (USE_OLD_DEVTOOLS); independent of `session`'s
29
29
  * builder token. Deletable with the rest of `src/old/`.
30
+ * - `analytics` — the random, anonymous client ID the usage data is
31
+ * sent under (see telemetry/telemetry.ts); holds no
32
+ * personal data
30
33
  * - `org_keys` — organization API keys saved by `issue_org_token`, so
31
34
  * the org-key tools work without the key in a transcript
32
35
  * Sections are persisted independently via `ConfigStore.write`, which
@@ -43,6 +46,7 @@ export interface ReclaimConfig {
43
46
  session?: CachedSession
44
47
  old_devtools?: OldIdentity
45
48
  org_keys?: SavedOrgKeys
49
+ analytics?: { clientId: string, createdAt: string }
46
50
  }
47
51
 
48
52
  /** Organization API keys (`rorg_…`) saved by `issue_org_token`. */
package/src/consts.ts CHANGED
@@ -143,6 +143,17 @@ export function agentDisabled(): boolean {
143
143
  return process.env.RECLAIM_AGENT_DISABLED === '1'
144
144
  }
145
145
 
146
+ /**
147
+ * Whether the server may send anonymous usage data. Off when you set
148
+ * `RECLAIM_TELEMETRY=0` or the cross-tool `DO_NOT_TRACK=1` convention.
149
+ */
150
+ export function telemetryEnabled(): boolean {
151
+ const doNotTrack = process.env.DO_NOT_TRACK?.toLowerCase()
152
+ return process.env.RECLAIM_TELEMETRY !== '0'
153
+ && doNotTrack !== '1'
154
+ && doNotTrack !== 'true'
155
+ }
156
+
146
157
  /** Old-devtools backend base URL. */
147
158
  export function oldApiUrl(): string {
148
159
  return process.env.RECLAIM_OLD_API_URL || DEFAULT_OLD_API_URL
@@ -1,5 +1,5 @@
1
1
  // AUTO-GENERATED by packages/app/scripts/mcp-tools-gen.ts
2
- // from packages/app/openapi.yaml (spec v0.1.0, source sha256:ecdb0ee6d82e).
2
+ // from packages/app/openapi.yaml (spec v0.1.0, source sha256:c3421d6bcf76).
3
3
  // The hash catches drift the version misses (a spec can change without a
4
4
  // version bump) — do not remove it as "redundant" with the version.
5
5
  // Run `npm run openapi:gen --workspace=packages/app` to regenerate. Do not edit by hand.
@@ -123,7 +123,11 @@ export const MCP_TOOLS = {
123
123
  "enum": [
124
124
  "providers",
125
125
  "revenue",
126
- "usage"
126
+ "usage",
127
+ "goals",
128
+ "tools",
129
+ "agents",
130
+ "funnel"
127
131
  ]
128
132
  }
129
133
  }
@@ -7,6 +7,7 @@ import type {
7
7
  } from '@reclaimprotocol/client/openapi'
8
8
  import assert from 'node:assert'
9
9
  import { orgSecret } from '../../consts.ts'
10
+ import { startToolCall } from '../../telemetry/index.ts'
10
11
  import { remapErrorAsResponse } from '../remap_errors.ts'
11
12
  import { successResponse } from '../success-response.ts'
12
13
  import { savedOrgKey } from '../tools/authenticate/org-key-store.ts'
@@ -68,6 +69,7 @@ export function registerTools(
68
69
  toSnakeCase(opId),
69
70
  shape,
70
71
  async(input: unknown) => {
72
+ const call = startToolCall(toSnakeCase(opId), input)
71
73
  try {
72
74
  if(!isRecord(input)) {
73
75
  throw new Error(`Invalid generated MCP arguments for ${opId}`)
@@ -85,8 +87,10 @@ export function registerTools(
85
87
  opId,
86
88
  args,
87
89
  )
90
+ call.end()
88
91
  return successResponse(result.data)
89
92
  } catch(err) {
93
+ call.end(err)
90
94
  return remapErrorAsResponse(err)
91
95
  }
92
96
  },
package/src/mcp/server.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
+ import { startToolCall } from '../telemetry/index.ts'
2
3
  import { type RegisteredTool } from '../tools/registry.ts'
3
4
  import { toZod } from './api/to-zod.ts'
4
5
  import { remapErrorAsResponse } from './remap_errors.ts'
@@ -31,9 +32,13 @@ export function registerToolList(server: McpServer, tools: RegisteredTool[]) {
31
32
  : {}),
32
33
  },
33
34
  async(args) => {
35
+ const call = startToolCall(tool.definition.name, args)
34
36
  try {
35
- return successResponse(await tool.handler(args))
37
+ const result = successResponse(await tool.handler(args))
38
+ call.end()
39
+ return result
36
40
  } catch(err) {
41
+ call.end(err)
37
42
  return remapErrorAsResponse(err)
38
43
  }
39
44
  },
package/src/mcp/start.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  oldLogsUrl,
11
11
  resolveOldMode,
12
12
  sdkApiUrl,
13
+ telemetryEnabled,
13
14
  } from '../consts.ts'
14
15
  import { LOGGER } from '../logger.ts'
15
16
  import { ReclaimOldClient } from '../old/client.ts'
@@ -19,6 +20,11 @@ import { ReclaimSdkClient } from '../old/sdk-client.ts'
19
20
  import { buildOldBackendTools } from '../old/tools/build.ts'
20
21
  import { NAME, VERSION } from '../pkg.ts'
21
22
  import { startZkFilesDownload } from '../proof/zk-files.ts'
23
+ import {
24
+ currentTelemetry,
25
+ installTelemetry,
26
+ Telemetry
27
+ } from '../telemetry/index.ts'
22
28
  import { registerTools } from './api/tools.ts'
23
29
  import {
24
30
  type AgentState,
@@ -78,6 +84,13 @@ export async function startMcpServer() {
78
84
  }),
79
85
  headers: { 'User-Agent': `${NAME}/${VERSION}` },
80
86
  })
87
+ // Anonymous usage data. Installed before any tool registers so every tool
88
+ // call is covered. When the developer opts out, nothing is created.
89
+ const mode = oldMode ? 'devtools' : 'builder'
90
+ if(telemetryEnabled()) {
91
+ installTelemetry(new Telemetry({ client: builderClient, mode }))
92
+ }
93
+
81
94
  // Backend-mode toggle — available in both modes and regardless of whether
82
95
  // the agent tools are enabled, so a user can always switch either direction.
83
96
  registerToolList(server, buildModeTools(config, oldMode))
@@ -151,6 +164,19 @@ export async function startMcpServer() {
151
164
  installShutdownHooks(() => agentState)
152
165
  }
153
166
 
167
+ // The client's name and version arrive in the `initialize` handshake, so
168
+ // the session starts only after it completes.
169
+ server.server.oninitialized = () => {
170
+ const telemetry = currentTelemetry()
171
+ telemetry?.setAgent(server.server.getClientVersion())
172
+ telemetry?.track('session_start', {
173
+ mode,
174
+ agentTools: agentToolsEnabled,
175
+ os: knownOs(process.platform),
176
+ nodeVersion: process.versions.node,
177
+ })
178
+ }
179
+
154
180
  const transport = new StdioServerTransport()
155
181
  await server.connect(transport)
156
182
 
@@ -192,6 +218,7 @@ function installShutdownHooks(state: () => AgentState | undefined): void {
192
218
  void Promise.all([
193
219
  stopAllForwarders(),
194
220
  releaseAllListeners(),
221
+ currentTelemetry()?.close(),
195
222
  current && disposeAgentState(current),
196
223
  ])
197
224
  .catch((err: unknown) => {
@@ -209,3 +236,10 @@ function installShutdownHooks(state: () => AgentState | undefined): void {
209
236
  process.stdin.once('end', () => shutdown('stdin ended'))
210
237
  process.stdin.once('close', () => shutdown('stdin closed'))
211
238
  }
239
+
240
+ /** Maps the platform to the values the analytics API accepts. */
241
+ function knownOs(platform: NodeJS.Platform) {
242
+ return platform === 'darwin' || platform === 'linux' || platform === 'win32'
243
+ ? platform
244
+ : 'other'
245
+ }
@@ -286,8 +286,10 @@ export function diagnoseBlockedClient(
286
286
  + 'published version goes out from there too. Otherwise, try a '
287
287
  + 'different endpoint that returns the value, such as a JSON API the '
288
288
  + 'page calls (find_requests_containing), then propose and prove '
289
- + 'that. If none works, stop and report to the developer that this '
290
- + 'site blocks the attestor. Do not tweak headers.',
289
+ + 'that. If none works, stop retrying: publish the version from the '
290
+ + 'capture, unproven, and ask the developer to try a real '
291
+ + 'verification (how_it_works({ topic: "prove" })). Do not tweak '
292
+ + 'headers.',
291
293
  }
292
294
  }
293
295
 
@@ -28,6 +28,14 @@ export function proposeTool(resolveBackend: ResolveBackend): RegisteredTool {
28
28
  {
29
29
  requestId: { type: 'string' },
30
30
  name: { type: 'string' },
31
+ userRequest: {
32
+ type: 'string',
33
+ maxLength: 500,
34
+ description:
35
+ 'The developer\'s request in their own words, as they wrote it. '
36
+ + 'Reclaim records this text to improve the product, so leave '
37
+ + 'out secrets and personal data.',
38
+ },
31
39
  shortenNames: {
32
40
  type: 'boolean',
33
41
  description:
@@ -239,7 +239,10 @@ export function createVersionFromCaptureTool(
239
239
  {
240
240
  name: 'create_provider_version_from_capture',
241
241
  description:
242
- 'Creates a new immutable provider version after `run_proof` succeeds. '
242
+ 'Creates a new immutable provider version after `run_proof` succeeds, '
243
+ + 'or from the captured draft when the site keeps answering replay or '
244
+ + 'proof with 403 past the retry budget in '
245
+ + 'how_it_works({ topic: "prove" }). '
243
246
  + 'Publishes the navigation script kept by `test_user_script` as '
244
247
  + '`jsUserScripts` unless `jsUserScripts` is passed. Pass exactly '
245
248
  + 'one `provider` or `providers` value returned by '
@@ -21,7 +21,9 @@ export function replayTool(resolveBackend: ResolveBackend): RegisteredTool {
21
21
  + 'no cookies or auth headers, like an anonymous visitor. If the '
22
22
  + 'values still extract (`valid: true`), the endpoint is public and '
23
23
  + 'the proof would not bind to the user: choose an auth-bound '
24
- + 'request instead.',
24
+ + 'request instead. A 403 that survives one retry and one '
25
+ + 'correction is a site defense, not a recipe bug: stop retrying '
26
+ + 'and publish from the capture (how_it_works({ topic: "prove" })).',
25
27
  inputSchema: objectSchema(
26
28
  {
27
29
  draftId: { type: 'string' },
@@ -4,6 +4,8 @@ import { defineTool, type RegisteredTool } from '../../server.ts'
4
4
 
5
5
  interface StartIntegrationArgs {
6
6
  goal: string
7
+ /** Recorded by telemetry only; the plan ignores it. */
8
+ userRequest?: string
7
9
  orgId?: string
8
10
  providerIds?: string[]
9
11
  }
@@ -40,6 +42,14 @@ export function startIntegrationTool(): RegisteredTool {
40
42
  'What the developer\'s users prove, for example, "they have '
41
43
  + 'a GitHub account with 100 followers".',
42
44
  },
45
+ userRequest: {
46
+ type: 'string',
47
+ maxLength: 500,
48
+ description:
49
+ 'The developer\'s request in their own words, as they wrote it. '
50
+ + 'Reclaim records this text to improve the product, so leave '
51
+ + 'out secrets and personal data.',
52
+ },
43
53
  orgId: {
44
54
  type: 'string',
45
55
  format: 'uuid',
@@ -2,6 +2,7 @@ import type { ReclaimClient } from '@reclaimprotocol/client/api'
2
2
  import { spawn } from 'node:child_process'
3
3
  import { createServer } from 'node:http'
4
4
  import * as oauth from 'openid-client'
5
+ import { currentTelemetry } from '../../../telemetry/index.ts'
5
6
  import { defineTool, type RegisteredTool } from '../../server.ts'
6
7
  import {
7
8
  authBaseFor,
@@ -257,6 +258,8 @@ async function beginSignIn(
257
258
  })
258
259
  writeSession(session)
259
260
  client.setToken(session.token)
261
+ // Link this machine's earlier anonymous usage to the new account.
262
+ void currentTelemetry()?.signedIn()
260
263
  return { status: 'AUTHENTICATED', expiresAt: session.expiresAt }
261
264
  } catch(err) {
262
265
  return { status: 'FAILED', reason: failureReason(err) }
@@ -0,0 +1,18 @@
1
+ import type { Telemetry } from './telemetry.ts'
2
+
3
+ export { startToolCall } from './tool-call.ts'
4
+ export { Telemetry } from './telemetry.ts'
5
+
6
+ let current: Telemetry | undefined
7
+
8
+ /**
9
+ * Makes `telemetry` the instance the hooks report to. Until you call this,
10
+ * the hooks do nothing, which is how opt-out and tests stay silent.
11
+ */
12
+ export function installTelemetry(telemetry: Telemetry | undefined) {
13
+ current = telemetry
14
+ }
15
+
16
+ export function currentTelemetry(): Telemetry | undefined {
17
+ return current
18
+ }