@blue-soda/dsh-remote 0.4.29 → 0.4.30

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 (40) hide show
  1. package/dist/client-runtime.d.ts +137 -1
  2. package/dist/client-runtime.d.ts.map +1 -1
  3. package/dist/client-runtime.js +399 -22
  4. package/dist/client-runtime.js.map +1 -1
  5. package/dist/client-secure-transport.d.ts.map +1 -1
  6. package/dist/client-secure-transport.js +2 -1
  7. package/dist/client-secure-transport.js.map +1 -1
  8. package/dist/client-target-store.d.ts +40 -0
  9. package/dist/client-target-store.d.ts.map +1 -0
  10. package/dist/client-target-store.js +79 -0
  11. package/dist/client-target-store.js.map +1 -0
  12. package/dist/client.github.js +63 -15
  13. package/dist/client.js +63 -15
  14. package/dist/codex/domain.d.ts +9 -0
  15. package/dist/codex/domain.d.ts.map +1 -1
  16. package/dist/codex/domain.js +48 -7
  17. package/dist/codex/domain.js.map +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +662 -162
  20. package/dist/index.js.map +4 -4
  21. package/dist/remote-typert-gateway.d.ts.map +1 -1
  22. package/dist/remote-typert-gateway.js +5 -3
  23. package/dist/remote-typert-gateway.js.map +1 -1
  24. package/dist/rpc-binary-attachments.d.ts +17 -0
  25. package/dist/rpc-binary-attachments.d.ts.map +1 -0
  26. package/dist/rpc-binary-attachments.js +89 -0
  27. package/dist/rpc-binary-attachments.js.map +1 -0
  28. package/dist/service.d.ts +10 -0
  29. package/dist/service.d.ts.map +1 -1
  30. package/dist/service.js +17 -1
  31. package/dist/service.js.map +1 -1
  32. package/dist/tui-command.js +7 -5
  33. package/dist/tui-command.js.map +1 -1
  34. package/dist/typert-gateway-switch.d.ts.map +1 -1
  35. package/dist/typert-gateway-switch.js +56 -4
  36. package/dist/typert-gateway-switch.js.map +1 -1
  37. package/dist/version.d.ts +1 -1
  38. package/dist/version.js +1 -1
  39. package/dsh-plugin.json +1 -1
  40. package/package.json +1 -1
@@ -1933,6 +1933,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
1933
1933
  noRemoteWorkspaces: "No remote workspaces yet. Use + to add one.",
1934
1934
  activeRemote: "{name}",
1935
1935
  exitRemote: "Exit",
1936
+ stopReconnecting: "Stop reconnecting",
1936
1937
  addRemoteWorkspace: "Add remote workspace",
1937
1938
  addCodexWorkspace: "Add CodeX workspace",
1938
1939
  noCodexWorkspaces: "No CodeX workspaces yet.",
@@ -2188,6 +2189,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
2188
2189
  noRemoteWorkspaces: "\u8FD9\u53F0\u4E3B\u673A\u8FD8\u6CA1\u6709\u5DE5\u4F5C\u533A\uFF0C\u70B9\u51FB + \u6DFB\u52A0\u3002",
2189
2190
  activeRemote: "{name}",
2190
2191
  exitRemote: "\u9000\u51FA",
2192
+ stopReconnecting: "\u505C\u6B62\u91CD\u8FDE",
2191
2193
  addRemoteWorkspace: "\u6DFB\u52A0\u8FDC\u7A0B\u5DE5\u4F5C\u533A",
2192
2194
  addCodexWorkspace: "\u6DFB\u52A0 CodeX \u5DE5\u4F5C\u533A",
2193
2195
  noCodexWorkspaces: "\u8FD8\u6CA1\u6709 CodeX \u5DE5\u4F5C\u533A\u3002",
@@ -3017,6 +3019,14 @@ Minimum version required to store current data is: ` + bestVersion + `.
3017
3019
  }, [status?.host?.connectedClients?.length]), React.useEffect(() => {
3018
3020
  props.control("status").then(setStatus).catch(() => {
3019
3021
  });
3022
+ }, []), React.useEffect(() => {
3023
+ let verify = () => {
3024
+ document.visibilityState === "visible" && props.control("client.connection.verify").then(setStatus).catch(() => {
3025
+ });
3026
+ };
3027
+ return document.addEventListener("visibilitychange", verify), window.addEventListener("focus", verify), () => {
3028
+ document.removeEventListener("visibilitychange", verify), window.removeEventListener("focus", verify);
3029
+ };
3020
3030
  }, []), React.useEffect(() => {
3021
3031
  status?.serverUrl !== void 0 && loginServerUrl === DEFAULT_REMOTE_SERVER_URL && setLoginServerUrl(status.serverUrl);
3022
3032
  }, [status?.serverUrl]), React.useEffect(() => {
@@ -3337,7 +3347,11 @@ Minimum version required to store current data is: ` + bestVersion + `.
3337
3347
  },
3338
3348
  React.createElement("rect", { x: 3, y: 4, width: 18, height: 13, rx: 2 }),
3339
3349
  React.createElement("path", { d: "M8 21h8M12 17v4" })
3340
- ), props.wide ? React.createElement("span", { className: "dshRemoteSidebarLabel" }, remoteLabel) : null),
3350
+ ), props.wide ? React.createElement(
3351
+ "span",
3352
+ { className: "dshRemoteSidebarLabel" },
3353
+ remoteLabel
3354
+ ) : null),
3341
3355
  (status?.mode === "remote" || status?.fellBackToLocal === !0) && props.wide ? React.createElement("button", {
3342
3356
  type: "button",
3343
3357
  className: "dshRemoteExitLink",
@@ -3883,8 +3897,8 @@ Minimum version required to store current data is: ` + bestVersion + `.
3883
3897
  event.key === "Escape" && setRouteOpen(!1);
3884
3898
  };
3885
3899
  return document.addEventListener("keydown", closeOnEscape), () => document.removeEventListener("keydown", closeOnEscape);
3886
- }, [routeOpen]), status?.mode !== "remote") return null;
3887
- let transport = status.network?.webRtc?.mode ?? status.transport ?? "Disconnected", networkLabel = t(transport === "P2P" ? "remoteNetworkP2p" : transport === "TURN" ? "remoteNetworkTurn" : transport === "Relay" ? "remoteNetworkRelay" : transport === "LAN" ? "remoteNetworkLan" : "remoteNetworkOffline"), networkOnline = status.connected === !0 && transport !== "Disconnected", routeVia = t(transport === "P2P" ? "connectionRouteP2p" : transport === "TURN" ? "connectionRouteTurn" : transport === "Relay" ? "connectionRouteRelay" : "connectionRouteLan"), routeViaDetail = t(transport === "P2P" ? "connectionRouteP2pDetail" : transport === "TURN" ? "connectionRouteTurnDetail" : transport === "Relay" ? "connectionRouteRelayDetail" : "connectionRouteLanDetail"), network = status.network, webRtc = network?.webRtc, controlStateLabel = network?.controlChannelState === "connecting" ? t("controlStateConnecting") : network?.controlChannelState === "open" ? t("controlStateOpen") : network?.controlChannelState === "closing" ? t("controlStateClosing") : t("controlStateClosed"), detailValue = (value) => value === void 0 || value === "" ? t("notProvided") : String(value), candidateLabel = (value) => value === "host" ? t("candidateHost") : value === "srflx" ? t("candidateSrflx") : value === "prflx" ? t("candidatePrflx") : value === "relay" ? t("candidateRelay") : detailValue(value), fact = (label, value, mono = !1) => React.createElement(
3900
+ }, [routeOpen]), status?.mode !== "remote" && status?.reconnecting === void 0) return null;
3901
+ let transport = status.network?.webRtc?.mode ?? status.transport ?? "Disconnected", networkLabel = t(transport === "P2P" ? "remoteNetworkP2p" : transport === "TURN" ? "remoteNetworkTurn" : transport === "Relay" ? "remoteNetworkRelay" : transport === "LAN" ? "remoteNetworkLan" : "remoteNetworkOffline"), networkOnline = status.connected === !0 && transport !== "Disconnected", reconnecting = status.reconnecting !== void 0, routeVia = t(transport === "P2P" ? "connectionRouteP2p" : transport === "TURN" ? "connectionRouteTurn" : transport === "Relay" ? "connectionRouteRelay" : "connectionRouteLan"), routeViaDetail = t(transport === "P2P" ? "connectionRouteP2pDetail" : transport === "TURN" ? "connectionRouteTurnDetail" : transport === "Relay" ? "connectionRouteRelayDetail" : "connectionRouteLanDetail"), network = status.network, webRtc = network?.webRtc, controlStateLabel = network?.controlChannelState === "connecting" ? t("controlStateConnecting") : network?.controlChannelState === "open" ? t("controlStateOpen") : network?.controlChannelState === "closing" ? t("controlStateClosing") : t("controlStateClosed"), detailValue = (value) => value === void 0 || value === "" ? t("notProvided") : String(value), candidateLabel = (value) => value === "host" ? t("candidateHost") : value === "srflx" ? t("candidateSrflx") : value === "prflx" ? t("candidatePrflx") : value === "relay" ? t("candidateRelay") : detailValue(value), fact = (label, value, mono = !1) => React.createElement(
3888
3902
  "div",
3889
3903
  null,
3890
3904
  React.createElement("dt", null, label),
@@ -3903,17 +3917,32 @@ Minimum version required to store current data is: ` + bestVersion + `.
3903
3917
  "aria-hidden": !0
3904
3918
  }, React.createElement("rect", { x: 3, y: 4, width: 18, height: 13, rx: 2 }), React.createElement("path", { d: "M8 21h8M12 17v4" })),
