@myagentroam/node 0.9.125 → 0.9.127
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/codex-app-server.js +30 -0
- package/dist/connector.js +65 -11
- package/dist/database.js +56 -1
- package/dist/migrations/v015.js +12 -0
- package/dist/runner/abstract-runner.js +13 -6
- package/dist/runner/codex-runner.js +32 -39
- package/dist/runner/mar-agent/computer-use-indicator.js +484 -0
- package/dist/runner/mar-agent/computer-use.js +54 -26
- package/dist/service/continuous-run-coordinator.js +12 -3
- package/dist/service/mail-notification-control.js +69 -0
- package/dist/service/mail-notification-mcp-server.js +150 -0
- package/dist/service/mail-notification-mcp-stdio.js +56 -0
- package/dist/service/mcp-node-operation-service.js +7 -1
- package/dist/service/node-control-message-service.js +2 -0
- package/dist/service/scheduled-session-store.js +13 -6
- package/dist/service/scheduled-task-mcp-server.js +4 -1
- package/dist/service/session-schedule-service.js +6 -1
- package/package.json +4 -4
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import { createInterface } from 'node:readline';
|
|
6
|
+
import { terminateProcessTree } from '../../process-tree.js';
|
|
7
|
+
const START_TIMEOUT_MS = 5_000;
|
|
8
|
+
const STOP_TIMEOUT_MS = 2_000;
|
|
9
|
+
/** A non-activating, pointer-transparent primary-display control marker. */
|
|
10
|
+
export class ComputerUseControlIndicator {
|
|
11
|
+
child;
|
|
12
|
+
directory;
|
|
13
|
+
#closed = false;
|
|
14
|
+
#lostError;
|
|
15
|
+
lost;
|
|
16
|
+
constructor(child, directory) {
|
|
17
|
+
this.child = child;
|
|
18
|
+
this.directory = directory;
|
|
19
|
+
this.lost = new Promise((resolveLost) => {
|
|
20
|
+
child.once('exit', (code, signal) => {
|
|
21
|
+
if (this.#closed)
|
|
22
|
+
return;
|
|
23
|
+
this.#lostError = new Error(`Computer Use control indicator exited (${code ?? signal ?? 'unknown'}).`);
|
|
24
|
+
resolveLost(this.#lostError);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
static async show(runtimeExecutable) {
|
|
29
|
+
const directory = await mkdtemp(resolve(tmpdir(), 'mar-computer-use-indicator-'));
|
|
30
|
+
const script = resolve(directory, 'indicator.py');
|
|
31
|
+
await writeFile(script, PYTHON_INDICATOR_SOURCE, { encoding: 'utf8', mode: 0o600 });
|
|
32
|
+
const child = spawn(runtimeExecutable, ['-u', script], {
|
|
33
|
+
cwd: directory,
|
|
34
|
+
env: indicatorEnvironment(),
|
|
35
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
36
|
+
windowsHide: true,
|
|
37
|
+
detached: process.platform !== 'win32'
|
|
38
|
+
});
|
|
39
|
+
const indicator = new ComputerUseControlIndicator(child, directory);
|
|
40
|
+
try {
|
|
41
|
+
await waitUntilVisible(child);
|
|
42
|
+
return indicator;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
await indicator.close();
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async close() {
|
|
50
|
+
if (this.#closed)
|
|
51
|
+
return;
|
|
52
|
+
this.#closed = true;
|
|
53
|
+
this.child.stdin.end();
|
|
54
|
+
if (this.child.exitCode === null && this.child.signalCode === null) {
|
|
55
|
+
await terminateProcessTree(this.child).catch(() => {
|
|
56
|
+
try {
|
|
57
|
+
this.child.kill('SIGTERM');
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// The process may have exited between the status check and termination.
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
if (!(await waitForExit(this.child, STOP_TIMEOUT_MS))) {
|
|
64
|
+
try {
|
|
65
|
+
this.child.kill('SIGKILL');
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// The process may have exited before the fallback signal.
|
|
69
|
+
}
|
|
70
|
+
await waitForExit(this.child, STOP_TIMEOUT_MS);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
await rm(this.directory, { recursive: true, force: true }).catch(() => undefined);
|
|
74
|
+
}
|
|
75
|
+
assertActive() {
|
|
76
|
+
if (this.#lostError !== undefined)
|
|
77
|
+
throw this.#lostError;
|
|
78
|
+
if (this.child.exitCode !== null || this.child.signalCode !== null)
|
|
79
|
+
throw new Error('Computer Use control indicator is no longer active.');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function waitForExit(child, timeoutMs) {
|
|
83
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
84
|
+
return true;
|
|
85
|
+
return await new Promise((resolveExit) => {
|
|
86
|
+
const timer = setTimeout(() => finish(false), timeoutMs);
|
|
87
|
+
const exit = () => finish(true);
|
|
88
|
+
const finish = (exited) => {
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
child.off('exit', exit);
|
|
91
|
+
resolveExit(exited);
|
|
92
|
+
};
|
|
93
|
+
child.once('exit', exit);
|
|
94
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
95
|
+
finish(true);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async function waitUntilVisible(child) {
|
|
99
|
+
const lines = createInterface({ input: child.stdout, crlfDelay: Infinity });
|
|
100
|
+
try {
|
|
101
|
+
await new Promise((resolveVisible, reject) => {
|
|
102
|
+
let stderr = '';
|
|
103
|
+
const timer = setTimeout(() => finish(() => reject(new Error('Computer Use control indicator start timed out.'))), START_TIMEOUT_MS);
|
|
104
|
+
const finish = (callback) => {
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
lines.off('line', line);
|
|
107
|
+
child.off('exit', exit);
|
|
108
|
+
child.off('error', failed);
|
|
109
|
+
callback();
|
|
110
|
+
};
|
|
111
|
+
const line = (value) => {
|
|
112
|
+
if (value !== 'ready')
|
|
113
|
+
return;
|
|
114
|
+
finish(resolveVisible);
|
|
115
|
+
};
|
|
116
|
+
const exit = () => finish(() => reject(new Error(`Computer Use control indicator exited during startup.${stderr ? ` ${stderr}` : ''}`)));
|
|
117
|
+
const failed = (error) => finish(() => reject(error));
|
|
118
|
+
child.stderr.on('data', (chunk) => {
|
|
119
|
+
stderr = `${stderr}${chunk.toString('utf8')}`.slice(-1_000).trim();
|
|
120
|
+
});
|
|
121
|
+
lines.on('line', line);
|
|
122
|
+
child.once('exit', exit);
|
|
123
|
+
child.once('error', failed);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
lines.close();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function indicatorEnvironment() {
|
|
131
|
+
const environment = {};
|
|
132
|
+
for (const key of [
|
|
133
|
+
'PATH',
|
|
134
|
+
'SystemRoot',
|
|
135
|
+
'WINDIR',
|
|
136
|
+
'TEMP',
|
|
137
|
+
'TMP',
|
|
138
|
+
'TMPDIR',
|
|
139
|
+
'HOME',
|
|
140
|
+
'USERPROFILE',
|
|
141
|
+
'DISPLAY',
|
|
142
|
+
'XAUTHORITY',
|
|
143
|
+
'LANG',
|
|
144
|
+
'LC_ALL'
|
|
145
|
+
]) {
|
|
146
|
+
const value = process.env[key];
|
|
147
|
+
if (value !== undefined)
|
|
148
|
+
environment[key] = value;
|
|
149
|
+
}
|
|
150
|
+
return environment;
|
|
151
|
+
}
|
|
152
|
+
const PYTHON_INDICATOR_SOURCE = String.raw `
|
|
153
|
+
import sys
|
|
154
|
+
|
|
155
|
+
COLOR = (235, 151, 55)
|
|
156
|
+
LABEL = "AI CONTROL"
|
|
157
|
+
THICKNESS = 3
|
|
158
|
+
|
|
159
|
+
def ready():
|
|
160
|
+
sys.stdout.write("ready\n")
|
|
161
|
+
sys.stdout.flush()
|
|
162
|
+
|
|
163
|
+
if sys.platform == "win32":
|
|
164
|
+
import ctypes
|
|
165
|
+
from ctypes import wintypes
|
|
166
|
+
|
|
167
|
+
user32 = ctypes.windll.user32
|
|
168
|
+
gdi32 = ctypes.windll.gdi32
|
|
169
|
+
WNDPROC = ctypes.WINFUNCTYPE(
|
|
170
|
+
ctypes.c_ssize_t, wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM
|
|
171
|
+
)
|
|
172
|
+
WM_PAINT = 0x000F
|
|
173
|
+
WM_DESTROY = 0x0002
|
|
174
|
+
WM_NCHITTEST = 0x0084
|
|
175
|
+
HTTRANSPARENT = -1
|
|
176
|
+
WS_POPUP = 0x80000000
|
|
177
|
+
WS_EX_LAYERED = 0x00080000
|
|
178
|
+
WS_EX_TRANSPARENT = 0x00000020
|
|
179
|
+
WS_EX_TOOLWINDOW = 0x00000080
|
|
180
|
+
WS_EX_NOACTIVATE = 0x08000000
|
|
181
|
+
HWND_TOPMOST = -1
|
|
182
|
+
SWP_NOACTIVATE = 0x0010
|
|
183
|
+
SW_SHOWNA = 8
|
|
184
|
+
RGN_OR = 2
|
|
185
|
+
TRANSPARENT = 1
|
|
186
|
+
DT_CENTER = 0x00000001
|
|
187
|
+
DT_VCENTER = 0x00000004
|
|
188
|
+
DT_SINGLELINE = 0x00000020
|
|
189
|
+
|
|
190
|
+
class WNDCLASS(ctypes.Structure):
|
|
191
|
+
_fields_ = [
|
|
192
|
+
("style", wintypes.UINT), ("lpfnWndProc", WNDPROC),
|
|
193
|
+
("cbClsExtra", ctypes.c_int), ("cbWndExtra", ctypes.c_int),
|
|
194
|
+
("hInstance", wintypes.HINSTANCE), ("hIcon", wintypes.HICON),
|
|
195
|
+
("hCursor", wintypes.HANDLE), ("hbrBackground", wintypes.HBRUSH),
|
|
196
|
+
("lpszMenuName", wintypes.LPCWSTR), ("lpszClassName", wintypes.LPCWSTR)
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
class PAINTSTRUCT(ctypes.Structure):
|
|
200
|
+
_fields_ = [
|
|
201
|
+
("hdc", wintypes.HDC), ("fErase", wintypes.BOOL),
|
|
202
|
+
("rcPaint", wintypes.RECT), ("fRestore", wintypes.BOOL),
|
|
203
|
+
("fIncUpdate", wintypes.BOOL), ("rgbReserved", ctypes.c_byte * 32)
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
rgb = COLOR[0] | (COLOR[1] << 8) | (COLOR[2] << 16)
|
|
207
|
+
brush = gdi32.CreateSolidBrush(rgb)
|
|
208
|
+
dark_brush = gdi32.CreateSolidBrush(0x00251C12)
|
|
209
|
+
|
|
210
|
+
@WNDPROC
|
|
211
|
+
def window_proc(hwnd, message, wparam, lparam):
|
|
212
|
+
if message == WM_PAINT:
|
|
213
|
+
paint = PAINTSTRUCT()
|
|
214
|
+
dc = user32.BeginPaint(hwnd, ctypes.byref(paint))
|
|
215
|
+
rect = wintypes.RECT()
|
|
216
|
+
user32.GetClientRect(hwnd, ctypes.byref(rect))
|
|
217
|
+
user32.FillRect(dc, ctypes.byref(rect), brush)
|
|
218
|
+
label = wintypes.RECT(rect.right - 130, 8, rect.right - 12, 34)
|
|
219
|
+
user32.FillRect(dc, ctypes.byref(label), dark_brush)
|
|
220
|
+
gdi32.SetBkMode(dc, TRANSPARENT)
|
|
221
|
+
gdi32.SetTextColor(dc, rgb)
|
|
222
|
+
user32.DrawTextW(dc, LABEL, -1, ctypes.byref(label), DT_CENTER | DT_VCENTER | DT_SINGLELINE)
|
|
223
|
+
user32.EndPaint(hwnd, ctypes.byref(paint))
|
|
224
|
+
return 0
|
|
225
|
+
if message == WM_NCHITTEST:
|
|
226
|
+
return HTTRANSPARENT
|
|
227
|
+
if message == WM_DESTROY:
|
|
228
|
+
user32.PostQuitMessage(0)
|
|
229
|
+
return 0
|
|
230
|
+
return user32.DefWindowProcW(hwnd, message, wparam, lparam)
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
user32.SetProcessDpiAwarenessContext(ctypes.c_void_p(-4))
|
|
234
|
+
except Exception:
|
|
235
|
+
try:
|
|
236
|
+
user32.SetProcessDPIAware()
|
|
237
|
+
except Exception:
|
|
238
|
+
pass
|
|
239
|
+
instance = ctypes.windll.kernel32.GetModuleHandleW(None)
|
|
240
|
+
class_name = "MyAgentRoamComputerUseIndicator"
|
|
241
|
+
window_class = WNDCLASS(0, window_proc, 0, 0, instance, None, None, None, None, class_name)
|
|
242
|
+
user32.RegisterClassW(ctypes.byref(window_class))
|
|
243
|
+
width = user32.GetSystemMetrics(0)
|
|
244
|
+
height = user32.GetSystemMetrics(1)
|
|
245
|
+
hwnd = user32.CreateWindowExW(
|
|
246
|
+
WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE,
|
|
247
|
+
class_name, "", WS_POPUP, 0, 0, width, height, None, None, instance, None
|
|
248
|
+
)
|
|
249
|
+
if not hwnd:
|
|
250
|
+
raise RuntimeError("cannot create the control indicator")
|
|
251
|
+
user32.SetLayeredWindowAttributes(hwnd, 0, 220, 0x00000002)
|
|
252
|
+
region = gdi32.CreateRectRgn(0, 0, width, THICKNESS)
|
|
253
|
+
for x, y, right, bottom in (
|
|
254
|
+
(0, height - THICKNESS, width, height),
|
|
255
|
+
(0, 0, THICKNESS, height),
|
|
256
|
+
(width - THICKNESS, 0, width, height),
|
|
257
|
+
(width - 142, 6, width - 8, 38),
|
|
258
|
+
):
|
|
259
|
+
part = gdi32.CreateRectRgn(x, y, right, bottom)
|
|
260
|
+
gdi32.CombineRgn(region, region, part, RGN_OR)
|
|
261
|
+
gdi32.DeleteObject(part)
|
|
262
|
+
user32.SetWindowRgn(hwnd, region, True)
|
|
263
|
+
user32.SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, width, height, SWP_NOACTIVATE)
|
|
264
|
+
user32.ShowWindow(hwnd, SW_SHOWNA)
|
|
265
|
+
user32.UpdateWindow(hwnd)
|
|
266
|
+
ready()
|
|
267
|
+
message = wintypes.MSG()
|
|
268
|
+
while user32.GetMessageW(ctypes.byref(message), None, 0, 0) > 0:
|
|
269
|
+
user32.TranslateMessage(ctypes.byref(message))
|
|
270
|
+
user32.DispatchMessageW(ctypes.byref(message))
|
|
271
|
+
|
|
272
|
+
elif sys.platform == "darwin":
|
|
273
|
+
from AppKit import (
|
|
274
|
+
NSApplication, NSApplicationActivationPolicyAccessory, NSBezierPath, NSColor,
|
|
275
|
+
NSFont, NSFontAttributeName, NSForegroundColorAttributeName, NSMakeRect, NSPanel,
|
|
276
|
+
NSScreen, NSStatusWindowLevel, NSView, NSWindowCollectionBehaviorCanJoinAllSpaces,
|
|
277
|
+
NSWindowCollectionBehaviorFullScreenAuxiliary, NSWindowStyleMaskBorderless,
|
|
278
|
+
NSWindowStyleMaskNonactivatingPanel
|
|
279
|
+
)
|
|
280
|
+
from Foundation import NSDictionary, NSString
|
|
281
|
+
|
|
282
|
+
class IndicatorView(NSView):
|
|
283
|
+
def drawRect_(self, rect):
|
|
284
|
+
NSColor.clearColor().set()
|
|
285
|
+
NSBezierPath.fillRect_(rect)
|
|
286
|
+
color = NSColor.colorWithCalibratedRed_green_blue_alpha_(
|
|
287
|
+
COLOR[0] / 255, COLOR[1] / 255, COLOR[2] / 255, 0.88
|
|
288
|
+
)
|
|
289
|
+
color.setStroke()
|
|
290
|
+
path = NSBezierPath.bezierPathWithRect_(NSMakeRect(1.5, 1.5, rect.size.width - 3, rect.size.height - 3))
|
|
291
|
+
path.setLineWidth_(THICKNESS)
|
|
292
|
+
path.stroke()
|
|
293
|
+
attributes = NSDictionary.dictionaryWithObjects_forKeys_(
|
|
294
|
+
[NSFont.boldSystemFontOfSize_(12), color],
|
|
295
|
+
[NSFontAttributeName, NSForegroundColorAttributeName]
|
|
296
|
+
)
|
|
297
|
+
NSString.stringWithString_(LABEL).drawAtPoint_withAttributes_(
|
|
298
|
+
(rect.size.width - 94, rect.size.height - 27), attributes
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
application = NSApplication.sharedApplication()
|
|
302
|
+
application.setActivationPolicy_(NSApplicationActivationPolicyAccessory)
|
|
303
|
+
screen = NSScreen.mainScreen()
|
|
304
|
+
frame = screen.frame()
|
|
305
|
+
panel = NSPanel.alloc().initWithContentRect_styleMask_backing_defer_(
|
|
306
|
+
frame, NSWindowStyleMaskBorderless | NSWindowStyleMaskNonactivatingPanel, 2, False
|
|
307
|
+
)
|
|
308
|
+
panel.setOpaque_(False)
|
|
309
|
+
panel.setBackgroundColor_(NSColor.clearColor())
|
|
310
|
+
panel.setHasShadow_(False)
|
|
311
|
+
panel.setIgnoresMouseEvents_(True)
|
|
312
|
+
panel.setLevel_(NSStatusWindowLevel)
|
|
313
|
+
panel.setCollectionBehavior_(
|
|
314
|
+
NSWindowCollectionBehaviorCanJoinAllSpaces |
|
|
315
|
+
NSWindowCollectionBehaviorFullScreenAuxiliary
|
|
316
|
+
)
|
|
317
|
+
content_frame = NSMakeRect(0, 0, frame.size.width, frame.size.height)
|
|
318
|
+
panel.setContentView_(IndicatorView.alloc().initWithFrame_(content_frame))
|
|
319
|
+
panel.orderFrontRegardless()
|
|
320
|
+
panel.displayIfNeeded()
|
|
321
|
+
ready()
|
|
322
|
+
application.run()
|
|
323
|
+
|
|
324
|
+
else:
|
|
325
|
+
import ctypes
|
|
326
|
+
import ctypes.util
|
|
327
|
+
from Xlib import display as xdisplay
|
|
328
|
+
|
|
329
|
+
x11_path = ctypes.util.find_library("X11")
|
|
330
|
+
xext_path = ctypes.util.find_library("Xext")
|
|
331
|
+
if not x11_path or not xext_path:
|
|
332
|
+
raise RuntimeError("the X11 indicator libraries are unavailable")
|
|
333
|
+
x11 = ctypes.cdll.LoadLibrary(x11_path)
|
|
334
|
+
xext = ctypes.cdll.LoadLibrary(xext_path)
|
|
335
|
+
x11.XOpenDisplay.argtypes = [ctypes.c_char_p]
|
|
336
|
+
x11.XOpenDisplay.restype = ctypes.c_void_p
|
|
337
|
+
x11.XDefaultScreen.argtypes = [ctypes.c_void_p]
|
|
338
|
+
x11.XDefaultScreen.restype = ctypes.c_int
|
|
339
|
+
x11.XRootWindow.argtypes = [ctypes.c_void_p, ctypes.c_int]
|
|
340
|
+
x11.XRootWindow.restype = ctypes.c_ulong
|
|
341
|
+
x11.XDefaultColormap.argtypes = [ctypes.c_void_p, ctypes.c_int]
|
|
342
|
+
x11.XDefaultColormap.restype = ctypes.c_ulong
|
|
343
|
+
x11.XDisplayWidth.argtypes = [ctypes.c_void_p, ctypes.c_int]
|
|
344
|
+
x11.XDisplayWidth.restype = ctypes.c_int
|
|
345
|
+
x11.XDisplayHeight.argtypes = [ctypes.c_void_p, ctypes.c_int]
|
|
346
|
+
x11.XDisplayHeight.restype = ctypes.c_int
|
|
347
|
+
x11.XCreateSimpleWindow.argtypes = [
|
|
348
|
+
ctypes.c_void_p, ctypes.c_ulong, ctypes.c_int, ctypes.c_int,
|
|
349
|
+
ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_ulong, ctypes.c_ulong
|
|
350
|
+
]
|
|
351
|
+
x11.XCreateSimpleWindow.restype = ctypes.c_ulong
|
|
352
|
+
x11.XCreateGC.argtypes = [ctypes.c_void_p, ctypes.c_ulong, ctypes.c_ulong, ctypes.c_void_p]
|
|
353
|
+
x11.XCreateGC.restype = ctypes.c_void_p
|
|
354
|
+
x11.XAllocColor.argtypes = [ctypes.c_void_p, ctypes.c_ulong, ctypes.c_void_p]
|
|
355
|
+
x11.XChangeWindowAttributes.argtypes = [
|
|
356
|
+
ctypes.c_void_p, ctypes.c_ulong, ctypes.c_ulong, ctypes.c_void_p
|
|
357
|
+
]
|
|
358
|
+
x11.XMapRaised.argtypes = [ctypes.c_void_p, ctypes.c_ulong]
|
|
359
|
+
x11.XSelectInput.argtypes = [ctypes.c_void_p, ctypes.c_ulong, ctypes.c_long]
|
|
360
|
+
x11.XSetForeground.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_ulong]
|
|
361
|
+
x11.XDrawString.argtypes = [
|
|
362
|
+
ctypes.c_void_p, ctypes.c_ulong, ctypes.c_void_p,
|
|
363
|
+
ctypes.c_int, ctypes.c_int, ctypes.c_char_p, ctypes.c_int
|
|
364
|
+
]
|
|
365
|
+
x11.XFlush.argtypes = [ctypes.c_void_p]
|
|
366
|
+
x11.XSync.argtypes = [ctypes.c_void_p, ctypes.c_int]
|
|
367
|
+
x11.XNextEvent.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
|
|
368
|
+
xext.XShapeQueryExtension.argtypes = [
|
|
369
|
+
ctypes.c_void_p, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)
|
|
370
|
+
]
|
|
371
|
+
xext.XShapeCombineRectangles.argtypes = [
|
|
372
|
+
ctypes.c_void_p, ctypes.c_ulong, ctypes.c_int, ctypes.c_int, ctypes.c_int,
|
|
373
|
+
ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_int
|
|
374
|
+
]
|
|
375
|
+
|
|
376
|
+
class XColor(ctypes.Structure):
|
|
377
|
+
_fields_ = [
|
|
378
|
+
("pixel", ctypes.c_ulong), ("red", ctypes.c_ushort),
|
|
379
|
+
("green", ctypes.c_ushort), ("blue", ctypes.c_ushort),
|
|
380
|
+
("flags", ctypes.c_byte), ("pad", ctypes.c_byte)
|
|
381
|
+
]
|
|
382
|
+
|
|
383
|
+
class XEvent(ctypes.Structure):
|
|
384
|
+
_fields_ = [("type", ctypes.c_int), ("pad", ctypes.c_long * 23)]
|
|
385
|
+
|
|
386
|
+
class XSetWindowAttributes(ctypes.Structure):
|
|
387
|
+
_fields_ = [
|
|
388
|
+
("background_pixmap", ctypes.c_ulong), ("background_pixel", ctypes.c_ulong),
|
|
389
|
+
("border_pixmap", ctypes.c_ulong), ("border_pixel", ctypes.c_ulong),
|
|
390
|
+
("bit_gravity", ctypes.c_int), ("win_gravity", ctypes.c_int),
|
|
391
|
+
("backing_store", ctypes.c_int), ("backing_planes", ctypes.c_ulong),
|
|
392
|
+
("backing_pixel", ctypes.c_ulong), ("save_under", ctypes.c_int),
|
|
393
|
+
("event_mask", ctypes.c_long), ("do_not_propagate_mask", ctypes.c_long),
|
|
394
|
+
("override_redirect", ctypes.c_int), ("colormap", ctypes.c_ulong),
|
|
395
|
+
("cursor", ctypes.c_ulong)
|
|
396
|
+
]
|
|
397
|
+
|
|
398
|
+
connection = x11.XOpenDisplay(None)
|
|
399
|
+
if not connection:
|
|
400
|
+
raise RuntimeError("cannot open the X11 display")
|
|
401
|
+
shape_event = ctypes.c_int()
|
|
402
|
+
shape_error = ctypes.c_int()
|
|
403
|
+
if not xext.XShapeQueryExtension(
|
|
404
|
+
connection, ctypes.byref(shape_event), ctypes.byref(shape_error)
|
|
405
|
+
):
|
|
406
|
+
raise RuntimeError("the X11 Shape extension is unavailable")
|
|
407
|
+
screen = x11.XDefaultScreen(connection)
|
|
408
|
+
root = x11.XRootWindow(connection, screen)
|
|
409
|
+
colormap = x11.XDefaultColormap(connection, screen)
|
|
410
|
+
|
|
411
|
+
def allocate(red, green, blue):
|
|
412
|
+
color = XColor(0, red * 257, green * 257, blue * 257, 0x07, 0)
|
|
413
|
+
if not x11.XAllocColor(connection, colormap, ctypes.byref(color)):
|
|
414
|
+
raise RuntimeError("cannot allocate the control indicator color")
|
|
415
|
+
return color.pixel
|
|
416
|
+
|
|
417
|
+
orange = allocate(*COLOR)
|
|
418
|
+
dark = allocate(18, 28, 37)
|
|
419
|
+
windows = []
|
|
420
|
+
|
|
421
|
+
def create(x, y, width, height, background):
|
|
422
|
+
window = x11.XCreateSimpleWindow(
|
|
423
|
+
connection, root, x, y, width, height, 0, background, background
|
|
424
|
+
)
|
|
425
|
+
if not window:
|
|
426
|
+
raise RuntimeError("cannot create the X11 control indicator")
|
|
427
|
+
attributes = XSetWindowAttributes()
|
|
428
|
+
attributes.override_redirect = 1
|
|
429
|
+
x11.XChangeWindowAttributes(connection, window, 1 << 9, ctypes.byref(attributes))
|
|
430
|
+
xext.XShapeCombineRectangles(
|
|
431
|
+
connection, window, 2, 0, 0, None, 0, 0, 0
|
|
432
|
+
)
|
|
433
|
+
x11.XMapRaised(connection, window)
|
|
434
|
+
windows.append(window)
|
|
435
|
+
return window
|
|
436
|
+
|
|
437
|
+
origin_x = 0
|
|
438
|
+
origin_y = 0
|
|
439
|
+
width = x11.XDisplayWidth(connection, screen)
|
|
440
|
+
height = x11.XDisplayHeight(connection, screen)
|
|
441
|
+
geometry_connection = xdisplay.Display()
|
|
442
|
+
try:
|
|
443
|
+
try:
|
|
444
|
+
geometry_root = geometry_connection.screen().root
|
|
445
|
+
primary = geometry_root.xrandr_get_output_primary().output
|
|
446
|
+
if primary:
|
|
447
|
+
resources = geometry_root.xrandr_get_screen_resources_current()
|
|
448
|
+
output = geometry_connection.xrandr_get_output_info(
|
|
449
|
+
primary, resources.config_timestamp
|
|
450
|
+
)
|
|
451
|
+
if output.crtc:
|
|
452
|
+
crtc = geometry_connection.xrandr_get_crtc_info(
|
|
453
|
+
output.crtc, resources.config_timestamp
|
|
454
|
+
)
|
|
455
|
+
origin_x, origin_y = crtc.x, crtc.y
|
|
456
|
+
width, height = crtc.width, crtc.height
|
|
457
|
+
except Exception:
|
|
458
|
+
pass
|
|
459
|
+
finally:
|
|
460
|
+
geometry_connection.close()
|
|
461
|
+
create(origin_x, origin_y, width, THICKNESS, orange)
|
|
462
|
+
create(origin_x, origin_y + height - THICKNESS, width, THICKNESS, orange)
|
|
463
|
+
create(origin_x, origin_y, THICKNESS, height, orange)
|
|
464
|
+
create(origin_x + width - THICKNESS, origin_y, THICKNESS, height, orange)
|
|
465
|
+
label = create(origin_x + width - 128, origin_y + 10, 116, 26, dark)
|
|
466
|
+
exposure_mask = 1 << 15
|
|
467
|
+
x11.XSelectInput(connection, label, exposure_mask)
|
|
468
|
+
gc = x11.XCreateGC(connection, label, 0, None)
|
|
469
|
+
x11.XSetForeground(connection, gc, orange)
|
|
470
|
+
label_bytes = LABEL.encode("ascii")
|
|
471
|
+
|
|
472
|
+
def draw_label():
|
|
473
|
+
x11.XDrawString(connection, label, gc, 18, 17, label_bytes, len(label_bytes))
|
|
474
|
+
x11.XFlush(connection)
|
|
475
|
+
|
|
476
|
+
draw_label()
|
|
477
|
+
x11.XSync(connection, False)
|
|
478
|
+
ready()
|
|
479
|
+
event = XEvent()
|
|
480
|
+
while True:
|
|
481
|
+
x11.XNextEvent(connection, ctypes.byref(event))
|
|
482
|
+
if event.type == 12:
|
|
483
|
+
draw_label()
|
|
484
|
+
`;
|
|
@@ -5,6 +5,7 @@ import { tmpdir } from 'node:os';
|
|
|
5
5
|
import { resolve } from 'node:path';
|
|
6
6
|
import { createInterface } from 'node:readline';
|
|
7
7
|
import { terminateProcessTree } from '../../process-tree.js';
|
|
8
|
+
import { ComputerUseControlIndicator } from './computer-use-indicator.js';
|
|
8
9
|
const TOOL_NAME = 'mcp__computer__exec';
|
|
9
10
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
10
11
|
const MAX_TIMEOUT_MS = 120_000;
|
|
@@ -42,6 +43,7 @@ export class PyAutoGuiComputerUseProvider {
|
|
|
42
43
|
configuration;
|
|
43
44
|
#lease;
|
|
44
45
|
#probe;
|
|
46
|
+
#showControlIndicator;
|
|
45
47
|
#sessions = new Map();
|
|
46
48
|
#pendingBinds = new Map();
|
|
47
49
|
#supportKey;
|
|
@@ -51,6 +53,7 @@ export class PyAutoGuiComputerUseProvider {
|
|
|
51
53
|
this.configuration = configuration;
|
|
52
54
|
this.#lease = options.lease ?? new ComputerUseDesktopLeaseService();
|
|
53
55
|
this.#probe = options.probe ?? probeComputerUse;
|
|
56
|
+
this.#showControlIndicator = options.showControlIndicator ?? ComputerUseControlIndicator.show;
|
|
54
57
|
}
|
|
55
58
|
support() {
|
|
56
59
|
const configuration = this.#configuration();
|
|
@@ -85,7 +88,7 @@ export class PyAutoGuiComputerUseProvider {
|
|
|
85
88
|
}
|
|
86
89
|
const entry = retained ?? {
|
|
87
90
|
runtimeExecutable: configuration.runtimeExecutable,
|
|
88
|
-
session: new ComputerUseRunSession(configuration.runtimeExecutable, this.#lease, async () => {
|
|
91
|
+
session: new ComputerUseRunSession(configuration.runtimeExecutable, this.#lease, this.#showControlIndicator, async () => {
|
|
89
92
|
const current = await this.#probe(configuration);
|
|
90
93
|
if (!current.available)
|
|
91
94
|
throw new ComputerUseError(current.reasonCode ?? 'COMPUTER_UNAVAILABLE', 'The interactive desktop is no longer available.');
|
|
@@ -201,15 +204,17 @@ export const computerUseToolDefinition = {
|
|
|
201
204
|
class ComputerUseRunSession {
|
|
202
205
|
runtimeExecutable;
|
|
203
206
|
lease;
|
|
207
|
+
showControlIndicator;
|
|
204
208
|
validateDesktop;
|
|
205
209
|
#worker;
|
|
206
210
|
#observationId;
|
|
207
211
|
#runtimeGeneration = 1;
|
|
208
212
|
#runId;
|
|
209
213
|
#closed = false;
|
|
210
|
-
constructor(runtimeExecutable, lease, validateDesktop) {
|
|
214
|
+
constructor(runtimeExecutable, lease, showControlIndicator, validateDesktop) {
|
|
211
215
|
this.runtimeExecutable = runtimeExecutable;
|
|
212
216
|
this.lease = lease;
|
|
217
|
+
this.showControlIndicator = showControlIndicator;
|
|
213
218
|
this.validateDesktop = validateDesktop;
|
|
214
219
|
}
|
|
215
220
|
attach(runId) {
|
|
@@ -241,37 +246,60 @@ class ComputerUseRunSession {
|
|
|
241
246
|
throw new ComputerUseError('COMPUTER_UNAVAILABLE', 'Computer Use Run resources are detached.');
|
|
242
247
|
this.lease.acquire(runId);
|
|
243
248
|
await this.validateDesktop();
|
|
244
|
-
|
|
249
|
+
let indicator;
|
|
245
250
|
try {
|
|
246
|
-
|
|
247
|
-
this.#observationId = randomUUID();
|
|
248
|
-
return {
|
|
249
|
-
content: renderResultContent('SUCCEEDED', this.#observationId, result.logs),
|
|
250
|
-
data: {
|
|
251
|
-
outcome: 'SUCCEEDED',
|
|
252
|
-
observationId: this.#observationId,
|
|
253
|
-
runtimeGeneration: this.#runtimeGeneration,
|
|
254
|
-
logs: result.logs
|
|
255
|
-
},
|
|
256
|
-
images: result.images
|
|
257
|
-
};
|
|
251
|
+
indicator = await this.showControlIndicator(this.runtimeExecutable);
|
|
258
252
|
}
|
|
259
253
|
catch (error) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
254
|
+
throw new ComputerUseError('COMPUTER_CONTROL_INDICATOR_UNAVAILABLE', 'The desktop control indicator could not be displayed.', { cause: error });
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
const worker = await this.#ensureWorker();
|
|
258
|
+
let executionStarted = false;
|
|
259
|
+
try {
|
|
260
|
+
indicator.assertActive?.();
|
|
261
|
+
executionStarted = true;
|
|
262
|
+
const execution = worker.execute(input.code, input.timeoutMs, signal);
|
|
263
|
+
const result = await (indicator.lost === undefined
|
|
264
|
+
? execution
|
|
265
|
+
: Promise.race([
|
|
266
|
+
execution,
|
|
267
|
+
indicator.lost.then((error) => {
|
|
268
|
+
throw error;
|
|
269
|
+
})
|
|
270
|
+
]));
|
|
271
|
+
this.#observationId = randomUUID();
|
|
264
272
|
return {
|
|
265
|
-
content:
|
|
273
|
+
content: renderResultContent('SUCCEEDED', this.#observationId, result.logs),
|
|
266
274
|
data: {
|
|
267
|
-
outcome: '
|
|
268
|
-
observationId:
|
|
275
|
+
outcome: 'SUCCEEDED',
|
|
276
|
+
observationId: this.#observationId,
|
|
269
277
|
runtimeGeneration: this.#runtimeGeneration,
|
|
270
|
-
logs:
|
|
271
|
-
}
|
|
278
|
+
logs: result.logs
|
|
279
|
+
},
|
|
280
|
+
images: result.images
|
|
272
281
|
};
|
|
273
282
|
}
|
|
274
|
-
|
|
283
|
+
catch (error) {
|
|
284
|
+
if (executionStarted && worker.startedRequest) {
|
|
285
|
+
await this.#resetWorker();
|
|
286
|
+
this.#observationId = undefined;
|
|
287
|
+
const message = error instanceof Error ? error.message : 'Computer Use execution ended unexpectedly.';
|
|
288
|
+
return {
|
|
289
|
+
content: `OUTCOME_UNKNOWN: ${message} Reobserve before continuing; do not replay automatically.`,
|
|
290
|
+
data: {
|
|
291
|
+
outcome: 'OUTCOME_UNKNOWN',
|
|
292
|
+
observationId: '',
|
|
293
|
+
runtimeGeneration: this.#runtimeGeneration,
|
|
294
|
+
logs: []
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
finally {
|
|
302
|
+
await indicator.close();
|
|
275
303
|
}
|
|
276
304
|
}
|
|
277
305
|
async close() {
|
|
@@ -370,10 +398,10 @@ class PythonWorker {
|
|
|
370
398
|
return worker;
|
|
371
399
|
}
|
|
372
400
|
execute(code, timeoutMs, signal) {
|
|
401
|
+
this.#requestStarted = false;
|
|
373
402
|
if (this.#closed)
|
|
374
403
|
return Promise.reject(new ComputerUseError('COMPUTER_WORKER_EXITED', 'Computer Use worker is closed.'));
|
|
375
404
|
const id = randomUUID();
|
|
376
|
-
this.#requestStarted = false;
|
|
377
405
|
return new Promise((resolve, reject) => {
|
|
378
406
|
let settled = false;
|
|
379
407
|
const finish = (callback) => {
|
|
@@ -287,6 +287,10 @@ export class ContinuousRunCoordinator {
|
|
|
287
287
|
? undefined
|
|
288
288
|
: await this.options.goalMcp?.(pending);
|
|
289
289
|
const scheduledMcp = await this.options.scheduledMcp?.(pending);
|
|
290
|
+
const mailMcp = await this.options.mailMcp?.({
|
|
291
|
+
...pending,
|
|
292
|
+
runId: executionId
|
|
293
|
+
});
|
|
290
294
|
if (!this.goalCurrent(pending)) {
|
|
291
295
|
this.dispatchedExecutions.delete(executionId);
|
|
292
296
|
this.rejectStaleGoal(executionId);
|
|
@@ -312,10 +316,14 @@ export class ContinuousRunCoordinator {
|
|
|
312
316
|
collaborationMode: pending.collaborationMode,
|
|
313
317
|
...(pending.serviceTier === undefined ? {} : { serviceTier: pending.serviceTier }),
|
|
314
318
|
secretEnvironment: pending.secretEnvironment,
|
|
315
|
-
...(goalMcp === undefined && scheduledMcp === undefined
|
|
319
|
+
...(goalMcp === undefined && scheduledMcp === undefined && mailMcp === undefined
|
|
316
320
|
? {}
|
|
317
321
|
: {
|
|
318
|
-
mcpSecretEnvironment: {
|
|
322
|
+
mcpSecretEnvironment: {
|
|
323
|
+
...goalMcp?.secrets,
|
|
324
|
+
...scheduledMcp?.secrets,
|
|
325
|
+
...mailMcp?.secrets
|
|
326
|
+
}
|
|
319
327
|
}),
|
|
320
328
|
...(pending.personalInstructions === undefined
|
|
321
329
|
? {}
|
|
@@ -329,7 +337,8 @@ export class ContinuousRunCoordinator {
|
|
|
329
337
|
mcpInstallations: [
|
|
330
338
|
...this.options.mcps(pending.workspaceId),
|
|
331
339
|
...(goalMcp === undefined ? [] : [goalMcp.installation]),
|
|
332
|
-
...(scheduledMcp === undefined ? [] : [scheduledMcp.installation])
|
|
340
|
+
...(scheduledMcp === undefined ? [] : [scheduledMcp.installation]),
|
|
341
|
+
...(mailMcp === undefined ? [] : [mailMcp.installation])
|
|
333
342
|
]
|
|
334
343
|
});
|
|
335
344
|
}
|