dsh-memory-palace 1.8.0 → 1.8.1-alpha.1
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 +7 -2
- package/lib/api.mjs +3 -12
- package/lib/client.js +37 -70
- package/lib/common/prompts.mjs +2 -39
- package/lib/common/records.mjs +6 -118
- package/lib/common/text.mjs +1 -1
- package/lib/distill.mjs +10 -191
- package/lib/hybrid/index.mjs +3 -3
- package/lib/hybrid/subagent.mjs +1 -1
- package/lib/hybrid/tools.mjs +2 -2
- package/lib/index.js +9 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
- **双层记忆**:用户级(跨项目个人偏好)+ 工作区级(项目约定),互不干扰。
|
|
11
11
|
- **WorkBuddy / CodeBuddy 桥接**:项目已有对应记忆目录时直接读写,无需重复维护。
|
|
12
12
|
- **记忆工具**:AI 可主动写入、聚合读取、按内容删除(删除需人工确认),写入自带去重。
|
|
13
|
-
-
|
|
13
|
+
- **手动蒸馏**:会话标题栏一键把项目记忆提炼成精简的长期记忆(v1.8.1 起只保留「蒸馏项目记忆」)。
|
|
14
14
|
- **设置页集成**:全部配置均可在 DSH 设置面板中图形化调整(按板块折叠),无需改配置文件。
|
|
15
15
|
- **自定义指令**:不适合写进记忆的特殊指令可在设置页配置,插件经系统提示词注入。
|
|
16
16
|
- **记忆注入**:长期记忆与今日日志以「会话起始快照」常驻上下文、按文件身份各只注入一次 —— 既保证每个步骤都可见,又不破坏前缀缓存、不堆积冗余快照。
|
|
@@ -119,10 +119,15 @@ dsh plugin --profile web remove dsh-memory-palace
|
|
|
119
119
|
## 开发
|
|
120
120
|
|
|
121
121
|
架构 / 构建 / 技术要点 / 蒸馏失败重试 见 [DEVELOPMENT.md](./DEVELOPMENT.md)。
|
|
122
|
-
提示词与蒸馏(分工指令 /
|
|
122
|
+
提示词与蒸馏(分工指令 / 项目蒸馏) 见 [PROMPTS.md](./PROMPTS.md)。
|
|
123
123
|
|
|
124
124
|
## 版本历史
|
|
125
125
|
|
|
126
|
+
- **1.8.1**
|
|
127
|
+
- **1.8.1-alpha.1**
|
|
128
|
+
- 💥移除「蒸馏会话」手动功能
|
|
129
|
+
- 💥配置项 summaryMaxTokens / feedbackEnabled 删除,旧值静默失效
|
|
130
|
+
- 💪优化 session header 记忆按钮样式
|
|
126
131
|
- **1.8.0**
|
|
127
132
|
- dsh peer [0.1.7 ... 0.2.0]
|
|
128
133
|
- npm publish
|
package/lib/api.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// memory-palace 自有同源 route(/memory-palace/api):设置读写(真保存)+
|
|
1
|
+
// memory-palace 自有同源 route(/memory-palace/api):设置读写(真保存)+ 手动蒸馏(项目记忆)。
|
|
2
2
|
// 经 registerApi 注入 ctx/paths/distill/运行时状态/设置面;在 index.mjs 装配时调用一次。
|
|
3
3
|
// 机制(v1.1.3):client 直接 fetch 本 route,handler 内走服务端 settings.replace → settings-file 持久化,
|
|
4
4
|
// 绕开 settingsScope(非 loopback 下 set() no-op)与 apiproxy allowlist 两层限制(参考 dsh-better-sidebar)。
|
|
@@ -161,7 +161,8 @@ export function registerApi({ ctx, paths, distill, getSettingsFace }) {
|
|
|
161
161
|
return;
|
|
162
162
|
}
|
|
163
163
|
// ---- v1.2.0 手动蒸馏(会话标题栏「记忆」按钮)----
|
|
164
|
-
|
|
164
|
+
// v1.8.1:只剩「蒸馏项目记忆」;原 distill.session 分支已随该功能移除。
|
|
165
|
+
if (method === "distill.project" || method === "distill.project.preview") {
|
|
165
166
|
const sid = typeof payload?.sessionId === "string" ? payload.sessionId : "";
|
|
166
167
|
// sessions 服务不在顶层 inject(测试环境无此服务时插件仍应激活),route 内惰性解析。
|
|
167
168
|
let session = null;
|
|
@@ -178,16 +179,6 @@ export function registerApi({ ctx, paths, distill, getSettingsFace }) {
|
|
|
178
179
|
if (!cwd) {
|
|
179
180
|
throw Object.assign(new Error("session has no cwd"), { code: "bad-request", status: 400 });
|
|
180
181
|
}
|
|
181
|
-
if (method === "distill.session") {
|
|
182
|
-
const dirs = paths.writeDirs(cwd);
|
|
183
|
-
if (!dirs.length) {
|
|
184
|
-
throw Object.assign(new Error("no memory dirs for this workspace"), { code: "bad-request", status: 400 });
|
|
185
|
-
}
|
|
186
|
-
// 整段会话全量蒸馏(fromSeq=0);allowDelete:true —— 手动按钮开放 delete 操作。
|
|
187
|
-
const r = await distill.distillSessionCore(session, dirs, 0, { allowDelete: true });
|
|
188
|
-
apiWriteOk(res, r);
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
182
|
if (method === "distill.project.preview") {
|
|
192
183
|
const dirs = paths.writeDirs(cwd);
|
|
193
184
|
if (!dirs.length) {
|
package/lib/client.js
CHANGED
|
@@ -42,7 +42,7 @@ window.__ModuleLoader__.load({
|
|
|
42
42
|
subagentLogBudgetLabel: "子代理日志回喂预算(字符)",
|
|
43
43
|
subagentLogBudgetHint: "记忆子代理回喂今日工作日志的字符上限;超出时仅回喂章节目录,子代理按需读取章节。默认 20000。",
|
|
44
44
|
summaryModelLabel: "模型",
|
|
45
|
-
summaryModelHint: "
|
|
45
|
+
summaryModelHint: "记忆插件当前使用的模型(手动「蒸馏项目记忆」与记忆子代理共用)。留空=复用当前会话的 provider/model;也可固定廉价小模型省 token。",
|
|
46
46
|
expand: "展开",
|
|
47
47
|
collapse: "收起",
|
|
48
48
|
save: "保存",
|
|
@@ -56,16 +56,12 @@ window.__ModuleLoader__.load({
|
|
|
56
56
|
invalidNumber: "请填数字;留空表示使用默认值。",
|
|
57
57
|
// ---- v1.2.0 会话标题栏「记忆」按钮(蒸馏入口)----
|
|
58
58
|
btnMemory: "记忆",
|
|
59
|
-
distillSession: "蒸馏会话",
|
|
60
59
|
distillProject: "蒸馏项目记忆",
|
|
61
|
-
confirmSessionTitle: "确认蒸馏会话",
|
|
62
|
-
confirmSessionTips: "插件已自动生成会话记忆,手动蒸馏可能会导致记忆重复。",
|
|
63
60
|
confirmProjectTitle: "确认蒸馏项目记忆",
|
|
64
61
|
confirmProjectTips: "只有当项目记忆过大时才建议蒸馏,过度蒸馏可能导致关键信息丢失,当前记忆大小:{size} 字符。",
|
|
65
62
|
cancel: "取消",
|
|
66
63
|
confirm: "确认",
|
|
67
64
|
distilling: "蒸馏中…",
|
|
68
|
-
notifySessionDone: "会话记忆蒸馏完成",
|
|
69
65
|
notifyProjectDone: "项目记忆蒸馏完成",
|
|
70
66
|
notifyFail: "记忆蒸馏失败",
|
|
71
67
|
distillDebugLogLabel: "蒸馏调试日志",
|
|
@@ -73,13 +69,9 @@ window.__ModuleLoader__.load({
|
|
|
73
69
|
dev: "开发",
|
|
74
70
|
devDesc: "蒸馏调试日志与超时",
|
|
75
71
|
distillTimeoutLabel: "蒸馏超时(秒)",
|
|
76
|
-
distillTimeoutHint: "单次蒸馏 LLM
|
|
77
|
-
summaryMaxTokensLabel: "会话蒸馏最大 Token",
|
|
78
|
-
summaryMaxTokensHint: "「蒸馏会话」最终输出软预算(prompt 约束,思考不受限;实际硬上限由模型自身 maxTokens 决定)。默认 2000,可调大以容纳更多 durable 事实。",
|
|
72
|
+
distillTimeoutHint: "单次蒸馏 LLM 调用的超时上限(秒);超时视为失败并降级。覆盖手动「蒸馏项目记忆」与记忆子代理,默认 60 秒。",
|
|
79
73
|
projectMaxTokensLabel: "项目蒸馏最大 Token",
|
|
80
74
|
projectMaxTokensHint: "最终输出软预算(prompt 约束,思考不受限;实际硬上限由模型自身 maxTokens 决定)。默认 8000。",
|
|
81
|
-
feedbackEnabledLabel: "回喂存量记忆",
|
|
82
|
-
feedbackEnabledHint: "每次蒸馏时附带项目级 + 用户级记忆作为参考,使其能基于既有记忆做增量维护(delete 仅手动蒸馏开放)。仅影响手动蒸馏;自动路径走记忆子代理、自带日志回喂。默认关。",
|
|
83
75
|
customLabel: "自定义指令内容",
|
|
84
76
|
customHint: "非空时注入系统提示词,位置在记忆插件与分工说明之后。对全部会话生效;留空则不注入。",
|
|
85
77
|
customPlaceholder: "例如:回答用中文;代码注释用英文;提交信息用祈使句…"
|
|
@@ -116,7 +108,7 @@ window.__ModuleLoader__.load({
|
|
|
116
108
|
subagentLogBudgetLabel: "Sub-agent log feed budget (chars)",
|
|
117
109
|
subagentLogBudgetHint: "Max chars of today's daily log fed back to the memory sub-agent; beyond this only the chapter TOC is fed and the sub-agent reads sections on demand. Default 20000.",
|
|
118
110
|
summaryModelLabel: "Model",
|
|
119
|
-
summaryModelHint: "The model currently used by the memory plugin (shared by manual distillation and the memory sub-agent). Leave empty to reuse the current session's provider/model, or pin a cheap model (provider/model) to save tokens.",
|
|
111
|
+
summaryModelHint: "The model currently used by the memory plugin (shared by manual project-memory distillation and the memory sub-agent). Leave empty to reuse the current session's provider/model, or pin a cheap model (provider/model) to save tokens.",
|
|
120
112
|
expand: "Expand",
|
|
121
113
|
collapse: "Collapse",
|
|
122
114
|
save: "Save",
|
|
@@ -130,16 +122,12 @@ window.__ModuleLoader__.load({
|
|
|
130
122
|
invalidNumber: "Enter a number; leave empty to use the default.",
|
|
131
123
|
// ---- v1.2.0 session-header Memory button (distill entry) ----
|
|
132
124
|
btnMemory: "Memory",
|
|
133
|
-
distillSession: "Distill session",
|
|
134
125
|
distillProject: "Distill project memory",
|
|
135
|
-
confirmSessionTitle: "Confirm session distillation",
|
|
136
|
-
confirmSessionTips: "The plugin already generates session memory automatically; manual distillation may create duplicates.",
|
|
137
126
|
confirmProjectTitle: "Confirm project memory distillation",
|
|
138
127
|
confirmProjectTips: "Distill only when project memory is too large; over-distillation may lose key info. Current size: {size} chars.",
|
|
139
128
|
cancel: "Cancel",
|
|
140
129
|
confirm: "Confirm",
|
|
141
130
|
distilling: "Distilling…",
|
|
142
|
-
notifySessionDone: "Session memory distilled",
|
|
143
131
|
notifyProjectDone: "Project memory distilled",
|
|
144
132
|
notifyFail: "Memory distillation failed",
|
|
145
133
|
distillDebugLogLabel: "Distill debug log",
|
|
@@ -147,21 +135,17 @@ window.__ModuleLoader__.load({
|
|
|
147
135
|
dev: "Developer",
|
|
148
136
|
devDesc: "Distill debug log and timeout",
|
|
149
137
|
distillTimeoutLabel: "Distill timeout (s)",
|
|
150
|
-
distillTimeoutHint: "Timeout (in seconds) for a single distill LLM call; on timeout it fails and degrades. Covers manual distillation and the memory sub-agent. Default 60s.",
|
|
151
|
-
summaryMaxTokensLabel: "Session distill max tokens",
|
|
152
|
-
summaryMaxTokensHint: "Final output soft budget for 'Distill session' (prompt constraint, thinking not limited; the actual hard cap is the model's own maxTokens). Default 2000; raise to fit more durable facts.",
|
|
138
|
+
distillTimeoutHint: "Timeout (in seconds) for a single distill LLM call; on timeout it fails and degrades. Covers manual project-memory distillation and the memory sub-agent. Default 60s.",
|
|
153
139
|
projectMaxTokensLabel: "Project distill max tokens",
|
|
154
140
|
projectMaxTokensHint: "Final output soft budget (prompt constraint, thinking not limited; actual hard cap is the model's own maxTokens). Default 8000.",
|
|
155
|
-
feedbackEnabledLabel: "Feed existing memory into distillation",
|
|
156
|
-
feedbackEnabledHint: "Feed the project-level + user-level memory in as reference on every distillation so the LLM can maintain existing memory incrementally (delete is only allowed via manual distillation). Manual distillation only; the automatic path runs through the memory sub-agent, which feeds the log itself. Off by default.",
|
|
157
141
|
customLabel: "Custom instruction text",
|
|
158
142
|
customHint: "When non-empty it is injected into the system prompt right after the memory-plugin and memory-roles instructions. Applies to every session; leave empty to inject nothing.",
|
|
159
143
|
customPlaceholder: "e.g. Answer in Chinese; write code comments in English; use imperative commit subjects…"
|
|
160
144
|
};
|
|
161
145
|
|
|
162
146
|
|
|
163
|
-
const FIELD_NUMERIC = new Set(["userBudgetChars", "workspaceBudgetChars", "summaryTimeoutMs", "
|
|
164
|
-
const FIELD_BOOL = new Set(["bridgeBuddyMemory", "distillDebugLog"
|
|
147
|
+
const FIELD_NUMERIC = new Set(["userBudgetChars", "workspaceBudgetChars", "summaryTimeoutMs", "projectMaxTokens", "reorgCooldownDays", "subagentLogBudget"]);
|
|
148
|
+
const FIELD_BOOL = new Set(["bridgeBuddyMemory", "distillDebugLog"]);
|
|
165
149
|
|
|
166
150
|
function projectDraft(snap) {
|
|
167
151
|
const v = snap && snap.value ? snap.value : {};
|
|
@@ -177,11 +161,9 @@ window.__ModuleLoader__.load({
|
|
|
177
161
|
distillDebugLog: v.distillDebugLog === false ? "false" : "true",
|
|
178
162
|
summaryTimeoutMs: v.summaryTimeoutMs != null ? String(Math.round((Number(v.summaryTimeoutMs) || 0) / 1000)) : "60",
|
|
179
163
|
summaryModel: v.summaryModel || "",
|
|
180
|
-
summaryMaxTokens: v.summaryMaxTokens != null ? String(v.summaryMaxTokens) : "2000",
|
|
181
164
|
projectMaxTokens: v.projectMaxTokens != null ? String(v.projectMaxTokens) : "8000",
|
|
182
165
|
reorgCooldownDays: v.reorgCooldownDays != null ? String(v.reorgCooldownDays) : "7",
|
|
183
166
|
subagentLogBudget: v.subagentLogBudget != null ? String(v.subagentLogBudget) : "20000",
|
|
184
|
-
feedbackEnabled: v.feedbackEnabled === false ? "false" : "true",
|
|
185
167
|
customInstructions: v.customInstructions || ""
|
|
186
168
|
};
|
|
187
169
|
}
|
|
@@ -677,7 +659,7 @@ window.__ModuleLoader__.load({
|
|
|
677
659
|
row(t("subagentLogBudgetLabel"), t("subagentLogBudgetHint"), h("input", num("subagentLogBudget")), "subagentLogBudget")
|
|
678
660
|
]),
|
|
679
661
|
// ---- v1.6.3:蒸馏/模型参数常显(v1.8.0 起 summaryModel 只被手动蒸馏与记忆子代理读取)----
|
|
680
|
-
//
|
|
662
|
+
// v1.8.1:本组只剩 summaryModel / projectMaxTokens——「蒸馏会话」与回喂开关已随该功能移除。
|
|
681
663
|
h("div", null, [
|
|
682
664
|
row(t("summaryModelLabel"), t("summaryModelHint"),
|
|
683
665
|
h("select", {
|
|
@@ -691,8 +673,6 @@ window.__ModuleLoader__.load({
|
|
|
691
673
|
h("option", { value: "" }, "复用当前会话模型"),
|
|
692
674
|
...modelOptions.map((o) => h("option", { value: o.value }, o.label))
|
|
693
675
|
]), "summaryModel"),
|
|
694
|
-
toggle(t("feedbackEnabledLabel"), t("feedbackEnabledHint"), "feedbackEnabled"),
|
|
695
|
-
row(t("summaryMaxTokensLabel"), t("summaryMaxTokensHint"), h("input", num("summaryMaxTokens")), "summaryMaxTokens"),
|
|
696
676
|
row(t("projectMaxTokensLabel"), t("projectMaxTokensHint"), h("input", num("projectMaxTokens")), "projectMaxTokens")
|
|
697
677
|
])
|
|
698
678
|
]),
|
|
@@ -734,7 +714,20 @@ window.__ModuleLoader__.load({
|
|
|
734
714
|
"@keyframes mpd-tw2{0%,100%{opacity:1}50%{opacity:.3}}",
|
|
735
715
|
".mpd-star-big{animation:mpd-tw1 2s ease-in-out infinite}",
|
|
736
716
|
".mpd-star-small{animation:mpd-tw2 1.5s ease-in-out infinite .5s}",
|
|
737
|
-
".mpd-item:hover{background:var(--dsw-alias-interactive-bg-hover,#f2f3f5)}"
|
|
717
|
+
".mpd-item:hover{background:var(--dsw-alias-interactive-bg-hover,#f2f3f5)}",
|
|
718
|
+
// v1.8.1:「记忆」胶囊按钮改由 class 驱动,尺寸对齐同槽位原生 Open In 按钮
|
|
719
|
+
// (ui-open-in-app/OpenTargetButton.module.css 的 .split/.main:24px 高 / 11px 字号 / 16px 行高 /
|
|
720
|
+
// radius-sm 圆角 / 0.5px l4 边框 / padding 3px 8px / gap 4px)。
|
|
721
|
+
// box-sizing:border-box 必须显式写:宿主 web 应用没有全局 border-box 重置,
|
|
722
|
+
// 缺它会把 24px 撑成 24+6(padding)+1(border)=31px,超出行容器 30px 的最小高度、把标题栏顶高。
|
|
723
|
+
".mpd-pill{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:4px;",
|
|
724
|
+
"height:24px;padding:3px 8px;",
|
|
725
|
+
"border:0.5px solid var(--dsw-alias-border-l4,#e5e7eb);border-radius:var(--dsw-radius-sm,6px);background:transparent;",
|
|
726
|
+
"color:var(--dsw-alias-label-primary,#1f2329);font-family:var(--dsw-font-family,inherit);font-size:11px;",
|
|
727
|
+
"font-weight:400;line-height:16px;white-space:nowrap;cursor:pointer;}",
|
|
728
|
+
".mpd-pill svg{display:block;}",
|
|
729
|
+
".mpd-pill:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#f2f3f5);}",
|
|
730
|
+
".mpd-pill:disabled{opacity:.6;cursor:wait;}"
|
|
738
731
|
].join("");
|
|
739
732
|
document.head.appendChild(style);
|
|
740
733
|
}
|
|
@@ -764,16 +757,16 @@ window.__ModuleLoader__.load({
|
|
|
764
757
|
}
|
|
765
758
|
|
|
766
759
|
|
|
767
|
-
//
|
|
760
|
+
// 会话标题栏「记忆」胶囊按钮:点击开合下拉(仅「蒸馏项目记忆」)→ 自绘确认弹窗 → fetch route → 浏览器通知。
|
|
761
|
+
// v1.8.1:原「蒸馏会话」下拉项与整条会话蒸馏链路已移除,本按钮只剩项目记忆蒸馏。
|
|
768
762
|
function DistillButton(props) {
|
|
769
763
|
const h = react.createElement;
|
|
770
764
|
const { sessionId, t } = props;
|
|
771
765
|
const [open, setOpen] = react.useState(false);
|
|
772
|
-
const [confirming, setConfirming] = react.useState(null); // "
|
|
766
|
+
const [confirming, setConfirming] = react.useState(null); // "project" | null
|
|
773
767
|
const [busy, setBusy] = react.useState(false);
|
|
774
768
|
const [status, setStatus] = react.useState(""); // Notification 未授权时的内联降级提示
|
|
775
769
|
const [size, setSize] = react.useState(null);
|
|
776
|
-
const [hover, setHover] = react.useState(false);
|
|
777
770
|
|
|
778
771
|
react.useEffect(() => { injectDistillStyles(); }, []);
|
|
779
772
|
|
|
@@ -813,27 +806,24 @@ window.__ModuleLoader__.load({
|
|
|
813
806
|
window.setTimeout(() => setStatus(""), 8000);
|
|
814
807
|
}
|
|
815
808
|
|
|
816
|
-
|
|
809
|
+
// 唯一入口「蒸馏项目记忆」:先取当前 MEMORY.md 字符数(供确认弹窗提示),再开确认弹窗。
|
|
810
|
+
async function pick() {
|
|
817
811
|
setOpen(false);
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
}
|
|
823
|
-
setConfirming(kind);
|
|
812
|
+
const r = await apiCall("distill.project.preview", { sessionId });
|
|
813
|
+
const v = r && r.ok && r.value ? r.value : null;
|
|
814
|
+
setSize(v && typeof v.size === "number" ? v.size : null);
|
|
815
|
+
setConfirming("project");
|
|
824
816
|
}
|
|
825
817
|
|
|
826
818
|
async function run() {
|
|
827
|
-
const kind = confirming;
|
|
828
819
|
setConfirming(null);
|
|
829
820
|
setBusy(true);
|
|
830
821
|
try {
|
|
831
|
-
const
|
|
832
|
-
const r = await apiCall(method, { sessionId });
|
|
822
|
+
const r = await apiCall("distill.project", { sessionId });
|
|
833
823
|
const v = r && r.ok ? r.value : null;
|
|
834
824
|
const errMsg = r && r.error && r.error.message ? r.error.message : "";
|
|
835
825
|
if (v && v.ok === true) {
|
|
836
|
-
notify(
|
|
826
|
+
notify(t("notifyProjectDone"), (v.message || "").slice(0, 120));
|
|
837
827
|
} else {
|
|
838
828
|
notify(t("notifyFail"), errMsg || (v && v.message) || "");
|
|
839
829
|
}
|
|
@@ -853,26 +843,6 @@ window.__ModuleLoader__.load({
|
|
|
853
843
|
borderTop: "4px solid currentColor",
|
|
854
844
|
opacity: 0.65
|
|
855
845
|
};
|
|
856
|
-
// 胶囊按钮样式对齐「Session log」下载按钮(HeaderAction.module.css)。
|
|
857
|
-
const pillStyle = {
|
|
858
|
-
display: "inline-flex",
|
|
859
|
-
alignItems: "center",
|
|
860
|
-
justifyContent: "center",
|
|
861
|
-
gap: "5px",
|
|
862
|
-
height: "32px",
|
|
863
|
-
padding: "6px 12px",
|
|
864
|
-
border: "1px solid var(--dsw-alias-border-l2, #e5e7eb)",
|
|
865
|
-
borderRadius: "18px",
|
|
866
|
-
background: hover ? "var(--dsw-alias-interactive-bg-hover, #f2f3f5)" : "transparent",
|
|
867
|
-
color: "var(--dsw-alias-label-primary, #1f2329)",
|
|
868
|
-
fontFamily: "var(--dsw-font-family, inherit)",
|
|
869
|
-
fontSize: "13px",
|
|
870
|
-
fontWeight: 400,
|
|
871
|
-
lineHeight: "20px",
|
|
872
|
-
cursor: busy ? "wait" : "pointer",
|
|
873
|
-
whiteSpace: "nowrap",
|
|
874
|
-
opacity: busy ? 0.7 : 1
|
|
875
|
-
};
|
|
876
846
|
const menuStyle = {
|
|
877
847
|
position: "absolute",
|
|
878
848
|
top: "calc(100% + 4px)",
|
|
@@ -958,12 +928,10 @@ window.__ModuleLoader__.load({
|
|
|
958
928
|
return h("div", { "data-memory-palace-distill": "", style: { position: "relative", display: "inline-flex" } }, [
|
|
959
929
|
h("button", {
|
|
960
930
|
type: "button",
|
|
961
|
-
|
|
931
|
+
className: "mpd-pill",
|
|
962
932
|
disabled: busy,
|
|
963
933
|
title: t("btnMemory"),
|
|
964
|
-
onClick: () => setOpen(!open)
|
|
965
|
-
onMouseEnter: () => setHover(true),
|
|
966
|
-
onMouseLeave: () => setHover(false)
|
|
934
|
+
onClick: () => setOpen(!open)
|
|
967
935
|
}, [
|
|
968
936
|
h("span", { dangerouslySetInnerHTML: { __html: SPARKLE_SVG } }),
|
|
969
937
|
h("span", null, busy ? t("distilling") : t("btnMemory")),
|
|
@@ -971,8 +939,7 @@ window.__ModuleLoader__.load({
|
|
|
971
939
|
]),
|
|
972
940
|
open
|
|
973
941
|
? h("div", { style: menuStyle }, [
|
|
974
|
-
h("div", { className: "mpd-item", style: itemStyle, onClick:
|
|
975
|
-
h("div", { className: "mpd-item", style: itemStyle, onClick: () => pick("project") }, t("distillProject"))
|
|
942
|
+
h("div", { className: "mpd-item", style: itemStyle, onClick: pick }, t("distillProject"))
|
|
976
943
|
])
|
|
977
944
|
: null,
|
|
978
945
|
status ? h("div", { style: statusStyle }, status) : null,
|
|
@@ -982,8 +949,8 @@ window.__ModuleLoader__.load({
|
|
|
982
949
|
onClick: (e) => { if (e && e.target === e.currentTarget) setConfirming(null); }
|
|
983
950
|
}, [
|
|
984
951
|
h("div", { style: modalStyle }, [
|
|
985
|
-
h("p", { style: modalTitleStyle },
|
|
986
|
-
h("p", { style: modalTipsStyle },
|
|
952
|
+
h("p", { style: modalTitleStyle }, t("confirmProjectTitle")),
|
|
953
|
+
h("p", { style: modalTipsStyle }, t("confirmProjectTips", { size: sizeText })),
|
|
987
954
|
h("div", { style: modalActionsStyle }, [
|
|
988
955
|
h("button", { type: "button", style: btnCancelStyle, onClick: () => setConfirming(null) }, t("cancel")),
|
|
989
956
|
h("button", { type: "button", style: btnOkStyle, onClick: run }, t("confirm"))
|
package/lib/common/prompts.mjs
CHANGED
|
@@ -1,44 +1,7 @@
|
|
|
1
|
-
// memory-palace
|
|
1
|
+
// memory-palace 共享常量:项目记忆蒸馏 prompt。
|
|
2
2
|
// 纯常量模块,无状态、可被任意模块与单测直接引用。
|
|
3
3
|
// v1.8.0:SCENE_KEYWORDS(plugin 模式防闲聊闸门关键词)已随 plugin 模式删除。
|
|
4
|
-
|
|
5
|
-
// 会话提炼 prompt(手动「蒸馏会话」按钮,distillSessionCore 使用)。
|
|
6
|
-
// 让 LLM 输出 {summary, durable:[{scope,fact}], memoryOps:[...]};memoryOps 用于基于编号的现有记忆做增量维护。
|
|
7
|
-
// allowDelete=true(手动蒸馏按钮):开放 add/replace/delete。
|
|
8
|
-
// 注意:回喂是否实际发生由 distill.mjs 按 cfg().feedbackEnabled 决定;
|
|
9
|
-
// 此处仅声明能力边界——allowDelete 决定 prompt 是否允许 delete 指令。
|
|
10
|
-
export function SUMMARY_PROMPT({ allowDelete = false, outputBudget } = {}) {
|
|
11
|
-
const feedbackInstr = allowDelete
|
|
12
|
-
? "【维护现有记忆】若对话中提供了「项目级记忆 / 用户级记忆(逐行编号)」,你可在输出 memoryOps 中对现有记忆做增量维护:\n" +
|
|
13
|
-
'- add:新增一条事实(scope + fact),勿与现有重复;\n' +
|
|
14
|
-
'- replace:line=现有行号,oldText=该行精确原文,newText=替换后全文(仅内容行,禁止改动 # / <!-- 结构行);\n' +
|
|
15
|
-
'- delete:line=现有行号,oldText=该行精确原文(仅内容行,禁止删除 # / <!-- 结构行);\n' +
|
|
16
|
-
"所有 replace/delete 必须带 oldText 精确匹配(匹配失败将被拒绝、不修改任何内容)。"
|
|
17
|
-
: "【维护现有记忆】若对话中提供了「项目级记忆 / 用户级记忆(逐行编号)」,你可在输出 memoryOps 中对现有记忆做增量维护:\n" +
|
|
18
|
-
'- add:新增一条事实(scope + fact),勿与现有重复;\n' +
|
|
19
|
-
'- replace:line=现有行号,oldText=该行精确原文,newText=替换后全文(仅内容行);\n' +
|
|
20
|
-
"(当前为自动模式,禁止使用 delete,避免误删记忆;replace 必须带 oldText 精确匹配,匹配失败将被拒绝。)";
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
"你是会话记忆提炼助手。根据下面这段【新产生的】对话(含工具调用与结果),提炼两样东西:\n" +
|
|
24
|
-
"1) summary:一段简洁中文摘要——这段对话做了什么、关键结果(路径/命令/数字)。\n" +
|
|
25
|
-
"2) durable:值得跨 session 长期记住的事实列表(最多 3 条),每条带 scope:\n" +
|
|
26
|
-
' - "project" = 当前项目的约定/决策/技术事实\n' +
|
|
27
|
-
' - "user" = 跨项目的个人偏好/习惯\n' +
|
|
28
|
-
" 闲聊、一次性操作、显而易见的内容、纯错误现象(错误已有独立记录)不要提炼;没有就留空数组。\n" +
|
|
29
|
-
"【输出风格硬约束】\n" +
|
|
30
|
-
"1) summary 必须是**客观、第三人称、陈述性**的中文摘要,描述「对话完成了什么 / 得出了什么结论」;\n" +
|
|
31
|
-
" **严禁**包含:提问、提议、请示、寒暄、自我指涉(如「我可以帮你…」「需要的话…」「要不要…」)、\n" +
|
|
32
|
-
" 内心独白(如「But wait」「I should…」)或任何对话体 / 元叙述——你是在**写记忆**,不是在**对话**。\n" +
|
|
33
|
-
"2) durable 每条必须是**可独立成立的客观事实**;**严禁**把「助手说过的话 / 向用户提问 / 未确认的提议」本身当作事实记录。\n" +
|
|
34
|
-
"3) 若对话中仅含未决的提问或提议、无确定结论,summary 写「本次对话为未决讨论,暂无确定结论」,durable 留空,绝不可照抄对话体文本。\n" +
|
|
35
|
-
feedbackInstr +
|
|
36
|
-
(outputBudget != null ? "\n【输出长度约束】你的最终 JSON(summary + durable + memoryOps 合计)请控制在约 " + outputBudget + " token(≈" + Math.round(outputBudget * 1.8) + " 字)以内。思考/推理过程不受此限、可充分判断(含「该不该记」「是否一次性操作」「与旧记忆是否冲突需 update/delete」),但**最终成稿必须精炼、不超预算**;严禁为凑结构而填充无用内容。\n" : "") +
|
|
37
|
-
"\n只输出 JSON,不要多余文字,格式:\n" +
|
|
38
|
-
'{"summary":"...","durable":[{"scope":"project","fact":"一句话"}],"memoryOps":[{"op":"add","scope":"project","fact":"..."},{"op":"replace","line":12,"oldText":"...","newText":"..."},{"op":"delete","line":7,"oldText":"..."}]}\n' +
|
|
39
|
-
"(没有需要维护的现有记忆时,memoryOps 留空数组 []。)"
|
|
40
|
-
);
|
|
41
|
-
}
|
|
4
|
+
// v1.8.1:SUMMARY_PROMPT(手动「蒸馏会话」)已随该功能整体移除。
|
|
42
5
|
|
|
43
6
|
// 项目记忆蒸馏 prompt(供按钮「蒸馏项目记忆」稳定调用)。
|
|
44
7
|
// 用法:system = 本 prompt,user = 项目 MEMORY.md 全文,输出 = 精炼后的项目记忆(直接覆盖写回)。
|
package/lib/common/records.mjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
// memory-palace 记忆记录读写(去重追加 / 匹配删除 /
|
|
2
|
-
//
|
|
1
|
+
// memory-palace 记忆记录读写(去重追加 / 匹配删除 / 日志日期枚举)。
|
|
2
|
+
// 纯函数模块,无状态、可被任意模块与单测直接引用。
|
|
3
3
|
// v1.8.0:轻量条目(writeLightEntry)、错误捕获(captureError)与日志过期迁移(prune)
|
|
4
4
|
// 随 plugin 模式一并删除——日志由记忆子代理维护、永不过期不删,是证据层。
|
|
5
|
+
// v1.8.1:createRecords 工厂(唯一方法 appendDaily)与 readNumberedMemory / applyMemoryOp
|
|
6
|
+
// 随「蒸馏会话」功能一并移除——每日日志由记忆子代理维护,长期记忆由 agent 侧工具写入。
|
|
5
7
|
import { mkdir, readdir, writeFile } from "node:fs/promises";
|
|
6
8
|
import { readFileSync } from "node:fs";
|
|
7
|
-
import {
|
|
8
|
-
import { todayISO, normLine, isStructural
|
|
9
|
+
import { dirname } from "node:path";
|
|
10
|
+
import { todayISO, normLine, isStructural } from "./text.mjs";
|
|
9
11
|
|
|
10
12
|
// 追加一行记忆,若目标文件已包含相同内容则跳过(去重),返回是否实际写入。
|
|
11
13
|
export async function appendLineDedup(file, line) {
|
|
@@ -77,87 +79,6 @@ export async function removeLineByMatch(file, match) {
|
|
|
77
79
|
return { removed, ok: true, reason: "done", lines: removedLines };
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
// 全文逐行编号读取(v1.4.0 特性3:回喂存量记忆)。返回 { lines:[{n,raw,structural}], text }。
|
|
81
|
-
// 行号 n 与文件实际行一一对应(1 起),供 LLM 通过 line 引用;text 为每行 `n|raw` 拼接,直接喂模型。
|
|
82
|
-
// 不截断、无字符预算(用户明确要求全量回喂,避免截断导致记忆错误)。
|
|
83
|
-
// v1.4.1:回喂行经 stripSmartTag 剥除行首 [smart] 标记——模型不可见该标签(写侧已停拼),
|
|
84
|
-
// 存量磁盘条目读时透明剥除。replace/delete 的 oldText 校验仍以磁盘原文(raw)为准,不受影响。
|
|
85
|
-
export function readNumberedMemory(file) {
|
|
86
|
-
let cur = "";
|
|
87
|
-
try {
|
|
88
|
-
cur = readFileSync(file, "utf8");
|
|
89
|
-
} catch {
|
|
90
|
-
return { lines: [], text: "" };
|
|
91
|
-
}
|
|
92
|
-
if (!cur) return { lines: [], text: "" };
|
|
93
|
-
const lines = cur.split("\n").map((raw, i) => {
|
|
94
|
-
const t = raw.trim();
|
|
95
|
-
return { n: i + 1, raw, structural: isStructural(t) };
|
|
96
|
-
});
|
|
97
|
-
const text = lines.map((l) => `${l.n}|${stripSmartTag(l.raw)}`).join("\n");
|
|
98
|
-
return { lines, text };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// 基于编号现有记忆的增量维护操作(v1.4.0 特性3)。op 之一:
|
|
102
|
-
// - add:追加一行(text/newText,去重);scope 由 caller 决定目标文件。
|
|
103
|
-
// - replace:替换第 line 行(写前重读 + normLine(oldText) 精确匹配校验,防 stale 行号误改)。
|
|
104
|
-
// - delete:删除第 line 行(结构行 / 未匹配 oldText 均拒绝)。
|
|
105
|
-
// 写前重读(re-read before write)保证基于最新文件内容,避免并发/重试导致的 stale 行号错位。
|
|
106
|
-
// 返回 { ok, reason, changed, line?, removed?, actual? }
|
|
107
|
-
export async function applyMemoryOp(file, op) {
|
|
108
|
-
const o = op || {};
|
|
109
|
-
await mkdir(dirname(file), { recursive: true });
|
|
110
|
-
let cur = "";
|
|
111
|
-
try {
|
|
112
|
-
cur = readFileSync(file, "utf8");
|
|
113
|
-
} catch {
|
|
114
|
-
/* 文件尚不存在 */
|
|
115
|
-
}
|
|
116
|
-
const lines = cur.split("\n");
|
|
117
|
-
const normRaw = (s) => normLine((s || "").trim());
|
|
118
|
-
|
|
119
|
-
if (o.op === "add") {
|
|
120
|
-
const text = (o.text ?? o.newText ?? "").trim();
|
|
121
|
-
if (!text) return { ok: false, reason: "empty-add" };
|
|
122
|
-
if (cur && normRaw(cur).includes(normRaw(text))) return { ok: true, reason: "dup", changed: false };
|
|
123
|
-
const base = cur && !cur.endsWith("\n") ? cur + "\n" : cur;
|
|
124
|
-
await writeFile(file, `${base}${text}\n`, "utf8");
|
|
125
|
-
return { ok: true, reason: "added", changed: true };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (o.op !== "replace" && o.op !== "delete") {
|
|
129
|
-
return { ok: false, reason: "unknown-op", op: o.op };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const n = o.line;
|
|
133
|
-
if (!Number.isInteger(n) || n < 1 || n > lines.length) {
|
|
134
|
-
return { ok: false, reason: "bad-line", line: n, totalLines: lines.length };
|
|
135
|
-
}
|
|
136
|
-
const idx = n - 1;
|
|
137
|
-
const rawLine = lines[idx];
|
|
138
|
-
const trimmed = rawLine.trim();
|
|
139
|
-
if (isStructural(trimmed)) {
|
|
140
|
-
return { ok: false, reason: "structural-protected", line: n };
|
|
141
|
-
}
|
|
142
|
-
// 写前重读后精确匹配:确保操作基于最新内容(防止 stale 行号导致误改/误删)。
|
|
143
|
-
const oldNorm = normRaw(o.oldText);
|
|
144
|
-
if (oldNorm && normRaw(rawLine) !== oldNorm) {
|
|
145
|
-
return { ok: false, reason: "conflict", line: n, expected: o.oldText, actual: trimmed };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (o.op === "delete") {
|
|
149
|
-
const newLines = lines.slice(0, idx).concat(lines.slice(idx + 1));
|
|
150
|
-
await writeFile(file, newLines.join("\n"), "utf8");
|
|
151
|
-
return { ok: true, reason: "deleted", changed: true, line: n, removed: trimmed };
|
|
152
|
-
}
|
|
153
|
-
// replace
|
|
154
|
-
const newText = (o.newText ?? "").trim();
|
|
155
|
-
if (!newText) return { ok: false, reason: "empty-replace" };
|
|
156
|
-
lines[idx] = newText;
|
|
157
|
-
await writeFile(file, lines.join("\n"), "utf8");
|
|
158
|
-
return { ok: true, reason: "replaced", changed: true, line: n };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
82
|
// 读取目录下最近的非今日每日日志日期(最多 limit 份,降序)。供删除范围圈定。
|
|
162
83
|
export async function recentLogDates(dir, limit = 3) {
|
|
163
84
|
let files = [];
|
|
@@ -176,36 +97,3 @@ export async function recentLogDates(dir, limit = 3) {
|
|
|
176
97
|
.slice(0, limit);
|
|
177
98
|
}
|
|
178
99
|
|
|
179
|
-
/**
|
|
180
|
-
* 依赖配置/路径解析的记录写入类函数工厂。
|
|
181
|
-
* @param {{ getConfig: () => object, paths: ReturnType<import("./paths.mjs").createPaths> }} deps
|
|
182
|
-
*/
|
|
183
|
-
export function createRecords({ getConfig, paths }) {
|
|
184
|
-
const cfg = () => getConfig();
|
|
185
|
-
|
|
186
|
-
async function appendDaily(dir, entry) {
|
|
187
|
-
await mkdir(dir, { recursive: true });
|
|
188
|
-
const file = join(dir, `${todayISO()}.md`);
|
|
189
|
-
let existing = "";
|
|
190
|
-
try {
|
|
191
|
-
existing = readFileSync(file, "utf8");
|
|
192
|
-
} catch {
|
|
193
|
-
/* 文件尚不存在 */
|
|
194
|
-
}
|
|
195
|
-
// v1.1.0:每日日志按天分割,文件头写当天日期(# YYYY-MM-DD),条目不再每条带时间戳。
|
|
196
|
-
// 新文件 → 标题+条目;旧文件(无当天一级标题,含历史 `## 时间戳` 条目)→ 补标题到文件头。
|
|
197
|
-
const todayTitle = `# ${todayISO()}`;
|
|
198
|
-
const trimmed = existing.trim();
|
|
199
|
-
if (!trimmed) {
|
|
200
|
-
await writeFile(file, `${todayTitle}\n\n${entry}`, "utf8");
|
|
201
|
-
} else {
|
|
202
|
-
const hasTitle = existing.split("\n").some((l) => l.trim() === todayTitle);
|
|
203
|
-
await writeFile(file, (hasTitle ? "" : `${todayTitle}\n\n`) + existing + entry, "utf8");
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// v1.7.1:recentLogs 已删除 —— memory_read 的 scope='daily' 改为按日期(isoDaysAgo)直读,
|
|
208
|
-
// 不再需要"最近 N 份文件"语义(跨日跳空会取偏)。
|
|
209
|
-
// v1.8.0:prune / writeLightEntry / captureError 已删除(plugin 模式整体移除,日志永不过期)。
|
|
210
|
-
return { appendDaily };
|
|
211
|
-
}
|
package/lib/common/text.mjs
CHANGED
|
@@ -101,7 +101,7 @@ export function budgetClip(text, budget) {
|
|
|
101
101
|
|
|
102
102
|
// v1.4.1:剥除行首 `[smart]` 标记(含重复),保留列表符与其余内容。用于注入与回喂的读侧——
|
|
103
103
|
// 模型全程不可见该标签(写侧 v1.4.1 起不再拼接),存量磁盘条目自然消亡、无需迁移。
|
|
104
|
-
// 逐行处理:允许行首有 `N|`
|
|
104
|
+
// 逐行处理:允许行首有 `N|` 计数前缀与列表符,
|
|
105
105
|
// 只剥「列表符/行首之后紧跟的 [smart] 标记序列」;正文中间的 "smart" 字样不误伤。
|
|
106
106
|
export function stripSmartTag(text) {
|
|
107
107
|
if (!text) return text;
|
package/lib/distill.mjs
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
// memory-palace
|
|
2
|
-
// 经 createDistill 工厂注入 ctx/config/paths
|
|
3
|
-
// v1.8.
|
|
1
|
+
// memory-palace 蒸馏业务:项目记忆蒸馏(按钮「蒸馏项目记忆」)。
|
|
2
|
+
// 经 createDistill 工厂注入 ctx/config/paths;返回句柄供 index.mjs 装配与 api.mjs 调用。
|
|
3
|
+
// v1.8.1:原「蒸馏会话」链路(distillSessionCore)已随该功能整体移除;本文件只剩项目记忆蒸馏。
|
|
4
4
|
import { mkdir, unlink, writeFile, rename } from "node:fs/promises";
|
|
5
5
|
import { join, dirname } from "node:path";
|
|
6
6
|
import { BlockAssembler, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { eventsFrom } from "./common/session.mjs";
|
|
10
|
-
import { appendLineDedup, readNumberedMemory, applyMemoryOp } from "./common/records.mjs";
|
|
7
|
+
import { DISTILL_PROMPT } from "./common/prompts.mjs";
|
|
8
|
+
import { readMdSync, toHomeShort, nowStamp } from "./common/text.mjs";
|
|
11
9
|
import { runWithRetry, RETRY_CONSTANTS } from "./common/retry.mjs";
|
|
12
10
|
|
|
13
11
|
/**
|
|
14
|
-
* @param {{ ctx: object, getConfig: () => object, paths: object
|
|
12
|
+
* @param {{ ctx: object, getConfig: () => object, paths: object }} deps
|
|
15
13
|
*/
|
|
16
|
-
export function createDistill({ ctx, getConfig, paths,
|
|
14
|
+
export function createDistill({ ctx, getConfig, paths, logger }) {
|
|
17
15
|
const cfg = () => getConfig();
|
|
18
16
|
|
|
19
17
|
// 日志(v1.8.0-alpha.4):原 stderr 输出改为经统一 logger 落盘
|
|
@@ -100,185 +98,6 @@ export function createDistill({ ctx, getConfig, paths, records, logger }) {
|
|
|
100
98
|
return { finish, text, chunks, deltaChars, gotFirstChunk, tFirstChunk, elapsed: Date.now() - t0 };
|
|
101
99
|
}
|
|
102
100
|
|
|
103
|
-
// 蒸馏核心(v1.2.0 抽取):对指定 session 的 [fromSeq, ∞) surface 事件做 LLM 智能摘要并写盘。
|
|
104
|
-
// v1.8.0:唯一调用方 = 按钮「蒸馏会话」(全量 fromSeq=0);原 smart 模式自动摘要链路已删除。
|
|
105
|
-
// 返回 { ok, summary, durableCount };失败 { ok:false }(调用方各自决定降级策略)。
|
|
106
|
-
async function distillSessionCore(session, dirs, fromSeq, opts) {
|
|
107
|
-
const log = logFor(session);
|
|
108
|
-
const dbg = log ? log.dbg : noop;
|
|
109
|
-
const dbgFail = log ? log.dbgFail : noop;
|
|
110
|
-
const allowDelete = !!(opts && opts.allowDelete);
|
|
111
|
-
// 回喂存量记忆(v1.4.0 特性3):feedbackEnabled 开启即回喂项目级 + 用户级 MEMORY.md 全文
|
|
112
|
-
// (逐行编号、无截断)。delete 由 allowDelete 决定(手动按钮传 true)。
|
|
113
|
-
// 回喂目标严格限定为 MEMORY.md;每日日志(.deepseek-harness/memory/YYYY-MM-DD.md)不读取、不改写、不删旧行。
|
|
114
|
-
const useFeedback = !!cfg().feedbackEnabled;
|
|
115
|
-
if (!session) {
|
|
116
|
-
dbgFail("no session");
|
|
117
|
-
return { ok: false };
|
|
118
|
-
}
|
|
119
|
-
const c = cfg();
|
|
120
|
-
// 1. 模型解析:summaryModel 注册表反查(带前缀/裸 id 均可)> 复用当前会话模型;皆缺 → 降级。
|
|
121
|
-
const resolved = await resolveModel(c.summaryModel, session);
|
|
122
|
-
if (!resolved) {
|
|
123
|
-
dbgFail("no model", { summaryModel: c.summaryModel, header: JSON.stringify(session.requestHeader?.()?.config) });
|
|
124
|
-
return { ok: false };
|
|
125
|
-
}
|
|
126
|
-
const { provider, model } = resolved;
|
|
127
|
-
// 2. 输入:取 seq >= fromSeq 的 surface 事件,投影成模型视角 Message[]。
|
|
128
|
-
// v1.6.2-alpha.4:eventsFrom 兼容层(宿主 0.1.2-alpha.4 删除 Session.events getter)。
|
|
129
|
-
const SURFACE = new Set(["user/message", "assistant/message", "tool/result"]);
|
|
130
|
-
const newEvents = eventsFrom(session, fromSeq).filter((e) => SURFACE.has(e.type));
|
|
131
|
-
dbg("session core entry", { sessionId: session.id, fromSeq, eventsLen: newEvents.length });
|
|
132
|
-
const hist = [];
|
|
133
|
-
for (const e of newEvents) {
|
|
134
|
-
const m = session.deriveEventMessage ? session.deriveEventMessage(e) : null;
|
|
135
|
-
if (m) hist.push(m);
|
|
136
|
-
}
|
|
137
|
-
if (!hist.length) {
|
|
138
|
-
dbgFail("no events in range", { fromSeq, eventsLen: eventsFrom(session).length, newEvents: newEvents.length, hasDerive: !!session.deriveEventMessage });
|
|
139
|
-
return { ok: false };
|
|
140
|
-
}
|
|
141
|
-
// 2.5 回喂存量记忆(v1.4.0 特性3):仅当 useFeedback 时,读取项目级 + 用户级 MEMORY.md 全文
|
|
142
|
-
// (逐行编号、无截断)作为上下文前置消息,让 LLM 基于既有记忆做 add/replace/delete 增量维护。
|
|
143
|
-
let messages = hist;
|
|
144
|
-
const feedbackFiles = { project: null, user: null };
|
|
145
|
-
if (useFeedback) {
|
|
146
|
-
const sessCwd = session?.header?.cwd ?? undefined;
|
|
147
|
-
const projectMem = paths.memoryFileOf(dirs[0], sessCwd);
|
|
148
|
-
const userMem = expandHome(cfg().userMemoryPath);
|
|
149
|
-
// 始终记录目标文件路径(即使文件尚不存在),便于后续 add 操作创建文件。
|
|
150
|
-
feedbackFiles.project = projectMem;
|
|
151
|
-
feedbackFiles.user = userMem;
|
|
152
|
-
let fbText = "";
|
|
153
|
-
if (projectMem) {
|
|
154
|
-
const pm = readNumberedMemory(projectMem);
|
|
155
|
-
if (pm.text) fbText += "【项目级记忆(逐行编号,行号从 1 开始)】\n" + pm.text + "\n";
|
|
156
|
-
}
|
|
157
|
-
if (userMem) {
|
|
158
|
-
const um = readNumberedMemory(userMem);
|
|
159
|
-
if (um.text) fbText += "【用户级记忆(逐行编号,行号从 1 开始)】\n" + um.text + "\n";
|
|
160
|
-
}
|
|
161
|
-
if (fbText) {
|
|
162
|
-
messages = [
|
|
163
|
-
createUserMessage({
|
|
164
|
-
content: [{ type: "text", text: "以下是现有记忆(仅供你增量维护参考,不要当作本次对话内容):\n" + fbText }],
|
|
165
|
-
source: { kind: "plugin:dsh-memory-palace" },
|
|
166
|
-
}),
|
|
167
|
-
...hist,
|
|
168
|
-
];
|
|
169
|
-
}
|
|
170
|
-
dbg("session feedback", { hasProject: !!feedbackFiles.project, hasUser: !!feedbackFiles.user });
|
|
171
|
-
}
|
|
172
|
-
// 3. LLM 调用:借鉴 dsh-sideband 的 summarizeJob(src/summarizer.ts)——
|
|
173
|
-
// a) SUMMARY_PROMPT 放 system 参数(GenerateOptions.system),指令不占 user 消息、模型更遵守;
|
|
174
|
-
// b) AbortSignal.timeout 超时保护(默认 60s,Config.summaryTimeoutMs 可调),流循环内 throwIfAborted,
|
|
175
|
-
// 杜绝「LLM 慢/挂起 → for await 无限等待」导致的摘要静默失败;
|
|
176
|
-
// c) finish.kind 细化:error/aborted → 失败降级(探针带 failure.message);max-tokens → 尝试用已有文本
|
|
177
|
-
// (解析失败时抽取 {...} JSON 块兜底,仍失败则 summary 置空、记极简占位,绝不把 LLM 自由文本当 summary 落盘)。
|
|
178
|
-
const timeoutMs = c.summaryTimeoutMs || 60000;
|
|
179
|
-
const t0 = Date.now();
|
|
180
|
-
dbg("session request", { provider, model, messages: messages.length, maxTokens: "(model native cap)", timeoutMs, maxRetries: RETRY_CONSTANTS.MAX_RETRIES });
|
|
181
|
-
let result;
|
|
182
|
-
try {
|
|
183
|
-
result = await runWithRetry(
|
|
184
|
-
() => streamOnce({ provider, model, system: SUMMARY_PROMPT({ allowDelete, outputBudget: c.summaryMaxTokens }), messages }, timeoutMs),
|
|
185
|
-
{
|
|
186
|
-
maxRetries: RETRY_CONSTANTS.MAX_RETRIES,
|
|
187
|
-
onAttempt: ({ attempt, maxRetries }) => dbg("session attempt", { attempt, maxRetries }),
|
|
188
|
-
},
|
|
189
|
-
);
|
|
190
|
-
} catch (e) {
|
|
191
|
-
const cls = e?.cls || { kind: "fatal", message: String(e?.message ?? e) };
|
|
192
|
-
dbgFail("session distill exhausted", { kind: cls.kind, status: cls.status ?? "", attempts: e?.attempts });
|
|
193
|
-
return { ok: false };
|
|
194
|
-
}
|
|
195
|
-
const { finish, text, chunks, deltaChars, tFirstChunk } = result;
|
|
196
|
-
dbg("session finish", { kind: finish?.kind, attempts: result.attempts });
|
|
197
|
-
dbg("session stream done", { chunks, deltaChars, firstChunkMs: tFirstChunk });
|
|
198
|
-
// 本地调试(受 distillDebugLog + distillLogLevel==="debug" 双门控):LLM 原始响应写入 debug.log,
|
|
199
|
-
// 用分隔符整段包裹,便于一眼框住起止、不与其它 dbg 行混淆。
|
|
200
|
-
if (cfg().distillLogLevel === "debug") log?.raw(text);
|
|
201
|
-
if (finish && finish.kind === "max-tokens") {
|
|
202
|
-
dbgFail("session llm finish max-tokens (attempting partial JSON)", {});
|
|
203
|
-
}
|
|
204
|
-
if (!text) {
|
|
205
|
-
dbgFail("empty llm text");
|
|
206
|
-
return { ok: false };
|
|
207
|
-
}
|
|
208
|
-
// 4. 解析 JSON:{summary, durable:[{scope,fact}], memoryOps:[...]}。
|
|
209
|
-
// 防污染(v1.4.0 修复):解析失败【绝不】退化成"全文当 summary"——否则对话原文 / 助手独白 / 向用户的请示
|
|
210
|
-
// 会被整段灌进每日日志。先尝试从自由文本抽取最后一个 {...} JSON 块再解析;仍失败则 summary 留空。
|
|
211
|
-
let summary = "";
|
|
212
|
-
let durable = [];
|
|
213
|
-
let memoryOps = [];
|
|
214
|
-
let jsonOk = false;
|
|
215
|
-
const tryParseJson = (s) => {
|
|
216
|
-
try {
|
|
217
|
-
const o = JSON.parse(s);
|
|
218
|
-
return o && typeof o === "object" ? o : null;
|
|
219
|
-
} catch { return null; }
|
|
220
|
-
};
|
|
221
|
-
let parsed = tryParseJson(text);
|
|
222
|
-
if (!parsed) {
|
|
223
|
-
const block = text.match(/\{[\s\S]*\}/);
|
|
224
|
-
if (block) parsed = tryParseJson(block[0]);
|
|
225
|
-
}
|
|
226
|
-
if (parsed) {
|
|
227
|
-
jsonOk = true;
|
|
228
|
-
if (typeof parsed.summary === "string") summary = parsed.summary;
|
|
229
|
-
if (Array.isArray(parsed.durable)) durable = parsed.durable;
|
|
230
|
-
if (Array.isArray(parsed.memoryOps)) memoryOps = parsed.memoryOps;
|
|
231
|
-
}
|
|
232
|
-
// 5. 写盘:summary → 每日日志 [smart] 前缀;durable → MEMORY.md `- [smart] fact`(按 scope 分层,去重)。
|
|
233
|
-
// v1.2.0 修复:手动蒸馏(按钮)时 dirs 按 session.header.cwd 解析,但 memoryFileOf 缺省用闭包
|
|
234
|
-
// activeCwd——会话 cwd ≠ activeCwd 时 isDshDir 判假,durable 会落进旧嵌套 memory/MEMORY.md。
|
|
235
|
-
// 此处显式传会话 cwd,使 dsh 目录正确解析到同级 MEMORY.md(自动模式 activeCwd 即会话 cwd,行为不变)。
|
|
236
|
-
const sessCwd = session?.header?.cwd ?? undefined;
|
|
237
|
-
for (const dir of dirs) {
|
|
238
|
-
// summary 为空(解析失败/未产出)→ 记极简占位,绝不把 LLM 自由文本当 summary 落盘(v1.4.0 防污染)。
|
|
239
|
-
const dailyLine = summary
|
|
240
|
-
? `\n[smart] ${summary}\n\n`
|
|
241
|
-
: "\n[smart] 本段会话已蒸馏,但 LLM 未返回可解析的结构化摘要,已跳过原文落盘以避免污染记忆。\n\n";
|
|
242
|
-
await records.appendDaily(dir, dailyLine);
|
|
243
|
-
}
|
|
244
|
-
for (const d of durable) {
|
|
245
|
-
if (!d || typeof d !== "object") continue;
|
|
246
|
-
let fact = String(d.fact ?? "").trim();
|
|
247
|
-
if (!fact) continue;
|
|
248
|
-
// v1.4.1:写侧不再拼接 [smart] 标签(防回喂二次拼接形成 [smart] [smart] 双前缀、
|
|
249
|
-
// 防标签注意力稀释);防御性剥除 LLM 自行带回的标签,保证落盘条目干净。
|
|
250
|
-
fact = stripSmartTag(fact).trim();
|
|
251
|
-
const line = `- ${fact}`;
|
|
252
|
-
if (d.scope === "user") {
|
|
253
|
-
await appendLineDedup(expandHome(c.userMemoryPath), line).catch(() => {});
|
|
254
|
-
} else {
|
|
255
|
-
for (const dir of dirs) {
|
|
256
|
-
await appendLineDedup(paths.memoryFileOf(dir, sessCwd), line).catch(() => {});
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
// 5.5 应用 memoryOps(v1.4.0 特性3 增量维护):useFeedback 时由 LLM 产出(自动/手动皆可能)。
|
|
261
|
-
// add → 追加到对应 MEMORY.md(file 由 scope 决定,可能即时创建);replace/delete → applyMemoryOp
|
|
262
|
-
// (写前重读 + normLine 精确匹配 + 结构行保护,防止 stale 行号误改/误删)。
|
|
263
|
-
// 自动模式(allowDelete=false)prompt 同步禁用 delete,且下方 delete op 经 !allowDelete 跳过;
|
|
264
|
-
// 自动模式仅允许 add/replace 增量维护,delete 仅手动蒸馏按钮开放。
|
|
265
|
-
let opApplied = 0, opSkipped = 0;
|
|
266
|
-
for (const op of memoryOps) {
|
|
267
|
-
if (!op || typeof op !== "object") continue;
|
|
268
|
-
const scope = op.scope === "user" ? "user" : "project";
|
|
269
|
-
const file = scope === "user" ? feedbackFiles.user : feedbackFiles.project;
|
|
270
|
-
if (!file) { opSkipped++; continue; }
|
|
271
|
-
if (op.op === "delete" && !allowDelete) { opSkipped++; continue; }
|
|
272
|
-
const r = await applyMemoryOp(file, op).catch(() => ({ ok: false, reason: "exception" }));
|
|
273
|
-
if (r && r.ok && r.changed) opApplied++;
|
|
274
|
-
else if (r && !r.ok) dbgFail("memoryOp rejected", { op: op.op, reason: r.reason, line: r.line });
|
|
275
|
-
}
|
|
276
|
-
dbg("session memoryOps", { total: memoryOps.length, applied: opApplied, skipped: opSkipped });
|
|
277
|
-
dbg("session parsed", { jsonOk, summaryChars: summary.length, durableCount: durable.length });
|
|
278
|
-
dbg("session write done", { dirs: dirs.length, durableWritten: durable.length });
|
|
279
|
-
return { ok: true, summary, durableCount: durable.length };
|
|
280
|
-
}
|
|
281
|
-
|
|
282
101
|
// ---------- v1.2.0 项目记忆蒸馏(按钮「蒸馏项目记忆」) ----------
|
|
283
102
|
// 流程:读主目标 MEMORY.md → DISTILL_PROMPT 蒸馏 → 写 memory-cover.md(同目录,保证 rename 原子)
|
|
284
103
|
// → 完整性检查 → 备份 MEMORY.md.{时间戳} → rename 覆盖 → cover 随 rename 消失。
|
|
@@ -306,7 +125,7 @@ export function createDistill({ ctx, getConfig, paths, records, logger }) {
|
|
|
306
125
|
try {
|
|
307
126
|
const t0 = Date.now();
|
|
308
127
|
let provider = null, model = null;
|
|
309
|
-
//
|
|
128
|
+
// 模型解析:summaryModel 注册表反查 > 会话 requestHeader。
|
|
310
129
|
const resolved = await resolveModel(c.summaryModel, session);
|
|
311
130
|
if (!resolved) {
|
|
312
131
|
return { ok: false, message: "无法确定蒸馏模型(summaryModel 未配置且会话无模型信息)。" };
|
|
@@ -345,7 +164,7 @@ export function createDistill({ ctx, getConfig, paths, records, logger }) {
|
|
|
345
164
|
return { ok: false, message: `蒸馏${kindZh}(重试 ${e?.attempts || 0} 次后仍失败):${cls.message || ""}` };
|
|
346
165
|
}
|
|
347
166
|
const { finish, text, chunks, deltaChars, gotFirstChunk, tFirstChunk } = result;
|
|
348
|
-
// 本地调试(受 distillDebugLog + distillLogLevel==="debug"
|
|
167
|
+
// 本地调试(受 distillDebugLog + distillLogLevel==="debug" 双门控):原始响应写入 debug.log。
|
|
349
168
|
if (cfg().distillLogLevel === "debug") log?.raw(text);
|
|
350
169
|
if (finish && finish.kind === "max-tokens") {
|
|
351
170
|
dbg("project finish", { kind: "max-tokens", attempts: result.attempts });
|
|
@@ -389,5 +208,5 @@ export function createDistill({ ctx, getConfig, paths, records, logger }) {
|
|
|
389
208
|
}
|
|
390
209
|
}
|
|
391
210
|
|
|
392
|
-
return {
|
|
211
|
+
return { distillProjectMemory, distillLocks };
|
|
393
212
|
}
|
package/lib/hybrid/index.mjs
CHANGED
|
@@ -7,11 +7,11 @@ import { registerHybridTools, attachHybridGuards } from "./tools.mjs";
|
|
|
7
7
|
export { HYBRID_PROACTIVE } from "./prompts.mjs";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* @param {{ ctx: object, getConfig: () => object, paths: object,
|
|
10
|
+
* @param {{ ctx: object, getConfig: () => object, paths: object, state: object }} deps
|
|
11
11
|
* @returns {{ runMemorySubagent: Function }}
|
|
12
12
|
*/
|
|
13
|
-
export function registerHybrid({ ctx, getConfig, paths,
|
|
14
|
-
registerHybridTools({ ctx, getConfig, paths,
|
|
13
|
+
export function registerHybrid({ ctx, getConfig, paths, state }) {
|
|
14
|
+
registerHybridTools({ ctx, getConfig, paths, state });
|
|
15
15
|
attachHybridGuards(ctx, getConfig, paths, state);
|
|
16
16
|
return { runMemorySubagent };
|
|
17
17
|
}
|
package/lib/hybrid/subagent.mjs
CHANGED
|
@@ -77,7 +77,7 @@ async function streamTurn(ctx, params, timeoutMs) {
|
|
|
77
77
|
return { finish, asm, stats: { chunks, deltaChars, firstChunkMs: tFirstChunk, elapsedMs: Date.now() - t0 } };
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
// 把本轮 surface 事件投影为消息数组(seq >= fromSeq
|
|
80
|
+
// 把本轮 surface 事件投影为消息数组(seq >= fromSeq)。
|
|
81
81
|
// v1.6.2-alpha.4:eventsFrom 兼容层(宿主 0.1.2-alpha.4 删除 Session.events getter)。
|
|
82
82
|
// v1.7.0 特性3(投影去噪):user 侧排除【注入类消息】,只留真实用户对话。
|
|
83
83
|
// 旧实现只看事件类型不看 source,导致首轮 lastSummarizedSeq 较小时把 agent-instructions
|
package/lib/hybrid/tools.mjs
CHANGED
|
@@ -68,9 +68,9 @@ function sectionHasEntry(md, section, entry) {
|
|
|
68
68
|
/**
|
|
69
69
|
* 注册记忆工具(MEMORY.md 章节化写入 / 整章节替换 / 全量重整)+ pre-execute 闸门。
|
|
70
70
|
* v1.8.0:由 index.mjs 无条件装配(原 hybrid 已成唯一写入路径)。
|
|
71
|
-
* @param {{ ctx: object, getConfig: () => object, paths: object,
|
|
71
|
+
* @param {{ ctx: object, getConfig: () => object, paths: object, state: object }} deps
|
|
72
72
|
*/
|
|
73
|
-
export function registerHybridTools({ ctx, getConfig, paths,
|
|
73
|
+
export function registerHybridTools({ ctx, getConfig, paths, state }) {
|
|
74
74
|
const cfg = () => getConfig();
|
|
75
75
|
// 从宿主 execute 第二参(ToolRunContext,defineTool 原样转发)取发起会话 cwd。
|
|
76
76
|
// 用可选链自然得 undefined(勿 `?? null`——null 会绕过 paths 默认参数导致 dirs 恒空)。
|
package/lib/index.js
CHANGED
|
@@ -32,7 +32,6 @@ import { extractText, extractToolErrorText } from "./common/text.mjs";
|
|
|
32
32
|
import { createPaths } from "./common/paths.mjs";
|
|
33
33
|
import { createLogger } from "./common/logger.mjs";
|
|
34
34
|
import { planModeOf } from "./common/planmode.mjs";
|
|
35
|
-
import { createRecords } from "./common/records.mjs";
|
|
36
35
|
import { createDistill } from "./distill.mjs";
|
|
37
36
|
import { registerTools } from "./tools.mjs";
|
|
38
37
|
import { registerApi } from "./api.mjs";
|
|
@@ -78,16 +77,15 @@ export const Config = Schema.object({
|
|
|
78
77
|
// 旧 profile 若仍写着这些键,作为未知键原样透传、静默失效;用户下次在设置页保存时自动从 profile patch 消失。
|
|
79
78
|
// 「记忆写入总开关」也一并删除(v1.8.0):写入是否发生改由 `enabled`(profile config 级停用)与
|
|
80
79
|
// `silentPresets`(会话预设级静默)决定,不再有独立的写入闸门。
|
|
81
|
-
summaryModel: Schema.string().default("").description("
|
|
82
|
-
summaryTimeoutMs: Schema.number().default(60000).description("蒸馏 LLM
|
|
80
|
+
summaryModel: Schema.string().default("").description("记忆插件当前使用的模型(手动「蒸馏项目记忆」与记忆子代理共用)。留空=复用当前会话 provider/model;也可填 provider/model(如 deepseek/deepseek-chat)固定廉价模型省 token。").volatile(),
|
|
81
|
+
summaryTimeoutMs: Schema.number().default(60000).description("蒸馏 LLM 调用的超时(毫秒),超时视为失败并降级;覆盖手动「蒸馏项目记忆」与记忆子代理两条链路;默认 60000(60s)。").volatile(),
|
|
83
82
|
distillDebugLog: Schema.boolean().default(false).description("调试开关:开启后把本插件**全部诊断日志落盘**(v1.8.0-alpha.4 起不再输出 stderr)。落点 = `<profile 目录>/.memory-palace/logs/<会话 id>/`(如 `$DSH_HOME/profiles/web/.memory-palace/logs/<sid>/`),按级别分文件:`info.log` 收失败/跳过留痕与子代理终态台账,`debug.log` 收详单(`distillLogLevel=debug` 时另含 LLM 原始响应)。关闭(默认)时零输出。单文件上限 1 MB、不自动清理。仅排障用").volatile(),
|
|
84
83
|
// ---- v1.4.1:蒸馏日志级别(平铺键,不进 UI;默认 info;distillDebugLog=true 时生效) ----
|
|
85
84
|
distillLogLevel: Schema.string().default("info").description("日志级别:`info`=只落盘元数据诊断(默认,不含 LLM 原始响应);`debug`=额外把 LLM 原始响应文本落盘到 `debug.log`(分隔符包裹),仅限受信本地排障开启。无需 UI 配置,经 profile 的 cordis.patch.yml 在 memory-palace 条目 config 下设置 distillLogLevel 键(dsh 0.1.7 起 settings.yaml 已被宿主移除、导入 profile patch)。").volatile(),
|
|
86
|
-
// ---- v1.4.0
|
|
87
|
-
|
|
85
|
+
// ---- v1.4.0:手动「蒸馏项目记忆」最终输出软预算(v1.4.1 起语义变更:prompt 软约束,思考不受限;实际硬上限由模型自身 maxTokens 决定) ----
|
|
86
|
+
// v1.8.1:原 summaryMaxTokens(会话蒸馏输出预算)与 feedbackEnabled(回喂存量记忆)
|
|
87
|
+
// 已随「蒸馏会话」功能整体摘除;老 profile 里的旧值作为未知键透传、静默失效。
|
|
88
88
|
projectMaxTokens: Schema.number().default(8000).description("手动「蒸馏项目记忆」LLM 的最终输出软预算(prompt 约束,思考不受限;实际硬上限由模型自身 maxTokens 决定)。默认 8000。").volatile(),
|
|
89
|
-
// ---- v1.4.0:蒸馏时回喂存量记忆(特性3,默认关;开启后手动蒸馏按钮回喂,delete 仅手动放开) ----
|
|
90
|
-
feedbackEnabled: Schema.boolean().default(false).description("每次蒸馏时把项目级 + 用户级 MEMORY.md 全文(逐行编号、无截断)回喂给 LLM,使其能基于既有记忆做增量维护(delete 手动蒸馏按钮开放)。自动路径走记忆子代理、自带日志回喂,不受此项影响。默认关。").volatile(),
|
|
91
89
|
// ---- v1.6.0:记忆子代理配置 ----
|
|
92
90
|
reorgCooldownDays: Schema.number().default(7).description("项目级 MEMORY.md 全量重整的冷却天数(距上次重整)。与「超出注入预算」(`workspaceBudgetChars`,即项目级 MEMORY.md 大于该值)双条件同时满足才允许 memory_reorganize;时间戳以 HTML 注释落在 MEMORY.md 文件尾。").volatile(),
|
|
93
91
|
subagentLogBudget: Schema.number().default(20000).description("记忆子代理回喂今日工作日志的字符上限。超出时仅回喂章节目录,子代理用 log_read_section 按需读取章节。").volatile(),
|
|
@@ -173,7 +171,7 @@ export function apply(ctx, config) {
|
|
|
173
171
|
});
|
|
174
172
|
const getSettingsFace = () => settingsFace;
|
|
175
173
|
|
|
176
|
-
// ---- 运行时状态(跨模块共享的可变状态;distill/tools/api
|
|
174
|
+
// ---- 运行时状态(跨模块共享的可变状态;distill/tools/api 经工厂注入读取) ----
|
|
177
175
|
const state = {
|
|
178
176
|
activeCwd: null, // 最近活跃 session 的 cwd(session/event 更新)
|
|
179
177
|
activeSession: null, // 最近活跃 session 引用
|
|
@@ -246,14 +244,13 @@ export function apply(ctx, config) {
|
|
|
246
244
|
return preset !== undefined && list.includes(preset);
|
|
247
245
|
}
|
|
248
246
|
|
|
249
|
-
// ---- 装配:路径解析 /
|
|
247
|
+
// ---- 装配:路径解析 / 蒸馏 / 工具 / route ----
|
|
250
248
|
const paths = createPaths(() => source(), () => state.activeCwd);
|
|
251
|
-
const records = createRecords({ getConfig: () => source(), paths });
|
|
252
249
|
// v1.8.0-alpha.4:统一日志落盘器——原 stderr 输出(含原「无条件」的失败留痕)全部改为落盘,
|
|
253
250
|
// 落点 `<profileDir>/.memory-palace/logs/<sessionId>/{info,debug}.log`,统一受 distillDebugLog 门控。
|
|
254
251
|
// 取径与分级依据见 src/common/logger.mjs 文件头。
|
|
255
252
|
const logger = createLogger({ ctx, getConfig: () => source() });
|
|
256
|
-
const distill = createDistill({ ctx, getConfig: () => source(), paths,
|
|
253
|
+
const distill = createDistill({ ctx, getConfig: () => source(), paths, logger });
|
|
257
254
|
registerTools({ ctx, getConfig: () => source(), paths, state });
|
|
258
255
|
registerApi({ ctx, paths, distill, getSettingsFace });
|
|
259
256
|
// v1.8.0:记忆子代理模块(原 hybrid)。必须【无条件注册】——apply() 同步段执行时 settings 服务
|
|
@@ -262,7 +259,7 @@ export function apply(ctx, config) {
|
|
|
262
259
|
// registerHybrid 未执行 → 子代理 TypeError 被吞、日志全空)。
|
|
263
260
|
// 运行时分派靠 _settle / proactive 的热 source() 判断;工具注册无副作用(enabled=false 或静默
|
|
264
261
|
// 会话下 execute 内有兜底)。
|
|
265
|
-
const hybrid = registerHybrid({ ctx, getConfig: () => source(), paths,
|
|
262
|
+
const hybrid = registerHybrid({ ctx, getConfig: () => source(), paths, state });
|
|
266
263
|
// v1.7.0 特性1:E 投影通道(记忆正文常驻注入)。与 hybrid 同理,无条件注册——
|
|
267
264
|
// 其内部读热配置,无副作用(enabled=false 或命中静默预设时 buildProjections 返回空)。
|
|
268
265
|
// v1.7.2:把 isSessionSilent 透传下去——投影按**会话**判定,故必须知道 agent 所属会话的预设。
|
|
@@ -511,7 +508,6 @@ export function apply(ctx, config) {
|
|
|
511
508
|
ctx,
|
|
512
509
|
getConfig: () => source(),
|
|
513
510
|
paths,
|
|
514
|
-
records,
|
|
515
511
|
state,
|
|
516
512
|
session,
|
|
517
513
|
dirs,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-memory-palace",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1-alpha.1",
|
|
4
4
|
"description": "把 WorkBuddy 的文件式记忆系统移植进 DeepSeek Harness:用户级 + 工作区的人类可读 Markdown 记忆(~/.deepseek-harness/MEMORY.md 与 <cwd>/.deepseek-harness/memory/)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|