@myagentroam/node 0.11.74 → 0.12.1

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.
Files changed (136) hide show
  1. package/dist/code-navigation/index.js +2 -0
  2. package/dist/code-navigation/languages/c-family.js +9 -0
  3. package/dist/code-navigation/languages/c-preprocessor.js +30 -0
  4. package/dist/code-navigation/languages/c.js +8 -0
  5. package/dist/code-navigation/languages/cpp.js +10 -0
  6. package/dist/code-navigation/languages/csharp.js +18 -0
  7. package/dist/code-navigation/languages/css.js +7 -0
  8. package/dist/code-navigation/languages/ecmascript.js +44 -0
  9. package/dist/code-navigation/languages/file-target.js +9 -0
  10. package/dist/code-navigation/languages/go.js +32 -0
  11. package/dist/code-navigation/languages/html.js +7 -0
  12. package/dist/code-navigation/languages/jvm.js +17 -0
  13. package/dist/code-navigation/languages/less.js +7 -0
  14. package/dist/code-navigation/languages/markdown.js +113 -0
  15. package/dist/code-navigation/languages/markup-reference.js +27 -0
  16. package/dist/code-navigation/languages/objective-c.js +11 -0
  17. package/dist/code-navigation/languages/objective-cpp.js +10 -0
  18. package/dist/code-navigation/languages/python.js +17 -0
  19. package/dist/code-navigation/languages/rust.js +18 -0
  20. package/dist/code-navigation/languages/sass.js +7 -0
  21. package/dist/code-navigation/languages/scss.js +7 -0
  22. package/dist/code-navigation/languages/stylesheet-reference.js +37 -0
  23. package/dist/code-navigation/languages/vue.js +75 -0
  24. package/dist/code-navigation/registry.js +72 -0
  25. package/dist/code-navigation/source-ranges.js +17 -0
  26. package/dist/code-navigation/symbol-maps.js +257 -0
  27. package/dist/code-navigation/symbol-matcher.js +48 -0
  28. package/dist/code-navigation/symbol-navigation.js +1 -0
  29. package/dist/code-navigation/types.js +1 -0
  30. package/dist/ctags-binary.js +3 -3
  31. package/dist/marctl/bin/marctl.mjs +6 -0
  32. package/dist/marctl/bin/marnodes.mjs +6 -0
  33. package/dist/marctl/bin/marscp.mjs +6 -0
  34. package/dist/marctl/bin/marssh.mjs +6 -0
  35. package/dist/marctl/package.json +1 -0
  36. package/dist/marctl/src/cli.mjs +271 -0
  37. package/dist/marctl/src/client.mjs +291 -0
  38. package/dist/marctl/src/config.mjs +46 -0
  39. package/dist/marctl/src/direct-control.mjs +225 -0
  40. package/dist/marctl/src/direct-errors.mjs +48 -0
  41. package/dist/marctl/src/direct-process.mjs +374 -0
  42. package/dist/marctl/src/direct-transport.mjs +235 -0
  43. package/dist/marctl/src/direct.mjs +421 -0
  44. package/dist/marctl/src/errors.mjs +11 -0
  45. package/dist/marctl/src/i18n/en-US.mjs +285 -0
  46. package/dist/marctl/src/i18n/index.mjs +20 -0
  47. package/dist/marctl/src/i18n/zh-CN.mjs +269 -0
  48. package/dist/marctl/src/scp-channel.mjs +99 -0
  49. package/dist/marctl/src/scp-copy.mjs +46 -0
  50. package/dist/marctl/src/scp-endpoint.mjs +29 -0
  51. package/dist/marctl/src/scp-protocol.mjs +2 -0
  52. package/dist/marctl/src/scp-worker.cjs +266 -0
  53. package/dist/marctl/src/scp-workers.mjs +179 -0
  54. package/dist/marctl/src/scp.mjs +64 -0
  55. package/dist/marctl/src/session-args.mjs +233 -0
  56. package/dist/marctl/src/session-input.mjs +43 -0
  57. package/dist/marctl/src/session-prepare.mjs +87 -0
  58. package/dist/marctl/src/session-target.mjs +26 -0
  59. package/dist/marctl/src/session-wait.mjs +232 -0
  60. package/dist/marctl/src/sessions.mjs +93 -0
  61. package/dist/marctl/src/terminal-frame.mjs +48 -0
  62. package/dist/marctl/src/terminal-interaction.mjs +177 -0
  63. package/dist/marctl/src/terminal-race.mjs +117 -0
  64. package/dist/marctl/src/terminal-relay-sender.mjs +129 -0
  65. package/dist/marctl/src/terminal-relay.mjs +305 -0
  66. package/dist/marctl/src/terminal.mjs +152 -0
  67. package/dist/marctl.js +1 -1
  68. package/dist/marnodes.js +1 -1
  69. package/dist/marscp.js +1 -1
  70. package/dist/marssh.js +1 -1
  71. package/dist/ripgrep-binary.js +3 -3
  72. package/dist/runner/mar-agent/agent-operation-presentation.js +22 -10
  73. package/dist/runner/mar-agent/hosted-conversation-runtime.js +3 -2
  74. package/dist/runner/mar-agent/runtime-extensions.js +33 -0
  75. package/dist/runner/mar-agent/session-runtime.js +5 -4
  76. package/dist/service/code-navigation/file-reference-parser.js +1 -1
  77. package/dist/service/node-terminal-channel.js +1 -1
  78. package/dist/session/native-session-projection-service.js +1 -2
  79. package/dist/session/session-catalog-service.js +74 -25
  80. package/dist/workspace/workspace-code-navigation-service.js +2 -2
  81. package/package.json +11 -9
  82. package/vendor/ctags/NOTICE +0 -22
  83. package/vendor/ctags/darwin-arm64/ctags +0 -0
  84. package/vendor/ctags/darwin-x64/ctags +0 -0
  85. package/vendor/ctags/licenses/COPYING +0 -340
  86. package/vendor/ctags/licenses/COPYING.MinGW-w64-runtime.txt +0 -240
  87. package/vendor/ctags/licenses/LICENSE.janssen +0 -19
  88. package/vendor/ctags/licenses/LICENSE.libyaml +0 -19
  89. package/vendor/ctags/licenses/official-snapshot/darwin-arm64/COPYING +0 -340
  90. package/vendor/ctags/licenses/official-snapshot/darwin-arm64/RECEIPT.yml +0 -27
  91. package/vendor/ctags/licenses/official-snapshot/darwin-arm64/dependencies/libiconv.yml +0 -24
  92. package/vendor/ctags/licenses/official-snapshot/darwin-arm64/dependencies/libjansson.yml +0 -19
  93. package/vendor/ctags/licenses/official-snapshot/darwin-arm64/dependencies/libxml2.yml +0 -20
  94. package/vendor/ctags/licenses/official-snapshot/darwin-arm64/dependencies/libyaml.yml +0 -7
  95. package/vendor/ctags/licenses/official-snapshot/darwin-arm64/dependencies/libz.yml +0 -13
  96. package/vendor/ctags/licenses/official-snapshot/darwin-x64/COPYING +0 -340
  97. package/vendor/ctags/licenses/official-snapshot/darwin-x64/RECEIPT.yml +0 -27
  98. package/vendor/ctags/licenses/official-snapshot/darwin-x64/dependencies/libiconv.yml +0 -24
  99. package/vendor/ctags/licenses/official-snapshot/darwin-x64/dependencies/libjansson.yml +0 -19
  100. package/vendor/ctags/licenses/official-snapshot/darwin-x64/dependencies/libxml2.yml +0 -20
  101. package/vendor/ctags/licenses/official-snapshot/darwin-x64/dependencies/libyaml.yml +0 -7
  102. package/vendor/ctags/licenses/official-snapshot/darwin-x64/dependencies/libz.yml +0 -13
  103. package/vendor/ctags/licenses/official-snapshot/linux-arm64/COPYING +0 -340
  104. package/vendor/ctags/licenses/official-snapshot/linux-arm64/RECEIPT.yml +0 -27
  105. package/vendor/ctags/licenses/official-snapshot/linux-arm64/dependencies/libiconv.yml +0 -24
  106. package/vendor/ctags/licenses/official-snapshot/linux-arm64/dependencies/libjansson.yml +0 -19
  107. package/vendor/ctags/licenses/official-snapshot/linux-arm64/dependencies/libxml2.yml +0 -20
  108. package/vendor/ctags/licenses/official-snapshot/linux-arm64/dependencies/libyaml.yml +0 -7
  109. package/vendor/ctags/licenses/official-snapshot/linux-arm64/dependencies/libz.yml +0 -13
  110. package/vendor/ctags/licenses/official-snapshot/linux-x64/COPYING +0 -340
  111. package/vendor/ctags/licenses/official-snapshot/linux-x64/RECEIPT.yml +0 -27
  112. package/vendor/ctags/licenses/official-snapshot/linux-x64/dependencies/libiconv.yml +0 -24
  113. package/vendor/ctags/licenses/official-snapshot/linux-x64/dependencies/libjansson.yml +0 -19
  114. package/vendor/ctags/licenses/official-snapshot/linux-x64/dependencies/libxml2.yml +0 -20
  115. package/vendor/ctags/licenses/official-snapshot/linux-x64/dependencies/libyaml.yml +0 -7
  116. package/vendor/ctags/licenses/official-snapshot/linux-x64/dependencies/libz.yml +0 -13
  117. package/vendor/ctags/licenses/win32-x64/COPYING +0 -340
  118. package/vendor/ctags/licenses/win32-x64/COPYING.MinGW-w64-runtime.txt +0 -240
  119. package/vendor/ctags/licenses/win32-x64/Copyright.libxml2 +0 -23
  120. package/vendor/ctags/licenses/win32-x64/LICENCE.pcre2 +0 -94
  121. package/vendor/ctags/licenses/win32-x64/LICENSE.janssen +0 -19
  122. package/vendor/ctags/licenses/win32-x64/LICENSE.libyaml +0 -19
  123. package/vendor/ctags/linux-arm64/ctags +0 -0
  124. package/vendor/ctags/linux-x64/ctags +0 -0
  125. package/vendor/ctags/manifest.json +0 -42
  126. package/vendor/ctags/source/universal-ctags-ab95af198f8eaeb6d50dc96e3adf52e769d3e5f0.tar.gz +0 -0
  127. package/vendor/ctags/win32-x64/ctags.exe +0 -0
  128. package/vendor/ripgrep/LICENSE-MIT +0 -21
  129. package/vendor/ripgrep/UNLICENSE +0 -24
  130. package/vendor/ripgrep/darwin-arm64/rg +0 -0
  131. package/vendor/ripgrep/darwin-x64/rg +0 -0
  132. package/vendor/ripgrep/linux-arm64/rg +0 -0
  133. package/vendor/ripgrep/linux-x64/rg +0 -0
  134. package/vendor/ripgrep/manifest.json +0 -42
  135. package/vendor/ripgrep/win32-arm64/rg.exe +0 -0
  136. package/vendor/ripgrep/win32-x64/rg.exe +0 -0
