@yottameta/yotta-memory 0.22.3 → 0.22.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/CHANGELOG.md +14 -0
- package/README.md +2 -2
- package/README.zh-CN.md +3 -3
- package/SKILL.md +3 -3
- package/USER_GUIDE.md +4 -3
- package/bin/yotta-memory.js +281 -69
- package/package.json +1 -1
- package/skill-manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## v0.22.5 (2026-10-07)
|
|
2
|
+
|
|
3
|
+
- **修复 Electron 宿主(YottaCode shim)下开机自启拉起 GUI 而非服务**:宿主运行时为 Electron 时(`process.versions.electron`;`process.execPath` 指向 Electron 可执行文件),此前生成的任务 / 兜底 / 定时配置不带 `ELECTRON_RUN_AS_NODE=1`,开机拉起的是桌面程序本体、8787 无监听(新机首例实测)。现在自动检测并统一注入:Windows 主任务与每日备份任务(cmd /c + `set "ELECTRON_RUN_AS_NODE=1"`)、Windows Startup 兜底 .cmd(set 前缀)、Linux systemd 单元 / crontab @reboot / 备份定时器(`Environment=` / 行内前缀)、macOS 备份 LaunchAgent(EnvironmentVariables)。
|
|
4
|
+
- `lan status` 新增告警:任务或 Startup 兜底命令指向 Electron 可执行文件且缺少 `ELECTRON_RUN_AS_NODE` 时,提示重新 `lan enable` 修复。
|
|
5
|
+
- 非 Electron 宿主行为不变(纯 Node 下生成的命令与旧版一致)。
|
|
6
|
+
- `test/lan-windows-autostart.test.js` 新增 8 项(Electron / 非 Electron 双态:任务 XML / 兜底 cmd / 备份任务 / Linux 单元与 crontab / macOS plist / status 告警两例);全量 `npm test` 352/352。
|
|
7
|
+
|
|
8
|
+
## v0.22.4 (2026-10-07)
|
|
9
|
+
|
|
10
|
+
- **修复 Windows `lan enable` 开机自启三缺陷(实测复现)**:此前计划任务用 schtasks 默认交互式主体——① 在登录会话运行会弹控制台窗口;② `--onstart` 开机触发时无登录会话,任务起不来且不补跑(实测 LastTaskResult 267011);③ schtasks 默认带电池限制与 72 小时执行时限。本版改为 **S4U 主体 + 任务 XML 注册**(零依赖):后台静默运行不弹窗口,开机 / 登录触发均可用;默认去掉电池限制与 72h 时限(`ExecutionTimeLimit=PT0S` 不限时);`--onstart` 在 Windows 真正生效(BootTrigger)。任务注册成功时自动清理旧 Startup 兜底文件,避免双份自启。
|
|
11
|
+
- `lan status` 增加显示「登录模式」并对旧交互式任务给出重新 `lan enable` 的修复提示;非管理员执行 `lan disable` 无法移除管理员注册的任务时,明确提示改用管理员终端。
|
|
12
|
+
- 非管理员路径行为不变:schtasks 失败(含 Access denied)自动降级用户级 Startup 静默自启(VBS 自愈保留)。
|
|
13
|
+
- 新增 `test/lan-windows-autostart.test.js`(11 项:任务 XML 结构 / S4U / 无电池与时限 / XML 转义 / XML 注册 / 旧兜底清理 / 权限与非权限失败降级 / status 登录模式与旧任务告警 / disable 双通道)。
|
|
14
|
+
|
|
1
15
|
## v0.22.3 (2026-10-05)
|
|
2
16
|
|
|
3
17
|
- **安装器 junction 守卫**:`bin/install.js` 对目标基目录 / 技能目录为符号链接(含 Windows junction)时 fail-closed 拒绝(退出码 4,与 `install.sh` 同口径)——不再写穿 Hub 链接指向的真源,也不再误删 Hub 侧 `bin/` runtimePayload。统一安装器以分技能灰度补丁落地(模板与其余技能输出不变)。
|
package/README.md
CHANGED
|
@@ -306,7 +306,7 @@ Optional post-upgrade self-check: `yotta-memory config get` (confirm `memory_hom
|
|
|
306
306
|
| `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) |
|
|
307
307
|
| `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`) |
|
|
308
308
|
| `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) |
|
|
309
|
-
| `yotta-memory lan enable [--onstart] / disable / status` | Autostart management (Windows: scheduled task
|
|
309
|
+
| `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) |
|
|
310
310
|
| `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 |
|
|
311
311
|
| `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 |
|
|
312
312
|
| `yotta-memory feedback <file> --useful|--useless [--reason <r>] [--undo]` | Usage feedback (useful/useless adjusts weight / confidence / feedback_net; --undo rolls back the last one) |
|
|
@@ -384,7 +384,7 @@ The store can live on any host or disk (= the memory engine) and be reached by a
|
|
|
384
384
|
yotta-memory lan enable # register autostart (Windows: scheduled task / user-level Startup; Linux: systemd user unit / user crontab)
|
|
385
385
|
yotta-memory lan status # autostart status
|
|
386
386
|
```
|
|
387
|
-
> `lan enable
|
|
387
|
+
> `lan enable` registers a silent S4U scheduled task (no console window); `--onstart` switches to start-at-boot without a logon session (registration needs admin); non-admin `lan enable` auto-degrades to user-level silent Startup; `lan disable` removes it.
|
|
388
388
|
|
|
389
389
|
> On first listen on 0.0.0.0, Windows / the system firewall may ask to allow it — allow it, or other LAN hosts cannot reach it; `--no-auth` disables auth for trusted intranets only.
|
|
390
390
|
|
package/README.zh-CN.md
CHANGED
|
@@ -174,7 +174,7 @@
|
|
|
174
174
|
|
|
175
175
|
- **记忆库即引擎**:`serve` 把记忆目录挂成 MCP 服务,目录随盘走;引擎主机只需装 CLI 当存放点,无需装任何 AI 智能体。
|
|
176
176
|
- **双模式可并存**:本地 `serve --stdio` 零进程(客户端按需拉起);局域网 streamable HTTP(默认 `0.0.0.0:8787`)+ 每智能体 token 鉴权。
|
|
177
|
-
- **开机自启**:`lan enable` 注册开机自启——Windows
|
|
177
|
+
- **开机自启**:`lan enable` 注册开机自启——Windows 优先计划任务(v0.22.4 起 S4U 主体后台静默、不弹窗口;默认登录自启,`--onstart` 开机即启、无登录会话也可启动,计划任务注册需管理员),非管理员自动降级为**用户级 Startup 静默自启**(免管理员,启动脚本内联启动命令、被清理也会在开机时自动重建,v0.6.3 起不再弹 80070002);**v0.22.5 起 Electron 宿主(如 YottaCode)自动注入 ELECTRON_RUN_AS_NODE,任务不再拉起 GUI**;Linux 优先 **systemd 用户单元**(`systemctl --user`,登录自启;`--onstart` 附加 `loginctl enable-linger` 开机即启),systemd 不可用时自动降级**用户 crontab @reboot**(v0.6.4);`lan disable` 移除,`lan status` 查询。
|
|
178
178
|
- **安全边界**:管理动作(init / config / token / lan / serve)不进 MCP,token 不远程暴露;远程智能体只能读写记忆,且路径限记忆库内(export/import 的 out/src 必须落在库内)、distill 不支持 `--model`(仅本地 CLI),不能改配置、不能管 token。
|
|
179
179
|
- 完整操作步骤见上文「局域网多机共享」章节与 [USER_GUIDE.md](USER_GUIDE.md)。
|
|
180
180
|
|
|
@@ -350,7 +350,7 @@ bash install.sh --agent <智能体名称>
|
|
|
350
350
|
| `yotta-memory iam <id> [--name <显示名>] [--user <用户名>] [--relationship <关系>] [--force]` | 登记本智能体唯一身份并自动落自我档案(`agents.json`,ID 必须唯一;可选扩展显示名 / 用户 / 关系)|
|
|
351
351
|
| `yotta-memory token new --agent <id> [--force]` / `token list` / `token revoke --agent <id>` | 为智能体生成 / 列出 / 吊销访问 token(登记于记忆库 `.server/tokens.json`;同 ID 已被其它来源占用需 `--force` 覆盖,防不同智能体合流)|
|
|
352
352
|
| `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 鉴权)|
|
|
353
|
-
| `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows
|
|
353
|
+
| `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:计划任务 S4U 主体后台静默、不弹窗口;默认登录自启、`--onstart` 开机即启(无登录会话也可启动,注册需管理员),非管理员自动降级用户级 Startup 静默自启;Linux:systemd 用户单元,不可用时自动降级用户 crontab @reboot)|
|
|
354
354
|
| `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 淘汰候选与晋升建议(冷却期与常青条目豁免)|
|
|
355
355
|
| `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` 查批次 |
|
|
356
356
|
| `yotta-memory feedback <文件|主题> --useful|--useless [--reason <原因>] [--undo]` | 使用反馈(useful/useless 调整 weight / confidence / feedback_net;`--undo` 回滚最近一次)|
|
|
@@ -428,7 +428,7 @@ yotta-memory recall --type FACT --limit 10
|
|
|
428
428
|
yotta-memory lan enable # 注册开机自启(Windows:计划任务/用户级 Startup;Linux:systemd 用户单元/用户 crontab)
|
|
429
429
|
yotta-memory lan status # 查看自启状态
|
|
430
430
|
```
|
|
431
|
-
> `lan enable
|
|
431
|
+
> `lan enable` 注册 S4U 后台静默计划任务(不弹窗口);`--onstart` 改为开机即启、无登录会话也可启动(注册需管理员);非管理员时 `lan enable` 自动改用用户级 Startup 静默自启(无需管理员);`lan disable` 移除自启。
|
|
432
432
|
|
|
433
433
|
> 首次监听 0.0.0.0 时,Windows / 系统防火墙可能询问是否放行,需允许放行,否则局域网内其它主机无法访问;`--no-auth` 会关闭鉴权,仅限可信内网使用。
|
|
434
434
|
|
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.22.
|
|
4
|
+
version: 0.22.5
|
|
5
5
|
license: MIT
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -333,7 +333,7 @@ yotta-memory doctor --json
|
|
|
333
333
|
| `yotta-memory token new --agent <id> [--force]` / `token list` / `token revoke --agent <id>` | 每智能体访问 token:生成 / 列出 / 吊销(登记 `<记忆库>/.server/tokens.json`;同 ID 已被其它来源占用需 `--force` 覆盖,防不同智能体合流)|
|
|
334
334
|
| `yotta-memory serve [--host 0.0.0.0] [--port 8787] [--no-auth] [--stdio] [--tools core|full]` | 启动 MCP 记忆引擎(streamable HTTP 局域网 / --stdio 本地零进程模式;Bearer token + X-Agent-Id + X-Agent-Key 鉴权;工具分组默认 full)|
|
|
335
335
|
| `yotta-memory runtime list / install <tarball|版本> [--from-current] [--force] / use <版本> [--restart] / rollback [--restart] / status` | 运行时稳定入口(runtime.json + versions + current;安装 / 切换 / 回滚 / 查看漂移;`--restart` 尝试重启受管 server,失败自动切回旧版本);漂移诊断用 `doctor --runtime` |
|
|
336
|
-
| `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows
|
|
336
|
+
| `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:任务计划程序,S4U 主体后台静默——v0.22.4 起不弹窗口,`--onstart` 无登录会话也可启动;任务 XML 注册,去电池限制与 72h 时限;非管理员自动降级用户级 Startup 静默自启,v0.6.3 起 VBS 自愈不弹 80070002;v0.22.5 起 Electron 宿主(如 YottaCode)自动注入 ELECTRON_RUN_AS_NODE,不再拉起 GUI;Linux:systemd 用户单元,不可用时自动降级用户 crontab @reboot)|
|
|
337
337
|
| `yotta-memory feedback <文件|主题> --useful|--useless [--reason <原因>] [--undo]` | 显式使用反馈(v0.8.0 自我学习闭环:useful → weight×1.2 / useless → weight×0.8,confidence / feedback_net 同步演化;`--undo` 回滚最近一次;审计写 `.archive/feedback-<日期>.jsonl`)|
|
|
338
338
|
| `yotta-memory maintain [--dry-run] [--apply] [--purge] [--threshold N] [--age N] [--dedup] [--dedup --apply] [--merge A,B] [--rules]` | 记忆自组织(v0.8.0 自我进化 + v0.10.0 自动合并):默认 dry-run 预览;`--apply` 执行归档(immutable / BOUND 豁免;私密归档入 `.archive/private/<owner>/<type>/`),`--purge` 才真删遗忘候选;`--dedup` 查重并给**置信度分档**(≥0.85 高置信 / 0.65–0.85 建议手动 / 其余忽略),`--dedup --apply` 自动合并同归属高置信组(写批次审计可回滚;与归档互斥,不误归档);`--merge A,B` 手动合并两条;`--rules` 只读输出重复踩坑 → 建议升级为规则的清单(阈值 `maintain_rule_min_hits`,默认 3;只给建议不写 BOUND);审计写 `.archive/audit-<日期>.jsonl`)|
|
|
339
339
|
| `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]` | 周期摘要压缩(v0.10.0 压缩遗忘 + v0.18.0 提案闸门 + v0.18.1 日期 / 标记:把超龄 + 长期闲置 + 低效用的同主题旧记忆归纳成**带溯源**的周期摘要并留在活跃区,原文整体进 `.archive/`;摘要正文按条目 `created` 把「昨天 / 上周 / 本月 / 今年」等绝对化,归档副本追加一行巩固标记;默认 dry-run;immutable / BOUND 豁免,活跃 / 高效用记忆不动;`--apply` 执行并写批次审计,`--undo <batch>` 剥离标记并回滚(幂等),`--batches` 查近期批次;`--model` 仅本地 CLI)|
|
|
@@ -555,7 +555,7 @@ MCP 模式由宿主显式声明身份:stdio 用 `--agent-id <agent_id> --agent
|
|
|
555
555
|
> 记忆盘场景:硬盘插上 → AI 检查盘上目录是否有数据 → 有则接入 + config 记住 → 插盘即恢复,机器记住位置。
|
|
556
556
|
|
|
557
557
|
**步骤 0.5 启动记忆引擎(仅引擎主机,供远程接入)**
|
|
558
|
-
- 本机若作引擎:🔒 **征得同意后**启动服务——临时运行 `yotta-memory serve`(默认 `0.0.0.0:8787`,Bearer token + X-Agent-Id + X-Agent-Key 鉴权;`--no-auth` 仅限可信内网),或注册开机自启 `yotta-memory lan enable`(Windows
|
|
558
|
+
- 本机若作引擎:🔒 **征得同意后**启动服务——临时运行 `yotta-memory serve`(默认 `0.0.0.0:8787`,Bearer token + X-Agent-Id + X-Agent-Key 鉴权;`--no-auth` 仅限可信内网),或注册开机自启 `yotta-memory lan enable`(Windows:任务计划程序 S4U 后台静默,默认登录自启、`--onstart` 开机即启;非管理员自动降级用户级 Startup 静默自启,免管理员)。
|
|
559
559
|
- 本地零进程模式:本机 AI 也可用 `serve --stdio` 由 MCP 客户端按需拉起 CLI(无常驻进程)。
|
|
560
560
|
- 远程客户端接入前,先确认引擎主机 serve 已运行(`lan status` 可查)。
|
|
561
561
|
|
package/USER_GUIDE.md
CHANGED
|
@@ -334,10 +334,11 @@ yotta-memory init --dir /srv/yotta-memory # 新库:初始化(
|
|
|
334
334
|
`lan enable` 会先准备稳定运行时入口:没有 `<runtimeRoot>/current` 时自动执行 `runtime install --from-current`,然后把计划任务 / systemd / crontab 指向 `<runtimeRoot>/current/bin/yotta-memory.js`。如需手动准备,可先执行 `yotta-memory runtime install --from-current && yotta-memory runtime status`。
|
|
335
335
|
|
|
336
336
|
```bash
|
|
337
|
-
# Windows
|
|
338
|
-
# v0.6.3
|
|
337
|
+
# Windows:内置命令(优先计划任务——v0.22.4 起 S4U 主体后台静默、不弹窗口;
|
|
338
|
+
# 非管理员自动降级用户级 Startup 静默自启;v0.6.3 起启动脚本自愈——启动文件被清理也会在开机时自动重建;
|
|
339
|
+
# v0.22.5 起 Electron 宿主(如 YottaCode)自动注入 ELECTRON_RUN_AS_NODE,不再拉起 GUI)
|
|
339
340
|
yotta-memory lan enable # 登录后自动启动(默认)
|
|
340
|
-
yotta-memory lan enable --onstart #
|
|
341
|
+
yotta-memory lan enable --onstart # 开机即启、无登录会话也可启动(计划任务注册需管理员)
|
|
341
342
|
yotta-memory lan status # 查看状态
|
|
342
343
|
yotta-memory lan disable # 取消
|
|
343
344
|
|
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.22.
|
|
28
|
+
const VERSION = '0.22.5';
|
|
29
29
|
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']);
|
|
30
30
|
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']);
|
|
31
31
|
|
|
@@ -298,7 +298,7 @@ const HELP_MODEL = [
|
|
|
298
298
|
helpOption('--agent-key-file', '<文件>', 'stdio 模式下从文件读取 agent_key', '宿主使用标准 key 文件时', ''),
|
|
299
299
|
] },
|
|
300
300
|
{ name: 'lan', usage: 'lan <子命令> [选项]', what: '管理开机自启', when: '需要元忆服务随系统或登录启动时', subcommands: [
|
|
301
|
-
helpSub('enable', 'enable [--onstart]', '启用开机自启', '希望元忆服务长期可用时', [helpOption('--onstart', '', '同时启用未登录也启动', 'Linux 上希望开机即启动、不依赖登录时', '
|
|
301
|
+
helpSub('enable', 'enable [--onstart]', '启用开机自启', '希望元忆服务长期可用时', [helpOption('--onstart', '', '同时启用未登录也启动', 'Windows / Linux 上希望开机即启动、不依赖登录时', '')]),
|
|
302
302
|
helpSub('disable', 'disable', '关闭开机自启', '不再需要常驻服务时', []),
|
|
303
303
|
helpSub('status', 'status', '查看自启状态', '确认服务是否会随系统启动时', []),
|
|
304
304
|
] },
|
|
@@ -2919,6 +2919,12 @@ function backupWindowsTaskXml(opts) {
|
|
|
2919
2919
|
const principal = userId
|
|
2920
2920
|
? '<Principal id="Author"><UserId>' + xmlEscape(userId) + '</UserId><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal>'
|
|
2921
2921
|
: '<Principal id="Author"><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal>';
|
|
2922
|
+
const cmdPath = opts.cmdPath || process.env.ComSpec || 'cmd.exe';
|
|
2923
|
+
// v0.22.5: Electron 宿主(如 YottaCode shim)下 process.execPath 是 Electron 可执行文件,
|
|
2924
|
+
// 任务需经 cmd /c + ELECTRON_RUN_AS_NODE=1 包装,否则拉起的是桌面程序本体而非 Node。
|
|
2925
|
+
const action = lanElectronHost(opts)
|
|
2926
|
+
? '<Actions Context="Author"><Exec><Command>' + xmlEscape(cmdPath) + '</Command><Arguments>/c "set "ELECTRON_RUN_AS_NODE=1" && "' + xmlEscape(nodePath) + '" "' + xmlEscape(scriptPath) + '" backup ensure-daily"</Arguments></Exec></Actions>'
|
|
2927
|
+
: '<Actions Context="Author"><Exec><Command>' + xmlEscape(nodePath) + '</Command><Arguments>"' + xmlEscape(scriptPath) + '" backup ensure-daily</Arguments></Exec></Actions>';
|
|
2922
2928
|
return [
|
|
2923
2929
|
'<?xml version="1.0" encoding="UTF-16"?>',
|
|
2924
2930
|
'<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">',
|
|
@@ -2940,7 +2946,7 @@ function backupWindowsTaskXml(opts) {
|
|
|
2940
2946
|
'<ExecutionTimeLimit>PT2H</ExecutionTimeLimit>',
|
|
2941
2947
|
'<Priority>7</Priority>',
|
|
2942
2948
|
'</Settings>',
|
|
2943
|
-
|
|
2949
|
+
action,
|
|
2944
2950
|
'</Task>',
|
|
2945
2951
|
].join('\r\n');
|
|
2946
2952
|
}
|
|
@@ -2948,16 +2954,20 @@ function backupSystemdServiceContent(opts) {
|
|
|
2948
2954
|
opts = opts || {};
|
|
2949
2955
|
const nodePath = opts.nodePath || process.execPath;
|
|
2950
2956
|
const scriptPath = opts.scriptPath || runtimeManagedScript();
|
|
2951
|
-
|
|
2957
|
+
const lines = [
|
|
2952
2958
|
'[Unit]',
|
|
2953
2959
|
'Description=Yotta Memory daily backup',
|
|
2954
2960
|
'',
|
|
2955
2961
|
'[Service]',
|
|
2956
2962
|
'Type=oneshot',
|
|
2963
|
+
];
|
|
2964
|
+
if (lanElectronHost(opts)) lines.push('Environment=ELECTRON_RUN_AS_NODE=1');
|
|
2965
|
+
lines.push(
|
|
2957
2966
|
'ExecStart=' + systemdEscapeArg(nodePath) + ' ' + systemdEscapeArg(scriptPath) + ' backup ensure-daily',
|
|
2958
2967
|
'Nice=10',
|
|
2959
2968
|
'',
|
|
2960
|
-
|
|
2969
|
+
);
|
|
2970
|
+
return lines.join('\n');
|
|
2961
2971
|
}
|
|
2962
2972
|
function backupSystemdTimerContent(opts) {
|
|
2963
2973
|
opts = opts || {};
|
|
@@ -2983,7 +2993,8 @@ function backupCronLine(opts) {
|
|
|
2983
2993
|
const scriptPath = opts.scriptPath || runtimeManagedScript();
|
|
2984
2994
|
const logPath = opts.logPath || '';
|
|
2985
2995
|
const redirect = logPath ? ' >> ' + shQuote(logPath) + ' 2>&1' : '';
|
|
2986
|
-
|
|
2996
|
+
const env = lanElectronHost(opts) ? 'ELECTRON_RUN_AS_NODE=1 ' : '';
|
|
2997
|
+
return time.minute + ' ' + time.hour + ' * * * ' + env + shQuote(nodePath) + ' ' + shQuote(scriptPath) + ' backup ensure-daily' + redirect + ' ' + BACKUP_CRON_MARKER;
|
|
2987
2998
|
}
|
|
2988
2999
|
function backupLaunchdPlist(opts) {
|
|
2989
3000
|
opts = opts || {};
|
|
@@ -2995,6 +3006,11 @@ function backupLaunchdPlist(opts) {
|
|
|
2995
3006
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
2996
3007
|
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
2997
3008
|
'<plist version="1.0"><dict>',
|
|
3009
|
+
];
|
|
3010
|
+
if (lanElectronHost(opts)) {
|
|
3011
|
+
lines.push('<key>EnvironmentVariables</key><dict><key>ELECTRON_RUN_AS_NODE</key><string>1</string></dict>');
|
|
3012
|
+
}
|
|
3013
|
+
lines.push(
|
|
2998
3014
|
'<key>Label</key><string>' + BACKUP_LAUNCHD_LABEL + '</string>',
|
|
2999
3015
|
'<key>ProgramArguments</key><array>',
|
|
3000
3016
|
'<string>' + xmlEscape(nodePath) + '</string>',
|
|
@@ -3006,7 +3022,7 @@ function backupLaunchdPlist(opts) {
|
|
|
3006
3022
|
'<key>Hour</key><integer>' + time.hour + '</integer>',
|
|
3007
3023
|
'<key>Minute</key><integer>' + time.minute + '</integer>',
|
|
3008
3024
|
'</dict>',
|
|
3009
|
-
|
|
3025
|
+
);
|
|
3010
3026
|
if (logPath) {
|
|
3011
3027
|
lines.push('<key>StandardOutPath</key><string>' + xmlEscape(logPath) + '</string>');
|
|
3012
3028
|
lines.push('<key>StandardErrorPath</key><string>' + xmlEscape(logPath) + '</string>');
|
|
@@ -3046,7 +3062,7 @@ function backupScheduleEnableCore(opts) {
|
|
|
3046
3062
|
const userId = opts.userId || (process.env.USERDOMAIN && process.env.USERNAME ? process.env.USERDOMAIN + '\\' + process.env.USERNAME : '');
|
|
3047
3063
|
const taskName = opts.taskName || BACKUP_TASK_NAME;
|
|
3048
3064
|
const xmlPath = opts.xmlPath || path.join(os.tmpdir(), taskName + '.xml');
|
|
3049
|
-
fs.writeFileSync(xmlPath, '\ufeff' + backupWindowsTaskXml({ time: time, nodePath: nodePath, scriptPath: scriptPath, userId: userId }), 'utf16le');
|
|
3065
|
+
fs.writeFileSync(xmlPath, '\ufeff' + backupWindowsTaskXml({ time: time, nodePath: nodePath, scriptPath: scriptPath, userId: userId, electronHost: opts.electronHost }), 'utf16le');
|
|
3050
3066
|
schedulerExec(opts, 'schtasks', ['/create', '/tn', taskName, '/xml', xmlPath, '/f']);
|
|
3051
3067
|
return { error: false, scheduler: 'windows-task', taskName: taskName, text: '已注册 Windows 每日备份任务: ' + taskName };
|
|
3052
3068
|
}
|
|
@@ -3055,7 +3071,7 @@ function backupScheduleEnableCore(opts) {
|
|
|
3055
3071
|
const servicePath = path.join(unitDir, BACKUP_SYSTEMD_SERVICE);
|
|
3056
3072
|
const timerPath = path.join(unitDir, BACKUP_SYSTEMD_TIMER);
|
|
3057
3073
|
fs.mkdirSync(unitDir, { recursive: true });
|
|
3058
|
-
fs.writeFileSync(servicePath, backupSystemdServiceContent({ nodePath: nodePath, scriptPath: scriptPath }), 'utf8');
|
|
3074
|
+
fs.writeFileSync(servicePath, backupSystemdServiceContent({ nodePath: nodePath, scriptPath: scriptPath, electronHost: opts.electronHost }), 'utf8');
|
|
3059
3075
|
fs.writeFileSync(timerPath, backupSystemdTimerContent({ time: time }), 'utf8');
|
|
3060
3076
|
const reload = schedulerTry(opts, 'systemctl', ['--user', 'daemon-reload']);
|
|
3061
3077
|
const enable = reload.ok ? schedulerTry(opts, 'systemctl', ['--user', 'enable', '--now', BACKUP_SYSTEMD_TIMER]) : reload;
|
|
@@ -3063,7 +3079,7 @@ function backupScheduleEnableCore(opts) {
|
|
|
3063
3079
|
return { error: false, scheduler: 'linux-systemd', unit: BACKUP_SYSTEMD_TIMER, text: '已注册 systemd 用户定时器: ' + BACKUP_SYSTEMD_TIMER };
|
|
3064
3080
|
}
|
|
3065
3081
|
const logPath = opts.logPath || path.join(os.homedir(), '.yottamemory', 'backup.log');
|
|
3066
|
-
const line = backupCronLine({ time: time, nodePath: nodePath, scriptPath: scriptPath, logPath: logPath });
|
|
3082
|
+
const line = backupCronLine({ time: time, nodePath: nodePath, scriptPath: scriptPath, logPath: logPath, electronHost: opts.electronHost });
|
|
3067
3083
|
const current = schedulerTry(opts, 'crontab', ['-l']);
|
|
3068
3084
|
const kept = current.stdout.split(/\r?\n/).filter(function (item) {
|
|
3069
3085
|
return item && item.indexOf(BACKUP_CRON_MARKER) === -1;
|
|
@@ -3087,6 +3103,7 @@ function backupScheduleEnableCore(opts) {
|
|
|
3087
3103
|
nodePath: nodePath,
|
|
3088
3104
|
scriptPath: scriptPath,
|
|
3089
3105
|
logPath: opts.logPath || path.join(os.homedir(), 'Library', 'Logs', 'yotta-memory-backup.log'),
|
|
3106
|
+
electronHost: opts.electronHost,
|
|
3090
3107
|
}), 'utf8');
|
|
3091
3108
|
const uid = typeof process.getuid === 'function' ? process.getuid() : '';
|
|
3092
3109
|
const bootstrap = schedulerTry(opts, 'launchctl', ['bootstrap', 'gui/' + uid, plistPath]);
|
|
@@ -10381,7 +10398,9 @@ function cmdServeStdio(opts) {
|
|
|
10381
10398
|
}
|
|
10382
10399
|
|
|
10383
10400
|
// ---- lan command (autostart management for serve) ----
|
|
10384
|
-
// Windows dual mechanism: (1) schtasks scheduled task (default, needs
|
|
10401
|
+
// Windows dual mechanism: (1) schtasks scheduled task registered from task XML (default, needs
|
|
10402
|
+
// permission; v0.22.4: S4U principal -> silent in background for both logon/boot triggers, no
|
|
10403
|
+
// console window; battery limits and the 72h execution time limit removed); (2) non-admin
|
|
10385
10404
|
// (Access denied) auto-fallback to user-level Startup silent autostart (VBS sh.Run <autostart.cmd>,
|
|
10386
10405
|
// 0, False + autostart.cmd; node path process.execPath auto-detected, 0.5.4)
|
|
10387
10406
|
// Linux (v0.6.4): (1) systemd user unit (systemctl --user enable/start, login autostart; --onstart
|
|
@@ -10392,11 +10411,13 @@ const LAN_GEN_MARKER = 'Generated by yotta-memory lan enable';
|
|
|
10392
10411
|
const LAN_UNIT_NAME = 'yotta-memory-serve.service';
|
|
10393
10412
|
const LAN_CRONTAB_MARKER = '#YTM_LAN:yotta-memory-serve';
|
|
10394
10413
|
function lanTaskRunCmd(opts) {
|
|
10414
|
+
opts = opts || {};
|
|
10395
10415
|
const host = opts.host || '0.0.0.0';
|
|
10396
10416
|
const port = opts.port || 8787;
|
|
10397
10417
|
// schtasks /tr 不接受多余内嵌引号:路径无空格不加引号,含空格/引号用 \" 转义(0.5.2 修复)
|
|
10398
10418
|
const q = (p) => /[\s"]/.test(p) ? '\\"' + p.replace(/"/g, '\\"') + '\\"' : p;
|
|
10399
|
-
|
|
10419
|
+
const prefix = lanElectronHost(opts) ? 'set "ELECTRON_RUN_AS_NODE=1" && ' : '';
|
|
10420
|
+
return prefix + q(process.execPath) + ' ' + q(runtimeManagedScript()) + ' serve --host ' + host + ' --port ' + port;
|
|
10400
10421
|
}
|
|
10401
10422
|
|
|
10402
10423
|
function lanServeArgs(opts) {
|
|
@@ -10412,6 +10433,28 @@ function lanSpawn(bin, args, spawnOpts) {
|
|
|
10412
10433
|
}
|
|
10413
10434
|
// platform override for testing/advanced use (YOTTA_LAN_PLATFORM=linux/win32/...)
|
|
10414
10435
|
function lanPlatform() { return process.env.YOTTA_LAN_PLATFORM || process.platform; }
|
|
10436
|
+
// ---- v0.22.5: Electron 宿主(YottaCode shim)适配 ----
|
|
10437
|
+
// 背景:宿主运行时为 Electron 时(如 YottaCode.exe 以 ELECTRON_RUN_AS_NODE=1 当 Node 运行本脚本),
|
|
10438
|
+
// process.execPath 指向 Electron 可执行文件;自启 / 定时任务环境不带 ELECTRON_RUN_AS_NODE=1 时,
|
|
10439
|
+
// 拉起的是桌面程序本体而非 Node 服务(0.22.4 后新机首例实测)。生成任务 / 兜底 / 定时配置时统一注入。
|
|
10440
|
+
function lanElectronHost(opts) {
|
|
10441
|
+
opts = opts || {};
|
|
10442
|
+
if (typeof opts.electronHost === 'boolean') return opts.electronHost;
|
|
10443
|
+
if (process.env.YOTTA_LAN_ELECTRON_HOST === '1') return true;
|
|
10444
|
+
if (process.env.YOTTA_LAN_ELECTRON_HOST === '0') return false;
|
|
10445
|
+
return !!(process.versions && process.versions.electron);
|
|
10446
|
+
}
|
|
10447
|
+
// 任务 / 兜底命令是否指向 Electron 可执行文件且缺少 ELECTRON_RUN_AS_NODE(旧任务告警用)
|
|
10448
|
+
function lanTaskCommandNeedsElectronEnv(text) {
|
|
10449
|
+
const t = String(text || '');
|
|
10450
|
+
if (!t || /ELECTRON_RUN_AS_NODE/.test(t)) return false;
|
|
10451
|
+
if (/electron|yottacode/i.test(t)) return true;
|
|
10452
|
+
if (lanElectronHost({})) {
|
|
10453
|
+
const exe = String(process.execPath || '');
|
|
10454
|
+
if (exe && t.toLowerCase().indexOf(exe.toLowerCase()) !== -1) return true;
|
|
10455
|
+
}
|
|
10456
|
+
return false;
|
|
10457
|
+
}
|
|
10415
10458
|
// sh-style single-quote quoting (crontab line is executed via /bin/sh -c)
|
|
10416
10459
|
function shQuote(s) {
|
|
10417
10460
|
const p = String(s);
|
|
@@ -10425,31 +10468,37 @@ function systemdEscapeArg(s) {
|
|
|
10425
10468
|
if (!/[\s"\\$;]/.test(p)) return p;
|
|
10426
10469
|
return '"' + p.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
|
|
10427
10470
|
}
|
|
10428
|
-
function lanStartupDir() {
|
|
10471
|
+
function lanStartupDir(opts) {
|
|
10472
|
+
if (opts && opts.startupDir) return String(opts.startupDir);
|
|
10429
10473
|
// 测试/高级用途可用 YOTTA_LAN_STARTUP_DIR 覆盖(默认用户级 Startup 目录,免管理员)
|
|
10430
10474
|
if (process.env.YOTTA_LAN_STARTUP_DIR) return process.env.YOTTA_LAN_STARTUP_DIR;
|
|
10431
10475
|
const base = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
|
|
10432
10476
|
return path.join(base, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
|
|
10433
10477
|
}
|
|
10434
|
-
function lanAutostartDir() {
|
|
10478
|
+
function lanAutostartDir(opts) {
|
|
10479
|
+
if (opts && opts.autostartDir) return String(opts.autostartDir);
|
|
10435
10480
|
// 产品自有目录 ~/.yottamemory/autostart/(引擎数据目录内,非系统配置);测试可用 YOTTA_LAN_AUTOSTART_DIR 覆盖
|
|
10436
10481
|
if (process.env.YOTTA_LAN_AUTOSTART_DIR) return process.env.YOTTA_LAN_AUTOSTART_DIR;
|
|
10437
10482
|
return path.join(os.homedir(), '.yottamemory', 'autostart');
|
|
10438
10483
|
}
|
|
10439
|
-
function lanVbsPath() { return path.join(lanStartupDir(), 'yotta-memory-serve.vbs'); }
|
|
10440
|
-
function lanAutostartCmdPath() { return path.join(lanAutostartDir(), 'yotta-memory-autostart.cmd'); }
|
|
10484
|
+
function lanVbsPath(opts) { return path.join(lanStartupDir(opts), 'yotta-memory-serve.vbs'); }
|
|
10485
|
+
function lanAutostartCmdPath(opts) { return path.join(lanAutostartDir(opts), 'yotta-memory-autostart.cmd'); }
|
|
10441
10486
|
function lanLogPath(opts) {
|
|
10487
|
+
if (opts && opts.logPath) return String(opts.logPath);
|
|
10442
10488
|
if (process.env.YOTTA_LAN_LOG_FILE) return process.env.YOTTA_LAN_LOG_FILE;
|
|
10443
10489
|
return path.join(os.homedir(), '.yottamemory', 'serve-' + (opts.port || 8787) + '.log');
|
|
10444
10490
|
}
|
|
10445
10491
|
function lanAutostartCmdContent(opts) {
|
|
10492
|
+
opts = opts || {};
|
|
10446
10493
|
const host = opts.host || '0.0.0.0';
|
|
10447
10494
|
const port = opts.port || 8787;
|
|
10448
10495
|
const q = (p) => '"' + String(p).replace(/"/g, '""') + '"';
|
|
10449
10496
|
// 生成文件用纯 ASCII(避免 cmd/VBS 按系统代码页读取中文注释乱码;VBS 自愈内联同一内容)
|
|
10497
|
+
// v0.22.5: Electron 宿主下需先设 ELECTRON_RUN_AS_NODE=1,否则拉起桌面程序本体而非 Node。
|
|
10450
10498
|
return '@echo off\r\n'
|
|
10451
10499
|
+ 'chcp 65001 >nul\r\n'
|
|
10452
10500
|
+ 'rem ' + LAN_GEN_MARKER + '; remove with: yotta-memory lan disable (English only, keep ASCII)\r\n'
|
|
10501
|
+
+ (lanElectronHost(opts) ? 'set "ELECTRON_RUN_AS_NODE=1"\r\n' : '')
|
|
10453
10502
|
+ q(process.execPath) + ' ' + q(runtimeManagedScript()) + ' serve --host ' + host + ' --port ' + port + ' >> ' + q(lanLogPath(opts)) + ' 2>&1\r\n';
|
|
10454
10503
|
}
|
|
10455
10504
|
function lanVbsContent(opts) {
|
|
@@ -10467,7 +10516,7 @@ function lanVbsContent(opts) {
|
|
|
10467
10516
|
return '"' + seg.replace(/"/g, '""') + '"';
|
|
10468
10517
|
})
|
|
10469
10518
|
.join(' & Chr(13) & Chr(10) & ');
|
|
10470
|
-
const vbsCmdPath = String(lanAutostartCmdPath()).replace(/"/g, '""');
|
|
10519
|
+
const vbsCmdPath = String(lanAutostartCmdPath(opts)).replace(/"/g, '""');
|
|
10471
10520
|
const lines = [
|
|
10472
10521
|
"' " + LAN_GEN_MARKER + '; remove with: yotta-memory lan disable',
|
|
10473
10522
|
"' v0.6.3 self-heal: always rebuild autostart.cmd from embedded content, then run it (fix 80070002)",
|
|
@@ -10486,16 +10535,16 @@ function lanVbsContent(opts) {
|
|
|
10486
10535
|
return lines.join('\r\n') + '\r\n';
|
|
10487
10536
|
}
|
|
10488
10537
|
function lanInstallStartup(opts) {
|
|
10489
|
-
fs.mkdirSync(lanAutostartDir(), { recursive: true });
|
|
10490
|
-
fs.writeFileSync(lanAutostartCmdPath(), lanAutostartCmdContent(opts), 'utf8');
|
|
10491
|
-
fs.mkdirSync(lanStartupDir(), { recursive: true });
|
|
10538
|
+
fs.mkdirSync(lanAutostartDir(opts), { recursive: true });
|
|
10539
|
+
fs.writeFileSync(lanAutostartCmdPath(opts), lanAutostartCmdContent(opts), 'utf8');
|
|
10540
|
+
fs.mkdirSync(lanStartupDir(opts), { recursive: true });
|
|
10492
10541
|
// VBS 用 UTF-16LE+BOM:wscript 按 Unicode 读取,中文路径不乱码(纯 ANSI 读取会按系统代码页误读)
|
|
10493
|
-
fs.writeFileSync(lanVbsPath(), '\ufeff' + lanVbsContent(opts), 'utf16le');
|
|
10542
|
+
fs.writeFileSync(lanVbsPath(opts), '\ufeff' + lanVbsContent(opts), 'utf16le');
|
|
10494
10543
|
}
|
|
10495
|
-
function lanRemoveStartupFiles() {
|
|
10544
|
+
function lanRemoveStartupFiles(opts) {
|
|
10496
10545
|
// 只删除带产品标记的生成文件,绝不误删用户自己的 Startup 文件
|
|
10497
10546
|
const removed = [];
|
|
10498
|
-
for (const f of [lanVbsPath(), lanAutostartCmdPath()]) {
|
|
10547
|
+
for (const f of [lanVbsPath(opts), lanAutostartCmdPath(opts)]) {
|
|
10499
10548
|
try {
|
|
10500
10549
|
if (lanFileHasMarker(f)) { fs.unlinkSync(f); removed.push(f); }
|
|
10501
10550
|
} catch (e) { /* 单个文件失败不阻断其它清理 */ }
|
|
@@ -10512,10 +10561,145 @@ function lanFileHasMarker(f) {
|
|
|
10512
10561
|
} catch (e) { return false; }
|
|
10513
10562
|
}
|
|
10514
10563
|
function isSchtasksAccessDenied(e) {
|
|
10515
|
-
const msg = String((e && e.stderr) || (e && e.message) || e);
|
|
10564
|
+
const msg = typeof e === 'string' ? e : String((e && e.stderr) || (e && e.message) || e || '');
|
|
10516
10565
|
return /access\s+is\s+denied|access\s+denied|拒绝访问/i.test(msg);
|
|
10517
10566
|
}
|
|
10518
10567
|
|
|
10568
|
+
// ---- lan Windows task XML (v0.22.4: S4U silent principal) ----
|
|
10569
|
+
// 背景(0.22.3 及更早缺陷):schtasks /create 未指定主体 -> 默认交互式(仅登录时运行):
|
|
10570
|
+
// ① 在登录会话里运行控制台程序会弹窗口;② --onstart 开机触发时无登录会话,任务起不来且不补跑。
|
|
10571
|
+
// 修复:改为注册任务 XML —— S4U 主体(“不管用户是否登录都运行”,不存密码、非交互、无窗口),
|
|
10572
|
+
// 默认去掉电池限制与 72 小时执行时限(PT0S = 不限时),开机/登录触发均可后台静默启动。
|
|
10573
|
+
function lanSchTasksBin() { return process.env.YOTTA_LAN_SCHTASKS_BIN || 'schtasks'; }
|
|
10574
|
+
function lanWindowsRunSchtasks(opts, args) {
|
|
10575
|
+
if (opts && typeof opts.execFileFn === 'function') {
|
|
10576
|
+
try {
|
|
10577
|
+
const out = opts.execFileFn(lanSchTasksBin(), args);
|
|
10578
|
+
return { status: 0, stdout: String(out || ''), stderr: '' };
|
|
10579
|
+
} catch (e) {
|
|
10580
|
+
return {
|
|
10581
|
+
status: typeof e.status === 'number' ? e.status : 1,
|
|
10582
|
+
stdout: String((e && e.stdout) || ''),
|
|
10583
|
+
stderr: String((e && e.stderr) || (e && e.message) || e),
|
|
10584
|
+
};
|
|
10585
|
+
}
|
|
10586
|
+
}
|
|
10587
|
+
const r = lanSpawn(lanSchTasksBin(), args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
10588
|
+
return {
|
|
10589
|
+
status: r.error ? 1 : r.status,
|
|
10590
|
+
stdout: String(r.stdout || ''),
|
|
10591
|
+
stderr: String(r.stderr || (r.error && r.error.message) || ''),
|
|
10592
|
+
};
|
|
10593
|
+
}
|
|
10594
|
+
function lanWindowsTaskUser(opts) {
|
|
10595
|
+
opts = opts || {};
|
|
10596
|
+
if (opts.userId) return String(opts.userId);
|
|
10597
|
+
// 测试/高级用途可用 YOTTA_LAN_TASK_USER 覆盖(S4U 主体必须是当前用户名)
|
|
10598
|
+
if (process.env.YOTTA_LAN_TASK_USER) return String(process.env.YOTTA_LAN_TASK_USER);
|
|
10599
|
+
if (process.env.USERDOMAIN && process.env.USERNAME) return process.env.USERDOMAIN + '\\' + process.env.USERNAME;
|
|
10600
|
+
if (process.env.COMPUTERNAME && process.env.USERNAME) return process.env.COMPUTERNAME + '\\' + process.env.USERNAME;
|
|
10601
|
+
try {
|
|
10602
|
+
const r = lanSpawn('whoami', [], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
10603
|
+
if (r.status === 0 && String(r.stdout || '').trim()) return String(r.stdout).trim();
|
|
10604
|
+
} catch (e) { /* 用户名探测失败 -> 主体省略 UserId,由 schtasks 按当前用户注册 */ }
|
|
10605
|
+
return '';
|
|
10606
|
+
}
|
|
10607
|
+
function lanWindowsTaskXml(opts) {
|
|
10608
|
+
opts = opts || {};
|
|
10609
|
+
const onstart = opts.trigger ? String(opts.trigger) === 'onstart' : !!opts.onstart;
|
|
10610
|
+
const host = opts.host || '0.0.0.0';
|
|
10611
|
+
const port = opts.port || 8787;
|
|
10612
|
+
const nodePath = opts.nodePath || process.execPath;
|
|
10613
|
+
const scriptPath = opts.scriptPath || runtimeManagedScript();
|
|
10614
|
+
const logPath = opts.logPath || lanLogPath(opts);
|
|
10615
|
+
const userId = opts.userId || '';
|
|
10616
|
+
const cmdPath = opts.cmdPath || process.env.ComSpec || 'cmd.exe';
|
|
10617
|
+
// cmd /c 外层引号:命令首段带引号时必须用 ""..." 包裹(cmd 的 /c 引号规则),
|
|
10618
|
+
// 让 node/脚本/日志路径里的空格与中文都能安全传递;S4U 非交互会话下不弹窗口。
|
|
10619
|
+
// 注意:重定向 `2>&1` 的 & 在 XML 文本里必须写成 &(DOM 解码后仍是 2>&1),
|
|
10620
|
+
// 否则 schtasks 报 "The task XML is malformed"。
|
|
10621
|
+
// v0.22.5: Electron 宿主(YottaCode shim)下先 set ELECTRON_RUN_AS_NODE=1,
|
|
10622
|
+
// 否则任务会拉起桌面程序本体(GUI)而不是 Node 服务。
|
|
10623
|
+
const envPrefix = lanElectronHost(opts) ? 'set "ELECTRON_RUN_AS_NODE=1" && ' : '';
|
|
10624
|
+
const inner = envPrefix + '"' + xmlEscape(nodePath) + '" "' + xmlEscape(scriptPath) + '" serve --host ' + host + ' --port ' + port
|
|
10625
|
+
+ ' >> "' + xmlEscape(logPath) + '" 2>&1';
|
|
10626
|
+
const triggerXml = onstart
|
|
10627
|
+
? '<BootTrigger><Enabled>true</Enabled></BootTrigger>'
|
|
10628
|
+
: '<LogonTrigger><Enabled>true</Enabled></LogonTrigger>';
|
|
10629
|
+
const principal = userId
|
|
10630
|
+
? '<Principal id="Author"><UserId>' + xmlEscape(userId) + '</UserId><LogonType>S4U</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal>'
|
|
10631
|
+
: '<Principal id="Author"><LogonType>S4U</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal>';
|
|
10632
|
+
return [
|
|
10633
|
+
'<?xml version="1.0" encoding="UTF-16"?>',
|
|
10634
|
+
'<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">',
|
|
10635
|
+
'<RegistrationInfo><Description>Yotta Memory serve autostart (generated by yotta-memory lan enable; S4U silent, no window)</Description></RegistrationInfo>',
|
|
10636
|
+
'<Triggers>' + triggerXml + '</Triggers>',
|
|
10637
|
+
'<Principals>' + principal + '</Principals>',
|
|
10638
|
+
'<Settings>',
|
|
10639
|
+
'<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>',
|
|
10640
|
+
'<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>',
|
|
10641
|
+
'<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>',
|
|
10642
|
+
'<AllowHardTerminate>true</AllowHardTerminate>',
|
|
10643
|
+
'<StartWhenAvailable>true</StartWhenAvailable>',
|
|
10644
|
+
'<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>',
|
|
10645
|
+
'<IdleSettings><StopOnIdleEnd>false</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleSettings>',
|
|
10646
|
+
'<Enabled>true</Enabled>',
|
|
10647
|
+
'<Hidden>false</Hidden>',
|
|
10648
|
+
'<RunOnlyIfIdle>false</RunOnlyIfIdle>',
|
|
10649
|
+
'<WakeToRun>false</WakeToRun>',
|
|
10650
|
+
'<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>',
|
|
10651
|
+
'<Priority>7</Priority>',
|
|
10652
|
+
'</Settings>',
|
|
10653
|
+
'<Actions Context="Author"><Exec><Command>' + xmlEscape(cmdPath) + '</Command><Arguments>/c "' + inner + '"</Arguments></Exec></Actions>',
|
|
10654
|
+
'</Task>',
|
|
10655
|
+
].join('\r\n');
|
|
10656
|
+
}
|
|
10657
|
+
function lanWindowsEnableCore(opts) {
|
|
10658
|
+
opts = opts || {};
|
|
10659
|
+
const trigger = opts.onstart ? 'onstart' : 'onlogon';
|
|
10660
|
+
const host = opts.host || '0.0.0.0';
|
|
10661
|
+
const port = opts.port || 8787;
|
|
10662
|
+
const nodePath = opts.nodePath || process.execPath;
|
|
10663
|
+
const scriptPath = opts.scriptPath || runtimeManagedScript();
|
|
10664
|
+
const logPath = lanLogPath(opts);
|
|
10665
|
+
const userId = lanWindowsTaskUser(opts);
|
|
10666
|
+
const xml = lanWindowsTaskXml({ trigger: trigger, host: host, port: port, nodePath: nodePath, scriptPath: scriptPath, logPath: logPath, userId: userId, electronHost: opts.electronHost });
|
|
10667
|
+
const xmlDir = opts.xmlDir || process.env.YOTTA_LAN_TASK_XML_DIR || os.tmpdir();
|
|
10668
|
+
const xmlPath = opts.xmlPath || path.join(xmlDir, LAN_TASK_NAME + '-task-' + process.pid + '-' + Date.now() + '.xml');
|
|
10669
|
+
let failure = null;
|
|
10670
|
+
try {
|
|
10671
|
+
fs.mkdirSync(path.dirname(xmlPath), { recursive: true });
|
|
10672
|
+
// UTF-16LE + BOM:与 schtasks /query /xml 导出格式一致,schtasks /create /xml 必然可读
|
|
10673
|
+
fs.writeFileSync(xmlPath, '\ufeff' + xml, 'utf16le');
|
|
10674
|
+
const r = lanWindowsRunSchtasks(opts, ['/create', '/tn', LAN_TASK_NAME, '/xml', xmlPath, '/f']);
|
|
10675
|
+
if (r.status !== 0) failure = String(r.stderr || r.stdout || '').trim() || ('schtasks 退出码 ' + r.status);
|
|
10676
|
+
} catch (e) {
|
|
10677
|
+
failure = String((e && e.message) || e);
|
|
10678
|
+
} finally {
|
|
10679
|
+
try { fs.unlinkSync(xmlPath); } catch (e) { /* 临时 XML 清理尽力而为 */ }
|
|
10680
|
+
}
|
|
10681
|
+
if (!failure) {
|
|
10682
|
+
let removedStartup = [];
|
|
10683
|
+
try { removedStartup = lanRemoveStartupFiles(opts); } catch (e) { removedStartup = []; }
|
|
10684
|
+
return { error: false, mode: 'task', taskName: LAN_TASK_NAME, trigger: trigger, logPath: logPath, removedStartup: removedStartup };
|
|
10685
|
+
}
|
|
10686
|
+
try {
|
|
10687
|
+
lanInstallStartup(opts);
|
|
10688
|
+
} catch (e2) {
|
|
10689
|
+
return { error: true, mode: 'startup', reason: failure, text: String((e2 && e2.message) || e2) };
|
|
10690
|
+
}
|
|
10691
|
+
return {
|
|
10692
|
+
error: false,
|
|
10693
|
+
mode: 'startup',
|
|
10694
|
+
trigger: trigger,
|
|
10695
|
+
reason: failure,
|
|
10696
|
+
accessDenied: isSchtasksAccessDenied(failure),
|
|
10697
|
+
vbsPath: lanVbsPath(opts),
|
|
10698
|
+
cmdPath: lanAutostartCmdPath(opts),
|
|
10699
|
+
logPath: logPath,
|
|
10700
|
+
};
|
|
10701
|
+
}
|
|
10702
|
+
|
|
10519
10703
|
// ---- lan Linux (systemd user unit / user crontab @reboot) ----
|
|
10520
10704
|
function lanLinuxSystemdUserDir() {
|
|
10521
10705
|
// override with YOTTA_LAN_SYSTEMD_USER_DIR for testing/advanced use (default ~/.config/systemd/user)
|
|
@@ -10531,7 +10715,7 @@ function lanLinuxExecStart(opts) {
|
|
|
10531
10715
|
}
|
|
10532
10716
|
function lanLinuxUnitContent(opts) {
|
|
10533
10717
|
// keep comments ASCII (avoid locale/encoding issues); ExecStart uses systemd's own quote rules
|
|
10534
|
-
|
|
10718
|
+
const lines = [
|
|
10535
10719
|
'# ' + LAN_GEN_MARKER + '; remove with: yotta-memory lan disable (English only, keep ASCII)',
|
|
10536
10720
|
'',
|
|
10537
10721
|
'[Unit]',
|
|
@@ -10539,6 +10723,9 @@ function lanLinuxUnitContent(opts) {
|
|
|
10539
10723
|
'',
|
|
10540
10724
|
'[Service]',
|
|
10541
10725
|
'Type=simple',
|
|
10726
|
+
];
|
|
10727
|
+
if (lanElectronHost(opts)) lines.push('Environment=ELECTRON_RUN_AS_NODE=1');
|
|
10728
|
+
lines.push(
|
|
10542
10729
|
'ExecStart=' + lanLinuxExecStart(opts),
|
|
10543
10730
|
'Restart=on-failure',
|
|
10544
10731
|
'RestartSec=3',
|
|
@@ -10546,12 +10733,15 @@ function lanLinuxUnitContent(opts) {
|
|
|
10546
10733
|
'[Install]',
|
|
10547
10734
|
'WantedBy=default.target',
|
|
10548
10735
|
'',
|
|
10549
|
-
|
|
10736
|
+
);
|
|
10737
|
+
return lines.join('\n');
|
|
10550
10738
|
}
|
|
10551
10739
|
function lanCrontabBin() { return process.env.YOTTA_LAN_CRONTAB_BIN || 'crontab'; }
|
|
10552
10740
|
function lanCrontabLine(opts) {
|
|
10553
|
-
const parts = ['@reboot'
|
|
10554
|
-
|
|
10741
|
+
const parts = ['@reboot'];
|
|
10742
|
+
if (lanElectronHost(opts)) parts.push('ELECTRON_RUN_AS_NODE=1');
|
|
10743
|
+
parts.push(shQuote(process.execPath), shQuote(runtimeManagedScript()));
|
|
10744
|
+
return parts.concat(lanServeArgs(opts)).join(' ') + ' >> ' + shQuote(lanLogPath(opts)) + ' 2>&1 ' + LAN_CRONTAB_MARKER;
|
|
10555
10745
|
}
|
|
10556
10746
|
function lanCrontabRead() {
|
|
10557
10747
|
try {
|
|
@@ -10700,54 +10890,55 @@ function cmdLanLinuxStatus() {
|
|
|
10700
10890
|
if (!any) console.log('未启用任何开机自启(可用 yotta-memory lan enable 注册)');
|
|
10701
10891
|
}
|
|
10702
10892
|
function cmdLanWinEnable(opts) {
|
|
10703
|
-
if (
|
|
10704
|
-
console.error('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' +
|
|
10893
|
+
if (lanPlatform() !== 'win32') {
|
|
10894
|
+
console.error('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' + lanPlatform());
|
|
10705
10895
|
process.exit(2);
|
|
10706
10896
|
}
|
|
10707
|
-
const
|
|
10708
|
-
|
|
10709
|
-
|
|
10710
|
-
|
|
10711
|
-
console.log('
|
|
10712
|
-
console.log('
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
} catch (e) {
|
|
10716
|
-
const firstLine = String((e && e.stderr) || (e && e.message) || e).split(/\r?\n/)[0];
|
|
10717
|
-
if (isSchtasksAccessDenied(e)) console.error('计划任务注册被拒绝(当前用户非管理员,Access denied): ' + firstLine);
|
|
10718
|
-
else console.error('计划任务注册失败(当前环境不可用计划任务): ' + firstLine);
|
|
10719
|
-
console.log('自动改用用户级 Startup 静默自启(免管理员)...');
|
|
10720
|
-
try {
|
|
10721
|
-
lanInstallStartup(opts);
|
|
10722
|
-
} catch (e2) {
|
|
10723
|
-
console.error('写 Startup 自启失败: ' + String((e2 && e2.message) || e2).split(/\r?\n/)[0]);
|
|
10724
|
-
console.error('请用管理员终端执行 yotta-memory lan enable,或将以下命令手动加入启动项: ' + tr);
|
|
10725
|
-
process.exit(1);
|
|
10897
|
+
const result = lanWindowsEnableCore(opts);
|
|
10898
|
+
if (result.mode === 'task') {
|
|
10899
|
+
const triggerText = result.trigger === 'onstart' ? '开机触发(无需登录)' : '登录触发';
|
|
10900
|
+
console.log('已注册开机自启: 计划任务 ' + LAN_TASK_NAME + '(' + triggerText + ',后台静默运行,不弹窗口)');
|
|
10901
|
+
console.log('运行命令: ' + lanTaskRunCmd(opts));
|
|
10902
|
+
console.log('日志: ' + result.logPath);
|
|
10903
|
+
if (result.removedStartup && result.removedStartup.length) {
|
|
10904
|
+
console.log('已清理旧 Startup 兜底文件 ' + result.removedStartup.length + ' 个(避免双份自启)');
|
|
10726
10905
|
}
|
|
10727
|
-
console.log('
|
|
10728
|
-
console.log('启动脚本: ' + lanVbsPath());
|
|
10729
|
-
console.log('启动命令: ' + lanAutostartCmdPath());
|
|
10730
|
-
console.log('日志: ' + lanLogPath(opts));
|
|
10731
|
-
console.log('备注: 服务不会立刻启动,需重新登录后自动启动;如需现在运行请执行 yotta-memory serve');
|
|
10732
|
-
console.log('提示: 如需改回计划任务,请用管理员终端重新执行 yotta-memory lan enable');
|
|
10906
|
+
console.log('备注: 服务不会立刻启动,需重启/重新登录后自动启动;如需现在运行请执行 yotta-memory serve');
|
|
10733
10907
|
return;
|
|
10734
10908
|
}
|
|
10909
|
+
if (result.error) {
|
|
10910
|
+
console.error('计划任务注册失败: ' + String(result.reason || '').split(/\r?\n/)[0]);
|
|
10911
|
+
console.error('写 Startup 自启失败: ' + String(result.text || '').split(/\r?\n/)[0]);
|
|
10912
|
+
console.error('请用管理员终端执行 yotta-memory lan enable,或将以下命令手动加入启动项: ' + lanTaskRunCmd(opts));
|
|
10913
|
+
process.exit(1);
|
|
10914
|
+
}
|
|
10915
|
+
const firstLine = String(result.reason || '').split(/\r?\n/)[0];
|
|
10916
|
+
if (result.accessDenied) console.error('计划任务注册被拒绝(当前用户非管理员,Access denied): ' + firstLine);
|
|
10917
|
+
else console.error('计划任务注册失败(当前环境不可用计划任务): ' + firstLine);
|
|
10918
|
+
console.log('自动改用用户级 Startup 静默自启(免管理员)...');
|
|
10919
|
+
console.log('已启用用户级 Startup 静默自启(无需管理员)');
|
|
10920
|
+
console.log('启动脚本: ' + result.vbsPath);
|
|
10921
|
+
console.log('启动命令: ' + result.cmdPath);
|
|
10922
|
+
console.log('日志: ' + result.logPath);
|
|
10923
|
+
console.log('备注: 服务不会立刻启动,需重新登录后自动启动;如需现在运行请执行 yotta-memory serve');
|
|
10924
|
+
console.log('提示: 如需改回计划任务(后台静默),请用管理员终端重新执行 yotta-memory lan enable');
|
|
10735
10925
|
}
|
|
10736
10926
|
function cmdLanWinDisable() {
|
|
10737
|
-
if (
|
|
10738
|
-
console.error('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' +
|
|
10927
|
+
if (lanPlatform() !== 'win32') {
|
|
10928
|
+
console.error('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' + lanPlatform());
|
|
10739
10929
|
process.exit(2);
|
|
10740
10930
|
}
|
|
10741
10931
|
let sawError = false;
|
|
10742
10932
|
let removedAny = false;
|
|
10743
|
-
|
|
10744
|
-
|
|
10933
|
+
const del = lanWindowsRunSchtasks({}, ['/delete', '/tn', LAN_TASK_NAME, '/f']);
|
|
10934
|
+
if (del.status === 0) {
|
|
10745
10935
|
console.log('已移除开机自启计划任务 ' + LAN_TASK_NAME);
|
|
10746
10936
|
removedAny = true;
|
|
10747
|
-
}
|
|
10748
|
-
const msg = String(
|
|
10937
|
+
} else {
|
|
10938
|
+
const msg = String(del.stderr || del.stdout || '');
|
|
10749
10939
|
if (!/cannot find the (path|file)|没有找到|找不到|不存在/i.test(msg)) {
|
|
10750
10940
|
console.error('移除计划任务失败: ' + msg.split(/\r?\n/)[0]);
|
|
10941
|
+
if (isSchtasksAccessDenied(msg)) console.error('(当前非管理员,无法移除管理员注册的计划任务;请用管理员终端执行 yotta-memory lan disable)');
|
|
10751
10942
|
sawError = true;
|
|
10752
10943
|
}
|
|
10753
10944
|
}
|
|
@@ -10759,21 +10950,32 @@ function cmdLanWinDisable() {
|
|
|
10759
10950
|
if (sawError) process.exit(1);
|
|
10760
10951
|
}
|
|
10761
10952
|
function cmdLanWinStatus() {
|
|
10762
|
-
if (
|
|
10763
|
-
console.log('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' +
|
|
10953
|
+
if (lanPlatform() !== 'win32') {
|
|
10954
|
+
console.log('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' + lanPlatform());
|
|
10764
10955
|
return;
|
|
10765
10956
|
}
|
|
10766
10957
|
let taskFound = false;
|
|
10767
|
-
|
|
10768
|
-
|
|
10958
|
+
const q = lanWindowsRunSchtasks({}, ['/query', '/tn', LAN_TASK_NAME, '/fo', 'LIST', '/v']);
|
|
10959
|
+
if (q.status === 0) {
|
|
10769
10960
|
taskFound = true;
|
|
10770
10961
|
console.log('计划任务 ' + LAN_TASK_NAME + ': 已启用');
|
|
10771
|
-
const re = /^(状态|Status)\s
|
|
10772
|
-
|
|
10962
|
+
const re = /^(状态|Status|登录模式|Logon Mode|上次运行时间|Last Run Time|上次结果|Last Result|要运行的任务|Task To Run)\s*[:=]\s*(.+)$/;
|
|
10963
|
+
let logonMode = '';
|
|
10964
|
+
let taskRun = '';
|
|
10965
|
+
for (const line of String(q.stdout).split(/\r?\n/)) {
|
|
10773
10966
|
const m = re.exec(line.trim());
|
|
10774
|
-
if (m)
|
|
10967
|
+
if (!m) continue;
|
|
10968
|
+
console.log(' ' + m[1] + ': ' + m[2]);
|
|
10969
|
+
if (/^(登录模式|Logon Mode)$/.test(m[1])) logonMode = m[2];
|
|
10970
|
+
if (/^(要运行的任务|Task To Run)$/.test(m[1])) taskRun = m[2];
|
|
10775
10971
|
}
|
|
10776
|
-
|
|
10972
|
+
if (/interactive|交互式/i.test(logonMode)) {
|
|
10973
|
+
console.log(' 提示: 登录模式为交互式(仅登录时运行,且在登录会话会弹窗口)——这是旧版任务;请重新执行 yotta-memory lan enable 修复为后台静默');
|
|
10974
|
+
}
|
|
10975
|
+
if (lanTaskCommandNeedsElectronEnv(taskRun)) {
|
|
10976
|
+
console.log(' 警告: 任务命令指向 Electron 可执行文件(如 YottaCode.exe)但缺少 ELECTRON_RUN_AS_NODE —— 开机将拉起桌面程序而非服务;请重新执行 yotta-memory lan enable 修复');
|
|
10977
|
+
}
|
|
10978
|
+
} else {
|
|
10777
10979
|
console.log('计划任务 ' + LAN_TASK_NAME + ': 未启用');
|
|
10778
10980
|
}
|
|
10779
10981
|
const vbsOk = lanFileHasMarker(lanVbsPath());
|
|
@@ -10782,6 +10984,12 @@ function cmdLanWinStatus() {
|
|
|
10782
10984
|
console.log('Startup 静默自启(免管理员): 已启用');
|
|
10783
10985
|
console.log(' 启动脚本: ' + lanVbsPath());
|
|
10784
10986
|
console.log(' 启动命令: ' + lanAutostartCmdPath());
|
|
10987
|
+
try {
|
|
10988
|
+
const cmdText = fs.readFileSync(lanAutostartCmdPath(), 'utf8');
|
|
10989
|
+
if (lanTaskCommandNeedsElectronEnv(cmdText)) {
|
|
10990
|
+
console.log(' 警告: Startup 兜底脚本缺少 ELECTRON_RUN_AS_NODE —— Electron 宿主下会拉起桌面程序而非服务;请重新执行 yotta-memory lan enable 修复');
|
|
10991
|
+
}
|
|
10992
|
+
} catch (e) { /* 读取失败不影响状态输出 */ }
|
|
10785
10993
|
} else if (vbsOk || cmdOk) {
|
|
10786
10994
|
console.log('Startup 静默自启: 部分残留(建议执行 yotta-memory lan disable 后重新 lan enable)');
|
|
10787
10995
|
} else {
|
|
@@ -12016,6 +12224,7 @@ module.exports = {
|
|
|
12016
12224
|
lanServeArgs: lanServeArgs,
|
|
12017
12225
|
lanSpawn: lanSpawn,
|
|
12018
12226
|
lanPlatform: lanPlatform,
|
|
12227
|
+
lanElectronHost: lanElectronHost,
|
|
12019
12228
|
shQuote: shQuote,
|
|
12020
12229
|
systemdEscapeArg: systemdEscapeArg,
|
|
12021
12230
|
lanLinuxSystemdUserDir: lanLinuxSystemdUserDir,
|
|
@@ -12124,6 +12333,9 @@ module.exports = {
|
|
|
12124
12333
|
doctorCore: doctorCore,
|
|
12125
12334
|
destructiveGuardCore: destructiveGuardCore,
|
|
12126
12335
|
backupWindowsTaskXml: backupWindowsTaskXml,
|
|
12336
|
+
lanWindowsTaskXml: lanWindowsTaskXml,
|
|
12337
|
+
lanWindowsEnableCore: lanWindowsEnableCore,
|
|
12338
|
+
lanWindowsTaskUser: lanWindowsTaskUser,
|
|
12127
12339
|
backupSystemdServiceContent: backupSystemdServiceContent,
|
|
12128
12340
|
backupSystemdTimerContent: backupSystemdTimerContent,
|
|
12129
12341
|
backupCronLine: backupCronLine,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yottameta/yotta-memory",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.5",
|
|
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": [
|