ds-harness-remote 0.3.35 → 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.
- package/README.md +26 -24
- package/dist/client-runtime.d.ts +9 -4
- package/dist/client-runtime.d.ts.map +1 -1
- package/dist/client-runtime.js +137 -35
- package/dist/client-runtime.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.github.js +79 -32
- package/dist/client.js +79 -32
- package/dist/client.js.map +1 -1
- package/dist/harness-api-bridge.d.ts +2 -9
- package/dist/harness-api-bridge.d.ts.map +1 -1
- package/dist/harness-api-bridge.js.map +1 -1
- package/dist/harness-remote-bridge.d.ts +46 -0
- package/dist/harness-remote-bridge.d.ts.map +1 -0
- package/dist/harness-remote-bridge.js +321 -0
- package/dist/harness-remote-bridge.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +870 -81
- package/dist/index.js.map +4 -4
- package/dist/remote-file-content-provider.d.ts +3 -1
- package/dist/remote-file-content-provider.d.ts.map +1 -1
- package/dist/remote-file-content-provider.js +10 -1
- package/dist/remote-file-content-provider.js.map +1 -1
- package/dist/remote-typert-gateway.d.ts +13 -0
- package/dist/remote-typert-gateway.d.ts.map +1 -0
- package/dist/remote-typert-gateway.js +242 -0
- package/dist/remote-typert-gateway.js.map +1 -0
- package/dist/rpc-router.d.ts +7 -2
- package/dist/rpc-router.d.ts.map +1 -1
- package/dist/rpc-router.js +61 -12
- package/dist/rpc-router.js.map +1 -1
- package/dist/server-api.d.ts +1 -1
- package/dist/server-api.d.ts.map +1 -1
- package/dist/server-api.js +14 -3
- package/dist/server-api.js.map +1 -1
- package/dist/service.d.ts +5 -3
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +26 -8
- package/dist/service.js.map +1 -1
- package/dist/typert-gateway-contract.d.ts +48 -0
- package/dist/typert-gateway-contract.d.ts.map +1 -0
- package/dist/typert-gateway-contract.js +2 -0
- package/dist/typert-gateway-contract.js.map +1 -0
- package/dist/typert-gateway-switch.d.ts +28 -7
- package/dist/typert-gateway-switch.d.ts.map +1 -1
- package/dist/typert-gateway-switch.js +114 -23
- package/dist/typert-gateway-switch.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +13 -8
package/dist/index.js
CHANGED
|
@@ -4082,6 +4082,7 @@ var controlFrameTypes = [
|
|
|
4082
4082
|
"error"
|
|
4083
4083
|
];
|
|
4084
4084
|
var rpcMethods = [
|
|
4085
|
+
"harness.transport.describe",
|
|
4085
4086
|
"harness.api.call",
|
|
4086
4087
|
"harness.api.transfer.open",
|
|
4087
4088
|
"harness.api.transfer.chunk",
|
|
@@ -4091,6 +4092,14 @@ var rpcMethods = [
|
|
|
4091
4092
|
"harness.api.respond",
|
|
4092
4093
|
"harness.api.stream.open",
|
|
4093
4094
|
"harness.api.stream.close",
|
|
4095
|
+
"harness.remote.call",
|
|
4096
|
+
"harness.remote.transfer.open",
|
|
4097
|
+
"harness.remote.transfer.chunk",
|
|
4098
|
+
"harness.remote.transfer.commit",
|
|
4099
|
+
"harness.remote.transfer.read",
|
|
4100
|
+
"harness.remote.transfer.close",
|
|
4101
|
+
"harness.remote.stream.open",
|
|
4102
|
+
"harness.remote.stream.close",
|
|
4094
4103
|
"fileviewer.call"
|
|
4095
4104
|
];
|
|
4096
4105
|
function normalizeSdpMLineIndex(value) {
|
|
@@ -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.
|
|
13773
|
+
var PLUGIN_VERSION = "0.4.0";
|
|
13547
13774
|
|
|
13548
13775
|
// src/server-api.ts
|
|
13549
13776
|
var HostServerApi = class {
|
|
@@ -13620,7 +13847,8 @@ var HostServerApi = class {
|
|
|
13620
13847
|
}
|
|
13621
13848
|
return { qrId: value.qrId, scanUrl: value.scanUrl, expiresIn: value.expiresIn };
|
|
13622
13849
|
}
|
|
13623
|
-
async pollOAuthQrLogin(identity, qrId) {
|
|
13850
|
+
async pollOAuthQrLogin(identity, qrId, recoverIdentity) {
|
|
13851
|
+
this.bindIdentity(identity);
|
|
13624
13852
|
const value = requireRecord(await this.publicRequest(
|
|
13625
13853
|
`/api/v1/auth/oauth/qr/${encodeURIComponent(qrId)}`,
|
|
13626
13854
|
{ method: "GET" }
|
|
@@ -13637,11 +13865,19 @@ var HostServerApi = class {
|
|
|
13637
13865
|
if (typeof account.account !== "string" || account.account.length === 0 || typeof account.isAdmin !== "boolean") {
|
|
13638
13866
|
throw new ServerApiError("INVALID_MESSAGE", "The Server returned an invalid account profile.", false);
|
|
13639
13867
|
}
|
|
13640
|
-
|
|
13868
|
+
const authorization = {
|
|
13641
13869
|
accountToken: value.token,
|
|
13642
13870
|
account: account.account,
|
|
13643
13871
|
authorizationMethod: "account"
|
|
13644
|
-
}
|
|
13872
|
+
};
|
|
13873
|
+
try {
|
|
13874
|
+
await this.register(identity, authorization);
|
|
13875
|
+
} catch (error) {
|
|
13876
|
+
if (!(error instanceof ServerApiError) || error.code !== "DEVICE_REVOKED" || recoverIdentity === void 0) throw error;
|
|
13877
|
+
const nextIdentity = await recoverIdentity();
|
|
13878
|
+
this.bindIdentity(nextIdentity);
|
|
13879
|
+
await this.register(nextIdentity, authorization);
|
|
13880
|
+
}
|
|
13645
13881
|
return {
|
|
13646
13882
|
status: "complete",
|
|
13647
13883
|
authorization: { method: "account", account: account.account, isAdmin: account.isAdmin }
|
|
@@ -13935,51 +14171,133 @@ function requireRecord(value, name2) {
|
|
|
13935
14171
|
var REMOTE_COMMAND_METHODS = ["execute", "list"];
|
|
13936
14172
|
var ALL_REMOTE_COMMANDS = { execute: true, list: true };
|
|
13937
14173
|
var TypertGatewaySwitch = class {
|
|
13938
|
-
|
|
13939
|
-
this.gateway = gateway;
|
|
13940
|
-
this.originalInvoke = gateway.invoke;
|
|
13941
|
-
this.localInvoke = this.originalInvoke.bind(gateway);
|
|
13942
|
-
}
|
|
14174
|
+
runtime;
|
|
13943
14175
|
originalInvoke;
|
|
13944
14176
|
localInvoke;
|
|
14177
|
+
originalStream;
|
|
14178
|
+
localStream;
|
|
14179
|
+
originalDispatch;
|
|
14180
|
+
localDispatch;
|
|
14181
|
+
originalOpen;
|
|
14182
|
+
localOpen;
|
|
13945
14183
|
remoteInvoke;
|
|
14184
|
+
remoteTarget;
|
|
13946
14185
|
remoteSupport = { execute: false, list: false };
|
|
14186
|
+
target;
|
|
13947
14187
|
installed = false;
|
|
13948
|
-
|
|
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. */
|
|
13949
14200
|
local() {
|
|
13950
|
-
|
|
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 } } };
|
|
13951
14227
|
}
|
|
13952
14228
|
install() {
|
|
13953
14229
|
if (this.installed) return;
|
|
13954
|
-
this.
|
|
13955
|
-
|
|
13956
|
-
|
|
13957
|
-
|
|
13958
|
-
|
|
13959
|
-
|
|
13960
|
-
|
|
13961
|
-
|
|
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
|
+
}
|
|
13962
14240
|
this.installed = true;
|
|
13963
14241
|
}
|
|
13964
|
-
selectRemote(
|
|
14242
|
+
selectRemote(remote, support = ALL_REMOTE_COMMANDS, target) {
|
|
13965
14243
|
if (!this.installed) throw new Error("The Typert gateway switch is not installed.");
|
|
13966
|
-
this.remoteInvoke = invoke;
|
|
14244
|
+
this.remoteInvoke = typeof remote === "function" ? remote : (request) => remote.invoke(request);
|
|
14245
|
+
this.remoteTarget = typeof remote === "function" ? void 0 : remote;
|
|
13967
14246
|
this.remoteSupport = { ...support };
|
|
14247
|
+
this.target = target === void 0 ? void 0 : { ...target };
|
|
13968
14248
|
}
|
|
13969
14249
|
selectLocal() {
|
|
13970
14250
|
this.remoteInvoke = void 0;
|
|
14251
|
+
this.remoteTarget = void 0;
|
|
13971
14252
|
this.remoteSupport = { execute: false, list: false };
|
|
14253
|
+
this.target = void 0;
|
|
13972
14254
|
}
|
|
13973
14255
|
restore() {
|
|
13974
14256
|
if (!this.installed) return;
|
|
13975
14257
|
this.selectLocal();
|
|
13976
|
-
this.
|
|
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;
|
|
13977
14262
|
this.installed = false;
|
|
13978
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
|
+
}
|
|
13979
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
|
+
}
|
|
13980
14295
|
function isRemoteCommandMethod(method) {
|
|
13981
14296
|
return REMOTE_COMMAND_METHODS.includes(method);
|
|
13982
14297
|
}
|
|
14298
|
+
function isRecord2(value) {
|
|
14299
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14300
|
+
}
|
|
13983
14301
|
|
|
13984
14302
|
// src/werift-rtc.ts
|
|
13985
14303
|
import { createSocket } from "node:dgram";
|
|
@@ -15048,7 +15366,7 @@ var ClientModeRuntime = class {
|
|
|
15048
15366
|
this.logger = logger;
|
|
15049
15367
|
this.host = host;
|
|
15050
15368
|
this.rtcFactoryProvider = rtcFactoryProvider;
|
|
15051
|
-
this.proxySwitch = new ApiProxySwitch(apiProxy);
|
|
15369
|
+
this.proxySwitch = apiProxy === void 0 ? void 0 : new ApiProxySwitch(apiProxy);
|
|
15052
15370
|
this.gatewaySwitch = new TypertGatewaySwitch(typertGateway);
|
|
15053
15371
|
}
|
|
15054
15372
|
identity;
|
|
@@ -15061,7 +15379,7 @@ var ClientModeRuntime = class {
|
|
|
15061
15379
|
if (this.closed) throw new Error("client remote-mode runtime is closed");
|
|
15062
15380
|
this.identity = await this.identities.loadOrCreate(this.config.deviceName);
|
|
15063
15381
|
this.server.bindIdentity(this.identity);
|
|
15064
|
-
this.proxySwitch
|
|
15382
|
+
this.proxySwitch?.install();
|
|
15065
15383
|
this.gatewaySwitch.install();
|
|
15066
15384
|
this.logger.info("client remote-mode identity ready", {
|
|
15067
15385
|
deviceId: shortId(this.identity.deviceId),
|
|
@@ -15074,13 +15392,14 @@ var ClientModeRuntime = class {
|
|
|
15074
15392
|
});
|
|
15075
15393
|
}
|
|
15076
15394
|
status() {
|
|
15395
|
+
const targetStatus = this.gatewaySwitch.supportsCarrier() ? this.gatewaySwitch.status() : this.proxySwitch?.status() ?? this.gatewaySwitch.status();
|
|
15077
15396
|
return {
|
|
15078
15397
|
available: this.config.serverUrl !== void 0,
|
|
15079
15398
|
identityReady: this.identity !== void 0,
|
|
15080
15399
|
deviceId: this.identity?.deviceId,
|
|
15081
15400
|
deviceName: this.identity?.name,
|
|
15082
15401
|
serverUrl: this.config.serverUrl,
|
|
15083
|
-
...
|
|
15402
|
+
...targetStatus,
|
|
15084
15403
|
connected: this.connected !== void 0,
|
|
15085
15404
|
transport: this.connected?.client.getStats().mode ?? "Disconnected",
|
|
15086
15405
|
preferredTransports: this.config.forceRelay ? ["relay"] : ["lan", "p2p", "turn", "relay"],
|
|
@@ -15139,23 +15458,20 @@ var ClientModeRuntime = class {
|
|
|
15139
15458
|
return this.server.startOAuthQrLogin(provider);
|
|
15140
15459
|
}
|
|
15141
15460
|
async pollClientOAuthQrLogin(qrId) {
|
|
15142
|
-
|
|
15143
|
-
const result = await this.server.pollOAuthQrLogin(this.requireIdentity(), qrId);
|
|
15144
|
-
if (result.status === "complete") this.logger.info("Client account authorized with QR login");
|
|
15145
|
-
return result;
|
|
15146
|
-
} catch (error) {
|
|
15147
|
-
if (!(error instanceof ServerApiError) || error.code !== "DEVICE_REVOKED") throw error;
|
|
15461
|
+
const result = await this.server.pollOAuthQrLogin(this.requireIdentity(), qrId, async () => {
|
|
15148
15462
|
this.identity = await this.identities.reset(this.config.deviceName);
|
|
15149
15463
|
this.server.bindIdentity(this.identity);
|
|
15150
|
-
this.logger.info("Rotated revoked Client identity before QR retry");
|
|
15151
|
-
return
|
|
15152
|
-
}
|
|
15464
|
+
this.logger.info("Rotated revoked Client identity before QR authorization retry");
|
|
15465
|
+
return this.identity;
|
|
15466
|
+
});
|
|
15467
|
+
if (result.status === "complete") this.logger.info("Client account authorized with QR login");
|
|
15468
|
+
return result;
|
|
15153
15469
|
}
|
|
15154
15470
|
async clearClientAuthorization() {
|
|
15155
15471
|
const previous = this.connected;
|
|
15156
15472
|
this.connected = void 0;
|
|
15157
15473
|
this.pendingWorkspaceSelection = void 0;
|
|
15158
|
-
this.proxySwitch
|
|
15474
|
+
this.proxySwitch?.selectLocal();
|
|
15159
15475
|
this.gatewaySwitch.selectLocal();
|
|
15160
15476
|
await previous?.client.close().catch(() => void 0);
|
|
15161
15477
|
await this.server.revokeCurrentDevice();
|
|
@@ -15174,7 +15490,7 @@ var ClientModeRuntime = class {
|
|
|
15174
15490
|
}
|
|
15175
15491
|
async setMode(mode, targetDeviceId, signal) {
|
|
15176
15492
|
if (mode === "local") {
|
|
15177
|
-
this.proxySwitch
|
|
15493
|
+
this.proxySwitch?.selectLocal();
|
|
15178
15494
|
this.gatewaySwitch.selectLocal();
|
|
15179
15495
|
const previous2 = this.connected;
|
|
15180
15496
|
this.connected = void 0;
|
|
@@ -15187,18 +15503,31 @@ var ClientModeRuntime = class {
|
|
|
15187
15503
|
throw new ClientModeError("INVALID_MESSAGE", "A targetDeviceId is required for remote mode.");
|
|
15188
15504
|
}
|
|
15189
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
|
+
}
|
|
15190
15512
|
const previous = this.connected;
|
|
15191
15513
|
this.connected = next;
|
|
15192
15514
|
this.pendingWorkspaceSelection = void 0;
|
|
15193
|
-
|
|
15194
|
-
this.proxySwitch.selectRemote(remoteApi, { deviceId: next.target.deviceId, name: next.target.name });
|
|
15195
|
-
this.selectRemoteCommands(next);
|
|
15515
|
+
this.selectRemoteTarget(next);
|
|
15196
15516
|
await previous?.client.close().catch(() => void 0);
|
|
15197
15517
|
this.logger.info("Harness target switched", { mode: "remote", targetDeviceId: shortId(next.target.deviceId) });
|
|
15198
15518
|
return this.status();
|
|
15199
15519
|
}
|
|
15200
15520
|
async listRemoteDirectory(targetDeviceId, path, signal) {
|
|
15201
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
|
+
}
|
|
15202
15531
|
const api = new RemoteHarnessApiProxy(remote.client).api;
|
|
15203
15532
|
const response = await api.host.listDirectory({
|
|
15204
15533
|
rpcId: `remote-directory-${Date.now()}`,
|
|
@@ -15208,6 +15537,9 @@ var ClientModeRuntime = class {
|
|
|
15208
15537
|
}
|
|
15209
15538
|
async listRemoteWorkspaces(targetDeviceId, signal) {
|
|
15210
15539
|
const remote = await this.ensureConnected(targetDeviceId, signal);
|
|
15540
|
+
if (remote.features.remoteGateway) {
|
|
15541
|
+
return readRemoteWorkspaceBaseline(new RemoteTypertGateway(remote.client), signal);
|
|
15542
|
+
}
|
|
15211
15543
|
const api = new RemoteHarnessApiProxy(remote.client).api;
|
|
15212
15544
|
const response = await api.workspace.list({
|
|
15213
15545
|
rpcId: `remote-workspaces-${Date.now()}`,
|
|
@@ -15219,14 +15551,24 @@ var ClientModeRuntime = class {
|
|
|
15219
15551
|
async openRemoteWorkspace(targetDeviceId, path, signal) {
|
|
15220
15552
|
if (path.trim() === "") throw new ClientModeError("INVALID_MESSAGE", "A remote working directory is required.");
|
|
15221
15553
|
const remote = await this.ensureConnected(targetDeviceId, signal);
|
|
15222
|
-
|
|
15223
|
-
|
|
15224
|
-
|
|
15225
|
-
|
|
15226
|
-
|
|
15227
|
-
|
|
15228
|
-
|
|
15229
|
-
|
|
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);
|
|
15230
15572
|
const workspaceId = workspaceRecordId(workspace.workspace);
|
|
15231
15573
|
this.pendingWorkspaceSelection = { targetDeviceId: remote.target.deviceId, workspaceId };
|
|
15232
15574
|
this.logger.info("Remote workspace opened", { targetDeviceId: shortId(remote.target.deviceId) });
|
|
@@ -15242,17 +15584,17 @@ var ClientModeRuntime = class {
|
|
|
15242
15584
|
async close() {
|
|
15243
15585
|
if (this.closed) return;
|
|
15244
15586
|
this.closed = true;
|
|
15245
|
-
this.proxySwitch
|
|
15587
|
+
this.proxySwitch?.selectLocal();
|
|
15246
15588
|
this.gatewaySwitch.selectLocal();
|
|
15247
15589
|
this.pendingWorkspaceSelection = void 0;
|
|
15248
15590
|
await this.connected?.client.close().catch(() => void 0);
|
|
15249
15591
|
this.connected = void 0;
|
|
15250
|
-
this.proxySwitch
|
|
15592
|
+
this.proxySwitch?.restore();
|
|
15251
15593
|
this.gatewaySwitch.restore();
|
|
15252
15594
|
}
|
|
15253
15595
|
async callRemoteFileViewer(endpoint, payload, signal) {
|
|
15254
15596
|
const remote = this.connected;
|
|
15255
|
-
if (remote === void 0 || this.
|
|
15597
|
+
if (remote === void 0 || this.status().mode !== "remote") {
|
|
15256
15598
|
throw new ClientModeError("REMOTE_NOT_CONNECTED", "No Remote Host is selected.", true);
|
|
15257
15599
|
}
|
|
15258
15600
|
if (!remote.features.fileViewer) {
|
|
@@ -15260,11 +15602,25 @@ var ClientModeRuntime = class {
|
|
|
15260
15602
|
}
|
|
15261
15603
|
return remote.client.rpc("fileviewer.call", { endpoint, payload }, signal);
|
|
15262
15604
|
}
|
|
15263
|
-
|
|
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);
|
|
15264
15612
|
this.gatewaySwitch.selectRemote((request) => invokeRemoteCommand(remote.client, request), {
|
|
15265
15613
|
execute: true,
|
|
15266
15614
|
list: remote.features.commandList
|
|
15267
|
-
});
|
|
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
|
+
);
|
|
15268
15624
|
}
|
|
15269
15625
|
async connect(targetDeviceId, signal) {
|
|
15270
15626
|
signal?.throwIfAborted();
|
|
@@ -15331,7 +15687,7 @@ var ClientModeRuntime = class {
|
|
|
15331
15687
|
if (this.connected?.client !== connectedClient) return;
|
|
15332
15688
|
this.connected = void 0;
|
|
15333
15689
|
this.pendingWorkspaceSelection = void 0;
|
|
15334
|
-
this.proxySwitch
|
|
15690
|
+
this.proxySwitch?.selectLocal();
|
|
15335
15691
|
this.gatewaySwitch.selectLocal();
|
|
15336
15692
|
void connectedClient.close().catch(() => void 0);
|
|
15337
15693
|
this.logger.warn("remote Harness transport closed; falling back to local mode", {
|
|
@@ -15344,7 +15700,8 @@ var ClientModeRuntime = class {
|
|
|
15344
15700
|
transport: connectedClient.getStats().mode,
|
|
15345
15701
|
...webrtcDiagnosticsLogFields(connectionDetails?.webRtc?.diagnostics)
|
|
15346
15702
|
});
|
|
15347
|
-
|
|
15703
|
+
const features = await probeRemoteHostFeatures(connectedClient, serverDevice.clientVersion);
|
|
15704
|
+
return { client: connectedClient, target, transport: connectedTransport, features };
|
|
15348
15705
|
} catch (error) {
|
|
15349
15706
|
await client?.close().catch(() => void 0);
|
|
15350
15707
|
throw error;
|
|
@@ -15513,6 +15870,9 @@ function record(value) {
|
|
|
15513
15870
|
}
|
|
15514
15871
|
return value;
|
|
15515
15872
|
}
|
|
15873
|
+
function isRecord3(value) {
|
|
15874
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15875
|
+
}
|
|
15516
15876
|
function ok(value) {
|
|
15517
15877
|
return { ok: true, value };
|
|
15518
15878
|
}
|
|
@@ -15528,6 +15888,27 @@ async function invokeRemoteCommand(client, request) {
|
|
|
15528
15888
|
}
|
|
15529
15889
|
return unwrapNativeResult(response);
|
|
15530
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
|
+
}
|
|
15531
15912
|
function unwrapNativeResult(response) {
|
|
15532
15913
|
const result = response.result;
|
|
15533
15914
|
if (typeof result !== "object" || result === null || !("ok" in result)) {
|
|
@@ -15564,7 +15945,34 @@ function shortId(value) {
|
|
|
15564
15945
|
function remoteHostFeatures(clientVersion) {
|
|
15565
15946
|
return {
|
|
15566
15947
|
commandList: isVersionAtLeast(clientVersion, REMOTE_COMMAND_LIST_MIN_VERSION),
|
|
15567
|
-
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
|
|
15568
15976
|
};
|
|
15569
15977
|
}
|
|
15570
15978
|
function isVersionAtLeast(value, minimum) {
|
|
@@ -16045,11 +16453,11 @@ function editableConfig(config) {
|
|
|
16045
16453
|
} : false
|
|
16046
16454
|
};
|
|
16047
16455
|
}
|
|
16048
|
-
function
|
|
16456
|
+
function isRecord4(value) {
|
|
16049
16457
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16050
16458
|
}
|
|
16051
16459
|
function record2(value) {
|
|
16052
|
-
if (!
|
|
16460
|
+
if (!isRecord4(value)) throw new ClientModeError("INVALID_MESSAGE", "The control request payload is invalid.");
|
|
16053
16461
|
return value;
|
|
16054
16462
|
}
|
|
16055
16463
|
function ok2(value) {
|
|
@@ -16103,7 +16511,6 @@ function redact(value, key = "") {
|
|
|
16103
16511
|
|
|
16104
16512
|
// src/service.ts
|
|
16105
16513
|
import { randomUUID } from "node:crypto";
|
|
16106
|
-
import { RpcId } from "@deepseek-ai/dsh-host-apiproxy/api";
|
|
16107
16514
|
|
|
16108
16515
|
// src/connection-controller.ts
|
|
16109
16516
|
var ConnectionController = class {
|
|
@@ -16320,7 +16727,9 @@ function safeErrorCode(error) {
|
|
|
16320
16727
|
|
|
16321
16728
|
// src/rpc-router.ts
|
|
16322
16729
|
var wireRequestSchema = external_exports.object({ method: external_exports.string().min(1), params: external_exports.unknown() }).strict();
|
|
16730
|
+
var emptyParamsSchema = external_exports.object({}).strict();
|
|
16323
16731
|
var apiMethods = /* @__PURE__ */ new Set([
|
|
16732
|
+
"harness.transport.describe",
|
|
16324
16733
|
"harness.api.call",
|
|
16325
16734
|
"harness.api.transfer.open",
|
|
16326
16735
|
"harness.api.transfer.chunk",
|
|
@@ -16330,19 +16739,38 @@ var apiMethods = /* @__PURE__ */ new Set([
|
|
|
16330
16739
|
"harness.api.respond",
|
|
16331
16740
|
"harness.api.stream.open",
|
|
16332
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",
|
|
16333
16750
|
"fileviewer.call"
|
|
16334
16751
|
]);
|
|
16335
|
-
var HOST_CAPABILITIES = [
|
|
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
|
+
];
|
|
16336
16759
|
var RpcRouter = class {
|
|
16337
|
-
constructor(harnessApi, maxPending = 128, logger, fileViewer) {
|
|
16760
|
+
constructor(harnessApi, maxPending = 128, logger, fileViewer, harnessRemote, capabilities = () => HOST_CAPABILITIES) {
|
|
16338
16761
|
this.harnessApi = harnessApi;
|
|
16339
16762
|
this.maxPending = maxPending;
|
|
16340
16763
|
this.logger = logger;
|
|
16341
16764
|
this.fileViewer = fileViewer;
|
|
16765
|
+
this.harnessRemote = harnessRemote;
|
|
16766
|
+
this.capabilities = capabilities;
|
|
16342
16767
|
}
|
|
16343
16768
|
active = 0;
|
|
16344
|
-
closePeerStreams() {
|
|
16345
|
-
|
|
16769
|
+
async closePeerStreams() {
|
|
16770
|
+
await Promise.all([
|
|
16771
|
+
this.harnessApi?.closeAll(),
|
|
16772
|
+
this.harnessRemote?.closeAll()
|
|
16773
|
+
]);
|
|
16346
16774
|
}
|
|
16347
16775
|
async handle(message) {
|
|
16348
16776
|
if (message.type !== "rpc.request") {
|
|
@@ -16381,24 +16809,44 @@ var RpcRouter = class {
|
|
|
16381
16809
|
}
|
|
16382
16810
|
invoke(method, params) {
|
|
16383
16811
|
switch (method) {
|
|
16812
|
+
case "harness.transport.describe": {
|
|
16813
|
+
emptyParamsSchema.parse(params);
|
|
16814
|
+
return { capabilities: [...this.capabilities()] };
|
|
16815
|
+
}
|
|
16384
16816
|
case "harness.api.call":
|
|
16385
|
-
return this.
|
|
16817
|
+
return this.requireApiProxy().call(params);
|
|
16386
16818
|
case "harness.api.transfer.open":
|
|
16387
|
-
return this.
|
|
16819
|
+
return this.requireApiProxy().openTransfer(params);
|
|
16388
16820
|
case "harness.api.transfer.chunk":
|
|
16389
|
-
return this.
|
|
16821
|
+
return this.requireApiProxy().appendTransfer(params);
|
|
16390
16822
|
case "harness.api.transfer.commit":
|
|
16391
|
-
return this.
|
|
16823
|
+
return this.requireApiProxy().commitTransfer(params);
|
|
16392
16824
|
case "harness.api.transfer.read":
|
|
16393
|
-
return this.
|
|
16825
|
+
return this.requireApiProxy().readTransfer(params);
|
|
16394
16826
|
case "harness.api.transfer.close":
|
|
16395
|
-
return this.
|
|
16827
|
+
return this.requireApiProxy().closeTransfer(params);
|
|
16396
16828
|
case "harness.api.respond":
|
|
16397
|
-
return this.
|
|
16829
|
+
return this.requireApiProxy().respond(params);
|
|
16398
16830
|
case "harness.api.stream.open":
|
|
16399
|
-
return this.
|
|
16831
|
+
return this.requireApiProxy().openStream(params);
|
|
16400
16832
|
case "harness.api.stream.close":
|
|
16401
|
-
return this.
|
|
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);
|
|
16402
16850
|
case "fileviewer.call": {
|
|
16403
16851
|
if (this.fileViewer === void 0) {
|
|
16404
16852
|
throw new RpcError("FILE_VIEWER_UNAVAILABLE", "The Remote Host does not have DSH File Viewer available.");
|
|
@@ -16409,6 +16857,18 @@ var RpcRouter = class {
|
|
|
16409
16857
|
throw new RpcError("METHOD_NOT_FOUND", "The requested method does not exist.");
|
|
16410
16858
|
}
|
|
16411
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
|
+
}
|
|
16412
16872
|
};
|
|
16413
16873
|
function errorResponse(requestId, error) {
|
|
16414
16874
|
const code = safeErrorCode(error);
|
|
@@ -18063,28 +18523,333 @@ function concatChunks(chunks, totalBytes) {
|
|
|
18063
18523
|
return result;
|
|
18064
18524
|
}
|
|
18065
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
|
+
|
|
18066
18825
|
// src/service.ts
|
|
18067
18826
|
var HostPluginRuntime = class {
|
|
18068
|
-
constructor(config, identities, apiProxy, logger,
|
|
18827
|
+
constructor(config, identities, apiProxy, logger, localGateway, fileViewerHost) {
|
|
18069
18828
|
this.config = config;
|
|
18070
18829
|
this.identities = identities;
|
|
18071
18830
|
this.apiProxy = apiProxy;
|
|
18072
18831
|
this.logger = logger;
|
|
18832
|
+
this.localGateway = localGateway;
|
|
18073
18833
|
this.fileViewerHost = fileViewerHost;
|
|
18074
18834
|
this.connections = new ConnectionController(this.identities, (_context, send) => {
|
|
18075
|
-
const harnessApi = new HarnessApiBridge(
|
|
18835
|
+
const harnessApi = this.apiProxy === void 0 ? void 0 : new HarnessApiBridge(
|
|
18076
18836
|
this.apiProxy,
|
|
18077
18837
|
(event, data) => send(createEvent(event, data)),
|
|
18078
18838
|
void 0,
|
|
18079
18839
|
this.logger,
|
|
18080
|
-
|
|
18840
|
+
this.localGateway,
|
|
18081
18841
|
this.harnessVersion
|
|
18082
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;
|
|
18083
18848
|
const fileViewer = new RemoteFileViewerBridge(
|
|
18084
18849
|
() => this.fileViewerHost?.(),
|
|
18085
18850
|
this.logger
|
|
18086
18851
|
);
|
|
18087
|
-
return new RpcRouter(harnessApi, void 0, this.logger, fileViewer);
|
|
18852
|
+
return new RpcRouter(harnessApi, void 0, this.logger, fileViewer, harnessRemote, () => this.hostCapabilities());
|
|
18088
18853
|
}, this.logger);
|
|
18089
18854
|
if (config.serverUrl !== void 0) {
|
|
18090
18855
|
this.serverApi = new HostServerApi(config.serverUrl, new ServerCredentialStore(identities.directory));
|
|
@@ -18225,7 +18990,7 @@ var HostPluginRuntime = class {
|
|
|
18225
18990
|
this.logger,
|
|
18226
18991
|
void 0,
|
|
18227
18992
|
this.config.forceRelay ? void 0 : () => loadNodeRtcFactory({ routeTargets: this.config.serverUrl === void 0 ? [] : [this.config.serverUrl] }),
|
|
18228
|
-
() => this.
|
|
18993
|
+
() => this.hostCapabilities(),
|
|
18229
18994
|
this.harnessVersion
|
|
18230
18995
|
);
|
|
18231
18996
|
}
|
|
@@ -18233,7 +18998,8 @@ var HostPluginRuntime = class {
|
|
|
18233
18998
|
let reportedVersion;
|
|
18234
18999
|
let errorCode2;
|
|
18235
19000
|
try {
|
|
18236
|
-
const response = await this.apiProxy
|
|
19001
|
+
const response = await this.apiProxy?.host.describe({ rpcId: randomUUID(), payload: {} });
|
|
19002
|
+
if (response === void 0) throw new Error("ApiProxy is unavailable");
|
|
18237
19003
|
if (!response.result.ok) {
|
|
18238
19004
|
errorCode2 = response.result.error.code;
|
|
18239
19005
|
} else {
|
|
@@ -18247,6 +19013,15 @@ var HostPluginRuntime = class {
|
|
|
18247
19013
|
this.logger.warn("Harness version is unavailable", errorCode2 === void 0 ? void 0 : { code: errorCode2 });
|
|
18248
19014
|
return void 0;
|
|
18249
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
|
+
}
|
|
18250
19025
|
};
|
|
18251
19026
|
function shortId5(value) {
|
|
18252
19027
|
return value.length <= 12 ? value : `${value.slice(0, 8)}\u2026${value.slice(-4)}`;
|
|
@@ -18254,6 +19029,7 @@ function shortId5(value) {
|
|
|
18254
19029
|
|
|
18255
19030
|
// src/remote-file-content-provider.ts
|
|
18256
19031
|
var REMOTE_FILE_SAVE_AS_MAX_BYTES = 100 * 1024 * 1024;
|
|
19032
|
+
var REMOTE_FILE_FAST_SAVE_AS_MAX_BYTES = 1024 * 1024 * 1024;
|
|
18257
19033
|
function createRemoteFileContentProvider(call, options = {}) {
|
|
18258
19034
|
return {
|
|
18259
19035
|
id: "dsh-remote-files",
|
|
@@ -18261,7 +19037,7 @@ function createRemoteFileContentProvider(call, options = {}) {
|
|
|
18261
19037
|
supports: () => true,
|
|
18262
19038
|
saveAsAllowed: () => ({
|
|
18263
19039
|
allowed: currentSaveAsAllowed(options.saveAsAllowed),
|
|
18264
|
-
maxBytes: options.saveAsMaxBytes
|
|
19040
|
+
maxBytes: currentSaveAsMaxBytes(options.saveAsMaxBytes)
|
|
18265
19041
|
}),
|
|
18266
19042
|
async stat(locator, signal) {
|
|
18267
19043
|
const value = await call("fileviewer.stat", { path: locator }, signal);
|
|
@@ -18314,6 +19090,9 @@ function createRemoteFileContentProvider(call, options = {}) {
|
|
|
18314
19090
|
function currentSaveAsAllowed(value) {
|
|
18315
19091
|
return typeof value === "function" ? value() : value === true;
|
|
18316
19092
|
}
|
|
19093
|
+
function currentSaveAsMaxBytes(value) {
|
|
19094
|
+
return typeof value === "function" ? value() : value ?? REMOTE_FILE_SAVE_AS_MAX_BYTES;
|
|
19095
|
+
}
|
|
18317
19096
|
function decodeBase64(value) {
|
|
18318
19097
|
const binary = atob(value);
|
|
18319
19098
|
const bytes = new Uint8Array(binary.length);
|
|
@@ -18325,7 +19104,13 @@ function decodeBase64(value) {
|
|
|
18325
19104
|
var name = "ds-harness-remote";
|
|
18326
19105
|
var legacyLoaderModuleNames = /* @__PURE__ */ new Set(["dsh-remote", "@dsh-remote/plugin"]);
|
|
18327
19106
|
function apply(ctx, input = {}) {
|
|
18328
|
-
ctx.inject(["settings", "
|
|
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
|
+
});
|
|
18329
19114
|
}
|
|
18330
19115
|
async function activate(ctx, input) {
|
|
18331
19116
|
const settings = ctx.get("settings");
|
|
@@ -18370,12 +19155,12 @@ async function activate(ctx, input) {
|
|
|
18370
19155
|
hostIdentities,
|
|
18371
19156
|
apiProxy,
|
|
18372
19157
|
logger,
|
|
18373
|
-
|
|
19158
|
+
localTypertGateway,
|
|
18374
19159
|
() => ctx.get("fileViewerHost")
|
|
18375
19160
|
);
|
|
18376
19161
|
let clientRuntime;
|
|
18377
19162
|
const hostControl = runtime;
|
|
18378
|
-
if (config.serverUrl !== void 0 &&
|
|
19163
|
+
if (config.serverUrl !== void 0 && connection !== void 0) {
|
|
18379
19164
|
const clientIdentities = new IdentityStore({
|
|
18380
19165
|
directory: serverStorageDirectory(defaultIdentityDirectory, config.serverUrl, "client")
|
|
18381
19166
|
});
|
|
@@ -18402,6 +19187,7 @@ async function activate(ctx, input) {
|
|
|
18402
19187
|
logger.warn("client remote mode is unavailable", {
|
|
18403
19188
|
serverConfigured: config.serverUrl !== void 0,
|
|
18404
19189
|
apiProxyAvailable: apiProxy !== void 0,
|
|
19190
|
+
remoteGatewayAvailable: localTypertGateway.supportsCarrier,
|
|
18405
19191
|
connectionAvailable: connection !== void 0
|
|
18406
19192
|
});
|
|
18407
19193
|
}
|
|
@@ -18451,8 +19237,10 @@ export {
|
|
|
18451
19237
|
ConnectionController,
|
|
18452
19238
|
ConnectionRejectedError,
|
|
18453
19239
|
HARNESS_API_ALLOWLIST,
|
|
19240
|
+
HARNESS_REMOTE_ALLOWLIST,
|
|
18454
19241
|
HOST_CAPABILITIES,
|
|
18455
19242
|
HarnessApiBridge,
|
|
19243
|
+
HarnessRemoteBridge,
|
|
18456
19244
|
HostPluginRuntime,
|
|
18457
19245
|
HostServerApi,
|
|
18458
19246
|
HostServerConnection,
|
|
@@ -18461,6 +19249,7 @@ export {
|
|
|
18461
19249
|
PluginControlRuntime,
|
|
18462
19250
|
RemoteFileViewerBridge,
|
|
18463
19251
|
RemoteHarnessApiProxy,
|
|
19252
|
+
RemoteTypertGateway,
|
|
18464
19253
|
RpcError,
|
|
18465
19254
|
RpcRouter,
|
|
18466
19255
|
ServerApiError,
|