@yottameta/yotta-memory 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/SKILL.md +3 -3
- package/USER_GUIDE.md +11 -20
- package/bin/yotta-memory.js +297 -13
- package/package.json +1 -1
- package/references/protocol.md +1 -1
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
| **越用越懂(v0.6.0)** | `profile` 画像聚合(零推断)+ `context` 开工上下文包(身份 / 画像 / 近期记忆 / 边界 / 承诺)+ SKILL「记忆守则」规则层,记忆随使用成长 |
|
|
44
44
|
| **生态分发** | GitHub + npm 双源同步发布;npx skills / npm / install.sh 三种方式,覆盖 17+ 类智能体目录 |
|
|
45
45
|
| **便携记忆盘(随盘走)** | 记忆库即引擎:装在硬盘 / 主机上,插上即恢复全部记忆;引擎主机只需装 CLI 当存放点,无需装任何 AI 智能体 |
|
|
46
|
-
| **局域网共享与自启** | 每智能体独立 token(Bearer + X-Agent-Id)鉴权、可吊销;`lan enable` 注册开机自启(Windows
|
|
46
|
+
| **局域网共享与自启** | 每智能体独立 token(Bearer + X-Agent-Id)鉴权、可吊销;`lan enable` 注册开机自启(Windows:优先计划任务,非管理员自动降级用户级 Startup 静默自启;Linux:systemd 用户单元,不可用时自动降级用户 crontab @reboot);MCP 工具集与 CLI 一致(8 个工具),管理动作不远程暴露 |
|
|
47
47
|
| **本地 / 局域网双模式** | 本地 `serve --stdio` 零进程、按需拉起(无常驻);局域网 streamable HTTP 常驻——两种模式可并存、按需选用 |
|
|
48
48
|
|
|
49
49
|
## 功能详解
|
|
@@ -97,6 +97,7 @@
|
|
|
97
97
|
- `remember` 时自动构建 `index.json` 索引;recall 用 TF 打分排序,中文按 bigram 分词,不用额外模型。
|
|
98
98
|
- `index.json` 的 `tokens` 字段是中文分词的词频表(TF 打分用),**不是**访问凭证;鉴权令牌在记忆目录下 `.server/tokens.json`。
|
|
99
99
|
- 支持关键词、`--type` 过滤、`--limit` 截断、项目级优先。
|
|
100
|
+
- **根位置去重(v0.6.5)**:当项目级与用户级记忆库指向同一目录(如 cwd = home 或其父)时,`recall` / `context` 自动唯一化根,同一文件只展示一次。
|
|
100
101
|
- 命中展示会累加 `access_count` / `last_accessed`,为生命周期管理提供依据。
|
|
101
102
|
|
|
102
103
|
### 生命周期管理
|
|
@@ -115,7 +116,7 @@
|
|
|
115
116
|
|
|
116
117
|
- **记忆库即引擎**:`serve` 把记忆目录挂成 MCP 服务,目录随盘走;引擎主机只需装 CLI 当存放点,无需装任何 AI 智能体。
|
|
117
118
|
- **双模式可并存**:本地 `serve --stdio` 零进程(客户端按需拉起);局域网 streamable HTTP(默认 `0.0.0.0:8787`)+ 每智能体 token 鉴权。
|
|
118
|
-
- **开机自启**:`lan enable`
|
|
119
|
+
- **开机自启**:`lan enable` 注册开机自启——Windows 优先计划任务(默认登录自启,`--onstart` 开机即启需管理员),非管理员自动降级为**用户级 Startup 静默自启**(免管理员,启动脚本内联启动命令、被清理也会在开机时自动重建,v0.6.3 起不再弹 80070002);Linux 优先 **systemd 用户单元**(`systemctl --user`,登录自启;`--onstart` 附加 `loginctl enable-linger` 开机即启),systemd 不可用时自动降级**用户 crontab @reboot**(v0.6.4);`lan disable` 移除,`lan status` 查询。
|
|
119
120
|
- **安全边界**:管理动作(init / config / token / lan / serve)不进 MCP,token 不远程暴露;远程智能体只能读写记忆,不能改配置、不能管 token。
|
|
120
121
|
- 完整操作步骤见上文「局域网多机共享」章节与 [USER_GUIDE.md](USER_GUIDE.md)。
|
|
121
122
|
|
|
@@ -196,7 +197,7 @@ npm i -g @yottameta/yotta-memory
|
|
|
196
197
|
| `yotta-memory iam <id> [--name <显示名>] [--user <用户名>] [--relationship <关系>] [--force]` | 登记本智能体唯一身份并自动落自我档案(`agents.json`,ID 必须唯一;可选扩展显示名 / 用户 / 关系)|
|
|
197
198
|
| `yotta-memory token new --agent <id> [--force]` / `token list` / `token revoke --agent <id>` | 为智能体生成 / 列出 / 吊销访问 token(登记于记忆库 `.server/tokens.json`;同 ID 已被其它来源占用需 `--force` 覆盖,防不同智能体合流)|
|
|
198
199
|
| `yotta-memory serve [--host 0.0.0.0] [--port 8787] [--no-auth] [--stdio]` | 启动 MCP 记忆引擎(streamable HTTP 局域网 / --stdio 本地零进程模式;Bearer token + X-Agent-Id 鉴权)|
|
|
199
|
-
| `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:计划任务,默认 ONLOGON
|
|
200
|
+
| `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:计划任务,默认 ONLOGON、--onstart 开机即启需管理员,非管理员自动降级用户级 Startup 静默自启;Linux:systemd 用户单元,不可用时自动降级用户 crontab @reboot)|
|
|
200
201
|
|
|
201
202
|
类型:`FACT`(事实,公共共享)/ `PREF`(偏好)/ `BOUND`(边界)/ `COMMIT`(承诺)。
|
|
202
203
|
|
|
@@ -239,7 +240,7 @@ yotta-memory recall --type FACT --limit 10
|
|
|
239
240
|
3. 启动服务(默认监听 0.0.0.0:8787,Bearer token + X-Agent-Id 鉴权)——临时运行或注册开机自启二选一:
|
|
240
241
|
```bash
|
|
241
242
|
yotta-memory serve # 临时前台运行
|
|
242
|
-
yotta-memory lan enable # 注册开机自启(Windows
|
|
243
|
+
yotta-memory lan enable # 注册开机自启(Windows:计划任务/用户级 Startup;Linux:systemd 用户单元/用户 crontab)
|
|
243
244
|
yotta-memory lan status # 查看自启状态
|
|
244
245
|
```
|
|
245
246
|
> `lan enable --onstart` 改为开机即启(需管理员);非管理员时 `lan enable` 自动改用用户级 Startup 静默自启(无需管理员);`lan disable` 移除自启。
|
package/SKILL.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yotta-memory
|
|
3
3
|
description: "元忆 —— 有权限边界的文件式智能体记忆。文件式、零依赖、可 diff/回滚:让任何 AI 智能体活过会话,开工 recall 恢复上下文、重要信息 remember 落盘、收工归档。类型体系 FACT(公共共享)/ PREF / BOUND / COMMIT(私密隔离)。触发:记住、别忘了、记一笔、记忆、remember、recall、跨会话、上次说到、续测、交接、归档、记忆盘、共享记忆、局域网记忆、画像、开工上下文、记忆守则、profile、context、越用越懂"
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.5
|
|
5
5
|
license: MIT
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# yotta-memory(元忆)— 有权限边界的文件式智能体记忆
|
|
9
9
|
|
|
10
10
|
> 一句话:元忆 —— 有权限边界的文件式智能体记忆(不注入、可 diff、能回滚;FACT 共享、PREF / BOUND / COMMIT 私密隔离)。
|
|
11
|
-
> 版本:0.6.
|
|
11
|
+
> 版本:0.6.5 | 最后更新:2026-08-26
|
|
12
12
|
|
|
13
13
|
## 这是什么
|
|
14
14
|
|
|
@@ -158,7 +158,7 @@ license: MIT
|
|
|
158
158
|
| `yotta-memory iam <id> [--name <显示名>] [--user <用户名>] [--relationship <关系>] [--force]` | 登记本智能体唯一身份并自动落自我档案(`agents.json`,ID 必须唯一;可选扩展显示名 / 用户 / 关系)|
|
|
159
159
|
| `yotta-memory token new --agent <id> [--force]` / `token list` / `token revoke --agent <id>` | 每智能体访问 token:生成 / 列出 / 吊销(登记 `<记忆库>/.server/tokens.json`;同 ID 已被其它来源占用需 `--force` 覆盖,防不同智能体合流)|
|
|
160
160
|
| `yotta-memory serve [--host 0.0.0.0] [--port 8787] [--no-auth] [--stdio]` | 启动 MCP 记忆引擎(streamable HTTP 局域网 / --stdio 本地零进程模式;Bearer token + X-Agent-Id 鉴权)|
|
|
161
|
-
| `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:计划任务,默认 ONLOGON
|
|
161
|
+
| `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:计划任务,默认 ONLOGON、--onstart 开机即启需管理员,非管理员自动降级用户级 Startup 静默自启,v0.6.3 起 VBS 自愈不弹 80070002;Linux:systemd 用户单元,不可用时自动降级用户 crontab @reboot)|
|
|
162
162
|
|
|
163
163
|
## 存储格式(摘要)
|
|
164
164
|
|
package/USER_GUIDE.md
CHANGED
|
@@ -131,25 +131,16 @@ yotta-memory lan enable --onstart # 开机即启(需管理员)
|
|
|
131
131
|
yotta-memory lan status # 查看状态
|
|
132
132
|
yotta-memory lan disable # 取消
|
|
133
133
|
|
|
134
|
-
# Linux
|
|
135
|
-
#
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
[Service]
|
|
142
|
-
ExecStart=/usr/local/bin/yotta-memory serve --host 0.0.0.0 --port 8787
|
|
143
|
-
Restart=on-failure
|
|
144
|
-
|
|
145
|
-
[Install]
|
|
146
|
-
WantedBy=multi-user.target
|
|
147
|
-
EOF
|
|
148
|
-
sudo systemctl daemon-reload
|
|
149
|
-
sudo systemctl enable --now yotta-memory
|
|
150
|
-
systemctl status yotta-memory # 查看状态
|
|
134
|
+
# Linux:内置命令(v0.6.4 起)——优先 systemd 用户单元(登录自启);
|
|
135
|
+
# systemd 不可用时自动降级用户 crontab @reboot(开机自启,免管理员)
|
|
136
|
+
yotta-memory lan enable # 登录后自动启动(默认)
|
|
137
|
+
yotta-memory lan enable --onstart # 开机即启(无需登录,需系统支持 loginctl)
|
|
138
|
+
yotta-memory lan status # 查看状态
|
|
139
|
+
yotta-memory lan disable # 取消
|
|
151
140
|
```
|
|
152
141
|
|
|
142
|
+
> 说明:`lan enable` 默认「登录后自启」;如需系统级(整机、开机即启)服务,可手动创建 `/etc/systemd/system/yotta-memory.service`(ExecStart 指向 `which yotta-memory` 实际路径)并 `sudo systemctl enable --now yotta-memory`;桌面环境也可在 `~/.config/autostart/` 放置 `yotta-memory.desktop` 实现图形登录自启。
|
|
143
|
+
|
|
153
144
|
**第 4 步:为局域网其它主机的智能体生成访问 token**
|
|
154
145
|
|
|
155
146
|
```bash
|
|
@@ -258,7 +249,7 @@ yotta-memory remember FACT 主题 内容 # 智能体落盘
|
|
|
258
249
|
| `yotta-memory iam <id> [--name <显示名>] [--user <用户名>] [--relationship <关系>] [--force]` | 登记本智能体唯一身份并自动落自我档案(`agents.json`,ID 必须唯一;可选扩展显示名 / 用户 / 关系)|
|
|
259
250
|
| `yotta-memory token new --agent <id> [--force]` / `token list` / `token revoke --agent <id>` | 访问 token(同 ID 已被其它来源占用需 `--force` 覆盖)|
|
|
260
251
|
| `yotta-memory serve [--port 8787] [--stdio] [--no-auth]` | 启动记忆引擎(--no-auth 关闭鉴权,仅限可信内网)|
|
|
261
|
-
| `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows
|
|
252
|
+
| `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:计划任务/用户级 Startup 静默自启;Linux:systemd 用户单元/用户 crontab @reboot)|
|
|
262
253
|
|
|
263
254
|
类型:`FACT`(事实,共享)/ `PREF`(偏好)/ `BOUND`(边界)/ `COMMIT`(承诺),后三类按智能体物理分目录隔离(`private/<owner>/<type>/`)。
|
|
264
255
|
|
|
@@ -266,7 +257,7 @@ yotta-memory remember FACT 主题 内容 # 智能体落盘
|
|
|
266
257
|
|
|
267
258
|
**远程连不上时,按顺序检查:**
|
|
268
259
|
|
|
269
|
-
1.
|
|
260
|
+
1. 引擎在运行吗?`yotta-memory lan status`(Windows / Linux 通用);Linux 也可 `systemctl --user status yotta-memory-serve.service`(systemd 自启时)或 `ps aux | grep yotta-memory`;也可直接 `yotta-memory --version`。
|
|
270
261
|
2. IP 对吗?引擎主机执行 `hostname -I`(Linux)或 `ipconfig`(Windows)确认。
|
|
271
262
|
3. 端口对吗?默认 8787,两端要一致。
|
|
272
263
|
4. 防火墙放行了吗?Linux:`sudo ufw status`,未放行则 `sudo ufw allow 8787/tcp`;Windows:允许首次监听的入站请求。
|
|
@@ -281,7 +272,7 @@ yotta-memory remember FACT 主题 内容 # 智能体落盘
|
|
|
281
272
|
|
|
282
273
|
**recall 没有结果:** 关键词太细或确实没有这条记忆;先 `recall`(不带关键词)看库里的全部记忆核对。
|
|
283
274
|
|
|
284
|
-
**systemd 自启的引擎看日志:** `journalctl -u yotta-memory -n 50
|
|
275
|
+
**systemd 自启的引擎看日志:** `journalctl --user -u yotta-memory-serve.service -n 50`(系统级手动服务为 `journalctl -u yotta-memory -n 50`)。
|
|
285
276
|
|
|
286
277
|
**记错位置了?** `yotta-memory config get` 查看当前生效位置;`config set memory_home <正确目录>` 改正。
|
|
287
278
|
|
package/bin/yotta-memory.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
// v0.6.0 新增:灵魂盘核心——profile(用户画像聚合,零推断)/ context(开工上下文包)/ iam 扩展(--name/--user/--relationship)/ remember --verify(写后回读)与 --no-hint(关闭类型启发式提示)+ SKILL「记忆守则」
|
|
9
9
|
// v0.6.1 新增:灵魂盘机制精髓补齐——context --budget(token 预算,近记忆按剩余预算放行)/ context 内嵌「多智能体接入铁律」段 / remember --source/--weight(来源 + 重要性权重,去重 weight 取 max)/ 近期记忆排序融合 importance(confidence×recency+updated+weight+immutable)
|
|
10
10
|
// v0.6.3 修复:lan 开机自启 VBS 自愈——VBS 内联 autostart.cmd 内容,启动时自动重建 .cmd(根治 80070002:wscript 找不到被引用启动文件)
|
|
11
|
+
// v0.6.4 新增:lan 命令扩展 Linux——systemd 用户单元(systemctl --user enable/start,登录自启;--onstart 附加 loginctl enable-linger 开机即启)/ systemd 不可用时自动降级用户 crontab @reboot;lanPlatform 测试钩子(YOTTA_LAN_PLATFORM)
|
|
12
|
+
// v0.6.5 修复:recall/context 对同一文件显示 2 条——projectRoot 与 userRoot 指向同一目录(如 cwd=home 或其父)时同一索引被遍历两次;新增 memoryRoots() 唯一化根,hasGrant/recallCore/forgetCore/cmdReindex/contextCore 统一走 memoryRoots()
|
|
11
13
|
// v0.6.2 修复:remember --verify 写后回读改为直查索引 + 权限判定 + 召回匹配性(不再依赖 recall top-N 排序,消除泛化 subject 下偶发误报「回读未命中」)
|
|
12
14
|
'use strict';
|
|
13
15
|
|
|
@@ -18,7 +20,7 @@ const crypto = require('crypto');
|
|
|
18
20
|
const http = require('http');
|
|
19
21
|
const child_process = require('child_process');
|
|
20
22
|
|
|
21
|
-
const VERSION = '0.6.
|
|
23
|
+
const VERSION = '0.6.5';
|
|
22
24
|
const TYPES = ['FACT', 'PREF', 'BOUND', 'COMMIT'];
|
|
23
25
|
const TYPE_DIRS = { FACT: 'facts', PREF: 'prefs', BOUND: 'bounds', COMMIT: 'commits' };
|
|
24
26
|
const PUBLIC_DIR = 'facts';
|
|
@@ -56,6 +58,19 @@ function userRoot() {
|
|
|
56
58
|
function projectRoot() {
|
|
57
59
|
return path.join(process.cwd(), '.yottamemory');
|
|
58
60
|
}
|
|
61
|
+
// 唯一化记忆库根:projectRoot 与 userRoot 可能指向同一目录(如 cwd=home 或其父时),
|
|
62
|
+
// 若不唯一化,recall/context 等会对同一索引遍历两次 -> 同一条记忆重复展示(v0.6.5 修复)。
|
|
63
|
+
function memoryRoots() {
|
|
64
|
+
const out = [], seen = new Set();
|
|
65
|
+
for (const r of [projectRoot(), userRoot()]) {
|
|
66
|
+
const abs = path.resolve(r);
|
|
67
|
+
if (!fs.existsSync(abs)) continue;
|
|
68
|
+
if (seen.has(abs)) continue;
|
|
69
|
+
seen.add(abs);
|
|
70
|
+
out.push(abs);
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
59
74
|
function today() {
|
|
60
75
|
const d = new Date();
|
|
61
76
|
const p = (n) => String(n).padStart(2, '0');
|
|
@@ -348,7 +363,7 @@ function loadGrants(root) {
|
|
|
348
363
|
}
|
|
349
364
|
function hasGrant(userAgent, ownerAgent) {
|
|
350
365
|
if (!userAgent || !ownerAgent) return false;
|
|
351
|
-
for (const root of
|
|
366
|
+
for (const root of memoryRoots()) {
|
|
352
367
|
if (!fs.existsSync(root)) continue;
|
|
353
368
|
const grants = loadGrants(root);
|
|
354
369
|
const list = grants[userAgent];
|
|
@@ -468,7 +483,7 @@ function rememberCore(type, subject, statement, opts) {
|
|
|
468
483
|
return { error: false, text: text };
|
|
469
484
|
}
|
|
470
485
|
function recallCore(query, opts) {
|
|
471
|
-
const roots =
|
|
486
|
+
const roots = memoryRoots();
|
|
472
487
|
if (!roots.length) return { error: false, exitCode: 0, text: '记忆库不存在,请先运行: yotta-memory init' };
|
|
473
488
|
const limit = opts.limit || 50;
|
|
474
489
|
const onlyType = opts.type ? String(opts.type).toUpperCase() : null;
|
|
@@ -550,7 +565,7 @@ function relOf(root, fp) { return path.relative(root, fp).replace(/\\/g, '/'); }
|
|
|
550
565
|
function forgetCore(fileRef, opts) {
|
|
551
566
|
opts = opts || {};
|
|
552
567
|
const selfAgent = opts.selfAgent || currentAgent();
|
|
553
|
-
const roots =
|
|
568
|
+
const roots = memoryRoots();
|
|
554
569
|
const ref = String(fileRef || '').replace(/\\/g, '/');
|
|
555
570
|
let target = null, targetRoot = null, targetRel = null;
|
|
556
571
|
for (const root of roots) {
|
|
@@ -624,7 +639,7 @@ function cmdArchive(opts) {
|
|
|
624
639
|
console.log(r.text);
|
|
625
640
|
}
|
|
626
641
|
function cmdReindex() {
|
|
627
|
-
const roots =
|
|
642
|
+
const roots = memoryRoots();
|
|
628
643
|
if (!roots.length) { console.log('记忆库不存在。'); return; }
|
|
629
644
|
for (const root of roots) {
|
|
630
645
|
const n = buildIndex(root).length;
|
|
@@ -963,7 +978,7 @@ function selfProfileKv(root, id) {
|
|
|
963
978
|
}
|
|
964
979
|
function contextCore(opts) {
|
|
965
980
|
opts = opts || {};
|
|
966
|
-
const roots =
|
|
981
|
+
const roots = memoryRoots();
|
|
967
982
|
if (!roots.length) return { error: false, exitCode: 0, text: '记忆库不存在,请先运行: yotta-memory init' };
|
|
968
983
|
const root = userRoot();
|
|
969
984
|
const limit = opts.limit || 10;
|
|
@@ -1268,11 +1283,17 @@ function cmdServeStdio() {
|
|
|
1268
1283
|
process.stdin.on('end', function () { process.exit(0); });
|
|
1269
1284
|
}
|
|
1270
1285
|
|
|
1271
|
-
// ---- lan
|
|
1272
|
-
//
|
|
1273
|
-
//
|
|
1286
|
+
// ---- lan command (autostart management for serve) ----
|
|
1287
|
+
// Windows dual mechanism: (1) schtasks scheduled task (default, needs permission); (2) non-admin
|
|
1288
|
+
// (Access denied) auto-fallback to user-level Startup silent autostart (VBS sh.Run <autostart.cmd>,
|
|
1289
|
+
// 0, False + autostart.cmd; node path process.execPath auto-detected, 0.5.4)
|
|
1290
|
+
// Linux (v0.6.4): (1) systemd user unit (systemctl --user enable/start, login autostart; --onstart
|
|
1291
|
+
// additionally runs loginctl enable-linger for boot autostart); (2) auto-fallback to user crontab
|
|
1292
|
+
// @reboot when no systemd user session is available.
|
|
1274
1293
|
const LAN_TASK_NAME = 'YottaMemoryServe';
|
|
1275
1294
|
const LAN_GEN_MARKER = 'Generated by yotta-memory lan enable';
|
|
1295
|
+
const LAN_UNIT_NAME = 'yotta-memory-serve.service';
|
|
1296
|
+
const LAN_CRONTAB_MARKER = '#YTM_LAN:yotta-memory-serve';
|
|
1276
1297
|
function lanTaskRunCmd(opts) {
|
|
1277
1298
|
const host = opts.host || '0.0.0.0';
|
|
1278
1299
|
const port = opts.port || 8787;
|
|
@@ -1280,6 +1301,33 @@ function lanTaskRunCmd(opts) {
|
|
|
1280
1301
|
const q = (p) => /[\s"]/.test(p) ? '\\"' + p.replace(/"/g, '\\"') + '\\"' : p;
|
|
1281
1302
|
return q(process.execPath) + ' ' + q(__filename) + ' serve --host ' + host + ' --port ' + port;
|
|
1282
1303
|
}
|
|
1304
|
+
|
|
1305
|
+
function lanServeArgs(opts) {
|
|
1306
|
+
return ['serve', '--host', String(opts.host || '0.0.0.0'), '--port', String(opts.port || 8787)];
|
|
1307
|
+
}
|
|
1308
|
+
// generic spawn wrapper: YOTTA_LAN_*_BIN may point to a .js stub (testing/advanced use;
|
|
1309
|
+
// on Windows run it with the current node interpreter automatically)
|
|
1310
|
+
function lanSpawn(bin, args, spawnOpts) {
|
|
1311
|
+
if (process.platform === 'win32' && /\.js$/i.test(String(bin))) {
|
|
1312
|
+
return child_process.spawnSync(process.execPath, [bin].concat(args), spawnOpts);
|
|
1313
|
+
}
|
|
1314
|
+
return child_process.spawnSync(bin, args, spawnOpts);
|
|
1315
|
+
}
|
|
1316
|
+
// platform override for testing/advanced use (YOTTA_LAN_PLATFORM=linux/win32/...)
|
|
1317
|
+
function lanPlatform() { return process.env.YOTTA_LAN_PLATFORM || process.platform; }
|
|
1318
|
+
// sh-style single-quote quoting (crontab line is executed via /bin/sh -c)
|
|
1319
|
+
function shQuote(s) {
|
|
1320
|
+
const p = String(s);
|
|
1321
|
+
if (!/[\s'"\\$\`]/.test(p)) return p;
|
|
1322
|
+
return "'" + p.replace(/'/g, "'\\''") + "'";
|
|
1323
|
+
}
|
|
1324
|
+
// systemd ExecStart quoting (systemd's own parse rules: double quotes group, \" escapes a quote,
|
|
1325
|
+
// \\ escapes a backslash)
|
|
1326
|
+
function systemdEscapeArg(s) {
|
|
1327
|
+
const p = String(s);
|
|
1328
|
+
if (!/[\s"\\$;]/.test(p)) return p;
|
|
1329
|
+
return '"' + p.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
|
|
1330
|
+
}
|
|
1283
1331
|
function lanStartupDir() {
|
|
1284
1332
|
// 测试/高级用途可用 YOTTA_LAN_STARTUP_DIR 覆盖(默认用户级 Startup 目录,免管理员)
|
|
1285
1333
|
if (process.env.YOTTA_LAN_STARTUP_DIR) return process.env.YOTTA_LAN_STARTUP_DIR;
|
|
@@ -1370,7 +1418,191 @@ function isSchtasksAccessDenied(e) {
|
|
|
1370
1418
|
const msg = String((e && e.stderr) || (e && e.message) || e);
|
|
1371
1419
|
return /access\s+is\s+denied|access\s+denied|拒绝访问/i.test(msg);
|
|
1372
1420
|
}
|
|
1373
|
-
|
|
1421
|
+
|
|
1422
|
+
// ---- lan Linux (systemd user unit / user crontab @reboot) ----
|
|
1423
|
+
function lanLinuxSystemdUserDir() {
|
|
1424
|
+
// override with YOTTA_LAN_SYSTEMD_USER_DIR for testing/advanced use (default ~/.config/systemd/user)
|
|
1425
|
+
if (process.env.YOTTA_LAN_SYSTEMD_USER_DIR) return process.env.YOTTA_LAN_SYSTEMD_USER_DIR;
|
|
1426
|
+
const cfg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
1427
|
+
return path.join(cfg, 'systemd', 'user');
|
|
1428
|
+
}
|
|
1429
|
+
function lanLinuxUnitPath() { return path.join(lanLinuxSystemdUserDir(), LAN_UNIT_NAME); }
|
|
1430
|
+
function lanLinuxSystemctlBin() { return process.env.YOTTA_LAN_SYSTEMCTL_BIN || 'systemctl'; }
|
|
1431
|
+
function lanLinuxLoginctlBin() { return process.env.YOTTA_LAN_LOGINCTL_BIN || 'loginctl'; }
|
|
1432
|
+
function lanLinuxExecStart(opts) {
|
|
1433
|
+
return [systemdEscapeArg(process.execPath), systemdEscapeArg(__filename)].concat(lanServeArgs(opts)).join(' ');
|
|
1434
|
+
}
|
|
1435
|
+
function lanLinuxUnitContent(opts) {
|
|
1436
|
+
// keep comments ASCII (avoid locale/encoding issues); ExecStart uses systemd's own quote rules
|
|
1437
|
+
return [
|
|
1438
|
+
'# ' + LAN_GEN_MARKER + '; remove with: yotta-memory lan disable (English only, keep ASCII)',
|
|
1439
|
+
'',
|
|
1440
|
+
'[Unit]',
|
|
1441
|
+
'Description=yotta-memory memory engine (lan autostart)',
|
|
1442
|
+
'',
|
|
1443
|
+
'[Service]',
|
|
1444
|
+
'Type=simple',
|
|
1445
|
+
'ExecStart=' + lanLinuxExecStart(opts),
|
|
1446
|
+
'Restart=on-failure',
|
|
1447
|
+
'RestartSec=3',
|
|
1448
|
+
'',
|
|
1449
|
+
'[Install]',
|
|
1450
|
+
'WantedBy=default.target',
|
|
1451
|
+
'',
|
|
1452
|
+
].join('\n');
|
|
1453
|
+
}
|
|
1454
|
+
function lanCrontabBin() { return process.env.YOTTA_LAN_CRONTAB_BIN || 'crontab'; }
|
|
1455
|
+
function lanCrontabLine(opts) {
|
|
1456
|
+
const parts = ['@reboot', shQuote(process.execPath), shQuote(__filename)].concat(lanServeArgs(opts));
|
|
1457
|
+
return parts.join(' ') + ' >> ' + shQuote(lanLogPath(opts)) + ' 2>&1 ' + LAN_CRONTAB_MARKER;
|
|
1458
|
+
}
|
|
1459
|
+
function lanCrontabRead() {
|
|
1460
|
+
try {
|
|
1461
|
+
const r = lanSpawn(lanCrontabBin(), ['-l'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
1462
|
+
if (r.status === 0) return String(r.stdout || '').split(/\r?\n/).filter(function (l) { return l !== ''; });
|
|
1463
|
+
} catch (e) { /* crontab unavailable -> treat as empty */ }
|
|
1464
|
+
return [];
|
|
1465
|
+
}
|
|
1466
|
+
function lanCrontabWrite(lines) {
|
|
1467
|
+
const content = lines.join('\n') + (lines.length ? '\n' : '');
|
|
1468
|
+
const r = lanSpawn(lanCrontabBin(), ['-'], { input: content, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
1469
|
+
if (r.error) throw r.error;
|
|
1470
|
+
if (r.status !== 0) throw new Error(String(r.stderr || r.stdout || '').toString().split(/\r?\n/)[0]);
|
|
1471
|
+
}
|
|
1472
|
+
function lanCrontabHasOurLine(lines) {
|
|
1473
|
+
return lines.some(function (l) { return l.indexOf(LAN_CRONTAB_MARKER) !== -1; });
|
|
1474
|
+
}
|
|
1475
|
+
function lanCrontabWithoutOurLine(lines) {
|
|
1476
|
+
return lines.filter(function (l) { return l.indexOf(LAN_CRONTAB_MARKER) === -1; });
|
|
1477
|
+
}
|
|
1478
|
+
function lanLinuxHasSystemd() {
|
|
1479
|
+
try {
|
|
1480
|
+
const r = lanSpawn(lanLinuxSystemctlBin(), ['--user', 'show-environment'], { stdio: ['ignore', 'ignore', 'ignore'] });
|
|
1481
|
+
return r.status === 0;
|
|
1482
|
+
} catch (e) { return false; }
|
|
1483
|
+
}
|
|
1484
|
+
function lanLinuxSystemctl(args) {
|
|
1485
|
+
const r = lanSpawn(lanLinuxSystemctlBin(), ['--user'].concat(args), { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
1486
|
+
if (r.error) throw r.error;
|
|
1487
|
+
return { status: r.status, out: String(r.stdout || ''), err: String(r.stderr || '') };
|
|
1488
|
+
}
|
|
1489
|
+
function lanLinuxInstallSystemd(opts) {
|
|
1490
|
+
const dir = lanLinuxSystemdUserDir();
|
|
1491
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1492
|
+
const unit = lanLinuxUnitPath();
|
|
1493
|
+
fs.writeFileSync(unit, lanLinuxUnitContent(opts), 'utf8');
|
|
1494
|
+
lanLinuxSystemctl(['daemon-reload']);
|
|
1495
|
+
const en = lanLinuxSystemctl(['enable', LAN_UNIT_NAME]);
|
|
1496
|
+
if (en.status !== 0) throw new Error('systemctl --user enable 失败: ' + en.err.split(/\r?\n/)[0]);
|
|
1497
|
+
const st = lanLinuxSystemctl(['start', LAN_UNIT_NAME]);
|
|
1498
|
+
if (st.status !== 0) {
|
|
1499
|
+
console.error('启动服务失败(已注册,稍后可执行 systemctl --user start yotta-memory-serve.service): ' + st.err.split(/\r?\n/)[0]);
|
|
1500
|
+
}
|
|
1501
|
+
if (opts.onstart) {
|
|
1502
|
+
try {
|
|
1503
|
+
const lr = lanSpawn(lanLinuxLoginctlBin(), ['enable-linger'], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
1504
|
+
if (lr.status === 0) console.log('已启用 lingering(开机即启,无需登录)');
|
|
1505
|
+
else console.error('提示: 开机即启需 loginctl enable-linger,当前未生效(' + String(lr.stderr || '').split(/\r?\n/)[0] + ')');
|
|
1506
|
+
} catch (e) {
|
|
1507
|
+
console.error('提示: 开机即启需 loginctl enable-linger,当前无 loginctl(' + String((e && e.message) || e).split(/\r?\n/)[0] + ')');
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
return unit;
|
|
1511
|
+
}
|
|
1512
|
+
function lanLinuxRemoveSystemd() {
|
|
1513
|
+
// only remove our own marker unit file, never a user's own systemd unit
|
|
1514
|
+
const unit = lanLinuxUnitPath();
|
|
1515
|
+
if (!fs.existsSync(unit) || !lanFileHasMarker(unit)) return false;
|
|
1516
|
+
try { lanLinuxSystemctl(['disable', LAN_UNIT_NAME]); } catch (e) {}
|
|
1517
|
+
try { lanLinuxSystemctl(['stop', LAN_UNIT_NAME]); } catch (e) {}
|
|
1518
|
+
try { fs.unlinkSync(unit); } catch (e) {}
|
|
1519
|
+
try { lanLinuxSystemctl(['daemon-reload']); } catch (e) {}
|
|
1520
|
+
return true;
|
|
1521
|
+
}
|
|
1522
|
+
function lanLinuxInstallCrontab(opts) {
|
|
1523
|
+
const lines = lanCrontabWithoutOurLine(lanCrontabRead());
|
|
1524
|
+
lines.push(lanCrontabLine(opts));
|
|
1525
|
+
lanCrontabWrite(lines);
|
|
1526
|
+
return lanCrontabLine(opts);
|
|
1527
|
+
}
|
|
1528
|
+
function lanLinuxRemoveCrontab() {
|
|
1529
|
+
const cur = lanCrontabRead();
|
|
1530
|
+
const lines = lanCrontabWithoutOurLine(cur);
|
|
1531
|
+
if (lines.length === cur.length) return false;
|
|
1532
|
+
lanCrontabWrite(lines);
|
|
1533
|
+
return true;
|
|
1534
|
+
}
|
|
1535
|
+
function cmdLanLinuxEnable(opts) {
|
|
1536
|
+
if (lanLinuxHasSystemd()) {
|
|
1537
|
+
try {
|
|
1538
|
+
const unit = lanLinuxInstallSystemd(opts);
|
|
1539
|
+
console.log('已注册开机自启: systemd 用户单元 ' + LAN_UNIT_NAME);
|
|
1540
|
+
console.log('单元文件: ' + unit);
|
|
1541
|
+
console.log('备注: 登录后自动启动(systemctl --user);如需现在运行请执行 yotta-memory serve');
|
|
1542
|
+
if (!opts.onstart) console.log('提示: 如需开机即启(无需登录),请用 --onstart 重新执行 yotta-memory lan enable');
|
|
1543
|
+
return;
|
|
1544
|
+
} catch (e) {
|
|
1545
|
+
const msg = String((e && e.message) || e).split(/\r?\n/)[0];
|
|
1546
|
+
console.error('systemd 注册失败: ' + msg);
|
|
1547
|
+
console.log('自动改用用户 crontab @reboot 自启...');
|
|
1548
|
+
}
|
|
1549
|
+
} else {
|
|
1550
|
+
console.log('未检测到 systemd 用户会话,改用用户 crontab @reboot 自启...');
|
|
1551
|
+
}
|
|
1552
|
+
try {
|
|
1553
|
+
const line = lanLinuxInstallCrontab(opts);
|
|
1554
|
+
console.log('已启用用户 crontab @reboot 自启');
|
|
1555
|
+
console.log('启动命令: ' + line);
|
|
1556
|
+
console.log('日志: ' + lanLogPath(opts));
|
|
1557
|
+
console.log('备注: 开机时自动启动;如需现在运行请执行 yotta-memory serve');
|
|
1558
|
+
} catch (e2) {
|
|
1559
|
+
console.error('crontab 注册失败: ' + String((e2 && e2.message) || e2).split(/\r?\n/)[0]);
|
|
1560
|
+
console.error('请手动配置开机自启(如 ~/.config/autostart/*.desktop 或系统服务),或直接运行 yotta-memory serve');
|
|
1561
|
+
process.exit(1);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
function cmdLanLinuxDisable() {
|
|
1565
|
+
let removedAny = false;
|
|
1566
|
+
let sawError = false;
|
|
1567
|
+
try {
|
|
1568
|
+
if (lanLinuxRemoveSystemd()) { console.log('已移除 systemd 用户单元 ' + LAN_UNIT_NAME); removedAny = true; }
|
|
1569
|
+
} catch (e) { console.error('移除 systemd 单元失败: ' + String((e && e.message) || e).split(/\r?\n/)[0]); sawError = true; }
|
|
1570
|
+
try {
|
|
1571
|
+
if (lanLinuxRemoveCrontab()) { console.log('已移除 crontab @reboot 自启'); removedAny = true; }
|
|
1572
|
+
} catch (e) { console.error('移除 crontab 自启失败: ' + String((e && e.message) || e).split(/\r?\n/)[0]); sawError = true; }
|
|
1573
|
+
if (!removedAny && !sawError) console.log('未启用: 未发现任何自启配置(systemd 单元与 crontab 均不存在)');
|
|
1574
|
+
if (sawError) process.exit(1);
|
|
1575
|
+
}
|
|
1576
|
+
function cmdLanLinuxStatus() {
|
|
1577
|
+
let any = false;
|
|
1578
|
+
const unit = lanLinuxUnitPath();
|
|
1579
|
+
const unitExists = fs.existsSync(unit) && lanFileHasMarker(unit);
|
|
1580
|
+
if (unitExists) {
|
|
1581
|
+
any = true;
|
|
1582
|
+
console.log('systemd 用户单元 ' + LAN_UNIT_NAME + ': 已注册');
|
|
1583
|
+
try {
|
|
1584
|
+
const en = lanLinuxSystemctl(['is-enabled', LAN_UNIT_NAME]);
|
|
1585
|
+
if (en.status === 0) console.log(' 启用状态: ' + String(en.out).trim());
|
|
1586
|
+
} catch (e) {}
|
|
1587
|
+
try {
|
|
1588
|
+
const ac = lanLinuxSystemctl(['is-active', LAN_UNIT_NAME]);
|
|
1589
|
+
if (ac.status === 0) console.log(' 运行状态: ' + String(ac.out).trim());
|
|
1590
|
+
} catch (e) {}
|
|
1591
|
+
console.log(' 单元文件: ' + unit);
|
|
1592
|
+
} else {
|
|
1593
|
+
console.log('systemd 用户单元 ' + LAN_UNIT_NAME + ': 未注册');
|
|
1594
|
+
}
|
|
1595
|
+
const lines = lanCrontabRead();
|
|
1596
|
+
if (lanCrontabHasOurLine(lines)) {
|
|
1597
|
+
any = true;
|
|
1598
|
+
console.log('crontab @reboot: 已启用');
|
|
1599
|
+
for (const l of lines) if (l.indexOf(LAN_CRONTAB_MARKER) !== -1) console.log(' ' + l.trim());
|
|
1600
|
+
} else {
|
|
1601
|
+
console.log('crontab @reboot: 未启用');
|
|
1602
|
+
}
|
|
1603
|
+
if (!any) console.log('未启用任何开机自启(可用 yotta-memory lan enable 注册)');
|
|
1604
|
+
}
|
|
1605
|
+
function cmdLanWinEnable(opts) {
|
|
1374
1606
|
if (process.platform !== 'win32') {
|
|
1375
1607
|
console.error('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' + process.platform);
|
|
1376
1608
|
process.exit(2);
|
|
@@ -1404,7 +1636,7 @@ function cmdLanEnable(opts) {
|
|
|
1404
1636
|
return;
|
|
1405
1637
|
}
|
|
1406
1638
|
}
|
|
1407
|
-
function
|
|
1639
|
+
function cmdLanWinDisable() {
|
|
1408
1640
|
if (process.platform !== 'win32') {
|
|
1409
1641
|
console.error('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' + process.platform);
|
|
1410
1642
|
process.exit(2);
|
|
@@ -1429,7 +1661,7 @@ function cmdLanDisable() {
|
|
|
1429
1661
|
if (!removedAny && !sawError) console.log('未启用: 未发现任何自启配置(计划任务与 Startup 文件均不存在)');
|
|
1430
1662
|
if (sawError) process.exit(1);
|
|
1431
1663
|
}
|
|
1432
|
-
function
|
|
1664
|
+
function cmdLanWinStatus() {
|
|
1433
1665
|
if (process.platform !== 'win32') {
|
|
1434
1666
|
console.log('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' + process.platform);
|
|
1435
1667
|
return;
|
|
@@ -1460,6 +1692,27 @@ function cmdLanStatus() {
|
|
|
1460
1692
|
}
|
|
1461
1693
|
if (!taskFound && !vbsOk && !cmdOk) console.log('未启用任何开机自启(可用 yotta-memory lan enable 注册)');
|
|
1462
1694
|
}
|
|
1695
|
+
|
|
1696
|
+
function cmdLanEnable(opts) {
|
|
1697
|
+
const p = lanPlatform();
|
|
1698
|
+
if (p === 'win32') return cmdLanWinEnable(opts);
|
|
1699
|
+
if (p === 'linux') return cmdLanLinuxEnable(opts);
|
|
1700
|
+
console.error('lan 命令当前仅支持 Windows / Linux;本机平台: ' + p);
|
|
1701
|
+
process.exit(2);
|
|
1702
|
+
}
|
|
1703
|
+
function cmdLanDisable() {
|
|
1704
|
+
const p = lanPlatform();
|
|
1705
|
+
if (p === 'win32') return cmdLanWinDisable();
|
|
1706
|
+
if (p === 'linux') return cmdLanLinuxDisable();
|
|
1707
|
+
console.error('lan 命令当前仅支持 Windows / Linux;本机平台: ' + p);
|
|
1708
|
+
process.exit(2);
|
|
1709
|
+
}
|
|
1710
|
+
function cmdLanStatus() {
|
|
1711
|
+
const p = lanPlatform();
|
|
1712
|
+
if (p === 'win32') return cmdLanWinStatus();
|
|
1713
|
+
if (p === 'linux') return cmdLanLinuxStatus();
|
|
1714
|
+
console.log('lan 命令当前仅支持 Windows / Linux;本机平台: ' + p);
|
|
1715
|
+
}
|
|
1463
1716
|
// ---- usage / main ----
|
|
1464
1717
|
function usage() {
|
|
1465
1718
|
console.log([
|
|
@@ -1484,7 +1737,7 @@ function usage() {
|
|
|
1484
1737
|
' yotta-memory token list 列出已登记智能体',
|
|
1485
1738
|
' yotta-memory token revoke --agent <id> 吊销某智能体 token',
|
|
1486
1739
|
' yotta-memory serve [--host 0.0.0.0] [--port 8787] [--no-auth] [--stdio] 启动 MCP 记忆引擎(streamable HTTP;--stdio 本地零进程模式)',
|
|
1487
|
-
' yotta-memory lan enable [--onstart] | disable | status 开机自启管理(Windows
|
|
1740
|
+
' yotta-memory lan enable [--onstart] | disable | status 开机自启管理(Windows:计划任务,默认 ONLOGON,非管理员自动降级用户级 Startup 静默自启;Linux:systemd 用户单元,不可用时自动降级用户 crontab @reboot)',
|
|
1488
1741
|
' yotta-memory --version 版本',
|
|
1489
1742
|
'',
|
|
1490
1743
|
'类型: FACT(公共共享) / PREF(偏好) / BOUND(边界) / COMMIT(承诺)',
|
|
@@ -1591,6 +1844,7 @@ module.exports = {
|
|
|
1591
1844
|
VERSION: VERSION,
|
|
1592
1845
|
userRoot: userRoot,
|
|
1593
1846
|
projectRoot: projectRoot,
|
|
1847
|
+
memoryRoots: memoryRoots,
|
|
1594
1848
|
rememberCore: rememberCore,
|
|
1595
1849
|
recallCore: recallCore,
|
|
1596
1850
|
profileCore: profileCore,
|
|
@@ -1620,4 +1874,34 @@ module.exports = {
|
|
|
1620
1874
|
lanRemoveStartupFiles: lanRemoveStartupFiles,
|
|
1621
1875
|
lanFileHasMarker: lanFileHasMarker,
|
|
1622
1876
|
isSchtasksAccessDenied: isSchtasksAccessDenied,
|
|
1877
|
+
lanServeArgs: lanServeArgs,
|
|
1878
|
+
lanSpawn: lanSpawn,
|
|
1879
|
+
lanPlatform: lanPlatform,
|
|
1880
|
+
shQuote: shQuote,
|
|
1881
|
+
systemdEscapeArg: systemdEscapeArg,
|
|
1882
|
+
lanLinuxSystemdUserDir: lanLinuxSystemdUserDir,
|
|
1883
|
+
lanLinuxUnitPath: lanLinuxUnitPath,
|
|
1884
|
+
lanLinuxSystemctlBin: lanLinuxSystemctlBin,
|
|
1885
|
+
lanLinuxLoginctlBin: lanLinuxLoginctlBin,
|
|
1886
|
+
lanLinuxExecStart: lanLinuxExecStart,
|
|
1887
|
+
lanLinuxUnitContent: lanLinuxUnitContent,
|
|
1888
|
+
lanCrontabBin: lanCrontabBin,
|
|
1889
|
+
lanCrontabLine: lanCrontabLine,
|
|
1890
|
+
lanCrontabRead: lanCrontabRead,
|
|
1891
|
+
lanCrontabWrite: lanCrontabWrite,
|
|
1892
|
+
lanCrontabHasOurLine: lanCrontabHasOurLine,
|
|
1893
|
+
lanCrontabWithoutOurLine: lanCrontabWithoutOurLine,
|
|
1894
|
+
lanLinuxHasSystemd: lanLinuxHasSystemd,
|
|
1895
|
+
lanLinuxSystemctl: lanLinuxSystemctl,
|
|
1896
|
+
lanLinuxInstallSystemd: lanLinuxInstallSystemd,
|
|
1897
|
+
lanLinuxRemoveSystemd: lanLinuxRemoveSystemd,
|
|
1898
|
+
lanLinuxInstallCrontab: lanLinuxInstallCrontab,
|
|
1899
|
+
lanLinuxRemoveCrontab: lanLinuxRemoveCrontab,
|
|
1900
|
+
cmdLanWinEnable: cmdLanWinEnable,
|
|
1901
|
+
cmdLanWinDisable: cmdLanWinDisable,
|
|
1902
|
+
cmdLanWinStatus: cmdLanWinStatus,
|
|
1903
|
+
cmdLanLinuxEnable: cmdLanLinuxEnable,
|
|
1904
|
+
cmdLanLinuxDisable: cmdLanLinuxDisable,
|
|
1905
|
+
cmdLanLinuxStatus: cmdLanLinuxStatus,
|
|
1906
|
+
|
|
1623
1907
|
};
|
package/package.json
CHANGED
package/references/protocol.md
CHANGED