@yottameta/yotta-memory 0.16.2 → 0.16.3

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/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## v0.16.3 (2026-09-22)
2
+
3
+ **迁移最短路径与授权引导修复**
4
+
5
+ - `migrate` 成功输出不再只提示 `key bind`:现在明确输出“授权二选一(等价)”——推荐 `yotta-memory view` 页面授权,高级 CLI 方式为 `yotta-memory key bind <id>`,随后 AI 统一执行 `key status` → `key claim`。
6
+ - `SKILL.md`、`USER_GUIDE.md`、`references/faq.md`、中英 README 统一加入明文库第一次转加密命令:`echo 主口令 | yotta-memory migrate --password-stdin --recovery-key-out ...`,并补充交互终端 / PowerShell / cmd / 管道等口令传递方式和一个统一报错对照。
7
+ - 修正迁移后的索引顺序:必须 **授权 → `key claim` → 带 `--agent-key-file` 执行 `reindex` → `recall` 验证**;`migrate` 在没有 agent_key 时无法建立每 owner 加密索引。MCP 自检同时要求配置带 `--agent-key-file`。
8
+ - `--help` 的 `migrate` 行同步给出首次迁移命令与两条等价授权路径。
9
+ - 新增 `test/migration-guidance.test.js`,锁定迁移命令、`view` / `key bind` 等价关系和 CLI 帮助口径。
10
+ - 版本对齐:package.json / SKILL.md frontmatter / skill-manifest.json / CHANGELOG / 引擎 VERSION = 0.16.3。
11
+
1
12
  ## v0.16.2 (2026-09-21)
2
13
 
3
14
  **首启修复:空加密库可解锁、非 TTY 可自举、缺 key 降级未授权**
package/README.md CHANGED
@@ -24,6 +24,9 @@
24
24
  > 📖 The user-facing operations manual lives in [USER_GUIDE.md](USER_GUIDE.md).
25
25
 
26
26
  > 🆕 **v0.16.2 (first-boot fixes)**: an empty encrypted store can unlock `view` with the recovery key; non-TTY hosts can use `--password-stdin`; recovery keys can be written with `--recovery-key-out <file>`; a missing `--agent-key-file` degrades to unauthenticated public-only mode; an empty plaintext store can be migrated to encryption; `view` reports port reuse/conflicts clearly.
27
+ > 🆕 **v0.16.3 (migration quick path)**: convert a plaintext store to encryption with
28
+ > `echo <master-password> | yotta-memory migrate --password-stdin --recovery-key-out "%USERPROFILE%\yotta-memory-recovery.key"`.
29
+ > Then authorize agents with `yotta-memory view`; `view` and `yotta-memory key bind <id>` are equivalent, followed by `key status` / `key claim`, then `reindex` with `--agent-key-file` and a `recall` verification.
27
30
  > 🆕 **v0.16.0 (identity model + stable runtime)**: identity is no longer read from environment variables. HTTP / remote MCP uses request headers `Authorization` + `X-Agent-Id` + `X-Agent-Key`; stdio MCP uses explicit `--agent-id` + `--agent-key-file`; CLI uses `--agent` + `--agent-key` / `--agent-key-file`. The new `runtime install --from-current` / `use` / `rollback` / `status` commands create a stable `<runtimeRoot>/current` launcher so managed MCP, autostart and backup tasks do not pin a version directory. `doctor --runtime` checks CLI / current / MCP config / running server / skill-copy drift, and MCP `serverInfo` returns `runtimePath` / `identityMode` / `toolProfile`.
28
31
  > 🆕 **v0.15.0 (MCP tool profiles)**: `serve --tools core|full` controls the exposed MCP surface. `core` keeps `context / recall / search / remember` resident; `full` keeps the existing 16 tools for diagnostics and maintenance. Omitting the flag keeps the previous `full` behavior for compatibility.
29
32
 
package/README.zh-CN.md CHANGED
@@ -24,6 +24,9 @@
24
24
  > 📖 面向用户的操作手册见 [USER_GUIDE.md](USER_GUIDE.md)。
25
25
 
26
26
  > 🆕 **v0.16.2(首启修复)**:空加密库 `view` 可用恢复钥匙解锁;非 TTY 支持 `--password-stdin`;恢复钥匙支持 `--recovery-key-out <文件>`;`--agent-key-file` 不存在时降级未授权(公共 FACT 可读、私密 fail-closed);空明文库可直接 `migrate` 启用加密;`view` 端口占用给明确提示。
27
+ > 🆕 **v0.16.3(迁移最短路径)**:明文库第一次转加密:
28
+ > `echo 主口令 | yotta-memory migrate --password-stdin --recovery-key-out "%USERPROFILE%\yotta-memory-recovery.key"`
29
+ > 迁移后用 `yotta-memory view` 授权 AI;`view` 与 `yotta-memory key bind <id>` 是等价路径,随后执行 `key status` / `key claim`,再带 `--agent-key-file` 执行 `reindex` 重建加密索引并 `recall` 验证。
27
30
  > 🆕 **v0.16.0(身份模型 + 稳定运行时)**:身份不再从环境变量读取。HTTP / 远程 MCP 用请求头 `Authorization` + `X-Agent-Id` + `X-Agent-Key`;stdio MCP 用显式参数 `--agent-id` + `--agent-key-file`;CLI 用 `--agent` + `--agent-key` / `--agent-key-file`。新增 `runtime install --from-current` / `use` / `rollback` / `status`,创建 `<runtimeRoot>/current` 稳定入口,受管 MCP、自启与备份任务不再写死版本目录。`doctor --runtime` 检查 CLI / current / MCP 配置 / 运行中 server / 技能副本漂移;MCP `serverInfo` 返回 `runtimePath` / `identityMode` / `toolProfile`。
