ds-harness-remote 0.3.36 → 0.4.0

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 (41) 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 +130 -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 +847 -68
  16. package/dist/index.js.map +4 -4
  17. package/dist/remote-typert-gateway.d.ts +13 -0
  18. package/dist/remote-typert-gateway.d.ts.map +1 -0
  19. package/dist/remote-typert-gateway.js +242 -0
  20. package/dist/remote-typert-gateway.js.map +1 -0
  21. package/dist/rpc-router.d.ts +7 -2
  22. package/dist/rpc-router.d.ts.map +1 -1
  23. package/dist/rpc-router.js +61 -12
  24. package/dist/rpc-router.js.map +1 -1
  25. package/dist/service.d.ts +5 -3
  26. package/dist/service.d.ts.map +1 -1
  27. package/dist/service.js +26 -8
  28. package/dist/service.js.map +1 -1
  29. package/dist/typert-gateway-contract.d.ts +48 -0
  30. package/dist/typert-gateway-contract.d.ts.map +1 -0
  31. package/dist/typert-gateway-contract.js +2 -0
  32. package/dist/typert-gateway-contract.js.map +1 -0
  33. package/dist/typert-gateway-switch.d.ts +28 -7
  34. package/dist/typert-gateway-switch.d.ts.map +1 -1
  35. package/dist/typert-gateway-switch.js +114 -23
  36. package/dist/typert-gateway-switch.js.map +1 -1
  37. package/dist/version.d.ts +1 -1
  38. package/dist/version.d.ts.map +1 -1
  39. package/dist/version.js +1 -1
  40. package/dist/version.js.map +1 -1
  41. 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) {
@@ -13460,6 +13469,224 @@ function base64ToBytes(value) {
13460
13469
  return bytes;
13461
13470
  }
13462
13471
 
13472
+ // src/remote-typert-gateway.ts
13473
+ var DIRECT_REMOTE_CALL_BYTES = 2 * 1024 * 1024;
13474
+ var RemoteTypertGateway = class {
13475
+ constructor(client) {
13476
+ this.client = client;
13477
+ }
13478
+ async invoke(request) {
13479
+ const result = await this.dispatch(
13480
+ `${request.namespace}/${request.method}`,
13481
+ { args: request.args },
13482
+ request.signal ?? new AbortController().signal
13483
+ );
13484
+ if (result.ok) return result.value;
13485
+ throw remoteFailure(result.error);
13486
+ }
13487
+ async dispatch(endpoint, payload, signal) {
13488
+ const request = { endpoint, payload };
13489
+ const encoded = new TextEncoder().encode(JSON.stringify(request));
13490
+ let response;
13491
+ if (encoded.byteLength > DIRECT_REMOTE_CALL_BYTES) {
13492
+ response = await this.callTransferred(encoded, signal);
13493
+ } else {
13494
+ try {
13495
+ response = await this.client.rpc("harness.remote.call", request, signal);
13496
+ } catch (error) {
13497
+ if (!hasErrorCode(error, "RESPONSE_TOO_LARGE")) throw error;
13498
+ response = await this.callTransferred(encoded, signal);
13499
+ }
13500
+ }
13501
+ return parseRpcResult(response);
13502
+ }
13503
+ async open(endpoint, payload, signal) {
13504
+ const streamId = uuidV7();
13505
+ const queue = new AsyncValueQueue();
13506
+ const unsubscribe = this.client.onEvent((event) => routeStreamEvent2(event, streamId, queue));
13507
+ const unsubscribeClose = this.client.onClose(() => {
13508
+ queue.fail(new RemoteClientError("TRANSPORT_CLOSED", "The remote Harness stream transport closed."));
13509
+ });
13510
+ const onAbort = () => queue.close();
13511
+ signal.addEventListener("abort", onAbort, { once: true });
13512
+ try {
13513
+ await this.client.rpc("harness.remote.stream.open", { streamId, endpoint, payload }, signal);
13514
+ } catch (error) {
13515
+ signal.removeEventListener("abort", onAbort);
13516
+ unsubscribe();
13517
+ unsubscribeClose();
13518
+ throw error;
13519
+ }
13520
+ return this.iterate(streamId, queue, unsubscribe, unsubscribeClose, signal, onAbort);
13521
+ }
13522
+ async *iterate(streamId, queue, unsubscribe, unsubscribeClose, signal, onAbort) {
13523
+ try {
13524
+ yield* queue;
13525
+ } finally {
13526
+ signal.removeEventListener("abort", onAbort);
13527
+ unsubscribe();
13528
+ unsubscribeClose();
13529
+ queue.close();
13530
+ await this.client.rpc("harness.remote.stream.close", { streamId }).catch(() => void 0);
13531
+ }
13532
+ }
13533
+ async callTransferred(encoded, signal) {
13534
+ if (encoded.byteLength > MAX_HARNESS_API_TRANSFER_BYTES) {
13535
+ throw new Error("The Harness Remote request exceeds the transfer limit.");
13536
+ }
13537
+ const transferId = uuidV7();
13538
+ const totalChunks = Math.ceil(encoded.byteLength / HARNESS_API_TRANSFER_CHUNK_BYTES);
13539
+ let opened = false;
13540
+ try {
13541
+ await this.client.rpc("harness.remote.transfer.open", {
13542
+ transferId,
13543
+ totalBytes: encoded.byteLength,
13544
+ totalChunks
13545
+ }, signal);
13546
+ opened = true;
13547
+ for (let index = 0; index < totalChunks; index += 1) {
13548
+ const start = index * HARNESS_API_TRANSFER_CHUNK_BYTES;
13549
+ const chunk = encoded.subarray(start, Math.min(start + HARNESS_API_TRANSFER_CHUNK_BYTES, encoded.byteLength));
13550
+ await this.client.rpc("harness.remote.transfer.chunk", {
13551
+ transferId,
13552
+ index,
13553
+ data: bytesToBase642(chunk)
13554
+ }, signal);
13555
+ }
13556
+ const committed = await this.client.rpc(
13557
+ "harness.remote.transfer.commit",
13558
+ { transferId },
13559
+ signal
13560
+ );
13561
+ if (committed.kind === "inline") return committed.response;
13562
+ 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)) {
13563
+ throw new Error("The remote Host returned an invalid Harness Remote transfer descriptor.");
13564
+ }
13565
+ const responseBytes = new Uint8Array(committed.totalBytes);
13566
+ let offset = 0;
13567
+ for (let index = 0; index < committed.totalChunks; index += 1) {
13568
+ const result = await this.client.rpc(
13569
+ "harness.remote.transfer.read",
13570
+ { transferId, index },
13571
+ signal
13572
+ );
13573
+ if (result.transferId !== transferId || result.index !== index) {
13574
+ throw new Error("The remote Host returned an out-of-order Harness Remote transfer chunk.");
13575
+ }
13576
+ const chunk = base64ToBytes2(result.data);
13577
+ const expectedBytes = Math.min(HARNESS_API_TRANSFER_CHUNK_BYTES, committed.totalBytes - offset);
13578
+ if (chunk.byteLength !== expectedBytes) {
13579
+ throw new Error("The remote Host returned an invalid Harness Remote transfer chunk.");
13580
+ }
13581
+ responseBytes.set(chunk, offset);
13582
+ offset += chunk.byteLength;
13583
+ }
13584
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(responseBytes));
13585
+ } finally {
13586
+ if (opened) await this.client.rpc("harness.remote.transfer.close", { transferId }).catch(() => void 0);
13587
+ }
13588
+ }
13589
+ };
13590
+ var AsyncValueQueue = class {
13591
+ values = [];
13592
+ waiters = [];
13593
+ closed = false;
13594
+ error;
13595
+ push(value) {
13596
+ if (this.closed) return;
13597
+ const waiter = this.waiters.shift();
13598
+ if (waiter === void 0) this.values.push(value);
13599
+ else waiter({ done: false, value });
13600
+ }
13601
+ close() {
13602
+ if (this.closed) return;
13603
+ this.closed = true;
13604
+ for (const waiter of this.waiters.splice(0)) waiter({ done: true, value: void 0 });
13605
+ }
13606
+ fail(error) {
13607
+ if (this.closed) return;
13608
+ this.error = error;
13609
+ this.close();
13610
+ }
13611
+ async *[Symbol.asyncIterator]() {
13612
+ while (true) {
13613
+ if (this.values.length > 0) {
13614
+ yield this.values.shift();
13615
+ continue;
13616
+ }
13617
+ if (this.closed) {
13618
+ if (this.error !== void 0) throw this.error;
13619
+ return;
13620
+ }
13621
+ const next = await new Promise((resolve2) => this.waiters.push(resolve2));
13622
+ if (next.done) {
13623
+ if (this.error !== void 0) throw this.error;
13624
+ return;
13625
+ }
13626
+ yield next.value;
13627
+ }
13628
+ }
13629
+ };
13630
+ function routeStreamEvent2(event, streamId, queue) {
13631
+ if (event.event === "harness.remote.frame") {
13632
+ const data2 = event.data;
13633
+ if (data2.streamId === streamId && data2.hasValue === true) queue.push(data2.value);
13634
+ return;
13635
+ }
13636
+ if (event.event !== "harness.remote.stream.closed") return;
13637
+ const data = event.data;
13638
+ if (data.streamId !== streamId) return;
13639
+ if (data.reason === "failed") {
13640
+ const failure = data.failure;
13641
+ queue.fail(remoteFailure({
13642
+ code: typeof failure?.code === "string" ? failure.code : "internal",
13643
+ message: typeof failure?.message === "string" ? failure.message : "The remote Harness stream failed.",
13644
+ details: isRecord(failure?.details) ? failure.details : {}
13645
+ }));
13646
+ } else {
13647
+ queue.close();
13648
+ }
13649
+ }
13650
+ function parseRpcResult(value) {
13651
+ if (!isRecord(value)) throw new Error("The remote Host returned an invalid Gateway result.");
13652
+ if (value.ok === true) return Object.hasOwn(value, "value") ? { ok: true, value: value.value } : { ok: true };
13653
+ if (value.ok !== false || !isRecord(value.error) || typeof value.error.code !== "string" || typeof value.error.message !== "string" || !isRecord(value.error.details)) {
13654
+ throw new Error("The remote Host returned an invalid Gateway failure.");
13655
+ }
13656
+ return {
13657
+ ok: false,
13658
+ error: { code: value.error.code, message: value.error.message, details: value.error.details }
13659
+ };
13660
+ }
13661
+ function remoteFailure(failure) {
13662
+ return Object.assign(new Error(failure.message), { code: failure.code, details: failure.details });
13663
+ }
13664
+ function bytesToBase642(bytes) {
13665
+ let binary = "";
13666
+ for (let offset = 0; offset < bytes.byteLength; offset += 32768) {
13667
+ binary += String.fromCharCode(...bytes.subarray(offset, Math.min(offset + 32768, bytes.byteLength)));
13668
+ }
13669
+ return btoa(binary);
13670
+ }
13671
+ function base64ToBytes2(value) {
13672
+ let binary;
13673
+ try {
13674
+ binary = atob(value);
13675
+ } catch {
13676
+ throw new Error("The remote Host returned malformed Harness Remote transfer data.");
13677
+ }
13678
+ const bytes = new Uint8Array(binary.length);
13679
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
13680
+ if (bytesToBase642(bytes) !== value) throw new Error("The remote Host returned non-canonical Harness Remote transfer data.");
13681
+ return bytes;
13682
+ }
13683
+ function isRecord(value) {
13684
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13685
+ }
13686
+ function hasErrorCode(error, code) {
13687
+ return error instanceof Error && "code" in error && error.code === code;
13688
+ }
13689
+
13463
13690
  // src/server-api.ts
