@myagentroam/node 0.11.38 → 0.11.40
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 +181 -22
- package/dist/database.js +35 -11
- package/dist/main.js +12 -1
- package/dist/marctl-guidance.js +18 -27
- package/dist/mcp/naming.js +6 -1
- package/dist/migrations/v022.js +58 -0
- package/dist/runner/abstract-runner.js +14 -0
- package/dist/runner/claude/tool-normalizer.js +6 -1
- package/dist/runner/codex/conversation-parser.js +23 -4
- package/dist/runner/codex/managed-run-controller.js +4 -2
- package/dist/runner/codex/tool-normalizer.js +9 -0
- package/dist/runner/execution-presentation.js +56 -0
- package/dist/runner/mar-agent/agent-network-extension.js +168 -0
- package/dist/runner/mar-agent/agent-network-wait-output.js +72 -0
- 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 +241 -121
- package/dist/runner/mar-agent/managed-run-controller.js +13 -5
- package/dist/runner/mar-agent/marctl-skill.js +3 -3
- package/dist/runner/mar-agent/runtime-model-factory.js +1 -0
- package/dist/runner/mar-agent/session-runtime.js +34 -1
- package/dist/runner/mar-agent-runner.js +36 -0
- package/dist/runner/opencode/tool-normalizer.js +6 -1
- package/dist/runtime-state.js +57 -0
- package/dist/service/agent-network-bindings.js +483 -0
- package/dist/service/agent-network-goal.js +19 -0
- package/dist/service/agent-network-invoke.js +179 -0
- package/dist/service/agent-network-observations.js +377 -0
- package/dist/service/agent-network-service.js +315 -0
- package/dist/service/agent-network-wait.js +130 -0
- package/dist/service/composer-message-input.js +9 -2
- package/dist/service/computer-use-enablement-service.js +13 -7
- package/dist/service/continuous-run-coordinator.js +24 -1
- package/dist/service/conversation-hosting-enablement-service.js +2 -2
- package/dist/service/goal-continuation-coordinator.js +12 -2
- package/dist/service/marctl-caller.js +80 -0
- package/dist/service/marctl-execution-credential-control.js +65 -0
- package/dist/service/model-usage-service.js +9 -0
- package/dist/service/node-config-mutations.js +27 -0
- package/dist/service/node-control-message-service.js +10 -0
- package/dist/service/node-request-service.js +52 -7
- package/dist/service/node-upgrade-coordinator.js +79 -8
- package/dist/service/node-upgrade-credential-control.js +57 -0
- package/dist/service/node-upgrade-reporter.js +108 -0
- package/dist/service/run-event-service.js +8 -0
- package/dist/service/run-workbench-service.js +11 -0
- package/dist/service/supervised-node-child.js +153 -0
- package/dist/service/supervisor-upgrade-handoff.js +104 -0
- package/dist/session/native-session-projection-service.js +69 -6
- package/dist/session/operation-admission.js +7 -0
- package/dist/session/runtime/conversation-projection.js +25 -3
- package/dist/session/session-catalog-service.js +156 -32
- package/dist/session/session-lifecycle-service.js +68 -42
- package/dist/session/session-message-service.js +16 -6
- package/dist/supervisor.js +209 -20
- package/dist/terminal.js +41 -21
- package/package.json +6 -5
- /package/dist/{runner/mar-agent/computer-use-probe.js → computer-use/probe-source.js} +0 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { resolve } from 'node:path';
|
|
6
|
+
import { createInterface } from 'node:readline';
|
|
7
|
+
import { terminateProcessTree } from '../process-tree.js';
|
|
8
|
+
import { ComputerUseError } from './contracts.js';
|
|
9
|
+
import { MAX_IMAGE_BYTES, MAX_IMAGES, PYTHON_CLEANUP_SOURCE, PYTHON_WORKER_SOURCE } from './worker-source.js';
|
|
10
|
+
const MAX_TEXT_BYTES = 1024 * 1024;
|
|
11
|
+
export class PythonWorker {
|
|
12
|
+
child;
|
|
13
|
+
lines;
|
|
14
|
+
directory;
|
|
15
|
+
#exited;
|
|
16
|
+
#pending = new Map();
|
|
17
|
+
#stderr = '';
|
|
18
|
+
#closed = false;
|
|
19
|
+
#closePromise;
|
|
20
|
+
#requestStarted = false;
|
|
21
|
+
constructor(child, lines, directory) {
|
|
22
|
+
this.child = child;
|
|
23
|
+
this.lines = lines;
|
|
24
|
+
this.directory = directory;
|
|
25
|
+
this.#exited = new Promise((resolveExit) => this.child.once('close', () => resolveExit()));
|
|
26
|
+
this.lines.on('line', (line) => this.#handleLine(line));
|
|
27
|
+
this.child.stderr.on('data', (chunk) => {
|
|
28
|
+
this.#stderr = boundedText(`${this.#stderr}${chunk.toString('utf8')}`);
|
|
29
|
+
});
|
|
30
|
+
this.child.once('exit', (code, signal) => {
|
|
31
|
+
const error = new ComputerUseError('COMPUTER_WORKER_EXITED', `Computer Use worker exited (${code ?? signal ?? 'unknown'}).${this.#stderr ? ` ${this.#stderr}` : ''}`);
|
|
32
|
+
for (const pending of this.#pending.values())
|
|
33
|
+
pending.reject(error);
|
|
34
|
+
this.#pending.clear();
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
get startedRequest() {
|
|
38
|
+
return this.#requestStarted;
|
|
39
|
+
}
|
|
40
|
+
static async start(runtimeExecutable, onStarting) {
|
|
41
|
+
const directory = await mkdtemp(resolve(tmpdir(), 'mar-computer-use-'));
|
|
42
|
+
const script = resolve(directory, 'worker.py');
|
|
43
|
+
let child;
|
|
44
|
+
try {
|
|
45
|
+
await writeFile(script, PYTHON_WORKER_SOURCE, { encoding: 'utf8', mode: 0o600 });
|
|
46
|
+
child = spawn(runtimeExecutable, ['-u', script], {
|
|
47
|
+
cwd: directory,
|
|
48
|
+
env: minimalWorkerEnvironment(),
|
|
49
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
50
|
+
windowsHide: true,
|
|
51
|
+
detached: process.platform !== 'win32'
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
try {
|
|
56
|
+
await rm(directory, { recursive: true, force: true });
|
|
57
|
+
}
|
|
58
|
+
catch (cleanup) {
|
|
59
|
+
throw new ComputerUseError('COMPUTER_CLEANUP_FAILED', 'Computer Use worker startup cleanup failed.', {
|
|
60
|
+
cause: new AggregateError([error, cleanup])
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
const lines = createInterface({ input: child.stdout, crlfDelay: Infinity });
|
|
66
|
+
const worker = new PythonWorker(child, lines, directory);
|
|
67
|
+
try {
|
|
68
|
+
onStarting(worker);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
try {
|
|
72
|
+
await worker.close();
|
|
73
|
+
}
|
|
74
|
+
catch (cleanup) {
|
|
75
|
+
throw new ComputerUseError('COMPUTER_CLEANUP_FAILED', 'Computer Use worker handoff cleanup failed.', {
|
|
76
|
+
cause: new AggregateError([error, cleanup])
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
await waitForWorkerReady(lines, child);
|
|
82
|
+
return worker;
|
|
83
|
+
}
|
|
84
|
+
execute(code, timeoutMs, signal) {
|
|
85
|
+
this.#requestStarted = false;
|
|
86
|
+
if (this.#closed)
|
|
87
|
+
return Promise.reject(new ComputerUseError('COMPUTER_WORKER_EXITED', 'Computer Use worker is closed.'));
|
|
88
|
+
const id = randomUUID();
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
let settled = false;
|
|
91
|
+
const finish = (callback) => {
|
|
92
|
+
if (settled)
|
|
93
|
+
return;
|
|
94
|
+
settled = true;
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
signal.removeEventListener('abort', abort);
|
|
97
|
+
this.#pending.delete(id);
|
|
98
|
+
callback();
|
|
99
|
+
};
|
|
100
|
+
const abort = () => finish(() => reject(new ComputerUseError('COMPUTER_EXECUTION_CANCELLED', 'Computer Use execution cancelled.')));
|
|
101
|
+
const timer = setTimeout(() => finish(() => reject(new ComputerUseError('COMPUTER_EXECUTION_TIMEOUT', 'Computer Use execution timed out.'))), timeoutMs);
|
|
102
|
+
if (signal.aborted) {
|
|
103
|
+
abort();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.#requestStarted = true;
|
|
107
|
+
this.#pending.set(id, {
|
|
108
|
+
resolve: (value) => finish(() => resolve(value)),
|
|
109
|
+
reject: (error) => finish(() => reject(error))
|
|
110
|
+
});
|
|
111
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
112
|
+
if (signal.aborted) {
|
|
113
|
+
abort();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this.child.stdin.write(`${JSON.stringify({ id, code })}\n`, (error) => {
|
|
117
|
+
if (error !== null && error !== undefined)
|
|
118
|
+
finish(() => reject(new ComputerUseError('COMPUTER_WORKER_WRITE_FAILED', 'Worker request failed.', {
|
|
119
|
+
cause: error
|
|
120
|
+
})));
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
close() {
|
|
125
|
+
if (this.#closePromise !== undefined)
|
|
126
|
+
return this.#closePromise;
|
|
127
|
+
this.#closed = true;
|
|
128
|
+
this.#closePromise = (async () => {
|
|
129
|
+
this.lines.close();
|
|
130
|
+
this.child.stdin.end();
|
|
131
|
+
if (this.child.exitCode === null && this.child.signalCode === null)
|
|
132
|
+
await terminateProcessTree(this.child).catch(() => this.child.kill('SIGKILL'));
|
|
133
|
+
if (!(await this.#waitForExit(2_000))) {
|
|
134
|
+
try {
|
|
135
|
+
if (process.platform !== 'win32' && this.child.pid !== undefined)
|
|
136
|
+
process.kill(-this.child.pid, 'SIGKILL');
|
|
137
|
+
else
|
|
138
|
+
this.child.kill('SIGKILL');
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
this.child.kill('SIGKILL');
|
|
142
|
+
}
|
|
143
|
+
if (!(await this.#waitForExit(2_000)))
|
|
144
|
+
throw new ComputerUseError('COMPUTER_WORKER_EXIT_FAILED', 'Computer Use worker did not exit.');
|
|
145
|
+
}
|
|
146
|
+
if (this.child.pid !== undefined)
|
|
147
|
+
await runWorkerCleanup(this.child.spawnfile, this.directory);
|
|
148
|
+
await rm(this.directory, { recursive: true, force: true });
|
|
149
|
+
})();
|
|
150
|
+
return this.#closePromise;
|
|
151
|
+
}
|
|
152
|
+
#waitForExit(timeoutMs) {
|
|
153
|
+
return new Promise((resolveWait) => {
|
|
154
|
+
const timer = setTimeout(() => resolveWait(false), timeoutMs);
|
|
155
|
+
void this.#exited.then(() => {
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
resolveWait(true);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
#handleLine(line) {
|
|
162
|
+
let value;
|
|
163
|
+
try {
|
|
164
|
+
value = JSON.parse(line);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
170
|
+
return;
|
|
171
|
+
const message = value;
|
|
172
|
+
if (message.type === 'ready')
|
|
173
|
+
return;
|
|
174
|
+
const pending = typeof message.id === 'string' ? this.#pending.get(message.id) : undefined;
|
|
175
|
+
if (pending === undefined)
|
|
176
|
+
return;
|
|
177
|
+
if (message.type === 'result') {
|
|
178
|
+
try {
|
|
179
|
+
pending.resolve({
|
|
180
|
+
logs: Array.isArray(message.logs)
|
|
181
|
+
? message.logs
|
|
182
|
+
.filter((value) => typeof value === 'string')
|
|
183
|
+
.map((value) => boundedText(value))
|
|
184
|
+
: [],
|
|
185
|
+
images: parseWorkerImages(message.images)
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
pending.reject(error);
|
|
190
|
+
}
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (message.type !== 'error')
|
|
194
|
+
return;
|
|
195
|
+
pending.reject(new ComputerUseError('COMPUTER_PYTHON_FAILED', typeof message.error === 'string' ? boundedText(message.error) : 'Python execution failed.'));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function parseWorkerImages(value) {
|
|
199
|
+
if (!Array.isArray(value) || value.length > MAX_IMAGES)
|
|
200
|
+
throw new ComputerUseError('COMPUTER_IMAGE_INVALID', 'Worker returned invalid images.');
|
|
201
|
+
return value.map((candidate) => {
|
|
202
|
+
if (candidate === null ||
|
|
203
|
+
typeof candidate !== 'object' ||
|
|
204
|
+
candidate['mimeType'] !== 'image/png' ||
|
|
205
|
+
typeof candidate['dataBase64'] !== 'string')
|
|
206
|
+
throw new ComputerUseError('COMPUTER_IMAGE_INVALID', 'Worker returned an invalid image.');
|
|
207
|
+
const dataBase64 = candidate['dataBase64'];
|
|
208
|
+
if (dataBase64.length > Math.ceil((MAX_IMAGE_BYTES * 4) / 3) + 4)
|
|
209
|
+
throw new ComputerUseError('COMPUTER_IMAGE_TOO_LARGE', 'Worker image exceeds the size limit.');
|
|
210
|
+
const bytes = Buffer.from(dataBase64, 'base64');
|
|
211
|
+
if (bytes.length === 0 ||
|
|
212
|
+
bytes.length > MAX_IMAGE_BYTES ||
|
|
213
|
+
!bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])))
|
|
214
|
+
throw new ComputerUseError('COMPUTER_IMAGE_INVALID', 'Worker returned an invalid PNG image.');
|
|
215
|
+
return { mimeType: 'image/png', dataBase64 };
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
async function waitForWorkerReady(lines, child) {
|
|
219
|
+
await new Promise((resolveReady, reject) => {
|
|
220
|
+
const timeout = setTimeout(() => {
|
|
221
|
+
cleanup();
|
|
222
|
+
reject(new ComputerUseError('COMPUTER_WORKER_START_FAILED', 'Worker start timed out.'));
|
|
223
|
+
}, 10_000);
|
|
224
|
+
const line = (value) => {
|
|
225
|
+
try {
|
|
226
|
+
if (JSON.parse(value).type !== 'ready')
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
cleanup();
|
|
233
|
+
resolveReady();
|
|
234
|
+
};
|
|
235
|
+
const exit = () => {
|
|
236
|
+
cleanup();
|
|
237
|
+
reject(new ComputerUseError('COMPUTER_WORKER_START_FAILED', 'Worker exited during startup.'));
|
|
238
|
+
};
|
|
239
|
+
const failed = (error) => {
|
|
240
|
+
cleanup();
|
|
241
|
+
reject(new ComputerUseError('COMPUTER_WORKER_START_FAILED', 'Worker could not start.', {
|
|
242
|
+
cause: error
|
|
243
|
+
}));
|
|
244
|
+
};
|
|
245
|
+
const cleanup = () => {
|
|
246
|
+
clearTimeout(timeout);
|
|
247
|
+
lines.off('line', line);
|
|
248
|
+
child.off('exit', exit);
|
|
249
|
+
child.off('error', failed);
|
|
250
|
+
};
|
|
251
|
+
lines.on('line', line);
|
|
252
|
+
child.once('exit', exit);
|
|
253
|
+
child.once('error', failed);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
async function runWorkerCleanup(executable, directory) {
|
|
257
|
+
const child = spawn(executable, ['-c', PYTHON_CLEANUP_SOURCE], {
|
|
258
|
+
cwd: directory,
|
|
259
|
+
env: minimalWorkerEnvironment(),
|
|
260
|
+
detached: process.platform !== 'win32',
|
|
261
|
+
stdio: 'ignore',
|
|
262
|
+
windowsHide: true
|
|
263
|
+
});
|
|
264
|
+
await new Promise((resolveCleanup, reject) => {
|
|
265
|
+
let timedOut = false;
|
|
266
|
+
let forceTimer;
|
|
267
|
+
const timer = setTimeout(() => {
|
|
268
|
+
timedOut = true;
|
|
269
|
+
void terminateProcessTree(child).catch(() => child.kill('SIGKILL'));
|
|
270
|
+
forceTimer = setTimeout(() => {
|
|
271
|
+
child.kill('SIGKILL');
|
|
272
|
+
reject(new ComputerUseError('COMPUTER_CLEANUP_FAILED', 'Computer Use cleanup process did not exit.'));
|
|
273
|
+
}, 2_000);
|
|
274
|
+
}, 5_000);
|
|
275
|
+
const finish = (error) => {
|
|
276
|
+
clearTimeout(timer);
|
|
277
|
+
if (forceTimer !== undefined)
|
|
278
|
+
clearTimeout(forceTimer);
|
|
279
|
+
if (error !== undefined)
|
|
280
|
+
reject(error);
|
|
281
|
+
else
|
|
282
|
+
resolveCleanup();
|
|
283
|
+
};
|
|
284
|
+
child.once('close', (code) => {
|
|
285
|
+
if (timedOut || code !== 0)
|
|
286
|
+
finish(new ComputerUseError('COMPUTER_CLEANUP_FAILED', 'Computer Use cleanup process failed.'));
|
|
287
|
+
else
|
|
288
|
+
finish();
|
|
289
|
+
});
|
|
290
|
+
child.once('error', (error) => finish(new ComputerUseError('COMPUTER_CLEANUP_FAILED', 'Computer Use cleanup process could not start.', { cause: error })));
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
function minimalWorkerEnvironment() {
|
|
294
|
+
const keys = [
|
|
295
|
+
'PATH',
|
|
296
|
+
'SystemRoot',
|
|
297
|
+
'WINDIR',
|
|
298
|
+
'TEMP',
|
|
299
|
+
'TMP',
|
|
300
|
+
'TMPDIR',
|
|
301
|
+
'HOME',
|
|
302
|
+
'USERPROFILE',
|
|
303
|
+
'DISPLAY',
|
|
304
|
+
'XAUTHORITY',
|
|
305
|
+
'LANG',
|
|
306
|
+
'LC_ALL'
|
|
307
|
+
];
|
|
308
|
+
const environment = {};
|
|
309
|
+
for (const key of keys) {
|
|
310
|
+
const value = process.env[key];
|
|
311
|
+
if (value !== undefined)
|
|
312
|
+
environment[key] = value;
|
|
313
|
+
}
|
|
314
|
+
return environment;
|
|
315
|
+
}
|
|
316
|
+
function boundedText(value) {
|
|
317
|
+
const bytes = Buffer.from(value);
|
|
318
|
+
return bytes.length <= MAX_TEXT_BYTES
|
|
319
|
+
? value
|
|
320
|
+
: `${bytes.subarray(0, MAX_TEXT_BYTES).toString('utf8')}\n[truncated]`;
|
|
321
|
+
}
|