@anusornneal/chat-relay 0.7.2 → 0.8.1
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/README.md +353 -320
- package/agent/capability-scheduler.mjs +121 -0
- package/agent/connection-state.mjs +141 -0
- package/agent/desktop-manager.mjs +220 -115
- package/agent/file-manager.mjs +527 -382
- package/agent/lifecycle.mjs +101 -0
- package/agent/local-agent.mjs +494 -242
- package/agent/macos-desktop-helper.js +243 -0
- package/agent/macos-desktop-runner.mjs +292 -0
- package/agent/process-manager.mjs +110 -26
- package/agent/protocol.mjs +67 -0
- package/agent/terminal-manager.mjs +587 -550
- package/agent/windows-desktop-runner.mjs +109 -0
- package/agent/windows-desktop-worker.ps1 +215 -13
- package/cli/config.mjs +114 -114
- package/cli/device-login.mjs +170 -170
- package/cli/index.mjs +241 -201
- package/cli/remote.mjs +139 -94
- package/package.json +76 -75
- package/agent/windows-desktop.ps1 +0 -288
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import readline from "node:readline";
|
|
5
|
-
import { randomUUID } from "node:crypto";
|
|
1
|
+
import { BoundedLane } from "./capability-scheduler.mjs";
|
|
2
|
+
import { PersistentDesktopRunner } from "./windows-desktop-runner.mjs";
|
|
3
|
+
import { MacOSDesktopRunner } from "./macos-desktop-runner.mjs";
|
|
6
4
|
|
|
7
5
|
const MAX_SCREENSHOT_BINARY_BYTES = 32 * 1024;
|
|
8
|
-
const POWERSHELL_TIMEOUT_MS = 15_000;
|
|
9
6
|
const MAX_TEXT_LENGTH = 8192;
|
|
10
|
-
const
|
|
7
|
+
const MAX_CLIPBOARD_TEXT_LENGTH = 8192;
|
|
8
|
+
const MIN_SCREENSHOT_WIDTH = 320;
|
|
9
|
+
const MAX_SCREENSHOT_WIDTH = 1920;
|
|
10
|
+
const MIN_SCREENSHOT_QUALITY = 20;
|
|
11
|
+
const MAX_SCREENSHOT_QUALITY = 85;
|
|
12
|
+
const MAX_WINDOW_LIST_LIMIT = 100;
|
|
11
13
|
|
|
12
14
|
const KEY_CODES = new Map([
|
|
13
15
|
["enter", 0x0D],
|
|
@@ -54,6 +56,7 @@ function normalizeKeyboardInput(input = {}) {
|
|
|
54
56
|
return {
|
|
55
57
|
ok: true,
|
|
56
58
|
payload: {
|
|
59
|
+
key,
|
|
57
60
|
keyCode,
|
|
58
61
|
ctrl: input.ctrl === true,
|
|
59
62
|
alt: input.alt === true,
|
|
@@ -63,129 +66,132 @@ function normalizeKeyboardInput(input = {}) {
|
|
|
63
66
|
};
|
|
64
67
|
}
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
function compactStepActions(actions) {
|
|
70
|
+
const compacted = [];
|
|
71
|
+
for (const action of actions) {
|
|
72
|
+
const previous = compacted.at(-1);
|
|
73
|
+
if (action.type === "wait" && action.ms === 0) continue;
|
|
74
|
+
if (action.type === "text" && previous?.type === "text" &&
|
|
75
|
+
previous.text.length + action.text.length <= MAX_TEXT_LENGTH) {
|
|
76
|
+
previous.text += action.text;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (action.type === "wait" && previous?.type === "wait" && previous.ms + action.ms <= 5000) {
|
|
80
|
+
previous.ms += action.ms;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
compacted.push({ ...action });
|
|
74
84
|
}
|
|
85
|
+
return compacted.length > 0 ? compacted : actions.map((action) => ({ ...action }));
|
|
86
|
+
}
|
|
75
87
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
this.process = child;
|
|
94
|
-
this.stderr = "";
|
|
95
|
-
this.stdout = readline.createInterface({ input: child.stdout });
|
|
96
|
-
|
|
97
|
-
this.stdout.on("line", (line) => {
|
|
98
|
-
let message;
|
|
99
|
-
try {
|
|
100
|
-
message = JSON.parse(line);
|
|
101
|
-
} catch {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
const pending = this.pending.get(message?.id);
|
|
105
|
-
if (!pending) return;
|
|
106
|
-
clearTimeout(pending.timer);
|
|
107
|
-
this.pending.delete(message.id);
|
|
108
|
-
pending.resolve(message.result ?? { ok: false, error: "desktop_worker_error" });
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
child.stderr.on("data", (chunk) => {
|
|
112
|
-
this.stderr = (this.stderr + String(chunk)).slice(-8192);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
child.on("exit", () => {
|
|
116
|
-
const error = { ok: false, error: "desktop_worker_exited" };
|
|
117
|
-
for (const pending of this.pending.values()) {
|
|
118
|
-
clearTimeout(pending.timer);
|
|
119
|
-
pending.resolve(error);
|
|
120
|
-
}
|
|
121
|
-
this.pending.clear();
|
|
122
|
-
this.stdout?.close();
|
|
123
|
-
this.stdout = null;
|
|
124
|
-
this.process = null;
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
child.on("error", () => {
|
|
128
|
-
// exit handler settles pending work.
|
|
129
|
-
});
|
|
88
|
+
function adaptiveActionSettleMs(action, nextAction) {
|
|
89
|
+
if (!nextAction || action.type === "wait") return 0;
|
|
90
|
+
if (action.type === "click") {
|
|
91
|
+
if (action.clicks === 2) return 45;
|
|
92
|
+
return nextAction.type === "text" || nextAction.type === "key" ? 25 : 15;
|
|
93
|
+
}
|
|
94
|
+
if (action.type === "text") return 0;
|
|
95
|
+
if (action.type === "key") {
|
|
96
|
+
if ((action.alt && action.key === "tab") || action.win) return 120;
|
|
97
|
+
if (["enter", "tab", "escape"].includes(action.key)) return 25;
|
|
98
|
+
return 5;
|
|
99
|
+
}
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
130
102
|
|
|
131
|
-
|
|
103
|
+
function adaptiveFinalSettleMs(actions, wantsCapture) {
|
|
104
|
+
if (!wantsCapture || actions.length === 0) return 0;
|
|
105
|
+
const action = actions.at(-1);
|
|
106
|
+
if (action.type === "wait") return 0;
|
|
107
|
+
if (action.type === "click") return action.clicks === 2 ? 60 : 35;
|
|
108
|
+
if (action.type === "text") return 10;
|
|
109
|
+
if (action.type === "key") {
|
|
110
|
+
if ((action.alt && action.key === "tab") || action.win) return 180;
|
|
111
|
+
if (["enter", "tab", "escape"].includes(action.key)) return 50;
|
|
112
|
+
return 15;
|
|
132
113
|
}
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
133
116
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
117
|
+
export function createDesktopPlatformAdapter(options = {}) {
|
|
118
|
+
const platform = options.platform ?? process.platform;
|
|
119
|
+
const customRunner = typeof options.runner === "function" ? options.runner : null;
|
|
138
120
|
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
this.pending.set(id, { resolve, timer });
|
|
149
|
-
this.process.stdin.write(request, (error) => {
|
|
150
|
-
if (!error) return;
|
|
151
|
-
clearTimeout(timer);
|
|
152
|
-
this.pending.delete(id);
|
|
153
|
-
resolve({ ok: false, error: operation === "screenshot" ? "capture_failed" : "input_failed" });
|
|
154
|
-
});
|
|
155
|
-
});
|
|
121
|
+
if (platform === "win32") {
|
|
122
|
+
const persistentRunner = customRunner ? null : (options.persistentRunner ?? new PersistentDesktopRunner({ platform }));
|
|
123
|
+
return {
|
|
124
|
+
platform,
|
|
125
|
+
supported: true,
|
|
126
|
+
transport: "persistent-worker",
|
|
127
|
+
run: customRunner ?? ((operation, args) => persistentRunner.run(operation, args)),
|
|
128
|
+
close: () => persistentRunner?.close?.(),
|
|
129
|
+
};
|
|
156
130
|
}
|
|
157
131
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
132
|
+
if (platform === "darwin") {
|
|
133
|
+
const macosRunner = customRunner ? null : (options.macosRunner ?? new MacOSDesktopRunner({ platform }));
|
|
134
|
+
return {
|
|
135
|
+
platform,
|
|
136
|
+
supported: true,
|
|
137
|
+
transport: "native-macos",
|
|
138
|
+
run: customRunner ?? ((operation, args) => macosRunner.run(operation, args)),
|
|
139
|
+
close: () => macosRunner?.close?.(),
|
|
140
|
+
};
|
|
163
141
|
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
platform,
|
|
145
|
+
supported: false,
|
|
146
|
+
transport: "unsupported",
|
|
147
|
+
run: customRunner ?? (async () => ({ ok: false, error: "unsupported_platform" })),
|
|
148
|
+
close: () => {},
|
|
149
|
+
};
|
|
164
150
|
}
|
|
165
151
|
|
|
166
152
|
export class DesktopManager {
|
|
167
153
|
constructor(options = {}) {
|
|
168
154
|
this.enabled = options.enabled ?? process.env.DESKTOP_ENABLED === "1";
|
|
169
|
-
this.
|
|
170
|
-
|
|
171
|
-
|
|
155
|
+
this.adapter = options.adapter ?? createDesktopPlatformAdapter({
|
|
156
|
+
platform: options.platform,
|
|
157
|
+
persistentRunner: options.persistentRunner,
|
|
158
|
+
macosRunner: options.macosRunner,
|
|
159
|
+
runner: options.runner,
|
|
160
|
+
});
|
|
161
|
+
this.platform = this.adapter.platform;
|
|
162
|
+
this.runner = (operation, args) => this.adapter.run(operation, args);
|
|
163
|
+
this.controlLane = options.controlLane ?? new BoundedLane({
|
|
164
|
+
name: "desktop_control",
|
|
165
|
+
concurrency: 1,
|
|
166
|
+
maxQueued: options.controlMaxQueued ?? process.env.DESKTOP_CONTROL_MAX_QUEUED,
|
|
167
|
+
queueTimeoutMs: options.controlQueueTimeoutMs ?? process.env.AGENT_QUEUE_TIMEOUT_MS,
|
|
168
|
+
});
|
|
172
169
|
}
|
|
173
170
|
|
|
174
171
|
getConfig() {
|
|
175
172
|
return {
|
|
176
173
|
enabled: this.enabled,
|
|
177
|
-
supported: this.
|
|
178
|
-
platform: this.platform,
|
|
179
|
-
transport: this.
|
|
174
|
+
supported: this.adapter.supported,
|
|
175
|
+
platform: this.adapter.platform,
|
|
176
|
+
transport: this.adapter.transport,
|
|
177
|
+
controlQueue: this.controlLane.snapshot(),
|
|
180
178
|
};
|
|
181
179
|
}
|
|
182
180
|
|
|
183
181
|
gate() {
|
|
184
182
|
if (!this.enabled) return { ok: false, error: "desktop_disabled" };
|
|
185
|
-
if (this.
|
|
183
|
+
if (!this.adapter.supported) return { ok: false, error: "unsupported_platform" };
|
|
186
184
|
return null;
|
|
187
185
|
}
|
|
188
186
|
|
|
187
|
+
async runControl(work) {
|
|
188
|
+
try {
|
|
189
|
+
return await this.controlLane.run(work);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
return { ok: false, error: error instanceof Error ? error.message : "desktop_queue_error" };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
189
195
|
async screenshot(input = {}) {
|
|
190
196
|
const gate = this.gate();
|
|
191
197
|
if (gate) return gate;
|
|
@@ -195,7 +201,16 @@ export class DesktopManager {
|
|
|
195
201
|
return { ok: false, error: "invalid_monitor" };
|
|
196
202
|
}
|
|
197
203
|
|
|
198
|
-
const
|
|
204
|
+
const maxWidth = input.maxWidth === undefined ? 960 : Number(input.maxWidth);
|
|
205
|
+
if (!Number.isInteger(maxWidth) || maxWidth < MIN_SCREENSHOT_WIDTH || maxWidth > MAX_SCREENSHOT_WIDTH) {
|
|
206
|
+
return { ok: false, error: "invalid_max_width" };
|
|
207
|
+
}
|
|
208
|
+
const quality = input.quality === undefined ? 58 : Number(input.quality);
|
|
209
|
+
if (!Number.isInteger(quality) || quality < MIN_SCREENSHOT_QUALITY || quality > MAX_SCREENSHOT_QUALITY) {
|
|
210
|
+
return { ok: false, error: "invalid_quality" };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const result = await this.runner("screenshot", { monitor, maxWidth, quality });
|
|
199
214
|
return this.normalizeScreenshotResult(result);
|
|
200
215
|
}
|
|
201
216
|
|
|
@@ -224,10 +239,60 @@ export class DesktopManager {
|
|
|
224
239
|
monitorIndex: Number(result.monitorIndex),
|
|
225
240
|
isPrimary: result.isPrimary === true,
|
|
226
241
|
deviceName: typeof result.deviceName === "string" ? result.deviceName : undefined,
|
|
242
|
+
virtualDesktopOriginX: Number(result.virtualDesktopOriginX),
|
|
243
|
+
virtualDesktopOriginY: Number(result.virtualDesktopOriginY),
|
|
244
|
+
virtualDesktopWidth: Number(result.virtualDesktopWidth),
|
|
245
|
+
virtualDesktopHeight: Number(result.virtualDesktopHeight),
|
|
246
|
+
monitorCount: Number(result.monitorCount),
|
|
247
|
+
jpegQuality: Number(result.jpegQuality),
|
|
227
248
|
byteLength: binaryBytes,
|
|
228
249
|
};
|
|
229
250
|
}
|
|
230
251
|
|
|
252
|
+
async clipboardRead() {
|
|
253
|
+
const gate = this.gate();
|
|
254
|
+
if (gate) return gate;
|
|
255
|
+
const result = await this.runner("clipboard_read", {});
|
|
256
|
+
if (!result?.ok || typeof result.text !== "string") return result || { ok: false, error: "clipboard_failed" };
|
|
257
|
+
return { ok: true, text: result.text.slice(0, MAX_CLIPBOARD_TEXT_LENGTH), truncated: result.truncated === true };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async clipboardWrite(input = {}) {
|
|
261
|
+
const gate = this.gate();
|
|
262
|
+
if (gate) return gate;
|
|
263
|
+
if (typeof input.text !== "string") return { ok: false, error: "invalid_clipboard_text" };
|
|
264
|
+
if (input.text.length > MAX_CLIPBOARD_TEXT_LENGTH) return { ok: false, error: "text_too_large" };
|
|
265
|
+
return this.runControl(() => this.runner("clipboard_write", { text: input.text }));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async listWindows(input = {}) {
|
|
269
|
+
const gate = this.gate();
|
|
270
|
+
if (gate) return gate;
|
|
271
|
+
const limit = input.limit === undefined ? 50 : Number(input.limit);
|
|
272
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > MAX_WINDOW_LIST_LIMIT) return { ok: false, error: "invalid_window_limit" };
|
|
273
|
+
const result = await this.runner("window_list", { limit });
|
|
274
|
+
if (!result?.ok || !Array.isArray(result.windows)) return result || { ok: false, error: "window_list_failed" };
|
|
275
|
+
const windows = result.windows.slice(0, limit).map((window) => ({
|
|
276
|
+
windowId: String(window.windowId ?? window.WindowId ?? ""),
|
|
277
|
+
processId: Number(window.processId ?? window.ProcessId),
|
|
278
|
+
title: String(window.title ?? window.Title ?? ""),
|
|
279
|
+
x: Number(window.x ?? window.X),
|
|
280
|
+
y: Number(window.y ?? window.Y),
|
|
281
|
+
width: Number(window.width ?? window.Width),
|
|
282
|
+
height: Number(window.height ?? window.Height),
|
|
283
|
+
isForeground: (window.isForeground ?? window.IsForeground) === true,
|
|
284
|
+
}));
|
|
285
|
+
return { ok: true, windows, count: Math.min(Number(result.count ?? result.Count) || windows.length, limit) };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async focusWindow(input = {}) {
|
|
289
|
+
const gate = this.gate();
|
|
290
|
+
if (gate) return gate;
|
|
291
|
+
const windowId = String(input.windowId ?? "").trim();
|
|
292
|
+
if (!/^[1-9][0-9]{0,19}$/.test(windowId)) return { ok: false, error: "invalid_window_id" };
|
|
293
|
+
return this.runControl(() => this.runner("window_focus", { windowId }));
|
|
294
|
+
}
|
|
295
|
+
|
|
231
296
|
async mouseClick(input = {}) {
|
|
232
297
|
const gate = this.gate();
|
|
233
298
|
if (gate) return gate;
|
|
@@ -241,7 +306,7 @@ export class DesktopManager {
|
|
|
241
306
|
if (!["left", "right", "middle"].includes(button)) return { ok: false, error: "invalid_button" };
|
|
242
307
|
if (clicks !== 1 && clicks !== 2) return { ok: false, error: "invalid_click_count" };
|
|
243
308
|
|
|
244
|
-
return this.runner("mouse_click", { x, y, button, clicks });
|
|
309
|
+
return this.runControl(() => this.runner("mouse_click", { x, y, button, clicks }));
|
|
245
310
|
}
|
|
246
311
|
|
|
247
312
|
async keyboardInput(input = {}) {
|
|
@@ -250,7 +315,7 @@ export class DesktopManager {
|
|
|
250
315
|
|
|
251
316
|
const normalized = normalizeKeyboardInput(input);
|
|
252
317
|
if (!normalized.ok) return normalized;
|
|
253
|
-
return this.runner("keyboard_input", normalized.payload);
|
|
318
|
+
return this.runControl(() => this.runner("keyboard_input", normalized.payload));
|
|
254
319
|
}
|
|
255
320
|
|
|
256
321
|
async step(input = {}) {
|
|
@@ -292,28 +357,68 @@ export class DesktopManager {
|
|
|
292
357
|
return { ok: false, error: "invalid_step_action" };
|
|
293
358
|
}
|
|
294
359
|
|
|
360
|
+
const compactedActions = compactStepActions(normalizedActions);
|
|
361
|
+
const optimizedActions = compactedActions.map((action, index) => {
|
|
362
|
+
const settleAfterMs = adaptiveActionSettleMs(action, compactedActions[index + 1]);
|
|
363
|
+
return settleAfterMs > 0 ? { ...action, settleAfterMs } : action;
|
|
364
|
+
});
|
|
365
|
+
|
|
295
366
|
const monitor = input.monitor ?? "primary";
|
|
296
367
|
if (!(monitor === "primary" || monitor === "secondary" || (Number.isInteger(monitor) && monitor >= 0 && monitor <= 15))) {
|
|
297
368
|
return { ok: false, error: "invalid_monitor" };
|
|
298
369
|
}
|
|
299
370
|
|
|
300
|
-
const
|
|
371
|
+
const wantsCapture = input.captureAfter === true;
|
|
372
|
+
const settleMs = input.settleMs === undefined
|
|
373
|
+
? adaptiveFinalSettleMs(compactedActions, wantsCapture)
|
|
374
|
+
: Number(input.settleMs);
|
|
301
375
|
if (!Number.isInteger(settleMs) || settleMs < 0 || settleMs > 5000) return { ok: false, error: "invalid_settle_ms" };
|
|
302
376
|
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
377
|
+
const maxWidth = input.maxWidth === undefined ? 960 : Number(input.maxWidth);
|
|
378
|
+
const quality = input.quality === undefined ? 58 : Number(input.quality);
|
|
379
|
+
if (wantsCapture && (!Number.isInteger(maxWidth) || maxWidth < MIN_SCREENSHOT_WIDTH || maxWidth > MAX_SCREENSHOT_WIDTH)) {
|
|
380
|
+
return { ok: false, error: "invalid_max_width" };
|
|
381
|
+
}
|
|
382
|
+
if (wantsCapture && (!Number.isInteger(quality) || quality < MIN_SCREENSHOT_QUALITY || quality > MAX_SCREENSHOT_QUALITY)) {
|
|
383
|
+
return { ok: false, error: "invalid_quality" };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const managerStartedAt = Date.now();
|
|
387
|
+
const result = await this.runControl(async (queueMeta = {}) => {
|
|
388
|
+
const handlerStartedAt = Date.now();
|
|
389
|
+
const workerResult = await this.runner("step", {
|
|
390
|
+
actions: optimizedActions,
|
|
391
|
+
captureAfter: wantsCapture,
|
|
392
|
+
settleMs,
|
|
393
|
+
monitor,
|
|
394
|
+
...(wantsCapture ? { maxWidth, quality } : {}),
|
|
395
|
+
});
|
|
396
|
+
if (!workerResult || typeof workerResult !== "object") return workerResult;
|
|
397
|
+
return {
|
|
398
|
+
...workerResult,
|
|
399
|
+
timing: {
|
|
400
|
+
...(workerResult.timing && typeof workerResult.timing === "object" ? workerResult.timing : {}),
|
|
401
|
+
queueWaitMs: Math.max(0, Number(queueMeta.queueWaitMs) || 0),
|
|
402
|
+
managerMs: Math.max(0, Date.now() - handlerStartedAt),
|
|
403
|
+
requestedActions: normalizedActions.length,
|
|
404
|
+
executedActions: optimizedActions.length,
|
|
405
|
+
},
|
|
406
|
+
};
|
|
308
407
|
});
|
|
408
|
+
if (result && typeof result === "object") {
|
|
409
|
+
result.timing = {
|
|
410
|
+
...(result.timing && typeof result.timing === "object" ? result.timing : {}),
|
|
411
|
+
totalMs: Math.max(0, Date.now() - managerStartedAt),
|
|
412
|
+
};
|
|
413
|
+
}
|
|
309
414
|
|
|
310
|
-
if (
|
|
415
|
+
if (!wantsCapture) return result;
|
|
311
416
|
return this.normalizeScreenshotResult(result);
|
|
312
417
|
}
|
|
313
418
|
|
|
314
419
|
close() {
|
|
315
|
-
this.
|
|
420
|
+
this.adapter.close();
|
|
316
421
|
}
|
|
317
422
|
}
|
|
318
423
|
|
|
319
|
-
export { KEY_CODES, MAX_SCREENSHOT_BINARY_BYTES, PersistentDesktopRunner };
|
|
424
|
+
export { KEY_CODES, MAX_SCREENSHOT_BINARY_BYTES, PersistentDesktopRunner, MacOSDesktopRunner };
|