13464
13691
  import { platform } from "node:os";
13465
13692
 
@@ -13543,7 +13770,7 @@ function normalizeServerUrl(value) {
13543
13770
  }
13544
13771
 
13545
13772
  // src/version.ts
13546
- var PLUGIN_VERSION = "0.3.36";
13773
+ var PLUGIN_VERSION = "0.4.0";
13547
13774
 
13548
13775
  // src/server-api.ts
13549
13776
  var HostServerApi = class {
@@ -13944,51 +14171,133 @@ function requireRecord(value, name2) {
13944
14171
  var REMOTE_COMMAND_METHODS = ["execute", "list"];
13945
14172
  var ALL_REMOTE_COMMANDS = { execute: true, list: true };
13946
14173
  var TypertGatewaySwitch = class {
13947
- constructor(gateway) {
13948
- this.gateway = gateway;
13949
- this.originalInvoke = gateway.invoke;
13950
- this.localInvoke = this.originalInvoke.bind(gateway);
13951
- }
14174
+ runtime;
13952
14175
  originalInvoke;
13953
14176
  localInvoke;
14177
+ originalStream;
14178
+ localStream;
14179
+ originalDispatch;
14180
+ localDispatch;
14181
+ originalOpen;
14182
+ localOpen;
13954
14183
  remoteInvoke;
14184
+ remoteTarget;
13955
14185
  remoteSupport = { execute: false, list: false };
14186
+ target;
13956
14187
  installed = false;
13957
- /** A facade that always invokes the original local gateway. */
14188
+ constructor(gateway) {
14189
+ this.runtime = gateway;
14190
+ this.originalInvoke = gateway.invoke;
14191
+ this.localInvoke = this.originalInvoke.bind(gateway);
14192
+ this.originalStream = gateway.stream;
14193
+ this.localStream = gateway.stream?.bind(gateway);
14194
+ this.originalDispatch = this.runtime.dispatchRpc;
14195
+ this.localDispatch = this.runtime.dispatchRpc?.bind(gateway);
14196
+ this.originalOpen = this.runtime.openWireStream;
14197
+ this.localOpen = this.runtime.openWireStream?.bind(gateway) ?? gateway.wireStream?.open.bind(gateway.wireStream);
14198
+ }
14199
+ /** Original local dispatcher, used by the Host bridge without switch recursion. */
13958
14200
  local() {
13959
- return { invoke: this.localInvoke };
14201
+ const dispatch = this.localDispatch ?? (async (endpoint, payload, signal) => {
14202
+ try {
14203
+ const request = requestFromCarrier(endpoint, payload, signal);
14204
+ return { ok: true, value: await this.localInvoke(request) };
14205
+ } catch (error) {
14206
+ return { ok: false, error: this.failure(error) };
14207
+ }
14208
+ });
14209
+ const open = this.localOpen ?? (async (endpoint, payload, signal) => {
14210
+ if (this.localStream === void 0) throw new Error("The local Harness Gateway does not support Remote streams.");
14211
+ return this.localStream(requestFromCarrier(endpoint, payload, signal));
14212
+ });
14213
+ return {
14214
+ invoke: this.localInvoke,
14215
+ ...this.localStream === void 0 ? {} : { stream: this.localStream },
14216
+ dispatch,
14217
+ open,
14218
+ failure: (error) => this.failure(error),
14219
+ supportsCarrier: this.localDispatch !== void 0 && this.localOpen !== void 0
14220
+ };
14221
+ }
14222
+ supportsCarrier() {
14223
+ return this.localDispatch !== void 0 && this.localOpen !== void 0;
14224
+ }
14225
+ status() {
14226
+ return this.remoteInvoke === void 0 ? { mode: "local" } : { mode: "remote", ...this.target === void 0 ? {} : { target: { ...this.target } } };
13960
14227
  }
13961
14228
  install() {
13962
14229
  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
- };
14230
+ this.runtime.invoke = (request) => this.selectInvoke(request);
14231
+ if (this.originalStream !== void 0) {
14232
+ this.runtime.stream = (request) => this.remoteTarget === void 0 ? this.localStream(request) : this.remoteTarget.open(endpointOf(request), { args: request.args }, request.signal ?? new AbortController().signal);
14233
+ }
14234
+ if (this.originalDispatch !== void 0) {
14235
+ this.runtime.dispatchRpc = (endpoint, payload, signal) => this.remoteTarget === void 0 ? this.localDispatch(endpoint, payload, signal) : this.remoteTarget.dispatch(endpoint, payload, signal);
14236
+ }
14237
+ if (this.originalOpen !== void 0) {
14238
+ this.runtime.openWireStream = (endpoint, payload, signal) => this.remoteTarget === void 0 ? this.localOpen(endpoint, payload, signal) : this.remoteTarget.open(endpoint, payload, signal);
14239
+ }
13971
14240
  this.installed = true;
13972
14241
  }
13973
- selectRemote(invoke, support = ALL_REMOTE_COMMANDS) {
14242
+ selectRemote(remote, support = ALL_REMOTE_COMMANDS, target) {
13974
14243
  if (!this.installed) throw new Error("The Typert gateway switch is not installed.");
13975
- this.remoteInvoke = invoke;
14244
+ this.remoteInvoke = typeof remote === "function" ? remote : (request) => remote.invoke(request);
14245
+ this.remoteTarget = typeof remote === "function" ? void 0 : remote;
13976
14246
  this.remoteSupport = { ...support };
14247
+ this.target = target === void 0 ? void 0 : { ...target };
13977
14248
  }
13978
14249
  selectLocal() {
13979
14250
  this.remoteInvoke = void 0;
14251
+ this.remoteTarget = void 0;
13980
14252
  this.remoteSupport = { execute: false, list: false };
14253
+ this.target = void 0;
13981
14254
  }
13982
14255
  restore() {
13983
14256
  if (!this.installed) return;
13984
14257
  this.selectLocal();
13985
- this.gateway.invoke = this.originalInvoke;
14258
+ this.runtime.invoke = this.originalInvoke;
14259
+ if (this.originalStream !== void 0) this.runtime.stream = this.originalStream;
14260
+ if (this.originalDispatch !== void 0) this.runtime.dispatchRpc = this.originalDispatch;
14261
+ if (this.originalOpen !== void 0) this.runtime.openWireStream = this.originalOpen;
13986
14262
  this.installed = false;
13987
14263
  }
14264
+ selectInvoke(request) {
14265
+ if (this.remoteTarget !== void 0) return this.remoteTarget.invoke(request);
14266
+ if (request.namespace !== "commands" || !isRemoteCommandMethod(request.method) || this.remoteInvoke === void 0) {
14267
+ return this.localInvoke(request);
14268
+ }
14269
+ if (this.remoteSupport[request.method]) return this.remoteInvoke(request);
14270
+ if (request.method === "list") return Promise.resolve([]);
14271
+ return this.localInvoke(request);
14272
+ }
14273
+ failure(error) {
14274
+ const normalized = this.runtime.wireStream?.failure(error);
14275
+ if (normalized !== void 0) return normalized;
14276
+ const source = error instanceof Error ? error : new Error("The Harness Gateway rejected the request.");
14277
+ const code = "code" in source && typeof source.code === "string" ? source.code : "internal";
14278
+ const details = "details" in source && isRecord2(source.details) ? source.details : {};
14279
+ return { code, message: source.message, details };
14280
+ }
13988
14281
  };
14282
+ function requestFromCarrier(endpoint, payload, signal) {
14283
+ const segments = endpoint.split("/");
14284
+ if (segments.length !== 2 || segments.some((segment) => segment.length === 0)) {
14285
+ throw new Error("The Harness Gateway endpoint is invalid.");
14286
+ }
14287
+ if (!isRecord2(payload) || !isRecord2(payload.args)) {
14288
+ throw new Error("The Harness Gateway payload is invalid.");
14289
+ }
14290
+ return { namespace: segments[0], method: segments[1], args: payload.args, signal };
14291
+ }
14292
+ function endpointOf(request) {
14293
+ return `${request.namespace}/${request.method}`;
14294
+ }
13989
14295
  function isRemoteCommandMethod(method) {
13990
14296
  return REMOTE_COMMAND_METHODS.includes(method);
13991
14297
  }
14298
+ function isRecord2(value) {
14299
+ return typeof value === "object" && value !== null && !Array.isArray(value);
14300
+ }
13992
14301
 
13993
14302
  // src/werift-rtc.ts
13994
14303
  import { createSocket } from "node:dgram";
@@ -15057,7 +15366,7 @@ var ClientModeRuntime = class {
15057
15366
  this.logger = logger;
15058
15367
  this.host = host;
15059
15368
  this.rtcFactoryProvider = rtcFactoryProvider;
15060
- this.proxySwitch = new ApiProxySwitch(apiProxy);
15369
+ this.proxySwitch = apiProxy === void 0 ? void 0 : new ApiProxySwitch(apiProxy);
15061
15370
  this.gatewaySwitch = new TypertGatewaySwitch(typertGateway);
15062
15371
  }
15063
15372
  identity;
@@ -15070,7 +15379,7 @@ var ClientModeRuntime = class {
15070
15379
  if (this.closed) throw new Error("client remote-mode runtime is closed");
15071
15380
  this.identity = await this.identities.loadOrCreate(this.config.deviceName);
15072
15381
  this.server.bindIdentity(this.identity);
15073
- this.proxySwitch.install();
15382
+ this.proxySwitch?.install();
15074
15383
  this.gatewaySwitch.install();
15075
15384
  this.logger.info("client remote-mode identity ready", {
15076
15385
  deviceId: shortId(this.identity.deviceId),
@@ -15083,13 +15392,14 @@ var ClientModeRuntime = class {
15083
15392
  });
15084
15393
  }
15085
15394
  status() {
15395
+ const targetStatus = this.gatewaySwitch.supportsCarrier() ? this.gatewaySwitch.status() : this.proxySwitch?.status() ?? this.gatewaySwitch.status();
15086
15396
  return {
15087
15397
  available: this.config.serverUrl !== void 0,
15088
15398
  identityReady: this.identity !== void 0,
15089
15399
  deviceId: this.identity?.deviceId,
15090
15400
  deviceName: this.identity?.name,
15091
15401
  serverUrl: this.config.serverUrl,
15092
- ...this.proxySwitch.status(),
15402
+ ...targetStatus,
15093
15403
  connected: this.connected !== void 0,
15094
15404
  transport: this.connected?.client.getStats().mode ?? "Disconnected",
15095
15405
  preferredTransports: this.config.forceRelay ? ["relay"] : ["lan", "p2p", "turn", "relay"],
@@ -15161,7 +15471,7 @@ var ClientModeRuntime = class {
15161
15471
  const previous = this.connected;
15162
15472
  this.connected = void 0;
15163
15473
  this.pendingWorkspaceSelection = void 0;
15164
- this.proxySwitch.selectLocal();
15474
+ this.proxySwitch?.selectLocal();
15165
15475
  this.gatewaySwitch.selectLocal();
15166
15476
  await previous?.client.close().catch(() => void 0);
15167
15477
  await this.server.revokeCurrentDevice();
@@ -15180,7 +15490,7 @@ var ClientModeRuntime = class {
15180
15490
  }
15181
15491
  async setMode(mode, targetDeviceId, signal) {
15182
15492
  if (mode === "local") {
15183
- this.proxySwitch.selectLocal();
15493
+ this.proxySwitch?.selectLocal();
15184
15494
  this.gatewaySwitch.selectLocal();
15185
15495
  const previous2 = this.connected;
15186
15496
  this.connected = void 0;
@@ -15193,18 +15503,31 @@ var ClientModeRuntime = class {
15193
15503
  throw new ClientModeError("INVALID_MESSAGE", "A targetDeviceId is required for remote mode.");
15194
15504
  }
15195
15505
  const next = await this.connect(targetDeviceId, signal);
15506
+ try {
15507
+ this.assertRemoteCompatible(next);
15508
+ } catch (error) {
15509
+ await next.client.close().catch(() => void 0);
15510
+ throw error;
15511
+ }
15196
15512
  const previous = this.connected;
15197
15513
  this.connected = next;
15198
15514
  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);
15515
+ this.selectRemoteTarget(next);
15202
15516
  await previous?.client.close().catch(() => void 0);
15203
15517
  this.logger.info("Harness target switched", { mode: "remote", targetDeviceId: shortId(next.target.deviceId) });
15204
15518
  return this.status();
15205
15519
  }
15206
15520
  async listRemoteDirectory(targetDeviceId, path, signal) {
15207
15521
  const remote = await this.ensureConnected(targetDeviceId, signal);
15522
+ if (remote.features.remoteGateway) {
15523
+ const value = await new RemoteTypertGateway(remote.client).invoke({
15524
+ namespace: "directoryPicker",
15525
+ method: "list",
15526
+ args: path === void 0 ? {} : { path },
15527
+ ...signal === void 0 ? {} : { signal }
15528
+ });
15529
+ return value;
15530
+ }
15208
15531
  const api = new RemoteHarnessApiProxy(remote.client).api;
15209
15532
  const response = await api.host.listDirectory({
15210
15533
  rpcId: `remote-directory-${Date.now()}`,
@@ -15214,6 +15537,9 @@ var ClientModeRuntime = class {
15214
15537
  }
15215
15538
  async listRemoteWorkspaces(targetDeviceId, signal) {
15216
15539
  const remote = await this.ensureConnected(targetDeviceId, signal);
15540
+ if (remote.features.remoteGateway) {
15541
+ return readRemoteWorkspaceBaseline(new RemoteTypertGateway(remote.client), signal);
15542
+ }
15217
15543
  const api = new RemoteHarnessApiProxy(remote.client).api;
15218
15544
  const response = await api.workspace.list({
15219
15545
  rpcId: `remote-workspaces-${Date.now()}`,
@@ -15225,14 +15551,24 @@ var ClientModeRuntime = class {
15225
15551
  async openRemoteWorkspace(targetDeviceId, path, signal) {
15226
15552
  if (path.trim() === "") throw new ClientModeError("INVALID_MESSAGE", "A remote working directory is required.");
15227
15553
  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);
15554
+ this.assertRemoteCompatible(remote);
15555
+ let workspace;
15556
+ if (remote.features.remoteGateway) {
15557
+ workspace = await new RemoteTypertGateway(remote.client).invoke({
15558
+ namespace: "workspace",
15559
+ method: "create",
15560
+ args: { request: { path } },
15561
+ ...signal === void 0 ? {} : { signal }
15562
+ });
15563
+ } else {
15564
+ const api = new RemoteHarnessApiProxy(remote.client).api;
15565
+ const response = await api.workspace.create({
15566
+ rpcId: `remote-workspace-${Date.now()}`,
15567
+ payload: { path }
15568
+ });
15569
+ workspace = unwrapNativeResult(response);
15570
+ }
15571
+ this.selectRemoteTarget(remote);
15236
15572
  const workspaceId = workspaceRecordId(workspace.workspace);
15237
15573
  this.pendingWorkspaceSelection = { targetDeviceId: remote.target.deviceId, workspaceId };
15238
15574
  this.logger.info("Remote workspace opened", { targetDeviceId: shortId(remote.target.deviceId) });
@@ -15248,17 +15584,17 @@ var ClientModeRuntime = class {
15248
15584
  async close() {
15249
15585
  if (this.closed) return;
15250
15586
  this.closed = true;
15251
- this.proxySwitch.selectLocal();
15587
+ this.proxySwitch?.selectLocal();
15252
15588
  this.gatewaySwitch.selectLocal();
15253
15589
  this.pendingWorkspaceSelection = void 0;
15254
15590
  await this.connected?.client.close().catch(() => void 0);
15255
15591
  this.connected = void 0;
15256
- this.proxySwitch.restore();
15592
+ this.proxySwitch?.restore();
15257
15593
  this.gatewaySwitch.restore();
15258
15594
  }
15259
15595
  async callRemoteFileViewer(endpoint, payload, signal) {
15260
15596
  const remote = this.connected;
15261
- if (remote === void 0 || this.proxySwitch.status().mode !== "remote") {
15597
+ if (remote === void 0 || this.status().mode !== "remote") {
15262
15598
  throw new ClientModeError("REMOTE_NOT_CONNECTED", "No Remote Host is selected.", true);
15263
15599
  }
15264
15600
  if (!remote.features.fileViewer) {
@@ -15266,11 +15602,25 @@ var ClientModeRuntime = class {
15266
15602
  }
15267
15603
  return remote.client.rpc("fileviewer.call", { endpoint, payload }, signal);
15268
15604
  }
15269
- selectRemoteCommands(remote) {
15605
+ selectRemoteTarget(remote) {
15606
+ const target = { deviceId: remote.target.deviceId, name: remote.target.name };
15607
+ if (this.gatewaySwitch.supportsCarrier()) {
15608
+ this.gatewaySwitch.selectRemote(new RemoteTypertGateway(remote.client), void 0, target);
15609
+ return;
15610
+ }
15611
+ this.proxySwitch.selectRemote(new RemoteHarnessApiProxy(remote.client).api, target);
15270
15612
  this.gatewaySwitch.selectRemote((request) => invokeRemoteCommand(remote.client, request), {
15271
15613
  execute: true,
15272
15614
  list: remote.features.commandList
15273
- });
15615
+ }, target);
15616
+ }
15617
+ assertRemoteCompatible(remote) {
15618
+ const localRemoteGateway = this.gatewaySwitch.supportsCarrier();
15619
+ if (localRemoteGateway && remote.features.remoteGateway || !localRemoteGateway && this.proxySwitch !== void 0 && remote.features.apiProxy) return;
15620
+ throw new ClientModeError(
15621
+ "HARNESS_VERSION_INCOMPATIBLE",
15622
+ localRemoteGateway ? "The selected Host does not provide the Harness alpha Remote Gateway transport." : "The selected Host does not provide the legacy Harness ApiProxy transport."
15623
+ );
15274
15624
  }
15275
15625
  async connect(targetDeviceId, signal) {
15276
15626
  signal?.throwIfAborted();
@@ -15337,7 +15687,7 @@ var ClientModeRuntime = class {
15337
15687
  if (this.connected?.client !== connectedClient) return;
15338
15688
  this.connected = void 0;
15339
15689
  this.pendingWorkspaceSelection = void 0;
15340
- this.proxySwitch.selectLocal();
15690
+ this.proxySwitch?.selectLocal();
15341
15691
  this.gatewaySwitch.selectLocal();
15342
15692
  void connectedClient.close().catch(() => void 0);
15343
15693
  this.logger.warn("remote Harness transport closed; falling back to local mode", {
@@ -15350,7 +15700,8 @@ var ClientModeRuntime = class {
15350
15700
  transport: connectedClient.getStats().mode,
15351
15701
  ...webrtcDiagnosticsLogFields(connectionDetails?.webRtc?.diagnostics)
15352
15702
  });
15353
- return { client: connectedClient, target, transport: connectedTransport, features: remoteHostFeatures(serverDevice.clientVersion) };
15703
+ const features = await probeRemoteHostFeatures(connectedClient, serverDevice.clientVersion);
15704
+ return { client: connectedClient, target, transport: connectedTransport, features };
15354
15705
  } catch (error) {
15355
15706
  await client?.close().catch(() => void 0);
15356
15707
  throw error;
@@ -15519,6 +15870,9 @@ function record(value) {
15519
15870
  }
15520
15871
  return value;
15521
15872
  }
15873
+ function isRecord3(value) {
15874
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15875
+ }
15522
15876
  function ok(value) {
15523
15877
  return { ok: true, value };
15524
15878
  }
@@ -15534,6 +15888,27 @@ async function invokeRemoteCommand(client, request) {
15534
15888
  }
15535
15889
  return unwrapNativeResult(response);
15536
15890
  }
15891
+ async function readRemoteWorkspaceBaseline(gateway, signal) {
15892
+ const lifetime = new AbortController();
15893
+ const activeSignal = signal === void 0 ? lifetime.signal : AbortSignal.any([signal, lifetime.signal]);
15894
+ const source = await gateway.open("workspace/follow", { args: {} }, activeSignal);
15895
+ const iterator = source[Symbol.asyncIterator]();
15896
+ try {
15897
+ const first = await iterator.next();
15898
+ if (first.done || !isRecord3(first.value) || first.value.type !== "baseline" || !isRecord3(first.value.value) || !Array.isArray(first.value.value.items)) {
15899
+ throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned an invalid Workspace baseline.");
15900
+ }
15901
+ return first.value.value.items.map((item) => {
15902
+ if (!isRecord3(item) || typeof item.workspaceId !== "string" || typeof item.path !== "string" || typeof item.title !== "string") {
15903
+ throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned an invalid Workspace row.");
15904
+ }
15905
+ return { workspaceId: item.workspaceId, path: item.path, title: item.title };
15906
+ });
15907
+ } finally {
15908
+ lifetime.abort("workspace-baseline-read");
15909
+ await iterator.return?.();
15910
+ }
15911
+ }
15537
15912
  function unwrapNativeResult(response) {
15538
15913
  const result = response.result;
15539
15914
  if (typeof result !== "object" || result === null || !("ok" in result)) {
@@ -15570,7 +15945,34 @@ function shortId(value) {
15570
15945
  function remoteHostFeatures(clientVersion) {
15571
15946
  return {
15572
15947
  commandList: isVersionAtLeast(clientVersion, REMOTE_COMMAND_LIST_MIN_VERSION),
15573
- fileViewer: isVersionAtLeast(clientVersion, REMOTE_FILE_VIEWER_MIN_VERSION)
15948
+ fileViewer: isVersionAtLeast(clientVersion, REMOTE_FILE_VIEWER_MIN_VERSION),
15949
+ apiProxy: true,
15950
+ remoteGateway: false
15951
+ };
15952
+ }
15953
+ async function probeRemoteHostFeatures(client, clientVersion) {
15954
+ const fallback = remoteHostFeatures(clientVersion);
15955
+ let value;
15956
+ try {
15957
+ value = await client.rpc("harness.transport.describe", {});
15958
+ } catch (error) {
15959
+ if (error instanceof Error && "code" in error && error.code === "METHOD_NOT_FOUND") return fallback;
15960
+ throw error;
15961
+ }
15962
+ if (!isRecord3(value) || !Array.isArray(value.capabilities) || value.capabilities.some((capability) => typeof capability !== "string")) {
15963
+ throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned invalid transport capabilities.");
15964
+ }
15965
+ const capabilities = new Set(value.capabilities);
15966
+ const apiProxy = capabilities.has("harness.api.v1");
15967
+ const remoteGateway = capabilities.has("harness.remote.v1");
15968
+ if (!apiProxy && !remoteGateway) {
15969
+ throw new ClientModeError("FEATURE_NOT_SUPPORTED", "The remote Host exposes no supported Harness transport.");
15970
+ }
15971
+ return {
15972
+ commandList: remoteGateway || apiProxy && fallback.commandList,
15973
+ fileViewer: capabilities.has("fileviewer.read.v1"),
15974
+ apiProxy,
15975
+ remoteGateway
15574
15976
  };
15575
15977
  }
15576
15978
  function isVersionAtLeast(value, minimum) {
@@ -16051,11 +16453,11 @@ function editableConfig(config) {
16051
16453
  } : false
16052
16454
  };
16053
16455
  }
16054
- function isRecord(value) {
16456
+ function isRecord4(value) {
16055
16457
  return typeof value === "object" && value !== null && !Array.isArray(value);
16056
16458
  }
16057
16459
  function record2(value) {
16058
- if (!isRecord(value)) throw new ClientModeError("INVALID_MESSAGE", "The control request payload is invalid.");
16460
+ if (!isRecord4(value)) throw new ClientModeError("INVALID_MESSAGE", "The control request payload is invalid.");
16059
16461
  return value;
16060
16462
  }
16061
16463
  function ok2(value) {
@@ -16109,7 +16511,6 @@ function redact(value, key = "") {
16109
16511
 
16110
16512
  // src/service.ts
16111
16513
  import { randomUUID } from "node:crypto";
16112
- import { RpcId } from "@deepseek-ai/dsh-host-apiproxy/api";
16113
16514
 
16114
16515
  // src/connection-controller.ts
16115
16516
  var ConnectionController = class {
@@ -16326,7 +16727,9 @@ function safeErrorCode(error) {
16326
16727
 
16327
16728
  // src/rpc-router.ts
16328
16729
  var wireRequestSchema = external_exports.object({ method: external_exports.string().min(1), params: external_exports.unknown() }).strict();
16730
+ var emptyParamsSchema = external_exports.object({}).strict();
16329
16731
  var apiMethods = /* @__PURE__ */ new Set([
16732
+ "harness.transport.describe",
16330
16733
  "harness.api.call",
16331
16734
  "harness.api.transfer.open",
16332
16735
  "harness.api.transfer.chunk",
@@ -16336,19 +16739,38 @@ var apiMethods = /* @__PURE__ */ new Set([
16336
16739
  "harness.api.respond",
16337
16740
  "harness.api.stream.open",
16338
16741
  "harness.api.stream.close",
16742
+ "harness.remote.call",
16743
+ "harness.remote.transfer.open",
16744
+ "harness.remote.transfer.chunk",
16745
+ "harness.remote.transfer.commit",
16746
+ "harness.remote.transfer.read",
16747
+ "harness.remote.transfer.close",
16748
+ "harness.remote.stream.open",
16749
+ "harness.remote.stream.close",
16339
16750
  "fileviewer.call"
16340
16751
  ]);
16341
- var HOST_CAPABILITIES = ["harness.api.v1", "harness.api.transfer.v1", "fileviewer.read.v1"];
16752
+ var HOST_CAPABILITIES = [
16753
+ "harness.api.v1",
16754
+ "harness.api.transfer.v1",
16755
+ "harness.remote.v1",
16756
+ "harness.remote.transfer.v1",
16757
+ "fileviewer.read.v1"
16758
+ ];
16342
16759
  var RpcRouter = class {
16343
- constructor(harnessApi, maxPending = 128, logger, fileViewer) {
16760
+ constructor(harnessApi, maxPending = 128, logger, fileViewer, harnessRemote, capabilities = () => HOST_CAPABILITIES) {
16344
16761
  this.harnessApi = harnessApi;
16345
16762
  this.maxPending = maxPending;
16346
16763
  this.logger = logger;
16347
16764
  this.fileViewer = fileViewer;
16765
+ this.harnessRemote = harnessRemote;
16766
+ this.capabilities = capabilities;
16348
16767
  }
16349
16768
  active = 0;
16350
- closePeerStreams() {
16351
- return this.harnessApi.closeAll();
16769
+ async closePeerStreams() {
16770
+ await Promise.all([
16771
+ this.harnessApi?.closeAll(),
16772
+ this.harnessRemote?.closeAll()
16773
+ ]);
16352
16774
  }
16353
16775
  async handle(message) {
16354
16776
  if (message.type !== "rpc.request") {
@@ -16387,24 +16809,44 @@ var RpcRouter = class {
16387
16809
  }
16388
16810
  invoke(method, params) {
16389
16811
  switch (method) {
16812
+ case "harness.transport.describe": {
16813
+ emptyParamsSchema.parse(params);
16814
+ return { capabilities: [...this.capabilities()] };
16815
+ }
16390
16816
  case "harness.api.call":
16391
- return this.harnessApi.call(params);
16817
+ return this.requireApiProxy().call(params);
16392
16818
  case "harness.api.transfer.open":
16393
- return this.harnessApi.openTransfer(params);
16819
+ return this.requireApiProxy().openTransfer(params);
16394
16820
  case "harness.api.transfer.chunk":
16395
- return this.harnessApi.appendTransfer(params);
16821
+ return this.requireApiProxy().appendTransfer(params);
16396
16822
  case "harness.api.transfer.commit":
16397
- return this.harnessApi.commitTransfer(params);
16823
+ return this.requireApiProxy().commitTransfer(params);
16398
16824
  case "harness.api.transfer.read":
16399
- return this.harnessApi.readTransfer(params);
16825
+ return this.requireApiProxy().readTransfer(params);
16400
16826
  case "harness.api.transfer.close":
16401
- return this.harnessApi.closeTransfer(params);
16827
+ return this.requireApiProxy().closeTransfer(params);
16402
16828
  case "harness.api.respond":
16403
- return this.harnessApi.respond(params);
16829
+ return this.requireApiProxy().respond(params);
16404
16830
  case "harness.api.stream.open":
16405
- return this.harnessApi.openStream(params);
16831
+ return this.requireApiProxy().openStream(params);
16406
16832
  case "harness.api.stream.close":
16407
- return this.harnessApi.closeStream(params);
16833
+ return this.requireApiProxy().closeStream(params);
16834
+ case "harness.remote.call":
16835
+ return this.requireRemoteGateway().call(params);
16836
+ case "harness.remote.transfer.open":
16837
+ return this.requireRemoteGateway().openTransfer(params);
16838
+ case "harness.remote.transfer.chunk":
16839
+ return this.requireRemoteGateway().appendTransfer(params);
16840
+ case "harness.remote.transfer.commit":
16841
+ return this.requireRemoteGateway().commitTransfer(params);
16842
+ case "harness.remote.transfer.read":
16843
+ return this.requireRemoteGateway().readTransfer(params);
16844
+ case "harness.remote.transfer.close":
16845
+ return this.requireRemoteGateway().closeTransfer(params);
16846
+ case "harness.remote.stream.open":
16847
+ return this.requireRemoteGateway().openStream(params);
16848
+ case "harness.remote.stream.close":
16849
+ return this.requireRemoteGateway().closeStream(params);
16408
16850
  case "fileviewer.call": {
16409
16851
  if (this.fileViewer === void 0) {
16410
16852
  throw new RpcError("FILE_VIEWER_UNAVAILABLE", "The Remote Host does not have DSH File Viewer available.");
@@ -16415,6 +16857,18 @@ var RpcRouter = class {
16415
16857
  throw new RpcError("METHOD_NOT_FOUND", "The requested method does not exist.");
16416
16858
  }
16417
16859
  }
16860
+ requireApiProxy() {
16861
+ if (this.harnessApi === void 0) {
16862
+ throw new RpcError("FEATURE_NOT_SUPPORTED", "This Harness version does not provide the legacy ApiProxy transport.");
16863
+ }
16864
+ return this.harnessApi;
16865
+ }
16866
+ requireRemoteGateway() {
16867
+ if (this.harnessRemote === void 0) {
16868
+ throw new RpcError("FEATURE_NOT_SUPPORTED", "This Harness version does not provide the Remote Gateway transport.");
16869
+ }
16870
+ return this.harnessRemote;
16871
+ }
16418
16872
  };
16419
16873
  function errorResponse(requestId, error) {
16420
16874
  const code = safeErrorCode(error);
@@ -18069,28 +18523,333 @@ function concatChunks(chunks, totalBytes) {
18069
18523
  return result;
18070
18524
  }
18071
18525
 
18526
+ // src/harness-remote-bridge.ts
18527
+ var endpointSchema = external_exports.string().min(1).max(128).regex(/^(?:\$events(?:\/result)?|[A-Za-z0-9_$.-]+\/[A-Za-z0-9_$.-]+)$/);
18528
+ var callSchema3 = external_exports.object({ endpoint: endpointSchema, payload: external_exports.unknown() }).strict();
18529
+ var streamOpenSchema2 = external_exports.object({
18530
+ streamId: external_exports.string().min(1).max(128),
18531
+ endpoint: endpointSchema,
18532
+ payload: external_exports.unknown()
18533
+ }).strict();
18534
+ var streamCloseSchema2 = external_exports.object({ streamId: external_exports.string().min(1).max(128) }).strict();
18535
+ var transferOpenSchema2 = external_exports.object({
18536
+ transferId: external_exports.string().uuid(),
18537
+ totalBytes: external_exports.number().int().positive().max(MAX_HARNESS_API_TRANSFER_BYTES),
18538
+ totalChunks: external_exports.number().int().positive()
18539
+ }).strict();
18540
+ var transferChunkSchema2 = external_exports.object({
18541
+ transferId: external_exports.string().uuid(),
18542
+ index: external_exports.number().int().nonnegative(),
18543
+ data: external_exports.string().min(1)
18544
+ }).strict();
18545
+ var transferIdSchema2 = external_exports.object({ transferId: external_exports.string().uuid() }).strict();
18546
+ var transferReadSchema2 = external_exports.object({
18547
+ transferId: external_exports.string().uuid(),
18548
+ index: external_exports.number().int().nonnegative()
18549
+ }).strict();
18550
+ var MAX_ACTIVE_STREAMS = 16;
18551
+ var MAX_ACTIVE_TRANSFERS = 2;
18552
+ var TRANSFER_IDLE_MS = 2 * 6e4;
18553
+ var INLINE_TRANSFER_RESPONSE_BYTES2 = 2 * 1024 * 1024;
18554
+ var HARNESS_REMOTE_ALLOWLIST = [
18555
+ "$events",
18556
+ "$events/result",
18557
+ "agentPresets/list",
18558
+ "agentPresets/read",
18559
+ "agentPresets/select",
18560
+ "commands/execute",
18561
+ "commands/list",
18562
+ "credentials/describe",
18563
+ "credentials/set",
18564
+ "credentials/unset",
18565
+ "directoryPicker/list",
18566
+ "fileReferences/list",
18567
+ "goals/clear",
18568
+ "goals/complete",
18569
+ "goals/create",
18570
+ "goals/edit",
18571
+ "goals/pause",
18572
+ "goals/resume",
18573
+ "llm/discoverModels",
18574
+ "llm/listConfigurableProviders",
18575
+ "llm/listProviders",
18576
+ "messageFeedback/delete",
18577
+ "messageFeedback/list",
18578
+ "messageFeedback/put",
18579
+ "pluginInventory/list",
18580
+ "session/attachment",
18581
+ "session/cancel",
18582
+ "session/control",
18583
+ "session/create",
18584
+ "session/follow",
18585
+ "session/fork",
18586
+ "session/list",
18587
+ "session/modelCatalog",
18588
+ "session/page",
18589
+ "session/prompt",
18590
+ "session/rename",
18591
+ "session/search",
18592
+ "session/selectModel",
18593
+ "session/updateQueue",
18594
+ "sessionReferenceResolver/candidates",
18595
+ "settings/describe",
18596
+ "settings/mutate",
18597
+ "settings/replace",
18598
+ "settings/update",
18599
+ "skills/list",
18600
+ "subagents/interruptByParent",
18601
+ "subagents/list",
18602
+ "subagents/prompt",
18603
+ "workspace/archiveSession",
18604
+ "workspace/create",
18605
+ "workspace/delete",
18606
+ "workspace/follow",
18607
+ "workspace/insertBefore",
18608
+ "workspace/insertSessionBefore",
18609
+ "workspace/rename"
18610
+ ];
18611
+ var allowedEndpoints = new Set(HARNESS_REMOTE_ALLOWLIST);
18612
+ var HarnessRemoteBridge = class {
18613
+ constructor(gateway, publish, logger) {
18614
+ this.gateway = gateway;
18615
+ this.publish = publish;
18616
+ this.logger = logger;
18617
+ }
18618
+ streams = /* @__PURE__ */ new Map();
18619
+ incomingTransfers = /* @__PURE__ */ new Map();
18620
+ outgoingTransfers = /* @__PURE__ */ new Map();
18621
+ async call(input) {
18622
+ const params = callSchema3.parse(input);
18623
+ this.assertAllowed(params.endpoint);
18624
+ const startedAt = performance.now();
18625
+ try {
18626
+ const result = await this.gateway.dispatch(params.endpoint, params.payload, AbortSignal.timeout(6e4));
18627
+ this.logger?.debug("harness remote call ok", {
18628
+ endpoint: params.endpoint,
18629
+ durationMs: Math.round(performance.now() - startedAt)
18630
+ });
18631
+ return result;
18632
+ } catch (error) {
18633
+ this.logger?.warn("harness remote call failed", {
18634
+ endpoint: params.endpoint,
18635
+ durationMs: Math.round(performance.now() - startedAt),
18636
+ code: this.gateway.failure(error).code
18637
+ });
18638
+ throw error;
18639
+ }
18640
+ }
18641
+ openTransfer(input) {
18642
+ this.pruneTransfers();
18643
+ const params = transferOpenSchema2.parse(input);
18644
+ if (params.totalChunks !== Math.ceil(params.totalBytes / HARNESS_API_TRANSFER_CHUNK_BYTES)) {
18645
+ throw new RpcError("INVALID_MESSAGE", "The Harness Remote transfer chunk count is invalid.");
18646
+ }
18647
+ if (this.incomingTransfers.has(params.transferId) || this.outgoingTransfers.has(params.transferId)) {
18648
+ throw new RpcError("REQUEST_CONFLICT", "The Harness Remote transfer id is already active.");
18649
+ }
18650
+ if (this.incomingTransfers.size >= MAX_ACTIVE_TRANSFERS) {
18651
+ throw new RpcError("RATE_LIMITED", "Too many Harness Remote transfers are active.", void 0, true);
18652
+ }
18653
+ this.incomingTransfers.set(params.transferId, {
18654
+ totalBytes: params.totalBytes,
18655
+ totalChunks: params.totalChunks,
18656
+ chunks: [],
18657
+ receivedBytes: 0,
18658
+ touchedAt: Date.now()
18659
+ });
18660
+ return { opened: true, transferId: params.transferId };
18661
+ }
18662
+ appendTransfer(input) {
18663
+ this.pruneTransfers();
18664
+ const params = transferChunkSchema2.parse(input);
18665
+ const transfer = this.incomingTransfers.get(params.transferId);
18666
+ if (transfer === void 0) throw new RpcError("TRANSFER_NOT_FOUND", "The Harness Remote transfer is not active.");
18667
+ if (params.index !== transfer.chunks.length || params.index >= transfer.totalChunks) {
18668
+ this.incomingTransfers.delete(params.transferId);
18669
+ throw new RpcError("INVALID_MESSAGE", "Harness Remote transfer chunks must arrive exactly once and in order.");
18670
+ }
18671
+ const chunk = decodeCanonicalBase642(params.data);
18672
+ const expectedBytes = Math.min(
18673
+ HARNESS_API_TRANSFER_CHUNK_BYTES,
18674
+ transfer.totalBytes - params.index * HARNESS_API_TRANSFER_CHUNK_BYTES
18675
+ );
18676
+ if (chunk.byteLength !== expectedBytes) {
18677
+ this.incomingTransfers.delete(params.transferId);
18678
+ throw new RpcError("INVALID_MESSAGE", "The Harness Remote transfer chunk size is invalid.");
18679
+ }
18680
+ transfer.chunks.push(chunk);
18681
+ transfer.receivedBytes += chunk.byteLength;
18682
+ transfer.touchedAt = Date.now();
18683
+ return { accepted: true, transferId: params.transferId, index: params.index };
18684
+ }
18685
+ async commitTransfer(input) {
18686
+ this.pruneTransfers();
18687
+ const params = transferIdSchema2.parse(input);
18688
+ const transfer = this.incomingTransfers.get(params.transferId);
18689
+ if (transfer === void 0) throw new RpcError("TRANSFER_NOT_FOUND", "The Harness Remote transfer is not active.");
18690
+ this.incomingTransfers.delete(params.transferId);
18691
+ if (transfer.chunks.length !== transfer.totalChunks || transfer.receivedBytes !== transfer.totalBytes) {
18692
+ throw new RpcError("INVALID_MESSAGE", "The Harness Remote transfer is incomplete.");
18693
+ }
18694
+ let request;
18695
+ try {
18696
+ request = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(concatChunks2(transfer.chunks, transfer.totalBytes)));
18697
+ } catch {
18698
+ throw new RpcError("INVALID_MESSAGE", "The Harness Remote transfer does not contain a valid request.");
18699
+ }
18700
+ const response = await this.call(callSchema3.parse(request));
18701
+ const responseBytes = new TextEncoder().encode(JSON.stringify(response));
18702
+ if (responseBytes.byteLength <= INLINE_TRANSFER_RESPONSE_BYTES2) return { kind: "inline", response };
18703
+ if (responseBytes.byteLength > MAX_HARNESS_API_TRANSFER_BYTES) {
18704
+ throw new RpcError("RESPONSE_TOO_LARGE", "The Harness Remote response exceeds the bounded transfer limit.");
18705
+ }
18706
+ if (this.outgoingTransfers.size >= MAX_ACTIVE_TRANSFERS) {
18707
+ throw new RpcError("RATE_LIMITED", "Too many Harness Remote response transfers are active.", void 0, true);
18708
+ }
18709
+ const totalChunks = Math.ceil(responseBytes.byteLength / HARNESS_API_TRANSFER_CHUNK_BYTES);
18710
+ this.outgoingTransfers.set(params.transferId, {
18711
+ bytes: responseBytes,
18712
+ totalChunks,
18713
+ nextIndex: 0,
18714
+ touchedAt: Date.now()
18715
+ });
18716
+ return { kind: "chunked", transferId: params.transferId, totalBytes: responseBytes.byteLength, totalChunks };
18717
+ }
18718
+ readTransfer(input) {
18719
+ this.pruneTransfers();
18720
+ const params = transferReadSchema2.parse(input);
18721
+ const transfer = this.outgoingTransfers.get(params.transferId);
18722
+ if (transfer === void 0) throw new RpcError("TRANSFER_NOT_FOUND", "The Harness Remote response transfer is not active.");
18723
+ if (params.index !== transfer.nextIndex || params.index >= transfer.totalChunks) {
18724
+ this.outgoingTransfers.delete(params.transferId);
18725
+ throw new RpcError("INVALID_MESSAGE", "Harness Remote response chunks must be read exactly once and in order.");
18726
+ }
18727
+ const start = params.index * HARNESS_API_TRANSFER_CHUNK_BYTES;
18728
+ const end = Math.min(start + HARNESS_API_TRANSFER_CHUNK_BYTES, transfer.bytes.byteLength);
18729
+ transfer.nextIndex += 1;
18730
+ transfer.touchedAt = Date.now();
18731
+ return {
18732
+ transferId: params.transferId,
18733
+ index: params.index,
18734
+ data: Buffer.from(transfer.bytes.subarray(start, end)).toString("base64")
18735
+ };
18736
+ }
18737
+ closeTransfer(input) {
18738
+ const params = transferIdSchema2.parse(input);
18739
+ const closed = this.incomingTransfers.delete(params.transferId) || this.outgoingTransfers.delete(params.transferId);
18740
+ return { closed, transferId: params.transferId };
18741
+ }
18742
+ async openStream(input) {
18743
+ const params = streamOpenSchema2.parse(input);
18744
+ this.assertAllowed(params.endpoint);
18745
+ if (this.streams.has(params.streamId)) throw new RpcError("REQUEST_CONFLICT", "The Harness Remote stream is already open.");
18746
+ if (this.streams.size >= MAX_ACTIVE_STREAMS) {
18747
+ throw new RpcError("RATE_LIMITED", "Too many Harness Remote streams are open.", void 0, true);
18748
+ }
18749
+ const controller = new AbortController();
18750
+ const source = await this.gateway.open(params.endpoint, params.payload, controller.signal);
18751
+ this.streams.set(params.streamId, { controller });
18752
+ void this.pump(params.streamId, source, controller.signal);
18753
+ return { opened: true, streamId: params.streamId };
18754
+ }
18755
+ closeStream(input) {
18756
+ const params = streamCloseSchema2.parse(input);
18757
+ const active = this.streams.get(params.streamId);
18758
+ if (active !== void 0) {
18759
+ this.streams.delete(params.streamId);
18760
+ active.controller.abort("client-closed");
18761
+ }
18762
+ return { closed: active !== void 0, streamId: params.streamId };
18763
+ }
18764
+ async closeAll(reason = "peer-disconnected") {
18765
+ const streams = [...this.streams.entries()];
18766
+ this.streams.clear();
18767
+ this.incomingTransfers.clear();
18768
+ this.outgoingTransfers.clear();
18769
+ for (const [, stream] of streams) stream.controller.abort(reason);
18770
+ }
18771
+ assertAllowed(endpoint) {
18772
+ if (!allowedEndpoints.has(endpoint)) {
18773
+ throw new RpcError("METHOD_NOT_ALLOWED", "The requested Harness Remote endpoint is not allowed.");
18774
+ }
18775
+ if (endpoint === "$events" && !this.gateway.supportsCarrier) {
18776
+ throw new RpcError("FEATURE_NOT_SUPPORTED", "This Harness version does not support Remote event transport.");
18777
+ }
18778
+ }
18779
+ async pump(streamId, source, signal) {
18780
+ let reason = "completed";
18781
+ let failure;
18782
+ try {
18783
+ for await (const value of source) {
18784
+ if (signal.aborted) break;
18785
+ await this.publish("harness.remote.frame", {
18786
+ streamId,
18787
+ hasValue: true,
18788
+ ...value === void 0 ? {} : { value }
18789
+ });
18790
+ }
18791
+ if (signal.aborted) reason = "cancelled";
18792
+ } catch (error) {
18793
+ reason = signal.aborted ? "cancelled" : "failed";
18794
+ if (!signal.aborted) failure = this.gateway.failure(error);
18795
+ } finally {
18796
+ this.streams.delete(streamId);
18797
+ await this.publish("harness.remote.stream.closed", {
18798
+ streamId,
18799
+ reason,
18800
+ ...failure === void 0 ? {} : { failure }
18801
+ }).catch(() => void 0);
18802
+ }
18803
+ }
18804
+ pruneTransfers() {
18805
+ const cutoff = Date.now() - TRANSFER_IDLE_MS;
18806
+ for (const [id, transfer] of this.incomingTransfers) if (transfer.touchedAt < cutoff) this.incomingTransfers.delete(id);
18807
+ for (const [id, transfer] of this.outgoingTransfers) if (transfer.touchedAt < cutoff) this.outgoingTransfers.delete(id);
18808
+ }
18809
+ };
18810
+ function decodeCanonicalBase642(value) {
18811
+ const bytes = Buffer.from(value, "base64");
18812
+ if (bytes.toString("base64") !== value) throw new RpcError("INVALID_MESSAGE", "The Harness Remote transfer chunk is invalid.");
18813
+ return bytes;
18814
+ }
18815
+ function concatChunks2(chunks, totalBytes) {
18816
+ const result = new Uint8Array(totalBytes);
18817
+ let offset = 0;
18818
+ for (const chunk of chunks) {
18819
+ result.set(chunk, offset);
18820
+ offset += chunk.byteLength;
18821
+ }
18822
+ return result;
18823
+ }
18824
+
18072
18825
  // src/service.ts
18073
18826
  var HostPluginRuntime = class {
18074
- constructor(config, identities, apiProxy, logger, typertGateway, fileViewerHost) {
18827
+ constructor(config, identities, apiProxy, logger, localGateway, fileViewerHost) {
18075
18828
  this.config = config;
18076
18829
  this.identities = identities;
18077
18830
  this.apiProxy = apiProxy;
18078
18831
  this.logger = logger;
18832
+ this.localGateway = localGateway;
18079
18833
  this.fileViewerHost = fileViewerHost;
18080
18834
  this.connections = new ConnectionController(this.identities, (_context, send) => {
18081
- const harnessApi = new HarnessApiBridge(
18835
+ const harnessApi = this.apiProxy === void 0 ? void 0 : new HarnessApiBridge(
18082
18836
  this.apiProxy,
18083
18837
  (event, data) => send(createEvent(event, data)),
18084
18838
  void 0,
18085
18839
  this.logger,
18086
- typertGateway?.(),
18840
+ this.localGateway,
18087
18841
  this.harnessVersion
18088
18842
  );
18843
+ const harnessRemote = this.localGateway?.supportsCarrier === true ? new HarnessRemoteBridge(
18844
+ this.localGateway,
18845
+ (event, data) => send(createEvent(event, data)),
18846
+ this.logger
18847
+ ) : void 0;
18089
18848
  const fileViewer = new RemoteFileViewerBridge(
18090
18849
  () => this.fileViewerHost?.(),
18091
18850
  this.logger
18092
18851
  );
18093
- return new RpcRouter(harnessApi, void 0, this.logger, fileViewer);
18852
+ return new RpcRouter(harnessApi, void 0, this.logger, fileViewer, harnessRemote, () => this.hostCapabilities());
18094
18853
  }, this.logger);
18095
18854
  if (config.serverUrl !== void 0) {
18096
18855
  this.serverApi = new HostServerApi(config.serverUrl, new ServerCredentialStore(identities.directory));
@@ -18231,7 +18990,7 @@ var HostPluginRuntime = class {
18231
18990
  this.logger,
18232
18991
  void 0,
18233
18992
  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"],
18993
+ () => this.hostCapabilities(),
18235
18994
  this.harnessVersion
18236
18995
  );
18237
18996
  }
@@ -18239,7 +18998,8 @@ var HostPluginRuntime = class {
18239
18998
  let reportedVersion;
18240
18999
  let errorCode2;
18241
19000
  try {
18242
- const response = await this.apiProxy.host.describe({ rpcId: RpcId(randomUUID()), payload: {} });
19001
+ const response = await this.apiProxy?.host.describe({ rpcId: randomUUID(), payload: {} });
19002
+ if (response === void 0) throw new Error("ApiProxy is unavailable");
18243
19003
  if (!response.result.ok) {
18244
19004
  errorCode2 = response.result.error.code;
18245
19005
  } else {
@@ -18253,6 +19013,15 @@ var HostPluginRuntime = class {
18253
19013
  this.logger.warn("Harness version is unavailable", errorCode2 === void 0 ? void 0 : { code: errorCode2 });
18254
19014
  return void 0;
18255
19015
  }
19016
+ hostCapabilities() {
19017
+ const capabilities = [];
19018
+ if (this.apiProxy !== void 0) capabilities.push("harness.api.v1", "harness.api.transfer.v1");
19019
+ if (this.localGateway?.supportsCarrier === true) {
19020
+ capabilities.push("harness.remote.v1", "harness.remote.transfer.v1");
19021
+ }
19022
+ if (this.fileViewerHost?.() !== void 0) capabilities.push("fileviewer.read.v1");
19023
+ return capabilities;
19024
+ }
18256
19025
  };
18257
19026
  function shortId5(value) {
18258
19027
  return value.length <= 12 ? value : `${value.slice(0, 8)}\u2026${value.slice(-4)}`;
@@ -18335,7 +19104,13 @@ function decodeBase64(value) {
18335
19104
  var name = "ds-harness-remote";
18336
19105
  var legacyLoaderModuleNames = /* @__PURE__ */ new Set(["dsh-remote", "@dsh-remote/plugin"]);
18337
19106
  function apply(ctx, input = {}) {
18338
- ctx.inject(["settings", "apiProxy", "connection", "typertGateway"], (runtimeContext) => activate(runtimeContext, input));
19107
+ ctx.inject(["settings", "connection", "typertGateway"], (runtimeContext) => {
19108
+ const gateway = runtimeContext.get("typertGateway");
19109
+ if (runtimeContext.get("apiProxy") !== void 0 || new TypertGatewaySwitch(gateway).supportsCarrier()) {
19110
+ return activate(runtimeContext, input);
19111
+ }
19112
+ runtimeContext.inject(["apiProxy"], (legacyContext) => activate(legacyContext, input));
19113
+ });
18339
19114
  }
18340
19115
  async function activate(ctx, input) {
18341
19116
  const settings = ctx.get("settings");
@@ -18380,12 +19155,12 @@ async function activate(ctx, input) {
18380
19155
  hostIdentities,
18381
19156
  apiProxy,
18382
19157
  logger,
18383
- () => localTypertGateway,
19158
+ localTypertGateway,
18384
19159
  () => ctx.get("fileViewerHost")
18385
19160
  );
18386
19161
  let clientRuntime;
18387
19162
  const hostControl = runtime;
18388
- if (config.serverUrl !== void 0 && apiProxy !== void 0 && connection !== void 0) {
19163
+ if (config.serverUrl !== void 0 && connection !== void 0) {
18389
19164
  const clientIdentities = new IdentityStore({
18390
19165
  directory: serverStorageDirectory(defaultIdentityDirectory, config.serverUrl, "client")
18391
19166
  });
@@ -18412,6 +19187,7 @@ async function activate(ctx, input) {
18412
19187
  logger.warn("client remote mode is unavailable", {
18413
19188
  serverConfigured: config.serverUrl !== void 0,
18414
19189
  apiProxyAvailable: apiProxy !== void 0,
19190
+ remoteGatewayAvailable: localTypertGateway.supportsCarrier,
18415
19191
  connectionAvailable: connection !== void 0
18416
19192
  });
18417
19193
  }
@@ -18461,8 +19237,10 @@ export {
18461
19237
  ConnectionController,
18462
19238
  ConnectionRejectedError,
18463
19239
  HARNESS_API_ALLOWLIST,
19240
+ HARNESS_REMOTE_ALLOWLIST,
18464
19241
  HOST_CAPABILITIES,
18465
19242
  HarnessApiBridge,
19243
+ HarnessRemoteBridge,
18466
19244
  HostPluginRuntime,
18467
19245
  HostServerApi,
18468
19246
  HostServerConnection,
@@ -18471,6 +19249,7 @@ export {
18471
19249
  PluginControlRuntime,
18472
19250
  RemoteFileViewerBridge,
18473
19251
  RemoteHarnessApiProxy,
19252
+ RemoteTypertGateway,
18474
19253
  RpcError,
18475
19254
  RpcRouter,
18476
19255
  ServerApiError,