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.
Files changed (49) hide show
  1. package/README.md +26 -24
  2. package/dist/client-runtime.d.ts +9 -4
  3. package/dist/client-runtime.d.ts.map +1 -1
  4. package/dist/client-runtime.js +140 -23
  5. package/dist/client-runtime.js.map +1 -1
  6. package/dist/harness-api-bridge.d.ts +2 -9
  7. package/dist/harness-api-bridge.d.ts.map +1 -1
  8. package/dist/harness-api-bridge.js.map +1 -1
  9. package/dist/harness-remote-bridge.d.ts +46 -0
  10. package/dist/harness-remote-bridge.d.ts.map +1 -0
  11. package/dist/harness-remote-bridge.js +321 -0
  12. package/dist/harness-remote-bridge.js.map +1 -0
  13. package/dist/index.d.ts +4 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1066 -83
  16. package/dist/index.js.map +4 -4
  17. package/dist/native-rtc-helper.d.ts +10 -0
  18. package/dist/native-rtc-helper.d.ts.map +1 -1
  19. package/dist/native-rtc-helper.js +21 -3
  20. package/dist/native-rtc-helper.js.map +1 -1
  21. package/dist/remote-typert-gateway.d.ts +13 -0
  22. package/dist/remote-typert-gateway.d.ts.map +1 -0
  23. package/dist/remote-typert-gateway.js +242 -0
  24. package/dist/remote-typert-gateway.js.map +1 -0
  25. package/dist/rpc-router.d.ts +7 -2
  26. package/dist/rpc-router.d.ts.map +1 -1
  27. package/dist/rpc-router.js +61 -12
  28. package/dist/rpc-router.js.map +1 -1
  29. package/dist/server-connection.d.ts +5 -0
  30. package/dist/server-connection.d.ts.map +1 -1
  31. package/dist/server-connection.js +120 -5
  32. package/dist/server-connection.js.map +1 -1
  33. package/dist/service.d.ts +5 -3
  34. package/dist/service.d.ts.map +1 -1
  35. package/dist/service.js +26 -8
  36. package/dist/service.js.map +1 -1
  37. package/dist/typert-gateway-contract.d.ts +48 -0
  38. package/dist/typert-gateway-contract.d.ts.map +1 -0
  39. package/dist/typert-gateway-contract.js +2 -0
  40. package/dist/typert-gateway-contract.js.map +1 -0
  41. package/dist/typert-gateway-switch.d.ts +28 -7
  42. package/dist/typert-gateway-switch.d.ts.map +1 -1
  43. package/dist/typert-gateway-switch.js +121 -23
  44. package/dist/typert-gateway-switch.js.map +1 -1
  45. package/dist/version.d.ts +1 -1
  46. package/dist/version.d.ts.map +1 -1
  47. package/dist/version.js +1 -1
  48. package/dist/version.js.map +1 -1
  49. 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
