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.
Files changed (253) hide show
  1. package/README.md +52 -111
  2. package/dist/index.js +1394 -581
  3. package/package.json +26 -54
  4. package/dist/capabilities/index.d.ts +0 -3
  5. package/dist/capabilities/index.js +0 -2
  6. package/dist/capabilities/manager.d.ts +0 -13
  7. package/dist/capabilities/manager.js +0 -115
  8. package/dist/capabilities/packs.d.ts +0 -2
  9. package/dist/capabilities/packs.js +0 -54
  10. package/dist/capabilities/types.d.ts +0 -21
  11. package/dist/capabilities/types.js +0 -1
  12. package/dist/channels/admin.d.ts +0 -10
  13. package/dist/channels/admin.js +0 -20
  14. package/dist/channels/command-handler.d.ts +0 -19
  15. package/dist/channels/command-handler.js +0 -235
  16. package/dist/channels/discord.d.ts +0 -17
  17. package/dist/channels/discord.js +0 -266
  18. package/dist/channels/index.d.ts +0 -7
  19. package/dist/channels/index.js +0 -6
  20. package/dist/channels/loader.d.ts +0 -7
  21. package/dist/channels/loader.js +0 -87
  22. package/dist/channels/pipeline.d.ts +0 -50
  23. package/dist/channels/pipeline.js +0 -92
  24. package/dist/channels/registry.d.ts +0 -36
  25. package/dist/channels/registry.js +0 -80
  26. package/dist/channels/send-helpers.d.ts +0 -19
  27. package/dist/channels/send-helpers.js +0 -21
  28. package/dist/channels/telegram.d.ts +0 -47
  29. package/dist/channels/telegram.js +0 -649
  30. package/dist/channels/types.d.ts +0 -41
  31. package/dist/channels/types.js +0 -1
  32. package/dist/channels/voice-state.d.ts +0 -29
  33. package/dist/channels/voice-state.js +0 -45
  34. package/dist/channels/webhook.d.ts +0 -16
  35. package/dist/channels/webhook.js +0 -222
  36. package/dist/channels/whatsapp.d.ts +0 -28
  37. package/dist/channels/whatsapp.js +0 -323
  38. package/dist/cli/capabilities.d.ts +0 -3
  39. package/dist/cli/capabilities.js +0 -72
  40. package/dist/cli/channel.d.ts +0 -3
  41. package/dist/cli/channel.js +0 -202
  42. package/dist/cli/commands.d.ts +0 -15
  43. package/dist/cli/commands.js +0 -279
  44. package/dist/cli/doctor.d.ts +0 -1
  45. package/dist/cli/doctor.js +0 -208
  46. package/dist/cli/handoff.d.ts +0 -8
  47. package/dist/cli/handoff.js +0 -33
  48. package/dist/cli/helpers.d.ts +0 -2
  49. package/dist/cli/helpers.js +0 -21
  50. package/dist/cli/mcp.d.ts +0 -3
  51. package/dist/cli/mcp.js +0 -47
  52. package/dist/cli/media.d.ts +0 -2
  53. package/dist/cli/media.js +0 -125
  54. package/dist/cli/setup.d.ts +0 -1
  55. package/dist/cli/setup.js +0 -239
  56. package/dist/cli/store.d.ts +0 -3
  57. package/dist/cli/store.js +0 -108
  58. package/dist/config.d.ts +0 -11
  59. package/dist/config.js +0 -115
  60. package/dist/cron/scheduler.d.ts +0 -22
  61. package/dist/cron/scheduler.js +0 -220
  62. package/dist/cron/store.d.ts +0 -12
  63. package/dist/cron/store.js +0 -83
  64. package/dist/daemon.d.ts +0 -1
  65. package/dist/daemon.js +0 -345
  66. package/dist/dashboard/html.d.ts +0 -1
  67. package/dist/dashboard/html.js +0 -849
  68. package/dist/dashboard/routes.d.ts +0 -17
  69. package/dist/dashboard/routes.js +0 -623
  70. package/dist/dashboard/server.d.ts +0 -1
  71. package/dist/dashboard/server.js +0 -104
  72. package/dist/db/connection.d.ts +0 -29
  73. package/dist/db/connection.js +0 -37
  74. package/dist/db/index.d.ts +0 -14
  75. package/dist/db/index.js +0 -127
  76. package/dist/db/migrations.d.ts +0 -2
  77. package/dist/db/migrations.js +0 -384
  78. package/dist/delegation/manager.d.ts +0 -18
  79. package/dist/delegation/manager.js +0 -89
  80. package/dist/index.d.ts +0 -2
  81. package/dist/knowledge/index.d.ts +0 -2
  82. package/dist/knowledge/index.js +0 -1
  83. package/dist/knowledge/manager.d.ts +0 -17
  84. package/dist/knowledge/manager.js +0 -160
  85. package/dist/knowledge/types.d.ts +0 -7
  86. package/dist/knowledge/types.js +0 -1
  87. package/dist/machines/forwarder.d.ts +0 -7
  88. package/dist/machines/forwarder.js +0 -35
  89. package/dist/machines/index.d.ts +0 -1
  90. package/dist/machines/index.js +0 -1
  91. package/dist/machines/registry.d.ts +0 -15
  92. package/dist/machines/registry.js +0 -46
  93. package/dist/mcp/handlers.d.ts +0 -37
  94. package/dist/mcp/handlers.js +0 -520
  95. package/dist/mcp/server.d.ts +0 -2
  96. package/dist/mcp/server.js +0 -86
  97. package/dist/mcp/tool-definitions.d.ts +0 -1225
  98. package/dist/mcp/tool-definitions.js +0 -412
  99. package/dist/mcp/validate.d.ts +0 -23
  100. package/dist/mcp/validate.js +0 -65
  101. package/dist/media/factory.d.ts +0 -6
  102. package/dist/media/factory.js +0 -36
  103. package/dist/media/generators/dall-e.d.ts +0 -10
  104. package/dist/media/generators/dall-e.js +0 -41
  105. package/dist/media/generators/elevenlabs-music.d.ts +0 -9
  106. package/dist/media/generators/elevenlabs-music.js +0 -34
  107. package/dist/media/generators/elevenlabs-sfx.d.ts +0 -9
  108. package/dist/media/generators/elevenlabs-sfx.js +0 -33
  109. package/dist/media/generators/kling.d.ts +0 -9
  110. package/dist/media/generators/kling.js +0 -47
  111. package/dist/media/generators/lyria.d.ts +0 -10
  112. package/dist/media/generators/lyria.js +0 -41
  113. package/dist/media/generators/nano-banana.d.ts +0 -10
  114. package/dist/media/generators/nano-banana.js +0 -45
  115. package/dist/media/generators/poll-util.d.ts +0 -12
  116. package/dist/media/generators/poll-util.js +0 -22
  117. package/dist/media/generators/recraft.d.ts +0 -9
  118. package/dist/media/generators/recraft.js +0 -52
  119. package/dist/media/generators/runway.d.ts +0 -9
  120. package/dist/media/generators/runway.js +0 -49
  121. package/dist/media/generators/stable-diffusion.d.ts +0 -9
  122. package/dist/media/generators/stable-diffusion.js +0 -35
  123. package/dist/media/generators/veo.d.ts +0 -9
  124. package/dist/media/generators/veo.js +0 -32
  125. package/dist/media/index.d.ts +0 -6
  126. package/dist/media/index.js +0 -8
  127. package/dist/media/loader.d.ts +0 -8
  128. package/dist/media/loader.js +0 -46
  129. package/dist/media/store.d.ts +0 -17
  130. package/dist/media/store.js +0 -69
  131. package/dist/media/types.d.ts +0 -43
  132. package/dist/media/types.js +0 -1
  133. package/dist/memory/extractor.d.ts +0 -5
  134. package/dist/memory/extractor.js +0 -157
  135. package/dist/notifications/index.d.ts +0 -4
  136. package/dist/notifications/index.js +0 -21
  137. package/dist/notifications/ntfy.d.ts +0 -9
  138. package/dist/notifications/ntfy.js +0 -25
  139. package/dist/notifications/pushover.d.ts +0 -9
  140. package/dist/notifications/pushover.js +0 -26
  141. package/dist/notifications/types.d.ts +0 -5
  142. package/dist/notifications/types.js +0 -1
  143. package/dist/notifications/webhook-provider.d.ts +0 -9
  144. package/dist/notifications/webhook-provider.js +0 -24
  145. package/dist/observability/analytics.d.ts +0 -30
  146. package/dist/observability/analytics.js +0 -118
  147. package/dist/pipe/anthropic-client.d.ts +0 -9
  148. package/dist/pipe/anthropic-client.js +0 -50
  149. package/dist/pipe/brain.d.ts +0 -16
  150. package/dist/pipe/brain.js +0 -89
  151. package/dist/pipe/memory-store.d.ts +0 -8
  152. package/dist/pipe/memory-store.js +0 -39
  153. package/dist/pipe/project-scanner.d.ts +0 -6
  154. package/dist/pipe/project-scanner.js +0 -26
  155. package/dist/pipe/types.d.ts +0 -34
  156. package/dist/pipe/types.js +0 -1
  157. package/dist/projects/index.d.ts +0 -4
  158. package/dist/projects/index.js +0 -2
  159. package/dist/projects/manager.d.ts +0 -13
  160. package/dist/projects/manager.js +0 -166
  161. package/dist/projects/router.d.ts +0 -17
  162. package/dist/projects/router.js +0 -246
  163. package/dist/projects/types.d.ts +0 -19
  164. package/dist/projects/types.js +0 -1
  165. package/dist/service/install.d.ts +0 -4
  166. package/dist/service/install.js +0 -146
  167. package/dist/service/templates.d.ts +0 -4
  168. package/dist/service/templates.js +0 -52
  169. package/dist/service/windows.d.ts +0 -8
  170. package/dist/service/windows.js +0 -59
  171. package/dist/session/budget-guard.d.ts +0 -20
  172. package/dist/session/budget-guard.js +0 -31
  173. package/dist/session/circuit-breaker.d.ts +0 -19
  174. package/dist/session/circuit-breaker.js +0 -91
  175. package/dist/session/context-compactor.d.ts +0 -32
  176. package/dist/session/context-compactor.js +0 -45
  177. package/dist/session/context-monitor.d.ts +0 -15
  178. package/dist/session/context-monitor.js +0 -44
  179. package/dist/session/handoff.d.ts +0 -21
  180. package/dist/session/handoff.js +0 -50
  181. package/dist/session/manager.d.ts +0 -60
  182. package/dist/session/manager.js +0 -416
  183. package/dist/session/memory-store.d.ts +0 -29
  184. package/dist/session/memory-store.js +0 -45
  185. package/dist/session/message-queue.d.ts +0 -28
  186. package/dist/session/message-queue.js +0 -52
  187. package/dist/session/pending-dispatcher.d.ts +0 -31
  188. package/dist/session/pending-dispatcher.js +0 -120
  189. package/dist/session/pending-store.d.ts +0 -60
  190. package/dist/session/pending-store.js +0 -118
  191. package/dist/session/stale-session.d.ts +0 -31
  192. package/dist/session/stale-session.js +0 -45
  193. package/dist/session/subprocess.d.ts +0 -32
  194. package/dist/session/subprocess.js +0 -230
  195. package/dist/session/tab-store.d.ts +0 -28
  196. package/dist/session/tab-store.js +0 -78
  197. package/dist/session/tool-classifier.d.ts +0 -4
  198. package/dist/session/tool-classifier.js +0 -56
  199. package/dist/tasks/scheduler.d.ts +0 -45
  200. package/dist/tasks/scheduler.js +0 -329
  201. package/dist/tasks/store.d.ts +0 -11
  202. package/dist/tasks/store.js +0 -103
  203. package/dist/timeline/index.d.ts +0 -3
  204. package/dist/timeline/index.js +0 -2
  205. package/dist/timeline/logger.d.ts +0 -8
  206. package/dist/timeline/logger.js +0 -11
  207. package/dist/timeline/query.d.ts +0 -11
  208. package/dist/timeline/query.js +0 -67
  209. package/dist/timeline/types.d.ts +0 -12
  210. package/dist/timeline/types.js +0 -1
  211. package/dist/types.d.ts +0 -225
  212. package/dist/types.js +0 -3
  213. package/dist/users/index.d.ts +0 -2
  214. package/dist/users/index.js +0 -1
  215. package/dist/users/service.d.ts +0 -17
  216. package/dist/users/service.js +0 -46
  217. package/dist/util/auto-heal.d.ts +0 -43
  218. package/dist/util/auto-heal.js +0 -107
  219. package/dist/util/install-info.d.ts +0 -37
  220. package/dist/util/install-info.js +0 -80
  221. package/dist/util/logger.d.ts +0 -18
  222. package/dist/util/logger.js +0 -118
  223. package/dist/util/paths.d.ts +0 -13
  224. package/dist/util/paths.js +0 -55
  225. package/dist/util/platform.d.ts +0 -2
  226. package/dist/util/platform.js +0 -11
  227. package/dist/util/progress.d.ts +0 -34
  228. package/dist/util/progress.js +0 -133
  229. package/dist/util/rate-limiter.d.ts +0 -14
  230. package/dist/util/rate-limiter.js +0 -50
  231. package/dist/util/retry.d.ts +0 -2
  232. package/dist/util/retry.js +0 -18
  233. package/dist/util/text.d.ts +0 -32
  234. package/dist/util/text.js +0 -101
  235. package/dist/version.d.ts +0 -1
  236. package/dist/version.js +0 -4
  237. package/dist/voice/index.d.ts +0 -17
  238. package/dist/voice/index.js +0 -42
  239. package/dist/voice/stt.d.ts +0 -16
  240. package/dist/voice/stt.js +0 -57
  241. package/dist/voice/tts.d.ts +0 -23
  242. package/dist/voice/tts.js +0 -84
  243. package/dist/watchers/evaluator.d.ts +0 -5
  244. package/dist/watchers/evaluator.js +0 -45
  245. package/dist/watchers/index.d.ts +0 -4
  246. package/dist/watchers/index.js +0 -3
  247. package/dist/watchers/scheduler.d.ts +0 -20
  248. package/dist/watchers/scheduler.js +0 -183
  249. package/dist/watchers/store.d.ts +0 -10
  250. package/dist/watchers/store.js +0 -56
  251. package/dist/watchers/types.d.ts +0 -15
  252. package/dist/watchers/types.js +0 -1
  253. package/scripts/postinstall.mjs +0 -50
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -1,3 +0,0 @@
1
- export type { ActivityEvent, EventType } from './types.js';
2
- export { logActivity } from './logger.js';
3
- export { getTimeline, formatTimeline, getReplayInfo } from './query.js';
@@ -1,2 +0,0 @@
1
- export { logActivity } from './logger.js';
2
- export { getTimeline, formatTimeline, getReplayInfo } from './query.js';
@@ -1,8 +0,0 @@
1
- import type { EventType } from './types.js';
2
- export declare function logActivity(eventType: EventType, summary: string, options?: {
3
- projectName?: string;
4
- tabName?: string;
5
- details?: string;
6
- durationMs?: number;
7
- costUsd?: number;
8
- }): void;
@@ -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
- }
@@ -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;
@@ -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
- }
@@ -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
- }
@@ -1 +0,0 @@
1
- export {};