@hasna/mementos 0.14.17 → 0.14.18
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/dist/cli/index.js +2883 -2332
- package/dist/index.js +215 -8
- package/dist/mcp/index.js +2862 -2589
- package/dist/server/index.js +770 -479
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -10167,6 +10167,35 @@ ALTER TABLE memories ADD COLUMN sequence_group TEXT DEFAULT NULL;
|
|
|
10167
10167
|
ALTER TABLE memories ADD COLUMN sequence_order INTEGER DEFAULT NULL;
|
|
10168
10168
|
CREATE INDEX IF NOT EXISTS idx_memories_sequence_group ON memories(sequence_group) WHERE sequence_group IS NOT NULL;
|
|
10169
10169
|
INSERT OR IGNORE INTO _migrations (id) VALUES (32);
|
|
10170
|
+
`,
|
|
10171
|
+
`
|
|
10172
|
+
ALTER TABLE machines ADD COLUMN is_primary INTEGER NOT NULL DEFAULT 0;
|
|
10173
|
+
CREATE INDEX IF NOT EXISTS idx_machines_primary ON machines(is_primary);
|
|
10174
|
+
CREATE TRIGGER IF NOT EXISTS machines_single_primary_insert
|
|
10175
|
+
AFTER INSERT ON machines
|
|
10176
|
+
WHEN NEW.is_primary = 1
|
|
10177
|
+
BEGIN
|
|
10178
|
+
UPDATE machines
|
|
10179
|
+
SET is_primary = 0,
|
|
10180
|
+
last_seen_at = COALESCE(NEW.last_seen_at, datetime('now'))
|
|
10181
|
+
WHERE id != NEW.id AND is_primary = 1;
|
|
10182
|
+
END;
|
|
10183
|
+
CREATE TRIGGER IF NOT EXISTS machines_single_primary_update
|
|
10184
|
+
AFTER UPDATE OF is_primary ON machines
|
|
10185
|
+
WHEN NEW.is_primary = 1
|
|
10186
|
+
BEGIN
|
|
10187
|
+
UPDATE machines
|
|
10188
|
+
SET is_primary = 0,
|
|
10189
|
+
last_seen_at = COALESCE(NEW.last_seen_at, datetime('now'))
|
|
10190
|
+
WHERE id != NEW.id AND is_primary = 1;
|
|
10191
|
+
END;
|
|
10192
|
+
CREATE TRIGGER IF NOT EXISTS machines_prevent_delete_primary
|
|
10193
|
+
BEFORE DELETE ON machines
|
|
10194
|
+
WHEN OLD.is_primary = 1
|
|
10195
|
+
BEGIN
|
|
10196
|
+
SELECT RAISE(ABORT, 'Primary machine cannot be deleted');
|
|
10197
|
+
END;
|
|
10198
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (33);
|
|
10170
10199
|
`
|
|
10171
10200
|
];
|
|
10172
10201
|
});
|
|
@@ -10250,11 +10279,16 @@ function ensureDir2(filePath) {
|
|
|
10250
10279
|
}
|
|
10251
10280
|
}
|
|
10252
10281
|
function getDatabase(dbPath) {
|
|
10253
|
-
if (_db)
|
|
10254
|
-
return _db;
|
|
10255
10282
|
const path = dbPath || getDbPath3();
|
|
10283
|
+
if (_db) {
|
|
10284
|
+
if (_dbPath === path)
|
|
10285
|
+
return _db;
|
|
10286
|
+
_db.close();
|
|
10287
|
+
_db = null;
|
|
10288
|
+
}
|
|
10289
|
+
_dbPath = path;
|
|
10256
10290
|
ensureDir2(path);
|
|
10257
|
-
_db = new SqliteAdapter(path
|
|
10291
|
+
_db = new SqliteAdapter(path);
|
|
10258
10292
|
_db.run("PRAGMA journal_mode = WAL");
|
|
10259
10293
|
_db.run("PRAGMA busy_timeout = 5000");
|
|
10260
10294
|
_db.run("PRAGMA foreign_keys = ON");
|
|
@@ -10278,13 +10312,17 @@ function runMigrations(db) {
|
|
|
10278
10312
|
for (let i = currentLevel;i < MIGRATIONS.length; i++) {
|
|
10279
10313
|
try {
|
|
10280
10314
|
db.exec(MIGRATIONS[i]);
|
|
10281
|
-
} catch {
|
|
10315
|
+
} catch (e) {
|
|
10316
|
+
console.warn(`[mementos] Migration ${i + 1} failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
10317
|
+
}
|
|
10282
10318
|
}
|
|
10283
10319
|
} catch {
|
|
10284
|
-
for (
|
|
10320
|
+
for (let i = 0;i < MIGRATIONS.length; i++) {
|
|
10285
10321
|
try {
|
|
10286
|
-
db.exec(
|
|
10287
|
-
} catch {
|
|
10322
|
+
db.exec(MIGRATIONS[i]);
|
|
10323
|
+
} catch (e) {
|
|
10324
|
+
console.warn(`[mementos] Migration ${i + 1} failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
10325
|
+
}
|
|
10288
10326
|
}
|
|
10289
10327
|
}
|
|
10290
10328
|
}
|
|
@@ -10307,6 +10345,9 @@ function shortUuid() {
|
|
|
10307
10345
|
return crypto.randomUUID().slice(0, 8);
|
|
10308
10346
|
}
|
|
10309
10347
|
function resolvePartialId(db, table, partialId) {
|
|
10348
|
+
if (!ALLOWED_TABLES.has(table)) {
|
|
10349
|
+
throw new Error(`Invalid table name: ${table}`);
|
|
10350
|
+
}
|
|
10310
10351
|
if (partialId.length >= 36) {
|
|
10311
10352
|
const row = db.query(`SELECT id FROM ${table} WHERE id = ?`).get(partialId);
|
|
10312
10353
|
return row?.id ?? null;
|
|
@@ -10317,10 +10358,25 @@ function resolvePartialId(db, table, partialId) {
|
|
|
10317
10358
|
}
|
|
10318
10359
|
return null;
|
|
10319
10360
|
}
|
|
10320
|
-
var _db = null;
|
|
10361
|
+
var _db = null, _dbPath = null, ALLOWED_TABLES;
|
|
10321
10362
|
var init_database = __esm(() => {
|
|
10322
10363
|
init_dist();
|
|
10323
10364
|
init_migrations();
|
|
10365
|
+
ALLOWED_TABLES = new Set([
|
|
10366
|
+
"memories",
|
|
10367
|
+
"agents",
|
|
10368
|
+
"entities",
|
|
10369
|
+
"projects",
|
|
10370
|
+
"relations",
|
|
10371
|
+
"memory_audit_log",
|
|
10372
|
+
"locks",
|
|
10373
|
+
"sessions",
|
|
10374
|
+
"session_memory_jobs",
|
|
10375
|
+
"synthesis_runs",
|
|
10376
|
+
"synthesis_proposals",
|
|
10377
|
+
"tool_events",
|
|
10378
|
+
"webhook_hooks"
|
|
10379
|
+
]);
|
|
10324
10380
|
});
|
|
10325
10381
|
|
|
10326
10382
|
// src/lib/hooks.ts
|
|
@@ -10993,6 +11049,22 @@ function listMemories(filter, db) {
|
|
|
10993
11049
|
conditions.push("session_id = ?");
|
|
10994
11050
|
params.push(filter.session_id);
|
|
10995
11051
|
}
|
|
11052
|
+
if ("machine_id" in filter) {
|
|
11053
|
+
if (filter.machine_id === null) {
|
|
11054
|
+
conditions.push("machine_id IS NULL");
|
|
11055
|
+
} else if (filter.machine_id) {
|
|
11056
|
+
conditions.push("machine_id = ?");
|
|
11057
|
+
params.push(filter.machine_id);
|
|
11058
|
+
}
|
|
11059
|
+
}
|
|
11060
|
+
if ("visible_to_machine_id" in filter) {
|
|
11061
|
+
if (filter.visible_to_machine_id === null) {
|
|
11062
|
+
conditions.push("machine_id IS NULL");
|
|
11063
|
+
} else if (filter.visible_to_machine_id !== undefined) {
|
|
11064
|
+
conditions.push("(machine_id IS NULL OR machine_id = ?)");
|
|
11065
|
+
params.push(filter.visible_to_machine_id);
|
|
11066
|
+
}
|
|
11067
|
+
}
|
|
10996
11068
|
if (filter.min_importance) {
|
|
10997
11069
|
conditions.push("importance >= ?");
|
|
10998
11070
|
params.push(filter.min_importance);
|
|
@@ -13355,12 +13427,14 @@ async function synthesizeProfile(options) {
|
|
|
13355
13427
|
const prefMemories = listMemories({
|
|
13356
13428
|
category: "preference",
|
|
13357
13429
|
project_id: options.project_id,
|
|
13430
|
+
machine_id: null,
|
|
13358
13431
|
status: "active",
|
|
13359
13432
|
limit: 30
|
|
13360
13433
|
});
|
|
13361
13434
|
const factMemories = listMemories({
|
|
13362
13435
|
category: "fact",
|
|
13363
13436
|
project_id: options.project_id,
|
|
13437
|
+
machine_id: null,
|
|
13364
13438
|
status: "active",
|
|
13365
13439
|
limit: 30
|
|
13366
13440
|
});
|
|
@@ -13671,6 +13745,65 @@ function ensureDir(dir) {
|
|
|
13671
13745
|
// src/server/index.ts
|
|
13672
13746
|
init_database();
|
|
13673
13747
|
|
|
13748
|
+
// src/db/machines.ts
|
|
13749
|
+
init_database();
|
|
13750
|
+
import { hostname, platform as platform2 } from "os";
|
|
13751
|
+
function parseMachine(row) {
|
|
13752
|
+
if (!row)
|
|
13753
|
+
return null;
|
|
13754
|
+
return {
|
|
13755
|
+
...row,
|
|
13756
|
+
is_primary: Boolean(row.is_primary)
|
|
13757
|
+
};
|
|
13758
|
+
}
|
|
13759
|
+
function registerMachine(name, db = getDatabase()) {
|
|
13760
|
+
const host = hostname();
|
|
13761
|
+
const plat = platform2();
|
|
13762
|
+
const machineName = name?.trim() || host;
|
|
13763
|
+
const existing = parseMachine(db.query("SELECT * FROM machines WHERE hostname = ?").get(host));
|
|
13764
|
+
if (existing) {
|
|
13765
|
+
db.run("UPDATE machines SET last_seen_at = ? WHERE id = ?", [now(), existing.id]);
|
|
13766
|
+
return parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(existing.id));
|
|
13767
|
+
}
|
|
13768
|
+
let finalName = machineName;
|
|
13769
|
+
let suffix = 2;
|
|
13770
|
+
while (db.query("SELECT id FROM machines WHERE name = ?").get(finalName)) {
|
|
13771
|
+
finalName = `${machineName}-${suffix++}`;
|
|
13772
|
+
}
|
|
13773
|
+
const id = uuid();
|
|
13774
|
+
db.run("INSERT INTO machines (id, name, hostname, platform) VALUES (?, ?, ?, ?)", [id, finalName, host, plat]);
|
|
13775
|
+
return parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(id));
|
|
13776
|
+
}
|
|
13777
|
+
function getPrimaryMachine(db = getDatabase()) {
|
|
13778
|
+
return parseMachine(db.query("SELECT * FROM machines WHERE is_primary = 1 LIMIT 1").get());
|
|
13779
|
+
}
|
|
13780
|
+
function getPrimaryMachineCandidate(db = getDatabase()) {
|
|
13781
|
+
if (getPrimaryMachine(db))
|
|
13782
|
+
return null;
|
|
13783
|
+
return parseMachine(db.query("SELECT * FROM machines ORDER BY created_at ASC, id ASC LIMIT 1").get());
|
|
13784
|
+
}
|
|
13785
|
+
function getPrimaryMachineStartupWarning(db = getDatabase()) {
|
|
13786
|
+
if (getPrimaryMachine(db))
|
|
13787
|
+
return null;
|
|
13788
|
+
const candidate = getPrimaryMachineCandidate(db);
|
|
13789
|
+
if (!candidate) {
|
|
13790
|
+
return "No primary machine configured. Fallback sync target is unset because no machines are registered yet.";
|
|
13791
|
+
}
|
|
13792
|
+
return `No primary machine configured. Fallback sync target is unset. Candidate: ${candidate.name} (${candidate.id.slice(0, 8)} / ${candidate.hostname}). Confirm it with set_primary_machine.`;
|
|
13793
|
+
}
|
|
13794
|
+
function touchMachine(id, db = getDatabase()) {
|
|
13795
|
+
db.run("UPDATE machines SET last_seen_at = ? WHERE id = ?", [now(), id]);
|
|
13796
|
+
}
|
|
13797
|
+
function getCurrentMachineId(db = getDatabase()) {
|
|
13798
|
+
const host = hostname();
|
|
13799
|
+
const m = db.query("SELECT id FROM machines WHERE hostname = ?").get(host);
|
|
13800
|
+
if (m) {
|
|
13801
|
+
touchMachine(m.id, db);
|
|
13802
|
+
return m.id;
|
|
13803
|
+
}
|
|
13804
|
+
return registerMachine(undefined, db).id;
|
|
13805
|
+
}
|
|
13806
|
+
|
|
13674
13807
|
// src/lib/built-in-hooks.ts
|
|
13675
13808
|
init_hooks();
|
|
13676
13809
|
|
|
@@ -14710,6 +14843,10 @@ async function _processNext() {
|
|
|
14710
14843
|
|
|
14711
14844
|
// src/server/router.ts
|
|
14712
14845
|
var routes = [];
|
|
14846
|
+
var nextRouteOrder = 0;
|
|
14847
|
+
function computeSpecificity(path) {
|
|
14848
|
+
return path.split("/").filter(Boolean).reduce((score, segment) => score + (segment.startsWith(":") ? 1 : 10), 0);
|
|
14849
|
+
}
|
|
14713
14850
|
function addRoute(method, path, handler) {
|
|
14714
14851
|
const paramNames = [];
|
|
14715
14852
|
const patternStr = path.replace(/:(\w+)/g, (_match, name) => {
|
|
@@ -14718,12 +14855,16 @@ function addRoute(method, path, handler) {
|
|
|
14718
14855
|
});
|
|
14719
14856
|
routes.push({
|
|
14720
14857
|
method,
|
|
14858
|
+
path,
|
|
14721
14859
|
pattern: new RegExp(`^${patternStr}$`),
|
|
14722
14860
|
paramNames,
|
|
14861
|
+
specificity: computeSpecificity(path),
|
|
14862
|
+
order: nextRouteOrder++,
|
|
14723
14863
|
handler
|
|
14724
14864
|
});
|
|
14725
14865
|
}
|
|
14726
14866
|
function matchRoute(method, pathname) {
|
|
14867
|
+
let bestMatch = null;
|
|
14727
14868
|
for (const route of routes) {
|
|
14728
14869
|
if (route.method !== method)
|
|
14729
14870
|
continue;
|
|
@@ -14733,10 +14874,14 @@ function matchRoute(method, pathname) {
|
|
|
14733
14874
|
route.paramNames.forEach((name, i) => {
|
|
14734
14875
|
params[name] = match[i + 1];
|
|
14735
14876
|
});
|
|
14736
|
-
|
|
14877
|
+
if (!bestMatch || route.specificity > bestMatch.route.specificity || route.specificity === bestMatch.route.specificity && route.order < bestMatch.route.order) {
|
|
14878
|
+
bestMatch = { route, params };
|
|
14879
|
+
}
|
|
14737
14880
|
}
|
|
14738
14881
|
}
|
|
14739
|
-
|
|
14882
|
+
if (!bestMatch)
|
|
14883
|
+
return null;
|
|
14884
|
+
return { handler: bestMatch.route.handler, params: bestMatch.params };
|
|
14740
14885
|
}
|
|
14741
14886
|
|
|
14742
14887
|
// src/server/helpers.ts
|
|
@@ -14744,7 +14889,7 @@ import { existsSync as existsSync6 } from "fs";
|
|
|
14744
14889
|
import { dirname as dirname4, extname, join as join7 } from "path";
|
|
14745
14890
|
import { fileURLToPath } from "url";
|
|
14746
14891
|
var CORS_HEADERS = {
|
|
14747
|
-
"Access-Control-Allow-Origin": "
|
|
14892
|
+
"Access-Control-Allow-Origin": process.env["MEMENTOS_CORS_ORIGIN"] ?? "http://localhost:19428",
|
|
14748
14893
|
"Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
|
|
14749
14894
|
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
14750
14895
|
"Access-Control-Max-Age": "86400"
|
|
@@ -14779,6 +14924,37 @@ async function readJson(req) {
|
|
|
14779
14924
|
return null;
|
|
14780
14925
|
}
|
|
14781
14926
|
}
|
|
14927
|
+
function getCorsHeaders(req) {
|
|
14928
|
+
const allowedOrigin = process.env["MEMENTOS_CORS_ORIGIN"] ?? "http://localhost:19428";
|
|
14929
|
+
const origin = req?.headers.get("origin");
|
|
14930
|
+
const finalOrigin = origin === allowedOrigin ? origin : allowedOrigin;
|
|
14931
|
+
return {
|
|
14932
|
+
"Access-Control-Allow-Origin": finalOrigin,
|
|
14933
|
+
"Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
|
|
14934
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
14935
|
+
"Access-Control-Max-Age": "86400"
|
|
14936
|
+
};
|
|
14937
|
+
}
|
|
14938
|
+
function authenticateRequest(req) {
|
|
14939
|
+
const requiredKey = process.env["MEMENTOS_API_KEY"];
|
|
14940
|
+
if (!requiredKey)
|
|
14941
|
+
return null;
|
|
14942
|
+
const authHeader = req.headers.get("authorization");
|
|
14943
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
14944
|
+
return new Response(JSON.stringify({ error: "Unauthorized. Provide a Bearer token in the Authorization header." }), {
|
|
14945
|
+
status: 401,
|
|
14946
|
+
headers: { "Content-Type": "application/json", ...getCorsHeaders(req) }
|
|
14947
|
+
});
|
|
14948
|
+
}
|
|
14949
|
+
const provided = authHeader.slice("Bearer ".length);
|
|
14950
|
+
if (provided !== requiredKey) {
|
|
14951
|
+
return new Response(JSON.stringify({ error: "Forbidden. Invalid API key." }), {
|
|
14952
|
+
status: 403,
|
|
14953
|
+
headers: { "Content-Type": "application/json", ...getCorsHeaders(req) }
|
|
14954
|
+
});
|
|
14955
|
+
}
|
|
14956
|
+
return null;
|
|
14957
|
+
}
|
|
14782
14958
|
function getSearchParams(url) {
|
|
14783
14959
|
const params = {};
|
|
14784
14960
|
url.searchParams.forEach((v, k) => {
|
|
@@ -14814,11 +14990,8 @@ function serveStaticFile(filePath) {
|
|
|
14814
14990
|
});
|
|
14815
14991
|
}
|
|
14816
14992
|
|
|
14817
|
-
// src/server/routes/memories.ts
|
|
14993
|
+
// src/server/routes/memories-crud.ts
|
|
14818
14994
|
init_memories();
|
|
14819
|
-
init_database();
|
|
14820
|
-
init_search();
|
|
14821
|
-
init_types2();
|
|
14822
14995
|
|
|
14823
14996
|
// src/lib/duration.ts
|
|
14824
14997
|
var UNIT_MS = {
|
|
@@ -14863,10 +15036,8 @@ var FORMAT_UNITS = [
|
|
|
14863
15036
|
["s", UNIT_MS["s"]]
|
|
14864
15037
|
];
|
|
14865
15038
|
|
|
14866
|
-
// src/server/routes/memories.ts
|
|
14867
|
-
|
|
14868
|
-
return json({ ok: true, version: "1", db: getDbPath() });
|
|
14869
|
-
});
|
|
15039
|
+
// src/server/routes/memories-crud.ts
|
|
15040
|
+
init_types2();
|
|
14870
15041
|
addRoute("GET", "/api/memories", (_req, url) => {
|
|
14871
15042
|
const q = getSearchParams(url);
|
|
14872
15043
|
const filter = {};
|
|
@@ -14900,6 +15071,80 @@ addRoute("GET", "/api/memories", (_req, url) => {
|
|
|
14900
15071
|
}
|
|
14901
15072
|
return json({ memories, count: memories.length });
|
|
14902
15073
|
});
|
|
15074
|
+
addRoute("POST", "/api/memories", async (req) => {
|
|
15075
|
+
const body = await readJson(req);
|
|
15076
|
+
if (!body) {
|
|
15077
|
+
return errorResponse("Invalid JSON body", 400);
|
|
15078
|
+
}
|
|
15079
|
+
if (!body["key"] || !body["value"]) {
|
|
15080
|
+
return errorResponse("Missing required fields: key, value", 400);
|
|
15081
|
+
}
|
|
15082
|
+
try {
|
|
15083
|
+
if (body["ttl_ms"] !== undefined && typeof body["ttl_ms"] === "string") {
|
|
15084
|
+
body["ttl_ms"] = parseDuration(body["ttl_ms"]);
|
|
15085
|
+
}
|
|
15086
|
+
const memory = createMemory(body);
|
|
15087
|
+
return json(memory, 201);
|
|
15088
|
+
} catch (e) {
|
|
15089
|
+
if (e instanceof DuplicateMemoryError) {
|
|
15090
|
+
return errorResponse(e.message, 409);
|
|
15091
|
+
}
|
|
15092
|
+
throw e;
|
|
15093
|
+
}
|
|
15094
|
+
});
|
|
15095
|
+
addRoute("GET", "/api/memories/:id", (_req, _url, params) => {
|
|
15096
|
+
const memory = getMemory(params["id"]);
|
|
15097
|
+
if (!memory) {
|
|
15098
|
+
return errorResponse("Memory not found", 404);
|
|
15099
|
+
}
|
|
15100
|
+
touchMemory(memory.id);
|
|
15101
|
+
return json(memory);
|
|
15102
|
+
});
|
|
15103
|
+
addRoute("PATCH", "/api/memories/:id", async (req, _url, params) => {
|
|
15104
|
+
const body = await readJson(req);
|
|
15105
|
+
if (!body) {
|
|
15106
|
+
return errorResponse("Invalid JSON body", 400);
|
|
15107
|
+
}
|
|
15108
|
+
const updateBody = { ...body };
|
|
15109
|
+
if (updateBody["version"] === undefined) {
|
|
15110
|
+
const existing = getMemory(params["id"]);
|
|
15111
|
+
if (!existing)
|
|
15112
|
+
return errorResponse("Memory not found", 404);
|
|
15113
|
+
updateBody["version"] = existing.version;
|
|
15114
|
+
}
|
|
15115
|
+
try {
|
|
15116
|
+
const memory = updateMemory(params["id"], updateBody);
|
|
15117
|
+
return json(memory);
|
|
15118
|
+
} catch (e) {
|
|
15119
|
+
if (e instanceof MemoryNotFoundError) {
|
|
15120
|
+
return errorResponse(e.message, 404);
|
|
15121
|
+
}
|
|
15122
|
+
if (e instanceof VersionConflictError) {
|
|
15123
|
+
return errorResponse(e.message, 409, {
|
|
15124
|
+
expected: e.expected,
|
|
15125
|
+
actual: e.actual
|
|
15126
|
+
});
|
|
15127
|
+
}
|
|
15128
|
+
throw e;
|
|
15129
|
+
}
|
|
15130
|
+
});
|
|
15131
|
+
addRoute("GET", "/api/memories/:id/versions", (_req, _url, params) => {
|
|
15132
|
+
const memory = getMemory(params["id"]);
|
|
15133
|
+
if (!memory)
|
|
15134
|
+
return errorResponse("Memory not found", 404);
|
|
15135
|
+
const versions = getMemoryVersions(memory.id);
|
|
15136
|
+
return json({ versions, count: versions.length, current_version: memory.version });
|
|
15137
|
+
});
|
|
15138
|
+
addRoute("DELETE", "/api/memories/:id", (_req, _url, params) => {
|
|
15139
|
+
const deleted = deleteMemory(params["id"]);
|
|
15140
|
+
if (!deleted) {
|
|
15141
|
+
return errorResponse("Memory not found", 404);
|
|
15142
|
+
}
|
|
15143
|
+
return json({ deleted: true });
|
|
15144
|
+
});
|
|
15145
|
+
|
|
15146
|
+
// src/server/routes/memories-stats.ts
|
|
15147
|
+
init_database();
|
|
14903
15148
|
addRoute("GET", "/api/memories/stats", (_req) => {
|
|
14904
15149
|
const db = getDatabase();
|
|
14905
15150
|
const total = db.query("SELECT COUNT(*) as c FROM memories WHERE status = 'active'").get().c;
|
|
@@ -14975,6 +15220,8 @@ addRoute("GET", "/api/activity", (_req, url) => {
|
|
|
14975
15220
|
params.push(projectId);
|
|
14976
15221
|
}
|
|
14977
15222
|
const where = conditions.map((c) => `AND ${c}`).join(" ");
|
|
15223
|
+
const cutoffDate = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);
|
|
15224
|
+
params.push(cutoffDate);
|
|
14978
15225
|
const rows = db.query(`
|
|
14979
15226
|
SELECT
|
|
14980
15227
|
date(created_at) AS date,
|
|
@@ -14984,7 +15231,7 @@ addRoute("GET", "/api/activity", (_req, url) => {
|
|
|
14984
15231
|
SUM(CASE WHEN scope = 'private' THEN 1 ELSE 0 END) AS private_count,
|
|
14985
15232
|
AVG(importance) AS avg_importance
|
|
14986
15233
|
FROM memories
|
|
14987
|
-
WHERE date(created_at) >=
|
|
15234
|
+
WHERE date(created_at) >= ? ${where}
|
|
14988
15235
|
GROUP BY date(created_at)
|
|
14989
15236
|
ORDER BY date ASC
|
|
14990
15237
|
`).all(...params);
|
|
@@ -14997,12 +15244,13 @@ addRoute("GET", "/api/memories/stale", (_req, url) => {
|
|
|
14997
15244
|
const agentId = q["agent_id"];
|
|
14998
15245
|
const limit = Math.min(parseInt(q["limit"] || "20", 10), 100);
|
|
14999
15246
|
const db = getDatabase();
|
|
15247
|
+
const cutoffDate = new Date(Date.now() - days * 86400000).toISOString();
|
|
15000
15248
|
const conds = [
|
|
15001
15249
|
"status = 'active'",
|
|
15002
|
-
|
|
15250
|
+
"(accessed_at IS NULL OR accessed_at < ?)",
|
|
15003
15251
|
"pinned = 0"
|
|
15004
15252
|
];
|
|
15005
|
-
const params = [];
|
|
15253
|
+
const params = [cutoffDate];
|
|
15006
15254
|
if (projectId) {
|
|
15007
15255
|
conds.push("project_id = ?");
|
|
15008
15256
|
params.push(projectId);
|
|
@@ -15020,23 +15268,28 @@ addRoute("GET", "/api/report", (_req, url) => {
|
|
|
15020
15268
|
const projectId = q["project_id"];
|
|
15021
15269
|
const agentId = q["agent_id"];
|
|
15022
15270
|
const db = getDatabase();
|
|
15023
|
-
const
|
|
15271
|
+
const cutoffDate = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);
|
|
15272
|
+
const scopedCond = [
|
|
15024
15273
|
projectId ? "AND project_id = ?" : "",
|
|
15025
15274
|
agentId ? "AND agent_id = ?" : ""
|
|
15026
15275
|
].filter(Boolean).join(" ");
|
|
15027
|
-
const
|
|
15028
|
-
|
|
15029
|
-
|
|
15276
|
+
const scopedParams = [
|
|
15277
|
+
...projectId ? [projectId] : [],
|
|
15278
|
+
...agentId ? [agentId] : []
|
|
15279
|
+
];
|
|
15280
|
+
const recentParams = [cutoffDate, ...scopedParams];
|
|
15281
|
+
const total = db.query(`SELECT COUNT(*) as c FROM memories WHERE status = 'active' ${scopedCond}`).get(...scopedParams).c;
|
|
15282
|
+
const pinned = db.query(`SELECT COUNT(*) as c FROM memories WHERE status = 'active' AND pinned = 1 ${scopedCond}`).get(...scopedParams).c;
|
|
15030
15283
|
const actRows = db.query(`
|
|
15031
15284
|
SELECT date(created_at) AS date, COUNT(*) AS memories_created
|
|
15032
|
-
FROM memories WHERE status = 'active' AND date(created_at) >=
|
|
15285
|
+
FROM memories WHERE status = 'active' AND date(created_at) >= ? ${scopedCond}
|
|
15033
15286
|
GROUP BY date(created_at) ORDER BY date(created_at) ASC
|
|
15034
|
-
`).all(...
|
|
15287
|
+
`).all(...recentParams);
|
|
15035
15288
|
const recentTotal = actRows.reduce((s, r) => s + r.memories_created, 0);
|
|
15036
|
-
const byScopeRows = db.query(`SELECT scope, COUNT(*) as c FROM memories WHERE status = 'active' ${
|
|
15037
|
-
const byCatRows = db.query(`SELECT category, COUNT(*) as c FROM memories WHERE status = 'active' ${
|
|
15038
|
-
const topMems = db.query(`SELECT id, key, value, importance, scope, category FROM memories WHERE status = 'active' ${
|
|
15039
|
-
const topAgents = db.query(`SELECT agent_id, COUNT(*) as c FROM memories WHERE status = 'active' AND agent_id IS NOT NULL ${
|
|
15289
|
+
const byScopeRows = db.query(`SELECT scope, COUNT(*) as c FROM memories WHERE status = 'active' ${scopedCond} GROUP BY scope`).all(...scopedParams);
|
|
15290
|
+
const byCatRows = db.query(`SELECT category, COUNT(*) as c FROM memories WHERE status = 'active' ${scopedCond} GROUP BY category`).all(...scopedParams);
|
|
15291
|
+
const topMems = db.query(`SELECT id, key, value, importance, scope, category FROM memories WHERE status = 'active' ${scopedCond} ORDER BY importance DESC, access_count DESC LIMIT 5`).all(...scopedParams);
|
|
15292
|
+
const topAgents = db.query(`SELECT agent_id, COUNT(*) as c FROM memories WHERE status = 'active' AND agent_id IS NOT NULL ${scopedCond} GROUP BY agent_id ORDER BY c DESC LIMIT 5`).all(...scopedParams);
|
|
15040
15293
|
return json({
|
|
15041
15294
|
total,
|
|
15042
15295
|
pinned,
|
|
@@ -15048,6 +15301,9 @@ addRoute("GET", "/api/report", (_req, url) => {
|
|
|
15048
15301
|
top_agents: topAgents
|
|
15049
15302
|
});
|
|
15050
15303
|
});
|
|
15304
|
+
|
|
15305
|
+
// src/server/routes/memories-search.ts
|
|
15306
|
+
init_search();
|
|
15051
15307
|
addRoute("POST", "/api/memories/search", async (req) => {
|
|
15052
15308
|
const body = await readJson(req);
|
|
15053
15309
|
if (!body || typeof body["query"] !== "string") {
|
|
@@ -15109,6 +15365,51 @@ addRoute("POST", "/api/memories/search/bm25", async (req) => {
|
|
|
15109
15365
|
const results = searchWithBm25(body["query"], filter);
|
|
15110
15366
|
return json({ results, count: results.length });
|
|
15111
15367
|
});
|
|
15368
|
+
|
|
15369
|
+
// src/server/routes/memories-bulk.ts
|
|
15370
|
+
init_memories();
|
|
15371
|
+
addRoute("POST", "/api/memories/bulk-forget", async (req) => {
|
|
15372
|
+
const body = await readJson(req);
|
|
15373
|
+
if (!body || !Array.isArray(body["ids"])) {
|
|
15374
|
+
return errorResponse("Missing required field: ids (array)", 400);
|
|
15375
|
+
}
|
|
15376
|
+
const ids = body["ids"];
|
|
15377
|
+
let deleted = 0;
|
|
15378
|
+
for (const id of ids) {
|
|
15379
|
+
try {
|
|
15380
|
+
if (deleteMemory(id))
|
|
15381
|
+
deleted++;
|
|
15382
|
+
} catch {}
|
|
15383
|
+
}
|
|
15384
|
+
return json({ deleted, total: ids.length });
|
|
15385
|
+
});
|
|
15386
|
+
addRoute("POST", "/api/memories/bulk-update", async (req) => {
|
|
15387
|
+
const body = await readJson(req);
|
|
15388
|
+
if (!body || !Array.isArray(body["ids"])) {
|
|
15389
|
+
return errorResponse("Missing required fields: ids (array)", 400);
|
|
15390
|
+
}
|
|
15391
|
+
const ids = body["ids"];
|
|
15392
|
+
const { ids: _ids, ...fields } = body;
|
|
15393
|
+
let updated = 0;
|
|
15394
|
+
const errors = [];
|
|
15395
|
+
for (const id of ids) {
|
|
15396
|
+
try {
|
|
15397
|
+
const memory = getMemory(id);
|
|
15398
|
+
if (memory) {
|
|
15399
|
+
updateMemory(id, { ...fields, version: memory.version });
|
|
15400
|
+
updated++;
|
|
15401
|
+
} else {
|
|
15402
|
+
errors.push(`Memory not found: ${id}`);
|
|
15403
|
+
}
|
|
15404
|
+
} catch (e) {
|
|
15405
|
+
errors.push(`Failed ${id}: ${e instanceof Error ? e.message : String(e)}`);
|
|
15406
|
+
}
|
|
15407
|
+
}
|
|
15408
|
+
return json({ updated, errors, total: ids.length });
|
|
15409
|
+
});
|
|
15410
|
+
|
|
15411
|
+
// src/server/routes/memories-io.ts
|
|
15412
|
+
init_memories();
|
|
15112
15413
|
addRoute("POST", "/api/memories/export", async (req) => {
|
|
15113
15414
|
const body = await readJson(req) || {};
|
|
15114
15415
|
const filter = {};
|
|
@@ -15149,49 +15450,35 @@ addRoute("POST", "/api/memories/import", async (req) => {
|
|
|
15149
15450
|
}
|
|
15150
15451
|
return json({ imported, errors, total: memoriesArr.length }, 201);
|
|
15151
15452
|
});
|
|
15152
|
-
|
|
15153
|
-
|
|
15154
|
-
|
|
15155
|
-
|
|
15156
|
-
|
|
15157
|
-
|
|
15158
|
-
|
|
15159
|
-
|
|
15160
|
-
try {
|
|
15161
|
-
if (deleteMemory(id))
|
|
15162
|
-
deleted++;
|
|
15163
|
-
} catch {}
|
|
15164
|
-
}
|
|
15165
|
-
return json({ deleted, total: ids.length });
|
|
15166
|
-
});
|
|
15167
|
-
addRoute("POST", "/api/memories/bulk-update", async (req) => {
|
|
15168
|
-
const body = await readJson(req);
|
|
15169
|
-
if (!body || !Array.isArray(body["ids"])) {
|
|
15170
|
-
return errorResponse("Missing required fields: ids (array)", 400);
|
|
15453
|
+
|
|
15454
|
+
// src/server/routes/memories-misc.ts
|
|
15455
|
+
init_memories();
|
|
15456
|
+
|
|
15457
|
+
// src/lib/machine-visibility.ts
|
|
15458
|
+
function resolveVisibleMachineId(machineId, db) {
|
|
15459
|
+
if (machineId !== undefined) {
|
|
15460
|
+
return machineId;
|
|
15171
15461
|
}
|
|
15172
|
-
|
|
15173
|
-
|
|
15174
|
-
|
|
15175
|
-
|
|
15176
|
-
for (const id of ids) {
|
|
15177
|
-
try {
|
|
15178
|
-
const memory = getMemory(id);
|
|
15179
|
-
if (memory) {
|
|
15180
|
-
updateMemory(id, { ...fields, version: memory.version });
|
|
15181
|
-
updated++;
|
|
15182
|
-
} else {
|
|
15183
|
-
errors.push(`Memory not found: ${id}`);
|
|
15184
|
-
}
|
|
15185
|
-
} catch (e) {
|
|
15186
|
-
errors.push(`Failed ${id}: ${e instanceof Error ? e.message : String(e)}`);
|
|
15187
|
-
}
|
|
15462
|
+
try {
|
|
15463
|
+
return getCurrentMachineId(db);
|
|
15464
|
+
} catch {
|
|
15465
|
+
return null;
|
|
15188
15466
|
}
|
|
15189
|
-
|
|
15467
|
+
}
|
|
15468
|
+
function visibleToMachineFilter(machineId, db) {
|
|
15469
|
+
return {
|
|
15470
|
+
visible_to_machine_id: resolveVisibleMachineId(machineId, db)
|
|
15471
|
+
};
|
|
15472
|
+
}
|
|
15473
|
+
|
|
15474
|
+
// src/server/routes/memories-misc.ts
|
|
15475
|
+
addRoute("GET", "/api/health", () => {
|
|
15476
|
+
return json({ ok: true, version: "1", db: getDbPath() });
|
|
15190
15477
|
});
|
|
15191
15478
|
addRoute("POST", "/api/memories/extract", async (req) => {
|
|
15192
15479
|
const body = await readJson(req);
|
|
15193
15480
|
if (!body)
|
|
15194
|
-
return
|
|
15481
|
+
return json({ error: "Invalid JSON body" }, 400);
|
|
15195
15482
|
const sessionId = body["session_id"];
|
|
15196
15483
|
const agentId = body["agent_id"];
|
|
15197
15484
|
const projectId = body["project_id"];
|
|
@@ -15259,77 +15546,6 @@ addRoute("POST", "/api/memories/clean", () => {
|
|
|
15259
15546
|
const cleaned = cleanExpiredMemories();
|
|
15260
15547
|
return json({ cleaned });
|
|
15261
15548
|
});
|
|
15262
|
-
addRoute("POST", "/api/memories", async (req) => {
|
|
15263
|
-
const body = await readJson(req);
|
|
15264
|
-
if (!body) {
|
|
15265
|
-
return errorResponse("Invalid JSON body", 400);
|
|
15266
|
-
}
|
|
15267
|
-
if (!body["key"] || !body["value"]) {
|
|
15268
|
-
return errorResponse("Missing required fields: key, value", 400);
|
|
15269
|
-
}
|
|
15270
|
-
try {
|
|
15271
|
-
if (body["ttl_ms"] !== undefined && typeof body["ttl_ms"] === "string") {
|
|
15272
|
-
body["ttl_ms"] = parseDuration(body["ttl_ms"]);
|
|
15273
|
-
}
|
|
15274
|
-
const memory = createMemory(body);
|
|
15275
|
-
return json(memory, 201);
|
|
15276
|
-
} catch (e) {
|
|
15277
|
-
if (e instanceof DuplicateMemoryError) {
|
|
15278
|
-
return errorResponse(e.message, 409);
|
|
15279
|
-
}
|
|
15280
|
-
throw e;
|
|
15281
|
-
}
|
|
15282
|
-
});
|
|
15283
|
-
addRoute("GET", "/api/memories/:id", (_req, _url, params) => {
|
|
15284
|
-
const memory = getMemory(params["id"]);
|
|
15285
|
-
if (!memory) {
|
|
15286
|
-
return errorResponse("Memory not found", 404);
|
|
15287
|
-
}
|
|
15288
|
-
touchMemory(memory.id);
|
|
15289
|
-
return json(memory);
|
|
15290
|
-
});
|
|
15291
|
-
addRoute("PATCH", "/api/memories/:id", async (req, _url, params) => {
|
|
15292
|
-
const body = await readJson(req);
|
|
15293
|
-
if (!body) {
|
|
15294
|
-
return errorResponse("Invalid JSON body", 400);
|
|
15295
|
-
}
|
|
15296
|
-
const updateBody = { ...body };
|
|
15297
|
-
if (updateBody["version"] === undefined) {
|
|
15298
|
-
const existing = getMemory(params["id"]);
|
|
15299
|
-
if (!existing)
|
|
15300
|
-
return errorResponse("Memory not found", 404);
|
|
15301
|
-
updateBody["version"] = existing.version;
|
|
15302
|
-
}
|
|
15303
|
-
try {
|
|
15304
|
-
const memory = updateMemory(params["id"], updateBody);
|
|
15305
|
-
return json(memory);
|
|
15306
|
-
} catch (e) {
|
|
15307
|
-
if (e instanceof MemoryNotFoundError) {
|
|
15308
|
-
return errorResponse(e.message, 404);
|
|
15309
|
-
}
|
|
15310
|
-
if (e instanceof VersionConflictError) {
|
|
15311
|
-
return errorResponse(e.message, 409, {
|
|
15312
|
-
expected: e.expected,
|
|
15313
|
-
actual: e.actual
|
|
15314
|
-
});
|
|
15315
|
-
}
|
|
15316
|
-
throw e;
|
|
15317
|
-
}
|
|
15318
|
-
});
|
|
15319
|
-
addRoute("GET", "/api/memories/:id/versions", (_req, _url, params) => {
|
|
15320
|
-
const memory = getMemory(params["id"]);
|
|
15321
|
-
if (!memory)
|
|
15322
|
-
return errorResponse("Memory not found", 404);
|
|
15323
|
-
const versions = getMemoryVersions(memory.id);
|
|
15324
|
-
return json({ versions, count: versions.length, current_version: memory.version });
|
|
15325
|
-
});
|
|
15326
|
-
addRoute("DELETE", "/api/memories/:id", (_req, _url, params) => {
|
|
15327
|
-
const deleted = deleteMemory(params["id"]);
|
|
15328
|
-
if (!deleted) {
|
|
15329
|
-
return errorResponse("Memory not found", 404);
|
|
15330
|
-
}
|
|
15331
|
-
return json({ deleted: true });
|
|
15332
|
-
});
|
|
15333
15549
|
addRoute("GET", "/api/inject", (_req, url) => {
|
|
15334
15550
|
const q = getSearchParams(url);
|
|
15335
15551
|
const maxTokens = q["max_tokens"] ? parseInt(q["max_tokens"], 10) : 500;
|
|
@@ -15339,6 +15555,7 @@ addRoute("GET", "/api/inject", (_req, url) => {
|
|
|
15339
15555
|
"fact",
|
|
15340
15556
|
"knowledge"
|
|
15341
15557
|
];
|
|
15558
|
+
const visibleMachineId = resolveVisibleMachineId(q["machine_id"]);
|
|
15342
15559
|
const allMemories = [];
|
|
15343
15560
|
const globalMems = listMemories({
|
|
15344
15561
|
scope: "global",
|
|
@@ -15346,6 +15563,7 @@ addRoute("GET", "/api/inject", (_req, url) => {
|
|
|
15346
15563
|
min_importance: minImportance,
|
|
15347
15564
|
status: "active",
|
|
15348
15565
|
project_id: q["project_id"],
|
|
15566
|
+
...visibleToMachineFilter(visibleMachineId),
|
|
15349
15567
|
limit: 50
|
|
15350
15568
|
});
|
|
15351
15569
|
allMemories.push(...globalMems);
|
|
@@ -15356,6 +15574,7 @@ addRoute("GET", "/api/inject", (_req, url) => {
|
|
|
15356
15574
|
min_importance: minImportance,
|
|
15357
15575
|
status: "active",
|
|
15358
15576
|
project_id: q["project_id"],
|
|
15577
|
+
...visibleToMachineFilter(visibleMachineId),
|
|
15359
15578
|
limit: 50
|
|
15360
15579
|
});
|
|
15361
15580
|
allMemories.push(...sharedMems);
|
|
@@ -15367,6 +15586,7 @@ addRoute("GET", "/api/inject", (_req, url) => {
|
|
|
15367
15586
|
min_importance: minImportance,
|
|
15368
15587
|
status: "active",
|
|
15369
15588
|
agent_id: q["agent_id"],
|
|
15589
|
+
...visibleToMachineFilter(visibleMachineId),
|
|
15370
15590
|
limit: 50
|
|
15371
15591
|
});
|
|
15372
15592
|
allMemories.push(...privateMems);
|
|
@@ -16028,10 +16248,133 @@ addRoute("GET", "/api/graph/:entityId", (_req, url, params) => {
|
|
|
16028
16248
|
}
|
|
16029
16249
|
});
|
|
16030
16250
|
|
|
16031
|
-
// src/server/routes/system.ts
|
|
16251
|
+
// src/server/routes/system-auto-memory.ts
|
|
16032
16252
|
init_auto_memory();
|
|
16033
16253
|
init_registry();
|
|
16254
|
+
function registerSystemAutoMemoryRoutes() {
|
|
16255
|
+
addRoute("POST", "/api/auto-memory/process", async (req) => {
|
|
16256
|
+
const body = await readJson(req);
|
|
16257
|
+
const turn = body?.turn;
|
|
16258
|
+
if (!turn)
|
|
16259
|
+
return errorResponse("turn is required", 400);
|
|
16260
|
+
processConversationTurn(turn, { agentId: body?.agent_id, projectId: body?.project_id, sessionId: body?.session_id });
|
|
16261
|
+
const stats = getAutoMemoryStats();
|
|
16262
|
+
return json({ queued: true, queue: stats }, 202);
|
|
16263
|
+
});
|
|
16264
|
+
addRoute("GET", "/api/auto-memory/status", () => {
|
|
16265
|
+
return json({
|
|
16266
|
+
queue: getAutoMemoryStats(),
|
|
16267
|
+
config: providerRegistry.getConfig(),
|
|
16268
|
+
providers: providerRegistry.health()
|
|
16269
|
+
});
|
|
16270
|
+
});
|
|
16271
|
+
addRoute("GET", "/api/auto-memory/config", () => {
|
|
16272
|
+
return json(providerRegistry.getConfig());
|
|
16273
|
+
});
|
|
16274
|
+
addRoute("PATCH", "/api/auto-memory/config", async (req) => {
|
|
16275
|
+
const body = await readJson(req) ?? {};
|
|
16276
|
+
const patch = {};
|
|
16277
|
+
if (body.provider)
|
|
16278
|
+
patch.provider = body.provider;
|
|
16279
|
+
if (body.model)
|
|
16280
|
+
patch.model = body.model;
|
|
16281
|
+
if (body.enabled !== undefined)
|
|
16282
|
+
patch.enabled = Boolean(body.enabled);
|
|
16283
|
+
if (body.min_importance !== undefined)
|
|
16284
|
+
patch.minImportance = Number(body.min_importance);
|
|
16285
|
+
if (body.auto_entity_link !== undefined)
|
|
16286
|
+
patch.autoEntityLink = Boolean(body.auto_entity_link);
|
|
16287
|
+
configureAutoMemory(patch);
|
|
16288
|
+
return json({ updated: true, config: providerRegistry.getConfig() });
|
|
16289
|
+
});
|
|
16290
|
+
addRoute("POST", "/api/auto-memory/test", async (req) => {
|
|
16291
|
+
const body = await readJson(req) ?? {};
|
|
16292
|
+
const { turn, provider: providerName, agent_id, project_id } = body;
|
|
16293
|
+
if (!turn)
|
|
16294
|
+
return errorResponse("turn is required", 400);
|
|
16295
|
+
const provider = providerName ? providerRegistry.getProvider(providerName) : providerRegistry.getAvailable();
|
|
16296
|
+
if (!provider)
|
|
16297
|
+
return errorResponse("No LLM provider configured. Set an API key (ANTHROPIC_API_KEY, OPENAI_API_KEY, CEREBRAS_API_KEY, or XAI_API_KEY).", 503);
|
|
16298
|
+
const memories = await provider.extractMemories(turn, { agentId: agent_id, projectId: project_id });
|
|
16299
|
+
return json({
|
|
16300
|
+
provider: provider.name,
|
|
16301
|
+
model: provider.config.model,
|
|
16302
|
+
extracted: memories,
|
|
16303
|
+
count: memories.length,
|
|
16304
|
+
note: "DRY RUN \u2014 nothing was saved"
|
|
16305
|
+
});
|
|
16306
|
+
});
|
|
16307
|
+
}
|
|
16308
|
+
|
|
16309
|
+
// src/server/routes/system-hooks.ts
|
|
16034
16310
|
init_hooks();
|
|
16311
|
+
function registerSystemHookRoutes() {
|
|
16312
|
+
addRoute("GET", "/api/hooks", (_req, url) => {
|
|
16313
|
+
const type = url.searchParams.get("type") ?? undefined;
|
|
16314
|
+
const hooks = hookRegistry.list(type);
|
|
16315
|
+
return json(hooks.map((h) => ({
|
|
16316
|
+
id: h.id,
|
|
16317
|
+
type: h.type,
|
|
16318
|
+
blocking: h.blocking,
|
|
16319
|
+
priority: h.priority,
|
|
16320
|
+
builtin: h.builtin ?? false,
|
|
16321
|
+
agentId: h.agentId,
|
|
16322
|
+
projectId: h.projectId,
|
|
16323
|
+
description: h.description
|
|
16324
|
+
})));
|
|
16325
|
+
});
|
|
16326
|
+
addRoute("GET", "/api/hooks/stats", () => json(hookRegistry.stats()));
|
|
16327
|
+
addRoute("GET", "/api/webhooks", (_req, url) => {
|
|
16328
|
+
const type = url.searchParams.get("type") ?? undefined;
|
|
16329
|
+
const enabledParam = url.searchParams.get("enabled");
|
|
16330
|
+
const enabled = enabledParam !== null ? enabledParam === "true" : undefined;
|
|
16331
|
+
return json(listWebhookHooks({
|
|
16332
|
+
type,
|
|
16333
|
+
enabled
|
|
16334
|
+
}));
|
|
16335
|
+
});
|
|
16336
|
+
addRoute("POST", "/api/webhooks", async (req) => {
|
|
16337
|
+
const body = await readJson(req) ?? {};
|
|
16338
|
+
if (!body.type || !body.handler_url) {
|
|
16339
|
+
return errorResponse("type and handler_url are required", 400);
|
|
16340
|
+
}
|
|
16341
|
+
const wh = createWebhookHook({
|
|
16342
|
+
type: body.type,
|
|
16343
|
+
handlerUrl: body.handler_url,
|
|
16344
|
+
priority: body.priority,
|
|
16345
|
+
blocking: body.blocking,
|
|
16346
|
+
agentId: body.agent_id,
|
|
16347
|
+
projectId: body.project_id,
|
|
16348
|
+
description: body.description
|
|
16349
|
+
});
|
|
16350
|
+
reloadWebhooks();
|
|
16351
|
+
return json(wh, 201);
|
|
16352
|
+
});
|
|
16353
|
+
addRoute("GET", "/api/webhooks/:id", (_req, _url, params) => {
|
|
16354
|
+
const wh = getWebhookHook(params["id"]);
|
|
16355
|
+
if (!wh)
|
|
16356
|
+
return errorResponse("Webhook not found", 404);
|
|
16357
|
+
return json(wh);
|
|
16358
|
+
});
|
|
16359
|
+
addRoute("PATCH", "/api/webhooks/:id", async (req, _url, params) => {
|
|
16360
|
+
const body = await readJson(req) ?? {};
|
|
16361
|
+
const updated = updateWebhookHook(params["id"], {
|
|
16362
|
+
enabled: body.enabled,
|
|
16363
|
+
priority: body.priority,
|
|
16364
|
+
description: body.description
|
|
16365
|
+
});
|
|
16366
|
+
if (!updated)
|
|
16367
|
+
return errorResponse("Webhook not found", 404);
|
|
16368
|
+
reloadWebhooks();
|
|
16369
|
+
return json(updated);
|
|
16370
|
+
});
|
|
16371
|
+
addRoute("DELETE", "/api/webhooks/:id", (_req, _url, params) => {
|
|
16372
|
+
const deleted = deleteWebhookHook(params["id"]);
|
|
16373
|
+
if (!deleted)
|
|
16374
|
+
return errorResponse("Webhook not found", 404);
|
|
16375
|
+
return new Response(null, { status: 204 });
|
|
16376
|
+
});
|
|
16377
|
+
}
|
|
16035
16378
|
|
|
16036
16379
|
// src/lib/synthesis/index.ts
|
|
16037
16380
|
init_database();
|
|
@@ -16935,9 +17278,49 @@ function getSynthesisStatus(runId, projectId, db) {
|
|
|
16935
17278
|
};
|
|
16936
17279
|
}
|
|
16937
17280
|
|
|
16938
|
-
// src/server/routes/system.ts
|
|
17281
|
+
// src/server/routes/system-synthesis.ts
|
|
16939
17282
|
init_synthesis();
|
|
16940
17283
|
init_profile_synthesizer();
|
|
17284
|
+
function registerSystemSynthesisRoutes() {
|
|
17285
|
+
addRoute("POST", "/api/synthesis/run", async (req) => {
|
|
17286
|
+
const body = await readJson(req) ?? {};
|
|
17287
|
+
const result = await runSynthesis({
|
|
17288
|
+
projectId: body.project_id,
|
|
17289
|
+
agentId: body.agent_id,
|
|
17290
|
+
dryRun: body.dry_run,
|
|
17291
|
+
maxProposals: body.max_proposals,
|
|
17292
|
+
provider: body.provider
|
|
17293
|
+
});
|
|
17294
|
+
return json(result, result.dryRun ? 200 : 201);
|
|
17295
|
+
});
|
|
17296
|
+
addRoute("GET", "/api/synthesis/runs", (_req, url) => {
|
|
17297
|
+
const projectId = url.searchParams.get("project_id") ?? undefined;
|
|
17298
|
+
const limit = url.searchParams.get("limit") ? parseInt(url.searchParams.get("limit")) : 20;
|
|
17299
|
+
const runs = listSynthesisRuns({ project_id: projectId, limit });
|
|
17300
|
+
return json({ runs, count: runs.length });
|
|
17301
|
+
});
|
|
17302
|
+
addRoute("GET", "/api/synthesis/status", (_req, url) => {
|
|
17303
|
+
const projectId = url.searchParams.get("project_id") ?? undefined;
|
|
17304
|
+
const runId = url.searchParams.get("run_id") ?? undefined;
|
|
17305
|
+
return json(getSynthesisStatus(runId, projectId));
|
|
17306
|
+
});
|
|
17307
|
+
addRoute("POST", "/api/synthesis/rollback/:run_id", async (_req, _url, params) => {
|
|
17308
|
+
const result = await rollbackSynthesis(params["run_id"]);
|
|
17309
|
+
return json(result);
|
|
17310
|
+
});
|
|
17311
|
+
addRoute("GET", "/api/profile/synthesize", async (_req, url) => {
|
|
17312
|
+
const q = getSearchParams(url);
|
|
17313
|
+
const result = await synthesizeProfile({
|
|
17314
|
+
project_id: q["project_id"] || undefined,
|
|
17315
|
+
agent_id: q["agent_id"] || undefined,
|
|
17316
|
+
force_refresh: q["force_refresh"] === "true"
|
|
17317
|
+
});
|
|
17318
|
+
if (!result) {
|
|
17319
|
+
return json({ profile: null, message: "No preference/fact memories found to synthesize" });
|
|
17320
|
+
}
|
|
17321
|
+
return json(result);
|
|
17322
|
+
});
|
|
17323
|
+
}
|
|
16941
17324
|
|
|
16942
17325
|
// src/lib/session-auto-resolve.ts
|
|
16943
17326
|
function autoResolveAgentProject(metadata, db) {
|
|
@@ -17006,255 +17389,136 @@ function autoResolveAgentProject(metadata, db) {
|
|
|
17006
17389
|
};
|
|
17007
17390
|
}
|
|
17008
17391
|
|
|
17009
|
-
// src/server/routes/system.ts
|
|
17010
|
-
|
|
17011
|
-
addRoute("POST", "/api/
|
|
17012
|
-
|
|
17013
|
-
|
|
17014
|
-
|
|
17015
|
-
|
|
17016
|
-
|
|
17017
|
-
|
|
17018
|
-
|
|
17019
|
-
|
|
17020
|
-
|
|
17021
|
-
|
|
17022
|
-
|
|
17023
|
-
|
|
17024
|
-
|
|
17392
|
+
// src/server/routes/system-sessions.ts
|
|
17393
|
+
function registerSystemSessionRoutes() {
|
|
17394
|
+
addRoute("POST", "/api/sessions/ingest", async (req) => {
|
|
17395
|
+
const body = await readJson(req) ?? {};
|
|
17396
|
+
const { transcript, session_id, agent_id, project_id, source, metadata } = body;
|
|
17397
|
+
if (!transcript || typeof transcript !== "string")
|
|
17398
|
+
return errorResponse("transcript is required", 400);
|
|
17399
|
+
if (!session_id || typeof session_id !== "string")
|
|
17400
|
+
return errorResponse("session_id is required", 400);
|
|
17401
|
+
let resolvedAgentId = agent_id;
|
|
17402
|
+
let resolvedProjectId = project_id;
|
|
17403
|
+
if (!resolvedAgentId || !resolvedProjectId) {
|
|
17404
|
+
const resolved = autoResolveAgentProject(metadata ?? {});
|
|
17405
|
+
if (!resolvedAgentId && resolved.agentId)
|
|
17406
|
+
resolvedAgentId = resolved.agentId;
|
|
17407
|
+
if (!resolvedProjectId && resolved.projectId)
|
|
17408
|
+
resolvedProjectId = resolved.projectId;
|
|
17409
|
+
}
|
|
17410
|
+
const job = createSessionJob({
|
|
17411
|
+
session_id,
|
|
17412
|
+
transcript,
|
|
17413
|
+
source: source ?? "manual",
|
|
17414
|
+
agent_id: resolvedAgentId,
|
|
17415
|
+
project_id: resolvedProjectId,
|
|
17416
|
+
metadata: metadata ?? {}
|
|
17417
|
+
});
|
|
17418
|
+
enqueueSessionJob(job.id);
|
|
17419
|
+
return json({ job_id: job.id, status: "queued", message: "Session queued for memory extraction" }, 202);
|
|
17025
17420
|
});
|
|
17026
|
-
|
|
17027
|
-
|
|
17028
|
-
|
|
17029
|
-
|
|
17030
|
-
|
|
17031
|
-
|
|
17032
|
-
|
|
17033
|
-
if (body.provider)
|
|
17034
|
-
patch.provider = body.provider;
|
|
17035
|
-
if (body.model)
|
|
17036
|
-
patch.model = body.model;
|
|
17037
|
-
if (body.enabled !== undefined)
|
|
17038
|
-
patch.enabled = Boolean(body.enabled);
|
|
17039
|
-
if (body.min_importance !== undefined)
|
|
17040
|
-
patch.minImportance = Number(body.min_importance);
|
|
17041
|
-
if (body.auto_entity_link !== undefined)
|
|
17042
|
-
patch.autoEntityLink = Boolean(body.auto_entity_link);
|
|
17043
|
-
configureAutoMemory(patch);
|
|
17044
|
-
return json({ updated: true, config: providerRegistry.getConfig() });
|
|
17045
|
-
});
|
|
17046
|
-
addRoute("POST", "/api/auto-memory/test", async (req) => {
|
|
17047
|
-
const body = await readJson(req) ?? {};
|
|
17048
|
-
const { turn, provider: providerName, agent_id, project_id } = body;
|
|
17049
|
-
if (!turn)
|
|
17050
|
-
return errorResponse("turn is required", 400);
|
|
17051
|
-
const provider = providerName ? providerRegistry.getProvider(providerName) : providerRegistry.getAvailable();
|
|
17052
|
-
if (!provider)
|
|
17053
|
-
return errorResponse("No LLM provider configured. Set an API key (ANTHROPIC_API_KEY, OPENAI_API_KEY, CEREBRAS_API_KEY, or XAI_API_KEY).", 503);
|
|
17054
|
-
const memories = await provider.extractMemories(turn, { agentId: agent_id, projectId: project_id });
|
|
17055
|
-
return json({
|
|
17056
|
-
provider: provider.name,
|
|
17057
|
-
model: provider.config.model,
|
|
17058
|
-
extracted: memories,
|
|
17059
|
-
count: memories.length,
|
|
17060
|
-
note: "DRY RUN \u2014 nothing was saved"
|
|
17421
|
+
addRoute("GET", "/api/sessions/jobs", (_req, url) => {
|
|
17422
|
+
const agentId = url.searchParams.get("agent_id") ?? undefined;
|
|
17423
|
+
const projectId = url.searchParams.get("project_id") ?? undefined;
|
|
17424
|
+
const status = url.searchParams.get("status") ?? undefined;
|
|
17425
|
+
const limit = url.searchParams.get("limit") ? parseInt(url.searchParams.get("limit")) : 20;
|
|
17426
|
+
const jobs = listSessionJobs({ agent_id: agentId, project_id: projectId, status, limit });
|
|
17427
|
+
return json({ jobs, count: jobs.length });
|
|
17061
17428
|
});
|
|
17062
|
-
|
|
17063
|
-
|
|
17064
|
-
|
|
17065
|
-
|
|
17066
|
-
|
|
17067
|
-
id: h.id,
|
|
17068
|
-
type: h.type,
|
|
17069
|
-
blocking: h.blocking,
|
|
17070
|
-
priority: h.priority,
|
|
17071
|
-
builtin: h.builtin ?? false,
|
|
17072
|
-
agentId: h.agentId,
|
|
17073
|
-
projectId: h.projectId,
|
|
17074
|
-
description: h.description
|
|
17075
|
-
})));
|
|
17076
|
-
});
|
|
17077
|
-
addRoute("GET", "/api/hooks/stats", () => json(hookRegistry.stats()));
|
|
17078
|
-
addRoute("GET", "/api/webhooks", (_req, url) => {
|
|
17079
|
-
const type = url.searchParams.get("type") ?? undefined;
|
|
17080
|
-
const enabledParam = url.searchParams.get("enabled");
|
|
17081
|
-
const enabled = enabledParam !== null ? enabledParam === "true" : undefined;
|
|
17082
|
-
return json(listWebhookHooks({
|
|
17083
|
-
type,
|
|
17084
|
-
enabled
|
|
17085
|
-
}));
|
|
17086
|
-
});
|
|
17087
|
-
addRoute("POST", "/api/webhooks", async (req) => {
|
|
17088
|
-
const body = await readJson(req) ?? {};
|
|
17089
|
-
if (!body.type || !body.handler_url) {
|
|
17090
|
-
return errorResponse("type and handler_url are required", 400);
|
|
17091
|
-
}
|
|
17092
|
-
const wh = createWebhookHook({
|
|
17093
|
-
type: body.type,
|
|
17094
|
-
handlerUrl: body.handler_url,
|
|
17095
|
-
priority: body.priority,
|
|
17096
|
-
blocking: body.blocking,
|
|
17097
|
-
agentId: body.agent_id,
|
|
17098
|
-
projectId: body.project_id,
|
|
17099
|
-
description: body.description
|
|
17429
|
+
addRoute("GET", "/api/sessions/jobs/:id", (_req, _url, params) => {
|
|
17430
|
+
const job = getSessionJob(params["id"]);
|
|
17431
|
+
if (!job)
|
|
17432
|
+
return errorResponse("Session job not found", 404);
|
|
17433
|
+
return json(job);
|
|
17100
17434
|
});
|
|
17101
|
-
|
|
17102
|
-
|
|
17103
|
-
|
|
17104
|
-
|
|
17105
|
-
|
|
17106
|
-
|
|
17107
|
-
|
|
17108
|
-
|
|
17109
|
-
|
|
17110
|
-
|
|
17111
|
-
|
|
17112
|
-
|
|
17113
|
-
enabled: body.enabled,
|
|
17114
|
-
priority: body.priority,
|
|
17115
|
-
description: body.description
|
|
17435
|
+
addRoute("GET", "/api/sessions/queue/stats", () => json(getSessionQueueStats()));
|
|
17436
|
+
}
|
|
17437
|
+
|
|
17438
|
+
// src/server/routes/system-tools.ts
|
|
17439
|
+
function registerSystemToolRoutes() {
|
|
17440
|
+
addRoute("POST", "/api/tool-events", async (req) => {
|
|
17441
|
+
const body = await readJson(req);
|
|
17442
|
+
if (!body || !body["tool_name"]) {
|
|
17443
|
+
return errorResponse("Missing required field: tool_name", 400);
|
|
17444
|
+
}
|
|
17445
|
+
const event = saveToolEvent(body);
|
|
17446
|
+
return json(event, 201);
|
|
17116
17447
|
});
|
|
17117
|
-
|
|
17118
|
-
|
|
17119
|
-
|
|
17120
|
-
|
|
17121
|
-
|
|
17122
|
-
|
|
17123
|
-
|
|
17124
|
-
|
|
17125
|
-
|
|
17126
|
-
|
|
17127
|
-
|
|
17128
|
-
|
|
17129
|
-
|
|
17130
|
-
|
|
17131
|
-
|
|
17132
|
-
|
|
17133
|
-
|
|
17134
|
-
|
|
17135
|
-
|
|
17448
|
+
addRoute("GET", "/api/tool-events", (_req, url) => {
|
|
17449
|
+
const q = getSearchParams(url);
|
|
17450
|
+
const filters = {};
|
|
17451
|
+
if (q["tool_name"])
|
|
17452
|
+
filters.tool_name = q["tool_name"];
|
|
17453
|
+
if (q["agent_id"])
|
|
17454
|
+
filters.agent_id = q["agent_id"];
|
|
17455
|
+
if (q["project_id"])
|
|
17456
|
+
filters.project_id = q["project_id"];
|
|
17457
|
+
if (q["success"] !== undefined && q["success"] !== "")
|
|
17458
|
+
filters.success = q["success"] === "true";
|
|
17459
|
+
if (q["from_date"])
|
|
17460
|
+
filters.from_date = q["from_date"];
|
|
17461
|
+
if (q["to_date"])
|
|
17462
|
+
filters.to_date = q["to_date"];
|
|
17463
|
+
if (q["limit"])
|
|
17464
|
+
filters.limit = parseInt(q["limit"], 10);
|
|
17465
|
+
if (q["offset"])
|
|
17466
|
+
filters.offset = parseInt(q["offset"], 10);
|
|
17467
|
+
const events = getToolEvents(filters);
|
|
17468
|
+
return json({ events, count: events.length });
|
|
17136
17469
|
});
|
|
17137
|
-
|
|
17138
|
-
|
|
17139
|
-
|
|
17140
|
-
|
|
17141
|
-
|
|
17142
|
-
|
|
17143
|
-
|
|
17144
|
-
});
|
|
17145
|
-
addRoute("GET", "/api/synthesis/status", (_req, url) => {
|
|
17146
|
-
const projectId = url.searchParams.get("project_id") ?? undefined;
|
|
17147
|
-
const runId = url.searchParams.get("run_id") ?? undefined;
|
|
17148
|
-
return json(getSynthesisStatus(runId, projectId));
|
|
17149
|
-
});
|
|
17150
|
-
addRoute("POST", "/api/synthesis/rollback/:run_id", async (_req, _url, params) => {
|
|
17151
|
-
const result = await rollbackSynthesis(params["run_id"]);
|
|
17152
|
-
return json(result);
|
|
17153
|
-
});
|
|
17154
|
-
addRoute("POST", "/api/sessions/ingest", async (req) => {
|
|
17155
|
-
const body = await readJson(req) ?? {};
|
|
17156
|
-
const { transcript, session_id, agent_id, project_id, source, metadata } = body;
|
|
17157
|
-
if (!transcript || typeof transcript !== "string")
|
|
17158
|
-
return errorResponse("transcript is required", 400);
|
|
17159
|
-
if (!session_id || typeof session_id !== "string")
|
|
17160
|
-
return errorResponse("session_id is required", 400);
|
|
17161
|
-
let resolvedAgentId = agent_id;
|
|
17162
|
-
let resolvedProjectId = project_id;
|
|
17163
|
-
if (!resolvedAgentId || !resolvedProjectId) {
|
|
17164
|
-
const resolved = autoResolveAgentProject(metadata ?? {});
|
|
17165
|
-
if (!resolvedAgentId && resolved.agentId)
|
|
17166
|
-
resolvedAgentId = resolved.agentId;
|
|
17167
|
-
if (!resolvedProjectId && resolved.projectId)
|
|
17168
|
-
resolvedProjectId = resolved.projectId;
|
|
17169
|
-
}
|
|
17170
|
-
const job = createSessionJob({
|
|
17171
|
-
session_id,
|
|
17172
|
-
transcript,
|
|
17173
|
-
source: source ?? "manual",
|
|
17174
|
-
agent_id: resolvedAgentId,
|
|
17175
|
-
project_id: resolvedProjectId,
|
|
17176
|
-
metadata: metadata ?? {}
|
|
17470
|
+
addRoute("GET", "/api/tool-insights/:tool_name", (_req, url, params) => {
|
|
17471
|
+
const q = getSearchParams(url);
|
|
17472
|
+
const toolName = decodeURIComponent(params["tool_name"]);
|
|
17473
|
+
const projectId = q["project_id"];
|
|
17474
|
+
const lessonsLimit = q["limit"] ? parseInt(q["limit"], 10) : 20;
|
|
17475
|
+
const stats = getToolStats(toolName, projectId || undefined);
|
|
17476
|
+
const lessons = getToolLessons(toolName, projectId || undefined, lessonsLimit);
|
|
17477
|
+
return json({ stats, lessons });
|
|
17177
17478
|
});
|
|
17178
|
-
|
|
17179
|
-
|
|
17180
|
-
|
|
17181
|
-
|
|
17182
|
-
|
|
17183
|
-
|
|
17184
|
-
|
|
17185
|
-
|
|
17186
|
-
|
|
17187
|
-
|
|
17188
|
-
});
|
|
17189
|
-
|
|
17190
|
-
|
|
17191
|
-
if (!job)
|
|
17192
|
-
return errorResponse("Session job not found", 404);
|
|
17193
|
-
return json(job);
|
|
17194
|
-
});
|
|
17195
|
-
addRoute("GET", "/api/sessions/queue/stats", () => json(getSessionQueueStats()));
|
|
17196
|
-
addRoute("POST", "/api/tool-events", async (req) => {
|
|
17197
|
-
const body = await readJson(req);
|
|
17198
|
-
if (!body || !body["tool_name"]) {
|
|
17199
|
-
return errorResponse("Missing required field: tool_name", 400);
|
|
17200
|
-
}
|
|
17201
|
-
const event = saveToolEvent(body);
|
|
17202
|
-
return json(event, 201);
|
|
17203
|
-
});
|
|
17204
|
-
addRoute("GET", "/api/tool-events", (_req, url) => {
|
|
17205
|
-
const q = getSearchParams(url);
|
|
17206
|
-
const filters = {};
|
|
17207
|
-
if (q["tool_name"])
|
|
17208
|
-
filters.tool_name = q["tool_name"];
|
|
17209
|
-
if (q["agent_id"])
|
|
17210
|
-
filters.agent_id = q["agent_id"];
|
|
17211
|
-
if (q["project_id"])
|
|
17212
|
-
filters.project_id = q["project_id"];
|
|
17213
|
-
if (q["success"] !== undefined && q["success"] !== "")
|
|
17214
|
-
filters.success = q["success"] === "true";
|
|
17215
|
-
if (q["from_date"])
|
|
17216
|
-
filters.from_date = q["from_date"];
|
|
17217
|
-
if (q["to_date"])
|
|
17218
|
-
filters.to_date = q["to_date"];
|
|
17219
|
-
if (q["limit"])
|
|
17220
|
-
filters.limit = parseInt(q["limit"], 10);
|
|
17221
|
-
if (q["offset"])
|
|
17222
|
-
filters.offset = parseInt(q["offset"], 10);
|
|
17223
|
-
const events = getToolEvents(filters);
|
|
17224
|
-
return json({ events, count: events.length });
|
|
17225
|
-
});
|
|
17226
|
-
addRoute("GET", "/api/tool-insights/:tool_name", (_req, url, params) => {
|
|
17227
|
-
const q = getSearchParams(url);
|
|
17228
|
-
const toolName = decodeURIComponent(params["tool_name"]);
|
|
17229
|
-
const projectId = q["project_id"];
|
|
17230
|
-
const lessonsLimit = q["limit"] ? parseInt(q["limit"], 10) : 20;
|
|
17231
|
-
const stats = getToolStats(toolName, projectId || undefined);
|
|
17232
|
-
const lessons = getToolLessons(toolName, projectId || undefined, lessonsLimit);
|
|
17233
|
-
return json({ stats, lessons });
|
|
17234
|
-
});
|
|
17235
|
-
addRoute("GET", "/api/profile/synthesize", async (_req, url) => {
|
|
17236
|
-
const q = getSearchParams(url);
|
|
17237
|
-
const result = await synthesizeProfile({
|
|
17238
|
-
project_id: q["project_id"] || undefined,
|
|
17239
|
-
agent_id: q["agent_id"] || undefined,
|
|
17240
|
-
force_refresh: q["force_refresh"] === "true"
|
|
17479
|
+
}
|
|
17480
|
+
|
|
17481
|
+
// src/server/routes/system-chain.ts
|
|
17482
|
+
init_database();
|
|
17483
|
+
function registerSystemChainRoutes() {
|
|
17484
|
+
addRoute("GET", "/api/chains/:sequence_group", (_req, _url, params) => {
|
|
17485
|
+
const db = getDatabase();
|
|
17486
|
+
const sequenceGroup = decodeURIComponent(params["sequence_group"]);
|
|
17487
|
+
const rows = db.query(`SELECT * FROM memories WHERE sequence_group = ? AND status = 'active' ORDER BY sequence_order ASC`).all(sequenceGroup);
|
|
17488
|
+
if (rows.length === 0) {
|
|
17489
|
+
return json({ chain: [], count: 0, sequence_group: sequenceGroup });
|
|
17490
|
+
}
|
|
17491
|
+
return json({ chain: rows, count: rows.length, sequence_group: sequenceGroup });
|
|
17241
17492
|
});
|
|
17242
|
-
|
|
17243
|
-
|
|
17244
|
-
|
|
17245
|
-
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
if (rows.length === 0) {
|
|
17252
|
-
return json({ chain: [], count: 0, sequence_group: sequenceGroup });
|
|
17253
|
-
}
|
|
17254
|
-
return json({ chain: rows, count: rows.length, sequence_group: sequenceGroup });
|
|
17255
|
-
});
|
|
17493
|
+
}
|
|
17494
|
+
|
|
17495
|
+
// src/server/routes/system.ts
|
|
17496
|
+
registerSystemAutoMemoryRoutes();
|
|
17497
|
+
registerSystemHookRoutes();
|
|
17498
|
+
registerSystemSynthesisRoutes();
|
|
17499
|
+
registerSystemSessionRoutes();
|
|
17500
|
+
registerSystemToolRoutes();
|
|
17501
|
+
registerSystemChainRoutes();
|
|
17256
17502
|
|
|
17257
17503
|
// src/server/index.ts
|
|
17504
|
+
async function findFreePort(start) {
|
|
17505
|
+
const net = await import("net");
|
|
17506
|
+
return new Promise((resolve4) => {
|
|
17507
|
+
const server = net.createServer();
|
|
17508
|
+
server.unref();
|
|
17509
|
+
server.on("error", () => {
|
|
17510
|
+
resolve4(findFreePort(start + 1));
|
|
17511
|
+
});
|
|
17512
|
+
server.listen(start, () => {
|
|
17513
|
+
const address = server.address();
|
|
17514
|
+
if (address && typeof address === "object") {
|
|
17515
|
+
server.close(() => resolve4(address.port));
|
|
17516
|
+
} else {
|
|
17517
|
+
resolve4(start);
|
|
17518
|
+
}
|
|
17519
|
+
});
|
|
17520
|
+
});
|
|
17521
|
+
}
|
|
17258
17522
|
var DEFAULT_PORT = 19428;
|
|
17259
17523
|
function hasFlag(...flags) {
|
|
17260
17524
|
return process.argv.some((arg) => flags.includes(arg));
|
|
@@ -17302,115 +17566,142 @@ function parsePort() {
|
|
|
17302
17566
|
}
|
|
17303
17567
|
return DEFAULT_PORT;
|
|
17304
17568
|
}
|
|
17305
|
-
|
|
17306
|
-
|
|
17307
|
-
|
|
17308
|
-
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
}
|
|
17312
|
-
}
|
|
17313
|
-
return start;
|
|
17569
|
+
var _serverInitialized = false;
|
|
17570
|
+
function warnIfPrimaryMachineUnset() {
|
|
17571
|
+
try {
|
|
17572
|
+
const warning = getPrimaryMachineStartupWarning(getDatabase());
|
|
17573
|
+
if (warning) {
|
|
17574
|
+
console.warn(`[mementos-serve] ${warning}`);
|
|
17575
|
+
}
|
|
17576
|
+
} catch {}
|
|
17314
17577
|
}
|
|
17315
|
-
function
|
|
17578
|
+
function initServer() {
|
|
17579
|
+
if (_serverInitialized)
|
|
17580
|
+
return;
|
|
17581
|
+
_serverInitialized = true;
|
|
17582
|
+
warnIfPrimaryMachineUnset();
|
|
17316
17583
|
loadWebhooksFromDb();
|
|
17317
17584
|
startSessionQueueWorker();
|
|
17318
|
-
|
|
17319
|
-
|
|
17320
|
-
|
|
17321
|
-
|
|
17322
|
-
|
|
17323
|
-
|
|
17324
|
-
|
|
17325
|
-
|
|
17326
|
-
|
|
17327
|
-
|
|
17328
|
-
|
|
17329
|
-
const
|
|
17330
|
-
|
|
17331
|
-
|
|
17332
|
-
|
|
17333
|
-
|
|
17334
|
-
|
|
17335
|
-
|
|
17336
|
-
|
|
17337
|
-
|
|
17338
|
-
|
|
17339
|
-
|
|
17340
|
-
|
|
17341
|
-
|
|
17342
|
-
|
|
17343
|
-
|
|
17344
|
-
|
|
17345
|
-
|
|
17346
|
-
|
|
17347
|
-
|
|
17348
|
-
|
|
17349
|
-
|
|
17350
|
-
|
|
17351
|
-
|
|
17352
|
-
|
|
17585
|
+
}
|
|
17586
|
+
function startServer(port, attempt = 0) {
|
|
17587
|
+
const maxRetries = 100;
|
|
17588
|
+
initServer();
|
|
17589
|
+
const hostname2 = process.env["MEMENTOS_HOST"] ?? "127.0.0.1";
|
|
17590
|
+
try {
|
|
17591
|
+
Bun.serve({
|
|
17592
|
+
port,
|
|
17593
|
+
hostname: hostname2,
|
|
17594
|
+
async fetch(req) {
|
|
17595
|
+
const url = new URL(req.url);
|
|
17596
|
+
const { pathname } = url;
|
|
17597
|
+
if (req.method === "OPTIONS") {
|
|
17598
|
+
const origin = req.headers.get("origin");
|
|
17599
|
+
const allowedOrigin = process.env["MEMENTOS_CORS_ORIGIN"] ?? "http://localhost:19428";
|
|
17600
|
+
if (origin && origin !== allowedOrigin) {
|
|
17601
|
+
return new Response(null, { status: 403 });
|
|
17602
|
+
}
|
|
17603
|
+
return new Response(null, { status: 204, headers: getCorsHeaders(req) });
|
|
17604
|
+
}
|
|
17605
|
+
if (pathname === "/api/health" || pathname === "/health") {
|
|
17606
|
+
const profile = getActiveProfile();
|
|
17607
|
+
const { createRequire: createRequire2 } = await import("module");
|
|
17608
|
+
const req2 = createRequire2(import.meta.url);
|
|
17609
|
+
const pkg = req2("../../package.json");
|
|
17610
|
+
const db = getDatabase();
|
|
17611
|
+
const total = db.query("SELECT COUNT(*) as c FROM memories WHERE status = 'active'").get().c;
|
|
17612
|
+
const expired = db.query("SELECT COUNT(*) as c FROM memories WHERE status = 'expired' OR (expires_at IS NOT NULL AND expires_at < datetime('now'))").get().c;
|
|
17613
|
+
const pinned = db.query("SELECT COUNT(*) as c FROM memories WHERE status = 'active' AND pinned = 1").get().c;
|
|
17614
|
+
const agents = db.query("SELECT COUNT(*) as c FROM agents").get().c;
|
|
17615
|
+
const projects = db.query("SELECT COUNT(*) as c FROM projects").get().c;
|
|
17616
|
+
const status = expired > 50 ? "warn" : "ok";
|
|
17617
|
+
return json({ status, version: pkg.version, profile: profile ?? "default", db_path: getDbPath(), hostname: hostname2, memories: { total, expired, pinned }, agents, projects });
|
|
17618
|
+
}
|
|
17619
|
+
if (pathname.startsWith("/api/") && pathname !== "/api/health") {
|
|
17620
|
+
const authError = authenticateRequest(req);
|
|
17621
|
+
if (authError)
|
|
17622
|
+
return authError;
|
|
17623
|
+
}
|
|
17624
|
+
if (pathname === "/api/profile" && req.method === "GET") {
|
|
17625
|
+
const profile = getActiveProfile();
|
|
17626
|
+
return json({ active: profile ?? null, profiles: listProfiles(), db_path: getDbPath() });
|
|
17627
|
+
}
|
|
17628
|
+
if (pathname === "/api/memories/stream" && req.method === "GET") {
|
|
17629
|
+
const stream = new ReadableStream({
|
|
17630
|
+
start(controller) {
|
|
17631
|
+
const encoder = new TextEncoder;
|
|
17632
|
+
let lastSeen = new Date().toISOString();
|
|
17633
|
+
const send = (data) => {
|
|
17634
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
|
|
17353
17635
|
|
|
17354
17636
|
`));
|
|
17355
|
-
|
|
17356
|
-
|
|
17357
|
-
|
|
17358
|
-
|
|
17359
|
-
|
|
17360
|
-
|
|
17361
|
-
|
|
17362
|
-
|
|
17363
|
-
|
|
17364
|
-
|
|
17365
|
-
|
|
17366
|
-
|
|
17367
|
-
|
|
17368
|
-
|
|
17369
|
-
|
|
17370
|
-
|
|
17637
|
+
};
|
|
17638
|
+
send({ type: "connected", timestamp: lastSeen });
|
|
17639
|
+
const interval = setInterval(() => {
|
|
17640
|
+
try {
|
|
17641
|
+
const db = getDatabase();
|
|
17642
|
+
const rows = db.query("SELECT * FROM memories WHERE updated_at > ? OR created_at > ? ORDER BY updated_at DESC LIMIT 50").all(lastSeen, lastSeen);
|
|
17643
|
+
if (rows.length > 0) {
|
|
17644
|
+
lastSeen = new Date().toISOString();
|
|
17645
|
+
send({ type: "memories", data: rows, count: rows.length });
|
|
17646
|
+
}
|
|
17647
|
+
} catch {}
|
|
17648
|
+
}, 1000);
|
|
17649
|
+
req.signal.addEventListener("abort", () => {
|
|
17650
|
+
clearInterval(interval);
|
|
17651
|
+
controller.close();
|
|
17652
|
+
});
|
|
17653
|
+
}
|
|
17654
|
+
});
|
|
17655
|
+
return new Response(stream, {
|
|
17656
|
+
headers: {
|
|
17657
|
+
"Content-Type": "text/event-stream",
|
|
17658
|
+
"Cache-Control": "no-cache",
|
|
17659
|
+
Connection: "keep-alive",
|
|
17660
|
+
...CORS_HEADERS
|
|
17661
|
+
}
|
|
17662
|
+
});
|
|
17663
|
+
}
|
|
17664
|
+
const matched = matchRoute(req.method, pathname);
|
|
17665
|
+
if (!matched) {
|
|
17666
|
+
if (pathname.startsWith("/api/")) {
|
|
17667
|
+
return errorResponse("Not found", 404);
|
|
17371
17668
|
}
|
|
17372
|
-
|
|
17373
|
-
|
|
17374
|
-
|
|
17375
|
-
|
|
17376
|
-
|
|
17377
|
-
|
|
17378
|
-
|
|
17669
|
+
const dashDir = resolveDashboardDir();
|
|
17670
|
+
if (existsSync7(dashDir) && (req.method === "GET" || req.method === "HEAD")) {
|
|
17671
|
+
if (pathname !== "/") {
|
|
17672
|
+
const resolvedDash = resolve3(dashDir) + sep;
|
|
17673
|
+
const requestedPath = resolve3(join8(dashDir, pathname));
|
|
17674
|
+
if (requestedPath.startsWith(resolvedDash)) {
|
|
17675
|
+
const staticRes = serveStaticFile(requestedPath);
|
|
17676
|
+
if (staticRes)
|
|
17677
|
+
return staticRes;
|
|
17678
|
+
}
|
|
17679
|
+
}
|
|
17680
|
+
const indexRes = serveStaticFile(join8(dashDir, "index.html"));
|
|
17681
|
+
if (indexRes)
|
|
17682
|
+
return indexRes;
|
|
17379
17683
|
}
|
|
17380
|
-
});
|
|
17381
|
-
}
|
|
17382
|
-
const matched = matchRoute(req.method, pathname);
|
|
17383
|
-
if (!matched) {
|
|
17384
|
-
if (pathname.startsWith("/api/")) {
|
|
17385
17684
|
return errorResponse("Not found", 404);
|
|
17386
17685
|
}
|
|
17387
|
-
|
|
17388
|
-
|
|
17389
|
-
|
|
17390
|
-
|
|
17391
|
-
|
|
17392
|
-
if (requestedPath.startsWith(resolvedDash)) {
|
|
17393
|
-
const staticRes = serveStaticFile(requestedPath);
|
|
17394
|
-
if (staticRes)
|
|
17395
|
-
return staticRes;
|
|
17396
|
-
}
|
|
17397
|
-
}
|
|
17398
|
-
const indexRes = serveStaticFile(join8(dashDir, "index.html"));
|
|
17399
|
-
if (indexRes)
|
|
17400
|
-
return indexRes;
|
|
17686
|
+
try {
|
|
17687
|
+
return await matched.handler(req, url, matched.params);
|
|
17688
|
+
} catch (e) {
|
|
17689
|
+
console.error(`[mementos-serve] ${req.method} ${pathname}:`, e);
|
|
17690
|
+
return errorResponse("Internal server error", 500);
|
|
17401
17691
|
}
|
|
17402
|
-
return errorResponse("Not found", 404);
|
|
17403
|
-
}
|
|
17404
|
-
try {
|
|
17405
|
-
return await matched.handler(req, url, matched.params);
|
|
17406
|
-
} catch (e) {
|
|
17407
|
-
console.error(`[mementos-serve] ${req.method} ${pathname}:`, e);
|
|
17408
|
-
const message = e instanceof Error ? e.message : "Internal server error";
|
|
17409
|
-
return errorResponse(message, 500);
|
|
17410
17692
|
}
|
|
17693
|
+
});
|
|
17694
|
+
console.log(`Mementos server listening on http://${hostname2}:${port}`);
|
|
17695
|
+
} catch (e) {
|
|
17696
|
+
const err = e;
|
|
17697
|
+
if (err.code === "EADDRINUSE" && attempt < maxRetries) {
|
|
17698
|
+
const nextPort = port + attempt + 1;
|
|
17699
|
+
console.log(`Port ${port} in use, trying ${nextPort}`);
|
|
17700
|
+
startServer(nextPort, attempt + 1);
|
|
17701
|
+
} else {
|
|
17702
|
+
throw e;
|
|
17411
17703
|
}
|
|
17412
|
-
}
|
|
17413
|
-
console.log(`Mementos server listening on http://${hostname}:${port}`);
|
|
17704
|
+
}
|
|
17414
17705
|
}
|
|
17415
17706
|
async function main() {
|
|
17416
17707
|
if (hasFlag("--help", "-h")) {
|