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,440 @@
|
|
|
1
|
+
import { q, vector_store } from "../../core/db";
|
|
2
|
+
import { now, rid, j, p } from "../../utils";
|
|
3
|
+
import {
|
|
4
|
+
add_hsg_memory,
|
|
5
|
+
hsg_query,
|
|
6
|
+
reinforce_memory,
|
|
7
|
+
update_memory,
|
|
8
|
+
} from "../../memory/hsg";
|
|
9
|
+
import { ingestDocument, ingestURL } from "../../ops/ingest";
|
|
10
|
+
import { env } from "../../core/cfg";
|
|
11
|
+
import { update_user_summary } from "../../memory/user_summary";
|
|
12
|
+
import type {
|
|
13
|
+
add_req,
|
|
14
|
+
q_req,
|
|
15
|
+
ingest_req,
|
|
16
|
+
ingest_url_req,
|
|
17
|
+
} from "../../core/types";
|
|
18
|
+
|
|
19
|
+
// Bulk operation interfaces
|
|
20
|
+
interface BulkDeleteRequest {
|
|
21
|
+
ids: string[];
|
|
22
|
+
user_id?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface BulkDeleteResponse {
|
|
26
|
+
deleted: number;
|
|
27
|
+
failed: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// POST /memory/bulk-move - Move memories to different workspace
|
|
31
|
+
// Requirements: 7.1, 7.2
|
|
32
|
+
interface BulkMoveRequest {
|
|
33
|
+
ids: string[];
|
|
34
|
+
target_workspace_id: string | null; // null to orphan memories
|
|
35
|
+
user_id?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface BulkMoveResponse {
|
|
39
|
+
moved: number;
|
|
40
|
+
failed: string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function mem(app: any) {
|
|
44
|
+
app.post("/memory/add", async (req: any, res: any) => {
|
|
45
|
+
const b = req.body as add_req & { memory_key_id?: string };
|
|
46
|
+
if (!b?.content) return res.status(400).json({ err: "content" });
|
|
47
|
+
try {
|
|
48
|
+
// Use memory_key_id from body if provided, otherwise use authenticated key's ID
|
|
49
|
+
const keyId = b.memory_key_id || req.key_id;
|
|
50
|
+
// Use user_id from body if provided, otherwise use authenticated user's ID
|
|
51
|
+
const userId = b.user_id || req.user?.user_id;
|
|
52
|
+
|
|
53
|
+
const m = await add_hsg_memory(
|
|
54
|
+
b.content,
|
|
55
|
+
j(b.tags || []),
|
|
56
|
+
b.metadata,
|
|
57
|
+
userId,
|
|
58
|
+
keyId, // Pass memory key ID
|
|
59
|
+
);
|
|
60
|
+
res.json(m);
|
|
61
|
+
|
|
62
|
+
if (b.user_id) {
|
|
63
|
+
update_user_summary(b.user_id).catch((e) =>
|
|
64
|
+
console.error("[mem] user summary update failed:", e),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
} catch (e: any) {
|
|
68
|
+
res.status(500).json({ err: e.message });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
app.post("/memory/ingest", async (req: any, res: any) => {
|
|
73
|
+
const b = req.body as ingest_req;
|
|
74
|
+
if (!b?.content_type || !b?.data)
|
|
75
|
+
return res.status(400).json({ err: "missing" });
|
|
76
|
+
try {
|
|
77
|
+
const userId = b.user_id || req.user?.user_id;
|
|
78
|
+
const r = await ingestDocument(
|
|
79
|
+
b.content_type,
|
|
80
|
+
b.data,
|
|
81
|
+
b.metadata,
|
|
82
|
+
b.config,
|
|
83
|
+
userId,
|
|
84
|
+
);
|
|
85
|
+
res.json(r);
|
|
86
|
+
} catch (e: any) {
|
|
87
|
+
res.status(500).json({ err: "ingest_fail", msg: e.message });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
app.post("/memory/ingest/url", async (req: any, res: any) => {
|
|
92
|
+
const b = req.body as ingest_url_req;
|
|
93
|
+
if (!b?.url) return res.status(400).json({ err: "no_url" });
|
|
94
|
+
try {
|
|
95
|
+
const userId = b.user_id || req.user?.user_id;
|
|
96
|
+
const r = await ingestURL(b.url, b.metadata, b.config, userId);
|
|
97
|
+
res.json(r);
|
|
98
|
+
} catch (e: any) {
|
|
99
|
+
res.status(500).json({ err: "url_fail", msg: e.message });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
app.post("/memory/query", async (req: any, res: any) => {
|
|
104
|
+
const b = req.body as q_req;
|
|
105
|
+
const k = b.k || 8;
|
|
106
|
+
try {
|
|
107
|
+
let userId = b.filters?.user_id || b.user_id;
|
|
108
|
+
// Enforce user isolation if authenticated and not admin
|
|
109
|
+
if (req.user && !req.user.is_admin) {
|
|
110
|
+
userId = req.user.user_id;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const f = {
|
|
114
|
+
sectors: b.filters?.sector ? [b.filters.sector] : undefined,
|
|
115
|
+
minSalience: b.filters?.min_score,
|
|
116
|
+
user_id: userId,
|
|
117
|
+
startTime: b.filters?.startTime,
|
|
118
|
+
endTime: b.filters?.endTime,
|
|
119
|
+
};
|
|
120
|
+
const m = await hsg_query(b.query, k, f);
|
|
121
|
+
res.json({
|
|
122
|
+
query: b.query,
|
|
123
|
+
matches: m.map((x: any) => ({
|
|
124
|
+
id: x.id,
|
|
125
|
+
content: x.content,
|
|
126
|
+
score: x.score,
|
|
127
|
+
sectors: x.sectors,
|
|
128
|
+
primary_sector: x.primary_sector,
|
|
129
|
+
path: x.path,
|
|
130
|
+
salience: x.salience,
|
|
131
|
+
last_seen_at: x.last_seen_at,
|
|
132
|
+
})),
|
|
133
|
+
});
|
|
134
|
+
} catch (e: any) {
|
|
135
|
+
res.json({ query: b.query, matches: [] });
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
app.post("/memory/reinforce", async (req: any, res: any) => {
|
|
140
|
+
const b = req.body as { id: string; boost?: number };
|
|
141
|
+
if (!b?.id) return res.status(400).json({ err: "id" });
|
|
142
|
+
try {
|
|
143
|
+
await reinforce_memory(b.id, b.boost);
|
|
144
|
+
res.json({ ok: true });
|
|
145
|
+
} catch (e: any) {
|
|
146
|
+
res.status(404).json({ err: "nf" });
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
app.patch("/memory/:id", async (req: any, res: any) => {
|
|
151
|
+
const id = req.params.id;
|
|
152
|
+
const b = req.body as {
|
|
153
|
+
content?: string;
|
|
154
|
+
tags?: string[];
|
|
155
|
+
metadata?: any;
|
|
156
|
+
user_id?: string;
|
|
157
|
+
};
|
|
158
|
+
if (!id) return res.status(400).json({ err: "id" });
|
|
159
|
+
try {
|
|
160
|
+
// Check if memory exists and user has permission
|
|
161
|
+
const m = await q.get_mem.get(id);
|
|
162
|
+
if (!m) return res.status(404).json({ err: "nf" });
|
|
163
|
+
|
|
164
|
+
// Enforce user ownership
|
|
165
|
+
if (req.user && !req.user.is_admin && m.user_id !== req.user.user_id) {
|
|
166
|
+
return res.status(403).json({ err: "forbidden" });
|
|
167
|
+
}
|
|
168
|
+
// Check explicit user_id match if provided (for admin or extra validation)
|
|
169
|
+
if (b.user_id && m.user_id !== b.user_id) {
|
|
170
|
+
return res.status(403).json({ err: "forbidden" });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const r = await update_memory(id, b.content, b.tags, b.metadata);
|
|
174
|
+
res.json(r);
|
|
175
|
+
} catch (e: any) {
|
|
176
|
+
if (e.message.includes("not found")) {
|
|
177
|
+
res.status(404).json({ err: "nf" });
|
|
178
|
+
} else {
|
|
179
|
+
res.status(500).json({ err: "internal" });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
app.get("/memory/all", async (req: any, res: any) => {
|
|
185
|
+
try {
|
|
186
|
+
const u = req.query.u ? parseInt(req.query.u) : 0;
|
|
187
|
+
const l = req.query.l ? parseInt(req.query.l) : 100;
|
|
188
|
+
const s = req.query.sector;
|
|
189
|
+
let user_id = req.query.user_id;
|
|
190
|
+
|
|
191
|
+
// Enforce user isolation if authenticated and not admin
|
|
192
|
+
if (req.user && !req.user.is_admin) {
|
|
193
|
+
user_id = req.user.user_id;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let r;
|
|
197
|
+
if (user_id) {
|
|
198
|
+
// Filter by user_id
|
|
199
|
+
r = await q.all_mem_by_user.all(user_id, l, u);
|
|
200
|
+
} else if (s) {
|
|
201
|
+
// Filter by sector
|
|
202
|
+
r = await q.all_mem_by_sector.all(s, l, u);
|
|
203
|
+
} else {
|
|
204
|
+
// No filter
|
|
205
|
+
r = await q.all_mem.all(l, u);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (req.query.key_id) {
|
|
209
|
+
r = r.filter((x: any) => x.memory_key_id === req.query.key_id);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const i = r.map((x: any) => ({
|
|
213
|
+
id: x.id,
|
|
214
|
+
content: x.content,
|
|
215
|
+
tags: p(x.tags),
|
|
216
|
+
metadata: p(x.meta),
|
|
217
|
+
created_at: x.created_at,
|
|
218
|
+
updated_at: x.updated_at,
|
|
219
|
+
last_seen_at: x.last_seen_at,
|
|
220
|
+
salience: x.salience,
|
|
221
|
+
decay_lambda: x.decay_lambda,
|
|
222
|
+
primary_sector: x.primary_sector,
|
|
223
|
+
version: x.version,
|
|
224
|
+
user_id: x.user_id,
|
|
225
|
+
memory_key_id: x.memory_key_id,
|
|
226
|
+
}));
|
|
227
|
+
res.json({ items: i });
|
|
228
|
+
} catch (e: any) {
|
|
229
|
+
res.status(500).json({ err: "internal" });
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
app.get("/memory/:id", async (req: any, res: any) => {
|
|
234
|
+
try {
|
|
235
|
+
const id = req.params.id;
|
|
236
|
+
const user_id = req.query.user_id;
|
|
237
|
+
const m = await q.get_mem.get(id);
|
|
238
|
+
if (!m) return res.status(404).json({ err: "nf" });
|
|
239
|
+
|
|
240
|
+
// Enforce user ownership
|
|
241
|
+
if (req.user && !req.user.is_admin && m.user_id !== req.user.user_id) {
|
|
242
|
+
return res.status(403).json({ err: "forbidden" });
|
|
243
|
+
}
|
|
244
|
+
// Check explicit user_id match if provided
|
|
245
|
+
if (user_id && m.user_id !== user_id) {
|
|
246
|
+
return res.status(403).json({ err: "forbidden" });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const v = await vector_store.getVectorsById(id);
|
|
250
|
+
const sec = v.map((x: any) => x.sector);
|
|
251
|
+
res.json({
|
|
252
|
+
id: m.id,
|
|
253
|
+
content: m.content,
|
|
254
|
+
primary_sector: m.primary_sector,
|
|
255
|
+
sectors: sec,
|
|
256
|
+
tags: p(m.tags),
|
|
257
|
+
metadata: p(m.meta),
|
|
258
|
+
created_at: m.created_at,
|
|
259
|
+
updated_at: m.updated_at,
|
|
260
|
+
last_seen_at: m.last_seen_at,
|
|
261
|
+
salience: m.salience,
|
|
262
|
+
decay_lambda: m.decay_lambda,
|
|
263
|
+
version: m.version,
|
|
264
|
+
user_id: m.user_id,
|
|
265
|
+
memory_key_id: m.memory_key_id,
|
|
266
|
+
});
|
|
267
|
+
} catch (e: any) {
|
|
268
|
+
res.status(500).json({ err: "internal" });
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
app.delete("/memory/:id", async (req: any, res: any) => {
|
|
273
|
+
try {
|
|
274
|
+
const id = req.params.id;
|
|
275
|
+
const user_id = req.query.user_id || req.body?.user_id;
|
|
276
|
+
const m = await q.get_mem.get(id);
|
|
277
|
+
if (!m) return res.status(404).json({ err: "nf" });
|
|
278
|
+
|
|
279
|
+
// Enforce user ownership
|
|
280
|
+
if (req.user && !req.user.is_admin && m.user_id !== req.user.user_id) {
|
|
281
|
+
return res.status(403).json({ err: "forbidden" });
|
|
282
|
+
}
|
|
283
|
+
// Check explicit user_id match if provided
|
|
284
|
+
if (user_id && m.user_id !== user_id) {
|
|
285
|
+
return res.status(403).json({ err: "forbidden" });
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
await q.del_mem.run(id);
|
|
289
|
+
await vector_store.deleteVectors(id);
|
|
290
|
+
await q.del_waypoints.run(id, id);
|
|
291
|
+
res.json({ ok: true });
|
|
292
|
+
} catch (e: any) {
|
|
293
|
+
res.status(500).json({ err: "internal" });
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// POST /memory/bulk-delete - Delete multiple memories
|
|
298
|
+
// Requirements: 6.2, 6.3
|
|
299
|
+
app.post("/memory/bulk-delete", async (req: any, res: any) => {
|
|
300
|
+
const b = req.body as BulkDeleteRequest;
|
|
301
|
+
|
|
302
|
+
// Validate request body
|
|
303
|
+
if (!b?.ids || !Array.isArray(b.ids)) {
|
|
304
|
+
return res.status(400).json({ err: "ids array required" });
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (b.ids.length === 0) {
|
|
308
|
+
return res.json({ deleted: 0, failed: [] } as BulkDeleteResponse);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const user_id = b.user_id;
|
|
312
|
+
const deleted: string[] = [];
|
|
313
|
+
const failed: string[] = [];
|
|
314
|
+
|
|
315
|
+
// Process each memory individually for ownership verification and partial failure handling
|
|
316
|
+
for (const id of b.ids) {
|
|
317
|
+
try {
|
|
318
|
+
// Check if memory exists
|
|
319
|
+
const m = await q.get_mem.get(id);
|
|
320
|
+
if (!m) {
|
|
321
|
+
failed.push(id);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Enforce user ownership
|
|
326
|
+
if (req.user && !req.user.is_admin && m.user_id !== req.user.user_id) {
|
|
327
|
+
failed.push(id);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
// Check user ownership if explicit user_id is provided
|
|
331
|
+
if (user_id && m.user_id !== user_id) {
|
|
332
|
+
failed.push(id);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Delete memory, vectors, and waypoints
|
|
337
|
+
await q.del_mem.run(id);
|
|
338
|
+
await vector_store.deleteVectors(id);
|
|
339
|
+
await q.del_waypoints.run(id, id);
|
|
340
|
+
deleted.push(id);
|
|
341
|
+
} catch (e: any) {
|
|
342
|
+
// Continue with remaining memories on individual failure
|
|
343
|
+
failed.push(id);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Return partial success response (207 if there are failures, 200 if all succeeded)
|
|
348
|
+
const response: BulkDeleteResponse = {
|
|
349
|
+
deleted: deleted.length,
|
|
350
|
+
failed: failed,
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
if (failed.length > 0 && deleted.length > 0) {
|
|
354
|
+
// Partial success
|
|
355
|
+
return res.status(207).json(response);
|
|
356
|
+
} else if (failed.length > 0 && deleted.length === 0) {
|
|
357
|
+
// All failed
|
|
358
|
+
return res.status(207).json(response);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// All succeeded
|
|
362
|
+
res.json(response);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// POST /memory/bulk-move - Move memories to different workspace
|
|
366
|
+
// Requirements: 7.1, 7.2
|
|
367
|
+
app.post("/memory/bulk-move", async (req: any, res: any) => {
|
|
368
|
+
const b = req.body as BulkMoveRequest;
|
|
369
|
+
|
|
370
|
+
// Validate request body
|
|
371
|
+
if (!b?.ids || !Array.isArray(b.ids)) {
|
|
372
|
+
return res.status(400).json({ err: "ids array required" });
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (b.ids.length === 0) {
|
|
376
|
+
return res.json({ moved: 0, failed: [] } as BulkMoveResponse);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// target_workspace_id can be null (to orphan memories) or a string
|
|
380
|
+
if (b.target_workspace_id !== null && typeof b.target_workspace_id !== 'string') {
|
|
381
|
+
return res.status(400).json({ err: "target_workspace_id must be string or null" });
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const user_id = b.user_id;
|
|
385
|
+
const moved: string[] = [];
|
|
386
|
+
const failed: string[] = [];
|
|
387
|
+
|
|
388
|
+
// Process each memory individually for ownership verification and partial failure handling
|
|
389
|
+
for (const id of b.ids) {
|
|
390
|
+
try {
|
|
391
|
+
// Check if memory exists
|
|
392
|
+
const m = await q.get_mem.get(id);
|
|
393
|
+
if (!m) {
|
|
394
|
+
failed.push(id);
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Enforce user ownership
|
|
399
|
+
if (req.user && !req.user.is_admin && m.user_id !== req.user.user_id) {
|
|
400
|
+
failed.push(id);
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
// Check user ownership if explicit user_id is provided
|
|
404
|
+
if (user_id && m.user_id !== user_id) {
|
|
405
|
+
failed.push(id);
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Update memory_key_id for this memory
|
|
410
|
+
await q.bulk_upd_mem_key.run(
|
|
411
|
+
b.target_workspace_id,
|
|
412
|
+
now(),
|
|
413
|
+
[id],
|
|
414
|
+
m.user_id
|
|
415
|
+
);
|
|
416
|
+
moved.push(id);
|
|
417
|
+
} catch (e: any) {
|
|
418
|
+
// Continue with remaining memories on individual failure
|
|
419
|
+
failed.push(id);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Return partial success response (207 if there are failures, 200 if all succeeded)
|
|
424
|
+
const response: BulkMoveResponse = {
|
|
425
|
+
moved: moved.length,
|
|
426
|
+
failed: failed,
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
if (failed.length > 0 && moved.length > 0) {
|
|
430
|
+
// Partial success
|
|
431
|
+
return res.status(207).json(response);
|
|
432
|
+
} else if (failed.length > 0 && moved.length === 0) {
|
|
433
|
+
// All failed
|
|
434
|
+
return res.status(207).json(response);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// All succeeded
|
|
438
|
+
res.json(response);
|
|
439
|
+
});
|
|
440
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sources webhook routes - ingest data from external sources via HTTP
|
|
3
|
+
*
|
|
4
|
+
* POST /sources/:source/ingest
|
|
5
|
+
* body: { creds: {...}, filters: {...}, user_id?: string }
|
|
6
|
+
*
|
|
7
|
+
* POST /sources/webhook/:source
|
|
8
|
+
* generic webhook endpoint for source-specific payloads
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as sources from "../../sources";
|
|
12
|
+
|
|
13
|
+
export function src(app: any) {
|
|
14
|
+
// list available sources
|
|
15
|
+
app.get("/sources", async (req: any, res: any) => {
|
|
16
|
+
res.json({
|
|
17
|
+
sources: ["github", "notion", "google_drive", "google_sheets", "google_slides", "onedrive", "web_crawler"],
|
|
18
|
+
usage: {
|
|
19
|
+
ingest: "POST /sources/:source/ingest { creds: {}, filters: {}, user_id? }",
|
|
20
|
+
webhook: "POST /sources/webhook/:source (source-specific payload)"
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// ingest from a source
|
|
26
|
+
app.post("/sources/:source/ingest", async (req: any, res: any) => {
|
|
27
|
+
const { source } = req.params;
|
|
28
|
+
const { creds = {}, filters = {}, user_id } = req.body || {};
|
|
29
|
+
|
|
30
|
+
const source_map: Record<string, any> = {
|
|
31
|
+
github: sources.github_source,
|
|
32
|
+
notion: sources.notion_source,
|
|
33
|
+
google_drive: sources.google_drive_source,
|
|
34
|
+
google_sheets: sources.google_sheets_source,
|
|
35
|
+
google_slides: sources.google_slides_source,
|
|
36
|
+
onedrive: sources.onedrive_source,
|
|
37
|
+
web_crawler: sources.web_crawler_source,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (!source_map[source]) {
|
|
41
|
+
return res.status(400).json({ error: `unknown source: ${source}`, available: Object.keys(source_map) });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const src = new source_map[source](user_id);
|
|
46
|
+
await src.connect(creds);
|
|
47
|
+
const ids = await src.ingest_all(filters);
|
|
48
|
+
res.json({ ok: true, ingested: ids.length, memory_ids: ids });
|
|
49
|
+
} catch (e: any) {
|
|
50
|
+
res.status(500).json({ error: e.message });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// webhook endpoint for github events
|
|
55
|
+
app.post("/sources/webhook/github", async (req: any, res: any) => {
|
|
56
|
+
const event_type = req.headers["x-github-event"];
|
|
57
|
+
const payload = req.body;
|
|
58
|
+
|
|
59
|
+
if (!payload) {
|
|
60
|
+
return res.status(400).json({ error: "no payload" });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const { ingestDocument } = await import("../../ops/ingest");
|
|
65
|
+
|
|
66
|
+
// handle different github events
|
|
67
|
+
let content = "";
|
|
68
|
+
let meta: Record<string, any> = { source: "github_webhook", event: event_type };
|
|
69
|
+
|
|
70
|
+
if (event_type === "push") {
|
|
71
|
+
const commits = payload.commits || [];
|
|
72
|
+
content = commits.map((c: any) => `${c.message}\n${c.url}`).join("\n\n");
|
|
73
|
+
meta.repo = payload.repository?.full_name;
|
|
74
|
+
meta.ref = payload.ref;
|
|
75
|
+
} else if (event_type === "issues") {
|
|
76
|
+
content = `[${payload.action}] ${payload.issue?.title}\n${payload.issue?.body || ""}`;
|
|
77
|
+
meta.repo = payload.repository?.full_name;
|
|
78
|
+
meta.issue_number = payload.issue?.number;
|
|
79
|
+
} else if (event_type === "pull_request") {
|
|
80
|
+
content = `[${payload.action}] PR: ${payload.pull_request?.title}\n${payload.pull_request?.body || ""}`;
|
|
81
|
+
meta.repo = payload.repository?.full_name;
|
|
82
|
+
meta.pr_number = payload.pull_request?.number;
|
|
83
|
+
} else {
|
|
84
|
+
content = JSON.stringify(payload, null, 2);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (content) {
|
|
88
|
+
const result = await ingestDocument("text", content, meta);
|
|
89
|
+
res.json({ ok: true, memory_id: result.root_memory_id, event: event_type });
|
|
90
|
+
} else {
|
|
91
|
+
res.json({ ok: true, skipped: true, reason: "no content" });
|
|
92
|
+
}
|
|
93
|
+
} catch (e: any) {
|
|
94
|
+
res.status(500).json({ error: e.message });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// generic webhook for notion
|
|
99
|
+
app.post("/sources/webhook/notion", async (req: any, res: any) => {
|
|
100
|
+
const payload = req.body;
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const { ingestDocument } = await import("../../ops/ingest");
|
|
104
|
+
const content = JSON.stringify(payload, null, 2);
|
|
105
|
+
const result = await ingestDocument("text", content, { source: "notion_webhook" });
|
|
106
|
+
res.json({ ok: true, memory_id: result.root_memory_id });
|
|
107
|
+
} catch (e: any) {
|
|
108
|
+
res.status(500).json({ error: e.message });
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { all_async } from "../../core/db";
|
|
2
|
+
import { sector_configs } from "../../memory/hsg";
|
|
3
|
+
import { getEmbeddingInfo } from "../../memory/embed";
|
|
4
|
+
import { tier, env } from "../../core/cfg";
|
|
5
|
+
|
|
6
|
+
const TIER_BENEFITS = {
|
|
7
|
+
hybrid: {
|
|
8
|
+
recall: 98,
|
|
9
|
+
qps: "700-800",
|
|
10
|
+
ram: "0.5gb/10k",
|
|
11
|
+
use: "For high accuracy",
|
|
12
|
+
},
|
|
13
|
+
fast: {
|
|
14
|
+
recall: 70,
|
|
15
|
+
qps: "700-850",
|
|
16
|
+
ram: "0.6GB/10k",
|
|
17
|
+
use: "Local apps, extensions",
|
|
18
|
+
},
|
|
19
|
+
smart: {
|
|
20
|
+
recall: 85,
|
|
21
|
+
qps: "500-600",
|
|
22
|
+
ram: "0.9GB/10k",
|
|
23
|
+
use: "Production servers",
|
|
24
|
+
},
|
|
25
|
+
deep: {
|
|
26
|
+
recall: 94,
|
|
27
|
+
qps: "350-400",
|
|
28
|
+
ram: "1.6GB/10k",
|
|
29
|
+
use: "Cloud, high-accuracy",
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function sys(app: any) {
|
|
34
|
+
app.get(
|
|
35
|
+
"/health",
|
|
36
|
+
async (incoming_http_request: any, outgoing_http_response: any) => {
|
|
37
|
+
outgoing_http_response.json({
|
|
38
|
+
ok: true,
|
|
39
|
+
version: "2.0-hsg-tiered",
|
|
40
|
+
embedding: getEmbeddingInfo(),
|
|
41
|
+
tier,
|
|
42
|
+
dim: env.vec_dim,
|
|
43
|
+
cache: env.cache_segments,
|
|
44
|
+
expected: TIER_BENEFITS[tier],
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
app.get(
|
|
50
|
+
"/sectors",
|
|
51
|
+
async (incoming_http_request: any, outgoing_http_response: any) => {
|
|
52
|
+
try {
|
|
53
|
+
const database_sector_statistics_rows = await all_async(`
|
|
54
|
+
select primary_sector as sector, count(*) as count, avg(salience) as avg_salience
|
|
55
|
+
from memories
|
|
56
|
+
group by primary_sector
|
|
57
|
+
`);
|
|
58
|
+
outgoing_http_response.json({
|
|
59
|
+
sectors: Object.keys(sector_configs),
|
|
60
|
+
configs: sector_configs,
|
|
61
|
+
stats: database_sector_statistics_rows,
|
|
62
|
+
});
|
|
63
|
+
} catch (unexpected_error_fetching_sectors) {
|
|
64
|
+
outgoing_http_response.status(500).json({ err: "internal" });
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
}
|