ds-harness-remote 0.5.13 → 0.5.14

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/dist/index.js CHANGED
@@ -14309,11 +14309,11 @@ var AdaptiveTransport = class extends BaseTransport {
14309
14309
  };
14310
14310
  socket.onerror = () => {
14311
14311
  if (this.lifetime === lifetime)
14312
- this.failConnection(new Error(`AdaptiveTransport failed to connect to ${this.url}`));
14312
+ this.failConnection(new Error(`AdaptiveTransport failed to connect to ${this.url}`), { reason: "control-error" });
14313
14313
  };
14314
14314
  socket.onclose = (event) => {
14315
14315
  if (this.lifetime === lifetime)
14316
- this.failConnection(adaptiveControlCloseError(event));
14316
+ this.failConnection(adaptiveControlCloseError(event), { reason: "control-closed", closeCode: event.code });
14317
14317
  };
14318
14318
  });
14319
14319
  }
@@ -14483,7 +14483,7 @@ var AdaptiveTransport = class extends BaseTransport {
14483
14483
  if (frame.type === "ping")
14484
14484
  this.sendControl("pong", frame.payload);
14485
14485
  } catch (error) {
14486
- this.failConnection(error instanceof Error ? error : new Error("Invalid adaptive control frame"));
14486
+ this.failConnection(error instanceof Error ? error : new Error("Invalid adaptive control frame"), { reason: "invalid-control-frame" });
14487
14487
  }
14488
14488
  }
14489
14489
  handleRelay(payload) {
@@ -14596,8 +14596,9 @@ var AdaptiveTransport = class extends BaseTransport {
14596
14596
  this.emit(data2);
14597
14597
  });
14598
14598
  rtc.onClose(() => {
14599
- if (this.rtc === rtc && this.dataMode === "webrtc")
14600
- this.emitClose();
14599
+ if (this.rtc === rtc && this.dataMode === "webrtc") {
14600
+ this.failConnection(new Error("Active WebRTC channel closed"), { reason: "webrtc-closed" });
14601
+ }
14601
14602
  });
