ds-harness-remote 0.4.14 → 0.4.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +2 -2
  2. package/dist/client-runtime.d.ts +5 -0
  3. package/dist/client-runtime.d.ts.map +1 -1
  4. package/dist/client-runtime.js +24 -0
  5. package/dist/client-runtime.js.map +1 -1
  6. package/dist/client-secure-transport.d.ts +1 -0
  7. package/dist/client-secure-transport.d.ts.map +1 -1
  8. package/dist/client-secure-transport.js +12 -5
  9. package/dist/client-secure-transport.js.map +1 -1
  10. package/dist/client.d.ts.map +1 -1
  11. package/dist/client.github.js +259 -76
  12. package/dist/client.js +259 -76
  13. package/dist/client.js.map +1 -1
  14. package/dist/config.d.ts +12 -0
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/config.js +6 -0
  17. package/dist/config.js.map +1 -1
  18. package/dist/control-route.d.ts +10 -1
  19. package/dist/control-route.d.ts.map +1 -1
  20. package/dist/control-route.js +17 -4
  21. package/dist/control-route.js.map +1 -1
  22. package/dist/control-runtime.d.ts +8 -1
  23. package/dist/control-runtime.d.ts.map +1 -1
  24. package/dist/control-runtime.js +40 -1
  25. package/dist/control-runtime.js.map +1 -1
  26. package/dist/control-stream.d.ts +67 -0
  27. package/dist/control-stream.d.ts.map +1 -0
  28. package/dist/control-stream.js +179 -0
  29. package/dist/control-stream.js.map +1 -0
  30. package/dist/harness-api-bridge.js +3 -0
  31. package/dist/harness-api-bridge.js.map +1 -1
  32. package/dist/harness-remote-bridge.d.ts +4 -2
  33. package/dist/harness-remote-bridge.d.ts.map +1 -1
  34. package/dist/harness-remote-bridge.js +42 -4
  35. package/dist/harness-remote-bridge.js.map +1 -1
  36. package/dist/index.js +1559 -580
  37. package/dist/index.js.map +4 -4
  38. package/dist/loopback-host.d.ts +20 -0
  39. package/dist/loopback-host.d.ts.map +1 -0
  40. package/dist/loopback-host.js +220 -0
  41. package/dist/loopback-host.js.map +1 -0
  42. package/dist/loopback-preview.d.ts +19 -0
  43. package/dist/loopback-preview.d.ts.map +1 -0
  44. package/dist/loopback-preview.js +232 -0
  45. package/dist/loopback-preview.js.map +1 -0
  46. package/dist/remote-typert-gateway.js +16 -4
  47. package/dist/remote-typert-gateway.js.map +1 -1
  48. package/dist/rpc-router.d.ts +3 -1
  49. package/dist/rpc-router.d.ts.map +1 -1
  50. package/dist/rpc-router.js +10 -1
  51. package/dist/rpc-router.js.map +1 -1
  52. package/dist/server-api.d.ts +6 -3
  53. package/dist/server-api.d.ts.map +1 -1
  54. package/dist/server-api.js +50 -26
  55. package/dist/server-api.js.map +1 -1
  56. package/dist/server-connection.d.ts +1 -0
  57. package/dist/server-connection.d.ts.map +1 -1
  58. package/dist/server-connection.js +54 -7
  59. package/dist/server-connection.js.map +1 -1
  60. package/dist/server-credentials.d.ts +9 -0
  61. package/dist/server-credentials.d.ts.map +1 -1
  62. package/dist/server-credentials.js +34 -0
  63. package/dist/server-credentials.js.map +1 -1
  64. package/dist/service.d.ts +5 -0
  65. package/dist/service.d.ts.map +1 -1
  66. package/dist/service.js +19 -2
  67. package/dist/service.js.map +1 -1
  68. package/dist/status-stream.d.ts +48 -0
  69. package/dist/status-stream.d.ts.map +1 -0
  70. package/dist/status-stream.js +133 -0
  71. package/dist/status-stream.js.map +1 -0
  72. package/dist/terminal-policy.d.ts +21 -0
  73. package/dist/terminal-policy.d.ts.map +1 -0
  74. package/dist/terminal-policy.js +62 -0
  75. package/dist/terminal-policy.js.map +1 -0
  76. package/dist/tui-command.js +6 -0
  77. package/dist/tui-command.js.map +1 -1
  78. package/dist/version.d.ts +1 -1
  79. package/dist/version.js +1 -1
  80. package/dsh-plugin.json +1 -1
  81. package/package.json +36 -16
  82. package/public.d.ts +2 -0
@@ -1694,7 +1694,61 @@ Minimum version required to store current data is: ` + bestVersion + `.
1694
1694
  }
1695
1695
 
1696
1696
  // src/control-route.ts
1697
- var CONTROL_RPC_PREFIX = "/ds-harness-remote";
1697
+ var CONTROL_RPC_PREFIX = "/ds-harness-remote", STATUS_STREAM_ENDPOINT = "status.events", STATUS_STREAM_PATH = `${CONTROL_RPC_PREFIX}/${STATUS_STREAM_ENDPOINT}`;
1698
+
1699
+ // src/status-stream.ts
1700
+ function parseStatus(data) {
1701
+ if (typeof data == "string")
1702
+ try {
1703
+ let parsed = JSON.parse(data);
1704
+ return parsed === null || typeof parsed != "object" ? void 0 : parsed;
1705
+ } catch {
1706
+ return;
1707
+ }
1708
+ }
1709
+ function createStatusFeed(options) {
1710
+ let listeners = /* @__PURE__ */ new Set(), pollIntervalMs = options.pollIntervalMs ?? 1500, openTimeoutMs = options.openTimeoutMs ?? 4e3, createSource = options.createSource ?? ((url) => new EventSource(url)), status, source, openTimer, pollTimer, publish = (next) => {
1711
+ status = next;
1712
+ for (let listener of [...listeners]) listener(next);
1713
+ }, teardown = () => {
1714
+ openTimer !== void 0 && (clearTimeout(openTimer), openTimer = void 0), pollTimer !== void 0 && (clearInterval(pollTimer), pollTimer = void 0), source?.close(), source = void 0;
1715
+ }, poll = () => {
1716
+ options.readStatus().then((next) => {
1717
+ pollTimer !== void 0 && publish(next);
1718
+ }).catch(() => {
1719
+ });
1720
+ }, fallbackToPolling = (reason) => {
1721
+ pollTimer !== void 0 || listeners.size === 0 || (openTimer !== void 0 && (clearTimeout(openTimer), openTimer = void 0), source?.close(), source = void 0, options.onFallback?.(reason), poll(), pollTimer = setInterval(poll, pollIntervalMs));
1722
+ }, openStream = () => {
1723
+ let opened;
1724
+ try {
1725
+ opened = createSource(options.url);
1726
+ } catch {
1727
+ fallbackToPolling("unsupported");
1728
+ return;
1729
+ }
1730
+ source = opened;
1731
+ let received = !1;
1732
+ openTimer = setTimeout(() => {
1733
+ received || fallbackToPolling("silent");
1734
+ }, openTimeoutMs), opened.onmessage = (event) => {
1735
+ received = !0, openTimer !== void 0 && (clearTimeout(openTimer), openTimer = void 0);
1736
+ let next = parseStatus(event.data);
1737
+ next !== void 0 && publish(next);
1738
+ }, opened.onerror = () => {
1739
+ opened.readyState === 2 && fallbackToPolling("unavailable");
1740
+ };
1741
+ };
1742
+ return {
1743
+ getSnapshot: () => status,
1744
+ subscribe: (listener) => (listeners.add(listener), status !== void 0 && listener(status), listeners.size === 1 && openStream(), () => {
1745
+ listeners.delete(listener), listeners.size === 0 && teardown();
1746
+ }),
1747
+ close: () => {
1748
+ listeners.clear(), teardown();
1749
+ }
1750
+ };
1751
+ }
1698
1752
 
1699
1753
  // src/client.ts