- const selected = await this.refreshStatsDiagnostics();
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 === "host" && remoteType === "host" ? "LAN" : "P2P"
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: this.options.forceRelay === true ? ["relay"] : this.options.preferredTransports ?? [...DEFAULT_PREFERRED_TRANSPORTS]
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.options.preferredTransports ?? [...DEFAULT_PREFERRED_TRANSPORTS];
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
- this.dataMode = "webrtc";
5809
- selected = rtcSelected;
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.3.36";
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
- constructor(gateway) {
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
- /** A facade that always invokes the original local gateway. */
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
- return { invoke: this.localInvoke };
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.gateway.invoke = (request) => {
13964
- if (request.namespace !== "commands" || !isRemoteCommandMethod(request.method) || this.remoteInvoke === void 0) {
13965
- return this.localInvoke(request);
13966
- }
13967
- if (this.remoteSupport[request.method]) return this.remoteInvoke(request);
13968
- if (request.method === "list") return Promise.resolve([]);
13969
- return this.localInvoke(request);
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(invoke, support = ALL_REMOTE_COMMANDS) {
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.gateway.invoke = this.originalInvoke;
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 = fileURLToPath(import.meta.url)) {
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 = fileURLToPath(import.meta.url);
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.install();
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
- ...this.proxySwitch.status(),
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.selectLocal();
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.selectLocal();
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
- const remoteApi = new RemoteHarnessApiProxy(next.client).api;
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
- const api = new RemoteHarnessApiProxy(remote.client).api;
15229
- const response = await api.workspace.create({
15230
- rpcId: `remote-workspace-${Date.now()}`,
15231
- payload: { path }
15232
- });
15233
- const workspace = unwrapNativeResult(response);
15234
- this.proxySwitch.selectRemote(api, { deviceId: remote.target.deviceId, name: remote.target.name });
15235
- this.selectRemoteCommands(remote);
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.selectLocal();
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.restore();
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.proxySwitch.status().mode !== "remote") {
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
- selectRemoteCommands(remote) {
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.selectLocal();
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
- return { client: connectedClient, target, transport: connectedTransport, features: remoteHostFeatures(serverDevice.clientVersion) };
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 isRecord(value) {
16555
+ function isRecord4(value) {
16055
16556
  return typeof value === "object" && value !== null && !Array.isArray(value);
16056
16557
  }
16057
16558
  function record2(value) {
16058
- if (!isRecord(value)) throw new ClientModeError("INVALID_MESSAGE", "The control request payload is invalid.");
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 = ["harness.api.v1", "harness.api.transfer.v1", "fileviewer.read.v1"];
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
- return this.harnessApi.closeAll();
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.harnessApi.call(params);
16916
+ return this.requireApiProxy().call(params);
16392
16917
  case "harness.api.transfer.open":
16393
- return this.harnessApi.openTransfer(params);
16918
+ return this.requireApiProxy().openTransfer(params);
16394
16919
  case "harness.api.transfer.chunk":
16395
- return this.harnessApi.appendTransfer(params);
16920
+ return this.requireApiProxy().appendTransfer(params);
16396
16921
  case "harness.api.transfer.commit":
16397
- return this.harnessApi.commitTransfer(params);
16922
+ return this.requireApiProxy().commitTransfer(params);
16398
16923
  case "harness.api.transfer.read":
16399
- return this.harnessApi.readTransfer(params);
16924
+ return this.requireApiProxy().readTransfer(params);
16400
16925
  case "harness.api.transfer.close":
16401
- return this.harnessApi.closeTransfer(params);
16926
+ return this.requireApiProxy().closeTransfer(params);
16402
16927
  case "harness.api.respond":
16403
- return this.harnessApi.respond(params);
16928
+ return this.requireApiProxy().respond(params);
16404
16929
  case "harness.api.stream.open":
16405
- return this.harnessApi.openStream(params);
16930
+ return this.requireApiProxy().openStream(params);
16406
16931
  case "harness.api.stream.close":
16407
- return this.harnessApi.closeStream(params);
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: this.rtcFactoryProvider === void 0 || this.config.forceRelay ? ["transport.relay", ...this.hostCapabilities()] : ["transport.p2p", "transport.turn", "transport.relay", ...this.hostCapabilities()]
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") return;
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, typertGateway, fileViewerHost) {
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
- typertGateway?.(),
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.fileViewerHost?.() === void 0 ? ["harness.api.v1"] : ["harness.api.v1", "fileviewer.read.v1"],
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.host.describe({ rpcId: RpcId(randomUUID()), payload: {} });
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", "apiProxy", "connection", "typertGateway"], (runtimeContext) => activate(runtimeContext, input));
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
- () => localTypertGateway,
19362
+ localTypertGateway,
18384
19363
  () => ctx.get("fileViewerHost")
18385
19364
  );
18386
19365
  let clientRuntime;
18387
19366
  const hostControl = runtime;
18388
- if (config.serverUrl !== void 0 && apiProxy !== void 0 && connection !== 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,