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,198 +0,0 @@
1
- /**
2
- * Cascade MCP Client
3
- *
4
- * Handles communication with Cascade MCP server via stdio JSON-RPC
5
- */
6
-
7
- import { spawn, ChildProcess } from 'child_process';
8
-
9
- export class CascadeMcpClient {
10
- private process: ChildProcess | null = null;
11
- private messageId = 0;
12
- private pendingRequests = new Map<number, { resolve: Function; reject: Function }>();
13
- private isInitialized = false;
14
- private readonly REQUEST_TIMEOUT = 30000; // 30 seconds
15
- private buffer = '';
16
-
17
- constructor(
18
- private pythonPath: string,
19
- private env: NodeJS.ProcessEnv
20
- ) {}
21
-
22
- /**
23
- * Start the MCP client and initialize connection
24
- */
25
- async start(): Promise<void> {
26
- try {
27
- // Spawn Cascade MCP server
28
- this.process = spawn(this.pythonPath, ['-m', 'mcp_server.cli'], {
29
- env: { ...process.env, ...this.env },
30
- stdio: ['pipe', 'pipe', 'pipe']
31
- });
32
-
33
- // Handle stdout for JSON-RPC responses
34
- if (this.process.stdout) {
35
- this.process.stdout.on('data', (data: Buffer) => {
36
- this.handleData(data.toString());
37
- });
38
- }
39
-
40
- // Handle stderr
41
- if (this.process.stderr) {
42
- this.process.stderr.on('data', (data: Buffer) => {
43
- console.error('Cascade MCP stderr:', data.toString());
44
- });
45
- }
46
-
47
- // Handle process errors
48
- this.process.on('error', (error: Error) => {
49
- console.error('Cascade MCP process error:', error);
50
- this.rejectAllPending(error);
51
- });
52
-
53
- // Initialize MCP connection
54
- await this.sendRequest('initialize', {
55
- protocolVersion: '2024-11-05',
56
- capabilities: {},
57
- clientInfo: { name: 'openclaw-cascade', version: '1.0.0' }
58
- });
59
-
60
- this.isInitialized = true;
61
- } catch (error) {
62
- this.stop();
63
- throw error;
64
- }
65
- }
66
-
67
- /**
68
- * Call a tool by name with arguments
69
- */
70
- async callTool(name: string, args: Record<string, any>): Promise<any> {
71
- if (!this.isInitialized) {
72
- throw new Error('MCP client not initialized. Call start() first.');
73
- }
74
-
75
- return this.sendRequest('tools/call', { name, arguments: args });
76
- }
77
-
78
- /**
79
- * List all available tools
80
- */
81
- async listTools(): Promise<any[]> {
82
- if (!this.isInitialized) {
83
- throw new Error('MCP client not initialized. Call start() first.');
84
- }
85
-
86
- const result = await this.sendRequest('tools/list', {});
87
- return result.tools || [];
88
- }
89
-
90
- /**
91
- * Check if client is connected
92
- */
93
- isConnected(): boolean {
94
- return this.isInitialized && this.process !== null && !this.process.killed;
95
- }
96
-
97
- /**
98
- * Stop the MCP client
99
- */
100
- stop(): void {
101
- this.rejectAllPending(new Error('Client stopped'));
102
-
103
- if (this.process && !this.process.killed) {
104
- this.process.kill();
105
- }
106
-
107
- this.process = null;
108
- this.isInitialized = false;
109
- this.buffer = '';
110
- }
111
-
112
- /**
113
- * Handle incoming data from stdout
114
- */
115
- private handleData(data: string): void {
116
- this.buffer += data;
117
-
118
- // Process complete lines
119
- const lines = this.buffer.split('\n');
120
- this.buffer = lines.pop() || ''; // Keep incomplete line in buffer
121
-
122
- for (const line of lines) {
123
- if (line.trim()) {
124
- this.handleResponse(line);
125
- }
126
- }
127
- }
128
-
129
- /**
130
- * Send a JSON-RPC request
131
- */
132
- private async sendRequest(method: string, params: any): Promise<any> {
133
- const id = ++this.messageId;
134
- const request = { jsonrpc: '2.0', id, method, params };
135
-
136
- return new Promise((resolve, reject) => {
137
- // Set timeout
138
- const timeoutId = setTimeout(() => {
139
- this.pendingRequests.delete(id);
140
- reject(new Error(`Request ${method} timed out after ${this.REQUEST_TIMEOUT}ms`));
141
- }, this.REQUEST_TIMEOUT);
142
-
143
- // Store pending request
144
- this.pendingRequests.set(id, {
145
- resolve: (result: any) => {
146
- clearTimeout(timeoutId);
147
- resolve(result);
148
- },
149
- reject: (error: Error) => {
150
- clearTimeout(timeoutId);
151
- reject(error);
152
- }
153
- });
154
-
155
- // Send request
156
- if (this.process?.stdin) {
157
- this.process.stdin.write(JSON.stringify(request) + '\n');
158
- } else {
159
- this.pendingRequests.delete(id);
160
- clearTimeout(timeoutId);
161
- reject(new Error('Process stdin not available'));
162
- }
163
- });
164
- }
165
-
166
- /**
167
- * Handle incoming JSON-RPC response
168
- */
169
- private handleResponse(line: string): void {
170
- try {
171
- const response = JSON.parse(line);
172
-
173
- if (response.id && this.pendingRequests.has(response.id)) {
174
- const { resolve, reject } = this.pendingRequests.get(response.id)!;
175
- this.pendingRequests.delete(response.id);
176
-
177
- if (response.error) {
178
- reject(new Error(response.error.message || 'Unknown error'));
179
- } else {
180
- resolve(response.result);
181
- }
182
- }
183
- } catch (e) {
184
- // Ignore parse errors for non-JSON lines
185
- console.debug('Failed to parse MCP response:', line.substring(0, 100));
186
- }
187
- }
188
-
189
- /**
190
- * Reject all pending requests
191
- */
192
- private rejectAllPending(error: Error): void {
193
- for (const [_id, { reject }] of this.pendingRequests) {
194
- reject(error);
195
- }
196
- this.pendingRequests.clear();
197
- }
198
- }
@@ -1,189 +0,0 @@
1
- /**
2
- * Tests for Config
3
- */
4
-
5
- import { loadConfig, validateConfig, getDefaults } from './config';
6
- import { CascadePluginConfig } from './types';
7
-
8
- describe('Config', () => {
9
- const originalEnv = process.env;
10
-
11
- beforeEach(() => {
12
- process.env = { ...originalEnv };
13
- });
14
-
15
- afterEach(() => {
16
- process.env = originalEnv;
17
- });
18
-
19
- describe('getDefaults', () => {
20
- test('should return default configuration', async () => {
21
- // Act
22
- const defaults = await getDefaults();
23
-
24
- // Assert
25
- expect(defaults).toEqual({
26
- cascadeGrpcEndpoint: 'localhost:50051',
27
- headless: false,
28
- actionTimeoutMs: 8000,
29
- enableA2A: true,
30
- verbose: false,
31
- screenshotMode: 'auto'
32
- });
33
- });
34
- });
35
-
36
- describe('validateConfig', () => {
37
- test('should pass with valid config', async () => {
38
- // Arrange
39
- const config: CascadePluginConfig = {
40
- cascadeGrpcEndpoint: 'localhost:50051'
41
- };
42
-
43
- // Act & Assert
44
- expect(() => validateConfig(config)).not.toThrow();
45
- });
46
-
47
- test('should throw when cascadeGrpcEndpoint is missing', async () => {
48
- // Arrange
49
- const config: CascadePluginConfig = {} as any;
50
-
51
- // Act & Assert
52
- expect(() => validateConfig(config)).toThrow('cascadeGrpcEndpoint is required');
53
- });
54
-
55
- test('should throw when cascadeGrpcEndpoint is empty', async () => {
56
- // Arrange
57
- const config: CascadePluginConfig = {
58
- cascadeGrpcEndpoint: ''
59
- };
60
-
61
- // Act & Assert
62
- expect(() => validateConfig(config)).toThrow('cascadeGrpcEndpoint is required');
63
- });
64
-
65
- test('should validate firestore credentials path if provided', async () => {
66
- // Arrange
67
- const config: CascadePluginConfig = {
68
- cascadeGrpcEndpoint: 'localhost:50051',
69
- firestoreCredentialsPath: '/path/to/creds.json'
70
- };
71
-
72
- // Act & Assert
73
- expect(() => validateConfig(config)).not.toThrow();
74
- });
75
-
76
- test('should validate allowed agents', async () => {
77
- // Arrange
78
- const config: CascadePluginConfig = {
79
- cascadeGrpcEndpoint: 'localhost:50051',
80
- allowedAgents: ['explorer', 'worker', 'orchestrator']
81
- };
82
-
83
- // Act & Assert
84
- expect(() => validateConfig(config)).not.toThrow();
85
- });
86
-
87
- test('should throw for invalid agent in allowedAgents', async () => {
88
- // Arrange
89
- const config: any = {
90
- cascadeGrpcEndpoint: 'localhost:50051',
91
- allowedAgents: ['explorer', 'invalid-agent']
92
- };
93
-
94
- // Act & Assert
95
- expect(() => validateConfig(config)).toThrow('Invalid agent');
96
- });
97
-
98
- test('should validate screenshotMode', async () => {
99
- // Arrange
100
- const config: CascadePluginConfig = {
101
- cascadeGrpcEndpoint: 'localhost:50051',
102
- screenshotMode: 'embed'
103
- };
104
-
105
- // Act & Assert
106
- expect(() => validateConfig(config)).not.toThrow();
107
- });
108
-
109
- test('should throw for invalid screenshotMode', async () => {
110
- // Arrange
111
- const config: any = {
112
- cascadeGrpcEndpoint: 'localhost:50051',
113
- screenshotMode: 'invalid'
114
- };
115
-
116
- // Act & Assert
117
- expect(() => validateConfig(config)).toThrow('Invalid screenshotMode');
118
- });
119
- });
120
-
121
- describe('loadConfig', () => {
122
- test('should load config with defaults when input is empty', async () => {
123
- // Act
124
- const config = await loadConfig({});
125
-
126
- // Assert
127
- expect(config.cascadeGrpcEndpoint).toBe('localhost:50051');
128
- expect(config.headless).toBe(false);
129
- expect(config.actionTimeoutMs).toBe(8000);
130
- });
131
-
132
- test('should merge with provided values', async () => {
133
- // Arrange
134
- const input = {
135
- cascadeGrpcEndpoint: '192.168.1.100:50051',
136
- headless: true,
137
- actionTimeoutMs: 15000
138
- };
139
-
140
- // Act
141
- const config = await loadConfig(input);
142
-
143
- // Assert
144
- expect(config.cascadeGrpcEndpoint).toBe('192.168.1.100:50051');
145
- expect(config.headless).toBe(true);
146
- expect(config.actionTimeoutMs).toBe(15000);
147
- });
148
-
149
- test('should expand environment variables in paths', async () => {
150
- // Arrange
151
- process.env.HOME = '/home/user';
152
- const input = {
153
- cascadeGrpcEndpoint: 'localhost:50051',
154
- firestoreCredentialsPath: '$HOME/creds.json'
155
- };
156
-
157
- // Act
158
- const config = await loadConfig(input);
159
-
160
- // Assert
161
- expect(config.firestoreCredentialsPath).toBe('/home/user/creds.json');
162
- });
163
-
164
- test('should handle Windows environment variables', async () => {
165
- // Arrange
166
- process.env.USERPROFILE = 'C:\\Users\\TestUser';
167
- const input = {
168
- cascadeGrpcEndpoint: 'localhost:50051',
169
- cascadePythonPath: '%USERPROFILE%\\Python\\python.exe'
170
- };
171
-
172
- // Act
173
- const config = await loadConfig(input);
174
-
175
- // Assert
176
- expect(config.cascadePythonPath).toBe('C:\\Users\\TestUser\\Python\\python.exe');
177
- });
178
-
179
- test('should validate loaded config', async () => {
180
- // Arrange
181
- const input = {
182
- cascadeGrpcEndpoint: ''
183
- };
184
-
185
- // Act & Assert
186
- await expect(loadConfig(input as any)).rejects.toThrow('cascadeGrpcEndpoint is required');
187
- });
188
- });
189
- });
package/src/config.ts DELETED
@@ -1,137 +0,0 @@
1
- /**
2
- * Configuration management for OpenClaw Cascade Plugin
3
- */
4
-
5
- import { CascadePluginConfig } from './types';
6
- import { isWsl, getWslHostIp } from './wsl';
7
-
8
- const VALID_AGENTS = ['explorer', 'worker', 'orchestrator'];
9
- const VALID_SCREENSHOT_MODES = ['embed', 'disk', 'auto'];
10
-
11
- /**
12
- * Get default configuration values
13
- */
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
-
24
- return {
25
- cascadeGrpcEndpoint: defaultEndpoint,
26
- headless: false,
27
- actionTimeoutMs: 8000,
28
- enableA2A: true,
29
- verbose: false,
30
- screenshotMode: 'auto'
31
- };
32
- }
33
-
34
- /**
35
- * Validate configuration object
36
- */
37
- export function validateConfig(config: CascadePluginConfig): void {
38
- // Required fields
39
- if (!config.cascadeGrpcEndpoint || config.cascadeGrpcEndpoint.trim() === '') {
40
- throw new Error('cascadeGrpcEndpoint is required');
41
- }
42
-
43
- // Validate gRPC endpoint format
44
- const endpointPattern = /^[\w.-]+:\d+$/;
45
- if (!endpointPattern.test(config.cascadeGrpcEndpoint)) {
46
- throw new Error(
47
- `Invalid cascadeGrpcEndpoint format: ${config.cascadeGrpcEndpoint}. ` +
48
- 'Expected format: host:port (e.g., localhost:50051)'
49
- );
50
- }
51
-
52
- // Validate allowed agents if provided
53
- if (config.allowedAgents) {
54
- for (const agent of config.allowedAgents) {
55
- if (!VALID_AGENTS.includes(agent)) {
56
- throw new Error(`Invalid agent in allowedAgents: ${agent}. Valid agents: ${VALID_AGENTS.join(', ')}`);
57
- }
58
- }
59
- }
60
-
61
- // Validate screenshot mode if provided
62
- if (config.screenshotMode && !VALID_SCREENSHOT_MODES.includes(config.screenshotMode)) {
63
- throw new Error(
64
- `Invalid screenshotMode: ${config.screenshotMode}. ` +
65
- `Valid modes: ${VALID_SCREENSHOT_MODES.join(', ')}`
66
- );
67
- }
68
-
69
- // Validate action timeout
70
- if (config.actionTimeoutMs !== undefined && config.actionTimeoutMs < 1000) {
71
- throw new Error('actionTimeoutMs must be at least 1000ms');
72
- }
73
- }
74
-
75
- /**
76
- * Expand environment variables in a string
77
- * Supports both $VAR and %VAR% syntax
78
- */
79
- export function expandEnvVars(value: string): string {
80
- if (!value) return value;
81
-
82
- // Unix-style: $VAR or ${VAR}
83
- let expanded = value.replace(/\$\{(\w+)\}/g, (match, varName) => {
84
- return process.env[varName] || match;
85
- });
86
-
87
- expanded = expanded.replace(/\$(\w+)/g, (match, varName) => {
88
- return process.env[varName] || match;
89
- });
90
-
91
- // Windows-style: %VAR%
92
- expanded = expanded.replace(/%(\w+)%/g, (match, varName) => {
93
- return process.env[varName] || match;
94
- });
95
-
96
- return expanded;
97
- }
98
-
99
- /**
100
- * Load and validate configuration
101
- */
102
- export async function loadConfig(input?: Partial<CascadePluginConfig>): Promise<CascadePluginConfig> {
103
- const actualInput = input || {};
104
-
105
- // Expand environment variables in string fields
106
- const expanded: Partial<CascadePluginConfig> = {};
107
-
108
- for (const [key, value] of Object.entries(actualInput)) {
109
- if (typeof value === 'string') {
110
- (expanded as any)[key] = expandEnvVars(value);
111
- } else {
112
- (expanded as any)[key] = value;
113
- }
114
- }
115
-
116
- // Merge with defaults
117
- const defaults = await getDefaults();
118
- const config: CascadePluginConfig = {
119
- ...defaults,
120
- ...expanded
121
- } as CascadePluginConfig;
122
-
123
- // Validate
124
- validateConfig(config);
125
-
126
- return config;
127
- }
128
-
129
- /**
130
- * Load configuration from OpenClaw API
131
- */
132
- export async function loadConfigFromOpenClaw(api: any): Promise<CascadePluginConfig> {
133
- const entries = api.config?.plugins?.entries || {};
134
- const pluginConfig = entries['openclaw-cascade-plugin']?.config || entries.cascade?.config || {};
135
-
136
- return loadConfig(pluginConfig);
137
- }