@shgroup/dsh-serenity-hooks 1.20.6 → 1.21.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,12 +7,266 @@ 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
10
264
  //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/SafeModePanel
11
- const css = "/* SafeModePanel — 会话头部 Serenity 状态徽章(点击展开详情卡)。\n * 遵循 web-styling.md:颜色只用 --dsw-alias-* 语义 token(明暗自适应);\n * 尺寸与官方 header 标签一致;前缀 sp- 防冲突。 */\n\n.sp-root {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 24px;\n padding: 0 4px 0 4px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-module-platform);\n font-size: 12px;\n line-height: 24px;\n white-space: nowrap;\n}\n\n.sp-badge {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 22px;\n padding: 0 6px;\n border: none;\n border-radius: 5px;\n background: transparent;\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}\n\n.sp-badge:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.sp-badge:focus-visible {\n outline: 2px solid var(--dsw-alias-button-ghost-active-border);\n outline-offset: 1px;\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-toggle {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 18px;\n padding: 0 8px;\n border: none;\n border-radius: 5px;\n font-size: 12px;\n font-weight: 600;\n line-height: 18px;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-toggle:focus-visible {\n outline: 2px solid var(--dsw-alias-button-ghost-active-border);\n outline-offset: 1px;\n}\n\n.sp-toggle:disabled {\n opacity: 0.55;\n cursor: default;\n}\n\n.sp-toggleOn {\n background: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.sp-toggleOn:hover:not(:disabled) {\n background: var(--dsw-alias-state-error-secondary);\n}\n\n.sp-toggleOff {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-toggleOff:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover-solid);\n}\n\n.sp-toggleIcon {\n flex: none;\n}\n\n.sp-toggleLg {\n height: 26px;\n padding: 0 14px;\n font-size: 13px;\n line-height: 26px;\n border-radius: 6px;\n}\n\n/* 详情卡:相对徽章绝对定位(右对齐),卡片 token */\n.sp-pop {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n z-index: 20;\n width: 300px;\n box-sizing: border-box;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n background: var(--dsw-specific-tip);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.sp-popRow {\n display: flex;\n align-items: baseline;\n gap: 10px;\n min-width: 0;\n}\n\n.sp-popLabel {\n flex: none;\n width: 34px;\n font-size: 12px;\n color: var(--dsw-alias-label-caption);\n}\n\n.sp-popValue {\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}\n\n.sp-popActions {\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/* loop 运行状态区块(WebUI 等待界面;并行任务各自一行) */\n.sp-popSection {\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 44px;\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: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.sp-loopRound {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-state-info-primary);\n font-family: var(--ds-font-family-code, monospace);\n}\n\n.sp-loopTime {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-caption);\n}\n\n.sp-loopResp {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n font-family: var(--ds-font-family-code, monospace);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sp-toggle,\n .sp-badge {\n transition: none;\n }\n}\n";
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";
12
266
  if (typeof document !== "undefined" && document.querySelector("style[data-sp-css]") === null) {
13
267
  const tag = document.createElement("style");
14
268
  tag.setAttribute("data-sp-css", "1");
15
- tag.textContent = css;
269
+ tag.textContent = css$1;
16
270
  document.head.appendChild(tag);
17
271
  }
18
272
  //#endregion
