@ai-agent-forge/plugin-desktop-macos-input 0.0.0-stage → 0.88.4
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.
- package/MANUAL.md +16 -0
- package/README.md +55 -2
- package/agent-forge.json +31 -0
- package/dist/capability.d.ts +21 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +456 -0
- package/dist/capability.js.map +1 -0
- package/dist/entry.d.ts +16 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +17 -0
- package/dist/entry.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/macos-ax-adapter.d.ts +72 -0
- package/dist/macos-ax-adapter.d.ts.map +1 -0
- package/dist/macos-ax-adapter.js +805 -0
- package/dist/macos-ax-adapter.js.map +1 -0
- package/dist/projection.d.ts +114 -0
- package/dist/projection.d.ts.map +1 -0
- package/dist/projection.js +226 -0
- package/dist/projection.js.map +1 -0
- package/dist/shared.d.ts +15 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +30 -0
- package/dist/shared.js.map +1 -0
- package/dist/types.d.ts +223 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +21 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -5
- package/plugin.json +20 -0
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS Accessibility driver (query + bounded input actions) and the input
|
|
3
|
+
* adapter facade for this package. Structurally mirrors the Windows package's
|
|
4
|
+
* `windows-uia-adapter.ts` + `windows-input-adapter.ts` (inlined from
|
|
5
|
+
* agent-forge `src/plugins/desktop/*`, D-075 S3) with these deliberate
|
|
6
|
+
* differences:
|
|
7
|
+
* - Driver boundary is `osascript -l JavaScript -e <script>` (JXA) instead of
|
|
8
|
+
* PowerShell UIA. The query script walks the System Events accessibility
|
|
9
|
+
* tree (`Application("com.apple.systemevents")`); the action script re-locates the
|
|
10
|
+
* observed target by its deterministic enumeration identity
|
|
11
|
+
* (`window-<pid>-<windowIndex>` / `<windowId>:<role>:<childIndex>`; Windows
|
|
12
|
+
* uses native HWND handles instead — mac AX exposes no stable handles).
|
|
13
|
+
* - Bounded actions: focus (frontmost + AXRaise / AXFocused), click
|
|
14
|
+
* (AXPress with a CGEvent coordinate-click fallback, plus screen-coordinate
|
|
15
|
+
* click targets — click-only by ruling), type (focus + System Events
|
|
16
|
+
* keystroke), key (System Events keyCode/keystroke over a minimal SendKeys
|
|
17
|
+
* aligned table, parsed host-side), scroll (CGEvent scroll wheel).
|
|
18
|
+
* - The facade exposes no replay method (this package ships no replay);
|
|
19
|
+
* sensitive actions stay behind the shared `requiresDesktopUserConfirmation`
|
|
20
|
+
* fail-closed gate.
|
|
21
|
+
* - Non-darwin platforms never throw at import or call time: probe reports
|
|
22
|
+
* `{available:false, reason:"macOS Accessibility requires darwin"}` and
|
|
23
|
+
* every tool returns an explicit `unsupported` result. Missing
|
|
24
|
+
* Accessibility (assistive access) permission is reported fail-closed as
|
|
25
|
+
* `accessibility-permission-required`; no degraded mode.
|
|
26
|
+
* This file is a deliberate copy shared (not imported) with the observe
|
|
27
|
+
* package's adapter; the observe copy carries the query script only.
|
|
28
|
+
*/
|
|
29
|
+
import { spawnSync as nodeSpawnSync } from "node:child_process";
|
|
30
|
+
import { detachedFreeze } from "@agent-forge/plugin-sdk";
|
|
31
|
+
import { requiresDesktopUserConfirmation } from "./types.js";
|
|
32
|
+
const READONLY_CAPABILITIES = ["desktop-session", "desktop-uia", "desktop-input"];
|
|
33
|
+
const SUPPORTED_ACTIONS = ["focus", "click", "type", "key", "scroll"];
|
|
34
|
+
const error = (code, message, causeCode) => ({
|
|
35
|
+
version: 1,
|
|
36
|
+
code,
|
|
37
|
+
message,
|
|
38
|
+
...(causeCode === undefined ? {} : { causeCode }),
|
|
39
|
+
});
|
|
40
|
+
const unsupported = (operationId, reason, causeCode = "platform-unavailable") => ({
|
|
41
|
+
version: 1,
|
|
42
|
+
operationId,
|
|
43
|
+
status: "unsupported",
|
|
44
|
+
summary: reason,
|
|
45
|
+
error: error(causeCode === "action-unsupported" ? "action_unsupported" : "platform_unavailable", reason, causeCode),
|
|
46
|
+
});
|
|
47
|
+
const rectangle = (raw) => ({
|
|
48
|
+
x: Number.isFinite(raw.x) ? Number(raw.x) : 0,
|
|
49
|
+
y: Number.isFinite(raw.y) ? Number(raw.y) : 0,
|
|
50
|
+
width: Number.isFinite(raw.width) ? Number(raw.width) : 0,
|
|
51
|
+
height: Number.isFinite(raw.height) ? Number(raw.height) : 0,
|
|
52
|
+
});
|
|
53
|
+
const isPermissionReason = (value) => /assistive access|not authorized|accessibility-permission-required/i.test(value);
|
|
54
|
+
/**
|
|
55
|
+
* JXA observation script. Enumerates every non-background System Events
|
|
56
|
+
* process's windows (caps: 64 windows, 256 controls total, mirroring the
|
|
57
|
+
* projection budgets) with role/name/position/size/enabled/value per control,
|
|
58
|
+
* plus the frontmost process's focused element matched by role and frame.
|
|
59
|
+
* Permission or automation failures are reported in-band as
|
|
60
|
+
* `{available:false, reason:...}` so the probe can classify them; the reason
|
|
61
|
+
* literal `accessibility-permission-required` is reserved for missing
|
|
62
|
+
* Accessibility (assistive access) grants.
|
|
63
|
+
*/
|
|
64
|
+
const QUERY_SCRIPT = `
|
|
65
|
+
var se = Application('com.apple.systemevents');
|
|
66
|
+
function safe(fn) { try { return fn(); } catch (e) { return undefined; } }
|
|
67
|
+
function rectOf(el) {
|
|
68
|
+
var p = safe(function() { return el.position(); });
|
|
69
|
+
var z = safe(function() { return el.size(); });
|
|
70
|
+
if (p === undefined || z === undefined || p.length < 2 || z.length < 2) return {x: 0, y: 0, width: 0, height: 0};
|
|
71
|
+
return {x: Math.round(p[0]), y: Math.round(p[1]), width: Math.round(z[0]), height: Math.round(z[1])};
|
|
72
|
+
}
|
|
73
|
+
function axReason(e) {
|
|
74
|
+
var msg = String(e && e.message !== undefined ? e.message : e);
|
|
75
|
+
var num = e && typeof e.errorNumber === 'number' ? e.errorNumber : 0;
|
|
76
|
+
if (msg.indexOf('assistive access') !== -1 || msg.indexOf('not authorized') !== -1 ||
|
|
77
|
+
num === -25211 || num === -1719 || num === -1743) return 'accessibility-permission-required';
|
|
78
|
+
return msg;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
var out = {available: true, windows: [], controls: [], focusedControlId: null};
|
|
82
|
+
var procs = se.processes();
|
|
83
|
+
var focused = null;
|
|
84
|
+
for (var fi = 0; fi < procs.length; fi++) {
|
|
85
|
+
var fp = procs[fi];
|
|
86
|
+
if (safe(function() { return fp.frontmost(); }) !== true) continue;
|
|
87
|
+
var fe = safe(function() { return fp.attributes['AXFocusedUIElement'].value(); });
|
|
88
|
+
if (fe !== undefined) focused = {role: safe(function() { return fe.role(); }), rect: rectOf(fe)};
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
var windowCount = 0;
|
|
92
|
+
var controlCount = 0;
|
|
93
|
+
for (var pi = 0; pi < procs.length && windowCount < 64; pi++) {
|
|
94
|
+
var proc = procs[pi];
|
|
95
|
+
if (safe(function() { return proc.backgroundOnly(); }) === true) continue;
|
|
96
|
+
var wins = safe(function() { return proc.windows(); });
|
|
97
|
+
if (wins === undefined || wins.length === 0) continue;
|
|
98
|
+
var pid = safe(function() { return proc.unixId(); });
|
|
99
|
+
var pname = safe(function() { return proc.name(); });
|
|
100
|
+
if (typeof pname !== 'string' || pname.length === 0) pname = 'unknown';
|
|
101
|
+
for (var wi = 0; wi < wins.length && windowCount < 64; wi++) {
|
|
102
|
+
var win = wins[wi];
|
|
103
|
+
var winRect = rectOf(win);
|
|
104
|
+
var winId = 'window-' + pid + '-' + wi;
|
|
105
|
+
windowCount++;
|
|
106
|
+
out.windows.push({id: winId, processId: pid, processName: pname,
|
|
107
|
+
name: safe(function() { return win.name(); }) || '', x: winRect.x, y: winRect.y,
|
|
108
|
+
width: winRect.width, height: winRect.height});
|
|
109
|
+
var contents = safe(function() { return win.entireContents(); });
|
|
110
|
+
if (contents === undefined) continue;
|
|
111
|
+
for (var ci = 0; ci < contents.length && controlCount < 256; ci++) {
|
|
112
|
+
var el = contents[ci];
|
|
113
|
+
var role = safe(function() { return el.role(); });
|
|
114
|
+
if (typeof role !== 'string' || role.length === 0) continue;
|
|
115
|
+
var r = rectOf(el);
|
|
116
|
+
var controlId = winId + ':' + role + ':' + ci;
|
|
117
|
+
var value = safe(function() { return el.value(); });
|
|
118
|
+
out.controls.push({windowId: winId, controlId: controlId, role: role,
|
|
119
|
+
name: safe(function() { return el.name(); }) || safe(function() { return el.title(); }) || '',
|
|
120
|
+
enabled: safe(function() { return el.enabled(); }) !== false,
|
|
121
|
+
value: typeof value === 'string' && value.length > 0 ? value : undefined,
|
|
122
|
+
x: r.x, y: r.y, width: r.width, height: r.height});
|
|
123
|
+
controlCount++;
|
|
124
|
+
if (focused !== null && out.focusedControlId === null && role === focused.role &&
|
|
125
|
+
r.x === focused.rect.x && r.y === focused.rect.y &&
|
|
126
|
+
r.width === focused.rect.width && r.height === focused.rect.height) {
|
|
127
|
+
out.focusedControlId = controlId;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
JSON.stringify(out);
|
|
133
|
+
} catch (e) {
|
|
134
|
+
JSON.stringify({available: false, reason: axReason(e)});
|
|
135
|
+
}`;
|
|
136
|
+
/**
|
|
137
|
+
* JXA bounded-input script. Reads the JSON payload from
|
|
138
|
+
* `AGENT_FORGE_MACOS_AX_INPUT`: `{kind, target:{windowId?, controlId?,
|
|
139
|
+
* coordinates?}, text?, key:{keyCode?, text?, modifiers?},
|
|
140
|
+
* scroll:{up, lines}}`. The target is re-located through the same
|
|
141
|
+
* deterministic enumeration the query script used; an unfound identity is a
|
|
142
|
+
* stale target. Output: `{ok:true}` or `{ok:false, code, reason}`.
|
|
143
|
+
*/
|
|
144
|
+
const ACTION_SCRIPT = `
|
|
145
|
+
ObjC.import('CoreGraphics');
|
|
146
|
+
var se = Application('com.apple.systemevents');
|
|
147
|
+
function safe(fn) { try { return fn(); } catch (e) { return undefined; } }
|
|
148
|
+
function rectOf(el) {
|
|
149
|
+
var p = safe(function() { return el.position(); });
|
|
150
|
+
var z = safe(function() { return el.size(); });
|
|
151
|
+
if (p === undefined || z === undefined || p.length < 2 || z.length < 2) return {x: 0, y: 0, width: 0, height: 0};
|
|
152
|
+
return {x: Math.round(p[0]), y: Math.round(p[1]), width: Math.round(z[0]), height: Math.round(z[1])};
|
|
153
|
+
}
|
|
154
|
+
function findProcess(pid) {
|
|
155
|
+
var procs = se.processes();
|
|
156
|
+
for (var i = 0; i < procs.length; i++) {
|
|
157
|
+
if (safe(function() { return procs[i].unixId(); }) === pid) return procs[i];
|
|
158
|
+
}
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
function parseWindowId(windowId) {
|
|
162
|
+
var m = /^window-(\\d+)-(\\d+)$/.exec(String(windowId));
|
|
163
|
+
return m === null ? undefined : {pid: Number(m[1]), index: Number(m[2])};
|
|
164
|
+
}
|
|
165
|
+
function findWindow(windowId) {
|
|
166
|
+
var parsed = parseWindowId(windowId);
|
|
167
|
+
if (parsed === undefined) return undefined;
|
|
168
|
+
var proc = findProcess(parsed.pid);
|
|
169
|
+
if (proc === undefined) return undefined;
|
|
170
|
+
var wins = safe(function() { return proc.windows(); });
|
|
171
|
+
if (wins === undefined || parsed.index >= wins.length) return undefined;
|
|
172
|
+
return {proc: proc, win: wins[parsed.index]};
|
|
173
|
+
}
|
|
174
|
+
function findControl(windowId, controlId) {
|
|
175
|
+
var located = findWindow(windowId);
|
|
176
|
+
if (located === undefined) return undefined;
|
|
177
|
+
var parts = String(controlId).split(':');
|
|
178
|
+
if (parts.length < 3) return undefined;
|
|
179
|
+
var index = Number(parts[parts.length - 1]);
|
|
180
|
+
var role = parts[parts.length - 2];
|
|
181
|
+
var contents = safe(function() { return located.win.entireContents(); });
|
|
182
|
+
if (contents === undefined || index >= contents.length) return undefined;
|
|
183
|
+
var el = contents[index];
|
|
184
|
+
if (safe(function() { return el.role(); }) !== role) return undefined;
|
|
185
|
+
return {proc: located.proc, win: located.win, el: el};
|
|
186
|
+
}
|
|
187
|
+
function cgClick(x, y) {
|
|
188
|
+
var pt = {x: x, y: y};
|
|
189
|
+
var move = $.CGEventCreateMouseEvent($(), $.kCGEventMouseMoved, pt, $.kCGMouseButtonLeft);
|
|
190
|
+
$.CGEventPost($.kCGHIDEventTap, move);
|
|
191
|
+
var down = $.CGEventCreateMouseEvent($(), $.kCGEventLeftMouseDown, pt, $.kCGMouseButtonLeft);
|
|
192
|
+
$.CGEventPost($.kCGHIDEventTap, down);
|
|
193
|
+
var up = $.CGEventCreateMouseEvent($(), $.kCGEventLeftMouseUp, pt, $.kCGMouseButtonLeft);
|
|
194
|
+
$.CGEventPost($.kCGHIDEventTap, up);
|
|
195
|
+
}
|
|
196
|
+
function cgScroll(x, y, lines, up) {
|
|
197
|
+
var pt = {x: x, y: y};
|
|
198
|
+
var move = $.CGEventCreateMouseEvent($(), $.kCGEventMouseMoved, pt, $.kCGMouseButtonLeft);
|
|
199
|
+
$.CGEventPost($.kCGHIDEventTap, move);
|
|
200
|
+
var ev = $.CGEventCreateScrollWheelEvent($(), $.kCGScrollEventUnitLine, 1, up ? lines : -lines);
|
|
201
|
+
$.CGEventPost($.kCGHIDEventTap, ev);
|
|
202
|
+
}
|
|
203
|
+
function setFocused(el) {
|
|
204
|
+
try { el.focused = true; return true; } catch (e) { return false; }
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
var payload = JSON.parse($.NSProcessInfo.processInfo.environment.objectForKey('AGENT_FORGE_MACOS_AX_INPUT').js);
|
|
208
|
+
var target = payload.target || {};
|
|
209
|
+
var el = undefined;
|
|
210
|
+
var win = undefined;
|
|
211
|
+
var proc = undefined;
|
|
212
|
+
if (target.windowId !== undefined && target.controlId !== undefined) {
|
|
213
|
+
var control = findControl(target.windowId, target.controlId);
|
|
214
|
+
if (control === undefined) throw {stale: true};
|
|
215
|
+
el = control.el; win = control.win; proc = control.proc;
|
|
216
|
+
} else if (target.windowId !== undefined) {
|
|
217
|
+
var located = findWindow(target.windowId);
|
|
218
|
+
if (located === undefined) throw {stale: true};
|
|
219
|
+
win = located.win; proc = located.proc;
|
|
220
|
+
}
|
|
221
|
+
if (payload.kind === 'focus') {
|
|
222
|
+
if (el !== undefined) {
|
|
223
|
+
if (!setFocused(el)) throw {reason: 'focus_failed'};
|
|
224
|
+
} else if (win !== undefined) {
|
|
225
|
+
try { proc.frontmost = true; } catch (e) {}
|
|
226
|
+
if (safe(function() { return win.actions['AXRaise'].perform(); }) === undefined && safe(function() { return proc.frontmost(); }) !== true) {
|
|
227
|
+
throw {reason: 'focus_failed'};
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
throw {reason: 'ax_action_exception'};
|
|
231
|
+
}
|
|
232
|
+
} else if (payload.kind === 'click') {
|
|
233
|
+
if (target.coordinates !== undefined) {
|
|
234
|
+
cgClick(Number(target.coordinates.x), Number(target.coordinates.y));
|
|
235
|
+
} else if (el !== undefined) {
|
|
236
|
+
var performed = false;
|
|
237
|
+
try { el.actions['AXPress'].perform(); performed = true; } catch (e) {}
|
|
238
|
+
if (!performed) {
|
|
239
|
+
var r = rectOf(el);
|
|
240
|
+
if (r.width <= 0 && r.height <= 0) throw {reason: 'press_unavailable'};
|
|
241
|
+
cgClick(r.x + Math.floor(r.width / 2), r.y + Math.floor(r.height / 2));
|
|
242
|
+
}
|
|
243
|
+
} else if (win !== undefined) {
|
|
244
|
+
var wr = rectOf(win);
|
|
245
|
+
cgClick(wr.x + Math.floor(wr.width / 2), wr.y + Math.floor(wr.height / 2));
|
|
246
|
+
} else {
|
|
247
|
+
throw {reason: 'ax_action_exception'};
|
|
248
|
+
}
|
|
249
|
+
} else if (payload.kind === 'type') {
|
|
250
|
+
if (el !== undefined && !setFocused(el)) throw {reason: 'focus_failed'};
|
|
251
|
+
if (el === undefined && win !== undefined) {
|
|
252
|
+
try { proc.frontmost = true; } catch (e) {}
|
|
253
|
+
}
|
|
254
|
+
se.keystroke(String(payload.text));
|
|
255
|
+
} else if (payload.kind === 'key') {
|
|
256
|
+
if (el !== undefined && !setFocused(el)) throw {reason: 'focus_failed'};
|
|
257
|
+
var using = Array.isArray(payload.key.modifiers) && payload.key.modifiers.length > 0 ? {using: payload.key.modifiers} : {};
|
|
258
|
+
if (typeof payload.key.keyCode === 'number') {
|
|
259
|
+
se.keyCode(payload.key.keyCode, using);
|
|
260
|
+
} else {
|
|
261
|
+
se.keystroke(String(payload.key.text), using);
|
|
262
|
+
}
|
|
263
|
+
} else if (payload.kind === 'scroll') {
|
|
264
|
+
var center = target.coordinates !== undefined
|
|
265
|
+
? {x: Number(target.coordinates.x), y: Number(target.coordinates.y)}
|
|
266
|
+
: (el !== undefined ? rectOf(el) : (win !== undefined ? rectOf(win) : {x: 0, y: 0, width: 0, height: 0}));
|
|
267
|
+
cgScroll(center.x + Math.floor((center.width || 0) / 2), center.y + Math.floor((center.height || 0) / 2),
|
|
268
|
+
Number(payload.scroll.lines), payload.scroll.up === true);
|
|
269
|
+
} else {
|
|
270
|
+
throw {reason: 'ax_action_exception'};
|
|
271
|
+
}
|
|
272
|
+
JSON.stringify({ok: true});
|
|
273
|
+
} catch (e) {
|
|
274
|
+
if (e && e.stale === true) {
|
|
275
|
+
JSON.stringify({ok: false, code: 'stale_target', reason: 'ax_action_exception'});
|
|
276
|
+
} else {
|
|
277
|
+
var reason = e && typeof e.reason === 'string' ? e.reason : 'ax_action_exception';
|
|
278
|
+
JSON.stringify({ok: false, code: 'action_failed', reason: reason});
|
|
279
|
+
}
|
|
280
|
+
}`;
|
|
281
|
+
const parseJson = (stdout) => {
|
|
282
|
+
try {
|
|
283
|
+
const parsed = JSON.parse(stdout.trim());
|
|
284
|
+
return typeof parsed === "object" && parsed !== null ? parsed : undefined;
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
291
|
+
* Minimal SendKeys-aligned key grammar, parsed host-side because JXA has no
|
|
292
|
+
* SendKeys equivalent. Grammar: optional modifier prefixes (`+` shift,
|
|
293
|
+
* `^` control, `%` option — the Windows SendKeys mapping) followed by either
|
|
294
|
+
* one `{NAME}` special key or exactly one literal character.
|
|
295
|
+
*/
|
|
296
|
+
const MAC_KEY_CODES = Object.freeze({
|
|
297
|
+
RETURN: 36,
|
|
298
|
+
ENTER: 36,
|
|
299
|
+
TAB: 48,
|
|
300
|
+
ESC: 53,
|
|
301
|
+
ESCAPE: 53,
|
|
302
|
+
DEL: 117,
|
|
303
|
+
DELETE: 117,
|
|
304
|
+
BS: 51,
|
|
305
|
+
BKSP: 51,
|
|
306
|
+
BACKSPACE: 51,
|
|
307
|
+
UP: 126,
|
|
308
|
+
DOWN: 125,
|
|
309
|
+
LEFT: 123,
|
|
310
|
+
RIGHT: 124,
|
|
311
|
+
HOME: 115,
|
|
312
|
+
END: 119,
|
|
313
|
+
PGUP: 116,
|
|
314
|
+
PAGEUP: 116,
|
|
315
|
+
PGDN: 121,
|
|
316
|
+
PAGEDOWN: 121,
|
|
317
|
+
SPACE: 49,
|
|
318
|
+
F1: 122,
|
|
319
|
+
F2: 120,
|
|
320
|
+
F3: 99,
|
|
321
|
+
F4: 118,
|
|
322
|
+
F5: 96,
|
|
323
|
+
F6: 97,
|
|
324
|
+
F7: 98,
|
|
325
|
+
F8: 100,
|
|
326
|
+
F9: 101,
|
|
327
|
+
F10: 109,
|
|
328
|
+
F11: 103,
|
|
329
|
+
F12: 111,
|
|
330
|
+
});
|
|
331
|
+
const parseMacKey = (key) => {
|
|
332
|
+
const modifiers = [];
|
|
333
|
+
let index = 0;
|
|
334
|
+
for (; index < key.length; index += 1) {
|
|
335
|
+
const modifier = key[index];
|
|
336
|
+
if (modifier === "+")
|
|
337
|
+
modifiers.push("shift down");
|
|
338
|
+
else if (modifier === "^")
|
|
339
|
+
modifiers.push("control down");
|
|
340
|
+
else if (modifier === "%")
|
|
341
|
+
modifiers.push("option down");
|
|
342
|
+
else
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
const unit = key.slice(index);
|
|
346
|
+
const braced = /^\{([A-Z0-9]+)\}$/.exec(unit);
|
|
347
|
+
if (braced !== null) {
|
|
348
|
+
const keyCode = MAC_KEY_CODES[braced[1] ?? ""];
|
|
349
|
+
if (keyCode === undefined)
|
|
350
|
+
return undefined;
|
|
351
|
+
return { keyCode, ...(modifiers.length === 0 ? {} : { modifiers }) };
|
|
352
|
+
}
|
|
353
|
+
if (unit.length !== 1)
|
|
354
|
+
return undefined;
|
|
355
|
+
return { text: unit, ...(modifiers.length === 0 ? {} : { modifiers }) };
|
|
356
|
+
};
|
|
357
|
+
const parseMacScroll = (text) => {
|
|
358
|
+
const match = /^(up|down)(?::(\d+))?$/.exec(text);
|
|
359
|
+
if (match === null)
|
|
360
|
+
return undefined;
|
|
361
|
+
const lines = match[2] === undefined ? 3 : Number(match[2]);
|
|
362
|
+
if (lines < 1 || lines > 100)
|
|
363
|
+
return undefined;
|
|
364
|
+
return { up: match[1] === "up", lines };
|
|
365
|
+
};
|
|
366
|
+
export class MacosAccessibilityDriver {
|
|
367
|
+
driverId = "macos-ax";
|
|
368
|
+
driverVersion = "1.0.0";
|
|
369
|
+
command;
|
|
370
|
+
timeoutMs;
|
|
371
|
+
platform;
|
|
372
|
+
spawn;
|
|
373
|
+
sessions = new Map();
|
|
374
|
+
targets = new Map();
|
|
375
|
+
sessionTargetKeys = new Map();
|
|
376
|
+
latestObservationIds = new Map();
|
|
377
|
+
observedSessions = new Set();
|
|
378
|
+
nextSessionId = 1;
|
|
379
|
+
nextObservationId = 1;
|
|
380
|
+
constructor(options = {}) {
|
|
381
|
+
this.command = options.command ?? "osascript";
|
|
382
|
+
// 60s: Apple events from an SSH (Background launchd) session run ~100x
|
|
383
|
+
// slower than a GUI session — one full enumeration measured 38s there;
|
|
384
|
+
// GUI sessions complete in seconds and stay unaffected by the raise.
|
|
385
|
+
this.timeoutMs = options.timeoutMs ?? 60_000;
|
|
386
|
+
this.platform = options.platform ?? process.platform;
|
|
387
|
+
this.spawn =
|
|
388
|
+
options.spawnSync ??
|
|
389
|
+
((command, args, spawnOptions) => nodeSpawnSync(command, args, spawnOptions));
|
|
390
|
+
}
|
|
391
|
+
probe() {
|
|
392
|
+
if (this.platform !== "darwin") {
|
|
393
|
+
return {
|
|
394
|
+
available: false,
|
|
395
|
+
platform: this.platform,
|
|
396
|
+
driver: "macos-ax",
|
|
397
|
+
reason: "macOS Accessibility requires darwin",
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
const run = this.run(QUERY_SCRIPT);
|
|
401
|
+
if (run === undefined)
|
|
402
|
+
return {
|
|
403
|
+
available: false,
|
|
404
|
+
platform: this.platform,
|
|
405
|
+
driver: "macos-ax",
|
|
406
|
+
reason: "macOS Accessibility requires darwin",
|
|
407
|
+
};
|
|
408
|
+
if (run.failed)
|
|
409
|
+
return {
|
|
410
|
+
available: false,
|
|
411
|
+
platform: this.platform,
|
|
412
|
+
driver: "macos-ax",
|
|
413
|
+
reason: isPermissionReason(run.stderr) ? "accessibility-permission-required" : "osascript is unavailable",
|
|
414
|
+
};
|
|
415
|
+
const raw = parseJson(run.stdout);
|
|
416
|
+
if (raw === undefined)
|
|
417
|
+
return {
|
|
418
|
+
available: false,
|
|
419
|
+
platform: this.platform,
|
|
420
|
+
driver: "macos-ax",
|
|
421
|
+
reason: "osascript is unavailable",
|
|
422
|
+
};
|
|
423
|
+
if (raw.available === true)
|
|
424
|
+
return {
|
|
425
|
+
available: true,
|
|
426
|
+
platform: this.platform,
|
|
427
|
+
driver: "macos-ax",
|
|
428
|
+
capabilities: READONLY_CAPABILITIES,
|
|
429
|
+
};
|
|
430
|
+
const reason = typeof raw.reason === "string" && raw.reason.trim() !== "" ? raw.reason : "macOS Accessibility probe failed";
|
|
431
|
+
return { available: false, platform: this.platform, driver: "macos-ax", reason };
|
|
432
|
+
}
|
|
433
|
+
openSession(request) {
|
|
434
|
+
const probe = this.probe();
|
|
435
|
+
if (!probe.available)
|
|
436
|
+
return {
|
|
437
|
+
status: "unsupported",
|
|
438
|
+
error: error("platform_unavailable", probe.reason ?? "macOS Accessibility is unavailable", "platform-unavailable"),
|
|
439
|
+
};
|
|
440
|
+
const session = detachedFreeze({
|
|
441
|
+
version: 1,
|
|
442
|
+
sessionId: `desktop-macos-session-${this.nextSessionId++}`,
|
|
443
|
+
isolation: request.isolation,
|
|
444
|
+
status: "ready",
|
|
445
|
+
...(request.applicationId === undefined ? {} : { applicationId: request.applicationId }),
|
|
446
|
+
});
|
|
447
|
+
this.sessions.set(session.sessionId, session);
|
|
448
|
+
return { status: "succeeded", session };
|
|
449
|
+
}
|
|
450
|
+
observe(sessionId) {
|
|
451
|
+
const session = this.sessions.get(sessionId);
|
|
452
|
+
if (session === undefined)
|
|
453
|
+
return this.failed("observe", "session_not_found", "Session does not exist");
|
|
454
|
+
if (session.status === "stopped")
|
|
455
|
+
return this.cancelled("observe", "Session has been stopped");
|
|
456
|
+
const run = this.run(QUERY_SCRIPT);
|
|
457
|
+
const raw = run === undefined || run.failed ? undefined : parseJson(run.stdout);
|
|
458
|
+
if (raw === undefined || raw.available !== true) {
|
|
459
|
+
this.invalidateObservation(sessionId);
|
|
460
|
+
return unsupported("observe", "macOS Accessibility observation failed");
|
|
461
|
+
}
|
|
462
|
+
return this.toObservation(session, raw);
|
|
463
|
+
}
|
|
464
|
+
execute(request) {
|
|
465
|
+
const session = this.sessions.get(request.sessionId);
|
|
466
|
+
if (session === undefined)
|
|
467
|
+
return this.failed(request.operationId, "session_not_found", "Session does not exist");
|
|
468
|
+
if (session.status === "stopped")
|
|
469
|
+
return this.cancelled(request.operationId, "Session has been stopped");
|
|
470
|
+
if (requiresDesktopUserConfirmation(request.action.kind))
|
|
471
|
+
return {
|
|
472
|
+
version: 1,
|
|
473
|
+
operationId: request.operationId,
|
|
474
|
+
status: "needs_user",
|
|
475
|
+
summary: `User confirmation required for ${request.action.kind}`,
|
|
476
|
+
error: error("needs_user", `The ${request.action.kind} action is paused for the user`, request.action.kind),
|
|
477
|
+
};
|
|
478
|
+
if (!SUPPORTED_ACTIONS.includes(request.action.kind))
|
|
479
|
+
return unsupported(request.operationId, "The selected macOS Accessibility adapter does not support this action", "action-unsupported");
|
|
480
|
+
if (request.action.target === undefined)
|
|
481
|
+
return this.failed(request.operationId, "target_required", "This desktop action requires an observed target");
|
|
482
|
+
if (request.action.target.kind === "coordinates" && request.action.kind !== "click")
|
|
483
|
+
return this.failed(request.operationId, "coordinates_unsupported", "Coordinate targets are supported by the click action only");
|
|
484
|
+
if (request.action.kind === "type" && typeof request.action.text !== "string")
|
|
485
|
+
return this.failed(request.operationId, "text_required", "Type action requires text");
|
|
486
|
+
if (request.action.kind === "key" && typeof request.action.key !== "string")
|
|
487
|
+
return this.failed(request.operationId, "key_required", "Key action requires a key sequence");
|
|
488
|
+
if (request.action.kind === "scroll" && typeof request.action.text !== "string")
|
|
489
|
+
return this.failed(request.operationId, "direction_required", "Scroll action requires an up/down direction");
|
|
490
|
+
const postconditions = request.postconditions ?? [];
|
|
491
|
+
if (postconditions.length > 0 && request.reobserveAfterAction === false)
|
|
492
|
+
return this.failed(request.operationId, "reobserve_required", "Postconditions require an action re-observation");
|
|
493
|
+
const payload = this.actionPayload(request);
|
|
494
|
+
if (payload === undefined)
|
|
495
|
+
return this.failed(request.operationId, payloadErrorCode(request.action), payloadErrorMessage(request.action));
|
|
496
|
+
const raw = this.run(ACTION_SCRIPT, payload);
|
|
497
|
+
if (raw === undefined || raw.failed)
|
|
498
|
+
return unsupported(request.operationId, "macOS Accessibility action failed");
|
|
499
|
+
const parsed = parseJson(raw.stdout);
|
|
500
|
+
if (parsed === undefined)
|
|
501
|
+
return unsupported(request.operationId, "macOS Accessibility action failed");
|
|
502
|
+
if (parsed.ok !== true) {
|
|
503
|
+
if (parsed.code === "stale_target") {
|
|
504
|
+
this.invalidateObservation(request.sessionId);
|
|
505
|
+
return this.failed(request.operationId, "stale_target", "Target identity is stale; reobserve the desktop before retrying");
|
|
506
|
+
}
|
|
507
|
+
const causeCode = typeof parsed.reason === "string" &&
|
|
508
|
+
["focus_failed", "press_unavailable", "coordinate_click_failed", "ax_action_exception"].includes(parsed.reason)
|
|
509
|
+
? parsed.reason
|
|
510
|
+
: "ax_action_exception";
|
|
511
|
+
return this.failed(request.operationId, "action_failed", "Accessibility action failed", causeCode);
|
|
512
|
+
}
|
|
513
|
+
const observation = request.reobserveAfterAction === false ? undefined : this.observe(request.sessionId);
|
|
514
|
+
const verification = this.verifyPostconditions(postconditions, observation);
|
|
515
|
+
if (postconditions.length > 0 && verification.status === "unknown")
|
|
516
|
+
return {
|
|
517
|
+
version: 1,
|
|
518
|
+
operationId: request.operationId,
|
|
519
|
+
status: "failed",
|
|
520
|
+
summary: "Desktop action completed but could not be re-observed",
|
|
521
|
+
verification,
|
|
522
|
+
error: error("verification_unavailable", "The requested desktop state could not be re-observed", "reobserve-failed"),
|
|
523
|
+
};
|
|
524
|
+
if (verification.status === "failed")
|
|
525
|
+
return {
|
|
526
|
+
version: 1,
|
|
527
|
+
operationId: request.operationId,
|
|
528
|
+
status: "failed",
|
|
529
|
+
summary: "Desktop action completed but a postcondition failed",
|
|
530
|
+
...(observation !== undefined && "observationId" in observation ? { observation } : {}),
|
|
531
|
+
verification,
|
|
532
|
+
error: error("postcondition_failed", "The requested desktop state was not observed", "verification-failed"),
|
|
533
|
+
};
|
|
534
|
+
return {
|
|
535
|
+
version: 1,
|
|
536
|
+
operationId: request.operationId,
|
|
537
|
+
status: "succeeded",
|
|
538
|
+
summary: "Desktop action completed through macOS Accessibility",
|
|
539
|
+
...(observation !== undefined && "observationId" in observation ? { observation } : {}),
|
|
540
|
+
verification,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
stop(sessionId) {
|
|
544
|
+
const session = this.sessions.get(sessionId);
|
|
545
|
+
if (session === undefined)
|
|
546
|
+
return this.failed("stop", "session_not_found", "Session does not exist");
|
|
547
|
+
const stopped = detachedFreeze({ ...session, status: "stopped" });
|
|
548
|
+
this.sessions.set(sessionId, stopped);
|
|
549
|
+
this.invalidateObservation(sessionId);
|
|
550
|
+
return stopped;
|
|
551
|
+
}
|
|
552
|
+
actionPayload(request) {
|
|
553
|
+
const target = request.action.target;
|
|
554
|
+
if (target === undefined)
|
|
555
|
+
return undefined;
|
|
556
|
+
const resolvedTarget = target.kind === "coordinates"
|
|
557
|
+
? { coordinates: { x: target.x, y: target.y } }
|
|
558
|
+
: (this.resolveTarget(request.sessionId, target) ?? undefined);
|
|
559
|
+
if (resolvedTarget === undefined)
|
|
560
|
+
return undefined;
|
|
561
|
+
if (request.action.kind === "key") {
|
|
562
|
+
const key = parseMacKey(request.action.key ?? "");
|
|
563
|
+
if (key === undefined)
|
|
564
|
+
return undefined;
|
|
565
|
+
return { kind: request.action.kind, target: resolvedTarget, key };
|
|
566
|
+
}
|
|
567
|
+
if (request.action.kind === "scroll") {
|
|
568
|
+
const scroll = parseMacScroll(request.action.text ?? "");
|
|
569
|
+
if (scroll === undefined)
|
|
570
|
+
return undefined;
|
|
571
|
+
return { kind: request.action.kind, target: resolvedTarget, scroll };
|
|
572
|
+
}
|
|
573
|
+
return {
|
|
574
|
+
kind: request.action.kind,
|
|
575
|
+
target: resolvedTarget,
|
|
576
|
+
...(request.action.text === undefined ? {} : { text: request.action.text }),
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
run(script, payload) {
|
|
580
|
+
if (this.platform !== "darwin")
|
|
581
|
+
return undefined;
|
|
582
|
+
const env = {};
|
|
583
|
+
for (const key of ["PATH", "HOME", "TMPDIR", "USER", "LOGNAME"]) {
|
|
584
|
+
const value = process.env[key];
|
|
585
|
+
if (value !== undefined)
|
|
586
|
+
env[key] = value;
|
|
587
|
+
}
|
|
588
|
+
if (payload !== undefined)
|
|
589
|
+
env.AGENT_FORGE_MACOS_AX_INPUT = JSON.stringify(payload);
|
|
590
|
+
const result = this.spawn(this.command, ["-l", "JavaScript", "-e", script], {
|
|
591
|
+
encoding: "utf8",
|
|
592
|
+
timeout: this.timeoutMs,
|
|
593
|
+
windowsHide: true,
|
|
594
|
+
env,
|
|
595
|
+
});
|
|
596
|
+
if (result.error !== undefined || result.status !== 0)
|
|
597
|
+
return { stdout: result.stdout, stderr: result.stderr ?? "", failed: true };
|
|
598
|
+
return { stdout: result.stdout, stderr: result.stderr ?? "", failed: false };
|
|
599
|
+
}
|
|
600
|
+
toObservation(session, raw) {
|
|
601
|
+
this.invalidateObservation(session.sessionId);
|
|
602
|
+
const observationId = `desktop-macos-observation-${this.nextObservationId++}`;
|
|
603
|
+
const targetKeys = new Set();
|
|
604
|
+
this.sessionTargetKeys.set(session.sessionId, targetKeys);
|
|
605
|
+
const windows = [];
|
|
606
|
+
for (const item of raw.windows ?? []) {
|
|
607
|
+
if (typeof item.id !== "string" || item.id === "")
|
|
608
|
+
continue;
|
|
609
|
+
const windowId = item.id;
|
|
610
|
+
windows.push({
|
|
611
|
+
version: 1,
|
|
612
|
+
windowId,
|
|
613
|
+
appId: `process:${Number(item.processId ?? 0)}`,
|
|
614
|
+
title: item.name ?? "",
|
|
615
|
+
bounds: rectangle(item),
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
const visibleWindows = session.applicationId === undefined
|
|
619
|
+
? windows
|
|
620
|
+
: windows.filter((window) => window.appId === session.applicationId);
|
|
621
|
+
for (const window of visibleWindows) {
|
|
622
|
+
const key = this.targetKey(session.sessionId, "window", window.windowId);
|
|
623
|
+
this.targets.set(key, { sessionId: session.sessionId, observationId, windowId: window.windowId });
|
|
624
|
+
targetKeys.add(key);
|
|
625
|
+
}
|
|
626
|
+
const controls = [];
|
|
627
|
+
for (const item of raw.controls ?? []) {
|
|
628
|
+
if (typeof item.windowId !== "string" || !visibleWindows.some((window) => window.windowId === item.windowId))
|
|
629
|
+
continue;
|
|
630
|
+
const controlId = item.controlId ?? "";
|
|
631
|
+
const key = this.targetKey(session.sessionId, "control", controlId);
|
|
632
|
+
this.targets.set(key, {
|
|
633
|
+
sessionId: session.sessionId,
|
|
634
|
+
observationId,
|
|
635
|
+
windowId: item.windowId,
|
|
636
|
+
controlId,
|
|
637
|
+
});
|
|
638
|
+
targetKeys.add(key);
|
|
639
|
+
controls.push({
|
|
640
|
+
version: 1,
|
|
641
|
+
controlId,
|
|
642
|
+
windowId: item.windowId,
|
|
643
|
+
role: item.role ?? "",
|
|
644
|
+
name: item.name ?? "",
|
|
645
|
+
bounds: rectangle(item),
|
|
646
|
+
enabled: item.enabled !== false,
|
|
647
|
+
...(typeof item.value === "string" && item.value.length > 0 ? { value: item.value } : {}),
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
const focusedControlId = typeof raw.focusedControlId === "string" &&
|
|
651
|
+
controls.some((control) => control.controlId === raw.focusedControlId)
|
|
652
|
+
? raw.focusedControlId
|
|
653
|
+
: undefined;
|
|
654
|
+
const observation = detachedFreeze({
|
|
655
|
+
version: 1,
|
|
656
|
+
sessionId: session.sessionId,
|
|
657
|
+
observationId,
|
|
658
|
+
source: "macos-ax",
|
|
659
|
+
windows: visibleWindows,
|
|
660
|
+
controls,
|
|
661
|
+
...(focusedControlId !== undefined ? { focusedControlId } : {}),
|
|
662
|
+
});
|
|
663
|
+
this.latestObservationIds.set(session.sessionId, observationId);
|
|
664
|
+
this.observedSessions.add(session.sessionId);
|
|
665
|
+
return observation;
|
|
666
|
+
}
|
|
667
|
+
resolveTarget(sessionId, target) {
|
|
668
|
+
const identity = target.kind === "control" ? target.controlId : target.windowId;
|
|
669
|
+
const record = this.targets.get(this.targetKey(sessionId, target.kind, identity));
|
|
670
|
+
if (record === undefined ||
|
|
671
|
+
record.sessionId !== sessionId ||
|
|
672
|
+
record.observationId !== this.latestObservationIds.get(sessionId))
|
|
673
|
+
return undefined;
|
|
674
|
+
return {
|
|
675
|
+
windowId: record.windowId,
|
|
676
|
+
...(record.controlId === undefined ? {} : { controlId: record.controlId }),
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
targetKey(sessionId, kind, identity) {
|
|
680
|
+
return `${sessionId}:${kind}:${identity}`;
|
|
681
|
+
}
|
|
682
|
+
invalidateObservation(sessionId) {
|
|
683
|
+
for (const key of this.sessionTargetKeys.get(sessionId) ?? [])
|
|
684
|
+
this.targets.delete(key);
|
|
685
|
+
this.sessionTargetKeys.delete(sessionId);
|
|
686
|
+
this.latestObservationIds.delete(sessionId);
|
|
687
|
+
}
|
|
688
|
+
verifyPostconditions(postconditions, observation) {
|
|
689
|
+
if (postconditions.length === 0)
|
|
690
|
+
return { status: "passed", checks: [] };
|
|
691
|
+
if (observation === undefined || !("observationId" in observation))
|
|
692
|
+
return { status: "unknown", checks: ["observation-unavailable"] };
|
|
693
|
+
const checks = [];
|
|
694
|
+
for (const condition of postconditions) {
|
|
695
|
+
const passed = this.checkPostcondition(condition, observation);
|
|
696
|
+
checks.push(`${condition.kind}:${passed ? "passed" : "failed"}`);
|
|
697
|
+
if (!passed)
|
|
698
|
+
return { status: "failed", checks };
|
|
699
|
+
}
|
|
700
|
+
return { status: "passed", checks };
|
|
701
|
+
}
|
|
702
|
+
checkPostcondition(condition, observation) {
|
|
703
|
+
if (condition.kind === "window-exists")
|
|
704
|
+
return observation.windows.some((window) => window.windowId === condition.windowId);
|
|
705
|
+
if (condition.kind === "control-exists")
|
|
706
|
+
return observation.controls.some((control) => control.controlId === condition.controlId);
|
|
707
|
+
if (condition.kind === "focused")
|
|
708
|
+
return observation.focusedControlId === condition.controlId;
|
|
709
|
+
const target = condition.target;
|
|
710
|
+
if (target.kind === "window") {
|
|
711
|
+
const window = observation.windows.find((item) => item.windowId === target.windowId);
|
|
712
|
+
if (window === undefined || condition.property !== "name")
|
|
713
|
+
return false;
|
|
714
|
+
return window.title === condition.expected;
|
|
715
|
+
}
|
|
716
|
+
// A coordinate target identifies no observed control, so it can never
|
|
717
|
+
// satisfy a property-equals postcondition.
|
|
718
|
+
if (target.kind === "coordinates")
|
|
719
|
+
return false;
|
|
720
|
+
const control = observation.controls.find((item) => {
|
|
721
|
+
if (item.controlId !== target.controlId)
|
|
722
|
+
return false;
|
|
723
|
+
return target.windowId === undefined || item.windowId === target.windowId;
|
|
724
|
+
});
|
|
725
|
+
if (control === undefined)
|
|
726
|
+
return false;
|
|
727
|
+
if (condition.property === "enabled")
|
|
728
|
+
return control.enabled === condition.expected;
|
|
729
|
+
if (condition.property === "name")
|
|
730
|
+
return control.name === condition.expected;
|
|
731
|
+
return control.value === condition.expected;
|
|
732
|
+
}
|
|
733
|
+
failed(operationId, code, message, causeCode) {
|
|
734
|
+
return { version: 1, operationId, status: "failed", summary: message, error: error(code, message, causeCode) };
|
|
735
|
+
}
|
|
736
|
+
cancelled(operationId, message) {
|
|
737
|
+
return {
|
|
738
|
+
version: 1,
|
|
739
|
+
operationId,
|
|
740
|
+
status: "cancelled",
|
|
741
|
+
summary: message,
|
|
742
|
+
error: error("cancelled", message, "stopped"),
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
const payloadErrorCode = (action) => {
|
|
747
|
+
if (action.kind === "key")
|
|
748
|
+
return "key_unsupported";
|
|
749
|
+
if (action.kind === "scroll")
|
|
750
|
+
return "direction_invalid";
|
|
751
|
+
return "target_not_found";
|
|
752
|
+
};
|
|
753
|
+
const payloadErrorMessage = (action) => {
|
|
754
|
+
if (action.kind === "key")
|
|
755
|
+
return "Key sequence does not match the supported macOS key grammar";
|
|
756
|
+
if (action.kind === "scroll")
|
|
757
|
+
return "Scroll direction must be up or down with optional :lines";
|
|
758
|
+
return "Target identity was not observed by macOS Accessibility";
|
|
759
|
+
};
|
|
760
|
+
/**
|
|
761
|
+
* The production input facade intentionally exposes no replay method. The
|
|
762
|
+
* driver remains responsible for using observed Accessibility identities
|
|
763
|
+
* (or explicit click coordinates) rather than global keyboard injection.
|
|
764
|
+
*/
|
|
765
|
+
export class DesktopMacosInputAdapter {
|
|
766
|
+
adapterId = "desktop-macos-ax";
|
|
767
|
+
adapterVersion = "1.0.0";
|
|
768
|
+
driver;
|
|
769
|
+
constructor(driver = new MacosAccessibilityDriver()) {
|
|
770
|
+
this.driver = driver;
|
|
771
|
+
}
|
|
772
|
+
probe() {
|
|
773
|
+
return this.driver.probe();
|
|
774
|
+
}
|
|
775
|
+
openSession(request) {
|
|
776
|
+
return this.driver.openSession(request);
|
|
777
|
+
}
|
|
778
|
+
observe(sessionId) {
|
|
779
|
+
return this.driver.observe(sessionId);
|
|
780
|
+
}
|
|
781
|
+
reobserve(sessionId) {
|
|
782
|
+
return this.driver.observe(sessionId);
|
|
783
|
+
}
|
|
784
|
+
execute(request) {
|
|
785
|
+
if (requiresDesktopUserConfirmation(request.action.kind)) {
|
|
786
|
+
return {
|
|
787
|
+
version: 1,
|
|
788
|
+
operationId: request.operationId,
|
|
789
|
+
status: "needs_user",
|
|
790
|
+
summary: `User confirmation required for ${request.action.kind}`,
|
|
791
|
+
error: {
|
|
792
|
+
version: 1,
|
|
793
|
+
code: "needs_user",
|
|
794
|
+
message: `The ${request.action.kind} action is paused for the user`,
|
|
795
|
+
causeCode: request.action.kind,
|
|
796
|
+
},
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
return this.driver.execute(request);
|
|
800
|
+
}
|
|
801
|
+
stop(sessionId) {
|
|
802
|
+
return this.driver.stop(sessionId);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
//# sourceMappingURL=macos-ax-adapter.js.map
|