@wenbin_wb/dsh-bridge 2.7.1 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client/client.js CHANGED
@@ -50,6 +50,10 @@ var BRIDGE_ENDPOINTS = {
50
50
  importBackup: "importBackup",
51
51
  diagnoseNetwork: "diagnoseNetwork",
52
52
  getSystemMetrics: "getSystemMetrics",
53
+ // 远程工作区管理与目录浏览
54
+ listRemoteDirectories: "listRemoteDirectories",
55
+ addRemoteWorkspace: "addRemoteWorkspace",
56
+ listWorkspaces: "listWorkspaces",
53
57
  // 访问安全认证(密码保护 / 扫码免密 Token)
54
58
  authGetStatus: "authGetStatus",
55
59
  authUpdateConfig: "authUpdateConfig",
@@ -94,6 +98,37 @@ if (typeof window !== "undefined") {
94
98
  };
95
99
  }
96
100
  }
101
+ var _globalAdminToken = "";
102
+ function setGlobalAdminToken(t) {
103
+ _globalAdminToken = t || "";
104
+ if (typeof window !== "undefined") {
105
+ try {
106
+ if (t) sessionStorage.setItem("dsh_admin_token", t);
107
+ else sessionStorage.removeItem("dsh_admin_token");
108
+ } catch {
109
+ }
110
+ }
111
+ }
112
+ function getGlobalAdminToken() {
113
+ if (_globalAdminToken) return _globalAdminToken;
114
+ if (typeof window !== "undefined") {
115
+ try {
116
+ const saved = sessionStorage.getItem("dsh_admin_token");
117
+ if (saved) {
118
+ _globalAdminToken = saved;
119
+ return saved;
120
+ }
121
+ } catch {
122
+ }
123
+ }
124
+ return "";
125
+ }
126
+ function isLocalEnvironment() {
127
+ if (typeof window === "undefined") return true;
128
+ const host = window.location.hostname || "";
129
+ const proto = window.location.protocol || "";
130
+ return host === "127.0.0.1" || host === "localhost" || host === "::1" || host === "" || proto === "file:" || proto === "vscode-webview:" || proto === "app:" || typeof window.__DSH_ELECTRON__ !== "undefined" || typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.includes("Electron");
131
+ }
97
132
  var GITHUB_URL = "https://github.com/wenbin-wb/dsh-bridge";
98
133
  var RELEASES_URL = "https://github.com/wenbin-wb/dsh-bridge/releases";
99
134
  var ISSUES_URL = "https://github.com/wenbin-wb/dsh-bridge/issues/new";
@@ -106,7 +141,7 @@ function upgradeCommands(latest) {
106
141
  ];
107
142
  }
108
143
  var name = "dsh-bridge";
109
- var inject = ["slots", "connection"];
144
+ var inject = ["slots", "connection", "workspaces", "sessions"];
110
145
  function semverGt(a, b) {
111
146
  const parse = (v) => {
112
147
  const [main = "", pre = ""] = String(v).split("-");
@@ -2085,6 +2120,91 @@ function RestartDshCard({ rpcCall }) {
2085
2120
  )
2086
2121
  );
2087
2122
  }