3905
3919
  React.createElement("span", { className: "dshRemoteModePrefix" }, t("remoteModePrefix")),
3906
- React.createElement("span", { className: "dshRemoteSessionTarget" }, status.target?.name ?? t("host")),
3920
+ React.createElement(
3921
+ "span",
3922
+ { className: "dshRemoteSessionTarget" },
3923
+ // While reconnecting the carriers may already describe the local shell, so its name must not
3924
+ // stand in for the target: name the Host the reconnect is about, or its short id.
3925
+ status.reconnecting === void 0 ? status.target?.name ?? t("host") : status.reconnecting.targetName ?? shortDeviceId(status.reconnecting.targetDeviceId)
3926
+ ),
3907
3927
  React.createElement("button", {
3908
3928
  type: "button",
3909
- className: `dshRemoteNetwork${networkOnline ? " isOnline" : " isOffline"}`,
3910
- title: networkLabel,
3911
- disabled: !networkOnline,
3912
- "aria-haspopup": "dialog",
3913
- "aria-expanded": routeOpen,
3914
- onClick: () => setRouteOpen((value) => !value)
3915
- }, React.createElement("i", { "aria-hidden": !0 }), networkLabel),
3916
- networkOnline ? React.createElement("span", { className: "dshRemoteEncrypted" }, t("remoteLinkEncrypted")) : null,
3929
+ className: `dshRemoteNetwork${reconnecting ? " isReconnecting" : networkOnline ? " isOnline" : " isOffline"}`,
3930
+ title: reconnecting ? t("stopReconnecting") : networkLabel,
3931
+ // Cancelling has to stay clickable: a reconnecting session is offline by definition.
3932
+ disabled: reconnecting ? !1 : !networkOnline,
3933
+ "aria-haspopup": reconnecting ? void 0 : "dialog",
3934
+ "aria-expanded": reconnecting ? void 0 : routeOpen,
3935
+ "aria-label": reconnecting ? t("stopReconnecting") : void 0,
3936
+ onClick: () => {
3937
+ if (!reconnecting) {
3938
+ setRouteOpen((value) => !value);
3939
+ return;
3940
+ }
3941
+ setRouteOpen(!1), props.control("mode.set", { mode: "local" }).catch(() => {
3942
+ });
3943
+ }
3944
+ }, React.createElement("i", { "aria-hidden": !0 }), reconnecting ? t("reconnecting") : networkLabel),
3945
+ networkOnline && !reconnecting ? React.createElement("span", { className: "dshRemoteEncrypted" }, t("remoteLinkEncrypted")) : null,
3917
3946
  routeOpen ? React.createElement("div", {
3918
3947
  className: "dshRemoteRouteBackdrop",
3919
3948
  role: "presentation",
@@ -4048,7 +4077,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
4048
4077
  ".dshRemoteModeButton:is(button):hover{background:var(--dsw-alias-interactive-bg-hover)}",
4049
4078
  ".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}",
4050
4079
  ".dshRemoteComputerIcon{box-sizing:border-box;width:18px;height:18px;flex:0 0 18px;color:var(--dsw-alias-label-secondary)}",
4051
- '.dshRemoteSessionHeader{position:fixed;z-index:25;top:12px;left:50%;transform:translateX(-50%);max-width:calc(100vw - 360px);height:28px;display:inline-flex;align-items:center;gap:7px;color:var(--dsw-alias-label-secondary);font-size:12px;white-space:nowrap;overflow:hidden}.dshRemoteSessionHeader>svg{width:15px;height:15px;flex:0 0 auto}.dshRemoteModePrefix{flex:0 0 auto}.dshRemoteSessionTarget{flex:0 1 auto;min-width:0;max-width:260px;overflow:hidden;text-overflow:ellipsis}.dshRemoteNetwork{flex:0 0 auto;border:0;background:transparent;color:inherit;font:inherit;padding:3px 2px;display:inline-flex;align-items:center;gap:5px;cursor:pointer}.dshRemoteNetwork:hover:not(:disabled){color:var(--dsw-alias-label-primary);text-decoration:underline}.dshRemoteNetwork:disabled{cursor:default}.dshRemoteNetwork>i{width:6px;height:6px;border-radius:50%;background:var(--dsw-alias-label-tertiary)}.dshRemoteNetwork.isOnline>i{background:var(--dsw-alias-state-success-primary)}.dshRemoteNetwork.isOffline{color:var(--dsw-alias-state-error-primary)}.dshRemoteNetwork.isOffline>i{background:currentColor}.dshRemoteEncrypted{flex:0 0 auto;color:var(--dsw-alias-label-tertiary)}.dshRemoteNetwork:focus-visible,.dshRemoteRoutePanel>header button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.dshRemoteRouteBackdrop{position:fixed;inset:0;z-index:26}.dshRemoteRoutePanel{box-sizing:border-box;position:absolute;top:48px;right:28px;width:min(680px,calc(100vw - 32px));max-height:calc(100vh - 72px);overflow:auto;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;padding:16px;white-space:normal}.dshRemoteRoutePanel>header{position:sticky;top:-16px;z-index:1;display:flex;align-items:center;justify-content:space-between;margin:-16px -16px 0;padding:16px;background:var(--dsw-alias-bg-layer-1)}.dshRemoteRoutePanel>header strong{font-size:14px}.dshRemoteRoutePanel>header button{width:28px;height:28px;border:0;border-radius:7px;background:transparent;color:inherit;font-size:20px;cursor:pointer}.dshRemoteRoutePanel>header button:hover{background:var(--dsw-alias-interactive-bg-hover)}.dshRemoteRoutePanel ol{display:flex;align-items:stretch;margin:12px 0 0;padding:0 0 16px;border-bottom:1px solid var(--dsw-alias-border-l2);list-style:none}.dshRemoteRoutePanel li{position:relative;min-width:0;flex:1;display:flex;flex-direction:column;gap:4px;padding-right:20px}.dshRemoteRoutePanel li:not(:last-child)::after{content:"\u2192";position:absolute;right:7px;top:21px;color:var(--dsw-alias-label-tertiary)}.dshRemoteRoutePanel li small{color:var(--dsw-alias-label-tertiary)}.dshRemoteRoutePanel li strong,.dshRemoteRoutePanel li span{overflow:hidden;text-overflow:ellipsis}.dshRemoteRoutePanel li strong{font-size:13px}.dshRemoteRoutePanel li span{color:var(--dsw-alias-label-secondary);font-size:11px}.dshRemoteRouteSection{padding-top:16px}.dshRemoteRouteSection h3{margin:0 0 10px;font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary)}.dshRemoteRouteSection dl{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 24px;margin:0}.dshRemoteRouteSection dl>div{min-width:0;display:grid;grid-template-columns:minmax(104px,auto) minmax(0,1fr);gap:10px;padding:7px 0;border-bottom:1px solid var(--dsw-alias-border-l2);font-size:12px;line-height:1.45}.dshRemoteRouteSection dt{color:var(--dsw-alias-label-tertiary)}.dshRemoteRouteSection dd{min-width:0;margin:0;text-align:right;overflow-wrap:anywhere}.dshRemoteRouteSection dd.isMono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}.dshRemoteRoutePanel>p{margin:16px 0 0;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5}@media(max-width:620px){.dshRemoteSessionHeader{top:8px;max-width:calc(100vw - 112px)}.dshRemoteSessionHeader>svg{display:none}.dshRemoteSessionTarget{max-width:130px}.dshRemoteEncrypted{display:none}.dshRemoteRoutePanel{top:42px;right:12px;max-height:calc(100vh - 56px)}.dshRemoteRoutePanel ol{flex-direction:column;gap:18px}.dshRemoteRoutePanel li:not(:last-child)::after{content:"\u2193";top:auto;right:auto;bottom:-16px;left:3px}.dshRemoteRouteSection dl{grid-template-columns:1fr}.dshRemoteRouteSection dl>div{grid-template-columns:1fr;gap:2px}.dshRemoteRouteSection dd{text-align:left}}',
4080
+ '.dshRemoteNetwork.isReconnecting{color:#f0a020;border-color:rgba(240,160,32,.55);cursor:pointer}.dshRemoteSessionHeader{position:fixed;z-index:25;top:12px;left:50%;transform:translateX(-50%);max-width:calc(100vw - 360px);height:28px;display:inline-flex;align-items:center;gap:7px;color:var(--dsw-alias-label-secondary);font-size:12px;white-space:nowrap;overflow:hidden}.dshRemoteSessionHeader>svg{width:15px;height:15px;flex:0 0 auto}.dshRemoteModePrefix{flex:0 0 auto}.dshRemoteSessionTarget{flex:0 1 auto;min-width:0;max-width:260px;overflow:hidden;text-overflow:ellipsis}.dshRemoteNetwork{flex:0 0 auto;border:0;background:transparent;color:inherit;font:inherit;padding:3px 2px;display:inline-flex;align-items:center;gap:5px;cursor:pointer}.dshRemoteNetwork:hover:not(:disabled){color:var(--dsw-alias-label-primary);text-decoration:underline}.dshRemoteNetwork:disabled{cursor:default}.dshRemoteNetwork>i{width:6px;height:6px;border-radius:50%;background:var(--dsw-alias-label-tertiary)}.dshRemoteNetwork.isOnline>i{background:var(--dsw-alias-state-success-primary)}.dshRemoteNetwork.isOffline{color:var(--dsw-alias-state-error-primary)}.dshRemoteNetwork.isOffline>i{background:currentColor}.dshRemoteEncrypted{flex:0 0 auto;color:var(--dsw-alias-label-tertiary)}.dshRemoteNetwork:focus-visible,.dshRemoteRoutePanel>header button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.dshRemoteRouteBackdrop{position:fixed;inset:0;z-index:26}.dshRemoteRoutePanel{box-sizing:border-box;position:absolute;top:48px;right:28px;width:min(680px,calc(100vw - 32px));max-height:calc(100vh - 72px);overflow:auto;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;padding:16px;white-space:normal}.dshRemoteRoutePanel>header{position:sticky;top:-16px;z-index:1;display:flex;align-items:center;justify-content:space-between;margin:-16px -16px 0;padding:16px;background:var(--dsw-alias-bg-layer-1)}.dshRemoteRoutePanel>header strong{font-size:14px}.dshRemoteRoutePanel>header button{width:28px;height:28px;border:0;border-radius:7px;background:transparent;color:inherit;font-size:20px;cursor:pointer}.dshRemoteRoutePanel>header button:hover{background:var(--dsw-alias-interactive-bg-hover)}.dshRemoteRoutePanel ol{display:flex;align-items:stretch;margin:12px 0 0;padding:0 0 16px;border-bottom:1px solid var(--dsw-alias-border-l2);list-style:none}.dshRemoteRoutePanel li{position:relative;min-width:0;flex:1;display:flex;flex-direction:column;gap:4px;padding-right:20px}.dshRemoteRoutePanel li:not(:last-child)::after{content:"\u2192";position:absolute;right:7px;top:21px;color:var(--dsw-alias-label-tertiary)}.dshRemoteRoutePanel li small{color:var(--dsw-alias-label-tertiary)}.dshRemoteRoutePanel li strong,.dshRemoteRoutePanel li span{overflow:hidden;text-overflow:ellipsis}.dshRemoteRoutePanel li strong{font-size:13px}.dshRemoteRoutePanel li span{color:var(--dsw-alias-label-secondary);font-size:11px}.dshRemoteRouteSection{padding-top:16px}.dshRemoteRouteSection h3{margin:0 0 10px;font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary)}.dshRemoteRouteSection dl{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 24px;margin:0}.dshRemoteRouteSection dl>div{min-width:0;display:grid;grid-template-columns:minmax(104px,auto) minmax(0,1fr);gap:10px;padding:7px 0;border-bottom:1px solid var(--dsw-alias-border-l2);font-size:12px;line-height:1.45}.dshRemoteRouteSection dt{color:var(--dsw-alias-label-tertiary)}.dshRemoteRouteSection dd{min-width:0;margin:0;text-align:right;overflow-wrap:anywhere}.dshRemoteRouteSection dd.isMono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}.dshRemoteRoutePanel>p{margin:16px 0 0;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5}@media(max-width:620px){.dshRemoteSessionHeader{top:8px;max-width:calc(100vw - 112px)}.dshRemoteSessionHeader>svg{display:none}.dshRemoteSessionTarget{max-width:130px}.dshRemoteEncrypted{display:none}.dshRemoteRoutePanel{top:42px;right:12px;max-height:calc(100vh - 56px)}.dshRemoteRoutePanel ol{flex-direction:column;gap:18px}.dshRemoteRoutePanel li:not(:last-child)::after{content:"\u2193";top:auto;right:auto;bottom:-16px;left:3px}.dshRemoteRouteSection dl{grid-template-columns:1fr}.dshRemoteRouteSection dl>div{grid-template-columns:1fr;gap:2px}.dshRemoteRouteSection dd{text-align:left}}',
4052
4081
  ".dshRemoteSessionHeader{left:auto;right:148px;transform:none;max-width:calc(100vw - 420px)}@media(max-width:760px){.dshRemoteSessionHeader{left:auto;right:88px;transform:none;max-width:calc(100vw - 104px);padding:3px 8px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}.dshRemoteModePrefix,.dshRemoteEncrypted{display:none}.dshRemoteSessionTarget{max-width:min(30vw,150px)}}",
4053
4082
  ".dshRemoteModeButton:focus-visible,.dshRemotePage button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}",
4054
4083
  ".dshRemotePage{width:min(720px,100%);max-height:min(760px,calc(100vh - 40px));display:flex;flex-direction:column;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:14px;overflow:hidden;animation:dshRemotePageIn .18s cubic-bezier(.25,1,.5,1)}",
@@ -4134,7 +4163,26 @@ Minimum version required to store current data is: ` + bestVersion + `.
4134
4163
  console.warn("ds-harness-remote: status event stream unavailable, polling status instead:", reason);
4135
4164
  }
4136
4165
  });
