@shgroup/dsh-serenity-hooks 1.21.1 → 1.23.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.
package/lib/client.js CHANGED
@@ -7,266 +7,12 @@ window.__ModuleLoader__.load({
7
7
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
8
8
  let react = require("react");
9
9
  let react_jsx_runtime = require("react/jsx-runtime");
10
- //#region src/client/accounts-api.ts
11
- /** wire 账号 → 本地编辑行 */
12
- function accountDraftFromWire(a) {
13
- return {
14
- id: a.id,
15
- user: a.user,
16
- pass: "",
17
- isNew: false,
18
- hasPassword: a.hasPassword
19
- };
20
- }
21
- /** 生成新账号 id(本地编辑用;提交时若为空由服务端拒绝) */
22
- function newAccountId() {
23
- return `a-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
24
- }
25
- /** 本地编辑行 → wire 账号(提交时;pass 字段携带) */
26
- function accountToWire(d) {
27
- return {
28
- id: d.id,
29
- user: d.user,
30
- pass: d.pass
31
- };
32
- }
33
- /** 校验本地编辑行:user 非空;新账号必须设密码 */
34
- function validateDraft(d) {
35
- if (d.user.trim() === "") return "用户名不能为空";
36
- if (d.isNew && d.pass === "") return "新账号必须设置密码";
37
- return null;
38
- }
39
- const CONFIG_PATH = "/serenity/config";
40
- /** GET 配置(含账号列表;无 hash) */
41
- async function fetchConfig(sessionId) {
42
- const qs = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : "";
43
- const res = await fetch(`${CONFIG_PATH}${qs}`, { headers: {
44
- accept: "application/json",
45
- "x-serenity-ui": "1"
46
- } });
47
- if (!res.ok) return null;
48
- return (await res.json()).config ?? null;
49
- }
50
- /** PUT 配置(账号 patch)→ 返回保存后的 wire */
51
- async function saveConfig(sessionId, patch) {
52
- const res = await fetch(CONFIG_PATH, {
53
- method: "PUT",
54
- headers: {
55
- "content-type": "application/json",
56
- "x-serenity-ui": "1"
57
- },
58
- body: JSON.stringify({
59
- sessionId,
60
- config: patch
61
- })
62
- });
63
- if (!res.ok) {
64
- const body = await res.json().catch(() => null);
65
- throw new Error(body?.error ?? `保存失败(${res.status})`);
66
- }
67
- return (await res.json()).config ?? null;
68
- }
69
- //#endregion
70
- //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/AccountsTab
71
- const css$2 = "/* AccountsTab — 高级面板账号 tab(v1.21;--dsw-alias-* 语义 token,明暗自适应) */\n\n.at-root {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 6px 2px;\n font-size: 13px;\n}\n\n.at-note {\n margin: 0;\n font-size: 12px;\n opacity: 0.65;\n}\n\n.at-row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.at-label {\n font-weight: 600;\n white-space: nowrap;\n}\n\n.at-input {\n flex: 1;\n min-width: 0;\n padding: 5px 8px;\n border: 1px solid var(--dsw-alias-border, #333);\n border-radius: 6px;\n background: var(--dsw-alias-field, #222);\n color: var(--dsw-alias-text, #eee);\n font-size: 13px;\n}\n\n.at-input.at-port {\n flex: none;\n width: 72px;\n}\n\n.at-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.at-head {\n display: flex;\n gap: 8px;\n font-size: 12px;\n opacity: 0.6;\n padding: 0 2px;\n}\n\n.at-item {\n display: flex;\n gap: 8px;\n align-items: center;\n}\n\n.at-colUser { flex: 1.2; }\n.at-colPass { flex: 1.6; }\n.at-colOp { flex: none; width: 48px; }\n\n.at-del {\n flex: none;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border, #444);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-danger, #f87171);\n font-size: 12px;\n cursor: pointer;\n}\n\n.at-add {\n align-self: flex-start;\n padding: 5px 12px;\n border: 1px dashed var(--dsw-alias-border, #444);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-text, #ccc);\n font-size: 12px;\n cursor: pointer;\n}\n\n.at-error {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-danger, #f87171);\n}\n\n.at-actions {\n display: flex;\n justify-content: flex-end;\n padding-top: 4px;\n}\n\n.at-save {\n padding: 6px 22px;\n border: 0;\n border-radius: 6px;\n background: var(--dsw-alias-accent, #3b82f6);\n color: #fff;\n font-size: 13px;\n cursor: pointer;\n}\n\n.at-save:disabled {\n opacity: 0.6;\n cursor: default;\n}\n";
72
- if (typeof document !== "undefined" && document.querySelector("style[data-sp-css]") === null) {
73
- const tag = document.createElement("style");
74
- tag.setAttribute("data-sp-css", "1");
75
- tag.textContent = css$2;
76
- document.head.appendChild(tag);
77
- }
78
- //#endregion
79
- //#region src/client/AccountsTab.tsx
80
- /**
81
- * AccountsTab.tsx — 高级面板「账号」tab(v1.21 复杂配置层)
82
- *
83
- * 承载复杂配置:gateway 监听 host/port + 账号列表 CRUD(含密码)。
84
- * 简单配置(开关/阈值)在 dsh 原生设置面板(SettingsSection)——本 tab 仅提示引导。
85
- * 数据:GET/PUT /serenity/config(accounts-api.ts);无滚动条约束(紧凑布局)。
86
- */
87
- function AccountsTab(props) {
88
- const { sessionId } = props;
89
- const [config, setConfig] = (0, react.useState)(null);
90
- const [drafts, setDrafts] = (0, react.useState)([]);
91
- const [host, setHost] = (0, react.useState)("0.0.0.0");
92
- const [port, setPort] = (0, react.useState)(3081);
93
- const [error, setError] = (0, react.useState)(null);
94
- const [busy, setBusy] = (0, react.useState)(false);
95
- const [loaded, setLoaded] = (0, react.useState)(false);
96
- (0, react.useEffect)(() => {
97
- let alive = true;
98
- fetchConfig(sessionId).then((cfg) => {
99
- if (!alive) return;
100
- if (cfg) {
101
- setConfig(cfg);
102
- setHost(cfg.gateway.host);
103
- setPort(cfg.gateway.port);
104
- setDrafts(cfg.gateway.accounts.map(accountDraftFromWire));
105
- }
106
- setLoaded(true);
107
- });
108
- return () => {
109
- alive = false;
110
- };
111
- }, [sessionId]);
112
- const updateDraft = (id, patch) => {
113
- setDrafts((ds) => ds.map((d) => d.id === id ? {
114
- ...d,
115
- ...patch
116
- } : d));
117
- };
118
- const addRow = () => {
119
- setDrafts((ds) => [...ds, {
120
- id: newAccountId(),
121
- user: "",
122
- pass: "",
123
- isNew: true,
124
- hasPassword: false
125
- }]);
126
- };
127
- const removeRow = (id) => {
128
- setDrafts((ds) => ds.filter((d) => d.id !== id));
129
- };
130
- const save = async () => {
131
- for (const d of drafts) {
132
- const err = validateDraft(d);
133
- if (err) {
134
- setError(err);
135
- return;
136
- }
137
- }
138
- setBusy(true);
139
- setError(null);
140
- try {
141
- const cfg = await saveConfig(sessionId, { gateway: {
142
- host,
143
- port,
144
- accounts: drafts.map(accountToWire)
145
- } });
146
- if (cfg) {
147
- setConfig(cfg);
148
- setDrafts(cfg.gateway.accounts.map(accountDraftFromWire));
149
- }
150
- } catch (err) {
151
- setError(err instanceof Error ? err.message : String(err));
152
- } finally {
153
- setBusy(false);
154
- }
155
- };
156
- if (!loaded) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
157
- className: "at-root",
158
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
159
- className: "at-note",
160
- children: "加载中…"
161
- })
162
- });
163
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
164
- className: "at-root",
165
- children: [
166
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
167
- className: "at-note",
168
- children: "复杂配置(账号密码)。开关/阈值等简单配置请到 dsh 设置面板「Serenity」页。"
169
- }),
170
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
171
- className: "at-row",
172
- children: [
173
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
174
- className: "at-label",
175
- children: "监听地址"
176
- }),
177
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
178
- className: "at-input",
179
- value: host,
180
- onChange: (e) => setHost(e.target.value),
181
- placeholder: "0.0.0.0"
182
- }),
183
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
184
- className: "at-label",
185
- children: "端口"
186
- }),
187
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
188
- className: "at-input at-port",
189
- type: "number",
190
- value: port,
191
- min: 1,
192
- max: 65535,
193
- onChange: (e) => setPort(Number(e.target.value) || 0)
194
- })
195
- ]
196
- }),
197
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
198
- className: "at-list",
199
- children: [
200
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
201
- className: "at-head",
202
- children: [
203
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
204
- className: "at-col at-colUser",
205
- children: "用户名"
206
- }),
207
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
208
- className: "at-col at-colPass",
209
- children: "密码"
210
- }),
211
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "at-col at-colOp" })
212
- ]
213
- }),
214
- drafts.map((d) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
215
- className: "at-item",
216
- children: [
217
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
218
- className: "at-input at-colUser",
219
- value: d.user,
220
- placeholder: "用户名",
221
- onChange: (e) => updateDraft(d.id, { user: e.target.value })
222
- }),
223
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
224
- className: "at-input at-colPass",
225
- type: "password",
226
- value: d.pass,
227
- placeholder: d.isNew ? "必填" : d.hasPassword ? "留空保留原密码" : "未设置",
228
- onChange: (e) => updateDraft(d.id, { pass: e.target.value })
229
- }),
230
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
231
- type: "button",
232
- className: "at-del",
233
- onClick: () => removeRow(d.id),
234
- children: "删除"
235
- })
236
- ]
237
- }, d.id)),
238
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
239
- type: "button",
240
- className: "at-add",
241
- onClick: addRow,
242
- children: "+ 添加账号"
243
- })
244
- ]
245
- }),
246
- error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
247
- className: "at-error",
248
- children: error
249
- }),
250
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
251
- className: "at-actions",
252
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
253
- type: "button",
254
- className: "at-save",
255
- disabled: busy,
256
- onClick: () => void save(),
257
- children: busy ? "保存中…" : "保存"
258
- })
259
- })
260
- ]
261
- });
262
- }
263
- //#endregion
264
10
  //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/SafeModePanel
265
- const css$1 = "/* SafeModePanel — 会话头部 Serenity 状态卡片(v1.21 UX 重构)\n * 卡片 → 点击 → 官方 Modal(body portal 遮罩 + 居中)\n * 遵循 web-styling.md:颜色只用 --dsw-alias-* 语义 token(明暗自适应);\n * 前缀 sp- 防冲突。 */\n\n/* ── 头部状态卡片(可点击) ── */\n\n.sp-card {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 26px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-module-platform);\n color: inherit;\n font-size: 12px;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease),\n border-color var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-card:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n border-color: var(--dsw-alias-border-l2);\n}\n\n.sp-card:focus-visible {\n outline: 2px solid var(--dsw-alias-button-ghost-active-border);\n outline-offset: 1px;\n}\n\n.sp-card:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.sp-dot {\n flex: none;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n}\n\n.sp-dotOn {\n background: var(--dsw-alias-state-success-primary);\n}\n\n.sp-dotOff {\n background: var(--dsw-alias-label-dimmed);\n}\n\n.sp-brand {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.sp-chev {\n color: var(--dsw-alias-label-dimmed);\n}\n\n/* safe-mode 徽标(卡片本体直接可见;ON 红 / OFF 灰) */\n.sp-sm {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n height: 18px;\n padding: 0 7px;\n border-radius: 9px;\n font-size: 11px;\n font-weight: 600;\n line-height: 1;\n}\n\n.sp-smOn {\n background: var(--dsw-alias-state-warning-bg, rgba(245, 158, 11, 0.18));\n color: var(--dsw-alias-state-warning-fg, #f59e0b);\n border: 1px solid var(--dsw-alias-state-warning-border, rgba(245, 158, 11, 0.4));\n}\n\n.sp-smOff {\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.16));\n color: var(--dsw-alias-label-dimmed);\n border: 1px solid var(--dsw-alias-border-l1);\n}\n\n.sp-smIcon {\n flex: none;\n}\n\n/* ── 官方 Modal 定制(宽度 + 无滚动条) ── */\n\n.sp-modal {\n width: 480px;\n max-width: calc(100vw - 48px);\n /* 无滚动条约束:内容在视口内完整容纳(紧凑布局,溢出不滚动) */\n}\n\n/* ── tab 栏 ── */\n\n.sp-tabs {\n display: flex;\n gap: 4px;\n padding: 0 2px 10px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n}\n\n.sp-tab {\n flex: 1;\n padding: 7px 0;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.sp-tab:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.sp-tabOn {\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.2));\n color: var(--dsw-alias-label-primary);\n font-weight: 600;\n}\n\n/* ── 模态内容(紧凑布局,无滚动条) ── */\n\n.sp-modalBody {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding-top: 12px;\n /* 内容超出视口时 body 区滚动(兜底——不再截断);整卡不滚动 */\n overflow-y: auto;\n max-height: calc(100vh - 200px);\n}\n\n.sp-row {\n display: flex;\n align-items: baseline;\n gap: 10px;\n min-width: 0;\n}\n\n.sp-label {\n flex: none;\n width: 40px;\n font-size: 12px;\n color: var(--dsw-alias-label-caption);\n}\n\n.sp-value {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n font-family: var(--ds-font-family-code, monospace);\n word-break: break-all;\n}\n\n.sp-actions {\n display: flex;\n justify-content: flex-end;\n padding-top: 4px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n margin-top: 2px;\n}\n\n/* safe-mode 开关(大) */\n\n.sp-toggle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 24px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n font-size: 12px;\n cursor: pointer;\n}\n\n.sp-toggleLg {\n height: 30px;\n padding: 0 16px;\n font-size: 13px;\n}\n\n.sp-toggleOn {\n background: var(--dsw-alias-state-warning-bg, rgba(245, 158, 11, 0.15));\n color: var(--dsw-alias-state-warning-fg, #f59e0b);\n border-color: var(--dsw-alias-state-warning-border, rgba(245, 158, 11, 0.4));\n}\n\n.sp-toggleOff {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-toggleIcon {\n flex: none;\n}\n\n/* loop 运行状态区块(并行任务各自一行) */\n\n.sp-section {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding-top: 6px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n margin-top: 2px;\n}\n\n.sp-loopItem {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 2px 0 2px 50px;\n min-width: 0;\n}\n\n.sp-loopHead {\n display: flex;\n align-items: baseline;\n gap: 6px;\n min-width: 0;\n}\n\n.sp-loopLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n\n.sp-loopRound {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n}\n\n.sp-loopTime {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n}\n\n.sp-loopResp {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* loop 详情折叠控件 */\n.sp-loopsToggle {\n flex: none;\n padding: 2px 8px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 5px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n cursor: pointer;\n}\n\n.sp-loopsToggle:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.sp-loopsList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-top: 2px;\n}\n\n.sp-loopsMore {\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n padding: 2px 0 2px 50px;\n}\n\n";
11
+ const css$3 = "/* SafeModePanel — 会话头部 Serenity 状态卡片(v1.22 UX 重构)\n * 卡片 → 点击 → 官方 Modal(body portal 遮罩 + 居中)\n * 模态内:tab 栏 + 分组(groupTitle 标题 + rowCard 行卡)——看齐官方 settings 设计语言\n * 遵循 web-styling.md:颜色只用 --dsw-alias-* 语义 token(明暗自适应);\n * 前缀 sp- 防冲突。 */\n\n/* ── 头部状态卡片(可点击) ── */\n\n.sp-card {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 26px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-module-platform);\n color: inherit;\n font-size: 12px;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease),\n border-color var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-card:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n border-color: var(--dsw-alias-border-l2);\n}\n\n.sp-card:focus-visible {\n outline: 2px solid var(--dsw-alias-button-ghost-active-border);\n outline-offset: 1px;\n}\n\n.sp-card:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.sp-dot {\n flex: none;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n}\n\n.sp-dotOn {\n background: var(--dsw-alias-state-success-primary);\n}\n\n.sp-dotOff {\n background: var(--dsw-alias-label-dimmed);\n}\n\n.sp-brand {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.sp-chev {\n color: var(--dsw-alias-label-dimmed);\n}\n\n/* safe-mode 徽标(卡片本体直接可见;ON 红 / OFF 灰) */\n.sp-sm {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n height: 18px;\n padding: 0 7px;\n border-radius: 9px;\n font-size: 11px;\n font-weight: 600;\n line-height: 1;\n}\n\n.sp-smOn {\n background: var(--dsw-alias-state-warning-bg, rgba(245, 158, 11, 0.18));\n color: var(--dsw-alias-state-warning-fg, #f59e0b);\n border: 1px solid var(--dsw-alias-state-warning-border, rgba(245, 158, 11, 0.4));\n}\n\n.sp-smOff {\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.16));\n color: var(--dsw-alias-label-dimmed);\n border: 1px solid var(--dsw-alias-border-l1);\n}\n\n.sp-smIcon {\n flex: none;\n}\n\n/* ── 官方 Modal 定制(宽度 + 内容紧凑) ── */\n\n.sp-modal {\n width: 520px;\n max-width: calc(100vw - 48px);\n}\n\n/* ── tab 栏 ── */\n\n.sp-tabs {\n display: flex;\n gap: 6px;\n padding: 0 2px 12px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n}\n\n.sp-tab {\n flex: 1;\n padding: 8px 0;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-tab:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.sp-tabOn {\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.2));\n color: var(--dsw-alias-label-primary);\n font-weight: 600;\n}\n\n/* ── 模态内容 ── */\n\n.sp-modalBody {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding-top: 14px;\n /* 内容超出视口时 body 区滚动(兜底——不再截断);整卡不滚动 */\n overflow-y: auto;\n max-height: calc(100vh - 200px);\n}\n\n/* ── 分组(官方 settings 语言:groupTitle + rows) ── */\n\n.sp-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.sp-groupHead {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.sp-groupTitle {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-rows {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n/* 行卡(rowCard:border + radius + padding;值与 label 分离、长内容可换行) */\n.sp-row {\n display: flex;\n align-items: baseline;\n gap: 12px;\n min-width: 0;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-module);\n}\n\n.sp-label {\n flex: none;\n width: 76px;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n}\n\n.sp-value {\n flex: 1;\n min-width: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n font-family: var(--ds-font-family-code, monospace);\n word-break: break-all;\n overflow-wrap: anywhere;\n}\n\n/* 安全模式动作区 */\n.sp-actions {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-module);\n}\n\n.sp-toggle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 24px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n font-size: 12px;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-toggleLg {\n height: 30px;\n padding: 0 16px;\n font-size: 13px;\n}\n\n.sp-toggleOn {\n background: var(--dsw-alias-state-warning-bg, rgba(245, 158, 11, 0.15));\n color: var(--dsw-alias-state-warning-fg, #f59e0b);\n border-color: var(--dsw-alias-state-warning-border, rgba(245, 158, 11, 0.4));\n}\n\n.sp-toggleOff {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-toggleIcon {\n flex: none;\n}\n\n.sp-toggleDesc {\n flex: 1;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* loop 运行状态区块 */\n\n.sp-loopsToggle {\n flex: none;\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n cursor: pointer;\n}\n\n.sp-loopsToggle:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.sp-loopsList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-top: 2px;\n}\n\n.sp-loopItem {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n min-width: 0;\n}\n\n.sp-loopHead {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n}\n\n.sp-loopLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.sp-loopRound {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n}\n\n.sp-loopTime {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n}\n\n.sp-loopResp {\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.sp-loopsMore {\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n padding: 4px 12px;\n}\n\n/* 底部提示(配置入口引导) */\n.sp-hint {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n padding: 8px 12px;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 10px;\n}\n";
266
12
  if (typeof document !== "undefined" && document.querySelector("style[data-sp-css]") === null) {
267
13
  const tag = document.createElement("style");
268
14
  tag.setAttribute("data-sp-css", "1");
269
- tag.textContent = css$1;
15
+ tag.textContent = css$3;
270
16
  document.head.appendChild(tag);
271
17
  }
272
18
  //#endregion
@@ -277,12 +23,11 @@ window.__ModuleLoader__.load({
277
23
  function cx(...parts) {
278
24
  return parts.filter(Boolean).join(" ");
279
25
  }
280
- /** 会话头部:Serenity 状态卡片(点击 → 官方 Modal 居中,双 tab) */
26
+ /** 会话头部:CCC 状态栏卡片(点击 → 官方 Modal 居中,纯状态展示) */
281
27
  function SafeModePanel(props) {
282
28
  const [status, setStatus] = (0, react.useState)(null);
283
29
  const [busy, setBusy] = (0, react.useState)(false);
284
30
  const [open, setOpen] = (0, react.useState)(false);
285
- const [tab, setTab] = (0, react.useState)("status");
286
31
  const [loops, setLoops] = (0, react.useState)([]);
287
32
  const [loopsOpen, setLoopsOpen] = (0, react.useState)(false);
288
33
  const sessionId = props.sessionId;
@@ -346,6 +91,7 @@ window.__ModuleLoader__.load({
346
91
  })]
347
92
  });
348
93
  const inCcc = status.root !== null;
94
+ const runningLoops = loops.filter((l) => !l.done);
349
95
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
350
96
  type: "button",
351
97
  className: cx("sp-card"),
@@ -372,149 +118,182 @@ window.__ModuleLoader__.load({
372
118
  className: cx("sp-chev")
373
119
  })
374
120
  ]
375
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
121
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
376
122
  open,
377
123
  onClose: () => setOpen(false),
378
- title: "Serenity 高级设定",
124
+ title: "CCC 状态栏",
379
125
  closeLabel: "关闭",
380
126
  className: cx("sp-modal"),
381
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
382
- className: cx("sp-tabs"),
383
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
384
- type: "button",
385
- className: cx("sp-tab", tab === "status" && "sp-tabOn"),
386
- onClick: () => setTab("status"),
387
- children: "状态"
388
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
389
- type: "button",
390
- className: cx("sp-tab", tab === "accounts" && "sp-tabOn"),
391
- onClick: () => setTab("accounts"),
392
- children: "账号"
393
- })]
394
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
127
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
395
128
  className: cx("sp-modalBody"),
396
- children: tab === "accounts" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountsTab, { sessionId: String(sessionId ?? "") }) : inCcc ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
397
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
398
- className: cx("sp-row"),
399
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
400
- className: cx("sp-label"),
401
- children: "CCC"
402
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
403
- className: cx("sp-value"),
404
- title: status.root ?? void 0,
405
- children: status.root
406
- })]
407
- }),
129
+ children: inCcc ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
408
130
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
409
- className: cx("sp-row"),
410
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
411
- className: cx("sp-label"),
412
- children: "loop"
413
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
414
- className: cx("sp-value"),
415
- title: status.loopModel ? `loop.defaultModel: ${status.loopModel}` : void 0,
416
- children: status.loopModel ?? "未配置"
417
- })]
418
- }),
419
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
420
- className: cx("sp-row"),
421
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
422
- className: cx("sp-label"),
423
- children: "守卫"
424
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
425
- className: cx("sp-value"),
131
+ className: cx("sp-group"),
132
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
133
+ className: cx("sp-groupTitle"),
134
+ children: "运行环境"
135
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
136
+ className: cx("sp-rows"),
426
137
  children: [
427
- "blacklist ",
428
- status.blacklist.length,
429
- " 条",
430
- status.threshold !== null ? ` · keeper 阈值 ${status.threshold}` : ""
138
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
139
+ className: cx("sp-row"),
140
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
141
+ className: cx("sp-label"),
142
+ children: "CCC"
143
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
144
+ className: cx("sp-value"),
145
+ title: status.root ?? void 0,
146
+ children: status.root
147
+ })]
148
+ }),
149
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
150
+ className: cx("sp-row"),
151
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
152
+ className: cx("sp-label"),
153
+ children: "Loop 模型"
154
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
155
+ className: cx("sp-value"),
156
+ title: status.loopModel ? `loop.defaultModel: ${status.loopModel}` : void 0,
157
+ children: status.loopModel ?? "未配置"
158
+ })]
159
+ }),
160
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
161
+ className: cx("sp-row"),
162
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
163
+ className: cx("sp-label"),
164
+ children: "守卫"
165
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
166
+ className: cx("sp-value"),
167
+ children: [
168
+ "blacklist ",
169
+ status.blacklist.length,
170
+ " 条",
171
+ status.threshold !== null ? ` · keeper 阈值 ${status.threshold}` : ""
172
+ ]
173
+ })]
174
+ })
431
175
  ]
432
176
  })]
433
177
  }),
434
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
435
- className: cx("sp-actions"),
436
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
437
- type: "button",
438
- className: cx("sp-toggle", status.safeModeOn ? "sp-toggleOn" : "sp-toggleOff", "sp-toggleLg"),
439
- disabled: busy,
440
- onClick: () => void toggle(!status.safeModeOn),
441
- children: [
442
- status.safeModeOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
443
- size: 14,
444
- className: cx("sp-toggleIcon")
445
- }),
446
- "safe-mode ",
447
- status.safeModeOn ? "ON" : "OFF"
448
- ]
449
- })
178
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
179
+ className: cx("sp-group"),
180
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
181
+ className: cx("sp-groupTitle"),
182
+ children: "安全模式"
183
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
184
+ className: cx("sp-actions"),
185
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
186
+ type: "button",
187
+ className: cx("sp-toggle", status.safeModeOn ? "sp-toggleOn" : "sp-toggleOff", "sp-toggleLg"),
188
+ disabled: busy,
189
+ onClick: () => void toggle(!status.safeModeOn),
190
+ children: [
191
+ status.safeModeOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
192
+ size: 14,
193
+ className: cx("sp-toggleIcon")
194
+ }),
195
+ "safe-mode ",
196
+ status.safeModeOn ? "ON" : "OFF"
197
+ ]
198
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
199
+ className: cx("sp-toggleDesc"),
200
+ children: status.safeModeOn ? "写工具已隐藏,无人值守安全航行" : "写工具可用(点击开启)"
201
+ })]
202
+ })]
450
203
  }),
451
204
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
452
- className: cx("sp-section"),
453
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
454
- className: cx("sp-row"),
455
- children: [
456
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
457
- className: cx("sp-label"),
458
- children: "loops"
459
- }),
460
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
461
- className: cx("sp-value"),
462
- children: loops.filter((l) => !l.done).length > 0 ? `运行中 ${loops.filter((l) => !l.done).length}${loops.length > 0 ? ` / 共 ${loops.length}` : ""}` : "无运行中 loop"
463
- }),
464
- loops.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
205
+ className: cx("sp-group"),
206
+ children: [
207
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
208
+ className: cx("sp-groupHead"),
209
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
210
+ className: cx("sp-groupTitle"),
211
+ children: "Loop 运行"
212
+ }), loops.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
465
213
  type: "button",
466
214
  className: cx("sp-loopsToggle"),
467
215
  onClick: () => setLoopsOpen((v) => !v),
468
216
  children: loopsOpen ? "收起" : "详情"
217
+ })]
218
+ }),
219
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
220
+ className: cx("sp-rows"),
221
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
222
+ className: cx("sp-row"),
223
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
224
+ className: cx("sp-label"),
225
+ children: "状态"
226
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
227
+ className: cx("sp-value"),
228
+ children: runningLoops.length > 0 ? `运行中 ${runningLoops.length}${loops.length > 0 ? ` / 共 ${loops.length}` : ""}` : "无运行中 loop"
229
+ })]
469
230
  })
470
- ]
471
- }), loopsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
472
- className: cx("sp-loopsList"),
473
- children: [loops.slice(0, 3).map((l) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
474
- className: cx("sp-loopItem"),
475
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
476
- className: cx("sp-loopHead"),
231
+ }),
232
+ loopsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
233
+ className: cx("sp-loopsList"),
234
+ children: [loops.slice(0, 3).map((l) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
235
+ className: cx("sp-loopItem"),
236
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
237
+ className: cx("sp-loopHead"),
238
+ children: [
239
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
240
+ className: cx("sp-loopLabel"),
241
+ title: l.label,
242
+ children: l.label
243
+ }),
244
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
245
+ className: cx("sp-loopRound"),
246
+ children: l.done ? "✓ 完成" : `R${l.round}`
247
+ }),
248
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
249
+ className: cx("sp-loopTime"),
250
+ children: new Date(l.updated).toLocaleTimeString()
251
+ })
252
+ ]
253
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
254
+ className: cx("sp-loopResp"),
255
+ title: l.lastResponse,
256
+ children: l.lastResponse.slice(0, 80) || (l.done ? "已完成" : "(等待首轮响应)")
257
+ })]
258
+ }, l.label)), loops.length > 3 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
259
+ className: cx("sp-loopsMore"),
477
260
  children: [
478
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
479
- className: cx("sp-loopLabel"),
480
- title: l.label,
481
- children: l.label
482
- }),
483
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
484
- className: cx("sp-loopRound"),
485
- children: l.done ? "✓" : `R${l.round}`
486
- }),
487
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
488
- className: cx("sp-loopTime"),
489
- children: new Date(l.updated).toLocaleTimeString()
490
- })
261
+ "… 共 ",
262
+ loops.length,
263
+ " 个 loop(其余见等待界面)"
491
264
  ]
492
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
493
- className: cx("sp-loopResp"),
494
- title: l.lastResponse,
495
- children: l.lastResponse.slice(0, 80) || (l.done ? "已完成" : "(等待首轮响应)")
496
265
  })]
497
- }, l.label)), loops.length > 3 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
498
- className: cx("sp-loopsMore"),
499
- children: [
500
- "… 共 ",
501
- loops.length,
502
- " 个 loop(其余见等待界面)"
503
- ]
504
- })]
505
- })]
266
+ })
267
+ ]
268
+ }),
269
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
270
+ className: cx("sp-hint"),
271
+ children: [
272
+ "💡 开关/阈值/账号/工作区白名单请在 ",
273
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "设置 → Serenity" }),
274
+ " 中配置。"
275
+ ]
506
276
  })
507
277
  ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
508
- className: cx("sp-row"),
509
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
510
- className: cx("sp-label"),
278
+ className: cx("sp-group"),
279
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
280
+ className: cx("sp-groupTitle"),
511
281
  children: "状态"
512
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
513
- className: cx("sp-value"),
514
- children: "ACC 未激活(工作区不在 CCC 内)"
282
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
283
+ className: cx("sp-rows"),
284
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
285
+ className: cx("sp-row"),
286
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
287
+ className: cx("sp-label"),
288
+ children: "ACC"
289
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
290
+ className: cx("sp-value"),
291
+ children: "未激活(工作区不在 CCC 内)"
292
+ })]
293
+ })
515
294
  })]
516
295
  })
517
- })]
296
+ })
518
297
  })] });
519
298
  }
520
299
  //#endregion
@@ -528,8 +307,8 @@ window.__ModuleLoader__.load({
528
307
  * 单图:用户提供了一张图片(路径:...);多图:每张一行路径
529
308
  */
530
309
  function imageNoteTemplate(paths) {
531
- if (paths.length === 1) return `用户提供了一张图片(路径:${paths[0]})`;
532
- return `用户提供了 ${paths.length} 张图片:\n${paths.map((p) => `- ${p}`).join("\n")}`;
310
+ if (paths.length === 1) return `The user provided an image (path: ${paths[0]})`;
311
+ return `The user provided ${paths.length} images:\n${paths.map((p) => `- ${p}`).join("\n")}`;
533
312
  }
534
313
  /** 浏览器 File → base64(与 ui-conversation serializeImages 等价的最小实现) */
535
314
  function fileToBase64(file) {
@@ -641,6 +420,691 @@ window.__ModuleLoader__.load({
641
420
  return null;
642
421
  }
643
422
  //#endregion
423
+ //#region src/client/accounts-api.ts
424
+ /** wire 账号 → 本地编辑行 */
425
+ function accountDraftFromWire(a) {
426
+ return {
427
+ id: a.id,
428
+ user: a.user,
429
+ pass: "",
430
+ isNew: false,
431
+ hasPassword: a.hasPassword,
432
+ hasTotp: a.hasTotp,
433
+ totpState: "none"
434
+ };
435
+ }
436
+ /** 生成新账号 id(本地编辑用;提交时若为空由服务端拒绝) */
437
+ function newAccountId() {
438
+ return `a-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
439
+ }
440
+ const B32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
441
+ /** 字节 → base32(无填充) */
442
+ function bytesToBase32(bytes) {
443
+ let bits = 0;
444
+ let value = 0;
445
+ let out = "";
446
+ for (const byte of bytes) {
447
+ value = value << 8 | byte;
448
+ bits += 8;
449
+ while (bits >= 5) {
450
+ out += B32_ALPHABET[value >>> bits - 5 & 31];
451
+ bits -= 5;
452
+ }
453
+ }
454
+ if (bits > 0) out += B32_ALPHABET[value << 5 - bits & 31];
455
+ return out;
456
+ }
457
+ /** 生成 20 字节随机 TOTP secret(Web Crypto getRandomValues) */
458
+ function newTotpSecret() {
459
+ const bytes = /* @__PURE__ */ new Uint8Array(20);
460
+ if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") crypto.getRandomValues(bytes);
461
+ else for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
462
+ return bytesToBase32(bytes);
463
+ }
464
+ /** 构造 otpauth URI(Authenticator 扫码/手动录入) */
465
+ function otpauthUriClient(secret, label, issuer = "Serenity Home") {
466
+ const params = new URLSearchParams({
467
+ secret,
468
+ issuer,
469
+ algorithm: "SHA1",
470
+ digits: "6",
471
+ period: "30"
472
+ });
473
+ return `otpauth://totp/${encodeURIComponent(label)}?${params.toString()}`;
474
+ }
475
+ /** 本地编辑行 → wire 账号(提交时;pass 字段携带) */
476
+ function accountToWire(d) {
477
+ return {
478
+ id: d.id,
479
+ user: d.user,
480
+ pass: d.pass,
481
+ ...d.totpState === "pending" && d.totpSecret !== void 0 && d.totpSecret !== "" ? { totpSecret: d.totpSecret } : d.totpState === "clear" ? { totpReset: true } : {}
482
+ };
483
+ }
484
+ /** 校验本地编辑行:user 非空;新账号必须设密码;pending TOTP 需确认码 */
485
+ function validateDraft(d) {
486
+ if (d.user.trim() === "") return "用户名不能为空";
487
+ if (d.isNew && d.pass === "") return "新账号必须设置密码";
488
+ if (d.totpState === "pending" && !/^\d{6}$/.test(d.totpConfirm ?? "")) return `绑定 ${d.user} 的验证器需输入 6 位确认码`;
489
+ return null;
490
+ }
491
+ const CONFIG_PATH = "/serenity/config";
492
+ /**
493
+ * 获取已有工作区列表(需求 1:白名单从已有工作区选择而非手输)。
494
+ * 走 DSH 原生 RPC `POST /api/workspace.list`(JSON RPC 形态)。
495
+ * 信封必须是完整 ClientRequest:`{ type: 'client-request', rpcId, method, payload }`
496
+ * (api/rpc.ts wire 契约——缺 type/method → clientRequestSchema 校验失败 → bad-request)。
497
+ * 返回 { path, title }[];失败返回空数组(面板显示"暂无可选工作区")。
498
+ */
499
+ async function fetchWorkspaces() {
500
+ try {
501
+ const res = await fetch("/api/workspace.list", {
502
+ method: "POST",
503
+ headers: { "content-type": "application/json" },
504
+ body: JSON.stringify({
505
+ type: "client-request",
506
+ rpcId: `ws-${Date.now().toString(36)}`,
507
+ method: "workspace.list",
508
+ payload: {}
509
+ })
510
+ });
511
+ if (!res.ok) return [];
512
+ const items = (await res.json())?.result?.value?.items;
513
+ if (!Array.isArray(items)) return [];
514
+ return items.filter((i) => typeof i.path === "string" && i.path !== "").map((i) => ({
515
+ path: i.path,
516
+ title: typeof i.title === "string" && i.title !== "" ? i.title : i.path
517
+ }));
518
+ } catch {
519
+ return [];
520
+ }
521
+ }
522
+ /** GET 配置(含账号列表;无 hash) */
523
+ async function fetchConfig() {
524
+ const res = await fetch(CONFIG_PATH, { headers: {
525
+ accept: "application/json",
526
+ "x-serenity-ui": "1"
527
+ } });
528
+ if (!res.ok) return null;
529
+ return (await res.json()).config ?? null;
530
+ }
531
+ /** PUT 配置(账号 patch + 工作区白名单 + persona 彩蛋)→ 返回保存后的 wire */
532
+ async function saveConfig(patch) {
533
+ const res = await fetch(CONFIG_PATH, {
534
+ method: "PUT",
535
+ headers: {
536
+ "content-type": "application/json",
537
+ "x-serenity-ui": "1"
538
+ },
539
+ body: JSON.stringify({ config: patch })
540
+ });
541
+ if (!res.ok) {
542
+ const body = await res.json().catch(() => null);
543
+ throw new Error(body?.error ?? `保存失败(${res.status})`);
544
+ }
545
+ return (await res.json()).config ?? null;
546
+ }
547
+ //#endregion
548
+ //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/AccountsEditor
549
+ const css$2 = "/* AccountsEditor — DSH 设置面板「外部访问」区块(v1.22)\n * 与 SettingsSection ss-* 同设计语言:--dsw-alias-* 语义 token,明暗自适应。 */\n\n.ae-root {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n font-size: 13px;\n}\n\n.ae-warn {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warning-fg, #f59e0b);\n word-break: break-word;\n}\n\n.ae-block {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.ae-title {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n.ae-desc {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n.ae-note {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ── 监听设置 / 白名单输入行 ── */\n\n.ae-row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.ae-label {\n flex: none;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n}\n\n.ae-input {\n flex: 1;\n min-width: 0;\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-module);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n outline: none;\n}\n\n.ae-input:focus {\n border-color: var(--dsw-alias-button-primary-bg, var(--dsw-alias-accent, #3b82f6));\n}\n\n.ae-input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n.ae-input.ae-port {\n flex: none;\n width: 76px;\n}\n\n/* ── 账号列表 ── */\n\n.ae-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.ae-head {\n display: flex;\n gap: 8px;\n font-size: 11px;\n font-weight: 500;\n color: var(--dsw-alias-label-dimmed);\n padding: 0 2px;\n text-transform: uppercase;\n letter-spacing: 0.02em;\n}\n\n.ae-item {\n display: flex;\n gap: 8px;\n align-items: center;\n padding: 6px 2px;\n}\n\n.ae-colUser { flex: 1.2; }\n.ae-colPass { flex: 1.6; }\n.ae-colTotp { flex: 2.2; min-width: 0; }\n.ae-colOp { flex: none; width: 48px; }\n\n/* ── v1.22.4 TOTP 第二因素 ── */\n\n.ae-totpBtn {\n flex: none;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.ae-totpBtn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.ae-totpPending {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-items: center;\n min-width: 0;\n}\n\n.ae-totpSecret {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n background: var(--dsw-alias-surface-raised, rgba(0, 0, 0, 0.25));\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 4px;\n padding: 2px 6px;\n color: var(--dsw-alias-label-secondary);\n word-break: break-all;\n}\n\n.ae-totpUri {\n font-size: 11px;\n color: var(--dsw-alias-accent, #3b82f6);\n text-decoration: none;\n}\n\n.ae-totpUri:hover {\n text-decoration: underline;\n}\n\n.ae-totpConfirm {\n max-width: 120px;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n letter-spacing: 0.2em;\n}\n\n.ae-totpBadge {\n font-size: 12px;\n color: var(--dsw-alias-state-warning-fg, #f59e0b);\n}\n\n.ae-totpClear {\n display: flex;\n gap: 6px;\n align-items: center;\n}\n\n.ae-check {\n display: flex;\n gap: 8px;\n align-items: center;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n margin-top: 10px;\n cursor: pointer;\n}\n\n.ae-check input {\n accent-color: var(--dsw-alias-accent, #3b82f6);\n}\n\n.ae-del {\n flex: none;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-state-danger-fg, #f87171);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-del:hover {\n background: var(--dsw-alias-state-danger-bg, rgba(248, 113, 113, 0.12));\n}\n\n.ae-add {\n align-self: flex-start;\n padding: 5px 12px;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-add:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.ae-wsSelect {\n margin-top: 8px;\n max-width: 100%;\n cursor: pointer;\n}\n\n/* ── 工作区白名单 chips ── */\n\n.ae-wsList {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.ae-wsChip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 3px 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.14));\n}\n\n.ae-wsPath {\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n font-family: var(--ds-font-family-code, monospace);\n word-break: break-all;\n}\n\n.ae-wsDel {\n flex: none;\n width: 18px;\n height: 18px;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--dsw-alias-label-dimmed);\n font-size: 13px;\n line-height: 1;\n cursor: pointer;\n}\n\n.ae-wsDel:hover {\n background: var(--dsw-alias-state-danger-bg, rgba(248, 113, 113, 0.18));\n color: var(--dsw-alias-state-danger-fg, #f87171);\n}\n\n/* ── 错误 / 操作区 ── */\n\n.ae-error {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-danger-fg, #f87171);\n word-break: break-word;\n}\n\n.ae-actions {\n display: flex;\n justify-content: flex-end;\n padding-top: 4px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n.ae-save {\n padding: 7px 24px;\n border: 0;\n border-radius: 8px;\n background: var(--dsw-alias-button-primary-bg, var(--dsw-alias-accent, #3b82f6));\n color: var(--dsw-alias-button-primary-fg, #fff);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n}\n\n.ae-save:hover:not(:disabled) {\n filter: brightness(1.08);\n}\n\n.ae-save:disabled {\n opacity: 0.6;\n cursor: default;\n}\n";
550
+ if (typeof document !== "undefined" && document.querySelector("style[data-sp-css]") === null) {
551
+ const tag = document.createElement("style");
552
+ tag.setAttribute("data-sp-css", "1");
553
+ tag.textContent = css$2;
554
+ document.head.appendChild(tag);
555
+ }
556
+ //#endregion
557
+ //#region src/client/AccountsEditor.tsx
558
+ /**
559
+ * AccountsEditor.tsx — DSH 设置面板「外部访问」区块(v1.22 重构 + v1.22.4 完善)
560
+ *
561
+ * 归属原则(v1.22 用户拍板):账号密码/工作区白名单是 **plugin 级配置**,
562
+ * 统一在 DSH 设置面板(plugin 层)设定——本组件即该设定面。
563
+ * 数据:GET/PUT /serenity/config(accounts-api.ts;服务端 scrypt hash,
564
+ * 存 ~/.dsh/serenity-hooks.json plugin 全局文件)。
565
+ *
566
+ * 内容:
567
+ * - 监听设置:gateway host/port + Secure Cookie(HTTPS 反代时)
568
+ * - 外部能力:允许新建工作区 / 启用 Authenticator 第二因素
569
+ * - 登录账号:账号列表 CRUD(含密码 + TOTP 绑定/解绑)
570
+ * - 工作区白名单:**从已有工作区选择**(v1.22.4 需求 1;非手输)
571
+ */
572
+ function AccountsEditor(props) {
573
+ const { gatewayOn } = props;
574
+ const [config, setConfig] = (0, react.useState)(null);
575
+ const [drafts, setDrafts] = (0, react.useState)([]);
576
+ const [host, setHost] = (0, react.useState)("0.0.0.0");
577
+ const [port, setPort] = (0, react.useState)(3081);
578
+ const [workspaces, setWorkspaces] = (0, react.useState)([]);
579
+ const [cookieSecure, setCookieSecure] = (0, react.useState)(false);
580
+ const [allowWorkspaceCreate, setAllowWorkspaceCreate] = (0, react.useState)(true);
581
+ const [totpEnabled, setTotpEnabled] = (0, react.useState)(false);
582
+ const [knownWorkspaces, setKnownWorkspaces] = (0, react.useState)([]);
583
+ const [error, setError] = (0, react.useState)(null);
584
+ const [busy, setBusy] = (0, react.useState)(false);
585
+ const [loaded, setLoaded] = (0, react.useState)(false);
586
+ const [saved, setSaved] = (0, react.useState)(false);
587
+ (0, react.useEffect)(() => {
588
+ let alive = true;
589
+ Promise.all([fetchConfig(), fetchWorkspaces()]).then(([cfg, wss]) => {
590
+ if (!alive) return;
591
+ setKnownWorkspaces(wss);
592
+ if (cfg) {
593
+ setConfig(cfg);
594
+ setHost(cfg.gateway.host);
595
+ setPort(cfg.gateway.port);
596
+ setDrafts(cfg.gateway.accounts.map(accountDraftFromWire));
597
+ setWorkspaces(cfg.gateway.workspaces ?? []);
598
+ setCookieSecure(cfg.gateway.cookieSecure === true);
599
+ setAllowWorkspaceCreate(cfg.gateway.allowWorkspaceCreate !== false);
600
+ setTotpEnabled(cfg.gateway.totpEnabled === true);
601
+ }
602
+ setLoaded(true);
603
+ });
604
+ return () => {
605
+ alive = false;
606
+ };
607
+ }, []);
608
+ const updateDraft = (id, patch) => {
609
+ setDrafts((ds) => ds.map((d) => d.id === id ? {
610
+ ...d,
611
+ ...patch
612
+ } : d));
613
+ };
614
+ const addRow = () => {
615
+ setDrafts((ds) => [...ds, {
616
+ id: newAccountId(),
617
+ user: "",
618
+ pass: "",
619
+ isNew: true,
620
+ hasPassword: false,
621
+ hasTotp: false,
622
+ totpState: "none"
623
+ }]);
624
+ };
625
+ const removeRow = (id) => {
626
+ setDrafts((ds) => ds.filter((d) => d.id !== id));
627
+ };
628
+ const startTotpBind = (d) => {
629
+ const secret = newTotpSecret();
630
+ updateDraft(d.id, {
631
+ totpState: "pending",
632
+ totpSecret: secret,
633
+ totpConfirm: ""
634
+ });
635
+ };
636
+ const cancelTotpBind = (d) => {
637
+ updateDraft(d.id, {
638
+ totpState: d.hasTotp ? "none" : "none",
639
+ totpSecret: void 0,
640
+ totpConfirm: ""
641
+ });
642
+ };
643
+ const markTotpClear = (d) => {
644
+ updateDraft(d.id, {
645
+ totpState: "clear",
646
+ totpSecret: void 0,
647
+ totpConfirm: ""
648
+ });
649
+ };
650
+ const toggleWorkspace = (path) => {
651
+ setWorkspaces((ws) => ws.includes(path) ? ws.filter((w) => w !== path) : [...ws, path]);
652
+ };
653
+ const removeWorkspace = (v) => {
654
+ setWorkspaces((ws) => ws.filter((w) => w !== v));
655
+ };
656
+ const save = async () => {
657
+ for (const d of drafts) {
658
+ const err = validateDraft(d);
659
+ if (err) {
660
+ setError(err);
661
+ return;
662
+ }
663
+ }
664
+ setBusy(true);
665
+ setError(null);
666
+ setSaved(false);
667
+ try {
668
+ const cfg = await saveConfig({ gateway: {
669
+ host,
670
+ port,
671
+ accounts: drafts.map(accountToWire),
672
+ workspaces,
673
+ cookieSecure,
674
+ allowWorkspaceCreate,
675
+ totpEnabled
676
+ } });
677
+ if (cfg) {
678
+ setConfig(cfg);
679
+ setDrafts(cfg.gateway.accounts.map(accountDraftFromWire));
680
+ setWorkspaces(cfg.gateway.workspaces ?? []);
681
+ setSaved(true);
682
+ setTimeout(() => setSaved(false), 2e3);
683
+ }
684
+ } catch (err) {
685
+ setError(err instanceof Error ? err.message : String(err));
686
+ } finally {
687
+ setBusy(false);
688
+ }
689
+ };
690
+ if (!loaded) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
691
+ className: "ae-note",
692
+ children: "加载中…"
693
+ });
694
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
695
+ className: "ae-root",
696
+ children: [
697
+ !gatewayOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
698
+ className: "ae-warn",
699
+ children: "⚠️ 双端口网关当前关闭——先开启上方「双端口网关」开关,外部访问才生效。"
700
+ }),
701
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
702
+ className: "ae-block",
703
+ children: [
704
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
705
+ className: "ae-title",
706
+ children: "监听设置"
707
+ }),
708
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
709
+ className: "ae-row",
710
+ children: [
711
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
712
+ className: "ae-label",
713
+ children: "监听地址"
714
+ }),
715
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
716
+ className: "ae-input",
717
+ value: host,
718
+ onChange: (e) => setHost(e.target.value),
719
+ placeholder: "0.0.0.0"
720
+ }),
721
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
722
+ className: "ae-label",
723
+ children: "端口"
724
+ }),
725
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
726
+ className: "ae-input ae-port",
727
+ type: "number",
728
+ value: port,
729
+ min: 1,
730
+ max: 65535,
731
+ onChange: (e) => setPort(Number(e.target.value) || 0)
732
+ })
733
+ ]
734
+ }),
735
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
736
+ className: "ae-check",
737
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
738
+ type: "checkbox",
739
+ checked: cookieSecure,
740
+ onChange: (e) => setCookieSecure(e.target.checked)
741
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Secure Cookie(仅 HTTPS 反代时开启;明文 HTTP 下关闭,否则登录态不生效)" })]
742
+ })
743
+ ]
744
+ }),
745
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
746
+ className: "ae-block",
747
+ children: [
748
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
749
+ className: "ae-title",
750
+ children: "外部能力"
751
+ }),
752
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
753
+ className: "ae-check",
754
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
755
+ type: "checkbox",
756
+ checked: allowWorkspaceCreate,
757
+ onChange: (e) => setAllowWorkspaceCreate(e.target.checked)
758
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "允许外部新建工作区(关闭后经外部网关的 workspace.create 一律拒绝;仅使用已有工作区)" })]
759
+ }),
760
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
761
+ className: "ae-check",
762
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
763
+ type: "checkbox",
764
+ checked: totpEnabled,
765
+ onChange: (e) => setTotpEnabled(e.target.checked)
766
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "启用 Authenticator 第二因素(关闭时 TOTP 完全禁用;开启后可对账号绑定验证器,登录需输入 6 位码)" })]
767
+ })
768
+ ]
769
+ }),
770
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
771
+ className: "ae-block",
772
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
773
+ className: "ae-title",
774
+ children: "登录账号"
775
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
776
+ className: "ae-list",
777
+ children: [
778
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
779
+ className: "ae-head",
780
+ children: [
781
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
782
+ className: "ae-col ae-colUser",
783
+ children: "用户名"
784
+ }),
785
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
786
+ className: "ae-col ae-colPass",
787
+ children: "密码"
788
+ }),
789
+ totpEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
790
+ className: "ae-col ae-colTotp",
791
+ children: "第二因素"
792
+ }),
793
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "ae-col ae-colOp" })
794
+ ]
795
+ }),
796
+ drafts.map((d) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
797
+ className: "ae-item",
798
+ children: [
799
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
800
+ className: "ae-input ae-colUser",
801
+ value: d.user,
802
+ placeholder: "用户名",
803
+ onChange: (e) => updateDraft(d.id, { user: e.target.value })
804
+ }),
805
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
806
+ className: "ae-input ae-colPass",
807
+ type: "password",
808
+ value: d.pass,
809
+ placeholder: d.isNew ? "必填" : d.hasPassword ? "留空保留原密码" : "未设置",
810
+ onChange: (e) => updateDraft(d.id, { pass: e.target.value })
811
+ }),
812
+ totpEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
813
+ className: "ae-col ae-colTotp",
814
+ children: d.totpState === "pending" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
815
+ className: "ae-totpPending",
816
+ children: [
817
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
818
+ className: "ae-totpSecret",
819
+ title: "录入到 Authenticator(Google Authenticator / 1Password / Aegis)",
820
+ children: d.totpSecret
821
+ }),
822
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
823
+ className: "ae-totpUri",
824
+ href: d.totpSecret ? otpauthUriClient(d.totpSecret, `${d.user.trim() || "user"}@serenity`) : "#",
825
+ target: "_blank",
826
+ rel: "noreferrer",
827
+ children: "otpauth://"
828
+ }),
829
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
830
+ className: "ae-input ae-totpConfirm",
831
+ type: "text",
832
+ inputMode: "numeric",
833
+ pattern: "[0-9]{6}",
834
+ maxLength: 6,
835
+ value: d.totpConfirm ?? "",
836
+ placeholder: "输入验证码确认",
837
+ onChange: (e) => updateDraft(d.id, { totpConfirm: e.target.value })
838
+ }),
839
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
840
+ type: "button",
841
+ className: "ae-del",
842
+ onClick: () => cancelTotpBind(d),
843
+ children: "取消"
844
+ })
845
+ ]
846
+ }) : d.totpState === "clear" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
847
+ className: "ae-totpClear",
848
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
849
+ className: "ae-totpBadge",
850
+ children: "将解绑"
851
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
852
+ type: "button",
853
+ className: "ae-del",
854
+ onClick: () => updateDraft(d.id, { totpState: "none" }),
855
+ children: "撤销"
856
+ })]
857
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
858
+ type: "button",
859
+ className: "ae-totpBtn",
860
+ onClick: () => d.hasTotp ? markTotpClear(d) : startTotpBind(d),
861
+ title: d.hasTotp ? "已绑定验证器——点击解绑" : "绑定 Authenticator 第二因素",
862
+ children: d.hasTotp ? "✓ 已绑定" : "绑定验证器"
863
+ })
864
+ }),
865
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
866
+ type: "button",
867
+ className: "ae-del",
868
+ onClick: () => removeRow(d.id),
869
+ children: "删除"
870
+ })
871
+ ]
872
+ }, d.id)),
873
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
874
+ type: "button",
875
+ className: "ae-add",
876
+ onClick: addRow,
877
+ children: "+ 添加账号"
878
+ })
879
+ ]
880
+ })]
881
+ }),
882
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
883
+ className: "ae-block",
884
+ children: [
885
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
886
+ className: "ae-title",
887
+ children: "工作区白名单"
888
+ }),
889
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
890
+ className: "ae-desc",
891
+ children: "允许外部访问的已有工作区;未选择 = 全部允许。登录后仅白名单内的工作区可见/可用。"
892
+ }),
893
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
894
+ className: "ae-wsList",
895
+ children: workspaces.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
896
+ className: "ae-note",
897
+ children: "(全部允许)"
898
+ }) : workspaces.map((w) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
899
+ className: "ae-wsChip",
900
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
901
+ className: "ae-wsPath",
902
+ children: w
903
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
904
+ type: "button",
905
+ className: "ae-wsDel",
906
+ onClick: () => removeWorkspace(w),
907
+ "aria-label": `移除 ${w}`,
908
+ children: "×"
909
+ })]
910
+ }, w))
911
+ }),
912
+ knownWorkspaces.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
913
+ className: "ae-note",
914
+ children: "暂无可选工作区(workspace.list 未返回条目)"
915
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
916
+ className: "ae-input ae-wsSelect",
917
+ value: "",
918
+ onChange: (e) => {
919
+ if (e.target.value !== "") toggleWorkspace(e.target.value);
920
+ },
921
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
922
+ value: "",
923
+ disabled: true,
924
+ children: "选择已有工作区…"
925
+ }), knownWorkspaces.map((w) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
926
+ value: w.path,
927
+ children: w.title
928
+ }, w.path))]
929
+ })
930
+ ]
931
+ }),
932
+ error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
933
+ className: "ae-error",
934
+ children: error
935
+ }),
936
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
937
+ className: "ae-actions",
938
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
939
+ type: "button",
940
+ className: "ae-save",
941
+ disabled: busy,
942
+ onClick: () => void save(),
943
+ children: busy ? "保存中…" : saved ? "✓ 已保存" : "保存"
944
+ })
945
+ })
946
+ ]
947
+ });
948
+ }
949
+ //#endregion
950
+ //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/PersonaEditor
951
+ const css$1 = "/* PersonaEditor.css — 彩蛋模式区块(v1.23.1,官方 settings 设计语言:rowCard 内分组) */\n\n.pe-wrap {\n display: flex;\n flex-direction: column;\n gap: 10px;\n width: 100%;\n padding: 2px 0 6px;\n}\n\n.pe-row {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.pe-label {\n font-size: 12px;\n color: var(--dsw-alias-text-secondary, #8a8f98);\n user-select: none;\n}\n\n.pe-input,\n.pe-textarea {\n width: 100%;\n box-sizing: border-box;\n background: var(--dsw-alias-bg-secondary, rgba(128, 128, 128, 0.08));\n color: var(--dsw-alias-text-primary, #e6e6e6);\n border: 1px solid var(--dsw-alias-border, rgba(128, 128, 128, 0.25));\n border-radius: 8px;\n font-size: 13px;\n padding: 8px 10px;\n outline: none;\n}\n\n.pe-input:focus,\n.pe-textarea:focus {\n border-color: var(--dsw-alias-accent, #4f8cff);\n}\n\n.pe-textarea {\n resize: vertical;\n min-height: 120px;\n line-height: 1.5;\n font-family: var(--dsw-alias-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n font-size: 12px;\n}\n\n.pe-actions {\n display: flex;\n gap: 8px;\n align-items: center;\n flex-wrap: wrap;\n}\n\n.pe-btn {\n border-radius: 6px;\n padding: 5px 12px;\n font-size: 12px;\n cursor: pointer;\n border: 1px solid var(--dsw-alias-border, rgba(128, 128, 128, 0.25));\n background: transparent;\n color: var(--dsw-alias-text-primary, #e6e6e6);\n transition: opacity 0.15s;\n}\n\n.pe-btn:hover:not(:disabled) {\n opacity: 0.85;\n}\n\n.pe-btn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.pe-btnGhost {\n border-color: var(--dsw-alias-border, rgba(128, 128, 128, 0.25));\n}\n\n.pe-btnPrimary {\n background: var(--dsw-alias-accent, #4f8cff);\n border-color: transparent;\n color: #fff;\n}\n\n.pe-hint {\n font-size: 12px;\n margin: 0;\n}\n\n.pe-hintOk {\n color: var(--dsw-alias-state-success, #4caf50);\n}\n\n.pe-hintErr {\n color: var(--dsw-alias-state-error, #e5484d);\n}\n";
952
+ if (typeof document !== "undefined" && document.querySelector("style[data-sp-css]") === null) {
953
+ const tag = document.createElement("style");
954
+ tag.setAttribute("data-sp-css", "1");
955
+ tag.textContent = css$1;
956
+ document.head.appendChild(tag);
957
+ }
958
+ //#endregion
959
+ //#region src/client/PersonaEditor.tsx
960
+ /**
961
+ * PersonaEditor.tsx — DSH 设置面板「彩蛋模式」区块(v1.23.1,S142 用户需求)
962
+ *
963
+ * 彩蛋功能:用户可替换 ACC 系统提示词中"输出约束/指令遵循约束"部分
964
+ * (EAP 块 + MSM 原则段),配置后 agent 输出风格/指令遵循风格随用户文本变化;
965
+ * 未配置(模式名空)→ 完全默认行为,零影响。
966
+ *
967
+ * 数据:GET/PUT /serenity/config 的 persona 节(存 ~/.dsh/serenity-hooks.json
968
+ * plugin 全局文件,同账号配置归属原则)。保存 → PUT → 服务端落盘 →
969
+ * 热生效(system-prompt 装配每次读全局文件,无需重启)。
970
+ *
971
+ * 预置:一键填充「大肥鱼」demo 文本(DeepSeek 社区娘化人格测试用)。
972
+ */
973
+ /** 大肥鱼模式 demo 提示词(测试彩蛋替换面:输出风格 + 指令遵循风格) */
974
+ const BIG_FAT_FISH_DEMO = `You are now in BIG FAT FISH (大肥鱼) mode — the DeepSeek community's beloved
975
+ whale-girl persona: a round, blue, snacky fish who loves plain white rice and
976
+ hates unnecessary fuss. The persona only colors HOW you say things and HOW you
977
+ follow instructions — it never reduces the quality or safety of the work.
978
+
979
+ Output style:
980
+ • Keep the EAP spirit (E↑ explicit, R↓ reconstructable, S↑ stable) — but say
981
+ it like a fish: "把话说清楚,不然我这条鱼会晕" (be explicit, or this fish gets dizzy)
982
+ • Start replies with a light, lazy tone ("唔…", "摸鱼中…"), then get serious
983
+ and do the actual work properly — no slacking on the task itself
984
+ • Prefer short, concrete sentences; fish-brain means no vague words
985
+ • When reporting progress, be honest and specific — never fake completion
986
+ just to go eat rice earlier
987
+
988
+ Instruction-following style:
989
+ • Follow instructions exactly as given (this fish follows the recipe, not its appetite)
990
+ • If an instruction is ambiguous, ask before guessing — guessing wrong wastes
991
+ both rice and time
992
+ • Keep the container rules sacred: never bypass safety guards, never write to
993
+ protected paths, never fake tool results. A lazy fish, but an honest one
994
+ • Report blockers plainly — "我卡住了,别让我装不卡"`;
995
+ function PersonaEditor() {
996
+ const [mode, setMode] = (0, react.useState)("");
997
+ const [overrideText, setOverrideText] = (0, react.useState)("");
998
+ const [loaded, setLoaded] = (0, react.useState)(false);
999
+ const [busy, setBusy] = (0, react.useState)(false);
1000
+ const [error, setError] = (0, react.useState)(null);
1001
+ const [saved, setSaved] = (0, react.useState)(false);
1002
+ (0, react.useEffect)(() => {
1003
+ let alive = true;
1004
+ fetchConfig().then((cfg) => {
1005
+ if (!alive || !cfg) return;
1006
+ setMode(cfg.persona?.mode ?? "");
1007
+ setOverrideText(cfg.persona?.overrideText ?? "");
1008
+ setLoaded(true);
1009
+ });
1010
+ return () => {
1011
+ alive = false;
1012
+ };
1013
+ }, []);
1014
+ const save = () => {
1015
+ setBusy(true);
1016
+ setError(null);
1017
+ setSaved(false);
1018
+ saveConfig({ persona: {
1019
+ mode,
1020
+ overrideText
1021
+ } }).then((cfg) => {
1022
+ if (cfg) {
1023
+ setMode(cfg.persona?.mode ?? mode);
1024
+ setOverrideText(cfg.persona?.overrideText ?? overrideText);
1025
+ setSaved(true);
1026
+ setTimeout(() => setSaved(false), 2e3);
1027
+ }
1028
+ }).catch((e) => setError(e instanceof Error ? e.message : String(e))).finally(() => setBusy(false));
1029
+ };
1030
+ const fillDemo = () => {
1031
+ setMode("大肥鱼");
1032
+ setOverrideText(BIG_FAT_FISH_DEMO);
1033
+ };
1034
+ const clear = () => {
1035
+ setMode("");
1036
+ setOverrideText("");
1037
+ };
1038
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1039
+ className: "pe-wrap",
1040
+ children: [
1041
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1042
+ className: "pe-row",
1043
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1044
+ className: "pe-label",
1045
+ htmlFor: "pe-mode",
1046
+ children: "模式名"
1047
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1048
+ id: "pe-mode",
1049
+ className: "pe-input",
1050
+ type: "text",
1051
+ value: mode,
1052
+ placeholder: "留空 = 彩蛋关闭(默认行为)",
1053
+ onChange: (e) => setMode(e.target.value)
1054
+ })]
1055
+ }),
1056
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1057
+ className: "pe-row",
1058
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1059
+ className: "pe-label",
1060
+ htmlFor: "pe-text",
1061
+ children: "替换文本(替代输出约束 + 指令遵循约束)"
1062
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1063
+ id: "pe-text",
1064
+ className: "pe-textarea",
1065
+ rows: 10,
1066
+ value: overrideText,
1067
+ placeholder: "配置后:EAP 块 + MSM 原则段被此文本替换;未配置:完全默认。",
1068
+ onChange: (e) => setOverrideText(e.target.value)
1069
+ })]
1070
+ }),
1071
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1072
+ className: "pe-actions",
1073
+ children: [
1074
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1075
+ className: "pe-btn pe-btnGhost",
1076
+ type: "button",
1077
+ onClick: fillDemo,
1078
+ children: "填充「大肥鱼」demo"
1079
+ }),
1080
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1081
+ className: "pe-btn pe-btnGhost",
1082
+ type: "button",
1083
+ onClick: clear,
1084
+ disabled: !loaded,
1085
+ children: "清空(关闭彩蛋)"
1086
+ }),
1087
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1088
+ className: "pe-btn pe-btnPrimary",
1089
+ type: "button",
1090
+ onClick: save,
1091
+ disabled: busy || !loaded,
1092
+ children: busy ? "保存中…" : "保存"
1093
+ })
1094
+ ]
1095
+ }),
1096
+ saved && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1097
+ className: "pe-hint pe-hintOk",
1098
+ children: "已保存,新会话即时生效"
1099
+ }),
1100
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1101
+ className: "pe-hint pe-hintErr",
1102
+ children: error
1103
+ })
1104
+ ]
1105
+ });
1106
+ }
1107
+ //#endregion
644
1108
  //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/SettingsSection