2123
+ function RemoteWorkspaceCard({ rpcCall }) {
2124
+ const [workspaces, setWorkspaces] = React.useState([]);
2125
+ const [loading, setLoading] = React.useState(false);
2126
+ const load = React.useCallback(async () => {
2127
+ if (!rpcCall) return;
2128
+ setLoading(true);
2129
+ try {
2130
+ const res = await rpcCall(BRIDGE_ENDPOINTS.listWorkspaces, {});
2131
+ const list = res?.value || res;
2132
+ if (Array.isArray(list)) setWorkspaces(list);
2133
+ else if (list?.workspaces && Array.isArray(list.workspaces)) setWorkspaces(list.workspaces);
2134
+ } catch {
2135
+ } finally {
2136
+ setLoading(false);
2137
+ }
2138
+ }, [rpcCall]);
2139
+ React.useEffect(() => {
2140
+ load();
2141
+ }, [load]);
2142
+ return React.createElement(
2143
+ "div",
2144
+ { style: { ...s.card, marginBottom: 16 } },
2145
+ React.createElement(
2146
+ "div",
2147
+ {
2148
+ style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 10, flexWrap: "wrap", gap: 8 }
2149
+ },
2150
+ React.createElement(
2151
+ "div",
2152
+ null,
2153
+ React.createElement(
2154
+ "div",
2155
+ { style: { ...s.label, fontSize: 13, display: "flex", alignItems: "center", gap: 6 } },
2156
+ "\u{1F5C2}\uFE0F \u8FDC\u7A0B\u5DE5\u4F5C\u533A\u7BA1\u7406 (\u76EE\u5F55\u6D4F\u89C8\u5668)"
2157
+ ),
2158
+ React.createElement(
2159
+ "div",
2160
+ { style: { ...s.muted, marginTop: 3 } },
2161
+ "\u5728\u79FB\u52A8\u7AEF\u6216\u8FDC\u7A0B\u8BBE\u5907\u4E0A\u53EF\u89C6\u70B9\u9009\u7535\u8111\u4E0A\u7684\u6587\u4EF6\u5939\u6216\u76F4\u63A5\u8F93\u5165\u8DEF\u5F84\u6DFB\u52A0\u81F3 DSH\u3002"
2162
+ )
2163
+ ),
2164
+ React.createElement("button", {
2165
+ type: "button",
2166
+ style: { ...s.btnPri, height: 32, fontSize: 12, padding: "0 14px" },
2167
+ onClick: () => {
2168
+ if (typeof window.__dshOpenRemoteWorkspaceModal === "function") {
2169
+ window.__dshOpenRemoteWorkspaceModal();
2170
+ } else if (typeof showRemoteWorkspaceDialog === "function") {
2171
+ showRemoteWorkspaceDialog(rpcCall, () => load());
2172
+ }
2173
+ }
2174
+ }, "+ \u8FDC\u7A0B\u6DFB\u52A0\u5DE5\u4F5C\u533A")
2175
+ ),
2176
+ workspaces.length > 0 ? React.createElement(
2177
+ "div",
2178
+ {
2179
+ style: { display: "flex", flexDirection: "column", gap: 6, marginTop: 8 }
2180
+ },
2181
+ workspaces.map((ws, i) => React.createElement(
2182
+ "div",
2183
+ {
2184
+ key: ws.path || i,
2185
+ style: {
2186
+ display: "flex",
2187
+ alignItems: "center",
2188
+ justifyContent: "space-between",
2189
+ padding: "7px 12px",
2190
+ background: "var(--dsw-alias-bg-layer-1,#ffffff)",
2191
+ border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)",
2192
+ borderRadius: 8,
2193
+ fontSize: 12
2194
+ }
2195
+ },
2196
+ React.createElement(
2197
+ "div",
2198
+ { style: { display: "flex", alignItems: "center", gap: 8, overflow: "hidden" } },
2199
+ React.createElement("span", { style: { fontWeight: 600, color: "var(--dsw-alias-brand-primary,#4f6ef7)", flexShrink: 0 } }, `@${i + 1} ${ws.title || ""}`),
2200
+ React.createElement("span", { style: { ...s.code, color: "var(--dsw-alias-label-tertiary,#6b7280)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, ws.path)
2201
+ )
2202
+ ))
2203
+ ) : React.createElement("div", {
2204
+ style: { ...s.muted, marginTop: 6, padding: "10px 14px", background: "var(--dsw-alias-bg-layer-1,#ffffff)", border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", borderRadius: 8, textAlign: "center" }
2205
+ }, loading ? "\u6B63\u5728\u8BFB\u53D6\u5DE5\u4F5C\u533A\u5217\u8868\u2026" : "\u6682\u65E0\u5DF2\u6CE8\u518C\u5DE5\u4F5C\u533A\uFF0C\u70B9\u51FB\u53F3\u4E0A\u89D2\u300C+ \u8FDC\u7A0B\u6DFB\u52A0\u5DE5\u4F5C\u533A\u300D\u5373\u53EF\u6D4F\u89C8\u6DFB\u52A0\u3002")
2206
+ );
2207
+ }
2088
2208
  function VersionBanner({ rpcCall }) {
2089
2209
  const [info, setInfo] = React.useState(null);
2090
2210
  const [loading, setLoading] = React.useState(false);
@@ -2522,6 +2642,7 @@ function BridgePanel({ rpcCall }) {
2522
2642
  const data = await res.json();
2523
2643
  if (data?.ok && data.adminToken) {
2524
2644
  setAdminToken(data.adminToken);
2645
+ setGlobalAdminToken(data.adminToken);
2525
2646
  setAdminUnlocked(true);
2526
2647
  return data.adminToken;
2527
2648
  }
@@ -2537,7 +2658,7 @@ function BridgePanel({ rpcCall }) {
2537
2658
  }
2538
2659
  }, [isLocalhost, adminUnlocked, fetchLoopbackToken]);
2539
2660
  const authRpcCall = React.useCallback(async (endpoint, payload = {}, signal) => {
2540
- let token = adminToken;
2661
+ let token = adminToken || getGlobalAdminToken();
2541
2662
  if (isLocalhost && !token) {
2542
2663
  token = await fetchLoopbackToken();
2543
2664
  }
@@ -2563,7 +2684,9 @@ function BridgePanel({ rpcCall }) {
2563
2684
  try {
2564
2685
  const res = await rpcCall(BRIDGE_ENDPOINTS.authAdminUnlock, { password: unlockPassword });
2565
2686
  if (res?.ok) {
2566
- setAdminToken(res.value?.adminToken || "");
2687
+ const token = res.value?.adminToken || "";
2688
+ setAdminToken(token);
2689
+ setGlobalAdminToken(token);
2567
2690
  setAdminUnlocked(true);
2568
2691
  setUnlockPassword("");
2569
2692
  setShowUnlockModal(false);
@@ -2585,6 +2708,7 @@ function BridgePanel({ rpcCall }) {
2585
2708
  } catch {
2586
2709
  }
2587
2710
  setAdminToken("");
2711
+ setGlobalAdminToken("");
2588
2712
  setAdminUnlocked(false);
2589
2713
  }, [rpcCall, adminToken]);
2590
2714
  const loadInFlightRef = React.useRef(false);
@@ -2761,6 +2885,7 @@ function BridgePanel({ rpcCall }) {
2761
2885
  React.Fragment,
2762
2886
  null,
2763
2887
  React.createElement(SystemMetricsWidget, { metrics: status?.system }),
2888
+ React.createElement(RemoteWorkspaceCard, { rpcCall: authRpcCall }),
2764
2889
  React.createElement(NetworkDiagnosticWidget, { rpcCall: authRpcCall }),
2765
2890
  React.createElement(BackupRestoreWidget, {
2766
2891
  rpcCall: authRpcCall,
@@ -3188,6 +3313,7 @@ function injectMobileStyles() {
3188
3313
  z-index: 9998 !important;
3189
3314
  box-sizing: border-box !important;
3190
3315
  user-select: none !important;
3316
+ pointer-events: none !important;
3191
3317
  }
3192
3318
 
3193
3319
  /* \u5DE6\u4FA7\u53CC\u6A2A\u7EBF\u6309\u94AE (DeepSeek App \u539F\u751F\u56FE\u6807) */
@@ -3204,6 +3330,7 @@ function injectMobileStyles() {
3204
3330
  cursor: pointer;
3205
3331
  padding: 0;
3206
3332
  transition: opacity 0.15s;
3333
+ pointer-events: auto !important;
3207
3334
  }
3208
3335
  .dsh-header-menu-btn:active {
3209
3336
  opacity: 0.6;
@@ -3223,11 +3350,29 @@ function injectMobileStyles() {
3223
3350
  cursor: pointer;
3224
3351
  padding: 0;
3225
3352
  transition: opacity 0.15s;
3353
+ pointer-events: auto !important;
3226
3354
  }
3227
3355
  .dsh-header-new-btn:active {
3228
3356
  opacity: 0.6;
3229
3357
  }
3230
3358
 
3359
+ /* \u4E2D\u95F4\u52A8\u6001\u4F1A\u8BDD\u6807\u9898 (\u5355\u884C\u5C45\u4E2D\u6253\u70B9\u622A\u65AD\uFF0C100% \u8FD8\u539F\u539F\u751F App \u5BFC\u822A\u4F53\u9A8C) */
3360
+ .dsh-mobile-header-title {
3361
+ flex: 1 1 auto !important;
3362
+ min-width: 0 !important;
3363
+ text-align: center !important;
3364
+ font-size: 15px !important;
3365
+ font-weight: 600 !important;
3366
+ color: var(--dsw-alias-label-primary, #111827) !important;
3367
+ overflow: hidden !important;
3368
+ text-overflow: ellipsis !important;
3369
+ white-space: nowrap !important;
3370
+ padding: 0 10px !important;
3371
+ user-select: none !important;
3372
+ pointer-events: none !important;
3373
+ letter-spacing: -0.2px !important;
3374
+ }
3375
+
3231
3376
  /* 3. \u4E2D\u95F4\u4E3B\u5185\u5BB9\u533A\u4E0E\u8F93\u5165\u6846 */
3232
3377
  div[class*="_centerCol"] {
3233
3378
  flex: 1 1 100% !important;
@@ -3244,6 +3389,110 @@ function injectMobileStyles() {
3244
3389
  display: none !important;
3245
3390
  }
3246
3391
 
3392
+ /* 3.1 \u4F1A\u8BDD\u5BF9\u8BDD\u5934\u90E8\u9876\u680F\uFF1A\u79FB\u52A8\u7AEF\u9632\u6324\u538B\u4E0E\u7A7A\u95F4\u91CA\u653E\u4F18\u5316\uFF08\u4E25\u683C\u6392\u9664 .dsh-mobile-app-header\uFF09 */
3393
+ div[class*="_centerCol"] header,
3394
+ header[class*="wSkVaW_header"] {
3395
+ padding: 4px 16px 2px 16px !important;
3396
+ position: relative !important;
3397
+ overflow: visible !important;
3398
+ }
3399
+
3400
+ div[class*="wSkVaW_titleRow"],
3401
+ div[class*="titleRow"] {
3402
+ display: flex !important;
3403
+ flex-direction: row !important;
3404
+ align-items: center !important;
3405
+ justify-content: space-between !important;
3406
+ gap: 8px !important;
3407
+ min-height: 32px !important;
3408
+ width: 100% !important;
3409
+ box-sizing: border-box !important;
3410
+ }
3411
+
3412
+ /* \u79FB\u52A8\u7AEF\u5C06\u539F\u6709\u5D4C\u5165\u5728\u5185\u5BB9\u533A\u7684\u957F\u9762\u5305\u5C51\u6807\u9898\u9690\u85CF\uFF08\u5DF2\u7EDF\u4E00\u63D0\u5347\u81F3\u9876\u90E8\u5BFC\u822A\u680F\u6B63\u4E2D\uFF09\uFF0C\u5F7B\u5E95\u91CA\u653E\u7B2C\u4E8C\u884C\u7A7A\u95F4 */
3413
+ nav[class*="wSkVaW_crumbs"],
3414
+ nav[class*="crumbs"],
3415
+ div[class*="wSkVaW_crumbs"],
3416
+ div[class*="crumbs"],
3417
+ [class*="wSkVaW_crumbs"] {
3418
+ display: none !important;
3419
+ }
3420
+
3421
+ /* \u5B50\u4EE3\u7406/\u667A\u80FD\u4F53\u6A21\u5F0F\u80F6\u56CA (Actions)\uFF1A\u7D27\u51D1\u5706\u89D2\u80F6\u56CA */
3422
+ div[class*="wSkVaW_headerActions"],
3423
+ div[class*="headerActions"] {
3424
+ flex: 0 0 auto !important;
3425
+ display: inline-flex !important;
3426
+ align-items: center !important;
3427
+ gap: 4px !important;
3428
+ margin-left: 0 !important;
3429
+ }
3430
+
3431
+ button[class*="h8S2Va_trigger"],
3432
+ button[class*="subagent"] {
3433
+ min-height: 26px !important;
3434
+ height: 26px !important;
3435
+ padding: 2px 8px !important;
3436
+ font-size: 11.5px !important;
3437
+ line-height: 16px !important;
3438
+ border-radius: 13px !important;
3439
+ background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.04)) !important;
3440
+ white-space: nowrap !important;
3441
+ flex-shrink: 0 !important;
3442
+ }
3443
+
3444
+ /* Session Log \u5BFC\u51FA\u4E0B\u8F7D\u6309\u94AE (Utilities)\uFF1A\u5728\u79FB\u52A8\u7AEF\u6781\u7B80\u4E3A 28px \u5706\u5F62\u7EAF\u56FE\u6807\u6309\u94AE\uFF0C\u9690\u85CF\u957F\u6587\u672C\uFF0C\u6781\u5927\u91CA\u653E\u9876\u90E8\u7A7A\u95F4 */
3445
+ div[class*="wSkVaW_headerUtilities"],
3446
+ div[class*="headerUtilities"] {
3447
+ flex: 0 0 auto !important;
3448
+ margin-left: 4px !important;
3449
+ display: inline-flex !important;
3450
+ align-items: center !important;
3451
+ }
3452
+
3453
+ button[class*="nL4_yW_sessionLogButton"],
3454
+ button[class*="sessionLogButton"] {
3455
+ min-width: 28px !important;
3456
+ width: 28px !important;
3457
+ height: 28px !important;
3458
+ padding: 0 !important;
3459
+ border-radius: 50% !important;
3460
+ display: inline-flex !important;
3461
+ align-items: center !important;
3462
+ justify-content: center !important;
3463
+ flex-shrink: 0 !important;
3464
+ border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.1)) !important;
3465
+ background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.03)) !important;
3466
+ color: var(--dsw-alias-label-secondary, #6b7280) !important;
3467
+ margin-left: 0 !important;
3468
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03) !important;
3469
+ }
3470
+
3471
+ button[class*="nL4_yW_sessionLogButton"]:hover:not(:disabled),
3472
+ button[class*="sessionLogButton"]:hover:not(:disabled) {
3473
+ background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.06)) !important;
3474
+ color: var(--dsw-alias-label-primary, #111827) !important;
3475
+ }
3476
+
3477
+ button[class*="nL4_yW_sessionLogButton"] span,
3478
+ button[class*="sessionLogButton"] span {
3479
+ display: none !important;
3480
+ }
3481
+
3482
+ button[class*="nL4_yW_sessionLogButton"] svg,
3483
+ button[class*="sessionLogButton"] svg {
3484
+ width: 13px !important;
3485
+ height: 13px !important;
3486
+ margin: 0 !important;
3487
+ }
3488
+
3489
+ /* \u5B50\u4EE3\u7406\u5C55\u5F00\u83DC\u5355\u5728\u79FB\u52A8\u7AEF\u53F3\u5BF9\u9F50\u4E0E\u5BBD\u5EA6\u81EA\u9002\u5E94 */
3490
+ div[class*="h8S2Va_menu"] {
3491
+ max-width: calc(100vw - 32px) !important;
3492
+ left: auto !important;
3493
+ right: 0 !important;
3494
+ }
3495
+
3247
3496
  /* \u8F93\u5165\u6846\u5E95\u5EA7\uFF1ADeepSeek App \u5C45\u4E2D\u53CA\u5E95\u90E8\u56FA\u5B9A */
3248
3497
  div[class*="wSkVaW_scrollBody"] {
3249
3498
  padding-bottom: max(16px, env(safe-area-inset-bottom)) !important;
@@ -3258,6 +3507,67 @@ function injectMobileStyles() {
3258
3507
  background: var(--dsw-alias-bg-layer-2, #f4f4f7) !important;
3259
3508
  }
3260
3509
 
3510
+ /* \u8F93\u5165\u6846\u5E95\u90E8\u5DE5\u5177\u680F\uFF1A\u5F39\u6027\u81EA\u9002\u5E94\uFF0C\u5F7B\u5E95\u675C\u7EDD\u6743\u9650\u9009\u62E9\u5668(Full access)\u4E0E\u6A21\u578B\u9009\u62E9\u5668\u91CD\u53E0\u78B0\u649E */
3511
+ div[class*="uV2eYG_row"] {
3512
+ display: flex !important;
3513
+ align-items: center !important;
3514
+ justify-content: space-between !important;
3515
+ gap: 6px !important;
3516
+ width: 100% !important;
3517
+ padding: 2px 2px 4px !important;
3518
+ box-sizing: border-box !important;
3519
+ }
3520
+
3521
+ div[class*="uV2eYG_tools"] {
3522
+ display: flex !important;
3523
+ align-items: center !important;
3524
+ gap: 6px !important;
3525
+ flex: 0 0 auto !important;
3526
+ min-width: 0 !important;
3527
+ }
3528
+
3529
+ div[class*="uV2eYG_modes"] {
3530
+ display: flex !important;
3531
+ align-items: center !important;
3532
+ gap: 4px !important;
3533
+ flex: 0 0 auto !important;
3534
+ min-width: 0 !important;
3535
+ }
3536
+
3537
+ button[class*="Sh0Q9G_trigger"] {
3538
+ flex: 0 0 auto !important;
3539
+ min-width: 0 !important;
3540
+ }
3541
+
3542
+ div[class*="uV2eYG_trailing"] {
3543
+ display: flex !important;
3544
+ align-items: center !important;
3545
+ justify-content: flex-end !important;
3546
+ gap: 6px !important;
3547
+ flex: 1 1 auto !important;
3548
+ min-width: 0 !important;
3549
+ }
3550
+
3551
+ div[class*="_7KE1Ra_root"] {
3552
+ flex: 0 1 auto !important;
3553
+ min-width: 0 !important;
3554
+ max-width: 180px !important;
3555
+ }
3556
+
3557
+ button[class*="_7KE1Ra_trigger"] {
3558
+ max-width: 100% !important;
3559
+ min-width: 0 !important;
3560
+ flex: 1 1 auto !important;
3561
+ padding: 0 4px 0 6px !important;
3562
+ }
3563
+
3564
+ span[class*="_7KE1Ra_triggerLabel"] {
3565
+ overflow: hidden !important;
3566
+ text-overflow: ellipsis !important;
3567
+ white-space: nowrap !important;
3568
+ min-width: 0 !important;
3569
+ }
3570
+
3261
3571
  /* 4. \u539F\u751F\u4FA7\u8FB9\u680F\u62BD\u5C49\u5316 (Drawer) */
3262
3572
  div[class*="_sidebarCol"] {
3263
3573
  position: fixed !important;
@@ -3474,6 +3784,86 @@ function injectMobileStyles() {
3474
3784
  }
3475
3785
  }
3476
3786
 
3787
+ /* \u8FDC\u7A0B\u5DE5\u4F5C\u533A\u9009\u62E9\u5F39\u7A97\u79FB\u52A8\u7AEF/\u684C\u9762\u7AEF\u81EA\u9002\u5E94\u6837\u5F0F */
3788
+ #dsh-remote-workspace-modal {
3789
+ position: fixed !important;
3790
+ inset: 0 !important;
3791
+ z-index: 100000 !important;
3792
+ background: rgba(0, 0, 0, 0.65) !important;
3793
+ backdrop-filter: blur(5px) !important;
3794
+ display: flex !important;
3795
+ align-items: center !important;
3796
+ justify-content: center !important;
3797
+ padding: 16px !important;
3798
+ box-sizing: border-box !important;
3799
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
3800
+ color: var(--dsw-alias-label-primary, #111827) !important;
3801
+ }
3802
+
3803
+ .dsh-ws-dialog-card {
3804
+ background: var(--dsw-alias-bg-layer-1, #ffffff) !important;
3805
+ border: 1px solid var(--dsw-alias-border-l1, #e5e7eb) !important;
3806
+ border-radius: 16px !important;
3807
+ width: 100% !important;
3808
+ max-width: 620px !important;
3809
+ max-height: 88vh !important;
3810
+ display: flex !important;
3811
+ flex-direction: column !important;
3812
+ box-shadow: 0 25px 35px -5px rgba(0,0,0,0.3), 0 12px 16px -5px rgba(0,0,0,0.2) !important;
3813
+ overflow: hidden !important;
3814
+ animation: dshModalFadeIn 0.2s ease-out !important;
3815
+ }
3816
+
3817
+ .dsh-ws-chips-scroll {
3818
+ display: flex !important;
3819
+ align-items: center !important;
3820
+ gap: 6px !important;
3821
+ overflow-x: auto !important;
3822
+ white-space: nowrap !important;
3823
+ scrollbar-width: none !important;
3824
+ -ms-overflow-style: none !important;
3825
+ -webkit-overflow-scrolling: touch !important;
3826
+ padding: 2px 0 !important;
3827
+ }
3828
+ .dsh-ws-chips-scroll::-webkit-scrollbar {
3829
+ display: none !important;
3830
+ }
3831
+
3832
+ @media (max-width: 640px) {
3833
+ #dsh-remote-workspace-modal {
3834
+ align-items: flex-end !important;
3835
+ padding: 0 !important;
3836
+ }
3837
+
3838
+ .dsh-ws-dialog-card {
3839
+ max-height: 92dvh !important;
3840
+ height: 92dvh !important;
3841
+ border-bottom-left-radius: 0 !important;
3842
+ border-bottom-right-radius: 0 !important;
3843
+ border-left: none !important;
3844
+ border-right: none !important;
3845
+ border-bottom: none !important;
3846
+ max-width: 100vw !important;
3847
+ width: 100vw !important;
3848
+ margin: 0 !important;
3849
+ animation: dshBottomSheetUp 0.25s cubic-bezier(0.16, 1, 0.3, 1) !important;
3850
+ }
3851
+
3852
+ .dsh-ws-drag-handle {
3853
+ display: block !important;
3854
+ }
3855
+ }
3856
+
3857
+ @keyframes dshModalFadeIn {
3858
+ from { opacity: 0; transform: scale(0.96); }
3859
+ to { opacity: 1; transform: scale(1); }
3860
+ }
3861
+
3862
+ @keyframes dshBottomSheetUp {
3863
+ from { transform: translateY(100%); }
3864
+ to { transform: translateY(0); }
3865
+ }
3866
+
3477
3867
  @media (min-width: 769px) {
3478
3868
  .dsh-mobile-app-header,
3479
3869
  .dsh-mobile-backdrop {
@@ -3483,10 +3873,11 @@ function injectMobileStyles() {
3483
3873
  `;
3484
3874
  document.head.appendChild(style);
3485
3875
  }
3486
- function setupMobileExperience() {
3876
+ function setupMobileExperience(rpcCall, ctx) {
3487
3877
  if (typeof document === "undefined" || typeof window === "undefined") return;
3488
3878
  injectMobileStyles();
3489
3879
  let header = document.querySelector(".dsh-mobile-app-header");
3880
+ let titleEl = document.querySelector(".dsh-mobile-header-title");
3490
3881
  if (!header) {
3491
3882
  header = document.createElement("header");
3492
3883
  header.className = "dsh-mobile-app-header";
@@ -3506,6 +3897,9 @@ function setupMobileExperience() {
3506
3897
  if (collapsedToggle) collapsedToggle.click();
3507
3898
  }
3508
3899
  };
3900
+ titleEl = document.createElement("div");
3901
+ titleEl.className = "dsh-mobile-header-title";
3902
+ titleEl.innerText = "\u65B0\u4F1A\u8BDD";
3509
3903
  const rightBtn = document.createElement("button");
3510
3904
  rightBtn.className = "dsh-header-new-btn";
3511
3905
  rightBtn.title = "\u65B0\u5EFA\u4F1A\u8BDD";
@@ -3521,9 +3915,26 @@ function setupMobileExperience() {
3521
3915
  if (dshNewBtn) dshNewBtn.click();
3522
3916
  };
3523
3917
  header.appendChild(leftBtn);
3918
+ header.appendChild(titleEl);
3524
3919
  header.appendChild(rightBtn);
3525
3920
  document.body.appendChild(header);
3526
3921
  }
3922
+ const syncMobileTitle = () => {
3923
+ if (!titleEl) titleEl = document.querySelector(".dsh-mobile-header-title");
3924
+ if (!titleEl) return;
3925
+ const snap = ctx?.sessions?.list?.getSnapshot?.();
3926
+ if (!snap) return;
3927
+ const cur = snap.current ? snap.byId?.[snap.current] : null;
3928
+ if (!cur || cur.blank) {
3929
+ titleEl.innerText = "\u65B0\u4F1A\u8BDD";
3930
+ } else {
3931
+ titleEl.innerText = cur.displayTitle || cur.title || "\u4F1A\u8BDD";
3932
+ }
3933
+ };
3934
+ syncMobileTitle();
3935
+ if (typeof ctx?.sessions?.list?.subscribe === "function") {
3936
+ ctx.sessions.list.subscribe(syncMobileTitle);
3937
+ }
3527
3938
  let backdrop = document.querySelector(".dsh-mobile-backdrop");
3528
3939
  if (!backdrop) {
3529
3940
  backdrop = document.createElement("div");
@@ -3619,10 +4030,617 @@ function setupMobileExperience() {
3619
4030
  }
3620
4031
  }
3621
4032
  }, { passive: true });
4033
+ document.addEventListener("click", (e) => {
4034
+ if (isLocalEnvironment()) return;
4035
+ const btn = e.target.closest('button, [role="button"], a');
4036
+ if (!btn) return;
4037
+ if (btn.closest("#dsh-remote-workspace-modal")) return;
4038
+ const label = (btn.getAttribute("aria-label") || btn.innerText || btn.title || "").trim();
4039
+ const isAddWorkspace = label === "\u6DFB\u52A0\u5DE5\u4F5C\u533A" || label === "\u65B0\u5EFA\u5DE5\u4F5C\u533A" || label === "\u6253\u5F00\u5DE5\u4F5C\u533A" || label === "\u6253\u5F00\u6587\u4EF6\u5939" || label === "Add Workspace" || label === "Open Folder" || label.includes("\u6DFB\u52A0\u5DE5\u4F5C\u533A") || label.includes("\u6253\u5F00\u5DE5\u4F5C\u533A") || label.includes("\u6253\u5F00\u6587\u4EF6\u5939") || btn.matches('button[aria-label*="\u5DE5\u4F5C\u533A"][aria-label*="\u6DFB\u52A0"], button[aria-label*="\u5DE5\u4F5C\u533A"][aria-label*="\u6253\u5F00"]');
4040
+ if (isAddWorkspace) {
4041
+ e.preventDefault();
4042
+ e.stopPropagation();
4043
+ e.stopImmediatePropagation();
4044
+ if (typeof window.__dshOpenRemoteWorkspaceModal === "function") {
4045
+ window.__dshOpenRemoteWorkspaceModal();
4046
+ }
4047
+ }
4048
+ }, true);
4049
+ }
4050
+ function escapeHtml(str) {
4051
+ if (!str) return "";
4052
+ return String(str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4053
+ }
4054
+ function showRemoteWorkspaceDialog(rpcCall, onWorkspaceAdded, clientCtx, onPicked, onCancel) {
4055
+ if (typeof document === "undefined" || typeof window === "undefined") return;
4056
+ const existing = document.getElementById("dsh-remote-workspace-modal");
4057
+ if (existing) existing.remove();
4058
+ const overlay = document.createElement("div");
4059
+ overlay.id = "dsh-remote-workspace-modal";
4060
+ overlay.style.cssText = `
4061
+ position: fixed;
4062
+ inset: 0;
4063
+ z-index: 100000;
4064
+ background: rgba(0, 0, 0, 0.65);
4065
+ backdrop-filter: blur(5px);
4066
+ display: flex;
4067
+ align-items: center;
4068
+ justify-content: center;
4069
+ padding: 16px;
4070
+ box-sizing: border-box;
4071
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
4072
+ color: var(--dsw-alias-label-primary, #111827);
4073
+ `;
4074
+ const modal = document.createElement("div");
4075
+ modal.className = "dsh-ws-dialog-card";
4076
+ let currentPath = "";
4077
+ let parentPath = null;
4078
+ let breadcrumbs = [];
4079
+ let entries = [];
4080
+ let roots = [];
4081
+ let drives = [];
4082
+ let workspaces = [];
4083
+ let filterQuery = "";
4084
+ let showManualInput = false;
4085
+ let isLoading = false;
4086
+ let isSubmitting = false;
4087
+ let statusMessage = null;
4088
+ let isErrorMessage = false;
4089
+ function closeModal() {
4090
+ document.removeEventListener("keydown", handleKeydown);
4091
+ overlay.style.opacity = "0";
4092
+ overlay.style.transition = "opacity 0.15s ease";
4093
+ setTimeout(() => overlay.remove(), 150);
4094
+ if (typeof onCancel === "function") {
4095
+ try {
4096
+ onCancel();
4097
+ } catch (e) {
4098
+ }
4099
+ }
4100
+ }
4101
+ overlay.addEventListener("click", (e) => {
4102
+ if (e.target === overlay) closeModal();
4103
+ });
4104
+ const handleKeydown = (e) => {
4105
+ if (e.key === "Escape") {
4106
+ closeModal();
4107
+ }
4108
+ };
4109
+ document.addEventListener("keydown", handleKeydown);
4110
+ function render() {
4111
+ const filteredEntries = (entries || []).filter((e) => {
4112
+ if (!filterQuery.trim()) return true;
4113
+ return e.name.toLowerCase().includes(filterQuery.trim().toLowerCase());
4114
+ });
4115
+ modal.innerHTML = `
4116
+ <!-- \u79FB\u52A8\u7AEF\u9876\u90E8\u4E0B\u62C9\u6307\u793A\u6761 -->
4117
+ <div class="dsh-ws-drag-handle" style="width: 36px; height: 4px; background: var(--dsw-alias-border-l2, #d1d5db); border-radius: 2px; margin: 8px auto 0 auto; display: none;"></div>
4118
+
4119
+ <!-- \u5F39\u7A97\u9876\u90E8\u6807\u9898\u680F -->
4120
+ <div style="padding: 12px 16px; border-bottom: 1px solid var(--dsw-alias-border-l2, #e5e7eb); display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; background: var(--dsw-alias-bg-layer-2, #f9fafb);">
4121
+ <div style="display: flex; align-items: center; gap: 8px; overflow: hidden;">
4122
+ <span style="font-size: 20px; flex-shrink: 0;">\u{1F5C2}\uFE0F</span>
4123
+ <div style="overflow: hidden;">
4124
+ <div style="font-size: 15px; font-weight: 600; color: var(--dsw-alias-label-primary, #111827); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">\u9009\u62E9\u7535\u8111\u5DE5\u4F5C\u533A</div>
4125
+ <div style="font-size: 11px; color: var(--dsw-alias-label-tertiary, #6b7280); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">\u70B9\u51FB\u8FDB\u5165\u6587\u4EF6\u5939\uFF0C\u6216\u70B9\u51FB\u300C+ \u9009\u4E3A\u5DE5\u4F5C\u533A\u300D\u76F4\u63A5\u6DFB\u52A0\u5E76\u5207\u6362</div>
4126
+ </div>
4127
+ </div>
4128
+ <button id="dsh-ws-close-btn" style="border: none; background: none; font-size: 18px; cursor: pointer; color: var(--dsw-alias-label-tertiary, #9ca3af); padding: 4px 8px; border-radius: 6px; line-height: 1; flex-shrink: 0;">\u2715</button>
4129
+ </div>
4130
+
4131
+ <div style="padding: 12px 16px; overflow-y: auto; flex: 1; display: flex; flex-direction: column; gap: 10px;">
4132
+ <!-- \u63D0\u793A\u4FE1\u606F\u6A2A\u5E45 -->
4133
+ ${statusMessage ? `
4134
+ <div style="padding: 8px 12px; border-radius: 8px; font-size: 12px; line-height: 1.5; font-weight: 500; display: flex; align-items: center; gap: 8px; ${isErrorMessage ? "background: var(--dsw-alias-state-error-bg, #fef2f2); border: 1px solid var(--dsw-alias-state-error-border, #fecaca); color: var(--dsw-alias-state-error-primary, #dc2626);" : "background: var(--dsw-alias-state-success-bg, #ecfdf5); border: 1px solid var(--dsw-alias-state-success-border, #a7f3d0); color: var(--dsw-alias-state-success-primary, #059669);"}">
4135
+ <span>${isErrorMessage ? "\u26A0\uFE0F" : "\u{1F389}"}</span>
4136
+ <span>${escapeHtml(statusMessage)}</span>
4137
+ </div>
4138
+ ` : ""}
4139
+
4140
+ <!-- \u5FEB\u901F\u76F4\u8FBE\u4E0E\u78C1\u76D8\u6A2A\u5411\u6ED1\u52A8\u680F (\u6781\u7B80\u7701\u7A7A\u95F4) -->
4141
+ <div class="dsh-ws-chips-scroll">
4142
+ ${(drives || []).map((d) => {
4143
+ const isActive = currentPath.startsWith(d.path) || currentPath === d.path;
4144
+ return `
4145
+ <button class="dsh-ws-quick-btn" data-path="${escapeHtml(d.path)}" style="border: 1px solid ${isActive ? "var(--dsw-alias-brand-primary, #4f6ef7)" : "var(--dsw-alias-border-l2, #d1d5db)"}; background: ${isActive ? "var(--dsw-alias-brand-primary, #4f6ef7)" : "var(--dsw-alias-bg-layer-2, #f9fafb)"}; color: ${isActive ? "#fff" : "var(--dsw-alias-label-primary, #111827)"}; border-radius: 14px; padding: 4px 10px; font-size: 11px; cursor: pointer; font-weight: 500; flex-shrink: 0; transition: all 0.1s;">
4146
+ \u{1F4BE} ${escapeHtml(d.name)}
4147
+ </button>
4148
+ `;
4149
+ }).join("")}
4150
+ <span style="color: var(--dsw-alias-border-l2, #d1d5db); margin: 0 1px; flex-shrink: 0;">|</span>
4151
+ ${(roots || []).map((r) => {
4152
+ const isActive = currentPath === r.path;
4153
+ return `
4154
+ <button class="dsh-ws-quick-btn" data-path="${escapeHtml(r.path)}" style="border: 1px solid ${isActive ? "var(--dsw-alias-brand-primary, #4f6ef7)" : "var(--dsw-alias-border-l2, #d1d5db)"}; background: ${isActive ? "var(--dsw-alias-state-info-bg, #eff6ff)" : "var(--dsw-alias-bg-layer-2, #f9fafb)"}; color: ${isActive ? "var(--dsw-alias-brand-primary, #4f6ef7)" : "var(--dsw-alias-label-secondary, #374151)"}; border-radius: 14px; padding: 4px 10px; font-size: 11px; cursor: pointer; font-weight: 500; flex-shrink: 0;">
4155
+ ${escapeHtml(r.name)}
4156
+ </button>
4157
+ `;
4158
+ }).join("")}
4159
+ </div>
4160
+
4161
+ <!-- \u4EA4\u4E92\u5F0F\u9762\u5305\u5C51\u8DEF\u5F84\u5BFC\u822A\u6761 (Breadcrumbs Bar) -->
4162
+ <div style="background: var(--dsw-alias-bg-layer-3, #f3f4f6); border: 1px solid var(--dsw-alias-border-l2, #e5e7eb); border-radius: 10px; padding: 6px 10px; display: flex; align-items: center; justify-content: space-between; gap: 6px;">
4163
+ <div class="dsh-ws-chips-scroll" style="flex: 1;">
4164
+ <span style="font-size: 12px; margin-right: 2px; flex-shrink: 0;">\u{1F4C2}</span>
4165
+ ${(breadcrumbs || []).map((crumb, idx) => {
4166
+ const isLast = idx === breadcrumbs.length - 1;
4167
+ return `
4168
+ <button class="dsh-ws-crumb-btn" data-path="${escapeHtml(crumb.path)}" style="border: none; background: ${isLast ? "var(--dsw-alias-bg-layer-1, #fff)" : "transparent"}; color: ${isLast ? "var(--dsw-alias-brand-primary, #4f6ef7)" : "var(--dsw-alias-label-secondary, #4b5563)"}; font-family: ui-monospace, Menlo, monospace; font-size: 11px; font-weight: ${isLast ? "700" : "500"}; padding: 3px 6px; border-radius: 4px; cursor: pointer; text-decoration: ${isLast ? "none" : "underline"}; text-underline-offset: 2px; flex-shrink: 0; box-shadow: ${isLast ? "0 1px 2px rgba(0,0,0,0.06)" : "none"};">
4169
+ ${escapeHtml(crumb.name)}
4170
+ </button>
4171
+ ${!isLast ? `<span style="color: var(--dsw-alias-label-tertiary, #9ca3af); font-size: 11px; font-weight: 600; flex-shrink: 0;">/</span>` : ""}
4172
+ `;
4173
+ }).join("")}
4174
+ </div>
4175
+
4176
+ <div style="display: flex; align-items: center; gap: 4px; flex-shrink: 0;">
4177
+ ${parentPath ? `
4178
+ <button id="dsh-ws-up-btn" data-path="${escapeHtml(parentPath)}" title="\u8FD4\u56DE\u4E0A\u4E00\u7EA7" style="border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #111827); padding: 3px 8px; border-radius: 6px; font-size: 11px; font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; gap: 2px;">
4179
+ \u2B06\uFE0F \u4E0A\u7EA7
4180
+ </button>
4181
+ ` : ""}
4182
+ <button id="dsh-ws-refresh-btn" title="\u5237\u65B0\u76EE\u5F55" style="border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #111827); padding: 3px 6px; border-radius: 6px; font-size: 11px; cursor: pointer;">
4183
+ \u{1F504}
4184
+ </button>
4185
+ </div>
4186
+ </div>
4187
+
4188
+ <!-- \u5F53\u524D\u6240\u5728\u76EE\u5F55\u786E\u8BA4\u5361\u7247 (Primary Action Card) -->
4189
+ <div style="background: var(--dsw-alias-state-info-bg, #eff6ff); border: 1px solid var(--dsw-alias-state-info-border, #bfdbfe); border-radius: 10px; padding: 10px 12px; display: flex; flex-direction: column; gap: 6px;">
4190
+ <div style="display: flex; align-items: center; justify-content: space-between; gap: 6px;">
4191
+ <span style="font-size: 11px; font-weight: 600; color: var(--dsw-alias-brand-primary, #2563eb); flex-shrink: 0;">\u5F53\u524D\u76EE\u5F55:</span>
4192
+ <span style="font-family: ui-monospace, Menlo, monospace; font-size: 11px; color: var(--dsw-alias-label-primary, #1e3a8a); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; text-align: right; font-weight: 600;">${escapeHtml(currentPath)}</span>
4193
+ </div>
4194
+ <button id="dsh-ws-add-current-btn" style="border: none; background: var(--dsw-alias-brand-primary, #2563eb); color: #fff; height: 36px; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 6px; width: 100%; box-shadow: 0 2px 4px rgba(37,99,235,0.25); transition: opacity 0.1s;" ${isSubmitting ? "disabled" : ""}>
4195
+ ${isSubmitting ? "\u6B63\u5728\u6DFB\u52A0\u5E76\u5207\u6362\u2026" : "\u{1F449} \u8BBE\u4E3A\u5F53\u524D\u5DE5\u4F5C\u533A\u5E76\u8FDB\u5165"}
4196
+ </button>
4197
+ </div>
4198
+
4199
+ <!-- \u5B50\u76EE\u5F55\u5217\u8868\u4E0E\u8FC7\u6EE4\u680F -->
4200
+ <div style="border: 1px solid var(--dsw-alias-border-l2, #e5e7eb); border-radius: 10px; overflow: hidden; display: flex; flex-direction: column; background: var(--dsw-alias-bg-layer-1, #fff);">
4201
+ <!-- \u5B9E\u65F6\u8FC7\u6EE4\u641C\u7D22\u6846 -->
4202
+ <div style="padding: 7px 10px; background: var(--dsw-alias-bg-layer-2, #f9fafb); border-bottom: 1px solid var(--dsw-alias-border-l2, #e5e7eb); display: flex; align-items: center; justify-content: space-between; gap: 6px;">
4203
+ <div style="display: flex; align-items: center; gap: 6px; flex: 1;">
4204
+ <span style="font-size: 11px; color: var(--dsw-alias-label-tertiary, #9ca3af);">\u{1F50D}</span>
4205
+ <input id="dsh-ws-filter-input" type="text" value="${escapeHtml(filterQuery)}" placeholder="\u8FC7\u6EE4\u5B50\u6587\u4EF6\u5939\u2026" style="border: none; background: transparent; font-size: 12px; width: 100%; color: var(--dsw-alias-label-primary, #111827); outline: none;" />
4206
+ </div>
4207
+ <span style="font-size: 10px; color: var(--dsw-alias-label-tertiary, #6b7280); flex-shrink: 0;">
4208
+ ${filteredEntries.length} \u4E2A\u6587\u4EF6\u5939
4209
+ </span>
4210
+ </div>
4211
+
4212
+ <!-- \u5B50\u6587\u4EF6\u5939\u6EDA\u52A8\u5217\u8868 (\u79FB\u52A8\u7AEF\u8212\u9002\u5927\u70B9\u6309\u533A\u57DF) -->
4213
+ <div style="max-height: 240px; min-height: 120px; overflow-y: auto; padding: 2px 0;">
4214
+ ${isLoading ? `
4215
+ <div style="padding: 32px; text-align: center; font-size: 12px; color: var(--dsw-alias-label-tertiary, #6b7280); display: flex; flex-direction: column; align-items: center; gap: 6px;">
4216
+ <span style="font-size: 20px;">\u23F3</span>
4217
+ <span>\u6B63\u5728\u8BFB\u53D6\u76EE\u5F55\u5185\u5BB9\u2026</span>
4218
+ </div>
4219
+ ` : filteredEntries.length === 0 ? `
4220
+ <div style="padding: 26px 16px; text-align: center; font-size: 12px; color: var(--dsw-alias-label-tertiary, #6b7280); display: flex; flex-direction: column; align-items: center; gap: 4px;">
4221
+ <span style="font-size: 22px;">\u{1F4C1}</span>
4222
+ <span>${filterQuery ? "\u672A\u627E\u5230\u5339\u914D\u7684\u5B50\u6587\u4EF6\u5939" : "\u5F53\u524D\u6587\u4EF6\u5939\u4E0B\u6CA1\u6709\u66F4\u591A\u5B50\u6587\u4EF6\u5939"}</span>
4223
+ <span style="font-size: 11px; color: var(--dsw-alias-label-tertiary, #9ca3af);">\uFF08\u76F4\u63A5\u70B9\u51FB\u4E0A\u65B9\u84DD\u8272\u6309\u94AE\u5373\u53EF\u8FDB\u5165\u5F53\u524D\u76EE\u5F55\uFF09</span>
4224
+ </div>
4225
+ ` : filteredEntries.map((e) => `
4226
+ <div class="dsh-ws-entry-row" data-path="${escapeHtml(e.path)}" style="display: flex; align-items: center; justify-content: space-between; padding: 9px 12px; border-bottom: 1px solid var(--dsw-alias-border-l2, #f3f4f6); cursor: pointer; font-size: 12px; transition: background 0.1s; min-height: 40px;">
4227
+ <div class="dsh-ws-drill-btn" data-path="${escapeHtml(e.path)}" style="display: flex; align-items: center; gap: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; padding: 2px 0;">
4228
+ <span style="font-size: 16px; flex-shrink: 0;">\u{1F4C1}</span>
4229
+ <span style="font-family: ui-monospace, Menlo, monospace; font-weight: 500; color: var(--dsw-alias-label-primary, #111827); overflow: hidden; text-overflow: ellipsis;">${escapeHtml(e.name)}</span>
4230
+ <span style="color: var(--dsw-alias-label-tertiary, #9ca3af); font-size: 12px; margin-left: 2px; flex-shrink: 0;">\u203A</span>
4231
+ </div>
4232
+ <button class="dsh-ws-pick-entry-btn" data-path="${escapeHtml(e.path)}" title="\u76F4\u63A5\u6DFB\u52A0\u6B64\u5B50\u6587\u4EF6\u5939\u4E3A\u5DE5\u4F5C\u533A\u5E76\u8FDB\u5165" style="border: 1px solid var(--dsw-alias-state-success-border, #a7f3d0); background: var(--dsw-alias-state-success-bg, #ecfdf5); color: var(--dsw-alias-state-success-primary, #059669); padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; gap: 2px; flex-shrink: 0; margin-left: 8px; white-space: nowrap; transition: all 0.1s;">
4233
+ + \u9009\u4E3A\u5DE5\u4F5C\u533A
4234
+ </button>
4235
+ </div>
4236
+ `).join("")}
4237
+ </div>
4238
+ </div>
4239
+
4240
+ <!-- \u624B\u52A8\u8F93\u5165\u8DEF\u5F84\u6298\u53E0\u533A -->
4241
+ <div>
4242
+ <div style="display: flex; align-items: center; justify-content: space-between;">
4243
+ <button id="dsh-ws-toggle-manual" style="border: none; background: none; color: var(--dsw-alias-label-tertiary, #6b7280); font-size: 11px; cursor: pointer; padding: 2px 0; text-decoration: underline;">
4244
+ ${showManualInput ? "\u25BC \u6536\u8D77\u7EDD\u5BF9\u8DEF\u5F84\u624B\u52A8\u8F93\u5165" : "\u25B6 \u624B\u52A8\u7C98\u8D34/\u8F93\u5165\u7EDD\u5BF9\u8DEF\u5F84"}
4245
+ </button>
4246
+ </div>
4247
+ ${showManualInput ? `
4248
+ <div style="margin-top: 6px; display: flex; gap: 6px;">
4249
+ <input id="dsh-ws-manual-input" type="text" value="${escapeHtml(currentPath)}" placeholder="\u8F93\u5165\u7535\u8111\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u4F8B\u5982 C:\\Projects\\my-app" style="flex: 1; font-family: ui-monospace, Menlo, monospace; font-size: 11px; padding: 6px 8px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #111827); outline: none;" />
4250
+ <button id="dsh-ws-manual-jump-btn" style="border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: var(--dsw-alias-bg-layer-2, #f9fafb); color: var(--dsw-alias-label-primary, #111827); padding: 0 10px; border-radius: 8px; font-size: 11px; cursor: pointer; white-space: nowrap;">
4251
+ \u524D\u5F80
4252
+ </button>
4253
+ <button id="dsh-ws-manual-add-btn" style="border: none; background: var(--dsw-alias-brand-primary, #4f6ef7); color: #fff; padding: 0 12px; border-radius: 8px; font-size: 11px; font-weight: 500; cursor: pointer; white-space: nowrap;">
4254
+ \u6DFB\u52A0\u5E76\u8FDB\u5165
4255
+ </button>
4256
+ </div>
4257
+ ` : ""}
4258
+ </div>
4259
+
4260
+ <!-- \u5DF2\u5728 DSH \u6CE8\u518C\u7684\u5DE5\u4F5C\u533A\u5C55\u793A (\u652F\u6301\u4E00\u952E\u5207\u6362) -->
4261
+ ${workspaces && workspaces.length > 0 ? `
4262
+ <div style="padding-top: 2px;">
4263
+ <div style="font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-tertiary, #6b7280); margin-bottom: 4px;">\u5DF2\u6CE8\u518C\u5DE5\u4F5C\u533A (${workspaces.length} \u4E2A\uFF0C\u70B9\u51FB\u76F4\u63A5\u5207\u6362)\uFF1A</div>
4264
+ <div style="display: flex; flex-direction: column; gap: 4px; max-height: 80px; overflow-y: auto;">
4265
+ ${workspaces.map((w, i) => `
4266
+ <div class="dsh-ws-registered-row" data-ws-id="${escapeHtml(w.id || "")}" data-ws-path="${escapeHtml(w.path)}" style="display: flex; align-items: center; justify-content: space-between; background: var(--dsw-alias-bg-layer-2, #f9fafb); border: 1px solid var(--dsw-alias-border-l2, #e5e7eb); border-radius: 6px; padding: 4px 8px; font-size: 11px; cursor: pointer; transition: background 0.1s;">
4267
+ <div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1;">
4268
+ <span style="font-weight: 600; color: var(--dsw-alias-brand-primary, #4f6ef7);">@${i + 1} ${escapeHtml(w.title || "")}</span>
4269
+ <span style="color: var(--dsw-alias-label-tertiary, #6b7280); margin-left: 6px; font-family: ui-monospace, Menlo, monospace; font-size: 10px;">${escapeHtml(w.path)}</span>
4270
+ </div>
4271
+ <button class="dsh-ws-switch-btn" data-ws-id="${escapeHtml(w.id || "")}" data-ws-path="${escapeHtml(w.path)}" style="border: 1px solid var(--dsw-alias-brand-primary, #4f6ef7); background: var(--dsw-alias-state-info-bg, #eff6ff); color: var(--dsw-alias-brand-primary, #4f6ef7); padding: 2px 8px; border-radius: 6px; font-size: 10px; font-weight: 600; cursor: pointer; flex-shrink: 0; margin-left: 6px; white-space: nowrap;">
4272
+ \u8FDB\u5165 \u2794
4273
+ </button>
4274
+ </div>
4275
+ `).join("")}
4276
+ </div>
4277
+ </div>
4278
+ ` : ""}
4279
+ </div>
4280
+
4281
+ <!-- \u5F39\u7A97\u5E95\u90E8\u64CD\u4F5C\u6761 -->
4282
+ <div style="padding: 8px 16px; border-top: 1px solid var(--dsw-alias-border-l2, #e5e7eb); display: flex; align-items: center; justify-content: space-between; background: var(--dsw-alias-bg-layer-2, #f9fafb); flex-shrink: 0;">
4283
+ <span style="font-size: 10px; color: var(--dsw-alias-label-tertiary, #6b7280);">
4284
+ \u{1F4A1} \u70B9\u51FB\u6587\u4EF6\u5939\u53EF\u9010\u7EA7\u8FDB\u5165
4285
+ </span>
4286
+ <button id="dsh-ws-cancel-btn" style="border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #111827); padding: 5px 14px; border-radius: 8px; font-size: 12px; cursor: pointer; font-weight: 500;">\u5173\u95ED</button>
4287
+ </div>
4288
+ `;
4289
+ modal.querySelector("#dsh-ws-close-btn")?.addEventListener("click", closeModal);
4290
+ modal.querySelector("#dsh-ws-cancel-btn")?.addEventListener("click", closeModal);
4291
+ modal.querySelectorAll(".dsh-ws-crumb-btn").forEach((btn) => {
4292
+ btn.addEventListener("click", () => {
4293
+ const p = btn.getAttribute("data-path");
4294
+ if (p) loadDirectory(p);
4295
+ });
4296
+ });
4297
+ modal.querySelectorAll(".dsh-ws-quick-btn").forEach((btn) => {
4298
+ btn.addEventListener("click", () => {
4299
+ const p = btn.getAttribute("data-path");
4300
+ if (p) loadDirectory(p);
4301
+ });
4302
+ });
4303
+ modal.querySelector("#dsh-ws-up-btn")?.addEventListener("click", (e) => {
4304
+ const p = e.currentTarget.getAttribute("data-path");
4305
+ if (p) loadDirectory(p);
4306
+ });
4307
+ modal.querySelector("#dsh-ws-refresh-btn")?.addEventListener("click", () => {
4308
+ loadDirectory(currentPath);
4309
+ });
4310
+ modal.querySelector("#dsh-ws-add-current-btn")?.addEventListener("click", () => {
4311
+ doSubmit(currentPath);
4312
+ });
4313
+ const filterInput = modal.querySelector("#dsh-ws-filter-input");
4314
+ if (filterInput) {
4315
+ filterInput.addEventListener("input", (e) => {
4316
+ filterQuery = e.target.value;
4317
+ render();
4318
+ const nextInput = modal.querySelector("#dsh-ws-filter-input");
4319
+ if (nextInput) {
4320
+ nextInput.focus();
4321
+ nextInput.selectionStart = nextInput.selectionEnd = nextInput.value.length;
4322
+ }
4323
+ });
4324
+ }
4325
+ modal.querySelectorAll(".dsh-ws-drill-btn").forEach((btn) => {
4326
+ btn.addEventListener("click", (e) => {
4327
+ e.stopPropagation();
4328
+ const p = btn.getAttribute("data-path");
4329
+ if (p) loadDirectory(p);
4330
+ });
4331
+ });
4332
+ modal.querySelectorAll(".dsh-ws-entry-row").forEach((row) => {
4333
+ row.addEventListener("click", (e) => {
4334
+ if (e.target.closest(".dsh-ws-pick-entry-btn")) return;
4335
+ const p = row.getAttribute("data-path");
4336
+ if (p) loadDirectory(p);
4337
+ });
4338
+ });
4339
+ modal.querySelectorAll(".dsh-ws-pick-entry-btn").forEach((btn) => {
4340
+ btn.addEventListener("click", (e) => {
4341
+ e.stopPropagation();
4342
+ const p = btn.getAttribute("data-path");
4343
+ if (p) doSubmit(p);
4344
+ });
4345
+ });
4346
+ modal.querySelectorAll(".dsh-ws-registered-row, .dsh-ws-switch-btn").forEach((el) => {
4347
+ el.addEventListener("click", (e) => {
4348
+ e.stopPropagation();
4349
+ const wsId = el.getAttribute("data-ws-id");
4350
+ const wsPath = el.getAttribute("data-ws-path");
4351
+ if (wsId || wsPath) {
4352
+ switchToWorkspace(wsId, wsPath);
4353
+ }
4354
+ });
4355
+ });
4356
+ modal.querySelector("#dsh-ws-toggle-manual")?.addEventListener("click", () => {
4357
+ showManualInput = !showManualInput;
4358
+ render();
4359
+ });
4360
+ const manualInput = modal.querySelector("#dsh-ws-manual-input");
4361
+ modal.querySelector("#dsh-ws-manual-jump-btn")?.addEventListener("click", () => {
4362
+ if (manualInput && manualInput.value.trim()) {
4363
+ loadDirectory(manualInput.value.trim());
4364
+ }
4365
+ });
4366
+ modal.querySelector("#dsh-ws-manual-add-btn")?.addEventListener("click", () => {
4367
+ if (manualInput && manualInput.value.trim()) {
4368
+ doSubmit(manualInput.value.trim());
4369
+ }
4370
+ });
4371
+ if (manualInput) {
4372
+ manualInput.addEventListener("keydown", (e) => {
4373
+ if (e.key === "Enter") {
4374
+ doSubmit(manualInput.value.trim());
4375
+ }
4376
+ });
4377
+ }
4378
+ }
4379
+ async function authRpc(endpoint, payload = {}) {
4380
+ let token = getGlobalAdminToken();
4381
+ if (!token && isLocalEnvironment()) {
4382
+ try {
4383
+ const res = await fetch("/__dsh_bridge__/loopback-token", { method: "POST" });
4384
+ if (res.ok) {
4385
+ const data = await res.json();
4386
+ if (data?.adminToken) {
4387
+ token = data.adminToken;
4388
+ setGlobalAdminToken(token);
4389
+ }
4390
+ }
4391
+ } catch {
4392
+ }
4393
+ }
4394
+ return rpcCall(endpoint, {
4395
+ ...payload,
4396
+ ...token ? { adminToken: token } : {},
4397
+ ...isLocalEnvironment() ? { isLocalhost: true } : {}
4398
+ });
4399
+ }
4400
+ async function switchToWorkspace(wsId, wsPath) {
4401
+ if (isSubmitting) return;
4402
+ isSubmitting = true;
4403
+ statusMessage = `\u6B63\u5728\u5207\u6362\u5DE5\u4F5C\u533A\u2026`;
4404
+ isErrorMessage = false;
4405
+ render();
4406
+ if (typeof onPicked === "function" && wsPath) {
4407
+ try {
4408
+ onPicked(wsPath);
4409
+ } catch (e) {
4410
+ }
4411
+ }
4412
+ let switched = false;
4413
+ if (clientCtx?.workspaces?.startSession && wsId) {
4414
+ try {
4415
+ clientCtx.workspaces.startSession(wsId);
4416
+ switched = true;
4417
+ } catch (e) {
4418
+ console.warn("[dsh-bridge] startSession failed:", e);
4419
+ }
4420
+ }
4421
+ if (!switched && wsPath) {
4422
+ try {
4423
+ if (clientCtx?.workspaces?.create) {
4424
+ const ws = await clientCtx.workspaces.create({ path: wsPath });
4425
+ if (ws?.workspaceId && clientCtx?.workspaces?.startSession) {
4426
+ clientCtx.workspaces.startSession(ws.workspaceId);
4427
+ switched = true;
4428
+ }
4429
+ }
4430
+ if (!switched) {
4431
+ const raw = await authRpc(BRIDGE_ENDPOINTS.addRemoteWorkspace, { path: wsPath });
4432
+ const res = raw?.value || raw;
4433
+ if (res?.workspaceId && clientCtx?.workspaces?.startSession) {
4434
+ try {
4435
+ clientCtx.workspaces.startSession(res.workspaceId);
4436
+ switched = true;
4437
+ } catch (e) {
4438
+ }
4439
+ }
4440
+ if (!switched && res?.sessionId && clientCtx?.sessions?.open) {
4441
+ try {
4442
+ clientCtx.sessions.open(res.sessionId);
4443
+ switched = true;
4444
+ } catch (e) {
4445
+ }
4446
+ }
4447
+ }
4448
+ } catch (e) {
4449
+ }
4450
+ }
4451
+ statusMessage = `\u2713 \u5DF2\u5207\u6362\u81F3\u5DE5\u4F5C\u533A\uFF01`;
4452
+ render();
4453
+ setTimeout(() => {
4454
+ closeModal();
4455
+ document.body.classList.remove("dsh-drawer-open");
4456
+ }, 400);
4457
+ }
4458
+ async function loadDirectory(targetPath) {
4459
+ if (isSubmitting) return;
4460
+ if (!rpcCall) return;
4461
+ isLoading = true;
4462
+ filterQuery = "";
4463
+ statusMessage = null;
4464
+ isErrorMessage = false;
4465
+ render();
4466
+ try {
4467
+ const raw = await authRpc(BRIDGE_ENDPOINTS.listRemoteDirectories, { path: targetPath });
4468
+ const res = raw?.value || raw;
4469
+ if (res) {
4470
+ currentPath = res.currentPath || targetPath || "";
4471
+ parentPath = res.parentPath || null;
4472
+ breadcrumbs = res.breadcrumbs || [];
4473
+ entries = res.entries || [];
4474
+ roots = res.roots || [];
4475
+ drives = res.drives || [];
4476
+ workspaces = res.workspaces || [];
4477
+ if (res.error) {
4478
+ statusMessage = res.error;
4479
+ isErrorMessage = true;
4480
+ }
4481
+ }
4482
+ } catch (err) {
4483
+ statusMessage = err.message || "\u8BFB\u53D6\u76EE\u5F55\u5931\u8D25";
4484
+ isErrorMessage = true;
4485
+ } finally {
4486
+ isLoading = false;
4487
+ render();
4488
+ }
4489
+ }
4490
+ async function doSubmit(pathToRegister) {
4491
+ if (isSubmitting) return;
4492
+ const p = (pathToRegister || currentPath || "").trim();
4493
+ if (!p) {
4494
+ statusMessage = "\u8BF7\u8F93\u5165\u6216\u9009\u62E9\u5DE5\u4F5C\u533A\u8DEF\u5F84";
4495
+ isErrorMessage = true;
4496
+ render();
4497
+ return;
4498
+ }
4499
+ if (!rpcCall) return;
4500
+ isSubmitting = true;
4501
+ statusMessage = null;
4502
+ isErrorMessage = false;
4503
+ render();
4504
+ try {
4505
+ let clientWs = null;
4506
+ if (clientCtx?.workspaces?.create) {
4507
+ try {
4508
+ clientWs = await clientCtx.workspaces.create({ path: p });
4509
+ } catch (e) {
4510
+ console.warn("[dsh-bridge] clientCtx.workspaces.create failed:", e);
4511
+ }
4512
+ }
4513
+ const raw = await authRpc(BRIDGE_ENDPOINTS.addRemoteWorkspace, { path: p });
4514
+ const res = raw?.value || raw;
4515
+ if (res && res.ok) {
4516
+ statusMessage = `\u2713 \u5DE5\u4F5C\u533A\u300C${res.title || p}\u300D\u5DF2\u9009\u5B9A\uFF0C\u6B63\u5728\u5207\u6362\u2026`;
4517
+ isErrorMessage = false;
4518
+ workspaces = res.workspaces || [];
4519
+ render();
4520
+ const targetWorkspaceId = clientWs?.workspaceId || res.workspaceId;
4521
+ if (typeof onPicked === "function") {
4522
+ try {
4523
+ onPicked(p);
4524
+ } catch (e) {
4525
+ }
4526
+ }
4527
+ let switched = false;
4528
+ if (clientCtx?.workspaces?.startSession && targetWorkspaceId) {
4529
+ try {
4530
+ clientCtx.workspaces.startSession(targetWorkspaceId);
4531
+ switched = true;
4532
+ } catch (e) {
4533
+ console.warn("[dsh-bridge] startSession failed:", e);
4534
+ }
4535
+ }
4536
+ if (!switched && clientCtx?.sessions?.open && res.sessionId) {
4537
+ try {
4538
+ clientCtx.sessions.open(res.sessionId);
4539
+ switched = true;
4540
+ } catch (e) {
4541
+ console.warn("[dsh-bridge] sessions.open failed:", e);
4542
+ }
4543
+ }
4544
+ if (typeof onWorkspaceAdded === "function") {
4545
+ onWorkspaceAdded(res);
4546
+ }
4547
+ setTimeout(() => {
4548
+ closeModal();
4549
+ document.body.classList.remove("dsh-drawer-open");
4550
+ }, 500);
4551
+ } else {
4552
+ statusMessage = res?.error || raw?.error || "\u6DFB\u52A0\u5DE5\u4F5C\u533A\u5931\u8D25";
4553
+ isErrorMessage = true;
4554
+ render();
4555
+ }
4556
+ } catch (err) {
4557
+ statusMessage = err.message || "\u6DFB\u52A0\u5DE5\u4F5C\u533A\u5F02\u5E38";
4558
+ isErrorMessage = true;
4559
+ render();
4560
+ } finally {
4561
+ isSubmitting = false;
4562
+ }
4563
+ }
4564
+ overlay.appendChild(modal);
4565
+ document.body.appendChild(overlay);
4566
+ loadDirectory("");
4567
+ }
4568
+ function RemoteDirectoryFlow(props) {
4569
+ const { open, pick } = props;
4570
+ const outcome = React.useRef(props);
4571
+ outcome.current = props;
4572
+ const armed = React.useRef(false);
4573
+ React.useEffect(() => {
4574
+ if (!open) {
4575
+ armed.current = false;
4576
+ return;
4577
+ }
4578
+ if (armed.current) return;
4579
+ armed.current = true;
4580
+ if (isLocalEnvironment()) {
4581
+ const pickFn = typeof pick === "function" ? pick : typeof window.__dshClientCtx?.workspaces?.pickDirectory === "function" ? () => window.__dshClientCtx.workspaces.pickDirectory() : null;
4582
+ if (pickFn) {
4583
+ pickFn().then((chosenPath) => {
4584
+ if (chosenPath === null) {
4585
+ if (outcome.current?.onCancel) outcome.current.onCancel();
4586
+ } else if (chosenPath) {
4587
+ if (outcome.current?.onPicked) outcome.current.onPicked(chosenPath);
4588
+ }
4589
+ }, (err) => {
4590
+ if (outcome.current?.onError) outcome.current.onError(err instanceof Error ? err.message : String(err));
4591
+ });
4592
+ return;
4593
+ }
4594
+ }
4595
+ if (typeof window.__dshOpenRemoteWorkspaceModal === "function") {
4596
+ window.__dshOpenRemoteWorkspaceModal(
4597
+ (res) => {
4598
+ if (res?.path && outcome.current?.onPicked) {
4599
+ outcome.current.onPicked(res.path);
4600
+ }
4601
+ },
4602
+ (chosenPath) => {
4603
+ if (chosenPath && outcome.current?.onPicked) {
4604
+ outcome.current.onPicked(chosenPath);
4605
+ }
4606
+ },
4607
+ () => {
4608
+ if (outcome.current?.onCancel) {
4609
+ outcome.current.onCancel();
4610
+ }
4611
+ }
4612
+ );
4613
+ }
4614
+ }, [open, pick]);
4615
+ return null;
3622
4616
  }
3623
4617
  function apply(ctx) {
3624
- setupMobileExperience();
4618
+ window.__dshClientCtx = ctx;
3625
4619
  const rpcCall = (endpoint, payload, signal) => ctx.connection.rpc.call(BRIDGE_RPC_CHANNEL, endpoint, payload, signal);
4620
+ window.__dshOpenRemoteWorkspaceModal = (onAdded, onPickDirect, onCancel) => showRemoteWorkspaceDialog(rpcCall, onAdded, ctx, onPickDirect, onCancel);
4621
+ setupMobileExperience(rpcCall, ctx);
4622
+ const injected = () => ({ pick: () => ctx.workspaces?.pickDirectory?.() });
4623
+ ctx.slots.inject(
4624
+ "conversation.hero.workspace.directoryFlow",
4625
+ () => ctx.slots.inject("sidebar.workspaces.directoryFlow", function* () {
4626
+ yield ctx.slots.register(
4627
+ {
4628
+ name: "conversation.hero.workspace.directoryFlow",
4629
+ priority: -10,
4630
+ inject: injected
4631
+ },
4632
+ RemoteDirectoryFlow
4633
+ );
4634
+ yield ctx.slots.register(
4635
+ {
4636
+ name: "sidebar.workspaces.directoryFlow",
4637
+ priority: -10,
4638
+ inject: injected
4639
+ },
4640
+ RemoteDirectoryFlow
4641
+ );
4642
+ })
4643
+ );
3626
4644
  ctx.slots.inject(
3627
4645
  "settings.section",
3628
4646
  () => ctx.slots.register(