@yottameta/yotta-memory 0.24.0 → 0.24.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 +15 -0
- package/README.md +3 -1
- package/README.zh-CN.md +3 -1
- package/SKILL.md +5 -1
- package/USER_GUIDE.md +15 -0
- package/bin/yotta-memory.js +223 -13
- package/package.json +1 -1
- package/references/faq.md +3 -0
- package/references/protocol.md +3 -1
- package/skill-manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.24.3 (2026-11-14)
|
|
4
|
+
|
|
5
|
+
- **CI 精度回归**:将命中打点后的 mtime 断言收紧到 Node 原生精度边界内(纳秒偏差 < 5µs)并保留可见毫秒不变;避免 Linux 上 `utimesSync` 的亚微秒浮点误差把正确行为误判为失败。
|
|
6
|
+
|
|
7
|
+
## v0.24.2 (2026-11-14)
|
|
8
|
+
|
|
9
|
+
- **跨平台修复**:Windows profile 绝对路径(如 `C:\Users\...\serve-profile.json`)在 Linux CI / 非 Windows 环境生成任务 XML 时不再被 `path.resolve()` 误拼成仓库内相对路径;新增 host-independent profile 路径解析并保留 XML 回归。
|
|
10
|
+
|
|
11
|
+
## v0.24.1 (2026-11-14)
|
|
12
|
+
|
|
13
|
+
- **自启服务 profile**:`lan enable` 在目标用户上下文解析并写入 `serve-profile.json`(memoryHome / runtimeHome / configDir / host / port / userId),Windows 任务 XML、Startup 兜底、systemd unit 与 crontab 均显式引用该 profile,不再依赖 S4U 启动环境的 `os.homedir()`。
|
|
14
|
+
- **serve fail-closed**:新增 `serve --profile <文件>`;启动时校验 profile schema、绝对路径、memoryHome / runtimeHome 是否存在、userId 是否匹配,并原子覆盖进程环境。缺文件、空库、错误用户或非法路径均拒绝启动并给出中文提示。
|
|
15
|
+
- **状态与迁移**:`lan status` 显示 profile 的 memoryHome / runtimeHome,与当前配置比较漂移;旧任务缺少 `--profile` 时提示重新执行 `lan enable`。重新执行 `lan enable` 即可迁移旧任务。
|
|
16
|
+
- **验证**:新增 Windows task / Startup / Linux unit / crontab profile 回归,以及 `serve --profile` 的路径固化、缺库、错用户、CLI 缺文件用例;全量测试覆盖正常与失败路径。
|
|
17
|
+
|
|
3
18
|
## v0.24.0 (2026-11-11)
|
|
4
19
|
|
|
5
20
|
- **备份可靠性**:`backup status` 改为按 manifest `created` 解析时间排序,超过当前时间 15 分钟的备份标记 `future_timestamp` 并排除;manifest 缺失 / 不完整 / created 无效不参与健康判定;新增 `latest_valid` / `config_last_success` / `effective_last_success` / `source` / `anomalies`,`ensure-daily` 与默认备份体检复用同一选择逻辑。
|
package/README.md
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
|
|
34
34
|
> 📖 The user-facing operations manual lives in [USER_GUIDE.md](USER_GUIDE.md).
|
|
35
35
|
> 🆕 **v0.24.0 (structural capacity)**: `backup status` now selects valid manifest times and isolates future-dated anomalies; `index --stats` adds read-only field/byte analysis; index v5 uses gzip year shards while remaining v4-compatible; new writes use `yyyy/mm/dd` buckets with `layout plan/apply/rollback`; `doctor` adds four type-pressure indicators; `scan` supports exact rule + file + line-hash + expiry policies, while `--policy-candidates` only generates candidates.
|
|
36
|
+
> 🆕 **v0.24.1 (autostart profile)**: `lan enable` writes a per-user `serve-profile.json`; Windows task XML, Startup fallback, systemd and crontab reference that absolute profile. `serve --profile <file>` pins memoryHome / runtimeHome / configDir and fails closed when paths or the Windows user do not match, preventing S4U / no-profile startup from landing on `C:\Users\Default\.yottamemory` and returning remote MCP 401.
|
|
36
37
|
> 🆕 **v0.23.2 (ClawHub storefront)**: the ClawHub listing now ships an English-first bilingual summary and description (a dedicated `clawhub-dist/yotta-memory` publish copy), and the README gained the three usage cases above. Commands, options, output and the memory store format are unchanged.
|
|
37
38
|
> 🆕 **v0.18.1 (distillation provenance + absolute dates + consolidation markers + rule-promotion suggestions + write discipline)**: `distill` now emits typed extraction lists (event / lesson / todo / growth / boundary-rule / other) with per-item `[source: file#L<a>-L<b>]` anchors, measured quality metrics and a skipped/boundary list, plus structured `--json`; `consolidate` rewrites relative dates ("yesterday / last week / this month / this year") into absolute dates based on each entry's `created` date and appends a consolidation marker to archived copies (`--undo` strips it); `maintain --rules` prints read-only "repeat pitfalls → promote to a rule" suggestions (default threshold 3, never writes BOUND automatically); the Memory Doctrine gained a 6-level conflict authority order and three write-discipline rules.
|
|
38
39
|
|
|
@@ -318,7 +319,8 @@ Optional post-upgrade self-check: `yotta-memory config get` (confirm `memory_hom
|
|
|
318
319
|
| `yotta-memory iam <id> [--name <name>] [--user <user>] [--relationship <rel>] [--force]` | Register this agent's unique identity and auto-write the self profile (`agents.json`, ID must be unique) |
|
|
319
320
|
| `yotta-memory token new --agent <id> [--force]` / `token list` / `token revoke --agent <id>` | Create / list / revoke access tokens for agents (registered at `.server/tokens.json`) |
|
|
320
321
|
| `yotta-memory serve [--host 0.0.0.0] [--port 8787] [--no-auth] [--stdio]` | Start the MCP memory engine (streamable HTTP LAN / --stdio local zero-process mode; Bearer token + X-Agent-Id + X-Agent-Key auth) |
|
|
321
|
-
| `yotta-memory lan enable [--onstart] / disable / status` | Autostart management (Windows: scheduled task with an S4U principal — runs silently in the background with no console window; `--onstart` starts at boot without a logon session, registration needs admin; non-admin auto-degrades to user-level silent Startup; Linux: systemd user unit, falls back to user crontab @reboot) |
|
|
322
|
+
| `yotta-memory lan enable [--onstart] / disable / status` | Autostart management (Windows: scheduled task with an S4U principal — runs silently in the background with no console window; `--onstart` starts at boot without a logon session, registration needs admin; non-admin auto-degrades to user-level silent Startup; Linux: systemd user unit, falls back to user crontab @reboot). v0.24.1 persists a per-user `serve-profile.json`; `lan status` reports drift and old tasks lacking `--profile` |
|
|
323
|
+
| `yotta-memory serve --profile <file>` | Read the per-user service profile, pin memory/runtime/config roots, validate absolute paths and the Windows user, and fail closed before opening the MCP port when a path is missing |
|
|
322
324
|
| `yotta-memory maintain [--dry-run] [--apply] [--purge] [--threshold N] [--age N] [--dedup] [--dedup --apply] [--merge A,B]` | Self-organization: archive / forget candidates / confidence-scored dedup / auto-merge high-confidence groups; dry-run by default; `--dedup` is mutually exclusive with archiving |
|
|
323
325
|
| `yotta-memory consolidate [--min-age N] [--min-idle N] [--max-utility N] [--min-group N] [--period N] [--type T] [--model <cmd>] [--apply] [--undo <batch>] [--batches]` | Periodic-summary compression (v0.10.0): group old idle low-value memories into one traceable summary and archive the originals; dry-run by default; `--undo <batch>` rolls a batch back; `--batches` lists batches |
|
|
324
326
|
| `yotta-memory feedback <file> --useful|--useless [--reason <r>] [--undo]` | Usage feedback (useful/useless adjusts weight / confidence / feedback_net; --undo rolls back the last one) |
|
package/README.zh-CN.md
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
> 📖 面向用户的操作手册见 [USER_GUIDE.md](USER_GUIDE.md)。
|
|
25
25
|
> 🆕 **v0.24.0(结构容量整改)**:`backup status` 按 manifest 时间选择有效备份并隔离未来时间异常;新增只读 `index --stats`,索引 v5 使用年份 gzip 分片并兼容 v4;新写入进入 `yyyy/mm/dd` 日级桶,`layout plan/apply/rollback` 支持可回滚迁移;`doctor` 增加类型压力四指标;`scan` 支持精确文件 + 规则 + 行哈希 + 有效期的例外策略,`--policy-candidates` 只生成候选、不自动启用。
|
|
26
|
+
> 🆕 **v0.24.1(自启服务 profile)**:`lan enable` 在目标用户上下文生成 `serve-profile.json`;Windows 计划任务、Startup 兜底、systemd 与 crontab 都引用该绝对 profile。`serve --profile <文件>` 固定 memoryHome / runtimeHome / configDir,并在路径缺失或 Windows 用户不匹配时 fail-closed,避免 S4U / 无用户环境启动落到 `C:\Users\Default\.yottamemory` 空库后返回远端 MCP 401。
|
|
26
27
|
> 🆕 **v0.23.2(ClawHub 门面)**:ClawHub 上的技能卡改为英文优先的双语摘要与描述(新增 `clawhub-dist/yotta-memory` 专属发布副本),README 首屏新增上面三条使用案例;命令、参数、输出与记忆库格式全部不变。
|
|
27
28
|
> 🆕 **v0.20.0(上下文分页 + 画像驱逐修复)**:`memory.hook` 的 PREF 驱逐空转已修复——hook / paging 决策生效时,画像段落按实时可读条目渲染并套用同一套过滤;自我接入档案不进候选、不可驱逐。新增 `context.paging`:仅在显式 `--budget` 时调用,hook 先过滤、paging 再排序与分页;`drop` 始终安全应用,`order` 仅在 `complete:true` 且未截断时应用。`context --json` 新增 `paging` 块,`--explain` 增加 `[paging]` 轨迹。配置与协议见 `references/provider-protocol.md`。
|
|
28
29
|
> 🆕 **v0.18.1(蒸馏溯源 + 日期绝对化 + 巩固标记 + 规则晋升建议 + 写入纪律)**:`distill` 新增分类型提取清单(事件 / 教训 / 待办 / 成长 / 规则边界 / 其他)+ 逐条 `[溯源: 文件#L<a>-L<b>]` + 实测质量指标 + 跳过与边界,`--json` 输出结构化报告;`consolidate` 摘要按条目 `created` 把「昨天 / 上周 / 本月 / 今年」写成绝对日期,归档副本写入巩固标记(`--undo` 剥离还原);`maintain --rules` 只读给出「重复踩坑 → 建议升级为规则」(默认阈值 3,不自动写 BOUND);记忆守则新增冲突权威顺序 6 层与写入纪律 3 条。
|
|
@@ -360,7 +361,8 @@ bash install.sh --agent <智能体名称>
|
|
|
360
361
|
| `yotta-memory iam <id> [--name <显示名>] [--user <用户名>] [--relationship <关系>] [--force]` | 登记本智能体唯一身份并自动落自我档案(`agents.json`,ID 必须唯一;可选扩展显示名 / 用户 / 关系)|
|
|
361
362
|
| `yotta-memory token new --agent <id> [--force]` / `token list` / `token revoke --agent <id>` | 为智能体生成 / 列出 / 吊销访问 token(登记于记忆库 `.server/tokens.json`;同 ID 已被其它来源占用需 `--force` 覆盖,防不同智能体合流)|
|
|
362
363
|
| `yotta-memory serve [--host 0.0.0.0] [--port 8787] [--no-auth] [--stdio]` | 启动 MCP 记忆引擎(streamable HTTP 局域网 / --stdio 本地零进程模式;Bearer token + X-Agent-Id + X-Agent-Key 鉴权)|
|
|
363
|
-
| `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:计划任务 S4U 主体后台静默、不弹窗口;默认登录自启、`--onstart` 开机即启(无登录会话也可启动,注册需管理员),非管理员自动降级用户级 Startup 静默自启;Linux:systemd 用户单元,不可用时自动降级用户 crontab @reboot
|
|
364
|
+
| `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:计划任务 S4U 主体后台静默、不弹窗口;默认登录自启、`--onstart` 开机即启(无登录会话也可启动,注册需管理员),非管理员自动降级用户级 Startup 静默自启;Linux:systemd 用户单元,不可用时自动降级用户 crontab @reboot)。v0.24.1 生成每用户 `serve-profile.json`;`lan status` 检查路径漂移并提示缺少 `--profile` 的旧任务 |
|
|
365
|
+
| `yotta-memory serve --profile <文件>` | 读取每用户服务 profile,固定记忆库 / 运行时 / 配置目录;路径缺失、非法或 Windows 用户不匹配时在监听端口前 fail-closed |
|
|
364
366
|
| `yotta-memory maintain [--dry-run] [--apply] [--purge] [--threshold N] [--age N] [--dedup] [--dedup --apply] [--merge A,B]` / `maintain --capacity [--json]` | 记忆自组织:归档 / 遗忘候选 / 置信度查重 / 自动合并高置信组;默认 dry-run;`--dedup` 与归档互斥。`--capacity` 只读报告容量水位、LRU / LFU 淘汰候选与晋升建议(冷却期与常青条目豁免)|
|
|
365
367
|
| `yotta-memory consolidate [--propose] [--min-age N] [--min-idle N] [--max-utility N] [--min-group N] [--period N] [--type T] [--model <cmd>] [--apply [--yes]] [--undo <batch>] [--batches]` | 周期摘要压缩:同主题旧记忆 → 带溯源摘要 + 原文归档;默认等价 `--propose`(结构化报告,不写盘);`--apply` 需确认(非交互必须 `--yes`);`--undo <batch>` 回滚批次;`--batches` 查批次 |
|
|
366
368
|
| `yotta-memory feedback <文件|主题> --useful|--useless [--reason <原因>] [--undo]` | 使用反馈(useful/useless 调整 weight / confidence / feedback_net;`--undo` 回滚最近一次)|
|
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、事务快照、基线探针、恢复演练、记忆库安全扫描、scan、命中打点、容量水位、--capacity、--propose、上下文压缩审计、context --audit
|
|
4
|
-
version: 0.24.
|
|
4
|
+
version: 0.24.3
|
|
5
5
|
license: MIT
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -14,6 +14,10 @@ license: MIT
|
|
|
14
14
|
新写入进入 `yyyy/mm/dd` 日级桶,`layout plan/apply/rollback` 提供可预演、可回滚迁移;
|
|
15
15
|
`doctor` 增加类型压力四指标(保留 7 天观察窗);`scan` 支持精确文件 + 规则 + 行哈希 +
|
|
16
16
|
有效期的例外策略,`--policy-candidates` 只生成候选、不自动启用。
|
|
17
|
+
- **自启服务 profile(v0.24.1)**:`lan enable` 在目标用户上下文生成
|
|
18
|
+
`serve-profile.json`,Windows 计划任务、Startup 兜底、systemd 与 crontab
|
|
19
|
+
均显式引用该 profile;`serve --profile <文件>` 固化 memory_home / runtime_home /
|
|
20
|
+
config_dir,避免 S4U 或无用户环境启动时 `os.homedir()` 漂移为空库并导致远端 401。
|
|
17
21
|
|
|
18
22
|
## 这是什么
|
|
19
23
|
|
package/USER_GUIDE.md
CHANGED
|
@@ -327,6 +327,21 @@ yotta-memory recall <关键词> --agent <id> --agent-key-file "<AI_HOME>/.yotta-
|
|
|
327
327
|
- `doctor --json` 的 `checks.scale` 新增 `index_logical_bytes` / `index_shards` / `index_format` 与 `type_pressure`(总数 / 180 天陈旧 / 归档候选 / 30 天增长);类型阈值按计划保留观察窗。
|
|
328
328
|
- `scan` 精确例外:`security/scan-policy.json` 中每条必须同时匹配规则、文件、整行 SHA-256 与未过期的 `review_by`;内容变化或过期自动恢复命中。`scan --no-policy` 做全量复核;`scan --policy-candidates --out security/scan-policy.candidates.json` 只生成候选,不自动启用。
|
|
329
329
|
|
|
330
|
+
## 3.12 每用户自启 profile(v0.24.1)
|
|
331
|
+
|
|
332
|
+
- `yotta-memory lan enable` 在目标用户上下文解析 `memoryHome`、`runtimeHome`、
|
|
333
|
+
`configDir`、`host`、`port` 和 `userId`,写到该用户配置目录下的
|
|
334
|
+
`serve-profile.json`。不同用户的路径互不影响,不是代码或全机器共享常量。
|
|
335
|
+
- Windows task XML、Startup 兜底、systemd unit 与 crontab 都引用该绝对 profile
|
|
336
|
+
路径。服务由 S4U 或无用户环境启动时,不再依赖 `os.homedir()`。
|
|
337
|
+
- `yotta-memory serve --profile <文件>` 在监听端口前校验 profile schema、
|
|
338
|
+
绝对路径、memoryHome / runtimeHome 是否存在以及 Windows 用户是否匹配;
|
|
339
|
+
校验失败直接退出,不创建 `C:\Users\Default\.yottamemory` 空库。
|
|
340
|
+
- `yotta-memory lan status` 显示 profile 中的记忆库和运行时路径,并与当前
|
|
341
|
+
设置比较漂移;旧任务命令缺少 `--profile` 时提示重新执行 `lan enable`。
|
|
342
|
+
- 修改 `memory_home`、配置目录或 runtime 路径后,应重新执行
|
|
343
|
+
`yotta-memory lan enable --onstart` 更新 profile 和自启任务。
|
|
344
|
+
|
|
330
345
|
## 4. 便携记忆盘 · 记忆引擎主机篇
|
|
331
346
|
|
|
332
347
|
场景:记忆放在一台主机上(Linux / Windows 均可),本机直接 CLI 读写;局域网内其它主机上的 AI 智能体经 MCP 远程接入。引擎主机只需装 CLI,不需要装任何 AI 智能体。
|
package/bin/yotta-memory.js
CHANGED
|
@@ -26,8 +26,8 @@ const zlib = require('zlib');
|
|
|
26
26
|
const child_process = require('child_process');
|
|
27
27
|
const { AsyncLocalStorage } = require('async_hooks');
|
|
28
28
|
|
|
29
|
-
const VERSION = '0.24.
|
|
30
|
-
const CLI_VALUE_OPTS = 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', '--year', '--evalset', '--k', '--seed', '--bootstrap', '--gate', '--against', '--template', '--path', '--from', '--from-plan', '--batch']);
|
|
29
|
+
const VERSION = '0.24.3';
|
|
30
|
+
const CLI_VALUE_OPTS = 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', '--year', '--evalset', '--k', '--seed', '--bootstrap', '--gate', '--against', '--template', '--path', '--from', '--from-plan', '--batch', '--profile']);
|
|
31
31
|
const CLI_FLAG_OPTS = new Set(['--project', '--all', '--unsafe', '--no-auth', '--stdio', '--onstart', '--from-current', '--restart', '--force', '--attach', '--allow-same-volume', '--verify', '--no-hint', '--encrypt', '--no-encrypt', '--password-stdin', '--json', '--manual', '--skip-schedule', '--useful', '--useless', '--undo', '--dry-run', '--apply', '--purge', '--dedup', '--batches', '--propose', '--audit', '--capacity', '--rules', '--explain', '--semantic', '--runtime', '--ablate', '--timing', '--baseline', '--probe', '--quarantine', '--restore', '--no-usage', '--yes', '--keep-memories', '--keep-identity', '--stats', '--include-flat', '--no-policy', '--policy-candidates']);
|
|
32
32
|
|
|
33
33
|
function helpOption(flag, arg, what, when, caution) {
|
|
@@ -316,8 +316,9 @@ const HELP_MODEL = [
|
|
|
316
316
|
helpSub('status', 'status [--json]', '查看 current 与运行时状态', '排查版本漂移时', [helpOption('--json', '', '输出 JSON', '自动化读取运行时状态时', '')]),
|
|
317
317
|
helpSub('list', 'list [--json]', '列出已安装运行时版本', '盘点可切换版本时', [helpOption('--json', '', '输出 JSON', '自动化盘点运行时版本时', '')]),
|
|
318
318
|
] },
|
|
319
|
-
{ name: 'serve', usage: 'serve [--stdio] [--tools core|full] [--host <地址>] [--port <端口>]', what: '启动 MCP 记忆引擎', when: '宿主需要以 MCP 方式访问元忆时', options: [
|
|
319
|
+
{ name: 'serve', usage: 'serve [--stdio] [--profile <文件>] [--tools core|full] [--host <地址>] [--port <端口>]', what: '启动 MCP 记忆引擎', when: '宿主需要以 MCP 方式访问元忆时', options: [
|
|
320
320
|
helpOption('--stdio', '', '用本地 stdio 模式启动', '本地宿主不需要 HTTP 服务时', ''),
|
|
321
|
+
helpOption('--profile', '<文件>', '读取每用户服务 profile', '自启任务需要固定记忆库与运行时路径时', 'profile 不存在、路径无效或用户不匹配时拒绝启动'),
|
|
321
322
|
helpOption('--tools', 'core|full', '选择工具分组', '想减少工具常驻开销时', 'core 更省上下文,full 暴露全部工具'),
|
|
322
323
|
helpOption('--host', '<地址>', '设置 HTTP 监听地址', '需要远端 MCP 接入时', '监听公网前必须配置鉴权和防火墙'),
|
|
323
324
|
helpOption('--port', '<端口>', '设置 HTTP 监听端口', '默认端口被占用时', ''),
|
|
@@ -11623,6 +11624,7 @@ function cmdServe(opts) {
|
|
|
11623
11624
|
server.listen(port, host, function () {
|
|
11624
11625
|
console.log('yotta-memory 记忆引擎已启动(v' + VERSION + ')');
|
|
11625
11626
|
console.log('URL: http://' + host + ':' + port + '/mcp');
|
|
11627
|
+
if (opts.profile) console.log('服务 profile: ' + path.resolve(String(opts.profile)));
|
|
11626
11628
|
console.log('记忆库: ' + root);
|
|
11627
11629
|
console.log('MCP 协议: 2026-07-28(modern,server/discover)/ 2025-11-25(legacy 握手兼容)');
|
|
11628
11630
|
console.log('工具分组: ' + normalizeMcpToolProfile(opts.toolProfile));
|
|
@@ -11691,18 +11693,144 @@ const LAN_TASK_NAME = 'YottaMemoryServe';
|
|
|
11691
11693
|
const LAN_GEN_MARKER = 'Generated by yotta-memory lan enable';
|
|
11692
11694
|
const LAN_UNIT_NAME = 'yotta-memory-serve.service';
|
|
11693
11695
|
const LAN_CRONTAB_MARKER = '#YTM_LAN:yotta-memory-serve';
|
|
11696
|
+
const SERVE_PROFILE_FILE = 'serve-profile.json';
|
|
11697
|
+
const SERVE_PROFILE_SCHEMA = 1;
|
|
11698
|
+
|
|
11699
|
+
function resolveProfilePath(value) {
|
|
11700
|
+
const text = String(value || '');
|
|
11701
|
+
if (!text) return '';
|
|
11702
|
+
if (path.isAbsolute(text)) return path.resolve(text);
|
|
11703
|
+
if (path.win32.isAbsolute(text)) return path.win32.normalize(text);
|
|
11704
|
+
return path.resolve(text);
|
|
11705
|
+
}
|
|
11706
|
+
|
|
11707
|
+
function lanServeProfilePath(opts) {
|
|
11708
|
+
opts = opts || {};
|
|
11709
|
+
if (opts.profilePath) return resolveProfilePath(opts.profilePath);
|
|
11710
|
+
if (process.env.YOTTA_LAN_PROFILE_FILE) return resolveProfilePath(process.env.YOTTA_LAN_PROFILE_FILE);
|
|
11711
|
+
const configDir = opts.configDir || process.env.YOTTA_MEMORY_CONFIG_DIR || path.dirname(configPath());
|
|
11712
|
+
return path.join(path.resolve(String(configDir)), SERVE_PROFILE_FILE);
|
|
11713
|
+
}
|
|
11714
|
+
|
|
11715
|
+
function lanCurrentUser(opts) {
|
|
11716
|
+
opts = opts || {};
|
|
11717
|
+
if (opts.currentUser) return String(opts.currentUser);
|
|
11718
|
+
if (lanPlatform() === 'win32') return lanWindowsTaskUser(opts);
|
|
11719
|
+
try {
|
|
11720
|
+
const info = os.userInfo();
|
|
11721
|
+
return String(info.username || '');
|
|
11722
|
+
} catch (e) {
|
|
11723
|
+
return '';
|
|
11724
|
+
}
|
|
11725
|
+
}
|
|
11726
|
+
|
|
11727
|
+
function sameLanUser(a, b) {
|
|
11728
|
+
const left = String(a || '').trim().toLowerCase();
|
|
11729
|
+
const right = String(b || '').trim().toLowerCase();
|
|
11730
|
+
return !left || !right || left === right;
|
|
11731
|
+
}
|
|
11732
|
+
|
|
11733
|
+
function lanWriteServeProfile(opts) {
|
|
11734
|
+
opts = opts || {};
|
|
11735
|
+
try {
|
|
11736
|
+
const memoryHome = path.resolve(String(opts.memoryHome || userRoot()));
|
|
11737
|
+
const runtimeHome = path.resolve(String(opts.runtimeHome || runtimeRoot()));
|
|
11738
|
+
const configDir = path.resolve(String(opts.configDir || process.env.YOTTA_MEMORY_CONFIG_DIR || path.dirname(configPath())));
|
|
11739
|
+
const profilePath = lanServeProfilePath(Object.assign({}, opts, { configDir: configDir }));
|
|
11740
|
+
if (!fs.existsSync(memoryHome) || !fs.statSync(memoryHome).isDirectory()) {
|
|
11741
|
+
return { error: true, text: '记忆库不存在或不是目录: ' + memoryHome + '。请先初始化或修正 memory_home。' };
|
|
11742
|
+
}
|
|
11743
|
+
const profile = {
|
|
11744
|
+
schemaVersion: SERVE_PROFILE_SCHEMA,
|
|
11745
|
+
generatedAt: new Date().toISOString(),
|
|
11746
|
+
userId: String(opts.userId || lanCurrentUser(opts)),
|
|
11747
|
+
memoryHome: memoryHome,
|
|
11748
|
+
runtimeHome: runtimeHome,
|
|
11749
|
+
configDir: configDir,
|
|
11750
|
+
host: String(opts.host || '0.0.0.0'),
|
|
11751
|
+
port: parseInt(opts.port, 10) || 8787,
|
|
11752
|
+
};
|
|
11753
|
+
fs.mkdirSync(path.dirname(profilePath), { recursive: true });
|
|
11754
|
+
atomicWriteFileSync(profilePath, JSON.stringify(profile, null, 2) + '\n', 'utf8');
|
|
11755
|
+
return { error: false, profilePath: profilePath, profile: profile };
|
|
11756
|
+
} catch (e) {
|
|
11757
|
+
return { error: true, text: '写入服务 profile 失败: ' + String((e && e.message) || e) };
|
|
11758
|
+
}
|
|
11759
|
+
}
|
|
11760
|
+
|
|
11761
|
+
function loadServeProfile(profilePath, opts) {
|
|
11762
|
+
opts = opts || {};
|
|
11763
|
+
const resolved = resolveProfilePath(profilePath);
|
|
11764
|
+
try {
|
|
11765
|
+
if (!profilePath) return { error: true, text: 'profile 路径不能为空。' };
|
|
11766
|
+
const profile = JSON.parse(String(fs.readFileSync(resolved, 'utf8')).replace(/^\uFEFF/, ''));
|
|
11767
|
+
if (!profile || profile.schemaVersion !== SERVE_PROFILE_SCHEMA) {
|
|
11768
|
+
return { error: true, text: 'profile schemaVersion 无效,需要 ' + SERVE_PROFILE_SCHEMA + '。' };
|
|
11769
|
+
}
|
|
11770
|
+
for (const key of ['memoryHome', 'runtimeHome', 'configDir']) {
|
|
11771
|
+
const value = String(profile[key] || '').trim();
|
|
11772
|
+
if (!value || !path.isAbsolute(value)) {
|
|
11773
|
+
return { error: true, text: 'profile 字段 ' + key + ' 必须是绝对路径。' };
|
|
11774
|
+
}
|
|
11775
|
+
}
|
|
11776
|
+
const memoryHome = path.resolve(String(profile.memoryHome));
|
|
11777
|
+
const runtimeHome = path.resolve(String(profile.runtimeHome));
|
|
11778
|
+
const configDir = path.resolve(String(profile.configDir));
|
|
11779
|
+
if (!fs.existsSync(memoryHome) || !fs.statSync(memoryHome).isDirectory()) {
|
|
11780
|
+
return { error: true, text: 'profile memoryHome 指定的记忆库不存在或不是目录: ' + memoryHome };
|
|
11781
|
+
}
|
|
11782
|
+
if (!fs.existsSync(runtimeHome) || !fs.statSync(runtimeHome).isDirectory()) {
|
|
11783
|
+
return { error: true, text: 'profile runtimeHome 指定的运行时目录不存在或不是目录: ' + runtimeHome };
|
|
11784
|
+
}
|
|
11785
|
+
const currentUser = opts.currentUser || lanCurrentUser(opts);
|
|
11786
|
+
if (!sameLanUser(profile.userId, currentUser)) {
|
|
11787
|
+
return { error: true, text: 'profile userId 与当前用户不匹配: ' + String(profile.userId || '(empty)') + ' != ' + String(currentUser || '(empty)') };
|
|
11788
|
+
}
|
|
11789
|
+
return {
|
|
11790
|
+
error: false,
|
|
11791
|
+
profilePath: resolved,
|
|
11792
|
+
profile: Object.assign({}, profile, {
|
|
11793
|
+
memoryHome: memoryHome,
|
|
11794
|
+
runtimeHome: runtimeHome,
|
|
11795
|
+
configDir: configDir,
|
|
11796
|
+
}),
|
|
11797
|
+
};
|
|
11798
|
+
} catch (e) {
|
|
11799
|
+
return { error: true, text: '无法读取服务 profile ' + resolved + ': ' + String((e && e.message) || e) };
|
|
11800
|
+
}
|
|
11801
|
+
}
|
|
11802
|
+
|
|
11803
|
+
function applyServeProfile(profilePath, opts) {
|
|
11804
|
+
const loaded = loadServeProfile(profilePath, opts);
|
|
11805
|
+
if (loaded.error) return loaded;
|
|
11806
|
+
process.env.YOTTA_MEMORY_HOME = loaded.profile.memoryHome;
|
|
11807
|
+
process.env.YOTTA_MEMORY_RUNTIME_HOME = loaded.profile.runtimeHome;
|
|
11808
|
+
process.env.YOTTA_MEMORY_CONFIG_DIR = loaded.profile.configDir;
|
|
11809
|
+
return loaded;
|
|
11810
|
+
}
|
|
11811
|
+
|
|
11812
|
+
function serveProfileDrift(profile) {
|
|
11813
|
+
if (!profile) return [];
|
|
11814
|
+
const drift = [];
|
|
11815
|
+
if (path.resolve(String(profile.memoryHome || '')) !== path.resolve(userRoot())) drift.push('memoryHome');
|
|
11816
|
+
if (path.resolve(String(profile.runtimeHome || '')) !== path.resolve(runtimeRoot())) drift.push('runtimeHome');
|
|
11817
|
+
if (path.resolve(String(profile.configDir || '')) !== path.resolve(path.dirname(configPath()))) drift.push('configDir');
|
|
11818
|
+
return drift;
|
|
11819
|
+
}
|
|
11820
|
+
|
|
11694
11821
|
function lanTaskRunCmd(opts) {
|
|
11695
11822
|
opts = opts || {};
|
|
11696
11823
|
const host = opts.host || '0.0.0.0';
|
|
11697
11824
|
const port = opts.port || 8787;
|
|
11825
|
+
const profilePath = lanServeProfilePath(opts);
|
|
11698
11826
|
// schtasks /tr 不接受多余内嵌引号:路径无空格不加引号,含空格/引号用 \" 转义(0.5.2 修复)
|
|
11699
11827
|
const q = (p) => /[\s"]/.test(p) ? '\\"' + p.replace(/"/g, '\\"') + '\\"' : p;
|
|
11700
11828
|
const prefix = lanElectronHost(opts) ? 'set "ELECTRON_RUN_AS_NODE=1" && ' : '';
|
|
11701
|
-
return prefix + q(process.execPath) + ' ' + q(runtimeManagedScript()) + ' serve --host ' + host + ' --port ' + port;
|
|
11829
|
+
return prefix + q(process.execPath) + ' ' + q(runtimeManagedScript()) + ' serve --profile ' + q(profilePath) + ' --host ' + host + ' --port ' + port;
|
|
11702
11830
|
}
|
|
11703
11831
|
|
|
11704
11832
|
function lanServeArgs(opts) {
|
|
11705
|
-
return ['serve', '--host', String(opts.host || '0.0.0.0'), '--port', String(opts.port || 8787)];
|
|
11833
|
+
return ['serve', '--profile', lanServeProfilePath(opts), '--host', String(opts.host || '0.0.0.0'), '--port', String(opts.port || 8787)];
|
|
11706
11834
|
}
|
|
11707
11835
|
// generic spawn wrapper: YOTTA_LAN_*_BIN may point to a .js stub (testing/advanced use;
|
|
11708
11836
|
// on Windows run it with the current node interpreter automatically)
|
|
@@ -11773,6 +11901,7 @@ function lanAutostartCmdContent(opts) {
|
|
|
11773
11901
|
opts = opts || {};
|
|
11774
11902
|
const host = opts.host || '0.0.0.0';
|
|
11775
11903
|
const port = opts.port || 8787;
|
|
11904
|
+
const profilePath = lanServeProfilePath(opts);
|
|
11776
11905
|
const q = (p) => '"' + String(p).replace(/"/g, '""') + '"';
|
|
11777
11906
|
// 生成文件用纯 ASCII(避免 cmd/VBS 按系统代码页读取中文注释乱码;VBS 自愈内联同一内容)
|
|
11778
11907
|
// v0.22.5: Electron 宿主下需先设 ELECTRON_RUN_AS_NODE=1,否则拉起桌面程序本体而非 Node。
|
|
@@ -11780,7 +11909,7 @@ function lanAutostartCmdContent(opts) {
|
|
|
11780
11909
|
+ 'chcp 65001 >nul\r\n'
|
|
11781
11910
|
+ 'rem ' + LAN_GEN_MARKER + '; remove with: yotta-memory lan disable (English only, keep ASCII)\r\n'
|
|
11782
11911
|
+ (lanElectronHost(opts) ? 'set "ELECTRON_RUN_AS_NODE=1"\r\n' : '')
|
|
11783
|
-
+ q(process.execPath) + ' ' + q(runtimeManagedScript()) + ' serve --host ' + host + ' --port ' + port + ' >> ' + q(lanLogPath(opts)) + ' 2>&1\r\n';
|
|
11912
|
+
+ q(process.execPath) + ' ' + q(runtimeManagedScript()) + ' serve --profile ' + q(profilePath) + ' --host ' + host + ' --port ' + port + ' >> ' + q(lanLogPath(opts)) + ' 2>&1\r\n';
|
|
11784
11913
|
}
|
|
11785
11914
|
function lanVbsContent(opts) {
|
|
11786
11915
|
// v0.6.3 自愈:VBS 内联 autostart.cmd 内容,启动时若 .cmd 缺失/被清理即就地重建,
|
|
@@ -11893,6 +12022,7 @@ function lanWindowsTaskXml(opts) {
|
|
|
11893
12022
|
const nodePath = opts.nodePath || process.execPath;
|
|
11894
12023
|
const scriptPath = opts.scriptPath || runtimeManagedScript();
|
|
11895
12024
|
const logPath = opts.logPath || lanLogPath(opts);
|
|
12025
|
+
const profilePath = lanServeProfilePath(opts);
|
|
11896
12026
|
const userId = opts.userId || '';
|
|
11897
12027
|
const cmdPath = opts.cmdPath || process.env.ComSpec || 'cmd.exe';
|
|
11898
12028
|
// cmd /c 外层引号:命令首段带引号时必须用 ""..." 包裹(cmd 的 /c 引号规则),
|
|
@@ -11902,7 +12032,7 @@ function lanWindowsTaskXml(opts) {
|
|
|
11902
12032
|
// v0.22.5: Electron 宿主(YottaCode shim)下先 set ELECTRON_RUN_AS_NODE=1,
|
|
11903
12033
|
// 否则任务会拉起桌面程序本体(GUI)而不是 Node 服务。
|
|
11904
12034
|
const envPrefix = lanElectronHost(opts) ? 'set "ELECTRON_RUN_AS_NODE=1" && ' : '';
|
|
11905
|
-
const inner = envPrefix + '"' + xmlEscape(nodePath) + '" "' + xmlEscape(scriptPath) + '" serve --host ' + host + ' --port ' + port
|
|
12035
|
+
const inner = envPrefix + '"' + xmlEscape(nodePath) + '" "' + xmlEscape(scriptPath) + '" serve --profile "' + xmlEscape(profilePath) + '" --host ' + host + ' --port ' + port
|
|
11906
12036
|
+ ' >> "' + xmlEscape(logPath) + '" 2>&1';
|
|
11907
12037
|
const triggerXml = onstart
|
|
11908
12038
|
? '<BootTrigger><Enabled>true</Enabled></BootTrigger>'
|
|
@@ -11937,6 +12067,8 @@ function lanWindowsTaskXml(opts) {
|
|
|
11937
12067
|
}
|
|
11938
12068
|
function lanWindowsEnableCore(opts) {
|
|
11939
12069
|
opts = opts || {};
|
|
12070
|
+
const profileResult = lanWriteServeProfile(opts);
|
|
12071
|
+
if (profileResult.error) return { error: true, phase: 'profile', text: profileResult.text };
|
|
11940
12072
|
const trigger = opts.onstart ? 'onstart' : 'onlogon';
|
|
11941
12073
|
const host = opts.host || '0.0.0.0';
|
|
11942
12074
|
const port = opts.port || 8787;
|
|
@@ -11944,7 +12076,7 @@ function lanWindowsEnableCore(opts) {
|
|
|
11944
12076
|
const scriptPath = opts.scriptPath || runtimeManagedScript();
|
|
11945
12077
|
const logPath = lanLogPath(opts);
|
|
11946
12078
|
const userId = lanWindowsTaskUser(opts);
|
|
11947
|
-
const xml = lanWindowsTaskXml({ trigger: trigger, host: host, port: port, nodePath: nodePath, scriptPath: scriptPath, logPath: logPath, userId: userId, electronHost: opts.electronHost });
|
|
12079
|
+
const xml = lanWindowsTaskXml({ trigger: trigger, host: host, port: port, nodePath: nodePath, scriptPath: scriptPath, logPath: logPath, profilePath: profileResult.profilePath, userId: userId, electronHost: opts.electronHost });
|
|
11948
12080
|
const xmlDir = opts.xmlDir || process.env.YOTTA_LAN_TASK_XML_DIR || os.tmpdir();
|
|
11949
12081
|
const xmlPath = opts.xmlPath || path.join(xmlDir, LAN_TASK_NAME + '-task-' + process.pid + '-' + Date.now() + '.xml');
|
|
11950
12082
|
let failure = null;
|
|
@@ -11962,7 +12094,7 @@ function lanWindowsEnableCore(opts) {
|
|
|
11962
12094
|
if (!failure) {
|
|
11963
12095
|
let removedStartup = [];
|
|
11964
12096
|
try { removedStartup = lanRemoveStartupFiles(opts); } catch (e) { removedStartup = []; }
|
|
11965
|
-
return { error: false, mode: 'task', taskName: LAN_TASK_NAME, trigger: trigger, logPath: logPath, removedStartup: removedStartup };
|
|
12097
|
+
return { error: false, mode: 'task', taskName: LAN_TASK_NAME, trigger: trigger, logPath: logPath, profilePath: profileResult.profilePath, removedStartup: removedStartup };
|
|
11966
12098
|
}
|
|
11967
12099
|
try {
|
|
11968
12100
|
lanInstallStartup(opts);
|
|
@@ -11978,6 +12110,7 @@ function lanWindowsEnableCore(opts) {
|
|
|
11978
12110
|
vbsPath: lanVbsPath(opts),
|
|
11979
12111
|
cmdPath: lanAutostartCmdPath(opts),
|
|
11980
12112
|
logPath: logPath,
|
|
12113
|
+
profilePath: profileResult.profilePath,
|
|
11981
12114
|
};
|
|
11982
12115
|
}
|
|
11983
12116
|
|
|
@@ -11992,7 +12125,9 @@ function lanLinuxUnitPath() { return path.join(lanLinuxSystemdUserDir(), LAN_UNI
|
|
|
11992
12125
|
function lanLinuxSystemctlBin() { return process.env.YOTTA_LAN_SYSTEMCTL_BIN || 'systemctl'; }
|
|
11993
12126
|
function lanLinuxLoginctlBin() { return process.env.YOTTA_LAN_LOGINCTL_BIN || 'loginctl'; }
|
|
11994
12127
|
function lanLinuxExecStart(opts) {
|
|
11995
|
-
return [systemdEscapeArg(process.execPath), systemdEscapeArg(runtimeManagedScript())]
|
|
12128
|
+
return [systemdEscapeArg(process.execPath), systemdEscapeArg(runtimeManagedScript())]
|
|
12129
|
+
.concat(lanServeArgs(opts).map(systemdEscapeArg))
|
|
12130
|
+
.join(' ');
|
|
11996
12131
|
}
|
|
11997
12132
|
function lanLinuxUnitContent(opts) {
|
|
11998
12133
|
// keep comments ASCII (avoid locale/encoding issues); ExecStart uses systemd's own quote rules
|
|
@@ -12022,7 +12157,7 @@ function lanCrontabLine(opts) {
|
|
|
12022
12157
|
const parts = ['@reboot'];
|
|
12023
12158
|
if (lanElectronHost(opts)) parts.push('ELECTRON_RUN_AS_NODE=1');
|
|
12024
12159
|
parts.push(shQuote(process.execPath), shQuote(runtimeManagedScript()));
|
|
12025
|
-
return parts.concat(lanServeArgs(opts)).join(' ') + ' >> ' + shQuote(lanLogPath(opts)) + ' 2>&1 ' + LAN_CRONTAB_MARKER;
|
|
12160
|
+
return parts.concat(lanServeArgs(opts).map(shQuote)).join(' ') + ' >> ' + shQuote(lanLogPath(opts)) + ' 2>&1 ' + LAN_CRONTAB_MARKER;
|
|
12026
12161
|
}
|
|
12027
12162
|
function lanCrontabRead() {
|
|
12028
12163
|
try {
|
|
@@ -12101,6 +12236,12 @@ function lanLinuxRemoveCrontab() {
|
|
|
12101
12236
|
return true;
|
|
12102
12237
|
}
|
|
12103
12238
|
function cmdLanLinuxEnable(opts) {
|
|
12239
|
+
const profileResult = lanWriteServeProfile(opts);
|
|
12240
|
+
if (profileResult.error) {
|
|
12241
|
+
console.error('服务 profile 写入失败,拒绝注册开机自启: ' + profileResult.text);
|
|
12242
|
+
process.exit(1);
|
|
12243
|
+
}
|
|
12244
|
+
opts = Object.assign({}, opts, { profilePath: profileResult.profilePath });
|
|
12104
12245
|
if (lanLinuxHasSystemd()) {
|
|
12105
12246
|
try {
|
|
12106
12247
|
const unit = lanLinuxInstallSystemd(opts);
|
|
@@ -12143,6 +12284,23 @@ function cmdLanLinuxDisable() {
|
|
|
12143
12284
|
}
|
|
12144
12285
|
function cmdLanLinuxStatus() {
|
|
12145
12286
|
let any = false;
|
|
12287
|
+
const profilePath = lanServeProfilePath();
|
|
12288
|
+
if (fs.existsSync(profilePath)) {
|
|
12289
|
+
console.log('服务 profile: ' + profilePath);
|
|
12290
|
+
const loaded = loadServeProfile(profilePath);
|
|
12291
|
+
if (loaded.error) {
|
|
12292
|
+
console.log(' 警告: ' + loaded.text);
|
|
12293
|
+
} else {
|
|
12294
|
+
console.log(' 记忆库: ' + loaded.profile.memoryHome);
|
|
12295
|
+
console.log(' 运行时: ' + loaded.profile.runtimeHome);
|
|
12296
|
+
const drift = serveProfileDrift(loaded.profile);
|
|
12297
|
+
if (drift.length) {
|
|
12298
|
+
console.log(' 警告: 服务 profile 与当前配置漂移(' + drift.join(', ') + '),请重新执行 yotta-memory lan enable');
|
|
12299
|
+
}
|
|
12300
|
+
}
|
|
12301
|
+
} else {
|
|
12302
|
+
console.log('服务 profile: 不存在 ' + profilePath + '(请重新执行 yotta-memory lan enable)');
|
|
12303
|
+
}
|
|
12146
12304
|
const unit = lanLinuxUnitPath();
|
|
12147
12305
|
const unitExists = fs.existsSync(unit) && lanFileHasMarker(unit);
|
|
12148
12306
|
if (unitExists) {
|
|
@@ -12164,7 +12322,13 @@ function cmdLanLinuxStatus() {
|
|
|
12164
12322
|
if (lanCrontabHasOurLine(lines)) {
|
|
12165
12323
|
any = true;
|
|
12166
12324
|
console.log('crontab @reboot: 已启用');
|
|
12167
|
-
for (const l of lines)
|
|
12325
|
+
for (const l of lines) {
|
|
12326
|
+
if (l.indexOf(LAN_CRONTAB_MARKER) === -1) continue;
|
|
12327
|
+
console.log(' ' + l.trim());
|
|
12328
|
+
if (!/--profile(?:\s|$)/.test(l)) {
|
|
12329
|
+
console.log(' 警告: crontab 命令未固定服务 profile;请重新执行 yotta-memory lan enable 修复');
|
|
12330
|
+
}
|
|
12331
|
+
}
|
|
12168
12332
|
} else {
|
|
12169
12333
|
console.log('crontab @reboot: 未启用');
|
|
12170
12334
|
}
|
|
@@ -12177,9 +12341,11 @@ function cmdLanWinEnable(opts) {
|
|
|
12177
12341
|
}
|
|
12178
12342
|
const result = lanWindowsEnableCore(opts);
|
|
12179
12343
|
if (result.mode === 'task') {
|
|
12344
|
+
opts = Object.assign({}, opts, { profilePath: result.profilePath });
|
|
12180
12345
|
const triggerText = result.trigger === 'onstart' ? '开机触发(无需登录)' : '登录触发';
|
|
12181
12346
|
console.log('已注册开机自启: 计划任务 ' + LAN_TASK_NAME + '(' + triggerText + ',后台静默运行,不弹窗口)');
|
|
12182
12347
|
console.log('运行命令: ' + lanTaskRunCmd(opts));
|
|
12348
|
+
console.log('服务 profile: ' + result.profilePath);
|
|
12183
12349
|
console.log('日志: ' + result.logPath);
|
|
12184
12350
|
if (result.removedStartup && result.removedStartup.length) {
|
|
12185
12351
|
console.log('已清理旧 Startup 兜底文件 ' + result.removedStartup.length + ' 个(避免双份自启)');
|
|
@@ -12188,6 +12354,10 @@ function cmdLanWinEnable(opts) {
|
|
|
12188
12354
|
return;
|
|
12189
12355
|
}
|
|
12190
12356
|
if (result.error) {
|
|
12357
|
+
if (result.phase === 'profile') {
|
|
12358
|
+
console.error('服务 profile 写入失败,拒绝注册开机自启: ' + String(result.text || '').split(/\r?\n/)[0]);
|
|
12359
|
+
process.exit(1);
|
|
12360
|
+
}
|
|
12191
12361
|
console.error('计划任务注册失败: ' + String(result.reason || '').split(/\r?\n/)[0]);
|
|
12192
12362
|
console.error('写 Startup 自启失败: ' + String(result.text || '').split(/\r?\n/)[0]);
|
|
12193
12363
|
console.error('请用管理员终端执行 yotta-memory lan enable,或将以下命令手动加入启动项: ' + lanTaskRunCmd(opts));
|
|
@@ -12200,6 +12370,7 @@ function cmdLanWinEnable(opts) {
|
|
|
12200
12370
|
console.log('已启用用户级 Startup 静默自启(无需管理员)');
|
|
12201
12371
|
console.log('启动脚本: ' + result.vbsPath);
|
|
12202
12372
|
console.log('启动命令: ' + result.cmdPath);
|
|
12373
|
+
console.log('服务 profile: ' + result.profilePath);
|
|
12203
12374
|
console.log('日志: ' + result.logPath);
|
|
12204
12375
|
console.log('备注: 服务不会立刻启动,需重新登录后自动启动;如需现在运行请执行 yotta-memory serve');
|
|
12205
12376
|
console.log('提示: 如需改回计划任务(后台静默),请用管理员终端重新执行 yotta-memory lan enable');
|
|
@@ -12236,9 +12407,12 @@ function cmdLanWinStatus() {
|
|
|
12236
12407
|
return;
|
|
12237
12408
|
}
|
|
12238
12409
|
let taskFound = false;
|
|
12410
|
+
const profilePath = lanServeProfilePath();
|
|
12239
12411
|
const q = lanWindowsRunSchtasks({}, ['/query', '/tn', LAN_TASK_NAME, '/fo', 'LIST', '/v']);
|
|
12240
12412
|
if (q.status === 0) {
|
|
12241
12413
|
taskFound = true;
|
|
12414
|
+
const xmlQ = lanWindowsRunSchtasks({}, ['/query', '/tn', LAN_TASK_NAME, '/xml']);
|
|
12415
|
+
const xmlSaysS4U = xmlQ.status === 0 && /<LogonType>\s*S4U\s*<\/LogonType>/i.test(String(xmlQ.stdout || ''));
|
|
12242
12416
|
console.log('计划任务 ' + LAN_TASK_NAME + ': 已启用');
|
|
12243
12417
|
const re = /^(状态|Status|登录模式|Logon Mode|上次运行时间|Last Run Time|上次结果|Last Result|要运行的任务|Task To Run)\s*[:=]\s*(.+)$/;
|
|
12244
12418
|
let logonMode = '';
|
|
@@ -12250,12 +12424,31 @@ function cmdLanWinStatus() {
|
|
|
12250
12424
|
if (/^(登录模式|Logon Mode)$/.test(m[1])) logonMode = m[2];
|
|
12251
12425
|
if (/^(要运行的任务|Task To Run)$/.test(m[1])) taskRun = m[2];
|
|
12252
12426
|
}
|
|
12253
|
-
if (/interactive|交互式/i.test(logonMode)) {
|
|
12427
|
+
if (/interactive|交互式/i.test(logonMode) && !xmlSaysS4U) {
|
|
12254
12428
|
console.log(' 提示: 登录模式为交互式(仅登录时运行,且在登录会话会弹窗口)——这是旧版任务;请重新执行 yotta-memory lan enable 修复为后台静默');
|
|
12255
12429
|
}
|
|
12256
12430
|
if (lanTaskCommandNeedsElectronEnv(taskRun)) {
|
|
12257
12431
|
console.log(' 警告: 任务命令指向 Electron 可执行文件(如 YottaCode.exe)但缺少 ELECTRON_RUN_AS_NODE —— 开机将拉起桌面程序而非服务;请重新执行 yotta-memory lan enable 修复');
|
|
12258
12432
|
}
|
|
12433
|
+
if (taskRun && !/--profile(?:\s|$)/.test(taskRun)) {
|
|
12434
|
+
console.log(' 警告: 任务命令未固定服务 profile,S4U / 无用户环境启动可能落到错误记忆库;请重新执行 yotta-memory lan enable 修复');
|
|
12435
|
+
}
|
|
12436
|
+
if (fs.existsSync(profilePath)) {
|
|
12437
|
+
console.log(' 服务 profile: ' + profilePath);
|
|
12438
|
+
const loaded = loadServeProfile(profilePath);
|
|
12439
|
+
if (loaded.error) {
|
|
12440
|
+
console.log(' 警告: ' + loaded.text);
|
|
12441
|
+
} else {
|
|
12442
|
+
console.log(' 记忆库: ' + loaded.profile.memoryHome);
|
|
12443
|
+
console.log(' 运行时: ' + loaded.profile.runtimeHome);
|
|
12444
|
+
const drift = serveProfileDrift(loaded.profile);
|
|
12445
|
+
if (drift.length) {
|
|
12446
|
+
console.log(' 警告: 服务 profile 与当前配置漂移(' + drift.join(', ') + '),请重新执行 yotta-memory lan enable');
|
|
12447
|
+
}
|
|
12448
|
+
}
|
|
12449
|
+
} else {
|
|
12450
|
+
console.log(' 警告: 服务 profile 不存在: ' + profilePath + ';请重新执行 yotta-memory lan enable');
|
|
12451
|
+
}
|
|
12259
12452
|
} else {
|
|
12260
12453
|
console.log('计划任务 ' + LAN_TASK_NAME + ': 未启用');
|
|
12261
12454
|
}
|
|
@@ -13254,6 +13447,7 @@ async function main() {
|
|
|
13254
13447
|
else if (a === '--id') opts.id = v;
|
|
13255
13448
|
else if (a === '--time') opts.time = v;
|
|
13256
13449
|
else if (a === '--tools') opts.toolProfile = v;
|
|
13450
|
+
else if (a === '--profile') opts.profile = v;
|
|
13257
13451
|
else if (a === '--mcp-config') opts.mcpConfigPaths = (opts.mcpConfigPaths || []).concat(v);
|
|
13258
13452
|
else if (a === '--skill-dir') opts.skillDirs = (opts.skillDirs || []).concat(v);
|
|
13259
13453
|
else if (a === '--year') opts.years = (opts.years || []).concat(v);
|
|
@@ -13296,6 +13490,16 @@ async function main() {
|
|
|
13296
13490
|
console.error('位置参数不足:yotta-memory remember <type> <subject> <statement>');
|
|
13297
13491
|
process.exit(2);
|
|
13298
13492
|
}
|
|
13493
|
+
if (first === 'serve' && opts.profile) {
|
|
13494
|
+
const applied = applyServeProfile(opts.profile);
|
|
13495
|
+
if (applied.error) {
|
|
13496
|
+
console.error('服务 profile 启动失败: ' + applied.text);
|
|
13497
|
+
process.exit(2);
|
|
13498
|
+
}
|
|
13499
|
+
opts.profile = applied.profilePath;
|
|
13500
|
+
if (opts.host === undefined && applied.profile.host) opts.host = applied.profile.host;
|
|
13501
|
+
if (opts.port === undefined && applied.profile.port) opts.port = applied.profile.port;
|
|
13502
|
+
}
|
|
13299
13503
|
const runtimeIdentity = resolveIdentity(opts);
|
|
13300
13504
|
setRuntimeAgent(runtimeIdentity.error ? '' : runtimeIdentity.id, runtimeIdentity.error ? '' : runtimeIdentity.agentKey);
|
|
13301
13505
|
switch (first) {
|
|
@@ -13512,6 +13716,12 @@ module.exports = {
|
|
|
13512
13716
|
runtimeDoctorCore: runtimeDoctorCore,
|
|
13513
13717
|
runtimeEnsureForManagedTask: runtimeEnsureForManagedTask,
|
|
13514
13718
|
runtimeRestartManagedServer: runtimeRestartManagedServer,
|
|
13719
|
+
lanServeProfilePath: lanServeProfilePath,
|
|
13720
|
+
resolveProfilePath: resolveProfilePath,
|
|
13721
|
+
lanWriteServeProfile: lanWriteServeProfile,
|
|
13722
|
+
loadServeProfile: loadServeProfile,
|
|
13723
|
+
applyServeProfile: applyServeProfile,
|
|
13724
|
+
serveProfileDrift: serveProfileDrift,
|
|
13515
13725
|
lanTaskRunCmd: lanTaskRunCmd,
|
|
13516
13726
|
lanStartupDir: lanStartupDir,
|
|
13517
13727
|
lanAutostartDir: lanAutostartDir,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yottameta/yotta-memory",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.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
|
@@ -53,6 +53,9 @@ yotta-memory recall <关键词> --agent <id> --agent-key-file "<AI_HOME>/.yotta-
|
|
|
53
53
|
## 6.1 全局 CLI、MCP 或常驻服务版本不一致?
|
|
54
54
|
先跑 `yotta-memory doctor --runtime`:它会列出 CLI、current、`runtime.json`、MCP 配置、运行中 server、技能副本与身份模式的漂移,并给出实际版本、期望版本、修复命令和是否阻断。用 `yotta-memory runtime install --from-current` 建立 `<runtimeRoot>/current` 稳定入口;升级用 `runtime install <tarball|版本>` + `runtime use <版本> --restart`,回滚用 `runtime rollback --restart`。`lan enable` 与备份调度只登记 `current/bin/yotta-memory.js`,不再写死版本目录。
|
|
55
55
|
|
|
56
|
+
## 6.2 电脑异常重启后,局域网 MCP 一直返回 401?
|
|
57
|
+
先运行 `yotta-memory lan status`,检查“服务 profile”的记忆库是否指向真实库,而不是 `C:\Users\Default\.yottamemory`。v0.24.1 起,`lan enable` 会生成每用户 `serve-profile.json`,任务命令应包含 `--profile <绝对路径>`。若仍显示旧任务未固定 profile,请在目标用户的管理员终端重新执行 `yotta-memory lan enable --onstart`,然后结束旧任务并从新任务启动。这样 S4U / 无登录会话启动不会重新猜测 home,也不会起在空库上。运行中的服务日志应显示 `服务 profile:` 与真实 `记忆库:`。
|
|
58
|
+
|
|
56
59
|
## 7. MCP 工具没加载?
|
|
57
60
|
检查客户端 `mcpServers` 已配置 yotta-memory(url + token + agent_key);agent_key 应来自 `<AI_HOME>/.yotta-memory-agent-key` 或 MCP secret 注入,不把明文 key 写进对话。改配置后重启/重载会话。本机直连可不配 MCP,直接用 CLI。
|
|
58
61
|
|
package/references/protocol.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# yotta-memory 协议规范 v0.24.
|
|
1
|
+
# yotta-memory 协议规范 v0.24.1
|
|
2
2
|
|
|
3
3
|
> 本文件定义 yotta-memory 记忆标准:存储位置、目录结构、文件格式、类型体系与 CLI 命令参考。
|
|
4
4
|
> 目标:任何支持 Agent Skills 开放标准的智能体,装完即可读写同一份记忆。
|
|
@@ -159,6 +159,8 @@ magic "YTMIDX1" (7B) | nonce(12B) | tag(16B) | ciphertext(JSON: {version, update
|
|
|
159
159
|
| `reindex` | 全量扫描重建 `index.json`(手动改 .md 后校正)|
|
|
160
160
|
| `index --stats [--json]` | 只读分析索引存储 / 逻辑体积、字段占比、token 与 gzip 年份分片预估 |
|
|
161
161
|
| `layout plan/apply/rollback` | 生成日级目录迁移计划、按 journal 执行、失败回滚或按批次恢复 |
|
|
162
|
+
| `serve --profile <文件>` | 读取每用户服务 profile,固定 memoryHome / runtimeHome / configDir;缺失或无效时 fail-closed |
|
|
163
|
+
| `lan enable/disable/status` | 生成并检查每用户 `serve-profile.json`;自启任务只引用该 profile |
|
|
162
164
|
| `export [--out f.json]` | 导出全部记忆为 JSON |
|
|
163
165
|
| `import <f.json>` | 从 JSON 导入(幂等)|
|
|
164
166
|
| `profile [--owner <id>]` | 用户画像聚合(零推断,写 `private/<owner>/profile.md`;跨 owner 默认拒绝)|
|