1700
1754
  var clientModuleId = "ds-harness-remote", pendingWorkspaceSelectionKey = "dsh-remote:pending-workspace-selection", deepSeekWorkspaceIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAACVBMVEUAAADy8vXx8fUsA3vyAAAAAXRSTlMAQObYZgAAAOxJREFUWMPtlsEOwyAMQxP+/6OnTZMGxHGcot3wDYgfhkJbs6urprzveGtu9d1NgEdto8w93GuA7zPS2RFg7ynsCOAokbtAmLr2YaeKgJ6fxpT8jCAC0qSqPyNoO5DXdvyouj4ClFAdYaDxK09uiBDCIYBf6CxLesnR0uF2pG+JmhCqrUb0AOvjjoQaYDTCEAAgMg5grhAeAQIB+/M15IcKlnUIp4CkTCd0AKb4zVsRSJVGYEVzT0agK107IMGMEurvromEpaX4wzk/IKw378kq4LafED6Oowxfk7AP2q8W16EdM3p29Eyvrv6vF0WIBfBziKyCAAAAAElFTkSuQmCC", gptWorkspaceIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAclBMVEX////v7+++wMf////s7O3k5OTExcrX2Ny6vMTv8PPW19yXmqRqboJmaHd3eoaqrLPs7fGMjpfb3eKlpq1eYXOxs7pWWWd+gY5HSVdRVGVub3k+QU74+Pg4OkcvMT1ISlUoKjUiIy4fICueoKkaGyUSFB21Bp+qAAAAAXRSTlMAQObYZgAABChJREFUWMPtl916qjoQhlsdEwIECEgMYCBxx/u/xT1DrFUL1LO1DtY8LSLJvPnmB4gfH//sr7TPDfvdebf/xXZbkE/4zZ0MVhGHd9zJDsv+7F3//Z69tT5wljAu3tXw+TIlzXKJlhflYmJ+5uF5WpWhb54X+C/zZCmT2wJA4dJZwus0UYjIGqWyMhVbEp4yKNRRqlv0opW5PJLJ4rFJXvP4JKDRMqturBZjkMVJFYjR6iHQDQDI4+k2k51wZbWrqwp4mR/1CVYATyko9TFGxBUqL5IvMbXSWt0T8bkGgFLqgqZBI/Uxbx5UAxLaNcCNLBpttM7wbCc1pmJOpWAsDtey68UaQMyrFgb9uxLPjqg3+nGluyKN0XUdWw0BJ0NvTFFqgzpr9J+Dh4w0Ga1qkqBNswagxdRgFPAbgHRQG5jumGWyMxrTIaRRa4AzFs0YrNMXgI4MJWnsKYFhGNMnIh/mBOPsV8CZBFjN9w8ArowxnZzDr5IcvxTaKpxJCVsoI3SWysz1UM4AqQdzlCgho1IKrK+x1mKFzisANtgEU8E7GwHDgOqxe4w9tnM166wbaA2xBMBLrTWcAGYGdCiYVUJUrMCF+7l6gklrW1RwPi8ByjuActANqhKzVWoYrFGcCFzbDgNaBrTWpncFQMcI2DfWoAiDqRCiHcYM/ZdCEKkZqfR8spj4F0CiJjv2gHL6URNoCQDaYacL6O1oFDO2eQDUFP7AKdCRdC4CRDZaNifbjA41l/uYb/SZMAFNTBGzY7sG4J2jTsJgToOzIzXF3HZqBpSOAGduXbkGEKV1OqVGqRJpRxtvwbSw5IqAiUJItwBCOT9R32FCGo3nCkAN3t0UTBRC6zDONcBeOee9pr4T+ASbnDOdc5OmtREwEEB5w8Wqgt4bOV5c7Lt9iiK8zdP/IsBbutUGX4gNwEVCqd3FUt/xYnRWt7BvPBYQjwjAGXa3ASguHVAZ/WXKlA3eZDX1wRegFsr7E2wAlB9xFbx/7MV70kFT8aqNgLL3F8nFBmA3hrn/IJHjKJOK2oj1WI4Z4FzwN/81AOhgYD6v2xbIvaZIurLC9AfvB1WLTYAoXejF1xeBL5cpBIyEbmxkZymIXwDQh1BAfOrhI1lfwtgz8tozF5T4ttf3+32AyxC6klwAgw/YVTHpoK82xYt3wLP/Bw4AAI4D7/01TLKXBj9NEz2wEuGiKpoy68S/FwCLMQAZ3gXhihaCa25vQo6RyToOz9PE6wYjJgGi1YnqZX8qMIK+TDlnargGzSv4NvFjlwXf/nfLLMrAB4O/Xl3BH0d+RIASFgDA8IYMIXgr2+cBsbDdPSwAAHjSZFmb3txETCKIxc0uWwI8rxsLJcTiVndNwyMgKljbbNN2/zcEAja2+4RgIDYN2Bu/WtAOaEodnu2dnzz/7I/Y/w/LaEcX/MdfAAAAAElFTkSuQmCC";
@@ -1716,6 +1770,17 @@ Minimum version required to store current data is: ` + bestVersion + `.
1716
1770
  return snapshot.baselinesReady === !0 || snapshot.phase === "ready";
1717
1771
  }
