@yottameta/yotta-memory 0.16.3 → 0.16.4

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,13 @@
1
+ ## v0.16.4 (2026-09-22)
2
+
3
+ **agent-key 缺文件提示范围修复**
4
+
5
+ - 主入口不再对 `--agent-key-file` 不存在无条件输出全局 `stderr` 警告;`migrate`、`doctor`、`config` 等公共 / 维护命令保持安静。
6
+ - 未授权提示下沉到真实私密访问边界:私密读写仍 fail-closed,并明确给出缺失文件、`view` / `key bind`、`key status` / `key claim` 的可操作步骤。
7
+ - `whoami --json`、`doctor --json`、`config get --json` 新增 `identity.mode` / `identity.agentKeyStatus` / `identity.source` / `identity.keyFile` / `identity.action`,默认人类输出不加全局警告。
8
+ - 回归覆盖公共命令安静、私密命令 fail-closed、JSON 身份状态三组行为;全量 `npm test` 158/158 PASS。
9
+ - 版本对齐:package.json / SKILL.md frontmatter / skill-manifest.json / CHANGELOG / 引擎 VERSION = 0.16.4。
10
+
1
11
  ## v0.16.3 (2026-09-22)
2
12
 
3
13
  **迁移最短路径与授权引导修复**
package/README.md CHANGED
@@ -23,6 +23,7 @@
23
23
 
24
24
  > 📖 The user-facing operations manual lives in [USER_GUIDE.md](USER_GUIDE.md).
25
25
 
26
+ > 🆕 **v0.16.4 (agent-key prompt scope)**: a missing `--agent-key-file` no longer prints a global `stderr` warning for public or maintenance commands. Only real private-data access fails closed, with the missing path, `view` / `key bind`, and `key status` / `key claim` guidance. `whoami --json`, `doctor --json`, and `config get --json` expose structured `identity.mode` / `identity.agentKeyStatus` fields.
26
27
  > 🆕 **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
28
  > 🆕 **v0.16.3 (migration quick path)**: convert a plaintext store to encryption with
28
29
  > `echo <master-password> | yotta-memory migrate --password-stdin --recovery-key-out "%USERPROFILE%\yotta-memory-recovery.key"`.
package/README.zh-CN.md CHANGED
@@ -23,6 +23,7 @@
23
23
 
24
24
  > 📖 面向用户的操作手册见 [USER_GUIDE.md](USER_GUIDE.md)。
25
25
 
26
+ > 🆕 **v0.16.4(agent-key 提示范围)**:`--agent-key-file` 不存在时不再为公共 / 维护命令输出全局 `stderr` 警告;只有真正访问私密区才 fail-closed,并给出缺失路径、`view` / `key bind`、`key status` / `key claim` 步骤。`whoami --json`、`doctor --json`、`config get --json` 返回结构化 `identity.mode` / `identity.agentKeyStatus`。
26
27
  > 🆕 **v0.16.2(首启修复)**:空加密库 `view` 可用恢复钥匙解锁;非 TTY 支持 `--password-stdin`;恢复钥匙支持 `--recovery-key-out <文件>`;`--agent-key-file` 不存在时降级未授权(公共 FACT 可读、私密 fail-closed);空明文库可直接 `migrate` 启用加密;`view` 端口占用给明确提示。
27
28
  > 🆕 **v0.16.3(迁移最短路径)**:明文库第一次转加密:
28
29
  > `echo 主口令 | yotta-memory migrate --password-stdin --recovery-key-out "%USERPROFILE%\yotta-memory-recovery.key"`
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.3
4
+ version: 0.16.4
5
5
  license: MIT
6
6
  ---
7
7
 
@@ -18,6 +18,7 @@ license: MIT
18
18
  - **越用越懂(v0.14.0)**:`context` 一键生成开工上下文包——长期理解摘要优先(复用 `consolidate` 产物)+ 用户画像(引擎零推断,只归组原文)+ 近期走廊(按更新时间取样)+ 近期高价值补位 + 边界 + 承诺 + 会话闭环契约;SKILL「记忆守则」规则层只注入规则与机制,不注入人格数据(出厂零数据)。
