@reclaimprotocol/agent 0.1.23 → 0.1.25

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 (149) hide show
  1. package/README.md +56 -20
  2. package/lib/authoring/backend.d.ts +5 -4
  3. package/lib/authoring/tools.js +30 -30
  4. package/lib/cdp/capture-engine.js +8 -7
  5. package/lib/cdp/claims.d.ts +21 -0
  6. package/lib/cdp/claims.js +129 -0
  7. package/lib/cdp/container.d.ts +85 -0
  8. package/lib/cdp/container.js +481 -0
  9. package/lib/cdp/geo.d.ts +10 -0
  10. package/lib/cdp/geo.js +36 -0
  11. package/lib/cdp/index.d.ts +1 -0
  12. package/lib/cdp/index.js +1 -0
  13. package/lib/live/cloudflared.d.ts +15 -0
  14. package/lib/live/cloudflared.js +268 -0
  15. package/lib/live/tunnel.d.ts +26 -0
  16. package/lib/live/tunnel.js +160 -0
  17. package/lib/mcp/api/operations.gen.js +34 -26
  18. package/lib/mcp/api/to-zod.d.ts +1 -1
  19. package/lib/mcp/api/to-zod.js +1 -1
  20. package/lib/mcp/api/tools.js +1 -1
  21. package/lib/mcp/instructions.d.ts +15 -2
  22. package/lib/mcp/instructions.js +102 -38
  23. package/lib/mcp/start.js +62 -6
  24. package/lib/mcp/tools/agent/analyze.js +5 -1
  25. package/lib/mcp/tools/agent/attach.d.ts +29 -2
  26. package/lib/mcp/tools/agent/attach.js +310 -24
  27. package/lib/mcp/tools/agent/build.d.ts +18 -8
  28. package/lib/mcp/tools/agent/build.js +30 -12
  29. package/lib/mcp/tools/agent/capture.js +5 -1
  30. package/lib/mcp/tools/agent/eval.js +3 -1
  31. package/lib/mcp/tools/agent/find.js +6 -5
  32. package/lib/mcp/tools/agent/how-it-works.d.ts +15 -7
  33. package/lib/mcp/tools/agent/how-it-works.js +57 -17
  34. package/lib/mcp/tools/agent/index.d.ts +1 -2
  35. package/lib/mcp/tools/agent/index.js +1 -1
  36. package/lib/mcp/tools/agent/live-view.d.ts +17 -0
  37. package/lib/mcp/tools/agent/live-view.js +146 -0
  38. package/lib/mcp/tools/agent/pod-proof.d.ts +8 -0
  39. package/lib/mcp/tools/agent/pod-proof.js +64 -0
  40. package/lib/mcp/tools/agent/propose.js +4 -2
  41. package/lib/mcp/tools/agent/publish.js +47 -77
  42. package/lib/mcp/tools/agent/session.d.ts +16 -1
  43. package/lib/mcp/tools/agent/session.js +32 -7
  44. package/lib/mcp/tools/authenticate/authenticate.js +26 -13
  45. package/lib/mcp/tools/mode/mode.d.ts +1 -1
  46. package/lib/mcp/tools/mode/mode.js +10 -22
  47. package/lib/old/client.d.ts +1 -1
  48. package/lib/old/client.js +1 -1
  49. package/lib/old/instructions.d.ts +9 -1
  50. package/lib/old/instructions.js +4 -6
  51. package/lib/old/login-cdp.d.ts +2 -2
  52. package/lib/old/login-cdp.js +46 -6
  53. package/lib/old/to-register.d.ts +3 -2
  54. package/lib/old/to-register.js +1 -1
  55. package/lib/old/tools/authenticate.js +9 -1
  56. package/lib/old/tools/get-provider-info.js +6 -5
  57. package/lib/old/tools/issue-app-credentials.js +3 -2
  58. package/lib/old/tools/publish.d.ts +1 -1
  59. package/lib/old/tools/publish.js +90 -131
  60. package/lib/old/tools/update-provider-metadata.d.ts +1 -1
  61. package/lib/old/tools/update-provider-metadata.js +1 -1
  62. package/lib/old/tools/verify-sdk-setup.js +1 -1
  63. package/lib/proof/attestor.d.ts +3 -3
  64. package/lib/proof/debug-response-dump.js +4 -4
  65. package/lib/proof/index.d.ts +1 -0
  66. package/lib/proof/index.js +1 -0
  67. package/lib/proof/page-replay.d.ts +1 -1
  68. package/lib/proof/page-replay.js +1 -1
  69. package/lib/proof/pod-prover.d.ts +47 -0
  70. package/lib/proof/pod-prover.js +155 -0
  71. package/lib/provider/dom-xpath.js +11 -9
  72. package/lib/provider/draft.d.ts +10 -7
  73. package/lib/provider/draft.js +11 -9
  74. package/lib/provider/injection-doc.d.ts +23 -1
  75. package/lib/provider/injection-doc.js +34 -21
  76. package/lib/provider/match-redaction-warning.d.ts +2 -2
  77. package/lib/provider/match-redaction-warning.js +3 -3
  78. package/lib/provider/matcher.js +13 -12
  79. package/lib/provider/secrets.d.ts +3 -3
  80. package/lib/provider/secrets.js +3 -3
  81. package/lib/provider/to-version.d.ts +7 -6
  82. package/lib/provider/to-version.js +3 -3
  83. package/lib/tools/registry.d.ts +5 -5
  84. package/lib/tools/registry.js +5 -5
  85. package/package.json +3 -3
  86. package/samples/demo-server/README.md +7 -6
  87. package/skill/SKILL.builder.md +356 -189
  88. package/skill/SKILL.common.md +400 -97
  89. package/skill/SKILL.old.md +340 -155
  90. package/skill/builder-mode.md +60 -17
  91. package/skill/hash-validation.md +170 -0
  92. package/skill/interception.md +236 -0
  93. package/skill/js-sdk-integration.md +13 -7
  94. package/skill/old-mode.md +62 -21
  95. package/skill/user-script.md +61 -0
  96. package/src/authoring/backend.ts +5 -4
  97. package/src/authoring/tools.ts +31 -30
  98. package/src/cdp/capture-engine.ts +8 -7
  99. package/src/cdp/claims.ts +153 -0
  100. package/src/cdp/container.ts +593 -0
  101. package/src/cdp/geo.ts +52 -0
  102. package/src/cdp/index.ts +1 -0
  103. package/src/consts.ts +1 -0
  104. package/src/live/cloudflared.ts +348 -0
  105. package/src/live/tunnel.ts +208 -0
  106. package/src/mcp/api/operations.gen.ts +34 -26
  107. package/src/mcp/api/to-zod.ts +1 -1
  108. package/src/mcp/api/tools.ts +1 -1
  109. package/src/mcp/instructions.ts +122 -48
  110. package/src/mcp/start.ts +69 -5
  111. package/src/mcp/tools/agent/analyze.ts +5 -1
  112. package/src/mcp/tools/agent/attach.ts +367 -26
  113. package/src/mcp/tools/agent/build.ts +35 -16
  114. package/src/mcp/tools/agent/capture.ts +5 -1
  115. package/src/mcp/tools/agent/eval.ts +4 -1
  116. package/src/mcp/tools/agent/find.ts +6 -5
  117. package/src/mcp/tools/agent/how-it-works.ts +62 -21
  118. package/src/mcp/tools/agent/index.ts +6 -2
  119. package/src/mcp/tools/agent/live-view.ts +182 -0
  120. package/src/mcp/tools/agent/pod-proof.ts +90 -0
  121. package/src/mcp/tools/agent/propose.ts +4 -2
  122. package/src/mcp/tools/agent/publish.ts +52 -77
  123. package/src/mcp/tools/agent/session.ts +33 -6
  124. package/src/mcp/tools/authenticate/authenticate.ts +28 -13
  125. package/src/mcp/tools/mode/mode.ts +10 -22
  126. package/src/old/client.ts +2 -2
  127. package/src/old/instructions.ts +4 -10
  128. package/src/old/login-cdp.ts +50 -6
  129. package/src/old/to-register.ts +3 -2
  130. package/src/old/tools/authenticate.ts +9 -1
  131. package/src/old/tools/get-provider-info.ts +6 -5
  132. package/src/old/tools/issue-app-credentials.ts +3 -2
  133. package/src/old/tools/publish.ts +95 -131
  134. package/src/old/tools/update-provider-metadata.ts +1 -1
  135. package/src/old/tools/verify-sdk-setup.ts +1 -1
  136. package/src/proof/attestor.ts +3 -3
  137. package/src/proof/debug-response-dump.ts +4 -4
  138. package/src/proof/index.ts +8 -0
  139. package/src/proof/page-replay.ts +1 -1
  140. package/src/proof/pod-prover.ts +231 -0
  141. package/src/provider/dom-xpath.ts +11 -9
  142. package/src/provider/draft.ts +13 -10
  143. package/src/provider/injection-doc.ts +37 -24
  144. package/src/provider/match-redaction-warning.ts +3 -3
  145. package/src/provider/matcher.ts +13 -12
  146. package/src/provider/secrets.ts +3 -3
  147. package/src/provider/to-version.ts +11 -9
  148. package/src/tools/registry.ts +5 -5
  149. package/skill/injection-api.md +0 -364
