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,286 @@
|
|
|
1
|
+
import { add_hsg_memory } from "../memory/hsg";
|
|
2
|
+
import { q, transaction } from "../core/db";
|
|
3
|
+
import { rid, now, j } from "../utils";
|
|
4
|
+
import { extractText, ExtractionResult } from "./extract";
|
|
5
|
+
|
|
6
|
+
const LG = 8000,
|
|
7
|
+
SEC = 3000;
|
|
8
|
+
|
|
9
|
+
export interface ingestion_cfg {
|
|
10
|
+
force_root?: boolean;
|
|
11
|
+
sec_sz?: number;
|
|
12
|
+
lg_thresh?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface IngestionResult {
|
|
15
|
+
root_memory_id: string;
|
|
16
|
+
child_count: number;
|
|
17
|
+
total_tokens: number;
|
|
18
|
+
strategy: "single" | "root-child";
|
|
19
|
+
extraction: ExtractionResult["metadata"];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const split = (t: string, sz: number): string[] => {
|
|
23
|
+
if (t.length <= sz) return [t];
|
|
24
|
+
const secs: string[] = [];
|
|
25
|
+
const paras = t.split(/\n\n+/);
|
|
26
|
+
let cur = "";
|
|
27
|
+
for (const p of paras) {
|
|
28
|
+
if (cur.length + p.length > sz && cur.length > 0) {
|
|
29
|
+
secs.push(cur.trim());
|
|
30
|
+
cur = p;
|
|
31
|
+
} else cur += (cur ? "\n\n" : "") + p;
|
|
32
|
+
}
|
|
33
|
+
if (cur.trim()) secs.push(cur.trim());
|
|
34
|
+
return secs;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const mkRoot = async (
|
|
38
|
+
txt: string,
|
|
39
|
+
ex: ExtractionResult,
|
|
40
|
+
meta?: Record<string, unknown>,
|
|
41
|
+
user_id?: string | null,
|
|
42
|
+
) => {
|
|
43
|
+
const sum = txt.length > 500 ? txt.slice(0, 500) + "..." : txt;
|
|
44
|
+
const cnt = `[Document: ${ex.metadata.content_type.toUpperCase()}]\n\n${sum}\n\n[Full content split across ${Math.ceil(txt.length / SEC)} sections]`;
|
|
45
|
+
const id = rid(),
|
|
46
|
+
ts = now();
|
|
47
|
+
await transaction.begin();
|
|
48
|
+
try {
|
|
49
|
+
await q.ins_mem.run(
|
|
50
|
+
id,
|
|
51
|
+
cnt,
|
|
52
|
+
"reflective",
|
|
53
|
+
j([]),
|
|
54
|
+
j({
|
|
55
|
+
...meta,
|
|
56
|
+
...ex.metadata,
|
|
57
|
+
is_root: true,
|
|
58
|
+
ingestion_strategy: "root-child",
|
|
59
|
+
ingested_at: ts,
|
|
60
|
+
}),
|
|
61
|
+
ts,
|
|
62
|
+
ts,
|
|
63
|
+
ts,
|
|
64
|
+
1.0,
|
|
65
|
+
0.1,
|
|
66
|
+
1,
|
|
67
|
+
user_id || "anonymous",
|
|
68
|
+
null,
|
|
69
|
+
);
|
|
70
|
+
await transaction.commit();
|
|
71
|
+
return id;
|
|
72
|
+
} catch (e) {
|
|
73
|
+
console.error("[ERROR] Root failed:", e);
|
|
74
|
+
await transaction.rollback();
|
|
75
|
+
throw e;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const mkChild = async (
|
|
80
|
+
txt: string,
|
|
81
|
+
idx: number,
|
|
82
|
+
tot: number,
|
|
83
|
+
rid: string,
|
|
84
|
+
meta?: Record<string, unknown>,
|
|
85
|
+
user_id?: string | null,
|
|
86
|
+
) => {
|
|
87
|
+
const r = await add_hsg_memory(
|
|
88
|
+
txt,
|
|
89
|
+
j([]),
|
|
90
|
+
{
|
|
91
|
+
...meta,
|
|
92
|
+
is_child: true,
|
|
93
|
+
section_index: idx,
|
|
94
|
+
total_sections: tot,
|
|
95
|
+
parent_id: rid,
|
|
96
|
+
},
|
|
97
|
+
user_id || undefined,
|
|
98
|
+
);
|
|
99
|
+
return r.id;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const link = async (
|
|
103
|
+
rid: string,
|
|
104
|
+
cid: string,
|
|
105
|
+
idx: number,
|
|
106
|
+
user_id?: string | null,
|
|
107
|
+
) => {
|
|
108
|
+
const ts = now();
|
|
109
|
+
await transaction.begin();
|
|
110
|
+
try {
|
|
111
|
+
await q.ins_waypoint.run(rid, cid, user_id || "anonymous", 1.0, ts, ts);
|
|
112
|
+
await transaction.commit();
|
|
113
|
+
console.log(
|
|
114
|
+
`[INGEST] Linked: ${rid.slice(0, 8)} -> ${cid.slice(0, 8)} (section ${idx})`,
|
|
115
|
+
);
|
|
116
|
+
} catch (e) {
|
|
117
|
+
await transaction.rollback();
|
|
118
|
+
console.error(`[INGEST] Link failed for section ${idx}:`, e);
|
|
119
|
+
throw e;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export async function ingestDocument(
|
|
124
|
+
t: string,
|
|
125
|
+
data: string | Buffer,
|
|
126
|
+
meta?: Record<string, unknown>,
|
|
127
|
+
cfg?: ingestion_cfg,
|
|
128
|
+
user_id?: string | null,
|
|
129
|
+
): Promise<IngestionResult> {
|
|
130
|
+
const th = cfg?.lg_thresh || LG,
|
|
131
|
+
sz = cfg?.sec_sz || SEC;
|
|
132
|
+
const ex = await extractText(t, data);
|
|
133
|
+
const { text, metadata: exMeta } = ex;
|
|
134
|
+
const useRC = cfg?.force_root || exMeta.estimated_tokens > th;
|
|
135
|
+
|
|
136
|
+
if (!useRC) {
|
|
137
|
+
const r = await add_hsg_memory(
|
|
138
|
+
text,
|
|
139
|
+
j([]),
|
|
140
|
+
{
|
|
141
|
+
...meta,
|
|
142
|
+
...exMeta,
|
|
143
|
+
ingestion_strategy: "single",
|
|
144
|
+
ingested_at: now(),
|
|
145
|
+
},
|
|
146
|
+
user_id || undefined,
|
|
147
|
+
);
|
|
148
|
+
return {
|
|
149
|
+
root_memory_id: r.id,
|
|
150
|
+
child_count: 0,
|
|
151
|
+
total_tokens: exMeta.estimated_tokens,
|
|
152
|
+
strategy: "single",
|
|
153
|
+
extraction: exMeta,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const secs = split(text, sz);
|
|
158
|
+
console.log(`[INGEST] Document: ${exMeta.estimated_tokens} tokens`);
|
|
159
|
+
console.log(`[INGEST] Splitting into ${secs.length} sections`);
|
|
160
|
+
|
|
161
|
+
let rid: string;
|
|
162
|
+
const cids: string[] = [];
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
rid = await mkRoot(text, ex, meta, user_id);
|
|
166
|
+
console.log(`[INGEST] Root memory created: ${rid}`);
|
|
167
|
+
for (let i = 0; i < secs.length; i++) {
|
|
168
|
+
try {
|
|
169
|
+
const cid = await mkChild(
|
|
170
|
+
secs[i],
|
|
171
|
+
i,
|
|
172
|
+
secs.length,
|
|
173
|
+
rid,
|
|
174
|
+
meta,
|
|
175
|
+
user_id,
|
|
176
|
+
);
|
|
177
|
+
cids.push(cid);
|
|
178
|
+
await link(rid, cid, i, user_id);
|
|
179
|
+
console.log(
|
|
180
|
+
`[INGEST] Section ${i + 1}/${secs.length} processed: ${cid}`,
|
|
181
|
+
);
|
|
182
|
+
} catch (e) {
|
|
183
|
+
console.error(
|
|
184
|
+
`[INGEST] Section ${i + 1}/${secs.length} failed:`,
|
|
185
|
+
e,
|
|
186
|
+
);
|
|
187
|
+
throw e;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
console.log(
|
|
191
|
+
`[INGEST] Completed: ${cids.length} sections linked to ${rid}`,
|
|
192
|
+
);
|
|
193
|
+
return {
|
|
194
|
+
root_memory_id: rid,
|
|
195
|
+
child_count: secs.length,
|
|
196
|
+
total_tokens: exMeta.estimated_tokens,
|
|
197
|
+
strategy: "root-child",
|
|
198
|
+
extraction: exMeta,
|
|
199
|
+
};
|
|
200
|
+
} catch (e) {
|
|
201
|
+
console.error("[INGEST] Document ingestion failed:", e);
|
|
202
|
+
throw e;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function ingestURL(
|
|
207
|
+
url: string,
|
|
208
|
+
meta?: Record<string, unknown>,
|
|
209
|
+
cfg?: ingestion_cfg,
|
|
210
|
+
user_id?: string | null,
|
|
211
|
+
): Promise<IngestionResult> {
|
|
212
|
+
const { extractURL } = await import("./extract");
|
|
213
|
+
const ex = await extractURL(url);
|
|
214
|
+
const th = cfg?.lg_thresh || LG,
|
|
215
|
+
sz = cfg?.sec_sz || SEC;
|
|
216
|
+
const useRC = cfg?.force_root || ex.metadata.estimated_tokens > th;
|
|
217
|
+
|
|
218
|
+
if (!useRC) {
|
|
219
|
+
const r = await add_hsg_memory(
|
|
220
|
+
ex.text,
|
|
221
|
+
j([]),
|
|
222
|
+
{
|
|
223
|
+
...meta,
|
|
224
|
+
...ex.metadata,
|
|
225
|
+
ingestion_strategy: "single",
|
|
226
|
+
ingested_at: now(),
|
|
227
|
+
},
|
|
228
|
+
user_id || undefined,
|
|
229
|
+
);
|
|
230
|
+
return {
|
|
231
|
+
root_memory_id: r.id,
|
|
232
|
+
child_count: 0,
|
|
233
|
+
total_tokens: ex.metadata.estimated_tokens,
|
|
234
|
+
strategy: "single",
|
|
235
|
+
extraction: ex.metadata,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const secs = split(ex.text, sz);
|
|
240
|
+
console.log(`[INGEST] URL: ${ex.metadata.estimated_tokens} tokens`);
|
|
241
|
+
console.log(`[INGEST] Splitting into ${secs.length} sections`);
|
|
242
|
+
|
|
243
|
+
let rid: string;
|
|
244
|
+
const cids: string[] = [];
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
rid = await mkRoot(ex.text, ex, { ...meta, source_url: url }, user_id);
|
|
248
|
+
console.log(`[INGEST] Root memory for URL: ${rid}`);
|
|
249
|
+
for (let i = 0; i < secs.length; i++) {
|
|
250
|
+
try {
|
|
251
|
+
const cid = await mkChild(
|
|
252
|
+
secs[i],
|
|
253
|
+
i,
|
|
254
|
+
secs.length,
|
|
255
|
+
rid,
|
|
256
|
+
{ ...meta, source_url: url },
|
|
257
|
+
user_id,
|
|
258
|
+
);
|
|
259
|
+
cids.push(cid);
|
|
260
|
+
await link(rid, cid, i, user_id);
|
|
261
|
+
console.log(
|
|
262
|
+
`[INGEST] URL section ${i + 1}/${secs.length} processed: ${cid}`,
|
|
263
|
+
);
|
|
264
|
+
} catch (e) {
|
|
265
|
+
console.error(
|
|
266
|
+
`[INGEST] URL section ${i + 1}/${secs.length} failed:`,
|
|
267
|
+
e,
|
|
268
|
+
);
|
|
269
|
+
throw e;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
console.log(
|
|
273
|
+
`[INGEST] URL completed: ${cids.length} sections linked to ${rid}`,
|
|
274
|
+
);
|
|
275
|
+
return {
|
|
276
|
+
root_memory_id: rid,
|
|
277
|
+
child_count: secs.length,
|
|
278
|
+
total_tokens: ex.metadata.estimated_tokens,
|
|
279
|
+
strategy: "root-child",
|
|
280
|
+
extraction: ex.metadata,
|
|
281
|
+
};
|
|
282
|
+
} catch (e) {
|
|
283
|
+
console.error("[INGEST] URL ingestion failed:", e);
|
|
284
|
+
throw e;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
const server = require("./server.js");
|
|
2
|
+
import { env, tier } from "../core/cfg";
|
|
3
|
+
import { run_decay_process, prune_weak_waypoints } from "../memory/hsg";
|
|
4
|
+
import { mcp } from "../ai/mcp";
|
|
5
|
+
import { routes } from "./routes";
|
|
6
|
+
import {
|
|
7
|
+
authenticate_api_request,
|
|
8
|
+
log_authenticated_request,
|
|
9
|
+
} from "./middleware/auth";
|
|
10
|
+
import { start_reflection } from "../memory/reflect";
|
|
11
|
+
import { start_user_summary_reflection } from "../memory/user_summary";
|
|
12
|
+
import { sendTelemetry } from "../core/telemetry";
|
|
13
|
+
import { req_tracker_mw } from "./routes/dashboard";
|
|
14
|
+
|
|
15
|
+
const ASC = ` ____ __ __
|
|
16
|
+
/ __ \\ | \\/ |
|
|
17
|
+
| | | |_ __ ___ _ __ | \\ / | ___ _ __ ___ ___ _ __ _ _
|
|
18
|
+
| | | | '_ \\ / _ \\ '_ \\| |\\/| |/ _ \\ '_ \` _ \\ / _ \\| '__| | | |
|
|
19
|
+
| |__| | |_) | __/ | | | | | | __/ | | | | | (_) | | | |_| |
|
|
20
|
+
\\____/| .__/ \\___|_| |_|_| |_|\\___|_| |_| |_|\\___/|_| \\__, |
|
|
21
|
+
| | __/ |
|
|
22
|
+
|_| |___/ `;
|
|
23
|
+
|
|
24
|
+
const app = server({ max_payload_size: env.max_payload_size });
|
|
25
|
+
|
|
26
|
+
console.log(ASC);
|
|
27
|
+
console.log(`[CONFIG] Vector Dimension: ${env.vec_dim}`);
|
|
28
|
+
console.log(`[CONFIG] Cache Segments: ${env.cache_segments}`);
|
|
29
|
+
console.log(`[CONFIG] Max Active Queries: ${env.max_active}`);
|
|
30
|
+
|
|
31
|
+
// Warn about configuration mismatch that causes embedding incompatibility
|
|
32
|
+
if (env.emb_kind !== "synthetic" && (tier === "hybrid" || tier === "fast")) {
|
|
33
|
+
console.warn(
|
|
34
|
+
`[CONFIG] ⚠️ WARNING: Embedding configuration mismatch detected!\n` +
|
|
35
|
+
` OM_EMBEDDINGS=${env.emb_kind} but OM_TIER=${tier}\n` +
|
|
36
|
+
` Storage will use ${env.emb_kind} embeddings, but queries will use synthetic embeddings.\n` +
|
|
37
|
+
` This causes semantic search to fail. Set OM_TIER=deep to fix.`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
app.use(req_tracker_mw());
|
|
42
|
+
|
|
43
|
+
app.use((req: any, res: any, next: any) => {
|
|
44
|
+
const allowedOrigins = [
|
|
45
|
+
"https://ahok.io",
|
|
46
|
+
"https://www.ahok.io",
|
|
47
|
+
"http://localhost:3000",
|
|
48
|
+
"http://localhost:8080",
|
|
49
|
+
];
|
|
50
|
+
const origin = req.headers.origin;
|
|
51
|
+
// Allow specific origins, Amplify apps, and App Runner domains
|
|
52
|
+
if (
|
|
53
|
+
allowedOrigins.includes(origin) ||
|
|
54
|
+
origin?.endsWith(".amplifyapp.com") ||
|
|
55
|
+
origin?.endsWith(".awsapprunner.com")
|
|
56
|
+
) {
|
|
57
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
58
|
+
} else {
|
|
59
|
+
// Allow any origin for API requests (backward compatibility)
|
|
60
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
61
|
+
}
|
|
62
|
+
res.setHeader(
|
|
63
|
+
"Access-Control-Allow-Methods",
|
|
64
|
+
"GET,POST,PUT,PATCH,DELETE,OPTIONS",
|
|
65
|
+
);
|
|
66
|
+
res.setHeader(
|
|
67
|
+
"Access-Control-Allow-Headers",
|
|
68
|
+
"Content-Type,Authorization,x-api-key",
|
|
69
|
+
);
|
|
70
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
71
|
+
if (req.method === "OPTIONS") {
|
|
72
|
+
res.status(200).end();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
next();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
app.use(authenticate_api_request);
|
|
79
|
+
|
|
80
|
+
if (process.env.OM_LOG_AUTH === "true") {
|
|
81
|
+
app.use(log_authenticated_request);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const path = require("path");
|
|
85
|
+
|
|
86
|
+
routes(app);
|
|
87
|
+
|
|
88
|
+
// Serve dashboard static files
|
|
89
|
+
const publicDir = path.join(__dirname, "../..", "public");
|
|
90
|
+
app.use(app.serverStatic("/", publicDir));
|
|
91
|
+
|
|
92
|
+
// Fallback for SPA routing
|
|
93
|
+
app.get("*", (req: any, res: any, next: any) => {
|
|
94
|
+
if (req.path.startsWith("/api") || req.path.startsWith("/auth")) {
|
|
95
|
+
return next();
|
|
96
|
+
}
|
|
97
|
+
res.setHeader("Content-Type", "text/html");
|
|
98
|
+
const fs = require("fs");
|
|
99
|
+
const indexPath = path.join(publicDir, "index.html");
|
|
100
|
+
if (fs.existsSync(indexPath)) {
|
|
101
|
+
res.send(fs.readFileSync(indexPath, "utf8"));
|
|
102
|
+
} else {
|
|
103
|
+
next();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
mcp(app);
|
|
108
|
+
if (env.mode === "langgraph") {
|
|
109
|
+
console.log("[MODE] LangGraph integration enabled");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const decayIntervalMs = env.decay_interval_minutes * 60 * 1000;
|
|
113
|
+
console.log(
|
|
114
|
+
`[DECAY] Interval: ${env.decay_interval_minutes} minutes (${decayIntervalMs / 1000}s)`,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
setInterval(async () => {
|
|
118
|
+
console.log("[DECAY] Running HSG decay process...");
|
|
119
|
+
try {
|
|
120
|
+
const result = await run_decay_process();
|
|
121
|
+
console.log(
|
|
122
|
+
`[DECAY] Completed: ${result.decayed}/${result.processed} memories updated`,
|
|
123
|
+
);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error("[DECAY] Process failed:", error);
|
|
126
|
+
}
|
|
127
|
+
}, decayIntervalMs);
|
|
128
|
+
setInterval(
|
|
129
|
+
async () => {
|
|
130
|
+
console.log("[PRUNE] Pruning weak waypoints...");
|
|
131
|
+
try {
|
|
132
|
+
const pruned = await prune_weak_waypoints();
|
|
133
|
+
console.log(`[PRUNE] Completed: ${pruned} waypoints removed`);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error("[PRUNE] Failed:", error);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
7 * 24 * 60 * 60 * 1000,
|
|
139
|
+
);
|
|
140
|
+
setTimeout(() => {
|
|
141
|
+
run_decay_process()
|
|
142
|
+
.then((result: any) => {
|
|
143
|
+
console.log(
|
|
144
|
+
`[INIT] Initial decay: ${result.decayed}/${result.processed} memories updated`,
|
|
145
|
+
);
|
|
146
|
+
})
|
|
147
|
+
.catch(console.error);
|
|
148
|
+
}, 3000);
|
|
149
|
+
|
|
150
|
+
start_reflection();
|
|
151
|
+
start_user_summary_reflection();
|
|
152
|
+
|
|
153
|
+
console.log(`[SERVER] Starting on port ${env.port}`);
|
|
154
|
+
app.listen(env.port, () => {
|
|
155
|
+
console.log(`[SERVER] Running on http://localhost:${env.port}`);
|
|
156
|
+
sendTelemetry().catch(() => {
|
|
157
|
+
// ignore telemetry failures
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { env } from "../../core/cfg";
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
|
|
4
|
+
const rate_limit_store = new Map<
|
|
5
|
+
string,
|
|
6
|
+
{ count: number; reset_time: number }
|
|
7
|
+
>();
|
|
8
|
+
const auth_config = {
|
|
9
|
+
api_key: env.api_key,
|
|
10
|
+
api_key_header: "x-api-key",
|
|
11
|
+
rate_limit_enabled: env.rate_limit_enabled,
|
|
12
|
+
rate_limit_window_ms: env.rate_limit_window_ms,
|
|
13
|
+
rate_limit_max_requests: env.rate_limit_max_requests,
|
|
14
|
+
public_endpoints: [
|
|
15
|
+
"/health",
|
|
16
|
+
"/api/system/health",
|
|
17
|
+
"/api/system/stats",
|
|
18
|
+
"/dashboard/health",
|
|
19
|
+
"/auth",
|
|
20
|
+
"/docs",
|
|
21
|
+
"/openapi.json",
|
|
22
|
+
"/.well-known",
|
|
23
|
+
"/mcp",
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function is_public_endpoint(path: string): boolean {
|
|
28
|
+
return auth_config.public_endpoints.some(
|
|
29
|
+
(e) => path === e || path.startsWith(e),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function extract_api_key(req: any): string | null {
|
|
34
|
+
const x_api_key = req.headers[auth_config.api_key_header];
|
|
35
|
+
if (x_api_key) return x_api_key;
|
|
36
|
+
const auth_header = req.headers["authorization"];
|
|
37
|
+
if (auth_header) {
|
|
38
|
+
if (auth_header.startsWith("Bearer ")) return auth_header.slice(7);
|
|
39
|
+
if (auth_header.startsWith("ApiKey ")) return auth_header.slice(7);
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function validate_api_key(provided: string, expected: string): boolean {
|
|
45
|
+
if (!provided || !expected || provided.length !== expected.length)
|
|
46
|
+
return false;
|
|
47
|
+
return crypto.timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function check_rate_limit(client_id: string): {
|
|
51
|
+
allowed: boolean;
|
|
52
|
+
remaining: number;
|
|
53
|
+
reset_time: number;
|
|
54
|
+
} {
|
|
55
|
+
if (!auth_config.rate_limit_enabled)
|
|
56
|
+
return { allowed: true, remaining: -1, reset_time: -1 };
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
const data = rate_limit_store.get(client_id);
|
|
59
|
+
if (!data || now >= data.reset_time) {
|
|
60
|
+
const new_data = {
|
|
61
|
+
count: 1,
|
|
62
|
+
reset_time: now + auth_config.rate_limit_window_ms,
|
|
63
|
+
};
|
|
64
|
+
rate_limit_store.set(client_id, new_data);
|
|
65
|
+
return {
|
|
66
|
+
allowed: true,
|
|
67
|
+
remaining: auth_config.rate_limit_max_requests - 1,
|
|
68
|
+
reset_time: new_data.reset_time,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
data.count++;
|
|
72
|
+
rate_limit_store.set(client_id, data);
|
|
73
|
+
const remaining = auth_config.rate_limit_max_requests - data.count;
|
|
74
|
+
return {
|
|
75
|
+
allowed: data.count <= auth_config.rate_limit_max_requests,
|
|
76
|
+
remaining: Math.max(0, remaining),
|
|
77
|
+
reset_time: data.reset_time,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function get_client_id(req: any, api_key: string | null): string {
|
|
82
|
+
if (api_key)
|
|
83
|
+
return crypto
|
|
84
|
+
.createHash("sha256")
|
|
85
|
+
.update(api_key)
|
|
86
|
+
.digest("hex")
|
|
87
|
+
.slice(0, 16);
|
|
88
|
+
return req.ip || req.connection.remoteAddress || "unknown";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function authenticate_api_request(req: any, res: any, next: any) {
|
|
92
|
+
const path = req.path || req.url;
|
|
93
|
+
if (is_public_endpoint(path)) return next();
|
|
94
|
+
|
|
95
|
+
const provided = extract_api_key(req);
|
|
96
|
+
if (!provided) {
|
|
97
|
+
return res.status(401).json({
|
|
98
|
+
error: "authentication_required",
|
|
99
|
+
message: "API key required",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 1. Check master key
|
|
104
|
+
if (auth_config.api_key && validate_api_key(provided, auth_config.api_key)) {
|
|
105
|
+
req.user = { user_id: "admin", is_admin: true };
|
|
106
|
+
return next();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 2. Check database for individual API keys
|
|
110
|
+
try {
|
|
111
|
+
const { q } = require("../../core/db");
|
|
112
|
+
let user = await q.get_user_by_api_key.get(provided);
|
|
113
|
+
if (!user) {
|
|
114
|
+
// Check secondary memory keys
|
|
115
|
+
user = await q.get_user_by_memory_key.get(provided);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (user) {
|
|
119
|
+
// Check usage vs capacity
|
|
120
|
+
if (user.memory_usage >= user.memory_capacity && req.method === "POST" && path.includes("/memories")) {
|
|
121
|
+
return res.status(403).json({
|
|
122
|
+
error: "capacity_exceeded",
|
|
123
|
+
message: "Memory capacity reached. Please upgrade your plan.",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
req.user = user;
|
|
127
|
+
if (user.key_id) {
|
|
128
|
+
req.key_id = user.key_id;
|
|
129
|
+
req.key_label = user.key_label;
|
|
130
|
+
}
|
|
131
|
+
return next();
|
|
132
|
+
}
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.error("[AUTH] Database lookup failed:", err);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return res.status(403).json({ error: "invalid_api_key" });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function log_authenticated_request(req: any, res: any, next: any) {
|
|
141
|
+
const key = extract_api_key(req);
|
|
142
|
+
if (key)
|
|
143
|
+
console.log(
|
|
144
|
+
`[AUTH] ${req.method} ${req.path} [${crypto.createHash("sha256").update(key).digest("hex").slice(0, 8)}...]`,
|
|
145
|
+
);
|
|
146
|
+
next();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
setInterval(
|
|
150
|
+
() => {
|
|
151
|
+
const now = Date.now();
|
|
152
|
+
for (const [id, data] of rate_limit_store.entries())
|
|
153
|
+
if (now >= data.reset_time) rate_limit_store.delete(id);
|
|
154
|
+
},
|
|
155
|
+
5 * 60 * 1000,
|
|
156
|
+
);
|