ds-harness-remote 0.3.36 → 0.4.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.
- package/README.md +26 -24
- package/dist/client-runtime.d.ts +9 -4
- package/dist/client-runtime.d.ts.map +1 -1
- package/dist/client-runtime.js +140 -23
- package/dist/client-runtime.js.map +1 -1
- package/dist/harness-api-bridge.d.ts +2 -9
- package/dist/harness-api-bridge.d.ts.map +1 -1
- package/dist/harness-api-bridge.js.map +1 -1
- package/dist/harness-remote-bridge.d.ts +46 -0
- package/dist/harness-remote-bridge.d.ts.map +1 -0
- package/dist/harness-remote-bridge.js +321 -0
- package/dist/harness-remote-bridge.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1066 -83
- package/dist/index.js.map +4 -4
- package/dist/native-rtc-helper.d.ts +10 -0
- package/dist/native-rtc-helper.d.ts.map +1 -1
- package/dist/native-rtc-helper.js +21 -3
- package/dist/native-rtc-helper.js.map +1 -1
- package/dist/remote-typert-gateway.d.ts +13 -0
- package/dist/remote-typert-gateway.d.ts.map +1 -0
- package/dist/remote-typert-gateway.js +242 -0
- package/dist/remote-typert-gateway.js.map +1 -0
- package/dist/rpc-router.d.ts +7 -2
- package/dist/rpc-router.d.ts.map +1 -1
- package/dist/rpc-router.js +61 -12
- package/dist/rpc-router.js.map +1 -1
- package/dist/server-connection.d.ts +5 -0
- package/dist/server-connection.d.ts.map +1 -1
- package/dist/server-connection.js +120 -5
- package/dist/server-connection.js.map +1 -1
- package/dist/service.d.ts +5 -3
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +26 -8
- package/dist/service.js.map +1 -1
- package/dist/typert-gateway-contract.d.ts +48 -0
- package/dist/typert-gateway-contract.d.ts.map +1 -0
- package/dist/typert-gateway-contract.js +2 -0
- package/dist/typert-gateway-contract.js.map +1 -0
- package/dist/typert-gateway-switch.d.ts +28 -7
- package/dist/typert-gateway-switch.d.ts.map +1 -1
- package/dist/typert-gateway-switch.js +121 -23
- package/dist/typert-gateway-switch.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +13 -8
package/dist/index.js
CHANGED
|
@@ -4082,6 +4082,7 @@ var controlFrameTypes = [
|
|
|
4082
4082
|
"error"
|
|
4083
4083
|
];
|
|
4084
4084
|
var rpcMethods = [
|
|
4085
|
+
"harness.transport.describe",
|
|
4085
4086
|
"harness.api.call",
|
|
4086
4087
|
"harness.api.transfer.open",
|
|
4087
4088
|
"harness.api.transfer.chunk",
|
|
@@ -4091,6 +4092,14 @@ var rpcMethods = [
|
|
|
4091
4092
|
"harness.api.respond",
|
|
4092
4093
|
"harness.api.stream.open",
|
|
4093
4094
|
"harness.api.stream.close",
|
|
4095
|
+
"harness.remote.call",
|
|
4096
|
+
"harness.remote.transfer.open",
|
|
4097
|
+
"harness.remote.transfer.chunk",
|
|
4098
|
+
"harness.remote.transfer.commit",
|
|
4099
|
+
"harness.remote.transfer.read",
|
|
4100
|
+
"harness.remote.transfer.close",
|
|
4101
|
+
"harness.remote.stream.open",
|
|
4102
|
+
"harness.remote.stream.close",
|
|
4094
4103
|
"fileviewer.call"
|
|
4095
4104
|
];
|
|
4096
4105
|
function normalizeSdpMLineIndex(value) {
|
|
@@ -4102,9 +4111,22 @@ function normalizeSdpMLineIndex(value) {
|
|
|
4102
4111
|
return null;
|
|
4103
4112
|
return value;
|
|
4104
4113
|
}
|
|
4114
|
+
function selectCapabilities(offered, supported) {
|
|
4115
|
+
const offeredCapabilities = new Set(offered);
|
|
4116
|
+
return [...new Set(supported)].filter((capability) => offeredCapabilities.has(capability));
|
|
4117
|
+
}
|
|
4118
|
+
function acceptNegotiatedCapabilities(offered, negotiated) {
|
|
4119
|
+
const accepted = negotiated ?? ["transport.relay"];
|
|
4120
|
+
if (selectCapabilities(accepted, offered).length !== accepted.length) {
|
|
4121
|
+
throw new Error("Server selected a capability that the peer did not offer.");
|
|
4122
|
+
}
|
|
4123
|
+
return [...accepted];
|
|
4124
|
+
}
|
|
4105
4125
|
var rpcMethodSchema = external_exports.enum(rpcMethods);
|
|
4106
4126
|
var messageTypeSchema = external_exports.enum(messageTypes);
|
|
4107
4127
|
var controlFrameTypeSchema = external_exports.enum(controlFrameTypes);
|
|
4128
|
+
var uniqueStrings = (values) => new Set(values).size === values.length;
|
|
4129
|
+
var uniqueNumbers = (values) => new Set(values).size === values.length;
|
|
4108
4130
|
var remoteMessageSchema = external_exports.object({
|
|
4109
4131
|
v: external_exports.literal(PROTOCOL_VERSION),
|
|
4110
4132
|
id: external_exports.string().min(1),
|
|
@@ -4125,8 +4147,8 @@ var helloPayloadSchema = external_exports.object({
|
|
|
4125
4147
|
role: external_exports.enum(["host", "client"]),
|
|
4126
4148
|
deviceId: external_exports.string().min(1),
|
|
4127
4149
|
accessToken: external_exports.string().min(1),
|
|
4128
|
-
protocols: external_exports.array(external_exports.number().int()).min(1),
|
|
4129
|
-
capabilities: external_exports.array(external_exports.string()),
|
|
4150
|
+
protocols: external_exports.array(external_exports.number().int().nonnegative().safe()).min(1).refine(uniqueNumbers),
|
|
4151
|
+
capabilities: external_exports.array(external_exports.string().min(1)).refine(uniqueStrings),
|
|
4130
4152
|
clientVersion: external_exports.string().optional(),
|
|
4131
4153
|
harnessVersion: external_exports.string().optional()
|
|
4132
4154
|
});
|
|
@@ -4137,6 +4159,7 @@ var helloAckPayloadSchema = external_exports.object({
|
|
|
4137
4159
|
heartbeatIntervalMs: external_exports.number().int().positive(),
|
|
4138
4160
|
maxControlFrameBytes: external_exports.number().int().positive(),
|
|
4139
4161
|
maxRelayFrameBytes: external_exports.number().int().positive(),
|
|
4162
|
+
capabilities: external_exports.array(external_exports.string().min(1)).refine(uniqueStrings).optional(),
|
|
4140
4163
|
webrtcEnabled: external_exports.boolean().optional(),
|
|
4141
4164
|
webrtcFallbackTimeoutMs: external_exports.number().int().positive().optional()
|
|
4142
4165
|
});
|
|
@@ -4168,7 +4191,7 @@ var secureHandshakePayloadSchema = external_exports.object({
|
|
|
4168
4191
|
var relayPayloadSchema = external_exports.object({
|
|
4169
4192
|
connectionId: external_exports.string().min(1),
|
|
4170
4193
|
targetDeviceId: external_exports.string().min(1),
|
|
4171
|
-
counter: external_exports.number().int().nonnegative(),
|
|
4194
|
+
counter: external_exports.number().int().nonnegative().safe(),
|
|
4172
4195
|
ciphertext: external_exports.string().min(1)
|
|
4173
4196
|
});
|
|
4174
4197
|
var signalPayloadSchema = external_exports.object({
|
|
@@ -4375,6 +4398,9 @@ function cryptoRandomId() {
|
|
|
4375
4398
|
return `msg_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
|
|
4376
4399
|
}
|
|
4377
4400
|
|
|
4401
|
+
// ../client-core/dist/remote-gateway.js
|
|
4402
|
+
var DIRECT_REMOTE_CALL_BYTES = 2 * 1024 * 1024;
|
|
4403
|
+
|
|
4378
4404
|
// ../client-core/dist/index.js
|
|
4379
4405
|
var RemoteClientError = class extends Error {
|
|
4380
4406
|
code;
|
|
@@ -4916,6 +4942,8 @@ function isChunk(frame) {
|
|
|
4916
4942
|
|
|
4917
4943
|
// ../webrtc/dist/rtc-data-channel.js
|
|
4918
4944
|
var DEFAULT_NEGOTIATE_TIMEOUT_MS = 8e3;
|
|
4945
|
+
var SELECTED_PATH_RETRY_COUNT = 5;
|
|
4946
|
+
var SELECTED_PATH_RETRY_DELAY_MS = 50;
|
|
4919
4947
|
var RtcDataChannelTransport = class {
|
|
4920
4948
|
pc;
|
|
4921
4949
|
role;
|
|
@@ -5245,7 +5273,11 @@ var RtcDataChannelTransport = class {
|
|
|
5245
5273
|
}
|
|
5246
5274
|
async resolveSelectedTransport() {
|
|
5247
5275
|
try {
|
|
5248
|
-
|
|
5276
|
+
let selected = await this.refreshStatsDiagnostics();
|
|
5277
|
+
for (let attempt = 0; attempt < SELECTED_PATH_RETRY_COUNT && !this.closed && (selected.transport === void 0 || selected.mode === void 0); attempt += 1) {
|
|
5278
|
+
await sleep(SELECTED_PATH_RETRY_DELAY_MS);
|
|
5279
|
+
selected = await this.refreshStatsDiagnostics();
|
|
5280
|
+
}
|
|
5249
5281
|
this.selected = selected?.transport;
|
|
5250
5282
|
this.selectedMode = selected?.mode;
|
|
5251
5283
|
this.selectedPathTelemetry = selectedPathTelemetry(selected);
|
|
@@ -5409,6 +5441,8 @@ function inspectSelectedPath(stats) {
|
|
|
5409
5441
|
const remote = candidates.get(String(pair.remoteCandidateId));
|
|
5410
5442
|
const localType = local?.candidateType;
|
|
5411
5443
|
const remoteType = remote?.candidateType;
|
|
5444
|
+
const localAddressScope = summarizeAddress(candidateAddressValue(local)).addressScope;
|
|
5445
|
+
const remoteAddressScope = summarizeAddress(candidateAddressValue(remote)).addressScope;
|
|
5412
5446
|
let selected;
|
|
5413
5447
|
if (localType === "relay" || remoteType === "relay")
|
|
5414
5448
|
selected = { transport: "turn", mode: "TURN" };
|
|
@@ -5416,13 +5450,15 @@ function inspectSelectedPath(stats) {
|
|
|
5416
5450
|
continue;
|
|
5417
5451
|
selected ??= {
|
|
5418
5452
|
transport: "p2p",
|
|
5419
|
-
mode: localType
|
|
5453
|
+
mode: isLanCandidatePair(localType, remoteType, localAddressScope, remoteAddressScope) ? "LAN" : "P2P"
|
|
5420
5454
|
};
|
|
5421
5455
|
const currentRoundTripTime = numberStat(pair, "currentRoundTripTime");
|
|
5422
5456
|
return {
|
|
5423
5457
|
...selected,
|
|
5424
5458
|
localCandidateType: stringStat(local, "candidateType"),
|
|
5425
5459
|
remoteCandidateType: stringStat(remote, "candidateType"),
|
|
5460
|
+
localAddressScope,
|
|
5461
|
+
remoteAddressScope,
|
|
5426
5462
|
localAddress: candidateAddress(local),
|
|
5427
5463
|
remoteAddress: candidateAddress(remote),
|
|
5428
5464
|
protocol: stringStat(local, "protocol") ?? stringStat(remote, "protocol"),
|
|
@@ -5547,6 +5583,19 @@ function candidateTypeStat(candidate) {
|
|
|
5547
5583
|
function candidateAddressValue(candidate) {
|
|
5548
5584
|
return stringStat(candidate, "address") ?? stringStat(candidate, "ip");
|
|
5549
5585
|
}
|
|
5586
|
+
function isLanCandidatePair(localType, remoteType, localScope, remoteScope) {
|
|
5587
|
+
if (localType === "host" && remoteType === "host")
|
|
5588
|
+
return true;
|
|
5589
|
+
const directTypes = /* @__PURE__ */ new Set(["host", "prflx"]);
|
|
5590
|
+
if (!directTypes.has(String(localType)) || !directTypes.has(String(remoteType)))
|
|
5591
|
+
return false;
|
|
5592
|
+
if (isLocalNetworkScope(localScope) && isLocalNetworkScope(remoteScope))
|
|
5593
|
+
return true;
|
|
5594
|
+
return localType === "host" && isLocalNetworkScope(localScope) && remoteType === "prflx" && remoteScope === "unknown" || remoteType === "host" && isLocalNetworkScope(remoteScope) && localType === "prflx" && localScope === "unknown";
|
|
5595
|
+
}
|
|
5596
|
+
function isLocalNetworkScope(scope) {
|
|
5597
|
+
return scope === "private" || scope === "link-local";
|
|
5598
|
+
}
|
|
5550
5599
|
function increment(map, key) {
|
|
5551
5600
|
map[key] = (map[key] ?? 0) + 1;
|
|
5552
5601
|
}
|
|
@@ -5556,6 +5605,8 @@ function selectedPathTelemetry(details) {
|
|
|
5556
5605
|
...details.mode === void 0 ? {} : { mode: details.mode },
|
|
5557
5606
|
...details.localCandidateType === void 0 ? {} : { localCandidateType: details.localCandidateType },
|
|
5558
5607
|
...details.remoteCandidateType === void 0 ? {} : { remoteCandidateType: details.remoteCandidateType },
|
|
5608
|
+
...details.localAddressScope === void 0 ? {} : { localAddressScope: details.localAddressScope },
|
|
5609
|
+
...details.remoteAddressScope === void 0 ? {} : { remoteAddressScope: details.remoteAddressScope },
|
|
5559
5610
|
...details.protocol === void 0 ? {} : { protocol: details.protocol },
|
|
5560
5611
|
...details.relayProtocol === void 0 ? {} : { relayProtocol: details.relayProtocol },
|
|
5561
5612
|
...details.currentRoundTripTimeMs === void 0 ? {} : { currentRoundTripTimeMs: details.currentRoundTripTimeMs },
|
|
@@ -5600,6 +5651,7 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5600
5651
|
handshakeTimer;
|
|
5601
5652
|
webrtcEnabled = true;
|
|
5602
5653
|
serverNegotiateTimeoutMs;
|
|
5654
|
+
negotiatedCapabilities = ["transport.relay"];
|
|
5603
5655
|
connectedAt;
|
|
5604
5656
|
lastRtcDiagnostics;
|
|
5605
5657
|
constructor(url, options) {
|
|
@@ -5678,6 +5730,8 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5678
5730
|
controlChannelUrl: this.url,
|
|
5679
5731
|
controlChannelState: socketState(this.socket?.readyState),
|
|
5680
5732
|
preferredTransports: this.options.forceRelay === true ? ["relay"] : [...this.options.preferredTransports ?? DEFAULT_PREFERRED_TRANSPORTS],
|
|
5733
|
+
negotiatedCapabilities: [...this.negotiatedCapabilities],
|
|
5734
|
+
webRtcEnabled: this.webrtcEnabled,
|
|
5681
5735
|
...this.rtc === void 0 ? {} : { webRtc: await this.rtc.connectionDetails() }
|
|
5682
5736
|
};
|
|
5683
5737
|
}
|
|
@@ -5724,6 +5778,12 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5724
5778
|
const payload = frame.payload;
|
|
5725
5779
|
if (payload.protocol !== PROTOCOL_VERSION)
|
|
5726
5780
|
throw new Error("Server selected an unsupported protocol version");
|
|
5781
|
+
const offered = this.options.capabilities ?? DEFAULT_CAPABILITIES;
|
|
5782
|
+
this.negotiatedCapabilities = acceptNegotiatedCapabilities(offered, payload.capabilities);
|
|
5783
|
+
const preferredTransports = this.negotiatedTransports();
|
|
5784
|
+
if (preferredTransports.length === 0) {
|
|
5785
|
+
throw new Error("Server did not negotiate a supported transport capability");
|
|
5786
|
+
}
|
|
5727
5787
|
if (payload.webrtcEnabled === false)
|
|
5728
5788
|
this.webrtcEnabled = false;
|
|
5729
5789
|
if (typeof payload.webrtcFallbackTimeoutMs === "number" && Number.isSafeInteger(payload.webrtcFallbackTimeoutMs) && payload.webrtcFallbackTimeoutMs > 0) {
|
|
@@ -5731,7 +5791,7 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5731
5791
|
}
|
|
5732
5792
|
this.sendControl("connect.request", {
|
|
5733
5793
|
hostDeviceId: this.options.targetDeviceId,
|
|
5734
|
-
preferredTransports
|
|
5794
|
+
preferredTransports
|
|
5735
5795
|
});
|
|
5736
5796
|
return;
|
|
5737
5797
|
}
|
|
@@ -5741,7 +5801,9 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5741
5801
|
throw new Error("connect.accepted did not include a connectionId");
|
|
5742
5802
|
}
|
|
5743
5803
|
this.connectionId = payload.connectionId;
|
|
5744
|
-
void this.negotiate()
|
|
5804
|
+
void this.negotiate().catch((error) => {
|
|
5805
|
+
this.failConnection(error instanceof Error ? error : new Error("Adaptive transport negotiation failed"));
|
|
5806
|
+
});
|
|
5745
5807
|
return;
|
|
5746
5808
|
}
|
|
5747
5809
|
if (frame.type === "connect.rejected" || frame.type === "error") {
|
|
@@ -5798,15 +5860,25 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5798
5860
|
async negotiate() {
|
|
5799
5861
|
if (this.connectionId === void 0)
|
|
5800
5862
|
return;
|
|
5801
|
-
const preferred = this.
|
|
5863
|
+
const preferred = this.negotiatedTransports();
|
|
5802
5864
|
const wantWebRtc = !this.options.forceRelay && this.webrtcEnabled && (preferred.includes("lan") || preferred.includes("p2p") || preferred.includes("turn"));
|
|
5803
5865
|
let selected = "relay";
|
|
5804
5866
|
this.lastRtcDiagnostics = void 0;
|
|
5805
5867
|
if (wantWebRtc) {
|
|
5806
5868
|
try {
|
|
5807
5869
|
const rtcSelected = await this.tryWebRtc();
|
|
5808
|
-
|
|
5809
|
-
|
|
5870
|
+
if (!preferred.includes(rtcSelected)) {
|
|
5871
|
+
await this.rtc?.close();
|
|
5872
|
+
this.rtc = void 0;
|
|
5873
|
+
if (!preferred.includes("relay")) {
|
|
5874
|
+
throw new Error(`WebRTC selected unnegotiated transport: ${rtcSelected}`);
|
|
5875
|
+
}
|
|
5876
|
+
this.dataMode = "relay";
|
|
5877
|
+
selected = "relay";
|
|
5878
|
+
} else {
|
|
5879
|
+
this.dataMode = "webrtc";
|
|
5880
|
+
selected = rtcSelected;
|
|
5881
|
+
}
|
|
5810
5882
|
} catch (error) {
|
|
5811
5883
|
const reason = error instanceof Error ? error : new Error("WebRTC negotiation failed.");
|
|
5812
5884
|
try {
|
|
@@ -5815,10 +5887,14 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5815
5887
|
}
|
|
5816
5888
|
await this.rtc?.close();
|
|
5817
5889
|
this.rtc = void 0;
|
|
5890
|
+
if (!preferred.includes("relay"))
|
|
5891
|
+
throw reason;
|
|
5818
5892
|
this.dataMode = "relay";
|
|
5819
5893
|
selected = "relay";
|
|
5820
5894
|
}
|
|
5821
5895
|
} else {
|
|
5896
|
+
if (!preferred.includes("relay"))
|
|
5897
|
+
throw new Error("No negotiated transport is available");
|
|
5822
5898
|
this.dataMode = "relay";
|
|
5823
5899
|
selected = "relay";
|
|
5824
5900
|
}
|
|
@@ -5830,6 +5906,14 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
5830
5906
|
});
|
|
5831
5907
|
this.finishConnection();
|
|
5832
5908
|
}
|
|
5909
|
+
negotiatedTransports() {
|
|
5910
|
+
const preferred = this.options.forceRelay === true ? ["relay"] : this.options.preferredTransports ?? DEFAULT_PREFERRED_TRANSPORTS;
|
|
5911
|
+
return preferred.filter((transport) => {
|
|
5912
|
+
if (transport === "lan" || transport === "p2p")
|
|
5913
|
+
return this.negotiatedCapabilities.includes("transport.p2p");
|
|
5914
|
+
return this.negotiatedCapabilities.includes(`transport.${transport}`);
|
|
5915
|
+
});
|
|
5916
|
+
}
|
|
5833
5917
|
async tryWebRtc() {
|
|
5834
5918
|
const connectionId = this.connectionId;
|
|
5835
5919
|
if (connectionId === void 0)
|
|
@@ -13460,6 +13544,224 @@ function base64ToBytes(value) {
|
|
|
13460
13544
|
return bytes;
|
|
13461
13545
|
}
|
|
13462
13546
|
|
|
13547
|
+
// src/remote-typert-gateway.ts
|
|
13548
|
+
var DIRECT_REMOTE_CALL_BYTES2 = 2 * 1024 * 1024;
|
|
13549
|
+
var RemoteTypertGateway2 = class {
|
|
13550
|
+
constructor(client) {
|
|
13551
|
+
this.client = client;
|
|
13552
|
+
}
|
|
13553
|
+
async invoke(request) {
|
|
13554
|
+
const result = await this.dispatch(
|
|
13555
|
+
`${request.namespace}/${request.method}`,
|
|
13556
|
+
{ args: request.args },
|
|
13557
|
+
request.signal ?? new AbortController().signal
|
|
13558
|
+
);
|
|
13559
|
+
if (result.ok) return result.value;
|
|
13560
|
+
throw remoteFailure(result.error);
|
|
13561
|
+
}
|
|
13562
|
+
async dispatch(endpoint, payload, signal) {
|
|
13563
|
+
const request = { endpoint, payload };
|
|
13564
|
+
const encoded = new TextEncoder().encode(JSON.stringify(request));
|
|
13565
|
+
let response;
|
|
13566
|
+
if (encoded.byteLength > DIRECT_REMOTE_CALL_BYTES2) {
|
|
13567
|
+
response = await this.callTransferred(encoded, signal);
|
|
13568
|
+
} else {
|
|
13569
|
+
try {
|
|
13570
|
+
response = await this.client.rpc("harness.remote.call", request, signal);
|
|
13571
|
+
} catch (error) {
|
|
13572
|
+
if (!hasErrorCode(error, "RESPONSE_TOO_LARGE")) throw error;
|
|
13573
|
+
response = await this.callTransferred(encoded, signal);
|
|
13574
|
+
}
|
|
13575
|
+
}
|
|
13576
|
+
return parseRpcResult(response);
|
|
13577
|
+
}
|
|
13578
|
+
async open(endpoint, payload, signal) {
|
|
13579
|
+
const streamId = uuidV7();
|
|
13580
|
+
const queue = new AsyncValueQueue();
|
|
13581
|
+
const unsubscribe = this.client.onEvent((event) => routeStreamEvent2(event, streamId, queue));
|
|
13582
|
+
const unsubscribeClose = this.client.onClose(() => {
|
|
13583
|
+
queue.fail(new RemoteClientError("TRANSPORT_CLOSED", "The remote Harness stream transport closed."));
|
|
13584
|
+
});
|
|
13585
|
+
const onAbort = () => queue.close();
|
|
13586
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
13587
|
+
try {
|
|
13588
|
+
await this.client.rpc("harness.remote.stream.open", { streamId, endpoint, payload }, signal);
|
|
13589
|
+
} catch (error) {
|
|
13590
|
+
signal.removeEventListener("abort", onAbort);
|
|
13591
|
+
unsubscribe();
|
|
13592
|
+
unsubscribeClose();
|
|
13593
|
+
throw error;
|
|
13594
|
+
}
|
|
13595
|
+
return this.iterate(streamId, queue, unsubscribe, unsubscribeClose, signal, onAbort);
|
|
13596
|
+
}
|
|
13597
|
+
async *iterate(streamId, queue, unsubscribe, unsubscribeClose, signal, onAbort) {
|
|
13598
|
+
try {
|
|
13599
|
+
yield* queue;
|
|
13600
|
+
} finally {
|
|
13601
|
+
signal.removeEventListener("abort", onAbort);
|
|
13602
|
+
unsubscribe();
|
|
13603
|
+
unsubscribeClose();
|
|
13604
|
+
queue.close();
|
|
13605
|
+
await this.client.rpc("harness.remote.stream.close", { streamId }).catch(() => void 0);
|
|
13606
|
+
}
|
|
13607
|
+
}
|
|
13608
|
+
async callTransferred(encoded, signal) {
|
|
13609
|
+
if (encoded.byteLength > MAX_HARNESS_API_TRANSFER_BYTES) {
|
|
13610
|
+
throw new Error("The Harness Remote request exceeds the transfer limit.");
|
|
13611
|
+
}
|
|
13612
|
+
const transferId = uuidV7();
|
|
13613
|
+
const totalChunks = Math.ceil(encoded.byteLength / HARNESS_API_TRANSFER_CHUNK_BYTES);
|
|
13614
|
+
let opened = false;
|
|
13615
|
+
try {
|
|
13616
|
+
await this.client.rpc("harness.remote.transfer.open", {
|
|
13617
|
+
transferId,
|
|
13618
|
+
totalBytes: encoded.byteLength,
|
|
13619
|
+
totalChunks
|
|
13620
|
+
}, signal);
|
|
13621
|
+
opened = true;
|
|
13622
|
+
for (let index = 0; index < totalChunks; index += 1) {
|
|
13623
|
+
const start = index * HARNESS_API_TRANSFER_CHUNK_BYTES;
|
|
13624
|
+
const chunk = encoded.subarray(start, Math.min(start + HARNESS_API_TRANSFER_CHUNK_BYTES, encoded.byteLength));
|
|
13625
|
+
await this.client.rpc("harness.remote.transfer.chunk", {
|
|
13626
|
+
transferId,
|
|
13627
|
+
index,
|
|
13628
|
+
data: bytesToBase642(chunk)
|
|
13629
|
+
}, signal);
|
|
13630
|
+
}
|
|
13631
|
+
const committed = await this.client.rpc(
|
|
13632
|
+
"harness.remote.transfer.commit",
|
|
13633
|
+
{ transferId },
|
|
13634
|
+
signal
|
|
13635
|
+
);
|
|
13636
|
+
if (committed.kind === "inline") return committed.response;
|
|
13637
|
+
if (committed.transferId !== transferId || committed.totalBytes <= 0 || committed.totalBytes > MAX_HARNESS_API_TRANSFER_BYTES || committed.totalChunks !== Math.ceil(committed.totalBytes / HARNESS_API_TRANSFER_CHUNK_BYTES)) {
|
|
13638
|
+
throw new Error("The remote Host returned an invalid Harness Remote transfer descriptor.");
|
|
13639
|
+
}
|
|
13640
|
+
const responseBytes = new Uint8Array(committed.totalBytes);
|
|
13641
|
+
let offset = 0;
|
|
13642
|
+
for (let index = 0; index < committed.totalChunks; index += 1) {
|
|
13643
|
+
const result = await this.client.rpc(
|
|
13644
|
+
"harness.remote.transfer.read",
|
|
13645
|
+
{ transferId, index },
|
|
13646
|
+
signal
|
|
13647
|
+
);
|
|
13648
|
+
if (result.transferId !== transferId || result.index !== index) {
|
|
13649
|
+
throw new Error("The remote Host returned an out-of-order Harness Remote transfer chunk.");
|
|
13650
|
+
}
|
|
13651
|
+
const chunk = base64ToBytes2(result.data);
|
|
13652
|
+
const expectedBytes = Math.min(HARNESS_API_TRANSFER_CHUNK_BYTES, committed.totalBytes - offset);
|
|
13653
|
+
if (chunk.byteLength !== expectedBytes) {
|
|
13654
|
+
throw new Error("The remote Host returned an invalid Harness Remote transfer chunk.");
|
|
13655
|
+
}
|
|
13656
|
+
responseBytes.set(chunk, offset);
|
|
13657
|
+
offset += chunk.byteLength;
|
|
13658
|
+
}
|
|
13659
|
+
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(responseBytes));
|
|
13660
|
+
} finally {
|
|
13661
|
+
if (opened) await this.client.rpc("harness.remote.transfer.close", { transferId }).catch(() => void 0);
|
|
13662
|
+
}
|
|
13663
|
+
}
|
|
13664
|
+
};
|
|
13665
|
+
var AsyncValueQueue = class {
|
|
13666
|
+
values = [];
|
|
13667
|
+
waiters = [];
|
|
13668
|
+
closed = false;
|
|
13669
|
+
error;
|
|
13670
|
+
push(value) {
|
|
13671
|
+
if (this.closed) return;
|
|
13672
|
+
const waiter = this.waiters.shift();
|
|
13673
|
+
if (waiter === void 0) this.values.push(value);
|
|
13674
|
+
else waiter({ done: false, value });
|
|
13675
|
+
}
|
|
13676
|
+
close() {
|
|
13677
|
+
if (this.closed) return;
|
|
13678
|
+
this.closed = true;
|
|
13679
|
+
for (const waiter of this.waiters.splice(0)) waiter({ done: true, value: void 0 });
|
|
13680
|
+
}
|
|
13681
|
+
fail(error) {
|
|
13682
|
+
if (this.closed) return;
|
|
13683
|
+
this.error = error;
|
|
13684
|
+
this.close();
|
|
13685
|
+
}
|
|
13686
|
+
async *[Symbol.asyncIterator]() {
|
|
13687
|
+
while (true) {
|
|
13688
|
+
if (this.values.length > 0) {
|
|
13689
|
+
yield this.values.shift();
|
|
13690
|
+
continue;
|
|
13691
|
+
}
|
|
13692
|
+
if (this.closed) {
|
|
13693
|
+
if (this.error !== void 0) throw this.error;
|
|
13694
|
+
return;
|
|
13695
|
+
}
|
|
13696
|
+
const next = await new Promise((resolve2) => this.waiters.push(resolve2));
|
|
13697
|
+
if (next.done) {
|
|
13698
|
+
if (this.error !== void 0) throw this.error;
|
|
13699
|
+
return;
|
|
13700
|
+
}
|
|
13701
|
+
yield next.value;
|
|
13702
|
+
}
|
|
13703
|
+
}
|
|
13704
|
+
};
|
|
13705
|
+
function routeStreamEvent2(event, streamId, queue) {
|
|
13706
|
+
if (event.event === "harness.remote.frame") {
|
|
13707
|
+
const data2 = event.data;
|
|
13708
|
+
if (data2.streamId === streamId && data2.hasValue === true) queue.push(data2.value);
|
|
13709
|
+
return;
|
|
13710
|
+
}
|
|
13711
|
+
if (event.event !== "harness.remote.stream.closed") return;
|
|
13712
|
+
const data = event.data;
|
|
13713
|
+
if (data.streamId !== streamId) return;
|
|
13714
|
+
if (data.reason === "failed") {
|
|
13715
|
+
const failure = data.failure;
|
|
13716
|
+
queue.fail(remoteFailure({
|
|
13717
|
+
code: typeof failure?.code === "string" ? failure.code : "internal",
|
|
13718
|
+
message: typeof failure?.message === "string" ? failure.message : "The remote Harness stream failed.",
|
|
13719
|
+
details: isRecord(failure?.details) ? failure.details : {}
|
|
13720
|
+
}));
|
|
13721
|
+
} else {
|
|
13722
|
+
queue.close();
|
|
13723
|
+
}
|
|
13724
|
+
}
|
|
13725
|
+
function parseRpcResult(value) {
|
|
13726
|
+
if (!isRecord(value)) throw new Error("The remote Host returned an invalid Gateway result.");
|
|
13727
|
+
if (value.ok === true) return Object.hasOwn(value, "value") ? { ok: true, value: value.value } : { ok: true };
|
|
13728
|
+
if (value.ok !== false || !isRecord(value.error) || typeof value.error.code !== "string" || typeof value.error.message !== "string" || !isRecord(value.error.details)) {
|
|
13729
|
+
throw new Error("The remote Host returned an invalid Gateway failure.");
|
|
13730
|
+
}
|
|
13731
|
+
return {
|
|
13732
|
+
ok: false,
|
|
13733
|
+
error: { code: value.error.code, message: value.error.message, details: value.error.details }
|
|
13734
|
+
};
|
|
13735
|
+
}
|
|
13736
|
+
function remoteFailure(failure) {
|
|
13737
|
+
return Object.assign(new Error(failure.message), { code: failure.code, details: failure.details });
|
|
13738
|
+
}
|
|
13739
|
+
function bytesToBase642(bytes) {
|
|
13740
|
+
let binary = "";
|
|
13741
|
+
for (let offset = 0; offset < bytes.byteLength; offset += 32768) {
|
|
13742
|
+
binary += String.fromCharCode(...bytes.subarray(offset, Math.min(offset + 32768, bytes.byteLength)));
|
|
13743
|
+
}
|
|
13744
|
+
return btoa(binary);
|
|
13745
|
+
}
|
|
13746
|
+
function base64ToBytes2(value) {
|
|
13747
|
+
let binary;
|
|
13748
|
+
try {
|
|
13749
|
+
binary = atob(value);
|
|
13750
|
+
} catch {
|
|
13751
|
+
throw new Error("The remote Host returned malformed Harness Remote transfer data.");
|
|
13752
|
+
}
|
|
13753
|
+
const bytes = new Uint8Array(binary.length);
|
|
13754
|
+
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
13755
|
+
if (bytesToBase642(bytes) !== value) throw new Error("The remote Host returned non-canonical Harness Remote transfer data.");
|
|
13756
|
+
return bytes;
|
|
13757
|
+
}
|
|
13758
|
+
function isRecord(value) {
|
|
13759
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13760
|
+
}
|
|
13761
|
+
function hasErrorCode(error, code) {
|
|
13762
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
13763
|
+
}
|
|
13764
|
+
|
|
13463
13765
|
// src/server-api.ts
|
|
13464
13766
|
import { platform } from "node:os";
|
|
13465
13767
|
|
|
@@ -13543,7 +13845,7 @@ function normalizeServerUrl(value) {
|
|
|
13543
13845
|
}
|
|
13544
13846
|
|
|
13545
13847
|
// src/version.ts
|
|
13546
|
-
var PLUGIN_VERSION = "0.
|
|
13848
|
+
var PLUGIN_VERSION = "0.4.1";
|
|
13547
13849
|
|
|
13548
13850
|
// src/server-api.ts
|
|
13549
13851
|
var HostServerApi = class {
|
|
@@ -13942,53 +14244,141 @@ function requireRecord(value, name2) {
|
|
|
13942
14244
|
|
|
13943
14245
|
// src/typert-gateway-switch.ts
|
|
13944
14246
|
var REMOTE_COMMAND_METHODS = ["execute", "list"];
|
|
14247
|
+
var LOCAL_ONLY_NAMESPACES = /* @__PURE__ */ new Set(["dynamicCordisRunner"]);
|
|
13945
14248
|
var ALL_REMOTE_COMMANDS = { execute: true, list: true };
|
|
13946
14249
|
var TypertGatewaySwitch = class {
|
|
13947
|
-
|
|
13948
|
-
this.gateway = gateway;
|
|
13949
|
-
this.originalInvoke = gateway.invoke;
|
|
13950
|
-
this.localInvoke = this.originalInvoke.bind(gateway);
|
|
13951
|
-
}
|
|
14250
|
+
runtime;
|
|
13952
14251
|
originalInvoke;
|
|
13953
14252
|
localInvoke;
|
|
14253
|
+
originalStream;
|
|
14254
|
+
localStream;
|
|
14255
|
+
originalDispatch;
|
|
14256
|
+
localDispatch;
|
|
14257
|
+
originalOpen;
|
|
14258
|
+
localOpen;
|
|
13954
14259
|
remoteInvoke;
|
|
14260
|
+
remoteTarget;
|
|
13955
14261
|
remoteSupport = { execute: false, list: false };
|
|
14262
|
+
target;
|
|
13956
14263
|
installed = false;
|
|
13957
|
-
|
|
14264
|
+
constructor(gateway) {
|
|
14265
|
+
this.runtime = gateway;
|
|
14266
|
+
this.originalInvoke = gateway.invoke;
|
|
14267
|
+
this.localInvoke = this.originalInvoke.bind(gateway);
|
|
14268
|
+
this.originalStream = gateway.stream;
|
|
14269
|
+
this.localStream = gateway.stream?.bind(gateway);
|
|
14270
|
+
this.originalDispatch = this.runtime.dispatchRpc;
|
|
14271
|
+
this.localDispatch = this.runtime.dispatchRpc?.bind(gateway);
|
|
14272
|
+
this.originalOpen = this.runtime.openWireStream;
|
|
14273
|
+
this.localOpen = this.runtime.openWireStream?.bind(gateway) ?? gateway.wireStream?.open.bind(gateway.wireStream);
|
|
14274
|
+
}
|
|
14275
|
+
/** Original local dispatcher, used by the Host bridge without switch recursion. */
|
|
13958
14276
|
local() {
|
|
13959
|
-
|
|
14277
|
+
const dispatch = this.localDispatch ?? (async (endpoint, payload, signal) => {
|
|
14278
|
+
try {
|
|
14279
|
+
const request = requestFromCarrier(endpoint, payload, signal);
|
|
14280
|
+
return { ok: true, value: await this.localInvoke(request) };
|
|
14281
|
+
} catch (error) {
|
|
14282
|
+
return { ok: false, error: this.failure(error) };
|
|
14283
|
+
}
|
|
14284
|
+
});
|
|
14285
|
+
const open = this.localOpen ?? (async (endpoint, payload, signal) => {
|
|
14286
|
+
if (this.localStream === void 0) throw new Error("The local Harness Gateway does not support Remote streams.");
|
|
14287
|
+
return this.localStream(requestFromCarrier(endpoint, payload, signal));
|
|
14288
|
+
});
|
|
14289
|
+
return {
|
|
14290
|
+
invoke: this.localInvoke,
|
|
14291
|
+
...this.localStream === void 0 ? {} : { stream: this.localStream },
|
|
14292
|
+
dispatch,
|
|
14293
|
+
open,
|
|
14294
|
+
failure: (error) => this.failure(error),
|
|
14295
|
+
supportsCarrier: this.localDispatch !== void 0 && this.localOpen !== void 0
|
|
14296
|
+
};
|
|
14297
|
+
}
|
|
14298
|
+
supportsCarrier() {
|
|
14299
|
+
return this.localDispatch !== void 0 && this.localOpen !== void 0;
|
|
14300
|
+
}
|
|
14301
|
+
status() {
|
|
14302
|
+
return this.remoteInvoke === void 0 ? { mode: "local" } : { mode: "remote", ...this.target === void 0 ? {} : { target: { ...this.target } } };
|
|
13960
14303
|
}
|
|
13961
14304
|
install() {
|
|
13962
14305
|
if (this.installed) return;
|
|
13963
|
-
this.
|
|
13964
|
-
|
|
13965
|
-
|
|
13966
|
-
|
|
13967
|
-
|
|
13968
|
-
|
|
13969
|
-
|
|
13970
|
-
|
|
14306
|
+
this.runtime.invoke = (request) => this.selectInvoke(request);
|
|
14307
|
+
if (this.originalStream !== void 0) {
|
|
14308
|
+
this.runtime.stream = (request) => this.remoteTarget === void 0 || isLocalOnlyEndpoint(endpointOf(request)) ? this.localStream(request) : this.remoteTarget.open(endpointOf(request), { args: request.args }, request.signal ?? new AbortController().signal);
|
|
14309
|
+
}
|
|
14310
|
+
if (this.originalDispatch !== void 0) {
|
|
14311
|
+
this.runtime.dispatchRpc = (endpoint, payload, signal) => this.remoteTarget === void 0 || isLocalOnlyEndpoint(endpoint) ? this.localDispatch(endpoint, payload, signal) : this.remoteTarget.dispatch(endpoint, payload, signal);
|
|
14312
|
+
}
|
|
14313
|
+
if (this.originalOpen !== void 0) {
|
|
14314
|
+
this.runtime.openWireStream = (endpoint, payload, signal) => this.remoteTarget === void 0 || isLocalOnlyEndpoint(endpoint) ? this.localOpen(endpoint, payload, signal) : this.remoteTarget.open(endpoint, payload, signal);
|
|
14315
|
+
}
|
|
13971
14316
|
this.installed = true;
|
|
13972
14317
|
}
|
|
13973
|
-
selectRemote(
|
|
14318
|
+
selectRemote(remote, support = ALL_REMOTE_COMMANDS, target) {
|
|
13974
14319
|
if (!this.installed) throw new Error("The Typert gateway switch is not installed.");
|
|
13975
|
-
this.remoteInvoke = invoke;
|
|
14320
|
+
this.remoteInvoke = typeof remote === "function" ? remote : (request) => remote.invoke(request);
|
|
14321
|
+
this.remoteTarget = typeof remote === "function" ? void 0 : remote;
|
|
13976
14322
|
this.remoteSupport = { ...support };
|
|
14323
|
+
this.target = target === void 0 ? void 0 : { ...target };
|
|
13977
14324
|
}
|
|
13978
14325
|
selectLocal() {
|
|
13979
14326
|
this.remoteInvoke = void 0;
|
|
14327
|
+
this.remoteTarget = void 0;
|
|
13980
14328
|
this.remoteSupport = { execute: false, list: false };
|
|
14329
|
+
this.target = void 0;
|
|
13981
14330
|
}
|
|
13982
14331
|
restore() {
|
|
13983
14332
|
if (!this.installed) return;
|
|
13984
14333
|
this.selectLocal();
|
|
13985
|
-
this.
|
|
14334
|
+
this.runtime.invoke = this.originalInvoke;
|
|
14335
|
+
if (this.originalStream !== void 0) this.runtime.stream = this.originalStream;
|
|
14336
|
+
if (this.originalDispatch !== void 0) this.runtime.dispatchRpc = this.originalDispatch;
|
|
14337
|
+
if (this.originalOpen !== void 0) this.runtime.openWireStream = this.originalOpen;
|
|
13986
14338
|
this.installed = false;
|
|
13987
14339
|
}
|
|
14340
|
+
selectInvoke(request) {
|
|
14341
|
+
if (isLocalOnlyEndpoint(endpointOf(request))) return this.localInvoke(request);
|
|
14342
|
+
if (this.remoteTarget !== void 0) return this.remoteTarget.invoke(request);
|
|
14343
|
+
if (request.namespace !== "commands" || !isRemoteCommandMethod(request.method) || this.remoteInvoke === void 0) {
|
|
14344
|
+
return this.localInvoke(request);
|
|
14345
|
+
}
|
|
14346
|
+
if (this.remoteSupport[request.method]) return this.remoteInvoke(request);
|
|
14347
|
+
if (request.method === "list") return Promise.resolve([]);
|
|
14348
|
+
return this.localInvoke(request);
|
|
14349
|
+
}
|
|
14350
|
+
failure(error) {
|
|
14351
|
+
const normalized = this.runtime.wireStream?.failure(error);
|
|
14352
|
+
if (normalized !== void 0) return normalized;
|
|
14353
|
+
const source = error instanceof Error ? error : new Error("The Harness Gateway rejected the request.");
|
|
14354
|
+
const code = "code" in source && typeof source.code === "string" ? source.code : "internal";
|
|
14355
|
+
const details = "details" in source && isRecord2(source.details) ? source.details : {};
|
|
14356
|
+
return { code, message: source.message, details };
|
|
14357
|
+
}
|
|
13988
14358
|
};
|
|
14359
|
+
function requestFromCarrier(endpoint, payload, signal) {
|
|
14360
|
+
const segments = endpoint.split("/");
|
|
14361
|
+
if (segments.length !== 2 || segments.some((segment) => segment.length === 0)) {
|
|
14362
|
+
throw new Error("The Harness Gateway endpoint is invalid.");
|
|
14363
|
+
}
|
|
14364
|
+
if (!isRecord2(payload) || !isRecord2(payload.args)) {
|
|
14365
|
+
throw new Error("The Harness Gateway payload is invalid.");
|
|
14366
|
+
}
|
|
14367
|
+
return { namespace: segments[0], method: segments[1], args: payload.args, signal };
|
|
14368
|
+
}
|
|
14369
|
+
function endpointOf(request) {
|
|
14370
|
+
return `${request.namespace}/${request.method}`;
|
|
14371
|
+
}
|
|
14372
|
+
function isLocalOnlyEndpoint(endpoint) {
|
|
14373
|
+
const separator = endpoint.indexOf("/");
|
|
14374
|
+
return separator > 0 && LOCAL_ONLY_NAMESPACES.has(endpoint.slice(0, separator));
|
|
14375
|
+
}
|
|
13989
14376
|
function isRemoteCommandMethod(method) {
|
|
13990
14377
|
return REMOTE_COMMAND_METHODS.includes(method);
|
|
13991
14378
|
}
|
|
14379
|
+
function isRecord2(value) {
|
|
14380
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14381
|
+
}
|
|
13992
14382
|
|
|
13993
14383
|
// src/werift-rtc.ts
|
|
13994
14384
|
import { createSocket } from "node:dgram";
|
|
@@ -13996,7 +14386,7 @@ import { networkInterfaces } from "node:os";
|
|
|
13996
14386
|
|
|
13997
14387
|
// src/native-rtc-helper.ts
|
|
13998
14388
|
import { spawn, spawnSync } from "node:child_process";
|
|
13999
|
-
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
14389
|
+
import { existsSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
14000
14390
|
import { delimiter, dirname, join } from "node:path";
|
|
14001
14391
|
import { fileURLToPath } from "node:url";
|
|
14002
14392
|
var cachedExternalFactory;
|
|
@@ -14012,17 +14402,25 @@ async function loadExternalNativeRtcFactory() {
|
|
|
14012
14402
|
cachedExternalFactory = buildExternalNativeRtcFactory(nodeBinary);
|
|
14013
14403
|
return cachedExternalFactory;
|
|
14014
14404
|
}
|
|
14015
|
-
function buildExternalNativeRtcFactory(nodeBinary, requireFrom =
|
|
14405
|
+
function buildExternalNativeRtcFactory(nodeBinary, requireFrom = resolveNativeRtcRequireFrom()) {
|
|
14016
14406
|
return {
|
|
14017
14407
|
create(configuration) {
|
|
14018
14408
|
return new ExternalNativePeerConnection(nodeBinary, requireFrom, configuration);
|
|
14019
14409
|
}
|
|
14020
14410
|
};
|
|
14021
14411
|
}
|
|
14412
|
+
function resolveNativeRtcRequireFrom(moduleUrl = import.meta.url) {
|
|
14413
|
+
const modulePath = fileURLToPath(moduleUrl);
|
|
14414
|
+
try {
|
|
14415
|
+
return realpathSync(modulePath);
|
|
14416
|
+
} catch {
|
|
14417
|
+
return modulePath;
|
|
14418
|
+
}
|
|
14419
|
+
}
|
|
14022
14420
|
function resolveExternalNodeBinaryForRtc() {
|
|
14023
14421
|
if (cachedNodeBinaryResolved) return cachedNodeBinary;
|
|
14024
14422
|
cachedNodeBinaryResolved = true;
|
|
14025
|
-
const requireFrom =
|
|
14423
|
+
const requireFrom = resolveNativeRtcRequireFrom();
|
|
14026
14424
|
for (const candidate of nodeBinaryCandidates()) {
|
|
14027
14425
|
if (isUsableExternalNode(candidate, requireFrom)) {
|
|
14028
14426
|
cachedNodeBinary = candidate;
|
|
@@ -15057,7 +15455,7 @@ var ClientModeRuntime = class {
|
|
|
15057
15455
|
this.logger = logger;
|
|
15058
15456
|
this.host = host;
|
|
15059
15457
|
this.rtcFactoryProvider = rtcFactoryProvider;
|
|
15060
|
-
this.proxySwitch = new ApiProxySwitch(apiProxy);
|
|
15458
|
+
this.proxySwitch = apiProxy === void 0 ? void 0 : new ApiProxySwitch(apiProxy);
|
|
15061
15459
|
this.gatewaySwitch = new TypertGatewaySwitch(typertGateway);
|
|
15062
15460
|
}
|
|
15063
15461
|
identity;
|
|
@@ -15070,7 +15468,7 @@ var ClientModeRuntime = class {
|
|
|
15070
15468
|
if (this.closed) throw new Error("client remote-mode runtime is closed");
|
|
15071
15469
|
this.identity = await this.identities.loadOrCreate(this.config.deviceName);
|
|
15072
15470
|
this.server.bindIdentity(this.identity);
|
|
15073
|
-
this.proxySwitch
|
|
15471
|
+
this.proxySwitch?.install();
|
|
15074
15472
|
this.gatewaySwitch.install();
|
|
15075
15473
|
this.logger.info("client remote-mode identity ready", {
|
|
15076
15474
|
deviceId: shortId(this.identity.deviceId),
|
|
@@ -15083,13 +15481,14 @@ var ClientModeRuntime = class {
|
|
|
15083
15481
|
});
|
|
15084
15482
|
}
|
|
15085
15483
|
status() {
|
|
15484
|
+
const targetStatus = this.gatewaySwitch.supportsCarrier() ? this.gatewaySwitch.status() : this.proxySwitch?.status() ?? this.gatewaySwitch.status();
|
|
15086
15485
|
return {
|
|
15087
15486
|
available: this.config.serverUrl !== void 0,
|
|
15088
15487
|
identityReady: this.identity !== void 0,
|
|
15089
15488
|
deviceId: this.identity?.deviceId,
|
|
15090
15489
|
deviceName: this.identity?.name,
|
|
15091
15490
|
serverUrl: this.config.serverUrl,
|
|
15092
|
-
...
|
|
15491
|
+
...targetStatus,
|
|
15093
15492
|
connected: this.connected !== void 0,
|
|
15094
15493
|
transport: this.connected?.client.getStats().mode ?? "Disconnected",
|
|
15095
15494
|
preferredTransports: this.config.forceRelay ? ["relay"] : ["lan", "p2p", "turn", "relay"],
|
|
@@ -15161,7 +15560,7 @@ var ClientModeRuntime = class {
|
|
|
15161
15560
|
const previous = this.connected;
|
|
15162
15561
|
this.connected = void 0;
|
|
15163
15562
|
this.pendingWorkspaceSelection = void 0;
|
|
15164
|
-
this.proxySwitch
|
|
15563
|
+
this.proxySwitch?.selectLocal();
|
|
15165
15564
|
this.gatewaySwitch.selectLocal();
|
|
15166
15565
|
await previous?.client.close().catch(() => void 0);
|
|
15167
15566
|
await this.server.revokeCurrentDevice();
|
|
@@ -15180,7 +15579,7 @@ var ClientModeRuntime = class {
|
|
|
15180
15579
|
}
|
|
15181
15580
|
async setMode(mode, targetDeviceId, signal) {
|
|
15182
15581
|
if (mode === "local") {
|
|
15183
|
-
this.proxySwitch
|
|
15582
|
+
this.proxySwitch?.selectLocal();
|
|
15184
15583
|
this.gatewaySwitch.selectLocal();
|
|
15185
15584
|
const previous2 = this.connected;
|
|
15186
15585
|
this.connected = void 0;
|
|
@@ -15193,18 +15592,31 @@ var ClientModeRuntime = class {
|
|
|
15193
15592
|
throw new ClientModeError("INVALID_MESSAGE", "A targetDeviceId is required for remote mode.");
|
|
15194
15593
|
}
|
|
15195
15594
|
const next = await this.connect(targetDeviceId, signal);
|
|
15595
|
+
try {
|
|
15596
|
+
this.assertRemoteCompatible(next);
|
|
15597
|
+
} catch (error) {
|
|
15598
|
+
await next.client.close().catch(() => void 0);
|
|
15599
|
+
throw error;
|
|
15600
|
+
}
|
|
15196
15601
|
const previous = this.connected;
|
|
15197
15602
|
this.connected = next;
|
|
15198
15603
|
this.pendingWorkspaceSelection = void 0;
|
|
15199
|
-
|
|
15200
|
-
this.proxySwitch.selectRemote(remoteApi, { deviceId: next.target.deviceId, name: next.target.name });
|
|
15201
|
-
this.selectRemoteCommands(next);
|
|
15604
|
+
this.selectRemoteTarget(next);
|
|
15202
15605
|
await previous?.client.close().catch(() => void 0);
|
|
15203
15606
|
this.logger.info("Harness target switched", { mode: "remote", targetDeviceId: shortId(next.target.deviceId) });
|
|
15204
15607
|
return this.status();
|
|
15205
15608
|
}
|
|
15206
15609
|
async listRemoteDirectory(targetDeviceId, path, signal) {
|
|
15207
15610
|
const remote = await this.ensureConnected(targetDeviceId, signal);
|
|
15611
|
+
if (remote.features.remoteGateway) {
|
|
15612
|
+
const value = await new RemoteTypertGateway2(remote.client).invoke({
|
|
15613
|
+
namespace: "directoryPicker",
|
|
15614
|
+
method: "list",
|
|
15615
|
+
args: path === void 0 ? {} : { path },
|
|
15616
|
+
...signal === void 0 ? {} : { signal }
|
|
15617
|
+
});
|
|
15618
|
+
return value;
|
|
15619
|
+
}
|
|
15208
15620
|
const api = new RemoteHarnessApiProxy(remote.client).api;
|
|
15209
15621
|
const response = await api.host.listDirectory({
|
|
15210
15622
|
rpcId: `remote-directory-${Date.now()}`,
|
|
@@ -15214,6 +15626,9 @@ var ClientModeRuntime = class {
|
|
|
15214
15626
|
}
|
|
15215
15627
|
async listRemoteWorkspaces(targetDeviceId, signal) {
|
|
15216
15628
|
const remote = await this.ensureConnected(targetDeviceId, signal);
|
|
15629
|
+
if (remote.features.remoteGateway) {
|
|
15630
|
+
return readRemoteWorkspaceBaseline(new RemoteTypertGateway2(remote.client), signal);
|
|
15631
|
+
}
|
|
15217
15632
|
const api = new RemoteHarnessApiProxy(remote.client).api;
|
|
15218
15633
|
const response = await api.workspace.list({
|
|
15219
15634
|
rpcId: `remote-workspaces-${Date.now()}`,
|
|
@@ -15225,14 +15640,24 @@ var ClientModeRuntime = class {
|
|
|
15225
15640
|
async openRemoteWorkspace(targetDeviceId, path, signal) {
|
|
15226
15641
|
if (path.trim() === "") throw new ClientModeError("INVALID_MESSAGE", "A remote working directory is required.");
|
|
15227
15642
|
const remote = await this.ensureConnected(targetDeviceId, signal);
|
|
15228
|
-
|
|
15229
|
-
|
|
15230
|
-
|
|
15231
|
-
|
|
15232
|
-
|
|
15233
|
-
|
|
15234
|
-
|
|
15235
|
-
|
|
15643
|
+
this.assertRemoteCompatible(remote);
|
|
15644
|
+
let workspace;
|
|
15645
|
+
if (remote.features.remoteGateway) {
|
|
15646
|
+
workspace = await new RemoteTypertGateway2(remote.client).invoke({
|
|
15647
|
+
namespace: "workspace",
|
|
15648
|
+
method: "create",
|
|
15649
|
+
args: { request: { path } },
|
|
15650
|
+
...signal === void 0 ? {} : { signal }
|
|
15651
|
+
});
|
|
15652
|
+
} else {
|
|
15653
|
+
const api = new RemoteHarnessApiProxy(remote.client).api;
|
|
15654
|
+
const response = await api.workspace.create({
|
|
15655
|
+
rpcId: `remote-workspace-${Date.now()}`,
|
|
15656
|
+
payload: { path }
|
|
15657
|
+
});
|
|
15658
|
+
workspace = unwrapNativeResult(response);
|
|
15659
|
+
}
|
|
15660
|
+
this.selectRemoteTarget(remote);
|
|
15236
15661
|
const workspaceId = workspaceRecordId(workspace.workspace);
|
|
15237
15662
|
this.pendingWorkspaceSelection = { targetDeviceId: remote.target.deviceId, workspaceId };
|
|
15238
15663
|
this.logger.info("Remote workspace opened", { targetDeviceId: shortId(remote.target.deviceId) });
|
|
@@ -15248,17 +15673,17 @@ var ClientModeRuntime = class {
|
|
|
15248
15673
|
async close() {
|
|
15249
15674
|
if (this.closed) return;
|
|
15250
15675
|
this.closed = true;
|
|
15251
|
-
this.proxySwitch
|
|
15676
|
+
this.proxySwitch?.selectLocal();
|
|
15252
15677
|
this.gatewaySwitch.selectLocal();
|
|
15253
15678
|
this.pendingWorkspaceSelection = void 0;
|
|
15254
15679
|
await this.connected?.client.close().catch(() => void 0);
|
|
15255
15680
|
this.connected = void 0;
|
|
15256
|
-
this.proxySwitch
|
|
15681
|
+
this.proxySwitch?.restore();
|
|
15257
15682
|
this.gatewaySwitch.restore();
|
|
15258
15683
|
}
|
|
15259
15684
|
async callRemoteFileViewer(endpoint, payload, signal) {
|
|
15260
15685
|
const remote = this.connected;
|
|
15261
|
-
if (remote === void 0 || this.
|
|
15686
|
+
if (remote === void 0 || this.status().mode !== "remote") {
|
|
15262
15687
|
throw new ClientModeError("REMOTE_NOT_CONNECTED", "No Remote Host is selected.", true);
|
|
15263
15688
|
}
|
|
15264
15689
|
if (!remote.features.fileViewer) {
|
|
@@ -15266,11 +15691,25 @@ var ClientModeRuntime = class {
|
|
|
15266
15691
|
}
|
|
15267
15692
|
return remote.client.rpc("fileviewer.call", { endpoint, payload }, signal);
|
|
15268
15693
|
}
|
|
15269
|
-
|
|
15694
|
+
selectRemoteTarget(remote) {
|
|
15695
|
+
const target = { deviceId: remote.target.deviceId, name: remote.target.name };
|
|
15696
|
+
if (this.gatewaySwitch.supportsCarrier()) {
|
|
15697
|
+
this.gatewaySwitch.selectRemote(new RemoteTypertGateway2(remote.client), void 0, target);
|
|
15698
|
+
return;
|
|
15699
|
+
}
|
|
15700
|
+
this.proxySwitch.selectRemote(new RemoteHarnessApiProxy(remote.client).api, target);
|
|
15270
15701
|
this.gatewaySwitch.selectRemote((request) => invokeRemoteCommand(remote.client, request), {
|
|
15271
15702
|
execute: true,
|
|
15272
15703
|
list: remote.features.commandList
|
|
15273
|
-
});
|
|
15704
|
+
}, target);
|
|
15705
|
+
}
|
|
15706
|
+
assertRemoteCompatible(remote) {
|
|
15707
|
+
const localRemoteGateway = this.gatewaySwitch.supportsCarrier();
|
|
15708
|
+
if (localRemoteGateway && remote.features.remoteGateway || !localRemoteGateway && this.proxySwitch !== void 0 && remote.features.apiProxy) return;
|
|
15709
|
+
throw new ClientModeError(
|
|
15710
|
+
"HARNESS_VERSION_INCOMPATIBLE",
|
|
15711
|
+
localRemoteGateway ? "The selected Host does not provide the Harness alpha Remote Gateway transport." : "The selected Host does not provide the legacy Harness ApiProxy transport."
|
|
15712
|
+
);
|
|
15274
15713
|
}
|
|
15275
15714
|
async connect(targetDeviceId, signal) {
|
|
15276
15715
|
signal?.throwIfAborted();
|
|
@@ -15284,6 +15723,11 @@ var ClientModeRuntime = class {
|
|
|
15284
15723
|
if (!presence.online) throw new ClientModeError("HOST_OFFLINE", "The selected Host is offline.", true);
|
|
15285
15724
|
const credentials = await this.server.authenticate(identity);
|
|
15286
15725
|
const rtcFactory = this.config.forceRelay ? void 0 : await this.rtcFactoryProvider({ routeTargets: [this.server.baseUrl] }).catch(() => void 0);
|
|
15726
|
+
if (!this.config.forceRelay && rtcFactory === void 0) {
|
|
15727
|
+
this.logger.warn("remote Harness WebRTC backend unavailable; using relay", {
|
|
15728
|
+
targetDeviceId: shortId(target.deviceId)
|
|
15729
|
+
});
|
|
15730
|
+
}
|
|
15287
15731
|
let webRtcFallback = false;
|
|
15288
15732
|
const createTransport = (attempt) => new AdaptiveTransport(
|
|
15289
15733
|
websocketUrl(this.server.baseUrl),
|
|
@@ -15337,7 +15781,7 @@ var ClientModeRuntime = class {
|
|
|
15337
15781
|
if (this.connected?.client !== connectedClient) return;
|
|
15338
15782
|
this.connected = void 0;
|
|
15339
15783
|
this.pendingWorkspaceSelection = void 0;
|
|
15340
|
-
this.proxySwitch
|
|
15784
|
+
this.proxySwitch?.selectLocal();
|
|
15341
15785
|
this.gatewaySwitch.selectLocal();
|
|
15342
15786
|
void connectedClient.close().catch(() => void 0);
|
|
15343
15787
|
this.logger.warn("remote Harness transport closed; falling back to local mode", {
|
|
@@ -15348,9 +15792,15 @@ var ClientModeRuntime = class {
|
|
|
15348
15792
|
this.logger.info("remote Harness transport ready", {
|
|
15349
15793
|
targetDeviceId: shortId(target.deviceId),
|
|
15350
15794
|
transport: connectedClient.getStats().mode,
|
|
15795
|
+
...connectionDetails === void 0 ? {} : {
|
|
15796
|
+
preferredTransports: connectionDetails.preferredTransports,
|
|
15797
|
+
negotiatedCapabilities: connectionDetails.negotiatedCapabilities,
|
|
15798
|
+
webRtcEnabled: connectionDetails.webRtcEnabled
|
|
15799
|
+
},
|
|
15351
15800
|
...webrtcDiagnosticsLogFields(connectionDetails?.webRtc?.diagnostics)
|
|
15352
15801
|
});
|
|
15353
|
-
|
|
15802
|
+
const features = await probeRemoteHostFeatures(connectedClient, serverDevice.clientVersion);
|
|
15803
|
+
return { client: connectedClient, target, transport: connectedTransport, features };
|
|
15354
15804
|
} catch (error) {
|
|
15355
15805
|
await client?.close().catch(() => void 0);
|
|
15356
15806
|
throw error;
|
|
@@ -15519,6 +15969,9 @@ function record(value) {
|
|
|
15519
15969
|
}
|
|
15520
15970
|
return value;
|
|
15521
15971
|
}
|
|
15972
|
+
function isRecord3(value) {
|
|
15973
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15974
|
+
}
|
|
15522
15975
|
function ok(value) {
|
|
15523
15976
|
return { ok: true, value };
|
|
15524
15977
|
}
|
|
@@ -15534,6 +15987,27 @@ async function invokeRemoteCommand(client, request) {
|
|
|
15534
15987
|
}
|
|
15535
15988
|
return unwrapNativeResult(response);
|
|
15536
15989
|
}
|
|
15990
|
+
async function readRemoteWorkspaceBaseline(gateway, signal) {
|
|
15991
|
+
const lifetime = new AbortController();
|
|
15992
|
+
const activeSignal = signal === void 0 ? lifetime.signal : AbortSignal.any([signal, lifetime.signal]);
|
|
15993
|
+
const source = await gateway.open("workspace/follow", { args: {} }, activeSignal);
|
|
15994
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
15995
|
+
try {
|
|
15996
|
+
const first = await iterator.next();
|
|
15997
|
+
if (first.done || !isRecord3(first.value) || first.value.type !== "baseline" || !isRecord3(first.value.value) || !Array.isArray(first.value.value.items)) {
|
|
15998
|
+
throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned an invalid Workspace baseline.");
|
|
15999
|
+
}
|
|
16000
|
+
return first.value.value.items.map((item) => {
|
|
16001
|
+
if (!isRecord3(item) || typeof item.workspaceId !== "string" || typeof item.path !== "string" || typeof item.title !== "string") {
|
|
16002
|
+
throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned an invalid Workspace row.");
|
|
16003
|
+
}
|
|
16004
|
+
return { workspaceId: item.workspaceId, path: item.path, title: item.title };
|
|
16005
|
+
});
|
|
16006
|
+
} finally {
|
|
16007
|
+
lifetime.abort("workspace-baseline-read");
|
|
16008
|
+
await iterator.return?.();
|
|
16009
|
+
}
|
|
16010
|
+
}
|
|
15537
16011
|
function unwrapNativeResult(response) {
|
|
15538
16012
|
const result = response.result;
|
|
15539
16013
|
if (typeof result !== "object" || result === null || !("ok" in result)) {
|
|
@@ -15570,7 +16044,34 @@ function shortId(value) {
|
|
|
15570
16044
|
function remoteHostFeatures(clientVersion) {
|
|
15571
16045
|
return {
|
|
15572
16046
|
commandList: isVersionAtLeast(clientVersion, REMOTE_COMMAND_LIST_MIN_VERSION),
|
|
15573
|
-
fileViewer: isVersionAtLeast(clientVersion, REMOTE_FILE_VIEWER_MIN_VERSION)
|
|
16047
|
+
fileViewer: isVersionAtLeast(clientVersion, REMOTE_FILE_VIEWER_MIN_VERSION),
|
|
16048
|
+
apiProxy: true,
|
|
16049
|
+
remoteGateway: false
|
|
16050
|
+
};
|
|
16051
|
+
}
|
|
16052
|
+
async function probeRemoteHostFeatures(client, clientVersion) {
|
|
16053
|
+
const fallback = remoteHostFeatures(clientVersion);
|
|
16054
|
+
let value;
|
|
16055
|
+
try {
|
|
16056
|
+
value = await client.rpc("harness.transport.describe", {});
|
|
16057
|
+
} catch (error) {
|
|
16058
|
+
if (error instanceof Error && "code" in error && error.code === "METHOD_NOT_FOUND") return fallback;
|
|
16059
|
+
throw error;
|
|
16060
|
+
}
|
|
16061
|
+
if (!isRecord3(value) || !Array.isArray(value.capabilities) || value.capabilities.some((capability) => typeof capability !== "string")) {
|
|
16062
|
+
throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned invalid transport capabilities.");
|
|
16063
|
+
}
|
|
16064
|
+
const capabilities = new Set(value.capabilities);
|
|
16065
|
+
const apiProxy = capabilities.has("harness.api.v1");
|
|
16066
|
+
const remoteGateway = capabilities.has("harness.remote.v1");
|
|
16067
|
+
if (!apiProxy && !remoteGateway) {
|
|
16068
|
+
throw new ClientModeError("FEATURE_NOT_SUPPORTED", "The remote Host exposes no supported Harness transport.");
|
|
16069
|
+
}
|
|
16070
|
+
return {
|
|
16071
|
+
commandList: remoteGateway || apiProxy && fallback.commandList,
|
|
16072
|
+
fileViewer: capabilities.has("fileviewer.read.v1"),
|
|
16073
|
+
apiProxy,
|
|
16074
|
+
remoteGateway
|
|
15574
16075
|
};
|
|
15575
16076
|
}
|
|
15576
16077
|
function isVersionAtLeast(value, minimum) {
|
|
@@ -16051,11 +16552,11 @@ function editableConfig(config) {
|
|
|
16051
16552
|
} : false
|
|
16052
16553
|
};
|
|
16053
16554
|
}
|
|
16054
|
-
function
|
|
16555
|
+
function isRecord4(value) {
|
|
16055
16556
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16056
16557
|
}
|
|
16057
16558
|
function record2(value) {
|
|
16058
|
-
if (!
|
|
16559
|
+
if (!isRecord4(value)) throw new ClientModeError("INVALID_MESSAGE", "The control request payload is invalid.");
|
|
16059
16560
|
return value;
|
|
16060
16561
|
}
|
|
16061
16562
|
function ok2(value) {
|
|
@@ -16109,7 +16610,6 @@ function redact(value, key = "") {
|
|
|
16109
16610
|
|
|
16110
16611
|
// src/service.ts
|
|
16111
16612
|
import { randomUUID } from "node:crypto";
|
|
16112
|
-
import { RpcId } from "@deepseek-ai/dsh-host-apiproxy/api";
|
|
16113
16613
|
|
|
16114
16614
|
// src/connection-controller.ts
|
|
16115
16615
|
var ConnectionController = class {
|
|
@@ -16326,7 +16826,9 @@ function safeErrorCode(error) {
|
|
|
16326
16826
|
|
|
16327
16827
|
// src/rpc-router.ts
|
|
16328
16828
|
var wireRequestSchema = external_exports.object({ method: external_exports.string().min(1), params: external_exports.unknown() }).strict();
|
|
16829
|
+
var emptyParamsSchema = external_exports.object({}).strict();
|
|
16329
16830
|
var apiMethods = /* @__PURE__ */ new Set([
|
|
16831
|
+
"harness.transport.describe",
|
|
16330
16832
|
"harness.api.call",
|
|
16331
16833
|
"harness.api.transfer.open",
|
|
16332
16834
|
"harness.api.transfer.chunk",
|
|
@@ -16336,19 +16838,38 @@ var apiMethods = /* @__PURE__ */ new Set([
|
|
|
16336
16838
|
"harness.api.respond",
|
|
16337
16839
|
"harness.api.stream.open",
|
|
16338
16840
|
"harness.api.stream.close",
|
|
16841
|
+
"harness.remote.call",
|
|
16842
|
+
"harness.remote.transfer.open",
|
|
16843
|
+
"harness.remote.transfer.chunk",
|
|
16844
|
+
"harness.remote.transfer.commit",
|
|
16845
|
+
"harness.remote.transfer.read",
|
|
16846
|
+
"harness.remote.transfer.close",
|
|
16847
|
+
"harness.remote.stream.open",
|
|
16848
|
+
"harness.remote.stream.close",
|
|
16339
16849
|
"fileviewer.call"
|
|
16340
16850
|
]);
|
|
16341
|
-
var HOST_CAPABILITIES = [
|
|
16851
|
+
var HOST_CAPABILITIES = [
|
|
16852
|
+
"harness.api.v1",
|
|
16853
|
+
"harness.api.transfer.v1",
|
|
16854
|
+
"harness.remote.v1",
|
|
16855
|
+
"harness.remote.transfer.v1",
|
|
16856
|
+
"fileviewer.read.v1"
|
|
16857
|
+
];
|
|
16342
16858
|
var RpcRouter = class {
|
|
16343
|
-
constructor(harnessApi, maxPending = 128, logger, fileViewer) {
|
|
16859
|
+
constructor(harnessApi, maxPending = 128, logger, fileViewer, harnessRemote, capabilities = () => HOST_CAPABILITIES) {
|
|
16344
16860
|
this.harnessApi = harnessApi;
|
|
16345
16861
|
this.maxPending = maxPending;
|
|
16346
16862
|
this.logger = logger;
|
|
16347
16863
|
this.fileViewer = fileViewer;
|
|
16864
|
+
this.harnessRemote = harnessRemote;
|
|
16865
|
+
this.capabilities = capabilities;
|
|
16348
16866
|
}
|
|
16349
16867
|
active = 0;
|
|
16350
|
-
closePeerStreams() {
|
|
16351
|
-
|
|
16868
|
+
async closePeerStreams() {
|
|
16869
|
+
await Promise.all([
|
|
16870
|
+
this.harnessApi?.closeAll(),
|
|
16871
|
+
this.harnessRemote?.closeAll()
|
|
16872
|
+
]);
|
|
16352
16873
|
}
|
|
16353
16874
|
async handle(message) {
|
|
16354
16875
|
if (message.type !== "rpc.request") {
|
|
@@ -16387,24 +16908,44 @@ var RpcRouter = class {
|
|
|
16387
16908
|
}
|
|
16388
16909
|
invoke(method, params) {
|
|
16389
16910
|
switch (method) {
|
|
16911
|
+
case "harness.transport.describe": {
|
|
16912
|
+
emptyParamsSchema.parse(params);
|
|
16913
|
+
return { capabilities: [...this.capabilities()] };
|
|
16914
|
+
}
|
|
16390
16915
|
case "harness.api.call":
|
|
16391
|
-
return this.
|
|
16916
|
+
return this.requireApiProxy().call(params);
|
|
16392
16917
|
case "harness.api.transfer.open":
|
|
16393
|
-
return this.
|
|
16918
|
+
return this.requireApiProxy().openTransfer(params);
|
|
16394
16919
|
case "harness.api.transfer.chunk":
|
|
16395
|
-
return this.
|
|
16920
|
+
return this.requireApiProxy().appendTransfer(params);
|
|
16396
16921
|
case "harness.api.transfer.commit":
|
|
16397
|
-
return this.
|
|
16922
|
+
return this.requireApiProxy().commitTransfer(params);
|
|
16398
16923
|
case "harness.api.transfer.read":
|
|
16399
|
-
return this.
|
|
16924
|
+
return this.requireApiProxy().readTransfer(params);
|
|
16400
16925
|
case "harness.api.transfer.close":
|
|
16401
|
-
return this.
|
|
16926
|
+
return this.requireApiProxy().closeTransfer(params);
|
|
16402
16927
|
case "harness.api.respond":
|
|
16403
|
-
return this.
|
|
16928
|
+
return this.requireApiProxy().respond(params);
|
|
16404
16929
|
case "harness.api.stream.open":
|
|
16405
|
-
return this.
|
|
16930
|
+
return this.requireApiProxy().openStream(params);
|
|
16406
16931
|
case "harness.api.stream.close":
|
|
16407
|
-
return this.
|
|
16932
|
+
return this.requireApiProxy().closeStream(params);
|
|
16933
|
+
case "harness.remote.call":
|
|
16934
|
+
return this.requireRemoteGateway().call(params);
|
|
16935
|
+
case "harness.remote.transfer.open":
|
|
16936
|
+
return this.requireRemoteGateway().openTransfer(params);
|
|
16937
|
+
case "harness.remote.transfer.chunk":
|
|
16938
|
+
return this.requireRemoteGateway().appendTransfer(params);
|
|
16939
|
+
case "harness.remote.transfer.commit":
|
|
16940
|
+
return this.requireRemoteGateway().commitTransfer(params);
|
|
16941
|
+
case "harness.remote.transfer.read":
|
|
16942
|
+
return this.requireRemoteGateway().readTransfer(params);
|
|
16943
|
+
case "harness.remote.transfer.close":
|
|
16944
|
+
return this.requireRemoteGateway().closeTransfer(params);
|
|
16945
|
+
case "harness.remote.stream.open":
|
|
16946
|
+
return this.requireRemoteGateway().openStream(params);
|
|
16947
|
+
case "harness.remote.stream.close":
|
|
16948
|
+
return this.requireRemoteGateway().closeStream(params);
|
|
16408
16949
|
case "fileviewer.call": {
|
|
16409
16950
|
if (this.fileViewer === void 0) {
|
|
16410
16951
|
throw new RpcError("FILE_VIEWER_UNAVAILABLE", "The Remote Host does not have DSH File Viewer available.");
|
|
@@ -16415,6 +16956,18 @@ var RpcRouter = class {
|
|
|
16415
16956
|
throw new RpcError("METHOD_NOT_FOUND", "The requested method does not exist.");
|
|
16416
16957
|
}
|
|
16417
16958
|
}
|
|
16959
|
+
requireApiProxy() {
|
|
16960
|
+
if (this.harnessApi === void 0) {
|
|
16961
|
+
throw new RpcError("FEATURE_NOT_SUPPORTED", "This Harness version does not provide the legacy ApiProxy transport.");
|
|
16962
|
+
}
|
|
16963
|
+
return this.harnessApi;
|
|
16964
|
+
}
|
|
16965
|
+
requireRemoteGateway() {
|
|
16966
|
+
if (this.harnessRemote === void 0) {
|
|
16967
|
+
throw new RpcError("FEATURE_NOT_SUPPORTED", "This Harness version does not provide the Remote Gateway transport.");
|
|
16968
|
+
}
|
|
16969
|
+
return this.harnessRemote;
|
|
16970
|
+
}
|
|
16418
16971
|
};
|
|
16419
16972
|
function errorResponse(requestId, error) {
|
|
16420
16973
|
const code = safeErrorCode(error);
|
|
@@ -16572,6 +17125,7 @@ var HostServerConnection = class {
|
|
|
16572
17125
|
reconnectRequested = false;
|
|
16573
17126
|
resumeQueued = false;
|
|
16574
17127
|
rtcFactory;
|
|
17128
|
+
negotiatedCapabilities = ["transport.relay"];
|
|
16575
17129
|
start() {
|
|
16576
17130
|
if (this.running !== void 0) return;
|
|
16577
17131
|
this.stopped = false;
|
|
@@ -16661,6 +17215,7 @@ var HostServerConnection = class {
|
|
|
16661
17215
|
async connectOnce() {
|
|
16662
17216
|
const credentials = await this.api.authenticate(this.identity);
|
|
16663
17217
|
if (this.stopped) return;
|
|
17218
|
+
const offeredCapabilities = this.rtcFactoryProvider === void 0 || this.config.forceRelay ? ["transport.relay", ...this.hostCapabilities()] : ["transport.p2p", "transport.turn", "transport.relay", ...this.hostCapabilities()];
|
|
16664
17219
|
const socket = this.createWebSocket(websocketUrl2(this.api.baseUrl));
|
|
16665
17220
|
this.socket = socket;
|
|
16666
17221
|
let acknowledged = false;
|
|
@@ -16684,7 +17239,7 @@ var HostServerConnection = class {
|
|
|
16684
17239
|
protocols: [PROTOCOL_VERSION],
|
|
16685
17240
|
clientVersion: PLUGIN_VERSION,
|
|
16686
17241
|
...this.harnessVersion === void 0 ? {} : { harnessVersion: this.harnessVersion },
|
|
16687
|
-
capabilities:
|
|
17242
|
+
capabilities: offeredCapabilities
|
|
16688
17243
|
});
|
|
16689
17244
|
};
|
|
16690
17245
|
socket.onmessage = (event) => {
|
|
@@ -16693,6 +17248,10 @@ var HostServerConnection = class {
|
|
|
16693
17248
|
this.lastActiveAt = Date.now();
|
|
16694
17249
|
if (frame.type === "hello.ack") {
|
|
16695
17250
|
const payload = requireHelloAck(frame.payload);
|
|
17251
|
+
this.negotiatedCapabilities = acceptNegotiatedCapabilities(offeredCapabilities, payload.capabilities);
|
|
17252
|
+
if (!this.negotiatedCapabilities.some((capability) => capability === "transport.relay" || capability === "transport.p2p" || capability === "transport.turn")) {
|
|
17253
|
+
throw new ControlConnectionError("INVALID_MESSAGE", "Server did not negotiate a transport capability.");
|
|
17254
|
+
}
|
|
16696
17255
|
acknowledged = true;
|
|
16697
17256
|
clearTimeout(helloTimer);
|
|
16698
17257
|
this.online = true;
|
|
@@ -16768,7 +17327,10 @@ var HostServerConnection = class {
|
|
|
16768
17327
|
this.handleSignalIce(requireSignalIce(frame.payload));
|
|
16769
17328
|
return;
|
|
16770
17329
|
}
|
|
16771
|
-
if (frame.type === "transport.selected")
|
|
17330
|
+
if (frame.type === "transport.selected") {
|
|
17331
|
+
await this.handleTransportSelected(requireTransportSelected(frame.payload));
|
|
17332
|
+
return;
|
|
17333
|
+
}
|
|
16772
17334
|
if (frame.type === "signal.answer") return;
|
|
16773
17335
|
if (frame.type === "error") {
|
|
16774
17336
|
const payload = requireControlError(frame.payload);
|
|
@@ -16855,6 +17417,17 @@ var HostServerConnection = class {
|
|
|
16855
17417
|
if (tunnel === void 0 || payload.targetDeviceId !== this.identity.deviceId || payload.step !== 1) {
|
|
16856
17418
|
throw new ControlConnectionError("SECURE_CHANNEL_FAILED", "Noise IK handshake is not valid for this connection.");
|
|
16857
17419
|
}
|
|
17420
|
+
if (tunnel.transport === "negotiating" && tunnel.rtc !== void 0) {
|
|
17421
|
+
if (tunnel.pendingHandshake === void 0) tunnel.pendingHandshake = payload;
|
|
17422
|
+
else this.logger.warn("duplicate pending secure handshake ignored", {
|
|
17423
|
+
connectionId: shortId3(tunnel.connectionId),
|
|
17424
|
+
peerDeviceId: shortId3(tunnel.peer.deviceId)
|
|
17425
|
+
});
|
|
17426
|
+
return;
|
|
17427
|
+
}
|
|
17428
|
+
await this.completeHandshake(tunnel, payload);
|
|
17429
|
+
}
|
|
17430
|
+
async completeHandshake(tunnel, payload) {
|
|
16858
17431
|
tunnel.noise.readHandshake(fromBase64Url2(payload.data));
|
|
16859
17432
|
const reply = tunnel.noise.writeHandshake();
|
|
16860
17433
|
if (!tunnel.noise.complete) throw new ControlConnectionError("SECURE_CHANNEL_FAILED", "Noise IK handshake did not complete.");
|
|
@@ -16879,7 +17452,16 @@ var HostServerConnection = class {
|
|
|
16879
17452
|
transport: mode
|
|
16880
17453
|
});
|
|
16881
17454
|
}
|
|
17455
|
+
async resumePendingHandshake(tunnel) {
|
|
17456
|
+
const payload = tunnel.pendingHandshake;
|
|
17457
|
+
if (payload === void 0 || tunnel.channel !== void 0 || tunnel.transport === "negotiating") return;
|
|
17458
|
+
tunnel.pendingHandshake = void 0;
|
|
17459
|
+
await this.completeHandshake(tunnel, payload);
|
|
17460
|
+
}
|
|
16882
17461
|
async handleRelay(payload) {
|
|
17462
|
+
if (!this.negotiatedCapabilities.includes("transport.relay")) {
|
|
17463
|
+
throw new ControlConnectionError("INVALID_MESSAGE", "Server forwarded Relay without negotiating it.");
|
|
17464
|
+
}
|
|
16883
17465
|
if (payload.targetDeviceId !== this.identity.deviceId) {
|
|
16884
17466
|
throw new ControlConnectionError("INVALID_MESSAGE", "Relay frame target does not match this Host.");
|
|
16885
17467
|
}
|
|
@@ -16898,6 +17480,9 @@ var HostServerConnection = class {
|
|
|
16898
17480
|
}
|
|
16899
17481
|
}
|
|
16900
17482
|
async handleSignalOffer(payload) {
|
|
17483
|
+
if (!this.canUseWebRtc()) {
|
|
17484
|
+
throw new ControlConnectionError("INVALID_MESSAGE", "Server forwarded WebRTC signaling without negotiating it.");
|
|
17485
|
+
}
|
|
16901
17486
|
const tunnel = this.tunnels.get(payload.connectionId);
|
|
16902
17487
|
if (tunnel === void 0 || payload.targetDeviceId !== this.identity.deviceId) {
|
|
16903
17488
|
this.logger.warn("stale webrtc offer ignored", { connectionId: shortId3(payload.connectionId) });
|
|
@@ -16959,10 +17544,54 @@ var HostServerConnection = class {
|
|
|
16959
17544
|
rtc.handleSignal({ type: "offer", sdp: payload.sdp });
|
|
16960
17545
|
}
|
|
16961
17546
|
handleSignalIce(payload) {
|
|
17547
|
+
if (!this.canUseWebRtc()) {
|
|
17548
|
+
throw new ControlConnectionError("INVALID_MESSAGE", "Server forwarded WebRTC signaling without negotiating it.");
|
|
17549
|
+
}
|
|
16962
17550
|
const tunnel = this.tunnels.get(payload.connectionId);
|
|
16963
17551
|
if (tunnel === void 0 || payload.targetDeviceId !== this.identity.deviceId) return;
|
|
16964
17552
|
tunnel.rtc?.handleSignal({ type: "ice", candidate: payload.candidate });
|
|
16965
17553
|
}
|
|
17554
|
+
async handleTransportSelected(payload) {
|
|
17555
|
+
const tunnel = this.tunnels.get(payload.connectionId);
|
|
17556
|
+
if (tunnel === void 0 || payload.targetDeviceId !== this.identity.deviceId) {
|
|
17557
|
+
this.logger.warn("stale transport selection ignored", { connectionId: shortId3(payload.connectionId) });
|
|
17558
|
+
return;
|
|
17559
|
+
}
|
|
17560
|
+
if (tunnel.channel !== void 0) {
|
|
17561
|
+
if (tunnel.transport !== payload.transport) {
|
|
17562
|
+
this.logger.warn("late transport selection ignored", {
|
|
17563
|
+
connectionId: shortId3(tunnel.connectionId),
|
|
17564
|
+
selected: payload.transport
|
|
17565
|
+
});
|
|
17566
|
+
}
|
|
17567
|
+
return;
|
|
17568
|
+
}
|
|
17569
|
+
const requiredCapability = `transport.${payload.transport}`;
|
|
17570
|
+
if (!this.negotiatedCapabilities.includes(requiredCapability)) {
|
|
17571
|
+
throw new ControlConnectionError(
|
|
17572
|
+
"INVALID_MESSAGE",
|
|
17573
|
+
`Client selected unnegotiated transport: ${payload.transport}`
|
|
17574
|
+
);
|
|
17575
|
+
}
|
|
17576
|
+
if (payload.transport === "relay") {
|
|
17577
|
+
const rtc = tunnel.rtc;
|
|
17578
|
+
tunnel.rtc = void 0;
|
|
17579
|
+
tunnel.transport = "relay";
|
|
17580
|
+
tunnel.transportMode = void 0;
|
|
17581
|
+
await rtc?.close();
|
|
17582
|
+
await this.resumePendingHandshake(tunnel);
|
|
17583
|
+
return;
|
|
17584
|
+
}
|
|
17585
|
+
if (tunnel.rtc === void 0) {
|
|
17586
|
+
throw new ControlConnectionError("INVALID_MESSAGE", "Client selected WebRTC before creating a data channel.");
|
|
17587
|
+
}
|
|
17588
|
+
tunnel.transport = payload.transport;
|
|
17589
|
+
tunnel.transportMode = tunnel.rtc.selectedPathMode();
|
|
17590
|
+
await this.resumePendingHandshake(tunnel);
|
|
17591
|
+
}
|
|
17592
|
+
canUseWebRtc() {
|
|
17593
|
+
return this.negotiatedCapabilities.includes("transport.p2p") || this.negotiatedCapabilities.includes("transport.turn");
|
|
17594
|
+
}
|
|
16966
17595
|
sendRtcSignal(tunnel, signal) {
|
|
16967
17596
|
if (signal.type === "answer") {
|
|
16968
17597
|
this.sendControl("signal.answer", {
|
|
@@ -16980,6 +17609,20 @@ var HostServerConnection = class {
|
|
|
16980
17609
|
}
|
|
16981
17610
|
handleRtcOpened(tunnel, selected) {
|
|
16982
17611
|
if (this.tunnels.get(tunnel.connectionId) !== tunnel || tunnel.rtc === void 0) return;
|
|
17612
|
+
const requiredCapability = selected === "turn" ? "transport.turn" : "transport.p2p";
|
|
17613
|
+
if (!this.negotiatedCapabilities.includes(requiredCapability)) {
|
|
17614
|
+
const error = new Error(`WebRTC selected unnegotiated transport: ${selected}`);
|
|
17615
|
+
if (!this.negotiatedCapabilities.includes("transport.relay")) {
|
|
17616
|
+
void this.dropTunnel(tunnel.connectionId, "CONNECTION_FAILED");
|
|
17617
|
+
return;
|
|
17618
|
+
}
|
|
17619
|
+
void this.handleRtcFailed(
|
|
17620
|
+
tunnel,
|
|
17621
|
+
tunnel.rtc,
|
|
17622
|
+
error
|
|
17623
|
+
);
|
|
17624
|
+
return;
|
|
17625
|
+
}
|
|
16983
17626
|
tunnel.transport = selected;
|
|
16984
17627
|
tunnel.transportMode = tunnel.rtc.selectedPathMode();
|
|
16985
17628
|
this.sendTransportSelected(tunnel, selected);
|
|
@@ -16989,6 +17632,13 @@ var HostServerConnection = class {
|
|
|
16989
17632
|
transport: tunnel.transportMode ?? selected,
|
|
16990
17633
|
...webrtcDiagnosticsLogFields2(rtcDiagnostics(tunnel.rtc))
|
|
16991
17634
|
});
|
|
17635
|
+
void this.resumePendingHandshake(tunnel).catch((error) => {
|
|
17636
|
+
this.logger.warn("pending secure handshake failed", {
|
|
17637
|
+
connectionId: shortId3(tunnel.connectionId),
|
|
17638
|
+
reason: diagnosticReason3(error)
|
|
17639
|
+
});
|
|
17640
|
+
void this.dropTunnel(tunnel.connectionId, "SECURE_CHANNEL_FAILED");
|
|
17641
|
+
});
|
|
16992
17642
|
}
|
|
16993
17643
|
async handleRtcFailed(tunnel, rtc, error) {
|
|
16994
17644
|
if (this.tunnels.get(tunnel.connectionId) !== tunnel || tunnel.rtc !== rtc) return;
|
|
@@ -17009,6 +17659,7 @@ var HostServerConnection = class {
|
|
|
17009
17659
|
reason: diagnosticReason3(error),
|
|
17010
17660
|
...webrtcDiagnosticsLogFields2(rtcDiagnostics(rtc))
|
|
17011
17661
|
});
|
|
17662
|
+
await this.resumePendingHandshake(tunnel);
|
|
17012
17663
|
}
|
|
17013
17664
|
sendTransportSelected(tunnel, transport) {
|
|
17014
17665
|
this.sendControl("transport.selected", {
|
|
@@ -17208,6 +17859,13 @@ function requireSignalIce(value) {
|
|
|
17208
17859
|
}
|
|
17209
17860
|
return payload;
|
|
17210
17861
|
}
|
|
17862
|
+
function requireTransportSelected(value) {
|
|
17863
|
+
const payload = requireObject(value);
|
|
17864
|
+
if (typeof payload.connectionId !== "string" || typeof payload.targetDeviceId !== "string" || payload.transport !== "p2p" && payload.transport !== "turn" && payload.transport !== "relay") {
|
|
17865
|
+
throw new ControlConnectionError("INVALID_MESSAGE", "transport.selected payload is invalid.");
|
|
17866
|
+
}
|
|
17867
|
+
return payload;
|
|
17868
|
+
}
|
|
17211
17869
|
function requireControlError(value) {
|
|
17212
17870
|
const payload = requireObject(value);
|
|
17213
17871
|
if (typeof payload.code !== "string" || typeof payload.message !== "string" || payload.connectionId !== void 0 && (typeof payload.connectionId !== "string" || payload.connectionId === "")) {
|
|
@@ -18069,28 +18727,333 @@ function concatChunks(chunks, totalBytes) {
|
|
|
18069
18727
|
return result;
|
|
18070
18728
|
}
|
|
18071
18729
|
|
|
18730
|
+
// src/harness-remote-bridge.ts
|
|
18731
|
+
var endpointSchema = external_exports.string().min(1).max(128).regex(/^(?:\$events(?:\/result)?|[A-Za-z0-9_$.-]+\/[A-Za-z0-9_$.-]+)$/);
|
|
18732
|
+
var callSchema3 = external_exports.object({ endpoint: endpointSchema, payload: external_exports.unknown() }).strict();
|
|
18733
|
+
var streamOpenSchema2 = external_exports.object({
|
|
18734
|
+
streamId: external_exports.string().min(1).max(128),
|
|
18735
|
+
endpoint: endpointSchema,
|
|
18736
|
+
payload: external_exports.unknown()
|
|
18737
|
+
}).strict();
|
|
18738
|
+
var streamCloseSchema2 = external_exports.object({ streamId: external_exports.string().min(1).max(128) }).strict();
|
|
18739
|
+
var transferOpenSchema2 = external_exports.object({
|
|
18740
|
+
transferId: external_exports.string().uuid(),
|
|
18741
|
+
totalBytes: external_exports.number().int().positive().max(MAX_HARNESS_API_TRANSFER_BYTES),
|
|
18742
|
+
totalChunks: external_exports.number().int().positive()
|
|
18743
|
+
}).strict();
|
|
18744
|
+
var transferChunkSchema2 = external_exports.object({
|
|
18745
|
+
transferId: external_exports.string().uuid(),
|
|
18746
|
+
index: external_exports.number().int().nonnegative(),
|
|
18747
|
+
data: external_exports.string().min(1)
|
|
18748
|
+
}).strict();
|
|
18749
|
+
var transferIdSchema2 = external_exports.object({ transferId: external_exports.string().uuid() }).strict();
|
|
18750
|
+
var transferReadSchema2 = external_exports.object({
|
|
18751
|
+
transferId: external_exports.string().uuid(),
|
|
18752
|
+
index: external_exports.number().int().nonnegative()
|
|
18753
|
+
}).strict();
|
|
18754
|
+
var MAX_ACTIVE_STREAMS = 16;
|
|
18755
|
+
var MAX_ACTIVE_TRANSFERS = 2;
|
|
18756
|
+
var TRANSFER_IDLE_MS = 2 * 6e4;
|
|
18757
|
+
var INLINE_TRANSFER_RESPONSE_BYTES2 = 2 * 1024 * 1024;
|
|
18758
|
+
var HARNESS_REMOTE_ALLOWLIST = [
|
|
18759
|
+
"$events",
|
|
18760
|
+
"$events/result",
|
|
18761
|
+
"agentPresets/list",
|
|
18762
|
+
"agentPresets/read",
|
|
18763
|
+
"agentPresets/select",
|
|
18764
|
+
"commands/execute",
|
|
18765
|
+
"commands/list",
|
|
18766
|
+
"credentials/describe",
|
|
18767
|
+
"credentials/set",
|
|
18768
|
+
"credentials/unset",
|
|
18769
|
+
"directoryPicker/list",
|
|
18770
|
+
"fileReferences/list",
|
|
18771
|
+
"goals/clear",
|
|
18772
|
+
"goals/complete",
|
|
18773
|
+
"goals/create",
|
|
18774
|
+
"goals/edit",
|
|
18775
|
+
"goals/pause",
|
|
18776
|
+
"goals/resume",
|
|
18777
|
+
"llm/discoverModels",
|
|
18778
|
+
"llm/listConfigurableProviders",
|
|
18779
|
+
"llm/listProviders",
|
|
18780
|
+
"messageFeedback/delete",
|
|
18781
|
+
"messageFeedback/list",
|
|
18782
|
+
"messageFeedback/put",
|
|
18783
|
+
"pluginInventory/list",
|
|
18784
|
+
"session/attachment",
|
|
18785
|
+
"session/cancel",
|
|
18786
|
+
"session/control",
|
|
18787
|
+
"session/create",
|
|
18788
|
+
"session/follow",
|
|
18789
|
+
"session/fork",
|
|
18790
|
+
"session/list",
|
|
18791
|
+
"session/modelCatalog",
|
|
18792
|
+
"session/page",
|
|
18793
|
+
"session/prompt",
|
|
18794
|
+
"session/rename",
|
|
18795
|
+
"session/search",
|
|
18796
|
+
"session/selectModel",
|
|
18797
|
+
"session/updateQueue",
|
|
18798
|
+
"sessionReferenceResolver/candidates",
|
|
18799
|
+
"settings/describe",
|
|
18800
|
+
"settings/mutate",
|
|
18801
|
+
"settings/replace",
|
|
18802
|
+
"settings/update",
|
|
18803
|
+
"skills/list",
|
|
18804
|
+
"subagents/interruptByParent",
|
|
18805
|
+
"subagents/list",
|
|
18806
|
+
"subagents/prompt",
|
|
18807
|
+
"workspace/archiveSession",
|
|
18808
|
+
"workspace/create",
|
|
18809
|
+
"workspace/delete",
|
|
18810
|
+
"workspace/follow",
|
|
18811
|
+
"workspace/insertBefore",
|
|
18812
|
+
"workspace/insertSessionBefore",
|
|
18813
|
+
"workspace/rename"
|
|
18814
|
+
];
|
|
18815
|
+
var allowedEndpoints = new Set(HARNESS_REMOTE_ALLOWLIST);
|
|
18816
|
+
var HarnessRemoteBridge = class {
|
|
18817
|
+
constructor(gateway, publish, logger) {
|
|
18818
|
+
this.gateway = gateway;
|
|
18819
|
+
this.publish = publish;
|
|
18820
|
+
this.logger = logger;
|
|
18821
|
+
}
|
|
18822
|
+
streams = /* @__PURE__ */ new Map();
|
|
18823
|
+
incomingTransfers = /* @__PURE__ */ new Map();
|
|
18824
|
+
outgoingTransfers = /* @__PURE__ */ new Map();
|
|
18825
|
+
async call(input) {
|
|
18826
|
+
const params = callSchema3.parse(input);
|
|
18827
|
+
this.assertAllowed(params.endpoint);
|
|
18828
|
+
const startedAt = performance.now();
|
|
18829
|
+
try {
|
|
18830
|
+
const result = await this.gateway.dispatch(params.endpoint, params.payload, AbortSignal.timeout(6e4));
|
|
18831
|
+
this.logger?.debug("harness remote call ok", {
|
|
18832
|
+
endpoint: params.endpoint,
|
|
18833
|
+
durationMs: Math.round(performance.now() - startedAt)
|
|
18834
|
+
});
|
|
18835
|
+
return result;
|
|
18836
|
+
} catch (error) {
|
|
18837
|
+
this.logger?.warn("harness remote call failed", {
|
|
18838
|
+
endpoint: params.endpoint,
|
|
18839
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
18840
|
+
code: this.gateway.failure(error).code
|
|
18841
|
+
});
|
|
18842
|
+
throw error;
|
|
18843
|
+
}
|
|
18844
|
+
}
|
|
18845
|
+
openTransfer(input) {
|
|
18846
|
+
this.pruneTransfers();
|
|
18847
|
+
const params = transferOpenSchema2.parse(input);
|
|
18848
|
+
if (params.totalChunks !== Math.ceil(params.totalBytes / HARNESS_API_TRANSFER_CHUNK_BYTES)) {
|
|
18849
|
+
throw new RpcError("INVALID_MESSAGE", "The Harness Remote transfer chunk count is invalid.");
|
|
18850
|
+
}
|
|
18851
|
+
if (this.incomingTransfers.has(params.transferId) || this.outgoingTransfers.has(params.transferId)) {
|
|
18852
|
+
throw new RpcError("REQUEST_CONFLICT", "The Harness Remote transfer id is already active.");
|
|
18853
|
+
}
|
|
18854
|
+
if (this.incomingTransfers.size >= MAX_ACTIVE_TRANSFERS) {
|
|
18855
|
+
throw new RpcError("RATE_LIMITED", "Too many Harness Remote transfers are active.", void 0, true);
|
|
18856
|
+
}
|
|
18857
|
+
this.incomingTransfers.set(params.transferId, {
|
|
18858
|
+
totalBytes: params.totalBytes,
|
|
18859
|
+
totalChunks: params.totalChunks,
|
|
18860
|
+
chunks: [],
|
|
18861
|
+
receivedBytes: 0,
|
|
18862
|
+
touchedAt: Date.now()
|
|
18863
|
+
});
|
|
18864
|
+
return { opened: true, transferId: params.transferId };
|
|
18865
|
+
}
|
|
18866
|
+
appendTransfer(input) {
|
|
18867
|
+
this.pruneTransfers();
|
|
18868
|
+
const params = transferChunkSchema2.parse(input);
|
|
18869
|
+
const transfer = this.incomingTransfers.get(params.transferId);
|
|
18870
|
+
if (transfer === void 0) throw new RpcError("TRANSFER_NOT_FOUND", "The Harness Remote transfer is not active.");
|
|
18871
|
+
if (params.index !== transfer.chunks.length || params.index >= transfer.totalChunks) {
|
|
18872
|
+
this.incomingTransfers.delete(params.transferId);
|
|
18873
|
+
throw new RpcError("INVALID_MESSAGE", "Harness Remote transfer chunks must arrive exactly once and in order.");
|
|
18874
|
+
}
|
|
18875
|
+
const chunk = decodeCanonicalBase642(params.data);
|
|
18876
|
+
const expectedBytes = Math.min(
|
|
18877
|
+
HARNESS_API_TRANSFER_CHUNK_BYTES,
|
|
18878
|
+
transfer.totalBytes - params.index * HARNESS_API_TRANSFER_CHUNK_BYTES
|
|
18879
|
+
);
|
|
18880
|
+
if (chunk.byteLength !== expectedBytes) {
|
|
18881
|
+
this.incomingTransfers.delete(params.transferId);
|
|
18882
|
+
throw new RpcError("INVALID_MESSAGE", "The Harness Remote transfer chunk size is invalid.");
|
|
18883
|
+
}
|
|
18884
|
+
transfer.chunks.push(chunk);
|
|
18885
|
+
transfer.receivedBytes += chunk.byteLength;
|
|
18886
|
+
transfer.touchedAt = Date.now();
|
|
18887
|
+
return { accepted: true, transferId: params.transferId, index: params.index };
|
|
18888
|
+
}
|
|
18889
|
+
async commitTransfer(input) {
|
|
18890
|
+
this.pruneTransfers();
|
|
18891
|
+
const params = transferIdSchema2.parse(input);
|
|
18892
|
+
const transfer = this.incomingTransfers.get(params.transferId);
|
|
18893
|
+
if (transfer === void 0) throw new RpcError("TRANSFER_NOT_FOUND", "The Harness Remote transfer is not active.");
|
|
18894
|
+
this.incomingTransfers.delete(params.transferId);
|
|
18895
|
+
if (transfer.chunks.length !== transfer.totalChunks || transfer.receivedBytes !== transfer.totalBytes) {
|
|
18896
|
+
throw new RpcError("INVALID_MESSAGE", "The Harness Remote transfer is incomplete.");
|
|
18897
|
+
}
|
|
18898
|
+
let request;
|
|
18899
|
+
try {
|
|
18900
|
+
request = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(concatChunks2(transfer.chunks, transfer.totalBytes)));
|
|
18901
|
+
} catch {
|
|
18902
|
+
throw new RpcError("INVALID_MESSAGE", "The Harness Remote transfer does not contain a valid request.");
|
|
18903
|
+
}
|
|
18904
|
+
const response = await this.call(callSchema3.parse(request));
|
|
18905
|
+
const responseBytes = new TextEncoder().encode(JSON.stringify(response));
|
|
18906
|
+
if (responseBytes.byteLength <= INLINE_TRANSFER_RESPONSE_BYTES2) return { kind: "inline", response };
|
|
18907
|
+
if (responseBytes.byteLength > MAX_HARNESS_API_TRANSFER_BYTES) {
|
|
18908
|
+
throw new RpcError("RESPONSE_TOO_LARGE", "The Harness Remote response exceeds the bounded transfer limit.");
|
|
18909
|
+
}
|
|
18910
|
+
if (this.outgoingTransfers.size >= MAX_ACTIVE_TRANSFERS) {
|
|
18911
|
+
throw new RpcError("RATE_LIMITED", "Too many Harness Remote response transfers are active.", void 0, true);
|
|
18912
|
+
}
|
|
18913
|
+
const totalChunks = Math.ceil(responseBytes.byteLength / HARNESS_API_TRANSFER_CHUNK_BYTES);
|
|
18914
|
+
this.outgoingTransfers.set(params.transferId, {
|
|
18915
|
+
bytes: responseBytes,
|
|
18916
|
+
totalChunks,
|
|
18917
|
+
nextIndex: 0,
|
|
18918
|
+
touchedAt: Date.now()
|
|
18919
|
+
});
|
|
18920
|
+
return { kind: "chunked", transferId: params.transferId, totalBytes: responseBytes.byteLength, totalChunks };
|
|
18921
|
+
}
|
|
18922
|
+
readTransfer(input) {
|
|
18923
|
+
this.pruneTransfers();
|
|
18924
|
+
const params = transferReadSchema2.parse(input);
|
|
18925
|
+
const transfer = this.outgoingTransfers.get(params.transferId);
|
|
18926
|
+
if (transfer === void 0) throw new RpcError("TRANSFER_NOT_FOUND", "The Harness Remote response transfer is not active.");
|
|
18927
|
+
if (params.index !== transfer.nextIndex || params.index >= transfer.totalChunks) {
|
|
18928
|
+
this.outgoingTransfers.delete(params.transferId);
|
|
18929
|
+
throw new RpcError("INVALID_MESSAGE", "Harness Remote response chunks must be read exactly once and in order.");
|
|
18930
|
+
}
|
|
18931
|
+
const start = params.index * HARNESS_API_TRANSFER_CHUNK_BYTES;
|
|
18932
|
+
const end = Math.min(start + HARNESS_API_TRANSFER_CHUNK_BYTES, transfer.bytes.byteLength);
|
|
18933
|
+
transfer.nextIndex += 1;
|
|
18934
|
+
transfer.touchedAt = Date.now();
|
|
18935
|
+
return {
|
|
18936
|
+
transferId: params.transferId,
|
|
18937
|
+
index: params.index,
|
|
18938
|
+
data: Buffer.from(transfer.bytes.subarray(start, end)).toString("base64")
|
|
18939
|
+
};
|
|
18940
|
+
}
|
|
18941
|
+
closeTransfer(input) {
|
|
18942
|
+
const params = transferIdSchema2.parse(input);
|
|
18943
|
+
const closed = this.incomingTransfers.delete(params.transferId) || this.outgoingTransfers.delete(params.transferId);
|
|
18944
|
+
return { closed, transferId: params.transferId };
|
|
18945
|
+
}
|
|
18946
|
+
async openStream(input) {
|
|
18947
|
+
const params = streamOpenSchema2.parse(input);
|
|
18948
|
+
this.assertAllowed(params.endpoint);
|
|
18949
|
+
if (this.streams.has(params.streamId)) throw new RpcError("REQUEST_CONFLICT", "The Harness Remote stream is already open.");
|
|
18950
|
+
if (this.streams.size >= MAX_ACTIVE_STREAMS) {
|
|
18951
|
+
throw new RpcError("RATE_LIMITED", "Too many Harness Remote streams are open.", void 0, true);
|
|
18952
|
+
}
|
|
18953
|
+
const controller = new AbortController();
|
|
18954
|
+
const source = await this.gateway.open(params.endpoint, params.payload, controller.signal);
|
|
18955
|
+
this.streams.set(params.streamId, { controller });
|
|
18956
|
+
void this.pump(params.streamId, source, controller.signal);
|
|
18957
|
+
return { opened: true, streamId: params.streamId };
|
|
18958
|
+
}
|
|
18959
|
+
closeStream(input) {
|
|
18960
|
+
const params = streamCloseSchema2.parse(input);
|
|
18961
|
+
const active = this.streams.get(params.streamId);
|
|
18962
|
+
if (active !== void 0) {
|
|
18963
|
+
this.streams.delete(params.streamId);
|
|
18964
|
+
active.controller.abort("client-closed");
|
|
18965
|
+
}
|
|
18966
|
+
return { closed: active !== void 0, streamId: params.streamId };
|
|
18967
|
+
}
|
|
18968
|
+
async closeAll(reason = "peer-disconnected") {
|
|
18969
|
+
const streams = [...this.streams.entries()];
|
|
18970
|
+
this.streams.clear();
|
|
18971
|
+
this.incomingTransfers.clear();
|
|
18972
|
+
this.outgoingTransfers.clear();
|
|
18973
|
+
for (const [, stream] of streams) stream.controller.abort(reason);
|
|
18974
|
+
}
|
|
18975
|
+
assertAllowed(endpoint) {
|
|
18976
|
+
if (!allowedEndpoints.has(endpoint)) {
|
|
18977
|
+
throw new RpcError("METHOD_NOT_ALLOWED", "The requested Harness Remote endpoint is not allowed.");
|
|
18978
|
+
}
|
|
18979
|
+
if (endpoint === "$events" && !this.gateway.supportsCarrier) {
|
|
18980
|
+
throw new RpcError("FEATURE_NOT_SUPPORTED", "This Harness version does not support Remote event transport.");
|
|
18981
|
+
}
|
|
18982
|
+
}
|
|
18983
|
+
async pump(streamId, source, signal) {
|
|
18984
|
+
let reason = "completed";
|
|
18985
|
+
let failure;
|
|
18986
|
+
try {
|
|
18987
|
+
for await (const value of source) {
|
|
18988
|
+
if (signal.aborted) break;
|
|
18989
|
+
await this.publish("harness.remote.frame", {
|
|
18990
|
+
streamId,
|
|
18991
|
+
hasValue: true,
|
|
18992
|
+
...value === void 0 ? {} : { value }
|
|
18993
|
+
});
|
|
18994
|
+
}
|
|
18995
|
+
if (signal.aborted) reason = "cancelled";
|
|
18996
|
+
} catch (error) {
|
|
18997
|
+
reason = signal.aborted ? "cancelled" : "failed";
|
|
18998
|
+
if (!signal.aborted) failure = this.gateway.failure(error);
|
|
18999
|
+
} finally {
|
|
19000
|
+
this.streams.delete(streamId);
|
|
19001
|
+
await this.publish("harness.remote.stream.closed", {
|
|
19002
|
+
streamId,
|
|
19003
|
+
reason,
|
|
19004
|
+
...failure === void 0 ? {} : { failure }
|
|
19005
|
+
}).catch(() => void 0);
|
|
19006
|
+
}
|
|
19007
|
+
}
|
|
19008
|
+
pruneTransfers() {
|
|
19009
|
+
const cutoff = Date.now() - TRANSFER_IDLE_MS;
|
|
19010
|
+
for (const [id, transfer] of this.incomingTransfers) if (transfer.touchedAt < cutoff) this.incomingTransfers.delete(id);
|
|
19011
|
+
for (const [id, transfer] of this.outgoingTransfers) if (transfer.touchedAt < cutoff) this.outgoingTransfers.delete(id);
|
|
19012
|
+
}
|
|
19013
|
+
};
|
|
19014
|
+
function decodeCanonicalBase642(value) {
|
|
19015
|
+
const bytes = Buffer.from(value, "base64");
|
|
19016
|
+
if (bytes.toString("base64") !== value) throw new RpcError("INVALID_MESSAGE", "The Harness Remote transfer chunk is invalid.");
|
|
19017
|
+
return bytes;
|
|
19018
|
+
}
|
|
19019
|
+
function concatChunks2(chunks, totalBytes) {
|
|
19020
|
+
const result = new Uint8Array(totalBytes);
|
|
19021
|
+
let offset = 0;
|
|
19022
|
+
for (const chunk of chunks) {
|
|
19023
|
+
result.set(chunk, offset);
|
|
19024
|
+
offset += chunk.byteLength;
|
|
19025
|
+
}
|
|
19026
|
+
return result;
|
|
19027
|
+
}
|
|
19028
|
+
|
|
18072
19029
|
// src/service.ts
|
|
18073
19030
|
var HostPluginRuntime = class {
|
|
18074
|
-
constructor(config, identities, apiProxy, logger,
|
|
19031
|
+
constructor(config, identities, apiProxy, logger, localGateway, fileViewerHost) {
|
|
18075
19032
|
this.config = config;
|
|
18076
19033
|
this.identities = identities;
|
|
18077
19034
|
this.apiProxy = apiProxy;
|
|
18078
19035
|
this.logger = logger;
|
|
19036
|
+
this.localGateway = localGateway;
|
|
18079
19037
|
this.fileViewerHost = fileViewerHost;
|
|
18080
19038
|
this.connections = new ConnectionController(this.identities, (_context, send) => {
|
|
18081
|
-
const harnessApi = new HarnessApiBridge(
|
|
19039
|
+
const harnessApi = this.apiProxy === void 0 ? void 0 : new HarnessApiBridge(
|
|
18082
19040
|
this.apiProxy,
|
|
18083
19041
|
(event, data) => send(createEvent(event, data)),
|
|
18084
19042
|
void 0,
|
|
18085
19043
|
this.logger,
|
|
18086
|
-
|
|
19044
|
+
this.localGateway,
|
|
18087
19045
|
this.harnessVersion
|
|
18088
19046
|
);
|
|
19047
|
+
const harnessRemote = this.localGateway?.supportsCarrier === true ? new HarnessRemoteBridge(
|
|
19048
|
+
this.localGateway,
|
|
19049
|
+
(event, data) => send(createEvent(event, data)),
|
|
19050
|
+
this.logger
|
|
19051
|
+
) : void 0;
|
|
18089
19052
|
const fileViewer = new RemoteFileViewerBridge(
|
|
18090
19053
|
() => this.fileViewerHost?.(),
|
|
18091
19054
|
this.logger
|
|
18092
19055
|
);
|
|
18093
|
-
return new RpcRouter(harnessApi, void 0, this.logger, fileViewer);
|
|
19056
|
+
return new RpcRouter(harnessApi, void 0, this.logger, fileViewer, harnessRemote, () => this.hostCapabilities());
|
|
18094
19057
|
}, this.logger);
|
|
18095
19058
|
if (config.serverUrl !== void 0) {
|
|
18096
19059
|
this.serverApi = new HostServerApi(config.serverUrl, new ServerCredentialStore(identities.directory));
|
|
@@ -18231,7 +19194,7 @@ var HostPluginRuntime = class {
|
|
|
18231
19194
|
this.logger,
|
|
18232
19195
|
void 0,
|
|
18233
19196
|
this.config.forceRelay ? void 0 : () => loadNodeRtcFactory({ routeTargets: this.config.serverUrl === void 0 ? [] : [this.config.serverUrl] }),
|
|
18234
|
-
() => this.
|
|
19197
|
+
() => this.hostCapabilities(),
|
|
18235
19198
|
this.harnessVersion
|
|
18236
19199
|
);
|
|
18237
19200
|
}
|
|
@@ -18239,7 +19202,8 @@ var HostPluginRuntime = class {
|
|
|
18239
19202
|
let reportedVersion;
|
|
18240
19203
|
let errorCode2;
|
|
18241
19204
|
try {
|
|
18242
|
-
const response = await this.apiProxy
|
|
19205
|
+
const response = await this.apiProxy?.host.describe({ rpcId: randomUUID(), payload: {} });
|
|
19206
|
+
if (response === void 0) throw new Error("ApiProxy is unavailable");
|
|
18243
19207
|
if (!response.result.ok) {
|
|
18244
19208
|
errorCode2 = response.result.error.code;
|
|
18245
19209
|
} else {
|
|
@@ -18253,6 +19217,15 @@ var HostPluginRuntime = class {
|
|
|
18253
19217
|
this.logger.warn("Harness version is unavailable", errorCode2 === void 0 ? void 0 : { code: errorCode2 });
|
|
18254
19218
|
return void 0;
|
|
18255
19219
|
}
|
|
19220
|
+
hostCapabilities() {
|
|
19221
|
+
const capabilities = [];
|
|
19222
|
+
if (this.apiProxy !== void 0) capabilities.push("harness.api.v1", "harness.api.transfer.v1");
|
|
19223
|
+
if (this.localGateway?.supportsCarrier === true) {
|
|
19224
|
+
capabilities.push("harness.remote.v1", "harness.remote.transfer.v1");
|
|
19225
|
+
}
|
|
19226
|
+
if (this.fileViewerHost?.() !== void 0) capabilities.push("fileviewer.read.v1");
|
|
19227
|
+
return capabilities;
|
|
19228
|
+
}
|
|
18256
19229
|
};
|
|
18257
19230
|
function shortId5(value) {
|
|
18258
19231
|
return value.length <= 12 ? value : `${value.slice(0, 8)}\u2026${value.slice(-4)}`;
|
|
@@ -18335,7 +19308,13 @@ function decodeBase64(value) {
|
|
|
18335
19308
|
var name = "ds-harness-remote";
|
|
18336
19309
|
var legacyLoaderModuleNames = /* @__PURE__ */ new Set(["dsh-remote", "@dsh-remote/plugin"]);
|
|
18337
19310
|
function apply(ctx, input = {}) {
|
|
18338
|
-
ctx.inject(["settings", "
|
|
19311
|
+
ctx.inject(["settings", "connection", "typertGateway"], (runtimeContext) => {
|
|
19312
|
+
const gateway = runtimeContext.get("typertGateway");
|
|
19313
|
+
if (runtimeContext.get("apiProxy") !== void 0 || new TypertGatewaySwitch(gateway).supportsCarrier()) {
|
|
19314
|
+
return activate(runtimeContext, input);
|
|
19315
|
+
}
|
|
19316
|
+
runtimeContext.inject(["apiProxy"], (legacyContext) => activate(legacyContext, input));
|
|
19317
|
+
});
|
|
18339
19318
|
}
|
|
18340
19319
|
async function activate(ctx, input) {
|
|
18341
19320
|
const settings = ctx.get("settings");
|
|
@@ -18380,12 +19359,12 @@ async function activate(ctx, input) {
|
|
|
18380
19359
|
hostIdentities,
|
|
18381
19360
|
apiProxy,
|
|
18382
19361
|
logger,
|
|
18383
|
-
|
|
19362
|
+
localTypertGateway,
|
|
18384
19363
|
() => ctx.get("fileViewerHost")
|
|
18385
19364
|
);
|
|
18386
19365
|
let clientRuntime;
|
|
18387
19366
|
const hostControl = runtime;
|
|
18388
|
-
if (config.serverUrl !== void 0 &&
|
|
19367
|
+
if (config.serverUrl !== void 0 && connection !== void 0) {
|
|
18389
19368
|
const clientIdentities = new IdentityStore({
|
|
18390
19369
|
directory: serverStorageDirectory(defaultIdentityDirectory, config.serverUrl, "client")
|
|
18391
19370
|
});
|
|
@@ -18412,6 +19391,7 @@ async function activate(ctx, input) {
|
|
|
18412
19391
|
logger.warn("client remote mode is unavailable", {
|
|
18413
19392
|
serverConfigured: config.serverUrl !== void 0,
|
|
18414
19393
|
apiProxyAvailable: apiProxy !== void 0,
|
|
19394
|
+
remoteGatewayAvailable: localTypertGateway.supportsCarrier,
|
|
18415
19395
|
connectionAvailable: connection !== void 0
|
|
18416
19396
|
});
|
|
18417
19397
|
}
|
|
@@ -18461,8 +19441,10 @@ export {
|
|
|
18461
19441
|
ConnectionController,
|
|
18462
19442
|
ConnectionRejectedError,
|
|
18463
19443
|
HARNESS_API_ALLOWLIST,
|
|
19444
|
+
HARNESS_REMOTE_ALLOWLIST,
|
|
18464
19445
|
HOST_CAPABILITIES,
|
|
18465
19446
|
HarnessApiBridge,
|
|
19447
|
+
HarnessRemoteBridge,
|
|
18466
19448
|
HostPluginRuntime,
|
|
18467
19449
|
HostServerApi,
|
|
18468
19450
|
HostServerConnection,
|
|
@@ -18471,6 +19453,7 @@ export {
|
|
|
18471
19453
|
PluginControlRuntime,
|
|
18472
19454
|
RemoteFileViewerBridge,
|
|
18473
19455
|
RemoteHarnessApiProxy,
|
|
19456
|
+
RemoteTypertGateway2 as RemoteTypertGateway,
|
|
18474
19457
|
RpcError,
|
|
18475
19458
|
RpcRouter,
|
|
18476
19459
|
ServerApiError,
|