@@ -1,24 +1,64 @@
1
- import { PROVIDER_GUIDE_BUILDER, PROVIDER_GUIDE_OLD, } from "../../instructions.js";
1
+ import { guideFor } from "../../instructions.js";
2
2
  import { defineTool } from "../../server.js";
3
3
  /**
4
- * Serves the full provider-authoring guide on demand. The MCP server's
5
- * always-on `instructions` are a terse tool-flow reference; this tool returns
6
- * the deep version (workflow, decision frameworks, gotchas, "never do" rules)
7
- * so any MCP client — not just Claude Code, which alone auto-loads skills — can
8
- * pull it. `oldMode` selects the backend-specific guide so the agent is never
9
- * told to call auth/publish tools that aren't registered in its mode.
10
- * Stateless: callable before `attach_browser`.
4
+ * Serves the provider-authoring guide on demand, ONE TOPIC AT A TIME. The
5
+ * server's always-on `instructions` are a terse tool-flow reference; this tool
6
+ * is the deep version, so any MCP client — not just Claude Code, which alone
7
+ * auto-loads skills — can pull it.
8
+ *
9
+ * Topic-scoped rather than all-at-once because the full guide is ~7k tokens:
10
+ * paying that to answer "how do I mark a header secret?" crowds out the actual
11
+ * task, which is exactly what makes an agent slow and prone to inventing
12
+ * details. Every response carries the topic index, so a caller that guessed
13
+ * wrong (or called with no topic at all) always learns what else is available
14
+ * without a second round-trip.
15
+ *
16
+ * `oldMode` selects the backend-specific guide, so the agent is never told to
17
+ * call auth/publish tools that aren't registered in its mode. Stateless:
18
+ * callable before `attach_browser`.
11
19
  */
