@cup319/mmpl 2.7.0 → 2.8.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 CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  ## 核心特性
9
9
 
10
- - **8 个精简工具**:write、search、manage、evolve、compress、palace、symbol、stats
10
+ - **9 个精简工具**:write、search、list、manage、evolve、compress、palace、symbol、stats
11
11
  - **模型自动下载**:首次启动自动从 HuggingFace 下载 ONNX 模型(默认 all-MiniLM-L6-v2,90MB,384维)
12
12
  - **跨平台支持**:Windows + Linux,自动适配路径和信号处理
13
13
  - **记忆生命周期**:Active → Infrequent → Archived → Permanent(含衰减、强化、SM-2 间隔复习)
@@ -140,7 +140,7 @@ curl http://127.0.0.1:7680/
140
140
  { "type": "summary" }
141
141
  ```
142
142
 
143
- 到这里你已经跑通了完整闭环。8 个工具的完整说明见下文「工具一览」。
143
+ 到这里你已经跑通了完整闭环。9 个工具的完整说明见下文「工具一览」。
144
144
 
145
145
  > **常驻保活**:HTTP server 自身不会自我守护。Windows 推荐 `nssm install MMPL node D:\path\to\dist\index.js`,Linux/macOS 推荐 `systemd` 或 `pm2 start dist/index.js --name mmpl`。
146
146
 
@@ -181,6 +181,29 @@ HTTP server 启动后不自我守护,靠外部保活:
181
181
 
182
182
  > server 默认只绑 `127.0.0.1`(本机访问)。多 client 共享时**不要**起第二个 server 进程——DuckDB 文件是单进程独占的,第二个进程会启动失败;多个 client 都连同一个 HTTP URL 即可。
183
183
 
184
+ ### ZCode 集成:常驻 + 无弹窗(开箱即用)
185
+
186
+ 如果你在 ZCode 里用 mmpl,推荐用仓库自带的部署示例 [`examples/zcode-deploy/`](./examples/zcode-deploy/)。它通过 SessionStart/Stop hook 实现:
187
+
188
+ - **常驻运行**:server 启动一次后一直留在后台,切换 ZCode session 不重启(避免每次等 5–10s ONNX 加载)
189
+ - **无 node.exe 黑窗**:所有 `spawn` 配 `windowsHide: true`(修复了 Windows 上启动时弹 `C:\...\node.exe` 窗口的问题)
190
+ - **日志落盘**:server 的 stdout/stderr 重定向到 `.mmpl-server.log`,排查启动失败有据可查
191
+
192
+ 一键安装:
193
+
194
+ ```bash
195
+ # 1. 准备部署目录
196
+ mkdir D:\mmpl-deploy && cd D:\mmpl-deploy
197
+ npm init -y && npm install @cup319/mmpl
198
+ # 2. 复制示例脚本
199
+ cp /path/to/examples/zcode-deploy/*.mjs .
200
+ # 3. 按示例 README 配置 ~/.zcode/cli/config.json 的 hook
201
+ ```
202
+
203
+ 完整步骤、配置示例、运行机制、弹窗 bug 修复历史详见 [`examples/zcode-deploy/README.md`](./examples/zcode-deploy/README.md)。
204
+
205
+ > **澄清**:`@cup319/mmpl` npm 包本身不 spawn 子进程,**不存在弹窗问题**。弹窗只发生在用 `start-mmpl.mjs` 包装启动时(该脚本 spawn 子进程的方式在 Windows 上需要 `windowsHide`),示例脚本已修复。
206
+
184
207
  ### 遗留:stdio 模式
185
208
 
186
209
  v2.5.1 之前默认走 stdio,每个 client 各 spawn 一个进程。该模式已在 v2.5.1 移除(多 client 场景下 DuckDB 文件锁无法解决)。如确需单 client stdio,可 git checkout v2.4.4 标签。
@@ -208,6 +231,7 @@ v2.5.1 之前默认走 stdio,每个 client 各 spawn 一个进程。该模式
208
231
  |------|------|
209
232
  | `memory_write` | 写入记忆(encode 编码 + capture 捕获) |
210
233
  | `memory_search` | 检索记忆(quick/deep/hybrid/keyword + drill 钻取) |
234
+ | `memory_list` | 列出记忆(分页 + 状态/类型/强度/关键词过滤 + 排序) |
211
235
  | `memory_manage` | 管理生命周期(forget/reinforce/consolidate/reclaim/reembed) |
212
236
  | `memory_evolve` | 自动进化(extract 提取 + persona 画像更新) |
213
237
  | `memory_compress` | 压缩记忆(compress/decompress/offload) |
package/dist/index.js CHANGED
@@ -943,6 +943,40 @@ var Storage = class _Storage {
943
943
  `);
944
944
  return rows.map((r) => this.rowToMemory(r));
945
945
  }
946
+ /**
947
+ * 分页列出记忆,支持按 status / type / permanent / 强度区间 / 关键词过滤,
948
+ * 支持白名单字段排序。返回 { items, total } —— total 是过滤后总条数
949
+ * (不受分页影响),用于客户端计算 has_more / 总页数。
950
+ *
951
+ * 与 getActiveMemories/getMemoriesByStatus 的区别:本方法做确定性分页浏览,
952
+ * 不依赖语义检索,专供 memory_list 工具浏览全库使用。
953
+ */
954
+ async listMemories(opts) {
955
+ const conditions = [];
956
+ if (opts.status) conditions.push(`status = '${this.escape(opts.status)}'`);
957
+ if (opts.type) conditions.push(`type = '${this.escape(opts.type)}'`);
958
+ if (opts.permanent !== void 0) conditions.push(`permanent = ${opts.permanent ? "true" : "false"}`);
959
+ if (opts.minStrength !== void 0) conditions.push(`strength >= ${Number(opts.minStrength)}`);
960
+ if (opts.maxStrength !== void 0) conditions.push(`strength <= ${Number(opts.maxStrength)}`);
961
+ if (opts.q) conditions.push(`content LIKE '%${this.escape(opts.q)}%'`);
962
+ const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
963
+ const validOrderBy = /* @__PURE__ */ new Set([
964
+ "created_at",
965
+ "strength",
966
+ "importance",
967
+ "last_accessed",
968
+ "access_count"
969
+ ]);
970
+ const orderBy = validOrderBy.has(opts.orderBy ?? "") ? opts.orderBy : "created_at";
971
+ const order = opts.order === "asc" ? "ASC" : "DESC";
972
+ const limit = Math.min(Math.max(opts.limit ?? 20, 1), 500);
973
+ const offset = Math.max(opts.offset ?? 0, 0);
974
+ const total = this.N((await this.get(`SELECT COUNT(*) as c FROM memories ${where}`))?.c) ?? 0;
975
+ const rows = await this.all(
976
+ `SELECT * FROM memories ${where} ORDER BY ${orderBy} ${order} LIMIT ${limit} OFFSET ${offset}`
977
+ );
978
+ return { items: rows.map((r) => this.rowToMemory(r)), total };
979
+ }
946
980
  async updateMemoryBatch(ids, updates) {
947
981
  for (const id of ids) {
948
982
  await this.updateMemory(id, updates);
@@ -21662,6 +21696,111 @@ async function handleMetrics(deps) {
21662
21696
  return { content: [{ type: "text", text: JSON.stringify({ metrics, report }, null, 2) }] };
21663
21697
  }
21664
21698
 
21699
+ // src/tools/memory-list.ts
21700
+ var TRUNC_LIGHT = 100;
21701
+ function serializeLight(m, fullContent) {
21702
+ const item = {
21703
+ memory_id: m.id,
21704
+ type: m.type,
21705
+ status: m.status,
21706
+ strength: Number(m.strength?.toFixed(3)),
21707
+ permanent: m.permanent
21708
+ };
21709
+ if (fullContent || m.content.length <= TRUNC_LIGHT) {
21710
+ item.content = m.content;
21711
+ item.content_length = m.content.length;
21712
+ } else {
21713
+ item.content = m.content.slice(0, TRUNC_LIGHT) + "...";
21714
+ item.content_length = m.content.length;
21715
+ item.content_truncated = true;
21716
+ }
21717
+ item.created_at_iso = m.created_at ? new Date(m.created_at).toISOString() : null;
21718
+ return item;
21719
+ }
21720
+ function serializeDetail(m, fullContent) {
21721
+ const TRUNC_DETAIL = 300;
21722
+ const item = {
21723
+ memory_id: m.id,
21724
+ type: m.type,
21725
+ status: m.status,
21726
+ strength: Number(m.strength?.toFixed(3)),
21727
+ importance: Number(m.importance?.toFixed(3)),
21728
+ permanent: m.permanent,
21729
+ tags: m.tags,
21730
+ access_count: m.access_count,
21731
+ created_at: m.created_at,
21732
+ created_at_iso: m.created_at ? new Date(m.created_at).toISOString() : null,
21733
+ last_accessed: m.last_accessed,
21734
+ last_accessed_iso: m.last_accessed ? new Date(m.last_accessed).toISOString() : null,
21735
+ anchor_id: m.anchor_id,
21736
+ compressed: m.compressed
21737
+ };
21738
+ if (fullContent || m.content.length <= TRUNC_DETAIL) {
21739
+ item.content = m.content;
21740
+ item.content_length = m.content.length;
21741
+ } else {
21742
+ item.content = m.content.slice(0, TRUNC_DETAIL) + "...";
21743
+ item.content_length = m.content.length;
21744
+ item.content_truncated = true;
21745
+ }
21746
+ return item;
21747
+ }
21748
+ function registerMemoryList(server, deps) {
21749
+ server.tool(
21750
+ "memory_list",
21751
+ "\u5217\u51FA\u8BB0\u5FC6\uFF1A\u5206\u9875\u67E5\u8BE2\u3001\u6309\u7C7B\u578B/\u72B6\u6001/\u5F3A\u5EA6/\u5173\u952E\u8BCD\u8FC7\u6EE4\u3001\u652F\u6301\u6392\u5E8F",
21752
+ {
21753
+ status: external_exports.enum(["active", "infrequent", "archived"]).optional().describe("\u72B6\u6001\u8FC7\u6EE4\uFF1B\u4E0D\u4F20=\u5168\u90E8"),
21754
+ type: external_exports.enum(["fact", "preference", "instruction", "event", "skill"]).optional().describe("\u7C7B\u578B\u8FC7\u6EE4"),
21755
+ permanent: external_exports.boolean().optional().describe("\u53EA\u770B\u6C38\u4E45\u8BB0\u5FC6\uFF08true\uFF09/\u53EA\u770B\u975E\u6C38\u4E45\uFF08false\uFF09\uFF1B\u4E0D\u4F20=\u5168\u90E8"),
21756
+ min_strength: external_exports.number().optional().describe("\u6700\u4F4E\u5F3A\u5EA6\u8FC7\u6EE4\uFF080-1\uFF09"),
21757
+ max_strength: external_exports.number().optional().describe("\u6700\u9AD8\u5F3A\u5EA6\u8FC7\u6EE4\uFF080-1\uFF09"),
21758
+ q: external_exports.string().optional().describe("\u5173\u952E\u8BCD\u6A21\u7CCA\u5339\u914D\uFF08LIKE\uFF09\uFF0C\u53EF\u9009"),
21759
+ order_by: external_exports.enum(["created_at", "strength", "importance", "last_accessed", "access_count"]).optional().describe("\u6392\u5E8F\u5B57\u6BB5\uFF0C\u9ED8\u8BA4 created_at"),
21760
+ order: external_exports.enum(["asc", "desc"]).optional().describe("\u6392\u5E8F\u65B9\u5411\uFF0C\u9ED8\u8BA4 desc"),
21761
+ limit: external_exports.number().optional().describe("\u6BCF\u9875\u6570\u91CF\uFF0C\u9ED8\u8BA4 20\uFF0C\u786C\u4E0A\u9650 500"),
21762
+ offset: external_exports.number().optional().describe("\u504F\u79FB\u91CF\uFF0C\u9ED8\u8BA4 0"),
21763
+ detail: external_exports.boolean().optional().describe("\u8FD4\u56DE\u5168\u5B57\u6BB5\uFF08importance/tags/access_count \u7B49\uFF09\uFF1B\u9ED8\u8BA4 false \u53EA\u8FD4\u56DE\u6838\u5FC3\u5B57\u6BB5"),
21764
+ full_content: external_exports.boolean().optional().describe("\u8FD4\u56DE\u5B8C\u6574 content\uFF08\u9ED8\u8BA4 light \u622A\u65AD\u5230 100 \u5B57\u7B26\uFF0Cdetail \u622A\u65AD\u5230 300\uFF09")
21765
+ },
21766
+ async (params) => {
21767
+ const { storage } = deps;
21768
+ const limit = Math.min(Math.max(params.limit ?? 20, 1), 500);
21769
+ const offset = Math.max(params.offset ?? 0, 0);
21770
+ const fullContent = params.full_content ?? false;
21771
+ const detail = params.detail ?? false;
21772
+ const { items, total } = await storage.listMemories({
21773
+ status: params.status,
21774
+ type: params.type,
21775
+ permanent: params.permanent,
21776
+ minStrength: params.min_strength,
21777
+ maxStrength: params.max_strength,
21778
+ q: params.q,
21779
+ orderBy: params.order_by,
21780
+ order: params.order,
21781
+ limit,
21782
+ offset
21783
+ });
21784
+ const serialized = items.map(
21785
+ (m) => detail ? serializeDetail(m, fullContent) : serializeLight(m, fullContent)
21786
+ );
21787
+ return {
21788
+ content: [{
21789
+ type: "text",
21790
+ text: JSON.stringify({
21791
+ items: serialized,
21792
+ total,
21793
+ limit,
21794
+ offset,
21795
+ mode: detail ? "detail" : "light",
21796
+ has_more: offset + items.length < total
21797
+ }, null, 2)
21798
+ }]
21799
+ };
21800
+ }
21801
+ );
21802
+ }
21803
+
21665
21804
  // src/index.ts
21666
21805
  BigInt.prototype.toJSON = function() {
21667
21806
  return Number(this);
@@ -21748,7 +21887,7 @@ async function main() {
21748
21887
  const createMcpServer = () => {
21749
21888
  const server = new McpServer({
21750
21889
  name: "MMPL",
21751
- version: "2.7.0"
21890
+ version: "2.8.0"
21752
21891
  });
21753
21892
  registerMemoryWrite(server, deps);
21754
21893
  registerMemorySearch(server, deps);
@@ -21758,6 +21897,7 @@ async function main() {
21758
21897
  registerMemoryPalace(server, deps);
21759
21898
  registerMemorySymbol(server, deps);
21760
21899
  registerMemoryStats(server, deps);
21900
+ registerMemoryList(server, deps);
21761
21901
  return server;
21762
21902
  };
21763
21903
  const httpHost = process.env.MCP_HTTP_HOST ?? "127.0.0.1";
@@ -21771,7 +21911,7 @@ async function main() {
21771
21911
  void embeddingService.ready().catch((err) => {
21772
21912
  console.error("[startup] embedding service init failed:", err);
21773
21913
  });
21774
- console.error(`MMPL (Memory Palace) MCP Server v2.7.0 listening on http://${httpHost}:${httpPort}${httpPath}`);
21914
+ console.error(`MMPL (Memory Palace) MCP Server v2.8.0 listening on http://${httpHost}:${httpPort}${httpPath}`);
21775
21915
  const gracefulShutdown = async (signal) => {
21776
21916
  console.error(`
21777
21917
  [shutdown] Received ${signal}, gracefully shutting down...`);