@reclaimprotocol/agent 0.1.18 → 0.1.21

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 (168) hide show
  1. package/README.md +6 -2
  2. package/lib/authoring/backend.d.ts +4 -0
  3. package/lib/authoring/tools.js +4 -1
  4. package/lib/cdp/capture.d.ts +2 -2
  5. package/lib/cdp/capture.js +25 -21
  6. package/lib/cdp/client.d.ts +39 -1
  7. package/lib/cdp/client.js +125 -1
  8. package/lib/mcp/api/operations.gen.js +33 -9
  9. package/lib/mcp/instructions.js +27 -3
  10. package/lib/mcp/remap_errors.js +2 -1
  11. package/lib/mcp/start.js +16 -9
  12. package/lib/mcp/tools/agent/attach.d.ts +18 -4
  13. package/lib/mcp/tools/agent/attach.js +142 -52
  14. package/lib/mcp/tools/agent/build.d.ts +9 -2
  15. package/lib/mcp/tools/agent/build.js +15 -7
  16. package/lib/mcp/tools/agent/capture.d.ts +1 -0
  17. package/lib/mcp/tools/agent/capture.js +8 -1
  18. package/lib/mcp/tools/agent/eval.js +2 -3
  19. package/lib/mcp/tools/agent/how-it-works.d.ts +1 -1
  20. package/lib/mcp/tools/agent/how-it-works.js +1 -1
  21. package/lib/mcp/tools/agent/index.d.ts +1 -1
  22. package/lib/mcp/tools/agent/index.js +1 -1
  23. package/lib/mcp/tools/agent/publish.d.ts +4 -1
  24. package/lib/mcp/tools/agent/publish.js +42 -3
  25. package/lib/mcp/tools/agent/session.d.ts +2 -2
  26. package/lib/mcp/tools/agent/session.js +21 -5
  27. package/lib/mcp/tools/agent/shared.js +4 -6
  28. package/lib/mcp/tools/authenticate/authenticate.d.ts +1 -1
  29. package/lib/mcp/tools/authenticate/authenticate.js +13 -6
  30. package/lib/mcp/tools/authenticate/builder-access.d.ts +8 -0
  31. package/lib/mcp/tools/authenticate/builder-access.js +31 -0
  32. package/lib/mcp/tools/authenticate/index.d.ts +1 -0
  33. package/lib/mcp/tools/authenticate/index.js +1 -0
  34. package/lib/old/login-cdp.js +11 -3
  35. package/lib/old/tools/build.d.ts +8 -1
  36. package/lib/old/tools/build.js +6 -2
  37. package/lib/old/tools/publish.d.ts +10 -1
  38. package/lib/old/tools/publish.js +49 -3
  39. package/lib/provider/draft.d.ts +20 -0
  40. package/lib/provider/draft.js +75 -51
  41. package/lib/provider/index.d.ts +1 -1
  42. package/lib/provider/index.js +1 -1
  43. package/lib/provider/secrets.d.ts +37 -0
  44. package/lib/provider/secrets.js +147 -6
  45. package/lib/provider/to-version.d.ts +7 -0
  46. package/lib/provider/to-version.js +11 -1
  47. package/package.json +2 -2
  48. package/skill/SKILL.builder.md +44 -94
  49. package/skill/SKILL.common.md +102 -0
  50. package/skill/SKILL.old.md +21 -73
  51. package/skill/builder-mode.md +2 -2
  52. package/skill/injection-api.md +95 -10
  53. package/skill/old-mode.md +2 -2
  54. package/src/authoring/backend.ts +4 -0
  55. package/src/authoring/tools.ts +4 -1
  56. package/src/cdp/capture.ts +24 -24
  57. package/src/cdp/client.ts +197 -2
  58. package/src/mcp/api/operations.gen.ts +33 -9
  59. package/src/mcp/instructions.ts +36 -3
  60. package/src/mcp/remap_errors.ts +2 -1
  61. package/src/mcp/start.ts +24 -9
  62. package/src/mcp/tools/agent/attach.ts +198 -57
  63. package/src/mcp/tools/agent/build.ts +20 -6
  64. package/src/mcp/tools/agent/capture.ts +10 -1
  65. package/src/mcp/tools/agent/eval.ts +2 -3
  66. package/src/mcp/tools/agent/how-it-works.ts +1 -1
  67. package/src/mcp/tools/agent/index.ts +1 -1
  68. package/src/mcp/tools/agent/publish.ts +46 -2
  69. package/src/mcp/tools/agent/session.ts +29 -8
  70. package/src/mcp/tools/agent/shared.ts +5 -10
  71. package/src/mcp/tools/authenticate/authenticate.ts +17 -9
  72. package/src/mcp/tools/authenticate/builder-access.ts +42 -0
  73. package/src/mcp/tools/authenticate/index.ts +4 -0
  74. package/src/old/login-cdp.ts +13 -3
  75. package/src/old/tools/build.ts +7 -1
  76. package/src/old/tools/publish.ts +55 -4
  77. package/src/provider/draft.ts +98 -56
  78. package/src/provider/index.ts +9 -1
  79. package/src/provider/secrets.ts +171 -6
  80. package/src/provider/to-version.ts +12 -1
  81. package/lib/authoring/find.d.ts +0 -3
  82. package/lib/authoring/find.js +0 -54
  83. package/lib/authoring/propose.d.ts +0 -3
  84. package/lib/authoring/propose.js +0 -109
  85. package/lib/authoring/replay.d.ts +0 -10
  86. package/lib/authoring/replay.js +0 -34
  87. package/lib/cli/args.d.ts +0 -14
  88. package/lib/cli/args.js +0 -19
  89. package/lib/cli/commands.d.ts +0 -1
  90. package/lib/cli/commands.js +0 -19
  91. package/lib/cli.d.ts +0 -1
  92. package/lib/cli.js +0 -7
  93. package/lib/mcp/credentials/authenticate.d.ts +0 -3
  94. package/lib/mcp/credentials/authenticate.js +0 -108
  95. package/lib/mcp/credentials/build.d.ts +0 -5
  96. package/lib/mcp/credentials/build.js +0 -18
  97. package/lib/mcp/credentials/cache.d.ts +0 -70
  98. package/lib/mcp/credentials/cache.js +0 -131
  99. package/lib/mcp/credentials/env-file.d.ts +0 -25
  100. package/lib/mcp/credentials/env-file.js +0 -71
  101. package/lib/mcp/credentials/import.d.ts +0 -2
  102. package/lib/mcp/credentials/import.js +0 -52
  103. package/lib/mcp/credentials/issue.d.ts +0 -2
  104. package/lib/mcp/credentials/issue.js +0 -84
  105. package/lib/mcp/credentials/keypair.d.ts +0 -17
  106. package/lib/mcp/credentials/keypair.js +0 -48
  107. package/lib/mcp/credentials/link.d.ts +0 -3
  108. package/lib/mcp/credentials/link.js +0 -107
  109. package/lib/mcp/credentials/list-cached.d.ts +0 -2
  110. package/lib/mcp/credentials/list-cached.js +0 -55
  111. package/lib/mcp/credentials/owner-key.d.ts +0 -23
  112. package/lib/mcp/credentials/owner-key.js +0 -58
  113. package/lib/mcp/credentials/paths.d.ts +0 -12
  114. package/lib/mcp/credentials/paths.js +0 -35
  115. package/lib/mcp/credentials/poll.d.ts +0 -27
  116. package/lib/mcp/credentials/poll.js +0 -61
  117. package/lib/mcp/credentials/register.d.ts +0 -18
  118. package/lib/mcp/credentials/register.js +0 -30
  119. package/lib/mcp/credentials/resolve-owner-key.d.ts +0 -12
  120. package/lib/mcp/credentials/resolve-owner-key.js +0 -68
  121. package/lib/mcp/credentials/session-cache.d.ts +0 -14
  122. package/lib/mcp/credentials/session-cache.js +0 -42
  123. package/lib/mcp/credentials/status.d.ts +0 -3
  124. package/lib/mcp/credentials/status.js +0 -76
  125. package/lib/mcp/register.d.ts +0 -38
  126. package/lib/mcp/register.js +0 -80
  127. package/lib/mcp/tools/analyze.d.ts +0 -3
  128. package/lib/mcp/tools/analyze.js +0 -26
  129. package/lib/mcp/tools/attach.d.ts +0 -11
  130. package/lib/mcp/tools/attach.js +0 -72
  131. package/lib/mcp/tools/capture.d.ts +0 -10
  132. package/lib/mcp/tools/capture.js +0 -142
  133. package/lib/mcp/tools/eval.d.ts +0 -5
  134. package/lib/mcp/tools/eval.js +0 -127
  135. package/lib/mcp/tools/find.d.ts +0 -3
  136. package/lib/mcp/tools/find.js +0 -60
  137. package/lib/mcp/tools/owner-key.d.ts +0 -2
  138. package/lib/mcp/tools/owner-key.js +0 -63
  139. package/lib/mcp/tools/proof.d.ts +0 -3
  140. package/lib/mcp/tools/proof.js +0 -214
  141. package/lib/mcp/tools/propose.d.ts +0 -9
  142. package/lib/mcp/tools/propose.js +0 -75
  143. package/lib/mcp/tools/publish.d.ts +0 -3
  144. package/lib/mcp/tools/publish.js +0 -59
  145. package/lib/mcp/tools/replay.d.ts +0 -3
  146. package/lib/mcp/tools/replay.js +0 -46
  147. package/lib/mcp/tools/session.d.ts +0 -10
  148. package/lib/mcp/tools/session.js +0 -29
  149. package/lib/mcp/tools/verification/diagnose.d.ts +0 -11
  150. package/lib/mcp/tools/verification/diagnose.js +0 -132
  151. package/lib/mcp/verifications/build.d.ts +0 -5
  152. package/lib/mcp/verifications/build.js +0 -14
  153. package/lib/mcp/verifications/create.d.ts +0 -3
  154. package/lib/mcp/verifications/create.js +0 -57
  155. package/lib/mcp/verifications/find.d.ts +0 -3
  156. package/lib/mcp/verifications/find.js +0 -37
  157. package/lib/mcp/verifications/get.d.ts +0 -3
  158. package/lib/mcp/verifications/get.js +0 -26
  159. package/lib/mcp/verifications/update.d.ts +0 -3
  160. package/lib/mcp/verifications/update.js +0 -78
  161. package/lib/old/tools/get-version-config.d.ts +0 -17
  162. package/lib/old/tools/get-version-config.js +0 -47
  163. package/lib/routes.gen.d.ts +0 -271
  164. package/lib/routes.gen.js +0 -73
  165. package/lib/types/openapi.d.ts +0 -4
  166. package/lib/types/openapi.gen.d.ts +0 -3967
  167. package/lib/types/openapi.gen.js +0 -5
  168. package/lib/types/openapi.js +0 -1
