verybot 0.1.8

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 (277) hide show
  1. package/README.md +167 -0
  2. package/dist/aliases/store.d.ts +21 -0
  3. package/dist/aliases/store.js +148 -0
  4. package/dist/aliases/types.d.ts +6 -0
  5. package/dist/aliases/types.js +1 -0
  6. package/dist/brain/agent-registry.d.ts +96 -0
  7. package/dist/brain/agent-registry.js +141 -0
  8. package/dist/brain/agent.d.ts +167 -0
  9. package/dist/brain/agent.js +932 -0
  10. package/dist/brain/channel-store.d.ts +27 -0
  11. package/dist/brain/channel-store.js +78 -0
  12. package/dist/brain/compaction.d.ts +37 -0
  13. package/dist/brain/compaction.js +214 -0
  14. package/dist/brain/context.d.ts +43 -0
  15. package/dist/brain/context.js +139 -0
  16. package/dist/brain/delegation-store.d.ts +33 -0
  17. package/dist/brain/delegation-store.js +106 -0
  18. package/dist/brain/loop.d.ts +24 -0
  19. package/dist/brain/loop.js +318 -0
  20. package/dist/brain/mcp-adapter.d.ts +43 -0
  21. package/dist/brain/mcp-adapter.js +244 -0
  22. package/dist/brain/memory-extractor.d.ts +26 -0
  23. package/dist/brain/memory-extractor.js +82 -0
  24. package/dist/brain/providers.d.ts +14 -0
  25. package/dist/brain/providers.js +85 -0
  26. package/dist/brain/queue.d.ts +18 -0
  27. package/dist/brain/queue.js +111 -0
  28. package/dist/brain/run-tools.d.ts +50 -0
  29. package/dist/brain/run-tools.js +136 -0
  30. package/dist/brain/session-key.d.ts +23 -0
  31. package/dist/brain/session-key.js +41 -0
  32. package/dist/brain/session-state.d.ts +36 -0
  33. package/dist/brain/session-state.js +51 -0
  34. package/dist/brain/session-store.d.ts +50 -0
  35. package/dist/brain/session-store.js +207 -0
  36. package/dist/brain/session.d.ts +32 -0
  37. package/dist/brain/session.js +75 -0
  38. package/dist/brain/task-subscriber.d.ts +56 -0
  39. package/dist/brain/task-subscriber.js +317 -0
  40. package/dist/brain/user-content.d.ts +16 -0
  41. package/dist/brain/user-content.js +32 -0
  42. package/dist/brain/utils.d.ts +4 -0
  43. package/dist/brain/utils.js +26 -0
  44. package/dist/brain/worker-coordinator.d.ts +25 -0
  45. package/dist/brain/worker-coordinator.js +83 -0
  46. package/dist/channels/commands.d.ts +50 -0
  47. package/dist/channels/commands.js +132 -0
  48. package/dist/channels/discord/channel.d.ts +29 -0
  49. package/dist/channels/discord/channel.js +159 -0
  50. package/dist/channels/discord/markdown.d.ts +19 -0
  51. package/dist/channels/discord/markdown.js +62 -0
  52. package/dist/channels/manager.d.ts +29 -0
  53. package/dist/channels/manager.js +100 -0
  54. package/dist/channels/slack/channel.d.ts +37 -0
  55. package/dist/channels/slack/channel.js +227 -0
  56. package/dist/channels/slack/markdown.d.ts +19 -0
  57. package/dist/channels/slack/markdown.js +62 -0
  58. package/dist/channels/specs.d.ts +32 -0
  59. package/dist/channels/specs.js +99 -0
  60. package/dist/channels/telegram/channel.d.ts +29 -0
  61. package/dist/channels/telegram/channel.js +182 -0
  62. package/dist/channels/telegram/markdown.d.ts +17 -0
  63. package/dist/channels/telegram/markdown.js +66 -0
  64. package/dist/channels/types.d.ts +26 -0
  65. package/dist/channels/types.js +1 -0
  66. package/dist/channels/whatsapp/channel.d.ts +34 -0
  67. package/dist/channels/whatsapp/channel.js +276 -0
  68. package/dist/channels/whatsapp/markdown.d.ts +20 -0
  69. package/dist/channels/whatsapp/markdown.js +51 -0
  70. package/dist/cli/claude-login.d.ts +5 -0
  71. package/dist/cli/claude-login.js +47 -0
  72. package/dist/cli/config.d.ts +5 -0
  73. package/dist/cli/config.js +78 -0
  74. package/dist/cli/index.d.ts +11 -0
  75. package/dist/cli/index.js +96 -0
  76. package/dist/computer/browser/actions.d.ts +31 -0
  77. package/dist/computer/browser/actions.js +148 -0
  78. package/dist/computer/browser/context-manager.d.ts +28 -0
  79. package/dist/computer/browser/context-manager.js +78 -0
  80. package/dist/computer/browser/manager.d.ts +91 -0
  81. package/dist/computer/browser/manager.js +344 -0
  82. package/dist/computer/browser/profile-badge.d.ts +13 -0
  83. package/dist/computer/browser/profile-badge.js +67 -0
  84. package/dist/computer/browser/screenshot.d.ts +5 -0
  85. package/dist/computer/browser/screenshot.js +21 -0
  86. package/dist/computer/browser/snapshot.d.ts +30 -0
  87. package/dist/computer/browser/snapshot.js +242 -0
  88. package/dist/computer/browser/tools.d.ts +5 -0
  89. package/dist/computer/browser/tools.js +167 -0
  90. package/dist/computer/browser/types.d.ts +26 -0
  91. package/dist/computer/browser/types.js +1 -0
  92. package/dist/computer/desktop/adapter.d.ts +25 -0
  93. package/dist/computer/desktop/adapter.js +11 -0
  94. package/dist/computer/desktop/macos.d.ts +24 -0
  95. package/dist/computer/desktop/macos.js +223 -0
  96. package/dist/computer/desktop/tools.d.ts +25 -0
  97. package/dist/computer/desktop/tools.js +114 -0
  98. package/dist/config/agent-config.d.ts +55 -0
  99. package/dist/config/agent-config.js +16 -0
  100. package/dist/config/model-catalog.d.ts +22 -0
  101. package/dist/config/model-catalog.js +112 -0
  102. package/dist/config/model-spec.d.ts +8 -0
  103. package/dist/config/model-spec.js +66 -0
  104. package/dist/config/store.d.ts +25 -0
  105. package/dist/config/store.js +143 -0
  106. package/dist/config.d.ts +110 -0
  107. package/dist/config.js +259 -0
  108. package/dist/control-ui/assets/index-Cbl7G5Sc.css +1 -0
  109. package/dist/control-ui/assets/index-Cu1P4C62.js +266 -0
  110. package/dist/control-ui/assets/noto-sans-cyrillic-ext-wght-normal-DSNfmdVt.woff2 +0 -0
  111. package/dist/control-ui/assets/noto-sans-cyrillic-wght-normal-B2hlT84T.woff2 +0 -0
  112. package/dist/control-ui/assets/noto-sans-devanagari-wght-normal-Cv-Vwajv.woff2 +0 -0
  113. package/dist/control-ui/assets/noto-sans-greek-ext-wght-normal-12T8GTDR.woff2 +0 -0
  114. package/dist/control-ui/assets/noto-sans-greek-wght-normal-Ymb6dZNd.woff2 +0 -0
  115. package/dist/control-ui/assets/noto-sans-latin-ext-wght-normal-W1qJv59z.woff2 +0 -0
  116. package/dist/control-ui/assets/noto-sans-latin-wght-normal-BYSzYMf3.woff2 +0 -0
  117. package/dist/control-ui/assets/noto-sans-vietnamese-wght-normal-DLTJy58D.woff2 +0 -0
  118. package/dist/control-ui/index.html +14 -0
  119. package/dist/control-ui/vite.svg +1 -0
  120. package/dist/events.d.ts +2 -0
  121. package/dist/events.js +11 -0
  122. package/dist/gateway/broadcast.d.ts +5 -0
  123. package/dist/gateway/broadcast.js +33 -0
  124. package/dist/gateway/methods/aliases.d.ts +17 -0
  125. package/dist/gateway/methods/aliases.js +22 -0
  126. package/dist/gateway/methods/chat.d.ts +33 -0
  127. package/dist/gateway/methods/chat.js +37 -0
  128. package/dist/gateway/methods/config.d.ts +14 -0
  129. package/dist/gateway/methods/config.js +24 -0
  130. package/dist/gateway/methods/models.d.ts +10 -0
  131. package/dist/gateway/methods/models.js +14 -0
  132. package/dist/gateway/methods/playbooks.d.ts +45 -0
  133. package/dist/gateway/methods/playbooks.js +488 -0
  134. package/dist/gateway/methods/prompt-templates.d.ts +27 -0
  135. package/dist/gateway/methods/prompt-templates.js +106 -0
  136. package/dist/gateway/methods/scheduler.d.ts +62 -0
  137. package/dist/gateway/methods/scheduler.js +129 -0
  138. package/dist/gateway/methods/sessions.d.ts +44 -0
  139. package/dist/gateway/methods/sessions.js +111 -0
  140. package/dist/gateway/methods/system.d.ts +12 -0
  141. package/dist/gateway/methods/system.js +39 -0
  142. package/dist/gateway/methods/tasks.d.ts +40 -0
  143. package/dist/gateway/methods/tasks.js +151 -0
  144. package/dist/gateway/methods/teams.d.ts +69 -0
  145. package/dist/gateway/methods/teams.js +376 -0
  146. package/dist/gateway/methods/tools.d.ts +6 -0
  147. package/dist/gateway/methods/tools.js +7 -0
  148. package/dist/gateway/methods/whatsapp.d.ts +19 -0
  149. package/dist/gateway/methods/whatsapp.js +35 -0
  150. package/dist/gateway/rpc.d.ts +38 -0
  151. package/dist/gateway/rpc.js +79 -0
  152. package/dist/gateway/server.d.ts +9 -0
  153. package/dist/gateway/server.js +137 -0
  154. package/dist/index.d.ts +1 -0
  155. package/dist/index.js +254 -0
  156. package/dist/integrations/github.d.ts +7 -0
  157. package/dist/integrations/github.js +133 -0
  158. package/dist/integrations/mcp.d.ts +7 -0
  159. package/dist/integrations/mcp.js +106 -0
  160. package/dist/integrations/registry.d.ts +47 -0
  161. package/dist/integrations/registry.js +332 -0
  162. package/dist/integrations/scanner.d.ts +10 -0
  163. package/dist/integrations/scanner.js +122 -0
  164. package/dist/integrations/twitter.d.ts +10 -0
  165. package/dist/integrations/twitter.js +120 -0
  166. package/dist/integrations/types.d.ts +72 -0
  167. package/dist/integrations/types.js +1 -0
  168. package/dist/logger.d.ts +16 -0
  169. package/dist/logger.js +104 -0
  170. package/dist/markdown/chunk.d.ts +9 -0
  171. package/dist/markdown/chunk.js +52 -0
  172. package/dist/markdown/ir.d.ts +37 -0
  173. package/dist/markdown/ir.js +529 -0
  174. package/dist/markdown/render.d.ts +22 -0
  175. package/dist/markdown/render.js +148 -0
  176. package/dist/markdown/table-render.d.ts +43 -0
  177. package/dist/markdown/table-render.js +219 -0
  178. package/dist/markdown/tables.d.ts +17 -0
  179. package/dist/markdown/tables.js +27 -0
  180. package/dist/memory/embedding.d.ts +16 -0
  181. package/dist/memory/embedding.js +66 -0
  182. package/dist/memory/explicit.d.ts +16 -0
  183. package/dist/memory/explicit.js +29 -0
  184. package/dist/memory/extractor.d.ts +13 -0
  185. package/dist/memory/extractor.js +82 -0
  186. package/dist/memory/search.d.ts +15 -0
  187. package/dist/memory/search.js +57 -0
  188. package/dist/memory/session-learning.d.ts +23 -0
  189. package/dist/memory/session-learning.js +55 -0
  190. package/dist/memory/store.d.ts +36 -0
  191. package/dist/memory/store.js +334 -0
  192. package/dist/memory/types.d.ts +9 -0
  193. package/dist/memory/types.js +2 -0
  194. package/dist/paths.d.ts +28 -0
  195. package/dist/paths.js +48 -0
  196. package/dist/prompt-templates/builtins/index.d.ts +4 -0
  197. package/dist/prompt-templates/builtins/index.js +5 -0
  198. package/dist/prompt-templates/builtins/planner.d.ts +4 -0
  199. package/dist/prompt-templates/builtins/planner.js +77 -0
  200. package/dist/prompt-templates/store.d.ts +45 -0
  201. package/dist/prompt-templates/store.js +224 -0
  202. package/dist/prompt-templates/types.d.ts +10 -0
  203. package/dist/prompt-templates/types.js +1 -0
  204. package/dist/scheduler/connected-channels.d.ts +24 -0
  205. package/dist/scheduler/connected-channels.js +57 -0
  206. package/dist/scheduler/scheduler.d.ts +22 -0
  207. package/dist/scheduler/scheduler.js +132 -0
  208. package/dist/scheduler/store.d.ts +27 -0
  209. package/dist/scheduler/store.js +205 -0
  210. package/dist/scheduler/types.d.ts +29 -0
  211. package/dist/scheduler/types.js +1 -0
  212. package/dist/security/command-validator.d.ts +22 -0
  213. package/dist/security/command-validator.js +160 -0
  214. package/dist/security/docker-sandbox.d.ts +48 -0
  215. package/dist/security/docker-sandbox.js +218 -0
  216. package/dist/security/env-filter.d.ts +8 -0
  217. package/dist/security/env-filter.js +41 -0
  218. package/dist/skills/loader.d.ts +33 -0
  219. package/dist/skills/loader.js +132 -0
  220. package/dist/skills/prompt.d.ts +6 -0
  221. package/dist/skills/prompt.js +17 -0
  222. package/dist/skills/read-tool.d.ts +7 -0
  223. package/dist/skills/read-tool.js +24 -0
  224. package/dist/skills/scanner.d.ts +6 -0
  225. package/dist/skills/scanner.js +73 -0
  226. package/dist/skills/types.d.ts +15 -0
  227. package/dist/skills/types.js +1 -0
  228. package/dist/tasks/inline-attachment-content.d.ts +9 -0
  229. package/dist/tasks/inline-attachment-content.js +64 -0
  230. package/dist/tasks/store.d.ts +112 -0
  231. package/dist/tasks/store.js +519 -0
  232. package/dist/tasks/types.d.ts +129 -0
  233. package/dist/tasks/types.js +80 -0
  234. package/dist/teams/status-config.d.ts +8 -0
  235. package/dist/teams/status-config.js +40 -0
  236. package/dist/teams/store.d.ts +111 -0
  237. package/dist/teams/store.js +671 -0
  238. package/dist/teams/types.d.ts +30 -0
  239. package/dist/teams/types.js +1 -0
  240. package/dist/tools/bash.d.ts +18 -0
  241. package/dist/tools/bash.js +64 -0
  242. package/dist/tools/channel-history.d.ts +10 -0
  243. package/dist/tools/channel-history.js +43 -0
  244. package/dist/tools/delegate.d.ts +20 -0
  245. package/dist/tools/delegate.js +299 -0
  246. package/dist/tools/fs.d.ts +4 -0
  247. package/dist/tools/fs.js +335 -0
  248. package/dist/tools/integration-toggle.d.ts +14 -0
  249. package/dist/tools/integration-toggle.js +47 -0
  250. package/dist/tools/memory.d.ts +13 -0
  251. package/dist/tools/memory.js +59 -0
  252. package/dist/tools/prompt-templates.d.ts +7 -0
  253. package/dist/tools/prompt-templates.js +133 -0
  254. package/dist/tools/registry.d.ts +6 -0
  255. package/dist/tools/registry.js +9 -0
  256. package/dist/tools/schedule.d.ts +8 -0
  257. package/dist/tools/schedule.js +219 -0
  258. package/dist/tools/speak.d.ts +10 -0
  259. package/dist/tools/speak.js +56 -0
  260. package/dist/tools/tasks.d.ts +67 -0
  261. package/dist/tools/tasks.js +288 -0
  262. package/dist/tools/teams.d.ts +22 -0
  263. package/dist/tools/teams.js +470 -0
  264. package/dist/tools/web-fetch.d.ts +3 -0
  265. package/dist/tools/web-fetch.js +22 -0
  266. package/dist/tts/edge.d.ts +10 -0
  267. package/dist/tts/edge.js +60 -0
  268. package/dist/tts/speak.d.ts +12 -0
  269. package/dist/tts/speak.js +81 -0
  270. package/dist/tts/transcribe.d.ts +5 -0
  271. package/dist/tts/transcribe.js +40 -0
  272. package/dist/utils.d.ts +5 -0
  273. package/dist/utils.js +22 -0
  274. package/dist/version.d.ts +1 -0
  275. package/dist/version.js +13 -0
  276. package/package.json +102 -0
  277. package/verybot.js +2 -0