4137
- ctx.effect(() => () => statusFeed.close(), "ds-harness-remote: status stream"), ctx.effect(() => {
4166
+ ctx.effect(() => () => statusFeed.close(), "ds-harness-remote: status stream");
4167
+ let firstSnapshot = !0, wasRemoteView = !1, wasFallenBack = !1, wasReconnecting = !1, lastFallbackReloadAt = 0, lastRecoveryReloadAt = 0, unsubscribeReconnectRefresh = statusFeed.subscribe(() => {
4168
+ let current = statusFeed.getSnapshot();
4169
+ if (current === void 0) return;
4170
+ if (firstSnapshot) {
4171
+ firstSnapshot = !1, wasRemoteView = current.mode === "remote", wasFallenBack = current.fellBackToLocal === !0, wasReconnecting = current.reconnecting !== void 0;
4172
+ return;
4173
+ }
4174
+ let sawRemoteViewBefore = wasRemoteView;
4175
+ if (current.mode === "remote" && (wasRemoteView = !0), current.fellBackToLocal === !0 ? wasFallenBack || (wasFallenBack = !0, wasRemoteView && Date.now() - lastFallbackReloadAt > 2e4 && (lastFallbackReloadAt = Date.now(), window.location.reload())) : wasFallenBack = !1, current.reconnecting !== void 0) {
4176
+ wasReconnecting = !0;
4177
+ return;
4178
+ }
4179
+ wasReconnecting && (wasReconnecting = !1, current.connected === !0 && (ctx.sessions.refresh().catch(() => {
4180
+ }), !sawRemoteViewBefore && Date.now() - lastRecoveryReloadAt > 5e3 && (lastRecoveryReloadAt = Date.now(), window.location.reload())));
4181
+ });
4182
+ ctx.effect(
4183
+ () => () => unsubscribeReconnectRefresh(),
4184
+ "ds-harness-remote: refresh the Session list after a reconnect"
4185
+ ), ctx.effect(() => {
4138
4186
  let disposed = !1, unsubscribeWorkspaces, unsubscribeSessions, selection, opening = !1, reconcile = () => {
4139
4187
  if (disposed || opening || selection === void 0) return;
4140
4188
  let pending = selection, workspaceSnapshot = ctx.workspaces.list.getSnapshot();
@@ -4142,7 +4190,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
4142
4190
  let sessionSnapshot = ctx.sessions.list.getSnapshot();
4143
4191
  if (pending.backend === "codex" && pending.sessionId !== void 0 && sessionSnapshot.phase !== "ready") return;
4144
4192
  opening = !0, unsubscribeWorkspaces?.(), unsubscribeSessions?.(), unsubscribeWorkspaces = void 0, unsubscribeSessions = void 0, (pending.backend === "codex" && pending.sessionId !== void 0 && sessionSnapshot.ids.includes(pending.sessionId) ? Promise.resolve(pending.sessionId) : ctx.uiWorkspace.connectWorkspace(pending.workspaceId)).then(async (sessionId) => {
4145
- disposed || (ctx.sessions.open(sessionId), window.sessionStorage.removeItem(pendingWorkspaceSelectionKey), await control("workspace.selection.consume", pending).catch(() => {
4193
+ disposed || (ctx.sessions.open(sessionId), await control("workspace.selection.consume", pending).catch(() => {
4146
4194
  }));
4147
4195
  }).catch((reason) => {
4148
4196
  disposed || console.warn("remote workspace selection failed:", reason);
package/dist/client.js CHANGED
@@ -1933,6 +1933,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
1933
1933
  noRemoteWorkspaces: "No remote workspaces yet. Use + to add one.",
1934
1934
  activeRemote: "{name}",
1935
1935
  exitRemote: "Exit",
1936
+ stopReconnecting: "Stop reconnecting",
1936
1937
  addRemoteWorkspace: "Add remote workspace",
1937
1938
  addCodexWorkspace: "Add CodeX workspace",
1938
1939
  noCodexWorkspaces: "No CodeX workspaces yet.",
@@ -2188,6 +2189,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
2188
2189
  noRemoteWorkspaces: "\u8FD9\u53F0\u4E3B\u673A\u8FD8\u6CA1\u6709\u5DE5\u4F5C\u533A\uFF0C\u70B9\u51FB + \u6DFB\u52A0\u3002",
2189
2190
  activeRemote: "{name}",
2190
2191
  exitRemote: "\u9000\u51FA",
2192
+ stopReconnecting: "\u505C\u6B62\u91CD\u8FDE",
2191
2193
  addRemoteWorkspace: "\u6DFB\u52A0\u8FDC\u7A0B\u5DE5\u4F5C\u533A",
2192
2194
  addCodexWorkspace: "\u6DFB\u52A0 CodeX \u5DE5\u4F5C\u533A",
2193
2195
  noCodexWorkspaces: "\u8FD8\u6CA1\u6709 CodeX \u5DE5\u4F5C\u533A\u3002",
@@ -3017,6 +3019,14 @@ Minimum version required to store current data is: ` + bestVersion + `.
3017
3019
  }, [status?.host?.connectedClients?.length]), React.useEffect(() => {
3018
3020
  props.control("status").then(setStatus).catch(() => {
3019
3021
  });
3022
+ }, []), React.useEffect(() => {
3023
+ let verify = () => {
3024
+ document.visibilityState === "visible" && props.control("client.connection.verify").then(setStatus).catch(() => {
3025
+ });
3026
+ };
3027
+ return document.addEventListener("visibilitychange", verify), window.addEventListener("focus", verify), () => {
3028
+ document.removeEventListener("visibilitychange", verify), window.removeEventListener("focus", verify);
3029
+ };
3020
3030
  }, []), React.useEffect(() => {
3021
3031
  status?.serverUrl !== void 0 && loginServerUrl === DEFAULT_REMOTE_SERVER_URL && setLoginServerUrl(status.serverUrl);
3022
3032
  }, [status?.serverUrl]), React.useEffect(() => {
@@ -3337,7 +3347,11 @@ Minimum version required to store current data is: ` + bestVersion + `.
3337
3347
  },
3338
3348
  React.createElement("rect", { x: 3, y: 4, width: 18, height: 13, rx: 2 }),
3339
3349
  React.createElement("path", { d: "M8 21h8M12 17v4" })
3340
- ), props.wide ? React.createElement("span", { className: "dshRemoteSidebarLabel" }, remoteLabel) : null),
3350
+ ), props.wide ? React.createElement(
3351
+ "span",
3352
+ { className: "dshRemoteSidebarLabel" },
3353
+ remoteLabel
3354
+ ) : null),
3341
3355
  (status?.mode === "remote" || status?.fellBackToLocal === !0) && props.wide ? React.createElement("button", {
3342
3356
  type: "button",
3343
3357
  className: "dshRemoteExitLink",
@@ -3883,8 +3897,8 @@ Minimum version required to store current data is: ` + bestVersion + `.
3883
3897
  event.key === "Escape" && setRouteOpen(!1);
3884
3898
  };
3885
3899
  return document.addEventListener("keydown", closeOnEscape), () => document.removeEventListener("keydown", closeOnEscape);
3886
- }, [routeOpen]), status?.mode !== "remote") return null;
3887
- let transport = status.network?.webRtc?.mode ?? status.transport ?? "Disconnected", networkLabel = t(transport === "P2P" ? "remoteNetworkP2p" : transport === "TURN" ? "remoteNetworkTurn" : transport === "Relay" ? "remoteNetworkRelay" : transport === "LAN" ? "remoteNetworkLan" : "remoteNetworkOffline"), networkOnline = status.connected === !0 && transport !== "Disconnected", routeVia = t(transport === "P2P" ? "connectionRouteP2p" : transport === "TURN" ? "connectionRouteTurn" : transport === "Relay" ? "connectionRouteRelay" : "connectionRouteLan"), routeViaDetail = t(transport === "P2P" ? "connectionRouteP2pDetail" : transport === "TURN" ? "connectionRouteTurnDetail" : transport === "Relay" ? "connectionRouteRelayDetail" : "connectionRouteLanDetail"), network = status.network, webRtc = network?.webRtc, controlStateLabel = network?.controlChannelState === "connecting" ? t("controlStateConnecting") : network?.controlChannelState === "open" ? t("controlStateOpen") : network?.controlChannelState === "closing" ? t("controlStateClosing") : t("controlStateClosed"), detailValue = (value) => value === void 0 || value === "" ? t("notProvided") : String(value), candidateLabel = (value) => value === "host" ? t("candidateHost") : value === "srflx" ? t("candidateSrflx") : value === "prflx" ? t("candidatePrflx") : value === "relay" ? t("candidateRelay") : detailValue(value), fact = (label, value, mono = !1) => React.createElement(
3900
+ }, [routeOpen]), status?.mode !== "remote" && status?.reconnecting === void 0) return null;
3901
+ let transport = status.network?.webRtc?.mode ?? status.transport ?? "Disconnected", networkLabel = t(transport === "P2P" ? "remoteNetworkP2p" : transport === "TURN" ? "remoteNetworkTurn" : transport === "Relay" ? "remoteNetworkRelay" : transport === "LAN" ? "remoteNetworkLan" : "remoteNetworkOffline"), networkOnline = status.connected === !0 && transport !== "Disconnected", reconnecting = status.reconnecting !== void 0, routeVia = t(transport === "P2P" ? "connectionRouteP2p" : transport === "TURN" ? "connectionRouteTurn" : transport === "Relay" ? "connectionRouteRelay" : "connectionRouteLan"), routeViaDetail = t(transport === "P2P" ? "connectionRouteP2pDetail" : transport === "TURN" ? "connectionRouteTurnDetail" : transport === "Relay" ? "connectionRouteRelayDetail" : "connectionRouteLanDetail"), network = status.network, webRtc = network?.webRtc, controlStateLabel = network?.controlChannelState === "connecting" ? t("controlStateConnecting") : network?.controlChannelState === "open" ? t("controlStateOpen") : network?.controlChannelState === "closing" ? t("controlStateClosing") : t("controlStateClosed"), detailValue = (value) => value === void 0 || value === "" ? t("notProvided") : String(value), candidateLabel = (value) => value === "host" ? t("candidateHost") : value === "srflx" ? t("candidateSrflx") : value === "prflx" ? t("candidatePrflx") : value === "relay" ? t("candidateRelay") : detailValue(value), fact = (label, value, mono = !1) => React.createElement(
3888
3902
  "div",
3889
3903
  null,
3890
3904
  React.createElement("dt", null, label),
@@ -3903,17 +3917,32 @@ Minimum version required to store current data is: ` + bestVersion + `.
3903
3917
  "aria-hidden": !0
3904
3918
  }, React.createElement("rect", { x: 3, y: 4, width: 18, height: 13, rx: 2 }), React.createElement("path", { d: "M8 21h8M12 17v4" })),
