fix(scripts): 自重启任务纳入卸载清理 —— 修「僵尸任务反复拉起已删文件」

真机发现:`system.agent.restart` 每次重启注册 `nodeagent-selfrestart-<毫秒>`,
而 TASK_WHITELIST 只有 nodeagent/na-gui/nodeagent-console → 卸载后这类任务残留,
会持续尝试拉起已被删除的 agent.mjs。

但它不能当垃圾随手清:**Running 的那一个往往就是当前 agent 的父任务**
(删了会连带杀掉 agent)。故只纳入「卸载清理」判定集合,由调用方在停掉
agent 之后使用。

匹配用严格命名空间 `^nodeagent-selfrestart-\d{6,}$`(纯数字时间戳形态),
不做任何通配;`nodeagent-selfrestart-abc`/前缀不全/带额外后缀一律不认,
单测同时钉死「第三方任务(Adobe/OneDrive)绝不出现」。
This commit is contained in:
jkooe committed 2026-10-27 13:06:51 +08:00
1 parent c062d1cc8b
commit cf51e0415a
2 files changed
+60 -1

No files matched your search

+26
View File
@@ -18,6 +18,26 @@
/** nodeagent 自己注册过的计划任务名(含历史遗留)。只增不改。 */
export const TASK_WHITELIST = ['nodeagent', 'na-gui', 'nodeagent-console'];
/**
* 自重启任务前缀 —— `system.agent.restart` 每次重启都注册
* `nodeagent-selfrestart-<毫秒时间戳>`(动作是「杀旧 PID → 前台拉起新进程」)。
*
* 2026-10-27 真机发现:这类任务不在上面的白名单里,于是**卸载时漏删**——
* 留下的任务会继续尝试拉起已被删除的 agent.mjs,形成每次登录都失败一次的僵尸。
* 但它也不能被当成垃圾随手清:**正在 Running 的那一个往往就是当前 agent 的
* 父任务**(删除会连带杀掉 agent)。故只把它纳入「卸载清理」的判定集合,
* 由调用方在「已经停掉 agent」之后使用。
*/
export const SELF_RESTART_PREFIX = 'nodeagent-selfrestart-';
/** `nodeagent-selfrestart-<纯数字>` —— 严格到毫秒时间戳形态,不接受任何通配。 */
const SELF_RESTART_RE = /^nodeagent-selfrestart-\d{6,}$/;
/** 是否是本框架的自重启任务(严格命名空间,非模糊匹配)。 */
export function isSelfRestartTask(name) {
return typeof name === 'string' && SELF_RESTART_RE.test(name);
}
/**
* 计划删除名单:白名单内**且实际存在**的任务,按白名单固定顺序返回。
*
@@ -37,5 +57,11 @@ export function planTaskCleanup(existing) {
for (const name of TASK_WHITELIST) {
if (present.has(name)) out.push(name);
}
// 自重启任务:仅接受严格命名空间(`nodeagent-selfrestart-<数字>`),
// 按名字排序保证输出稳定;与白名单重名时去重(实际不会发生,防御性)。
const selfRestart = [...present].filter(isSelfRestartTask).sort();
for (const name of selfRestart) {
if (!out.includes(name)) out.push(name);
}
return out;
}
+34 -1
View File
@@ -2,7 +2,7 @@ import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { TASK_WHITELIST, planTaskCleanup } from '../../scripts/lib/scheduled-tasks.mjs';
import { TASK_WHITELIST, isSelfRestartTask, planTaskCleanup } from '../../scripts/lib/scheduled-tasks.mjs';
/**
* na-gui 陈旧计划任务清理(ARCH B-P1-6 / T05)。
@@ -100,3 +100,36 @@ test('control.cmd 菜单暴露 cleanup 入口(纯 ASCII + CRLF 由 ps1-encodin
assert.match(cmd, /call :RUN cleanup/, 'control.cmd 菜单必须有 cleanup 项转发');
assert.match(cmd, /8\)\s*Cleanup stale tasks/, 'control.cmd 菜单应展示 cleanup 项');
});
// ===========================================================================
// 自重启任务(2026-10-27 真机发现:卸载漏删 → 僵尸任务反复拉起已删文件)
// ===========================================================================
test('isSelfRestartTask:只认严格命名空间 nodeagent-selfrestart-<数字>', () => {
assert.equal(isSelfRestartTask('nodeagent-selfrestart-1793077118968'), true);
assert.equal(isSelfRestartTask('nodeagent-selfrestart-123456'), true, '6 位时间戳级仍算');
// 以下都不是我们注册的名字,绝不能被当成自重启任务
assert.equal(isSelfRestartTask('nodeagent-selfrestart-'), false, '没有数字');
assert.equal(isSelfRestartTask('nodeagent-selfrestart-abc123'), false, '非纯数字');
assert.equal(isSelfRestartTask('nodeagent-selfrestart-123'), false, '位数过短(非时间戳形态)');
assert.equal(isSelfRestartTask('mynodeagent-selfrestart-123456'), false, '前缀必须从头匹配');
assert.equal(isSelfRestartTask('nodeagent-selfrestart-123456-extra'), false, '不得带额外后缀');
assert.equal(isSelfRestartTask(''), false);
assert.equal(isSelfRestartTask(null), false);
});
test('planTaskCleanup:白名单外的自重启任务纳入清理,且仍然只增不误删', () => {
const plan = planTaskCleanup([
'nodeagent',
'nodeagent-selfrestart-1793077118968',
'nodeagent-selfrestart-1793077118999',
'Adobe Acrobat Update Task', // 用户/第三方任务,绝不能碰
'OneDrive Standalone Update',
'nodeagent-selfrestart-notatask',
]);
assert.deepEqual(plan, [
'nodeagent',
'nodeagent-selfrestart-1793077118968',
'nodeagent-selfrestart-1793077118999',
], '白名单先出、自重启按名字排序,第三方任务一律不出现');
});