12
20
  export function howItWorksTool(oldMode) {
13
- const guide = oldMode ? PROVIDER_GUIDE_OLD : PROVIDER_GUIDE_BUILDER;
21
+ const guide = guideFor(oldMode);
22
+ const names = [...guide.topics.keys()];
14
23
  return defineTool({
15
24
  name: 'how_it_works',
16
- description: 'Return the full Reclaim provider-authoring guide for THIS server: '
17
- + 'the capture → draft → replay → prove → publish workflow, decision '
18
- + 'frameworks (login detection, OPRF privacy, context vs bare params), '
19
- + 'multi-request handling, troubleshooting, and what never to do. '
20
- + 'Call this BEFORE building a provider unless you are already '
21
- + 'fluent in the flow.',
22
- inputSchema: { type: 'object', properties: {} },
23
- }, async () => ({ guide }));
25
+ description: 'Reclaim provider-authoring guide for THIS server, by topic. Call it '
26
+ + 'with no arguments for the overview plus the topic index, then again '
27
+ + 'for whichever topic you need. Topics: '
28
+ + names.join(', ')
29
+ + '. Use `all` for the entire guide at once (large — prefer a topic). '
30
+ + 'Read the relevant topic BEFORE acting unless you are already fluent '
31
+ + 'in that part of the flow.',
32
+ inputSchema: {
33
+ type: 'object',
34
+ properties: {
35
+ topic: {
36
+ type: 'string',
37
+ // Deliberately NOT an `enum`: schema validation runs before the
38
+ // handler, so an enum turns a mistyped topic into a validation
39
+ // error instead of a response carrying the real topic list. A
40
+ // plain string lets the tool answer the mistake usefully.
41
+ description: 'Which section to return: ' + names.join(', ')
42
+ + ', or `all`. Omit for the overview + topic index. An '
43
+ + 'unrecognized value comes back with the list, not an error.',
44
+ },
45
+ },
46
+ },
47
+ }, async ({ topic }) => {
48
+ if (topic === 'all') {
49
+ return { topic: 'all', topics: guide.index, guide: guide.full };
50
+ }
51
+ const requested = topic ?? 'overview';
52
+ const section = guide.topics.get(requested);
53
+ if (!section) {
54
+ // Not an error: hand back the index so the caller self-corrects in
55
+ // the same turn rather than retrying blind.
56
+ return {
57
+ error: `Unknown topic "${requested}".`,
58
+ topics: guide.index,
59
+ guide: guide.topics.get('overview'),
60
+ };
61
+ }
62
+ return { topic: requested, topics: guide.index, guide: section };
63
+ });
24
64
  }
@@ -1,2 +1 @@
1
- export { buildAgentState, buildAgentTools } from './build.ts';
2
- export type { AgentState } from './build.ts';
1
+ export { type AgentState, buildAgentState, buildAgentTools, disposeAgentState, } from './build.ts';
@@ -1,3 +1,3 @@
1
1
  // Barrel for the agent tool group (Chrome CDP capture, provider synthesis,
2
2
  // replay, zkTLS proof). Import from here, not the individual files.
