@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/index.js CHANGED
@@ -9765,6 +9765,7 @@ CREATE TABLE IF NOT EXISTS machines (
9765
9765
  name TEXT NOT NULL UNIQUE,
9766
9766
  hostname TEXT NOT NULL,
9767
9767
  platform TEXT NOT NULL DEFAULT 'unknown',
9768
+ is_primary INTEGER NOT NULL DEFAULT 0,
9768
9769
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
9769
9770
  last_seen_at TEXT NOT NULL DEFAULT (datetime('now'))
9770
9771
  );
@@ -10142,6 +10143,46 @@ ALTER TABLE memories ADD COLUMN sequence_group TEXT DEFAULT NULL;
10142
10143
  ALTER TABLE memories ADD COLUMN sequence_order INTEGER DEFAULT NULL;
10143
10144
  CREATE INDEX IF NOT EXISTS idx_memories_sequence_group ON memories(sequence_group) WHERE sequence_group IS NOT NULL;
10144
10145
  INSERT OR IGNORE INTO _migrations (id) VALUES (32);
10146
+ `,
10147
+ `
10148
+ CREATE TABLE IF NOT EXISTS tasks (
10149
+ id TEXT PRIMARY KEY,
10150
+ subject TEXT NOT NULL,
10151
+ description TEXT DEFAULT '',
10152
+ status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'in_progress', 'completed', 'failed', 'cancelled')),
10153
+ priority TEXT NOT NULL DEFAULT 'medium' CHECK(priority IN ('critical', 'high', 'medium', 'low')),
10154
+ tags TEXT NOT NULL DEFAULT '[]',
10155
+ assigned_agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
10156
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
10157
+ session_id TEXT,
10158
+ parent_task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
10159
+ metadata TEXT NOT NULL DEFAULT '{}',
10160
+ progress REAL NOT NULL DEFAULT 0 CHECK(progress >= 0 AND progress <= 1),
10161
+ due_at TEXT,
10162
+ started_at TEXT,
10163
+ completed_at TEXT,
10164
+ failed_at TEXT,
10165
+ error TEXT,
10166
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
10167
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
10168
+ );
10169
+ CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
10170
+ CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority);
10171
+ CREATE INDEX IF NOT EXISTS idx_tasks_agent ON tasks(assigned_agent_id);
10172
+ CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks(project_id);
10173
+ CREATE INDEX IF NOT EXISTS idx_tasks_session ON tasks(session_id);
10174
+ CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_task_id);
10175
+
10176
+ CREATE TABLE IF NOT EXISTS task_comments (
10177
+ id TEXT PRIMARY KEY,
10178
+ task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
10179
+ agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
10180
+ body TEXT NOT NULL,
10181
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
10182
+ );
10183
+ CREATE INDEX IF NOT EXISTS idx_task_comments_task ON task_comments(task_id);
10184
+ CREATE INDEX IF NOT EXISTS idx_task_comments_agent ON task_comments(agent_id);
10185
+ INSERT OR IGNORE INTO _migrations (id) VALUES (34);
10145
10186
  `
10146
10187
  ];
10147
10188
  });
@@ -10225,11 +10266,16 @@ function ensureDir(filePath) {
10225
10266
  }
10226
10267
  }
10227
10268
  function getDatabase(dbPath) {
10228
- if (_db)
10229
- return _db;
10230
10269
  const path = dbPath || getDbPath2();
10270
+ if (_db) {
10271
+ if (_dbPath === path)
10272
+ return _db;
10273
+ _db.close();
10274
+ _db = null;
10275
+ }
10276
+ _dbPath = path;
10231
10277
  ensureDir(path);
10232
- _db = new SqliteAdapter(path, { create: true });
10278
+ _db = new SqliteAdapter(path);
10233
10279
  _db.run("PRAGMA journal_mode = WAL");
10234
10280
  _db.run("PRAGMA busy_timeout = 5000");
10235
10281
  _db.run("PRAGMA foreign_keys = ON");
@@ -10253,13 +10299,17 @@ function runMigrations(db) {
10253
10299
  for (let i = currentLevel;i < MIGRATIONS.length; i++) {
10254
10300
  try {
10255
10301
  db.exec(MIGRATIONS[i]);
10256
- } catch {}
10302
+ } catch (e) {
10303
+ console.warn(`[mementos] Migration ${i + 1} failed: ${e instanceof Error ? e.message : String(e)}`);
10304
+ }
10257
10305
  }
10258
10306
  } catch {
10259
- for (const migration of MIGRATIONS) {
10307
+ for (let i = 0;i < MIGRATIONS.length; i++) {
10260
10308
  try {
10261
- db.exec(migration);
10262
- } catch {}
10309
+ db.exec(MIGRATIONS[i]);
10310
+ } catch (e) {
10311
+ console.warn(`[mementos] Migration ${i + 1} failed: ${e instanceof Error ? e.message : String(e)}`);
10312
+ }
10263
10313
  }
10264
10314
  }
10265
10315
  }
@@ -10282,6 +10332,9 @@ function shortUuid() {
10282
10332
  return crypto.randomUUID().slice(0, 8);
10283
10333
  }
10284
10334
  function resolvePartialId(db, table, partialId) {
10335
+ if (!ALLOWED_TABLES.has(table)) {
10336
+ throw new Error(`Invalid table name: ${table}`);
10337
+ }
10285
10338
  if (partialId.length >= 36) {
10286
10339
  const row = db.query(`SELECT id FROM ${table} WHERE id = ?`).get(partialId);
10287
10340
  return row?.id ?? null;
@@ -10292,10 +10345,25 @@ function resolvePartialId(db, table, partialId) {
10292
10345
  }
10293
10346
  return null;
10294
10347
  }
10295
- var _db = null;
10348
+ var _db = null, _dbPath = null, ALLOWED_TABLES;
10296
10349
  var init_database = __esm(() => {
10297
10350
  init_dist();
10298
10351
  init_migrations();
10352
+ ALLOWED_TABLES = new Set([
10353
+ "memories",
10354
+ "agents",
10355
+ "entities",
10356
+ "projects",
10357
+ "relations",
10358
+ "memory_audit_log",
10359
+ "locks",
10360
+ "sessions",
10361
+ "session_memory_jobs",
10362
+ "synthesis_runs",
10363
+ "synthesis_proposals",
10364
+ "tool_events",
10365
+ "webhook_hooks"
10366
+ ]);
10299
10367
  });
10300
10368
 
10301
10369
  // src/types/index.ts
@@ -10836,7 +10904,7 @@ function createMemory(input, dedupeMode = "merge", db) {
10836
10904
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 1, ?, ?, ?, ?, ?, ?)`, [
10837
10905
  id,
10838
10906
  input.key,
10839
- input.value,
10907
+ safeValue,
10840
10908
  input.category || "knowledge",
10841
10909
  input.scope || "private",
10842
10910
  input.summary || null,
@@ -10999,6 +11067,22 @@ function listMemories(filter, db) {
10999
11067
  conditions.push("session_id = ?");
11000
11068
  params.push(filter.session_id);
11001
11069
  }
11070
+ if ("machine_id" in filter) {
11071
+ if (filter.machine_id === null) {
11072
+ conditions.push("machine_id IS NULL");
11073
+ } else if (filter.machine_id) {
11074
+ conditions.push("machine_id = ?");
11075
+ params.push(filter.machine_id);
11076
+ }
11077
+ }
11078
+ if ("visible_to_machine_id" in filter) {
11079
+ if (filter.visible_to_machine_id === null) {
11080
+ conditions.push("machine_id IS NULL");
11081
+ } else if (filter.visible_to_machine_id !== undefined) {
11082
+ conditions.push("(machine_id IS NULL OR machine_id = ?)");
11083
+ params.push(filter.visible_to_machine_id);
11084
+ }
11085
+ }
11002
11086
  if (filter.min_importance) {
11003
11087
  conditions.push("importance >= ?");
11004
11088
  params.push(filter.min_importance);
@@ -11605,6 +11689,106 @@ function listProjects(db) {
11605
11689
  const rows = d.query("SELECT * FROM projects ORDER BY updated_at DESC").all();
11606
11690
  return rows.map(parseProjectRow);
11607
11691
  }
11692
+ // src/db/machines.ts
11693
+ init_database();
11694
+ import { hostname, platform as platform2 } from "os";
11695
+ function parseMachine(row) {
11696
+ if (!row)
11697
+ return null;
11698
+ return {
11699
+ ...row,
11700
+ is_primary: Boolean(row.is_primary)
11701
+ };
11702
+ }
11703
+ function normalizeHostname(host) {
11704
+ return host.replace(/\.(local|lan|home|internal)$/i, "");
11705
+ }
11706
+ function registerMachine(name, db = getDatabase()) {
11707
+ const rawHost = hostname();
11708
+ const host = normalizeHostname(rawHost);
11709
+ const plat = platform2();
11710
+ const machineName = name?.trim() || host;
11711
+ const existing = parseMachine(db.query("SELECT * FROM machines WHERE hostname = ?").get(host));
11712
+ if (existing) {
11713
+ db.run("UPDATE machines SET last_seen_at = ? WHERE id = ?", [now(), existing.id]);
11714
+ return parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(existing.id));
11715
+ }
11716
+ let finalName = machineName;
11717
+ let suffix = 2;
11718
+ while (db.query("SELECT id FROM machines WHERE name = ?").get(finalName)) {
11719
+ finalName = `${machineName}-${suffix++}`;
11720
+ }
11721
+ const id = uuid();
11722
+ db.run("INSERT INTO machines (id, name, hostname, platform) VALUES (?, ?, ?, ?)", [id, finalName, host, plat]);
11723
+ return parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(id));
11724
+ }
11725
+ function listMachines(db = getDatabase()) {
11726
+ const rows = db.query("SELECT * FROM machines ORDER BY is_primary DESC, last_seen_at DESC, created_at ASC").all();
11727
+ return rows.map((row) => parseMachine(row));
11728
+ }
11729
+ function getMachine(id, db = getDatabase()) {
11730
+ return parseMachine(db.query("SELECT * FROM machines WHERE id = ? OR name = ?").get(id, id));
11731
+ }
11732
+ function renameMachine(id, newName, db = getDatabase()) {
11733
+ const m = parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(id));
11734
+ if (!m)
11735
+ throw new Error(`Machine not found: ${id}`);
11736
+ const clash = db.query("SELECT id FROM machines WHERE name = ? AND id != ?").get(newName, id);
11737
+ if (clash)
11738
+ throw new Error(`Machine name already taken: ${newName}`);
11739
+ db.run("UPDATE machines SET name = ?, last_seen_at = ? WHERE id = ?", [newName, now(), id]);
11740
+ return parseMachine(db.query("SELECT * FROM machines WHERE id = ?").get(id));
11741
+ }
11742
+ function getPrimaryMachine(db = getDatabase()) {
11743
+ return parseMachine(db.query("SELECT * FROM machines WHERE is_primary = 1 LIMIT 1").get());
11744
+ }
11745
+ function getPrimaryMachineCandidate(db = getDatabase()) {
11746
+ if (getPrimaryMachine(db))
11747
+ return null;
11748
+ return parseMachine(db.query("SELECT * FROM machines ORDER BY created_at ASC, id ASC LIMIT 1").get());
11749
+ }
11750
+ function setPrimaryMachine(id, db = getDatabase()) {
11751
+ const machine = getMachine(id, db);
11752
+ if (!machine)
11753
+ throw new Error(`Machine not found: ${id}`);
11754
+ const updatedAt = now();
11755
+ db.run("UPDATE machines SET is_primary = 0, last_seen_at = ? WHERE is_primary = 1 AND id != ?", [updatedAt, machine.id]);
11756
+ db.run("UPDATE machines SET is_primary = 1, last_seen_at = ? WHERE id = ?", [updatedAt, machine.id]);
11757
+ return getMachine(machine.id, db);
11758
+ }
11759
+ function deleteMachine(id, db = getDatabase()) {
11760
+ const machine = getMachine(id, db);
11761
+ if (!machine)
11762
+ throw new Error(`Machine not found: ${id}`);
11763
+ if (machine.is_primary) {
11764
+ throw new Error(`Primary machine cannot be deleted: ${machine.name}`);
11765
+ }
11766
+ db.run("DELETE FROM machines WHERE id = ?", [machine.id]);
11767
+ }
11768
+ function getFallbackSyncTargetMachine(db = getDatabase()) {
11769
+ return getPrimaryMachine(db);
11770
+ }
11771
+ function getPrimaryMachineStartupWarning(db = getDatabase()) {
11772
+ if (getPrimaryMachine(db))
11773
+ return null;
11774
+ const candidate = getPrimaryMachineCandidate(db);
11775
+ if (!candidate) {
11776
+ return "No primary machine configured. Fallback sync target is unset because no machines are registered yet.";
11777
+ }
11778
+ 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.`;
11779
+ }
11780
+ function touchMachine(id, db = getDatabase()) {
11781
+ db.run("UPDATE machines SET last_seen_at = ? WHERE id = ?", [now(), id]);
11782
+ }
11783
+ function getCurrentMachineId(db = getDatabase()) {
11784
+ const host = normalizeHostname(hostname());
11785
+ const m = db.query("SELECT id FROM machines WHERE hostname = ?").get(host);
11786
+ if (m) {
11787
+ touchMachine(m.id, db);
11788
+ return m.id;
11789
+ }
11790
+ return registerMachine(undefined, db).id;
11791
+ }
11608
11792
  // src/lib/search.ts
11609
11793
  init_database();
11610
11794
 
@@ -12138,6 +12322,10 @@ function buildFilterConditions(filter) {
12138
12322
  params.push(tag);
12139
12323
  }
12140
12324
  }
12325
+ if (filter.namespace) {
12326
+ conditions.push("m.namespace = ?");
12327
+ params.push(filter.namespace);
12328
+ }
12141
12329
  return { conditions, params };
12142
12330
  }
12143
12331
  function searchWithFts5(d, query, queryLower, filter, graphBoostedIds) {
@@ -12470,6 +12658,23 @@ function ensureDir2(dir) {
12470
12658
  mkdirSync5(dir, { recursive: true });
12471
12659
  }
12472
12660
  }
12661
+ // src/lib/machine-visibility.ts
12662
+ function resolveVisibleMachineId(machineId, db) {
12663
+ if (machineId !== undefined) {
12664
+ return machineId;
12665
+ }
12666
+ try {
12667
+ return getCurrentMachineId(db);
12668
+ } catch {
12669
+ return null;
12670
+ }
12671
+ }
12672
+ function visibleToMachineFilter(machineId, db) {
12673
+ return {
12674
+ visible_to_machine_id: resolveVisibleMachineId(machineId, db)
12675
+ };
12676
+ }
12677
+
12473
12678
  // src/db/tool-events.ts
12474
12679
  init_database();
12475
12680
 
@@ -12485,12 +12690,14 @@ class MemoryInjector {
12485
12690
  const minImportance = options.min_importance || this.config.injection.min_importance;
12486
12691
  const categories = options.categories || this.config.injection.categories;
12487
12692
  const db = options.db;
12693
+ const visibleMachineId = resolveVisibleMachineId(options.machine_id, db);
12488
12694
  const allMemories = [];
12489
12695
  const globalMems = listMemories({
12490
12696
  scope: "global",
12491
12697
  category: categories,
12492
12698
  min_importance: minImportance,
12493
12699
  status: "active",
12700
+ ...visibleToMachineFilter(visibleMachineId),
12494
12701
  limit: 100
12495
12702
  }, db);
12496
12703
  allMemories.push(...globalMems);
@@ -12501,6 +12708,7 @@ class MemoryInjector {
12501
12708
  min_importance: minImportance,
12502
12709
  status: "active",
12503
12710
  project_id: options.project_id,
12711
+ ...visibleToMachineFilter(visibleMachineId),
12504
12712
  limit: 100
12505
12713
  }, db);
12506
12714
  allMemories.push(...sharedMems);
@@ -12512,6 +12720,7 @@ class MemoryInjector {
12512
12720
  min_importance: minImportance,
12513
12721
  status: "active",
12514
12722
  agent_id: options.agent_id,
12723
+ ...visibleToMachineFilter(visibleMachineId),
12515
12724
  limit: 100
12516
12725
  }, db);
12517
12726
  allMemories.push(...privateMems);
@@ -12523,6 +12732,7 @@ class MemoryInjector {
12523
12732
  ...options.session_id ? { session_id: options.session_id } : {},
12524
12733
  ...options.agent_id ? { agent_id: options.agent_id } : {},
12525
12734
  ...options.project_id ? { project_id: options.project_id } : {},
12735
+ ...visibleToMachineFilter(visibleMachineId),
12526
12736
  limit: 100
12527
12737
  }, db);
12528
12738
  allMemories.push(...workingMems);
@@ -12630,12 +12840,14 @@ ${sections.join(`
12630
12840
  const minImportance = options.min_importance || this.config.injection.min_importance;
12631
12841
  const categories = options.categories || this.config.injection.categories;
12632
12842
  const db = options.db;
12843
+ const visibleMachineId = resolveVisibleMachineId(options.machine_id, db);
12633
12844
  const allMemories = [];
12634
12845
  const globalMems = listMemories({
12635
12846
  scope: "global",
12636
12847
  category: categories,
12637
12848
  min_importance: minImportance,
12638
12849
  status: "active",
12850
+ ...visibleToMachineFilter(visibleMachineId),
12639
12851
  limit: 100
12640
12852
  }, db);
12641
12853
  allMemories.push(...globalMems);
@@ -12646,6 +12858,7 @@ ${sections.join(`
12646
12858
  min_importance: minImportance,
12647
12859
  status: "active",
12648
12860
  project_id: options.project_id,
12861
+ ...visibleToMachineFilter(visibleMachineId),
12649
12862
  limit: 100
12650
12863
  }, db);
12651
12864
  allMemories.push(...sharedMems);
@@ -12657,6 +12870,7 @@ ${sections.join(`
12657
12870
  min_importance: minImportance,
12658
12871
  status: "active",
12659
12872
  agent_id: options.agent_id,
12873
+ ...visibleToMachineFilter(visibleMachineId),
12660
12874
  limit: 100
12661
12875
  }, db);
12662
12876
  allMemories.push(...privateMems);
@@ -12668,6 +12882,7 @@ ${sections.join(`
12668
12882
  ...options.session_id ? { session_id: options.session_id } : {},
12669
12883
  ...options.agent_id ? { agent_id: options.agent_id } : {},
12670
12884
  ...options.project_id ? { project_id: options.project_id } : {},
12885
+ ...visibleToMachineFilter(visibleMachineId),
12671
12886
  limit: 100
12672
12887
  }, db);
12673
12888
  allMemories.push(...workingMems);
@@ -13966,19 +14181,434 @@ function clearActiveModel() {
13966
14181
  delete config.activeModel;
13967
14182
  writeConfig(config);
13968
14183
  }
14184
+ // src/db/tasks.ts
14185
+ init_database();
14186
+ function parseTask(row) {
14187
+ return {
14188
+ id: row.id,
14189
+ subject: row.subject,
14190
+ description: row.description ?? "",
14191
+ status: row.status,
14192
+ priority: row.priority,
14193
+ tags: JSON.parse(row.tags ?? "[]"),
14194
+ assigned_agent_id: row.assigned_agent_id ?? null,
14195
+ project_id: row.project_id ?? null,
14196
+ session_id: row.session_id ?? null,
14197
+ parent_task_id: row.parent_task_id ?? null,
14198
+ metadata: JSON.parse(row.metadata ?? "{}"),
14199
+ progress: row.progress,
14200
+ due_at: row.due_at ?? null,
14201
+ started_at: row.started_at ?? null,
14202
+ completed_at: row.completed_at ?? null,
14203
+ failed_at: row.failed_at ?? null,
14204
+ error: row.error ?? null,
14205
+ created_at: row.created_at,
14206
+ updated_at: row.updated_at
14207
+ };
14208
+ }
14209
+ function createTask(db, input) {
14210
+ const id = uuid();
14211
+ const ts = now();
14212
+ 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)
14213
+ VALUES (?, ?, ?, 'pending', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
14214
+ id,
14215
+ input.subject,
14216
+ input.description ?? "",
14217
+ input.priority ?? "medium",
14218
+ JSON.stringify(input.tags ?? []),
14219
+ input.assigned_agent_id ?? null,
14220
+ input.project_id ?? null,
14221
+ input.session_id ?? null,
14222
+ input.parent_task_id ?? null,
14223
+ JSON.stringify(input.metadata ?? {}),
14224
+ input.due_at ?? null,
14225
+ ts,
14226
+ ts
14227
+ ]);
14228
+ const row = db.query("SELECT * FROM tasks WHERE id = ?").get(id);
14229
+ return parseTask(row);
14230
+ }
14231
+ function getTask(db, id) {
14232
+ const row = db.query("SELECT * FROM tasks WHERE id = ?").get(id);
14233
+ return row ? parseTask(row) : null;
14234
+ }
14235
+ function listTasks(db, filter) {
14236
+ let sql = "SELECT * FROM tasks WHERE 1=1";
14237
+ let countSql = "SELECT COUNT(*) as c FROM tasks WHERE 1=1";
14238
+ const params = [];
14239
+ const countParams = [];
14240
+ if (filter?.status) {
14241
+ sql += " AND status = ?";
14242
+ countSql += " AND status = ?";
14243
+ params.push(filter.status);
14244
+ countParams.push(filter.status);
14245
+ }
14246
+ if (filter?.priority) {
14247
+ sql += " AND priority = ?";
14248
+ countSql += " AND priority = ?";
14249
+ params.push(filter.priority);
14250
+ countParams.push(filter.priority);
14251
+ }
14252
+ if (filter?.assigned_agent_id) {
14253
+ sql += " AND assigned_agent_id = ?";
14254
+ countSql += " AND assigned_agent_id = ?";
14255
+ params.push(filter.assigned_agent_id);
14256
+ countParams.push(filter.assigned_agent_id);
14257
+ }
14258
+ if (filter?.project_id) {
14259
+ sql += " AND project_id = ?";
14260
+ countSql += " AND project_id = ?";
14261
+ params.push(filter.project_id);
14262
+ countParams.push(filter.project_id);
14263
+ }
14264
+ if (filter?.session_id) {
14265
+ sql += " AND session_id = ?";
14266
+ countSql += " AND session_id = ?";
14267
+ params.push(filter.session_id);
14268
+ countParams.push(filter.session_id);
14269
+ }
14270
+ if (filter?.parent_task_id !== undefined) {
14271
+ if (filter.parent_task_id === null) {
14272
+ sql += " AND parent_task_id IS NULL";
14273
+ countSql += " AND parent_task_id IS NULL";
14274
+ } else {
14275
+ sql += " AND parent_task_id = ?";
14276
+ countSql += " AND parent_task_id = ?";
14277
+ params.push(filter.parent_task_id);
14278
+ countParams.push(filter.parent_task_id);
14279
+ }
14280
+ }
14281
+ if (filter?.tags?.length) {
14282
+ const placeholders = filter.tags.map(() => "?").join(", ");
14283
+ sql += ` AND EXISTS (SELECT 1 FROM json_each(tags) WHERE value IN (${placeholders}))`;
14284
+ countSql += ` AND EXISTS (SELECT 1 FROM json_each(tags) WHERE value IN (${placeholders}))`;
14285
+ params.push(...filter.tags);
14286
+ countParams.push(...filter.tags);
14287
+ }
14288
+ 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";
14289
+ if (filter?.limit !== undefined) {
14290
+ sql += " LIMIT ?";
14291
+ params.push(filter.limit);
14292
+ }
14293
+ if (filter?.offset !== undefined) {
14294
+ sql += " OFFSET ?";
14295
+ params.push(filter.offset);
14296
+ }
14297
+ const rows = db.query(sql).all(...params);
14298
+ const countRow = db.query(countSql).get(...countParams);
14299
+ return { tasks: rows.map(parseTask), count: countRow.c };
14300
+ }
14301
+ function updateTask(db, id, input) {
14302
+ const existing = getTask(db, id);
14303
+ if (!existing)
14304
+ return null;
14305
+ const updates = [];
14306
+ const params = [];
14307
+ const ts = now();
14308
+ if (input.subject !== undefined) {
14309
+ updates.push("subject = ?");
14310
+ params.push(input.subject);
14311
+ }
14312
+ if (input.description !== undefined) {
14313
+ updates.push("description = ?");
14314
+ params.push(input.description);
14315
+ }
14316
+ if (input.status !== undefined) {
14317
+ updates.push("status = ?");
14318
+ params.push(input.status);
14319
+ if (input.status === "in_progress" && !existing.started_at) {
14320
+ updates.push("started_at = ?");
14321
+ params.push(ts);
14322
+ }
14323
+ if (input.status === "completed") {
14324
+ updates.push("completed_at = ?");
14325
+ params.push(ts);
14326
+ updates.push("progress = ?");
14327
+ params.push(1);
14328
+ }
14329
+ if (input.status === "failed") {
14330
+ updates.push("failed_at = ?");
14331
+ params.push(ts);
14332
+ }
14333
+ }
14334
+ if (input.priority !== undefined) {
14335
+ updates.push("priority = ?");
14336
+ params.push(input.priority);
14337
+ }
14338
+ if (input.tags !== undefined) {
14339
+ updates.push("tags = ?");
14340
+ params.push(JSON.stringify(input.tags));
14341
+ }
14342
+ if (input.assigned_agent_id !== undefined) {
14343
+ updates.push("assigned_agent_id = ?");
14344
+ params.push(input.assigned_agent_id);
14345
+ }
14346
+ if (input.metadata !== undefined) {
14347
+ updates.push("metadata = ?");
14348
+ params.push(JSON.stringify(input.metadata));
14349
+ }
14350
+ if (input.progress !== undefined) {
14351
+ updates.push("progress = ?");
14352
+ params.push(input.progress);
14353
+ }
14354
+ if (input.due_at !== undefined) {
14355
+ updates.push("due_at = ?");
14356
+ params.push(input.due_at);
14357
+ }
14358
+ if (input.error !== undefined) {
14359
+ updates.push("error = ?");
14360
+ params.push(input.error);
14361
+ }
14362
+ if (updates.length === 0)
14363
+ return existing;
14364
+ updates.push("updated_at = ?");
14365
+ params.push(ts);
14366
+ params.push(id);
14367
+ db.run(`UPDATE tasks SET ${updates.join(", ")} WHERE id = ?`, ...params);
14368
+ return getTask(db, id);
14369
+ }
14370
+ function deleteTask(db, id) {
14371
+ const result = db.run("DELETE FROM tasks WHERE id = ?", id);
14372
+ return result.changes > 0;
14373
+ }
14374
+ function addTaskComment(db, taskId, body, agentId) {
14375
+ const id = uuid();
14376
+ db.run("INSERT INTO task_comments (id, task_id, agent_id, body) VALUES (?, ?, ?, ?)", [id, taskId, agentId ?? null, body]);
14377
+ const row = db.query("SELECT * FROM task_comments WHERE id = ?").get(id);
14378
+ return parseTaskComment(row);
14379
+ }
14380
+ function parseTaskComment(row) {
14381
+ return {
14382
+ id: row.id,
14383
+ task_id: row.task_id,
14384
+ agent_id: row.agent_id ?? null,
14385
+ body: row.body,
14386
+ created_at: row.created_at
14387
+ };
14388
+ }
14389
+ function listTaskComments(db, taskId) {
14390
+ const rows = db.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at ASC").all(taskId);
14391
+ return { comments: rows.map(parseTaskComment), count: rows.length };
14392
+ }
14393
+ function deleteTaskComment(db, id) {
14394
+ const result = db.run("DELETE FROM task_comments WHERE id = ?", id);
14395
+ return result.changes > 0;
14396
+ }
14397
+ function getTaskStats(db, filter) {
14398
+ let where = "WHERE 1=1";
14399
+ const params = [];
14400
+ if (filter?.project_id) {
14401
+ where += " AND project_id = ?";
14402
+ params.push(filter.project_id);
14403
+ }
14404
+ if (filter?.agent_id) {
14405
+ where += " AND assigned_agent_id = ?";
14406
+ params.push(filter.agent_id);
14407
+ }
14408
+ const total = db.query(`SELECT COUNT(*) as c FROM tasks ${where}`).get(...params).c;
14409
+ const byStatus = {};
14410
+ const statusRows = db.query(`SELECT status, COUNT(*) as c FROM tasks ${where} GROUP BY status`).all(...params);
14411
+ for (const row of statusRows)
14412
+ byStatus[row.status] = row.c;
14413
+ const byPriority = {};
14414
+ const priorityRows = db.query(`SELECT priority, COUNT(*) as c FROM tasks ${where} GROUP BY priority`).all(...params);
14415
+ for (const row of priorityRows)
14416
+ byPriority[row.priority] = row.c;
14417
+ 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;
14418
+ return {
14419
+ total,
14420
+ by_status: {
14421
+ pending: byStatus["pending"] ?? 0,
14422
+ in_progress: byStatus["in_progress"] ?? 0,
14423
+ completed: byStatus["completed"] ?? 0,
14424
+ failed: byStatus["failed"] ?? 0,
14425
+ cancelled: byStatus["cancelled"] ?? 0
14426
+ },
14427
+ by_priority: {
14428
+ critical: byPriority["critical"] ?? 0,
14429
+ high: byPriority["high"] ?? 0,
14430
+ medium: byPriority["medium"] ?? 0,
14431
+ low: byPriority["low"] ?? 0
14432
+ },
14433
+ overdue
14434
+ };
14435
+ }
14436
+ // src/lib/task-runner.ts
14437
+ init_database();
14438
+ var _handlers = new Map;
14439
+ var _defaultHandler = null;
14440
+ function registerTaskHandler(type, handler) {
14441
+ _handlers.set(type, handler);
14442
+ }
14443
+ function setDefaultTaskHandler(handler) {
14444
+ _defaultHandler = handler;
14445
+ }
14446
+ function resolveHandler(task) {
14447
+ for (const tag of task.tags) {
14448
+ const h = _handlers.get(tag);
14449
+ if (h)
14450
+ return h;
14451
+ }
14452
+ const metaType = task.metadata?.["type"];
14453
+ if (metaType) {
14454
+ const h = _handlers.get(metaType);
14455
+ if (h)
14456
+ return h;
14457
+ }
14458
+ return _defaultHandler;
14459
+ }
14460
+ var _workerStarted = false;
14461
+ var _processing = false;
14462
+ var _totalProcessed = 0;
14463
+ var _pollIntervalMs = 60000;
14464
+ function startTaskRunner(intervalMs) {
14465
+ if (_workerStarted)
14466
+ return;
14467
+ _workerStarted = true;
14468
+ if (intervalMs)
14469
+ _pollIntervalMs = intervalMs;
14470
+ console.log(`[task-runner] Started, polling every ${_pollIntervalMs / 1000}s`);
14471
+ _tick();
14472
+ setInterval(() => {
14473
+ _tick();
14474
+ }, _pollIntervalMs);
14475
+ }
14476
+ async function _tick() {
14477
+ if (_processing)
14478
+ return;
14479
+ let task = null;
14480
+ try {
14481
+ const db = getDatabase();
14482
+ const result = listTasks(db, { status: "pending", limit: 1 });
14483
+ if (result.tasks.length === 0)
14484
+ return;
14485
+ task = result.tasks[0] ?? null;
14486
+ } catch (e) {
14487
+ console.error("[task-runner] Failed to list pending tasks:", e);
14488
+ return;
14489
+ }
14490
+ if (!task)
14491
+ return;
14492
+ _processing = true;
14493
+ try {
14494
+ const db = getDatabase();
14495
+ updateTask(db, task.id, { status: "in_progress" });
14496
+ task = getTask(db, task.id);
14497
+ } catch (e) {
14498
+ console.error("[task-runner] Failed to claim task:", e);
14499
+ _processing = false;
14500
+ return;
14501
+ }
14502
+ const handler = resolveHandler(task);
14503
+ if (!handler) {
14504
+ console.warn(`[task-runner] No handler for task: ${task.subject} (tags: ${JSON.stringify(task.tags)}, meta.type: ${task.metadata?.["type"]})`);
14505
+ try {
14506
+ const db = getDatabase();
14507
+ updateTask(db, task.id, {
14508
+ status: "failed",
14509
+ error: "No handler registered for this task type"
14510
+ });
14511
+ } catch {}
14512
+ _processing = false;
14513
+ _totalProcessed++;
14514
+ return;
14515
+ }
14516
+ const taskId = task.id;
14517
+ let progress = 0;
14518
+ const ctx = {
14519
+ task,
14520
+ updateProgress: (p) => {
14521
+ progress = Math.min(1, Math.max(0, p));
14522
+ try {
14523
+ const db = getDatabase();
14524
+ updateTask(db, taskId, { progress });
14525
+ } catch {}
14526
+ },
14527
+ addComment: (body, agentId) => {
14528
+ try {
14529
+ const db = getDatabase();
14530
+ addTaskComment(db, taskId, body, agentId);
14531
+ } catch {}
14532
+ }
14533
+ };
14534
+ try {
14535
+ await handler(ctx);
14536
+ const db = getDatabase();
14537
+ updateTask(db, taskId, { status: "completed", progress: 1 });
14538
+ console.log(`[task-runner] Completed: ${task.subject}`);
14539
+ } catch (e) {
14540
+ const errorMsg = e instanceof Error ? e.message : String(e);
14541
+ try {
14542
+ const db = getDatabase();
14543
+ updateTask(db, taskId, { status: "failed", error: errorMsg });
14544
+ } catch {}
14545
+ console.error(`[task-runner] Failed: ${task.subject} \u2014 ${errorMsg}`);
14546
+ } finally {
14547
+ _processing = false;
14548
+ _totalProcessed++;
14549
+ }
14550
+ }
14551
+ function getTaskRunnerStats() {
14552
+ try {
14553
+ const db = getDatabase();
14554
+ const rows = db.query("SELECT status, COUNT(*) as c FROM tasks GROUP BY status").all();
14555
+ const stats = {
14556
+ pending: 0,
14557
+ inProgress: 0,
14558
+ completed: 0,
14559
+ failed: 0,
14560
+ cancelled: 0,
14561
+ totalProcessed: _totalProcessed
14562
+ };
14563
+ for (const row of rows) {
14564
+ switch (row.status) {
14565
+ case "pending":
14566
+ stats.pending = row.c;
14567
+ break;
14568
+ case "in_progress":
14569
+ stats.inProgress = row.c;
14570
+ break;
14571
+ case "completed":
14572
+ stats.completed = row.c;
14573
+ break;
14574
+ case "failed":
14575
+ stats.failed = row.c;
14576
+ break;
14577
+ case "cancelled":
14578
+ stats.cancelled = row.c;
14579
+ break;
14580
+ }
14581
+ }
14582
+ return stats;
14583
+ } catch {
14584
+ return {
14585
+ pending: 0,
14586
+ inProgress: 0,
14587
+ completed: 0,
14588
+ failed: 0,
14589
+ cancelled: 0,
14590
+ totalProcessed: _totalProcessed
14591
+ };
14592
+ }
14593
+ }
13969
14594
  export {
13970
14595
  withMemoryLock,
13971
14596
  uuid,
14597
+ updateTask,
13972
14598
  updateMemory,
13973
14599
  updateEntity,
13974
14600
  updateAgent,
13975
14601
  unlinkEntityFromMemory,
13976
14602
  unfocus,
13977
14603
  touchMemory,
14604
+ touchMachine,
13978
14605
  touchAgent,
13979
14606
  syncMemories,
14607
+ startTaskRunner,
13980
14608
  shortUuid,
14609
+ setPrimaryMachine,
13981
14610
  setFocus,
14611
+ setDefaultTaskHandler,
13982
14612
  setActiveProfile,
13983
14613
  setActiveModel,
13984
14614
  searchMemories,
@@ -13986,11 +14616,14 @@ export {
13986
14616
  resolveProjectId,
13987
14617
  resolvePartialId,
13988
14618
  resetDatabase,
14619
+ renameMachine,
13989
14620
  releaseResourceLocks,
13990
14621
  releaseMemoryWriteLock,
13991
14622
  releaseLock,
13992
14623
  releaseAllAgentLocks,
14624
+ registerTaskHandler,
13993
14625
  registerProject,
14626
+ registerMachine,
13994
14627
  registerAgent,
13995
14628
  redactSecrets,
13996
14629
  providerRegistry,
@@ -14001,25 +14634,36 @@ export {
14001
14634
  mergeEntities,
14002
14635
  memoryLockId,
14003
14636
  loadConfig,
14637
+ listTasks,
14638
+ listTaskComments,
14004
14639
  listRelations,
14005
14640
  listProjects,
14006
14641
  listProfiles,
14007
14642
  listMemories,
14643
+ listMachines,
14008
14644
  listEntities,
14009
14645
  listAgentsByProject,
14010
14646
  listAgents,
14011
14647
  listAgentLocks,
14012
14648
  linkEntityToMemory,
14013
14649
  incrementRecallCount,
14650
+ getTaskStats,
14651
+ getTaskRunnerStats,
14652
+ getTask,
14014
14653
  getRelation,
14015
14654
  getRelatedEntities,
14016
14655
  getProject,
14656
+ getPrimaryMachineStartupWarning,
14657
+ getPrimaryMachineCandidate,
14658
+ getPrimaryMachine,
14017
14659
  getMemoryVersions,
14018
14660
  getMemoryByKey,
14019
14661
  getMemory,
14020
14662
  getMemoriesForEntity,
14021
14663
  getMemoriesByKey,
14664
+ getMachine,
14022
14665
  getFocus,
14666
+ getFallbackSyncTargetMachine,
14023
14667
  getEntityMemoryLinks,
14024
14668
  getEntityGraph,
14025
14669
  getEntityByName,
@@ -14028,6 +14672,7 @@ export {
14028
14672
  getDedupStats,
14029
14673
  getDbPath2 as getDbPath,
14030
14674
  getDatabase,
14675
+ getCurrentMachineId,
14031
14676
  getAutoMemoryStats,
14032
14677
  getAgent,
14033
14678
  getActiveProfile,
@@ -14037,12 +14682,16 @@ export {
14037
14682
  findPath,
14038
14683
  enforceQuotas,
14039
14684
  deprioritizeStale,
14685
+ deleteTaskComment,
14686
+ deleteTask,
14040
14687
  deleteRelation,
14041
14688
  deleteProfile,
14042
14689
  deleteMemory,
14690
+ deleteMachine,
14043
14691
  deleteEntity,
14044
14692
  defaultSyncAgents,
14045
14693
  dedup,
14694
+ createTask,
14046
14695
  createRelation,
14047
14696
  createMemory,
14048
14697
  createEntity,
@@ -14060,6 +14709,7 @@ export {
14060
14709
  archiveUnused,
14061
14710
  archiveStale,
14062
14711
  agentHoldsLock,
14712
+ addTaskComment,
14063
14713
  acquireMemoryWriteLock,
14064
14714
  acquireLock,
14065
14715
  VersionConflictError,