@myagentroam/node 0.11.38 → 0.11.39
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/dist/computer-use/contracts.js +8 -0
- package/dist/{runner/mar-agent/computer-use-indicator.js → computer-use/indicator.js} +61 -38
- package/dist/computer-use/probe.js +95 -0
- package/dist/computer-use/provider.js +159 -0
- package/dist/computer-use/session.js +334 -0
- package/dist/computer-use/worker-source.js +135 -0
- package/dist/computer-use/worker.js +321 -0
- package/dist/connector.js +5 -1
- package/dist/mcp/naming.js +6 -1
- package/dist/runner/mar-agent/computer-use-skill.js +11 -2
- package/dist/runner/mar-agent/computer-use.js +104 -728
- package/dist/runner/mar-agent/conversation-normalizer.js +10 -4
- package/dist/runner/mar-agent/managed-run-controller.js +8 -5
- package/dist/service/computer-use-enablement-service.js +13 -7
- package/dist/service/conversation-hosting-enablement-service.js +2 -2
- package/dist/service/node-config-mutations.js +27 -0
- package/package.json +6 -5
- /package/dist/{runner/mar-agent/computer-use-probe.js → computer-use/probe-source.js} +0 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { ComputerUseError } from './contracts.js';
|
|
3
|
+
import { PythonWorker } from './worker.js';
|
|
4
|
+
export class ComputerUseDesktopLeaseService {
|
|
5
|
+
#holder;
|
|
6
|
+
acquire(runId) {
|
|
7
|
+
if (this.#holder?.runId === runId)
|
|
8
|
+
return this.#holder;
|
|
9
|
+
if (this.#holder !== undefined)
|
|
10
|
+
throw new ComputerUseError('DESKTOP_BUSY', `The desktop is in use by another Run since ${new Date(this.#holder.acquiredAt).toISOString()}.`);
|
|
11
|
+
this.#holder = { runId, leaseId: randomUUID(), acquiredAt: Date.now() };
|
|
12
|
+
return this.#holder;
|
|
13
|
+
}
|
|
14
|
+
release(runId) {
|
|
15
|
+
if (this.#holder?.runId === runId)
|
|
16
|
+
this.#holder = undefined;
|
|
17
|
+
}
|
|
18
|
+
holder() {
|
|
19
|
+
return this.#holder;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class ComputerUseSessionResources {
|
|
23
|
+
runtimeExecutable;
|
|
24
|
+
lease;
|
|
25
|
+
showControlIndicator;
|
|
26
|
+
validateDesktop;
|
|
27
|
+
#worker;
|
|
28
|
+
#observationId;
|
|
29
|
+
#runtimeGeneration = 1;
|
|
30
|
+
#runId;
|
|
31
|
+
#closed = false;
|
|
32
|
+
#bindingClose;
|
|
33
|
+
#closePromise;
|
|
34
|
+
#cleanupError;
|
|
35
|
+
#task;
|
|
36
|
+
#bindingStop = new AbortController();
|
|
37
|
+
#unsubscribeExecution;
|
|
38
|
+
constructor(runtimeExecutable, lease, showControlIndicator, validateDesktop) {
|
|
39
|
+
this.runtimeExecutable = runtimeExecutable;
|
|
40
|
+
this.lease = lease;
|
|
41
|
+
this.showControlIndicator = showControlIndicator;
|
|
42
|
+
this.validateDesktop = validateDesktop;
|
|
43
|
+
}
|
|
44
|
+
attach(runId, signal) {
|
|
45
|
+
if (this.#cleanupError !== undefined)
|
|
46
|
+
throw this.#cleanupError;
|
|
47
|
+
if (this.#closed)
|
|
48
|
+
throw new ComputerUseError('COMPUTER_UNAVAILABLE', 'Computer Use resources are closed.');
|
|
49
|
+
if (this.#runId !== undefined)
|
|
50
|
+
throw new ComputerUseError('COMPUTER_SESSION_BUSY', 'Computer Use Session resources are already attached to another Run.');
|
|
51
|
+
this.#runId = runId;
|
|
52
|
+
this.#observationId = undefined;
|
|
53
|
+
this.#task = undefined;
|
|
54
|
+
this.#bindingStop = new AbortController();
|
|
55
|
+
const abort = () => {
|
|
56
|
+
this.#bindingStop.abort();
|
|
57
|
+
if (this.#task?.settled === undefined)
|
|
58
|
+
this.#task?.stop.abort();
|
|
59
|
+
};
|
|
60
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
61
|
+
this.#unsubscribeExecution = () => signal?.removeEventListener('abort', abort);
|
|
62
|
+
if (signal?.aborted)
|
|
63
|
+
abort();
|
|
64
|
+
}
|
|
65
|
+
async detach(runId) {
|
|
66
|
+
if (this.#runId !== runId)
|
|
67
|
+
return;
|
|
68
|
+
this.#bindingStop.abort();
|
|
69
|
+
this.#unsubscribeExecution?.();
|
|
70
|
+
this.#unsubscribeExecution = undefined;
|
|
71
|
+
if (this.#task?.settled === undefined)
|
|
72
|
+
this.#task?.stop.abort();
|
|
73
|
+
await this.#task?.completion;
|
|
74
|
+
if (this.#cleanupError !== undefined)
|
|
75
|
+
throw this.#cleanupError;
|
|
76
|
+
if (this.#closed)
|
|
77
|
+
return;
|
|
78
|
+
this.#observationId = undefined;
|
|
79
|
+
this.#task = undefined;
|
|
80
|
+
this.lease.release(runId);
|
|
81
|
+
this.#runId = undefined;
|
|
82
|
+
this.#bindingClose = undefined;
|
|
83
|
+
}
|
|
84
|
+
setBindingClose(close) {
|
|
85
|
+
this.#bindingClose = close;
|
|
86
|
+
}
|
|
87
|
+
async call(input, signal) {
|
|
88
|
+
const runId = this.#assertBound();
|
|
89
|
+
if (signal.aborted)
|
|
90
|
+
throw new ComputerUseError('COMPUTER_UNAVAILABLE', 'Computer Use call was cancelled.');
|
|
91
|
+
if (this.#cleanupError !== undefined)
|
|
92
|
+
throw this.#cleanupError;
|
|
93
|
+
if (this.#task !== undefined && this.#task.settled === undefined)
|
|
94
|
+
throw new ComputerUseError('COMPUTER_SESSION_BUSY', 'Computer Use is already executing.');
|
|
95
|
+
if (input.basedOnObservationId !== undefined &&
|
|
96
|
+
input.basedOnObservationId !== this.#observationId)
|
|
97
|
+
throw new ComputerUseError('COMPUTER_OBSERVATION_STALE', 'The desktop observation is stale; observe again before acting.');
|
|
98
|
+
this.lease.acquire(runId);
|
|
99
|
+
this.#observationId = undefined;
|
|
100
|
+
const stop = new AbortController();
|
|
101
|
+
const abort = () => stop.abort();
|
|
102
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
103
|
+
const task = {
|
|
104
|
+
id: `cu_${randomUUID()}`,
|
|
105
|
+
stop,
|
|
106
|
+
// Admission is synchronous; preparation and every failure belong to this slot.
|
|
107
|
+
completion: Promise.resolve()
|
|
108
|
+
.then(() => this.#execute(input, stop.signal, runId))
|
|
109
|
+
.then((result) => (task.settled = { result }), (error) => (task.settled = { error }))
|
|
110
|
+
};
|
|
111
|
+
this.#task = task;
|
|
112
|
+
try {
|
|
113
|
+
return await this.#waitTask(task, input.yieldMs);
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
signal.removeEventListener('abort', abort);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async wait(input, signal) {
|
|
120
|
+
this.#assertBound();
|
|
121
|
+
if (signal.aborted)
|
|
122
|
+
throw waitCancelled();
|
|
123
|
+
const task = this.#task;
|
|
124
|
+
if (task === undefined || task.id !== input.taskId)
|
|
125
|
+
throw new ComputerUseError('COMPUTER_TASK_NOT_FOUND', 'The task is not available in this binding.');
|
|
126
|
+
if (input.action === 'cancel' && task.settled === undefined)
|
|
127
|
+
task.stop.abort();
|
|
128
|
+
return this.#waitTask(task, input.yieldMs, signal);
|
|
129
|
+
}
|
|
130
|
+
#assertBound() {
|
|
131
|
+
if (this.#closed || this.#runId === undefined || this.#bindingStop.signal.aborted)
|
|
132
|
+
throw new ComputerUseError('COMPUTER_UNAVAILABLE', 'Computer Use binding is closed.');
|
|
133
|
+
return this.#runId;
|
|
134
|
+
}
|
|
135
|
+
async #waitTask(task, yieldMs, signal) {
|
|
136
|
+
let timer;
|
|
137
|
+
let onAbort;
|
|
138
|
+
try {
|
|
139
|
+
if (task.settled === undefined && yieldMs !== 0) {
|
|
140
|
+
const waits = [task.completion];
|
|
141
|
+
if (yieldMs !== undefined)
|
|
142
|
+
waits.push(new Promise((resolve) => {
|
|
143
|
+
timer = setTimeout(resolve, yieldMs);
|
|
144
|
+
}));
|
|
145
|
+
if (signal !== undefined)
|
|
146
|
+
waits.push(new Promise((_, reject) => {
|
|
147
|
+
onAbort = () => reject(waitCancelled());
|
|
148
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
149
|
+
if (signal.aborted)
|
|
150
|
+
onAbort();
|
|
151
|
+
}));
|
|
152
|
+
await Promise.race(waits);
|
|
153
|
+
}
|
|
154
|
+
if (this.#task !== task)
|
|
155
|
+
throw new ComputerUseError('COMPUTER_TASK_NOT_FOUND', 'The task is no longer available in this binding.');
|
|
156
|
+
if (task.settled === undefined)
|
|
157
|
+
return { taskId: task.id, status: task.stop.signal.aborted ? 'STOPPING' : 'RUNNING' };
|
|
158
|
+
if ('error' in task.settled)
|
|
159
|
+
throw task.settled.error;
|
|
160
|
+
return { ...task.settled.result, taskId: task.id, status: 'COMPLETED' };
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
if (timer !== undefined)
|
|
164
|
+
clearTimeout(timer);
|
|
165
|
+
if (onAbort !== undefined)
|
|
166
|
+
signal?.removeEventListener('abort', onAbort);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async #execute(input, signal, runId) {
|
|
170
|
+
this.#assertActive(signal, runId);
|
|
171
|
+
await this.validateDesktop();
|
|
172
|
+
this.#assertActive(signal, runId);
|
|
173
|
+
let indicator;
|
|
174
|
+
try {
|
|
175
|
+
indicator = await this.showControlIndicator(this.runtimeExecutable);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (error instanceof ComputerUseError && error.code === 'COMPUTER_CLEANUP_FAILED') {
|
|
179
|
+
this.#cleanupError = error;
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
throw new ComputerUseError('COMPUTER_CONTROL_INDICATOR_UNAVAILABLE', 'The desktop control indicator could not be displayed.', { cause: error });
|
|
183
|
+
}
|
|
184
|
+
let result;
|
|
185
|
+
let primaryError;
|
|
186
|
+
let failed = false;
|
|
187
|
+
try {
|
|
188
|
+
this.#assertActive(signal, runId);
|
|
189
|
+
const worker = await this.#ensureWorker();
|
|
190
|
+
this.#assertActive(signal, runId);
|
|
191
|
+
let executionStarted = false;
|
|
192
|
+
try {
|
|
193
|
+
indicator.assertActive?.();
|
|
194
|
+
executionStarted = true;
|
|
195
|
+
const execution = worker.execute(input.code, input.timeoutMs, signal);
|
|
196
|
+
const executionResult = await (indicator.lost === undefined
|
|
197
|
+
? execution
|
|
198
|
+
: Promise.race([
|
|
199
|
+
execution,
|
|
200
|
+
indicator.lost.then((error) => {
|
|
201
|
+
throw error;
|
|
202
|
+
})
|
|
203
|
+
]));
|
|
204
|
+
result = {
|
|
205
|
+
outcome: 'SUCCEEDED',
|
|
206
|
+
observationId: randomUUID(),
|
|
207
|
+
runtimeGeneration: this.#runtimeGeneration,
|
|
208
|
+
logs: executionResult.logs,
|
|
209
|
+
images: executionResult.images
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
if (executionStarted && worker.startedRequest) {
|
|
214
|
+
try {
|
|
215
|
+
await this.#resetWorker();
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// The worker owner records cleanup failure; the sent request remains uncertain.
|
|
219
|
+
}
|
|
220
|
+
this.#observationId = undefined;
|
|
221
|
+
const message = error instanceof Error ? error.message : 'Computer Use execution ended unexpectedly.';
|
|
222
|
+
result = {
|
|
223
|
+
message,
|
|
224
|
+
outcome: 'OUTCOME_UNKNOWN',
|
|
225
|
+
observationId: '',
|
|
226
|
+
runtimeGeneration: this.#runtimeGeneration,
|
|
227
|
+
logs: []
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
else
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
failed = true;
|
|
236
|
+
primaryError = error;
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
await indicator.close();
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
this.#observationId = undefined;
|
|
243
|
+
this.#cleanupError ??= new ComputerUseError('COMPUTER_CLEANUP_FAILED', 'Computer Use indicator cleanup failed.', { cause: error });
|
|
244
|
+
}
|
|
245
|
+
if (failed) {
|
|
246
|
+
if (this.#cleanupError !== undefined)
|
|
247
|
+
throw withCleanupFailure(primaryError, this.#cleanupError);
|
|
248
|
+
throw primaryError;
|
|
249
|
+
}
|
|
250
|
+
if (result === undefined)
|
|
251
|
+
throw new Error('Computer Use execution returned no result.');
|
|
252
|
+
if (this.#cleanupError !== undefined)
|
|
253
|
+
return { ...result, observationId: '', cleanupFailed: true };
|
|
254
|
+
this.#observationId = result.observationId || undefined;
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
close() {
|
|
258
|
+
if (this.#closePromise !== undefined)
|
|
259
|
+
return this.#closePromise;
|
|
260
|
+
this.#closed = true;
|
|
261
|
+
this.#observationId = undefined;
|
|
262
|
+
this.#closePromise = (async () => {
|
|
263
|
+
let failure;
|
|
264
|
+
try {
|
|
265
|
+
await this.#bindingClose?.();
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
failure = error;
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
await this.#resetWorker();
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
failure ??= error;
|
|
275
|
+
}
|
|
276
|
+
if (failure !== undefined)
|
|
277
|
+
throw failure;
|
|
278
|
+
if (this.#runId !== undefined)
|
|
279
|
+
this.lease.release(this.#runId);
|
|
280
|
+
this.#runId = undefined;
|
|
281
|
+
this.#bindingClose = undefined;
|
|
282
|
+
this.#task = undefined;
|
|
283
|
+
})();
|
|
284
|
+
return this.#closePromise;
|
|
285
|
+
}
|
|
286
|
+
#assertActive(signal, runId) {
|
|
287
|
+
if (signal.aborted || this.#closed || this.#runId !== runId)
|
|
288
|
+
throw new ComputerUseError('COMPUTER_UNAVAILABLE', 'Computer Use binding is closed.');
|
|
289
|
+
}
|
|
290
|
+
async #ensureWorker() {
|
|
291
|
+
if (this.#worker !== undefined)
|
|
292
|
+
return this.#worker;
|
|
293
|
+
try {
|
|
294
|
+
return await PythonWorker.start(this.runtimeExecutable, (worker) => {
|
|
295
|
+
this.#worker = worker;
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
if (error instanceof ComputerUseError && error.code === 'COMPUTER_CLEANUP_FAILED')
|
|
300
|
+
this.#cleanupError = error;
|
|
301
|
+
try {
|
|
302
|
+
await this.#resetWorker();
|
|
303
|
+
}
|
|
304
|
+
catch (cleanupError) {
|
|
305
|
+
throw withCleanupFailure(error, cleanupError);
|
|
306
|
+
}
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async #resetWorker() {
|
|
311
|
+
const worker = this.#worker;
|
|
312
|
+
if (worker !== undefined) {
|
|
313
|
+
try {
|
|
314
|
+
await worker.close();
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
this.#cleanupError = new ComputerUseError('COMPUTER_CLEANUP_FAILED', 'Computer Use worker cleanup failed.', { cause: error });
|
|
318
|
+
throw this.#cleanupError;
|
|
319
|
+
}
|
|
320
|
+
this.#worker = undefined;
|
|
321
|
+
this.#runtimeGeneration++;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function waitCancelled() {
|
|
326
|
+
return new ComputerUseError('COMPUTER_WAIT_CANCELLED', 'Waiting was cancelled; use wait to inspect the task.');
|
|
327
|
+
}
|
|
328
|
+
function withCleanupFailure(primary, cleanup) {
|
|
329
|
+
const message = `${primary instanceof Error ? primary.message : 'Computer Use execution failed.'} Computer Use cleanup failed; further use is blocked.`;
|
|
330
|
+
const cause = new AggregateError([primary, cleanup], 'Computer Use execution and cleanup failed');
|
|
331
|
+
return primary instanceof ComputerUseError
|
|
332
|
+
? new ComputerUseError(primary.code, message, { cause })
|
|
333
|
+
: new Error(message, { cause });
|
|
334
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
export const MAX_IMAGE_BYTES = 16 * 1024 * 1024;
|
|
2
|
+
export const MAX_IMAGES = 4;
|
|
3
|
+
export const PYTHON_WORKER_SOURCE = String.raw `
|
|
4
|
+
import base64
|
|
5
|
+
import contextlib
|
|
6
|
+
import io
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
import traceback
|
|
11
|
+
|
|
12
|
+
import pyautogui
|
|
13
|
+
|
|
14
|
+
namespace = {"pyautogui": pyautogui, "time": time}
|
|
15
|
+
current_logs = []
|
|
16
|
+
current_images = []
|
|
17
|
+
|
|
18
|
+
def _emit(message):
|
|
19
|
+
sys.__stdout__.write(json.dumps(message, ensure_ascii=False, separators=(",", ":")) + "\n")
|
|
20
|
+
sys.__stdout__.flush()
|
|
21
|
+
|
|
22
|
+
def log(value):
|
|
23
|
+
current_logs.append(str(value))
|
|
24
|
+
|
|
25
|
+
def _image_bytes(image):
|
|
26
|
+
if hasattr(image, "save"):
|
|
27
|
+
buffer = io.BytesIO()
|
|
28
|
+
image.save(buffer, format="PNG")
|
|
29
|
+
value = buffer.getvalue()
|
|
30
|
+
if len(value) > ${MAX_IMAGE_BYTES}:
|
|
31
|
+
raise ValueError("display image exceeds the size limit")
|
|
32
|
+
return value
|
|
33
|
+
if isinstance(image, (bytes, bytearray)):
|
|
34
|
+
value = bytes(image)
|
|
35
|
+
if len(value) > ${MAX_IMAGE_BYTES}:
|
|
36
|
+
raise ValueError("display image exceeds the size limit")
|
|
37
|
+
return value
|
|
38
|
+
raise TypeError("display expects a PIL image or PNG bytes")
|
|
39
|
+
|
|
40
|
+
def display(image):
|
|
41
|
+
if len(current_images) >= ${MAX_IMAGES}:
|
|
42
|
+
raise ValueError("too many displayed images")
|
|
43
|
+
current_images.append({
|
|
44
|
+
"mimeType": "image/png",
|
|
45
|
+
"dataBase64": base64.b64encode(_image_bytes(image)).decode("ascii")
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
def paste_text(text):
|
|
49
|
+
value = str(text)
|
|
50
|
+
try:
|
|
51
|
+
import pyperclip
|
|
52
|
+
previous = None
|
|
53
|
+
try:
|
|
54
|
+
previous = pyperclip.paste()
|
|
55
|
+
except Exception:
|
|
56
|
+
pass
|
|
57
|
+
with open("clipboard.json", "w", encoding="utf-8") as clipboard_file:
|
|
58
|
+
json.dump({"value": previous}, clipboard_file, ensure_ascii=False)
|
|
59
|
+
pyperclip.copy(value)
|
|
60
|
+
pyautogui.hotkey("command" if sys.platform == "darwin" else "ctrl", "v")
|
|
61
|
+
time.sleep(0.05)
|
|
62
|
+
if previous is not None:
|
|
63
|
+
pyperclip.copy(previous)
|
|
64
|
+
try:
|
|
65
|
+
import os
|
|
66
|
+
os.remove("clipboard.json")
|
|
67
|
+
except OSError:
|
|
68
|
+
pass
|
|
69
|
+
return
|
|
70
|
+
except Exception as error:
|
|
71
|
+
raise RuntimeError("Unicode clipboard paste is unavailable") from error
|
|
72
|
+
|
|
73
|
+
namespace.update({"display": display, "log": log, "paste_text": paste_text})
|
|
74
|
+
_emit({"type": "ready"})
|
|
75
|
+
|
|
76
|
+
for raw in sys.stdin:
|
|
77
|
+
request = None
|
|
78
|
+
try:
|
|
79
|
+
request = json.loads(raw)
|
|
80
|
+
request_id = request["id"]
|
|
81
|
+
code = request["code"]
|
|
82
|
+
current_logs = []
|
|
83
|
+
current_images = []
|
|
84
|
+
captured_stdout = io.StringIO()
|
|
85
|
+
captured_stderr = io.StringIO()
|
|
86
|
+
with contextlib.redirect_stdout(captured_stdout), contextlib.redirect_stderr(captured_stderr):
|
|
87
|
+
if code:
|
|
88
|
+
exec(compile(code, "<computer-use>", "exec"), namespace, namespace)
|
|
89
|
+
if not current_images:
|
|
90
|
+
display(pyautogui.screenshot())
|
|
91
|
+
stdout_value = captured_stdout.getvalue()
|
|
92
|
+
stderr_value = captured_stderr.getvalue()
|
|
93
|
+
if stdout_value:
|
|
94
|
+
current_logs.append(stdout_value.rstrip())
|
|
95
|
+
if stderr_value:
|
|
96
|
+
current_logs.append(stderr_value.rstrip())
|
|
97
|
+
_emit({
|
|
98
|
+
"type": "result",
|
|
99
|
+
"id": request_id,
|
|
100
|
+
"logs": current_logs,
|
|
101
|
+
"images": current_images
|
|
102
|
+
})
|
|
103
|
+
except BaseException:
|
|
104
|
+
_emit({
|
|
105
|
+
"type": "error",
|
|
106
|
+
"id": request.get("id") if isinstance(request, dict) else "",
|
|
107
|
+
"error": traceback.format_exc(limit=20)
|
|
108
|
+
})
|
|
109
|
+
`;
|
|
110
|
+
export const PYTHON_CLEANUP_SOURCE = String.raw `
|
|
111
|
+
import json
|
|
112
|
+
import os
|
|
113
|
+
|
|
114
|
+
import pyautogui
|
|
115
|
+
|
|
116
|
+
for key in ("ctrl", "shift", "alt", "win", "command"):
|
|
117
|
+
try:
|
|
118
|
+
pyautogui.keyUp(key)
|
|
119
|
+
except Exception:
|
|
120
|
+
pass
|
|
121
|
+
for button in ("left", "middle", "right"):
|
|
122
|
+
try:
|
|
123
|
+
pyautogui.mouseUp(button=button)
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
try:
|
|
127
|
+
import pyperclip
|
|
128
|
+
if os.path.exists("clipboard.json"):
|
|
129
|
+
with open("clipboard.json", "r", encoding="utf-8") as clipboard_file:
|
|
130
|
+
previous = json.load(clipboard_file).get("value")
|
|
131
|
+
if previous is not None:
|
|
132
|
+
pyperclip.copy(previous)
|
|
133
|
+
except Exception:
|
|
134
|
+
pass
|
|
135
|
+
`;
|