@hasna/mementos 0.14.17 → 0.14.19
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 +2913 -2330
- package/dist/index.js +659 -9
- package/dist/mcp/index.js +3060 -2754
- package/dist/sdk/index.js +397 -0
- package/dist/server/index.js +1207 -431
- package/package.json +6 -2
package/dist/server/index.js
CHANGED
|
@@ -9790,6 +9790,7 @@ CREATE TABLE IF NOT EXISTS machines (
|
|
|
9790
9790
|
name TEXT NOT NULL UNIQUE,
|
|
9791
9791
|
hostname TEXT NOT NULL,
|
|
9792
9792
|
platform TEXT NOT NULL DEFAULT 'unknown',
|
|
9793
|
+
is_primary INTEGER NOT NULL DEFAULT 0,
|
|
9793
9794
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
9794
9795
|
last_seen_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
9795
9796
|
);
|
|
@@ -10167,6 +10168,46 @@ ALTER TABLE memories ADD COLUMN sequence_group TEXT DEFAULT NULL;
|
|
|
10167
10168
|
ALTER TABLE memories ADD COLUMN sequence_order INTEGER DEFAULT NULL;
|
|
10168
10169
|
CREATE INDEX IF NOT EXISTS idx_memories_sequence_group ON memories(sequence_group) WHERE sequence_group IS NOT NULL;
|
|
10169
10170
|
INSERT OR IGNORE INTO _migrations (id) VALUES (32);
|
|
10171
|
+
`,
|
|
10172
|
+
`
|
|
10173
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
10174
|
+
id TEXT PRIMARY KEY,
|
|
10175
|
+
subject TEXT NOT NULL,
|
|
10176
|
+
description TEXT DEFAULT '',
|
|
10177
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'in_progress', 'completed', 'failed', 'cancelled')),
|
|
10178
|
+
priority TEXT NOT NULL DEFAULT 'medium' CHECK(priority IN ('critical', 'high', 'medium', 'low')),
|
|
10179
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
10180
|
+
assigned_agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
|
10181
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
10182
|
+
session_id TEXT,
|
|
10183
|
+
parent_task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
10184
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
10185
|
+
progress REAL NOT NULL DEFAULT 0 CHECK(progress >= 0 AND progress <= 1),
|
|
10186
|
+
due_at TEXT,
|
|
10187
|
+
started_at TEXT,
|
|
10188
|
+
completed_at TEXT,
|
|
10189
|
+
failed_at TEXT,
|
|
10190
|
+
error TEXT,
|
|
10191
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
10192
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
10193
|
+
);
|
|
10194
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
10195
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority);
|
|
10196
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_agent ON tasks(assigned_agent_id);
|
|
10197
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks(project_id);
|
|
10198
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_session ON tasks(session_id);
|
|
10199
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_task_id);
|
|
10200
|
+
|
|
10201
|
+
CREATE TABLE IF NOT EXISTS task_comments (
|
|
10202
|
+
id TEXT PRIMARY KEY,
|
|
10203
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
10204
|
+
agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
|
10205
|
+
body TEXT NOT NULL,
|
|
10206
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
10207
|
+
);
|
|
10208
|
+
CREATE INDEX IF NOT EXISTS idx_task_comments_task ON task_comments(task_id);
|
|
10209
|
+
CREATE INDEX IF NOT EXISTS idx_task_comments_agent ON task_comments(agent_id);
|
|
10210
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (34);
|
|
10170
10211
|
`
|
|
10171
10212
|
];
|
|
10172
10213
|
});
|
|
@@ -10250,11 +10291,16 @@ function ensureDir2(filePath) {
|
|
|
10250
10291
|
}
|
|
10251
10292
|
}
|
|
10252
10293
|
function getDatabase(dbPath) {
|
|
10253
|
-
if (_db)
|
|
10254
|
-
return _db;
|
|
10255
10294
|
const path = dbPath || getDbPath3();
|
|
10295
|
+
if (_db) {
|
|
10296
|
+
if (_dbPath === path)
|
|
10297
|
+
return _db;
|
|
10298
|
+
_db.close();
|
|
10299
|
+
_db = null;
|
|
10300
|
+
}
|
|
10301
|
+
_dbPath = path;
|
|
10256
10302
|
ensureDir2(path);
|
|
10257
|
-
_db = new SqliteAdapter(path
|
|
10303
|
+
_db = new SqliteAdapter(path);
|
|
10258
10304
|
_db.run("PRAGMA journal_mode = WAL");
|
|
10259
10305
|
_db.run("PRAGMA busy_timeout = 5000");
|
|
10260
10306
|
_db.run("PRAGMA foreign_keys = ON");
|
|
@@ -10278,13 +10324,17 @@ function runMigrations(db) {
|
|
|
10278
10324
|
for (let i = currentLevel;i < MIGRATIONS.length; i++) {
|
|
10279
10325
|
try {
|
|
10280
10326
|
db.exec(MIGRATIONS[i]);
|
|
10281
|
-
} catch {
|
|
10327
|
+
} catch (e) {
|
|
10328
|
+
console.warn(`[mementos] Migration ${i + 1} failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
10329
|
+
}
|
|
10282
10330
|
}
|
|
10283
10331
|
} catch {
|
|
10284
|
-
for (
|
|
10332
|
+
for (let i = 0;i < MIGRATIONS.length; i++) {
|
|
10285
10333
|
try {
|
|
10286
|
-
db.exec(
|
|
10287
|
-
} catch {
|
|
10334
|
+
db.exec(MIGRATIONS[i]);
|
|
10335
|
+
} catch (e) {
|
|
10336
|
+
console.warn(`[mementos] Migration ${i + 1} failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
10337
|
+
}
|
|
10288
10338
|
}
|
|
10289
10339
|
}
|
|
10290
10340
|
}
|
|
@@ -10307,6 +10357,9 @@ function shortUuid() {
|
|
|
10307
10357
|
return crypto.randomUUID().slice(0, 8);
|
|
10308
10358
|
}
|
|
10309
10359
|
function resolvePartialId(db, table, partialId) {
|
|
10360
|
+
if (!ALLOWED_TABLES.has(table)) {
|
|
10361
|
+
throw new Error(`Invalid table name: ${table}`);
|
|
10362
|
+
}
|
|
10310
10363
|
if (partialId.length >= 36) {
|
|
10311
10364
|
const row = db.query(`SELECT id FROM ${table} WHERE id = ?`).get(partialId);
|
|
10312
10365
|
return row?.id ?? null;
|
|
@@ -10317,10 +10370,25 @@ function resolvePartialId(db, table, partialId) {
|
|
|
10317
10370
|
}
|
|
10318
10371
|
return null;
|
|
10319
10372
|
}
|
|
10320
|
-
var _db = null;
|
|
10373
|
+
var _db = null, _dbPath = null, ALLOWED_TABLES;
|
|
10321
10374
|
var init_database = __esm(() => {
|
|
10322
10375
|
init_dist();
|
|
10323
10376
|
init_migrations();
|
|
10377
|
+
ALLOWED_TABLES = new Set([
|
|
10378
|
+
"memories",
|
|
10379
|
+
"agents",
|
|
10380
|
+
"entities",
|
|
10381
|
+
"projects",
|
|
10382
|
+
"relations",
|
|
10383
|
+
"memory_audit_log",
|
|
10384
|
+
"locks",
|
|
10385
|
+
"sessions",
|
|
10386
|
+
"session_memory_jobs",
|
|
10387
|
+
"synthesis_runs",
|
|
10388
|
+
"synthesis_proposals",
|
|
10389
|
+
"tool_events",
|
|
10390
|
+
"webhook_hooks"
|
|
10391
|
+
]);
|
|
10324
10392
|
});
|
|
10325
10393
|
|
|
10326
10394
|
// src/lib/hooks.ts
|
|
@@ -10830,7 +10898,7 @@ function createMemory(input, dedupeMode = "merge", db) {
|
|
|
10830
10898
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 1, ?, ?, ?, ?, ?, ?)`, [
|
|
10831
10899
|
id,
|
|
10832
10900
|
input.key,
|
|
10833
|
-
|
|
10901
|
+
safeValue,
|
|
10834
10902
|
input.category || "knowledge",
|
|
10835
10903
|
input.scope || "private",
|
|
10836
10904
|
input.summary || null,
|
|
@@ -10993,6 +11061,22 @@ function listMemories(filter, db) {
|
|
|
10993
11061
|
conditions.push("session_id = ?");
|
|
10994
11062
|
params.push(filter.session_id);
|
|
10995
11063
|
}
|
|
11064
|
+
if ("machine_id" in filter) {
|
|
11065
|
+
if (filter.machine_id === null) {
|
|
11066
|
+
conditions.push("machine_id IS NULL");
|
|
11067
|
+
} else if (filter.machine_id) {
|
|
11068
|
+
conditions.push("machine_id = ?");
|
|
11069
|
+
params.push(filter.machine_id);
|
|
11070
|
+
}
|
|
11071
|
+
}
|
|
11072
|
+
if ("visible_to_machine_id" in filter) {
|
|
11073
|
+
if (filter.visible_to_machine_id === null) {
|
|
11074
|
+
conditions.push("machine_id IS NULL");
|
|
11075
|
+
} else if (filter.visible_to_machine_id !== undefined) {
|
|
11076
|
+
conditions.push("(machine_id IS NULL OR machine_id = ?)");
|
|
11077
|
+
params.push(filter.visible_to_machine_id);
|
|
11078
|
+
}
|
|
11079
|
+
}
|
|
10996
11080
|
if (filter.min_importance) {
|
|
10997
11081
|
conditions.push("importance >= ?");
|
|
10998
11082
|
params.push(filter.min_importance);
|
|
@@ -11721,6 +11805,10 @@ function buildFilterConditions(filter) {
|
|
|
11721
11805
|
params.push(tag);
|
|
11722
11806
|
}
|
|
11723
11807
|
}
|
|
11808
|
+
if (filter.namespace) {
|
|
11809
|
+
conditions.push("m.namespace = ?");
|
|
11810
|
+
params.push(filter.namespace);
|
|
11811
|
+
}
|
|
11724
11812
|
return { conditions, params };
|
|
11725
11813
|
}
|
|
11726
11814
|
function searchWithFts5(d, query, queryLower, filter, graphBoostedIds) {
|
|
@@ -13355,12 +13443,14 @@ async function synthesizeProfile(options) {
|
|
|
13355
13443
|
const prefMemories = listMemories({
|
|
13356
13444
|
category: "preference",
|
|
13357
13445
|
project_id: options.project_id,
|
|
13446
|
+
machine_id: null,
|
|
13358
13447
|
status: "active",
|
|
13359
13448
|
limit: 30
|
|
13360
13449
|
});
|
|
13361
13450
|
const factMemories = listMemories({
|
|
13362
13451
|
category: "fact",
|
|
13363
13452
|
project_id: options.project_id,
|
|
13453
|
+
machine_id: null,
|
|
13364
13454
|
status: "active",
|
|
13365
13455
|
limit: 30
|
|
13366
13456
|
});
|
|
@@ -13671,6 +13761,69 @@ function ensureDir(dir) {
|
|
|
13671
13761
|
// src/server/index.ts
|
|
13672
13762
|
init_database();
|
|
13673
13763
|
|
|
13764
|
+
// src/db/machines.ts
|
|
13765
|
+
init_database();
|
|
13766
|
+
import { hostname, platform as platform2 } from "os";
|
|
13767
|
+
function parseMachine(row) {
|
|
13768
|
+
if (!row)
|
|
13769
|
+
return null;
|
|
13770
|
+
return {
|
|
13771
|
+
...row,
|
|
13772
|
+
is_primary: Boolean(row.is_primary)
|
|
13773
|
+
};
|
|
13774
|
+
}
|
|
13775
|
+
function normalizeHostname(host) {
|
|
13776
|
+
return host.replace(/\.(local|lan|home|internal)$/i, "");
|
|
13777
|
+
}
|
|
13778
|
+
function registerMachine(name, db = getDatabase()) {
|
|
13779
|
+
const rawHost = hostname();
|
|
13780
|
+
const host = normalizeHostname(rawHost);
|
|
13781
|
+
const plat = platform2();
|
|
13782
|
+
const machineName = name?.trim() || host;
|
|
13783
|
+
const existing = parseMachine(db.query("SELECT * FROM machines WHERE hostname = ?").get(host));
|
|
13784
|
+
if (existing) {
|
|
13785
|
+
db.run("UPDATE machines SET last_seen_at = ? WHERE id = ?", [now(), existing.id]);
|
|
13786
|
+
return parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(existing.id));
|
|
13787
|
+
}
|
|
13788
|
+
let finalName = machineName;
|
|
13789
|
+
let suffix = 2;
|
|
13790
|
+
while (db.query("SELECT id FROM machines WHERE name = ?").get(finalName)) {
|
|
13791
|
+
finalName = `${machineName}-${suffix++}`;
|
|
13792
|
+
}
|
|
13793
|
+
const id = uuid();
|
|
13794
|
+
db.run("INSERT INTO machines (id, name, hostname, platform) VALUES (?, ?, ?, ?)", [id, finalName, host, plat]);
|
|
13795
|
+
return parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(id));
|
|
13796
|
+
}
|
|
13797
|
+
function getPrimaryMachine(db = getDatabase()) {
|
|
13798
|
+
return parseMachine(db.query("SELECT * FROM machines WHERE is_primary = 1 LIMIT 1").get());
|
|
13799
|
+
}
|
|
13800
|
+
function getPrimaryMachineCandidate(db = getDatabase()) {
|
|
13801
|
+
if (getPrimaryMachine(db))
|
|
13802
|
+
return null;
|
|
13803
|
+
return parseMachine(db.query("SELECT * FROM machines ORDER BY created_at ASC, id ASC LIMIT 1").get());
|
|
13804
|
+
}
|
|
13805
|
+
function getPrimaryMachineStartupWarning(db = getDatabase()) {
|
|
13806
|
+
if (getPrimaryMachine(db))
|
|
13807
|
+
return null;
|
|
13808
|
+
const candidate = getPrimaryMachineCandidate(db);
|
|
13809
|
+
if (!candidate) {
|
|
13810
|
+
return "No primary machine configured. Fallback sync target is unset because no machines are registered yet.";
|
|
13811
|
+
}
|
|
13812
|
+
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.`;
|
|
13813
|
+
}
|
|
13814
|
+
function touchMachine(id, db = getDatabase()) {
|
|
13815
|
+
db.run("UPDATE machines SET last_seen_at = ? WHERE id = ?", [now(), id]);
|
|
13816
|
+
}
|
|
13817
|
+
function getCurrentMachineId(db = getDatabase()) {
|
|
13818
|
+
const host = normalizeHostname(hostname());
|
|
13819
|
+
const m = db.query("SELECT id FROM machines WHERE hostname = ?").get(host);
|
|
13820
|
+
if (m) {
|
|
13821
|
+
touchMachine(m.id, db);
|
|
13822
|
+
return m.id;
|
|
13823
|
+
}
|
|
13824
|
+
return registerMachine(undefined, db).id;
|
|
13825
|
+
}
|
|
13826
|
+
|
|
13674
13827
|
// src/lib/built-in-hooks.ts
|
|
13675
13828
|
init_hooks();
|
|
13676
13829
|
|
|
@@ -14708,8 +14861,377 @@ async function _processNext() {
|
|
|
14708
14861
|
}
|
|
14709
14862
|
}
|
|
14710
14863
|
|
|
14864
|
+
// src/lib/task-runner.ts
|
|
14865
|
+
init_database();
|
|
14866
|
+
|
|
14867
|
+
// src/db/tasks.ts
|
|
14868
|
+
init_database();
|
|
14869
|
+
function parseTask(row) {
|
|
14870
|
+
return {
|
|
14871
|
+
id: row.id,
|
|
14872
|
+
subject: row.subject,
|
|
14873
|
+
description: row.description ?? "",
|
|
14874
|
+
status: row.status,
|
|
14875
|
+
priority: row.priority,
|
|
14876
|
+
tags: JSON.parse(row.tags ?? "[]"),
|
|
14877
|
+
assigned_agent_id: row.assigned_agent_id ?? null,
|
|
14878
|
+
project_id: row.project_id ?? null,
|
|
14879
|
+
session_id: row.session_id ?? null,
|
|
14880
|
+
parent_task_id: row.parent_task_id ?? null,
|
|
14881
|
+
metadata: JSON.parse(row.metadata ?? "{}"),
|
|
14882
|
+
progress: row.progress,
|
|
14883
|
+
due_at: row.due_at ?? null,
|
|
14884
|
+
started_at: row.started_at ?? null,
|
|
14885
|
+
completed_at: row.completed_at ?? null,
|
|
14886
|
+
failed_at: row.failed_at ?? null,
|
|
14887
|
+
error: row.error ?? null,
|
|
14888
|
+
created_at: row.created_at,
|
|
14889
|
+
updated_at: row.updated_at
|
|
14890
|
+
};
|
|
14891
|
+
}
|
|
14892
|
+
function createTask(db, input) {
|
|
14893
|
+
const id = uuid();
|
|
14894
|
+
const ts = now();
|
|
14895
|
+
db.run(`INSERT INTO tasks (id, subject, description, status, priority, tags, assigned_agent_id, project_id, session_id, parent_task_id, metadata, due_at, created_at, updated_at)
|
|
14896
|
+
VALUES (?, ?, ?, 'pending', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
14897
|
+
id,
|
|
14898
|
+
input.subject,
|
|
14899
|
+
input.description ?? "",
|
|
14900
|
+
input.priority ?? "medium",
|
|
14901
|
+
JSON.stringify(input.tags ?? []),
|
|
14902
|
+
input.assigned_agent_id ?? null,
|
|
14903
|
+
input.project_id ?? null,
|
|
14904
|
+
input.session_id ?? null,
|
|
14905
|
+
input.parent_task_id ?? null,
|
|
14906
|
+
JSON.stringify(input.metadata ?? {}),
|
|
14907
|
+
input.due_at ?? null,
|
|
14908
|
+
ts,
|
|
14909
|
+
ts
|
|
14910
|
+
]);
|
|
14911
|
+
const row = db.query("SELECT * FROM tasks WHERE id = ?").get(id);
|
|
14912
|
+
return parseTask(row);
|
|
14913
|
+
}
|
|
14914
|
+
function getTask(db, id) {
|
|
14915
|
+
const row = db.query("SELECT * FROM tasks WHERE id = ?").get(id);
|
|
14916
|
+
return row ? parseTask(row) : null;
|
|
14917
|
+
}
|
|
14918
|
+
function listTasks(db, filter) {
|
|
14919
|
+
let sql = "SELECT * FROM tasks WHERE 1=1";
|
|
14920
|
+
let countSql = "SELECT COUNT(*) as c FROM tasks WHERE 1=1";
|
|
14921
|
+
const params = [];
|
|
14922
|
+
const countParams = [];
|
|
14923
|
+
if (filter?.status) {
|
|
14924
|
+
sql += " AND status = ?";
|
|
14925
|
+
countSql += " AND status = ?";
|
|
14926
|
+
params.push(filter.status);
|
|
14927
|
+
countParams.push(filter.status);
|
|
14928
|
+
}
|
|
14929
|
+
if (filter?.priority) {
|
|
14930
|
+
sql += " AND priority = ?";
|
|
14931
|
+
countSql += " AND priority = ?";
|
|
14932
|
+
params.push(filter.priority);
|
|
14933
|
+
countParams.push(filter.priority);
|
|
14934
|
+
}
|
|
14935
|
+
if (filter?.assigned_agent_id) {
|
|
14936
|
+
sql += " AND assigned_agent_id = ?";
|
|
14937
|
+
countSql += " AND assigned_agent_id = ?";
|
|
14938
|
+
params.push(filter.assigned_agent_id);
|
|
14939
|
+
countParams.push(filter.assigned_agent_id);
|
|
14940
|
+
}
|
|
14941
|
+
if (filter?.project_id) {
|
|
14942
|
+
sql += " AND project_id = ?";
|
|
14943
|
+
countSql += " AND project_id = ?";
|
|
14944
|
+
params.push(filter.project_id);
|
|
14945
|
+
countParams.push(filter.project_id);
|
|
14946
|
+
}
|
|
14947
|
+
if (filter?.session_id) {
|
|
14948
|
+
sql += " AND session_id = ?";
|
|
14949
|
+
countSql += " AND session_id = ?";
|
|
14950
|
+
params.push(filter.session_id);
|
|
14951
|
+
countParams.push(filter.session_id);
|
|
14952
|
+
}
|
|
14953
|
+
if (filter?.parent_task_id !== undefined) {
|
|
14954
|
+
if (filter.parent_task_id === null) {
|
|
14955
|
+
sql += " AND parent_task_id IS NULL";
|
|
14956
|
+
countSql += " AND parent_task_id IS NULL";
|
|
14957
|
+
} else {
|
|
14958
|
+
sql += " AND parent_task_id = ?";
|
|
14959
|
+
countSql += " AND parent_task_id = ?";
|
|
14960
|
+
params.push(filter.parent_task_id);
|
|
14961
|
+
countParams.push(filter.parent_task_id);
|
|
14962
|
+
}
|
|
14963
|
+
}
|
|
14964
|
+
if (filter?.tags?.length) {
|
|
14965
|
+
const placeholders = filter.tags.map(() => "?").join(", ");
|
|
14966
|
+
sql += ` AND EXISTS (SELECT 1 FROM json_each(tags) WHERE value IN (${placeholders}))`;
|
|
14967
|
+
countSql += ` AND EXISTS (SELECT 1 FROM json_each(tags) WHERE value IN (${placeholders}))`;
|
|
14968
|
+
params.push(...filter.tags);
|
|
14969
|
+
countParams.push(...filter.tags);
|
|
14970
|
+
}
|
|
14971
|
+
sql += " ORDER BY CASE priority WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 WHEN 'low' THEN 4 END, created_at ASC";
|
|
14972
|
+
if (filter?.limit !== undefined) {
|
|
14973
|
+
sql += " LIMIT ?";
|
|
14974
|
+
params.push(filter.limit);
|
|
14975
|
+
}
|
|
14976
|
+
if (filter?.offset !== undefined) {
|
|
14977
|
+
sql += " OFFSET ?";
|
|
14978
|
+
params.push(filter.offset);
|
|
14979
|
+
}
|
|
14980
|
+
const rows = db.query(sql).all(...params);
|
|
14981
|
+
const countRow = db.query(countSql).get(...countParams);
|
|
14982
|
+
return { tasks: rows.map(parseTask), count: countRow.c };
|
|
14983
|
+
}
|
|
14984
|
+
function updateTask(db, id, input) {
|
|
14985
|
+
const existing = getTask(db, id);
|
|
14986
|
+
if (!existing)
|
|
14987
|
+
return null;
|
|
14988
|
+
const updates = [];
|
|
14989
|
+
const params = [];
|
|
14990
|
+
const ts = now();
|
|
14991
|
+
if (input.subject !== undefined) {
|
|
14992
|
+
updates.push("subject = ?");
|
|
14993
|
+
params.push(input.subject);
|
|
14994
|
+
}
|
|
14995
|
+
if (input.description !== undefined) {
|
|
14996
|
+
updates.push("description = ?");
|
|
14997
|
+
params.push(input.description);
|
|
14998
|
+
}
|
|
14999
|
+
if (input.status !== undefined) {
|
|
15000
|
+
updates.push("status = ?");
|
|
15001
|
+
params.push(input.status);
|
|
15002
|
+
if (input.status === "in_progress" && !existing.started_at) {
|
|
15003
|
+
updates.push("started_at = ?");
|
|
15004
|
+
params.push(ts);
|
|
15005
|
+
}
|
|
15006
|
+
if (input.status === "completed") {
|
|
15007
|
+
updates.push("completed_at = ?");
|
|
15008
|
+
params.push(ts);
|
|
15009
|
+
updates.push("progress = ?");
|
|
15010
|
+
params.push(1);
|
|
15011
|
+
}
|
|
15012
|
+
if (input.status === "failed") {
|
|
15013
|
+
updates.push("failed_at = ?");
|
|
15014
|
+
params.push(ts);
|
|
15015
|
+
}
|
|
15016
|
+
}
|
|
15017
|
+
if (input.priority !== undefined) {
|
|
15018
|
+
updates.push("priority = ?");
|
|
15019
|
+
params.push(input.priority);
|
|
15020
|
+
}
|
|
15021
|
+
if (input.tags !== undefined) {
|
|
15022
|
+
updates.push("tags = ?");
|
|
15023
|
+
params.push(JSON.stringify(input.tags));
|
|
15024
|
+
}
|
|
15025
|
+
if (input.assigned_agent_id !== undefined) {
|
|
15026
|
+
updates.push("assigned_agent_id = ?");
|
|
15027
|
+
params.push(input.assigned_agent_id);
|
|
15028
|
+
}
|
|
15029
|
+
if (input.metadata !== undefined) {
|
|
15030
|
+
updates.push("metadata = ?");
|
|
15031
|
+
params.push(JSON.stringify(input.metadata));
|
|
15032
|
+
}
|
|
15033
|
+
if (input.progress !== undefined) {
|
|
15034
|
+
updates.push("progress = ?");
|
|
15035
|
+
params.push(input.progress);
|
|
15036
|
+
}
|
|
15037
|
+
if (input.due_at !== undefined) {
|
|
15038
|
+
updates.push("due_at = ?");
|
|
15039
|
+
params.push(input.due_at);
|
|
15040
|
+
}
|
|
15041
|
+
if (input.error !== undefined) {
|
|
15042
|
+
updates.push("error = ?");
|
|
15043
|
+
params.push(input.error);
|
|
15044
|
+
}
|
|
15045
|
+
if (updates.length === 0)
|
|
15046
|
+
return existing;
|
|
15047
|
+
updates.push("updated_at = ?");
|
|
15048
|
+
params.push(ts);
|
|
15049
|
+
params.push(id);
|
|
15050
|
+
db.run(`UPDATE tasks SET ${updates.join(", ")} WHERE id = ?`, ...params);
|
|
15051
|
+
return getTask(db, id);
|
|
15052
|
+
}
|
|
15053
|
+
function deleteTask(db, id) {
|
|
15054
|
+
const result = db.run("DELETE FROM tasks WHERE id = ?", id);
|
|
15055
|
+
return result.changes > 0;
|
|
15056
|
+
}
|
|
15057
|
+
function addTaskComment(db, taskId, body, agentId) {
|
|
15058
|
+
const id = uuid();
|
|
15059
|
+
db.run("INSERT INTO task_comments (id, task_id, agent_id, body) VALUES (?, ?, ?, ?)", [id, taskId, agentId ?? null, body]);
|
|
15060
|
+
const row = db.query("SELECT * FROM task_comments WHERE id = ?").get(id);
|
|
15061
|
+
return parseTaskComment(row);
|
|
15062
|
+
}
|
|
15063
|
+
function parseTaskComment(row) {
|
|
15064
|
+
return {
|
|
15065
|
+
id: row.id,
|
|
15066
|
+
task_id: row.task_id,
|
|
15067
|
+
agent_id: row.agent_id ?? null,
|
|
15068
|
+
body: row.body,
|
|
15069
|
+
created_at: row.created_at
|
|
15070
|
+
};
|
|
15071
|
+
}
|
|
15072
|
+
function listTaskComments(db, taskId) {
|
|
15073
|
+
const rows = db.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at ASC").all(taskId);
|
|
15074
|
+
return { comments: rows.map(parseTaskComment), count: rows.length };
|
|
15075
|
+
}
|
|
15076
|
+
function deleteTaskComment(db, id) {
|
|
15077
|
+
const result = db.run("DELETE FROM task_comments WHERE id = ?", id);
|
|
15078
|
+
return result.changes > 0;
|
|
15079
|
+
}
|
|
15080
|
+
function getTaskStats(db, filter) {
|
|
15081
|
+
let where = "WHERE 1=1";
|
|
15082
|
+
const params = [];
|
|
15083
|
+
if (filter?.project_id) {
|
|
15084
|
+
where += " AND project_id = ?";
|
|
15085
|
+
params.push(filter.project_id);
|
|
15086
|
+
}
|
|
15087
|
+
if (filter?.agent_id) {
|
|
15088
|
+
where += " AND assigned_agent_id = ?";
|
|
15089
|
+
params.push(filter.agent_id);
|
|
15090
|
+
}
|
|
15091
|
+
const total = db.query(`SELECT COUNT(*) as c FROM tasks ${where}`).get(...params).c;
|
|
15092
|
+
const byStatus = {};
|
|
15093
|
+
const statusRows = db.query(`SELECT status, COUNT(*) as c FROM tasks ${where} GROUP BY status`).all(...params);
|
|
15094
|
+
for (const row of statusRows)
|
|
15095
|
+
byStatus[row.status] = row.c;
|
|
15096
|
+
const byPriority = {};
|
|
15097
|
+
const priorityRows = db.query(`SELECT priority, COUNT(*) as c FROM tasks ${where} GROUP BY priority`).all(...params);
|
|
15098
|
+
for (const row of priorityRows)
|
|
15099
|
+
byPriority[row.priority] = row.c;
|
|
15100
|
+
const overdue = db.query(`SELECT COUNT(*) as c FROM tasks ${where} AND status != 'completed' AND status != 'cancelled' AND due_at IS NOT NULL AND due_at < datetime('now')`).get(...params).c;
|
|
15101
|
+
return {
|
|
15102
|
+
total,
|
|
15103
|
+
by_status: {
|
|
15104
|
+
pending: byStatus["pending"] ?? 0,
|
|
15105
|
+
in_progress: byStatus["in_progress"] ?? 0,
|
|
15106
|
+
completed: byStatus["completed"] ?? 0,
|
|
15107
|
+
failed: byStatus["failed"] ?? 0,
|
|
15108
|
+
cancelled: byStatus["cancelled"] ?? 0
|
|
15109
|
+
},
|
|
15110
|
+
by_priority: {
|
|
15111
|
+
critical: byPriority["critical"] ?? 0,
|
|
15112
|
+
high: byPriority["high"] ?? 0,
|
|
15113
|
+
medium: byPriority["medium"] ?? 0,
|
|
15114
|
+
low: byPriority["low"] ?? 0
|
|
15115
|
+
},
|
|
15116
|
+
overdue
|
|
15117
|
+
};
|
|
15118
|
+
}
|
|
15119
|
+
|
|
15120
|
+
// src/lib/task-runner.ts
|
|
15121
|
+
var _handlers = new Map;
|
|
15122
|
+
var _defaultHandler = null;
|
|
15123
|
+
function resolveHandler(task) {
|
|
15124
|
+
for (const tag of task.tags) {
|
|
15125
|
+
const h = _handlers.get(tag);
|
|
15126
|
+
if (h)
|
|
15127
|
+
return h;
|
|
15128
|
+
}
|
|
15129
|
+
const metaType = task.metadata?.["type"];
|
|
15130
|
+
if (metaType) {
|
|
15131
|
+
const h = _handlers.get(metaType);
|
|
15132
|
+
if (h)
|
|
15133
|
+
return h;
|
|
15134
|
+
}
|
|
15135
|
+
return _defaultHandler;
|
|
15136
|
+
}
|
|
15137
|
+
var _workerStarted2 = false;
|
|
15138
|
+
var _processing = false;
|
|
15139
|
+
var _totalProcessed = 0;
|
|
15140
|
+
var _pollIntervalMs = 60000;
|
|
15141
|
+
function startTaskRunner(intervalMs) {
|
|
15142
|
+
if (_workerStarted2)
|
|
15143
|
+
return;
|
|
15144
|
+
_workerStarted2 = true;
|
|
15145
|
+
if (intervalMs)
|
|
15146
|
+
_pollIntervalMs = intervalMs;
|
|
15147
|
+
console.log(`[task-runner] Started, polling every ${_pollIntervalMs / 1000}s`);
|
|
15148
|
+
_tick();
|
|
15149
|
+
setInterval(() => {
|
|
15150
|
+
_tick();
|
|
15151
|
+
}, _pollIntervalMs);
|
|
15152
|
+
}
|
|
15153
|
+
async function _tick() {
|
|
15154
|
+
if (_processing)
|
|
15155
|
+
return;
|
|
15156
|
+
let task = null;
|
|
15157
|
+
try {
|
|
15158
|
+
const db = getDatabase();
|
|
15159
|
+
const result = listTasks(db, { status: "pending", limit: 1 });
|
|
15160
|
+
if (result.tasks.length === 0)
|
|
15161
|
+
return;
|
|
15162
|
+
task = result.tasks[0] ?? null;
|
|
15163
|
+
} catch (e) {
|
|
15164
|
+
console.error("[task-runner] Failed to list pending tasks:", e);
|
|
15165
|
+
return;
|
|
15166
|
+
}
|
|
15167
|
+
if (!task)
|
|
15168
|
+
return;
|
|
15169
|
+
_processing = true;
|
|
15170
|
+
try {
|
|
15171
|
+
const db = getDatabase();
|
|
15172
|
+
updateTask(db, task.id, { status: "in_progress" });
|
|
15173
|
+
task = getTask(db, task.id);
|
|
15174
|
+
} catch (e) {
|
|
15175
|
+
console.error("[task-runner] Failed to claim task:", e);
|
|
15176
|
+
_processing = false;
|
|
15177
|
+
return;
|
|
15178
|
+
}
|
|
15179
|
+
const handler = resolveHandler(task);
|
|
15180
|
+
if (!handler) {
|
|
15181
|
+
console.warn(`[task-runner] No handler for task: ${task.subject} (tags: ${JSON.stringify(task.tags)}, meta.type: ${task.metadata?.["type"]})`);
|
|
15182
|
+
try {
|
|
15183
|
+
const db = getDatabase();
|
|
15184
|
+
updateTask(db, task.id, {
|
|
15185
|
+
status: "failed",
|
|
15186
|
+
error: "No handler registered for this task type"
|
|
15187
|
+
});
|
|
15188
|
+
} catch {}
|
|
15189
|
+
_processing = false;
|
|
15190
|
+
_totalProcessed++;
|
|
15191
|
+
return;
|
|
15192
|
+
}
|
|
15193
|
+
const taskId = task.id;
|
|
15194
|
+
let progress = 0;
|
|
15195
|
+
const ctx = {
|
|
15196
|
+
task,
|
|
15197
|
+
updateProgress: (p) => {
|
|
15198
|
+
progress = Math.min(1, Math.max(0, p));
|
|
15199
|
+
try {
|
|
15200
|
+
const db = getDatabase();
|
|
15201
|
+
updateTask(db, taskId, { progress });
|
|
15202
|
+
} catch {}
|
|
15203
|
+
},
|
|
15204
|
+
addComment: (body, agentId) => {
|
|
15205
|
+
try {
|
|
15206
|
+
const db = getDatabase();
|
|
15207
|
+
addTaskComment(db, taskId, body, agentId);
|
|
15208
|
+
} catch {}
|
|
15209
|
+
}
|
|
15210
|
+
};
|
|
15211
|
+
try {
|
|
15212
|
+
await handler(ctx);
|
|
15213
|
+
const db = getDatabase();
|
|
15214
|
+
updateTask(db, taskId, { status: "completed", progress: 1 });
|
|
15215
|
+
console.log(`[task-runner] Completed: ${task.subject}`);
|
|
15216
|
+
} catch (e) {
|
|
15217
|
+
const errorMsg = e instanceof Error ? e.message : String(e);
|
|
15218
|
+
try {
|
|
15219
|
+
const db = getDatabase();
|
|
15220
|
+
updateTask(db, taskId, { status: "failed", error: errorMsg });
|
|
15221
|
+
} catch {}
|
|
15222
|
+
console.error(`[task-runner] Failed: ${task.subject} \u2014 ${errorMsg}`);
|
|
15223
|
+
} finally {
|
|
15224
|
+
_processing = false;
|
|
15225
|
+
_totalProcessed++;
|
|
15226
|
+
}
|
|
15227
|
+
}
|
|
15228
|
+
|
|
14711
15229
|
// src/server/router.ts
|
|
14712
15230
|
var routes = [];
|
|
15231
|
+
var nextRouteOrder = 0;
|
|
15232
|
+
function computeSpecificity(path) {
|
|
15233
|
+
return path.split("/").filter(Boolean).reduce((score, segment) => score + (segment.startsWith(":") ? 1 : 10), 0);
|
|
15234
|
+
}
|
|
14713
15235
|
function addRoute(method, path, handler) {
|
|
14714
15236
|
const paramNames = [];
|
|
14715
15237
|
const patternStr = path.replace(/:(\w+)/g, (_match, name) => {
|
|
@@ -14718,12 +15240,16 @@ function addRoute(method, path, handler) {
|
|
|
14718
15240
|
});
|
|
14719
15241
|
routes.push({
|
|
14720
15242
|
method,
|
|
15243
|
+
path,
|
|
14721
15244
|
pattern: new RegExp(`^${patternStr}$`),
|
|
14722
15245
|
paramNames,
|
|
15246
|
+
specificity: computeSpecificity(path),
|
|
15247
|
+
order: nextRouteOrder++,
|
|
14723
15248
|
handler
|
|
14724
15249
|
});
|
|
14725
15250
|
}
|
|
14726
15251
|
function matchRoute(method, pathname) {
|
|
15252
|
+
let bestMatch = null;
|
|
14727
15253
|
for (const route of routes) {
|
|
14728
15254
|
if (route.method !== method)
|
|
14729
15255
|
continue;
|
|
@@ -14733,10 +15259,14 @@ function matchRoute(method, pathname) {
|
|
|
14733
15259
|
route.paramNames.forEach((name, i) => {
|
|
14734
15260
|
params[name] = match[i + 1];
|
|
14735
15261
|
});
|
|
14736
|
-
|
|
15262
|
+
if (!bestMatch || route.specificity > bestMatch.route.specificity || route.specificity === bestMatch.route.specificity && route.order < bestMatch.route.order) {
|
|
15263
|
+
bestMatch = { route, params };
|
|
15264
|
+
}
|
|
14737
15265
|
}
|
|
14738
15266
|
}
|
|
14739
|
-
|
|
15267
|
+
if (!bestMatch)
|
|
15268
|
+
return null;
|
|
15269
|
+
return { handler: bestMatch.route.handler, params: bestMatch.params };
|
|
14740
15270
|
}
|
|
14741
15271
|
|
|
14742
15272
|
// src/server/helpers.ts
|
|
@@ -14744,7 +15274,7 @@ import { existsSync as existsSync6 } from "fs";
|
|
|
14744
15274
|
import { dirname as dirname4, extname, join as join7 } from "path";
|
|
14745
15275
|
import { fileURLToPath } from "url";
|
|
14746
15276
|
var CORS_HEADERS = {
|
|
14747
|
-
"Access-Control-Allow-Origin": "
|
|
15277
|
+
"Access-Control-Allow-Origin": process.env["MEMENTOS_CORS_ORIGIN"] ?? "http://localhost:19428",
|
|
14748
15278
|
"Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
|
|
14749
15279
|
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
14750
15280
|
"Access-Control-Max-Age": "86400"
|
|
@@ -14772,13 +15302,49 @@ function errorResponse(message, status, details) {
|
|
|
14772
15302
|
body["details"] = details;
|
|
14773
15303
|
return json(body, status);
|
|
14774
15304
|
}
|
|
15305
|
+
var MAX_BODY_BYTES = 1 * 1024 * 1024;
|
|
14775
15306
|
async function readJson(req) {
|
|
14776
15307
|
try {
|
|
15308
|
+
const contentLength = req.headers.get("content-length");
|
|
15309
|
+
if (contentLength && Number(contentLength) > MAX_BODY_BYTES) {
|
|
15310
|
+
throw Object.assign(new Error("Payload too large"), { status: 413 });
|
|
15311
|
+
}
|
|
14777
15312
|
return await req.json();
|
|
14778
15313
|
} catch {
|
|
14779
15314
|
return null;
|
|
14780
15315
|
}
|
|
14781
15316
|
}
|
|
15317
|
+
function getCorsHeaders(req) {
|
|
15318
|
+
const allowedOrigin = process.env["MEMENTOS_CORS_ORIGIN"] ?? "http://localhost:19428";
|
|
15319
|
+
const origin = req?.headers.get("origin");
|
|
15320
|
+
const finalOrigin = origin === allowedOrigin ? origin : allowedOrigin;
|
|
15321
|
+
return {
|
|
15322
|
+
"Access-Control-Allow-Origin": finalOrigin,
|
|
15323
|
+
"Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
|
|
15324
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
15325
|
+
"Access-Control-Max-Age": "86400"
|
|
15326
|
+
};
|
|
15327
|
+
}
|
|
15328
|
+
function authenticateRequest(req) {
|
|
15329
|
+
const requiredKey = process.env["MEMENTOS_API_KEY"];
|
|
15330
|
+
if (!requiredKey)
|
|
15331
|
+
return null;
|
|
15332
|
+
const authHeader = req.headers.get("authorization");
|
|
15333
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
15334
|
+
return new Response(JSON.stringify({ error: "Unauthorized. Provide a Bearer token in the Authorization header." }), {
|
|
15335
|
+
status: 401,
|
|
15336
|
+
headers: { "Content-Type": "application/json", ...getCorsHeaders(req) }
|
|
15337
|
+
});
|
|
15338
|
+
}
|
|
15339
|
+
const provided = authHeader.slice("Bearer ".length);
|
|
15340
|
+
if (provided !== requiredKey) {
|
|
15341
|
+
return new Response(JSON.stringify({ error: "Forbidden. Invalid API key." }), {
|
|
15342
|
+
status: 403,
|
|
15343
|
+
headers: { "Content-Type": "application/json", ...getCorsHeaders(req) }
|
|
15344
|
+
});
|
|
15345
|
+
}
|
|
15346
|
+
return null;
|
|
15347
|
+
}
|
|
14782
15348
|
function getSearchParams(url) {
|
|
14783
15349
|
const params = {};
|
|
14784
15350
|
url.searchParams.forEach((v, k) => {
|
|
@@ -14814,11 +15380,8 @@ function serveStaticFile(filePath) {
|
|
|
14814
15380
|
});
|
|
14815
15381
|
}
|
|
14816
15382
|
|
|
14817
|
-
// src/server/routes/memories.ts
|
|
15383
|
+
// src/server/routes/memories-crud.ts
|
|
14818
15384
|
init_memories();
|
|
14819
|
-
init_database();
|
|
14820
|
-
init_search();
|
|
14821
|
-
init_types2();
|
|
14822
15385
|
|
|
14823
15386
|
// src/lib/duration.ts
|
|
14824
15387
|
var UNIT_MS = {
|
|
@@ -14863,10 +15426,8 @@ var FORMAT_UNITS = [
|
|
|
14863
15426
|
["s", UNIT_MS["s"]]
|
|
14864
15427
|
];
|
|
14865
15428
|
|
|
14866
|
-
// src/server/routes/memories.ts
|
|
14867
|
-
|
|
14868
|
-
return json({ ok: true, version: "1", db: getDbPath() });
|
|
14869
|
-
});
|
|
15429
|
+
// src/server/routes/memories-crud.ts
|
|
15430
|
+
init_types2();
|
|
14870
15431
|
addRoute("GET", "/api/memories", (_req, url) => {
|
|
14871
15432
|
const q = getSearchParams(url);
|
|
14872
15433
|
const filter = {};
|
|
@@ -14886,6 +15447,8 @@ addRoute("GET", "/api/memories", (_req, url) => {
|
|
|
14886
15447
|
filter.project_id = q["project_id"];
|
|
14887
15448
|
if (q["session_id"])
|
|
14888
15449
|
filter.session_id = q["session_id"];
|
|
15450
|
+
if (q["namespace"])
|
|
15451
|
+
filter.namespace = q["namespace"];
|
|
14889
15452
|
if (q["status"])
|
|
14890
15453
|
filter.status = q["status"];
|
|
14891
15454
|
if (q["limit"])
|
|
@@ -14900,40 +15463,114 @@ addRoute("GET", "/api/memories", (_req, url) => {
|
|
|
14900
15463
|
}
|
|
14901
15464
|
return json({ memories, count: memories.length });
|
|
14902
15465
|
});
|
|
14903
|
-
addRoute("
|
|
14904
|
-
const
|
|
14905
|
-
|
|
14906
|
-
|
|
14907
|
-
|
|
14908
|
-
|
|
14909
|
-
|
|
14910
|
-
|
|
14911
|
-
|
|
14912
|
-
|
|
14913
|
-
|
|
14914
|
-
|
|
14915
|
-
|
|
14916
|
-
|
|
14917
|
-
|
|
14918
|
-
|
|
14919
|
-
|
|
14920
|
-
for (const row of byScope)
|
|
14921
|
-
stats.by_scope[row.scope] = row.c;
|
|
14922
|
-
for (const row of byCategory)
|
|
14923
|
-
stats.by_category[row.category] = row.c;
|
|
14924
|
-
for (const row of byStatus) {
|
|
14925
|
-
if (row.status in stats.by_status) {
|
|
14926
|
-
stats.by_status[row.status] = row.c;
|
|
15466
|
+
addRoute("POST", "/api/memories", async (req) => {
|
|
15467
|
+
const body = await readJson(req);
|
|
15468
|
+
if (!body) {
|
|
15469
|
+
return errorResponse("Invalid JSON body", 400);
|
|
15470
|
+
}
|
|
15471
|
+
if (!body["key"] || !body["value"]) {
|
|
15472
|
+
return errorResponse("Missing required fields: key, value", 400);
|
|
15473
|
+
}
|
|
15474
|
+
try {
|
|
15475
|
+
if (body["ttl_ms"] !== undefined && typeof body["ttl_ms"] === "string") {
|
|
15476
|
+
body["ttl_ms"] = parseDuration(body["ttl_ms"]);
|
|
15477
|
+
}
|
|
15478
|
+
const memory = createMemory(body);
|
|
15479
|
+
return json(memory, 201);
|
|
15480
|
+
} catch (e) {
|
|
15481
|
+
if (e instanceof DuplicateMemoryError) {
|
|
15482
|
+
return errorResponse(e.message, 409);
|
|
14927
15483
|
}
|
|
15484
|
+
throw e;
|
|
14928
15485
|
}
|
|
14929
|
-
const byAgent = db.query("SELECT agent_id, COUNT(*) as c FROM memories WHERE status = 'active' AND agent_id IS NOT NULL GROUP BY agent_id").all();
|
|
14930
|
-
for (const row of byAgent)
|
|
14931
|
-
stats.by_agent[row.agent_id] = row.c;
|
|
14932
|
-
return json(stats);
|
|
14933
15486
|
});
|
|
14934
|
-
addRoute("GET", "/api/
|
|
14935
|
-
const
|
|
14936
|
-
|
|
15487
|
+
addRoute("GET", "/api/memories/:id", (_req, _url, params) => {
|
|
15488
|
+
const memory = getMemory(params["id"]);
|
|
15489
|
+
if (!memory) {
|
|
15490
|
+
return errorResponse("Memory not found", 404);
|
|
15491
|
+
}
|
|
15492
|
+
touchMemory(memory.id);
|
|
15493
|
+
return json(memory);
|
|
15494
|
+
});
|
|
15495
|
+
addRoute("PATCH", "/api/memories/:id", async (req, _url, params) => {
|
|
15496
|
+
const body = await readJson(req);
|
|
15497
|
+
if (!body) {
|
|
15498
|
+
return errorResponse("Invalid JSON body", 400);
|
|
15499
|
+
}
|
|
15500
|
+
const updateBody = { ...body };
|
|
15501
|
+
if (updateBody["version"] === undefined) {
|
|
15502
|
+
const existing = getMemory(params["id"]);
|
|
15503
|
+
if (!existing)
|
|
15504
|
+
return errorResponse("Memory not found", 404);
|
|
15505
|
+
updateBody["version"] = existing.version;
|
|
15506
|
+
}
|
|
15507
|
+
try {
|
|
15508
|
+
const memory = updateMemory(params["id"], updateBody);
|
|
15509
|
+
return json(memory);
|
|
15510
|
+
} catch (e) {
|
|
15511
|
+
if (e instanceof MemoryNotFoundError) {
|
|
15512
|
+
return errorResponse(e.message, 404);
|
|
15513
|
+
}
|
|
15514
|
+
if (e instanceof VersionConflictError) {
|
|
15515
|
+
return errorResponse(e.message, 409, {
|
|
15516
|
+
expected: e.expected,
|
|
15517
|
+
actual: e.actual
|
|
15518
|
+
});
|
|
15519
|
+
}
|
|
15520
|
+
throw e;
|
|
15521
|
+
}
|
|
15522
|
+
});
|
|
15523
|
+
addRoute("GET", "/api/memories/:id/versions", (_req, _url, params) => {
|
|
15524
|
+
const memory = getMemory(params["id"]);
|
|
15525
|
+
if (!memory)
|
|
15526
|
+
return errorResponse("Memory not found", 404);
|
|
15527
|
+
const versions = getMemoryVersions(memory.id);
|
|
15528
|
+
return json({ versions, count: versions.length, current_version: memory.version });
|
|
15529
|
+
});
|
|
15530
|
+
addRoute("DELETE", "/api/memories/:id", (_req, _url, params) => {
|
|
15531
|
+
const deleted = deleteMemory(params["id"]);
|
|
15532
|
+
if (!deleted) {
|
|
15533
|
+
return errorResponse("Memory not found", 404);
|
|
15534
|
+
}
|
|
15535
|
+
return json({ deleted: true });
|
|
15536
|
+
});
|
|
15537
|
+
|
|
15538
|
+
// src/server/routes/memories-stats.ts
|
|
15539
|
+
init_database();
|
|
15540
|
+
addRoute("GET", "/api/memories/stats", (_req) => {
|
|
15541
|
+
const db = getDatabase();
|
|
15542
|
+
const total = db.query("SELECT COUNT(*) as c FROM memories WHERE status = 'active'").get().c;
|
|
15543
|
+
const byScope = db.query("SELECT scope, COUNT(*) as c FROM memories WHERE status = 'active' GROUP BY scope").all();
|
|
15544
|
+
const byCategory = db.query("SELECT category, COUNT(*) as c FROM memories WHERE status = 'active' GROUP BY category").all();
|
|
15545
|
+
const byStatus = db.query("SELECT status, COUNT(*) as c FROM memories GROUP BY status").all();
|
|
15546
|
+
const pinnedCount = db.query("SELECT COUNT(*) as c FROM memories WHERE pinned = 1 AND status = 'active'").get().c;
|
|
15547
|
+
const expiredCount = db.query("SELECT COUNT(*) as c FROM memories WHERE status = 'expired' OR (expires_at IS NOT NULL AND expires_at < datetime('now'))").get().c;
|
|
15548
|
+
const stats = {
|
|
15549
|
+
total,
|
|
15550
|
+
by_scope: { global: 0, shared: 0, private: 0, working: 0 },
|
|
15551
|
+
by_category: { preference: 0, fact: 0, knowledge: 0, history: 0, procedural: 0, resource: 0 },
|
|
15552
|
+
by_status: { active: 0, archived: 0, expired: 0 },
|
|
15553
|
+
by_agent: {},
|
|
15554
|
+
pinned_count: pinnedCount,
|
|
15555
|
+
expired_count: expiredCount
|
|
15556
|
+
};
|
|
15557
|
+
for (const row of byScope)
|
|
15558
|
+
stats.by_scope[row.scope] = row.c;
|
|
15559
|
+
for (const row of byCategory)
|
|
15560
|
+
stats.by_category[row.category] = row.c;
|
|
15561
|
+
for (const row of byStatus) {
|
|
15562
|
+
if (row.status in stats.by_status) {
|
|
15563
|
+
stats.by_status[row.status] = row.c;
|
|
15564
|
+
}
|
|
15565
|
+
}
|
|
15566
|
+
const byAgent = db.query("SELECT agent_id, COUNT(*) as c FROM memories WHERE status = 'active' AND agent_id IS NOT NULL GROUP BY agent_id").all();
|
|
15567
|
+
for (const row of byAgent)
|
|
15568
|
+
stats.by_agent[row.agent_id] = row.c;
|
|
15569
|
+
return json(stats);
|
|
15570
|
+
});
|
|
15571
|
+
addRoute("GET", "/api/metrics", (_req) => {
|
|
15572
|
+
const db = getDatabase();
|
|
15573
|
+
const total = db.query("SELECT COUNT(*) as c FROM memories WHERE status = 'active'").get().c;
|
|
14937
15574
|
const byScope = db.query("SELECT scope, COUNT(*) as c FROM memories WHERE status = 'active' GROUP BY scope").all();
|
|
14938
15575
|
const byCategory = db.query("SELECT category, COUNT(*) as c FROM memories WHERE status = 'active' GROUP BY category").all();
|
|
14939
15576
|
const last7 = db.query("SELECT COUNT(*) as c FROM memories WHERE created_at >= datetime('now', '-7 days')").get().c;
|
|
@@ -14975,6 +15612,8 @@ addRoute("GET", "/api/activity", (_req, url) => {
|
|
|
14975
15612
|
params.push(projectId);
|
|
14976
15613
|
}
|
|
14977
15614
|
const where = conditions.map((c) => `AND ${c}`).join(" ");
|
|
15615
|
+
const cutoffDate = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);
|
|
15616
|
+
params.push(cutoffDate);
|
|
14978
15617
|
const rows = db.query(`
|
|
14979
15618
|
SELECT
|
|
14980
15619
|
date(created_at) AS date,
|
|
@@ -14984,7 +15623,7 @@ addRoute("GET", "/api/activity", (_req, url) => {
|
|
|
14984
15623
|
SUM(CASE WHEN scope = 'private' THEN 1 ELSE 0 END) AS private_count,
|
|
14985
15624
|
AVG(importance) AS avg_importance
|
|
14986
15625
|
FROM memories
|
|
14987
|
-
WHERE date(created_at) >=
|
|
15626
|
+
WHERE date(created_at) >= ? ${where}
|
|
14988
15627
|
GROUP BY date(created_at)
|
|
14989
15628
|
ORDER BY date ASC
|
|
14990
15629
|
`).all(...params);
|
|
@@ -14997,12 +15636,13 @@ addRoute("GET", "/api/memories/stale", (_req, url) => {
|
|
|
14997
15636
|
const agentId = q["agent_id"];
|
|
14998
15637
|
const limit = Math.min(parseInt(q["limit"] || "20", 10), 100);
|
|
14999
15638
|
const db = getDatabase();
|
|
15639
|
+
const cutoffDate = new Date(Date.now() - days * 86400000).toISOString();
|
|
15000
15640
|
const conds = [
|
|
15001
15641
|
"status = 'active'",
|
|
15002
|
-
|
|
15642
|
+
"(accessed_at IS NULL OR accessed_at < ?)",
|
|
15003
15643
|
"pinned = 0"
|
|
15004
15644
|
];
|
|
15005
|
-
const params = [];
|
|
15645
|
+
const params = [cutoffDate];
|
|
15006
15646
|
if (projectId) {
|
|
15007
15647
|
conds.push("project_id = ?");
|
|
15008
15648
|
params.push(projectId);
|
|
@@ -15020,23 +15660,28 @@ addRoute("GET", "/api/report", (_req, url) => {
|
|
|
15020
15660
|
const projectId = q["project_id"];
|
|
15021
15661
|
const agentId = q["agent_id"];
|
|
15022
15662
|
const db = getDatabase();
|
|
15023
|
-
const
|
|
15663
|
+
const cutoffDate = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);
|
|
15664
|
+
const scopedCond = [
|
|
15024
15665
|
projectId ? "AND project_id = ?" : "",
|
|
15025
15666
|
agentId ? "AND agent_id = ?" : ""
|
|
15026
15667
|
].filter(Boolean).join(" ");
|
|
15027
|
-
const
|
|
15028
|
-
|
|
15029
|
-
|
|
15668
|
+
const scopedParams = [
|
|
15669
|
+
...projectId ? [projectId] : [],
|
|
15670
|
+
...agentId ? [agentId] : []
|
|
15671
|
+
];
|
|
15672
|
+
const recentParams = [cutoffDate, ...scopedParams];
|
|
15673
|
+
const total = db.query(`SELECT COUNT(*) as c FROM memories WHERE status = 'active' ${scopedCond}`).get(...scopedParams).c;
|
|
15674
|
+
const pinned = db.query(`SELECT COUNT(*) as c FROM memories WHERE status = 'active' AND pinned = 1 ${scopedCond}`).get(...scopedParams).c;
|
|
15030
15675
|
const actRows = db.query(`
|
|
15031
15676
|
SELECT date(created_at) AS date, COUNT(*) AS memories_created
|
|
15032
|
-
FROM memories WHERE status = 'active' AND date(created_at) >=
|
|
15677
|
+
FROM memories WHERE status = 'active' AND date(created_at) >= ? ${scopedCond}
|
|
15033
15678
|
GROUP BY date(created_at) ORDER BY date(created_at) ASC
|
|
15034
|
-
`).all(...
|
|
15679
|
+
`).all(...recentParams);
|
|
15035
15680
|
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 ${
|
|
15681
|
+
const byScopeRows = db.query(`SELECT scope, COUNT(*) as c FROM memories WHERE status = 'active' ${scopedCond} GROUP BY scope`).all(...scopedParams);
|
|
15682
|
+
const byCatRows = db.query(`SELECT category, COUNT(*) as c FROM memories WHERE status = 'active' ${scopedCond} GROUP BY category`).all(...scopedParams);
|
|
15683
|
+
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);
|
|
15684
|
+
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
15685
|
return json({
|
|
15041
15686
|
total,
|
|
15042
15687
|
pinned,
|
|
@@ -15048,6 +15693,9 @@ addRoute("GET", "/api/report", (_req, url) => {
|
|
|
15048
15693
|
top_agents: topAgents
|
|
15049
15694
|
});
|
|
15050
15695
|
});
|
|
15696
|
+
|
|
15697
|
+
// src/server/routes/memories-search.ts
|
|
15698
|
+
init_search();
|
|
15051
15699
|
addRoute("POST", "/api/memories/search", async (req) => {
|
|
15052
15700
|
const body = await readJson(req);
|
|
15053
15701
|
if (!body || typeof body["query"] !== "string") {
|
|
@@ -15060,6 +15708,10 @@ addRoute("POST", "/api/memories/search", async (req) => {
|
|
|
15060
15708
|
filter.category = body["category"];
|
|
15061
15709
|
if (body["tags"])
|
|
15062
15710
|
filter.tags = body["tags"];
|
|
15711
|
+
if (body["session_id"])
|
|
15712
|
+
filter.session_id = body["session_id"];
|
|
15713
|
+
if (body["namespace"])
|
|
15714
|
+
filter.namespace = body["namespace"];
|
|
15063
15715
|
if (body["limit"])
|
|
15064
15716
|
filter.limit = body["limit"];
|
|
15065
15717
|
const results = searchMemories(body["query"], filter);
|
|
@@ -15081,6 +15733,10 @@ addRoute("POST", "/api/memories/search/hybrid", async (req) => {
|
|
|
15081
15733
|
filter.agent_id = body["agent_id"];
|
|
15082
15734
|
if (body["project_id"])
|
|
15083
15735
|
filter.project_id = body["project_id"];
|
|
15736
|
+
if (body["session_id"])
|
|
15737
|
+
filter.session_id = body["session_id"];
|
|
15738
|
+
if (body["namespace"])
|
|
15739
|
+
filter.namespace = body["namespace"];
|
|
15084
15740
|
const results = await hybridSearch(body["query"], {
|
|
15085
15741
|
filter,
|
|
15086
15742
|
semantic_threshold: body["semantic_threshold"] ?? undefined,
|
|
@@ -15104,11 +15760,60 @@ addRoute("POST", "/api/memories/search/bm25", async (req) => {
|
|
|
15104
15760
|
filter.agent_id = body["agent_id"];
|
|
15105
15761
|
if (body["project_id"])
|
|
15106
15762
|
filter.project_id = body["project_id"];
|
|
15763
|
+
if (body["session_id"])
|
|
15764
|
+
filter.session_id = body["session_id"];
|
|
15765
|
+
if (body["namespace"])
|
|
15766
|
+
filter.namespace = body["namespace"];
|
|
15107
15767
|
if (body["limit"])
|
|
15108
15768
|
filter.limit = body["limit"];
|
|
15109
15769
|
const results = searchWithBm25(body["query"], filter);
|
|
15110
15770
|
return json({ results, count: results.length });
|
|
15111
15771
|
});
|
|
15772
|
+
|
|
15773
|
+
// src/server/routes/memories-bulk.ts
|
|
15774
|
+
init_memories();
|
|
15775
|
+
addRoute("POST", "/api/memories/bulk-forget", async (req) => {
|
|
15776
|
+
const body = await readJson(req);
|
|
15777
|
+
if (!body || !Array.isArray(body["ids"])) {
|
|
15778
|
+
return errorResponse("Missing required field: ids (array)", 400);
|
|
15779
|
+
}
|
|
15780
|
+
const ids = body["ids"];
|
|
15781
|
+
let deleted = 0;
|
|
15782
|
+
for (const id of ids) {
|
|
15783
|
+
try {
|
|
15784
|
+
if (deleteMemory(id))
|
|
15785
|
+
deleted++;
|
|
15786
|
+
} catch {}
|
|
15787
|
+
}
|
|
15788
|
+
return json({ deleted, total: ids.length });
|
|
15789
|
+
});
|
|
15790
|
+
addRoute("POST", "/api/memories/bulk-update", async (req) => {
|
|
15791
|
+
const body = await readJson(req);
|
|
15792
|
+
if (!body || !Array.isArray(body["ids"])) {
|
|
15793
|
+
return errorResponse("Missing required fields: ids (array)", 400);
|
|
15794
|
+
}
|
|
15795
|
+
const ids = body["ids"];
|
|
15796
|
+
const { ids: _ids, ...fields } = body;
|
|
15797
|
+
let updated = 0;
|
|
15798
|
+
const errors = [];
|
|
15799
|
+
for (const id of ids) {
|
|
15800
|
+
try {
|
|
15801
|
+
const memory = getMemory(id);
|
|
15802
|
+
if (memory) {
|
|
15803
|
+
updateMemory(id, { ...fields, version: memory.version });
|
|
15804
|
+
updated++;
|
|
15805
|
+
} else {
|
|
15806
|
+
errors.push(`Memory not found: ${id}`);
|
|
15807
|
+
}
|
|
15808
|
+
} catch (e) {
|
|
15809
|
+
errors.push(`Failed ${id}: ${e instanceof Error ? e.message : String(e)}`);
|
|
15810
|
+
}
|
|
15811
|
+
}
|
|
15812
|
+
return json({ updated, errors, total: ids.length });
|
|
15813
|
+
});
|
|
15814
|
+
|
|
15815
|
+
// src/server/routes/memories-io.ts
|
|
15816
|
+
init_memories();
|
|
15112
15817
|
addRoute("POST", "/api/memories/export", async (req) => {
|
|
15113
15818
|
const body = await readJson(req) || {};
|
|
15114
15819
|
const filter = {};
|
|
@@ -15149,49 +15854,35 @@ addRoute("POST", "/api/memories/import", async (req) => {
|
|
|
15149
15854
|
}
|
|
15150
15855
|
return json({ imported, errors, total: memoriesArr.length }, 201);
|
|
15151
15856
|
});
|
|
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);
|
|
15857
|
+
|
|
15858
|
+
// src/server/routes/memories-misc.ts
|
|
15859
|
+
init_memories();
|
|
15860
|
+
|
|
15861
|
+
// src/lib/machine-visibility.ts
|
|
15862
|
+
function resolveVisibleMachineId(machineId, db) {
|
|
15863
|
+
if (machineId !== undefined) {
|
|
15864
|
+
return machineId;
|
|
15171
15865
|
}
|
|
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
|
-
}
|
|
15866
|
+
try {
|
|
15867
|
+
return getCurrentMachineId(db);
|
|
15868
|
+
} catch {
|
|
15869
|
+
return null;
|
|
15188
15870
|
}
|
|
15189
|
-
|
|
15871
|
+
}
|
|
15872
|
+
function visibleToMachineFilter(machineId, db) {
|
|
15873
|
+
return {
|
|
15874
|
+
visible_to_machine_id: resolveVisibleMachineId(machineId, db)
|
|
15875
|
+
};
|
|
15876
|
+
}
|
|
15877
|
+
|
|
15878
|
+
// src/server/routes/memories-misc.ts
|
|
15879
|
+
addRoute("GET", "/api/health", () => {
|
|
15880
|
+
return json({ ok: true, version: "1", db: getDbPath() });
|
|
15190
15881
|
});
|
|
15191
15882
|
addRoute("POST", "/api/memories/extract", async (req) => {
|
|
15192
15883
|
const body = await readJson(req);
|
|
15193
15884
|
if (!body)
|
|
15194
|
-
return
|
|
15885
|
+
return json({ error: "Invalid JSON body" }, 400);
|
|
15195
15886
|
const sessionId = body["session_id"];
|
|
15196
15887
|
const agentId = body["agent_id"];
|
|
15197
15888
|
const projectId = body["project_id"];
|
|
@@ -15259,77 +15950,6 @@ addRoute("POST", "/api/memories/clean", () => {
|
|
|
15259
15950
|
const cleaned = cleanExpiredMemories();
|
|
15260
15951
|
return json({ cleaned });
|
|
15261
15952
|
});
|
|
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
15953
|
addRoute("GET", "/api/inject", (_req, url) => {
|
|
15334
15954
|
const q = getSearchParams(url);
|
|
15335
15955
|
const maxTokens = q["max_tokens"] ? parseInt(q["max_tokens"], 10) : 500;
|
|
@@ -15339,6 +15959,7 @@ addRoute("GET", "/api/inject", (_req, url) => {
|
|
|
15339
15959
|
"fact",
|
|
15340
15960
|
"knowledge"
|
|
15341
15961
|
];
|
|
15962
|
+
const visibleMachineId = resolveVisibleMachineId(q["machine_id"]);
|
|
15342
15963
|
const allMemories = [];
|
|
15343
15964
|
const globalMems = listMemories({
|
|
15344
15965
|
scope: "global",
|
|
@@ -15346,6 +15967,7 @@ addRoute("GET", "/api/inject", (_req, url) => {
|
|
|
15346
15967
|
min_importance: minImportance,
|
|
15347
15968
|
status: "active",
|
|
15348
15969
|
project_id: q["project_id"],
|
|
15970
|
+
...visibleToMachineFilter(visibleMachineId),
|
|
15349
15971
|
limit: 50
|
|
15350
15972
|
});
|
|
15351
15973
|
allMemories.push(...globalMems);
|
|
@@ -15356,6 +15978,7 @@ addRoute("GET", "/api/inject", (_req, url) => {
|
|
|
15356
15978
|
min_importance: minImportance,
|
|
15357
15979
|
status: "active",
|
|
15358
15980
|
project_id: q["project_id"],
|
|
15981
|
+
...visibleToMachineFilter(visibleMachineId),
|
|
15359
15982
|
limit: 50
|
|
15360
15983
|
});
|
|
15361
15984
|
allMemories.push(...sharedMems);
|
|
@@ -15367,6 +15990,7 @@ addRoute("GET", "/api/inject", (_req, url) => {
|
|
|
15367
15990
|
min_importance: minImportance,
|
|
15368
15991
|
status: "active",
|
|
15369
15992
|
agent_id: q["agent_id"],
|
|
15993
|
+
...visibleToMachineFilter(visibleMachineId),
|
|
15370
15994
|
limit: 50
|
|
15371
15995
|
});
|
|
15372
15996
|
allMemories.push(...privateMems);
|
|
@@ -16028,10 +16652,212 @@ addRoute("GET", "/api/graph/:entityId", (_req, url, params) => {
|
|
|
16028
16652
|
}
|
|
16029
16653
|
});
|
|
16030
16654
|
|
|
16031
|
-
// src/server/routes/
|
|
16655
|
+
// src/server/routes/tasks.ts
|
|
16656
|
+
init_database();
|
|
16657
|
+
addRoute("POST", "/api/tasks", async (req, _url) => {
|
|
16658
|
+
const body = await readJson(req);
|
|
16659
|
+
if (!body)
|
|
16660
|
+
return errorResponse("Invalid JSON body", 400);
|
|
16661
|
+
if (!body.subject)
|
|
16662
|
+
return errorResponse("subject is required", 400);
|
|
16663
|
+
const task = createTask(getDatabase(), body);
|
|
16664
|
+
return json(task, 201);
|
|
16665
|
+
});
|
|
16666
|
+
addRoute("GET", "/api/tasks", async (_req, url) => {
|
|
16667
|
+
const q = getSearchParams(url);
|
|
16668
|
+
const filter = {};
|
|
16669
|
+
for (const key of ["status", "priority", "assigned_agent_id", "project_id", "session_id", "parent_task_id"]) {
|
|
16670
|
+
if (q[key])
|
|
16671
|
+
filter[key] = q[key];
|
|
16672
|
+
}
|
|
16673
|
+
if (q["tags"])
|
|
16674
|
+
filter.tags = q["tags"].split(",");
|
|
16675
|
+
if (q["limit"])
|
|
16676
|
+
filter.limit = parseInt(q["limit"]);
|
|
16677
|
+
if (q["offset"])
|
|
16678
|
+
filter.offset = parseInt(q["offset"]);
|
|
16679
|
+
return json(listTasks(getDatabase(), filter));
|
|
16680
|
+
});
|
|
16681
|
+
addRoute("GET", "/api/tasks/stats", async (_req, url) => {
|
|
16682
|
+
const q = getSearchParams(url);
|
|
16683
|
+
const filter = {};
|
|
16684
|
+
if (q["project_id"])
|
|
16685
|
+
filter.project_id = q["project_id"];
|
|
16686
|
+
if (q["agent_id"])
|
|
16687
|
+
filter.agent_id = q["agent_id"];
|
|
16688
|
+
return json(getTaskStats(getDatabase(), filter));
|
|
16689
|
+
});
|
|
16690
|
+
addRoute("GET", "/api/tasks/:id", async (_req, _url, params) => {
|
|
16691
|
+
const task = getTask(getDatabase(), params.id);
|
|
16692
|
+
if (!task)
|
|
16693
|
+
return errorResponse("Task not found", 404);
|
|
16694
|
+
return json(task);
|
|
16695
|
+
});
|
|
16696
|
+
addRoute("PATCH", "/api/tasks/:id", async (req, _url, params) => {
|
|
16697
|
+
const body = await readJson(req);
|
|
16698
|
+
if (!body)
|
|
16699
|
+
return errorResponse("Invalid JSON body", 400);
|
|
16700
|
+
const task = updateTask(getDatabase(), params.id, body);
|
|
16701
|
+
if (!task)
|
|
16702
|
+
return errorResponse("Task not found", 404);
|
|
16703
|
+
return json(task);
|
|
16704
|
+
});
|
|
16705
|
+
addRoute("DELETE", "/api/tasks/:id", async (_req, _url, params) => {
|
|
16706
|
+
const deleted = deleteTask(getDatabase(), params.id);
|
|
16707
|
+
if (!deleted)
|
|
16708
|
+
return errorResponse("Task not found", 404);
|
|
16709
|
+
return json({ deleted: true });
|
|
16710
|
+
});
|
|
16711
|
+
addRoute("GET", "/api/tasks/:id/comments", async (_req, _url, params) => {
|
|
16712
|
+
return json(listTaskComments(getDatabase(), params.id));
|
|
16713
|
+
});
|
|
16714
|
+
addRoute("POST", "/api/tasks/:id/comments", async (req, _url, params) => {
|
|
16715
|
+
const body = await readJson(req);
|
|
16716
|
+
if (!body)
|
|
16717
|
+
return errorResponse("Invalid JSON body", 400);
|
|
16718
|
+
if (!body.body)
|
|
16719
|
+
return errorResponse("body is required", 400);
|
|
16720
|
+
const comment = addTaskComment(getDatabase(), params.id, body.body, body.agent_id);
|
|
16721
|
+
return json(comment, 201);
|
|
16722
|
+
});
|
|
16723
|
+
addRoute("DELETE", "/api/tasks/:id/comments/:commentId", async (_req, _url, params) => {
|
|
16724
|
+
const deleted = deleteTaskComment(getDatabase(), params.commentId);
|
|
16725
|
+
if (!deleted)
|
|
16726
|
+
return errorResponse("Comment not found", 404);
|
|
16727
|
+
return json({ deleted: true });
|
|
16728
|
+
});
|
|
16729
|
+
|
|
16730
|
+
// src/server/routes/system-auto-memory.ts
|
|
16032
16731
|
init_auto_memory();
|
|
16033
16732
|
init_registry();
|
|
16733
|
+
function registerSystemAutoMemoryRoutes() {
|
|
16734
|
+
addRoute("POST", "/api/auto-memory/process", async (req) => {
|
|
16735
|
+
const body = await readJson(req);
|
|
16736
|
+
const turn = body?.turn;
|
|
16737
|
+
if (!turn)
|
|
16738
|
+
return errorResponse("turn is required", 400);
|
|
16739
|
+
processConversationTurn(turn, { agentId: body?.agent_id, projectId: body?.project_id, sessionId: body?.session_id });
|
|
16740
|
+
const stats = getAutoMemoryStats();
|
|
16741
|
+
return json({ queued: true, queue: stats }, 202);
|
|
16742
|
+
});
|
|
16743
|
+
addRoute("GET", "/api/auto-memory/status", () => {
|
|
16744
|
+
return json({
|
|
16745
|
+
queue: getAutoMemoryStats(),
|
|
16746
|
+
config: providerRegistry.getConfig(),
|
|
16747
|
+
providers: providerRegistry.health()
|
|
16748
|
+
});
|
|
16749
|
+
});
|
|
16750
|
+
addRoute("GET", "/api/auto-memory/config", () => {
|
|
16751
|
+
return json(providerRegistry.getConfig());
|
|
16752
|
+
});
|
|
16753
|
+
addRoute("PATCH", "/api/auto-memory/config", async (req) => {
|
|
16754
|
+
const body = await readJson(req) ?? {};
|
|
16755
|
+
const patch = {};
|
|
16756
|
+
if (body.provider)
|
|
16757
|
+
patch.provider = body.provider;
|
|
16758
|
+
if (body.model)
|
|
16759
|
+
patch.model = body.model;
|
|
16760
|
+
if (body.enabled !== undefined)
|
|
16761
|
+
patch.enabled = Boolean(body.enabled);
|
|
16762
|
+
if (body.min_importance !== undefined)
|
|
16763
|
+
patch.minImportance = Number(body.min_importance);
|
|
16764
|
+
if (body.auto_entity_link !== undefined)
|
|
16765
|
+
patch.autoEntityLink = Boolean(body.auto_entity_link);
|
|
16766
|
+
configureAutoMemory(patch);
|
|
16767
|
+
return json({ updated: true, config: providerRegistry.getConfig() });
|
|
16768
|
+
});
|
|
16769
|
+
addRoute("POST", "/api/auto-memory/test", async (req) => {
|
|
16770
|
+
const body = await readJson(req) ?? {};
|
|
16771
|
+
const { turn, provider: providerName, agent_id, project_id } = body;
|
|
16772
|
+
if (!turn)
|
|
16773
|
+
return errorResponse("turn is required", 400);
|
|
16774
|
+
const provider = providerName ? providerRegistry.getProvider(providerName) : providerRegistry.getAvailable();
|
|
16775
|
+
if (!provider)
|
|
16776
|
+
return errorResponse("No LLM provider configured. Set an API key (ANTHROPIC_API_KEY, OPENAI_API_KEY, CEREBRAS_API_KEY, or XAI_API_KEY).", 503);
|
|
16777
|
+
try {
|
|
16778
|
+
const memories = await provider.extractMemories(turn, { agentId: agent_id, projectId: project_id });
|
|
16779
|
+
return json({
|
|
16780
|
+
provider: provider.name,
|
|
16781
|
+
model: provider.config.model,
|
|
16782
|
+
extracted: memories,
|
|
16783
|
+
count: memories.length,
|
|
16784
|
+
note: "DRY RUN \u2014 nothing was saved"
|
|
16785
|
+
});
|
|
16786
|
+
} catch (e) {
|
|
16787
|
+
return json({ error: e instanceof Error ? e.message : String(e) }, 500);
|
|
16788
|
+
}
|
|
16789
|
+
});
|
|
16790
|
+
}
|
|
16791
|
+
|
|
16792
|
+
// src/server/routes/system-hooks.ts
|
|
16034
16793
|
init_hooks();
|
|
16794
|
+
function registerSystemHookRoutes() {
|
|
16795
|
+
addRoute("GET", "/api/hooks", (_req, url) => {
|
|
16796
|
+
const type = url.searchParams.get("type") ?? undefined;
|
|
16797
|
+
const hooks = hookRegistry.list(type);
|
|
16798
|
+
return json(hooks.map((h) => ({
|
|
16799
|
+
id: h.id,
|
|
16800
|
+
type: h.type,
|
|
16801
|
+
blocking: h.blocking,
|
|
16802
|
+
priority: h.priority,
|
|
16803
|
+
builtin: h.builtin ?? false,
|
|
16804
|
+
agentId: h.agentId,
|
|
16805
|
+
projectId: h.projectId,
|
|
16806
|
+
description: h.description
|
|
16807
|
+
})));
|
|
16808
|
+
});
|
|
16809
|
+
addRoute("GET", "/api/hooks/stats", () => json(hookRegistry.stats()));
|
|
16810
|
+
addRoute("GET", "/api/webhooks", (_req, url) => {
|
|
16811
|
+
const type = url.searchParams.get("type") ?? undefined;
|
|
16812
|
+
const enabledParam = url.searchParams.get("enabled");
|
|
16813
|
+
const enabled = enabledParam !== null ? enabledParam === "true" : undefined;
|
|
16814
|
+
return json(listWebhookHooks({
|
|
16815
|
+
type,
|
|
16816
|
+
enabled
|
|
16817
|
+
}));
|
|
16818
|
+
});
|
|
16819
|
+
addRoute("POST", "/api/webhooks", async (req) => {
|
|
16820
|
+
const body = await readJson(req) ?? {};
|
|
16821
|
+
if (!body.type || !body.handler_url) {
|
|
16822
|
+
return errorResponse("type and handler_url are required", 400);
|
|
16823
|
+
}
|
|
16824
|
+
const wh = createWebhookHook({
|
|
16825
|
+
type: body.type,
|
|
16826
|
+
handlerUrl: body.handler_url,
|
|
16827
|
+
priority: body.priority,
|
|
16828
|
+
blocking: body.blocking,
|
|
16829
|
+
agentId: body.agent_id,
|
|
16830
|
+
projectId: body.project_id,
|
|
16831
|
+
description: body.description
|
|
16832
|
+
});
|
|
16833
|
+
reloadWebhooks();
|
|
16834
|
+
return json(wh, 201);
|
|
16835
|
+
});
|
|
16836
|
+
addRoute("GET", "/api/webhooks/:id", (_req, _url, params) => {
|
|
16837
|
+
const wh = getWebhookHook(params["id"]);
|
|
16838
|
+
if (!wh)
|
|
16839
|
+
return errorResponse("Webhook not found", 404);
|
|
16840
|
+
return json(wh);
|
|
16841
|
+
});
|
|
16842
|
+
addRoute("PATCH", "/api/webhooks/:id", async (req, _url, params) => {
|
|
16843
|
+
const body = await readJson(req) ?? {};
|
|
16844
|
+
const updated = updateWebhookHook(params["id"], {
|
|
16845
|
+
enabled: body.enabled,
|
|
16846
|
+
priority: body.priority,
|
|
16847
|
+
description: body.description
|
|
16848
|
+
});
|
|
16849
|
+
if (!updated)
|
|
16850
|
+
return errorResponse("Webhook not found", 404);
|
|
16851
|
+
reloadWebhooks();
|
|
16852
|
+
return json(updated);
|
|
16853
|
+
});
|
|
16854
|
+
addRoute("DELETE", "/api/webhooks/:id", (_req, _url, params) => {
|
|
16855
|
+
const deleted = deleteWebhookHook(params["id"]);
|
|
16856
|
+
if (!deleted)
|
|
16857
|
+
return errorResponse("Webhook not found", 404);
|
|
16858
|
+
return new Response(null, { status: 204 });
|
|
16859
|
+
});
|
|
16860
|
+
}
|
|
16035
16861
|
|
|
16036
16862
|
// src/lib/synthesis/index.ts
|
|
16037
16863
|
init_database();
|
|
@@ -16935,9 +17761,61 @@ function getSynthesisStatus(runId, projectId, db) {
|
|
|
16935
17761
|
};
|
|
16936
17762
|
}
|
|
16937
17763
|
|
|
16938
|
-
// src/server/routes/system.ts
|
|
17764
|
+
// src/server/routes/system-synthesis.ts
|
|
16939
17765
|
init_synthesis();
|
|
16940
17766
|
init_profile_synthesizer();
|
|
17767
|
+
function registerSystemSynthesisRoutes() {
|
|
17768
|
+
addRoute("POST", "/api/synthesis/run", async (req) => {
|
|
17769
|
+
const body = await readJson(req) ?? {};
|
|
17770
|
+
try {
|
|
17771
|
+
const result = await runSynthesis({
|
|
17772
|
+
projectId: body.project_id,
|
|
17773
|
+
agentId: body.agent_id,
|
|
17774
|
+
dryRun: body.dry_run,
|
|
17775
|
+
maxProposals: body.max_proposals,
|
|
17776
|
+
provider: body.provider
|
|
17777
|
+
});
|
|
17778
|
+
return json(result, result.dryRun ? 200 : 201);
|
|
17779
|
+
} catch (e) {
|
|
17780
|
+
return json({ error: e instanceof Error ? e.message : String(e) }, 500);
|
|
17781
|
+
}
|
|
17782
|
+
});
|
|
17783
|
+
addRoute("GET", "/api/synthesis/runs", (_req, url) => {
|
|
17784
|
+
const projectId = url.searchParams.get("project_id") ?? undefined;
|
|
17785
|
+
const limit = url.searchParams.get("limit") ? parseInt(url.searchParams.get("limit")) : 20;
|
|
17786
|
+
const runs = listSynthesisRuns({ project_id: projectId, limit });
|
|
17787
|
+
return json({ runs, count: runs.length });
|
|
17788
|
+
});
|
|
17789
|
+
addRoute("GET", "/api/synthesis/status", (_req, url) => {
|
|
17790
|
+
const projectId = url.searchParams.get("project_id") ?? undefined;
|
|
17791
|
+
const runId = url.searchParams.get("run_id") ?? undefined;
|
|
17792
|
+
return json(getSynthesisStatus(runId, projectId));
|
|
17793
|
+
});
|
|
17794
|
+
addRoute("POST", "/api/synthesis/rollback/:run_id", async (_req, _url, params) => {
|
|
17795
|
+
try {
|
|
17796
|
+
const result = await rollbackSynthesis(params["run_id"]);
|
|
17797
|
+
return json(result);
|
|
17798
|
+
} catch (e) {
|
|
17799
|
+
return json({ error: e instanceof Error ? e.message : String(e) }, 500);
|
|
17800
|
+
}
|
|
17801
|
+
});
|
|
17802
|
+
addRoute("GET", "/api/profile/synthesize", async (_req, url) => {
|
|
17803
|
+
const q = getSearchParams(url);
|
|
17804
|
+
try {
|
|
17805
|
+
const result = await synthesizeProfile({
|
|
17806
|
+
project_id: q["project_id"] || undefined,
|
|
17807
|
+
agent_id: q["agent_id"] || undefined,
|
|
17808
|
+
force_refresh: q["force_refresh"] === "true"
|
|
17809
|
+
});
|
|
17810
|
+
if (!result) {
|
|
17811
|
+
return json({ profile: null, message: "No preference/fact memories found to synthesize" });
|
|
17812
|
+
}
|
|
17813
|
+
return json(result);
|
|
17814
|
+
} catch (e) {
|
|
17815
|
+
return json({ error: e instanceof Error ? e.message : String(e) }, 500);
|
|
17816
|
+
}
|
|
17817
|
+
});
|
|
17818
|
+
}
|
|
16941
17819
|
|
|
16942
17820
|
// src/lib/session-auto-resolve.ts
|
|
16943
17821
|
function autoResolveAgentProject(metadata, db) {
|
|
@@ -17006,255 +17884,136 @@ function autoResolveAgentProject(metadata, db) {
|
|
|
17006
17884
|
};
|
|
17007
17885
|
}
|
|
17008
17886
|
|
|
17009
|
-
// src/server/routes/system.ts
|
|
17010
|
-
|
|
17011
|
-
addRoute("POST", "/api/
|
|
17012
|
-
|
|
17013
|
-
|
|
17014
|
-
|
|
17015
|
-
|
|
17016
|
-
|
|
17017
|
-
|
|
17018
|
-
|
|
17019
|
-
|
|
17020
|
-
|
|
17021
|
-
|
|
17022
|
-
|
|
17023
|
-
|
|
17024
|
-
|
|
17887
|
+
// src/server/routes/system-sessions.ts
|
|
17888
|
+
function registerSystemSessionRoutes() {
|
|
17889
|
+
addRoute("POST", "/api/sessions/ingest", async (req) => {
|
|
17890
|
+
const body = await readJson(req) ?? {};
|
|
17891
|
+
const { transcript, session_id, agent_id, project_id, source, metadata } = body;
|
|
17892
|
+
if (!transcript || typeof transcript !== "string")
|
|
17893
|
+
return errorResponse("transcript is required", 400);
|
|
17894
|
+
if (!session_id || typeof session_id !== "string")
|
|
17895
|
+
return errorResponse("session_id is required", 400);
|
|
17896
|
+
let resolvedAgentId = agent_id;
|
|
17897
|
+
let resolvedProjectId = project_id;
|
|
17898
|
+
if (!resolvedAgentId || !resolvedProjectId) {
|
|
17899
|
+
const resolved = autoResolveAgentProject(metadata ?? {});
|
|
17900
|
+
if (!resolvedAgentId && resolved.agentId)
|
|
17901
|
+
resolvedAgentId = resolved.agentId;
|
|
17902
|
+
if (!resolvedProjectId && resolved.projectId)
|
|
17903
|
+
resolvedProjectId = resolved.projectId;
|
|
17904
|
+
}
|
|
17905
|
+
const job = createSessionJob({
|
|
17906
|
+
session_id,
|
|
17907
|
+
transcript,
|
|
17908
|
+
source: source ?? "manual",
|
|
17909
|
+
agent_id: resolvedAgentId,
|
|
17910
|
+
project_id: resolvedProjectId,
|
|
17911
|
+
metadata: metadata ?? {}
|
|
17912
|
+
});
|
|
17913
|
+
enqueueSessionJob(job.id);
|
|
17914
|
+
return json({ job_id: job.id, status: "queued", message: "Session queued for memory extraction" }, 202);
|
|
17025
17915
|
});
|
|
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"
|
|
17916
|
+
addRoute("GET", "/api/sessions/jobs", (_req, url) => {
|
|
17917
|
+
const agentId = url.searchParams.get("agent_id") ?? undefined;
|
|
17918
|
+
const projectId = url.searchParams.get("project_id") ?? undefined;
|
|
17919
|
+
const status = url.searchParams.get("status") ?? undefined;
|
|
17920
|
+
const limit = url.searchParams.get("limit") ? parseInt(url.searchParams.get("limit")) : 20;
|
|
17921
|
+
const jobs = listSessionJobs({ agent_id: agentId, project_id: projectId, status, limit });
|
|
17922
|
+
return json({ jobs, count: jobs.length });
|
|
17061
17923
|
});
|
|
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
|
|
17924
|
+
addRoute("GET", "/api/sessions/jobs/:id", (_req, _url, params) => {
|
|
17925
|
+
const job = getSessionJob(params["id"]);
|
|
17926
|
+
if (!job)
|
|
17927
|
+
return errorResponse("Session job not found", 404);
|
|
17928
|
+
return json(job);
|
|
17100
17929
|
});
|
|
17101
|
-
|
|
17102
|
-
|
|
17103
|
-
|
|
17104
|
-
|
|
17105
|
-
|
|
17106
|
-
|
|
17107
|
-
|
|
17108
|
-
|
|
17109
|
-
|
|
17110
|
-
|
|
17111
|
-
|
|
17112
|
-
|
|
17113
|
-
enabled: body.enabled,
|
|
17114
|
-
priority: body.priority,
|
|
17115
|
-
description: body.description
|
|
17930
|
+
addRoute("GET", "/api/sessions/queue/stats", () => json(getSessionQueueStats()));
|
|
17931
|
+
}
|
|
17932
|
+
|
|
17933
|
+
// src/server/routes/system-tools.ts
|
|
17934
|
+
function registerSystemToolRoutes() {
|
|
17935
|
+
addRoute("POST", "/api/tool-events", async (req) => {
|
|
17936
|
+
const body = await readJson(req);
|
|
17937
|
+
if (!body || !body["tool_name"]) {
|
|
17938
|
+
return errorResponse("Missing required field: tool_name", 400);
|
|
17939
|
+
}
|
|
17940
|
+
const event = saveToolEvent(body);
|
|
17941
|
+
return json(event, 201);
|
|
17116
17942
|
});
|
|
17117
|
-
|
|
17118
|
-
|
|
17119
|
-
|
|
17120
|
-
|
|
17121
|
-
|
|
17122
|
-
|
|
17123
|
-
|
|
17124
|
-
|
|
17125
|
-
|
|
17126
|
-
|
|
17127
|
-
|
|
17128
|
-
|
|
17129
|
-
|
|
17130
|
-
|
|
17131
|
-
|
|
17132
|
-
|
|
17133
|
-
|
|
17134
|
-
|
|
17135
|
-
|
|
17943
|
+
addRoute("GET", "/api/tool-events", (_req, url) => {
|
|
17944
|
+
const q = getSearchParams(url);
|
|
17945
|
+
const filters = {};
|
|
17946
|
+
if (q["tool_name"])
|
|
17947
|
+
filters.tool_name = q["tool_name"];
|
|
17948
|
+
if (q["agent_id"])
|
|
17949
|
+
filters.agent_id = q["agent_id"];
|
|
17950
|
+
if (q["project_id"])
|
|
17951
|
+
filters.project_id = q["project_id"];
|
|
17952
|
+
if (q["success"] !== undefined && q["success"] !== "")
|
|
17953
|
+
filters.success = q["success"] === "true";
|
|
17954
|
+
if (q["from_date"])
|
|
17955
|
+
filters.from_date = q["from_date"];
|
|
17956
|
+
if (q["to_date"])
|
|
17957
|
+
filters.to_date = q["to_date"];
|
|
17958
|
+
if (q["limit"])
|
|
17959
|
+
filters.limit = parseInt(q["limit"], 10);
|
|
17960
|
+
if (q["offset"])
|
|
17961
|
+
filters.offset = parseInt(q["offset"], 10);
|
|
17962
|
+
const events = getToolEvents(filters);
|
|
17963
|
+
return json({ events, count: events.length });
|
|
17136
17964
|
});
|
|
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 ?? {}
|
|
17965
|
+
addRoute("GET", "/api/tool-insights/:tool_name", (_req, url, params) => {
|
|
17966
|
+
const q = getSearchParams(url);
|
|
17967
|
+
const toolName = decodeURIComponent(params["tool_name"]);
|
|
17968
|
+
const projectId = q["project_id"];
|
|
17969
|
+
const lessonsLimit = q["limit"] ? parseInt(q["limit"], 10) : 20;
|
|
17970
|
+
const stats = getToolStats(toolName, projectId || undefined);
|
|
17971
|
+
const lessons = getToolLessons(toolName, projectId || undefined, lessonsLimit);
|
|
17972
|
+
return json({ stats, lessons });
|
|
17177
17973
|
});
|
|
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"
|
|
17974
|
+
}
|
|
17975
|
+
|
|
17976
|
+
// src/server/routes/system-chain.ts
|
|
17977
|
+
init_database();
|
|
17978
|
+
function registerSystemChainRoutes() {
|
|
17979
|
+
addRoute("GET", "/api/chains/:sequence_group", (_req, _url, params) => {
|
|
17980
|
+
const db = getDatabase();
|
|
17981
|
+
const sequenceGroup = decodeURIComponent(params["sequence_group"]);
|
|
17982
|
+
const rows = db.query(`SELECT * FROM memories WHERE sequence_group = ? AND status = 'active' ORDER BY sequence_order ASC`).all(sequenceGroup);
|
|
17983
|
+
if (rows.length === 0) {
|
|
17984
|
+
return json({ chain: [], count: 0, sequence_group: sequenceGroup });
|
|
17985
|
+
}
|
|
17986
|
+
return json({ chain: rows, count: rows.length, sequence_group: sequenceGroup });
|
|
17241
17987
|
});
|
|
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
|
-
});
|
|
17988
|
+
}
|
|
17989
|
+
|
|
17990
|
+
// src/server/routes/system.ts
|
|
17991
|
+
registerSystemAutoMemoryRoutes();
|
|
17992
|
+
registerSystemHookRoutes();
|
|
17993
|
+
registerSystemSynthesisRoutes();
|
|
17994
|
+
registerSystemSessionRoutes();
|
|
17995
|
+
registerSystemToolRoutes();
|
|
17996
|
+
registerSystemChainRoutes();
|
|
17256
17997
|
|
|
17257
17998
|
// src/server/index.ts
|
|
17999
|
+
async function findFreePort(start) {
|
|
18000
|
+
const net = await import("net");
|
|
18001
|
+
return new Promise((resolve4) => {
|
|
18002
|
+
const server = net.createServer();
|
|
18003
|
+
server.unref();
|
|
18004
|
+
server.on("error", () => {
|
|
18005
|
+
resolve4(findFreePort(start + 1));
|
|
18006
|
+
});
|
|
18007
|
+
server.listen(start, () => {
|
|
18008
|
+
const address = server.address();
|
|
18009
|
+
if (address && typeof address === "object") {
|
|
18010
|
+
server.close(() => resolve4(address.port));
|
|
18011
|
+
} else {
|
|
18012
|
+
resolve4(start);
|
|
18013
|
+
}
|
|
18014
|
+
});
|
|
18015
|
+
});
|
|
18016
|
+
}
|
|
17258
18017
|
var DEFAULT_PORT = 19428;
|
|
17259
18018
|
function hasFlag(...flags) {
|
|
17260
18019
|
return process.argv.some((arg) => flags.includes(arg));
|
|
@@ -17302,28 +18061,41 @@ function parsePort() {
|
|
|
17302
18061
|
}
|
|
17303
18062
|
return DEFAULT_PORT;
|
|
17304
18063
|
}
|
|
17305
|
-
|
|
17306
|
-
|
|
17307
|
-
|
|
17308
|
-
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
}
|
|
17312
|
-
}
|
|
17313
|
-
return start;
|
|
18064
|
+
var _serverInitialized = false;
|
|
18065
|
+
function warnIfPrimaryMachineUnset() {
|
|
18066
|
+
try {
|
|
18067
|
+
const warning = getPrimaryMachineStartupWarning(getDatabase());
|
|
18068
|
+
if (warning) {
|
|
18069
|
+
console.warn(`[mementos-serve] ${warning}`);
|
|
18070
|
+
}
|
|
18071
|
+
} catch {}
|
|
17314
18072
|
}
|
|
17315
|
-
function
|
|
18073
|
+
function initServer() {
|
|
18074
|
+
if (_serverInitialized)
|
|
18075
|
+
return;
|
|
18076
|
+
_serverInitialized = true;
|
|
18077
|
+
warnIfPrimaryMachineUnset();
|
|
17316
18078
|
loadWebhooksFromDb();
|
|
17317
18079
|
startSessionQueueWorker();
|
|
17318
|
-
|
|
18080
|
+
startTaskRunner();
|
|
18081
|
+
}
|
|
18082
|
+
function startServer(port) {
|
|
18083
|
+
initServer();
|
|
18084
|
+
const hostname2 = process.env["MEMENTOS_HOST"] ?? "127.0.0.1";
|
|
17319
18085
|
Bun.serve({
|
|
17320
18086
|
port,
|
|
17321
|
-
hostname,
|
|
18087
|
+
hostname: hostname2,
|
|
18088
|
+
maxRequestBodySize: 1048576,
|
|
17322
18089
|
async fetch(req) {
|
|
17323
18090
|
const url = new URL(req.url);
|
|
17324
18091
|
const { pathname } = url;
|
|
17325
18092
|
if (req.method === "OPTIONS") {
|
|
17326
|
-
|
|
18093
|
+
const origin = req.headers.get("origin");
|
|
18094
|
+
const allowedOrigin = process.env["MEMENTOS_CORS_ORIGIN"] ?? "http://localhost:19428";
|
|
18095
|
+
if (origin && origin !== allowedOrigin) {
|
|
18096
|
+
return new Response(null, { status: 403 });
|
|
18097
|
+
}
|
|
18098
|
+
return new Response(null, { status: 204, headers: getCorsHeaders(req) });
|
|
17327
18099
|
}
|
|
17328
18100
|
if (pathname === "/api/health" || pathname === "/health") {
|
|
17329
18101
|
const profile = getActiveProfile();
|
|
@@ -17337,7 +18109,12 @@ function startServer(port) {
|
|
|
17337
18109
|
const agents = db.query("SELECT COUNT(*) as c FROM agents").get().c;
|
|
17338
18110
|
const projects = db.query("SELECT COUNT(*) as c FROM projects").get().c;
|
|
17339
18111
|
const status = expired > 50 ? "warn" : "ok";
|
|
17340
|
-
return json({ status, version: pkg.version, profile: profile ?? "default", db_path: getDbPath(), hostname, memories: { total, expired, pinned }, agents, projects });
|
|
18112
|
+
return json({ status, version: pkg.version, profile: profile ?? "default", db_path: getDbPath(), hostname: hostname2, memories: { total, expired, pinned }, agents, projects });
|
|
18113
|
+
}
|
|
18114
|
+
if (pathname.startsWith("/api/") && pathname !== "/api/health") {
|
|
18115
|
+
const authError = authenticateRequest(req);
|
|
18116
|
+
if (authError)
|
|
18117
|
+
return authError;
|
|
17341
18118
|
}
|
|
17342
18119
|
if (pathname === "/api/profile" && req.method === "GET") {
|
|
17343
18120
|
const profile = getActiveProfile();
|
|
@@ -17405,12 +18182,11 @@ function startServer(port) {
|
|
|
17405
18182
|
return await matched.handler(req, url, matched.params);
|
|
17406
18183
|
} catch (e) {
|
|
17407
18184
|
console.error(`[mementos-serve] ${req.method} ${pathname}:`, e);
|
|
17408
|
-
|
|
17409
|
-
return errorResponse(message, 500);
|
|
18185
|
+
return errorResponse("Internal server error", 500);
|
|
17410
18186
|
}
|
|
17411
18187
|
}
|
|
17412
18188
|
});
|
|
17413
|
-
console.log(`Mementos server listening on http://${
|
|
18189
|
+
console.log(`Mementos server listening on http://${hostname2}:${port}`);
|
|
17414
18190
|
}
|
|
17415
18191
|
async function main() {
|
|
17416
18192
|
if (hasFlag("--help", "-h")) {
|