sage-mem 1.1.0 → 1.2.0

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.
@@ -0,0 +1,98 @@
1
+ /**
2
+ * 面板设置与「保留名原样读写」的落点(1.2.0 从 `lib/index.js` 原样搬出)。
3
+ *
4
+ * 这里的每一项都与 `memory/config.js` 的 `SAGE_MEM_*` 一一对应;边界值照抄那边的
5
+ * envInt 调用(lo/hi 不许在这里另立一套)。
6
+ */
7
+ import { readFile } from 'node:fs/promises'
8
+ import { basename, join } from 'node:path'
9
+ import { STATE_ROOT } from './memory/config.js'
10
+ import { DEFAULT_CONFIG } from './autodream/config.js'
11
+ import { readJson } from './autodream/util.js'
12
+
13
+ /**
14
+ * 面板可改的 5 项注入上限 —— 与 `memory/config.js` 里的 `SAGE_MEM_*` 一一对应。
15
+ * 边界值照抄那边的 envInt 调用(lo/hi 不许在这里另立一套)。
16
+ */
17
+ export const LIMIT_SPECS = {
18
+ maxResults: { env: 'SAGE_MEM_MAX_RESULTS', dflt: 10, lo: 1, hi: 30 },
19
+ maxChars: { env: 'SAGE_MEM_MAX_CHARS', dflt: 1500, lo: 200, hi: 20000 },
20
+ maxBaseline: { env: 'SAGE_MEM_MAX_BASELINE', dflt: 8, lo: 1, hi: 50 },
21
+ maxSessionBytes: { env: 'SAGE_MEM_MAX_SESSION_BYTES', dflt: 60 * 1024, lo: 4096, hi: 512 * 1024 },
22
+ staleDays: { env: 'SAGE_MEM_STALE_DAYS', dflt: 1, lo: 0, hi: 365 },
23
+ }
24
+
25
+ /** 面板设置的落点:状态根下(与 autodream 的配置、台账同级)。 */
26
+ export const SETTINGS_FILE = join(STATE_ROOT, 'settings.json')
27
+ export const RESERVED_FILE = join(STATE_ROOT, 'reserved.json')
28
+
29
+ /**
30
+ * `writeRaw` 的体积上限(4 MB)。
31
+ *
32
+ * ⚠️ **故意不复用 `MAX_FILE_BYTES`(512 KB)**:保留名里就有 `session-log.md` 这种追加型
33
+ * 流水,它天然长得比单条记忆大得多(实测已 > 512 KB)。沿用小上限 = 用户永远存不回去。
34
+ */
35
+ export const MAX_RAW_BYTES = 4 * 1024 * 1024
36
+
37
+ /** 整数收敛(与 config.js 的 envInt 同一套语义):非有限数回落默认,越界夹到边界。 */
38
+ export const clampInt = (v, spec) => (Number.isFinite(v) ? Math.max(spec.lo, Math.min(spec.hi, Math.round(v))) : spec.dflt)
39
+
40
+ /** 读 `<状态根>/settings.json`;缺失、坏 JSON、不是对象都当「没设置」。 */
41
+ export async function readSettingsFile() {
42
+ try {
43
+ const parsed = JSON.parse(await readFile(SETTINGS_FILE, 'utf8'))
44
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null
45
+ } catch {
46
+ return null
47
+ }
48
+ }
49
+
50
+ /** 待生效的 5 项上限:`settings.json` > 环境变量 > 内置默认。 */
51
+ export function pendingLimits(fileObj) {
52
+ const out = {}
53
+ const saved = fileObj?.limits && typeof fileObj.limits === 'object' ? fileObj.limits : {}
54
+ for (const [key, spec] of Object.entries(LIMIT_SPECS)) {
55
+ const fromFile = Number(saved[key])
56
+ if (Number.isFinite(fromFile)) {
57
+ out[key] = clampInt(fromFile, spec)
58
+ continue
59
+ }
60
+ const raw = process.env[spec.env]
61
+ out[key] = clampInt(raw === undefined || raw === '' ? NaN : Number(raw), spec)
62
+ }
63
+ return out
64
+ }
65
+
66
+ /** 读 `<状态根>/reserved.json`(用户声明的保护名单)。**保留原始大小写**,好让 get 读回一致。 */
67
+ export async function readReservedFile() {
68
+ try {
69
+ const arr = JSON.parse(await readFile(RESERVED_FILE, 'utf8'))
70
+ if (!Array.isArray(arr)) return []
71
+ return arr
72
+ .map((s) => String(s).trim())
73
+ .filter((s) => s.endsWith('.md') && !s.includes('/') && !s.includes('\\') && !s.startsWith('..'))
74
+ } catch {
75
+ return []
76
+ }
77
+ }
78
+
79
+ /**
80
+ * 原样读写用的名字校验:只要求「单段、`.md`」。
81
+ *
82
+ * ⚠️ **故意不走 `safeName`**:那个函数把 `MEMORY.md` / `session-log.md` 当非记忆条目排除,
83
+ * 而它们恰恰是 `readRaw` / `writeRaw` 的目标。这里只防路径成分与扩展名。
84
+ */
85
+ export function rawName(raw) {
86
+ const s = typeof raw === 'string' ? raw : ''
87
+ if (!s || basename(s) !== s) return null
88
+ // `:` 是 NTFS 交替数据流的分隔符:`MEMORY.md:evil.md` 能过扩展名判据,落盘却是写到
89
+ // 索引的隐藏流上(`readdir` 看不见)。与 `safeName` 同一条理由,这里也要拒。
90
+ if (s.includes(':')) return null
91
+ return /\.md$/i.test(s) ? s : null
92
+ }
93
+
94
+ /** autodream 的实际配置(缺文件/坏文件回落默认值)—— 面板要显示真实策略,不能显示想象。 */
95
+ export async function autodreamConfigSnapshot() {
96
+ const parsed = await readJson(join(STATE_ROOT, 'autodream.json'))
97
+ return { ...DEFAULT_CONFIG, ...(parsed?.config ?? {}) }
98
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sage-mem",
3
3
  "description": "Cross-session memory for DeepSeek Harness — plain markdown files, no database, no worker.",
4
- "version": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {