@yottameta/yotta-memory 0.16.5 → 0.16.7
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 +22 -1
- package/README.md +3 -2
- package/README.zh-CN.md +3 -2
- package/SKILL.md +14 -12
- package/USER_GUIDE.md +3 -3
- package/bin/plugin-mcp.js +111 -0
- package/bin/yotta-memory.js +68 -17
- package/package.json +1 -1
- package/references/faq.md +3 -3
- package/skill-manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
## v0.16.7 (2026-09-23)
|
|
2
|
+
|
|
3
|
+
**迁移口令安全 + view 根指纹复用校验**
|
|
4
|
+
|
|
5
|
+
- 明文库迁移文档与 CLI `--help` 不再给出可复制的中文占位符管道命令;推荐交互式输入,非 ASCII 口令明确禁止使用 Windows 管道,自动化改用 `YOTTA_MEMORY_PASS` + ASCII 口令。
|
|
6
|
+
- `migrate` 后 `view` 报「口令错误」时,文档给出用恢复钥匙 `reset-password` 的安全恢复路径。
|
|
7
|
+
- `view /api/status` 新增 memory-home `rootId`;`view` 复用已在运行的服务前必须校验根指纹,跨 memory_home 或旧版无指纹服务会拒绝复用并提示关闭进程或换端口。
|
|
8
|
+
- 新增回归:`test/view-root-guard.test.js`(同库复用、跨库拒绝、旧版无指纹拒绝)+ `test/migration-guidance.test.js`(文档安全命令与 CLI help 检查)。
|
|
9
|
+
- 版本对齐:package.json / SKILL.md frontmatter / skill-manifest.json / CHANGELOG / 引擎 VERSION = 0.16.7。
|
|
10
|
+
|
|
11
|
+
## v0.16.6 (2026-09-23)
|
|
12
|
+
|
|
13
|
+
**插件 MCP 身份启动器(Agent Plugin 开箱可用)**
|
|
14
|
+
|
|
15
|
+
- 新增 `bin/plugin-mcp.js`:宿主只替换 `${PLUGIN_ROOT}`、不替换 `${AGENT_ID}` / `${AGENT_KEY_FILE}` 时,按「宿主替换值 → 环境变量 → `PLUGIN_DATA/identity.json` → 未授权可执行指引」解析身份。
|
|
16
|
+
- 未装配身份时不再把字面量占位符传给引擎:以未授权模式启动(公共 FACT 可读、私密 fail-closed),并输出含 `key claim` 与 `identity.json` 的可执行指引。
|
|
17
|
+
- `key claim <id> --plugin-data <PLUGIN_DATA>`:一条命令同时写入插件 key 与 `identity.json`;与 `--to` / `--agent-key-file` 互斥校验。
|
|
18
|
+
- 插件 `mcp.json` 入口由 `yotta-memory.js` 改为 `plugin-mcp.js`;宿主替换占位符的旧路径继续有效。
|
|
19
|
+
- 回归:`test/plugin-mcp-launcher.test.js`(身份优先级 / 无身份指引)+ `test/plugin-claim-binding.test.js`(claim → 插件身份 → 私密读取端到端);全量 `npm test` 167/167 PASS。
|
|
20
|
+
- 版本对齐:package.json / SKILL.md frontmatter / skill-manifest.json / CHANGELOG / 引擎 VERSION = 0.16.6。
|
|
21
|
+
|
|
1
22
|
## v0.16.5 (2026-09-22)
|
|
2
23
|
|
|
3
24
|
**doctor --json 稳定契约补齐**
|
|
@@ -23,7 +44,7 @@
|
|
|
23
44
|
**迁移最短路径与授权引导修复**
|
|
24
45
|
|
|
25
46
|
- `migrate` 成功输出不再只提示 `key bind`:现在明确输出“授权二选一(等价)”——推荐 `yotta-memory view` 页面授权,高级 CLI 方式为 `yotta-memory key bind <id>`,随后 AI 统一执行 `key status` → `key claim`。
|
|
26
|
-
- `SKILL.md`、`USER_GUIDE.md`、`references/faq.md`、中英 README
|
|
47
|
+
- `SKILL.md`、`USER_GUIDE.md`、`references/faq.md`、中英 README 统一加入明文库第一次转加密命令(v0.16.7 已废弃原中文占位符管道写法);后续补充交互终端 / PowerShell / cmd / 管道等口令传递方式和一个统一报错对照。
|
|
27
48
|
- 修正迁移后的索引顺序:必须 **授权 → `key claim` → 带 `--agent-key-file` 执行 `reindex` → `recall` 验证**;`migrate` 在没有 agent_key 时无法建立每 owner 加密索引。MCP 自检同时要求配置带 `--agent-key-file`。
|
|
28
49
|
- `--help` 的 `migrate` 行同步给出首次迁移命令与两条等价授权路径。
|
|
29
50
|
- 新增 `test/migration-guidance.test.js`,锁定迁移命令、`view` / `key bind` 等价关系和 CLI 帮助口径。
|
package/README.md
CHANGED
|
@@ -23,11 +23,12 @@
|
|
|
23
23
|
|
|
24
24
|
> 📖 The user-facing operations manual lives in [USER_GUIDE.md](USER_GUIDE.md).
|
|
25
25
|
|
|
26
|
+
> 🆕 **v0.16.7 (migration password safety + view root fingerprint)**: use the interactive migration prompt `yotta-memory migrate --recovery-key-out "%USERPROFILE%\yotta-memory-recovery.key"` and type the master password. Automation can set `YOTTA_MEMORY_PASS` with an ASCII password. Do not pipe non-ASCII passwords on Windows (`echo 中文 | ...` can change the bytes). `view` now verifies a memory-home fingerprint before reusing a running server and refuses cross-store or legacy servers without one.
|
|
26
27
|
> 🆕 **v0.16.5 (doctor JSON contract)**: `doctor --json` now exposes stable top-level `schemaVersion`, `encryption`, and `migration_required` fields while preserving the existing `checks` / `warnings` / `identity` structure.
|
|
27
28
|
> 🆕 **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.
|
|
28
29
|
> 🆕 **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.
|
|
29
|
-
> 🆕 **v0.16.3 (migration quick path)**: convert a plaintext store to encryption with
|
|
30
|
-
> `
|
|
30
|
+
> 🆕 **v0.16.3 (migration quick path)**: convert a plaintext store to encryption with the interactive command
|
|
31
|
+
> `yotta-memory migrate --recovery-key-out "%USERPROFILE%\yotta-memory-recovery.key"`.
|
|
31
32
|
> 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.
|
|
32
33
|
> 🆕 **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`.
|
|
33
34
|
> 🆕 **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.
|
package/README.zh-CN.md
CHANGED
|
@@ -23,11 +23,12 @@
|
|
|
23
23
|
|
|
24
24
|
> 📖 面向用户的操作手册见 [USER_GUIDE.md](USER_GUIDE.md)。
|
|
25
25
|
|
|
26
|
+
> 🆕 **v0.16.7(迁移口令安全 + view 根指纹)**:明文库转加密推荐交互式 `yotta-memory migrate --recovery-key-out "$env:USERPROFILE\yotta-memory-recovery.key"`,按提示输入主口令;自动化使用 `YOTTA_MEMORY_PASS`。非 ASCII 口令不要用 Windows 管道(`echo 中文 | ...` 可能改变实际口令)。`view` 复用端口前会校验 memory_home 指纹;跨库或旧版无指纹服务会拒绝复用并提示换端口。
|
|
26
27
|
> 🆕 **v0.16.5(doctor JSON 契约)**:`doctor --json` 顶层新增稳定字段 `schemaVersion` / `encryption` / `migration_required`,同时保留原有 `checks` / `warnings` / `identity` 结构。
|
|
27
28
|
> 🆕 **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`。
|
|
28
29
|
> 🆕 **v0.16.2(首启修复)**:空加密库 `view` 可用恢复钥匙解锁;非 TTY 支持 `--password-stdin`;恢复钥匙支持 `--recovery-key-out <文件>`;`--agent-key-file` 不存在时降级未授权(公共 FACT 可读、私密 fail-closed);空明文库可直接 `migrate` 启用加密;`view` 端口占用给明确提示。
|
|
29
|
-
> 🆕 **v0.16.3
|
|
30
|
-
> `
|
|
30
|
+
> 🆕 **v0.16.3(迁移最短路径)**:明文库第一次转加密推荐交互式:
|
|
31
|
+
> `yotta-memory migrate --recovery-key-out "$env:USERPROFILE\yotta-memory-recovery.key"`
|
|
31
32
|
> 迁移后用 `yotta-memory view` 授权 AI;`view` 与 `yotta-memory key bind <id>` 是等价路径,随后执行 `key status` / `key claim`,再带 `--agent-key-file` 执行 `reindex` 重建加密索引并 `recall` 验证。
|
|
32
33
|
> 🆕 **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`。
|
|
33
34
|
> 🆕 **v0.15.0(MCP 工具分组)**:`serve --tools core|full` 控制 MCP 工具暴露面。`core` 常驻 `context / recall / search / remember`;`full` 保留现有 16 个诊断与维护工具。未指定参数时默认 `full`,保持现有配置兼容。
|
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.
|
|
4
|
+
version: 0.16.7
|
|
5
5
|
license: MIT
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -243,7 +243,9 @@ yotta-memory doctor --json
|
|
|
243
243
|
- 输出 `memory_home: (未设置,默认 ~/.yottamemory)` → 🔒 征得同意后引导设置:问用户用默认还是指定目录(项目级 `<repo>/.yottamemory`、记忆盘等),确认后 AI 执行 `yotta-memory config set memory_home <目录>`,回读 `config get` 验证。
|
|
244
244
|
2. **已有记忆**:目标目录已存在 `facts/` 等子目录或 `index.json` → 直接 recall;全新目录 → 按「便携记忆盘模式 §0.3」初始化。
|
|
245
245
|
3. **私密区为明文(无 `keys/`;`doctor` 显示「加密: 否」)**:首次使用即主动告知风险——明文私密记忆(PREF / BOUND / COMMIT)可被同机任何能读文件的进程或用户直接看到;然后给出《明文库转加密(第一次最短路径)》。口令与迁移必须由用户本人执行,AI 只讲解并负责后续 `key claim`。用户明确拒绝加密时,记录其选择并复述明文风险,不反复打扰。
|
|
246
|
-
4.
|
|
246
|
+
4. **Agent Plugin 开箱装配(v0.16.6)**:插件 `mcp.json` 入口是 `bin/plugin-mcp.js`。宿主已替换身份占位符时优先用替换值;未替换时回退到 `YOTTA_MEMORY_AGENT_ID` + `YOTTA_MEMORY_AGENT_KEY_FILE`,再回退到 `PLUGIN_DATA/identity.json`。都没有时插件以未授权模式启动(公共 FACT 可读、私密 fail-closed)并打印绑定指引。绑定一条命令:用户在 `yotta-memory view` 授权后执行 `yotta-memory key claim <id> --plugin-data <PLUGIN_DATA>`(同时写 key 与 `identity.json`),重启会话即可。
|
|
247
|
+
|
|
248
|
+
5. **私密区已加密(存在 `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 不受影响)。
|
|
247
249
|
|
|
248
250
|
**B. 确认本智能体唯一身份(强制,写私密记忆前必做)**:
|
|
249
251
|
|
|
@@ -278,7 +280,7 @@ yotta-memory doctor --json
|
|
|
278
280
|
| 命令 | 作用 |
|
|
279
281
|
|---|---|
|
|
280
282
|
| `yotta-memory init [--project] [--dir <目录>] [--attach] [--encrypt|--no-encrypt] [--password-stdin] [--recovery-key-out <文件>]` | 初始化(**新建默认加密**:设主口令 + 抄下恢复钥匙;已有库必须用 `--attach`,默认拒绝覆盖;`--no-encrypt` 降级明文;老明文库用 `migrate`;非 TTY 用 `--password-stdin`;恢复钥匙可写文件)|
|
|
281
|
-
| `yotta-memory migrate [--password-stdin] [--recovery-key-out <文件>]` | 明文库 →
|
|
283
|
+
| `yotta-memory migrate [--password-stdin] [--recovery-key-out <文件>]` | 明文库 → 密文迁移(**由用户执行**;首次迁移推荐交互式,非 ASCII 口令勿用 Windows 管道;`view` 授权与 `key bind` 等价关系见《明文库转加密(第一次最短路径)》)|
|
|
282
284
|
| `yotta-memory view [--port 8788] [--host 127.0.0.1]` | 用户查看平台(本机 Web:口令解锁浏览 / 搜索 / 导出全部 AI 记忆 + 授权 / 吊销 AI + 重设口令 + 显示恢复钥匙;已在运行则复用 URL,端口占用给明确提示)|
|
|
283
285
|
| `yotta-memory reset-password [--password <当前> | --recovery-key <钥匙>] [--new-password <新>]` | 重设主口令(忘口令用恢复钥匙)|
|
|
284
286
|
| `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` 重新授权)|
|
|
@@ -318,22 +320,22 @@ yotta-memory doctor --json
|
|
|
318
320
|
|
|
319
321
|
> 适用于 `yotta-memory 0.16.2+`。迁移前先完整备份。
|
|
320
322
|
|
|
321
|
-
1.
|
|
323
|
+
1. **迁移**(推荐交互式;非 ASCII 口令不要走 Windows 管道):
|
|
322
324
|
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
+
```powershell
|
|
326
|
+
yotta-memory migrate --recovery-key-out "$env:USERPROFILE\yotta-memory-recovery.key"
|
|
325
327
|
```
|
|
326
328
|
|
|
327
|
-
|
|
329
|
+
按提示输入主口令。自动化场景按终端选择一种;不要直接把中文占位符粘贴到命令里,也不要把口令写进 `--password` 参数:
|
|
328
330
|
|
|
329
331
|
| 场景 | 命令 |
|
|
330
332
|
|---|---|
|
|
331
|
-
|
|
|
332
|
-
|
|
|
333
|
-
|
|
|
334
|
-
|
|
|
333
|
+
| 交互终端(推荐,支持非 ASCII) | `yotta-memory migrate --recovery-key-out "<钥匙文件>"`,按提示输入口令 |
|
|
334
|
+
| PowerShell 环境变量(自动化) | `$env:YOTTA_MEMORY_PASS='<ASCII-PASSWORD>'; yotta-memory migrate --recovery-key-out "<钥匙文件>"; Remove-Item Env:\YOTTA_MEMORY_PASS` |
|
|
335
|
+
| cmd 环境变量(自动化) | `set "YOTTA_MEMORY_PASS=<ASCII-PASSWORD>"`,执行 `yotta-memory migrate --recovery-key-out "<钥匙文件>"`,最后 `set "YOTTA_MEMORY_PASS="` |
|
|
336
|
+
| 纯 ASCII 管道(非 ASCII 不要用) | `"<ASCII-PASSWORD>" \| yotta-memory migrate --password-stdin --recovery-key-out "<钥匙文件>"` |
|
|
335
337
|
|
|
336
|
-
常见报错:`当前为非交互环境` = 没有 stdin 也没有 `YOTTA_MEMORY_PASS`;`'"..."' is not recognized` = 在 `cmd.exe` 里用了 PowerShell
|
|
338
|
+
常见报错:`当前为非交互环境` = 没有 stdin 也没有 `YOTTA_MEMORY_PASS`;`'"..."' is not recognized` = 在 `cmd.exe` 里用了 PowerShell 的管道写法。Windows 管道可能按控制台代码页编码非 ASCII 字符,导致实际入库口令与输入口令不一致;因此非 ASCII 口令请使用交互终端或环境变量,不要使用 `echo 中文 | ...`。若迁移后 `view` 报 `口令错误`,优先用恢复钥匙执行 `yotta-memory reset-password --recovery-key "<钥匙文件>"` 重设,不要继续猜口令。
|
|
337
339
|
|
|
338
340
|
恢复钥匙文件必须离线保存,不要和记忆库或备份放在一起。
|
|
339
341
|
|
package/USER_GUIDE.md
CHANGED
|
@@ -136,11 +136,11 @@ statement: 本周完成发布
|
|
|
136
136
|
|
|
137
137
|
1. 迁移:
|
|
138
138
|
|
|
139
|
-
```
|
|
140
|
-
|
|
139
|
+
```powershell
|
|
140
|
+
yotta-memory migrate --recovery-key-out "$env:USERPROFILE\yotta-memory-recovery.key"
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
交互终端按提示输入口令;非 ASCII 口令请使用交互输入。非 TTY 自动化可使用 `YOTTA_MEMORY_PASS`(PowerShell / cmd 语法不同),但不要使用 `echo 中文 | ...`,Windows 管道可能改变实际口令。不要把口令写进 `--password` 参数。若迁移后 `view` 报口令错误,用恢复钥匙执行 `yotta-memory reset-password --recovery-key "<钥匙文件>"` 重设。
|
|
144
144
|
|
|
145
145
|
2. 授权 AI(二选一,等价):
|
|
146
146
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// yotta-memory(元忆)插件 MCP 启动器(v0.16.6)
|
|
3
|
+
// 宿主(Codex / Agent Plugins)只替换 ${PLUGIN_ROOT} 与注入 PLUGIN_ROOT / PLUGIN_DATA,
|
|
4
|
+
// 不替换 mcp.json 里的 ${AGENT_ID} / ${AGENT_KEY_FILE}。本启动器按优先级解析身份:
|
|
5
|
+
// 1) 宿主已替换的 --agent-id / --agent-key-file
|
|
6
|
+
// 2) YOTTA_MEMORY_AGENT_ID + YOTTA_MEMORY_AGENT_KEY_FILE 环境变量
|
|
7
|
+
// 3) PLUGIN_DATA/identity.json(agent_id / key_file;key_file 缺省 <PLUGIN_DATA>/.yotta-memory-agent-key)
|
|
8
|
+
// 4) 都没有 -> 以未授权模式启动,输出可执行的绑定指引(不出现字面量占位符)
|
|
9
|
+
// 绑定路径:用户在 yotta-memory view 授权后,AI 执行
|
|
10
|
+
// yotta-memory key claim <agent_id> --to <PLUGIN_DATA>
|
|
11
|
+
// 或写入 <PLUGIN_DATA>/identity.json。
|
|
12
|
+
'use strict';
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { spawn } = require('child_process');
|
|
16
|
+
|
|
17
|
+
const AGENT_KEY_FILENAME = '.yotta-memory-agent-key';
|
|
18
|
+
const PLACEHOLDER_RE = /^\$\{[A-Z_]+\}$/;
|
|
19
|
+
|
|
20
|
+
function cleanValue(value) {
|
|
21
|
+
const v = String(value == null ? '' : value).trim();
|
|
22
|
+
if (!v || PLACEHOLDER_RE.test(v)) return '';
|
|
23
|
+
return v;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function readIdentityFile(pluginData) {
|
|
27
|
+
if (!pluginData) return null;
|
|
28
|
+
const file = path.join(pluginData, 'identity.json');
|
|
29
|
+
if (!fs.existsSync(file)) return null;
|
|
30
|
+
try {
|
|
31
|
+
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
32
|
+
const id = cleanValue(data.agent_id || data.agentId || '');
|
|
33
|
+
if (!id) return null;
|
|
34
|
+
const keyFile = cleanValue(data.key_file || data.keyFile || '') ||
|
|
35
|
+
path.join(pluginData, AGENT_KEY_FILENAME);
|
|
36
|
+
return { id: id, keyFile: keyFile, source: 'plugin-data' };
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolvePluginIdentity(opts, env) {
|
|
43
|
+
opts = opts || {};
|
|
44
|
+
env = env || process.env;
|
|
45
|
+
const hostId = cleanValue(opts.agentId);
|
|
46
|
+
const hostKey = cleanValue(opts.keyFile);
|
|
47
|
+
if (hostId && hostKey) {
|
|
48
|
+
return { id: hostId, keyFile: hostKey, source: 'host-args' };
|
|
49
|
+
}
|
|
50
|
+
const envId = cleanValue(env.YOTTA_MEMORY_AGENT_ID);
|
|
51
|
+
const envKey = cleanValue(env.YOTTA_MEMORY_AGENT_KEY_FILE);
|
|
52
|
+
if (envId && envKey) {
|
|
53
|
+
return { id: envId, keyFile: envKey, source: 'env' };
|
|
54
|
+
}
|
|
55
|
+
const fromData = readIdentityFile(cleanValue(env.PLUGIN_DATA));
|
|
56
|
+
if (fromData) return fromData;
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseArgs(argv) {
|
|
61
|
+
const rest = [];
|
|
62
|
+
const opts = { agentId: '', keyFile: '' };
|
|
63
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
64
|
+
const a = argv[i];
|
|
65
|
+
if (a === '--agent-id') { opts.agentId = argv[++i]; continue; }
|
|
66
|
+
if (a === '--agent-key-file') { opts.keyFile = argv[++i]; continue; }
|
|
67
|
+
rest.push(a);
|
|
68
|
+
}
|
|
69
|
+
return { rest: rest, opts: opts };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function guidanceText(pluginData) {
|
|
73
|
+
const dataDir = pluginData || '<PLUGIN_DATA>';
|
|
74
|
+
return [
|
|
75
|
+
'元忆插件 MCP 尚未装配智能体身份:宿主未替换 mcp.json 中的身份占位符,且未找到环境变量或 identity.json。',
|
|
76
|
+
'当前以未授权模式启动:公共 FACT 可读,私密记忆(PREF / BOUND / COMMIT)fail-closed。',
|
|
77
|
+
'绑定步骤(用户授权后由 AI 执行):',
|
|
78
|
+
' 1) 用户在 yotta-memory view 中授权本智能体;',
|
|
79
|
+
' 2) AI 执行 yotta-memory key claim <agent_id> --to ' + dataDir,
|
|
80
|
+
' 或写入 ' + path.join(dataDir, 'identity.json') + ':{"agent_id":"<agent_id>"}',
|
|
81
|
+
' 3) 重启会话后插件即可读取私密记忆。',
|
|
82
|
+
].join('\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function main() {
|
|
86
|
+
const parsed = parseArgs(process.argv.slice(2));
|
|
87
|
+
const ident = resolvePluginIdentity(parsed.opts, process.env);
|
|
88
|
+
const cli = path.join(__dirname, 'yotta-memory.js');
|
|
89
|
+
const args = [cli].concat(parsed.rest);
|
|
90
|
+
if (ident) {
|
|
91
|
+
args.push('--agent-id', ident.id, '--agent-key-file', ident.keyFile);
|
|
92
|
+
} else {
|
|
93
|
+
process.stderr.write(guidanceText(process.env.PLUGIN_DATA) + '\n');
|
|
94
|
+
}
|
|
95
|
+
const child = spawn(process.execPath, args, { stdio: 'inherit' });
|
|
96
|
+
child.on('exit', function (code, signal) {
|
|
97
|
+
if (signal) process.kill(process.pid, signal);
|
|
98
|
+
else process.exit(code == null ? 0 : code);
|
|
99
|
+
});
|
|
100
|
+
child.on('error', function (err) {
|
|
101
|
+
process.stderr.write('元忆插件 MCP 启动失败: ' + (err && err.message ? err.message : String(err)) + '\n');
|
|
102
|
+
process.exit(2);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
resolvePluginIdentity: resolvePluginIdentity,
|
|
108
|
+
guidanceText: guidanceText,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (require.main === module) main();
|
package/bin/yotta-memory.js
CHANGED
|
@@ -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.
|
|
28
|
+
const VERSION = '0.16.7';
|
|
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 <钥匙></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/<agent_id>.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{'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[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
|
|
@@ -78,6 +78,11 @@ function userRoot() {
|
|
|
78
78
|
function projectRoot() {
|
|
79
79
|
return path.join(process.cwd(), '.yottamemory');
|
|
80
80
|
}
|
|
81
|
+
function memoryRootId(root) {
|
|
82
|
+
let resolved = path.resolve(String(root));
|
|
83
|
+
if (process.platform === 'win32') resolved = resolved.toLowerCase();
|
|
84
|
+
return crypto.createHash('sha256').update(resolved, 'utf8').digest('hex');
|
|
85
|
+
}
|
|
81
86
|
// 唯一化记忆库根:projectRoot 与 userRoot 可能指向同一目录(如 cwd=home 或其父时),
|
|
82
87
|
// 若不唯一化,recall/context 等会对同一索引遍历两次 -> 同一条记忆重复展示(v0.6.5 修复)。
|
|
83
88
|
function memoryRoots() {
|
|
@@ -336,7 +341,11 @@ function runtimeValidateTarballEntries(entries) {
|
|
|
336
341
|
}
|
|
337
342
|
function runtimeListTarballEntries(tarball) {
|
|
338
343
|
const tarBin = process.env.YOTTA_RUNTIME_TAR || 'tar';
|
|
339
|
-
const listed = child_process.spawnSync(tarBin, ['-tzf', tarball], {
|
|
344
|
+
const listed = child_process.spawnSync(tarBin, ['-tzf', path.basename(tarball)], {
|
|
345
|
+
cwd: path.dirname(tarball),
|
|
346
|
+
encoding: 'utf8',
|
|
347
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
348
|
+
});
|
|
340
349
|
if (listed.error) throw new Error('无法执行 tar: ' + listed.error.message);
|
|
341
350
|
if (listed.status !== 0) throw new Error('读取 runtime tarball 失败: ' + String(listed.stderr || listed.stdout || '').trim());
|
|
342
351
|
return String(listed.stdout || '').split(/\r?\n/).filter(Boolean);
|
|
@@ -344,7 +353,8 @@ function runtimeListTarballEntries(tarball) {
|
|
|
344
353
|
function runtimeExtractTarball(tarball, tempDir) {
|
|
345
354
|
runtimeValidateTarballEntries(runtimeListTarballEntries(tarball));
|
|
346
355
|
const tarBin = process.env.YOTTA_RUNTIME_TAR || 'tar';
|
|
347
|
-
const
|
|
356
|
+
const relativeTarball = path.relative(tempDir, tarball).replace(/\\/g, '/');
|
|
357
|
+
const result = child_process.spawnSync(tarBin, ['-xzf', relativeTarball], { cwd: tempDir, encoding: 'utf8' });
|
|
348
358
|
if (result.error) throw new Error('无法执行 tar: ' + result.error.message);
|
|
349
359
|
if (result.status !== 0) throw new Error('解压 runtime tarball 失败: ' + String(result.stderr || result.stdout || '').trim());
|
|
350
360
|
const packaged = path.join(tempDir, 'package');
|
|
@@ -1142,9 +1152,16 @@ function resolveAgentKeyLocation(owner, opts) {
|
|
|
1142
1152
|
opts = opts || {};
|
|
1143
1153
|
const explicitHome = String(opts.to || '').trim();
|
|
1144
1154
|
const explicitFile = String(opts.agentKeyFile || '').trim();
|
|
1155
|
+
const explicitPluginData = String(opts.pluginData || '').trim();
|
|
1145
1156
|
if (explicitHome && explicitFile) {
|
|
1146
1157
|
return { ok: false, error: '--to 与 --agent-key-file 不能同时使用。' };
|
|
1147
1158
|
}
|
|
1159
|
+
if (explicitPluginData && (explicitHome || explicitFile)) {
|
|
1160
|
+
return { ok: false, error: '--plugin-data 不能与 --to / --agent-key-file 同时使用。' };
|
|
1161
|
+
}
|
|
1162
|
+
if (explicitPluginData) {
|
|
1163
|
+
return { ok: true, file: agentKeyFilePath(explicitPluginData), source: 'option:--plugin-data' };
|
|
1164
|
+
}
|
|
1148
1165
|
if (explicitFile) {
|
|
1149
1166
|
return { ok: true, file: path.resolve(explicitFile), source: 'option:--agent-key-file' };
|
|
1150
1167
|
}
|
|
@@ -4557,11 +4574,27 @@ function keyClaimCore(root, owner, opts) {
|
|
|
4557
4574
|
return { error: true, text: '写入宿主 key 文件失败:' + e.message + '。pending 文件保留,可重试。' };
|
|
4558
4575
|
}
|
|
4559
4576
|
removePendingAgentKey(root, owner);
|
|
4577
|
+
let identityFile = '';
|
|
4578
|
+
if (opts && opts.pluginData) {
|
|
4579
|
+
try {
|
|
4580
|
+
const dataDir = path.resolve(String(opts.pluginData));
|
|
4581
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
4582
|
+
identityFile = path.join(dataDir, 'identity.json');
|
|
4583
|
+
const tempIdentity = identityFile + '.tmp-' + process.pid + '-' + crypto.randomBytes(4).toString('hex');
|
|
4584
|
+
fs.writeFileSync(tempIdentity, JSON.stringify({ agent_id: owner }, null, 2) + '\n', { encoding: 'utf8' });
|
|
4585
|
+
try { fs.renameSync(tempIdentity, identityFile); }
|
|
4586
|
+
catch (e) { fs.rmSync(identityFile, { force: true }); fs.renameSync(tempIdentity, identityFile); }
|
|
4587
|
+
} catch (e) {
|
|
4588
|
+
identityFile = '';
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
4591
|
+
const identityLine = identityFile ? '\n插件身份文件: ' + identityFile : '';
|
|
4560
4592
|
return {
|
|
4561
4593
|
error: false,
|
|
4562
4594
|
targetFile: targetFile,
|
|
4595
|
+
identityFile: identityFile,
|
|
4563
4596
|
source: location.source,
|
|
4564
|
-
text: '已领取 ' + owner + ' 的 agent_key。\n目标文件: ' + targetFile + '\n发现规则: ' + location.source + '\npending 文件已删除。',
|
|
4597
|
+
text: '已领取 ' + owner + ' 的 agent_key。\n目标文件: ' + targetFile + identityLine + '\n发现规则: ' + location.source + '\npending 文件已删除。',
|
|
4565
4598
|
};
|
|
4566
4599
|
}
|
|
4567
4600
|
|
|
@@ -4645,7 +4678,7 @@ function cmdKeyRevoke(id) {
|
|
|
4645
4678
|
|
|
4646
4679
|
function cmdKeyClaim(id, opts) {
|
|
4647
4680
|
const root = userRoot();
|
|
4648
|
-
if (!id) { console.error('请指定 <id>:yotta-memory key claim <id> [--to <AI_HOME> | --agent-key-file <文件>]'); process.exit(2); }
|
|
4681
|
+
if (!id) { console.error('请指定 <id>:yotta-memory key claim <id> [--to <AI_HOME> | --plugin-data <PLUGIN_DATA> | --agent-key-file <文件>]'); process.exit(2); }
|
|
4649
4682
|
const r = keyClaimCore(root, id, opts);
|
|
4650
4683
|
console.log(r.text);
|
|
4651
4684
|
if (r.error) process.exit(2);
|
|
@@ -4653,7 +4686,7 @@ function cmdKeyClaim(id, opts) {
|
|
|
4653
4686
|
|
|
4654
4687
|
function cmdKeyStatus(id, opts) {
|
|
4655
4688
|
const root = userRoot();
|
|
4656
|
-
if (!id) { console.error('请指定 <id>:yotta-memory key status <id> [--to <AI_HOME> | --agent-key-file <文件>]'); process.exit(2); }
|
|
4689
|
+
if (!id) { console.error('请指定 <id>:yotta-memory key status <id> [--to <AI_HOME> | --plugin-data <PLUGIN_DATA> | --agent-key-file <文件>]'); process.exit(2); }
|
|
4657
4690
|
const r = keyStatusCore(root, id, opts);
|
|
4658
4691
|
console.log(r.text);
|
|
4659
4692
|
if (r.error) process.exit(2);
|
|
@@ -4748,7 +4781,7 @@ function viewServerCore(root, port, host, opts) {
|
|
|
4748
4781
|
return;
|
|
4749
4782
|
}
|
|
4750
4783
|
if (req.method === 'POST' && pathname === '/api/status') {
|
|
4751
|
-
return json(200, { encrypted: isEncrypted(root), unlocked: !!session.umk, version: VERSION });
|
|
4784
|
+
return json(200, { encrypted: isEncrypted(root), unlocked: !!session.umk, version: VERSION, rootId: memoryRootId(root) });
|
|
4752
4785
|
}
|
|
4753
4786
|
if (req.method === 'POST' && pathname === '/api/unlock') {
|
|
4754
4787
|
return readBody(function (d) {
|
|
@@ -4909,14 +4942,23 @@ function probeViewStatus(host, port) {
|
|
|
4909
4942
|
res.on('end', function () {
|
|
4910
4943
|
try {
|
|
4911
4944
|
const parsed = JSON.parse(data);
|
|
4912
|
-
|
|
4945
|
+
if (!parsed.version || typeof parsed.unlocked !== 'boolean') {
|
|
4946
|
+
resolve({ detected: false });
|
|
4947
|
+
return;
|
|
4948
|
+
}
|
|
4949
|
+
resolve({
|
|
4950
|
+
detected: true,
|
|
4951
|
+
rootId: typeof parsed.rootId === 'string' ? parsed.rootId : '',
|
|
4952
|
+
version: String(parsed.version),
|
|
4953
|
+
unlocked: !!parsed.unlocked,
|
|
4954
|
+
});
|
|
4913
4955
|
} catch (e) {
|
|
4914
|
-
resolve(false);
|
|
4956
|
+
resolve({ detected: false });
|
|
4915
4957
|
}
|
|
4916
4958
|
});
|
|
4917
4959
|
});
|
|
4918
|
-
req.on('timeout', function () { req.destroy(); resolve(false); });
|
|
4919
|
-
req.on('error', function () { resolve(false); });
|
|
4960
|
+
req.on('timeout', function () { req.destroy(); resolve({ detected: false }); });
|
|
4961
|
+
req.on('error', function () { resolve({ detected: false }); });
|
|
4920
4962
|
req.end(body);
|
|
4921
4963
|
});
|
|
4922
4964
|
}
|
|
@@ -4932,11 +4974,18 @@ async function cmdView(opts) {
|
|
|
4932
4974
|
const port = opts.port || 8788;
|
|
4933
4975
|
const state = await probeViewPort(host, port);
|
|
4934
4976
|
if (state === 'open') {
|
|
4935
|
-
const
|
|
4936
|
-
if (
|
|
4977
|
+
const status = await probeViewStatus(host, port);
|
|
4978
|
+
if (status.detected && status.rootId === memoryRootId(root)) {
|
|
4937
4979
|
console.log('检测到已在运行的 yotta-memory view: http://' + host + ':' + port + '(无需重复启动)');
|
|
4938
4980
|
return;
|
|
4939
4981
|
}
|
|
4982
|
+
if (status.detected) {
|
|
4983
|
+
const reason = status.rootId
|
|
4984
|
+
? '该服务属于另一个 memory_home(rootId 不匹配)'
|
|
4985
|
+
: '该服务是旧版 view,未提供 memory_home 指纹(rootId)';
|
|
4986
|
+
console.error('检测到端口 ' + host + ':' + port + ' 上的 yotta-memory view 无法复用:' + reason + '。请关闭该进程,或使用 --port <其它端口>。');
|
|
4987
|
+
process.exit(2);
|
|
4988
|
+
}
|
|
4940
4989
|
console.error('端口已被占用: ' + host + ':' + port + '。请关闭占用进程,或使用 --port <其它端口>。');
|
|
4941
4990
|
process.exit(2);
|
|
4942
4991
|
}
|
|
@@ -7173,10 +7222,10 @@ function usage() {
|
|
|
7173
7222
|
['token', '生成/列出/吊销访问 token(new --agent / list / revoke --agent)']
|
|
7174
7223
|
]],
|
|
7175
7224
|
['加密与安全', [
|
|
7176
|
-
['migrate', '把明文库迁移为密文(需主口令;空明文库同样可用;--password-stdin
|
|
7177
|
-
['view', '启动用户查看平台(--port/--host
|
|
7225
|
+
['migrate', '把明文库迁移为密文(需主口令;空明文库同样可用;--password-stdin 仅用于纯 ASCII 管道;--recovery-key-out <文件> 写恢复钥匙;首次迁移推荐交互式:yotta-memory migrate --recovery-key-out <文件>;非 ASCII 口令请交互输入,勿用 echo 中文管道;迁移后授权二选一:推荐 yotta-memory view,等价 CLI 为 yotta-memory key bind <id>)'],
|
|
7226
|
+
['view', '启动用户查看平台(--port/--host;复用前校验 memory_home 指纹,跨库或旧版无指纹服务会拒绝复用;空加密库可用恢复钥匙校验主口令)'],
|
|
7178
7227
|
['reset-password', '重设主口令(忘口令用恢复钥匙)'],
|
|
7179
|
-
['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
|
|
7228
|
+
['key', '管理 agent_key binding(list / bind <id> / rotate <id> / claim <id> [--to <AI_HOME> | --plugin-data <PLUGIN_DATA> | --agent-key-file <文件>] / status <id> [--to <AI_HOME> | --plugin-data <PLUGIN_DATA> | --agent-key-file <文件>] / revoke <id>;bind/rotate 需主口令或恢复钥匙;--plugin-data 供 Agent Plugin 一条命令绑定插件身份)'],
|
|
7180
7229
|
['config', '查看/设置配置(get;set memory_home <目录> / backup_dir <目录> / embedding_cmd <命令> / embedding_timeout <毫秒> / maintain_* 阈值与半衰 / consolidate_* 参数)']
|
|
7181
7230
|
]],
|
|
7182
7231
|
['平台与服务', [
|
|
@@ -7206,7 +7255,7 @@ async function main() {
|
|
|
7206
7255
|
if (!args.length) { usage(); return; }
|
|
7207
7256
|
const opts = {};
|
|
7208
7257
|
const positional = [];
|
|
7209
|
-
const valueOpts = new Set(['--type', '--limit', '--days', '--out', '--owner', '--agent', '--agent-id', '--agent-key', '--agent-key-file', '--threshold', '--scope', '--host', '--port', '--dir', '--name', '--user', '--relationship', '--source', '--weight', '--budget', '--password', '--new-password', '--recovery-key', '--recovery-key-out', '--reason', '--merge', '--model', '--subject', '--embedding', '--focus', '--embedding-timeout', '--min-age', '--min-idle', '--max-utility', '--min-group', '--period', '--to', '--id', '--time', '--tools', '--mcp-config', '--skill-dir']);
|
|
7258
|
+
const valueOpts = new Set(['--type', '--limit', '--days', '--out', '--owner', '--agent', '--agent-id', '--agent-key', '--agent-key-file', '--plugin-data', '--threshold', '--scope', '--host', '--port', '--dir', '--name', '--user', '--relationship', '--source', '--weight', '--budget', '--password', '--new-password', '--recovery-key', '--recovery-key-out', '--reason', '--merge', '--model', '--subject', '--embedding', '--focus', '--embedding-timeout', '--min-age', '--min-idle', '--max-utility', '--min-group', '--period', '--to', '--id', '--time', '--tools', '--mcp-config', '--skill-dir']);
|
|
7210
7259
|
for (let i = 0; i < args.length; i++) {
|
|
7211
7260
|
const a = args[i];
|
|
7212
7261
|
if (a === '--version' || a === '-v') { console.log(VERSION); return; }
|
|
@@ -7280,6 +7329,7 @@ async function main() {
|
|
|
7280
7329
|
else if (a === '--min-group') opts.minGroup = parseInt(v, 10) || 0;
|
|
7281
7330
|
else if (a === '--period') opts.period = parseInt(v, 10) || 0;
|
|
7282
7331
|
else if (a === '--to') opts.to = v;
|
|
7332
|
+
else if (a === '--plugin-data') opts.pluginData = v;
|
|
7283
7333
|
else if (a === '--id') opts.id = v;
|
|
7284
7334
|
else if (a === '--time') opts.time = v;
|
|
7285
7335
|
else if (a === '--tools') opts.toolProfile = v;
|
|
@@ -7588,6 +7638,7 @@ module.exports = {
|
|
|
7588
7638
|
backupDoctorCore: backupDoctorCore,
|
|
7589
7639
|
backupRestoreCore: backupRestoreCore,
|
|
7590
7640
|
backupDrillCore: backupDrillCore,
|
|
7641
|
+
memoryRootId: memoryRootId,
|
|
7591
7642
|
viewServerCore: viewServerCore,
|
|
7592
7643
|
|
|
7593
7644
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yottameta/yotta-memory",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.7",
|
|
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
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
**明文库第一次转加密**:
|
|
12
12
|
|
|
13
|
-
```
|
|
14
|
-
|
|
13
|
+
```powershell
|
|
14
|
+
yotta-memory migrate --recovery-key-out "$env:USERPROFILE\yotta-memory-recovery.key"
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
按提示输入主口令;非 ASCII 口令请交互输入,非 TTY 自动化可使用 `YOTTA_MEMORY_PASS`。不要使用 `echo 中文 | ...`,Windows 管道可能改变实际口令;若迁移后 `view` 报口令错误,用恢复钥匙 `reset-password` 重设。迁移后授权二选一(等价):推荐 `yotta-memory view` 页面授权;高级用户可 `yotta-memory key bind <id>`。授权并 `key claim` 后,带 `--agent-key-file` 执行 `yotta-memory reindex` 重建每 owner 加密索引,再做 `recall` 验证。
|
|
18
18
|
|
|
19
19
|
## 2.1 非 TTY / GUI 宿主怎么初始化?
|
|
20
20
|
不要依赖交互提示。用 `--password-stdin` 从管道读主口令(不进 argv),或用 `YOTTA_MEMORY_PASS`;恢复钥匙用 `--recovery-key-out <文件>` 写文件,避免 GUI 宿主吞掉 stdout。非 TTY 且未提供口令时会明确提示改用哪种方式,不会静默「已取消」。
|