28
31
  > 🆕 **v0.15.0(MCP 工具分组)**:`serve --tools core|full` 控制 MCP 工具暴露面。`core` 常驻 `context / recall / search / remember`;`full` 保留现有 16 个诊断与维护工具。未指定参数时默认 `full`,保持现有配置兼容。
29
32
 
package/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: yotta-memory
3
3
  description: 元忆 —— 有权限边界的文件式智能体记忆。文件式、零依赖、可 diff/可回滚:让任何 AI 智能体活过会话,开工 recall 恢复上下文、重要信息 remember 落盘、收工归档。类型体系 FACT(公共共享)/ PREF / BOUND / COMMIT(私密隔离)。触发:记住、别忘了、记一笔、记忆、remember、recall、跨会话、上次说到、续测、交接、归档、记忆盘、共享记忆、局域网记忆、画像、开工上下文、长期理解摘要、近期走廊、会话闭环、记忆守则、profile、context、越用越懂、语义检索、反馈、维护、蒸馏、feedback、maintain、distill、explain、自我学习、自我进化、自我提升、查看平台分页、recall 候选预过滤、任务相关记忆、--focus、--embedding、压缩遗忘、consolidate、周期摘要、自动合并、分类型衰减、回滚、备份、backup、防误删、doctor、事务快照
4
- version: 0.16.2
4
+ version: 0.16.3
5
5
  license: MIT
6
6
  ---
7
7
 
@@ -230,7 +230,8 @@ yotta-memory doctor --json
230
230
  - 输出 `memory_home: <目录>`(已显式设置)→ 直接用该位置。
231
231
  - 输出 `memory_home: (未设置,默认 ~/.yottamemory)` → 🔒 征得同意后引导设置:问用户用默认还是指定目录(项目级 `<repo>/.yottamemory`、记忆盘等),确认后 AI 执行 `yotta-memory config set memory_home <目录>`,回读 `config get` 验证。
232
232
  2. **已有记忆**:目标目录已存在 `facts/` 等子目录或 `index.json` → 直接 recall;全新目录 → 按「便携记忆盘模式 §0.3」初始化。
233
- 3. **私密区已加密(存在 `keys/`)**:先 `yotta-memory key list` 确认本智能体是否有 agent binding;没有 → 告知用户由用户自己执行 `yotta-memory view` → 浏览器打开平台 → 输入主口令 → 点「授权」并保存只展示一次的 `agent_key`。用户授权后服务端会写 `keys/pending/<id>.key`;AI 在新会话执行 `yotta-memory key status <id>`(需要时显式加 `--to <AI_HOME>` 或 `--agent-key-file <文件>`;默认发现规则见下),有 pending 就执行 `yotta-memory key claim <id>`,落到 `<AI_HOME>/.yotta-memory-agent-key` 后再使用 `--agent-key-file`。**升级后首次调用元忆 / 重启会话时**,若输出 `[YTM_MIGRATION_REQUIRED]`,必须主动把 marker、受影响 agent 和处理步骤转达给用户。**AI 不得代替用户执行 `migrate` / `key bind` 迁移**,只负责提醒和讲解(marker 只列仍有私密数据、未绑定的 agent;仅有 legacy cache、无迁移数据的 owner 会单独提示,不进入迁移清单;公共 FACT 不受影响)。
233
+ 3. **私密区为明文(无 `keys/`;`doctor` 显示「加密: 否」)**:首次使用即主动告知风险——明文私密记忆(PREF / BOUND / COMMIT)可被同机任何能读文件的进程或用户直接看到;然后给出《明文库转加密(第一次最短路径)》。口令与迁移必须由用户本人执行,AI 只讲解并负责后续 `key claim`。用户明确拒绝加密时,记录其选择并复述明文风险,不反复打扰。
234
+ 4. **私密区已加密(存在 `keys/`)**:先 `yotta-memory key list` 确认本智能体是否有 agent binding;没有 → 告知用户由用户自己执行 `yotta-memory view` → 浏览器打开平台 → 输入主口令 → 点「授权」并保存只展示一次的 `agent_key`。用户授权后服务端会写 `keys/pending/<id>.key`;AI 在新会话执行 `yotta-memory key status <id>`(需要时显式加 `--to <AI_HOME>` 或 `--agent-key-file <文件>`;默认发现规则见下),有 pending 就执行 `yotta-memory key claim <id>`,落到 `<AI_HOME>/.yotta-memory-agent-key` 后再使用 `--agent-key-file`。**升级后首次调用元忆 / 重启会话时**,若输出 `[YTM_MIGRATION_REQUIRED]`,必须主动把 marker、受影响 agent 和处理步骤转达给用户。**AI 不得代替用户执行 `migrate` / `key bind` 迁移**,只负责提醒和讲解(marker 只列仍有私密数据、未绑定的 agent;仅有 legacy cache、无迁移数据的 owner 会单独提示,不进入迁移清单;公共 FACT 不受影响)。
234
235
 
235
236
  **B. 确认本智能体唯一身份(强制,写私密记忆前必做)**:
236
237
 
@@ -265,7 +266,7 @@ yotta-memory doctor --json
265
266
  | 命令 | 作用 |
266
267
  |---|---|
267
268
  | `yotta-memory init [--project] [--dir <目录>] [--attach] [--encrypt|--no-encrypt] [--password-stdin] [--recovery-key-out <文件>]` | 初始化(**新建默认加密**:设主口令 + 抄下恢复钥匙;已有库必须用 `--attach`,默认拒绝覆盖;`--no-encrypt` 降级明文;老明文库用 `migrate`;非 TTY 用 `--password-stdin`;恢复钥匙可写文件)|