1718
1772
  var localeNamespace = "ds-harness-remote", en = {
1773
+ terminalRemote: "Remote terminal",
1774
+ terminalRemoteHint: "Allow trusted Remote devices to run an interactive shell as this Host user. Off by default; independent of Agent approvals. Toggling saves and applies immediately.",
1775
+ terminalRemoteUnavailable: "Remote terminal is unavailable. Enable Remote terminal in the Host Remote settings, then restart the Host.",
1776
+ previewPorts: "Remote preview ports",
1777
+ previewPortsHint: "Allow specific Host ports (1024\u201365535), separated by commas. Empty disables preview. HTTP, WebSocket and hot reload are supported.",
1778
+ developmentSave: "Save access settings",
1779
+ developmentSaved: "Remote preview ports saved. Changes apply immediately.",
1780
+ terminalSaved: "Remote terminal setting saved. Applies immediately.",
1781
+ previewOpen: "Preview service",
1782
+ previewPort: "Host port",
1783
+ previewLocalOnly: "Preview requires Desktop or a browser connected to Harness on this computer.",
1719
1784
  pluginTitle: "DeepSeek Remote",
1720
1785
  pluginDescription: "Connect once. Available anytime.",
1721
1786
  expandSettings: "Show settings: {name}",
@@ -1787,6 +1852,8 @@ Minimum version required to store current data is: ` + bestVersion + `.
1787
1852
  reconnectingAction: "Reconnecting\u2026",
1788
1853
  reconnectStarted: "Reconnect requested.",
1789
1854
  connectionAuthorizationExpired: "Authorization expired. Sign out and authorize this Host again.",
1855
+ connectionReplaced: "Another instance is using this Host identity. Stop it or use a separate DSH_HOME before reconnecting.",
1856
+ connectionCredentialsBusy: "Credential refresh is locked. Stop other instances. After a crash, stop all instances before removing server-credentials.json.refresh-lock and authorizing again.",
1790
1857
  connectionDeviceRevoked: "This Host was revoked on the Server. Sign out and authorize it again.",
1791
1858
  connectionOwnershipRequired: "The Server no longer recognizes this Host as an owned device.",
1792
1859
  connectionRateLimited: "The Server is receiving too many requests. Automatic retry will continue.",
@@ -1939,6 +2006,17 @@ Minimum version required to store current data is: ` + bestVersion + `.
1939
2006
  codexVirtualWorkspace: "CodeX virtual workspace",
1940
2007
  codexVirtualSessions: "Sessions"
1941
2008
  }, zh = {
2009
+ terminalRemote: "\u8FDC\u7A0B\u7EC8\u7AEF",
2010
+ terminalRemoteHint: "\u5141\u8BB8\u53D7\u4FE1\u4EFB\u7684 Remote \u8BBE\u5907\u4EE5\u6B64 Host \u7528\u6237\u8EAB\u4EFD\u8FD0\u884C\u4EA4\u4E92\u5F0F Shell\u3002\u9ED8\u8BA4\u5173\u95ED\uFF0C\u72EC\u7ACB\u4E8E Agent \u5BA1\u6279\uFF1B\u5207\u6362\u540E\u7ACB\u5373\u4FDD\u5B58\u5E76\u751F\u6548\u3002",
2011
+ terminalRemoteUnavailable: "\u8FDC\u7A0B\u7EC8\u7AEF\u4E0D\u53EF\u7528\u3002\u8BF7\u5728 Host \u7684 Remote \u8BBE\u7F6E\u4E2D\u5F00\u542F\u300C\u8FDC\u7A0B\u7EC8\u7AEF\u300D\uFF0C\u7136\u540E\u91CD\u542F Host\u3002",
2012
+ previewPorts: "\u8FDC\u7A0B\u9884\u89C8\u7AEF\u53E3",
2013
+ previewPortsHint: "\u586B\u5199\u5141\u8BB8\u8BBF\u95EE\u7684 Host \u7AEF\u53E3\uFF081024\u201365535\uFF09\uFF0C\u4EE5\u9017\u53F7\u5206\u9694\u3002\u7559\u7A7A\u7981\u7528\u9884\u89C8\uFF1B\u652F\u6301 HTTP\u3001WebSocket \u548C\u70ED\u66F4\u65B0\u3002",
2014
+ developmentSave: "\u4FDD\u5B58\u8BBF\u95EE\u8BBE\u7F6E",
2015
+ developmentSaved: "\u8FDC\u7A0B\u9884\u89C8\u7AEF\u53E3\u5DF2\u4FDD\u5B58\uFF0C\u7ACB\u5373\u751F\u6548\u3002",
2016
+ terminalSaved: "\u8FDC\u7A0B\u7EC8\u7AEF\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u7ACB\u5373\u751F\u6548\u3002",
2017
+ previewOpen: "\u9884\u89C8\u670D\u52A1",
2018
+ previewPort: "Host \u7AEF\u53E3",
2019
+ previewLocalOnly: "\u9884\u89C8\u9700\u8981 Desktop\uFF0C\u6216\u8FDE\u63A5\u5230\u672C\u673A Harness \u7684\u6D4F\u89C8\u5668\u3002",
1942
2020
  pluginTitle: "DeepSeek \u8FDC\u7A0B\u8FDE\u63A5",
1943
2021
  pluginDescription: "\u4E00\u6B21\u8FDE\u63A5\uFF0C\u968F\u65F6\u53EF\u7528\u3002",
1944
2022
  expandSettings: "\u5C55\u5F00\u8BBE\u7F6E\uFF1A{name}",
@@ -2010,6 +2088,8 @@ Minimum version required to store current data is: ` + bestVersion + `.
2010
2088
  reconnectingAction: "\u6B63\u5728\u91CD\u8FDE\u2026",
2011
2089
  reconnectStarted: "\u5DF2\u53D1\u8D77\u91CD\u8FDE\u3002",
2012
2090
  connectionAuthorizationExpired: "\u6388\u6743\u5DF2\u5931\u6548\uFF0C\u8BF7\u9000\u51FA\u6388\u6743\u540E\u91CD\u65B0\u8FDE\u63A5\u6B64 Host\u3002",
2091
+ connectionReplaced: "\u53E6\u4E00\u5B9E\u4F8B\u6B63\u5728\u4F7F\u7528\u6B64 Host \u8EAB\u4EFD\u3002\u8BF7\u505C\u6B62\u8BE5\u5B9E\u4F8B\uFF0C\u6216\u4F7F\u7528\u72EC\u7ACB\u7684 DSH_HOME \u540E\u91CD\u65B0\u8FDE\u63A5\u3002",
2092
+ connectionCredentialsBusy: "\u51ED\u636E\u5237\u65B0\u88AB\u9501\u5B9A\uFF0C\u8BF7\u505C\u6B62\u5176\u4ED6\u5B9E\u4F8B\u3002\u82E5\u6B64\u524D\u5F02\u5E38\u9000\u51FA\uFF0C\u8BF7\u505C\u6B62\u6240\u6709\u5B9E\u4F8B\u540E\u79FB\u9664 server-credentials.json.refresh-lock \u5E76\u91CD\u65B0\u6388\u6743\u3002",
2013
2093
  connectionDeviceRevoked: "\u6B64 Host \u5DF2\u5728 Server \u4E0A\u88AB\u64A4\u9500\uFF0C\u8BF7\u9000\u51FA\u6388\u6743\u540E\u91CD\u65B0\u8FDE\u63A5\u3002",
2014
2094
  connectionOwnershipRequired: "Server \u5DF2\u4E0D\u518D\u5C06\u6B64 Host \u8BC6\u522B\u4E3A\u5F53\u524D\u8D26\u53F7\u7684\u8BBE\u5907\u3002",
2015
2095
  connectionRateLimited: "Server \u8BF7\u6C42\u8FC7\u591A\uFF0C\u63D2\u4EF6\u5C06\u7EE7\u7EED\u81EA\u52A8\u91CD\u8BD5\u3002",
@@ -2173,7 +2253,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
2173
2253
  controlUnavailable: !0,
2174
2254
  connected: !1,
2175
2255
  transport: "Disconnected",
2176
- remoteFeatures: { commandList: !1, fileViewer: !1, codex: !1 },
2256
+ remoteFeatures: { commandList: !1, fileViewer: !1, terminal: !1, codex: !1 },
2177
2257
  hostAuthorizationAvailable: !1
2178
2258
  };
2179
2259
  }
@@ -2266,7 +2346,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
2266
2346
  };
2267
2347
  }
2268
2348
  function connectionErrorMessage(code, t) {
2269
- return t(code === "ACCOUNT_AUTH_REQUIRED" || code === "AUTH_INVALID" || code === "TOKEN_EXPIRED" ? "connectionAuthorizationExpired" : code === "DEVICE_REVOKED" ? "connectionDeviceRevoked" : code === "DEVICE_OWNERSHIP_REQUIRED" ? "connectionOwnershipRequired" : code === "RATE_LIMITED" ? "connectionRateLimited" : code === "UNSUPPORTED_VERSION" ? "connectionVersionMismatch" : code === "INVALID_MESSAGE" ? "connectionInvalidResponse" : code === "CONNECTION_FAILED" || code === "SERVER_NOT_CONFIGURED" ? "connectionReachability" : "connectionUnexpected");
2349
+ return t(code === "ACCOUNT_AUTH_REQUIRED" || code === "AUTH_INVALID" || code === "TOKEN_EXPIRED" ? "connectionAuthorizationExpired" : code === "CONNECTION_REPLACED" ? "connectionReplaced" : code === "SERVER_CREDENTIALS_BUSY" ? "connectionCredentialsBusy" : code === "DEVICE_REVOKED" ? "connectionDeviceRevoked" : code === "DEVICE_OWNERSHIP_REQUIRED" ? "connectionOwnershipRequired" : code === "RATE_LIMITED" ? "connectionRateLimited" : code === "UNSUPPORTED_VERSION" ? "connectionVersionMismatch" : code === "INVALID_MESSAGE" ? "connectionInvalidResponse" : code === "CONNECTION_FAILED" || code === "SERVER_NOT_CONFIGURED" ? "connectionReachability" : "connectionUnexpected");
2270
2350
  }
2271
2351
  function connectionStatusLabel(status, t) {
2272
2352
  return status === void 0 ? t("checkingConnection") : status.online ? t("online") : status.reconnecting ? t(status.lastActiveAt === void 0 && status.error === void 0 ? "connecting" : "reconnecting") : t("offline");
@@ -2354,8 +2434,8 @@ Minimum version required to store current data is: ` + bestVersion + `.
2354
2434
  }
2355
2435
  }
