@myagentroam/node 0.9.179 → 0.9.180

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/connector.js CHANGED
@@ -1674,13 +1674,13 @@ export class NodeConnector {
1674
1674
  this.scheduledSessions.start();
1675
1675
  await this.initializeConversationHosting();
1676
1676
  this.computerUseEnablement.initialize();
1677
- this.directTransferService ??= this.createDirectTransferService();
1677
+ this.directTransferService ??= await this.createDirectTransferService();
1678
1678
  this.connectionLifecycle.start();
1679
1679
  this.controlChannel.connect();
1680
1680
  }
1681
- createDirectTransferService() {
1681
+ async createDirectTransferService() {
1682
1682
  try {
1683
- return new DirectTransferService({
1683
+ return await DirectTransferService.create({
1684
1684
  nodeGeneration: () => this.nodeGeneration,
1685
1685
  send: (type, payload) => this.send(type, payload),
1686
1686
  terminalManager: this.terminalManager,
@@ -20,6 +20,7 @@ const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0
20
20
  const MAX_EDIT_IMAGES = 5;
21
21
  const SUPPORTED_EDIT_MIME_TYPES = new Set(['image/png', 'image/jpeg', 'image/webp']);
22
22
  const IMAGE_GENERATION_RESPONSE_TIMEOUT_MS = 5 * 60 * 1000;
23
+ const IMAGE_GENERATION_READ_IDLE_TIMEOUT_MS = 5 * 60 * 1000;
23
24
  export const codexImageGenToolDefinition = {
24
25
  name: 'codex_image_gen',
25
26
  agentScope: 'ROOT_ONLY',
@@ -230,19 +231,34 @@ async function readJsonResponse(response, signal) {
230
231
  if (response.body === null)
231
232
  return {};
232
233
  const reader = response.body.getReader();
234
+ const readController = new AbortController();
235
+ const readSignal = AbortSignal.any([signal, readController.signal]);
233
236
  const abort = () => {
234
- void reader.cancel(signal.reason).catch(() => undefined);
237
+ void reader.cancel(readSignal.reason).catch(() => undefined);
235
238
  };
236
- signal.addEventListener('abort', abort, { once: true });
239
+ readSignal.addEventListener('abort', abort, { once: true });
240
+ const onIdle = () => {
241
+ readController.abort(new MarAgentError('MAR_AGENT_IMAGE_GENERATION_READ_TIMEOUT', 'Image response body read timed out waiting for data.', {
242
+ details: {
243
+ reason: 'IMAGE_RESPONSE_READ_TIMEOUT',
244
+ idleTimeoutMs: IMAGE_GENERATION_READ_IDLE_TIMEOUT_MS
245
+ }
246
+ }));
247
+ };
248
+ let readTimeout = setTimeout(onIdle, IMAGE_GENERATION_READ_IDLE_TIMEOUT_MS);
237
249
  const chunks = [];
238
250
  let bytes = 0;
239
251
  try {
240
- signal.throwIfAborted();
252
+ readSignal.throwIfAborted();
241
253
  for (;;) {
242
- const chunk = await reader.read();
243
- signal.throwIfAborted();
254
+ const chunk = await waitForImageStep(reader.read(), readSignal);
255
+ readSignal.throwIfAborted();
244
256
  if (chunk.done)
245
257
  break;
258
+ if (chunk.value.byteLength === 0)
259
+ continue;
260
+ clearTimeout(readTimeout);
261
+ readTimeout = setTimeout(onIdle, IMAGE_GENERATION_READ_IDLE_TIMEOUT_MS);
246
262
  bytes += chunk.value.byteLength;
247
263
  if (bytes > RESPONSE_MAX_BYTES)
248
264
  throw new MarAgentError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Image response exceeded the size limit.');
@@ -254,7 +270,8 @@ async function readJsonResponse(response, signal) {
254
270
  throw error;
255
271
  }
256
272
  finally {
257
- signal.removeEventListener('abort', abort);
273
+ clearTimeout(readTimeout);
274
+ readSignal.removeEventListener('abort', abort);
258
275
  reader.releaseLock();
259
276
  }
260
277
  try {
@@ -1,3 +1,4 @@
1
+ import { createSocket } from 'node:dgram';
1
2
  import { directDataControlFrameSchema, directNodeCommandSchema } from '@myagentroam/protocol';
2
3
  import { TerminalDirectService } from './terminal-direct-service.js';
3
4
  import { IceUdpMuxListener as NativeIceUdpMuxListener } from 'node-datachannel';
@@ -21,12 +22,18 @@ export class DirectTransferService {
21
22
  leaseCheckMs;
22
23
  terminalDirect;
23
24
  disposed = false;
24
- constructor(options) {
25
- this.options = options;
26
- this.now = options.now ?? Date.now;
25
+ static async create(options) {
27
26
  const range = options.udpPortRange?.() ?? { begin: 49_152, end: 49_215 };
28
27
  const createIceUdpMuxListener = options.createIceUdpMuxListener ?? ((port) => new NativeIceUdpMuxListener(port));
29
- this.iceUdpMuxListener = bindIceUdpMuxListener(range, createIceUdpMuxListener);
28
+ // Windows can accept an IPv6 mux bind while another IPv4 endpoint owns the port.
29
+ const probe = options.probeUdp4Port ?? (process.platform === 'win32' ? probeUdp4Port : undefined);
30
+ const listener = await bindIceUdpMuxListener(range, createIceUdpMuxListener, probe);
31
+ return new DirectTransferService(options, listener);
32
+ }
33
+ constructor(options, iceUdpMuxListener) {
34
+ this.options = options;
35
+ this.now = options.now ?? Date.now;
36
+ this.iceUdpMuxListener = iceUdpMuxListener;
30
37
  const udpPort = this.iceUdpMuxListener.port();
31
38
  this.peerConfiguration = {
32
39
  iceServers: [],
@@ -465,10 +472,12 @@ function openFrameSessionId(data) {
465
472
  return undefined;
466
473
  }
467
474
  }
468
- function bindIceUdpMuxListener(range, create) {
475
+ async function bindIceUdpMuxListener(range, create, probe) {
469
476
  let lastError;
470
477
  for (let port = range.begin; port <= range.end; port += 1) {
471
478
  try {
479
+ if (probe !== undefined)
480
+ await probe(port);
472
481
  return create(port);
473
482
  }
474
483
  catch (error) {
@@ -477,6 +486,13 @@ function bindIceUdpMuxListener(range, create) {
477
486
  }
478
487
  throw new Error('DIRECT_UDP_PORT_UNAVAILABLE', { cause: lastError });
479
488
  }
489
+ export function probeUdp4Port(port) {
490
+ const socket = createSocket({ type: 'udp4', reuseAddr: false });
491
+ return new Promise((resolve, reject) => {
492
+ socket.once('error', (error) => socket.close(() => reject(error)));
493
+ socket.bind({ port, address: '0.0.0.0', exclusive: true }, () => socket.close(() => resolve()));
494
+ });
495
+ }
480
496
  export function fingerprint(sdp) {
481
497
  const match = sdp?.match(/^a=fingerprint:(sha-256 [A-F0-9:]+)\r?$/imu);
482
498
  return match?.[1];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.9.179",
3
+ "version": "0.9.180",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -36,10 +36,10 @@
36
36
  "playwright": "1.62.1",
37
37
  "ws": "^8.21.3",
38
38
  "zod": "4.4.3",
39
- "@myagentroam/agent": "0.9.179",
40
- "@myagentroam/code-navigation": "0.9.129",
39
+ "@myagentroam/agent": "0.9.180",
40
+ "@myagentroam/protocol": "0.9.180",
41
41
  "@myagentroam/marctl": "0.9.123",
42
- "@myagentroam/protocol": "0.9.179"
42
+ "@myagentroam/code-navigation": "0.9.129"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/ws": "^8.18.1"