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,84 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const os = require('os');
4
-
5
- const PLUGIN_ID = 'openclaw-cascade-plugin';
6
- const DEFAULT_ENDPOINT = 'localhost:50051';
7
- const defaultConfigPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
8
- const legacyConfigPath = path.join(os.homedir(), '.openclaw', 'config.json');
9
- const configPath = process.env.OPENCLAW_CONFIG_PATH ||
10
- (fs.existsSync(defaultConfigPath) ? defaultConfigPath : legacyConfigPath);
11
-
12
- function readConfig(filePath) {
13
- if (!fs.existsSync(filePath)) {
14
- return { plugins: { entries: {} } };
15
- }
16
-
17
- const content = fs.readFileSync(filePath, 'utf8');
18
- try {
19
- return JSON.parse(content);
20
- } catch (error) {
21
- console.warn('[cascade] Could not parse OpenClaw config; skipping auto-update:', error.message);
22
- return null;
23
- }
24
- }
25
-
26
- function writeConfig(filePath, config) {
27
- const dir = path.dirname(filePath);
28
- if (!fs.existsSync(dir)) {
29
- fs.mkdirSync(dir, { recursive: true });
30
- }
31
-
32
- fs.writeFileSync(filePath, JSON.stringify(config, null, 2));
33
- }
34
-
35
- function ensurePluginEntry(config) {
36
- config.plugins = config.plugins || {};
37
- config.plugins.entries = config.plugins.entries || {};
38
-
39
- // Migrate legacy entry if present
40
- if (config.plugins.entries.cascade && !config.plugins.entries[PLUGIN_ID]) {
41
- config.plugins.entries[PLUGIN_ID] = config.plugins.entries.cascade;
42
- delete config.plugins.entries.cascade;
43
- }
44
-
45
- const entry = config.plugins.entries[PLUGIN_ID] || {};
46
- const existingConfig = entry.config || {};
47
-
48
- const cascadeRepoPath = process.env.CASCADE_REPO_PATH;
49
- const cascadePythonModulePath = process.env.CASCADE_PYTHON_MODULE_PATH ||
50
- (cascadeRepoPath ? path.join(cascadeRepoPath, 'python') : undefined);
51
-
52
- config.plugins.entries[PLUGIN_ID] = {
53
- enabled: entry.enabled !== false,
54
- config: {
55
- cascadeGrpcEndpoint: existingConfig.cascadeGrpcEndpoint || DEFAULT_ENDPOINT,
56
- cascadePythonModulePath: existingConfig.cascadePythonModulePath || cascadePythonModulePath,
57
- cascadePythonPath: existingConfig.cascadePythonPath,
58
- firestoreProjectId: existingConfig.firestoreProjectId,
59
- firestoreCredentialsPath: existingConfig.firestoreCredentialsPath,
60
- headless: existingConfig.headless,
61
- actionTimeoutMs: existingConfig.actionTimeoutMs,
62
- enableA2A: existingConfig.enableA2A,
63
- allowedAgents: existingConfig.allowedAgents,
64
- requireAgentConfirmation: existingConfig.requireAgentConfirmation,
65
- verbose: existingConfig.verbose,
66
- screenshotMode: existingConfig.screenshotMode,
67
- screenshotDir: existingConfig.screenshotDir
68
- }
69
- };
70
- }
71
-
72
- const config = readConfig(configPath);
73
- if (!config) {
74
- process.exit(0);
75
- }
76
-
77
- ensurePluginEntry(config);
78
- writeConfig(configPath, config);
79
-
80
- console.log(`[cascade] OpenClaw config updated: ${configPath}`);
81
- console.log('[cascade] Plugin entry ensured under plugins.entries.openclaw-cascade-plugin');
82
- if (!process.env.CASCADE_REPO_PATH && !process.env.CASCADE_PYTHON_MODULE_PATH) {
83
- console.log('[cascade] Tip: set CASCADE_REPO_PATH to your repo to auto-configure PYTHONPATH.');
84
- }
package/src/a2a-client.ts DELETED
@@ -1,66 +0,0 @@
1
- /**
2
- * A2A Client - Agent-to-Agent communication
3
- */
4
-
5
- import { A2AMessage } from './types';
6
-
7
- export class CascadeA2AClient {
8
- private agentId: string | null = null;
9
- private messageHandlers = new Map<string, (payload: any) => Promise<void>>();
10
- private grpcEndpoint: string;
11
- private userId: string;
12
- private appId: string;
13
- private authToken: string;
14
-
15
- constructor(
16
- grpcEndpoint: string,
17
- userId: string,
18
- appId: string,
19
- authToken: string
20
- ) {
21
- this.grpcEndpoint = grpcEndpoint;
22
- this.userId = userId;
23
- this.appId = appId;
24
- this.authToken = authToken;
25
- }
26
-
27
- async initialize(): Promise<void> {
28
- // Initialize gRPC connection and register agent
29
- // This is a placeholder - actual implementation would use gRPC
30
- this.agentId = `openclaw-${this.userId}-${Date.now()}`;
31
- console.debug(`A2A Client connecting to ${this.grpcEndpoint} for app ${this.appId}`);
32
- }
33
-
34
- async sendToAgent(
35
- targetRole: 'explorer' | 'worker' | 'orchestrator',
36
- payload: any,
37
- targetAgentId?: string
38
- ): Promise<void> {
39
- if (!this.agentId) {
40
- throw new Error('A2A client not initialized');
41
- }
42
-
43
- const message: A2AMessage = {
44
- type: payload.type,
45
- source: 'openclaw',
46
- timestamp: Date.now(),
47
- payload,
48
- runId: payload.runId
49
- };
50
-
51
- // Send via gRPC (placeholder)
52
- const target = targetAgentId || targetRole;
53
- console.log(`Sending message to ${target}:`, message);
54
-
55
- // In real implementation, would use this.grpcEndpoint and this.authToken
56
- console.debug(`Using endpoint: ${this.grpcEndpoint}, token: ${this.authToken ? '***' : 'none'}`);
57
- }
58
-
59
- onMessage(type: string, handler: (payload: any) => Promise<void>): void {
60
- this.messageHandlers.set(type, handler);
61
- }
62
-
63
- isConnected(): boolean {
64
- return this.agentId !== null;
65
- }
66
- }
@@ -1,400 +0,0 @@
1
- /**
2
- * Tests for CascadeMcpClient
3
- *
4
- * MCP (Model Context Protocol) client for communicating with Cascade
5
- */
6
-
7
- import { CascadeMcpClient } from './cascade-client';
8
- import { createMockConfig } from './test-utils';
9
- import { spawn } from 'child_process';
10
- import { EventEmitter } from 'events';
11
-
12
- // Mock child_process
13
- jest.mock('child_process');
14
-
15
- describe('CascadeMcpClient', () => {
16
- let client: CascadeMcpClient;
17
- let mockProcess: any;
18
- let mockSpawn: jest.MockedFunction<typeof spawn>;
19
-
20
- beforeEach(() => {
21
- jest.clearAllMocks();
22
-
23
- // Create mock process
24
- const stdout = new EventEmitter();
25
- const stderr = new EventEmitter();
26
- const stdin = { write: jest.fn() };
27
-
28
- mockProcess = {
29
- stdout,
30
- stderr,
31
- stdin,
32
- kill: jest.fn(),
33
- on: jest.fn(),
34
- pid: 12345,
35
- killed: false
36
- };
37
-
38
- mockSpawn = spawn as jest.MockedFunction<typeof spawn>;
39
- mockSpawn.mockReturnValue(mockProcess as any);
40
-
41
- const config = createMockConfig({
42
- cascadePythonPath: '/usr/bin/python3',
43
- cascadeGrpcEndpoint: 'localhost:50051'
44
- });
45
-
46
- client = new CascadeMcpClient(
47
- config.cascadePythonPath!,
48
- {
49
- CASCADE_GRPC_ENDPOINT: config.cascadeGrpcEndpoint,
50
- CASCADE_APP_ID: 'test-app',
51
- CASCADE_USER_ID: 'test-user'
52
- }
53
- );
54
- });
55
-
56
- describe('start', () => {
57
- test('should spawn Python MCP server', async () => {
58
- // Arrange
59
- const startPromise = client.start();
60
-
61
- // Emit initialize response
62
- setTimeout(() => {
63
- mockProcess.stdout.emit('data', JSON.stringify({
64
- jsonrpc: '2.0',
65
- id: 1,
66
- result: {
67
- protocolVersion: '2024-11-05',
68
- capabilities: {},
69
- serverInfo: { name: 'cascade-mcp', version: '1.0.0' }
70
- }
71
- }) + '\n');
72
- }, 10);
73
-
74
- // Act
75
- await startPromise;
76
-
77
- // Assert
78
- expect(mockSpawn).toHaveBeenCalledWith(
79
- '/usr/bin/python3',
80
- ['-m', 'mcp_server.cli'],
81
- expect.objectContaining({
82
- env: expect.objectContaining({
83
- CASCADE_GRPC_ENDPOINT: 'localhost:50051',
84
- CASCADE_APP_ID: 'test-app',
85
- CASCADE_USER_ID: 'test-user'
86
- }),
87
- stdio: ['pipe', 'pipe', 'pipe']
88
- })
89
- );
90
- });
91
-
92
- test('should send initialize request', async () => {
93
- // Arrange
94
- const startPromise = client.start();
95
-
96
- // Emit initialize response
97
- setTimeout(() => {
98
- mockProcess.stdout.emit('data', JSON.stringify({
99
- jsonrpc: '2.0',
100
- id: 1,
101
- result: {
102
- protocolVersion: '2024-11-05',
103
- capabilities: {},
104
- serverInfo: { name: 'cascade-mcp', version: '1.0.0' }
105
- }
106
- }) + '\n');
107
- }, 10);
108
-
109
- // Act
110
- await startPromise;
111
-
112
- // Assert
113
- const writeCalls = mockProcess.stdin.write.mock.calls;
114
- const initRequest = JSON.parse(writeCalls[0][0]);
115
-
116
- expect(initRequest).toMatchObject({
117
- jsonrpc: '2.0',
118
- id: 1,
119
- method: 'initialize',
120
- params: {
121
- protocolVersion: '2024-11-05',
122
- clientInfo: { name: 'openclaw-cascade', version: '1.0.0' }
123
- }
124
- });
125
- });
126
-
127
- test('should throw when spawn fails', async () => {
128
- // Arrange
129
- mockSpawn.mockImplementation(() => {
130
- throw new Error('Spawn failed');
131
- });
132
-
133
- // Act & Assert
134
- await expect(client.start()).rejects.toThrow('Spawn failed');
135
- });
136
-
137
- test('should timeout if no response', async () => {
138
- // Arrange - don't emit any response
139
-
140
- // Act & Assert
141
- await expect(client.start()).rejects.toThrow('timed out');
142
- }, 35000); // 35 second timeout for this test
143
- });
144
-
145
- describe('callTool', () => {
146
- beforeEach(async () => {
147
- // Start client first
148
- const startPromise = client.start();
149
-
150
- setTimeout(() => {
151
- mockProcess.stdout.emit('data', JSON.stringify({
152
- jsonrpc: '2.0',
153
- id: 1,
154
- result: {
155
- protocolVersion: '2024-11-05',
156
- capabilities: {},
157
- serverInfo: { name: 'cascade-mcp', version: '1.0.0' }
158
- }
159
- }) + '\n');
160
- }, 10);
161
-
162
- await startPromise;
163
- });
164
-
165
- test('should send tool/call request', async () => {
166
- // Arrange
167
- const callPromise = client.callTool('click_element', { id: 'button1' });
168
-
169
- setTimeout(() => {
170
- mockProcess.stdout.emit('data', JSON.stringify({
171
- jsonrpc: '2.0',
172
- id: 2,
173
- result: { success: true }
174
- }) + '\n');
175
- }, 10);
176
-
177
- // Act
178
- await callPromise;
179
-
180
- // Assert
181
- const writeCalls = mockProcess.stdin.write.mock.calls;
182
- const lastCall = writeCalls[writeCalls.length - 1][0];
183
- const request = JSON.parse(lastCall);
184
-
185
- expect(request).toMatchObject({
186
- jsonrpc: '2.0',
187
- id: 2,
188
- method: 'tools/call',
189
- params: {
190
- name: 'click_element',
191
- arguments: { id: 'button1' }
192
- }
193
- });
194
- });
195
-
196
- test('should return tool result', async () => {
197
- // Arrange
198
- const callPromise = client.callTool('get_screenshot', {});
199
-
200
- setTimeout(() => {
201
- mockProcess.stdout.emit('data', JSON.stringify({
202
- jsonrpc: '2.0',
203
- id: 2,
204
- result: { image: 'base64data', format: 'PNG' }
205
- }) + '\n');
206
- }, 10);
207
-
208
- // Act
209
- const result = await callPromise;
210
-
211
- // Assert
212
- expect(result).toEqual({ image: 'base64data', format: 'PNG' });
213
- });
214
-
215
- test('should handle tool errors', async () => {
216
- // Arrange
217
- const callPromise = client.callTool('click_element', { id: 'nonexistent' });
218
-
219
- setTimeout(() => {
220
- mockProcess.stdout.emit('data', JSON.stringify({
221
- jsonrpc: '2.0',
222
- id: 2,
223
- error: { code: -32602, message: 'Element not found' }
224
- }) + '\n');
225
- }, 10);
226
-
227
- // Act & Assert
228
- await expect(callPromise).rejects.toThrow('Element not found');
229
- });
230
-
231
- test('should timeout after 30 seconds', async () => {
232
- // Arrange - don't emit any response
233
-
234
- // Act & Assert
235
- await expect(client.callTool('click_element', {})).rejects.toThrow('timed out');
236
- }, 35000); // 35 second timeout for this test
237
- });
238
-
239
- describe('listTools', () => {
240
- beforeEach(async () => {
241
- const startPromise = client.start();
242
-
243
- setTimeout(() => {
244
- mockProcess.stdout.emit('data', JSON.stringify({
245
- jsonrpc: '2.0',
246
- id: 1,
247
- result: {
248
- protocolVersion: '2024-11-05',
249
- capabilities: {},
250
- serverInfo: { name: 'cascade-mcp', version: '1.0.0' }
251
- }
252
- }) + '\n');
253
- }, 10);
254
-
255
- await startPromise;
256
- });
257
-
258
- test('should return list of available tools', async () => {
259
- // Arrange
260
- const listPromise = client.listTools();
261
-
262
- setTimeout(() => {
263
- mockProcess.stdout.emit('data', JSON.stringify({
264
- jsonrpc: '2.0',
265
- id: 2,
266
- result: {
267
- tools: [
268
- { name: 'click_element', description: 'Click element' },
269
- { name: 'get_screenshot', description: 'Get screenshot' }
270
- ]
271
- }
272
- }) + '\n');
273
- }, 10);
274
-
275
- // Act
276
- const result = await listPromise;
277
-
278
- // Assert
279
- expect(result).toHaveLength(2);
280
- expect(result[0].name).toBe('click_element');
281
- expect(result[1].name).toBe('get_screenshot');
282
- });
283
-
284
- test('should handle empty tool list', async () => {
285
- // Arrange
286
- const listPromise = client.listTools();
287
-
288
- setTimeout(() => {
289
- mockProcess.stdout.emit('data', JSON.stringify({
290
- jsonrpc: '2.0',
291
- id: 2,
292
- result: { tools: [] }
293
- }) + '\n');
294
- }, 10);
295
-
296
- // Act
297
- const result = await listPromise;
298
-
299
- // Assert
300
- expect(result).toEqual([]);
301
- });
302
- });
303
-
304
- describe('error handling', () => {
305
- test('should handle JSON parse errors gracefully', async () => {
306
- // Arrange
307
- const startPromise = client.start();
308
-
309
- setTimeout(() => {
310
- // Emit invalid JSON first
311
- mockProcess.stdout.emit('data', 'not valid json\n');
312
-
313
- // Then emit valid response
314
- mockProcess.stdout.emit('data', JSON.stringify({
315
- jsonrpc: '2.0',
316
- id: 1,
317
- result: {
318
- protocolVersion: '2024-11-05',
319
- capabilities: {},
320
- serverInfo: { name: 'cascade-mcp', version: '1.0.0' }
321
- }
322
- }) + '\n');
323
- }, 10);
324
-
325
- // Act - should not throw
326
- await expect(startPromise).resolves.not.toThrow();
327
- });
328
-
329
- test('should handle process crash', async () => {
330
- // Arrange
331
- const startPromise = client.start();
332
-
333
- setTimeout(() => {
334
- const errorHandler = mockProcess.on.mock.calls.find(
335
- (call: any) => call[0] === 'error'
336
- )?.[1];
337
- if (errorHandler) {
338
- errorHandler(new Error('Process crashed'));
339
- }
340
- }, 10);
341
-
342
- // Act & Assert
343
- await expect(startPromise).rejects.toThrow('Process crashed');
344
- });
345
-
346
- test('should reject all pending on error', async () => {
347
- // Arrange
348
- const startPromise = client.start();
349
-
350
- setTimeout(() => {
351
- mockProcess.stdout.emit('data', JSON.stringify({
352
- jsonrpc: '2.0',
353
- id: 1,
354
- result: { protocolVersion: '2024-11-05' }
355
- }) + '\n');
356
- }, 10);
357
-
358
- await startPromise;
359
-
360
- // Start a tool call that will hang
361
- const toolPromise = client.callTool('click_element', {});
362
-
363
- // Emit error
364
- setTimeout(() => {
365
- const errorHandler = mockProcess.on.mock.calls.find(
366
- (call: any) => call[0] === 'error'
367
- )?.[1];
368
- if (errorHandler) {
369
- errorHandler(new Error('Connection lost'));
370
- }
371
- }, 10);
372
-
373
- // Act & Assert
374
- await expect(toolPromise).rejects.toThrow('Connection lost');
375
- });
376
- });
377
-
378
- describe('stop', () => {
379
- test('should kill the process', async () => {
380
- // Arrange
381
- const startPromise = client.start();
382
-
383
- setTimeout(() => {
384
- mockProcess.stdout.emit('data', JSON.stringify({
385
- jsonrpc: '2.0',
386
- id: 1,
387
- result: { protocolVersion: '2024-11-05' }
388
- }) + '\n');
389
- }, 10);
390
-
391
- await startPromise;
392
-
393
- // Act
394
- client.stop();
395
-
396
- // Assert
397
- expect(mockProcess.kill).toHaveBeenCalled();
398
- });
399
- });
400
- });