2356
2436
  function RemotePluginOptions(props) {
2357
- let { t } = props, [open, setOpen] = React.useState(!1), [serverUrl, setServerUrl] = React.useState(""), [codexEnabled, setCodexEnabled] = React.useState(!0), role = "host", [registrationCode, setRegistrationCode] = React.useState(""), [associations, setAssociations] = React.useState({}), [loaded, setLoaded] = React.useState(!1), [writable, setWritable] = React.useState(!1), [busy, setBusy] = React.useState(!1), [codexBusy, setCodexBusy] = React.useState(!1), [acpBackends, setAcpBackends] = React.useState([]), [acpAvailability, setAcpAvailability] = React.useState({}), [acpChecking, setAcpChecking] = React.useState({}), [acpCheckResults, setAcpCheckResults] = React.useState({}), [addingAcp, setAddingAcp] = React.useState(!1), [acpName, setAcpName] = React.useState(""), [acpCommand, setAcpCommand] = React.useState(""), [acpArguments, setAcpArguments] = React.useState("acp"), [reconnectBusy, setReconnectBusy] = React.useState(!1), [hostStatus, setHostStatus] = React.useState(void 0), [notice, setNotice] = React.useState(void 0), [error, setError] = React.useState(void 0), [settingsView, setSettingsView] = React.useState(void 0), persistedServerUrl = settingsView?.config.serverUrl ?? "https://dsh.r2049.cn", association = associations.client ?? associations.host, serverDirty = settingsView !== void 0 && serverUrl !== persistedServerUrl, draftDirty = serverDirty, applyView = (view) => {
2358
- setSettingsView(view), setServerUrl(view.config.serverUrl ?? "https://dsh.r2049.cn"), setCodexEnabled(view.config.codex?.enabled ?? !0), setAcpBackends((view.config.acp?.backends ?? []).map((item) => ({ id: item.id, enabled: item.enabled !== !1 }))), setAcpAvailability(view.acpAvailability ?? {}), setAssociations(view.associations ?? (view.association === void 0 ? {} : { host: view.association })), setWritable(view.writable), setLoaded(!0);
2437
+ let { t } = props, [open, setOpen] = React.useState(props.view === "page"), [serverUrl, setServerUrl] = React.useState(""), [codexEnabled, setCodexEnabled] = React.useState(!0), [terminalEnabled, setTerminalEnabled] = React.useState(!1), [terminalBusy, setTerminalBusy] = React.useState(!1), [portsBusy, setPortsBusy] = React.useState(!1), [previewPorts, setPreviewPorts] = React.useState(""), role = "host", [registrationCode, setRegistrationCode] = React.useState(""), [associations, setAssociations] = React.useState({}), [loaded, setLoaded] = React.useState(!1), [writable, setWritable] = React.useState(!1), [busy, setBusy] = React.useState(!1), [codexBusy, setCodexBusy] = React.useState(!1), [acpBackends, setAcpBackends] = React.useState([]), [acpAvailability, setAcpAvailability] = React.useState({}), [acpChecking, setAcpChecking] = React.useState({}), [acpCheckResults, setAcpCheckResults] = React.useState({}), [addingAcp, setAddingAcp] = React.useState(!1), [acpName, setAcpName] = React.useState(""), [acpCommand, setAcpCommand] = React.useState(""), [acpArguments, setAcpArguments] = React.useState("acp"), [reconnectBusy, setReconnectBusy] = React.useState(!1), [hostStatus, setHostStatus] = React.useState(void 0), [notice, setNotice] = React.useState(void 0), [error, setError] = React.useState(void 0), [settingsView, setSettingsView] = React.useState(void 0), persistedServerUrl = settingsView?.config.serverUrl ?? "https://dsh.r2049.cn", association = associations.client ?? associations.host, serverDirty = settingsView !== void 0 && serverUrl !== persistedServerUrl, draftDirty = serverDirty, applyView = (view) => {
2438
+ setSettingsView(view), setServerUrl(view.config.serverUrl ?? "https://dsh.r2049.cn"), setCodexEnabled(view.config.codex?.enabled ?? !0), setTerminalEnabled(view.config.terminal?.enabled ?? !1), setPreviewPorts((view.config.loopback?.ports ?? []).join(", ")), setAcpBackends((view.config.acp?.backends ?? []).map((item) => ({ id: item.id, enabled: item.enabled !== !1 }))), setAcpAvailability(view.acpAvailability ?? {}), setAssociations(view.associations ?? (view.association === void 0 ? {} : { host: view.association })), setWritable(view.writable), setLoaded(!0);
2359
2439
  }, load = async () => {
2360
2440
  let [view, status] = await Promise.all([
2361
2441
  props.control("settings.get"),
@@ -2363,20 +2443,12 @@ Minimum version required to store current data is: ` + bestVersion + `.
2363
2443
  })
2364
2444
  ]);
2365
2445
  applyView(view), setHostStatus(status?.host);
2366
- }, refreshHostStatus = async () => {
2367
- setHostStatus((await props.control("status")).host);
2368
2446
  };
2369
2447
  React.useEffect(() => {
2370
2448
  load().catch((reason) => setError(messageOf(reason)));
2371
2449
  }, []), React.useEffect(() => {
2372
- if (association === void 0) return;
2373
- refreshHostStatus().catch(() => {
2374
- });
2375
- let timer = window.setInterval(() => {
2376
- refreshHostStatus().catch(() => {
2377
- });
2378
- }, 3e4);
2379
- return () => window.clearInterval(timer);
2450
+ if (association !== void 0)
2451
+ return props.statusFeed.subscribe((status) => setHostStatus(status.host));
2380
2452
  }, [association !== void 0]);
2381
2453
  let save = async (event) => {
2382
2454
  if (event?.preventDefault(), !(!writable || !serverDirty)) {
@@ -2433,6 +2505,27 @@ Minimum version required to store current data is: ` + bestVersion + `.
2433
2505
  } finally {
2434
2506
  setCodexBusy(!1);
2435
2507
  }
2508
+ }, setTerminalRemote = async (enabled) => {
2509
+ let previous = terminalEnabled, portsDraft = previewPorts;
2510
+ setTerminalEnabled(enabled), setTerminalBusy(!0), setError(void 0), setNotice(void 0);
2511
+ try {
2512
+ let view = await props.control("settings.development.set", { terminalEnabled: enabled });
2513
+ applyView(view), setPreviewPorts(portsDraft), setNotice({ key: "terminalSaved" });
2514
+ } catch (reason) {
2515
+ setTerminalEnabled(previous), setError(messageOf(reason));
2516
+ } finally {
2517
+ setTerminalBusy(!1);
2518
+ }
2519
+ }, savePreviewPorts = async () => {
2520
+ setPortsBusy(!0), setError(void 0), setNotice(void 0);
2521
+ try {
2522
+ let ports = previewPorts.trim() === "" ? [] : previewPorts.split(/[,,]/).map((value) => Number(value.trim())), view = await props.control("settings.development.set", { ports });
2523
+ applyView(view), setNotice({ key: "developmentSaved" });
2524
+ } catch (reason) {
2525
+ setError(messageOf(reason));
2526
+ } finally {
2527
+ setPortsBusy(!1);
2528
+ }
2436
2529
  }, checkAcp = async (backend) => {
2437
2530
  setAcpChecking((current) => ({ ...current, [backend]: !0 })), setAcpCheckResults((current) => ({ ...current, [backend]: void 0 })), setError(void 0);
2438
2531
  try {
@@ -2475,7 +2568,51 @@ Minimum version required to store current data is: ` + bestVersion + `.
2475
2568
  }
2476
2569
  }, discard = () => {
2477
2570
  settingsView !== void 0 && applyView(settingsView), setRegistrationCode(""), setNotice(void 0), setError(void 0);
2478
- }, codexSetting = React.createElement(
2571
+ }, developmentSetting = React.createElement(
2572
+ "section",
2573
+ { className: "dshRemoteDevelopmentSettings" },
2574
+ React.createElement(
2575
+ "div",
2576
+ { className: "dshRemoteAuthorizationSetting" },
2577
+ React.createElement(
2578
+ "div",
2579
+ null,
2580
+ React.createElement("strong", null, t("terminalRemote")),
2581
+ React.createElement("p", null, t("terminalRemoteHint"))
2582
+ ),
2583
+ React.createElement("input", {
2584
+ type: "checkbox",
2585
+ role: "switch",
2586
+ checked: terminalEnabled,
2587
+ disabled: busy || terminalBusy || !writable,
2588
+ "aria-label": t("terminalRemote"),
2589
+ onChange: (event) => void setTerminalRemote(event.target.checked)
2590
+ })
2591
+ ),
2592
+ React.createElement(
2593
+ "div",
2594
+ { className: "dshRemotePathField" },
2595
+ React.createElement("span", null, t("previewPorts")),
2596
+ React.createElement(
2597
+ "div",
2598
+ { className: "dshRemotePortsRow" },
2599
+ React.createElement("input", {
2600
+ value: previewPorts,
2601
+ maxLength: 128,
2602
+ disabled: !writable,
2603
+ placeholder: "3000, 5173",
2604
+ "aria-label": t("previewPorts"),
2605
+ onChange: (event) => setPreviewPorts(event.target.value)
2606
+ }),
2607
+ React.createElement("button", {
2608
+ type: "button",
2609
+ disabled: portsBusy || terminalBusy || !writable,
2610
+ onClick: () => void savePreviewPorts()
2611
+ }, t("developmentSave"))
2612
+ ),
2613
+ React.createElement("small", null, t("previewPortsHint"))
2614
+ )
2615
+ ), codexSetting = React.createElement(
2479
2616
  "div",
2480
2617
  { className: "dshRemoteAuthorizationSetting" },
2481
2618
  React.createElement(
@@ -2548,9 +2685,9 @@ Minimum version required to store current data is: ` + bestVersion + `.
2548
2685
  } }, t("addAcp"))
2549
2686
  )
2550
2687
  );
2551
- return React.createElement(
2552
- "li",
2553
- { className: `dshRemotePluginCard${open ? " isOpen" : ""}` },
2688
+ return props.view === "summary" ? React.createElement("span", null, t("pluginDescription")) : React.createElement(
2689
+ props.view === "page" ? "div" : "li",
2690
+ { className: `dshRemotePluginCard${open ? " isOpen" : ""}${props.view === "page" ? " dshRemotePluginModern" : ""}` },
2554
2691
  React.createElement(
2555
2692
  "div",
2556
2693
  { className: "dshRemotePluginCardHeader" },
@@ -2609,6 +2746,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
2609
2746
  }),
2610
2747
  React.createElement("p", null, t("serverUrlHint"))
2611
2748
  ),
2749
+ developmentSetting,
2612
2750
  codexSetting,
2613
2751
  acpSetting,
2614
2752
  React.createElement(
@@ -2659,7 +2797,10 @@ Minimum version required to store current data is: ` + bestVersion + `.
2659
2797
  React.createElement(
2660
2798
  "div",
2661
2799
  { className: "dshRemoteSettingsFooter" },
2662
- error !== void 0 ? React.createElement("p", { className: "dshRemoteError", role: "alert" }, error) : notice === void 0 ? null : React.createElement("p", { className: "dshRemoteNotice", role: "status" }, t(notice.key, notice.params)),
2800
+ React.createElement("p", {
2801
+ className: error !== void 0 ? "dshRemoteError" : "dshRemoteNotice",
2802
+ role: error !== void 0 ? "alert" : "status"
2803
+ }, error ?? (notice === void 0 ? "" : t(notice.key, notice.params))),
2663
2804
  draftDirty ? React.createElement(
2664
2805
  React.Fragment,
2665
2806
  null,
@@ -2692,6 +2833,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
2692
2833
  }),
2693
2834
  React.createElement("p", null, t("serverUrlHint"))
2694
2835
  ),
2836
+ developmentSetting,
2695
2837
  codexSetting,
2696
2838
  acpSetting,
2697
2839
  React.createElement("p", { className: "dshRemoteSettingsState" }, t("authorizeFromRemote")),
@@ -2699,7 +2841,10 @@ Minimum version required to store current data is: ` + bestVersion + `.
2699
2841
  React.createElement(
2700
2842
  "div",
2701
2843
  { className: "dshRemoteSettingsFooter" },
2702
- error !== void 0 ? React.createElement("p", { className: "dshRemoteError", role: "alert" }, error) : notice === void 0 ? null : React.createElement("p", { className: "dshRemoteNotice", role: "status" }, t(notice.key, notice.params)),
2844
+ React.createElement("p", {
2845
+ className: error !== void 0 ? "dshRemoteError" : "dshRemoteNotice",
2846
+ role: error !== void 0 ? "alert" : "status"
2847
+ }, error ?? (notice === void 0 ? "" : t(notice.key, notice.params))),
2703
2848
  React.createElement("button", { type: "button", className: "dshRemoteDiscard", disabled: busy || !draftDirty, onClick: discard }, t("discard")),
2704
2849
  React.createElement("button", { type: "submit", className: "dshRemoteSave", disabled: busy || !writable || !serverDirty }, t(busy ? "saving" : "save"))
2705
2850
  )
@@ -2708,7 +2853,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
2708
2853
  );
2709
2854
  }
2710
2855
  function RemoteWorkspaceAction(props) {
2711
- let { t } = props, [open, setOpen] = React.useState(!1), [status, setStatus] = React.useState(void 0), [devices, setDevices] = React.useState([]), [selectedHost, setSelectedHost] = React.useState(void 0), [workspaces, setWorkspaces] = React.useState([]), [codexWorkspaces, setCodexWorkspaces] = React.useState([]), [workspaceBackend, setWorkspaceBackend] = React.useState("harness"), [codexWorkspaceId, setCodexWorkspaceId] = React.useState(void 0), [directory, setDirectory] = React.useState(void 0), [path, setPath] = React.useState(""), [addingWorkspace, setAddingWorkspace] = React.useState(!1), [showAllWorkspaces, setShowAllWorkspaces] = React.useState(!1), [showAllCodexWorkspaces, setShowAllCodexWorkspaces] = React.useState(!1), [devicesOpen, setDevicesOpen] = React.useState(!1), workspaceListId = "dsh-remote-workspace-list", codexWorkspaceHeadingId = "dsh-remote-codex-workspace-heading", codexWorkspaceListId = "dsh-remote-codex-workspace-list", [busy, setBusy] = React.useState(!1), [needsAuthorization, setNeedsAuthorization] = React.useState(!1), [email, setEmail] = React.useState(""), [password, setPassword] = React.useState(""), [loginMethod, setLoginMethod] = React.useState(props.preferredQrProvider), [loginMethodManuallySelected, setLoginMethodManuallySelected] = React.useState(!1), [qrSession, setQrSession] = React.useState(void 0), [qrImage, setQrImage] = React.useState(void 0), [qrExpired, setQrExpired] = React.useState(!1), [progress, setProgress] = React.useState(void 0), progressRun = React.useRef(0), qrFlowRun = React.useRef(0), [notice, setNotice] = React.useState(void 0), [error, setError] = React.useState(void 0);
2856
+ let { t } = props, [open, setOpen] = React.useState(!1), [status, setStatus] = React.useState(void 0), [devices, setDevices] = React.useState([]), [selectedHost, setSelectedHost] = React.useState(void 0), [workspaces, setWorkspaces] = React.useState([]), [codexWorkspaces, setCodexWorkspaces] = React.useState([]), [workspaceBackend, setWorkspaceBackend] = React.useState("harness"), [codexWorkspaceId, setCodexWorkspaceId] = React.useState(void 0), [directory, setDirectory] = React.useState(void 0), [path, setPath] = React.useState(""), [addingWorkspace, setAddingWorkspace] = React.useState(!1), [showAllWorkspaces, setShowAllWorkspaces] = React.useState(!1), [showAllCodexWorkspaces, setShowAllCodexWorkspaces] = React.useState(!1), [devicesOpen, setDevicesOpen] = React.useState(!1), workspaceListId = "dsh-remote-workspace-list", codexWorkspaceHeadingId = "dsh-remote-codex-workspace-heading", codexWorkspaceListId = "dsh-remote-codex-workspace-list", [busy, setBusy] = React.useState(!1), [needsAuthorization, setNeedsAuthorization] = React.useState(!1), [email, setEmail] = React.useState(""), [password, setPassword] = React.useState(""), [loginServerUrl, setLoginServerUrl] = React.useState("https://dsh.r2049.cn"), [loginMethod, setLoginMethod] = React.useState(props.preferredQrProvider), [loginMethodManuallySelected, setLoginMethodManuallySelected] = React.useState(!1), [qrSession, setQrSession] = React.useState(void 0), [qrImage, setQrImage] = React.useState(void 0), [qrExpired, setQrExpired] = React.useState(!1), [progress, setProgress] = React.useState(void 0), progressRun = React.useRef(0), qrFlowRun = React.useRef(0), [notice, setNotice] = React.useState(void 0), [error, setError] = React.useState(void 0);
2712
2857
  React.useEffect(() => {
2713
2858
  open || setDevicesOpen(!1);
2714
2859
  }, [open]), React.useEffect(() => {
@@ -2736,6 +2881,8 @@ Minimum version required to store current data is: ` + bestVersion + `.
2736
2881
  props.control("status").then(setStatus).catch(() => {
2737
2882
  });
2738
2883
  }, []), React.useEffect(() => {
2884
+ status?.serverUrl !== void 0 && loginServerUrl === "https://dsh.r2049.cn" && setLoginServerUrl(status.serverUrl);
2885
+ }, [status?.serverUrl]), React.useEffect(() => {
2739
2886
  let remoteActive = status?.mode === "remote";
2740
2887
  return document.documentElement.classList.toggle("dshRemoteTargetActive", remoteActive), () => {
2741
2888
  remoteActive && document.documentElement.classList.remove("dshRemoteTargetActive");
@@ -2875,20 +3022,21 @@ Minimum version required to store current data is: ` + bestVersion + `.
2875
3022
  setShowAllWorkspaces(!1), setShowAllCodexWorkspaces(!1), setOpen(!0), await refreshRemote();
2876
3023
  };
2877
3024
  React.useEffect(() => {
2878
- if (!open || selectedHost !== void 0) return;
2879
- let timer = window.setInterval(() => {
2880
- props.control("status").then(setStatus).catch(() => {
2881
- });
2882
- }, 1500);
2883
- return () => window.clearInterval(timer);
3025
+ if (!(!open || selectedHost !== void 0))
3026
+ return props.statusFeed.subscribe(setStatus);
2884
3027
  }, [open, selectedHost]);
2885
3028
  let chooseAnotherHost = () => {
2886
3029
  setSelectedHost(void 0), setWorkspaces([]), setCodexWorkspaces([]), setShowAllWorkspaces(!1), setShowAllCodexWorkspaces(!1), setWorkspaceBackend("harness"), setCodexWorkspaceId(void 0), setDirectory(void 0), setPath(""), setAddingWorkspace(!1), setError(void 0);
2887
3030
  }, signInClient = async () => {
2888
- if (!(email.trim() === "" || password === "")) {
3031
+ if (!(email.trim() === "" || password === "" || loginServerUrl.trim() === "")) {
2889
3032
  setBusy(!0), setError(void 0);
2890
3033
  try {
2891
- await props.control("client.account.login", { email: email.trim(), password }), setDevices(await props.control("devices")), setStatus(await props.control("status")), setNeedsAuthorization(!1), setPassword("");
3034
+ await props.control("settings.configure", {
3035
+ role: "client",
3036
+ serverUrl: loginServerUrl.trim(),
3037
+ email: email.trim(),
3038
+ password
3039
+ }), setDevices(await props.control("devices")), setStatus(await props.control("status")), setNeedsAuthorization(!1), setPassword("");
2892
3040
  } catch (reason) {
2893
3041
  setError(messageOf(reason));
2894
3042
  } finally {
@@ -3160,6 +3308,15 @@ Minimum version required to store current data is: ` + bestVersion + `.
3160
3308
  id: "dsh-remote-password-panel",
3161
3309
  "aria-labelledby": "dsh-remote-password-tab"
3162
3310
  },
3311
+ React.createElement("input", {
3312
+ type: "url",
3313
+ value: loginServerUrl,
3314
+ disabled: busy,
3315
+ autoComplete: "url",
3316
+ placeholder: t("serverUrl"),
3317
+ "aria-label": t("serverUrl"),
3318
+ onChange: (event) => setLoginServerUrl(event.target.value)
3319
+ }),
3163
3320
  React.createElement("input", {
3164
3321
  type: "email",
3165
3322
  value: email,
@@ -3178,7 +3335,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
3178
3335
  "aria-label": t("password"),
3179
3336
  onChange: (event) => setPassword(event.target.value)
3180
3337
  }),
3181
- React.createElement("button", { type: "button", disabled: busy || email.trim() === "" || password === "", onClick: () => void signInClient() }, t(busy ? "signingIn" : "startSignIn"))
3338
+ React.createElement("button", { type: "button", disabled: busy || loginServerUrl.trim() === "" || email.trim() === "" || password === "", onClick: () => void signInClient() }, t(busy ? "signingIn" : "startSignIn"))
3182
3339
  )
3183
3340
  ) : null,
3184
3341
  needsAuthorization ? null : React.createElement(
@@ -3421,12 +3578,8 @@ Minimum version required to store current data is: ` + bestVersion + `.
3421
3578
  setError(messageOf(reason)), setSupported(!1);
3422
3579
  });
3423
3580
  }, []), React.useEffect(() => {
3424
- if (!open) return;
3425
- refreshStatus();
3426
- let timer = window.setInterval(() => {
3427
- refreshStatus();
3428
- }, 1500);
3429
- return () => window.clearInterval(timer);
3581
+ if (open)
3582
+ return props.statusFeed.subscribe(setStatus);
3430
3583
  }, [open]);
3431
3584
  let switchMode = async (mode, targetDeviceId) => {
3432
3585
  setBusy(!0), setError(void 0);
@@ -3559,20 +3712,12 @@ Minimum version required to store current data is: ` + bestVersion + `.
3559
3712
  ) : null;
3560
3713
  }