268
- | `yotta-memory migrate [--password-stdin] [--recovery-key-out <文件>]` | 明文库 → 密文迁移(**由用户执行**;需主口令;空明文库同样可启用加密;迁移后打印/写文件恢复钥匙;不写明文授权缓存,授权由用户在 `view` 平台完成)|
269
+ | `yotta-memory migrate [--password-stdin] [--recovery-key-out <文件>]` | 明文库 → 密文迁移(**由用户执行**;首次迁移命令、`view` 授权与 `key bind` 等价关系见《明文库转加密(第一次最短路径)》)|
269
270
  | `yotta-memory view [--port 8788] [--host 127.0.0.1]` | 用户查看平台(本机 Web:口令解锁浏览 / 搜索 / 导出全部 AI 记忆 + 授权 / 吊销 AI + 重设口令 + 显示恢复钥匙;已在运行则复用 URL,端口占用给明确提示)|
270
271
  | `yotta-memory reset-password [--password <当前> | --recovery-key <钥匙>] [--new-password <新>]` | 重设主口令(忘口令用恢复钥匙)|
271
272
  | `yotta-memory key list / bind <id> / rotate <id> / claim <id> [--to <AI_HOME> | --agent-key-file <文件>] / status <id> [--to <AI_HOME> | --agent-key-file <文件>] / revoke <id>` | 管理 agent_key binding(**bind/rotate 由用户执行**,需主口令或恢复钥匙;claim/status 由 AI 读取 pending 并落到宿主目录,按同一 AI_HOME 发现规则;revoke 立即吊销该 AI 解密能力,旧 key 随即校验失败;`key list` 合并 `keys/*.key.enc`,显示仅有钥、尚未写记忆的 owner;输出 `[YTM_MIGRATION_REQUIRED]` 时提醒用户走 `view` 重新授权)|
@@ -295,13 +296,75 @@ yotta-memory doctor --json
295
296
 
296
297
  ### 首启(非 TTY / GUI 宿主)
297
298
 
298
- - **非 TTY 初始化 / 迁移**:用 `--password-stdin` 从管道读主口令(不进 argv),或设 `YOTTA_MEMORY_PASS`;不要依赖交互提示,非 TTY 下会给出可操作错误而不是静默「已取消」。
299
+ - **非 TTY 初始化 / 迁移**:完整步骤见下一节《明文库转加密(第一次最短路径)》。
299
300
  - **恢复钥匙写文件**:`--recovery-key-out <文件>`(`init` / `migrate`)把恢复钥匙写入文件,适配 Windows GUI 宿主 stdout 不可捕获;不要把钥匙粘贴到对话。
300
301
  - **缺 `--agent-key-file`**:文件不存在时降级为未授权模式(公共 FACT 可读,私密操作 fail-closed 并提示 `key bind <id>`);文件存在但为空 / 不可读仍报错。
301
- - **空明文库启用加密**:`yotta-memory migrate --password-stdin` 可直接为空的明文库创建加密层(不创建 owner key),随后为每个 agent 执行 `yotta-memory key bind <id>`。
302
302
  - **空加密库授权**:`yotta-memory view` 在无 owner key 时可用恢复钥匙校验主口令进入平台;无 owner 时先 `yotta-memory iam <id>`,再回页面授权。
303
303
  - **view 端口**:启动前做健康检查;已在运行则打印 URL 复用,端口被占用给明确提示,不再抛未处理的 `EADDRINUSE`。
304
304
 
305
+ ### 明文库转加密(第一次最短路径)
306
+
307
+ > 适用于 `yotta-memory 0.16.2+`。迁移前先完整备份。
308
+
309
+ 1. **迁移**:
310
+
311
+ ```cmd
312
+ echo 主口令 | yotta-memory migrate --password-stdin --recovery-key-out "%USERPROFILE%\yotta-memory-recovery.key"
313
+ ```
314
+
315
+ 口令传递方式(同一迁移命令,按终端选择一种;不要把口令写进 `--password` 参数):
316
+
317
+ | 场景 | 命令 |
318
+ |---|---|
319
+ | 非 TTY / 管道(推荐) | `echo 主口令 \| yotta-memory migrate --password-stdin --recovery-key-out "<钥匙文件>"` |
320
+ | 交互终端 | `yotta-memory migrate --recovery-key-out "<钥匙文件>"`,按提示输入口令 |
321
+ | PowerShell 环境变量 | `$env:YOTTA_MEMORY_PASS='<主口令>'; yotta-memory migrate --recovery-key-out "<钥匙文件>"; Remove-Item Env:\YOTTA_MEMORY_PASS` |
322
+ | cmd 环境变量 | `set "YOTTA_MEMORY_PASS=<主口令>"`,执行 `yotta-memory migrate --recovery-key-out "<钥匙文件>"`,最后 `set "YOTTA_MEMORY_PASS="` |
323
+
324
+ 常见报错:`当前为非交互环境` = 没有 stdin 也没有 `YOTTA_MEMORY_PASS`;`'"..."' is not recognized` = 在 `cmd.exe` 里用了 PowerShell 的管道写法。口令含非 ASCII 时,PowerShell 可先设置 `$OutputEncoding=[Text.Encoding]::UTF8`。
325
+
326
+ 恢复钥匙文件必须离线保存,不要和记忆库或备份放在一起。
327
+
328
+ 2. **授权 AI(二选一,完全等价)**:
329
+
330
+ 推荐:网页授权
331
+
332
+ ```cmd
333
+ yotta-memory view
334
+ ```
335
+
336
+ 浏览器输入主口令,点击 `授权 <id>`,保存只显示一次的 `agent_key`。
337
+
338
+ 高级:CLI 等价方式
339
+
340
+ ```cmd
341
+ yotta-memory key bind <id>
342
+ ```
343
+
344
+ 两种方式都会写 binding + pending,效果相同。
345
+
346
+ 3. **AI 领取 key**:
347
+
348
+ ```cmd
349
+ yotta-memory key status <id>
350
+ yotta-memory key claim <id> --to "<AI_HOME>"
351
+ ```
352
+
353
+ 默认写入 `<AI_HOME>/.yotta-memory-agent-key`。
354
+
355
+ 4. **重建加密索引并验证**:
356
+
357
+ ```cmd
358
+ yotta-memory reindex --agent <id> --agent-key-file "<AI_HOME>/.yotta-memory-agent-key"
359
+ yotta-memory recall <关键词> --agent <id> --agent-key-file "<AI_HOME>/.yotta-memory-agent-key"
360
+ ```
361
+
362
+ 必须在授权并领取 agent_key 后执行 `reindex`。`migrate` 在没有 agent_key 时无法建立每 owner 加密索引;跳过这步会让私密 `recall` 看起来像“无匹配记忆”。
363
+
364
+ 5. **MCP 自检**:宿主 MCP 配置必须带 `--agent-key-file <AI_HOME>/.yotta-memory-agent-key`;只有 `--agent-id` 时,加密库的私密 MCP 调用会报缺少 agent_key,而 CLI 可能仍正常。补参后重启 MCP / 会话并回读验证。
365
+
366
+ > 加密范围:`private/` 下的 PREF / BOUND / COMMIT 与画像索引;公共 `facts/` 仍为明文。
367
+
305
368
  ## 存储格式(摘要)
