@myagentroam/marctl 0.9.121 → 0.9.122
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/package.json +1 -1
- package/src/direct.mjs +21 -2
- package/src/terminal.mjs +40 -13
package/package.json
CHANGED
package/src/direct.mjs
CHANGED
|
@@ -27,14 +27,17 @@ export async function openDirectTerminal({
|
|
|
27
27
|
WebSocketClass,
|
|
28
28
|
nodeId,
|
|
29
29
|
terminalId,
|
|
30
|
+
manifest,
|
|
30
31
|
afterSequence = 0,
|
|
31
32
|
timeoutMs = DIRECT_SETUP_TIMEOUT_MS,
|
|
32
33
|
heartbeatMs = DIRECT_HEARTBEAT_MS,
|
|
33
34
|
peerConnectionFactory,
|
|
35
|
+
onPrepared,
|
|
36
|
+
onIceConnected,
|
|
34
37
|
onTransportReady,
|
|
35
38
|
signal
|
|
36
39
|
}) {
|
|
37
|
-
if (typeof client.getTerminalManifest !== 'function')
|
|
40
|
+
if (manifest === undefined && typeof client.getTerminalManifest !== 'function')
|
|
38
41
|
throw new DirectFallbackError('DIRECT_MANIFEST_UNAVAILABLE');
|
|
39
42
|
|
|
40
43
|
let live;
|
|
@@ -49,7 +52,7 @@ export async function openDirectTerminal({
|
|
|
49
52
|
let transportNegotiationActive = true;
|
|
50
53
|
let cleanupNative;
|
|
51
54
|
try {
|
|
52
|
-
|
|
55
|
+
manifest ??= await client.getTerminalManifest(nodeId);
|
|
53
56
|
if (
|
|
54
57
|
typeof manifest?.nodeGeneration !== 'string' ||
|
|
55
58
|
manifest.nodeGeneration.length < 8 ||
|
|
@@ -84,7 +87,12 @@ export async function openDirectTerminal({
|
|
|
84
87
|
peerConnectionFactory ?? ((name, options) => new native.PeerConnection(name, options));
|
|
85
88
|
peer = createPeer(`marctl-${transportSessionId}`, { iceServers: [] });
|
|
86
89
|
|
|
90
|
+
// Start negotiation only after both paths finish their control-plane preparation.
|
|
91
|
+
if (onPrepared) await onPrepared();
|
|
92
|
+
if (signal?.aborted) throw new DirectFallbackError('DIRECT_RACE_CANCELLED');
|
|
93
|
+
|
|
87
94
|
const transportReady = new Promise((resolve, reject) => {
|
|
95
|
+
let iceConnected = false;
|
|
88
96
|
let connected = false;
|
|
89
97
|
let serverOpened = false;
|
|
90
98
|
let settled = false;
|
|
@@ -162,6 +170,17 @@ export async function openDirectTerminal({
|
|
|
162
170
|
)
|
|
163
171
|
finish(new DirectFallbackError('DIRECT_CONTROL_UNAVAILABLE'));
|
|
164
172
|
});
|
|
173
|
+
peer.onIceStateChange((state) => {
|
|
174
|
+
if (!transportNegotiationActive || settled || iceConnected) return;
|
|
175
|
+
const normalizedState = String(state).toLowerCase();
|
|
176
|
+
if (normalizedState !== 'connected' && normalizedState !== 'completed') return;
|
|
177
|
+
iceConnected = true;
|
|
178
|
+
try {
|
|
179
|
+
onIceConnected?.();
|
|
180
|
+
} catch (error) {
|
|
181
|
+
finish(error);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
165
184
|
peer.onStateChange((state) => {
|
|
166
185
|
if (!transportNegotiationActive || settled) return;
|
|
167
186
|
const normalizedState = String(state).toLowerCase();
|
package/src/terminal.mjs
CHANGED
|
@@ -28,13 +28,16 @@ export async function runTerminalSession({
|
|
|
28
28
|
let transport;
|
|
29
29
|
let result;
|
|
30
30
|
let primaryError;
|
|
31
|
+
let manifest;
|
|
31
32
|
|
|
32
33
|
try {
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
if (binary) {
|
|
35
|
+
if (command === undefined)
|
|
36
|
+
throw new MarctlError(translate('errors.terminalBinaryUnavailable'));
|
|
37
|
+
manifest = await client.getTerminalManifest(nodeId);
|
|
38
|
+
if (manifest?.binaryTerminal !== true)
|
|
39
|
+
throw new MarctlError(translate('errors.terminalBinaryUnavailable'));
|
|
40
|
+
}
|
|
38
41
|
const created = await client.createTerminal({
|
|
39
42
|
nodeId,
|
|
40
43
|
...dimensions,
|
|
@@ -47,17 +50,18 @@ export async function runTerminalSession({
|
|
|
47
50
|
try {
|
|
48
51
|
const openDirectTerminal =
|
|
49
52
|
openDirectTerminalImpl ?? (await import('./direct.mjs')).openDirectTerminal;
|
|
53
|
+
if (raceConnections && manifest === undefined)
|
|
54
|
+
manifest = await client.getTerminalManifest(nodeId).catch(() => undefined);
|
|
50
55
|
const directOptions = {
|
|
51
56
|
client,
|
|
52
57
|
WebSocketClass,
|
|
53
58
|
nodeId,
|
|
54
59
|
terminalId,
|
|
60
|
+
manifest,
|
|
55
61
|
afterSequence: 0,
|
|
56
62
|
timeoutMs: connectionTimeoutMs
|
|
57
63
|
};
|
|
58
|
-
const canRace =
|
|
59
|
-
raceConnections &&
|
|
60
|
-
(await client.getTerminalManifest(nodeId).catch(() => undefined))?.deferredRelay === true;
|
|
64
|
+
const canRace = raceConnections && manifest?.deferredRelay === true;
|
|
61
65
|
transport = canRace
|
|
62
66
|
? await raceTerminalConnections({
|
|
63
67
|
openDirectTerminal,
|
|
@@ -137,6 +141,16 @@ async function raceTerminalConnections({
|
|
|
137
141
|
}) {
|
|
138
142
|
const abort = new AbortController();
|
|
139
143
|
const relayAbort = new AbortController();
|
|
144
|
+
const preparing = new Set(['direct', 'relay']);
|
|
145
|
+
let start;
|
|
146
|
+
const preparation = new Promise((resolve) => {
|
|
147
|
+
start = resolve;
|
|
148
|
+
});
|
|
149
|
+
const prepared = (candidate) => {
|
|
150
|
+
preparing.delete(candidate);
|
|
151
|
+
if (preparing.size === 0) start();
|
|
152
|
+
return preparation;
|
|
153
|
+
};
|
|
140
154
|
let selected;
|
|
141
155
|
let announce;
|
|
142
156
|
const first = new Promise((resolve) => {
|
|
@@ -153,12 +167,19 @@ async function raceTerminalConnections({
|
|
|
153
167
|
const direct = openDirectTerminal({
|
|
154
168
|
...directOptions,
|
|
155
169
|
signal: abort.signal,
|
|
156
|
-
|
|
170
|
+
onPrepared: () => prepared('direct'),
|
|
171
|
+
onIceConnected: () => {
|
|
157
172
|
if (!select('direct')) throw new Error('DIRECT_RACE_CANCELLED');
|
|
173
|
+
},
|
|
174
|
+
onTransportReady: () => {
|
|
175
|
+
if (selected !== 'direct') throw new Error('DIRECT_RACE_CANCELLED');
|
|
158
176
|
}
|
|
159
177
|
}).then(
|
|
160
178
|
(value) => ({ value }),
|
|
161
|
-
(error) =>
|
|
179
|
+
(error) => {
|
|
180
|
+
prepared('direct');
|
|
181
|
+
return { error };
|
|
182
|
+
}
|
|
162
183
|
);
|
|
163
184
|
const relay = prepareRelayTransport({
|
|
164
185
|
client,
|
|
@@ -167,13 +188,17 @@ async function raceTerminalConnections({
|
|
|
167
188
|
terminalId,
|
|
168
189
|
translate,
|
|
169
190
|
connectionTimeoutMs,
|
|
170
|
-
signal: relayAbort.signal
|
|
191
|
+
signal: relayAbort.signal,
|
|
192
|
+
onPrepared: () => prepared('relay')
|
|
171
193
|
}).then(
|
|
172
194
|
(value) => {
|
|
173
195
|
if (!select('relay')) value.close();
|
|
174
196
|
return { value };
|
|
175
197
|
},
|
|
176
|
-
(error) =>
|
|
198
|
+
(error) => {
|
|
199
|
+
prepared('relay');
|
|
200
|
+
return { error };
|
|
201
|
+
}
|
|
177
202
|
);
|
|
178
203
|
const winner = await Promise.race([
|
|
179
204
|
first,
|
|
@@ -253,9 +278,11 @@ async function prepareRelayTransport({
|
|
|
253
278
|
terminalId,
|
|
254
279
|
translate,
|
|
255
280
|
connectionTimeoutMs,
|
|
256
|
-
signal
|
|
281
|
+
signal,
|
|
282
|
+
onPrepared
|
|
257
283
|
}) {
|
|
258
284
|
const ticket = await client.createAttachTicket({ nodeId, terminalId });
|
|
285
|
+
if (onPrepared) await onPrepared();
|
|
259
286
|
if (signal?.aborted) throw new MarctlError(translate('errors.terminalAttachConnection'));
|
|
260
287
|
const socket = client.openTerminalWebSocket(WebSocketClass, {
|
|
261
288
|
terminalId,
|