@deepmaid/maid-cron 0.0.0-stage → 0.1.0-alpha.1
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/LICENSE +21 -0
- package/cordis.patch.yml +3 -0
- package/lib/index.d.ts +191 -0
- package/lib/index.js +1303 -0
- package/package.json +44 -3
- package/README.md +0 -3
package/lib/index.js
ADDED
|
@@ -0,0 +1,1303 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
//#region ../../node_modules/.pnpm/croner@10.0.1/node_modules/croner/dist/croner.js
|
|
6
|
+
function T(s) {
|
|
7
|
+
return Date.UTC(s.y, s.m - 1, s.d, s.h, s.i, s.s);
|
|
8
|
+
}
|
|
9
|
+
function D(s, e) {
|
|
10
|
+
return s.y === e.y && s.m === e.m && s.d === e.d && s.h === e.h && s.i === e.i && s.s === e.s;
|
|
11
|
+
}
|
|
12
|
+
function A(s, e) {
|
|
13
|
+
let t = new Date(Date.parse(s));
|
|
14
|
+
if (isNaN(t)) throw new Error("Invalid ISO8601 passed to timezone parser.");
|
|
15
|
+
let r = s.substring(9);
|
|
16
|
+
return r.includes("Z") || r.includes("+") || r.includes("-") ? b(t.getUTCFullYear(), t.getUTCMonth() + 1, t.getUTCDate(), t.getUTCHours(), t.getUTCMinutes(), t.getUTCSeconds(), "Etc/UTC") : b(t.getFullYear(), t.getMonth() + 1, t.getDate(), t.getHours(), t.getMinutes(), t.getSeconds(), e);
|
|
17
|
+
}
|
|
18
|
+
function v(s, e, t) {
|
|
19
|
+
return k(A(s, e), t);
|
|
20
|
+
}
|
|
21
|
+
function k(s, e) {
|
|
22
|
+
let t = new Date(T(s)), r = g(t, s.tz), a = T(s) - T(r), o = new Date(t.getTime() + a), h = g(o, s.tz);
|
|
23
|
+
if (D(h, s)) {
|
|
24
|
+
let u = /* @__PURE__ */ new Date(o.getTime() - 36e5);
|
|
25
|
+
return D(g(u, s.tz), s) ? u : o;
|
|
26
|
+
}
|
|
27
|
+
let l = new Date(o.getTime() + T(s) - T(h));
|
|
28
|
+
if (D(g(l, s.tz), s)) return l;
|
|
29
|
+
if (e) throw new Error("Invalid date passed to fromTZ()");
|
|
30
|
+
return o.getTime() > l.getTime() ? o : l;
|
|
31
|
+
}
|
|
32
|
+
function g(s, e) {
|
|
33
|
+
let t, r;
|
|
34
|
+
try {
|
|
35
|
+
t = new Intl.DateTimeFormat("en-US", {
|
|
36
|
+
timeZone: e,
|
|
37
|
+
year: "numeric",
|
|
38
|
+
month: "numeric",
|
|
39
|
+
day: "numeric",
|
|
40
|
+
hour: "numeric",
|
|
41
|
+
minute: "numeric",
|
|
42
|
+
second: "numeric",
|
|
43
|
+
hour12: !1
|
|
44
|
+
}), r = t.formatToParts(s);
|
|
45
|
+
} catch (i) {
|
|
46
|
+
let a = i instanceof Error ? i.message : String(i);
|
|
47
|
+
throw new RangeError(`toTZ: Invalid timezone '${e}' or date. Please provide a valid IANA timezone (e.g., 'America/New_York', 'Europe/Stockholm'). Original error: ${a}`);
|
|
48
|
+
}
|
|
49
|
+
let n = {
|
|
50
|
+
year: 0,
|
|
51
|
+
month: 0,
|
|
52
|
+
day: 0,
|
|
53
|
+
hour: 0,
|
|
54
|
+
minute: 0,
|
|
55
|
+
second: 0
|
|
56
|
+
};
|
|
57
|
+
for (let i of r) (i.type === "year" || i.type === "month" || i.type === "day" || i.type === "hour" || i.type === "minute" || i.type === "second") && (n[i.type] = parseInt(i.value, 10));
|
|
58
|
+
if (isNaN(n.year) || isNaN(n.month) || isNaN(n.day) || isNaN(n.hour) || isNaN(n.minute) || isNaN(n.second)) throw new Error(`toTZ: Failed to parse all date components from timezone '${e}'. This may indicate an invalid date or timezone configuration. Parsed components: ${JSON.stringify(n)}`);
|
|
59
|
+
return n.hour === 24 && (n.hour = 0), {
|
|
60
|
+
y: n.year,
|
|
61
|
+
m: n.month,
|
|
62
|
+
d: n.day,
|
|
63
|
+
h: n.hour,
|
|
64
|
+
i: n.minute,
|
|
65
|
+
s: n.second,
|
|
66
|
+
tz: e
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function b(s, e, t, r, n, i, a) {
|
|
70
|
+
return {
|
|
71
|
+
y: s,
|
|
72
|
+
m: e,
|
|
73
|
+
d: t,
|
|
74
|
+
h: r,
|
|
75
|
+
i: n,
|
|
76
|
+
s: i,
|
|
77
|
+
tz: a
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
var O = [
|
|
81
|
+
1,
|
|
82
|
+
2,
|
|
83
|
+
4,
|
|
84
|
+
8,
|
|
85
|
+
16
|
|
86
|
+
];
|
|
87
|
+
var C = class {
|
|
88
|
+
pattern;
|
|
89
|
+
timezone;
|
|
90
|
+
mode;
|
|
91
|
+
alternativeWeekdays;
|
|
92
|
+
sloppyRanges;
|
|
93
|
+
second;
|
|
94
|
+
minute;
|
|
95
|
+
hour;
|
|
96
|
+
day;
|
|
97
|
+
month;
|
|
98
|
+
dayOfWeek;
|
|
99
|
+
year;
|
|
100
|
+
lastDayOfMonth;
|
|
101
|
+
lastWeekday;
|
|
102
|
+
nearestWeekdays;
|
|
103
|
+
starDOM;
|
|
104
|
+
starDOW;
|
|
105
|
+
starYear;
|
|
106
|
+
useAndLogic;
|
|
107
|
+
constructor(e, t, r) {
|
|
108
|
+
this.pattern = e, this.timezone = t, this.mode = r?.mode ?? "auto", this.alternativeWeekdays = r?.alternativeWeekdays ?? !1, this.sloppyRanges = r?.sloppyRanges ?? !1, this.second = Array(60).fill(0), this.minute = Array(60).fill(0), this.hour = Array(24).fill(0), this.day = Array(31).fill(0), this.month = Array(12).fill(0), this.dayOfWeek = Array(7).fill(0), this.year = Array(1e4).fill(0), this.lastDayOfMonth = !1, this.lastWeekday = !1, this.nearestWeekdays = Array(31).fill(0), this.starDOM = !1, this.starDOW = !1, this.starYear = !1, this.useAndLogic = !1, this.parse();
|
|
109
|
+
}
|
|
110
|
+
parse() {
|
|
111
|
+
if (!(typeof this.pattern == "string" || this.pattern instanceof String)) throw new TypeError("CronPattern: Pattern has to be of type string.");
|
|
112
|
+
this.pattern.indexOf("@") >= 0 && (this.pattern = this.handleNicknames(this.pattern).trim());
|
|
113
|
+
let e = this.pattern.match(/\S+/g) || [""], t = e.length;
|
|
114
|
+
if (e.length < 5 || e.length > 7) throw new TypeError("CronPattern: invalid configuration format ('" + this.pattern + "'), exactly five, six, or seven space separated parts are required.");
|
|
115
|
+
if (this.mode !== "auto") {
|
|
116
|
+
let n;
|
|
117
|
+
switch (this.mode) {
|
|
118
|
+
case "5-part":
|
|
119
|
+
n = 5;
|
|
120
|
+
break;
|
|
121
|
+
case "6-part":
|
|
122
|
+
n = 6;
|
|
123
|
+
break;
|
|
124
|
+
case "7-part":
|
|
125
|
+
n = 7;
|
|
126
|
+
break;
|
|
127
|
+
case "5-or-6-parts":
|
|
128
|
+
n = [5, 6];
|
|
129
|
+
break;
|
|
130
|
+
case "6-or-7-parts":
|
|
131
|
+
n = [6, 7];
|
|
132
|
+
break;
|
|
133
|
+
default: n = 0;
|
|
134
|
+
}
|
|
135
|
+
if (!(Array.isArray(n) ? n.includes(t) : t === n)) {
|
|
136
|
+
let a = Array.isArray(n) ? n.join(" or ") : n.toString();
|
|
137
|
+
throw new TypeError(`CronPattern: mode '${this.mode}' requires exactly ${a} parts, but pattern '${this.pattern}' has ${t} parts.`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (e.length === 5 && e.unshift("0"), e.length === 6 && e.push("*"), e[3].toUpperCase() === "LW" ? (this.lastWeekday = !0, e[3] = "") : e[3].toUpperCase().indexOf("L") >= 0 && (e[3] = e[3].replace(/L/gi, ""), this.lastDayOfMonth = !0), e[3] == "*" && (this.starDOM = !0), e[6] == "*" && (this.starYear = !0), e[4].length >= 3 && (e[4] = this.replaceAlphaMonths(e[4])), e[5].length >= 3 && (e[5] = this.alternativeWeekdays ? this.replaceAlphaDaysQuartz(e[5]) : this.replaceAlphaDays(e[5])), e[5].startsWith("+") && (this.useAndLogic = !0, e[5] = e[5].substring(1), e[5] === "")) throw new TypeError("CronPattern: Day-of-week field cannot be empty after '+' modifier.");
|
|
141
|
+
switch (e[5] == "*" && (this.starDOW = !0), this.pattern.indexOf("?") >= 0 && (e[0] = e[0].replace(/\?/g, "*"), e[1] = e[1].replace(/\?/g, "*"), e[2] = e[2].replace(/\?/g, "*"), e[3] = e[3].replace(/\?/g, "*"), e[4] = e[4].replace(/\?/g, "*"), e[5] = e[5].replace(/\?/g, "*"), e[6] && (e[6] = e[6].replace(/\?/g, "*"))), this.mode) {
|
|
142
|
+
case "5-part":
|
|
143
|
+
e[0] = "0", e[6] = "*";
|
|
144
|
+
break;
|
|
145
|
+
case "6-part":
|
|
146
|
+
e[6] = "*";
|
|
147
|
+
break;
|
|
148
|
+
case "5-or-6-parts": e[6] = "*";
|
|
149
|
+
}
|
|
150
|
+
this.throwAtIllegalCharacters(e), this.partToArray("second", e[0], 0, 1), this.partToArray("minute", e[1], 0, 1), this.partToArray("hour", e[2], 0, 1), this.partToArray("day", e[3], -1, 1), this.partToArray("month", e[4], -1, 1);
|
|
151
|
+
let r = this.alternativeWeekdays ? -1 : 0;
|
|
152
|
+
this.partToArray("dayOfWeek", e[5], r, 63), this.partToArray("year", e[6], 0, 1), !this.alternativeWeekdays && this.dayOfWeek[7] && (this.dayOfWeek[0] = this.dayOfWeek[7]);
|
|
153
|
+
}
|
|
154
|
+
partToArray(e, t, r, n) {
|
|
155
|
+
let i = this[e], a = e === "day" && this.lastDayOfMonth, o = e === "day" && this.lastWeekday;
|
|
156
|
+
if (t === "" && !a && !o) throw new TypeError("CronPattern: configuration entry " + e + " (" + t + ") is empty, check for trailing spaces.");
|
|
157
|
+
if (t === "*") return i.fill(n);
|
|
158
|
+
let h = t.split(",");
|
|
159
|
+
if (h.length > 1) for (let l = 0; l < h.length; l++) this.partToArray(e, h[l], r, n);
|
|
160
|
+
else t.indexOf("-") !== -1 && t.indexOf("/") !== -1 ? this.handleRangeWithStepping(t, e, r, n) : t.indexOf("-") !== -1 ? this.handleRange(t, e, r, n) : t.indexOf("/") !== -1 ? this.handleStepping(t, e, r, n) : t !== "" && this.handleNumber(t, e, r, n);
|
|
161
|
+
}
|
|
162
|
+
throwAtIllegalCharacters(e) {
|
|
163
|
+
for (let t = 0; t < e.length; t++) if ((t === 3 ? /[^/*0-9,\-WwLl]+/ : t === 5 ? /[^/*0-9,\-#Ll]+/ : /[^/*0-9,\-]+/).test(e[t])) throw new TypeError("CronPattern: configuration entry " + t + " (" + e[t] + ") contains illegal characters.");
|
|
164
|
+
}
|
|
165
|
+
handleNumber(e, t, r, n) {
|
|
166
|
+
let i = this.extractNth(e, t), a = e.toUpperCase().includes("W");
|
|
167
|
+
if (t !== "day" && a) throw new TypeError("CronPattern: Nearest weekday modifier (W) only allowed in day-of-month.");
|
|
168
|
+
a && (t = "nearestWeekdays");
|
|
169
|
+
let o = parseInt(i[0], 10) + r;
|
|
170
|
+
if (isNaN(o)) throw new TypeError("CronPattern: " + t + " is not a number: '" + e + "'");
|
|
171
|
+
this.setPart(t, o, i[1] || n);
|
|
172
|
+
}
|
|
173
|
+
setPart(e, t, r) {
|
|
174
|
+
if (!Object.prototype.hasOwnProperty.call(this, e)) throw new TypeError("CronPattern: Invalid part specified: " + e);
|
|
175
|
+
if (e === "dayOfWeek") {
|
|
176
|
+
if (t === 7 && (t = 0), t < 0 || t > 6) throw new RangeError("CronPattern: Invalid value for dayOfWeek: " + t);
|
|
177
|
+
this.setNthWeekdayOfMonth(t, r);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (e === "second" || e === "minute") {
|
|
181
|
+
if (t < 0 || t >= 60) throw new RangeError("CronPattern: Invalid value for " + e + ": " + t);
|
|
182
|
+
} else if (e === "hour") {
|
|
183
|
+
if (t < 0 || t >= 24) throw new RangeError("CronPattern: Invalid value for " + e + ": " + t);
|
|
184
|
+
} else if (e === "day" || e === "nearestWeekdays") {
|
|
185
|
+
if (t < 0 || t >= 31) throw new RangeError("CronPattern: Invalid value for " + e + ": " + t);
|
|
186
|
+
} else if (e === "month") {
|
|
187
|
+
if (t < 0 || t >= 12) throw new RangeError("CronPattern: Invalid value for " + e + ": " + t);
|
|
188
|
+
} else if (e === "year" && (t < 1 || t >= 1e4)) throw new RangeError("CronPattern: Invalid value for " + e + ": " + t + " (supported range: 1-9999)");
|
|
189
|
+
this[e][t] = r;
|
|
190
|
+
}
|
|
191
|
+
validateNotNaN(e, t) {
|
|
192
|
+
if (isNaN(e)) throw new TypeError(t);
|
|
193
|
+
}
|
|
194
|
+
validateRange(e, t, r, n, i) {
|
|
195
|
+
if (e > t) throw new TypeError("CronPattern: From value is larger than to value: '" + i + "'");
|
|
196
|
+
if (r !== void 0) {
|
|
197
|
+
if (r === 0) throw new TypeError("CronPattern: Syntax error, illegal stepping: 0");
|
|
198
|
+
if (r > this[n].length) throw new TypeError("CronPattern: Syntax error, steps cannot be greater than maximum value of part (" + this[n].length + ")");
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
handleRangeWithStepping(e, t, r, n) {
|
|
202
|
+
if (e.toUpperCase().includes("W")) throw new TypeError("CronPattern: Syntax error, W is not allowed in ranges with stepping.");
|
|
203
|
+
let i = this.extractNth(e, t), a = i[0].match(/^(\d+)-(\d+)\/(\d+)$/);
|
|
204
|
+
if (a === null) throw new TypeError("CronPattern: Syntax error, illegal range with stepping: '" + e + "'");
|
|
205
|
+
let [, o, h, l] = a, y = parseInt(o, 10) + r, u = parseInt(h, 10) + r, d = parseInt(l, 10);
|
|
206
|
+
this.validateNotNaN(y, "CronPattern: Syntax error, illegal lower range (NaN)"), this.validateNotNaN(u, "CronPattern: Syntax error, illegal upper range (NaN)"), this.validateNotNaN(d, "CronPattern: Syntax error, illegal stepping: (NaN)"), this.validateRange(y, u, d, t, e);
|
|
207
|
+
for (let c = y; c <= u; c += d) this.setPart(t, c, i[1] || n);
|
|
208
|
+
}
|
|
209
|
+
extractNth(e, t) {
|
|
210
|
+
let r = e, n;
|
|
211
|
+
if (r.includes("#")) {
|
|
212
|
+
if (t !== "dayOfWeek") throw new Error("CronPattern: nth (#) only allowed in day-of-week field");
|
|
213
|
+
n = r.split("#")[1], r = r.split("#")[0];
|
|
214
|
+
} else if (r.toUpperCase().endsWith("L")) {
|
|
215
|
+
if (t !== "dayOfWeek") throw new Error("CronPattern: L modifier only allowed in day-of-week field (use L alone for day-of-month)");
|
|
216
|
+
n = "L", r = r.slice(0, -1);
|
|
217
|
+
}
|
|
218
|
+
return [r, n];
|
|
219
|
+
}
|
|
220
|
+
handleRange(e, t, r, n) {
|
|
221
|
+
if (e.toUpperCase().includes("W")) throw new TypeError("CronPattern: Syntax error, W is not allowed in a range.");
|
|
222
|
+
let i = this.extractNth(e, t), a = i[0].split("-");
|
|
223
|
+
if (a.length !== 2) throw new TypeError("CronPattern: Syntax error, illegal range: '" + e + "'");
|
|
224
|
+
let o = parseInt(a[0], 10) + r, h = parseInt(a[1], 10) + r;
|
|
225
|
+
this.validateNotNaN(o, "CronPattern: Syntax error, illegal lower range (NaN)"), this.validateNotNaN(h, "CronPattern: Syntax error, illegal upper range (NaN)"), this.validateRange(o, h, void 0, t, e);
|
|
226
|
+
for (let l = o; l <= h; l++) this.setPart(t, l, i[1] || n);
|
|
227
|
+
}
|
|
228
|
+
handleStepping(e, t, r, n) {
|
|
229
|
+
if (e.toUpperCase().includes("W")) throw new TypeError("CronPattern: Syntax error, W is not allowed in parts with stepping.");
|
|
230
|
+
let i = this.extractNth(e, t), a = i[0].split("/");
|
|
231
|
+
if (a.length !== 2) throw new TypeError("CronPattern: Syntax error, illegal stepping: '" + e + "'");
|
|
232
|
+
if (this.sloppyRanges) a[0] === "" && (a[0] = "*");
|
|
233
|
+
else {
|
|
234
|
+
if (a[0] === "") throw new TypeError("CronPattern: Syntax error, stepping with missing prefix ('" + e + "') is not allowed. Use wildcard (*/step) or range (min-max/step) instead.");
|
|
235
|
+
if (a[0] !== "*") throw new TypeError("CronPattern: Syntax error, stepping with numeric prefix ('" + e + "') is not allowed. Use wildcard (*/step) or range (min-max/step) instead.");
|
|
236
|
+
}
|
|
237
|
+
let o = 0;
|
|
238
|
+
a[0] !== "*" && (o = parseInt(a[0], 10) + r);
|
|
239
|
+
let h = parseInt(a[1], 10);
|
|
240
|
+
this.validateNotNaN(h, "CronPattern: Syntax error, illegal stepping: (NaN)"), this.validateRange(0, this[t].length - 1, h, t, e);
|
|
241
|
+
for (let l = o; l < this[t].length; l += h) this.setPart(t, l, i[1] || n);
|
|
242
|
+
}
|
|
243
|
+
replaceAlphaDays(e) {
|
|
244
|
+
return e.replace(/-sun/gi, "-7").replace(/sun/gi, "0").replace(/mon/gi, "1").replace(/tue/gi, "2").replace(/wed/gi, "3").replace(/thu/gi, "4").replace(/fri/gi, "5").replace(/sat/gi, "6");
|
|
245
|
+
}
|
|
246
|
+
replaceAlphaDaysQuartz(e) {
|
|
247
|
+
return e.replace(/sun/gi, "1").replace(/mon/gi, "2").replace(/tue/gi, "3").replace(/wed/gi, "4").replace(/thu/gi, "5").replace(/fri/gi, "6").replace(/sat/gi, "7");
|
|
248
|
+
}
|
|
249
|
+
replaceAlphaMonths(e) {
|
|
250
|
+
return e.replace(/jan/gi, "1").replace(/feb/gi, "2").replace(/mar/gi, "3").replace(/apr/gi, "4").replace(/may/gi, "5").replace(/jun/gi, "6").replace(/jul/gi, "7").replace(/aug/gi, "8").replace(/sep/gi, "9").replace(/oct/gi, "10").replace(/nov/gi, "11").replace(/dec/gi, "12");
|
|
251
|
+
}
|
|
252
|
+
handleNicknames(e) {
|
|
253
|
+
let t = e.trim().toLowerCase();
|
|
254
|
+
if (t === "@yearly" || t === "@annually") return "0 0 1 1 *";
|
|
255
|
+
if (t === "@monthly") return "0 0 1 * *";
|
|
256
|
+
if (t === "@weekly") return "0 0 * * 0";
|
|
257
|
+
if (t === "@daily" || t === "@midnight") return "0 0 * * *";
|
|
258
|
+
if (t === "@hourly") return "0 * * * *";
|
|
259
|
+
if (t === "@reboot") throw new TypeError("CronPattern: @reboot is not supported in this environment. This is an event-based trigger that requires system startup detection.");
|
|
260
|
+
return e;
|
|
261
|
+
}
|
|
262
|
+
setNthWeekdayOfMonth(e, t) {
|
|
263
|
+
if (typeof t != "number" && t.toUpperCase() === "L") this.dayOfWeek[e] = this.dayOfWeek[e] | 32;
|
|
264
|
+
else if (t === 63) this.dayOfWeek[e] = 63;
|
|
265
|
+
else if (t < 6 && t > 0) this.dayOfWeek[e] = this.dayOfWeek[e] | O[t - 1];
|
|
266
|
+
else throw new TypeError(`CronPattern: nth weekday out of range, should be 1-5 or L. Value: ${t}, Type: ${typeof t}`);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
var P = [
|
|
270
|
+
31,
|
|
271
|
+
28,
|
|
272
|
+
31,
|
|
273
|
+
30,
|
|
274
|
+
31,
|
|
275
|
+
30,
|
|
276
|
+
31,
|
|
277
|
+
31,
|
|
278
|
+
30,
|
|
279
|
+
31,
|
|
280
|
+
30,
|
|
281
|
+
31
|
|
282
|
+
];
|
|
283
|
+
var f = [
|
|
284
|
+
[
|
|
285
|
+
"month",
|
|
286
|
+
"year",
|
|
287
|
+
0
|
|
288
|
+
],
|
|
289
|
+
[
|
|
290
|
+
"day",
|
|
291
|
+
"month",
|
|
292
|
+
-1
|
|
293
|
+
],
|
|
294
|
+
[
|
|
295
|
+
"hour",
|
|
296
|
+
"day",
|
|
297
|
+
0
|
|
298
|
+
],
|
|
299
|
+
[
|
|
300
|
+
"minute",
|
|
301
|
+
"hour",
|
|
302
|
+
0
|
|
303
|
+
],
|
|
304
|
+
[
|
|
305
|
+
"second",
|
|
306
|
+
"minute",
|
|
307
|
+
0
|
|
308
|
+
]
|
|
309
|
+
];
|
|
310
|
+
var m = class s {
|
|
311
|
+
tz;
|
|
312
|
+
ms;
|
|
313
|
+
second;
|
|
314
|
+
minute;
|
|
315
|
+
hour;
|
|
316
|
+
day;
|
|
317
|
+
month;
|
|
318
|
+
year;
|
|
319
|
+
constructor(e, t) {
|
|
320
|
+
if (this.tz = t, e && e instanceof Date) if (!isNaN(e)) this.fromDate(e);
|
|
321
|
+
else throw new TypeError("CronDate: Invalid date passed to CronDate constructor");
|
|
322
|
+
else if (e == null) this.fromDate(/* @__PURE__ */ new Date());
|
|
323
|
+
else if (e && typeof e == "string") this.fromString(e);
|
|
324
|
+
else if (e instanceof s) this.fromCronDate(e);
|
|
325
|
+
else throw new TypeError("CronDate: Invalid type (" + typeof e + ") passed to CronDate constructor");
|
|
326
|
+
}
|
|
327
|
+
getLastDayOfMonth(e, t) {
|
|
328
|
+
return t !== 1 ? P[t] : new Date(Date.UTC(e, t + 1, 0)).getUTCDate();
|
|
329
|
+
}
|
|
330
|
+
getLastWeekday(e, t) {
|
|
331
|
+
let r = this.getLastDayOfMonth(e, t), i = new Date(Date.UTC(e, t, r)).getUTCDay();
|
|
332
|
+
return i === 0 ? r - 2 : i === 6 ? r - 1 : r;
|
|
333
|
+
}
|
|
334
|
+
getNearestWeekday(e, t, r) {
|
|
335
|
+
let n = this.getLastDayOfMonth(e, t);
|
|
336
|
+
if (r > n) return -1;
|
|
337
|
+
let a = new Date(Date.UTC(e, t, r)).getUTCDay();
|
|
338
|
+
return a === 0 ? r === n ? r - 2 : r + 1 : a === 6 ? r === 1 ? r + 2 : r - 1 : r;
|
|
339
|
+
}
|
|
340
|
+
isNthWeekdayOfMonth(e, t, r, n) {
|
|
341
|
+
let a = new Date(Date.UTC(e, t, r)).getUTCDay(), o = 0;
|
|
342
|
+
for (let h = 1; h <= r; h++) new Date(Date.UTC(e, t, h)).getUTCDay() === a && o++;
|
|
343
|
+
if (n & 63 && O[o - 1] & n) return !0;
|
|
344
|
+
if (n & 32) {
|
|
345
|
+
let h = this.getLastDayOfMonth(e, t);
|
|
346
|
+
for (let l = r + 1; l <= h; l++) if (new Date(Date.UTC(e, t, l)).getUTCDay() === a) return !1;
|
|
347
|
+
return !0;
|
|
348
|
+
}
|
|
349
|
+
return !1;
|
|
350
|
+
}
|
|
351
|
+
fromDate(e) {
|
|
352
|
+
if (this.tz !== void 0) if (typeof this.tz == "number") this.ms = e.getUTCMilliseconds(), this.second = e.getUTCSeconds(), this.minute = e.getUTCMinutes() + this.tz, this.hour = e.getUTCHours(), this.day = e.getUTCDate(), this.month = e.getUTCMonth(), this.year = e.getUTCFullYear(), this.apply();
|
|
353
|
+
else try {
|
|
354
|
+
let t = g(e, this.tz);
|
|
355
|
+
this.ms = e.getMilliseconds(), this.second = t.s, this.minute = t.i, this.hour = t.h, this.day = t.d, this.month = t.m - 1, this.year = t.y;
|
|
356
|
+
} catch (t) {
|
|
357
|
+
let r = t instanceof Error ? t.message : String(t);
|
|
358
|
+
throw new TypeError(`CronDate: Failed to convert date to timezone '${this.tz}'. This may happen with invalid timezone names or dates. Original error: ${r}`);
|
|
359
|
+
}
|
|
360
|
+
else this.ms = e.getMilliseconds(), this.second = e.getSeconds(), this.minute = e.getMinutes(), this.hour = e.getHours(), this.day = e.getDate(), this.month = e.getMonth(), this.year = e.getFullYear();
|
|
361
|
+
}
|
|
362
|
+
fromCronDate(e) {
|
|
363
|
+
this.tz = e.tz, this.year = e.year, this.month = e.month, this.day = e.day, this.hour = e.hour, this.minute = e.minute, this.second = e.second, this.ms = e.ms;
|
|
364
|
+
}
|
|
365
|
+
apply() {
|
|
366
|
+
if (this.month > 11 || this.month < 0 || this.day > P[this.month] || this.day < 1 || this.hour > 59 || this.minute > 59 || this.second > 59 || this.hour < 0 || this.minute < 0 || this.second < 0) {
|
|
367
|
+
let e = new Date(Date.UTC(this.year, this.month, this.day, this.hour, this.minute, this.second, this.ms));
|
|
368
|
+
return this.ms = e.getUTCMilliseconds(), this.second = e.getUTCSeconds(), this.minute = e.getUTCMinutes(), this.hour = e.getUTCHours(), this.day = e.getUTCDate(), this.month = e.getUTCMonth(), this.year = e.getUTCFullYear(), !0;
|
|
369
|
+
} else return !1;
|
|
370
|
+
}
|
|
371
|
+
fromString(e) {
|
|
372
|
+
if (typeof this.tz == "number") {
|
|
373
|
+
let t = v(e);
|
|
374
|
+
this.ms = t.getUTCMilliseconds(), this.second = t.getUTCSeconds(), this.minute = t.getUTCMinutes(), this.hour = t.getUTCHours(), this.day = t.getUTCDate(), this.month = t.getUTCMonth(), this.year = t.getUTCFullYear(), this.apply();
|
|
375
|
+
} else return this.fromDate(v(e, this.tz));
|
|
376
|
+
}
|
|
377
|
+
findNext(e, t, r, n) {
|
|
378
|
+
return this._findMatch(e, t, r, n, 1);
|
|
379
|
+
}
|
|
380
|
+
_findMatch(e, t, r, n, i) {
|
|
381
|
+
let a = this[t], o;
|
|
382
|
+
r.lastDayOfMonth && (o = this.getLastDayOfMonth(this.year, this.month));
|
|
383
|
+
let h = !r.starDOW && t == "day" ? new Date(Date.UTC(this.year, this.month, 1, 0, 0, 0, 0)).getUTCDay() : void 0, l = this[t] + n, y = i === 1 ? (u) => u < r[t].length : (u) => u >= 0;
|
|
384
|
+
for (let u = l; y(u); u += i) {
|
|
385
|
+
let d = r[t][u];
|
|
386
|
+
if (t === "day" && !d) {
|
|
387
|
+
for (let c = 0; c < r.nearestWeekdays.length; c++) if (r.nearestWeekdays[c]) {
|
|
388
|
+
let M = this.getNearestWeekday(this.year, this.month, c - n);
|
|
389
|
+
if (M === -1) continue;
|
|
390
|
+
if (M === u - n) {
|
|
391
|
+
d = 1;
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (t === "day" && r.lastWeekday) {
|
|
397
|
+
let c = this.getLastWeekday(this.year, this.month);
|
|
398
|
+
u - n === c && (d = 1);
|
|
399
|
+
}
|
|
400
|
+
if (t === "day" && r.lastDayOfMonth && u - n == o && (d = 1), t === "day" && !r.starDOW) {
|
|
401
|
+
let c = r.dayOfWeek[(h + (u - n - 1)) % 7];
|
|
402
|
+
if (c && c & 63) c = this.isNthWeekdayOfMonth(this.year, this.month, u - n, c) ? 1 : 0;
|
|
403
|
+
else if (c) throw new Error(`CronDate: Invalid value for dayOfWeek encountered. ${c}`);
|
|
404
|
+
r.useAndLogic ? d = d && c : !e.domAndDow && !r.starDOM ? d = d || c : d = d && c;
|
|
405
|
+
}
|
|
406
|
+
if (d) return this[t] = u - n, a !== this[t] ? 2 : 1;
|
|
407
|
+
}
|
|
408
|
+
return 3;
|
|
409
|
+
}
|
|
410
|
+
recurse(e, t, r) {
|
|
411
|
+
if (r === 0 && !e.starYear) {
|
|
412
|
+
if (this.year >= 0 && this.year < e.year.length && e.year[this.year] === 0) {
|
|
413
|
+
let i = -1;
|
|
414
|
+
for (let a = this.year + 1; a < e.year.length && a < 1e4; a++) if (e.year[a] === 1) {
|
|
415
|
+
i = a;
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
if (i === -1) return null;
|
|
419
|
+
this.year = i, this.month = 0, this.day = 1, this.hour = 0, this.minute = 0, this.second = 0, this.ms = 0;
|
|
420
|
+
}
|
|
421
|
+
if (this.year >= 1e4) return null;
|
|
422
|
+
}
|
|
423
|
+
let n = this.findNext(t, f[r][0], e, f[r][2]);
|
|
424
|
+
if (n > 1) {
|
|
425
|
+
let i = r + 1;
|
|
426
|
+
for (; i < f.length;) this[f[i][0]] = -f[i][2], i++;
|
|
427
|
+
if (n === 3) {
|
|
428
|
+
if (this[f[r][1]]++, this[f[r][0]] = -f[r][2], this.apply(), r === 0 && !e.starYear) {
|
|
429
|
+
for (; this.year >= 0 && this.year < e.year.length && e.year[this.year] === 0 && this.year < 1e4;) this.year++;
|
|
430
|
+
if (this.year >= 1e4 || this.year >= e.year.length) return null;
|
|
431
|
+
}
|
|
432
|
+
return this.recurse(e, t, 0);
|
|
433
|
+
} else if (this.apply()) return this.recurse(e, t, r - 1);
|
|
434
|
+
}
|
|
435
|
+
return r += 1, r >= f.length ? this : (e.starYear ? this.year >= 3e3 : this.year >= 1e4) ? null : this.recurse(e, t, r);
|
|
436
|
+
}
|
|
437
|
+
increment(e, t, r) {
|
|
438
|
+
return this.second += t.interval !== void 0 && t.interval > 1 && r ? t.interval : 1, this.ms = 0, this.apply(), this.recurse(e, t, 0);
|
|
439
|
+
}
|
|
440
|
+
decrement(e, t) {
|
|
441
|
+
return this.second -= t.interval !== void 0 && t.interval > 1 ? t.interval : 1, this.ms = 0, this.apply(), this.recurseBackward(e, t, 0, 0);
|
|
442
|
+
}
|
|
443
|
+
recurseBackward(e, t, r, n = 0) {
|
|
444
|
+
if (n > 1e4) return null;
|
|
445
|
+
if (r === 0 && !e.starYear) {
|
|
446
|
+
if (this.year >= 0 && this.year < e.year.length && e.year[this.year] === 0) {
|
|
447
|
+
let a = -1;
|
|
448
|
+
for (let o = this.year - 1; o >= 0; o--) if (e.year[o] === 1) {
|
|
449
|
+
a = o;
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
if (a === -1) return null;
|
|
453
|
+
this.year = a, this.month = 11, this.day = 31, this.hour = 23, this.minute = 59, this.second = 59, this.ms = 0;
|
|
454
|
+
}
|
|
455
|
+
if (this.year < 0) return null;
|
|
456
|
+
}
|
|
457
|
+
let i = this.findPrevious(t, f[r][0], e, f[r][2]);
|
|
458
|
+
if (i > 1) {
|
|
459
|
+
let a = r + 1;
|
|
460
|
+
for (; a < f.length;) {
|
|
461
|
+
let o = f[a][0], h = f[a][2], l = this.getMaxPatternValue(o, e, h);
|
|
462
|
+
this[o] = l, a++;
|
|
463
|
+
}
|
|
464
|
+
if (i === 3) {
|
|
465
|
+
if (this[f[r][1]]--, r === 0) {
|
|
466
|
+
let y = this.getLastDayOfMonth(this.year, this.month);
|
|
467
|
+
this.day > y && (this.day = y);
|
|
468
|
+
}
|
|
469
|
+
if (r === 1) if (this.day <= 0) this.day = 1;
|
|
470
|
+
else {
|
|
471
|
+
let y = this.year, u = this.month;
|
|
472
|
+
for (; u < 0;) u += 12, y--;
|
|
473
|
+
for (; u > 11;) u -= 12, y++;
|
|
474
|
+
let d = u !== 1 ? P[u] : new Date(Date.UTC(y, u + 1, 0)).getUTCDate();
|
|
475
|
+
this.day > d && (this.day = d);
|
|
476
|
+
}
|
|
477
|
+
this.apply();
|
|
478
|
+
let o = f[r][0], h = f[r][2], l = this.getMaxPatternValue(o, e, h);
|
|
479
|
+
if (o === "day") {
|
|
480
|
+
let y = this.getLastDayOfMonth(this.year, this.month);
|
|
481
|
+
this[o] = Math.min(l, y);
|
|
482
|
+
} else this[o] = l;
|
|
483
|
+
if (this.apply(), r === 0) {
|
|
484
|
+
let y = f[1][2], u = this.getMaxPatternValue("day", e, y), d = this.getLastDayOfMonth(this.year, this.month), c = Math.min(u, d);
|
|
485
|
+
c !== this.day && (this.day = c, this.hour = this.getMaxPatternValue("hour", e, f[2][2]), this.minute = this.getMaxPatternValue("minute", e, f[3][2]), this.second = this.getMaxPatternValue("second", e, f[4][2]));
|
|
486
|
+
}
|
|
487
|
+
if (r === 0 && !e.starYear) {
|
|
488
|
+
for (; this.year >= 0 && this.year < e.year.length && e.year[this.year] === 0;) this.year--;
|
|
489
|
+
if (this.year < 0) return null;
|
|
490
|
+
}
|
|
491
|
+
return this.recurseBackward(e, t, 0, n + 1);
|
|
492
|
+
} else if (this.apply()) return this.recurseBackward(e, t, r - 1, n + 1);
|
|
493
|
+
}
|
|
494
|
+
return r += 1, r >= f.length ? this : this.year < 0 ? null : this.recurseBackward(e, t, r, n + 1);
|
|
495
|
+
}
|
|
496
|
+
getMaxPatternValue(e, t, r) {
|
|
497
|
+
if (e === "day" && t.lastDayOfMonth) return this.getLastDayOfMonth(this.year, this.month);
|
|
498
|
+
if (e === "day" && !t.starDOW) return this.getLastDayOfMonth(this.year, this.month);
|
|
499
|
+
for (let n = t[e].length - 1; n >= 0; n--) if (t[e][n]) return n - r;
|
|
500
|
+
return t[e].length - 1 - r;
|
|
501
|
+
}
|
|
502
|
+
findPrevious(e, t, r, n) {
|
|
503
|
+
return this._findMatch(e, t, r, n, -1);
|
|
504
|
+
}
|
|
505
|
+
getDate(e) {
|
|
506
|
+
return e || this.tz === void 0 ? new Date(this.year, this.month, this.day, this.hour, this.minute, this.second, this.ms) : typeof this.tz == "number" ? new Date(Date.UTC(this.year, this.month, this.day, this.hour, this.minute - this.tz, this.second, this.ms)) : k(b(this.year, this.month + 1, this.day, this.hour, this.minute, this.second, this.tz), !1);
|
|
507
|
+
}
|
|
508
|
+
getTime() {
|
|
509
|
+
return this.getDate(!1).getTime();
|
|
510
|
+
}
|
|
511
|
+
match(e, t) {
|
|
512
|
+
if (!e.starYear && (this.year < 0 || this.year >= e.year.length || e.year[this.year] === 0)) return !1;
|
|
513
|
+
for (let r = 0; r < f.length; r++) {
|
|
514
|
+
let n = f[r][0], i = f[r][2], a = this[n];
|
|
515
|
+
if (a + i < 0 || a + i >= e[n].length) return !1;
|
|
516
|
+
let o = e[n][a + i];
|
|
517
|
+
if (n === "day") {
|
|
518
|
+
if (!o) {
|
|
519
|
+
for (let h = 0; h < e.nearestWeekdays.length; h++) if (e.nearestWeekdays[h]) {
|
|
520
|
+
let l = this.getNearestWeekday(this.year, this.month, h - i);
|
|
521
|
+
if (l !== -1 && l === a) {
|
|
522
|
+
o = 1;
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (e.lastWeekday) a === this.getLastWeekday(this.year, this.month) && (o = 1);
|
|
528
|
+
if (e.lastDayOfMonth) a === this.getLastDayOfMonth(this.year, this.month) && (o = 1);
|
|
529
|
+
if (!e.starDOW) {
|
|
530
|
+
let h = new Date(Date.UTC(this.year, this.month, 1, 0, 0, 0, 0)).getUTCDay(), l = e.dayOfWeek[(h + (a - 1)) % 7];
|
|
531
|
+
l && l & 63 && (l = this.isNthWeekdayOfMonth(this.year, this.month, a, l) ? 1 : 0), e.useAndLogic ? o = o && l : !t.domAndDow && !e.starDOM ? o = o || l : o = o && l;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (!o) return !1;
|
|
535
|
+
}
|
|
536
|
+
return !0;
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
function R(s) {
|
|
540
|
+
if (s === void 0 && (s = {}), delete s.name, s.legacyMode !== void 0 && s.domAndDow === void 0 ? s.domAndDow = !s.legacyMode : s.domAndDow === void 0 && (s.domAndDow = !1), s.legacyMode = !s.domAndDow, s.paused = s.paused === void 0 ? !1 : s.paused, s.maxRuns = s.maxRuns === void 0 ? 1 / 0 : s.maxRuns, s.catch = s.catch === void 0 ? !1 : s.catch, s.interval = s.interval === void 0 ? 0 : parseInt(s.interval.toString(), 10), s.utcOffset = s.utcOffset === void 0 ? void 0 : parseInt(s.utcOffset.toString(), 10), s.dayOffset = s.dayOffset === void 0 ? 0 : parseInt(s.dayOffset.toString(), 10), s.unref = s.unref === void 0 ? !1 : s.unref, s.mode = s.mode === void 0 ? "auto" : s.mode, s.alternativeWeekdays = s.alternativeWeekdays === void 0 ? !1 : s.alternativeWeekdays, s.sloppyRanges = s.sloppyRanges === void 0 ? !1 : s.sloppyRanges, ![
|
|
541
|
+
"auto",
|
|
542
|
+
"5-part",
|
|
543
|
+
"6-part",
|
|
544
|
+
"7-part",
|
|
545
|
+
"5-or-6-parts",
|
|
546
|
+
"6-or-7-parts"
|
|
547
|
+
].includes(s.mode)) throw new Error("CronOptions: mode must be one of 'auto', '5-part', '6-part', '7-part', '5-or-6-parts', or '6-or-7-parts'.");
|
|
548
|
+
if (s.startAt && (s.startAt = new m(s.startAt, s.timezone)), s.stopAt && (s.stopAt = new m(s.stopAt, s.timezone)), s.interval !== null) {
|
|
549
|
+
if (isNaN(s.interval)) throw new Error("CronOptions: Supplied value for interval is not a number");
|
|
550
|
+
if (s.interval < 0) throw new Error("CronOptions: Supplied value for interval can not be negative");
|
|
551
|
+
}
|
|
552
|
+
if (s.utcOffset !== void 0) {
|
|
553
|
+
if (isNaN(s.utcOffset)) throw new Error("CronOptions: Invalid value passed for utcOffset, should be number representing minutes offset from UTC.");
|
|
554
|
+
if (s.utcOffset < -870 || s.utcOffset > 870) throw new Error("CronOptions: utcOffset out of bounds.");
|
|
555
|
+
if (s.utcOffset !== void 0 && s.timezone) throw new Error("CronOptions: Combining 'utcOffset' with 'timezone' is not allowed.");
|
|
556
|
+
}
|
|
557
|
+
if (s.unref !== !0 && s.unref !== !1) throw new Error("CronOptions: Unref should be either true, false or undefined(false).");
|
|
558
|
+
if (s.dayOffset !== void 0 && s.dayOffset !== 0 && isNaN(s.dayOffset)) throw new Error("CronOptions: Invalid value passed for dayOffset, should be a number representing days to offset.");
|
|
559
|
+
return s;
|
|
560
|
+
}
|
|
561
|
+
function p(s) {
|
|
562
|
+
return Object.prototype.toString.call(s) === "[object Function]" || typeof s == "function" || s instanceof Function;
|
|
563
|
+
}
|
|
564
|
+
function _(s) {
|
|
565
|
+
return p(s);
|
|
566
|
+
}
|
|
567
|
+
function x(s) {
|
|
568
|
+
typeof Deno < "u" && typeof Deno.unrefTimer < "u" ? Deno.unrefTimer(s) : s && typeof s.unref < "u" && s.unref();
|
|
569
|
+
}
|
|
570
|
+
var W = 3e4;
|
|
571
|
+
var w = [];
|
|
572
|
+
var E = class {
|
|
573
|
+
name;
|
|
574
|
+
options;
|
|
575
|
+
_states;
|
|
576
|
+
fn;
|
|
577
|
+
getTz() {
|
|
578
|
+
return this.options.timezone || this.options.utcOffset;
|
|
579
|
+
}
|
|
580
|
+
applyDayOffset(e) {
|
|
581
|
+
if (this.options.dayOffset !== void 0 && this.options.dayOffset !== 0) {
|
|
582
|
+
let t = this.options.dayOffset * 24 * 60 * 60 * 1e3;
|
|
583
|
+
return new Date(e.getTime() + t);
|
|
584
|
+
}
|
|
585
|
+
return e;
|
|
586
|
+
}
|
|
587
|
+
constructor(e, t, r) {
|
|
588
|
+
let n, i;
|
|
589
|
+
if (p(t)) i = t;
|
|
590
|
+
else if (typeof t == "object") n = t;
|
|
591
|
+
else if (t !== void 0) throw new Error("Cron: Invalid argument passed for optionsIn. Should be one of function, or object (options).");
|
|
592
|
+
if (p(r)) i = r;
|
|
593
|
+
else if (typeof r == "object") n = r;
|
|
594
|
+
else if (r !== void 0) throw new Error("Cron: Invalid argument passed for funcIn. Should be one of function, or object (options).");
|
|
595
|
+
if (this.name = n?.name, this.options = R(n), this._states = {
|
|
596
|
+
kill: !1,
|
|
597
|
+
blocking: !1,
|
|
598
|
+
previousRun: void 0,
|
|
599
|
+
currentRun: void 0,
|
|
600
|
+
once: void 0,
|
|
601
|
+
currentTimeout: void 0,
|
|
602
|
+
maxRuns: n ? n.maxRuns : void 0,
|
|
603
|
+
paused: n ? n.paused : !1,
|
|
604
|
+
pattern: new C("* * * * *", void 0, { mode: "auto" })
|
|
605
|
+
}, e && (e instanceof Date || typeof e == "string" && e.indexOf(":") > 0) ? this._states.once = new m(e, this.getTz()) : this._states.pattern = new C(e, this.options.timezone, {
|
|
606
|
+
mode: this.options.mode,
|
|
607
|
+
alternativeWeekdays: this.options.alternativeWeekdays,
|
|
608
|
+
sloppyRanges: this.options.sloppyRanges
|
|
609
|
+
}), this.name) {
|
|
610
|
+
if (w.find((o) => o.name === this.name)) throw new Error("Cron: Tried to initialize new named job '" + this.name + "', but name already taken.");
|
|
611
|
+
w.push(this);
|
|
612
|
+
}
|
|
613
|
+
return i !== void 0 && _(i) && (this.fn = i, this.schedule()), this;
|
|
614
|
+
}
|
|
615
|
+
nextRun(e) {
|
|
616
|
+
let t = this._next(e);
|
|
617
|
+
return t ? this.applyDayOffset(t.getDate(!1)) : null;
|
|
618
|
+
}
|
|
619
|
+
nextRuns(e, t) {
|
|
620
|
+
this._states.maxRuns !== void 0 && e > this._states.maxRuns && (e = this._states.maxRuns);
|
|
621
|
+
let r = t || this._states.currentRun || void 0;
|
|
622
|
+
return this._enumerateRuns(e, r, "next");
|
|
623
|
+
}
|
|
624
|
+
previousRuns(e, t) {
|
|
625
|
+
return this._enumerateRuns(e, t || void 0, "previous");
|
|
626
|
+
}
|
|
627
|
+
_enumerateRuns(e, t, r) {
|
|
628
|
+
let n = [], i = t ? new m(t, this.getTz()) : null, a = r === "next" ? this._next : this._previous;
|
|
629
|
+
for (; e--;) {
|
|
630
|
+
let o = a.call(this, i);
|
|
631
|
+
if (!o) break;
|
|
632
|
+
let h = o.getDate(!1);
|
|
633
|
+
n.push(this.applyDayOffset(h)), i = o;
|
|
634
|
+
}
|
|
635
|
+
return n;
|
|
636
|
+
}
|
|
637
|
+
match(e) {
|
|
638
|
+
if (this._states.once) {
|
|
639
|
+
let r = new m(e, this.getTz());
|
|
640
|
+
r.ms = 0;
|
|
641
|
+
let n = new m(this._states.once, this.getTz());
|
|
642
|
+
return n.ms = 0, r.getTime() === n.getTime();
|
|
643
|
+
}
|
|
644
|
+
let t = new m(e, this.getTz());
|
|
645
|
+
return t.ms = 0, t.match(this._states.pattern, this.options);
|
|
646
|
+
}
|
|
647
|
+
getPattern() {
|
|
648
|
+
if (!this._states.once) return this._states.pattern ? this._states.pattern.pattern : void 0;
|
|
649
|
+
}
|
|
650
|
+
getOnce() {
|
|
651
|
+
return this._states.once ? this._states.once.getDate() : null;
|
|
652
|
+
}
|
|
653
|
+
isRunning() {
|
|
654
|
+
let e = this.nextRun(this._states.currentRun), t = !this._states.paused, r = this.fn !== void 0, n = !this._states.kill;
|
|
655
|
+
return t && r && n && e !== null;
|
|
656
|
+
}
|
|
657
|
+
isStopped() {
|
|
658
|
+
return this._states.kill;
|
|
659
|
+
}
|
|
660
|
+
isBusy() {
|
|
661
|
+
return this._states.blocking;
|
|
662
|
+
}
|
|
663
|
+
currentRun() {
|
|
664
|
+
return this._states.currentRun ? this._states.currentRun.getDate() : null;
|
|
665
|
+
}
|
|
666
|
+
previousRun() {
|
|
667
|
+
return this._states.previousRun ? this._states.previousRun.getDate() : null;
|
|
668
|
+
}
|
|
669
|
+
msToNext(e) {
|
|
670
|
+
let t = this._next(e);
|
|
671
|
+
return t ? e instanceof m || e instanceof Date ? t.getTime() - e.getTime() : t.getTime() - new m(e).getTime() : null;
|
|
672
|
+
}
|
|
673
|
+
stop() {
|
|
674
|
+
this._states.kill = !0, this._states.currentTimeout && clearTimeout(this._states.currentTimeout);
|
|
675
|
+
let e = w.indexOf(this);
|
|
676
|
+
e >= 0 && w.splice(e, 1);
|
|
677
|
+
}
|
|
678
|
+
pause() {
|
|
679
|
+
return this._states.paused = !0, !this._states.kill;
|
|
680
|
+
}
|
|
681
|
+
resume() {
|
|
682
|
+
return this._states.paused = !1, !this._states.kill;
|
|
683
|
+
}
|
|
684
|
+
schedule(e) {
|
|
685
|
+
if (e && this.fn) throw new Error("Cron: It is not allowed to schedule two functions using the same Croner instance.");
|
|
686
|
+
e && (this.fn = e);
|
|
687
|
+
let t = this.msToNext(), r = this.nextRun(this._states.currentRun);
|
|
688
|
+
return t == null || isNaN(t) || r === null ? this : (t > W && (t = W), this._states.currentTimeout = setTimeout(() => this._checkTrigger(r), t), this._states.currentTimeout && this.options.unref && x(this._states.currentTimeout), this);
|
|
689
|
+
}
|
|
690
|
+
async _trigger(e) {
|
|
691
|
+
this._states.blocking = !0, this._states.currentRun = new m(void 0, this.getTz());
|
|
692
|
+
try {
|
|
693
|
+
if (this.options.catch) try {
|
|
694
|
+
this.fn !== void 0 && await this.fn(this, this.options.context);
|
|
695
|
+
} catch (t) {
|
|
696
|
+
if (p(this.options.catch)) try {
|
|
697
|
+
this.options.catch(t, this);
|
|
698
|
+
} catch {}
|
|
699
|
+
}
|
|
700
|
+
else this.fn !== void 0 && await this.fn(this, this.options.context);
|
|
701
|
+
} finally {
|
|
702
|
+
this._states.previousRun = new m(e, this.getTz()), this._states.blocking = !1;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
async trigger() {
|
|
706
|
+
await this._trigger();
|
|
707
|
+
}
|
|
708
|
+
runsLeft() {
|
|
709
|
+
return this._states.maxRuns;
|
|
710
|
+
}
|
|
711
|
+
_checkTrigger(e) {
|
|
712
|
+
let t = /* @__PURE__ */ new Date(), r = !this._states.paused && t.getTime() >= e.getTime(), n = this._states.blocking && this.options.protect;
|
|
713
|
+
r && !n ? (this._states.maxRuns !== void 0 && this._states.maxRuns--, this._trigger()) : r && n && p(this.options.protect) && setTimeout(() => this.options.protect(this), 0), this.schedule();
|
|
714
|
+
}
|
|
715
|
+
_next(e) {
|
|
716
|
+
let t = !!(e || this._states.currentRun), r = !1;
|
|
717
|
+
!e && this.options.startAt && this.options.interval && ([e, t] = this._calculatePreviousRun(e, t), r = !e), e = new m(e, this.getTz()), this.options.startAt && e && e.getTime() < this.options.startAt.getTime() && (e = this.options.startAt);
|
|
718
|
+
let n = this._states.once || new m(e, this.getTz());
|
|
719
|
+
return !r && n !== this._states.once && (n = n.increment(this._states.pattern, this.options, t)), this._states.once && this._states.once.getTime() <= e.getTime() || n === null || this._states.maxRuns !== void 0 && this._states.maxRuns <= 0 || this._states.kill || this.options.stopAt && n.getTime() >= this.options.stopAt.getTime() ? null : n;
|
|
720
|
+
}
|
|
721
|
+
_previous(e) {
|
|
722
|
+
let t = new m(e, this.getTz());
|
|
723
|
+
this.options.stopAt && t.getTime() > this.options.stopAt.getTime() && (t = this.options.stopAt);
|
|
724
|
+
let r = new m(t, this.getTz());
|
|
725
|
+
return this._states.once ? this._states.once.getTime() < t.getTime() ? this._states.once : null : (r = r.decrement(this._states.pattern, this.options), r === null || this.options.startAt && r.getTime() < this.options.startAt.getTime() ? null : r);
|
|
726
|
+
}
|
|
727
|
+
_calculatePreviousRun(e, t) {
|
|
728
|
+
let r = new m(void 0, this.getTz()), n = e;
|
|
729
|
+
if (this.options.startAt.getTime() <= r.getTime()) {
|
|
730
|
+
n = this.options.startAt;
|
|
731
|
+
let i = n.getTime() + this.options.interval * 1e3;
|
|
732
|
+
for (; i <= r.getTime();) n = new m(n, this.getTz()).increment(this._states.pattern, this.options, !0), i = n.getTime() + this.options.interval * 1e3;
|
|
733
|
+
t = !0;
|
|
734
|
+
}
|
|
735
|
+
return n === null && (n = void 0), [n, t];
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
//#endregion
|
|
739
|
+
//#region src/schedule.ts
|
|
740
|
+
/** 下一次 due(epoch ms);null = 沉寂(once 已过/表达式非法)。fromMs 语义:从该时刻起算下一次。 */
|
|
741
|
+
function nextDueAfter(schedule, fromMs) {
|
|
742
|
+
if ("everyMinutes" in schedule) return fromMs + schedule.everyMinutes * 6e4;
|
|
743
|
+
if ("once" in schedule) {
|
|
744
|
+
const t = Date.parse(schedule.once);
|
|
745
|
+
return Number.isNaN(t) ? null : t > fromMs ? t : null;
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
const next = new E(schedule.cron, {
|
|
749
|
+
timezone: schedule.timezone,
|
|
750
|
+
paused: true
|
|
751
|
+
}).nextRun(new Date(fromMs));
|
|
752
|
+
return next === null ? null : next.getTime();
|
|
753
|
+
} catch {
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
//#endregion
|
|
758
|
+
//#region src/jobs.ts
|
|
759
|
+
const BRIEFING_TEMPLATE_DEFAULT = [
|
|
760
|
+
"今天是 {{date}}。以下是你最近的记忆摘要:",
|
|
761
|
+
"{{recentMemories}}",
|
|
762
|
+
"",
|
|
763
|
+
"请给主人写今天的晨报:两三句自然的话,自然带出记忆里值得提起的事,口吻是你自己。",
|
|
764
|
+
"不要使用任何工具,直接写晨报正文。"
|
|
765
|
+
].join("\n");
|
|
766
|
+
function compileJobs(jobs, warn) {
|
|
767
|
+
const out = [];
|
|
768
|
+
const seen = /* @__PURE__ */ new Set();
|
|
769
|
+
for (const decl of jobs ?? []) {
|
|
770
|
+
const jobId = `${decl.maidId}:${decl.name ?? "default"}`;
|
|
771
|
+
if (seen.has(jobId)) {
|
|
772
|
+
warn(`maid-cron: duplicate jobId '${jobId}', skipped`);
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
if ("everyMinutes" in decl.schedule && decl.schedule.everyMinutes <= 0) {
|
|
776
|
+
warn(`maid-cron: '${jobId}' everyMinutes must be > 0, skipped`);
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
if (nextDueAfter(decl.schedule, 0) === null && !("once" in decl.schedule)) {
|
|
780
|
+
warn(`maid-cron: '${jobId}' schedule unusable, skipped`);
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
if (decl.skipOnEmptyScriptOutput === true && decl.script === void 0) {
|
|
784
|
+
warn(`maid-cron: '${jobId}' skipOnEmptyScriptOutput requires script, skipped`);
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
if (decl.persistTo !== void 0 && (typeof decl.persistTo !== "string" || decl.persistTo.length === 0)) {
|
|
788
|
+
warn(`maid-cron: '${jobId}' persistTo must be a non-empty string, skipped`);
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
seen.add(jobId);
|
|
792
|
+
const kind = decl.script !== void 0 && decl.promptTemplate === void 0 ? "script" : "llm";
|
|
793
|
+
out.push({
|
|
794
|
+
jobId,
|
|
795
|
+
type: decl.type ?? "briefing",
|
|
796
|
+
maidId: decl.maidId,
|
|
797
|
+
enabled: decl.enabled !== false,
|
|
798
|
+
kind,
|
|
799
|
+
promptTemplate: decl.promptTemplate ?? "",
|
|
800
|
+
script: decl.script,
|
|
801
|
+
deliverLocalDir: decl.deliverLocalDir,
|
|
802
|
+
persistTo: decl.persistTo,
|
|
803
|
+
recentLimit: decl.recentLimit,
|
|
804
|
+
skipOnEmptyScriptOutput: decl.skipOnEmptyScriptOutput === true,
|
|
805
|
+
target: decl.target ?? null,
|
|
806
|
+
genTimeoutMs: decl.genTimeoutMs ?? 18e4,
|
|
807
|
+
schedule: decl.schedule
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
return out;
|
|
811
|
+
}
|
|
812
|
+
function renderRecentMemories(recents, maxChars) {
|
|
813
|
+
const text = recents.map((r) => `- [${r.createdAt?.slice(0, 10) ?? "日期未知"}] ${r.title}:${r.content}`).join("\n");
|
|
814
|
+
return text.length > maxChars ? text.slice(0, maxChars) : text;
|
|
815
|
+
}
|
|
816
|
+
function renderBriefingPrompt(template, vars) {
|
|
817
|
+
return (template || BRIEFING_TEMPLATE_DEFAULT).replaceAll("{{date}}", vars.date).replaceAll("{{recentMemories}}", vars.recentMemories).replaceAll("{{scriptOutput}}", vars.scriptOutput ?? "").replaceAll("{{nightContext}}", vars.nightContext ?? "").replaceAll("{{overnightSentence}}", vars.overnightSentence ?? "");
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* job 时区的日期渲染(zh-CN 长格式);cron 型 schedule 的 timezone 由 plugin 传入。
|
|
821
|
+
* 注意:真实 ICU 输出中数字与「星期X」之间不带空格(如「2026年9月2日星期三」),
|
|
822
|
+
* 且具体形态随 ICU 构建而异——测试绝不 pin 此字面量。
|
|
823
|
+
*/
|
|
824
|
+
function formatDateZh(ms, timezone) {
|
|
825
|
+
return new Intl.DateTimeFormat("zh-CN", {
|
|
826
|
+
timeZone: timezone,
|
|
827
|
+
year: "numeric",
|
|
828
|
+
month: "long",
|
|
829
|
+
day: "numeric",
|
|
830
|
+
weekday: "long"
|
|
831
|
+
}).format(new Date(ms));
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* job 时区的 ISO 日期(YYYY-MM-DD;Task 12):en-CA locale 恒产 ISO 形态(同 maid-emotion
|
|
835
|
+
* dateKey 惯例)——persistTo 路径的 `{{date}}` 替换源,与 maid-voice 读取端 overnight-<date>.txt
|
|
836
|
+
* 档名同形同源。
|
|
837
|
+
*/
|
|
838
|
+
function isoDateOf(ms, timezone) {
|
|
839
|
+
return new Intl.DateTimeFormat("en-CA", {
|
|
840
|
+
timeZone: timezone,
|
|
841
|
+
year: "numeric",
|
|
842
|
+
month: "2-digit",
|
|
843
|
+
day: "2-digit"
|
|
844
|
+
}).format(new Date(ms));
|
|
845
|
+
}
|
|
846
|
+
//#endregion
|
|
847
|
+
//#region src/state.ts
|
|
848
|
+
function freshRunState() {
|
|
849
|
+
return {
|
|
850
|
+
lastRunAt: void 0,
|
|
851
|
+
lastStatus: void 0,
|
|
852
|
+
lastError: void 0,
|
|
853
|
+
misfireCount: 0,
|
|
854
|
+
nextDueAt: void 0
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
function loadState(file) {
|
|
858
|
+
if (!existsSync(file)) return {};
|
|
859
|
+
try {
|
|
860
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
861
|
+
} catch {
|
|
862
|
+
return {};
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
function saveState(file, map) {
|
|
866
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
867
|
+
const tmp = `${file}.tmp`;
|
|
868
|
+
writeFileSync(tmp, JSON.stringify(map, null, 2));
|
|
869
|
+
renameSync(tmp, file);
|
|
870
|
+
}
|
|
871
|
+
//#endregion
|
|
872
|
+
//#region src/script-runner.ts
|
|
873
|
+
const KILL_GRACE_MS = 5e3;
|
|
874
|
+
/**
|
|
875
|
+
* 宿主子进程执行 cron script:不起 shell(无注入面,args 数组直通),stdout 全量捕获
|
|
876
|
+
* (投递正文;脚本均为宿主受控配置),stderr 只留尾部。超时先 SIGTERM,宽限 5s 后 SIGKILL。
|
|
877
|
+
*/
|
|
878
|
+
function runScript(spec) {
|
|
879
|
+
return new Promise((resolve) => {
|
|
880
|
+
const child = spawn(spec.command, spec.args ?? [], {
|
|
881
|
+
cwd: spec.cwd,
|
|
882
|
+
env: {
|
|
883
|
+
...process.env,
|
|
884
|
+
...spec.env
|
|
885
|
+
},
|
|
886
|
+
shell: false,
|
|
887
|
+
stdio: [
|
|
888
|
+
"ignore",
|
|
889
|
+
"pipe",
|
|
890
|
+
"pipe"
|
|
891
|
+
]
|
|
892
|
+
});
|
|
893
|
+
const stdoutChunks = [];
|
|
894
|
+
let stderr = "";
|
|
895
|
+
let timedOut = false;
|
|
896
|
+
let settled = false;
|
|
897
|
+
const timeoutMs = spec.timeoutMs ?? 12e4;
|
|
898
|
+
const timer = setTimeout(() => {
|
|
899
|
+
timedOut = true;
|
|
900
|
+
child.kill("SIGTERM");
|
|
901
|
+
setTimeout(() => {
|
|
902
|
+
if (!settled) child.kill("SIGKILL");
|
|
903
|
+
}, KILL_GRACE_MS).unref();
|
|
904
|
+
}, timeoutMs);
|
|
905
|
+
timer.unref();
|
|
906
|
+
child.stdout.on("data", (c) => stdoutChunks.push(c));
|
|
907
|
+
child.stderr.on("data", (c) => {
|
|
908
|
+
stderr = (stderr + c.toString("utf8")).slice(-4096);
|
|
909
|
+
});
|
|
910
|
+
child.on("error", (error) => {
|
|
911
|
+
if (settled) return;
|
|
912
|
+
settled = true;
|
|
913
|
+
clearTimeout(timer);
|
|
914
|
+
resolve({
|
|
915
|
+
ok: false,
|
|
916
|
+
reason: "spawn-error",
|
|
917
|
+
stderrTail: String(error)
|
|
918
|
+
});
|
|
919
|
+
});
|
|
920
|
+
child.on("close", (code) => {
|
|
921
|
+
if (settled) return;
|
|
922
|
+
settled = true;
|
|
923
|
+
clearTimeout(timer);
|
|
924
|
+
const stderrTail = stderr.slice(-2048);
|
|
925
|
+
if (timedOut) {
|
|
926
|
+
resolve({
|
|
927
|
+
ok: false,
|
|
928
|
+
reason: "timeout",
|
|
929
|
+
stderrTail
|
|
930
|
+
});
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
if (code !== 0) {
|
|
934
|
+
resolve({
|
|
935
|
+
ok: false,
|
|
936
|
+
reason: "exit",
|
|
937
|
+
exitCode: code,
|
|
938
|
+
stderrTail
|
|
939
|
+
});
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
resolve({
|
|
943
|
+
ok: true,
|
|
944
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf8")
|
|
945
|
+
});
|
|
946
|
+
});
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
//#endregion
|
|
950
|
+
//#region src/local-archive.ts
|
|
951
|
+
/** jobId/时间戳进文件名前的安全化:只留字母数字 . _ -,其余归 _。 */
|
|
952
|
+
function sanitizeFileName(s) {
|
|
953
|
+
return s.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
954
|
+
}
|
|
955
|
+
/** deliverLocalDir 落盘:<dir>/<jobId 安全化>/<runAt ISO 安全化>.md,tmp+rename 原子写(照 state.ts 先例)。 */
|
|
956
|
+
function writeLocalArchive(dir, jobId, runAtMs, text) {
|
|
957
|
+
const file = join(dir, sanitizeFileName(jobId), `${sanitizeFileName(new Date(runAtMs).toISOString())}.md`);
|
|
958
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
959
|
+
const tmp = `${file}.tmp`;
|
|
960
|
+
writeFileSync(tmp, text);
|
|
961
|
+
renameSync(tmp, file);
|
|
962
|
+
return file;
|
|
963
|
+
}
|
|
964
|
+
//#endregion
|
|
965
|
+
//#region src/engine.ts
|
|
966
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
967
|
+
/**
|
|
968
|
+
* persistTo 落盘(Task 12):固定路径,tmp+rename 原子写(照 local-archive.ts 惯例);
|
|
969
|
+
* 目录缺省即建(mkdirSync recursive)。tmp 落同目录——跨目录 rename 非原子,且 maid-voice
|
|
970
|
+
* 读取端档名正则不匹配 .tmp 残留(overnight.ts 合法档名约定)。
|
|
971
|
+
*/
|
|
972
|
+
function writePersistFile(path, text) {
|
|
973
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
974
|
+
const tmp = `${path}.tmp`;
|
|
975
|
+
writeFileSync(tmp, text);
|
|
976
|
+
renameSync(tmp, path);
|
|
977
|
+
}
|
|
978
|
+
function createCronEngine(config, jobs, ports) {
|
|
979
|
+
const states = loadState(config.stateFile);
|
|
980
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
981
|
+
const cancels = /* @__PURE__ */ new Map();
|
|
982
|
+
const persist = () => saveState(config.stateFile, states);
|
|
983
|
+
const stateOf = (jobId) => {
|
|
984
|
+
if (states[jobId] === void 0) states[jobId] = freshRunState();
|
|
985
|
+
return states[jobId];
|
|
986
|
+
};
|
|
987
|
+
for (const job of jobs) {
|
|
988
|
+
if (!job.enabled) continue;
|
|
989
|
+
const state = stateOf(job.jobId);
|
|
990
|
+
if (state.nextDueAt === void 0) {
|
|
991
|
+
const first = nextDueAfter(job.schedule, ports.now());
|
|
992
|
+
if (first !== null) {
|
|
993
|
+
state.nextDueAt = first;
|
|
994
|
+
persist();
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
async function runJob(job, due) {
|
|
999
|
+
const state = stateOf(job.jobId);
|
|
1000
|
+
const finish = (status, error, runAt) => {
|
|
1001
|
+
state.lastRunAt = new Date(runAt).toISOString();
|
|
1002
|
+
state.lastStatus = status;
|
|
1003
|
+
state.lastError = error;
|
|
1004
|
+
const chained = nextDueAfter(job.schedule, due);
|
|
1005
|
+
state.nextDueAt = chained === null ? void 0 : Math.max(state.nextDueAt ?? Number.NEGATIVE_INFINITY, chained);
|
|
1006
|
+
persist();
|
|
1007
|
+
config.onTerminal?.(job.jobId, status, error);
|
|
1008
|
+
};
|
|
1009
|
+
try {
|
|
1010
|
+
const dest = job.persistTo !== void 0 ? {
|
|
1011
|
+
kind: "persist",
|
|
1012
|
+
path: job.persistTo.replaceAll("{{date}}", isoDateOf(due, config.timezone))
|
|
1013
|
+
} : job.deliverLocalDir !== void 0 ? {
|
|
1014
|
+
kind: "local",
|
|
1015
|
+
dir: job.deliverLocalDir
|
|
1016
|
+
} : (() => {
|
|
1017
|
+
const address = job.target ?? ports.identity.addressOf(job.maidId);
|
|
1018
|
+
return address === void 0 ? void 0 : {
|
|
1019
|
+
kind: "im",
|
|
1020
|
+
address
|
|
1021
|
+
};
|
|
1022
|
+
})();
|
|
1023
|
+
if (dest === void 0) {
|
|
1024
|
+
finish("gen-failed", `no delivery address for maid '${job.maidId}'`, due);
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
if (job.kind === "script") {
|
|
1028
|
+
const res = await runScript(job.script);
|
|
1029
|
+
if (!res.ok) {
|
|
1030
|
+
finish("script-failed", `${res.reason}: ${res.stderrTail}`.slice(0, 500), due);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
if (res.stdout.trim().length === 0) {
|
|
1034
|
+
finish("ok-silent", void 0, due);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
if (dest.kind === "persist") {
|
|
1038
|
+
writePersistFile(dest.path, res.stdout);
|
|
1039
|
+
finish("ok", void 0, due);
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
if (dest.kind === "local") {
|
|
1043
|
+
writeLocalArchive(dest.dir, job.jobId, due, res.stdout);
|
|
1044
|
+
finish("ok", void 0, due);
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
if ((await ports.delivery.send(job.maidId, dest.address, res.stdout)).sent) {
|
|
1048
|
+
finish("ok", void 0, due);
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
if (config.deliverRetryDelayMs > 0) await sleep(config.deliverRetryDelayMs);
|
|
1052
|
+
const second = await ports.delivery.send(job.maidId, dest.address, res.stdout);
|
|
1053
|
+
finish(second.sent ? "ok" : "deliver-failed", second.sent ? void 0 : `delivery failed: ${second.code}`, due);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
if (config.agent === void 0) {
|
|
1057
|
+
finish("gen-failed", "config.agent (provider/model) required", due);
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
const limit = job.recentLimit ?? config.recentLimit;
|
|
1061
|
+
const recents = ports.memory !== void 0 && limit > 0 ? await ports.memory.recent(job.maidId, limit).catch(() => []) : [];
|
|
1062
|
+
let scriptOutput = "";
|
|
1063
|
+
if (job.script !== void 0) {
|
|
1064
|
+
const res = await runScript(job.script);
|
|
1065
|
+
if (!res.ok) {
|
|
1066
|
+
finish("script-failed", `${res.reason}: ${res.stderrTail}`.slice(0, 500), due);
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
if (res.stdout.trim().length === 0 && job.skipOnEmptyScriptOutput) {
|
|
1070
|
+
finish("ok-silent", void 0, due);
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
scriptOutput = res.stdout;
|
|
1074
|
+
}
|
|
1075
|
+
const sessionId = `cron_${job.jobId}_${new Date(due).toISOString()}`;
|
|
1076
|
+
ports.memory?.markSkip(sessionId);
|
|
1077
|
+
const texts = [];
|
|
1078
|
+
const off = ports.events.onAssistantText(sessionId, (t) => {
|
|
1079
|
+
if (t.length > 0) texts.push(t);
|
|
1080
|
+
});
|
|
1081
|
+
let handle;
|
|
1082
|
+
let timedOut = false;
|
|
1083
|
+
try {
|
|
1084
|
+
handle = await ports.agents.create({
|
|
1085
|
+
sessionId,
|
|
1086
|
+
meta: {
|
|
1087
|
+
agentPreset: ports.identity.presetOf(job.maidId),
|
|
1088
|
+
...config.agent.cwd !== void 0 ? { cwd: config.agent.cwd } : {}
|
|
1089
|
+
},
|
|
1090
|
+
agentOptions: {
|
|
1091
|
+
provider: config.agent.provider,
|
|
1092
|
+
model: config.agent.model
|
|
1093
|
+
},
|
|
1094
|
+
...config.agentSetup !== void 0 ? { setup: config.agentSetup } : {}
|
|
1095
|
+
});
|
|
1096
|
+
cancels.set(job.jobId, () => {
|
|
1097
|
+
handle?.dispose();
|
|
1098
|
+
});
|
|
1099
|
+
const nightContext = ports.voice !== void 0 && job.promptTemplate.includes("{{nightContext}}") ? ports.voice.buildNightContext(job.maidId) : "";
|
|
1100
|
+
const overnightSentence = ports.voice !== void 0 && job.promptTemplate.includes("{{overnightSentence}}") ? ports.voice.getMorningSentence(job.maidId) : "";
|
|
1101
|
+
const prompt = renderBriefingPrompt(job.promptTemplate, {
|
|
1102
|
+
date: formatDateZh(due, config.timezone),
|
|
1103
|
+
recentMemories: renderRecentMemories(recents, config.recentMaxChars),
|
|
1104
|
+
scriptOutput,
|
|
1105
|
+
nightContext,
|
|
1106
|
+
overnightSentence
|
|
1107
|
+
});
|
|
1108
|
+
handle.agent.followup(createUserMessage({
|
|
1109
|
+
content: [{
|
|
1110
|
+
type: "text",
|
|
1111
|
+
text: prompt
|
|
1112
|
+
}],
|
|
1113
|
+
source: { kind: "user" }
|
|
1114
|
+
}));
|
|
1115
|
+
if (job.genTimeoutMs > 0) setTimeout(() => {
|
|
1116
|
+
timedOut = true;
|
|
1117
|
+
handle?.dispose();
|
|
1118
|
+
}, job.genTimeoutMs).unref?.();
|
|
1119
|
+
await handle.agent.whenIdle();
|
|
1120
|
+
if (timedOut) {
|
|
1121
|
+
finish("gen-failed", `generation timeout after ${job.genTimeoutMs}ms`, due);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
const text = texts.at(-1);
|
|
1125
|
+
if (text === void 0) {
|
|
1126
|
+
finish("gen-failed", "no assistant output", due);
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
if (dest.kind === "persist") {
|
|
1130
|
+
writePersistFile(dest.path, text);
|
|
1131
|
+
finish("ok", void 0, due);
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
if (dest.kind === "local") {
|
|
1135
|
+
writeLocalArchive(dest.dir, job.jobId, due, text);
|
|
1136
|
+
finish("ok", void 0, due);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
if ((await ports.delivery.send(job.maidId, dest.address, text)).sent) {
|
|
1140
|
+
finish("ok", void 0, due);
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
if (config.deliverRetryDelayMs > 0) await sleep(config.deliverRetryDelayMs);
|
|
1144
|
+
const second = await ports.delivery.send(job.maidId, dest.address, text);
|
|
1145
|
+
finish(second.sent ? "ok" : "deliver-failed", second.sent ? void 0 : `delivery failed: ${second.code}`, due);
|
|
1146
|
+
} finally {
|
|
1147
|
+
cancels.delete(job.jobId);
|
|
1148
|
+
off();
|
|
1149
|
+
await handle?.dispose();
|
|
1150
|
+
}
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
finish("gen-failed", error instanceof Error ? error.message : String(error), due);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
function tick(nowMs = ports.now()) {
|
|
1156
|
+
for (const job of jobs) {
|
|
1157
|
+
if (!job.enabled) continue;
|
|
1158
|
+
const state = stateOf(job.jobId);
|
|
1159
|
+
if (state.nextDueAt === void 0) continue;
|
|
1160
|
+
if (nowMs < state.nextDueAt) continue;
|
|
1161
|
+
const due = state.nextDueAt;
|
|
1162
|
+
if (inFlight.has(job.jobId)) {
|
|
1163
|
+
state.lastRunAt = new Date(nowMs).toISOString();
|
|
1164
|
+
state.lastStatus = "overlap-skipped";
|
|
1165
|
+
state.lastError = void 0;
|
|
1166
|
+
state.nextDueAt = nextDueAfter(job.schedule, due) ?? void 0;
|
|
1167
|
+
persist();
|
|
1168
|
+
config.onTerminal?.(job.jobId, "overlap-skipped", void 0);
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
if (nowMs - due > config.misfireWindowMs) {
|
|
1172
|
+
state.lastRunAt = new Date(nowMs).toISOString();
|
|
1173
|
+
state.lastStatus = "misfire-skipped";
|
|
1174
|
+
state.misfireCount += 1;
|
|
1175
|
+
state.nextDueAt = nextDueAfter(job.schedule, nowMs) ?? void 0;
|
|
1176
|
+
persist();
|
|
1177
|
+
config.onTerminal?.(job.jobId, "misfire-skipped", void 0);
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
const run = runJob(job, due).finally(() => {
|
|
1181
|
+
inFlight.delete(job.jobId);
|
|
1182
|
+
});
|
|
1183
|
+
inFlight.set(job.jobId, run);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
return {
|
|
1187
|
+
tick,
|
|
1188
|
+
whenIdle: () => Promise.allSettled([...inFlight.values()]).then(() => {}),
|
|
1189
|
+
dispose: async () => {
|
|
1190
|
+
for (const cancel of cancels.values()) cancel();
|
|
1191
|
+
await Promise.allSettled([...inFlight.values()]);
|
|
1192
|
+
},
|
|
1193
|
+
runStates: () => states
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
//#endregion
|
|
1197
|
+
//#region src/preset-mount.ts
|
|
1198
|
+
/** registry 服务消费入口:reflect 免 inject 直读(照 maid-cron maidMemory 同款探测纪律)。 */
|
|
1199
|
+
function presetRegistryOf(ctx, serviceName) {
|
|
1200
|
+
return ctx?.reflect.get(serviceName, false);
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* 包装 agents.create:job 带 meta.agentPreset 时注入 setup 钩子,在 agent 装配期把
|
|
1204
|
+
* maid preset 挂进 agent scope(persona/工具/技能目录全部继承);与既有 opts.setup
|
|
1205
|
+
* (引擎侧 config.agentSetup)链式组合,挂载在前。
|
|
1206
|
+
* - 无 preset id → 不注入,fail-open(无 preset 的 job 与现状同);
|
|
1207
|
+
* - registry 缺失 / resolve 抛(未注册) / mount throw → 拒绝 create(fail-loud,
|
|
1208
|
+
* job 以 gen-failed 落 state 可观测)。
|
|
1209
|
+
*/
|
|
1210
|
+
function createPresetMountingAgents(base, registry) {
|
|
1211
|
+
return async (opts) => {
|
|
1212
|
+
const presetId = opts.meta?.agentPreset;
|
|
1213
|
+
if (typeof presetId !== "string" || presetId.length === 0) return base(opts);
|
|
1214
|
+
if (registry === void 0) throw new Error("maid-cron: mountPresets 开启但 agentPresets registry 服务不可用——0.2.0 起预设须经插件行声明(profile patch 的 preset-doris 行),且宿主组合须含 @deepseek-ai/dsh-agent-preset-registry");
|
|
1215
|
+
const resolved = await registry.resolve(presetId);
|
|
1216
|
+
const chained = typeof opts.setup === "function" ? opts.setup : void 0;
|
|
1217
|
+
const setup = async (agentCtx) => {
|
|
1218
|
+
await registry.mount(agentCtx, resolved.id);
|
|
1219
|
+
await chained?.(agentCtx);
|
|
1220
|
+
};
|
|
1221
|
+
return base({
|
|
1222
|
+
...opts,
|
|
1223
|
+
setup
|
|
1224
|
+
});
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
//#endregion
|
|
1228
|
+
//#region src/plugin.ts
|
|
1229
|
+
const apply = (ctx, config) => {
|
|
1230
|
+
if (!config?.stateFile) throw new Error("maid-cron: config.stateFile 必填");
|
|
1231
|
+
const warn = (m) => (ctx.logger?.("maid-cron") ?? console).warn(m);
|
|
1232
|
+
const jobs = compileJobs(config.jobs, warn);
|
|
1233
|
+
const resolved = {
|
|
1234
|
+
tickIntervalMs: config.tickIntervalMs ?? 3e4,
|
|
1235
|
+
misfireWindowMs: config.misfireWindowMs ?? 36e5,
|
|
1236
|
+
recentLimit: config.recentLimit ?? 10,
|
|
1237
|
+
recentMaxChars: config.recentMaxChars ?? 1500,
|
|
1238
|
+
timezone: config.timezone ?? "Asia/Shanghai",
|
|
1239
|
+
deliverRetryDelayMs: config.deliverRetryDelayMs ?? 5e3,
|
|
1240
|
+
agent: config.agent,
|
|
1241
|
+
agentSetup: config.agentSetup,
|
|
1242
|
+
stateFile: config.stateFile,
|
|
1243
|
+
onTerminal: (jobId, status, error) => warn(`maid-cron: ${jobId} → ${status}${error !== void 0 ? ` (${error})` : ""}`)
|
|
1244
|
+
};
|
|
1245
|
+
const maidMemoryService = ctx.reflect.get("maidMemory", false);
|
|
1246
|
+
if (maidMemoryService === void 0) warn("maid-cron: maidMemory 未挂载——晨报将不带记忆摘要");
|
|
1247
|
+
const memory = maidMemoryService === void 0 ? void 0 : {
|
|
1248
|
+
recent: (maidId, limit) => {
|
|
1249
|
+
const ns = maidMemoryService.resolveNamespace(ctx.maidIdentity.get(maidId)?.preset);
|
|
1250
|
+
return ns === void 0 ? Promise.resolve([]) : maidMemoryService.recent(ns, limit);
|
|
1251
|
+
},
|
|
1252
|
+
markSkip: (sessionId) => {
|
|
1253
|
+
maidMemoryService.markSkipMemory(sessionId);
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
const maidVoiceService = ctx.reflect?.get?.("maidVoice", false);
|
|
1257
|
+
if (maidVoiceService === void 0) warn("maid-cron: maidVoice 未挂载——{{nightContext}}/{{overnightSentence}} 将渲染为空");
|
|
1258
|
+
const voice = maidVoiceService === void 0 ? void 0 : {
|
|
1259
|
+
buildNightContext: (maidId) => maidVoiceService.buildNightContext(maidId) ?? "",
|
|
1260
|
+
getMorningSentence: (maidId) => maidVoiceService.getMorningSentence(maidId) ?? ""
|
|
1261
|
+
};
|
|
1262
|
+
const baseCreate = (opts) => ctx.agents.create(opts);
|
|
1263
|
+
const presetRegistry = config.mountPresets === true ? presetRegistryOf(ctx, config.presetRegistryService ?? "agentPresets") : void 0;
|
|
1264
|
+
const agentsCreate = config.mountPresets === true ? createPresetMountingAgents(baseCreate, presetRegistry) : baseCreate;
|
|
1265
|
+
const engine = createCronEngine(resolved, jobs, {
|
|
1266
|
+
identity: {
|
|
1267
|
+
presetOf: (maidId) => ctx.maidIdentity.get(maidId)?.preset,
|
|
1268
|
+
addressOf: (maidId) => ctx.maidIdentity.deliveryTargets(maidId)?.[0]
|
|
1269
|
+
},
|
|
1270
|
+
memory,
|
|
1271
|
+
...voice !== void 0 ? { voice } : {},
|
|
1272
|
+
agents: { create: agentsCreate },
|
|
1273
|
+
delivery: ctx.maidDelivery,
|
|
1274
|
+
events: { onAssistantText: (sessionId, cb) => ctx.on("session/event", (session, event) => {
|
|
1275
|
+
if (session.id !== sessionId || event.type !== "assistant/message") return;
|
|
1276
|
+
const text = (event.data?.message?.content ?? []).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
|
|
1277
|
+
if (text.length > 0) cb(text);
|
|
1278
|
+
}) },
|
|
1279
|
+
now: config.now ?? (() => Date.now())
|
|
1280
|
+
});
|
|
1281
|
+
const disposeProvide = ctx.provide("maidCron", {
|
|
1282
|
+
tick: (nowMs) => engine.tick(nowMs),
|
|
1283
|
+
whenIdle: () => engine.whenIdle(),
|
|
1284
|
+
runStates: () => engine.runStates()
|
|
1285
|
+
});
|
|
1286
|
+
const interval = resolved.tickIntervalMs > 0 ? setInterval(() => engine.tick(), resolved.tickIntervalMs) : void 0;
|
|
1287
|
+
interval?.unref();
|
|
1288
|
+
return async () => {
|
|
1289
|
+
if (interval !== void 0) clearInterval(interval);
|
|
1290
|
+
disposeProvide();
|
|
1291
|
+
await engine.dispose();
|
|
1292
|
+
};
|
|
1293
|
+
};
|
|
1294
|
+
//#endregion
|
|
1295
|
+
//#region src/identity.ts
|
|
1296
|
+
const name = "maid-cron";
|
|
1297
|
+
const inject = [
|
|
1298
|
+
"maidIdentity",
|
|
1299
|
+
"maidDelivery",
|
|
1300
|
+
"agents"
|
|
1301
|
+
];
|
|
1302
|
+
//#endregion
|
|
1303
|
+
export { BRIEFING_TEMPLATE_DEFAULT, apply, compileJobs, createCronEngine, inject, loadState, name, nextDueAfter, renderBriefingPrompt, renderRecentMemories, saveState };
|