cron-up 26.10.32 → 26.10.34
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/CONTRIBUTING.md +61 -0
- package/README.md +228 -112
- package/docs/internals.md +190 -0
- package/docs/migration.md +47 -0
- package/docs/release.md +75 -0
- package/package.json +5 -3
- package/src/args.mjs +1 -2
- package/src/cli.mjs +18 -20
- package/src/config.mjs +22 -19
- package/src/constants.mjs +7 -17
- package/src/cron.mjs +18 -9
- package/src/display.mjs +2 -2
- package/src/list.mjs +1 -1
- package/src/logs.mjs +2 -1
- package/src/overview.mjs +7 -7
- package/src/patrol.mjs +199 -162
- package/src/registry.mjs +10 -13
- package/src/service.mjs +189 -49
- package/src/sessions.mjs +61 -20
- package/src/tasks.mjs +45 -84
package/src/cron.mjs
CHANGED
|
@@ -12,11 +12,13 @@ import { isDecInt } from './internals.mjs';
|
|
|
12
12
|
const MS_DAY = 86400_000;
|
|
13
13
|
const MS_MINUTE = 60_000;
|
|
14
14
|
|
|
15
|
-
//
|
|
16
|
-
// 5
|
|
15
|
+
// 严格十进制安全整数(isDecInt 的谓词见 internals):parseInt("5x") 会
|
|
16
|
+
// 静默返回 5;达到 2^53 后 v+=1 不再改变数值,区间循环会永久挂住。
|
|
17
17
|
function strictInt(s) {
|
|
18
18
|
if (!isDecInt(s)) throw new Error(`不是整数:${s}`);
|
|
19
|
-
|
|
19
|
+
const n = parseInt(s, 10);
|
|
20
|
+
if (!Number.isSafeInteger(n)) throw new Error(`整数超出安全范围:${s}`);
|
|
21
|
+
return n;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
// 解析一个 cron 字段为它命名的合法值域集合。
|
|
@@ -53,9 +55,16 @@ export function parseCronField(expr, lo, hi) {
|
|
|
53
55
|
start = end = strictInt(rng);
|
|
54
56
|
if (slash >= 0) end = hi; // vixie:"5/10" 表示 5,15,25,… 直到 hi
|
|
55
57
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
if (start > end || end < lo || start > hi) continue;
|
|
59
|
+
// 只迭代合法值域内的至多 hi-lo+1 个值。起点低于 lo 时用步进算出
|
|
60
|
+
// 首个候选,不能直接钳到 lo,否则 0-20/3 在 lo=1 时会从 3,6,…
|
|
61
|
+
// 变成 1,4,…;终点直接钳到 hi,超大区间不会拖死整轮巡检。
|
|
62
|
+
const first = start < lo
|
|
63
|
+
? start + Math.ceil((lo - start) / step) * step
|
|
64
|
+
: start;
|
|
65
|
+
if (!Number.isSafeInteger(first)) continue;
|
|
66
|
+
const last = Math.min(end, hi);
|
|
67
|
+
for (let v = first; v <= last; v += step) values.add(v);
|
|
59
68
|
}
|
|
60
69
|
return values;
|
|
61
70
|
}
|
|
@@ -251,7 +260,7 @@ export function taskWanted(task, now, leadMs) {
|
|
|
251
260
|
// 两个口径供展示视图复用(同一个已解析 cron 顺带回答巡检问题)。
|
|
252
261
|
export { wantedNow, wantedWithin };
|
|
253
262
|
|
|
254
|
-
// ---- Claude Code
|
|
263
|
+
// ---- Claude Code 投递抖动(预计投递时间)----
|
|
255
264
|
// 复刻 v2.1.285 内部的 M():任务 id 前 8 位十六进制除以 2^32,得 [0,1) 的
|
|
256
265
|
// 固定哈希——同一任务每次触发的抖动完全相同,不是随机数。id 缺失或前 8
|
|
257
266
|
// 位都不是十六进制时 parseInt 得 NaN,上游回退 0;任务文件由别的程序书
|
|
@@ -262,7 +271,7 @@ export function taskIdHash(taskId) {
|
|
|
262
271
|
return Number.isFinite(n) ? n : 0;
|
|
263
272
|
}
|
|
264
273
|
|
|
265
|
-
//
|
|
274
|
+
// 任务在展示语境里的「设定时间 → 预计投递」对:一次性任务是落点加网格
|
|
266
275
|
// 提前量;周期任务在投递尾窗内属于上一落点(即将到来的这次延迟投递,
|
|
267
276
|
// 不是 nextAfter 给出的下个落点——后者严格在未来,会把展示拐到明天),
|
|
268
277
|
// 否则是下一落点 + 按 id 的抖动。先于窗口检查尾窗:2 月 29 日这类长周期
|
|
@@ -317,7 +326,7 @@ function recurringDelayMs(cron, slot, taskId, withinDays) {
|
|
|
317
326
|
|
|
318
327
|
// 周期任务的「投递尾窗」判定:上一落点已过、按 id 算的延迟投递时刻未
|
|
319
328
|
// 到。返回 [落点, 投递时刻 ms];不在尾窗、找不到上一或下一落点返回
|
|
320
|
-
// null。wanted
|
|
329
|
+
// null。wanted(会话需求判定)与 nextDelivery(展示)共用这一口径,两边不会
|
|
321
330
|
// 各说各话;窗口取 TAIL_WINDOW_DAYS,理由见 constants。
|
|
322
331
|
function recurringTailWindow(cron, taskId, now) {
|
|
323
332
|
const prev = cron.prevAtOrBefore(now, TAIL_WINDOW_DAYS);
|
package/src/display.mjs
CHANGED
|
@@ -110,7 +110,7 @@ export function taskView(task, now, leadMs) {
|
|
|
110
110
|
// wanted 恒为 null 或布尔:没传 leadMs(不问)与 cron 无效(问不了)
|
|
111
111
|
// 都是 null,键的形状不随参数变化。
|
|
112
112
|
wanted: leadMs !== undefined ? wantedWithin(cron, task, now, leadMs) : null,
|
|
113
|
-
// 计入 Claude Code
|
|
113
|
+
// 计入 Claude Code 投递抖动后的预计投递时刻;已错过的一次性任务
|
|
114
114
|
// 没有未来触发,为 null;尾窗内的周期任务是即将到来的这次延迟投递。
|
|
115
115
|
// 与 nxt 同为 Date 或 null。
|
|
116
116
|
fire: missed ? null : fire,
|
|
@@ -135,7 +135,7 @@ export function pad2(n) {
|
|
|
135
135
|
return String(n).padStart(2, '0');
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
//
|
|
138
|
+
// 「设定时间 → 预计投递」文案。抖动四舍五入到秒(调度器秒级轮询):
|
|
139
139
|
// 同日省略第二个日期;预计值带非零秒时显示 HH:MM:SS(如 05:15:37);落
|
|
140
140
|
// 在同一分钟(无抖动)则只显示落点,不画无意义的箭头。
|
|
141
141
|
export function fmtFireRange(nxt, fire) {
|
package/src/list.mjs
CHANGED
package/src/logs.mjs
CHANGED
|
@@ -72,7 +72,8 @@ export function cmdLogs(args) {
|
|
|
72
72
|
+ `「${args.workspace}」匹配的会话日志`);
|
|
73
73
|
throw new ExitError(1);
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
// -F 同时按名字与描述符跟随:日志 rename 后原路径重建也能接上。
|
|
76
|
+
const tailArgs = args.follow ? ['-F'] : ['-n', '100'];
|
|
76
77
|
tailArgs.push(file);
|
|
77
78
|
const r = deps.spawnSync('/usr/bin/tail', tailArgs, { stdio: 'inherit' });
|
|
78
79
|
if (r.error) throw r.error;
|
package/src/overview.mjs
CHANGED
|
@@ -77,7 +77,7 @@ function overviewTasks(cfg, cols, details, state) {
|
|
|
77
77
|
bad += 1;
|
|
78
78
|
continue;
|
|
79
79
|
}
|
|
80
|
-
//
|
|
80
|
+
// 「最近」按计入抖动后的预计投递时间排,而非 cron 落点。
|
|
81
81
|
const fire = v.fire ?? v.nxt;
|
|
82
82
|
if (fire && (!soonest || fire.getTime() < soonest[0].getTime())) {
|
|
83
83
|
soonest = [fire, ws, v.summary, v.nxt];
|
|
@@ -189,18 +189,18 @@ function overviewSessions(cols, state, details, suppressCooling, discovered) {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
const COMMANDS = [
|
|
192
|
-
['list', '
|
|
192
|
+
['list', '逐个列出任务的内容摘要、设定/预计投递时间与预热状态'],
|
|
193
193
|
['logs [目录片段] [-f]', '查看巡检日志或后台会话记录'],
|
|
194
194
|
['run', '立即手动巡检一轮'],
|
|
195
|
-
['install --roots ~/a,~/b', '
|
|
195
|
+
['install --roots ~/a,~/b', '更新扫描目录并重新安装(不带参数沿用有效配置)'],
|
|
196
196
|
];
|
|
197
197
|
|
|
198
198
|
export async function cmdOverview(args) {
|
|
199
199
|
const cols = deps.terminalWidth();
|
|
200
200
|
const v = packageVersion();
|
|
201
201
|
deps.print(v === null
|
|
202
|
-
? 'cron-up ——
|
|
203
|
-
: `cron-up ${v} ——
|
|
202
|
+
? 'cron-up —— 为 Claude Code 定时任务按需预热交互会话'
|
|
203
|
+
: `cron-up ${v} —— 为 Claude Code 定时任务按需预热交互会话(一任务一会话)`);
|
|
204
204
|
|
|
205
205
|
const cfgPath = args?.config ?? deps.paths.configPath;
|
|
206
206
|
let cfg = null;
|
|
@@ -232,8 +232,8 @@ export async function cmdOverview(args) {
|
|
|
232
232
|
} else if (cfg === null) {
|
|
233
233
|
deps.print('配置:缺失,运行 cron-up install 生成');
|
|
234
234
|
} else {
|
|
235
|
-
deps.print(`巡检:每 ${INTERVAL_SECONDS}
|
|
236
|
-
+ '
|
|
235
|
+
deps.print(`巡检:每 ${INTERVAL_SECONDS} 秒一轮,周期任务提前 ${LEAD_SECONDS} `
|
|
236
|
+
+ '秒进入预热窗口');
|
|
237
237
|
if (info && info.interval !== null && info.interval !== undefined
|
|
238
238
|
&& info.interval !== INTERVAL_SECONDS) {
|
|
239
239
|
alerts.push(`launchd 实际间隔 ${info.interval} 秒与当前版本的 `
|