306
369
 
307
370
  目录结构(v0.5.0 起,私密记忆按 owner 物理分目录):
@@ -340,8 +403,8 @@ yotta-memory doctor --json
340
403
 
341
404
  ### 流程
342
405
  1. **建加密库**:`yotta-memory init --encrypt`(新建默认加密)→ 设主口令 → 抄下恢复钥匙离线保存。
343
- 2. **老库迁移**:由用户执行 `yotta-memory migrate`(需主口令)→ 明文私密逐文件加密后删除明文 → 打印恢复钥匙。迁移不写明文授权缓存;每个 AI 的重新授权由用户自己在 `yotta-memory view` 平台完成并自行备份弹窗 `agent_key`;AI 随后用 `key status` / `key claim` 领取(AI 只提醒授权,不代执行 `migrate` / `key bind`)。
344
- 3. **AI 读写自己的私密(v0.16.0)**:用户侧完成一次 `yotta-memory view` 授权(或用户自行执行 `yotta-memory key bind <id>`),生成只展示一次的 `agent_key`,并写入 `keys/bindings/<id>.key.agent` 与临时 `keys/pending/<id>.key`。AI 新会话用 `key status` / `key claim` 将 pending 落到 `<AI_HOME>/.yotta-memory-agent-key`,之后 CLI 用 `--agent <id> --agent-key-file <宿主key文件>`;stdio MCP 用 `--agent-id <id> --agent-key-file <宿主key文件>`;HTTP MCP 用 `X-Agent-Id` + `X-Agent-Key` 请求头。owner ID 单独存在时不能解密私密;legacy `keys/cache/<id>.key` 不再加载。
406
+ 2. **老库迁移 / 重新授权**:完整步骤见《明文库转加密(第一次最短路径)》。迁移与授权由用户执行,AI 只提醒,不代执行 `migrate` / `key bind`。
407
+ 3. **AI 读写自己的私密(v0.16.0)**:授权入口为 `yotta-memory view` 或等价的 `yotta-memory key bind <id>`;生成只展示一次的 `agent_key`,并写入 `keys/bindings/<id>.key.agent` 与临时 `keys/pending/<id>.key`。AI 新会话用 `key status` / `key claim` 将 pending 落到 `<AI_HOME>/.yotta-memory-agent-key`,之后 CLI 用 `--agent <id> --agent-key-file <宿主key文件>`;stdio MCP 用 `--agent-id <id> --agent-key-file <宿主key文件>`;HTTP MCP 用 `X-Agent-Id` + `X-Agent-Key` 请求头。owner ID 单独存在时不能解密私密;legacy `keys/cache/<id>.key` 不再加载。
345
408
  4. **用户查看全部 AI 记忆**:`yotta-memory view` → 输口令 → 浏览 / 搜索 / 导出全部(含各 AI 私密明文,仅用户可见)。口令只在本地内存派生,不落盘、不发远端;默认仅 127.0.0.1,远程需 `--host` 显式开启。
346
409
  5. **口令管理**:`yotta-memory reset-password`(当前口令或恢复钥匙);`key revoke <id>` 立即吊销某 AI 的 agent binding(该 AI 随即失去解密能力)。`view` 平台的「授权」只对未绑定 agent 生成新 key;已绑定的 agent 需先「吊销」再授权,避免误打断在用的 agent_key。
347
410
 
package/USER_GUIDE.md CHANGED
@@ -127,10 +127,44 @@ statement: 本周完成发布
127
127
  **启用与迁移**
128
128
 
129
129
  - **新库**:`yotta-memory init`(新建默认加密)→ 输入主口令两次 → **抄下打印的恢复钥匙**(44 位 base64,离线保存;忘口令时用它重设)。不要加密用 `--no-encrypt`。
130
- - **老明文库升级**:`yotta-memory migrate` → 输入主口令 → 私密文件逐个加密、明文删除 → 抄下恢复钥匙。**空明文库也可以直接 migrate**(只创建加密层,不创建 owner key),随后为每个 agent 执行 `key bind <id>`。
131
- - **非 TTY / GUI 宿主**:不要依赖交互口令——用 `--password-stdin` 从管道读主口令(推荐,不进 argv),或设 `YOTTA_MEMORY_PASS`;用 `--recovery-key-out <文件>` 把恢复钥匙写文件,避免 stdout 被 GUI 宿主吞掉。当前为非交互环境且未提供口令时,命令会给出可操作错误,不再静默「已取消」。
130
+ - **老明文库升级 / 非 TTY**:见下方《明文库转加密(第一次最短路径)》,只保留一套步骤。
132
131
  - **缺 `--agent-key-file`**:文件不存在时降级为未授权模式——公共 FACT 可读,私密操作 fail-closed 并提示 `key bind <id>`;文件存在但为空 / 不可读仍报错。
