@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,374 @@
1
+ import { fork } from 'node:child_process';
2
+ import { EventEmitter } from 'node:events';
3
+ import { fileURLToPath } from 'node:url';
4
+ import {
5
+ DirectFallbackError,
6
+ DirectUnsafeError,
7
+ DirectRejectedError,
8
+ directControlError
9
+ } from './direct-errors.mjs';
10
+
11
+ const CLEANUP_TIMEOUT_MS = 1000;
12
+ const MAX_BUFFER_BYTES = 2 * 1024 * 1024;
13
+ const ipcBytes = new WeakMap();
14
+
15
+ function sendIpc(target, message, onError = () => {}, onSent = () => {}) {
16
+ const bytes = Buffer.byteLength(JSON.stringify(message));
17
+ const pending = ipcBytes.get(target) ?? 0;
18
+ if (!target.connected || pending + bytes > MAX_BUFFER_BYTES) {
19
+ onError(new Error('TERMINAL_CONNECTION_LOST'));
20
+ return false;
21
+ }
22
+ ipcBytes.set(target, pending + bytes);
23
+ try {
24
+ target.send(message, (error) => {
25
+ ipcBytes.set(target, (ipcBytes.get(target) ?? 0) - bytes);
26
+ if (error) onError(error);
27
+ else onSent();
28
+ });
29
+ return true;
30
+ } catch (error) {
31
+ ipcBytes.set(target, (ipcBytes.get(target) ?? 0) - bytes);
32
+ onError(error);
33
+ return false;
34
+ }
35
+ }
36
+
37
+ export function openDirectTerminalProcess({
38
+ connection,
39
+ nodeId,
40
+ terminalId,
41
+ manifest,
42
+ afterSequence = 0,
43
+ timeoutMs = 15000,
44
+ heartbeatMs = 10000,
45
+ onPrepared,
46
+ onIceConnected,
47
+ onTransportReady,
48
+ signal,
49
+ forkImpl = fork
50
+ }) {
51
+ if (signal?.aborted) return Promise.reject(new DirectFallbackError('DIRECT_RACE_CANCELLED'));
52
+ return new Promise((resolve, reject) => {
53
+ let child;
54
+ try {
55
+ child = forkImpl(new URL('./direct-process.mjs', import.meta.url), [], {
56
+ stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
57
+ execArgv: []
58
+ });
59
+ } catch {
60
+ reject(new DirectFallbackError('DIRECT_UNAVAILABLE'));
61
+ return;
62
+ }
63
+ let settled = false;
64
+ let opening = false;
65
+ let stopping = false;
66
+ let exited = false;
67
+ let cleanupTimer;
68
+ const stop = () => {
69
+ if (stopping) return;
70
+ stopping = true;
71
+ clearTimeout(setupTimer);
72
+ signal?.removeEventListener('abort', onAbort);
73
+ if (exited) return;
74
+ sendIpc(child, { type: 'abort' });
75
+ // Native cleanup may block. Only this connection's process is terminated.
76
+ cleanupTimer = setTimeout(() => child.kill('SIGKILL'), CLEANUP_TIMEOUT_MS);
77
+ };
78
+ const transport = new IsolatedDirectTransport(child, stop);
79
+ const fail = (error) => {
80
+ if (settled) {
81
+ transport.fail(error);
82
+ } else {
83
+ settled = true;
84
+ reject(error);
85
+ stop();
86
+ }
87
+ };
88
+ const setupError = () =>
89
+ opening
90
+ ? new DirectUnsafeError('DIRECT_OPEN_CANCEL_UNCONFIRMED')
91
+ : new DirectFallbackError('DIRECT_UNAVAILABLE');
92
+ const setupTimer = setTimeout(() => fail(setupError()), timeoutMs);
93
+ const onAbort = () =>
94
+ fail(
95
+ opening
96
+ ? new DirectUnsafeError('DIRECT_OPEN_CANCEL_UNCONFIRMED')
97
+ : new DirectFallbackError('DIRECT_RACE_CANCELLED')
98
+ );
99
+ child.on('message', (message) => {
100
+ if (message.type === 'callback') {
101
+ const callback = { prepared: onPrepared, ice: onIceConnected, transport: onTransportReady }[
102
+ message.event
103
+ ];
104
+ if (message.event === 'opening') opening = true;
105
+ Promise.resolve()
106
+ .then(() => {
107
+ if (stopping) throw new DirectFallbackError('DIRECT_RACE_CANCELLED');
108
+ return callback?.();
109
+ })
110
+ .then(
111
+ () =>
112
+ sendIpc(child, { type: 'callback.result', id: message.id }, () => fail(setupError())),
113
+ () => sendIpc(child, { type: 'callback.result', id: message.id, failed: true })
114
+ );
115
+ } else if (message.type === 'attached' && !settled) {
116
+ settled = true;
117
+ clearTimeout(setupTimer);
118
+ resolve(transport);
119
+ } else if (message.type === 'failure') {
120
+ const error = settled
121
+ ? message.name === 'DirectRejectedError' ||
122
+ directControlError(message.code) instanceof DirectRejectedError
123
+ ? directControlError(message.code)
124
+ : new Error(message.code ?? 'TERMINAL_CONNECTION_LOST')
125
+ : message.name === 'DirectRejectedError'
126
+ ? directControlError(message.code)
127
+ : message.name === 'DirectUnsafeError' ||
128
+ (opening && message.name !== 'DirectFallbackError')
129
+ ? new DirectUnsafeError(message.code)
130
+ : new DirectFallbackError(message.code);
131
+ fail(error);
132
+ } else if (message.type === 'frame') {
133
+ transport.frame(message.frame, message.isBinary);
134
+ } else if (message.type === 'send.result') {
135
+ transport.written(message.id, message.code);
136
+ } else if (message.type === 'finished') {
137
+ clearTimeout(cleanupTimer);
138
+ }
139
+ });
140
+ child.on('error', () => fail(setupError()));
141
+ child.on('disconnect', () => {
142
+ if (!stopping) fail(setupError());
143
+ });
144
+ child.on('exit', () => {
145
+ exited = true;
146
+ clearTimeout(cleanupTimer);
147
+ clearTimeout(setupTimer);
148
+ signal?.removeEventListener('abort', onAbort);
149
+ if (!stopping) fail(setupError());
150
+ });
151
+ signal?.addEventListener('abort', onAbort, { once: true });
152
+ sendIpc(
153
+ child,
154
+ {
155
+ type: 'start',
156
+ options: { connection, nodeId, terminalId, manifest, afterSequence, timeoutMs, heartbeatMs }
157
+ },
158
+ () => fail(setupError())
159
+ );
160
+ if (signal?.aborted) onAbort();
161
+ });
162
+ }
163
+
164
+ class IsolatedDirectTransport extends EventEmitter {
165
+ #child;
166
+ #stop;
167
+ #active = false;
168
+ #closed = false;
169
+ #events = [];
170
+ #bytes = 0;
171
+ #writes = new Map();
172
+ #writeBytes = 0;
173
+ #nextId = 0;
174
+
175
+ constructor(child, stop) {
176
+ super();
177
+ this.#child = child;
178
+ this.#stop = stop;
179
+ }
180
+
181
+ activate() {
182
+ if (this.#active) return;
183
+ this.#active = true;
184
+ const events = this.#events;
185
+ this.#events = [];
186
+ this.#bytes = 0;
187
+ for (const [type, value, isBinary] of events) this.#emit(type, value, isBinary);
188
+ }
189
+
190
+ frame(frame, isBinary = false) {
191
+ if (this.#closed) return;
192
+ if (!this.#active) {
193
+ this.#bytes += Buffer.byteLength(frame);
194
+ if (this.#bytes > MAX_BUFFER_BYTES)
195
+ return this.fail(new Error('TERMINAL_DIRECT_BUFFER_EXCEEDED'));
196
+ }
197
+ this.#emit('message', frame, isBinary);
198
+ }
199
+
200
+ send(frame) {
201
+ if (this.#closed) return Promise.reject(new Error('TERMINAL_CONNECTION_LOST'));
202
+ const value = typeof frame === 'string' ? frame : JSON.stringify(frame);
203
+ const bytes = Buffer.byteLength(value);
204
+ if (this.#writeBytes + bytes > MAX_BUFFER_BYTES)
205
+ return Promise.reject(new Error('TERMINAL_DIRECT_BUFFER_EXCEEDED'));
206
+ const id = ++this.#nextId;
207
+ return new Promise((resolve, reject) => {
208
+ this.#writeBytes += bytes;
209
+ this.#writes.set(id, { resolve, reject, bytes });
210
+ sendIpc(this.#child, { type: 'send', id, frame: value }, () =>
211
+ this.fail(new Error('TERMINAL_CONNECTION_LOST'))
212
+ );
213
+ });
214
+ }
215
+
216
+ written(id, code) {
217
+ const write = this.#writes.get(id);
218
+ if (!write) return;
219
+ this.#writes.delete(id);
220
+ this.#writeBytes -= write.bytes;
221
+ if (code) write.reject(new Error(code));
222
+ else write.resolve();
223
+ }
224
+
225
+ fail(error) {
226
+ if (this.#closed) return;
227
+ this.#emit(
228
+ 'error',
229
+ error instanceof DirectRejectedError
230
+ ? error
231
+ : new Error(
232
+ error instanceof DirectFallbackError || error instanceof DirectUnsafeError
233
+ ? 'TERMINAL_CONNECTION_LOST'
234
+ : error.message
235
+ )
236
+ );
237
+ this.close();
238
+ }
239
+
240
+ close() {
241
+ if (this.#closed) return;
242
+ this.#closed = true;
243
+ for (const { reject } of this.#writes.values()) reject(new Error('TERMINAL_CONNECTION_LOST'));
244
+ this.#writes.clear();
245
+ this.#writeBytes = 0;
246
+ this.#stop();
247
+ this.#emit('close');
248
+ }
249
+
250
+ #emit(type, value, isBinary = false) {
251
+ if (!this.#active) this.#events.push([type, value, isBinary]);
252
+ else if (type !== 'error' || this.listenerCount('error') > 0) this.emit(type, value, isBinary);
253
+ }
254
+ }
255
+
256
+ function runChild() {
257
+ const controller = new AbortController();
258
+ const callbacks = new Map();
259
+ let nextId = 0;
260
+ let transport;
261
+ let native;
262
+ let cleaning = false;
263
+ const cleanup = () => {
264
+ if (cleaning) return;
265
+ cleaning = true;
266
+ setImmediate(() => {
267
+ try {
268
+ native?.cleanup();
269
+ } catch {
270
+ /* Process exit also releases native resources. */
271
+ }
272
+ sendIpc(
273
+ process,
274
+ { type: 'finished' },
275
+ () => process.exit(0),
276
+ () => process.exit(0)
277
+ );
278
+ });
279
+ };
280
+ const callback = (event) =>
281
+ new Promise((resolve, reject) => {
282
+ const id = ++nextId;
283
+ callbacks.set(id, { resolve, reject });
284
+ sendIpc(process, { type: 'callback', id, event }, reject);
285
+ });
286
+ const abort = () => {
287
+ controller.abort();
288
+ for (const { reject } of callbacks.values())
289
+ reject(new DirectFallbackError('DIRECT_RACE_CANCELLED'));
290
+ callbacks.clear();
291
+ if (transport) transport.close();
292
+ };
293
+ process.on('disconnect', () => {
294
+ abort();
295
+ cleanup();
296
+ });
297
+ process.on('message', async (message) => {
298
+ if (message.type === 'callback.result') {
299
+ const pending = callbacks.get(message.id);
300
+ callbacks.delete(message.id);
301
+ if (message.failed) pending?.reject(new DirectFallbackError('DIRECT_RACE_CANCELLED'));
302
+ else pending?.resolve();
303
+ } else if (message.type === 'abort') {
304
+ abort();
305
+ } else if (message.type === 'send') {
306
+ try {
307
+ await transport.send(message.frame);
308
+ sendIpc(process, { type: 'send.result', id: message.id });
309
+ } catch {
310
+ sendIpc(process, { type: 'send.result', id: message.id, code: 'TERMINAL_INPUT_FAILED' });
311
+ }
312
+ } else if (message.type === 'start') {
313
+ let rejectionSent = false;
314
+ try {
315
+ const { MarctlClient } = await import('./client.mjs');
316
+ const { openDirectTerminal } = await import('./direct.mjs');
317
+ const { default: WebSocketClass } = await import('ws');
318
+ native = await import('node-datachannel');
319
+ const { connection, ...options } = message.options;
320
+ let iceSelection;
321
+ transport = await openDirectTerminal({
322
+ ...options,
323
+ client: new MarctlClient(connection),
324
+ WebSocketClass,
325
+ peerConnectionFactory: (name, config) => new native.PeerConnection(name, config),
326
+ cleanupNative: cleanup,
327
+ signal: controller.signal,
328
+ onRejected: (error) => {
329
+ rejectionSent = true;
330
+ sendIpc(process, { type: 'failure', name: 'DirectRejectedError', code: error.code });
331
+ },
332
+ onPrepared: () => callback('prepared'),
333
+ onIceConnected: () => {
334
+ iceSelection = callback('ice');
335
+ iceSelection.catch(() => controller.abort());
336
+ },
337
+ onTransportReady: async () => {
338
+ await iceSelection;
339
+ await callback('transport');
340
+ },
341
+ beforeOpen: () => callback('opening')
342
+ });
343
+ sendIpc(process, { type: 'attached' });
344
+ transport.on('message', (frame, isBinary) =>
345
+ sendIpc(process, { type: 'frame', frame: String(frame), isBinary }, abort)
346
+ );
347
+ transport.on('error', (error) => {
348
+ sendIpc(process, { type: 'failure', code: error.code ?? error.message });
349
+ abort();
350
+ });
351
+ transport.on('close', () => {
352
+ sendIpc(process, { type: 'failure', code: 'TERMINAL_DIRECT_CLOSED' });
353
+ cleanup();
354
+ });
355
+ transport.activate();
356
+ } catch (error) {
357
+ if (!rejectionSent)
358
+ sendIpc(process, {
359
+ type: 'failure',
360
+ name:
361
+ error instanceof DirectRejectedError
362
+ ? 'DirectRejectedError'
363
+ : error instanceof DirectUnsafeError
364
+ ? 'DirectUnsafeError'
365
+ : 'DirectFallbackError',
366
+ code: error.code ?? 'DIRECT_UNAVAILABLE'
367
+ });
368
+ cleanup();
369
+ }
370
+ }
371
+ });
372
+ }
373
+
374
+ if (process.send && process.argv[1] === fileURLToPath(import.meta.url)) runChild();
@@ -0,0 +1,235 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { DirectFallbackError, DirectUnsafeError, directControlError } from './direct-errors.mjs';
3
+ import { cancelSession, withTimeout, rawToString, isRecord } from './direct-control.mjs';
4
+
5
+ export class DirectTerminalTransport extends EventEmitter {
6
+ #channel;
7
+ #live;
8
+ #peer;
9
+ #transportSessionId;
10
+ #terminalSessionId;
11
+ #terminalId;
12
+ #cleanupNative;
13
+ #heartbeatTimer;
14
+ #unsubscribeLive;
15
+ #closed = false;
16
+ #attached = false;
17
+ #active = false;
18
+ #pendingEvents = [];
19
+ #attachmentResolve;
20
+ #attachmentReject;
21
+ #openSent = false;
22
+ #cancelSent = false;
23
+
24
+ constructor({
25
+ channel,
26
+ live,
27
+ peer,
28
+ transportSessionId,
29
+ terminalSessionId,
30
+ terminalId,
31
+ heartbeatMs,
32
+ cleanupNative
33
+ }) {
34
+ super();
35
+ this.#channel = channel;
36
+ this.#live = live;
37
+ this.#peer = peer;
38
+ this.#transportSessionId = transportSessionId;
39
+ this.#terminalSessionId = terminalSessionId;
40
+ this.#terminalId = terminalId;
41
+ this.#cleanupNative = cleanupNative;
42
+ this.#channel.onMessage?.((raw) => this.#handleMessage(raw));
43
+ this.#channel.onClosed?.(() => this.#fail('TERMINAL_DIRECT_CLOSED'));
44
+ this.#channel.onError?.(() => this.#fail('TERMINAL_CONNECTION_FAILED'));
45
+ this.#unsubscribeLive = this.#live.onControl((message) => this.#handleControl(message));
46
+ this.#heartbeatTimer = setInterval(() => {
47
+ if (
48
+ !this.#live.send({
49
+ type: 'direct.heartbeat',
50
+ directSessionId: this.#transportSessionId
51
+ }) ||
52
+ !this.#live.send({
53
+ type: 'terminal.direct.heartbeat',
54
+ directSessionId: this.#terminalSessionId
55
+ })
56
+ )
57
+ this.#fail('TERMINAL_CONNECTION_LOST');
58
+ }, heartbeatMs);
59
+ this.#heartbeatTimer.unref?.();
60
+ }
61
+
62
+ get openSent() {
63
+ return this.#openSent;
64
+ }
65
+
66
+ activate() {
67
+ if (this.#active) return;
68
+ this.#active = true;
69
+ const pendingEvents = this.#pendingEvents;
70
+ this.#pendingEvents = [];
71
+ for (const { type, args } of pendingEvents) this.#emit(type, ...args);
72
+ }
73
+
74
+ waitForAttachment(grant) {
75
+ return new Promise((resolve, reject) => {
76
+ if (this.#closed) {
77
+ reject(new DirectFallbackError('TERMINAL_DIRECT_CLOSED'));
78
+ return;
79
+ }
80
+ this.#attachmentResolve = resolve;
81
+ this.#attachmentReject = reject;
82
+ let opened = false;
83
+ const onOpen = () => {
84
+ if (opened || this.#closed) return;
85
+ opened = true;
86
+ try {
87
+ this.#openSent = true;
88
+ if (
89
+ !this.#channel.sendMessage(
90
+ JSON.stringify({
91
+ type: 'OPEN',
92
+ directSessionId: this.#terminalSessionId,
93
+ grant
94
+ })
95
+ )
96
+ )
97
+ throw new Error('TERMINAL_ATTACH_FAILED');
98
+ } catch {
99
+ this.#fail('TERMINAL_ATTACH_FAILED');
100
+ }
101
+ };
102
+ this.#channel.onOpen?.(onOpen);
103
+ if (this.#channel.isOpen?.() === true) onOpen();
104
+ });
105
+ }
106
+
107
+ send(frame) {
108
+ if (this.#closed || this.#channel.isOpen?.() !== true)
109
+ throw new Error('TERMINAL_CONNECTION_LOST');
110
+ const value = typeof frame === 'string' ? frame : JSON.stringify(frame);
111
+ if (!this.#channel.sendMessage(value)) throw new Error('TERMINAL_INPUT_FAILED');
112
+ }
113
+
114
+ close() {
115
+ this.dispose();
116
+ }
117
+
118
+ dispose({ skipCancel = false } = {}) {
119
+ if (this.#closed) return;
120
+ this.#closed = true;
121
+ clearInterval(this.#heartbeatTimer);
122
+ if (!skipCancel) this.#sendCancel();
123
+ this.#attachmentReject?.(new DirectFallbackError('TERMINAL_DIRECT_CLOSED'));
124
+ this.#attachmentResolve = undefined;
125
+ this.#attachmentReject = undefined;
126
+ this.#unsubscribeLive?.();
127
+ this.#unsubscribeLive = undefined;
128
+ this.#channel.close?.();
129
+ this.#peer.close?.();
130
+ this.#live.close();
131
+ this.#cleanupNative?.();
132
+ this.#emit('close');
133
+ }
134
+
135
+ async cancelAndDispose() {
136
+ const terminalClosed = this.#live.waitFor(
137
+ (message) =>
138
+ message.type === 'terminal.direct.closed' &&
139
+ message.directSessionId === this.#terminalSessionId,
140
+ undefined,
141
+ 'DIRECT_CANCEL_TIMEOUT',
142
+ 1000
143
+ );
144
+ const transportClosed = this.#live.waitFor(
145
+ (message) =>
146
+ message.type === 'direct.closed' && message.directSessionId === this.#transportSessionId,
147
+ undefined,
148
+ 'DIRECT_CANCEL_TIMEOUT',
149
+ 1000
150
+ );
151
+ this.#sendCancel();
152
+ await Promise.allSettled([terminalClosed, transportClosed]);
153
+ this.dispose({ skipCancel: true });
154
+ }
155
+
156
+ #handleMessage(raw) {
157
+ if (this.#closed) return;
158
+ if (!this.#attached) {
159
+ let frame;
160
+ try {
161
+ frame = JSON.parse(rawToString(raw));
162
+ } catch {
163
+ this.#fail('TERMINAL_FRAME_INVALID');
164
+ return;
165
+ }
166
+ if (
167
+ !isRecord(frame) ||
168
+ frame.type !== 'ATTACHED' ||
169
+ frame.terminalId !== this.#terminalId ||
170
+ !Number.isSafeInteger(frame.afterSequence) ||
171
+ frame.afterSequence < 0
172
+ ) {
173
+ this.#fail('TERMINAL_ATTACH_FAILED');
174
+ return;
175
+ }
176
+ this.#attached = true;
177
+ this.#attachmentResolve?.();
178
+ this.#attachmentResolve = undefined;
179
+ this.#attachmentReject = undefined;
180
+ return;
181
+ }
182
+ this.#emit(
183
+ 'message',
184
+ raw,
185
+ typeof raw === 'string'
186
+ ? false
187
+ : Buffer.isBuffer(raw) || raw instanceof ArrayBuffer || ArrayBuffer.isView(raw)
188
+ );
189
+ }
190
+
191
+ #handleControl(message) {
192
+ if (
193
+ (message.type === 'terminal.direct.error' || message.type === 'terminal.direct.closed') &&
194
+ message.directSessionId === this.#terminalSessionId
195
+ )
196
+ this.#fail(message.code);
197
+ if (
198
+ (message.type === 'direct.closed' || message.type === 'direct.error') &&
199
+ message.directSessionId === this.#transportSessionId
200
+ )
201
+ this.#fail(message.code);
202
+ }
203
+
204
+ #fail(code) {
205
+ if (this.#closed) return;
206
+ const error = directControlError(code);
207
+ if (!this.#attached) this.#attachmentReject?.(error);
208
+ this.#attachmentResolve = undefined;
209
+ this.#attachmentReject = undefined;
210
+ this.#emit('error', error);
211
+ this.dispose();
212
+ }
213
+
214
+ #emit(type, ...args) {
215
+ if (!this.#active) {
216
+ this.#pendingEvents.push({ type, args });
217
+ return;
218
+ }
219
+ if (type !== 'error' || this.listenerCount('error') > 0) this.emit(type, ...args);
220
+ }
221
+
222
+ #sendCancel() {
223
+ if (this.#cancelSent) return;
224
+ this.#cancelSent = true;
225
+ this.#live.send({
226
+ type: 'terminal.direct.cancel',
227
+ directSessionId: this.#terminalSessionId
228
+ });
229
+ this.#live.send({
230
+ type: 'direct.cancel',
231
+ directSessionId: this.#transportSessionId,
232
+ reason: 'CLI_CLOSED'
233
+ });
234
+ }
235
+ }