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.
- package/README.md +8 -0
- package/README.zh.md +8 -0
- package/lib/assembly/autodream-mount.js +24 -0
- package/lib/assembly/index.js +13 -0
- package/lib/assembly/observe-mount.js +37 -0
- package/lib/assembly/tools-mount.js +44 -0
- package/lib/assembly/write-gate-mount.js +92 -0
- package/lib/{autodream-audit.js → autodream/audit.js} +2 -2
- package/lib/autodream/engine.js +2 -2
- package/lib/autodream/llm-loop.js +1 -1
- package/lib/autodream/report.js +1 -1
- package/lib/{autodream-tools.js → autodream/tools.js} +8 -8
- package/lib/index.js +26 -719
- package/lib/memory/write-gate.js +1 -1
- package/lib/recall/compose.js +160 -0
- package/lib/recall/debug.js +23 -0
- package/lib/recall/index.js +15 -0
- package/lib/recall/injector.js +181 -0
- package/lib/recall/select.js +138 -0
- package/lib/recall/state.js +46 -0
- package/lib/recall/text.js +14 -0
- package/lib/settings.js +98 -0
- package/package.json +1 -1
- /package/lib/{autodream-prompt.js → autodream/prompt.js} +0 -0
package/README.md
CHANGED
|
@@ -492,6 +492,14 @@ memory/ directory (markdown files, 4 types)
|
|
|
492
492
|
|
|
493
493
|
No worker, no SQLite, no HTTP port, no long-running process.
|
|
494
494
|
|
|
495
|
+
Module boundaries (since 1.2.0):
|
|
496
|
+
|
|
497
|
+
- `lib/index.js` — the **composition root** plus the three gateways. The typert manifest pins `sourceLocation` to this file, so the gateway classes stay here (moving them would be a contract drift)
|
|
498
|
+
- `lib/recall/` — the injection chain: selection (`select`) / rendering and budget (`compose`) / text (`text`) / session state (`state`)
|
|
499
|
+
- `lib/assembly/` — the four mounts: autodream / retrieval tools / observation / write gate
|
|
500
|
+
- `lib/memory/` — pure memory-layer functions and file operations; `lib/autodream/` — the autodream engine
|
|
501
|
+
- Dependency direction: `memory` never imports `autodream`, `recall` depends only on `memory`, `assembly` never imports the composition root (`test/structure-test.mjs` turns red if that changes)
|
|
502
|
+
|
|
495
503
|
## Contributing
|
|
496
504
|
|
|
497
505
|
```bash
|
package/README.zh.md
CHANGED
|
@@ -496,6 +496,14 @@ memory/ 目录(markdown 文件,4 类)
|
|
|
496
496
|
|
|
497
497
|
没有 worker、没有 SQLite、没有 HTTP 端口、没有常驻进程。
|
|
498
498
|
|
|
499
|
+
模块边界(1.2.0 起):
|
|
500
|
+
|
|
501
|
+
- `lib/index.js` —— **组合根** + 三个 Gateway。typert manifest 的 `sourceLocation` 钉在这个文件上,所以 Gateway 类不搬走(搬 = 契约漂移)
|
|
502
|
+
- `lib/recall/` —— 注入链:选材(`select`)/ 渲染与预算(`compose`)/ 文本(`text`)/ 会话状态(`state`)
|
|
503
|
+
- `lib/assembly/` —— 四块挂载:autodream / 检索工具 / 观测 / 写入门
|
|
504
|
+
- `lib/memory/` —— 记忆层的纯函数与文件操作;`lib/autodream/` —— 自动做梦引擎
|
|
505
|
+
- 依赖方向:`memory` 不认识 `autodream`,`recall` 只依赖 `memory`,`assembly` 不反向依赖组合根(`test/structure-test.mjs` 会红)
|
|
506
|
+
|
|
499
507
|
## 参与开发
|
|
500
508
|
|
|
501
509
|
```bash
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* autodream 的挂载(1.2.0 从 `MemoryGateway` 构造函数原样搬出)。
|
|
3
|
+
*
|
|
4
|
+
* 整个包在 try 里:autodream 起不来(比如 timer 服务缺失、状态目录没权限)
|
|
5
|
+
* 不该把 sage-mem 最核心的那件事——「记忆按问题注入」——一起拖死。
|
|
6
|
+
* 这是本插件唯一一处「降级而不是失败」的设计,理由就在这里。
|
|
7
|
+
*/
|
|
8
|
+
import { warnLog } from '../memory/util.js'
|
|
9
|
+
|
|
10
|
+
/** @param {object} ctx @param {{AutodreamEngine:Function, AutodreamGateway:Function, memoryDir:string, sessionsRoot:string, reservedFiles:string[]}} deps */
|
|
11
|
+
export function mountAutodream(ctx, { AutodreamEngine, AutodreamGateway, memoryDir, sessionsRoot, reservedFiles }) {
|
|
12
|
+
try {
|
|
13
|
+
const engine = new AutodreamEngine(ctx, { memoryDir, sessionsRoot, reservedFiles })
|
|
14
|
+
new AutodreamGateway(ctx, engine)
|
|
15
|
+
engine.start()
|
|
16
|
+
// 卸载时把定时器与锁收干净。effect 拿不到就交给 unref 兜底(不阻止进程退出)。
|
|
17
|
+
if (typeof ctx.effect === 'function') ctx.effect(() => () => engine.dispose())
|
|
18
|
+
} catch (err) {
|
|
19
|
+
warnLog(ctx, `autodream 初始化失败(记忆本体不受影响):${err?.message ?? err}`)
|
|
20
|
+
// 异常路径额外打一行到 stderr:warnLog 走 ctx.logger,某些启动阶段它还没接上,
|
|
21
|
+
// 而「autodream 静默不工作」正是最难查的那种故障(曾为此排查很久)。
|
|
22
|
+
console.error('[sage-mem] autodream init failed:', err && err.stack ? err.stack : err)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 组合根的四块挂载(1.2.0 的模块边界)。
|
|
3
|
+
*
|
|
4
|
+
* 每块一个文件、一个 `mountXxx(ctx, …)`:组合根的构造函数只负责按顺序调用它们。
|
|
5
|
+
* 顺序是语义的一部分(autodream → 工具 → 观测 → 写门),所以这里按同一顺序再导出。
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ `mountWriteGate` 必须**同步**调用(注册窗口在 fs 就绪之前就关),
|
|
8
|
+
* 这条约束写在 `write-gate-mount.js` 头注里,并由 `write-gate-wiring-test.mjs` 第 7 组看着。
|
|
9
|
+
*/
|
|
10
|
+
export { mountAutodream } from './autodream-mount.js'
|
|
11
|
+
export { mountMemoryTools } from './tools-mount.js'
|
|
12
|
+
export { mountObserve } from './observe-mount.js'
|
|
13
|
+
export { mountWriteGate } from './write-gate-mount.js'
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「模型自己翻了记忆目录」的观测挂载(1.2.0 从构造函数原样搬出)。
|
|
3
|
+
*
|
|
4
|
+
* 宿主的 `tools/result` 把**每一次**工具调用的 `exec`(含 name / arguments)交给我们,
|
|
5
|
+
* 于是「模型用内置 read/grep 读了哪份记忆」第一次变得可观测 —— 此前台账只记得下
|
|
6
|
+
* 「插件注入了什么」,记不下「模型主动翻了什么」。
|
|
7
|
+
*
|
|
8
|
+
* 三条纪律:
|
|
9
|
+
* 1. **只记能可靠判定的**(`observe.js` 认 read/grep/glob 的路径参数;`pwsh` 一律不认,
|
|
10
|
+
* 那是**已知观测盲区** —— 不许把「看不到」说成「没读」)。
|
|
11
|
+
* 2. **按 session × 文件去重**:翻页(read 的 offset 不同)不刷计数,那是同一个动作。
|
|
12
|
+
* 3. **失败绝不影响任何东西**:观测是副产品,它的问题不许冒泡到工具调用链上。
|
|
13
|
+
*/
|
|
14
|
+
import { accessEventKey, classifyToolExecution, createDeduper } from '../memory/observe.js'
|
|
15
|
+
import { recordRead } from '../memory/access.js'
|
|
16
|
+
|
|
17
|
+
export function mountObserve(ctx, memoryDir) {
|
|
18
|
+
const observedReads = createDeduper()
|
|
19
|
+
try {
|
|
20
|
+
ctx.on('tools/result', (exec) => {
|
|
21
|
+
try {
|
|
22
|
+
const cls = classifyToolExecution(exec?.name, exec?.arguments, memoryDir)
|
|
23
|
+
if (cls.kind !== 'active_read' || !cls.file) return
|
|
24
|
+
const key = accessEventKey({
|
|
25
|
+
sessionId: exec?.agent?.id ?? '',
|
|
26
|
+
file: cls.file,
|
|
27
|
+
// revision 要读盘才算得出,这里拿不到 —— 所以去重粒度是「session × 文件」。
|
|
28
|
+
// 写明这条口径,免得以后有人以为它按内容去重。
|
|
29
|
+
revision: '',
|
|
30
|
+
kind: cls.kind,
|
|
31
|
+
})
|
|
32
|
+
if (!key || !observedReads.shouldCount(key)) return
|
|
33
|
+
recordRead(cls.file, '')
|
|
34
|
+
} catch { /* 观测失败静默:它不该有任何存在感 */ }
|
|
35
|
+
})
|
|
36
|
+
} catch { /* 老宿主没有这个事件:不影响注入 */ }
|
|
37
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 检索工具(`search_memory` / `read_memory`)的挂载(1.2.0 从构造函数原样搬出)。
|
|
3
|
+
*
|
|
4
|
+
* 工具是否注册成功。拿不到宿主的 `tools` 服务时为 false —— 状态卡据此换文案:
|
|
5
|
+
* 有工具就教它「用 search_memory 搜」,没有就退回「直接读记忆目录」。
|
|
6
|
+
*
|
|
7
|
+
* 为什么是个可变量:注册是**异步就绪**的(`ctx.inject` 要等依赖服务上线),而注入链
|
|
8
|
+
* 每步都跑 —— 前几步可能还没就绪、后几步才就绪,这是正常的。
|
|
9
|
+
* 因此返回 `{ ready: () => boolean }` 而不是一个布尔值:值拷贝会让注入链永远读到 false。
|
|
10
|
+
*
|
|
11
|
+
* ⚠️ **用 `ctx.inject` 挂子 fiber,绝不给本类加 `static inject = ['tools']`。**
|
|
12
|
+
* 理由(`docs/decisions.md` D5,有官方依据):`inject` 是**硬依赖** —— 声明了它的插件
|
|
13
|
+
* 要等依赖就绪才会被加载。宿主没有 `tools` 服务时,整个 sage-mem 会一直待机,
|
|
14
|
+
* **记忆注入跟着一起消失**。而工具只是锦上添花,不值得拿核心功能去换。
|
|
15
|
+
* `ctx.inject` 只让这个子回调等依赖就绪,主插件照常工作;依赖永远不来就只有它不生效。
|
|
16
|
+
*
|
|
17
|
+
* 另一条实测事实(`docs/host-facts.md`):不声明 inject 时 `ctx.get('tools')` 只给
|
|
18
|
+
* `undefined`(那一刻服务还没被提供,而插件不会重跑),所以「先探测再注册」在这里
|
|
19
|
+
* 是行不通的 —— 必须走 `ctx.inject`。
|
|
20
|
+
*/
|
|
21
|
+
import { createMemoryTools } from '../tools/memory-tools.js'
|
|
22
|
+
import { warnLog } from '../memory/util.js'
|
|
23
|
+
|
|
24
|
+
export function mountMemoryTools(ctx) {
|
|
25
|
+
const state = { ready: false }
|
|
26
|
+
try {
|
|
27
|
+
if (typeof ctx.inject === 'function') {
|
|
28
|
+
ctx.inject(['tools'], (inner) => {
|
|
29
|
+
try {
|
|
30
|
+
const { searchMemory, readMemory } = createMemoryTools(ctx)
|
|
31
|
+
inner.tools.register(searchMemory)
|
|
32
|
+
inner.tools.register(readMemory)
|
|
33
|
+
state.ready = true
|
|
34
|
+
} catch (err) {
|
|
35
|
+
// 注册失败 ≠ 注入失败。出声一次,然后什么也不做。
|
|
36
|
+
warnLog(ctx, `检索工具注册失败(记忆注入不受影响):${err?.message ?? err}`)
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
} catch (err) {
|
|
41
|
+
warnLog(ctx, `检索工具挂载失败(记忆注入不受影响):${err?.message ?? err}`)
|
|
42
|
+
}
|
|
43
|
+
return { ready: () => state.ready }
|
|
44
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 写入门(版本守卫)的挂载(1.2.0 从构造函数原样搬出)。
|
|
3
|
+
*
|
|
4
|
+
* 实测(`scripts/events-probe.mjs`,本机 headless,2026-10-15):这两个事件**只对
|
|
5
|
+
* 「工具发起」的写入**触发 —— 模型调内置 `write` / `edit` 打到记忆文件时会走这里;
|
|
6
|
+
* 而**插件自己**调 `ctx.fs.writeText` 时一次都不发(实测 0 次),那条路由调用方自己传
|
|
7
|
+
* `expected`(见 `lib/autodream/tools.js` 的 `writeMemoryText`)。
|
|
8
|
+
* 监听器**不需要声明 inject** 也能收到,所以这里不挂子 fiber。
|
|
9
|
+
*
|
|
10
|
+
* 它买到的东西:**模型的 `write` 没法在一个记忆文件被改过之后静默覆盖它** ——
|
|
11
|
+
* 守卫过不了,宿主会以 `FS_STALE_VERSION` 明确拒绝(实测确实会被拒)。
|
|
12
|
+
* 这属于「把静默的坏结果变成显式的失败」,不是「阻止写入」:这个事件**没有拒绝动作**,
|
|
13
|
+
* 我们也不发明一个(见 write-gate.js 的头注释)。
|
|
14
|
+
*
|
|
15
|
+
* 单槽纪律:返回 `undefined` 才是「不管」。目录外**必须**返回 undefined,否则会占掉槽位、
|
|
16
|
+
* 影响整块盘的写入(write-gate.js 的 `outside_memory` 判在 `caller_wins` 之前正是为此)。
|
|
17
|
+
*
|
|
18
|
+
* ⚠️⚠️ **本函数必须在 `apply()` 里被直接调用,绝不能挪进 `ctx.inject(['fs'], …)`,也不能给
|
|
19
|
+
* 插件加 `static inject = ['fs']`。** 这不是风格偏好,是实测出来的硬约束:
|
|
20
|
+
* 这两个事件有一个**注册窗口**,窗口在 fs 服务就绪**之前**就关了。
|
|
21
|
+
* 不 inject、apply 里直接挂 → 收到 1 次 ✅
|
|
22
|
+
* 顶层 `inject:['fs']` → **0 次**
|
|
23
|
+
* 顶层 `inject:['tokenMeter']` → **0 次**(与 fs 无关,是时机问题)
|
|
24
|
+
* `ctx.inject(['fs'], sub)` 子 fiber → **0 次**
|
|
25
|
+
* 不 inject、但 apply 之后 2.5 秒再挂 → **0 次**(晚挂收不到)
|
|
26
|
+
* 证据:`scripts/host-facts-probe.mjs` 的七组对照 + `docs/host-facts.md` 3.3 节。
|
|
27
|
+
* 之所以特别标注:**按 D5 的规范直觉(可选服务要用 ctx.inject 挂子 fiber)改这里,
|
|
28
|
+
* 会让写门静默失效** —— 编译得过、测试可能也过,只是永远不触发。
|
|
29
|
+
* 而我们**不需要** inject:实测不声明 inject 时,处理器**内部** `ctx.get('fs')` 照样拿得到服务
|
|
30
|
+
* (`{hasFs:true, hasStat:true}`),取版本就靠它。
|
|
31
|
+
*/
|
|
32
|
+
import { memoryPathOf } from '../memory/paths.js'
|
|
33
|
+
import { decideEditGuard, decideWriteIntent } from '../memory/write-gate.js'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 探目标的当前版本。**区分「不存在」与「不知道」**。
|
|
37
|
+
*
|
|
38
|
+
* 这个区分不是洁癖:`!!info` 会把「拿不到 fs 服务」也写成 `exists:false`,判定表据此给
|
|
39
|
+
* `createIfAbsent` —— 而目标**其实是存在的**记忆文件时,那是一次**误拦**(写入必然失败,
|
|
40
|
+
* 模型收到一句看不懂的错误)。拿不准就不干涉,比猜一个安全。
|
|
41
|
+
*/
|
|
42
|
+
function makeProbeTarget(ctx) {
|
|
43
|
+
return async (target) => {
|
|
44
|
+
try {
|
|
45
|
+
const fs = typeof ctx.get === 'function' ? ctx.get('fs') : undefined
|
|
46
|
+
if (!fs || typeof fs.stat !== 'function' || !target) return { known: false }
|
|
47
|
+
const info = await fs.stat(target)
|
|
48
|
+
return { known: true, exists: !!info, version: info?.version ?? null }
|
|
49
|
+
} catch {
|
|
50
|
+
return { known: false }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function mountWriteGate(ctx, memoryDir) {
|
|
56
|
+
const probeTarget = makeProbeTarget(ctx)
|
|
57
|
+
try {
|
|
58
|
+
if (typeof ctx.on === 'function') {
|
|
59
|
+
// 公共骨架:**先判位置、再决定要不要碰链子**。
|
|
60
|
+
//
|
|
61
|
+
// ⚠️ 三条纪律(都是踩出来的):
|
|
62
|
+
// 1. **目录外连 `next()` 都不调**。宿主对**任何**工具写入都发这个事件(实测:写一个
|
|
63
|
+
// 临时文件也发),无条件调 next() + stat 等于给整个系统的每次写文件都加一次系统调用,
|
|
64
|
+
// 而我们本来就什么都不该做。直接 `return next()` 把决策权原样交出去。
|
|
65
|
+
// 2. **一旦调了 `next()`,就必须把它给的东西交回去**。`next()` 返回的是下游(或调用方
|
|
66
|
+
// 自己)已经做出的决定;此时再 `return undefined` 会把这个决定**丢掉** ——
|
|
67
|
+
// 我们本想「不干涉」,实际却比干涉还糟。
|
|
68
|
+
// 3. **探不到版本就不碰**(见 probeTarget):把「不知道」当「不存在」会误拦真实存在的文件。
|
|
69
|
+
const gateEvent = (decide) => async (target, actor, next) => {
|
|
70
|
+
try {
|
|
71
|
+
const memoryPath = memoryPathOf(target?.displayPath, memoryDir)
|
|
72
|
+
if (!memoryPath.ok) return typeof next === 'function' ? next() : undefined
|
|
73
|
+
const caller = typeof next === 'function' ? await next() : undefined
|
|
74
|
+
const probed = await probeTarget(target)
|
|
75
|
+
if (!probed.known) return caller
|
|
76
|
+
const chosen = decide({
|
|
77
|
+
memoryPath,
|
|
78
|
+
exists: probed.exists,
|
|
79
|
+
version: probed.version,
|
|
80
|
+
caller,
|
|
81
|
+
})
|
|
82
|
+
// 我们给出决定就用我们的;决定不了(undefined)就把 `caller` 原样交回去,不吞。
|
|
83
|
+
return chosen === undefined ? caller : chosen
|
|
84
|
+
} catch {
|
|
85
|
+
return undefined /* 写门在宿主的写入链路上:抛出去会把一次正常写入炸掉 */
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
ctx.on('fs/write-intent', gateEvent((facts) => decideWriteIntent(facts).intent))
|
|
89
|
+
ctx.on('fs/edit-intent', gateEvent((facts) => decideEditGuard(facts).guard))
|
|
90
|
+
}
|
|
91
|
+
} catch { /* 老宿主没有这两个事件:写入按宿主默认语义走 */ }
|
|
92
|
+
}
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
|
|
24
24
|
import { readdir, readFile } from 'node:fs/promises'
|
|
25
25
|
import { join } from 'node:path'
|
|
26
|
-
import { ARCHIVE_DIR_NAME, inspectMemoryEntry } from '
|
|
27
|
-
import { warnLog } from '
|
|
26
|
+
import { ARCHIVE_DIR_NAME, inspectMemoryEntry } from '../memory/scan.js'
|
|
27
|
+
import { warnLog } from '../memory/util.js'
|
|
28
28
|
|
|
29
29
|
/** 索引文件名。它本身不是一条记忆,是索引。 */
|
|
30
30
|
const INDEX_FILE = 'MEMORY.md'
|
package/lib/autodream/engine.js
CHANGED
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
* 这里保留**同名同签名**的方法,以 `.call(this, …)` 转发 —— 纯搬移、零行为变更。
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
-
import { auditMemoryDir } from '
|
|
37
|
-
import { autodreamToolSchemas, createToolRunner } from '
|
|
36
|
+
import { auditMemoryDir } from './audit.js'
|
|
37
|
+
import { autodreamToolSchemas, createToolRunner } from './tools.js'
|
|
38
38
|
import { CONFIG_VERSION, DEFAULT_CONFIG, DEFAULT_TOKEN_BUDGET, FAILURE_BACKOFF_MS, FEATURE_DIR, LEGACY_SNAPSHOT_DIR, LEGACY_STATE_FILE, SCHEDULE_INTERVAL_MS, STARTUP_CHECK_DELAY_MS, STATE_DIR } from './config.js'
|
|
39
39
|
import { countSessionsSince, newRunId, stampHuman } from './util.js'
|
|
40
40
|
import { flushAccess, ledgerFileFor, pickArchiveCandidates, readAccessLedger } from '../memory/access.js'
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 之所以不用真正的子代理:见 engine.js 顶部第 2 条设计决定。
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { buildAutodreamSystemPrompt, buildAutodreamUserPrompt } from '
|
|
11
|
+
import { buildAutodreamSystemPrompt, buildAutodreamUserPrompt } from './prompt.js'
|
|
12
12
|
import { randomUUID } from 'node:crypto'
|
|
13
13
|
|
|
14
14
|
export class BlockAssembler {
|
package/lib/autodream/report.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 与拆分前完全一致,没有引入任何模块级可变状态。
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { formatAudit } from '
|
|
9
|
+
import { formatAudit } from './audit.js'
|
|
10
10
|
import { LEGACY_REPORT_DIR, REPORT_DIR } from './config.js'
|
|
11
11
|
import { OP_LABEL, formatDelta, readJson, safeName, stampHuman } from './util.js'
|
|
12
12
|
import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises'
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
import { readFile, readdir, stat } from 'node:fs/promises'
|
|
28
28
|
import { join, basename } from 'node:path'
|
|
29
29
|
import { createHash } from 'node:crypto'
|
|
30
|
-
import { auditMemoryDir, formatAudit, parseFrontmatterLite } from './
|
|
31
|
-
import { archiveMemory } from '
|
|
32
|
-
import { MAX_FILE_BYTES, STATE_ROOT } from '
|
|
33
|
-
import { writeFileAtomic, warnLog } from '
|
|
34
|
-
import { buildConflict, writeConflict } from '
|
|
35
|
-
import { memoryPathOf } from '
|
|
36
|
-
import { decideWriteIntent } from '
|
|
37
|
-
import { inspectMemoryEntry, reservedIdentitySet } from '
|
|
30
|
+
import { auditMemoryDir, formatAudit, parseFrontmatterLite } from './audit.js'
|
|
31
|
+
import { archiveMemory } from '../memory/archive.js'
|
|
32
|
+
import { MAX_FILE_BYTES, STATE_ROOT } from '../memory/config.js'
|
|
33
|
+
import { writeFileAtomic, warnLog } from '../memory/util.js'
|
|
34
|
+
import { buildConflict, writeConflict } from '../memory/conflicts.js'
|
|
35
|
+
import { memoryPathOf } from '../memory/paths.js'
|
|
36
|
+
import { decideWriteIntent } from '../memory/write-gate.js'
|
|
37
|
+
import { inspectMemoryEntry, reservedIdentitySet } from '../memory/scan.js'
|
|
38
38
|
|
|
39
39
|
/** 单次 read_memory 返回的正文上限(字符)。超过就截断并明确告诉模型。 */
|
|
40
40
|
const READ_LIMIT = 20000
|