@@ -23,13 +277,14 @@ window.__ModuleLoader__.load({
23
277
  function cx(...parts) {
24
278
  return parts.filter(Boolean).join(" ");
25
279
  }
26
- /** 会话头部:可点击徽章 + safe-mode 开关;点击徽章展开状态详情卡 */
280
+ /** 会话头部:Serenity 状态卡片(点击 → 官方 Modal 居中,双 tab) */
27
281
  function SafeModePanel(props) {
28
282
  const [status, setStatus] = (0, react.useState)(null);
29
283
  const [busy, setBusy] = (0, react.useState)(false);
30
284
  const [open, setOpen] = (0, react.useState)(false);
285
+ const [tab, setTab] = (0, react.useState)("status");
31
286
  const [loops, setLoops] = (0, react.useState)([]);
32
- const rootRef = (0, react.useRef)(null);
287
+ const [loopsOpen, setLoopsOpen] = (0, react.useState)(false);
33
288
  const sessionId = props.sessionId;
34
289
  const refresh = (0, react.useCallback)(async () => {
35
290
  const qs = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : "";
@@ -62,14 +317,6 @@ window.__ModuleLoader__.load({
62
317
  clearInterval(timer);
63
318
  };
64
319
  }, [open, sessionId]);
65
- (0, react.useEffect)(() => {
66
- if (!open) return;
67
- const onDown = (e) => {
68
- if (rootRef.current !== null && !rootRef.current.contains(e.target)) setOpen(false);
69
- };
70
- document.addEventListener("pointerdown", onDown);
71
- return () => document.removeEventListener("pointerdown", onDown);
72
- }, [open]);
73
320
  const toggle = async (on) => {
74
321
  setBusy(true);
75
322
  try {
@@ -89,117 +336,141 @@ window.__ModuleLoader__.load({
89
336
  setBusy(false);
90
337
  }
91
338
  };
92
- if (!status) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
93
- className: cx("sp-root"),
94
- children: "⏳ Serenity…"
339
+ if (!status) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
340
+ type: "button",
341
+ className: cx("sp-card"),
342
+ disabled: true,
343
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cx("sp-dot", "sp-dotOff") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
344
+ className: cx("sp-brand"),
345
+ children: "Serenity…"
346
+ })]
95
347
  });
96
348
  const inCcc = status.root !== null;
97
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
98
- ref: rootRef,
99
- className: cx("sp-root"),
100
- title: status.root ?? void 0,
349
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
350
+ type: "button",
351
+ className: cx("sp-card"),
352
+ "aria-haspopup": "dialog",
353
+ "aria-expanded": open,
354
+ onClick: () => setOpen(true),
355
+ title: inCcc ? `Serenity v${status.accVersion} — ${status.root}` : "Serenity(未激活)",
101
356
  children: [
102
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
103
- type: "button",
104
- className: cx("sp-badge"),
105
- "aria-expanded": open,
106
- onClick: () => setOpen((v) => !v),
107
- title: open ? "收起详情" : "查看 Serenity 状态",
108
- children: [
109
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cx("sp-dot", inCcc ? "sp-dotOn" : "sp-dotOff") }),
110
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
111
- className: cx("sp-brand"),
112
- children: ["Serenity v", status.accVersion]
113
- }),
114
- open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 12 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 12 })
115
- ]
357
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cx("sp-dot", inCcc ? "sp-dotOn" : "sp-dotOff") }),
358
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
359
+ className: cx("sp-brand"),
360
+ children: ["Serenity v", status.accVersion]
116
361
  }),
117
- inCcc && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
118
- type: "button",
119
- className: cx("sp-toggle", status.safeModeOn ? "sp-toggleOn" : "sp-toggleOff"),
120
- disabled: busy,
121
- onClick: () => void toggle(!status.safeModeOn),
122
- title: status.safeModeOn ? "关闭 safe-mode(解除 bash 限制)" : "开启 safe-mode(隐藏写工具)",
123
- children: [
124
- status.safeModeOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
125
- size: 12,
126
- className: cx("sp-toggleIcon")
127
- }),
128
- "safe-mode ",
129
- status.safeModeOn ? "ON" : "OFF"
130
- ]
362
+ inCcc && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
363
+ className: cx("sp-sm", status.safeModeOn ? "sp-smOn" : "sp-smOff"),
364
+ title: status.safeModeOn ? "safe-mode 已开启(隐藏写工具)" : "safe-mode 关闭",
365
+ children: [status.safeModeOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
366
+ size: 11,
367
+ className: cx("sp-smIcon")
368
+ }), status.safeModeOn ? "safe" : "safe-off"]
131
369
  }),