133
132
 
133
+ **明文库转加密(第一次最短路径)**
134
+
135
+ > 适用于 `yotta-memory 0.16.2+`。迁移前先完整备份。
136
+
137
+ 1. 迁移:
138
+
139
+ ```cmd
140
+ echo 主口令 | yotta-memory migrate --password-stdin --recovery-key-out "%USERPROFILE%\yotta-memory-recovery.key"
141
+ ```
142
+
143
+ 非 TTY 也可以使用 `YOTTA_MEMORY_PASS`(PowerShell / cmd 语法不同);交互终端直接运行 `yotta-memory migrate --recovery-key-out "<钥匙文件>"` 并按提示输入口令。不要把口令写进 `--password` 参数。
144
+
145
+ 2. 授权 AI(二选一,等价):
146
+
147
+ - 推荐:`yotta-memory view` → 浏览器输入主口令 → 点「授权 <id>」→ 保存一次性 `agent_key`。
148
+ - 高级:`yotta-memory key bind <id>`。
149
+
150
+ 3. AI 领取 key:
151
+
152
+ ```cmd
153
+ yotta-memory key status <id>
154
+ yotta-memory key claim <id> --to "<AI_HOME>"
155
+ ```
156
+
157
+ 4. 授权并领取 key 后,重建加密索引:
158
+
159
+ ```cmd
160
+ yotta-memory reindex --agent <id> --agent-key-file "<AI_HOME>/.yotta-memory-agent-key"
161
+ yotta-memory recall <关键词> --agent <id> --agent-key-file "<AI_HOME>/.yotta-memory-agent-key"
162
+ ```
163
+
164
+ 顺序必须是:迁移 → `view` / `key bind` → `key claim` → `reindex`。没有 agent_key 时 `reindex` 无法建立每 owner 加密索引。
165
+
166
+ 5. MCP 配置必须包含 `--agent-key-file <AI_HOME>/.yotta-memory-agent-key`;只有 `--agent-id` 时,加密库的私密 MCP 会报缺少 agent_key。
167
+
134
168
  **用户查看平台(看所有 AI 的记忆)**
135
169
 
136
170
  `yotta-memory view` → 浏览器打开 http://127.0.0.1:8788 → 输入主口令解锁 → 浏览 / 搜索 / 导出全部记忆(含各 AI 私密);也可在此**授权 / 吊销**某 AI 读取其私密、**重设口令**、**查看恢复钥匙**。空加密库没有 owner key 时,`view` 会用恢复钥匙校验主口令进入平台;此时先在终端执行 `yotta-memory iam <id>`,页面才会出现可授权的 owner。`view` 启动前会做端口健康检查:已在运行则直接打印 URL 复用;端口被占用会给出明确提示。点击「授权」后会弹窗展示只显示一次的 `agent_key`,请立即单独保存;引擎同时写临时 `keys/pending/<id>.key`,该 AI 新会话用 `key claim` 领取到自己的宿主目录,领取成功后 pending 删除。已绑定的 agent 需先「吊销」再授权,旧 key 随即校验失败。口令只在本地内存派生,不落盘、不发远端;默认仅本机,远程需 `--host` 显式开启。
@@ -25,7 +25,7 @@ const net = require('net');
25
25
  const child_process = require('child_process');
26
26
  const { AsyncLocalStorage } = require('async_hooks');
27
27
 
28
- const VERSION = '0.16.2';
28
+ const VERSION = '0.16.3';
29
29
  // @generated view-html:start
