dsh-memory-palace 1.7.2 → 1.8.0-alpha.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.
@@ -11,6 +11,7 @@ import { join, dirname } from "node:path";
11
11
  import { readFileSync } from "node:fs";
12
12
  import { expandHome, toHomeShort, readMdSync, nowStamp, normLine } from "../common/text.mjs";
13
13
  import { appendToSectionText, upsertSectionText, replaceSectionText, markEntryDeletedText, locateSection } from "../common/sections.mjs";
14
+ import { planModeOf } from "../common/planmode.mjs";
14
15
 
15
16
  const REORG_MARK = "memory-palace:last-reorg:";
16
17
  const REORG_MARK_RE = new RegExp(`\\n?<!--\\s*${REORG_MARK}[^>]*-->\\s*$`);
@@ -35,7 +36,7 @@ export function readLastReorg(md) {
35
36
  export function checkReorgGate(memFile, cfg) {
36
37
  const md = readMdSync(memFile);
37
38
  const size = md.length;
38
- const overBudget = size > (cfg.workspaceBudgetChars || 3000);
39
+ const overBudget = size > (cfg.workspaceBudgetChars || 6000);
39
40
  const last = readLastReorg(md);
40
41
  const cooldownMs = (cfg.reorgCooldownDays ?? 7) * 86400000;
41
42
  const cooled = Date.now() - last >= cooldownMs;
@@ -49,7 +50,7 @@ export function checkReorgGate(memFile, cfg) {
49
50
  ? cooled
50
51
  ? "ok"
51
52
  : `重整冷却期内(上次重整 ${new Date(last).toLocaleString()},冷却 ${cfg.reorgCooldownDays ?? 7} 天),请用 memory_update_section 做章节级修正。`
52
- : `MEMORY.md 当前 ${size} 字符未超出注入预算(${cfg.workspaceBudgetChars || 3000}),无需重整;请用 memory_write / memory_update_section 维护。`,
53
+ : `MEMORY.md 当前 ${size} 字符未超出注入预算(${cfg.workspaceBudgetChars || 6000}),无需重整;请用 memory_write / memory_update_section 维护。`,
53
54
  };
54
55
  }
55
56
 
@@ -65,8 +66,8 @@ function sectionHasEntry(md, section, entry) {
65
66
  }
66
67
 
67
68
  /**
68
- * 注册 hybrid 工具 + pre-execute 闸门。仅在 memoryMode === "hybrid" 时由 index.mjs 调用
69
- * (memoryMode 切换需重启 dsh——注册时机安全,不存在运行中换挡错配)。
69
+ * 注册记忆工具(MEMORY.md 章节化写入 / 整章节替换 / 全量重整)+ pre-execute 闸门。
70
+ * v1.8.0:由 index.mjs 无条件装配(原 hybrid 已成唯一写入路径)。
70
71
  * @param {{ ctx: object, getConfig: () => object, paths: object, records: object, state: object }} deps
71
72
  */
72
73
  export function registerHybridTools({ ctx, getConfig, paths, records, state }) {
@@ -118,7 +119,6 @@ export function registerHybridTools({ ctx, getConfig, paths, records, state }) {
118
119
  render: (_args, value) => [{ type: "text", text: value?.message ?? "Saved." }],
119
120
  },
120
121
  async execute(args, exec) {
121
- state.recentAgentWrote = true;
122
122
  const c = cfg();
123
123
  if (!c.enabled) return { ok: false, message: "memory-palace is currently disabled in settings." };
124
124
  const section = String(args.section ?? "").trim();
@@ -127,28 +127,33 @@ export function registerHybridTools({ ctx, getConfig, paths, records, state }) {
127
127
  const cwd = sessionCwd(exec);
128
128
  const target = memFileOf(args.scope === "user" ? "user" : "project", cwd);
129
129
  if (!target) return { ok: false, message: "No active workspace." };
130
- try {
131
- let wrote = 0;
132
- let skipped = 0;
133
- const files = target.dirs.length ? target.dirs.map((d) => paths.memoryFileOf(d, cwd)) : [target.file];
134
- for (const file of files) {
135
- const md = readMdSync(file);
136
- if (sectionHasEntry(md, section, entry)) {
137
- skipped++;
138
- continue;
139
- }
140
- const r = upsertSectionText(md, section, entry);
141
- if (!r.ok) {
142
- skipped++;
143
- continue;
130
+ try {
131
+ let wrote = 0;
132
+ let skipped = 0;
133
+ let flattened = false;
134
+ const files = target.dirs.length ? target.dirs.map((d) => paths.memoryFileOf(d, cwd)) : [target.file];
135
+ for (const file of files) {
136
+ const md = readMdSync(file);
137
+ if (sectionHasEntry(md, section, entry)) {
138
+ skipped++;
139
+ continue;
140
+ }
141
+ const r = upsertSectionText(md, section, entry);
142
+ if (!r.ok) {
143
+ skipped++;
144
+ continue;
145
+ }
146
+ await writeFile(file, r.text, "utf8");
147
+ wrote++;
148
+ flattened = flattened || r.flattened === true;
144
149
  }
145
- await writeFile(file, r.text, "utf8");
146
- wrote++;
147
- }
150
+ // v1.8.0-alpha.1 修复(D1):entry 经 flattenEntry 扁平化为单行——含内嵌换行/标题行
151
+ // 时不再原样落盘破坏结构,message 明示合并,不静默撒谎。
152
+ const flatNote = flattened ? "(entry 含多行,已合并为单行)" : "";
148
153
  return {
149
154
  ok: wrote > 0,
150
155
  message: wrote > 0
151
- ? `Saved to ${wrote} MEMORY.md file(s) under 「${section}」${skipped ? ` (${skipped} duplicate skipped)` : ""}.`
156
+ ? `Saved to ${wrote} MEMORY.md file(s) under 「${section}」${flatNote}${skipped ? ` (${skipped} duplicate skipped)` : ""}.`
152
157
  : `Duplicate entry under 「${section}」; nothing written.`,
153
158
  };
154
159
  } catch (e) {
@@ -202,7 +207,6 @@ export function registerHybridTools({ ctx, getConfig, paths, records, state }) {
202
207
  [{ type: "text", text: value?.actual ? `${value.message}\n\n当前实际内容:\n${value.actual}` : value?.message ?? "Done." }],
203
208
  },
204
209
  async execute(args, exec) {
205
- state.recentAgentWrote = true;
206
210
  const c = cfg();
207
211
  if (!c.enabled) return { ok: false, message: "memory-palace is currently disabled in settings.", actual: "" };
208
212
  const section = String(args.section ?? "").trim();
@@ -217,7 +221,10 @@ export function registerHybridTools({ ctx, getConfig, paths, records, state }) {
217
221
  ? markEntryDeletedText(md, section, oldText)
218
222
  : replaceSectionText(md, section, oldText, args.newText);
219
223
  if (!r.ok) {
220
- return { ok: false, message: `更新被拒绝(${r.reason}):请先 memory_read 重读后再试。`, actual: r.actual ?? "" };
224
+ const msg = r.reason === "heading-mismatch"
225
+ ? `更新被拒绝(heading-mismatch):newText 的标题行与 section 参数「${section}」不一致(或缺少 ## 标题行)。请保持两者一致后重试;确需重命名章节,请同步修改标题行与 section 参数。`
226
+ : `更新被拒绝(${r.reason}):请先 memory_read 重读后再试。`;
227
+ return { ok: false, message: msg, actual: r.actual ?? "" };
221
228
  }
222
229
  await writeFile(file, r.text, "utf8");
223
230
  return { ok: true, message: `Updated 「${section}」 in ${toHomeShort(file)} (${r.reason}).`, actual: "" };
@@ -258,7 +265,6 @@ export function registerHybridTools({ ctx, getConfig, paths, records, state }) {
258
265
  render: (_args, value) => [{ type: "text", text: value?.message ?? "Done." }],
259
266
  },
260
267
  async execute(args, exec) {
261
- state.recentAgentWrote = true;
262
268
  const c = cfg();
263
269
  if (!c.enabled) return { ok: false, message: "memory-palace is currently disabled in settings." };
264
270
  const cwd = sessionCwd(exec);
@@ -310,7 +316,8 @@ export function attachHybridGuards(ctx, getConfig, paths, state) {
310
316
  ctx.on("tools/pre-execute", async (exec, next) => {
311
317
  const name = exec?.name;
312
318
  if (!HYBRID_WRITE_TOOLS.has(name)) return next();
313
- if (state.planModeActive) {
319
+ // 按**会话**判定(plan 模式是会话级状态,见 common/planmode.mjs)——别的会话进 plan 不该 deny 本会话
320
+ if (planModeOf(state, exec?.agent?.session)) {
314
321
  return { kind: "deny", reason: "plan 模式下禁止写入记忆" };
315
322
  }
316
323
  if (name !== "memory_reorganize") return next();
@@ -327,7 +334,7 @@ export function attachHybridGuards(ctx, getConfig, paths, state) {
327
334
  kind: "ask",
328
335
  reason:
329
336
  `⚠️ 项目级 MEMORY.md 全量重整确认\n` +
330
- `当前 ${gate.size} 字符(已超预算 ${c.workspaceBudgetChars || 3000}),冷却已满足(上次重整 ${gate.last ? new Date(gate.last).toLocaleString() : "从未"})。\n` +
337
+ `当前 ${gate.size} 字符(已超预算 ${c.workspaceBudgetChars || 6000}),冷却已满足(上次重整 ${gate.last ? new Date(gate.last).toLocaleString() : "从未"})。\n` +
331
338
  `即将用 ${size} 字符的新全文覆盖 ${file}(原文件会自动备份)。\n` +
332
339
  `请确认是否执行重整?`,
333
340
  };