3905
3919
  React.createElement("span", { className: "dshRemoteModePrefix" }, t("remoteModePrefix")),
3906
- React.createElement("span", { className: "dshRemoteSessionTarget" }, status.target?.name ?? t("host")),
3920
+ React.createElement(
3921
+ "span",
3922
+ { className: "dshRemoteSessionTarget" },
3923
+ // While reconnecting the carriers may already describe the local shell, so its name must not
3924
+ // stand in for the target: name the Host the reconnect is about, or its short id.
3925
+ status.reconnecting === void 0 ? status.target?.name ?? t("host") : status.reconnecting.targetName ?? shortDeviceId(status.reconnecting.targetDeviceId)
3926
+ ),
3907
3927
  React.createElement("button", {
3908
3928
  type: "button",
3909
- className: `dshRemoteNetwork${networkOnline ? " isOnline" : " isOffline"}`,
3910
- title: networkLabel,
3911
- disabled: !networkOnline,
3912
- "aria-haspopup": "dialog",
3913
- "aria-expanded": routeOpen,
3914
- onClick: () => setRouteOpen((value) => !value)
3915
- }, React.createElement("i", { "aria-hidden": !0 }), networkLabel),
3916
- networkOnline ? React.createElement("span", { className: "dshRemoteEncrypted" }, t("remoteLinkEncrypted")) : null,
3929
+ className: `dshRemoteNetwork${reconnecting ? " isReconnecting" : networkOnline ? " isOnline" : " isOffline"}`,
3930
+ title: reconnecting ? t("stopReconnecting") : networkLabel,
3931
+ // Cancelling has to stay clickable: a reconnecting session is offline by definition.
3932
+ disabled: reconnecting ? !1 : !networkOnline,
3933
+ "aria-haspopup": reconnecting ? void 0 : "dialog",
3934
+ "aria-expanded": reconnecting ? void 0 : routeOpen,
3935
+ "aria-label": reconnecting ? t("stopReconnecting") : void 0,
3936
+ onClick: () => {
3937
+ if (!reconnecting) {
3938
+ setRouteOpen((value) => !value);
3939
+ return;
3940
+ }
3941
+ setRouteOpen(!1), props.control("mode.set", { mode: "local" }).catch(() => {
3942
+ });
3943
+ }
3944
+ }, React.createElement("i", { "aria-hidden": !0 }), reconnecting ? t("reconnecting") : networkLabel),
3945
+ networkOnline && !reconnecting ? React.createElement("span", { className: "dshRemoteEncrypted" }, t("remoteLinkEncrypted")) : null,
3917
3946
  routeOpen ? React.createElement("div", {
3918
3947
  className: "dshRemoteRouteBackdrop",
3919
3948
  role: "presentation",
@@ -4048,7 +4077,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
4048
4077
  ".dshRemoteModeButton:is(button):hover{background:var(--dsw-alias-interactive-bg-hover)}",
4049
4078
  ".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}",
4050
4079
  ".dshRemoteComputerIcon{box-sizing:border-box;width:18px;height:18px;flex:0 0 18px;color:var(--dsw-alias-label-secondary)}",
4051
- '.dshRemoteSessionHeader{position:fixed;z-index:25;top:12px;left:50%;transform:translateX(-50%);max-width:calc(100vw - 360px);height:28px;display:inline-flex;align-items:center;gap:7px;color:var(--dsw-alias-label-secondary);font-size:12px;white-space:nowrap;overflow:hidden}.dshRemoteSessionHeader>svg{width:15px;height:15px;flex:0 0 auto}.dshRemoteModePrefix{flex:0 0 auto}.dshRemoteSessionTarget{flex:0 1 auto;min-width:0;max-width:260px;overflow:hidden;text-overflow:ellipsis}.dshRemoteNetwork{flex:0 0 auto;border:0;background:transparent;color:inherit;font:inherit;padding:3px 2px;display:inline-flex;align-items:center;gap:5px;cursor:pointer}.dshRemoteNetwork:hover:not(:disabled){color:var(--dsw-alias-label-primary);text-decoration:underline}.dshRemoteNetwork:disabled{cursor:default}.dshRemoteNetwork>i{width:6px;height:6px;border-radius:50%;background:var(--dsw-alias-label-tertiary)}.dshRemoteNetwork.isOnline>i{background:var(--dsw-alias-state-success-primary)}.dshRemoteNetwork.isOffline{color:var(--dsw-alias-state-error-primary)}.dshRemoteNetwork.isOffline>i{background:currentColor}.dshRemoteEncrypted{flex:0 0 auto;color:var(--dsw-alias-label-tertiary)}.dshRemoteNetwork:focus-visible,.dshRemoteRoutePanel>header button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.dshRemoteRouteBackdrop{position:fixed;inset:0;z-index:26}.dshRemoteRoutePanel{box-sizing:border-box;position:absolute;top:48px;right:28px;width:min(680px,calc(100vw - 32px));max-height:calc(100vh - 72px);overflow:auto;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;padding:16px;white-space:normal}.dshRemoteRoutePanel>header{position:sticky;top:-16px;z-index:1;display:flex;align-items:center;justify-content:space-between;margin:-16px -16px 0;padding:16px;background:var(--dsw-alias-bg-layer-1)}.dshRemoteRoutePanel>header strong{font-size:14px}.dshRemoteRoutePanel>header button{width:28px;height:28px;border:0;border-radius:7px;background:transparent;color:inherit;font-size:20px;cursor:pointer}.dshRemoteRoutePanel>header button:hover{background:var(--dsw-alias-interactive-bg-hover)}.dshRemoteRoutePanel ol{display:flex;align-items:stretch;margin:12px 0 0;padding:0 0 16px;border-bottom:1px solid var(--dsw-alias-border-l2);list-style:none}.dshRemoteRoutePanel li{position:relative;min-width:0;flex:1;display:flex;flex-direction:column;gap:4px;padding-right:20px}.dshRemoteRoutePanel li:not(:last-child)::after{content:"\u2192";position:absolute;right:7px;top:21px;color:var(--dsw-alias-label-tertiary)}.dshRemoteRoutePanel li small{color:var(--dsw-alias-label-tertiary)}.dshRemoteRoutePanel li strong,.dshRemoteRoutePanel li span{overflow:hidden;text-overflow:ellipsis}.dshRemoteRoutePanel li strong{font-size:13px}.dshRemoteRoutePanel li span{color:var(--dsw-alias-label-secondary);font-size:11px}.dshRemoteRouteSection{padding-top:16px}.dshRemoteRouteSection h3{margin:0 0 10px;font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary)}.dshRemoteRouteSection dl{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 24px;margin:0}.dshRemoteRouteSection dl>div{min-width:0;display:grid;grid-template-columns:minmax(104px,auto) minmax(0,1fr);gap:10px;padding:7px 0;border-bottom:1px solid var(--dsw-alias-border-l2);font-size:12px;line-height:1.45}.dshRemoteRouteSection dt{color:var(--dsw-alias-label-tertiary)}.dshRemoteRouteSection dd{min-width:0;margin:0;text-align:right;overflow-wrap:anywhere}.dshRemoteRouteSection dd.isMono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}.dshRemoteRoutePanel>p{margin:16px 0 0;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5}@media(max-width:620px){.dshRemoteSessionHeader{top:8px;max-width:calc(100vw - 112px)}.dshRemoteSessionHeader>svg{display:none}.dshRemoteSessionTarget{max-width:130px}.dshRemoteEncrypted{display:none}.dshRemoteRoutePanel{top:42px;right:12px;max-height:calc(100vh - 56px)}.dshRemoteRoutePanel ol{flex-direction:column;gap:18px}.dshRemoteRoutePanel li:not(:last-child)::after{content:"\u2193";top:auto;right:auto;bottom:-16px;left:3px}.dshRemoteRouteSection dl{grid-template-columns:1fr}.dshRemoteRouteSection dl>div{grid-template-columns:1fr;gap:2px}.dshRemoteRouteSection dd{text-align:left}}',
4080
+ '.dshRemoteNetwork.isReconnecting{color:#f0a020;border-color:rgba(240,160,32,.55);cursor:pointer}.dshRemoteSessionHeader{position:fixed;z-index:25;top:12px;left:50%;transform:translateX(-50%);max-width:calc(100vw - 360px);height:28px;display:inline-flex;align-items:center;gap:7px;color:var(--dsw-alias-label-secondary);font-size:12px;white-space:nowrap;overflow:hidden}.dshRemoteSessionHeader>svg{width:15px;height:15px;flex:0 0 auto}.dshRemoteModePrefix{flex:0 0 auto}.dshRemoteSessionTarget{flex:0 1 auto;min-width:0;max-width:260px;overflow:hidden;text-overflow:ellipsis}.dshRemoteNetwork{flex:0 0 auto;border:0;background:transparent;color:inherit;font:inherit;padding:3px 2px;display:inline-flex;align-items:center;gap:5px;cursor:pointer}.dshRemoteNetwork:hover:not(:disabled){color:var(--dsw-alias-label-primary);text-decoration:underline}.dshRemoteNetwork:disabled{cursor:default}.dshRemoteNetwork>i{width:6px;height:6px;border-radius:50%;background:var(--dsw-alias-label-tertiary)}.dshRemoteNetwork.isOnline>i{background:var(--dsw-alias-state-success-primary)}.dshRemoteNetwork.isOffline{color:var(--dsw-alias-state-error-primary)}.dshRemoteNetwork.isOffline>i{background:currentColor}.dshRemoteEncrypted{flex:0 0 auto;color:var(--dsw-alias-label-tertiary)}.dshRemoteNetwork:focus-visible,.dshRemoteRoutePanel>header button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.dshRemoteRouteBackdrop{position:fixed;inset:0;z-index:26}.dshRemoteRoutePanel{box-sizing:border-box;position:absolute;top:48px;right:28px;width:min(680px,calc(100vw - 32px));max-height:calc(100vh - 72px);overflow:auto;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;padding:16px;white-space:normal}.dshRemoteRoutePanel>header{position:sticky;top:-16px;z-index:1;display:flex;align-items:center;justify-content:space-between;margin:-16px -16px 0;padding:16px;background:var(--dsw-alias-bg-layer-1)}.dshRemoteRoutePanel>header strong{font-size:14px}.dshRemoteRoutePanel>header button{width:28px;height:28px;border:0;border-radius:7px;background:transparent;color:inherit;font-size:20px;cursor:pointer}.dshRemoteRoutePanel>header button:hover{background:var(--dsw-alias-interactive-bg-hover)}.dshRemoteRoutePanel ol{display:flex;align-items:stretch;margin:12px 0 0;padding:0 0 16px;border-bottom:1px solid var(--dsw-alias-border-l2);list-style:none}.dshRemoteRoutePanel li{position:relative;min-width:0;flex:1;display:flex;flex-direction:column;gap:4px;padding-right:20px}.dshRemoteRoutePanel li:not(:last-child)::after{content:"\u2192";position:absolute;right:7px;top:21px;color:var(--dsw-alias-label-tertiary)}.dshRemoteRoutePanel li small{color:var(--dsw-alias-label-tertiary)}.dshRemoteRoutePanel li strong,.dshRemoteRoutePanel li span{overflow:hidden;text-overflow:ellipsis}.dshRemoteRoutePanel li strong{font-size:13px}.dshRemoteRoutePanel li span{color:var(--dsw-alias-label-secondary);font-size:11px}.dshRemoteRouteSection{padding-top:16px}.dshRemoteRouteSection h3{margin:0 0 10px;font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary)}.dshRemoteRouteSection dl{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 24px;margin:0}.dshRemoteRouteSection dl>div{min-width:0;display:grid;grid-template-columns:minmax(104px,auto) minmax(0,1fr);gap:10px;padding:7px 0;border-bottom:1px solid var(--dsw-alias-border-l2);font-size:12px;line-height:1.45}.dshRemoteRouteSection dt{color:var(--dsw-alias-label-tertiary)}.dshRemoteRouteSection dd{min-width:0;margin:0;text-align:right;overflow-wrap:anywhere}.dshRemoteRouteSection dd.isMono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}.dshRemoteRoutePanel>p{margin:16px 0 0;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5}@media(max-width:620px){.dshRemoteSessionHeader{top:8px;max-width:calc(100vw - 112px)}.dshRemoteSessionHeader>svg{display:none}.dshRemoteSessionTarget{max-width:130px}.dshRemoteEncrypted{display:none}.dshRemoteRoutePanel{top:42px;right:12px;max-height:calc(100vh - 56px)}.dshRemoteRoutePanel ol{flex-direction:column;gap:18px}.dshRemoteRoutePanel li:not(:last-child)::after{content:"\u2193";top:auto;right:auto;bottom:-16px;left:3px}.dshRemoteRouteSection dl{grid-template-columns:1fr}.dshRemoteRouteSection dl>div{grid-template-columns:1fr;gap:2px}.dshRemoteRouteSection dd{text-align:left}}',
4052
4081
  ".dshRemoteSessionHeader{left:auto;right:148px;transform:none;max-width:calc(100vw - 420px)}@media(max-width:760px){.dshRemoteSessionHeader{left:auto;right:88px;transform:none;max-width:calc(100vw - 104px);padding:3px 8px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}.dshRemoteModePrefix,.dshRemoteEncrypted{display:none}.dshRemoteSessionTarget{max-width:min(30vw,150px)}}",
4053
4082
  ".dshRemoteModeButton:focus-visible,.dshRemotePage button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}",
4054
4083
  ".dshRemotePage{width:min(720px,100%);max-height:min(760px,calc(100vh - 40px));display:flex;flex-direction:column;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:14px;overflow:hidden;animation:dshRemotePageIn .18s cubic-bezier(.25,1,.5,1)}",
@@ -4134,7 +4163,26 @@ Minimum version required to store current data is: ` + bestVersion + `.
4134
4163
  console.warn("ds-harness-remote: status event stream unavailable, polling status instead:", reason);
4135
4164
  }
4136
4165
  });
