ds-harness-remote 0.3.20 → 0.3.22

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.
@@ -0,0 +1,42 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { dirname, isAbsolute, join } from 'node:path';
3
+ const LEGACY_PLACEHOLDER_VERSION = '0.0.1';
4
+ export function normalizeHarnessVersion(value) {
5
+ if (typeof value !== 'string')
6
+ return undefined;
7
+ const version = value.trim();
8
+ if (version.length === 0 || version.length > 64 || /[\u0000-\u001f]/u.test(version))
9
+ return undefined;
10
+ return version;
11
+ }
12
+ export function selectHarnessVersion(reportedVersion, distributionVersion) {
13
+ if (reportedVersion !== undefined && reportedVersion !== LEGACY_PLACEHOLDER_VERSION)
14
+ return reportedVersion;
15
+ return distributionVersion;
16
+ }
17
+ /**
18
+ * Compatibility fallback for Harness builds whose host.describe still returns
19
+ * the historical 0.0.1 placeholder. The running CLI entrypoint sits below the
20
+ * @deepseek-ai/dsh package root, so walk only its ancestor chain.
21
+ */
22
+ export async function readHarnessDistributionVersion(entrypoint = process.argv[1]) {
23
+ if (entrypoint === undefined || !isAbsolute(entrypoint))
24
+ return undefined;
25
+ let directory = dirname(entrypoint);
26
+ for (let depth = 0; depth < 8; depth += 1) {
27
+ try {
28
+ const manifest = JSON.parse(await readFile(join(directory, 'package.json'), 'utf8'));
29
+ if (manifest.name === '@deepseek-ai/dsh')
30
+ return normalizeHarnessVersion(manifest.version);
31
+ }
32
+ catch {
33
+ // Most ancestors do not contain a package manifest.
34
+ }
35
+ const parent = dirname(directory);
36
+ if (parent === directory)
37
+ break;
38
+ directory = parent;
39
+ }
40
+ return undefined;
41
+ }
42
+ //# sourceMappingURL=harness-version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness-version.js","sourceRoot":"","sources":["../src/harness-version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAErD,MAAM,0BAA0B,GAAG,OAAO,CAAA;AAE1C,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACpD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAA;IAC/C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAA;IAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,EAAE,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAA;IACrG,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,eAAmC,EACnC,mBAAuC;IAEvC,IAAI,eAAe,KAAK,SAAS,IAAI,eAAe,KAAK,0BAA0B;QAAE,OAAO,eAAe,CAAA;IAC3G,OAAO,mBAAmB,CAAA;AAC5B,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,aAAiC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAEhD,IAAI,UAAU,KAAK,SAAS,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,SAAS,CAAA;IACzE,IAAI,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;IACnC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC1C,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAA4B,CAAA;YAC/G,IAAI,QAAQ,CAAC,IAAI,KAAK,kBAAkB;gBAAE,OAAO,uBAAuB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;QAC5F,CAAC;QAAC,MAAM,CAAC;YACP,oDAAoD;QACtD,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;QACjC,IAAI,MAAM,KAAK,SAAS;YAAE,MAAK;QAC/B,SAAS,GAAG,MAAM,CAAA;IACpB,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC"}
package/dist/index.js CHANGED
@@ -4111,7 +4111,8 @@ var helloPayloadSchema = external_exports.object({
4111
4111
  accessToken: external_exports.string().min(1),
4112
4112
  protocols: external_exports.array(external_exports.number().int()).min(1),
4113
4113
  capabilities: external_exports.array(external_exports.string()),
4114
- clientVersion: external_exports.string().optional()
4114
+ clientVersion: external_exports.string().optional(),
4115
+ harnessVersion: external_exports.string().optional()
4115
4116
  });
