sage-mem 2.1.3 → 2.1.4
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 +1 -1
- package/README.zh.md +1 -1
- package/lib/autodream/tools.js +14 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
**Cross-session memory for DeepSeek Harness — plain markdown files, no database, no worker.**
|
|
14
14
|
|
|
15
|
-
**Current release: `2.1.
|
|
15
|
+
**Current release: `2.1.4`** — the npm badge above is the dynamic source of truth. **Contract baseline: `1.0.0`** — the first stable release, whose public commitments, the capability boundaries and the list of things it explicitly cannot do are frozen in the
|
|
16
16
|
[frozen contract](https://github.com/gezi-wen/sage-mem/blob/main/docs/1.0.0-contract.md).
|
|
17
17
|
|
|
18
18
|
If it is useful to you, a Star is the most direct feedback.
|
package/README.zh.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
> File-based cross-session memory for DeepSeek Harness — every memory is a plain markdown file you can open, edit and delete. No database, no worker, no port.
|
|
16
16
|
|
|
17
|
-
**当前发布版本:`2.1.
|
|
17
|
+
**当前发布版本:`2.1.4`** —— 顶部 npm 徽章是动态权威。**契约基线:`1.0.0`** —— 第一个稳定版,公开承诺、能力边界与「明确做不到」的清单冻结在
|
|
18
18
|
[契约冻结](https://github.com/gezi-wen/sage-mem/blob/main/docs/1.0.0-contract.md)。
|
|
19
19
|
|
|
20
20
|
如果它对你有用,给个 Star 是最直接的反馈。
|
package/lib/autodream/tools.js
CHANGED
|
@@ -36,7 +36,7 @@ import { writeFileAtomic, warnLog } from '../memory/util.js'
|
|
|
36
36
|
import { buildConflict, listConflicts, readConflict, sameConflict, writeConflict } from '../memory/conflicts.js'
|
|
37
37
|
import { memoryPathOf } from '../memory/paths.js'
|
|
38
38
|
import { decideWriteIntent } from '../memory/write-gate.js'
|
|
39
|
-
import { inspectMemoryEntry, reservedIdentitySet } from '../memory/scan.js'
|
|
39
|
+
import { inspectMemoryEntry, readVerified, reservedIdentitySet } from '../memory/scan.js'
|
|
40
40
|
|
|
41
41
|
/** 单次 read_memory 返回的正文上限(字符)。超过就截断并明确告诉模型。 */
|
|
42
42
|
const READ_LIMIT = 20000
|
|
@@ -121,7 +121,8 @@ async function scanEntries(memoryDir, ctx) {
|
|
|
121
121
|
continue
|
|
122
122
|
}
|
|
123
123
|
try {
|
|
124
|
-
|
|
124
|
+
// 同上:清单也要从闸门检查过的那个句柄读(N03 —— 检查后换文件不能把外部标题带进来)。
|
|
125
|
+
const content = await readVerified(full, gate, 'utf8')
|
|
125
126
|
const meta = parseFrontmatterLite(content)
|
|
126
127
|
const body = content.replace(/^---\r?\n[\s\S]*?\r?\n---/, '')
|
|
127
128
|
const h1 = body.match(/^#\s+(.+)$/m)
|
|
@@ -538,9 +539,16 @@ export function createToolRunner(opts) {
|
|
|
538
539
|
const before = await hostStat()
|
|
539
540
|
let content
|
|
540
541
|
try {
|
|
541
|
-
|
|
542
|
+
/**
|
|
543
|
+
* 内容走**公共句柄读取入口**(外部审查 2026-10-31,N03):`inspectMemoryEntry` 是按路径
|
|
544
|
+
* `lstat`,`readFile(路径)` 是另一次按路径打开 —— 两次之间把普通文件换成符号链接 / 另一份
|
|
545
|
+
* 文件,读的就是没检查过的东西(审查用真符号链接复现出目录外内容)。`readVerified`
|
|
546
|
+
* 改成 open 句柄 + fstat 比对 `dev:ino` / 大小,只从那个句柄读;体积上限也在全文读入
|
|
547
|
+
* **之前**生效,不会先整份读进内存再判。
|
|
548
|
+
*/
|
|
549
|
+
content = await readVerified(full, gate, 'utf8')
|
|
542
550
|
} catch (err) {
|
|
543
|
-
//
|
|
551
|
+
// 扫到读之间被删是正常状态;其余读失败(含「检查后被换掉」)照旧抛给上层。
|
|
544
552
|
if (err?.code === 'ENOENT') return { ok: false, gate: { ok: false, code: 'missing' } }
|
|
545
553
|
throw err
|
|
546
554
|
}
|
|
@@ -982,10 +990,11 @@ export function createToolRunner(opts) {
|
|
|
982
990
|
return '错误:report_contradiction 必须给 note —— 一句话说明这两条到底哪里互斥。'
|
|
983
991
|
}
|
|
984
992
|
// 两份正文都存进记录:面板上人要对照着判,只留一边等于没得判。
|
|
993
|
+
// 同样走句柄读取入口(N03):记录里的正文会被人和模型看到,不能来自目录外。
|
|
985
994
|
const readBody = async (full) => {
|
|
986
995
|
const gate = await inspectMemoryEntry(full)
|
|
987
996
|
if (!gate.ok) return null
|
|
988
|
-
return await
|
|
997
|
+
return await readVerified(full, gate, 'utf8').catch(() => null)
|
|
989
998
|
}
|
|
990
999
|
const onDisk = await readBody(oldFull)
|
|
991
1000
|
const incoming = await readBody(newFull)
|
package/package.json
CHANGED