@@ -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 {};
@@ -0,0 +1,160 @@
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
+ /** Shell tokens blocked outside of quotes. */
8
+ const BLOCKED_CHARS = new Set(["`", ">", "<", "(", ")"]);
9
+ /** Default read-only commands that always pass in allowlist mode. */
10
+ export const DEFAULT_SAFE_BINS = new Set([
11
+ "ls",
12
+ "cat",
13
+ "head",
14
+ "tail",
15
+ "grep",
16
+ "rg",
17
+ "find",
18
+ "wc",
19
+ "sort",
20
+ "uniq",
21
+ "cut",
22
+ "tr",
23
+ "jq",
24
+ "date",
25
+ "whoami",
26
+ "pwd",
27
+ "echo",
28
+ "which",
29
+ "file",
30
+ "stat",
31
+ "du",
32
+ "df",
33
+ "uname",
34
+ "env",
35
+ "printenv",
36
+ "ps",
37
+ "curl",
38
+ "wget",
39
+ ]);
40
+ /**
41
+ * Check for dangerous shell syntax (quote-aware).
42
+ *
43
+ * Blocks backticks, command substitution `$(...)`, redirects `> <`,
44
+ * and subshells `( )` when they appear outside of quotes.
45
+ */
46
+ export function validateCommand(command) {
47
+ let inSingle = false;
48
+ let inDouble = false;
49
+ for (let i = 0; i < command.length; i++) {
50
+ const ch = command[i];
51
+ // Track quote state
52
+ if (ch === "'" && !inDouble) {
53
+ inSingle = !inSingle;
54
+ continue;
55
+ }
56
+ if (ch === '"' && !inSingle) {
57
+ inDouble = !inDouble;
58
+ continue;
59
+ }
60
+ // Skip chars inside quotes
61
+ if (inSingle || inDouble)
62
+ continue;
63
+ // Check for $( command substitution
64
+ if (ch === "$" && i + 1 < command.length && command[i + 1] === "(") {
65
+ return { ok: false, reason: "command substitution $(...) is not allowed" };
66
+ }
67
+ if (BLOCKED_CHARS.has(ch)) {
68
+ const names = {
69
+ "`": "backtick execution",
70
+ ">": "output redirect",
71
+ "<": "input redirect",
72
+ "(": "subshell",
73
+ ")": "subshell",
74
+ };
75
+ return { ok: false, reason: `${names[ch] ?? ch} is not allowed` };
76
+ }
77
+ }
78
+ return { ok: true };
79
+ }
80
+ /**
81
+ * Extract the executable name from a command string.
82
+ * Handles env-prefix patterns like `VAR=val cmd` and paths like `/usr/bin/python3`.
83
+ */
84
+ function extractExecutable(segment) {
85
+ const trimmed = segment.trim();
86
+ const tokens = trimmed.split(/\s+/);
87
+ // Skip leading env assignments (KEY=VALUE)
88
+ let idx = 0;
89
+ while (idx < tokens.length && /^[A-Za-z_]\w*=/.test(tokens[idx])) {
90
+ idx++;
91
+ }
92
+ const raw = tokens[idx] ?? "";
93
+ // Strip path: /usr/bin/python3 -> python3
94
+ const slashIdx = raw.lastIndexOf("/");
95
+ return slashIdx >= 0 ? raw.slice(slashIdx + 1) : raw;
96
+ }
97
+ /** Split a command into segments on `&&`, `||`, `;`, and `|`. */
98
+ function splitSegments(command) {
99
+ // Split on && || ; | while respecting quotes
100
+ const segments = [];
101
+ let current = "";
102
+ let inSingle = false;
103
+ let inDouble = false;
104
+ for (let i = 0; i < command.length; i++) {
105
+ const ch = command[i];
106
+ if (ch === "'" && !inDouble) {
107
+ inSingle = !inSingle;
108
+ current += ch;
109
+ continue;
110
+ }
111
+ if (ch === '"' && !inSingle) {
112
+ inDouble = !inDouble;
113
+ current += ch;
114
+ continue;
115
+ }
116
+ if (inSingle || inDouble) {
117
+ current += ch;
118
+ continue;
119
+ }
120
+ // Check for && or ||
121
+ if ((ch === "&" || ch === "|") && i + 1 < command.length && command[i + 1] === ch) {
122
+ segments.push(current);
123
+ current = "";
124
+ i++; // skip second char
125
+ continue;
126
+ }
127
+ // Check for ; or single |
128
+ if (ch === ";" || ch === "|") {
129
+ segments.push(current);
130
+ current = "";
131
+ continue;
132
+ }
133
+ current += ch;
134
+ }
135
+ if (current.trim())
136
+ segments.push(current);
137
+ return segments.filter((s) => s.trim().length > 0);
138
+ }
139
+ /** Check if ALL segments of a command use only safe bins. */
140
+ function isSafeBin(command, safeBins) {
141
+ const segments = splitSegments(command);
142
+ return segments.every((seg) => safeBins.has(extractExecutable(seg)));
143
+ }
144
+ /** Convert a simple glob pattern to a RegExp (`*` -> `.*`). */
145
+ function globToRegExp(pattern) {
146
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
147
+ return new RegExp(`^${escaped}$`);
148
+ }
149
+ /** Check if an executable matches any allowlist glob pattern. */
150
+ function matchesAllowlist(executable, patterns) {
151
+ return patterns.some((pat) => globToRegExp(pat).test(executable));
152
+ }
153
+ /** Check if ALL segments of a command match the allowlist. */
154
+ export function isAllowed(command, safeBins, allowlist) {
155
+ const segments = splitSegments(command);
156
+ return segments.every((seg) => {
157
+ const exe = extractExecutable(seg);
158
+ return safeBins.has(exe) || matchesAllowlist(exe, allowlist);
159
+ });
160
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Docker sandbox for bash tool execution.
3
+ *
4
+ * Commands run inside isolated Docker containers with:
5
+ * - Read-only root filesystem
6
+ * - No network access
7
+ * - All capabilities dropped
8
+ * - Resource limits (memory, pids)
9
+ * - Workspace mounted at /workspace (read-write)
10
+ *
11
+ * Containers are created lazily per session and reused for subsequent commands.
12
+ * Idle containers are pruned after a configurable timeout.
13
+ *
14
+ * If Docker is not available at startup, the sandbox polls in the background
15
+ * and returns an error to the user until Docker comes online.
16
+ */
17
+ export interface SandboxConfig {
18
+ image: string;
19
+ memoryLimit: string;
20
+ pidsLimit: number;
21
+ idleTimeoutMs: number;
22
+ }
23
+ export declare class DockerSandbox {
24
+ private containers;
25
+ private config;
26
+ private ready;
27
+ constructor(config: SandboxConfig);
28
+ /**
29
+ * Execute a command in the sandbox container for the given session.
30
+ * Checks Docker availability on first use (and after losing connection).
31
+ */
32
+ exec(sessionKey: string, command: string): string;
33
+ /** Remove container for a session. */
34
+ cleanup(sessionKey: string): void;
35
+ /** Remove all containers (call on shutdown). */
36
+ cleanupAll(): void;
37
+ private getOrCreateContainer;
38
+ private resetIdleTimer;
39
+ private onIdle;
40
+ private removeContainer;
41
+ private containerName;
42
+ /**
43
+ * Distinguish Docker daemon errors (connection lost, daemon stopped) from
44
+ * normal command failures inside the container (non-zero exit code).
45
+ */
46
+ private isDockerError;
47
+ private pruneOrphans;
48
+ }
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Docker sandbox for bash tool execution.
3
+ *
4
+ * Commands run inside isolated Docker containers with:
5
+ * - Read-only root filesystem
6
+ * - No network access
7
+ * - All capabilities dropped
8
+ * - Resource limits (memory, pids)
9
+ * - Workspace mounted at /workspace (read-write)
10
+ *
11
+ * Containers are created lazily per session and reused for subsequent commands.
12
+ * Idle containers are pruned after a configurable timeout.
13
+ *
14
+ * If Docker is not available at startup, the sandbox polls in the background
15
+ * and returns an error to the user until Docker comes online.
16
+ */
17
+ import { execFileSync } from "child_process";
18
+ import { createHash } from "crypto";
19
+ import { mkdirSync } from "fs";
20
+ import { resolve } from "path";
21
+ import { logger } from "../logger.js";
22
+ import { SANDBOX_WORKSPACE } from "../paths.js";
23
+ const CONTAINER_PREFIX = "verybot-sandbox";
24
+ const LABEL = "verybot-sandbox=1";
25
+ const EXEC_TIMEOUT = 30_000;
26
+ const MAX_OUTPUT = 10_000;
27
+ const MAX_ERROR = 5_000;
28
+ /** Check if Docker is available by running `docker info`. */
29
+ function checkDocker() {
30
+ try {
31
+ execFileSync("docker", ["info"], { stdio: "ignore", timeout: 5_000 });
32
+ return true;
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
38
+ export class DockerSandbox {
39
+ containers = new Map();
40
+ config;
41
+ ready = false;
42
+ constructor(config) {
43
+ this.config = config;
44
+ mkdirSync(resolve(SANDBOX_WORKSPACE), { recursive: true });
45
+ logger.info(`Docker sandbox: configured (image=${config.image}, memory=${config.memoryLimit}, ` +
46
+ `pids=${config.pidsLimit}, workspace=${SANDBOX_WORKSPACE}, idle=${config.idleTimeoutMs}ms)`);
47
+ }
48
+ /**
49
+ * Execute a command in the sandbox container for the given session.
50
+ * Checks Docker availability on first use (and after losing connection).
51
+ */
52
+ exec(sessionKey, command) {
53
+ if (!this.ready) {
54
+ if (!checkDocker()) {
55
+ return "Docker is not available. Please start Docker and try again.";
56
+ }
57
+ this.ready = true;
58
+ this.pruneOrphans();
59
+ logger.info("Docker sandbox: Docker is available, ready to execute");
60
+ }
61
+ const handle = this.getOrCreateContainer(sessionKey);
62
+ this.resetIdleTimer(sessionKey, handle);
63
+ try {
64
+ const output = execFileSync("docker", ["exec", handle.id, "sh", "-c", command], {
65
+ encoding: "utf-8",
66
+ timeout: EXEC_TIMEOUT,
67
+ maxBuffer: 1024 * 1024,
68
+ });
69
+ return output.slice(0, MAX_OUTPUT);
70
+ }
71
+ catch (err) {
72
+ // If docker exec itself failed (not the command inside), reset ready flag
73
+ // so next call re-checks Docker availability
74
+ if (this.isDockerError(err)) {
75
+ logger.warn("Docker sandbox: Docker connection lost, will re-check on next exec");
76
+ this.ready = false;
77
+ this.containers.delete(sessionKey);
78
+ }
79
+ const msg = err instanceof Error ? err.message : String(err);
80
+ const stderr = err.stderr ?? "";
81
+ return `Error: ${msg}\n${stderr}`.slice(0, MAX_ERROR);
82
+ }
83
+ }
84
+ /** Remove container for a session. */
85
+ cleanup(sessionKey) {
86
+ const handle = this.containers.get(sessionKey);
87
+ if (!handle)
88
+ return;
89
+ clearTimeout(handle.timer);
90
+ this.removeContainer(handle);
91
+ this.containers.delete(sessionKey);
92
+ }
93
+ /** Remove all containers (call on shutdown). */
94
+ cleanupAll() {
95
+ for (const [key, handle] of this.containers) {
96
+ clearTimeout(handle.timer);
97
+ this.removeContainer(handle);
98
+ this.containers.delete(key);
99
+ }
100
+ logger.info("Docker sandbox: all containers cleaned up");
101
+ }
102
+ getOrCreateContainer(sessionKey) {
103
+ const existing = this.containers.get(sessionKey);
104
+ if (existing)
105
+ return existing;
106
+ const name = this.containerName(sessionKey);
107
+ const workspaceAbs = resolve(SANDBOX_WORKSPACE);
108
+ // Create container
109
+ const createArgs = [
110
+ "create",
111
+ "--name",
112
+ name,
113
+ "--label",
114
+ LABEL,
115
+ "--read-only",
116
+ "--tmpfs",
117
+ "/tmp:rw,noexec,nosuid,size=64m",
118
+ "--network",
119
+ "none",
120
+ "--cap-drop",
121
+ "ALL",
122
+ "--security-opt",
123
+ "no-new-privileges",
124
+ "--memory",
125
+ this.config.memoryLimit,
126
+ "--pids-limit",
127
+ String(this.config.pidsLimit),
128
+ "-v",
129
+ `${workspaceAbs}:/workspace:rw`,
130
+ "-w",
131
+ "/workspace",
132
+ this.config.image,
133
+ "sleep",
134
+ "infinity",
135
+ ];
136
+ const id = execFileSync("docker", createArgs, {
137
+ encoding: "utf-8",
138
+ timeout: 30_000,
139
+ }).trim();
140
+ // Start container
141
+ execFileSync("docker", ["start", id], {
142
+ stdio: "ignore",
143
+ timeout: 10_000,
144
+ });
145
+ logger.info(`Docker sandbox: created container ${name} (${id.slice(0, 12)}) for ${sessionKey}`);
146
+ const handle = {
147
+ id,
148
+ name,
149
+ timer: setTimeout(() => this.onIdle(sessionKey), this.config.idleTimeoutMs),
150
+ };
151
+ handle.timer.unref();
152
+ this.containers.set(sessionKey, handle);
153
+ return handle;
154
+ }
155
+ resetIdleTimer(sessionKey, handle) {
156
+ clearTimeout(handle.timer);
157
+ handle.timer = setTimeout(() => this.onIdle(sessionKey), this.config.idleTimeoutMs);
158
+ handle.timer.unref();
159
+ }
160
+ onIdle(sessionKey) {
161
+ const handle = this.containers.get(sessionKey);
162
+ if (!handle)
163
+ return;
164
+ logger.info(`Docker sandbox: pruning idle container ${handle.name} for ${sessionKey}`);
165
+ this.removeContainer(handle);
166
+ this.containers.delete(sessionKey);
167
+ }
168
+ removeContainer(handle) {
169
+ try {
170
+ execFileSync("docker", ["rm", "-f", handle.id], {
171
+ stdio: "ignore",
172
+ timeout: 10_000,
173
+ });
174
+ }
175
+ catch (err) {
176
+ logger.warn(`Docker sandbox: failed to remove ${handle.name}: ${err instanceof Error ? err.message : err}`);
177
+ }
178
+ }
179
+ containerName(sessionKey) {
180
+ const hash = createHash("sha256").update(sessionKey).digest("hex").slice(0, 12);
181
+ return `${CONTAINER_PREFIX}-${hash}`;
182
+ }
183
+ /**
184
+ * Distinguish Docker daemon errors (connection lost, daemon stopped) from
185
+ * normal command failures inside the container (non-zero exit code).
186
+ */
187
+ isDockerError(err) {
188
+ const msg = err instanceof Error ? err.message : String(err);
189
+ const stderr = err.stderr ?? "";
190
+ const combined = `${msg}\n${stderr}`.toLowerCase();
191
+ // These indicate Docker itself is unavailable, not a command failure
192
+ return (combined.includes("cannot connect to the docker daemon") ||
193
+ combined.includes("connection refused") ||
194
+ combined.includes("is the docker daemon running") ||
195
+ combined.includes("no such container") ||
196
+ combined.includes("is not running"));
197
+ }
198
+ pruneOrphans() {
199
+ try {
200
+ const ids = execFileSync("docker", ["ps", "-aq", "--filter", `label=${LABEL}`], { encoding: "utf-8", timeout: 5_000 }).trim();
201
+ if (!ids)
202
+ return;
203
+ const idList = ids.split("\n").filter(Boolean);
204
+ for (const id of idList) {
205
+ try {
206
+ execFileSync("docker", ["rm", "-f", id], { stdio: "ignore", timeout: 5_000 });
207
+ }
208
+ catch {
209
+ // ignore individual failures
210
+ }
211
+ }
212
+ logger.info(`Docker sandbox: pruned ${idList.length} orphan container(s)`);
213
+ }
214
+ catch {
215
+ // docker ps failed, ignore
216
+ }
217
+ }
218
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Environment variable filtering for bash tool security.
3
+ *
4
+ * Strips dangerous env vars that could be used for code injection
5
+ * (LD_PRELOAD, DYLD_*, NODE_OPTIONS, etc.) before passing to child processes.
6
+ */
7
+ /** Returns a copy of process.env with dangerous variables removed. */
8
+ export declare function sanitizeEnv(): Record<string, string>;