4137
- ctx.effect(() => () => statusFeed.close(), "ds-harness-remote: status stream"), ctx.effect(() => {
4166
+ ctx.effect(() => () => statusFeed.close(), "ds-harness-remote: status stream");
4167
+ let firstSnapshot = !0, wasRemoteView = !1, wasFallenBack = !1, wasReconnecting = !1, lastFallbackReloadAt = 0, lastRecoveryReloadAt = 0, unsubscribeReconnectRefresh = statusFeed.subscribe(() => {
4168
+ let current = statusFeed.getSnapshot();
4169
+ if (current === void 0) return;
4170
+ if (firstSnapshot) {
4171
+ firstSnapshot = !1, wasRemoteView = current.mode === "remote", wasFallenBack = current.fellBackToLocal === !0, wasReconnecting = current.reconnecting !== void 0;
4172
+ return;
4173
+ }
4174
+ let sawRemoteViewBefore = wasRemoteView;
4175
+ if (current.mode === "remote" && (wasRemoteView = !0), current.fellBackToLocal === !0 ? wasFallenBack || (wasFallenBack = !0, wasRemoteView && Date.now() - lastFallbackReloadAt > 2e4 && (lastFallbackReloadAt = Date.now(), window.location.reload())) : wasFallenBack = !1, current.reconnecting !== void 0) {
4176
+ wasReconnecting = !0;
4177
+ return;
4178
+ }
4179
+ wasReconnecting && (wasReconnecting = !1, current.connected === !0 && (ctx.sessions.refresh().catch(() => {
4180
+ }), !sawRemoteViewBefore && Date.now() - lastRecoveryReloadAt > 5e3 && (lastRecoveryReloadAt = Date.now(), window.location.reload())));
4181
+ });
4182
+ ctx.effect(
4183
+ () => () => unsubscribeReconnectRefresh(),
4184
+ "ds-harness-remote: refresh the Session list after a reconnect"
4185
+ ), ctx.effect(() => {
4138
4186
  let disposed = !1, unsubscribeWorkspaces, unsubscribeSessions, selection, opening = !1, reconcile = () => {
4139
4187
  if (disposed || opening || selection === void 0) return;
4140
4188
  let pending = selection, workspaceSnapshot = ctx.workspaces.list.getSnapshot();
@@ -4142,7 +4190,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
4142
4190
  let sessionSnapshot = ctx.sessions.list.getSnapshot();
4143
4191
  if (pending.backend === "codex" && pending.sessionId !== void 0 && sessionSnapshot.phase !== "ready") return;
4144
4192
  opening = !0, unsubscribeWorkspaces?.(), unsubscribeSessions?.(), unsubscribeWorkspaces = void 0, unsubscribeSessions = void 0, (pending.backend === "codex" && pending.sessionId !== void 0 && sessionSnapshot.ids.includes(pending.sessionId) ? Promise.resolve(pending.sessionId) : ctx.uiWorkspace.connectWorkspace(pending.workspaceId)).then(async (sessionId) => {
4145
- disposed || (ctx.sessions.open(sessionId), window.sessionStorage.removeItem(pendingWorkspaceSelectionKey), await control("workspace.selection.consume", pending).catch(() => {
4193
+ disposed || (ctx.sessions.open(sessionId), await control("workspace.selection.consume", pending).catch(() => {
4146
4194
  }));
4147
4195
  }).catch((reason) => {
4148
4196
  disposed || console.warn("remote workspace selection failed:", reason);
@@ -49,6 +49,15 @@ export declare class CodexRemoteDomain {
49
49
  }>;
50
50
  detachPeer(connectionId: string): Promise<void>;
51
51
  close(): Promise<void>;
52
+ /**
53
+ * Launch the App Server within {@link CODEX_START_BUDGET_MS}.
54
+ *
55
+ * The losing side of the race keeps running until the disposal in start()'s catch stops it, so
56
+ * it needs its own handler: an unhandled rejection here would surface as a process-level error
57
+ * for what is only an optional feature being unavailable.
58
+ * @returns nothing once a candidate is ready.
59
+ */
60
+ private launchWithinBudget;
52
61
  private launchAppServer;
53
62
  private launchAppServerCandidate;
54
63
  private handleAppServerUnavailable;
@@ -1 +1 @@
1
- {"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../../src/codex/domain.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../../src/codex/domain.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
2
2
  import { accessSync, constants, existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
3
3
  import { readdir, realpath, stat } from 'node:fs/promises';
4
4
  import { homedir } from 'node:os';
5
- import { basename, isAbsolute, join, relative, resolve } from 'node:path';
5
+ import { basename, isAbsolute, join, posix, relative, resolve } from 'node:path';
6
6
  import { deriveCodexCwdWorkspaces } from '@dsh-remote/client-core';
7
7
  import { RpcError } from '../safe-error.js';
8
8
  import { CodexAppServerClient, CodexAppServerError, } from './app-server.js';
@@ -12,6 +12,16 @@ import { codexPermissionPresetFromResponse } from './permissions.js';
12
12
  import { paginateCodexNativeHistory, projectCodexNativeHistory } from './virtual-harness.js';
13
13
  const APPROVAL_TTL_MS = 5 * 60_000;
14
14
  const DEFAULT_RESTART_DELAYS_MS = [1_000, 2_000, 4_000, 8_000, 15_000];
15
+ /**
16
+ * How long the whole App Server launch may take.
17
+ *
18
+ * `launchAppServer` tries several binary candidates and each attempt can spend a full request
19
+ * timeout waiting for a binary that will never answer, so a missing or broken install used to
20
+ * cost that timeout several times over. One budget for all candidates keeps the domain's own
21
+ * status honest ('unavailable' in seconds, not tens of seconds) while the existing restart
22
+ * backoff keeps trying in the background.
23
+ */
24
+ const CODEX_START_BUDGET_MS = 5_000;
15
25
  const CODEX_PAGE_LIMIT = 100;
16
26
  const MAX_CODEX_PAGES = 32;
17
27
  const CODEX_HISTORY_PAGE_LIMIT = 25;
@@ -55,7 +65,7 @@ export class CodexRemoteDomain {
55
65
  return;
56
66
  try {
57
67
  this.state = 'starting';
58
- await this.launchAppServer();
68
+ await this.launchWithinBudget();
59
69
  }
60
70
  catch (error) {
61
71
  this.available = false;
@@ -306,6 +316,34 @@ export class CodexRemoteDomain {
306
316
  await this.appServer?.close();
307
317
  this.appServer = undefined;
308
318
  }
319
+ /**
320
+ * Launch the App Server within {@link CODEX_START_BUDGET_MS}.
321
+ *
322
+ * The losing side of the race keeps running until the disposal in start()'s catch stops it, so
323
+ * it needs its own handler: an unhandled rejection here would surface as a process-level error
324
+ * for what is only an optional feature being unavailable.
325
+ * @returns nothing once a candidate is ready.
326
+ */
327
+ async launchWithinBudget() {
328
+ let timer;
329
+ const attempt = this.launchAppServer();
330
+ attempt.catch(() => undefined);
331
+ try {
332
+ await Promise.race([
333
+ attempt,
334
+ new Promise((_resolve, reject) => {
335
+ timer = setTimeout(() => {
336
+ reject(new RpcError('CODEX_START_TIMEOUT', `Codex App Server did not start within ${CODEX_START_BUDGET_MS}ms.`));
337
+ }, CODEX_START_BUDGET_MS);
338
+ timer.unref?.();
339
+ }),
340
+ ]);
341
+ }
342
+ finally {
343
+ if (timer !== undefined)
344
+ clearTimeout(timer);
345
+ }
346
+ }
309
347
  async launchAppServer() {
310
348
  let lastError;
311
349
  for (const binary of codexBinaryCandidates(this.config.binary)) {
@@ -951,17 +989,20 @@ export function codexBinaryCandidates(configured, hostPlatform = process.platfor
951
989
  return [...bundledWindowsCodex(userHome), configured];
952
990
  if (hostPlatform !== 'darwin')
953
991
  return [configured];
992
+ // The platform is a parameter, not necessarily this host: joining with the host flavour would
993
+ // return Windows separators for a macOS home and make the darwin branch unreachable from a test
994
+ // or tool running elsewhere.
954
995
  const bundledCandidates = [
955
996
  '/Applications/ChatGPT.app',
956
- join(userHome, 'Applications', 'ChatGPT.app'),
997
+ posix.join(userHome, 'Applications', 'ChatGPT.app'),
957
998
  ].flatMap(chatGptApp => {
958
- const codexCli = join(chatGptApp, 'Contents', 'Resources', 'codex-cli');
999
+ const codexCli = posix.join(chatGptApp, 'Contents', 'Resources', 'codex-cli');
959
1000
  try {
960
- const manifest = JSON.parse(readFileSync(join(codexCli, 'codex-package.json'), 'utf8'));
1001
+ const manifest = JSON.parse(readFileSync(posix.join(codexCli, 'codex-package.json'), 'utf8'));
961
1002
  if (!isRecord(manifest) || typeof manifest.entrypoint !== 'string' || manifest.entrypoint.length === 0) {
962
1003
  return [];
963
1004
  }
964
- const candidate = join(codexCli, manifest.entrypoint);
1005
+ const candidate = posix.join(codexCli, manifest.entrypoint);
965
1006
  if (!existsSync(candidate))
966
1007
  return [];
967
1008
  accessSync(candidate, constants.X_OK);
@@ -974,7 +1015,7 @@ export function codexBinaryCandidates(configured, hostPlatform = process.platfor
974
1015
  return [...new Set([
975
1016
  ...bundledCandidates,
976
1017
  '/Applications/ChatGPT.app/Contents/Resources/codex',
977
- join(userHome, 'Applications', 'ChatGPT.app', 'Contents', 'Resources', 'codex'),
1018
+ posix.join(userHome, 'Applications', 'ChatGPT.app', 'Contents', 'Resources', 'codex'),
978
1019
  configured,
979
1020
  ])];
980
1021
  }