4116
4117
  var helloAckPayloadSchema = external_exports.object({
4117
4118
  protocol: external_exports.literal(PROTOCOL_VERSION),
@@ -4359,6 +4360,14 @@ function cryptoRandomId() {
4359
4360
  }
4360
4361
 
4361
4362
  // ../client-core/dist/index.js
4363
+ var RemoteClientError = class extends Error {
4364
+ code;
4365
+ constructor(code, message, options) {
4366
+ super(message, options);
4367
+ this.code = code;
4368
+ this.name = "RemoteClientError";
4369
+ }
4370
+ };
4362
4371
  var RemoteClientCore = class {
4363
4372
  transport;
4364
4373
  timeoutMs;
@@ -4367,6 +4376,7 @@ var RemoteClientCore = class {
4367
4376
  unsubscribeTransport;
4368
4377
  unsubscribeClose;
4369
4378
  closeHandlers = /* @__PURE__ */ new Set();
4379
+ closeNotified = false;
4370
4380
  constructor(transport, timeoutMs = 3e4) {
4371
4381
  this.transport = transport;
4372
4382
  this.timeoutMs = timeoutMs;
@@ -4374,6 +4384,7 @@ var RemoteClientCore = class {
4374
4384
  async connect() {
4375
4385
  if (this.unsubscribeTransport !== void 0)
4376
4386
  return;
4387
+ this.closeNotified = false;
4377
4388
  this.unsubscribeTransport = this.transport.onMessage((data) => this.handleMessage(data));
4378
4389
  this.unsubscribeClose = this.transport.onClose?.(() => this.handleTransportClose());
4379
4390
  try {
@@ -4387,23 +4398,24 @@ var RemoteClientCore = class {
4387
4398
  }
4388
4399
  }
4389
4400
  async rpc(method, params, signal) {
4390
- signal?.throwIfAborted();
4401
+ if (signal?.aborted)
4402
+ throw rpcAbortedError(method, signal.reason);
4391
4403
  const request = createRpcRequest(method, params);
4392
4404
  const result = new Promise((resolve2, reject) => {
4393
4405
  const timer = setTimeout(() => {
4394
- const pending2 = this.pending.get(request.id);
4395
- pending2?.removeAbort?.();
4396
- this.pending.delete(request.id);
4397
- reject(new Error(`RPC ${method} timed out`));
4406
+ this.rejectPending(request.id, new RemoteClientError("RPC_TIMEOUT", `RPC ${method} timed out after ${this.timeoutMs}ms`));
4398
4407
  }, this.timeoutMs);
4399
- const pending = { resolve: resolve2, reject, timer };
4408
+ const pending = {
4409
+ method,
4410
+ resolve: resolve2,
4411
+ reject,
4412
+ timer
4413
+ };
4400
4414
  if (signal !== void 0) {
4401
4415
  const onAbort = () => {
4402
4416
  if (this.pending.get(request.id) !== pending)
4403
4417
  return;
4404
- clearTimeout(timer);
4405
- this.pending.delete(request.id);
4406
- reject(signal.reason instanceof Error ? signal.reason : new Error("RPC was cancelled"));
4418
+ this.rejectPending(request.id, rpcAbortedError(method, signal.reason));
4407
4419
  };
4408
4420
  signal.addEventListener("abort", onAbort, { once: true });
4409
4421
  pending.removeAbort = () => signal.removeEventListener("abort", onAbort);
@@ -4411,15 +4423,12 @@ var RemoteClientCore = class {
4411
4423
  this.pending.set(request.id, pending);
4412
4424
  });
4413
4425
  try {
4414
- await this.transport.send(encodeMessage(request));
4426
+ const send = this.transport.send(encodeMessage(request));
4427
+ void send.catch((error) => {
4428
+ this.rejectPending(request.id, transportSendError(error));
4429
+ });
4415
4430
  } catch (error) {
4416
- const pending = this.pending.get(request.id);
4417
- if (pending !== void 0) {
4418
- clearTimeout(pending.timer);
4419
- pending.removeAbort?.();
4420
- }
4421
- this.pending.delete(request.id);
4422
- throw error;
4431
+ this.rejectPending(request.id, transportSendError(error));
4423
4432
  }
4424
4433
  return result;
4425
4434
  }
@@ -4439,23 +4448,13 @@ var RemoteClientCore = class {
4439
4448
  this.unsubscribeTransport = void 0;
4440
4449
  this.unsubscribeClose?.();
4441
4450
  this.unsubscribeClose = void 0;
4451
+ this.rejectAllPending((pending) => new RemoteClientError("CLIENT_CLOSED", `RPC ${pending.method} terminated because the remote client closed`));
4452
+ this.notifyClose();
4442
4453
  await this.transport.close();
4443
- for (const pending of this.pending.values()) {
4444
- clearTimeout(pending.timer);
4445
- pending.removeAbort?.();
4446
- pending.reject(new Error("remote client closed"));
4447
- }
4448
- this.pending.clear();
4449
4454
  }
4450
4455
  handleTransportClose() {
4451
- for (const pending of this.pending.values()) {
4452
- clearTimeout(pending.timer);
4453
- pending.removeAbort?.();
4454
- pending.reject(new Error("remote transport closed"));
4455
- }
4456
- this.pending.clear();
4457
- for (const handler of this.closeHandlers)
4458
- handler();
4456
+ this.rejectAllPending((pending) => new RemoteClientError("TRANSPORT_CLOSED", `RPC ${pending.method} terminated because the remote transport closed`));
4457
+ this.notifyClose();
4459
4458
  }
4460
4459
  handleMessage(data) {
4461
4460
  const message = decodeMessage(data);
@@ -4470,24 +4469,54 @@ var RemoteClientCore = class {
4470
4469
  }
4471
4470
  }
4472
4471
  handleResponse(message) {
4473
- const pending = this.pending.get(message.payload.requestId);
4472
+ const pending = this.takePending(message.payload.requestId);
4474
4473
  if (pending === void 0)
4475
4474
  return;
4476
- this.pending.delete(message.payload.requestId);
4477
- clearTimeout(pending.timer);
4478
- pending.removeAbort?.();
4479
4475
  pending.resolve(message.payload.result);
4480
4476
  }
4481
4477
  handleError(message) {
4482
- const pending = this.pending.get(message.payload.requestId);
4478
+ const pending = this.takePending(message.payload.requestId);
4483
4479
  if (pending === void 0)
4484
4480
  return;
4485
- this.pending.delete(message.payload.requestId);
4481
+ pending.reject(Object.assign(new Error(message.payload.message), { code: message.payload.code }));
4482
+ }
4483
+ takePending(requestId) {
4484
+ const pending = this.pending.get(requestId);
4485
+ if (pending === void 0)
4486
+ return void 0;
4487
+ this.pending.delete(requestId);
4486
4488
  clearTimeout(pending.timer);
4487
4489
  pending.removeAbort?.();
4488
- pending.reject(Object.assign(new Error(message.payload.message), { code: message.payload.code }));
4490
+ return pending;
4491
+ }
4492
+ rejectPending(requestId, error) {
4493
+ const pending = this.takePending(requestId);
4494
+ if (pending === void 0)
4495
+ return false;
4496
+ pending.reject(error);
4497
+ return true;
4498
+ }
4499
+ rejectAllPending(createError) {
4500
+ for (const requestId of [...this.pending.keys()]) {
4501
+ const pending = this.takePending(requestId);
4502
+ if (pending !== void 0)
4503
+ pending.reject(createError(pending));
4504
+ }
4505
+ }
4506
+ notifyClose() {
4507
+ if (this.closeNotified)
4508
+ return;
4509
+ this.closeNotified = true;
4510
+ for (const handler of this.closeHandlers)
4511
+ handler();
4489
4512
  }
4490
4513
  };
4514
+ function rpcAbortedError(method, reason) {
4515
+ return new RemoteClientError("RPC_ABORTED", `RPC ${method} was aborted`, reason === void 0 ? void 0 : { cause: reason });
4516
+ }
4517
+ function transportSendError(error) {
4518
+ return error instanceof Error ? error : new Error("remote transport send failed", { cause: error });
4519
+ }
4491
4520
 
4492
4521
  // ../webrtc/dist/transport.js
4493
4522
  var BaseTransport = class {
@@ -12966,7 +12995,7 @@ function normalizeLegacyResponse(method, response) {
12966
12995
  };
12967
12996
  }
12968
12997
  function isRemoteDisconnect(error) {
12969
- return error instanceof Error && (error.message === "remote transport closed" || error.message === "remote client closed");
12998
+ return error instanceof RemoteClientError && (error.code === "TRANSPORT_CLOSED" || error.code === "CLIENT_CLOSED");
12970
12999
  }
12971
13000
  var AsyncFrameQueue = class {
12972
13001
  values = [];
@@ -13092,7 +13121,7 @@ function normalizeServerUrl(value) {
13092
13121
  }
13093
13122
 
13094
13123
  // src/version.ts
13095
- var PLUGIN_VERSION = "0.3.20";
13124
+ var PLUGIN_VERSION = "0.3.22";
13096
13125
 
13097
13126
  // src/server-api.ts
13098
13127
  var HostServerApi = class {
@@ -13155,12 +13184,12 @@ var HostServerApi = class {
13155
13184
  isAdmin: login.isAdmin
13156
13185
  };
13157
13186
  }
13158
- async startOAuthQrLogin() {
13159
- const value = requireRecord(await this.publicRequest("/api/v1/auth/oauth/qr/start", {
13187
+ async startOAuthQrLogin(provider = "zhihu") {
13188
+ const value = requireRecord(await this.publicRequest(`/api/v1/auth/oauth/qr/start?provider=${provider}`, {
13160
13189
  method: "POST",
13161
13190
  body: "{}"
13162
13191
  }), "QR login");
13163
- if (typeof value.qrId !== "string" || value.qrId.length < 20 || typeof value.scanUrl !== "string" || !value.scanUrl.startsWith(`${this.baseUrl}/`) || !Number.isSafeInteger(value.expiresIn)) {
13192
+ if (typeof value.qrId !== "string" || value.qrId.length < 20 || typeof value.scanUrl !== "string" || !value.scanUrl.startsWith(`${this.baseUrl}/`) || !Number.isSafeInteger(value.expiresIn) || provider === "github" && value.provider !== "github" || value.provider !== void 0 && value.provider !== provider) {
13164
13193
  throw new ServerApiError("INVALID_MESSAGE", "The Server returned an invalid QR login session.", false);
13165
13194
  }
13166
13195
  return { qrId: value.qrId, scanUrl: value.scanUrl, expiresIn: value.expiresIn };
@@ -13315,8 +13344,7 @@ var HostServerApi = class {
13315
13344
  role: this.role,
13316
13345
  platform: platform(),
13317
13346
  identityKey: identity.publicKey,
13318
- clientVersion: PLUGIN_VERSION,
13319
- ...this.role === "host" ? { harnessVersion: "0.1.0-rc.6" } : {}
13347
+ clientVersion: PLUGIN_VERSION
13320
13348
  };
13321
13349
  }
13322
13350
  saveTokens(identity, tokens, authorization) {
@@ -13735,6 +13763,7 @@ var ClientModeRuntime = class {
13735
13763
  }
13736
13764
  identity;
13737
13765
  connected;
13766
+ pendingWorkspaceSelection;
13738
13767
  proxySwitch;
13739
13768
  gatewaySwitch;
13740
13769
  closed = false;
@@ -13764,6 +13793,7 @@ var ClientModeRuntime = class {
13764
13793
  connected: this.connected !== void 0,
13765
13794
  transport: this.connected?.client.getStats().mode ?? "Disconnected",
13766
13795
  remoteFeatures: this.connected?.features ?? remoteHostFeatures(),
13796
+ ...this.pendingWorkspaceSelection === void 0 ? {} : { workspaceSelection: { ...this.pendingWorkspaceSelection } },
13767
13797
  hostAuthorizationAvailable: this.host !== void 0,
13768
13798
  ...this.host === void 0 ? {} : { host: this.host.hostStatus() }
13769
13799
  };
@@ -13813,8 +13843,8 @@ var ClientModeRuntime = class {
13813
13843
  this.logger.info("Client account authorized");
13814
13844
  return authorization;
13815
13845
  }
13816
- async startClientOAuthQrLogin() {
13817
- return this.server.startOAuthQrLogin();
13846
+ async startClientOAuthQrLogin(provider) {
13847
+ return this.server.startOAuthQrLogin(provider);
13818
13848
  }
13819
13849
  async pollClientOAuthQrLogin(qrId) {
13820
13850
  try {
@@ -13832,6 +13862,7 @@ var ClientModeRuntime = class {
13832
13862
  async clearClientAuthorization() {
13833
13863
  const previous = this.connected;
13834
13864
  this.connected = void 0;
13865
+ this.pendingWorkspaceSelection = void 0;
13835
13866
  this.proxySwitch.selectLocal();
13836
13867
  this.gatewaySwitch.selectLocal();
13837
13868
  await previous?.client.close().catch(() => void 0);
@@ -13855,6 +13886,7 @@ var ClientModeRuntime = class {
13855
13886
  this.gatewaySwitch.selectLocal();
13856
13887
  const previous2 = this.connected;
13857
13888
  this.connected = void 0;
13889
+ this.pendingWorkspaceSelection = void 0;
13858
13890
  await previous2?.client.close().catch(() => void 0);
13859
13891
  this.logger.info("Harness target switched", { mode: "local" });
13860
13892
  return this.status();
@@ -13865,6 +13897,7 @@ var ClientModeRuntime = class {
13865
13897
  const next = await this.connect(targetDeviceId, signal);
13866
13898
  const previous = this.connected;
13867
13899
  this.connected = next;
13900
+ this.pendingWorkspaceSelection = void 0;
13868
13901
  const remoteApi = new RemoteHarnessApiProxy(next.client).api;
13869
13902
  this.proxySwitch.selectRemote(remoteApi, { deviceId: next.target.deviceId, name: next.target.name });
13870
13903
  this.selectRemoteCommands(next);
@@ -13902,14 +13935,24 @@ var ClientModeRuntime = class {
13902
13935
  const workspace = unwrapNativeResult(response);
13903
13936
  this.proxySwitch.selectRemote(api, { deviceId: remote.target.deviceId, name: remote.target.name });
13904
13937
  this.selectRemoteCommands(remote);
13938
+ const workspaceId = workspaceRecordId(workspace.workspace);
13939
+ this.pendingWorkspaceSelection = { targetDeviceId: remote.target.deviceId, workspaceId };
13905
13940
  this.logger.info("Remote workspace opened", { targetDeviceId: shortId(remote.target.deviceId) });
13906
13941
  return { ...this.status(), workspace };
13907
13942
  }
13943
+ consumeWorkspaceSelection(selection) {
13944
+ const pending = this.pendingWorkspaceSelection;
13945
+ if (pending?.targetDeviceId === selection.targetDeviceId && pending.workspaceId === selection.workspaceId) {
13946
+ this.pendingWorkspaceSelection = void 0;
13947
+ }
13948
+ return this.status();
13949
+ }
13908
13950
  async close() {
13909
13951
  if (this.closed) return;
13910
13952
  this.closed = true;
13911
13953
  this.proxySwitch.selectLocal();
13912
13954
  this.gatewaySwitch.selectLocal();
13955
+ this.pendingWorkspaceSelection = void 0;
13913
13956
  await this.connected?.client.close().catch(() => void 0);
13914
13957
  this.connected = void 0;
13915
13958
  this.proxySwitch.restore();
@@ -13982,6 +14025,7 @@ var ClientModeRuntime = class {
13982
14025
  client.onClose(() => {
13983
14026
  if (this.connected?.client !== client) return;
13984
14027
  this.connected = void 0;
14028
+ this.pendingWorkspaceSelection = void 0;
13985
14029
  this.proxySwitch.selectLocal();
13986
14030
  this.gatewaySwitch.selectLocal();
13987
14031
  void client.close().catch(() => void 0);
@@ -14018,7 +14062,14 @@ var ClientModeRuntime = class {
14018
14062
  }
14019
14063
  return ok(await this.authorizeClientWithAccount(value.email, value.password));
14020
14064
  }
14021
- if (endpoint === "client.account.qr.start") return ok(await this.startClientOAuthQrLogin());
14065
+ if (endpoint === "client.account.qr.start") {
14066
+ const value = record(payload);
14067
+ const provider = value.provider ?? "zhihu";
14068
+ if (provider !== "zhihu" && provider !== "github") {
14069
+ throw new ClientModeError("INVALID_MESSAGE", "A supported OAuth provider is required.");
14070
+ }
14071
+ return ok(await this.startClientOAuthQrLogin(provider));
14072
+ }
14022
14073
  if (endpoint === "client.account.qr.poll") {
14023
14074
  const value = record(payload);
14024
14075
  if (typeof value.qrId !== "string" || value.qrId.length < 20) {
@@ -14047,6 +14098,16 @@ var ClientModeRuntime = class {
14047
14098
  }
14048
14099
  return ok(await this.openRemoteWorkspace(value.targetDeviceId, value.path, signal));
14049
14100
  }
14101
+ if (endpoint === "workspace.selection.consume") {
14102
+ const value = record(payload);
14103
+ if (typeof value.targetDeviceId !== "string" || typeof value.workspaceId !== "string") {
14104
+ throw new ClientModeError("INVALID_MESSAGE", "A Host and Workspace are required.");
14105
+ }
14106
+ return ok(this.consumeWorkspaceSelection({
14107
+ targetDeviceId: value.targetDeviceId,
14108
+ workspaceId: value.workspaceId
14109
+ }));
14110
+ }
14050
14111
  if (endpoint === "fileviewer.stat" || endpoint === "fileviewer.readRange" || endpoint === "fileviewer.list") {
14051
14112
  const method = endpoint === "fileviewer.stat" ? "stat" : endpoint === "fileviewer.readRange" ? "readRange" : "list";
14052
14113
  return ok(await this.callRemoteFileViewer(method, payload, signal));
@@ -14157,6 +14218,12 @@ function unwrapNativeResult(response) {
14157
14218
  }
14158
14219
  return result.value;
14159
14220
  }
14221
+ function workspaceRecordId(value) {
14222
+ if (typeof value !== "object" || value === null || !("workspaceId" in value) || typeof value.workspaceId !== "string" || value.workspaceId.length === 0) {
14223
+ throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned an invalid Workspace.");
14224
+ }
14225
+ return value.workspaceId;
14226
+ }
14160
14227
  function fail(error) {
14161
14228
  const source = error instanceof Error ? error : void 0;
14162
14229
  const remoteCode = source !== void 0 && "code" in source && typeof source.code === "string" ? source.code : source instanceof ClientModeError ? source.code : void 0;
@@ -14704,6 +14771,10 @@ function redact(value, key = "") {
14704
14771
  return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [childKey, redact(child, childKey)]));
14705
14772
  }
14706
14773
 
14774
+ // src/service.ts
14775
+ import { randomUUID } from "node:crypto";
14776
+ import { RpcId } from "@deepseek-ai/dsh-host-apiproxy/api";
14777
+
14707
14778
  // src/connection-controller.ts
14708
14779
  var ConnectionController = class {
14709
14780
  constructor(identities, createRouter, logger) {
@@ -14872,6 +14943,36 @@ function shortId2(value) {
14872
14943
  return value.length <= 12 ? value : `${value.slice(0, 8)}\u2026${value.slice(-4)}`;
14873
14944
  }
14874
14945
 
14946
+ // src/harness-version.ts
14947
+ import { readFile as readFile3 } from "node:fs/promises";
14948
+ import { dirname as dirname3, isAbsolute, join as join3 } from "node:path";
14949
+ var LEGACY_PLACEHOLDER_VERSION = "0.0.1";
14950
+ function normalizeHarnessVersion(value) {
14951
+ if (typeof value !== "string") return void 0;
14952
+ const version = value.trim();
14953
+ if (version.length === 0 || version.length > 64 || /[\u0000-\u001f]/u.test(version)) return void 0;
14954
+ return version;
14955
+ }
14956
+ function selectHarnessVersion(reportedVersion, distributionVersion) {
14957
+ if (reportedVersion !== void 0 && reportedVersion !== LEGACY_PLACEHOLDER_VERSION) return reportedVersion;
14958
+ return distributionVersion;
14959
+ }
14960
+ async function readHarnessDistributionVersion(entrypoint = process.argv[1]) {
14961
+ if (entrypoint === void 0 || !isAbsolute(entrypoint)) return void 0;
14962
+ let directory = dirname3(entrypoint);
14963
+ for (let depth = 0; depth < 8; depth += 1) {
14964
+ try {
14965
+ const manifest = JSON.parse(await readFile3(join3(directory, "package.json"), "utf8"));
14966
+ if (manifest.name === "@deepseek-ai/dsh") return normalizeHarnessVersion(manifest.version);
14967
+ } catch {
14968
+ }
14969
+ const parent = dirname3(directory);
14970
+ if (parent === directory) break;
14971
+ directory = parent;
14972
+ }
14973
+ return void 0;
14974
+ }
14975
+
14875
14976
  // src/rpc-router.ts
14876
14977
  var wireRequestSchema = external_exports.object({ method: external_exports.string().min(1), params: external_exports.unknown() }).strict();
14877
14978
  var apiMethods = /* @__PURE__ */ new Set([
@@ -15091,7 +15192,7 @@ function isOversizedListing(value) {
15091
15192
  // src/server-connection.ts
15092
15193
  var DEFAULT_WEBRTC_NEGOTIATE_TIMEOUT_MS = 8e3;
15093
15194
  var HostServerConnection = class {
15094
- constructor(config, identity, identities, api, connections, logger, createWebSocket = (url) => new WebSocket(url), rtcFactoryProvider, hostCapabilities = () => ["harness.api.v1"]) {
15195
+ constructor(config, identity, identities, api, connections, logger, createWebSocket = (url) => new WebSocket(url), rtcFactoryProvider, hostCapabilities = () => ["harness.api.v1"], harnessVersion) {
15095
15196
  this.config = config;
15096
15197
  this.identity = identity;
15097
15198
  this.identities = identities;
@@ -15101,6 +15202,7 @@ var HostServerConnection = class {
15101
15202
  this.createWebSocket = createWebSocket;
15102
15203
  this.rtcFactoryProvider = rtcFactoryProvider;
15103
15204
  this.hostCapabilities = hostCapabilities;
15205
+ this.harnessVersion = harnessVersion;
15104
15206
  }
15105
15207
  socket;
15106
15208
  running;
@@ -15224,6 +15326,7 @@ var HostServerConnection = class {
15224
15326
  accessToken: credentials.accessToken,
15225
15327
  protocols: [PROTOCOL_VERSION],
15226
15328
  clientVersion: PLUGIN_VERSION,
15329
+ ...this.harnessVersion === void 0 ? {} : { harnessVersion: this.harnessVersion },
15227
15330
  capabilities: this.rtcFactoryProvider === void 0 || this.config.forceRelay ? ["transport.relay", ...this.hostCapabilities()] : ["transport.p2p", "transport.turn", "transport.relay", ...this.hostCapabilities()]
15228
15331
  });
15229
15332
  };
@@ -15776,13 +15879,13 @@ function closeCode(code) {
15776
15879
  // src/remote-directory-browser.ts
15777
15880
  import { readdir, stat as stat3 } from "node:fs/promises";
15778
15881
  import { homedir as homedir2, platform as platform2 } from "node:os";
15779
- import { basename, dirname as dirname3, isAbsolute, parse, resolve } from "node:path";
15882
+ import { basename, dirname as dirname4, isAbsolute as isAbsolute2, parse, resolve } from "node:path";
15780
15883
  var MAX_ENTRIES = 500;
15781
15884
  async function listRemoteDirectory(path, signal) {
15782
15885
  signal?.throwIfAborted();
15783
15886
  const home = resolve(homedir2());
15784
15887
  const target = path === void 0 || path.trim() === "" ? home : resolve(path);
15785
- if (!isAbsolute(target)) throw new Error("The remote directory path must be absolute.");
15888
+ if (!isAbsolute2(target)) throw new Error("The remote directory path must be absolute.");
15786
15889
  const rows = await readdir(target, { withFileTypes: true });
15787
15890
  const directories = [];
15788
15891
  for (const row of rows) {
@@ -15809,7 +15912,7 @@ function crumbs(path) {
15809
15912
  let current = path;
15810
15913
  while (current !== root) {
15811
15914
  segments.unshift(basename(current));
15812
- current = dirname3(current);
15915
+ current = dirname4(current);
15813
15916
  }
15814
15917
  for (const segment of segments) {
15815
15918
  current = resolve(current, segment);
@@ -16173,11 +16276,12 @@ var HostPluginRuntime = class {
16173
16276
  constructor(config, identities, apiProxy, logger, typertGateway, fileViewerHost) {
16174
16277
  this.config = config;
16175
16278
  this.identities = identities;
16279
+ this.apiProxy = apiProxy;
16176
16280
  this.logger = logger;
16177
16281
  this.fileViewerHost = fileViewerHost;
16178
16282
  this.connections = new ConnectionController(this.identities, (_context, send) => {
16179
16283
  const harnessApi = new HarnessApiBridge(
16180
- apiProxy,
16284
+ this.apiProxy,
16181
16285
  (event, data) => send(createEvent(event, data)),
16182
16286
  void 0,
16183
16287
  this.logger,
@@ -16197,6 +16301,7 @@ var HostPluginRuntime = class {
16197
16301
  identity;
16198
16302
  serverApi;
16199
16303
  serverConnection;
16304
+ harnessVersion;
16200
16305
  closed = false;
16201
16306
  async start() {
16202
16307
  if (this.closed) throw new Error("remote runtime is closed");
@@ -16207,6 +16312,7 @@ var HostPluginRuntime = class {
16207
16312
  server: this.config.serverUrl ?? "not configured"
16208
16313
  });
16209
16314
  if (this.serverApi !== void 0) {
16315
+ this.harnessVersion = await this.readHarnessVersion();
16210
16316
  this.serverApi.bindIdentity(this.identity);
16211
16317
  this.serverConnection = this.createServerConnection(this.identity);
16212
16318
  this.serverConnection.start();
@@ -16325,9 +16431,28 @@ var HostPluginRuntime = class {
16325
16431
  this.logger,
16326
16432
  void 0,
16327
16433
  this.config.forceRelay ? void 0 : loadWeriftFactory,
16328
- () => this.fileViewerHost?.() === void 0 ? ["harness.api.v1"] : ["harness.api.v1", "fileviewer.read.v1"]
16434
+ () => this.fileViewerHost?.() === void 0 ? ["harness.api.v1"] : ["harness.api.v1", "fileviewer.read.v1"],
16435
+ this.harnessVersion
16329
16436
  );
16330
16437
  }
16438
+ async readHarnessVersion() {
16439
+ let reportedVersion;
16440
+ let errorCode2;
16441
+ try {
16442
+ const response = await this.apiProxy.host.describe({ rpcId: RpcId(randomUUID()), payload: {} });
16443
+ if (!response.result.ok) {
16444
+ errorCode2 = response.result.error.code;
16445
+ } else {
16446
+ reportedVersion = normalizeHarnessVersion(response.result.value.version);
16447
+ }
16448
+ } catch {
16449
+ }
16450
+ const distributionVersion = reportedVersion === void 0 || reportedVersion === "0.0.1" ? await readHarnessDistributionVersion() : void 0;
16451
+ const version = selectHarnessVersion(reportedVersion, distributionVersion);
16452
+ if (version !== void 0) return version;
16453
+ this.logger.warn("Harness version is unavailable", errorCode2 === void 0 ? void 0 : { code: errorCode2 });
16454
+ return void 0;
16455
+ }
16331
16456
  };
16332
16457
  function shortId5(value) {
16333
16458
  return value.length <= 12 ? value : `${value.slice(0, 8)}\u2026${value.slice(-4)}`;