dsh-remote-local-plugin 0.0.0-stage → 0.8.1

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 (35) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +97 -2
  3. package/assets/dsh-remote-agent-0.2.1.tgz +0 -0
  4. package/cordis.patch.yml +50 -0
  5. package/lib/client.js +1571 -0
  6. package/lib/client.js.map +7 -0
  7. package/lib/index.js +4637 -0
  8. package/lib/types/config.d.ts +94 -0
  9. package/lib/types/credentials/proxy.d.ts +40 -0
  10. package/lib/types/errors.d.ts +23 -0
  11. package/lib/types/host-api/contract.d.ts +217 -0
  12. package/lib/types/host-api/handler.d.ts +15 -0
  13. package/lib/types/host-api/terminal.d.ts +15 -0
  14. package/lib/types/hosts/id.d.ts +16 -0
  15. package/lib/types/hosts/store.d.ts +34 -0
  16. package/lib/types/hosts/types.d.ts +85 -0
  17. package/lib/types/index.d.ts +19 -0
  18. package/lib/types/mirror/service.d.ts +87 -0
  19. package/lib/types/mirror/test-visibility.d.ts +23 -0
  20. package/lib/types/remote/directory.d.ts +19 -0
  21. package/lib/types/remote/files.d.ts +14 -0
  22. package/lib/types/remote/local-directory.d.ts +11 -0
  23. package/lib/types/remote/provisioner.d.ts +32 -0
  24. package/lib/types/remote/terminal.d.ts +55 -0
  25. package/lib/types/service.d.ts +92 -0
  26. package/lib/types/ssh/probe.d.ts +33 -0
  27. package/lib/types/ssh/target.d.ts +24 -0
  28. package/lib/types/ssh/tunnel.d.ts +50 -0
  29. package/lib/types/tools.d.ts +19 -0
  30. package/node_modules/dsh-remote-terminal-controller/README.md +9 -0
  31. package/node_modules/dsh-remote-terminal-controller/cordis.patch.yml +8 -0
  32. package/node_modules/dsh-remote-terminal-controller/lib/client.js +1367 -0
  33. package/node_modules/dsh-remote-terminal-controller/lib/index.js +3 -0
  34. package/node_modules/dsh-remote-terminal-controller/package.json +59 -0
  35. package/package.json +146 -4
