alvin-bot 5.7.0 → 5.8.0
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/CHANGELOG.md +13 -0
- package/dist/claude.js +1 -102
- package/dist/config.js +1 -96
- package/dist/engine.js +1 -90
- package/dist/find-claude-binary.js +1 -98
- package/dist/handlers/async-agent-chunk-handler.js +1 -50
- package/dist/handlers/background-bypass.js +1 -75
- package/dist/handlers/commands.js +1 -2336
- package/dist/handlers/cron-progress.js +1 -52
- package/dist/handlers/document.js +1 -194
- package/dist/handlers/message.js +1 -959
- package/dist/handlers/photo.js +1 -154
- package/dist/handlers/platform-message.js +1 -360
- package/dist/handlers/stuck-timer.js +1 -54
- package/dist/handlers/video.js +1 -237
- package/dist/handlers/voice.js +1 -148
- package/dist/i18n.js +1 -805
- package/dist/index.js +1 -697
- package/dist/init-data-dir.js +1 -98
- package/dist/middleware/auth.js +1 -233
- package/dist/migrate.js +1 -162
- package/dist/paths.js +1 -146
- package/dist/platforms/discord.js +1 -175
- package/dist/platforms/index.js +1 -130
- package/dist/platforms/signal.js +1 -205
- package/dist/platforms/slack-slash-parser.js +1 -32
- package/dist/platforms/slack.js +1 -501
- package/dist/platforms/telegram.js +1 -111
- package/dist/platforms/types.js +1 -8
- package/dist/platforms/whatsapp-auth-helpers.js +1 -53
- package/dist/platforms/whatsapp.js +1 -707
- package/dist/providers/claude-sdk-provider.js +1 -565
- package/dist/providers/codex-cli-provider.js +1 -134
- package/dist/providers/index.js +1 -7
- package/dist/providers/ollama-provider.js +1 -32
- package/dist/providers/openai-compatible.js +1 -406
- package/dist/providers/registry.js +1 -352
- package/dist/providers/runtime-header.js +1 -45
- package/dist/providers/tool-executor.js +1 -475
- package/dist/providers/types.js +1 -227
- package/dist/services/access.js +1 -144
- package/dist/services/allowed-users-gate.js +1 -56
- package/dist/services/alvin-dispatch.js +1 -174
- package/dist/services/alvin-mcp-tools.js +1 -104
- package/dist/services/asset-index.js +1 -224
- package/dist/services/async-agent-parser.js +1 -418
- package/dist/services/async-agent-watcher.js +1 -583
- package/dist/services/auto-diagnostic.js +1 -228
- package/dist/services/broadcast.js +1 -52
- package/dist/services/browser-manager.js +1 -562
- package/dist/services/browser-webfetch.js +1 -127
- package/dist/services/browser.js +1 -121
- package/dist/services/cdp-bootstrap.js +1 -357
- package/dist/services/compaction.js +1 -144
- package/dist/services/critical-notify.js +1 -203
- package/dist/services/cron-resolver.js +1 -58
- package/dist/services/cron-scheduling.js +1 -310
- package/dist/services/cron.js +1 -861
- package/dist/services/custom-tools.js +1 -317
- package/dist/services/delivery-queue.js +1 -173
- package/dist/services/delivery-registry.js +1 -21
- package/dist/services/disk-cleanup.js +1 -203
- package/dist/services/elevenlabs.js +1 -58
- package/dist/services/embeddings/auto-detect.js +1 -74
- package/dist/services/embeddings/fts5.js +1 -108
- package/dist/services/embeddings/gemini.js +1 -65
- package/dist/services/embeddings/index.js +1 -496
- package/dist/services/embeddings/ollama.js +1 -78
- package/dist/services/embeddings/openai.js +1 -49
- package/dist/services/embeddings/provider.js +1 -22
- package/dist/services/embeddings/vector-base.js +1 -113
- package/dist/services/embeddings-migration.js +1 -193
- package/dist/services/embeddings.js +1 -9
- package/dist/services/env-file.js +1 -50
- package/dist/services/exec-guard.js +1 -71
- package/dist/services/fallback-order.js +1 -154
- package/dist/services/file-permissions.js +1 -93
- package/dist/services/heartbeat-file.js +1 -65
- package/dist/services/heartbeat.js +1 -313
- package/dist/services/hooks.js +1 -44
- package/dist/services/imagegen.js +1 -72
- package/dist/services/language-detect.js +1 -154
- package/dist/services/markdown.js +1 -63
- package/dist/services/mcp.js +1 -263
- package/dist/services/memory-extractor.js +1 -178
- package/dist/services/memory-inject-mode.js +1 -43
- package/dist/services/memory-layers.js +1 -156
- package/dist/services/memory.js +1 -146
- package/dist/services/ollama-manager.js +1 -339
- package/dist/services/permissions-wizard.js +1 -291
- package/dist/services/personality.js +1 -376
- package/dist/services/plugins.js +1 -171
- package/dist/services/preflight.js +1 -292
- package/dist/services/process-manager.js +1 -291
- package/dist/services/release-highlights.js +1 -79
- package/dist/services/reminders.js +1 -97
- package/dist/services/restart.js +1 -48
- package/dist/services/security-audit.js +1 -74
- package/dist/services/self-diagnosis.js +1 -272
- package/dist/services/self-search.js +1 -129
- package/dist/services/session-persistence.js +1 -237
- package/dist/services/session.js +1 -282
- package/dist/services/skills.js +1 -290
- package/dist/services/ssrf-guard.js +1 -162
- package/dist/services/standing-orders.js +1 -29
- package/dist/services/steer-channel.js +1 -46
- package/dist/services/stop-controller.js +1 -52
- package/dist/services/subagent-dedup.js +1 -86
- package/dist/services/subagent-delivery.js +1 -452
- package/dist/services/subagent-stats.js +1 -123
- package/dist/services/subagents.js +1 -814
- package/dist/services/sudo.js +1 -329
- package/dist/services/telegram.js +1 -158
- package/dist/services/timing-safe-bearer.js +1 -51
- package/dist/services/tool-discovery.js +1 -214
- package/dist/services/trends.js +1 -580
- package/dist/services/updater.js +1 -291
- package/dist/services/usage-tracker.js +1 -144
- package/dist/services/users.js +1 -271
- package/dist/services/voice.js +1 -104
- package/dist/services/watchdog-brake.js +1 -154
- package/dist/services/watchdog.js +1 -311
- package/dist/services/workspaces.js +1 -276
- package/dist/tui/index.js +1 -667
- package/dist/util/console-formatter.js +1 -109
- package/dist/util/debounce.js +1 -24
- package/dist/util/telegram-error-filter.js +1 -62
- package/dist/version.js +1 -24
- package/dist/web/bind-strategy.js +1 -42
- package/dist/web/canvas.js +1 -30
- package/dist/web/doctor-api.js +1 -604
- package/dist/web/openai-compat.js +1 -252
- package/dist/web/server.js +1 -1902
- package/dist/web/setup-api.js +1 -1101
- package/package.json +5 -2
- package/dist/.metadata_never_index +0 -0
package/dist/services/cron.js
CHANGED
|
@@ -1,861 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cron Service — Persistent scheduled tasks.
|
|
3
|
-
*
|
|
4
|
-
* Supports:
|
|
5
|
-
* - Interval-based jobs (every 5m, 1h, etc.)
|
|
6
|
-
* - Cron expressions (0 9 * * 1 = every Monday 9am)
|
|
7
|
-
* - One-shot scheduled tasks (run once at a specific time)
|
|
8
|
-
* - Job types: reminder, shell, ai-query, http
|
|
9
|
-
* - Management via /cron command + Web UI
|
|
10
|
-
* - Persisted to docs/cron-jobs.json (survives restarts)
|
|
11
|
-
*/
|
|
12
|
-
import fs from "fs";
|
|
13
|
-
import { execSync } from "child_process";
|
|
14
|
-
import { resolve, dirname } from "path";
|
|
15
|
-
import { CRON_FILE, BOT_ROOT } from "../paths.js";
|
|
16
|
-
import { prepareForExecution, handleStartupCatchup, calculateNextRunFrom, } from "./cron-scheduling.js";
|
|
17
|
-
import { resolveJobByNameOrId } from "./cron-resolver.js";
|
|
18
|
-
import { bootWasExpectedRestart } from "./watchdog.js";
|
|
19
|
-
// ── Storage ─────────────────────────────────────────────
|
|
20
|
-
/** Allowed job types — must stay in sync with the JobType union above. */
|
|
21
|
-
const ALLOWED_JOB_TYPES = new Set(["reminder", "shell", "ai-query", "http", "message"]);
|
|
22
|
-
/**
|
|
23
|
-
* M4: Per-entry structural validation for a raw parsed cron-jobs.json entry.
|
|
24
|
-
*
|
|
25
|
-
* Rules:
|
|
26
|
-
* - Must be a non-null object
|
|
27
|
-
* - `id`, `name`, `schedule`, `createdBy` must be non-empty strings
|
|
28
|
-
* - `type` must be one of the allowed JobType values
|
|
29
|
-
* - `payload` must be an object (not null)
|
|
30
|
-
* - `target` must be an object with `platform` (string) and `chatId` (string)
|
|
31
|
-
* - `enabled` must be a boolean
|
|
32
|
-
* - `createdAt`, `runCount` must be numbers
|
|
33
|
-
* - `oneShot` must be a boolean
|
|
34
|
-
* - Nullable fields (lastRunAt, lastResult, lastError, nextRunAt) can be
|
|
35
|
-
* null or their expected types — lenient check, just must not be undefined
|
|
36
|
-
*
|
|
37
|
-
* Returns a validated CronJob (cast) on success, null on failure.
|
|
38
|
-
* Logs a calm warning for every skipped entry.
|
|
39
|
-
*/
|
|
40
|
-
function validateCronJobEntry(raw, index) {
|
|
41
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
42
|
-
console.warn(`[cron] skipping entry #${index}: not an object`);
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
const e = raw;
|
|
46
|
-
if (typeof e.id !== "string" || !e.id) {
|
|
47
|
-
console.warn(`[cron] skipping entry #${index}: missing or invalid 'id'`);
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
if (typeof e.name !== "string" || !e.name) {
|
|
51
|
-
console.warn(`[cron] skipping entry #${index} (id=${e.id}): missing or invalid 'name'`);
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
if (typeof e.type !== "string" || !ALLOWED_JOB_TYPES.has(e.type)) {
|
|
55
|
-
console.warn(`[cron] skipping entry #${index} (id=${e.id}): unknown or missing job type '${e.type}'`);
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
if (typeof e.schedule !== "string" || !e.schedule) {
|
|
59
|
-
console.warn(`[cron] skipping entry #${index} (id=${e.id}): missing or invalid 'schedule'`);
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
if (!e.payload || typeof e.payload !== "object" || Array.isArray(e.payload)) {
|
|
63
|
-
console.warn(`[cron] skipping entry #${index} (id=${e.id}): 'payload' must be an object`);
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
if (!e.target || typeof e.target !== "object" || Array.isArray(e.target)) {
|
|
67
|
-
console.warn(`[cron] skipping entry #${index} (id=${e.id}): 'target' must be an object`);
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
const t = e.target;
|
|
71
|
-
if (typeof t.platform !== "string" || typeof t.chatId !== "string") {
|
|
72
|
-
console.warn(`[cron] skipping entry #${index} (id=${e.id}): 'target.platform' and 'target.chatId' must be strings`);
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
if (typeof e.enabled !== "boolean") {
|
|
76
|
-
console.warn(`[cron] skipping entry #${index} (id=${e.id}): 'enabled' must be a boolean`);
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
if (typeof e.createdAt !== "number") {
|
|
80
|
-
console.warn(`[cron] skipping entry #${index} (id=${e.id}): 'createdAt' must be a number`);
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
if (typeof e.runCount !== "number") {
|
|
84
|
-
console.warn(`[cron] skipping entry #${index} (id=${e.id}): 'runCount' must be a number`);
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
if (typeof e.oneShot !== "boolean") {
|
|
88
|
-
console.warn(`[cron] skipping entry #${index} (id=${e.id}): 'oneShot' must be a boolean`);
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
// Lenient nullable fields
|
|
92
|
-
if (e.createdBy !== undefined && typeof e.createdBy !== "string") {
|
|
93
|
-
console.warn(`[cron] skipping entry #${index} (id=${e.id}): 'createdBy' must be a string`);
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
return raw;
|
|
97
|
-
}
|
|
98
|
-
function loadJobs() {
|
|
99
|
-
let raw;
|
|
100
|
-
try {
|
|
101
|
-
raw = fs.readFileSync(CRON_FILE, "utf-8");
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
return [];
|
|
105
|
-
}
|
|
106
|
-
let parsed;
|
|
107
|
-
try {
|
|
108
|
-
parsed = JSON.parse(raw);
|
|
109
|
-
}
|
|
110
|
-
catch (err) {
|
|
111
|
-
console.warn("[cron] cron-jobs.json is not valid JSON — starting with empty job list:", err instanceof Error ? err.message : String(err));
|
|
112
|
-
return [];
|
|
113
|
-
}
|
|
114
|
-
if (!Array.isArray(parsed)) {
|
|
115
|
-
console.warn("[cron] cron-jobs.json root is not an array — starting with empty job list");
|
|
116
|
-
return [];
|
|
117
|
-
}
|
|
118
|
-
// M4: Per-entry validation — one bad entry must NOT crash the whole list
|
|
119
|
-
const jobs = [];
|
|
120
|
-
for (let i = 0; i < parsed.length; i++) {
|
|
121
|
-
const validated = validateCronJobEntry(parsed[i], i);
|
|
122
|
-
if (validated !== null) {
|
|
123
|
-
jobs.push(validated);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return jobs;
|
|
127
|
-
}
|
|
128
|
-
function saveJobs(jobs) {
|
|
129
|
-
const dir = dirname(CRON_FILE);
|
|
130
|
-
if (!fs.existsSync(dir))
|
|
131
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
132
|
-
fs.writeFileSync(CRON_FILE, JSON.stringify(jobs, null, 2));
|
|
133
|
-
}
|
|
134
|
-
// ── Cron Parsing ────────────────────────────────────────
|
|
135
|
-
/**
|
|
136
|
-
* Parse an interval string (5m, 1h, 30s, 2d) to milliseconds.
|
|
137
|
-
*/
|
|
138
|
-
function parseInterval(input) {
|
|
139
|
-
const match = input.match(/^(\d+(?:\.\d+)?)\s*(s|sec|m|min|h|hr|d|day)s?$/i);
|
|
140
|
-
if (!match)
|
|
141
|
-
return null;
|
|
142
|
-
const value = parseFloat(match[1]);
|
|
143
|
-
const unit = match[2].toLowerCase();
|
|
144
|
-
const mult = { s: 1000, sec: 1000, m: 60_000, min: 60_000, h: 3_600_000, hr: 3_600_000, d: 86_400_000, day: 86_400_000 };
|
|
145
|
-
return value * (mult[unit] || 60_000);
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Parse a cron expression and find the next run time.
|
|
149
|
-
* Supports: minute hour day month weekday
|
|
150
|
-
* Simple implementation — covers common cases.
|
|
151
|
-
*/
|
|
152
|
-
function nextCronRun(expression, after = new Date()) {
|
|
153
|
-
const parts = expression.trim().split(/\s+/);
|
|
154
|
-
if (parts.length !== 5)
|
|
155
|
-
return null;
|
|
156
|
-
const [minExpr, hourExpr, dayExpr, monthExpr, weekdayExpr] = parts;
|
|
157
|
-
/**
|
|
158
|
-
* Parse a single cron field token (no commas — commas handled by parseField).
|
|
159
|
-
* Supports: `*`, `a`, `a-b`, `a-b/s`, `*\/s`, `a/s`.
|
|
160
|
-
* Returns null for invalid/garbage tokens.
|
|
161
|
-
*/
|
|
162
|
-
function parseFieldToken(token, min, max) {
|
|
163
|
-
const fullRange = () => Array.from({ length: max - min + 1 }, (_, i) => i + min);
|
|
164
|
-
if (token.includes("/")) {
|
|
165
|
-
const slashIdx = token.indexOf("/");
|
|
166
|
-
const basePart = token.slice(0, slashIdx);
|
|
167
|
-
const stepPart = token.slice(slashIdx + 1);
|
|
168
|
-
const step = parseInt(stepPart, 10);
|
|
169
|
-
if (!Number.isFinite(step) || step <= 0)
|
|
170
|
-
return null;
|
|
171
|
-
let base;
|
|
172
|
-
if (basePart === "*") {
|
|
173
|
-
base = fullRange();
|
|
174
|
-
}
|
|
175
|
-
else if (basePart.includes("-")) {
|
|
176
|
-
const dashParts = basePart.split("-");
|
|
177
|
-
if (dashParts.length !== 2)
|
|
178
|
-
return null;
|
|
179
|
-
const a = parseInt(dashParts[0], 10);
|
|
180
|
-
const b = parseInt(dashParts[1], 10);
|
|
181
|
-
if (!Number.isFinite(a) || !Number.isFinite(b) || a > b || a < min || b > max)
|
|
182
|
-
return null;
|
|
183
|
-
base = Array.from({ length: b - a + 1 }, (_, i) => i + a);
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
const a = parseInt(basePart, 10);
|
|
187
|
-
if (!Number.isFinite(a) || a < min || a > max)
|
|
188
|
-
return null;
|
|
189
|
-
base = [a];
|
|
190
|
-
}
|
|
191
|
-
const baseStart = base[0];
|
|
192
|
-
return base.filter((v) => (v - baseStart) % step === 0);
|
|
193
|
-
}
|
|
194
|
-
if (token === "*")
|
|
195
|
-
return fullRange();
|
|
196
|
-
if (token.includes("-")) {
|
|
197
|
-
const dashParts = token.split("-");
|
|
198
|
-
if (dashParts.length !== 2)
|
|
199
|
-
return null;
|
|
200
|
-
const a = parseInt(dashParts[0], 10);
|
|
201
|
-
const b = parseInt(dashParts[1], 10);
|
|
202
|
-
if (!Number.isFinite(a) || !Number.isFinite(b) || a > b || a < min || b > max)
|
|
203
|
-
return null;
|
|
204
|
-
return Array.from({ length: b - a + 1 }, (_, i) => i + a);
|
|
205
|
-
}
|
|
206
|
-
const v = parseInt(token, 10);
|
|
207
|
-
if (!Number.isFinite(v) || v < min || v > max)
|
|
208
|
-
return null;
|
|
209
|
-
return [v];
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Parse a cron field expression (may contain commas) into a sorted array of valid integers.
|
|
213
|
-
* Returns null if any token is invalid/garbage (the expression is rejected).
|
|
214
|
-
*/
|
|
215
|
-
function parseField(expr, min, max) {
|
|
216
|
-
const tokens = expr.split(",").filter((t) => t.length > 0);
|
|
217
|
-
if (tokens.length === 0)
|
|
218
|
-
return null;
|
|
219
|
-
const result = new Set();
|
|
220
|
-
for (const token of tokens) {
|
|
221
|
-
const vals = parseFieldToken(token, min, max);
|
|
222
|
-
if (vals === null)
|
|
223
|
-
return null;
|
|
224
|
-
for (const v of vals)
|
|
225
|
-
result.add(v);
|
|
226
|
-
}
|
|
227
|
-
const arr = [...result].sort((a, b) => a - b);
|
|
228
|
-
return arr.length > 0 ? arr : null;
|
|
229
|
-
}
|
|
230
|
-
const minutes = parseField(minExpr, 0, 59);
|
|
231
|
-
const hours = parseField(hourExpr, 0, 23);
|
|
232
|
-
const days = parseField(dayExpr, 1, 31);
|
|
233
|
-
const months = parseField(monthExpr, 1, 12);
|
|
234
|
-
const weekdays = parseField(weekdayExpr, 0, 6); // 0=Sun
|
|
235
|
-
// Any field returning null means the expression is invalid — reject it
|
|
236
|
-
if (!minutes || !hours || !days || !months || !weekdays)
|
|
237
|
-
return null;
|
|
238
|
-
// Search forward up to 366 days
|
|
239
|
-
const candidate = new Date(after);
|
|
240
|
-
candidate.setSeconds(0, 0);
|
|
241
|
-
candidate.setMinutes(candidate.getMinutes() + 1);
|
|
242
|
-
for (let i = 0; i < 366 * 24 * 60; i++) {
|
|
243
|
-
const m = candidate.getMinutes();
|
|
244
|
-
const h = candidate.getHours();
|
|
245
|
-
const d = candidate.getDate();
|
|
246
|
-
const mo = candidate.getMonth() + 1;
|
|
247
|
-
const wd = candidate.getDay();
|
|
248
|
-
if (minutes.includes(m) && hours.includes(h) && days.includes(d) && months.includes(mo) && weekdays.includes(wd)) {
|
|
249
|
-
return candidate;
|
|
250
|
-
}
|
|
251
|
-
candidate.setMinutes(candidate.getMinutes() + 1);
|
|
252
|
-
}
|
|
253
|
-
return null;
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Calculate next run time for a job.
|
|
257
|
-
*/
|
|
258
|
-
function calculateNextRun(job) {
|
|
259
|
-
if (!job.enabled)
|
|
260
|
-
return null;
|
|
261
|
-
// Interval-based
|
|
262
|
-
const intervalMs = parseInterval(job.schedule);
|
|
263
|
-
if (intervalMs) {
|
|
264
|
-
const base = job.lastRunAt || job.createdAt;
|
|
265
|
-
return base + intervalMs;
|
|
266
|
-
}
|
|
267
|
-
// Cron expression
|
|
268
|
-
const next = nextCronRun(job.schedule);
|
|
269
|
-
return next ? next.getTime() : null;
|
|
270
|
-
}
|
|
271
|
-
let notifyCallback = null;
|
|
272
|
-
export function setNotifyCallback(fn) {
|
|
273
|
-
notifyCallback = fn;
|
|
274
|
-
}
|
|
275
|
-
/** @internal exported for unit-test use only */
|
|
276
|
-
export async function runJob(job) {
|
|
277
|
-
return executeJob(job);
|
|
278
|
-
}
|
|
279
|
-
async function executeJob(job) {
|
|
280
|
-
try {
|
|
281
|
-
switch (job.type) {
|
|
282
|
-
case "reminder":
|
|
283
|
-
case "message": {
|
|
284
|
-
const text = job.payload.text || "(no message)";
|
|
285
|
-
if (notifyCallback) {
|
|
286
|
-
await notifyCallback(job.target, `⏰ ${job.name}\n\n${text}`);
|
|
287
|
-
}
|
|
288
|
-
return { output: `Sent: ${text.slice(0, 100)}` };
|
|
289
|
-
}
|
|
290
|
-
case "shell": {
|
|
291
|
-
const cmd = job.payload.command || "echo 'no command'";
|
|
292
|
-
// v4.12.2 — Cron shell jobs now go through exec-guard. Before
|
|
293
|
-
// v4.12.2 cron bypassed the allowlist, which was inconsistent
|
|
294
|
-
// with the rest of the bot's shell execution policy. With
|
|
295
|
-
// EXEC_SECURITY=allowlist (default) this rejects jobs with
|
|
296
|
-
// shell metacharacters or non-allowlisted binaries. Operators
|
|
297
|
-
// who legitimately need complex shell pipelines in cron set
|
|
298
|
-
// EXEC_SECURITY=full explicitly.
|
|
299
|
-
const { checkExecAllowed } = await import("./exec-guard.js");
|
|
300
|
-
const guard = checkExecAllowed(cmd);
|
|
301
|
-
if (!guard.allowed) {
|
|
302
|
-
const msg = `Cron shell job blocked by exec-guard: ${guard.reason}`;
|
|
303
|
-
console.warn(`[cron] ${job.name}: ${msg}`);
|
|
304
|
-
if (notifyCallback) {
|
|
305
|
-
await notifyCallback(job.target, `🛑 ${job.name}\n${msg}\n\nSet EXEC_SECURITY=full if this is intentional.`);
|
|
306
|
-
}
|
|
307
|
-
return { output: msg };
|
|
308
|
-
}
|
|
309
|
-
// Per-job timeout, default = no timeout (execSync treats timeout=0
|
|
310
|
-
// or "undefined" as infinite). Users opt in via /cron add … --timeout N.
|
|
311
|
-
const shellOpts = {
|
|
312
|
-
stdio: "pipe",
|
|
313
|
-
env: { ...process.env, PATH: process.env.PATH + ":/opt/homebrew/bin:/usr/local/bin" },
|
|
314
|
-
};
|
|
315
|
-
if (typeof job.timeoutMs === "number" && job.timeoutMs > 0) {
|
|
316
|
-
shellOpts.timeout = job.timeoutMs;
|
|
317
|
-
}
|
|
318
|
-
const output = execSync(cmd, shellOpts).toString().trim();
|
|
319
|
-
// Notify with output
|
|
320
|
-
if (notifyCallback && output) {
|
|
321
|
-
await notifyCallback(job.target, `🔧 ${job.name}\n\`\`\`\n${output.slice(0, 3000)}\n\`\`\``);
|
|
322
|
-
}
|
|
323
|
-
return { output: output.slice(0, 5000) };
|
|
324
|
-
}
|
|
325
|
-
case "http": {
|
|
326
|
-
const url = job.payload.url || "";
|
|
327
|
-
const method = job.payload.method || "GET";
|
|
328
|
-
const headers = job.payload.headers || {};
|
|
329
|
-
// M1: SSRF guard — reject private/internal destinations before fetching.
|
|
330
|
-
// Runs even when EXEC_SECURITY=deny (it's a separate, independent control).
|
|
331
|
-
// We validate EVERY redirect hop manually (redirect:"manual") so a
|
|
332
|
-
// public host cannot 302 us into an internal address (post-redirect SSRF).
|
|
333
|
-
const { assertSsrfSafe, SsrfBlockedError: SsrfBlockedErrorCron } = await import("./ssrf-guard.js");
|
|
334
|
-
await assertSsrfSafe(url);
|
|
335
|
-
const baseOpts = { method, headers };
|
|
336
|
-
if (job.payload.body && method !== "GET") {
|
|
337
|
-
baseOpts.body = job.payload.body;
|
|
338
|
-
}
|
|
339
|
-
const MAX_CRON_REDIRECTS = 10;
|
|
340
|
-
let currentUrl = url;
|
|
341
|
-
let res;
|
|
342
|
-
for (let hop = 0;; hop++) {
|
|
343
|
-
res = await fetch(currentUrl, { ...baseOpts, redirect: "manual" });
|
|
344
|
-
// Not a redirect — we have the final response
|
|
345
|
-
if (res.status < 300 || res.status >= 400)
|
|
346
|
-
break;
|
|
347
|
-
const loc = res.headers.get("location");
|
|
348
|
-
if (!loc)
|
|
349
|
-
break; // no Location header — treat as final response
|
|
350
|
-
if (hop >= MAX_CRON_REDIRECTS) {
|
|
351
|
-
throw new SsrfBlockedErrorCron(url, `too many redirects (> ${MAX_CRON_REDIRECTS})`);
|
|
352
|
-
}
|
|
353
|
-
const next = new URL(loc, currentUrl).href;
|
|
354
|
-
// Re-validate each redirect target before following — closes the
|
|
355
|
-
// post-redirect SSRF bypass.
|
|
356
|
-
await assertSsrfSafe(next);
|
|
357
|
-
currentUrl = next;
|
|
358
|
-
}
|
|
359
|
-
const text = await res.text();
|
|
360
|
-
const output = `HTTP ${res.status}: ${text.slice(0, 2000)}`;
|
|
361
|
-
if (notifyCallback) {
|
|
362
|
-
await notifyCallback(job.target, `🌐 ${job.name}\n${output.slice(0, 500)}`);
|
|
363
|
-
}
|
|
364
|
-
return { output };
|
|
365
|
-
}
|
|
366
|
-
case "ai-query": {
|
|
367
|
-
// AI queries run as isolated sub-agents rather than directly against
|
|
368
|
-
// the registry. This gives cron jobs timeout/cancel/state-tracking
|
|
369
|
-
// "for free" via the existing subagents infrastructure, and — most
|
|
370
|
-
// importantly — keeps them completely independent of any user's
|
|
371
|
-
// active main session. A cron job can run in the background while
|
|
372
|
-
// the user chats with Alvin in the foreground; neither interferes
|
|
373
|
-
// with the other.
|
|
374
|
-
const prompt = job.payload.prompt || "";
|
|
375
|
-
// Dynamic import to avoid circular dep chain (cron → engine → registry
|
|
376
|
-
// and subagents → engine). Type-only import at file top is erased,
|
|
377
|
-
// so no runtime cycle is created.
|
|
378
|
-
const { spawnSubAgent } = await import("./subagents.js");
|
|
379
|
-
try {
|
|
380
|
-
// Turn the fire-and-forget spawnSubAgent into an awaitable via
|
|
381
|
-
// the onComplete callback. Rejection of the spawn promise itself
|
|
382
|
-
// means the max-parallel limit was hit.
|
|
383
|
-
// Parse the target chat id for I3 delivery routing. Only telegram
|
|
384
|
-
// targets get a numeric parentChatId — other platforms/web get
|
|
385
|
-
// undefined and fall through the delivery router's warning path.
|
|
386
|
-
const parentChatId = job.target.platform === "telegram" && job.target.chatId
|
|
387
|
-
? Number(job.target.chatId)
|
|
388
|
-
: undefined;
|
|
389
|
-
const result = await new Promise((resolve, reject) => {
|
|
390
|
-
// Only pass `timeout` through when the job has a per-job value.
|
|
391
|
-
// Otherwise the sub-agent inherits the current /subagents default.
|
|
392
|
-
const spawnConfig = {
|
|
393
|
-
name: job.name,
|
|
394
|
-
prompt,
|
|
395
|
-
workingDir: BOT_ROOT,
|
|
396
|
-
source: "cron",
|
|
397
|
-
parentChatId,
|
|
398
|
-
onComplete: (r) => resolve(r),
|
|
399
|
-
};
|
|
400
|
-
if (typeof job.timeoutMs === "number") {
|
|
401
|
-
spawnConfig.timeout = job.timeoutMs;
|
|
402
|
-
}
|
|
403
|
-
spawnSubAgent(spawnConfig).catch(reject);
|
|
404
|
-
});
|
|
405
|
-
// Non-success: don't notify here. The I3 delivery router has
|
|
406
|
-
// already posted the appropriate banner (cancelled / timeout /
|
|
407
|
-
// error) to parentChatId, so a legacy notifyCallback would
|
|
408
|
-
// produce a duplicate message.
|
|
409
|
-
if (result.status !== "completed") {
|
|
410
|
-
return {
|
|
411
|
-
output: "",
|
|
412
|
-
error: `Sub-agent ${result.status}: ${result.error || result.status}`,
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
const fullResponse = result.output;
|
|
416
|
-
// NOTE: No notifyCallback for ai-query jobs. The I3 delivery
|
|
417
|
-
// router (src/services/subagent-delivery.ts) fires from
|
|
418
|
-
// spawnSubAgent().finally() and sends a proper banner+final to
|
|
419
|
-
// parentChatId. Legacy notifyCallback stays in use for the
|
|
420
|
-
// other job types (reminder, shell, http, message) which do
|
|
421
|
-
// not route through spawnSubAgent.
|
|
422
|
-
return { output: fullResponse.slice(0, 500) };
|
|
423
|
-
}
|
|
424
|
-
catch (err) {
|
|
425
|
-
// Re-throw without notifying — the outer catch will record
|
|
426
|
-
// lastError on the job, and the I3 delivery router has already
|
|
427
|
-
// posted a banner if the failure came from inside spawnSubAgent.
|
|
428
|
-
throw err;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
default:
|
|
432
|
-
return { output: "", error: `Unknown job type: ${job.type}` };
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
catch (err) {
|
|
436
|
-
const error = err instanceof Error ? err.message : String(err);
|
|
437
|
-
// Skip notification for ai-query jobs — the I3 delivery router has
|
|
438
|
-
// already posted the banner. Other job types still get the legacy
|
|
439
|
-
// notify path because they don't route through spawnSubAgent.
|
|
440
|
-
if (notifyCallback && job.type !== "ai-query") {
|
|
441
|
-
await notifyCallback(job.target, `❌ Cron Error (${job.name}): ${error}`);
|
|
442
|
-
}
|
|
443
|
-
return { output: "", error };
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
// ── Scheduler Loop ──────────────────────────────────────
|
|
447
|
-
let schedulerTimer = null;
|
|
448
|
-
const runningJobs = new Set(); // Guard against overlapping executions
|
|
449
|
-
// ── Cross-process job lock ──────────────────────────────
|
|
450
|
-
//
|
|
451
|
-
// `runningJobs` only guards overlap WITHIN this process. If two bot
|
|
452
|
-
// instances are briefly alive at once (a launchd/pm2 restart that left
|
|
453
|
-
// the old process running, or startup-catchup racing the normal tick),
|
|
454
|
-
// each has its own in-memory Set and the same job can fire twice —
|
|
455
|
-
// observed in the wild: a weekly job mailed its report, then mailed an
|
|
456
|
-
// empty duplicate 30 s later. This atomic `mkdir` lock makes the claim
|
|
457
|
-
// cross-process: the second instance sees the lock and skips the slot
|
|
458
|
-
// instead of double-firing. Stale locks (owning PID gone, or — when the
|
|
459
|
-
// meta is unreadable — older than the catch-up grace) are reclaimed so a
|
|
460
|
-
// crash can never wedge a job forever. No deps, cross-platform.
|
|
461
|
-
const CRON_LOCK_DIR = resolve(dirname(CRON_FILE), ".cron-locks");
|
|
462
|
-
const CRON_LOCK_MAX_AGE_MS = 6 * 60 * 60 * 1000; // backstop for corrupt meta
|
|
463
|
-
function cronLockPath(jobId) {
|
|
464
|
-
return resolve(CRON_LOCK_DIR, `${jobId.replace(/[^A-Za-z0-9_-]/g, "_")}.lock`);
|
|
465
|
-
}
|
|
466
|
-
function acquireJobLock(jobId) {
|
|
467
|
-
const lock = cronLockPath(jobId);
|
|
468
|
-
const writeMeta = () => {
|
|
469
|
-
try {
|
|
470
|
-
fs.writeFileSync(resolve(lock, "meta"), JSON.stringify({ pid: process.pid, at: Date.now() }));
|
|
471
|
-
}
|
|
472
|
-
catch { /* meta is best-effort */ }
|
|
473
|
-
};
|
|
474
|
-
try {
|
|
475
|
-
fs.mkdirSync(CRON_LOCK_DIR, { recursive: true });
|
|
476
|
-
}
|
|
477
|
-
catch { /* ignore */ }
|
|
478
|
-
try {
|
|
479
|
-
fs.mkdirSync(lock); // atomic: throws EEXIST if another instance holds it
|
|
480
|
-
writeMeta();
|
|
481
|
-
return true;
|
|
482
|
-
}
|
|
483
|
-
catch {
|
|
484
|
-
let stale = false;
|
|
485
|
-
try {
|
|
486
|
-
const meta = JSON.parse(fs.readFileSync(resolve(lock, "meta"), "utf-8"));
|
|
487
|
-
if (typeof meta.pid === "number") {
|
|
488
|
-
try {
|
|
489
|
-
process.kill(meta.pid, 0); // same-host liveness probe (no signal sent)
|
|
490
|
-
}
|
|
491
|
-
catch (e) {
|
|
492
|
-
if (e.code === "ESRCH")
|
|
493
|
-
stale = true; // owner gone
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
else {
|
|
497
|
-
stale = true; // no usable pid recorded
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
catch {
|
|
501
|
-
// meta missing/corrupt → fall back to lock-dir age
|
|
502
|
-
try {
|
|
503
|
-
stale = Date.now() - fs.statSync(lock).mtimeMs > CRON_LOCK_MAX_AGE_MS;
|
|
504
|
-
}
|
|
505
|
-
catch {
|
|
506
|
-
stale = false; // can't stat → treat as held (skip rather than double-fire)
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
if (!stale)
|
|
510
|
-
return false;
|
|
511
|
-
try {
|
|
512
|
-
fs.rmSync(lock, { recursive: true, force: true });
|
|
513
|
-
fs.mkdirSync(lock);
|
|
514
|
-
writeMeta();
|
|
515
|
-
return true;
|
|
516
|
-
}
|
|
517
|
-
catch {
|
|
518
|
-
return false;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
function releaseJobLock(jobId) {
|
|
523
|
-
try {
|
|
524
|
-
fs.rmSync(cronLockPath(jobId), { recursive: true, force: true });
|
|
525
|
-
}
|
|
526
|
-
catch { /* ignore */ }
|
|
527
|
-
}
|
|
528
|
-
export function startScheduler() {
|
|
529
|
-
if (schedulerTimer)
|
|
530
|
-
return;
|
|
531
|
-
// Startup catch-up — nachholen runs whose last attempt crashed within
|
|
532
|
-
// the grace window. Must run BEFORE the first scheduler tick so the
|
|
533
|
-
// catch-up nextRunAt rewind is visible on the very next pass.
|
|
534
|
-
try {
|
|
535
|
-
const bootJobs = loadJobs();
|
|
536
|
-
const caught = handleStartupCatchup(bootJobs, Date.now(), undefined, {
|
|
537
|
-
expectedRestart: bootWasExpectedRestart(),
|
|
538
|
-
});
|
|
539
|
-
// Only persist if something actually changed to avoid needless writes
|
|
540
|
-
const mutated = caught.some((j, i) => j.nextRunAt !== bootJobs[i].nextRunAt);
|
|
541
|
-
if (mutated) {
|
|
542
|
-
saveJobs(caught);
|
|
543
|
-
const names = caught
|
|
544
|
-
.filter((j, i) => j.nextRunAt !== bootJobs[i].nextRunAt)
|
|
545
|
-
.map((j) => j.name);
|
|
546
|
-
console.log(`⏰ Cron startup catch-up: rewound ${names.length} job(s): ${names.join(", ")}`);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
catch (err) {
|
|
550
|
-
console.error("⏰ Cron startup catch-up failed:", err);
|
|
551
|
-
}
|
|
552
|
-
// Check every 30 seconds for due jobs
|
|
553
|
-
schedulerTimer = setInterval(async () => {
|
|
554
|
-
const jobs = loadJobs();
|
|
555
|
-
const now = Date.now();
|
|
556
|
-
let changed = false;
|
|
557
|
-
for (const job of jobs) {
|
|
558
|
-
if (!job.enabled)
|
|
559
|
-
continue;
|
|
560
|
-
// Skip if this job is already running in THIS bot instance
|
|
561
|
-
if (runningJobs.has(job.id))
|
|
562
|
-
continue;
|
|
563
|
-
// Calculate next run if not set
|
|
564
|
-
if (!job.nextRunAt) {
|
|
565
|
-
job.nextRunAt = calculateNextRun(job);
|
|
566
|
-
changed = true;
|
|
567
|
-
}
|
|
568
|
-
if (job.nextRunAt && now >= job.nextRunAt) {
|
|
569
|
-
console.log(`Cron: Running job "${job.name}" (${job.id})`);
|
|
570
|
-
// Pre-execution state update: advance nextRunAt to the NEXT regular
|
|
571
|
-
// trigger (NOT null) and stamp lastAttemptAt. If the bot crashes
|
|
572
|
-
// mid-execution, handleStartupCatchup will notice the attempt
|
|
573
|
-
// without completion and nachholen within the grace window.
|
|
574
|
-
runningJobs.add(job.id);
|
|
575
|
-
// Cross-process claim: if another bot instance already owns this
|
|
576
|
-
// slot, skip instead of double-firing (the duplicate-report bug).
|
|
577
|
-
if (!acquireJobLock(job.id)) {
|
|
578
|
-
runningJobs.delete(job.id);
|
|
579
|
-
console.log(`Cron: job "${job.name}" (${job.id}) already claimed by another instance — skipping to avoid double-fire`);
|
|
580
|
-
continue;
|
|
581
|
-
}
|
|
582
|
-
const prepared = prepareForExecution(job, now);
|
|
583
|
-
Object.assign(job, prepared);
|
|
584
|
-
saveJobs(jobs);
|
|
585
|
-
try {
|
|
586
|
-
const result = await executeJob(job);
|
|
587
|
-
// Re-load jobs in case they were modified during execution
|
|
588
|
-
const freshJobs = loadJobs();
|
|
589
|
-
const freshJob = freshJobs.find(j => j.id === job.id);
|
|
590
|
-
if (freshJob) {
|
|
591
|
-
freshJob.lastRunAt = Date.now();
|
|
592
|
-
freshJob.lastResult = result.output.slice(0, 4000);
|
|
593
|
-
freshJob.lastError = result.error || null;
|
|
594
|
-
freshJob.runCount++;
|
|
595
|
-
if (freshJob.oneShot) {
|
|
596
|
-
freshJob.enabled = false;
|
|
597
|
-
freshJob.nextRunAt = null;
|
|
598
|
-
}
|
|
599
|
-
else {
|
|
600
|
-
// nextRunAt already set pre-execution, but recalculate in case
|
|
601
|
-
// the schedule or enabled state changed during execution.
|
|
602
|
-
freshJob.nextRunAt = calculateNextRunFrom(freshJob, Date.now());
|
|
603
|
-
}
|
|
604
|
-
saveJobs(freshJobs);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
finally {
|
|
608
|
-
runningJobs.delete(job.id);
|
|
609
|
-
releaseJobLock(job.id);
|
|
610
|
-
}
|
|
611
|
-
continue; // Skip the outer changed/save since we save inside
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
if (changed)
|
|
615
|
-
saveJobs(jobs);
|
|
616
|
-
}, 30_000);
|
|
617
|
-
console.log("⏰ Cron scheduler started (30s interval)");
|
|
618
|
-
}
|
|
619
|
-
export function stopScheduler() {
|
|
620
|
-
if (schedulerTimer) {
|
|
621
|
-
clearInterval(schedulerTimer);
|
|
622
|
-
schedulerTimer = null;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
// ── Public CRUD API ─────────────────────────────────────
|
|
626
|
-
function generateId() {
|
|
627
|
-
return Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
|
628
|
-
}
|
|
629
|
-
export function createJob(input) {
|
|
630
|
-
const job = {
|
|
631
|
-
id: generateId(),
|
|
632
|
-
name: input.name,
|
|
633
|
-
type: input.type,
|
|
634
|
-
schedule: input.schedule,
|
|
635
|
-
oneShot: input.oneShot ?? false,
|
|
636
|
-
payload: input.payload,
|
|
637
|
-
target: input.target,
|
|
638
|
-
enabled: input.enabled ?? true,
|
|
639
|
-
createdAt: Date.now(),
|
|
640
|
-
lastRunAt: null,
|
|
641
|
-
lastResult: null,
|
|
642
|
-
lastError: null,
|
|
643
|
-
nextRunAt: null,
|
|
644
|
-
runCount: 0,
|
|
645
|
-
createdBy: input.createdBy || "unknown",
|
|
646
|
-
...(typeof input.timeoutMs === "number" ? { timeoutMs: input.timeoutMs } : {}),
|
|
647
|
-
};
|
|
648
|
-
// Calculate first run
|
|
649
|
-
job.nextRunAt = calculateNextRun(job);
|
|
650
|
-
const jobs = loadJobs();
|
|
651
|
-
jobs.push(job);
|
|
652
|
-
saveJobs(jobs);
|
|
653
|
-
return job;
|
|
654
|
-
}
|
|
655
|
-
export function listJobs() {
|
|
656
|
-
return loadJobs();
|
|
657
|
-
}
|
|
658
|
-
export function getJob(id) {
|
|
659
|
-
return loadJobs().find(j => j.id === id);
|
|
660
|
-
}
|
|
661
|
-
export function updateJob(id, updates) {
|
|
662
|
-
const jobs = loadJobs();
|
|
663
|
-
const idx = jobs.findIndex(j => j.id === id);
|
|
664
|
-
if (idx < 0)
|
|
665
|
-
return null;
|
|
666
|
-
Object.assign(jobs[idx], updates);
|
|
667
|
-
if (updates.schedule || updates.enabled !== undefined) {
|
|
668
|
-
jobs[idx].nextRunAt = calculateNextRun(jobs[idx]);
|
|
669
|
-
}
|
|
670
|
-
saveJobs(jobs);
|
|
671
|
-
return jobs[idx];
|
|
672
|
-
}
|
|
673
|
-
export function deleteJob(id) {
|
|
674
|
-
const jobs = loadJobs();
|
|
675
|
-
const filtered = jobs.filter(j => j.id !== id);
|
|
676
|
-
if (filtered.length === jobs.length)
|
|
677
|
-
return false;
|
|
678
|
-
saveJobs(filtered);
|
|
679
|
-
return true;
|
|
680
|
-
}
|
|
681
|
-
export function toggleJob(id) {
|
|
682
|
-
const jobs = loadJobs();
|
|
683
|
-
const job = jobs.find(j => j.id === id);
|
|
684
|
-
if (!job)
|
|
685
|
-
return null;
|
|
686
|
-
job.enabled = !job.enabled;
|
|
687
|
-
job.nextRunAt = calculateNextRun(job);
|
|
688
|
-
saveJobs(jobs);
|
|
689
|
-
return job;
|
|
690
|
-
}
|
|
691
|
-
/**
|
|
692
|
-
* Manual /cron run — resolves `nameOrId` against the job list, then
|
|
693
|
-
* executes the job while honouring the in-memory `runningJobs` guard
|
|
694
|
-
* so a simultaneous scheduler-trigger can't overlap.
|
|
695
|
-
*/
|
|
696
|
-
export async function runJobNow(nameOrId) {
|
|
697
|
-
const job = resolveJobByNameOrId(loadJobs(), nameOrId);
|
|
698
|
-
if (!job)
|
|
699
|
-
return { status: "not-found" };
|
|
700
|
-
if (runningJobs.has(job.id)) {
|
|
701
|
-
return { status: "already-running", job };
|
|
702
|
-
}
|
|
703
|
-
runningJobs.add(job.id);
|
|
704
|
-
// Cross-process: another bot instance may already be running this job.
|
|
705
|
-
if (!acquireJobLock(job.id)) {
|
|
706
|
-
runningJobs.delete(job.id);
|
|
707
|
-
return { status: "already-running", job };
|
|
708
|
-
}
|
|
709
|
-
try {
|
|
710
|
-
// executeJob catches its own errors and returns { output, error }.
|
|
711
|
-
// The inner try/catch here is a defensive belt against future
|
|
712
|
-
// refactors that might remove executeJob's outer catch — it
|
|
713
|
-
// guarantees runJobNow's typed contract, so commands.ts never
|
|
714
|
-
// sees an uncaught throw escape into grammy's middleware.
|
|
715
|
-
let result;
|
|
716
|
-
try {
|
|
717
|
-
result = await executeJob(job);
|
|
718
|
-
}
|
|
719
|
-
catch (err) {
|
|
720
|
-
result = {
|
|
721
|
-
output: "",
|
|
722
|
-
error: err instanceof Error ? err.message : String(err),
|
|
723
|
-
};
|
|
724
|
-
}
|
|
725
|
-
// Persist the manual run the same way the scheduler does so the
|
|
726
|
-
// timeline stays honest: lastAttemptAt + lastRunAt + runCount bump.
|
|
727
|
-
try {
|
|
728
|
-
const freshJobs = loadJobs();
|
|
729
|
-
const freshJob = freshJobs.find((j) => j.id === job.id);
|
|
730
|
-
if (freshJob) {
|
|
731
|
-
const now = Date.now();
|
|
732
|
-
freshJob.lastAttemptAt = now;
|
|
733
|
-
freshJob.lastRunAt = now;
|
|
734
|
-
freshJob.lastResult = result.output.slice(0, 4000);
|
|
735
|
-
freshJob.lastError = result.error || null;
|
|
736
|
-
freshJob.runCount++;
|
|
737
|
-
saveJobs(freshJobs);
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
catch (err) {
|
|
741
|
-
console.error("[cron] failed to persist manual run state:", err);
|
|
742
|
-
}
|
|
743
|
-
return { status: "ran", job, output: result.output, error: result.error };
|
|
744
|
-
}
|
|
745
|
-
finally {
|
|
746
|
-
runningJobs.delete(job.id);
|
|
747
|
-
releaseJobLock(job.id);
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
/**
|
|
751
|
-
* Convert a cron expression or interval string to a human-readable German description.
|
|
752
|
-
*/
|
|
753
|
-
export function humanReadableSchedule(schedule) {
|
|
754
|
-
// Interval strings (5m, 1h, 30s, 2d)
|
|
755
|
-
const intervalMatch = schedule.match(/^(\d+(?:\.\d+)?)\s*(s|sec|m|min|h|hr|d|day)s?$/i);
|
|
756
|
-
if (intervalMatch) {
|
|
757
|
-
const value = parseFloat(intervalMatch[1]);
|
|
758
|
-
const unit = intervalMatch[2].toLowerCase();
|
|
759
|
-
const labels = {
|
|
760
|
-
s: ["second", "seconds"], sec: ["second", "seconds"],
|
|
761
|
-
m: ["minute", "minutes"], min: ["minute", "minutes"],
|
|
762
|
-
h: ["hour", "hours"], hr: ["hour", "hours"],
|
|
763
|
-
d: ["day", "days"], day: ["day", "days"],
|
|
764
|
-
};
|
|
765
|
-
const [sing, plur] = labels[unit] || ["?", "?"];
|
|
766
|
-
return `Every ${value} ${value === 1 ? sing : plur}`;
|
|
767
|
-
}
|
|
768
|
-
// Cron expression: MIN HOUR DAY MONTH WEEKDAY
|
|
769
|
-
const parts = schedule.trim().split(/\s+/);
|
|
770
|
-
if (parts.length !== 5)
|
|
771
|
-
return schedule;
|
|
772
|
-
const [minExpr, hourExpr, dayExpr, monthExpr, weekdayExpr] = parts;
|
|
773
|
-
const weekdayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
774
|
-
const monthNames = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
775
|
-
// Helper: format time from hour + minute expressions
|
|
776
|
-
function formatTime(h, m) {
|
|
777
|
-
if (h === "*" && m === "*")
|
|
778
|
-
return "";
|
|
779
|
-
const hh = h === "*" ? "*" : h.padStart(2, "0");
|
|
780
|
-
const mm = m === "*" ? "00" : m.padStart(2, "0");
|
|
781
|
-
return `${hh}:${mm}`;
|
|
782
|
-
}
|
|
783
|
-
// Helper: expand comma/range fields to readable list
|
|
784
|
-
function expandField(expr, names) {
|
|
785
|
-
if (expr === "*")
|
|
786
|
-
return "";
|
|
787
|
-
const vals = expr.split(",").map(v => {
|
|
788
|
-
if (v.includes("-")) {
|
|
789
|
-
const [a, b] = v.split("-");
|
|
790
|
-
if (names)
|
|
791
|
-
return `${names[+a]}–${names[+b]}`;
|
|
792
|
-
return `${a}–${b}`;
|
|
793
|
-
}
|
|
794
|
-
return names ? (names[+v] || v) : v;
|
|
795
|
-
});
|
|
796
|
-
return vals.join(", ");
|
|
797
|
-
}
|
|
798
|
-
const time = formatTime(hourExpr, minExpr);
|
|
799
|
-
const hasStep = [minExpr, hourExpr].some(e => e.includes("/"));
|
|
800
|
-
// Every X minutes/hours
|
|
801
|
-
if (minExpr.includes("/") && hourExpr === "*" && dayExpr === "*" && monthExpr === "*" && weekdayExpr === "*") {
|
|
802
|
-
const step = minExpr.split("/")[1];
|
|
803
|
-
return `Every ${step} min`;
|
|
804
|
-
}
|
|
805
|
-
if (hourExpr.includes("/") && dayExpr === "*" && monthExpr === "*" && weekdayExpr === "*") {
|
|
806
|
-
const step = hourExpr.split("/")[1];
|
|
807
|
-
return `Every ${step}h`;
|
|
808
|
-
}
|
|
809
|
-
// Build description
|
|
810
|
-
const descParts = [];
|
|
811
|
-
// Weekday specific
|
|
812
|
-
if (weekdayExpr !== "*") {
|
|
813
|
-
const days = expandField(weekdayExpr, weekdayNames);
|
|
814
|
-
if (weekdayExpr === "1-5")
|
|
815
|
-
descParts.push("Weekdays");
|
|
816
|
-
else if (weekdayExpr === "0,6" || weekdayExpr === "6,0")
|
|
817
|
-
descParts.push("Weekends");
|
|
818
|
-
else
|
|
819
|
-
descParts.push(`Every ${days}`);
|
|
820
|
-
}
|
|
821
|
-
// Day of month specific
|
|
822
|
-
else if (dayExpr !== "*") {
|
|
823
|
-
const dayList = expandField(dayExpr);
|
|
824
|
-
if (monthExpr !== "*") {
|
|
825
|
-
const monthList = expandField(monthExpr, monthNames);
|
|
826
|
-
descParts.push(`On the ${dayList}. of ${monthList}`);
|
|
827
|
-
}
|
|
828
|
-
else {
|
|
829
|
-
descParts.push(`On the ${dayList}. of every month`);
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
// Month specific only
|
|
833
|
-
else if (monthExpr !== "*") {
|
|
834
|
-
const monthList = expandField(monthExpr, monthNames);
|
|
835
|
-
descParts.push(`In ${monthList}`);
|
|
836
|
-
}
|
|
837
|
-
// Daily (all wildcards except time)
|
|
838
|
-
else if (!hasStep) {
|
|
839
|
-
descParts.push("Daily");
|
|
840
|
-
}
|
|
841
|
-
if (time && !hasStep)
|
|
842
|
-
descParts.push(time);
|
|
843
|
-
return descParts.join(", ") || schedule;
|
|
844
|
-
}
|
|
845
|
-
/**
|
|
846
|
-
* Format next run time as human-readable.
|
|
847
|
-
*/
|
|
848
|
-
export function formatNextRun(nextRunAt) {
|
|
849
|
-
if (!nextRunAt)
|
|
850
|
-
return "—";
|
|
851
|
-
const diff = nextRunAt - Date.now();
|
|
852
|
-
if (diff < 0)
|
|
853
|
-
return "overdue";
|
|
854
|
-
if (diff < 60_000)
|
|
855
|
-
return `in ${Math.round(diff / 1000)}s`;
|
|
856
|
-
if (diff < 3_600_000)
|
|
857
|
-
return `in ${Math.round(diff / 60_000)} Min`;
|
|
858
|
-
if (diff < 86_400_000)
|
|
859
|
-
return `in ${(diff / 3_600_000).toFixed(1)}h`;
|
|
860
|
-
return `in ${(diff / 86_400_000).toFixed(1)} days`;
|
|
861
|
-
}
|
|
1
|
+
const _0x40522f=_0x441e,_0x973e82=_0x441e;(function(_0x5c5d7f,_0x1cca5d){const _0x52dee1=_0x441e,_0x3f8ba9=_0x441e,_0x13886c=_0x5c5d7f();while(!![]){try{const _0x5ed237=-parseInt(_0x52dee1(0x22d))/(-0x156b*0x1+-0x1d07+-0x267*-0x15)+-parseInt(_0x52dee1(0x226))/(0x2cc+-0x8*-0x9d+0xc5*-0xa)+parseInt(_0x52dee1(0x211))/(0x116b+-0x1423+-0x1*-0x2bb)*(parseInt(_0x52dee1(0x184))/(0x208*0x8+-0x2009*0x1+0xfcd))+-parseInt(_0x52dee1(0x1f1))/(-0x3a1*-0xa+-0x21*0x6d+0xed*-0x18)+-parseInt(_0x52dee1(0x1bc))/(-0x25*-0x106+-0x2*0x28d+0x575*-0x6)+-parseInt(_0x3f8ba9(0x19d))/(-0x184+-0x185e+0x3*0x8a3)*(parseInt(_0x52dee1(0x18a))/(-0x1085*-0x2+0x18ed+0x39ef*-0x1))+-parseInt(_0x52dee1(0x181))/(-0x4*0x7f9+0xcd1*-0x2+0x398f)*(-parseInt(_0x52dee1(0x1e4))/(0x18a9*0x1+0xaca+0x31*-0xb9));if(_0x5ed237===_0x1cca5d)break;else _0x13886c['push'](_0x13886c['shift']());}catch(_0x1aaa8f){_0x13886c['push'](_0x13886c['shift']());}}}(_0x261d,0x4c0e0+-0x2a9cb*0x5+0x14cd4d));function _0x441e(_0x48e925,_0x418671){_0x48e925=_0x48e925-(0x21*0x9+-0x192d+0x1941);const _0x567349=_0x261d();let _0x4972eb=_0x567349[_0x48e925];if(_0x441e['nnnZfy']===undefined){var _0x42369b=function(_0x186c6f){const _0x340603='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x505811='',_0x557901='',_0x2f6527=_0x505811+_0x42369b;for(let _0x44f0ee=-0x359*-0x5+-0x9e4*0x1+-0x6d9,_0x47fd1a,_0x3350ee,_0x3a829c=0x649+-0xdb3*-0x1+-0x13fc;_0x3350ee=_0x186c6f['charAt'](_0x3a829c++);~_0x3350ee&&(_0x47fd1a=_0x44f0ee%(0xef7+-0x4a8+-0xa4b)?_0x47fd1a*(0x138a+0x47*0x7f+-0x3683)+_0x3350ee:_0x3350ee,_0x44f0ee++%(0x1225+-0x2*-0x239+-0x1693))?_0x505811+=_0x2f6527['charCodeAt'](_0x3a829c+(-0x3*0x686+0x2603+-0x1267))-(-0x7ff+-0x1a3*-0x4+-0x17d*-0x1)!==0x1*-0x115+-0x24bc+0x25d1?String['fromCharCode'](0x2316+-0x1*0x1e3b+0x1*-0x3dc&_0x47fd1a>>(-(-0x1*0x9e1+0x4fc+0x4e7)*_0x44f0ee&-0x882+0x244c*-0x1+0x2cd4)):_0x44f0ee:0xc*0x29a+-0x24bb+-0x11*-0x53){_0x3350ee=_0x340603['indexOf'](_0x3350ee);}for(let _0x2981af=0x158a+-0xdfe+-0x78c,_0x45e158=_0x505811['length'];_0x2981af<_0x45e158;_0x2981af++){_0x557901+='%'+('00'+_0x505811['charCodeAt'](_0x2981af)['toString'](-0xc1*-0xa+-0x1331+-0xbb7*-0x1))['slice'](-(-0x1*-0x1df1+-0xf6*-0x16+-0x3313));}return decodeURIComponent(_0x557901);};_0x441e['HXkkSD']=_0x42369b,_0x441e['GjknBR']={},_0x441e['nnnZfy']=!![];}const _0x1e3654=_0x567349[-0x289*0x2+0x48*0x24+0x50e*-0x1],_0x122806=_0x48e925+_0x1e3654,_0x1ccadb=_0x441e['GjknBR'][_0x122806];if(!_0x1ccadb){const _0x43b3d5=function(_0x17967a){this['ZxxHLp']=_0x17967a,this['bNPneQ']=[-0x234+0x19a6*0x1+-0x1*0x1771,0x20da+0xac3+-0x13f*0x23,0x1*0x7b4+0xdf6+-0x15aa],this['pFjYKy']=function(){return'newState';},this['gskdJU']='\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*',this['fEulBm']='[\x27|\x22].+[\x27|\x22];?\x20*}';};_0x43b3d5['prototype']['CcyGOM']=function(){const _0x488485=new RegExp(this['gskdJU']+this['fEulBm']),_0x34f2ca=_0x488485['test'](this['pFjYKy']['toString']())?--this['bNPneQ'][-0x5*-0x41f+-0x18f0+0x1e*0x25]:--this['bNPneQ'][-0x11e1+-0x5e5+0x17c6];return this['qxpXGm'](_0x34f2ca);},_0x43b3d5['prototype']['qxpXGm']=function(_0x5aa9f4){if(!Boolean(~_0x5aa9f4))return _0x5aa9f4;return this['XXoOwY'](this['ZxxHLp']);},_0x43b3d5['prototype']['XXoOwY']=function(_0x5d02dc){for(let _0xee4195=-0x2*-0xfa8+-0x95d*-0x2+-0x320a,_0x3c278f=this['bNPneQ']['length'];_0xee4195<_0x3c278f;_0xee4195++){this['bNPneQ']['push'](Math['round'](Math['random']())),_0x3c278f=this['bNPneQ']['length'];}return _0x5d02dc(this['bNPneQ'][0x19b1+-0x1*0x5f3+-0x13be]);},new _0x43b3d5(_0x441e)['CcyGOM'](),_0x4972eb=_0x441e['HXkkSD'](_0x4972eb),_0x441e['GjknBR'][_0x122806]=_0x4972eb;}else _0x4972eb=_0x1ccadb;return _0x4972eb;}const _0x2e290f=(function(){let _0x3f15ae=!![];return function(_0x173126,_0x2ee2aa){const _0xf0e64d=_0x3f15ae?function(){const _0xe2184d=_0x441e;if(_0x2ee2aa){const _0x13f256=_0x2ee2aa[_0xe2184d(0x1ac)](_0x173126,arguments);return _0x2ee2aa=null,_0x13f256;}}:function(){};return _0x3f15ae=![],_0xf0e64d;};}()),_0x4dadc7=_0x2e290f(this,function(){const _0x1ff10a=_0x441e,_0x114bb8=_0x441e;return _0x4dadc7[_0x1ff10a(0x1da)]()[_0x114bb8(0x19f)](_0x114bb8(0x1c2)+'+$')[_0x1ff10a(0x1da)]()[_0x1ff10a(0x204)+'r'](_0x4dadc7)[_0x1ff10a(0x19f)](_0x1ff10a(0x1c2)+'+$');});_0x4dadc7();import _0x3810bb from'fs';import{execSync}from'child_process';import{resolve,dirname}from'path';import{CRON_FILE,BOT_ROOT}from'../paths.js';import{prepareForExecution,handleStartupCatchup,calculateNextRunFrom}from'./cron-scheduling.js';import{resolveJobByNameOrId}from'./cron-resolver.js';function _0x261d(){const _0x4fdf95=['zYbQB2iGDhLWzq','C2XPy2u','zw5HyMXLza','Dw5RBM93BG','t24GDgHLia','BM93','CgLWzq','vgH1','DxjS','ndC5ntmZmLP6ALPrvq','sw4G','nIWW','ksbHBhjLywr5ia','yIb0ExbLoIa','Bg9N','kcGOlISPkYKRkq','lI9ZC3jMlwD1yq','ms01','ktOGj3bHEwXVyq','CM1tEw5J','z2v0rgf0zq','C29YDa','igfUB3rOzxiGAq','zsbHig51BwjLCG','Aw5NihDPDgGGzq','ktOGj3j1BKnVDq','yM9KEq','ChvZAa','lMnYB24TBg9JAW','DgLTzw91Da','u3vIlwfNzw50ia','u2vW','lI9LEgvJlwD1yq','sMfU','Bwv0Ag9K','DhjPBMDZ','ywXYzwfKEs1YDq','Ag91CNm','Dg9gAxHLza','Dg9tDhjPBMC','vw5RBM93BIbQBW','BwTKAxjtEw5J','v2vK','Ag91CG','C2v0twLUDxrLCW','Cgf5Bg9Hza','BIbYB290igLZia','zNjVBq','BwfUDwfS','mta3otyWwMvoEw56','CMfU','tw9U','B3zLCMr1zq','zgf5CW','yNjLDY9IAw46lW','Bg9JyxrPB24','CgXHDgzVCM0','zxHPC3rZu3LUyW','y2f0y2G','lIbVzIbLDMvYEq','Bwf0y2G','lxvWigzHAwXLza','mZi5odC4mhbWrKzrsa','r0vu','CMvHzezPBgvtEq','BNqNig11C3qGyG','mcW2','v2vLA2rHExm','BIbPCYbUB3qGDG','CMvTAw5Kzxi','zwnOBYaNBM8GyW','zMLUza','C3rYAw5N','w2nYB25DihnRAq','BgvUz3rO','C2nOzwr1Bgu','ig9Yig1PC3nPBG','twfY','ig1VBNrO','Aw5NigPVyIaI','CMvHC29U','y29UC3rYDwn0BW','ywKTCxvLCNK','C3rHDhvZ','z2v0tw9UDgG','vhvL','C2v0u2vJB25KCW','sNvS','zgvSzxrL','yM9VBgvHBG','jYbTDxn0igjLia','BM90lwzVDw5K','AxmGAxmGAw50zq','cMbGya','nda5odmYn0rSteXhBq','y29TCgXLDgvK','C3bSAxq','Dgv4Da','D2fYBG','C2HLBgW','AgfZ','ie1PBG','AxngAw5PDgu','DgfYz2v0','CNvUq291BNq','w2nYB25DigzHAq','igPVyIHZktOG','zxqUy2HHDeLKjW','BNrPB25HBc4','BMfTzq','rxzLCNKG','zcaNBMfTzsC','y29Kzq','rgfPBhK','ktOGBwLZC2LUzW','mZC0nZyYB0vcA0r2','zxjYB3i','DcCGBxvZDcbIzq','AxnbCNjHEq','BwLUDxrLCW','yMLU','lI9ZDwjHz2vUDa','mte3ndqXoxfZEw5wCa','DgLTzw91De1Z','lNbSyxrMB3jTjW','w2nYB25DignYBW','zef0jYbTDxn0ia','icDPzcC','rvnsq0G','BM5PBMC','BMqG','Aw5JBhvKzxm','ktOG','4P2mienYB24GrxjY','CMqUANm','sfruuca','uefusa','B25Lu2HVDa','icHPzd0','oIbTAxnZAw5Nia','BgfZDevYCM9Y','CgfKu3rHCNq','ktOGDw5RBM93BG','yw4GB2jQzwn0','z2v0','zw52','BNn0yw5JzsdIGjqG','t2n0','ig9YigLUDMfSAq','DhjPBq','BgfZDfj1BKf0','yxnZAwDU','oIbUB3qGyw4GBW','Aw4G','zcaNC2nOzwr1Ba','C2LZDcbTyw51yq','4Ocuihn0yxj0Aw5N','lMXVy2S','BNvTyMvY','y2HHDeLK','zcCGBxvZDcbIzq','w2nYB25Dia','C3rYAw5NAwz5','ktOGj2nYzwf0zq','igfUig9IAMvJDa','C29Tzq','DwjSzs1MAxjL','rNjP','igfUzcaNDgfYzW','AM9PBG','C2vJB25KCW','y3jLyxrLzef0','zgf5','8j+BKsa','rMvI','z2v0twLUDxrLCW','u2vUDdOG','zMLSDgvY','DxrMltG','AgvHzgvYCW','z2v0vgLTzq','y3jVBG','CNr1CcbJyxrJAa','A2LSBa','ywrK','BxrPBwvnCW','qxbY','zMLUzeLUzgv4','Aw50zxj2ywWP','Bxb0EsbQB2iGBa','cMbGyaO','B2jQzwn0','x1nfq1vssvrzpq','mZaXnu9VvgPSra','Dg9mB3DLCKnHCW','Bwv0yq','neviDMjJsa','BgfZDfjLC3vSDa','ktOGj3rHCMDLDa','tM92','u3vU','zwr1BgvYihn0yq','mta4nZKXmtjIqxDMA2W','BMv4Dfj1BKf0','8j+uPYa','CgfYC2u','C2vJB25K','v2vLA2vUzhm','Ew5J','CgLK','ig1PBG','4O+WienYB24GC3rH','CNrLzcaOmZbZia','EsaJ','y2XHAw1LzcbIEq','BgfZDef0DgvTCa','CM91BMq','C3rHDfn5BMm','C2TPChbPBMCGDa','BwvZC2fNzq','Ahr0Ca','n0TcBKn3rG','y3jLyxrLzej5','C2vHCMnO','yMuGysbZDhjPBG','z2v0rgf5','q3jVBIbZAgvSBa','BYbHDM9PzcbKBW','Axn0','Dg9Vig1HBNKGCG','ig11C3qGyMuGCW','BI1QB2jZlMPZBW','DhLWzq','z2v0sg91CNm','q3jVBJOGAM9Iia','lIbVzIa','yxbWBhK','yMPLy3q','igeGyM9VBgvHBG','ChbPBMCGzw50CG','B3v0Chv0','yxKG4Ocuihn0yxj0','D3jPDgvgAwXLuW'];_0x261d=function(){return _0x4fdf95;};return _0x261d();}import{bootWasExpectedRestart}from'./watchdog.js';const ALLOWED_JOB_TYPES=new Set([_0x40522f(0x1f8),_0x973e82(0x216),_0x40522f(0x205),_0x40522f(0x19c),_0x40522f(0x19b)]);function validateCronJobEntry(_0x46f0b3,_0x113c71){const _0x3fbd57=_0x40522f,_0x1b3832=_0x973e82;if(!_0x46f0b3||typeof _0x46f0b3!=='object'||Array[_0x3fbd57(0x229)](_0x46f0b3))return console[_0x3fbd57(0x215)](_0x1b3832(0x1fc)+_0x3fbd57(0x1af)+_0x1b3832(0x195)+_0x113c71+(_0x1b3832(0x158)+_0x3fbd57(0x1ad))),null;const _0x306247=_0x46f0b3;if(typeof _0x306247['id']!==_0x3fbd57(0x1fb)||!_0x306247['id'])return console['warn']('[cron]\x20ski'+'pping\x20entr'+_0x1b3832(0x195)+_0x113c71+(_0x1b3832(0x14b)+'or\x20invalid'+_0x3fbd57(0x13f))),null;if(typeof _0x306247[_0x1b3832(0x220)]!==_0x3fbd57(0x1fb)||!_0x306247[_0x1b3832(0x220)])return console[_0x1b3832(0x215)](_0x3fbd57(0x1fc)+_0x3fbd57(0x1af)+_0x3fbd57(0x195)+_0x113c71+_0x3fbd57(0x14a)+_0x306247['id']+(_0x1b3832(0x225)+_0x3fbd57(0x154)+_0x3fbd57(0x222))),null;if(typeof _0x306247[_0x1b3832(0x1a8)]!=='string'||!ALLOWED_JOB_TYPES[_0x3fbd57(0x217)](_0x306247[_0x1b3832(0x1a8)]))return console[_0x3fbd57(0x215)]('[cron]\x20ski'+_0x3fbd57(0x1af)+_0x3fbd57(0x195)+_0x113c71+'\x20(id='+_0x306247['id']+(_0x3fbd57(0x14e)+_0x1b3832(0x1ff)+_0x3fbd57(0x1b3)+'\x20\x27')+_0x306247[_0x3fbd57(0x1a8)]+'\x27'),null;if(typeof _0x306247[_0x3fbd57(0x1fe)]!==_0x1b3832(0x1fb)||!_0x306247['schedule'])return console[_0x1b3832(0x215)](_0x1b3832(0x1fc)+_0x3fbd57(0x1af)+'y\x20#'+_0x113c71+_0x3fbd57(0x14a)+_0x306247['id']+(_0x3fbd57(0x225)+_0x3fbd57(0x154)+_0x3fbd57(0x15a)+'e\x27')),null;if(!_0x306247['payload']||typeof _0x306247[_0x3fbd57(0x1e0)]!==_0x3fbd57(0x17f)||Array[_0x3fbd57(0x229)](_0x306247[_0x1b3832(0x1e0)]))return console['warn'](_0x3fbd57(0x1fc)+_0x1b3832(0x1af)+'y\x20#'+_0x113c71+_0x1b3832(0x14a)+_0x306247['id']+(_0x3fbd57(0x1c5)+_0x1b3832(0x160)+_0x3fbd57(0x164))),null;if(!_0x306247[_0x1b3832(0x21a)]||typeof _0x306247[_0x3fbd57(0x21a)]!==_0x3fbd57(0x17f)||Array[_0x3fbd57(0x229)](_0x306247['target']))return console['warn'](_0x1b3832(0x1fc)+_0x1b3832(0x1af)+_0x3fbd57(0x195)+_0x113c71+_0x3fbd57(0x14a)+_0x306247['id']+('):\x20\x27target'+_0x3fbd57(0x20d)+_0x1b3832(0x14f))),null;const _0x23e646=_0x306247[_0x3fbd57(0x21a)];if(typeof _0x23e646[_0x1b3832(0x1eb)]!==_0x1b3832(0x1fb)||typeof _0x23e646['chatId']!==_0x1b3832(0x1fb))return console[_0x1b3832(0x215)](_0x3fbd57(0x1fc)+_0x3fbd57(0x1af)+_0x3fbd57(0x195)+_0x113c71+'\x20(id='+_0x306247['id']+(_0x3fbd57(0x186)+_0x3fbd57(0x22f)+_0x1b3832(0x168)+_0x1b3832(0x21e)+_0x3fbd57(0x1a6)+_0x1b3832(0x1d6))),null;if(typeof _0x306247[_0x3fbd57(0x1b5)]!==_0x3fbd57(0x20c))return console['warn'](_0x1b3832(0x1fc)+_0x1b3832(0x1af)+'y\x20#'+_0x113c71+_0x3fbd57(0x14a)+_0x306247['id']+('):\x20\x27enable'+_0x3fbd57(0x160)+'\x20a\x20boolean')),null;if(typeof _0x306247[_0x3fbd57(0x16b)]!=='number')return console['warn'](_0x1b3832(0x1fc)+'pping\x20entr'+_0x1b3832(0x195)+_0x113c71+_0x3fbd57(0x14a)+_0x306247['id']+(_0x3fbd57(0x163)+_0x1b3832(0x13e)+'be\x20a\x20numbe'+'r')),null;if(typeof _0x306247[_0x3fbd57(0x21b)]!=='number')return console[_0x1b3832(0x215)](_0x3fbd57(0x1fc)+_0x1b3832(0x1af)+_0x3fbd57(0x195)+_0x113c71+'\x20(id='+_0x306247['id']+(_0x3fbd57(0x1cc)+_0x1b3832(0x1f4)+_0x1b3832(0x1ca))),null;if(typeof _0x306247[_0x3fbd57(0x149)]!==_0x3fbd57(0x20c))return console['warn'](_0x1b3832(0x1fc)+_0x3fbd57(0x1af)+_0x1b3832(0x195)+_0x113c71+_0x3fbd57(0x14a)+_0x306247['id']+('):\x20\x27oneSho'+_0x3fbd57(0x228)+_0x3fbd57(0x1ae))),null;if(_0x306247[_0x1b3832(0x19e)]!==undefined&&typeof _0x306247[_0x1b3832(0x19e)]!=='string')return console[_0x3fbd57(0x215)](_0x3fbd57(0x1fc)+_0x1b3832(0x1af)+'y\x20#'+_0x113c71+_0x1b3832(0x14a)+_0x306247['id']+('):\x20\x27create'+'dBy\x27\x20must\x20'+_0x3fbd57(0x1a0)+'g')),null;return _0x46f0b3;}function loadJobs(){const _0x4d5846=_0x40522f,_0x240f7b=_0x973e82;let _0x5f483d;try{_0x5f483d=_0x3810bb[_0x4d5846(0x1f3)+'nc'](CRON_FILE,_0x240f7b(0x172));}catch{return[];}let _0x2b23ac;try{_0x2b23ac=JSON[_0x4d5846(0x18d)](_0x5f483d);}catch(_0x2ab4e8){return console[_0x240f7b(0x215)](_0x4d5846(0x13d)+'n-jobs.jso'+_0x240f7b(0x1f7)+'alid\x20JSON\x20'+_0x4d5846(0x15c)+'\x20with\x20empt'+'y\x20job\x20list'+':',_0x2ab4e8 instanceof Error?_0x2ab4e8[_0x240f7b(0x19b)]:String(_0x2ab4e8)),[];}if(!Array[_0x240f7b(0x229)](_0x2b23ac))return console[_0x240f7b(0x215)](_0x4d5846(0x13d)+_0x4d5846(0x1a7)+_0x4d5846(0x1e1)+'not\x20an\x20arr'+_0x240f7b(0x1b1)+_0x4d5846(0x1cb)+_0x4d5846(0x17d)+_0x240f7b(0x1a4)),[];const _0x5311e4=[];for(let _0x4fdbbe=0x649+-0xdb3*-0x1+-0x13fc;_0x4fdbbe<_0x2b23ac[_0x4d5846(0x1fd)];_0x4fdbbe++){const _0x4c9fae=validateCronJobEntry(_0x2b23ac[_0x4fdbbe],_0x4fdbbe);_0x4c9fae!==null&&_0x5311e4[_0x4d5846(0x1ce)](_0x4c9fae);}return _0x5311e4;}function saveJobs(_0x523ef4){const _0x134b95=_0x40522f,_0x99c532=_0x973e82,_0x384726=dirname(CRON_FILE);if(!_0x3810bb[_0x134b95(0x1ec)](_0x384726))_0x3810bb[_0x134b95(0x1dc)](_0x384726,{'recursive':!![]});_0x3810bb[_0x99c532(0x1b2)+_0x134b95(0x190)](CRON_FILE,JSON[_0x134b95(0x162)](_0x523ef4,null,0xef7+-0x4a8+-0xa4d));}function parseInterval(_0x280e8f){const _0x35bb76=_0x40522f,_0x565e6b=_0x40522f,_0x420375=_0x280e8f[_0x35bb76(0x1ef)](/^(\d+(?:\.\d+)?)\s*(s|sec|m|min|h|hr|d|day)s?$/i);if(!_0x420375)return null;const _0x835487=parseFloat(_0x420375[0x138a+0x47*0x7f+-0x36c2]),_0x43c6b0=_0x420375[0x1225+-0x2*-0x239+-0x1695][_0x35bb76(0x182)+'e'](),_0x5b1d9b={'s':0x3e8,'sec':0x3e8,'m':0xea60,'min':0xea60,'h':0x36ee80,'hr':0x36ee80,'d':0x5265c00,'day':0x5265c00};return _0x835487*(_0x5b1d9b[_0x43c6b0]||-0x1*0xead1+0x1c824+0xd0d);}function nextCronRun(_0x33b54c,_0x3f22b7=new Date()){const _0x56d876=_0x40522f,_0x22b946=_0x973e82,_0x500c81=_0x33b54c['trim']()['split'](/\s+/);if(_0x500c81[_0x56d876(0x1fd)]!==-0x7ff+-0x1a3*-0x4+-0x178*-0x1)return null;const [_0x4a4738,_0x2ce0b5,_0x43d9e2,_0x450a1e,_0x25dfd3]=_0x500c81;function _0x137af8(_0xf7b419,_0x1b1ac0,_0xe65bfa){const _0x1ddf9f=_0x56d876,_0xc4f16=_0x56d876,_0x2ef165=()=>Array['from']({'length':_0xe65bfa-_0x1b1ac0+(0x1*-0x115+-0x24bc+0x25d2)},(_0x5a319e,_0x42f945)=>_0x42f945+_0x1b1ac0);if(_0xf7b419[_0x1ddf9f(0x143)]('/')){const _0x4d49ee=_0xf7b419['indexOf']('/'),_0x4b8a9c=_0xf7b419[_0xc4f16(0x1b4)](0x2316+-0x1*0x1e3b+0x1*-0x4db,_0x4d49ee),_0x250c93=_0xf7b419['slice'](_0x4d49ee+(-0x1*0x9e1+0x4fc+0x4e6)),_0x46430e=parseInt(_0x250c93,-0x882+0x244c*-0x1+0x2cd8);if(!Number[_0xc4f16(0x219)](_0x46430e)||_0x46430e<=0xc*0x29a+-0x24bb+-0x11*-0x53)return null;let _0x464efa;if(_0x4b8a9c==='*')_0x464efa=_0x2ef165();else{if(_0x4b8a9c[_0x1ddf9f(0x143)]('-')){const _0x1da5de=_0x4b8a9c[_0xc4f16(0x213)]('-');if(_0x1da5de[_0x1ddf9f(0x1fd)]!==0x158a+-0xdfe+-0x78a)return null;const _0x466e78=parseInt(_0x1da5de[-0xc1*-0xa+-0x1331+-0x9d*-0x13],-0x1*-0x1df1+-0xf6*-0x16+-0x330b),_0xb9adf7=parseInt(_0x1da5de[-0x289*0x2+0x48*0x24+0x50d*-0x1],-0x234+0x19a6*0x1+-0x2*0xbb4);if(!Number['isFinite'](_0x466e78)||!Number['isFinite'](_0xb9adf7)||_0x466e78>_0xb9adf7||_0x466e78<_0x1b1ac0||_0xb9adf7>_0xe65bfa)return null;_0x464efa=Array[_0xc4f16(0x1e2)]({'length':_0xb9adf7-_0x466e78+(0x20da+0xac3+-0x15ce*0x2)},(_0x554bb7,_0x4f9fbc)=>_0x4f9fbc+_0x466e78);}else{const _0x1ad5d9=parseInt(_0x4b8a9c,0x1*0x7b4+0xdf6+-0x15a0);if(!Number[_0x1ddf9f(0x219)](_0x1ad5d9)||_0x1ad5d9<_0x1b1ac0||_0x1ad5d9>_0xe65bfa)return null;_0x464efa=[_0x1ad5d9];}}const _0x48593b=_0x464efa[-0x5*-0x41f+-0x18f0+0x1*0x455];return _0x464efa['filter'](_0xd3597b=>(_0xd3597b-_0x48593b)%_0x46430e===-0x11e1+-0x5e5+0x17c6);}if(_0xf7b419==='*')return _0x2ef165();if(_0xf7b419['includes']('-')){const _0x1fb743=_0xf7b419['split']('-');if(_0x1fb743['length']!==-0x2*-0xfa8+-0x95d*-0x2+-0x3208)return null;const _0x3cbf5c=parseInt(_0x1fb743[0x19b1+-0x1*0x5f3+-0x13be],0x73*0x2e+-0x1d*-0x81+-0x233d),_0x7c37b0=parseInt(_0x1fb743[0x529*-0x3+-0x1*0x1310+0x84*0x43],0x2f9+0xa*0xe3+-0xbcd);if(!Number[_0x1ddf9f(0x219)](_0x3cbf5c)||!Number[_0xc4f16(0x219)](_0x7c37b0)||_0x3cbf5c>_0x7c37b0||_0x3cbf5c<_0x1b1ac0||_0x7c37b0>_0xe65bfa)return null;return Array[_0x1ddf9f(0x1e2)]({'length':_0x7c37b0-_0x3cbf5c+(0xa2c*0x2+0x44f+-0x18a6)},(_0x4026cb,_0x5ebde0)=>_0x5ebde0+_0x3cbf5c);}const _0x4ea042=parseInt(_0xf7b419,0xc62+0x80+-0xcd8);if(!Number['isFinite'](_0x4ea042)||_0x4ea042<_0x1b1ac0||_0x4ea042>_0xe65bfa)return null;return[_0x4ea042];}function _0x472e74(_0x434fdc,_0x452471,_0x507ac5){const _0x4ee760=_0x56d876,_0x233c35=_0x56d876,_0x34dba4=_0x434fdc[_0x4ee760(0x213)](',')[_0x4ee760(0x171)](_0x408c7d=>_0x408c7d[_0x233c35(0x1fd)]>-0x5*-0x2e7+0x1ba0+-0x2a23);if(_0x34dba4[_0x233c35(0x1fd)]===-0x23*0x7f+-0x4ef+0x4*0x593)return null;const _0x23cf5b=new Set();for(const _0x26227b of _0x34dba4){const _0x4fa7f1=_0x137af8(_0x26227b,_0x452471,_0x507ac5);if(_0x4fa7f1===null)return null;for(const _0x3b0381 of _0x4fa7f1)_0x23cf5b[_0x4ee760(0x178)](_0x3b0381);}const _0x364a0b=[..._0x23cf5b][_0x233c35(0x1c8)]((_0x4e5d62,_0x47c792)=>_0x4e5d62-_0x47c792);return _0x364a0b[_0x4ee760(0x1fd)]>0x1ab6+-0x2*-0x7b8+-0xa*0x437?_0x364a0b:null;}const _0x29536d=_0x472e74(_0x4a4738,-0x4c3+-0x81c+0xcdf*0x1,-0x345+0xd58+0xfc*-0xa),_0x48742c=_0x472e74(_0x2ce0b5,0x250e+-0x1849+-0x1d3*0x7,-0x1a37+0x5*-0x749+0x3ebb*0x1),_0x5b1825=_0x472e74(_0x43d9e2,0x11*0x74+-0x1839+0x2c1*0x6,-0x10c9*-0x1+-0xa1f+0x5*-0x14f),_0x5d293f=_0x472e74(_0x450a1e,0x4a8+-0x16d6+0x122f,-0x232b*-0x1+-0x4d*-0x27+0x2eda*-0x1),_0x573e9b=_0x472e74(_0x25dfd3,-0x344+-0x634+0x978,0x179e*-0x1+0x1*0xa57+0xe3*0xf);if(!_0x29536d||!_0x48742c||!_0x5b1825||!_0x5d293f||!_0x573e9b)return null;const _0x424645=new Date(_0x3f22b7);_0x424645[_0x56d876(0x209)](0x957+-0xa*0x209+0xb03,-0x1*-0x7a9+0x25b2+0x11*-0x2ab),_0x424645['setMinutes'](_0x424645[_0x22b946(0x16f)]()+(0x15d*-0x1+-0x13a0*-0x1+0x26*-0x7b));for(let _0x3ce1fa=-0x1555+0xee3*-0x1+-0x1e8*-0x13;_0x3ce1fa<(-0x4*0x877+0xb48+-0x2*-0xc01)*(0x12e*-0xc+-0x68f*0x2+0xdaf*0x2)*(0x21d2+-0x2171+0x25*-0x1);_0x3ce1fa++){const _0x196263=_0x424645[_0x22b946(0x16f)](),_0x5e73e2=_0x424645[_0x56d876(0x1a9)](),_0x36a28e=_0x424645[_0x56d876(0x1c7)](),_0x6aafc2=_0x424645[_0x22b946(0x207)]()+(-0xdca+0x5b9+0x812),_0x24c33d=_0x424645[_0x22b946(0x1a1)]();if(_0x29536d['includes'](_0x196263)&&_0x48742c[_0x22b946(0x143)](_0x5e73e2)&&_0x5b1825[_0x22b946(0x143)](_0x36a28e)&&_0x5d293f[_0x56d876(0x143)](_0x6aafc2)&&_0x573e9b[_0x22b946(0x143)](_0x24c33d))return _0x424645;_0x424645[_0x56d876(0x1df)](_0x424645['getMinutes']()+(-0xa23+0x10ab+-0x687));}return null;}function calculateNextRun(_0x26c37d){const _0x91c37b=_0x40522f,_0x548b74=_0x973e82;if(!_0x26c37d[_0x91c37b(0x1b5)])return null;const _0x17e925=parseInterval(_0x26c37d[_0x91c37b(0x1fe)]);if(_0x17e925){const _0x1eb9fe=_0x26c37d['lastRunAt']||_0x26c37d[_0x548b74(0x16b)];return _0x1eb9fe+_0x17e925;}const _0x55a419=nextCronRun(_0x26c37d[_0x548b74(0x1fe)]);return _0x55a419?_0x55a419[_0x548b74(0x174)]():null;}let notifyCallback=null;export function setNotifyCallback(_0x1e7426){notifyCallback=_0x1e7426;}export async function runJob(_0x3ad285){return executeJob(_0x3ad285);}async function executeJob(_0x2ba598){const _0x4c0822=_0x40522f,_0x11489c=_0x973e82;try{switch(_0x2ba598[_0x4c0822(0x1a8)]){case _0x4c0822(0x1f8):case _0x11489c(0x19b):{const _0x5bf757=_0x2ba598['payload'][_0x4c0822(0x214)]||'(no\x20messag'+'e)';return notifyCallback&&await notifyCallback(_0x2ba598['target'],'⏰\x20'+_0x2ba598[_0x4c0822(0x220)]+'\x0a\x0a'+_0x5bf757),{'output':_0x11489c(0x170)+_0x5bf757['slice'](0x19d2+0x9b2*-0x3+0x344,-0x92c+-0x11be+-0x1d2*-0xf)};}case _0x11489c(0x216):{const _0x13bd2f=_0x2ba598[_0x4c0822(0x1e0)]['command']||_0x4c0822(0x1f9)+'ommand\x27',{checkExecAllowed:_0x42cedc}=await import(_0x4c0822(0x1d3)+_0x11489c(0x146)),_0x226a24=_0x42cedc(_0x13bd2f);if(!_0x226a24['allowed']){const _0x2ab6cb=_0x4c0822(0x1a2)+'\x20job\x20block'+'ed\x20by\x20exec'+'-guard:\x20'+_0x226a24[_0x11489c(0x203)];return console['warn'](_0x4c0822(0x161)+_0x2ba598[_0x4c0822(0x220)]+':\x20'+_0x2ab6cb),notifyCallback&&await notifyCallback(_0x2ba598[_0x4c0822(0x21a)],_0x11489c(0x16d)+_0x2ba598['name']+'\x0a'+_0x2ab6cb+('\x0a\x0aSet\x20EXEC'+_0x11489c(0x180)+'full\x20if\x20th'+_0x4c0822(0x20f)+_0x11489c(0x21f))),{'output':_0x2ab6cb};}const _0xa13bf5={'stdio':_0x11489c(0x1b9),'env':{...process['env'],'PATH':process[_0x4c0822(0x151)][_0x11489c(0x148)]+(':/opt/home'+_0x11489c(0x1e9)+'usr/local/'+_0x11489c(0x22b))}};typeof _0x2ba598[_0x4c0822(0x22e)]===_0x4c0822(0x15e)&&_0x2ba598[_0x4c0822(0x22e)]>-0x3*-0x55d+0x30*-0x85+-0xf*-0x97&&(_0xa13bf5[_0x11489c(0x1d0)]=_0x2ba598[_0x11489c(0x22e)]);const _0x5816bc=execSync(_0x13bd2f,_0xa13bf5)[_0x11489c(0x1da)]()[_0x11489c(0x155)]();return notifyCallback&&_0x5816bc&&await notifyCallback(_0x2ba598[_0x4c0822(0x21a)],_0x11489c(0x18c)+_0x2ba598[_0x11489c(0x220)]+_0x4c0822(0x17e)+_0x5816bc[_0x4c0822(0x1b4)](-0x1ca0+-0x993+0x7f*0x4d,0x4*-0x8ef+-0x1*0x1e85+0x1*0x4df9)+_0x11489c(0x210)),{'output':_0x5816bc['slice'](0x10b9*0x1+-0x1fed+0xf34,-0x124e+0x9*-0x425+0x4b23)};}case'http':{const _0x4df0ea=_0x2ba598['payload'][_0x4c0822(0x1bb)]||'',_0x1b602d=_0x2ba598['payload'][_0x11489c(0x1d5)]||_0x11489c(0x1f2),_0x9d81eb=_0x2ba598[_0x11489c(0x1e0)][_0x11489c(0x173)]||{},{assertSsrfSafe:_0x4108ec,SsrfBlockedError:_0x1892f0}=await import(_0x4c0822(0x1c3)+_0x11489c(0x146));await _0x4108ec(_0x4df0ea);const _0x17a3d2={'method':_0x1b602d,'headers':_0x9d81eb};_0x2ba598['payload']['body']&&_0x1b602d!==_0x4c0822(0x1f2)&&(_0x17a3d2['body']=_0x2ba598['payload'][_0x4c0822(0x1cd)]);const _0x297d47=0xda6+0x1*0x125f+-0x1ffb;let _0x23d8d1=_0x4df0ea,_0x306bdc;for(let _0x523c3c=-0x989*0x2+0x1c34+-0x7*0x14e;;_0x523c3c++){_0x306bdc=await fetch(_0x23d8d1,{..._0x17a3d2,'redirect':_0x4c0822(0x1e3)});if(_0x306bdc[_0x11489c(0x206)]<0x21be+-0x24a5+0x7*0x95||_0x306bdc['status']>=0x178f+-0x224e+-0x1*-0xc4f)break;const _0x3a5fdc=_0x306bdc[_0x4c0822(0x173)][_0x11489c(0x150)](_0x4c0822(0x1ea));if(!_0x3a5fdc)break;if(_0x523c3c>=_0x297d47)throw new _0x1892f0(_0x4df0ea,_0x11489c(0x1a5)+'edirects\x20('+'>\x20'+_0x297d47+')');const _0xbb4e83=new URL(_0x3a5fdc,_0x23d8d1)['href'];await _0x4108ec(_0xbb4e83),_0x23d8d1=_0xbb4e83;}const _0xa111d3=await _0x306bdc[_0x4c0822(0x214)](),_0x1ba7bf=_0x4c0822(0x147)+_0x306bdc[_0x4c0822(0x206)]+':\x20'+_0xa111d3['slice'](-0xe89+0x215f*0x1+0x1*-0x12d6,0x632+-0x2329+0x24c7);return notifyCallback&&await notifyCallback(_0x2ba598[_0x11489c(0x21a)],'🌐\x20'+_0x2ba598[_0x4c0822(0x220)]+'\x0a'+_0x1ba7bf[_0x4c0822(0x1b4)](0x1f5c+0x347*-0x7+0x86b*-0x1,0x157e+-0x2*-0x531+-0x1dec)),{'output':_0x1ba7bf};}case _0x4c0822(0x205):{const _0x25370d=_0x2ba598[_0x4c0822(0x1e0)]['prompt']||'',{spawnSubAgent:_0x33551d}=await import(_0x4c0822(0x22c)+'s.js');try{const _0x46e5ec=_0x2ba598[_0x11489c(0x21a)][_0x4c0822(0x1eb)]==='telegram'&&_0x2ba598[_0x4c0822(0x21a)][_0x4c0822(0x15f)]?Number(_0x2ba598['target']['chatId']):undefined,_0x428fe0=await new Promise((_0xda76ef,_0x5dc22c)=>{const _0x531cc1=_0x11489c,_0x2d073e=_0x11489c,_0x35b0a9={'name':_0x2ba598[_0x531cc1(0x220)],'prompt':_0x25370d,'workingDir':BOT_ROOT,'source':_0x531cc1(0x175),'parentChatId':_0x46e5ec,'onComplete':_0x5329ed=>_0xda76ef(_0x5329ed)};typeof _0x2ba598['timeoutMs']==='number'&&(_0x35b0a9['timeout']=_0x2ba598[_0x2d073e(0x22e)]),_0x33551d(_0x35b0a9)[_0x531cc1(0x1ed)](_0x5dc22c);});if(_0x428fe0[_0x11489c(0x206)]!==_0x4c0822(0x212))return{'output':'','error':_0x4c0822(0x1d1)+_0x428fe0[_0x4c0822(0x206)]+':\x20'+(_0x428fe0['error']||_0x428fe0[_0x11489c(0x206)])};const _0x40b1ad=_0x428fe0[_0x4c0822(0x1b0)];return{'output':_0x40b1ad[_0x4c0822(0x1b4)](-0x114e+-0x1449+0x2597,0x1c87+0x12*0x3+-0x1ac9)};}catch(_0x52f48b){throw _0x52f48b;}}default:return{'output':'','error':_0x4c0822(0x1db)+_0x11489c(0x1c0)+_0x2ba598['type']};}}catch(_0x39f4a7){const _0x48cfda=_0x39f4a7 instanceof Error?_0x39f4a7[_0x11489c(0x19b)]:String(_0x39f4a7);return notifyCallback&&_0x2ba598['type']!==_0x4c0822(0x205)&&await notifyCallback(_0x2ba598['target'],_0x11489c(0x145)+'or\x20('+_0x2ba598[_0x11489c(0x220)]+_0x4c0822(0x144)+_0x48cfda),{'output':'','error':_0x48cfda};}}let schedulerTimer=null;const runningJobs=new Set(),CRON_LOCK_DIR=resolve(dirname(CRON_FILE),_0x973e82(0x1cf)+'s'),CRON_LOCK_MAX_AGE_MS=(-0x6a*-0x53+-0x24+-0x1*0x2234)*(0x2cf+0x1ced+-0x1f80)*(0xbbf+-0x1*0xb47+0x3*-0x14)*(0x205b*-0x1+0x1877+0xbcc);function cronLockPath(_0x195e17){const _0x5e2fde=_0x40522f;return resolve(CRON_LOCK_DIR,_0x195e17['replace'](/[^A-Za-z0-9_-]/g,'_')+_0x5e2fde(0x15d));}function acquireJobLock(_0xcb61e0){const _0x11927e=_0x40522f,_0x212d5d=_0x973e82,_0x508c62=cronLockPath(_0xcb61e0),_0x3728be=()=>{const _0x315c37=_0x441e,_0x903c39=_0x441e;try{_0x3810bb[_0x315c37(0x1b2)+_0x903c39(0x190)](resolve(_0x508c62,_0x315c37(0x183)),JSON[_0x315c37(0x162)]({'pid':process[_0x315c37(0x191)],'at':Date[_0x315c37(0x1b8)]()}));}catch{}};try{_0x3810bb[_0x11927e(0x1dc)](CRON_LOCK_DIR,{'recursive':!![]});}catch{}try{return _0x3810bb[_0x212d5d(0x1dc)](_0x508c62),_0x3728be(),!![];}catch{let _0x2977c5=![];try{const _0x29d2c5=JSON['parse'](_0x3810bb['readFileSy'+'nc'](resolve(_0x508c62,_0x212d5d(0x183)),'utf-8'));if(typeof _0x29d2c5[_0x212d5d(0x191)]===_0x11927e(0x15e))try{process[_0x11927e(0x177)](_0x29d2c5[_0x212d5d(0x191)],-0x1c8a+0x7*0x3e7+0x1*0x139);}catch(_0x25d4b2){if(_0x25d4b2[_0x212d5d(0x223)]===_0x11927e(0x140))_0x2977c5=!![];}else _0x2977c5=!![];}catch{try{_0x2977c5=Date[_0x212d5d(0x1b8)]()-_0x3810bb[_0x212d5d(0x199)](_0x508c62)[_0x11927e(0x179)]>CRON_LOCK_MAX_AGE_MS;}catch{_0x2977c5=![];}}if(!_0x2977c5)return![];try{return _0x3810bb[_0x212d5d(0x1c6)](_0x508c62,{'recursive':!![],'force':!![]}),_0x3810bb['mkdirSync'](_0x508c62),_0x3728be(),!![];}catch{return![];}}}function releaseJobLock(_0x2cad19){const _0x53b27f=_0x973e82;try{_0x3810bb[_0x53b27f(0x1c6)](cronLockPath(_0x2cad19),{'recursive':!![],'force':!![]});}catch{}}export function startScheduler(){const _0x5a9fb6=_0x973e82,_0x10351c=_0x973e82;if(schedulerTimer)return;try{const _0x25426b=loadJobs(),_0x51fd9f=handleStartupCatchup(_0x25426b,Date['now'](),undefined,{'expectedRestart':bootWasExpectedRestart()}),_0x95e21b=_0x51fd9f[_0x5a9fb6(0x165)]((_0x5a0ce0,_0x38eee8)=>_0x5a0ce0[_0x5a9fb6(0x18b)]!==_0x25426b[_0x38eee8][_0x5a9fb6(0x18b)]);if(_0x95e21b){saveJobs(_0x51fd9f);const _0x116988=_0x51fd9f[_0x5a9fb6(0x171)]((_0x4de7b2,_0xfe329d)=>_0x4de7b2['nextRunAt']!==_0x25426b[_0xfe329d]['nextRunAt'])['map'](_0xe2ba0=>_0xe2ba0[_0x10351c(0x220)]);console['log'](_0x5a9fb6(0x193)+_0x10351c(0x176)+'-up:\x20rewou'+_0x10351c(0x142)+_0x116988[_0x5a9fb6(0x1fd)]+_0x10351c(0x21d)+_0x116988['join'](',\x20'));}}catch(_0x14f1cb){console[_0x10351c(0x227)](_0x5a9fb6(0x193)+_0x10351c(0x176)+_0x10351c(0x1f0)+':',_0x14f1cb);}schedulerTimer=setInterval(async()=>{const _0x149285=_0x5a9fb6,_0x50689a=_0x10351c,_0x2ae141=loadJobs(),_0x3ba07b=Date[_0x149285(0x1b8)]();let _0x1ca3ec=![];for(const _0x243db4 of _0x2ae141){if(!_0x243db4[_0x50689a(0x1b5)])continue;if(runningJobs[_0x149285(0x217)](_0x243db4['id']))continue;!_0x243db4[_0x50689a(0x18b)]&&(_0x243db4[_0x149285(0x18b)]=calculateNextRun(_0x243db4),_0x1ca3ec=!![]);if(_0x243db4[_0x50689a(0x18b)]&&_0x3ba07b>=_0x243db4[_0x149285(0x18b)]){console[_0x149285(0x1c1)]('Cron:\x20Runn'+_0x50689a(0x202)+_0x243db4['name']+'\x22\x20('+_0x243db4['id']+')'),runningJobs['add'](_0x243db4['id']);if(!acquireJobLock(_0x243db4['id'])){runningJobs[_0x50689a(0x20b)](_0x243db4['id']),console['log'](_0x50689a(0x1aa)+'\x22'+_0x243db4[_0x50689a(0x220)]+'\x22\x20('+_0x243db4['id']+(_0x50689a(0x1bf)+_0x149285(0x196)+_0x50689a(0x1c9)+_0x149285(0x152)+_0x50689a(0x19a)+_0x50689a(0x1a3)+_0x149285(0x166)));continue;}const _0x3ea1fd=prepareForExecution(_0x243db4,_0x3ba07b);Object[_0x50689a(0x157)](_0x243db4,_0x3ea1fd),saveJobs(_0x2ae141);try{const _0x295ebe=await executeJob(_0x243db4),_0x477fec=loadJobs(),_0x1fdd19=_0x477fec[_0x50689a(0x1fa)](_0x14f2fb=>_0x14f2fb['id']===_0x243db4['id']);_0x1fdd19&&(_0x1fdd19[_0x50689a(0x156)]=Date[_0x149285(0x1b8)](),_0x1fdd19[_0x149285(0x185)]=_0x295ebe['output'][_0x50689a(0x1b4)](0x1efa+-0x2367+0x46d*0x1,-0x21*-0x97+0x109*-0x4+0x4d),_0x1fdd19['lastError']=_0x295ebe[_0x50689a(0x227)]||null,_0x1fdd19[_0x149285(0x21b)]++,_0x1fdd19[_0x50689a(0x149)]?(_0x1fdd19['enabled']=![],_0x1fdd19[_0x149285(0x18b)]=null):_0x1fdd19[_0x50689a(0x18b)]=calculateNextRunFrom(_0x1fdd19,Date[_0x149285(0x1b8)]()),saveJobs(_0x477fec));}finally{runningJobs[_0x50689a(0x20b)](_0x243db4['id']),releaseJobLock(_0x243db4['id']);}continue;}}if(_0x1ca3ec)saveJobs(_0x2ae141);},-0x2*-0x3f03+-0x7d41+0x746b),console[_0x5a9fb6(0x1c1)]('⏰\x20Cron\x20sch'+_0x5a9fb6(0x189)+_0x10351c(0x194)+_0x10351c(0x17c));}export function stopScheduler(){schedulerTimer&&(clearInterval(schedulerTimer),schedulerTimer=null);}function generateId(){const _0x3d22dc=_0x973e82,_0x5263af=_0x973e82;return Date[_0x3d22dc(0x1b8)]()['toString'](-0x1*-0x767+-0x6dd+-0x66)+Math['random']()['toString'](-0x2215+-0x1fa0+0x41d9)[_0x5263af(0x1b4)](-0x1a3+0x1*0x30a+-0x33*0x7,0x7*0x24+0xfbd+0x1*-0x10b3);}export function createJob(_0x47c235){const _0x1bc011=_0x40522f,_0x3c5a9c=_0x40522f,_0x284fa6={'id':generateId(),'name':_0x47c235[_0x1bc011(0x220)],'type':_0x47c235[_0x1bc011(0x1a8)],'schedule':_0x47c235[_0x1bc011(0x1fe)],'oneShot':_0x47c235[_0x3c5a9c(0x149)]??![],'payload':_0x47c235[_0x3c5a9c(0x1e0)],'target':_0x47c235[_0x3c5a9c(0x21a)],'enabled':_0x47c235[_0x3c5a9c(0x1b5)]??!![],'createdAt':Date[_0x1bc011(0x1b8)](),'lastRunAt':null,'lastResult':null,'lastError':null,'nextRunAt':null,'runCount':0x0,'createdBy':_0x47c235[_0x3c5a9c(0x19e)]||_0x3c5a9c(0x1b6),...typeof _0x47c235[_0x3c5a9c(0x22e)]===_0x1bc011(0x15e)?{'timeoutMs':_0x47c235['timeoutMs']}:{}};_0x284fa6[_0x3c5a9c(0x18b)]=calculateNextRun(_0x284fa6);const _0x33157b=loadJobs();return _0x33157b[_0x3c5a9c(0x1ce)](_0x284fa6),saveJobs(_0x33157b),_0x284fa6;}export function listJobs(){return loadJobs();}export function getJob(_0x3a2cc5){const _0x3ef5fa=_0x973e82;return loadJobs()[_0x3ef5fa(0x1fa)](_0x5eb1e3=>_0x5eb1e3['id']===_0x3a2cc5);}export function updateJob(_0x4c3b6a,_0x3ceb02){const _0x116547=_0x973e82,_0x559bc4=_0x973e82,_0x5872f9=loadJobs(),_0x4c6c1e=_0x5872f9[_0x116547(0x17b)](_0x37b160=>_0x37b160['id']===_0x4c3b6a);if(_0x4c6c1e<-0x19fd+-0x15d4+-0x2fd1*-0x1)return null;return Object[_0x559bc4(0x157)](_0x5872f9[_0x4c6c1e],_0x3ceb02),(_0x3ceb02[_0x559bc4(0x1fe)]||_0x3ceb02[_0x116547(0x1b5)]!==undefined)&&(_0x5872f9[_0x4c6c1e][_0x559bc4(0x18b)]=calculateNextRun(_0x5872f9[_0x4c6c1e])),saveJobs(_0x5872f9),_0x5872f9[_0x4c6c1e];}export function deleteJob(_0x205a41){const _0x1ab37a=_0x40522f,_0x63eb3b=_0x40522f,_0xa99270=loadJobs(),_0x32fbf5=_0xa99270[_0x1ab37a(0x171)](_0x5835c8=>_0x5835c8['id']!==_0x205a41);if(_0x32fbf5[_0x63eb3b(0x1fd)]===_0xa99270[_0x63eb3b(0x1fd)])return![];return saveJobs(_0x32fbf5),!![];}export function toggleJob(_0x98d0e9){const _0x24f3e0=_0x973e82,_0x147463=_0x973e82,_0x5a4ac3=loadJobs(),_0x44e31a=_0x5a4ac3[_0x24f3e0(0x1fa)](_0x1874b4=>_0x1874b4['id']===_0x98d0e9);if(!_0x44e31a)return null;return _0x44e31a[_0x147463(0x1b5)]=!_0x44e31a['enabled'],_0x44e31a[_0x147463(0x18b)]=calculateNextRun(_0x44e31a),saveJobs(_0x5a4ac3),_0x44e31a;}export async function runJobNow(_0x527d6a){const _0x419f5e=_0x40522f,_0x3fdb04=_0x973e82,_0x352340=resolveJobByNameOrId(loadJobs(),_0x527d6a);if(!_0x352340)return{'status':_0x419f5e(0x20e)};if(runningJobs['has'](_0x352340['id']))return{'status':_0x419f5e(0x1d7)+'nning','job':_0x352340};runningJobs[_0x3fdb04(0x178)](_0x352340['id']);if(!acquireJobLock(_0x352340['id']))return runningJobs[_0x3fdb04(0x20b)](_0x352340['id']),{'status':'already-ru'+_0x419f5e(0x141),'job':_0x352340};try{let _0x2fc102;try{_0x2fc102=await executeJob(_0x352340);}catch(_0x197da8){_0x2fc102={'output':'','error':_0x197da8 instanceof Error?_0x197da8[_0x419f5e(0x19b)]:String(_0x197da8)};}try{const _0x496409=loadJobs(),_0x1dfb12=_0x496409[_0x419f5e(0x1fa)](_0x40c6c7=>_0x40c6c7['id']===_0x352340['id']);if(_0x1dfb12){const _0x284290=Date[_0x3fdb04(0x1b8)]();_0x1dfb12[_0x3fdb04(0x197)+'tAt']=_0x284290,_0x1dfb12[_0x419f5e(0x156)]=_0x284290,_0x1dfb12[_0x3fdb04(0x185)]=_0x2fc102[_0x419f5e(0x1b0)][_0x3fdb04(0x1b4)](0xa04+0x1c8d+-0x2691,-0x1*0x1813+-0x3*-0xa67+0x87e),_0x1dfb12[_0x419f5e(0x14c)]=_0x2fc102[_0x419f5e(0x227)]||null,_0x1dfb12[_0x419f5e(0x21b)]++,saveJobs(_0x496409);}}catch(_0x574809){console[_0x3fdb04(0x227)](_0x419f5e(0x21c)+'led\x20to\x20per'+_0x419f5e(0x15b)+'l\x20run\x20stat'+'e:',_0x574809);}return{'status':_0x3fdb04(0x1e5),'job':_0x352340,'output':_0x2fc102['output'],'error':_0x2fc102[_0x419f5e(0x227)]};}finally{runningJobs[_0x3fdb04(0x20b)](_0x352340['id']),releaseJobLock(_0x352340['id']);}}export function humanReadableSchedule(_0x497e1f){const _0x47ee53=_0x40522f,_0x2a30b9=_0x40522f,_0x37aed1=_0x497e1f['match'](/^(\d+(?:\.\d+)?)\s*(s|sec|m|min|h|hr|d|day)s?$/i);if(_0x37aed1){const _0x3d55dc=parseFloat(_0x37aed1[0xd*-0x8e+-0x833*-0x1+-0x1c*0x9]),_0x520259=_0x37aed1[-0x23e5+-0x12bf*0x2+0x4965*0x1][_0x47ee53(0x182)+'e'](),_0x5a42aa={'s':[_0x47ee53(0x18e),_0x2a30b9(0x16a)],'sec':[_0x2a30b9(0x18e),_0x47ee53(0x16a)],'m':['minute','minutes'],'min':['minute',_0x47ee53(0x22a)],'h':[_0x47ee53(0x1de),_0x2a30b9(0x1d8)],'hr':[_0x2a30b9(0x1de),_0x2a30b9(0x1d8)],'d':[_0x2a30b9(0x16c),_0x47ee53(0x1e8)],'day':[_0x47ee53(0x16c),_0x2a30b9(0x1e8)]},[_0x1d3391,_0x1ba7d4]=_0x5a42aa[_0x520259]||['?','?'];return _0x2a30b9(0x221)+_0x3d55dc+'\x20'+(_0x3d55dc===-0xaa6+0xe29+-0x382?_0x1d3391:_0x1ba7d4);}const _0x20f782=_0x497e1f[_0x47ee53(0x155)]()[_0x2a30b9(0x213)](/\s+/);if(_0x20f782[_0x47ee53(0x1fd)]!==-0x1*0x96b+-0x1652+0x6*0x54b)return _0x497e1f;const [_0x1beab9,_0x1e0c44,_0x44913d,_0x56787f,_0x5a696f]=_0x20f782,_0x18bdb9=[_0x47ee53(0x188),_0x2a30b9(0x1e6),_0x47ee53(0x208),_0x2a30b9(0x1dd),_0x47ee53(0x1ba),_0x2a30b9(0x167),'Sat'],_0x2ade17=['',_0x47ee53(0x1d4),_0x2a30b9(0x16e),_0x47ee53(0x200),_0x2a30b9(0x17a),'May','Jun',_0x47ee53(0x20a),'Aug',_0x2a30b9(0x1d2),_0x47ee53(0x153),_0x2a30b9(0x187),'Dec'];function _0x5079e5(_0x89ad87,_0x32a887){const _0x3ab1b9=_0x47ee53,_0x3243f4=_0x2a30b9;if(_0x89ad87==='*'&&_0x32a887==='*')return'';const _0x148277=_0x89ad87==='*'?'*':_0x89ad87[_0x3ab1b9(0x14d)](0x47*0x3b+-0x1*0x194a+-0x8ef*-0x1,'0'),_0xd7dba5=_0x32a887==='*'?'00':_0x32a887[_0x3ab1b9(0x14d)](-0x2648+-0x467+0x2ab1,'0');return _0x148277+':'+_0xd7dba5;}function _0x3e7176(_0x27cb2c,_0x56ec41){const _0x38bccb=_0x2a30b9;if(_0x27cb2c==='*')return'';const _0x3000c9=_0x27cb2c[_0x38bccb(0x213)](',')['map'](_0x3e4ca6=>{const _0x3b8771=_0x38bccb;if(_0x3e4ca6['includes']('-')){const [_0xdf7abb,_0x4fb0c0]=_0x3e4ca6[_0x3b8771(0x213)]('-');if(_0x56ec41)return _0x56ec41[+_0xdf7abb]+'–'+_0x56ec41[+_0x4fb0c0];return _0xdf7abb+'–'+_0x4fb0c0;}return _0x56ec41?_0x56ec41[+_0x3e4ca6]||_0x3e4ca6:_0x3e4ca6;});return _0x3000c9['join'](',\x20');}const _0x135f66=_0x5079e5(_0x1e0c44,_0x1beab9),_0x5805d8=[_0x1beab9,_0x1e0c44][_0x47ee53(0x165)](_0x5f3a83=>_0x5f3a83[_0x2a30b9(0x143)]('/'));if(_0x1beab9[_0x2a30b9(0x143)]('/')&&_0x1e0c44==='*'&&_0x44913d==='*'&&_0x56787f==='*'&&_0x5a696f==='*'){const _0x596e0a=_0x1beab9[_0x2a30b9(0x213)]('/')[0x1f0a+-0x19*0x166+0x3ed];return'Every\x20'+_0x596e0a+_0x47ee53(0x192);}if(_0x1e0c44[_0x2a30b9(0x143)]('/')&&_0x44913d==='*'&&_0x56787f==='*'&&_0x5a696f==='*'){const _0x5f8ffc=_0x1e0c44['split']('/')[-0x3fa+-0x6*-0x1f+-0x77*-0x7];return'Every\x20'+_0x5f8ffc+'h';}const _0x41a21a=[];if(_0x5a696f!=='*'){const _0x39209a=_0x3e7176(_0x5a696f,_0x18bdb9);if(_0x5a696f===_0x47ee53(0x1c4))_0x41a21a[_0x2a30b9(0x1ce)](_0x2a30b9(0x1f6));else{if(_0x5a696f===_0x47ee53(0x1f5)||_0x5a696f===_0x47ee53(0x1be))_0x41a21a['push'](_0x2a30b9(0x18f));else _0x41a21a[_0x2a30b9(0x1ce)]('Every\x20'+_0x39209a);}}else{if(_0x44913d!=='*'){const _0x16ae79=_0x3e7176(_0x44913d);if(_0x56787f!=='*'){const _0x33e980=_0x3e7176(_0x56787f,_0x2ade17);_0x41a21a['push'](_0x47ee53(0x1b7)+_0x16ae79+_0x2a30b9(0x1ab)+_0x33e980);}else _0x41a21a[_0x47ee53(0x1ce)](_0x47ee53(0x1b7)+_0x16ae79+(_0x47ee53(0x1ee)+_0x2a30b9(0x201)));}else{if(_0x56787f!=='*'){const _0x24d660=_0x3e7176(_0x56787f,_0x2ade17);_0x41a21a['push'](_0x2a30b9(0x1bd)+_0x24d660);}else!_0x5805d8&&_0x41a21a[_0x47ee53(0x1ce)](_0x47ee53(0x224));}}if(_0x135f66&&!_0x5805d8)_0x41a21a[_0x47ee53(0x1ce)](_0x135f66);return _0x41a21a[_0x2a30b9(0x169)](',\x20')||_0x497e1f;}export function formatNextRun(_0x5b2f76){const _0x4f88b6=_0x40522f,_0x2016fc=_0x973e82;if(!_0x5b2f76)return'—';const _0x5b11b4=_0x5b2f76-Date['now']();if(_0x5b11b4<0x1ee9+-0x21f0+0x307)return _0x4f88b6(0x1e7);if(_0x5b11b4<0xc536+-0x7a79+0x9fa3)return _0x4f88b6(0x159)+Math[_0x4f88b6(0x198)](_0x5b11b4/(-0x1edf+0x1881+0xa46))+'s';if(_0x5b11b4<-0x3*-0x200eb9+-0x65276d+0x3be9c2)return _0x4f88b6(0x159)+Math['round'](_0x5b11b4/(-0x1*-0x1694e+-0x1*-0x17748+-0x1f636))+_0x4f88b6(0x218);if(_0x5b11b4<0x180d7*0x10f+-0x2*-0x1ebb97f+-0x487a97)return _0x2016fc(0x159)+(_0x5b11b4/(0x14178*-0x35+0x6008*-0xb+0x10*0x7d9cb))[_0x2016fc(0x1d9)](-0x194f+-0x3f4+0x2*0xea2)+'h';return _0x4f88b6(0x159)+(_0x5b11b4/(-0x2*-0x6af516+0x2*0x40b2621+-0x3c5da6e))['toFixed'](0x2*0x4a9+0xfc4+0x1*-0x1915)+'\x20days';}
|