@dsh-plus/secret-env 0.1.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,992 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@dsh-plus/secret-env",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ //#region ../shared/src/client/fetch.ts
10
+ /**
11
+ * 同源自定义端点的最小 fetch 封装(各插件 client/api.ts 的公共收编版)。
12
+ * 仅同源(credentials: same-origin),错误统一抛 Error(body.error)。
13
+ * @module @dsh-plus/shared/client/fetch
14
+ */
15
+ async function parse(res) {
16
+ const body = await res.json();
17
+ if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`);
18
+ return body;
19
+ }
20
+ /** GET 同源 JSON 端点。 */
21
+ async function getJson(route) {
22
+ return parse(await fetch(route, { credentials: "same-origin" }));
23
+ }
24
+ /** POST 同源 JSON 端点(body 可省略)。 */
25
+ async function postJson(route, body) {
26
+ return parse(await fetch(route, {
27
+ method: "POST",
28
+ credentials: "same-origin",
29
+ headers: { "content-type": "application/json" },
30
+ body: body === void 0 ? void 0 : JSON.stringify(body)
31
+ }));
32
+ }
33
+ //#endregion
34
+ //#region ../shared/src/client/i18n.ts
35
+ /** 卡片通用文案(save/discard/unsaved/loading 等四插件共有键)。 */
36
+ const commonZh = {
37
+ save: "保存",
38
+ saving: "保存中…",
39
+ discard: "放弃",
40
+ unsaved: "未保存",
41
+ loading: "加载中…",
42
+ unavailable: "配置服务不可用。",
43
+ readOnly: "当前部署无 settings provider,配置为只读;请编辑 settings.yaml。",
44
+ invalidNumber: "请输入正整数。",
45
+ collapse: "收起",
46
+ expand: "展开",
47
+ enabledOn: "已启用",
48
+ enabledOff: "未启用",
49
+ saveFailed: "保存失败,请检查填写内容。",
50
+ confirm: "确认",
51
+ cancel: "取消"
52
+ };
53
+ const commonEn = {
54
+ save: "Save",
55
+ saving: "Saving…",
56
+ discard: "Discard",
57
+ unsaved: "Unsaved",
58
+ loading: "Loading…",
59
+ unavailable: "Settings service unavailable.",
60
+ readOnly: "No settings provider in this deployment; config is read-only. Edit settings.yaml.",
61
+ invalidNumber: "Enter a positive integer.",
62
+ collapse: "Collapse",
63
+ expand: "Expand",
64
+ enabledOn: "On",
65
+ enabledOff: "Off",
66
+ saveFailed: "Save failed; check the fields.",
67
+ confirm: "Confirm",
68
+ cancel: "Cancel"
69
+ };
70
+ /** own 覆盖同键合并(返回新对象,入参不可变)。 */
71
+ function mergeDict(base, own) {
72
+ return {
73
+ ...base,
74
+ ...own
75
+ };
76
+ }
77
+ //#endregion
78
+ //#region ../shared/src/client/styles.ts
79
+ /**
80
+ * 配置卡片样式工厂(各插件 client 的公共收编版)。
81
+ * 视觉规则与迁移前逐字一致(类名前缀换为各插件自身,DOM 类名不变),
82
+ * 另补移动端与可达性增强(767px 断点 + pointer:coarse,对齐 ui-mobile-fit 约定):
83
+ * - 窄屏 input/textarea 16px(防 iOS 聚焦缩放)、可点目标 ≥44px;
84
+ * - footer 按钮等宽换行、状态文字独占一行;
85
+ * - 展开态 body 底部 sticky 保存栏(长表单滚动中始终可达)。
86
+ * 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载),不覆盖上游选择器。
87
+ * @module @dsh-plus/shared/client/styles
88
+ */
89
+ /** 生成全套卡片 CSS;prefix 为插件类名前缀(如 'dne'),extra 为插件特有规则。 */
90
+ function cardCss(prefix, extra = "") {
91
+ return `
92
+ .${prefix}-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}
93
+ .${prefix}-card:hover{border-color:var(--dsw-alias-label-dimmed)}
94
+ .${prefix}-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}
95
+ .${prefix}-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}
96
+ .${prefix}-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}
97
+ .${prefix}-headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}
98
+ .${prefix}-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}
99
+ .${prefix}-description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
100
+ .${prefix}-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s;display:inline-flex}
101
+ .${prefix}-chevronOpen{transform:rotate(180deg)}
102
+ .${prefix}-pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
103
+ .${prefix}-statusBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
104
+ .${prefix}-statusOn{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
105
+ .${prefix}-statusOff{color:var(--dsw-alias-label-tertiary)}
106
+ .${prefix}-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}
107
+ .${prefix}-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
108
+ .${prefix}-field+.${prefix}-field{border-top:1px solid var(--dsw-alias-border-l2)}
109
+ .${prefix}-head{align-items:center;gap:8px;display:flex}
110
+ .${prefix}-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}
111
+ .${prefix}-badge{white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
112
+ .${prefix}-badgeSet{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
113
+ .${prefix}-badgeUnset{color:var(--dsw-alias-label-tertiary)}
114
+ .${prefix}-input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;transition:border-color .18s,background .18s}
115
+ .${prefix}-input:hover:not(:disabled):not(:focus-visible){border-color:var(--dsw-alias-label-dimmed)}
116
+ .${prefix}-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
117
+ .${prefix}-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
118
+ .${prefix}-inputInvalid{border-color:var(--dsw-alias-label-error)}
119
+ .${prefix}-select{appearance:none;background:var(--dsw-alias-bg-module-platform);border:none;cursor:pointer;background-image:linear-gradient(45deg,transparent 50%,var(--dsw-alias-label-secondary) 50%),linear-gradient(135deg,var(--dsw-alias-label-secondary) 50%,transparent 50%);background-position:calc(100% - 16px) 50%,calc(100% - 11px) 50%;background-size:5px 5px;background-repeat:no-repeat;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;height:34px;box-sizing:border-box;border-radius:8px;padding:0 30px 0 12px;transition:background-color .18s}
120
+ .${prefix}-select:hover:not(:disabled){background-color:var(--dsw-alias-interactive-bg-hover)}
121
+ .${prefix}-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
122
+ .${prefix}-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}
123
+ .${prefix}-checkRow{align-items:center;gap:10px;display:flex;padding:6px 0}
124
+ .${prefix}-checkRow label{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5;cursor:pointer;flex:1;min-width:0}
125
+ /* 自绘圆角扁平开关(官方 UI 无可见原生 checkbox,选择类全走胶囊/菜单) */
126
+ .${prefix}-checkRow input{appearance:none;-webkit-appearance:none;position:relative;box-sizing:border-box;background:var(--dsw-alias-bg-module-platform);border:none;border-radius:999px;width:34px;height:20px;flex:none;cursor:pointer;transition:background .18s;margin:0}
127
+ .${prefix}-checkRow input::after{content:"";position:absolute;top:2px;left:2px;background:var(--dsw-alias-label-primary);border-radius:999px;width:16px;height:16px;transition:left .18s cubic-bezier(.4,0,.2,1)}
128
+ .${prefix}-checkRow input:checked{background:var(--dsw-alias-brand-primary)}
129
+ .${prefix}-checkRow input:checked::after{left:16px;background:var(--dsw-alias-bg-base)}
130
+ .${prefix}-checkRow input:disabled{opacity:.4;cursor:default}
131
+ .${prefix}-checkRow input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
132
+ .${prefix}-groupLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;padding:12px 0 2px;margin:0}
133
+ .${prefix}-readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}
134
+ .${prefix}-warn{color:var(--dsw-alias-label-error);margin:12px 0 0;font-size:12px;line-height:1.5}
135
+ .${prefix}-footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex;flex-wrap:wrap}
136
+ .${prefix}-status{min-width:0;color:var(--dsw-alias-label-secondary);flex:1;margin:0;font-size:12px;line-height:1.5}
137
+ .${prefix}-statusError{color:var(--dsw-alias-label-error)}
138
+ .${prefix}-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}
139
+ .${prefix}-btnGhost{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}
140
+ .${prefix}-btnGhost:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}
141
+ .${prefix}-btnPrimary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
142
+ .${prefix}-btn:disabled{opacity:.4;cursor:default}
143
+ .${prefix}-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
144
+ @media (max-width:767px){
145
+ .${prefix}-body{margin:0 10px}
146
+ .${prefix}-header{padding:12px 12px}
147
+ .${prefix}-input,.${prefix}-select{font-size:16px;height:40px}
148
+ .${prefix}-checkRow{min-height:44px;padding:8px 0}
149
+ .${prefix}-footer{position:sticky;bottom:0;background:var(--dsw-alias-bg-layer-2);margin:0 -10px;padding:10px 10px 6px;border-top:1px solid var(--dsw-alias-border-l2);border-bottom-left-radius:12px;border-bottom-right-radius:12px}
150
+ .${prefix}-status{flex-basis:100%;flex:none}
151
+ .${prefix}-btn{min-height:44px;flex:1;min-width:96px}
152
+ .${prefix}-statusBadge{display:none}
153
+ }
154
+ @media (pointer:coarse){
155
+ .${prefix}-btn{min-height:44px}
156
+ .${prefix}-checkRow{min-height:44px}
157
+ .${prefix}-input,.${prefix}-select{height:40px}
158
+ }
159
+ ${extra}
160
+ `;
161
+ }
162
+ /**
163
+ * 幂等注入样式标签(官方 data-plugin-css 约定);返回新标签,
164
+ * 已存在或环境无 document 时返回 null(调用方 effect 卸载用)。
165
+ */
166
+ function injectCardStyle(pluginId, css) {
167
+ if (typeof document === "undefined") return null;
168
+ const tagId = `${pluginId}/card.css`;
169
+ if (document.querySelector(`style[data-plugin-css=${JSON.stringify(tagId)}]`) !== null) return null;
170
+ const tag = document.createElement("style");
171
+ tag.dataset.plugin = pluginId;
172
+ tag.dataset.pluginCss = tagId;
173
+ tag.textContent = css;
174
+ document.head.appendChild(tag);
175
+ return tag;
176
+ }
177
+ //#endregion
178
+ //#region src/client/api.ts
179
+ /**
180
+ * 数据端点通道:GET list / POST set/unset(同源 fetch,值不上行到任何对话通道)。
181
+ * @module secret-env/client/api
182
+ */
183
+ /** 端点错误:携带结构化错误码,UI 按码映射文案。 */
184
+ var ApiError = class extends Error {
185
+ code;
186
+ constructor(code) {
187
+ super(code);
188
+ this.code = code;
189
+ }
190
+ };
191
+ async function fetchSecrets(sessionId) {
192
+ return getJson(`/dsh-plus/secret-env/list${sessionId === void 0 ? "" : `?sessionId=${encodeURIComponent(sessionId)}`}`);
193
+ }
194
+ async function write(path, body) {
195
+ try {
196
+ await postJson(`/dsh-plus/secret-env/${path}`, body);
197
+ } catch (error) {
198
+ throw new ApiError(error instanceof Error ? error.message : "internal");
199
+ }
200
+ }
201
+ function setGlobal(name, value, description) {
202
+ return write("global/set", {
203
+ name,
204
+ value,
205
+ description
206
+ });
207
+ }
208
+ function unsetGlobal(name) {
209
+ return write("global/unset", { name });
210
+ }
211
+ function setSession(sessionId, name, value, description, once) {
212
+ return write("session/set", {
213
+ sessionId,
214
+ name,
215
+ value,
216
+ description,
217
+ once
218
+ });
219
+ }
220
+ function unsetSession(sessionId, name) {
221
+ return write("session/unset", {
222
+ sessionId,
223
+ name
224
+ });
225
+ }
226
+ //#endregion
227
+ //#region src/client/common.ts
228
+ /**
229
+ * 浏览器半共享小件:错误码文案映射与复制助手(设置页与会话控件共用)。
230
+ * @module secret-env/client/common
231
+ */
232
+ /** 端点错误 → 本地化文案(未知码回落 internal)。 */
233
+ function errorText(t, error) {
234
+ const code = error instanceof ApiError ? error.code : "internal";
235
+ const text = t(`error.${code}`);
236
+ return text === `error.${code}` ? t("error.internal") : text;
237
+ }
238
+ /** 复制文本到剪贴板(clipboard API 缺席时退 execCommand)。 */
239
+ async function copyText(text) {
240
+ try {
241
+ await navigator.clipboard.writeText(text);
242
+ return true;
243
+ } catch {
244
+ const area = document.createElement("textarea");
245
+ area.value = text;
246
+ area.style.position = "fixed";
247
+ area.style.opacity = "0";
248
+ document.body.appendChild(area);
249
+ area.select();
250
+ const ok = document.execCommand("copy");
251
+ area.remove();
252
+ return ok;
253
+ }
254
+ }
255
+ //#endregion
256
+ //#region src/client/composer.tsx
257
+ /**
258
+ * 会话注入控件(conversation.input.right 官方插槽,session 作用域):
259
+ * composer 工具行的钥匙胶囊,弹出本会话的会话级密钥面板(列表 + 增删)。
260
+ * 会话 id 由插槽 inject 工厂直接供给(框架解析的严格 session 作用域)。
261
+ * 窄屏(≤767px)popover 转底部抽屉;桌面点外部或 Esc 关闭。
262
+ * @module secret-env/client/composer
263
+ */
264
+ const EMPTY_FORM$1 = {
265
+ name: "",
266
+ value: "",
267
+ description: "",
268
+ once: false
269
+ };
270
+ function SessionRow(props) {
271
+ const { t, entry } = props;
272
+ const [copied, setCopied] = (0, react.useState)(false);
273
+ const timer = (0, react.useRef)(null);
274
+ (0, react.useEffect)(() => () => {
275
+ if (timer.current !== null) clearTimeout(timer.current);
276
+ }, []);
277
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
278
+ className: "dse-row",
279
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
280
+ className: "dse-rowMain",
281
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
282
+ className: "dse-env",
283
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
284
+ className: "dse-envName",
285
+ children: ["$", entry.envName]
286
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
287
+ type: "button",
288
+ className: "dse-iconBtn",
289
+ title: t("copy"),
290
+ "aria-label": t("copy"),
291
+ onClick: () => {
292
+ copyText(`$${entry.envName}`).then((ok) => {
293
+ if (!ok) return;
294
+ setCopied(true);
295
+ if (timer.current !== null) clearTimeout(timer.current);
296
+ timer.current = setTimeout(() => setCopied(false), 1500);
297
+ });
298
+ },
299
+ children: copied ? "✓" : "⧉"
300
+ })]
301
+ }), entry.description !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
302
+ className: "dse-note",
303
+ children: entry.description
304
+ }) : null]
305
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
306
+ className: "dse-badges",
307
+ children: [entry.once ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
308
+ className: "dse-badge",
309
+ children: t("scopeOnce")
310
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
311
+ type: "button",
312
+ className: "dse-iconBtn dse-delBtn",
313
+ title: t("delete"),
314
+ "aria-label": t("delete"),
315
+ onClick: () => {
316
+ unsetSession(props.sessionId, entry.name).then(() => props.onDeleted()).catch((error) => props.onError(errorText(t, error)));
317
+ },
318
+ children: "✕"
319
+ })]
320
+ })]
321
+ });
322
+ }
323
+ function ComposerSecret(props) {
324
+ const { sessionId, t } = props;
325
+ const [open, setOpen] = (0, react.useState)(false);
326
+ const [items, setItems] = (0, react.useState)(null);
327
+ const [form, setForm] = (0, react.useState)(EMPTY_FORM$1);
328
+ const [saving, setSaving] = (0, react.useState)(false);
329
+ const [status, setStatus] = (0, react.useState)(null);
330
+ const wrapRef = (0, react.useRef)(null);
331
+ const load = () => {
332
+ fetchSecrets(sessionId).then((list) => setItems(list.session)).catch(() => setStatus(errorText(t, /* @__PURE__ */ new Error("internal"))));
333
+ };
334
+ (0, react.useEffect)(() => {
335
+ if (!open) return;
336
+ load();
337
+ }, [open, sessionId]);
338
+ (0, react.useEffect)(() => {
339
+ if (!open) return;
340
+ const onDown = (event) => {
341
+ if (wrapRef.current !== null && !wrapRef.current.contains(event.target)) setOpen(false);
342
+ };
343
+ const onKey = (event) => {
344
+ if (event.key === "Escape") setOpen(false);
345
+ };
346
+ document.addEventListener("mousedown", onDown);
347
+ document.addEventListener("keydown", onKey);
348
+ return () => {
349
+ document.removeEventListener("mousedown", onDown);
350
+ document.removeEventListener("keydown", onKey);
351
+ };
352
+ }, [open]);
353
+ const onSave = () => {
354
+ setSaving(true);
355
+ setStatus(null);
356
+ setSession(sessionId, form.name, form.value, form.description, form.once).then(() => {
357
+ setForm(EMPTY_FORM$1);
358
+ load();
359
+ }).catch((error) => setStatus(errorText(t, error))).finally(() => setSaving(false));
360
+ };
361
+ const count = items?.length ?? 0;
362
+ const canSave = form.name.trim() !== "" && form.value !== "" && !saving;
363
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
364
+ className: "dse-wrap",
365
+ ref: wrapRef,
366
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
367
+ type: "button",
368
+ className: "dse-chip",
369
+ "aria-expanded": open,
370
+ "aria-label": t("sessionSecrets"),
371
+ title: t("sessionSecrets"),
372
+ onClick: () => setOpen(!open),
373
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
374
+ width: "14",
375
+ height: "14",
376
+ viewBox: "0 0 24 24",
377
+ fill: "none",
378
+ "aria-hidden": "true",
379
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
380
+ d: "M14.5 2a7.5 7.5 0 0 0-7.36 9.04L2 20.19V22h1.81l1.5-1.5v-1.81h1.81v-1.81h1.81l1.22-1.22A7.5 7.5 0 1 0 14.5 2Zm2 5.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3Z",
381
+ fill: "currentColor"
382
+ })
383
+ }), count > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
384
+ className: "dse-count",
385
+ children: count
386
+ }) : null]
387
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
388
+ className: "dse-pop",
389
+ role: "dialog",
390
+ "aria-label": t("sessionSecrets"),
391
+ children: [
392
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
393
+ className: "dse-popHead",
394
+ children: [
395
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
396
+ className: "dse-popTitle",
397
+ children: t("sessionSecrets")
398
+ }),
399
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
400
+ type: "button",
401
+ className: "dse-iconBtn",
402
+ title: t("refresh"),
403
+ "aria-label": t("refresh"),
404
+ onClick: load,
405
+ children: "⟳"
406
+ }),
407
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
408
+ type: "button",
409
+ className: "dse-iconBtn",
410
+ title: t("close"),
411
+ "aria-label": t("close"),
412
+ onClick: () => setOpen(false),
413
+ children: "✕"
414
+ })
415
+ ]
416
+ }),
417
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
418
+ className: "dse-popHint",
419
+ children: t("sessionHint")
420
+ }),
421
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
422
+ className: "dse-popList",
423
+ children: items === null || items.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
424
+ className: "dse-empty",
425
+ children: t("sessionEmpty")
426
+ }) : items.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRow, {
427
+ t,
428
+ sessionId,
429
+ entry,
430
+ onDeleted: load,
431
+ onError: (text) => setStatus(text === "" ? null : text)
432
+ }, entry.name))
433
+ }),
434
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
435
+ className: "dse-popForm",
436
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
437
+ className: "dse-field",
438
+ children: [
439
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
440
+ className: "dse-head",
441
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
442
+ className: "dse-label",
443
+ htmlFor: "dse-s-name",
444
+ children: t("addSession")
445
+ })
446
+ }),
447
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
448
+ id: "dse-s-name",
449
+ className: "dse-input",
450
+ placeholder: "API_TOKEN",
451
+ value: form.name,
452
+ onChange: (event) => setForm({
453
+ ...form,
454
+ name: event.target.value
455
+ })
456
+ }),
457
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
458
+ className: "dse-input",
459
+ type: "password",
460
+ autoComplete: "off",
461
+ placeholder: t("valueLabel"),
462
+ value: form.value,
463
+ onChange: (event) => setForm({
464
+ ...form,
465
+ value: event.target.value
466
+ })
467
+ }),
468
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
469
+ className: "dse-input",
470
+ placeholder: t("descLabel"),
471
+ value: form.description,
472
+ onChange: (event) => setForm({
473
+ ...form,
474
+ description: event.target.value
475
+ })
476
+ }),
477
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
478
+ className: "dse-checkRow",
479
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
480
+ id: "dse-s-once",
481
+ type: "checkbox",
482
+ role: "switch",
483
+ "aria-checked": form.once,
484
+ checked: form.once,
485
+ onChange: (event) => setForm({
486
+ ...form,
487
+ once: event.target.checked
488
+ })
489
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
490
+ htmlFor: "dse-s-once",
491
+ children: t("onceLabel")
492
+ })]
493
+ })
494
+ ]
495
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
496
+ className: "dse-foot",
497
+ children: [status !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
498
+ className: "dse-status dse-statusError",
499
+ children: status
500
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
501
+ type: "button",
502
+ className: "dse-btn dse-btnPrimary",
503
+ disabled: !canSave,
504
+ onClick: onSave,
505
+ children: saving ? t("saving") : t("save")
506
+ })]
507
+ })]
508
+ })
509
+ ]
510
+ }) : null]
511
+ });
512
+ }
513
+ //#endregion
514
+ //#region src/client/i18n.ts
515
+ /**
516
+ * 浏览器半文案(zh/en)。公共键来自 shared common,本文件只维护业务键。
517
+ * @module secret-env/client/i18n
518
+ */
519
+ const NS = "dsh-plus-secret-env";
520
+ const ownZh = {
521
+ nav: "密钥变量",
522
+ title: "密钥变量",
523
+ description: "以 $DSH_SECRET_* 变量名向 agent 暴露密钥:值在每次 shell 调用时由宿主注入,不进入对话记录与提示词,不影响上下文缓存。",
524
+ globalList: "全局密钥",
525
+ colName: "变量名(模型可见)",
526
+ colDescription: "描述",
527
+ colState: "状态",
528
+ colActions: "操作",
529
+ configured: "已配置",
530
+ unconfigured: "未配置",
531
+ readonly: "只读来源",
532
+ addGlobal: "添加全局密钥",
533
+ nameLabel: "变量名后缀",
534
+ nameHint: "大写字母/数字/下划线,如 GITHUB_TOKEN;模型实际使用 $DSH_SECRET_ 前缀拼接后的完整名。",
535
+ valueLabel: "值",
536
+ valueHint: "写入后不可回读;保存于宿主凭据库,不进入对话。",
537
+ descLabel: "描述(可选)",
538
+ descHint: "仅人读备注,不进提示词。",
539
+ save: "保存",
540
+ saving: "保存中…",
541
+ delete: "删除",
542
+ deleteConfirm: "确认删除 {name}?该变量将立即对后续命令失效。",
543
+ copy: "复制变量名",
544
+ copied: "已复制",
545
+ emptyGlobal: "暂无全局密钥。添加后 agent 即可通过变量名引用。",
546
+ loadFailed: "加载失败,请稍后重试。",
547
+ retry: "重试",
548
+ sessionSecrets: "会话密钥",
549
+ sessionEmpty: "本会话暂无有默认值的密钥。",
550
+ sessionHint: "仅当前会话的 shell 命令可用;会话结束或一次性使用即失效。",
551
+ addSession: "添加会话密钥",
552
+ onceLabel: "一次性(首次使用后销毁)",
553
+ close: "关闭",
554
+ refresh: "刷新",
555
+ scopeGlobal: "全局",
556
+ scopeSession: "会话",
557
+ scopeOnce: "一次性",
558
+ "error.invalid-name": "变量名不合法:大写字母开头,仅字母/数字/下划线,≤64 字符。",
559
+ "error.empty-value": "值不能为空。",
560
+ "error.shadowed": "进程环境中已存在同名变量且优先级更高,写入无效;请先取消该环境变量。",
561
+ "error.conflict": "变量名与其他插件注册的受管变量冲突。",
562
+ "error.no-session": "缺少会话标识,请刷新后重试。",
563
+ "error.method": "请求方法不允许。",
564
+ "error.internal": "服务内部错误,请查看宿主日志。"
565
+ };
566
+ const ownEn = {
567
+ nav: "Secret Variables",
568
+ title: "Secret Variables",
569
+ description: "Expose secrets to the agent as $DSH_SECRET_* variables: values are injected by the host into each shell call, never enter the transcript or prompt, and do not affect context caching.",
570
+ globalList: "Global secrets",
571
+ colName: "Variable (model-visible)",
572
+ colDescription: "Description",
573
+ colState: "State",
574
+ colActions: "Actions",
575
+ configured: "Configured",
576
+ unconfigured: "Not configured",
577
+ readonly: "Read-only source",
578
+ addGlobal: "Add global secret",
579
+ nameLabel: "Name suffix",
580
+ nameHint: "Uppercase letters/digits/underscore, e.g. GITHUB_TOKEN; the model uses the full name with the $DSH_SECRET_ prefix.",
581
+ valueLabel: "Value",
582
+ valueHint: "Write-only after saving; stored in the host credential store, never in chat.",
583
+ descLabel: "Description (optional)",
584
+ descHint: "Human-readable note only; never enters prompts.",
585
+ save: "Save",
586
+ saving: "Saving…",
587
+ delete: "Delete",
588
+ deleteConfirm: "Delete {name}? The variable stops working for subsequent commands immediately.",
589
+ copy: "Copy variable name",
590
+ copied: "Copied",
591
+ emptyGlobal: "No global secrets yet. Once added, the agent can reference them by name.",
592
+ loadFailed: "Failed to load. Try again later.",
593
+ retry: "Retry",
594
+ sessionSecrets: "Session secrets",
595
+ sessionEmpty: "No session secrets yet.",
596
+ sessionHint: "Available to shell commands of this session only; expires when the session ends or after one use.",
597
+ addSession: "Add session secret",
598
+ onceLabel: "One-time (destroyed after first use)",
599
+ close: "Close",
600
+ refresh: "Refresh",
601
+ scopeGlobal: "global",
602
+ scopeSession: "session",
603
+ scopeOnce: "once",
604
+ "error.invalid-name": "Invalid name: start with an uppercase letter; letters/digits/underscore only, ≤64 chars.",
605
+ "error.empty-value": "Value must not be empty.",
606
+ "error.shadowed": "A same-named process environment variable shadows this write; unset it first.",
607
+ "error.conflict": "The variable conflicts with a managed variable registered by another plugin.",
608
+ "error.no-session": "Missing session id; refresh and retry.",
609
+ "error.method": "Method not allowed.",
610
+ "error.internal": "Internal error; check the host log."
611
+ };
612
+ const zh = mergeDict(commonZh, ownZh);
613
+ const en = mergeDict(commonEn, ownEn);
614
+ //#endregion
615
+ //#region src/client/section.tsx
616
+ /**
617
+ * 密钥变量设置页(settings.section 官方插槽):全局密钥的列表与增删。
618
+ * 只展示元数据与模型可见的完整变量名——值写入后不可回读(端点不出值)。
619
+ * 响应式:≤767px 行转堆叠卡、表单转单列。
620
+ * @module secret-env/client/section
621
+ */
622
+ const EMPTY_FORM = {
623
+ name: "",
624
+ value: "",
625
+ description: ""
626
+ };
627
+ /** 单行:完整变量名(复制)+ 描述 + 状态徽标 + 两步删除。 */
628
+ function GlobalRow(props) {
629
+ const { t, entry } = props;
630
+ const [confirming, setConfirming] = (0, react.useState)(false);
631
+ const [copied, setCopied] = (0, react.useState)(false);
632
+ const timer = (0, react.useRef)(null);
633
+ (0, react.useEffect)(() => () => {
634
+ if (timer.current !== null) clearTimeout(timer.current);
635
+ }, []);
636
+ const onCopy = () => {
637
+ copyText(`$${entry.envName}`).then((ok) => {
638
+ if (!ok) return;
639
+ setCopied(true);
640
+ if (timer.current !== null) clearTimeout(timer.current);
641
+ timer.current = setTimeout(() => setCopied(false), 1500);
642
+ });
643
+ };
644
+ const onDelete = () => {
645
+ unsetGlobal(entry.name).then(() => props.onDeleted()).catch((error) => props.onError(errorText(t, error)));
646
+ };
647
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
648
+ className: "dse-row",
649
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
650
+ className: "dse-rowMain",
651
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
652
+ className: "dse-env",
653
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
654
+ className: "dse-envName",
655
+ children: ["$", entry.envName]
656
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
657
+ type: "button",
658
+ className: "dse-iconBtn",
659
+ title: t("copy"),
660
+ "aria-label": t("copy"),
661
+ onClick: onCopy,
662
+ children: copied ? "✓" : "⧉"
663
+ })]
664
+ }), entry.description !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
665
+ className: "dse-note",
666
+ children: entry.description
667
+ }) : null]
668
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
669
+ className: "dse-badges",
670
+ children: [
671
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
672
+ className: `dse-badge${entry.configured ? "" : " dse-badgeDim"}`,
673
+ children: entry.configured ? t("configured") : t("unconfigured")
674
+ }),
675
+ entry.writable ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
676
+ className: "dse-badge dse-badgeDim",
677
+ title: entry.source ?? "",
678
+ children: t("readonly")
679
+ }),
680
+ confirming ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
681
+ type: "button",
682
+ className: "dse-btn dse-btnPrimary",
683
+ onClick: onDelete,
684
+ children: t("delete")
685
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
686
+ type: "button",
687
+ className: "dse-btn dse-btnGhost",
688
+ onClick: () => setConfirming(false),
689
+ children: t("close")
690
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
691
+ type: "button",
692
+ className: "dse-iconBtn dse-delBtn",
693
+ title: t("delete"),
694
+ "aria-label": t("delete"),
695
+ onClick: () => setConfirming(true),
696
+ children: "✕"
697
+ })
698
+ ]
699
+ })]
700
+ });
701
+ }
702
+ function SecretsSection(props) {
703
+ const { t } = props;
704
+ const [data, setData] = (0, react.useState)(null);
705
+ const [failed, setFailed] = (0, react.useState)(false);
706
+ const [form, setForm] = (0, react.useState)(EMPTY_FORM);
707
+ const [saving, setSaving] = (0, react.useState)(false);
708
+ const [status, setStatus] = (0, react.useState)(null);
709
+ const load = () => {
710
+ fetchSecrets().then((list) => {
711
+ setData(list);
712
+ setFailed(false);
713
+ }).catch(() => setFailed(true));
714
+ };
715
+ (0, react.useEffect)(() => {
716
+ load();
717
+ }, []);
718
+ const onSave = () => {
719
+ setSaving(true);
720
+ setStatus(null);
721
+ setGlobal(form.name, form.value, form.description).then(() => {
722
+ setForm(EMPTY_FORM);
723
+ setStatus({
724
+ kind: "ok",
725
+ text: t("configured")
726
+ });
727
+ load();
728
+ }).catch((error) => {
729
+ setStatus({
730
+ kind: "err",
731
+ text: errorText(t, error instanceof ApiError ? error : new ApiError("internal"))
732
+ });
733
+ }).finally(() => setSaving(false));
734
+ };
735
+ if (data === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
736
+ className: "dse-section",
737
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
738
+ className: "dse-empty",
739
+ children: failed ? t("loadFailed") : "…"
740
+ }), failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
741
+ type: "button",
742
+ className: "dse-btn dse-btnGhost",
743
+ onClick: load,
744
+ children: t("retry")
745
+ }) : null]
746
+ });
747
+ const canSave = form.name.trim() !== "" && form.value !== "" && !saving;
748
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
749
+ className: "dse-section",
750
+ children: [
751
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
752
+ className: "dse-head",
753
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
754
+ className: "dse-title",
755
+ children: t("title")
756
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
757
+ className: "dse-desc",
758
+ children: t("description")
759
+ })]
760
+ }),
761
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
762
+ className: "dse-groupLabel",
763
+ children: t("globalList")
764
+ }),
765
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
766
+ className: "dse-rows",
767
+ children: data.global.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
768
+ className: "dse-empty",
769
+ children: t("emptyGlobal")
770
+ }) : data.global.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GlobalRow, {
771
+ t,
772
+ entry,
773
+ onDeleted: load,
774
+ onError: (text) => setStatus({
775
+ kind: "err",
776
+ text
777
+ })
778
+ }, entry.name))
779
+ }),
780
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
781
+ className: "dse-groupLabel",
782
+ children: t("addGlobal")
783
+ }),
784
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
785
+ className: "dse-form",
786
+ children: [
787
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
788
+ className: "dse-formGrid",
789
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
790
+ className: "dse-field",
791
+ children: [
792
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
793
+ className: "dse-head",
794
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
795
+ className: "dse-label",
796
+ htmlFor: "dse-name",
797
+ children: t("nameLabel")
798
+ })
799
+ }),
800
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
801
+ id: "dse-name",
802
+ className: "dse-input",
803
+ value: form.name,
804
+ placeholder: "GITHUB_TOKEN",
805
+ onChange: (event) => setForm({
806
+ ...form,
807
+ name: event.target.value
808
+ })
809
+ }),
810
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
811
+ className: "dse-hint",
812
+ children: t("nameHint")
813
+ })
814
+ ]
815
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
816
+ className: "dse-field",
817
+ children: [
818
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
819
+ className: "dse-head",
820
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
821
+ className: "dse-label",
822
+ htmlFor: "dse-value",
823
+ children: t("valueLabel")
824
+ })
825
+ }),
826
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
827
+ id: "dse-value",
828
+ className: "dse-input",
829
+ type: "password",
830
+ autoComplete: "off",
831
+ value: form.value,
832
+ onChange: (event) => setForm({
833
+ ...form,
834
+ value: event.target.value
835
+ })
836
+ }),
837
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
838
+ className: "dse-hint",
839
+ children: t("valueHint")
840
+ })
841
+ ]
842
+ })]
843
+ }),
844
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
845
+ className: "dse-field",
846
+ children: [
847
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
848
+ className: "dse-head",
849
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
850
+ className: "dse-label",
851
+ htmlFor: "dse-desc",
852
+ children: t("descLabel")
853
+ })
854
+ }),
855
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
856
+ id: "dse-desc",
857
+ className: "dse-input",
858
+ value: form.description,
859
+ onChange: (event) => setForm({
860
+ ...form,
861
+ description: event.target.value
862
+ })
863
+ }),
864
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
865
+ className: "dse-hint",
866
+ children: t("descHint")
867
+ })
868
+ ]
869
+ }),
870
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
871
+ className: "dse-foot",
872
+ children: [status !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
873
+ className: `dse-status${status.kind === "err" ? " dse-statusError" : ""}`,
874
+ children: status.text
875
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
876
+ type: "button",
877
+ className: "dse-btn dse-btnPrimary",
878
+ disabled: !canSave,
879
+ onClick: onSave,
880
+ children: saving ? t("saving") : t("save")
881
+ })]
882
+ })
883
+ ]
884
+ })
885
+ ]
886
+ });
887
+ }
888
+ //#endregion
889
+ //#region src/client/styles.ts
890
+ /**
891
+ * 样式注入:shared cardCss('dse') 提供官方卡片/字段/按钮基件,
892
+ * extra 部分为本插件特有规则——设置页变量表(桌面网格、≤767px 堆叠)
893
+ * 与会话注入控件(官方 chip 胶囊 + popover,窄屏转底部抽屉)。
894
+ * @module secret-env/client/styles
895
+ */
896
+ const EXTRA = `
897
+ /* 设置页布局 */
898
+ .dse-section{padding:0 0 24px}
899
+ .dse-head{flex-direction:column;gap:6px;padding:16px 0 12px;display:flex}
900
+ .dse-title{color:var(--dsw-alias-label-primary);font-size:18px;font-weight:600;margin:0;line-height:1.4}
901
+ .dse-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;margin:0;line-height:1.6}
902
+ .dse-groupLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;padding:14px 0 8px;margin:0}
903
+ .dse-rows{flex-direction:column;display:flex;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;overflow:hidden}
904
+ .dse-row{align-items:center;gap:12px;padding:10px 14px;display:flex;background:var(--dsw-alias-bg-layer-3)}
905
+ .dse-row+.dse-row{border-top:1px solid var(--dsw-alias-border-l2)}
906
+ .dse-rowMain{flex-direction:column;gap:3px;flex:1;min-width:0;display:flex}
907
+ .dse-env{align-items:center;gap:6px;display:flex;min-width:0}
908
+ .dse-envName{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--dsw-alias-label-primary);font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
909
+ .dse-note{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
910
+ .dse-badges{align-items:center;gap:6px;flex:none;display:flex}
911
+ .dse-badge{white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
912
+ .dse-badgeDim{background:0 0;color:var(--dsw-alias-label-tertiary)}
913
+ .dse-iconBtn{appearance:none;border:none;background:0 0;cursor:pointer;color:var(--dsw-alias-label-tertiary);border-radius:6px;padding:4px;display:inline-flex}
914
+ .dse-iconBtn:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}
915
+ .dse-iconBtn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
916
+ .dse-delBtn{color:var(--dsw-alias-label-tertiary)}
917
+ .dse-delBtn:hover{color:var(--dsw-alias-label-error)}
918
+ .dse-empty{color:var(--dsw-alias-label-tertiary);font-size:13px;padding:18px 14px;margin:0;background:var(--dsw-alias-bg-layer-3)}
919
+ .dse-form{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-3);padding:4px 14px 12px;margin-top:10px}
920
+ .dse-formGrid{display:grid;grid-template-columns:1fr 1fr;gap:0 16px}
921
+ /* 会话注入控件(composer 工具行胶囊,对齐官方 chip 语言) */
922
+ .dse-wrap{position:relative;align-items:center;display:inline-flex}
923
+ .dse-chip{background:var(--dsw-alias-bg-module-platform);min-width:34px;color:var(--dsw-alias-label-secondary);cursor:pointer;border:none;border-radius:999px;align-items:center;gap:4px;padding:2px 8px;font-size:13px;font-weight:500;line-height:20px;display:inline-flex}
924
+ .dse-chip:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
925
+ .dse-chip:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
926
+ .dse-chip:disabled{opacity:.6;cursor:default}
927
+ .dse-count{background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-bg-base);border-radius:999px;min-width:16px;text-align:center;font-size:11px;line-height:16px;padding:0 4px}
928
+ .dse-pop{position:absolute;bottom:calc(100% + 8px);right:0;z-index:60;width:320px;max-width:calc(100vw - 24px);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;box-shadow:0 8px 28px rgba(0,0,0,.18);padding:10px 12px 12px}
929
+ .dse-popHead{align-items:center;gap:8px;display:flex;padding:2px 0 8px}
930
+ .dse-popTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;flex:1;margin:0}
931
+ .dse-popHint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;margin:0 0 8px}
932
+ .dse-popList{flex-direction:column;display:flex;max-height:220px;overflow:auto;margin:0 -4px;padding:0 4px}
933
+ .dse-popList .dse-row{background:0 0;border-radius:8px;padding:8px 8px}
934
+ .dse-popList .dse-row+.dse-row{border-top:1px solid var(--dsw-alias-border-l2)}
935
+ .dse-popForm{border-top:1px solid var(--dsw-alias-border-l2);margin-top:8px;padding-top:4px}
936
+ .dse-foot{justify-content:flex-end;gap:8px;display:flex;padding-top:8px}
937
+ /* 响应式:窄屏表格转堆叠,popover 转底部抽屉 */
938
+ @media (max-width:767px){
939
+ .dse-formGrid{grid-template-columns:1fr}
940
+ .dse-row{flex-wrap:wrap;padding:12px 12px}
941
+ .dse-rowMain{flex-basis:100%}
942
+ .dse-badges{margin-left:auto}
943
+ .dse-pop{position:fixed;left:8px;right:8px;bottom:8px;top:auto;width:auto;max-width:none;max-height:70vh;overflow:auto}
944
+ .dse-chip{min-height:32px}
945
+ }
946
+ @media (pointer:coarse){
947
+ .dse-chip{min-height:32px}
948
+ .dse-iconBtn{min-width:44px;min-height:44px;align-items:center;justify-content:center}
949
+ }
950
+ `;
951
+ /** 幂等注入本插件样式;返回新标签(卸载用),已存在或无 document 返回 null。 */
952
+ function injectSecretEnvStyle(pluginId) {
953
+ return injectCardStyle(pluginId, cardCss("dse", EXTRA));
954
+ }
955
+ //#endregion
956
+ //#region src/client/client.ts
957
+ const name = "dsh-plus-secret-env";
958
+ /** 浏览器半需要的 cordis 服务 key(loader 据此注入;package.json 的 dsh.client.inject 管包加载顺序)。 */
959
+ const inject = ["slots", "locale"];
960
+ function apply(ctx) {
961
+ const c = ctx;
962
+ const styleTag = injectSecretEnvStyle("@dsh-plus/secret-env");
963
+ c.effect(() => () => {
964
+ styleTag?.remove();
965
+ }, "secret-env: style");
966
+ c.effect(() => c.locale.register(NS, {
967
+ zh,
968
+ en
969
+ }), "secret-env: locale");
970
+ const t = c.locale.bind(NS);
971
+ c.slots.inject("settings.section", () => c.slots.register({
972
+ name: "settings.section",
973
+ id: "dsh-plus-secret-env",
974
+ order: 16,
975
+ label: () => t("nav"),
976
+ inject: () => ({ t })
977
+ }, SecretsSection));
978
+ c.slots.inject("conversation.input.right", () => c.slots.register({
979
+ name: "conversation.input.right",
980
+ id: "dsh-plus-secret-env",
981
+ order: 40,
982
+ locale: NS,
983
+ inject: (sessionId) => ({ sessionId })
984
+ }, ComposerSecret));
985
+ }
986
+ //#endregion
987
+ exports.apply = apply;
988
+ exports.inject = inject;
989
+ exports.name = name;
990
+ return module.exports;
991
+ }
992
+ });