132
- open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
133
- className: cx("sp-pop"),
134
- role: "dialog",
135
- "aria-label": "Serenity 状态详情",
136
- children: inCcc ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
137
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
138
- className: cx("sp-popRow"),
139
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
140
- className: cx("sp-popLabel"),
141
- children: "CCC"
142
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
143
- className: cx("sp-popValue"),
144
- title: status.root ?? void 0,
145
- children: status.root
146
- })]
147
- }),
148
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
149
- className: cx("sp-popRow"),
150
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
151
- className: cx("sp-popLabel"),
152
- children: "loop"
153
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
154
- className: cx("sp-popValue"),
155
- title: status.loopModel ? `loop.defaultModel: ${status.loopModel}` : void 0,
156
- children: status.loopModel ?? "未配置"
157
- })]
158
- }),
159
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
160
- className: cx("sp-popRow"),
161
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
162
- className: cx("sp-popLabel"),
163
- children: "守卫"
164
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
165
- className: cx("sp-popValue"),
166
- children: [
167
- "blacklist ",
168
- status.blacklist.length,
169
- " 条",
170
- status.threshold !== null ? ` · keeper 阈值 ${status.threshold}` : ""
171
- ]
172
- })]
173
- }),
174
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
175
- className: cx("sp-popActions"),
176
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
177
- type: "button",
178
- className: cx("sp-toggle", status.safeModeOn ? "sp-toggleOn" : "sp-toggleOff", "sp-toggleLg"),
179
- disabled: busy,
180
- onClick: () => void toggle(!status.safeModeOn),
181
- children: [
182
- status.safeModeOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
183
- size: 14,
184
- className: cx("sp-toggleIcon")
185
- }),
186
- "safe-mode ",
187
- status.safeModeOn ? "ON" : "OFF"
188
- ]
189
- })
190
- }),
191
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
192
- className: cx("sp-popSection"),
193
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
194
- className: cx("sp-popRow"),
195
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
196
- className: cx("sp-popLabel"),
370
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {
371
+ size: 12,
372
+ className: cx("sp-chev")
373
+ })
374
+ ]
375
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
376
+ open,
377
+ onClose: () => setOpen(false),
378
+ title: "Serenity 高级设定",
379
+ closeLabel: "关闭",
380
+ 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", {
395
+ 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
+ }),
408
+ /* @__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"),
426
+ children: [
427
+ "blacklist ",
428
+ status.blacklist.length,
429
+ " 条",
430
+ status.threshold !== null ? ` · keeper 阈值 ${status.threshold}` : ""
431
+ ]
432
+ })]
433
+ }),
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
+ })
450
+ }),
451
+ /* @__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"),
197
458
  children: "loops"
198
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
199
- className: cx("sp-popValue"),
459
+ }),
460
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
461
+ className: cx("sp-value"),
200
462
  children: loops.filter((l) => !l.done).length > 0 ? `运行中 ${loops.filter((l) => !l.done).length}${loops.length > 0 ? ` / 共 ${loops.length}` : ""}` : "无运行中 loop"
201
- })]
202
- }), loops.slice(0, 5).map((l) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
463
+ }),
464
+ loops.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
465
+ type: "button",
466
+ className: cx("sp-loopsToggle"),
467
+ onClick: () => setLoopsOpen((v) => !v),
468
+ children: loopsOpen ? "收起" : "详情"
469
+ })
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", {
203
474
  className: cx("sp-loopItem"),
204
475
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
205
476
  className: cx("sp-loopHead"),
@@ -223,21 +494,28 @@ window.__ModuleLoader__.load({
223
494
  title: l.lastResponse,
224
495
  children: l.lastResponse.slice(0, 80) || (l.done ? "已完成" : "(等待首轮响应)")
225
496
  })]
226
- }, l.label))]
227
- })
228
- ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
229
- className: cx("sp-popRow"),
230
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
231
- className: cx("sp-popLabel"),
232
- children: "状态"
233
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
234
- className: cx("sp-popValue"),
235
- children: "ACC 未激活(工作区不在 CCC 内)"
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
+ })]
236
505
  })]
237
506
  })
507
+ ] }) : /* @__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"),
511
+ children: "状态"
512
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
513
+ className: cx("sp-value"),
514
+ children: "ACC 未激活(工作区不在 CCC 内)"
515
+ })]
238
516
  })
239
- ]
240
- });
517
+ })]
518
+ })] });
241
519
  }
242
520
  //#endregion
243
521
  //#region src/client/image-fallback-api.ts
@@ -363,17 +641,179 @@ window.__ModuleLoader__.load({
363
641
  return null;
364
642
  }
365
643
  //#endregion
644
+ //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/SettingsSection
645
+ 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
+ if (typeof document !== "undefined" && document.querySelector("style[data-sp-css]") === null) {
647
+ const tag = document.createElement("style");
648
+ tag.setAttribute("data-sp-css", "1");
649
+ tag.textContent = css;
650
+ document.head.appendChild(tag);
651
+ }
652
+ //#endregion
653
+ //#region src/client/SettingsSection.tsx
654
+ /** 降级提示文案(旧 RC 白名单时引导去宁静号面板) */
655
+ const DEGRADE_NOTE = "当前运行版本未暴露 serenity-hooks 配置(旧 RC 白名单)。请在会话头部 Serenity 徽章的「高级设定」面板中管理,或升级 DSH。";
656
+ /** 开关(官方 settings 语言:label 右置 toggle,--dsw-alias-* token) */
657
+ function Toggle(props) {
658
+ const { checked, disabled, onChange } = props;
659
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
660
+ className: "ss-switch",
661
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
662
+ type: "checkbox",
663
+ checked,
664
+ disabled,
665
+ onChange: (e) => onChange(e.target.checked)
666
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "ss-switchTrack" })]
667
+ });
668
+ }
669
+ /** 一个配置行卡(官方 rowCard:标题+说明 左列 / 控件右置) */
670
+ function RowCard(props) {
671
+ const { title, desc, control } = props;
672
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
673
+ className: "ss-rowCard",
674
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
675
+ className: "ss-rowText",
676
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
677
+ className: "ss-rowName",
678
+ children: title
679
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
680
+ className: "ss-rowDesc",
681
+ children: desc
682
+ })]
683
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
684
+ className: "ss-rowControl",
685
+ children: control
686
+ })]
687
+ });
688
+ }
689
+ /** 分组(多级标题:分组标题 + 一组行卡) */
690
+ function Group(props) {
691
+ const { title, children } = props;
692
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
693
+ className: "ss-group",
694
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
695
+ className: "ss-groupTitle",
696
+ children: title
697
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
698
+ className: "ss-rows",
699
+ children
700
+ })]
701
+ });
702
+ }
703
+ /** dsh 原生设置面板:serenity-hooks 简单配置页(官方 settings 设计语言,多级标题) */
704
+ function SettingsSection(props) {
705
+ const { scope } = props;
706
+ const [snapshot, setSnapshot] = (0, react.useState)(() => scope.getSnapshot());
707
+ (0, react.useEffect)(() => {
708
+ return scope.subscribe(() => setSnapshot(scope.getSnapshot()));
709
+ }, [scope]);
710
+ const value = snapshot.value;
711
+ const ready = snapshot.status === "ready";
712
+ const toggle = (field, on) => {
713
+ scope.set(field, on);
714
+ };
715
+ const setThreshold = (v) => {
716
+ scope.set("rebuildThreshold", Math.min(1, Math.max(.01, v)));
717
+ };
718
+ if (!ready) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
719
+ className: "ss-section",
720
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
721
+ className: "ss-degrade",
722
+ children: DEGRADE_NOTE
723
+ })
724
+ });
725
+ const gatewayOn = value?.gatewayEnabled ?? false;
726
+ const rebuildOn = value?.rebuildEnabled ?? true;
727
+ const threshold = value?.rebuildThreshold ?? .9;
728
+ const namingOn = value?.namingEnabled ?? true;
729
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
730
+ className: "ss-section",
731
+ children: [
732
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
733
+ className: "ss-title",
734
+ children: "Serenity"
735
+ }),
736
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
737
+ className: "ss-intro",
738
+ children: "宁静号的开关与阈值,改动即时保存(DSH settings.yaml)。账号密码等复杂配置请到会话头部「Serenity」卡片 → 高级设定 → 账号。"
739
+ }),
740
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Group, {
741
+ title: "访问",
742
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
743
+ title: "双端口网关",
744
+ desc: "额外监听一个端口,登录后可从外部访问 Web UI",
745
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
746
+ checked: gatewayOn,
747
+ onChange: (on) => toggle("gatewayEnabled", on)
748
+ })
749
+ }) })
750
+ }),
751
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Group, {
752
+ title: "上下文",
753
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
754
+ title: "超限重建",
755
+ desc: "上下文接近上限时提示调用 session_rebuild 清空重建",
756
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
757
+ checked: rebuildOn,
758
+ onChange: (on) => toggle("rebuildEnabled", on)
759
+ })
760
+ }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
761
+ title: "重建阈值",
762
+ desc: "上下文占用达到该比例时提示(0.10 ~ 1.00)",
763
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
764
+ className: "ss-threshold",
765
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
766
+ type: "range",
767
+ min: .1,
768
+ max: 1,
769
+ step: .05,
770
+ value: threshold,
771
+ disabled: !rebuildOn,
772
+ onChange: (e) => setThreshold(Number(e.target.value))
773
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
774
+ className: "ss-value",
775
+ children: threshold.toFixed(2)
776
+ })]
777
+ })
778
+ }) })]
779
+ }),
780
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Group, {
781
+ title: "会话",
782
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
783
+ title: "会话命名",
784
+ desc: "使用宁静号会话时,把当前会话命名为 SESSION 目录名",
785
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
786
+ checked: namingOn,
787
+ onChange: (on) => toggle("namingEnabled", on)
788
+ })
789
+ }) })
790
+ }),
791
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
792
+ className: "ss-hint",
793
+ children: "💡 双端口网关需先在「Serenity 卡片 → 账号」里添加账号并设置监听端口。"
794
+ })
795
+ ]
796
+ });
797
+ }
798
+ //#endregion
366
799
  //#region src/client/index.ts
