@ai-agent-forge/plugin-desktop-macos-observe 0.88.3

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.
@@ -0,0 +1,342 @@
1
+ /**
2
+ * macOS Accessibility read-only driver and adapter facade for the observe
3
+ * package. Structurally mirrors the Windows package's `windows-uia-adapter.ts`
4
+ * (inlined from agent-forge `src/plugins/desktop/windows-uia-adapter.ts`,
5
+ * D-075 S3) with these deliberate differences:
6
+ * - Driver boundary is `osascript -l JavaScript -e <script>` (JXA) instead of
7
+ * PowerShell UIA; the query script walks the System Events accessibility
8
+ * tree (`Application("com.apple.systemevents")` processes/windows/entireContents) and
9
+ * prints one JSON document to stdout.
10
+ * - This observe copy carries the query script only: the class implements
11
+ * `DesktopMacosReadonlyDriverV1` and the facade implements
12
+ * `DesktopReadonlyAdapterV1` — there is deliberately no execute surface.
13
+ * - Non-darwin platforms never throw at import or call time: probe reports
14
+ * `{available: false, reason: "macOS Accessibility requires darwin"}` and
15
+ * every tool returns an explicit `unsupported` result (same fail-closed
16
+ * contract as the Windows package off win32).
17
+ * - Missing Accessibility (assistive access) permission is reported
18
+ * fail-closed as `accessibility-permission-required`; no degraded mode.
19
+ * This file is a deliberate copy shared (not imported) with the input
20
+ * package's adapter; the input copy carries the action script and execute.
21
+ */
22
+ import { spawnSync as nodeSpawnSync } from "node:child_process";
23
+ import { detachedFreeze } from "@agent-forge/plugin-sdk";
24
+ const READONLY_CAPABILITIES = ["desktop-session", "desktop-uia"];
25
+ const error = (code, message, causeCode) => ({
26
+ version: 1,
27
+ code,
28
+ message,
29
+ ...(causeCode === undefined ? {} : { causeCode }),
30
+ });
31
+ const unsupported = (operationId, reason) => ({
32
+ version: 1,
33
+ operationId,
34
+ status: "unsupported",
35
+ summary: reason,
36
+ error: error("platform_unavailable", reason, "platform-unavailable"),
37
+ });
38
+ const rectangle = (raw) => ({
39
+ x: Number.isFinite(raw.x) ? Number(raw.x) : 0,
40
+ y: Number.isFinite(raw.y) ? Number(raw.y) : 0,
41
+ width: Number.isFinite(raw.width) ? Number(raw.width) : 0,
42
+ height: Number.isFinite(raw.height) ? Number(raw.height) : 0,
43
+ });
44
+ const isPermissionReason = (value) => /assistive access|not authorized|accessibility-permission-required/i.test(value);
45
+ /**
46
+ * JXA observation script. Enumerates every non-background System Events
47
+ * process's windows (caps: 64 windows, 256 controls total, mirroring the
48
+ * projection budgets) with role/name/position/size/enabled/value per control,
49
+ * plus the frontmost process's focused element matched by role and frame.
50
+ * Permission or automation failures are reported in-band as
51
+ * `{available:false, reason:...}` so the probe can classify them; the reason
52
+ * literal `accessibility-permission-required` is reserved for missing
53
+ * Accessibility (assistive access) grants.
54
+ */
55
+ const QUERY_SCRIPT = `
56
+ var se = Application('com.apple.systemevents');
57
+ function safe(fn) { try { return fn(); } catch (e) { return undefined; } }
58
+ function rectOf(el) {
59
+ var p = safe(function() { return el.position(); });
60
+ var z = safe(function() { return el.size(); });
61
+ if (p === undefined || z === undefined || p.length < 2 || z.length < 2) return {x: 0, y: 0, width: 0, height: 0};
62
+ return {x: Math.round(p[0]), y: Math.round(p[1]), width: Math.round(z[0]), height: Math.round(z[1])};
63
+ }
64
+ function axReason(e) {
65
+ var msg = String(e && e.message !== undefined ? e.message : e);
66
+ var num = e && typeof e.errorNumber === 'number' ? e.errorNumber : 0;
67
+ if (msg.indexOf('assistive access') !== -1 || msg.indexOf('not authorized') !== -1 ||
68
+ num === -25211 || num === -1719 || num === -1743) return 'accessibility-permission-required';
69
+ return msg;
70
+ }
71
+ try {
72
+ var out = {available: true, windows: [], controls: [], focusedControlId: null};
73
+ var procs = se.processes();
74
+ var focused = null;
75
+ for (var fi = 0; fi < procs.length; fi++) {
76
+ var fp = procs[fi];
77
+ if (safe(function() { return fp.frontmost(); }) !== true) continue;
78
+ var fe = safe(function() { return fp.attributes['AXFocusedUIElement'].value(); });
79
+ if (fe !== undefined) focused = {role: safe(function() { return fe.role(); }), rect: rectOf(fe)};
80
+ break;
81
+ }
82
+ var windowCount = 0;
83
+ var controlCount = 0;
84
+ for (var pi = 0; pi < procs.length && windowCount < 64; pi++) {
85
+ var proc = procs[pi];
86
+ if (safe(function() { return proc.backgroundOnly(); }) === true) continue;
87
+ var wins = safe(function() { return proc.windows(); });
88
+ if (wins === undefined || wins.length === 0) continue;
89
+ var pid = safe(function() { return proc.unixId(); });
90
+ var pname = safe(function() { return proc.name(); });
91
+ if (typeof pname !== 'string' || pname.length === 0) pname = 'unknown';
92
+ for (var wi = 0; wi < wins.length && windowCount < 64; wi++) {
93
+ var win = wins[wi];
94
+ var winRect = rectOf(win);
95
+ var winId = 'window-' + pid + '-' + wi;
96
+ windowCount++;
97
+ out.windows.push({id: winId, processId: pid, processName: pname,
98
+ name: safe(function() { return win.name(); }) || '', x: winRect.x, y: winRect.y,
99
+ width: winRect.width, height: winRect.height});
100
+ var contents = safe(function() { return win.entireContents(); });
101
+ if (contents === undefined) continue;
102
+ for (var ci = 0; ci < contents.length && controlCount < 256; ci++) {
103
+ var el = contents[ci];
104
+ var role = safe(function() { return el.role(); });
105
+ if (typeof role !== 'string' || role.length === 0) continue;
106
+ var r = rectOf(el);
107
+ var controlId = winId + ':' + role + ':' + ci;
108
+ var value = safe(function() { return el.value(); });
109
+ out.controls.push({windowId: winId, controlId: controlId, role: role,
110
+ name: safe(function() { return el.name(); }) || safe(function() { return el.title(); }) || '',
111
+ enabled: safe(function() { return el.enabled(); }) !== false,
112
+ value: typeof value === 'string' && value.length > 0 ? value : undefined,
113
+ x: r.x, y: r.y, width: r.width, height: r.height});
114
+ controlCount++;
115
+ if (focused !== null && out.focusedControlId === null && role === focused.role &&
116
+ r.x === focused.rect.x && r.y === focused.rect.y &&
117
+ r.width === focused.rect.width && r.height === focused.rect.height) {
118
+ out.focusedControlId = controlId;
119
+ }
120
+ }
121
+ }
122
+ }
123
+ JSON.stringify(out);
124
+ } catch (e) {
125
+ JSON.stringify({available: false, reason: axReason(e)});
126
+ }`;
127
+ const parseJson = (stdout) => {
128
+ try {
129
+ const parsed = JSON.parse(stdout.trim());
130
+ return typeof parsed === "object" && parsed !== null ? parsed : undefined;
131
+ }
132
+ catch {
133
+ return undefined;
134
+ }
135
+ };
136
+ export class MacosAccessibilityDriver {
137
+ driverId = "macos-ax";
138
+ driverVersion = "1.0.0";
139
+ command;
140
+ timeoutMs;
141
+ platform;
142
+ spawn;
143
+ sessions = new Map();
144
+ nextSessionId = 1;
145
+ nextObservationId = 1;
146
+ constructor(options = {}) {
147
+ this.command = options.command ?? "osascript";
148
+ // 60s: Apple events from an SSH (Background launchd) session run ~100x
149
+ // slower than a GUI session — one full enumeration measured 38s there;
150
+ // GUI sessions complete in seconds and stay unaffected by the raise.
151
+ this.timeoutMs = options.timeoutMs ?? 60_000;
152
+ this.platform = options.platform ?? process.platform;
153
+ this.spawn =
154
+ options.spawnSync ??
155
+ ((command, args, spawnOptions) => nodeSpawnSync(command, args, spawnOptions));
156
+ }
157
+ probe() {
158
+ if (this.platform !== "darwin") {
159
+ return {
160
+ available: false,
161
+ platform: this.platform,
162
+ driver: "macos-ax",
163
+ reason: "macOS Accessibility requires darwin",
164
+ };
165
+ }
166
+ const run = this.run(QUERY_SCRIPT);
167
+ if (run === undefined)
168
+ return {
169
+ available: false,
170
+ platform: this.platform,
171
+ driver: "macos-ax",
172
+ reason: "macOS Accessibility requires darwin",
173
+ };
174
+ if (run.failed)
175
+ return {
176
+ available: false,
177
+ platform: this.platform,
178
+ driver: "macos-ax",
179
+ reason: isPermissionReason(run.stderr) ? "accessibility-permission-required" : "osascript is unavailable",
180
+ };
181
+ const raw = parseJson(run.stdout);
182
+ if (raw === undefined)
183
+ return {
184
+ available: false,
185
+ platform: this.platform,
186
+ driver: "macos-ax",
187
+ reason: "osascript is unavailable",
188
+ };
189
+ if (raw.available === true)
190
+ return {
191
+ available: true,
192
+ platform: this.platform,
193
+ driver: "macos-ax",
194
+ capabilities: READONLY_CAPABILITIES,
195
+ };
196
+ const reason = typeof raw.reason === "string" && raw.reason.trim() !== "" ? raw.reason : "macOS Accessibility probe failed";
197
+ return { available: false, platform: this.platform, driver: "macos-ax", reason };
198
+ }
199
+ openSession(request) {
200
+ const probe = this.probe();
201
+ if (!probe.available)
202
+ return {
203
+ status: "unsupported",
204
+ error: error("platform_unavailable", probe.reason ?? "macOS Accessibility is unavailable", "platform-unavailable"),
205
+ };
206
+ const session = detachedFreeze({
207
+ version: 1,
208
+ sessionId: `desktop-macos-session-${this.nextSessionId++}`,
209
+ isolation: request.isolation,
210
+ status: "ready",
211
+ ...(request.applicationId === undefined ? {} : { applicationId: request.applicationId }),
212
+ });
213
+ this.sessions.set(session.sessionId, session);
214
+ return { status: "succeeded", session };
215
+ }
216
+ observe(sessionId) {
217
+ const session = this.sessions.get(sessionId);
218
+ if (session === undefined)
219
+ return this.failed("observe", "session_not_found", "Session does not exist");
220
+ if (session.status === "stopped")
221
+ return this.cancelled("observe", "Session has been stopped");
222
+ const run = this.run(QUERY_SCRIPT);
223
+ const raw = run === undefined || run.failed ? undefined : parseJson(run.stdout);
224
+ if (raw === undefined || raw.available !== true)
225
+ return unsupported("observe", "macOS Accessibility observation failed");
226
+ return this.toObservation(session, raw);
227
+ }
228
+ stop(sessionId) {
229
+ const session = this.sessions.get(sessionId);
230
+ if (session === undefined)
231
+ return this.failed("stop", "session_not_found", "Session does not exist");
232
+ const stopped = detachedFreeze({ ...session, status: "stopped" });
233
+ this.sessions.set(sessionId, stopped);
234
+ return stopped;
235
+ }
236
+ run(script, payload) {
237
+ if (this.platform !== "darwin")
238
+ return undefined;
239
+ const env = {};
240
+ for (const key of ["PATH", "HOME", "TMPDIR", "USER", "LOGNAME"]) {
241
+ const value = process.env[key];
242
+ if (value !== undefined)
243
+ env[key] = value;
244
+ }
245
+ if (payload !== undefined)
246
+ env.AGENT_FORGE_MACOS_AX_INPUT = JSON.stringify(payload);
247
+ const result = this.spawn(this.command, ["-l", "JavaScript", "-e", script], {
248
+ encoding: "utf8",
249
+ timeout: this.timeoutMs,
250
+ windowsHide: true,
251
+ env,
252
+ });
253
+ if (result.error !== undefined || result.status !== 0)
254
+ return { stdout: result.stdout, stderr: result.stderr ?? "", failed: true };
255
+ return { stdout: result.stdout, stderr: result.stderr ?? "", failed: false };
256
+ }
257
+ toObservation(session, raw) {
258
+ const observationId = `desktop-macos-observation-${this.nextObservationId++}`;
259
+ const windows = [];
260
+ for (const item of raw.windows ?? []) {
261
+ if (typeof item.id !== "string" || item.id === "")
262
+ continue;
263
+ windows.push({
264
+ version: 1,
265
+ windowId: item.id,
266
+ appId: `process:${Number(item.processId ?? 0)}`,
267
+ title: item.name ?? "",
268
+ bounds: rectangle(item),
269
+ });
270
+ }
271
+ const visibleWindows = session.applicationId === undefined
272
+ ? windows
273
+ : windows.filter((window) => window.appId === session.applicationId);
274
+ const visibleWindowIds = new Set(visibleWindows.map((window) => window.windowId));
275
+ const controls = [];
276
+ for (const item of raw.controls ?? []) {
277
+ if (typeof item.windowId !== "string" || !visibleWindowIds.has(item.windowId))
278
+ continue;
279
+ controls.push({
280
+ version: 1,
281
+ controlId: item.controlId ?? "",
282
+ windowId: item.windowId,
283
+ role: item.role ?? "",
284
+ name: item.name ?? "",
285
+ bounds: rectangle(item),
286
+ enabled: item.enabled !== false,
287
+ ...(typeof item.value === "string" && item.value.length > 0 ? { value: item.value } : {}),
288
+ });
289
+ }
290
+ const focusedControlId = typeof raw.focusedControlId === "string" &&
291
+ controls.some((control) => control.controlId === raw.focusedControlId)
292
+ ? raw.focusedControlId
293
+ : undefined;
294
+ return detachedFreeze({
295
+ version: 1,
296
+ sessionId: session.sessionId,
297
+ observationId,
298
+ source: "macos-ax",
299
+ windows: visibleWindows,
300
+ controls,
301
+ ...(focusedControlId !== undefined ? { focusedControlId } : {}),
302
+ });
303
+ }
304
+ failed(operationId, code, message, causeCode) {
305
+ return { version: 1, operationId, status: "failed", summary: message, error: error(code, message, causeCode) };
306
+ }
307
+ cancelled(operationId, message) {
308
+ return {
309
+ version: 1,
310
+ operationId,
311
+ status: "cancelled",
312
+ summary: message,
313
+ error: error("cancelled", message, "stopped"),
314
+ };
315
+ }
316
+ }
317
+ /** Read-only adapter facade over the macOS Accessibility driver. */
318
+ export class DesktopMacosAdapter {
319
+ adapterId = "desktop-macos-ax";
320
+ adapterVersion = "1.0.0";
321
+ capabilities = READONLY_CAPABILITIES;
322
+ driver;
323
+ constructor(driver = new MacosAccessibilityDriver()) {
324
+ this.driver = driver;
325
+ }
326
+ probe() {
327
+ return this.driver.probe();
328
+ }
329
+ openSession(request) {
330
+ return this.driver.openSession(request);
331
+ }
332
+ observe(sessionId) {
333
+ return this.driver.observe(sessionId);
334
+ }
335
+ reobserve(sessionId) {
336
+ return this.driver.observe(sessionId);
337
+ }
338
+ stop(sessionId) {
339
+ return this.driver.stop(sessionId);
340
+ }
341
+ }
342
+ //# sourceMappingURL=macos-ax-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"macos-ax-adapter.js","sourceRoot":"","sources":["../src/macos-ax-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AA0EzD,MAAM,qBAAqB,GAAqC,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;AAEnG,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,OAAe,EAAE,SAAkB,EAAkB,EAAE,CAAC,CAAC;IACrF,OAAO,EAAE,CAAC;IACV,IAAI;IACJ,OAAO;IACP,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC;CACjD,CAAC,CAAC;AAEH,MAAM,WAAW,GAAG,CAAC,WAAmB,EAAE,MAAc,EAA4B,EAAE,CAAC,CAAC;IACvF,OAAO,EAAE,CAAC;IACV,WAAW;IACX,MAAM,EAAE,aAAa;IACrB,OAAO,EAAE,MAAM;IACf,KAAK,EAAE,KAAK,CAAC,sBAAsB,EAAE,MAAM,EAAE,sBAAsB,CAAC;CACpE,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,CAAC,GAAgE,EAAE,EAAE,CAAC,CAAC;IACxF,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7C,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACzD,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;CAC5D,CAAC,CAAC;AAEH,MAAM,kBAAkB,GAAG,CAAC,KAAa,EAAW,EAAE,CACrD,oEAAoE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAElF;;;;;;;;;GASG;AACH,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuEnB,CAAC;AAEH,MAAM,SAAS,GAAG,CAAC,MAAc,EAAuC,EAAE,CAAC;IAC1E,IAAI,CAAC;QACJ,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QAClD,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,CAAC,CAAC,CAAE,MAAkC,CAAC,CAAC,CAAC,SAAS,CAAC;IACxG,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;AAAA,CACD,CAAC;AAEF,MAAM,OAAO,wBAAwB;IAC3B,QAAQ,GAAG,UAAU,CAAC;IACtB,aAAa,GAAG,OAAO,CAAC;IAChB,OAAO,CAAS;IAChB,SAAS,CAAS;IAClB,QAAQ,CAAkB;IAC1B,KAAK,CAAa;IAClB,QAAQ,GAAG,IAAI,GAAG,EAA4B,CAAC;IACxD,aAAa,GAAG,CAAC,CAAC;IAClB,iBAAiB,GAAG,CAAC,CAAC;IAE9B,YAAY,OAAO,GAAoC,EAAE,EAAE;QAC1D,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,WAAW,CAAC;QAC9C,uEAAuE;QACvE,yEAAuE;QACvE,qEAAqE;QACrE,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC;QAC7C,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC;QACrD,IAAI,CAAC,KAAK;YACT,OAAO,CAAC,SAAS;gBACjB,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,EAAE,YAAY,CAAgC,CAAC,CAAC;IAAA,CAC9G;IAED,KAAK,GAAwB;QAC5B,IAAI,IAAI,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAChC,OAAO;gBACN,SAAS,EAAE,KAAK;gBAChB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,MAAM,EAAE,UAAU;gBAClB,MAAM,EAAE,qCAAqC;aAC7C,CAAC;QACH,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACnC,IAAI,GAAG,KAAK,SAAS;YACpB,OAAO;gBACN,SAAS,EAAE,KAAK;gBAChB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,MAAM,EAAE,UAAU;gBAClB,MAAM,EAAE,qCAAqC;aAC7C,CAAC;QACH,IAAI,GAAG,CAAC,MAAM;YACb,OAAO;gBACN,SAAS,EAAE,KAAK;gBAChB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,MAAM,EAAE,UAAU;gBAClB,MAAM,EAAE,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,mCAAmC,CAAC,CAAC,CAAC,0BAA0B;aACzG,CAAC;QACH,MAAM,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAClC,IAAI,GAAG,KAAK,SAAS;YACpB,OAAO;gBACN,SAAS,EAAE,KAAK;gBAChB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,MAAM,EAAE,UAAU;gBAClB,MAAM,EAAE,0BAA0B;aAClC,CAAC;QACH,IAAI,GAAG,CAAC,SAAS,KAAK,IAAI;YACzB,OAAO;gBACN,SAAS,EAAE,IAAI;gBACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,MAAM,EAAE,UAAU;gBAClB,YAAY,EAAE,qBAAqB;aACnC,CAAC;QACH,MAAM,MAAM,GACX,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,kCAAkC,CAAC;QAC9G,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;IAAA,CACjF;IAED,WAAW,CAAC,OAAgC,EAA0B;QACrE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;QAC3B,IAAI,CAAC,KAAK,CAAC,SAAS;YACnB,OAAO;gBACN,MAAM,EAAE,aAAa;gBACrB,KAAK,EAAE,KAAK,CACX,sBAAsB,EACtB,KAAK,CAAC,MAAM,IAAI,oCAAoC,EACpD,sBAAsB,CACtB;aACD,CAAC;QACH,MAAM,OAAO,GAAG,cAAc,CAAC;YAC9B,OAAO,EAAE,CAAU;YACnB,SAAS,EAAE,yBAAyB,IAAI,CAAC,aAAa,EAAE,EAAE;YAC1D,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,MAAM,EAAE,OAAgB;YACxB,GAAG,CAAC,OAAO,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC;SACxF,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAC9C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;IAAA,CACxC;IAED,OAAO,CAAC,SAAiB,EAAmD;QAC3E,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,mBAAmB,EAAE,wBAAwB,CAAC,CAAC;QACxG,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,0BAA0B,CAAC,CAAC;QAC/F,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACnC,MAAM,GAAG,GAAG,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAChF,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,SAAS,KAAK,IAAI;YAC9C,OAAO,WAAW,CAAC,SAAS,EAAE,wCAAwC,CAAC,CAAC;QACzE,OAAO,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,GAA6B,CAAC,CAAC;IAAA,CAClE;IAED,IAAI,CAAC,SAAiB,EAA+C;QACpE,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,EAAE,wBAAwB,CAAC,CAAC;QACrG,MAAM,OAAO,GAAG,cAAc,CAAC,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,SAAkB,EAAE,CAAC,CAAC;QAC3E,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACtC,OAAO,OAAO,CAAC;IAAA,CACf;IAEO,GAAG,CACV,MAAc,EACd,OAAiB,EAC4E;QAC7F,IAAI,IAAI,CAAC,QAAQ,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACjD,MAAM,GAAG,GAAsB,EAAE,CAAC;QAClC,KAAK,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAU,EAAE,CAAC;YAC1E,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,KAAK,KAAK,SAAS;gBAAE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAC3C,CAAC;QACD,IAAI,OAAO,KAAK,SAAS;YAAE,GAAG,CAAC,0BAA0B,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACpF,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE;YAC3E,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,IAAI,CAAC,SAAS;YACvB,WAAW,EAAE,IAAI;YACjB,GAAG;SACH,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YACpD,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QAC7E,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAAA,CAC7E;IAEO,aAAa,CAAC,OAAyB,EAAE,GAAgB,EAAwB;QACxF,MAAM,aAAa,GAAG,6BAA6B,IAAI,CAAC,iBAAiB,EAAE,EAAE,CAAC;QAC9E,MAAM,OAAO,GAA8B,EAAE,CAAC;QAC9C,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;YACtC,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC,EAAE,KAAK,EAAE;gBAAE,SAAS;YAC5D,OAAO,CAAC,IAAI,CAAC;gBACZ,OAAO,EAAE,CAAC;gBACV,QAAQ,EAAE,IAAI,CAAC,EAAE;gBACjB,KAAK,EAAE,WAAW,MAAM,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,EAAE;gBAC/C,KAAK,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE;gBACtB,MAAM,EAAE,SAAS,CAAC,IAAI,CAAC;aACvB,CAAC,CAAC;QACJ,CAAC;QACD,MAAM,cAAc,GACnB,OAAO,CAAC,aAAa,KAAK,SAAS;YAClC,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;QACvE,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClF,MAAM,QAAQ,GAA+B,EAAE,CAAC;QAChD,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;YACvC,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YACxF,QAAQ,CAAC,IAAI,CAAC;gBACb,OAAO,EAAE,CAAC;gBACV,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,EAAE;gBAC/B,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE;gBACrB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE;gBACrB,MAAM,EAAE,SAAS,CAAC,IAAI,CAAC;gBACvB,OAAO,EAAE,IAAI,CAAC,OAAO,KAAK,KAAK;gBAC/B,GAAG,CAAC,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACzF,CAAC,CAAC;QACJ,CAAC;QACD,MAAM,gBAAgB,GACrB,OAAO,GAAG,CAAC,gBAAgB,KAAK,QAAQ;YACxC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS,KAAK,GAAG,CAAC,gBAAgB,CAAC;YACrE,CAAC,CAAC,GAAG,CAAC,gBAAgB;YACtB,CAAC,CAAC,SAAS,CAAC;QACd,OAAO,cAAc,CAAC;YACrB,OAAO,EAAE,CAAU;YACnB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,aAAa;YACb,MAAM,EAAE,UAAmB;YAC3B,OAAO,EAAE,cAAc;YACvB,QAAQ;YACR,GAAG,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/D,CAAC,CAAC;IAAA,CACH;IAEO,MAAM,CAAC,WAAmB,EAAE,IAAY,EAAE,OAAe,EAAE,SAAkB,EAA4B;QAChH,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;IAAA,CAC/G;IACO,SAAS,CAAC,WAAmB,EAAE,OAAe,EAA4B;QACjF,OAAO;YACN,OAAO,EAAE,CAAC;YACV,WAAW;YACX,MAAM,EAAE,WAAW;YACnB,OAAO,EAAE,OAAO;YAChB,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,OAAO,EAAE,SAAS,CAAC;SAC7C,CAAC;IAAA,CACF;CACD;AAED,oEAAoE;AACpE,MAAM,OAAO,mBAAmB;IACtB,SAAS,GAAG,kBAAkB,CAAC;IAC/B,cAAc,GAAG,OAAO,CAAC;IACzB,YAAY,GAAG,qBAAqB,CAAC;IAC7B,MAAM,CAA+B;IACtD,YAAY,MAAM,GAAiC,IAAI,wBAAwB,EAAE,EAAE;QAClF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IAAA,CACrB;IACD,KAAK,GAAwB;QAC5B,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IAAA,CAC3B;IACD,WAAW,CAAC,OAAgC,EAA0B;QACrE,OAAO,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAAA,CACxC;IACD,OAAO,CAAC,SAAiB,EAAmD;QAC3E,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAAA,CACtC;IACD,SAAS,CAAC,SAAiB,EAAmD;QAC7E,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAAA,CACtC;IACD,IAAI,CAAC,SAAiB,EAA+C;QACpE,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAAA,CACnC;CACD","sourcesContent":["/**\n * macOS Accessibility read-only driver and adapter facade for the observe\n * package. Structurally mirrors the Windows package's `windows-uia-adapter.ts`\n * (inlined from agent-forge `src/plugins/desktop/windows-uia-adapter.ts`,\n * D-075 S3) with these deliberate differences:\n * - Driver boundary is `osascript -l JavaScript -e <script>` (JXA) instead of\n * PowerShell UIA; the query script walks the System Events accessibility\n * tree (`Application(\"com.apple.systemevents\")` processes/windows/entireContents) and\n * prints one JSON document to stdout.\n * - This observe copy carries the query script only: the class implements\n * `DesktopMacosReadonlyDriverV1` and the facade implements\n * `DesktopReadonlyAdapterV1` — there is deliberately no execute surface.\n * - Non-darwin platforms never throw at import or call time: probe reports\n * `{available: false, reason: \"macOS Accessibility requires darwin\"}` and\n * every tool returns an explicit `unsupported` result (same fail-closed\n * contract as the Windows package off win32).\n * - Missing Accessibility (assistive access) permission is reported\n * fail-closed as `accessibility-permission-required`; no degraded mode.\n * This file is a deliberate copy shared (not imported) with the input\n * package's adapter; the input copy carries the action script and execute.\n */\nimport { spawnSync as nodeSpawnSync } from \"node:child_process\";\nimport { detachedFreeze } from \"@agent-forge/plugin-sdk\";\nimport type {\n\tDesktopCapabilityIdV1,\n\tDesktopControlIdentityV1,\n\tDesktopErrorV1,\n\tDesktopMacosProbeV1,\n\tDesktopMacosReadonlyDriverV1,\n\tDesktopObservationV1,\n\tDesktopOperationResultV1,\n\tDesktopReadonlyAdapterV1,\n\tDesktopSessionRequestV1,\n\tDesktopSessionResultV1,\n\tDesktopSessionV1,\n\tDesktopWindowIdentityV1,\n} from \"./types.ts\";\n\ntype RawWindow = {\n\tid?: string;\n\tprocessId?: number;\n\tprocessName?: string;\n\tname?: string;\n\tx?: number;\n\ty?: number;\n\twidth?: number;\n\theight?: number;\n};\ntype RawControl = {\n\twindowId?: string;\n\tcontrolId?: string;\n\trole?: string;\n\tname?: string;\n\tenabled?: boolean;\n\tvalue?: string;\n\tx?: number;\n\ty?: number;\n\twidth?: number;\n\theight?: number;\n};\ntype RawSnapshot = {\n\tavailable?: boolean;\n\treason?: string;\n\twindows?: RawWindow[];\n\tcontrols?: RawControl[];\n\tfocusedControlId?: string;\n};\n\ntype MacosSpawnResult = {\n\treadonly error?: Error;\n\treadonly status: number | null;\n\treadonly stdout: string;\n\treadonly stderr?: string;\n};\n\ntype MacosSpawn = (\n\tcommand: string,\n\targs: readonly string[],\n\toptions: {\n\t\treadonly encoding: \"utf8\";\n\t\treadonly timeout: number;\n\t\treadonly windowsHide: boolean;\n\t\treadonly env: NodeJS.ProcessEnv;\n\t},\n) => MacosSpawnResult;\n\nexport interface MacosAccessibilityDriverOptions {\n\t/** Override the osascript executable for a brokered host or test harness. */\n\treadonly command?: string;\n\treadonly timeoutMs?: number;\n\t/** Injectable host boundary for deterministic tests and brokered deployments. */\n\treadonly spawnSync?: MacosSpawn;\n\t/** Override the platform reported to the driver; intended for host adapters/tests. */\n\treadonly platform?: NodeJS.Platform;\n}\n\nconst READONLY_CAPABILITIES: readonly DesktopCapabilityIdV1[] = [\"desktop-session\", \"desktop-uia\"];\n\nconst error = (code: string, message: string, causeCode?: string): DesktopErrorV1 => ({\n\tversion: 1,\n\tcode,\n\tmessage,\n\t...(causeCode === undefined ? {} : { causeCode }),\n});\n\nconst unsupported = (operationId: string, reason: string): DesktopOperationResultV1 => ({\n\tversion: 1,\n\toperationId,\n\tstatus: \"unsupported\",\n\tsummary: reason,\n\terror: error(\"platform_unavailable\", reason, \"platform-unavailable\"),\n});\n\nconst rectangle = (raw: { x?: number; y?: number; width?: number; height?: number }) => ({\n\tx: Number.isFinite(raw.x) ? Number(raw.x) : 0,\n\ty: Number.isFinite(raw.y) ? Number(raw.y) : 0,\n\twidth: Number.isFinite(raw.width) ? Number(raw.width) : 0,\n\theight: Number.isFinite(raw.height) ? Number(raw.height) : 0,\n});\n\nconst isPermissionReason = (value: string): boolean =>\n\t/assistive access|not authorized|accessibility-permission-required/i.test(value);\n\n/**\n * JXA observation script. Enumerates every non-background System Events\n * process's windows (caps: 64 windows, 256 controls total, mirroring the\n * projection budgets) with role/name/position/size/enabled/value per control,\n * plus the frontmost process's focused element matched by role and frame.\n * Permission or automation failures are reported in-band as\n * `{available:false, reason:...}` so the probe can classify them; the reason\n * literal `accessibility-permission-required` is reserved for missing\n * Accessibility (assistive access) grants.\n */\nconst QUERY_SCRIPT = `\nvar se = Application('com.apple.systemevents');\nfunction safe(fn) { try { return fn(); } catch (e) { return undefined; } }\nfunction rectOf(el) {\n var p = safe(function() { return el.position(); });\n var z = safe(function() { return el.size(); });\n if (p === undefined || z === undefined || p.length < 2 || z.length < 2) return {x: 0, y: 0, width: 0, height: 0};\n return {x: Math.round(p[0]), y: Math.round(p[1]), width: Math.round(z[0]), height: Math.round(z[1])};\n}\nfunction axReason(e) {\n var msg = String(e && e.message !== undefined ? e.message : e);\n var num = e && typeof e.errorNumber === 'number' ? e.errorNumber : 0;\n if (msg.indexOf('assistive access') !== -1 || msg.indexOf('not authorized') !== -1 ||\n num === -25211 || num === -1719 || num === -1743) return 'accessibility-permission-required';\n return msg;\n}\ntry {\n var out = {available: true, windows: [], controls: [], focusedControlId: null};\n var procs = se.processes();\n var focused = null;\n for (var fi = 0; fi < procs.length; fi++) {\n var fp = procs[fi];\n if (safe(function() { return fp.frontmost(); }) !== true) continue;\n var fe = safe(function() { return fp.attributes['AXFocusedUIElement'].value(); });\n if (fe !== undefined) focused = {role: safe(function() { return fe.role(); }), rect: rectOf(fe)};\n break;\n }\n var windowCount = 0;\n var controlCount = 0;\n for (var pi = 0; pi < procs.length && windowCount < 64; pi++) {\n var proc = procs[pi];\n if (safe(function() { return proc.backgroundOnly(); }) === true) continue;\n var wins = safe(function() { return proc.windows(); });\n if (wins === undefined || wins.length === 0) continue;\n var pid = safe(function() { return proc.unixId(); });\n var pname = safe(function() { return proc.name(); });\n if (typeof pname !== 'string' || pname.length === 0) pname = 'unknown';\n for (var wi = 0; wi < wins.length && windowCount < 64; wi++) {\n var win = wins[wi];\n var winRect = rectOf(win);\n var winId = 'window-' + pid + '-' + wi;\n windowCount++;\n out.windows.push({id: winId, processId: pid, processName: pname,\n name: safe(function() { return win.name(); }) || '', x: winRect.x, y: winRect.y,\n width: winRect.width, height: winRect.height});\n var contents = safe(function() { return win.entireContents(); });\n if (contents === undefined) continue;\n for (var ci = 0; ci < contents.length && controlCount < 256; ci++) {\n var el = contents[ci];\n var role = safe(function() { return el.role(); });\n if (typeof role !== 'string' || role.length === 0) continue;\n var r = rectOf(el);\n var controlId = winId + ':' + role + ':' + ci;\n var value = safe(function() { return el.value(); });\n out.controls.push({windowId: winId, controlId: controlId, role: role,\n name: safe(function() { return el.name(); }) || safe(function() { return el.title(); }) || '',\n enabled: safe(function() { return el.enabled(); }) !== false,\n value: typeof value === 'string' && value.length > 0 ? value : undefined,\n x: r.x, y: r.y, width: r.width, height: r.height});\n controlCount++;\n if (focused !== null && out.focusedControlId === null && role === focused.role &&\n r.x === focused.rect.x && r.y === focused.rect.y &&\n r.width === focused.rect.width && r.height === focused.rect.height) {\n out.focusedControlId = controlId;\n }\n }\n }\n }\n JSON.stringify(out);\n} catch (e) {\n JSON.stringify({available: false, reason: axReason(e)});\n}`;\n\nconst parseJson = (stdout: string): Record<string, unknown> | undefined => {\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(stdout.trim());\n\t\treturn typeof parsed === \"object\" && parsed !== null ? (parsed as Record<string, unknown>) : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n};\n\nexport class MacosAccessibilityDriver implements DesktopMacosReadonlyDriverV1 {\n\treadonly driverId = \"macos-ax\";\n\treadonly driverVersion = \"1.0.0\";\n\tprivate readonly command: string;\n\tprivate readonly timeoutMs: number;\n\tprivate readonly platform: NodeJS.Platform;\n\tprivate readonly spawn: MacosSpawn;\n\tprivate readonly sessions = new Map<string, DesktopSessionV1>();\n\tprivate nextSessionId = 1;\n\tprivate nextObservationId = 1;\n\n\tconstructor(options: MacosAccessibilityDriverOptions = {}) {\n\t\tthis.command = options.command ?? \"osascript\";\n\t\t// 60s: Apple events from an SSH (Background launchd) session run ~100x\n\t\t// slower than a GUI session — one full enumeration measured 38s there;\n\t\t// GUI sessions complete in seconds and stay unaffected by the raise.\n\t\tthis.timeoutMs = options.timeoutMs ?? 60_000;\n\t\tthis.platform = options.platform ?? process.platform;\n\t\tthis.spawn =\n\t\t\toptions.spawnSync ??\n\t\t\t((command, args, spawnOptions) => nodeSpawnSync(command, args, spawnOptions) as unknown as MacosSpawnResult);\n\t}\n\n\tprobe(): DesktopMacosProbeV1 {\n\t\tif (this.platform !== \"darwin\") {\n\t\t\treturn {\n\t\t\t\tavailable: false,\n\t\t\t\tplatform: this.platform,\n\t\t\t\tdriver: \"macos-ax\",\n\t\t\t\treason: \"macOS Accessibility requires darwin\",\n\t\t\t};\n\t\t}\n\t\tconst run = this.run(QUERY_SCRIPT);\n\t\tif (run === undefined)\n\t\t\treturn {\n\t\t\t\tavailable: false,\n\t\t\t\tplatform: this.platform,\n\t\t\t\tdriver: \"macos-ax\",\n\t\t\t\treason: \"macOS Accessibility requires darwin\",\n\t\t\t};\n\t\tif (run.failed)\n\t\t\treturn {\n\t\t\t\tavailable: false,\n\t\t\t\tplatform: this.platform,\n\t\t\t\tdriver: \"macos-ax\",\n\t\t\t\treason: isPermissionReason(run.stderr) ? \"accessibility-permission-required\" : \"osascript is unavailable\",\n\t\t\t};\n\t\tconst raw = parseJson(run.stdout);\n\t\tif (raw === undefined)\n\t\t\treturn {\n\t\t\t\tavailable: false,\n\t\t\t\tplatform: this.platform,\n\t\t\t\tdriver: \"macos-ax\",\n\t\t\t\treason: \"osascript is unavailable\",\n\t\t\t};\n\t\tif (raw.available === true)\n\t\t\treturn {\n\t\t\t\tavailable: true,\n\t\t\t\tplatform: this.platform,\n\t\t\t\tdriver: \"macos-ax\",\n\t\t\t\tcapabilities: READONLY_CAPABILITIES,\n\t\t\t};\n\t\tconst reason =\n\t\t\ttypeof raw.reason === \"string\" && raw.reason.trim() !== \"\" ? raw.reason : \"macOS Accessibility probe failed\";\n\t\treturn { available: false, platform: this.platform, driver: \"macos-ax\", reason };\n\t}\n\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1 {\n\t\tconst probe = this.probe();\n\t\tif (!probe.available)\n\t\t\treturn {\n\t\t\t\tstatus: \"unsupported\",\n\t\t\t\terror: error(\n\t\t\t\t\t\"platform_unavailable\",\n\t\t\t\t\tprobe.reason ?? \"macOS Accessibility is unavailable\",\n\t\t\t\t\t\"platform-unavailable\",\n\t\t\t\t),\n\t\t\t};\n\t\tconst session = detachedFreeze({\n\t\t\tversion: 1 as const,\n\t\t\tsessionId: `desktop-macos-session-${this.nextSessionId++}`,\n\t\t\tisolation: request.isolation,\n\t\t\tstatus: \"ready\" as const,\n\t\t\t...(request.applicationId === undefined ? {} : { applicationId: request.applicationId }),\n\t\t});\n\t\tthis.sessions.set(session.sessionId, session);\n\t\treturn { status: \"succeeded\", session };\n\t}\n\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\tconst session = this.sessions.get(sessionId);\n\t\tif (session === undefined) return this.failed(\"observe\", \"session_not_found\", \"Session does not exist\");\n\t\tif (session.status === \"stopped\") return this.cancelled(\"observe\", \"Session has been stopped\");\n\t\tconst run = this.run(QUERY_SCRIPT);\n\t\tconst raw = run === undefined || run.failed ? undefined : parseJson(run.stdout);\n\t\tif (raw === undefined || raw.available !== true)\n\t\t\treturn unsupported(\"observe\", \"macOS Accessibility observation failed\");\n\t\treturn this.toObservation(session, raw as unknown as RawSnapshot);\n\t}\n\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1 {\n\t\tconst session = this.sessions.get(sessionId);\n\t\tif (session === undefined) return this.failed(\"stop\", \"session_not_found\", \"Session does not exist\");\n\t\tconst stopped = detachedFreeze({ ...session, status: \"stopped\" as const });\n\t\tthis.sessions.set(sessionId, stopped);\n\t\treturn stopped;\n\t}\n\n\tprivate run(\n\t\tscript: string,\n\t\tpayload?: unknown,\n\t): { readonly stdout: string; readonly stderr: string; readonly failed: boolean } | undefined {\n\t\tif (this.platform !== \"darwin\") return undefined;\n\t\tconst env: NodeJS.ProcessEnv = {};\n\t\tfor (const key of [\"PATH\", \"HOME\", \"TMPDIR\", \"USER\", \"LOGNAME\"] as const) {\n\t\t\tconst value = process.env[key];\n\t\t\tif (value !== undefined) env[key] = value;\n\t\t}\n\t\tif (payload !== undefined) env.AGENT_FORGE_MACOS_AX_INPUT = JSON.stringify(payload);\n\t\tconst result = this.spawn(this.command, [\"-l\", \"JavaScript\", \"-e\", script], {\n\t\t\tencoding: \"utf8\",\n\t\t\ttimeout: this.timeoutMs,\n\t\t\twindowsHide: true,\n\t\t\tenv,\n\t\t});\n\t\tif (result.error !== undefined || result.status !== 0)\n\t\t\treturn { stdout: result.stdout, stderr: result.stderr ?? \"\", failed: true };\n\t\treturn { stdout: result.stdout, stderr: result.stderr ?? \"\", failed: false };\n\t}\n\n\tprivate toObservation(session: DesktopSessionV1, raw: RawSnapshot): DesktopObservationV1 {\n\t\tconst observationId = `desktop-macos-observation-${this.nextObservationId++}`;\n\t\tconst windows: DesktopWindowIdentityV1[] = [];\n\t\tfor (const item of raw.windows ?? []) {\n\t\t\tif (typeof item.id !== \"string\" || item.id === \"\") continue;\n\t\t\twindows.push({\n\t\t\t\tversion: 1,\n\t\t\t\twindowId: item.id,\n\t\t\t\tappId: `process:${Number(item.processId ?? 0)}`,\n\t\t\t\ttitle: item.name ?? \"\",\n\t\t\t\tbounds: rectangle(item),\n\t\t\t});\n\t\t}\n\t\tconst visibleWindows =\n\t\t\tsession.applicationId === undefined\n\t\t\t\t? windows\n\t\t\t\t: windows.filter((window) => window.appId === session.applicationId);\n\t\tconst visibleWindowIds = new Set(visibleWindows.map((window) => window.windowId));\n\t\tconst controls: DesktopControlIdentityV1[] = [];\n\t\tfor (const item of raw.controls ?? []) {\n\t\t\tif (typeof item.windowId !== \"string\" || !visibleWindowIds.has(item.windowId)) continue;\n\t\t\tcontrols.push({\n\t\t\t\tversion: 1,\n\t\t\t\tcontrolId: item.controlId ?? \"\",\n\t\t\t\twindowId: item.windowId,\n\t\t\t\trole: item.role ?? \"\",\n\t\t\t\tname: item.name ?? \"\",\n\t\t\t\tbounds: rectangle(item),\n\t\t\t\tenabled: item.enabled !== false,\n\t\t\t\t...(typeof item.value === \"string\" && item.value.length > 0 ? { value: item.value } : {}),\n\t\t\t});\n\t\t}\n\t\tconst focusedControlId =\n\t\t\ttypeof raw.focusedControlId === \"string\" &&\n\t\t\tcontrols.some((control) => control.controlId === raw.focusedControlId)\n\t\t\t\t? raw.focusedControlId\n\t\t\t\t: undefined;\n\t\treturn detachedFreeze({\n\t\t\tversion: 1 as const,\n\t\t\tsessionId: session.sessionId,\n\t\t\tobservationId,\n\t\t\tsource: \"macos-ax\" as const,\n\t\t\twindows: visibleWindows,\n\t\t\tcontrols,\n\t\t\t...(focusedControlId !== undefined ? { focusedControlId } : {}),\n\t\t});\n\t}\n\n\tprivate failed(operationId: string, code: string, message: string, causeCode?: string): DesktopOperationResultV1 {\n\t\treturn { version: 1, operationId, status: \"failed\", summary: message, error: error(code, message, causeCode) };\n\t}\n\tprivate cancelled(operationId: string, message: string): DesktopOperationResultV1 {\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\toperationId,\n\t\t\tstatus: \"cancelled\",\n\t\t\tsummary: message,\n\t\t\terror: error(\"cancelled\", message, \"stopped\"),\n\t\t};\n\t}\n}\n\n/** Read-only adapter facade over the macOS Accessibility driver. */\nexport class DesktopMacosAdapter implements DesktopReadonlyAdapterV1 {\n\treadonly adapterId = \"desktop-macos-ax\";\n\treadonly adapterVersion = \"1.0.0\";\n\treadonly capabilities = READONLY_CAPABILITIES;\n\tprivate readonly driver: DesktopMacosReadonlyDriverV1;\n\tconstructor(driver: DesktopMacosReadonlyDriverV1 = new MacosAccessibilityDriver()) {\n\t\tthis.driver = driver;\n\t}\n\tprobe(): DesktopMacosProbeV1 {\n\t\treturn this.driver.probe();\n\t}\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1 {\n\t\treturn this.driver.openSession(request);\n\t}\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.observe(sessionId);\n\t}\n\treobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.observe(sessionId);\n\t}\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.stop(sessionId);\n\t}\n}\n"]}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Inlined from agent-forge `src/profile/runtime-capabilities-desktop.ts`
3
+ * (D-075 S3 extraction), mirrored from the Windows desktop plugin packages,
4
+ * so this package keeps its dependency closure limited to the plugin SDK and
5
+ * typebox. Semantics are unchanged. Differences from the Windows copies:
6
+ * - Desktop V1 types import from the package-local `types.ts` copy.
7
+ * - The accepted observation `source` literals are `"mock"` and `"macos-ax"`
8
+ * instead of `"windows-uia"`.
9
+ * Budgets and truncation logic are unchanged (64 windows / 256 controls /
10
+ * 256 chars / 64 KiB result budget). Deliberate copy shared (subset-compatible)
11
+ * with `plugins/desktop-macos-input/src/projection.ts`; keep both in sync.
12
+ */
13
+ import type { DesktopCapabilityIdV1, DesktopControlIdentityV1, DesktopObservationV1, DesktopWindowIdentityV1 } from "./types.ts";
14
+ export declare const DESKTOP_MAX_STRING_CHARS_V1 = 256;
15
+ export declare const DESKTOP_MAX_POSTCONDITIONS_V1 = 32;
16
+ export declare const DESKTOP_MAX_TYPE_TEXT_CHARS_V1 = 4096;
17
+ export declare const DESKTOP_MAX_REPLAY_ACTIONS_V1 = 32;
18
+ export declare function desktopFailure(operationId: string, code: string, message: string, status?: "failed" | "unsupported" | "cancelled" | "needs_user"): {
19
+ version: 1;
20
+ operationId: string;
21
+ status: "cancelled" | "failed" | "needs_user" | "unsupported";
22
+ summary: string;
23
+ error: {
24
+ version: 1;
25
+ code: string;
26
+ message: string;
27
+ };
28
+ };
29
+ export declare function desktopBoundedString(value: unknown, label: string): string;
30
+ export declare function desktopClampedString(value: unknown, label: string): string;
31
+ export declare function desktopRequiredString(value: unknown, label: string): string;
32
+ declare function desktopCapabilities(value: unknown): readonly DesktopCapabilityIdV1[] | undefined;
33
+ export declare function projectDesktopObservation(value: unknown): {
34
+ readonly observation: DesktopObservationV1;
35
+ readonly truncated: {
36
+ readonly windows: boolean;
37
+ readonly controls: boolean;
38
+ };
39
+ };
40
+ export declare function desktopResultWithinBudget<T>(value: T): T;
41
+ declare function desktopObservationResult(value: unknown, operationId: string): {
42
+ version: 1;
43
+ operationId: string;
44
+ status: "cancelled" | "failed" | "needs_user" | "unsupported";
45
+ summary: string;
46
+ error: {
47
+ version: 1;
48
+ code: string;
49
+ message: string;
50
+ };
51
+ } | {
52
+ version: number;
53
+ operationId: string;
54
+ status: "succeeded";
55
+ observation: {
56
+ version: 1;
57
+ sessionId: string;
58
+ observationId: string;
59
+ source: "macos-ax" | "mock";
60
+ focusedControlId?: string | undefined;
61
+ controls: DesktopControlIdentityV1[];
62
+ windows: DesktopWindowIdentityV1[];
63
+ };
64
+ truncated: {
65
+ windows: boolean;
66
+ controls: boolean;
67
+ };
68
+ };
69
+ declare function desktopSessionResult(value: unknown, operationId: string): {
70
+ version: 1;
71
+ operationId: string;
72
+ status: "cancelled" | "failed" | "needs_user" | "unsupported";
73
+ summary: string;
74
+ error: {
75
+ version: 1;
76
+ code: string;
77
+ message: string;
78
+ };
79
+ } | {
80
+ version: number;
81
+ operationId: string;
82
+ status: "succeeded";
83
+ session: {
84
+ version: number;
85
+ sessionId: string;
86
+ isolation: string;
87
+ status: string;
88
+ applicationId?: string | undefined;
89
+ };
90
+ };
91
+ declare function desktopStopResult(value: unknown, operationId: string): {
92
+ version: 1;
93
+ operationId: string;
94
+ status: "cancelled" | "failed" | "needs_user" | "unsupported";
95
+ summary: string;
96
+ error: {
97
+ version: 1;
98
+ code: string;
99
+ message: string;
100
+ };
101
+ } | {
102
+ version: number;
103
+ operationId: string;
104
+ status: "succeeded";
105
+ session: {
106
+ version: number;
107
+ sessionId: string;
108
+ isolation: string;
109
+ status: string;
110
+ applicationId?: string | undefined;
111
+ };
112
+ };
113
+ export { desktopCapabilities, desktopObservationResult, desktopSessionResult, desktopStopResult };
114
+ //# sourceMappingURL=projection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../src/projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EACX,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,EACpB,uBAAuB,EACvB,MAAM,YAAY,CAAC;AAIpB,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAChD,eAAO,MAAM,8BAA8B,OAAO,CAAC;AACnD,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAShD,wBAAgB,cAAc,CAC7B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,QAAQ,GAAG,aAAa,GAAG,WAAW,GAAG,YAAuB;;;;;;;;;;EASxE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAI1E;AAKD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAI1E;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAI3E;AAED,iBAAS,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,qBAAqB,EAAE,GAAG,SAAS,CAQzF;AAiDD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG;IAC1D,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,QAAQ,CAAC,SAAS,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;CAC9E,CAgCA;AAED,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAIxD;AAED,iBAAS,wBAAwB,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkDpE;AAED,iBAAS,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;EAsChE;AAED,iBAAS,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;EAS7D;AAED,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,CAAC","sourcesContent":["/**\n * Inlined from agent-forge `src/profile/runtime-capabilities-desktop.ts`\n * (D-075 S3 extraction), mirrored from the Windows desktop plugin packages,\n * so this package keeps its dependency closure limited to the plugin SDK and\n * typebox. Semantics are unchanged. Differences from the Windows copies:\n * - Desktop V1 types import from the package-local `types.ts` copy.\n * - The accepted observation `source` literals are `\"mock\"` and `\"macos-ax\"`\n * instead of `\"windows-uia\"`.\n * Budgets and truncation logic are unchanged (64 windows / 256 controls /\n * 256 chars / 64 KiB result budget). Deliberate copy shared (subset-compatible)\n * with `plugins/desktop-macos-input/src/projection.ts`; keep both in sync.\n */\nimport type {\n\tDesktopCapabilityIdV1,\n\tDesktopControlIdentityV1,\n\tDesktopObservationV1,\n\tDesktopWindowIdentityV1,\n} from \"./types.ts\";\n\nconst DESKTOP_MAX_WINDOWS_V1 = 64;\nconst DESKTOP_MAX_CONTROLS_V1 = 256;\nexport const DESKTOP_MAX_STRING_CHARS_V1 = 256;\nconst DESKTOP_MAX_RESULT_BYTES_V1 = 64 * 1024;\nexport const DESKTOP_MAX_POSTCONDITIONS_V1 = 32;\nexport const DESKTOP_MAX_TYPE_TEXT_CHARS_V1 = 4096;\nexport const DESKTOP_MAX_REPLAY_ACTIONS_V1 = 32;\nconst DESKTOP_CAPABILITY_IDS_V1: readonly DesktopCapabilityIdV1[] = [\n\t\"desktop-session\",\n\t\"desktop-uia\",\n\t\"desktop-input\",\n\t\"desktop-vision\",\n\t\"desktop-replay\",\n];\n\nexport function desktopFailure(\n\toperationId: string,\n\tcode: string,\n\tmessage: string,\n\tstatus: \"failed\" | \"unsupported\" | \"cancelled\" | \"needs_user\" = \"failed\",\n) {\n\treturn {\n\t\tversion: 1 as const,\n\t\toperationId,\n\t\tstatus,\n\t\tsummary: message,\n\t\terror: { version: 1 as const, code, message },\n\t};\n}\n\nexport function desktopBoundedString(value: unknown, label: string): string {\n\tif (typeof value !== \"string\" || value.length > DESKTOP_MAX_STRING_CHARS_V1)\n\t\tthrow new Error(`desktop-result-budget:${label}`);\n\treturn value;\n}\n\n/// 观察投影专用:显示文本超长时截断而非抛异常(dogfood 2026-09-25 实测,真实桌面\n/// 单控件文本超上限很常见)。输入校验(type text/key/target)仍用 desktopBoundedString\n/// 严格拒绝——截断输入等于静默打错字。\nexport function desktopClampedString(value: unknown, label: string): string {\n\tif (typeof value !== \"string\") throw new Error(`desktop-result-budget:${label}`);\n\tif (value.length > DESKTOP_MAX_STRING_CHARS_V1) return value.slice(0, DESKTOP_MAX_STRING_CHARS_V1);\n\treturn value;\n}\n\nexport function desktopRequiredString(value: unknown, label: string): string {\n\tconst result = desktopBoundedString(value, label);\n\tif (result.trim() === \"\") throw new Error(`desktop-result-invalid:${label}`);\n\treturn result;\n}\n\nfunction desktopCapabilities(value: unknown): readonly DesktopCapabilityIdV1[] | undefined {\n\tif (!Array.isArray(value)) return undefined;\n\tconst capabilities = value.filter(\n\t\t(item): item is DesktopCapabilityIdV1 =>\n\t\t\ttypeof item === \"string\" && DESKTOP_CAPABILITY_IDS_V1.includes(item as DesktopCapabilityIdV1),\n\t);\n\tconst unique = [...new Set(capabilities)].slice(0, DESKTOP_CAPABILITY_IDS_V1.length);\n\treturn unique.length === 0 ? undefined : unique;\n}\n\nfunction desktopBounds(value: unknown): {\n\treadonly x: number;\n\treadonly y: number;\n\treadonly width: number;\n\treadonly height: number;\n} {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) throw new Error(\"desktop-result-invalid\");\n\tconst record = value as Record<string, unknown>;\n\tconst bounds = { x: record.x, y: record.y, width: record.width, height: record.height };\n\tif (\n\t\tObject.values(bounds).some((item) => typeof item !== \"number\" || !Number.isFinite(item)) ||\n\t\t(bounds.width as number) < 0 ||\n\t\t(bounds.height as number) < 0\n\t)\n\t\tthrow new Error(\"desktop-result-invalid\");\n\treturn bounds as { x: number; y: number; width: number; height: number };\n}\n\nfunction projectDesktopWindow(value: unknown): DesktopWindowIdentityV1 {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) throw new Error(\"desktop-result-invalid\");\n\tconst record = value as Record<string, unknown>;\n\tif (record.version !== 1) throw new Error(\"desktop-result-invalid\");\n\treturn {\n\t\tversion: 1,\n\t\twindowId: desktopClampedString(record.windowId, \"windowId\"),\n\t\tappId: desktopClampedString(record.appId, \"appId\"),\n\t\ttitle: desktopClampedString(record.title, \"title\"),\n\t\tbounds: desktopBounds(record.bounds),\n\t};\n}\n\nfunction projectDesktopControl(value: unknown): DesktopControlIdentityV1 {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) throw new Error(\"desktop-result-invalid\");\n\tconst record = value as Record<string, unknown>;\n\tif (record.version !== 1 || typeof record.enabled !== \"boolean\") throw new Error(\"desktop-result-invalid\");\n\treturn {\n\t\tversion: 1,\n\t\tcontrolId: desktopClampedString(record.controlId, \"controlId\"),\n\t\twindowId: desktopClampedString(record.windowId, \"windowId\"),\n\t\trole: desktopClampedString(record.role, \"role\"),\n\t\tname: desktopClampedString(record.name, \"name\"),\n\t\tbounds: desktopBounds(record.bounds),\n\t\tenabled: record.enabled,\n\t\t...(record.value === undefined ? {} : { value: desktopClampedString(record.value, \"value\") }),\n\t};\n}\n\nexport function projectDesktopObservation(value: unknown): {\n\treadonly observation: DesktopObservationV1;\n\treadonly truncated: { readonly windows: boolean; readonly controls: boolean };\n} {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) throw new Error(\"desktop-result-invalid\");\n\tconst record = value as Record<string, unknown>;\n\tif (\n\t\trecord.version !== 1 ||\n\t\ttypeof record.sessionId !== \"string\" ||\n\t\ttypeof record.observationId !== \"string\" ||\n\t\t(record.source !== \"mock\" && record.source !== \"macos-ax\") ||\n\t\t!Array.isArray(record.windows) ||\n\t\t!Array.isArray(record.controls)\n\t)\n\t\tthrow new Error(\"desktop-result-invalid\");\n\tconst windows = record.windows.slice(0, DESKTOP_MAX_WINDOWS_V1).map(projectDesktopWindow);\n\tconst controls = record.controls.slice(0, DESKTOP_MAX_CONTROLS_V1).map(projectDesktopControl);\n\tconst observation = {\n\t\tversion: 1 as const,\n\t\tsessionId: desktopClampedString(record.sessionId, \"sessionId\"),\n\t\tobservationId: desktopClampedString(record.observationId, \"observationId\"),\n\t\tsource: record.source,\n\t\twindows,\n\t\tcontrols,\n\t\t...(record.focusedControlId === undefined\n\t\t\t? {}\n\t\t\t: { focusedControlId: desktopClampedString(record.focusedControlId, \"focusedControlId\") }),\n\t} satisfies DesktopObservationV1;\n\treturn {\n\t\tobservation,\n\t\ttruncated: {\n\t\t\twindows: record.windows.length > DESKTOP_MAX_WINDOWS_V1,\n\t\t\tcontrols: record.controls.length > DESKTOP_MAX_CONTROLS_V1,\n\t\t},\n\t};\n}\n\nexport function desktopResultWithinBudget<T>(value: T): T {\n\tif (new TextEncoder().encode(JSON.stringify(value)).byteLength > DESKTOP_MAX_RESULT_BYTES_V1)\n\t\tthrow new Error(\"desktop-result-budget\");\n\treturn value;\n}\n\nfunction desktopObservationResult(value: unknown, operationId: string) {\n\tif (value !== null && typeof value === \"object\" && !Array.isArray(value) && \"observationId\" in value) {\n\t\tconst projected = projectDesktopObservation(value);\n\t\t// 真实桌面控件数轻松超出 64KB 结果预算(dogfood 2026-09-25 实测):超预算时\n\t\t// 控件/窗口逐级减半裁剪并如实标记 truncated,而不是让整次观察失败。\n\t\tconst controls = projected.observation.controls;\n\t\tconst windows = projected.observation.windows;\n\t\tlet controlCount = controls.length;\n\t\tlet windowCount = windows.length;\n\t\tfor (;;) {\n\t\t\ttry {\n\t\t\t\treturn desktopResultWithinBudget({\n\t\t\t\t\tversion: 1,\n\t\t\t\t\toperationId,\n\t\t\t\t\tstatus: \"succeeded\" as const,\n\t\t\t\t\tobservation: {\n\t\t\t\t\t\t...projected.observation,\n\t\t\t\t\t\tcontrols: controls.slice(0, controlCount),\n\t\t\t\t\t\twindows: windows.slice(0, windowCount),\n\t\t\t\t\t},\n\t\t\t\t\ttruncated: {\n\t\t\t\t\t\twindows: projected.truncated.windows || windowCount < windows.length,\n\t\t\t\t\t\tcontrols: projected.truncated.controls || controlCount < controls.length,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} catch {\n\t\t\t\tif (controlCount > 0) controlCount = Math.floor(controlCount / 2);\n\t\t\t\telse if (windowCount > 0) windowCount = Math.floor(windowCount / 2);\n\t\t\t\telse throw new Error(\"desktop-result-budget\");\n\t\t\t}\n\t\t}\n\t}\n\tif (value !== null && typeof value === \"object\" && !Array.isArray(value) && \"status\" in value) {\n\t\tconst status = value.status;\n\t\tif (status === \"unsupported\" || status === \"failed\" || status === \"cancelled\" || status === \"needs_user\") {\n\t\t\tconst record0 = value as Record<string, unknown>;\n\t\t\tconst adapterError =\n\t\t\t\t\"error\" in record0 && record0.error !== null && typeof record0.error === \"object\"\n\t\t\t\t\t? (record0.error as Record<string, unknown>)\n\t\t\t\t\t: undefined;\n\t\t\tconst errorCode =\n\t\t\t\tadapterError && typeof adapterError.code === \"string\" ? adapterError.code : \"desktop_adapter_failed\";\n\t\t\tconst errorMessage =\n\t\t\t\tadapterError && typeof adapterError.message === \"string\"\n\t\t\t\t\t? adapterError.message\n\t\t\t\t\t: \"Desktop observation is unavailable\";\n\t\t\treturn desktopFailure(operationId, errorCode, errorMessage, status);\n\t\t}\n\t}\n\tthrow new Error(\"desktop-result-invalid\");\n}\n\nfunction desktopSessionResult(value: unknown, operationId: string) {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value) || !(\"status\" in value))\n\t\tthrow new Error(\"desktop-result-invalid\");\n\tconst record = value as Record<string, unknown>;\n\tif (record.status !== \"succeeded\" && record.status !== \"unsupported\" && record.status !== \"failed\")\n\t\tthrow new Error(\"desktop-result-invalid\");\n\tif (record.status !== \"succeeded\")\n\t\treturn desktopFailure(\n\t\t\toperationId,\n\t\t\t\"desktop_adapter_unavailable\",\n\t\t\t\"Desktop session is unavailable\",\n\t\t\trecord.status,\n\t\t);\n\tconst session = record.session;\n\tif (session === null || typeof session !== \"object\" || Array.isArray(session))\n\t\tthrow new Error(\"desktop-result-invalid\");\n\tconst item = session as Record<string, unknown>;\n\tif (\n\t\titem.version !== 1 ||\n\t\ttypeof item.sessionId !== \"string\" ||\n\t\t(item.isolation !== \"foreground\" && item.isolation !== \"vm\" && item.isolation !== \"mock\") ||\n\t\t(item.status !== \"ready\" && item.status !== \"unavailable\" && item.status !== \"stopped\")\n\t)\n\t\tthrow new Error(\"desktop-result-invalid\");\n\treturn desktopResultWithinBudget({\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"succeeded\" as const,\n\t\tsession: {\n\t\t\tversion: 1,\n\t\t\tsessionId: desktopBoundedString(item.sessionId, \"sessionId\"),\n\t\t\tisolation: item.isolation,\n\t\t\tstatus: item.status,\n\t\t\t...(item.applicationId === undefined\n\t\t\t\t? {}\n\t\t\t\t: { applicationId: desktopBoundedString(item.applicationId, \"applicationId\") }),\n\t\t},\n\t});\n}\n\nfunction desktopStopResult(value: unknown, operationId: string) {\n\tif (value !== null && typeof value === \"object\" && !Array.isArray(value) && \"sessionId\" in value)\n\t\treturn desktopSessionResult({ status: \"succeeded\", session: value }, operationId);\n\tif (value !== null && typeof value === \"object\" && !Array.isArray(value) && \"status\" in value) {\n\t\tconst status = value.status;\n\t\tif (status === \"unsupported\" || status === \"failed\" || status === \"cancelled\" || status === \"needs_user\")\n\t\t\treturn desktopFailure(operationId, \"desktop_adapter_failed\", \"Desktop session could not be stopped\", status);\n\t}\n\tthrow new Error(\"desktop-result-invalid\");\n}\n\nexport { desktopCapabilities, desktopObservationResult, desktopSessionResult, desktopStopResult };\n"]}