@siftd/connect-agent 0.2.10 → 0.2.11
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,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Store Interface
|
|
3
|
+
*
|
|
4
|
+
* Common interface for memory backends (SQLite, Postgres, etc.)
|
|
5
|
+
*/
|
|
6
|
+
export type MemoryType = 'episodic' | 'semantic' | 'procedural' | 'working';
|
|
7
|
+
export interface Memory {
|
|
8
|
+
id: string;
|
|
9
|
+
type: MemoryType;
|
|
10
|
+
content: string;
|
|
11
|
+
summary?: string;
|
|
12
|
+
source: string;
|
|
13
|
+
timestamp: string;
|
|
14
|
+
lastAccessed: string;
|
|
15
|
+
importance: number;
|
|
16
|
+
accessCount: number;
|
|
17
|
+
decayRate: number;
|
|
18
|
+
associations: string[];
|
|
19
|
+
tags: string[];
|
|
20
|
+
hasEmbedding: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface MemorySearchOptions {
|
|
23
|
+
type?: MemoryType;
|
|
24
|
+
limit?: number;
|
|
25
|
+
minImportance?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface MemoryStore {
|
|
28
|
+
/**
|
|
29
|
+
* Store a new memory
|
|
30
|
+
*/
|
|
31
|
+
remember(content: string, options?: {
|
|
32
|
+
type?: MemoryType;
|
|
33
|
+
source?: string;
|
|
34
|
+
importance?: number;
|
|
35
|
+
tags?: string[];
|
|
36
|
+
}): Promise<string>;
|
|
37
|
+
/**
|
|
38
|
+
* Search memories using semantic similarity
|
|
39
|
+
*/
|
|
40
|
+
search(query: string, options?: MemorySearchOptions): Promise<Memory[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Get a specific memory by ID
|
|
43
|
+
*/
|
|
44
|
+
get(id: string): Promise<Memory | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Delete a memory
|
|
47
|
+
*/
|
|
48
|
+
forget(id: string): Promise<boolean>;
|
|
49
|
+
/**
|
|
50
|
+
* Boost importance of a memory
|
|
51
|
+
*/
|
|
52
|
+
reinforce(id: string, boost?: number): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Get memory statistics
|
|
55
|
+
*/
|
|
56
|
+
stats(): {
|
|
57
|
+
total: number;
|
|
58
|
+
byType: Record<string, number>;
|
|
59
|
+
avgImportance: number;
|
|
60
|
+
totalAssociations: number;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Close the store and release resources
|
|
64
|
+
*/
|
|
65
|
+
close(): void | Promise<void>;
|
|
66
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL Memory Store with pgvector
|
|
3
|
+
*
|
|
4
|
+
* Cloud-native memory backend for Railway deployments.
|
|
5
|
+
* Uses pgvector extension for efficient vector similarity search.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - All AdvancedMemoryStore features
|
|
9
|
+
* - Scalable Postgres backend
|
|
10
|
+
* - pgvector for vector similarity
|
|
11
|
+
* - Shared across multiple agent instances
|
|
12
|
+
*/
|
|
13
|
+
export type MemoryType = 'episodic' | 'semantic' | 'procedural' | 'working';
|
|
14
|
+
export interface Memory {
|
|
15
|
+
id: string;
|
|
16
|
+
type: MemoryType;
|
|
17
|
+
content: string;
|
|
18
|
+
summary?: string;
|
|
19
|
+
source: string;
|
|
20
|
+
timestamp: string;
|
|
21
|
+
lastAccessed: string;
|
|
22
|
+
importance: number;
|
|
23
|
+
accessCount: number;
|
|
24
|
+
decayRate: number;
|
|
25
|
+
associations: string[];
|
|
26
|
+
tags: string[];
|
|
27
|
+
hasEmbedding: boolean;
|
|
28
|
+
}
|
|
29
|
+
interface PostgresMemoryConfig {
|
|
30
|
+
connectionString: string;
|
|
31
|
+
userId?: string;
|
|
32
|
+
dimensions?: number;
|
|
33
|
+
}
|
|
34
|
+
export declare class PostgresMemoryStore {
|
|
35
|
+
private pool;
|
|
36
|
+
private userId;
|
|
37
|
+
private dimensions;
|
|
38
|
+
private initialized;
|
|
39
|
+
constructor(config: PostgresMemoryConfig);
|
|
40
|
+
/**
|
|
41
|
+
* Initialize database schema
|
|
42
|
+
*/
|
|
43
|
+
initialize(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Store a memory
|
|
46
|
+
*/
|
|
47
|
+
remember(content: string, options?: {
|
|
48
|
+
type?: MemoryType;
|
|
49
|
+
source?: string;
|
|
50
|
+
importance?: number;
|
|
51
|
+
tags?: string[];
|
|
52
|
+
}): Promise<string>;
|
|
53
|
+
/**
|
|
54
|
+
* Search memories using vector similarity
|
|
55
|
+
*/
|
|
56
|
+
search(query: string, options?: {
|
|
57
|
+
type?: MemoryType;
|
|
58
|
+
limit?: number;
|
|
59
|
+
minImportance?: number;
|
|
60
|
+
}): Promise<Memory[]>;
|
|
61
|
+
/**
|
|
62
|
+
* Get memory by ID
|
|
63
|
+
*/
|
|
64
|
+
get(id: string): Promise<Memory | null>;
|
|
65
|
+
/**
|
|
66
|
+
* Delete a memory
|
|
67
|
+
*/
|
|
68
|
+
forget(id: string): Promise<boolean>;
|
|
69
|
+
/**
|
|
70
|
+
* Update memory importance
|
|
71
|
+
*/
|
|
72
|
+
reinforce(id: string, boost?: number): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Get memory statistics
|
|
75
|
+
*/
|
|
76
|
+
stats(): {
|
|
77
|
+
total: number;
|
|
78
|
+
byType: Record<string, number>;
|
|
79
|
+
avgImportance: number;
|
|
80
|
+
totalAssociations: number;
|
|
81
|
+
};
|
|
82
|
+
private _cachedStats;
|
|
83
|
+
/**
|
|
84
|
+
* Update stats cache (call periodically)
|
|
85
|
+
*/
|
|
86
|
+
updateStatsCache(): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Apply temporal decay to all memories
|
|
89
|
+
*/
|
|
90
|
+
applyDecay(): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Cleanup old low-importance memories
|
|
93
|
+
*/
|
|
94
|
+
cleanup(options?: {
|
|
95
|
+
maxAge?: number;
|
|
96
|
+
minImportance?: number;
|
|
97
|
+
}): Promise<number>;
|
|
98
|
+
/**
|
|
99
|
+
* Close the connection pool
|
|
100
|
+
*/
|
|
101
|
+
close(): Promise<void>;
|
|
102
|
+
private inferType;
|
|
103
|
+
private calculateImportance;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check if Postgres is available and configured
|
|
107
|
+
*/
|
|
108
|
+
export declare function isPostgresConfigured(): boolean;
|
|
109
|
+
export {};
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL Memory Store with pgvector
|
|
3
|
+
*
|
|
4
|
+
* Cloud-native memory backend for Railway deployments.
|
|
5
|
+
* Uses pgvector extension for efficient vector similarity search.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - All AdvancedMemoryStore features
|
|
9
|
+
* - Scalable Postgres backend
|
|
10
|
+
* - pgvector for vector similarity
|
|
11
|
+
* - Shared across multiple agent instances
|
|
12
|
+
*/
|
|
13
|
+
import pg from 'pg';
|
|
14
|
+
const { Pool } = pg;
|
|
15
|
+
// Simple local embedding using character n-grams (same as SQLite version)
|
|
16
|
+
function computeEmbedding(text, dimensions = 384) {
|
|
17
|
+
const embedding = new Array(dimensions).fill(0);
|
|
18
|
+
const normalized = text.toLowerCase();
|
|
19
|
+
// Character trigrams
|
|
20
|
+
for (let i = 0; i < normalized.length - 2; i++) {
|
|
21
|
+
const trigram = normalized.slice(i, i + 3);
|
|
22
|
+
const hash = hashString(trigram);
|
|
23
|
+
const idx = Math.abs(hash) % dimensions;
|
|
24
|
+
embedding[idx] += 1;
|
|
25
|
+
}
|
|
26
|
+
// Word unigrams
|
|
27
|
+
const words = normalized.split(/\s+/);
|
|
28
|
+
for (const word of words) {
|
|
29
|
+
const hash = hashString(word);
|
|
30
|
+
const idx = Math.abs(hash) % dimensions;
|
|
31
|
+
embedding[idx] += 2;
|
|
32
|
+
}
|
|
33
|
+
// Normalize
|
|
34
|
+
const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0));
|
|
35
|
+
if (magnitude > 0) {
|
|
36
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
37
|
+
embedding[i] /= magnitude;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return embedding;
|
|
41
|
+
}
|
|
42
|
+
function hashString(str) {
|
|
43
|
+
let hash = 0;
|
|
44
|
+
for (let i = 0; i < str.length; i++) {
|
|
45
|
+
const char = str.charCodeAt(i);
|
|
46
|
+
hash = ((hash << 5) - hash) + char;
|
|
47
|
+
hash = hash & hash;
|
|
48
|
+
}
|
|
49
|
+
return hash;
|
|
50
|
+
}
|
|
51
|
+
export class PostgresMemoryStore {
|
|
52
|
+
pool;
|
|
53
|
+
userId;
|
|
54
|
+
dimensions;
|
|
55
|
+
initialized = false;
|
|
56
|
+
constructor(config) {
|
|
57
|
+
this.pool = new Pool({
|
|
58
|
+
connectionString: config.connectionString,
|
|
59
|
+
ssl: { rejectUnauthorized: false } // Required for Railway
|
|
60
|
+
});
|
|
61
|
+
this.userId = config.userId || 'default';
|
|
62
|
+
this.dimensions = config.dimensions || 384;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Initialize database schema
|
|
66
|
+
*/
|
|
67
|
+
async initialize() {
|
|
68
|
+
if (this.initialized)
|
|
69
|
+
return;
|
|
70
|
+
const client = await this.pool.connect();
|
|
71
|
+
try {
|
|
72
|
+
// Enable pgvector extension
|
|
73
|
+
await client.query('CREATE EXTENSION IF NOT EXISTS vector');
|
|
74
|
+
// Create memories table
|
|
75
|
+
await client.query(`
|
|
76
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
77
|
+
id TEXT PRIMARY KEY,
|
|
78
|
+
user_id TEXT NOT NULL,
|
|
79
|
+
type TEXT NOT NULL,
|
|
80
|
+
content TEXT NOT NULL,
|
|
81
|
+
summary TEXT,
|
|
82
|
+
source TEXT,
|
|
83
|
+
timestamp TIMESTAMPTZ DEFAULT NOW(),
|
|
84
|
+
last_accessed TIMESTAMPTZ DEFAULT NOW(),
|
|
85
|
+
importance REAL DEFAULT 0.5,
|
|
86
|
+
access_count INTEGER DEFAULT 0,
|
|
87
|
+
decay_rate REAL DEFAULT 0.01,
|
|
88
|
+
associations TEXT[] DEFAULT '{}',
|
|
89
|
+
tags TEXT[] DEFAULT '{}',
|
|
90
|
+
embedding vector(${this.dimensions}),
|
|
91
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
92
|
+
)
|
|
93
|
+
`);
|
|
94
|
+
// Create indexes
|
|
95
|
+
await client.query(`
|
|
96
|
+
CREATE INDEX IF NOT EXISTS idx_memories_user_id ON memories(user_id)
|
|
97
|
+
`);
|
|
98
|
+
await client.query(`
|
|
99
|
+
CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type)
|
|
100
|
+
`);
|
|
101
|
+
await client.query(`
|
|
102
|
+
CREATE INDEX IF NOT EXISTS idx_memories_importance ON memories(importance DESC)
|
|
103
|
+
`);
|
|
104
|
+
// Create vector similarity index (IVFFlat for performance)
|
|
105
|
+
await client.query(`
|
|
106
|
+
CREATE INDEX IF NOT EXISTS idx_memories_embedding
|
|
107
|
+
ON memories USING ivfflat (embedding vector_cosine_ops)
|
|
108
|
+
WITH (lists = 100)
|
|
109
|
+
`).catch(() => {
|
|
110
|
+
// IVFFlat requires at least 100 rows, fall back to exact search
|
|
111
|
+
console.log('[POSTGRES] IVFFlat index skipped (will use exact search)');
|
|
112
|
+
});
|
|
113
|
+
this.initialized = true;
|
|
114
|
+
console.log('[POSTGRES] Memory store initialized');
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
client.release();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Store a memory
|
|
122
|
+
*/
|
|
123
|
+
async remember(content, options) {
|
|
124
|
+
await this.initialize();
|
|
125
|
+
const id = `mem_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
126
|
+
const type = options?.type || this.inferType(content);
|
|
127
|
+
const importance = options?.importance ?? this.calculateImportance(content);
|
|
128
|
+
const embedding = computeEmbedding(content, this.dimensions);
|
|
129
|
+
await this.pool.query(`
|
|
130
|
+
INSERT INTO memories (id, user_id, type, content, source, importance, tags, embedding)
|
|
131
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
132
|
+
`, [
|
|
133
|
+
id,
|
|
134
|
+
this.userId,
|
|
135
|
+
type,
|
|
136
|
+
content,
|
|
137
|
+
options?.source || 'user',
|
|
138
|
+
importance,
|
|
139
|
+
options?.tags || [],
|
|
140
|
+
`[${embedding.join(',')}]`
|
|
141
|
+
]);
|
|
142
|
+
console.log(`[POSTGRES] Stored memory ${id} (${type}, importance=${importance.toFixed(2)})`);
|
|
143
|
+
return id;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Search memories using vector similarity
|
|
147
|
+
*/
|
|
148
|
+
async search(query, options) {
|
|
149
|
+
await this.initialize();
|
|
150
|
+
const embedding = computeEmbedding(query, this.dimensions);
|
|
151
|
+
const limit = options?.limit || 10;
|
|
152
|
+
const minImportance = options?.minImportance || 0;
|
|
153
|
+
let sql = `
|
|
154
|
+
SELECT
|
|
155
|
+
id, type, content, summary, source, timestamp, last_accessed,
|
|
156
|
+
importance, access_count, decay_rate, associations, tags,
|
|
157
|
+
1 - (embedding <=> $1::vector) as similarity
|
|
158
|
+
FROM memories
|
|
159
|
+
WHERE user_id = $2
|
|
160
|
+
AND importance >= $3
|
|
161
|
+
`;
|
|
162
|
+
const params = [
|
|
163
|
+
`[${embedding.join(',')}]`,
|
|
164
|
+
this.userId,
|
|
165
|
+
minImportance
|
|
166
|
+
];
|
|
167
|
+
if (options?.type) {
|
|
168
|
+
sql += ` AND type = $${params.length + 1}`;
|
|
169
|
+
params.push(options.type);
|
|
170
|
+
}
|
|
171
|
+
sql += ` ORDER BY similarity DESC LIMIT $${params.length + 1}`;
|
|
172
|
+
params.push(limit);
|
|
173
|
+
const result = await this.pool.query(sql, params);
|
|
174
|
+
// Update access counts for retrieved memories
|
|
175
|
+
if (result.rows.length > 0) {
|
|
176
|
+
const ids = result.rows.map(r => r.id);
|
|
177
|
+
await this.pool.query(`
|
|
178
|
+
UPDATE memories
|
|
179
|
+
SET access_count = access_count + 1,
|
|
180
|
+
last_accessed = NOW()
|
|
181
|
+
WHERE id = ANY($1)
|
|
182
|
+
`, [ids]);
|
|
183
|
+
}
|
|
184
|
+
return result.rows.map(row => ({
|
|
185
|
+
id: row.id,
|
|
186
|
+
type: row.type,
|
|
187
|
+
content: row.content,
|
|
188
|
+
summary: row.summary,
|
|
189
|
+
source: row.source,
|
|
190
|
+
timestamp: row.timestamp.toISOString(),
|
|
191
|
+
lastAccessed: row.last_accessed.toISOString(),
|
|
192
|
+
importance: row.importance,
|
|
193
|
+
accessCount: row.access_count,
|
|
194
|
+
decayRate: row.decay_rate,
|
|
195
|
+
associations: row.associations || [],
|
|
196
|
+
tags: row.tags || [],
|
|
197
|
+
hasEmbedding: true
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get memory by ID
|
|
202
|
+
*/
|
|
203
|
+
async get(id) {
|
|
204
|
+
await this.initialize();
|
|
205
|
+
const result = await this.pool.query(`
|
|
206
|
+
SELECT * FROM memories WHERE id = $1 AND user_id = $2
|
|
207
|
+
`, [id, this.userId]);
|
|
208
|
+
if (result.rows.length === 0)
|
|
209
|
+
return null;
|
|
210
|
+
const row = result.rows[0];
|
|
211
|
+
return {
|
|
212
|
+
id: row.id,
|
|
213
|
+
type: row.type,
|
|
214
|
+
content: row.content,
|
|
215
|
+
summary: row.summary,
|
|
216
|
+
source: row.source,
|
|
217
|
+
timestamp: row.timestamp.toISOString(),
|
|
218
|
+
lastAccessed: row.last_accessed.toISOString(),
|
|
219
|
+
importance: row.importance,
|
|
220
|
+
accessCount: row.access_count,
|
|
221
|
+
decayRate: row.decay_rate,
|
|
222
|
+
associations: row.associations || [],
|
|
223
|
+
tags: row.tags || [],
|
|
224
|
+
hasEmbedding: true
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Delete a memory
|
|
229
|
+
*/
|
|
230
|
+
async forget(id) {
|
|
231
|
+
await this.initialize();
|
|
232
|
+
const result = await this.pool.query(`
|
|
233
|
+
DELETE FROM memories WHERE id = $1 AND user_id = $2
|
|
234
|
+
`, [id, this.userId]);
|
|
235
|
+
return (result.rowCount ?? 0) > 0;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Update memory importance
|
|
239
|
+
*/
|
|
240
|
+
async reinforce(id, boost = 0.1) {
|
|
241
|
+
await this.initialize();
|
|
242
|
+
await this.pool.query(`
|
|
243
|
+
UPDATE memories
|
|
244
|
+
SET importance = LEAST(1.0, importance + $1)
|
|
245
|
+
WHERE id = $2 AND user_id = $3
|
|
246
|
+
`, [boost, id, this.userId]);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Get memory statistics
|
|
250
|
+
*/
|
|
251
|
+
stats() {
|
|
252
|
+
// This is synchronous in the interface, but we need async for Postgres
|
|
253
|
+
// Return cached stats or default values
|
|
254
|
+
return this._cachedStats || {
|
|
255
|
+
total: 0,
|
|
256
|
+
byType: { episodic: 0, semantic: 0, procedural: 0, working: 0 },
|
|
257
|
+
avgImportance: 0.5,
|
|
258
|
+
totalAssociations: 0
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
_cachedStats = null;
|
|
262
|
+
/**
|
|
263
|
+
* Update stats cache (call periodically)
|
|
264
|
+
*/
|
|
265
|
+
async updateStatsCache() {
|
|
266
|
+
await this.initialize();
|
|
267
|
+
const result = await this.pool.query(`
|
|
268
|
+
SELECT
|
|
269
|
+
COUNT(*) as total,
|
|
270
|
+
AVG(importance) as avg_importance,
|
|
271
|
+
SUM(array_length(associations, 1)) as total_associations
|
|
272
|
+
FROM memories
|
|
273
|
+
WHERE user_id = $1
|
|
274
|
+
`, [this.userId]);
|
|
275
|
+
const typeResult = await this.pool.query(`
|
|
276
|
+
SELECT type, COUNT(*) as count
|
|
277
|
+
FROM memories
|
|
278
|
+
WHERE user_id = $1
|
|
279
|
+
GROUP BY type
|
|
280
|
+
`, [this.userId]);
|
|
281
|
+
const byType = {
|
|
282
|
+
episodic: 0, semantic: 0, procedural: 0, working: 0
|
|
283
|
+
};
|
|
284
|
+
for (const row of typeResult.rows) {
|
|
285
|
+
byType[row.type] = parseInt(row.count);
|
|
286
|
+
}
|
|
287
|
+
this._cachedStats = {
|
|
288
|
+
total: parseInt(result.rows[0].total) || 0,
|
|
289
|
+
byType,
|
|
290
|
+
avgImportance: parseFloat(result.rows[0].avg_importance) || 0.5,
|
|
291
|
+
totalAssociations: parseInt(result.rows[0].total_associations) || 0
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Apply temporal decay to all memories
|
|
296
|
+
*/
|
|
297
|
+
async applyDecay() {
|
|
298
|
+
await this.initialize();
|
|
299
|
+
await this.pool.query(`
|
|
300
|
+
UPDATE memories
|
|
301
|
+
SET importance = GREATEST(0.1, importance * (1 - decay_rate))
|
|
302
|
+
WHERE user_id = $1
|
|
303
|
+
AND importance > 0.1
|
|
304
|
+
AND last_accessed < NOW() - INTERVAL '1 day'
|
|
305
|
+
`, [this.userId]);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Cleanup old low-importance memories
|
|
309
|
+
*/
|
|
310
|
+
async cleanup(options) {
|
|
311
|
+
await this.initialize();
|
|
312
|
+
const maxAgeDays = (options?.maxAge || 30 * 24 * 60 * 60 * 1000) / (1000 * 60 * 60 * 24);
|
|
313
|
+
const minImportance = options?.minImportance || 0.2;
|
|
314
|
+
const result = await this.pool.query(`
|
|
315
|
+
DELETE FROM memories
|
|
316
|
+
WHERE user_id = $1
|
|
317
|
+
AND importance < $2
|
|
318
|
+
AND last_accessed < NOW() - INTERVAL '1 day' * $3
|
|
319
|
+
`, [this.userId, minImportance, maxAgeDays]);
|
|
320
|
+
const count = result.rowCount ?? 0;
|
|
321
|
+
if (count > 0) {
|
|
322
|
+
console.log(`[POSTGRES] Cleaned up ${count} old memories`);
|
|
323
|
+
}
|
|
324
|
+
return count;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Close the connection pool
|
|
328
|
+
*/
|
|
329
|
+
async close() {
|
|
330
|
+
await this.pool.end();
|
|
331
|
+
console.log('[POSTGRES] Connection pool closed');
|
|
332
|
+
}
|
|
333
|
+
// Helper methods
|
|
334
|
+
inferType(content) {
|
|
335
|
+
const lower = content.toLowerCase();
|
|
336
|
+
// Procedural indicators
|
|
337
|
+
if (lower.includes('how to') || lower.includes('steps to') ||
|
|
338
|
+
lower.includes('process for') || lower.includes('when doing')) {
|
|
339
|
+
return 'procedural';
|
|
340
|
+
}
|
|
341
|
+
// Episodic indicators (past tense, specific events)
|
|
342
|
+
if (lower.includes('yesterday') || lower.includes('last week') ||
|
|
343
|
+
lower.includes('we did') || lower.includes('happened')) {
|
|
344
|
+
return 'episodic';
|
|
345
|
+
}
|
|
346
|
+
// Default to semantic (facts)
|
|
347
|
+
return 'semantic';
|
|
348
|
+
}
|
|
349
|
+
calculateImportance(content) {
|
|
350
|
+
let importance = 0.5;
|
|
351
|
+
// Boost for longer, detailed content
|
|
352
|
+
if (content.length > 200)
|
|
353
|
+
importance += 0.1;
|
|
354
|
+
if (content.length > 500)
|
|
355
|
+
importance += 0.1;
|
|
356
|
+
// Boost for structured content
|
|
357
|
+
if (content.includes(':') || content.includes('-') || content.includes('•')) {
|
|
358
|
+
importance += 0.1;
|
|
359
|
+
}
|
|
360
|
+
// Boost for preference indicators
|
|
361
|
+
const lower = content.toLowerCase();
|
|
362
|
+
if (lower.includes('prefer') || lower.includes('always') ||
|
|
363
|
+
lower.includes('never') || lower.includes('important')) {
|
|
364
|
+
importance += 0.15;
|
|
365
|
+
}
|
|
366
|
+
return Math.min(1.0, importance);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Check if Postgres is available and configured
|
|
371
|
+
*/
|
|
372
|
+
export function isPostgresConfigured() {
|
|
373
|
+
return !!process.env.DATABASE_URL;
|
|
374
|
+
}
|
package/dist/heartbeat.js
CHANGED
|
@@ -10,7 +10,7 @@ import { hostname } from 'os';
|
|
|
10
10
|
import { createHash } from 'crypto';
|
|
11
11
|
import { getServerUrl, getAgentToken, getUserId, isCloudMode } from './config.js';
|
|
12
12
|
const HEARTBEAT_INTERVAL = 10000; // 10 seconds
|
|
13
|
-
const VERSION = '0.2.
|
|
13
|
+
const VERSION = '0.2.11'; // Should match package.json
|
|
14
14
|
const state = {
|
|
15
15
|
intervalId: null,
|
|
16
16
|
runnerId: null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siftd/connect-agent",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Master orchestrator agent - control Claude Code remotely via web",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"conf": "^13.0.1",
|
|
44
44
|
"node-cron": "^3.0.3",
|
|
45
45
|
"ora": "^8.1.1",
|
|
46
|
+
"pg": "^8.13.1",
|
|
46
47
|
"vectra": "^0.9.0",
|
|
47
48
|
"ws": "^8.18.3"
|
|
48
49
|
},
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
"@types/better-sqlite3": "^7.6.12",
|
|
51
52
|
"@types/node": "^22.10.2",
|
|
52
53
|
"@types/node-cron": "^3.0.11",
|
|
54
|
+
"@types/pg": "^8.11.10",
|
|
53
55
|
"@types/ws": "^8.18.1",
|
|
54
56
|
"typescript": "^5.7.2"
|
|
55
57
|
}
|