@@ -1,72 +1,162 @@
1
- import { connectCdp } from "../../../cdp/client.js";
1
+ import { badRequest } from '@hapi/boom';
2
+ import assert from 'node:assert';
3
+ import { connectCdp, connectCdpUrl, } from "../../../cdp/client.js";
2
4
  import { defaultProfileDir, launchChrome, waitForCdpReady, } from "../../../cdp/launcher.js";
3
5
  import { defaultConfigPath } from "../../../config/store.js";
4
6
  import { defineTool } from "../../server.js";
5
- export function attachTool(state) {
7
+ const DEFAULT_DEPENDENCIES = {
8
+ connectLocal: connectCdp,
9
+ connectRemote: connectCdpUrl,
10
+ };
11
+ export function attachTool(state, builderClient, name = 'attach_browser', beforeReplace, dependencies = DEFAULT_DEPENDENCIES) {
6
12
  return defineTool({
7
- name: 'attach_chrome',
8
- description: 'Attach to a Chrome instance via CDP. ' +
9
- 'Auto-launches a dedicated-profile Chrome ' +
10
- 'on first call if no attachMode is persisted. ' +
11
- 'Persists the chosen mode in ~/.reclaim/config.json.',
13
+ name,
14
+ description: name === 'attach_chrome'
15
+ ? 'Backward-compatible alias for attach_browser.'
16
+ : 'Connect the provider-authoring tools to a browser. Choose '
17
+ + 'dedicated (launch local Chrome), attach (existing local CDP '
18
+ + 'port), builder (quota-accounted Popcorn browser with a '
19
+ + 'shareable live view), or custom (caller-supplied CDP '
20
+ + 'websocket). '
21
+ + 'Builder and custom CDP credentials are held in memory and never '
22
+ + 'returned.',
12
23
  inputSchema: {
13
24
  type: 'object',
14
25
  properties: {
15
- attachMode: { type: 'string', enum: ['dedicated', 'attach'] },
16
- port: { type: 'number' },
26
+ mode: {
27
+ type: 'string',
28
+ enum: ['dedicated', 'attach', 'builder', 'custom'],
29
+ },
30
+ attachMode: {
31
+ type: 'string',
32
+ enum: ['dedicated', 'attach'],
33
+ description: 'Deprecated local-mode alias for mode.',
34
+ },
35
+ port: { type: 'number', minimum: 1, maximum: 65535 },
36
+ orgId: { type: 'string', format: 'uuid' },
37
+ cdpUrl: { type: 'string', maxLength: 4000 },
38
+ liveViewUrl: { type: 'string', format: 'uri', maxLength: 4000 },
17
39
  },
18
40
  },
19
41
  }, async (parsed) => {
20
- let cfg = state.config.read()?.browser_agent;
21
- if (parsed.attachMode || parsed.port) {
22
- cfg = {
23
- attachMode: parsed.attachMode ?? cfg?.attachMode ?? 'dedicated',
24
- port: parsed.port ?? cfg?.port ?? 9222,
25
- };
26
- state.config.write({ browser_agent: cfg });
27
- }
28
- if (!cfg) {
42
+ const persisted = state.config.read()?.browser_agent;
43
+ const mode = parsed.mode ?? parsed.attachMode ?? persisted?.attachMode;
44
+ if (!mode) {
29
45
  return {
30
46
  needsChoice: true,
31
- message: 'First-run: choose attach mode. ' +
32
- 'Call attach_chrome again with attachMode="dedicated" ' +
33
- '(auto-launch a Chrome profile under ~/.reclaim/chrome-profile) ' +
34
- 'or attachMode="attach" ' +
35
- '(connect to a Chrome you launched yourself with ' +
36
- 'BOTH --remote-debugging-port=9222 AND ' +
37
- '--user-data-dir=<some-non-default-path>; ' +
38
- 'since Chrome 136 the debug-port flag is silently ' +
39
- 'ignored if the default profile is in use).',
47
+ message: 'Choose a browser mode and call attach_browser again: '
48
+ + '"dedicated" launches a local dedicated Chrome profile; '
49
+ + '"attach" connects to a local Chrome debugging port; '
50
+ + '"builder" creates a quota-accounted remote browser and '
51
+ + 'requires orgId; "custom" requires a CDP websocket URL.',
40
52
  configPath: defaultConfigPath(),
41
53
  };
42
54
  }
43
- if (cfg.attachMode === 'dedicated') {
44
- try {
45
- await waitForCdpReady(cfg.port, 500);
46
- }
47
- catch {
48
- launchChrome({ port: cfg.port, profileDir: defaultProfileDir() });
49
- await waitForCdpReady(cfg.port, 10000);
50
- }
55
+ const next = await connect(mode, parsed, persisted, builderClient, dependencies);
56
+ try {
57
+ await beforeReplace?.();
58
+ await disposeAttached(state);
51
59
  }
52
- else {
53
- await waitForCdpReady(cfg.port, 2000).catch(() => {
54
- throw new Error(`attach mode: nothing answering CDP on port ${cfg.port}. ` +
55
- 'Chrome must be launched with BOTH ' +
56
- `--remote-debugging-port=${cfg.port} AND ` +
57
- '--user-data-dir=<some-non-default-path>. ' +
58
- 'Since Chrome 136 (May 2025), the debug-port flag is ' +
59
- 'silently ignored when the default ' +
60
- "user profile is in use — that's why nothing opens the port. " +
61
- 'Either re-launch Chrome with ' +
62
- 'a custom --user-data-dir, or switch to attachMode="dedicated".');
63
- });
60
+ catch (err) {
61
+ await disposeState(next).catch(() => { });
62
+ throw err;
64
63
  }
65
- if (state.current) {
66
- await state.current.handles.close().catch(() => { });
64
+ state.current = next;
65
+ if (mode === 'dedicated' || mode === 'attach') {
66
+ state.config.write({
67
+ browser_agent: { attachMode: mode, port: next.port },
68
+ });
67
69
  }
68
- const handles = await connectCdp(cfg.port);
69
- state.current = { handles, port: cfg.port };
70
- return { attached: true, port: cfg.port, mode: cfg.attachMode };
70
+ return {
71
+ attached: true,
72
+ mode,
73
+ ...(next.port ? { port: next.port } : {}),
74
+ ...(next.browserSessionId
75
+ ? { browserSessionId: next.browserSessionId }
76
+ : {}),
77
+ ...(next.liveViewUrl ? { liveViewUrl: next.liveViewUrl } : {}),
78
+ };
71
79
  });
72
80
  }
81
+ export async function disposeAttached(state) {
82
+ const current = state.current;
83
+ if (!current) {
84
+ return;
85
+ }
86
+ delete state.current;
87
+ await disposeState(current);
88
+ }
89
+ async function connect(mode, parsed, persisted, builderClient, dependencies) {
90
+ if (mode === 'builder') {
91
+ assert(builderClient, badRequest('Builder browser service is unavailable'));
92
+ assert(parsed.orgId, badRequest('orgId is required for builder mode'));
93
+ const { data } = await builderClient.call('CreateAgentBrowserSession', {
94
+ params: { orgId: parsed.orgId },
95
+ });
96
+ let handles;
97
+ try {
98
+ handles = await dependencies.connectRemote(data.cdpUrl);
99
+ }
100
+ catch (err) {
101
+ await builderClient.call('DisposeAgentBrowserSession', {
102
+ params: {
103
+ orgId: parsed.orgId,
104
+ browserSessionId: data.id,
105
+ },
106
+ }).catch(() => { });
107
+ throw err;
108
+ }
109
+ return {
110
+ handles,
111
+ mode,
112
+ liveViewUrl: data.liveViewUrl,
113
+ browserSessionId: data.id,
114
+ orgId: parsed.orgId,
115
+ disposeRemote: async () => {
116
+ await builderClient.call('DisposeAgentBrowserSession', {
117
+ params: {
118
+ orgId: parsed.orgId,
119
+ browserSessionId: data.id,
120
+ },
121
+ });
122
+ },
123
+ };
124
+ }
125
+ if (mode === 'custom') {
126
+ assert(parsed.cdpUrl, badRequest('cdpUrl is required for custom mode'));
127
+ const url = new URL(parsed.cdpUrl);
128
+ assert(url.protocol === 'ws:' || url.protocol === 'wss:', badRequest('cdpUrl must use ws:// or wss://'));
129
+ return {
130
+ handles: await dependencies.connectRemote(parsed.cdpUrl),
131
+ mode,
132
+ ...(parsed.liveViewUrl ? { liveViewUrl: parsed.liveViewUrl } : {}),
133
+ };
134
+ }
135
+ const port = parsed.port ?? persisted?.port ?? 9222;
136
+ if (mode === 'dedicated') {
137
+ try {
138
+ await waitForCdpReady(port, 500);
139
+ }
140
+ catch {
141
+ launchChrome({ port, profileDir: defaultProfileDir() });
142
+ await waitForCdpReady(port, 10000);
143
+ }
144
+ }
145
+ else {
146
+ await waitForCdpReady(port, 2000).catch(() => {
147
+ throw new Error(`attach mode: nothing answering CDP on port ${port}. Chrome must `
148
+ + `be launched with --remote-debugging-port=${port} AND `
149
+ + '--user-data-dir=<some-non-default-path>. Chrome 136+ ignores '
150
+ + 'the debugging flag for the default profile.');
151
+ });
152
+ }
153
+ return { handles: await dependencies.connectLocal(port), mode, port };
154
+ }
155
+ async function disposeState(state) {
156
+ try {
157
+ await state.handles.close();
158
+ }
159
+ finally {
160
+ await state.disposeRemote?.();
161
+ }
162
+ }
@@ -21,9 +21,16 @@ export interface AgentState {
21
21
  };
22
22
  captures: CaptureStore;
23
23
  }
24
+ /** Exported so a mode (e.g. old-devtools) that supplies its own publish tool
25
+ * can build the attach/capture state up front and share the SAME instance
26
+ * with `buildAgentTools` — letting that publish tool see the live attach
27
+ * state (e.g. to remind the caller about a still-open browser session). */
28
+ export declare function buildAgentState(): AgentState;
24
29
  /** Build the flat list of `RegisteredTool` entries, ready for
25
30
  * `registerToolList`.
26
31
  * Pass `client` to include the builder `create_provider_version_from_capture`;
27
32
  * omit it (e.g. OLD-devtools mode) to register only the backend-independent
28
- * capture/proof tools — old mode supplies its own publish tool. */
29
- export declare function buildAgentTools(client?: ReclaimClient): RegisteredTool[];
33
+ * capture/proof tools — old mode supplies its own publish tool.
34
+ * Pass `externalState` to share attach/capture state with tools registered
35
+ * elsewhere (e.g. old mode's publish tool) instead of building a fresh one. */
36
+ export declare function buildAgentTools(client?: ReclaimClient, builderClient?: ReclaimClient | undefined, externalState?: AgentState): RegisteredTool[];
@@ -13,13 +13,17 @@ import { ConfigStore } from "../../../config/store.js";
13
13
  import { ensureAttestorCrypto } from "../../../proof/proof-core.js";
14
14
  import { analyzeTool } from "./analyze.js";
15
15
  import { attachTool } from "./attach.js";
16
- import { captureTools } from "./capture.js";
16
+ import { captureTools, resetCaptures } from "./capture.js";
17
17
  import { listTabsTool } from "./eval.js";
18
18
  import { howItWorksTool } from "./how-it-works.js";
19
19
  import { createVersionFromCaptureTool } from "./publish.js";
20
20
  import { sessionTools } from "./session.js";
21
21
  import { sharedAuthoringTools } from "./shared.js";
22
- function buildAgentState() {
22
+ /** Exported so a mode (e.g. old-devtools) that supplies its own publish tool
23
+ * can build the attach/capture state up front and share the SAME instance
24
+ * with `buildAgentTools` — letting that publish tool see the live attach
25
+ * state (e.g. to remind the caller about a still-open browser session). */
26
+ export function buildAgentState() {
23
27
  return {
24
28
  attach: { config: new ConfigStore() },
25
29
  captures: { sessions: new Map() },
@@ -29,15 +33,19 @@ function buildAgentState() {
29
33
  * `registerToolList`.
30
34
  * Pass `client` to include the builder `create_provider_version_from_capture`;
31
35
  * omit it (e.g. OLD-devtools mode) to register only the backend-independent
32
- * capture/proof tools — old mode supplies its own publish tool. */
33
- export function buildAgentTools(client) {
34
- const state = buildAgentState();
36
+ * capture/proof tools — old mode supplies its own publish tool.
37
+ * Pass `externalState` to share attach/capture state with tools registered
38
+ * elsewhere (e.g. old mode's publish tool) instead of building a fresh one. */
39
+ export function buildAgentTools(client, builderClient = client, externalState) {
40
+ const state = externalState ?? buildAgentState();
35
41
  ensureAttestorCrypto();
36
42
  const tools = [];
37
43
  // Stateless docs tool — serve the mode-specific authoring guide to any
38
44
  // client. No `client` ⇒ old-devtools mode (see `mcp/start.ts`).
39
45
  tools.push(howItWorksTool(!client));
40
- tools.push(attachTool(state.attach));
46
+ const beforeReplace = () => resetCaptures(state.captures);
47
+ tools.push(attachTool(state.attach, builderClient, 'attach_browser', beforeReplace));
48
+ tools.push(attachTool(state.attach, builderClient, 'attach_chrome', beforeReplace));
41
49
  tools.push(listTabsTool(state.attach));
42
50
  for (const t of captureTools(state.attach, state.captures)) {
43
51
  tools.push(t);
@@ -53,7 +61,7 @@ export function buildAgentTools(client) {
53
61
  tools.push(t);
54
62
  }
55
63
  if (client) {
56
- tools.push(createVersionFromCaptureTool(client));
64
+ tools.push(createVersionFromCaptureTool(client, state.attach));
57
65
  }
58
66
  return tools;
59
67
  }
@@ -17,6 +17,7 @@ export interface CaptureStore {
17
17
  sessions: Map<string, CaptureEntry>;
18
18
  activeTab?: string;
19
19
  }
20
+ export declare function resetCaptures(captures: CaptureStore): Promise<void>;
20
21
  export declare function captureTools(attachRef: {
21
22
  current?: AttachState;
22
23
  }, captures: CaptureStore): RegisteredTool[];
@@ -1,10 +1,17 @@
1
1
  import { makeDraftStore } from "../../../authoring/index.js";
2
2
  import { startCapture } from "../../../cdp/capture.js";
3
3
  import { defineTool } from "../../server.js";
4
+ export async function resetCaptures(captures) {
5
+ for (const entry of captures.sessions.values()) {
6
+ await entry.session.stop().catch(() => { });
7
+ }
8
+ captures.sessions.clear();
9
+ delete captures.activeTab;
10
+ }
4
11
  export function captureTools(attachRef, captures) {
5
12
  const requireAttached = () => {
6
13
  if (!attachRef.current) {
7
- throw new Error('Not attached. Call attach_chrome first.');
14
+ throw new Error('Not attached. Call attach_browser first.');
8
15
  }
9
16
  return attachRef.current;
10
17
  };
@@ -1,4 +1,3 @@
1
- import { listTabs } from "../../../cdp/tabs.js";
2
1
  import { defineTool } from "../../server.js";
3
2
  /** The one browser tool that isn't session-bound: enumerate tabs so the caller
4
3
  * can pick one to `start_capture` on. Navigate/eval/wait are the shared
@@ -10,8 +9,8 @@ export function listTabsTool(attachRef) {
10
9
  inputSchema: { type: 'object', properties: {} },
11
10
  }, async () => {
12
11
  if (!attachRef.current) {
13
- throw new Error('Not attached. Call attach_chrome first.');
12
+ throw new Error('Not attached. Call attach_browser first.');
14
13
  }
15
- return { tabs: await listTabs(attachRef.current.port) };
14
+ return { tabs: await attachRef.current.handles.listTabs() };
16
15
  });
17
16
  }
@@ -6,6 +6,6 @@ import { type RegisteredTool } from '../../server.ts';
6
6
  * so any MCP client — not just Claude Code, which alone auto-loads skills — can
7
7
  * pull it. `oldMode` selects the backend-specific guide so the agent is never
8
8
  * told to call auth/publish tools that aren't registered in its mode.
9
- * Stateless: callable before `attach_chrome`.
9
+ * Stateless: callable before `attach_browser`.
10
10
  */
11
11
  export declare function howItWorksTool(oldMode: boolean): RegisteredTool;
@@ -7,7 +7,7 @@ import { defineTool } from "../../server.js";
7
7
  * so any MCP client — not just Claude Code, which alone auto-loads skills — can
8
8
  * pull it. `oldMode` selects the backend-specific guide so the agent is never
9
9
  * told to call auth/publish tools that aren't registered in its mode.
10
- * Stateless: callable before `attach_chrome`.
10
+ * Stateless: callable before `attach_browser`.
11
11
  */
12
12
  export function howItWorksTool(oldMode) {
13
13
  const guide = oldMode ? PROVIDER_GUIDE_OLD : PROVIDER_GUIDE_BUILDER;
@@ -1,2 +1,2 @@
1
- export { buildAgentTools } from './build.ts';
1
+ export { buildAgentState, buildAgentTools } from './build.ts';
2
2
  export type { AgentState } 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 { buildAgentTools } from "./build.js";
3
+ export { buildAgentState, buildAgentTools } from "./build.js";
@@ -1,3 +1,6 @@
1
1
  import type { ReclaimClient } from '@reclaimprotocol/client';
2
2
  import { type RegisteredTool } from '../../server.ts';
3
- export declare function createVersionFromCaptureTool(client: ReclaimClient): RegisteredTool;
3
+ import type { AttachState } from './attach.ts';
4
+ export declare function createVersionFromCaptureTool(client: ReclaimClient, attachRef?: {
5
+ current?: AttachState;
6
+ }): RegisteredTool;
@@ -97,7 +97,7 @@ const ALLOWED_JS_REQUEST_SCHEMA = {
97
97
  },
98
98
  required: ['url', 'method'],
99
99
  };
100
- export function createVersionFromCaptureTool(client) {
100
+ export function createVersionFromCaptureTool(client, attachRef) {
101
101
  return defineTool({
102
102
  name: 'create_provider_version_from_capture',
103
103
  description: 'Persist drafted ReclaimProvider(s) as a provider version. ' +
@@ -125,7 +125,11 @@ export function createVersionFromCaptureTool(client) {
125
125
  'sessions (verification matches those values exactly; a missing ' +
126
126
  'field is a 400 at session create). ' +
127
127
  'Refine types/descriptions via `update_provider_version` while ' +
128
- 'still on draft.',
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.',
129
133
  inputSchema: {
130
134
  type: 'object',
131
135
  properties: {
@@ -181,7 +185,28 @@ export function createVersionFromCaptureTool(client) {
181
185
  type: 'array',
182
186
  description: 'Extra requests the injection script is allowed to fire ' +
183
187
  '(e.g. paginated API calls), as RequestSelectionTemplates. ' +
184
- '`multiple`/`required` default to true.',
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.',
185
210
  items: ALLOWED_JS_REQUEST_SCHEMA,
186
211
  },
187
212
  interceptorOptions: {
@@ -240,6 +265,20 @@ export function createVersionFromCaptureTool(client) {
240
265
  ...(args.allowedJsRequests ?? []).map((r, i) => countsFor(`Injected request ${i + 1}`, r)),
241
266
  ];
242
267
  notes.push(...matchRedactionWarnings(requestCounts));
268
+ // A browser session left attached after a successful publish is easy
269
+ // to forget about — surface it so the caller asks the developer
270
+ // before closing it (a builder-mode session is quota-accounted and
271
+ // otherwise just sits until its TTL expires).
272
+ const attached = attachRef?.current;
273
+ if (attached) {
274
+ notes.push(`A "${attached.mode}" browser session is still attached` +
275
+ (attached.browserSessionId
276
+ ? ` (browserSessionId: ${attached.browserSessionId})`
277
+ : '') +
278
+ '. Ask the developer whether they want to keep it open for ' +
279
+ 'further edits — only call dispose_browser once they confirm ' +
280
+ 'they\'re done with it.');
281
+ }
243
282
  // Let the dev know how/when the script runs (and the window.Reclaim gap).
244
283
  if (args.jsUserScripts?.trim()) {
245
284
  notes.push('jsUserScripts will run in the Builder verification client before ' +
@@ -1,7 +1,7 @@
1
1
  import type { ConfigStore } from '../../../config/store.ts';
2
2
  import { type RegisteredTool } from '../../server.ts';
3
- import type { AttachState } from './attach.ts';
4
- import type { CaptureStore } from './capture.ts';
3
+ import { type AttachState } from './attach.ts';
4
+ import { type CaptureStore } from './capture.ts';
5
5
  export declare function sessionTools(attachRef: {
6
6
  current?: AttachState;
7
7
  config: ConfigStore;
@@ -1,4 +1,6 @@
1
1
  import { defineTool } from "../../server.js";
2
+ import { disposeAttached } from "./attach.js";
3
+ import { resetCaptures } from "./capture.js";
2
4
  export function sessionTools(attachRef, captures) {
3
5
  return [
4
6
  defineTool({
@@ -8,22 +10,36 @@ export function sessionTools(attachRef, captures) {
8
10
  }, async () => ({
9
11
  config: attachRef.config.read()?.browser_agent,
10
12
  attached: Boolean(attachRef.current),
13
+ mode: attachRef.current?.mode,
11
14
  port: attachRef.current?.port,
15
+ liveViewUrl: attachRef.current?.liveViewUrl,
16
+ browserSessionId: attachRef.current?.browserSessionId,
17
+ orgId: attachRef.current?.orgId,
12
18
  captures: [...captures.sessions.keys()],
13
19
  activeTab: captures.activeTab,
14
20
  })),
21
+ defineTool({
22
+ name: 'dispose_browser',
23
+ description: 'Stop captures, disconnect the current browser, and immediately '
24
+ + 'delete a Builder-hosted Popcorn session. Custom CDP sessions '
25
+ + 'are disconnected but never deleted by Reclaim.',
26
+ inputSchema: { type: 'object', properties: {} },
27
+ }, async () => {
28
+ await resetAll(attachRef, captures);
29
+ return { disposed: true };
30
+ }),
15
31
  defineTool({
16
32
  name: 'reset_session',
17
33
  description: 'Stop all captures and clear capture state — drops every '
18
34
  + 'session\'s drafts and their in-memory secrets.',
19
35
  inputSchema: { type: 'object', properties: {} },
20
36
  }, async () => {
21
- for (const entry of captures.sessions.values()) {
22
- await entry.session.stop().catch(() => { });
23
- }
24
- captures.sessions.clear();
25
- delete captures.activeTab;
37
+ await resetAll(attachRef, captures);
26
38
  return { reset: true };
27
39
  }),
28
40
  ];
29
41
  }
42
+ async function resetAll(attachRef, captures) {
43
+ await resetCaptures(captures);
44
+ await disposeAttached(attachRef);
45
+ }
@@ -42,14 +42,12 @@ function withLocalOprfMode(provider) {
42
42
  * captured requests, prove via the in-process attestor, replay by re-fetch. */
43
43
  function localBackend(entry) {
44
44
  const { tabClient, session, drafts } = entry;
45
- // The per-tab CDP client is strongly typed to `keyof Commands`; the shared
46
- // tools drive it with dynamic method/event names, so view it as string-keyed.
47
- const client = tabClient;
48
45
  return {
49
46
  capture: session,
50
47
  drafts,
51
- sendCdp: (method, params) => client.send(method, params ?? {}),
48
+ sendCdp: (method, params) => tabClient.send(method, params ?? {}),
52
49
  waitForEvent: (method, timeoutMs) => new Promise((resolve) => {
50
+ let stopListening = () => { };
53
51
  const onEvent = () => {
54
52
  cleanup();
55
53
  resolve('fired');
@@ -60,9 +58,9 @@ function localBackend(entry) {
60
58
  }, timeoutMs);
61
59
  const cleanup = () => {
62
60
  clearTimeout(timer);
63
- client.removeListener(method, onEvent);
61
+ stopListening();
64
62
  };
65
- client.once(method, onEvent);
63
+ stopListening = tabClient.subscribeOnce(method, onEvent);
66
64
  }),
67
65
  replay: async (draft, opts) => {
68
66
  // Auth-bound check: re-fetch with the secrets dropped. replayProvider
@@ -1,3 +1,3 @@
1
1
  import type { ReclaimClient } from '@reclaimprotocol/client';
2
2
  import { type RegisteredTool } from '../../server.ts';
3
- export declare function authenticateTool(client: ReclaimClient): RegisteredTool;
3
+ export declare function authenticateTool(client: ReclaimClient, name?: 'reclaim_authenticate' | 'authenticate_builder'): RegisteredTool;
@@ -6,11 +6,17 @@ import { defineTool } from "../../server.js";
6
6
  import { writeSession } from "./session-cache.js";
7
7
  const SCOPE = 'openid profile email';
8
8
  const CALLBACK_TIMEOUT_MS = 5 * 60 * 1000;
9
- export function authenticateTool(client) {
9
+ export function authenticateTool(client, name = 'reclaim_authenticate') {
10
10
  return defineTool({
11
- name: 'reclaim_authenticate',
12
- description: 'Authorize the CLI / MCP to make authenticated API calls. Opens the ' +
13
- 'browser to the builder sign-in page (standard OAuth 2.0 ' +
11
+ name,
12
+ description: (name === 'authenticate_builder'
13
+ ? 'Authenticate specifically with Builder. Available regardless '
14
+ + 'of '
15
+ + 'the selected publishing backend. '
16
+ : '')
17
+ + 'Authorize the CLI / MCP to make authenticated API calls. Opens '
18
+ + 'the '
19
+ + 'browser to the builder sign-in page (standard OAuth 2.0 ' +
14
20
  'authorization-code + PKCE with a localhost redirect) and waits for ' +
15
21
  'the user to sign in and approve. On success the access token is ' +
16
22
  'cached in ~/.reclaim/config.json and activated for this session.\n\n' +
@@ -164,8 +170,9 @@ async function exchangeCode(authBase, opts) {
164
170
  client_id: opts.clientId,
165
171
  }).toString(),
166
172
  });
167
- assert(res.ok, new Error(`Token exchange failed (${res.status}): ${await res.text()}`));
168
- const json = (await res.json());
173
+ const body = await res.text();
174
+ assert(res.ok, new Error(`Token exchange failed (${res.status}): ${body}`));
175
+ const json = JSON.parse(body);
169
176
  assert(json.access_token, new Error('Token exchange returned no access_token.'));
170
177
  return {
171
178
  access_token: json.access_token,
@@ -0,0 +1,8 @@
1
+ import type { ReclaimClient } from '@reclaimprotocol/client';
2
+ import { type RegisteredTool } from '../../server.ts';
3
+ /** Legacy-devtools mode needs a distinct Builder login solely for allocating
4
+ * quota-accounted Popcorn sessions. Builder mode uses its native auth tool. */
5
+ export declare function buildBuilderAuthenticationTools(client: ReclaimClient): RegisteredTool[];
6
+ /** Legacy-devtools mode lacks generated Builder API tools, so it needs this
7
+ * narrow organization-discovery bridge for remote-browser allocation. */
8
+ export declare function buildBuilderOrganizationTools(client: ReclaimClient): RegisteredTool[];
@@ -0,0 +1,31 @@
1
+ import { defineTool } from "../../server.js";
2
+ import { authenticateTool } from "./authenticate.js";
3
+ /** Legacy-devtools mode needs a distinct Builder login solely for allocating
4
+ * quota-accounted Popcorn sessions. Builder mode uses its native auth tool. */
5
+ export function buildBuilderAuthenticationTools(client) {
6
+ return [authenticateTool(client, 'authenticate_builder')];
7
+ }
8
+ /** Legacy-devtools mode lacks generated Builder API tools, so it needs this
9
+ * narrow organization-discovery bridge for remote-browser allocation. */
10
+ export function buildBuilderOrganizationTools(client) {
11
+ return [
12
+ defineTool({
13
+ name: 'list_builder_organizations',
14
+ description: 'List organizations available to the authenticated Builder user. '
15
+ + 'Use an organization id from this result when calling '
16
+ + 'attach_browser with mode="builder". Available only in '
17
+ + 'legacy-devtools publishing mode; Builder mode uses list_orgs.',
18
+ inputSchema: {
19
+ type: 'object',
20
+ properties: {
21
+ cursor: { type: 'string', maxLength: 500 },
22
+ },
23
+ },
24
+ }, async ({ cursor }) => {
25
+ const { data } = await client.call('ListOrgs', {
26
+ query: { ...(cursor ? { cursor } : {}) },
27
+ });
28
+ return data;
29
+ }),
30
+ ];
31
+ }
@@ -1,2 +1,3 @@
1
+ export { buildBuilderAuthenticationTools, buildBuilderOrganizationTools, } from './builder-access.ts';
1
2
  export { buildCredentialTools, buildLocalCredentialTools } from './build.ts';
2
3
  export { readSession } from './session-cache.ts';
@@ -1,4 +1,5 @@
1
1
  // Barrel for the credential tool group: local eth proof-owner key (env/PEM)
2
2
  // and the org-token-authenticated builder tools. Import from here.
3
+ export { buildBuilderAuthenticationTools, buildBuilderOrganizationTools, } from "./builder-access.js";
3
4
  export { buildCredentialTools, buildLocalCredentialTools } from "./build.js";
4
5
  export { readSession } from "./session-cache.js";