dsh-plugin-prompt-tool 0.1.1 → 0.1.3

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,31 +58,148 @@ 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
64
70
  //#region src/client/PromptEditor.tsx
65
- const NS = "prompt-tool";
71
+ const SETTINGS_BRIDGE_PREFIX = "/api/prompt-tool/settings";
72
+ async function bridgePost(path, body) {
73
+ try {
74
+ const payload = await (await fetch(SETTINGS_BRIDGE_PREFIX + path, {
75
+ method: "POST",
76
+ headers: { "content-type": "application/json" },
77
+ body: JSON.stringify(body)
78
+ })).json();
79
+ if (payload !== null && typeof payload === "object") return payload;
80
+ return {
81
+ ok: false,
82
+ message: "settings bridge unavailable"
83
+ };
84
+ } catch (error) {
85
+ return {
86
+ ok: false,
87
+ message: errorMessage(error)
88
+ };
89
+ }
90
+ }
66
91
  const EMPTY = {
67
92
  promptText: "",
68
93
  promptPath: "",
94
+ agentsText: "",
95
+ agentsPath: "",
96
+ injectAgentsPrompt: false,
97
+ anchorFirstTurn: false,
98
+ anchorText: "",
99
+ injectPrompt: true,
100
+ skillSwitches: {},
101
+ skillCatalog: [],
102
+ writeAgents: true,
103
+ writePreset: true
104
+ };
105
+ const EMPTY_SWITCHES = {
106
+ injectAgentsPrompt: false,
69
107
  anchorFirstTurn: false,
70
108
  anchorText: "",
71
109
  injectPrompt: true,
110
+ skillSwitches: {},
72
111
  writeAgents: true,
73
112
  writePreset: true
74
113
  };