30
30
  const VIEW_HTML = "<!doctype html><html lang=\"zh\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>元忆 · 用户查看平台</title><style>\r\nbody{font-family:system-ui,-apple-system,\"Microsoft YaHei\",sans-serif;max-width:1000px;margin:24px auto;padding:0 16px;color:#1f2328;background:#fafafa}\r\nh1{font-size:22px} .card{background:#fff;border:1px solid #e2e2e2;border-radius:10px;padding:16px 18px;margin:14px 0;box-shadow:0 1px 2px rgba(0,0,0,.04)}\r\nbutton{background:#2563eb;color:#fff;border:0;border-radius:6px;padding:7px 14px;cursor:pointer;margin:2px;font-size:14px}\r\nbutton.danger{background:#dc2626} button.ghost{background:#e5e7eb;color:#1f2328}\r\ninput,select{padding:8px;border:1px solid #c9c9c9;border-radius:6px;margin:2px;font-size:14px;box-sizing:border-box}\r\ntable{border-collapse:collapse;width:100%;font-size:13px} td,th{border:1px solid #ececec;padding:6px 8px;text-align:left;vertical-align:top}\r\n.owner{display:inline-flex;align-items:center;gap:6px;border:1px solid #ddd;border-radius:8px;padding:5px 10px;margin:4px 6px 4px 0;background:#f6f8fa}\r\n.entry{border-bottom:1px solid #eee;padding:8px 0} .meta{color:#8a8a8a;font-size:12px}\r\n.err{color:#dc2626;margin-top:8px} .ok{color:#16a34a;margin-top:8px}\r\n#app{display:none} code{background:#f0f0f0;padding:1px 5px;border-radius:4px;font-size:12px}\r\n</style></head><body>\r\n<h1>元忆 · 用户查看平台 <span id=\"ver\" style=\"font-size:14px;color:#888\"></span></h1>\r\n<div id=\"lock\" class=\"card\">\r\n <p><b>输入主口令解锁</b>(口令只在本地内存派生,不落盘、不发送远端)。忘口令可在 CLI 用恢复钥匙重设:<code>yotta-memory reset-password --recovery-key &lt;钥匙&gt;</code></p>\r\n <input type=\"password\" id=\"pw\" placeholder=\"主口令\" style=\"width:260px\">\r\n <button onclick=\"unlock()\">解锁</button>\r\n <div class=\"err\" id=\"lockerr\"></div>\r\n</div>\r\n<div id=\"app\">\r\n <div class=\"card\">\r\n <b>AI 列表</b>(✅=已授权可读自己私密,🔒=未授权)\r\n <div class=\"meta\" style=\"margin-top:6px\">「授权」由你(用户)操作:确认后生成只显示一次的 agent_key,请立即单独保存;服务端同时写临时待领取文件 <code>keys/pending/&lt;agent_id&gt;.key</code>,供该 AI 新会话领取,领取成功后自动删除。</div>\r\n <div id=\"owners\" style=\"margin-top:8px\"></div>\r\n </div>\r\n <div class=\"card\">\r\n <b>记忆</b>\r\n <input id=\"q\" placeholder=\"搜索关键词\" style=\"width:220px\" onkeydown=\"if(event.key==='Enter'){off=0;load()}\">\r\n <button onclick=\"off=0;load()\">搜索</button>\r\n <button class=\"ghost\" onclick=\"doExport()\">导出 JSON</button>\r\n <button class=\"ghost\" onclick=\"showRk()\">显示恢复钥匙</button>\r\n <span id=\"rkout\" style=\"font-size:12px;color:#888;margin-left:8px\"></span>\r\n <div id=\"meta\" style=\"margin-top:10px;font-size:12px;color:#666\"></div>\r\n <div id=\"entries\" style=\"margin-top:6px\"></div>\r\n <div id=\"pager\" style=\"margin-top:10px\">\r\n <button class=\"ghost\" id=\"prevb\" onclick=\"prevPage()\">上一页</button>\r\n <span id=\"pageinfo\" style=\"font-size:12px;color:#888;margin:0 8px\"></span>\r\n <button class=\"ghost\" id=\"nextb\" onclick=\"nextPage()\">下一页</button>\r\n </div>\r\n </div>\r\n <div class=\"card\">\r\n <b>重设口令</b><br>\r\n <input type=\"password\" id=\"cur\" placeholder=\"当前口令\">\r\n <input type=\"password\" id=\"np1\" placeholder=\"新口令\">\r\n <input type=\"password\" id=\"np2\" placeholder=\"确认新口令\">\r\n <button onclick=\"resetPw()\">重设</button>\r\n <span id=\"pwout\"></span>\r\n </div>\r\n</div>\r\n<script>\r\nfunction esc(s){return String(s==null?'':s).replace(/[&<>\"']/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c];});}\r\nasync function api(p,b){try{const r=await fetch(p,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(b||{})});return await r.json();}catch(e){return{error:String(e)};}}\r\nasync function boot(){const s=await api('/api/status');document.getElementById('ver').textContent='v'+(s.version||'');if(s.unlocked){showApp();}}\r\nfunction showApp(){document.getElementById('lock').style.display='none';document.getElementById('app').style.display='block';loadOwners();load();}\r\nasync function unlock(){const d=await api('/api/unlock',{password:document.getElementById('pw').value});if(d.error){document.getElementById('lockerr').textContent=d.error;return;}showApp();}\r\nasync function loadOwners(){const d=await api('/api/owners');const box=document.getElementById('owners');box.innerHTML='';if(!d.owners||!d.owners.length){box.innerHTML=esc(d.hint||'(无 owner)');return;}\r\n for(const o of d.owners){const c=document.createElement('span');c.className='owner';c.innerHTML=esc(o.owner)+(o.authorized?' ✅':' 🔒')+' <button class=\"ghost\" data-a=\"'+esc(o.owner)+'\">授权</button><button class=\"danger\" data-r=\"'+esc(o.owner)+'\">吊销</button>';box.appendChild(c);}\r\n box.querySelectorAll('[data-a]').forEach(function(b){b.onclick=function(){var owner=b.getAttribute('data-a');if(!confirm('确认由你为用户授权 '+owner+' 读取其私密记忆?授权后将生成只显示一次的 agent_key,请立即保存;同时写入待领取文件供该 AI 新会话领取。AI 不应代为执行该授权操作。'))return;b.disabled=true;api('/api/authorize',{owner:owner}).then(function(d){b.disabled=false;if(!d||d.error){alert((d&&d.error)||'授权失败');loadOwners();return;}if(d.agentKey){showKey(d.agentKey);}loadOwners();});};});\r\n box.querySelectorAll('[data-r]').forEach(function(b){b.onclick=function(){if(!confirm('确认吊销 '+b.getAttribute('data-r')+' 的 agent_key?吊销后该智能体立即失去私密读写能力。'))return;api('/api/revoke',{owner:b.getAttribute('data-r')}).then(function(){loadOwners();});};});\r\nfunction showKey(k){var ov=document.createElement('div');ov.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index:99';var box=document.createElement('div');box.className='card';box.style.cssText='max-width:640px;word-break:break-all';var t=document.createElement('div');t.innerHTML='<b>agent_key(只显示一次)</b>';var hint=document.createElement('div');hint.className='meta';hint.textContent='请用户立即单独保存。AI 新会话先执行 yotta-memory key status <agent_id>,有 pending 再执行 key claim <agent_id>;默认写入 AI_HOME/.yotta-memory-agent-key,需要时用 --to 或 --agent-key-file 指定。若 key 丢失,可吊销后重新授权;旧 key 会立即校验失败。';var ta=document.createElement('textarea');ta.readOnly=true;ta.value=k;ta.style.cssText='width:100%;height:72px;margin-top:8px;font-family:monospace;font-size:12px';var close=document.createElement('button');close.textContent='我已保存,关闭';close.onclick=function(){ov.remove();};box.appendChild(t);box.appendChild(hint);box.appendChild(ta);box.appendChild(close);ov.appendChild(box);document.body.appendChild(ov);ta.focus();ta.select();}\r\n}\r\nlet off=0,PS=50;\r\nasync function load(){const d=await api('/api/entries',{query:document.getElementById('q').value,offset:off,limit:PS});const meta=document.getElementById('meta');const pg=document.getElementById('pageinfo');if(meta)meta.textContent='共 '+d.count+' 条';const lim=d.limit||PS;const totalPg=Math.max(1,Math.ceil(d.count/lim));const curPg=Math.floor((d.offset||0)/lim)+1;if(pg)pg.textContent='第 '+curPg+' / '+totalPg+' 页';const box=document.getElementById('entries');box.innerHTML='';if(d.entries)for(const e of d.entries){const div=document.createElement('div');div.className='entry';div.innerHTML='<b>['+esc(e.type)+'] '+esc(e.subject)+'</b><div>'+esc(e.statement)+'</div><div class=\"meta\">'+esc(e.file)+' · owner='+esc(e.owner||'-')+' · '+esc(e.updated||e.created||'')+'</div>';box.appendChild(div);}const pb=document.getElementById('prevb'),nb=document.getElementById('nextb');if(pb)pb.disabled=(d.offset||0)<=0;if(nb)nb.disabled=!d.hasMore;}\r\nfunction prevPage(){if(off>=PS){off-=PS;load();}}\r\nfunction nextPage(){off+=PS;load();}\r\nasync function doExport(){const d=await api('/api/export');if(d.error){alert(d.error);return;}const blob=new Blob([JSON.stringify(d,null,2)],{type:'application/json'});const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='yottamemory-view-export.json';a.click();}\r\nasync function showRk(){const d=await api('/api/recovery-key');document.getElementById('rkout').textContent=d.recoveryKey?('恢复钥匙: '+d.recoveryKey):(d.error||'');}\r\nasync function resetPw(){const np1=document.getElementById('np1').value,np2=document.getElementById('np2').value;if(np1!==np2){document.getElementById('pwout').innerHTML='<span class=\"err\">两次新口令不一致</span>';return;}\r\n const d=await api('/api/reset-password',{currentPassword:document.getElementById('cur').value,newPassword:np1});document.getElementById('pwout').innerHTML=d.error?('<span class=\"err\">'+esc(d.error)+'</span>'):('<span class=\"ok\">'+esc(d.text||'ok')+'</span>');}\r\nboot();\r\n</script></body></html>\r\n";
