@yottameta/yotta-memory 0.22.2 → 0.22.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## v0.22.4 (2026-10-07)
2
+
3
+ - **修复 Windows `lan enable` 开机自启三缺陷(实测复现)**:此前计划任务用 schtasks 默认交互式主体——① 在登录会话运行会弹控制台窗口;② `--onstart` 开机触发时无登录会话,任务起不来且不补跑(实测 LastTaskResult 267011);③ schtasks 默认带电池限制与 72 小时执行时限。本版改为 **S4U 主体 + 任务 XML 注册**(零依赖):后台静默运行不弹窗口,开机 / 登录触发均可用;默认去掉电池限制与 72h 时限(`ExecutionTimeLimit=PT0S` 不限时);`--onstart` 在 Windows 真正生效(BootTrigger)。任务注册成功时自动清理旧 Startup 兜底文件,避免双份自启。
4
+ - `lan status` 增加显示「登录模式」并对旧交互式任务给出重新 `lan enable` 的修复提示;非管理员执行 `lan disable` 无法移除管理员注册的任务时,明确提示改用管理员终端。
5
+ - 非管理员路径行为不变:schtasks 失败(含 Access denied)自动降级用户级 Startup 静默自启(VBS 自愈保留)。
6
+ - 新增 `test/lan-windows-autostart.test.js`(11 项:任务 XML 结构 / S4U / 无电池与时限 / XML 转义 / XML 注册 / 旧兜底清理 / 权限与非权限失败降级 / status 登录模式与旧任务告警 / disable 双通道)。
7
+
8
+ ## v0.22.3 (2026-10-05)
9
+
10
+ - **安装器 junction 守卫**:`bin/install.js` 对目标基目录 / 技能目录为符号链接(含 Windows junction)时 fail-closed 拒绝(退出码 4,与 `install.sh` 同口径)——不再写穿 Hub 链接指向的真源,也不再误删 Hub 侧 `bin/` runtimePayload。统一安装器以分技能灰度补丁落地(模板与其余技能输出不变)。
11
+
1
12
  ## v0.22.2 (2026-10-05)
2
13
 
3
14
  - **修复(分发载荷缺口)**:`@yottameta/yotta-memory-plugin@0.22.1` 包内载荷落后技能源(`errText` 0/17、`bodyMirrorsStatement` 0/2)——0.22.1 的 D2/D3 修复未随插件分发。本版为维护性同步升版:引擎行为与 0.22.1 一致,插件载荷重新从技能源构建并对齐(逐字节校验)。
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, default ONLOGON, --onstart needs admin, non-admin auto-degrades to user-level Startup; Linux: systemd user unit, falls back to user crontab @reboot) |
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 --onstart` switches to start-at-boot (needs admin); non-admin `lan enable` auto-degrades to user-level silent Startup; `lan disable` removes it.
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 优先计划任务(默认登录自启,`--onstart` 开机即启需管理员),非管理员自动降级为**用户级 Startup 静默自启**(免管理员,启动脚本内联启动命令、被清理也会在开机时自动重建,v0.6.3 起不再弹 80070002);Linux 优先 **systemd 用户单元**(`systemctl --user`,登录自启;`--onstart` 附加 `loginctl enable-linger` 开机即启),systemd 不可用时自动降级**用户 crontab @reboot**(v0.6.4);`lan disable` 移除,`lan status` 查询。
177
+ - **开机自启**:`lan enable` 注册开机自启——Windows 优先计划任务(v0.22.4 起 S4U 主体后台静默、不弹窗口;默认登录自启,`--onstart` 开机即启、无登录会话也可启动,计划任务注册需管理员),非管理员自动降级为**用户级 Startup 静默自启**(免管理员,启动脚本内联启动命令、被清理也会在开机时自动重建,v0.6.3 起不再弹 80070002);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:计划任务,默认 ONLOGON、--onstart 开机即启需管理员,非管理员自动降级用户级 Startup 静默自启;Linux:systemd 用户单元,不可用时自动降级用户 crontab @reboot)|
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 --onstart` 改为开机即启(需管理员);非管理员时 `lan enable` 自动改用用户级 Startup 静默自启(无需管理员);`lan disable` 移除自启。
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.2
4
+ version: 0.22.4
5
5
  license: MIT
6
6
  ---
7
7
 