367
800
  const inject = [
368
801
  "slots",
369
802
  "conversation",
370
- "sessions"
803
+ "sessions",
804
+ "settingsScope"
371
805
  ];
806
+ /** serenity-hooks 简单配置 scope spec(与 host 侧 registerSettingsSection 对齐) */
807
+ const SERENITY_SCOPE_SPEC = {
808
+ namespace: "serenity-hooks",
809
+ decode: (section) => section !== null && typeof section === "object" ? section : void 0
810
+ };
372
811
  function apply(ctx) {
373
812
  ctx.inject([
374
813
  "slots",
375
814
  "conversation",
376
- "sessions"
815
+ "sessions",
816
+ "settingsScope"
377
817
  ], (scope) => {
378
818
  scope.effect(() => scope.slots.register({
379
819
  name: "conversation.session.header.actions",
@@ -390,6 +830,14 @@ window.__ModuleLoader__.load({
390
830
  resendText: (sessionId, text) => resendText(scope, sessionId, text)
391
831
  })
392
832
  }, ImageFallbackDock), "serenity: image fallback dock");
833
+ const serenityScope = scope.get("settingsScope").bind(SERENITY_SCOPE_SPEC);
834
+ scope.effect(() => scope.slots.register({
835
+ name: "settings.section",
836
+ id: "serenity-hooks",
837
+ order: 90,
838
+ label: () => "Serenity",
839
+ inject: () => ({ scope: serenityScope })
840
+ }, SettingsSection), "serenity: simple settings section");
393
841
  });
394
842
  }
395
843
  //#endregion