dsh-plugin-prompt-tool 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -23,7 +23,7 @@ window.__ModuleLoader__.load({
23
23
  }
24
24
  //#endregion
25
25
  //#region \0dsh-css:E:\Documents\GitHub\dsh-plugin-prompt-tool\src\client\PromptEditor.module.css.mjs
26
- const css = ".Ru2OGG_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.Ru2OGG_card:hover{border-color:var(--dsw-alias-label-dimmed)}.Ru2OGG_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.Ru2OGG_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.Ru2OGG_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Ru2OGG_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.Ru2OGG_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.Ru2OGG_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.Ru2OGG_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Ru2OGG_chevronOpen{transform:rotate(180deg)}.Ru2OGG_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Ru2OGG_body{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:14px;margin:0 16px;padding:14px 0 16px;display:flex}.Ru2OGG_loading{color:var(--dsw-alias-label-tertiary);font-size:12px;display:inline-block}.Ru2OGG_group{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;gap:8px;padding:12px 14px;display:flex}.Ru2OGG_groupTitle{color:var(--dsw-alias-label-secondary);font-size:13px;font-weight:600}.Ru2OGG_row{cursor:pointer;align-items:flex-start;gap:10px;padding:6px 0;display:flex}.Ru2OGG_rowStack{flex-direction:column;align-items:stretch;gap:8px;padding:6px 0;display:flex}.Ru2OGG_row input{flex:none;margin-top:3px}.Ru2OGG_rowText{flex-direction:column;gap:2px;display:flex}.Ru2OGG_rowName{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5}.Ru2OGG_rowDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.Ru2OGG_rowDisabled{opacity:.55}.Ru2OGG_anchorInput{box-sizing:border-box;width:100%;min-height:64px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;padding:8px 10px;font-family:ui-monospace,monospace;font-size:12px;line-height:1.6}.Ru2OGG_textarea{box-sizing:border-box;width:100%;min-height:420px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;padding:10px;font-family:ui-monospace,monospace;font-size:13px;line-height:1.6}.Ru2OGG_actions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.Ru2OGG_primary,.Ru2OGG_secondary{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.Ru2OGG_primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.Ru2OGG_secondary{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.Ru2OGG_primary:disabled,.Ru2OGG_secondary:disabled{opacity:.55;cursor:default}.Ru2OGG_primary:focus-visible,.Ru2OGG_secondary:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.Ru2OGG_notice{color:var(--dsw-alias-state-success-primary);font-size:12px}.Ru2OGG_noticeError{color:var(--dsw-alias-state-error-primary,#d5484d)}";
26
+ const css = ".Ru2OGG_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.Ru2OGG_card:hover{border-color:var(--dsw-alias-label-dimmed)}.Ru2OGG_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.Ru2OGG_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.Ru2OGG_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Ru2OGG_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.Ru2OGG_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.Ru2OGG_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.Ru2OGG_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Ru2OGG_chevronOpen{transform:rotate(180deg)}.Ru2OGG_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Ru2OGG_body{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:14px;margin:0 16px;padding:14px 0 16px;display:flex}.Ru2OGG_loading{color:var(--dsw-alias-label-tertiary);font-size:12px;display:inline-block}.Ru2OGG_group{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;gap:8px;padding:12px 14px;display:flex}.Ru2OGG_groupTitle{color:var(--dsw-alias-label-secondary);font-size:13px;font-weight:600}.Ru2OGG_subSection{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;display:flex;overflow:hidden}.Ru2OGG_subHeader{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;align-items:center;gap:12px;padding:12px 14px;display:flex}.Ru2OGG_subHeader:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Ru2OGG_subTitle{color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:600;line-height:1.5}.Ru2OGG_subCount{color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px}.Ru2OGG_subBody{flex-direction:column;gap:8px;padding:2px 14px 12px;display:flex}.Ru2OGG_subEmpty{color:var(--dsw-alias-label-tertiary);padding:4px 0 8px;font-size:12px}.Ru2OGG_row{cursor:pointer;align-items:flex-start;gap:10px;padding:6px 0;display:flex}.Ru2OGG_rowStack{flex-direction:column;align-items:stretch;gap:8px;padding:6px 0;display:flex}.Ru2OGG_row input{flex:none;margin-top:3px}.Ru2OGG_rowText{flex-direction:column;gap:2px;display:flex}.Ru2OGG_rowName{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5}.Ru2OGG_rowDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.Ru2OGG_rowDisabled{opacity:.55}.Ru2OGG_anchorInput{box-sizing:border-box;width:100%;min-height:64px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;padding:8px 10px;font-family:ui-monospace,monospace;font-size:12px;line-height:1.6}.Ru2OGG_textarea{box-sizing:border-box;width:100%;min-height:420px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;padding:10px;font-family:ui-monospace,monospace;font-size:13px;line-height:1.6}.Ru2OGG_actions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.Ru2OGG_primary,.Ru2OGG_secondary{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.Ru2OGG_primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.Ru2OGG_secondary{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.Ru2OGG_primary:disabled,.Ru2OGG_secondary:disabled{opacity:.55;cursor:default}.Ru2OGG_primary:focus-visible,.Ru2OGG_secondary:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.Ru2OGG_notice{color:var(--dsw-alias-state-success-primary);font-size:12px}.Ru2OGG_noticeError{color:var(--dsw-alias-state-error-primary,#d5484d)}";
27
27
  const tagId = "dsh-plugin-prompt-tool/PromptEditor.module.css";
28
28
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
29
29
  const tag = document.createElement("style");
@@ -58,6 +58,12 @@ window.__ModuleLoader__.load({
58
58
  "rowStack": "Ru2OGG_rowStack",
59
59
  "rowText": "Ru2OGG_rowText",
60
60
  "secondary": "Ru2OGG_secondary",
61
+ "subBody": "Ru2OGG_subBody",
62
+ "subCount": "Ru2OGG_subCount",
63
+ "subEmpty": "Ru2OGG_subEmpty",
64
+ "subHeader": "Ru2OGG_subHeader",
65
+ "subSection": "Ru2OGG_subSection",
66
+ "subTitle": "Ru2OGG_subTitle",
61
67
  "textarea": "Ru2OGG_textarea"
62
68
  };
63
69
  //#endregion
@@ -66,23 +72,78 @@ window.__ModuleLoader__.load({
66
72
  const EMPTY = {
67
73
  promptText: "",
68
74
  promptPath: "",
75
+ agentsText: "",
76
+ agentsPath: "",
69
77
  anchorFirstTurn: false,
70
78
  anchorText: "",
71
79
  injectPrompt: true,
80
+ skillSwitches: {},
81
+ skillCatalog: [],
72
82
  writeAgents: true,
73
83
  writePreset: true
74
84
  };
85
+ const EMPTY_SWITCHES = {
86
+ anchorFirstTurn: false,
87
+ anchorText: "",
88
+ injectPrompt: true,
89
+ skillSwitches: {},
90
+ writeAgents: true,
91
+ writePreset: true
92
+ };
93
+ const snapshotSwitches = (fields) => ({
94
+ anchorFirstTurn: fields.anchorFirstTurn,
95
+ anchorText: fields.anchorText,
96
+ injectPrompt: fields.injectPrompt,
97
+ skillSwitches: { ...fields.skillSwitches },
98
+ writeAgents: fields.writeAgents,
99
+ writePreset: fields.writePreset
100
+ });
101
+ const switchesEqual = (a, b) => a.anchorFirstTurn === b.anchorFirstTurn && a.anchorText === b.anchorText && a.injectPrompt === b.injectPrompt && JSON.stringify(a.skillSwitches) === JSON.stringify(b.skillSwitches) && a.writeAgents === b.writeAgents && a.writePreset === b.writePreset;
102
+ function CollapsibleSection(props) {
103
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
104
+ className: PromptEditor_module_css_default.subSection,
105
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
106
+ type: "button",
107
+ className: PromptEditor_module_css_default.subHeader,
108
+ "aria-expanded": props.open,
109
+ onClick: props.onToggle,
110
+ children: [
111
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
112
+ className: PromptEditor_module_css_default.subTitle,
113
+ children: props.title
114
+ }),
115
+ props.hint && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
116
+ className: PromptEditor_module_css_default.subCount,
117
+ children: props.hint
118
+ }),
119
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: clsx(PromptEditor_module_css_default.chevron, props.open && PromptEditor_module_css_default.chevronOpen) })
120
+ ]
121
+ }), props.open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
122
+ className: PromptEditor_module_css_default.subBody,
123
+ children: props.children
124
+ })]
125
+ });
126
+ }
75
127
  function PromptEditor(props) {
76
128
  const { api } = props;
77
129
  const [open, setOpen] = (0, react.useState)(false);
130
+ const [promptOpen, setPromptOpen] = (0, react.useState)(false);
131
+ const [agentsOpen, setAgentsOpen] = (0, react.useState)(false);
132
+ const [skillsOpen, setSkillsOpen] = (0, react.useState)(false);
133
+ const [presetOpen, setPresetOpen] = (0, react.useState)(false);
78
134
  const [loaded, setLoaded] = (0, react.useState)(false);
79
135
  const [fields, setFields] = (0, react.useState)(EMPTY);
80
- const [saved, setSaved] = (0, react.useState)(void 0);
81
- const [revision, setRevision] = (0, react.useState)(void 0);
136
+ const [savedPromptText, setSavedPromptText] = (0, react.useState)("");
137
+ const [savedAgentsText, setSavedAgentsText] = (0, react.useState)("");
138
+ const [savedSwitches, setSavedSwitches] = (0, react.useState)(EMPTY_SWITCHES);
82
139
  const [loading, setLoading] = (0, react.useState)(false);
83
- const [saving, setSaving] = (0, react.useState)(false);
140
+ const [savingPrompt, setSavingPrompt] = (0, react.useState)(false);
141
+ const [savingAgents, setSavingAgents] = (0, react.useState)(false);
84
142
  const [notice, setNotice] = (0, react.useState)("");
85
143
  const [noticeKind, setNoticeKind] = (0, react.useState)("ok");
144
+ const fieldsRef = (0, react.useRef)(EMPTY);
145
+ const revisionRef = (0, react.useRef)(void 0);
146
+ const saveQueueRef = (0, react.useRef)(Promise.resolve());
86
147
  const showNotice = (0, react.useCallback)((kind, message) => {
87
148
  setNotice(message);
88
149
  setNoticeKind(kind);
@@ -100,18 +161,27 @@ window.__ModuleLoader__.load({
100
161
  showNotice("error", "未找到提示词工具配置");
101
162
  return;
102
163
  }
164
+ const value = ns.value ?? {};
165
+ const base = ns.base ?? {};
103
166
  const next = {
104
- promptText: ns.value?.promptText ?? "",
105
- promptPath: ns.value?.promptPath ?? "",
106
- anchorFirstTurn: ns.value?.anchorFirstTurn ?? false,
107
- anchorText: ns.value?.anchorText ?? "",
108
- injectPrompt: ns.value?.injectPrompt ?? true,
109
- writeAgents: ns.value?.writeAgents ?? true,
110
- writePreset: ns.value?.writePreset ?? true
167
+ promptText: value.promptText ?? base.promptText ?? "",
168
+ promptPath: value.promptPath ?? base.promptPath ?? "",
169
+ agentsText: value.agentsText ?? base.agentsText ?? "",
170
+ agentsPath: value.agentsPath ?? base.agentsPath ?? "",
171
+ anchorFirstTurn: value.anchorFirstTurn ?? base.anchorFirstTurn ?? false,
172
+ anchorText: value.anchorText ?? base.anchorText ?? "",
173
+ injectPrompt: value.injectPrompt ?? base.injectPrompt ?? true,
174
+ skillSwitches: value.skillSwitches ?? base.skillSwitches ?? {},
175
+ skillCatalog: Array.isArray(value.skillCatalog) ? value.skillCatalog : Array.isArray(base.skillCatalog) ? base.skillCatalog : [],
176
+ writeAgents: value.writeAgents ?? base.writeAgents ?? true,
177
+ writePreset: value.writePreset ?? base.writePreset ?? true
111
178
  };
179
+ fieldsRef.current = next;
112
180
  setFields(next);
113
- setSaved(JSON.stringify(next));
114
- setRevision(ns.revision);
181
+ setSavedPromptText(next.promptText);
182
+ setSavedAgentsText(next.agentsText);
183
+ setSavedSwitches(snapshotSwitches(next));
184
+ revisionRef.current = ns.revision;
115
185
  setNotice("");
116
186
  } catch (e) {
117
187
  showNotice("error", "读取失败:" + (e?.message ?? e));
@@ -124,13 +194,17 @@ window.__ModuleLoader__.load({
124
194
  const res = await api.settings.describe({});
125
195
  if (!res.result.ok) return;
126
196
  const ns = res.result.value.namespaces.find((n) => n.ns === NS);
127
- if (ns) setRevision(ns.revision);
197
+ if (ns) revisionRef.current = ns.revision;
128
198
  } catch {}
129
199
  }, [api]);
130
- const patch = (partial) => setFields((prev) => ({
131
- ...prev,
132
- ...partial
133
- }));
200
+ const patch = (partial) => {
201
+ const next = {
202
+ ...fieldsRef.current,
203
+ ...partial
204
+ };
205
+ fieldsRef.current = next;
206
+ setFields(next);
207
+ };
134
208
  const toggleOpen = () => {
135
209
  const next = !open;
136
210
  setOpen(next);
@@ -140,9 +214,6 @@ window.__ModuleLoader__.load({
140
214
  load();
141
215
  } else if (!dirty) load();
142
216
  };
143
- const discard = () => {
144
- load();
145
- };
146
217
  const openEdit = async () => {
147
218
  if (!fields.promptPath) {
148
219
  showNotice("error", "路径未知,请先保存一次或在下方直接编辑");
@@ -150,69 +221,120 @@ window.__ModuleLoader__.load({
150
221
  }
151
222
  try {
152
223
  const res = await api.host.openPath({ path: fields.promptPath });
153
- if (res.result.ok) showNotice("ok", "已用系统编辑器打开 prompt.md");
224
+ if (res.result.ok) showNotice("ok", "已用系统编辑器打开 preset.md");
154
225
  else showNotice("error", "打开失败:" + (res.result.error?.message ?? "") + ";可直接在下方编辑框保存");
155
226
  } catch (e) {
156
227
  showNotice("error", "打开失败:" + (e?.message ?? e) + ";可直接在下方编辑框保存");
157
228
  }
158
229
  };
159
- const save = async () => {
160
- if (saving) return;
161
- setSaving(true);
230
+ const openAgents = async () => {
231
+ if (!fields.agentsPath) {
232
+ showNotice("error", "路径未知,请先保存一次或在下方直接编辑");
233
+ return;
234
+ }
162
235
  try {
163
- const res = await api.settings.mutate({
164
- ns: NS,
165
- ops: [
166
- {
167
- op: "set",
168
- path: ["promptText"],
169
- value: fields.promptText
170
- },
171
- {
172
- op: "set",
173
- path: ["anchorFirstTurn"],
174
- value: fields.anchorFirstTurn
175
- },
176
- {
177
- op: "set",
178
- path: ["anchorText"],
179
- value: fields.anchorText
180
- },
181
- {
182
- op: "set",
183
- path: ["injectPrompt"],
184
- value: fields.injectPrompt
185
- },
186
- {
187
- op: "set",
188
- path: ["writeAgents"],
189
- value: fields.writeAgents
190
- },
191
- {
192
- op: "set",
193
- path: ["writePreset"],
194
- value: fields.writePreset
195
- }
196
- ],
197
- expectedRevision: revision
198
- });
199
- if (res.result.ok) {
200
- setRevision(res.result.value.revision);
201
- setSaved(JSON.stringify(fields));
202
- showNotice("ok", "已保存并生效");
203
- } else {
236
+ const res = await api.host.openPath({ path: fields.agentsPath });
237
+ if (res.result.ok) showNotice("ok", "已用系统编辑器打开 AGENTS.md");
238
+ else showNotice("error", "打开失败:" + (res.result.error?.message ?? "") + ";可直接在下方编辑框保存");
239
+ } catch (e) {
240
+ showNotice("error", "打开失败:" + (e?.message ?? e) + ";可直接在下方编辑框保存");
241
+ }
242
+ };
243
+ const enqueueSave = (0, react.useCallback)((ops, okMessage, onSaved, setBusy) => {
244
+ setBusy?.(true);
245
+ saveQueueRef.current = saveQueueRef.current.then(async () => {
246
+ try {
247
+ const res = await api.settings.mutate({
248
+ ns: NS,
249
+ ops,
250
+ expectedRevision: revisionRef.current
251
+ });
252
+ if (res.result.ok) {
253
+ revisionRef.current = res.result.value.revision;
254
+ onSaved();
255
+ if (okMessage) showNotice("ok", okMessage);
256
+ } else {
257
+ await refreshRevision();
258
+ showNotice("error", "保存失败:" + (res.result.error?.message ?? "") + "(已刷新配置版本,可重试)");
259
+ }
260
+ } catch (e) {
204
261
  await refreshRevision();
205
- showNotice("error", "保存失败:" + (res.result.error?.message ?? "") + "(已刷新配置版本,可重试)");
262
+ showNotice("error", "保存失败:" + (e?.message ?? e) + "(已刷新配置版本,可重试)");
263
+ } finally {
264
+ setBusy?.(false);
206
265
  }
207
- } catch (e) {
208
- await refreshRevision();
209
- showNotice("error", "保存失败:" + (e?.message ?? e) + "(已刷新配置版本,可重试)");
210
- } finally {
211
- setSaving(false);
266
+ }).catch(() => {});
267
+ }, [
268
+ api,
269
+ refreshRevision,
270
+ showNotice
271
+ ]);
272
+ const savePrompt = () => enqueueSave([{
273
+ op: "set",
274
+ path: ["promptText"],
275
+ value: fieldsRef.current.promptText
276
+ }], "preset.md 已保存并生效", () => setSavedPromptText(fieldsRef.current.promptText), setSavingPrompt);
277
+ const saveAgents = () => enqueueSave([{
278
+ op: "set",
279
+ path: ["agentsText"],
280
+ value: fieldsRef.current.agentsText
281
+ }], "AGENTS.md 已保存并生效", () => setSavedAgentsText(fieldsRef.current.agentsText), setSavingAgents);
282
+ const persistSwitches = () => enqueueSave([
283
+ {
284
+ op: "set",
285
+ path: ["anchorFirstTurn"],
286
+ value: fieldsRef.current.anchorFirstTurn
287
+ },
288
+ {
289
+ op: "set",
290
+ path: ["anchorText"],
291
+ value: fieldsRef.current.anchorText
292
+ },
293
+ {
294
+ op: "set",
295
+ path: ["injectPrompt"],
296
+ value: fieldsRef.current.injectPrompt
297
+ },
298
+ {
299
+ op: "set",
300
+ path: ["skillSwitches"],
301
+ value: fieldsRef.current.skillSwitches
302
+ },
303
+ {
304
+ op: "set",
305
+ path: ["writeAgents"],
306
+ value: fieldsRef.current.writeAgents
307
+ },
308
+ {
309
+ op: "set",
310
+ path: ["writePreset"],
311
+ value: fieldsRef.current.writePreset
212
312
  }
313
+ ], void 0, () => setSavedSwitches(snapshotSwitches(fieldsRef.current)));
314
+ const toggle = (key) => {
315
+ patch({ [key]: !fieldsRef.current[key] });
316
+ persistSwitches();
317
+ };
318
+ const skillEnabled = (folder) => fields.skillSwitches[folder] !== false;
319
+ const toggleSkill = (folder) => {
320
+ const enabled = fieldsRef.current.skillSwitches[folder] !== false;
321
+ patch({ skillSwitches: {
322
+ ...fieldsRef.current.skillSwitches,
323
+ [folder]: !enabled
324
+ } });
325
+ persistSwitches();
213
326
  };
214
- const toggle = (key) => patch({ [key]: !fields[key] });
215
- const dirty = saved !== void 0 && JSON.stringify(fields) !== saved;
327
+ const currentSwitches = snapshotSwitches(fields);
328
+ const dirtyPrompt = fields.promptText !== savedPromptText;
329
+ const dirtyAgents = fields.agentsText !== savedAgentsText;
330
+ const dirtySwitches = !switchesEqual(currentSwitches, savedSwitches);
331
+ const dirty = dirtyPrompt || dirtyAgents || dirtySwitches;
332
+ const promptSwitchDirty = fields.injectPrompt !== savedSwitches.injectPrompt;
333
+ const agentsSwitchDirty = fields.writeAgents !== savedSwitches.writeAgents;
334
+ const skillsSwitchDirty = JSON.stringify(fields.skillSwitches) !== JSON.stringify(savedSwitches.skillSwitches);
335
+ const presetSwitchDirty = fields.writePreset !== savedSwitches.writePreset || fields.anchorFirstTurn !== savedSwitches.anchorFirstTurn || fields.anchorText !== savedSwitches.anchorText;
336
+ const discardPrompt = () => patch({ promptText: savedPromptText });
337
+ const discardAgents = () => patch({ agentsText: savedAgentsText });
216
338
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
217
339
  className: clsx(PromptEditor_module_css_default.card, open && PromptEditor_module_css_default.cardOpen),
218
340
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
@@ -228,7 +350,7 @@ window.__ModuleLoader__.load({
228
350
  children: "提示词工具"
229
351
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
230
352
  className: PromptEditor_module_css_default.description,
231
- children: "编辑模型行为规范 prompt.md 与注入开关,保存后自动生效"
353
+ children: "编辑 preset.md / AGENTS.md 与各层注入开关,保存后自动生效"
232
354
  })]
233
355
  }),
234
356
  dirty && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -244,13 +366,12 @@ window.__ModuleLoader__.load({
244
366
  className: PromptEditor_module_css_default.loading,
245
367
  children: "正在读取配置…"
246
368
  }),
247
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
248
- className: PromptEditor_module_css_default.group,
369
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
370
+ title: "Preset预设",
371
+ hint: dirtyPrompt || promptSwitchDirty ? "未保存" : void 0,
372
+ open: promptOpen,
373
+ onToggle: () => setPromptOpen(!promptOpen),
249
374
  children: [
250
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
251
- className: PromptEditor_module_css_default.groupTitle,
252
- children: "注入开关"
253
- }),
254
375
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
255
376
  className: PromptEditor_module_css_default.row,
256
377
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
@@ -261,126 +382,199 @@ window.__ModuleLoader__.load({
261
382
  className: PromptEditor_module_css_default.rowText,
262
383
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
263
384
  className: PromptEditor_module_css_default.rowName,
264
- children: "注入 prompt.md 提示词"
385
+ children: "注入 preset.md(锚定层)"
265
386
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
266
387
  className: PromptEditor_module_css_default.rowDesc,
267
- children: "关闭后保留首轮工具引导,但不向会话注入 prompt.md"
388
+ children: "开启时由 preset 在 we 锚定确认后注入一次 preset.md;关闭仅保留首轮工具引导"
268
389
  })]
269
390
  })]
