@siftd/connect-agent 0.1.0 → 0.2.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/README.md +106 -0
- package/dist/agent.js +97 -12
- package/dist/cli.js +18 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +12 -0
- 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/orchestrator.d.ts +85 -0
- package/dist/orchestrator.js +532 -0
- package/package.json +12 -4
|
@@ -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,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Master Orchestrator Agent
|
|
3
|
+
*
|
|
4
|
+
* This is the BRAIN - it orchestrates, remembers, and delegates.
|
|
5
|
+
* It does NOT do the work itself. Claude Code CLI workers do the work.
|
|
6
|
+
*/
|
|
7
|
+
import type { MessageParam } from '@anthropic-ai/sdk/resources/messages';
|
|
8
|
+
export type MessageSender = (message: string) => Promise<void>;
|
|
9
|
+
export declare class MasterOrchestrator {
|
|
10
|
+
private client;
|
|
11
|
+
private model;
|
|
12
|
+
private maxTokens;
|
|
13
|
+
private memory;
|
|
14
|
+
private scheduler;
|
|
15
|
+
private jobs;
|
|
16
|
+
private jobCounter;
|
|
17
|
+
private userId;
|
|
18
|
+
private workspaceDir;
|
|
19
|
+
constructor(options: {
|
|
20
|
+
apiKey: string;
|
|
21
|
+
userId: string;
|
|
22
|
+
workspaceDir?: string;
|
|
23
|
+
model?: string;
|
|
24
|
+
maxTokens?: number;
|
|
25
|
+
voyageApiKey?: string;
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* Process a user message
|
|
29
|
+
*/
|
|
30
|
+
processMessage(message: string, conversationHistory?: MessageParam[], sendMessage?: MessageSender): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Get relevant memory context for a message
|
|
33
|
+
*/
|
|
34
|
+
private getMemoryContext;
|
|
35
|
+
/**
|
|
36
|
+
* Auto-remember important information from conversations
|
|
37
|
+
*/
|
|
38
|
+
private autoRemember;
|
|
39
|
+
/**
|
|
40
|
+
* Run the agentic loop
|
|
41
|
+
*/
|
|
42
|
+
private runAgentLoop;
|
|
43
|
+
/**
|
|
44
|
+
* Get tool definitions for the orchestrator
|
|
45
|
+
*/
|
|
46
|
+
private getToolDefinitions;
|
|
47
|
+
/**
|
|
48
|
+
* Process tool calls
|
|
49
|
+
*/
|
|
50
|
+
private processToolCalls;
|
|
51
|
+
/**
|
|
52
|
+
* Delegate task to Claude Code CLI worker
|
|
53
|
+
*/
|
|
54
|
+
private delegateToWorker;
|
|
55
|
+
/**
|
|
56
|
+
* Execute remember tool
|
|
57
|
+
*/
|
|
58
|
+
private executeRemember;
|
|
59
|
+
/**
|
|
60
|
+
* Execute search memory tool
|
|
61
|
+
*/
|
|
62
|
+
private executeSearchMemory;
|
|
63
|
+
/**
|
|
64
|
+
* Execute schedule task tool
|
|
65
|
+
*/
|
|
66
|
+
private executeScheduleTask;
|
|
67
|
+
/**
|
|
68
|
+
* Execute list scheduled tool
|
|
69
|
+
*/
|
|
70
|
+
private executeListScheduled;
|
|
71
|
+
/**
|
|
72
|
+
* Execute memory stats tool
|
|
73
|
+
*/
|
|
74
|
+
private executeMemoryStats;
|
|
75
|
+
/**
|
|
76
|
+
* Format tool preview for user
|
|
77
|
+
*/
|
|
78
|
+
private formatToolPreview;
|
|
79
|
+
private hasToolUse;
|
|
80
|
+
private extractText;
|
|
81
|
+
/**
|
|
82
|
+
* Shutdown cleanly
|
|
83
|
+
*/
|
|
84
|
+
shutdown(): void;
|
|
85
|
+
}
|