3561
3714
  function RemoteSessionHeaderAction(props) {
3562
- let { t } = props, [status, setStatus] = React.useState(void 0), [busy, setBusy] = React.useState(!1), [routeOpen, setRouteOpen] = React.useState(!1);
3715
+ let { t } = props, status = React.useSyncExternalStore(
3716
+ props.statusFeed.subscribe,
3717
+ props.statusFeed.getSnapshot,
3718
+ props.statusFeed.getSnapshot
3719
+ ), [busy, setBusy] = React.useState(!1), [routeOpen, setRouteOpen] = React.useState(!1);
3563
3720
  if (React.useEffect(() => {
3564
- let active = !0, refresh = () => {
3565
- props.control("status").then((next) => {
3566
- active && setStatus(next);
3567
- }).catch(() => {
3568
- });
3569
- };
3570
- refresh();
3571
- let timer = window.setInterval(refresh, 1500);
3572
- return () => {
3573
- active = !1, window.clearInterval(timer);
3574
- };
3575
- }, []), React.useEffect(() => {
3576
3721
  if (status?.mode === "remote")
3577
3722
  return hideLocalSessionActions();
3578
3723
  }, [status?.mode]), React.useEffect(() => (document.documentElement.classList.toggle(
@@ -3742,6 +3887,16 @@ Minimum version required to store current data is: ` + bestVersion + `.
3742
3887
  'html.dshRemoteTargetActive button[aria-label="\u6DFB\u52A0\u5DE5\u4F5C\u533A"],html.dshRemoteTargetActive button[aria-label="Add workspace"]{display:none!important}',
3743
3888
  'html.dshRemoteCodexTargetActive [data-composer-card] button[aria-haspopup="listbox"][aria-label="\u6307\u4EE4"],html.dshRemoteCodexTargetActive [data-composer-card] button[aria-haspopup="listbox"][aria-label="Commands"]{display:none!important}',
3744
3889
  "[data-dsh-remote-hidden-action]{display:none!important}",
3890
+ // The 0.1.6 right sidebar occupies the same shell header area as the
3891
+ // remote status strip. Hide the strip while that sidebar is expanded;
3892
+ // the sidebar itself remains the active context for terminal/files.
3893
+ ":has([data-sidebar-right-open]) .dshRemoteSessionHeader{display:none!important}",
3894
+ // TerminalRecovery renders a retry action in the conversation header.
3895
+ // A failed remote terminal is reported when its tab is opened/closed;
3896
+ // keeping this background recovery action out of the top-left header
3897
+ // avoids overlapping the session controls.
3898
+ '[title^="\u6062\u590D\u7EC8\u7AEF\u5931\u8D25"],[title^="Terminal recovery failed"]{display:none!important}',
3899
+ ".dshRemoteTerminalUnavailable{box-sizing:border-box;margin:12px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);font-size:13px;line-height:1.5}",
3745
3900
  ".dshRemoteModeButton{min-height:36px;border:0;background:transparent;color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:8px;padding:0 10px;border-radius:8px}.dshRemoteModeButton:is(button){cursor:pointer}",
3746
3901
  ".dshRemoteModeButton:is(button):hover{background:var(--dsw-alias-interactive-bg-hover)}",
3747
3902
  ".dshRemoteSidebarEntry{box-sizing:border-box;position:relative;min-width:0;display:block;overflow:hidden}.dshRemoteSidebarEntry .dshRemoteModeButton{box-sizing:border-box;width:100%;min-width:0}.dshRemoteSidebarEntry.isWide{width:calc(100% + 8px);height:34px;margin:4px -4px}.dshRemoteSidebarEntry.isWide .dshRemoteModeButton{height:34px;min-height:34px;padding:6px 48px 6px 10px;border-radius:12px}.dshRemoteSidebarEntry.isRail{width:36px;height:54px}.dshRemoteSidebarEntry.isRail .dshRemoteModeButton{width:36px;height:36px;min-height:36px;justify-content:center;gap:0;margin:8px 0 10px;padding:0;border-radius:50%}.dshRemoteSidebarEntry.isActive .dshRemoteModeButton{color:var(--dsw-alias-label-secondary);background:transparent}.dshRemoteSidebarLabel{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dshRemoteExitLink{position:absolute;top:50%;right:10px;transform:translateY(-50%);white-space:nowrap;border:0;background:transparent;color:var(--dsw-alias-label-secondary);padding:0;font:inherit;font-size:12px;line-height:20px;cursor:pointer}.dshRemoteExitLink:hover{color:var(--dsw-alias-label-primary);text-decoration:underline}.dshRemoteExitLink:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;border-radius:2px}.dshRemoteExitLink:disabled{opacity:.45;cursor:default;text-decoration:none}",
@@ -3769,6 +3924,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
3769
3924
  ".dshRemoteWorkspaceLists{overflow:visible}",
3770
3925
  ".dshRemoteDirectoryList{min-height:72px;display:flex;flex-direction:column;border-top:1px solid var(--dsw-alias-border-l2)}.dshRemoteDirectoryList>button{min-height:52px;display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:10px;text-align:left;border:0;border-bottom:1px solid var(--dsw-alias-border-l2);background:transparent;padding:8px 4px;cursor:pointer}.dshRemoteDirectoryList>button:hover,.dshRemoteDirectoryList>button.isSelected{background:var(--dsw-alias-interactive-bg-hover)}.dshRemoteDirectoryList>button.isSelected{color:var(--dsw-alias-label-primary)}.dshRemoteDirectoryList>button>span:first-child,.dshRemoteDirectoryList>button>.dshRemoteWorkspaceIcon{grid-row:1/3}.dshRemoteWorkspaceIcon{box-sizing:border-box;width:22px;height:22px;align-self:center;object-fit:contain}.dshRemoteWorkspaceIcon.isGpt{border-radius:6px}.dshRemoteDirectoryList>button>span:not(:first-child),.dshRemoteDirectoryList>button>small{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dshRemoteDirectoryList>button>small{grid-column:2;color:var(--dsw-alias-label-secondary)}.dshRemoteDirectoryList>p,.dshRemoteHint{margin:12px 0;color:var(--dsw-alias-label-secondary);font-size:13px}",
3771
3926
  ".dshRemoteAddWorkspace{box-sizing:border-box;width:40px;height:40px;display:inline-grid;place-items:center;flex:0 0 auto;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary);padding:0;cursor:pointer}.dshRemoteAddWorkspace:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.dshRemoteAddWorkspace:disabled{opacity:.5;cursor:default}.dshRemoteAddWorkspaceIcon{width:20px;height:20px}.dshRemoteCodexWorkspaceGroup{margin-top:16px}.dshRemoteWorkspaceSourceHeading{min-height:44px;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 4px 7px}.dshRemoteWorkspaceSourceText{min-width:0;display:flex;flex-direction:column;gap:2px}.dshRemoteWorkspaceSourceText>strong{font-size:13px}.dshRemoteWorkspaceSourceText>small{color:var(--dsw-alias-label-secondary);font-size:11px}.dshRemoteCodexWorkspaceList{min-height:0}.dshRemoteDirectoryList>.dshRemoteWorkspaceMore,.dshRemoteCodexWorkspaceGroup>.dshRemoteWorkspaceMore{box-sizing:border-box;width:100%;min-height:48px;display:flex;align-items:center;justify-content:center;border:0;border-bottom:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-secondary);padding:8px 4px;text-align:center;font-size:16px;cursor:pointer}.dshRemoteWorkspaceMore>span{display:block;line-height:1;transform:translateY(-2px)}.dshRemoteWorkspaceMore:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:transparent}.dshRemoteWorkspaceMore:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.dshRemoteWorkspaceMore:disabled{opacity:.5;cursor:default}",
3927
+ ".dshRemotePreviewAction,.dshRemotePreviewForm{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap}.dshRemotePreviewForm input{width:90px;color:inherit;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);padding:6px}.dshRemotePreviewForm [role=alert]{max-width:360px;white-space:normal}.dshRemoteDevelopmentSettings{margin:16px 0}.dshRemoteDevelopmentSettings .dshRemotePathField{margin:8px 0}.dshRemoteDevelopmentSettings .dshRemotePortsRow{display:flex;gap:8px;align-items:stretch}.dshRemoteDevelopmentSettings .dshRemotePortsRow>input{flex:1;min-width:0;min-height:40px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-3);color:inherit;padding:0 12px;font:inherit}.dshRemoteDevelopmentSettings button{min-height:36px;color:inherit;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:6px 12px;white-space:nowrap}",
3772
3928
  ".dshRemoteFolderBrowser{margin-top:14px}.dshRemoteFolderBrowser>p,.dshRemoteFolderList>p{margin:12px 0;color:var(--dsw-alias-label-secondary);font-size:13px}.dshRemoteFolderList{max-height:260px;overflow:auto;border-block:1px solid var(--dsw-alias-border-l2)}.dshRemoteFolderList>button{width:100%;min-height:42px;display:flex;align-items:center;gap:9px;border:0;border-bottom:1px solid var(--dsw-alias-border-l2);background:transparent;padding:7px 6px;text-align:left;cursor:pointer}.dshRemoteFolderList>button:hover{background:var(--dsw-alias-interactive-bg-hover)}.dshRemoteFolderBrowser>small{display:block;margin-top:8px;color:var(--dsw-alias-state-warn-label)}",
3773
3929
  ".dshRemotePathField{display:flex;flex-direction:column;gap:6px;margin-top:20px}.dshRemotePathField>span{font-size:13px;font-weight:600}.dshRemotePathField>input{min-height:40px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-3);color:inherit;padding:0 12px;font:inherit}.dshRemotePathField>small{color:var(--dsw-alias-label-secondary)}",
3774
3930
  ".dshRemoteOpenBar{position:sticky;bottom:-96px;display:flex;align-items:center;justify-content:space-between;gap:20px;margin-top:20px;padding:14px 0;background:var(--dsw-alias-bg-layer-1);border-top:1px solid var(--dsw-alias-border-l2)}.dshRemoteOpenBar>div{min-width:0;display:flex;flex-direction:column;gap:3px}.dshRemoteOpenBar span{color:var(--dsw-alias-label-secondary);font-size:12px}.dshRemoteOpenBar strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px}.dshRemoteOpenBar>button,.dshRemoteEnable>button{min-height:40px;flex:0 0 auto;border:0;border-radius:8px;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-1);padding:8px 16px;cursor:pointer}.dshRemoteOpenBar>button:disabled,.dshRemoteEnable>button:disabled{opacity:.5;cursor:default}",
@@ -3791,6 +3947,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
3791
3947
  ".dshRemoteHostAccount{display:grid;gap:8px;border-top:1px solid var(--dsw-alias-border-l3);padding-top:12px}.dshRemoteHostAccount p{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px}",
3792
3948
  ".dshRemoteLogin{display:grid;grid-template-columns:1fr 1fr;gap:8px}.dshRemoteLogin button{grid-column:1/-1}",
3793
3949
  ".dshRemotePluginCard{list-style:none;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;transition:border-color .16s,background .16s}",
3950
+ ".dshRemotePluginModern{box-sizing:border-box;width:100%;max-width:768px;box-shadow:none}.dshRemotePluginModern>.dshRemotePluginCardHeader{display:none}.dshRemotePluginModern>.dshRemotePluginCardBody{border-top:0;margin:0;padding:8px 20px}",
3794
3951
  ".dshRemotePluginCard:hover{border-color:var(--dsw-alias-label-dimmed)}.dshRemotePluginCard.isOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}",
3795
3952
  ".dshRemotePluginCardHeader{display:flex;align-items:center}.dshRemotePluginCardToggle{appearance:none;width:100%;min-width:0;font:inherit;color:inherit;text-align:left;cursor:pointer;background:transparent;border:0;border-radius:12px;display:flex;align-items:center;gap:12px;padding:14px 16px}.dshRemotePluginCardToggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}",
3796
3953
  ".dshRemotePluginCardHeading{display:flex;flex-direction:column;gap:4px;min-width:0;flex:1}.dshRemotePluginCardHeading>strong{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.dshRemotePluginCardHeading>span{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.dshRemotePluginCardStatus{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.dshRemotePluginCardStatus.isOnline{color:var(--dsw-alias-state-success-primary)}.dshRemotePluginCardStatus.isReconnecting{color:var(--dsw-alias-state-warn-label)}.dshRemotePluginCardStatus.isOffline{color:var(--dsw-alias-state-error-primary)}.dshRemotePluginCardChevron{color:var(--dsw-alias-label-tertiary);font-size:18px;line-height:14px;transition:transform .16s}.dshRemotePluginCard.isOpen .dshRemotePluginCardChevron{transform:rotate(180deg)}",
@@ -3822,8 +3979,14 @@ Minimum version required to store current data is: ` + bestVersion + `.
3822
3979
  }
3823
3980
  if (!result.ok) throw new Error(result.error?.message ?? t("remoteRequestFailed"));
3824
3981
  return result.value;
3825
- };
3826
- ctx.effect(() => {
3982
+ }, statusFeed = createStatusFeed({
3983
+ url: STATUS_STREAM_PATH,
3984
+ readStatus: () => control("status"),
3985
+ onFallback: (reason) => {
3986
+ console.warn("ds-harness-remote: status event stream unavailable, polling status instead:", reason);
3987
+ }
3988
+ });
3989
+ ctx.effect(() => () => statusFeed.close(), "ds-harness-remote: status stream"), ctx.effect(() => {
3827
3990
  let disposed = !1, unsubscribeWorkspaces, unsubscribeSessions, selection, opening = !1, reconcile = () => {
3828
3991
  if (disposed || opening || selection === void 0) return;
3829
3992
  let pending = selection, workspaceSnapshot = ctx.workspaces.list.getSnapshot();
@@ -3847,32 +4010,23 @@ Minimum version required to store current data is: ` + bestVersion + `.
3847
4010
  }, "ds-harness-remote: resume selected workspace"), ctx.inject(["fileViewer"], (fileViewerContext) => {
3848
4011
  let viewer = fileViewerContext.get("fileViewer");
3849
4012
  viewer !== void 0 && fileViewerContext.effect(() => {
3850
- let active = !0, unregister, latestSaveAsAllowed = !1, latestSaveAsMaxBytes = REMOTE_FILE_SAVE_AS_MAX_BYTES, sync = async () => {
3851
- try {
3852
- let status = await control("status");
3853
- if (!active) return;
3854
- let supported = shouldUseRemoteFileViewer(status);
3855
- latestSaveAsAllowed = shouldAllowRemoteFileSaveAs(status), latestSaveAsMaxBytes = remoteFileSaveAsMaxBytes(status), supported && unregister === void 0 ? unregister = viewer.registerContentProvider(createRemoteFileContentProvider(
3856
- (endpoint, payload) => control(endpoint, payload),
3857
- { saveAsAllowed: () => latestSaveAsAllowed, saveAsMaxBytes: () => latestSaveAsMaxBytes }
3858
- )) : !supported && unregister !== void 0 && (unregister(), unregister = void 0, latestSaveAsAllowed = !1, latestSaveAsMaxBytes = REMOTE_FILE_SAVE_AS_MAX_BYTES);
3859
- } catch {
3860
- }
3861
- };
3862
- sync();
3863
- let timer = window.setInterval(() => {
3864
- sync();
3865
- }, 1500);
4013
+ let unregister, latestSaveAsAllowed = !1, latestSaveAsMaxBytes = REMOTE_FILE_SAVE_AS_MAX_BYTES, unsubscribe = statusFeed.subscribe((status) => {
4014
+ let supported = shouldUseRemoteFileViewer(status);
4015
+ latestSaveAsAllowed = shouldAllowRemoteFileSaveAs(status), latestSaveAsMaxBytes = remoteFileSaveAsMaxBytes(status), supported && unregister === void 0 ? unregister = viewer.registerContentProvider(createRemoteFileContentProvider(
4016
+ (endpoint, payload) => control(endpoint, payload),
4017
+ { saveAsAllowed: () => latestSaveAsAllowed, saveAsMaxBytes: () => latestSaveAsMaxBytes }
4018
+ )) : !supported && unregister !== void 0 && (unregister(), unregister = void 0, latestSaveAsAllowed = !1, latestSaveAsMaxBytes = REMOTE_FILE_SAVE_AS_MAX_BYTES);
4019
+ });
3866
4020
  return () => {
3867
- active = !1, window.clearInterval(timer), unregister?.();
4021
+ unsubscribe(), unregister?.();
3868
4022
  };
3869
4023
  }, "ds-harness-remote: remote file viewer provider");
3870
- }), ctx.effect(() => ctx.locale.register(localeNamespace, { zh, en }), "ds-harness-remote: dictionaries"), ctx.effect(installStyle, "ds-harness-remote: client styles"), ctx.slots.inject("shell.overlay", () => ctx.slots.register({
4024
+ }), ctx.effect(() => ctx.locale.register(localeNamespace, { zh, en }), "ds-harness-remote: dictionaries"), ctx.effect(installStyle, "ds-harness-remote: client styles"), ctx.effect(() => installTerminalAvailabilityNotice(statusFeed, t), "ds-harness-remote: terminal availability notice"), ctx.slots.inject("shell.overlay", () => ctx.slots.register({
3871
4025
  name: "shell.overlay",
3872
4026
  id: "ds-harness-remote-global-context",
3873
4027
  order: 20,
3874
4028
  locale: localeNamespace,
3875
- inject: () => ({ control })
4029
+ inject: () => ({ control, statusFeed })
3876
4030
  }, RemoteSessionHeaderAction)), ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
3877
4031
  name: "sidebar.footer.action",
3878
4032
  id: "ds-harness-remote-workspace",
@@ -3880,20 +4034,49 @@ Minimum version required to store current data is: ` + bestVersion + `.
3880
4034
  locale: localeNamespace,
3881
4035
  inject: () => ({
3882
4036
  control,
4037
+ statusFeed,
3883
4038
  preferredQrProvider: ctx.locale.getLocale().active === "zh" ? "zhihu" : "github"
3884
4039
  })
3885
- }, RemoteWorkspaceAction)), ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
3886
- name: "settings.plugin.item",
4040
+ }, RemoteWorkspaceAction));
4041
+ let registerSettingsItem = (slotName) => {
4042
+ try {
4043
+ ctx.slots.inject(slotName, () => ctx.slots.register({
4044
+ name: slotName,
4045
+ label: () => t("pluginTitle"),
4046
+ key: "ds-harness-remote",
4047
+ id: "ds-harness-remote",
4048
+ order: 30,
4049
+ locale: localeNamespace,
4050
+ inject: () => ({ control, statusFeed })
4051
+ }, RemotePluginOptions));
4052
+ } catch {
4053
+ }
4054
+ };
4055
+ registerSettingsItem("plugins.item"), registerSettingsItem("settings.plugin.item"), ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
4056
+ name: "plugins.bundle.config",
3887
4057
  key: "ds-harness-remote",