270
391
  }),
392
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
393
+ className: PromptEditor_module_css_default.textarea,
394
+ "aria-label": "preset.md 内容",
395
+ value: fields.promptText,
396
+ onChange: (e) => patch({ promptText: e.target.value }),
397
+ spellCheck: false
398
+ }),
399
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
400
+ className: PromptEditor_module_css_default.actions,
401
+ children: [
402
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
403
+ type: "button",
404
+ className: PromptEditor_module_css_default.primary,
405
+ disabled: savingPrompt || savingAgents || !dirtyPrompt,
406
+ onClick: savePrompt,
407
+ children: savingPrompt ? "保存中…" : "保存"
408
+ }),
409
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
410
+ type: "button",
411
+ className: PromptEditor_module_css_default.secondary,
412
+ disabled: !dirtyPrompt,
413
+ onClick: discardPrompt,
414
+ children: "还原"
415
+ }),
416
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
417
+ type: "button",
418
+ className: PromptEditor_module_css_default.secondary,
419
+ onClick: openEdit,
420
+ children: "打开"
421
+ })
422
+ ]
423
+ })
424
+ ]
425
+ }),
426
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
427
+ title: "AGENTS设置",
428
+ hint: dirtyAgents || agentsSwitchDirty ? "未保存" : void 0,
429
+ open: agentsOpen,
430
+ onToggle: () => setAgentsOpen(!agentsOpen),
431
+ children: [
271
432
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
272
433
  className: PromptEditor_module_css_default.row,
273
434
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
274
435
  type: "checkbox",
275
- checked: fields.anchorFirstTurn,
276
- onChange: () => toggle("anchorFirstTurn")
436
+ checked: fields.writeAgents,
437
+ onChange: () => toggle("writeAgents")
277
438
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
278
439
  className: PromptEditor_module_css_default.rowText,
279
440
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
280
441
  className: PromptEditor_module_css_default.rowName,
281
- children: "首轮独立锚定轮"
442
+ children: "写入 ~/.dsh/AGENTS.md"
282
443
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
283
444
  className: PromptEditor_module_css_default.rowDesc,
284
- children: "首个用户消息先入 next-step,首步只发锚定句"
445
+ children: "开启时把项目 AGENTS.md 写入常驻层;关闭时不写入,已有文件保持原样"
285
446
  })]
