@myagentroam/node 0.9.68 → 0.9.69
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 +67 -38
- package/dist/service/direct-transfer-service.js +35 -9
- package/package.json +3 -3
package/dist/connector.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { dirname, resolve } from 'node:path';
|
|
2
|
-
import { createEnvelope } from '@myagentroam/protocol';
|
|
2
|
+
import { createEnvelope, directNodeCommandSchema } from '@myagentroam/protocol';
|
|
3
3
|
import { hostedConversationStorePath, loadNodeConfig, directTransferUdpPortRange, nodeDataDirectory, nodeDatabasePath } from './config.js';
|
|
4
4
|
import { NodeMetrics, nodeLog } from './operational.js';
|
|
5
5
|
import { assertLocalSqlitePath } from './storage.js';
|
|
@@ -1075,36 +1075,6 @@ export class NodeConnector {
|
|
|
1075
1075
|
void this.terminalManager.shutdown();
|
|
1076
1076
|
}
|
|
1077
1077
|
});
|
|
1078
|
-
this.directTransferService = new DirectTransferService({
|
|
1079
|
-
nodeGeneration: () => this.nodeGeneration,
|
|
1080
|
-
send: (type, payload) => this.send(type, payload),
|
|
1081
|
-
udpPortRange: () => directTransferUdpPortRange(this.config),
|
|
1082
|
-
openUpload: async (descriptor) => {
|
|
1083
|
-
if (descriptor.resource.kind !== 'WORKSPACE_FILE')
|
|
1084
|
-
throw new Error('DIRECT_RESOURCE_UNSUPPORTED');
|
|
1085
|
-
const workspace = this.workspaceCoordinator.require(descriptor.resource.workspaceId);
|
|
1086
|
-
const upload = await this.workspaceUploadService.createDirect(workspace, {
|
|
1087
|
-
path: descriptor.resource.path,
|
|
1088
|
-
size: descriptor.size,
|
|
1089
|
-
sha256: descriptor.sha256,
|
|
1090
|
-
overwrite: descriptor.overwrite === true,
|
|
1091
|
-
...(descriptor.composerAttachment === undefined
|
|
1092
|
-
? {}
|
|
1093
|
-
: { composerAttachment: descriptor.composerAttachment })
|
|
1094
|
-
});
|
|
1095
|
-
return {
|
|
1096
|
-
offset: upload.receivedBytes,
|
|
1097
|
-
write: (bytes) => this.workspaceUploadService.writeDirect(upload.uploadId, bytes),
|
|
1098
|
-
complete: (size, sha256) => this.workspaceUploadService.completeDirect(upload.uploadId, size, sha256),
|
|
1099
|
-
cancel: () => this.workspaceUploadService.cancel(upload.uploadId)
|
|
1100
|
-
};
|
|
1101
|
-
},
|
|
1102
|
-
openDownload: async (descriptor) => {
|
|
1103
|
-
if (this.workspaceWorkbenchService === undefined)
|
|
1104
|
-
throw new Error('DIRECT_RESOURCE_UNAVAILABLE');
|
|
1105
|
-
return this.workspaceWorkbenchService.directDownload(descriptor);
|
|
1106
|
-
}
|
|
1107
|
-
});
|
|
1108
1078
|
this.controlMessages = new NodeControlMessageService({
|
|
1109
1079
|
config: () => this.config,
|
|
1110
1080
|
register: (nodeId, credential) => this.connectionLifecycle.register(nodeId, credential),
|
|
@@ -1113,12 +1083,7 @@ export class NodeConnector {
|
|
|
1113
1083
|
inspectWorkspace: (envelope) => this.workspaceCoordinator.inspect(envelope),
|
|
1114
1084
|
handleNodeRequest: (envelope) => this.nodeRequests.handle(envelope),
|
|
1115
1085
|
handleRuntimeCredentialResponse: (envelope) => this.runtimeCredentials.handle(envelope),
|
|
1116
|
-
handleDirectTransfer: (envelope) => this.
|
|
1117
|
-
type: envelope.type,
|
|
1118
|
-
...(typeof envelope.payload === 'object' && envelope.payload !== null
|
|
1119
|
-
? envelope.payload
|
|
1120
|
-
: {})
|
|
1121
|
-
}),
|
|
1086
|
+
handleDirectTransfer: (envelope) => this.handleDirectTransfer(envelope),
|
|
1122
1087
|
reconcileRuns: (envelope) => this.runEventBridge.reconcile(envelope),
|
|
1123
1088
|
runners: this.runners,
|
|
1124
1089
|
deliverSessionChannelMessage: (envelope) => this.runnerChannelMessages.handle(envelope),
|
|
@@ -1419,9 +1384,72 @@ export class NodeConnector {
|
|
|
1419
1384
|
this.database ??= new NodeDatabase(nodeDatabasePath(this.config, this.configPath), () => this.config?.nodeId ?? 'unregistered');
|
|
1420
1385
|
this.database.closeRunningTerminalSessions();
|
|
1421
1386
|
await this.initializeConversationHosting();
|
|
1387
|
+
this.directTransferService ??= this.createDirectTransferService();
|
|
1422
1388
|
this.connectionLifecycle.start();
|
|
1423
1389
|
this.controlChannel.connect();
|
|
1424
1390
|
}
|
|
1391
|
+
createDirectTransferService() {
|
|
1392
|
+
try {
|
|
1393
|
+
return new DirectTransferService({
|
|
1394
|
+
nodeGeneration: () => this.nodeGeneration,
|
|
1395
|
+
send: (type, payload) => this.send(type, payload),
|
|
1396
|
+
udpPortRange: () => directTransferUdpPortRange(this.config),
|
|
1397
|
+
openUpload: async (descriptor) => {
|
|
1398
|
+
if (descriptor.resource.kind !== 'WORKSPACE_FILE')
|
|
1399
|
+
throw new Error('DIRECT_RESOURCE_UNSUPPORTED');
|
|
1400
|
+
const workspace = this.workspaceCoordinator.require(descriptor.resource.workspaceId);
|
|
1401
|
+
const upload = await this.workspaceUploadService.createDirect(workspace, {
|
|
1402
|
+
path: descriptor.resource.path,
|
|
1403
|
+
size: descriptor.size,
|
|
1404
|
+
sha256: descriptor.sha256,
|
|
1405
|
+
overwrite: descriptor.overwrite === true,
|
|
1406
|
+
...(descriptor.composerAttachment === undefined
|
|
1407
|
+
? {}
|
|
1408
|
+
: { composerAttachment: descriptor.composerAttachment })
|
|
1409
|
+
});
|
|
1410
|
+
return {
|
|
1411
|
+
offset: upload.receivedBytes,
|
|
1412
|
+
write: (bytes) => this.workspaceUploadService.writeDirect(upload.uploadId, bytes),
|
|
1413
|
+
complete: (size, sha256) => this.workspaceUploadService.completeDirect(upload.uploadId, size, sha256),
|
|
1414
|
+
cancel: () => this.workspaceUploadService.cancel(upload.uploadId)
|
|
1415
|
+
};
|
|
1416
|
+
},
|
|
1417
|
+
openDownload: async (descriptor) => {
|
|
1418
|
+
if (this.workspaceWorkbenchService === undefined)
|
|
1419
|
+
throw new Error('DIRECT_RESOURCE_UNAVAILABLE');
|
|
1420
|
+
return this.workspaceWorkbenchService.directDownload(descriptor);
|
|
1421
|
+
}
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
catch (error) {
|
|
1425
|
+
if (!(error instanceof Error) || error.message !== 'DIRECT_UDP_PORT_UNAVAILABLE')
|
|
1426
|
+
throw error;
|
|
1427
|
+
const range = directTransferUdpPortRange(this.config);
|
|
1428
|
+
nodeLog('direct-transfer.udp-unavailable', {
|
|
1429
|
+
portRangeBegin: range.begin,
|
|
1430
|
+
portRangeEnd: range.end
|
|
1431
|
+
});
|
|
1432
|
+
return undefined;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
handleDirectTransfer(envelope) {
|
|
1436
|
+
const command = {
|
|
1437
|
+
type: envelope.type,
|
|
1438
|
+
...(typeof envelope.payload === 'object' && envelope.payload !== null ? envelope.payload : {})
|
|
1439
|
+
};
|
|
1440
|
+
if (this.directTransferService !== undefined)
|
|
1441
|
+
return this.directTransferService.handle(command);
|
|
1442
|
+
const parsed = directNodeCommandSchema.safeParse(command);
|
|
1443
|
+
if (!parsed.success)
|
|
1444
|
+
return false;
|
|
1445
|
+
this.send('direct.error', {
|
|
1446
|
+
type: 'direct.error',
|
|
1447
|
+
directSessionId: parsed.data.directSessionId,
|
|
1448
|
+
code: 'DIRECT_UDP_PORT_UNAVAILABLE',
|
|
1449
|
+
message: 'The configured direct-transfer UDP port range is unavailable.'
|
|
1450
|
+
});
|
|
1451
|
+
return true;
|
|
1452
|
+
}
|
|
1425
1453
|
clearMissionRetry(sessionId) {
|
|
1426
1454
|
const retry = this.missionRetryTimers.get(sessionId);
|
|
1427
1455
|
if (retry !== undefined)
|
|
@@ -1600,7 +1628,8 @@ export class NodeConnector {
|
|
|
1600
1628
|
const terminalDrain = this.terminalManager.shutdown();
|
|
1601
1629
|
this.terminalChannel.stop();
|
|
1602
1630
|
this.runtimeCredentials.close();
|
|
1603
|
-
this.directTransferService
|
|
1631
|
+
this.directTransferService?.dispose();
|
|
1632
|
+
this.directTransferService = undefined;
|
|
1604
1633
|
this.controlChannel.stop();
|
|
1605
1634
|
const database = this.database;
|
|
1606
1635
|
this.database = undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { directDataControlFrameSchema, directNodeCommandSchema } from '@myagentroam/protocol';
|
|
2
|
+
import { IceUdpMuxListener as NativeIceUdpMuxListener } from 'node-datachannel';
|
|
2
3
|
import { RTCPeerConnection } from 'node-datachannel/polyfill';
|
|
3
4
|
const MAX_HOST_CANDIDATES = 32;
|
|
4
5
|
const MAX_ACTIVE_SESSIONS = 32;
|
|
@@ -11,21 +12,27 @@ export class DirectTransferService {
|
|
|
11
12
|
sessions = new Map();
|
|
12
13
|
now;
|
|
13
14
|
createPeerConnection;
|
|
15
|
+
iceUdpMuxListener;
|
|
16
|
+
peerConfiguration;
|
|
14
17
|
leaseGraceMs;
|
|
15
18
|
leaseCheckMs;
|
|
19
|
+
disposed = false;
|
|
16
20
|
constructor(options) {
|
|
17
21
|
this.options = options;
|
|
18
22
|
this.now = options.now ?? Date.now;
|
|
23
|
+
const range = options.udpPortRange?.() ?? { begin: 49_152, end: 49_215 };
|
|
24
|
+
const createIceUdpMuxListener = options.createIceUdpMuxListener ?? ((port) => new NativeIceUdpMuxListener(port));
|
|
25
|
+
this.iceUdpMuxListener = bindIceUdpMuxListener(range, createIceUdpMuxListener);
|
|
26
|
+
const udpPort = this.iceUdpMuxListener.port();
|
|
27
|
+
this.peerConfiguration = {
|
|
28
|
+
iceServers: [],
|
|
29
|
+
enableIceUdpMux: true,
|
|
30
|
+
portRangeBegin: udpPort,
|
|
31
|
+
portRangeEnd: udpPort
|
|
32
|
+
};
|
|
19
33
|
this.createPeerConnection =
|
|
20
34
|
options.createPeerConnection ??
|
|
21
|
-
(() =>
|
|
22
|
-
const range = options.udpPortRange?.() ?? { begin: 49_152, end: 49_215 };
|
|
23
|
-
return new RTCPeerConnection({
|
|
24
|
-
iceServers: [],
|
|
25
|
-
portRangeBegin: range.begin,
|
|
26
|
-
portRangeEnd: range.end
|
|
27
|
-
});
|
|
28
|
-
});
|
|
35
|
+
((configuration) => new RTCPeerConnection(configuration));
|
|
29
36
|
this.leaseGraceMs = options.leaseGraceMs ?? LEASE_GRACE_MS;
|
|
30
37
|
this.leaseCheckMs = options.leaseCheckMs ?? LEASE_CHECK_MS;
|
|
31
38
|
}
|
|
@@ -49,6 +56,13 @@ export class DirectTransferService {
|
|
|
49
56
|
for (const session of [...this.sessions.values()])
|
|
50
57
|
void this.remove(session, 'DIRECT_NODE_STOPPED');
|
|
51
58
|
}
|
|
59
|
+
dispose() {
|
|
60
|
+
if (this.disposed)
|
|
61
|
+
return;
|
|
62
|
+
this.disposed = true;
|
|
63
|
+
this.close();
|
|
64
|
+
this.iceUdpMuxListener.stop();
|
|
65
|
+
}
|
|
52
66
|
async dispatch(command) {
|
|
53
67
|
if (command.type === 'direct.prepare') {
|
|
54
68
|
this.prepare(command);
|
|
@@ -86,7 +100,7 @@ export class DirectTransferService {
|
|
|
86
100
|
throw new Error('DIRECT_SESSION_CONFLICT');
|
|
87
101
|
if (this.sessions.size >= MAX_ACTIVE_SESSIONS)
|
|
88
102
|
throw new Error('DIRECT_SESSION_LIMIT_REACHED');
|
|
89
|
-
const peer = this.createPeerConnection();
|
|
103
|
+
const peer = this.createPeerConnection(this.peerConfiguration);
|
|
90
104
|
const session = {
|
|
91
105
|
command,
|
|
92
106
|
peer,
|
|
@@ -360,6 +374,18 @@ export class DirectTransferService {
|
|
|
360
374
|
});
|
|
361
375
|
}
|
|
362
376
|
}
|
|
377
|
+
function bindIceUdpMuxListener(range, create) {
|
|
378
|
+
let lastError;
|
|
379
|
+
for (let port = range.begin; port <= range.end; port += 1) {
|
|
380
|
+
try {
|
|
381
|
+
return create(port);
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
lastError = error;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
throw new Error('DIRECT_UDP_PORT_UNAVAILABLE', { cause: lastError });
|
|
388
|
+
}
|
|
363
389
|
export function fingerprint(sdp) {
|
|
364
390
|
const match = sdp?.match(/^a=fingerprint:(sha-256 [A-F0-9:]+)\r?$/imu);
|
|
365
391
|
return match?.[1];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.69",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"node-pty": "1.1.0",
|
|
29
29
|
"ws": "^8.21.3",
|
|
30
30
|
"zod": "4.4.3",
|
|
31
|
-
"@myagentroam/agent": "0.9.
|
|
32
|
-
"@myagentroam/protocol": "0.9.
|
|
31
|
+
"@myagentroam/agent": "0.9.69",
|
|
32
|
+
"@myagentroam/protocol": "0.9.69"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/ws": "^8.18.1"
|