@@ -0,0 +1,421 @@
1
+ import {
2
+ DirectFallbackError,
3
+ DirectUnsafeError,
4
+ DirectRejectedError,
5
+ directControlError
6
+ } from './direct-errors.mjs';
7
+ import { randomUUID } from 'node:crypto';
8
+ import { LiveControl, cancelSession, withTimeout, isRecord } from './direct-control.mjs';
9
+ import { DirectTerminalTransport } from './direct-transport.mjs';
10
+
11
+ export const DIRECT_PREPARE_TIMEOUT_MS = 5000;
12
+ export const DIRECT_SETUP_TIMEOUT_MS = 15000;
13
+ export const DIRECT_ICE_TIMEOUT_MS = 3000;
14
+ export const DIRECT_HEARTBEAT_MS = 10000;
15
+
16
+ export async function openDirectTerminal(options) {
17
+ const {
18
+ client,
19
+ WebSocketClass,
20
+ nodeId,
21
+ terminalId,
22
+ afterSequence = 0,
23
+ timeoutMs = DIRECT_SETUP_TIMEOUT_MS,
24
+ heartbeatMs = DIRECT_HEARTBEAT_MS,
25
+ peerConnectionFactory,
26
+ onPrepared,
27
+ onIceConnected,
28
+ onTransportReady,
29
+ beforeOpen,
30
+ onRejected,
31
+ cleanupNative: cleanupNativeImpl,
32
+ signal
33
+ } = options;
34
+ let { manifest } = options;
35
+ if (peerConnectionFactory === undefined && typeof client.openDirectTerminal === 'function')
36
+ return client.openDirectTerminal(options);
37
+ if (manifest === undefined && typeof client.getTerminalManifest !== 'function')
38
+ throw new DirectFallbackError('DIRECT_MANIFEST_UNAVAILABLE');
39
+
40
+ let live;
41
+ let peer;
42
+ let bootstrapChannel;
43
+ let terminalChannel;
44
+ let transportSessionId;
45
+ let terminalSessionId;
46
+ let transport;
47
+ let unsafeFallback = false;
48
+ let transportControlCleanup;
49
+ let transportNegotiationActive = true;
50
+ let preparationError;
51
+ let cleanupNative = cleanupNativeImpl;
52
+ try {
53
+ manifest ??= await client.getTerminalManifest(nodeId);
54
+ if (
55
+ typeof manifest?.nodeGeneration !== 'string' ||
56
+ manifest.nodeGeneration.length < 8 ||
57
+ manifest.terminalDirect?.apiVersion !== 1 ||
58
+ manifest.terminalDirect.transport !== true
59
+ )
60
+ throw new DirectFallbackError('DIRECT_UNSUPPORTED');
61
+
62
+ live = new LiveControl(client.openWorkbenchWebSocket(WebSocketClass), timeoutMs);
63
+ await withTimeout(live.openAndResume(), timeoutMs, 'DIRECT_CONTROL_TIMEOUT', signal);
64
+
65
+ let dispatchControl;
66
+ let rejectPreparation;
67
+ const earlyTransportErrors = new Map();
68
+ const preparationFailure = new Promise((_, reject) => {
69
+ rejectPreparation = reject;
70
+ });
71
+ void preparationFailure.catch(() => undefined);
72
+ const failPreparation = (error) => {
73
+ preparationError ??= error;
74
+ rejectPreparation(preparationError);
75
+ };
76
+ // Node errors carry a session ID, not necessarily the browser's request ID.
77
+ // Listen before prepare and native initialization, then correlate with prepared.
78
+ transportControlCleanup = live.onControl((message) => {
79
+ if (dispatchControl) dispatchControl(message);
80
+ else if (
81
+ (message.type === 'direct.error' || message.type === 'direct.closed') &&
82
+ typeof message.directSessionId === 'string'
83
+ ) {
84
+ const error = directControlError(message.code);
85
+ if (transportSessionId === undefined) {
86
+ if (earlyTransportErrors.size >= 16 && !earlyTransportErrors.has(message.directSessionId))
87
+ failPreparation(new DirectRejectedError('DIRECT_CONTROL_MESSAGE_LIMIT'));
88
+ else if (!earlyTransportErrors.has(message.directSessionId))
89
+ earlyTransportErrors.set(message.directSessionId, error);
90
+ } else if (message.directSessionId === transportSessionId) failPreparation(error);
91
+ }
92
+ });
93
+ const transportPrepared = await withTimeout(
94
+ Promise.race([
95
+ live.request(
96
+ {
97
+ type: 'direct.prepare',
98
+ requestId: requestId(),
99
+ nodeId,
100
+ nodeGeneration: manifest.nodeGeneration,
101
+ purpose: 'TRANSPORT'
102
+ },
103
+ (message, request) =>
104
+ message.type === 'direct.prepared' &&
105
+ message.requestId === request.requestId &&
106
+ message.purpose === 'TRANSPORT',
107
+ 'DIRECT_TRANSPORT_PREPARE_FAILED'
108
+ ),
109
+ preparationFailure
110
+ ]),
111
+ timeoutMs,
112
+ 'DIRECT_TRANSPORT_PREPARE_FAILED',
113
+ signal
114
+ );
115
+ transportSessionId = transportPrepared.directSessionId;
116
+ const earlyError = earlyTransportErrors.get(transportSessionId);
117
+ earlyTransportErrors.clear();
118
+ if (earlyError) throw earlyError;
119
+
120
+ const native =
121
+ peerConnectionFactory === undefined ? await import('node-datachannel') : undefined;
122
+ cleanupNative ??= native?.cleanup;
123
+ if (preparationError) throw preparationError;
124
+ const createPeer =
125
+ peerConnectionFactory ?? ((name, options) => new native.PeerConnection(name, options));
126
+ peer = createPeer(`marctl-${transportSessionId}`, { iceServers: [] });
127
+ if (preparationError) throw preparationError;
128
+
129
+ // Start negotiation only after both paths finish their control-plane preparation.
130
+ await withTimeout(
131
+ Promise.race([preparationFailure, onPrepared?.()]),
132
+ timeoutMs,
133
+ 'DIRECT_SETUP_TIMEOUT',
134
+ signal
135
+ );
136
+ if (signal?.aborted) throw new DirectFallbackError('DIRECT_RACE_CANCELLED');
137
+
138
+ const transportReady = new Promise((resolve, reject) => {
139
+ let iceConnected = false;
140
+ let connected = false;
141
+ let serverOpened = false;
142
+ let settled = false;
143
+ const finish = (error) => {
144
+ if (settled) return;
145
+ settled = true;
146
+ transportNegotiationActive = false;
147
+ if (error === undefined) resolve();
148
+ else reject(error);
149
+ };
150
+ const maybeReady = () => {
151
+ if (connected && serverOpened) finish();
152
+ };
153
+ dispatchControl = (message) => {
154
+ if (!transportNegotiationActive || settled) return;
155
+ if (message.type === 'direct.signal' && message.directSessionId === transportSessionId) {
156
+ try {
157
+ applyRemoteSignal(peer, message.signal);
158
+ } catch (error) {
159
+ finish(
160
+ error instanceof DirectFallbackError
161
+ ? error
162
+ : new DirectFallbackError('DIRECT_SIGNAL_INVALID')
163
+ );
164
+ }
165
+ return;
166
+ }
167
+ if (message.type === 'direct.opened' && message.directSessionId === transportSessionId) {
168
+ serverOpened = true;
169
+ maybeReady();
170
+ return;
171
+ }
172
+ if (
173
+ (message.type === 'direct.error' || message.type === 'direct.closed') &&
174
+ message.directSessionId === transportSessionId
175
+ )
176
+ finish(directControlError(message.code));
177
+ };
178
+ peer.onLocalDescription((sdp, type) => {
179
+ if (!transportNegotiationActive || settled) return;
180
+ if (String(type).toLowerCase() !== 'offer') return;
181
+ let signal;
182
+ try {
183
+ signal = localOfferSignal(sdp);
184
+ } catch (error) {
185
+ finish(error instanceof DirectFallbackError ? error : new DirectFallbackError());
186
+ return;
187
+ }
188
+ if (!live.send({ type: 'direct.signal', directSessionId: transportSessionId, signal }))
189
+ finish(new DirectFallbackError('DIRECT_CONTROL_UNAVAILABLE'));
190
+ });
191
+ peer.onLocalCandidate((candidate, mid) => {
192
+ if (!transportNegotiationActive || settled || typeof candidate !== 'string') return;
193
+ if (candidate === '') {
194
+ if (
195
+ !live.send({
196
+ type: 'direct.signal',
197
+ directSessionId: transportSessionId,
198
+ signal: { kind: 'END_OF_CANDIDATES' }
199
+ })
200
+ )
201
+ finish(new DirectFallbackError('DIRECT_CONTROL_UNAVAILABLE'));
202
+ return;
203
+ }
204
+ if (!safeHostCandidate(candidate)) return;
205
+ if (
206
+ !live.send({
207
+ type: 'direct.signal',
208
+ directSessionId: transportSessionId,
209
+ signal: {
210
+ kind: 'CANDIDATE',
211
+ candidate: { candidate, sdpMid: mid || null, sdpMLineIndex: null }
212
+ }
213
+ })
214
+ )
215
+ finish(new DirectFallbackError('DIRECT_CONTROL_UNAVAILABLE'));
216
+ });
217
+ peer.onIceStateChange((state) => {
218
+ if (!transportNegotiationActive || settled || iceConnected) return;
219
+ const normalizedState = String(state).toLowerCase();
220
+ if (normalizedState !== 'connected' && normalizedState !== 'completed') return;
221
+ iceConnected = true;
222
+ try {
223
+ onIceConnected?.();
224
+ } catch (error) {
225
+ finish(error);
226
+ }
227
+ });
228
+ peer.onStateChange((state) => {
229
+ if (!transportNegotiationActive || settled) return;
230
+ const normalizedState = String(state).toLowerCase();
231
+ if (normalizedState === 'connected') {
232
+ if (!connected) {
233
+ connected = true;
234
+ if (!live.send({ type: 'direct.connected', directSessionId: transportSessionId }))
235
+ finish(new DirectFallbackError('DIRECT_CONTROL_UNAVAILABLE'));
236
+ }
237
+ maybeReady();
238
+ } else if (normalizedState === 'failed' || normalizedState === 'closed') {
239
+ finish(new DirectFallbackError('DIRECT_CONNECTION_FAILED'));
240
+ }
241
+ });
242
+ try {
243
+ bootstrapChannel = peer.createDataChannel('mar-direct-transport', {
244
+ unordered: false
245
+ });
246
+ if (peer.localDescription?.() == null) peer.setLocalDescription('Offer');
247
+ } catch {
248
+ finish(new DirectFallbackError('DIRECT_OFFER_FAILED'));
249
+ }
250
+ });
251
+ try {
252
+ await withTimeout(
253
+ transportReady,
254
+ Math.min(timeoutMs, DIRECT_ICE_TIMEOUT_MS),
255
+ 'DIRECT_SETUP_TIMEOUT',
256
+ signal
257
+ );
258
+ } finally {
259
+ transportNegotiationActive = false;
260
+ transportControlCleanup?.();
261
+ transportControlCleanup = undefined;
262
+ }
263
+ bootstrapChannel?.close?.();
264
+ if (onTransportReady) await onTransportReady();
265
+
266
+ const terminalPrepared = await withTimeout(
267
+ live.request(
268
+ {
269
+ type: 'terminal.direct.prepare',
270
+ requestId: requestId(),
271
+ nodeId,
272
+ nodeGeneration: manifest.nodeGeneration,
273
+ transportSessionId,
274
+ terminalId,
275
+ afterSequence,
276
+ readonly: false
277
+ },
278
+ (message, request) =>
279
+ message.type === 'terminal.direct.prepared' &&
280
+ message.requestId === request.requestId &&
281
+ typeof message.directSessionId === 'string' &&
282
+ typeof message.grant === 'string',
283
+ 'TERMINAL_DIRECT_PREPARE_FAILED'
284
+ ),
285
+ timeoutMs,
286
+ 'TERMINAL_DIRECT_PREPARE_FAILED',
287
+ signal
288
+ );
289
+ terminalSessionId = terminalPrepared.directSessionId;
290
+ terminalChannel = peer.createDataChannel(`mar-terminal-${terminalSessionId}`, {
291
+ unordered: false
292
+ });
293
+
294
+ transport = new DirectTerminalTransport({
295
+ channel: terminalChannel,
296
+ live,
297
+ peer,
298
+ transportSessionId,
299
+ terminalSessionId,
300
+ terminalId,
301
+ heartbeatMs,
302
+ cleanupNative
303
+ });
304
+ if (beforeOpen) await beforeOpen();
305
+ if (signal?.aborted) throw new DirectFallbackError('DIRECT_RACE_CANCELLED');
306
+ await withTimeout(
307
+ transport.waitForAttachment(terminalPrepared.grant),
308
+ timeoutMs,
309
+ 'TERMINAL_DIRECT_TIMEOUT',
310
+ signal
311
+ );
312
+ return transport;
313
+ } catch (error) {
314
+ error = preparationError ?? error;
315
+ transportControlCleanup?.();
316
+ transportControlCleanup = undefined;
317
+ unsafeFallback = transport?.openSent === true;
318
+ if (error instanceof DirectRejectedError) onRejected?.(error);
319
+ if (transport !== undefined) {
320
+ await transport.cancelAndDispose().catch(() => undefined);
321
+ transport.dispose({ skipCancel: true });
322
+ }
323
+ bootstrapChannel?.close?.();
324
+ terminalChannel?.close?.();
325
+ try {
326
+ if (transport === undefined && terminalSessionId !== undefined)
327
+ await cancelSession(live, 'terminal.direct.cancel', terminalSessionId);
328
+ if (transport === undefined && transportSessionId !== undefined)
329
+ await cancelSession(live, 'direct.cancel', transportSessionId);
330
+ } catch {
331
+ // Direct setup cleanup is best effort before relay fallback.
332
+ }
333
+ live?.close();
334
+ if (transport === undefined) peer?.close?.();
335
+ if (transport === undefined) cleanupNative?.();
336
+ if (error instanceof DirectRejectedError) throw error;
337
+ if (unsafeFallback) throw new DirectUnsafeError('DIRECT_OPEN_CANCEL_UNCONFIRMED');
338
+ if (error instanceof DirectFallbackError || error instanceof DirectUnsafeError) throw error;
339
+ throw new DirectFallbackError(errorCode(error));
340
+ }
341
+ }
342
+
343
+ function requestId() {
344
+ return randomUUID().replaceAll('-', '');
345
+ }
346
+
347
+ function localOfferSignal(sdp) {
348
+ const fingerprint = extractFingerprint(sdp);
349
+ if (fingerprint === undefined) throw new DirectFallbackError('DIRECT_FINGERPRINT_INVALID');
350
+ return { kind: 'OFFER', sdp: stripHostCandidates(sdp), fingerprint };
351
+ }
352
+
353
+ function applyRemoteSignal(peer, signal) {
354
+ if (signal.kind === 'CANDIDATE') {
355
+ if (
356
+ !isRecord(signal.candidate) ||
357
+ typeof signal.candidate.candidate !== 'string' ||
358
+ !safeHostCandidate(signal.candidate.candidate)
359
+ )
360
+ throw new DirectFallbackError('DIRECT_CANDIDATE_REJECTED');
361
+ peer.addRemoteCandidate(
362
+ signal.candidate.candidate,
363
+ signal.candidate.sdpMid ?? String(signal.candidate.sdpMLineIndex ?? 0)
364
+ );
365
+ return;
366
+ }
367
+ if (signal.kind === 'END_OF_CANDIDATES') return;
368
+ if (
369
+ !hostOnlySdp(signal.sdp) ||
370
+ !fingerprintMatches(signal.sdp, signal.fingerprint) ||
371
+ signal.kind !== 'ANSWER'
372
+ )
373
+ throw new DirectFallbackError('DIRECT_FINGERPRINT_INVALID');
374
+ peer.setRemoteDescription(signal.sdp, 'Answer');
375
+ }
376
+
377
+ function extractFingerprint(sdp) {
378
+ if (typeof sdp !== 'string') return undefined;
379
+ const match = sdp.match(/^a=fingerprint:sha-256 ([A-F0-9:]+)\r?$/imu);
380
+ if (match === null) return undefined;
381
+ const value = match[1].toUpperCase();
382
+ return /^([A-F0-9]{2}:){31}[A-F0-9]{2}$/u.test(value) ? `sha-256 ${value}` : undefined;
383
+ }
384
+
385
+ function safeHostCandidate(candidate) {
386
+ return typeof candidate === 'string' && /\styp host(?:\s|$)/u.test(candidate);
387
+ }
388
+
389
+ function hostOnlySdp(sdp) {
390
+ if (typeof sdp !== 'string') return false;
391
+ return sdp
392
+ .split(/\r?\n/u)
393
+ .filter((line) => line.startsWith('a=candidate:'))
394
+ .every((line) => safeHostCandidate(line.slice(2)));
395
+ }
396
+
397
+ function stripHostCandidates(sdp) {
398
+ if (typeof sdp !== 'string') throw new DirectFallbackError('DIRECT_SDP_INVALID');
399
+ return sdp
400
+ .split(/\r?\n/u)
401
+ .filter((line) => !line.startsWith('a=candidate:') || safeHostCandidate(line.slice(2)))
402
+ .join('\r\n');
403
+ }
404
+
405
+ function fingerprintMatches(sdp, fingerprint) {
406
+ return extractFingerprint(sdp) === normalizeFingerprint(fingerprint);
407
+ }
408
+
409
+ function normalizeFingerprint(value) {
410
+ if (typeof value !== 'string') return undefined;
411
+ const match = /^sha-256 ([A-F0-9:]+)$/iu.exec(value);
412
+ if (match === null) return undefined;
413
+ const normalized = match[1].toUpperCase();
414
+ return /^([A-F0-9]{2}:){31}[A-F0-9]{2}$/u.test(normalized) ? `sha-256 ${normalized}` : undefined;
415
+ }
416
+
417
+ function errorCode(error) {
418
+ return error instanceof Error && /^[A-Z][A-Z0-9_]{0,127}$/u.test(error.message)
419
+ ? error.message
420
+ : 'DIRECT_CONNECTION_FAILED';
421
+ }
@@ -0,0 +1,11 @@
1
+ export class MarctlError extends Error {
2
+ constructor(message, options = {}) {
3
+ super(message, options);
4
+ this.name = 'MarctlError';
5
+ this.code = options.code;
6
+ this.requestId = options.requestId;
7
+ this.upstreamRequestId = options.upstreamRequestId;
8
+ this.details = options.details;
9
+ this.statusCode = options.statusCode;
10
+ }
11
+ }