dsh-plugin-prompt-tool 0.1.0 → 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,22 +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: "",
79
+ injectPrompt: true,
80
+ skillSwitches: {},
81
+ skillCatalog: [],
71
82
  writeAgents: true,
72
83
  writePreset: true
73
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
+ }
74
127
  function PromptEditor(props) {
75
128
  const { api } = props;
76
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);
77
134
  const [loaded, setLoaded] = (0, react.useState)(false);
78
135
  const [fields, setFields] = (0, react.useState)(EMPTY);
79
- const [saved, setSaved] = (0, react.useState)(void 0);
80
- 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);
81
139
  const [loading, setLoading] = (0, react.useState)(false);
82
- const [saving, setSaving] = (0, react.useState)(false);
140
+ const [savingPrompt, setSavingPrompt] = (0, react.useState)(false);
141
+ const [savingAgents, setSavingAgents] = (0, react.useState)(false);
83
142
  const [notice, setNotice] = (0, react.useState)("");
84
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());
85
147
  const showNotice = (0, react.useCallback)((kind, message) => {
86
148
  setNotice(message);
87
149
  setNoticeKind(kind);
@@ -99,17 +161,27 @@ window.__ModuleLoader__.load({
99
161
  showNotice("error", "未找到提示词工具配置");
100
162
  return;
101
163
  }
164
+ const value = ns.value ?? {};
165
+ const base = ns.base ?? {};
102
166
  const next = {
103
- promptText: ns.value?.promptText ?? "",
104
- promptPath: ns.value?.promptPath ?? "",
105
- anchorFirstTurn: ns.value?.anchorFirstTurn ?? false,
106
- anchorText: ns.value?.anchorText ?? "",
107
- writeAgents: ns.value?.writeAgents ?? true,
108
- 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
109
178
  };
179
+ fieldsRef.current = next;
110
180
  setFields(next);
111
- setSaved(JSON.stringify(next));
112
- setRevision(ns.revision);
181
+ setSavedPromptText(next.promptText);
182
+ setSavedAgentsText(next.agentsText);
183
+ setSavedSwitches(snapshotSwitches(next));
184
+ revisionRef.current = ns.revision;
113
185
  setNotice("");
114
186
  } catch (e) {
115
187
  showNotice("error", "读取失败:" + (e?.message ?? e));
@@ -122,13 +194,17 @@ window.__ModuleLoader__.load({
122
194
  const res = await api.settings.describe({});
123
195
  if (!res.result.ok) return;
124
196
  const ns = res.result.value.namespaces.find((n) => n.ns === NS);
125
- if (ns) setRevision(ns.revision);
197
+ if (ns) revisionRef.current = ns.revision;
126
198
  } catch {}
127
199
  }, [api]);
128
- const patch = (partial) => setFields((prev) => ({
129
- ...prev,
130
- ...partial
131
- }));
200
+ const patch = (partial) => {
201
+ const next = {
202
+ ...fieldsRef.current,
203
+ ...partial
204
+ };
205
+ fieldsRef.current = next;
206
+ setFields(next);
207
+ };
132
208
  const toggleOpen = () => {
133
209
  const next = !open;
134
210
  setOpen(next);
@@ -138,9 +214,6 @@ window.__ModuleLoader__.load({
138
214
  load();
139
215
  } else if (!dirty) load();
140
216
  };
141
- const discard = () => {
142
- load();
143
- };
144
217
  const openEdit = async () => {
145
218
  if (!fields.promptPath) {
146
219
  showNotice("error", "路径未知,请先保存一次或在下方直接编辑");
@@ -148,64 +221,120 @@ window.__ModuleLoader__.load({
148
221
  }
149
222
  try {
150
223
  const res = await api.host.openPath({ path: fields.promptPath });
151
- if (res.result.ok) showNotice("ok", "已用系统编辑器打开 prompt.md");
224
+ if (res.result.ok) showNotice("ok", "已用系统编辑器打开 preset.md");
152
225
  else showNotice("error", "打开失败:" + (res.result.error?.message ?? "") + ";可直接在下方编辑框保存");
153
226
  } catch (e) {
154
227
  showNotice("error", "打开失败:" + (e?.message ?? e) + ";可直接在下方编辑框保存");
155
228
  }
156
229
  };
157
- const save = async () => {
158
- if (saving) return;
159
- setSaving(true);
230
+ const openAgents = async () => {
231
+ if (!fields.agentsPath) {
232
+ showNotice("error", "路径未知,请先保存一次或在下方直接编辑");
233
+ return;
234
+ }
160
235
  try {
161
- const res = await api.settings.mutate({
162
- ns: NS,
163
- ops: [
164
- {
165
- op: "set",
166
- path: ["promptText"],
167
- value: fields.promptText
168
- },
169
- {
170
- op: "set",
171
- path: ["anchorFirstTurn"],
172
- value: fields.anchorFirstTurn
173
- },
174
- {
175
- op: "set",
176
- path: ["anchorText"],
177
- value: fields.anchorText
178
- },
179
- {
180
- op: "set",
181
- path: ["writeAgents"],
182
- value: fields.writeAgents
183
- },
184
- {
185
- op: "set",
186
- path: ["writePreset"],
187
- value: fields.writePreset
188
- }
189
- ],
190
- expectedRevision: revision
191
- });
192
- if (res.result.ok) {
193
- setRevision(res.result.value.revision);
194
- setSaved(JSON.stringify(fields));
195
- showNotice("ok", "已保存并生效");
196
- } 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) {
197
261
  await refreshRevision();
198
- showNotice("error", "保存失败:" + (res.result.error?.message ?? "") + "(已刷新配置版本,可重试)");
262
+ showNotice("error", "保存失败:" + (e?.message ?? e) + "(已刷新配置版本,可重试)");
263
+ } finally {
264
+ setBusy?.(false);
199
265
  }
200
- } catch (e) {
201
- await refreshRevision();
202
- showNotice("error", "保存失败:" + (e?.message ?? e) + "(已刷新配置版本,可重试)");
203
- } finally {
204
- 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
205
312
  }
313
+ ], void 0, () => setSavedSwitches(snapshotSwitches(fieldsRef.current)));
314
+ const toggle = (key) => {
315
+ patch({ [key]: !fieldsRef.current[key] });
316
+ persistSwitches();
206
317
  };
207
- const toggle = (key) => patch({ [key]: !fields[key] });
208
- const dirty = saved !== void 0 && JSON.stringify(fields) !== saved;
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();
326
+ };
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 });
209
338
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
210
339
  className: clsx(PromptEditor_module_css_default.card, open && PromptEditor_module_css_default.cardOpen),
211
340
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
@@ -221,7 +350,7 @@ window.__ModuleLoader__.load({
221
350
  children: "提示词工具"
222
351
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
223
352
  className: PromptEditor_module_css_default.description,
224
- children: "编辑模型行为规范 prompt.md 与注入开关,保存后自动生效"
353
+ children: "编辑 preset.md / AGENTS.md 与各层注入开关,保存后自动生效"
225
354
  })]
