openclaw-cascade-plugin 1.0.0

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 (98) hide show
  1. package/PHASE1_SUMMARY.md +191 -0
  2. package/PHASE3_SUMMARY.md +195 -0
  3. package/README.md +43 -0
  4. package/dist/a2a-client.d.ts +17 -0
  5. package/dist/a2a-client.d.ts.map +1 -0
  6. package/dist/a2a-client.js +47 -0
  7. package/dist/a2a-client.js.map +1 -0
  8. package/dist/cascade-client.d.ts +53 -0
  9. package/dist/cascade-client.d.ts.map +1 -0
  10. package/dist/cascade-client.js +179 -0
  11. package/dist/cascade-client.js.map +1 -0
  12. package/dist/config.d.ts +26 -0
  13. package/dist/config.d.ts.map +1 -0
  14. package/dist/config.js +116 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/index.d.ts +29 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +136 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/python-manager.d.ts +59 -0
  21. package/dist/python-manager.d.ts.map +1 -0
  22. package/dist/python-manager.js +190 -0
  23. package/dist/python-manager.js.map +1 -0
  24. package/dist/test-utils/helpers.d.ts +20 -0
  25. package/dist/test-utils/helpers.d.ts.map +1 -0
  26. package/dist/test-utils/helpers.js +89 -0
  27. package/dist/test-utils/helpers.js.map +1 -0
  28. package/dist/test-utils/index.d.ts +3 -0
  29. package/dist/test-utils/index.d.ts.map +1 -0
  30. package/dist/test-utils/index.js +19 -0
  31. package/dist/test-utils/index.js.map +1 -0
  32. package/dist/test-utils/mocks.d.ts +51 -0
  33. package/dist/test-utils/mocks.d.ts.map +1 -0
  34. package/dist/test-utils/mocks.js +84 -0
  35. package/dist/test-utils/mocks.js.map +1 -0
  36. package/dist/tools/a2a-tools.d.ts +9 -0
  37. package/dist/tools/a2a-tools.d.ts.map +1 -0
  38. package/dist/tools/a2a-tools.js +147 -0
  39. package/dist/tools/a2a-tools.js.map +1 -0
  40. package/dist/tools/api-tools.d.ts +9 -0
  41. package/dist/tools/api-tools.d.ts.map +1 -0
  42. package/dist/tools/api-tools.js +102 -0
  43. package/dist/tools/api-tools.js.map +1 -0
  44. package/dist/tools/desktop-automation.d.ts +10 -0
  45. package/dist/tools/desktop-automation.d.ts.map +1 -0
  46. package/dist/tools/desktop-automation.js +330 -0
  47. package/dist/tools/desktop-automation.js.map +1 -0
  48. package/dist/tools/index.d.ts +12 -0
  49. package/dist/tools/index.d.ts.map +1 -0
  50. package/dist/tools/index.js +35 -0
  51. package/dist/tools/index.js.map +1 -0
  52. package/dist/tools/response-helpers.d.ts +25 -0
  53. package/dist/tools/response-helpers.d.ts.map +1 -0
  54. package/dist/tools/response-helpers.js +71 -0
  55. package/dist/tools/response-helpers.js.map +1 -0
  56. package/dist/tools/sandbox-tools.d.ts +9 -0
  57. package/dist/tools/sandbox-tools.d.ts.map +1 -0
  58. package/dist/tools/sandbox-tools.js +79 -0
  59. package/dist/tools/sandbox-tools.js.map +1 -0
  60. package/dist/tools/tool-registry.d.ts +34 -0
  61. package/dist/tools/tool-registry.d.ts.map +1 -0
  62. package/dist/tools/tool-registry.js +50 -0
  63. package/dist/tools/tool-registry.js.map +1 -0
  64. package/dist/tools/web-automation.d.ts +9 -0
  65. package/dist/tools/web-automation.d.ts.map +1 -0
  66. package/dist/tools/web-automation.js +471 -0
  67. package/dist/tools/web-automation.js.map +1 -0
  68. package/dist/types/index.d.ts +111 -0
  69. package/dist/types/index.d.ts.map +1 -0
  70. package/dist/types/index.js +38 -0
  71. package/dist/types/index.js.map +1 -0
  72. package/jest.setup.js +19 -0
  73. package/openclaw-cascade-plugin-1.0.0.tgz +0 -0
  74. package/openclaw.plugin.json +116 -0
  75. package/package.json +74 -0
  76. package/src/a2a-client.ts +66 -0
  77. package/src/cascade-client.test.ts +400 -0
  78. package/src/cascade-client.ts +198 -0
  79. package/src/config.test.ts +194 -0
  80. package/src/config.ts +135 -0
  81. package/src/index.ts +164 -0
  82. package/src/python-manager.test.ts +187 -0
  83. package/src/python-manager.ts +230 -0
  84. package/src/test-utils/helpers.ts +107 -0
  85. package/src/test-utils/index.ts +2 -0
  86. package/src/test-utils/mocks.ts +101 -0
  87. package/src/tools/a2a-tools.ts +162 -0
  88. package/src/tools/api-tools.ts +110 -0
  89. package/src/tools/desktop-automation.test.ts +305 -0
  90. package/src/tools/desktop-automation.ts +366 -0
  91. package/src/tools/index.ts +13 -0
  92. package/src/tools/response-helpers.ts +78 -0
  93. package/src/tools/sandbox-tools.ts +83 -0
  94. package/src/tools/tool-registry.ts +51 -0
  95. package/src/tools/web-automation.test.ts +177 -0
  96. package/src/tools/web-automation.ts +518 -0
  97. package/src/types/index.ts +132 -0
  98. package/tsconfig.json +27 -0
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Tests for Desktop Automation Tools
3
+ */
4
+
5
+ import { registerDesktopTools } from './desktop-automation';
6
+ import { ToolRegistry } from './tool-registry';
7
+ import { MockCascadeMcpClient } from '../test-utils';
8
+
9
+ describe('Desktop Automation Tools', () => {
10
+ let registry: ToolRegistry;
11
+ let mockClient: MockCascadeMcpClient;
12
+
13
+ beforeEach(() => {
14
+ registry = new ToolRegistry();
15
+ mockClient = new MockCascadeMcpClient();
16
+ registerDesktopTools(registry, mockClient as any);
17
+ });
18
+
19
+ describe('cascade_click_element', () => {
20
+ test('should be registered', () => {
21
+ expect(registry.has('cascade_click_element')).toBe(true);
22
+ });
23
+
24
+ test('should click element by ID', async () => {
25
+ // Arrange
26
+ mockClient.registerMockTool('click_element', () => ({ success: true }));
27
+
28
+ // Act
29
+ const result = await registry.call('cascade_click_element', {
30
+ platform_source: 'WINDOWS',
31
+ id: 'button1'
32
+ });
33
+
34
+ // Assert
35
+ expect(mockClient.callTool).toHaveBeenCalledWith('click_element', {
36
+ platform_source: 'WINDOWS',
37
+ id: 'button1'
38
+ });
39
+ expect(result.content[0].text).toContain('success');
40
+ });
41
+
42
+ test('should handle click failure', async () => {
43
+ // Arrange
44
+ mockClient.simulateError(new Error('Element not found'));
45
+
46
+ // Act
47
+ const result = await registry.call('cascade_click_element', {
48
+ platform_source: 'WINDOWS',
49
+ id: 'nonexistent'
50
+ });
51
+
52
+ // Assert
53
+ expect(result.isError).toBe(true);
54
+ expect(result.content[0].text).toContain('Element not found');
55
+ });
56
+
57
+ test('should require platform_source', async () => {
58
+ // Act
59
+ const result = await registry.call('cascade_click_element', {
60
+ id: 'button1'
61
+ });
62
+
63
+ // Assert
64
+ expect(result.isError).toBe(true);
65
+ expect(result.content[0].text).toContain('platform_source is required');
66
+ });
67
+ });
68
+
69
+ describe('cascade_type_text', () => {
70
+ test('should be registered', () => {
71
+ expect(registry.has('cascade_type_text')).toBe(true);
72
+ });
73
+
74
+ test('should type text into element', async () => {
75
+ // Arrange
76
+ mockClient.registerMockTool('type_text', () => ({ success: true }));
77
+
78
+ // Act
79
+ await registry.call('cascade_type_text', {
80
+ selector: { platform_source: 'WINDOWS', id: 'input1' },
81
+ text: 'Hello World'
82
+ });
83
+
84
+ // Assert
85
+ expect(mockClient.callTool).toHaveBeenCalledWith('type_text', {
86
+ selector: { platform_source: 'WINDOWS', id: 'input1' },
87
+ text: 'Hello World'
88
+ });
89
+ });
90
+
91
+ test('should require selector', async () => {
92
+ // Act
93
+ const result = await registry.call('cascade_type_text', {
94
+ text: 'Hello'
95
+ });
96
+
97
+ // Assert
98
+ expect(result.isError).toBe(true);
99
+ expect(result.content[0].text).toContain('selector');
100
+ });
101
+
102
+ test('should require text', async () => {
103
+ // Act
104
+ const result = await registry.call('cascade_type_text', {
105
+ selector: { platform_source: 'WINDOWS' }
106
+ });
107
+
108
+ // Assert
109
+ expect(result.isError).toBe(true);
110
+ expect(result.content[0].text).toContain('text is required');
111
+ });
112
+ });
113
+
114
+ describe('cascade_get_semantic_tree', () => {
115
+ test('should be registered', () => {
116
+ expect(registry.has('cascade_get_semantic_tree')).toBe(true);
117
+ });
118
+
119
+ test('should return semantic tree', async () => {
120
+ // Arrange
121
+ mockClient.registerMockTool('get_semantic_tree', () => ({
122
+ elements: [
123
+ { id: '1', name: 'Button', control_type: 'BUTTON' },
124
+ { id: '2', name: 'Input', control_type: 'INPUT' }
125
+ ]
126
+ }));
127
+
128
+ // Act
129
+ const result = await registry.call('cascade_get_semantic_tree', {});
130
+
131
+ // Assert
132
+ expect(mockClient.callTool).toHaveBeenCalledWith('get_semantic_tree', {});
133
+ expect(result.content[0].text).toContain('Button');
134
+ expect(result.content[0].text).toContain('Input');
135
+ });
136
+
137
+ test('should handle empty tree', async () => {
138
+ // Arrange
139
+ mockClient.registerMockTool('get_semantic_tree', () => ({
140
+ elements: []
141
+ }));
142
+
143
+ // Act
144
+ const result = await registry.call('cascade_get_semantic_tree', {});
145
+
146
+ // Assert
147
+ expect(result.content[0].text).toContain('elements');
148
+ });
149
+ });
150
+
151
+ describe('cascade_get_screenshot', () => {
152
+ test('should be registered', () => {
153
+ expect(registry.has('cascade_get_screenshot')).toBe(true);
154
+ });
155
+
156
+ test('should return embedded screenshot when small', async () => {
157
+ // Arrange
158
+ const smallImage = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
159
+ mockClient.registerMockTool('get_screenshot', () => ({
160
+ image: smallImage,
161
+ format: 'PNG',
162
+ marks: [{ element_id: '1', label: 'A' }]
163
+ }));
164
+
165
+ // Act
166
+ const result = await registry.call('cascade_get_screenshot', {});
167
+
168
+ // Assert
169
+ expect(result.content[0].type).toBe('image');
170
+ expect(result.content[0].source.data).toBe(smallImage);
171
+ });
172
+
173
+ test('should save large screenshot to disk', async () => {
174
+ // Arrange
175
+ const largeImage = 'A'.repeat(5000000); // 5MB base64 = ~3.75MB actual
176
+ mockClient.registerMockTool('get_screenshot', () => ({
177
+ image: largeImage,
178
+ format: 'PNG',
179
+ marks: []
180
+ }));
181
+
182
+ // Act
183
+ const result = await registry.call('cascade_get_screenshot', {});
184
+
185
+ // Assert
186
+ expect(result.content[0].type).toBe('text');
187
+ expect(result.content[0].text).toContain('saved');
188
+ });
189
+ });
190
+
191
+ describe('cascade_start_app', () => {
192
+ test('should be registered', () => {
193
+ expect(registry.has('cascade_start_app')).toBe(true);
194
+ });
195
+
196
+ test('should start application', async () => {
197
+ // Arrange
198
+ mockClient.registerMockTool('start_app', () => ({ success: true }));
199
+
200
+ // Act
201
+ const result = await registry.call('cascade_start_app', {
202
+ app_name: 'notepad'
203
+ });
204
+
205
+ // Assert
206
+ expect(mockClient.callTool).toHaveBeenCalledWith('start_app', {
207
+ app_name: 'notepad'
208
+ });
209
+ expect(result.content[0].text).toContain('success');
210
+ });
211
+
212
+ test('should require app_name', async () => {
213
+ // Act
214
+ const result = await registry.call('cascade_start_app', {});
215
+
216
+ // Assert
217
+ expect(result.isError).toBe(true);
218
+ expect(result.content[0].text).toContain('app_name is required');
219
+ });
220
+ });
221
+
222
+ describe('cascade_hover_element', () => {
223
+ test('should be registered', () => {
224
+ expect(registry.has('cascade_hover_element')).toBe(true);
225
+ });
226
+
227
+ test('should hover over element', async () => {
228
+ // Arrange
229
+ mockClient.registerMockTool('hover_element', () => ({ success: true }));
230
+
231
+ // Act
232
+ await registry.call('cascade_hover_element', {
233
+ selector: { platform_source: 'WINDOWS', id: 'button1' }
234
+ });
235
+
236
+ // Assert
237
+ expect(mockClient.callTool).toHaveBeenCalledWith('hover_element', {
238
+ selector: { platform_source: 'WINDOWS', id: 'button1' }
239
+ });
240
+ });
241
+ });
242
+
243
+ describe('cascade_focus_element', () => {
244
+ test('should be registered', () => {
245
+ expect(registry.has('cascade_focus_element')).toBe(true);
246
+ });
247
+
248
+ test('should focus on element', async () => {
249
+ // Arrange
250
+ mockClient.registerMockTool('focus_element', () => ({ success: true }));
251
+
252
+ // Act
253
+ await registry.call('cascade_focus_element', {
254
+ selector: { platform_source: 'WINDOWS', id: 'input1' }
255
+ });
256
+
257
+ // Assert
258
+ expect(mockClient.callTool).toHaveBeenCalledWith('focus_element', {
259
+ selector: { platform_source: 'WINDOWS', id: 'input1' }
260
+ });
261
+ });
262
+ });
263
+
264
+ describe('cascade_scroll_element', () => {
265
+ test('should be registered', () => {
266
+ expect(registry.has('cascade_scroll_element')).toBe(true);
267
+ });
268
+
269
+ test('should scroll element', async () => {
270
+ // Arrange
271
+ mockClient.registerMockTool('scroll_element', () => ({ success: true }));
272
+
273
+ // Act
274
+ await registry.call('cascade_scroll_element', {
275
+ selector: { platform_source: 'WINDOWS', id: 'scrollable' }
276
+ });
277
+
278
+ // Assert
279
+ expect(mockClient.callTool).toHaveBeenCalledWith('scroll_element', {
280
+ selector: { platform_source: 'WINDOWS', id: 'scrollable' }
281
+ });
282
+ });
283
+ });
284
+
285
+ describe('cascade_wait_visible', () => {
286
+ test('should be registered', () => {
287
+ expect(registry.has('cascade_wait_visible')).toBe(true);
288
+ });
289
+
290
+ test('should wait for element visibility', async () => {
291
+ // Arrange
292
+ mockClient.registerMockTool('wait_visible', () => ({ success: true }));
293
+
294
+ // Act
295
+ await registry.call('cascade_wait_visible', {
296
+ selector: { platform_source: 'WINDOWS', id: 'loading' }
297
+ });
298
+
299
+ // Assert
300
+ expect(mockClient.callTool).toHaveBeenCalledWith('wait_visible', {
301
+ selector: { platform_source: 'WINDOWS', id: 'loading' }
302
+ });
303
+ });
304
+ });
305
+ });
@@ -0,0 +1,366 @@
1
+ /**
2
+ * Desktop Automation Tools
3
+ *
4
+ * 9 tools for controlling Windows desktop applications
5
+ */
6
+
7
+ import { ToolRegistry } from './tool-registry';
8
+ import { CascadeMcpClient } from '../cascade-client';
9
+ import { ToolResponse, CascadePluginConfig } from '../types';
10
+ import { errorResponse, formatSuccess, imageResponse } from './response-helpers';
11
+ import { writeFile, mkdir } from 'fs/promises';
12
+ import { join } from 'path';
13
+ import { homedir } from 'os';
14
+
15
+ // Max size for embedded images (4MB)
16
+ const MAX_EMBED_SIZE = 4 * 1024 * 1024;
17
+
18
+ export function registerDesktopTools(
19
+ registry: ToolRegistry,
20
+ client: CascadeMcpClient,
21
+ config?: CascadePluginConfig
22
+ ): void {
23
+ const screenshotMode = config?.screenshotMode || 'auto';
24
+ const screenshotDir = config?.screenshotDir || join(homedir(), '.openclaw', 'screenshots');
25
+
26
+ // 1. cascade_click_element
27
+ registry.register({
28
+ name: 'cascade_click_element',
29
+ description: 'Click on a UI element on the desktop or in an application',
30
+ inputSchema: {
31
+ type: 'object',
32
+ properties: {
33
+ platform_source: {
34
+ type: 'string',
35
+ enum: ['WINDOWS', 'JAVA', 'WEB'],
36
+ description: 'Platform type (Windows desktop, Java app, or web)'
37
+ },
38
+ id: { type: 'string', description: 'Element ID if known' },
39
+ name: { type: 'string', description: 'Element name/label' },
40
+ control_type: {
41
+ type: 'string',
42
+ enum: ['BUTTON', 'INPUT', 'COMBO', 'MENU', 'TREE', 'TABLE', 'CUSTOM'],
43
+ description: 'Control type'
44
+ },
45
+ path: {
46
+ type: 'array',
47
+ items: { type: 'string' },
48
+ description: 'Path components for navigation'
49
+ },
50
+ index: { type: 'integer', description: 'Index if multiple matches' },
51
+ text_hint: { type: 'string', description: 'Text hint for element' }
52
+ },
53
+ required: ['platform_source']
54
+ },
55
+ handler: async (args): Promise<ToolResponse> => {
56
+ try {
57
+ if (!args.platform_source) {
58
+ return errorResponse('platform_source is required');
59
+ }
60
+
61
+ const result = await client.callTool('click_element', args);
62
+ return formatSuccess(result);
63
+ } catch (error) {
64
+ return errorResponse(
65
+ error instanceof Error ? error.message : 'Failed to click element',
66
+ 'Try using cascade_get_semantic_tree first to identify the correct element'
67
+ );
68
+ }
69
+ }
70
+ });
71
+
72
+ // 2. cascade_type_text
73
+ registry.register({
74
+ name: 'cascade_type_text',
75
+ description: 'Type text into a UI element (input field, text area, etc.)',
76
+ inputSchema: {
77
+ type: 'object',
78
+ properties: {
79
+ selector: {
80
+ type: 'object',
81
+ description: 'Element selector',
82
+ properties: {
83
+ platform_source: {
84
+ type: 'string',
85
+ enum: ['WINDOWS', 'JAVA', 'WEB']
86
+ },
87
+ id: { type: 'string' },
88
+ name: { type: 'string' },
89
+ control_type: {
90
+ type: 'string',
91
+ enum: ['BUTTON', 'INPUT', 'COMBO', 'MENU', 'TREE', 'TABLE', 'CUSTOM']
92
+ },
93
+ path: { type: 'array', items: { type: 'string' } },
94
+ index: { type: 'integer' },
95
+ text_hint: { type: 'string' }
96
+ },
97
+ required: ['platform_source']
98
+ },
99
+ text: {
100
+ type: 'string',
101
+ description: 'Text to type'
102
+ }
103
+ },
104
+ required: ['selector', 'text']
105
+ },
106
+ handler: async (args): Promise<ToolResponse> => {
107
+ try {
108
+ if (!args.selector) {
109
+ return errorResponse('selector is required');
110
+ }
111
+ if (!args.text) {
112
+ return errorResponse('text is required');
113
+ }
114
+
115
+ const result = await client.callTool('type_text', args);
116
+ return formatSuccess(result);
117
+ } catch (error) {
118
+ return errorResponse(
119
+ error instanceof Error ? error.message : 'Failed to type text'
120
+ );
121
+ }
122
+ }
123
+ });
124
+
125
+ // 3. cascade_get_semantic_tree
126
+ registry.register({
127
+ name: 'cascade_get_semantic_tree',
128
+ description: 'Get the UI element structure of the current window or application',
129
+ inputSchema: {
130
+ type: 'object',
131
+ properties: {}
132
+ },
133
+ handler: async (): Promise<ToolResponse> => {
134
+ try {
135
+ const result = await client.callTool('get_semantic_tree', {});
136
+ return formatSuccess(result);
137
+ } catch (error) {
138
+ return errorResponse(
139
+ error instanceof Error ? error.message : 'Failed to get semantic tree'
140
+ );
141
+ }
142
+ }
143
+ });
144
+
145
+ // 4. cascade_get_screenshot
146
+ registry.register({
147
+ name: 'cascade_get_screenshot',
148
+ description: 'Capture a screenshot of the current screen with element annotations',
149
+ inputSchema: {
150
+ type: 'object',
151
+ properties: {}
152
+ },
153
+ handler: async (): Promise<ToolResponse> => {
154
+ try {
155
+ const result = await client.callTool('get_screenshot', {});
156
+
157
+ if (!result.image) {
158
+ return errorResponse('No image data received');
159
+ }
160
+
161
+ const imageSize = Buffer.from(result.image, 'base64').length;
162
+ const shouldEmbed = screenshotMode === 'embed' ||
163
+ (screenshotMode === 'auto' && imageSize < MAX_EMBED_SIZE);
164
+
165
+ if (shouldEmbed) {
166
+ return imageResponse(
167
+ result.image,
168
+ result.format?.toLowerCase() === 'jpeg' ? 'jpeg' : 'png',
169
+ `Screenshot captured (${(imageSize / 1024).toFixed(1)} KB)`
170
+ );
171
+ } else {
172
+ // Save to disk
173
+ await mkdir(screenshotDir, { recursive: true });
174
+ const filename = `cascade-${Date.now()}.${result.format?.toLowerCase() || 'png'}`;
175
+ const filepath = join(screenshotDir, filename);
176
+ await writeFile(filepath, Buffer.from(result.image, 'base64'));
177
+
178
+ return formatSuccess({
179
+ saved_to: filepath,
180
+ size_kb: (imageSize / 1024).toFixed(1),
181
+ marks: result.marks
182
+ });
183
+ }
184
+ } catch (error) {
185
+ return errorResponse(
186
+ error instanceof Error ? error.message : 'Failed to capture screenshot'
187
+ );
188
+ }
189
+ }
190
+ });
191
+
192
+ // 5. cascade_start_app
193
+ registry.register({
194
+ name: 'cascade_start_app',
195
+ description: 'Start an application by name',
196
+ inputSchema: {
197
+ type: 'object',
198
+ properties: {
199
+ app_name: {
200
+ type: 'string',
201
+ description: 'Application name or executable (e.g., notepad, calc, excel)'
202
+ }
203
+ },
204
+ required: ['app_name']
205
+ },
206
+ handler: async (args): Promise<ToolResponse> => {
207
+ try {
208
+ if (!args.app_name) {
209
+ return errorResponse('app_name is required');
210
+ }
211
+
212
+ const result = await client.callTool('start_app', args);
213
+ return formatSuccess(result);
214
+ } catch (error) {
215
+ return errorResponse(
216
+ error instanceof Error ? error.message : 'Failed to start application',
217
+ 'Verify the app name is correct and the application is installed'
218
+ );
219
+ }
220
+ }
221
+ });
222
+
223
+ // 6. cascade_hover_element
224
+ registry.register({
225
+ name: 'cascade_hover_element',
226
+ description: 'Hover over a UI element',
227
+ inputSchema: {
228
+ type: 'object',
229
+ properties: {
230
+ selector: {
231
+ type: 'object',
232
+ description: 'Element selector',
233
+ properties: {
234
+ platform_source: { type: 'string', enum: ['WINDOWS', 'JAVA', 'WEB'] },
235
+ id: { type: 'string' },
236
+ name: { type: 'string' },
237
+ control_type: { type: 'string' },
238
+ path: { type: 'array', items: { type: 'string' } },
239
+ index: { type: 'integer' },
240
+ text_hint: { type: 'string' }
241
+ },
242
+ required: ['platform_source']
243
+ }
244
+ },
245
+ required: ['selector']
246
+ },
247
+ handler: async (args): Promise<ToolResponse> => {
248
+ try {
249
+ const result = await client.callTool('hover_element', args);
250
+ return formatSuccess(result);
251
+ } catch (error) {
252
+ return errorResponse(
253
+ error instanceof Error ? error.message : 'Failed to hover element'
254
+ );
255
+ }
256
+ }
257
+ });
258
+
259
+ // 7. cascade_focus_element
260
+ registry.register({
261
+ name: 'cascade_focus_element',
262
+ description: 'Focus on a UI element',
263
+ inputSchema: {
264
+ type: 'object',
265
+ properties: {
266
+ selector: {
267
+ type: 'object',
268
+ description: 'Element selector',
269
+ properties: {
270
+ platform_source: { type: 'string', enum: ['WINDOWS', 'JAVA', 'WEB'] },
271
+ id: { type: 'string' },
272
+ name: { type: 'string' },
273
+ control_type: { type: 'string' },
274
+ path: { type: 'array', items: { type: 'string' } },
275
+ index: { type: 'integer' },
276
+ text_hint: { type: 'string' }
277
+ },
278
+ required: ['platform_source']
279
+ }
280
+ },
281
+ required: ['selector']
282
+ },
283
+ handler: async (args): Promise<ToolResponse> => {
284
+ try {
285
+ const result = await client.callTool('focus_element', args);
286
+ return formatSuccess(result);
287
+ } catch (error) {
288
+ return errorResponse(
289
+ error instanceof Error ? error.message : 'Failed to focus element'
290
+ );
291
+ }
292
+ }
293
+ });
294
+
295
+ // 8. cascade_scroll_element
296
+ registry.register({
297
+ name: 'cascade_scroll_element',
298
+ description: 'Scroll a UI element',
299
+ inputSchema: {
300
+ type: 'object',
301
+ properties: {
302
+ selector: {
303
+ type: 'object',
304
+ description: 'Element selector',
305
+ properties: {
306
+ platform_source: { type: 'string', enum: ['WINDOWS', 'JAVA', 'WEB'] },
307
+ id: { type: 'string' },
308
+ name: { type: 'string' },
309
+ control_type: { type: 'string' },
310
+ path: { type: 'array', items: { type: 'string' } },
311
+ index: { type: 'integer' },
312
+ text_hint: { type: 'string' }
313
+ },
314
+ required: ['platform_source']
315
+ }
316
+ },
317
+ required: ['selector']
318
+ },
319
+ handler: async (args): Promise<ToolResponse> => {
320
+ try {
321
+ const result = await client.callTool('scroll_element', args);
322
+ return formatSuccess(result);
323
+ } catch (error) {
324
+ return errorResponse(
325
+ error instanceof Error ? error.message : 'Failed to scroll element'
326
+ );
327
+ }
328
+ }
329
+ });
330
+
331
+ // 9. cascade_wait_visible
332
+ registry.register({
333
+ name: 'cascade_wait_visible',
334
+ description: 'Wait for a UI element to become visible',
335
+ inputSchema: {
336
+ type: 'object',
337
+ properties: {
338
+ selector: {
339
+ type: 'object',
340
+ description: 'Element selector',
341
+ properties: {
342
+ platform_source: { type: 'string', enum: ['WINDOWS', 'JAVA', 'WEB'] },
343
+ id: { type: 'string' },
344
+ name: { type: 'string' },
345
+ control_type: { type: 'string' },
346
+ path: { type: 'array', items: { type: 'string' } },
347
+ index: { type: 'integer' },
348
+ text_hint: { type: 'string' }
349
+ },
350
+ required: ['platform_source']
351
+ }
352
+ },
353
+ required: ['selector']
354
+ },
355
+ handler: async (args): Promise<ToolResponse> => {
356
+ try {
357
+ const result = await client.callTool('wait_visible', args);
358
+ return formatSuccess(result);
359
+ } catch (error) {
360
+ return errorResponse(
361
+ error instanceof Error ? error.message : 'Failed to wait for element'
362
+ );
363
+ }
364
+ }
365
+ });
366
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Tools Index
3
+ *
4
+ * Export all tool registration functions
5
+ */
6
+
7
+ export { registerDesktopTools } from './desktop-automation';
8
+ export { registerWebTools } from './web-automation';
9
+ export { registerApiTools } from './api-tools';
10
+ export { registerSandboxTools } from './sandbox-tools';
11
+ export { registerA2ATools } from './a2a-tools';
12
+ // Note: ToolRegistry class is not exported from main index to avoid conflict with ToolRegistry interface
13
+ export * from './response-helpers';