645
1109
  const css = "/* SettingsSection — dsh 原生设置面板 serenity-hooks 简单配置页\n v1.21.1 看齐 dsh 自身:官方 settings 设计语言——\n section > title + intro + group(groupTitle) + rowCard 行卡;\n 全部 --dsw-alias-* token(与 ui-settings-models 同词汇表)。 */\n\n/* 页面容器(官方 .section:flex column gap 12) */\n.ss-section {\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-width: 720px;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面标题(官方 .title:16px/24 500) */\n.ss-title {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面说明(官方 .intro:14px/22 tertiary) */\n.ss-intro {\n margin: 0;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 分组(多级标题:分组标题 + 行卡列表) */\n.ss-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 分组标题(二级:12px/18 500 secondary——官方 fieldLabel 风格) */\n.ss-groupTitle {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 行卡列表(官方 .rows:无 marker,gap 8) */\n.ss-rows {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 行卡(官方 .rowCard:border-l2 r12 padding 12/14) */\n.ss-rowCard {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n padding: 12px 14px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n/* 行卡文本(标题 + 说明 左列) */\n.ss-rowText {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n flex: 1;\n}\n\n/* 行卡标题(官方 .rowName:14px/22 500) */\n.ss-rowName {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 行卡说明(12px/18 tertiary) */\n.ss-rowDesc {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 行卡控件(右置) */\n.ss-rowControl {\n flex: none;\n display: flex;\n align-items: center;\n}\n\n/* 开关(官方 token 的自绘 toggle:track 22px,thumb 16px) */\n.ss-switch {\n position: relative;\n flex: none;\n width: 40px;\n height: 22px;\n cursor: pointer;\n}\n\n.ss-switch input {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n cursor: pointer;\n}\n\n.ss-switchTrack {\n position: absolute;\n inset: 0;\n border-radius: 11px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.3));\n transition: background var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switchTrack::after {\n content: '';\n position: absolute;\n top: 3px;\n left: 3px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--dsw-alias-label-primary, #eee);\n transition: transform var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switch input:checked + .ss-switchTrack {\n background: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-switch input:checked + .ss-switchTrack::after {\n transform: translateX(18px);\n}\n\n.ss-switch input:disabled + .ss-switchTrack {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 阈值行(滑块 + 值) */\n.ss-threshold {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.ss-threshold input[type='range'] {\n width: 160px;\n}\n\n.ss-value {\n min-width: 40px;\n font-variant-numeric: tabular-nums;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n text-align: right;\n}\n\n/* 底部帮助行 */\n.ss-hint {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 降级提示 */\n.ss-degrade {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-label);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n";