3888
- id: "ds-harness-remote",
3889
- order: 30,
3890
4058
  locale: localeNamespace,
3891
- inject: () => ({ control })
4059
+ inject: () => ({ control, statusFeed })
3892
4060
  }, RemotePluginOptions));
3893
4061
  }
3894
4062
  function isMissingControlRoute(reason) {
3895
4063
  return reason instanceof Error && reason.message.startsWith(`transport failure for ${CONTROL_RPC_PREFIX}/`) && (reason.message.endsWith(": HTTP 404") || reason.message.endsWith(": HTTP 405"));
3896
4064
  }
4065
+ function installTerminalAvailabilityNotice(feed, t) {
4066
+ let latest, notice, update = () => {
4067
+ let terminal = document.querySelector("[data-sidebar-terminal]"), unavailable = latest?.mode === "remote" && latest.remoteFeatures?.terminal !== !0;
4068
+ if (!terminal || !unavailable) {
4069
+ notice?.remove(), notice = void 0;
4070
+ return;
4071
+ }
4072
+ notice !== void 0 && notice.isConnected || (notice = document.createElement("div"), notice.className = "dshRemoteTerminalUnavailable", notice.setAttribute("role", "alert"), notice.textContent = t("terminalRemoteUnavailable"), terminal.prepend(notice));
4073
+ }, unsubscribe = feed.subscribe((status) => {
4074
+ latest = status, update();
4075
+ }), observer = new MutationObserver(update);
4076
+ return observer.observe(document.body, { childList: !0, subtree: !0 }), update(), () => {
4077
+ unsubscribe(), observer.disconnect(), notice?.remove();
4078
+ };
4079
+ }
3897
4080
  function messageOf(reason) {
3898
4081
  return reason instanceof Error ? reason.message : String(reason);
3899
4082
  }