@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.
@@ -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, { create: true });
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 (const migration of MIGRATIONS) {
10332
+ for (let i = 0;i < MIGRATIONS.length; i++) {
10285
10333
  try {
10286
- db.exec(migration);
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
- input.value,
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
- return { handler: route.handler, params };
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
- return null;
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
- addRoute("GET", "/api/health", () => {
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("GET", "/api/memories/stats", (_req) => {
14904
- const db = getDatabase();
14905
- const total = db.query("SELECT COUNT(*) as c FROM memories WHERE status = 'active'").get().c;
14906
- const byScope = db.query("SELECT scope, COUNT(*) as c FROM memories WHERE status = 'active' GROUP BY scope").all();
14907
- const byCategory = db.query("SELECT category, COUNT(*) as c FROM memories WHERE status = 'active' GROUP BY category").all();
14908
- const byStatus = db.query("SELECT status, COUNT(*) as c FROM memories GROUP BY status").all();
14909
- const pinnedCount = db.query("SELECT COUNT(*) as c FROM memories WHERE pinned = 1 AND status = 'active'").get().c;
14910
- 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;
14911
- const stats = {
14912
- total,
14913
- by_scope: { global: 0, shared: 0, private: 0, working: 0 },
14914
- by_category: { preference: 0, fact: 0, knowledge: 0, history: 0, procedural: 0, resource: 0 },
14915
- by_status: { active: 0, archived: 0, expired: 0 },
14916
- by_agent: {},
14917
- pinned_count: pinnedCount,
14918
- expired_count: expiredCount
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/metrics", (_req) => {
14935
- const db = getDatabase();
14936
- const total = db.query("SELECT COUNT(*) as c FROM memories WHERE status = 'active'").get().c;
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) >= date('now', '-${days} days') ${where}
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
- `(accessed_at IS NULL OR accessed_at < datetime('now', '-${days} days'))`,
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 cond = [
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 params = [...projectId ? [projectId] : [], ...agentId ? [agentId] : []];
15028
- const total = db.query(`SELECT COUNT(*) as c FROM memories WHERE status = 'active' ${cond}`).get(...params).c;
15029
- const pinned = db.query(`SELECT COUNT(*) as c FROM memories WHERE status = 'active' AND pinned = 1 ${cond}`).get(...params).c;
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) >= date('now', '-${days} days') ${cond}
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(...params);
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' ${cond} GROUP BY scope`).all(...params);
15037
- const byCatRows = db.query(`SELECT category, COUNT(*) as c FROM memories WHERE status = 'active' ${cond} GROUP BY category`).all(...params);
15038
- const topMems = db.query(`SELECT id, key, value, importance, scope, category FROM memories WHERE status = 'active' ${cond} ORDER BY importance DESC, access_count DESC LIMIT 5`).all(...params);
15039
- const topAgents = db.query(`SELECT agent_id, COUNT(*) as c FROM memories WHERE status = 'active' AND agent_id IS NOT NULL ${cond} GROUP BY agent_id ORDER BY c DESC LIMIT 5`).all(...params);
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
- addRoute("POST", "/api/memories/bulk-forget", async (req) => {
15153
- const body = await readJson(req);
15154
- if (!body || !Array.isArray(body["ids"])) {
15155
- return errorResponse("Missing required field: ids (array)", 400);
15156
- }
15157
- const ids = body["ids"];
15158
- let deleted = 0;
15159
- for (const id of ids) {
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
- const ids = body["ids"];
15173
- const { ids: _ids, ...fields } = body;
15174
- let updated = 0;
15175
- const errors = [];
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
- return json({ updated, errors, total: ids.length });
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 errorResponse("Invalid JSON body", 400);
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/system.ts
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
- init_database();
17011
- addRoute("POST", "/api/auto-memory/process", async (req) => {
17012
- const body = await readJson(req);
17013
- const turn = body?.turn;
17014
- if (!turn)
17015
- return errorResponse("turn is required", 400);
17016
- processConversationTurn(turn, { agentId: body?.agent_id, projectId: body?.project_id, sessionId: body?.session_id });
17017
- const stats = getAutoMemoryStats();
17018
- return json({ queued: true, queue: stats }, 202);
17019
- });
17020
- addRoute("GET", "/api/auto-memory/status", () => {
17021
- return json({
17022
- queue: getAutoMemoryStats(),
17023
- config: providerRegistry.getConfig(),
17024
- providers: providerRegistry.health()
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
- addRoute("GET", "/api/auto-memory/config", () => {
17028
- return json(providerRegistry.getConfig());
17029
- });
17030
- addRoute("PATCH", "/api/auto-memory/config", async (req) => {
17031
- const body = await readJson(req) ?? {};
17032
- const patch = {};
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
- addRoute("GET", "/api/hooks", (_req, url) => {
17064
- const type = url.searchParams.get("type") ?? undefined;
17065
- const hooks = hookRegistry.list(type);
17066
- return json(hooks.map((h) => ({
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
- reloadWebhooks();
17102
- return json(wh, 201);
17103
- });
17104
- addRoute("GET", "/api/webhooks/:id", (_req, _url, params) => {
17105
- const wh = getWebhookHook(params["id"]);
17106
- if (!wh)
17107
- return errorResponse("Webhook not found", 404);
17108
- return json(wh);
17109
- });
17110
- addRoute("PATCH", "/api/webhooks/:id", async (req, _url, params) => {
17111
- const body = await readJson(req) ?? {};
17112
- const updated = updateWebhookHook(params["id"], {
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
- if (!updated)
17118
- return errorResponse("Webhook not found", 404);
17119
- reloadWebhooks();
17120
- return json(updated);
17121
- });
17122
- addRoute("DELETE", "/api/webhooks/:id", (_req, _url, params) => {
17123
- const deleted = deleteWebhookHook(params["id"]);
17124
- if (!deleted)
17125
- return errorResponse("Webhook not found", 404);
17126
- return new Response(null, { status: 204 });
17127
- });
17128
- addRoute("POST", "/api/synthesis/run", async (req) => {
17129
- const body = await readJson(req) ?? {};
17130
- const result = await runSynthesis({
17131
- projectId: body.project_id,
17132
- agentId: body.agent_id,
17133
- dryRun: body.dry_run,
17134
- maxProposals: body.max_proposals,
17135
- provider: body.provider
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
- return json(result, result.dryRun ? 200 : 201);
17138
- });
17139
- addRoute("GET", "/api/synthesis/runs", (_req, url) => {
17140
- const projectId = url.searchParams.get("project_id") ?? undefined;
17141
- const limit = url.searchParams.get("limit") ? parseInt(url.searchParams.get("limit")) : 20;
17142
- const runs = listSynthesisRuns({ project_id: projectId, limit });
17143
- return json({ runs, count: runs.length });
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
- enqueueSessionJob(job.id);
17179
- return json({ job_id: job.id, status: "queued", message: "Session queued for memory extraction" }, 202);
17180
- });
17181
- addRoute("GET", "/api/sessions/jobs", (_req, url) => {
17182
- const agentId = url.searchParams.get("agent_id") ?? undefined;
17183
- const projectId = url.searchParams.get("project_id") ?? undefined;
17184
- const status = url.searchParams.get("status") ?? undefined;
17185
- const limit = url.searchParams.get("limit") ? parseInt(url.searchParams.get("limit")) : 20;
17186
- const jobs = listSessionJobs({ agent_id: agentId, project_id: projectId, status, limit });
17187
- return json({ jobs, count: jobs.length });
17188
- });
17189
- addRoute("GET", "/api/sessions/jobs/:id", (_req, _url, params) => {
17190
- const job = getSessionJob(params["id"]);
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
- if (!result) {
17243
- return json({ profile: null, message: "No preference/fact memories found to synthesize" });
17244
- }
17245
- return json(result);
17246
- });
17247
- addRoute("GET", "/api/chains/:sequence_group", (_req, _url, params) => {
17248
- const db = getDatabase();
17249
- const sequenceGroup = decodeURIComponent(params["sequence_group"]);
17250
- const rows = db.query(`SELECT * FROM memories WHERE sequence_group = ? AND status = 'active' ORDER BY sequence_order ASC`).all(sequenceGroup);
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
- async function findFreePort(start) {
17306
- for (let port = start;port < start + 100; port++) {
17307
- try {
17308
- const server = Bun.serve({ port, fetch: () => new Response("") });
17309
- server.stop(true);
17310
- return port;
17311
- } catch {}
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 startServer(port) {
18073
+ function initServer() {
18074
+ if (_serverInitialized)
18075
+ return;
18076
+ _serverInitialized = true;
18077
+ warnIfPrimaryMachineUnset();
17316
18078
  loadWebhooksFromDb();
17317
18079
  startSessionQueueWorker();
17318
- const hostname = process.env["MEMENTOS_HOST"] ?? "127.0.0.1";
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
- return new Response(null, { status: 204, headers: CORS_HEADERS });
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
- const message = e instanceof Error ? e.message : "Internal server error";
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://${hostname}:${port}`);
18189
+ console.log(`Mementos server listening on http://${hostname2}:${port}`);
17414
18190
  }
17415
18191
  async function main() {
17416
18192
  if (hasFlag("--help", "-h")) {