@cloudcli-ai/cloudcli 1.35.0 → 1.36.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 (126) hide show
  1. package/README.md +12 -7
  2. package/dist/api-docs.html +8 -2
  3. package/dist/assets/{index-CtwxqzGG.js → index-S6cIy7hj.js} +271 -260
  4. package/dist/assets/index-uLUE6Qt1.css +32 -0
  5. package/dist/index.html +12 -4
  6. package/dist-server/server/claude-sdk.js +25 -26
  7. package/dist-server/server/claude-sdk.js.map +1 -1
  8. package/dist-server/server/index.js +0 -13
  9. package/dist-server/server/index.js.map +1 -1
  10. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +70 -1
  11. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
  12. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +52 -11
  13. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
  14. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +138 -1
  15. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
  16. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +5 -0
  17. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
  18. package/dist-server/server/modules/providers/services/provider-models.service.js +1 -1
  19. package/dist-server/server/modules/providers/tests/opencode-models.test.js +58 -1
  20. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
  21. package/dist-server/server/modules/websocket/services/websocket-server.service.js +0 -5
  22. package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -1
  23. package/dist-server/server/openai-codex.js +9 -8
  24. package/dist-server/server/openai-codex.js.map +1 -1
  25. package/dist-server/server/opencode-cli.js +20 -2
  26. package/dist-server/server/opencode-cli.js.map +1 -1
  27. package/dist-server/server/routes/agent.js +14 -3
  28. package/dist-server/server/routes/agent.js.map +1 -1
  29. package/electron/desktopWindow.js +0 -15
  30. package/electron/launcher/launcher.js +1 -143
  31. package/electron/main.js +1 -117
  32. package/electron/preload.cjs +0 -3
  33. package/package.json +3 -8
  34. package/public/api-docs.html +8 -2
  35. package/scripts/release/prepare-desktop-app.js +0 -6
  36. package/server/claude-sdk.js +32 -26
  37. package/server/index.js +0 -14
  38. package/server/modules/providers/list/claude/claude-models.provider.ts +73 -1
  39. package/server/modules/providers/list/codex/codex-models.provider.ts +59 -11
  40. package/server/modules/providers/list/opencode/opencode-models.provider.ts +180 -1
  41. package/server/modules/providers/services/provider-capabilities.service.ts +7 -0
  42. package/server/modules/providers/services/provider-models.service.ts +1 -1
  43. package/server/modules/providers/tests/opencode-models.test.ts +62 -0
  44. package/server/modules/websocket/services/websocket-server.service.ts +0 -6
  45. package/server/openai-codex.js +9 -7
  46. package/server/opencode-cli.js +21 -2
  47. package/server/routes/agent.js +14 -3
  48. package/server/shared/types.ts +7 -0
  49. package/dist/assets/index-DlvtxFyt.css +0 -32
  50. package/dist-server/server/computer-use-agent.js +0 -242
  51. package/dist-server/server/computer-use-agent.js.map +0 -1
  52. package/dist-server/server/computer-use-mcp.js +0 -519
  53. package/dist-server/server/computer-use-mcp.js.map +0 -1
  54. package/dist-server/server/modules/computer-use/actions/raw-action-dispatcher.js +0 -55
  55. package/dist-server/server/modules/computer-use/actions/raw-action-dispatcher.js.map +0 -1
  56. package/dist-server/server/modules/computer-use/actions/raw-action-types.js +0 -2
  57. package/dist-server/server/modules/computer-use/actions/raw-action-types.js.map +0 -1
  58. package/dist-server/server/modules/computer-use/computer-executor.js +0 -212
  59. package/dist-server/server/modules/computer-use/computer-executor.js.map +0 -1
  60. package/dist-server/server/modules/computer-use/computer-semantics.service.js +0 -422
  61. package/dist-server/server/modules/computer-use/computer-semantics.service.js.map +0 -1
  62. package/dist-server/server/modules/computer-use/computer-use-mcp.routes.js +0 -126
  63. package/dist-server/server/modules/computer-use/computer-use-mcp.routes.js.map +0 -1
  64. package/dist-server/server/modules/computer-use/computer-use.routes.js +0 -196
  65. package/dist-server/server/modules/computer-use/computer-use.routes.js.map +0 -1
  66. package/dist-server/server/modules/computer-use/computer-use.service.js +0 -759
  67. package/dist-server/server/modules/computer-use/computer-use.service.js.map +0 -1
  68. package/dist-server/server/modules/computer-use/desktop-agent-relay.service.js +0 -126
  69. package/dist-server/server/modules/computer-use/desktop-agent-relay.service.js.map +0 -1
  70. package/dist-server/server/modules/computer-use/index.js +0 -3
  71. package/dist-server/server/modules/computer-use/index.js.map +0 -1
  72. package/dist-server/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.js +0 -59
  73. package/dist-server/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.js.map +0 -1
  74. package/dist-server/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.js +0 -5
  75. package/dist-server/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.js.map +0 -1
  76. package/dist-server/server/modules/computer-use/semantics/adapters/semantic-adapter.js +0 -2
  77. package/dist-server/server/modules/computer-use/semantics/adapters/semantic-adapter.js.map +0 -1
  78. package/dist-server/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.js +0 -5
  79. package/dist-server/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.js.map +0 -1
  80. package/dist-server/server/modules/computer-use/semantics/bin/darwin-arm64/CloudCLISemantics +0 -0
  81. package/dist-server/server/modules/computer-use/semantics/bin/darwin-x64/CloudCLISemantics +0 -0
  82. package/dist-server/server/modules/computer-use/semantics/bin/win32-arm64/CloudCLISemantics.exe +0 -0
  83. package/dist-server/server/modules/computer-use/semantics/bin/win32-x64/CloudCLISemantics.exe +0 -0
  84. package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-process.js +0 -106
  85. package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-process.js.map +0 -1
  86. package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.js +0 -78
  87. package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.js.map +0 -1
  88. package/dist-server/server/modules/computer-use/semantics/semantic-session-store.js +0 -68
  89. package/dist-server/server/modules/computer-use/semantics/semantic-session-store.js.map +0 -1
  90. package/dist-server/server/modules/computer-use/semantics/semantic-tool-dispatcher.js +0 -16
  91. package/dist-server/server/modules/computer-use/semantics/semantic-tool-dispatcher.js.map +0 -1
  92. package/dist-server/server/modules/computer-use/semantics/semantic-types.js +0 -2
  93. package/dist-server/server/modules/computer-use/semantics/semantic-types.js.map +0 -1
  94. package/dist-server/server/modules/websocket/services/desktop-agent-websocket.service.js +0 -39
  95. package/dist-server/server/modules/websocket/services/desktop-agent-websocket.service.js.map +0 -1
  96. package/electron/computerAgent.js +0 -290
  97. package/scripts/build-computer-semantics.mjs +0 -133
  98. package/scripts/copy-computer-semantics-bin.mjs +0 -24
  99. package/server/computer-use-agent.ts +0 -279
  100. package/server/computer-use-mcp.ts +0 -574
  101. package/server/modules/computer-use/actions/raw-action-dispatcher.ts +0 -67
  102. package/server/modules/computer-use/actions/raw-action-types.ts +0 -28
  103. package/server/modules/computer-use/computer-executor.ts +0 -242
  104. package/server/modules/computer-use/computer-semantics.service.ts +0 -460
  105. package/server/modules/computer-use/computer-use-mcp.routes.ts +0 -141
  106. package/server/modules/computer-use/computer-use.routes.ts +0 -211
  107. package/server/modules/computer-use/computer-use.service.ts +0 -920
  108. package/server/modules/computer-use/desktop-agent-relay.service.ts +0 -158
  109. package/server/modules/computer-use/index.ts +0 -2
  110. package/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.ts +0 -82
  111. package/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.ts +0 -5
  112. package/server/modules/computer-use/semantics/adapters/semantic-adapter.ts +0 -23
  113. package/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.ts +0 -5
  114. package/server/modules/computer-use/semantics/bin/darwin-arm64/CloudCLISemantics +0 -0
  115. package/server/modules/computer-use/semantics/bin/darwin-x64/CloudCLISemantics +0 -0
  116. package/server/modules/computer-use/semantics/bin/win32-arm64/CloudCLISemantics.exe +0 -0
  117. package/server/modules/computer-use/semantics/bin/win32-x64/CloudCLISemantics.exe +0 -0
  118. package/server/modules/computer-use/semantics/helpers/macos/CloudCLISemantics.swift +0 -467
  119. package/server/modules/computer-use/semantics/helpers/semantic-helper-process.ts +0 -124
  120. package/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.ts +0 -97
  121. package/server/modules/computer-use/semantics/helpers/windows/CloudCLISemantics.csproj +0 -11
  122. package/server/modules/computer-use/semantics/helpers/windows/Program.cs +0 -534
  123. package/server/modules/computer-use/semantics/semantic-session-store.ts +0 -87
  124. package/server/modules/computer-use/semantics/semantic-tool-dispatcher.ts +0 -17
  125. package/server/modules/computer-use/semantics/semantic-types.ts +0 -58
  126. package/server/modules/websocket/services/desktop-agent-websocket.service.ts +0 -51
