openclaw-cascade-plugin 1.0.12 → 1.0.14

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 (80) hide show
  1. package/dist/grpc-client.d.ts +17 -0
  2. package/dist/grpc-client.d.ts.map +1 -0
  3. package/dist/grpc-client.js +154 -0
  4. package/dist/grpc-client.js.map +1 -0
  5. package/dist/index.d.ts +2 -3
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +14 -54
  8. package/dist/index.js.map +1 -1
  9. package/dist/test-utils/mocks.d.ts +6 -4
  10. package/dist/test-utils/mocks.d.ts.map +1 -1
  11. package/dist/test-utils/mocks.js +24 -14
  12. package/dist/test-utils/mocks.js.map +1 -1
  13. package/dist/tools/desktop-automation.d.ts +2 -7
  14. package/dist/tools/desktop-automation.d.ts.map +1 -1
  15. package/dist/tools/desktop-automation.js +64 -123
  16. package/dist/tools/desktop-automation.js.map +1 -1
  17. package/dist/tools/index.d.ts +3 -10
  18. package/dist/tools/index.d.ts.map +1 -1
  19. package/dist/tools/index.js +3 -17
  20. package/dist/tools/index.js.map +1 -1
  21. package/openclaw.plugin.json +1 -1
  22. package/package.json +13 -2
  23. package/proto/cascade.proto +297 -0
  24. package/PHASE1_SUMMARY.md +0 -191
  25. package/PHASE3_SUMMARY.md +0 -195
  26. package/dist/cascade-client.d.ts +0 -53
  27. package/dist/cascade-client.d.ts.map +0 -1
  28. package/dist/cascade-client.js +0 -179
  29. package/dist/cascade-client.js.map +0 -1
  30. package/dist/python-manager.d.ts +0 -59
  31. package/dist/python-manager.d.ts.map +0 -1
  32. package/dist/python-manager.js +0 -190
  33. package/dist/python-manager.js.map +0 -1
  34. package/dist/tools/api-tools.d.ts +0 -9
  35. package/dist/tools/api-tools.d.ts.map +0 -1
  36. package/dist/tools/api-tools.js +0 -102
  37. package/dist/tools/api-tools.js.map +0 -1
  38. package/dist/tools/sandbox-tools.d.ts +0 -9
  39. package/dist/tools/sandbox-tools.d.ts.map +0 -1
  40. package/dist/tools/sandbox-tools.js +0 -79
  41. package/dist/tools/sandbox-tools.js.map +0 -1
  42. package/dist/tools/web-automation.d.ts +0 -9
  43. package/dist/tools/web-automation.d.ts.map +0 -1
  44. package/dist/tools/web-automation.js +0 -471
  45. package/dist/tools/web-automation.js.map +0 -1
  46. package/jest.setup.js +0 -19
  47. package/openclaw-cascade-plugin-1.0.0.tgz +0 -0
  48. package/openclaw-cascade-plugin-1.0.10.tgz +0 -0
  49. package/openclaw-cascade-plugin-1.0.11.tgz +0 -0
  50. package/openclaw-cascade-plugin-1.0.12.tgz +0 -0
  51. package/openclaw-cascade-plugin-1.0.4.tgz +0 -0
  52. package/openclaw-cascade-plugin-1.0.6.tgz +0 -0
  53. package/openclaw-cascade-plugin-1.0.7.tgz +0 -0
  54. package/openclaw-cascade-plugin-1.0.8.tgz +0 -0
  55. package/openclaw-cascade-plugin-1.0.9.tgz +0 -0
  56. package/scripts/postinstall.js +0 -84
  57. package/src/a2a-client.ts +0 -66
  58. package/src/cascade-client.test.ts +0 -400
  59. package/src/cascade-client.ts +0 -198
  60. package/src/config.test.ts +0 -189
  61. package/src/config.ts +0 -137
  62. package/src/index.ts +0 -202
  63. package/src/python-manager.test.ts +0 -187
  64. package/src/python-manager.ts +0 -230
  65. package/src/test-utils/helpers.ts +0 -107
  66. package/src/test-utils/index.ts +0 -2
  67. package/src/test-utils/mocks.ts +0 -101
  68. package/src/tools/a2a-tools.ts +0 -162
  69. package/src/tools/api-tools.ts +0 -110
  70. package/src/tools/desktop-automation.test.ts +0 -308
  71. package/src/tools/desktop-automation.ts +0 -366
  72. package/src/tools/index.ts +0 -13
  73. package/src/tools/response-helpers.ts +0 -78
  74. package/src/tools/sandbox-tools.ts +0 -83
  75. package/src/tools/tool-registry.ts +0 -51
  76. package/src/tools/web-automation.test.ts +0 -177
  77. package/src/tools/web-automation.ts +0 -518
  78. package/src/types/index.ts +0 -133
  79. package/src/wsl.ts +0 -53
  80. package/tsconfig.json +0 -27
