@vibebrowser/mcp 0.2.6 → 0.2.7
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 +26 -5
- package/dist/browser-cli.d.ts.map +1 -1
- package/dist/browser-cli.js +343 -58
- package/dist/browser-cli.js.map +1 -1
- package/dist/browser-main.js +1 -0
- package/dist/browser-main.js.map +1 -1
- package/dist/cli.js +50 -4
- package/dist/cli.js.map +1 -1
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +0 -3
- package/dist/connection.js.map +1 -1
- package/dist/devtools-fallback.d.ts +22 -0
- package/dist/devtools-fallback.d.ts.map +1 -0
- package/dist/devtools-fallback.js +172 -0
- package/dist/devtools-fallback.js.map +1 -0
- package/dist/relay.d.ts +12 -0
- package/dist/relay.d.ts.map +1 -1
- package/dist/relay.js +233 -48
- package/dist/relay.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +67 -12
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/chrome-devtools-relay.md +7 -0
- package/docs/openclaw-local-browser.md +11 -1
- package/openclaw/vibebrowser/SKILL.md +70 -4
- package/package.json +5 -1
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
6
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
7
|
+
const TOOLS_REFRESH_TIMEOUT_MS = 6_000;
|
|
8
|
+
const TOOL_CALL_TIMEOUT_MS = 30_000;
|
|
9
|
+
const DEVTOOLS_UNAVAILABLE_PREFIX = 'chrome-devtools backend unavailable';
|
|
10
|
+
const DEVTOOLS_NOT_INSTALLED_MESSAGE = `${DEVTOOLS_UNAVAILABLE_PREFIX}: chrome-devtools-mcp is not installed`;
|
|
11
|
+
function normalizeToolName(value) {
|
|
12
|
+
return value.replace(/[-\s]/g, '_').toLowerCase();
|
|
13
|
+
}
|
|
14
|
+
function toToolDefinition(input) {
|
|
15
|
+
return {
|
|
16
|
+
name: input.name,
|
|
17
|
+
description: input.description ?? '',
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: input.inputSchema.properties,
|
|
21
|
+
required: input.inputSchema.required,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export class DevtoolsFallbackConnection extends EventEmitter {
|
|
26
|
+
debug;
|
|
27
|
+
client = null;
|
|
28
|
+
transport = null;
|
|
29
|
+
tools = [];
|
|
30
|
+
available = false;
|
|
31
|
+
unavailableReason;
|
|
32
|
+
constructor(debug) {
|
|
33
|
+
super();
|
|
34
|
+
this.debug = debug;
|
|
35
|
+
}
|
|
36
|
+
async start() {
|
|
37
|
+
const binaryPath = this.resolveBinaryPath();
|
|
38
|
+
if (!binaryPath) {
|
|
39
|
+
this.unavailableReason = DEVTOOLS_NOT_INSTALLED_MESSAGE;
|
|
40
|
+
this.log(this.unavailableReason);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const transport = new StdioClientTransport({
|
|
44
|
+
command: process.execPath,
|
|
45
|
+
args: [binaryPath, '--autoConnect'],
|
|
46
|
+
stderr: this.debug ? 'inherit' : 'pipe',
|
|
47
|
+
});
|
|
48
|
+
const client = new Client({
|
|
49
|
+
name: 'vibebrowser-mcp-devtools-fallback',
|
|
50
|
+
version: '1.0.0',
|
|
51
|
+
}, { capabilities: {} });
|
|
52
|
+
try {
|
|
53
|
+
await client.connect(transport, { timeout: TOOLS_REFRESH_TIMEOUT_MS });
|
|
54
|
+
this.client = client;
|
|
55
|
+
this.transport = transport;
|
|
56
|
+
this.available = true;
|
|
57
|
+
this.unavailableReason = undefined;
|
|
58
|
+
await this.refreshTools(TOOLS_REFRESH_TIMEOUT_MS);
|
|
59
|
+
this.emit('connected');
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
63
|
+
this.unavailableReason = `${DEVTOOLS_UNAVAILABLE_PREFIX}: ${message}`;
|
|
64
|
+
this.log(this.unavailableReason);
|
|
65
|
+
this.available = false;
|
|
66
|
+
this.client = null;
|
|
67
|
+
this.transport = null;
|
|
68
|
+
this.tools = [];
|
|
69
|
+
this.emit('unavailable', this.unavailableReason);
|
|
70
|
+
try {
|
|
71
|
+
await transport.close();
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// ignore cleanup errors
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async stop() {
|
|
79
|
+
this.available = false;
|
|
80
|
+
this.tools = [];
|
|
81
|
+
if (this.client) {
|
|
82
|
+
try {
|
|
83
|
+
await this.client.close();
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// ignore shutdown errors
|
|
87
|
+
}
|
|
88
|
+
this.client = null;
|
|
89
|
+
}
|
|
90
|
+
if (this.transport) {
|
|
91
|
+
try {
|
|
92
|
+
await this.transport.close();
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// ignore shutdown errors
|
|
96
|
+
}
|
|
97
|
+
this.transport = null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async refreshTools(timeoutMs = TOOLS_REFRESH_TIMEOUT_MS) {
|
|
101
|
+
if (!this.client || !this.available) {
|
|
102
|
+
return this.tools;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const listed = await this.client.listTools(undefined, { timeout: timeoutMs });
|
|
106
|
+
const nextTools = listed.tools.map(toToolDefinition);
|
|
107
|
+
const previousNames = this.tools.map((tool) => normalizeToolName(tool.name)).join(',');
|
|
108
|
+
const nextNames = nextTools.map((tool) => normalizeToolName(tool.name)).join(',');
|
|
109
|
+
this.tools = nextTools;
|
|
110
|
+
if (previousNames !== nextNames) {
|
|
111
|
+
this.emit('tools_updated', this.tools);
|
|
112
|
+
}
|
|
113
|
+
return this.tools;
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
117
|
+
this.log(`Unable to refresh chrome-devtools tools: ${message}`);
|
|
118
|
+
return this.tools;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
getTools() {
|
|
122
|
+
return this.tools;
|
|
123
|
+
}
|
|
124
|
+
hasTool(name) {
|
|
125
|
+
const needle = normalizeToolName(name);
|
|
126
|
+
return this.tools.some((tool) => normalizeToolName(tool.name) === needle);
|
|
127
|
+
}
|
|
128
|
+
async callTool(name, args, timeoutMs = TOOL_CALL_TIMEOUT_MS) {
|
|
129
|
+
if (!this.client || !this.available) {
|
|
130
|
+
throw new Error(this.unavailableReason || DEVTOOLS_UNAVAILABLE_PREFIX);
|
|
131
|
+
}
|
|
132
|
+
const result = await this.client.callTool({ name, arguments: args }, undefined, { timeout: timeoutMs });
|
|
133
|
+
const content = Array.isArray(result.content)
|
|
134
|
+
? result.content
|
|
135
|
+
: [{ type: 'text', text: JSON.stringify(result) }];
|
|
136
|
+
const isError = typeof result.isError === 'boolean' ? result.isError : false;
|
|
137
|
+
return {
|
|
138
|
+
success: !isError,
|
|
139
|
+
isError,
|
|
140
|
+
content,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
isAvailable() {
|
|
144
|
+
return this.available;
|
|
145
|
+
}
|
|
146
|
+
getUnavailableReason() {
|
|
147
|
+
return this.unavailableReason;
|
|
148
|
+
}
|
|
149
|
+
resolveBinaryPath() {
|
|
150
|
+
try {
|
|
151
|
+
const require = createRequire(import.meta.url);
|
|
152
|
+
const packageJsonPath = require.resolve('chrome-devtools-mcp/package.json');
|
|
153
|
+
const metadata = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
154
|
+
const bin = typeof metadata.bin === 'string'
|
|
155
|
+
? metadata.bin
|
|
156
|
+
: metadata.bin?.['chrome-devtools-mcp'] ?? metadata.bin?.['chrome-devtools'];
|
|
157
|
+
if (!bin) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
return resolve(dirname(packageJsonPath), bin);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
log(message) {
|
|
167
|
+
if (this.debug) {
|
|
168
|
+
console.error(`[vibebrowser-mcp] ${message}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=devtools-fallback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devtools-fallback.js","sourceRoot":"","sources":["../src/devtools-fallback.ts"],"names":[],"mappings":"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"}
|
package/dist/relay.d.ts
CHANGED
|
@@ -13,6 +13,11 @@ export declare const EXTENSION_PORT: number;
|
|
|
13
13
|
export declare const AGENT_PORT: number;
|
|
14
14
|
/**
|
|
15
15
|
* Vibe MCP Relay Server
|
|
16
|
+
*
|
|
17
|
+
* Owns exactly one shared chrome-devtools fallback backend process per relay
|
|
18
|
+
* daemon instance. All connected MCP clients (vibebrowser-mcp and
|
|
19
|
+
* vibebrowser-cli) route through this relay, so fallback lifecycle and tool
|
|
20
|
+
* routing stay deterministic across concurrent agents.
|
|
16
21
|
*/
|
|
17
22
|
export declare class RelayServer extends EventEmitter {
|
|
18
23
|
private extensionWss;
|
|
@@ -24,6 +29,7 @@ export declare class RelayServer extends EventEmitter {
|
|
|
24
29
|
private requestIdCounter;
|
|
25
30
|
private anonymousSessionCounter;
|
|
26
31
|
private debug;
|
|
32
|
+
private readonly devtoolsFallback;
|
|
27
33
|
constructor(debug?: boolean);
|
|
28
34
|
/**
|
|
29
35
|
* Start the relay server
|
|
@@ -87,6 +93,12 @@ export declare class RelayServer extends EventEmitter {
|
|
|
87
93
|
private buildSessionsList;
|
|
88
94
|
private getDefaultSession;
|
|
89
95
|
private resolveTargetSession;
|
|
96
|
+
private parseToolDefinitions;
|
|
97
|
+
private getToolsForSession;
|
|
98
|
+
private findExtensionTool;
|
|
99
|
+
private findFallbackTool;
|
|
100
|
+
private broadcastDefaultToolsToAgents;
|
|
101
|
+
private hasConnectedExtensionSession;
|
|
90
102
|
private sendSessionStateToAgent;
|
|
91
103
|
private broadcastSessionState;
|
|
92
104
|
private rejectPendingRequestsForSession;
|
package/dist/relay.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relay.d.ts","sourceRoot":"","sources":["../src/relay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"relay.d.ts","sourceRoot":"","sources":["../src/relay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAsBtC,eAAO,MAAM,cAAc,QAAiD,CAAC;AAC7E,eAAO,MAAM,UAAU,QAA6C,CAAC;AAkErE;;;;;;;GAOG;AACH,qBAAa,WAAY,SAAQ,YAAY;IAC3C,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,iBAAiB,CAA4C;IACrE,OAAO,CAAC,iBAAiB,CAAqC;IAC9D,OAAO,CAAC,MAAM,CAA2C;IACzD,OAAO,CAAC,eAAe,CAA0C;IACjE,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,uBAAuB,CAAK;IACpC,OAAO,CAAC,KAAK,CAAU;IACvB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA6B;gBAElD,KAAK,GAAE,OAAe;IAMlC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmC5B;;OAEG;YACW,oBAAoB;IAoBlC;;OAEG;YACW,gBAAgB;IAoB9B;;OAEG;IACH,OAAO,CAAC,yBAAyB;IA2CjC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAiD7B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAiE9B;;OAEG;YACW,kBAAkB;IA+KhC;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAUjC;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IAmB7B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,iBAAiB;IAOzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAYzB;;OAEG;YACW,QAAQ;IAiDtB;;OAEG;IACH,OAAO,CAAC,GAAG;IAiBX,OAAO,CAAC,sBAAsB;IA2C9B,OAAO,CAAC,yBAAyB;IAajC,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,oBAAoB;IAW5B,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,6BAA6B;IAUrC,OAAO,CAAC,4BAA4B;IAIpC,OAAO,CAAC,uBAAuB;IAkB/B,OAAO,CAAC,qBAAqB;IAkB7B,OAAO,CAAC,+BAA+B;CAiBxC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAmBxC;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,GAAG,IAAI,CAY3C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,GAAE,OAAe,GAAG,IAAI,CAgB7D;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAM5E"}
|
package/dist/relay.js
CHANGED
|
@@ -13,6 +13,7 @@ import { writeFileSync, readFileSync, existsSync, unlinkSync, mkdirSync } from '
|
|
|
13
13
|
import { join } from 'path';
|
|
14
14
|
import { homedir } from 'os';
|
|
15
15
|
import { EventEmitter } from 'events';
|
|
16
|
+
import { DevtoolsFallbackConnection } from './devtools-fallback.js';
|
|
16
17
|
function parseEnvPort(name, fallback) {
|
|
17
18
|
const raw = process.env[name];
|
|
18
19
|
if (!raw) {
|
|
@@ -24,6 +25,9 @@ function parseEnvPort(name, fallback) {
|
|
|
24
25
|
}
|
|
25
26
|
return port;
|
|
26
27
|
}
|
|
28
|
+
function normalizeToolName(value) {
|
|
29
|
+
return value.replace(/[-\s]/g, '_').toLowerCase();
|
|
30
|
+
}
|
|
27
31
|
// Ports (19888/19889 to avoid conflict with Playwriter MCP which uses 19988/19989)
|
|
28
32
|
export const EXTENSION_PORT = parseEnvPort('VIBE_MCP_EXTENSION_PORT', 19889);
|
|
29
33
|
export const AGENT_PORT = parseEnvPort('VIBE_MCP_AGENT_PORT', 19888);
|
|
@@ -33,6 +37,11 @@ const PID_FILE = join(VIBE_DIR, 'relay.pid');
|
|
|
33
37
|
const LOG_FILE = join(VIBE_DIR, 'relay.log');
|
|
34
38
|
/**
|
|
35
39
|
* Vibe MCP Relay Server
|
|
40
|
+
*
|
|
41
|
+
* Owns exactly one shared chrome-devtools fallback backend process per relay
|
|
42
|
+
* daemon instance. All connected MCP clients (vibebrowser-mcp and
|
|
43
|
+
* vibebrowser-cli) route through this relay, so fallback lifecycle and tool
|
|
44
|
+
* routing stay deterministic across concurrent agents.
|
|
36
45
|
*/
|
|
37
46
|
export class RelayServer extends EventEmitter {
|
|
38
47
|
extensionWss = null;
|
|
@@ -44,9 +53,11 @@ export class RelayServer extends EventEmitter {
|
|
|
44
53
|
requestIdCounter = 0;
|
|
45
54
|
anonymousSessionCounter = 0;
|
|
46
55
|
debug;
|
|
56
|
+
devtoolsFallback;
|
|
47
57
|
constructor(debug = false) {
|
|
48
58
|
super();
|
|
49
59
|
this.debug = debug;
|
|
60
|
+
this.devtoolsFallback = new DevtoolsFallbackConnection(debug);
|
|
50
61
|
}
|
|
51
62
|
/**
|
|
52
63
|
* Start the relay server
|
|
@@ -60,6 +71,19 @@ export class RelayServer extends EventEmitter {
|
|
|
60
71
|
await this.startExtensionServer();
|
|
61
72
|
// Start agent WebSocket server
|
|
62
73
|
await this.startAgentServer();
|
|
74
|
+
await this.devtoolsFallback.start();
|
|
75
|
+
this.devtoolsFallback.on('tools_updated', () => {
|
|
76
|
+
this.broadcastDefaultToolsToAgents();
|
|
77
|
+
this.broadcastSessionState();
|
|
78
|
+
});
|
|
79
|
+
this.devtoolsFallback.on('connected', () => {
|
|
80
|
+
this.broadcastDefaultToolsToAgents();
|
|
81
|
+
this.broadcastSessionState();
|
|
82
|
+
});
|
|
83
|
+
this.devtoolsFallback.on('unavailable', () => {
|
|
84
|
+
this.broadcastDefaultToolsToAgents();
|
|
85
|
+
this.broadcastSessionState();
|
|
86
|
+
});
|
|
63
87
|
// Write PID file
|
|
64
88
|
writeFileSync(PID_FILE, String(process.pid));
|
|
65
89
|
this.log(`Relay started (PID: ${process.pid})`);
|
|
@@ -138,6 +162,7 @@ export class RelayServer extends EventEmitter {
|
|
|
138
162
|
this.broadcastSessionState();
|
|
139
163
|
if (this.extensionSessions.size === 0) {
|
|
140
164
|
this.broadcastToAgents({ type: 'extension_disconnected' });
|
|
165
|
+
this.broadcastDefaultToolsToAgents();
|
|
141
166
|
}
|
|
142
167
|
});
|
|
143
168
|
ws.on('error', (error) => {
|
|
@@ -158,13 +183,17 @@ export class RelayServer extends EventEmitter {
|
|
|
158
183
|
this.log(`Agent connected: ${agentId} (total: ${this.agents.size})`);
|
|
159
184
|
this.sendSessionStateToAgent(ws);
|
|
160
185
|
const defaultSession = this.getDefaultSession();
|
|
161
|
-
|
|
162
|
-
|
|
186
|
+
const tools = this.getToolsForSession(defaultSession);
|
|
187
|
+
if (tools.length > 0) {
|
|
188
|
+
ws.send(JSON.stringify({ type: 'tools_list', data: tools, sessionId: defaultSession?.sessionId }));
|
|
163
189
|
}
|
|
164
190
|
ws.on('message', (data) => {
|
|
165
191
|
try {
|
|
166
192
|
const message = JSON.parse(data.toString());
|
|
167
|
-
this.handleAgentMessage(agentId, message)
|
|
193
|
+
this.handleAgentMessage(agentId, message).catch((error) => {
|
|
194
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
195
|
+
this.log(`Unhandled agent message failure (${agentId}): ${reason}`);
|
|
196
|
+
});
|
|
168
197
|
}
|
|
169
198
|
catch (error) {
|
|
170
199
|
this.log(`Failed to parse agent message: ${error}`);
|
|
@@ -207,18 +236,28 @@ export class RelayServer extends EventEmitter {
|
|
|
207
236
|
// Forward response to the requesting agent
|
|
208
237
|
const agent = this.agents.get(pending.agentId);
|
|
209
238
|
if (agent) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
239
|
+
if (message.type === 'tools_list') {
|
|
240
|
+
agent.ws.send(JSON.stringify({
|
|
241
|
+
type: 'tools_list',
|
|
242
|
+
requestId: pending.originalRequestId,
|
|
243
|
+
data: this.getToolsForSession(this.extensionSessions.get(pending.sessionId) || null),
|
|
244
|
+
sessionId: pending.sessionId,
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
agent.ws.send(JSON.stringify({
|
|
249
|
+
...message,
|
|
250
|
+
sessionId: session.sessionId,
|
|
251
|
+
requestId: pending.originalRequestId,
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
215
254
|
}
|
|
216
255
|
// For tools_list we still want to cache + broadcast to *other* agents
|
|
217
256
|
// so they stay in sync, but the requesting agent already got its copy.
|
|
218
257
|
if (message.type === 'tools_list') {
|
|
219
|
-
session.tools = message.data;
|
|
258
|
+
session.tools = this.parseToolDefinitions(message.data);
|
|
220
259
|
this.stopToolsSyncLoop(session);
|
|
221
|
-
this.
|
|
260
|
+
this.broadcastDefaultToolsToAgents(pending.agentId);
|
|
222
261
|
this.broadcastSessionState();
|
|
223
262
|
}
|
|
224
263
|
return;
|
|
@@ -226,9 +265,9 @@ export class RelayServer extends EventEmitter {
|
|
|
226
265
|
}
|
|
227
266
|
// Handle unsolicited tools list (e.g. extension announces on connect)
|
|
228
267
|
if (message.type === 'tools_list') {
|
|
229
|
-
session.tools = message.data;
|
|
268
|
+
session.tools = this.parseToolDefinitions(message.data);
|
|
230
269
|
this.stopToolsSyncLoop(session);
|
|
231
|
-
this.
|
|
270
|
+
this.broadcastDefaultToolsToAgents();
|
|
232
271
|
this.broadcastSessionState();
|
|
233
272
|
return;
|
|
234
273
|
}
|
|
@@ -238,25 +277,118 @@ export class RelayServer extends EventEmitter {
|
|
|
238
277
|
/**
|
|
239
278
|
* Handle message from an agent
|
|
240
279
|
*/
|
|
241
|
-
handleAgentMessage(agentId, message) {
|
|
242
|
-
|
|
243
|
-
|
|
280
|
+
async handleAgentMessage(agentId, message) {
|
|
281
|
+
try {
|
|
282
|
+
this.log(`Agent ${agentId} message: ${message.type}`);
|
|
283
|
+
if (message.type === 'list_sessions') {
|
|
284
|
+
const agent = this.agents.get(agentId);
|
|
285
|
+
if (agent && message.requestId) {
|
|
286
|
+
agent.ws.send(JSON.stringify({
|
|
287
|
+
type: 'sessions_list',
|
|
288
|
+
requestId: message.requestId,
|
|
289
|
+
sessions: this.buildSessionsList(),
|
|
290
|
+
}));
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const requestedSessionId = typeof message.data?.sessionId === 'string' ? message.data.sessionId : undefined;
|
|
295
|
+
const targetSession = this.resolveTargetSession(requestedSessionId);
|
|
244
296
|
const agent = this.agents.get(agentId);
|
|
245
|
-
if (agent
|
|
297
|
+
if (!agent || !message.requestId) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (message.type === 'list_tools') {
|
|
301
|
+
if (requestedSessionId && !targetSession) {
|
|
302
|
+
agent.ws.send(JSON.stringify({
|
|
303
|
+
type: 'error',
|
|
304
|
+
requestId: message.requestId,
|
|
305
|
+
error: `No browser session connected for sessionId=${requestedSessionId}`,
|
|
306
|
+
}));
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
246
309
|
agent.ws.send(JSON.stringify({
|
|
247
|
-
type: '
|
|
310
|
+
type: 'tools_list',
|
|
248
311
|
requestId: message.requestId,
|
|
249
|
-
|
|
312
|
+
data: this.getToolsForSession(targetSession),
|
|
313
|
+
sessionId: targetSession?.sessionId,
|
|
250
314
|
}));
|
|
315
|
+
return;
|
|
251
316
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
317
|
+
if (message.type === 'call_tool') {
|
|
318
|
+
const requestedToolName = message.data?.name;
|
|
319
|
+
if (typeof requestedToolName !== 'string' || requestedToolName.trim().length === 0) {
|
|
320
|
+
agent.ws.send(JSON.stringify({
|
|
321
|
+
type: 'error',
|
|
322
|
+
requestId: message.requestId,
|
|
323
|
+
error: 'Tool name is required',
|
|
324
|
+
}));
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (requestedSessionId && !targetSession) {
|
|
328
|
+
agent.ws.send(JSON.stringify({
|
|
329
|
+
type: 'error',
|
|
330
|
+
requestId: message.requestId,
|
|
331
|
+
error: `No browser session connected for sessionId=${requestedSessionId}`,
|
|
332
|
+
}));
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const extensionTool = this.findExtensionTool(targetSession, requestedToolName);
|
|
336
|
+
if (extensionTool && targetSession) {
|
|
337
|
+
const relayRequestId = `relay_${++this.requestIdCounter}`;
|
|
338
|
+
const cleanData = message.data ? { ...message.data } : undefined;
|
|
339
|
+
if (cleanData && 'sessionId' in cleanData) {
|
|
340
|
+
delete cleanData.sessionId;
|
|
341
|
+
}
|
|
342
|
+
const forwardMessage = {
|
|
343
|
+
...message,
|
|
344
|
+
requestId: relayRequestId,
|
|
345
|
+
...(cleanData ? { data: cleanData } : {}),
|
|
346
|
+
};
|
|
347
|
+
this.pendingRequests.set(relayRequestId, {
|
|
348
|
+
agentId,
|
|
349
|
+
originalRequestId: message.requestId,
|
|
350
|
+
lastSentAt: Date.now(),
|
|
351
|
+
forwardMessage,
|
|
352
|
+
sessionId: targetSession.sessionId,
|
|
353
|
+
});
|
|
354
|
+
targetSession.ws.send(JSON.stringify(forwardMessage));
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (!this.hasConnectedExtensionSession() && this.findFallbackTool(requestedToolName)) {
|
|
358
|
+
try {
|
|
359
|
+
const args = message.data?.arguments && typeof message.data.arguments === 'object'
|
|
360
|
+
? message.data.arguments
|
|
361
|
+
: {};
|
|
362
|
+
const result = await this.devtoolsFallback.callTool(requestedToolName, args);
|
|
363
|
+
agent.ws.send(JSON.stringify({
|
|
364
|
+
type: 'tool_result',
|
|
365
|
+
requestId: message.requestId,
|
|
366
|
+
data: result,
|
|
367
|
+
sessionId: targetSession?.sessionId,
|
|
368
|
+
}));
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
372
|
+
agent.ws.send(JSON.stringify({
|
|
373
|
+
type: 'error',
|
|
374
|
+
requestId: message.requestId,
|
|
375
|
+
error: reason,
|
|
376
|
+
sessionId: targetSession?.sessionId,
|
|
377
|
+
}));
|
|
378
|
+
}
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
agent.ws.send(JSON.stringify({
|
|
382
|
+
type: 'error',
|
|
383
|
+
requestId: message.requestId,
|
|
384
|
+
error: targetSession
|
|
385
|
+
? `Tool not found: ${requestedToolName}`
|
|
386
|
+
: 'No extension connected',
|
|
387
|
+
}));
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (!targetSession) {
|
|
391
|
+
// No extension connected, send error back
|
|
260
392
|
agent.ws.send(JSON.stringify({
|
|
261
393
|
type: 'error',
|
|
262
394
|
requestId: message.requestId,
|
|
@@ -264,31 +396,43 @@ export class RelayServer extends EventEmitter {
|
|
|
264
396
|
? `No browser session connected for sessionId=${requestedSessionId}`
|
|
265
397
|
: 'No extension connected',
|
|
266
398
|
}));
|
|
399
|
+
return;
|
|
267
400
|
}
|
|
268
|
-
|
|
401
|
+
// Generate relay request ID
|
|
402
|
+
const relayRequestId = `relay_${++this.requestIdCounter}`;
|
|
403
|
+
const cleanData = message.data ? { ...message.data } : undefined;
|
|
404
|
+
if (cleanData && 'sessionId' in cleanData) {
|
|
405
|
+
delete cleanData.sessionId;
|
|
406
|
+
}
|
|
407
|
+
const forwardMessage = {
|
|
408
|
+
...message,
|
|
409
|
+
requestId: relayRequestId,
|
|
410
|
+
...(cleanData ? { data: cleanData } : {}),
|
|
411
|
+
};
|
|
412
|
+
// Store pending request mapping so it can be replayed if the extension
|
|
413
|
+
// swaps sockets mid-flight.
|
|
414
|
+
this.pendingRequests.set(relayRequestId, {
|
|
415
|
+
agentId,
|
|
416
|
+
originalRequestId: message.requestId,
|
|
417
|
+
lastSentAt: Date.now(),
|
|
418
|
+
forwardMessage,
|
|
419
|
+
sessionId: targetSession.sessionId,
|
|
420
|
+
});
|
|
421
|
+
// Forward to extension with relay request ID
|
|
422
|
+
targetSession.ws.send(JSON.stringify(forwardMessage));
|
|
269
423
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
424
|
+
catch (error) {
|
|
425
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
426
|
+
this.log(`Failed to handle agent message (${agentId}, ${message.type}): ${reason}`);
|
|
427
|
+
const agent = this.agents.get(agentId);
|
|
428
|
+
if (agent && message.requestId && agent.ws.readyState === WebSocket.OPEN) {
|
|
429
|
+
agent.ws.send(JSON.stringify({
|
|
430
|
+
type: 'error',
|
|
431
|
+
requestId: message.requestId,
|
|
432
|
+
error: `Relay error: ${reason}`,
|
|
433
|
+
}));
|
|
434
|
+
}
|
|
275
435
|
}
|
|
276
|
-
const forwardMessage = {
|
|
277
|
-
...message,
|
|
278
|
-
requestId: relayRequestId,
|
|
279
|
-
...(cleanData ? { data: cleanData } : {}),
|
|
280
|
-
};
|
|
281
|
-
// Store pending request mapping so it can be replayed if the extension
|
|
282
|
-
// swaps sockets mid-flight.
|
|
283
|
-
this.pendingRequests.set(relayRequestId, {
|
|
284
|
-
agentId,
|
|
285
|
-
originalRequestId: message.requestId,
|
|
286
|
-
lastSentAt: Date.now(),
|
|
287
|
-
forwardMessage,
|
|
288
|
-
sessionId: targetSession.sessionId,
|
|
289
|
-
});
|
|
290
|
-
// Forward to extension with relay request ID
|
|
291
|
-
targetSession.ws.send(JSON.stringify(forwardMessage));
|
|
292
436
|
}
|
|
293
437
|
/**
|
|
294
438
|
* Request tools list from extension
|
|
@@ -400,6 +544,7 @@ export class RelayServer extends EventEmitter {
|
|
|
400
544
|
}
|
|
401
545
|
this.extensionSessions.clear();
|
|
402
546
|
this.socketToSessionId.clear();
|
|
547
|
+
await this.devtoolsFallback.stop();
|
|
403
548
|
// Close servers
|
|
404
549
|
if (this.agentWss) {
|
|
405
550
|
this.agentWss.close();
|
|
@@ -507,6 +652,46 @@ export class RelayServer extends EventEmitter {
|
|
|
507
652
|
}
|
|
508
653
|
return this.getDefaultSession();
|
|
509
654
|
}
|
|
655
|
+
parseToolDefinitions(data) {
|
|
656
|
+
if (!Array.isArray(data)) {
|
|
657
|
+
return [];
|
|
658
|
+
}
|
|
659
|
+
return data
|
|
660
|
+
.filter((entry) => Boolean(entry) && typeof entry === 'object' && typeof entry.name === 'string')
|
|
661
|
+
.map((entry) => entry);
|
|
662
|
+
}
|
|
663
|
+
getToolsForSession(session) {
|
|
664
|
+
if (session) {
|
|
665
|
+
return [...session.tools];
|
|
666
|
+
}
|
|
667
|
+
if (this.hasConnectedExtensionSession()) {
|
|
668
|
+
return [];
|
|
669
|
+
}
|
|
670
|
+
return [...this.devtoolsFallback.getTools()];
|
|
671
|
+
}
|
|
672
|
+
findExtensionTool(session, toolName) {
|
|
673
|
+
if (!session) {
|
|
674
|
+
return undefined;
|
|
675
|
+
}
|
|
676
|
+
const key = normalizeToolName(toolName);
|
|
677
|
+
return session.tools.find((tool) => normalizeToolName(tool.name) === key);
|
|
678
|
+
}
|
|
679
|
+
findFallbackTool(toolName) {
|
|
680
|
+
const key = normalizeToolName(toolName);
|
|
681
|
+
return this.devtoolsFallback.getTools().find((tool) => normalizeToolName(tool.name) === key);
|
|
682
|
+
}
|
|
683
|
+
broadcastDefaultToolsToAgents(excludeAgentId) {
|
|
684
|
+
const defaultSession = this.getDefaultSession();
|
|
685
|
+
const tools = this.getToolsForSession(defaultSession);
|
|
686
|
+
this.broadcastToAgents({
|
|
687
|
+
type: 'tools_list',
|
|
688
|
+
data: tools,
|
|
689
|
+
sessionId: defaultSession?.sessionId,
|
|
690
|
+
}, excludeAgentId);
|
|
691
|
+
}
|
|
692
|
+
hasConnectedExtensionSession() {
|
|
693
|
+
return this.getDefaultSession() !== null;
|
|
694
|
+
}
|
|
510
695
|
sendSessionStateToAgent(ws) {
|
|
511
696
|
const sessions = this.buildSessionsList();
|
|
512
697
|
const defaultSession = this.getDefaultSession();
|