286
447
  })]
287
448
  }),
449
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
450
+ className: PromptEditor_module_css_default.textarea,
451
+ "aria-label": "AGENTS.md 内容",
452
+ value: fields.agentsText,
453
+ onChange: (e) => patch({ agentsText: e.target.value }),
454
+ spellCheck: false
455
+ }),
456
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
457
+ className: PromptEditor_module_css_default.actions,
458
+ children: [
459
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
460
+ type: "button",
461
+ className: PromptEditor_module_css_default.primary,
462
+ disabled: savingPrompt || savingAgents || !dirtyAgents,
463
+ onClick: saveAgents,
464
+ children: savingAgents ? "保存中…" : "保存"
465
+ }),
466
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
467
+ type: "button",
468
+ className: PromptEditor_module_css_default.secondary,
469
+ disabled: !dirtyAgents,
470
+ onClick: discardAgents,
471
+ children: "还原"
472
+ }),
473
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
474
+ type: "button",
475
+ className: PromptEditor_module_css_default.secondary,
476
+ onClick: openAgents,
477
+ children: "打开"
478
+ })
479
+ ]
480
+ })
481
+ ]
482
+ }),
483
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
484
+ title: "Skills设置",
485
+ hint: `${fields.skillCatalog.length} 个${skillsSwitchDirty ? " · 未保存" : ""}`,
486
+ open: skillsOpen,
487
+ onToggle: () => setSkillsOpen(!skillsOpen),
488
+ children: fields.skillCatalog.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
489
+ className: PromptEditor_module_css_default.subEmpty,
490
+ children: "skills 目录下没有技能"
491
+ }) : fields.skillCatalog.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
492
+ className: PromptEditor_module_css_default.row,
493
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
494
+ type: "checkbox",
495
+ checked: skillEnabled(skill.folder),
496
+ onChange: () => toggleSkill(skill.folder)
497
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
498
+ className: PromptEditor_module_css_default.rowText,
499
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
500
+ className: PromptEditor_module_css_default.rowName,
501
+ children: skill.name || skill.folder
502
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
503
+ className: PromptEditor_module_css_default.rowDesc,
504
+ children: [
505
+ "skills/",
506
+ skill.folder,
507
+ skill.description ? ` · ${skill.description}` : ""
508
+ ]
509
+ })]
510
+ })]
511
+ }, skill.folder))
512
+ }),
513
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
514
+ title: "锚定轮与 preset",
515
+ hint: presetSwitchDirty ? "未保存" : void 0,
516
+ open: presetOpen,
517
+ onToggle: () => setPresetOpen(!presetOpen),
518
+ children: [
288
519
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
289
- className: clsx(PromptEditor_module_css_default.rowStack, !fields.anchorFirstTurn && PromptEditor_module_css_default.rowDisabled),
290
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
520
+ className: PromptEditor_module_css_default.row,
521
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
522
+ type: "checkbox",
523
+ checked: fields.writePreset,
524
+ onChange: () => toggle("writePreset")
525
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
291
526
  className: PromptEditor_module_css_default.rowText,
292
527
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
293
528
  className: PromptEditor_module_css_default.rowName,
294
- children: "锚定句文本(可自定义)"
529
+ children: "生成锚定注入 preset"
295
530
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
296
531
  className: PromptEditor_module_css_default.rowDesc,
297
- children: "独立锚定轮发给模型的输入内容"
532
+ children: "开启时生成并刷新 preset 目录,承载首轮工具引导与上述注入件;关闭时不生成,已有文件保持原样"
298
533
  })]