226
355
  }),
227
356
  dirty && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -237,63 +366,170 @@ window.__ModuleLoader__.load({
237
366
  className: PromptEditor_module_css_default.loading,
238
367
  children: "正在读取配置…"
239
368
  }),
240
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
241
- 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),
242
374
  children: [
243
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
244
- className: PromptEditor_module_css_default.groupTitle,
245
- children: "注入开关"
246
- }),
247
375
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
248
376
  className: PromptEditor_module_css_default.row,
249
377
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
250
378
  type: "checkbox",
251
- checked: fields.anchorFirstTurn,
252
- onChange: () => toggle("anchorFirstTurn")
379
+ checked: fields.injectPrompt,
380
+ onChange: () => toggle("injectPrompt")
253
381
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
254
382
  className: PromptEditor_module_css_default.rowText,
255
383
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
256
384
  className: PromptEditor_module_css_default.rowName,
257
- children: "首轮独立锚定轮"
385
+ children: "注入 preset.md(锚定层)"
258
386
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
259
387
  className: PromptEditor_module_css_default.rowDesc,
260
- children: "首个用户消息先入 next-step,首步只发锚定句"
388
+ children: "开启时由 preset 在 we 锚定确认后注入一次 preset.md;关闭仅保留首轮工具引导"
261
389
  })]
262
390
  })]
263
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: [
264
432
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
265
- className: clsx(PromptEditor_module_css_default.rowStack, !fields.anchorFirstTurn && PromptEditor_module_css_default.rowDisabled),
266
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
433
+ className: PromptEditor_module_css_default.row,
434
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
435
+ type: "checkbox",
436
+ checked: fields.writeAgents,
437
+ onChange: () => toggle("writeAgents")
438
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
267
439
  className: PromptEditor_module_css_default.rowText,
268
440
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
269
441
  className: PromptEditor_module_css_default.rowName,
270
- children: "锚定句文本(可自定义)"
442
+ children: "写入 ~/.dsh/AGENTS.md"
271
443
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
272
444
  className: PromptEditor_module_css_default.rowDesc,
273
- children: "独立锚定轮发给模型的输入内容"
445
+ children: "开启时把项目 AGENTS.md 写入常驻层;关闭时不写入,已有文件保持原样"
274
446
  })]
