@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.
@@ -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
+ }