@max-null/dsh-memory 0.22.1 → 0.22.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/README.md +15 -4
- package/client.js +72 -80
- package/dist/engine.d.ts +6 -4
- package/dist/index.d.ts +46 -6
- package/dist/index.js +56 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -148,9 +148,19 @@ DSH 存储层的写入是「读—改—写全量覆盖」,且官方两个后
|
|
|
148
148
|
|
|
149
149
|
把本机记忆镜像到一个 git 仓库,多个环境之间共享同一份记忆。
|
|
150
150
|
|
|
151
|
-
>
|
|
152
|
-
>
|
|
153
|
-
>
|
|
151
|
+
> **默认不启用,而且是显式开关。** `memory.sync.enabled` 为 `true` 时才注册
|
|
152
|
+
> `memory_sync` 与 `memory_conflicts`——关着时**根本不注册**(模型看不到,也就不会去调),
|
|
153
|
+
> 自述里的工具数停在 14。
|
|
154
|
+
>
|
|
155
|
+
> 判据**不是「配了 remote」**(2026-11-12 改):填了目标地址不等于打开了功能,那会让一次
|
|
156
|
+
> 配置试验变成意外开机。开关与目标解耦后,还多出一档「只开本地镜像、暂不推送」
|
|
157
|
+
> ——此时 `remote` 留空即可,push 会明确回「未配置远端地址 —— 已完成本地提交」。
|
|
158
|
+
>
|
|
159
|
+
> 开关在插件页「记忆」详情里点(与日记开关同一张卡片)。值写进 **profile 的
|
|
160
|
+
> `cordis.patch.yml`**(`memory.config.sync.enabled`,不是独立的设置文件):该 profile 开了
|
|
161
|
+
> `patchReload: live` 时即时生效,否则**重启实例后生效**——2026-11-12 在 dev 实例里实测,
|
|
162
|
+
> 关掉热重载时点了开关、工具不会立刻出现。`remote` / `mirrorDir` 那几项决定连接目标,
|
|
163
|
+
> 无论哪种情况都要重启。
|
|
154
164
|
|
|
155
165
|
**为什么是「一条记忆一个文件」。** 整份 `memory.json` 放进 git 会撞:2026-11-06 实测,两端各新增一条记忆后 merge,`git` 报 `CONFLICT (content)`——两条记录挤在同一个插入点,按「取一边」解决会**静默丢掉另一端那条**,而 git 仍报「合并成功」。改成一条一个文件后同一场景是 `Merge made by the 'ort' strategy`,0 冲突。
|
|
156
166
|
|
|
@@ -159,8 +169,9 @@ DSH 存储层的写入是「读—改—写全量覆盖」,且官方两个后
|
|
|
159
169
|
```yaml
|
|
160
170
|
memory:
|
|
161
171
|
sync:
|
|
172
|
+
enabled: true # 总开关;关着两个工具根本不注册
|
|
162
173
|
mirrorDir: '' # 缺省 <DSH_HOME>/memory-sync
|
|
163
|
-
remote: '' # https
|
|
174
|
+
remote: '' # https 远端;留空只做本地提交
|
|
164
175
|
branch: main
|
|
165
176
|
envName: '' # 本环境短名,冲突留证时用作后缀
|
|
166
177
|
```
|
package/client.js
CHANGED
|
@@ -104,51 +104,17 @@ function brainIcon() {
|
|
|
104
104
|
strokeLinejoin: "round"
|
|
105
105
|
}, BRAIN_PATHS.map((d, index) => (0, import_react.createElement)("path", { key: index, d })));
|
|
106
106
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
mask: url("data:image/svg+xml,${BRAIN_MASK_SVG}") center / contain no-repeat;
|
|
119
|
-
}
|
|
120
|
-
`;
|
|
121
|
-
var navCssInjected = false;
|
|
122
|
-
function injectNavCss() {
|
|
123
|
-
if (navCssInjected || typeof document === "undefined") return;
|
|
124
|
-
navCssInjected = true;
|
|
125
|
-
const style = document.createElement("style");
|
|
126
|
-
style.setAttribute("data-plugin", "@max-null/dsh-memory");
|
|
127
|
-
style.textContent = NAV_ICON_CSS;
|
|
128
|
-
document.head.append(style);
|
|
129
|
-
}
|
|
130
|
-
function registerSettingsNavIcon(label) {
|
|
131
|
-
let disposed = false;
|
|
132
|
-
const sync = () => {
|
|
133
|
-
if (disposed) return;
|
|
134
|
-
const currentLabel = label().trim();
|
|
135
|
-
const buttons = document.querySelectorAll('[role="dialog"] nav button');
|
|
136
|
-
for (const button of buttons) {
|
|
137
|
-
const matches = currentLabel.length > 0 && button.textContent?.trim() === currentLabel;
|
|
138
|
-
if (matches) button.setAttribute(SETTINGS_NAV_MARKER, "");
|
|
139
|
-
else button.removeAttribute(SETTINGS_NAV_MARKER);
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
sync();
|
|
143
|
-
const observer = new MutationObserver(sync);
|
|
144
|
-
observer.observe(document.body, { childList: true, subtree: true, characterData: true });
|
|
145
|
-
return () => {
|
|
146
|
-
disposed = true;
|
|
147
|
-
observer.disconnect();
|
|
148
|
-
document.querySelectorAll(`[${SETTINGS_NAV_MARKER}]`).forEach((element) => {
|
|
149
|
-
element.removeAttribute(SETTINGS_NAV_MARKER);
|
|
150
|
-
});
|
|
151
|
-
};
|
|
107
|
+
function navIcon(size) {
|
|
108
|
+
return (0, import_react.createElement)("svg", {
|
|
109
|
+
width: size,
|
|
110
|
+
height: size,
|
|
111
|
+
viewBox: "0 0 24 24",
|
|
112
|
+
fill: "none",
|
|
113
|
+
stroke: "currentColor",
|
|
114
|
+
strokeWidth: 2,
|
|
115
|
+
strokeLinecap: "round",
|
|
116
|
+
strokeLinejoin: "round"
|
|
117
|
+
}, BRAIN_PATHS.map((d, index) => (0, import_react.createElement)("path", { key: index, d })));
|
|
152
118
|
}
|
|
153
119
|
var STRINGS = {
|
|
154
120
|
zh: {
|
|
@@ -1031,19 +997,8 @@ function SettingsMemoryView(props) {
|
|
|
1031
997
|
cwd: currentSessionCwd(props.ctx)
|
|
1032
998
|
});
|
|
1033
999
|
}
|
|
1034
|
-
function
|
|
1035
|
-
const {
|
|
1036
|
-
const [enabled, setEnabled] = (0, import_react.useState)(true);
|
|
1037
|
-
(0, import_react.useEffect)(() => {
|
|
1038
|
-
const read = () => {
|
|
1039
|
-
setEnabled(form.getSnapshot()?.value?.diary?.enabled ?? true);
|
|
1040
|
-
};
|
|
1041
|
-
read();
|
|
1042
|
-
const off = form.subscribe(read);
|
|
1043
|
-
return () => {
|
|
1044
|
-
if (typeof off === "function") off();
|
|
1045
|
-
};
|
|
1046
|
-
}, [form]);
|
|
1000
|
+
function SwitchRow(props) {
|
|
1001
|
+
const { label, hint, enabled, ariaLabel, onToggle, divided } = props;
|
|
1047
1002
|
return (0, import_react.createElement)(
|
|
1048
1003
|
"div",
|
|
1049
1004
|
{
|
|
@@ -1053,18 +1008,14 @@ function DiarySettingsCard(props) {
|
|
|
1053
1008
|
justifyContent: "space-between",
|
|
1054
1009
|
gap: 12,
|
|
1055
1010
|
padding: "12px 0",
|
|
1056
|
-
borderTop: "1px solid var(--dsw-alias-border-l2, #1e2836)"
|
|
1011
|
+
...divided ? { borderTop: "1px solid var(--dsw-alias-border-l2, #1e2836)" } : {}
|
|
1057
1012
|
}
|
|
1058
1013
|
},
|
|
1059
1014
|
(0, import_react.createElement)(
|
|
1060
1015
|
"div",
|
|
1061
1016
|
{ style: { minWidth: 0 } },
|
|
1062
|
-
(0, import_react.createElement)("div", { style: { fontSize: 13, fontWeight: 600, color: "var(--dsw-alias-label-primary, #d8e0ea)" } },
|
|
1063
|
-
(0, import_react.createElement)(
|
|
1064
|
-
"div",
|
|
1065
|
-
{ style: { fontSize: 11.5, color: "var(--dsw-alias-label-secondary, #67748a)", marginTop: 3, lineHeight: 1.5 } },
|
|
1066
|
-
"\u672C\u673A\u5199\u65E5\u8BB0\u3002\u5173\u6389\u540E\u65E5\u8BB0\u5DE5\u5177\u62D2\u7EDD\u6267\u884C\u3001\u4E0D\u518D\u6CE8\u5165\u6307\u9488\uFF1B\u5DF2\u6709\u65E5\u8BB0\u6587\u4EF6\u539F\u6837\u4FDD\u7559\uFF0C\u4E0D\u4F1A\u88AB\u5220\u3002"
|
|
1067
|
-
)
|
|
1017
|
+
(0, import_react.createElement)("div", { style: { fontSize: 13, fontWeight: 600, color: "var(--dsw-alias-label-primary, #d8e0ea)" } }, label),
|
|
1018
|
+
(0, import_react.createElement)("div", { style: { fontSize: 11.5, color: "var(--dsw-alias-label-secondary, #67748a)", marginTop: 3, lineHeight: 1.5 } }, hint)
|
|
1068
1019
|
),
|
|
1069
1020
|
(0, import_react.createElement)(
|
|
1070
1021
|
"button",
|
|
@@ -1072,17 +1023,8 @@ function DiarySettingsCard(props) {
|
|
|
1072
1023
|
type: "button",
|
|
1073
1024
|
role: "switch",
|
|
1074
1025
|
"aria-checked": enabled,
|
|
1075
|
-
"aria-label":
|
|
1076
|
-
onClick:
|
|
1077
|
-
try {
|
|
1078
|
-
const pending = form.mutate([{ op: "set", path: ["diary", "enabled"], value: !enabled }]);
|
|
1079
|
-
void Promise.resolve(pending).catch((error) => {
|
|
1080
|
-
console.warn("dsh-memory: \u65E5\u8BB0\u5F00\u5173\u5199\u5165\u5931\u8D25", error);
|
|
1081
|
-
});
|
|
1082
|
-
} catch (error) {
|
|
1083
|
-
console.warn("dsh-memory: \u65E5\u8BB0\u5F00\u5173\u5199\u5165\u629B\u9519", error);
|
|
1084
|
-
}
|
|
1085
|
-
},
|
|
1026
|
+
"aria-label": ariaLabel,
|
|
1027
|
+
onClick: onToggle,
|
|
1086
1028
|
style: {
|
|
1087
1029
|
flex: "none",
|
|
1088
1030
|
width: 40,
|
|
@@ -1110,6 +1052,52 @@ function DiarySettingsCard(props) {
|
|
|
1110
1052
|
)
|
|
1111
1053
|
);
|
|
1112
1054
|
}
|
|
1055
|
+
function MemorySettingsCard(props) {
|
|
1056
|
+
const { form } = props;
|
|
1057
|
+
const [diary, setDiary] = (0, import_react.useState)(true);
|
|
1058
|
+
const [sync, setSync] = (0, import_react.useState)(false);
|
|
1059
|
+
(0, import_react.useEffect)(() => {
|
|
1060
|
+
const read = () => {
|
|
1061
|
+
setDiary(form.getSnapshot()?.value?.diary?.enabled ?? true);
|
|
1062
|
+
setSync(form.getSnapshot()?.value?.sync?.enabled ?? false);
|
|
1063
|
+
};
|
|
1064
|
+
read();
|
|
1065
|
+
const off = form.subscribe(read);
|
|
1066
|
+
return () => {
|
|
1067
|
+
if (typeof off === "function") off();
|
|
1068
|
+
};
|
|
1069
|
+
}, [form]);
|
|
1070
|
+
const write = (path, value, what) => {
|
|
1071
|
+
try {
|
|
1072
|
+
const pending = form.mutate([{ op: "set", path, value }]);
|
|
1073
|
+
void Promise.resolve(pending).catch((error) => {
|
|
1074
|
+
console.warn(`dsh-memory: ${what}\u5F00\u5173\u5199\u5165\u5931\u8D25`, error);
|
|
1075
|
+
});
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
console.warn(`dsh-memory: ${what}\u5F00\u5173\u5199\u5165\u629B\u9519`, error);
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
return (0, import_react.createElement)(
|
|
1081
|
+
"div",
|
|
1082
|
+
null,
|
|
1083
|
+
(0, import_react.createElement)(SwitchRow, {
|
|
1084
|
+
label: "\u65E5\u8BB0",
|
|
1085
|
+
hint: "\u672C\u673A\u5199\u65E5\u8BB0\u3002\u5173\u6389\u540E\u65E5\u8BB0\u5DE5\u5177\u62D2\u7EDD\u6267\u884C\u3001\u4E0D\u518D\u6CE8\u5165\u6307\u9488\uFF1B\u5DF2\u6709\u65E5\u8BB0\u6587\u4EF6\u539F\u6837\u4FDD\u7559\uFF0C\u4E0D\u4F1A\u88AB\u5220\u3002",
|
|
1086
|
+
enabled: diary,
|
|
1087
|
+
ariaLabel: "\u65E5\u8BB0\u5F00\u5173",
|
|
1088
|
+
onToggle: () => write(["diary", "enabled"], !diary, "\u65E5\u8BB0"),
|
|
1089
|
+
divided: false
|
|
1090
|
+
}),
|
|
1091
|
+
(0, import_react.createElement)(SwitchRow, {
|
|
1092
|
+
label: "\u4E91\u540C\u6B65",
|
|
1093
|
+
hint: "\u628A\u672C\u673A\u8BB0\u5FC6\u955C\u50CF\u5230\u4E00\u4E2A git \u4ED3\u5E93\uFF0C\u591A\u4E2A\u73AF\u5883\u5171\u7528\u4E00\u4EFD\u8BB0\u5FC6\u3002\u5173\u7740\u65F6 memory_sync \u4E0E memory_conflicts \u6839\u672C\u4E0D\u6CE8\u518C\u3002\u6253\u5F00\u540E\u8FD8\u9700\u8981\u5728\u63D2\u4EF6\u914D\u7F6E\u91CC\u586B sync.remote\uFF08\u7559\u7A7A = \u53EA\u505A\u672C\u5730\u955C\u50CF\u4E0E\u63D0\u4EA4\uFF09\u3002\u5F00\u5173\u5199\u5165 profile \u7684 cordis.patch.yml\uFF1Aprofile \u5F00 patchReload: live \u65F6\u5373\u65F6\u751F\u6548\uFF0C\u5426\u5219\u91CD\u542F\u5B9E\u4F8B\u540E\u751F\u6548\u3002",
|
|
1094
|
+
enabled: sync,
|
|
1095
|
+
ariaLabel: "\u4E91\u540C\u6B65\u5F00\u5173",
|
|
1096
|
+
onToggle: () => write(["sync", "enabled"], !sync, "\u4E91\u540C\u6B65"),
|
|
1097
|
+
divided: true
|
|
1098
|
+
})
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1113
1101
|
function apply(ctx) {
|
|
1114
1102
|
const face = ctx;
|
|
1115
1103
|
const locale = face.get?.("locale");
|
|
@@ -1120,7 +1108,6 @@ function apply(ctx) {
|
|
|
1120
1108
|
});
|
|
1121
1109
|
const slots = ctx.slots;
|
|
1122
1110
|
if (slots?.inject !== void 0) {
|
|
1123
|
-
injectNavCss();
|
|
1124
1111
|
slots.inject("settings.section", () => slots.register({
|
|
1125
1112
|
name: "settings.section",
|
|
1126
1113
|
id: "dsh-memory",
|
|
@@ -1130,7 +1117,12 @@ function apply(ctx) {
|
|
|
1130
1117
|
}, () => (0, import_react.createElement)(SettingsMemoryView, {
|
|
1131
1118
|
ctx
|
|
1132
1119
|
})));
|
|
1133
|
-
|
|
1120
|
+
slots.inject("settings.section.icon", () => slots.register({
|
|
1121
|
+
name: "settings.section.icon",
|
|
1122
|
+
id: "dsh-memory",
|
|
1123
|
+
order: 60,
|
|
1124
|
+
label: () => STRINGS[localeId].tabMemory
|
|
1125
|
+
}, (props) => navIcon(props?.size ?? 16)));
|
|
1134
1126
|
}
|
|
1135
1127
|
try {
|
|
1136
1128
|
const configForms = face.get?.("configForms");
|
|
@@ -1140,10 +1132,10 @@ function apply(ctx) {
|
|
|
1140
1132
|
name: "plugins.bundle.config",
|
|
1141
1133
|
key: "@max-null/dsh-memory",
|
|
1142
1134
|
inject: () => ({})
|
|
1143
|
-
}, () => (0, import_react.createElement)(
|
|
1135
|
+
}, () => (0, import_react.createElement)(MemorySettingsCard, { form: settingsForm })));
|
|
1144
1136
|
}
|
|
1145
1137
|
} catch (error) {
|
|
1146
|
-
console.warn("dsh-memory: \
|
|
1138
|
+
console.warn("dsh-memory: \u8BBE\u7F6E\u5361\u7247\u672A\u6CE8\u518C\uFF08\u4E0D\u5F71\u54CD\u8BB0\u5FC6\u4E0E\u65E5\u8BB0\u5DE5\u5177\uFF09", error);
|
|
1147
1139
|
}
|
|
1148
1140
|
const root = ctx;
|
|
1149
1141
|
if (root.inject === void 0) return;
|
package/dist/engine.d.ts
CHANGED
|
@@ -370,14 +370,16 @@ export interface MemoryConfig {
|
|
|
370
370
|
*/
|
|
371
371
|
backup?: MemoryBackupConfig;
|
|
372
372
|
/**
|
|
373
|
-
* 云同步配置(2026-11-11
|
|
373
|
+
* 云同步配置(2026-11-11;2026-11-12 改成显式开关):
|
|
374
|
+
* `{ enabled, mirrorDir, remote?, branch?, envName? }`。
|
|
374
375
|
*
|
|
375
|
-
*
|
|
376
|
+
* **开关是 `enabled`,不是「配了 remote 就算开」**——与日记同一个模式。缺省不配 = 不同步。
|
|
377
|
+
* 设计见 `docs/设计/2026-11-06-记忆云同步-设计方案.md`。
|
|
376
378
|
*/
|
|
377
379
|
sync?: unknown;
|
|
378
380
|
/**
|
|
379
|
-
* 日记开关的**部署默认值**(2026-10-24
|
|
380
|
-
*
|
|
381
|
+
* 日记开关的**部署默认值**(2026-10-24)。运行中的选择在面板里改,值写进
|
|
382
|
+
* profile 的 `cordis.patch.yml`(**不另设存储文件**,见设计文档 §四);两边都开才开——否则部署时关掉会被
|
|
381
383
|
* 使用者那一侧缺省的 `true` 翻回来。
|
|
382
384
|
*
|
|
383
385
|
* 关掉时日记工具会拒绝写入、不注入指针、也不提醒;已有的 md 文件原样保留
|
package/dist/index.d.ts
CHANGED
|
@@ -34,11 +34,31 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
34
34
|
embeddings: z<any, any, "plain">;
|
|
35
35
|
backup: z<any, any, "plain">;
|
|
36
36
|
/**
|
|
37
|
-
* 云同步配置(2026-11-11
|
|
37
|
+
* 云同步配置(2026-11-11;2026-11-12 改成显式开关)。
|
|
38
38
|
*
|
|
39
|
-
*
|
|
39
|
+
* **用 `enabled` 开关,而不是「配了 remote 就算开」**:与日记同一个模式。语义清楚得多
|
|
40
|
+
* ——「我打开了这个功能」和「我碰巧填了一个字段」是两件事;而且开启后可以只配一部分
|
|
41
|
+
* (比如只做本地镜像、暂不推送)。
|
|
42
|
+
*
|
|
43
|
+
* `volatile()` 让 `enabled` 能在运行时改:设置面板把值写进 profile 的
|
|
44
|
+
* `cordis.patch.yml`,是否即时生效取决于该 profile 的 `patchReload`(`live` 才热重载)。
|
|
45
|
+
* 其余字段(remote / branch 等)改了要重启——它们决定的是连接目标,不是临时状态。
|
|
46
|
+
*
|
|
47
|
+
* 默认 **关闭**:实验性功能不该影响别人的安装。
|
|
40
48
|
*/
|
|
41
|
-
sync: z<
|
|
49
|
+
sync: z<NoInfer<Schemastery.ObjectS<NoInfer<{
|
|
50
|
+
enabled: z<boolean, boolean, "defined">;
|
|
51
|
+
mirrorDir: z<string, string, "defined">;
|
|
52
|
+
remote: z<string, string, "defined">;
|
|
53
|
+
branch: z<string, string, "defined">;
|
|
54
|
+
envName: z<string, string, "defined">;
|
|
55
|
+
}>>>, NoInfer<Schemastery.ObjectT<NoInfer<{
|
|
56
|
+
enabled: z<boolean, boolean, "defined">;
|
|
57
|
+
mirrorDir: z<string, string, "defined">;
|
|
58
|
+
remote: z<string, string, "defined">;
|
|
59
|
+
branch: z<string, string, "defined">;
|
|
60
|
+
envName: z<string, string, "defined">;
|
|
61
|
+
}>>>, "volatile-defined">;
|
|
42
62
|
/**
|
|
43
63
|
* 日记开关。默认开——这是**功能默认,不是采集默认**。
|
|
44
64
|
*
|
|
@@ -61,11 +81,31 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
61
81
|
embeddings: z<any, any, "plain">;
|
|
62
82
|
backup: z<any, any, "plain">;
|
|
63
83
|
/**
|
|
64
|
-
* 云同步配置(2026-11-11
|
|
84
|
+
* 云同步配置(2026-11-11;2026-11-12 改成显式开关)。
|
|
65
85
|
*
|
|
66
|
-
*
|
|
86
|
+
* **用 `enabled` 开关,而不是「配了 remote 就算开」**:与日记同一个模式。语义清楚得多
|
|
87
|
+
* ——「我打开了这个功能」和「我碰巧填了一个字段」是两件事;而且开启后可以只配一部分
|
|
88
|
+
* (比如只做本地镜像、暂不推送)。
|
|
89
|
+
*
|
|
90
|
+
* `volatile()` 让 `enabled` 能在运行时改:设置面板把值写进 profile 的
|
|
91
|
+
* `cordis.patch.yml`,是否即时生效取决于该 profile 的 `patchReload`(`live` 才热重载)。
|
|
92
|
+
* 其余字段(remote / branch 等)改了要重启——它们决定的是连接目标,不是临时状态。
|
|
93
|
+
*
|
|
94
|
+
* 默认 **关闭**:实验性功能不该影响别人的安装。
|
|
67
95
|
*/
|
|
68
|
-
sync: z<
|
|
96
|
+
sync: z<NoInfer<Schemastery.ObjectS<NoInfer<{
|
|
97
|
+
enabled: z<boolean, boolean, "defined">;
|
|
98
|
+
mirrorDir: z<string, string, "defined">;
|
|
99
|
+
remote: z<string, string, "defined">;
|
|
100
|
+
branch: z<string, string, "defined">;
|
|
101
|
+
envName: z<string, string, "defined">;
|
|
102
|
+
}>>>, NoInfer<Schemastery.ObjectT<NoInfer<{
|
|
103
|
+
enabled: z<boolean, boolean, "defined">;
|
|
104
|
+
mirrorDir: z<string, string, "defined">;
|
|
105
|
+
remote: z<string, string, "defined">;
|
|
106
|
+
branch: z<string, string, "defined">;
|
|
107
|
+
envName: z<string, string, "defined">;
|
|
108
|
+
}>>>, "volatile-defined">;
|
|
69
109
|
/**
|
|
70
110
|
* 日记开关。默认开——这是**功能默认,不是采集默认**。
|
|
71
111
|
*
|
package/dist/index.js
CHANGED
|
@@ -45,11 +45,25 @@ export const Config = z.object({
|
|
|
45
45
|
embeddings: z.any(),
|
|
46
46
|
backup: z.any(),
|
|
47
47
|
/**
|
|
48
|
-
* 云同步配置(2026-11-11
|
|
48
|
+
* 云同步配置(2026-11-11;2026-11-12 改成显式开关)。
|
|
49
49
|
*
|
|
50
|
-
*
|
|
50
|
+
* **用 `enabled` 开关,而不是「配了 remote 就算开」**:与日记同一个模式。语义清楚得多
|
|
51
|
+
* ——「我打开了这个功能」和「我碰巧填了一个字段」是两件事;而且开启后可以只配一部分
|
|
52
|
+
* (比如只做本地镜像、暂不推送)。
|
|
53
|
+
*
|
|
54
|
+
* `volatile()` 让 `enabled` 能在运行时改:设置面板把值写进 profile 的
|
|
55
|
+
* `cordis.patch.yml`,是否即时生效取决于该 profile 的 `patchReload`(`live` 才热重载)。
|
|
56
|
+
* 其余字段(remote / branch 等)改了要重启——它们决定的是连接目标,不是临时状态。
|
|
57
|
+
*
|
|
58
|
+
* 默认 **关闭**:实验性功能不该影响别人的安装。
|
|
51
59
|
*/
|
|
52
|
-
sync: z.
|
|
60
|
+
sync: z.object({
|
|
61
|
+
enabled: z.boolean().default(false),
|
|
62
|
+
mirrorDir: z.string().default(''),
|
|
63
|
+
remote: z.string().default(''),
|
|
64
|
+
branch: z.string().default('main'),
|
|
65
|
+
envName: z.string().default(''),
|
|
66
|
+
}).default({ enabled: false }).volatile(),
|
|
53
67
|
/**
|
|
54
68
|
* 日记开关。默认开——这是**功能默认,不是采集默认**。
|
|
55
69
|
*
|
|
@@ -93,6 +107,35 @@ function diaryOn(config) {
|
|
|
93
107
|
: raw;
|
|
94
108
|
return unwrapped?.enabled !== false;
|
|
95
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* 解包 `config.sync`。
|
|
112
|
+
*
|
|
113
|
+
* `config.sync` 经 loader 解析后是 `Volatile<{...}>`(`settings` 侧的解包就是
|
|
114
|
+
* `isVolatile(v) → v.get()`)——**schema 声明了 `.volatile()`,所以挂载时一定会被包装**,
|
|
115
|
+
* 直接读字段拿到的是空的。裸对象只在直接调用 `apply` 的场合出现,一并兜住。
|
|
116
|
+
* 开关与字段读的是同一份解包结果,所以只在这里做一次。
|
|
117
|
+
* @param config - 插件配置。
|
|
118
|
+
* @returns 解包后的对象;没配就是空对象(字段全按「缺省」处理)。
|
|
119
|
+
*/
|
|
120
|
+
function syncSettings(config) {
|
|
121
|
+
const raw = config?.sync;
|
|
122
|
+
if (raw === undefined || raw === null)
|
|
123
|
+
return {};
|
|
124
|
+
const unwrapped = typeof raw.get === 'function'
|
|
125
|
+
? raw.get()
|
|
126
|
+
: raw;
|
|
127
|
+
return (unwrapped ?? {});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 读云同步开关。
|
|
131
|
+
*
|
|
132
|
+
* 判据是显式 `enabled`,不是「配了 remote」——与日记同一个模式(2026-11-12)。
|
|
133
|
+
* 与日记相反:**解不出来就是关**(`=== true` 才开)。默认值必须与 schema 的
|
|
134
|
+
* `default(false)` 一致——实验性功能不能因为读不到开关就自己跑起来。
|
|
135
|
+
*/
|
|
136
|
+
function syncOn(config) {
|
|
137
|
+
return syncSettings(config).enabled === true;
|
|
138
|
+
}
|
|
96
139
|
/** 日记提醒的冷却窗口(毫秒)。6 小时是拍出来的第一个值,实测后再调。 */
|
|
97
140
|
const DIARY_REMIND_MS = 6 * 60 * 60 * 1000;
|
|
98
141
|
/**
|
|
@@ -508,11 +551,11 @@ export async function apply(ctx, config) {
|
|
|
508
551
|
order: 115,
|
|
509
552
|
text: GUIDANCE,
|
|
510
553
|
});
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
|
|
515
|
-
const syncEnabled =
|
|
554
|
+
// 云同步**默认关闭**(2026-11-11):实验性功能不该影响别人的安装。判据是显式开关
|
|
555
|
+
// `enabled`(2026-11-12,与日记一致)——「我打开了这个功能」与「我碰巧填了一个
|
|
556
|
+
// remote」是两件事,后者会让一次配置试验变成意外开机。关闭时那两个工具**根本不
|
|
557
|
+
// 注册**,而不是注册了再报错:模型看不到就不会去调。
|
|
558
|
+
const syncEnabled = syncOn(config);
|
|
516
559
|
// 0.3.2:记忆机制自述——常驻注入(不落用户存储),随发版更新。
|
|
517
560
|
// order 49 < recall(50):机制说明在具体记忆内容之前。
|
|
518
561
|
ctx.systemPrompt.context({
|
|
@@ -1024,10 +1067,12 @@ export async function apply(ctx, config) {
|
|
|
1024
1067
|
// 决定。把 `git pull --rebase && git add && git commit && git push` 那一串交给模型,
|
|
1025
1068
|
// 出错难排查,也容易越权。
|
|
1026
1069
|
//
|
|
1027
|
-
//
|
|
1028
|
-
//
|
|
1070
|
+
// 缺省不启用:没打开 `memory.sync.enabled` 时这两个工具根本不注册(见上面的
|
|
1071
|
+
// `syncEnabled`);打开之后配置才可能不全——`remote` 为空就只做本地提交。
|
|
1029
1072
|
const syncConfig = () => {
|
|
1030
|
-
|
|
1073
|
+
// 走 {@link syncSettings} 解包:`.volatile()` 包装后直接读字段是空的,
|
|
1074
|
+
// 「开关打开了、配置读不出来」正是半成品状态会踩的坑。
|
|
1075
|
+
const raw = syncSettings(config);
|
|
1031
1076
|
const mirrorDir = typeof raw.mirrorDir === 'string' && raw.mirrorDir !== ''
|
|
1032
1077
|
? raw.mirrorDir
|
|
1033
1078
|
: join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'memory-sync');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@max-null/dsh-memory",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.2",
|
|
4
4
|
"description": "Cross-session plaintext memory plugin for the DeepSeek Harness — deterministic BM25 recall, writes take effect immediately, signal-driven injection, no embeddings",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|