From cf51e0415ad2388a7e949bcffabf884e50ea3ab5 Mon Sep 17 00:00:00 2001 From: jkooe <62494645+jkooe@users.noreply.github.com> Date: Tue, 27 Oct 2026 13:06:51 +0800 Subject: [PATCH] =?UTF-8?q?fix(scripts):=20=E8=87=AA=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E7=BA=B3=E5=85=A5=E5=8D=B8=E8=BD=BD=E6=B8=85?= =?UTF-8?q?=E7=90=86=20=E2=80=94=E2=80=94=20=E4=BF=AE=E3=80=8C=E5=83=B5?= =?UTF-8?q?=E5=B0=B8=E4=BB=BB=E5=8A=A1=E5=8F=8D=E5=A4=8D=E6=8B=89=E8=B5=B7?= =?UTF-8?q?=E5=B7=B2=E5=88=A0=E6=96=87=E4=BB=B6=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 真机发现:`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)绝不出现」。 --- scripts/lib/scheduled-tasks.mjs | 26 ++++++++++++++++++++++ tests/unit/na-gui-cleanup.test.mjs | 35 +++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/scripts/lib/scheduled-tasks.mjs b/scripts/lib/scheduled-tasks.mjs index b70d19d..acb6ad4 100644 --- a/scripts/lib/scheduled-tasks.mjs +++ b/scripts/lib/scheduled-tasks.mjs @@ -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; } diff --git a/tests/unit/na-gui-cleanup.test.mjs b/tests/unit/na-gui-cleanup.test.mjs index 158a571..6ae8eda 100644 --- a/tests/unit/na-gui-cleanup.test.mjs +++ b/tests/unit/na-gui-cleanup.test.mjs @@ -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', + ], '白名单先出、自重启按名字排序,第三方任务一律不出现'); +});