shieldcortex 3.0.3 → 3.0.4
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/dashboard/.next/standalone/dashboard/.next/BUILD_ID +1 -1
- package/dashboard/.next/standalone/dashboard/.next/build-manifest.json +2 -2
- package/dashboard/.next/standalone/dashboard/.next/prerender-manifest.json +3 -3
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.rsc +3 -3
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/page/react-loadable-manifest.json +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/chunks/ssr/dashboard_3051539d._.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.json +1 -1
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/313c0d327bbf244a.js +9 -0
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/{fa5217550a8ab9a6.js → 49c1cec591af1460.js} +2 -2
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/{f69fd1c5e71fbbfd.js → ca21f348cb163905.js} +1 -1
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/f4ca424319f58dc7.css +3 -0
- package/dist/api/routes/admin.d.ts +12 -0
- package/dist/api/routes/admin.js +502 -0
- package/dist/api/routes/graph.d.ts +4 -0
- package/dist/api/routes/graph.js +333 -0
- package/dist/api/routes/incidents.d.ts +2 -0
- package/dist/api/routes/incidents.js +32 -0
- package/dist/api/routes/memories.d.ts +4 -0
- package/dist/api/routes/memories.js +659 -0
- package/dist/api/routes/recall.d.ts +4 -0
- package/dist/api/routes/recall.js +36 -0
- package/dist/api/routes/system.d.ts +9 -0
- package/dist/api/routes/system.js +201 -0
- package/dist/api/visualization-server.js +31 -1913
- package/dist/memory/search.d.ts +37 -0
- package/dist/memory/search.js +143 -0
- package/dist/memory/store.js +2 -166
- package/dist/tools/forget.d.ts +2 -2
- package/dist/tools/recall.d.ts +2 -2
- package/hooks/openclaw/cortex-memory/handler.ts +5 -141
- package/hooks/openclaw/cortex-memory/runtime.mjs +129 -0
- package/package.json +8 -4
- package/plugins/openclaw/dist/index.js +5 -39
- package/scripts/run-jest.mjs +25 -1
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/be6970da20a17c0b.js +0 -9
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/e63d2228780629dd.css +0 -3
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/_tsc.js +0 -133818
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/_tsserver.js +0 -659
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/_typingsInstaller.js +0 -222
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/de/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/es/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/it/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tsc.js +0 -8
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tsserver.js +0 -8
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tsserverlibrary.js +0 -21
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/typesMap.json +0 -497
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/typescript.js +0 -200276
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/typingsInstaller.js +0 -8
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/watchGuard.js +0 -53
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/package.json +0 -120
- package/scripts/start-dashboard.sh +0 -41
- package/scripts/stop-dashboard.sh +0 -21
- /package/dashboard/.next/standalone/dashboard/.next/static/{THy6JENQ0c1sq6jQhvIDp → BEvyMAX62LQMyt5iSb-F9}/_buildManifest.js +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{THy6JENQ0c1sq6jQhvIDp → BEvyMAX62LQMyt5iSb-F9}/_clientMiddlewareManifest.json +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{THy6JENQ0c1sq6jQhvIDp → BEvyMAX62LQMyt5iSb-F9}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { getDatabase } from '../../database/init.js';
|
|
2
|
+
function parseAliases(raw) {
|
|
3
|
+
try {
|
|
4
|
+
return JSON.parse(raw || '[]');
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function registerGraphRoutes(app, requireNotLocked) {
|
|
11
|
+
app.get('/api/graph/entities', requireNotLocked, (req, res) => {
|
|
12
|
+
try {
|
|
13
|
+
const db = getDatabase();
|
|
14
|
+
const type = typeof req.query.type === 'string' ? req.query.type : undefined;
|
|
15
|
+
const minMentions = typeof req.query.minMentions === 'string' ? parseInt(req.query.minMentions, 10) : 0;
|
|
16
|
+
const limit = typeof req.query.limit === 'string' ? Math.min(parseInt(req.query.limit, 10), 500) : 100;
|
|
17
|
+
const offset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : 0;
|
|
18
|
+
let whereClause = 'WHERE 1=1';
|
|
19
|
+
const params = [];
|
|
20
|
+
if (type) {
|
|
21
|
+
whereClause += ' AND type = ?';
|
|
22
|
+
params.push(type);
|
|
23
|
+
}
|
|
24
|
+
if (minMentions > 0) {
|
|
25
|
+
whereClause += ' AND memory_count >= ?';
|
|
26
|
+
params.push(minMentions);
|
|
27
|
+
}
|
|
28
|
+
const total = db.prepare(`SELECT COUNT(*) as count FROM entities ${whereClause}`).get(...params).count;
|
|
29
|
+
const rows = db.prepare(`SELECT * FROM entities ${whereClause} ORDER BY memory_count DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
30
|
+
res.json({
|
|
31
|
+
entities: rows.map((row) => ({
|
|
32
|
+
id: row.id,
|
|
33
|
+
name: row.name,
|
|
34
|
+
type: row.type,
|
|
35
|
+
memoryCount: row.memory_count ?? 0,
|
|
36
|
+
aliases: parseAliases(row.aliases),
|
|
37
|
+
createdAt: row.created_at,
|
|
38
|
+
updatedAt: row.updated_at,
|
|
39
|
+
})),
|
|
40
|
+
total,
|
|
41
|
+
offset,
|
|
42
|
+
limit,
|
|
43
|
+
hasMore: offset + limit < total,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
res.status(500).json({ error: error.message });
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
app.get('/api/graph/entities/:id/triples', requireNotLocked, (req, res) => {
|
|
51
|
+
try {
|
|
52
|
+
const db = getDatabase();
|
|
53
|
+
const id = parseInt(req.params.id, 10);
|
|
54
|
+
if (Number.isNaN(id)) {
|
|
55
|
+
return res.status(400).json({ error: 'Invalid entity ID' });
|
|
56
|
+
}
|
|
57
|
+
const rows = db.prepare(`
|
|
58
|
+
SELECT t.*, s.name as subject_name, s.type as subject_type,
|
|
59
|
+
o.name as object_name, o.type as object_type
|
|
60
|
+
FROM triples t
|
|
61
|
+
JOIN entities s ON s.id = t.subject_id
|
|
62
|
+
JOIN entities o ON o.id = t.object_id
|
|
63
|
+
WHERE t.subject_id = ? OR t.object_id = ?
|
|
64
|
+
ORDER BY t.created_at DESC
|
|
65
|
+
`).all(id, id);
|
|
66
|
+
res.json({ triples: rows });
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
res.status(500).json({ error: error.message });
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
app.get('/api/graph/entities/:id/memories', requireNotLocked, (req, res) => {
|
|
73
|
+
try {
|
|
74
|
+
const db = getDatabase();
|
|
75
|
+
const id = parseInt(req.params.id, 10);
|
|
76
|
+
if (Number.isNaN(id)) {
|
|
77
|
+
return res.status(400).json({ error: 'Invalid entity ID' });
|
|
78
|
+
}
|
|
79
|
+
const rows = db.prepare(`
|
|
80
|
+
SELECT m.id, m.title, m.type, m.category, m.salience, m.created_at
|
|
81
|
+
FROM memories m
|
|
82
|
+
JOIN memory_entities me ON me.memory_id = m.id
|
|
83
|
+
WHERE me.entity_id = ?
|
|
84
|
+
ORDER BY m.salience DESC, m.created_at DESC
|
|
85
|
+
LIMIT 50
|
|
86
|
+
`).all(id);
|
|
87
|
+
res.json({ memories: rows });
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
res.status(500).json({ error: error.message });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
app.get('/api/graph/entities/:id/neighbourhood', requireNotLocked, (req, res) => {
|
|
94
|
+
try {
|
|
95
|
+
const db = getDatabase();
|
|
96
|
+
const id = parseInt(req.params.id, 10);
|
|
97
|
+
if (Number.isNaN(id)) {
|
|
98
|
+
return res.status(400).json({ error: 'Invalid entity ID' });
|
|
99
|
+
}
|
|
100
|
+
const focal = db.prepare('SELECT id, name, type, memory_count as memoryCount, aliases FROM entities WHERE id = ?').get(id);
|
|
101
|
+
if (!focal) {
|
|
102
|
+
return res.status(404).json({ error: 'Entity not found' });
|
|
103
|
+
}
|
|
104
|
+
focal.aliases = parseAliases(focal.aliases);
|
|
105
|
+
const triplesAll = db.prepare(`
|
|
106
|
+
SELECT t.id, t.subject_id, t.object_id, t.predicate,
|
|
107
|
+
s.name as subject_name, s.type as subject_type, s.memory_count as subject_count,
|
|
108
|
+
o.name as object_name, o.type as object_type, o.memory_count as object_count
|
|
109
|
+
FROM triples t
|
|
110
|
+
JOIN entities s ON s.id = t.subject_id
|
|
111
|
+
JOIN entities o ON o.id = t.object_id
|
|
112
|
+
WHERE (t.subject_id = ? OR t.object_id = ?)
|
|
113
|
+
ORDER BY
|
|
114
|
+
CASE WHEN t.predicate != 'related_to' THEN 0 ELSE 1 END,
|
|
115
|
+
CASE WHEN t.subject_id = ? THEN o.memory_count ELSE s.memory_count END DESC
|
|
116
|
+
`).all(id, id, id);
|
|
117
|
+
const neighbourIds = new Map();
|
|
118
|
+
const meaningfulTriples = [];
|
|
119
|
+
const relatedToTriples = [];
|
|
120
|
+
for (const triple of triplesAll) {
|
|
121
|
+
const neighbourId = triple.subject_id === id ? triple.object_id : triple.subject_id;
|
|
122
|
+
const count = triple.subject_id === id ? triple.object_count : triple.subject_count;
|
|
123
|
+
if (neighbourId === id)
|
|
124
|
+
continue;
|
|
125
|
+
if (triple.predicate !== 'related_to') {
|
|
126
|
+
meaningfulTriples.push(triple);
|
|
127
|
+
if (!neighbourIds.has(neighbourId)) {
|
|
128
|
+
neighbourIds.set(neighbourId, { predicate: triple.predicate, count });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
relatedToTriples.push(triple);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const triple of relatedToTriples) {
|
|
136
|
+
if (neighbourIds.size >= 25)
|
|
137
|
+
break;
|
|
138
|
+
const neighbourId = triple.subject_id === id ? triple.object_id : triple.subject_id;
|
|
139
|
+
const count = triple.subject_id === id ? triple.object_count : triple.subject_count;
|
|
140
|
+
if (!neighbourIds.has(neighbourId)) {
|
|
141
|
+
neighbourIds.set(neighbourId, { predicate: 'related_to', count });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const includedTriples = [
|
|
145
|
+
...meaningfulTriples.filter((triple) => {
|
|
146
|
+
const neighbourId = triple.subject_id === id ? triple.object_id : triple.subject_id;
|
|
147
|
+
return neighbourIds.has(neighbourId);
|
|
148
|
+
}),
|
|
149
|
+
...relatedToTriples.filter((triple) => {
|
|
150
|
+
const neighbourId = triple.subject_id === id ? triple.object_id : triple.subject_id;
|
|
151
|
+
return neighbourIds.has(neighbourId);
|
|
152
|
+
}),
|
|
153
|
+
];
|
|
154
|
+
const seenTriples = new Set();
|
|
155
|
+
const uniqueTriples = includedTriples.filter((triple) => {
|
|
156
|
+
if (seenTriples.has(triple.id))
|
|
157
|
+
return false;
|
|
158
|
+
seenTriples.add(triple.id);
|
|
159
|
+
return true;
|
|
160
|
+
});
|
|
161
|
+
const neighbours = [];
|
|
162
|
+
if (neighbourIds.size > 0) {
|
|
163
|
+
const ids = [...neighbourIds.keys()];
|
|
164
|
+
const placeholders = ids.map(() => '?').join(',');
|
|
165
|
+
const rows = db.prepare(`
|
|
166
|
+
SELECT id, name, type, memory_count as memoryCount, aliases
|
|
167
|
+
FROM entities WHERE id IN (${placeholders})
|
|
168
|
+
`).all(...ids);
|
|
169
|
+
for (const row of rows) {
|
|
170
|
+
row.aliases = parseAliases(row.aliases);
|
|
171
|
+
neighbours.push(row);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
res.json({
|
|
175
|
+
focal,
|
|
176
|
+
neighbours,
|
|
177
|
+
triples: uniqueTriples,
|
|
178
|
+
totalConnections: triplesAll.length,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
res.status(500).json({ error: error.message });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
app.get('/api/graph/triples', requireNotLocked, (req, res) => {
|
|
186
|
+
try {
|
|
187
|
+
const db = getDatabase();
|
|
188
|
+
const predicate = typeof req.query.predicate === 'string' ? req.query.predicate : undefined;
|
|
189
|
+
const limit = typeof req.query.limit === 'string' ? Math.min(parseInt(req.query.limit, 10), 10000) : 100;
|
|
190
|
+
const offset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : 0;
|
|
191
|
+
let whereClause = '';
|
|
192
|
+
const params = [];
|
|
193
|
+
if (predicate) {
|
|
194
|
+
whereClause = 'WHERE t.predicate = ?';
|
|
195
|
+
params.push(predicate);
|
|
196
|
+
}
|
|
197
|
+
const total = db.prepare(`SELECT COUNT(*) as count FROM triples t ${whereClause}`).get(...params).count;
|
|
198
|
+
const triples = db.prepare(`
|
|
199
|
+
SELECT t.*, s.name as subject_name, s.type as subject_type,
|
|
200
|
+
o.name as object_name, o.type as object_type
|
|
201
|
+
FROM triples t
|
|
202
|
+
JOIN entities s ON s.id = t.subject_id
|
|
203
|
+
JOIN entities o ON o.id = t.object_id
|
|
204
|
+
${whereClause}
|
|
205
|
+
ORDER BY t.created_at DESC
|
|
206
|
+
LIMIT ? OFFSET ?
|
|
207
|
+
`).all(...params, limit, offset);
|
|
208
|
+
res.json({ triples, total, offset, limit, hasMore: offset + limit < total });
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
res.status(500).json({ error: error.message });
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
app.get('/api/graph/search', requireNotLocked, (req, res) => {
|
|
215
|
+
try {
|
|
216
|
+
const db = getDatabase();
|
|
217
|
+
const q = typeof req.query.q === 'string' ? req.query.q : '';
|
|
218
|
+
if (!q) {
|
|
219
|
+
return res.status(400).json({ error: 'Query parameter "q" is required' });
|
|
220
|
+
}
|
|
221
|
+
const rows = db.prepare(`SELECT * FROM entities WHERE LOWER(name) LIKE ? ORDER BY memory_count DESC LIMIT 20`).all(`%${q.toLowerCase()}%`);
|
|
222
|
+
res.json({
|
|
223
|
+
entities: rows.map((row) => ({
|
|
224
|
+
id: row.id,
|
|
225
|
+
name: row.name,
|
|
226
|
+
type: row.type,
|
|
227
|
+
memoryCount: row.memory_count ?? 0,
|
|
228
|
+
aliases: parseAliases(row.aliases),
|
|
229
|
+
})),
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
res.status(500).json({ error: error.message });
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
app.get('/api/graph/paths', requireNotLocked, (req, res) => {
|
|
237
|
+
try {
|
|
238
|
+
const db = getDatabase();
|
|
239
|
+
const fromName = typeof req.query.from === 'string' ? req.query.from : '';
|
|
240
|
+
const toName = typeof req.query.to === 'string' ? req.query.to : '';
|
|
241
|
+
if (!fromName || !toName) {
|
|
242
|
+
return res.status(400).json({ error: 'Both "from" and "to" query parameters are required' });
|
|
243
|
+
}
|
|
244
|
+
const fromRow = db.prepare('SELECT * FROM entities WHERE LOWER(name) = LOWER(?)').get(fromName);
|
|
245
|
+
if (!fromRow) {
|
|
246
|
+
return res.status(404).json({ error: `Entity "${fromName}" not found` });
|
|
247
|
+
}
|
|
248
|
+
const toRow = db.prepare('SELECT * FROM entities WHERE LOWER(name) = LOWER(?)').get(toName);
|
|
249
|
+
if (!toRow) {
|
|
250
|
+
return res.status(404).json({ error: `Entity "${toName}" not found` });
|
|
251
|
+
}
|
|
252
|
+
if (fromRow.id === toRow.id) {
|
|
253
|
+
return res.json({ path: [{ entity: fromRow.name, predicate: '(self)' }], sourceMemories: [] });
|
|
254
|
+
}
|
|
255
|
+
const maxDepth = 4;
|
|
256
|
+
const visited = new Map();
|
|
257
|
+
visited.set(fromRow.id, {
|
|
258
|
+
id: fromRow.id,
|
|
259
|
+
name: fromRow.name,
|
|
260
|
+
parentId: null,
|
|
261
|
+
predicate: '',
|
|
262
|
+
sourceMemoryId: null,
|
|
263
|
+
});
|
|
264
|
+
let frontier = [fromRow.id];
|
|
265
|
+
let found = false;
|
|
266
|
+
for (let depth = 0; depth < maxDepth && !found; depth++) {
|
|
267
|
+
const nextFrontier = [];
|
|
268
|
+
for (const nodeId of frontier) {
|
|
269
|
+
const outgoing = db.prepare('SELECT t.object_id as next_id, t.predicate, t.source_memory_id, e.name FROM triples t JOIN entities e ON e.id = t.object_id WHERE t.subject_id = ?').all(nodeId);
|
|
270
|
+
for (const row of outgoing) {
|
|
271
|
+
if (!visited.has(row.next_id)) {
|
|
272
|
+
visited.set(row.next_id, {
|
|
273
|
+
id: row.next_id,
|
|
274
|
+
name: row.name,
|
|
275
|
+
parentId: nodeId,
|
|
276
|
+
predicate: row.predicate,
|
|
277
|
+
sourceMemoryId: row.source_memory_id,
|
|
278
|
+
});
|
|
279
|
+
nextFrontier.push(row.next_id);
|
|
280
|
+
if (row.next_id === toRow.id) {
|
|
281
|
+
found = true;
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (found)
|
|
287
|
+
break;
|
|
288
|
+
const incoming = db.prepare('SELECT t.subject_id as next_id, t.predicate, t.source_memory_id, e.name FROM triples t JOIN entities e ON e.id = t.subject_id WHERE t.object_id = ?').all(nodeId);
|
|
289
|
+
for (const row of incoming) {
|
|
290
|
+
if (!visited.has(row.next_id)) {
|
|
291
|
+
visited.set(row.next_id, {
|
|
292
|
+
id: row.next_id,
|
|
293
|
+
name: row.name,
|
|
294
|
+
parentId: nodeId,
|
|
295
|
+
predicate: `~${row.predicate}`,
|
|
296
|
+
sourceMemoryId: row.source_memory_id,
|
|
297
|
+
});
|
|
298
|
+
nextFrontier.push(row.next_id);
|
|
299
|
+
if (row.next_id === toRow.id) {
|
|
300
|
+
found = true;
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (found)
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
frontier = nextFrontier;
|
|
309
|
+
if (frontier.length === 0)
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
if (!found) {
|
|
313
|
+
return res.json({ path: [], sourceMemories: [], message: 'No path found' });
|
|
314
|
+
}
|
|
315
|
+
const path = [];
|
|
316
|
+
const sourceMemoryIds = [];
|
|
317
|
+
let current = visited.get(toRow.id);
|
|
318
|
+
while (current) {
|
|
319
|
+
path.unshift({ entity: current.name, predicate: current.predicate });
|
|
320
|
+
if (current.sourceMemoryId)
|
|
321
|
+
sourceMemoryIds.push(current.sourceMemoryId);
|
|
322
|
+
current = current.parentId !== null ? visited.get(current.parentId) : undefined;
|
|
323
|
+
}
|
|
324
|
+
const sourceMemories = sourceMemoryIds.length > 0
|
|
325
|
+
? db.prepare(`SELECT id, title FROM memories WHERE id IN (${sourceMemoryIds.map(() => '?').join(',')})`).all(...sourceMemoryIds)
|
|
326
|
+
: [];
|
|
327
|
+
res.json({ path, sourceMemories });
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
res.status(500).json({ error: error.message });
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { queryIncidentReplay } from '../../defence/audit/queries.js';
|
|
2
|
+
export function registerIncidentRoutes(app) {
|
|
3
|
+
app.get('/api/v1/incidents/replay', (req, res) => {
|
|
4
|
+
try {
|
|
5
|
+
const limit = Math.min(parseInt(req.query.limit, 10) || 200, 500);
|
|
6
|
+
const startTime = typeof req.query.startTime === 'string' ? req.query.startTime : undefined;
|
|
7
|
+
const endTime = typeof req.query.endTime === 'string' ? req.query.endTime : undefined;
|
|
8
|
+
const project = typeof req.query.project === 'string' ? req.query.project : undefined;
|
|
9
|
+
const sourceIdentifier = typeof req.query.sourceIdentifier === 'string' ? req.query.sourceIdentifier : undefined;
|
|
10
|
+
const memoryId = req.query.memoryId ? parseInt(req.query.memoryId, 10) : undefined;
|
|
11
|
+
const events = queryIncidentReplay({
|
|
12
|
+
startTime,
|
|
13
|
+
endTime,
|
|
14
|
+
project,
|
|
15
|
+
sourceIdentifier,
|
|
16
|
+
memoryId,
|
|
17
|
+
limit,
|
|
18
|
+
});
|
|
19
|
+
res.json({
|
|
20
|
+
events,
|
|
21
|
+
total: events.length,
|
|
22
|
+
coverage: {
|
|
23
|
+
sources: ['defence_audit', 'quarantine', 'events'],
|
|
24
|
+
note: 'Replay is best-effort. Durable audit and quarantine history is complete; generic event coverage depends on the retained events table window.',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
res.status(500).json({ error: error.message });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|