@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.
@@ -0,0 +1,571 @@
1
+ /**
2
+ * Advanced Memory System
3
+ *
4
+ * Features:
5
+ * - Hierarchical memory types (episodic, semantic, procedural)
6
+ * - Vector embeddings for semantic search
7
+ * - Importance scoring with temporal decay
8
+ * - Memory associations/links
9
+ * - Reflection and consolidation
10
+ */
11
+ import Database from 'better-sqlite3';
12
+ import { LocalIndex } from 'vectra';
13
+ import * as fs from 'fs';
14
+ // Simple local embedding using character n-grams (no API needed)
15
+ class LocalEmbedding {
16
+ dimensions = 384;
17
+ async embed(text) {
18
+ return this.computeEmbedding(text);
19
+ }
20
+ async embedBatch(texts) {
21
+ return texts.map(t => this.computeEmbedding(t));
22
+ }
23
+ computeEmbedding(text) {
24
+ const embedding = new Array(this.dimensions).fill(0);
25
+ const normalized = text.toLowerCase();
26
+ // Character trigrams
27
+ for (let i = 0; i < normalized.length - 2; i++) {
28
+ const trigram = normalized.slice(i, i + 3);
29
+ const hash = this.hashString(trigram);
30
+ const idx = Math.abs(hash) % this.dimensions;
31
+ embedding[idx] += 1;
32
+ }
33
+ // Word unigrams
34
+ const words = normalized.split(/\s+/);
35
+ for (const word of words) {
36
+ const hash = this.hashString(word);
37
+ const idx = Math.abs(hash) % this.dimensions;
38
+ embedding[idx] += 2;
39
+ }
40
+ // Normalize
41
+ const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0));
42
+ if (magnitude > 0) {
43
+ for (let i = 0; i < embedding.length; i++) {
44
+ embedding[i] /= magnitude;
45
+ }
46
+ }
47
+ return embedding;
48
+ }
49
+ hashString(str) {
50
+ let hash = 5381;
51
+ for (let i = 0; i < str.length; i++) {
52
+ hash = ((hash << 5) + hash) + str.charCodeAt(i);
53
+ }
54
+ return hash;
55
+ }
56
+ }
57
+ // Voyage AI embedding service
58
+ class VoyageEmbedding {
59
+ apiKey;
60
+ model = 'voyage-2';
61
+ constructor(apiKey) {
62
+ this.apiKey = apiKey;
63
+ }
64
+ async embed(text) {
65
+ const results = await this.embedBatch([text]);
66
+ return results[0];
67
+ }
68
+ async embedBatch(texts) {
69
+ const response = await fetch('https://api.voyageai.com/v1/embeddings', {
70
+ method: 'POST',
71
+ headers: {
72
+ 'Content-Type': 'application/json',
73
+ 'Authorization': `Bearer ${this.apiKey}`
74
+ },
75
+ body: JSON.stringify({
76
+ model: this.model,
77
+ input: texts
78
+ })
79
+ });
80
+ if (!response.ok) {
81
+ throw new Error(`Voyage API error: ${response.status}`);
82
+ }
83
+ const data = await response.json();
84
+ return data.data.map(d => d.embedding);
85
+ }
86
+ }
87
+ export class AdvancedMemoryStore {
88
+ db;
89
+ vectorIndex = null;
90
+ vectorPath;
91
+ embedder;
92
+ config;
93
+ constructor(options = {}) {
94
+ const dbPath = options.dbPath || 'memories_advanced.db';
95
+ this.vectorPath = options.vectorPath || './memory_vectors';
96
+ this.db = new Database(dbPath);
97
+ if (options.voyageApiKey) {
98
+ this.embedder = new VoyageEmbedding(options.voyageApiKey);
99
+ }
100
+ else {
101
+ this.embedder = new LocalEmbedding();
102
+ }
103
+ this.config = {
104
+ maxMemories: options.maxMemories || 10000,
105
+ decayInterval: 3600000,
106
+ consolidationInterval: 86400000,
107
+ minImportanceThreshold: 0.1
108
+ };
109
+ this.init();
110
+ }
111
+ async init() {
112
+ this.db.exec(`
113
+ CREATE TABLE IF NOT EXISTS memories (
114
+ id TEXT PRIMARY KEY,
115
+ type TEXT NOT NULL,
116
+ content TEXT NOT NULL,
117
+ summary TEXT,
118
+ source TEXT NOT NULL,
119
+ timestamp TEXT NOT NULL,
120
+ lastAccessed TEXT NOT NULL,
121
+ importance REAL NOT NULL DEFAULT 0.5,
122
+ accessCount INTEGER NOT NULL DEFAULT 0,
123
+ decayRate REAL NOT NULL DEFAULT 0.1,
124
+ associations TEXT NOT NULL DEFAULT '[]',
125
+ tags TEXT NOT NULL DEFAULT '[]',
126
+ hasEmbedding INTEGER NOT NULL DEFAULT 0
127
+ );
128
+
129
+ CREATE INDEX IF NOT EXISTS idx_memory_type ON memories(type);
130
+ CREATE INDEX IF NOT EXISTS idx_memory_importance ON memories(importance);
131
+ CREATE INDEX IF NOT EXISTS idx_memory_timestamp ON memories(timestamp);
132
+ CREATE INDEX IF NOT EXISTS idx_memory_tags ON memories(tags);
133
+
134
+ CREATE TABLE IF NOT EXISTS reflections (
135
+ id TEXT PRIMARY KEY,
136
+ timestamp TEXT NOT NULL,
137
+ insights TEXT NOT NULL,
138
+ memoriesProcessed INTEGER NOT NULL,
139
+ consolidations INTEGER NOT NULL
140
+ );
141
+ `);
142
+ await this.initVectorIndex();
143
+ }
144
+ async initVectorIndex() {
145
+ if (!fs.existsSync(this.vectorPath)) {
146
+ fs.mkdirSync(this.vectorPath, { recursive: true });
147
+ }
148
+ this.vectorIndex = new LocalIndex(this.vectorPath);
149
+ if (!await this.vectorIndex.isIndexCreated()) {
150
+ await this.vectorIndex.createIndex();
151
+ }
152
+ }
153
+ async remember(content, options = {}) {
154
+ const id = `mem_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
155
+ const now = new Date().toISOString();
156
+ const type = options.type || this.inferMemoryType(content);
157
+ const importance = options.importance ?? this.calculateInitialImportance(content, type);
158
+ const memory = {
159
+ id,
160
+ type,
161
+ content,
162
+ summary: options.summary,
163
+ source: options.source || 'user',
164
+ timestamp: now,
165
+ lastAccessed: now,
166
+ importance,
167
+ accessCount: 1,
168
+ decayRate: this.getDecayRateForType(type),
169
+ associations: options.associations || [],
170
+ tags: options.tags || this.extractTags(content),
171
+ hasEmbedding: false
172
+ };
173
+ const stmt = this.db.prepare(`
174
+ INSERT INTO memories (id, type, content, summary, source, timestamp, lastAccessed,
175
+ importance, accessCount, decayRate, associations, tags, hasEmbedding)
176
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
177
+ `);
178
+ stmt.run(memory.id, memory.type, memory.content, memory.summary || null, memory.source, memory.timestamp, memory.lastAccessed, memory.importance, memory.accessCount, memory.decayRate, JSON.stringify(memory.associations), JSON.stringify(memory.tags), 0);
179
+ await this.addEmbedding(id, content);
180
+ await this.createAssociations(id, content);
181
+ await this.pruneIfNeeded();
182
+ return id;
183
+ }
184
+ async search(query, options = {}) {
185
+ const limit = options.limit || 10;
186
+ if (!this.vectorIndex) {
187
+ return this.textSearch(query, options);
188
+ }
189
+ const queryEmbedding = await this.embedder.embed(query);
190
+ const results = await this.vectorIndex.queryItems(queryEmbedding, limit * 2);
191
+ const memories = [];
192
+ for (const result of results) {
193
+ const memory = this.getById(result.item.metadata.id);
194
+ if (memory) {
195
+ if (options.type && memory.type !== options.type)
196
+ continue;
197
+ if (options.minImportance && memory.importance < options.minImportance)
198
+ continue;
199
+ this.recordAccess(memory.id);
200
+ memories.push(memory);
201
+ if (memories.length >= limit)
202
+ break;
203
+ }
204
+ }
205
+ if (options.includeAssociations) {
206
+ const associated = new Set();
207
+ for (const mem of memories) {
208
+ for (const assocId of mem.associations) {
209
+ if (!memories.find(m => m.id === assocId)) {
210
+ associated.add(assocId);
211
+ }
212
+ }
213
+ }
214
+ for (const assocId of associated) {
215
+ const mem = this.getById(assocId);
216
+ if (mem)
217
+ memories.push(mem);
218
+ }
219
+ }
220
+ return memories;
221
+ }
222
+ textSearch(query, options) {
223
+ const limit = options.limit || 10;
224
+ const conditions = ['1=1'];
225
+ const params = [];
226
+ if (options.type) {
227
+ conditions.push('type = ?');
228
+ params.push(options.type);
229
+ }
230
+ if (options.minImportance) {
231
+ conditions.push('importance >= ?');
232
+ params.push(options.minImportance);
233
+ }
234
+ conditions.push('(content LIKE ? OR tags LIKE ?)');
235
+ params.push(`%${query}%`, `%${query}%`);
236
+ const stmt = this.db.prepare(`
237
+ SELECT * FROM memories
238
+ WHERE ${conditions.join(' AND ')}
239
+ ORDER BY importance DESC, accessCount DESC
240
+ LIMIT ?
241
+ `);
242
+ const rows = stmt.all(...params, limit);
243
+ return rows.map(row => this.rowToMemory(row));
244
+ }
245
+ getById(id) {
246
+ const stmt = this.db.prepare('SELECT * FROM memories WHERE id = ?');
247
+ const row = stmt.get(id);
248
+ return row ? this.rowToMemory(row) : null;
249
+ }
250
+ getByType(type, limit = 50) {
251
+ const stmt = this.db.prepare(`
252
+ SELECT * FROM memories
253
+ WHERE type = ?
254
+ ORDER BY importance DESC, lastAccessed DESC
255
+ LIMIT ?
256
+ `);
257
+ const rows = stmt.all(type, limit);
258
+ return rows.map(row => this.rowToMemory(row));
259
+ }
260
+ recall(id) {
261
+ const memory = this.getById(id);
262
+ if (memory) {
263
+ this.recordAccess(id);
264
+ return this.getById(id);
265
+ }
266
+ return null;
267
+ }
268
+ async forget(id) {
269
+ const stmt = this.db.prepare('DELETE FROM memories WHERE id = ?');
270
+ const result = stmt.run(id);
271
+ if (this.vectorIndex) {
272
+ try {
273
+ await this.vectorIndex.deleteItem(id);
274
+ }
275
+ catch {
276
+ // May not exist
277
+ }
278
+ }
279
+ this.removeAssociations(id);
280
+ return result.changes > 0;
281
+ }
282
+ updateImportance(id, importance) {
283
+ const stmt = this.db.prepare('UPDATE memories SET importance = ? WHERE id = ?');
284
+ stmt.run(Math.max(0, Math.min(1, importance)), id);
285
+ }
286
+ associate(id1, id2) {
287
+ const mem1 = this.getById(id1);
288
+ const mem2 = this.getById(id2);
289
+ if (mem1 && mem2) {
290
+ if (!mem1.associations.includes(id2)) {
291
+ mem1.associations.push(id2);
292
+ this.db.prepare('UPDATE memories SET associations = ? WHERE id = ?')
293
+ .run(JSON.stringify(mem1.associations), id1);
294
+ }
295
+ if (!mem2.associations.includes(id1)) {
296
+ mem2.associations.push(id1);
297
+ this.db.prepare('UPDATE memories SET associations = ? WHERE id = ?')
298
+ .run(JSON.stringify(mem2.associations), id2);
299
+ }
300
+ }
301
+ }
302
+ applyDecay() {
303
+ const now = Date.now();
304
+ const memories = this.list();
305
+ let decayed = 0;
306
+ for (const memory of memories) {
307
+ const age = now - new Date(memory.lastAccessed).getTime();
308
+ const hoursSinceAccess = age / 3600000;
309
+ const decayFactor = Math.exp(-memory.decayRate * hoursSinceAccess / 24);
310
+ const newImportance = memory.importance * decayFactor;
311
+ const accessBoost = Math.min(0.3, memory.accessCount * 0.02);
312
+ const finalImportance = Math.min(1, newImportance + accessBoost);
313
+ if (Math.abs(finalImportance - memory.importance) > 0.01) {
314
+ this.updateImportance(memory.id, finalImportance);
315
+ decayed++;
316
+ }
317
+ if (finalImportance < this.config.minImportanceThreshold && memory.type !== 'procedural') {
318
+ this.forget(memory.id);
319
+ }
320
+ }
321
+ return decayed;
322
+ }
323
+ async reflect() {
324
+ const recentMemories = this.getRecent(50);
325
+ const insights = [];
326
+ let consolidations = 0;
327
+ const tagGroups = new Map();
328
+ for (const mem of recentMemories) {
329
+ for (const tag of mem.tags) {
330
+ if (!tagGroups.has(tag)) {
331
+ tagGroups.set(tag, []);
332
+ }
333
+ tagGroups.get(tag).push(mem);
334
+ }
335
+ }
336
+ for (const [tag, mems] of tagGroups) {
337
+ if (mems.length >= 3) {
338
+ insights.push(`Recurring theme: "${tag}" appears in ${mems.length} recent memories`);
339
+ if (mems.length >= 5) {
340
+ const summary = this.consolidateMemories(mems);
341
+ if (summary) {
342
+ await this.remember(summary, {
343
+ type: 'semantic',
344
+ source: 'reflection',
345
+ importance: 0.8,
346
+ tags: [tag, 'consolidated']
347
+ });
348
+ consolidations++;
349
+ }
350
+ }
351
+ }
352
+ }
353
+ for (const mem of recentMemories) {
354
+ if (mem.associations.length === 0 && mem.importance > 0.6) {
355
+ insights.push(`Important isolated memory: "${mem.content.slice(0, 50)}..." - consider linking`);
356
+ }
357
+ }
358
+ const reflectionId = `ref_${Date.now()}`;
359
+ this.db.prepare(`
360
+ INSERT INTO reflections (id, timestamp, insights, memoriesProcessed, consolidations)
361
+ VALUES (?, ?, ?, ?, ?)
362
+ `).run(reflectionId, new Date().toISOString(), JSON.stringify(insights), recentMemories.length, consolidations);
363
+ return {
364
+ insights,
365
+ consolidations,
366
+ memoriesProcessed: recentMemories.length
367
+ };
368
+ }
369
+ stats() {
370
+ const total = this.db.prepare('SELECT COUNT(*) as count FROM memories').get();
371
+ const byType = {
372
+ episodic: 0,
373
+ semantic: 0,
374
+ procedural: 0,
375
+ working: 0
376
+ };
377
+ const types = this.db.prepare('SELECT type, COUNT(*) as count FROM memories GROUP BY type').all();
378
+ for (const t of types) {
379
+ byType[t.type] = t.count;
380
+ }
381
+ const avgImportance = this.db.prepare('SELECT AVG(importance) as avg FROM memories').get();
382
+ const memories = this.list();
383
+ let totalAssociations = 0;
384
+ for (const m of memories) {
385
+ totalAssociations += m.associations.length;
386
+ }
387
+ const oldest = this.db.prepare('SELECT timestamp FROM memories ORDER BY timestamp ASC LIMIT 1').get();
388
+ const newest = this.db.prepare('SELECT timestamp FROM memories ORDER BY timestamp DESC LIMIT 1').get();
389
+ return {
390
+ total: total.count,
391
+ byType,
392
+ avgImportance: avgImportance.avg || 0,
393
+ totalAssociations: totalAssociations / 2,
394
+ oldestMemory: oldest?.timestamp || null,
395
+ newestMemory: newest?.timestamp || null
396
+ };
397
+ }
398
+ list(limit = 100) {
399
+ const stmt = this.db.prepare(`
400
+ SELECT * FROM memories
401
+ ORDER BY importance DESC, lastAccessed DESC
402
+ LIMIT ?
403
+ `);
404
+ const rows = stmt.all(limit);
405
+ return rows.map(row => this.rowToMemory(row));
406
+ }
407
+ getRecent(limit = 20) {
408
+ const stmt = this.db.prepare(`
409
+ SELECT * FROM memories
410
+ ORDER BY timestamp DESC
411
+ LIMIT ?
412
+ `);
413
+ const rows = stmt.all(limit);
414
+ return rows.map(row => this.rowToMemory(row));
415
+ }
416
+ async addEmbedding(id, content) {
417
+ if (!this.vectorIndex)
418
+ return;
419
+ try {
420
+ const embedding = await this.embedder.embed(content);
421
+ await this.vectorIndex.insertItem({
422
+ vector: embedding,
423
+ metadata: { id }
424
+ });
425
+ this.db.prepare('UPDATE memories SET hasEmbedding = 1 WHERE id = ?').run(id);
426
+ }
427
+ catch (error) {
428
+ console.error('Failed to create embedding:', error);
429
+ }
430
+ }
431
+ async createAssociations(id, content) {
432
+ const similar = await this.search(content, { limit: 5 });
433
+ for (const mem of similar) {
434
+ if (mem.id !== id) {
435
+ this.associate(id, mem.id);
436
+ }
437
+ }
438
+ }
439
+ removeAssociations(id) {
440
+ const stmt = this.db.prepare('SELECT id, associations FROM memories WHERE associations LIKE ?');
441
+ const rows = stmt.all(`%${id}%`);
442
+ for (const row of rows) {
443
+ const associations = JSON.parse(row.associations);
444
+ const filtered = associations.filter(a => a !== id);
445
+ this.db.prepare('UPDATE memories SET associations = ? WHERE id = ?')
446
+ .run(JSON.stringify(filtered), row.id);
447
+ }
448
+ }
449
+ recordAccess(id) {
450
+ const stmt = this.db.prepare(`
451
+ UPDATE memories
452
+ SET lastAccessed = ?, accessCount = accessCount + 1
453
+ WHERE id = ?
454
+ `);
455
+ stmt.run(new Date().toISOString(), id);
456
+ }
457
+ inferMemoryType(content) {
458
+ const lower = content.toLowerCase();
459
+ if (lower.includes('to ') && (lower.includes('run ') || lower.includes('use ') || lower.includes('do '))) {
460
+ return 'procedural';
461
+ }
462
+ if (lower.includes('step ') || lower.includes('first ') || lower.includes('then ')) {
463
+ return 'procedural';
464
+ }
465
+ if (lower.match(/\b(today|yesterday|on \w+day|last \w+|this morning)\b/)) {
466
+ return 'episodic';
467
+ }
468
+ if (lower.match(/\b(happened|did|said|asked|told)\b/)) {
469
+ return 'episodic';
470
+ }
471
+ return 'semantic';
472
+ }
473
+ calculateInitialImportance(content, type) {
474
+ let importance = 0.5;
475
+ if (type === 'procedural')
476
+ importance += 0.2;
477
+ if (type === 'semantic')
478
+ importance += 0.1;
479
+ const lower = content.toLowerCase();
480
+ if (lower.includes('important') || lower.includes('remember'))
481
+ importance += 0.15;
482
+ if (lower.includes('always') || lower.includes('never'))
483
+ importance += 0.1;
484
+ if (lower.includes('password') || lower.includes('key') || lower.includes('secret'))
485
+ importance += 0.2;
486
+ if (lower.includes('prefer') || lower.includes('like') || lower.includes('favorite'))
487
+ importance += 0.1;
488
+ return Math.min(1, importance);
489
+ }
490
+ getDecayRateForType(type) {
491
+ switch (type) {
492
+ case 'procedural': return 0.02;
493
+ case 'semantic': return 0.05;
494
+ case 'episodic': return 0.15;
495
+ case 'working': return 0.5;
496
+ default: return 0.1;
497
+ }
498
+ }
499
+ extractTags(content) {
500
+ const tags = [];
501
+ const lower = content.toLowerCase();
502
+ const patterns = [
503
+ /\b(preference|setting|config|password|key|api|token)\b/g,
504
+ /\b(project|file|folder|code|script)\b/g,
505
+ /\b(schedule|reminder|task|todo)\b/g,
506
+ /\b(user|name|email|phone)\b/g
507
+ ];
508
+ for (const pattern of patterns) {
509
+ const matches = lower.match(pattern);
510
+ if (matches) {
511
+ tags.push(...matches);
512
+ }
513
+ }
514
+ return [...new Set(tags)];
515
+ }
516
+ consolidateMemories(memories) {
517
+ if (memories.length < 3)
518
+ return null;
519
+ const contents = memories.map(m => m.content);
520
+ const commonWords = this.findCommonWords(contents);
521
+ if (commonWords.length > 0) {
522
+ return `Consolidated insight: Topics frequently discussed include: ${commonWords.slice(0, 5).join(', ')}. Based on ${memories.length} related memories.`;
523
+ }
524
+ return null;
525
+ }
526
+ findCommonWords(texts) {
527
+ const wordCounts = new Map();
528
+ const stopWords = new Set(['the', 'a', 'an', 'is', 'are', 'was', 'were', 'to', 'for', 'of', 'and', 'in', 'on', 'at']);
529
+ for (const text of texts) {
530
+ const words = text.toLowerCase().split(/\s+/);
531
+ const seen = new Set();
532
+ for (const word of words) {
533
+ if (word.length > 3 && !stopWords.has(word) && !seen.has(word)) {
534
+ seen.add(word);
535
+ wordCounts.set(word, (wordCounts.get(word) || 0) + 1);
536
+ }
537
+ }
538
+ }
539
+ return Array.from(wordCounts.entries())
540
+ .filter(([_, count]) => count >= 2)
541
+ .sort((a, b) => b[1] - a[1])
542
+ .map(([word]) => word);
543
+ }
544
+ async pruneIfNeeded() {
545
+ const count = this.db.prepare('SELECT COUNT(*) as count FROM memories').get().count;
546
+ if (count > this.config.maxMemories) {
547
+ const toRemove = count - this.config.maxMemories;
548
+ const stmt = this.db.prepare(`
549
+ SELECT id FROM memories
550
+ WHERE type != 'procedural'
551
+ ORDER BY importance ASC, accessCount ASC
552
+ LIMIT ?
553
+ `);
554
+ const rows = stmt.all(toRemove);
555
+ for (const row of rows) {
556
+ await this.forget(row.id);
557
+ }
558
+ }
559
+ }
560
+ rowToMemory(row) {
561
+ return {
562
+ ...row,
563
+ associations: JSON.parse(row.associations),
564
+ tags: JSON.parse(row.tags),
565
+ hasEmbedding: Boolean(row.hasEmbedding)
566
+ };
567
+ }
568
+ close() {
569
+ this.db.close();
570
+ }
571
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Task Scheduler
3
+ * Schedule tasks for future execution with notifications
4
+ */
5
+ export interface ScheduledTask {
6
+ id: string;
7
+ task: string;
8
+ schedule: ScheduleSpec;
9
+ chatId: string;
10
+ enabled: boolean;
11
+ created: string;
12
+ lastRun?: string;
13
+ nextRun?: string;
14
+ runCount: number;
15
+ }
16
+ export type ScheduleSpec = {
17
+ type: 'once';
18
+ at: string;
19
+ } | {
20
+ type: 'delay';
21
+ ms: number;
22
+ } | {
23
+ type: 'cron';
24
+ pattern: string;
25
+ } | {
26
+ type: 'interval';
27
+ ms: number;
28
+ };
29
+ export type NotifyCallback = (chatId: string, message: string) => Promise<void>;
30
+ export declare class TaskScheduler {
31
+ private tasks;
32
+ private activeTimers;
33
+ private notifyCallback?;
34
+ private taskExecutor?;
35
+ private dataPath;
36
+ constructor(dataPath?: string);
37
+ /**
38
+ * Set the notification callback for sending messages
39
+ */
40
+ setNotifyCallback(callback: NotifyCallback): void;
41
+ /**
42
+ * Set the task executor callback
43
+ */
44
+ setTaskExecutor(executor: (task: string, chatId: string) => Promise<string>): void;
45
+ /**
46
+ * Parse natural language schedule
47
+ */
48
+ parseSchedule(when: string): ScheduleSpec | null;
49
+ /**
50
+ * Schedule a task
51
+ */
52
+ schedule(task: string, when: string | ScheduleSpec, chatId: string): string;
53
+ /**
54
+ * Calculate next run time
55
+ */
56
+ private calculateNextRun;
57
+ /**
58
+ * Activate a task's timer
59
+ */
60
+ private activateTask;
61
+ /**
62
+ * Deactivate a task's timer
63
+ */
64
+ private deactivateTask;
65
+ /**
66
+ * Cancel a task
67
+ */
68
+ cancel(taskId: string): boolean;
69
+ /**
70
+ * Pause a task
71
+ */
72
+ pause(taskId: string): boolean;
73
+ /**
74
+ * Resume a task
75
+ */
76
+ resume(taskId: string): boolean;
77
+ /**
78
+ * List all tasks
79
+ */
80
+ list(): ScheduledTask[];
81
+ /**
82
+ * Get a specific task
83
+ */
84
+ get(taskId: string): ScheduledTask | undefined;
85
+ /**
86
+ * Format schedule for display
87
+ */
88
+ formatSchedule(schedule: ScheduleSpec): string;
89
+ /**
90
+ * Save tasks to disk
91
+ */
92
+ private saveTasks;
93
+ /**
94
+ * Load tasks from disk
95
+ */
96
+ private loadTasks;
97
+ /**
98
+ * Stop all timers (for shutdown)
99
+ */
100
+ shutdown(): void;
101
+ }