299
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
300
- className: PromptEditor_module_css_default.anchorInput,
301
- value: fields.anchorText,
302
- disabled: !fields.anchorFirstTurn,
303
- onChange: (e) => patch({ anchorText: e.target.value }),
304
- spellCheck: false
305
534
  })]
306
535
  }),
307
536
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
308
537
  className: PromptEditor_module_css_default.row,
309
538
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
310
539
  type: "checkbox",
311
- checked: fields.writeAgents,
312
- onChange: () => toggle("writeAgents")
540
+ checked: fields.anchorFirstTurn,
541
+ onChange: () => toggle("anchorFirstTurn")
313
542
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
314
543
  className: PromptEditor_module_css_default.rowText,
315
544
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
316
545
  className: PromptEditor_module_css_default.rowName,
317
- children: "写入 ~/.dsh/AGENTS.md"
546
+ children: "首轮独立锚定轮"
318
547
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
319
548
  className: PromptEditor_module_css_default.rowDesc,
320
- children: "常驻层规则(默认开启)"
549
+ children: "开启时 preset 挂载 turn-anchor:首个真实用户消息先入 next-step,首步只发锚定句"
321
550
  })]
322
551
  })]
323
552
  }),
324
553
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
325
- className: PromptEditor_module_css_default.row,
326
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
327
- type: "checkbox",
328
- checked: fields.writePreset,
329
- onChange: () => toggle("writePreset")
330
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
554
+ className: clsx(PromptEditor_module_css_default.rowStack, !fields.anchorFirstTurn && PromptEditor_module_css_default.rowDisabled),
555
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
331
556
  className: PromptEditor_module_css_default.rowText,
