verybot 0.1.3

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.

Potentially problematic release.


This version of verybot might be problematic. Click here for more details.

Files changed (244) hide show
  1. package/dist/brain/agent-registry.d.ts +75 -0
  2. package/dist/brain/agent-registry.js +124 -0
  3. package/dist/brain/agent.d.ts +146 -0
  4. package/dist/brain/agent.js +680 -0
  5. package/dist/brain/channel-store.d.ts +27 -0
  6. package/dist/brain/channel-store.js +78 -0
  7. package/dist/brain/compaction.d.ts +37 -0
  8. package/dist/brain/compaction.js +214 -0
  9. package/dist/brain/context.d.ts +33 -0
  10. package/dist/brain/context.js +77 -0
  11. package/dist/brain/delegation-store.d.ts +33 -0
  12. package/dist/brain/delegation-store.js +106 -0
  13. package/dist/brain/loop.d.ts +21 -0
  14. package/dist/brain/loop.js +161 -0
  15. package/dist/brain/mcp-adapter.d.ts +39 -0
  16. package/dist/brain/mcp-adapter.js +227 -0
  17. package/dist/brain/memory-extractor.d.ts +26 -0
  18. package/dist/brain/memory-extractor.js +82 -0
  19. package/dist/brain/providers.d.ts +10 -0
  20. package/dist/brain/providers.js +69 -0
  21. package/dist/brain/queue.d.ts +18 -0
  22. package/dist/brain/queue.js +84 -0
  23. package/dist/brain/run-tools.d.ts +47 -0
  24. package/dist/brain/run-tools.js +84 -0
  25. package/dist/brain/session-key.d.ts +23 -0
  26. package/dist/brain/session-key.js +41 -0
  27. package/dist/brain/session-state.d.ts +36 -0
  28. package/dist/brain/session-state.js +51 -0
  29. package/dist/brain/session-store.d.ts +50 -0
  30. package/dist/brain/session-store.js +207 -0
  31. package/dist/brain/session.d.ts +32 -0
  32. package/dist/brain/session.js +75 -0
  33. package/dist/brain/utils.d.ts +4 -0
  34. package/dist/brain/utils.js +26 -0
  35. package/dist/brain/worker-coordinator.d.ts +25 -0
  36. package/dist/brain/worker-coordinator.js +83 -0
  37. package/dist/channels/commands.d.ts +35 -0
  38. package/dist/channels/commands.js +65 -0
  39. package/dist/channels/discord/channel.d.ts +18 -0
  40. package/dist/channels/discord/channel.js +154 -0
  41. package/dist/channels/discord/markdown.d.ts +19 -0
  42. package/dist/channels/discord/markdown.js +62 -0
  43. package/dist/channels/manager.d.ts +29 -0
  44. package/dist/channels/manager.js +100 -0
  45. package/dist/channels/slack/channel.d.ts +26 -0
  46. package/dist/channels/slack/channel.js +207 -0
  47. package/dist/channels/slack/markdown.d.ts +19 -0
  48. package/dist/channels/slack/markdown.js +62 -0
  49. package/dist/channels/specs.d.ts +21 -0
  50. package/dist/channels/specs.js +96 -0
  51. package/dist/channels/telegram/channel.d.ts +18 -0
  52. package/dist/channels/telegram/channel.js +156 -0
  53. package/dist/channels/telegram/markdown.d.ts +17 -0
  54. package/dist/channels/telegram/markdown.js +66 -0
  55. package/dist/channels/types.d.ts +26 -0
  56. package/dist/channels/types.js +1 -0
  57. package/dist/channels/whatsapp/channel.d.ts +23 -0
  58. package/dist/channels/whatsapp/channel.js +242 -0
  59. package/dist/channels/whatsapp/markdown.d.ts +20 -0
  60. package/dist/channels/whatsapp/markdown.js +51 -0
  61. package/dist/cli/config.d.ts +5 -0
  62. package/dist/cli/config.js +78 -0
  63. package/dist/cli/index.d.ts +5 -0
  64. package/dist/cli/index.js +13 -0
  65. package/dist/computer/browser/actions.d.ts +31 -0
  66. package/dist/computer/browser/actions.js +148 -0
  67. package/dist/computer/browser/manager.d.ts +55 -0
  68. package/dist/computer/browser/manager.js +496 -0
  69. package/dist/computer/browser/profile-badge.d.ts +13 -0
  70. package/dist/computer/browser/profile-badge.js +67 -0
  71. package/dist/computer/browser/screenshot.d.ts +5 -0
  72. package/dist/computer/browser/screenshot.js +21 -0
  73. package/dist/computer/browser/snapshot.d.ts +30 -0
  74. package/dist/computer/browser/snapshot.js +242 -0
  75. package/dist/computer/browser/tools.d.ts +5 -0
  76. package/dist/computer/browser/tools.js +167 -0
  77. package/dist/computer/desktop/adapter.d.ts +25 -0
  78. package/dist/computer/desktop/adapter.js +11 -0
  79. package/dist/computer/desktop/macos.d.ts +24 -0
  80. package/dist/computer/desktop/macos.js +223 -0
  81. package/dist/computer/desktop/tools.d.ts +25 -0
  82. package/dist/computer/desktop/tools.js +114 -0
  83. package/dist/config/agent-config.d.ts +41 -0
  84. package/dist/config/agent-config.js +14 -0
  85. package/dist/config/model-catalog.d.ts +22 -0
  86. package/dist/config/model-catalog.js +99 -0
  87. package/dist/config/store.d.ts +25 -0
  88. package/dist/config/store.js +143 -0
  89. package/dist/config.d.ts +103 -0
  90. package/dist/config.js +224 -0
  91. package/dist/control-ui/assets/index-BANXNUyt.js +143 -0
  92. package/dist/control-ui/assets/index-BSUFrP9R.css +1 -0
  93. package/dist/control-ui/assets/noto-sans-cyrillic-ext-wght-normal-DSNfmdVt.woff2 +0 -0
  94. package/dist/control-ui/assets/noto-sans-cyrillic-wght-normal-B2hlT84T.woff2 +0 -0
  95. package/dist/control-ui/assets/noto-sans-devanagari-wght-normal-Cv-Vwajv.woff2 +0 -0
  96. package/dist/control-ui/assets/noto-sans-greek-ext-wght-normal-12T8GTDR.woff2 +0 -0
  97. package/dist/control-ui/assets/noto-sans-greek-wght-normal-Ymb6dZNd.woff2 +0 -0
  98. package/dist/control-ui/assets/noto-sans-latin-ext-wght-normal-W1qJv59z.woff2 +0 -0
  99. package/dist/control-ui/assets/noto-sans-latin-wght-normal-BYSzYMf3.woff2 +0 -0
  100. package/dist/control-ui/assets/noto-sans-vietnamese-wght-normal-DLTJy58D.woff2 +0 -0
  101. package/dist/control-ui/index.html +14 -0
  102. package/dist/control-ui/vite.svg +1 -0
  103. package/dist/events.d.ts +2 -0
  104. package/dist/events.js +11 -0
  105. package/dist/gateway/broadcast.d.ts +5 -0
  106. package/dist/gateway/broadcast.js +33 -0
  107. package/dist/gateway/methods/chat.d.ts +24 -0
  108. package/dist/gateway/methods/chat.js +19 -0
  109. package/dist/gateway/methods/config.d.ts +13 -0
  110. package/dist/gateway/methods/config.js +14 -0
  111. package/dist/gateway/methods/models.d.ts +10 -0
  112. package/dist/gateway/methods/models.js +14 -0
  113. package/dist/gateway/methods/prompt-templates.d.ts +23 -0
  114. package/dist/gateway/methods/prompt-templates.js +82 -0
  115. package/dist/gateway/methods/scheduler.d.ts +62 -0
  116. package/dist/gateway/methods/scheduler.js +129 -0
  117. package/dist/gateway/methods/sessions.d.ts +26 -0
  118. package/dist/gateway/methods/sessions.js +54 -0
  119. package/dist/gateway/methods/skills.d.ts +35 -0
  120. package/dist/gateway/methods/skills.js +202 -0
  121. package/dist/gateway/methods/system.d.ts +12 -0
  122. package/dist/gateway/methods/system.js +39 -0
  123. package/dist/gateway/methods/tasks.d.ts +21 -0
  124. package/dist/gateway/methods/tasks.js +46 -0
  125. package/dist/gateway/methods/teams.d.ts +70 -0
  126. package/dist/gateway/methods/teams.js +374 -0
  127. package/dist/gateway/methods/tools.d.ts +6 -0
  128. package/dist/gateway/methods/tools.js +7 -0
  129. package/dist/gateway/methods/whatsapp.d.ts +19 -0
  130. package/dist/gateway/methods/whatsapp.js +35 -0
  131. package/dist/gateway/rpc.d.ts +38 -0
  132. package/dist/gateway/rpc.js +75 -0
  133. package/dist/gateway/server.d.ts +4 -0
  134. package/dist/gateway/server.js +133 -0
  135. package/dist/index.d.ts +1 -0
  136. package/dist/index.js +212 -0
  137. package/dist/integrations/github.d.ts +7 -0
  138. package/dist/integrations/github.js +133 -0
  139. package/dist/integrations/mcp.d.ts +7 -0
  140. package/dist/integrations/mcp.js +106 -0
  141. package/dist/integrations/registry.d.ts +43 -0
  142. package/dist/integrations/registry.js +258 -0
  143. package/dist/integrations/scanner.d.ts +10 -0
  144. package/dist/integrations/scanner.js +122 -0
  145. package/dist/integrations/twitter.d.ts +10 -0
  146. package/dist/integrations/twitter.js +120 -0
  147. package/dist/integrations/types.d.ts +72 -0
  148. package/dist/integrations/types.js +1 -0
  149. package/dist/logger.d.ts +16 -0
  150. package/dist/logger.js +104 -0
  151. package/dist/markdown/chunk.d.ts +9 -0
  152. package/dist/markdown/chunk.js +52 -0
  153. package/dist/markdown/ir.d.ts +37 -0
  154. package/dist/markdown/ir.js +529 -0
  155. package/dist/markdown/render.d.ts +22 -0
  156. package/dist/markdown/render.js +148 -0
  157. package/dist/markdown/table-render.d.ts +43 -0
  158. package/dist/markdown/table-render.js +219 -0
  159. package/dist/markdown/tables.d.ts +17 -0
  160. package/dist/markdown/tables.js +27 -0
  161. package/dist/memory/embedding.d.ts +16 -0
  162. package/dist/memory/embedding.js +66 -0
  163. package/dist/memory/extractor.d.ts +6 -0
  164. package/dist/memory/extractor.js +72 -0
  165. package/dist/memory/search.d.ts +15 -0
  166. package/dist/memory/search.js +57 -0
  167. package/dist/memory/store.d.ts +34 -0
  168. package/dist/memory/store.js +328 -0
  169. package/dist/memory/types.d.ts +9 -0
  170. package/dist/memory/types.js +2 -0
  171. package/dist/paths.d.ts +20 -0
  172. package/dist/paths.js +29 -0
  173. package/dist/prompt-templates/builtins.d.ts +2 -0
  174. package/dist/prompt-templates/builtins.js +72 -0
  175. package/dist/prompt-templates/store.d.ts +39 -0
  176. package/dist/prompt-templates/store.js +174 -0
  177. package/dist/prompt-templates/types.d.ts +10 -0
  178. package/dist/prompt-templates/types.js +1 -0
  179. package/dist/scheduler/connected-channels.d.ts +24 -0
  180. package/dist/scheduler/connected-channels.js +57 -0
  181. package/dist/scheduler/scheduler.d.ts +22 -0
  182. package/dist/scheduler/scheduler.js +132 -0
  183. package/dist/scheduler/store.d.ts +27 -0
  184. package/dist/scheduler/store.js +205 -0
  185. package/dist/scheduler/types.d.ts +29 -0
  186. package/dist/scheduler/types.js +1 -0
  187. package/dist/security/command-validator.d.ts +22 -0
  188. package/dist/security/command-validator.js +160 -0
  189. package/dist/security/docker-sandbox.d.ts +48 -0
  190. package/dist/security/docker-sandbox.js +218 -0
  191. package/dist/security/env-filter.d.ts +8 -0
  192. package/dist/security/env-filter.js +41 -0
  193. package/dist/skills/loader.d.ts +33 -0
  194. package/dist/skills/loader.js +132 -0
  195. package/dist/skills/prompt.d.ts +6 -0
  196. package/dist/skills/prompt.js +17 -0
  197. package/dist/skills/read-tool.d.ts +7 -0
  198. package/dist/skills/read-tool.js +24 -0
  199. package/dist/skills/scanner.d.ts +6 -0
  200. package/dist/skills/scanner.js +73 -0
  201. package/dist/skills/types.d.ts +15 -0
  202. package/dist/skills/types.js +1 -0
  203. package/dist/tasks/store.d.ts +47 -0
  204. package/dist/tasks/store.js +193 -0
  205. package/dist/tasks/types.d.ts +75 -0
  206. package/dist/tasks/types.js +32 -0
  207. package/dist/teams/store.d.ts +78 -0
  208. package/dist/teams/store.js +420 -0
  209. package/dist/teams/types.d.ts +23 -0
  210. package/dist/teams/types.js +1 -0
  211. package/dist/tools/bash.d.ts +16 -0
  212. package/dist/tools/bash.js +62 -0
  213. package/dist/tools/channel-history.d.ts +10 -0
  214. package/dist/tools/channel-history.js +43 -0
  215. package/dist/tools/delegate.d.ts +16 -0
  216. package/dist/tools/delegate.js +216 -0
  217. package/dist/tools/fs.d.ts +4 -0
  218. package/dist/tools/fs.js +335 -0
  219. package/dist/tools/integration-toggle.d.ts +14 -0
  220. package/dist/tools/integration-toggle.js +47 -0
  221. package/dist/tools/memory.d.ts +13 -0
  222. package/dist/tools/memory.js +65 -0
  223. package/dist/tools/registry.d.ts +6 -0
  224. package/dist/tools/registry.js +9 -0
  225. package/dist/tools/schedule.d.ts +8 -0
  226. package/dist/tools/schedule.js +219 -0
  227. package/dist/tools/speak.d.ts +10 -0
  228. package/dist/tools/speak.js +56 -0
  229. package/dist/tools/tasks.d.ts +29 -0
  230. package/dist/tools/tasks.js +92 -0
  231. package/dist/tools/teams.d.ts +7 -0
  232. package/dist/tools/teams.js +180 -0
  233. package/dist/tools/web-fetch.d.ts +3 -0
  234. package/dist/tools/web-fetch.js +22 -0
  235. package/dist/tts/edge.d.ts +10 -0
  236. package/dist/tts/edge.js +60 -0
  237. package/dist/tts/speak.d.ts +12 -0
  238. package/dist/tts/speak.js +81 -0
  239. package/dist/tts/transcribe.d.ts +5 -0
  240. package/dist/tts/transcribe.js +40 -0
  241. package/dist/utils.d.ts +5 -0
  242. package/dist/utils.js +22 -0
  243. package/package.json +90 -0
  244. package/verybot.js +2 -0