3
- export { buildAgentState, buildAgentTools } from "./build.js";
3
+ export { buildAgentState, buildAgentTools, disposeAgentState, } from "./build.js";
@@ -0,0 +1,17 @@
1
+ import { type Tunnel } from '../../../live/tunnel.ts';
2
+ import { type RegisteredTool } from '../../server.ts';
3
+ import { type AttachState } from './attach.ts';
4
+ export interface LiveShare {
5
+ tunnel: Tunnel;
6
+ /** The public URL handed to whoever is watching. */
7
+ url: string;
8
+ }
9
+ export interface LiveShareRef {
10
+ current?: LiveShare;
11
+ }
12
+ /** Tear down whatever share is running. Safe when none is. Exported so the
13
+ * session tools can call it from `dispose_browser` / `reset_session`. */
14
+ export declare function stopLiveShare(ref: LiveShareRef): Promise<boolean>;
15
+ export declare function liveViewTools(attachRef: {
16
+ current?: AttachState;
17
+ }, shareRef: LiveShareRef): RegisteredTool[];
@@ -0,0 +1,146 @@
1
+ /**
2
+ * `share_browser_view` / `stop_browser_view` — put the LOCAL attached tab
3
+ * behind a public URL so someone who is not at this machine can watch it, and
4
+ * optionally drive it.
5
+ *
6
+ * Scoped to the CDP-attached modes (`dedicated`, `attach`, `custom`) on
7
+ * purpose. A `builder`-mode browser is a Popcorn pod that already ships its
8
+ * own hosted live view on `attach_browser`'s `liveViewUrl`; tunnelling a
9
+ * second one would duplicate it and burn a share URL for nothing, so this
10
+ * refuses and points at the one that exists.
11
+ *
12
+ * The share is deliberately explicit and single-instance: one live view per
13
+ * attached browser, started only when asked for, and torn down by
14
+ * `stop_browser_view`, `dispose_browser`, or `reset_session`.
15
+ */
16
+ import { badRequest, conflict } from '@hapi/boom';
17
+ import assert from 'node:assert';
18
+ import { startTunnel } from "../../../live/tunnel.js";
19
+ import { LOGGER } from "../../../logger.js";
20
+ import { defineTool } from "../../server.js";
21
+ import { SHAREABLE_MODES } from "./attach.js";
22
+ /** Tear down whatever share is running. Safe when none is. Exported so the
23
+ * session tools can call it from `dispose_browser` / `reset_session`. */
24
+ export async function stopLiveShare(ref) {
25
+ const share = ref.current;
26
+ if (!share) {
27
+ return false;
28
+ }
29
+ ref.current = undefined;
30
+ // Only the tunnel is ours to close. The browser and its viewer belong to the
31
+ // container, which keeps running until the browser is disposed.
32
+ await share.tunnel.stop().catch((err) => {
33
+ LOGGER.warn({ err }, 'live view: tunnel stop failed');
34
+ });
35
+ return true;
36
+ }
37
+ export function liveViewTools(attachRef, shareRef) {
38
+ return [
39
+ defineTool({
40
+ name: 'share_browser_view',
41
+ description: 'Put the attached browser\'s live view behind a PUBLIC HTTPS URL (a '
42
+ + 'cloudflared quick tunnel) so someone elsewhere can watch it and '
43
+ + 'act in it. Use it to hand a login or a 2FA step to the person '
44
+ + 'who holds the credentials, or to let a colleague watch a '
45
+ + 'capture. Returns the URL — relay it to the developer VERBATIM, '
46
+ + 'on its own line. '
47
+ + 'ONLY for `container` mode, whose browser runs its own viewer. '
48
+ + 'A `builder` browser already has a hosted live view: use the '
49
+ + '`liveViewUrl` attach_browser returned. A local browser '
50
+ + '(`dedicated`, `attach`, `custom`) cannot be shared at all — no '
51
+ + 'tunnel can fix that, because there is no viewer behind it. Tell '
52
+ + 'them so, and offer to re-attach: `container` is free and needs '
53
+ + 'Docker, `builder` needs no install but is chargeable. '
54
+ + 'THE URL IS THE ONLY CREDENTIAL: the random hostname is the whole '
55
+ + 'secret, with no token or login behind it, so anyone who learns '
56
+ + 'it can see and drive this browser as the signed-in user. Share '
57
+ + 'it only with the person who needs it, and call stop_browser_view '
58
+ + 'the moment they are done. '
59
+ + 'Needs cloudflared: an existing install on PATH is used as-is, '
60
+ + 'otherwise it is downloaded once to ~/.reclaim/bin on first '
61
+ + 'share (never at install time). Set RECLAIM_CLOUDFLARED_PATH to '
62
+ + 'point at your own copy instead.',
63
+ inputSchema: { type: 'object', properties: {} },
64
+ }, async () => {
65
+ const attached = attachRef.current;
66
+ assert(attached, badRequest('No browser attached. Call attach_browser first.'));
67
+ assert(SHAREABLE_MODES.has(attached.mode), badRequest(`share_browser_view does not apply to \`${attached.mode}\` mode. `
68
+ + (attached.liveViewUrl
69
+ ? 'That browser already has a hosted live view — use the '
70
+ + `\`liveViewUrl\` from attach_browser: ${attached.liveViewUrl}`
71
+ : 'That browser has no live view to put behind a URL. To let '
72
+ + 'someone else in, re-attach with mode "container" (the '
73
+ + 'Reclaim runtime in Docker — free, needs Docker) or '
74
+ + '"builder" (hosted, no install, chargeable)')
75
+ + '.'));
76
+ assert(!shareRef.current, conflict('A live view is already running at '
77
+ + `${shareRef.current?.url}. Call stop_browser_view first, or `
78
+ + 'reuse that URL.'));
79
+ // The container publishes its viewer on loopback and its full CDP
80
+ // port beside it. Only the VIEWER is ever tunnelled: the CDP port is
81
+ // unauthenticated, total control of the browser, and putting it on a
82
+ // public URL would hand that to anyone with the link.
83
+ const viewerPort = attached.viewerPort;
84
+ assert(viewerPort, badRequest('This browser reports no live-view port. Re-attach with mode '
85
+ + '"container".'));
86
+ const tunnel = await startTunnel(viewerPort);
87
+ // The path is the runtime's own viewer page; `magnify=1` is what makes
88
+ // it lay out for the device that opens it.
89
+ const url = `${tunnel.url}/liveview.html?magnify=1`;
90
+ const share = {
91
+ tunnel, url,
92
+ };
93
+ shareRef.current = share;
94
+ LOGGER.info({ mode: attached.mode }, 'live view shared');
95
+ return {
96
+ url,
97
+ ...(share.tunnel.binary.source === 'downloaded'
98
+ ? { cloudflaredInstalled: true }
99
+ : {}),
100
+ ...(share.tunnel.ready
101
+ ? {}
102
+ : {
103
+ propagating: true,
104
+ // cloudflared's own last words. Without them, "the URL does
105
+ // not connect" is unactionable: the tunnel, the local server
106
+ // and this machine's egress all look identical from outside.
107
+ ...(share.tunnel.diagnostics
108
+ ? { cloudflaredLog: share.tunnel.diagnostics }
109
+ : {}),
110
+ }),
111
+ _notes: [
112
+ ...(share.tunnel.binary.source === 'downloaded'
113
+ ? ['cloudflared was not installed, so it was downloaded once '
114
+ + 'to ~/.reclaim/bin. Mention that; later shares reuse it.']
115
+ : []),
116
+ ...(share.tunnel.ready
117
+ ? []
118
+ : ['The tunnel never confirmed a connection to Cloudflare, so '
119
+ + 'the URL may not resolve yet. Give it ~10s and reload '
120
+ + 'first. If it still fails, say so and offer to retry '
121
+ + '(stop_browser_view then share_browser_view) — a quick '
122
+ + 'tunnel does sometimes fail to register. '
123
+ + '`cloudflaredLog` has its output if it keeps happening.']),
124
+ 'Relay `url` to the developer verbatim, on its own line.',
125
+ 'The URL is a credential: it needs no login, and anyone with it '
126
+ + 'can see this browser and act in it as the signed-in user.',
127
+ 'It shows the whole browser, so a sign-in that opens a separate '
128
+ + 'window is visible too — unlike a single-tab stream.',
129
+ 'Opening it on a phone lays the page out for that phone, with '
130
+ + 'touch, pinch-zoom and the soft keyboard.',
131
+ 'Call stop_browser_view when they are done. That closes the '
132
+ + 'tunnel only — the browser keeps running.',
133
+ ],
134
+ };
135
+ }),
136
+ defineTool({
137
+ name: 'stop_browser_view',
138
+ description: 'Close the public URL opened by share_browser_view: shuts the '
139
+ + 'tunnel down, so the URL stops resolving for whoever still has '
140
+ + 'it. Call it as soon as the person you shared with is finished. '
141
+ + 'Nothing else changes — the browser stays attached, its own live '
142
+ + 'view keeps serving locally, and any capture keeps running.',
143
+ inputSchema: { type: 'object', properties: {} },
144
+ }, async () => ({ stopped: await stopLiveShare(shareRef) })),
145
+ ];
146
+ }
@@ -0,0 +1,8 @@
1
+ import type { ConfigStore } from '../../../config/store.ts';
2
+ import { type RegisteredTool } from '../../server.ts';
3
+ import type { AttachState } from './attach.ts';
4
+ import type { CaptureStore } from './capture.ts';
5
+ export declare function podProofTools(attachRef: {
6
+ current?: AttachState;
7
+ config: ConfigStore;
8
+ }, captures: CaptureStore): RegisteredTool[];
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Prove a drafted recipe through the Popcorn pod's OWN `/reclaim/prove` — the
3
+ * remote browser session's own API for creating a proof from that same
4
+ * session — instead of the local in-process attestor SDK (`run_proof`).
5
+ * Recommended for any `builder`-mode attach: the pod is the SAME enclave that
6
+ * holds the browser's TLS session, so proving there reaches the target from
7
+ * the identical exit IP the browser used — including the country-routed egress
8
+ * Popcorn applies when the session was allocated with a country. A local SDK
9
+ * proof runs from the agent's own network instead, which won't match a
10
+ * country-locked / IP-allowlisted target the remote browser could reach.
11
+ *
12
+ * Builder mode only (needs the attach's pod `apiUrl`, from
13
+ * `CreateAgentBrowserSession`) — throws a clear error otherwise rather than
14
+ * silently falling back to the local path.
15
+ */
16
+ import { badRequest } from '@hapi/boom';
17
+ import assert from 'node:assert';
18
+ import { popcornProve } from "../../../proof/pod-prover.js";
19
+ import { buildHttpParams } from "../../../proof/proof-core.js";
20
+ import { defineTool } from "../../server.js";
21
+ import { shapeRichProofResult } from "./proof.js";
22
+ export function podProofTools(attachRef, captures) {
23
+ const tool = defineTool({
24
+ name: 'run_proof_via_pod',
25
+ description: 'Prove the drafted recipe through the Popcorn pod\'s own TEE '
26
+ + '(/reclaim/prove) instead of the local attestor SDK — this is '
27
+ + 'the remote browser session\'s own api for creating a proof '
28
+ + 'from that same session. Use this instead of run_proof '
29
+ + 'whenever this capture\'s '
30
+ + 'attach mode is "builder": the pod is the same enclave that '
31
+ + 'held the browser\'s TLS session (and its country-routed egress, '
32
+ + 'if any), so '
33
+ + 'the proof reaches the target from the browser\'s own exit IP. '
34
+ + 'No ownerAddress needed — the pod signs with the attestor\'s '
35
+ + 'own TEE key, not a local credential.',
36
+ inputSchema: {
37
+ type: 'object',
38
+ properties: {
39
+ captureId: { type: 'string' },
40
+ draftId: { type: 'string' },
41
+ },
42
+ required: ['captureId', 'draftId'],
43
+ },
44
+ }, async ({ captureId, draftId }) => {
45
+ const apiUrl = attachRef.current?.apiUrl;
46
+ assert(apiUrl, badRequest('No pod api available for this attach — run_proof_via_pod needs '
47
+ + 'mode "builder" (attach_browser), not local/custom CDP.'));
48
+ const entry = captures.sessions.get(captureId);
49
+ assert(entry, badRequest(`no capture ${captureId} — call start_capture first `
50
+ + '(it has been reset or never started).'));
51
+ const draft = entry.drafts.get(draftId);
52
+ assert(draft, badRequest(`no draft ${draftId} in capture ${captureId}`));
53
+ const result = await popcornProve({
54
+ apiUrl,
55
+ params: buildHttpParams(draft.provider),
56
+ secretParams: { headers: draft.secrets },
57
+ });
58
+ return shapeRichProofResult(result, {
59
+ provider: draft.provider,
60
+ hasSecrets: Object.keys(draft.secrets).length > 0,
61
+ });
62
+ });
63
+ return [tool];
64
+ }
@@ -21,13 +21,15 @@ export function proposeTool(resolveBackend) {
21
21
  + 'Single-value shorthand: `target` + `name`. Per-target `hash` '
22
22
  + 'OPRF-hashes that value. BY DEFAULT each `name` is shortened to '
23
23
  + 'its last alphanumeric token, lowercased, to form the param key '
24
- + '(e.g. "employer_name" → "name") — pick names that stay distinct '
24
+ + '(for example, "employer_name" → "name") — pick names that stay '
25
+ + 'distinct '
25
26
  + 'after shortening ("firstName"/"surname", not "first_name"/'
26
27
  + '"last_name"). Pass `shortenNames: false` to use each `name` '
27
28
  + 'VERBATIM as the param key instead — it becomes both a regex '
28
29
  + 'capture-group name and a paramValues key, so it must be a plain '
29
30
  + 'identifier (letters/digits/underscore, not starting with a '
30
- + 'digit); we recommend lower_snake_case (e.g. "employer_name"), '
31
+ + 'digit); we recommend lower_snake_case (for example, '
32
+ + '"employer_name"), '
31
33
  + 'but you choose the exact name.',
32
34
  inputSchema: objectSchema({
33
35
  requestId: { type: 'string' },
@@ -1,4 +1,4 @@
1
- import { INJECTION_GUIDE } from "../../../provider/injection-doc.js";
1
+ import { ALLOWED_REQUESTS_HINT, USER_SCRIPT_HINT, } from "../../../provider/injection-doc.js";
2
2
  import { countsFor, matchRedactionWarnings, } from "../../../provider/match-redaction-warning.js";
3
3
  import { nextVersion, providerToCreateVersionRequest, } from "../../../provider/to-version.js";
4
4
  import { defineTool } from "../../server.js";
@@ -62,7 +62,8 @@ const REQUEST_DRAFT_PROPERTIES = {
62
62
  const PROVIDER_SCHEMA = {
63
63
  type: 'object',
64
64
  description: 'A `provider` object from propose_provider, passed through verbatim — ' +
65
- 'the tool builds the version body (the requests[] entry etc.) for you.',
65
+ 'the tool builds the version body (the requests[] entry and so on) ' +
66
+ 'for you.',
66
67
  properties: {
67
68
  name: { type: 'string', description: 'Verification name.' },
68
69
  ...REQUEST_DRAFT_PROPERTIES,
@@ -73,7 +74,7 @@ const PROVIDER_SCHEMA = {
73
74
  },
74
75
  paramValues: {
75
76
  type: 'object',
76
- description: 'Real values for the {{param}} placeholders, e.g. ' +
77
+ description: 'Real values for the {{param}} placeholders, for example, ' +
77
78
  '{ "username": "Syed" }.',
78
79
  },
79
80
  },
@@ -87,8 +88,8 @@ const ALLOWED_JS_REQUEST_SCHEMA = {
87
88
  ...REQUEST_DRAFT_PROPERTIES,
88
89
  multiple: {
89
90
  type: 'boolean',
90
- description: 'Allow the template to match many requests (e.g. pagination). ' +
91
- 'Default true.',
91
+ description: 'Allow the template to match many requests (for example, ' +
92
+ 'pagination). Default true.',
92
93
  },
93
94
  required: {
94
95
  type: 'boolean',
@@ -100,36 +101,21 @@ const ALLOWED_JS_REQUEST_SCHEMA = {
100
101
  export function createVersionFromCaptureTool(client, attachRef) {
101
102
  return defineTool({
102
103
  name: 'create_provider_version_from_capture',
103
- description: 'Persist drafted ReclaimProvider(s) as a provider version. ' +
104
- 'Call this after run_proof succeeds, passing the `provider` ' +
105
- 'object(s) from propose_provider VERBATIM. This tool builds the ' +
106
- 'body (the requests[] array, webSettings, etc.) for you — do NOT ' +
107
- 'hand-construct that shape or call create_provider_version directly. ' +
108
- 'For a MULTI-REQUEST provider, pass `providers: [...]` (each entry a ' +
109
- 'proven draft) — every entry becomes one request in the version, and ' +
110
- 'all are required at verification time. Pass a single `provider` ' +
111
- 'for the one-request case (exactly one of `provider`/`providers`). ' +
112
- 'For data gated behind login/navigation, supply `jsUserScripts` (a ' +
113
- 'page-injection script that drives the page to where the request ' +
114
- 'fires) and/or `allowedJsRequests` (extra requests the script may ' +
115
- 'fire, e.g. pagination). ' +
116
- 'Omit `version` to have the tool auto-select the next available ' +
117
- 'version (it lists existing versions and bumps past the highest). ' +
118
- 'The version lands on the draft branch — to go live, call ' +
119
- '`publish_provider_version` (PRIVATE) or ' +
120
- '`submit_provider_version_for_review` (PUBLIC) afterwards. ' +
104
+ description: 'Persist drafted ReclaimProvider(s) as a provider version, after ' +
105
+ 'run_proof succeeds. Pass the `provider` object(s) from ' +
106
+ 'propose_provider VERBATIM — this tool builds the version body ' +
107
+ '(requests[], webSettings, …) for you; do NOT hand-construct that ' +
108
+ 'shape or call create_provider_version directly. Exactly one of ' +
109
+ '`provider` (one request) or `providers` (a multi-request provider — ' +
110
+ 'every entry is required at verification time). Omit `version` to ' +
111
+ 'auto-bump past the highest existing one. The version lands on the ' +
112
+ 'draft branch: call `publish_provider_version` (PRIVATE) or ' +
113
+ '`submit_provider_version_for_review` (PUBLIC) to go live. ' +
121
114
  '`requiredContext` is derived from any {{context.<name>}} ' +
122
- 'placeholders across ALL requests (plus injected requests / script) ' +
123
- 'and returned on the created version — when present, tell the dev ' +
124
- 'verbatim which context fields consumers MUST send when creating ' +
125
- 'sessions (verification matches those values exactly; a missing ' +
126
- 'field is a 400 at session create). ' +
127
- 'Refine types/descriptions via `update_provider_version` while ' +
128
- 'still on draft. ' +
129
- 'On success, if a browser is still attached, the response\'s ' +
130
- '`_notes` reminds you to confirm with the developer before ' +
131
- 'calling `dispose_browser` — never close it unprompted, they ' +
132
- 'may want it for further edits.',
115
+ 'placeholders and returned on the created version — relay it to the ' +
116
+ 'dev verbatim, since a session missing those fields is a 400. Always ' +
117
+ 'read the response\'s `_notes`. ' +
118
+ 'Full walkthrough: how_it_works({ topic: "publish" }).',
133
119
  inputSchema: {
134
120
  type: 'object',
135
121
  properties: {
@@ -149,12 +135,14 @@ export function createVersionFromCaptureTool(client, attachRef) {
149
135
  initialUrl: {
150
136
  type: 'string',
151
137
  description: 'The website URL the user landed on to trigger the request ' +
152
- '(e.g. https://github.com/Syed). The URL the verification ' +
153
- 'flow opens for end-users — NOT the API URL inside the provider.',
138
+ '(for example, https://github.com/Syed). The URL the ' +
139
+ 'verification flow opens for end-users — NOT the API URL ' +
140
+ 'inside the provider.',
154
141
  },
155
142
  notes: {
156
143
  type: 'string',
157
- description: 'Short change note (e.g. "Initial draft from browser capture").',
144
+ description: 'Short change note (for example, "Initial draft from ' +
145
+ 'browser capture").',
158
146
  },
159
147
  version: {
160
148
  type: 'object',
@@ -169,44 +157,21 @@ export function createVersionFromCaptureTool(client, attachRef) {
169
157
  },
170
158
  jsUserScripts: {
171
159
  type: 'string',
172
- description: 'Plain JavaScript injected into the provider page before every ' +
173
- 'page load (stored as webSettings.jsUserScripts). Use standard ' +
174
- 'browser APIs (window.location, element.click(), forms) to ' +
175
- 'drive login/navigation so the target request fires — the ' +
176
- 'client already intercepts page requests, and window.Reclaim ' +
177
- 'is only for rare edge cases, not navigation. The Builder ' +
178
- 'verification client installs this to run before every page ' +
179
- 'load (before navigating to initialUrl), matching old-devtool ' +
180
- 'customInjection; window.Reclaim itself is not yet exposed ' +
181
- 'there, so prefer plain browser JS. Guide:\n\n' +
182
- INJECTION_GUIDE,
160
+ description: USER_SCRIPT_HINT +
161
+ ' Stored as webSettings.jsUserScripts; the Builder ' +
162
+ 'verification client runs it before every page load ' +
163
+ '(including before navigating to initialUrl), matching ' +
164
+ 'old-devtools customInjection. window.Reclaim is not exposed ' +
165
+ 'there yet, so prefer plain browser JS.',
183
166
  },
184
167
  allowedJsRequests: {
185
168
  type: 'array',
186
- description: 'Extra requests the injection script is allowed to fire ' +
187
- '(e.g. paginated API calls), as RequestSelectionTemplates. ' +
188
- '`multiple`/`required` default to true. ' +
189
- '⚠️ BEFORE populating this to gate ' +
190
- '`verifyProof(proof, { providerId })` (old-devtools calls the ' +
191
- 'same concept `allowedInjectedRequestData` — same mechanics, ' +
192
- 'different field name): this whole entry (url, method, body, ' +
193
- 'responseMatches, responseRedactions) feeds a client-side ' +
194
- 'hash-equality check, and `url` is hashed VERBATIM — no regex/ ' +
195
- 'template resolution, even with `urlType: REGEX` or ' +
196
- '`TEMPLATE`. If the real captured URL contains ANY per-session ' +
197
- 'part (a tracking nonce, CSRF token, cache-busting timestamp), ' +
198
- 'no declared spec can EVER hash-match a real proof — populating' +
199
- ' this for such a provider makes verifyProof reject every real ' +
200
- 'proof, which is worse than leaving it empty. Diff the same ' +
201
- 'endpoint\'s captured URL across two separate sessions before ' +
202
- 'wiring this up. If provider-hash validation keeps causing ' +
203
- 'problems (unstable url, or any other persistent mismatch), ' +
204
- 'the fallback on the verifying side is `verifyProof(proof, { ' +
205
- 'dangerouslyDisableContentValidation: true })` — trusts only ' +
206
- 'the attestor\'s signature, skips the hash-equality check ' +
207
- 'entirely; see the "Provider-hash validation" section of the ' +
208
- 'injection script\'s guide for the full mechanics and when to ' +
209
- 'reach for it.',
169
+ description: 'Extra requests the user script is allowed to fire (for ' +
170
+ 'example, paginated API calls), as ' +
171
+ 'RequestSelectionTemplates. ' +
172
+ '`multiple`/`required` default to true. Old-devtools calls the ' +
173
+ 'same concept `allowedInjectedRequestData`. ' +
174
+ ALLOWED_REQUESTS_HINT,
210
175
  items: ALLOWED_JS_REQUEST_SCHEMA,
211
176
  },
212
177
  interceptorOptions: {
@@ -214,11 +179,7 @@ export function createVersionFromCaptureTool(client, attachRef) {
214
179
  description: 'In-app interception settings, stored as ' +
215
180
  'webSettings.clientOptions.inapp.interceptorOptions — only ' +
216
181
  'needed for a genuine live-interception provider (a real ' +
217
- 'HAWKEYE/MSWJS script, not the standard capture→replay flow). ' +
218
- 'Document replay (webSettings.clientOptions.inapp' +
219
- '.interceptorOptions.isDocumentRequestReplayEnabled) is ' +
220
- 'ALWAYS published disabled regardless of this field — there ' +
221
- 'is no way to enable it here.',
182
+ 'HAWKEYE/MSWJS script, not the standard capture→replay flow).',
222
183
  properties: {
223
184
  interceptorType: {
224
185
  type: 'string',
@@ -229,6 +190,15 @@ export function createVersionFromCaptureTool(client, attachRef) {
229
190
  type: 'string',
230
191
  description: 'Free-form JSON config for the chosen interceptor.',
231
192
  },
193
+ isDocumentRequestReplayEnabled: {
194
+ type: 'boolean',
195
+ description: 'Whether the page\'s own document/navigation request is ' +
196
+ 'genuinely replayed through the interceptor, rather than ' +
197
+ 'constructed locally. Defaults to `false` (disabled) when ' +
198
+ 'omitted. Only meaningful for HAWKEYE/MSWJS — pass `true` ' +
199
+ 'when the target value is embedded in the page\'s own HTML ' +
200
+ 'rather than a separate API call. Inert for CDP.',
201
+ },
232
202
  },
233
203
  required: ['interceptorType'],
234
204
  },
@@ -2,7 +2,22 @@ import type { ConfigStore } from '../../../config/store.ts';
2
2
  import { type RegisteredTool } from '../../server.ts';
3
3
  import { type AttachState } from './attach.ts';
4
4
  import { type CaptureStore } from './capture.ts';
5
+ import { type LiveShareRef } from './live-view.ts';
5
6
  export declare function sessionTools(attachRef: {
6
7
  current?: AttachState;
7
8
  config: ConfigStore;
8
- }, captures: CaptureStore): RegisteredTool[];
9
+ }, captures: CaptureStore, liveShare: LiveShareRef): RegisteredTool[];
10
+ /**
11
+ * Release everything a session holds: the public tunnel, the captures, and the
12
+ * browser itself.
13
+ *
14
+ * Exported because a session can end WITHOUT anyone calling `dispose_browser` —
15
+ * the developer closes their agent, the client kills the server, the transport
16
+ * drops. Nothing runs on its own then, so `start.ts` runs this. Without it a
17
+ * container keeps running and, worse, a public URL to a live browser outlives
18
+ * the session that created it.
19
+ */
20
+ export declare function resetAll(attachRef: {
21
+ current?: AttachState;
22
+ config: ConfigStore;
23
+ }, captures: CaptureStore, liveShare: LiveShareRef): Promise<void>;