14602
14603
  try {
14603
14604
  await rtc.connect(signal);
@@ -14688,7 +14689,8 @@ var AdaptiveTransport = class extends BaseTransport {
14688
14689
  this.readyResolve = void 0;
14689
14690
  this.readyReject = void 0;
14690
14691
  }
14691
- failConnection(error) {
14692
+ failConnection(error, diagnostic) {
14693
+ const established = this.connectedAt !== void 0;
14692
14694
  this.clearHandshake();
14693
14695
  const reject = this.readyReject;
14694
14696
  this.readyResolve = void 0;
@@ -14707,6 +14709,12 @@ var AdaptiveTransport = class extends BaseTransport {
14707
14709
  reject?.(error);
14708
14710
  void rtc?.close();
14709
14711
  socket?.close();
14712
+ if (established && diagnostic !== void 0) {
14713
+ try {
14714
+ this.options.onDisconnect?.(diagnostic);
14715
+ } catch {
14716
+ }
14717
+ }
14710
14718
  if (socket !== void 0)
14711
14719
  this.emitClose();
14712
14720
  }
@@ -22260,16 +22268,24 @@ var ClientConnection = class {
22260
22268
  this.context.connectionProgress = { runId: progressRunId, targetDeviceId, phase: "checking-host" };
22261
22269
  const identity2 = this.context.requireIdentity();
22262
22270
  let client;
22271
+ const onAbort = () => {
22272
+ void client?.close().catch(() => void 0);
22273
+ };
22274
+ signal?.addEventListener("abort", onAbort, { once: true });
22263
22275
  try {
22264
22276
  const serverDevice = (await this.context.server.listDevices()).find((device) => device.deviceId === targetDeviceId);
22277
+ signal?.throwIfAborted();
22265
22278
  if (serverDevice === void 0) {
22266
22279
  throw new ClientModeError("MEMBERSHIP_REQUIRED", "The selected Host is not authorized for this account.");
22267
22280
  }
22268
22281
  this.updateConnectionProgress(progressRunId, "authorizing-peer");
22269
22282
  const target2 = await this.authorizeHostPeer(serverDevice);
22283
+ signal?.throwIfAborted();
22270
22284
  const presence = await this.context.server.presenceFor(targetDeviceId);
22285
+ signal?.throwIfAborted();
22271
22286
  if (!presence.online) throw new ClientModeError("HOST_OFFLINE", "The selected Host is offline.", true);
22272
22287
  const credentials = await this.context.server.authenticate(identity2);
22288
+ signal?.throwIfAborted();
22273
22289
  const rtcFactory = this.context.config.forceRelay ? void 0 : await this.context.rtcFactoryProvider({ routeTargets: [this.context.server.baseUrl] }).catch(() => void 0);
22274
22290
  if (!this.context.config.forceRelay && rtcFactory === void 0) {
22275
22291
  this.context.logger.warn("remote Harness WebRTC backend unavailable; using relay", {
@@ -22315,16 +22331,8 @@ var ClientConnection = class {
22315
22331
  transport = createTransport(attempt);
22316
22332
  client = new RemoteClientCore(new ClientSecureTransport(transport, identity2, target2), 6e4);
22317
22333
  const attemptClient = client;
22318
- const onAbort = () => {
22319
- void attemptClient.close();
22320
- };
22321
- signal?.addEventListener("abort", onAbort, { once: true });
22322
- try {
22323
- signal?.throwIfAborted();
22324
- await attemptClient.connect();
22325
- } finally {
22326
- signal?.removeEventListener("abort", onAbort);
22327
- }
22334
+ signal?.throwIfAborted();
22335
+ await attemptClient.connect();
22328
22336
  signal?.throwIfAborted();
22329
22337
  } finally {
22330
22338
  stopProgressTimer();
@@ -22382,6 +22390,7 @@ var ClientConnection = class {
22382
22390
  });
22383
22391
  }
22384
22392
  const features = await probeRemoteHostFeatures(connectedClient, serverDevice.clientVersion);
22393
+ signal?.throwIfAborted();
22385
22394
  return {
22386
22395
  client: connectedClient,
22387
22396
  target: target2,
@@ -22395,6 +22404,8 @@ var ClientConnection = class {
22395
22404
  this.clearConnectionProgress(progressRunId);
22396
22405
  await client?.close().catch(() => void 0);
22397
22406
  throw error;
22407
+ } finally {
22408
+ signal?.removeEventListener("abort", onAbort);
22398
22409
  }
22399
22410
  }
22400
22411
  beginAttemptProgress(runId, attempt) {
@@ -27404,7 +27415,7 @@ function normalizeServerUrl(value) {
27404
27415
  }
27405
27416
 
27406
27417
  // src/version.ts
27407
- var PLUGIN_VERSION = "0.5.13";
27418
+ var PLUGIN_VERSION = "0.5.14";
27408
27419
 
27409
27420
  // src/server-api.ts
27410
27421
  var TERMINAL_CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/u;
@@ -29530,8 +29541,16 @@ var ClientModeRuntime = class {
29530
29541
  this.proxySwitch = apiProxy === void 0 ? void 0 : new ApiProxySwitch(apiProxy);
29531
29542
  this.gatewaySwitch = new TypertGatewaySwitch(typertGateway);
29532
29543
  }
29533
- connect(targetDeviceId, signal) {
29534
- return this.connection.connect(targetDeviceId, signal);
29544
+ async connect(targetDeviceId, signal) {
29545
+ const lifetime = this.remoteLifetime.signal;
29546
+ const connectionSignal = signal === void 0 ? lifetime : AbortSignal.any([lifetime, signal]);
29547
+ connectionSignal.throwIfAborted();
29548
+ const remote = await this.connection.connect(targetDeviceId, connectionSignal);
29549
+ if (connectionSignal.aborted || this.closed) {
29550
+ await remote.client.close().catch(() => void 0);
29551
+ throw new ClientModeError("CANCELLED", "The Remote connection request was superseded or closed.");
29552
+ }
29553
+ return remote;
29535
29554
  }
29536
29555
  handleControl(endpoint, payload, signal) {
29537
29556
  return this.control.handleControl(endpoint, payload, signal);
@@ -29555,6 +29574,7 @@ var ClientModeRuntime = class {
29555
29574
  connectionProgress;
29556
29575
  connectionProgressRun = 0;
29557
29576
  closed = false;
29577
+ remoteLifetime = new AbortController();
29558
29578
  async start() {
29559
29579
  if (this.closed) throw new Error("client remote-mode runtime is closed");
29560
29580
  this.identity = await this.identities.loadOrCreate(this.config.deviceName);
@@ -29698,21 +29718,13 @@ var ClientModeRuntime = class {
29698
29718
  return result;
29699
29719
  }
29700
29720
  async clearClientAuthorization() {
29701
- const previous = this.connected;
29702
- this.connected = void 0;
29703
- this.connectionProgress = void 0;
29704
- this.pendingWorkspaceSelection = void 0;
29705
- await this.closeCodexVirtual();
29721
+ const cleanup = this.leaveRemote();
29706
29722
  this.codexOpenedSessions.clear();
29707
- await this.closeCursorVirtual();
29708
- this.proxySwitch?.selectLocal();
29709
- await this.closePreview();
29710
- this.gatewaySwitch.selectLocal();
29711
- await this.codexStreamBridge.closeCodexStreams(previous?.client);
29712
- await previous?.client.close().catch(() => void 0);
29713
- await this.server.revokeCurrentDevice();
29723
+ const results = await Promise.allSettled([cleanup, this.server.revokeCurrentDevice()]);
29714
29724
  this.identity = await this.identities.reset(this.config.deviceName);
29715
29725
  this.server.bindIdentity(this.identity);
29726
+ const failure4 = results.find((result) => result.status === "rejected");
29727
+ if (failure4?.status === "rejected") throw failure4.reason;
29716
29728
  }
29717
29729
  async setHostAuthorization(enabled) {
29718
29730
  if (this.host === void 0) throw new ClientModeError("METHOD_NOT_ALLOWED", "This plugin is not running as a Host.");
@@ -29726,23 +29738,14 @@ var ClientModeRuntime = class {
29726
29738
  }
29727
29739
  async setMode(mode2, targetDeviceId, signal) {
29728
29740
  if (mode2 === "local") {
29729
- await this.closeCodexVirtual();
29730
- await this.closeCursorVirtual();
29731
- this.proxySwitch?.selectLocal();
29732
- await this.closePreview();
29733
- this.gatewaySwitch.selectLocal();
29734
- const previous2 = this.connected;
29735
- this.connected = void 0;
29736
- this.connectionProgress = void 0;
29737
- this.pendingWorkspaceSelection = void 0;
29738
- await this.codexStreamBridge.closeCodexStreams(previous2?.client);
29739
- await previous2?.client.close().catch(() => void 0);
29741
+ await this.leaveRemote();
29740
29742
  this.logger.info("Harness target switched", { mode: "local" });
29741
29743
  return this.status();
29742
29744
  }
29743
29745
  if (targetDeviceId === void 0 || targetDeviceId.length === 0) {
29744
29746
  throw new ClientModeError("INVALID_MESSAGE", "A targetDeviceId is required for remote mode.");
29745
29747
  }
29748
+ const lifetime = this.remoteLifetime.signal;
29746
29749
  const next = await this.connect(targetDeviceId, signal);
29747
29750
  try {
29748
29751
  this.assertRemoteCompatible(next);
@@ -29753,11 +29756,17 @@ var ClientModeRuntime = class {
29753
29756
  }
29754
29757
  const previous = this.connected;
29755
29758
  await this.closePreview();
29759
+ if (lifetime.aborted || this.closed) {
29760
+ await next.client.close().catch(() => void 0);
29761
+ throw new ClientModeError("CANCELLED", "The Remote connection request was superseded or closed.");
29762
+ }
29756
29763
  this.connected = next;
29757
29764
  this.connection.clearConnectionProgress(next.progressRunId);
29758
29765
  this.pendingWorkspaceSelection = void 0;
29759
29766
  await this.closeCodexVirtual();
29767
+ this.assertRemoteCurrent(next, signal);
29760
29768
  await this.closeCursorVirtual();
29769
+ this.assertRemoteCurrent(next, signal);
29761
29770
  this.selectRemoteTarget(next);
29762
29771
  await this.codexStreamBridge.closeCodexStreams(previous?.client);
29763
29772
  await previous?.client.close().catch(() => void 0);
@@ -29815,8 +29824,11 @@ var ClientModeRuntime = class {
29815
29824
  });
29816
29825
  workspace = unwrapNativeResult(response);
29817
29826
  }
29827
+ this.assertRemoteCurrent(remote, signal);
29818
29828
  await this.closeCodexVirtual();
29829
+ this.assertRemoteCurrent(remote, signal);
29819
29830
  await this.closeCursorVirtual();
29831
+ this.assertRemoteCurrent(remote, signal);
29820
29832
  this.selectRemoteTarget(remote, transport);
29821
29833
  const workspaceId = workspaceRecordId(workspace.workspace);
29822
29834
  this.pendingWorkspaceSelection = { targetDeviceId: remote.target.deviceId, workspaceId };
@@ -29898,6 +29910,7 @@ var ClientModeRuntime = class {
29898
29910
  idempotencyKey: uuidV7()
29899
29911
  }, signal));
29900
29912
  const project = record2(result.project);
29913
+ this.assertRemoteCurrent(remote, signal);
29901
29914
  if (typeof project.id !== "string" || project.id.length === 0) {
29902
29915
  throw new ClientModeError("INVALID_MESSAGE", "The Host returned an invalid CodeX project.");
29903
29916
  }
@@ -29929,23 +29942,32 @@ var ClientModeRuntime = class {
29929
29942
  }, backend, remote.features.acpModelLoadsSession ? ["agent.acp.antigravity.model-load.v1"] : [], new RemoteTypertGateway2(remote.client));
29930
29943
  let workspace;
29931
29944
  try {
29932
- workspace = await virtual.selectWorkspace(workspaceId);
29933
- } catch {
29945
+ try {
29946
+ workspace = await virtual.selectWorkspace(workspaceId);
29947
+ } catch {
29948
+ this.assertRemoteCurrent(remote, signal);
29949
+ throw new ClientModeError("WORKSPACE_NOT_FOUND", `The selected ${backend} workspace is no longer available.`);
29950
+ }
29951
+ this.assertRemoteCurrent(remote, signal);
29952
+ await this.closeCodexVirtual();
29953
+ this.assertRemoteCurrent(remote, signal);
29954
+ await this.closeCursorVirtual();
29955
+ this.assertRemoteCurrent(remote, signal);
29956
+ const preferredSessionId = await virtual.preferredSessionId();
29957
+ this.assertRemoteCurrent(remote, signal);
29958
+ this.cursorVirtual = virtual;
29959
+ this.acpVirtualBackend = backend;
29960
+ this.selectCursorTarget(virtual, remote);
29961
+ this.pendingWorkspaceSelection = {
29962
+ targetDeviceId: remote.target.deviceId,
29963
+ workspaceId: workspace.workspaceId,
29964
+ backend,
29965
+ ...preferredSessionId === void 0 ? {} : { sessionId: preferredSessionId }
29966
+ };
29967
+ } catch (error) {
29934
29968
  await virtual.close();
29935
- throw new ClientModeError("WORKSPACE_NOT_FOUND", `The selected ${backend} workspace is no longer available.`);
29969
+ throw error;
29936
29970
  }
29937
- await this.closeCodexVirtual();
29938
- await this.closeCursorVirtual();
29939
- this.cursorVirtual = virtual;
29940
- this.acpVirtualBackend = backend;
29941
- this.selectCursorTarget(virtual, remote);
29942
- const preferredSessionId = await virtual.preferredSessionId();
29943
- this.pendingWorkspaceSelection = {
29944
- targetDeviceId: remote.target.deviceId,
29945
- workspaceId: workspace.workspaceId,
29946
- backend,
29947
- ...preferredSessionId === void 0 ? {} : { sessionId: preferredSessionId }
29948
- };
29949
29971
  this.logger.info(`${backend} virtual workspace opened`, { targetDeviceId: shortId(remote.target.deviceId) });
29950
29972
  return { ...this.status(), workspace };
29951
29973
  }
@@ -29962,19 +29984,27 @@ var ClientModeRuntime = class {
29962
29984
  deviceId: remote.target.deviceId,
29963
29985
  name: remote.target.name
29964
29986
  }, backend, remote.features.acpModelLoadsSession ? ["agent.acp.antigravity.model-load.v1"] : [], new RemoteTypertGateway2(remote.client));
29965
- const workspace = await virtual.selectOrCreateWorkspace(trimmedPath);
29966
- await this.closeCodexVirtual();
29967
- await this.closeCursorVirtual();
29968
- this.cursorVirtual = virtual;
29969
- this.acpVirtualBackend = backend;
29970
- this.selectCursorTarget(virtual, remote);
29971
- this.pendingWorkspaceSelection = {
29972
- targetDeviceId: remote.target.deviceId,
29973
- workspaceId: workspace.workspaceId,
29974
- backend
29975
- };
29976
- this.logger.info(`${backend} virtual workspace created`, { targetDeviceId: shortId(remote.target.deviceId) });
29977
- return { ...this.status(), workspace };
29987
+ try {
29988
+ const workspace = await virtual.selectOrCreateWorkspace(trimmedPath);
29989
+ this.assertRemoteCurrent(remote, signal);
29990
+ await this.closeCodexVirtual();
29991
+ this.assertRemoteCurrent(remote, signal);
29992
+ await this.closeCursorVirtual();
29993
+ this.assertRemoteCurrent(remote, signal);
29994
+ this.cursorVirtual = virtual;
29995
+ this.acpVirtualBackend = backend;
29996
+ this.selectCursorTarget(virtual, remote);
29997
+ this.pendingWorkspaceSelection = {
29998
+ targetDeviceId: remote.target.deviceId,
29999
+ workspaceId: workspace.workspaceId,
30000
+ backend
30001
+ };
30002
+ this.logger.info(`${backend} virtual workspace created`, { targetDeviceId: shortId(remote.target.deviceId) });
30003
+ return { ...this.status(), workspace };
30004
+ } catch (error) {
30005
+ await virtual.close();
30006
+ throw error;
30007
+ }
29978
30008
  }
29979
30009
  consumeWorkspaceSelection(selection) {
29980
30010
  const pending = this.pendingWorkspaceSelection;
@@ -29984,21 +30014,34 @@ var ClientModeRuntime = class {
29984
30014
  return this.status();
29985
30015
  }
29986
30016
  async close() {
29987
- await this.closePreview();
29988
30017
  if (this.closed) return;
29989
30018
  this.closed = true;
29990
- this.proxySwitch?.selectLocal();
29991
- await this.closePreview();
29992
- this.gatewaySwitch.selectLocal();
29993
- this.pendingWorkspaceSelection = void 0;
29994
- await this.closeCodexVirtual();
29995
- await this.closeCursorVirtual();
29996
- await this.codexStreamBridge.closeCodexStreams(this.connected?.client);
29997
- await this.connected?.client.close().catch(() => void 0);
30019
+ this.server.close?.();
30020
+ try {
30021
+ await this.leaveRemote();
30022
+ } finally {
30023
+ this.proxySwitch?.restore();
30024
+ this.gatewaySwitch.restore();
30025
+ }
30026
+ }
30027
+ async leaveRemote() {
30028
+ this.remoteLifetime.abort(new ClientModeError("CANCELLED", "The Remote connection was closed."));
30029
+ this.remoteLifetime = new AbortController();
30030
+ const previous = this.connected;
29998
30031
  this.connected = void 0;
29999
30032
  this.connectionProgress = void 0;
30000
- this.proxySwitch?.restore();
30001
- this.gatewaySwitch.restore();
30033
+ this.pendingWorkspaceSelection = void 0;
30034
+ this.proxySwitch?.selectLocal();
30035
+ this.gatewaySwitch.selectLocal();
30036
+ const results = await Promise.allSettled([
30037
+ this.closeCodexVirtual(),
30038
+ this.closeCursorVirtual(),
30039
+ this.closePreview(),
30040
+ this.codexStreamBridge.closeCodexStreams(previous?.client),
30041
+ previous?.client.close()
30042
+ ]);
30043
+ const failure4 = results.find((result) => result.status === "rejected");
30044
+ if (failure4?.status === "rejected") throw failure4.reason;
30002
30045
  }
30003
30046
  async callRemoteFileViewer(endpoint, payload, signal) {
30004
30047
  const remote = this.connected;
@@ -30111,11 +30154,12 @@ var ClientModeRuntime = class {
30111
30154
  );
30112
30155
  }
30113
30156
  async ensureConnected(targetDeviceId, signal, isCurrent) {
30157
+ const lifetime = this.remoteLifetime.signal;
30114
30158
  if (this.connected?.target.deviceId === targetDeviceId) return this.connected;
30115
30159
  const next = await this.connect(targetDeviceId, signal);
30116
30160
  const assertCurrent = () => {
30117
30161
  signal?.throwIfAborted();
30118
- if (this.closed || isCurrent?.() === false) {
30162
+ if (this.closed || lifetime.aborted || isCurrent?.() === false) {
30119
30163
  throw new ClientModeError("CANCELLED", "The Remote connection request was superseded or closed.");
30120
30164
  }
30121
30165
  };
@@ -30134,6 +30178,12 @@ var ClientModeRuntime = class {
30134
30178
  await previous?.client.close().catch(() => void 0);
30135
30179
  return next;
30136
30180
  }
30181
+ assertRemoteCurrent(remote, signal) {
30182
+ signal?.throwIfAborted();
30183
+ if (this.closed || this.connected !== remote) {
30184
+ throw new ClientModeError("CANCELLED", "The Remote workspace request was superseded or closed.");
30185
+ }
30186
+ }
30137
30187
  requireIdentity() {
30138
30188
  if (this.identity === void 0) throw new ClientModeError("IDENTITY_INVALID", "The client identity is not ready.");
30139
30189
  return this.identity;
@@ -37765,13 +37815,16 @@ function isRecord19(value) {
37765
37815
 
37766
37816
  // src/control-runtime.ts
37767
37817
  var PluginControlRuntime = class {
37768
- constructor(config, identityDirectory, settings, client, host) {
37818
+ constructor(config, identityDirectory, settings, client, host, switchServer) {
37769
37819
  this.config = config;
37770
37820
  this.identityDirectory = identityDirectory;
37771
37821
  this.settings = settings;
37772
37822
  this.client = client;
37773
37823
  this.host = host;
37824
+ this.switchServer = switchServer;
37774
37825
  }
37826
+ logoutFlight;
37827
+ serverSwitchFlight;
37775
37828
  register(connection, webServer) {
37776
37829
  const statusStream = new ControlStatusStream(() => this.streamStatus());
37777
37830
  return registerControlRoute(
@@ -37793,6 +37846,7 @@ var PluginControlRuntime = class {
37793
37846
  }
37794
37847
  async handle(endpoint, payload, signal) {
37795
37848
  try {
37849
+ if (endpoint !== "settings.server.set") await this.serverSwitchFlight;
37796
37850
  if (endpoint === "settings.development.set") return ok3(await this.setDevelopment(payload));
37797
37851
  if (endpoint === "settings.get") return ok3(await this.settingsView());
37798
37852
  if (endpoint === "settings.configure") return ok3(await this.configure(payload));
@@ -37886,6 +37940,19 @@ var PluginControlRuntime = class {
37886
37940
  };
37887
37941
  }
37888
37942
  async setServer(payload) {
37943
+ if (this.serverSwitchFlight !== void 0) {
37944
+ throw new ClientModeError("SERVER_SWITCH_BUSY", "A Server switch is already in progress.");
37945
+ }
37946
+ const flight = this.applyServer(payload);
37947
+ this.serverSwitchFlight = flight;
37948
+ try {
37949
+ return await flight;
37950
+ } finally {
37951
+ if (this.serverSwitchFlight === flight) this.serverSwitchFlight = void 0;
37952
+ }
37953
+ }
37954
+ async applyServer(payload) {
37955
+ await this.logoutFlight;
37889
37956
  if (this.settings === void 0) {
37890
37957
  throw new ClientModeError("SETTINGS_UNAVAILABLE", "DSH user settings are unavailable in this profile.");
37891
37958
  }
@@ -37896,6 +37963,15 @@ var PluginControlRuntime = class {
37896
37963
  const current = editableConfig(resolveConfig(this.settings.get()));
37897
37964
  const next = resolveConfig({ ...current, serverUrl: value.serverUrl });
37898
37965
  await this.settings.replace(editableConfig(next));
37966
+ if (this.switchServer !== void 0) {
37967
+ if (next.serverUrl !== this.config.serverUrl) {
37968
+ const runtimes = await this.switchServer(next);
37969
+ this.client = runtimes.client;
37970
+ this.host = runtimes.host;
37971
+ }
37972
+ this.config = next;
37973
+ return { ...await this.settingsView(), applies: "live" };
37974
+ }
37899
37975
  return this.settingsView();
37900
37976
  }
37901
37977
  async setRole(payload) {
@@ -38017,20 +38093,28 @@ var PluginControlRuntime = class {
38017
38093
  const targetApi = targetRole === "host" ? new HostServerApi(serverUrl, new ServerCredentialStore(targetDirectory)) : new ClientServerApi(serverUrl, new ServerCredentialStore(targetDirectory));
38018
38094
  await targetApi.authorizeOwnedRole(targetIdentity, sourceCredentials.accessToken, sourceCredentials.account);
38019
38095
  }
38020
- async logout() {
38021
- if (this.settings === void 0) {
38022
- throw new ClientModeError("SETTINGS_UNAVAILABLE", "DSH user settings are unavailable in this profile.");
38023
- }
38024
- const config = resolveConfig(this.settings.get());
38096
+ logout() {
38097
+ if (this.logoutFlight !== void 0) return this.logoutFlight;
38098
+ const task = this.clearAuthorization();
38099
+ this.logoutFlight = task;
38100
+ void task.finally(() => {
38101
+ if (this.logoutFlight === task) this.logoutFlight = void 0;
38102
+ }).catch(() => void 0);
38103
+ return task;
38104
+ }
38105
+ async clearAuthorization() {
38106
+ const config = this.settings === void 0 ? this.config : resolveConfig(this.settings.get());
38025
38107
  if (config.serverUrl !== void 0) {
38026
- await Promise.all([
38108
+ const runtimes = await Promise.allSettled([
38027
38109
  this.client?.clearClientAuthorization(),
38028
38110
  this.host?.clearHostAuthorization()
38029
38111
  ]);
38030
- await Promise.all(["host", "client"].map(async (role) => {
38112
+ const stores = await Promise.allSettled(["host", "client"].map(async (role) => {
38031
38113
  const directory = serverStorageDirectory(this.identityDirectory, config.serverUrl, role);
38032
38114
  await new ServerCredentialStore(directory).clear();
38033
38115
  }));
38116
+ const failure4 = [...runtimes, ...stores].find((result) => result.status === "rejected");
38117
+ if (failure4?.status === "rejected") throw failure4.reason;
38034
38118
  }
38035
38119
  return this.settingsView();
38036
38120
  }
@@ -42704,9 +42788,6 @@ async function activate(ctx, readConfig, entryId, tuiBinding) {
42704
42788
  console.error(message);
42705
42789
  }
42706
42790
  }, config.logLevel);
42707
- const hostIdentities = new IdentityStore({
42708
- directory: config.serverUrl === void 0 ? defaultIdentityDirectory : serverStorageDirectory(defaultIdentityDirectory, config.serverUrl, "host")
42709
- });
42710
42791
  const apiProxy = ctx.get("apiProxy");
42711
42792
  const nativeTypertGateway = ctx.get("typertGateway");
42712
42793
  const hostGateway = new TypertGatewaySwitch(nativeTypertGateway);
@@ -42725,56 +42806,103 @@ async function activate(ctx, readConfig, entryId, tuiBinding) {
42725
42806
  };
42726
42807
  }
42727
42808
  const subprocess = ctx.get("subprocess", false);
42728
- const runtime = new HostPluginRuntime(
42729
- config,
42730
- hostIdentities,
42731
- apiProxy,
42732
- logger,
42733
- localTypertGateway,
42734
- () => ctx.get("fileViewerHost"),
42735
- subprocess === void 0 ? void 0 : subprocessTerminalSpawner(subprocess),
42736
- (peer) => createPluginRouteDispatcher(
42737
- () => ctx.get("connection"),
42738
- () => ctx.get("webServer")?.port,
42739
- peer
42740
- )
42741
- );
42742
- let clientRuntime;
42743
- const hostControl = runtime;
42744
- if (config.serverUrl !== void 0 && connection !== void 0) {
42745
- const clientIdentities = new IdentityStore({
42746
- directory: serverStorageDirectory(defaultIdentityDirectory, config.serverUrl, "client")
42809
+ const createRuntimes = (config2) => {
42810
+ const hostIdentities = new IdentityStore({
42811
+ directory: config2.serverUrl === void 0 ? defaultIdentityDirectory : serverStorageDirectory(defaultIdentityDirectory, config2.serverUrl, "host")
42747
42812
  });
42748
- clientRuntime = new ClientModeRuntime(
42749
- config,
42750
- clientIdentities,
42751
- new ClientServerApi(config.serverUrl, new ServerCredentialStore(clientIdentities.directory)),
42813
+ const runtime2 = new HostPluginRuntime(
42814
+ config2,
42815
+ hostIdentities,
42752
42816
  apiProxy,
42753
- nativeTypertGateway,
42754
42817
  logger,
42755
- hostControl
42818
+ localTypertGateway,
42819
+ () => ctx.get("fileViewerHost"),
42820
+ subprocess === void 0 ? void 0 : subprocessTerminalSpawner(subprocess),
42821
+ (peer) => createPluginRouteDispatcher(
42822
+ () => ctx.get("connection"),
42823
+ () => ctx.get("webServer")?.port,
42824
+ peer
42825
+ )
42756
42826
  );
42757
- }
42758
- const controlRuntime = connection === void 0 ? void 0 : new PluginControlRuntime(config, defaultIdentityDirectory, settingsBinding, clientRuntime, hostControl);
42827
+ let clientRuntime2;
42828
+ if (config2.serverUrl !== void 0 && connection !== void 0) {
42829
+ const clientIdentities = new IdentityStore({
42830
+ directory: serverStorageDirectory(defaultIdentityDirectory, config2.serverUrl, "client")
42831
+ });
42832
+ clientRuntime2 = new ClientModeRuntime(
42833
+ config2,
42834
+ clientIdentities,
42835
+ new ClientServerApi(config2.serverUrl, new ServerCredentialStore(clientIdentities.directory)),
42836
+ apiProxy,
42837
+ nativeTypertGateway,
42838
+ logger,
42839
+ runtime2
42840
+ );
42841
+ }
42842
+ return { runtime: runtime2, clientRuntime: clientRuntime2 };
42843
+ };
42844
+ let { runtime, clientRuntime } = createRuntimes(config);
42845
+ let closed = false;
42846
+ let clientServiceProvided = clientRuntime !== void 0;
42847
+ let startup;
42848
+ let replacement;
42849
+ const replaceServer = (next) => {
42850
+ const operation = async () => {
42851
+ await startup;
42852
+ if (closed) throw new Error("Remote plugin is closed");
42853
+ const shutdown = await Promise.allSettled([clientRuntime?.close(), runtime.close()]);
42854
+ const failure4 = shutdown.find((result) => result.status === "rejected");
42855
+ if (failure4?.status === "rejected") throw failure4.reason;
42856
+ if (closed) throw new Error("Remote plugin is closed");
42857
+ const fresh = createRuntimes(next);
42858
+ try {
42859
+ await fresh.runtime.start();
42860
+ await fresh.clientRuntime?.start();
42861
+ if (closed) throw new Error("Remote plugin is closed");
42862
+ } catch (error) {
42863
+ await Promise.allSettled([fresh.clientRuntime?.close(), fresh.runtime.close()]);
42864
+ throw error;
42865
+ }
42866
+ runtime = fresh.runtime;
42867
+ clientRuntime = fresh.clientRuntime;
42868
+ ctx.dshRemote = runtime;
42869
+ if (clientRuntime !== void 0) {
42870
+ if (clientServiceProvided) ctx.dshRemoteClient = clientRuntime;
42871
+ else {
42872
+ ctx.provide("dshRemoteClient", clientRuntime);
42873
+ clientServiceProvided = true;
42874
+ }
42875
+ }
42876
+ tuiTarget = { runtime, config: next };
42877
+ if (tuiBinding !== void 0) tuiBinding.target = tuiTarget;
42878
+ return { client: clientRuntime, host: runtime };
42879
+ };
42880
+ replacement = operation();
42881
+ return replacement;
42882
+ };
42883
+ const controlRuntime = connection === void 0 ? void 0 : new PluginControlRuntime(config, defaultIdentityDirectory, settingsBinding, clientRuntime, runtime, replaceServer);
42759
42884
  ctx.provide("dshRemote", runtime);
42760
42885
  if (clientRuntime !== void 0) ctx.provide("dshRemoteClient", clientRuntime);
42761
- const tuiTarget = { runtime, config };
42886
+ let tuiTarget = { runtime, config };
42762
42887
  if (tuiBinding !== void 0) tuiBinding.target = tuiTarget;
42763
42888
  await disableLegacyLoaderEntries(ctx, logger);
42764
42889
  await ctx.effect(async () => {
42765
42890
  const disposeControl = controlRuntime?.register(connection, webServer);
42766
42891
  try {
42767
- await runtime.start();
42768
- if (clientRuntime !== void 0) {
42769
- await clientRuntime.start();
42770
- } else {
42771
- logger.warn("client remote mode is unavailable", {
42772
- serverConfigured: config.serverUrl !== void 0,
42773
- apiProxyAvailable: apiProxy !== void 0,
42774
- remoteGatewayAvailable: localTypertGateway.supportsCarrier,
42775
- connectionAvailable: connection !== void 0
42776
- });
42777
- }
42892
+ startup = (async () => {
42893
+ await runtime.start();
42894
+ if (clientRuntime !== void 0) {
42895
+ await clientRuntime.start();
42896
+ } else {
42897
+ logger.warn("client remote mode is unavailable", {
42898
+ serverConfigured: config.serverUrl !== void 0,
42899
+ apiProxyAvailable: apiProxy !== void 0,
42900
+ remoteGatewayAvailable: localTypertGateway.supportsCarrier,
42901
+ connectionAvailable: connection !== void 0
42902
+ });
42903
+ }
42904
+ })();
42905
+ await startup;
42778
42906
  } catch (error) {
42779
42907
  if (tuiBinding?.target === tuiTarget) tuiBinding.target = void 0;
42780
42908
  await disposeControl?.();
@@ -42783,6 +42911,8 @@ async function activate(ctx, readConfig, entryId, tuiBinding) {
42783
42911
  throw error;
42784
42912
  }
42785
42913
  return async () => {
42914
+ closed = true;
42915
+ await replacement?.catch(() => void 0);
42786
42916
  if (tuiBinding?.target === tuiTarget) tuiBinding.target = void 0;
42787
42917
  await disposeControl?.();
42788
42918
  await clientRuntime?.close();