@@ -0,0 +1,10 @@
1
+ export interface PromptTemplate {
2
+ id: string;
3
+ name: string;
4
+ description: string;
5
+ role: "orchestrator" | "worker";
6
+ content: string;
7
+ builtin: boolean;
8
+ createdAt: number;
9
+ updatedAt: number;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ export interface ConnectedChannel {
2
+ channelType: string;
3
+ channelId: string;
4
+ send: (text: string) => Promise<void>;
5
+ }
6
+ /**
7
+ * In-memory registry tracking which channels are connected to which team's scheduler.
8
+ * Multiple channels (web UI, Telegram, etc.) can connect simultaneously.
9
+ */
10
+ export declare class ConnectedChannelRegistry {
11
+ /** Map<teamId, Map<connectionKey, ConnectedChannel>> */
12
+ private channels;
13
+ /** Register a channel connection. Returns a unique connection key for later disconnect. */
14
+ connect(teamId: string, channel: ConnectedChannel): string;
15
+ /** Unregister a channel connection. */
16
+ disconnect(teamId: string, key: string): void;
17
+ /** Get all connected channels for a team. */
18
+ getAll(teamId: string): ConnectedChannel[];
19
+ /**
20
+ * Broadcast a message to all connected channels for a team.
21
+ * `excludeKey` skips a specific connection (e.g., the sender that already has the reply).
22
+ */
23
+ broadcastToTeam(teamId: string, text: string, excludeKey?: string): Promise<void>;
24
+ }
@@ -0,0 +1,57 @@
1
+ import { randomUUID } from "crypto";
2
+ import { logger } from "../logger.js";
3
+ /**
4
+ * In-memory registry tracking which channels are connected to which team's scheduler.
5
+ * Multiple channels (web UI, Telegram, etc.) can connect simultaneously.
6
+ */
7
+ export class ConnectedChannelRegistry {
8
+ /** Map<teamId, Map<connectionKey, ConnectedChannel>> */
9
+ channels = new Map();
10
+ /** Register a channel connection. Returns a unique connection key for later disconnect. */
11
+ connect(teamId, channel) {
12
+ const key = randomUUID();
13
+ let teamMap = this.channels.get(teamId);
14
+ if (!teamMap) {
15
+ teamMap = new Map();
16
+ this.channels.set(teamId, teamMap);
17
+ }
18
+ teamMap.set(key, channel);
19
+ logger.info(`[scheduler] Channel connected: ${channel.channelType}:${channel.channelId} → team ${teamId} (key: ${key.slice(0, 8)})`);
20
+ return key;
21
+ }
22
+ /** Unregister a channel connection. */
23
+ disconnect(teamId, key) {
24
+ const teamMap = this.channels.get(teamId);
25
+ if (!teamMap)
26
+ return;
27
+ teamMap.delete(key);
28
+ if (teamMap.size === 0)
29
+ this.channels.delete(teamId);
30
+ logger.info(`[scheduler] Channel disconnected: team ${teamId} (key: ${key.slice(0, 8)})`);
31
+ }
32
+ /** Get all connected channels for a team. */
33
+ getAll(teamId) {
34
+ const teamMap = this.channels.get(teamId);
35
+ if (!teamMap)
36
+ return [];
37
+ return [...teamMap.values()];
38
+ }
39
+ /**
40
+ * Broadcast a message to all connected channels for a team.
41
+ * `excludeKey` skips a specific connection (e.g., the sender that already has the reply).
42
+ */
43
+ async broadcastToTeam(teamId, text, excludeKey) {
44
+ const teamMap = this.channels.get(teamId);
45
+ if (!teamMap)
46
+ return;
47
+ const sends = [];
48
+ for (const [key, channel] of teamMap) {
49
+ if (key === excludeKey)
50
+ continue;
51
+ sends.push(channel.send(text).catch((err) => {
52
+ logger.warn(`[scheduler] Broadcast to ${channel.channelType}:${channel.channelId} failed: ${err instanceof Error ? err.message : err}`);
53
+ }));
54
+ }
55
+ await Promise.all(sends);
56
+ }
57
+ }
@@ -0,0 +1,22 @@
1
+ import type { ScheduleStore } from "./store.js";
2
+ import type { Agent } from "../brain/agent.js";
3
+ import type { ConnectedChannelRegistry } from "./connected-channels.js";
4
+ export declare class Scheduler {
5
+ private store;
6
+ private agent;
7
+ private connectedChannels;
8
+ private tickMs;
9
+ private timer;
10
+ private running;
11
+ constructor(store: ScheduleStore, agent: Agent, connectedChannels: ConnectedChannelRegistry, tickMs?: number);
12
+ start(): void;
13
+ stop(): void;
14
+ /**
15
+ * On startup: fire missed one-shots, skip missed recurring.
16
+ */
17
+ private catchUp;
18
+ private tick;
19
+ private executeTask;
20
+ private advanceSchedule;
21
+ private handleError;
22
+ }
@@ -0,0 +1,132 @@
1
+ import { logger } from "../logger.js";
2
+ const TICK_INTERVAL_MS = 5_000;
3
+ const MAX_RETRY_COUNT = 1;
4
+ /** Prefix that LLM uses to signal "not worth notifying". */
5
+ const SKIP_SIGNAL = "[SKIP]";
6
+ export class Scheduler {
7
+ store;
8
+ agent;
9
+ connectedChannels;
10
+ tickMs;
11
+ timer = null;
12
+ running = false;
13
+ constructor(store, agent, connectedChannels, tickMs = TICK_INTERVAL_MS) {
14
+ this.store = store;
15
+ this.agent = agent;
16
+ this.connectedChannels = connectedChannels;
17
+ this.tickMs = tickMs;
18
+ }
19
+ start() {
20
+ // Catch up missed tasks on startup
21
+ this.catchUp().catch((err) => {
22
+ logger.error(`Scheduler catch-up failed: ${err instanceof Error ? err.message : err}`);
23
+ });
24
+ this.timer = setInterval(() => {
25
+ this.tick().catch((err) => {
26
+ logger.error(`Scheduler tick failed: ${err instanceof Error ? err.message : err}`);
27
+ });
28
+ }, this.tickMs);
29
+ logger.info(`Scheduler started (tick every ${this.tickMs}ms)`);
30
+ }
31
+ stop() {
32
+ if (this.timer) {
33
+ clearInterval(this.timer);
34
+ this.timer = null;
35
+ }
36
+ logger.info("Scheduler stopped");
37
+ }
38
+ /**
39
+ * On startup: fire missed one-shots, skip missed recurring.
40
+ */
41
+ async catchUp() {
42
+ const now = new Date();
43
+ const due = this.store.getDueSchedules(now);
44
+ if (due.length === 0)
45
+ return;
46
+ const oneShots = due.filter((s) => s.type === "one_shot");
47
+ const recurring = due.filter((s) => s.type === "recurring");
48
+ // Skip missed recurring — advance to next future run
49
+ for (const schedule of recurring) {
50
+ const nextRun = this.store.computeNextRun(schedule);
51
+ this.store.markCompleted(schedule.id, nextRun);
52
+ logger.info(`[scheduler] Skipped missed recurring "${schedule.prompt.slice(0, 40)}..." → next: ${nextRun}`);
53
+ }
54
+ // Fire missed one-shots
55
+ if (oneShots.length > 0) {
56
+ logger.info(`[scheduler] Catching up ${oneShots.length} missed one-shot task(s)`);
57
+ await Promise.allSettled(oneShots.map((s) => this.executeTask(s)));
58
+ }
59
+ }
60
+ async tick() {
61
+ if (this.running)
62
+ return; // prevent overlapping ticks
63
+ this.running = true;
64
+ try {
65
+ const due = this.store.getDueSchedules(new Date());
66
+ if (due.length === 0)
67
+ return;
68
+ logger.info(`[scheduler] ${due.length} task(s) due`);
69
+ await Promise.allSettled(due.map((s) => this.executeTask(s)));
70
+ }
71
+ finally {
72
+ this.running = false;
73
+ }
74
+ }
75
+ async executeTask(schedule) {
76
+ const taskLabel = `"${schedule.prompt.slice(0, 50)}..." (${schedule.id})`;
77
+ logger.info(`[scheduler] Executing task ${taskLabel}`);
78
+ try {
79
+ const reply = await this.agent.runScheduledTask({
80
+ prompt: schedule.prompt,
81
+ teamId: schedule.teamId,
82
+ integrations: schedule.integrations.split(",").filter(Boolean),
83
+ });
84
+ // Check for [SKIP] signal on conditional tasks
85
+ if (schedule.conditional && reply.trimStart().startsWith(SKIP_SIGNAL)) {
86
+ logger.info(`[scheduler] Task ${taskLabel} skipped by LLM`);
87
+ this.advanceSchedule(schedule);
88
+ return;
89
+ }
90
+ // Broadcast to all connected channels for this team
91
+ await this.connectedChannels.broadcastToTeam(schedule.teamId, reply);
92
+ logger.info(`[scheduler] Task ${taskLabel} broadcast to team ${schedule.teamId}`);
93
+ // Reset fail count and advance
94
+ this.advanceSchedule(schedule);
95
+ }
96
+ catch (err) {
97
+ await this.handleError(schedule, err);
98
+ }
99
+ }
100
+ advanceSchedule(schedule) {
101
+ const nextRun = schedule.type === "recurring" ? this.store.computeNextRun(schedule) : null;
102
+ this.store.markCompleted(schedule.id, nextRun);
103
+ if (nextRun) {
104
+ logger.info(`[scheduler] Next run for ${schedule.id}: ${nextRun}`);
105
+ }
106
+ }
107
+ async handleError(schedule, err) {
108
+ const msg = err instanceof Error ? err.message : String(err);
109
+ const taskLabel = `"${schedule.prompt.slice(0, 50)}..." (${schedule.id})`;
110
+ // Re-read to get current fail count
111
+ const current = this.store.getById(schedule.id);
112
+ const failCount = (current?.failCount ?? schedule.failCount) + 1;
113
+ if (failCount <= MAX_RETRY_COUNT) {
114
+ // Retry: increment fail count, leave next_run unchanged (fires on next tick)
115
+ logger.warn(`[scheduler] Task ${taskLabel} failed (attempt ${failCount}), will retry: ${msg}`);
116
+ this.store.update(schedule.id, { failCount });
117
+ return;
118
+ }
119
+ // Max retries exceeded — advance the schedule and notify user
120
+ logger.error(`[scheduler] Task ${taskLabel} failed after ${failCount} attempts: ${msg}`);
121
+ const nextRun = schedule.type === "recurring" ? this.store.computeNextRun(schedule) : null;
122
+ this.store.markFailed(schedule.id, nextRun);
123
+ // Best-effort notification to connected channels
124
+ try {
125
+ const notification = `Scheduled task failed: "${schedule.prompt.slice(0, 80)}..."\nError: ${msg}`;
126
+ await this.connectedChannels.broadcastToTeam(schedule.teamId, notification);
127
+ }
128
+ catch {
129
+ logger.warn(`[scheduler] Could not notify about failed task ${schedule.id}`);
130
+ }
131
+ }
132
+ }
@@ -0,0 +1,27 @@
1
+ import type { Schedule, ScheduleStatus } from "./types.js";
2
+ export declare class ScheduleStore {
3
+ private db;
4
+ private constructor();
5
+ /** Async factory — mirrors MemoryStore.create pattern. */
6
+ static create(dbPath: string): Promise<ScheduleStore>;
7
+ private createSchema;
8
+ create(schedule: Schedule): void;
9
+ getById(id: string): Schedule | null;
10
+ listByTeam(teamId: string, status?: ScheduleStatus): Schedule[];
11
+ /** Get all schedules that are due (next_run <= now and status = 'active'). */
12
+ getDueSchedules(now: Date): Schedule[];
13
+ /** Get all schedules with a specific status. */
14
+ getByStatus(status: ScheduleStatus): Schedule[];
15
+ /** Update specific fields on a schedule. */
16
+ update(id: string, fields: Partial<Pick<Schedule, "status" | "nextRun" | "lastRun" | "failCount">>): void;
17
+ delete(id: string): boolean;
18
+ /** Mark a schedule as completed for this run and advance to the next. */
19
+ markCompleted(id: string, nextRun: string | null): void;
20
+ /** Increment fail count and optionally advance the schedule. */
21
+ markFailed(id: string, nextRun: string | null): void;
22
+ /** Compute the next run time for a recurring schedule using croner. */
23
+ computeNextRun(schedule: Schedule): string | null;
24
+ setTimezone(teamId: string, timezone: string): void;
25
+ getTimezone(teamId: string): string | null;
26
+ close(): void;
27
+ }
@@ -0,0 +1,205 @@
1
+ import { mkdirSync } from "fs";
2
+ import { dirname } from "path";
3
+ import Database from "better-sqlite3";
4
+ import { Cron } from "croner";
5
+ import { logger } from "../logger.js";
6
+ /** Map a DB row to a Schedule object. */
7
+ function rowToSchedule(row) {
8
+ return {
9
+ id: row.id,
10
+ teamId: row.team_id,
11
+ prompt: row.prompt,
12
+ type: row.type,
13
+ cron: row.cron,
14
+ runAt: row.run_at,
15
+ timezone: row.timezone,
16
+ integrations: row.integrations,
17
+ conditional: row.conditional === 1,
18
+ status: row.status,
19
+ nextRun: row.next_run,
20
+ lastRun: row.last_run,
21
+ failCount: row.fail_count,
22
+ createdAt: row.created_at,
23
+ updatedAt: row.updated_at,
24
+ };
25
+ }
26
+ export class ScheduleStore {
27
+ db;
28
+ constructor(db) {
29
+ this.db = db;
30
+ }
31
+ /** Async factory — mirrors MemoryStore.create pattern. */
32
+ static async create(dbPath) {
33
+ mkdirSync(dirname(dbPath), { recursive: true });
34
+ const db = new Database(dbPath);
35
+ db.pragma("journal_mode = WAL");
36
+ const store = new ScheduleStore(db);
37
+ store.createSchema();
38
+ return store;
39
+ }
40
+ createSchema() {
41
+ this.db.exec(`
42
+ CREATE TABLE IF NOT EXISTS schedules (
43
+ id TEXT PRIMARY KEY,
44
+ team_id TEXT NOT NULL,
45
+ prompt TEXT NOT NULL,
46
+ type TEXT NOT NULL,
47
+ cron TEXT,
48
+ run_at TEXT,
49
+ timezone TEXT NOT NULL DEFAULT 'UTC',
50
+ integrations TEXT NOT NULL DEFAULT '',
51
+ conditional INTEGER NOT NULL DEFAULT 0,
52
+ status TEXT NOT NULL DEFAULT 'active',
53
+ next_run TEXT,
54
+ last_run TEXT,
55
+ fail_count INTEGER NOT NULL DEFAULT 0,
56
+ created_at TEXT NOT NULL,
57
+ updated_at TEXT NOT NULL
58
+ );
59
+ CREATE INDEX IF NOT EXISTS idx_schedules_next_run ON schedules(next_run);
60
+ CREATE INDEX IF NOT EXISTS idx_schedules_status ON schedules(status);
61
+ CREATE INDEX IF NOT EXISTS idx_schedules_team ON schedules(team_id);
62
+
63
+ CREATE TABLE IF NOT EXISTS scheduler_settings (
64
+ team_id TEXT PRIMARY KEY,
65
+ timezone TEXT NOT NULL DEFAULT 'UTC'
66
+ );
67
+ `);
68
+ }
69
+ // --- CRUD ---
70
+ create(schedule) {
71
+ this.db
72
+ .prepare(`INSERT INTO schedules (
73
+ id, team_id, prompt, type, cron, run_at, timezone,
74
+ integrations, conditional,
75
+ status, next_run, last_run, fail_count, created_at, updated_at
76
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
77
+ .run(schedule.id, schedule.teamId, schedule.prompt, schedule.type, schedule.cron, schedule.runAt, schedule.timezone, schedule.integrations, schedule.conditional ? 1 : 0, schedule.status, schedule.nextRun, schedule.lastRun, schedule.failCount, schedule.createdAt, schedule.updatedAt);
78
+ }
79
+ getById(id) {
80
+ const row = this.db
81
+ .prepare("SELECT * FROM schedules WHERE id = ?")
82
+ .get(id);
83
+ return row ? rowToSchedule(row) : null;
84
+ }
85
+ listByTeam(teamId, status) {
86
+ if (status) {
87
+ const rows = this.db
88
+ .prepare("SELECT * FROM schedules WHERE team_id = ? AND status = ? ORDER BY created_at DESC")
89
+ .all(teamId, status);
90
+ return rows.map(rowToSchedule);
91
+ }
92
+ const rows = this.db
93
+ .prepare("SELECT * FROM schedules WHERE team_id = ? ORDER BY created_at DESC")
94
+ .all(teamId);
95
+ return rows.map(rowToSchedule);
96
+ }
97
+ /** Get all schedules that are due (next_run <= now and status = 'active'). */
98
+ getDueSchedules(now) {
99
+ const rows = this.db
100
+ .prepare("SELECT * FROM schedules WHERE status = 'active' AND next_run IS NOT NULL AND next_run <= ?")
101
+ .all(now.toISOString());
102
+ return rows.map(rowToSchedule);
103
+ }
104
+ /** Get all schedules with a specific status. */
105
+ getByStatus(status) {
106
+ const rows = this.db
107
+ .prepare("SELECT * FROM schedules WHERE status = ?")
108
+ .all(status);
109
+ return rows.map(rowToSchedule);
110
+ }
111
+ /** Update specific fields on a schedule. */
112
+ update(id, fields) {
113
+ const sets = [];
114
+ const values = [];
115
+ if (fields.status !== undefined) {
116
+ sets.push("status = ?");
117
+ values.push(fields.status);
118
+ }
119
+ if (fields.nextRun !== undefined) {
120
+ sets.push("next_run = ?");
121
+ values.push(fields.nextRun);
122
+ }
123
+ if (fields.lastRun !== undefined) {
124
+ sets.push("last_run = ?");
125
+ values.push(fields.lastRun);
126
+ }
127
+ if (fields.failCount !== undefined) {
128
+ sets.push("fail_count = ?");
129
+ values.push(fields.failCount);
130
+ }
131
+ if (sets.length === 0)
132
+ return;
133
+ sets.push("updated_at = ?");
134
+ values.push(new Date().toISOString());
135
+ values.push(id);
136
+ this.db.prepare(`UPDATE schedules SET ${sets.join(", ")} WHERE id = ?`).run(...values);
137
+ }
138
+ delete(id) {
139
+ const info = this.db.prepare("DELETE FROM schedules WHERE id = ?").run(id);
140
+ return info.changes > 0;
141
+ }
142
+ /** Mark a schedule as completed for this run and advance to the next. */
143
+ markCompleted(id, nextRun) {
144
+ const now = new Date().toISOString();
145
+ if (nextRun) {
146
+ // Recurring — advance to next run
147
+ this.db
148
+ .prepare("UPDATE schedules SET last_run = ?, next_run = ?, fail_count = 0, updated_at = ? WHERE id = ?")
149
+ .run(now, nextRun, now, id);
150
+ }
151
+ else {
152
+ // One-shot — mark completed
153
+ this.db
154
+ .prepare("UPDATE schedules SET last_run = ?, next_run = NULL, status = 'completed', fail_count = 0, updated_at = ? WHERE id = ?")
155
+ .run(now, now, id);
156
+ }
157
+ }
158
+ /** Increment fail count and optionally advance the schedule. */
159
+ markFailed(id, nextRun) {
160
+ const now = new Date().toISOString();
161
+ if (nextRun) {
162
+ // Recurring — advance despite failure
163
+ this.db
164
+ .prepare("UPDATE schedules SET last_run = ?, next_run = ?, fail_count = fail_count + 1, updated_at = ? WHERE id = ?")
165
+ .run(now, nextRun, now, id);
166
+ }
167
+ else {
168
+ // One-shot — mark failed
169
+ this.db
170
+ .prepare("UPDATE schedules SET last_run = ?, next_run = NULL, status = 'failed', fail_count = fail_count + 1, updated_at = ? WHERE id = ?")
171
+ .run(now, now, id);
172
+ }
173
+ }
174
+ // --- Next run computation ---
175
+ /** Compute the next run time for a recurring schedule using croner. */
176
+ computeNextRun(schedule) {
177
+ if (schedule.type !== "recurring" || !schedule.cron)
178
+ return null;
179
+ try {
180
+ const job = new Cron(schedule.cron, { timezone: schedule.timezone });
181
+ const next = job.nextRun();
182
+ return next ? next.toISOString() : null;
183
+ }
184
+ catch (err) {
185
+ logger.warn(`Failed to compute next run for schedule ${schedule.id}: ${err instanceof Error ? err.message : err}`);
186
+ return null;
187
+ }
188
+ }
189
+ // --- Team Settings ---
190
+ setTimezone(teamId, timezone) {
191
+ this.db
192
+ .prepare("INSERT INTO scheduler_settings (team_id, timezone) VALUES (?, ?) ON CONFLICT(team_id) DO UPDATE SET timezone = ?")
193
+ .run(teamId, timezone, timezone);
194
+ }
195
+ getTimezone(teamId) {
196
+ const row = this.db
197
+ .prepare("SELECT timezone FROM scheduler_settings WHERE team_id = ?")
198
+ .get(teamId);
199
+ return row?.timezone ?? null;
200
+ }
201
+ close() {
202
+ this.db.close();
203
+ logger.info("Schedule store closed");
204
+ }
205
+ }
@@ -0,0 +1,29 @@
1
+ export type ScheduleType = "one_shot" | "recurring";
2
+ export type ScheduleStatus = "active" | "paused" | "completed" | "failed";
3
+ export interface Schedule {
4
+ id: string;
5
+ /** Team that owns this schedule */
6
+ teamId: string;
7
+ /** The user's instruction / prompt for the LLM */
8
+ prompt: string;
9
+ type: ScheduleType;
10
+ /** Cron expression (recurring only) */
11
+ cron: string | null;
12
+ /** ISO timestamp (one-shot only) */
13
+ runAt: string | null;
14
+ /** IANA timezone, e.g. "America/New_York" */
15
+ timezone: string;
16
+ /** Comma-separated integration names to enable for this task */
17
+ integrations: string;
18
+ /** Whether the LLM can skip delivery with [SKIP] */
19
+ conditional: boolean;
20
+ status: ScheduleStatus;
21
+ /** ISO timestamp of next scheduled run */
22
+ nextRun: string | null;
23
+ /** ISO timestamp of last completed run */
24
+ lastRun: string | null;
25
+ /** Number of consecutive failures */
26
+ failCount: number;
27
+ createdAt: string;
28
+ updatedAt: string;
29
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Command validation for bash tool security.
3
+ *
4
+ * Blocks dangerous shell syntax (quote-aware) and checks executables
5
+ * against safe-bin and allowlist rules.
6
+ */
7
+ /** Default read-only commands that always pass in allowlist mode. */
8
+ export declare const DEFAULT_SAFE_BINS: Set<string>;
9
+ interface ValidationResult {
10
+ ok: boolean;
11
+ reason?: string;
12
+ }
13
+ /**
14
+ * Check for dangerous shell syntax (quote-aware).
15
+ *
16
+ * Blocks backticks, command substitution `$(...)`, redirects `> <`,
17
+ * and subshells `( )` when they appear outside of quotes.
18
+ */
19
+ export declare function validateCommand(command: string): ValidationResult;
20
+ /** Check if ALL segments of a command match the allowlist. */
21
+ export declare function isAllowed(command: string, safeBins: Set<string>, allowlist: string[]): boolean;
22
+ export {};