@@ -1,242 +0,0 @@
1
- import { createRequire } from 'node:module';
2
-
3
- const require = createRequire(import.meta.url);
4
-
5
- export type Point = { x: number; y: number };
6
- export type ClickButton = 'left' | 'right' | 'middle';
7
- export type ScrollDirection = 'up' | 'down' | 'left' | 'right';
8
- export type DisplaySize = { width: number; height: number };
9
-
10
- export type RuntimeReadiness = {
11
- nut: any | null;
12
- screenshot: any | null;
13
- nutInstalled: boolean;
14
- screenshotInstalled: boolean;
15
- };
16
-
17
- /**
18
- * Coordinate space the executor reports/accepts. The screenshot pixel space is
19
- * the canonical space agents and users address; it is mapped to the nut-js
20
- * logical mouse space before any action runs.
21
- */
22
- export type ExecutorTarget = {
23
- displaySize: DisplaySize | null;
24
- };
25
-
26
- export function getNut(): any | null {
27
- try {
28
- return require('@nut-tree-fork/nut-js');
29
- } catch {
30
- return null;
31
- }
32
- }
33
-
34
- export function getScreenshot(): any | null {
35
- try {
36
- const mod = require('screenshot-desktop');
37
- return mod?.default || mod;
38
- } catch {
39
- return null;
40
- }
41
- }
42
-
43
- export function getRuntimeReadiness(): RuntimeReadiness {
44
- const nut = getNut();
45
- const screenshot = getScreenshot();
46
- return {
47
- nut,
48
- screenshot,
49
- nutInstalled: Boolean(nut),
50
- screenshotInstalled: typeof screenshot === 'function',
51
- };
52
- }
53
-
54
- /** Reads the pixel dimensions from a PNG/JPEG buffer header without decoding it. */
55
- export function readImageSize(buffer: Buffer): DisplaySize | null {
56
- // PNG: 8-byte signature, then IHDR chunk with width/height as big-endian uint32.
57
- if (buffer.length >= 24 && buffer[0] === 0x89 && buffer[1] === 0x50) {
58
- return { width: buffer.readUInt32BE(16), height: buffer.readUInt32BE(20) };
59
- }
60
- // JPEG: scan for a Start-Of-Frame marker (0xFFC0..0xFFCF, excluding C4/C8/CC).
61
- if (buffer.length >= 4 && buffer[0] === 0xff && buffer[1] === 0xd8) {
62
- let offset = 2;
63
- while (offset + 9 < buffer.length) {
64
- if (buffer[offset] !== 0xff) {
65
- offset += 1;
66
- continue;
67
- }
68
- const marker = buffer[offset + 1];
69
- if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {
70
- return { height: buffer.readUInt16BE(offset + 5), width: buffer.readUInt16BE(offset + 7) };
71
- }
72
- offset += 2 + buffer.readUInt16BE(offset + 2);
73
- }
74
- }
75
- return null;
76
- }
77
-
78
- export async function captureScreenshot(): Promise<{ dataUrl: string; size: DisplaySize | null }> {
79
- const screenshot = getScreenshot();
80
- if (typeof screenshot !== 'function') {
81
- throw new Error('Computer Use runtime is not available.');
82
- }
83
- const buffer: Buffer = await screenshot({ format: 'png' });
84
- return {
85
- dataUrl: `data:image/png;base64,${buffer.toString('base64')}`,
86
- size: readImageSize(buffer),
87
- };
88
- }
89
-
90
- /** Returns the mouse coordinate space size (logical screen pixels). */
91
- export async function getMouseSpaceSize(): Promise<DisplaySize> {
92
- const nut = getNut();
93
- if (!nut) {
94
- throw new Error('Computer Use runtime is not available.');
95
- }
96
- const width = await nut.screen.width();
97
- const height = await nut.screen.height();
98
- return { width, height };
99
- }
100
-
101
- /** Maps a point from screenshot/image space to the mouse coordinate space. */
102
- export async function toMouseSpace(target: ExecutorTarget, point: Point): Promise<Point> {
103
- const mouseSize = await getMouseSpaceSize();
104
- const image = target.displaySize || mouseSize;
105
- const scaleX = image.width ? mouseSize.width / image.width : 1;
106
- const scaleY = image.height ? mouseSize.height / image.height : 1;
107
- return {
108
- x: Math.round(point.x * scaleX),
109
- y: Math.round(point.y * scaleY),
110
- };
111
- }
112
-
113
- /** Maps a point from the mouse coordinate space back to screenshot/image space. */
114
- export function toImageSpace(target: ExecutorTarget, point: Point, mouseSize: DisplaySize): Point {
115
- const image = target.displaySize || mouseSize;
116
- const scaleX = mouseSize.width ? image.width / mouseSize.width : 1;
117
- const scaleY = mouseSize.height ? image.height / mouseSize.height : 1;
118
- return {
119
- x: Math.round(point.x * scaleX),
120
- y: Math.round(point.y * scaleY),
121
- };
122
- }
123
-
124
- function nutButton(nut: any, button: ClickButton) {
125
- if (button === 'right') return nut.Button.RIGHT;
126
- if (button === 'middle') return nut.Button.MIDDLE;
127
- return nut.Button.LEFT;
128
- }
129
-
130
- /** Maps a key name (xdotool-style, as Anthropic's computer tool emits) to a nut-js Key. */
131
- function nutKey(nut: any, token: string): any {
132
- const map: Record<string, string> = {
133
- return: 'Enter', enter: 'Enter', esc: 'Escape', escape: 'Escape', tab: 'Tab',
134
- space: 'Space', backspace: 'Backspace', delete: 'Delete', del: 'Delete', insert: 'Insert',
135
- up: 'Up', down: 'Down', left: 'Left', right: 'Right',
136
- home: 'Home', end: 'End', pageup: 'PageUp', page_up: 'PageUp', pagedown: 'PageDown', page_down: 'PageDown',
137
- ctrl: 'LeftControl', control: 'LeftControl', alt: 'LeftAlt', shift: 'LeftShift',
138
- meta: 'LeftSuper', super: 'LeftSuper', cmd: 'LeftSuper', win: 'LeftSuper',
139
- capslock: 'CapsLock',
140
- };
141
- const lower = token.toLowerCase();
142
- if (map[lower]) {
143
- return nut.Key[map[lower]];
144
- }
145
- if (/^f([1-9]|1[0-9]|2[0-4])$/.test(lower)) {
146
- return nut.Key[`F${lower.slice(1)}`];
147
- }
148
- if (token.length === 1) {
149
- const upper = token.toUpperCase();
150
- if (nut.Key[upper] !== undefined) {
151
- return nut.Key[upper];
152
- }
153
- if (nut.Key[`Num${token}`] !== undefined && /[0-9]/.test(token)) {
154
- return nut.Key[`Num${token}`];
155
- }
156
- }
157
- throw new Error(`Unsupported key: ${token}`);
158
- }
159
-
160
- /**
161
- * The cross-platform OS executor. It is intentionally free of any server,
162
- * database, or session dependencies so it can run both inside the local server
163
- * process (OSS mode) and inside the standalone desktop agent (cloud relay).
164
- */
165
- export const executor = {
166
- async configure() {
167
- const nut = getNut();
168
- if (nut) {
169
- // Make actions responsive; the agent loop already paces itself with screenshots.
170
- nut.mouse.config.autoDelayMs = 2;
171
- nut.keyboard.config.autoDelayMs = 2;
172
- }
173
- return nut;
174
- },
175
-
176
- async cursorPosition(target: ExecutorTarget): Promise<Point> {
177
- const nut = await this.configure();
178
- const mouseSize = await getMouseSpaceSize();
179
- const pos = await nut.mouse.getPosition();
180
- return toImageSpace(target, { x: pos.x, y: pos.y }, mouseSize);
181
- },
182
-
183
- async moveTo(target: ExecutorTarget, point: Point): Promise<void> {
184
- const nut = await this.configure();
185
- const dest = await toMouseSpace(target, point);
186
- await nut.mouse.setPosition(new nut.Point(dest.x, dest.y));
187
- },
188
-
189
- async click(target: ExecutorTarget, button: ClickButton, point?: Point, doubleClick = false): Promise<void> {
190
- const nut = await this.configure();
191
- if (point) {
192
- await this.moveTo(target, point);
193
- }
194
- if (doubleClick) {
195
- await nut.mouse.doubleClick(nutButton(nut, button));
196
- } else {
197
- await nut.mouse.click(nutButton(nut, button));
198
- }
199
- },
200
-
201
- async drag(target: ExecutorTarget, from: Point, to: Point, button: ClickButton = 'left'): Promise<void> {
202
- const nut = await this.configure();
203
- const start = await toMouseSpace(target, from);
204
- const end = await toMouseSpace(target, to);
205
- await nut.mouse.setPosition(new nut.Point(start.x, start.y));
206
- await nut.mouse.pressButton(nutButton(nut, button));
207
- await nut.mouse.setPosition(new nut.Point(end.x, end.y));
208
- await nut.mouse.releaseButton(nutButton(nut, button));
209
- },
210
-
211
- async type(text: string): Promise<void> {
212
- const nut = await this.configure();
213
- await nut.keyboard.type(text);
214
- },
215
-
216
- async pressChord(chord: string): Promise<void> {
217
- const nut = await this.configure();
218
- const tokens = chord.split('+').map((token) => token.trim()).filter(Boolean);
219
- if (tokens.length === 0) {
220
- return;
221
- }
222
- const keys = tokens.map((token) => nutKey(nut, token));
223
- for (const key of keys) {
224
- await nut.keyboard.pressKey(key);
225
- }
226
- for (const key of [...keys].reverse()) {
227
- await nut.keyboard.releaseKey(key);
228
- }
229
- },
230
-
231
- async scroll(target: ExecutorTarget, direction: ScrollDirection, amount: number, point?: Point): Promise<void> {
232
- const nut = await this.configure();
233
- if (point) {
234
- await this.moveTo(target, point);
235
- }
236
- const steps = Math.max(1, Math.round(amount));
237
- if (direction === 'up') await nut.mouse.scrollUp(steps);
238
- else if (direction === 'down') await nut.mouse.scrollDown(steps);
239
- else if (direction === 'left') await nut.mouse.scrollLeft(steps);
240
- else await nut.mouse.scrollRight(steps);
241
- },
242
- };
@@ -1,460 +0,0 @@
1
- import { execFile } from 'node:child_process';
2
- import { promisify } from 'node:util';
3
-
4
- import {
5
- captureScreenshot,
6
- executor,
7
- type ClickButton,
8
- type ExecutorTarget,
9
- type Point,
10
- type ScrollDirection,
11
- } from '@/modules/computer-use/computer-executor.js';
12
- import type { SemanticAdapter } from '@/modules/computer-use/semantics/adapters/semantic-adapter.js';
13
- import { createMacOsSemanticAdapter } from '@/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.js';
14
- import { createWindowsSemanticAdapter } from '@/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.js';
15
- import { resolveSemanticHelper } from '@/modules/computer-use/semantics/helpers/semantic-helper-resolver.js';
16
- import { semanticSessionStore } from '@/modules/computer-use/semantics/semantic-session-store.js';
17
- import type { SemanticAppState, SemanticElement } from '@/modules/computer-use/semantics/semantic-types.js';
18
-
19
- const execFileAsync = promisify(execFile);
20
- const MAX_APP_STATE_ELEMENTS = 250;
21
- let helperAdapter: SemanticAdapter | null | undefined;
22
-
23
- function readString(value: unknown): string {
24
- return typeof value === 'string' ? value.trim() : '';
25
- }
26
-
27
- function requireApp(input: Record<string, unknown>): string {
28
- const app = readString(input.app);
29
- if (!app) {
30
- throw new Error('app is required.');
31
- }
32
- return app;
33
- }
34
-
35
- function readNumber(value: unknown): number | undefined {
36
- return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
37
- }
38
-
39
- function readButton(value: unknown): ClickButton {
40
- return value === 'right' || value === 'middle' ? value : 'left';
41
- }
42
-
43
- function readClickCount(value: unknown): number {
44
- const count = readNumber(value);
45
- if (count === undefined) {
46
- return 1;
47
- }
48
- return Math.max(1, Math.min(5, Math.trunc(count)));
49
- }
50
-
51
- function readDirection(value: unknown): ScrollDirection {
52
- return value === 'up' || value === 'left' || value === 'right' ? value : 'down';
53
- }
54
-
55
- function readSessionId(input: Record<string, unknown>): string {
56
- return readString(input.sessionId) || 'default';
57
- }
58
-
59
- function centerOf(element: SemanticElement): Point | null {
60
- const bounds = element.bounds;
61
- if (!bounds) {
62
- return null;
63
- }
64
- return {
65
- x: Math.round(bounds.x + bounds.width / 2),
66
- y: Math.round(bounds.y + bounds.height / 2),
67
- };
68
- }
69
-
70
- function getCachedElement(sessionId: string, app: string, index: string, stateId?: string): SemanticElement | null {
71
- return semanticSessionStore.getElement(sessionId, app, index, stateId);
72
- }
73
-
74
- function getPoint(input: Record<string, unknown>, sessionId: string, app: string): Point | undefined {
75
- const x = readNumber(input.x);
76
- const y = readNumber(input.y);
77
- if (x !== undefined && y !== undefined) {
78
- return { x, y };
79
- }
80
-
81
- const elementIndex = readString(input.element_index);
82
- if (!elementIndex) {
83
- return undefined;
84
- }
85
- const element = getCachedElement(sessionId, app, elementIndex, readString(input.stateId) || undefined);
86
- return element ? centerOf(element) || undefined : undefined;
87
- }
88
-
89
- function getHelperAdapter(): SemanticAdapter | null {
90
- if (helperAdapter !== undefined) {
91
- return helperAdapter;
92
- }
93
-
94
- if (process.platform !== 'darwin' && process.platform !== 'win32') {
95
- helperAdapter = null;
96
- return helperAdapter;
97
- }
98
-
99
- const resolution = resolveSemanticHelper();
100
- if (!resolution.available) {
101
- helperAdapter = null;
102
- return helperAdapter;
103
- }
104
-
105
- helperAdapter = process.platform === 'darwin'
106
- ? createMacOsSemanticAdapter()
107
- : createWindowsSemanticAdapter();
108
- return helperAdapter;
109
- }
110
-
111
- function shouldFallbackFromHelper(error: unknown): boolean {
112
- const message = error instanceof Error ? error.message : String(error);
113
- return /not implemented|unavailable|not found|does not exist|timed out|not running|exited with code|failed to start/i.test(message);
114
- }
115
-
116
- async function withHelperState(
117
- sessionId: string,
118
- operation: (adapter: SemanticAdapter) => Promise<SemanticAppState>,
119
- ): Promise<SemanticAppState | null> {
120
- const adapter = getHelperAdapter();
121
- if (!adapter) {
122
- return null;
123
- }
124
- try {
125
- return semanticSessionStore.save(sessionId, await operation(adapter));
126
- } catch (error) {
127
- if (shouldFallbackFromHelper(error)) {
128
- console.warn('[ComputerSemantics] Falling back from helper:', error instanceof Error ? error.message : String(error));
129
- return null;
130
- }
131
- throw error;
132
- }
133
- }
134
-
135
- async function run(command: string, args: string[], timeout = 5000): Promise<string> {
136
- const { stdout } = await execFileAsync(command, args, {
137
- timeout,
138
- windowsHide: true,
139
- maxBuffer: 1024 * 1024 * 4,
140
- });
141
- return stdout;
142
- }
143
-
144
- async function listMacApps(): Promise<Array<Record<string, unknown>>> {
145
- const script = [
146
- 'tell application "System Events"',
147
- 'set appRows to {}',
148
- 'repeat with p in (application processes whose background only is false)',
149
- 'set end of appRows to (name of p as text)',
150
- 'end repeat',
151
- 'return appRows',
152
- 'end tell',
153
- ].join('\n');
154
- const output = await run('osascript', ['-e', script]);
155
- return output.split(', ')
156
- .map((name) => name.trim())
157
- .filter(Boolean)
158
- .map((name) => ({ name, running: true }));
159
- }
160
-
161
- async function listWindowsApps(): Promise<Array<Record<string, unknown>>> {
162
- const script = [
163
- 'Get-Process | Where-Object { $_.MainWindowTitle } |',
164
- 'Select-Object ProcessName, Id, MainWindowTitle | ConvertTo-Json -Depth 3',
165
- ].join(' ');
166
- const output = await run('powershell.exe', ['-NoProfile', '-Command', script]);
167
- const parsed = JSON.parse(output || '[]');
168
- const rows = Array.isArray(parsed) ? parsed : [parsed];
169
- return rows.map((row) => ({
170
- name: row.ProcessName,
171
- pid: row.Id,
172
- windowTitle: row.MainWindowTitle,
173
- running: true,
174
- }));
175
- }
176
-
177
- async function listLinuxApps(): Promise<Array<Record<string, unknown>>> {
178
- try {
179
- const output = await run('wmctrl', ['-lx']);
180
- return output.split(/\r?\n/)
181
- .map((line) => line.trim())
182
- .filter(Boolean)
183
- .map((line) => {
184
- const parts = line.split(/\s+/);
185
- return {
186
- windowId: parts[0],
187
- desktop: parts[1],
188
- host: parts[2],
189
- className: parts[3],
190
- windowTitle: parts.slice(4).join(' '),
191
- running: true,
192
- };
193
- });
194
- } catch {
195
- const output = await run('ps', ['-eo', 'comm=']);
196
- return [...new Set(output.split(/\r?\n/).map((name) => name.trim()).filter(Boolean))]
197
- .slice(0, 200)
198
- .map((name) => ({ name, running: true }));
199
- }
200
- }
201
-
202
- async function listApps(): Promise<Array<Record<string, unknown>>> {
203
- if (process.platform === 'darwin') {
204
- return listMacApps();
205
- }
206
- if (process.platform === 'win32') {
207
- return listWindowsApps();
208
- }
209
- return listLinuxApps();
210
- }
211
-
212
- async function macAccessibilityTree(app: string): Promise<SemanticElement[]> {
213
- const escapedApp = app.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
214
- const script = `
215
- on safeText(v)
216
- try
217
- return v as text
218
- on error
219
- return ""
220
- end try
221
- end safeText
222
-
223
- on emitElement(e, depth, maxDepth, counter)
224
- if depth > maxDepth then return {}
225
- set rows to {}
226
- try
227
- set roleText to my safeText(role of e)
228
- on error
229
- set roleText to "element"
230
- end try
231
- try
232
- set titleText to my safeText(title of e)
233
- on error
234
- set titleText to ""
235
- end try
236
- try
237
- set valueText to my safeText(value of e)
238
- on error
239
- set valueText to ""
240
- end try
241
- try
242
- set posValue to position of e
243
- set sizeValue to size of e
244
- set boundsText to ((item 1 of posValue) as text) & "," & ((item 2 of posValue) as text) & "," & ((item 1 of sizeValue) as text) & "," & ((item 2 of sizeValue) as text)
245
- on error
246
- set boundsText to ""
247
- end try
248
- set end of rows to ((counter as text) & tab & roleText & tab & titleText & tab & valueText & tab & boundsText)
249
- if counter > ${MAX_APP_STATE_ELEMENTS} then return rows
250
- try
251
- repeat with childElement in UI elements of e
252
- set childRows to my emitElement(childElement, depth + 1, maxDepth, counter + (count of rows))
253
- set rows to rows & childRows
254
- if (count of rows) > ${MAX_APP_STATE_ELEMENTS} then return rows
255
- end repeat
256
- end try
257
- return rows
258
- end emitElement
259
-
260
- tell application "System Events"
261
- if not (exists process "${escapedApp}") then error "App is not running: ${escapedApp}"
262
- tell process "${escapedApp}"
263
- set rows to {}
264
- repeat with w in windows
265
- set rows to rows & my emitElement(w, 0, 4, (count of rows) + 1)
266
- if (count of rows) > ${MAX_APP_STATE_ELEMENTS} then exit repeat
267
- end repeat
268
- return rows
269
- end tell
270
- end tell
271
- `;
272
- const output = await run('osascript', ['-e', script], 10000);
273
- return output.split(/\r?\n|, /)
274
- .map((line) => line.trim())
275
- .filter(Boolean)
276
- .map((line, index) => {
277
- const [rawIndex, role, title, value, boundsText] = line.split('\t');
278
- const boundsParts = (boundsText || '').split(',').map((part) => Number.parseFloat(part));
279
- const hasBounds = boundsParts.length === 4 && boundsParts.every(Number.isFinite);
280
- return {
281
- index: rawIndex || String(index + 1),
282
- role: role || 'element',
283
- title: title || undefined,
284
- value: value || undefined,
285
- bounds: hasBounds
286
- ? { x: boundsParts[0], y: boundsParts[1], width: boundsParts[2], height: boundsParts[3] }
287
- : undefined,
288
- };
289
- });
290
- }
291
-
292
- async function getAccessibilityTree(app: string): Promise<{ elements: SemanticElement[]; message?: string }> {
293
- if (process.platform === 'darwin') {
294
- try {
295
- return { elements: await macAccessibilityTree(app) };
296
- } catch (error) {
297
- return { elements: [], message: error instanceof Error ? error.message : String(error) };
298
- }
299
- }
300
-
301
- return {
302
- elements: [],
303
- message: 'Native accessibility tree capture is not implemented for this platform yet.',
304
- };
305
- }
306
-
307
- async function getAppState(sessionId: string, app: string): Promise<SemanticAppState> {
308
- if (!app) {
309
- throw new Error('app is required.');
310
- }
311
- const helperState = await withHelperState(sessionId, (adapter) => adapter.getAppState({ sessionId, app }));
312
- if (helperState) {
313
- return helperState;
314
- }
315
-
316
- const screenshot = await captureScreenshot();
317
- const tree = await getAccessibilityTree(app);
318
- const state: SemanticAppState = {
319
- stateId: semanticSessionStore.createStateId(),
320
- app,
321
- platform: process.platform,
322
- screenshotDataUrl: screenshot.dataUrl,
323
- displaySize: screenshot.size,
324
- elements: tree.elements,
325
- accessibilityTree: tree.elements,
326
- message: tree.message,
327
- };
328
- return semanticSessionStore.save(sessionId, state);
329
- }
330
-
331
- async function targetFor(sessionId: string, app: string, stateId?: string): Promise<ExecutorTarget> {
332
- const cached = semanticSessionStore.getState(sessionId, app, stateId);
333
- return { displaySize: cached?.displaySize || (await captureScreenshot()).size };
334
- }
335
-
336
- export const computerSemanticsService = {
337
- async callTool(name: string, input: Record<string, unknown>): Promise<unknown> {
338
- const sessionId = readSessionId(input);
339
- switch (name) {
340
- case 'list_apps': {
341
- const adapter = getHelperAdapter();
342
- if (adapter) {
343
- try {
344
- return { apps: await adapter.listApps(), platform: process.platform };
345
- } catch (error) {
346
- if (!shouldFallbackFromHelper(error)) {
347
- throw error;
348
- }
349
- console.warn('[ComputerSemantics] Falling back from helper:', error instanceof Error ? error.message : String(error));
350
- }
351
- }
352
- return { apps: await listApps(), platform: process.platform };
353
- }
354
- case 'get_app_state':
355
- return getAppState(sessionId, readString(input.app));
356
- case 'click':
357
- case 'click_element': {
358
- const app = requireApp(input);
359
- const helperState = await withHelperState(sessionId, (adapter) => adapter.clickElement({ ...input, sessionId, app }));
360
- if (helperState) {
361
- return helperState;
362
- }
363
- const stateId = readString(input.stateId) || undefined;
364
- const point = getPoint(input, sessionId, app);
365
- if (!point) {
366
- throw new Error('click requires x/y or an element_index from computer_get_app_state.');
367
- }
368
- const target = await targetFor(sessionId, app, stateId);
369
- const button = readButton(input.mouse_button ?? input.mouseButton);
370
- const clickCount = readClickCount(input.click_count ?? input.clickCount);
371
- for (let index = 0; index < clickCount; index += 1) {
372
- await executor.click(target, button, point, false);
373
- }
374
- return getAppState(sessionId, app);
375
- }
376
- case 'drag': {
377
- const app = requireApp(input);
378
- const helperState = await withHelperState(sessionId, (adapter) => adapter.drag({ ...input, sessionId, app }));
379
- if (helperState) {
380
- return helperState;
381
- }
382
- const stateId = readString(input.stateId) || undefined;
383
- const fromX = readNumber(input.from_x);
384
- const fromY = readNumber(input.from_y);
385
- const toX = readNumber(input.to_x);
386
- const toY = readNumber(input.to_y);
387
- if (fromX === undefined || fromY === undefined || toX === undefined || toY === undefined) {
388
- throw new Error('drag requires from_x/from_y/to_x/to_y.');
389
- }
390
- await executor.drag(await targetFor(sessionId, app, stateId), { x: fromX, y: fromY }, { x: toX, y: toY }, readButton(input.mouse_button ?? input.mouseButton));
391
- return getAppState(sessionId, app);
392
- }
393
- case 'scroll':
394
- case 'scroll_element': {
395
- const app = requireApp(input);
396
- const helperState = await withHelperState(sessionId, (adapter) => adapter.scrollElement({ ...input, sessionId, app }));
397
- if (helperState) {
398
- return helperState;
399
- }
400
- const stateId = readString(input.stateId) || undefined;
401
- const point = getPoint(input, sessionId, app);
402
- if (!point) {
403
- throw new Error('scroll requires x/y or an element_index from computer_get_app_state.');
404
- }
405
- await executor.scroll(await targetFor(sessionId, app, stateId), readDirection(input.direction), readNumber(input.pages) ?? 1, point);
406
- return getAppState(sessionId, app);
407
- }
408
- case 'type_text': {
409
- const app = requireApp(input);
410
- const helperState = await withHelperState(sessionId, (adapter) => adapter.typeText({ ...input, sessionId, app }));
411
- if (helperState) {
412
- return helperState;
413
- }
414
- await executor.type(readString(input.text));
415
- return getAppState(sessionId, app);
416
- }
417
- case 'press_key': {
418
- const app = requireApp(input);
419
- const helperState = await withHelperState(sessionId, (adapter) => adapter.pressKey({ ...input, sessionId, app }));
420
- if (helperState) {
421
- return helperState;
422
- }
423
- await executor.pressChord(readString(input.key));
424
- return getAppState(sessionId, app);
425
- }
426
- case 'set_value': {
427
- const app = requireApp(input);
428
- const helperState = await withHelperState(sessionId, (adapter) => adapter.setValue({ ...input, sessionId, app }));
429
- if (helperState) {
430
- return helperState;
431
- }
432
- const stateId = readString(input.stateId) || undefined;
433
- const point = getPoint(input, sessionId, app);
434
- if (!point) {
435
- throw new Error('set_value requires x/y or an element_index from computer_get_app_state.');
436
- }
437
- await executor.click(await targetFor(sessionId, app, stateId), 'left', point, false);
438
- await executor.pressChord(process.platform === 'darwin' ? 'cmd+a' : 'ctrl+a');
439
- await executor.type(readString(input.value));
440
- return getAppState(sessionId, app);
441
- }
442
- case 'perform_secondary_action': {
443
- const app = requireApp(input);
444
- const helperState = await withHelperState(sessionId, (adapter) => adapter.performSecondaryAction({ ...input, sessionId, app }));
445
- if (helperState) {
446
- return helperState;
447
- }
448
- const stateId = readString(input.stateId) || undefined;
449
- const point = getPoint(input, sessionId, app);
450
- if (!point) {
451
- throw new Error('perform_secondary_action requires x/y or an element_index from computer_get_app_state.');
452
- }
453
- await executor.click(await targetFor(sessionId, app, stateId), 'right', point, false);
454
- return getAppState(sessionId, app);
455
- }
456
- default:
457
- throw new Error(`Unknown semantic Computer Use tool: ${name}`);
458
- }
459
- },
460
- };