@siftd/connect-agent 0.1.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -0
- package/dist/agent.js +202 -23
- package/dist/api.d.ts +1 -0
- package/dist/cli.js +21 -2
- package/dist/config.d.ts +14 -0
- package/dist/config.js +36 -3
- package/dist/core/memory-advanced.d.ts +91 -0
- package/dist/core/memory-advanced.js +571 -0
- package/dist/core/scheduler.d.ts +101 -0
- package/dist/core/scheduler.js +311 -0
- package/dist/core/system-indexer.d.ts +65 -0
- package/dist/core/system-indexer.js +354 -0
- package/dist/genesis/index.d.ts +56 -0
- package/dist/genesis/index.js +71 -0
- package/dist/genesis/system-knowledge.json +62 -0
- package/dist/genesis/tool-patterns.json +88 -0
- package/dist/heartbeat.d.ts +32 -0
- package/dist/heartbeat.js +166 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/orchestrator.d.ts +122 -0
- package/dist/orchestrator.js +1001 -0
- package/dist/tools/bash.d.ts +18 -0
- package/dist/tools/bash.js +85 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.js +7 -0
- package/dist/tools/web.d.ts +38 -0
- package/dist/tools/web.js +284 -0
- package/dist/tools/worker.d.ts +36 -0
- package/dist/tools/worker.js +149 -0
- package/dist/websocket.d.ts +73 -0
- package/dist/websocket.js +240 -0
- package/dist/workers/manager.d.ts +62 -0
- package/dist/workers/manager.js +270 -0
- package/dist/workers/types.d.ts +31 -0
- package/dist/workers/types.js +10 -0
- package/package.json +15 -5
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Scheduler
|
|
3
|
+
* Schedule tasks for future execution with notifications
|
|
4
|
+
*/
|
|
5
|
+
import * as cron from 'node-cron';
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
export class TaskScheduler {
|
|
8
|
+
tasks = new Map();
|
|
9
|
+
activeTimers = new Map();
|
|
10
|
+
notifyCallback;
|
|
11
|
+
taskExecutor;
|
|
12
|
+
dataPath;
|
|
13
|
+
constructor(dataPath = 'scheduled_tasks.json') {
|
|
14
|
+
this.dataPath = dataPath;
|
|
15
|
+
this.loadTasks();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Set the notification callback for sending messages
|
|
19
|
+
*/
|
|
20
|
+
setNotifyCallback(callback) {
|
|
21
|
+
this.notifyCallback = callback;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Set the task executor callback
|
|
25
|
+
*/
|
|
26
|
+
setTaskExecutor(executor) {
|
|
27
|
+
this.taskExecutor = executor;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parse natural language schedule
|
|
31
|
+
*/
|
|
32
|
+
parseSchedule(when) {
|
|
33
|
+
const lower = when.toLowerCase().trim();
|
|
34
|
+
// "in X minutes/hours/days"
|
|
35
|
+
const delayMatch = lower.match(/^in\s+(\d+)\s+(minute|hour|day|second)s?$/);
|
|
36
|
+
if (delayMatch) {
|
|
37
|
+
const amount = parseInt(delayMatch[1]);
|
|
38
|
+
const unit = delayMatch[2];
|
|
39
|
+
const multipliers = {
|
|
40
|
+
second: 1000,
|
|
41
|
+
minute: 60000,
|
|
42
|
+
hour: 3600000,
|
|
43
|
+
day: 86400000
|
|
44
|
+
};
|
|
45
|
+
return { type: 'delay', ms: amount * multipliers[unit] };
|
|
46
|
+
}
|
|
47
|
+
// "every X minutes/hours"
|
|
48
|
+
const intervalMatch = lower.match(/^every\s+(\d+)\s+(minute|hour|second)s?$/);
|
|
49
|
+
if (intervalMatch) {
|
|
50
|
+
const amount = parseInt(intervalMatch[1]);
|
|
51
|
+
const unit = intervalMatch[2];
|
|
52
|
+
const multipliers = {
|
|
53
|
+
second: 1000,
|
|
54
|
+
minute: 60000,
|
|
55
|
+
hour: 3600000
|
|
56
|
+
};
|
|
57
|
+
return { type: 'interval', ms: amount * multipliers[unit] };
|
|
58
|
+
}
|
|
59
|
+
// "daily at HH:MM"
|
|
60
|
+
const dailyMatch = lower.match(/^daily\s+at\s+(\d{1,2}):(\d{2})$/);
|
|
61
|
+
if (dailyMatch) {
|
|
62
|
+
const hour = parseInt(dailyMatch[1]);
|
|
63
|
+
const minute = parseInt(dailyMatch[2]);
|
|
64
|
+
return { type: 'cron', pattern: `${minute} ${hour} * * *` };
|
|
65
|
+
}
|
|
66
|
+
// "every day at HH:MM"
|
|
67
|
+
const everyDayMatch = lower.match(/^every\s+day\s+at\s+(\d{1,2}):(\d{2})$/);
|
|
68
|
+
if (everyDayMatch) {
|
|
69
|
+
const hour = parseInt(everyDayMatch[1]);
|
|
70
|
+
const minute = parseInt(everyDayMatch[2]);
|
|
71
|
+
return { type: 'cron', pattern: `${minute} ${hour} * * *` };
|
|
72
|
+
}
|
|
73
|
+
// "tomorrow at HH:MM"
|
|
74
|
+
const tomorrowMatch = lower.match(/^tomorrow\s+at\s+(\d{1,2}):(\d{2})$/);
|
|
75
|
+
if (tomorrowMatch) {
|
|
76
|
+
const hour = parseInt(tomorrowMatch[1]);
|
|
77
|
+
const minute = parseInt(tomorrowMatch[2]);
|
|
78
|
+
const tomorrow = new Date();
|
|
79
|
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
80
|
+
tomorrow.setHours(hour, minute, 0, 0);
|
|
81
|
+
return { type: 'once', at: tomorrow.toISOString() };
|
|
82
|
+
}
|
|
83
|
+
// Raw cron pattern (5 parts)
|
|
84
|
+
if (/^\S+\s+\S+\s+\S+\s+\S+\s+\S+$/.test(lower)) {
|
|
85
|
+
if (cron.validate(lower)) {
|
|
86
|
+
return { type: 'cron', pattern: lower };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Schedule a task
|
|
93
|
+
*/
|
|
94
|
+
schedule(task, when, chatId) {
|
|
95
|
+
const schedule = typeof when === 'string' ? this.parseSchedule(when) : when;
|
|
96
|
+
if (!schedule) {
|
|
97
|
+
throw new Error(`Could not parse schedule: "${when}". Try formats like "in 30 minutes", "daily at 9:00", or "every 2 hours".`);
|
|
98
|
+
}
|
|
99
|
+
const id = `task_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
|
100
|
+
const now = new Date();
|
|
101
|
+
const scheduledTask = {
|
|
102
|
+
id,
|
|
103
|
+
task,
|
|
104
|
+
schedule,
|
|
105
|
+
chatId,
|
|
106
|
+
enabled: true,
|
|
107
|
+
created: now.toISOString(),
|
|
108
|
+
runCount: 0,
|
|
109
|
+
nextRun: this.calculateNextRun(schedule)
|
|
110
|
+
};
|
|
111
|
+
this.tasks.set(id, scheduledTask);
|
|
112
|
+
this.activateTask(scheduledTask);
|
|
113
|
+
this.saveTasks();
|
|
114
|
+
return id;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Calculate next run time
|
|
118
|
+
*/
|
|
119
|
+
calculateNextRun(schedule) {
|
|
120
|
+
const now = new Date();
|
|
121
|
+
switch (schedule.type) {
|
|
122
|
+
case 'once':
|
|
123
|
+
return schedule.at;
|
|
124
|
+
case 'delay':
|
|
125
|
+
return new Date(now.getTime() + schedule.ms).toISOString();
|
|
126
|
+
case 'interval':
|
|
127
|
+
return new Date(now.getTime() + schedule.ms).toISOString();
|
|
128
|
+
case 'cron':
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Activate a task's timer
|
|
134
|
+
*/
|
|
135
|
+
activateTask(task) {
|
|
136
|
+
this.deactivateTask(task.id);
|
|
137
|
+
if (!task.enabled)
|
|
138
|
+
return;
|
|
139
|
+
const executeAndNotify = async () => {
|
|
140
|
+
task.lastRun = new Date().toISOString();
|
|
141
|
+
task.runCount++;
|
|
142
|
+
let result = `Scheduled task "${task.task}" executed.`;
|
|
143
|
+
if (this.taskExecutor) {
|
|
144
|
+
try {
|
|
145
|
+
result = await this.taskExecutor(task.task, task.chatId);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
result = `Task failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (this.notifyCallback) {
|
|
152
|
+
try {
|
|
153
|
+
await this.notifyCallback(task.chatId, `Scheduled task completed:\n\n${result}`);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
console.error('Failed to send notification:', error);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// For one-time tasks, disable after execution
|
|
160
|
+
if (task.schedule.type === 'once' || task.schedule.type === 'delay') {
|
|
161
|
+
task.enabled = false;
|
|
162
|
+
this.deactivateTask(task.id);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
task.nextRun = this.calculateNextRun(task.schedule);
|
|
166
|
+
}
|
|
167
|
+
this.saveTasks();
|
|
168
|
+
};
|
|
169
|
+
switch (task.schedule.type) {
|
|
170
|
+
case 'once': {
|
|
171
|
+
const runAt = new Date(task.schedule.at).getTime();
|
|
172
|
+
const delay = Math.max(0, runAt - Date.now());
|
|
173
|
+
const handle = setTimeout(executeAndNotify, delay);
|
|
174
|
+
this.activeTimers.set(task.id, { type: 'timeout', handle });
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case 'delay': {
|
|
178
|
+
const handle = setTimeout(executeAndNotify, task.schedule.ms);
|
|
179
|
+
this.activeTimers.set(task.id, { type: 'timeout', handle });
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
case 'interval': {
|
|
183
|
+
const handle = setInterval(executeAndNotify, task.schedule.ms);
|
|
184
|
+
this.activeTimers.set(task.id, { type: 'interval', handle });
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case 'cron': {
|
|
188
|
+
const handle = cron.schedule(task.schedule.pattern, executeAndNotify);
|
|
189
|
+
this.activeTimers.set(task.id, { type: 'cron', handle });
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Deactivate a task's timer
|
|
196
|
+
*/
|
|
197
|
+
deactivateTask(taskId) {
|
|
198
|
+
const timer = this.activeTimers.get(taskId);
|
|
199
|
+
if (!timer)
|
|
200
|
+
return;
|
|
201
|
+
switch (timer.type) {
|
|
202
|
+
case 'timeout':
|
|
203
|
+
case 'interval':
|
|
204
|
+
clearTimeout(timer.handle);
|
|
205
|
+
break;
|
|
206
|
+
case 'cron':
|
|
207
|
+
timer.handle.stop();
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
this.activeTimers.delete(taskId);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Cancel a task
|
|
214
|
+
*/
|
|
215
|
+
cancel(taskId) {
|
|
216
|
+
const task = this.tasks.get(taskId);
|
|
217
|
+
if (!task)
|
|
218
|
+
return false;
|
|
219
|
+
this.deactivateTask(taskId);
|
|
220
|
+
this.tasks.delete(taskId);
|
|
221
|
+
this.saveTasks();
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Pause a task
|
|
226
|
+
*/
|
|
227
|
+
pause(taskId) {
|
|
228
|
+
const task = this.tasks.get(taskId);
|
|
229
|
+
if (!task)
|
|
230
|
+
return false;
|
|
231
|
+
task.enabled = false;
|
|
232
|
+
this.deactivateTask(taskId);
|
|
233
|
+
this.saveTasks();
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Resume a task
|
|
238
|
+
*/
|
|
239
|
+
resume(taskId) {
|
|
240
|
+
const task = this.tasks.get(taskId);
|
|
241
|
+
if (!task)
|
|
242
|
+
return false;
|
|
243
|
+
task.enabled = true;
|
|
244
|
+
this.activateTask(task);
|
|
245
|
+
this.saveTasks();
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* List all tasks
|
|
250
|
+
*/
|
|
251
|
+
list() {
|
|
252
|
+
return Array.from(this.tasks.values()).sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime());
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Get a specific task
|
|
256
|
+
*/
|
|
257
|
+
get(taskId) {
|
|
258
|
+
return this.tasks.get(taskId);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Format schedule for display
|
|
262
|
+
*/
|
|
263
|
+
formatSchedule(schedule) {
|
|
264
|
+
switch (schedule.type) {
|
|
265
|
+
case 'once':
|
|
266
|
+
return `once at ${new Date(schedule.at).toLocaleString()}`;
|
|
267
|
+
case 'delay':
|
|
268
|
+
const minutes = Math.round(schedule.ms / 60000);
|
|
269
|
+
return `in ${minutes} minute${minutes !== 1 ? 's' : ''}`;
|
|
270
|
+
case 'interval':
|
|
271
|
+
const intervalMins = Math.round(schedule.ms / 60000);
|
|
272
|
+
return `every ${intervalMins} minute${intervalMins !== 1 ? 's' : ''}`;
|
|
273
|
+
case 'cron':
|
|
274
|
+
return `cron: ${schedule.pattern}`;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Save tasks to disk
|
|
279
|
+
*/
|
|
280
|
+
saveTasks() {
|
|
281
|
+
const data = Array.from(this.tasks.values());
|
|
282
|
+
fs.writeFileSync(this.dataPath, JSON.stringify(data, null, 2));
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Load tasks from disk
|
|
286
|
+
*/
|
|
287
|
+
loadTasks() {
|
|
288
|
+
if (!fs.existsSync(this.dataPath))
|
|
289
|
+
return;
|
|
290
|
+
try {
|
|
291
|
+
const data = JSON.parse(fs.readFileSync(this.dataPath, 'utf8'));
|
|
292
|
+
for (const task of data) {
|
|
293
|
+
this.tasks.set(task.id, task);
|
|
294
|
+
if (task.enabled) {
|
|
295
|
+
this.activateTask(task);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
console.error('Failed to load scheduled tasks:', error);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Stop all timers (for shutdown)
|
|
305
|
+
*/
|
|
306
|
+
shutdown() {
|
|
307
|
+
for (const taskId of this.activeTimers.keys()) {
|
|
308
|
+
this.deactivateTask(taskId);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Indexer
|
|
3
|
+
*
|
|
4
|
+
* Indexes the user's filesystem INTO the memory system.
|
|
5
|
+
* When user says "that project" or "save in downloads",
|
|
6
|
+
* semantic search finds the relevant paths.
|
|
7
|
+
*
|
|
8
|
+
* No MCP. No new protocols. Just smart memory use.
|
|
9
|
+
*/
|
|
10
|
+
import { AdvancedMemoryStore } from './memory-advanced.js';
|
|
11
|
+
export declare class SystemIndexer {
|
|
12
|
+
private memory;
|
|
13
|
+
private home;
|
|
14
|
+
private indexed;
|
|
15
|
+
constructor(memory: AdvancedMemoryStore);
|
|
16
|
+
/**
|
|
17
|
+
* Index the user's home directory structure into memory
|
|
18
|
+
*/
|
|
19
|
+
indexHome(): Promise<{
|
|
20
|
+
projects: number;
|
|
21
|
+
directories: number;
|
|
22
|
+
total: number;
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* Index a directory and its subdirectories
|
|
26
|
+
*/
|
|
27
|
+
private indexDirectory;
|
|
28
|
+
/**
|
|
29
|
+
* Find and index git repositories as projects
|
|
30
|
+
*/
|
|
31
|
+
private indexGitRepos;
|
|
32
|
+
/**
|
|
33
|
+
* Get detailed info about a git project
|
|
34
|
+
*/
|
|
35
|
+
private getProjectInfo;
|
|
36
|
+
/**
|
|
37
|
+
* Detect project type by looking at files
|
|
38
|
+
*/
|
|
39
|
+
private detectProjectType;
|
|
40
|
+
/**
|
|
41
|
+
* Remember a key directory (Downloads, Documents, Desktop, etc.)
|
|
42
|
+
* These get high importance and multiple aliases for better search
|
|
43
|
+
*/
|
|
44
|
+
private rememberKeyDirectory;
|
|
45
|
+
/**
|
|
46
|
+
* Store a project in memory
|
|
47
|
+
*/
|
|
48
|
+
private rememberProject;
|
|
49
|
+
/**
|
|
50
|
+
* Generate a description for a directory
|
|
51
|
+
*/
|
|
52
|
+
private describeDirectory;
|
|
53
|
+
/**
|
|
54
|
+
* Extract relevant tags from directory contents
|
|
55
|
+
*/
|
|
56
|
+
private extractTags;
|
|
57
|
+
/**
|
|
58
|
+
* Re-index (clear old filesystem memories and re-scan)
|
|
59
|
+
*/
|
|
60
|
+
reindex(): Promise<{
|
|
61
|
+
projects: number;
|
|
62
|
+
directories: number;
|
|
63
|
+
total: number;
|
|
64
|
+
}>;
|
|
65
|
+
}
|