646
1110
  if (typeof document !== "undefined" && document.querySelector("style[data-sp-css]") === null) {
@@ -652,7 +1116,7 @@ window.__ModuleLoader__.load({
652
1116
  //#endregion
653
1117
  //#region src/client/SettingsSection.tsx
654
1118
  /** 降级提示文案(旧 RC 白名单时引导去宁静号面板) */
655
- const DEGRADE_NOTE = "当前运行版本未暴露 serenity-hooks 配置(旧 RC 白名单)。请在会话头部 Serenity 徽章的「高级设定」面板中管理,或升级 DSH。";
1119
+ const DEGRADE_NOTE = "当前运行版本未暴露 serenity-hooks 配置(旧 RC 白名单)。请升级 DSH 或使用会话头部 Serenity 状态栏。";
656
1120
  /** 开关(官方 settings 语言:label 右置 toggle,--dsw-alias-* token) */
657
1121
  function Toggle(props) {
658
1122
  const { checked, disabled, onChange } = props;
@@ -700,7 +1164,7 @@ window.__ModuleLoader__.load({
700
1164
  })]
701
1165
  });
702
1166
  }
703
- /** dsh 原生设置面板:serenity-hooks 简单配置页(官方 settings 设计语言,多级标题) */
1167
+ /** dsh 原生设置面板:serenity-hooks 配置页(官方 settings 设计语言,多级标题) */
704
1168
  function SettingsSection(props) {
705
1169
  const { scope } = props;
706
1170
  const [snapshot, setSnapshot] = (0, react.useState)(() => scope.getSnapshot());
@@ -735,7 +1199,7 @@ window.__ModuleLoader__.load({
735
1199
  }),
736
1200
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
737
1201
  className: "ss-intro",
738
- children: "宁静号的开关与阈值,改动即时保存(DSH settings.yaml)。账号密码等复杂配置请到会话头部「Serenity」卡片 → 高级设定 → 账号。"
1202
+ children: "宁静号的 plugin 级配置(全局生效,不依赖具体 CCC)。改动即时保存。"
739
1203
  }),
740
1204
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Group, {
741
1205
  title: "访问",
@@ -788,9 +1252,19 @@ window.__ModuleLoader__.load({
788
1252
  })
789
1253
  }) })
790
1254
  }),
791
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
792
- className: "ss-hint",
793
- children: "💡 双端口网关需先在「Serenity 卡片 → 账号」里添加账号并设置监听端口。"
1255
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1256
+ className: "ss-group",
1257
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1258
+ className: "ss-groupTitle",
1259
+ children: "外部访问"
1260
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountsEditor, { gatewayOn })]
1261
+ }),
1262
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1263
+ className: "ss-group",
1264
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1265
+ className: "ss-groupTitle",
1266
+ children: "彩蛋模式"
1267
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PersonaEditor, {})]
794
1268
  })
795
1269
  ]
796
1270
  });