114
+ const snapshotSwitches = (fields) => ({
115
+ injectAgentsPrompt: fields.injectAgentsPrompt,
116
+ anchorFirstTurn: fields.anchorFirstTurn,
117
+ anchorText: fields.anchorText,
118
+ injectPrompt: fields.injectPrompt,
119
+ skillSwitches: { ...fields.skillSwitches },
120
+ writeAgents: fields.writeAgents,
121
+ writePreset: fields.writePreset
122
+ });
123
+ const switchesEqual = (a, b) => a.injectAgentsPrompt === b.injectAgentsPrompt && 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;
124
+ const asRecord = (value) => value !== null && typeof value === "object" ? value : {};
125
+ const readString = (source, key) => {
126
+ const value = source[key];
127
+ return typeof value === "string" ? value : void 0;
128
+ };
129
+ const readBoolean = (source, key, fallback) => {
130
+ const value = source[key];
131
+ return typeof value === "boolean" ? value : fallback;
132
+ };
133
+ const readSkillSwitches = (source, key) => {
134
+ const value = source[key];
135
+ if (value === null || typeof value !== "object") return {};
136
+ const entries = Object.entries(value);
137
+ const result = {};
138
+ for (const [name, enabled] of entries) if (typeof enabled === "boolean") result[name] = enabled;
139
+ return result;
140
+ };
141
+ const readSkillCatalog = (source, key) => {
142
+ const value = source[key];
143
+ if (!Array.isArray(value)) return [];
144
+ return value.flatMap((entry) => {
145
+ if (entry === null || typeof entry !== "object") return [];
146
+ const record = entry;
147
+ const folder = readString(record, "folder");
148
+ const name = readString(record, "name");
149
+ if (folder === void 0 || name === void 0) return [];
150
+ return [{
151
+ folder,
152
+ name,
153
+ description: readString(record, "description") ?? ""
154
+ }];
155
+ });
156
+ };
157
+ const errorMessage = (error) => error instanceof Error ? error.message : String(error);
158
+ function CollapsibleSection(props) {
159
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
160
+ className: PromptEditor_module_css_default.subSection,
161
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
162
+ type: "button",
163
+ className: PromptEditor_module_css_default.subHeader,
164
+ "aria-expanded": props.open,
165
+ onClick: props.onToggle,
166
+ children: [
167
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
168
+ className: PromptEditor_module_css_default.subTitle,
169
+ children: props.title
170
+ }),
171
+ props.hint && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
172
+ className: PromptEditor_module_css_default.subCount,
173
+ children: props.hint
174
+ }),
175
+ /* @__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) })
176
+ ]
177
+ }), props.open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
178
+ className: PromptEditor_module_css_default.subBody,
179
+ children: props.children
180
+ })]
181
+ });
182
+ }
75
183
  function PromptEditor(props) {
76
184
  const { api } = props;
77
185
  const [open, setOpen] = (0, react.useState)(false);
186
+ const [promptOpen, setPromptOpen] = (0, react.useState)(false);
187
+ const [agentsOpen, setAgentsOpen] = (0, react.useState)(false);
188
+ const [skillsOpen, setSkillsOpen] = (0, react.useState)(false);
189
+ const [presetOpen, setPresetOpen] = (0, react.useState)(false);
78
190
  const [loaded, setLoaded] = (0, react.useState)(false);
79
191
  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);
192
+ const [savedPromptText, setSavedPromptText] = (0, react.useState)("");
193
+ const [savedAgentsText, setSavedAgentsText] = (0, react.useState)("");
194
+ const [savedSwitches, setSavedSwitches] = (0, react.useState)(EMPTY_SWITCHES);
82
195
  const [loading, setLoading] = (0, react.useState)(false);
83
- const [saving, setSaving] = (0, react.useState)(false);
196
+ const [savingPrompt, setSavingPrompt] = (0, react.useState)(false);
197
+ const [savingAgents, setSavingAgents] = (0, react.useState)(false);
84
198
  const [notice, setNotice] = (0, react.useState)("");
85
199
  const [noticeKind, setNoticeKind] = (0, react.useState)("ok");
200
+ const fieldsRef = (0, react.useRef)(EMPTY);
201
+ const revisionRef = (0, react.useRef)(void 0);
202
+ const saveQueueRef = (0, react.useRef)(Promise.resolve());
86
203
  const showNotice = (0, react.useCallback)((kind, message) => {
87
204
  setNotice(message);
88
205
  setNoticeKind(kind);
@@ -90,47 +207,59 @@ window.__ModuleLoader__.load({
90
207
  const load = (0, react.useCallback)(async () => {
91
208
  setLoading(true);
92
209
  try {
93
- const res = await api.settings.describe({});
94
- if (!res.result.ok) {
95
- showNotice("error", "读取配置失败");
96
- return;
97
- }
98
- const ns = res.result.value.namespaces.find((n) => n.ns === NS);
99
- if (!ns) {
100
- showNotice("error", "未找到提示词工具配置");
210
+ const res = await bridgePost("/describe", {});
211
+ if (!res.ok) {
212
+ showNotice("error", "读取配置失败:" + (res.message ?? ""));
101
213
  return;
102
214
  }
215
+ const ns = res.value;
216
+ const value = asRecord(ns.value);
217
+ const base = asRecord(ns.base);
103
218
  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
219
+ promptText: readString(value, "promptText") ?? readString(base, "promptText") ?? "",
220
+ promptPath: readString(value, "promptPath") ?? readString(base, "promptPath") ?? "",
221
+ agentsText: readString(value, "agentsText") ?? readString(base, "agentsText") ?? "",
222
+ agentsPath: readString(value, "agentsPath") ?? readString(base, "agentsPath") ?? "",
223
+ injectAgentsPrompt: readBoolean(value, "injectAgentsPrompt", readBoolean(base, "injectAgentsPrompt", false)),
224
+ anchorFirstTurn: readBoolean(value, "anchorFirstTurn", readBoolean(base, "anchorFirstTurn", false)),
225
+ anchorText: readString(value, "anchorText") ?? readString(base, "anchorText") ?? "",
226
+ injectPrompt: readBoolean(value, "injectPrompt", readBoolean(base, "injectPrompt", true)),
227
+ skillSwitches: value.skillSwitches !== void 0 || base.skillSwitches !== void 0 ? {
228
+ ...readSkillSwitches(base, "skillSwitches"),
229
+ ...readSkillSwitches(value, "skillSwitches")
230
+ } : {},
231
+ skillCatalog: readSkillCatalog(value, "skillCatalog").length > 0 ? readSkillCatalog(value, "skillCatalog") : readSkillCatalog(base, "skillCatalog"),
232
+ writeAgents: readBoolean(value, "writeAgents", readBoolean(base, "writeAgents", true)),
233
+ writePreset: readBoolean(value, "writePreset", readBoolean(base, "writePreset", true))
111
234
  };
235
+ fieldsRef.current = next;
112
236
  setFields(next);
113
- setSaved(JSON.stringify(next));
114
- setRevision(ns.revision);
237
+ setSavedPromptText(next.promptText);
238
+ setSavedAgentsText(next.agentsText);
239
+ setSavedSwitches(snapshotSwitches(next));
240
+ revisionRef.current = ns.revision;
115
241
  setNotice("");
116
- } catch (e) {
117
- showNotice("error", "读取失败:" + (e?.message ?? e));
242
+ } catch (error) {
243
+ showNotice("error", "读取失败:" + errorMessage(error));
118
244
  } finally {
119
245
  setLoading(false);
120
246
  }
121
- }, [api, showNotice]);
247
+ }, [showNotice]);
122
248
  const refreshRevision = (0, react.useCallback)(async () => {
123
249
  try {
124
- const res = await api.settings.describe({});
125
- if (!res.result.ok) return;
126
- const ns = res.result.value.namespaces.find((n) => n.ns === NS);
127
- if (ns) setRevision(ns.revision);
250
+ const res = await bridgePost("/describe", {});
251
+ if (!res.ok) return;
252
+ revisionRef.current = res.value.revision;
128
253
  } catch {}
129
- }, [api]);
130
- const patch = (partial) => setFields((prev) => ({
131
- ...prev,
132
- ...partial
133
- }));
254
+ }, []);
255
+ const patch = (partial) => {
256
+ const next = {
257
+ ...fieldsRef.current,
258
+ ...partial
259
+ };
260
+ fieldsRef.current = next;
261
+ setFields(next);
262
+ };
134
263
  const toggleOpen = () => {
135
264
  const next = !open;
136
265
  setOpen(next);
@@ -140,9 +269,6 @@ window.__ModuleLoader__.load({
140
269
  load();
141
270
  } else if (!dirty) load();
142
271
  };
143
- const discard = () => {
144
- load();
145
- };
146
272
  const openEdit = async () => {
147
273
  if (!fields.promptPath) {
148
274
  showNotice("error", "路径未知,请先保存一次或在下方直接编辑");
@@ -150,69 +276,120 @@ window.__ModuleLoader__.load({
150
276
  }
151
277
  try {
152
278
  const res = await api.host.openPath({ path: fields.promptPath });
153
- if (res.result.ok) showNotice("ok", "已用系统编辑器打开 prompt.md");
279
+ if (res.result.ok) showNotice("ok", "已用系统编辑器打开 preset.md");
154
280
  else showNotice("error", "打开失败:" + (res.result.error?.message ?? "") + ";可直接在下方编辑框保存");
155
- } catch (e) {
156
- showNotice("error", "打开失败:" + (e?.message ?? e) + ";可直接在下方编辑框保存");
281
+ } catch (error) {
282
+ showNotice("error", "打开失败:" + errorMessage(error) + ";可直接在下方编辑框保存");
157
283
  }
158
284
  };
159
- const save = async () => {
160
- if (saving) return;
161
- setSaving(true);
285
+ const openAgents = async () => {
286
+ if (!fields.agentsPath) {
287
+ showNotice("error", "路径未知,请先保存一次或在下方直接编辑");
288
+ return;
289
+ }
162
290
  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 {
291
+ const res = await api.host.openPath({ path: fields.agentsPath });
292
+ if (res.result.ok) showNotice("ok", "已用系统编辑器打开 AGENTS.md");
293
+ else showNotice("error", "打开失败:" + (res.result.error?.message ?? "") + ";可直接在下方编辑框保存");
294
+ } catch (error) {
295
+ showNotice("error", "打开失败:" + errorMessage(error) + ";可直接在下方编辑框保存");
296
+ }
297
+ };
298
+ const enqueueSave = (0, react.useCallback)((ops, okMessage, onSaved, setBusy) => {
299
+ setBusy?.(true);
300
+ saveQueueRef.current = saveQueueRef.current.then(async () => {
301
+ try {
302
+ const res = await bridgePost("/mutate", {
303
+ ops,
304
+ expectedRevision: revisionRef.current
305
+ });
306
+ if (res.ok) {
307
+ revisionRef.current = res.value.revision;
308
+ onSaved();
309
+ if (okMessage) showNotice("ok", okMessage);
310
+ } else {
311
+ await refreshRevision();
312
+ showNotice("error", "保存失败:" + (res.message ?? "") + "(已刷新配置版本,可重试)");
313
+ }
314
+ } catch (error) {
204
315
  await refreshRevision();
205
- showNotice("error", "保存失败:" + (res.result.error?.message ?? "") + "(已刷新配置版本,可重试)");
316
+ showNotice("error", "保存失败:" + errorMessage(error) + "(已刷新配置版本,可重试)");
317
+ } finally {
318
+ setBusy?.(false);
206
319
  }
207
- } catch (e) {
208
- await refreshRevision();
209
- showNotice("error", "保存失败:" + (e?.message ?? e) + "(已刷新配置版本,可重试)");
210
- } finally {
211
- setSaving(false);
320
+ }).catch(() => {});
321
+ }, [refreshRevision, showNotice]);
322
+ const savePrompt = () => enqueueSave([{
323
+ op: "set",
324
+ path: ["promptText"],
325
+ value: fieldsRef.current.promptText
326
+ }], "preset.md 已保存并生效", () => setSavedPromptText(fieldsRef.current.promptText), setSavingPrompt);
327
+ const saveAgents = () => enqueueSave([{
328
+ op: "set",
329
+ path: ["agentsText"],
330
+ value: fieldsRef.current.agentsText
331
+ }], "AGENTS.md 已保存并生效", () => setSavedAgentsText(fieldsRef.current.agentsText), setSavingAgents);
332
+ const persistSwitches = () => enqueueSave([
333
+ {
334
+ op: "set",
335
+ path: ["injectAgentsPrompt"],
336
+ value: fieldsRef.current.injectAgentsPrompt
337
+ },
338
+ {
339
+ op: "set",
340
+ path: ["anchorFirstTurn"],
341
+ value: fieldsRef.current.anchorFirstTurn
342
+ },
343
+ {
344
+ op: "set",
345
+ path: ["anchorText"],
346
+ value: fieldsRef.current.anchorText
347
+ },
348
+ {
349
+ op: "set",
350
+ path: ["injectPrompt"],
351
+ value: fieldsRef.current.injectPrompt
352
+ },
353
+ {
354
+ op: "set",
355
+ path: ["skillSwitches"],
356
+ value: fieldsRef.current.skillSwitches
357
+ },
358
+ {
359
+ op: "set",
360
+ path: ["writeAgents"],
361
+ value: fieldsRef.current.writeAgents
362
+ },
363
+ {
364
+ op: "set",
365
+ path: ["writePreset"],
366
+ value: fieldsRef.current.writePreset
212
367
  }
368
+ ], void 0, () => setSavedSwitches(snapshotSwitches(fieldsRef.current)));
369
+ const toggle = (key) => {
370
+ patch({ [key]: !fieldsRef.current[key] });
371
+ persistSwitches();
372
+ };
373
+ const skillEnabled = (folder) => fields.skillSwitches[folder] !== false;
374
+ const toggleSkill = (folder) => {
375
+ const enabled = fieldsRef.current.skillSwitches[folder] !== false;
376
+ patch({ skillSwitches: {
377
+ ...fieldsRef.current.skillSwitches,
378
+ [folder]: !enabled
379
+ } });
380
+ persistSwitches();
213
381
  };
214
- const toggle = (key) => patch({ [key]: !fields[key] });
215
- const dirty = saved !== void 0 && JSON.stringify(fields) !== saved;
382
+ const currentSwitches = snapshotSwitches(fields);
383
+ const dirtyPrompt = fields.promptText !== savedPromptText;
384
+ const dirtyAgents = fields.agentsText !== savedAgentsText;
385
+ const dirtySwitches = !switchesEqual(currentSwitches, savedSwitches);
386
+ const dirty = dirtyPrompt || dirtyAgents || dirtySwitches;
387
+ const promptSwitchDirty = fields.injectPrompt !== savedSwitches.injectPrompt;
388
+ const agentsSwitchDirty = fields.injectAgentsPrompt !== savedSwitches.injectAgentsPrompt || fields.writeAgents !== savedSwitches.writeAgents;
389
+ const skillsSwitchDirty = JSON.stringify(fields.skillSwitches) !== JSON.stringify(savedSwitches.skillSwitches);
390
+ const presetSwitchDirty = fields.writePreset !== savedSwitches.writePreset || fields.anchorFirstTurn !== savedSwitches.anchorFirstTurn || fields.anchorText !== savedSwitches.anchorText;
391
+ const discardPrompt = () => patch({ promptText: savedPromptText });
392
+ const discardAgents = () => patch({ agentsText: savedAgentsText });
216
393
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
217
394
  className: clsx(PromptEditor_module_css_default.card, open && PromptEditor_module_css_default.cardOpen),
218
395
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
@@ -228,7 +405,7 @@ window.__ModuleLoader__.load({
228
405
  children: "提示词工具"
229
406
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
230
407
  className: PromptEditor_module_css_default.description,
231
- children: "编辑模型行为规范 prompt.md 与注入开关,保存后自动生效"
408
+ children: "编辑 preset.md / AGENTS.md 与各层注入开关,保存后自动生效"
232
409
  })]
233
410
  }),
234
411
  dirty && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -244,80 +421,189 @@ window.__ModuleLoader__.load({
244
421
  className: PromptEditor_module_css_default.loading,
245
422
  children: "正在读取配置…"
246
423
  }),
247
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
248
- className: PromptEditor_module_css_default.group,
424
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
425
+ title: "Preset预设",
426
+ hint: dirtyPrompt || promptSwitchDirty ? "未保存" : void 0,
427
+ open: promptOpen,
428
+ onToggle: () => setPromptOpen(!promptOpen),
249
429
  children: [
250
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
251
- className: PromptEditor_module_css_default.groupTitle,
252
- children: "注入开关"
253
- }),
254
430
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
255
431
  className: PromptEditor_module_css_default.row,
256
432
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
257
433
  type: "checkbox",
258
434
  checked: fields.injectPrompt,
435
+ disabled: !fields.writePreset,
259
436
  onChange: () => toggle("injectPrompt")
260
437
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
261
438
  className: PromptEditor_module_css_default.rowText,
262
439
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
263
440
  className: PromptEditor_module_css_default.rowName,
264
- children: "注入 prompt.md 提示词"
441
+ children: "注入 preset.md(锚定层)"
265
442
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
266
443
  className: PromptEditor_module_css_default.rowDesc,
267
- children: "关闭后保留首轮工具引导,但不向会话注入 prompt.md"
444
+ children: "注入会话晋升后的 agent/pre-step,消息插入决策消息开头,每会话一次"
268
445
  })]
269
446
  })]
270
447
  }),
448
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
449
+ className: PromptEditor_module_css_default.textarea,
450
+ "aria-label": "preset.md 内容",
451
+ value: fields.promptText,
452
+ onChange: (e) => patch({ promptText: e.target.value }),
453
+ spellCheck: false
454
+ }),
455
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
456
+ className: PromptEditor_module_css_default.actions,
457
+ children: [
458
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
459
+ type: "button",
460
+ className: PromptEditor_module_css_default.primary,
461
+ disabled: savingPrompt || savingAgents || !dirtyPrompt,
462
+ onClick: savePrompt,
463
+ children: savingPrompt ? "保存中…" : "保存"
464
+ }),
465
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
466
+ type: "button",
467
+ className: PromptEditor_module_css_default.secondary,
468
+ disabled: !dirtyPrompt,
469
+ onClick: discardPrompt,
470
+ children: "还原"
471
+ }),
472
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
473
+ type: "button",
474
+ className: PromptEditor_module_css_default.secondary,
475
+ onClick: openEdit,
476
+ children: "打开"
477
+ })
478
+ ]
479
+ })
480
+ ]
481
+ }),
482
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
483
+ title: "AGENTS设置",
484
+ hint: dirtyAgents || agentsSwitchDirty ? "未保存" : void 0,
485
+ open: agentsOpen,
486
+ onToggle: () => setAgentsOpen(!agentsOpen),
487
+ children: [
271
488
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
272
489
  className: PromptEditor_module_css_default.row,
273
490
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
274
491
  type: "checkbox",
275
- checked: fields.anchorFirstTurn,
276
- onChange: () => toggle("anchorFirstTurn")
492
+ checked: fields.injectAgentsPrompt,
493
+ disabled: !fields.writePreset,
494
+ onChange: () => toggle("injectAgentsPrompt")
277
495
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
278
496
  className: PromptEditor_module_css_default.rowText,
279
497
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
280
498
  className: PromptEditor_module_css_default.rowName,
281
- children: "首轮独立锚定轮"
499
+ children: "注入 AGENTS.md"
282
500
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
283
501
  className: PromptEditor_module_css_default.rowDesc,
284
- children: "首个用户消息先入 next-step,首步只发锚定句"
502
+ children: "注入会话晋升后的第一个 agent/pre-step,消息追加在决策消息末尾,每会话一次"
285
503
  })]
286
504
  })]
287
505
  }),
288
506
  /* @__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", {
507
+ className: PromptEditor_module_css_default.row,
508
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
509
+ type: "checkbox",
510
+ checked: fields.writeAgents,
511
+ onChange: () => toggle("writeAgents")
512
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
291
513
  className: PromptEditor_module_css_default.rowText,
292
514
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
293
515
  className: PromptEditor_module_css_default.rowName,
294
- children: "锚定句文本(可自定义)"
516
+ children: "写入 ~/.dsh/AGENTS.md"
295
517
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
296
518
  className: PromptEditor_module_css_default.rowDesc,
297
- children: "独立锚定轮发给模型的输入内容"
519
+ children: "保持 AGENTS.md 的全局常驻注入;关闭后不再写入,已有文件保持原样"
298
520
  })]
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
521
  })]
306
522
  }),
523
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
524
+ className: PromptEditor_module_css_default.textarea,
525
+ "aria-label": "AGENTS.md 内容",
526
+ value: fields.agentsText,
527
+ onChange: (e) => patch({ agentsText: e.target.value }),
528
+ spellCheck: false
529
+ }),
530
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
531
+ className: PromptEditor_module_css_default.actions,
532
+ children: [
533
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
534
+ type: "button",
535
+ className: PromptEditor_module_css_default.primary,
536
+ disabled: savingPrompt || savingAgents || !dirtyAgents,
537
+ onClick: saveAgents,
538
+ children: savingAgents ? "保存中…" : "保存"
539
+ }),
540
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
541
+ type: "button",
542
+ className: PromptEditor_module_css_default.secondary,
543
+ disabled: !dirtyAgents,
544
+ onClick: discardAgents,
545
+ children: "还原"
546
+ }),
547
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
548
+ type: "button",
549
+ className: PromptEditor_module_css_default.secondary,
550
+ onClick: openAgents,
551
+ children: "打开"
552
+ })
553
+ ]
554
+ })
555
+ ]
556
+ }),
557
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
558
+ title: "Skills设置",
559
+ hint: `${fields.skillCatalog.length} 个${skillsSwitchDirty ? " · 未保存" : ""}`,
560
+ open: skillsOpen,
561
+ onToggle: () => setSkillsOpen(!skillsOpen),
562
+ children: fields.skillCatalog.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
563
+ className: PromptEditor_module_css_default.subEmpty,
564
+ children: "skills 目录下没有技能"
565
+ }) : fields.skillCatalog.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
566
+ className: PromptEditor_module_css_default.row,
567
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
568
+ type: "checkbox",
569
+ checked: skillEnabled(skill.folder),
570
+ onChange: () => toggleSkill(skill.folder)
571
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
572
+ className: PromptEditor_module_css_default.rowText,
573
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
574
+ className: PromptEditor_module_css_default.rowName,
575
+ children: skill.name || skill.folder
576
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
577
+ className: PromptEditor_module_css_default.rowDesc,
578
+ children: [
579
+ "skills/",
580
+ skill.folder,
581
+ skill.description ? ` · ${skill.description}` : ""
582
+ ]
583
+ })]
584
+ })]
585
+ }, skill.folder))
586
+ }),
587
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
588
+ title: "锚定轮与 preset",
589
+ hint: presetSwitchDirty ? "未保存" : void 0,
590
+ open: presetOpen,
591
+ onToggle: () => setPresetOpen(!presetOpen),
592
+ children: [
307
593
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
308
594
  className: PromptEditor_module_css_default.row,
309
595
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
310
596
  type: "checkbox",
311
- checked: fields.writeAgents,
312
- onChange: () => toggle("writeAgents")
597
+ checked: fields.writePreset,
598
+ onChange: () => toggle("writePreset")
313
599
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
314
600
  className: PromptEditor_module_css_default.rowText,
315
601
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
316
602
  className: PromptEditor_module_css_default.rowName,
317
- children: "写入 ~/.dsh/AGENTS.md"
603
+ children: "生成锚定注入 preset"
318
604
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
319
605
  className: PromptEditor_module_css_default.rowDesc,
320
- children: "常驻层规则(默认开启)"
606
+ children: "开启时生成并刷新 preset 目录,承载首轮工具引导与上述注入件;关闭时不生成,已有文件保持原样"
321
607
  })]
322
608
  })]
323
609
  }),
@@ -325,62 +611,45 @@ window.__ModuleLoader__.load({
325
611
  className: PromptEditor_module_css_default.row,
326
612
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
327
613
  type: "checkbox",
328
- checked: fields.writePreset,
329
- onChange: () => toggle("writePreset")
614
+ checked: fields.anchorFirstTurn,
615
+ disabled: !fields.writePreset,
616
+ onChange: () => toggle("anchorFirstTurn")
330
617
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
331
618
  className: PromptEditor_module_css_default.rowText,
332
619
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
333
620
  className: PromptEditor_module_css_default.rowName,
334
- children: "生成锚定注入 preset"
621
+ children: "首轮独立锚定轮"
335
622
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
336
623
  className: PromptEditor_module_css_default.rowDesc,
337
- children: "prompt.md 注入与首轮工具引导(默认开启)"
624
+ children: "开启时 preset 挂载 turn-anchor:首个真实用户消息先入 next-step,首步只发锚定句"
338
625
  })]
339
626
  })]
340
- })
341
- ]
342
- }),
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
627
  }),
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
628
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
629
+ className: clsx(PromptEditor_module_css_default.rowStack, !fields.anchorFirstTurn && PromptEditor_module_css_default.rowDisabled),
630
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
631
+ className: PromptEditor_module_css_default.rowText,
632
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
633
+ className: PromptEditor_module_css_default.rowName,
634
+ children: "锚定句文本(可自定义)"
635
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
636
+ className: PromptEditor_module_css_default.rowDesc,
637
+ children: "独立锚定轮发给模型的输入内容"
638
+ })]
639
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
640
+ className: PromptEditor_module_css_default.anchorInput,
641
+ value: fields.anchorText,
642
+ disabled: !fields.writePreset || !fields.anchorFirstTurn,
643
+ onChange: (e) => patch({ anchorText: e.target.value }),
644
+ onBlur: () => persistSwitches(),
645
+ spellCheck: false
646
+ })]
382
647
  })
383
648
  ]
649
+ }),
650
+ notice && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
651
+ className: clsx(PromptEditor_module_css_default.notice, noticeKind === "error" && PromptEditor_module_css_default.noticeError),
652
+ children: notice
384
653
  })
385
654
  ]
386
655
  })]