@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.
@@ -24,6 +24,7 @@ export class MarAgentManagedRunController {
24
24
  #resourceIdleTtlMs;
25
25
  #createAgent;
26
26
  #modelFactory;
27
+ #computerUse;
27
28
  #disposePromise;
28
29
  #disposed = false;
29
30
  constructor(runner, host, options = {}) {
@@ -43,6 +44,7 @@ export class MarAgentManagedRunController {
43
44
  };
44
45
  })
45
46
  };
47
+ this.#computerUse = options.computerUse;
46
48
  }
47
49
  dispose() {
48
50
  if (this.#disposePromise !== undefined)
@@ -56,6 +58,7 @@ export class MarAgentManagedRunController {
56
58
  execution.cancel();
57
59
  await Promise.all([...new Set(this.#sessionRuntimes.values())].map((runtime) => this.#releaseRuntime(runtime)));
58
60
  await this.#mcpClients.close();
61
+ await this.#computerUse?.dispose();
59
62
  })();
60
63
  return this.#disposePromise;
61
64
  }
@@ -66,11 +69,17 @@ export class MarAgentManagedRunController {
66
69
  const release = (async () => {
67
70
  await this.#runtimeAcquisitions.get(sessionId)?.catch(() => undefined);
68
71
  const runtime = this.#sessionRuntimes.get(sessionId);
69
- if (runtime === undefined)
72
+ if (runtime === undefined) {
73
+ await this.#computerUse?.releaseSession(sessionId);
70
74
  return;
75
+ }
71
76
  if (runtime.activeRunId !== undefined)
72
77
  this.#executions.get(runtime.activeRunId)?.cancel();
73
78
  await this.#releaseRuntime(runtime);
79
+ await Promise.all([
80
+ this.#computerUse?.releaseSession(sessionId),
81
+ this.#computerUse?.releaseSession(runtime.session.id)
82
+ ]);
74
83
  })();
75
84
  const tracked = release.finally(() => {
76
85
  if (this.#sessionReleases.get(sessionId) === tracked)
@@ -157,6 +166,8 @@ export class MarAgentManagedRunController {
157
166
  ...secretEnvironment,
158
167
  ...mcpSecretEnvironment
159
168
  });
169
+ if (mcpServers.some((server) => server.name === 'computer'))
170
+ throw new Error('MAR_AGENT_MCP_RESERVED_NAMESPACE');
160
171
  images = parseRunnerImageAttachments(payload.attachments);
161
172
  }
162
173
  catch {
@@ -190,7 +201,8 @@ export class MarAgentManagedRunController {
190
201
  : {}),
191
202
  ...(typeof payload.personalInstructions === 'string'
192
203
  ? { personalInstructions: payload.personalInstructions }
193
- : {})
204
+ : {}),
205
+ computerUseEnabled: payload.computerUse === true
194
206
  });
195
207
  }
196
208
  cancel(runId, intent) {
@@ -230,6 +242,7 @@ export class MarAgentManagedRunController {
230
242
  let runtime;
231
243
  let executionMcpLease;
232
244
  let executionMcpBinding;
245
+ let computerUseBinding;
233
246
  try {
234
247
  const sessionKey = input.externalSessionId ?? input.sessionId;
235
248
  const acquisitionKeys = [...new Set([sessionKey, input.sessionId])];
@@ -257,16 +270,45 @@ export class MarAgentManagedRunController {
257
270
  throw new Error('MAR_AGENT_SESSION_RUNTIME_ACTIVE');
258
271
  runtime.activeRunId = input.runId;
259
272
  const executionMcpServers = input.mcpServers.filter((server) => server.lifecycle === 'EXECUTION');
273
+ computerUseBinding = await this.#computerUse?.bind({
274
+ runId: input.runId,
275
+ sessionId: runtime.session.id,
276
+ mode: input.mode,
277
+ enabled: input.computerUseEnabled === true,
278
+ modelSupportsImages: input.model.inputCapabilities.includes('IMAGE')
279
+ });
280
+ if (this.#disposed ||
281
+ this.#sessionReleases.has(sessionKey) ||
282
+ this.#sessionReleases.has(input.sessionId) ||
283
+ this.#sessionReleases.has(runtime.session.id)) {
284
+ await computerUseBinding?.close().catch(() => undefined);
285
+ computerUseBinding = undefined;
286
+ await this.#computerUse?.releaseSession(runtime.session.id);
287
+ throw new Error('MAR_AGENT_SESSION_RUNTIME_RELEASED');
288
+ }
260
289
  if (executionMcpServers.length > 0) {
261
290
  executionMcpLease = await this.#mcpClients.acquire(executionMcpServers, input.cwd, {
262
291
  reusable: false
263
292
  });
264
- const mcp = executionMcpLease.pool;
293
+ }
294
+ const userTools = executionMcpLease?.pool.definitions() ?? [];
295
+ const nodeTools = computerUseBinding?.tools ?? [];
296
+ if (userTools.some((tool) => tool.name.startsWith('mcp__computer__')))
297
+ throw new Error('MAR_AGENT_MCP_RESERVED_NAMESPACE');
298
+ if (new Set([...userTools, ...nodeTools].map((tool) => tool.name)).size !==
299
+ userTools.length + nodeTools.length)
300
+ throw new Error('MAR_AGENT_MCP_TOOL_NAMES_CONFLICT');
301
+ if (userTools.length > 0 || nodeTools.length > 0)
265
302
  executionMcpBinding = runtime.host.bindExecutionMcp({
266
- tools: mcp.definitions(),
267
- call: async (call) => mcp.call(call.name, call.arguments)
303
+ tools: [...nodeTools, ...userTools],
304
+ call: async (call, signal) => {
305
+ if (computerUseBinding?.tools.some((tool) => tool.name === call.name))
306
+ return computerUseBinding.call(call, signal);
307
+ if (executionMcpLease?.pool.definitions().some((tool) => tool.name === call.name))
308
+ return executionMcpLease.pool.call(call.name, call.arguments);
309
+ throw new Error('MAR_AGENT_MCP_TOOL_NOT_FOUND');
310
+ }
268
311
  });
269
- }
270
312
  const session = runtime.session;
271
313
  const reasoningEffort = input.effort
272
314
  ? marAgentReasoningEffortSchema.parse(input.effort)
@@ -330,6 +372,7 @@ export class MarAgentManagedRunController {
330
372
  this.#normalizers.delete(input.runId);
331
373
  this.host.active.delete(input.runId);
332
374
  this.#executions.delete(input.runId);
375
+ await computerUseBinding?.close().catch(() => undefined);
333
376
  runtime?.host.unbindExecutionMcp(executionMcpBinding);
334
377
  await executionMcpLease?.release().catch(() => undefined);
335
378
  if (runtime !== undefined)
@@ -511,7 +554,12 @@ export class MarAgentManagedRunController {
511
554
  delete runtime.idleTimer;
512
555
  }
513
556
  try {
514
- await runtime.agent.dispose();
557
+ try {
558
+ await this.#computerUse?.releaseSession(runtime.session.id);
559
+ }
560
+ finally {
561
+ await runtime.agent.dispose();
562
+ }
515
563
  }
516
564
  finally {
517
565
  await runtime.mcpLease.release().catch(() => undefined);
@@ -0,0 +1,172 @@
1
+ import { computerUseDisableInputSchema, computerUseEnableInputSchema, computerUseEnableResultSchema } from '@myagentroam/protocol';
2
+ import { computerUseRuntimeExecutable, saveNodeConfig } from '../config.js';
3
+ import { nodeOperationData } from '../util/node-operation-data.js';
4
+ /** Persists Computer Use authorization and owns its asynchronous capability probe. */
5
+ export class ComputerUseEnablementService {
6
+ options;
7
+ #now;
8
+ #capability;
9
+ #probeGeneration = 0;
10
+ constructor(options) {
11
+ this.options = options;
12
+ this.#now = options.now ?? Date.now;
13
+ this.#capability = this.#disabled();
14
+ }
15
+ capability() {
16
+ return this.#capability;
17
+ }
18
+ operations() {
19
+ return {
20
+ 'computer.use.enable': (data) => this.enable(data),
21
+ 'computer.use.disable': (data) => this.disable(data)
22
+ };
23
+ }
24
+ initialize() {
25
+ if (this.options.config()?.computerUse?.enabled !== true) {
26
+ this.#set(this.#disabled(), false);
27
+ return;
28
+ }
29
+ this.#beginProbe();
30
+ }
31
+ async enable(data) {
32
+ const parsed = computerUseEnableInputSchema.safeParse(nodeOperationData(data));
33
+ if (!parsed.success)
34
+ throw new Error('COMPUTER_USE_ENABLE_INPUT_INVALID');
35
+ const current = this.options.config();
36
+ if (current === undefined)
37
+ throw new Error('NODE_CONFIG_UNAVAILABLE');
38
+ if (current.computerUse?.enabled !== true) {
39
+ const next = {
40
+ ...current,
41
+ computerUse: {
42
+ ...current.computerUse,
43
+ enabled: true
44
+ }
45
+ };
46
+ await saveNodeConfig(next, this.options.configPath);
47
+ this.options.setConfig(next);
48
+ }
49
+ this.#beginProbe();
50
+ return computerUseEnableResultSchema.parse({ capability: this.#capability });
51
+ }
52
+ async disable(data) {
53
+ const parsed = computerUseDisableInputSchema.safeParse(nodeOperationData(data));
54
+ if (!parsed.success)
55
+ throw new Error('COMPUTER_USE_DISABLE_INPUT_INVALID');
56
+ const current = this.options.config();
57
+ if (current === undefined)
58
+ throw new Error('NODE_CONFIG_UNAVAILABLE');
59
+ this.#probeGeneration += 1;
60
+ if (current.computerUse?.enabled === true) {
61
+ const next = {
62
+ ...current,
63
+ computerUse: {
64
+ ...current.computerUse,
65
+ enabled: false
66
+ }
67
+ };
68
+ this.options.setConfig(next);
69
+ try {
70
+ await this.options.provider.reset();
71
+ await saveNodeConfig(next, this.options.configPath);
72
+ }
73
+ catch (error) {
74
+ this.options.setConfig(current);
75
+ this.initialize();
76
+ throw error;
77
+ }
78
+ }
79
+ else {
80
+ await this.options.provider.reset();
81
+ }
82
+ this.#set(this.#disabled());
83
+ return computerUseEnableResultSchema.parse({ capability: this.#capability });
84
+ }
85
+ #beginProbe() {
86
+ const config = this.options.config();
87
+ if (config === undefined || config.computerUse?.enabled !== true) {
88
+ this.#set(this.#disabled());
89
+ return;
90
+ }
91
+ const runtimeExecutable = computerUseRuntimeExecutable(config, this.options.configPath);
92
+ if (runtimeExecutable === undefined) {
93
+ this.#set(this.#unavailable('COMPUTER_RUNTIME_MISSING'));
94
+ return;
95
+ }
96
+ const generation = ++this.#probeGeneration;
97
+ this.#set({
98
+ apiVersion: 1,
99
+ enabled: true,
100
+ state: 'INITIALIZING',
101
+ backend: 'PYAUTOGUI',
102
+ supportsScreenshot: false,
103
+ supportsInput: false,
104
+ supportsUnicodePaste: false,
105
+ supportsPrimaryDisplay: false,
106
+ measuredAt: this.#now()
107
+ });
108
+ void this.options.provider
109
+ .reset()
110
+ .then(() => this.options.prepareRuntime())
111
+ .then(() => this.options.provider.support())
112
+ .then((support) => {
113
+ if (generation !== this.#probeGeneration)
114
+ return;
115
+ this.#set(this.#fromSupport(support));
116
+ })
117
+ .catch((error) => {
118
+ if (generation !== this.#probeGeneration)
119
+ return;
120
+ this.#set(this.#unavailable(error instanceof Error && /^[A-Z][A-Z0-9_]{0,79}$/u.test(error.message)
121
+ ? error.message
122
+ : 'COMPUTER_PROBE_FAILED'));
123
+ });
124
+ }
125
+ #fromSupport(support) {
126
+ if (!support.available)
127
+ return this.#unavailable(support.reasonCode ?? 'COMPUTER_UNAVAILABLE', support);
128
+ return {
129
+ apiVersion: 1,
130
+ enabled: true,
131
+ state: 'AVAILABLE',
132
+ backend: 'PYAUTOGUI',
133
+ supportsScreenshot: support.supportsScreenshot,
134
+ supportsInput: support.supportsInput,
135
+ supportsUnicodePaste: support.supportsUnicodePaste,
136
+ supportsPrimaryDisplay: support.supportsPrimaryDisplay,
137
+ measuredAt: this.#now()
138
+ };
139
+ }
140
+ #disabled() {
141
+ return {
142
+ apiVersion: 1,
143
+ enabled: false,
144
+ state: 'DISABLED',
145
+ backend: 'PYAUTOGUI',
146
+ supportsScreenshot: false,
147
+ supportsInput: false,
148
+ supportsUnicodePaste: false,
149
+ supportsPrimaryDisplay: false,
150
+ measuredAt: this.#now()
151
+ };
152
+ }
153
+ #unavailable(reasonCode, support) {
154
+ return {
155
+ apiVersion: 1,
156
+ enabled: true,
157
+ state: 'UNAVAILABLE',
158
+ backend: 'PYAUTOGUI',
159
+ supportsScreenshot: support?.supportsScreenshot ?? false,
160
+ supportsInput: support?.supportsInput ?? false,
161
+ supportsUnicodePaste: support?.supportsUnicodePaste ?? false,
162
+ supportsPrimaryDisplay: support?.supportsPrimaryDisplay ?? false,
163
+ reasonCode,
164
+ measuredAt: this.#now()
165
+ };
166
+ }
167
+ #set(capability, publish = true) {
168
+ this.#capability = computerUseEnableResultSchema.parse({ capability }).capability;
169
+ if (publish)
170
+ this.options.publish(this.#capability);
171
+ }
172
+ }
@@ -320,6 +320,7 @@ export class ContinuousRunCoordinator {
320
320
  ...(pending.marAgentRuntimeScopeId === undefined
321
321
  ? {}
322
322
  : { marAgentRuntimeScopeId: pending.marAgentRuntimeScopeId }),
323
+ ...(pending.computerUse === undefined ? {} : { computerUse: pending.computerUse }),
323
324
  mcpInstallations: [
324
325
  ...this.options.mcps(pending.workspaceId),
325
326
  ...(missionMcp === undefined ? [] : [missionMcp.installation])
@@ -38,6 +38,7 @@ export class NodeConnectionLifecycleService {
38
38
  ? {}
39
39
  : { conversationHosting: config.conversationHosting }),
40
40
  ...(config.directTransfer === undefined ? {} : { directTransfer: config.directTransfer }),
41
+ ...(config.computerUse === undefined ? {} : { computerUse: config.computerUse }),
41
42
  nodeId,
42
43
  credential
43
44
  };
@@ -215,6 +215,7 @@ export class SessionLifecycleService {
215
215
  ...(input.marAgentRuntimeScopeId === undefined
216
216
  ? {}
217
217
  : { marAgentRuntimeScopeId: input.marAgentRuntimeScopeId }),
218
+ ...(input.computerUse === undefined ? {} : { computerUse: input.computerUse }),
218
219
  ...(input.attachmentIds === undefined ? {} : { attachmentIds: input.attachmentIds })
219
220
  }));
220
221
  const nativeSessionId = await identity;
@@ -160,6 +160,7 @@ export class SessionMessageService {
160
160
  ...(input.marAgentRuntimeScopeId === undefined
161
161
  ? {}
162
162
  : { marAgentRuntimeScopeId: input.marAgentRuntimeScopeId }),
163
+ ...(input.computerUse === undefined ? {} : { computerUse: input.computerUse }),
163
164
  ...(missionMessage !== undefined
164
165
  ? { mission: missionMessage.mission }
165
166
  : isMissionRef(input.__missionRef)