31
31
  // @generated view-html:end
@@ -4341,8 +4341,12 @@ function migrateCore(root, password, recoveryKeyIn) {
4341
4341
  } else {
4342
4342
  tail += '\n[恢复钥匙](务必离线保存,仅此一次,泄露=可解全部私密): ' + rk.toString('base64');
4343
4343
  }
4344
- tail += '\n迁移不会写明文 owner key cache;请为每个 agent 执行一次 yotta-memory key bind <id>,再注入 agent_key。';
4345
- if (self && owners.indexOf(self) !== -1) tail += '\n你的身份:' + self + ',请先执行 yotta-memory key bind ' + self + '。';
4344
+ tail += '\n迁移不会写明文 owner key cache。下一步为每个 agent 做一次授权,二选一(等价):';
4345
+ tail += '\n 推荐:yotta-memory view → 浏览器解锁 → 授权 <id> → 保存一次性 agent_key';
4346
+ tail += '\n 高级:yotta-memory key bind <id>';
4347
+ tail += '\n授权后 AI 执行:yotta-memory key status <id> → yotta-memory key claim <id> → <AI_HOME>/.yotta-memory-agent-key';
4348
+ tail += '\n然后带 agent-key-file 重建加密索引:yotta-memory reindex --agent <id> --agent-key-file <AI_HOME>/.yotta-memory-agent-key';
4349
+ if (self && owners.indexOf(self) !== -1) tail += '\n你的身份:' + self + '。推荐直接在 yotta-memory view 页面授权它。';
4346
4350
  return { error: false, text: tail };
4347
4351
  }
4348
4352
 
@@ -7111,7 +7115,7 @@ function usage() {
7111
7115
  ['token', '生成/列出/吊销访问 token(new --agent / list / revoke --agent)']
7112
7116
  ]],