19
19
  - **MCP 工具分组(v0.15.0)**:`serve --tools core|full` 控制工具暴露面。`core` 固定为 `context / recall / search / remember`,适合常驻;`full` 为现有 16 工具,适合维护与诊断。未指定时默认 `full`,保持现有配置兼容。
20
20
  - **身份模型(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`。旧身份 env 会在 MCP 启动时被明确拒绝。
21
+ - **未授权提示边界(v0.16.4)**:`--agent-key-file` 不存在时不再由主入口向全局 `stderr` 告警。公共 / 维护命令保持安静;只有真正访问私密区时才 fail-closed,并给出缺失文件、`view` / `key bind`、`key status` / `key claim` 的可操作步骤。`whoami --json`、`doctor --json`、`config get --json` 返回 `identity.mode` / `identity.agentKeyStatus`。
21
22
  - **运行时稳定入口(v0.16.0 M2)**:`runtime install --from-current` 把当前引擎安装到 `<runtimeRoot>/versions/<version>/` 并创建 `<runtimeRoot>/current` 稳定指针;`runtime use <version>` 原子切换、`runtime rollback` 回滚、`runtime status` 查看漂移。stdio MCP、`lan enable` 与备份调度只指向 `<runtimeRoot>/current/bin/yotta-memory.js`,不写版本目录。
22
23
  - **运行时诊断与握手(v0.16.0 M3)**:`doctor --runtime` 检查 CLI / current / runtime.json / MCP 配置 / 运行中 server / 技能副本 / 身份模式漂移,逐项给出实际版本、期望版本、修复命令和是否阻断;MCP `initialize` / `server/discover` 的 `serverInfo` 返回 `runtimePath` / `identityMode` / `toolProfile`。
23
24
  - **自我学习 / 自我进化 / 自我提升(v0.8.0)**:`recall` 语义检索(同义词 / 拼音 / 字段加权 / 模糊匹配,零依赖);`feedback` 显式使用反馈闭环(useful / useless → weight / confidence / feedback_net 演化,越用越懂);`maintain` 规则层自组织(统一效用分 + 年龄自动归档 / 遗忘候选 / 去重,默认 dry-run,immutable / BOUND 豁免);`distill` 心理日志蒸馏(统计摘要 / 主题画像 / 知识地图,可选 `--model` 外部模型增强);`explain` 查看单条记忆效用分项。
@@ -277,12 +278,12 @@ yotta-memory doctor --json
277
278
  | `yotta-memory forget <文件>` | 移入 `.trash/<时间>/` 回收区并写审计(v0.12.0;不再物理删除)|
278
279
  | `yotta-memory backup volumes / setup --dir <目录> / status / ensure-daily / schedule enable|disable|status` | 每日自动备份(v0.12.0;只展示实际枚举的异卷、用户确认一次位置后默认每日执行,Windows Task Scheduler / systemd timer / launchd 调度,`serve` 补跑)|
279
280
  | `yotta-memory backup create / list / doctor / restore <ID> --to <目录> / drill [<ID>]` | 备份、恢复与恢复演练(v0.12.0;独立盘校验、SHA-256 清单、排除 `keys/cache`、恢复默认只写新目录;drill 验证 manifest / 索引 / 测试私密解密)|
280
- | `yotta-memory doctor [--json] [--runtime] [--mcp-config <文件>] [--skill-dir <目录>]` | 开工可靠性检查(v0.12.2;根目录 / 密钥库 / 索引 / 身份 / 最近备份;严重异常时锁定破坏性写入);全新空库的缺失 index / agents 降为 info;输出 agent home 发现规则与 `YOTTA_MEMORY_AGENT_HOME` 提示;加 `--runtime` 检查 CLI / current / MCP 配置 / 运行中 server / 技能副本漂移 |
281
+ | `yotta-memory doctor [--json] [--runtime] [--mcp-config <文件>] [--skill-dir <目录>]` | 开工可靠性检查(v0.12.2;根目录 / 密钥库 / 索引 / 身份 / 最近备份;严重异常时锁定破坏性写入);全新空库的缺失 index / agents 降为 info;输出 agent home 发现规则与 `YOTTA_MEMORY_AGENT_HOME` 提示;加 `--runtime` 检查 CLI / current / MCP 配置 / 运行中 server / 技能副本漂移;v0.16.4 起 `--json` 含身份 / agent-key 状态 |
281
282
  | `yotta-memory archive [--days 180] [--threshold 0.35]` | 归档旧记忆(v0.8.0 统一效用分 + v0.10.0 分类型衰减;immutable / BOUND 豁免;私密归档入 `.archive/private/<owner>/<type>/`;阈值默认读 config `maintain_archived_utility`)|
282
283
  | `yotta-memory reindex` | 重建索引(手动改 .md 后校正)|
283
284
  | `yotta-memory export [--out f.json]` / `import <f.json>` | 导出 / 导入 |
284
- | `yotta-memory config set memory_home <目录>` / `config set backup_dir <目录>` / `config get` | 持久记住 / 查看记忆库位置与备份目录(`~/.yottamemory/config.json`)|
285
- | `yotta-memory whoami --agent <id> [--agent-key <key>]` | 查看当前显式身份与登记状态;身份不从环境变量读取 |
285
+ | `yotta-memory config set memory_home <目录>` / `config set backup_dir <目录>` / `config get [--json]` | 持久记住 / 查看记忆库位置与备份目录(`~/.yottamemory/config.json`;`get --json` 同时返回身份状态)|
286
+ | `yotta-memory whoami --agent <id> [--agent-key <key>] [--json]` | 查看当前显式身份与登记状态;身份不从环境变量读取;`--json` 返回 `identity` 结构化状态 |
286
287
  | `yotta-memory iam <id> [--name <显示名>] [--user <用户名>] [--relationship <关系>] [--force]` | 登记本智能体唯一身份并自动落自我档案(`agents.json`,ID 必须唯一;可选扩展显示名 / 用户 / 关系)|
287
288
  | `yotta-memory token new --agent <id> [--force]` / `token list` / `token revoke --agent <id>` | 每智能体访问 token:生成 / 列出 / 吊销(登记 `<记忆库>/.server/tokens.json`;同 ID 已被其它来源占用需 `--force` 覆盖,防不同智能体合流)|
288
289
  | `yotta-memory serve [--host 0.0.0.0] [--port 8787] [--no-auth] [--stdio] [--tools core|full]` | 启动 MCP 记忆引擎(streamable HTTP 局域网 / --stdio 本地零进程模式;Bearer token + X-Agent-Id + X-Agent-Key 鉴权;工具分组默认 full)|
@@ -298,7 +299,7 @@ yotta-memory doctor --json
298
299
 
299
300
  - **非 TTY 初始化 / 迁移**:完整步骤见下一节《明文库转加密(第一次最短路径)》。
300
301
  - **恢复钥匙写文件**:`--recovery-key-out <文件>`(`init` / `migrate`)把恢复钥匙写入文件,适配 Windows GUI 宿主 stdout 不可捕获;不要把钥匙粘贴到对话。
301
- - **缺 `--agent-key-file`**:文件不存在时降级为未授权模式(公共 FACT 可读,私密操作 fail-closed 并提示 `key bind <id>`);文件存在但为空 / 不可读仍报错。
302
+ - **缺 `--agent-key-file`**:文件不存在时进入未授权模式(公共 FACT 可读);公共 / 维护命令不再输出全局 `stderr` 告警。只有私密操作 fail-closed,并提示缺失文件、`view` / `key bind <id>`、`key status` / `key claim`;文件存在但为空 / 不可读仍报错。
302
303
  - **空加密库授权**:`yotta-memory view` 在无 owner key 时可用恢复钥匙校验主口令进入平台;无 owner 时先 `yotta-memory iam <id>`,再回页面授权。
303
304
  - **view 端口**:启动前做健康检查;已在运行则打印 URL 复用,端口被占用给明确提示,不再抛未处理的 `EADDRINUSE`。
304
305
 
package/USER_GUIDE.md CHANGED
@@ -128,11 +128,11 @@ statement: 本周完成发布
128
128
 
129
129
  - **新库**:`yotta-memory init`(新建默认加密)→ 输入主口令两次 → **抄下打印的恢复钥匙**(44 位 base64,离线保存;忘口令时用它重设)。不要加密用 `--no-encrypt`。
130
130
  - **老明文库升级 / 非 TTY**:见下方《明文库转加密(第一次最短路径)》,只保留一套步骤。
131
- - **缺 `--agent-key-file`**:文件不存在时降级为未授权模式——公共 FACT 可读,私密操作 fail-closed 并提示 `key bind <id>`;文件存在但为空 / 不可读仍报错。
131
+ - **缺 `--agent-key-file`**:文件不存在时进入未授权模式——公共 FACT 可读;公共 / 维护命令保持安静。只有私密操作 fail-closed,并提示缺失文件、`view` / `key bind <id>`、`key status` / `key claim`;文件存在但为空 / 不可读仍报错。
132
132
 
133
133
  **明文库转加密(第一次最短路径)**
134
134
 
135
- > 适用于 `yotta-memory 0.16.2+`。迁移前先完整备份。
135
+ > 适用于 `yotta-memory 0.16.4+`。迁移前先完整备份。
136
136
 
137
137
  1. 迁移:
138
138
 
@@ -270,7 +270,7 @@ yotta-memory recall <关键词> --agent <id> --agent-key-file "<AI_HOME>/.yotta-
270
270
 
271
271
  ## 3.9 开工 doctor 与事务快照(v0.12.2)
272
272
 
273
- - `yotta-memory doctor`:只读检查记忆库根目录、加密库密钥文件、公共索引、`agents.json` 与最近备份;加 `--json` 可输出机器可读结果。
273
+ - `yotta-memory doctor`:只读检查记忆库根目录、加密库密钥文件、公共索引、`agents.json` 与最近备份;加 `--json` 可输出机器可读结果与 `identity.mode` / `identity.agentKeyStatus`。
274
274
  - 严重异常(根目录缺失、密钥库缺文件、备份目录同卷)会返回非零退出码;此时 `context` 也会显示“破坏性写入已锁定”。
275
275
  - `maintain --apply`、`consolidate --apply`、`merge`、`archive` 与 `--purge` 在写入前自动创建新的整库事务快照,并在 `.archive/audit-<日期>.jsonl` 记录 transaction / operation / snapshot。
276
276
  - 未配置独立备份目录、doctor critical 或快照失败时,命令直接拒绝执行,原记忆保持不变;`forget` 仍只移入 `.trash/`,不重复创建整库快照。
@@ -441,8 +441,8 @@ yotta-memory key claim <本智能体ID>
441
441
  | `yotta-memory archive [--days 180] [--threshold 0.4]` | 归档旧记忆(分类型衰减效用分 + 年龄;immutable / BOUND 豁免;私密入 `.archive/private/<owner>/<type>/`)|
442
442
  | `yotta-memory reindex` | 重建索引 |
443
443
  | `yotta-memory export [--out 文件.json]` / `import <文件.json>` | 导出 / 导入 |
444
- | `yotta-memory config set <键> <值>` / `config get` | 记忆库位置与引擎参数(`memory_home` / `embedding_cmd` / `embedding_timeout` / `maintain_archived_utility` / `maintain_decay_halflife_<TYPE>` / `consolidate_*` 等)|
445
- | `yotta-memory whoami --agent <id>` | 查看当前显式身份与登记状态;身份不从环境变量读取 |
444
+ | `yotta-memory config set <键> <值>` / `config get [--json]` | 记忆库位置与引擎参数(`memory_home` / `embedding_cmd` / `embedding_timeout` / `maintain_archived_utility` / `maintain_decay_halflife_<TYPE>` / `consolidate_*` 等;`get --json` 同时返回身份状态)|
445
+ | `yotta-memory whoami --agent <id> [--json]` | 查看当前显式身份与登记状态;身份不从环境变量读取;`--json` 返回结构化身份状态 |
446
446
  | `yotta-memory iam <id> [--name <显示名>] [--user <用户名>] [--relationship <关系>] [--force]` | 登记本智能体唯一身份并自动落自我档案(`agents.json`,ID 必须唯一;可选扩展显示名 / 用户 / 关系)|
447
447
  | `yotta-memory token new --agent <id> [--force]` / `token list` / `token revoke --agent <id>` | 访问 token(同 ID 已被其它来源占用需 `--force` 覆盖)|
448
448
  | `yotta-memory serve [--port 8787] [--stdio] [--no-auth]` | 启动记忆引擎(--no-auth 关闭鉴权,仅限可信内网)|
@@ -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.3';
28
+ const VERSION = '0.16.4';
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
@@ -624,6 +624,7 @@ function resolveIdentity(opts) {
624
624
  id: explicit,
625
625
  agentKey: '',
626
626
  source: 'missing-key-file',
627
+ keyFile: keyFile,
627
628
  warning: '未找到 agent-key 文件: ' + keyFile + ';已降级为未授权模式(公共 FACT 可读,私密操作需要显式授权)。',
628
629
  error: '',
629
630
  };
@@ -642,6 +643,24 @@ function resolveIdentity(opts) {
642
643
  }
643
644
  return { id: '', agentKey: '', source: 'none', error: '' };
644
645
  }
646
+ function identityDiagnostic(ident) {
647
+ ident = ident || {};
648
+ const invalid = !!ident.error;
649
+ const authenticated = !invalid && !!ident.agentKey;
650
+ const mode = invalid ? 'invalid' : (authenticated ? 'authenticated' : (ident.id ? 'unauthenticated' : 'anonymous'));
651
+ const agentKeyStatus = invalid ? 'invalid' : (authenticated ? 'present' : (ident.source === 'missing-key-file' ? 'missing' : 'not-provided'));
652
+ const out = {
653
+ agentId: ident.id || '',
654
+ mode: mode,
655
+ agentKeyStatus: agentKeyStatus,
656
+ source: ident.source || 'none',
657
+ };
658
+ if (ident.keyFile) out.keyFile = path.resolve(String(ident.keyFile));
659
+ if (ident.source === 'missing-key-file') {
660
+ out.action = '由用户在 yotta-memory view 中授权,或执行 yotta-memory key bind ' + (ident.id || '<id>') + ';随后 AI 执行 key status / key claim。';
661
+ }
662
+ return out;
663
+ }
645
664
  function isSafeAgentId(id) {
646
665
  const value = String(id || '');
647
666
  if (!value || value.length > 128) return false;
@@ -1358,6 +1377,9 @@ function validatePrivateIdentity(root, ident, owner) {
1358
1377
  const current = currentRuntimeIdentity();
1359
1378
  const agentKey = ident.agentKey || (current.id === ident.id ? current.agentKey : '');
1360
1379
  if (!agentKey) {
1380
+ if (ident.source === 'missing-key-file') {
1381
+ return '未找到 agent-key 文件: ' + ident.keyFile + ';当前为未授权模式(公共 FACT 可读)。该私密操作需要先由用户执行 yotta-memory view 授权或 yotta-memory key bind ' + (owner || ident.id) + ',再由 AI 执行 yotta-memory key status ' + (owner || ident.id) + ' → key claim ' + (owner || ident.id) + '。';
1382
+ }
1361
1383
  return '记忆库已加密,但缺少 agent_key。CLI 请使用 --agent-key <key> 或 --agent-key-file <path>;stdio MCP 请使用 --agent-id <id> + --agent-key-file <path>;HTTP MCP 请发送 X-Agent-Key 请求头。若该 agent 尚未绑定,请执行 yotta-memory key bind ' + (owner || ident.id) + '。';
1362
1384
  }
1363
1385
  const context = IDENTITY_CONTEXT.getStore();
@@ -2952,6 +2974,7 @@ function doctorCore(opts) {
2952
2974
  opts = opts || {};
2953
2975
  const root = path.resolve(opts.root || userRoot());
2954
2976
  const cfg = loadConfig();
2977
+ const identity = identityDiagnostic(resolveIdentity(opts));
2955
2978
  const checks = {};
2956
2979
  const warnings = [];
2957
2980
  const critical = [];
@@ -3001,6 +3024,7 @@ function doctorCore(opts) {
3001
3024
  warnings.push('agents.json 无法解析;请先恢复身份登记。');
3002
3025
  }
3003
3026
  }
3027
+ checks.identity = identity;
3004
3028
 
3005
3029
  const migration = migrationRequiredInfo(root);
3006
3030
  if (migration) {
@@ -3090,6 +3114,7 @@ function doctorCore(opts) {
3090
3114
  critical: critical,
3091
3115
  warnings: warnings,
3092
3116
  checks: checks,
3117
+ identity: identity,
3093
3118
  root: root,
3094
3119
  text: lines.join('\n'),
3095
3120
  };
@@ -5361,11 +5386,16 @@ function cmdWhoami(opts) {
5361
5386
  const root = userRoot();
5362
5387
  const ident = resolveIdentity(opts);
5363
5388
  if (ident.error) {
5364
- console.log(ident.error);
5389
+ if (opts.json) console.log(JSON.stringify({ identity: identityDiagnostic(ident), error: ident.error }, null, 2));
5390
+ else console.log(ident.error);
5365
5391
  process.exit(2);
5366
5392
  }
5367
5393
  const id = ident.id;
5368
5394
  if (!id) {
5395
+ if (opts.json) {
5396
+ console.log(JSON.stringify({ identity: identityDiagnostic(ident), registration: 'none', profile: '' }, null, 2));
5397
+ return;
5398
+ }
5369
5399
  console.log('当前未声明显式智能体身份(身份不再从环境变量读取)。');
5370
5400
  console.log('CLI:请传 --agent <唯一ID>。stdio MCP:使用 --agent-id <唯一ID> + --agent-key-file <path>。HTTP MCP:发送 X-Agent-Id + X-Agent-Key。');
5371
5401
  console.log('远端:token + X-Agent-Id + X-Agent-Key(token new --agent <id>;agent_key 由 view 授权生成)。');
@@ -5377,11 +5407,22 @@ function cmdWhoami(opts) {
5377
5407
  if (agents[id]) reg = 'agents.json 已登记(host=' + agents[id].host + ')';
5378
5408
  else if (tokens[id]) reg = 'tokens.json 已登记(远端)';
5379
5409
  const profile = findSelfProfile(root, id);
5410
+ const kv = profile ? selfProfileKv(root, id) : {};
5411
+ if (opts.json) {
5412
+ console.log(JSON.stringify({
5413
+ identity: identityDiagnostic(ident),
5414
+ registration: reg,
5415
+ profile: profile || '',
5416
+ agentName: kv.agent_name || '',
5417
+ userName: kv.user_name || '',
5418
+ relationship: kv.relationship || '',
5419
+ }, null, 2));
5420
+ return;
5421
+ }
5380
5422
  console.log('当前智能体身份: ' + id);
5381
5423
  console.log('登记状态: ' + reg);
5382
5424
  console.log('自我档案: ' + (profile ? profile : '未写入(请执行 yotta-memory iam ' + id + ')'));
5383
5425
  if (profile) {
5384
- const kv = selfProfileKv(root, id);
5385
5426
  if (kv.agent_name) console.log('显示名: ' + kv.agent_name);
5386
5427
  if (kv.user_name) console.log('用户: ' + kv.user_name);
5387
5428
  if (kv.relationship) console.log('关系: ' + kv.relationship);
@@ -5727,8 +5768,15 @@ function cmdConfigSet(key, value) {
5727
5768
  saveConfig(cfg);
5728
5769
  console.log('已写入配置: ' + key + ' = ' + (key === 'memory_home' || key === 'backup_dir' || key === 'embedding_cmd' ? value : cfg[key]));
5729
5770
  }
5730
- function cmdConfigGet() {
5771
+ function cmdConfigGet(opts) {
5731
5772
  const cfg = loadConfig();
5773
+ if (opts && opts.json) {
5774
+ console.log(JSON.stringify(Object.assign({}, cfg, {
5775
+ effective_memory_home: userRoot(),
5776
+ identity: identityDiagnostic(resolveIdentity(opts)),
5777
+ }), null, 2));
5778
+ return;
5779
+ }
5732
5780
  console.log('memory_home: ' + (cfg.memory_home || '(未设置,默认 ~/.yottamemory)'));
5733
5781
  console.log('backup_dir: ' + (cfg.backup_dir || '(未设置)'));
5734
5782
  console.log('backup_enabled: ' + (cfg.backup_enabled === undefined ? '(未设置)' : cfg.backup_enabled));
@@ -7256,7 +7304,6 @@ async function main() {
7256
7304
  process.exit(2);
7257
7305
  }
7258
7306
  const runtimeIdentity = resolveIdentity(opts);
7259
- if (runtimeIdentity.warning) console.error(runtimeIdentity.warning);
7260
7307
  setRuntimeAgent(runtimeIdentity.error ? '' : runtimeIdentity.id, runtimeIdentity.error ? '' : runtimeIdentity.agentKey);
7261
7308
  switch (first) {
7262
7309
  case 'init': await cmdInit(opts); break;
@@ -7265,7 +7312,7 @@ async function main() {
7265
7312
  case 'config': {
7266
7313
  const sub = rest[0];
7267
7314
  if (sub === 'set') cmdConfigSet(rest[1], rest[2]);
7268
- else if (sub === 'get') cmdConfigGet();
7315
+ else if (sub === 'get') cmdConfigGet(opts);
7269
7316
  else { console.error('config 子命令: set <键> <值>(memory_home / embedding_cmd / embedding_timeout / maintain_* / consolidate_*)/ get'); process.exit(2); }
7270
7317
  break;
7271
7318
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yottameta/yotta-memory",
3
- "version": "0.16.3",
3
+ "version": "0.16.4",
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
@@ -23,7 +23,7 @@ echo 主口令 | yotta-memory migrate --password-stdin --recovery-key-out "%USER
23
23
  v0.16.2 起,空加密库(没有 owner key)可用恢复钥匙校验主口令进入 `view`。如果页面显示「无 owner」,先在终端执行 `yotta-memory iam <id>` 登记身份,再回页面授权;也可以由用户在终端执行 `yotta-memory key bind <id>`。
24
24
 
25
25
  ## 2.3 `--agent-key-file` 指向的文件不存在?
26
- 文件不存在时不再致命:元忆降级为未授权模式,公共 FACT 仍可读;私密操作 fail-closed,并提示 `key bind <id>`。文件存在但为空或不可读仍会报错。推荐用 `YOTTA_MEMORY_AGENT_HOME` 指定宿主目录,授权后 key 原地生效。
26
+ 文件不存在时不再致命:元忆进入未授权模式,公共 FACT 仍可读;`migrate` / `doctor` / `config` 等公共或维护命令不会再向 `stderr` 打印降级警告。只有私密读写 fail-closed,并提示缺失文件、`view` / `key bind <id>`、`key status` / `key claim`。需要程序化判断时,使用 `whoami --json`、`doctor --json` 或 `config get --json` 的 `identity.mode` / `identity.agentKeyStatus`。文件存在但为空或不可读仍会报错。推荐用 `YOTTA_MEMORY_AGENT_HOME` 指定宿主目录,授权后 key 原地生效。
27
27
 
28
28
  ## 2.4 迁移后必须 `key bind` 吗?
29
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`。
@@ -3,7 +3,7 @@
3
3
  "slug": "yotta-memory",
4
4
  "name": "元忆",
5
5
  "package": "@yottameta/yotta-memory",
6
- "version": "0.16.3",
6
+ "version": "0.16.4",
7
7
  "trust": "yottameta",
8
8
  "install": {
9
9
  "idempotent": true