@rynx-ai/remote-runtime-client 0.1.11-beta.4 → 0.1.11-beta.40
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/client.d.ts +29 -5
- package/dist/client.js +248 -119
- package/package.json +3 -3
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RemoteRuntimeRpcMethod, type RemoteRuntimeRpcParams, type RemoteRuntimeRpcResultFor, type RemoteRuntimeSequencedSessionEvent, type
|
|
1
|
+
import { type RemoteRuntimeRpcMethod, type RemoteRuntimeRpcParams, type RemoteRuntimeRpcResultFor, type RemoteRuntimeSequencedSessionEvent, type RemoteRuntimeSessionTerminalRole } from "@rynx-ai/protocol/remote-runtime-rpc";
|
|
2
2
|
import { type DaemonStatus } from "@rynx-ai/protocol/remote-runtime";
|
|
3
3
|
import { type RuntimeEmulatorSurfaceClosedFrame, type RuntimeEmulatorSurfaceImageFrame, type RuntimeEmulatorSurfaceReadyFrame } from "@rynx-ai/protocol/runtime-emulator-surface";
|
|
4
4
|
import { type DirectRuntimeClientIdentity, type DirectRuntimeCredential } from "./credential.js";
|
|
@@ -12,8 +12,9 @@ export interface DirectRuntimeClientOptions {
|
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
export interface DirectRuntimeCallOptions {
|
|
15
|
-
/** Per-call override for operations whose expected duration differs from the
|
|
16
|
-
|
|
15
|
+
/** Per-call override for operations whose expected duration differs from the
|
|
16
|
+
* handshake. `null` leaves the operation to its own lifecycle deadlines. */
|
|
17
|
+
timeoutMs?: number | null;
|
|
17
18
|
}
|
|
18
19
|
export interface DirectRuntimeSessionEventsOptions {
|
|
19
20
|
/** Timeout for the accepted ready barrier and cancellation acknowledgement. */
|
|
@@ -33,11 +34,34 @@ export interface DirectRuntimeSessionTerminalOptions {
|
|
|
33
34
|
/** Timeout for the opened barrier and close acknowledgement. */
|
|
34
35
|
timeoutMs?: number;
|
|
35
36
|
}
|
|
36
|
-
export interface
|
|
37
|
+
export interface DirectRuntimeSessionTerminalRead {
|
|
38
|
+
data: Uint8Array;
|
|
39
|
+
nextOffset: number;
|
|
40
|
+
done: boolean;
|
|
41
|
+
finalOffset?: number;
|
|
42
|
+
}
|
|
43
|
+
export interface DirectRuntimeSessionTerminalCloseInfo {
|
|
44
|
+
reason: "client_closed" | "terminal_exited" | "not_live" | "unauthorized" | "capacity" | "backpressure" | "internal";
|
|
45
|
+
exitCode?: number;
|
|
46
|
+
finalOffset?: number;
|
|
47
|
+
}
|
|
48
|
+
export interface DirectRuntimeSessionTerminal {
|
|
37
49
|
readonly attachmentId: string;
|
|
38
50
|
readonly sessionId: string;
|
|
39
51
|
readonly role: RemoteRuntimeSessionTerminalRole;
|
|
40
|
-
readonly
|
|
52
|
+
readonly seedBytes: number;
|
|
53
|
+
readonly dimensions?: {
|
|
54
|
+
cols: number;
|
|
55
|
+
rows: number;
|
|
56
|
+
};
|
|
57
|
+
readonly closed: Promise<DirectRuntimeSessionTerminalCloseInfo>;
|
|
58
|
+
readSeed(offset: number, maxBytes: number): Promise<DirectRuntimeSessionTerminalRead>;
|
|
59
|
+
start(): Promise<void>;
|
|
60
|
+
read(offset: number, maxBytes: number): Promise<DirectRuntimeSessionTerminalRead>;
|
|
61
|
+
onResize(listener: (dimensions: {
|
|
62
|
+
cols: number;
|
|
63
|
+
rows: number;
|
|
64
|
+
}) => void): void;
|
|
41
65
|
write(data: Uint8Array): Promise<void>;
|
|
42
66
|
resize(cols: number, rows: number): Promise<void>;
|
|
43
67
|
/** Idempotently detach and wait for the remote close acknowledgement. */
|
package/dist/client.js
CHANGED
|
@@ -25,8 +25,6 @@ const MAX_PENDING_OUTBOUND_BYTES = 1024 * 1024;
|
|
|
25
25
|
const MAX_E2EE_ENVELOPE_OVERHEAD_BYTES = 256;
|
|
26
26
|
const MAX_SUBSCRIPTION_QUEUED_EVENTS = 128;
|
|
27
27
|
const MAX_SUBSCRIPTION_QUEUED_BYTES = 256 * 1024;
|
|
28
|
-
const MAX_TERMINAL_QUEUED_OUTPUT_FRAMES = 128;
|
|
29
|
-
const MAX_TERMINAL_QUEUED_OUTPUT_BYTES = 512 * 1024;
|
|
30
28
|
const establishedTextDecoder = new TextDecoder("utf-8", { fatal: true });
|
|
31
29
|
/** Enroll a client identity, authenticate the issued grant, and probe status. */
|
|
32
30
|
export async function pairDirectRuntime(untrustedOffer, options = {}) {
|
|
@@ -208,20 +206,25 @@ class DirectRuntimeConnectionImpl {
|
|
|
208
206
|
outcome: "not_started",
|
|
209
207
|
});
|
|
210
208
|
}
|
|
211
|
-
const timeoutMs =
|
|
209
|
+
const timeoutMs = options.timeoutMs === null
|
|
210
|
+
? null
|
|
211
|
+
: parseTimeout(options.timeoutMs ?? this.timeoutMs);
|
|
212
212
|
return await new Promise((resolve, reject) => {
|
|
213
|
-
const timer =
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
213
|
+
const timer = timeoutMs === null
|
|
214
|
+
? undefined
|
|
215
|
+
: setTimeout(() => {
|
|
216
|
+
this.pending.delete(id);
|
|
217
|
+
reject(callTransportError(clientError("deadline_exceeded", "Direct Runtime RPC timed out"), method, id));
|
|
218
|
+
}, timeoutMs);
|
|
219
|
+
timer?.unref?.();
|
|
218
220
|
this.pending.set(id, { method, resolve, reject, timer });
|
|
219
221
|
void this.channel.socket.sendText(frame).catch((error) => {
|
|
220
222
|
const pending = this.pending.get(id);
|
|
221
223
|
if (!pending)
|
|
222
224
|
return;
|
|
223
225
|
this.pending.delete(id);
|
|
224
|
-
|
|
226
|
+
if (pending.timer)
|
|
227
|
+
clearTimeout(pending.timer);
|
|
225
228
|
pending.reject(error instanceof DirectRuntimeClientCapacityError
|
|
226
229
|
? error
|
|
227
230
|
: callTransportError(error, pending.method, id));
|
|
@@ -232,7 +235,11 @@ class DirectRuntimeConnectionImpl {
|
|
|
232
235
|
if (this.statusSnapshot)
|
|
233
236
|
return Promise.resolve(this.statusSnapshot);
|
|
234
237
|
if (!this.statusProbe) {
|
|
235
|
-
|
|
238
|
+
// An application operation may deliberately have no outer deadline, but
|
|
239
|
+
// capability preflight is transport metadata and retains the connection
|
|
240
|
+
// default rather than inheriting that unbounded lifecycle.
|
|
241
|
+
const statusOptions = options.timeoutMs === null ? {} : options;
|
|
242
|
+
this.statusProbe = this.sendCall("status.get", {}, statusOptions).finally(() => {
|
|
236
243
|
this.statusProbe = undefined;
|
|
237
244
|
});
|
|
238
245
|
}
|
|
@@ -469,8 +476,14 @@ class DirectRuntimeConnectionImpl {
|
|
|
469
476
|
case "session.events.closed":
|
|
470
477
|
this.receiveSessionFrame(value, frame.byteLength);
|
|
471
478
|
return;
|
|
472
|
-
case "session.terminal.
|
|
473
|
-
case "session.terminal.
|
|
479
|
+
case "session.terminal.prepared":
|
|
480
|
+
case "session.terminal.seed.chunk":
|
|
481
|
+
case "session.terminal.started":
|
|
482
|
+
case "session.terminal.chunk":
|
|
483
|
+
case "session.terminal.input.ack":
|
|
484
|
+
case "session.terminal.resize.ack":
|
|
485
|
+
case "session.terminal.reader.done":
|
|
486
|
+
case "session.terminal.dimensions":
|
|
474
487
|
case "session.terminal.closed":
|
|
475
488
|
this.receiveTerminalFrame(value);
|
|
476
489
|
return;
|
|
@@ -498,7 +511,8 @@ class DirectRuntimeConnectionImpl {
|
|
|
498
511
|
return;
|
|
499
512
|
}
|
|
500
513
|
this.pending.delete(envelope.id);
|
|
501
|
-
|
|
514
|
+
if (pending.timer)
|
|
515
|
+
clearTimeout(pending.timer);
|
|
502
516
|
let response;
|
|
503
517
|
try {
|
|
504
518
|
response = parseRemoteRuntimeRpcResponseForMethod(envelope, pending.method);
|
|
@@ -615,16 +629,30 @@ class DirectRuntimeConnectionImpl {
|
|
|
615
629
|
return;
|
|
616
630
|
}
|
|
617
631
|
switch (frame.type) {
|
|
618
|
-
case "session.terminal.
|
|
632
|
+
case "session.terminal.prepared":
|
|
619
633
|
this.acceptTerminal(frame);
|
|
620
634
|
return;
|
|
621
|
-
case "session.terminal.
|
|
635
|
+
case "session.terminal.seed.chunk":
|
|
636
|
+
case "session.terminal.chunk":
|
|
637
|
+
case "session.terminal.started":
|
|
638
|
+
case "session.terminal.input.ack":
|
|
639
|
+
case "session.terminal.resize.ack":
|
|
640
|
+
case "session.terminal.reader.done": {
|
|
622
641
|
const terminal = this.terminal;
|
|
623
642
|
if (!terminal || terminal.attachmentId !== frame.attachmentId) {
|
|
624
|
-
this.fail(clientError("protocol_error", "Terminal
|
|
643
|
+
this.fail(clientError("protocol_error", "Terminal response has no active attachment"));
|
|
625
644
|
return;
|
|
626
645
|
}
|
|
627
|
-
terminal.
|
|
646
|
+
terminal.receive(frame);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
case "session.terminal.dimensions": {
|
|
650
|
+
const terminal = this.terminal;
|
|
651
|
+
if (!terminal || terminal.attachmentId !== frame.attachmentId) {
|
|
652
|
+
this.fail(clientError("protocol_error", "Terminal dimensions have no active attachment"));
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
terminal.updateDimensions(frame.cols, frame.rows);
|
|
628
656
|
return;
|
|
629
657
|
}
|
|
630
658
|
case "session.terminal.closed":
|
|
@@ -643,7 +671,7 @@ class DirectRuntimeConnectionImpl {
|
|
|
643
671
|
}
|
|
644
672
|
this.pendingTerminal = undefined;
|
|
645
673
|
clearTimeout(pending.timer);
|
|
646
|
-
const terminal = new SessionTerminalImpl(frame, pending.timeoutMs, (
|
|
674
|
+
const terminal = new SessionTerminalImpl(frame, pending.timeoutMs, (value) => this.channel.socket.sendText(encodeRemoteRuntimeSessionTerminalClientFrame(value)), () => this.closeTerminal(frame.attachmentId), (error) => this.fail(error));
|
|
647
675
|
this.terminal = terminal;
|
|
648
676
|
pending.resolve(terminal);
|
|
649
677
|
}
|
|
@@ -746,28 +774,6 @@ class DirectRuntimeConnectionImpl {
|
|
|
746
774
|
...point,
|
|
747
775
|
}));
|
|
748
776
|
}
|
|
749
|
-
async writeTerminal(attachmentId, data) {
|
|
750
|
-
if (data.byteLength === 0)
|
|
751
|
-
return;
|
|
752
|
-
for (let offset = 0; offset < data.byteLength; offset += REMOTE_RUNTIME_SESSION_TERMINAL_MAX_DATA_BYTES) {
|
|
753
|
-
const chunk = data.subarray(offset, Math.min(offset + REMOTE_RUNTIME_SESSION_TERMINAL_MAX_DATA_BYTES, data.byteLength));
|
|
754
|
-
const frame = encodeRemoteRuntimeSessionTerminalClientFrame({
|
|
755
|
-
type: "session.terminal.input",
|
|
756
|
-
attachmentId,
|
|
757
|
-
data: Buffer.from(chunk).toString("base64"),
|
|
758
|
-
});
|
|
759
|
-
await this.channel.socket.sendText(frame);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
async resizeTerminal(attachmentId, cols, rows) {
|
|
763
|
-
const frame = encodeRemoteRuntimeSessionTerminalClientFrame({
|
|
764
|
-
type: "session.terminal.resize",
|
|
765
|
-
attachmentId,
|
|
766
|
-
cols,
|
|
767
|
-
rows,
|
|
768
|
-
});
|
|
769
|
-
await this.channel.socket.sendText(frame);
|
|
770
|
-
}
|
|
771
777
|
async closeTerminal(attachmentId) {
|
|
772
778
|
const terminal = this.terminal;
|
|
773
779
|
if (!terminal || terminal.attachmentId !== attachmentId)
|
|
@@ -847,7 +853,8 @@ class DirectRuntimeConnectionImpl {
|
|
|
847
853
|
}
|
|
848
854
|
rejectPending(error) {
|
|
849
855
|
for (const [id, pending] of this.pending) {
|
|
850
|
-
|
|
856
|
+
if (pending.timer)
|
|
857
|
+
clearTimeout(pending.timer);
|
|
851
858
|
pending.reject(callTransportError(error, pending.method, id));
|
|
852
859
|
}
|
|
853
860
|
this.pending.clear();
|
|
@@ -1176,31 +1183,39 @@ function sessionEventsError(frame, sessionId) {
|
|
|
1176
1183
|
}
|
|
1177
1184
|
class SessionTerminalImpl {
|
|
1178
1185
|
timeoutMs;
|
|
1179
|
-
|
|
1180
|
-
sendResize;
|
|
1186
|
+
sendFrame;
|
|
1181
1187
|
requestClose;
|
|
1182
1188
|
reportBackpressure;
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
+
state = "prepared";
|
|
1190
|
+
seedOffset = 0;
|
|
1191
|
+
liveOffset = 0;
|
|
1192
|
+
closedSettled = false;
|
|
1193
|
+
detachedSettled = false;
|
|
1194
|
+
pending = new Map();
|
|
1189
1195
|
resolveClosed;
|
|
1190
1196
|
rejectClosed;
|
|
1197
|
+
resolveDetached;
|
|
1198
|
+
rejectDetached;
|
|
1191
1199
|
closed;
|
|
1200
|
+
detached;
|
|
1192
1201
|
attachmentId;
|
|
1193
1202
|
sessionId;
|
|
1194
1203
|
role;
|
|
1195
|
-
|
|
1204
|
+
seedBytes;
|
|
1205
|
+
dimensions;
|
|
1206
|
+
resizeListener;
|
|
1207
|
+
constructor(opened, timeoutMs, sendFrame, requestClose, reportBackpressure) {
|
|
1196
1208
|
this.timeoutMs = timeoutMs;
|
|
1197
|
-
this.
|
|
1198
|
-
this.sendResize = sendResize;
|
|
1209
|
+
this.sendFrame = sendFrame;
|
|
1199
1210
|
this.requestClose = requestClose;
|
|
1200
1211
|
this.reportBackpressure = reportBackpressure;
|
|
1201
1212
|
this.attachmentId = opened.attachmentId;
|
|
1202
1213
|
this.sessionId = opened.sessionId;
|
|
1203
1214
|
this.role = opened.role;
|
|
1215
|
+
this.seedBytes = opened.seedBytes;
|
|
1216
|
+
this.dimensions = opened.cols === undefined || opened.rows === undefined
|
|
1217
|
+
? undefined
|
|
1218
|
+
: { cols: opened.cols, rows: opened.rows };
|
|
1204
1219
|
let resolveClosed;
|
|
1205
1220
|
let rejectClosed;
|
|
1206
1221
|
this.closed = new Promise((resolve, reject) => {
|
|
@@ -1209,111 +1224,225 @@ class SessionTerminalImpl {
|
|
|
1209
1224
|
});
|
|
1210
1225
|
this.resolveClosed = resolveClosed;
|
|
1211
1226
|
this.rejectClosed = rejectClosed;
|
|
1227
|
+
let resolveDetached;
|
|
1228
|
+
let rejectDetached;
|
|
1229
|
+
this.detached = new Promise((resolve, reject) => {
|
|
1230
|
+
resolveDetached = resolve;
|
|
1231
|
+
rejectDetached = reject;
|
|
1232
|
+
});
|
|
1233
|
+
this.resolveDetached = resolveDetached;
|
|
1234
|
+
this.rejectDetached = rejectDetached;
|
|
1212
1235
|
void this.closed.catch(() => undefined);
|
|
1236
|
+
void this.detached.catch(() => undefined);
|
|
1213
1237
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1238
|
+
onResize(listener) {
|
|
1239
|
+
this.resizeListener = listener;
|
|
1240
|
+
if (this.dimensions)
|
|
1241
|
+
queueMicrotask(() => listener(this.dimensions));
|
|
1216
1242
|
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1243
|
+
updateDimensions(cols, rows) {
|
|
1244
|
+
if (this.state === "ended")
|
|
1245
|
+
return;
|
|
1246
|
+
this.dimensions = { cols, rows };
|
|
1247
|
+
this.resizeListener?.(this.dimensions);
|
|
1248
|
+
}
|
|
1249
|
+
readSeed(offset, maxBytes) {
|
|
1250
|
+
if (this.state !== "prepared" || offset !== this.seedOffset) {
|
|
1251
|
+
return Promise.reject(clientError("protocol_error", "Session terminal seed read is out of order"));
|
|
1252
|
+
}
|
|
1253
|
+
return this.requestRead("session.terminal.seed.chunk", {
|
|
1254
|
+
type: "session.terminal.seed.read",
|
|
1255
|
+
attachmentId: this.attachmentId,
|
|
1256
|
+
requestId: this.nextRequestId(),
|
|
1257
|
+
offset,
|
|
1258
|
+
maxBytes: Math.min(maxBytes, REMOTE_RUNTIME_SESSION_TERMINAL_MAX_DATA_BYTES),
|
|
1259
|
+
}).then((result) => {
|
|
1260
|
+
this.seedOffset = result.nextOffset;
|
|
1261
|
+
if (result.done && result.nextOffset !== this.seedBytes) {
|
|
1262
|
+
throw clientError("protocol_error", "Session terminal seed length changed");
|
|
1263
|
+
}
|
|
1264
|
+
return result;
|
|
1232
1265
|
});
|
|
1233
1266
|
}
|
|
1234
|
-
async
|
|
1235
|
-
|
|
1236
|
-
|
|
1267
|
+
async start() {
|
|
1268
|
+
if (this.state !== "prepared" || this.seedOffset !== this.seedBytes) {
|
|
1269
|
+
throw clientError("protocol_error", "Session terminal history must be consumed before start");
|
|
1270
|
+
}
|
|
1271
|
+
this.state = "starting";
|
|
1272
|
+
await this.requestVoid("session.terminal.started", {
|
|
1273
|
+
type: "session.terminal.start",
|
|
1274
|
+
attachmentId: this.attachmentId,
|
|
1275
|
+
requestId: this.nextRequestId(),
|
|
1276
|
+
});
|
|
1277
|
+
if (this.state === "starting")
|
|
1278
|
+
this.state = "started";
|
|
1279
|
+
}
|
|
1280
|
+
read(offset, maxBytes) {
|
|
1281
|
+
if (this.state !== "started" || offset !== this.liveOffset) {
|
|
1282
|
+
return Promise.reject(clientError("protocol_error", "Session terminal read is out of order"));
|
|
1283
|
+
}
|
|
1284
|
+
return this.requestRead("session.terminal.chunk", {
|
|
1285
|
+
type: "session.terminal.read",
|
|
1286
|
+
attachmentId: this.attachmentId,
|
|
1287
|
+
requestId: this.nextRequestId(),
|
|
1288
|
+
offset,
|
|
1289
|
+
maxBytes: Math.min(maxBytes, REMOTE_RUNTIME_SESSION_TERMINAL_MAX_DATA_BYTES),
|
|
1290
|
+
}).then((result) => {
|
|
1291
|
+
this.liveOffset = result.nextOffset;
|
|
1292
|
+
return result;
|
|
1293
|
+
});
|
|
1237
1294
|
}
|
|
1238
1295
|
async write(data) {
|
|
1239
|
-
if (this.
|
|
1296
|
+
if (this.state !== "started" || this.role !== "owner" || data.byteLength === 0)
|
|
1240
1297
|
return;
|
|
1241
|
-
|
|
1298
|
+
for (let offset = 0; offset < data.byteLength; offset += REMOTE_RUNTIME_SESSION_TERMINAL_MAX_DATA_BYTES) {
|
|
1299
|
+
const chunk = data.subarray(offset, Math.min(offset + REMOTE_RUNTIME_SESSION_TERMINAL_MAX_DATA_BYTES, data.byteLength));
|
|
1300
|
+
await this.requestVoid("session.terminal.input.ack", {
|
|
1301
|
+
type: "session.terminal.input",
|
|
1302
|
+
attachmentId: this.attachmentId,
|
|
1303
|
+
requestId: this.nextRequestId(),
|
|
1304
|
+
data: Buffer.from(chunk).toString("base64"),
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1242
1307
|
}
|
|
1243
1308
|
async resize(cols, rows) {
|
|
1244
|
-
if (this.
|
|
1309
|
+
if (this.state !== "started")
|
|
1245
1310
|
return;
|
|
1246
|
-
await this.
|
|
1311
|
+
await this.requestVoid("session.terminal.resize.ack", {
|
|
1312
|
+
type: "session.terminal.resize",
|
|
1313
|
+
attachmentId: this.attachmentId,
|
|
1314
|
+
requestId: this.nextRequestId(),
|
|
1315
|
+
cols,
|
|
1316
|
+
rows,
|
|
1317
|
+
});
|
|
1247
1318
|
}
|
|
1248
1319
|
async close() {
|
|
1249
|
-
if (this.ended)
|
|
1320
|
+
if (this.state === "ended")
|
|
1250
1321
|
return;
|
|
1251
1322
|
await this.requestClose();
|
|
1252
1323
|
}
|
|
1253
|
-
|
|
1254
|
-
if (
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1324
|
+
receive(frame) {
|
|
1325
|
+
if (frame.type === "session.terminal.reader.done") {
|
|
1326
|
+
if (!this.closedSettled) {
|
|
1327
|
+
this.closedSettled = true;
|
|
1328
|
+
this.resolveClosed({
|
|
1329
|
+
reason: frame.reason,
|
|
1330
|
+
exitCode: frame.exitCode,
|
|
1331
|
+
finalOffset: frame.finalOffset,
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1260
1334
|
return;
|
|
1261
1335
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
this.reportBackpressure(clientError("
|
|
1336
|
+
const pending = this.pending.get(frame.requestId);
|
|
1337
|
+
if (!pending || pending.expectedType !== frame.type) {
|
|
1338
|
+
this.reportBackpressure(clientError("protocol_error", "Remote Session terminal response is duplicate or out of order"));
|
|
1265
1339
|
return;
|
|
1266
1340
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1341
|
+
this.pending.delete(frame.requestId);
|
|
1342
|
+
clearTimeout(pending.timer);
|
|
1343
|
+
if (frame.type === "session.terminal.seed.chunk" || frame.type === "session.terminal.chunk") {
|
|
1344
|
+
pending.resolve({
|
|
1345
|
+
data: Uint8Array.from(Buffer.from(frame.data, "base64")),
|
|
1346
|
+
nextOffset: frame.nextOffset,
|
|
1347
|
+
done: frame.done,
|
|
1348
|
+
...(frame.finalOffset === undefined ? {} : { finalOffset: frame.finalOffset }),
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
else
|
|
1352
|
+
pending.resolve(undefined);
|
|
1270
1353
|
}
|
|
1271
1354
|
beginClose() {
|
|
1272
|
-
if (this.ended || this.closing)
|
|
1355
|
+
if (this.state === "ended" || this.state === "closing")
|
|
1273
1356
|
return false;
|
|
1274
|
-
this.
|
|
1275
|
-
this.
|
|
1357
|
+
this.state = "closing";
|
|
1358
|
+
this.rejectPending(clientError("closed", "Remote Session terminal is closing"));
|
|
1276
1359
|
return true;
|
|
1277
1360
|
}
|
|
1278
1361
|
remoteClose(frame) {
|
|
1279
|
-
if (this.ended)
|
|
1362
|
+
if (this.state === "ended")
|
|
1280
1363
|
return;
|
|
1364
|
+
this.state = "ended";
|
|
1281
1365
|
const error = frame.reason === "client_closed" || frame.reason === "terminal_exited"
|
|
1282
1366
|
? undefined
|
|
1283
1367
|
: sessionTerminalError(frame, this.sessionId);
|
|
1284
|
-
this.
|
|
1368
|
+
this.rejectPending(error ?? clientError("closed", "Remote Session terminal detached"));
|
|
1369
|
+
if (!this.closedSettled) {
|
|
1370
|
+
this.closedSettled = true;
|
|
1371
|
+
if (error)
|
|
1372
|
+
this.rejectClosed(error);
|
|
1373
|
+
else
|
|
1374
|
+
this.resolveClosed({ reason: frame.reason, ...(frame.exitCode === undefined ? {} : { exitCode: frame.exitCode }) });
|
|
1375
|
+
}
|
|
1376
|
+
if (!this.detachedSettled) {
|
|
1377
|
+
this.detachedSettled = true;
|
|
1378
|
+
if (error)
|
|
1379
|
+
this.rejectDetached(error);
|
|
1380
|
+
else
|
|
1381
|
+
this.resolveDetached();
|
|
1382
|
+
}
|
|
1285
1383
|
}
|
|
1286
1384
|
terminate(error) {
|
|
1287
|
-
if (this.ended)
|
|
1385
|
+
if (this.state === "ended")
|
|
1288
1386
|
return;
|
|
1289
|
-
this.
|
|
1387
|
+
this.state = "ended";
|
|
1388
|
+
this.rejectPending(error);
|
|
1389
|
+
if (!this.closedSettled) {
|
|
1390
|
+
this.closedSettled = true;
|
|
1391
|
+
this.rejectClosed(error);
|
|
1392
|
+
}
|
|
1393
|
+
if (!this.detachedSettled) {
|
|
1394
|
+
this.detachedSettled = true;
|
|
1395
|
+
this.rejectDetached(error);
|
|
1396
|
+
}
|
|
1290
1397
|
}
|
|
1291
1398
|
waitClosed() {
|
|
1292
|
-
return this.
|
|
1399
|
+
return this.detached;
|
|
1293
1400
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
this.
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
if (waiter) {
|
|
1304
|
-
if (error === undefined)
|
|
1305
|
-
waiter.resolve({ done: true, value: undefined });
|
|
1306
|
-
else
|
|
1307
|
-
waiter.reject(error);
|
|
1401
|
+
requestRead(expectedType, frame) {
|
|
1402
|
+
return this.request(expectedType, frame);
|
|
1403
|
+
}
|
|
1404
|
+
requestVoid(expectedType, frame) {
|
|
1405
|
+
return this.request(expectedType, frame).then(() => undefined);
|
|
1406
|
+
}
|
|
1407
|
+
request(expectedType, frame) {
|
|
1408
|
+
if (!("requestId" in frame)) {
|
|
1409
|
+
return Promise.reject(clientError("protocol_error", "terminal request id is missing"));
|
|
1308
1410
|
}
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1411
|
+
const pull = expectedType === "session.terminal.seed.chunk" || expectedType === "session.terminal.chunk";
|
|
1412
|
+
const sameClassPending = [...this.pending.values()].some((pending) => {
|
|
1413
|
+
const pendingPull = pending.expectedType === "session.terminal.seed.chunk" ||
|
|
1414
|
+
pending.expectedType === "session.terminal.chunk";
|
|
1415
|
+
return pendingPull === pull;
|
|
1416
|
+
});
|
|
1417
|
+
if (sameClassPending) {
|
|
1418
|
+
return Promise.reject(clientError("protocol_error", pull ? "concurrent terminal pulls are not allowed" : "concurrent terminal controls are not allowed"));
|
|
1419
|
+
}
|
|
1420
|
+
return new Promise((resolve, reject) => {
|
|
1421
|
+
const timer = setTimeout(() => {
|
|
1422
|
+
this.pending.delete(frame.requestId);
|
|
1423
|
+
reject(clientError("deadline_exceeded", "Remote Session terminal operation timed out"));
|
|
1424
|
+
}, this.timeoutMs);
|
|
1425
|
+
timer.unref?.();
|
|
1426
|
+
this.pending.set(frame.requestId, { expectedType, resolve, reject, timer });
|
|
1427
|
+
void this.sendFrame(frame).catch((error) => {
|
|
1428
|
+
const pending = this.pending.get(frame.requestId);
|
|
1429
|
+
if (!pending)
|
|
1430
|
+
return;
|
|
1431
|
+
this.pending.delete(frame.requestId);
|
|
1432
|
+
clearTimeout(pending.timer);
|
|
1433
|
+
pending.reject(error);
|
|
1434
|
+
});
|
|
1435
|
+
});
|
|
1313
1436
|
}
|
|
1314
|
-
|
|
1315
|
-
this.
|
|
1316
|
-
|
|
1437
|
+
rejectPending(error) {
|
|
1438
|
+
for (const pending of this.pending.values()) {
|
|
1439
|
+
clearTimeout(pending.timer);
|
|
1440
|
+
pending.reject(error);
|
|
1441
|
+
}
|
|
1442
|
+
this.pending.clear();
|
|
1443
|
+
}
|
|
1444
|
+
nextRequestId() {
|
|
1445
|
+
return `treq_${randomBytes(12).toString("base64url")}`;
|
|
1317
1446
|
}
|
|
1318
1447
|
}
|
|
1319
1448
|
function sessionTerminalError(frame, sessionId) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rynx-ai/remote-runtime-client",
|
|
3
|
-
"version": "0.1.11-beta.
|
|
3
|
+
"version": "0.1.11-beta.40",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/rynx-ai/rynx.git",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"ws": "^8.21.0",
|
|
28
|
-
"@rynx-ai/protocol": "0.1.11-beta.
|
|
29
|
-
"@rynx-ai/remote-runtime-e2ee": "0.1.11-beta.
|
|
28
|
+
"@rynx-ai/protocol": "0.1.11-beta.40",
|
|
29
|
+
"@rynx-ai/remote-runtime-e2ee": "0.1.11-beta.40"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/ws": "^8.18.1"
|