@@ -1,518 +0,0 @@
1
- /**
2
- * Web Automation Tools (Playwright)
3
- *
4
- * 14 tools for web browser automation
5
- */
6
-
7
- import { ToolRegistry } from './tool-registry';
8
- import { CascadeMcpClient } from '../cascade-client';
9
- import { ToolResponse } from '../types';
10
- import { errorResponse, formatSuccess } from './response-helpers';
11
-
12
- export function registerWebTools(registry: ToolRegistry, getClient: () => Promise<CascadeMcpClient>): void {
13
- // Navigation Tools (5)
14
-
15
- // 1. cascade_pw_goto
16
- registry.register({
17
- name: 'cascade_pw_goto',
18
- description: 'Navigate to a URL in the browser',
19
- inputSchema: {
20
- type: 'object',
21
- properties: {
22
- url: {
23
- type: 'string',
24
- description: 'URL to navigate to'
25
- },
26
- wait_until: {
27
- type: 'string',
28
- enum: ['load', 'domcontentloaded', 'networkidle'],
29
- default: 'networkidle',
30
- description: 'When to consider navigation complete'
31
- }
32
- },
33
- required: ['url']
34
- },
35
- handler: async (args): Promise<ToolResponse> => {
36
- try {
37
- if (!args.url) {
38
- return errorResponse('url is required');
39
- }
40
-
41
- const result = await (await getClient()).callTool('pw_goto', {
42
- url: args.url,
43
- wait_until: args.wait_until || 'networkidle'
44
- });
45
- return formatSuccess(result);
46
- } catch (error) {
47
- return errorResponse(
48
- error instanceof Error ? error.message : 'Failed to navigate',
49
- 'Check that the URL is valid and accessible'
50
- );
51
- }
52
- }
53
- });
54
-
55
- // 2. cascade_pw_back
56
- registry.register({
57
- name: 'cascade_pw_back',
58
- description: 'Go back to the previous page in browser history',
59
- inputSchema: {
60
- type: 'object',
61
- properties: { _placeholder: { type: "boolean", description: "Ignore this field" } }
62
- },
63
- handler: async (): Promise<ToolResponse> => {
64
- try {
65
- const result = await (await getClient()).callTool('pw_back', {});
66
- return formatSuccess(result);
67
- } catch (error) {
68
- return errorResponse(
69
- error instanceof Error ? error.message : 'Failed to go back'
70
- );
71
- }
72
- }
73
- });
74
-
75
- // 3. cascade_pw_forward
76
- registry.register({
77
- name: 'cascade_pw_forward',
78
- description: 'Go forward to the next page in browser history',
79
- inputSchema: {
80
- type: 'object',
81
- properties: { _placeholder: { type: "boolean", description: "Ignore this field" } }
82
- },
83
- handler: async (): Promise<ToolResponse> => {
84
- try {
85
- const result = await (await getClient()).callTool('pw_forward', {});
86
- return formatSuccess(result);
87
- } catch (error) {
88
- return errorResponse(
89
- error instanceof Error ? error.message : 'Failed to go forward'
90
- );
91
- }
92
- }
93
- });
94
-
95
- // 4. cascade_pw_reload
96
- registry.register({
97
- name: 'cascade_pw_reload',
98
- description: 'Reload the current page',
99
- inputSchema: {
100
- type: 'object',
101
- properties: {
102
- wait_until: {
103
- type: 'string',
104
- enum: ['load', 'domcontentloaded', 'networkidle'],
105
- default: 'networkidle'
106
- }
107
- }
108
- },
109
- handler: async (args): Promise<ToolResponse> => {
110
- try {
111
- const result = await (await getClient()).callTool('pw_reload', {
112
- wait_until: args.wait_until || 'networkidle'
113
- });
114
- return formatSuccess(result);
115
- } catch (error) {
116
- return errorResponse(
117
- error instanceof Error ? error.message : 'Failed to reload page'
118
- );
119
- }
120
- }
121
- });
122
-
123
- // 5. cascade_pw_wait_for_url
124
- registry.register({
125
- name: 'cascade_pw_wait_for_url',
126
- description: 'Wait for the current URL to match a pattern',
127
- inputSchema: {
128
- type: 'object',
129
- properties: {
130
- url: {
131
- type: 'string',
132
- description: 'URL pattern to wait for (glob or regex)'
133
- },
134
- timeout_ms: {
135
- type: 'integer',
136
- default: 10000,
137
- description: 'Timeout in milliseconds'
138
- }
139
- },
140
- required: ['url']
141
- },
142
- handler: async (args): Promise<ToolResponse> => {
143
- try {
144
- if (!args.url) {
145
- return errorResponse('url is required');
146
- }
147
-
148
- const result = await (await getClient()).callTool('pw_wait_for_url', {
149
- url: args.url,
150
- timeout_ms: args.timeout_ms || 10000
151
- });
152
- return formatSuccess(result);
153
- } catch (error) {
154
- return errorResponse(
155
- error instanceof Error ? error.message : 'Failed to wait for URL',
156
- 'The page may be loading slowly or the URL pattern may not match'
157
- );
158
- }
159
- }
160
- });
161
-
162
- // Locator Tools (2)
163
-
164
- // 6. cascade_pw_locator_count
165
- registry.register({
166
- name: 'cascade_pw_locator_count',
167
- description: 'Count elements matching a selector',
168
- inputSchema: {
169
- type: 'object',
170
- properties: {
171
- selector: {
172
- type: 'string',
173
- description: 'CSS selector or Playwright locator'
174
- }
175
- },
176
- required: ['selector']
177
- },
178
- handler: async (args): Promise<ToolResponse> => {
179
- try {
180
- if (!args.selector) {
181
- return errorResponse('selector is required');
182
- }
183
-
184
- const result = await (await getClient()).callTool('pw_locator_count', {
185
- selector: args.selector
186
- });
187
- return formatSuccess({ count: result.count });
188
- } catch (error) {
189
- return errorResponse(
190
- error instanceof Error ? error.message : 'Failed to count elements'
191
- );
192
- }
193
- }
194
- });
195
-
196
- // 7. cascade_pw_locator_text
197
- registry.register({
198
- name: 'cascade_pw_locator_text',
199
- description: 'Get text content of the first matching element',
200
- inputSchema: {
201
- type: 'object',
202
- properties: {
203
- selector: {
204
- type: 'string',
205
- description: 'CSS selector or Playwright locator'
206
- },
207
- timeout_ms: {
208
- type: 'integer',
209
- default: 8000,
210
- description: 'Timeout in milliseconds'
211
- }
212
- },
213
- required: ['selector']
214
- },
215
- handler: async (args): Promise<ToolResponse> => {
216
- try {
217
- if (!args.selector) {
218
- return errorResponse('selector is required');
219
- }
220
-
221
- const result = await (await getClient()).callTool('pw_locator_text', {
222
- selector: args.selector,
223
- timeout_ms: args.timeout_ms || 8000
224
- });
225
- return formatSuccess({ text: result.text });
226
- } catch (error) {
227
- return errorResponse(
228
- error instanceof Error ? error.message : 'Failed to get element text'
229
- );
230
- }
231
- }
232
- });
233
-
234
- // Interaction Tools (5)
235
-
236
- // 8. cascade_pw_click
237
- registry.register({
238
- name: 'cascade_pw_click',
239
- description: 'Click on an element',
240
- inputSchema: {
241
- type: 'object',
242
- properties: {
243
- selector: {
244
- type: 'string',
245
- description: 'CSS selector or Playwright locator'
246
- },
247
- timeout_ms: {
248
- type: 'integer',
249
- default: 8000
250
- }
251
- },
252
- required: ['selector']
253
- },
254
- handler: async (args): Promise<ToolResponse> => {
255
- try {
256
- if (!args.selector) {
257
- return errorResponse('selector is required');
258
- }
259
-
260
- const result = await (await getClient()).callTool('pw_click', {
261
- selector: args.selector,
262
- timeout_ms: args.timeout_ms || 8000
263
- });
264
- return formatSuccess(result);
265
- } catch (error) {
266
- return errorResponse(
267
- error instanceof Error ? error.message : 'Failed to click element',
268
- 'Make sure the element exists and is visible'
269
- );
270
- }
271
- }
272
- });
273
-
274
- // 9. cascade_pw_fill
275
- registry.register({
276
- name: 'cascade_pw_fill',
277
- description: 'Fill a form field with text',
278
- inputSchema: {
279
- type: 'object',
280
- properties: {
281
- selector: {
282
- type: 'string',
283
- description: 'CSS selector or Playwright locator'
284
- },
285
- text: {
286
- type: 'string',
287
- description: 'Text to fill'
288
- },
289
- timeout_ms: {
290
- type: 'integer',
291
- default: 8000
292
- }
293
- },
294
- required: ['selector', 'text']
295
- },
296
- handler: async (args): Promise<ToolResponse> => {
297
- try {
298
- if (!args.selector) {
299
- return errorResponse('selector is required');
300
- }
301
- if (!args.text) {
302
- return errorResponse('text is required');
303
- }
304
-
305
- const result = await (await getClient()).callTool('pw_fill', {
306
- selector: args.selector,
307
- text: args.text,
308
- timeout_ms: args.timeout_ms || 8000
309
- });
310
- return formatSuccess(result);
311
- } catch (error) {
312
- return errorResponse(
313
- error instanceof Error ? error.message : 'Failed to fill field'
314
- );
315
- }
316
- }
317
- });
318
-
319
- // 10. cascade_pw_press
320
- registry.register({
321
- name: 'cascade_pw_press',
322
- description: 'Press a key on an element',
323
- inputSchema: {
324
- type: 'object',
325
- properties: {
326
- selector: {
327
- type: 'string',
328
- description: 'CSS selector or Playwright locator'
329
- },
330
- key: {
331
- type: 'string',
332
- description: 'Key to press (e.g., Enter, Tab, Escape)'
333
- },
334
- timeout_ms: {
335
- type: 'integer',
336
- default: 8000
337
- }
338
- },
339
- required: ['selector', 'key']
340
- },
341
- handler: async (args): Promise<ToolResponse> => {
342
- try {
343
- if (!args.selector) {
344
- return errorResponse('selector is required');
345
- }
346
- if (!args.key) {
347
- return errorResponse('key is required');
348
- }
349
-
350
- const result = await (await getClient()).callTool('pw_press', {
351
- selector: args.selector,
352
- key: args.key,
353
- timeout_ms: args.timeout_ms || 8000
354
- });
355
- return formatSuccess(result);
356
- } catch (error) {
357
- return errorResponse(
358
- error instanceof Error ? error.message : 'Failed to press key'
359
- );
360
- }
361
- }
362
- });
363
-
364
- // 11. cascade_pw_select_option
365
- registry.register({
366
- name: 'cascade_pw_select_option',
367
- description: 'Select option(s) in a dropdown',
368
- inputSchema: {
369
- type: 'object',
370
- properties: {
371
- selector: {
372
- type: 'string',
373
- description: 'CSS selector for select element'
374
- },
375
- values: {
376
- type: 'array',
377
- items: { type: 'string' },
378
- description: 'Values to select'
379
- }
380
- },
381
- required: ['selector', 'values']
382
- },
383
- handler: async (args): Promise<ToolResponse> => {
384
- try {
385
- if (!args.selector) {
386
- return errorResponse('selector is required');
387
- }
388
- if (!args.values || !Array.isArray(args.values)) {
389
- return errorResponse('values is required and must be an array');
390
- }
391
-
392
- const result = await (await getClient()).callTool('pw_select_option', {
393
- selector: args.selector,
394
- values: args.values
395
- });
396
- return formatSuccess({ selected: result.selected });
397
- } catch (error) {
398
- return errorResponse(
399
- error instanceof Error ? error.message : 'Failed to select option'
400
- );
401
- }
402
- }
403
- });
404
-
405
- // Evaluation Tools (3)
406
-
407
- // 12. cascade_pw_eval
408
- registry.register({
409
- name: 'cascade_pw_eval',
410
- description: 'Evaluate JavaScript in the page context',
411
- inputSchema: {
412
- type: 'object',
413
- properties: {
414
- expression: {
415
- type: 'string',
416
- description: 'JavaScript expression to evaluate'
417
- }
418
- },
419
- required: ['expression']
420
- },
421
- handler: async (args): Promise<ToolResponse> => {
422
- try {
423
- if (!args.expression) {
424
- return errorResponse('expression is required');
425
- }
426
-
427
- const result = await (await getClient()).callTool('pw_eval', {
428
- expression: args.expression
429
- });
430
- return formatSuccess({ result: result.result });
431
- } catch (error) {
432
- return errorResponse(
433
- error instanceof Error ? error.message : 'Failed to evaluate expression'
434
- );
435
- }
436
- }
437
- });
438
-
439
- // 13. cascade_pw_eval_on_selector
440
- registry.register({
441
- name: 'cascade_pw_eval_on_selector',
442
- description: 'Evaluate JavaScript on a specific element',
443
- inputSchema: {
444
- type: 'object',
445
- properties: {
446
- selector: {
447
- type: 'string',
448
- description: 'CSS selector for target element'
449
- },
450
- expression: {
451
- type: 'string',
452
- description: 'JavaScript function body (receives element as argument)'
453
- }
454
- },
455
- required: ['selector', 'expression']
456
- },
457
- handler: async (args): Promise<ToolResponse> => {
458
- try {
459
- if (!args.selector) {
460
- return errorResponse('selector is required');
461
- }
462
- if (!args.expression) {
463
- return errorResponse('expression is required');
464
- }
465
-
466
- const result = await (await getClient()).callTool('pw_eval_on_selector', {
467
- selector: args.selector,
468
- expression: args.expression
469
- });
470
- return formatSuccess({ result: result.result });
471
- } catch (error) {
472
- return errorResponse(
473
- error instanceof Error ? error.message : 'Failed to evaluate on element'
474
- );
475
- }
476
- }
477
- });
478
-
479
- // 14. cascade_pw_list_frames
480
- registry.register({
481
- name: 'cascade_pw_list_frames',
482
- description: 'List all frames (iframes) on the current page',
483
- inputSchema: {
484
- type: 'object',
485
- properties: { _placeholder: { type: "boolean", description: "Ignore this field" } }
486
- },
487
- handler: async (): Promise<ToolResponse> => {
488
- try {
489
- const result = await (await getClient()).callTool('pw_list_frames', {});
490
- return formatSuccess({ frames: result.frames });
491
- } catch (error) {
492
- return errorResponse(
493
- error instanceof Error ? error.message : 'Failed to list frames'
494
- );
495
- }
496
- }
497
- });
498
-
499
- // 15. cascade_pw_get_cookies
500
- registry.register({
501
- name: 'cascade_pw_get_cookies',
502
- description: 'Get all cookies for the current context',
503
- inputSchema: {
504
- type: 'object',
505
- properties: { _placeholder: { type: "boolean", description: "Ignore this field" } }
506
- },
507
- handler: async (): Promise<ToolResponse> => {
508
- try {
509
- const result = await (await getClient()).callTool('pw_get_cookies', {});
510
- return formatSuccess({ cookies: result.cookies });
511
- } catch (error) {
512
- return errorResponse(
513
- error instanceof Error ? error.message : 'Failed to get cookies'
514
- );
515
- }
516
- }
517
- });
518
- }
@@ -1,133 +0,0 @@
1
- /**
2
- * Type definitions for OpenClaw Plugin
3
- */
4
-
5
- // Configuration Types
6
- export interface CascadePluginConfig {
7
- cascadeGrpcEndpoint: string;
8
- cascadePythonPath?: string;
9
- cascadePythonModulePath?: string;
10
- firestoreProjectId?: string;
11
- firestoreCredentialsPath?: string;
12
- headless?: boolean;
13
- actionTimeoutMs?: number;
14
- enableA2A?: boolean;
15
- allowedAgents?: AgentRole[];
16
- requireAgentConfirmation?: boolean;
17
- verbose?: boolean;
18
- screenshotMode?: 'embed' | 'disk' | 'auto';
19
- screenshotDir?: string;
20
- }
21
-
22
- export type AgentRole = 'explorer' | 'worker' | 'orchestrator';
23
-
24
- // Tool Types
25
- export interface ToolSchema {
26
- name: string;
27
- description: string;
28
- inputSchema: Record<string, any>;
29
- }
30
-
31
- export interface ToolRegistry {
32
- register(tool: ToolSchema & { handler: ToolHandler }): void;
33
- getAll(): Array<ToolSchema & { handler: ToolHandler }>;
34
- get(name: string): (ToolSchema & { handler: ToolHandler }) | undefined;
35
- }
36
-
37
- export type ToolHandler = (args: Record<string, any>) => Promise<ToolResponse>;
38
-
39
- export interface ToolResponse {
40
- content: Array<{ type: string; [key: string]: any }>;
41
- isError?: boolean;
42
- }
43
-
44
- // MCP Types
45
- export interface McpRequest {
46
- jsonrpc: '2.0';
47
- id: number;
48
- method: string;
49
- params?: Record<string, any>;
50
- }
51
-
52
- export interface McpResponse {
53
- jsonrpc: '2.0';
54
- id: number;
55
- result?: any;
56
- error?: {
57
- code: number;
58
- message: string;
59
- };
60
- }
61
-
62
- // A2A Types
63
- export interface A2AMessage {
64
- type: string;
65
- source: 'openclaw' | AgentRole;
66
- timestamp: number;
67
- payload: any;
68
- runId?: string;
69
- }
70
-
71
- export interface AgentMessage {
72
- messageId: string;
73
- userId: string;
74
- appId: string;
75
- senderAgentId: string;
76
- senderRole: string;
77
- targetAgentId?: string;
78
- targetRole?: string;
79
- runId?: string;
80
- headers: Record<string, string>;
81
- jsonPayload: string;
82
- createdAtMs: number;
83
- }
84
-
85
- // OpenClaw API Types
86
- export interface OpenClawApi {
87
- config: {
88
- plugins: {
89
- entries: {
90
- cascade?: {
91
- config?: CascadePluginConfig;
92
- };
93
- };
94
- };
95
- };
96
- registerTool(tool: ToolSchema & { handler: ToolHandler }): void;
97
- registerGatewayMethod(name: string, handler: (context: any) => void): void;
98
- registerCli(handler: (context: { program: any }) => void): void;
99
- notify(message: string): void;
100
- }
101
-
102
- // Error Types
103
- export class CascadeError extends Error {
104
- constructor(
105
- message: string,
106
- public code: string,
107
- public suggestion?: string
108
- ) {
109
- super(message);
110
- this.name = 'CascadeError';
111
- }
112
- }
113
-
114
- export class A2ADisabledError extends CascadeError {
115
- constructor(message: string) {
116
- super(message, 'A2A_DISABLED');
117
- this.name = 'A2ADisabledError';
118
- }
119
- }
120
-
121
- export class AgentNotAllowedError extends CascadeError {
122
- constructor(message: string) {
123
- super(message, 'AGENT_NOT_ALLOWED');
124
- this.name = 'AgentNotAllowedError';
125
- }
126
- }
127
-
128
- export class UserCancelledError extends CascadeError {
129
- constructor(message: string) {
130
- super(message, 'USER_CANCELLED');
131
- this.name = 'UserCancelledError';
132
- }
133
- }
package/src/wsl.ts DELETED
@@ -1,53 +0,0 @@
1
- import * as os from 'os';
2
- import { exec } from 'child_process';
3
- import { promisify } from 'util';
4
-
5
- const execAsync = promisify(exec);
6
-
7
- /**
8
- * Detect if OpenClaw is running inside Windows Subsystem for Linux (WSL)
9
- */
10
- export function isWsl(): boolean {
11
- if (process.platform !== 'linux') {
12
- return false;
13
- }
14
- try {
15
- const release = os.release().toLowerCase();
16
- return release.includes('microsoft') || release.includes('wsl');
17
- } catch {
18
- return false;
19
- }
20
- }
21
-
22
- /**
23
- * Automatically fetch the Windows host IP address when running inside WSL.
24
- * This is crucial because `localhost` inside WSL refers to the Linux VM,
25
- * not the Windows host where the C# Body or Windows Python is running.
26
- */
27
- export async function getWslHostIp(): Promise<string | null> {
28
- if (!isWsl()) return null;
29
-
30
- try {
31
- // Strategy 1: Read /etc/resolv.conf which points to the WSL virtual switch gateway
32
- const { stdout } = await execAsync("cat /etc/resolv.conf | grep nameserver | awk '{print $2}'");
33
- const ip = stdout.trim();
34
- if (ip && /^\d+\.\d+\.\d+\.\d+$/.test(ip)) {
35
- return ip;
36
- }
37
- } catch (e) {
38
- // Ignore and try strategy 2
39
- }
40
-
41
- try {
42
- // Strategy 2: Check the default IP route
43
- const { stdout } = await execAsync("ip route show default | awk '{print $3}'");
44
- const ip = stdout.trim();
45
- if (ip && /^\d+\.\d+\.\d+\.\d+$/.test(ip)) {
46
- return ip;
47
- }
48
- } catch (e) {
49
- console.debug('Cascade auto-discovery failed to resolve WSL host IP:', e);
50
- }
51
-
52
- return null;
53
- }