@@ -46,7 +46,7 @@ license: MIT
46
46
  ## 安装 / 更新(两个 bin)
47
47
 
48
48
  - `yotta-memory`:引擎 CLI,负责读写记忆库。`npx -y @yottameta/yotta-memory` 只是临时运行引擎,不会安装技能。
49
- - `yotta-memory-install`:技能安装器。安装或更新技能使用 `npx -y --package @yottameta/yotta-memory yotta-memory-install --agent <name>`(或 `--dir <技能目录>`)。
49
+ - `yotta-memory-install`:技能安装器。安装或更新技能使用 `npx -y --package @yottameta/yotta-memory yotta-memory-install --agent <name>`(或 `--dir <技能目录>`);目标基目录 / 技能目录是符号链接(含 Windows junction)时 fail-closed 拒绝(不写穿 Hub 链接、不动 runtimePayload)。
50
50
 
51
51
  AI 更新流程:先运行 `yotta-memory --version` 记录当前引擎版本;升级 CLI 用 `npm i -g @yottameta/yotta-memory`;升级技能用 `npx -y --package @yottameta/yotta-memory yotta-memory-install --agent <name>`(或原安装时使用的 `--dir` / `install.sh`);完成后回读 `yotta-memory --version` 与已安装 `SKILL.md` 的 `version:`,两者不一致时不得声称更新成功。
52
52
 
