beecork 1.7.1 ā 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/README.md +52 -111
- package/dist/index.js +1394 -581
- package/package.json +26 -54
- package/dist/capabilities/index.d.ts +0 -3
- package/dist/capabilities/index.js +0 -2
- package/dist/capabilities/manager.d.ts +0 -13
- package/dist/capabilities/manager.js +0 -115
- package/dist/capabilities/packs.d.ts +0 -2
- package/dist/capabilities/packs.js +0 -54
- package/dist/capabilities/types.d.ts +0 -21
- package/dist/capabilities/types.js +0 -1
- package/dist/channels/admin.d.ts +0 -10
- package/dist/channels/admin.js +0 -20
- package/dist/channels/command-handler.d.ts +0 -19
- package/dist/channels/command-handler.js +0 -235
- package/dist/channels/discord.d.ts +0 -17
- package/dist/channels/discord.js +0 -266
- package/dist/channels/index.d.ts +0 -7
- package/dist/channels/index.js +0 -6
- package/dist/channels/loader.d.ts +0 -7
- package/dist/channels/loader.js +0 -87
- package/dist/channels/pipeline.d.ts +0 -50
- package/dist/channels/pipeline.js +0 -92
- package/dist/channels/registry.d.ts +0 -36
- package/dist/channels/registry.js +0 -80
- package/dist/channels/send-helpers.d.ts +0 -19
- package/dist/channels/send-helpers.js +0 -21
- package/dist/channels/telegram.d.ts +0 -47
- package/dist/channels/telegram.js +0 -649
- package/dist/channels/types.d.ts +0 -41
- package/dist/channels/types.js +0 -1
- package/dist/channels/voice-state.d.ts +0 -29
- package/dist/channels/voice-state.js +0 -45
- package/dist/channels/webhook.d.ts +0 -16
- package/dist/channels/webhook.js +0 -222
- package/dist/channels/whatsapp.d.ts +0 -28
- package/dist/channels/whatsapp.js +0 -323
- package/dist/cli/capabilities.d.ts +0 -3
- package/dist/cli/capabilities.js +0 -72
- package/dist/cli/channel.d.ts +0 -3
- package/dist/cli/channel.js +0 -202
- package/dist/cli/commands.d.ts +0 -15
- package/dist/cli/commands.js +0 -279
- package/dist/cli/doctor.d.ts +0 -1
- package/dist/cli/doctor.js +0 -208
- package/dist/cli/handoff.d.ts +0 -8
- package/dist/cli/handoff.js +0 -33
- package/dist/cli/helpers.d.ts +0 -2
- package/dist/cli/helpers.js +0 -21
- package/dist/cli/mcp.d.ts +0 -3
- package/dist/cli/mcp.js +0 -47
- package/dist/cli/media.d.ts +0 -2
- package/dist/cli/media.js +0 -125
- package/dist/cli/setup.d.ts +0 -1
- package/dist/cli/setup.js +0 -239
- package/dist/cli/store.d.ts +0 -3
- package/dist/cli/store.js +0 -108
- package/dist/config.d.ts +0 -11
- package/dist/config.js +0 -115
- package/dist/cron/scheduler.d.ts +0 -22
- package/dist/cron/scheduler.js +0 -220
- package/dist/cron/store.d.ts +0 -12
- package/dist/cron/store.js +0 -83
- package/dist/daemon.d.ts +0 -1
- package/dist/daemon.js +0 -345
- package/dist/dashboard/html.d.ts +0 -1
- package/dist/dashboard/html.js +0 -849
- package/dist/dashboard/routes.d.ts +0 -17
- package/dist/dashboard/routes.js +0 -623
- package/dist/dashboard/server.d.ts +0 -1
- package/dist/dashboard/server.js +0 -104
- package/dist/db/connection.d.ts +0 -29
- package/dist/db/connection.js +0 -37
- package/dist/db/index.d.ts +0 -14
- package/dist/db/index.js +0 -127
- package/dist/db/migrations.d.ts +0 -2
- package/dist/db/migrations.js +0 -384
- package/dist/delegation/manager.d.ts +0 -18
- package/dist/delegation/manager.js +0 -89
- package/dist/index.d.ts +0 -2
- package/dist/knowledge/index.d.ts +0 -2
- package/dist/knowledge/index.js +0 -1
- package/dist/knowledge/manager.d.ts +0 -17
- package/dist/knowledge/manager.js +0 -160
- package/dist/knowledge/types.d.ts +0 -7
- package/dist/knowledge/types.js +0 -1
- package/dist/machines/forwarder.d.ts +0 -7
- package/dist/machines/forwarder.js +0 -35
- package/dist/machines/index.d.ts +0 -1
- package/dist/machines/index.js +0 -1
- package/dist/machines/registry.d.ts +0 -15
- package/dist/machines/registry.js +0 -46
- package/dist/mcp/handlers.d.ts +0 -37
- package/dist/mcp/handlers.js +0 -520
- package/dist/mcp/server.d.ts +0 -2
- package/dist/mcp/server.js +0 -86
- package/dist/mcp/tool-definitions.d.ts +0 -1225
- package/dist/mcp/tool-definitions.js +0 -412
- package/dist/mcp/validate.d.ts +0 -23
- package/dist/mcp/validate.js +0 -65
- package/dist/media/factory.d.ts +0 -6
- package/dist/media/factory.js +0 -36
- package/dist/media/generators/dall-e.d.ts +0 -10
- package/dist/media/generators/dall-e.js +0 -41
- package/dist/media/generators/elevenlabs-music.d.ts +0 -9
- package/dist/media/generators/elevenlabs-music.js +0 -34
- package/dist/media/generators/elevenlabs-sfx.d.ts +0 -9
- package/dist/media/generators/elevenlabs-sfx.js +0 -33
- package/dist/media/generators/kling.d.ts +0 -9
- package/dist/media/generators/kling.js +0 -47
- package/dist/media/generators/lyria.d.ts +0 -10
- package/dist/media/generators/lyria.js +0 -41
- package/dist/media/generators/nano-banana.d.ts +0 -10
- package/dist/media/generators/nano-banana.js +0 -45
- package/dist/media/generators/poll-util.d.ts +0 -12
- package/dist/media/generators/poll-util.js +0 -22
- package/dist/media/generators/recraft.d.ts +0 -9
- package/dist/media/generators/recraft.js +0 -52
- package/dist/media/generators/runway.d.ts +0 -9
- package/dist/media/generators/runway.js +0 -49
- package/dist/media/generators/stable-diffusion.d.ts +0 -9
- package/dist/media/generators/stable-diffusion.js +0 -35
- package/dist/media/generators/veo.d.ts +0 -9
- package/dist/media/generators/veo.js +0 -32
- package/dist/media/index.d.ts +0 -6
- package/dist/media/index.js +0 -8
- package/dist/media/loader.d.ts +0 -8
- package/dist/media/loader.js +0 -46
- package/dist/media/store.d.ts +0 -17
- package/dist/media/store.js +0 -69
- package/dist/media/types.d.ts +0 -43
- package/dist/media/types.js +0 -1
- package/dist/memory/extractor.d.ts +0 -5
- package/dist/memory/extractor.js +0 -157
- package/dist/notifications/index.d.ts +0 -4
- package/dist/notifications/index.js +0 -21
- package/dist/notifications/ntfy.d.ts +0 -9
- package/dist/notifications/ntfy.js +0 -25
- package/dist/notifications/pushover.d.ts +0 -9
- package/dist/notifications/pushover.js +0 -26
- package/dist/notifications/types.d.ts +0 -5
- package/dist/notifications/types.js +0 -1
- package/dist/notifications/webhook-provider.d.ts +0 -9
- package/dist/notifications/webhook-provider.js +0 -24
- package/dist/observability/analytics.d.ts +0 -30
- package/dist/observability/analytics.js +0 -118
- package/dist/pipe/anthropic-client.d.ts +0 -9
- package/dist/pipe/anthropic-client.js +0 -50
- package/dist/pipe/brain.d.ts +0 -16
- package/dist/pipe/brain.js +0 -89
- package/dist/pipe/memory-store.d.ts +0 -8
- package/dist/pipe/memory-store.js +0 -39
- package/dist/pipe/project-scanner.d.ts +0 -6
- package/dist/pipe/project-scanner.js +0 -26
- package/dist/pipe/types.d.ts +0 -34
- package/dist/pipe/types.js +0 -1
- package/dist/projects/index.d.ts +0 -4
- package/dist/projects/index.js +0 -2
- package/dist/projects/manager.d.ts +0 -13
- package/dist/projects/manager.js +0 -166
- package/dist/projects/router.d.ts +0 -17
- package/dist/projects/router.js +0 -246
- package/dist/projects/types.d.ts +0 -19
- package/dist/projects/types.js +0 -1
- package/dist/service/install.d.ts +0 -4
- package/dist/service/install.js +0 -146
- package/dist/service/templates.d.ts +0 -4
- package/dist/service/templates.js +0 -52
- package/dist/service/windows.d.ts +0 -8
- package/dist/service/windows.js +0 -59
- package/dist/session/budget-guard.d.ts +0 -20
- package/dist/session/budget-guard.js +0 -31
- package/dist/session/circuit-breaker.d.ts +0 -19
- package/dist/session/circuit-breaker.js +0 -91
- package/dist/session/context-compactor.d.ts +0 -32
- package/dist/session/context-compactor.js +0 -45
- package/dist/session/context-monitor.d.ts +0 -15
- package/dist/session/context-monitor.js +0 -44
- package/dist/session/handoff.d.ts +0 -21
- package/dist/session/handoff.js +0 -50
- package/dist/session/manager.d.ts +0 -60
- package/dist/session/manager.js +0 -416
- package/dist/session/memory-store.d.ts +0 -29
- package/dist/session/memory-store.js +0 -45
- package/dist/session/message-queue.d.ts +0 -28
- package/dist/session/message-queue.js +0 -52
- package/dist/session/pending-dispatcher.d.ts +0 -31
- package/dist/session/pending-dispatcher.js +0 -120
- package/dist/session/pending-store.d.ts +0 -60
- package/dist/session/pending-store.js +0 -118
- package/dist/session/stale-session.d.ts +0 -31
- package/dist/session/stale-session.js +0 -45
- package/dist/session/subprocess.d.ts +0 -32
- package/dist/session/subprocess.js +0 -230
- package/dist/session/tab-store.d.ts +0 -28
- package/dist/session/tab-store.js +0 -78
- package/dist/session/tool-classifier.d.ts +0 -4
- package/dist/session/tool-classifier.js +0 -56
- package/dist/tasks/scheduler.d.ts +0 -45
- package/dist/tasks/scheduler.js +0 -329
- package/dist/tasks/store.d.ts +0 -11
- package/dist/tasks/store.js +0 -103
- package/dist/timeline/index.d.ts +0 -3
- package/dist/timeline/index.js +0 -2
- package/dist/timeline/logger.d.ts +0 -8
- package/dist/timeline/logger.js +0 -11
- package/dist/timeline/query.d.ts +0 -11
- package/dist/timeline/query.js +0 -67
- package/dist/timeline/types.d.ts +0 -12
- package/dist/timeline/types.js +0 -1
- package/dist/types.d.ts +0 -225
- package/dist/types.js +0 -3
- package/dist/users/index.d.ts +0 -2
- package/dist/users/index.js +0 -1
- package/dist/users/service.d.ts +0 -17
- package/dist/users/service.js +0 -46
- package/dist/util/auto-heal.d.ts +0 -43
- package/dist/util/auto-heal.js +0 -107
- package/dist/util/install-info.d.ts +0 -37
- package/dist/util/install-info.js +0 -80
- package/dist/util/logger.d.ts +0 -18
- package/dist/util/logger.js +0 -118
- package/dist/util/paths.d.ts +0 -13
- package/dist/util/paths.js +0 -55
- package/dist/util/platform.d.ts +0 -2
- package/dist/util/platform.js +0 -11
- package/dist/util/progress.d.ts +0 -34
- package/dist/util/progress.js +0 -133
- package/dist/util/rate-limiter.d.ts +0 -14
- package/dist/util/rate-limiter.js +0 -50
- package/dist/util/retry.d.ts +0 -2
- package/dist/util/retry.js +0 -18
- package/dist/util/text.d.ts +0 -32
- package/dist/util/text.js +0 -101
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -4
- package/dist/voice/index.d.ts +0 -17
- package/dist/voice/index.js +0 -42
- package/dist/voice/stt.d.ts +0 -16
- package/dist/voice/stt.js +0 -57
- package/dist/voice/tts.d.ts +0 -23
- package/dist/voice/tts.js +0 -84
- package/dist/watchers/evaluator.d.ts +0 -5
- package/dist/watchers/evaluator.js +0 -45
- package/dist/watchers/index.d.ts +0 -4
- package/dist/watchers/index.js +0 -3
- package/dist/watchers/scheduler.d.ts +0 -20
- package/dist/watchers/scheduler.js +0 -183
- package/dist/watchers/store.d.ts +0 -10
- package/dist/watchers/store.js +0 -56
- package/dist/watchers/types.d.ts +0 -15
- package/dist/watchers/types.js +0 -1
- package/scripts/postinstall.mjs +0 -50
package/dist/tasks/scheduler.js
DELETED
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
import { CronExpressionParser } from 'cron-parser';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { exec } from 'node:child_process';
|
|
5
|
-
import { promisify } from 'node:util';
|
|
6
|
-
import { TaskStore } from './store.js';
|
|
7
|
-
import { getCronReloadSignalPath, getLogsDir } from '../util/paths.js';
|
|
8
|
-
import { logger } from '../util/logger.js';
|
|
9
|
-
export const execAsync = promisify(exec);
|
|
10
|
-
/**
|
|
11
|
-
* Disable a task after this many consecutive failures so a misconfigured
|
|
12
|
-
* cron doesn't fire (and notify) every interval indefinitely. Reset on
|
|
13
|
-
* success.
|
|
14
|
-
*/
|
|
15
|
-
const MAX_CONSECUTIVE_FAILURES = 5;
|
|
16
|
-
export class TaskScheduler {
|
|
17
|
-
tabManager;
|
|
18
|
-
onNotify;
|
|
19
|
-
// taskId -> due time in ms epoch
|
|
20
|
-
nextRunAt = new Map();
|
|
21
|
-
// taskIds with an in-flight fireJob
|
|
22
|
-
running = new Set();
|
|
23
|
-
// taskId -> consecutive failure count, used for auto-disable
|
|
24
|
-
failureCounts = new Map();
|
|
25
|
-
stopping = false;
|
|
26
|
-
store = new TaskStore();
|
|
27
|
-
constructor(tabManager, onNotify) {
|
|
28
|
-
this.tabManager = tabManager;
|
|
29
|
-
this.onNotify = onNotify;
|
|
30
|
-
}
|
|
31
|
-
/** Load all tasks from store and schedule them */
|
|
32
|
-
loadAndSchedule() {
|
|
33
|
-
this.nextRunAt.clear();
|
|
34
|
-
this.stopping = false;
|
|
35
|
-
const jobs = this.store.list();
|
|
36
|
-
let scheduled = 0;
|
|
37
|
-
let missedFires = 0;
|
|
38
|
-
for (const job of jobs) {
|
|
39
|
-
if (!job.enabled)
|
|
40
|
-
continue;
|
|
41
|
-
// Fast path: one-time 'at' tasks whose target time has passed without ever firing.
|
|
42
|
-
// (The general tick() loop would catch these too, but this keeps the original
|
|
43
|
-
// single-pass startup behavior ā fire now and disable atomically.)
|
|
44
|
-
if (job.scheduleType === 'at' && !job.lastRunAt) {
|
|
45
|
-
const targetTime = new Date(job.schedule).getTime();
|
|
46
|
-
if (Number.isFinite(targetTime) && targetTime <= Date.now()) {
|
|
47
|
-
logger.warn(`Task: missed fire detected for "${job.name}" (was scheduled for ${job.schedule}), firing now`);
|
|
48
|
-
missedFires++;
|
|
49
|
-
this.store.update(job.id, { enabled: false, nextRunAt: null });
|
|
50
|
-
void this.fireJob(job);
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
// Seed nextRunAt: prefer stored value (catch-up after sleep/restart);
|
|
55
|
-
// otherwise compute fresh from now.
|
|
56
|
-
let due = null;
|
|
57
|
-
if (job.nextRunAt) {
|
|
58
|
-
const stored = new Date(job.nextRunAt).getTime();
|
|
59
|
-
if (Number.isFinite(stored))
|
|
60
|
-
due = stored;
|
|
61
|
-
}
|
|
62
|
-
if (due === null) {
|
|
63
|
-
due = this.computeNextRun(job, Date.now());
|
|
64
|
-
if (due === null)
|
|
65
|
-
continue; // computeNextRun already logged
|
|
66
|
-
this.store.update(job.id, { nextRunAt: new Date(due).toISOString() });
|
|
67
|
-
}
|
|
68
|
-
this.nextRunAt.set(job.id, due);
|
|
69
|
-
scheduled++;
|
|
70
|
-
}
|
|
71
|
-
logger.info(`Tasks: loaded ${scheduled} active tasks (${jobs.length} total)${missedFires > 0 ? `, fired ${missedFires} missed` : ''}`);
|
|
72
|
-
}
|
|
73
|
-
/** Check for the reload signal file and reload if present */
|
|
74
|
-
checkForReload() {
|
|
75
|
-
const signalPath = getCronReloadSignalPath();
|
|
76
|
-
if (fs.existsSync(signalPath)) {
|
|
77
|
-
try {
|
|
78
|
-
fs.unlinkSync(signalPath);
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
/* race condition, ok */
|
|
82
|
-
}
|
|
83
|
-
logger.info('Tasks: reload signal detected, reloading schedules');
|
|
84
|
-
this.loadAndSchedule();
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Fire any tasks whose nextRunAt has elapsed. Called every ~5s by the daemon
|
|
89
|
-
* poll loop. This is the macOS-sleep-resilient replacement for setTimeout-driven
|
|
90
|
-
* cron heartbeats ā after a wake, the next tick catches all overdue tasks.
|
|
91
|
-
*/
|
|
92
|
-
tick() {
|
|
93
|
-
if (this.stopping)
|
|
94
|
-
return;
|
|
95
|
-
const now = Date.now();
|
|
96
|
-
for (const [id, due] of [...this.nextRunAt]) {
|
|
97
|
-
if (this.stopping)
|
|
98
|
-
return;
|
|
99
|
-
if (due > now)
|
|
100
|
-
continue;
|
|
101
|
-
if (this.running.has(id))
|
|
102
|
-
continue;
|
|
103
|
-
const job = this.store.get(id);
|
|
104
|
-
if (!job || !job.enabled) {
|
|
105
|
-
this.nextRunAt.delete(id);
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
// Advance BEFORE firing ā eliminates double-fire race if tick is re-entered
|
|
109
|
-
// before the async fireJob completes.
|
|
110
|
-
if (job.scheduleType === 'at') {
|
|
111
|
-
this.nextRunAt.delete(id);
|
|
112
|
-
this.store.update(id, { enabled: false, nextRunAt: null });
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
const next = this.computeNextRun(job, now);
|
|
116
|
-
if (next === null) {
|
|
117
|
-
this.nextRunAt.delete(id);
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
this.nextRunAt.set(id, next);
|
|
121
|
-
this.store.update(id, { nextRunAt: new Date(next).toISOString() });
|
|
122
|
-
}
|
|
123
|
-
this.running.add(id);
|
|
124
|
-
void this.fireJob(job).finally(() => this.running.delete(id));
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
/** Stop the scheduler. In-flight fireJob promises are detached and resolve naturally. */
|
|
128
|
-
stopAll() {
|
|
129
|
-
this.stopping = true;
|
|
130
|
-
this.nextRunAt.clear();
|
|
131
|
-
}
|
|
132
|
-
/** Compute next run time in ms epoch, given a "from" anchor. Returns null if invalid. */
|
|
133
|
-
computeNextRun(job, fromMs) {
|
|
134
|
-
try {
|
|
135
|
-
switch (job.scheduleType) {
|
|
136
|
-
case 'cron':
|
|
137
|
-
return CronExpressionParser.parse(job.schedule, { currentDate: new Date(fromMs) })
|
|
138
|
-
.next()
|
|
139
|
-
.getTime();
|
|
140
|
-
case 'every': {
|
|
141
|
-
const expr = intervalToCron(job.schedule);
|
|
142
|
-
if (expr) {
|
|
143
|
-
return CronExpressionParser.parse(expr, { currentDate: new Date(fromMs) })
|
|
144
|
-
.next()
|
|
145
|
-
.getTime();
|
|
146
|
-
}
|
|
147
|
-
const ms = intervalToMs(job.schedule);
|
|
148
|
-
return ms ? fromMs + ms : null;
|
|
149
|
-
}
|
|
150
|
-
case 'at': {
|
|
151
|
-
const t = new Date(job.schedule).getTime();
|
|
152
|
-
return Number.isFinite(t) ? t : null;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
catch (err) {
|
|
157
|
-
logger.error(`Task: invalid expression for "${job.name}": ${job.schedule}`, err);
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
async fireJob(job) {
|
|
162
|
-
logger.info(`Task firing: "${job.name}" (${job.payloadType || 'agentTurn'}) -> tab:${job.tabName}`);
|
|
163
|
-
const logFile = path.join(getLogsDir(), `task-${job.name}.log`);
|
|
164
|
-
// Handle systemEvent -- internal Beecork actions, not Claude Code
|
|
165
|
-
if (job.payloadType === 'systemEvent') {
|
|
166
|
-
try {
|
|
167
|
-
await this.handleSystemEvent(job);
|
|
168
|
-
this.store.update(job.id, { lastRunAt: new Date().toISOString() });
|
|
169
|
-
await fs.promises.appendFile(logFile, `[${new Date().toISOString()}] SYSTEM: ${job.message}\n`);
|
|
170
|
-
}
|
|
171
|
-
catch (err) {
|
|
172
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
173
|
-
logger.error(`System event "${job.name}" failed:`, err);
|
|
174
|
-
await fs.promises.appendFile(logFile, `[${new Date().toISOString()}] ERROR: ${errMsg}\n`);
|
|
175
|
-
}
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
try {
|
|
179
|
-
this.tabManager.ensureTab(job.tabName);
|
|
180
|
-
const result = await this.tabManager.sendMessage(job.tabName, job.message);
|
|
181
|
-
this.store.update(job.id, { lastRunAt: new Date().toISOString() });
|
|
182
|
-
const firstLine = result.text.split('\n')[0]?.slice(0, 200) || '(no output)';
|
|
183
|
-
const status = result.error ? 'ERROR' : 'SUCCESS';
|
|
184
|
-
// Log result (status reflects subprocess exit / is_error, not just completion)
|
|
185
|
-
await fs.promises.appendFile(logFile, `[${new Date().toISOString()}] ${status}: ${firstLine}\n`);
|
|
186
|
-
if (result.error) {
|
|
187
|
-
this.recordFailure(job);
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
this.failureCounts.delete(job.id);
|
|
191
|
-
}
|
|
192
|
-
// Notify (separate try/catch -- notification failure shouldn't be reported as job failure)
|
|
193
|
-
try {
|
|
194
|
-
if (this.onNotify) {
|
|
195
|
-
if (result.error) {
|
|
196
|
-
await this.onNotify(`[${job.name}] Failed -- ${firstLine}`);
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
await this.onNotify(`[${job.name}] Done -- ${firstLine}`);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
catch (notifyErr) {
|
|
204
|
-
logger.warn(`Task "${job.name}" notification failed:`, notifyErr);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
catch (err) {
|
|
208
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
209
|
-
logger.error(`Task "${job.name}" failed:`, err);
|
|
210
|
-
await fs.promises.appendFile(logFile, `[${new Date().toISOString()}] ERROR: ${errMsg}\n`);
|
|
211
|
-
this.recordFailure(job);
|
|
212
|
-
try {
|
|
213
|
-
await this.onNotify?.(`[${job.name}] Failed -- ${errMsg}`);
|
|
214
|
-
}
|
|
215
|
-
catch {
|
|
216
|
-
/* notification best-effort */
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* Track consecutive failures per task. After MAX_CONSECUTIVE_FAILURES the
|
|
222
|
-
* task is auto-disabled in the DB and a single "auto-disabled" notification
|
|
223
|
-
* is sent. Counter resets on the next successful fire.
|
|
224
|
-
*/
|
|
225
|
-
recordFailure(job) {
|
|
226
|
-
const count = (this.failureCounts.get(job.id) ?? 0) + 1;
|
|
227
|
-
this.failureCounts.set(job.id, count);
|
|
228
|
-
if (count >= MAX_CONSECUTIVE_FAILURES) {
|
|
229
|
-
logger.warn(`Task "${job.name}" hit ${count} consecutive failures ā auto-disabling`);
|
|
230
|
-
this.store.update(job.id, { enabled: false });
|
|
231
|
-
this.nextRunAt.delete(job.id);
|
|
232
|
-
this.failureCounts.delete(job.id);
|
|
233
|
-
// Notify once. Subsequent enable + re-failure will trigger again.
|
|
234
|
-
this.onNotify?.(`Task "${job.name}" auto-disabled after ${count} consecutive failures. Re-enable from the dashboard or via beecork_task_update.`).catch(() => { });
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
async handleSystemEvent(job) {
|
|
238
|
-
switch (job.message) {
|
|
239
|
-
case 'health_check':
|
|
240
|
-
logger.info('System event: health check -- daemon alive');
|
|
241
|
-
if (this.onNotify) {
|
|
242
|
-
await this.onNotify('Beecork health check: all systems operational');
|
|
243
|
-
}
|
|
244
|
-
break;
|
|
245
|
-
default:
|
|
246
|
-
logger.warn(`Unknown system event: ${job.message}`);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
/** Parse a "1w2d3h45m"-style interval into its parts. Returns null on invalid input. */
|
|
251
|
-
function parseInterval(interval) {
|
|
252
|
-
const match = interval.match(/^(?:(\d+)w)?(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?$/);
|
|
253
|
-
if (!match || match.slice(1).every((g) => g === undefined))
|
|
254
|
-
return null;
|
|
255
|
-
return {
|
|
256
|
-
weeks: parseInt(match[1] || '0', 10),
|
|
257
|
-
days: parseInt(match[2] || '0', 10),
|
|
258
|
-
hours: parseInt(match[3] || '0', 10),
|
|
259
|
-
mins: parseInt(match[4] || '0', 10),
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
/** Convert human interval (30m, 2h, 1d, 1h30m, 2w) to milliseconds */
|
|
263
|
-
export function intervalToMs(interval) {
|
|
264
|
-
const parts = parseInterval(interval);
|
|
265
|
-
if (!parts)
|
|
266
|
-
return null;
|
|
267
|
-
const { weeks, days, hours, mins } = parts;
|
|
268
|
-
const totalMs = (weeks * 7 * 24 * 60 + days * 24 * 60 + hours * 60 + mins) * 60 * 1000;
|
|
269
|
-
return totalMs > 0 ? totalMs : null;
|
|
270
|
-
}
|
|
271
|
-
/** Convert human interval (30m, 2h, 1d, 1h30m, 2w) to cron expression */
|
|
272
|
-
export function intervalToCron(interval) {
|
|
273
|
-
const parts = parseInterval(interval);
|
|
274
|
-
if (!parts)
|
|
275
|
-
return null;
|
|
276
|
-
const { weeks, days, hours, mins } = parts;
|
|
277
|
-
// Convert to total minutes for simple intervals
|
|
278
|
-
const totalMins = weeks * 7 * 24 * 60 + days * 24 * 60 + hours * 60 + mins;
|
|
279
|
-
if (totalMins <= 0)
|
|
280
|
-
return null;
|
|
281
|
-
// Simple minute interval
|
|
282
|
-
if (totalMins <= 59)
|
|
283
|
-
return `*/${totalMins} * * * *`;
|
|
284
|
-
// Hourly intervals
|
|
285
|
-
if (mins === 0 && days === 0 && weeks === 0 && hours > 0 && hours <= 23)
|
|
286
|
-
return `0 */${hours} * * *`;
|
|
287
|
-
// Daily intervals
|
|
288
|
-
if (mins === 0 && hours === 0 && weeks === 0 && days > 0)
|
|
289
|
-
return `0 0 */${days} * *`;
|
|
290
|
-
// Weekly intervals
|
|
291
|
-
if (mins === 0 && hours === 0 && days === 0 && weeks > 0)
|
|
292
|
-
return `0 0 * * 0`;
|
|
293
|
-
// Combined or large intervals -- return null, handled by intervalToMs fallback
|
|
294
|
-
return null;
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Validate a schedule string for a given scheduleType. Returns an error string
|
|
298
|
-
* on invalid input or null when valid. Used by MCP + dashboard before insert
|
|
299
|
-
* so misconfigured tasks fail loud instead of silently never firing.
|
|
300
|
-
*/
|
|
301
|
-
export function validateSchedule(scheduleType, schedule) {
|
|
302
|
-
if (!schedule)
|
|
303
|
-
return 'schedule is required';
|
|
304
|
-
switch (scheduleType) {
|
|
305
|
-
case 'at': {
|
|
306
|
-
const ts = Date.parse(schedule);
|
|
307
|
-
if (Number.isNaN(ts))
|
|
308
|
-
return `"${schedule}" is not a valid ISO datetime`;
|
|
309
|
-
return null;
|
|
310
|
-
}
|
|
311
|
-
case 'every': {
|
|
312
|
-
if (intervalToMs(schedule) === null) {
|
|
313
|
-
return `"${schedule}" is not a valid interval. Use formats like "30m", "2h", "1d", "1h30m".`;
|
|
314
|
-
}
|
|
315
|
-
return null;
|
|
316
|
-
}
|
|
317
|
-
case 'cron': {
|
|
318
|
-
try {
|
|
319
|
-
CronExpressionParser.parse(schedule);
|
|
320
|
-
return null;
|
|
321
|
-
}
|
|
322
|
-
catch (err) {
|
|
323
|
-
return `"${schedule}" is not a valid cron expression: ${err instanceof Error ? err.message : String(err)}`;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
default:
|
|
327
|
-
return `unknown scheduleType "${scheduleType}"`;
|
|
328
|
-
}
|
|
329
|
-
}
|
package/dist/tasks/store.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { Task } from '../types.js';
|
|
2
|
-
export declare class TaskStore {
|
|
3
|
-
constructor();
|
|
4
|
-
list(): Task[];
|
|
5
|
-
get(id: string): Task | undefined;
|
|
6
|
-
add(job: Task): void;
|
|
7
|
-
update(id: string, updates: Partial<Task>): boolean;
|
|
8
|
-
delete(id: string): boolean;
|
|
9
|
-
/** One-time migration from crontab.json to SQLite */
|
|
10
|
-
private migrateFromJson;
|
|
11
|
-
}
|
package/dist/tasks/store.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import { getDb } from '../db/index.js';
|
|
3
|
-
import { getCrontabPath } from '../util/paths.js';
|
|
4
|
-
import { logger } from '../util/logger.js';
|
|
5
|
-
function rowToTask(row) {
|
|
6
|
-
return {
|
|
7
|
-
id: row.id,
|
|
8
|
-
name: row.name,
|
|
9
|
-
scheduleType: row.schedule_type,
|
|
10
|
-
schedule: row.schedule,
|
|
11
|
-
tabName: row.tab_name,
|
|
12
|
-
message: row.message,
|
|
13
|
-
payloadType: row.payload_type || 'agentTurn',
|
|
14
|
-
enabled: row.enabled === 1,
|
|
15
|
-
createdAt: row.created_at,
|
|
16
|
-
lastRunAt: row.last_run_at,
|
|
17
|
-
nextRunAt: row.next_run_at,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
// One-shot JSON migration only needs to run once per process lifetime, but the
|
|
21
|
-
// dashboard creates a fresh TaskStore per request and MCP creates one per task
|
|
22
|
-
// call. Without this flag, every instantiation re-fired existsSync + COUNT(*).
|
|
23
|
-
let migrationChecked = false;
|
|
24
|
-
export class TaskStore {
|
|
25
|
-
constructor() {
|
|
26
|
-
if (!migrationChecked) {
|
|
27
|
-
migrationChecked = true;
|
|
28
|
-
this.migrateFromJson();
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
list() {
|
|
32
|
-
const db = getDb();
|
|
33
|
-
return db.prepare('SELECT * FROM tasks ORDER BY created_at').all().map(rowToTask);
|
|
34
|
-
}
|
|
35
|
-
get(id) {
|
|
36
|
-
const db = getDb();
|
|
37
|
-
const row = db.prepare('SELECT * FROM tasks WHERE id = ?').get(id);
|
|
38
|
-
return row ? rowToTask(row) : undefined;
|
|
39
|
-
}
|
|
40
|
-
add(job) {
|
|
41
|
-
const db = getDb();
|
|
42
|
-
db.prepare(`INSERT INTO tasks (id, name, schedule_type, schedule, tab_name, message, payload_type, enabled, created_at, last_run_at, next_run_at)
|
|
43
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(job.id, job.name, job.scheduleType, job.schedule, job.tabName, job.message, job.payloadType || 'agentTurn', job.enabled ? 1 : 0, job.createdAt, job.lastRunAt, job.nextRunAt);
|
|
44
|
-
}
|
|
45
|
-
update(id, updates) {
|
|
46
|
-
const db = getDb();
|
|
47
|
-
const existing = this.get(id);
|
|
48
|
-
if (!existing)
|
|
49
|
-
return false;
|
|
50
|
-
// If the schedule changes, the stored next_run_at is no longer valid ā
|
|
51
|
-
// null it out so the scheduler recomputes from the new expression on
|
|
52
|
-
// next load. Caller can override by explicitly passing nextRunAt.
|
|
53
|
-
const scheduleChanged = (updates.schedule !== undefined && updates.schedule !== existing.schedule) ||
|
|
54
|
-
(updates.scheduleType !== undefined && updates.scheduleType !== existing.scheduleType);
|
|
55
|
-
const merged = { ...existing, ...updates };
|
|
56
|
-
if (scheduleChanged && updates.nextRunAt === undefined)
|
|
57
|
-
merged.nextRunAt = null;
|
|
58
|
-
db.prepare(`UPDATE tasks SET name=?, schedule_type=?, schedule=?, tab_name=?, message=?, enabled=?, last_run_at=?, next_run_at=? WHERE id=?`).run(merged.name, merged.scheduleType, merged.schedule, merged.tabName, merged.message, merged.enabled ? 1 : 0, merged.lastRunAt, merged.nextRunAt, id);
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
delete(id) {
|
|
62
|
-
const db = getDb();
|
|
63
|
-
const result = db.prepare('DELETE FROM tasks WHERE id = ?').run(id);
|
|
64
|
-
return result.changes > 0;
|
|
65
|
-
}
|
|
66
|
-
/** One-time migration from crontab.json to SQLite */
|
|
67
|
-
migrateFromJson() {
|
|
68
|
-
const jsonPath = getCrontabPath();
|
|
69
|
-
if (!fs.existsSync(jsonPath))
|
|
70
|
-
return;
|
|
71
|
-
const db = getDb();
|
|
72
|
-
const count = db.prepare('SELECT COUNT(*) as count FROM tasks').get();
|
|
73
|
-
if (count.count > 0) {
|
|
74
|
-
// Already migrated, clean up JSON
|
|
75
|
-
try {
|
|
76
|
-
fs.renameSync(jsonPath, jsonPath + '.bak');
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
/* ok */
|
|
80
|
-
}
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
try {
|
|
84
|
-
const data = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
|
|
85
|
-
const jobs = data.jobs || [];
|
|
86
|
-
if (jobs.length === 0)
|
|
87
|
-
return;
|
|
88
|
-
const insert = db.prepare(`INSERT OR IGNORE INTO tasks (id, name, schedule_type, schedule, tab_name, message, enabled, created_at, last_run_at, next_run_at)
|
|
89
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
90
|
-
const tx = db.transaction(() => {
|
|
91
|
-
for (const j of jobs) {
|
|
92
|
-
insert.run(j.id, j.name, j.scheduleType, j.schedule, j.tabName || 'default', j.message, j.enabled ? 1 : 0, j.createdAt, j.lastRunAt, j.nextRunAt);
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
tx();
|
|
96
|
-
fs.renameSync(jsonPath, jsonPath + '.bak');
|
|
97
|
-
logger.info(`Migrated ${jobs.length} tasks from JSON to SQLite`);
|
|
98
|
-
}
|
|
99
|
-
catch (err) {
|
|
100
|
-
logger.error('Failed to migrate tasks from JSON:', err);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
package/dist/timeline/index.d.ts
DELETED
package/dist/timeline/index.js
DELETED
package/dist/timeline/logger.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
-
import { getDb } from '../db/index.js';
|
|
3
|
-
export function logActivity(eventType, summary, options) {
|
|
4
|
-
try {
|
|
5
|
-
const db = getDb();
|
|
6
|
-
db.prepare('INSERT INTO activity_log (id, event_type, project_name, tab_name, summary, details, duration_ms, cost_usd) VALUES (?, ?, ?, ?, ?, ?, ?, ?)').run(uuidv4(), eventType, options?.projectName || null, options?.tabName || null, summary, options?.details || null, options?.durationMs || null, options?.costUsd || null);
|
|
7
|
-
}
|
|
8
|
-
catch {
|
|
9
|
-
/* non-critical ā don't crash if logging fails */
|
|
10
|
-
}
|
|
11
|
-
}
|
package/dist/timeline/query.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { ActivityEvent } from './types.js';
|
|
2
|
-
export declare function getTimeline(options?: {
|
|
3
|
-
date?: string;
|
|
4
|
-
tabName?: string;
|
|
5
|
-
limit?: number;
|
|
6
|
-
}): ActivityEvent[];
|
|
7
|
-
export declare function formatTimeline(events: ActivityEvent[]): string;
|
|
8
|
-
export declare function getReplayInfo(eventId: string): {
|
|
9
|
-
tabName: string;
|
|
10
|
-
message: string;
|
|
11
|
-
} | null;
|
package/dist/timeline/query.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { getDb } from '../db/index.js';
|
|
2
|
-
function rowToEvent(r) {
|
|
3
|
-
return {
|
|
4
|
-
id: r.id,
|
|
5
|
-
eventType: r.event_type,
|
|
6
|
-
projectName: r.project_name,
|
|
7
|
-
tabName: r.tab_name,
|
|
8
|
-
summary: r.summary,
|
|
9
|
-
details: r.details,
|
|
10
|
-
durationMs: r.duration_ms,
|
|
11
|
-
costUsd: r.cost_usd,
|
|
12
|
-
createdAt: r.created_at,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
export function getTimeline(options) {
|
|
16
|
-
const db = getDb();
|
|
17
|
-
let query = 'SELECT * FROM activity_log';
|
|
18
|
-
const conditions = [];
|
|
19
|
-
const params = [];
|
|
20
|
-
if (options?.date) {
|
|
21
|
-
// Sargable range so idx_activity_log_created can serve the query.
|
|
22
|
-
// `date(created_at) = ?` is non-sargable and forces a full scan.
|
|
23
|
-
const dayStart = `${options.date} 00:00:00`;
|
|
24
|
-
const dayEnd = `${options.date} 23:59:59.999`;
|
|
25
|
-
conditions.push('created_at >= ? AND created_at <= ?');
|
|
26
|
-
params.push(dayStart, dayEnd);
|
|
27
|
-
}
|
|
28
|
-
if (options?.tabName) {
|
|
29
|
-
conditions.push('tab_name = ?');
|
|
30
|
-
params.push(options.tabName);
|
|
31
|
-
}
|
|
32
|
-
if (conditions.length > 0)
|
|
33
|
-
query += ' WHERE ' + conditions.join(' AND ');
|
|
34
|
-
query += ' ORDER BY created_at DESC';
|
|
35
|
-
query += ' LIMIT ?';
|
|
36
|
-
params.push(options?.limit || 50);
|
|
37
|
-
return db.prepare(query).all(...params).map(rowToEvent);
|
|
38
|
-
}
|
|
39
|
-
export function formatTimeline(events) {
|
|
40
|
-
if (events.length === 0)
|
|
41
|
-
return 'No activity recorded.';
|
|
42
|
-
const byDate = new Map();
|
|
43
|
-
for (const event of events.reverse()) {
|
|
44
|
-
const date = event.createdAt.slice(0, 10);
|
|
45
|
-
if (!byDate.has(date))
|
|
46
|
-
byDate.set(date, []);
|
|
47
|
-
byDate.get(date).push(event);
|
|
48
|
-
}
|
|
49
|
-
const lines = [];
|
|
50
|
-
for (const [date, dayEvents] of byDate) {
|
|
51
|
-
lines.push(`\nš
${date}`);
|
|
52
|
-
for (const e of dayEvents) {
|
|
53
|
-
const time = e.createdAt.slice(11, 16);
|
|
54
|
-
const tab = e.tabName ? `[${e.tabName}]` : '';
|
|
55
|
-
const cost = e.costUsd ? ` $${e.costUsd.toFixed(4)}` : '';
|
|
56
|
-
lines.push(` ${time} ${tab} ${e.summary}${cost}`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return lines.join('\n');
|
|
60
|
-
}
|
|
61
|
-
export function getReplayInfo(eventId) {
|
|
62
|
-
const db = getDb();
|
|
63
|
-
const event = db.prepare('SELECT * FROM activity_log WHERE id = ?').get(eventId);
|
|
64
|
-
if (!event || !event.tab_name || !event.details)
|
|
65
|
-
return null;
|
|
66
|
-
return { tabName: event.tab_name, message: event.details };
|
|
67
|
-
}
|
package/dist/timeline/types.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export type EventType = 'task_started' | 'task_completed' | 'task_failed' | 'watcher_triggered' | 'media_generated' | 'delegation_completed' | 'user_command' | 'system_event';
|
|
2
|
-
export interface ActivityEvent {
|
|
3
|
-
id: string;
|
|
4
|
-
eventType: EventType;
|
|
5
|
-
projectName: string | null;
|
|
6
|
-
tabName: string | null;
|
|
7
|
-
summary: string;
|
|
8
|
-
details: string | null;
|
|
9
|
-
durationMs: number | null;
|
|
10
|
-
costUsd: number | null;
|
|
11
|
-
createdAt: string;
|
|
12
|
-
}
|
package/dist/timeline/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|