openclaw-cascade-plugin 1.0.5 → 1.0.8

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/dist/wsl.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Detect if OpenClaw is running inside Windows Subsystem for Linux (WSL)
3
+ */
4
+ export declare function isWsl(): boolean;
5
+ /**
6
+ * Automatically fetch the Windows host IP address when running inside WSL.
7
+ * This is crucial because `localhost` inside WSL refers to the Linux VM,
8
+ * not the Windows host where the C# Body or Windows Python is running.
9
+ */
10
+ export declare function getWslHostIp(): Promise<string | null>;
11
+ //# sourceMappingURL=wsl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wsl.d.ts","sourceRoot":"","sources":["../src/wsl.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,wBAAgB,KAAK,IAAI,OAAO,CAU/B;AAED;;;;GAIG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0B3D"}
package/dist/wsl.js ADDED
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.isWsl = isWsl;
37
+ exports.getWslHostIp = getWslHostIp;
38
+ const os = __importStar(require("os"));
39
+ const child_process_1 = require("child_process");
40
+ const util_1 = require("util");
41
+ const execAsync = (0, util_1.promisify)(child_process_1.exec);
42
+ /**
43
+ * Detect if OpenClaw is running inside Windows Subsystem for Linux (WSL)
44
+ */
45
+ function isWsl() {
46
+ if (process.platform !== 'linux') {
47
+ return false;
48
+ }
49
+ try {
50
+ const release = os.release().toLowerCase();
51
+ return release.includes('microsoft') || release.includes('wsl');
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ }
57
+ /**
58
+ * Automatically fetch the Windows host IP address when running inside WSL.
59
+ * This is crucial because `localhost` inside WSL refers to the Linux VM,
60
+ * not the Windows host where the C# Body or Windows Python is running.
61
+ */
62
+ async function getWslHostIp() {
63
+ if (!isWsl())
64
+ return null;
65
+ try {
66
+ // Strategy 1: Read /etc/resolv.conf which points to the WSL virtual switch gateway
67
+ const { stdout } = await execAsync("cat /etc/resolv.conf | grep nameserver | awk '{print $2}'");
68
+ const ip = stdout.trim();
69
+ if (ip && /^\d+\.\d+\.\d+\.\d+$/.test(ip)) {
70
+ return ip;
71
+ }
72
+ }
73
+ catch (e) {
74
+ // Ignore and try strategy 2
75
+ }
76
+ try {
77
+ // Strategy 2: Check the default IP route
78
+ const { stdout } = await execAsync("ip route show default | awk '{print $3}'");
79
+ const ip = stdout.trim();
80
+ if (ip && /^\d+\.\d+\.\d+\.\d+$/.test(ip)) {
81
+ return ip;
82
+ }
83
+ }
84
+ catch (e) {
85
+ console.debug('Cascade auto-discovery failed to resolve WSL host IP:', e);
86
+ }
87
+ return null;
88
+ }
89
+ //# sourceMappingURL=wsl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wsl.js","sourceRoot":"","sources":["../src/wsl.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA,sBAUC;AAOD,oCA0BC;AApDD,uCAAyB;AACzB,iDAAqC;AACrC,+BAAiC;AAEjC,MAAM,SAAS,GAAG,IAAA,gBAAS,EAAC,oBAAI,CAAC,CAAC;AAElC;;GAEG;AACH,SAAgB,KAAK;IACnB,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,WAAW,EAAE,CAAC;QAC3C,OAAO,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,YAAY;IAChC,IAAI,CAAC,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAE1B,IAAI,CAAC;QACH,mFAAmF;QACnF,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,2DAA2D,CAAC,CAAC;QAChG,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QACzB,IAAI,EAAE,IAAI,sBAAsB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YAC1C,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,4BAA4B;IAC9B,CAAC;IAED,IAAI,CAAC;QACH,yCAAyC;QACzC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,0CAA0C,CAAC,CAAC;QAC/E,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QACzB,IAAI,EAAE,IAAI,sBAAsB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YAC1C,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,uDAAuD,EAAE,CAAC,CAAC,CAAC;IAC5E,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-cascade-plugin",
3
- "version": "1.0.5",
3
+ "version": "1.0.8",
4
4
  "description": "Desktop automation plugin for OpenClaw powered by Cascade",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -17,9 +17,9 @@ describe('Config', () => {
17
17
  });
18
18
 
19
19
  describe('getDefaults', () => {
20
- test('should return default configuration', () => {
20
+ test('should return default configuration', async () => {
21
21
  // Act
22
- const defaults = getDefaults();
22
+ const defaults = await getDefaults();
23
23
 
24
24
  // Assert
25
25
  expect(defaults).toEqual({
@@ -34,7 +34,7 @@ describe('Config', () => {
34
34
  });
35
35
 
36
36
  describe('validateConfig', () => {
37
- test('should pass with valid config', () => {
37
+ test('should pass with valid config', async () => {
38
38
  // Arrange
39
39
  const config: CascadePluginConfig = {
40
40
  cascadeGrpcEndpoint: 'localhost:50051'
@@ -44,7 +44,7 @@ describe('Config', () => {
44
44
  expect(() => validateConfig(config)).not.toThrow();
45
45
  });
46
46
 
47
- test('should throw when cascadeGrpcEndpoint is missing', () => {
47
+ test('should throw when cascadeGrpcEndpoint is missing', async () => {
48
48
  // Arrange
49
49
  const config: CascadePluginConfig = {} as any;
50
50
 
@@ -52,7 +52,7 @@ describe('Config', () => {
52
52
  expect(() => validateConfig(config)).toThrow('cascadeGrpcEndpoint is required');
53
53
  });
54
54
 
55
- test('should throw when cascadeGrpcEndpoint is empty', () => {
55
+ test('should throw when cascadeGrpcEndpoint is empty', async () => {
56
56
  // Arrange
57
57
  const config: CascadePluginConfig = {
58
58
  cascadeGrpcEndpoint: ''
@@ -62,7 +62,7 @@ describe('Config', () => {
62
62
  expect(() => validateConfig(config)).toThrow('cascadeGrpcEndpoint is required');
63
63
  });
64
64
 
65
- test('should validate firestore credentials path if provided', () => {
65
+ test('should validate firestore credentials path if provided', async () => {
66
66
  // Arrange
67
67
  const config: CascadePluginConfig = {
68
68
  cascadeGrpcEndpoint: 'localhost:50051',
@@ -73,7 +73,7 @@ describe('Config', () => {
73
73
  expect(() => validateConfig(config)).not.toThrow();
74
74
  });
75
75
 
76
- test('should validate allowed agents', () => {
76
+ test('should validate allowed agents', async () => {
77
77
  // Arrange
78
78
  const config: CascadePluginConfig = {
79
79
  cascadeGrpcEndpoint: 'localhost:50051',
@@ -84,7 +84,7 @@ describe('Config', () => {
84
84
  expect(() => validateConfig(config)).not.toThrow();
85
85
  });
86
86
 
87
- test('should throw for invalid agent in allowedAgents', () => {
87
+ test('should throw for invalid agent in allowedAgents', async () => {
88
88
  // Arrange
89
89
  const config: any = {
90
90
  cascadeGrpcEndpoint: 'localhost:50051',
@@ -95,7 +95,7 @@ describe('Config', () => {
95
95
  expect(() => validateConfig(config)).toThrow('Invalid agent');
96
96
  });
97
97
 
98
- test('should validate screenshotMode', () => {
98
+ test('should validate screenshotMode', async () => {
99
99
  // Arrange
100
100
  const config: CascadePluginConfig = {
101
101
  cascadeGrpcEndpoint: 'localhost:50051',
@@ -106,7 +106,7 @@ describe('Config', () => {
106
106
  expect(() => validateConfig(config)).not.toThrow();
107
107
  });
108
108
 
109
- test('should throw for invalid screenshotMode', () => {
109
+ test('should throw for invalid screenshotMode', async () => {
110
110
  // Arrange
111
111
  const config: any = {
112
112
  cascadeGrpcEndpoint: 'localhost:50051',
@@ -119,9 +119,9 @@ describe('Config', () => {
119
119
  });
120
120
 
121
121
  describe('loadConfig', () => {
122
- test('should load config with defaults when input is empty', () => {
122
+ test('should load config with defaults when input is empty', async () => {
123
123
  // Act
124
- const config = loadConfig({});
124
+ const config = await loadConfig({});
125
125
 
126
126
  // Assert
127
127
  expect(config.cascadeGrpcEndpoint).toBe('localhost:50051');
@@ -129,7 +129,7 @@ describe('Config', () => {
129
129
  expect(config.actionTimeoutMs).toBe(8000);
130
130
  });
131
131
 
132
- test('should merge with provided values', () => {
132
+ test('should merge with provided values', async () => {
133
133
  // Arrange
134
134
  const input = {
135
135
  cascadeGrpcEndpoint: '192.168.1.100:50051',
@@ -138,7 +138,7 @@ describe('Config', () => {
138
138
  };
139
139
 
140
140
  // Act
141
- const config = loadConfig(input);
141
+ const config = await loadConfig(input);
142
142
 
143
143
  // Assert
144
144
  expect(config.cascadeGrpcEndpoint).toBe('192.168.1.100:50051');
@@ -146,7 +146,7 @@ describe('Config', () => {
146
146
  expect(config.actionTimeoutMs).toBe(15000);
147
147
  });
148
148
 
149
- test('should expand environment variables in paths', () => {
149
+ test('should expand environment variables in paths', async () => {
150
150
  // Arrange
151
151
  process.env.HOME = '/home/user';
152
152
  const input = {
@@ -155,13 +155,13 @@ describe('Config', () => {
155
155
  };
156
156
 
157
157
  // Act
158
- const config = loadConfig(input);
158
+ const config = await loadConfig(input);
159
159
 
160
160
  // Assert
161
161
  expect(config.firestoreCredentialsPath).toBe('/home/user/creds.json');
162
162
  });
163
163
 
164
- test('should handle Windows environment variables', () => {
164
+ test('should handle Windows environment variables', async () => {
165
165
  // Arrange
166
166
  process.env.USERPROFILE = 'C:\\Users\\TestUser';
167
167
  const input = {
@@ -170,20 +170,20 @@ describe('Config', () => {
170
170
  };
171
171
 
172
172
  // Act
173
- const config = loadConfig(input);
173
+ const config = await loadConfig(input);
174
174
 
175
175
  // Assert
176
176
  expect(config.cascadePythonPath).toBe('C:\\Users\\TestUser\\Python\\python.exe');
177
177
  });
178
178
 
179
- test('should validate loaded config', () => {
179
+ test('should validate loaded config', async () => {
180
180
  // Arrange
181
181
  const input = {
182
182
  cascadeGrpcEndpoint: ''
183
183
  };
184
184
 
185
185
  // Act & Assert
186
- expect(() => loadConfig(input as any)).toThrow('cascadeGrpcEndpoint is required');
186
+ await expect(loadConfig(input as any)).rejects.toThrow('cascadeGrpcEndpoint is required');
187
187
  });
188
188
  });
189
189
  });
package/src/config.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { CascadePluginConfig } from './types';
6
+ import { isWsl, getWslHostIp } from './wsl';
6
7
 
7
8
  const VALID_AGENTS = ['explorer', 'worker', 'orchestrator'];
8
9
  const VALID_SCREENSHOT_MODES = ['embed', 'disk', 'auto'];
@@ -10,9 +11,18 @@ const VALID_SCREENSHOT_MODES = ['embed', 'disk', 'auto'];
10
11
  /**
11
12
  * Get default configuration values
12
13
  */
13
- export function getDefaults(): Partial<CascadePluginConfig> {
14
+ export async function getDefaults(): Promise<Partial<CascadePluginConfig>> {
15
+ let defaultEndpoint = 'localhost:50051';
16
+
17
+ if (isWsl()) {
18
+ const wslIp = await getWslHostIp();
19
+ if (wslIp) {
20
+ defaultEndpoint = `${wslIp}:50051`;
21
+ }
22
+ }
23
+
14
24
  return {
15
- cascadeGrpcEndpoint: 'localhost:50051',
25
+ cascadeGrpcEndpoint: defaultEndpoint,
16
26
  headless: false,
17
27
  actionTimeoutMs: 8000,
18
28
  enableA2A: true,
@@ -89,7 +99,7 @@ export function expandEnvVars(value: string): string {
89
99
  /**
90
100
  * Load and validate configuration
91
101
  */
92
- export function loadConfig(input?: Partial<CascadePluginConfig>): CascadePluginConfig {
102
+ export async function loadConfig(input?: Partial<CascadePluginConfig>): Promise<CascadePluginConfig> {
93
103
  const actualInput = input || {};
94
104
 
95
105
  // Expand environment variables in string fields
@@ -104,8 +114,9 @@ export function loadConfig(input?: Partial<CascadePluginConfig>): CascadePluginC
104
114
  }
105
115
 
106
116
  // Merge with defaults
117
+ const defaults = await getDefaults();
107
118
  const config: CascadePluginConfig = {
108
- ...getDefaults(),
119
+ ...defaults,
109
120
  ...expanded
110
121
  } as CascadePluginConfig;
111
122
 
@@ -118,7 +129,7 @@ export function loadConfig(input?: Partial<CascadePluginConfig>): CascadePluginC
118
129
  /**
119
130
  * Load configuration from OpenClaw API
120
131
  */
121
- export function loadConfigFromOpenClaw(api: any): CascadePluginConfig {
132
+ export async function loadConfigFromOpenClaw(api: any): Promise<CascadePluginConfig> {
122
133
  const entries = api.config?.plugins?.entries || {};
123
134
  const pluginConfig = entries['openclaw-cascade-plugin']?.config || entries.cascade?.config || {};
124
135
 
package/src/index.ts CHANGED
@@ -44,7 +44,7 @@ export default async function register(api: OpenClawApi) {
44
44
  try {
45
45
  // Load and validate configuration
46
46
  const entries = api.config.plugins.entries || {};
47
- const config = loadConfig(
47
+ const config = await loadConfig(
48
48
  entries['openclaw-cascade-plugin']?.config ||
49
49
  entries.cascade?.config ||
50
50
  {}
@@ -71,7 +71,20 @@ export default async function register(api: OpenClawApi) {
71
71
  })
72
72
  };
73
73
 
74
- const modulePath = config.cascadePythonModulePath || process.env.CASCADE_PYTHON_MODULE_PATH;
74
+ let modulePath = config.cascadePythonModulePath || process.env.CASCADE_PYTHON_MODULE_PATH;
75
+
76
+ // Auto-detect Cascade python module path relative to plugin installation
77
+ if (!modulePath) {
78
+ const { join } = require('path');
79
+ const { existsSync } = require('fs');
80
+ // __dirname is openclaw-plugin/dist
81
+ // repo root python is openclaw-plugin/../python
82
+ const guessedPath = join(__dirname, '..', '..', 'python');
83
+ if (existsSync(join(guessedPath, 'mcp_server', '__init__.py'))) {
84
+ modulePath = guessedPath;
85
+ }
86
+ }
87
+
75
88
  if (modulePath) {
76
89
  const existingPath = process.env.PYTHONPATH || '';
77
90
  pythonEnv.PYTHONPATH = existingPath
@@ -39,7 +39,7 @@ export function registerA2ATools(registry: ToolRegistry, getA2aClient: () => Pro
39
39
  return errorResponse('app_name is required');
40
40
  }
41
41
 
42
- await await (await getA2aClient())?.sendToAgent('explorer', {
42
+ await (await getA2aClient())?.sendToAgent('explorer', {
43
43
  type: 'start_exploration',
44
44
  appName: args.app_name,
45
45
  instructions: args.instructions || `Learn how to use ${args.app_name}`,
@@ -93,7 +93,7 @@ export function registerA2ATools(registry: ToolRegistry, getA2aClient: () => Pro
93
93
  return errorResponse('task is required');
94
94
  }
95
95
 
96
- await await (await getA2aClient())?.sendToAgent('worker', {
96
+ await (await getA2aClient())?.sendToAgent('worker', {
97
97
  type: 'execute_task',
98
98
  task: args.task,
99
99
  skillId: args.skill_id,
@@ -140,7 +140,7 @@ export function registerA2ATools(registry: ToolRegistry, getA2aClient: () => Pro
140
140
  return errorResponse('goal is required');
141
141
  }
142
142
 
143
- await await (await getA2aClient())?.sendToAgent('orchestrator', {
143
+ await (await getA2aClient())?.sendToAgent('orchestrator', {
144
144
  type: 'coordinate',
145
145
  goal: args.goal,
146
146
  requireApproval: args.require_approval !== false
@@ -35,7 +35,7 @@ export function registerApiTools(registry: ToolRegistry, getClient: () => Promis
35
35
  return errorResponse('query is required');
36
36
  }
37
37
 
38
- const result = await await (await getClient()).callTool('web_search', {
38
+ const result = await (await getClient()).callTool('web_search', {
39
39
  query: args.query,
40
40
  top_k: args.top_k || 5
41
41
  });
@@ -91,7 +91,7 @@ export function registerApiTools(registry: ToolRegistry, getClient: () => Promis
91
91
  return errorResponse('url is required');
92
92
  }
93
93
 
94
- const result = await await (await getClient()).callTool('call_http_api', {
94
+ const result = await (await getClient()).callTool('call_http_api', {
95
95
  method: args.method,
96
96
  url: args.url,
97
97
  headers: args.headers,
@@ -58,7 +58,7 @@ export function registerDesktopTools(
58
58
  return errorResponse('platform_source is required');
59
59
  }
60
60
 
61
- const result = await await (await getClient()).callTool('click_element', args);
61
+ const result = await (await getClient()).callTool('click_element', args);
62
62
  return formatSuccess(result);
63
63
  } catch (error) {
64
64
  return errorResponse(
@@ -112,7 +112,7 @@ export function registerDesktopTools(
112
112
  return errorResponse('text is required');
113
113
  }
114
114
 
115
- const result = await await (await getClient()).callTool('type_text', args);
115
+ const result = await (await getClient()).callTool('type_text', args);
116
116
  return formatSuccess(result);
117
117
  } catch (error) {
118
118
  return errorResponse(
@@ -128,11 +128,11 @@ export function registerDesktopTools(
128
128
  description: 'Get the UI element structure of the current window or application',
129
129
  inputSchema: {
130
130
  type: 'object',
131
- properties: {}
131
+ properties: { _placeholder: { type: "boolean", description: "Ignore this field" } }
132
132
  },
133
133
  handler: async (): Promise<ToolResponse> => {
134
134
  try {
135
- const result = await await (await getClient()).callTool('get_semantic_tree', {});
135
+ const result = await (await getClient()).callTool('get_semantic_tree', {});
136
136
  return formatSuccess(result);
137
137
  } catch (error) {
138
138
  return errorResponse(
@@ -148,11 +148,11 @@ export function registerDesktopTools(
148
148
  description: 'Capture a screenshot of the current screen with element annotations',
149
149
  inputSchema: {
150
150
  type: 'object',
151
- properties: {}
151
+ properties: { _placeholder: { type: "boolean", description: "Ignore this field" } }
152
152
  },
153
153
  handler: async (): Promise<ToolResponse> => {
154
154
  try {
155
- const result = await await (await getClient()).callTool('get_screenshot', {});
155
+ const result = await (await getClient()).callTool('get_screenshot', {});
156
156
 
157
157
  if (!result.image) {
158
158
  return errorResponse('No image data received');
@@ -209,7 +209,7 @@ export function registerDesktopTools(
209
209
  return errorResponse('app_name is required');
210
210
  }
211
211
 
212
- const result = await await (await getClient()).callTool('start_app', args);
212
+ const result = await (await getClient()).callTool('start_app', args);
213
213
  return formatSuccess(result);
214
214
  } catch (error) {
215
215
  return errorResponse(
@@ -246,7 +246,7 @@ export function registerDesktopTools(
246
246
  },
247
247
  handler: async (args): Promise<ToolResponse> => {
248
248
  try {
249
- const result = await await (await getClient()).callTool('hover_element', args);
249
+ const result = await (await getClient()).callTool('hover_element', args);
250
250
  return formatSuccess(result);
251
251
  } catch (error) {
252
252
  return errorResponse(
@@ -282,7 +282,7 @@ export function registerDesktopTools(
282
282
  },
283
283
  handler: async (args): Promise<ToolResponse> => {
284
284
  try {
285
- const result = await await (await getClient()).callTool('focus_element', args);
285
+ const result = await (await getClient()).callTool('focus_element', args);
286
286
  return formatSuccess(result);
287
287
  } catch (error) {
288
288
  return errorResponse(
@@ -318,7 +318,7 @@ export function registerDesktopTools(
318
318
  },
319
319
  handler: async (args): Promise<ToolResponse> => {
320
320
  try {
321
- const result = await await (await getClient()).callTool('scroll_element', args);
321
+ const result = await (await getClient()).callTool('scroll_element', args);
322
322
  return formatSuccess(result);
323
323
  } catch (error) {
324
324
  return errorResponse(
@@ -354,7 +354,7 @@ export function registerDesktopTools(
354
354
  },
355
355
  handler: async (args): Promise<ToolResponse> => {
356
356
  try {
357
- const result = await await (await getClient()).callTool('wait_visible', args);
357
+ const result = await (await getClient()).callTool('wait_visible', args);
358
358
  return formatSuccess(result);
359
359
  } catch (error) {
360
360
  return errorResponse(
@@ -63,7 +63,7 @@ export function registerSandboxTools(registry: ToolRegistry, getClient: () => Pr
63
63
  return errorResponse('task is required');
64
64
  }
65
65
 
66
- const result = await await (await getClient()).callTool('execute_sandbox_skill', {
66
+ const result = await (await getClient()).callTool('execute_sandbox_skill', {
67
67
  skill_id: args.skill_id,
68
68
  task: args.task,
69
69
  inputs: args.inputs || {},
@@ -38,7 +38,7 @@ export function registerWebTools(registry: ToolRegistry, getClient: () => Promis
38
38
  return errorResponse('url is required');
39
39
  }
40
40
 
41
- const result = await await (await getClient()).callTool('pw_goto', {
41
+ const result = await (await getClient()).callTool('pw_goto', {
42
42
  url: args.url,
43
43
  wait_until: args.wait_until || 'networkidle'
44
44
  });
@@ -58,11 +58,11 @@ export function registerWebTools(registry: ToolRegistry, getClient: () => Promis
58
58
  description: 'Go back to the previous page in browser history',
59
59
  inputSchema: {
60
60
  type: 'object',
61
- properties: {}
61
+ properties: { _placeholder: { type: "boolean", description: "Ignore this field" } }
62
62
  },
63
63
  handler: async (): Promise<ToolResponse> => {
64
64
  try {
65
- const result = await await (await getClient()).callTool('pw_back', {});
65
+ const result = await (await getClient()).callTool('pw_back', {});
66
66
  return formatSuccess(result);
67
67
  } catch (error) {
68
68
  return errorResponse(
@@ -78,11 +78,11 @@ export function registerWebTools(registry: ToolRegistry, getClient: () => Promis
78
78
  description: 'Go forward to the next page in browser history',
79
79
  inputSchema: {
80
80
  type: 'object',
81
- properties: {}
81
+ properties: { _placeholder: { type: "boolean", description: "Ignore this field" } }
82
82
  },
83
83
  handler: async (): Promise<ToolResponse> => {
84
84
  try {
85
- const result = await await (await getClient()).callTool('pw_forward', {});
85
+ const result = await (await getClient()).callTool('pw_forward', {});
86
86
  return formatSuccess(result);
87
87
  } catch (error) {
88
88
  return errorResponse(
@@ -108,7 +108,7 @@ export function registerWebTools(registry: ToolRegistry, getClient: () => Promis
108
108
  },
109
109
  handler: async (args): Promise<ToolResponse> => {
110
110
  try {
111
- const result = await await (await getClient()).callTool('pw_reload', {
111
+ const result = await (await getClient()).callTool('pw_reload', {
112
112
  wait_until: args.wait_until || 'networkidle'
113
113
  });
114
114
  return formatSuccess(result);
@@ -145,7 +145,7 @@ export function registerWebTools(registry: ToolRegistry, getClient: () => Promis
145
145
  return errorResponse('url is required');
146
146
  }
147
147
 
148
- const result = await await (await getClient()).callTool('pw_wait_for_url', {
148
+ const result = await (await getClient()).callTool('pw_wait_for_url', {
149
149
  url: args.url,
150
150
  timeout_ms: args.timeout_ms || 10000
151
151
  });
@@ -181,7 +181,7 @@ export function registerWebTools(registry: ToolRegistry, getClient: () => Promis
181
181
  return errorResponse('selector is required');
182
182
  }
183
183
 
184
- const result = await await (await getClient()).callTool('pw_locator_count', {
184
+ const result = await (await getClient()).callTool('pw_locator_count', {
185
185
  selector: args.selector
186
186
  });
187
187
  return formatSuccess({ count: result.count });
@@ -218,7 +218,7 @@ export function registerWebTools(registry: ToolRegistry, getClient: () => Promis
218
218
  return errorResponse('selector is required');
219
219
  }
220
220
 
221
- const result = await await (await getClient()).callTool('pw_locator_text', {
221
+ const result = await (await getClient()).callTool('pw_locator_text', {
222
222
  selector: args.selector,
223
223
  timeout_ms: args.timeout_ms || 8000
224
224
  });
@@ -257,7 +257,7 @@ export function registerWebTools(registry: ToolRegistry, getClient: () => Promis
257
257
  return errorResponse('selector is required');
258
258
  }
259
259
 
260
- const result = await await (await getClient()).callTool('pw_click', {
260
+ const result = await (await getClient()).callTool('pw_click', {
261
261
  selector: args.selector,
262
262
  timeout_ms: args.timeout_ms || 8000
263
263
  });
@@ -302,7 +302,7 @@ export function registerWebTools(registry: ToolRegistry, getClient: () => Promis
302
302
  return errorResponse('text is required');
303
303
  }
304
304
 
305
- const result = await await (await getClient()).callTool('pw_fill', {
305
+ const result = await (await getClient()).callTool('pw_fill', {
306
306
  selector: args.selector,
307
307
  text: args.text,
308
308
  timeout_ms: args.timeout_ms || 8000
@@ -347,7 +347,7 @@ export function registerWebTools(registry: ToolRegistry, getClient: () => Promis
347
347
  return errorResponse('key is required');
348
348
  }
349
349
 
350
- const result = await await (await getClient()).callTool('pw_press', {
350
+ const result = await (await getClient()).callTool('pw_press', {
351
351
  selector: args.selector,
352
352
  key: args.key,
353
353
  timeout_ms: args.timeout_ms || 8000
@@ -389,7 +389,7 @@ export function registerWebTools(registry: ToolRegistry, getClient: () => Promis
389
389
  return errorResponse('values is required and must be an array');
390
390
  }
391
391
 
392
- const result = await await (await getClient()).callTool('pw_select_option', {
392
+ const result = await (await getClient()).callTool('pw_select_option', {
393
393
  selector: args.selector,
394
394
  values: args.values
395
395
  });
@@ -424,7 +424,7 @@ export function registerWebTools(registry: ToolRegistry, getClient: () => Promis
424
424
  return errorResponse('expression is required');
425
425
  }
426
426
 
427
- const result = await await (await getClient()).callTool('pw_eval', {
427
+ const result = await (await getClient()).callTool('pw_eval', {
428
428
  expression: args.expression
429
429
  });
430
430
  return formatSuccess({ result: result.result });
@@ -463,7 +463,7 @@ export function registerWebTools(registry: ToolRegistry, getClient: () => Promis
463
463
  return errorResponse('expression is required');
464
464
  }
465
465
 
466
- const result = await await (await getClient()).callTool('pw_eval_on_selector', {
466
+ const result = await (await getClient()).callTool('pw_eval_on_selector', {
467
467
  selector: args.selector,
468
468
  expression: args.expression
469
469
  });
@@ -482,11 +482,11 @@ export function registerWebTools(registry: ToolRegistry, getClient: () => Promis
482
482
  description: 'List all frames (iframes) on the current page',
483
483
  inputSchema: {
484
484
  type: 'object',
485
- properties: {}
485
+ properties: { _placeholder: { type: "boolean", description: "Ignore this field" } }
486
486
  },
487
487
  handler: async (): Promise<ToolResponse> => {
488
488
  try {
489
- const result = await await (await getClient()).callTool('pw_list_frames', {});
489
+ const result = await (await getClient()).callTool('pw_list_frames', {});
490
490
  return formatSuccess({ frames: result.frames });
491
491
  } catch (error) {
492
492
  return errorResponse(
@@ -502,11 +502,11 @@ export function registerWebTools(registry: ToolRegistry, getClient: () => Promis
502
502
  description: 'Get all cookies for the current context',
503
503
  inputSchema: {
504
504
  type: 'object',
505
- properties: {}
505
+ properties: { _placeholder: { type: "boolean", description: "Ignore this field" } }
506
506
  },
507
507
  handler: async (): Promise<ToolResponse> => {
508
508
  try {
509
- const result = await await (await getClient()).callTool('pw_get_cookies', {});
509
+ const result = await (await getClient()).callTool('pw_get_cookies', {});
510
510
  return formatSuccess({ cookies: result.cookies });
511
511
  } catch (error) {
512
512
  return errorResponse(