@myagentroam/node 0.9.94 → 0.9.96
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-runtime-version.js +1 -0
- package/dist/computer-use-runtime.js +330 -0
- package/dist/config.js +35 -1
- package/dist/connector.js +49 -7
- package/dist/ctags-binary.js +18 -0
- package/dist/runner/mar-agent/computer-use.js +750 -0
- package/dist/runner/mar-agent/managed-run-controller.js +55 -7
- package/dist/service/computer-use-enablement-service.js +172 -0
- package/dist/service/continuous-run-coordinator.js +1 -0
- package/dist/service/node-connection-lifecycle-service.js +1 -0
- package/dist/service/session-lifecycle-service.js +1 -0
- package/dist/service/session-message-service.js +1 -0
- package/dist/service/workspace-code-navigation-service.js +1219 -0
- package/dist/service/workspace-content-search-service.js +55 -25
- package/dist/service/workspace-file-service.js +1 -1
- package/dist/service/workspace-workbench-service.js +28 -1
- package/dist/workspace.js +70 -6
- package/package.json +7 -4
- package/vendor/ctags/NOTICE +10 -0
- package/vendor/ctags/licenses/COPYING +340 -0
- package/vendor/ctags/licenses/COPYING.MinGW-w64-runtime.txt +240 -0
- package/vendor/ctags/licenses/LICENSE.janssen +19 -0
- package/vendor/ctags/licenses/LICENSE.libyaml +19 -0
- package/vendor/ctags/linux-x64/ctags +0 -0
- package/vendor/ctags/manifest.json +25 -0
- package/vendor/ctags/source/universal-ctags-6.2.1.tar.gz +0 -0
- package/vendor/ctags/win32-x64/ctags.exe +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const computerUseRuntimeVersion = '0.9.95';
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { constants } from 'node:fs';
|
|
4
|
+
import { access, cp, lstat, mkdir, readFile, readdir, realpath, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import { dirname, relative, resolve, sep } from 'node:path';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { computerUseRuntimeExecutable, nodeDataDirectory } from './config.js';
|
|
10
|
+
import { computerUseRuntimeVersion } from './computer-use-runtime-version.js';
|
|
11
|
+
const manifestSchema = z
|
|
12
|
+
.object({
|
|
13
|
+
formatVersion: z.literal(1),
|
|
14
|
+
runtimeVersion: z.string().trim().min(1).max(128),
|
|
15
|
+
platform: z.enum(['win32', 'darwin', 'linux']),
|
|
16
|
+
architecture: z.enum(['x64', 'arm64']),
|
|
17
|
+
executable: z.string().trim().min(1).max(512),
|
|
18
|
+
files: z
|
|
19
|
+
.array(z
|
|
20
|
+
.object({
|
|
21
|
+
path: z.string().trim().min(1).max(512),
|
|
22
|
+
size: z.number().int().nonnegative(),
|
|
23
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/u)
|
|
24
|
+
})
|
|
25
|
+
.strict())
|
|
26
|
+
.min(1)
|
|
27
|
+
})
|
|
28
|
+
.strict();
|
|
29
|
+
// Only Windows x64 and Linux x64 are published initially because maintainers do not
|
|
30
|
+
// currently have native build environments for the other platform/architecture targets.
|
|
31
|
+
const runtimePackages = {
|
|
32
|
+
'win32-x64': {
|
|
33
|
+
name: '@myagentroam/computer-runtime-win32-x64',
|
|
34
|
+
version: computerUseRuntimeVersion
|
|
35
|
+
},
|
|
36
|
+
'linux-x64': {
|
|
37
|
+
name: '@myagentroam/computer-runtime-linux-x64',
|
|
38
|
+
version: computerUseRuntimeVersion
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const execFileAsync = promisify(execFile);
|
|
42
|
+
const installLockTimeoutMs = 620_000;
|
|
43
|
+
const installLockPollMs = 100;
|
|
44
|
+
export class ComputerUseRuntimeError extends Error {
|
|
45
|
+
code;
|
|
46
|
+
constructor(code, options = {}) {
|
|
47
|
+
super(code, options);
|
|
48
|
+
this.code = code;
|
|
49
|
+
this.name = 'ComputerUseRuntimeError';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export class ManagedComputerUseRuntime {
|
|
53
|
+
config;
|
|
54
|
+
configPath;
|
|
55
|
+
platform;
|
|
56
|
+
architecture;
|
|
57
|
+
installRuntimePackage;
|
|
58
|
+
#ensurePromise;
|
|
59
|
+
constructor(config, configPath, platform = process.platform, architecture = process.arch, installRuntimePackage = defaultRuntimePackageInstaller) {
|
|
60
|
+
this.config = config;
|
|
61
|
+
this.configPath = configPath;
|
|
62
|
+
this.platform = platform;
|
|
63
|
+
this.architecture = architecture;
|
|
64
|
+
this.installRuntimePackage = installRuntimePackage;
|
|
65
|
+
}
|
|
66
|
+
ensure() {
|
|
67
|
+
this.#ensurePromise ??= this.#ensure().finally(() => {
|
|
68
|
+
this.#ensurePromise = undefined;
|
|
69
|
+
});
|
|
70
|
+
return this.#ensurePromise;
|
|
71
|
+
}
|
|
72
|
+
async #ensure() {
|
|
73
|
+
const config = this.config();
|
|
74
|
+
if (config === undefined)
|
|
75
|
+
throw new ComputerUseRuntimeError('NODE_CONFIG_UNAVAILABLE');
|
|
76
|
+
if (config.computerUse?.runtimeExecutable !== undefined) {
|
|
77
|
+
const executable = computerUseRuntimeExecutable(config, this.configPath, this.platform);
|
|
78
|
+
if (executable === undefined)
|
|
79
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_MISSING');
|
|
80
|
+
await access(executable).catch((cause) => {
|
|
81
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_MISSING', { cause });
|
|
82
|
+
});
|
|
83
|
+
return executable;
|
|
84
|
+
}
|
|
85
|
+
const runtimePackage = runtimePackages[`${this.platform}-${this.architecture}`];
|
|
86
|
+
if (runtimePackage === undefined)
|
|
87
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_PLATFORM_UNSUPPORTED');
|
|
88
|
+
const runtimeRoot = resolve(nodeDataDirectory(config, this.configPath), 'computer-use', 'runtime');
|
|
89
|
+
await mkdir(runtimeRoot, { recursive: true });
|
|
90
|
+
const current = resolve(runtimeRoot, `${runtimePackage.version}-${this.platform}-${this.architecture}`);
|
|
91
|
+
const currentExecutable = await this.#currentExecutable(current, runtimePackage.version);
|
|
92
|
+
if (currentExecutable !== undefined)
|
|
93
|
+
return currentExecutable;
|
|
94
|
+
return this.#withInstallLock(runtimeRoot, async () => {
|
|
95
|
+
const lockedCurrent = await this.#currentExecutable(current, runtimePackage.version);
|
|
96
|
+
if (lockedCurrent !== undefined)
|
|
97
|
+
return lockedCurrent;
|
|
98
|
+
const packageStaging = resolve(runtimeRoot, `.package-${randomUUID()}`);
|
|
99
|
+
const staging = resolve(runtimeRoot, `.staging-${randomUUID()}`);
|
|
100
|
+
try {
|
|
101
|
+
const packageRoot = await this.installRuntimePackage(runtimePackage, packageStaging);
|
|
102
|
+
await this.#verifyPackageMetadata(packageRoot, runtimePackage);
|
|
103
|
+
const source = resolve(packageRoot, 'runtime');
|
|
104
|
+
const manifest = await this.#readManifest(source);
|
|
105
|
+
if (manifest.runtimeVersion !== runtimePackage.version ||
|
|
106
|
+
manifest.platform !== this.platform ||
|
|
107
|
+
manifest.architecture !== this.architecture)
|
|
108
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_MANIFEST_INVALID');
|
|
109
|
+
await this.#verify(source, manifest);
|
|
110
|
+
await cp(source, staging, { recursive: true, errorOnExist: true, force: false });
|
|
111
|
+
await this.#verify(staging, manifest);
|
|
112
|
+
await rm(current, { recursive: true, force: true });
|
|
113
|
+
try {
|
|
114
|
+
await rename(staging, current);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (!(await this.#currentExecutable(current, runtimePackage.version)))
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
return resolve(current, manifest.executable);
|
|
121
|
+
}
|
|
122
|
+
catch (cause) {
|
|
123
|
+
await rm(staging, { recursive: true, force: true });
|
|
124
|
+
if (cause instanceof ComputerUseRuntimeError)
|
|
125
|
+
throw cause;
|
|
126
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_INSTALL_FAILED', { cause });
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
await rm(packageStaging, { recursive: true, force: true });
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
async #currentExecutable(directory, runtimeVersion) {
|
|
134
|
+
try {
|
|
135
|
+
const manifest = await this.#readManifest(directory);
|
|
136
|
+
if (manifest.runtimeVersion !== runtimeVersion ||
|
|
137
|
+
manifest.platform !== this.platform ||
|
|
138
|
+
manifest.architecture !== this.architecture)
|
|
139
|
+
return undefined;
|
|
140
|
+
await this.#verify(directory, manifest);
|
|
141
|
+
return resolve(directory, manifest.executable);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async #readManifest(directory) {
|
|
148
|
+
try {
|
|
149
|
+
const path = resolve(directory, 'manifest.json');
|
|
150
|
+
if (!(await lstat(path)).isFile())
|
|
151
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_MANIFEST_INVALID');
|
|
152
|
+
return manifestSchema.parse(JSON.parse(await readFile(path, 'utf8')));
|
|
153
|
+
}
|
|
154
|
+
catch (cause) {
|
|
155
|
+
if (cause instanceof ComputerUseRuntimeError)
|
|
156
|
+
throw cause;
|
|
157
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_MANIFEST_INVALID', { cause });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async #verify(directory, manifest) {
|
|
161
|
+
if (!(await lstat(directory)).isDirectory())
|
|
162
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_MANIFEST_INVALID');
|
|
163
|
+
const root = await realpath(directory);
|
|
164
|
+
const paths = new Set();
|
|
165
|
+
for (const file of manifest.files) {
|
|
166
|
+
const path = safeRuntimePath(directory, file.path);
|
|
167
|
+
if (paths.has(path))
|
|
168
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_MANIFEST_INVALID');
|
|
169
|
+
paths.add(path);
|
|
170
|
+
const fileStat = await lstat(path).catch((cause) => {
|
|
171
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_MISSING', { cause });
|
|
172
|
+
});
|
|
173
|
+
if (!fileStat.isFile() || !isWithin(root, await realpath(path)))
|
|
174
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_MANIFEST_INVALID');
|
|
175
|
+
const bytes = await readFile(path).catch((cause) => {
|
|
176
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_MISSING', { cause });
|
|
177
|
+
});
|
|
178
|
+
if (bytes.byteLength !== file.size ||
|
|
179
|
+
createHash('sha256').update(bytes).digest('hex') !== file.sha256)
|
|
180
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_INTEGRITY_FAILED');
|
|
181
|
+
}
|
|
182
|
+
const actualPaths = new Set((await regularRuntimeFiles(directory))
|
|
183
|
+
.filter((path) => relative(directory, path).split(sep).join('/') !== 'manifest.json')
|
|
184
|
+
.map((path) => resolve(path)));
|
|
185
|
+
if (actualPaths.size !== paths.size || [...actualPaths].some((path) => !paths.has(path)))
|
|
186
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_MANIFEST_INVALID');
|
|
187
|
+
const executable = safeRuntimePath(directory, manifest.executable);
|
|
188
|
+
if (!paths.has(executable) || !(await stat(executable)).isFile())
|
|
189
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_MANIFEST_INVALID');
|
|
190
|
+
if (this.platform !== 'win32')
|
|
191
|
+
await access(executable, constants.X_OK).catch((cause) => {
|
|
192
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_INTEGRITY_FAILED', { cause });
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
async #verifyPackageMetadata(packageRoot, runtimePackage) {
|
|
196
|
+
try {
|
|
197
|
+
const path = resolve(packageRoot, 'package.json');
|
|
198
|
+
if (!(await lstat(path)).isFile())
|
|
199
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_PACKAGE_INVALID');
|
|
200
|
+
const metadata = z
|
|
201
|
+
.object({ name: z.string(), version: z.string() })
|
|
202
|
+
.passthrough()
|
|
203
|
+
.parse(JSON.parse(await readFile(path, 'utf8')));
|
|
204
|
+
if (metadata.name !== runtimePackage.name || metadata.version !== runtimePackage.version)
|
|
205
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_PACKAGE_INVALID');
|
|
206
|
+
}
|
|
207
|
+
catch (cause) {
|
|
208
|
+
if (cause instanceof ComputerUseRuntimeError)
|
|
209
|
+
throw cause;
|
|
210
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_PACKAGE_INVALID', { cause });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async #withInstallLock(runtimeRoot, operation) {
|
|
214
|
+
const lock = resolve(runtimeRoot, '.install-lock');
|
|
215
|
+
const owner = resolve(lock, 'owner.json');
|
|
216
|
+
const deadline = Date.now() + installLockTimeoutMs;
|
|
217
|
+
for (;;) {
|
|
218
|
+
try {
|
|
219
|
+
await mkdir(lock);
|
|
220
|
+
await writeFile(owner, JSON.stringify({ pid: process.pid, id: randomUUID() }), {
|
|
221
|
+
flag: 'wx'
|
|
222
|
+
});
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
catch (cause) {
|
|
226
|
+
if (!isAlreadyExists(cause))
|
|
227
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_INSTALL_FAILED', { cause });
|
|
228
|
+
if (!(await lockOwnerIsAlive(owner, lock))) {
|
|
229
|
+
await rm(lock, { recursive: true, force: true });
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (Date.now() >= deadline)
|
|
233
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_INSTALL_FAILED', {
|
|
234
|
+
cause: new Error('COMPUTER_RUNTIME_INSTALL_LOCK_TIMEOUT')
|
|
235
|
+
});
|
|
236
|
+
await delay(installLockPollMs);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
return await operation();
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
await rm(lock, { recursive: true, force: true });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function defaultRuntimePackageInstaller(runtimePackage, destination) {
|
|
248
|
+
const npmRoot = dirname(createRequire(import.meta.url).resolve('npm/package.json'));
|
|
249
|
+
const npmCli = resolve(npmRoot, 'bin/npm-cli.js');
|
|
250
|
+
await mkdir(destination, { recursive: true });
|
|
251
|
+
await execFileAsync(process.execPath, [
|
|
252
|
+
npmCli,
|
|
253
|
+
'install',
|
|
254
|
+
`${runtimePackage.name}@${runtimePackage.version}`,
|
|
255
|
+
'--prefix',
|
|
256
|
+
destination,
|
|
257
|
+
'--ignore-scripts',
|
|
258
|
+
'--no-audit',
|
|
259
|
+
'--no-fund',
|
|
260
|
+
'--package-lock=false',
|
|
261
|
+
'--omit=dev',
|
|
262
|
+
'--loglevel=error'
|
|
263
|
+
], {
|
|
264
|
+
env: { ...process.env, NPM_CONFIG_UPDATE_NOTIFIER: 'false' },
|
|
265
|
+
timeout: 600_000,
|
|
266
|
+
maxBuffer: 1024 * 1024
|
|
267
|
+
});
|
|
268
|
+
return resolve(destination, 'node_modules', '@myagentroam', runtimePackage.name.split('/')[1]);
|
|
269
|
+
}
|
|
270
|
+
function safeRuntimePath(root, path) {
|
|
271
|
+
const target = resolve(root, path);
|
|
272
|
+
const suffix = relative(root, target);
|
|
273
|
+
if (suffix.length === 0 ||
|
|
274
|
+
suffix === '..' ||
|
|
275
|
+
suffix.startsWith(`..${sep}`) ||
|
|
276
|
+
resolve(root, suffix) !== target)
|
|
277
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_MANIFEST_INVALID');
|
|
278
|
+
return target;
|
|
279
|
+
}
|
|
280
|
+
function isMissing(error) {
|
|
281
|
+
return (error instanceof Error && 'code' in error && error.code === 'ENOENT');
|
|
282
|
+
}
|
|
283
|
+
function isAlreadyExists(error) {
|
|
284
|
+
return (error instanceof Error && 'code' in error && error.code === 'EEXIST');
|
|
285
|
+
}
|
|
286
|
+
function isWithin(root, target) {
|
|
287
|
+
const suffix = relative(root, target);
|
|
288
|
+
return suffix === '' || (suffix !== '..' && !suffix.startsWith(`..${sep}`));
|
|
289
|
+
}
|
|
290
|
+
async function regularRuntimeFiles(directory) {
|
|
291
|
+
const output = [];
|
|
292
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
293
|
+
const path = resolve(directory, entry.name);
|
|
294
|
+
if (entry.isDirectory())
|
|
295
|
+
output.push(...(await regularRuntimeFiles(path)));
|
|
296
|
+
else if (entry.isFile())
|
|
297
|
+
output.push(path);
|
|
298
|
+
else
|
|
299
|
+
throw new ComputerUseRuntimeError('COMPUTER_RUNTIME_MANIFEST_INVALID');
|
|
300
|
+
}
|
|
301
|
+
return output;
|
|
302
|
+
}
|
|
303
|
+
async function lockOwnerIsAlive(ownerPath, lockPath) {
|
|
304
|
+
try {
|
|
305
|
+
const owner = z
|
|
306
|
+
.object({ pid: z.number().int().positive() })
|
|
307
|
+
.passthrough()
|
|
308
|
+
.parse(JSON.parse(await readFile(ownerPath, 'utf8')));
|
|
309
|
+
try {
|
|
310
|
+
process.kill(owner.pid, 0);
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
catch (cause) {
|
|
314
|
+
return !(cause instanceof Error &&
|
|
315
|
+
'code' in cause &&
|
|
316
|
+
cause.code === 'ESRCH');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
try {
|
|
321
|
+
return Date.now() - (await stat(lockPath)).mtimeMs < 5_000;
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function delay(milliseconds) {
|
|
329
|
+
return new Promise((resolveDelay) => setTimeout(resolveDelay, milliseconds));
|
|
330
|
+
}
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { dirname, isAbsolute, relative, resolve, sep, win32 } from 'node:path';
|
|
4
|
+
import { computerUseRuntimeVersion } from './computer-use-runtime-version.js';
|
|
4
5
|
export function nodeConfigPath() {
|
|
5
6
|
return resolve(process.env['MAR_NODE_CONFIG'] ?? 'data/mar-node.json');
|
|
6
7
|
}
|
|
@@ -27,6 +28,7 @@ export async function loadNodeConfig(path = nodeConfigPath()) {
|
|
|
27
28
|
: defaultAllowedRoots();
|
|
28
29
|
const conversationHosting = parseConversationHosting(input['conversationHosting']);
|
|
29
30
|
const directTransfer = parseDirectTransfer(input['directTransfer']);
|
|
31
|
+
const computerUse = parseComputerUse(input['computerUse']);
|
|
30
32
|
return {
|
|
31
33
|
serverUrl: parsed.serverUrl,
|
|
32
34
|
allowedRoots,
|
|
@@ -35,7 +37,8 @@ export async function loadNodeConfig(path = nodeConfigPath()) {
|
|
|
35
37
|
...(registrationToken === undefined ? {} : { registrationToken }),
|
|
36
38
|
...(dataDirectory === undefined ? {} : { dataDirectory }),
|
|
37
39
|
...(conversationHosting === undefined ? {} : { conversationHosting }),
|
|
38
|
-
...(directTransfer === undefined ? {} : { directTransfer })
|
|
40
|
+
...(directTransfer === undefined ? {} : { directTransfer }),
|
|
41
|
+
...(computerUse === undefined ? {} : { computerUse })
|
|
39
42
|
};
|
|
40
43
|
}
|
|
41
44
|
export function directTransferUdpPortRange(config) {
|
|
@@ -57,6 +60,16 @@ export function nodeDataDirectory(config, configPath = nodeConfigPath()) {
|
|
|
57
60
|
return resolve(dirname(configPath), config.dataDirectory);
|
|
58
61
|
return resolve(dirname(configPath));
|
|
59
62
|
}
|
|
63
|
+
export function computerUseRuntimeExecutable(config, configPath = nodeConfigPath(), platform = process.platform, architecture = process.arch) {
|
|
64
|
+
if (config.computerUse?.enabled !== true)
|
|
65
|
+
return undefined;
|
|
66
|
+
if (config.computerUse.runtimeExecutable !== undefined)
|
|
67
|
+
return resolve(dirname(configPath), config.computerUse.runtimeExecutable);
|
|
68
|
+
const runtimeRoot = resolve(nodeDataDirectory(config, configPath), 'computer-use', 'runtime', `${computerUseRuntimeVersion}-${platform}-${architecture}`);
|
|
69
|
+
return platform === 'win32'
|
|
70
|
+
? resolve(runtimeRoot, 'python.exe')
|
|
71
|
+
: resolve(runtimeRoot, 'bin', 'python3');
|
|
72
|
+
}
|
|
60
73
|
export function hostedConversationStorePath(config, configPath = nodeConfigPath()) {
|
|
61
74
|
const hosting = config.conversationHosting;
|
|
62
75
|
if (hosting?.enabled !== true)
|
|
@@ -129,6 +142,27 @@ function parseDirectTransfer(value) {
|
|
|
129
142
|
udpPortRangeEnd: end
|
|
130
143
|
};
|
|
131
144
|
}
|
|
145
|
+
function parseComputerUse(value) {
|
|
146
|
+
if (value === undefined)
|
|
147
|
+
return undefined;
|
|
148
|
+
if (typeof value !== 'object' ||
|
|
149
|
+
value === null ||
|
|
150
|
+
Array.isArray(value) ||
|
|
151
|
+
typeof value['enabled'] !== 'boolean' ||
|
|
152
|
+
Object.keys(value).some((key) => key !== 'enabled' && key !== 'runtimeExecutable'))
|
|
153
|
+
throw new Error('NODE_CONFIG_INVALID');
|
|
154
|
+
const input = value;
|
|
155
|
+
const runtimeExecutable = input['runtimeExecutable'];
|
|
156
|
+
if (runtimeExecutable !== undefined &&
|
|
157
|
+
(typeof runtimeExecutable !== 'string' ||
|
|
158
|
+
runtimeExecutable.length === 0 ||
|
|
159
|
+
runtimeExecutable.trim() !== runtimeExecutable))
|
|
160
|
+
throw new Error('NODE_CONFIG_INVALID');
|
|
161
|
+
return {
|
|
162
|
+
enabled: input['enabled'],
|
|
163
|
+
...(runtimeExecutable === undefined ? {} : { runtimeExecutable })
|
|
164
|
+
};
|
|
165
|
+
}
|
|
132
166
|
function validStoreDirectory(value) {
|
|
133
167
|
if (typeof value !== 'string' || value.trim() !== value || value.length === 0)
|
|
134
168
|
return false;
|
package/dist/connector.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { dirname, resolve } from 'node:path';
|
|
2
2
|
import { createEnvelope, directNodeCommandSchema } from '@myagentroam/protocol';
|
|
3
|
-
import { hostedConversationStorePath, loadNodeConfig, directTransferUdpPortRange, nodeDataDirectory, nodeDatabasePath } from './config.js';
|
|
3
|
+
import { hostedConversationStorePath, loadNodeConfig, computerUseRuntimeExecutable, directTransferUdpPortRange, nodeDataDirectory, nodeDatabasePath } from './config.js';
|
|
4
4
|
import { NodeMetrics, nodeLog } from './operational.js';
|
|
5
5
|
import { assertLocalSqlitePath } from './storage.js';
|
|
6
6
|
import { detectCapabilitiesAsync, unavailableCapabilities } from './capabilities.js';
|
|
@@ -20,6 +20,8 @@ import { RunnerRegistry } from './runner/runner-registry.js';
|
|
|
20
20
|
import { OpenCodeRunner } from './runner/opencode-runner.js';
|
|
21
21
|
import { MarAgentRunner } from './runner/mar-agent-runner.js';
|
|
22
22
|
import { MarAgentManagedRunController } from './runner/mar-agent/managed-run-controller.js';
|
|
23
|
+
import { PyAutoGuiComputerUseProvider } from './runner/mar-agent/computer-use.js';
|
|
24
|
+
import { ManagedComputerUseRuntime } from './computer-use-runtime.js';
|
|
23
25
|
import { OpenCodeManagedRunController } from './runner/opencode/managed-run-controller.js';
|
|
24
26
|
import { NodeOperationRouter } from './connector/node-operation-router.js';
|
|
25
27
|
import { SkillNodeOperationService } from './service/skill-node-operation-service.js';
|
|
@@ -85,6 +87,7 @@ import { MarAgentHostedConversationRuntime } from './runner/mar-agent/hosted-con
|
|
|
85
87
|
import { conversationHostingCapability, probeBubblewrap } from './service/conversation-hosting-capability-service.js';
|
|
86
88
|
import { ConversationHostingMaintenanceService } from './service/conversation-hosting-maintenance-service.js';
|
|
87
89
|
import { ConversationHostingEnablementService } from './service/conversation-hosting-enablement-service.js';
|
|
90
|
+
import { ComputerUseEnablementService } from './service/computer-use-enablement-service.js';
|
|
88
91
|
import { safeErrorCode } from './util/safe-error.js';
|
|
89
92
|
import { RuntimeCredentialControlService } from './service/runtime-credential-control-service.js';
|
|
90
93
|
import { MarAgentRuntimeModelFactory } from './runner/mar-agent/runtime-model-factory.js';
|
|
@@ -210,6 +213,9 @@ export class NodeConnector {
|
|
|
210
213
|
openCodeClient;
|
|
211
214
|
marAgentClient;
|
|
212
215
|
marAgentRunController;
|
|
216
|
+
computerUseProvider;
|
|
217
|
+
computerUseEnablement;
|
|
218
|
+
managedComputerUseRuntime;
|
|
213
219
|
openCodeRunController;
|
|
214
220
|
claudeRunController;
|
|
215
221
|
runners;
|
|
@@ -262,7 +268,7 @@ export class NodeConnector {
|
|
|
262
268
|
terminalManager;
|
|
263
269
|
upgradeCoordinator;
|
|
264
270
|
workspaceState = new WorkspaceDomainState();
|
|
265
|
-
workspaceWatchService = new WorkspaceWatchService(this.workspaceState, (workspaceId) => this.workspaceSessionService.ensureWatch(workspaceId), (workspaceId) => this.workspaceSessionService.stopWatch(workspaceId), (workspaceId) => this.workspaceWorkbenchService?.
|
|
271
|
+
workspaceWatchService = new WorkspaceWatchService(this.workspaceState, (workspaceId) => this.workspaceSessionService.ensureWatch(workspaceId), (workspaceId) => this.workspaceSessionService.stopWatch(workspaceId), (workspaceId) => this.workspaceWorkbenchService?.releaseWorkspace(workspaceId));
|
|
266
272
|
workspaceSessionService = new WorkspaceSessionService(this.workspaceState, {
|
|
267
273
|
discover: (workspaceId) => this.discoverWorkspaceSessions(workspaceId),
|
|
268
274
|
listPage: (workspaceId) => this.executeNodeOperation('session.list', { workspaceId, limit: 20 }),
|
|
@@ -274,9 +280,13 @@ export class NodeConnector {
|
|
|
274
280
|
waitMs
|
|
275
281
|
})
|
|
276
282
|
});
|
|
277
|
-
workspaceFileService = new WorkspaceFileService(this.workspaceState, () => this.config?.allowedRoots ?? [], (workspaceId) =>
|
|
283
|
+
workspaceFileService = new WorkspaceFileService(this.workspaceState, () => this.config?.allowedRoots ?? [], (workspaceId) => {
|
|
284
|
+
this.workspaceWorkbenchService?.invalidateWorkspaceFiles(workspaceId);
|
|
285
|
+
this.workspaceCoordinator.invalidate(workspaceId);
|
|
286
|
+
});
|
|
278
287
|
workspaceUploadService = new WorkspaceUploadService(this.workspaceState, (workspaceId) => {
|
|
279
288
|
this.workspaceFileService.dispose(workspaceId);
|
|
289
|
+
this.workspaceWorkbenchService?.invalidateWorkspaceFiles(workspaceId);
|
|
280
290
|
this.workspaceCoordinator.invalidate(workspaceId);
|
|
281
291
|
});
|
|
282
292
|
directTransferService;
|
|
@@ -443,10 +453,10 @@ export class NodeConnector {
|
|
|
443
453
|
const detect = options.capabilityDetector ?? detectCapabilitiesAsync;
|
|
444
454
|
this.capabilityDetector = async () => {
|
|
445
455
|
const capabilities = await detect();
|
|
446
|
-
return this.projectConversationHostingCapability({
|
|
456
|
+
return this.projectComputerUseCapability(await this.projectConversationHostingCapability({
|
|
447
457
|
...capabilities,
|
|
448
458
|
metadata: upgradeMetadata(capabilities.metadata, this.upgradeCoordinator.snapshot())
|
|
449
|
-
});
|
|
459
|
+
}));
|
|
450
460
|
};
|
|
451
461
|
this.reconnect = options.reconnect ?? true;
|
|
452
462
|
this.fakeRunner =
|
|
@@ -493,6 +503,25 @@ export class NodeConnector {
|
|
|
493
503
|
this.openCodeClient = new OpenCodeRunner(options.openCodeClient);
|
|
494
504
|
this.marAgentClient = new MarAgentRunner();
|
|
495
505
|
this.marAgentClient.setGeneratedImageRegistrar((session, images) => this.runAttachmentService.registerNative(session, images));
|
|
506
|
+
this.computerUseProvider = new PyAutoGuiComputerUseProvider(() => {
|
|
507
|
+
if (this.config?.computerUse?.enabled !== true)
|
|
508
|
+
return undefined;
|
|
509
|
+
const runtimeExecutable = computerUseRuntimeExecutable(this.config, this.configPath);
|
|
510
|
+
return runtimeExecutable === undefined ? undefined : { enabled: true, runtimeExecutable };
|
|
511
|
+
});
|
|
512
|
+
this.managedComputerUseRuntime = new ManagedComputerUseRuntime(() => this.config, this.configPath);
|
|
513
|
+
this.computerUseEnablement = new ComputerUseEnablementService({
|
|
514
|
+
config: () => this.config,
|
|
515
|
+
setConfig: (config) => (this.config = config),
|
|
516
|
+
configPath: this.configPath,
|
|
517
|
+
provider: this.computerUseProvider,
|
|
518
|
+
prepareRuntime: () => this.managedComputerUseRuntime.ensure(),
|
|
519
|
+
publish: (capability) => {
|
|
520
|
+
this.capabilities = { ...this.capabilities, computerUse: capability };
|
|
521
|
+
this.send('capabilities', this.capabilities);
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
this.capabilities = this.projectComputerUseCapability(this.capabilities);
|
|
496
525
|
this.marAgentRunController = new MarAgentManagedRunController(this.marAgentClient, {
|
|
497
526
|
active: this.runState.active,
|
|
498
527
|
adopt: (input) => this.runtime.adoptRun(input),
|
|
@@ -519,7 +548,8 @@ export class NodeConnector {
|
|
|
519
548
|
: this.marAgentClient.registerGeneratedImages(session, nativeSessionId, images);
|
|
520
549
|
}
|
|
521
550
|
}, {
|
|
522
|
-
modelFactory: this.marAgentRuntimeModels
|
|
551
|
+
modelFactory: this.marAgentRuntimeModels,
|
|
552
|
+
computerUse: this.computerUseProvider
|
|
523
553
|
});
|
|
524
554
|
this.marAgentClient.setSessionResourceReleaser((sessionId) => this.marAgentRunController.releaseSession(sessionId));
|
|
525
555
|
this.openCodeRunController = new OpenCodeManagedRunController(this.openCodeClient, {
|
|
@@ -1173,7 +1203,10 @@ export class NodeConnector {
|
|
|
1173
1203
|
metrics: this.metrics,
|
|
1174
1204
|
heartbeatMs: HEARTBEAT_MS,
|
|
1175
1205
|
refreshMs: CAPABILITY_REFRESH_MS,
|
|
1176
|
-
onRegistered: () =>
|
|
1206
|
+
onRegistered: async () => {
|
|
1207
|
+
await this.initializeConversationHosting();
|
|
1208
|
+
this.computerUseEnablement.initialize();
|
|
1209
|
+
},
|
|
1177
1210
|
onConnected: () => {
|
|
1178
1211
|
this.runtimeCredentials.connected();
|
|
1179
1212
|
this.recentRunProjectionService.publishSnapshot();
|
|
@@ -1213,6 +1246,7 @@ export class NodeConnector {
|
|
|
1213
1246
|
listSessions: (workspaceId) => this.executeNodeOperation('session.list', { workspaceId, limit: 20 }),
|
|
1214
1247
|
invalidate: (workspaceId) => {
|
|
1215
1248
|
this.workspaceFileService.dispose(workspaceId);
|
|
1249
|
+
this.workspaceWorkbenchService.invalidateWorkspaceFiles(workspaceId);
|
|
1216
1250
|
this.workspaceCoordinator.invalidate(workspaceId);
|
|
1217
1251
|
},
|
|
1218
1252
|
nodeId: () => this.config?.nodeId ?? 'runtime-node',
|
|
@@ -1263,6 +1297,7 @@ export class NodeConnector {
|
|
|
1263
1297
|
prepareForDisable: () => this.hostedConversationService?.prepareForDisable() ?? (() => { }),
|
|
1264
1298
|
publishCapabilities: () => this.send('capabilities', this.capabilities)
|
|
1265
1299
|
}).operations());
|
|
1300
|
+
this.operationRouter.registerAll(this.computerUseEnablement.operations());
|
|
1266
1301
|
this.operationRouter.registerAll(new SkillNodeOperationService(requireDatabase, () => {
|
|
1267
1302
|
if (this.config === undefined)
|
|
1268
1303
|
throw new Error('NODE_CONFIG_UNAVAILABLE');
|
|
@@ -1393,6 +1428,12 @@ export class NodeConnector {
|
|
|
1393
1428
|
}
|
|
1394
1429
|
this.capabilities = await this.projectConversationHostingCapability(this.capabilities);
|
|
1395
1430
|
}
|
|
1431
|
+
projectComputerUseCapability(capabilities) {
|
|
1432
|
+
return {
|
|
1433
|
+
...capabilities,
|
|
1434
|
+
computerUse: this.computerUseEnablement.capability()
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1396
1437
|
async projectConversationHostingCapability(capabilities) {
|
|
1397
1438
|
let storageReady = this.conversationHostingStorageReady;
|
|
1398
1439
|
let storageFailureCode = this.conversationHostingFailureCode;
|
|
@@ -1422,6 +1463,7 @@ export class NodeConnector {
|
|
|
1422
1463
|
this.database ??= new NodeDatabase(nodeDatabasePath(this.config, this.configPath), () => this.config?.nodeId ?? 'unregistered');
|
|
1423
1464
|
this.database.closeRunningTerminalSessions();
|
|
1424
1465
|
await this.initializeConversationHosting();
|
|
1466
|
+
this.computerUseEnablement.initialize();
|
|
1425
1467
|
this.directTransferService ??= this.createDirectTransferService();
|
|
1426
1468
|
this.connectionLifecycle.start();
|
|
1427
1469
|
this.controlChannel.connect();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { dirname, resolve } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
export const BUNDLED_CTAGS_VERSION = '6.2.1';
|
|
4
|
+
const TARGETS = {
|
|
5
|
+
'linux-x64': ['linux-x64', 'ctags'],
|
|
6
|
+
'win32-x64': ['win32-x64', 'ctags.exe']
|
|
7
|
+
};
|
|
8
|
+
export function bundledCtagsDirectory(packageRoot) {
|
|
9
|
+
return packageRoot === undefined
|
|
10
|
+
? resolve(dirname(fileURLToPath(import.meta.url)), '..', 'vendor/ctags')
|
|
11
|
+
: resolve(packageRoot, 'mar-node/vendor/ctags');
|
|
12
|
+
}
|
|
13
|
+
export function bundledCtagsPath(platform = process.platform, architecture = process.arch, packageRoot) {
|
|
14
|
+
const target = TARGETS[`${platform}-${architecture}`];
|
|
15
|
+
if (target === undefined)
|
|
16
|
+
throw new Error('CTAGS_PLATFORM_UNSUPPORTED');
|
|
17
|
+
return resolve(bundledCtagsDirectory(packageRoot), ...target);
|
|
18
|
+
}
|