275
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
276
- className: PromptEditor_module_css_default.anchorInput,
277
- value: fields.anchorText,
278
- disabled: !fields.anchorFirstTurn,
279
- onChange: (e) => patch({ anchorText: e.target.value }),
280
- spellCheck: false
281
447
  })]
282
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: [
283
519
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
284
520
  className: PromptEditor_module_css_default.row,
285
521
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
286
522
  type: "checkbox",
287
- checked: fields.writeAgents,
288
- onChange: () => toggle("writeAgents")
523
+ checked: fields.writePreset,
524
+ onChange: () => toggle("writePreset")
289
525
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
290
526
  className: PromptEditor_module_css_default.rowText,
291
527
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
292
528
  className: PromptEditor_module_css_default.rowName,
293
- children: "写入 ~/.dsh/AGENTS.md"
529
+ children: "生成锚定注入 preset"
294
530
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
295
531
  className: PromptEditor_module_css_default.rowDesc,
296
- children: "常驻层规则(默认开启)"
532
+ children: "开启时生成并刷新 preset 目录,承载首轮工具引导与上述注入件;关闭时不生成,已有文件保持原样"
297
533
  })]
298
534
  })]
299
535
  }),
@@ -301,62 +537,44 @@ window.__ModuleLoader__.load({
301
537
  className: PromptEditor_module_css_default.row,
302
538
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
303
539
  type: "checkbox",
304
- checked: fields.writePreset,
305
- onChange: () => toggle("writePreset")
540
+ checked: fields.anchorFirstTurn,
541
+ onChange: () => toggle("anchorFirstTurn")
306
542
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
307
543
  className: PromptEditor_module_css_default.rowText,
308
544
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
309
545
  className: PromptEditor_module_css_default.rowName,
310
- children: "生成锚定注入 preset"
546
+ children: "首轮独立锚定轮"
311
547
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
312
548
  className: PromptEditor_module_css_default.rowDesc,
313
- children: "prompt.md 注入与首轮工具引导(默认开启)"
549
+ children: "开启时 preset 挂载 turn-anchor:首个真实用户消息先入 next-step,首步只发锚定句"
314
550
  })]
315
551
  })]
316
- })
317
- ]
318
- }),
319
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
320
- className: PromptEditor_module_css_default.group,
321
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
322
- className: PromptEditor_module_css_default.groupTitle,
323
- children: "prompt.md 内容"
324
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
325
- className: PromptEditor_module_css_default.textarea,
326
- "aria-label": "prompt.md 内容",
327
- value: fields.promptText,
328
- onChange: (e) => patch({ promptText: e.target.value }),
329
- spellCheck: false
330
- })]
331
- }),
332
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
333
- className: PromptEditor_module_css_default.actions,
334
- children: [
335
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
336
- type: "button",
337
- className: PromptEditor_module_css_default.primary,
338
- disabled: saving || !dirty,
339
- onClick: save,
340
- children: saving ? "保存中…" : "保存"
341
552
  }),
342
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
343
- type: "button",
344
- className: PromptEditor_module_css_default.secondary,
345
- disabled: !dirty,
346
- onClick: discard,
347
- children: "还原"
348
- }),
349
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
350
- type: "button",
351
- className: PromptEditor_module_css_default.secondary,
352
- onClick: openEdit,
353
- children: "打开编辑"
354
- }),
355
- notice && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
356
- className: clsx(PromptEditor_module_css_default.notice, noticeKind === "error" && PromptEditor_module_css_default.noticeError),
357
- children: notice
553
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
554
+ className: clsx(PromptEditor_module_css_default.rowStack, !fields.anchorFirstTurn && PromptEditor_module_css_default.rowDisabled),
555
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
556
+ className: PromptEditor_module_css_default.rowText,
557
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
558
+ className: PromptEditor_module_css_default.rowName,
559
+ children: "锚定句文本(可自定义)"
560
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
561
+ className: PromptEditor_module_css_default.rowDesc,
562
+ children: "独立锚定轮发给模型的输入内容"
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
571
+ })]
358
572
  })
359
573
  ]
574
+ }),
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
360
578
  })
361
579
  ]
362
580
  })]