memory-eternal 0.25.0 → 0.26.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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +27 -26
- package/README.zh-CN.md +33 -32
- package/cursor-extension/package.json +1 -1
- package/dsh-tui-plugin/dsh-plugin.json +1 -1
- package/dsh-tui-plugin/package.json +1 -1
- package/index.js +81 -1
- package/lib/api.js +66 -1
- package/lib/client.js +69 -50
- package/lib/working-memory.js +188 -0
- package/orca-plugin/orca-plugin.json +1 -1
- package/package.json +2 -2
- package/web/app.js +72 -53
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "memory-eternal",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.26.0",
|
|
5
5
|
"description": "记忆核心(Memory Eternal):对话结束后自动沉淀知识卡到本地记忆库(~/.dsh/memory-vault),支持检索 / 召回 / 审核中心。",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "EternalNight996",
|
package/README.md
CHANGED
|
@@ -4,20 +4,22 @@
|
|
|
4
4
|
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/cover-zh.png" width="900" alt="记忆核心 memory-eternal:给 AI 一个不会忘、可审核、可量化的长期记忆" />
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/graph-en.png" width="31%" alt="Knowledge graph: clusters by kind, tag edges" />
|
|
9
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/audit-en.png" width="31%" alt="Review inbox: type bar, queue and verdict pane" />
|
|
10
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/status-en.png" width="31%" alt="Runtime status: database, backups, capture, version" />
|
|
11
|
+
</p>
|
|
12
|
+
<p align="center"><sub>▲ Knowledge graph · review inbox · runtime status</sub></p>
|
|
13
|
+
|
|
7
14
|
**记忆核心(memory-eternal)** —— 给 AI 一个不会忘、可审核、可量化的长期记忆。本地优先 · 审核闸门 · 记忆账本 · 一个库接入 DSH / Claude Code / Codex / Cursor 与任意 MCP 客户端。
|
|
8
15
|
|
|
9
16
|
[](https://github.com/EternalNight996/memory-eternal/stargazers)
|
|
10
17
|
[](https://www.npmjs.com/package/memory-eternal)
|
|
11
|
-
[](https://github.com/EternalNight996/memory-eternal/releases)
|
|
12
19
|
[](LICENSE)
|
|
13
20
|
|
|
14
21
|
**English (default)** · [简体中文](README.zh-CN.md)
|
|
15
22
|
|
|
16
|
-
<p align="center">
|
|
17
|
-
<a href="https://eternalnight996.github.io/memory-eternal/demo/"><img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/demo.gif" width="900" alt="memory-eternal 20-second tour: review queue, token ledger, knowledge graph" /></a>
|
|
18
|
-
</p>
|
|
19
|
-
<p align="center"><sub>▲ 20-second tour — review queue · token ledger · knowledge graph · <a href="https://eternalnight996.github.io/memory-eternal/demo/">live demo</a></sub></p>
|
|
20
|
-
|
|
21
23
|
> **Your AI forgets every session. Give it a memory it can trust.**
|
|
22
24
|
>
|
|
23
25
|
> Local-first, review-gated, measurable — one SQLite vault shared by DSH, Claude Code, Codex, Cursor and any MCP client.
|
|
@@ -154,23 +156,23 @@ net benefit ≈ avoided context cost − (injected characters + distillation cal
|
|
|
154
156
|
|
|
155
157
|
## 📊 How it compares — measured on a real vault
|
|
156
158
|
|
|
157
|
-
Snapshot **2026-11-07**. Our figures are measured on a real **2,321-card vault** on Windows (not a toy corpus); competitor
|
|
159
|
+
Snapshot **2026-11-07**. Our figures are measured on a real **2,321-card vault** on Windows (not a toy corpus); competitor facts come from their public README / docs / license page on the same date. `—` means *not advertised or not verifiable*, not "absent".
|
|
158
160
|
|
|
159
|
-
| Capability | **memory-eternal v0.
|
|
160
|
-
|
|
161
|
-
|
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
| Recall
|
|
167
|
-
|
|
|
168
|
-
|
|
|
169
|
-
|
|
|
170
|
-
|
|
|
171
|
-
| License | MIT |
|
|
172
|
-
|
|
173
|
-
**We are not the biggest here.**
|
|
161
|
+
| Capability | **memory-eternal v0.25.0** | [agentmemory](https://github.com/rohitg00/agentmemory) v0.9.32 | [mem0](https://github.com/mem0ai/mem0) v3 | [Obsidian](https://obsidian.md) | CLAUDE.md / AGENTS.md |
|
|
162
|
+
|---|---|---|---|---|---|
|
|
163
|
+
| Form factor | DSH-native plugin: capture, review UI, graph, CLI/TUI/MCP | local memory server + MCP for coding agents | memory SDK / self-hosted server / cloud | local Markdown notes app | hand-written context files |
|
|
164
|
+
| Storage | SQLite + Markdown mirror (git-friendly) | local file store (Redis optional) | vector + graph stores (self-hosted or cloud) | Markdown files on disk | one flat file |
|
|
165
|
+
| Write gate | **human review queue** (three stores) | automatic capture, no review | LLM extraction, no review | human-written only | none |
|
|
166
|
+
| Immutable audit log | ✅ enforced at the SQLite layer | operation audit tool, not storage-enforced | — | — | — |
|
|
167
|
+
| Retrieval | FTS5 + **BM25/IDF**, column weights | BM25 + vector + graph (local embeddings optional) | semantic + BM25 + entity + graph | full-text search / plugins | whole file injected |
|
|
168
|
+
| Recall latency | **p50 2.33 ms · p95 6.18 ms** | — | p50 ~0.88–1.09 s (vendor paper, cloud) | grows with vault size | grows with file size |
|
|
169
|
+
| Recall model calls | **0** (BM25 only) | 0 for keyword; 1 embedding call when semantic is on | 1 embedding call + optional LLM rerank | 0 | 0 |
|
|
170
|
+
| Model calls per write | 1 when auto-capture runs (can be off) | 0 by default (synthetic compression); LLM optional | ≥1 LLM call per add | 0 | 0 |
|
|
171
|
+
| Token ledger (measured usefulness) | ✅ | — | platform usage dashboard | — | — |
|
|
172
|
+
| Multi-agent | DSH + MCP + hooks + TUI + browser | MCP + REST: Claude Code, Codex, Cursor, DSH, … | Python/TS SDK, self-hosted server, cloud | no native agent interface | loaded per tool |
|
|
173
|
+
| License | MIT | Apache-2.0 | Apache-2.0 | proprietary (free for all use) | n/a |
|
|
174
|
+
|
|
175
|
+
**We are not the biggest here.** mem0 has a funded team, a research paper and a managed service; agentmemory's automatic capture covers more coding agents than we do; Obsidian is where many people already keep their notes. What we do differently is the *write path*: a human gate, a three-store separation, and an audit trail you can point at — plus a token ledger so you can check whether recall is worth its cost.
|
|
174
176
|
|
|
175
177
|
Reproduce our numbers on your own vault:
|
|
176
178
|
|
|
@@ -228,11 +230,10 @@ Not everything should be injected into every session. Core memory is the small,
|
|
|
228
230
|
## 📸 Screenshots
|
|
229
231
|
|
|
230
232
|
<p align="center">
|
|
231
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/
|
|
232
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/audit-en.png" width="32%" alt="Review inbox: type bar, queue and verdict pane" />
|
|
233
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/status-en.png" width="32%" alt="Runtime status: database, backups, capture, version" />
|
|
233
|
+
<a href="https://eternalnight996.github.io/memory-eternal/demo/"><img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/demo.gif" width="900" alt="memory-eternal 20-second tour: review queue, token ledger, knowledge graph" /></a>
|
|
234
234
|
</p>
|
|
235
|
-
<p align="center"><sub>▲
|
|
235
|
+
<p align="center"><sub>▲ 20-second tour — review queue · token ledger · knowledge graph · <a href="https://eternalnight996.github.io/memory-eternal/demo/">live demo</a></sub></p>
|
|
236
|
+
|
|
236
237
|
|
|
237
238
|
<p align="center">
|
|
238
239
|
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/tui-audit.png" width="49%" alt="Native terminal audit: select-all, batch approve/reject" />
|
package/README.zh-CN.md
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
# 🧠 memory-eternal —— 给 AI 装一个可信的第二大脑
|
|
2
2
|
|
|
3
|
-
<p align="center">
|
|
4
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/cover-zh.png" width="900" alt="记忆核心 memory-eternal:给 AI 一个不会忘、可审核、可量化的长期记忆" />
|
|
5
|
-
</p>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/cover-zh.png" width="900" alt="记忆核心 memory-eternal:给 AI 一个不会忘、可审核、可量化的长期记忆" />
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/graph.png" width="31%" alt="知识星图:按类型分簇、标签连边" />
|
|
9
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/audit.png" width="31%" alt="审核中心:类型条、队列与判词栏" />
|
|
10
|
+
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/status.png" width="31%" alt="运行状态:库、备份、自动沉淀与版本" />
|
|
11
|
+
</p>
|
|
12
|
+
<p align="center"><sub>▲ 知识星图 · 审核中心 · 运行状态</sub></p>
|
|
13
|
+
|
|
14
|
+
**记忆核心(memory-eternal)** —— 给 AI 一个不会忘、可审核、可量化的长期记忆。本地优先 · 审核闸门 · 记忆账本 · 一个库接入 DSH / Claude Code / Codex / Cursor 与任意 MCP 客户端。
|
|
15
|
+
|
|
9
16
|
[](https://github.com/EternalNight996/memory-eternal/stargazers)
|
|
10
17
|
[](https://www.npmjs.com/package/memory-eternal)
|
|
11
|
-
[](https://github.com/EternalNight996/memory-eternal/releases)
|
|
12
19
|
[](LICENSE)
|
|
13
20
|
|
|
14
21
|
[English (default)](README.md) · **简体中文(镜像)**
|
|
15
22
|
|
|
16
|
-
<p align="center">
|
|
17
|
-
<a href="https://eternalnight996.github.io/memory-eternal/demo/"><img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/demo.gif" width="900" alt="memory-eternal 20 秒演示:审核队列、记忆账本、知识星图" /></a>
|
|
18
|
-
</p>
|
|
19
|
-
<p align="center"><sub>▲ 20 秒演示 —— 审核队列 · 记忆账本 · 知识星图 · <a href="https://eternalnight996.github.io/memory-eternal/demo/">在线 Demo</a></sub></p>
|
|
20
|
-
|
|
21
23
|
> **你的 AI 每次会话都失忆。给它一个可信的记忆库。**
|
|
22
24
|
>
|
|
23
25
|
> 本地优先、先审后入、可度量 —— 一个 SQLite 库,DSH / Claude Code / Codex / Cursor 与任意 MCP 客户端共享。
|
|
@@ -154,23 +156,23 @@ flowchart TD
|
|
|
154
156
|
|
|
155
157
|
## 📊 与同类产品的实测对比
|
|
156
158
|
|
|
157
|
-
数据快照:**2026-11-07**。我们的数字在**真实 2,321
|
|
159
|
+
数据快照:**2026-11-07**。我们的数字在**真实 2,321 张卡的库**上实测(不是玩具语料);同类产品信息取自同一日期的官方 README / 文档 / 许可证页。`—` 表示**未公开或无法核实**,不代表"没有"。
|
|
158
160
|
|
|
159
|
-
| 能力 | **memory-eternal v0.
|
|
160
|
-
|
|
161
|
-
|
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
|
|
|
167
|
-
|
|
|
168
|
-
|
|
|
169
|
-
|
|
|
170
|
-
|
|
|
171
|
-
| 许可证 | MIT |
|
|
172
|
-
|
|
173
|
-
**我们不是这里最大的。**
|
|
161
|
+
| 能力 | **memory-eternal v0.25.0** | [agentmemory](https://github.com/rohitg00/agentmemory) v0.9.32 | [mem0](https://github.com/mem0ai/mem0) v3 | [Obsidian](https://obsidian.md) | CLAUDE.md / AGENTS.md |
|
|
162
|
+
|---|---|---|---|---|---|
|
|
163
|
+
| 形态 | DSH 原生插件:采集、审核界面、星图、CLI/TUI/MCP | 给编码 Agent 的本地记忆服务 + MCP | 记忆 SDK / 自托管服务 / 云平台 | 本地 Markdown 笔记应用 | 手写上下文文件 |
|
|
164
|
+
| 存储 | SQLite + Markdown 镜像(可 git) | 本地文件存储(可选 Redis) | 向量库 + 图库(自托管或云) | 磁盘上的 Markdown 文件 | 单个文件 |
|
|
165
|
+
| 写入闸门 | **人工审核队列**(三库分离) | 自动捕获,无人工审核 | LLM 抽取写入,无人工审核 | 只由人写 | 无 |
|
|
166
|
+
| 不可变审计日志 | ✅ SQLite 层强制 | 有操作审计工具,非存储层强制 | — | — | — |
|
|
167
|
+
| 检索 | FTS5 + **BM25/IDF**,多列加权 | BM25 + 向量 + 图谱(可选本地嵌入) | 语义 + BM25 + 实体 + 图谱 | 全文搜索 / 插件 | 整文件注入 |
|
|
168
|
+
| 召回延迟 | **p50 2.33ms · p95 6.18ms** | — | p50 约 0.88–1.09s(官方论文,云平台) | 随库规模增长 | 随文件体积增长 |
|
|
169
|
+
| 召回模型调用 | **0**(纯 BM25) | 关键词 0;开启语义时 1 次嵌入调用 | 1 次嵌入 + 可选 LLM 重排 | 0 | 0 |
|
|
170
|
+
| 写入模型调用 | 触发自动沉淀时 1 次(可关) | 默认 0 次(合成压缩),可选 LLM 压缩 | 每次 add 至少 1 次 LLM | 0 | 0 |
|
|
171
|
+
| 记忆账本(可量化有用率) | ✅ | — | 平台用量面板 | — | — |
|
|
172
|
+
| 多 Agent | DSH + MCP + Hook + TUI + 浏览器 | MCP + REST:Claude Code、Codex、Cursor、DSH 等 | Python/TS SDK、自托管服务、云平台 | 无原生 Agent 接口 | 各工具各自加载 |
|
|
173
|
+
| 许可证 | MIT | Apache-2.0 | Apache-2.0 | 专有(个人/商用免费) | 不适用 |
|
|
174
|
+
|
|
175
|
+
**我们不是这里最大的。** mem0 背后是成熟团队、研究论文与托管服务;agentmemory 自动捕获覆盖的编码 Agent 比我们多;Obsidian 是很多人已经在用的笔记库。我们做的不一样的是**写入路径**:人审闸门、三库分离、可指认的审计链条,外加一本让你能自己核对"召回到底值不值"的记忆账本。
|
|
174
176
|
|
|
175
177
|
在我们的库上自己复跑:
|
|
176
178
|
|
|
@@ -228,11 +230,10 @@ dsh-memory ledger --days 30 --json # 召回注入 / 沉淀调用 / 有用
|
|
|
228
230
|
## 📸 截图
|
|
229
231
|
|
|
230
232
|
<p align="center">
|
|
231
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/
|
|
232
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/audit.png" width="32%" alt="审核中心:类型条、队列与判词栏" />
|
|
233
|
-
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/status.png" width="32%" alt="运行状态:库、备份、自动沉淀与版本" />
|
|
233
|
+
<a href="https://eternalnight996.github.io/memory-eternal/demo/"><img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/demo.gif" width="900" alt="memory-eternal 20 秒演示:审核队列、记忆账本、知识星图" /></a>
|
|
234
234
|
</p>
|
|
235
|
-
<p align="center"><sub>▲
|
|
235
|
+
<p align="center"><sub>▲ 20 秒演示 —— 审核队列 · 记忆账本 · 知识星图 · <a href="https://eternalnight996.github.io/memory-eternal/demo/">在线 Demo</a></sub></p>
|
|
236
|
+
|
|
236
237
|
|
|
237
238
|
<p align="center">
|
|
238
239
|
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/tui-audit.png" width="49%" alt="终端原生审核:全选、批量批准/驳回" />
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "memory-eternal",
|
|
3
3
|
"displayName": "Memory Atlas",
|
|
4
4
|
"description": "Memory-eternal: open the local Memory Atlas library, review queue and knowledge graph in a Cursor/VS Code webview.",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.26.0",
|
|
6
6
|
"publisher": "EternalNight996",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "urn:dsh-std:community-draft:dsh-plugin:0.15",
|
|
3
3
|
"id": "memory-eternal-tui",
|
|
4
4
|
"name": "memory-eternal-tui",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.26.0",
|
|
6
6
|
"manifestVersion": "0.15",
|
|
7
7
|
"description": "Open Memory Atlas from dsh-TUI: native /audit scene plus browser launcher.",
|
|
8
8
|
"facets": {
|
package/index.js
CHANGED
|
@@ -26,6 +26,7 @@ const PACKAGE_ROOT = path.resolve(path.dirname(__filename))
|
|
|
26
26
|
const versionRef = (() => { try { const p = JSON.parse(readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8')); return p.version } catch { return '' } })()
|
|
27
27
|
import z from '@deepseek-ai/schemastery'
|
|
28
28
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
29
|
+
import { workingPrompt, readWorking, writeWorking, clearWorking } from './lib/working-memory.js'
|
|
29
30
|
import { ensureVault, search } from './lib/vault.js'
|
|
30
31
|
import { migrateFromMarkdown, setAuditConfig, backupDb } from './lib/db.js'
|
|
31
32
|
import { evaluateStall, MAX_STAMPS } from './lib/stall.js'
|
|
@@ -58,6 +59,9 @@ export const Config = z.object({
|
|
|
58
59
|
coreMemoryEnabled: z.boolean().default(true),
|
|
59
60
|
coreMemoryLimit: z.number().min(1).max(20).default(6),
|
|
60
61
|
coreMemoryMaxChars: z.number().min(200).max(10000).default(2000),
|
|
62
|
+
// 会话工作区(草稿本):按会话隔离的 Markdown 文件,不写 SQLite、不进 FTS、不参与召回。
|
|
63
|
+
// 默认开:只有写入过才有内容,空文档不注入、不占位。
|
|
64
|
+
workingMemoryEnabled: z.boolean().default(true),
|
|
61
65
|
// 关注摘要推送:开启后按间隔抓取「新闻关注」,有新内容时注入 systemPrompt,
|
|
62
66
|
// 让 Agent 在下一轮主动告诉用户。**默认关闭** —— 这是会打扰人的功能,必须用户显式开启。
|
|
63
67
|
newsDigestEnabled: z.boolean().default(false),
|
|
@@ -945,6 +949,9 @@ export function apply(ctx, config) {
|
|
|
945
949
|
}
|
|
946
950
|
|
|
947
951
|
const lastSeqs = new Map() // sessionId -> 已处理到的 seq 水位
|
|
952
|
+
// 最近活跃会话:sessionPrompt 分段的 text 是「每次装配时求值」的函数,但装配上下文只给 scope,
|
|
953
|
+
// 不一定带 sessionId。所以这里用事件钩子兜底记录一个「最近会话」,供工作区注入使用。
|
|
954
|
+
let activeSession = ''
|
|
948
955
|
const lastTouched = new Map() // sessionId -> 上次监听时间(用于清理,避免 Map 无界增长)
|
|
949
956
|
// 轮次计数:inbox/claimed 每轮必发(不依赖 turn-stopping),两个计数器对不上
|
|
950
957
|
// 就说明收尾事件没到——这是「监听器整个没被调用」这类静默死亡的兜底探测。
|
|
@@ -959,6 +966,7 @@ export function apply(ctx, config) {
|
|
|
959
966
|
turnsStopped += 1
|
|
960
967
|
stamp(stoppedAt)
|
|
961
968
|
const sessionId = agent?.session?.id ?? agent?.id ?? 'unknown'
|
|
969
|
+
if (sessionId && sessionId !== 'unknown') activeSession = sessionId
|
|
962
970
|
try {
|
|
963
971
|
const api = sessionEventApi(agent?.session)
|
|
964
972
|
const first = !lastTouched.has(sessionId)
|
|
@@ -1087,6 +1095,33 @@ export function apply(ctx, config) {
|
|
|
1087
1095
|
}
|
|
1088
1096
|
}, 'memory-eternal: core memory section')
|
|
1089
1097
|
|
|
1098
|
+
// -- 2b-2. 会话工作区:按会话注入草稿(**不是知识**,见 lib/working-memory.js)----
|
|
1099
|
+
// text 传函数:每次装配时求值,所以同一个进程里多个会话各自拿到自己的那份,
|
|
1100
|
+
// 且内容随时改写立刻生效(不需要 settings.watch 触发重建)。
|
|
1101
|
+
ctx.effect(() => {
|
|
1102
|
+
const sessionOfScope = (context) => {
|
|
1103
|
+
try {
|
|
1104
|
+
const s = context && context.scope
|
|
1105
|
+
if (!s) return ''
|
|
1106
|
+
return String(s.session?.id || s.id || s.sessionId || '')
|
|
1107
|
+
} catch { return '' }
|
|
1108
|
+
}
|
|
1109
|
+
const dispose = ctx.systemPrompt.section({
|
|
1110
|
+
name: 'memory-eternal: working',
|
|
1111
|
+
order: 122,
|
|
1112
|
+
text: (context) => {
|
|
1113
|
+
try {
|
|
1114
|
+
const cfg = settings.get() ?? {}
|
|
1115
|
+
if (cfg.enabled === false || cfg.workingMemoryEnabled === false) return ''
|
|
1116
|
+
const sid = sessionOfScope(context) || activeSession
|
|
1117
|
+
if (!sid) return ''
|
|
1118
|
+
return workingPrompt(sid)
|
|
1119
|
+
} catch { return '' }
|
|
1120
|
+
},
|
|
1121
|
+
})
|
|
1122
|
+
return () => { if (typeof dispose === 'function') dispose() }
|
|
1123
|
+
}, 'memory-eternal: working section')
|
|
1124
|
+
|
|
1090
1125
|
// -- 2c. 关注摘要:默认关闭;开启后按间隔抓取,只把「还没送过」的那一份推一轮 ----
|
|
1091
1126
|
// 送达方式与核心记忆一致:systemPrompt 分段。不新增宿主能力,也不往会话里插消息。
|
|
1092
1127
|
ctx.effect(() => {
|
|
@@ -1238,6 +1273,49 @@ export function apply(ctx, config) {
|
|
|
1238
1273
|
return out
|
|
1239
1274
|
},
|
|
1240
1275
|
}))
|
|
1276
|
+
|
|
1277
|
+
// -- 2b-3. memory_note:会话工作区(草稿本)读写 -------------------------
|
|
1278
|
+
// 与卡片库彻底分开:不写 SQLite、不进 FTS、不参与召回、不需要审核。
|
|
1279
|
+
// 要长期保留的内容必须走 memory_write(进待审队列),这里只是「当前会话的状态」。
|
|
1280
|
+
tools.register(defineTool({
|
|
1281
|
+
name: 'memory_note',
|
|
1282
|
+
description:
|
|
1283
|
+
'读写**当前会话的工作区草稿**(session workspace):适合记录"这个任务现在做到哪一步、下一步做什么、' +
|
|
1284
|
+
'已经确认过的临时决策"。它按会话隔离、整篇覆盖、不进入记忆库、不需要审核、也不会被召回。' +
|
|
1285
|
+
'不要把它当长期记忆:跨会话要保留的内容请用 memory_write 提交待审卡。' +
|
|
1286
|
+
'action=replace 覆盖整篇(主用法,建议每次写完当前全貌);append 追加;clear 清空;read 读回当前内容。',
|
|
1287
|
+
parameters: {
|
|
1288
|
+
action: { type: 'string', required: true, description: 'replace | append | clear | read' },
|
|
1289
|
+
text: { type: 'string', description: '正文;replace / append 时必填(≤200 行 / 24KB,超出截断)' },
|
|
1290
|
+
},
|
|
1291
|
+
output: { schema: { type: 'string' }, render(_a, v) { return [{ type: 'text', text: v }] } },
|
|
1292
|
+
timeoutMs: 10000,
|
|
1293
|
+
async execute(args, exec) {
|
|
1294
|
+
const cfg = settings.get() ?? {}
|
|
1295
|
+
if (cfg.enabled === false) return '(记忆核心已禁用)'
|
|
1296
|
+
if (cfg.workingMemoryEnabled === false) return '(会话工作区已在设置里关闭)'
|
|
1297
|
+
const sid = String(exec?.agent?.session?.id || exec?.agent?.id || activeSession || '')
|
|
1298
|
+
if (!sid) return '(取不到当前会话 id,工作区未写入)'
|
|
1299
|
+
const action = String(args.action || '').trim().toLowerCase()
|
|
1300
|
+
if (action === 'read') {
|
|
1301
|
+
const w = readWorking(process.env, sid)
|
|
1302
|
+
if (!w.exists || !w.text.trim()) return '(本会话工作区为空)'
|
|
1303
|
+
return '本会话工作区(' + w.lines + ' 行 / ' + w.bytes + ' 字节):\n\n' + w.text
|
|
1304
|
+
}
|
|
1305
|
+
if (action === 'clear') {
|
|
1306
|
+
const r = clearWorking(process.env, sid)
|
|
1307
|
+
return r.ok ? '已清空本会话工作区。' : '(清空失败:' + r.error + ')'
|
|
1308
|
+
}
|
|
1309
|
+
if (action !== 'replace' && action !== 'append') return '(action 必须是 replace / append / clear / read)'
|
|
1310
|
+
const text = String(args.text == null ? '' : args.text)
|
|
1311
|
+
if (!text.trim()) return '(text 为空,未写入)'
|
|
1312
|
+
const r = writeWorking(process.env, sid, text, { mode: action })
|
|
1313
|
+
if (!r.ok) return '(写入失败:' + r.error + ')'
|
|
1314
|
+
return '已' + (action === 'replace' ? '覆盖' : '追加') + '本会话工作区:' + r.lines + ' 行 / ' + r.bytes + ' 字节'
|
|
1315
|
+
+ (r.truncated ? '(超出上限,已截断到 ' + r.maxLines + ' 行 / ' + r.maxBytes + ' 字节)' : '')
|
|
1316
|
+
+ '。注意:这是草稿,不会进入记忆库。'
|
|
1317
|
+
},
|
|
1318
|
+
}))
|
|
1241
1319
|
}
|
|
1242
1320
|
|
|
1243
1321
|
// 沉淀停滞探测:只有「有轮次在跑」且「管线 15 分钟一个活口都没有」才报(真死才报)。
|
|
@@ -1323,6 +1401,8 @@ export function apply(ctx, config) {
|
|
|
1323
1401
|
beatHostMarker()
|
|
1324
1402
|
const handleApi = createApi({
|
|
1325
1403
|
vaultDir, vaultRoots, getSettings: settings.get,
|
|
1404
|
+
// 会话工作区:面板没传 session 时按「最近活跃会话」兜底
|
|
1405
|
+
getActiveSession: () => activeSession,
|
|
1326
1406
|
// 矛盾扫描(#36)在宿主侧跑模型:这里给它 llm 句柄,独立 web 进程拿不到就会如实报错
|
|
1327
1407
|
getLlm: () => ctx.get('llm'),
|
|
1328
1408
|
// 技能卡同步:宿主进程能直接访问 ctx.skills(真正安装/可用的技能),独立 web 会走文件兜底
|
|
@@ -1415,7 +1495,7 @@ export function apply(ctx, config) {
|
|
|
1415
1495
|
const cfg = settings.get() ?? {}
|
|
1416
1496
|
// 只暴露可安全展示/回填的字段
|
|
1417
1497
|
const safe = {
|
|
1418
|
-
autoCapture: cfg.autoCapture, autoRecall: cfg.autoRecall, coreMemoryEnabled: cfg.coreMemoryEnabled, coreMemoryLimit: cfg.coreMemoryLimit, coreMemoryMaxChars: cfg.coreMemoryMaxChars, uiScale: cfg.uiScale, recallLimit: cfg.recallLimit, recallSummaryLen: cfg.recallSummaryLen, recallIncludeBody: cfg.recallIncludeBody, secretHint: cfg.secretHint || '',
|
|
1498
|
+
autoCapture: cfg.autoCapture, autoRecall: cfg.autoRecall, coreMemoryEnabled: cfg.coreMemoryEnabled, workingMemoryEnabled: cfg.workingMemoryEnabled, coreMemoryLimit: cfg.coreMemoryLimit, coreMemoryMaxChars: cfg.coreMemoryMaxChars, uiScale: cfg.uiScale, recallLimit: cfg.recallLimit, recallSummaryLen: cfg.recallSummaryLen, recallIncludeBody: cfg.recallIncludeBody, secretHint: cfg.secretHint || '',
|
|
1419
1499
|
captureMinChars: cfg.captureMinChars, captureCooldownMs: cfg.captureCooldownMs, dedupThreshold: cfg.dedupThreshold, maxCardsPerDay: cfg.maxCardsPerDay,
|
|
1420
1500
|
distillEnabled: cfg.distillEnabled, dedupByLLM: cfg.dedupByLLM, captureMaxTokens: cfg.captureMaxTokens, recallMinScore: cfg.recallMinScore,
|
|
1421
1501
|
autoWeb: cfg.autoWeb, autoWebMode: cfg.autoWebMode, webPort: cfg.webPort, webCheckIntervalMs: cfg.webCheckIntervalMs, webMaxRestart: cfg.webMaxRestart, watchdogAutoSpawn: cfg.watchdogAutoSpawn, autoRestartOnDrift: cfg.autoRestartOnDrift, autoMcpSetup: cfg.autoMcpSetup,
|
package/lib/api.js
CHANGED
|
@@ -25,6 +25,7 @@ import { buildDigest, readDigest, digestPrompt } from './news-digest.js'
|
|
|
25
25
|
import { getSetupStatus, runSetup, mcpAgentAction } from './setup.js'
|
|
26
26
|
import { ledgerSummary } from './ledger.js'
|
|
27
27
|
import { starPromptStatus, markStarAction } from './star-prompt.js'
|
|
28
|
+
import { readWorking, writeWorking, clearWorking, listWorking, archiveWorking } from './working-memory.js'
|
|
28
29
|
import { backupDb, dbStatus, rebuildGuidance, withDbRetry } from './db.js'
|
|
29
30
|
import { listNews, getNews, saveNews, deleteNews, setNewsRead } from './news.js'
|
|
30
31
|
import { getNewsFeed, refreshNewsFeed } from './news-feed.js'
|
|
@@ -236,6 +237,8 @@ export function createApi(deps = {}) {
|
|
|
236
237
|
// 自动沉淀运行轨迹(最新在前)+ 健康状态(异常时 UI 顶部亮红)。
|
|
237
238
|
const getCaptureLog = deps.getCaptureLog ?? (() => null)
|
|
238
239
|
const getCaptureHealth = deps.getCaptureHealth ?? (() => null)
|
|
240
|
+
// 最近活跃会话(宿主注入):会话工作区按会话隔离,独立 web 没有会话概念时退化为 anonymous
|
|
241
|
+
const getActiveSession = deps.getActiveSession ?? (() => '')
|
|
239
242
|
// 矛盾扫描(issue #36)要用模型:宿主把 ctx.get('llm') 传进来;独立 web 进程拿不到就如实报错
|
|
240
243
|
const getLlm = deps.getLlm ?? (() => null)
|
|
241
244
|
// 技能卡同步:宿主传入能访问 ctx.skills 的实现;独立 web 没有技能注册表,
|
|
@@ -1325,6 +1328,68 @@ export function createApi(deps = {}) {
|
|
|
1325
1328
|
json(res, 200, { ok: markStarAction(env, action), action })
|
|
1326
1329
|
return
|
|
1327
1330
|
}
|
|
1331
|
+
case '/working': {
|
|
1332
|
+
// 会话工作区:按 sessionId 隔离的草稿文件,不写 DB、不进 FTS、不参与召回。
|
|
1333
|
+
const sidOf = (raw) => String(raw || '').trim() || String(getActiveSession() || '').trim() || 'anonymous'
|
|
1334
|
+
if (req.method === 'POST') {
|
|
1335
|
+
const wChunks = []
|
|
1336
|
+
for await (const c of req) wChunks.push(c)
|
|
1337
|
+
let body = {}
|
|
1338
|
+
try { body = JSON.parse(Buffer.concat(wChunks).toString('utf8') || '{}') } catch { return json(res, 400, { ok: false, error: 'JSON 解析失败' }) }
|
|
1339
|
+
const sid = sidOf(body.session)
|
|
1340
|
+
const mode = String(body.mode || 'replace') === 'append' ? 'append' : 'replace'
|
|
1341
|
+
const out = writeWorking(process.env, sid, String(body.text == null ? '' : body.text), { mode })
|
|
1342
|
+
json(res, 200, { ...out, session: sid })
|
|
1343
|
+
return
|
|
1344
|
+
}
|
|
1345
|
+
const sid = sidOf(query.get('session'))
|
|
1346
|
+
json(res, 200, { ok: true, activeSession: String(getActiveSession() || ''), ...readWorking(process.env, sid) })
|
|
1347
|
+
return
|
|
1348
|
+
}
|
|
1349
|
+
case '/working/list': {
|
|
1350
|
+
json(res, 200, { ok: true, items: listWorking(process.env) })
|
|
1351
|
+
return
|
|
1352
|
+
}
|
|
1353
|
+
case '/working/clear': {
|
|
1354
|
+
const cChunks = []
|
|
1355
|
+
for await (const c of req) cChunks.push(c)
|
|
1356
|
+
let body = {}
|
|
1357
|
+
try { body = JSON.parse(Buffer.concat(cChunks).toString('utf8') || '{}') } catch { /* 允许空体 */ }
|
|
1358
|
+
const sid = String(body.session || '').trim() || String(getActiveSession() || '').trim() || 'anonymous'
|
|
1359
|
+
json(res, 200, { ...clearWorking(process.env, sid), session: sid })
|
|
1360
|
+
return
|
|
1361
|
+
}
|
|
1362
|
+
case '/working/archive': {
|
|
1363
|
+
const aChunks = []
|
|
1364
|
+
for await (const c of req) aChunks.push(c)
|
|
1365
|
+
let body = {}
|
|
1366
|
+
try { body = JSON.parse(Buffer.concat(aChunks).toString('utf8') || '{}') } catch { /* 允许空体 */ }
|
|
1367
|
+
const sid = String(body.session || '').trim() || String(getActiveSession() || '').trim() || 'anonymous'
|
|
1368
|
+
json(res, 200, { ...archiveWorking(process.env, sid), session: sid })
|
|
1369
|
+
return
|
|
1370
|
+
}
|
|
1371
|
+
case '/working/promote': {
|
|
1372
|
+
// 「存为待审卡」:工作区 → 审核队列。这是草稿进入长期记忆的唯一正门。
|
|
1373
|
+
const pChunks = []
|
|
1374
|
+
for await (const c of req) pChunks.push(c)
|
|
1375
|
+
let body = {}
|
|
1376
|
+
try { body = JSON.parse(Buffer.concat(pChunks).toString('utf8') || '{}') } catch { /* 允许空体 */ }
|
|
1377
|
+
const sid = String(body.session || '').trim() || String(getActiveSession() || '').trim() || 'anonymous'
|
|
1378
|
+
const w = readWorking(process.env, sid)
|
|
1379
|
+
if (!w.exists || !w.text.trim()) return json(res, 400, { ok: false, error: '工作区为空,没有可提升的内容' })
|
|
1380
|
+
const firstLine = String(w.text.split('\n').find((l) => String(l).trim()) || '会话工作区笔记')
|
|
1381
|
+
const title = firstLine.replace(/^#{1,6}\s*/, '').trim().slice(0, 60) || '会话工作区笔记'
|
|
1382
|
+
const out = await writeCard(vaultRoot, {
|
|
1383
|
+
kind: 'knowledge',
|
|
1384
|
+
title,
|
|
1385
|
+
body: String(body.text || '').trim() || w.text,
|
|
1386
|
+
tags: ['会话工作区'],
|
|
1387
|
+
status: 'pending',
|
|
1388
|
+
source: 'working:' + sid,
|
|
1389
|
+
})
|
|
1390
|
+
json(res, 200, { ok: true, ...out, session: sid })
|
|
1391
|
+
return
|
|
1392
|
+
}
|
|
1328
1393
|
case '/budget': {
|
|
1329
1394
|
try {
|
|
1330
1395
|
const cfg = getSettings() ?? {}
|
|
@@ -1360,7 +1425,7 @@ export function createApi(deps = {}) {
|
|
|
1360
1425
|
const cfg = getSettings() ?? {}
|
|
1361
1426
|
const env = typeof process !== 'undefined' ? process.env : {}
|
|
1362
1427
|
const safe = {
|
|
1363
|
-
autoCapture: cfg.autoCapture ?? true, autoRecall: cfg.autoRecall ?? true, coreMemoryEnabled: cfg.coreMemoryEnabled ?? true, coreMemoryLimit: cfg.coreMemoryLimit ?? 6, coreMemoryMaxChars: cfg.coreMemoryMaxChars ?? 2000, uiScale: cfg.uiScale || 'normal', recallLimit: cfg.recallLimit ?? 5, recallSummaryLen: cfg.recallSummaryLen ?? 130, recallIncludeBody: cfg.recallIncludeBody ?? false, secretHint: cfg.secretHint || '',
|
|
1428
|
+
autoCapture: cfg.autoCapture ?? true, autoRecall: cfg.autoRecall ?? true, coreMemoryEnabled: cfg.coreMemoryEnabled ?? true, workingMemoryEnabled: cfg.workingMemoryEnabled ?? true, coreMemoryLimit: cfg.coreMemoryLimit ?? 6, coreMemoryMaxChars: cfg.coreMemoryMaxChars ?? 2000, uiScale: cfg.uiScale || 'normal', recallLimit: cfg.recallLimit ?? 5, recallSummaryLen: cfg.recallSummaryLen ?? 130, recallIncludeBody: cfg.recallIncludeBody ?? false, secretHint: cfg.secretHint || '',
|
|
1364
1429
|
captureMinChars: cfg.captureMinChars ?? 200, captureCooldownMs: cfg.captureCooldownMs ?? 300000, dedupThreshold: cfg.dedupThreshold ?? 0.62, maxCardsPerDay: cfg.maxCardsPerDay ?? 60,
|
|
1365
1430
|
distillEnabled: cfg.distillEnabled ?? true, dedupByLLM: cfg.dedupByLLM ?? true, captureMaxTokens: cfg.captureMaxTokens ?? DEFAULT_CAPTURE_MAX_TOKENS, recallMinScore: cfg.recallMinScore ?? 2,
|
|
1366
1431
|
autoWeb: cfg.autoWeb ?? env.MEMORY_AUTO_WEB !== '0', autoWebMode: cfg.autoWebMode || 'init', webPort: Number(cfg.webPort) || Number(env.MEMORY_WEB_PORT) || 7999, webCheckIntervalMs: Number(cfg.webCheckIntervalMs) || 5000, webMaxRestart: Number(cfg.webMaxRestart) || 10, watchdogAutoSpawn: cfg.watchdogAutoSpawn ?? env.MEMORY_WATCHDOG === '1', autoRestartOnDrift: cfg.autoRestartOnDrift ?? true, autoMcpSetup: cfg.autoMcpSetup ?? false,
|