ahok-skill 1.3.1

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.
Files changed (141) hide show
  1. package/.prettierrc +8 -0
  2. package/Dockerfile +59 -0
  3. package/RAW_SKILL.md +219 -0
  4. package/README.md +277 -0
  5. package/SKILL.md +58 -0
  6. package/bin/opm.js +268 -0
  7. package/data/openmemory.sqlite +0 -0
  8. package/data/openmemory.sqlite-shm +0 -0
  9. package/data/openmemory.sqlite-wal +0 -0
  10. package/dist/ai/graph.js +293 -0
  11. package/dist/ai/mcp.js +397 -0
  12. package/dist/cli.js +78 -0
  13. package/dist/core/cfg.js +87 -0
  14. package/dist/core/db.js +636 -0
  15. package/dist/core/memory.js +116 -0
  16. package/dist/core/migrate.js +227 -0
  17. package/dist/core/models.js +105 -0
  18. package/dist/core/telemetry.js +57 -0
  19. package/dist/core/types.js +2 -0
  20. package/dist/core/vector/postgres.js +52 -0
  21. package/dist/core/vector/valkey.js +246 -0
  22. package/dist/core/vector_store.js +2 -0
  23. package/dist/index.js +44 -0
  24. package/dist/memory/decay.js +301 -0
  25. package/dist/memory/embed.js +675 -0
  26. package/dist/memory/hsg.js +959 -0
  27. package/dist/memory/reflect.js +131 -0
  28. package/dist/memory/user_summary.js +99 -0
  29. package/dist/migrate.js +9 -0
  30. package/dist/ops/compress.js +255 -0
  31. package/dist/ops/dynamics.js +189 -0
  32. package/dist/ops/extract.js +333 -0
  33. package/dist/ops/ingest.js +214 -0
  34. package/dist/server/index.js +109 -0
  35. package/dist/server/middleware/auth.js +137 -0
  36. package/dist/server/routes/auth.js +186 -0
  37. package/dist/server/routes/compression.js +108 -0
  38. package/dist/server/routes/dashboard.js +399 -0
  39. package/dist/server/routes/docs.js +241 -0
  40. package/dist/server/routes/dynamics.js +312 -0
  41. package/dist/server/routes/ide.js +280 -0
  42. package/dist/server/routes/index.js +33 -0
  43. package/dist/server/routes/keys.js +132 -0
  44. package/dist/server/routes/langgraph.js +61 -0
  45. package/dist/server/routes/memory.js +213 -0
  46. package/dist/server/routes/sources.js +140 -0
  47. package/dist/server/routes/system.js +63 -0
  48. package/dist/server/routes/temporal.js +293 -0
  49. package/dist/server/routes/users.js +101 -0
  50. package/dist/server/routes/vercel.js +57 -0
  51. package/dist/server/server.js +211 -0
  52. package/dist/server.js +3 -0
  53. package/dist/sources/base.js +223 -0
  54. package/dist/sources/github.js +171 -0
  55. package/dist/sources/google_drive.js +166 -0
  56. package/dist/sources/google_sheets.js +112 -0
  57. package/dist/sources/google_slides.js +139 -0
  58. package/dist/sources/index.js +34 -0
  59. package/dist/sources/notion.js +165 -0
  60. package/dist/sources/onedrive.js +143 -0
  61. package/dist/sources/web_crawler.js +166 -0
  62. package/dist/temporal_graph/index.js +20 -0
  63. package/dist/temporal_graph/query.js +240 -0
  64. package/dist/temporal_graph/store.js +116 -0
  65. package/dist/temporal_graph/timeline.js +241 -0
  66. package/dist/temporal_graph/types.js +2 -0
  67. package/dist/utils/chunking.js +60 -0
  68. package/dist/utils/index.js +31 -0
  69. package/dist/utils/keyword.js +94 -0
  70. package/dist/utils/text.js +120 -0
  71. package/nodemon.json +7 -0
  72. package/package.json +50 -0
  73. package/references/api_reference.md +66 -0
  74. package/references/examples.md +45 -0
  75. package/src/ai/graph.ts +363 -0
  76. package/src/ai/mcp.ts +494 -0
  77. package/src/cli.ts +94 -0
  78. package/src/core/cfg.ts +110 -0
  79. package/src/core/db.ts +1052 -0
  80. package/src/core/memory.ts +99 -0
  81. package/src/core/migrate.ts +302 -0
  82. package/src/core/models.ts +107 -0
  83. package/src/core/telemetry.ts +47 -0
  84. package/src/core/types.ts +130 -0
  85. package/src/core/vector/postgres.ts +61 -0
  86. package/src/core/vector/valkey.ts +261 -0
  87. package/src/core/vector_store.ts +9 -0
  88. package/src/index.ts +5 -0
  89. package/src/memory/decay.ts +427 -0
  90. package/src/memory/embed.ts +707 -0
  91. package/src/memory/hsg.ts +1245 -0
  92. package/src/memory/reflect.ts +158 -0
  93. package/src/memory/user_summary.ts +110 -0
  94. package/src/migrate.ts +8 -0
  95. package/src/ops/compress.ts +296 -0
  96. package/src/ops/dynamics.ts +272 -0
  97. package/src/ops/extract.ts +360 -0
  98. package/src/ops/ingest.ts +286 -0
  99. package/src/server/index.ts +159 -0
  100. package/src/server/middleware/auth.ts +156 -0
  101. package/src/server/routes/auth.ts +223 -0
  102. package/src/server/routes/compression.ts +106 -0
  103. package/src/server/routes/dashboard.ts +420 -0
  104. package/src/server/routes/docs.ts +380 -0
  105. package/src/server/routes/dynamics.ts +516 -0
  106. package/src/server/routes/ide.ts +283 -0
  107. package/src/server/routes/index.ts +32 -0
  108. package/src/server/routes/keys.ts +131 -0
  109. package/src/server/routes/langgraph.ts +71 -0
  110. package/src/server/routes/memory.ts +440 -0
  111. package/src/server/routes/sources.ts +111 -0
  112. package/src/server/routes/system.ts +68 -0
  113. package/src/server/routes/temporal.ts +335 -0
  114. package/src/server/routes/users.ts +111 -0
  115. package/src/server/routes/vercel.ts +55 -0
  116. package/src/server/server.js +215 -0
  117. package/src/server.ts +1 -0
  118. package/src/sources/base.ts +257 -0
  119. package/src/sources/github.ts +156 -0
  120. package/src/sources/google_drive.ts +144 -0
  121. package/src/sources/google_sheets.ts +85 -0
  122. package/src/sources/google_slides.ts +115 -0
  123. package/src/sources/index.ts +19 -0
  124. package/src/sources/notion.ts +148 -0
  125. package/src/sources/onedrive.ts +131 -0
  126. package/src/sources/web_crawler.ts +161 -0
  127. package/src/temporal_graph/index.ts +4 -0
  128. package/src/temporal_graph/query.ts +299 -0
  129. package/src/temporal_graph/store.ts +156 -0
  130. package/src/temporal_graph/timeline.ts +319 -0
  131. package/src/temporal_graph/types.ts +41 -0
  132. package/src/utils/chunking.ts +66 -0
  133. package/src/utils/index.ts +25 -0
  134. package/src/utils/keyword.ts +137 -0
  135. package/src/utils/text.ts +115 -0
  136. package/tests/test_api_workspace_management.ts +413 -0
  137. package/tests/test_bulk_delete.ts +267 -0
  138. package/tests/test_omnibus.ts +166 -0
  139. package/tests/test_workspace_management.ts +278 -0
  140. package/tests/verify.ts +104 -0
  141. package/tsconfig.json +15 -0