7113
7117
  ['加密与安全', [
7114
- ['migrate', '把明文库迁移为密文(需主口令;空明文库同样可用;--password-stdin;--recovery-key-out <文件> 写恢复钥匙)'],
7118
+ ['migrate', '把明文库迁移为密文(需主口令;空明文库同样可用;--password-stdin;--recovery-key-out <文件> 写恢复钥匙;首次迁移:echo 主口令 | yotta-memory migrate --password-stdin --recovery-key-out <文件>;迁移后授权二选一:推荐 yotta-memory view,等价 CLI 为 yotta-memory key bind <id>)'],
7115
7119
  ['view', '启动用户查看平台(--port/--host;空加密库可用恢复钥匙校验主口令;已在运行则复用 URL)'],
7116
7120
  ['reset-password', '重设主口令(忘口令用恢复钥匙)'],
7117
7121
  ['key', '管理 agent_key binding(list / bind <id> / rotate <id> / claim <id> [--to <AI_HOME> | --agent-key-file <文件>] / status <id> [--to <AI_HOME> | --agent-key-file <文件>] / revoke <id>;bind/rotate 需主口令或恢复钥匙)'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yottameta/yotta-memory",
3
- "version": "0.16.2",
3
+ "version": "0.16.3",
4
4
  "description": "Yuanyi (元忆) — boundary-aware, file-based memory for AI agents. File-based, zero-dependency, diff/rollback-able; FACT/PREF/BOUND/COMMIT types (public shared / private isolated), user-level + project-level storage; v0.12 reliability baseline (init guard, trash-based deletion, independent-volume backup/list/doctor/restore, start-of-work doctor, transactional snapshots before destructive writes), v0.10 consolidation (periodic summaries with provenance, near-duplicate auto-merge, per-type decay, batch audit + rollback), v0.9 recall quality + context focus + optional local embedding plugin, plus v0.8 semantic search, feedback loop, self-organization and distillation.",
5
5
  "license": "MIT",
6
6
  "keywords": [
package/references/faq.md CHANGED
@@ -8,6 +8,14 @@
8
8
  ## 2. 私密区加密怎么用?
9
9
  `init` 默认初始化加密库(需主口令 + 恢复钥匙,请妥善保存);明文库可用 `migrate` 升级为加密。私密区文件为 `.md.enc`,可 git 版本化。查看/授权用 `yotta-memory view`(口令解锁,浏览/授权/吊销 AI);授权时一次性展示的 `agent_key` 请立即保存,服务端同时写 `keys/pending/<id>.key` 供该 AI 新会话用 `key claim` 领取。出现 `[YTM_MIGRATION_REQUIRED]` 说明还有 agent 未绑定,请由你在 `view` 平台逐个点「授权」完成重新授权(AI 只提醒、不代执行)。
10
10
 
11
+ **明文库第一次转加密**:
12
+
13
+ ```cmd
14
+ echo 主口令 | yotta-memory migrate --password-stdin --recovery-key-out "%USERPROFILE%\yotta-memory-recovery.key"
15
+ ```
16
+
17
+ 迁移后授权二选一(等价):推荐 `yotta-memory view` 页面授权;高级用户可 `yotta-memory key bind <id>`。授权并 `key claim` 后,带 `--agent-key-file` 执行 `yotta-memory reindex` 重建每 owner 加密索引,再做 `recall` 验证。
18
+
11
19
  ## 2.1 非 TTY / GUI 宿主怎么初始化?
12
20
  不要依赖交互提示。用 `--password-stdin` 从管道读主口令(不进 argv),或用 `YOTTA_MEMORY_PASS`;恢复钥匙用 `--recovery-key-out <文件>` 写文件,避免 GUI 宿主吞掉 stdout。非 TTY 且未提供口令时会明确提示改用哪种方式,不会静默「已取消」。
13
21
 
@@ -17,6 +25,19 @@ v0.16.2 起,空加密库(没有 owner key)可用恢复钥匙校验主口
17
25
  ## 2.3 `--agent-key-file` 指向的文件不存在?
18
26
  文件不存在时不再致命:元忆降级为未授权模式,公共 FACT 仍可读;私密操作 fail-closed,并提示 `key bind <id>`。文件存在但为空或不可读仍会报错。推荐用 `YOTTA_MEMORY_AGENT_HOME` 指定宿主目录,授权后 key 原地生效。
19
27
 
28
+ ## 2.4 迁移后必须 `key bind` 吗?
29
+ 不必。`yotta-memory view` 页面授权和 `yotta-memory key bind <id>` 是两条等价路径:两条都会写 `binding + pending`,随后 AI 都执行 `yotta-memory key status <id>` → `yotta-memory key claim <id>`,再落到 `<AI_HOME>/.yotta-memory-agent-key`。普通用户推荐 `view`,高级用户或无可视化环境再用 `key bind`。
30
+
31
+ ## 2.5 迁移后 `recall` 读不到私密怎么办?
32
+ 先确认已经完成授权和 `key claim`,再带 agent key 重建加密索引:
33
+
34
+ ```cmd
35
+ yotta-memory reindex --agent <id> --agent-key-file "<AI_HOME>/.yotta-memory-agent-key"
36
+ yotta-memory recall <关键词> --agent <id> --agent-key-file "<AI_HOME>/.yotta-memory-agent-key"
37
+ ```
38
+
39
+ `migrate` 在没有 agent_key 时无法建立每 owner 加密索引,所以 `reindex` 必须在授权 / claim 之后执行。MCP 场景还要确认宿主配置带 `--agent-key-file`,只有 `--agent-id` 会报缺少 agent_key。
40
+
20
41
  ## 3. 多智能体权限怎么隔离?
21
42
  公共 FACT 所有智能体可读;PREF / BOUND / COMMIT 按 owner 物理隔离,调用方必须持有匹配的 `agent_key`(用户执行 `key bind <id>`,或在 `view` 平台授权获得)。owner ID 单独存在不构成认证,不授权 / 无 key 读不到。私密操作缺 key 时会输出 `[YTM_MIGRATION_REQUIRED]`;授权完成后该标记消失。AI 新会话用 `key status <id>` 检查,pending 存在则 `key claim <id>` 落到 `<AI_HOME>/.yotta-memory-agent-key`;`AI_HOME` 默认规则由 claim / status 共用(显式 `--to` / `--agent-key-file` > `YOTTA_MEMORY_AGENT_HOME` / `YOTTA_MEMORY_AGENT_KEY_FILE` > Codex / OpenCode / 通用宿主默认),status 会显示 `checked` 与 `discovery`。吊销后旧 key 立即校验失败,需重新授权。
22
43
 
@@ -3,7 +3,7 @@
3
3
  "slug": "yotta-memory",
4
4
  "name": "元忆",
5
5
  "package": "@yottameta/yotta-memory",
6
- "version": "0.16.2",
6
+ "version": "0.16.3",
7
7
  "trust": "yottameta",
8
8
  "install": {
9
9
  "idempotent": true