package/lib/client.js ADDED
@@ -0,0 +1,1571 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-remote-local-plugin",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ "use strict";
7
+ var __defProp = Object.defineProperty;
8
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
+ var __getOwnPropNames = Object.getOwnPropertyNames;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
24
+
25
+ // src/client/index.tsx
26
+ var index_exports = {};
27
+ __export(index_exports, {
28
+ apply: () => apply,
29
+ inject: () => inject
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+
33
+ // src/host-api/contract.ts
34
+ var HOST_API_PATH = "/api/dsh-remote";
35
+
36
+ // src/client/api.ts
37
+ var RemoteUiError = class extends Error {
38
+ code;
39
+ details;
40
+ constructor(code, message, details = {}) {
41
+ super(message);
42
+ this.name = "RemoteUiError";
43
+ this.code = code;
44
+ this.details = details;
45
+ }
46
+ };
47
+ function createHostsApi(ctx) {
48
+ return {
49
+ state: () => call("state", void 0),
50
+ validateTarget: (sshTarget) => call("target/validate", { sshTarget }),
51
+ addHost: (input) => call("host/add", input),
52
+ removeHost: async (hostId) => {
53
+ await call("host/remove", { hostId });
54
+ },
55
+ connect: (hostId) => call("host/connect", { hostId }),
56
+ disconnect: (hostId) => call("host/disconnect", { hostId }),
57
+ refresh: (hostId) => call("host/refresh", { hostId }),
58
+ provision: (hostId) => call("host/provision", { hostId }),
59
+ workspaces: (hostId) => call("host/workspaces", { hostId }),
60
+ listDirectories: (hostId, path) => call("host/directory/list", { hostId, ...path === void 0 ? {} : { path } }),
61
+ listLocalDirectories: (path) => call("local/directory/list", { ...path === void 0 ? {} : { path } }),
62
+ createLocalWorkspace: (input) => call("local/workspace/create", input),
63
+ createWorkspace: (hostId, input) => call("host/workspace/create", { hostId, ...input }),
64
+ sessions: (hostId, workspaceId) => call("host/sessions", {
65
+ hostId,
66
+ ...workspaceId === void 0 ? {} : { workspaceId }
67
+ }),
68
+ webUrl: (hostId) => call("host/web-url", { hostId }),
69
+ snapshot: (hostId, sessionId, limit) => call("host/session", {
70
+ hostId,
71
+ sessionId,
72
+ ...limit === void 0 ? {} : { limit }
73
+ }),
74
+ createSession: (hostId, input) => call("host/session/create", { hostId, ...input }),
75
+ openSession: (sessionId) => {
76
+ ctx.uiWorkspace.openSession(sessionId);
77
+ },
78
+ fileList: (sessionId, path) => call("host/files/list", { sessionId, path }),
79
+ fileRead: (sessionId, path, offset, limit) => call("host/files/read", {
80
+ sessionId,
81
+ path,
82
+ ...offset === void 0 ? {} : { offset },
83
+ ...limit === void 0 ? {} : { limit }
84
+ }),
85
+ fileStat: (sessionId, path) => call("host/files/stat", { sessionId, path }),
86
+ fileBytes: (sessionId, path, offset, length) => call("host/files/bytes", {
87
+ sessionId,
88
+ path,
89
+ ...offset === void 0 ? {} : { offset },
90
+ ...length === void 0 ? {} : { length }
91
+ }),
92
+ prompt: (hostId, sessionId, text, mode, selection) => call("host/session/prompt", {
93
+ hostId,
94
+ sessionId,
95
+ text,
96
+ ...mode === void 0 ? {} : { mode },
97
+ ...selection === void 0 ? {} : selection
98
+ }),
99
+ cancel: (hostId, sessionId) => call("host/session/cancel", { hostId, sessionId })
100
+ };
101
+ }
102
+ async function call(endpoint, payload) {
103
+ const response = await fetch(HOST_API_PATH, {
104
+ method: "POST",
105
+ credentials: "same-origin",
106
+ headers: { "content-type": "application/json" },
107
+ body: JSON.stringify(payload === void 0 ? { endpoint } : { endpoint, payload })
108
+ });
109
+ if (!response.ok) {
110
+ throw new RemoteUiError("request/invalid", `host API returned HTTP ${response.status}`);
111
+ }
112
+ const result = await response.json();
113
+ if (!result.ok) {
114
+ throw new RemoteUiError(result.error.code, result.error.message, result.error.details ?? {});
115
+ }
116
+ return result.value;
117
+ }
118
+
119
+ // src/client/RemoteDevelopmentPanel.tsx
120
+ var import_react = require("react");
121
+
122
+ // src/client/panel.css
123
+ var panel_default = ".dshr-root {\n --dshr-accent: #4f7cff;\n --dshr-danger: #d24b4b;\n --dshr-success: #2f9e63;\n --dshr-warning: #c98417;\n --dshr-border: color-mix(in srgb, currentColor 14%, transparent);\n --dshr-soft: color-mix(in srgb, currentColor 4%, transparent);\n --dshr-softer: color-mix(in srgb, currentColor 2.5%, transparent);\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 4px 2px 32px;\n font-size: 13px;\n line-height: 1.5;\n}\n\n.dshr-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n}\n\n.dshr-header h2 {\n margin: 0 0 4px;\n font-size: 17px;\n font-weight: 650;\n}\n\n.dshr-subtitle {\n max-width: 760px;\n margin: 0;\n opacity: 0.72;\n}\n\n.dshr-subtitle code,\n.dshr-host-title code,\n.dshr-workspace-head code,\n.dshr-session code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 0.92em;\n}\n\n.dshr-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--dshr-border);\n border-radius: 12px;\n background: var(--dshr-softer);\n}\n\n.dshr-card h3 {\n margin: 0;\n font-size: 14px;\n font-weight: 650;\n}\n\n.dshr-section-title {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 12px;\n}\n\n.dshr-form {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n}\n\n.dshr-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n min-width: 0;\n}\n\n.dshr-field-wide {\n grid-column: 1 / -1;\n}\n\n.dshr-field > span {\n font-size: 12px;\n opacity: 0.72;\n}\n\n.dshr-field input {\n width: 100%;\n box-sizing: border-box;\n padding: 7px 9px;\n border: 1px solid var(--dshr-border);\n border-radius: 8px;\n background: var(--dshr-soft);\n color: inherit;\n font: inherit;\n outline: none;\n}\n\n.dshr-field input:focus {\n border-color: var(--dshr-accent);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--dshr-accent) 22%, transparent);\n}\n\n.dshr-check {\n display: flex;\n align-items: center;\n gap: 7px;\n font-size: 12px;\n}\n\n.dshr-form-actions {\n display: flex;\n grid-column: 1 / -1;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.dshr-button {\n padding: 6px 12px;\n border: 1px solid var(--dshr-border);\n border-radius: 8px;\n background: var(--dshr-soft);\n color: inherit;\n font: inherit;\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease;\n}\n\n.dshr-button:hover:not(:disabled) {\n background: color-mix(in srgb, currentColor 8%, transparent);\n}\n\n.dshr-button:disabled {\n cursor: not-allowed;\n opacity: 0.45;\n}\n\n.dshr-button-primary {\n border-color: color-mix(in srgb, var(--dshr-accent) 65%, transparent);\n background: var(--dshr-accent);\n color: #fff;\n}\n\n.dshr-button-primary:hover:not(:disabled) {\n background: color-mix(in srgb, var(--dshr-accent) 84%, #000);\n}\n\n.dshr-button-danger {\n border-color: color-mix(in srgb, var(--dshr-danger) 45%, transparent);\n color: var(--dshr-danger);\n}\n\n.dshr-hint {\n font-size: 12px;\n opacity: 0.62;\n}\n\n.dshr-empty {\n margin: 0;\n padding: 10px 0;\n opacity: 0.62;\n}\n\n.dshr-banner {\n margin: 0;\n padding: 9px 12px;\n border-radius: 9px;\n border: 1px solid var(--dshr-border);\n}\n\n.dshr-banner-error {\n border-color: color-mix(in srgb, var(--dshr-danger) 45%, transparent);\n background: color-mix(in srgb, var(--dshr-danger) 10%, transparent);\n color: var(--dshr-danger);\n}\n\n.dshr-banner-info {\n border-color: color-mix(in srgb, var(--dshr-accent) 35%, transparent);\n background: color-mix(in srgb, var(--dshr-accent) 9%, transparent);\n}\n\n.dshr-hosts {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dshr-host {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px;\n border: 1px solid var(--dshr-border);\n border-radius: 10px;\n background: var(--dshr-soft);\n}\n\n.dshr-host-head {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n}\n\n.dshr-host-title {\n display: flex;\n align-items: baseline;\n gap: 10px;\n flex-wrap: wrap;\n min-width: 0;\n}\n\n.dshr-host-title strong {\n font-size: 14px;\n}\n\n.dshr-phase {\n flex: none;\n padding: 1px 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n border: 1px solid currentColor;\n opacity: 0.9;\n}\n\n.dshr-phase-connected {\n color: var(--dshr-success);\n}\n\n.dshr-phase-discovering,\n.dshr-phase-connecting {\n color: var(--dshr-warning);\n}\n\n.dshr-phase-error {\n color: var(--dshr-danger);\n}\n\n.dshr-phase-idle,\n.dshr-phase-disconnected {\n opacity: 0.62;\n}\n\n.dshr-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 6px 20px;\n margin: 0;\n font-size: 12px;\n}\n\n.dshr-meta > div {\n display: flex;\n gap: 5px;\n}\n\n.dshr-meta dt {\n opacity: 0.58;\n}\n\n.dshr-meta dd {\n margin: 0;\n font-variant-numeric: tabular-nums;\n}\n\n.dshr-host-error {\n margin: 0;\n color: var(--dshr-danger);\n font-size: 12px;\n overflow-wrap: anywhere;\n}\n\n.dshr-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.dshr-workspaces {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding-top: 4px;\n border-top: 1px dashed var(--dshr-border);\n}\n\n.dshr-workspace {\n border-radius: 8px;\n background: var(--dshr-softer);\n}\n\n.dshr-workspace-head {\n display: flex;\n align-items: baseline;\n gap: 8px;\n width: 100%;\n box-sizing: border-box;\n padding: 8px 10px;\n border: 0;\n border-radius: 8px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n flex-wrap: wrap;\n}\n\n.dshr-workspace-head:hover {\n background: var(--dshr-soft);\n}\n\n.dshr-workspace-head code {\n opacity: 0.6;\n}\n\n.dshr-caret {\n width: 12px;\n opacity: 0.6;\n}\n\n.dshr-count {\n margin-left: auto;\n font-size: 11px;\n opacity: 0.55;\n white-space: nowrap;\n}\n\n.dshr-sessions {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 0 10px 8px 30px;\n}\n\n.dshr-session {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 3px 0;\n font-size: 12px;\n flex-wrap: wrap;\n}\n\n.dshr-session > span:not(.dshr-live):not(.dshr-idle) {\n opacity: 0.62;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dshr-live,\n.dshr-idle {\n flex: none;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n transform: translateY(-1px);\n}\n\n.dshr-live {\n background: var(--dshr-success);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--dshr-success) 20%, transparent);\n}\n\n.dshr-idle {\n background: color-mix(in srgb, currentColor 28%, transparent);\n}\n\n@media (max-width: 720px) {\n .dshr-form {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .dshr-header {\n flex-direction: column;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dshr-button {\n transition: none;\n }\n}\n\n.dshr-detail {\n margin: 0;\n font-size: 12px;\n color: var(--dshr-warning);\n}\n\n.dshr-workspace-row {\n display: flex;\n align-items: center;\n gap: 6px;\n padding-right: 6px;\n}\n\n.dshr-workspace-row .dshr-workspace-head {\n flex: 1;\n}\n\n.dshr-button-small {\n flex: none;\n padding: 3px 8px;\n font-size: 11px;\n}\n\n.dshr-workspace-add {\n display: flex;\n gap: 6px;\n padding: 4px 0 6px;\n}\n\n.dshr-workspace-add input {\n flex: 1;\n min-width: 0;\n padding: 5px 8px;\n border: 1px solid var(--dshr-border);\n border-radius: 7px;\n background: var(--dshr-soft);\n color: inherit;\n font: inherit;\n font-size: 12px;\n outline: none;\n}\n\n.dshr-workspace-add input:focus {\n border-color: var(--dshr-accent);\n}\n";
124
+
125
+ // src/client/RemoteDevelopmentPanel.tsx
126
+ var import_jsx_runtime = require("react/jsx-runtime");
127
+ function phaseLabel(t, phase) {
128
+ return t(`phase.${phase}`);
129
+ }
130
+ var DETAIL_KEYS = {
131
+ "\u6B63\u5728\u901A\u8FC7 SSH \u53D1\u73B0\u8FDC\u7AEF agent\u2026": "detail.discovering",
132
+ "\u6B63\u5728\u8FDC\u7AEF\u5B89\u88C5 DSH \u4E0E dsh-remote-agent\uFF0C\u9996\u6B21\u7EA6 1\u20133 \u5206\u949F\u2026": "detail.installing",
133
+ "\u6B63\u5728\u5EFA\u7ACB SSH \u96A7\u9053\u5E76\u6821\u9A8C\u8FDC\u7AEF agent\u2026": "detail.connecting",
134
+ "\u6B63\u5728\u91CD\u65B0\u5B89\u88C5\u8FDC\u7AEF agent\u2026": "detail.reprovisioning",
135
+ "SSH \u96A7\u9053\u65AD\u5F00\uFF0C\u7B49\u5F85\u91CD\u8FDE\u2026": "detail.reconnecting",
136
+ "\u6B63\u5728\u51C6\u5907\u8FDC\u7AEF\u5B89\u88C5\u73AF\u5883\u2026": "detail.preparing",
137
+ "\u6B63\u5728\u4E0A\u4F20\u8FDC\u7AEF agent \u5B89\u88C5\u5305\u2026": "detail.uploadingAgent",
138
+ "\u5B89\u88C5\u5305\u4E0A\u4F20\u5B8C\u6210\uFF0C\u5F00\u59CB\u8FDC\u7AEF\u5B89\u88C5\u2026": "detail.uploaded",
139
+ "\u6B63\u5728\u4E0B\u8F7D Node.js\u2026": "detail.nodeDownloading",
140
+ "Node.js \u5B89\u88C5\u5B8C\u6210\uFF0C\u7EE7\u7EED\u5B89\u88C5 pnpm/DSH\u2026": "detail.nodeInstalled",
141
+ "Node.js \u5DF2\u5C31\u7EEA\uFF0C\u51C6\u5907\u5B89\u88C5 pnpm/DSH\u2026": "detail.nodeReady",
142
+ "\u6B63\u5728\u5B89\u88C5 pnpm\u2026": "detail.pnpmInstalling",
143
+ "\u6B63\u5728\u5B89\u88C5\u8FDC\u7AEF DSH\uFF08\u4E0B\u8F7D\u4F9D\u8D56\u53EF\u80FD\u9700\u8981\u51E0\u5206\u949F\uFF09\u2026": "detail.dshInstalling",
144
+ "\u6B63\u5728\u5B89\u88C5\u8FDC\u7AEF agent\u2026": "detail.agentInstalling",
145
+ "\u6B63\u5728\u542F\u52A8\u8FDC\u7AEF DSH Web \u670D\u52A1\u2026": "detail.webStarting",
146
+ "\u6B63\u5728\u7B49\u5F85\u8FDC\u7AEF agent \u72B6\u6001\u6587\u4EF6\u2026": "detail.waitingState",
147
+ "\u6B63\u5728\u7B49\u5F85\u8FDC\u7AEF agent \u72B6\u6001\u2026": "detail.waitingAgent",
148
+ "\u8FDC\u7AEF agent \u5DF2\u5C31\u7EEA\uFF0C\u6B63\u5728\u5EFA\u7ACB\u8FDE\u63A5\u2026": "detail.agentReady",
149
+ "\u8FDC\u7AEF\u5B89\u88C5\u5B8C\u6210\u2026": "detail.installed",
150
+ "\u8FDC\u7AEF\u5B89\u88C5\u5B8C\u6210\uFF0C\u6B63\u5728\u7B49\u5F85 DSH web \u542F\u52A8\u2026": "detail.waitingWeb",
151
+ "\u4ECD\u5728\u7B49\u5F85\u8FDC\u7AEF dsh web \u5C31\u7EEA\u2026": "detail.stillWaiting",
152
+ "\u6B63\u5728\u8BFB\u53D6\u8FDC\u7AEF agent \u72B6\u6001\u2026": "detail.readingState",
153
+ "\u6B63\u5728\u5EFA\u7ACB SSH \u96A7\u9053\u2026": "detail.tunnel",
154
+ "\u6B63\u5728\u6821\u9A8C\u8FDC\u7AEF agent\u2026": "detail.checkingAgent",
155
+ "\u6B63\u5728\u540C\u6B65\u8FDC\u7AEF\u6A21\u578B\u73AF\u5883\u2026": "detail.reconcilingModel",
156
+ "\u5DF2\u8FDE\u63A5\uFF1B\u6A21\u578B\u8BF7\u6C42\u7ECF\u672C\u5730\u51ED\u636E\u4EE3\u7406\u8F6C\u53D1": "detail.connectedProxy",
157
+ "\u5DF2\u8FDE\u63A5\uFF1B\u6A21\u578B\u8BF7\u6C42\u7531\u8FDC\u7AEF\u76F4\u8FDE DeepSeek": "detail.connectedDirect",
158
+ "\u5DF2\u8FDE\u63A5\uFF1B\u8FDC\u7AEF\u4F7F\u7528\u81EA\u8EAB\u51ED\u636E\u6216\u672A\u914D\u7F6E\u6A21\u578B key": "detail.connectedRemote"
159
+ };
160
+ function detailLabel(t, detail) {
161
+ const key = DETAIL_KEYS[detail];
162
+ return key === void 0 ? detail : t(key);
163
+ }
164
+ var POLL_MS = 2500;
165
+ function RemoteDevelopmentPanel({ api, t }) {
166
+ const [state, setState] = (0, import_react.useState)(null);
167
+ const [error, setError] = (0, import_react.useState)(null);
168
+ const [notice, setNotice] = (0, import_react.useState)(null);
169
+ const [busy, setBusy] = (0, import_react.useState)(null);
170
+ const [expandedHost, setExpandedHost] = (0, import_react.useState)(null);
171
+ const [workspaces, setWorkspaces] = (0, import_react.useState)({});
172
+ const [openWorkspace, setOpenWorkspace] = (0, import_react.useState)({});
173
+ const [sessions, setSessions] = (0, import_react.useState)({});
174
+ const [workspaceDraft, setWorkspaceDraft] = (0, import_react.useState)("");
175
+ const [form, setForm] = (0, import_react.useState)({
176
+ sshTarget: "",
177
+ label: ""
178
+ });
179
+ useInjectedStyle(panel_default);
180
+ const hosts = (0, import_react.useMemo)(() => state?.hosts ?? [], [state]);
181
+ (0, import_react.useEffect)(() => {
182
+ let cancelled = false;
183
+ const load = async () => {
184
+ try {
185
+ const next = await api.state();
186
+ if (!cancelled) {
187
+ setState(next);
188
+ setError((current) => current === t("settings.unavailable") ? null : current);
189
+ }
190
+ } catch (failure) {
191
+ if (!cancelled) setError(messageOf(failure, t("settings.unavailable")));
192
+ }
193
+ };
194
+ void load();
195
+ const timer = window.setInterval(() => {
196
+ void load();
197
+ }, POLL_MS);
198
+ const onFocus = () => {
199
+ void load();
200
+ };
201
+ window.addEventListener("focus", onFocus);
202
+ return () => {
203
+ cancelled = true;
204
+ window.clearInterval(timer);
205
+ window.removeEventListener("focus", onFocus);
206
+ };
207
+ }, [api, t]);
208
+ (0, import_react.useEffect)(() => {
209
+ const host = hosts.find((item) => item.id === expandedHost);
210
+ if (host?.phase === "connected" && workspaces[host.id] === void 0) {
211
+ void loadWorkspaces(host.id);
212
+ }
213
+ }, [hosts, expandedHost, workspaces]);
214
+ async function refresh() {
215
+ setState(await api.state());
216
+ }
217
+ async function run(key, task) {
218
+ setBusy(key);
219
+ setError(null);
220
+ setNotice(null);
221
+ try {
222
+ await task();
223
+ await refresh();
224
+ } catch (failure) {
225
+ setError(messageOf(failure, t("settings.operationFailed")));
226
+ } finally {
227
+ setBusy(null);
228
+ }
229
+ }
230
+ async function onAdd() {
231
+ const sshTarget = form.sshTarget.trim();
232
+ if (sshTarget === "") {
233
+ setError(t("settings.sshRequired"));
234
+ return;
235
+ }
236
+ await run("add", async () => {
237
+ await api.validateTarget(sshTarget);
238
+ const host = await api.addHost({
239
+ sshTarget,
240
+ ...form.label.trim() === "" ? {} : { label: form.label.trim() },
241
+ autoConnect: true
242
+ });
243
+ setForm({ sshTarget: "", label: "" });
244
+ setExpandedHost(host.id);
245
+ setNotice(t("settings.hostSaved"));
246
+ });
247
+ }
248
+ async function toggleHost(host) {
249
+ if (expandedHost === host.id) {
250
+ setExpandedHost(null);
251
+ return;
252
+ }
253
+ setExpandedHost(host.id);
254
+ await loadWorkspaces(host.id);
255
+ }
256
+ async function loadWorkspaces(hostId) {
257
+ setWorkspaces((current) => ({ ...current, [hostId]: void 0 }));
258
+ try {
259
+ const result = await api.workspaces(hostId);
260
+ setWorkspaces((current) => ({ ...current, [hostId]: [...result.workspaces] }));
261
+ } catch (failure) {
262
+ setWorkspaces((current) => ({ ...current, [hostId]: [] }));
263
+ setError(messageOf(failure, t("settings.readWorkspacesFailed")));
264
+ }
265
+ }
266
+ async function toggleWorkspace(hostId, workspaceId) {
267
+ const key = `${hostId}:${workspaceId}`;
268
+ const next = !(openWorkspace[key] ?? false);
269
+ setOpenWorkspace((current) => ({ ...current, [key]: next }));
270
+ if (!next || sessions[key] !== void 0) return;
271
+ setSessions((current) => ({ ...current, [key]: void 0 }));
272
+ try {
273
+ const result = await api.sessions(hostId, workspaceId);
274
+ setSessions((current) => ({ ...current, [key]: [...result.sessions] }));
275
+ } catch (failure) {
276
+ setSessions((current) => ({ ...current, [key]: [] }));
277
+ setError(messageOf(failure, t("settings.readSessionsFailed")));
278
+ }
279
+ }
280
+ async function addWorkspace(host) {
281
+ const path = workspaceDraft.trim();
282
+ if (path === "") {
283
+ setError(t("settings.pathRequired"));
284
+ return;
285
+ }
286
+ setBusy(`workspace:${host.id}`);
287
+ setError(null);
288
+ try {
289
+ await api.createWorkspace(host.id, { path });
290
+ setWorkspaceDraft("");
291
+ await loadWorkspaces(host.id);
292
+ setExpandedHost(host.id);
293
+ } catch (failure) {
294
+ setError(messageOf(failure, t("settings.addWorkspaceFailed")));
295
+ } finally {
296
+ setBusy(null);
297
+ }
298
+ }
299
+ async function newSession(host, workspaceId) {
300
+ setBusy(`create:${host.id}:${workspaceId}`);
301
+ setError(null);
302
+ try {
303
+ const created = await api.createSession(host.id, { workspaceId });
304
+ const key = `${host.id}:${workspaceId}`;
305
+ setOpenWorkspace((current) => ({ ...current, [key]: true }));
306
+ if (created.localSessionId !== void 0) {
307
+ api.openSession(created.localSessionId);
308
+ setNotice(t("settings.sessionOpening"));
309
+ window.setTimeout(() => {
310
+ document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true }));
311
+ }, 80);
312
+ } else {
313
+ setNotice(t("settings.sessionSyncing"));
314
+ }
315
+ const result = await api.sessions(host.id, workspaceId);
316
+ setSessions((current) => ({ ...current, [key]: [...result.sessions] }));
317
+ const refreshed = await api.workspaces(host.id);
318
+ setWorkspaces((current) => ({ ...current, [host.id]: [...refreshed.workspaces] }));
319
+ } catch (failure) {
320
+ setError(messageOf(failure, t("settings.createSessionFailed")));
321
+ } finally {
322
+ setBusy(null);
323
+ }
324
+ }
325
+ async function onRemove(host) {
326
+ if (!window.confirm(`${t("settings.confirmRemovePrefix")}${host.label}${t("settings.confirmRemoveSuffix")}`)) return;
327
+ await run(`remove:${host.id}`, async () => {
328
+ await api.removeHost(host.id);
329
+ if (expandedHost === host.id) setExpandedHost(null);
330
+ });
331
+ }
332
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshr-root", children: [
333
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "dshr-header", children: [
334
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
335
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: t("settings.title") }),
336
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "dshr-subtitle", children: [
337
+ t("settings.subtitle.lead"),
338
+ " ",
339
+ t("settings.subtitle.body")
340
+ ] })
341
+ ] }),
342
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
343
+ "button",
344
+ {
345
+ type: "button",
346
+ className: "dshr-button",
347
+ onClick: () => {
348
+ void refresh().catch((failure) => setError(messageOf(failure, t("settings.refreshFailed"))));
349
+ },
350
+ disabled: busy !== null,
351
+ children: t("settings.refresh")
352
+ }
353
+ )
354
+ ] }),
355
+ error !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dshr-banner dshr-banner-error", role: "alert", children: error }),
356
+ notice !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dshr-banner dshr-banner-info", role: "status", children: notice }),
357
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "dshr-card", children: [
358
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: t("settings.addHost") }),
359
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
360
+ "form",
361
+ {
362
+ className: "dshr-form",
363
+ onSubmit: (event) => {
364
+ event.preventDefault();
365
+ void onAdd();
366
+ },
367
+ children: [
368
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "dshr-field dshr-field-wide", children: [
369
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("settings.sshTarget") }),
370
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
371
+ "input",
372
+ {
373
+ value: form.sshTarget,
374
+ onChange: (event) => setForm((current) => ({ ...current, sshTarget: event.target.value })),
375
+ placeholder: t("settings.sshPlaceholder"),
376
+ autoComplete: "off",
377
+ spellCheck: false
378
+ }
379
+ )
380
+ ] }),
381
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "dshr-field", children: [
382
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("settings.label") }),
383
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
384
+ "input",
385
+ {
386
+ value: form.label,
387
+ onChange: (event) => setForm((current) => ({ ...current, label: event.target.value })),
388
+ placeholder: t("settings.labelPlaceholder")
389
+ }
390
+ )
391
+ ] }),
392
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshr-form-actions", children: [
393
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "submit", className: "dshr-button dshr-button-primary", disabled: busy !== null, children: busy === "add" ? t("settings.adding") : t("settings.saveConnect") }),
394
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshr-hint", children: t("settings.sshHint") })
395
+ ] })
396
+ ]
397
+ }
398
+ )
399
+ ] }),
400
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "dshr-card", children: [
401
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshr-section-title", children: [
402
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: t("settings.hosts") }),
403
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dshr-hint", children: [
404
+ hosts.length,
405
+ " ",
406
+ t("settings.hostsUnit")
407
+ ] })
408
+ ] }),
409
+ hosts.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dshr-empty", children: t("settings.noHosts") }),
410
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dshr-hosts", children: hosts.map((host) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("article", { className: "dshr-host", children: [
411
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "dshr-host-head", children: [
412
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshr-host-title", children: [
413
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: host.label }),
414
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: host.sshTarget })
415
+ ] }),
416
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `dshr-phase dshr-phase-${host.phase}`, children: phaseLabel(t, host.phase) })
417
+ ] }),
418
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", { className: "dshr-meta", children: [
419
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
420
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: t("settings.remotePort") }),
421
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: host.remoteAgentPort ?? "\u2014" })
422
+ ] }),
423
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
424
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: t("settings.localTunnel") }),
425
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: host.localPort ?? "\u2014" })
426
+ ] }),
427
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
428
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: t("settings.workspaces") }),
429
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: host.workspaceCount ?? "\u2014" })
430
+ ] }),
431
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
432
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Agent" }),
433
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: host.hostInfo?.agentVersion ?? "\u2014" })
434
+ ] }),
435
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
436
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: t("settings.token") }),
437
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: host.hasToken ? t("settings.saved") : t("settings.unsaved") })
438
+ ] }),
439
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
440
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: t("settings.modelProxy") }),
441
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: host.modelProxy ? t("settings.localProxy") : host.modelDirect ? t("settings.direct") : t("settings.remoteDefault") })
442
+ ] })
443
+ ] }),
444
+ host.detail !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dshr-detail", children: detailLabel(t, host.detail) }),
445
+ host.error !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dshr-host-error", role: "alert", children: host.error }),
446
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshr-actions", children: [
447
+ host.phase === "connected" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
448
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
449
+ "button",
450
+ {
451
+ type: "button",
452
+ className: "dshr-button",
453
+ disabled: busy !== null,
454
+ onClick: () => {
455
+ void toggleHost(host);
456
+ },
457
+ children: expandedHost === host.id ? t("settings.collapseWorkspaces") : t("settings.expandWorkspaces")
458
+ }
459
+ ),
460
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
461
+ "button",
462
+ {
463
+ type: "button",
464
+ className: "dshr-button",
465
+ disabled: busy !== null,
466
+ onClick: () => {
467
+ void run(`refresh:${host.id}`, async () => {
468
+ await api.refresh(host.id);
469
+ });
470
+ },
471
+ children: t("settings.rediscover")
472
+ }
473
+ ),
474
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
475
+ "button",
476
+ {
477
+ type: "button",
478
+ className: "dshr-button",
479
+ disabled: busy !== null,
480
+ onClick: () => {
481
+ void run(`disconnect:${host.id}`, async () => {
482
+ await api.disconnect(host.id);
483
+ });
484
+ },
485
+ children: t("settings.disconnect")
486
+ }
487
+ )
488
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
489
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
490
+ "button",
491
+ {
492
+ type: "button",
493
+ className: "dshr-button dshr-button-primary",
494
+ disabled: busy !== null || host.phase === "installing" || host.phase === "discovering" || host.phase === "connecting",
495
+ onClick: () => {
496
+ void run(`connect:${host.id}`, async () => {
497
+ await api.connect(host.id);
498
+ });
499
+ },
500
+ children: host.phase === "installing" ? t("settings.installing") : host.phase === "connecting" ? t("settings.connecting") : t("settings.connect")
501
+ }
502
+ ),
503
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
504
+ "button",
505
+ {
506
+ type: "button",
507
+ className: "dshr-button",
508
+ disabled: busy !== null,
509
+ onClick: () => {
510
+ void run(`refresh:${host.id}`, async () => {
511
+ await api.refresh(host.id);
512
+ });
513
+ },
514
+ children: t("settings.rediscover")
515
+ }
516
+ )
517
+ ] }),
518
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
519
+ "button",
520
+ {
521
+ type: "button",
522
+ className: "dshr-button",
523
+ disabled: busy !== null || host.phase === "installing",
524
+ onClick: () => {
525
+ void run(`provision:${host.id}`, async () => {
526
+ await api.provision(host.id);
527
+ });
528
+ },
529
+ children: t("settings.reprovision")
530
+ }
531
+ ),
532
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
533
+ "button",
534
+ {
535
+ type: "button",
536
+ className: "dshr-button dshr-button-danger",
537
+ disabled: busy !== null,
538
+ onClick: () => {
539
+ void onRemove(host);
540
+ },
541
+ children: t("settings.remove")
542
+ }
543
+ )
544
+ ] }),
545
+ expandedHost === host.id && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshr-workspaces", children: [
546
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshr-workspace-add", children: [
547
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
548
+ "input",
549
+ {
550
+ value: workspaceDraft,
551
+ onChange: (event) => setWorkspaceDraft(event.target.value),
552
+ placeholder: t("settings.workspacePathPlaceholder"),
553
+ spellCheck: false,
554
+ onKeyDown: (event) => {
555
+ if (event.key === "Enter") {
556
+ event.preventDefault();
557
+ void addWorkspace(host);
558
+ }
559
+ }
560
+ }
561
+ ),
562
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
563
+ "button",
564
+ {
565
+ type: "button",
566
+ className: "dshr-button dshr-button-small",
567
+ disabled: busy !== null,
568
+ onClick: () => {
569
+ void addWorkspace(host);
570
+ },
571
+ children: busy === `workspace:${host.id}` ? t("settings.addingWorkspace") : t("settings.addWorkspace")
572
+ }
573
+ )
574
+ ] }),
575
+ workspaces[host.id] === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dshr-empty", children: t("settings.readingWorkspaces") }) : workspaces[host.id]?.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dshr-empty", children: t("settings.noWorkspaces") }) : workspaces[host.id]?.map((workspace) => {
576
+ const workspaceKey = `${host.id}:${workspace.workspaceId}`;
577
+ const open = openWorkspace[workspaceKey] ?? false;
578
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshr-workspace", children: [
579
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshr-workspace-row", children: [
580
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
581
+ "button",
582
+ {
583
+ type: "button",
584
+ className: "dshr-workspace-head",
585
+ "aria-expanded": open,
586
+ onClick: () => {
587
+ void toggleWorkspace(host.id, workspace.workspaceId);
588
+ },
589
+ children: [
590
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshr-caret", children: open ? "\u25BE" : "\u25B8" }),
591
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: workspace.title }),
592
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: workspace.path }),
593
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dshr-count", children: [
594
+ workspace.sessionIds.length,
595
+ " ",
596
+ t("settings.sessionsUnit")
597
+ ] })
598
+ ]
599
+ }
600
+ ),
601
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
602
+ "button",
603
+ {
604
+ type: "button",
605
+ className: "dshr-button dshr-button-small",
606
+ disabled: busy !== null,
607
+ onClick: () => {
608
+ void newSession(host, workspace.workspaceId);
609
+ },
610
+ children: busy === `create:${host.id}:${workspace.workspaceId}` ? t("settings.creating") : t("settings.newSession")
611
+ }
612
+ )
613
+ ] }),
614
+ open && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dshr-sessions", children: sessions[workspaceKey] === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dshr-empty", children: t("settings.readingSessions") }) : sessions[workspaceKey]?.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dshr-empty", children: t("settings.noSessions") }) : sessions[workspaceKey]?.map((session) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshr-session", children: [
615
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: session.live ? "dshr-live" : "dshr-idle" }),
616
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: session.sessionId }),
617
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: session.cwd ?? "\u2014" }),
618
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dshr-count", children: [
619
+ session.eventCount ?? "\u2014",
620
+ " ",
621
+ t("settings.eventsUnit")
622
+ ] })
623
+ ] }, session.sessionId)) })
624
+ ] }, workspace.workspaceId);
625
+ })
626
+ ] })
627
+ ] }, host.id)) })
628
+ ] })
629
+ ] });
630
+ }
631
+ function useInjectedStyle(css) {
632
+ (0, import_react.useEffect)(() => {
633
+ const tag = document.createElement("style");
634
+ tag.dataset.plugin = "dsh-remote-local";
635
+ tag.textContent = css;
636
+ document.head.appendChild(tag);
637
+ return () => {
638
+ tag.remove();
639
+ };
640
+ }, [css]);
641
+ }
642
+ function messageOf(value, fallback) {
643
+ return value instanceof Error && value.message !== "" ? value.message : fallback;
644
+ }
645
+
646
+ // src/client/routing.ts
647
+ function installNativeWorkspaceRouting(ctx, api) {
648
+ const workspace = ctx.uiWorkspace;
649
+ if (workspace === void 0 || typeof workspace.startSession !== "function") return;
650
+ const original = workspace.startSession.bind(workspace);
651
+ workspace.startSession = (workspaceId, options) => {
652
+ if (workspaceId === void 0) {
653
+ original(workspaceId, options);
654
+ return;
655
+ }
656
+ void route(api, String(workspaceId)).then((routed) => {
657
+ if (!routed) original(workspaceId, options);
658
+ }).catch(() => {
659
+ original(workspaceId, options);
660
+ });
661
+ };
662
+ }
663
+ async function route(api, localWorkspaceId) {
664
+ const state = await api.state();
665
+ for (const host of state.hosts) {
666
+ if (host.phase !== "connected") continue;
667
+ const result = await api.workspaces(host.id);
668
+ const workspace = result.workspaces.find((item) => item.localWorkspaceId === localWorkspaceId);
669
+ if (workspace === void 0) continue;
670
+ const created = await api.createSession(host.id, { workspaceId: workspace.workspaceId });
671
+ if (created.localSessionId !== void 0) {
672
+ api.openSession(created.localSessionId);
673
+ return true;
674
+ }
675
+ }
676
+ return false;
677
+ }
678
+
679
+ // src/client/remote-files.ts
680
+ function installRemoteFiles(ctx, api) {
681
+ const remote = ctx.remote;
682
+ const face = remote?.workspaceFiles;
683
+ if (face === void 0) return;
684
+ const original = {
685
+ list: face.list.bind(face),
686
+ read: face.read.bind(face),
687
+ stat: face.stat.bind(face),
688
+ readBytes: face.readBytes.bind(face)
689
+ };
690
+ face.list = async (sessionId, path, signal) => {
691
+ const result = await api.fileList(sessionId, path);
692
+ return result.mirrored ? { ok: true, value: result.value } : original.list(sessionId, path, signal);
693
+ };
694
+ face.read = async (sessionId, path, range, signal) => {
695
+ const result = await api.fileRead(sessionId, path, range.offset, range.limit);
696
+ return result.mirrored ? { ok: true, value: result.value } : original.read(sessionId, path, range, signal);
697
+ };
698
+ face.stat = async (sessionId, path, signal) => {
699
+ const result = await api.fileStat(sessionId, path);
700
+ return result.mirrored ? { ok: true, value: result.value } : original.stat(sessionId, path, signal);
701
+ };
702
+ face.readBytes = async (sessionId, path, options, signal) => {
703
+ const result = await api.fileBytes(sessionId, path, options.range?.offset, options.range?.length);
704
+ return result.mirrored ? { ok: true, value: result.value } : original.readBytes(sessionId, path, options, signal);
705
+ };
706
+ }
707
+
708
+ // src/client/locale.ts
709
+ var NS = "dshRemote";
710
+ var zh = {
711
+ "settings.title": "\u8FDC\u7A0B\u5F00\u53D1",
712
+ "settings.subtitle.lead": "\u53EA\u9700\u586B\u5199 SSH \u76EE\u6807\u3002\u63D2\u4EF6\u4F1A\u81EA\u52A8\u5B89\u88C5\u5E76\u542F\u52A8\u8FDC\u7AEF agent\uFF0C\u5EFA\u7ACB\u96A7\u9053\u3002",
713
+ "settings.subtitle.body": "\u8FDE\u63A5\u540E\u8FDC\u7AEF\u5DE5\u4F5C\u533A\u548C\u4F1A\u8BDD\u4F1A\u540C\u6B65\u5230\u5DE6\u4FA7\uFF0C\u50CF\u672C\u5730\u4F1A\u8BDD\u4E00\u6837\u6253\u5F00\uFF1B\u6A21\u578B\u51ED\u636E\u9ED8\u8BA4\u5199\u5165\u8FDC\u7AEF\u670D\u52A1\u73AF\u5883\uFF1B\u4E5F\u53EF\u542F\u7528\u672C\u5730\u4EE3\u7406\u6A21\u5F0F\u3002",
714
+ "settings.refresh": "\u5237\u65B0",
715
+ "settings.addHost": "\u6DFB\u52A0\u4E3B\u673A",
716
+ "settings.sshTarget": "SSH \u76EE\u6807",
717
+ "settings.sshPlaceholder": "user@host \u6216 ssh://user@host:22",
718
+ "settings.label": "\u663E\u793A\u540D\u79F0\uFF08\u53EF\u9009\uFF09",
719
+ "settings.labelPlaceholder": "\u53EF\u9009\uFF0C\u4F8B\u5982 \u5F00\u53D1\u673A",
720
+ "settings.saveConnect": "\u4FDD\u5B58\u5E76\u8FDE\u63A5",
721
+ "settings.adding": "\u6DFB\u52A0\u4E2D\u2026",
722
+ "settings.sshHint": "\u9700\u8981\u5BC6\u94A5\u6216 ssh-agent \u514D\u4EA4\u4E92\u767B\u5F55\uFF1B\u9996\u6B21\u81EA\u52A8\u5B89\u88C5\u7EA6 1\u20133 \u5206\u949F\u3002",
723
+ "settings.hosts": "\u4E3B\u673A",
724
+ "settings.hostsUnit": "\u53F0",
725
+ "settings.noHosts": "\u8FD8\u6CA1\u6709\u8FDC\u7A0B\u4E3B\u673A\u3002\u5148\u5728\u4E0A\u65B9\u6DFB\u52A0\u4E00\u53F0\u3002",
726
+ "settings.refreshFailed": "\u5237\u65B0\u5931\u8D25",
727
+ "settings.unavailable": "\u65E0\u6CD5\u8FDE\u63A5\u672C\u5730\u8FDC\u7A0B\u5BBF\u4E3B\u670D\u52A1",
728
+ "settings.operationFailed": "\u64CD\u4F5C\u5931\u8D25",
729
+ "settings.sshRequired": "\u8BF7\u586B\u5199 SSH \u76EE\u6807\uFF0C\u4F8B\u5982 user@host \u6216 ssh://user@host:22",
730
+ "settings.hostSaved": "\u4E3B\u673A\u5DF2\u4FDD\u5B58\u3002\u63D2\u4EF6\u4F1A\u81EA\u52A8\u5B89\u88C5\u5E76\u542F\u52A8\u8FDC\u7AEF agent\uFF1B\u5B89\u88C5\u8FDB\u5EA6\u4F1A\u663E\u793A\u5728\u4E3B\u673A\u5361\u7247\u4E0A\u3002",
731
+ "settings.readWorkspacesFailed": "\u8BFB\u53D6\u8FDC\u7AEF\u5DE5\u4F5C\u533A\u5931\u8D25",
732
+ "settings.readSessionsFailed": "\u8BFB\u53D6\u8FDC\u7AEF\u4F1A\u8BDD\u5931\u8D25",
733
+ "settings.pathRequired": "\u8BF7\u586B\u5199\u8FDC\u7AEF\u76EE\u5F55\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u4F8B\u5982 /home/user/project",
734
+ "settings.addWorkspaceFailed": "\u6DFB\u52A0\u8FDC\u7AEF\u5DE5\u4F5C\u533A\u5931\u8D25",
735
+ "settings.sessionOpening": "\u8FDC\u7AEF\u4F1A\u8BDD\u5DF2\u521B\u5EFA\uFF0C\u6B63\u5728\u666E\u901A\u4F1A\u8BDD\u754C\u9762\u6253\u5F00\u2026",
736
+ "settings.sessionSyncing": "\u8FDC\u7AEF\u4F1A\u8BDD\u5DF2\u521B\u5EFA\uFF0C\u6B63\u5728\u540C\u6B65\u5230\u4FA7\u8FB9\u680F\u2026",
737
+ "settings.createSessionFailed": "\u521B\u5EFA\u8FDC\u7AEF\u4F1A\u8BDD\u5931\u8D25",
738
+ "settings.confirmRemovePrefix": "\u5220\u9664\u4E3B\u673A\u300C",
739
+ "settings.confirmRemoveSuffix": "\u300D\uFF1F\u8FDC\u7AEF\u6587\u4EF6\u4E0E\u4F1A\u8BDD\u4E0D\u4F1A\u88AB\u5220\u9664\u3002",
740
+ "settings.remotePort": "\u8FDC\u7AEF\u7AEF\u53E3",
741
+ "settings.localTunnel": "\u672C\u5730\u96A7\u9053",
742
+ "settings.workspaces": "\u5DE5\u4F5C\u533A",
743
+ "settings.token": "Token",
744
+ "settings.saved": "\u5DF2\u4FDD\u5B58",
745
+ "settings.unsaved": "\u672A\u4FDD\u5B58",
746
+ "settings.modelProxy": "\u6A21\u578B\u4EE3\u7406",
747
+ "settings.localProxy": "\u672C\u5730\u4EE3\u7406",
748
+ "settings.direct": "\u8FDC\u7AEF\u76F4\u8FDE",
749
+ "settings.remoteDefault": "\u8FDC\u7AEF\u9ED8\u8BA4",
750
+ "settings.collapseWorkspaces": "\u6536\u8D77\u5DE5\u4F5C\u533A",
751
+ "settings.expandWorkspaces": "\u5DE5\u4F5C\u533A",
752
+ "settings.rediscover": "\u91CD\u65B0\u53D1\u73B0",
753
+ "settings.disconnect": "\u65AD\u5F00",
754
+ "settings.connect": "\u8FDE\u63A5",
755
+ "settings.installing": "\u6B63\u5728\u5B89\u88C5\u2026",
756
+ "settings.connecting": "\u6B63\u5728\u8FDE\u63A5\u2026",
757
+ "settings.reprovision": "\u91CD\u65B0\u5B89\u88C5",
758
+ "settings.remove": "\u5220\u9664",
759
+ "settings.workspacePathPlaceholder": "\u8FDC\u7AEF\u76EE\u5F55\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u4F8B\u5982 /home/user/project",
760
+ "settings.addWorkspace": "\u6DFB\u52A0\u5DE5\u4F5C\u533A",
761
+ "settings.addingWorkspace": "\u6DFB\u52A0\u4E2D\u2026",
762
+ "settings.readingWorkspaces": "\u6B63\u5728\u8BFB\u53D6\u5DE5\u4F5C\u533A\u2026",
763
+ "settings.noWorkspaces": "\u8FDC\u7AEF\u8FD8\u6CA1\u6709\u6CE8\u518C\u5DE5\u4F5C\u533A\u3002\u53EF\u5728\u8FDC\u7AEF DSH \u91CC\u6DFB\u52A0\u76EE\u5F55\u540E\u5237\u65B0\u3002",
764
+ "settings.sessionsUnit": "\u4E2A\u4F1A\u8BDD",
765
+ "settings.newSession": "+ \u65B0\u4F1A\u8BDD",
766
+ "settings.creating": "\u521B\u5EFA\u4E2D\u2026",
767
+ "settings.readingSessions": "\u6B63\u5728\u8BFB\u53D6\u4F1A\u8BDD\u2026",
768
+ "settings.noSessions": "\u8BE5\u5DE5\u4F5C\u533A\u6682\u65E0\u4F1A\u8BDD\u3002\u70B9\u201C+ \u65B0\u4F1A\u8BDD\u201D\u5F00\u59CB\u3002",
769
+ "settings.eventsUnit": "\u4E8B\u4EF6",
770
+ "phase.idle": "\u672A\u8FDE\u63A5",
771
+ "phase.discovering": "\u53D1\u73B0\u4E2D",
772
+ "phase.installing": "\u5B89\u88C5\u4E2D",
773
+ "phase.connecting": "\u8FDE\u63A5\u4E2D",
774
+ "phase.connected": "\u5DF2\u8FDE\u63A5",
775
+ "phase.disconnected": "\u5DF2\u65AD\u5F00",
776
+ "phase.error": "\u5F02\u5E38",
777
+ "detail.discovering": "\u6B63\u5728\u901A\u8FC7 SSH \u53D1\u73B0\u8FDC\u7AEF agent\u2026",
778
+ "detail.installing": "\u6B63\u5728\u8FDC\u7AEF\u5B89\u88C5 DSH \u4E0E dsh-remote-agent\uFF0C\u9996\u6B21\u7EA6 1\u20133 \u5206\u949F\u2026",
779
+ "detail.connecting": "\u6B63\u5728\u5EFA\u7ACB SSH \u96A7\u9053\u5E76\u6821\u9A8C\u8FDC\u7AEF agent\u2026",
780
+ "detail.reprovisioning": "\u6B63\u5728\u91CD\u65B0\u5B89\u88C5\u8FDC\u7AEF agent\u2026",
781
+ "detail.reconnecting": "SSH \u96A7\u9053\u65AD\u5F00\uFF0C\u7B49\u5F85\u91CD\u8FDE\u2026",
782
+ "detail.connectedProxy": "\u5DF2\u8FDE\u63A5\uFF1B\u6A21\u578B\u8BF7\u6C42\u7ECF\u672C\u5730\u51ED\u636E\u4EE3\u7406\u8F6C\u53D1",
783
+ "detail.connectedDirect": "\u5DF2\u8FDE\u63A5\uFF1B\u6A21\u578B\u8BF7\u6C42\u7531\u8FDC\u7AEF\u76F4\u8FDE DeepSeek",
784
+ "detail.connectedRemote": "\u5DF2\u8FDE\u63A5\uFF1B\u8FDC\u7AEF\u4F7F\u7528\u81EA\u8EAB\u51ED\u636E\u6216\u672A\u914D\u7F6E\u6A21\u578B key",
785
+ "detail.preparing": "\u6B63\u5728\u51C6\u5907\u8FDC\u7AEF\u5B89\u88C5\u73AF\u5883\u2026",
786
+ "detail.uploadingAgent": "\u6B63\u5728\u4E0A\u4F20\u8FDC\u7AEF agent \u5B89\u88C5\u5305\u2026",
787
+ "detail.uploaded": "\u5B89\u88C5\u5305\u4E0A\u4F20\u5B8C\u6210\uFF0C\u5F00\u59CB\u8FDC\u7AEF\u5B89\u88C5\u2026",
788
+ "detail.nodeDownloading": "\u6B63\u5728\u4E0B\u8F7D Node.js\u2026",
789
+ "detail.nodeInstalled": "Node.js \u5B89\u88C5\u5B8C\u6210\uFF0C\u7EE7\u7EED\u5B89\u88C5 pnpm/DSH\u2026",
790
+ "detail.nodeReady": "Node.js \u5DF2\u5C31\u7EEA\uFF0C\u51C6\u5907\u5B89\u88C5 pnpm/DSH\u2026",
791
+ "detail.pnpmInstalling": "\u6B63\u5728\u5B89\u88C5 pnpm\u2026",
792
+ "detail.dshInstalling": "\u6B63\u5728\u5B89\u88C5\u8FDC\u7AEF DSH\uFF08\u4E0B\u8F7D\u4F9D\u8D56\u53EF\u80FD\u9700\u8981\u51E0\u5206\u949F\uFF09\u2026",
793
+ "detail.agentInstalling": "\u6B63\u5728\u5B89\u88C5\u8FDC\u7AEF agent\u2026",
794
+ "detail.webStarting": "\u6B63\u5728\u542F\u52A8\u8FDC\u7AEF DSH Web \u670D\u52A1\u2026",
795
+ "detail.waitingState": "\u6B63\u5728\u7B49\u5F85\u8FDC\u7AEF agent \u72B6\u6001\u6587\u4EF6\u2026",
796
+ "detail.waitingAgent": "\u6B63\u5728\u7B49\u5F85\u8FDC\u7AEF agent \u72B6\u6001\u2026",
797
+ "detail.agentReady": "\u8FDC\u7AEF agent \u5DF2\u5C31\u7EEA\uFF0C\u6B63\u5728\u5EFA\u7ACB\u8FDE\u63A5\u2026",
798
+ "detail.installed": "\u8FDC\u7AEF\u5B89\u88C5\u5B8C\u6210\u2026",
799
+ "detail.waitingWeb": "\u8FDC\u7AEF\u5B89\u88C5\u5B8C\u6210\uFF0C\u6B63\u5728\u7B49\u5F85 DSH web \u542F\u52A8\u2026",
800
+ "detail.stillWaiting": "\u4ECD\u5728\u7B49\u5F85\u8FDC\u7AEF dsh web \u5C31\u7EEA\u2026",
801
+ "detail.readingState": "\u6B63\u5728\u8BFB\u53D6\u8FDC\u7AEF agent \u72B6\u6001\u2026",
802
+ "detail.tunnel": "\u6B63\u5728\u5EFA\u7ACB SSH \u96A7\u9053\u2026",
803
+ "detail.checkingAgent": "\u6B63\u5728\u6821\u9A8C\u8FDC\u7AEF agent\u2026",
804
+ "detail.reconcilingModel": "\u6B63\u5728\u540C\u6B65\u8FDC\u7AEF\u6A21\u578B\u73AF\u5883\u2026",
805
+ "dialog.title": "\u65B0\u5EFA\u5DE5\u4F5C\u533A",
806
+ "dialog.close": "\u5173\u95ED",
807
+ "dialog.name": "\u5DE5\u4F5C\u533A\u540D\u79F0",
808
+ "dialog.namePlaceholder": "\u5DE5\u4F5C\u533A\u540D\u79F0\uFF08\u53EF\u9009\uFF09",
809
+ "dialog.source": "\u6E90\u76EE\u5F55",
810
+ "dialog.addToPrefix": "\u5C06\u76EE\u5F55\u6DFB\u52A0\u5230 ",
811
+ "dialog.addToSuffix": "",
812
+ "dialog.add": "\u6DFB\u52A0",
813
+ "dialog.local": "\u672C\u673A",
814
+ "dialog.remoteDevices": "\u8FDC\u7AEF\u8BBE\u5907",
815
+ "dialog.addRemote": "+ \u6DFB\u52A0\u8FDC\u7AEF\u2026",
816
+ "dialog.addRemoteHint": "\u8BF7\u5148\u5728 Settings \u2192 \u8FDC\u7A0B\u5F00\u53D1 \u6DFB\u52A0\u5E76\u8FDE\u63A5\u8FDC\u7AEF\u4E3B\u673A\u3002",
817
+ "dialog.cancel": "\u53D6\u6D88",
818
+ "dialog.create": "\u521B\u5EFA\u5DE5\u4F5C\u533A",
819
+ "dialog.creating": "\u521B\u5EFA\u4E2D\u2026",
820
+ "dialog.createFailed": "\u65B0\u5EFA\u5DE5\u4F5C\u533A\u5931\u8D25",
821
+ "dialog.removeFolder": "\u79FB\u9664\u76EE\u5F55",
822
+ "dialog.selectedFallback": "\u8FDC\u7AEF workspace \u5DF2\u521B\u5EFA\uFF0C\u4F46\u672C\u5730\u955C\u50CF\u8FD8\u6CA1\u6709\u540C\u6B65\u5B8C\u6210",
823
+ "dialog.folderTitle": "\u9009\u62E9\u6E90\u76EE\u5F55",
824
+ "dialog.up": "\u4E0A\u4E00\u7EA7",
825
+ "dialog.selectFolder": "\u9009\u62E9\u5F53\u524D\u76EE\u5F55",
826
+ "dialog.reading": "\u6B63\u5728\u8BFB\u53D6\u76EE\u5F55\u2026",
827
+ "dialog.emptyFolder": "\u6B64\u76EE\u5F55\u6CA1\u6709\u5B50\u76EE\u5F55",
828
+ "dialog.readFailed": "\u8BFB\u53D6\u76EE\u5F55\u5931\u8D25",
829
+ "dialog.nativePickerFailed": "\u65E0\u6CD5\u6253\u5F00\u7CFB\u7EDF\u76EE\u5F55\u9009\u62E9\u5668",
830
+ "dialog.useFolder": "\u4F7F\u7528\u6B64\u76EE\u5F55"
831
+ };
832
+ var en = {
833
+ "settings.title": "Remote development",
834
+ "settings.subtitle.lead": "Enter an SSH target. The plugin installs and starts the remote agent and opens a tunnel.",
835
+ "settings.subtitle.body": "Remote workspaces and sessions sync into the normal sidebar and open like local sessions. The model key is written into the remote service environment by default; local proxying remains optional.",
836
+ "settings.refresh": "Refresh",
837
+ "settings.addHost": "Add host",
838
+ "settings.sshTarget": "SSH target",
839
+ "settings.sshPlaceholder": "user@host or ssh://user@host:22",
840
+ "settings.label": "Display name (optional)",
841
+ "settings.labelPlaceholder": "Optional, e.g. dev box",
842
+ "settings.saveConnect": "Save and connect",
843
+ "settings.adding": "Adding\u2026",
844
+ "settings.sshHint": "Requires key or ssh-agent authentication. First install takes about 1\u20133 minutes.",
845
+ "settings.hosts": "Hosts",
846
+ "settings.hostsUnit": "",
847
+ "settings.noHosts": "No remote hosts yet. Add one above.",
848
+ "settings.refreshFailed": "Refresh failed",
849
+ "settings.unavailable": "Cannot reach the local remote-host service",
850
+ "settings.operationFailed": "Operation failed",
851
+ "settings.sshRequired": "Enter an SSH target, e.g. user@host or ssh://user@host:22",
852
+ "settings.hostSaved": "Host saved. The plugin will install and start the remote agent; progress appears on the host card.",
853
+ "settings.readWorkspacesFailed": "Could not read remote workspaces",
854
+ "settings.readSessionsFailed": "Could not read remote sessions",
855
+ "settings.pathRequired": "Enter an absolute remote directory, e.g. /home/user/project",
856
+ "settings.addWorkspaceFailed": "Could not add the remote workspace",
857
+ "settings.sessionOpening": "Remote session created; opening it in the conversation view\u2026",
858
+ "settings.sessionSyncing": "Remote session created; syncing it into the sidebar\u2026",
859
+ "settings.createSessionFailed": "Could not create the remote session",
860
+ "settings.confirmRemovePrefix": 'Remove host "',
861
+ "settings.confirmRemoveSuffix": '"? Remote files and sessions are not deleted.',
862
+ "settings.remotePort": "Remote port",
863
+ "settings.localTunnel": "Local tunnel",
864
+ "settings.workspaces": "Workspaces",
865
+ "settings.token": "Token",
866
+ "settings.saved": "Saved",
867
+ "settings.unsaved": "Not saved",
868
+ "settings.modelProxy": "Model credentials",
869
+ "settings.localProxy": "Local proxy",
870
+ "settings.direct": "Remote direct",
871
+ "settings.remoteDefault": "Remote default",
872
+ "settings.collapseWorkspaces": "Hide workspaces",
873
+ "settings.expandWorkspaces": "Workspaces",
874
+ "settings.rediscover": "Rediscover",
875
+ "settings.disconnect": "Disconnect",
876
+ "settings.connect": "Connect",
877
+ "settings.installing": "Installing\u2026",
878
+ "settings.connecting": "Connecting\u2026",
879
+ "settings.reprovision": "Reinstall",
880
+ "settings.remove": "Remove",
881
+ "settings.workspacePathPlaceholder": "Absolute remote path, e.g. /home/user/project",
882
+ "settings.addWorkspace": "Add workspace",
883
+ "settings.addingWorkspace": "Adding\u2026",
884
+ "settings.readingWorkspaces": "Reading workspaces\u2026",
885
+ "settings.noWorkspaces": "No remote workspaces registered yet. Add a directory in the remote DSH, then refresh.",
886
+ "settings.sessionsUnit": "sessions",
887
+ "settings.newSession": "+ New session",
888
+ "settings.creating": "Creating\u2026",
889
+ "settings.readingSessions": "Reading sessions\u2026",
890
+ "settings.noSessions": "No sessions in this workspace. Start one with \u201C+ New session\u201D.",
891
+ "settings.eventsUnit": "events",
892
+ "phase.idle": "Idle",
893
+ "phase.discovering": "Discovering",
894
+ "phase.installing": "Installing",
895
+ "phase.connecting": "Connecting",
896
+ "phase.connected": "Connected",
897
+ "phase.disconnected": "Disconnected",
898
+ "phase.error": "Error",
899
+ "detail.discovering": "Discovering the remote agent over SSH\u2026",
900
+ "detail.installing": "Installing DSH and dsh-remote-agent remotely; this takes about 1\u20133 minutes\u2026",
901
+ "detail.connecting": "Opening the SSH tunnel and checking the remote agent\u2026",
902
+ "detail.reprovisioning": "Reinstalling the remote agent\u2026",
903
+ "detail.reconnecting": "SSH tunnel closed; waiting to reconnect\u2026",
904
+ "detail.connectedProxy": "Connected; model requests go through the local credential proxy",
905
+ "detail.connectedDirect": "Connected; the remote host calls DeepSeek directly",
906
+ "detail.connectedRemote": "Connected; the remote host uses its own credentials or has no model key",
907
+ "detail.preparing": "Preparing the remote installation environment\u2026",
908
+ "detail.uploadingAgent": "Uploading the remote agent package\u2026",
909
+ "detail.uploaded": "Package uploaded; starting the remote installation\u2026",
910
+ "detail.nodeDownloading": "Downloading Node.js\u2026",
911
+ "detail.nodeInstalled": "Node.js installed; continuing with pnpm/DSH\u2026",
912
+ "detail.nodeReady": "Node.js is ready; preparing pnpm/DSH\u2026",
913
+ "detail.pnpmInstalling": "Installing pnpm\u2026",
914
+ "detail.dshInstalling": "Installing remote DSH (dependency download may take several minutes)\u2026",
915
+ "detail.agentInstalling": "Installing the remote agent\u2026",
916
+ "detail.webStarting": "Starting the remote DSH Web service\u2026",
917
+ "detail.waitingState": "Waiting for the remote agent state file\u2026",
918
+ "detail.waitingAgent": "Waiting for the remote agent\u2026",
919
+ "detail.agentReady": "Remote agent is ready; establishing the connection\u2026",
920
+ "detail.installed": "Remote installation complete\u2026",
921
+ "detail.waitingWeb": "Remote installation complete; waiting for DSH Web\u2026",
922
+ "detail.stillWaiting": "Still waiting for remote DSH Web\u2026",
923
+ "detail.readingState": "Reading the remote agent state\u2026",
924
+ "detail.tunnel": "Establishing the SSH tunnel\u2026",
925
+ "detail.checkingAgent": "Checking the remote agent\u2026",
926
+ "detail.reconcilingModel": "Synchronizing the remote model environment\u2026",
927
+ "dialog.title": "New workspace",
928
+ "dialog.close": "Close",
929
+ "dialog.name": "Workspace name",
930
+ "dialog.namePlaceholder": "Workspace name (optional)",
931
+ "dialog.source": "Source folder",
932
+ "dialog.addToPrefix": "Add a folder on ",
933
+ "dialog.addToSuffix": "",
934
+ "dialog.add": "Add",
935
+ "dialog.local": "This computer",
936
+ "dialog.remoteDevices": "Remote devices",
937
+ "dialog.addRemote": "+ Add remote\u2026",
938
+ "dialog.addRemoteHint": "Add and connect a remote host in Settings \u2192 Remote development first.",
939
+ "dialog.cancel": "Cancel",
940
+ "dialog.create": "Create workspace",
941
+ "dialog.creating": "Creating\u2026",
942
+ "dialog.createFailed": "Could not create the workspace",
943
+ "dialog.removeFolder": "Remove folder",
944
+ "dialog.selectedFallback": "The remote workspace was created, but the local mirror has not synced yet",
945
+ "dialog.folderTitle": "Choose a source folder",
946
+ "dialog.up": "Parent folder",
947
+ "dialog.selectFolder": "Select current folder",
948
+ "dialog.reading": "Reading folder\u2026",
949
+ "dialog.emptyFolder": "No subfolders here",
950
+ "dialog.readFailed": "Could not read the folder",
951
+ "dialog.nativePickerFailed": "Could not open the system folder picker",
952
+ "dialog.useFolder": "Use folder"
953
+ };
954
+
955
+ // src/client/workspace-add.ts
956
+ var ADD_LABELS = /* @__PURE__ */ new Set(["Add workspace", "\u6DFB\u52A0\u5DE5\u4F5C\u533A"]);
957
+ var NATIVE_CLICK_FLAG = "dshrNativeClick";
958
+ var DIALOG_CLASS = "dshr-workspace-add-dialog";
959
+ var closeDialog;
960
+ var closeBrowser;
961
+ var closeMenu;
962
+ async function pickLocalDirectory(ctx) {
963
+ const desktop = globalThis.__DSH_DIRECTORY_PICKER__;
964
+ if (desktop !== void 0) return desktop.pick();
965
+ const uiWorkspace = ctx.uiWorkspace;
966
+ if (uiWorkspace?.pickDirectory !== void 0) return uiWorkspace.pickDirectory();
967
+ const remote = ctx.remote;
968
+ if (remote?.directoryPicker === void 0) throw new Error("directory picker is unavailable");
969
+ const result = await remote.directoryPicker.pick();
970
+ if (!result.ok) throw new Error(result.error.message);
971
+ return result.value;
972
+ }
973
+ function installWorkspaceAddMenu(ctx, api, t) {
974
+ if (typeof document === "undefined") return;
975
+ ensureStyles();
976
+ ctx.effect(() => {
977
+ const onClick = (event) => {
978
+ const target = event.target;
979
+ if (!(target instanceof Element)) return;
980
+ const button = target.closest("button[aria-label]");
981
+ if (button === null) return;
982
+ const label = button.getAttribute("aria-label") ?? "";
983
+ if (!ADD_LABELS.has(label)) return;
984
+ if (button.closest('[class*="headerActions"]') === null) return;
985
+ if (button.dataset[NATIVE_CLICK_FLAG] === "1") {
986
+ delete button.dataset[NATIVE_CLICK_FLAG];
987
+ return;
988
+ }
989
+ event.preventDefault();
990
+ event.stopImmediatePropagation();
991
+ void openCreateDialog({ ctx, api, t }, button);
992
+ };
993
+ document.addEventListener("click", onClick, true);
994
+ return () => {
995
+ document.removeEventListener("click", onClick, true);
996
+ closeTransient();
997
+ };
998
+ }, "dsh-remote: workspace add dialog");
999
+ }
1000
+ async function openCreateDialog(flow, anchor) {
1001
+ closeTransient();
1002
+ const overlay = document.createElement("div");
1003
+ overlay.className = `${DIALOG_CLASS}-overlay`;
1004
+ overlay.setAttribute("role", "presentation");
1005
+ const dialog = document.createElement("div");
1006
+ dialog.className = DIALOG_CLASS;
1007
+ dialog.setAttribute("role", "dialog");
1008
+ dialog.setAttribute("aria-modal", "true");
1009
+ dialog.setAttribute("aria-label", flow.t("dialog.title"));
1010
+ const title = document.createElement("h2");
1011
+ title.className = `${DIALOG_CLASS}-title`;
1012
+ title.textContent = flow.t("dialog.title");
1013
+ const header = document.createElement("div");
1014
+ header.className = `${DIALOG_CLASS}-header`;
1015
+ const closeButton = document.createElement("button");
1016
+ closeButton.type = "button";
1017
+ closeButton.className = `${DIALOG_CLASS}-close`;
1018
+ closeButton.setAttribute("aria-label", flow.t("dialog.close"));
1019
+ closeButton.textContent = "\xD7";
1020
+ header.append(title, closeButton);
1021
+ const nameLabel = document.createElement("label");
1022
+ nameLabel.className = `${DIALOG_CLASS}-field`;
1023
+ const nameText = document.createElement("span");
1024
+ nameText.textContent = flow.t("dialog.name");
1025
+ const nameRow = document.createElement("div");
1026
+ nameRow.className = `${DIALOG_CLASS}-input-row`;
1027
+ const nameIcon = document.createElement("span");
1028
+ nameIcon.className = `${DIALOG_CLASS}-input-icon`;
1029
+ setFolderIcon(nameIcon);
1030
+ const nameInput = document.createElement("input");
1031
+ nameInput.className = `${DIALOG_CLASS}-input`;
1032
+ nameInput.placeholder = flow.t("dialog.namePlaceholder");
1033
+ nameInput.autocomplete = "off";
1034
+ nameRow.append(nameIcon, nameInput);
1035
+ nameLabel.append(nameText, nameRow);
1036
+ const sourceTitle = document.createElement("h3");
1037
+ sourceTitle.className = `${DIALOG_CLASS}-section-title`;
1038
+ sourceTitle.textContent = flow.t("dialog.source");
1039
+ const sourceHeader = document.createElement("div");
1040
+ sourceHeader.className = `${DIALOG_CLASS}-source-header`;
1041
+ const hostIndicator = document.createElement("span");
1042
+ hostIndicator.className = `${DIALOG_CLASS}-host-indicator`;
1043
+ const hostIcon = document.createElement("span");
1044
+ hostIcon.className = `${DIALOG_CLASS}-host-icon`;
1045
+ setComputerIcon(hostIcon);
1046
+ const hostLabelText = document.createElement("span");
1047
+ hostIndicator.append(hostIcon, hostLabelText);
1048
+ sourceHeader.append(sourceTitle, hostIndicator);
1049
+ const sourceBox = document.createElement("div");
1050
+ sourceBox.className = `${DIALOG_CLASS}-source-box`;
1051
+ const sourceCenter = document.createElement("div");
1052
+ sourceCenter.className = `${DIALOG_CLASS}-source-center`;
1053
+ const sourceTrigger = document.createElement("button");
1054
+ sourceTrigger.type = "button";
1055
+ sourceTrigger.className = `${DIALOG_CLASS}-source-trigger`;
1056
+ const sourceTriggerText = document.createElement("span");
1057
+ const sourceChevron = document.createElement("span");
1058
+ sourceChevron.className = `${DIALOG_CLASS}-chevron`;
1059
+ sourceChevron.textContent = "\u2304";
1060
+ sourceTrigger.append(sourceTriggerText, sourceChevron);
1061
+ const addFolder = document.createElement("button");
1062
+ addFolder.type = "button";
1063
+ addFolder.className = `${DIALOG_CLASS}-add-folder`;
1064
+ const addFolderIcon = document.createElement("span");
1065
+ addFolderIcon.className = `${DIALOG_CLASS}-add-folder-icon`;
1066
+ const addFolderText = document.createElement("span");
1067
+ addFolderText.textContent = flow.t("dialog.add");
1068
+ addFolder.append(addFolderIcon, addFolderText);
1069
+ sourceCenter.append(sourceTrigger, addFolder);
1070
+ const selectedFolder = document.createElement("div");
1071
+ selectedFolder.className = `${DIALOG_CLASS}-selected-folder`;
1072
+ const selectedIcon = document.createElement("span");
1073
+ selectedIcon.textContent = "\u{1F4C1}";
1074
+ const selectedName = document.createElement("span");
1075
+ selectedName.className = `${DIALOG_CLASS}-selected-name`;
1076
+ const removeFolder = document.createElement("button");
1077
+ removeFolder.type = "button";
1078
+ removeFolder.className = `${DIALOG_CLASS}-remove-folder`;
1079
+ removeFolder.setAttribute("aria-label", flow.t("dialog.removeFolder"));
1080
+ removeFolder.textContent = "\xD7";
1081
+ selectedFolder.append(selectedIcon, selectedName, removeFolder);
1082
+ sourceBox.append(sourceCenter, selectedFolder);
1083
+ const error = document.createElement("p");
1084
+ error.className = `${DIALOG_CLASS}-error`;
1085
+ error.setAttribute("role", "alert");
1086
+ const actions = document.createElement("div");
1087
+ actions.className = `${DIALOG_CLASS}-actions`;
1088
+ const cancel = document.createElement("button");
1089
+ cancel.type = "button";
1090
+ cancel.className = `${DIALOG_CLASS}-button`;
1091
+ cancel.textContent = flow.t("dialog.cancel");
1092
+ const create = document.createElement("button");
1093
+ create.type = "button";
1094
+ create.className = `${DIALOG_CLASS}-button ${DIALOG_CLASS}-primary`;
1095
+ create.textContent = flow.t("dialog.create");
1096
+ actions.append(cancel, create);
1097
+ dialog.append(header, nameLabel, sourceHeader, sourceBox, error, actions);
1098
+ overlay.append(dialog);
1099
+ document.body.append(overlay);
1100
+ let hosts = [];
1101
+ let destination = { kind: "local" };
1102
+ let selectedPath = "";
1103
+ const update = () => {
1104
+ const label = destination.kind === "local" ? flow.t("dialog.local") : destination.host.label;
1105
+ sourceTriggerText.textContent = `${flow.t("dialog.addToPrefix")}${label}${flow.t("dialog.addToSuffix")}`;
1106
+ hostLabelText.textContent = label;
1107
+ addFolderIcon.textContent = "+ ";
1108
+ const hasFolder = selectedPath !== "";
1109
+ sourceCenter.style.display = hasFolder ? "none" : "flex";
1110
+ selectedFolder.style.display = hasFolder ? "flex" : "none";
1111
+ sourceBox.classList.toggle(`${DIALOG_CLASS}-source-box-selected`, hasFolder);
1112
+ selectedName.textContent = hasFolder ? folderName(selectedPath) : "";
1113
+ selectedFolder.title = hasFolder ? selectedPath : "";
1114
+ create.disabled = !hasFolder;
1115
+ };
1116
+ const close = () => {
1117
+ closeBrowser?.();
1118
+ closeMenu?.();
1119
+ overlay.remove();
1120
+ window.removeEventListener("keydown", keyHandler, true);
1121
+ overlay.removeEventListener("pointerdown", outside, true);
1122
+ if (closeDialog === close) closeDialog = void 0;
1123
+ };
1124
+ closeDialog = close;
1125
+ const keyHandler = (event) => {
1126
+ if (event.key === "Escape") close();
1127
+ };
1128
+ const outside = (event) => {
1129
+ if (event.target === overlay) close();
1130
+ };
1131
+ window.addEventListener("keydown", keyHandler, true);
1132
+ overlay.addEventListener("pointerdown", outside, true);
1133
+ cancel.addEventListener("click", close);
1134
+ closeButton.addEventListener("click", close);
1135
+ sourceTrigger.addEventListener("click", () => {
1136
+ openDestinationMenu(sourceTrigger, hosts, destination, flow.t, (next) => {
1137
+ destination = next;
1138
+ selectedPath = "";
1139
+ update();
1140
+ });
1141
+ });
1142
+ addFolder.addEventListener("click", () => {
1143
+ if (destination.kind === "local") {
1144
+ addFolder.disabled = true;
1145
+ void pickLocalDirectory(flow.ctx).then((path) => {
1146
+ if (path !== null) {
1147
+ selectedPath = path;
1148
+ update();
1149
+ }
1150
+ }).catch((failure) => {
1151
+ error.textContent = messageOf2(failure, flow.t("dialog.nativePickerFailed"));
1152
+ }).finally(() => {
1153
+ addFolder.disabled = false;
1154
+ });
1155
+ return;
1156
+ }
1157
+ const initial = destination.host.hostInfo?.home ?? "~";
1158
+ openDirectoryBrowser(flow, destination, initial, (path) => {
1159
+ selectedPath = path;
1160
+ update();
1161
+ });
1162
+ });
1163
+ removeFolder.addEventListener("click", () => {
1164
+ selectedPath = "";
1165
+ update();
1166
+ });
1167
+ nameInput.addEventListener("input", update);
1168
+ try {
1169
+ const state = await flow.api.state();
1170
+ hosts = state.hosts.filter((host) => host.phase === "connected");
1171
+ } catch {
1172
+ hosts = [];
1173
+ }
1174
+ update();
1175
+ create.addEventListener("click", () => {
1176
+ if (selectedPath === "") return;
1177
+ const title2 = nameInput.value.trim() || folderName(selectedPath);
1178
+ create.disabled = true;
1179
+ cancel.disabled = true;
1180
+ create.textContent = flow.t("dialog.creating");
1181
+ void (async () => {
1182
+ try {
1183
+ if (destination.kind === "local") {
1184
+ const local = await flow.api.createLocalWorkspace({ path: selectedPath, title: title2 });
1185
+ close();
1186
+ flow.ctx.uiWorkspace.startSession(local.workspaceId);
1187
+ return;
1188
+ }
1189
+ const workspace = await flow.api.createWorkspace(destination.host.id, {
1190
+ path: selectedPath,
1191
+ title: title2
1192
+ });
1193
+ const session = await flow.api.createSession(destination.host.id, {
1194
+ workspaceId: workspace.workspaceId
1195
+ });
1196
+ if (session.localSessionId !== void 0) {
1197
+ close();
1198
+ flow.api.openSession(session.localSessionId);
1199
+ return;
1200
+ }
1201
+ const localId = await waitForLocalWorkspace(flow.api, destination.host.id, workspace.workspaceId);
1202
+ if (localId === void 0) throw new Error(flow.t("dialog.selectedFallback"));
1203
+ close();
1204
+ flow.ctx.uiWorkspace.startSession(localId);
1205
+ } catch (failure) {
1206
+ error.textContent = messageOf2(failure, flow.t("dialog.createFailed"));
1207
+ create.disabled = false;
1208
+ cancel.disabled = false;
1209
+ create.textContent = flow.t("dialog.create");
1210
+ }
1211
+ })();
1212
+ });
1213
+ nameInput.focus();
1214
+ }
1215
+ function openDestinationMenu(anchor, hosts, current, t, onPick) {
1216
+ closeMenu?.();
1217
+ const menu = document.createElement("div");
1218
+ menu.className = `${DIALOG_CLASS}-destination-menu`;
1219
+ const add = (label, destination) => {
1220
+ const item = document.createElement("button");
1221
+ item.type = "button";
1222
+ item.className = `${DIALOG_CLASS}-destination-item`;
1223
+ const text = document.createElement("span");
1224
+ text.textContent = label;
1225
+ const check = document.createElement("span");
1226
+ check.className = `${DIALOG_CLASS}-destination-check`;
1227
+ const selected = current.kind === destination.kind && (destination.kind === "local" || current.kind === "remote" && current.host.id === destination.host.id);
1228
+ check.textContent = selected ? "\u2713" : "";
1229
+ item.append(text, check);
1230
+ item.addEventListener("click", () => {
1231
+ onPick(destination);
1232
+ close();
1233
+ });
1234
+ menu.append(item);
1235
+ };
1236
+ add(t("dialog.local"), { kind: "local" });
1237
+ if (hosts.length > 0) {
1238
+ const section = document.createElement("div");
1239
+ section.className = `${DIALOG_CLASS}-destination-section`;
1240
+ section.textContent = t("dialog.remoteDevices");
1241
+ menu.append(section);
1242
+ for (const host of hosts) add(host.label, { kind: "remote", host });
1243
+ }
1244
+ const addRemote = document.createElement("button");
1245
+ addRemote.type = "button";
1246
+ addRemote.className = `${DIALOG_CLASS}-destination-item ${DIALOG_CLASS}-destination-add`;
1247
+ addRemote.textContent = t("dialog.addRemote");
1248
+ addRemote.addEventListener("click", () => {
1249
+ close();
1250
+ const error = document.querySelector(`.${DIALOG_CLASS}-error`);
1251
+ if (error !== null) error.textContent = t("dialog.addRemoteHint");
1252
+ });
1253
+ menu.append(addRemote);
1254
+ document.body.append(menu);
1255
+ const rect = anchor.getBoundingClientRect();
1256
+ menu.style.left = `${Math.min(rect.left, window.innerWidth - 260)}px`;
1257
+ menu.style.top = `${rect.bottom + 6}px`;
1258
+ const close = () => {
1259
+ menu.remove();
1260
+ window.removeEventListener("pointerdown", dismiss, true);
1261
+ if (closeMenu === close) closeMenu = void 0;
1262
+ };
1263
+ closeMenu = close;
1264
+ const dismiss = (event) => {
1265
+ if (event.target instanceof Node && menu.contains(event.target)) return;
1266
+ close();
1267
+ };
1268
+ window.addEventListener("pointerdown", dismiss, true);
1269
+ }
1270
+ async function openDirectoryBrowser(flow, destination, initialPath, onPick) {
1271
+ closeBrowser?.();
1272
+ const overlay = document.createElement("div");
1273
+ overlay.className = `${DIALOG_CLASS}-browser-overlay`;
1274
+ overlay.setAttribute("role", "presentation");
1275
+ const dialog = document.createElement("div");
1276
+ dialog.className = `${DIALOG_CLASS} ${DIALOG_CLASS}-browser`;
1277
+ dialog.setAttribute("role", "dialog");
1278
+ dialog.setAttribute("aria-modal", "true");
1279
+ dialog.setAttribute("aria-label", flow.t("dialog.folderTitle"));
1280
+ const title = document.createElement("h2");
1281
+ title.className = `${DIALOG_CLASS}-browser-title`;
1282
+ title.textContent = flow.t("dialog.folderTitle");
1283
+ const browserHeader = document.createElement("div");
1284
+ browserHeader.className = `${DIALOG_CLASS}-browser-header`;
1285
+ const browserClose = document.createElement("button");
1286
+ browserClose.type = "button";
1287
+ browserClose.className = `${DIALOG_CLASS}-close`;
1288
+ browserClose.setAttribute("aria-label", flow.t("dialog.close"));
1289
+ browserClose.textContent = "\xD7";
1290
+ browserHeader.append(title, browserClose);
1291
+ const pathRow = document.createElement("div");
1292
+ pathRow.className = `${DIALOG_CLASS}-browser-path-row`;
1293
+ const up = document.createElement("button");
1294
+ up.type = "button";
1295
+ up.className = `${DIALOG_CLASS}-up`;
1296
+ up.textContent = "\u2191";
1297
+ up.setAttribute("aria-label", flow.t("dialog.up"));
1298
+ const pathInput = document.createElement("input");
1299
+ pathInput.className = `${DIALOG_CLASS}-browser-path`;
1300
+ pathInput.value = initialPath;
1301
+ pathInput.autocomplete = "off";
1302
+ pathRow.append(up, pathInput);
1303
+ const status = document.createElement("p");
1304
+ status.className = `${DIALOG_CLASS}-browser-status`;
1305
+ status.textContent = flow.t("dialog.selectFolder");
1306
+ const list = document.createElement("div");
1307
+ list.className = `${DIALOG_CLASS}-browser-list`;
1308
+ const actions = document.createElement("div");
1309
+ actions.className = `${DIALOG_CLASS}-actions`;
1310
+ const cancel = document.createElement("button");
1311
+ cancel.type = "button";
1312
+ cancel.className = `${DIALOG_CLASS}-button`;
1313
+ cancel.textContent = flow.t("dialog.cancel");
1314
+ const use = document.createElement("button");
1315
+ use.type = "button";
1316
+ use.className = `${DIALOG_CLASS}-button ${DIALOG_CLASS}-primary`;
1317
+ use.textContent = flow.t("dialog.useFolder");
1318
+ actions.append(cancel, use);
1319
+ dialog.append(browserHeader, pathRow, status, list, actions);
1320
+ overlay.append(dialog);
1321
+ document.body.append(overlay);
1322
+ let current = initialPath;
1323
+ let parent = initialPath;
1324
+ let currentEntries = [];
1325
+ let requestSeq = 0;
1326
+ let filterTimer;
1327
+ const fetchListing = async (path) => {
1328
+ const sequence = ++requestSeq;
1329
+ status.textContent = flow.t("dialog.reading");
1330
+ status.classList.remove(`${DIALOG_CLASS}-error-text`);
1331
+ try {
1332
+ const listing = destination.kind === "local" ? await flow.api.listLocalDirectories(path) : await flow.api.listDirectories(destination.host.id, path);
1333
+ if (sequence !== requestSeq) return void 0;
1334
+ return listing;
1335
+ } catch (failure) {
1336
+ if (sequence === requestSeq) {
1337
+ status.textContent = messageOf2(failure, flow.t("dialog.readFailed"));
1338
+ status.classList.add(`${DIALOG_CLASS}-error-text`);
1339
+ }
1340
+ return void 0;
1341
+ }
1342
+ };
1343
+ const render = (entries) => {
1344
+ list.replaceChildren();
1345
+ if (entries.length === 0) {
1346
+ const empty = document.createElement("p");
1347
+ empty.className = `${DIALOG_CLASS}-browser-empty`;
1348
+ empty.textContent = flow.t("dialog.emptyFolder");
1349
+ list.append(empty);
1350
+ return;
1351
+ }
1352
+ for (const entry of entries) {
1353
+ const item = document.createElement("button");
1354
+ item.type = "button";
1355
+ item.className = `${DIALOG_CLASS}-browser-entry`;
1356
+ const icon = document.createElement("span");
1357
+ icon.textContent = "\u{1F4C1}";
1358
+ const label = document.createElement("span");
1359
+ label.textContent = entry.name;
1360
+ item.append(icon, label);
1361
+ item.addEventListener("click", () => {
1362
+ void load(entry.path);
1363
+ });
1364
+ list.append(item);
1365
+ }
1366
+ };
1367
+ const load = async (path) => {
1368
+ const listing = await fetchListing(path);
1369
+ if (listing === void 0) return;
1370
+ current = listing.path;
1371
+ parent = listing.parent;
1372
+ currentEntries = listing.entries;
1373
+ pathInput.value = listing.path;
1374
+ status.textContent = flow.t("dialog.selectFolder");
1375
+ render(listing.entries);
1376
+ };
1377
+ const applyInputFilter = async (raw) => {
1378
+ if (raw === "") return;
1379
+ const slash = raw.lastIndexOf("/");
1380
+ const dir = slash < 0 ? current : raw.slice(0, slash + 1) || "/";
1381
+ const query = slash < 0 ? raw : raw.slice(slash + 1);
1382
+ if (query === "") {
1383
+ await load(raw);
1384
+ return;
1385
+ }
1386
+ const normalizedCurrent = current.replace(/\/+$/u, "") || "/";
1387
+ const normalizedDir = dir.replace(/\/+$/u, "") || "/";
1388
+ if (normalizedDir === normalizedCurrent) {
1389
+ const needle2 = query.toLocaleLowerCase();
1390
+ render(currentEntries.filter((entry) => entry.name.toLocaleLowerCase().startsWith(needle2)));
1391
+ status.textContent = flow.t("dialog.selectFolder");
1392
+ return;
1393
+ }
1394
+ const listing = await fetchListing(dir);
1395
+ if (listing === void 0) return;
1396
+ current = listing.path;
1397
+ parent = listing.parent;
1398
+ currentEntries = listing.entries;
1399
+ const needle = query.toLocaleLowerCase();
1400
+ render(listing.entries.filter((entry) => entry.name.toLocaleLowerCase().startsWith(needle)));
1401
+ status.textContent = flow.t("dialog.selectFolder");
1402
+ };
1403
+ const close = () => {
1404
+ overlay.remove();
1405
+ window.removeEventListener("keydown", keyHandler, true);
1406
+ overlay.removeEventListener("pointerdown", outside, true);
1407
+ if (closeBrowser === close) closeBrowser = void 0;
1408
+ };
1409
+ closeBrowser = close;
1410
+ const keyHandler = (event) => {
1411
+ if (event.key === "Escape") close();
1412
+ };
1413
+ const outside = (event) => {
1414
+ if (event.target === overlay) close();
1415
+ };
1416
+ window.addEventListener("keydown", keyHandler, true);
1417
+ overlay.addEventListener("pointerdown", outside, true);
1418
+ cancel.addEventListener("click", close);
1419
+ browserClose.addEventListener("click", close);
1420
+ use.addEventListener("click", () => {
1421
+ onPick(current);
1422
+ close();
1423
+ });
1424
+ up.addEventListener("click", () => {
1425
+ if (parent !== "" && parent !== current) void load(parent);
1426
+ });
1427
+ pathInput.addEventListener("input", () => {
1428
+ if (filterTimer !== void 0) window.clearTimeout(filterTimer);
1429
+ const value = pathInput.value;
1430
+ filterTimer = window.setTimeout(() => {
1431
+ void applyInputFilter(value);
1432
+ }, 140);
1433
+ });
1434
+ pathInput.addEventListener("keydown", (event) => {
1435
+ if (event.key !== "Enter") return;
1436
+ event.preventDefault();
1437
+ const value = pathInput.value.trim();
1438
+ if (value !== "") void applyInputFilter(value);
1439
+ });
1440
+ void load(initialPath);
1441
+ }
1442
+ async function waitForLocalWorkspace(api, hostId, workspaceId) {
1443
+ for (let attempt = 0; attempt < 40; attempt += 1) {
1444
+ const result = await api.workspaces(hostId);
1445
+ const workspace = result.workspaces.find((item) => item.workspaceId === workspaceId);
1446
+ if (workspace?.localWorkspaceId !== void 0) return workspace.localWorkspaceId;
1447
+ await new Promise((resolve) => setTimeout(resolve, 500));
1448
+ }
1449
+ return void 0;
1450
+ }
1451
+ function closeTransient() {
1452
+ closeDialog?.();
1453
+ closeBrowser?.();
1454
+ closeMenu?.();
1455
+ }
1456
+ function messageOf2(failure, fallback) {
1457
+ if (failure instanceof Error && failure.message !== "") return failure.message;
1458
+ return fallback;
1459
+ }
1460
+ function setFolderIcon(icon) {
1461
+ icon.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M3.5 7.5A2.5 2.5 0 0 1 6 5h4l2 2h6a2.5 2.5 0 0 1 2.5 2.5v7A2.5 2.5 0 0 1 18 19H6a2.5 2.5 0 0 1-2.5-2.5v-9Z" stroke="currentColor" stroke-width="1.5"/></svg>';
1462
+ }
1463
+ function folderName(path) {
1464
+ const clean = path.replace(/[\\/]+$/u, "");
1465
+ const parts = clean.split(/[\\/]+/u);
1466
+ return parts[parts.length - 1] ?? clean;
1467
+ }
1468
+ function setComputerIcon(icon) {
1469
+ icon.innerHTML = '<svg width="17" height="17" viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="3.5" y="5" width="17" height="12" rx="1.7" stroke="currentColor" stroke-width="1.5"/><path d="M8 20h8M12 17v3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>';
1470
+ }
1471
+ function ensureStyles() {
1472
+ const existing = document.getElementById("dshr-workspace-add-style");
1473
+ const style = existing instanceof HTMLStyleElement ? existing : document.createElement("style");
1474
+ style.id = "dshr-workspace-add-style";
1475
+ style.textContent = `
1476
+ .${DIALOG_CLASS}-overlay {
1477
+ position: fixed; inset: 0; z-index: 100001; display: grid; place-items: center;
1478
+ padding: 24px; background: rgba(0,0,0,.55);
1479
+ }
1480
+ .${DIALOG_CLASS} {
1481
+ width: min(632px, calc(100vw - 48px));
1482
+ box-sizing: border-box; padding: 26px; display: flex; flex-direction: column;
1483
+ border-radius: 14px; border: 1px solid rgba(255,255,255,.1);
1484
+ background: #242424; color: #f2f2f2; box-shadow: 0 28px 80px rgba(0,0,0,.65);
1485
+ font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1486
+ }
1487
+ .${DIALOG_CLASS}-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 18px; }
1488
+ .${DIALOG_CLASS}-title { margin: 0; font-size: 28px; line-height: 1.1; font-weight: 700; letter-spacing: -.02em; }
1489
+ .${DIALOG_CLASS}-close { border: 0; background: transparent; color: rgba(255,255,255,.75); font-size: 30px; line-height: 1; cursor: pointer; padding: 0 2px; }
1490
+ .${DIALOG_CLASS}-close:hover { color: #fff; }
1491
+ .${DIALOG_CLASS}-field { display: flex; flex-direction: column; gap: 7px; margin-bottom: 14px; }
1492
+ .${DIALOG_CLASS}-field > span { font-size: 13px; color: rgba(255,255,255,.72); }
1493
+ .${DIALOG_CLASS}-input-row { display: flex; align-items: center; gap: 10px; border: 1px solid rgba(255,255,255,.24); border-radius: 9px; padding: 0 12px; background: transparent; }
1494
+ .${DIALOG_CLASS}-input-icon { display: inline-flex; color: rgba(255,255,255,.7); }
1495
+ .${DIALOG_CLASS}-input { width: 100%; border: 0; outline: none; background: transparent; color: inherit; font: inherit; font-size: 14px; padding: 13px 0; }
1496
+ .${DIALOG_CLASS}-source-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 4px 0 10px; }
1497
+ .${DIALOG_CLASS}-section-title { margin: 0; font-size: 17px; font-weight: 600; color: rgba(255,255,255,.92); }
1498
+ .${DIALOG_CLASS}-host-indicator { display: inline-flex; align-items: center; gap: 8px; color: rgba(255,255,255,.62); font-size: 15px; }
1499
+ .${DIALOG_CLASS}-host-icon { display: inline-flex; }
1500
+ .${DIALOG_CLASS}-source-box { position: relative; border: 1px solid rgba(255,255,255,.16); border-radius: 9px; padding: 24px 16px; min-height: 150px; flex: none; display: flex; flex-direction: column; align-items: center; justify-content: center; }
1501
+ .${DIALOG_CLASS}-source-center { display: flex; flex-direction: column; align-items: center; gap: 16px; }
1502
+ .${DIALOG_CLASS}-source-trigger { display: inline-flex; align-items: center; gap: 8px; border: 0; background: transparent; color: rgba(255,255,255,.75); font: inherit; font-size: 15px; cursor: pointer; }
1503
+ .${DIALOG_CLASS}-source-trigger span:first-child { color: #fff; font-weight: 600; }
1504
+ .${DIALOG_CLASS}-chevron { font-size: 18px; line-height: 1; }
1505
+ .${DIALOG_CLASS}-add-folder { display: inline-flex; align-items: center; gap: 8px; border: 0; border-radius: 8px; padding: 8px 14px; background: rgba(255,255,255,.12); color: #fff; font: inherit; font-size: 14px; cursor: pointer; }
1506
+ .${DIALOG_CLASS}-add-folder:hover { background: rgba(255,255,255,.18); }
1507
+ .${DIALOG_CLASS}-add-folder-icon { font-size: 18px; line-height: 1; }
1508
+ .${DIALOG_CLASS}-source-box-selected { padding: 0; min-height: 0; flex: none; justify-content: flex-start; }
1509
+ .${DIALOG_CLASS}-selected-folder { display: none; align-items: center; gap: 12px; width: 100%; box-sizing: border-box; padding: 12px 14px; color: rgba(255,255,255,.92); font-size: 16px; }
1510
+ .${DIALOG_CLASS}-selected-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1511
+ .${DIALOG_CLASS}-remove-folder { flex: none; border: 0; background: transparent; color: rgba(255,255,255,.6); font-size: 24px; line-height: 1; cursor: pointer; padding: 0 2px; }
1512
+ .${DIALOG_CLASS}-remove-folder:hover { color: #fff; }
1513
+ .${DIALOG_CLASS}-error { margin: 6px 0 0; color: #ff8b8b; font-size: 12px; white-space: pre-wrap; }
1514
+ .${DIALOG_CLASS}-error:empty { display: none; }
1515
+ .${DIALOG_CLASS}-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; }
1516
+ .${DIALOG_CLASS}-button { padding: 11px 18px; border-radius: 8px; border: 1px solid rgba(255,255,255,.18); background: transparent; color: #eee; cursor: pointer; font: inherit; font-size: 15px; }
1517
+ .${DIALOG_CLASS}-button:disabled { opacity: .45; cursor: default; }
1518
+ .${DIALOG_CLASS}-primary { border-color: #bdbdbd; background: #bdbdbd; color: #1a1a1a; font-weight: 600; }
1519
+ .${DIALOG_CLASS}-primary:hover:not(:disabled) { background: #d0d0d0; }
1520
+ .${DIALOG_CLASS}-destination-menu {
1521
+ position: fixed; z-index: 100010; min-width: 250px; padding: 6px;
1522
+ border-radius: 12px; border: 1px solid rgba(255,255,255,.14); background: #2a2a2a; color: #eee;
1523
+ box-shadow: 0 18px 48px rgba(0,0,0,.55); font: 14px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1524
+ }
1525
+ .${DIALOG_CLASS}-destination-item { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: 10px 12px; border: 0; border-radius: 8px; background: transparent; color: inherit; font: inherit; cursor: pointer; text-align: left; }
1526
+ .${DIALOG_CLASS}-destination-item:hover { background: rgba(255,255,255,.09); }
1527
+ .${DIALOG_CLASS}-destination-section { padding: 14px 12px 6px; color: rgba(255,255,255,.5); font-size: 13px; }
1528
+ .${DIALOG_CLASS}-destination-check { color: #bdbdbd; }
1529
+ .${DIALOG_CLASS}-destination-add { color: rgba(255,255,255,.75); }
1530
+ .${DIALOG_CLASS}-browser-overlay { position: fixed; inset: 0; z-index: 100002; display: grid; place-items: center; background: rgba(0,0,0,.6); padding: 24px; }
1531
+ .${DIALOG_CLASS}-browser { width: 640px; max-height: 86vh; display: flex; flex-direction: column; padding: 26px; }
1532
+ .${DIALOG_CLASS}-browser-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 20px; }
1533
+ .${DIALOG_CLASS}-browser-title { margin: 0; font-size: 30px; line-height: 1.15; font-weight: 700; }
1534
+ .${DIALOG_CLASS}-browser-path-row { display: flex; align-items: center; gap: 8px; }
1535
+ .${DIALOG_CLASS}-up { flex: none; width: 34px; height: 34px; border: 0; background: transparent; color: rgba(255,255,255,.65); font-size: 22px; cursor: pointer; }
1536
+ .${DIALOG_CLASS}-browser-path { flex: 1; border: 1px solid rgba(255,255,255,.22); border-radius: 9px; background: transparent; color: #fff; font: inherit; font-size: 16px; padding: 12px 14px; outline: none; }
1537
+ .${DIALOG_CLASS}-browser-path:focus { border-color: #4f8cff; }
1538
+ .${DIALOG_CLASS}-browser-status { margin: 14px 0 8px; color: rgba(255,255,255,.55); font-size: 13px; }
1539
+ .${DIALOG_CLASS}-browser-list { flex: 1; min-height: 220px; max-height: 420px; overflow: auto; border: 1px solid rgba(255,255,255,.16); border-radius: 12px; padding: 6px; }
1540
+ .${DIALOG_CLASS}-browser-entry { display: flex; align-items: center; gap: 12px; width: 100%; box-sizing: border-box; padding: 11px 12px; border: 0; border-radius: 8px; background: transparent; color: #fff; font: inherit; font-size: 16px; text-align: left; cursor: pointer; }
1541
+ .${DIALOG_CLASS}-browser-entry:hover { background: rgba(255,255,255,.08); }
1542
+ .${DIALOG_CLASS}-browser-empty { margin: 16px 12px; color: rgba(255,255,255,.5); font-size: 13px; }
1543
+ `;
1544
+ if (existing === null) document.head.append(style);
1545
+ }
1546
+
1547
+ // src/client/index.tsx
1548
+ var inject = ["slots", "uiWorkspace", "locale"];
1549
+ function apply(ctx) {
1550
+ const api = createHostsApi(ctx);
1551
+ const locale = ctx.locale;
1552
+ ctx.effect(() => locale.register(NS, { zh, en }), "dsh-remote: locale dictionaries");
1553
+ const t = locale.bind(NS);
1554
+ installNativeWorkspaceRouting(ctx, api);
1555
+ installWorkspaceAddMenu(ctx, api, t);
1556
+ ctx.inject(["remote.workspaceFiles"], (child) => {
1557
+ installRemoteFiles(child, api);
1558
+ });
1559
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
1560
+ name: "settings.section",
1561
+ id: "remote-development",
1562
+ order: 76,
1563
+ label: () => t("settings.title"),
1564
+ locale: NS,
1565
+ inject: () => ({ api })
1566
+ }, RemoteDevelopmentPanel));
1567
+ }
1568
+ return module.exports;
1569
+ },
1570
+ });
1571
+ //# sourceMappingURL=client.js.map