@stevederico/dotbot 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +136 -0
- package/README.md +380 -0
- package/bin/dotbot.js +461 -0
- package/core/agent.js +779 -0
- package/core/compaction.js +261 -0
- package/core/cron_handler.js +262 -0
- package/core/events.js +229 -0
- package/core/failover.js +193 -0
- package/core/gptoss_tool_parser.js +173 -0
- package/core/init.js +154 -0
- package/core/normalize.js +324 -0
- package/core/trigger_handler.js +148 -0
- package/docs/core.md +103 -0
- package/docs/protected-files.md +59 -0
- package/examples/sqlite-session-example.js +69 -0
- package/index.js +341 -0
- package/observer/index.js +164 -0
- package/package.json +42 -0
- package/storage/CronStore.js +145 -0
- package/storage/EventStore.js +71 -0
- package/storage/MemoryStore.js +175 -0
- package/storage/MongoAdapter.js +291 -0
- package/storage/MongoCronAdapter.js +347 -0
- package/storage/MongoTaskAdapter.js +242 -0
- package/storage/MongoTriggerAdapter.js +158 -0
- package/storage/SQLiteAdapter.js +382 -0
- package/storage/SQLiteCronAdapter.js +562 -0
- package/storage/SQLiteEventStore.js +300 -0
- package/storage/SQLiteMemoryAdapter.js +240 -0
- package/storage/SQLiteTaskAdapter.js +419 -0
- package/storage/SQLiteTriggerAdapter.js +262 -0
- package/storage/SessionStore.js +149 -0
- package/storage/TaskStore.js +100 -0
- package/storage/TriggerStore.js +90 -0
- package/storage/cron_constants.js +48 -0
- package/storage/index.js +21 -0
- package/tools/appgen.js +311 -0
- package/tools/browser.js +634 -0
- package/tools/code.js +101 -0
- package/tools/events.js +145 -0
- package/tools/files.js +201 -0
- package/tools/images.js +253 -0
- package/tools/index.js +97 -0
- package/tools/jobs.js +159 -0
- package/tools/memory.js +332 -0
- package/tools/messages.js +135 -0
- package/tools/notify.js +42 -0
- package/tools/tasks.js +404 -0
- package/tools/triggers.js +159 -0
- package/tools/weather.js +82 -0
- package/tools/web.js +283 -0
- package/utils/providers.js +136 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { TaskStore } from './TaskStore.js';
|
|
2
|
+
|
|
3
|
+
// Lazy-load mongodb to avoid hard dependency at module evaluation time
|
|
4
|
+
let _ObjectId = null;
|
|
5
|
+
async function getObjectId() {
|
|
6
|
+
if (!_ObjectId) { _ObjectId = (await import('mongodb')).ObjectId; }
|
|
7
|
+
return _ObjectId;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* MongoDB implementation of TaskStore
|
|
12
|
+
*/
|
|
13
|
+
export class MongoTaskStore extends TaskStore {
|
|
14
|
+
constructor() {
|
|
15
|
+
super();
|
|
16
|
+
this.collection = null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Initialize MongoDB task store
|
|
21
|
+
*
|
|
22
|
+
* @param {import('mongodb').Db} db - MongoDB database instance
|
|
23
|
+
* @param {Object} options - Optional configuration
|
|
24
|
+
*/
|
|
25
|
+
async init(db, options = {}) {
|
|
26
|
+
this.collection = db.collection('tasks');
|
|
27
|
+
|
|
28
|
+
// Create indexes
|
|
29
|
+
await this.collection.createIndex({ userId: 1, status: 1 }).catch(() => {});
|
|
30
|
+
await this.collection.createIndex({ userId: 1, category: 1 }).catch(() => {});
|
|
31
|
+
await this.collection.createIndex({ userId: 1, priority: 1 }).catch(() => {});
|
|
32
|
+
await this.collection.createIndex({ userId: 1, deadline: 1 }).catch(() => {});
|
|
33
|
+
|
|
34
|
+
console.log('[tasks] MongoTaskStore initialized');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a new task
|
|
39
|
+
*/
|
|
40
|
+
async createTask({ userId, description, steps = [], category = 'general', priority = 'medium', deadline = null, mode = 'auto' }) {
|
|
41
|
+
// Normalize steps to objects
|
|
42
|
+
const normalizedSteps = steps.map(step => {
|
|
43
|
+
if (typeof step === 'string') {
|
|
44
|
+
return {
|
|
45
|
+
text: step,
|
|
46
|
+
action: step, // Default action is same as text
|
|
47
|
+
done: false,
|
|
48
|
+
result: null,
|
|
49
|
+
startedAt: null,
|
|
50
|
+
completedAt: null,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
text: step.text || step.description || '',
|
|
55
|
+
action: step.action || step.text || '',
|
|
56
|
+
done: step.done || false,
|
|
57
|
+
result: step.result || null,
|
|
58
|
+
startedAt: step.startedAt || null,
|
|
59
|
+
completedAt: step.completedAt || null,
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const doc = {
|
|
64
|
+
userId,
|
|
65
|
+
description,
|
|
66
|
+
steps: normalizedSteps,
|
|
67
|
+
category,
|
|
68
|
+
priority,
|
|
69
|
+
deadline,
|
|
70
|
+
mode, // 'auto' or 'manual'
|
|
71
|
+
status: 'pending', // pending, in_progress, completed
|
|
72
|
+
currentStep: 0,
|
|
73
|
+
progress: 0,
|
|
74
|
+
createdAt: new Date(),
|
|
75
|
+
updatedAt: new Date(),
|
|
76
|
+
lastWorkedAt: null,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const result = await this.collection.insertOne(doc);
|
|
80
|
+
return { ...doc, _id: result.insertedId };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get tasks for a user
|
|
85
|
+
*/
|
|
86
|
+
async getTasks(userId, filters = {}) {
|
|
87
|
+
const query = { userId };
|
|
88
|
+
|
|
89
|
+
if (filters.status) query.status = filters.status;
|
|
90
|
+
if (filters.category) query.category = filters.category;
|
|
91
|
+
if (filters.priority) query.priority = filters.priority;
|
|
92
|
+
|
|
93
|
+
const tasks = await this.collection
|
|
94
|
+
.find(query)
|
|
95
|
+
.sort({ createdAt: -1 })
|
|
96
|
+
.toArray();
|
|
97
|
+
|
|
98
|
+
// Calculate progress for each task
|
|
99
|
+
return tasks.map(task => ({
|
|
100
|
+
...task,
|
|
101
|
+
progress: this._calculateProgress(task),
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get a single task by ID
|
|
107
|
+
*/
|
|
108
|
+
async getTask(userId, taskId) {
|
|
109
|
+
const ObjectId = await getObjectId();
|
|
110
|
+
const task = await this.collection.findOne({
|
|
111
|
+
_id: new ObjectId(taskId),
|
|
112
|
+
userId,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (!task) return null;
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
...task,
|
|
119
|
+
progress: this._calculateProgress(task),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Update a task
|
|
125
|
+
*/
|
|
126
|
+
async updateTask(userId, taskId, updates) {
|
|
127
|
+
const ObjectId = await getObjectId();
|
|
128
|
+
const validUpdates = {};
|
|
129
|
+
const allowedFields = [
|
|
130
|
+
'description', 'steps', 'category', 'priority', 'deadline',
|
|
131
|
+
'mode', 'status', 'currentStep', 'lastWorkedAt'
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
for (const field of allowedFields) {
|
|
135
|
+
if (updates[field] !== undefined) {
|
|
136
|
+
validUpdates[field] = updates[field];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Recalculate progress if steps changed
|
|
141
|
+
if (updates.steps) {
|
|
142
|
+
validUpdates.progress = this._calculateProgressFromSteps(updates.steps);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
validUpdates.updatedAt = new Date();
|
|
146
|
+
|
|
147
|
+
const result = await this.collection.updateOne(
|
|
148
|
+
{ _id: new ObjectId(taskId), userId },
|
|
149
|
+
{ $set: validUpdates }
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Delete a task
|
|
157
|
+
*/
|
|
158
|
+
async deleteTask(userId, taskId) {
|
|
159
|
+
const ObjectId = await getObjectId();
|
|
160
|
+
const result = await this.collection.deleteOne({
|
|
161
|
+
_id: new ObjectId(taskId),
|
|
162
|
+
userId,
|
|
163
|
+
});
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Search tasks by text
|
|
169
|
+
*/
|
|
170
|
+
async searchTasks(userId, query) {
|
|
171
|
+
const regex = new RegExp(query, 'i');
|
|
172
|
+
const tasks = await this.collection
|
|
173
|
+
.find({
|
|
174
|
+
userId,
|
|
175
|
+
$or: [
|
|
176
|
+
{ description: regex },
|
|
177
|
+
{ 'steps.text': regex },
|
|
178
|
+
],
|
|
179
|
+
})
|
|
180
|
+
.sort({ createdAt: -1 })
|
|
181
|
+
.toArray();
|
|
182
|
+
|
|
183
|
+
return tasks.map(task => ({
|
|
184
|
+
...task,
|
|
185
|
+
progress: this._calculateProgress(task),
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get task statistics
|
|
191
|
+
*/
|
|
192
|
+
async getTaskStats(userId) {
|
|
193
|
+
const tasks = await this.collection.find({ userId }).toArray();
|
|
194
|
+
|
|
195
|
+
const stats = {
|
|
196
|
+
total: tasks.length,
|
|
197
|
+
pending: tasks.filter(g => g.status === 'pending').length,
|
|
198
|
+
in_progress: tasks.filter(g => g.status === 'in_progress').length,
|
|
199
|
+
completed: tasks.filter(g => g.status === 'completed').length,
|
|
200
|
+
by_category: {},
|
|
201
|
+
by_priority: {},
|
|
202
|
+
overdue: 0,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const now = new Date();
|
|
206
|
+
|
|
207
|
+
for (const task of tasks) {
|
|
208
|
+
// Count by category
|
|
209
|
+
const cat = task.category || 'general';
|
|
210
|
+
stats.by_category[cat] = (stats.by_category[cat] || 0) + 1;
|
|
211
|
+
|
|
212
|
+
// Count by priority
|
|
213
|
+
const pri = task.priority || 'medium';
|
|
214
|
+
stats.by_priority[pri] = (stats.by_priority[pri] || 0) + 1;
|
|
215
|
+
|
|
216
|
+
// Count overdue
|
|
217
|
+
if (task.deadline && new Date(task.deadline) < now && task.status !== 'completed') {
|
|
218
|
+
stats.overdue++;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return stats;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Calculate progress percentage from task
|
|
227
|
+
* @private
|
|
228
|
+
*/
|
|
229
|
+
_calculateProgress(task) {
|
|
230
|
+
return this._calculateProgressFromSteps(task.steps || []);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Calculate progress percentage from steps array
|
|
235
|
+
* @private
|
|
236
|
+
*/
|
|
237
|
+
_calculateProgressFromSteps(steps) {
|
|
238
|
+
if (!steps || steps.length === 0) return 0;
|
|
239
|
+
const doneCount = steps.filter(s => s.done).length;
|
|
240
|
+
return Math.round((doneCount / steps.length) * 100);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { TriggerStore } from './TriggerStore.js';
|
|
2
|
+
|
|
3
|
+
// Lazy-load mongodb to avoid hard dependency at module evaluation time
|
|
4
|
+
let _ObjectId = null;
|
|
5
|
+
async function getObjectId() {
|
|
6
|
+
if (!_ObjectId) { _ObjectId = (await import('mongodb')).ObjectId; }
|
|
7
|
+
return _ObjectId;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* MongoDB implementation of TriggerStore
|
|
12
|
+
*/
|
|
13
|
+
export class MongoTriggerStore extends TriggerStore {
|
|
14
|
+
constructor() {
|
|
15
|
+
super();
|
|
16
|
+
this.collection = null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Initialize MongoDB trigger store
|
|
21
|
+
*
|
|
22
|
+
* @param {import('mongodb').Db} db - MongoDB database instance
|
|
23
|
+
* @param {Object} options - Optional configuration
|
|
24
|
+
*/
|
|
25
|
+
async init(db, options = {}) {
|
|
26
|
+
this.collection = db.collection('triggers');
|
|
27
|
+
|
|
28
|
+
// Create indexes
|
|
29
|
+
await this.collection.createIndex({ userId: 1, eventType: 1 }).catch(() => {});
|
|
30
|
+
await this.collection.createIndex({ userId: 1, enabled: 1 }).catch(() => {});
|
|
31
|
+
|
|
32
|
+
console.log('[triggers] MongoTriggerStore initialized');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create an event trigger
|
|
37
|
+
*/
|
|
38
|
+
async createTrigger({ userId, eventType, prompt, cooldownMs = 0, metadata = {}, enabled = true }) {
|
|
39
|
+
const doc = {
|
|
40
|
+
userId,
|
|
41
|
+
eventType,
|
|
42
|
+
prompt,
|
|
43
|
+
cooldownMs,
|
|
44
|
+
metadata,
|
|
45
|
+
enabled,
|
|
46
|
+
lastFiredAt: null,
|
|
47
|
+
fireCount: 0,
|
|
48
|
+
createdAt: new Date(),
|
|
49
|
+
updatedAt: new Date(),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const result = await this.collection.insertOne(doc);
|
|
53
|
+
return { ...doc, _id: result.insertedId };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* List triggers for a user
|
|
58
|
+
*/
|
|
59
|
+
async listTriggers(userId, filters = {}) {
|
|
60
|
+
const query = { userId };
|
|
61
|
+
|
|
62
|
+
if (filters.enabled !== undefined) {
|
|
63
|
+
query.enabled = filters.enabled;
|
|
64
|
+
}
|
|
65
|
+
if (filters.eventType) {
|
|
66
|
+
query.eventType = filters.eventType;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return await this.collection
|
|
70
|
+
.find(query)
|
|
71
|
+
.sort({ createdAt: -1 })
|
|
72
|
+
.toArray();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Find enabled triggers matching userId and eventType, filtering out
|
|
77
|
+
* those still within cooldown period
|
|
78
|
+
*/
|
|
79
|
+
async findMatchingTriggers(userId, eventType, metadata = {}) {
|
|
80
|
+
const now = Date.now();
|
|
81
|
+
|
|
82
|
+
// Find all enabled triggers for this user and event type
|
|
83
|
+
const triggers = await this.collection
|
|
84
|
+
.find({ userId, eventType, enabled: true })
|
|
85
|
+
.toArray();
|
|
86
|
+
|
|
87
|
+
// Filter by cooldown
|
|
88
|
+
const activeTriggers = triggers.filter(trigger => {
|
|
89
|
+
// No cooldown → always active
|
|
90
|
+
if (!trigger.cooldownMs) return true;
|
|
91
|
+
|
|
92
|
+
// Never fired → active
|
|
93
|
+
if (!trigger.lastFiredAt) return true;
|
|
94
|
+
|
|
95
|
+
// Check if cooldown period has passed
|
|
96
|
+
const lastFiredTime = new Date(trigger.lastFiredAt).getTime();
|
|
97
|
+
return (lastFiredTime + trigger.cooldownMs) <= now;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Filter by metadata if trigger has metadata requirements
|
|
101
|
+
const matchedTriggers = activeTriggers.filter(trigger => {
|
|
102
|
+
if (!trigger.metadata || Object.keys(trigger.metadata).length === 0) {
|
|
103
|
+
return true; // No metadata requirements
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check if event metadata matches trigger metadata
|
|
107
|
+
for (const [key, value] of Object.entries(trigger.metadata)) {
|
|
108
|
+
if (metadata[key] !== value) return false;
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return matchedTriggers;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Toggle a trigger on/off
|
|
118
|
+
*/
|
|
119
|
+
async toggleTrigger(userId, triggerId, enabled) {
|
|
120
|
+
const ObjectId = await getObjectId();
|
|
121
|
+
const result = await this.collection.updateOne(
|
|
122
|
+
{ _id: new ObjectId(triggerId), userId },
|
|
123
|
+
{
|
|
124
|
+
$set: {
|
|
125
|
+
enabled,
|
|
126
|
+
updatedAt: new Date()
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Delete a trigger
|
|
135
|
+
*/
|
|
136
|
+
async deleteTrigger(userId, triggerId) {
|
|
137
|
+
const ObjectId = await getObjectId();
|
|
138
|
+
const result = await this.collection.deleteOne({
|
|
139
|
+
_id: new ObjectId(triggerId),
|
|
140
|
+
userId,
|
|
141
|
+
});
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Record that a trigger has fired
|
|
147
|
+
*/
|
|
148
|
+
async markTriggerFired(triggerId) {
|
|
149
|
+
const ObjectId = await getObjectId();
|
|
150
|
+
await this.collection.updateOne(
|
|
151
|
+
{ _id: new ObjectId(triggerId) },
|
|
152
|
+
{
|
|
153
|
+
$set: { lastFiredAt: new Date() },
|
|
154
|
+
$inc: { fireCount: 1 }
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|