332
557
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
333
558
  className: PromptEditor_module_css_default.rowName,
334
- children: "生成锚定注入 preset"
559
+ children: "锚定句文本(可自定义)"
335
560
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
336
561
  className: PromptEditor_module_css_default.rowDesc,
337
- children: "prompt.md 注入与首轮工具引导(默认开启)"
562
+ children: "独立锚定轮发给模型的输入内容"
338
563
  })]
564
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
565
+ className: PromptEditor_module_css_default.anchorInput,
566
+ value: fields.anchorText,
567
+ disabled: !fields.anchorFirstTurn,
568
+ onChange: (e) => patch({ anchorText: e.target.value }),
569
+ onBlur: () => persistSwitches(),
570
+ spellCheck: false
339
571
  })]
340
572
  })
341
573
  ]
342
574
  }),
343
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
344
- className: PromptEditor_module_css_default.group,
345
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
346
- className: PromptEditor_module_css_default.groupTitle,
347
- children: "prompt.md 内容"
348
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
349
- className: PromptEditor_module_css_default.textarea,
350
- "aria-label": "prompt.md 内容",
351
- value: fields.promptText,
352
- onChange: (e) => patch({ promptText: e.target.value }),
353
- spellCheck: false
354
- })]
355
- }),
356
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
357
- className: PromptEditor_module_css_default.actions,
358
- children: [
359
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
360
- type: "button",
361
- className: PromptEditor_module_css_default.primary,
362
- disabled: saving || !dirty,
363
- onClick: save,
364
- children: saving ? "保存中…" : "保存"
365
- }),
366
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
367
- type: "button",
368
- className: PromptEditor_module_css_default.secondary,
369
- disabled: !dirty,
370
- onClick: discard,
371
- children: "还原"
372
- }),
373
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
374
- type: "button",
375
- className: PromptEditor_module_css_default.secondary,
376
- onClick: openEdit,
377
- children: "打开编辑"
378
- }),
379
- notice && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
380
- className: clsx(PromptEditor_module_css_default.notice, noticeKind === "error" && PromptEditor_module_css_default.noticeError),
381
- children: notice
382
- })
383
- ]
575
+ notice && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
576
+ className: clsx(PromptEditor_module_css_default.notice, noticeKind === "error" && PromptEditor_module_css_default.noticeError),
577
+ children: notice
384
578
  })
385
579
  ]
386
580
  })]