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.
- package/.prettierrc +8 -0
- package/Dockerfile +59 -0
- package/RAW_SKILL.md +219 -0
- package/README.md +277 -0
- package/SKILL.md +58 -0
- package/bin/opm.js +268 -0
- package/data/openmemory.sqlite +0 -0
- package/data/openmemory.sqlite-shm +0 -0
- package/data/openmemory.sqlite-wal +0 -0
- package/dist/ai/graph.js +293 -0
- package/dist/ai/mcp.js +397 -0
- package/dist/cli.js +78 -0
- package/dist/core/cfg.js +87 -0
- package/dist/core/db.js +636 -0
- package/dist/core/memory.js +116 -0
- package/dist/core/migrate.js +227 -0
- package/dist/core/models.js +105 -0
- package/dist/core/telemetry.js +57 -0
- package/dist/core/types.js +2 -0
- package/dist/core/vector/postgres.js +52 -0
- package/dist/core/vector/valkey.js +246 -0
- package/dist/core/vector_store.js +2 -0
- package/dist/index.js +44 -0
- package/dist/memory/decay.js +301 -0
- package/dist/memory/embed.js +675 -0
- package/dist/memory/hsg.js +959 -0
- package/dist/memory/reflect.js +131 -0
- package/dist/memory/user_summary.js +99 -0
- package/dist/migrate.js +9 -0
- package/dist/ops/compress.js +255 -0
- package/dist/ops/dynamics.js +189 -0
- package/dist/ops/extract.js +333 -0
- package/dist/ops/ingest.js +214 -0
- package/dist/server/index.js +109 -0
- package/dist/server/middleware/auth.js +137 -0
- package/dist/server/routes/auth.js +186 -0
- package/dist/server/routes/compression.js +108 -0
- package/dist/server/routes/dashboard.js +399 -0
- package/dist/server/routes/docs.js +241 -0
- package/dist/server/routes/dynamics.js +312 -0
- package/dist/server/routes/ide.js +280 -0
- package/dist/server/routes/index.js +33 -0
- package/dist/server/routes/keys.js +132 -0
- package/dist/server/routes/langgraph.js +61 -0
- package/dist/server/routes/memory.js +213 -0
- package/dist/server/routes/sources.js +140 -0
- package/dist/server/routes/system.js +63 -0
- package/dist/server/routes/temporal.js +293 -0
- package/dist/server/routes/users.js +101 -0
- package/dist/server/routes/vercel.js +57 -0
- package/dist/server/server.js +211 -0
- package/dist/server.js +3 -0
- package/dist/sources/base.js +223 -0
- package/dist/sources/github.js +171 -0
- package/dist/sources/google_drive.js +166 -0
- package/dist/sources/google_sheets.js +112 -0
- package/dist/sources/google_slides.js +139 -0
- package/dist/sources/index.js +34 -0
- package/dist/sources/notion.js +165 -0
- package/dist/sources/onedrive.js +143 -0
- package/dist/sources/web_crawler.js +166 -0
- package/dist/temporal_graph/index.js +20 -0
- package/dist/temporal_graph/query.js +240 -0
- package/dist/temporal_graph/store.js +116 -0
- package/dist/temporal_graph/timeline.js +241 -0
- package/dist/temporal_graph/types.js +2 -0
- package/dist/utils/chunking.js +60 -0
- package/dist/utils/index.js +31 -0
- package/dist/utils/keyword.js +94 -0
- package/dist/utils/text.js +120 -0
- package/nodemon.json +7 -0
- package/package.json +50 -0
- package/references/api_reference.md +66 -0
- package/references/examples.md +45 -0
- package/src/ai/graph.ts +363 -0
- package/src/ai/mcp.ts +494 -0
- package/src/cli.ts +94 -0
- package/src/core/cfg.ts +110 -0
- package/src/core/db.ts +1052 -0
- package/src/core/memory.ts +99 -0
- package/src/core/migrate.ts +302 -0
- package/src/core/models.ts +107 -0
- package/src/core/telemetry.ts +47 -0
- package/src/core/types.ts +130 -0
- package/src/core/vector/postgres.ts +61 -0
- package/src/core/vector/valkey.ts +261 -0
- package/src/core/vector_store.ts +9 -0
- package/src/index.ts +5 -0
- package/src/memory/decay.ts +427 -0
- package/src/memory/embed.ts +707 -0
- package/src/memory/hsg.ts +1245 -0
- package/src/memory/reflect.ts +158 -0
- package/src/memory/user_summary.ts +110 -0
- package/src/migrate.ts +8 -0
- package/src/ops/compress.ts +296 -0
- package/src/ops/dynamics.ts +272 -0
- package/src/ops/extract.ts +360 -0
- package/src/ops/ingest.ts +286 -0
- package/src/server/index.ts +159 -0
- package/src/server/middleware/auth.ts +156 -0
- package/src/server/routes/auth.ts +223 -0
- package/src/server/routes/compression.ts +106 -0
- package/src/server/routes/dashboard.ts +420 -0
- package/src/server/routes/docs.ts +380 -0
- package/src/server/routes/dynamics.ts +516 -0
- package/src/server/routes/ide.ts +283 -0
- package/src/server/routes/index.ts +32 -0
- package/src/server/routes/keys.ts +131 -0
- package/src/server/routes/langgraph.ts +71 -0
- package/src/server/routes/memory.ts +440 -0
- package/src/server/routes/sources.ts +111 -0
- package/src/server/routes/system.ts +68 -0
- package/src/server/routes/temporal.ts +335 -0
- package/src/server/routes/users.ts +111 -0
- package/src/server/routes/vercel.ts +55 -0
- package/src/server/server.js +215 -0
- package/src/server.ts +1 -0
- package/src/sources/base.ts +257 -0
- package/src/sources/github.ts +156 -0
- package/src/sources/google_drive.ts +144 -0
- package/src/sources/google_sheets.ts +85 -0
- package/src/sources/google_slides.ts +115 -0
- package/src/sources/index.ts +19 -0
- package/src/sources/notion.ts +148 -0
- package/src/sources/onedrive.ts +131 -0
- package/src/sources/web_crawler.ts +161 -0
- package/src/temporal_graph/index.ts +4 -0
- package/src/temporal_graph/query.ts +299 -0
- package/src/temporal_graph/store.ts +156 -0
- package/src/temporal_graph/timeline.ts +319 -0
- package/src/temporal_graph/types.ts +41 -0
- package/src/utils/chunking.ts +66 -0
- package/src/utils/index.ts +25 -0
- package/src/utils/keyword.ts +137 -0
- package/src/utils/text.ts +115 -0
- package/tests/test_api_workspace_management.ts +413 -0
- package/tests/test_bulk_delete.ts +267 -0
- package/tests/test_omnibus.ts +166 -0
- package/tests/test_workspace_management.ts +278 -0
- package/tests/verify.ts +104 -0
- 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,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
|
+
}
|