@@ -0,0 +1,161 @@
1
+ /**
2
+ * web crawler source for openmemory - production grade
3
+ * requires: cheerio (for html parsing)
4
+ * no auth required for public urls
5
+ */
6
+
7
+ import { base_source, source_config_error, source_item, source_content, source_config } from './base';
8
+
9
+ export interface web_crawler_config extends source_config {
10
+ max_pages?: number;
11
+ max_depth?: number;
12
+ timeout?: number;
13
+ }
14
+
15
+ export class web_crawler_source extends base_source {
16
+ name = 'web_crawler';
17
+ private max_pages: number;
18
+ private max_depth: number;
19
+ private timeout: number;
20
+ private visited: Set<string> = new Set();
21
+ private crawled: source_item[] = [];
22
+
23
+ constructor(
24
+ user_id?: string,
25
+ config?: web_crawler_config
26
+ ) {
27
+ super(user_id, config);
28
+ this.max_pages = config?.max_pages || 50;
29
+ this.max_depth = config?.max_depth || 3;
30
+ this.timeout = config?.timeout || 30000;
31
+ }
32
+
33
+ async _connect(): Promise<boolean> {
34
+ return true; // no auth needed
35
+ }
36
+
37
+ async _list_items(filters: Record<string, any>): Promise<source_item[]> {
38
+ if (!filters.start_url) {
39
+ throw new source_config_error('start_url is required', this.name);
40
+ }
41
+
42
+ let cheerio: any;
43
+ try {
44
+ cheerio = await import('cheerio');
45
+ } catch {
46
+ throw new source_config_error('missing deps: npm install cheerio', this.name);
47
+ }
48
+
49
+ this.visited.clear();
50
+ this.crawled = [];
51
+
52
+ const base_url = new URL(filters.start_url);
53
+ const base_domain = base_url.hostname;
54
+ const to_visit: { url: string; depth: number }[] = [{ url: filters.start_url, depth: 0 }];
55
+ const follow_links = filters.follow_links !== false;
56
+
57
+ while (to_visit.length > 0 && this.crawled.length < this.max_pages) {
58
+ const { url, depth } = to_visit.shift()!;
59
+
60
+ if (this.visited.has(url) || depth > this.max_depth) continue;
61
+ this.visited.add(url);
62
+
63
+ try {
64
+ const controller = new AbortController();
65
+ const timeout_id = setTimeout(() => controller.abort(), this.timeout);
66
+
67
+ const resp = await fetch(url, {
68
+ headers: { 'User-Agent': 'OpenMemory-Crawler/1.0 (compatible)' },
69
+ signal: controller.signal
70
+ });
71
+
72
+ clearTimeout(timeout_id);
73
+
74
+ if (!resp.ok) continue;
75
+
76
+ const content_type = resp.headers.get('content-type') || '';
77
+ if (!content_type.includes('text/html')) continue;
78
+
79
+ const html = await resp.text();
80
+ const $ = cheerio.load(html);
81
+
82
+ const title = $('title').text() || url;
83
+
84
+ this.crawled.push({
85
+ id: url,
86
+ name: title.trim(),
87
+ type: 'webpage',
88
+ url,
89
+ depth
90
+ });
91
+
92
+ // find and queue links
93
+ if (follow_links && depth < this.max_depth) {
94
+ $('a[href]').each((_: any, el: any) => {
95
+ try {
96
+ const href = $(el).attr('href');
97
+ if (!href) return;
98
+
99
+ const full_url = new URL(href, url);
100
+ if (full_url.hostname !== base_domain) return;
101
+
102
+ const clean_url = `${full_url.protocol}//${full_url.hostname}${full_url.pathname}`;
103
+ if (!this.visited.has(clean_url)) {
104
+ to_visit.push({ url: clean_url, depth: depth + 1 });
105
+ }
106
+ } catch { }
107
+ });
108
+ }
109
+ } catch (e: any) {
110
+ console.warn(`[web_crawler] failed to fetch ${url}: ${e.message}`);
111
+ }
112
+ }
113
+
114
+ return this.crawled;
115
+ }
116
+
117
+ async _fetch_item(item_id: string): Promise<source_content> {
118
+ let cheerio: any;
119
+ try {
120
+ cheerio = await import('cheerio');
121
+ } catch {
122
+ throw new source_config_error('missing deps: npm install cheerio', this.name);
123
+ }
124
+
125
+ const controller = new AbortController();
126
+ const timeout_id = setTimeout(() => controller.abort(), this.timeout);
127
+
128
+ const resp = await fetch(item_id, {
129
+ headers: { 'User-Agent': 'OpenMemory-Crawler/1.0 (compatible)' },
130
+ signal: controller.signal
131
+ });
132
+
133
+ clearTimeout(timeout_id);
134
+
135
+ if (!resp.ok) throw new Error(`http ${resp.status}: ${resp.statusText}`);
136
+
137
+ const html = await resp.text();
138
+ const $ = cheerio.load(html);
139
+
140
+ // remove noise
141
+ $('script, style, nav, footer, header, aside').remove();
142
+
143
+ const title = $('title').text() || item_id;
144
+
145
+ // get main content
146
+ const main = $('main').length ? $('main') : $('article').length ? $('article') : $('body');
147
+ let text = main.text();
148
+
149
+ // clean up whitespace
150
+ text = text.split('\n').map((l: string) => l.trim()).filter(Boolean).join('\n');
151
+
152
+ return {
153
+ id: item_id,
154
+ name: title.trim(),
155
+ type: 'webpage',
156
+ text,
157
+ data: text,
158
+ meta: { source: 'web_crawler', url: item_id, char_count: text.length }
159
+ };
160
+ }
161
+ }
@@ -0,0 +1,4 @@
1
+ export * from './types'
2
+ export * from './store'
3
+ export * from './query'
4
+ export * from './timeline'
@@ -0,0 +1,299 @@
1
+
2
+
3
+ import { get_async, all_async } from '../core/db'
4
+ import { TemporalFact, TemporalQuery, TimelineEntry } from './types'
5
+
6
+
7
+ export const query_facts_at_time = async (
8
+ subject?: string,
9
+ predicate?: string,
10
+ object?: string,
11
+ at: Date = new Date(),
12
+ min_confidence: number = 0.1
13
+ ): Promise<TemporalFact[]> => {
14
+ const timestamp = at.getTime()
15
+ const conditions: string[] = []
16
+ const params: any[] = []
17
+
18
+ // Build WHERE clause
19
+ conditions.push('(valid_from <= ? AND (valid_to IS NULL OR valid_to >= ?))')
20
+ params.push(timestamp, timestamp)
21
+
22
+ if (subject) {
23
+ conditions.push('subject = ?')
24
+ params.push(subject)
25
+ }
26
+
27
+ if (predicate) {
28
+ conditions.push('predicate = ?')
29
+ params.push(predicate)
30
+ }
31
+
32
+ if (object) {
33
+ conditions.push('object = ?')
34
+ params.push(object)
35
+ }
36
+
37
+ if (min_confidence > 0) {
38
+ conditions.push('confidence >= ?')
39
+ params.push(min_confidence)
40
+ }
41
+
42
+ const sql = `
43
+ SELECT id, subject, predicate, object, valid_from, valid_to, confidence, last_updated, metadata
44
+ FROM temporal_facts
45
+ WHERE ${conditions.join(' AND ')}
46
+ ORDER BY confidence DESC, valid_from DESC
47
+ `
48
+
49
+ const rows = await all_async(sql, params)
50
+ return rows.map(row => ({
51
+ id: row.id,
52
+ subject: row.subject,
53
+ predicate: row.predicate,
54
+ object: row.object,
55
+ valid_from: new Date(row.valid_from),
56
+ valid_to: row.valid_to ? new Date(row.valid_to) : null,
57
+ confidence: row.confidence,
58
+ last_updated: new Date(row.last_updated),
59
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined
60
+ }))
61
+ }
62
+
63
+
64
+ export const get_current_fact = async (
65
+ subject: string,
66
+ predicate: string
67
+ ): Promise<TemporalFact | null> => {
68
+ const now = Date.now()
69
+
70
+ const row = await get_async(`
71
+ SELECT id, subject, predicate, object, valid_from, valid_to, confidence, last_updated, metadata
72
+ FROM temporal_facts
73
+ WHERE subject = ? AND predicate = ? AND valid_to IS NULL
74
+ ORDER BY valid_from DESC
75
+ LIMIT 1
76
+ `, [subject, predicate])
77
+
78
+ if (!row) return null
79
+
80
+ return {
81
+ id: row.id,
82
+ subject: row.subject,
83
+ predicate: row.predicate,
84
+ object: row.object,
85
+ valid_from: new Date(row.valid_from),
86
+ valid_to: row.valid_to ? new Date(row.valid_to) : null,
87
+ confidence: row.confidence,
88
+ last_updated: new Date(row.last_updated),
89
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined
90
+ }
91
+ }
92
+
93
+
94
+ export const query_facts_in_range = async (
95
+ subject?: string,
96
+ predicate?: string,
97
+ from?: Date,
98
+ to?: Date,
99
+ min_confidence: number = 0.1
100
+ ): Promise<TemporalFact[]> => {
101
+ const conditions: string[] = []
102
+ const params: any[] = []
103
+
104
+ if (from && to) {
105
+ const from_ts = from.getTime()
106
+ const to_ts = to.getTime()
107
+ conditions.push('((valid_from <= ? AND (valid_to IS NULL OR valid_to >= ?)) OR (valid_from >= ? AND valid_from <= ?))')
108
+ params.push(to_ts, from_ts, from_ts, to_ts)
109
+ } else if (from) {
110
+ conditions.push('valid_from >= ?')
111
+ params.push(from.getTime())
112
+ } else if (to) {
113
+ conditions.push('valid_from <= ?')
114
+ params.push(to.getTime())
115
+ }
116
+
117
+ if (subject) {
118
+ conditions.push('subject = ?')
119
+ params.push(subject)
120
+ }
121
+
122
+ if (predicate) {
123
+ conditions.push('predicate = ?')
124
+ params.push(predicate)
125
+ }
126
+
127
+ if (min_confidence > 0) {
128
+ conditions.push('confidence >= ?')
129
+ params.push(min_confidence)
130
+ }
131
+
132
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
133
+ const sql = `
134
+ SELECT id, subject, predicate, object, valid_from, valid_to, confidence, last_updated, metadata
135
+ FROM temporal_facts
136
+ ${where}
137
+ ORDER BY valid_from DESC
138
+ `
139
+
140
+ const rows = await all_async(sql, params)
141
+ return rows.map(row => ({
142
+ id: row.id,
143
+ subject: row.subject,
144
+ predicate: row.predicate,
145
+ object: row.object,
146
+ valid_from: new Date(row.valid_from),
147
+ valid_to: row.valid_to ? new Date(row.valid_to) : null,
148
+ confidence: row.confidence,
149
+ last_updated: new Date(row.last_updated),
150
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined
151
+ }))
152
+ }
153
+
154
+
155
+ export const find_conflicting_facts = async (
156
+ subject: string,
157
+ predicate: string,
158
+ at?: Date
159
+ ): Promise<TemporalFact[]> => {
160
+ const timestamp = at ? at.getTime() : Date.now()
161
+
162
+ const rows = await all_async(`
163
+ SELECT id, subject, predicate, object, valid_from, valid_to, confidence, last_updated, metadata
164
+ FROM temporal_facts
165
+ WHERE subject = ? AND predicate = ?
166
+ AND (valid_from <= ? AND (valid_to IS NULL OR valid_to >= ?))
167
+ ORDER BY confidence DESC
168
+ `, [subject, predicate, timestamp, timestamp])
169
+
170
+ return rows.map(row => ({
171
+ id: row.id,
172
+ subject: row.subject,
173
+ predicate: row.predicate,
174
+ object: row.object,
175
+ valid_from: new Date(row.valid_from),
176
+ valid_to: row.valid_to ? new Date(row.valid_to) : null,
177
+ confidence: row.confidence,
178
+ last_updated: new Date(row.last_updated),
179
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined
180
+ }))
181
+ }
182
+
183
+
184
+ export const get_facts_by_subject = async (
185
+ subject: string,
186
+ at?: Date,
187
+ include_historical: boolean = false
188
+ ): Promise<TemporalFact[]> => {
189
+ let sql: string
190
+ let params: any[]
191
+
192
+ if (include_historical) {
193
+ sql = `
194
+ SELECT id, subject, predicate, object, valid_from, valid_to, confidence, last_updated, metadata
195
+ FROM temporal_facts
196
+ WHERE subject = ?
197
+ ORDER BY predicate ASC, valid_from DESC
198
+ `
199
+ params = [subject]
200
+ } else {
201
+ const timestamp = at ? at.getTime() : Date.now()
202
+ sql = `
203
+ SELECT id, subject, predicate, object, valid_from, valid_to, confidence, last_updated, metadata
204
+ FROM temporal_facts
205
+ WHERE subject = ?
206
+ AND (valid_from <= ? AND (valid_to IS NULL OR valid_to >= ?))
207
+ ORDER BY predicate ASC, confidence DESC
208
+ `
209
+ params = [subject, timestamp, timestamp]
210
+ }
211
+
212
+ const rows = await all_async(sql, params)
213
+ return rows.map(row => ({
214
+ id: row.id,
215
+ subject: row.subject,
216
+ predicate: row.predicate,
217
+ object: row.object,
218
+ valid_from: new Date(row.valid_from),
219
+ valid_to: row.valid_to ? new Date(row.valid_to) : null,
220
+ confidence: row.confidence,
221
+ last_updated: new Date(row.last_updated),
222
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined
223
+ }))
224
+ }
225
+
226
+
227
+ export const search_facts = async (
228
+ pattern: string,
229
+ field: 'subject' | 'predicate' | 'object' = 'subject',
230
+ at?: Date
231
+ ): Promise<TemporalFact[]> => {
232
+ const timestamp = at ? at.getTime() : Date.now()
233
+ const search_pattern = `%${pattern}%`
234
+
235
+ const sql = `
236
+ SELECT id, subject, predicate, object, valid_from, valid_to, confidence, last_updated, metadata
237
+ FROM temporal_facts
238
+ WHERE ${field} LIKE ?
239
+ AND (valid_from <= ? AND (valid_to IS NULL OR valid_to >= ?))
240
+ ORDER BY confidence DESC, valid_from DESC
241
+ LIMIT 100
242
+ `
243
+
244
+ const rows = await all_async(sql, [search_pattern, timestamp, timestamp])
245
+ return rows.map(row => ({
246
+ id: row.id,
247
+ subject: row.subject,
248
+ predicate: row.predicate,
249
+ object: row.object,
250
+ valid_from: new Date(row.valid_from),
251
+ valid_to: row.valid_to ? new Date(row.valid_to) : null,
252
+ confidence: row.confidence,
253
+ last_updated: new Date(row.last_updated),
254
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined
255
+ }))
256
+ }
257
+
258
+
259
+ export const get_related_facts = async (
260
+ fact_id: string,
261
+ relation_type?: string,
262
+ at?: Date
263
+ ): Promise<Array<{ fact: TemporalFact; relation: string; weight: number }>> => {
264
+ const timestamp = at ? at.getTime() : Date.now()
265
+ const conditions = ['(e.valid_from <= ? AND (e.valid_to IS NULL OR e.valid_to >= ?))']
266
+ const params: any[] = [timestamp, timestamp]
267
+
268
+ if (relation_type) {
269
+ conditions.push('e.relation_type = ?')
270
+ params.push(relation_type)
271
+ }
272
+
273
+ const sql = `
274
+ SELECT f.*, e.relation_type, e.weight
275
+ FROM temporal_edges e
276
+ JOIN temporal_facts f ON e.target_id = f.id
277
+ WHERE e.source_id = ?
278
+ AND ${conditions.join(' AND ')}
279
+ AND (f.valid_from <= ? AND (f.valid_to IS NULL OR f.valid_to >= ?))
280
+ ORDER BY e.weight DESC, f.confidence DESC
281
+ `
282
+
283
+ const rows = await all_async(sql, [fact_id, ...params, timestamp, timestamp])
284
+ return rows.map(row => ({
285
+ fact: {
286
+ id: row.id,
287
+ subject: row.subject,
288
+ predicate: row.predicate,
289
+ object: row.object,
290
+ valid_from: new Date(row.valid_from),
291
+ valid_to: row.valid_to ? new Date(row.valid_to) : null,
292
+ confidence: row.confidence,
293
+ last_updated: new Date(row.last_updated),
294
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined
295
+ },
296
+ relation: row.relation_type,
297
+ weight: row.weight
298
+ }))
299
+ }
@@ -0,0 +1,156 @@
1
+ import { run_async, get_async, all_async } from '../core/db'
2
+ import { TemporalFact, TemporalEdge } from './types'
3
+ import { randomUUID } from 'crypto'
4
+
5
+ export const insert_fact = async (
6
+ subject: string,
7
+ predicate: string,
8
+ object: string,
9
+ valid_from: Date = new Date(),
10
+ confidence: number = 1.0,
11
+ metadata?: Record<string, any>
12
+ ): Promise<string> => {
13
+ const id = randomUUID()
14
+ const now = Date.now()
15
+ const valid_from_ts = valid_from.getTime()
16
+
17
+ const existing = await all_async(`
18
+ SELECT id, valid_from FROM temporal_facts
19
+ WHERE subject = ? AND predicate = ? AND valid_to IS NULL
20
+ ORDER BY valid_from DESC
21
+ `, [subject, predicate])
22
+
23
+ for (const old of existing) {
24
+ if (old.valid_from < valid_from_ts) {
25
+ await run_async(`UPDATE temporal_facts SET valid_to = ? WHERE id = ?`, [valid_from_ts - 1, old.id])
26
+ console.error(`[TEMPORAL] Closed fact ${old.id} at ${new Date(valid_from_ts - 1).toISOString()}`) // Use stderr for MCP compatibility
27
+ }
28
+ }
29
+
30
+ await run_async(`
31
+ INSERT INTO temporal_facts (id, subject, predicate, object, valid_from, valid_to, confidence, last_updated, metadata)
32
+ VALUES (?, ?, ?, ?, ?, NULL, ?, ?, ?)
33
+ `, [id, subject, predicate, object, valid_from_ts, confidence, now, metadata ? JSON.stringify(metadata) : null])
34
+
35
+ console.error(`[TEMPORAL] Inserted fact: ${subject} ${predicate} ${object} (from ${valid_from.toISOString()}, confidence=${confidence})`) // Use stderr for MCP compatibility
36
+ return id
37
+ }
38
+
39
+ export const update_fact = async (id: string, confidence?: number, metadata?: Record<string, any>): Promise<void> => {
40
+ const updates: string[] = []
41
+ const params: any[] = []
42
+
43
+ if (confidence !== undefined) {
44
+ updates.push('confidence = ?')
45
+ params.push(confidence)
46
+ }
47
+
48
+ if (metadata !== undefined) {
49
+ updates.push('metadata = ?')
50
+ params.push(JSON.stringify(metadata))
51
+ }
52
+
53
+ updates.push('last_updated = ?')
54
+ params.push(Date.now())
55
+
56
+ params.push(id)
57
+
58
+ if (updates.length > 0) {
59
+ await run_async(`UPDATE temporal_facts SET ${updates.join(', ')} WHERE id = ?`, params)
60
+ console.error(`[TEMPORAL] Updated fact ${id}`) // Use stderr for MCP compatibility
61
+ }
62
+ }
63
+
64
+ export const invalidate_fact = async (id: string, valid_to: Date = new Date()): Promise<void> => {
65
+ await run_async(`UPDATE temporal_facts SET valid_to = ?, last_updated = ? WHERE id = ?`, [valid_to.getTime(), Date.now(), id])
66
+ console.error(`[TEMPORAL] Invalidated fact ${id} at ${valid_to.toISOString()}`) // Use stderr for MCP compatibility
67
+ }
68
+
69
+ export const delete_fact = async (id: string): Promise<void> => {
70
+ await run_async(`DELETE FROM temporal_facts WHERE id = ?`, [id])
71
+ console.error(`[TEMPORAL] Deleted fact ${id}`) // Use stderr for MCP compatibility
72
+ }
73
+
74
+ export const insert_edge = async (
75
+ source_id: string,
76
+ target_id: string,
77
+ relation_type: string,
78
+ valid_from: Date = new Date(),
79
+ weight: number = 1.0,
80
+ metadata?: Record<string, any>
81
+ ): Promise<string> => {
82
+ const id = randomUUID()
83
+ const valid_from_ts = valid_from.getTime()
84
+
85
+ await run_async(`
86
+ INSERT INTO temporal_edges (id, source_id, target_id, relation_type, valid_from, valid_to, weight, metadata)
87
+ VALUES (?, ?, ?, ?, ?, NULL, ?, ?)
88
+ `, [id, source_id, target_id, relation_type, valid_from_ts, weight, metadata ? JSON.stringify(metadata) : null])
89
+
90
+ console.log(`[TEMPORAL] Created edge: ${source_id} --[${relation_type}]--> ${target_id}`)
91
+ return id
92
+ }
93
+
94
+ export const invalidate_edge = async (id: string, valid_to: Date = new Date()): Promise<void> => {
95
+ await run_async(`UPDATE temporal_edges SET valid_to = ? WHERE id = ?`, [valid_to.getTime(), id])
96
+ console.log(`[TEMPORAL] Invalidated edge ${id}`)
97
+ }
98
+
99
+ export const batch_insert_facts = async (facts: Array<{
100
+ subject: string
101
+ predicate: string
102
+ object: string
103
+ valid_from?: Date
104
+ confidence?: number
105
+ metadata?: Record<string, any>
106
+ }>): Promise<string[]> => {
107
+ const ids: string[] = []
108
+
109
+ await run_async('BEGIN TRANSACTION')
110
+ try {
111
+ for (const fact of facts) {
112
+ const id = await insert_fact(
113
+ fact.subject,
114
+ fact.predicate,
115
+ fact.object,
116
+ fact.valid_from,
117
+ fact.confidence,
118
+ fact.metadata
119
+ )
120
+ ids.push(id)
121
+ }
122
+ await run_async('COMMIT')
123
+ console.log(`[TEMPORAL] Batch inserted ${ids.length} facts`)
124
+ } catch (error) {
125
+ await run_async('ROLLBACK')
126
+ throw error
127
+ }
128
+
129
+ return ids
130
+ }
131
+
132
+ export const apply_confidence_decay = async (decay_rate: number = 0.01): Promise<number> => {
133
+ const now = Date.now()
134
+ const one_day = 86400000
135
+
136
+ await run_async(`
137
+ UPDATE temporal_facts
138
+ SET confidence = MAX(0.1, confidence * (1 - ? * ((? - valid_from) / ?)))
139
+ WHERE valid_to IS NULL AND confidence > 0.1
140
+ `, [decay_rate, now, one_day])
141
+
142
+ const result = await get_async(`SELECT changes() as changes`) as any
143
+ const changes = result?.changes || 0
144
+ console.log(`[TEMPORAL] Applied confidence decay to ${changes} facts`)
145
+ return changes
146
+ }
147
+
148
+ export const get_active_facts_count = async (): Promise<number> => {
149
+ const result = await get_async(`SELECT COUNT(*) as count FROM temporal_facts WHERE valid_to IS NULL`) as any
150
+ return result?.count || 0
151
+ }
152
+
153
+ export const get_total_facts_count = async (): Promise<number> => {
154
+ const result = await get_async(`SELECT COUNT(*) as count FROM temporal_facts`) as any
155
+ return result?.count || 0
156
+ }