@myagentroam/marctl 0.11.2 → 0.11.4
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/cli.mjs +54 -29
- package/src/client.mjs +1 -1
- package/src/config.mjs +1 -11
- package/src/direct-control.mjs +225 -0
- package/src/direct-errors.mjs +48 -0
- package/src/direct-process.mjs +40 -17
- package/src/direct-transport.mjs +235 -0
- package/src/direct.mjs +102 -494
- package/src/errors.mjs +11 -0
- package/src/i18n/en-US.mjs +6 -3
- package/src/i18n/zh-CN.mjs +5 -3
- package/src/scp-channel.mjs +99 -0
- package/src/scp-copy.mjs +46 -0
- package/src/scp-endpoint.mjs +29 -0
- package/src/scp-protocol.mjs +2 -0
- package/src/scp-workers.mjs +179 -0
- package/src/scp.mjs +26 -344
- package/src/session-args.mjs +176 -0
- package/src/session-input.mjs +88 -0
- package/src/session-prepare.mjs +51 -0
- package/src/session-target.mjs +25 -0
- package/src/session-wait.mjs +232 -0
- package/src/sessions.mjs +27 -567
- package/src/terminal-frame.mjs +48 -0
- package/src/terminal-interaction.mjs +177 -0
- package/src/terminal-race.mjs +117 -0
- package/src/terminal-relay.mjs +305 -0
- package/src/terminal.mjs +50 -603
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { MarctlClient } from './client.mjs';
|
|
3
|
-
import {
|
|
3
|
+
import { loadConfig } from './config.mjs';
|
|
4
|
+
import { MarctlError } from './errors.mjs';
|
|
4
5
|
import { createTranslator } from './i18n/index.mjs';
|
|
5
6
|
import { runTerminalSession } from './terminal.mjs';
|
|
6
7
|
import { runScp } from './scp.mjs';
|
|
7
|
-
import { parseSessionArgs
|
|
8
|
+
import { parseSessionArgs } from './session-args.mjs';
|
|
9
|
+
import { runSessionCommand, sessionExitCode } from './sessions.mjs';
|
|
10
|
+
|
|
11
|
+
async function withCommandSignals(run) {
|
|
12
|
+
const controller = new AbortController();
|
|
13
|
+
const onSigint = () => controller.abort('SIGINT');
|
|
14
|
+
const onSigterm = () => controller.abort('SIGTERM');
|
|
15
|
+
process.once('SIGINT', onSigint);
|
|
16
|
+
process.once('SIGTERM', onSigterm);
|
|
17
|
+
try {
|
|
18
|
+
return await run(controller.signal);
|
|
19
|
+
} finally {
|
|
20
|
+
process.off('SIGINT', onSigint);
|
|
21
|
+
process.off('SIGTERM', onSigterm);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
8
24
|
|
|
9
25
|
export async function main(
|
|
10
26
|
argv = process.argv.slice(2),
|
|
@@ -60,42 +76,51 @@ export async function main(
|
|
|
60
76
|
return 0;
|
|
61
77
|
}
|
|
62
78
|
if (args.command === 'sessions') {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
const execute = (signal) =>
|
|
80
|
+
runSessionCommand({
|
|
81
|
+
args,
|
|
82
|
+
client: marctlClient,
|
|
83
|
+
env,
|
|
84
|
+
stdin,
|
|
85
|
+
WebSocketClass,
|
|
86
|
+
translate,
|
|
87
|
+
signal
|
|
88
|
+
});
|
|
89
|
+
const result = args.action === 'wait' ? await withCommandSignals(execute) : await execute();
|
|
71
90
|
writeLine(stdout, JSON.stringify(result, null, args.json ? undefined : 2));
|
|
72
91
|
return 0;
|
|
73
92
|
}
|
|
74
93
|
if (args.command === 'scp') {
|
|
75
94
|
const socketClass = WebSocketClass ?? (await import('ws')).default;
|
|
76
|
-
return await
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
95
|
+
return await withCommandSignals((signal) =>
|
|
96
|
+
runScp({
|
|
97
|
+
source: args.source,
|
|
98
|
+
destination: args.destination,
|
|
99
|
+
preserve: args.preserve,
|
|
100
|
+
client: marctlClient,
|
|
101
|
+
WebSocketClass: socketClass,
|
|
102
|
+
resolveNodeId: (reference) => resolveNodeId(marctlClient, reference, translate),
|
|
103
|
+
translate,
|
|
104
|
+
signal
|
|
105
|
+
})
|
|
106
|
+
);
|
|
85
107
|
}
|
|
86
108
|
|
|
87
109
|
const nodeId = await resolveNodeId(marctlClient, args.nodeId, translate);
|
|
88
110
|
const socketClass = WebSocketClass ?? (await import('ws')).default;
|
|
89
|
-
return await
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
111
|
+
return await withCommandSignals((signal) =>
|
|
112
|
+
runTerminalSession({
|
|
113
|
+
client: marctlClient,
|
|
114
|
+
WebSocketClass: socketClass,
|
|
115
|
+
nodeId,
|
|
116
|
+
command: args.remoteCommand,
|
|
117
|
+
raceConnections: true,
|
|
118
|
+
stdout,
|
|
119
|
+
stderr,
|
|
120
|
+
translate,
|
|
121
|
+
signal
|
|
122
|
+
})
|
|
123
|
+
);
|
|
99
124
|
} catch (error) {
|
|
100
125
|
if (argv[0] === 'sessions') {
|
|
101
126
|
const failure = {
|
package/src/client.mjs
CHANGED
package/src/config.mjs
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
import { createTranslator } from './i18n/index.mjs';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
constructor(message, options = {}) {
|
|
5
|
-
super(message, options);
|
|
6
|
-
this.name = 'MarctlError';
|
|
7
|
-
this.code = options.code;
|
|
8
|
-
this.requestId = options.requestId;
|
|
9
|
-
this.upstreamRequestId = options.upstreamRequestId;
|
|
10
|
-
this.details = options.details;
|
|
11
|
-
this.statusCode = options.statusCode;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
3
|
+
import { MarctlError } from './errors.mjs';
|
|
14
4
|
|
|
15
5
|
export async function loadConfig({ env = process.env, translate = createTranslator(env) } = {}) {
|
|
16
6
|
const serverUrl = nonEmptyString(env.MAR_CTL_SERVER_URL);
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { DirectFallbackError, directControlError } from './direct-errors.mjs';
|
|
2
|
+
|
|
3
|
+
export class LiveControl {
|
|
4
|
+
#socket;
|
|
5
|
+
#timeoutMs;
|
|
6
|
+
#ready = false;
|
|
7
|
+
#closed = false;
|
|
8
|
+
#waiters = new Set();
|
|
9
|
+
#listeners = new Set();
|
|
10
|
+
#onMessage;
|
|
11
|
+
#onError;
|
|
12
|
+
#onClose;
|
|
13
|
+
|
|
14
|
+
constructor(socket, timeoutMs) {
|
|
15
|
+
this.#socket = socket;
|
|
16
|
+
this.#timeoutMs = timeoutMs;
|
|
17
|
+
this.#onMessage = (raw, isBinary) => {
|
|
18
|
+
if (isBinary) return;
|
|
19
|
+
let message;
|
|
20
|
+
try {
|
|
21
|
+
message = JSON.parse(rawToString(raw));
|
|
22
|
+
} catch {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (!isRecord(message) || typeof message.type !== 'string') return;
|
|
26
|
+
for (const listener of this.#listeners) listener(message);
|
|
27
|
+
for (const waiter of [...this.#waiters]) {
|
|
28
|
+
if (
|
|
29
|
+
((waiter.request?.type === 'terminal.direct.prepare' &&
|
|
30
|
+
message.type === 'terminal.direct.error') ||
|
|
31
|
+
(waiter.request?.type === 'direct.prepare' && message.type === 'direct.error')) &&
|
|
32
|
+
message.requestId === waiter.request.requestId
|
|
33
|
+
) {
|
|
34
|
+
this.#waiters.delete(waiter);
|
|
35
|
+
clearTimeout(waiter.timer);
|
|
36
|
+
waiter.reject(directControlError(message.code));
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (!waiter.predicate(message, waiter.request)) continue;
|
|
40
|
+
this.#waiters.delete(waiter);
|
|
41
|
+
clearTimeout(waiter.timer);
|
|
42
|
+
waiter.resolve(message);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
this.#onError = () => this.#fail(new DirectFallbackError('DIRECT_CONTROL_UNAVAILABLE'));
|
|
46
|
+
this.#onClose = () => this.#fail(new DirectFallbackError('DIRECT_CONTROL_CLOSED'));
|
|
47
|
+
socket.on?.('message', this.#onMessage);
|
|
48
|
+
socket.on?.('error', this.#onError);
|
|
49
|
+
socket.on?.('close', this.#onClose);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async openAndResume() {
|
|
53
|
+
await this.#waitForSocket('open', 'DIRECT_CONTROL_TIMEOUT');
|
|
54
|
+
const ready = this.waitFor(
|
|
55
|
+
(message) =>
|
|
56
|
+
message.type === 'workbench.ready' && typeof message.workbenchConnectionId === 'string',
|
|
57
|
+
undefined,
|
|
58
|
+
'DIRECT_RESUME_TIMEOUT'
|
|
59
|
+
);
|
|
60
|
+
if (!this.send({ type: 'resume' })) {
|
|
61
|
+
ready.catch(() => undefined);
|
|
62
|
+
throw new DirectFallbackError('DIRECT_CONTROL_UNAVAILABLE');
|
|
63
|
+
}
|
|
64
|
+
await ready;
|
|
65
|
+
this.#ready = true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
request(message, predicate, fallbackCode) {
|
|
69
|
+
if (!this.#ready) throw new DirectFallbackError('DIRECT_CONTROL_UNAVAILABLE');
|
|
70
|
+
const response = this.waitFor(predicate, message, fallbackCode);
|
|
71
|
+
if (!this.send(message)) {
|
|
72
|
+
response.catch(() => undefined);
|
|
73
|
+
throw new DirectFallbackError('DIRECT_CONTROL_UNAVAILABLE');
|
|
74
|
+
}
|
|
75
|
+
return response;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
waitFor(predicate, request, fallbackCode, timeoutMs = this.#timeoutMs) {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
if (this.#closed) {
|
|
81
|
+
reject(new DirectFallbackError('DIRECT_CONTROL_CLOSED'));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const waiter = {
|
|
85
|
+
predicate,
|
|
86
|
+
request,
|
|
87
|
+
resolve,
|
|
88
|
+
reject,
|
|
89
|
+
timer: setTimeout(() => {
|
|
90
|
+
this.#waiters.delete(waiter);
|
|
91
|
+
reject(new DirectFallbackError(fallbackCode ?? 'DIRECT_SETUP_TIMEOUT'));
|
|
92
|
+
}, timeoutMs)
|
|
93
|
+
};
|
|
94
|
+
this.#waiters.add(waiter);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
onControl(listener) {
|
|
99
|
+
if (this.#closed) return () => {};
|
|
100
|
+
this.#listeners.add(listener);
|
|
101
|
+
return () => this.#listeners.delete(listener);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
send(message) {
|
|
105
|
+
if (this.#closed || (this.#socket.readyState !== undefined && this.#socket.readyState !== 1))
|
|
106
|
+
return false;
|
|
107
|
+
try {
|
|
108
|
+
this.#socket.send(JSON.stringify(message));
|
|
109
|
+
return true;
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
close() {
|
|
116
|
+
if (this.#closed) return;
|
|
117
|
+
this.#closed = true;
|
|
118
|
+
this.#fail(new DirectFallbackError('DIRECT_CONTROL_CLOSED'));
|
|
119
|
+
this.#listeners.clear();
|
|
120
|
+
this.#socket.removeListener?.('message', this.#onMessage);
|
|
121
|
+
this.#socket.removeListener?.('error', this.#onError);
|
|
122
|
+
this.#socket.removeListener?.('close', this.#onClose);
|
|
123
|
+
try {
|
|
124
|
+
this.#socket.close();
|
|
125
|
+
} catch {
|
|
126
|
+
// The control socket is already closed.
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#waitForSocket(event, code) {
|
|
131
|
+
if (this.#socket.readyState === 1) {
|
|
132
|
+
return Promise.resolve();
|
|
133
|
+
}
|
|
134
|
+
return new Promise((resolve, reject) => {
|
|
135
|
+
const onEvent = () => {
|
|
136
|
+
cleanup();
|
|
137
|
+
resolve();
|
|
138
|
+
};
|
|
139
|
+
const onError = () => {
|
|
140
|
+
cleanup();
|
|
141
|
+
reject(new DirectFallbackError(code));
|
|
142
|
+
};
|
|
143
|
+
const onClose = () => {
|
|
144
|
+
cleanup();
|
|
145
|
+
reject(new DirectFallbackError(code));
|
|
146
|
+
};
|
|
147
|
+
const timer = setTimeout(() => {
|
|
148
|
+
cleanup();
|
|
149
|
+
reject(new DirectFallbackError(code));
|
|
150
|
+
}, this.#timeoutMs);
|
|
151
|
+
const cleanup = () => {
|
|
152
|
+
clearTimeout(timer);
|
|
153
|
+
this.#socket.removeListener?.(event, onEvent);
|
|
154
|
+
this.#socket.removeListener?.('error', onError);
|
|
155
|
+
this.#socket.removeListener?.('close', onClose);
|
|
156
|
+
};
|
|
157
|
+
this.#socket.once?.(event, onEvent);
|
|
158
|
+
this.#socket.once?.('error', onError);
|
|
159
|
+
this.#socket.once?.('close', onClose);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#fail(error) {
|
|
164
|
+
if (this.#closed && this.#waiters.size === 0) return;
|
|
165
|
+
for (const waiter of [...this.#waiters]) {
|
|
166
|
+
this.#waiters.delete(waiter);
|
|
167
|
+
clearTimeout(waiter.timer);
|
|
168
|
+
waiter.reject(error);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function cancelSession(live, type, directSessionId) {
|
|
174
|
+
if (live === undefined) return;
|
|
175
|
+
const closed = live.waitFor(
|
|
176
|
+
(message) =>
|
|
177
|
+
message.type === `${type.replace('.cancel', '.closed')}` &&
|
|
178
|
+
message.directSessionId === directSessionId,
|
|
179
|
+
undefined,
|
|
180
|
+
'DIRECT_CANCEL_TIMEOUT',
|
|
181
|
+
1000
|
|
182
|
+
);
|
|
183
|
+
live.send({
|
|
184
|
+
type,
|
|
185
|
+
directSessionId,
|
|
186
|
+
...(type === 'direct.cancel' ? { reason: 'CLI_FALLBACK' } : {})
|
|
187
|
+
});
|
|
188
|
+
await closed.catch(() => undefined);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function withTimeout(promise, timeoutMs, code, signal) {
|
|
192
|
+
let timer;
|
|
193
|
+
let onAbort;
|
|
194
|
+
return Promise.race([
|
|
195
|
+
promise,
|
|
196
|
+
new Promise((_, reject) => {
|
|
197
|
+
timer = setTimeout(() => reject(new DirectFallbackError(code)), timeoutMs);
|
|
198
|
+
}),
|
|
199
|
+
...(signal
|
|
200
|
+
? [
|
|
201
|
+
new Promise((_, reject) => {
|
|
202
|
+
onAbort = () => reject(new DirectFallbackError('DIRECT_RACE_CANCELLED'));
|
|
203
|
+
if (signal.aborted) onAbort();
|
|
204
|
+
else signal.addEventListener('abort', onAbort, { once: true });
|
|
205
|
+
})
|
|
206
|
+
]
|
|
207
|
+
: [])
|
|
208
|
+
]).finally(() => {
|
|
209
|
+
clearTimeout(timer);
|
|
210
|
+
if (onAbort) signal.removeEventListener('abort', onAbort);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function rawToString(raw) {
|
|
215
|
+
if (typeof raw === 'string') return raw;
|
|
216
|
+
if (Buffer.isBuffer(raw)) return raw.toString('utf8');
|
|
217
|
+
if (raw instanceof ArrayBuffer) return Buffer.from(raw).toString('utf8');
|
|
218
|
+
if (ArrayBuffer.isView(raw))
|
|
219
|
+
return Buffer.from(raw.buffer, raw.byteOffset, raw.byteLength).toString('utf8');
|
|
220
|
+
return String(raw);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function isRecord(value) {
|
|
224
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
225
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export class DirectFallbackError extends Error {
|
|
2
|
+
constructor(code = 'DIRECT_UNAVAILABLE') {
|
|
3
|
+
super(code);
|
|
4
|
+
this.name = 'DirectFallbackError';
|
|
5
|
+
this.code = code;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class DirectUnsafeError extends Error {
|
|
10
|
+
constructor(code = 'DIRECT_CANCEL_UNCONFIRMED') {
|
|
11
|
+
super(code);
|
|
12
|
+
this.name = 'DirectUnsafeError';
|
|
13
|
+
this.code = code;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const directRejections = new Set([
|
|
18
|
+
'DIRECT_TRANSFER_NOT_AUTHORIZED',
|
|
19
|
+
'DIRECT_SESSION_LIMIT_REACHED',
|
|
20
|
+
'DIRECT_NODE_GENERATION_MISMATCH',
|
|
21
|
+
'DIRECT_TRANSFER_SESSION_CONFLICT',
|
|
22
|
+
'TERMINAL_NOT_AUTHORIZED',
|
|
23
|
+
'TERMINAL_DIRECT_LEASE_EXPIRED',
|
|
24
|
+
'TERMINAL_NOT_FOUND',
|
|
25
|
+
'TERMINAL_CLOSED',
|
|
26
|
+
'TERMINAL_WORKSPACE_MISMATCH',
|
|
27
|
+
'TERMINAL_GIT_OWNER_REQUIRED',
|
|
28
|
+
'TERMINAL_ATTACH_CONFLICT',
|
|
29
|
+
'TERMINAL_RELAY_LIMIT_REACHED'
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
export class DirectRejectedError extends Error {
|
|
33
|
+
constructor(code) {
|
|
34
|
+
super(code);
|
|
35
|
+
this.name = 'DirectRejectedError';
|
|
36
|
+
this.code = code;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function directControlError(code) {
|
|
41
|
+
const safeCode =
|
|
42
|
+
typeof code === 'string' && /^[A-Z][A-Z0-9_]{0,127}$/u.test(code)
|
|
43
|
+
? code
|
|
44
|
+
: 'TERMINAL_DIRECT_UNAVAILABLE';
|
|
45
|
+
return directRejections.has(safeCode)
|
|
46
|
+
? new DirectRejectedError(safeCode)
|
|
47
|
+
: new DirectFallbackError(safeCode);
|
|
48
|
+
}
|
package/src/direct-process.mjs
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { fork } from 'node:child_process';
|
|
2
2
|
import { EventEmitter } from 'node:events';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
DirectFallbackError,
|
|
6
|
+
DirectUnsafeError,
|
|
7
|
+
DirectRejectedError,
|
|
8
|
+
directControlError
|
|
9
|
+
} from './direct-errors.mjs';
|
|
5
10
|
|
|
6
11
|
const CLEANUP_TIMEOUT_MS = 1000;
|
|
7
12
|
const MAX_BUFFER_BYTES = 2 * 1024 * 1024;
|
|
@@ -113,11 +118,16 @@ export function openDirectTerminalProcess({
|
|
|
113
118
|
resolve(transport);
|
|
114
119
|
} else if (message.type === 'failure') {
|
|
115
120
|
const error = settled
|
|
116
|
-
?
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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);
|
|
121
131
|
fail(error);
|
|
122
132
|
} else if (message.type === 'frame') {
|
|
123
133
|
transport.frame(message.frame, message.isBinary);
|
|
@@ -216,11 +226,13 @@ class IsolatedDirectTransport extends EventEmitter {
|
|
|
216
226
|
if (this.#closed) return;
|
|
217
227
|
this.#emit(
|
|
218
228
|
'error',
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
229
|
+
error instanceof DirectRejectedError
|
|
230
|
+
? error
|
|
231
|
+
: new Error(
|
|
232
|
+
error instanceof DirectFallbackError || error instanceof DirectUnsafeError
|
|
233
|
+
? 'TERMINAL_CONNECTION_LOST'
|
|
234
|
+
: error.message
|
|
235
|
+
)
|
|
224
236
|
);
|
|
225
237
|
this.close();
|
|
226
238
|
}
|
|
@@ -298,6 +310,7 @@ function runChild() {
|
|
|
298
310
|
sendIpc(process, { type: 'send.result', id: message.id, code: 'TERMINAL_INPUT_FAILED' });
|
|
299
311
|
}
|
|
300
312
|
} else if (message.type === 'start') {
|
|
313
|
+
let rejectionSent = false;
|
|
301
314
|
try {
|
|
302
315
|
const { MarctlClient } = await import('./client.mjs');
|
|
303
316
|
const { openDirectTerminal } = await import('./direct.mjs');
|
|
@@ -312,6 +325,10 @@ function runChild() {
|
|
|
312
325
|
peerConnectionFactory: (name, config) => new native.PeerConnection(name, config),
|
|
313
326
|
cleanupNative: cleanup,
|
|
314
327
|
signal: controller.signal,
|
|
328
|
+
onRejected: (error) => {
|
|
329
|
+
rejectionSent = true;
|
|
330
|
+
sendIpc(process, { type: 'failure', name: 'DirectRejectedError', code: error.code });
|
|
331
|
+
},
|
|
315
332
|
onPrepared: () => callback('prepared'),
|
|
316
333
|
onIceConnected: () => {
|
|
317
334
|
iceSelection = callback('ice');
|
|
@@ -328,7 +345,7 @@ function runChild() {
|
|
|
328
345
|
sendIpc(process, { type: 'frame', frame: String(frame), isBinary }, abort)
|
|
329
346
|
);
|
|
330
347
|
transport.on('error', (error) => {
|
|
331
|
-
sendIpc(process, { type: 'failure', code: error.message });
|
|
348
|
+
sendIpc(process, { type: 'failure', code: error.code ?? error.message });
|
|
332
349
|
abort();
|
|
333
350
|
});
|
|
334
351
|
transport.on('close', () => {
|
|
@@ -337,11 +354,17 @@ function runChild() {
|
|
|
337
354
|
});
|
|
338
355
|
transport.activate();
|
|
339
356
|
} catch (error) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
+
});
|
|
345
368
|
cleanup();
|
|
346
369
|
}
|
|
347
370
|
}
|