beast-agent 1.9.0 → 2.0.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/LICENSE +21 -21
- package/README.md +130 -130
- package/bin/beast-agent.js +137 -137
- package/package.json +1 -1
- package/scripts/fix-electron.js +138 -138
- package/scripts/release.js +137 -131
- package/scripts/swap-electron.js +40 -40
- package/src/agent/agentdefs.js +122 -122
- package/src/agent/bots.js +580 -580
- package/src/agent/bus.js +389 -389
- package/src/agent/computeruse.js +200 -200
- package/src/agent/config.js +284 -284
- package/src/agent/discord.js +332 -332
- package/src/agent/engine.js +75 -10
- package/src/agent/kb.js +123 -123
- package/src/agent/llm.js +430 -430
- package/src/agent/logger.js +90 -90
- package/src/agent/mcp.js +427 -427
- package/src/agent/mem0.js +605 -605
- package/src/agent/memory.js +427 -427
- package/src/agent/mqueue.js +124 -124
- package/src/agent/pdf.js +20 -20
- package/src/agent/research.js +133 -133
- package/src/agent/scripts/news.py +113 -113
- package/src/agent/scripts/stealthsearch.py +30 -30
- package/src/agent/scripts/websearch.py +225 -225
- package/src/agent/searxng.js +325 -325
- package/src/agent/seeds/brainstorming/SKILL.md +90 -90
- package/src/agent/seeds/dispatching-parallel-agents/SKILL.md +120 -120
- package/src/agent/seeds/executing-plans/SKILL.md +60 -60
- package/src/agent/seeds/subagent-driven-development/SKILL.md +167 -167
- package/src/agent/seeds/systematic-debugging/SKILL.md +131 -131
- package/src/agent/seeds/test-driven-development/SKILL.md +152 -152
- package/src/agent/seeds/verification-before-completion/SKILL.md +63 -63
- package/src/agent/seeds/writing-plans/SKILL.md +162 -162
- package/src/agent/seeds/writing-skills/SKILL.md +229 -229
- package/src/agent/skills.js +652 -652
- package/src/agent/store.js +378 -378
- package/src/agent/telegram.js +155 -155
- package/src/agent/tokens.js +39 -39
- package/src/agent/usage.js +125 -125
- package/src/agent/watchers.js +312 -312
- package/src/agent/watext.js +80 -80
- package/src/agent/whatsapp.js +555 -555
- package/src/cron.js +255 -255
- package/src/main.js +348 -6
- package/src/preload.js +9 -0
- package/src/renderer/browserPreload.js +73 -73
- package/src/renderer/i18n.js +4 -0
- package/src/renderer/index.html +448 -409
- package/src/renderer/renderer.js +824 -41
- package/src/renderer/style.css +264 -5
package/src/cron.js
CHANGED
|
@@ -1,255 +1,255 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* Beast cron: zamanlanmış görevler (%APPDATA%\beast\cron.json).
|
|
4
|
-
5 alanlı cron ifadeleri: dakika saat ayGunu ay haftaGunu
|
|
5
|
-
Desteklenen sözdizimi: * , - / sayılar. Saat geldiğinde onFire tetiklenir. */
|
|
6
|
-
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const { beastRoot } = require('./agent/memory');
|
|
10
|
-
|
|
11
|
-
function file() {
|
|
12
|
-
return path.join(beastRoot(), 'cron.json');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
let jobs = [];
|
|
16
|
-
let timer = null;
|
|
17
|
-
let onFire = null;
|
|
18
|
-
|
|
19
|
-
function uid() {
|
|
20
|
-
return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function load() {
|
|
24
|
-
try {
|
|
25
|
-
const raw = JSON.parse(fs.readFileSync(file(), 'utf8'));
|
|
26
|
-
jobs = Array.isArray(raw.jobs) ? raw.jobs : [];
|
|
27
|
-
} catch {
|
|
28
|
-
jobs = [];
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function save() {
|
|
33
|
-
try {
|
|
34
|
-
fs.mkdirSync(beastRoot(), { recursive: true });
|
|
35
|
-
fs.writeFileSync(file(), JSON.stringify({ jobs }, null, 2));
|
|
36
|
-
} catch {}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/* ---------- cron ayrıştırıcı ---------- */
|
|
40
|
-
|
|
41
|
-
function parseField(field, min, max) {
|
|
42
|
-
const allowed = new Set();
|
|
43
|
-
for (const partRaw of String(field).split(',')) {
|
|
44
|
-
const part = partRaw.trim();
|
|
45
|
-
if (!part) return null;
|
|
46
|
-
const [rangeRaw, stepRaw] = part.split('/');
|
|
47
|
-
const step = stepRaw === undefined ? 1 : Number(stepRaw);
|
|
48
|
-
if (!Number.isInteger(step) || step < 1) return null;
|
|
49
|
-
let lo = min;
|
|
50
|
-
let hi = max;
|
|
51
|
-
if (rangeRaw !== '*') {
|
|
52
|
-
const m = rangeRaw.match(/^(\d+)(?:-(\d+))?$/);
|
|
53
|
-
if (!m) return null;
|
|
54
|
-
lo = Number(m[1]);
|
|
55
|
-
hi = m[2] === undefined ? lo : Number(m[2]);
|
|
56
|
-
if (lo < min || hi > max || lo > hi) return null;
|
|
57
|
-
}
|
|
58
|
-
for (let v = lo; v <= hi; v += step) allowed.add(v);
|
|
59
|
-
}
|
|
60
|
-
return allowed;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/* {min,h,dow,mon} alan eşleşmesini Date üzerinde kontrol eder */
|
|
64
|
-
function cronMatches(fields, d) {
|
|
65
|
-
return (
|
|
66
|
-
fields.min.has(d.getMinutes()) &&
|
|
67
|
-
fields.hour.has(d.getHours()) &&
|
|
68
|
-
fields.dom.has(d.getDate()) &&
|
|
69
|
-
fields.mon.has(d.getMonth() + 1) &&
|
|
70
|
-
(fields.dow.has(d.getDay()) || (fields.dow.has(7) && d.getDay() === 0))
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function parseCron(expr) {
|
|
75
|
-
const parts = String(expr || '').trim().split(/\s+/);
|
|
76
|
-
if (parts.length !== 5) return null;
|
|
77
|
-
const fields = {
|
|
78
|
-
min: parseField(parts[0], 0, 59),
|
|
79
|
-
hour: parseField(parts[1], 0, 23),
|
|
80
|
-
dom: parseField(parts[2], 1, 31),
|
|
81
|
-
mon: parseField(parts[3], 1, 12),
|
|
82
|
-
dow: parseField(parts[4], 0, 7),
|
|
83
|
-
};
|
|
84
|
-
if (Object.values(fields).some((f) => !f)) return null;
|
|
85
|
-
return fields;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/* Verilen zamandan sonraki ilk uyarın anı (ms) — en fazla 400 gün tarar */
|
|
89
|
-
function nextRunFrom(expr, fromDate) {
|
|
90
|
-
const fields = parseCron(expr);
|
|
91
|
-
if (!fields) return null;
|
|
92
|
-
const d = new Date(fromDate.getTime());
|
|
93
|
-
d.setSeconds(0, 0);
|
|
94
|
-
d.setMinutes(d.getMinutes() + 1);
|
|
95
|
-
for (let i = 0; i < 400 * 24 * 60; i++) {
|
|
96
|
-
if (cronMatches(fields, d)) return d.getTime();
|
|
97
|
-
d.setMinutes(d.getMinutes() + 1);
|
|
98
|
-
}
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function isValidSchedule(expr) {
|
|
103
|
-
return nextRunFrom(expr, new Date()) !== null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/* Tekrarlı hatırlatma ("her sabah 09:00" tarzı): when'in saat/dakikası +
|
|
107
|
-
repeat frekansından 5 alanlı cron üretir. repeat için presetler YA DA
|
|
108
|
-
doğrudan geçerli bir cron ifadesi kabul edilir. Saf fonksiyon. */
|
|
109
|
-
function reminderSchedule(when, repeat) {
|
|
110
|
-
const r = String(repeat || '').trim().toLowerCase();
|
|
111
|
-
if (!r) return { ok: false, error: 'repeat gerekli: daily/weekly/monthly/weekdays veya 5 alanlı cron' };
|
|
112
|
-
/* doğrudan cron verilmişse (5 boşlukla ayrılmış alan) aynen doğrula */
|
|
113
|
-
if (/^(\S+\s+){4}\S+$/.test(r)) {
|
|
114
|
-
if (isValidSchedule(r)) return { ok: true, schedule: r };
|
|
115
|
-
return { ok: false, error: 'geçersiz cron ifadesi (örn: */15 * * * *)' };
|
|
116
|
-
}
|
|
117
|
-
const d = new Date(String(when || '').trim());
|
|
118
|
-
if (isNaN(d.getTime())) return { ok: false, error: 'tekrarlı hatırlatma için geçerli when (saat bilgisiyle) gerekli' };
|
|
119
|
-
const map = {
|
|
120
|
-
daily: `${d.getMinutes()} ${d.getHours()} * * *`,
|
|
121
|
-
weekdays: `${d.getMinutes()} ${d.getHours()} * * 1-5`,
|
|
122
|
-
weekly: `${d.getMinutes()} ${d.getHours()} * * ${d.getDay()}`,
|
|
123
|
-
monthly: `${d.getMinutes()} ${d.getHours()} ${d.getDate()} * *`,
|
|
124
|
-
};
|
|
125
|
-
const schedule = map[r];
|
|
126
|
-
if (!schedule) {
|
|
127
|
-
return { ok: false, error: 'bilinmeyen repeat — daily/weekly/monthly/weekdays kullan ya da 5 alanlı cron ver' };
|
|
128
|
-
}
|
|
129
|
-
if (!isValidSchedule(schedule)) return { ok: false, error: 'oluşan takvim hiç tetiklenmiyor — when/repeat uyumsuz' };
|
|
130
|
-
return { ok: true, schedule };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/* ---------- CRUD ---------- */
|
|
134
|
-
|
|
135
|
-
function list() {
|
|
136
|
-
return jobs.map((j) => ({ ...j }));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function add({ name, schedule, prompt, once, sessionId }) {
|
|
140
|
-
const n = String(name || '').trim().slice(0, 80) || 'Görev';
|
|
141
|
-
const s = String(schedule || '').trim();
|
|
142
|
-
const p = String(prompt || '').trim().slice(0, 4000);
|
|
143
|
-
if (!isValidSchedule(s)) return { ok: false, error: 'geçersiz cron ifadesi (örn: */15 * * * *)' };
|
|
144
|
-
if (!p) return { ok: false, error: 'görev metni boş olamaz' };
|
|
145
|
-
const job = {
|
|
146
|
-
id: uid(),
|
|
147
|
-
name: n,
|
|
148
|
-
schedule: s,
|
|
149
|
-
prompt: p,
|
|
150
|
-
sessionId: sessionId || null,
|
|
151
|
-
once: !!once,
|
|
152
|
-
enabled: true,
|
|
153
|
-
createdAt: new Date().toISOString(),
|
|
154
|
-
lastRunAt: null,
|
|
155
|
-
nextRunAt: nextRunFrom(s, new Date()),
|
|
156
|
-
};
|
|
157
|
-
jobs.push(job);
|
|
158
|
-
save();
|
|
159
|
-
return { ok: true, job: { ...job } };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function update(id, patch) {
|
|
163
|
-
const job = jobs.find((j) => j.id === id);
|
|
164
|
-
if (!job) return { ok: false, error: 'görev bulunamadı' };
|
|
165
|
-
if (patch.sessionId !== undefined) job.sessionId = patch.sessionId;
|
|
166
|
-
if (patch.name !== undefined) job.name = String(patch.name).trim().slice(0, 80) || job.name;
|
|
167
|
-
if (patch.prompt !== undefined) {
|
|
168
|
-
const p = String(patch.prompt).trim().slice(0, 4000);
|
|
169
|
-
if (!p) return { ok: false, error: 'görev metni boş olamaz' };
|
|
170
|
-
job.prompt = p;
|
|
171
|
-
}
|
|
172
|
-
if (patch.schedule !== undefined) {
|
|
173
|
-
const s = String(patch.schedule).trim();
|
|
174
|
-
if (!isValidSchedule(s)) return { ok: false, error: 'geçersiz cron ifadesi' };
|
|
175
|
-
job.schedule = s;
|
|
176
|
-
job.nextRunAt = nextRunFrom(s, new Date());
|
|
177
|
-
}
|
|
178
|
-
save();
|
|
179
|
-
return { ok: true, job: { ...job } };
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function remove(id) {
|
|
183
|
-
const before = jobs.length;
|
|
184
|
-
jobs = jobs.filter((j) => j.id !== id);
|
|
185
|
-
save();
|
|
186
|
-
return { ok: jobs.length < before };
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/* Tüm görevleri bellekten ve diskten sil; zamanlayıcıyı durdur.
|
|
190
|
-
"hepsini sil" / "/cron clear" için kullanılır. */
|
|
191
|
-
function clearAll() {
|
|
192
|
-
const count = jobs.length;
|
|
193
|
-
jobs = [];
|
|
194
|
-
if (timer) { clearInterval(timer); timer = null; }
|
|
195
|
-
save();
|
|
196
|
-
return { ok: true, count };
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function toggle(id) {
|
|
200
|
-
const job = jobs.find((j) => j.id === id);
|
|
201
|
-
if (!job) return { ok: false, error: 'görev bulunamadı' };
|
|
202
|
-
job.enabled = !job.enabled;
|
|
203
|
-
job.nextRunAt = job.enabled ? nextRunFrom(job.schedule, new Date()) : null;
|
|
204
|
-
save();
|
|
205
|
-
return { ok: true, job: { ...job } };
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function runNow(id) {
|
|
209
|
-
const job = jobs.find((j) => j.id === id);
|
|
210
|
-
if (!job) return { ok: false, error: 'görev bulunamadı' };
|
|
211
|
-
fire(job);
|
|
212
|
-
return { ok: true };
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/* ---------- zamanlayıcı ---------- */
|
|
216
|
-
|
|
217
|
-
function fire(job) {
|
|
218
|
-
job.lastRunAt = new Date().toISOString();
|
|
219
|
-
if (!job.once) job.nextRunAt = nextRunFrom(job.schedule, new Date());
|
|
220
|
-
if (job.once) {
|
|
221
|
-
// tek seferlik (hatırlatma): uyarınca kendini sil
|
|
222
|
-
jobs = jobs.filter((j) => j.id !== job.id);
|
|
223
|
-
}
|
|
224
|
-
save();
|
|
225
|
-
if (onFire) onFire({ ...job });
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function tick() {
|
|
229
|
-
const now = Date.now();
|
|
230
|
-
for (const job of jobs.slice()) {
|
|
231
|
-
if (!job.enabled || !job.nextRunAt) continue;
|
|
232
|
-
if (new Date(job.nextRunAt).getTime() <= now) fire(job);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function init(hooks) {
|
|
237
|
-
onFire = (hooks && hooks.onFire) || null;
|
|
238
|
-
load();
|
|
239
|
-
for (const job of jobs) {
|
|
240
|
-
if (job.enabled && (!job.nextRunAt || new Date(job.nextRunAt).getTime() <= Date.now())) {
|
|
241
|
-
job.nextRunAt = nextRunFrom(job.schedule, new Date());
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
save();
|
|
245
|
-
if (timer) clearInterval(timer);
|
|
246
|
-
timer = setInterval(tick, 20 * 1000);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/* /stop: zamanlayıcıyı durdur (init ile geri başlar) */
|
|
250
|
-
function stop() {
|
|
251
|
-
if (timer) clearInterval(timer);
|
|
252
|
-
timer = null;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
module.exports = { init, stop, list, add, update, remove, clearAll, toggle, runNow, parseCron, nextRunFrom, isValidSchedule, reminderSchedule };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* Beast cron: zamanlanmış görevler (%APPDATA%\beast\cron.json).
|
|
4
|
+
5 alanlı cron ifadeleri: dakika saat ayGunu ay haftaGunu
|
|
5
|
+
Desteklenen sözdizimi: * , - / sayılar. Saat geldiğinde onFire tetiklenir. */
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { beastRoot } = require('./agent/memory');
|
|
10
|
+
|
|
11
|
+
function file() {
|
|
12
|
+
return path.join(beastRoot(), 'cron.json');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let jobs = [];
|
|
16
|
+
let timer = null;
|
|
17
|
+
let onFire = null;
|
|
18
|
+
|
|
19
|
+
function uid() {
|
|
20
|
+
return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function load() {
|
|
24
|
+
try {
|
|
25
|
+
const raw = JSON.parse(fs.readFileSync(file(), 'utf8'));
|
|
26
|
+
jobs = Array.isArray(raw.jobs) ? raw.jobs : [];
|
|
27
|
+
} catch {
|
|
28
|
+
jobs = [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function save() {
|
|
33
|
+
try {
|
|
34
|
+
fs.mkdirSync(beastRoot(), { recursive: true });
|
|
35
|
+
fs.writeFileSync(file(), JSON.stringify({ jobs }, null, 2));
|
|
36
|
+
} catch {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* ---------- cron ayrıştırıcı ---------- */
|
|
40
|
+
|
|
41
|
+
function parseField(field, min, max) {
|
|
42
|
+
const allowed = new Set();
|
|
43
|
+
for (const partRaw of String(field).split(',')) {
|
|
44
|
+
const part = partRaw.trim();
|
|
45
|
+
if (!part) return null;
|
|
46
|
+
const [rangeRaw, stepRaw] = part.split('/');
|
|
47
|
+
const step = stepRaw === undefined ? 1 : Number(stepRaw);
|
|
48
|
+
if (!Number.isInteger(step) || step < 1) return null;
|
|
49
|
+
let lo = min;
|
|
50
|
+
let hi = max;
|
|
51
|
+
if (rangeRaw !== '*') {
|
|
52
|
+
const m = rangeRaw.match(/^(\d+)(?:-(\d+))?$/);
|
|
53
|
+
if (!m) return null;
|
|
54
|
+
lo = Number(m[1]);
|
|
55
|
+
hi = m[2] === undefined ? lo : Number(m[2]);
|
|
56
|
+
if (lo < min || hi > max || lo > hi) return null;
|
|
57
|
+
}
|
|
58
|
+
for (let v = lo; v <= hi; v += step) allowed.add(v);
|
|
59
|
+
}
|
|
60
|
+
return allowed;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* {min,h,dow,mon} alan eşleşmesini Date üzerinde kontrol eder */
|
|
64
|
+
function cronMatches(fields, d) {
|
|
65
|
+
return (
|
|
66
|
+
fields.min.has(d.getMinutes()) &&
|
|
67
|
+
fields.hour.has(d.getHours()) &&
|
|
68
|
+
fields.dom.has(d.getDate()) &&
|
|
69
|
+
fields.mon.has(d.getMonth() + 1) &&
|
|
70
|
+
(fields.dow.has(d.getDay()) || (fields.dow.has(7) && d.getDay() === 0))
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseCron(expr) {
|
|
75
|
+
const parts = String(expr || '').trim().split(/\s+/);
|
|
76
|
+
if (parts.length !== 5) return null;
|
|
77
|
+
const fields = {
|
|
78
|
+
min: parseField(parts[0], 0, 59),
|
|
79
|
+
hour: parseField(parts[1], 0, 23),
|
|
80
|
+
dom: parseField(parts[2], 1, 31),
|
|
81
|
+
mon: parseField(parts[3], 1, 12),
|
|
82
|
+
dow: parseField(parts[4], 0, 7),
|
|
83
|
+
};
|
|
84
|
+
if (Object.values(fields).some((f) => !f)) return null;
|
|
85
|
+
return fields;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Verilen zamandan sonraki ilk uyarın anı (ms) — en fazla 400 gün tarar */
|
|
89
|
+
function nextRunFrom(expr, fromDate) {
|
|
90
|
+
const fields = parseCron(expr);
|
|
91
|
+
if (!fields) return null;
|
|
92
|
+
const d = new Date(fromDate.getTime());
|
|
93
|
+
d.setSeconds(0, 0);
|
|
94
|
+
d.setMinutes(d.getMinutes() + 1);
|
|
95
|
+
for (let i = 0; i < 400 * 24 * 60; i++) {
|
|
96
|
+
if (cronMatches(fields, d)) return d.getTime();
|
|
97
|
+
d.setMinutes(d.getMinutes() + 1);
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isValidSchedule(expr) {
|
|
103
|
+
return nextRunFrom(expr, new Date()) !== null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Tekrarlı hatırlatma ("her sabah 09:00" tarzı): when'in saat/dakikası +
|
|
107
|
+
repeat frekansından 5 alanlı cron üretir. repeat için presetler YA DA
|
|
108
|
+
doğrudan geçerli bir cron ifadesi kabul edilir. Saf fonksiyon. */
|
|
109
|
+
function reminderSchedule(when, repeat) {
|
|
110
|
+
const r = String(repeat || '').trim().toLowerCase();
|
|
111
|
+
if (!r) return { ok: false, error: 'repeat gerekli: daily/weekly/monthly/weekdays veya 5 alanlı cron' };
|
|
112
|
+
/* doğrudan cron verilmişse (5 boşlukla ayrılmış alan) aynen doğrula */
|
|
113
|
+
if (/^(\S+\s+){4}\S+$/.test(r)) {
|
|
114
|
+
if (isValidSchedule(r)) return { ok: true, schedule: r };
|
|
115
|
+
return { ok: false, error: 'geçersiz cron ifadesi (örn: */15 * * * *)' };
|
|
116
|
+
}
|
|
117
|
+
const d = new Date(String(when || '').trim());
|
|
118
|
+
if (isNaN(d.getTime())) return { ok: false, error: 'tekrarlı hatırlatma için geçerli when (saat bilgisiyle) gerekli' };
|
|
119
|
+
const map = {
|
|
120
|
+
daily: `${d.getMinutes()} ${d.getHours()} * * *`,
|
|
121
|
+
weekdays: `${d.getMinutes()} ${d.getHours()} * * 1-5`,
|
|
122
|
+
weekly: `${d.getMinutes()} ${d.getHours()} * * ${d.getDay()}`,
|
|
123
|
+
monthly: `${d.getMinutes()} ${d.getHours()} ${d.getDate()} * *`,
|
|
124
|
+
};
|
|
125
|
+
const schedule = map[r];
|
|
126
|
+
if (!schedule) {
|
|
127
|
+
return { ok: false, error: 'bilinmeyen repeat — daily/weekly/monthly/weekdays kullan ya da 5 alanlı cron ver' };
|
|
128
|
+
}
|
|
129
|
+
if (!isValidSchedule(schedule)) return { ok: false, error: 'oluşan takvim hiç tetiklenmiyor — when/repeat uyumsuz' };
|
|
130
|
+
return { ok: true, schedule };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* ---------- CRUD ---------- */
|
|
134
|
+
|
|
135
|
+
function list() {
|
|
136
|
+
return jobs.map((j) => ({ ...j }));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function add({ name, schedule, prompt, once, sessionId }) {
|
|
140
|
+
const n = String(name || '').trim().slice(0, 80) || 'Görev';
|
|
141
|
+
const s = String(schedule || '').trim();
|
|
142
|
+
const p = String(prompt || '').trim().slice(0, 4000);
|
|
143
|
+
if (!isValidSchedule(s)) return { ok: false, error: 'geçersiz cron ifadesi (örn: */15 * * * *)' };
|
|
144
|
+
if (!p) return { ok: false, error: 'görev metni boş olamaz' };
|
|
145
|
+
const job = {
|
|
146
|
+
id: uid(),
|
|
147
|
+
name: n,
|
|
148
|
+
schedule: s,
|
|
149
|
+
prompt: p,
|
|
150
|
+
sessionId: sessionId || null,
|
|
151
|
+
once: !!once,
|
|
152
|
+
enabled: true,
|
|
153
|
+
createdAt: new Date().toISOString(),
|
|
154
|
+
lastRunAt: null,
|
|
155
|
+
nextRunAt: nextRunFrom(s, new Date()),
|
|
156
|
+
};
|
|
157
|
+
jobs.push(job);
|
|
158
|
+
save();
|
|
159
|
+
return { ok: true, job: { ...job } };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function update(id, patch) {
|
|
163
|
+
const job = jobs.find((j) => j.id === id);
|
|
164
|
+
if (!job) return { ok: false, error: 'görev bulunamadı' };
|
|
165
|
+
if (patch.sessionId !== undefined) job.sessionId = patch.sessionId;
|
|
166
|
+
if (patch.name !== undefined) job.name = String(patch.name).trim().slice(0, 80) || job.name;
|
|
167
|
+
if (patch.prompt !== undefined) {
|
|
168
|
+
const p = String(patch.prompt).trim().slice(0, 4000);
|
|
169
|
+
if (!p) return { ok: false, error: 'görev metni boş olamaz' };
|
|
170
|
+
job.prompt = p;
|
|
171
|
+
}
|
|
172
|
+
if (patch.schedule !== undefined) {
|
|
173
|
+
const s = String(patch.schedule).trim();
|
|
174
|
+
if (!isValidSchedule(s)) return { ok: false, error: 'geçersiz cron ifadesi' };
|
|
175
|
+
job.schedule = s;
|
|
176
|
+
job.nextRunAt = nextRunFrom(s, new Date());
|
|
177
|
+
}
|
|
178
|
+
save();
|
|
179
|
+
return { ok: true, job: { ...job } };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function remove(id) {
|
|
183
|
+
const before = jobs.length;
|
|
184
|
+
jobs = jobs.filter((j) => j.id !== id);
|
|
185
|
+
save();
|
|
186
|
+
return { ok: jobs.length < before };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* Tüm görevleri bellekten ve diskten sil; zamanlayıcıyı durdur.
|
|
190
|
+
"hepsini sil" / "/cron clear" için kullanılır. */
|
|
191
|
+
function clearAll() {
|
|
192
|
+
const count = jobs.length;
|
|
193
|
+
jobs = [];
|
|
194
|
+
if (timer) { clearInterval(timer); timer = null; }
|
|
195
|
+
save();
|
|
196
|
+
return { ok: true, count };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function toggle(id) {
|
|
200
|
+
const job = jobs.find((j) => j.id === id);
|
|
201
|
+
if (!job) return { ok: false, error: 'görev bulunamadı' };
|
|
202
|
+
job.enabled = !job.enabled;
|
|
203
|
+
job.nextRunAt = job.enabled ? nextRunFrom(job.schedule, new Date()) : null;
|
|
204
|
+
save();
|
|
205
|
+
return { ok: true, job: { ...job } };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function runNow(id) {
|
|
209
|
+
const job = jobs.find((j) => j.id === id);
|
|
210
|
+
if (!job) return { ok: false, error: 'görev bulunamadı' };
|
|
211
|
+
fire(job);
|
|
212
|
+
return { ok: true };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* ---------- zamanlayıcı ---------- */
|
|
216
|
+
|
|
217
|
+
function fire(job) {
|
|
218
|
+
job.lastRunAt = new Date().toISOString();
|
|
219
|
+
if (!job.once) job.nextRunAt = nextRunFrom(job.schedule, new Date());
|
|
220
|
+
if (job.once) {
|
|
221
|
+
// tek seferlik (hatırlatma): uyarınca kendini sil
|
|
222
|
+
jobs = jobs.filter((j) => j.id !== job.id);
|
|
223
|
+
}
|
|
224
|
+
save();
|
|
225
|
+
if (onFire) onFire({ ...job });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function tick() {
|
|
229
|
+
const now = Date.now();
|
|
230
|
+
for (const job of jobs.slice()) {
|
|
231
|
+
if (!job.enabled || !job.nextRunAt) continue;
|
|
232
|
+
if (new Date(job.nextRunAt).getTime() <= now) fire(job);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function init(hooks) {
|
|
237
|
+
onFire = (hooks && hooks.onFire) || null;
|
|
238
|
+
load();
|
|
239
|
+
for (const job of jobs) {
|
|
240
|
+
if (job.enabled && (!job.nextRunAt || new Date(job.nextRunAt).getTime() <= Date.now())) {
|
|
241
|
+
job.nextRunAt = nextRunFrom(job.schedule, new Date());
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
save();
|
|
245
|
+
if (timer) clearInterval(timer);
|
|
246
|
+
timer = setInterval(tick, 20 * 1000);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* /stop: zamanlayıcıyı durdur (init ile geri başlar) */
|
|
250
|
+
function stop() {
|
|
251
|
+
if (timer) clearInterval(timer);
|
|
252
|
+
timer = null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
module.exports = { init, stop, list, add, update, remove, clearAll, toggle, runNow, parseCron, nextRunFrom, isValidSchedule, reminderSchedule };
|