真机发现:`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)绝不出现」。
68 lines
3.3 KiB
JavaScript
68 lines
3.3 KiB
JavaScript
/**
|
|
* Windows 计划任务清理白名单(B-P1-6 / T05)。
|
|
*
|
|
* ## 为什么需要
|
|
* nodeagent 历史版本/缺陷修复在被控端上注册过计划任务(na-gui 等旧名),
|
|
* 卸载时若漏删,会留下「端口被占、起不来新版本」的幽灵任务。
|
|
* 但清理动作**只许按白名单删**——误删用户自己的任务是比漏删严重得多的
|
|
* 生产事故(可能停掉业务服务),故本文件是纯函数 + 白名单,禁通配删除。
|
|
*
|
|
* ## 铁律
|
|
* - 零依赖、零 import:与 startup-guard-core 同风格,任何 Node ≥22 可加载;
|
|
* - 白名单**只增不改**:发现新的历史任务名时追加,不从清单里移除旧名
|
|
* (老被控端上旧名任务可能还在);
|
|
* - 匹配是精确相等(大小写敏感):Windows 任务名实际大小写不敏感,
|
|
* 但这里宁可漏删也不模糊匹配——模糊匹配是误删之门。
|
|
*/
|
|
|
|
/** 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);
|
|
}
|
|
|
|
/**
|
|
* 计划删除名单:白名单内**且实际存在**的任务,按白名单固定顺序返回。
|
|
*
|
|
* 纯函数:查询结果由调用方注入(PowerShell 侧 Get-ScheduledTask),
|
|
* 本函数不碰文件系统、不碰注册表 —— 判断可单测,误删风险可证明。
|
|
*
|
|
* @param {string[]} existing 被控端上实际存在的任务名(可含重复/空串/非字符串)
|
|
* @returns {string[]} 需要删除的任务名(每个最多一次;白名单外一律不出现)
|
|
*/
|
|
export function planTaskCleanup(existing) {
|
|
if (!Array.isArray(existing)) return [];
|
|
const present = new Set();
|
|
for (const n of existing) {
|
|
if (typeof n === 'string' && n.length > 0) present.add(n);
|
|
}
|
|
const out = [];
|
|
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;
|
|
}
|