@@ -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:计划任务,默认 ONLOGON、--onstart 开机即启需管理员,非管理员自动降级用户级 Startup 静默自启,v0.6.3 起 VBS 自愈不弹 80070002;Linux:systemd 用户单元,不可用时自动降级用户 crontab @reboot)|
336
+ | `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:任务计划程序,S4U 主体后台静默——v0.22.4 起不弹窗口,`--onstart` 无登录会话也可启动;任务 XML 注册,去电池限制与 72h 时限;非管理员自动降级用户级 Startup 静默自启,v0.6.3 起 VBS 自愈不弹 80070002;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:优先计划任务,默认登录自启;非管理员自动降级用户级 Startup 静默自启,免管理员)。
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,10 @@ 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:内置命令(优先计划任务;非管理员自动降级用户级 Startup 静默自启;
338
- # v0.6.3 起启动脚本自愈——启动文件被清理也会在开机时自动重建,无需手动处理)
337
+ # Windows:内置命令(优先计划任务——v0.22.4 起 S4U 主体后台静默、不弹窗口;
338
+ # 非管理员自动降级用户级 Startup 静默自启;v0.6.3 起启动脚本自愈——启动文件被清理也会在开机时自动重建)
339
339
  yotta-memory lan enable # 登录后自动启动(默认)
340
- yotta-memory lan enable --onstart # 开机即启(需管理员)
340
+ yotta-memory lan enable --onstart # 开机即启、无登录会话也可启动(计划任务注册需管理员)
341
341
  yotta-memory lan status # 查看状态
342
342
  yotta-memory lan disable # 取消
343
343
 
package/bin/install.js CHANGED
@@ -244,6 +244,13 @@ function installTo(dest, opts) {
244
244
  if (!dest || typeof dest !== 'string') throw new UsageError('目标目录不能为空');
245
245
  const target = path.resolve(dest, SKILL_NAME);
246
246
  assertSafeTarget(target);
247
+ // fail-closed:目标基目录 / 技能目录是符号链接(含 Windows junction)时拒绝跟随;
248
+ // 与 install.sh 同口径,避免安装器改写 Hub 链接指向的真源(含 runtimePayload)。
249
+ for (const p of [path.resolve(dest), target]) {
250
+ let st = null;
251
+ try { st = fs.lstatSync(p); } catch (_) { continue; }
252
+ if (st.isSymbolicLink()) throw new TargetError('目标路径是符号链接,拒绝跟随:' + p);
253
+ }
247
254
  if (opts.dryRun) {
248
255
  console.log('[dry-run] 将安装到 -> ' + target);
249
256
  return target;
@@ -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.2';
28
+ const VERSION = '0.22.4';
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 上希望开机即启动、不依赖登录时', 'Windows 上此选项不改变计划任务语义')]),
301
+ helpSub('enable', 'enable [--onstart]', '启用开机自启', '希望元忆服务长期可用时', [helpOption('--onstart', '', '同时启用未登录也启动', 'Windows / Linux 上希望开机即启动、不依赖登录时', '')]),
302
302
  helpSub('disable', 'disable', '关闭开机自启', '不再需要常驻服务时', []),
303
303
  helpSub('status', 'status', '查看自启状态', '确认服务是否会随系统启动时', []),
304
304
  ] },
@@ -10381,7 +10381,9 @@ function cmdServeStdio(opts) {
10381
10381
  }
10382
10382
 
10383
10383
  // ---- lan command (autostart management for serve) ----
10384
- // Windows dual mechanism: (1) schtasks scheduled task (default, needs permission); (2) non-admin
10384
+ // Windows dual mechanism: (1) schtasks scheduled task registered from task XML (default, needs
10385
+ // permission; v0.22.4: S4U principal -> silent in background for both logon/boot triggers, no
10386
+ // console window; battery limits and the 72h execution time limit removed); (2) non-admin
10385
10387
  // (Access denied) auto-fallback to user-level Startup silent autostart (VBS sh.Run <autostart.cmd>,
10386
10388
  // 0, False + autostart.cmd; node path process.execPath auto-detected, 0.5.4)
10387
10389
  // Linux (v0.6.4): (1) systemd user unit (systemctl --user enable/start, login autostart; --onstart
@@ -10425,20 +10427,23 @@ function systemdEscapeArg(s) {
10425
10427
  if (!/[\s"\\$;]/.test(p)) return p;
10426
10428
  return '"' + p.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
10427
10429
  }
10428
- function lanStartupDir() {
10430
+ function lanStartupDir(opts) {
10431
+ if (opts && opts.startupDir) return String(opts.startupDir);
10429
10432
  // 测试/高级用途可用 YOTTA_LAN_STARTUP_DIR 覆盖(默认用户级 Startup 目录,免管理员)
10430
10433
  if (process.env.YOTTA_LAN_STARTUP_DIR) return process.env.YOTTA_LAN_STARTUP_DIR;
10431
10434
  const base = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
10432
10435
  return path.join(base, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
10433
10436
  }
10434
- function lanAutostartDir() {
10437
+ function lanAutostartDir(opts) {
10438
+ if (opts && opts.autostartDir) return String(opts.autostartDir);
10435
10439
  // 产品自有目录 ~/.yottamemory/autostart/(引擎数据目录内,非系统配置);测试可用 YOTTA_LAN_AUTOSTART_DIR 覆盖
10436
10440
  if (process.env.YOTTA_LAN_AUTOSTART_DIR) return process.env.YOTTA_LAN_AUTOSTART_DIR;
10437
10441
  return path.join(os.homedir(), '.yottamemory', 'autostart');
10438
10442
  }
10439
- function lanVbsPath() { return path.join(lanStartupDir(), 'yotta-memory-serve.vbs'); }
10440
- function lanAutostartCmdPath() { return path.join(lanAutostartDir(), 'yotta-memory-autostart.cmd'); }
10443
+ function lanVbsPath(opts) { return path.join(lanStartupDir(opts), 'yotta-memory-serve.vbs'); }
10444
+ function lanAutostartCmdPath(opts) { return path.join(lanAutostartDir(opts), 'yotta-memory-autostart.cmd'); }
10441
10445
  function lanLogPath(opts) {
10446
+ if (opts && opts.logPath) return String(opts.logPath);
10442
10447
  if (process.env.YOTTA_LAN_LOG_FILE) return process.env.YOTTA_LAN_LOG_FILE;
10443
10448
  return path.join(os.homedir(), '.yottamemory', 'serve-' + (opts.port || 8787) + '.log');
10444
10449
  }
@@ -10467,7 +10472,7 @@ function lanVbsContent(opts) {
10467
10472
  return '"' + seg.replace(/"/g, '""') + '"';
10468
10473
  })
10469
10474
  .join(' & Chr(13) & Chr(10) & ');
10470
- const vbsCmdPath = String(lanAutostartCmdPath()).replace(/"/g, '""');
10475
+ const vbsCmdPath = String(lanAutostartCmdPath(opts)).replace(/"/g, '""');
10471
10476
  const lines = [
10472
10477
  "' " + LAN_GEN_MARKER + '; remove with: yotta-memory lan disable',
10473
10478
  "' v0.6.3 self-heal: always rebuild autostart.cmd from embedded content, then run it (fix 80070002)",
@@ -10486,16 +10491,16 @@ function lanVbsContent(opts) {
10486
10491
  return lines.join('\r\n') + '\r\n';
10487
10492
  }
10488
10493
  function lanInstallStartup(opts) {
10489
- fs.mkdirSync(lanAutostartDir(), { recursive: true });
10490
- fs.writeFileSync(lanAutostartCmdPath(), lanAutostartCmdContent(opts), 'utf8');
10491
- fs.mkdirSync(lanStartupDir(), { recursive: true });
10494
+ fs.mkdirSync(lanAutostartDir(opts), { recursive: true });
10495
+ fs.writeFileSync(lanAutostartCmdPath(opts), lanAutostartCmdContent(opts), 'utf8');
10496
+ fs.mkdirSync(lanStartupDir(opts), { recursive: true });
10492
10497
  // VBS 用 UTF-16LE+BOM:wscript 按 Unicode 读取,中文路径不乱码(纯 ANSI 读取会按系统代码页误读)
10493
- fs.writeFileSync(lanVbsPath(), '\ufeff' + lanVbsContent(opts), 'utf16le');
10498
+ fs.writeFileSync(lanVbsPath(opts), '\ufeff' + lanVbsContent(opts), 'utf16le');
10494
10499
  }
10495
- function lanRemoveStartupFiles() {
10500
+ function lanRemoveStartupFiles(opts) {
10496
10501
  // 只删除带产品标记的生成文件,绝不误删用户自己的 Startup 文件
10497
10502
  const removed = [];
10498
- for (const f of [lanVbsPath(), lanAutostartCmdPath()]) {
10503
+ for (const f of [lanVbsPath(opts), lanAutostartCmdPath(opts)]) {
10499
10504
  try {
10500
10505
  if (lanFileHasMarker(f)) { fs.unlinkSync(f); removed.push(f); }
10501
10506
  } catch (e) { /* 单个文件失败不阻断其它清理 */ }
@@ -10512,10 +10517,142 @@ function lanFileHasMarker(f) {
10512
10517
  } catch (e) { return false; }
10513
10518
  }
10514
10519
  function isSchtasksAccessDenied(e) {
10515
- const msg = String((e && e.stderr) || (e && e.message) || e);
10520
+ const msg = typeof e === 'string' ? e : String((e && e.stderr) || (e && e.message) || e || '');
10516
10521
  return /access\s+is\s+denied|access\s+denied|拒绝访问/i.test(msg);
10517
10522
  }
10518
10523
 
10524
+ // ---- lan Windows task XML (v0.22.4: S4U silent principal) ----
10525
+ // 背景(0.22.3 及更早缺陷):schtasks /create 未指定主体 -> 默认交互式(仅登录时运行):
10526
+ // ① 在登录会话里运行控制台程序会弹窗口;② --onstart 开机触发时无登录会话,任务起不来且不补跑。
10527
+ // 修复:改为注册任务 XML —— S4U 主体(“不管用户是否登录都运行”,不存密码、非交互、无窗口),
10528
+ // 默认去掉电池限制与 72 小时执行时限(PT0S = 不限时),开机/登录触发均可后台静默启动。
10529
+ function lanSchTasksBin() { return process.env.YOTTA_LAN_SCHTASKS_BIN || 'schtasks'; }
10530
+ function lanWindowsRunSchtasks(opts, args) {
10531
+ if (opts && typeof opts.execFileFn === 'function') {
10532
+ try {
10533
+ const out = opts.execFileFn(lanSchTasksBin(), args);
10534
+ return { status: 0, stdout: String(out || ''), stderr: '' };
10535
+ } catch (e) {
10536
+ return {
10537
+ status: typeof e.status === 'number' ? e.status : 1,
10538
+ stdout: String((e && e.stdout) || ''),
10539
+ stderr: String((e && e.stderr) || (e && e.message) || e),
10540
+ };
10541
+ }
10542
+ }
10543
+ const r = lanSpawn(lanSchTasksBin(), args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
10544
+ return {
10545
+ status: r.error ? 1 : r.status,
10546
+ stdout: String(r.stdout || ''),
10547
+ stderr: String(r.stderr || (r.error && r.error.message) || ''),
10548
+ };
10549
+ }
10550
+ function lanWindowsTaskUser(opts) {
10551
+ opts = opts || {};
10552
+ if (opts.userId) return String(opts.userId);
10553
+ // 测试/高级用途可用 YOTTA_LAN_TASK_USER 覆盖(S4U 主体必须是当前用户名)
10554
+ if (process.env.YOTTA_LAN_TASK_USER) return String(process.env.YOTTA_LAN_TASK_USER);
10555
+ if (process.env.USERDOMAIN && process.env.USERNAME) return process.env.USERDOMAIN + '\\' + process.env.USERNAME;
10556
+ if (process.env.COMPUTERNAME && process.env.USERNAME) return process.env.COMPUTERNAME + '\\' + process.env.USERNAME;
10557
+ try {
10558
+ const r = lanSpawn('whoami', [], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
10559
+ if (r.status === 0 && String(r.stdout || '').trim()) return String(r.stdout).trim();
10560
+ } catch (e) { /* 用户名探测失败 -> 主体省略 UserId,由 schtasks 按当前用户注册 */ }
10561
+ return '';
10562
+ }
10563
+ function lanWindowsTaskXml(opts) {
10564
+ opts = opts || {};
10565
+ const onstart = opts.trigger ? String(opts.trigger) === 'onstart' : !!opts.onstart;
10566
+ const host = opts.host || '0.0.0.0';
10567
+ const port = opts.port || 8787;
10568
+ const nodePath = opts.nodePath || process.execPath;
10569
+ const scriptPath = opts.scriptPath || runtimeManagedScript();
10570
+ const logPath = opts.logPath || lanLogPath(opts);
10571
+ const userId = opts.userId || '';
10572
+ const cmdPath = opts.cmdPath || process.env.ComSpec || 'cmd.exe';
10573
+ // cmd /c 外层引号:命令首段带引号时必须用 ""..." 包裹(cmd 的 /c 引号规则),
10574
+ // 让 node/脚本/日志路径里的空格与中文都能安全传递;S4U 非交互会话下不弹窗口。
10575
+ // 注意:重定向 `2>&1` 的 & 在 XML 文本里必须写成 &amp;(DOM 解码后仍是 2>&1),
10576
+ // 否则 schtasks 报 "The task XML is malformed"。
10577
+ const inner = '"' + xmlEscape(nodePath) + '" "' + xmlEscape(scriptPath) + '" serve --host ' + host + ' --port ' + port
10578
+ + ' >> "' + xmlEscape(logPath) + '" 2>&amp;1';
10579
+ const triggerXml = onstart
10580
+ ? '<BootTrigger><Enabled>true</Enabled></BootTrigger>'
10581
+ : '<LogonTrigger><Enabled>true</Enabled></LogonTrigger>';
10582
+ const principal = userId
10583
+ ? '<Principal id="Author"><UserId>' + xmlEscape(userId) + '</UserId><LogonType>S4U</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal>'
10584
+ : '<Principal id="Author"><LogonType>S4U</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal>';
10585
+ return [
10586
+ '<?xml version="1.0" encoding="UTF-16"?>',
10587
+ '<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">',
10588
+ '<RegistrationInfo><Description>Yotta Memory serve autostart (generated by yotta-memory lan enable; S4U silent, no window)</Description></RegistrationInfo>',
10589
+ '<Triggers>' + triggerXml + '</Triggers>',
10590
+ '<Principals>' + principal + '</Principals>',
10591
+ '<Settings>',
10592
+ '<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>',
10593
+ '<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>',
10594
+ '<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>',
10595
+ '<AllowHardTerminate>true</AllowHardTerminate>',
10596
+ '<StartWhenAvailable>true</StartWhenAvailable>',
10597
+ '<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>',
10598
+ '<IdleSettings><StopOnIdleEnd>false</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleSettings>',
10599
+ '<Enabled>true</Enabled>',
10600
+ '<Hidden>false</Hidden>',
10601
+ '<RunOnlyIfIdle>false</RunOnlyIfIdle>',
10602
+ '<WakeToRun>false</WakeToRun>',
10603
+ '<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>',
10604
+ '<Priority>7</Priority>',
10605
+ '</Settings>',
10606
+ '<Actions Context="Author"><Exec><Command>' + xmlEscape(cmdPath) + '</Command><Arguments>/c "' + inner + '"</Arguments></Exec></Actions>',
10607
+ '</Task>',
10608
+ ].join('\r\n');
10609
+ }
10610
+ function lanWindowsEnableCore(opts) {
10611
+ opts = opts || {};
10612
+ const trigger = opts.onstart ? 'onstart' : 'onlogon';
10613
+ const host = opts.host || '0.0.0.0';
10614
+ const port = opts.port || 8787;
10615
+ const nodePath = opts.nodePath || process.execPath;
10616
+ const scriptPath = opts.scriptPath || runtimeManagedScript();
10617
+ const logPath = lanLogPath(opts);
10618
+ const userId = lanWindowsTaskUser(opts);
10619
+ const xml = lanWindowsTaskXml({ trigger: trigger, host: host, port: port, nodePath: nodePath, scriptPath: scriptPath, logPath: logPath, userId: userId });
10620
+ const xmlDir = opts.xmlDir || process.env.YOTTA_LAN_TASK_XML_DIR || os.tmpdir();
10621
+ const xmlPath = opts.xmlPath || path.join(xmlDir, LAN_TASK_NAME + '-task-' + process.pid + '-' + Date.now() + '.xml');
10622
+ let failure = null;
10623
+ try {
10624
+ fs.mkdirSync(path.dirname(xmlPath), { recursive: true });
10625
+ // UTF-16LE + BOM:与 schtasks /query /xml 导出格式一致,schtasks /create /xml 必然可读
10626
+ fs.writeFileSync(xmlPath, '\ufeff' + xml, 'utf16le');
10627
+ const r = lanWindowsRunSchtasks(opts, ['/create', '/tn', LAN_TASK_NAME, '/xml', xmlPath, '/f']);
10628
+ if (r.status !== 0) failure = String(r.stderr || r.stdout || '').trim() || ('schtasks 退出码 ' + r.status);
10629
+ } catch (e) {
10630
+ failure = String((e && e.message) || e);
10631
+ } finally {
10632
+ try { fs.unlinkSync(xmlPath); } catch (e) { /* 临时 XML 清理尽力而为 */ }
10633
+ }
10634
+ if (!failure) {
10635
+ let removedStartup = [];
10636
+ try { removedStartup = lanRemoveStartupFiles(opts); } catch (e) { removedStartup = []; }
10637
+ return { error: false, mode: 'task', taskName: LAN_TASK_NAME, trigger: trigger, logPath: logPath, removedStartup: removedStartup };
10638
+ }
10639
+ try {
10640
+ lanInstallStartup(opts);
10641
+ } catch (e2) {
10642
+ return { error: true, mode: 'startup', reason: failure, text: String((e2 && e2.message) || e2) };
10643
+ }
10644
+ return {
10645
+ error: false,
10646
+ mode: 'startup',
10647
+ trigger: trigger,
10648
+ reason: failure,
10649
+ accessDenied: isSchtasksAccessDenied(failure),
10650
+ vbsPath: lanVbsPath(opts),
10651
+ cmdPath: lanAutostartCmdPath(opts),
10652
+ logPath: logPath,
10653
+ };
10654
+ }
10655
+
10519
10656
  // ---- lan Linux (systemd user unit / user crontab @reboot) ----
10520
10657
  function lanLinuxSystemdUserDir() {
10521
10658
  // override with YOTTA_LAN_SYSTEMD_USER_DIR for testing/advanced use (default ~/.config/systemd/user)
@@ -10700,54 +10837,55 @@ function cmdLanLinuxStatus() {
10700
10837
  if (!any) console.log('未启用任何开机自启(可用 yotta-memory lan enable 注册)');
10701
10838
  }
10702
10839
  function cmdLanWinEnable(opts) {
10703
- if (process.platform !== 'win32') {
10704
- console.error('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' + process.platform);
10840
+ if (lanPlatform() !== 'win32') {
10841
+ console.error('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' + lanPlatform());
10705
10842
  process.exit(2);
10706
10843
  }
10707
- const trigger = opts.onstart ? 'onstart' : 'onlogon';
10708
- const tr = lanTaskRunCmd(opts);
10709
- try {
10710
- child_process.execFileSync('schtasks', ['/create', '/tn', LAN_TASK_NAME, '/tr', tr, '/sc', trigger, '/f'], { stdio: 'inherit' });
10711
- console.log('已注册开机自启: 计划任务 ' + LAN_TASK_NAME + '(触发器 ' + trigger + ')');
10712
- console.log('运行命令: ' + tr);
10713
- console.log('备注: 服务不会立刻启动,需重启/重新登录后自动启动;如需现在运行请执行 yotta-memory serve');
10714
- return;
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);
10844
+ const result = lanWindowsEnableCore(opts);
10845
+ if (result.mode === 'task') {
10846
+ const triggerText = result.trigger === 'onstart' ? '开机触发(无需登录)' : '登录触发';
10847
+ console.log('已注册开机自启: 计划任务 ' + LAN_TASK_NAME + '(' + triggerText + ',后台静默运行,不弹窗口)');
10848
+ console.log('运行命令: ' + lanTaskRunCmd(opts));
10849
+ console.log('日志: ' + result.logPath);
10850
+ if (result.removedStartup && result.removedStartup.length) {
10851
+ console.log('已清理旧 Startup 兜底文件 ' + result.removedStartup.length + ' 个(避免双份自启)');
10726
10852
  }
10727
- console.log('已启用用户级 Startup 静默自启(无需管理员)');
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');
10853
+ console.log('备注: 服务不会立刻启动,需重启/重新登录后自动启动;如需现在运行请执行 yotta-memory serve');
10733
10854
  return;
10734
10855
  }
10856
+ if (result.error) {
10857
+ console.error('计划任务注册失败: ' + String(result.reason || '').split(/\r?\n/)[0]);
10858
+ console.error('写 Startup 自启失败: ' + String(result.text || '').split(/\r?\n/)[0]);
10859
+ console.error('请用管理员终端执行 yotta-memory lan enable,或将以下命令手动加入启动项: ' + lanTaskRunCmd(opts));
10860
+ process.exit(1);
10861
+ }
10862
+ const firstLine = String(result.reason || '').split(/\r?\n/)[0];
10863
+ if (result.accessDenied) console.error('计划任务注册被拒绝(当前用户非管理员,Access denied): ' + firstLine);
10864
+ else console.error('计划任务注册失败(当前环境不可用计划任务): ' + firstLine);
10865
+ console.log('自动改用用户级 Startup 静默自启(免管理员)...');
10866
+ console.log('已启用用户级 Startup 静默自启(无需管理员)');
10867
+ console.log('启动脚本: ' + result.vbsPath);
10868
+ console.log('启动命令: ' + result.cmdPath);
10869
+ console.log('日志: ' + result.logPath);
10870
+ console.log('备注: 服务不会立刻启动,需重新登录后自动启动;如需现在运行请执行 yotta-memory serve');
10871
+ console.log('提示: 如需改回计划任务(后台静默),请用管理员终端重新执行 yotta-memory lan enable');
10735
10872
  }
10736
10873
  function cmdLanWinDisable() {
10737
- if (process.platform !== 'win32') {
10738
- console.error('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' + process.platform);
10874
+ if (lanPlatform() !== 'win32') {
10875
+ console.error('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' + lanPlatform());
10739
10876
  process.exit(2);
10740
10877
  }
10741
10878
  let sawError = false;
10742
10879
  let removedAny = false;
10743
- try {
10744
- child_process.execFileSync('schtasks', ['/delete', '/tn', LAN_TASK_NAME, '/f'], { stdio: ['ignore', 'pipe', 'pipe'] });
10880
+ const del = lanWindowsRunSchtasks({}, ['/delete', '/tn', LAN_TASK_NAME, '/f']);
10881
+ if (del.status === 0) {
10745
10882
  console.log('已移除开机自启计划任务 ' + LAN_TASK_NAME);
10746
10883
  removedAny = true;
10747
- } catch (e) {
10748
- const msg = String((e && e.stderr) || (e && e.message) || e);
10884
+ } else {
10885
+ const msg = String(del.stderr || del.stdout || '');
10749
10886
  if (!/cannot find the (path|file)|没有找到|找不到|不存在/i.test(msg)) {
10750
10887
  console.error('移除计划任务失败: ' + msg.split(/\r?\n/)[0]);
10888
+ if (isSchtasksAccessDenied(msg)) console.error('(当前非管理员,无法移除管理员注册的计划任务;请用管理员终端执行 yotta-memory lan disable)');
10751
10889
  sawError = true;
10752
10890
  }
10753
10891
  }
@@ -10759,21 +10897,27 @@ function cmdLanWinDisable() {
10759
10897
  if (sawError) process.exit(1);
10760
10898
  }
10761
10899
  function cmdLanWinStatus() {
10762
- if (process.platform !== 'win32') {
10763
- console.log('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' + process.platform);
10900
+ if (lanPlatform() !== 'win32') {
10901
+ console.log('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' + lanPlatform());
10764
10902
  return;
10765
10903
  }
10766
10904
  let taskFound = false;
10767
- try {
10768
- const out = child_process.execFileSync('schtasks', ['/query', '/tn', LAN_TASK_NAME, '/fo', 'LIST', '/v'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
10905
+ const q = lanWindowsRunSchtasks({}, ['/query', '/tn', LAN_TASK_NAME, '/fo', 'LIST', '/v']);
10906
+ if (q.status === 0) {
10769
10907
  taskFound = true;
10770
10908
  console.log('计划任务 ' + LAN_TASK_NAME + ': 已启用');
10771
- const re = /^(状态|Status)\s*:\s*(.+)$/;
10772
- for (const line of String(out).split(/\r?\n/)) {
10909
+ const re = /^(状态|Status|登录模式|Logon Mode|上次运行时间|Last Run Time|上次结果|Last Result)\s*:\s*(.+)$/;
10910
+ let logonMode = '';
10911
+ for (const line of String(q.stdout).split(/\r?\n/)) {
10773
10912
  const m = re.exec(line.trim());
10774
- if (m) console.log(' ' + m[1] + ': ' + m[2]);
10913
+ if (!m) continue;
10914
+ console.log(' ' + m[1] + ': ' + m[2]);
10915
+ if (/^(登录模式|Logon Mode)$/.test(m[1])) logonMode = m[2];
10775
10916
  }
10776
- } catch (e) {
10917
+ if (/interactive|交互式/i.test(logonMode)) {
10918
+ console.log(' 提示: 登录模式为交互式(仅登录时运行,且在登录会话会弹窗口)——这是旧版任务;请重新执行 yotta-memory lan enable 修复为后台静默');
10919
+ }
10920
+ } else {
10777
10921
  console.log('计划任务 ' + LAN_TASK_NAME + ': 未启用');
10778
10922
  }
10779
10923
  const vbsOk = lanFileHasMarker(lanVbsPath());
@@ -12124,6 +12268,9 @@ module.exports = {
12124
12268
  doctorCore: doctorCore,
12125
12269
  destructiveGuardCore: destructiveGuardCore,
12126
12270
  backupWindowsTaskXml: backupWindowsTaskXml,
12271
+ lanWindowsTaskXml: lanWindowsTaskXml,
12272
+ lanWindowsEnableCore: lanWindowsEnableCore,
12273
+ lanWindowsTaskUser: lanWindowsTaskUser,
12127
12274
  backupSystemdServiceContent: backupSystemdServiceContent,
12128
12275
  backupSystemdTimerContent: backupSystemdTimerContent,
12129
12276
  backupCronLine: backupCronLine,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yottameta/yotta-memory",
3
- "version": "0.22.2",
3
+ "version": "0.22.4",
4
4
  "description": "Yuanyi (元忆) — boundary-aware, file-based memory for AI agents. File-based, zero-dependency, diff/rollback-able; FACT/PREF/BOUND/COMMIT types (public shared / private isolated), user-level + project-level storage; v0.12 reliability baseline (init guard, trash-based deletion, independent-volume backup/list/doctor/restore, start-of-work doctor, transactional snapshots before destructive writes), v0.10 consolidation (periodic summaries with provenance, near-duplicate auto-merge, per-type decay, batch audit + rollback), v0.9 recall quality + context focus + optional local embedding plugin, plus v0.8 semantic search, feedback loop, self-organization and distillation.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -3,7 +3,7 @@
3
3
  "slug": "yotta-memory",
4
4
  "name": "元忆",
5
5
  "package": "@yottameta/yotta-memory",
6
- "version": "0.22.2",
6
+ "version": "0.22.4",
7
7
  "trust": "yottameta",
8
8
  "install": {
9
9
  "idempotent": true