dsh-agent-persona 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,2251 @@
1
+ (() => {
2
+ // src/remote.ts
3
+ var PKG = "dsh-agent-persona";
4
+ var codec = (typeSymbol, parse) => ({
5
+ mode: "strict",
6
+ typeSymbol: `${PKG}#${typeSymbol}`,
7
+ schema: { parse }
8
+ });
9
+ var passthrough = (value) => value;
10
+ var asInput = (value) => {
11
+ if (value === void 0 || value === null) return {};
12
+ if (typeof value !== "object" || Array.isArray(value)) throw new Error("input must be an object");
13
+ return value;
14
+ };
15
+ var withInput = () => [
16
+ { name: "input", wire: "input", source: "json", codec: codec("Input", asInput) }
17
+ ];
18
+ var method = (name, parameters) => ({
19
+ id: `${PKG}#agentPersona/${name}`,
20
+ service: "agentPersona",
21
+ namespace: "agentPersona",
22
+ method: name,
23
+ invocation: { kind: "direct" },
24
+ parameters,
25
+ result: codec("View", passthrough)
26
+ });
27
+ var RPC_REMOTE = {
28
+ package: PKG,
29
+ descriptors: [
30
+ method("listPersonas", []),
31
+ method("savePersona", withInput()),
32
+ method("deletePersona", withInput()),
33
+ method("movePersona", withInput()),
34
+ method("duplicatePersona", withInput()),
35
+ method("reorderPersona", withInput()),
36
+ method("listTargets", []),
37
+ method("sessionHistory", withInput()),
38
+ method("sessionPrompt", withInput()),
39
+ method("tunePersona", withInput()),
40
+ method("listModels", []),
41
+ method("savePrefs", withInput())
42
+ ]
43
+ };
44
+
45
+ // src/client.ts
46
+ window.__ModuleLoader__.load({
47
+ id: "dsh-agent-persona",
48
+ factory: (require2) => {
49
+ var module = { exports: {} };
50
+ var exports = module.exports;
51
+ const React = require2("react");
52
+ const primitives = require2("@deepseek-ai/dsh-client-ui-primitives");
53
+ const h = React.createElement;
54
+ const {
55
+ Button,
56
+ DisclosureRow,
57
+ Input,
58
+ MarkdownText,
59
+ Menu,
60
+ Modal,
61
+ Toast,
62
+ Pill,
63
+ Tooltip,
64
+ IconCheckOutline14,
65
+ IconChevronDownOutline14,
66
+ IconChevronRightOutline14,
67
+ IconAgentPresetOutline16,
68
+ IconBrowseOutline16,
69
+ IconChevronUpOutline14,
70
+ IconCloseOutline16,
71
+ IconEditOutline16,
72
+ IconEllipsisOutline16,
73
+ IconEnhanceOutline16,
74
+ IconFolderOpen16,
75
+ IconListPenOutline16,
76
+ IconPersonalizationOutline16,
77
+ IconRefreshOutline16,
78
+ IconUserOutline16,
79
+ IconWarningOutline16,
80
+ IconPlusOutline16,
81
+ IconTrashOutline16
82
+ } = primitives;
83
+ const NS = "agent-persona";
84
+ const MOD_KEY = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? navigator.userAgent ?? "") ? "\u2318" : "Ctrl+";
85
+ const DESCRIPTION = "\u7ED9\u4E0D\u540C\u7684\u5DE5\u4F5C\u533A\u3001\u4E0D\u540C\u7684\u4F1A\u8BDD\u7528\u4E0D\u540C\u7684\u4EBA\u8BBE\u3002\u4EBA\u8BBE\u4F1A\u4F5C\u4E3A\u7CFB\u7EDF\u63D0\u793A\u8BCD\u6CE8\u5165\uFF0C\u4F18\u5148\u7EA7\u9AD8\u4E8E\u5DE5\u4F5C\u533A\u91CC\u7684 AGENTS.md\u3002";
86
+ const SCOPE = "dsh-agent-persona";
87
+ const KIND_LABEL = { workspace: "\u5DE5\u4F5C\u533A", sessionId: "\u4F1A\u8BDD" };
88
+ const MATCH_LABEL = { exact: "\u5B8C\u5168\u4E00\u81F4", prefix: "\u5F00\u5934\u662F", regex: "\u6B63\u5219\u5339\u914D", contains: "\u5305\u542B" };
89
+ const MODES = [
90
+ { value: "polish", label: "\u6DA6\u8272" },
91
+ { value: "expand", label: "\u8865\u5145\u7EC6\u8282" },
92
+ { value: "compress", label: "\u7CBE\u7B80" },
93
+ { value: "draft", label: "\u6309\u63CF\u8FF0\u751F\u6210" }
94
+ ];
95
+ const PREF_KEY = "dsh-agent-persona.prefs";
96
+ const DEFAULT_PREFS = { autosave: false, showTab: true, showSidebar: true };
97
+ let sidebarAvailable = false;
98
+ let sidebarProbe = () => void 0;
99
+ let syncSidebarModule = () => {
100
+ };
101
+ const openSidebarModule = (ctx) => {
102
+ try {
103
+ const better = ctx.get("betterSidebar");
104
+ if (better === void 0 || better === null || typeof better.openTab !== "function") return false;
105
+ const current = JSON.parse(window.localStorage?.getItem("dsh.sessions.current") ?? "{}");
106
+ if (typeof current?.sessionId !== "string") return false;
107
+ void better.openTab({ type: NS }, { sessionId: current.sessionId });
108
+ return true;
109
+ } catch {
110
+ return false;
111
+ }
112
+ };
113
+ const readPrefs = () => {
114
+ try {
115
+ return { ...DEFAULT_PREFS, ...JSON.parse(window.localStorage?.getItem(PREF_KEY) ?? "{}") };
116
+ } catch {
117
+ return { ...DEFAULT_PREFS };
118
+ }
119
+ };
120
+ const prefStatus = (next, patch) => "showTab" in patch || "showSidebar" in patch ? "\u5DF2\u4FDD\u5B58\uFF1A\u5DF2\u7ACB\u5373\u751F\u6548" : next.autosave === true ? "\u5DF2\u5F00\u542F\uFF1A\u7F16\u8F91\u540E\u505C\u6B62\u8F93\u5165\u7EA6 1 \u79D2\u5373\u81EA\u52A8\u4FDD\u5B58" : "\u5DF2\u5173\u95ED\uFF1A\u7F16\u8F91\u540E\u81EA\u52A8\u4FDD\u5B58";
121
+ const persistPrefs = (api, unwrap, current, patch, report) => {
122
+ const next = writePrefs({ ...current, ...patch });
123
+ report({ prefs: next, status: prefStatus(next, patch) });
124
+ syncSidebarModule();
125
+ if (api === void 0 || typeof api.savePrefs !== "function") return next;
126
+ void api.savePrefs(next).then((result) => {
127
+ const view = unwrap === void 0 ? result : unwrap(result);
128
+ const stored = view !== void 0 && view !== null && view.prefs !== void 0 && view.prefs !== null ? writePrefs(view.prefs) : void 0;
129
+ report(stored === void 0 ? { view } : { prefs: { ...next, ...stored }, view });
130
+ }).catch((error) => {
131
+ report({ status: `\u504F\u597D\u672A\u5199\u5165\u5B58\u50A8\uFF08\u53EA\u5728\u672C\u6D4F\u89C8\u5668\u751F\u6548\uFF09\uFF1A${String(error && error.message || error)}` });
132
+ });
133
+ return next;
134
+ };
135
+ const writePrefs = (patch) => {
136
+ const next = { ...readPrefs(), ...patch };
137
+ try {
138
+ window.localStorage?.setItem(PREF_KEY, JSON.stringify(next));
139
+ } catch {
140
+ }
141
+ return next;
142
+ };
143
+ const CSS = `
144
+ .wsp-root, .wsp-view, .wsp-chat-modal, [data-plugin="dsh-agent-persona"], [data-plugin="agent-persona"] {
145
+ --wsp-line: var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));
146
+ --wsp-line-soft: rgba(128, 128, 128, 0.14);
147
+ --wsp-line-strong: var(--dsw-alias-border-l3, rgba(128, 128, 128, 0.4));
148
+ --wsp-text: var(--dsw-alias-label-primary, inherit);
149
+ --wsp-muted: var(--dsw-alias-label-tertiary, rgba(128, 128, 128, 0.85));
150
+ --wsp-muted-2: var(--dsw-alias-label-caption, rgba(128, 128, 128, 0.62));
151
+ --wsp-accent: var(--dsw-alias-brand-primary, #58a6ff);
152
+ --wsp-mode: #7c5cff;
153
+ --wsp-mode-soft: #a78bfa;
154
+ --wsp-success: var(--dsw-alias-state-success-primary, #3fb950);
155
+ --wsp-danger: var(--dsw-alias-state-error-primary, #f85149);
156
+ --wsp-warn: var(--dsw-alias-state-warn-primary, #d29922);
157
+ --wsp-surface: var(--dsw-alias-bg-layer-1, rgba(128, 128, 128, 0.04));
158
+ --wsp-surface-2: var(--dsw-alias-bg-layer-2, rgba(128, 128, 128, 0.08));
159
+ --wsp-surface-3: var(--dsw-alias-bg-layer-3, rgba(128, 128, 128, 0.12));
160
+ --wsp-hover: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.1));
161
+ --wsp-active: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.16));
162
+ --wsp-card-shadow: var(--dsw-shadow-lv3, 0 2px 10px rgba(0, 0, 0, 0.10), 0 1px 2px rgba(0, 0, 0, 0.06));
163
+ --wsp-radius: 8px;
164
+ --wsp-radius-sm: 6px;
165
+ --wsp-mono: var(--dsw-font-markdown-code-font-family, ui-monospace, SFMono-Regular, Menlo, monospace);
166
+ display: flex; flex-direction: column; gap: 14px;
167
+ width: 100%; max-width: 860px; min-width: 0;
168
+ color: var(--wsp-text); -webkit-font-smoothing: antialiased;
169
+ }
170
+
171
+ .wsp-head { display: flex; flex-direction: column; gap: 6px; padding: 2px 0 2px; }
172
+ .wsp-title { font-size: 18px; font-weight: 600; letter-spacing: -0.012em; line-height: 1.3; margin: 0; }
173
+ .wsp-sub { font-size: 13px; line-height: 1.65; color: var(--wsp-muted); text-wrap: pretty; max-width: 72ch; margin: 0; }
174
+ .wsp-meta { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; font-size: 11.5px; color: var(--wsp-muted-2); margin-top: 2px; }
175
+ .wsp-meta code { font-family: var(--wsp-mono); font-size: 11px; }
176
+ .wsp-sep { opacity: 0.45; }
177
+ .wsp-spacer { flex: 1; min-width: 6px; }
178
+
179
+ .wsp-badge { font-size: 10.5px; line-height: 1.6; padding: 0 7px; border-radius: 999px; border: 1px solid var(--wsp-line); color: var(--wsp-muted); white-space: nowrap; }
180
+ .wsp-badge-on { color: var(--wsp-success); border-color: color-mix(in srgb, var(--wsp-success) 40%, transparent); }
181
+ .wsp-badge-warn { color: var(--wsp-warn); border-color: color-mix(in srgb, var(--wsp-warn) 40%, transparent); }
182
+ .wsp-badge-off { opacity: 0.7; border-style: dashed; }
183
+ .wsp-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: rgba(128, 128, 128, 0.4); }
184
+ .wsp-dot-on { background: var(--wsp-success); }
185
+ .wsp-dot-empty { background: var(--wsp-warn); }
186
+ .wsp-idx { font-size: 10.5px; font-variant-numeric: tabular-nums; color: var(--wsp-muted-2); min-width: 14px; text-align: right; }
187
+
188
+ /* \u2500\u2500 cards: macOS-ish \u2014 soft radii, hairline border, a gentle lift \u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
189
+ .wsp-cards { display: flex; flex-direction: column; gap: 10px; }
190
+ .wsp-card {
191
+ border: 1px solid var(--wsp-line);
192
+ border-radius: 12px;
193
+ background: var(--wsp-surface);
194
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.03);
195
+ transition: box-shadow 150ms ease, border-color 150ms ease;
196
+ }
197
+ /* The whole collapsed card is the hover target: background and border follow the
198
+ card's own 12px corners, instead of a second rounded box drawn inside it. */
199
+ .wsp-card:not(.wsp-card-open):hover { border-color: var(--wsp-line-strong); background: var(--wsp-surface-2); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); }
200
+ .wsp-card-open { border-color: var(--wsp-line-strong); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.09), 0 1px 2px rgba(0, 0, 0, 0.04); }
201
+ .wsp-prefs { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; margin-top: 10px; }
202
+ .wsp-prefs-row { display: flex; flex-direction: column; gap: 4px; }
203
+ .wsp-prefs-row-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
204
+ .wsp-prefs-row-title { font-size: 13px; font-weight: 600; color: var(--wsp-text); }
205
+ .wsp-prefs-row-note { font-size: 11px; color: var(--wsp-muted-2); }
206
+ .wsp-prefs-row-status { font-size: 11.5px; color: var(--wsp-muted); }
207
+ .wsp-prefs-row .wsp-prefs { margin-top: 4px; }
208
+ .wsp-prefs .wsp-switch-text { font-size: 12.5px; }
209
+ .wsp-row-head { display: flex; align-items: center; gap: 8px; padding: 9px 12px 3px; min-height: 44px; box-sizing: border-box; flex-wrap: wrap; border-radius: 12px 12px 0 0; }
210
+ /* \u5C55\u5F00\u540E\u7684\u5361\u7247\uFF1A\u5934\u90E8\u5438\u9876\uFF0C\u4FDD\u5B58\u6309\u94AE\u5C31\u5728\u624B\u8FB9 */
211
+ .wsp-row-open { position: sticky; top: 0; z-index: 3; background: var(--wsp-surface); }
212
+ .wsp-row-switch { display: inline-flex; align-items: center; }
213
+ .wsp-row-switch .wsp-switch-text { display: none; }
214
+ .wsp-save-chip { height: 20px; padding: 0 9px; border: 1px solid color-mix(in srgb, var(--wsp-warn, #d29922) 45%, transparent); border-radius: 999px; background: color-mix(in srgb, var(--wsp-warn, #d29922) 14%, transparent); font: inherit; font-size: 11px; line-height: 1; color: var(--wsp-warn, #d29922); cursor: pointer; transition: background 150ms ease, border-color 150ms ease; }
215
+ .wsp-save-chip:hover { background: color-mix(in srgb, var(--wsp-warn, #d29922) 24%, transparent); border-color: color-mix(in srgb, var(--wsp-warn, #d29922) 65%, transparent); }
216
+ .wsp-save-chip:disabled { opacity: 0.6; cursor: default; }
217
+ .wsp-row-click { cursor: pointer; }
218
+ .wsp-row-click:focus-visible { outline: 2px solid var(--wsp-accent); outline-offset: -2px; }
219
+ .wsp-dragging { opacity: 0.55; }
220
+ .wsp-drop-before { box-shadow: 0 -2px 0 0 var(--wsp-accent); }
221
+ .wsp-drop-after { box-shadow: 0 2px 0 0 var(--wsp-accent); }
222
+ .wsp-chevron {
223
+ flex: none; width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center;
224
+ border: 0; border-radius: 6px; background: transparent; color: var(--wsp-muted); cursor: pointer;
225
+ transition: background 150ms ease, color 150ms ease;
226
+ }
227
+ .wsp-chevron:hover { background: var(--wsp-surface-2); color: var(--wsp-text); }
228
+ .wsp-chevron:active { cursor: grabbing; }
229
+ .wsp-chevron[draggable="true"] { cursor: grab; }
230
+ .wsp-chevron:focus-visible { outline: 2px solid var(--wsp-accent); outline-offset: 1px; }
231
+ .wsp-pname { font-size: 13px; font-weight: 600; letter-spacing: -0.005em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px; }
232
+ .wsp-chip { display: inline-flex; align-items: center; gap: 4px; height: 22px; padding: 0 9px; border: 1px solid var(--wsp-line); border-radius: 999px; font-size: 11.5px; line-height: 1; color: var(--wsp-muted); background: var(--wsp-surface-3); white-space: nowrap; font-variant-numeric: tabular-nums; }
233
+ .wsp-chip-on { color: var(--wsp-success); border-color: color-mix(in srgb, var(--wsp-success) 45%, transparent); background: color-mix(in srgb, var(--wsp-success) 14%, transparent); }
234
+ .wsp-chip-off { color: var(--wsp-muted-2); border-style: dashed; background: transparent; }
235
+ .wsp-chip-mode { color: var(--wsp-mode); border-color: color-mix(in srgb, var(--wsp-mode) 40%, transparent); background: color-mix(in srgb, var(--wsp-mode) 12%, transparent); }
236
+ .wsp-chip-mode-strong { color: var(--wsp-mode); border-color: color-mix(in srgb, var(--wsp-mode) 60%, transparent); background: color-mix(in srgb, var(--wsp-mode) 22%, transparent); font-weight: 500; }
237
+ .wsp-chip-warn { color: var(--wsp-warn, #d29922); border-color: color-mix(in srgb, var(--wsp-warn, #d29922) 45%, transparent); background: color-mix(in srgb, var(--wsp-warn, #d29922) 14%, transparent); }
238
+ .wsp-card-body { display: flex; flex-direction: column; gap: 12px; padding: 12px 14px 14px; border-top: 1px solid var(--wsp-line-soft); background: var(--wsp-surface); border-radius: 0 0 12px 12px; }
239
+ .wsp-search { display: flex; align-items: center; gap: 8px; }
240
+ .wsp-search > *:first-child { flex: 1; }
241
+ .wsp-conflict-notice { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--wsp-warn); padding: 6px 9px; border: 1px solid color-mix(in srgb, var(--wsp-warn) 35%, transparent); border-radius: 8px; background: color-mix(in srgb, var(--wsp-warn) 8%, transparent); }
242
+ .wsp-kbd { font: inherit; font-size: 10.5px; line-height: 1.6; padding: 0 5px; border: 1px solid var(--wsp-line-strong); border-bottom-width: 2px; border-radius: 5px; color: var(--wsp-muted-2); }
243
+ .wsp-add-row {
244
+ display: flex; align-items: center; justify-content: center; gap: 6px;
245
+ padding: 11px; border: 1px dashed var(--wsp-line-strong); border-radius: 12px;
246
+ color: var(--wsp-muted); font-size: 12.5px; cursor: pointer;
247
+ transition: border-color 150ms ease, background 150ms ease, color 150ms ease;
248
+ }
249
+ .wsp-add-row:hover { border-color: var(--wsp-accent); color: var(--wsp-text); background: var(--wsp-surface); }
250
+ .wsp-add-row:focus-visible { outline: 2px solid var(--wsp-accent); outline-offset: 2px; }
251
+ .wsp-empty-card { border: 1px dashed var(--wsp-line-strong); border-radius: 12px; padding: 16px; text-align: center; color: var(--wsp-muted); font-size: 12px; display: flex; flex-direction: column; gap: 10px; align-items: center; }
252
+
253
+ /* \u2500\u2500 fields \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
254
+ .wsp-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
255
+ .wsp-field-row { display: flex; align-items: flex-end; gap: 8px; min-width: 0; }
256
+ .wsp-label { font-size: 11px; color: var(--wsp-muted); white-space: nowrap; }
257
+ .wsp-hint { font-size: 11px; line-height: 1.55; color: var(--wsp-muted-2); }
258
+ .wsp-warn { color: var(--wsp-warn); }
259
+ .wsp-sect-title { font-size: 11.5px; font-weight: 550; color: var(--wsp-muted); display: flex; align-items: center; gap: 6px; }
260
+ .wsp-group { display: flex; flex-direction: column; gap: 7px; padding: 9px 10px; border: 1px solid var(--wsp-line-soft); border-radius: var(--wsp-radius-sm); }
261
+ .wsp-input { width: 100%; box-sizing: border-box; padding: 6px 9px; border-radius: var(--wsp-radius-sm); border: 1px solid var(--wsp-line); background: var(--dsw-alias-bg-base, transparent); color: inherit; font: inherit; font-size: 12px; }
262
+ .wsp-input:focus-visible { outline: 2px solid var(--wsp-accent); outline-offset: -1px; }
263
+ .wsp-input-mono { font-family: var(--wsp-mono); font-size: 11.5px; }
264
+ .wsp-input-invalid { border-color: var(--wsp-danger); }
265
+ .wsp-select { padding: 5px 7px; border-radius: var(--wsp-radius-sm); border: 1px solid var(--wsp-line); background: var(--dsw-alias-bg-base, transparent); color: inherit; font: inherit; font-size: 12px; cursor: pointer; }
266
+ .wsp-select:focus-visible { outline: 2px solid var(--wsp-accent); outline-offset: -1px; }
267
+ /* \u2500\u2500 scope rows: a segmented type switch plus a real picker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
268
+ .wsp-scope-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
269
+ .wsp-scope-row > span { flex: 1 1 190px; min-width: 0; }
270
+ .wsp-kind { display: inline-flex; flex: none; gap: 4px; }
271
+ .wsp-picker {
272
+ width: 100%; min-width: 0; box-sizing: border-box;
273
+ display: inline-flex; align-items: center; gap: 7px;
274
+ padding: 6px 9px; border: 1px solid var(--wsp-line); border-radius: 7px;
275
+ background: var(--dsw-alias-bg-layer-2, transparent); color: inherit;
276
+ font: inherit; font-size: 12px; text-align: left; cursor: pointer;
277
+ transition: border-color 150ms ease, background 150ms ease, box-shadow 150ms ease;
278
+ }
279
+ .wsp-picker:hover { border-color: var(--wsp-line-strong); background: var(--wsp-surface-2); }
280
+ .wsp-picker:focus-visible { outline: 2px solid var(--wsp-accent); outline-offset: -1px; }
281
+ .wsp-picker-empty { color: var(--wsp-muted-2); }
282
+ .wsp-picker-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
283
+ .wsp-scope-value { flex: 1 1 200px; }
284
+ .wsp-scope-match { flex: none; width: 96px; }
285
+ .wsp-scope-context { flex-basis: 100%; font-size: 10.5px; color: var(--wsp-muted-2); font-family: var(--wsp-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding-left: 2px; }
286
+ .wsp-icon-btn { border: 1px solid transparent; background: transparent; color: var(--wsp-muted); border-radius: 6px; cursor: pointer; padding: 4px 6px; min-width: 28px; min-height: 28px; justify-content: center; display: inline-flex; align-items: center; gap: 4px; font: inherit; font-size: 11.5px; line-height: 1.2; }
287
+ .wsp-icon-btn:hover { background: var(--wsp-surface-2); color: var(--wsp-text); }
288
+ .wsp-icon-btn:focus-visible { outline: 2px solid var(--wsp-accent); outline-offset: 1px; }
289
+ .wsp-icon-btn:disabled { opacity: 0.4; cursor: default; }
290
+ .wsp-icon-btn-danger:hover { color: var(--wsp-danger); background: var(--dsw-alias-interactive-bg-hover-danger, rgba(248, 81, 73, 0.1)); }
291
+
292
+ /* \u2500\u2500 switch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
293
+ .wsp-switch { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; user-select: none; }
294
+ .wsp-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
295
+ .wsp-switch-track { position: relative; width: 30px; height: 18px; border-radius: 999px; background: rgba(128, 128, 128, 0.3); transition: background 150ms ease; flex: none; }
296
+ .wsp-switch-track::after { content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25); transition: transform 150ms ease; }
297
+ .wsp-switch input:checked + .wsp-switch-track { background: var(--wsp-success); }
298
+ .wsp-switch input:checked + .wsp-switch-track::after { transform: translateX(12px); }
299
+ .wsp-switch input:focus-visible + .wsp-switch-track { outline: 2px solid var(--wsp-accent); outline-offset: 2px; }
300
+ .wsp-switch-text { font-size: 11.5px; color: var(--wsp-muted); }
301
+
302
+ /* \u2500\u2500 editor / preview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
303
+ .wsp-editor { border: 1px solid var(--wsp-line); border-radius: var(--wsp-radius-sm); background: var(--dsw-alias-bg-base, transparent); overflow: hidden; }
304
+ .wsp-textarea { display: block; width: 100%; box-sizing: border-box; min-height: 240px; max-height: 46vh; resize: vertical; padding: 11px 12px; border: 0; outline: none; background: transparent; color: inherit; font-family: var(--wsp-mono); font-size: 12.5px; line-height: 1.7; tab-size: 2; }
305
+ .wsp-textarea::placeholder { color: var(--wsp-muted-2); }
306
+ .wsp-textarea:focus-visible { box-shadow: inset 0 0 0 2px var(--wsp-accent); }
307
+ .wsp-preview { min-height: 240px; max-height: 46vh; overflow: auto; padding: 11px 13px; font-size: 13px; line-height: 1.7; }
308
+ .wsp-preview-empty { color: var(--wsp-muted-2); font-size: 12px; }
309
+
310
+ /* \u2500\u2500 footer / status / tuning \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
311
+ .wsp-foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; border-top: 1px solid var(--wsp-line-soft); padding-top: 10px; }
312
+ .wsp-caption { font-size: 11px; color: var(--wsp-muted-2); font-variant-numeric: tabular-nums; }
313
+ .wsp-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
314
+ .wsp-tune-fields { display: flex; flex-direction: column; gap: 9px; }
315
+ .wsp-proposal { border: 1px dashed var(--wsp-line-strong); border-radius: var(--wsp-radius-sm); padding: 10px 11px; display: flex; flex-direction: column; gap: 8px; }
316
+ .wsp-proposal-body { max-height: 32vh; overflow: auto; font-size: 12.5px; line-height: 1.7; border-top: 1px solid var(--wsp-line-soft); padding-top: 8px; }
317
+ /* Menus portal to <body>: global on purpose, and widened \u2014 the default card is
318
+ far too narrow for "N \u6B21\u5BF9\u8BDD \xB7 id \xB7 4 \u5C0F\u65F6\u524D" beside a title. */
319
+ .wsp-menu { min-width: 460px !important; max-width: min(600px, 92vw) !important; }
320
+ .wsp-opt-turns { color: var(--wsp-muted); }
321
+ .wsp-menu-tip { display: inline-block; max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }
322
+ .wsp-opt { display: flex; align-items: center; gap: 8px; width: 100%; min-width: 0; }
323
+ .wsp-opt-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
324
+ .wsp-opt-owner { flex: none; font-size: 10.5px; color: var(--wsp-warn); }
325
+ .wsp-opt-id { flex: none; font-family: var(--wsp-mono); font-size: 10.5px; color: var(--wsp-muted-2); padding-left: 10px; }
326
+ .wsp-skeleton { display: inline-block; border-radius: 6px; background: var(--wsp-surface-2); }
327
+ .wsp-skeleton-chevron { width: 22px; height: 22px; }
328
+ .wsp-skeleton-name { width: 120px; height: 13px; }
329
+ .wsp-skeleton-chip { width: 180px; height: 12px; margin-left: auto; }
330
+ @keyframes wsp-pulse { 0%, 100% { opacity: 1 } 50% { opacity: 0.55 } }
331
+ .wsp-skeleton { animation: wsp-pulse 1.4s ease-in-out infinite; }
332
+ .wsp-scope-lines { display: flex; flex-direction: column; gap: 4px; padding: 0 12px 10px 40px; }
333
+ .wsp-scope-line { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; min-width: 0; }
334
+ .wsp-scope-label { flex: none; font-size: 10.5px; color: var(--wsp-muted-2); }
335
+ /* The dialog portals to <body>, so these rules are global on purpose and carry
336
+ their own copies of the plugin variables (the scoped ones do not reach it). */
337
+ .wsp-chat-modal {
338
+ --wsp-line: var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));
339
+ --wsp-line-soft: rgba(128, 128, 128, 0.14);
340
+ --wsp-text: var(--dsw-alias-label-primary, inherit);
341
+ --wsp-muted-2: var(--dsw-alias-label-caption, rgba(128, 128, 128, 0.62));
342
+ --wsp-success: var(--dsw-alias-state-success-primary, #3fb950);
343
+ --wsp-surface-2: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.08));
344
+ --wsp-mono: var(--dsw-font-markdown-code-font-family, ui-monospace, Menlo, monospace);
345
+ width: min(980px, 94vw) !important;
346
+ max-width: min(980px, 94vw) !important;
347
+ }
348
+ .wsp-chat-content { max-width: none !important; width: 100% !important; }
349
+ /* Fill the seat the shell hands us, the way dsh-context does (flex-1 + min-w-0); the column
350
+ inside centres itself with auto margins. Widths are relative: no pixel caps. */
351
+ /* The seat is full width; the page keeps 80% of it and centres (narrow screens below use it all). */
352
+ .wsp-view { display: flex; flex-direction: column; overflow: auto; height: 100%; box-sizing: border-box; padding: 2.5% 2% 4%; flex: 0 1 auto; width: 90%; min-width: 0; max-width: none; margin-inline: auto; align-self: stretch; }
353
+ /* Inside the sidebar pane the column is narrow already: use all of it. */
354
+ .wsp-view[data-host="sidebar"] { width: 100%; }
355
+ /* Fluid column: it fills the conversation panel and only caps on very wide windows. */
356
+ .wsp-shell { display: flex; flex-direction: column; gap: 20px; width: 100%; max-width: none; margin-inline: auto; }
357
+ /* The manage panel reuses the settings component; centre it and let it fill the column. */
358
+ .wsp-panel-area > * { width: 100%; max-width: none; margin-inline: auto; box-sizing: border-box; }
359
+ /* The manage panel reuses the settings page: keep its chrome out of the tab, which has its own
360
+ header, so the reader sees one short line instead of two overlapping descriptions. */
361
+ /* The manage panel reuses the settings component: its own heading and description would repeat
362
+ the tab's, but its counts and store path are the useful part and stay. */
363
+ .wsp-panel-area .wsp-title, .wsp-panel-area .wsp-sub { display: none; }
364
+ .wsp-view-head { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
365
+ .wsp-view-headtext { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
366
+ .wsp-view-title { margin: 0; font-size: 18px; font-weight: 600; line-height: 1.3; color: var(--wsp-text); }
367
+ .wsp-view-lead { margin: 0; font-size: 13px; line-height: 1.6; color: var(--wsp-muted); max-width: 68ch; }
368
+ .wsp-view-headactions { margin-left: auto; display: flex; align-items: center; gap: 10px; }
369
+ /* Tabs live on top, and the active one is a colour + underline (ux: Active State). */
370
+ .wsp-tabs { display: inline-flex; align-items: stretch; gap: 3px; height: 28px; padding: 0 3px; box-sizing: border-box; border: 1px solid var(--wsp-line); border-radius: 10px; background: rgba(128, 128, 128, 0.12); }
371
+ .wsp-tab { display: inline-flex; align-items: center; padding: 0 12px; border: 0; border-radius: 8px; background: transparent; font: inherit; font-size: 12.5px; color: var(--wsp-muted); cursor: pointer; transition: background 150ms ease, color 150ms ease, box-shadow 150ms ease; }
372
+ .wsp-tab:hover { color: var(--wsp-text); background: var(--wsp-hover); }
373
+ .wsp-tab:focus-visible { outline: 2px solid var(--wsp-accent); outline-offset: 2px; }
374
+ .wsp-tab-on { background: var(--wsp-surface); color: var(--wsp-text); font-weight: 600; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08); }
375
+ /* Enter transitions: opacity + a 4px lift only, so nothing reflows and there is no flash.
376
+ Each tab renders its own keyed panel, so the animation replays on every switch. */
377
+ .wsp-panel-area { display: flex; flex-direction: column; gap: 16px; animation: wsp-enter 260ms cubic-bezier(0.22, 1, 0.36, 1) both; }
378
+ .wsp-shell > .wsp-view-head, .wsp-shell > .wsp-tabs { animation: wsp-enter 200ms cubic-bezier(0.22, 1, 0.36, 1) both; }
379
+ @keyframes wsp-enter { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
380
+
381
+ /* Reading something: a hairline bar at the top of the column, and the icon turns. */
382
+ .wsp-progress { position: relative; height: 2px; margin: -6px 0 2px; border-radius: 999px; overflow: hidden; background: var(--wsp-line-soft); }
383
+ .wsp-progress-bar { position: absolute; inset: 0 auto 0 0; width: 40%; border-radius: 999px; background: var(--wsp-accent); animation: wsp-slide 1100ms ease-in-out infinite; }
384
+ @keyframes wsp-slide { 0% { transform: translateX(-100%); } 100% { transform: translateX(250%); } }
385
+ .wsp-spin { animation: wsp-rotate 900ms linear infinite; }
386
+ @keyframes wsp-rotate { to { transform: rotate(360deg); } }
387
+ .wsp-card-panel { display: flex; flex-direction: column; gap: 14px; padding: 0 20px 20px; border: 1px solid var(--wsp-line); border-radius: 16px; background: var(--wsp-surface); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 8px 24px rgba(0, 0, 0, 0.06); overflow: hidden; transition: box-shadow 200ms ease, border-color 200ms ease; }
388
+ .wsp-card-panel:hover { border-color: var(--wsp-line-strong); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06), 0 12px 28px rgba(0, 0, 0, 0.09); }
389
+ .wsp-card-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0 -20px; padding: 14px 20px; border-bottom: 1px solid var(--wsp-line-soft); }
390
+ .wsp-card-title { margin: 0; font-size: 14.5px; font-weight: 600; color: var(--wsp-text); }
391
+ .wsp-card-title::before { content: ''; display: inline-block; width: 3px; height: 13px; margin-right: 8px; border-radius: 2px; background: var(--wsp-accent); vertical-align: -1px; }
392
+ .wsp-card-meta { font-size: 11.5px; color: var(--wsp-muted-2); font-variant-numeric: tabular-nums; }
393
+ .wsp-card-note { margin: 0; font-size: 12.5px; line-height: 1.7; color: var(--wsp-muted); width: 100%; max-width: none; }
394
+ .wsp-grow { flex: 1 1 auto; }
395
+ .wsp-tags { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
396
+ .wsp-prose { font-size: 13px; line-height: 1.75; color: var(--wsp-text); width: 100%; max-width: none; }
397
+ .wsp-prose-scroll { padding-right: 4px; }
398
+ .wsp-source { margin: 0; padding: 14px 16px; border: 1px solid var(--wsp-line-soft); border-radius: 10px; background: var(--wsp-surface-3); font-family: var(--dsw-font-markdown-code-font-family, ui-monospace, monospace); font-size: 12px; line-height: 1.7; color: var(--wsp-text); white-space: pre-wrap; word-break: break-word; }
399
+ .wsp-switch { display: flex; align-items: center; gap: 2px; padding: 2px; border: 1px solid var(--wsp-line); border-radius: 9px; background: var(--wsp-surface); }
400
+ .wsp-switch-item { border: 0; border-radius: 7px; background: none; font: inherit; font-size: 12px; padding: 3px 10px; color: var(--wsp-muted); cursor: pointer; transition: background 150ms ease, color 150ms ease; }
401
+ .wsp-switch-item:hover { color: var(--wsp-text); }
402
+ .wsp-switch-on { background: var(--wsp-surface-3); color: var(--wsp-text); box-shadow: var(--wsp-card-shadow); font-weight: 500; }
403
+ .wsp-form { display: flex; flex-direction: column; gap: 14px; }
404
+ .wsp-field { display: flex; flex-direction: column; gap: 6px; }
405
+ .wsp-field-label { font-size: 12px; font-weight: 500; color: var(--wsp-muted); }
406
+ .wsp-field-hint { font-size: 12px; color: var(--wsp-muted-2); }
407
+ .wsp-seg { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
408
+ .wsp-form-text { width: 100%; min-height: 280px; resize: vertical; box-sizing: border-box; padding: 12px 14px; border: 1px solid var(--wsp-line); border-radius: 10px; font-family: var(--dsw-font-markdown-code-font-family, ui-monospace, monospace); font-size: 12.5px; line-height: 1.7; color: var(--wsp-text); background: var(--wsp-surface); transition: border-color 150ms ease; }
409
+ .wsp-form-text:focus-visible { outline: 2px solid var(--wsp-accent); outline-offset: 1px; }
410
+ .wsp-form-actions { display: flex; align-items: center; gap: 8px; }
411
+ .wsp-alert { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border: 1px solid color-mix(in srgb, var(--wsp-danger, #f85149) 40%, transparent); border-radius: 10px; font-size: 12.5px; color: var(--wsp-text); background: color-mix(in srgb, var(--wsp-danger, #f85149) 8%, transparent); box-sizing: border-box; }
412
+ .wsp-skel { display: flex; flex-direction: column; gap: 10px; }
413
+ .wsp-skel-line { height: 12px; border-radius: 6px; background: var(--wsp-surface-2); animation: wsp-pulse 1.4s ease-in-out infinite; }
414
+ @keyframes wsp-pulse { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
415
+ .wsp-view button, .wsp-view [role="button"] { cursor: pointer; }
416
+ .wsp-view textarea, .wsp-view input { cursor: text; }
417
+ @media (max-width: 640px) {
418
+ .wsp-view { padding: 16px 14px 24px; gap: 14px; }
419
+ .wsp-view-head { flex-direction: column; align-items: stretch; gap: 10px; }
420
+ .wsp-view-headactions { margin-left: 0; justify-content: space-between; }
421
+ .wsp-view-lead { font-size: 12.5px; }
422
+ .wsp-tabs { overflow-x: auto; padding-bottom: 0; }
423
+ .wsp-tab { flex: none; padding: 6px 14px 7px; }
424
+ .wsp-shell { gap: 16px; }
425
+ .wsp-view { width: 100%; padding: 4% 3% 6%; }
426
+ .wsp-card-panel { padding: 0 14px 16px; border-radius: 14px; }
427
+ .wsp-card-head { margin: 0 -14px; padding: 12px 14px; }
428
+ .wsp-card-title { font-size: 14px; }
429
+ .wsp-card-note, .wsp-prose { max-width: none; }
430
+ .wsp-form-text { min-height: 200px; font-size: 12px; }
431
+ .wsp-form-actions { flex-wrap: wrap; }
432
+ }
433
+ @media (prefers-reduced-motion: reduce) {
434
+ .wsp-skel-line, .wsp-panel-area, .wsp-shell > .wsp-view-head, .wsp-shell > .wsp-tabs { animation: none; }
435
+ .wsp-tab, .wsp-card-panel, .wsp-form-text, .wsp-switch-item { transition: none; }
436
+ .wsp-spin { animation: none; }
437
+ .wsp-progress-bar { animation: none; width: 100%; }
438
+ }
439
+ .wsp-md { display: flex; flex-direction: column; gap: 6px; }
440
+ .wsp-md-fallback { display: flex; flex-direction: column; gap: 4px; }
441
+ .wsp-md-note { font-size: 10.5px; color: var(--wsp-warn, #d29922); }
442
+ .wsp-plain { margin: 0; font-family: var(--wsp-mono, ui-monospace, Menlo, monospace); font-size: 11.5px; white-space: pre-wrap; overflow-wrap: anywhere; }
443
+ /* A clipped message says so on its own row, behind a dashed rule, instead of running into the
444
+ prose \u2014 and carries the action that fixes it. */
445
+ .wsp-trunc { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; padding-top: 8px; border-top: 1px dashed var(--wsp-line); font-size: 11.5px; color: var(--wsp-muted); }
446
+ .wsp-trunc svg { flex: none; opacity: 0.8; color: var(--wsp-warn, #d29922); }
447
+ .wsp-trunc-text { min-width: 0; }
448
+ .wsp-trunc-quiet svg { display: none; }
449
+ .wsp-trunc .wsp-expand-btn { margin-top: 0; margin-left: auto; }
450
+ /* Expanding a clipped message: a pill, so it reads as an action and not as prose with an
451
+ underline. Visible surface at rest, darker on hover, ring on keyboard focus. */
452
+ .wsp-expand-btn {
453
+ align-self: flex-start; display: inline-flex; align-items: center; gap: 4px;
454
+ height: 24px; padding: 0 10px 0 8px; margin-top: 2px;
455
+ border: 1px solid var(--wsp-line); border-radius: 999px; background: var(--wsp-surface-2);
456
+ font: inherit; font-size: 11.5px; line-height: 1; color: var(--wsp-muted); cursor: pointer;
457
+ transition: color 150ms ease, background 150ms ease, border-color 150ms ease;
458
+ }
459
+ .wsp-expand-btn:hover { color: var(--wsp-text); background: var(--wsp-hover); border-color: var(--wsp-line-strong); }
460
+ .wsp-expand-btn:focus-visible { outline: 2px solid var(--wsp-accent); outline-offset: 1px; }
461
+ .wsp-expand-btn:disabled { opacity: 0.6; cursor: default; }
462
+ .wsp-expand-btn svg { flex: none; opacity: 0.75; }
463
+ .wsp-msg-mine .wsp-expand-btn { align-self: flex-end; }
464
+ .wsp-chat {
465
+ display: flex; flex-direction: column; gap: 16px;
466
+ max-height: 64vh; overflow: auto; padding: 6px 4px;
467
+ }
468
+ .wsp-msg { display: flex; align-items: flex-start; gap: 10px; }
469
+ .wsp-msg-mine { flex-direction: row-reverse; }
470
+ .wsp-avatar {
471
+ flex: none; width: 32px; height: 32px; border-radius: 9px;
472
+ display: inline-flex; align-items: center; justify-content: center;
473
+ background: var(--wsp-surface-2); color: var(--wsp-muted-2);
474
+ }
475
+ .wsp-msg-mine .wsp-avatar { background: color-mix(in srgb, var(--wsp-success) 26%, transparent); color: var(--wsp-text); }
476
+ .wsp-msg-col { display: flex; flex-direction: column; gap: 3px; max-width: 74%; min-width: 0; }
477
+ .wsp-msg-mine .wsp-msg-col { align-items: flex-end; }
478
+ .wsp-msg-who { font-size: 10.5px; color: var(--wsp-muted-2); padding: 0 4px; }
479
+ .wsp-bubble {
480
+ position: relative; padding: 9px 12px; border-radius: 10px;
481
+ background: var(--dsw-alias-bg-layer-2, rgba(128, 128, 128, 0.06));
482
+ border: 1px solid var(--wsp-line-soft); color: var(--wsp-text);
483
+ font-size: 12.5px; line-height: 1.65; max-width: 100%; overflow-wrap: anywhere;
484
+ }
485
+ .wsp-bubble::before {
486
+ content: ''; position: absolute; top: 13px; left: -5px; width: 9px; height: 9px;
487
+ background: inherit; border-left: 1px solid var(--wsp-line-soft); border-bottom: 1px solid var(--wsp-line-soft);
488
+ transform: rotate(45deg);
489
+ }
490
+ .wsp-bubble-mine {
491
+ background: color-mix(in srgb, var(--wsp-success) 20%, var(--dsw-alias-bg-layer-2, #fff));
492
+ border-color: color-mix(in srgb, var(--wsp-success) 34%, transparent);
493
+ }
494
+ .wsp-msg-mine .wsp-bubble::before {
495
+ left: auto; right: -5px; border-left: 0; border-bottom: 0;
496
+ border-right: 1px solid color-mix(in srgb, var(--wsp-success) 34%, transparent);
497
+ border-top: 1px solid color-mix(in srgb, var(--wsp-success) 34%, transparent);
498
+ }
499
+ /* Markdown inside a bubble should read like a message, not a document. */
500
+ .wsp-bubble > *:first-child { margin-top: 0 !important; }
501
+ .wsp-bubble > *:last-child { margin-bottom: 0 !important; }
502
+ .wsp-bubble p { margin: 0 0 6px; }
503
+ .wsp-bubble h1, .wsp-bubble h2, .wsp-bubble h3, .wsp-bubble h4 { font-size: 13px; font-weight: 600; margin: 8px 0 4px; }
504
+ .wsp-bubble ul, .wsp-bubble ol { margin: 0 0 6px; padding-left: 18px; }
505
+ .wsp-bubble li { margin: 2px 0; }
506
+ .wsp-bubble pre { max-width: 100%; overflow: auto; margin: 6px 0; }
507
+ .wsp-bubble code { font-family: var(--wsp-mono); font-size: 11.5px; }
508
+ .wsp-bubble blockquote { margin: 6px 0; padding-left: 8px; border-left: 2px solid var(--wsp-line-soft); color: var(--wsp-muted-2); }
509
+ .wsp-bubble table { border-collapse: collapse; font-size: 11.5px; }
510
+ .wsp-bubble th, .wsp-bubble td { border: 1px solid var(--wsp-line-soft); padding: 2px 6px; }
511
+ .wsp-status { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--wsp-muted); }
512
+ .wsp-status-ok { color: var(--wsp-success); }
513
+ .wsp-status-err { color: var(--wsp-danger); }
514
+ .wsp-status svg { flex: none; }
515
+
516
+ @media (prefers-reduced-motion: reduce) {
517
+ [data-plugin="dsh-agent-persona"] * { transition: none !important; }
518
+ }
519
+ `;
520
+ let styleElement;
521
+ const injectStyles = () => {
522
+ if (styleElement !== void 0) return styleElement;
523
+ styleElement = document.createElement("style");
524
+ styleElement.setAttribute("data-dsh-agent-persona", "");
525
+ styleElement.textContent = CSS;
526
+ document.head.appendChild(styleElement);
527
+ return styleElement;
528
+ };
529
+ class SafeMarkdown extends React.Component {
530
+ constructor(props) {
531
+ super(props === void 0 || props === null ? {} : props);
532
+ this.state = { failed: false, reason: void 0 };
533
+ }
534
+ static getDerivedStateFromError() {
535
+ return { failed: true };
536
+ }
537
+ componentDidCatch(error) {
538
+ console.warn("[agent-persona] message markdown failed to render; showing plain text", error);
539
+ this.setState({ reason: String(error && error.message || error).slice(0, 220) });
540
+ }
541
+ render() {
542
+ const text = typeof this.props.text === "string" ? this.props.text : "";
543
+ if (this.state.failed) {
544
+ return h("div", { className: "wsp-md-fallback" }, [
545
+ h("div", { className: "wsp-md-note", key: "n" }, `\u8FD9\u6761\u6D88\u606F\u6CA1\u80FD\u6309 Markdown \u6E32\u67D3\uFF08${this.state.reason ?? "\u539F\u56E0\u672A\u77E5"}\uFF09\uFF0C\u4E0B\u9762\u662F\u539F\u6587\uFF1A`),
546
+ h("pre", { className: "wsp-plain", key: "p" }, text)
547
+ ]);
548
+ }
549
+ return h(MarkdownText, { text, labels: { code: { copyLabel: "\u590D\u5236", copiedLabel: "\u5DF2\u590D\u5236" }, footnotes: "" } });
550
+ }
551
+ }
552
+ const verifyElements = () => {
553
+ const table = {
554
+ Button,
555
+ DisclosureRow,
556
+ Input,
557
+ MarkdownText,
558
+ Menu,
559
+ Modal,
560
+ Pill,
561
+ Toast,
562
+ Tooltip,
563
+ Badge,
564
+ Dot,
565
+ Switch,
566
+ SafeMarkdown,
567
+ WorkspacePersonaSection,
568
+ IconAgentPresetOutline16,
569
+ IconBrowseOutline16,
570
+ IconCheckOutline14,
571
+ IconChevronDownOutline14,
572
+ IconChevronRightOutline14,
573
+ IconChevronUpOutline14,
574
+ IconCloseOutline16,
575
+ IconEditOutline16,
576
+ IconEllipsisOutline16,
577
+ IconEnhanceOutline16,
578
+ IconFolderOpen16,
579
+ IconListPenOutline16,
580
+ IconPersonalizationOutline16,
581
+ IconPlusOutline16,
582
+ IconTrashOutline16,
583
+ IconUserOutline16,
584
+ IconWarningOutline16
585
+ };
586
+ const missing = Object.entries(table).filter(([, value]) => value === void 0).map(([name]) => name);
587
+ if (missing.length > 0) throw new Error(`UI \u7EC4\u4EF6\u7F3A\u5931\uFF08\u5BBF\u4E3B\u7EC4\u4EF6\u5E93\u6CA1\u6709\u5BFC\u51FA\uFF09\uFF1A${missing.join("\u3001")}`);
588
+ };
589
+ let capturedApi;
590
+ const apiOf = (props) => props !== void 0 && props !== null && props.api !== void 0 ? props.api : capturedApi;
591
+ const unwrapEnvelope = (raw) => {
592
+ if (raw !== null && typeof raw === "object" && "ok" in raw) {
593
+ if (raw.ok === false) {
594
+ const error = raw.error;
595
+ throw new Error(typeof error === "string" ? error : String(error && (error.message || error.code) || "\u8FDC\u7A0B\u8C03\u7528\u5931\u8D25"));
596
+ }
597
+ return raw.value;
598
+ }
599
+ if (raw !== null && typeof raw === "object" && "error" in raw) {
600
+ const error = raw.error;
601
+ throw new Error(typeof error === "string" ? error : String(error && (error.message || error.code) || "\u8FDC\u7A0B\u8C03\u7528\u5931\u8D25"));
602
+ }
603
+ return raw;
604
+ };
605
+ const SidebarPersonaView = (props) => h(PersonaView, { ...props, host: "sidebar" });
606
+ class PersonaView extends React.Component {
607
+ constructor(props) {
608
+ super(props === void 0 || props === null ? {} : props);
609
+ this.state = { loading: true, data: null, error: "", busy: "", draft: null, editing: false, copied: false, tab: "persona", promptView: "render" };
610
+ }
611
+ componentDidMount() {
612
+ void this.load();
613
+ }
614
+ sessionId() {
615
+ const id = this.props?.sessionId ?? this.props?.session?.id;
616
+ return typeof id === "string" && id !== "" ? id : null;
617
+ }
618
+ async load() {
619
+ const api = apiOf(this.props);
620
+ const sessionId = this.sessionId();
621
+ if (api === void 0 || typeof api.sessionPrompt !== "function") {
622
+ this.setState({ loading: false, error: "\u8FDC\u7A0B\u670D\u52A1\u8FD8\u6CA1\u5C31\u7EEA\uFF0C\u7A0D\u540E\u91CD\u8BD5" });
623
+ return;
624
+ }
625
+ if (sessionId === null) {
626
+ this.setState({ loading: false, error: "\u8FD9\u4E2A\u89C6\u56FE\u6CA1\u6709\u62FF\u5230\u4F1A\u8BDD id" });
627
+ return;
628
+ }
629
+ this.setState({ busy: "load", error: "" });
630
+ try {
631
+ const data = unwrapEnvelope(await api.sessionPrompt({ id: sessionId }));
632
+ this.setState({
633
+ loading: false,
634
+ busy: "",
635
+ data,
636
+ draft: data?.persona === null || data?.persona === void 0 ? null : {
637
+ name: data.persona.name,
638
+ text: data.persona.text,
639
+ mode: data.persona.mode,
640
+ // The card's own switch edits this, so it has to be in the draft: without it the
641
+ // switch looked inert (no dirty state) and saving wrote the old value back.
642
+ fallback: data.persona.fallback === true
643
+ }
644
+ });
645
+ } catch (error) {
646
+ this.setState({ loading: false, busy: "", error: String(error && error.message || error) });
647
+ }
648
+ }
649
+ async save() {
650
+ const api = apiOf(this.props);
651
+ const { data, draft } = this.state;
652
+ if (api === void 0 || data?.persona === null || draft === null) return;
653
+ this.setState({ busy: "save", error: "" });
654
+ try {
655
+ unwrapEnvelope(await api.savePersona({
656
+ id: data.persona.id,
657
+ name: draft.name,
658
+ enabled: data.persona.enabled,
659
+ fallback: draft.fallback === true,
660
+ mode: draft.mode,
661
+ text: draft.text,
662
+ targets: data.persona.targets ?? []
663
+ }));
664
+ await this.load();
665
+ this.setState({ busy: "", error: "" });
666
+ } catch (error) {
667
+ this.setState({ busy: "", error: String(error && error.message || error) });
668
+ }
669
+ }
670
+ async copyPrompt() {
671
+ const prompt = this.state.data?.prompt ?? "";
672
+ if (prompt === "") return;
673
+ try {
674
+ await navigator.clipboard.writeText(prompt);
675
+ this.setState({ copied: true });
676
+ setTimeout(() => this.setState({ copied: false }), 1600);
677
+ } catch (error) {
678
+ this.setState({ error: `\u590D\u5236\u5931\u8D25\uFF1A${String(error && error.message || error)}` });
679
+ }
680
+ }
681
+ patch(patch) {
682
+ this.setState((previous) => ({ draft: { ...previous.draft, ...patch } }));
683
+ }
684
+ render() {
685
+ const { data, draft, error, busy, loading, editing, copied, tab, promptView } = this.state;
686
+ const persona = data?.persona ?? null;
687
+ const dirty = persona !== null && draft !== null && (draft.text !== persona.text || draft.name !== persona.name || draft.mode !== persona.mode || draft.fallback === true !== (persona.fallback === true));
688
+ const matched = data?.matchedBy ?? [];
689
+ const prompt = data?.prompt ?? null;
690
+ const TABS = [
691
+ { id: "persona", label: "\u4EBA\u8BBE" },
692
+ { id: "prompt", label: "\u63D0\u793A\u8BCD" },
693
+ { id: "manage", label: "\u7BA1\u7406" }
694
+ ];
695
+ const tabs = h("div", { className: "wsp-tabs", key: "tabs", role: "tablist" }, TABS.map((item) => h("button", {
696
+ key: item.id,
697
+ type: "button",
698
+ role: "tab",
699
+ "aria-selected": tab === item.id,
700
+ className: tab === item.id ? "wsp-tab wsp-tab-on" : "wsp-tab",
701
+ onClick: () => {
702
+ if (item.id === tab) return;
703
+ const reduce = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
704
+ this.setState({ tab: item.id });
705
+ this.scroller?.scrollTo?.({ top: 0, behavior: reduce ? "auto" : "smooth" });
706
+ if (item.id !== "manage") void this.load();
707
+ }
708
+ }, item.label)));
709
+ const header = h("header", { className: "wsp-view-head", key: "head" }, [
710
+ h("div", { className: "wsp-view-headtext", key: "t" }, [
711
+ h("h2", { className: "wsp-view-title", key: "h" }, "Agent \u4EBA\u8BBE"),
712
+ h("p", { className: "wsp-view-lead", key: "p" }, "\u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\u7684\u4EBA\u8BBE\u3001\u5B9E\u9645\u53D1\u9001\u7684\u63D0\u793A\u8BCD\uFF0C\u4EE5\u53CA\u7BA1\u7406\u5168\u90E8\u4EBA\u8BBE\u3002")
713
+ ]),
714
+ busy === "" ? null : h(
715
+ "div",
716
+ { className: "wsp-progress", key: "prog", role: "status", "aria-label": "\u6B63\u5728\u8BFB\u53D6" },
717
+ h("span", { className: "wsp-progress-bar" })
718
+ ),
719
+ h("div", { className: "wsp-view-headactions", key: "a" }, [
720
+ tabs,
721
+ h(Button, {
722
+ key: "reload",
723
+ variant: "outline",
724
+ size: "sm",
725
+ icon: h(IconRefreshOutline16, { className: busy === "load" ? "wsp-spin" : void 0 }),
726
+ disabled: busy === "load",
727
+ onClick: () => void this.load()
728
+ }, busy === "load" ? "\u5237\u65B0\u4E2D\u2026" : "\u5237\u65B0")
729
+ ])
730
+ ]);
731
+ const alerts = [];
732
+ if (error !== "") {
733
+ alerts.push(h("div", { className: "wsp-alert", role: "alert", key: "err" }, [
734
+ h(IconWarningOutline16, { key: "i" }),
735
+ h("span", { key: "t" }, error),
736
+ h(Button, { key: "r", variant: "outline", size: "sm", onClick: () => void this.load() }, "\u91CD\u8BD5")
737
+ ]));
738
+ }
739
+ const card = (opts) => h("section", { className: "wsp-card-panel", key: opts.key }, [
740
+ h("div", { className: "wsp-card-head", key: "h" }, [
741
+ h("h3", { className: "wsp-card-title", key: "t" }, opts.title),
742
+ opts.meta === void 0 ? null : h("span", { className: "wsp-card-meta", key: "m" }, opts.meta),
743
+ ...opts.tags ?? [],
744
+ h("span", { className: "wsp-grow", key: "g" }),
745
+ ...opts.actions ?? []
746
+ ]),
747
+ ...opts.extra ?? [],
748
+ opts.note === void 0 ? null : h("p", { className: "wsp-card-note", key: "n" }, opts.note),
749
+ ...opts.body ?? []
750
+ ]);
751
+ let panel;
752
+ if (loading) {
753
+ panel = h("div", { className: "wsp-panel-area", key: "sk" }, h("div", { className: "wsp-skel" }, [
754
+ h("div", { className: "wsp-skel-line", key: "a", style: { width: "38%" } }),
755
+ h("div", { className: "wsp-skel-line", key: "b", style: { width: "92%" } }),
756
+ h("div", { className: "wsp-skel-line", key: "c", style: { width: "74%" } })
757
+ ]));
758
+ } else if (tab === "persona") {
759
+ if (persona === null) {
760
+ panel = h("div", { className: "wsp-panel-area", key: "empty" }, card({
761
+ key: "c",
762
+ title: "\u8FD9\u6761\u4F1A\u8BDD\u6CA1\u6709\u547D\u4E2D\u4EFB\u4F55\u4EBA\u8BBE",
763
+ note: "\u5B83\u4F7F\u7528 DSH \u81EA\u5E26\u7684 system prompt \u2014\u2014 \u5728\u300C\u63D0\u793A\u8BCD\u300D\u91CC\u53EF\u4EE5\u770B\u5230\u5B8C\u6574\u5185\u5BB9\u3002\u8981\u7ED9\u5B83\u6307\u5B9A\u4EBA\u8BBE\uFF0C\u53BB\u300C\u7BA1\u7406\u300D\u65B0\u5EFA\u4E00\u6761\uFF0C\u5E76\u628A\u9002\u7528\u8303\u56F4\u6307\u5230\u8FD9\u6761\u4F1A\u8BDD\u6216\u5B83\u7684\u5DE5\u4F5C\u533A\u3002"
764
+ }));
765
+ } else {
766
+ panel = h("div", { className: "wsp-panel-area", key: "persona" }, card({
767
+ key: "c",
768
+ title: persona.name,
769
+ meta: void 0,
770
+ tags: [
771
+ h("span", { className: "wsp-card-meta", key: "n" }, `${persona.text.length} \u5B57`),
772
+ h("span", {
773
+ key: "e",
774
+ className: persona.enabled ? "wsp-chip wsp-chip-on" : "wsp-chip wsp-chip-off",
775
+ title: persona.enabled ? "\u8FD9\u6761\u4EBA\u8BBE\u5DF2\u542F\u7528\uFF0C\u4F1A\u53C2\u4E0E\u6CE8\u5165" : "\u8FD9\u6761\u4EBA\u8BBE\u5DF2\u505C\u7528\uFF0C\u4E0D\u53C2\u4E0E\u6CE8\u5165"
776
+ }, persona.enabled ? "\u5728\u7528" : "\u5DF2\u505C\u7528"),
777
+ persona.fallback === true ? h("span", { className: "wsp-chip wsp-chip-on", key: "fb", title: "\u672A\u547D\u4E2D\u4EFB\u4F55\u4F4D\u7F6E\u7684\u4F1A\u8BDD\u90FD\u7528\u8FD9\u6761\u4EBA\u8BBE" }, "\u9ED8\u8BA4\u4EBA\u8BBE") : null,
778
+ h("span", {
779
+ key: "m",
780
+ className: persona.mode === "replace" ? "wsp-chip wsp-chip-mode-strong" : "wsp-chip wsp-chip-mode",
781
+ title: persona.mode === "replace" ? "\u66FF\u6362\uFF1ADSH \u81EA\u5E26\u7684\u8EAB\u4EFD\u53E5\u4E0E\u90E8\u7F72\u8EAB\u4EFD\u90FD\u53BB\u6389\uFF0C\u53EA\u7528\u8FD9\u6761\u4EBA\u8BBE" : "\u8FFD\u52A0\uFF1ADSH \u81EA\u5E26\u7684\u8EAB\u4EFD\u8BF4\u660E\u4FDD\u7559\uFF0C\u8FD9\u6761\u4EBA\u8BBE\u63A5\u5728\u540E\u9762"
782
+ }, persona.mode === "replace" ? "\u66FF\u6362 DSH \u8EAB\u4EFD" : "\u8FFD\u52A0\u5728 DSH \u8EAB\u4EFD\u4E4B\u540E"),
783
+ dirty ? h("span", { key: "d", className: "wsp-chip wsp-chip-warn", title: "\u7F16\u8F91\u533A\u7684\u5185\u5BB9\u4E0E\u5DF2\u4FDD\u5B58\u7684\u6B63\u6587\u4E0D\u4E00\u81F4" }, "\u6709\u672A\u4FDD\u5B58\u7684\u4FEE\u6539") : null
784
+ ],
785
+ actions: [
786
+ h(
787
+ Button,
788
+ { key: "edit", variant: "outline", size: "sm", onClick: () => this.setState({ editing: !editing }) },
789
+ editing ? "\u53D6\u6D88\u7F16\u8F91" : "\u7F16\u8F91\u6B63\u6587"
790
+ )
791
+ ],
792
+ body: [
793
+ editing && draft !== null ? h("div", { className: "wsp-form", key: "form" }, [
794
+ h("label", { className: "wsp-field", key: "name" }, [
795
+ h("span", { className: "wsp-field-label", key: "l" }, "\u540D\u79F0"),
796
+ h(Input, { key: "i", value: draft.name, onChange: (event) => this.patch({ name: event.target.value }) })
797
+ ]),
798
+ h("div", { className: "wsp-field", key: "fallback" }, [
799
+ h("span", { className: "wsp-field-label", key: "l" }, "\u9ED8\u8BA4\u4EBA\u8BBE"),
800
+ h("div", { className: "wsp-seg", key: "s" }, [
801
+ h(Switch, {
802
+ key: "fb",
803
+ checked: draft.fallback === true,
804
+ label: "\u4F5C\u4E3A\u9ED8\u8BA4\u4EBA\u8BBE\uFF08\u672A\u547D\u4E2D\u4EFB\u4F55\u4F4D\u7F6E\u7684\u4F1A\u8BDD\u90FD\u7528\u5B83\uFF0C\u540C\u4E00\u65F6\u523B\u53EA\u80FD\u6709\u4E00\u6761\uFF09",
805
+ onChange: (next) => this.patch({ fallback: next })
806
+ })
807
+ ])
808
+ ]),
809
+ h("div", { className: "wsp-field", key: "mode" }, [
810
+ h("span", { className: "wsp-field-label", key: "l" }, "\u6CE8\u5165\u65B9\u5F0F"),
811
+ h("div", { className: "wsp-seg", key: "s" }, [
812
+ h(Pill, { key: "a", active: draft.mode !== "replace", onClick: () => this.patch({ mode: "append" }) }, "\u8FFD\u52A0"),
813
+ h(Pill, { key: "r", active: draft.mode === "replace", onClick: () => this.patch({ mode: "replace" }) }, "\u66FF\u6362"),
814
+ h("span", { className: "wsp-field-hint", key: "h" }, draft.mode === "replace" ? "DSH \u81EA\u5E26\u7684\u8EAB\u4EFD\u53E5\u548C\u90E8\u7F72\u8EAB\u4EFD\u90FD\u53BB\u6389\uFF0C\u53EA\u7528\u8FD9\u6761\u4EBA\u8BBE\u3002" : "DSH \u81EA\u5E26\u7684\u8EAB\u4EFD\u8BF4\u660E\u4FDD\u7559\uFF0C\u8FD9\u6761\u4EBA\u8BBE\u63A5\u5728\u540E\u9762\u3002")
815
+ ])
816
+ ]),
817
+ h("label", { className: "wsp-field", key: "body" }, [
818
+ h("span", { className: "wsp-field-label", key: "l" }, "\u6B63\u6587\uFF08Markdown\uFF09"),
819
+ h("textarea", {
820
+ key: "t",
821
+ className: "wsp-form-text",
822
+ value: draft.text,
823
+ spellCheck: false,
824
+ onChange: (event) => this.patch({ text: event.target.value })
825
+ })
826
+ ]),
827
+ h("div", { className: "wsp-form-actions", key: "a" }, [
828
+ h("span", { className: "wsp-card-meta", key: "c" }, `${draft.text.length} \u5B57 \xB7 \u4FDD\u5B58\u540E\u4E0B\u4E00\u4E2A\u8BF7\u6C42\u751F\u6548`),
829
+ h("span", { className: "wsp-grow", key: "g" }),
830
+ h(Button, { key: "cancel", variant: "outline", size: "sm", disabled: busy !== "", onClick: () => this.setState({ editing: false }) }, "\u653E\u5F03\u4FEE\u6539"),
831
+ h(Button, {
832
+ key: "save",
833
+ variant: "primary",
834
+ size: "sm",
835
+ disabled: busy !== "" || !dirty,
836
+ onClick: () => void this.save()
837
+ }, busy === "save" ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58")
838
+ ])
839
+ ]) : h("div", { className: "wsp-prose", key: "body" }, h(SafeMarkdown, { text: draft?.text ?? persona.text }))
840
+ ]
841
+ }));
842
+ }
843
+ } else if (tab === "manage") {
844
+ panel = h(
845
+ "div",
846
+ { className: "wsp-panel-area", key: "manage" },
847
+ h(WorkspacePersonaSection, { key: "settings", api: this.props?.api ?? capturedApi })
848
+ );
849
+ } else {
850
+ panel = h("div", { className: "wsp-panel-area", key: "prompt" }, card({
851
+ key: "c",
852
+ title: "System Prompt",
853
+ meta: prompt === null ? "\u8FD8\u6CA1\u6709\u8BB0\u5F55" : `${prompt.length} \u5B57 \xB7 \u6700\u8FD1\u4E00\u6B21\u5B9E\u9645\u53D1\u9001`,
854
+ note: prompt === null ? "\u8FD9\u6761\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u53D1\u8D77\u8FC7\u8BF7\u6C42\uFF0C\u56E0\u6B64\u6CA1\u6709 system prompt \u8BB0\u5F55\u3002" : void 0,
855
+ actions: prompt === null ? [] : [
856
+ h("div", { className: "wsp-switch", key: "sw" }, [
857
+ h("button", { key: "r", type: "button", className: promptView === "render" ? "wsp-switch-item wsp-switch-on" : "wsp-switch-item", onClick: () => this.setState({ promptView: "render" }) }, "\u6E32\u67D3"),
858
+ h("button", { key: "s", type: "button", className: promptView === "source" ? "wsp-switch-item wsp-switch-on" : "wsp-switch-item", onClick: () => this.setState({ promptView: "source" }) }, "\u6E90\u7801")
859
+ ]),
860
+ h(Button, { key: "copy", variant: "outline", size: "sm", onClick: () => void this.copyPrompt() }, copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236")
861
+ ],
862
+ body: prompt === null ? [] : [promptView === "source" ? h("pre", { className: "wsp-source", key: "src" }, prompt) : h("div", { className: "wsp-prose wsp-prose-scroll", key: "md" }, h(SafeMarkdown, { text: prompt }))]
863
+ }));
864
+ }
865
+ return h(
866
+ "div",
867
+ { className: "wsp-view", ref: (node) => {
868
+ this.scroller = node;
869
+ }, "data-plugin": NS, "data-host": this.props?.host === "sidebar" ? "sidebar" : void 0 },
870
+ h("div", { className: "wsp-shell" }, [header, ...alerts, panel])
871
+ );
872
+ }
873
+ }
874
+ const dotState = (persona) => {
875
+ if (persona === void 0) return "off";
876
+ if (!persona.enabled) return "off";
877
+ return (persona.text || "").trim() === "" ? "empty" : "on";
878
+ };
879
+ const targetValid = (target) => {
880
+ if ((target.value || "").trim() === "") return false;
881
+ if (target.match !== "regex") return true;
882
+ try {
883
+ new RegExp(target.value);
884
+ return true;
885
+ } catch {
886
+ return false;
887
+ }
888
+ };
889
+ const describeTarget = (target) => `${MATCH_LABEL[target.match] ?? target.match} ${target.value}`;
890
+ const ownerMark = (owner, personaId) => owner.id === personaId ? "\u5F53\u524D\u4EBA\u8BBE" : `\u5DF2\u88AB\u300C${owner.name}\u300D\u7528`;
891
+ const shortSessionId = (id) => {
892
+ const uuid = /^session-([0-9a-f]{8})/i.exec(id);
893
+ if (uuid !== null) return `session-${uuid[1]}`;
894
+ return id.length <= 26 ? id : `${id.slice(0, 12)}\u2026${id.slice(-6)}`;
895
+ };
896
+ const since = (timestamp) => {
897
+ const seconds = Math.max(0, Math.round((Date.now() - timestamp) / 1e3));
898
+ if (seconds < 60) return "\u521A\u521A";
899
+ if (seconds < 3600) return `${Math.round(seconds / 60)} \u5206\u949F\u524D`;
900
+ if (seconds < 86400) return `${Math.round(seconds / 3600)} \u5C0F\u65F6\u524D`;
901
+ return `${Math.round(seconds / 86400)} \u5929\u524D`;
902
+ };
903
+ const Dot = ({ state }) => h("span", { className: `wsp-dot wsp-dot-${state}`, "aria-hidden": "true" });
904
+ const Badge = ({ children, tone }) => h("span", {
905
+ className: tone === void 0 ? "wsp-badge" : `wsp-badge wsp-badge-${tone}`
906
+ }, children);
907
+ const Switch = ({ checked, disabled, label, onChange }) => h("label", { className: "wsp-switch", title: label }, [
908
+ h("input", {
909
+ key: "i",
910
+ type: "checkbox",
911
+ checked,
912
+ disabled,
913
+ "aria-label": label,
914
+ onChange: (event) => onChange(event.target.checked)
915
+ }),
916
+ h("span", { key: "t", className: "wsp-switch-track" }),
917
+ h("span", { key: "l", className: "wsp-switch-text" }, label)
918
+ ]);
919
+ class PersonaPrefsRow extends React.Component {
920
+ constructor(props) {
921
+ super(props);
922
+ this.state = { prefs: readPrefs(), status: "" };
923
+ this.mounted = false;
924
+ }
925
+ componentDidMount() {
926
+ this.mounted = true;
927
+ void this.load();
928
+ }
929
+ componentWillUnmount() {
930
+ this.mounted = false;
931
+ }
932
+ api() {
933
+ return this.props?.api ?? capturedApi;
934
+ }
935
+ async load() {
936
+ const api = this.api();
937
+ if (api === void 0) return;
938
+ try {
939
+ const view = unwrapEnvelope(await api.listPersonas());
940
+ if (this.mounted !== true) return;
941
+ if (view !== void 0 && view !== null && view.prefs !== void 0 && view.prefs !== null) writePrefs(view.prefs);
942
+ this.setState({ prefs: view !== void 0 && view !== null && view.prefs || this.state.prefs });
943
+ } catch (error) {
944
+ if (this.mounted === true) this.setState({ status: `\u8BFB\u53D6\u504F\u597D\u5931\u8D25\uFF1A${String(error && error.message || error)}` });
945
+ }
946
+ }
947
+ set(patch) {
948
+ persistPrefs(this.api(), unwrapEnvelope, this.state.prefs ?? DEFAULT_PREFS, patch, (update) => {
949
+ if (this.mounted !== true) return;
950
+ this.setState((previous) => ({
951
+ prefs: update.prefs ?? previous.prefs,
952
+ status: update.status ?? previous.status
953
+ }));
954
+ });
955
+ }
956
+ render() {
957
+ const prefs = this.state.prefs ?? DEFAULT_PREFS;
958
+ return h("div", { className: "wsp-prefs-row", "data-plugin": NS }, [
959
+ h("div", { className: "wsp-prefs-row-head", key: "h" }, [
960
+ h("span", { className: "wsp-prefs-row-title", key: "t" }, "Agent \u4EBA\u8BBE"),
961
+ sidebarProbe() === void 0 ? null : h("span", { className: "wsp-prefs-row-note", key: "n" }, "\u6CE8\u518C\u540E\uFF0Cdsh-better-sidebar \u7684\u300C\u4FA7\u8FB9\u5361\u7247 \u2192 \u4FA7\u8FB9\u680F\u5185\u5BB9\u300D\u91CC\u4F1A\u591A\u51FA Agent \u4EBA\u8BBE\uFF1B\u6CE8\u9500\u5373\u79FB\u9664\uFF08\u7ACB\u5373\u751F\u6548\uFF09")
962
+ ]),
963
+ h("div", { className: "wsp-prefs", key: "p" }, [
964
+ h(Switch, { key: "auto", checked: prefs.autosave === true, label: "\u7F16\u8F91\u540E\u81EA\u52A8\u4FDD\u5B58", onChange: (next) => this.set({ autosave: next }) }),
965
+ h(Switch, {
966
+ key: "tab",
967
+ checked: prefs.showTab !== false,
968
+ label: "\u5728\u5BF9\u8BDD\u9875\u663E\u793A\u300C\u4EBA\u8BBE\u300D\u6807\u7B7E",
969
+ onChange: (next) => this.set({ showTab: next })
970
+ }),
971
+ ...sidebarProbe() === void 0 ? [] : [h(Switch, {
972
+ key: "side",
973
+ checked: prefs.showSidebar !== false,
974
+ label: "\u6CE8\u518C\u5230 dsh-better-sidebar",
975
+ onChange: (next) => this.set({ showSidebar: next })
976
+ })]
977
+ ]),
978
+ this.state.status === "" ? null : h("span", { className: "wsp-prefs-row-status", key: "s", "aria-live": "polite" }, this.state.status)
979
+ ]);
980
+ }
981
+ }
982
+ class WorkspacePersonaSection extends React.Component {
983
+ constructor(props) {
984
+ super(props === void 0 || props === null ? {} : props);
985
+ this.state = {
986
+ prefs: readPrefs(),
987
+ view: void 0,
988
+ openId: void 0,
989
+ draft: void 0,
990
+ pane: "edit",
991
+ confirmDelete: void 0,
992
+ picker: void 0,
993
+ menu: void 0,
994
+ filter: "",
995
+ dragId: void 0,
996
+ dropIndex: void 0,
997
+ history: void 0,
998
+ historyBusy: void 0,
999
+ models: [],
1000
+ model: "",
1001
+ tuneOpen: false,
1002
+ tuneMode: "polish",
1003
+ instruction: "",
1004
+ proposal: "",
1005
+ targets: { workspaces: [], sessions: [], hidden: { subagents: 0, archived: 0, blank: 0 } },
1006
+ busy: "",
1007
+ error: "",
1008
+ status: ""
1009
+ };
1010
+ this.mounted = false;
1011
+ this.rootRef = React.createRef();
1012
+ this.onChange = (patch) => {
1013
+ if (this.mounted) this.setState(patch);
1014
+ };
1015
+ this.onKeyDown = (event) => {
1016
+ if (!(event.metaKey || event.ctrlKey) || String(event.key).toLowerCase() !== "s") return;
1017
+ const root = this.rootRef.current;
1018
+ if (root === null || !root.contains(event.target)) return;
1019
+ event.preventDefault();
1020
+ void this.save();
1021
+ };
1022
+ }
1023
+ componentDidMount() {
1024
+ this.mounted = true;
1025
+ document.addEventListener("keydown", this.onKeyDown);
1026
+ void this.loadTargets();
1027
+ void this.loadModels();
1028
+ void this.load();
1029
+ }
1030
+ componentWillUnmount() {
1031
+ clearTimeout(this.autosaveTimer);
1032
+ this.mounted = false;
1033
+ document.removeEventListener("keydown", this.onKeyDown);
1034
+ }
1035
+ api() {
1036
+ return apiOf(this.props);
1037
+ }
1038
+ /** `{ok:true,value}` / `{ok:false,error}`; a host failure arrives as `{error}`. */
1039
+ unwrap(raw) {
1040
+ if (raw !== null && typeof raw === "object" && raw.ok === false) {
1041
+ const failure = raw.error;
1042
+ const code = failure !== null && typeof failure === "object" && typeof failure.code === "string" ? failure.code : typeof failure === "string" && failure !== "" ? failure : "gateway";
1043
+ const message = failure !== null && typeof failure === "object" && typeof failure.message === "string" ? `: ${failure.message}` : "";
1044
+ throw new Error(`${code}${message}`);
1045
+ }
1046
+ const value = raw !== null && typeof raw === "object" && raw.ok === true ? raw.value : raw;
1047
+ if (value !== null && typeof value === "object" && typeof value.error === "string") throw new Error(value.error);
1048
+ return value;
1049
+ }
1050
+ async loadModels() {
1051
+ const api = this.api();
1052
+ if (api === void 0) return;
1053
+ try {
1054
+ const result = this.unwrap(await api.listModels());
1055
+ const models = Array.isArray(result?.models) ? result.models : [];
1056
+ const first = models[0];
1057
+ this.onChange({ models, model: first === void 0 ? "" : `${first.provider}/${first.model}` });
1058
+ } catch {
1059
+ }
1060
+ }
1061
+ /** Refresh the list; keeps the expanded card open when it still exists. */
1062
+ async load(keepOpen = false) {
1063
+ const api = this.api();
1064
+ if (api === void 0) {
1065
+ this.apiRetries = (this.apiRetries ?? 0) + 1;
1066
+ if (this.apiRetries <= 12) {
1067
+ setTimeout(() => {
1068
+ if (this.mounted) void this.load(keepOpen);
1069
+ }, 150);
1070
+ return;
1071
+ }
1072
+ this.onChange({ error: "\u63D2\u4EF6\u8FD8\u6CA1\u52A0\u8F7D\u597D\uFF1A\u8BF7\u91CD\u542F dsh web \u540E\u518D\u6253\u5F00\u8FD9\u4E2A\u9875\u9762" });
1073
+ return;
1074
+ }
1075
+ this.apiRetries = 0;
1076
+ this.onChange({ busy: "load", error: "" });
1077
+ try {
1078
+ const view = this.unwrap(await api.listPersonas());
1079
+ if (view?.prefs !== void 0 && view?.prefs !== null) writePrefs(view.prefs);
1080
+ const wanted = keepOpen === true ? this.state.openId : void 0;
1081
+ const persona = wanted === void 0 ? void 0 : (view.personas || []).find((item) => item.id === wanted);
1082
+ this.onChange({
1083
+ view,
1084
+ busy: "",
1085
+ openId: persona === void 0 ? void 0 : wanted,
1086
+ draft: persona === void 0 ? void 0 : this.draftFrom(persona)
1087
+ });
1088
+ } catch (error) {
1089
+ this.onChange({ busy: "", error: String(error && error.message || error) });
1090
+ }
1091
+ }
1092
+ /** Which row's picker or which card's action menu is open (one at a time). */
1093
+ togglePicker(key) {
1094
+ this.onChange({ picker: this.state.picker === key ? void 0 : key, menu: void 0 });
1095
+ }
1096
+ toggleMenu(key) {
1097
+ this.onChange({ menu: this.state.menu === key ? void 0 : key, picker: void 0 });
1098
+ }
1099
+ closePopovers() {
1100
+ if (this.state.picker !== void 0 || this.state.menu !== void 0) {
1101
+ this.onChange({ picker: void 0, menu: void 0 });
1102
+ }
1103
+ }
1104
+ /** "自定义输入…" is always the last row of a picker menu. */
1105
+ customItem() {
1106
+ return { id: "__custom__", label: "\u81EA\u5DF1\u8F93\u5165\u2026", icon: h(IconEditOutline16, {}) };
1107
+ }
1108
+ workspaceItems(personaId) {
1109
+ const workspaces = this.state.targets.workspaces ?? [];
1110
+ if (workspaces.length === 0) {
1111
+ return [{ type: "label", id: "empty", text: "\u5BBF\u4E3B\u6CA1\u6709\u8FD4\u56DE\u5DE5\u4F5C\u533A\u5217\u8868\uFF0C\u8BF7\u7528\u300C\u81EA\u5DF1\u8F93\u5165\u2026\u300D" }];
1112
+ }
1113
+ return workspaces.map((workspace) => ({
1114
+ id: workspace.path,
1115
+ label: h("span", { className: "wsp-opt" }, [
1116
+ h("span", { className: "wsp-opt-text", key: "t" }, workspace.sessionCount > 0 ? `${workspace.title} \xB7 ${workspace.sessionCount} \u4E2A\u4F1A\u8BDD` : workspace.title),
1117
+ workspace.owner === void 0 ? null : h("span", { className: "wsp-opt-owner", key: "o" }, ownerMark(workspace.owner, personaId)),
1118
+ h("span", { className: "wsp-opt-id", key: "i" }, workspace.path.replace(/^\/Users\/[^/]+/, "~"))
1119
+ ]),
1120
+ icon: h(IconFolderOpen16, {})
1121
+ }));
1122
+ }
1123
+ sessionItems(personaId) {
1124
+ const sessions = this.state.targets.sessions ?? [];
1125
+ if (sessions.length === 0) {
1126
+ return [{ type: "label", id: "empty", text: "\u5BBF\u4E3B\u6CA1\u6709\u8FD4\u56DE\u4F1A\u8BDD\u5217\u8868\uFF0C\u8BF7\u7528\u300C\u81EA\u5DF1\u8F93\u5165\u2026\u300D" }];
1127
+ }
1128
+ const optionLabel = (session) => h("span", { className: "wsp-opt" }, [
1129
+ h("span", { className: "wsp-opt-text", key: "t" }, session.title ?? "\uFF08\u8FD8\u6CA1\u6709\u6807\u9898\uFF09"),
1130
+ session.owner === void 0 ? null : h("span", { className: "wsp-opt-owner", key: "o" }, ownerMark(session.owner, personaId)),
1131
+ h("span", { className: "wsp-opt-id", key: "i" }, [
1132
+ session.turns === void 0 ? null : h("span", { className: "wsp-opt-turns", key: "t" }, `${session.turns} \u6B21\u5BF9\u8BDD`),
1133
+ session.turns === void 0 ? null : " \xB7 ",
1134
+ shortSessionId(session.id),
1135
+ session.createdAt === null || session.createdAt === void 0 ? null : ` \xB7 ${since(session.createdAt)}`
1136
+ ])
1137
+ ]);
1138
+ const items = [{ type: "label", id: "recent", text: "\u6700\u8FD1\u7684\u4F1A\u8BDD" }];
1139
+ for (const session of sessions.slice(0, 10)) {
1140
+ items.push({ id: session.id, label: optionLabel(session), icon: h(IconListPenOutline16, {}) });
1141
+ }
1142
+ const groups = /* @__PURE__ */ new Map();
1143
+ for (const session of sessions.slice(10)) {
1144
+ const key = session.cwd ?? "(\u672A\u77E5\u76EE\u5F55)";
1145
+ if (!groups.has(key)) groups.set(key, []);
1146
+ groups.get(key).push(session);
1147
+ }
1148
+ if (groups.size > 0) {
1149
+ const titleOf = (cwd) => (this.state.targets.workspaces ?? []).find((workspace) => workspace.path === cwd)?.title ?? cwd;
1150
+ items.push({ type: "separator", id: "sep" });
1151
+ items.push({ type: "label", id: "older", text: "\u66F4\u65E9\u7684\u4F1A\u8BDD\uFF08\u6309\u76EE\u5F55\uFF09" });
1152
+ for (const [cwd, group] of groups) {
1153
+ items.push({
1154
+ id: `dir:${cwd}`,
1155
+ label: `${titleOf(cwd)}\uFF08${group.length}\uFF09`,
1156
+ icon: h(IconFolderOpen16, {}),
1157
+ submenu: group.slice(0, 40).map((session) => ({ id: session.id, label: optionLabel(session) }))
1158
+ });
1159
+ }
1160
+ }
1161
+ return items;
1162
+ }
1163
+ /** Read a session's recent messages so the user can recognise it. */
1164
+ async openHistory(sessionId) {
1165
+ const api = this.api();
1166
+ if (api === void 0 || typeof api.sessionHistory !== "function") {
1167
+ this.onChange({ status: "", error: "\u8FD9\u4E2A\u7248\u672C\u8FD8\u6CA1\u6709\u4F1A\u8BDD\u5386\u53F2\u63A5\u53E3\uFF1A\u8BF7\u66F4\u65B0\u63D2\u4EF6\u540E\u91CD\u542F dsh web" });
1168
+ return;
1169
+ }
1170
+ this.onChange({ historyBusy: sessionId, history: void 0, error: "" });
1171
+ try {
1172
+ const result = this.unwrap(await api.sessionHistory({ id: sessionId, offset: 0, events: 2e3 }));
1173
+ this.onChange({ historyBusy: void 0, history: { ...result, mode: "head", shown: result?.messages?.length ?? 0 } });
1174
+ } catch (error) {
1175
+ this.onChange({ historyBusy: void 0, history: { id: sessionId, available: false, messages: [], error: String(error && error.message || error) } });
1176
+ }
1177
+ }
1178
+ /** Append the next window of the conversation (only when asked). */
1179
+ async loadMoreHistory() {
1180
+ const current = this.state.history;
1181
+ const api = this.api();
1182
+ if (current === void 0 || api === void 0) return;
1183
+ if (current.nextOffset === null || current.nextOffset === void 0) return;
1184
+ this.onChange({ historyBusy: current.id, error: "" });
1185
+ try {
1186
+ const next = this.unwrap(await api.sessionHistory({ id: current.id, offset: current.nextOffset, events: 2e3 }));
1187
+ this.onChange({
1188
+ historyBusy: void 0,
1189
+ history: {
1190
+ ...next,
1191
+ messages: [...current.messages ?? [], ...next?.messages ?? []],
1192
+ shown: (current.shown ?? 0) + (next?.messages?.length ?? 0)
1193
+ }
1194
+ });
1195
+ } catch (error) {
1196
+ this.onChange({ historyBusy: void 0, error: String(error && error.message || error) });
1197
+ }
1198
+ }
1199
+ /**
1200
+ * Jump to the end: the list is replaced by the last exchange only, so one
1201
+ * click answers "what were we just talking about".
1202
+ */
1203
+ /** Re-read one message in full: one event, one bubble replaced. */
1204
+ async expandMessage(index) {
1205
+ const api = this.api();
1206
+ const current = this.state.history;
1207
+ const message = current?.messages?.[index];
1208
+ if (api === void 0 || message === void 0 || message.at === void 0) return;
1209
+ this.onChange({ historyBusy: `msg:${index}`, error: "" });
1210
+ try {
1211
+ const page = this.unwrap(await api.sessionHistory({ id: current.id, offset: message.at, events: 1, maxChars: 1e5 }));
1212
+ const full = page?.messages?.[0]?.text;
1213
+ if (typeof full !== "string" || full === "") throw new Error("\u8FD9\u6761\u6D88\u606F\u8BFB\u4E0D\u51FA\u5B8C\u6574\u5185\u5BB9");
1214
+ const messages = current.messages.map((item, i) => i === index ? { ...item, text: full, expanded: true, shortText: item.text } : item);
1215
+ this.onChange({ historyBusy: void 0, history: { ...current, messages } });
1216
+ } catch (error) {
1217
+ this.onChange({ historyBusy: void 0, error: String(error && error.message || error) });
1218
+ }
1219
+ }
1220
+ /** Put the shortened text back (no re-read: it was kept). */
1221
+ collapseMessage(index) {
1222
+ const current = this.state.history;
1223
+ const message = current?.messages?.[index];
1224
+ if (message?.shortText === void 0) return;
1225
+ const messages = current.messages.map((item, i) => i === index ? { ...item, text: item.shortText, expanded: false, shortText: void 0 } : item);
1226
+ this.onChange({ history: { ...current, messages } });
1227
+ }
1228
+ /**
1229
+ * Back to the beginning of the conversation. The dialog stays mounted and only
1230
+ * its contents are replaced: clearing `history` would unmount the modal and
1231
+ * mount a new one, which reads as a flash.
1232
+ */
1233
+ async readHeadHistory() {
1234
+ const api = this.api();
1235
+ const current = this.state.history;
1236
+ if (api === void 0 || current === void 0) return;
1237
+ this.onChange({ historyBusy: current.id, error: "" });
1238
+ try {
1239
+ const head = this.unwrap(await api.sessionHistory({ id: current.id, offset: 0, events: 2e3 }));
1240
+ this.onChange({
1241
+ historyBusy: void 0,
1242
+ history: { ...head, mode: "head", shown: head?.messages?.length ?? 0 }
1243
+ });
1244
+ } catch (error) {
1245
+ this.onChange({ historyBusy: void 0, error: String(error && error.message || error) });
1246
+ }
1247
+ }
1248
+ async readTailHistory() {
1249
+ const api = this.api();
1250
+ const current = this.state.history;
1251
+ if (api === void 0 || current === void 0) return;
1252
+ this.onChange({ historyBusy: current.id, error: "" });
1253
+ try {
1254
+ const tail = this.unwrap(await api.sessionHistory({ id: current.id, tail: true, keep: 4, events: 2e3 }));
1255
+ this.onChange({
1256
+ historyBusy: void 0,
1257
+ history: { ...tail, mode: "tail", shown: tail?.messages?.length ?? 0 }
1258
+ });
1259
+ } catch (error) {
1260
+ this.onChange({ historyBusy: void 0, error: String(error && error.message || error) });
1261
+ }
1262
+ }
1263
+ draftFrom(persona) {
1264
+ return {
1265
+ id: persona.id,
1266
+ name: persona.name,
1267
+ enabled: persona.enabled,
1268
+ fallback: persona.fallback === true,
1269
+ mode: persona.mode === "replace" ? "replace" : "append",
1270
+ text: persona.text,
1271
+ targets: (persona.targets || []).map((target) => ({ kind: target.kind, match: target.match, value: target.value }))
1272
+ };
1273
+ }
1274
+ open(persona) {
1275
+ const lost = this.isDirty() && persona.id !== this.state.openId;
1276
+ this.onChange({
1277
+ openId: persona.id,
1278
+ draft: this.draftFrom(persona),
1279
+ pane: "edit",
1280
+ confirmDelete: void 0,
1281
+ proposal: "",
1282
+ instruction: "",
1283
+ error: "",
1284
+ status: lost ? "\u4E0A\u4E00\u6761\u4EBA\u8BBE\u7684\u6539\u52A8\u6CA1\u6709\u4FDD\u5B58" : ""
1285
+ });
1286
+ }
1287
+ close() {
1288
+ const lost = this.isDirty();
1289
+ this.onChange({
1290
+ openId: void 0,
1291
+ draft: void 0,
1292
+ proposal: "",
1293
+ confirmDelete: void 0,
1294
+ status: lost ? "\u6539\u52A8\u6CA1\u6709\u4FDD\u5B58" : this.state.status
1295
+ });
1296
+ }
1297
+ /**
1298
+ * Drop the dragged persona at the boundary the pointer picked. `from < to`
1299
+ * means the item leaves a slot behind itself, hence the `- 1`.
1300
+ */
1301
+ async drop() {
1302
+ const id = this.state.dragId;
1303
+ const boundary = this.state.dropIndex;
1304
+ const api = this.api();
1305
+ this.onChange({ dragId: void 0, dropIndex: void 0 });
1306
+ if (id === void 0 || boundary === void 0 || api === void 0) return;
1307
+ const personas = this.state.view?.personas ?? [];
1308
+ const from = personas.findIndex((persona) => persona.id === id);
1309
+ if (from === -1) return;
1310
+ const to = from < boundary ? boundary - 1 : boundary;
1311
+ if (to === from) return;
1312
+ this.onChange({ busy: "reorder" });
1313
+ try {
1314
+ const view = this.unwrap(await api.reorderPersona({ id, toIndex: to }));
1315
+ this.onChange({ view, busy: "", status: "\u987A\u5E8F\u5DF2\u66F4\u65B0" });
1316
+ } catch (error) {
1317
+ this.onChange({ busy: "", error: String(error && error.message || error) });
1318
+ }
1319
+ }
1320
+ /** Draft vs saved, ignoring UI-only keys (`custom`, `invalid`). */
1321
+ isDirty() {
1322
+ const draft = this.state.draft;
1323
+ const saved = (this.state.view?.personas ?? []).find((persona) => persona.id === this.state.openId);
1324
+ if (draft === void 0 || saved === void 0) return false;
1325
+ const shape = (value) => JSON.stringify({
1326
+ n: value.name,
1327
+ e: value.enabled,
1328
+ f: value.fallback === true,
1329
+ m: value.mode ?? "append",
1330
+ t: value.text,
1331
+ g: (value.targets ?? []).map((target) => [target.kind, target.match, target.value])
1332
+ });
1333
+ return shape(draft) !== shape(saved);
1334
+ }
1335
+ /** Persist one interface preference. The two position switches apply on the next page load. */
1336
+ setPref(patch) {
1337
+ persistPrefs(this.api(), (result) => this.unwrap(result), this.state.prefs ?? DEFAULT_PREFS, patch, (update) => this.onChange(update));
1338
+ }
1339
+ /** Debounced autosave, armed only while the preference is on. */
1340
+ scheduleAutosave() {
1341
+ if (this.state.prefs?.autosave !== true) return;
1342
+ clearTimeout(this.autosaveTimer);
1343
+ this.autosaveTimer = setTimeout(() => {
1344
+ this.autosaveTimer = void 0;
1345
+ this.onChange({ status: "\u6B63\u5728\u81EA\u52A8\u4FDD\u5B58\u2026" });
1346
+ void this.save();
1347
+ }, 1200);
1348
+ }
1349
+ /**
1350
+ * @param patch - the fields to merge into the draft.
1351
+ * @param options.autosave - false for edits that are only one step of a longer choice
1352
+ * (picking a workspace/session, adding or removing a rule row). Those must not save on
1353
+ * their own: the row would be stored half-filled, and a queued autosave from an earlier
1354
+ * edit would land in the middle of the picking.
1355
+ */
1356
+ patchDraft(patch, options = {}) {
1357
+ const draft = this.state.draft;
1358
+ if (draft === void 0) return;
1359
+ this.onChange({ draft: Object.assign({}, draft, patch) });
1360
+ if (options.autosave === false) {
1361
+ clearTimeout(this.autosaveTimer);
1362
+ this.autosaveTimer = void 0;
1363
+ } else {
1364
+ this.scheduleAutosave();
1365
+ }
1366
+ }
1367
+ patchTarget(index, patch) {
1368
+ const draft = this.state.draft;
1369
+ if (draft === void 0) return;
1370
+ const targets = draft.targets.map((target, i) => i === index ? Object.assign({}, target, patch) : target);
1371
+ this.patchDraft({ targets }, { autosave: false });
1372
+ }
1373
+ addTarget() {
1374
+ const draft = this.state.draft;
1375
+ if (draft === void 0) return;
1376
+ this.patchDraft({ targets: [...draft.targets, { kind: "workspace", match: "exact", value: "" }] }, { autosave: false });
1377
+ }
1378
+ removeTarget(index) {
1379
+ const draft = this.state.draft;
1380
+ if (draft === void 0) return;
1381
+ this.patchDraft({ targets: draft.targets.filter((_target, i) => i !== index) }, { autosave: false });
1382
+ }
1383
+ async create() {
1384
+ const api = this.api();
1385
+ if (api === void 0) return;
1386
+ const before = (this.state.view?.personas || []).map((persona) => persona.id);
1387
+ this.onChange({ busy: "create", error: "", status: "" });
1388
+ try {
1389
+ const view = this.unwrap(await api.savePersona({ name: "\u65B0\u4EBA\u8BBE", enabled: false, text: "", targets: [] }));
1390
+ const created = (view.personas || []).find((item) => !before.includes(item.id));
1391
+ this.onChange({ view, busy: "" });
1392
+ if (created !== void 0) {
1393
+ this.open(created);
1394
+ this.onChange({ status: "\u5DF2\u65B0\u5EFA\uFF08\u9ED8\u8BA4\u505C\u7528\uFF09\uFF1A\u9009\u597D\u4F4D\u7F6E\u540E\u6253\u5F00\u5F00\u5173\u5C31\u4F1A\u751F\u6548" });
1395
+ }
1396
+ } catch (error) {
1397
+ this.onChange({ busy: "", error: String(error && error.message || error) });
1398
+ }
1399
+ }
1400
+ /** Flip one persona on or off straight from the list, without opening its card. */
1401
+ async toggleEnabled(persona, checked) {
1402
+ const api = this.api();
1403
+ if (api === void 0) return;
1404
+ this.onChange({ busy: "save" });
1405
+ try {
1406
+ const view = this.unwrap(await api.savePersona({
1407
+ id: persona.id,
1408
+ name: persona.name,
1409
+ enabled: checked,
1410
+ fallback: persona.fallback === true,
1411
+ mode: persona.mode,
1412
+ text: persona.text,
1413
+ targets: persona.targets ?? []
1414
+ }));
1415
+ const openDraft = this.state.openId === persona.id && this.state.draft !== void 0 && this.state.draft !== null ? { ...this.state.draft, enabled: checked } : void 0;
1416
+ this.onChange({
1417
+ view,
1418
+ busy: "",
1419
+ status: checked ? `\u5DF2\u542F\u7528\u300C${persona.name}\u300D` : `\u5DF2\u505C\u7528\u300C${persona.name}\u300D`,
1420
+ ...openDraft === void 0 ? {} : { draft: openDraft }
1421
+ });
1422
+ } catch (error) {
1423
+ this.onChange({ busy: "", status: `\u4FDD\u5B58\u5931\u8D25\uFF1A${String(error && error.message || error)}` });
1424
+ }
1425
+ }
1426
+ async save() {
1427
+ const api = this.api();
1428
+ const draft = this.state.draft;
1429
+ if (api === void 0 || draft === void 0) return;
1430
+ this.onChange({ busy: "save", error: "", status: "" });
1431
+ try {
1432
+ const view = this.unwrap(await api.savePersona({
1433
+ id: draft.id,
1434
+ name: draft.name,
1435
+ enabled: draft.enabled,
1436
+ fallback: draft.fallback === true,
1437
+ mode: draft.mode,
1438
+ text: draft.text,
1439
+ targets: draft.targets
1440
+ }));
1441
+ const persona = (view.personas || []).find((item) => item.id === draft.id);
1442
+ this.onChange({
1443
+ view,
1444
+ busy: "",
1445
+ draft: persona === void 0 ? void 0 : this.draftFrom(persona),
1446
+ status: view.notice ?? "\u5DF2\u4FDD\u5B58\uFF0C\u4E0B\u4E00\u6761\u6D88\u606F\u5C31\u4F1A\u7528\u4E0A"
1447
+ });
1448
+ } catch (error) {
1449
+ this.onChange({ busy: "", error: String(error && error.message || error) });
1450
+ }
1451
+ }
1452
+ async remove(id) {
1453
+ const api = this.api();
1454
+ if (api === void 0) return;
1455
+ this.onChange({ busy: "remove", error: "", status: "" });
1456
+ try {
1457
+ const view = this.unwrap(await api.deletePersona({ id }));
1458
+ this.onChange({ view, busy: "", openId: void 0, draft: void 0, confirmDelete: void 0, status: "\u4EBA\u8BBE\u5DF2\u5220\u9664" });
1459
+ } catch (error) {
1460
+ this.onChange({ busy: "", error: String(error && error.message || error) });
1461
+ }
1462
+ }
1463
+ async move(id, delta) {
1464
+ const api = this.api();
1465
+ if (api === void 0) return;
1466
+ this.onChange({ busy: "move", error: "", status: "" });
1467
+ try {
1468
+ const view = this.unwrap(await api.movePersona({ id, delta }));
1469
+ this.onChange({ view, busy: "", status: delta < 0 ? "\u5DF2\u4E0A\u79FB" : "\u5DF2\u4E0B\u79FB" });
1470
+ } catch (error) {
1471
+ this.onChange({ busy: "", error: String(error && error.message || error) });
1472
+ }
1473
+ }
1474
+ async duplicate(id) {
1475
+ const api = this.api();
1476
+ if (api === void 0) return;
1477
+ this.onChange({ busy: "duplicate", error: "", status: "" });
1478
+ try {
1479
+ const view = this.unwrap(await api.duplicatePersona({ id }));
1480
+ this.onChange({ view, busy: "", status: "\u5DF2\u590D\u5236\uFF08\u526F\u672C\u9ED8\u8BA4\u505C\u7528\uFF09" });
1481
+ } catch (error) {
1482
+ this.onChange({ busy: "", error: String(error && error.message || error) });
1483
+ }
1484
+ }
1485
+ /** Candidate workspaces and sessions for the dropdowns (never fatal). */
1486
+ async loadTargets() {
1487
+ const api = this.api();
1488
+ if (api === void 0 || typeof api.listTargets !== "function") return;
1489
+ try {
1490
+ const result = this.unwrap(await api.listTargets());
1491
+ this.onChange({
1492
+ targets: {
1493
+ workspaces: Array.isArray(result?.workspaces) ? result.workspaces : [],
1494
+ sessions: Array.isArray(result?.sessions) ? result.sessions : [],
1495
+ hidden: result?.hidden ?? { subagents: 0, archived: 0, blank: 0 }
1496
+ }
1497
+ });
1498
+ } catch {
1499
+ }
1500
+ }
1501
+ /** Switching a row's kind clears its value: a path is not a session id. */
1502
+ setKind(index, kind) {
1503
+ this.patchTarget(index, { kind, match: "exact", value: "", custom: false });
1504
+ }
1505
+ pickTarget(index, value) {
1506
+ if (value === "__custom__") {
1507
+ this.patchTarget(index, { value: "", custom: true });
1508
+ return;
1509
+ }
1510
+ this.patchTarget(index, { value, custom: false });
1511
+ }
1512
+ async tune() {
1513
+ const api = this.api();
1514
+ const draft = this.state.draft;
1515
+ if (api === void 0 || draft === void 0) return;
1516
+ const parts = this.state.model.split("/");
1517
+ this.onChange({ busy: "tune", error: "", status: "", proposal: "" });
1518
+ try {
1519
+ const result = this.unwrap(await api.tunePersona({
1520
+ mode: this.state.tuneMode,
1521
+ instruction: this.state.instruction,
1522
+ text: draft.text,
1523
+ ...this.state.model === "" ? {} : { provider: parts[0], model: parts.slice(1).join("/") }
1524
+ }));
1525
+ this.onChange({
1526
+ busy: "",
1527
+ proposal: result && result.text || "",
1528
+ status: `\u5DF2\u751F\u6210\u5EFA\u8BAE\uFF08${result && result.provider || ""}/${result && result.model || ""}\uFF09`
1529
+ });
1530
+ } catch (error) {
1531
+ this.onChange({ busy: "", error: String(error && error.message || error) });
1532
+ }
1533
+ }
1534
+ render() {
1535
+ try {
1536
+ return this.renderBody();
1537
+ } catch (error) {
1538
+ return h(
1539
+ "div",
1540
+ { className: "wsp-status wsp-status-err", "data-plugin": SCOPE },
1541
+ `Agent \u4EBA\u8BBE\u9875\u6E32\u67D3\u5F02\u5E38\uFF1A${String(error && error.message || error)}`
1542
+ );
1543
+ }
1544
+ }
1545
+ renderBody() {
1546
+ verifyElements();
1547
+ const state = this.state;
1548
+ const view = state.view;
1549
+ const rootProps = { "data-plugin": SCOPE, ref: this.rootRef };
1550
+ const busy = state.busy !== "";
1551
+ const personas = view?.personas ?? [];
1552
+ const sessionById = (id) => (state.targets.sessions ?? []).find((item) => item.id === id);
1553
+ const header = h("div", { className: "wsp-head", key: "head" }, [
1554
+ h("h2", { className: "wsp-title", key: "t" }, "Agent \u4EBA\u8BBE"),
1555
+ h("p", { className: "wsp-sub", key: "s" }, DESCRIPTION),
1556
+ h("div", { className: "wsp-meta", key: "m" }, view === void 0 ? ["\u6B63\u5728\u8BFB\u53D6\u4EBA\u8BBE\u2026"] : [
1557
+ h(Badge, { key: "total" }, `${view.counts.total} \u6761\u4EBA\u8BBE`),
1558
+ h(Badge, { key: "enabled", tone: view.counts.enabled > 0 ? "on" : "off" }, `${view.counts.enabled} \u6761\u5728\u7528`),
1559
+ h("span", { className: "wsp-spacer", key: "sp" }),
1560
+ "\u4FDD\u5B58\u5728 ",
1561
+ h("code", { key: "c" }, view.storePath)
1562
+ ])
1563
+ ]);
1564
+ if (view === void 0) {
1565
+ const status2 = state.error === "" ? h("div", { className: "wsp-status", key: "st" }, "\u6B63\u5728\u8BFB\u53D6\u4EBA\u8BBE\u2026") : h("div", { className: "wsp-status wsp-status-err", key: "st", "aria-live": "polite" }, state.error);
1566
+ const skeleton = h(
1567
+ "div",
1568
+ { className: "wsp-cards", key: "cards", "aria-hidden": "true" },
1569
+ [0, 1].map((i) => h(
1570
+ "div",
1571
+ { className: "wsp-card", key: `s${i}` },
1572
+ h("div", { className: "wsp-row-head" }, [
1573
+ h("span", { className: "wsp-skeleton wsp-skeleton-chevron", key: "c" }),
1574
+ h("span", { className: "wsp-skeleton wsp-skeleton-name", key: "n" }),
1575
+ h("span", { className: "wsp-skeleton wsp-skeleton-chip", key: "g" })
1576
+ ])
1577
+ ))
1578
+ );
1579
+ return h("div", rootProps, [header, skeleton, status2]);
1580
+ }
1581
+ const modelOptions = state.models.length === 0 ? [h("option", { key: "none", value: "" }, "\u9ED8\u8BA4\u6A21\u578B")] : state.models.map((item) => h("option", { key: `${item.provider}/${item.model}`, value: `${item.provider}/${item.model}` }, item.name));
1582
+ const cardHead = (persona, index) => {
1583
+ const isOpen = state.openId === persona.id;
1584
+ const menuKey = `card:${persona.id}`;
1585
+ const toggle = () => isOpen ? this.close() : this.open(persona);
1586
+ return h("div", {
1587
+ key: `h${persona.id}`,
1588
+ className: isOpen ? "wsp-row-head wsp-row-open wsp-row-click" : "wsp-row-head wsp-row-click wsp-row-head-switch",
1589
+ role: isOpen ? void 0 : "button",
1590
+ "aria-expanded": isOpen,
1591
+ tabIndex: isOpen ? void 0 : 0,
1592
+ onClick: toggle,
1593
+ onKeyDown: (event) => {
1594
+ if (event.key === "Enter" || event.key === " ") {
1595
+ event.preventDefault();
1596
+ toggle();
1597
+ }
1598
+ }
1599
+ }, [
1600
+ h("button", {
1601
+ key: "chev",
1602
+ type: "button",
1603
+ draggable: true,
1604
+ className: "wsp-chevron",
1605
+ title: "\u70B9\u51FB\u5C55\u5F00 / \u6536\u8D77\uFF1B\u6309\u4F4F\u62D6\u52A8\u53EF\u8C03\u6574\u8FD9\u6761\u4EBA\u8BBE\u7684\u987A\u5E8F",
1606
+ "aria-label": isOpen ? `\u6536\u8D77 ${persona.name}` : `\u5C55\u5F00 ${persona.name}`,
1607
+ "aria-expanded": isOpen,
1608
+ onClick: (event) => {
1609
+ event.stopPropagation();
1610
+ toggle();
1611
+ },
1612
+ onDragStart: (event) => {
1613
+ this.onChange({ dragId: persona.id, dropIndex: void 0 });
1614
+ if (event.dataTransfer) {
1615
+ event.dataTransfer.effectAllowed = "move";
1616
+ try {
1617
+ event.dataTransfer.setData("text/plain", persona.id);
1618
+ } catch {
1619
+ }
1620
+ }
1621
+ },
1622
+ onDragEnd: () => this.onChange({ dragId: void 0, dropIndex: void 0 })
1623
+ }, h(isOpen ? IconChevronDownOutline14 : IconChevronRightOutline14, {})),
1624
+ h(Dot, { key: "d", state: dotState(persona) }),
1625
+ h("span", { className: "wsp-pname", key: "n", title: persona.name }, persona.name),
1626
+ h("span", { className: "wsp-spacer", key: "sp" }),
1627
+ h("span", { className: "wsp-idx", key: "i", title: `\u4F18\u5148\u7EA7\u7B2C ${index + 1} \u4F4D\uFF08\u8D8A\u9760\u4E0A\u8D8A\u4F18\u5148\uFF09` }, String(index + 1)),
1628
+ h(Badge, {
1629
+ key: "b1",
1630
+ tone: persona.enabled ? persona.text.trim() === "" ? "warn" : "on" : "off"
1631
+ }, persona.enabled ? persona.text.trim() === "" ? "\u5728\u7528\xB7\u6B63\u6587\u7A7A" : "\u5728\u7528" : "\u5DF2\u505C\u7528"),
1632
+ ...persona.fallback === true ? [h(Badge, { key: "fb", tone: "on", title: "\u672A\u547D\u4E2D\u4EFB\u4F55\u4F4D\u7F6E\u7684\u4F1A\u8BDD\u90FD\u7528\u8FD9\u6761\u4EBA\u8BBE" }, "\u9ED8\u8BA4")] : [],
1633
+ ...conflicts.has(persona.id) ? [h(Badge, { key: "conflict", tone: "warn", title: (conflicts.get(persona.id) ?? []).join("\n") }, "\u53EF\u80FD\u88AB\u8986\u76D6")] : [],
1634
+ ...state.openId === persona.id && this.isDirty() ? [h("button", {
1635
+ key: "dirty",
1636
+ type: "button",
1637
+ className: "wsp-save-chip",
1638
+ title: "\u7F16\u8F91\u533A\u4E0E\u5DF2\u4FDD\u5B58\u7684\u5185\u5BB9\u4E0D\u4E00\u81F4\uFF1A\u70B9\u8FD9\u91CC\u7ACB\u5373\u4FDD\u5B58",
1639
+ disabled: state.busy !== "",
1640
+ onClick: (event) => {
1641
+ event.stopPropagation();
1642
+ void this.save();
1643
+ }
1644
+ }, state.busy === "save" ? "\u4FDD\u5B58\u4E2D\u2026" : "\u672A\u4FDD\u5B58 \xB7 \u70B9\u51FB\u4FDD\u5B58")] : [],
1645
+ h(
1646
+ "span",
1647
+ { key: "on", className: "wsp-row-switch", onClick: (event) => event.stopPropagation() },
1648
+ h(Switch, {
1649
+ checked: persona.enabled,
1650
+ disabled: state.busy !== "",
1651
+ label: persona.enabled ? "\u5DF2\u542F\u7528\uFF0C\u70B9\u51FB\u505C\u7528" : "\u5DF2\u505C\u7528\uFF0C\u70B9\u51FB\u542F\u7528",
1652
+ onChange: (checked) => void this.toggleEnabled(persona, checked)
1653
+ })
1654
+ ),
1655
+ h("span", { className: "wsp-caption", key: "c" }, `${persona.chars} \u5B57`),
1656
+ h(Menu, {
1657
+ key: "more",
1658
+ open: state.menu === menuKey,
1659
+ portal: true,
1660
+ align: "end",
1661
+ items: [
1662
+ { id: "up", label: "\u4E0A\u79FB\uFF08\u5177\u4F53\u5EA6\u76F8\u540C\u65F6\u66F4\u4F18\u5148\uFF09", icon: h(IconChevronUpOutline14, {}), disabled: index === 0 },
1663
+ { id: "down", label: "\u4E0B\u79FB\uFF08\u5177\u4F53\u5EA6\u76F8\u540C\u65F6\u66F4\u9760\u540E\uFF09", icon: h(IconChevronDownOutline14, {}), disabled: index === personas.length - 1 },
1664
+ { type: "separator", id: "sep-order" },
1665
+ { id: "duplicate", label: "\u590D\u5236\u4E00\u4EFD\uFF08\u526F\u672C\u9ED8\u8BA4\u505C\u7528\uFF09", icon: h(IconEditOutline16, {}) },
1666
+ { type: "separator", id: "sep" },
1667
+ { id: "delete", label: "\u5220\u9664\u2026", icon: h(IconTrashOutline16, {}), danger: true }
1668
+ ],
1669
+ onSelect: (id) => {
1670
+ this.onChange({ menu: void 0 });
1671
+ if (id === "up") void this.move(persona.id, -1);
1672
+ if (id === "down") void this.move(persona.id, 1);
1673
+ if (id === "duplicate") void this.duplicate(persona.id);
1674
+ if (id === "delete") {
1675
+ this.onChange({ confirmDelete: persona.id, menu: void 0 });
1676
+ }
1677
+ },
1678
+ onClose: () => this.onChange({ menu: void 0 }),
1679
+ anchor: h("button", {
1680
+ key: "a",
1681
+ type: "button",
1682
+ className: "wsp-icon-btn",
1683
+ title: "\u66F4\u591A\u64CD\u4F5C",
1684
+ "aria-label": `\u66F4\u591A\u64CD\u4F5C ${persona.name}`,
1685
+ "aria-haspopup": "menu",
1686
+ onClick: (event) => {
1687
+ event.stopPropagation();
1688
+ this.toggleMenu(menuKey);
1689
+ }
1690
+ }, h(IconEllipsisOutline16, {}))
1691
+ })
1692
+ ]);
1693
+ };
1694
+ const targetLabel = (target) => {
1695
+ if (target.invalid) return "\u6CA1\u586B\u5339\u914D\u503C";
1696
+ const exact = target.match === "exact";
1697
+ if (target.kind === "workspace") {
1698
+ const workspace = (state.targets.workspaces ?? []).find((item) => item.path === target.value);
1699
+ if (exact && workspace !== void 0) return workspace.title;
1700
+ return `${MATCH_LABEL[target.match] ?? target.match} ${target.value}`;
1701
+ }
1702
+ const session = (state.targets.sessions ?? []).find((item) => item.id === target.value);
1703
+ if (exact && session !== void 0) return `${session.title ?? "\uFF08\u6CA1\u6709\u6807\u9898\uFF09"} \xB7 ${shortSessionId(target.value)}`;
1704
+ return `${MATCH_LABEL[target.match] ?? target.match} ${shortSessionId(target.value)}`;
1705
+ };
1706
+ const scopeLine = (persona, kind, text) => {
1707
+ const rows = (persona.targets ?? []).filter((target) => target.kind === kind);
1708
+ if (rows.length === 0) return null;
1709
+ return h("div", { className: "wsp-scope-line", key: kind }, [
1710
+ h("span", { className: "wsp-scope-label", key: "l" }, text),
1711
+ ...rows.map((target, i) => h("span", {
1712
+ key: `t${i}`,
1713
+ className: target.invalid ? "wsp-chip wsp-warn" : "wsp-chip",
1714
+ title: target.invalid ? "\u8FD9\u6761\u89C4\u5219\u6CA1\u6709\u5339\u914D\u503C\uFF0C\u4E0D\u4F1A\u751F\u6548" : `${MATCH_LABEL[target.match] ?? target.match} ${target.value}`
1715
+ }, targetLabel(target)))
1716
+ ]);
1717
+ };
1718
+ const scopeLines = (persona) => h("div", { className: "wsp-scope-lines", key: "sl" }, (persona.targets ?? []).length === 0 ? [h("div", { className: "wsp-scope-line", key: "default" }, [
1719
+ h("span", { className: "wsp-scope-label", key: "l" }, "\u8303\u56F4"),
1720
+ h("span", { className: persona.fallback === true ? "wsp-chip wsp-chip-on" : "wsp-chip wsp-chip-warn", key: "c" }, persona.fallback === true ? "\u9ED8\u8BA4\u4EBA\u8BBE\uFF1A\u672A\u547D\u4E2D\u4F4D\u7F6E\u7684\u4F1A\u8BDD\u90FD\u7528\u5B83" : "\u672A\u8BBE\u7F6E\u4F4D\u7F6E\uFF1A\u4E0D\u751F\u6548\uFF0C\u8BE5\u4F1A\u8BDD\u4ECD\u7528 DSH \u81EA\u5E26\u7684 prompt")
1721
+ ])] : [scopeLine(persona, "workspace", "\u5DE5\u4F5C\u533A"), scopeLine(persona, "sessionId", "\u4F1A\u8BDD")]);
1722
+ const draft = state.draft;
1723
+ const cardBody = (persona) => {
1724
+ if (state.openId !== persona.id || draft === void 0) return null;
1725
+ const targets = draft.targets || [];
1726
+ const knownValues = /* @__PURE__ */ new Set([
1727
+ ...(state.targets.workspaces ?? []).map((item) => item.path),
1728
+ ...(state.targets.sessions ?? []).map((item) => item.id)
1729
+ ]);
1730
+ const titleOfPath = (path) => (state.targets.workspaces ?? []).find((item) => item.path === path)?.title ?? path;
1731
+ const sessionOf = (id) => (state.targets.sessions ?? []).find((item) => item.id === id);
1732
+ const targetRows = targets.map((target, index) => {
1733
+ const key = `${persona.id}:${index}`;
1734
+ const isCustom = target.custom === true || target.value !== "" && !knownValues.has(target.value);
1735
+ const picked = target.kind === "workspace" ? target.value === "" ? void 0 : { label: titleOfPath(target.value) } : target.value === "" ? void 0 : { label: sessionOf(target.value)?.title ?? target.value };
1736
+ const session = target.kind === "sessionId" && target.value !== "" ? sessionOf(target.value) : void 0;
1737
+ const context = isCustom || target.value === "" ? void 0 : target.kind === "workspace" ? target.value : [session?.cwd ?? null, session?.createdAt === null || session?.createdAt === void 0 ? null : since(session.createdAt), target.value].filter(Boolean).join(" \xB7 ");
1738
+ return h("div", { className: "wsp-scope-row", key: `t${index}` }, [
1739
+ h("div", { className: "wsp-kind", key: "k", role: "group", "aria-label": "\u9009\u62E9\u7C7B\u578B" }, [
1740
+ h(Pill, { key: "w", active: target.kind === "workspace", onClick: () => this.setKind(index, "workspace") }, "\u5DE5\u4F5C\u533A"),
1741
+ h(Pill, { key: "s", active: target.kind === "sessionId", onClick: () => this.setKind(index, "sessionId") }, "\u67D0\u4E2A\u4F1A\u8BDD")
1742
+ ]),
1743
+ h(Menu, {
1744
+ key: "p",
1745
+ open: state.picker === key,
1746
+ portal: true,
1747
+ items: target.kind === "workspace" ? this.workspaceItems(persona.id) : this.sessionItems(persona.id),
1748
+ className: "wsp-menu",
1749
+ ...isCustom || target.value === "" ? {} : { selectedId: target.value },
1750
+ footer: [
1751
+ ...target.kind === "sessionId" && target.value !== "" ? [{
1752
+ id: "__history__",
1753
+ icon: h(IconBrowseOutline16, {}),
1754
+ label: h(
1755
+ Tooltip,
1756
+ { label: "\u6253\u5F00\u8FD9\u4E2A\u4F1A\u8BDD\u6700\u8FD1\u804A\u8FC7\u7684\u5185\u5BB9\uFF08\u53EA\u8BFB\uFF0C\u4E0D\u4F1A\u6539\u52A8\u4EFB\u4F55\u4E1C\u897F\uFF09" },
1757
+ h("span", { className: "wsp-menu-tip", title: "\u6253\u5F00\u8FD9\u4E2A\u4F1A\u8BDD\u6700\u8FD1\u804A\u8FC7\u7684\u5185\u5BB9\uFF08\u53EA\u8BFB\uFF09" }, "\u67E5\u770B\u8FD9\u6761\u4F1A\u8BDD\u7684\u5BF9\u8BDD\u8BE6\u60C5")
1758
+ )
1759
+ }] : [],
1760
+ this.customItem()
1761
+ ],
1762
+ onSelect: (id) => {
1763
+ this.onChange({ picker: void 0 });
1764
+ if (id === "__history__") {
1765
+ void this.openHistory(target.value);
1766
+ return;
1767
+ }
1768
+ this.pickTarget(index, id);
1769
+ },
1770
+ onClose: () => this.onChange({ picker: void 0 }),
1771
+ anchor: h("button", {
1772
+ type: "button",
1773
+ className: isCustom || target.value === "" ? "wsp-picker wsp-picker-empty" : "wsp-picker",
1774
+ "aria-haspopup": "menu",
1775
+ "aria-expanded": state.picker === key,
1776
+ onClick: () => this.togglePicker(key)
1777
+ }, [
1778
+ h(isCustom ? IconEditOutline16 : target.kind === "workspace" ? IconFolderOpen16 : IconListPenOutline16, { key: "i" }),
1779
+ h(
1780
+ "span",
1781
+ { className: "wsp-picker-label", key: "l", title: isCustom ? void 0 : picked?.label },
1782
+ isCustom ? "\u81EA\u5DF1\u8F93\u5165\u2026" : picked?.label ?? (target.kind === "workspace" ? "\u9009\u62E9\u4E00\u4E2A\u5DE5\u4F5C\u533A\u2026" : "\u9009\u62E9\u4E00\u6761\u4F1A\u8BDD\u2026")
1783
+ ),
1784
+ h(IconChevronDownOutline14, { key: "c" })
1785
+ ])
1786
+ }),
1787
+ isCustom ? h("input", {
1788
+ key: "v",
1789
+ className: targetValid(target) ? "wsp-input wsp-input-mono wsp-scope-value" : "wsp-input wsp-input-mono wsp-scope-value wsp-input-invalid",
1790
+ value: target.value,
1791
+ spellCheck: false,
1792
+ "aria-label": "\u5339\u914D\u503C",
1793
+ placeholder: target.kind === "workspace" ? target.match === "exact" ? "/Users/you/code/my-project" : target.match === "regex" ? "^/Users/you/code/" : "/Users/you/code" : target.match === "exact" ? "session-id" : target.match === "regex" ? "^im-bot-.*-g[0-9]+$" : "im-bot-",
1794
+ onChange: (event) => this.patchTarget(index, { value: event.target.value, custom: true })
1795
+ }) : null,
1796
+ isCustom ? h("select", {
1797
+ key: "m",
1798
+ className: "wsp-select wsp-scope-match",
1799
+ value: target.match,
1800
+ "aria-label": "\u5339\u914D\u65B9\u5F0F",
1801
+ onChange: (event) => this.patchTarget(index, { match: event.target.value })
1802
+ }, (view.targetMatches || []).map((mode) => h("option", { key: mode, value: mode }, MATCH_LABEL[mode] ?? mode))) : null,
1803
+ h("button", {
1804
+ key: "x",
1805
+ type: "button",
1806
+ className: "wsp-icon-btn",
1807
+ title: "\u5220\u6389\u8FD9\u4E00\u884C",
1808
+ "aria-label": `\u5220\u6389\u7B2C ${index + 1} \u884C`,
1809
+ onClick: () => this.removeTarget(index)
1810
+ }, h(IconCloseOutline16, {}))
1811
+ ], context === void 0 ? null : h("div", { className: "wsp-scope-context", key: "c" }, context));
1812
+ });
1813
+ const tuneBlock = h(DisclosureRow, {
1814
+ key: "tune",
1815
+ icon: h(IconEnhanceOutline16, {}),
1816
+ title: "\u7528 AI \u6539\u4E00\u7248",
1817
+ open: state.tuneOpen,
1818
+ expandable: true,
1819
+ onToggle: () => this.onChange({ tuneOpen: !state.tuneOpen }),
1820
+ expandOnRowClick: true,
1821
+ keepContentWhenOpen: true
1822
+ }, state.tuneOpen ? h("div", { className: "wsp-tune-fields", style: { paddingTop: "10px" } }, [
1823
+ h("div", { className: "wsp-row-head", key: "r", style: { padding: 0, minHeight: "auto", gap: "8px" } }, [
1824
+ h("span", { className: "wsp-label", key: "l1" }, "\u6A21\u5F0F"),
1825
+ h("select", {
1826
+ key: "mode",
1827
+ className: "wsp-select",
1828
+ value: state.tuneMode,
1829
+ "aria-label": "\u8C03\u4F18\u6A21\u5F0F",
1830
+ onChange: (event) => this.onChange({ tuneMode: event.target.value })
1831
+ }, MODES.map((item) => h("option", { key: item.value, value: item.value }, item.label))),
1832
+ h("span", { className: "wsp-label", key: "l2" }, "\u6A21\u578B"),
1833
+ h("select", {
1834
+ key: "model",
1835
+ className: "wsp-select",
1836
+ value: state.model,
1837
+ "aria-label": "\u8C03\u4F18\u4F7F\u7528\u7684\u6A21\u578B",
1838
+ onChange: (event) => this.onChange({ model: event.target.value })
1839
+ }, modelOptions),
1840
+ h("span", { className: "wsp-spacer", key: "sp" }),
1841
+ h(Button, {
1842
+ key: "go",
1843
+ variant: "outline",
1844
+ size: "sm",
1845
+ disabled: busy,
1846
+ icon: h(IconEnhanceOutline16, {}),
1847
+ onClick: () => void this.tune()
1848
+ }, state.busy === "tune" ? "\u751F\u6210\u4E2D\u2026" : "\u751F\u6210\u5EFA\u8BAE")
1849
+ ]),
1850
+ h(Input, {
1851
+ key: "ins",
1852
+ value: state.instruction,
1853
+ placeholder: "\u8865\u5145\u8981\u6C42\uFF08\u53EF\u9009\uFF09\uFF1A\u4F8B\u5982\u300C\u5148\u7ED9\u7ED3\u8BBA\u518D\u7ED9\u8BC1\u636E\u300D\u300C\u52A0\u5165\u7981\u6B62\u5916\u4F20\u5185\u90E8\u6570\u636E\u7684\u7EA6\u675F\u300D",
1854
+ "aria-label": "\u5BF9 AI \u7684\u8865\u5145\u8981\u6C42",
1855
+ onChange: (event) => this.onChange({ instruction: event.target.value })
1856
+ }),
1857
+ h("span", { className: "wsp-hint", key: "h" }, `\u4F7F\u7528\u5F53\u524D\u6A21\u578B ${state.model === "" ? "\uFF08DSH \u9ED8\u8BA4\uFF09" : state.model} \u751F\u6210\u4E00\u7248\u5EFA\u8BAE\uFF0C\u53EA\u663E\u793A\u5728\u4E0B\u9762\uFF0C\u4E0D\u4F1A\u81EA\u52A8\u4FDD\u5B58\u3002`),
1858
+ state.proposal === "" ? null : h("div", { className: "wsp-proposal", key: "prop" }, [
1859
+ h("div", { className: "wsp-row-head", key: "ph", style: { padding: 0, minHeight: "auto" } }, [
1860
+ h("span", { className: "wsp-label", key: "l" }, "AI \u5EFA\u8BAE\uFF08\u8FD8\u6CA1\u4FDD\u5B58\uFF09"),
1861
+ h("span", { className: "wsp-spacer", key: "sp" }),
1862
+ h(Button, { key: "use", variant: "primary", size: "sm", onClick: () => this.onChange({ draft: Object.assign({}, draft, { text: state.proposal }), proposal: "", status: "\u5EFA\u8BAE\u5DF2\u66FF\u6362\u5230\u7F16\u8F91\u533A" }) }, "\u91C7\u7528"),
1863
+ h(Button, { key: "app", variant: "ghost", size: "sm", onClick: () => this.onChange({ draft: Object.assign({}, draft, { text: `${draft.text.trim()}
1864
+
1865
+ ${state.proposal.trim()}
1866
+ ` }), proposal: "", status: "\u5EFA\u8BAE\u5DF2\u8FFD\u52A0\u5230\u7F16\u8F91\u533A" }) }, "\u8FFD\u52A0"),
1867
+ h(Button, { key: "drop", variant: "ghost", size: "sm", onClick: () => this.onChange({ proposal: "" }) }, "\u653E\u5F03")
1868
+ ]),
1869
+ h("div", { className: "wsp-proposal-body", key: "pb" }, h(SafeMarkdown, { text: state.proposal }))
1870
+ ])
1871
+ ]) : null);
1872
+ return h("div", { className: "wsp-card-body", key: `b${persona.id}` }, [
1873
+ h("div", { className: "wsp-field-row", key: "name" }, [
1874
+ h("div", { className: "wsp-field", key: "f", style: { flex: 1 } }, [
1875
+ h("span", { className: "wsp-label", key: "l" }, "\u540D\u79F0"),
1876
+ h(Input, {
1877
+ key: "i",
1878
+ value: draft.name,
1879
+ "aria-label": "\u4EBA\u8BBE\u540D\u79F0",
1880
+ placeholder: "\u4F8B\u5982\uFF1A\u524D\u7AEF\u9879\u76EE\u52A9\u624B / \u5B89\u5168\u503C\u73ED / \u4EE3\u7801\u5BA1\u67E5",
1881
+ onChange: (event) => this.patchDraft({ name: event.target.value })
1882
+ })
1883
+ ]),
1884
+ h("div", { className: "wsp-field", key: "sw" }, [
1885
+ h("span", { className: "wsp-label", key: "l" }, "\u72B6\u6001"),
1886
+ h(Switch, {
1887
+ checked: draft.enabled,
1888
+ label: draft.enabled ? "\u542F\u7528" : "\u505C\u7528",
1889
+ onChange: (next) => this.patchDraft({ enabled: next })
1890
+ })
1891
+ ])
1892
+ ]),
1893
+ h("div", { className: "wsp-field-row", key: "fallback" }, [
1894
+ h("div", { className: "wsp-field", key: "f" }, [
1895
+ h("span", { className: "wsp-label", key: "l" }, "\u9ED8\u8BA4\u4EBA\u8BBE"),
1896
+ h("div", { className: "wsp-kind", key: "k", role: "group", "aria-label": "\u9ED8\u8BA4\u4EBA\u8BBE" }, [
1897
+ h(Switch, {
1898
+ key: "fb",
1899
+ checked: draft.fallback === true,
1900
+ label: "\u4F5C\u4E3A\u9ED8\u8BA4\u4EBA\u8BBE\uFF1A\u672A\u547D\u4E2D\u4EFB\u4F55\u4F4D\u7F6E\u7684\u4F1A\u8BDD\u90FD\u7528\u5B83\uFF08\u540C\u4E00\u65F6\u523B\u53EA\u80FD\u6709\u4E00\u6761\uFF09",
1901
+ onChange: (next) => this.patchDraft({ fallback: next })
1902
+ })
1903
+ ])
1904
+ ])
1905
+ ]),
1906
+ h("div", { className: "wsp-field-row", key: "mode" }, [
1907
+ h("div", { className: "wsp-field", key: "f" }, [
1908
+ h("span", { className: "wsp-label", key: "l" }, "\u6CE8\u5165\u65B9\u5F0F"),
1909
+ h("div", { className: "wsp-kind", key: "k", role: "group", "aria-label": "\u6CE8\u5165\u65B9\u5F0F" }, [
1910
+ h(Pill, { key: "a", active: draft.mode !== "replace", title: "\u4E24\u53E5\u90FD\u751F\u6548\uFF1A\u5148 DSH \u81EA\u5E26\u8EAB\u4EFD\uFF0C\u518D\u8FD9\u6761\u4EBA\u8BBE", onClick: () => this.patchDraft({ mode: "append" }) }, "\u8FFD\u52A0"),
1911
+ h(Pill, { key: "r", active: draft.mode === "replace", title: "\u53EA\u7528\u8FD9\u6761\u4EBA\u8BBE\uFF1ADSH \u81EA\u5E26\u7684\u8EAB\u4EFD\u53E5\u4E0E\u90E8\u7F72\u8EAB\u4EFD\u90FD\u53BB\u6389\uFF08Agent \u9884\u8BBE\u81EA\u5DF1\u5199\u7684\u8EAB\u4EFD\u4E0D\u52A8\uFF09", onClick: () => this.patchDraft({ mode: "replace" }) }, "\u66FF\u6362")
1912
+ ])
1913
+ ]),
1914
+ h(
1915
+ "span",
1916
+ { className: "wsp-hint", key: "h", style: { flex: "1 1 200px", minWidth: "160px" } },
1917
+ draft.mode === "replace" ? "\u66FF\u6362\uFF1ADSH \u81EA\u5E26\u7684\u8EAB\u4EFD\u53E5\u548C\u90E8\u7F72\u8EAB\u4EFD\u8BF4\u660E\u90FD\u53BB\u6389\uFF0C\u53EA\u7528\u8FD9\u6761\u4EBA\u8BBE\u3002" : "\u8FFD\u52A0\uFF1ADSH \u81EA\u5E26\u7684\u8EAB\u4EFD\u8BF4\u660E\u4FDD\u7559\uFF0C\u8FD9\u6761\u4EBA\u8BBE\u63A5\u5728\u540E\u9762\u3002"
1918
+ )
1919
+ ]),
1920
+ h("div", { className: "wsp-group", key: "scope" }, [
1921
+ h("div", { className: "wsp-row-head", key: "gh", style: { padding: 0, minHeight: "auto" } }, [
1922
+ h("span", { className: "wsp-sect-title", key: "t" }, "\u7528\u5728\u54EA\u4E9B\u5730\u65B9"),
1923
+ h("span", { className: "wsp-spacer", key: "sp" }),
1924
+ h("span", { className: "wsp-hint", key: "h" }, "\u6EE1\u8DB3\u4EFB\u610F\u4E00\u884C\u5C31\u751F\u6548\uFF1B\u8D8A\u5177\u4F53\u8D8A\u4F18\u5148\uFF08\u4F1A\u8BDD > \u5DE5\u4F5C\u533A\uFF0C\u7CBE\u786E > \u524D\u7F00/\u6B63\u5219\uFF09\uFF0C\u540C\u6837\u5177\u4F53\u624D\u770B\u5217\u8868\u987A\u5E8F")
1925
+ ]),
1926
+ ...targetRows,
1927
+ h("div", { className: "wsp-row-head", key: "add", style: { padding: 0, minHeight: "auto" } }, [
1928
+ h(Button, {
1929
+ key: "add",
1930
+ variant: "ghost",
1931
+ size: "sm",
1932
+ icon: h(IconPlusOutline16, {}),
1933
+ onClick: () => this.addTarget()
1934
+ }, "\u518D\u52A0\u4E00\u4E2A\u4F4D\u7F6E"),
1935
+ h("span", { className: "wsp-spacer", key: "sp" }),
1936
+ h("span", { className: targets.length === 0 ? "wsp-hint wsp-warn" : "wsp-hint", key: "h" }, targets.length === 0 ? draft.fallback === true ? "\u4E00\u4E2A\u4F4D\u7F6E\u90FD\u6CA1\u52A0\uFF0C\u4F46\u5B83\u662F\u9ED8\u8BA4\u4EBA\u8BBE \u2192 \u6240\u6709\u672A\u547D\u4E2D\u4EFB\u4F55\u4F4D\u7F6E\u7684\u4F1A\u8BDD\u90FD\u7528\u5B83" : "\u4E00\u4E2A\u4F4D\u7F6E\u90FD\u6CA1\u52A0 \u2192 \u8FD9\u6761\u4EBA\u8BBE\u4E0D\u751F\u6548\uFF0C\u76F8\u5173\u4F1A\u8BDD\u7EE7\u7EED\u4F7F\u7528 DSH \u81EA\u5E26\u7684 system prompt" : `\u5171 ${targets.length} \u4E2A\u4F4D\u7F6E`)
1937
+ ])
1938
+ ]),
1939
+ h("div", { className: "wsp-field", key: "body" }, [
1940
+ h("div", { className: "wsp-row-head", key: "bh", style: { padding: 0, minHeight: "auto" } }, [
1941
+ h("span", { className: "wsp-sect-title", key: "t" }, "\u4EBA\u8BBE\u5185\u5BB9\uFF08Markdown\uFF09"),
1942
+ h("span", { className: "wsp-spacer", key: "sp" }),
1943
+ h(Button, { key: "e", variant: state.pane === "edit" ? "outline" : "ghost", size: "sm", onClick: () => this.onChange({ pane: "edit" }) }, "\u7F16\u8F91"),
1944
+ h(Button, { key: "p", variant: state.pane === "preview" ? "outline" : "ghost", size: "sm", onClick: () => this.onChange({ pane: "preview" }) }, "\u9884\u89C8")
1945
+ ]),
1946
+ h("div", { className: "wsp-editor", key: "ed" }, state.pane === "edit" ? h("textarea", {
1947
+ className: "wsp-textarea",
1948
+ "aria-label": "\u4EBA\u8BBE Markdown \u6B63\u6587",
1949
+ spellCheck: false,
1950
+ placeholder: "\u5728\u8FD9\u91CC\u5199\u4EBA\u8BBE\u3002\u53EF\u4EE5\u7528 {{model}} \u548C {{cwd}}\uFF0C\u53D1\u9001\u65F6\u4F1A\u66FF\u6362\u6210\u5F53\u524D\u7684\u503C\u3002\n\n\u7559\u7A7A\u8868\u793A\uFF1A\u8FD9\u6761\u4EBA\u8BBE\u5339\u914D\u5230\u7684\u4F1A\u8BDD\uFF0C\u4E0D\u52A0\u4EFB\u4F55\u4EBA\u8BBE\u3002",
1951
+ value: draft.text,
1952
+ onChange: (event) => this.patchDraft({ text: event.target.value })
1953
+ }) : h("div", { className: "wsp-preview" }, draft.text.trim() === "" ? h("span", { className: "wsp-preview-empty" }, "\uFF08\u6B63\u6587\u4E3A\u7A7A \u2192 \u8FD9\u4E9B\u5730\u65B9\u4E0D\u52A0\u4EBA\u8BBE\uFF09") : h(SafeMarkdown, { text: draft.text })))
1954
+ ]),
1955
+ tuneBlock,
1956
+ h("div", { className: "wsp-foot", key: "foot" }, [
1957
+ h("span", { className: "wsp-caption", key: "c" }, `${draft.text.length} \u5B57 \xB7 \u2318/Ctrl+S \u4FDD\u5B58`),
1958
+ h("div", { className: "wsp-actions", key: "a" }, [
1959
+ h(Button, { key: "dup", variant: "ghost", size: "sm", disabled: busy, onClick: () => void this.duplicate(draft.id) }, "\u590D\u5236"),
1960
+ state.confirmDelete === draft.id ? h(Button, { key: "del2", variant: "ghost", size: "sm", className: "wsp-icon-btn-danger", disabled: busy, onClick: () => void this.remove(draft.id) }, "\u786E\u8BA4\u5220\u9664") : h(Button, {
1961
+ key: "del",
1962
+ variant: "ghost",
1963
+ size: "sm",
1964
+ className: "wsp-icon-btn-danger",
1965
+ disabled: busy,
1966
+ icon: h(IconTrashOutline16, {}),
1967
+ onClick: () => this.onChange({ confirmDelete: draft.id })
1968
+ }, "\u5220\u9664"),
1969
+ h("kbd", { className: "wsp-kbd", key: "kbd", title: "\u4FDD\u5B58\u7684\u5FEB\u6377\u952E" }, `${MOD_KEY}S`),
1970
+ h(Button, {
1971
+ key: "save",
1972
+ variant: "primary",
1973
+ size: "sm",
1974
+ disabled: busy,
1975
+ title: `\u4FDD\u5B58\uFF08${MOD_KEY}S\uFF09`,
1976
+ ...state.busy === "save" ? {} : { icon: h(IconCheckOutline14, {}) },
1977
+ onClick: () => void this.save()
1978
+ }, state.busy === "save" ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58")
1979
+ ])
1980
+ ])
1981
+ ]);
1982
+ };
1983
+ const conflicts = /* @__PURE__ */ new Map();
1984
+ const patternHits = (row, kind, value) => {
1985
+ if (row.kind !== kind || row.value.trim() === "") return false;
1986
+ const needle2 = row.value.trim();
1987
+ if (row.match === "exact") return false;
1988
+ if (row.match === "prefix") return value.startsWith(needle2);
1989
+ if (row.match === "contains") return value.includes(needle2);
1990
+ if (row.match === "regex") {
1991
+ try {
1992
+ return new RegExp(needle2).test(value);
1993
+ } catch {
1994
+ return false;
1995
+ }
1996
+ }
1997
+ return false;
1998
+ };
1999
+ const enabled = personas.filter((persona) => persona.enabled);
2000
+ for (let i = 0; i < enabled.length; i += 1) {
2001
+ for (let j = i + 1; j < enabled.length; j += 1) {
2002
+ const winner = enabled[i];
2003
+ const loser = enabled[j];
2004
+ for (const row of loser.targets ?? []) {
2005
+ if (row.match !== "exact" || row.value.trim() === "") continue;
2006
+ if (!(winner.targets ?? []).some((candidate) => patternHits(candidate, row.kind, row.value))) continue;
2007
+ const notes = conflicts.get(loser.id) ?? [];
2008
+ const note = `\u8FD9\u6761\u89C4\u5219\uFF08${row.kind === "workspace" ? "\u5DE5\u4F5C\u533A" : "\u4F1A\u8BDD"} ${row.value}\uFF09\u4F1A\u88AB\u4E0A\u9762\u7684\u300C${winner.name}\u300D\u5148\u547D\u4E2D`;
2009
+ if (!notes.includes(note)) notes.push(note);
2010
+ conflicts.set(loser.id, notes);
2011
+ }
2012
+ }
2013
+ }
2014
+ const needle = state.filter.trim().toLowerCase();
2015
+ const matches = (persona) => [
2016
+ persona.name,
2017
+ ...(persona.targets ?? []).map((target) => targetLabel(target)),
2018
+ ...(persona.targets ?? []).map((target) => target.value)
2019
+ ].join(" ").toLowerCase().includes(needle);
2020
+ const visible = needle === "" ? personas : personas.filter(matches);
2021
+ const searchRow = personas.length < 4 ? null : h("div", { className: "wsp-search", key: "search" }, [
2022
+ h(Input, {
2023
+ value: state.filter,
2024
+ placeholder: "\u641C\u7D22\u4EBA\u8BBE\uFF1A\u540D\u79F0\u3001\u5DE5\u4F5C\u533A\u3001\u4F1A\u8BDD",
2025
+ "aria-label": "\u641C\u7D22\u4EBA\u8BBE",
2026
+ onChange: (event) => this.onChange({ filter: event.target.value })
2027
+ }),
2028
+ state.filter === "" ? null : h(Button, {
2029
+ key: "clear",
2030
+ variant: "ghost",
2031
+ size: "sm",
2032
+ onClick: () => this.onChange({ filter: "" })
2033
+ }, "\u6E05\u7A7A")
2034
+ ]);
2035
+ const hiddenNote = null;
2036
+ const conflictNotice = conflicts.size === 0 ? null : h("div", { className: "wsp-conflict-notice", key: "conflicts" }, [
2037
+ h(IconWarningOutline16, { key: "i" }),
2038
+ `${conflicts.size} \u6761\u4EBA\u8BBE\u7684\u8303\u56F4\u53EF\u80FD\u88AB\u4E0A\u9762\u7684\u4EBA\u8BBE\u5148\u547D\u4E2D\uFF08\u9F20\u6807\u60AC\u505C\u5BF9\u5E94\u5361\u7247\u770B\u539F\u56E0\uFF09`
2039
+ ]);
2040
+ const cards = personas.length === 0 ? [h("div", { className: "wsp-empty-card", key: "empty" }, [
2041
+ h("span", { key: "t" }, "\u8FD8\u6CA1\u6709\u4EBA\u8BBE\u3002\u65B0\u5EFA\u4E00\u6761\uFF0C\u9009\u597D\u5B83\u7528\u5728\u54EA\u4E9B\u5DE5\u4F5C\u533A\u6216\u4F1A\u8BDD\u4E0A\uFF0C\u518D\u6253\u5F00\u5F00\u5173\u3002"),
2042
+ h(Button, { key: "new", variant: "primary", size: "sm", icon: h(IconPlusOutline16, {}), onClick: () => void this.create() }, "\u65B0\u5EFA\u4EBA\u8BBE")
2043
+ ])] : visible.length === 0 ? [h("div", { className: "wsp-empty-card", key: "nomatch" }, `\u6CA1\u6709\u5339\u914D\u300C${state.filter}\u300D\u7684\u4EBA\u8BBE`)] : [...visible.map((persona, index) => h("div", {
2044
+ key: persona.id,
2045
+ className: [
2046
+ state.openId === persona.id ? "wsp-card wsp-card-open" : "wsp-card",
2047
+ needle !== "" ? "" : state.dropIndex === index ? "wsp-drop-before" : state.dropIndex === index + 1 ? "wsp-drop-after" : "",
2048
+ state.dragId === persona.id ? "wsp-dragging" : ""
2049
+ ].filter(Boolean).join(" "),
2050
+ onDragOver: needle !== "" || state.dragId === void 0 ? void 0 : (event) => {
2051
+ event.preventDefault();
2052
+ const rect = event.currentTarget.getBoundingClientRect();
2053
+ const boundary = event.clientY > rect.top + rect.height / 2 ? index + 1 : index;
2054
+ if (state.dropIndex !== boundary) this.onChange({ dropIndex: boundary });
2055
+ },
2056
+ onDrop: needle !== "" || state.dragId === void 0 ? void 0 : (event) => {
2057
+ event.preventDefault();
2058
+ void this.drop();
2059
+ }
2060
+ }, [
2061
+ cardHead(persona, index),
2062
+ state.openId === persona.id ? null : scopeLines(persona),
2063
+ cardBody(persona)
2064
+ ])), h("div", {
2065
+ className: "wsp-add-row",
2066
+ key: "add",
2067
+ role: "button",
2068
+ tabIndex: 0,
2069
+ "aria-label": "\u65B0\u5EFA\u4EBA\u8BBE",
2070
+ onClick: () => void this.create(),
2071
+ onKeyDown: (event) => {
2072
+ if (event.key === "Enter" || event.key === " ") {
2073
+ event.preventDefault();
2074
+ void this.create();
2075
+ }
2076
+ }
2077
+ }, [h(IconPlusOutline16, { key: "i" }), h("span", { key: "t" }, busy ? "\u6B63\u5728\u65B0\u5EFA\u2026" : "\u65B0\u5EFA\u4EBA\u8BBE")])];
2078
+ const status = state.error !== "" ? h("div", { className: "wsp-status wsp-status-err", key: "st", "aria-live": "polite" }, state.error) : state.status === "" ? null : h(Toast, {
2079
+ key: "st",
2080
+ text: state.status,
2081
+ icon: h(IconCheckOutline14, {}),
2082
+ onDone: () => this.onChange({ status: "" })
2083
+ });
2084
+ const history = state.history;
2085
+ const historyModal = h(Modal, {
2086
+ key: "history",
2087
+ className: "wsp-chat-modal",
2088
+ contentClassName: "wsp-chat-content",
2089
+ open: history !== void 0,
2090
+ onClose: () => this.onChange({ history: void 0 }),
2091
+ title: history === void 0 ? "" : sessionById(history.id)?.title ?? `\u4F1A\u8BDD ${history.id}`,
2092
+ description: history === void 0 ? void 0 : [
2093
+ history.cwd,
2094
+ `\u5DF2\u663E\u793A ${history.shown ?? 0} \u6761`,
2095
+ (history.skipped ?? 0) > 0 ? `\u5DF2\u9690\u85CF ${history.skipped} \u6761\u63D2\u4EF6\u6CE8\u5165\u5185\u5BB9\uFF08\u5DE5\u4F5C\u533A\u6307\u4EE4\u3001skill \u76EE\u5F55\u7B49\uFF09` : null
2096
+ ].filter(Boolean).join(" \xB7 "),
2097
+ footer: history === void 0 || history.unavailable === true ? null : h("div", { className: "wsp-actions", key: "f" }, [
2098
+ h("span", { className: "wsp-caption", key: "c" }, history.mode === "tail" ? `\u53EA\u663E\u793A\u6700\u540E\u4E00\u6B21\u5F80\u6765\uFF08\u5171 ${history.total ?? 0} \u6761\u6D88\u606F\uFF0C\u4ECE\u7ED3\u5C3E\u5B9A\u4F4D\uFF09` : history.done === true ? `\u5DF2\u8BFB\u5B8C\u6574\u6761\u4F1A\u8BDD \xB7 ${history.shown ?? 0} \u6761\u6D88\u606F` : `\u5DF2\u8BFB\u5230 ${history.shown ?? 0} \u6761\uFF08\u4ECE\u4F1A\u8BDD\u5F00\u5934\u987A\u5E8F\u8BFB\uFF09`),
2099
+ h(Button, {
2100
+ key: "all",
2101
+ variant: "ghost",
2102
+ size: "sm",
2103
+ disabled: state.historyBusy !== void 0,
2104
+ onClick: () => void (history.mode === "tail" ? this.readHeadHistory() : this.readTailHistory())
2105
+ }, history.mode === "tail" ? "\u4ECE\u5934\u770B" : "\u8DF3\u5230\u6700\u65B0"),
2106
+ h(Button, {
2107
+ key: "more",
2108
+ variant: "outline",
2109
+ size: "sm",
2110
+ disabled: state.historyBusy !== void 0 || history.mode === "tail" || history.done === true,
2111
+ onClick: () => void this.loadMoreHistory()
2112
+ }, state.historyBusy === history.id ? "\u8BFB\u53D6\u4E2D\u2026" : "\u7EE7\u7EED\u8BFB\u540E\u9762\u7684")
2113
+ ])
2114
+ }, history === void 0 ? null : history.available === false ? h("div", { className: "wsp-hint" }, history.error ?? (history.unavailable ? "\u5BBF\u4E3B\u6CA1\u6709\u63D0\u4F9B\u8BFB\u53D6\u8FD9\u6761\u4F1A\u8BDD\u7684\u63A5\u53E3\uFF08\u53EF\u80FD\u662F DSH \u7248\u672C\u5DEE\u5F02\uFF09\u3002" : "\u8FD9\u6761\u4F1A\u8BDD\u91CC\u6CA1\u6709\u53EF\u663E\u793A\u7684\u6D88\u606F\u3002")) : h("div", { className: "wsp-chat" }, (history.messages ?? []).map((message, i) => {
2115
+ const mine = message.role === "user";
2116
+ return h("div", { className: mine ? "wsp-msg wsp-msg-mine" : "wsp-msg", key: `m${i}` }, [
2117
+ h(
2118
+ "span",
2119
+ { className: "wsp-avatar", key: "a", "aria-hidden": "true" },
2120
+ h(mine ? IconUserOutline16 : IconAgentPresetOutline16, {})
2121
+ ),
2122
+ h("div", { className: "wsp-msg-col", key: "c" }, [
2123
+ h("span", { className: "wsp-msg-who", key: "w" }, [
2124
+ mine ? "\u4F60" : "Agent"
2125
+ ]),
2126
+ h("div", { className: mine ? "wsp-bubble wsp-bubble-mine" : "wsp-bubble", key: "b" }, [
2127
+ h(SafeMarkdown, { key: "md", text: message.text }),
2128
+ message.truncated === true && message.expanded !== true ? h("div", { className: "wsp-trunc", key: "trunc" }, [
2129
+ h(IconWarningOutline16, { key: "i" }),
2130
+ h("span", { className: "wsp-trunc-text", key: "t" }, `\u5185\u5BB9\u8FC7\u957F\uFF0C\u53EA\u663E\u793A\u5F00\u5934\uFF08\u524D ${(message.shortText ?? message.text ?? "").length} \u5B57\uFF09`),
2131
+ h("button", {
2132
+ key: "exp",
2133
+ type: "button",
2134
+ className: "wsp-expand-btn",
2135
+ disabled: state.historyBusy === `msg:${i}`,
2136
+ title: "\u8BFB\u53D6\u8FD9\u6761\u6D88\u606F\u7684\u5B8C\u6574\u5185\u5BB9\uFF08\u53EA\u91CD\u753B\u8FD9\u4E00\u4E2A\u6C14\u6CE1\uFF09",
2137
+ onClick: () => void this.expandMessage(i)
2138
+ }, [
2139
+ h(IconChevronDownOutline14, { key: "i" }),
2140
+ h("span", { key: "l" }, state.historyBusy === `msg:${i}` ? "\u5C55\u5F00\u4E2D\u2026" : "\u5C55\u5F00\u5168\u6587")
2141
+ ])
2142
+ ]) : message.expanded === true ? h("div", { className: "wsp-trunc wsp-trunc-quiet", key: "trunc" }, [
2143
+ h("span", { className: "wsp-trunc-text", key: "t" }, "\u5DF2\u663E\u793A\u5168\u6587"),
2144
+ h("button", {
2145
+ key: "exp",
2146
+ type: "button",
2147
+ className: "wsp-expand-btn",
2148
+ disabled: state.historyBusy === `msg:${i}`,
2149
+ title: "\u53EA\u663E\u793A\u524D\u4E00\u6BB5",
2150
+ onClick: () => void this.collapseMessage(i)
2151
+ }, [
2152
+ h(IconChevronUpOutline14, { key: "i" }),
2153
+ h("span", { key: "l" }, "\u6536\u8D77\u5168\u6587")
2154
+ ])
2155
+ ]) : null
2156
+ ])
2157
+ ])
2158
+ ]);
2159
+ })));
2160
+ const prefs = state.prefs ?? DEFAULT_PREFS;
2161
+ const prefsRow = h("div", { className: "wsp-prefs", key: "prefs" }, [
2162
+ h(Switch, { key: "auto", checked: prefs.autosave === true, label: "\u7F16\u8F91\u540E\u81EA\u52A8\u4FDD\u5B58", onChange: (next) => this.setPref({ autosave: next }) }),
2163
+ h(Switch, { key: "tab", checked: prefs.showTab !== false, label: "\u5728\u5BF9\u8BDD\u9875\u663E\u793A\u300C\u4EBA\u8BBE\u300D\u6807\u7B7E", onChange: (next) => this.setPref({ showTab: next }) }),
2164
+ ...sidebarProbe() === void 0 ? [] : [h(Switch, { key: "side", checked: prefs.showSidebar !== false, label: "\u6CE8\u518C\u5230 dsh-better-sidebar", onChange: (next) => this.setPref({ showSidebar: next }) })]
2165
+ ]);
2166
+ const pendingDelete = state.confirmDelete === void 0 || state.confirmDelete === null ? void 0 : personas.find((item) => item.id === state.confirmDelete);
2167
+ const confirmModal = pendingDelete === void 0 ? null : h(Modal, {
2168
+ key: "confirm",
2169
+ open: true,
2170
+ onClose: () => this.onChange({ confirmDelete: void 0 }),
2171
+ title: "\u5220\u9664\u8FD9\u6761\u4EBA\u8BBE\uFF1F",
2172
+ description: `\u300C${pendingDelete.name}\u300D\u4F1A\u4ECE\u5B58\u50A8\u91CC\u79FB\u9664\uFF0C\u65E0\u6CD5\u64A4\u9500\u3002`,
2173
+ footer: h("div", { className: "wsp-form-actions" }, [
2174
+ h(Button, { key: "cancel", variant: "outline", size: "sm", onClick: () => this.onChange({ confirmDelete: void 0 }) }, "\u53D6\u6D88"),
2175
+ h(Button, { key: "ok", variant: "primary", size: "sm", disabled: busy, onClick: () => void this.remove(pendingDelete.id) }, "\u5220\u9664")
2176
+ ])
2177
+ }, h("div", { className: "wsp-card-note" }, `\u540D\u79F0\uFF1A${pendingDelete.name}\u3000\xB7\u3000${pendingDelete.chars} \u5B57\u3000\xB7\u3000${pendingDelete.targets?.length ?? 0} \u6761\u9002\u7528\u8303\u56F4`));
2178
+ return h("div", rootProps, [header, prefsRow, searchRow, hiddenNote, conflictNotice, h("div", { className: "wsp-cards", key: "cards" }, cards), status, historyModal, confirmModal]);
2179
+ }
2180
+ }
2181
+ const inject = ["slots", "remote", "connection"];
2182
+ function apply(ctx) {
2183
+ const style = injectStyles();
2184
+ ctx.effect(() => () => style.remove(), `${NS}: styles`);
2185
+ const prefs = readPrefs();
2186
+ if (prefs.showTab) ctx.slots.inject("conversation.view", () => ctx.slots.register({
2187
+ name: "conversation.view",
2188
+ id: "agent-persona",
2189
+ order: 30,
2190
+ label: () => "\u4EBA\u8BBE"
2191
+ }, PersonaView));
2192
+ sidebarProbe = () => {
2193
+ try {
2194
+ return ctx.get("betterSidebar");
2195
+ } catch {
2196
+ return void 0;
2197
+ }
2198
+ };
2199
+ {
2200
+ let disposeTab;
2201
+ syncSidebarModule = () => {
2202
+ if (typeof disposeTab === "function") {
2203
+ disposeTab();
2204
+ disposeTab = void 0;
2205
+ }
2206
+ if (readPrefs().showSidebar === false) return;
2207
+ let better;
2208
+ try {
2209
+ better = ctx.get("betterSidebar");
2210
+ } catch {
2211
+ better = void 0;
2212
+ }
2213
+ sidebarAvailable = better !== void 0 && better !== null && typeof better.registerTab === "function";
2214
+ if (sidebarAvailable !== true) return;
2215
+ disposeTab = better.registerTab({
2216
+ id: NS,
2217
+ title: () => "Agent \u4EBA\u8BBE",
2218
+ order: 30,
2219
+ single: true,
2220
+ component: SidebarPersonaView
2221
+ });
2222
+ };
2223
+ syncSidebarModule();
2224
+ }
2225
+ ctx.slots.inject("settings.general.item", () => ctx.slots.register({
2226
+ name: "settings.general.item",
2227
+ id: NS,
2228
+ order: 30,
2229
+ inject: () => ({ api: capturedApi })
2230
+ }, PersonaPrefsRow));
2231
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
2232
+ name: "settings.section",
2233
+ id: NS,
2234
+ order: 22,
2235
+ label: () => "Agent \u4EBA\u8BBE",
2236
+ inject: () => ({ api: capturedApi })
2237
+ }, WorkspacePersonaSection));
2238
+ void (async () => {
2239
+ try {
2240
+ await ctx.remote.$mount(RPC_REMOTE);
2241
+ capturedApi = ctx.get("remote.agentPersona");
2242
+ } catch (error) {
2243
+ ctx.logger?.warn?.(`[${NS}] could not mount remote namespace: ${String(error)}`);
2244
+ }
2245
+ })();
2246
+ }
2247
+ module.exports = { name: NS, inject, apply };
2248
+ return module.exports;
2249
+ }
2250
+ });
2251
+ })();