dsh-prompt-customizer 0.4.0 → 0.4.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/client/client.js CHANGED
@@ -65,6 +65,8 @@ window.__ModuleLoader__.load({
65
65
  importInvalid: "无效的配置文件",
66
66
  targetGlobal: "目标:全局默认",
67
67
  targetHint: "选择编辑目标 —— 选中某个 agent 预设后,改动只对该预设生效(字段级覆盖)。",
68
+ syncAllPhases: "三态同步",
69
+ syncAllPhasesHint: "勾选后,屏蔽 / 解除屏蔽对三个阶段一起生效(只作用于同名的那一个工具或提示词段)。默认关闭:三个阶段需逐一处理。",
68
70
  broken: "损坏",
69
71
  brokenPreset: "该 agent 预设当前无法挂载,仍可编辑其定制(修复预设后自动生效)。",
70
72
  phaseBootstrap: "仅引导阶段",
@@ -192,6 +194,8 @@ window.__ModuleLoader__.load({
192
194
  importInvalid: "Invalid preset file",
193
195
  targetGlobal: "Target: global default",
194
196
  targetHint: "Pick an edit target — changes apply only to the chosen agent preset (field-level override).",
197
+ syncAllPhases: "Sync phases",
198
+ syncAllPhasesHint: "When checked, blocking / unblocking applies to the same-named tool or section across all three phases at once. Unchecked (default): handle each phase separately.",
195
199
  broken: "broken",
196
200
  brokenPreset: "This agent preset cannot mount right now; its customization is still editable and applies once fixed.",
197
201
  phaseBootstrap: "Bootstrap only",
@@ -703,7 +707,7 @@ window.__ModuleLoader__.load({
703
707
  /**
704
708
  * 计算应用预设时的配置补丁:
705
709
  * - 同名段被覆盖(顺序列表在运行时驱动这一行为)
706
- * - 预设中有、当前提示词中没有的段被添加
710
+ * - 预设中有、当前系统里匹配不上的段默认跳过(跨系统导入不凭空建段)
707
711
  * - 当前有、但不在预设有序列表中的段默认被屏蔽
708
712
  * - 只有预设的「激活段」(在 order 列表且不在其屏蔽名单中)被解除屏蔽;
709
713
  * 预设自己屏蔽的段保持屏蔽。
@@ -715,8 +719,8 @@ window.__ModuleLoader__.load({
715
719
  function applyPresetData(data, cfg, currentNames) {
716
720
  const presetOrder = data.order ?? [];
717
721
  const presetNames = new Set(presetOrder.map((x) => x.name));
718
- const blocked = new Set(data.sections ?? []);
719
- const inject = resolveOrder(presetOrder);
722
+ const blocked = new Set((data.sections ?? []).filter((n) => currentNames.has(n)));
723
+ const inject = resolveOrder(presetOrder.filter((x) => currentNames.has(x.name)));
720
724
  const kept = new Set(inject.map((x) => x.name));
721
725
  let order = inject.length;
722
726
  for (const item of cfg.inject ?? []) {
@@ -914,11 +918,11 @@ window.__ModuleLoader__.load({
914
918
  if (phase === "always") return true;
915
919
  return phase === key;
916
920
  }
917
- /** 该阶段生效的屏蔽名单(全局 + 阶段独立 的 union)。 */
921
+ /** 该阶段自己的屏蔽名单(三态互相独立:引导期 sectionsBootstrap /
922
+ * 压缩受控期 sectionsCompaction / 常驻期 sections,互不继承、互不影响)。 */
918
923
  function deniedNames(cfg, key) {
919
924
  const list = sectionListOf(key);
920
- const phase = list === "bootstrap" ? cfg.sectionsBootstrap ?? [] : list === "compaction" ? cfg.sectionsCompaction ?? [] : [];
921
- return [...cfg.sections ?? [], ...phase];
925
+ return list === "bootstrap" ? cfg.sectionsBootstrap ?? [] : list === "compaction" ? cfg.sectionsCompaction ?? [] : cfg.sections ?? [];
922
926
  }
923
927
  /**
924
928
  * 某个阶段部分当前的注入身份(含未保存草稿)。
@@ -959,48 +963,25 @@ window.__ModuleLoader__.load({
959
963
  }
960
964
  /**
961
965
  * 屏蔽/恢复一个段(当前阶段语义):返回需要写盘的字段补丁。
962
- * - 屏蔽:该阶段名单加入(常驻期直接加入全局 sections)。
963
- * - 恢复:从该阶段名单与全局名单同时移除(union 语义 —— 否则仍会被另一
964
- * 份名单挡住,恢复不生效)。
966
+ * 三态名单互相独立:只读写本阶段自己的名单(引导期 → sectionsBootstrap,
967
+ * 压缩受控期 → sectionsCompaction,常驻期 → sections),屏蔽 / 恢复都绝不
968
+ * 波及其它阶段的名单 —— 一个阶段的屏蔽不再连带屏蔽另一个阶段。
965
969
  * 名单无变化时不产出字段(空补丁 = 不写盘),避免把继承值冻结成无意义的
966
970
  * 空数组覆盖。
967
971
  */
968
972
  function blockPatch(cfg, key, name, blocked) {
969
973
  const list = sectionListOf(key);
970
- const g = (cfg.sections ?? []).slice();
971
- const gi = g.indexOf(name);
972
- const patch = {};
973
- if (list === "global") {
974
- if (blocked) {
975
- if (gi < 0) {
976
- g.push(name);
977
- patch.sections = g;
978
- }
979
- } else if (gi >= 0) {
980
- g.splice(gi, 1);
981
- patch.sections = g;
982
- }
983
- return patch;
984
- }
985
- const field = list === "bootstrap" ? "sectionsBootstrap" : "sectionsCompaction";
986
- const phase = (cfg[field] ?? []).slice();
987
- const pi = phase.indexOf(name);
974
+ const field = list === "bootstrap" ? "sectionsBootstrap" : list === "compaction" ? "sectionsCompaction" : "sections";
975
+ const cur = (cfg[field] ?? []).slice();
976
+ const i = cur.indexOf(name);
988
977
  if (blocked) {
989
- if (pi < 0) {
990
- phase.push(name);
991
- patch[field] = phase;
992
- }
978
+ if (i >= 0) return {};
979
+ cur.push(name);
993
980
  } else {
994
- if (pi >= 0) {
995
- phase.splice(pi, 1);
996
- patch[field] = phase;
997
- }
998
- if (gi >= 0) {
999
- g.splice(gi, 1);
1000
- patch.sections = g;
1001
- }
981
+ if (i < 0) return {};
982
+ cur.splice(i, 1);
1002
983
  }
1003
- return patch;
984
+ return { [field]: cur };
1004
985
  }
1005
986
  /**
1006
987
  * 重排/插入一个阶段的有序行列表:把 dragName 从列表中移除后,插入到
@@ -1050,8 +1031,8 @@ window.__ModuleLoader__.load({
1050
1031
  * - 被屏蔽的段与普通段一模一样:留在列表原位、一样可操作(拖拽/箭头/替换/
1051
1032
  * 恢复/删除),随时可互相转换,唯一区别是屏蔽勾选框与「已屏蔽」徽标 ——
1052
1033
  * 屏蔽只意味着不注入模型,不剥夺任何编辑能力(v1 语义)。屏蔽勾选直接
1053
- * 绑定当前生效名单(deniedNames = 全局 + 该阶段名单,含未保存草稿),
1054
- * 点击立即反馈。
1034
+ * 绑定该阶段自己的名单(deniedNames 三态互相独立,含未保存草稿),
1035
+ * 点击立即反馈 —— 屏蔽一个阶段绝不连带屏蔽另一个阶段。
1055
1036
  * - 重排(拖入/箭头)走 `persistPhase`:把该阶段全部行重写为连续整数
1056
1037
  * 虚拟 order(系统段写空文本 = 仅 order 覆盖,服务端保留原文;custom 段
1057
1038
  * 保留文本)—— UI 行序叠加同一份 order 立即重排,拖动即时可见。
@@ -1067,20 +1048,22 @@ window.__ModuleLoader__.load({
1067
1048
  * 在目标阶段被屏蔽则同步解除屏蔽 —— 行立即出现)。上三段 → 全部:从该
1068
1049
  * 阶段移除 —— custom 段删除注入条目;系统段等价于屏蔽该阶段(系统段是
1069
1050
  * 装配的一部分,无法真正删除)。
1070
- * - 解除屏蔽按份额归属分流写入:继承自全局名单的屏蔽直接改全局名单(立即
1071
- * 写盘通道)—— override 无法表达「比全局更宽松」(空名单回落全局,解除
1072
- * 会在保存后复现);override 自报名单与阶段名单的份额走目标草稿。
1051
+ * - 屏蔽 / 解除屏蔽的写入严格落在当前编辑目标的草稿里(全局默认 → 全局名单;
1052
+ * agent 预设 → 该预设自己的 override,显式空名单也是接管语义)—— 预设之间
1053
+ * 互相独立:在 A 预设里的任何屏蔽 / 解除屏蔽对 B 预设与全局默认零影响。
1073
1054
  * - 每个阶段部分按自己那一阶段的装配结果给出警示:该 scope 有 complete 段
1074
1055
  * 整段接管最终提示词,或本插件产出的段被下游装配规则丢弃时,明确写出
1075
1056
  * 「段级定制不会(完全)进入模型看到的提示词」,避免提示词 Tab 与预览 Tab
1076
1057
  * 各说各话。
1058
+ * - 「三态同步」勾选(默认关):屏蔽勾选对三个阶段一起生效,只作用于同名
1059
+ * 的那一段;拖拽 / 排序仍按各自阶段的副本语义只动本阶段。
1077
1060
  *
1078
1061
  * 全部阶段状态逻辑来自 lib/sectionOps.mjs(纯函数,node --test 单测直接
1079
1062
  * 覆盖同一份代码)。
1080
1063
  */
1081
1064
  /** 编辑态的键:段名之外还要带阶段,否则三个部分里同名的行会同时展开编辑器。 */
1082
1065
  const editKey = (key, name) => `${key}:${name}`;
1083
- function SectionsTab({ cfg, inv, phases, target, globalSections, ownedSections, t, write, writeGlobalField }) {
1066
+ function SectionsTab({ cfg, inv, phases, syncAll, t, write }) {
1084
1067
  const [editing, setEditing] = (0, react.useState)(null);
1085
1068
  const [draft, setDraft] = (0, react.useState)("");
1086
1069
  const [dragName, setDragName] = (0, react.useState)(null);
@@ -1176,27 +1159,13 @@ window.__ModuleLoader__.load({
1176
1159
  return rows;
1177
1160
  };
1178
1161
  const applyBlock = (key, name, blocked) => {
1179
- if (blocked || !target) {
1180
- const patch = blockPatch(cfg, key, name, blocked);
1181
- for (const [field, value] of Object.entries(patch)) {
1182
- if (JSON.stringify(value) === JSON.stringify(cfg[field] ?? [])) continue;
1183
- write(field, value);
1184
- }
1185
- return;
1186
- }
1187
- const patch = blockPatch(cfg, key, name, false);
1188
- const effectiveOwned = ownedSections && ownedSections.length > 0 ? ownedSections : void 0;
1189
- for (const [field, value] of Object.entries(patch)) {
1190
- if (field === "sections") {
1191
- if ((globalSections ?? []).includes(name) && (!effectiveOwned || ownedSections.includes(name))) writeGlobalField("sections", (globalSections ?? []).filter((n) => n !== name));
1192
- if (effectiveOwned?.includes(name)) write("sections", value);
1193
- continue;
1194
- }
1195
- if (JSON.stringify(value) === JSON.stringify(cfg[field] ?? [])) continue;
1196
- write(field, value);
1197
- }
1162
+ const patch = blockPatch(cfg, key, name, blocked);
1163
+ for (const [field, value] of Object.entries(patch)) write(field, value);
1164
+ };
1165
+ const toggleBlocked = (key, name) => {
1166
+ const blocked = !deniedNames(cfg, key).includes(name);
1167
+ for (const k of syncAll ? PART_ORDER : [key]) applyBlock(k, name, blocked);
1198
1168
  };
1199
- const toggleBlocked = (key, name) => applyBlock(key, name, !deniedNames(cfg, key).includes(name));
1200
1169
  const inBaseOf = (key, name) => (phases?.[key]?.baseSections ?? []).some((sec) => sec.name === name);
1201
1170
  const persistPhase = (key, rows) => {
1202
1171
  write("inject", phaseInjectEntries(cfg, key, rows));
@@ -1277,7 +1246,7 @@ window.__ModuleLoader__.load({
1277
1246
  });
1278
1247
  };
1279
1248
  const carryFor = (key, source, name) => {
1280
- if (source.kind !== "all" || source.custom) return {
1249
+ if (source.custom) return {
1281
1250
  text: "",
1282
1251
  blocked: false
1283
1252
  };
@@ -1285,7 +1254,7 @@ window.__ModuleLoader__.load({
1285
1254
  text: "",
1286
1255
  blocked: false
1287
1256
  };
1288
- if (source.text === "") return {
1257
+ if (source.text === "" || source.text.startsWith("<")) return {
1289
1258
  text: "",
1290
1259
  blocked: true
1291
1260
  };
@@ -1312,7 +1281,7 @@ window.__ModuleLoader__.load({
1312
1281
  };
1313
1282
  const next = reorderInsert(rows, dragName, targetName, pos, newRow);
1314
1283
  if (next === null) return;
1315
- if (dragSource.kind === "all" && inBaseOf(key, dragName) && deniedNames(cfg, key).includes(dragName)) applyBlock(key, dragName, false);
1284
+ if (dragSource.key !== key && inBaseOf(key, dragName) && deniedNames(cfg, key).includes(dragName)) applyBlock(key, dragName, false);
1316
1285
  persistPhase(key, next);
1317
1286
  };
1318
1287
  const rowDrop = (e, key, name) => {
@@ -1342,7 +1311,7 @@ window.__ModuleLoader__.load({
1342
1311
  override: carry.text,
1343
1312
  blocked: false
1344
1313
  });
1345
- if (dragSource.kind === "all" && inBaseOf(key, dragName) && deniedNames(cfg, key).includes(dragName)) applyBlock(key, dragName, false);
1314
+ if (dragSource.key !== key && inBaseOf(key, dragName) && deniedNames(cfg, key).includes(dragName)) applyBlock(key, dragName, false);
1346
1315
  persistPhase(key, next);
1347
1316
  } else setNotice(t("sectionDynamicNoAdd", { name: dragName }));
1348
1317
  }
@@ -1388,7 +1357,7 @@ window.__ModuleLoader__.load({
1388
1357
  onDragStart: (e) => startDrag(e, row.name, {
1389
1358
  kind: "part",
1390
1359
  key,
1391
- text: row.custom ? row.override || row.text : "",
1360
+ text: row.override || row.text,
1392
1361
  custom: row.custom
1393
1362
  }),
1394
1363
  onDragOver: (e) => rowDragOver(e, key, row.name),
@@ -1568,8 +1537,10 @@ window.__ModuleLoader__.load({
1568
1537
  * - 拖动:全部 → 某阶段 = 让该工具在这一阶段出现;阶段 → 阶段 = 搬移(源阶段
1569
1538
  * 隐藏 + 目标阶段显示);上三段 → 全部 = 在该阶段隐藏它。
1570
1539
  * - 只有黑名单,没有白名单:每个动作只作用于被拖 / 被点的那一个工具,绝不
1571
- * 因为「拖进了一个工具」而把该阶段其它工具一起关掉。 */
1572
- function ToolsTab({ cfg, inv, phases, t, write }) {
1540
+ * 因为「拖进了一个工具」而把该阶段其它工具一起关掉。
1541
+ * - 「三态同步」勾选(默认关):勾选 / 取消勾选对三个阶段一起生效,只作用
1542
+ * 于同名的那一个工具;拖拽仍按复制 / 搬移语义只动涉及的阶段。 */
1543
+ function ToolsTab({ cfg, inv, phases, syncAll, t, write }) {
1573
1544
  const [dragFrom, setDragFrom] = (0, react.useState)(null);
1574
1545
  const [draggedName, setDraggedName] = (0, react.useState)(null);
1575
1546
  const [dropHint, setDropHint] = (0, react.useState)(null);
@@ -1617,6 +1588,19 @@ window.__ModuleLoader__.load({
1617
1588
  return [...catalog, ...added];
1618
1589
  };
1619
1590
  const toggleHide = (key, name, currentlyHidden) => {
1591
+ if (syncAll) {
1592
+ let tools = cfg.tools;
1593
+ for (const k of PART_ORDER) {
1594
+ const add = addOf(k);
1595
+ const exclude = excludeOf(k);
1596
+ if (currentlyHidden) tools = withPhaseExclude(tools, k, exclude.filter((x) => x !== name));
1597
+ else if (add.includes(name)) tools = withPhaseAdd(tools, k, add.filter((x) => x !== name));
1598
+ else if (!exclude.includes(name)) tools = withPhaseExclude(tools, k, [...exclude, name]);
1599
+ }
1600
+ write("tools", tools);
1601
+ setNotice(null);
1602
+ return;
1603
+ }
1620
1604
  if (currentlyHidden) addToPhase(key, name, false);
1621
1605
  else {
1622
1606
  let exclude = excludeOf(key);
@@ -2337,6 +2321,7 @@ window.__ModuleLoader__.load({
2337
2321
  const [error, setError] = (0, react.useState)(null);
2338
2322
  const [refreshId, setRefreshId] = (0, react.useState)(0);
2339
2323
  const [target, setTarget] = (0, react.useState)(void 0);
2324
+ const [syncAll, setSyncAll] = (0, react.useState)(false);
2340
2325
  const [version, setVersion] = (0, react.useState)(0);
2341
2326
  const [draft, setDraft] = (0, react.useState)(null);
2342
2327
  const [saving, setSaving] = (0, react.useState)(false);
@@ -2457,7 +2442,6 @@ window.__ModuleLoader__.load({
2457
2442
  }).catch((e) => setError(String(e instanceof Error ? e.message : e)));
2458
2443
  };
2459
2444
  const writeGlobal = writeField;
2460
- const writeGlobalField = writeField;
2461
2445
  const resetAll = () => {
2462
2446
  fetch(CONFIG_RESET_URL, {
2463
2447
  method: "POST",
@@ -2546,6 +2530,26 @@ window.__ModuleLoader__.load({
2546
2530
  value: p.id,
2547
2531
  style: s.option
2548
2532
  }, `${p.name}${p.broken ? ` (${t("broken")})` : ""}`))]),
2533
+ tab === "sections" || tab === "tools" ? (0, react.createElement)("label", {
2534
+ style: {
2535
+ ...s.muted,
2536
+ marginLeft: 6,
2537
+ display: "inline-flex",
2538
+ alignItems: "center",
2539
+ gap: 4,
2540
+ cursor: "pointer",
2541
+ whiteSpace: "nowrap"
2542
+ },
2543
+ title: t("syncAllPhasesHint")
2544
+ }, [(0, react.createElement)("input", {
2545
+ type: "checkbox",
2546
+ checked: syncAll,
2547
+ onChange: (e) => setSyncAll(e.target.checked),
2548
+ style: {
2549
+ margin: 0,
2550
+ cursor: "pointer"
2551
+ }
2552
+ }), t("syncAllPhases")]) : null,
2549
2553
  (0, react.createElement)("button", {
2550
2554
  style: draft?.dirty ? s.saveBtnDirty : s.saveBtn,
2551
2555
  disabled: !draft?.dirty || saving,
@@ -2564,18 +2568,16 @@ window.__ModuleLoader__.load({
2564
2568
  cfg: view,
2565
2569
  inv,
2566
2570
  phases,
2567
- target,
2568
- globalSections: cfg.sections,
2569
- ownedSections: target ? cfg.overrides?.[target]?.sections : void 0,
2571
+ syncAll,
2570
2572
  t,
2571
- write: edit,
2572
- writeGlobalField
2573
+ write: edit
2573
2574
  }) : tab === "tools" ? (0, react.createElement)(ToolsTab, {
2574
2575
  cfg: view,
2575
2576
  inv,
2576
2577
  phases,
2577
2578
  t,
2578
- write: edit
2579
+ write: edit,
2580
+ syncAll
2579
2581
  }) : tab === "presets" ? (0, react.createElement)(PresetsTab, {
2580
2582
  cfg: view,
2581
2583
  inv,