@reclaimprotocol/agent 0.1.28 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -9
- package/lib/consts.d.ts +5 -5
- package/lib/consts.js +7 -7
- package/lib/live/tunnel-watchdog.d.ts +1 -0
- package/lib/live/tunnel-watchdog.js +66 -0
- package/lib/live/tunnel.d.ts +30 -7
- package/lib/live/tunnel.js +217 -40
- package/lib/mcp/api/operations.gen.d.ts +12 -0
- package/lib/mcp/api/operations.gen.js +537 -59
- package/lib/mcp/api/tools.js +27 -5
- package/lib/mcp/instructions.js +25 -32
- package/lib/mcp/remap_errors.js +6 -0
- package/lib/mcp/server.js +2 -8
- package/lib/mcp/start.js +3 -5
- package/lib/mcp/success-response.d.ts +5 -0
- package/lib/mcp/success-response.js +21 -0
- package/lib/mcp/tools/agent/attach.js +9 -66
- package/lib/mcp/tools/agent/build.d.ts +0 -3
- package/lib/mcp/tools/agent/build.js +3 -4
- package/lib/mcp/tools/agent/how-it-works.d.ts +5 -15
- package/lib/mcp/tools/agent/how-it-works.js +9 -22
- package/lib/mcp/tools/agent/live-view.d.ts +1 -15
- package/lib/mcp/tools/agent/live-view.js +71 -96
- package/lib/mcp/tools/agent/propose.js +7 -23
- package/lib/mcp/tools/agent/publish.js +80 -18
- package/lib/mcp/tools/agent/session.d.ts +4 -7
- package/lib/mcp/tools/agent/session.js +8 -23
- package/lib/mcp/tools/verification/result.d.ts +2 -1
- package/lib/mcp/tools/verification/result.js +29 -28
- package/lib/old/client.d.ts +2 -2
- package/lib/old/flag.d.ts +2 -5
- package/lib/old/flag.js +2 -5
- package/lib/old/instructions.d.ts +1 -1
- package/lib/old/instructions.js +1 -1
- package/lib/old/to-register.d.ts +4 -3
- package/lib/old/tools/publish.js +11 -14
- package/lib/proof/attestor-auth.d.ts +9 -1
- package/lib/proof/attestor-auth.js +15 -2
- package/lib/proof/attestor.d.ts +2 -2
- package/lib/proof/attestor.js +20 -2
- package/lib/proof/pod-prover.js +1 -1
- package/lib/proof/proof-core.d.ts +4 -0
- package/lib/proof/proof-core.js +20 -0
- package/lib/provider/injection-doc.d.ts +3 -2
- package/lib/provider/injection-doc.js +12 -10
- package/lib/provider/matcher.js +19 -15
- package/lib/provider/to-version.d.ts +3 -0
- package/lib/provider/to-version.js +2 -1
- package/package.json +3 -3
- package/skill/SKILL.builder.md +32 -85
- package/skill/SKILL.common.md +38 -22
- package/skill/SKILL.old.md +17 -3
- package/skill/builder-mode.md +37 -4
- package/skill/community-contributions.md +12 -3
- package/skill/hash-validation.md +9 -9
- package/skill/interception.md +39 -46
- package/skill/old-mode.md +13 -0
- package/skill/user-script.md +24 -8
- package/skill/verification-client-integration.md +130 -0
- package/src/consts.ts +9 -7
- package/src/mcp/api/operations.gen.ts +539 -59
- package/src/mcp/api/tools.ts +38 -7
- package/src/mcp/instructions.ts +35 -32
- package/src/mcp/remap_errors.ts +6 -0
- package/src/mcp/server.ts +2 -8
- package/src/mcp/start.ts +3 -5
- package/src/mcp/success-response.ts +26 -0
- package/src/mcp/tools/agent/attach.ts +9 -71
- package/src/mcp/tools/agent/build.ts +4 -7
- package/src/mcp/tools/agent/how-it-works.ts +9 -22
- package/src/mcp/tools/agent/live-view.ts +76 -116
- package/src/mcp/tools/agent/propose.ts +7 -23
- package/src/mcp/tools/agent/publish.ts +89 -18
- package/src/mcp/tools/agent/session.ts +6 -23
- package/src/mcp/tools/verification/result.ts +30 -29
- package/src/old/client.ts +2 -2
- package/src/old/flag.ts +2 -5
- package/src/old/instructions.ts +1 -1
- package/src/old/to-register.ts +4 -3
- package/src/old/tools/publish.ts +16 -14
- package/src/proof/attestor-auth.ts +18 -1
- package/src/proof/attestor.ts +24 -3
- package/src/proof/pod-prover.ts +1 -1
- package/src/proof/proof-core.ts +28 -0
- package/src/provider/injection-doc.ts +12 -10
- package/src/provider/matcher.ts +25 -17
- package/src/provider/to-version.ts +7 -1
- package/src/live/cloudflared.ts +0 -348
- package/src/live/tunnel.ts +0 -254
package/lib/mcp/api/tools.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { remapErrorAsResponse } from "../remap_errors.js";
|
|
2
|
+
import { successResponse } from "../success-response.js";
|
|
2
3
|
import { MCP_TOOLS } from "./operations.gen.js";
|
|
3
4
|
/**
|
|
4
5
|
* The generated `MCP_TOOLS` are keyed by OpenAPI `operationId` (PascalCase,
|
|
@@ -15,18 +16,20 @@ function toSnakeCase(name) {
|
|
|
15
16
|
}
|
|
16
17
|
export function registerTools(server, client) {
|
|
17
18
|
for (const [opId, shape] of Object.entries(MCP_TOOLS)) {
|
|
19
|
+
if (!isOperationId(opId)) {
|
|
20
|
+
throw new Error(`Generated MCP operation is not in the API client: ${opId}`);
|
|
21
|
+
}
|
|
18
22
|
// snake_case to match the hand-written tools. A name that collides with
|
|
19
23
|
// a hand-written tool throws here at startup — intentional: hand-written
|
|
20
24
|
// and generated names must stay disjoint (see start.ts), so a clash is a
|
|
21
25
|
// bug to surface, not silently shadow.
|
|
22
26
|
server.registerTool(toSnakeCase(opId), shape, async (args) => {
|
|
23
27
|
try {
|
|
28
|
+
if (!isOperationArgs(opId, args)) {
|
|
29
|
+
throw new Error(`Invalid generated MCP arguments for ${opId}`);
|
|
30
|
+
}
|
|
24
31
|
const result = await client.call(opId, args);
|
|
25
|
-
return
|
|
26
|
-
content: [{
|
|
27
|
-
type: 'text', text: JSON.stringify(result.data, null, 2)
|
|
28
|
-
}],
|
|
29
|
-
};
|
|
32
|
+
return successResponse(result.data);
|
|
30
33
|
}
|
|
31
34
|
catch (err) {
|
|
32
35
|
return remapErrorAsResponse(err);
|
|
@@ -34,3 +37,22 @@ export function registerTools(server, client) {
|
|
|
34
37
|
});
|
|
35
38
|
}
|
|
36
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Always true in practice — `value` comes from `Object.entries(MCP_TOOLS)`
|
|
42
|
+
* itself — but the check still earns its keep: it narrows `opId` to
|
|
43
|
+
* `IOperationId` for `client.call` below without an unchecked cast, and it
|
|
44
|
+
* turns "the generator and the API client drifted apart" into a startup
|
|
45
|
+
* error instead of a silent `as`.
|
|
46
|
+
*/
|
|
47
|
+
function isOperationId(value) {
|
|
48
|
+
return value in MCP_TOOLS;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The MCP SDK parses `args` through the generated Zod schema before invoking
|
|
52
|
+
* this callback. Keep a runtime object check at this TypeScript boundary so
|
|
53
|
+
* generated operation arguments retain their operation-specific type without
|
|
54
|
+
* an unchecked cast.
|
|
55
|
+
*/
|
|
56
|
+
function isOperationArgs(_operationId, value) {
|
|
57
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
58
|
+
}
|
package/lib/mcp/instructions.js
CHANGED
|
@@ -1,29 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Loads concise, always-on mode instructions and topic guides served on
|
|
3
|
+
* demand by `how_it_works`. Keeping detailed guidance out of tool schemas
|
|
4
|
+
* avoids adding it to every request's context.
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* deliberately small, since every client pays for them on every turn;
|
|
7
|
-
* - the deep authoring guide, split into TOPICS and served on demand by the
|
|
8
|
-
* `how_it_works` tool. Nothing here is inlined into a tool schema: a doc
|
|
9
|
-
* embedded in an input-schema description is paid for on every request
|
|
10
|
-
* whether or not it's ever relevant.
|
|
11
|
-
*
|
|
12
|
-
* The prose lives in markdown (published via package.json's `files`) so it
|
|
13
|
-
* can be edited without touching code. Paths resolve identically from
|
|
14
|
-
* `src/mcp` (dev) and `lib/mcp` (published) — both are two levels below the
|
|
15
|
-
* package root.
|
|
16
|
-
*
|
|
17
|
-
* Assembly has three steps:
|
|
18
|
-
* 1. `<!-- common: name -->` splices a shared block out of `SKILL.common.md`
|
|
19
|
-
* (delimited there by `<!-- section: name -->`), so the two mode guides
|
|
20
|
-
* don't drift on the ~60% of prose that is identical.
|
|
21
|
-
* 2. `%CTX%` expands to the mode's consumer-param namespace — `context.` on
|
|
22
|
-
* the builder, `context_` on old-devtools. That is the only textual
|
|
23
|
-
* difference across most shared blocks, so tokenizing it keeps them
|
|
24
|
-
* shared.
|
|
25
|
-
* 3. `<!-- topic: name -->` splits the result into the units `how_it_works`
|
|
26
|
-
* serves. Backend-independent topics come from their own files instead.
|
|
6
|
+
* `<!-- common: name -->` includes shared sections, `%CTX%` selects the
|
|
7
|
+
* mode-specific context prefix, and `<!-- topic: name -->` creates a topic.
|
|
27
8
|
*/
|
|
28
9
|
import assert from 'node:assert';
|
|
29
10
|
import { readFileSync } from 'node:fs';
|
|
@@ -31,7 +12,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
31
12
|
function readSkill(file) {
|
|
32
13
|
const url = new URL(`../../skill/${file}`, import.meta.url);
|
|
33
14
|
return readFileSync(fileURLToPath(url), 'utf8')
|
|
34
|
-
//
|
|
15
|
+
// These files are internal topic fragments, but tolerate skill frontmatter.
|
|
35
16
|
.replace(/^---\n[\s\S]*?\n---\n/, '')
|
|
36
17
|
.trim();
|
|
37
18
|
}
|
|
@@ -42,7 +23,7 @@ function splitBlocks(text, marker) {
|
|
|
42
23
|
const blocks = new Map();
|
|
43
24
|
const re = new RegExp(`<!-- ${marker}: (\\S+) -->\\n([\\s\\S]*?)(?=\\n<!-- ${marker}: |$)`, 'g');
|
|
44
25
|
for (const match of text.matchAll(re)) {
|
|
45
|
-
// Trim blank
|
|
26
|
+
// Trim blank lines only, not inline indentation. Some blocks are
|
|
46
27
|
// list-continuation text that's intentionally indented on its first line.
|
|
47
28
|
blocks.set(match[1], match[2].replace(/^\n+/, '').replace(/\n+$/, ''));
|
|
48
29
|
}
|
|
@@ -55,11 +36,18 @@ const SHARED_TOPIC_FILES = {
|
|
|
55
36
|
'interception': 'interception.md',
|
|
56
37
|
'hash-validation': 'hash-validation.md',
|
|
57
38
|
};
|
|
39
|
+
/** Detailed topics that exist only in Builder mode. */
|
|
40
|
+
const BUILDER_TOPIC_FILES = {
|
|
41
|
+
'contributions': 'community-contributions.md',
|
|
42
|
+
'integration': 'verification-client-integration.md',
|
|
43
|
+
};
|
|
58
44
|
/** One line per topic, shown in the `how_it_works` index so a caller can pick
|
|
59
45
|
* the right one without fetching several. Keyed across both modes; only the
|
|
60
46
|
* topics a mode actually has are listed back to it. */
|
|
61
47
|
const TOPIC_SUMMARIES = {
|
|
62
48
|
'overview': 'Terminology, your stance, and the whole loop at a glance.',
|
|
49
|
+
'contributions': 'Contributing a public provider as a volunteer: the '
|
|
50
|
+
+ 'candidate-and-review workflow and the extra rules it adds.',
|
|
63
51
|
'auth': 'Signing in to the backend, and which token authorizes what.',
|
|
64
52
|
'devtools-migration': 'Importing a public legacy provider with the same '
|
|
65
53
|
+ 'UUID.',
|
|
@@ -78,21 +66,22 @@ const TOPIC_SUMMARIES = {
|
|
|
78
66
|
+ 'and going live.',
|
|
79
67
|
'discover': 'Finding providers, resolving them, and creating a verification '
|
|
80
68
|
+ 'session.',
|
|
81
|
-
'integration': 'Builder
|
|
82
|
-
+ '
|
|
69
|
+
'integration': 'Set up a Builder token, callback, frontend and backend '
|
|
70
|
+
+ 'with @reclaimprotocol/client; direct `api=2` API and result '
|
|
71
|
+
+ 'verification.',
|
|
83
72
|
'user-script': 'Authoring jsUserScripts / customInjection to drive '
|
|
84
73
|
+ 'login-gated pages.',
|
|
85
74
|
'interception': 'injectionType / interceptorType, live interception, the '
|
|
86
75
|
+ 'document-replay flag, and what to do when a site won\'t load or '
|
|
87
76
|
+ 'won\'t let the user sign in.',
|
|
88
|
-
'hash-validation': 'bodySniff, and how
|
|
89
|
-
+ 'the declared spec hash.',
|
|
77
|
+
'hash-validation': 'bodySniff, and how verifyRequestProof({ providerId }) '
|
|
78
|
+
+ 'gates on the declared spec hash.',
|
|
90
79
|
'credentials': 'The eth proof-owner key run_proof signs with.',
|
|
91
80
|
'never-do': 'The short list of things that always break a provider.',
|
|
92
81
|
'troubleshooting': 'Expired drafts, browser problems, and where to look '
|
|
93
82
|
+ 'next.',
|
|
94
83
|
};
|
|
95
|
-
function buildGuide(file, ctxPrefix) {
|
|
84
|
+
function buildGuide(file, ctxPrefix, modeTopicFiles = {}) {
|
|
96
85
|
const raw = readSkill(file)
|
|
97
86
|
.replace(/<!-- common: (\S+) -->/g, (_whole, name) => {
|
|
98
87
|
const section = COMMON_SECTIONS.get(name);
|
|
@@ -105,6 +94,10 @@ function buildGuide(file, ctxPrefix) {
|
|
|
105
94
|
for (const [name, path] of Object.entries(SHARED_TOPIC_FILES)) {
|
|
106
95
|
topics.set(name, readSkill(path));
|
|
107
96
|
}
|
|
97
|
+
for (const [name, path] of Object.entries(modeTopicFiles)) {
|
|
98
|
+
assert(topics.has(name), `${file}: no topic marker named "${name}"`);
|
|
99
|
+
topics.set(name, readSkill(path));
|
|
100
|
+
}
|
|
108
101
|
const index = [...topics.keys()].map((topic) => {
|
|
109
102
|
const summary = TOPIC_SUMMARIES[topic];
|
|
110
103
|
assert(summary, `${file}: no TOPIC_SUMMARIES entry for "${topic}"`);
|
|
@@ -112,7 +105,7 @@ function buildGuide(file, ctxPrefix) {
|
|
|
112
105
|
});
|
|
113
106
|
return { topics, index, full: [...topics.values()].join('\n\n') };
|
|
114
107
|
}
|
|
115
|
-
export const GUIDE_BUILDER = buildGuide('SKILL.builder.md', 'context.');
|
|
108
|
+
export const GUIDE_BUILDER = buildGuide('SKILL.builder.md', 'context.', BUILDER_TOPIC_FILES);
|
|
116
109
|
export const GUIDE_OLD = buildGuide('SKILL.old.md', 'context_');
|
|
117
110
|
export function guideFor(oldMode) {
|
|
118
111
|
return oldMode ? GUIDE_OLD : GUIDE_BUILDER;
|
package/lib/mcp/remap_errors.js
CHANGED
|
@@ -5,6 +5,12 @@ import { errorAsToolText } from "../tools/registry.js";
|
|
|
5
5
|
// (shared with the cloud loop); Boom problems also attach structuredContent.
|
|
6
6
|
export const remapErrorAsResponse = (err) => {
|
|
7
7
|
const text = errorAsToolText(err);
|
|
8
|
+
// Two unrelated things can carry structured data here: the client's own
|
|
9
|
+
// ProblemError (an RFC 9457 response from a Builder API call), and a
|
|
10
|
+
// hand-thrown Boom error whose second argument is arbitrary tool context,
|
|
11
|
+
// for example `badRequest('invalid provider', { field: 'providerId' })`.
|
|
12
|
+
// `isProblemDetails` only rejects a wrongly typed KNOWN field, so it lets
|
|
13
|
+
// that arbitrary shape through too — this isn't a strict RFC 9457 check.
|
|
8
14
|
const data = isProblemError(err)
|
|
9
15
|
? err.data
|
|
10
16
|
: isBoom(err) && err.output?.statusCode
|
package/lib/mcp/server.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { toZod } from "./api/to-zod.js";
|
|
2
2
|
import { remapErrorAsResponse } from "./remap_errors.js";
|
|
3
|
+
import { successResponse } from "./success-response.js";
|
|
3
4
|
// The tool primitive lives in `../tools/registry.ts` (SDK-free, so the cloud
|
|
4
5
|
// loop can use it too). Re-exported here so the MCP-side tool files can keep
|
|
5
6
|
// importing `defineTool`/`RegisteredTool` from the server module.
|
|
@@ -16,14 +17,7 @@ export function registerToolList(server, tools) {
|
|
|
16
17
|
inputSchema: toZod(tool.definition.inputSchema),
|
|
17
18
|
}, async (args) => {
|
|
18
19
|
try {
|
|
19
|
-
return
|
|
20
|
-
content: [
|
|
21
|
-
{
|
|
22
|
-
type: 'text',
|
|
23
|
-
text: JSON.stringify(await tool.handler(args), null, 2),
|
|
24
|
-
},
|
|
25
|
-
],
|
|
26
|
-
};
|
|
20
|
+
return successResponse(await tool.handler(args));
|
|
27
21
|
}
|
|
28
22
|
catch (err) {
|
|
29
23
|
return remapErrorAsResponse(err);
|
package/lib/mcp/start.js
CHANGED
|
@@ -15,7 +15,7 @@ import { buildAgentState, buildAgentTools, buildBuilderAuthenticationTools, buil
|
|
|
15
15
|
import { BUILDER_MODE_INSTRUCTIONS } from "./instructions.js";
|
|
16
16
|
import { registerToolList } from "./server.js";
|
|
17
17
|
export async function startMcpServer() {
|
|
18
|
-
// Mode precedence: explicit
|
|
18
|
+
// Mode precedence: explicit env → persisted config → builder.
|
|
19
19
|
// The same store instance backs the set_devtools_mode toggle below.
|
|
20
20
|
const config = new ConfigStore();
|
|
21
21
|
const oldMode = resolveOldMode(config.read()?.mode);
|
|
@@ -42,8 +42,7 @@ export async function startMcpServer() {
|
|
|
42
42
|
// the agent tools are enabled, so a user can always switch either direction.
|
|
43
43
|
registerToolList(server, buildModeTools(config, oldMode));
|
|
44
44
|
if (oldMode) {
|
|
45
|
-
// OLD-devtools mode
|
|
46
|
-
// production while builder is built. All auto-generated builder tools
|
|
45
|
+
// Explicit OLD-devtools mode. All auto-generated builder tools
|
|
47
46
|
// are disabled; a small ReclaimOldClient serves authenticate / publish /
|
|
48
47
|
// get_me_providers instead.
|
|
49
48
|
const oldClient = new ReclaimOldClient({
|
|
@@ -89,8 +88,7 @@ export async function startMcpServer() {
|
|
|
89
88
|
// Anything the session holds has to be released when the session ends, and
|
|
90
89
|
// the session can end without a single tool call: the developer closes their
|
|
91
90
|
// agent, the client kills the server, stdin drops. Nothing ran here before,
|
|
92
|
-
// which left a container up
|
|
93
|
-
// live browser after the session that made it was gone.
|
|
91
|
+
// which left a container up after the session that made it was gone.
|
|
94
92
|
if (agentToolsEnabled) {
|
|
95
93
|
installShutdownHooks(() => agentState);
|
|
96
94
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
/** Return successful tool data in both MCP representations. Older clients read
|
|
3
|
+
* the JSON text; newer clients can consume `structuredContent` without asking
|
|
4
|
+
* the model to parse or reconstruct identifiers, URLs, and state. */
|
|
5
|
+
export declare function successResponse(value: unknown): CallToolResult;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Return successful tool data in both MCP representations. Older clients read
|
|
2
|
+
* the JSON text; newer clients can consume `structuredContent` without asking
|
|
3
|
+
* the model to parse or reconstruct identifiers, URLs, and state. */
|
|
4
|
+
export function successResponse(value) {
|
|
5
|
+
return {
|
|
6
|
+
structuredContent: structured(value),
|
|
7
|
+
content: [{
|
|
8
|
+
type: 'text',
|
|
9
|
+
text: JSON.stringify(value, null, 2) ?? 'null',
|
|
10
|
+
}],
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function structured(value) {
|
|
14
|
+
if (isRecord(value)) {
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
return { result: value ?? null };
|
|
18
|
+
}
|
|
19
|
+
function isRecord(value) {
|
|
20
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
21
|
+
}
|
|
@@ -16,22 +16,8 @@ import { ensureLiveViewUrl } from "./live-view-url.js";
|
|
|
16
16
|
* show — one on this machine is watched by looking at it.
|
|
17
17
|
*/
|
|
18
18
|
export const SHAREABLE_MODES = new Set(['container']);
|
|
19
|
-
/**
|
|
20
|
-
*
|
|
21
|
-
* DESCRIPTION (so an agent can put the choice to the developer straight from
|
|
22
|
-
* the tool list, without a discovery round-trip) and returned verbatim as
|
|
23
|
-
* structured `options` when the tool is called without a `mode` anyway.
|
|
24
|
-
*
|
|
25
|
-
* Being the single source for both is the point — an option list that a model
|
|
26
|
-
* reads in the description but that disagrees with what the tool returns is
|
|
27
|
-
* worse than either alone.
|
|
28
|
-
*
|
|
29
|
-
* Ordered and labelled BY USE CASE, because that is the question a developer
|
|
30
|
-
* can actually answer. "Which CDP transport?" is not; "does anyone else need to
|
|
31
|
-
* get into this browser?" is, and it decides two of the four. `useCase` is the
|
|
32
|
-
* grouping key, so the rendered list reads as two pairs rather than four
|
|
33
|
-
* unrelated transports.
|
|
34
|
-
*/
|
|
19
|
+
/** Browser sources returned when the caller omits `mode`. Keep selection detail
|
|
20
|
+
* here instead of repeating it in the always-loaded tool description. */
|
|
35
21
|
const BROWSER_OPTIONS = [
|
|
36
22
|
{
|
|
37
23
|
mode: 'dedicated',
|
|
@@ -103,28 +89,6 @@ const BROWSER_OPTIONS = [
|
|
|
103
89
|
liveView: 'None, unless you pass your own liveViewUrl alongside the cdpUrl.',
|
|
104
90
|
},
|
|
105
91
|
];
|
|
106
|
-
/**
|
|
107
|
-
* The options as prose, grouped by use case.
|
|
108
|
-
*
|
|
109
|
-
* The grouping is the useful part: it turns "pick one of five transports" into
|
|
110
|
-
* "answer one question, then pick one of two".
|
|
111
|
-
*/
|
|
112
|
-
function renderBrowserOptions() {
|
|
113
|
-
const seen = new Set();
|
|
114
|
-
const lines = [];
|
|
115
|
-
for (const option of BROWSER_OPTIONS) {
|
|
116
|
-
if (!seen.has(option.useCase)) {
|
|
117
|
-
seen.add(option.useCase);
|
|
118
|
-
lines.push(`— ${option.useCase.toUpperCase()} —`);
|
|
119
|
-
}
|
|
120
|
-
const needs = option.requires.length
|
|
121
|
-
? ` Needs: ${option.requires.join('; ')}.`
|
|
122
|
-
: '';
|
|
123
|
-
lines.push(`(${option.mode}) ${option.label}. ${option.detail}${needs}`
|
|
124
|
-
+ ` Live view: ${option.liveView}`);
|
|
125
|
-
}
|
|
126
|
-
return lines.join(' ');
|
|
127
|
-
}
|
|
128
92
|
const DEFAULT_DEPENDENCIES = {
|
|
129
93
|
connectLocal: connectCdp,
|
|
130
94
|
connectRemote: connectCdpUrl,
|
|
@@ -140,34 +104,13 @@ dependencies = {}) {
|
|
|
140
104
|
};
|
|
141
105
|
return defineTool({
|
|
142
106
|
name: 'attach_browser',
|
|
143
|
-
description: '
|
|
144
|
-
+ '
|
|
145
|
-
+ '
|
|
146
|
-
+ '
|
|
147
|
-
+ '
|
|
148
|
-
+ '
|
|
149
|
-
+ '
|
|
150
|
-
+ 'THEMSELVES → offer `dedicated` or `attach`. SOMEONE ELSE, or the '
|
|
151
|
-
+ 'site only works on a phone → offer `container` or `builder`, '
|
|
152
|
-
+ 'which are the only two that produce a link another person can '
|
|
153
|
-
+ 'open. Say which of those two costs money: `container` is free '
|
|
154
|
-
+ 'but needs Docker, `builder` needs no install but is billed '
|
|
155
|
-
+ 'against the org\'s quota. '
|
|
156
|
-
+ 'Then relay the matching options with their live-view lines. You '
|
|
157
|
-
+ 'do not need a discovery call — the full list is here, and calling '
|
|
158
|
-
+ 'with no `mode` returns it as structured `options` if you would '
|
|
159
|
-
+ 'rather read it back verbatim. '
|
|
160
|
-
+ 'OPTIONS — ' + renderBrowserOptions() + ' '
|
|
161
|
-
+ 'A `container` browser returns a local `viewerUrl`; relay it. When '
|
|
162
|
-
+ 'someone remote needs in, call share_browser_view and relay the '
|
|
163
|
-
+ 'FULL public url verbatim, on its own line. `builder` returns a '
|
|
164
|
-
+ 'hosted `liveViewUrl` — relay that the same way. `dedicated`, '
|
|
165
|
-
+ '`attach` and `custom` cannot be shared at all: if they ask for a '
|
|
166
|
-
+ 'link after picking one, say so and offer to re-attach with '
|
|
167
|
-
+ '`container`. '
|
|
168
|
-
+ 'Builder and custom CDP credentials are held in memory and are '
|
|
169
|
-
+ 'never returned. '
|
|
170
|
-
+ 'More: how_it_works({ topic: "browser" }).',
|
|
107
|
+
description: 'Connects provider-authoring tools to a browser. Call without '
|
|
108
|
+
+ '`mode` to receive the current options, then ask the developer to '
|
|
109
|
+
+ 'choose; `builder` consumes quota and only `container` and `builder` '
|
|
110
|
+
+ 'support a shareable live view. Call again once with the chosen '
|
|
111
|
+
+ 'mode. Relay returned live-view URLs verbatim. For selection, '
|
|
112
|
+
+ 'geolocation, and lifecycle guidance, call '
|
|
113
|
+
+ 'how_it_works({ topic: "browser" }).',
|
|
171
114
|
inputSchema: {
|
|
172
115
|
type: 'object',
|
|
173
116
|
properties: {
|
|
@@ -14,15 +14,12 @@ import { ConfigStore } from '../../../config/store.ts';
|
|
|
14
14
|
import { type RegisteredTool } from '../../server.ts';
|
|
15
15
|
import { type AttachState } from './attach.ts';
|
|
16
16
|
import { type CaptureStore } from './capture.ts';
|
|
17
|
-
import { type LiveShareRef } from './live-view.ts';
|
|
18
17
|
export interface AgentState {
|
|
19
18
|
attach: {
|
|
20
19
|
current?: AttachState;
|
|
21
20
|
config: ConfigStore;
|
|
22
21
|
};
|
|
23
22
|
captures: CaptureStore;
|
|
24
|
-
/** The one public live view, when `share_browser_view` has opened it. */
|
|
25
|
-
liveShare: LiveShareRef;
|
|
26
23
|
}
|
|
27
24
|
/** Exported so a mode (for example, old-devtools) that supplies its own
|
|
28
25
|
* publish tool can build the attach/capture state up front and share the SAME
|
|
@@ -30,7 +30,6 @@ export function buildAgentState() {
|
|
|
30
30
|
return {
|
|
31
31
|
attach: { config: new ConfigStore() },
|
|
32
32
|
captures: { sessions: new Map() },
|
|
33
|
-
liveShare: {},
|
|
34
33
|
};
|
|
35
34
|
}
|
|
36
35
|
/** Build the flat list of `RegisteredTool` entries, ready for
|
|
@@ -54,11 +53,11 @@ export function buildAgentTools(client, builderClient = client, externalState) {
|
|
|
54
53
|
for (const t of captureTools(state.attach, state.captures)) {
|
|
55
54
|
tools.push(t);
|
|
56
55
|
}
|
|
57
|
-
for (const t of sessionTools(state.attach, state.captures
|
|
56
|
+
for (const t of sessionTools(state.attach, state.captures)) {
|
|
58
57
|
tools.push(t);
|
|
59
58
|
}
|
|
60
59
|
// Local-CDP only: a builder-mode browser already has a hosted live view.
|
|
61
|
-
for (const t of liveViewTools(state.attach
|
|
60
|
+
for (const t of liveViewTools(state.attach)) {
|
|
62
61
|
tools.push(t);
|
|
63
62
|
}
|
|
64
63
|
for (const t of podProofTools(state.attach, state.captures)) {
|
|
@@ -81,5 +80,5 @@ export function buildAgentTools(client, builderClient = client, externalState) {
|
|
|
81
80
|
* safe when nothing was ever attached.
|
|
82
81
|
*/
|
|
83
82
|
export async function disposeAgentState(state) {
|
|
84
|
-
await resetAll(state.attach, state.captures
|
|
83
|
+
await resetAll(state.attach, state.captures);
|
|
85
84
|
}
|
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
import { type RegisteredTool } from '../../server.ts';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Topic-scoped rather than all-at-once because the full guide is ~7k tokens:
|
|
9
|
-
* paying that to answer "how do I mark a header secret?" crowds out the actual
|
|
10
|
-
* task, which is exactly what makes an agent slow and prone to inventing
|
|
11
|
-
* details. Every response carries the topic index, so a caller that guessed
|
|
12
|
-
* wrong (or called with no topic at all) always learns what else is available
|
|
13
|
-
* without a second round-trip.
|
|
14
|
-
*
|
|
15
|
-
* `oldMode` selects the backend-specific guide, so the agent is never told to
|
|
16
|
-
* call auth/publish tools that aren't registered in its mode. Stateless:
|
|
17
|
-
* callable before `attach_browser`.
|
|
3
|
+
* Serve one mode-specific guide topic without loading the full guide (~7k
|
|
4
|
+
* tokens) — paying that cost to answer one question would crowd out the
|
|
5
|
+
* actual task. Every response includes the topic index, so a caller that
|
|
6
|
+
* guessed the topic name wrong still learns what else exists in the same
|
|
7
|
+
* round-trip.
|
|
18
8
|
*/
|
|
19
9
|
export declare function howItWorksTool(oldMode: boolean): RegisteredTool;
|
|
@@ -1,34 +1,21 @@
|
|
|
1
1
|
import { guideFor } from "../../instructions.js";
|
|
2
2
|
import { defineTool } from "../../server.js";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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`.
|
|
4
|
+
* Serve one mode-specific guide topic without loading the full guide (~7k
|
|
5
|
+
* tokens) — paying that cost to answer one question would crowd out the
|
|
6
|
+
* actual task. Every response includes the topic index, so a caller that
|
|
7
|
+
* guessed the topic name wrong still learns what else exists in the same
|
|
8
|
+
* round-trip.
|
|
19
9
|
*/
|
|
20
10
|
export function howItWorksTool(oldMode) {
|
|
21
11
|
const guide = guideFor(oldMode);
|
|
22
12
|
const names = [...guide.topics.keys()];
|
|
23
13
|
return defineTool({
|
|
24
14
|
name: 'how_it_works',
|
|
25
|
-
description: '
|
|
26
|
-
+ '
|
|
27
|
-
+ '
|
|
28
|
-
+
|
|
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.',
|
|
15
|
+
description: 'Returns topic-specific provider-authoring guidance for the active '
|
|
16
|
+
+ 'backend. Call without a topic for the overview and index. Before an '
|
|
17
|
+
+ 'unfamiliar or sensitive step, request that topic. Prefer one topic; '
|
|
18
|
+
+ '`all` returns the much larger complete guide.',
|
|
32
19
|
inputSchema: {
|
|
33
20
|
type: 'object',
|
|
34
21
|
properties: {
|
|
@@ -1,19 +1,5 @@
|
|
|
1
|
-
import { startTunnel, type Tunnel } from '../../../live/tunnel.ts';
|
|
2
1
|
import { type RegisteredTool } from '../../server.ts';
|
|
3
2
|
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
|
-
type TunnelStarter = typeof startTunnel;
|
|
13
|
-
/** Tear down whatever share is running. Safe when none is. Exported so the
|
|
14
|
-
* session tools can call it from `dispose_browser` / `reset_session`. */
|
|
15
|
-
export declare function stopLiveShare(ref: LiveShareRef): Promise<boolean>;
|
|
16
3
|
export declare function liveViewTools(attachRef: {
|
|
17
4
|
current?: AttachState;
|
|
18
|
-
}
|
|
19
|
-
export {};
|
|
5
|
+
}): RegisteredTool[];
|