@yanglaofish/dsh-proxy-pro 1.0.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/lib/client.js ADDED
@@ -0,0 +1,457 @@
1
+ /**
2
+ * dsh-proxy-pro — browser face.
3
+ *
4
+ * Two contributions:
5
+ * 1. A capsule proxy button in the conversation header
6
+ * (`conversation.session.header.utilities`): green/red status dot with a
7
+ * live label, one click toggles the master `enabled` switch.
8
+ * 2. A dedicated Settings page (`settings.section`, label 代理管理): runtime
9
+ * facts (active / url / reason — from the host's /dsh-proxy-pro/api,
10
+ * NOT derived from settings), the editable config fields (mode / custom
11
+ * URL / NO_PROXY), and a per-URL route diagnostic.
12
+ *
13
+ * Both read and write the `proxy` settings namespace through the shared
14
+ * `ctx.settingsScope` transport (same as dsh-plugin-proxy); runtime facts
15
+ * come from the same-origin /dsh-proxy-pro/api endpoints served by the host
16
+ * half (browser-trust fenced, see lib/index.js).
17
+ *
18
+ * This hand-written module mirrors the loader format the in-repo client
19
+ * bundles emit (window.__ModuleLoader__.load with a CommonJS factory): react
20
+ * is required through the app's module table, no JSX, no bundler, no TS.
21
+ */
22
+ window.__ModuleLoader__.load({
23
+ id: "dsh-proxy-pro/client",
24
+ factory: (require) => {
25
+ var module = { exports: {} };
26
+ var exports = module.exports;
27
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
28
+ var react = require("react");
29
+ var useState = react.useState;
30
+ var useEffect = react.useEffect;
31
+ var useRef = react.useRef;
32
+ var useSyncExternalStore = react.useSyncExternalStore;
33
+
34
+ var cssId = "dsh-proxy-pro/client";
35
+ var css = [
36
+ ".dsxprx{display:flex;flex-direction:column;gap:14px}",
37
+ ".dsxprx-status{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-specific-tip)}",
38
+ ".dsxprx-dot{flex:none;width:10px;height:10px;border-radius:50%;background:var(--dsw-alias-state-error-primary)}",
39
+ ".dsxprx-status[data-active=true] .dsxprx-dot{background:var(--dsw-alias-state-success-primary)}",
40
+ ".dsxprx-statusbody{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}",
41
+ ".dsxprx-statusline{font-size:13px;font-weight:600;line-height:18px;color:var(--dsw-alias-label-primary)}",
42
+ ".dsxprx-subline{font-size:11px;line-height:15px;color:var(--dsw-alias-label-caption);word-break:break-all}",
43
+ ".dsxprx-switchwrap{flex:none;display:flex;align-items:center;gap:6px;cursor:pointer}",
44
+ ".dsxprx-switch{position:relative;width:36px;height:20px;border-radius:999px;background:var(--dsw-alias-border-l1);transition:background .15s ease;flex:none}",
45
+ ".dsxprx-switch[data-on=true]{background:#4d6bfe}",
46
+ ".dsxprx-switch::after{content:'';position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .15s ease}",
47
+ ".dsxprx-switch[data-on=true]::after{transform:translateX(16px)}",
48
+ ".dsxprx-switchwrap input{position:absolute;opacity:0;width:0;height:0}",
49
+ ".dsxprx-switchwrap[data-disabled=true]{opacity:.5;cursor:not-allowed}",
50
+ ".dsxprx-switch-sm{width:30px;height:17px}",
51
+ ".dsxprx-switch-sm::after{width:13px;height:13px;top:2px;left:2px}",
52
+ ".dsxprx-switch-sm[data-on=true]::after{transform:translateX(13px)}",
53
+ ".dsxprx-field{display:flex;flex-direction:column;gap:4px}",
54
+ ".dsxprx-field>label{font-size:12px;font-weight:500;color:var(--dsw-alias-label-secondary)}",
55
+ ".dsxprx-field input[type=text],.dsxprx-field select{box-sizing:border-box;width:100%;padding:6px 8px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-specific-input);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}",
56
+ ".dsxprx-field input:disabled{opacity:.5}",
57
+ ".dsxprx-hint{font-size:11px;line-height:15px;color:var(--dsw-alias-label-caption)}",
58
+ ".dsxprx-radios{display:flex;gap:6px;flex-wrap:wrap}",
59
+ ".dsxprx-radio{flex:none;display:flex;align-items:center;gap:6px;padding:5px 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:999px;font-size:12px;line-height:16px;cursor:pointer;color:var(--dsw-alias-label-secondary)}",
60
+ ".dsxprx-radio[data-on=true]{background:rgba(77,107,254,.12);border-color:rgba(77,107,254,.45);color:var(--dsw-alias-label-primary);font-weight:600}",
61
+ ".dsxprx-radio input{position:absolute;opacity:0;width:0;height:0}",
62
+ ".dsxprx-diag{display:flex;flex-direction:column;gap:6px}",
63
+ ".dsxprx-diagbar{display:flex;gap:8px}",
64
+ ".dsxprx-diagbar input[type=text]{flex:1;min-width:0;box-sizing:border-box;padding:6px 8px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-specific-input);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px}",
65
+ ".dsxprx-diagbtn{flex:none;padding:6px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-specific-tip);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;cursor:pointer}",
66
+ ".dsxprx-diagbtn:disabled{opacity:.5;cursor:not-allowed}",
67
+ ".dsxprx-diagout{display:flex;flex-direction:column;gap:3px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-specific-tip);font-size:12px;line-height:17px;color:var(--dsw-alias-label-primary)}",
68
+ ".dsxprx-title{font-size:16px;font-weight:700;line-height:22px;color:var(--dsw-alias-label-primary)}",
69
+ ".dsxprx-diagcheck{flex:none;display:flex;align-items:center;gap:5px;font-size:12px;line-height:16px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap}",
70
+ ".dsxprx-diagcheck input{width:14px;height:14px;accent-color:#4d6bfe;margin:0}",
71
+ ".dsxprx-hdr{display:flex;align-items:center;gap:6px;max-width:180px;height:26px;padding:0 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:999px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;line-height:16px;cursor:pointer;white-space:nowrap;overflow:hidden}",
72
+ ".dsxprx-hdr:hover{background:var(--dsw-alias-state-hover)}",
73
+ ".dsxprx-hdr .dsxprx-dot{width:8px;height:8px}",
74
+ ".dsxprx-hdr[data-active=true] .dsxprx-dot{background:var(--dsw-alias-state-success-primary)}",
75
+ ".dsxprx-hdrlabel{min-width:0;overflow:hidden;text-overflow:ellipsis;font-weight:500}",
76
+ ".dsxprx-hdr[data-active=false] .dsxprx-hdrlabel{color:var(--dsw-alias-label-secondary)}",
77
+ ].join("");
78
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + cssId + "\"]") === null) {
79
+ var tag = document.createElement("style");
80
+ tag.dataset.plugin = "dsh-proxy-pro";
81
+ tag.dataset.pluginCss = cssId;
82
+ tag.textContent = css;
83
+ document.head.appendChild(tag);
84
+ }
85
+
86
+ var API_BASE = "/dsh-proxy-pro/api";
87
+
88
+ /** Same-origin JSON helper toward the host API; resolves null on failure. */
89
+ function fetchJson(path, options) {
90
+ return fetch(API_BASE + path, options)
91
+ .then(function (res) { return res.json().catch(function () { return {}; }); })
92
+ .catch(function () { return null; });
93
+ }
94
+
95
+ // ---- reactive settings scope (the `proxy` namespace) ----
96
+ /** @returns `{ value, writable, ready }` from a bound SettingsScope. */
97
+ function useProxySnapshot(scope) {
98
+ var snapshot = useSyncExternalStore(
99
+ function (onChange) { return scope.subscribe(onChange); },
100
+ function () { return scope.getSnapshot(); }
101
+ );
102
+ var ready = snapshot.status === "ready" && snapshot.value !== undefined;
103
+ return {
104
+ value: ready ? snapshot.value : undefined,
105
+ writable: snapshot.writable === true && ready,
106
+ ready: snapshot.status === "unavailable" ? false : snapshot.status === "ready",
107
+ };
108
+ }
109
+
110
+ // ---- runtime facts (from /dsh-proxy-pro/api/status) ----
111
+ /** Fetch the host status snapshot on mount and whenever `refreshKey`
112
+ * changes (config edits / mode switches). No timed polling: each pull
113
+ * makes the host re-read the system facts (pull-triggered refresh,
114
+ * idempotent — never reinstalls unless the proxy actually changed), so
115
+ * a viewer always sees fresh facts while the host's own 30s poll keeps
116
+ * following system-proxy changes unattended (§17). */
117
+ function useStatusOnce(refreshKey) {
118
+ var state = useState({ loading: true, status: null, error: "" });
119
+ var setState = state[1];
120
+ useEffect(function () {
121
+ var alive = true;
122
+ fetchJson("/status").then(function (data) {
123
+ if (!alive) return;
124
+ if (data === null || data.ok !== true) {
125
+ setState({ loading: false, status: null, error: (data && data.error) || "status API unavailable" });
126
+ return;
127
+ }
128
+ setState({ loading: false, status: data.status, error: "" });
129
+ });
130
+ return function () { alive = false; };
131
+ }, [refreshKey]);
132
+ return state[0];
133
+ }
134
+
135
+ // A text input that drafts locally and commits on blur (external value
136
+ // wins only when it differs from the last committed draft).
137
+ function DraftInput(props) {
138
+ var textState = useState(props.value || "");
139
+ var text = textState[0];
140
+ var setText = textState[1];
141
+ var committed = useRef(props.value || "");
142
+ useEffect(function () {
143
+ var next = props.value || "";
144
+ if (next !== committed.current) {
145
+ committed.current = next;
146
+ setText(next);
147
+ }
148
+ }, [props.value]);
149
+ return react.createElement("input", {
150
+ type: "text",
151
+ value: text,
152
+ disabled: props.disabled === true,
153
+ placeholder: props.placeholder || "",
154
+ onChange: function (event) { setText(event.target.value); },
155
+ onBlur: function () {
156
+ if (text === committed.current) return;
157
+ committed.current = text;
158
+ props.onCommit(text);
159
+ },
160
+ });
161
+ }
162
+
163
+ // ---- conversation header capsule button ----
164
+ function ProxyHeaderButton(props) {
165
+ var scope = props.scope;
166
+ var settings = useProxySnapshot(scope);
167
+ var busy = useState(false);
168
+ var setBusy = busy[1];
169
+ // Configuration-driven: the capsule mirrors the switch, resolved locally
170
+ // with zero requests. The host applies the change instantly when the
171
+ // settings revision lands (and follows external system-proxy changes on
172
+ // its own 30s poll), so polling here buys nothing (§17).
173
+ var config = settings.value || {};
174
+ var active = config.enabled === true && config.mode !== "none";
175
+ var enabled = config.enabled === true;
176
+ var mode = config.mode || "system";
177
+ var modeLabel = mode === "system" ? "system" : mode === "custom" ? "custom" : "none";
178
+ function toggle() {
179
+ if (!settings.writable) return;
180
+ setBusy(true);
181
+ var next = !enabled;
182
+ scope.set("enabled", next).catch(function (error) {
183
+ console.error("[dsh-proxy-pro] toggle failed:", error);
184
+ }).finally(function () { setBusy(false); });
185
+ // Toggle doubles as a manual "recheck now" even when the settings
186
+ // panel is not mounted: pulling /status makes the host re-read the
187
+ // system facts (idempotent — snapshot refresh unless the proxy really
188
+ // changed). The settings hook already re-syncs on scope.set; this
189
+ // pull is a visible, UI-independent follow-up so 关闭→打开 re-applies
190
+ // and re-verifies the proxy from the header alone.
191
+ fetchJson("/status");
192
+ }
193
+ var title = active
194
+ ? "代理已开启(界面开关)· 点击关闭"
195
+ : "代理已关闭 · 点击开启";
196
+ return react.createElement(
197
+ "button",
198
+ {
199
+ type: "button",
200
+ className: "dsxprx-hdr",
201
+ "data-active": active ? "true" : "false",
202
+ "data-proxy-toggle": true,
203
+ onClick: toggle,
204
+ disabled: !settings.writable || busy[0],
205
+ title: title,
206
+ "aria-pressed": active,
207
+ },
208
+ react.createElement("span", { className: "dsxprx-dot" }),
209
+ react.createElement("span", { className: "dsxprx-hdrlabel" }, "代理 · " + (active ? "ON " + modeLabel : "OFF"))
210
+ );
211
+ }
212
+
213
+ // ---- Settings page: 代理管理 ----
214
+ function ProxyManagerPanel(props) {
215
+ var scope = props.scope;
216
+ var settings = useProxySnapshot(scope);
217
+ // Re-fetch the host snapshot when the configuration changes (enabled /
218
+ // mode / customUrl / noProxy are the only inputs that move the effKey);
219
+ // no timed polling, mount + change-driven only (§17.3d).
220
+ var valueKey = settings.ready && settings.value
221
+ ? JSON.stringify([
222
+ !!settings.value.enabled,
223
+ settings.value.mode || "",
224
+ settings.value.customUrl || "",
225
+ settings.value.noProxy || "",
226
+ ])
227
+ : "pending";
228
+ var runtime = useStatusOnce(valueKey);
229
+ var diagState = useState("");
230
+ var diagUrl = diagState[0];
231
+ var setDiagUrl = diagState[1];
232
+ var diagBusy = useState(false);
233
+ var setDiagBusy = diagBusy[1];
234
+ var diagResult = useState(null);
235
+ var setDiagResult = diagResult[1];
236
+
237
+ if (!settings.ready) {
238
+ return react.createElement("div", { className: "dsxprx", "data-proxy-settings": true },
239
+ react.createElement("p", { className: "dsxprx-hint" }, "代理设置加载中…"));
240
+ }
241
+ var value = settings.value || { enabled: false, mode: "system", customUrl: "", noProxy: "" };
242
+ var writable = settings.writable;
243
+ var status = runtime.status;
244
+ // The status line follows the SWITCH (configuration-driven): turning
245
+ // the toggle off must immediately read 「代理未启用」. The host snapshot
246
+ // only supplements the effective URL/source and surfaces a mismatch when
247
+ // the switch is on but the proxy did not take effect (LESSONS §17.3c).
248
+ var cfgActive = value.enabled === true && value.mode !== "none";
249
+ var active = cfgActive;
250
+ var modeNow = value.mode || "system";
251
+ // Address is configuration-driven: custom mode shows the custom URL
252
+ // immediately (no stale host snapshot), system mode shows the URL the
253
+ // host read from Windows.
254
+ var shownUrl = modeNow === "custom"
255
+ ? (value.customUrl || "—")
256
+ : ((status && status.url) || "—");
257
+ var statusLine = cfgActive
258
+ ? "代理生效中 · " + shownUrl
259
+ : "代理未启用";
260
+ var subLine = "";
261
+ if (cfgActive) {
262
+ if (modeNow === "custom") {
263
+ subLine = "自定义地址(立即生效)" + (status && status.url && status.url !== value.customUrl ? " · 已应用: " + status.url : "");
264
+ } else if (status !== null && status !== undefined) {
265
+ subLine = status.active === true
266
+ ? "跟随 Windows 系统代理(自动刷新)"
267
+ : "实际未生效 · " + (status.reason || "未知原因") + "。可检查下面测试或系统代理设置。";
268
+ } else if (runtime.error !== "") {
269
+ subLine = "无法读取 host 状态(" + runtime.error + ")。";
270
+ }
271
+ }
272
+ var effectiveNoProxy = status && status.noProxy
273
+ ? status.noProxy
274
+ : (value.noProxy || "").trim();
275
+ // "含系统 ProxyOverride 并入" only makes sense in system mode with the
276
+ // proxy ACTUALLY active: custom mode keeps its own list and a disabled/
277
+ // unmatched state returns only the always-local entries — comparing raw
278
+ // strings alone mislabeled those as a system merge (review fix, 2026-09-17).
279
+ var systemMerged = modeNow === "system"
280
+ && status !== null && status !== undefined && status.active === true && status.noProxy
281
+ && status.noProxy !== (value.noProxy || "").trim();
282
+
283
+ function setField(field) {
284
+ return function (next) {
285
+ scope.set(field, next).catch(function (error) {
286
+ console.error("[dsh-proxy-pro] save failed:", error);
287
+ });
288
+ };
289
+ }
290
+ var diagUseProxy = useState(true);
291
+ var setDiagUseProxy = diagUseProxy[1];
292
+ function runDiag() {
293
+ var target = diagUrl.trim();
294
+ if (!target) return;
295
+ setDiagBusy(true);
296
+ setDiagResult(null);
297
+ fetchJson("/route?url=" + encodeURIComponent(target) + "&useProxy=" + (diagUseProxy[0] ? "1" : "0")).then(function (data) {
298
+ setDiagResult(data === null ? { error: "诊断接口不可用" } : data);
299
+ }).finally(function () { setDiagBusy(false); });
300
+ }
301
+ var diag = diagResult[0];
302
+
303
+ return react.createElement("div", { className: "dsxprx", "data-proxy-settings": true },
304
+
305
+ // --- title: 代理配置 ---
306
+ react.createElement("div", { className: "dsxprx-title" }, "代理配置"),
307
+
308
+ // --- status line first, with a compact enable switch on its right ---
309
+ react.createElement("div", { className: "dsxprx-status", "data-active": active ? "true" : "false" },
310
+ react.createElement("span", { className: "dsxprx-dot" }),
311
+ react.createElement("div", { className: "dsxprx-statusbody" },
312
+ react.createElement("span", { className: "dsxprx-statusline" }, statusLine),
313
+ subLine ? react.createElement("span", { className: "dsxprx-subline" }, subLine) : null),
314
+ react.createElement("label", { className: "dsxprx-switchwrap", "data-disabled": writable ? undefined : "true", title: "启用代理" },
315
+ react.createElement("span", { className: "dsxprx-switch dsxprx-switch-sm", "data-on": value.enabled === true ? "true" : "false" }),
316
+ react.createElement("input", {
317
+ type: "checkbox",
318
+ checked: value.enabled === true,
319
+ disabled: !writable,
320
+ onChange: function (event) { setField("enabled")(event.target.checked); },
321
+ }))
322
+ ),
323
+
324
+ // --- mode ---
325
+ react.createElement("div", { className: "dsxprx-field" },
326
+ react.createElement("label", { htmlFor: "dsxprx-mode" }, "代理地址来源"),
327
+ react.createElement("div", { className: "dsxprx-radios" },
328
+ [["system", "系统代理(Windows)"], ["custom", "自定义地址"]].map(function (entry) {
329
+ var modeValue = entry[0];
330
+ var modeLabel = entry[1];
331
+ var on = (value.mode || "system") === modeValue;
332
+ return react.createElement("label", { key: modeValue, className: "dsxprx-radio", "data-on": on ? "true" : "false", title: modeValue === "system" ? "读取 Windows ·Internet 设置· 中的代理" : "使用下方自定义地址" },
333
+ react.createElement("input", {
334
+ type: "radio",
335
+ name: "dsxprx-mode",
336
+ checked: on,
337
+ disabled: !writable,
338
+ onChange: function () { setField("mode")(modeValue); },
339
+ }),
340
+ modeLabel);
341
+ })
342
+ ),
343
+ react.createElement("span", { className: "dsxprx-hint" }, "关闭代理请用上面的「启用代理」开关。")
344
+ ),
345
+
346
+ // --- custom url ---
347
+ (value.mode || "system") === "custom"
348
+ ? react.createElement("div", { className: "dsxprx-field" },
349
+ react.createElement("label", { htmlFor: "dsxprx-url" }, "自定义代理地址"),
350
+ react.createElement(DraftInput, {
351
+ value: value.customUrl || "",
352
+ disabled: !writable,
353
+ placeholder: "http://127.0.0.1:7890",
354
+ onCommit: setField("customUrl"),
355
+ }),
356
+ react.createElement("span", { className: "dsxprx-hint" }, "支持 http:// 或 https:// 开头的地址。")
357
+ )
358
+ : null,
359
+
360
+ // --- no_proxy ---
361
+ react.createElement("div", { className: "dsxprx-field" },
362
+ react.createElement("label", { htmlFor: "dsxprx-noproxy" }, "直连名单(NO_PROXY)"),
363
+ react.createElement(DraftInput, {
364
+ value: value.noProxy || "",
365
+ disabled: !writable,
366
+ placeholder: "localhost,127.0.0.1,::1",
367
+ onCommit: setField("noProxy"),
368
+ }),
369
+ react.createElement("span", { className: "dsxprx-hint" }, "逗号分隔的主机名;localhost / 127.0.0.1 / ::1 始终直连。"),
370
+ effectiveNoProxy
371
+ ? react.createElement("span", { className: "dsxprx-subline" },
372
+ (systemMerged ? "当前生效(含 Windows 系统 ProxyOverride 并入):" : "当前生效:") + effectiveNoProxy)
373
+ : null
374
+ ),
375
+
376
+ // --- diagnostics ---
377
+ react.createElement("div", { className: "dsxprx-diag" },
378
+ react.createElement("label", { htmlFor: "dsxprx-diagurl" }, "路由诊断"),
379
+ react.createElement("div", { className: "dsxprx-diagbar" },
380
+ react.createElement("input", {
381
+ id: "dsxprx-diagurl",
382
+ type: "text",
383
+ value: diagUrl,
384
+ placeholder: "https://github.com/foo/bar",
385
+ onChange: function (event) { setDiagUrl(event.target.value); },
386
+ onKeyDown: function (event) { if (event.key === "Enter") runDiag(); },
387
+ }),
388
+ react.createElement("label", { className: "dsxprx-diagcheck" },
389
+ react.createElement("input", {
390
+ type: "checkbox",
391
+ checked: diagUseProxy[0],
392
+ onChange: function (event) { setDiagUseProxy(event.target.checked); },
393
+ }),
394
+ "使用代理"),
395
+ react.createElement("button", { type: "button", className: "dsxprx-diagbtn", onClick: runDiag, disabled: diagBusy[0] || !diagUrl.trim() }, "测试路由")
396
+ ),
397
+ react.createElement("span", { className: "dsxprx-hint" }, "勾选「使用代理」= 强制走代理测通不通;不勾 = 强制直连测。对比可判断该域名该不该加进 NO_PROXY。"),
398
+ diag !== null
399
+ ? react.createElement("div", { className: "dsxprx-diagout" },
400
+ diag.error
401
+ ? react.createElement("span", null, "诊断失败:" + diag.error)
402
+ : [
403
+ react.createElement("span", { key: "c" }, "测试通道: " + (diag.channel === "proxy" ? "经代理(强制)" : diag.channel === "direct" ? "直连(强制绕过)" : "按当前策略")),
404
+ react.createElement("span", { key: "r" }, "策略判定: " + diag.route + (diag.bypassed ? "(NO_PROXY 命中)" : "")),
405
+ react.createElement("span", { key: "p" }, "连通探测: " + (diag.probe && diag.probe.ok ? "可达(HTTP " + diag.probe.status + ")" : (diag.probe && diag.probe.short) || "未知")),
406
+ diag.hint ? react.createElement("span", { key: "h" }, "提示: " + diag.hint) : null,
407
+ ]
408
+ )
409
+ : null
410
+ ),
411
+
412
+ react.createElement("span", { className: "dsxprx-hint" }, "对话中的模型会实时感知代理状态(系统提示中标注),并可用 proxy_status / proxy_set / proxy_test / proxy_config 查询或临时切换。")
413
+ );
414
+ }
415
+
416
+ /**
417
+ * Browser plugin body: bind the `proxy` settings scope, then register the
418
+ * conversation header button and the 代理配置 settings section.
419
+ * @param ctx - client cordis context.
420
+ */
421
+ function apply(ctx) {
422
+ var scope = ctx.settingsScope.bind({ namespace: "proxy" });
423
+ ctx.slots.inject("settings.section", function () {
424
+ return ctx.slots.register({
425
+ name: "settings.section",
426
+ id: "dsh-proxy-pro",
427
+ order: 20,
428
+ label: "代理配置",
429
+ }, function Section() {
430
+ return react.createElement(ProxyManagerPanel, { scope: scope });
431
+ });
432
+ });
433
+ ctx.slots.inject("conversation.session.header.utilities", function () {
434
+ return ctx.slots.register({
435
+ name: "conversation.session.header.utilities",
436
+ id: "dsh-proxy-pro",
437
+ order: 10,
438
+ label: "代理",
439
+ }, function Header() {
440
+ return react.createElement(ProxyHeaderButton, { scope: scope });
441
+ });
442
+ });
443
+ }
444
+
445
+ exports.name = "dsh-proxy-pro-ui";
446
+ exports.apply = apply;
447
+ // Cordis service names the browser loader must wait for — NOT package
448
+ // names (those belong in package.json's dsh.client.inject). Writing
449
+ // package names here keeps the plugin permanently pending and blocks
450
+ // web boot (see docs/LESSONS.md §6).
451
+ exports.inject = [
452
+ "slots",
453
+ "settingsScope",
454
+ ];
455
+ return module.exports;
456
+ },
457
+ });