@memtensor/memos-local-openclaw-plugin 1.0.8-beta → 1.0.8-beta.2

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/index.ts CHANGED
@@ -1151,6 +1151,10 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
1151
1151
  };
1152
1152
  }
1153
1153
 
1154
+ const disabledWarning = skill.status === "archived"
1155
+ ? "\n\n> **Warning:** This skill is currently **disabled** (archived). Its content is shown for reference only — it will not be used in search or auto-recall.\n\n"
1156
+ : "";
1157
+
1154
1158
  const manifest = skillInstaller.getCompanionManifest(resolvedSkillId);
1155
1159
  let footer = "\n\n---\n";
1156
1160
 
@@ -1175,7 +1179,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
1175
1179
  return {
1176
1180
  content: [{
1177
1181
  type: "text",
1178
- text: `## Skill: ${skill.name} (v${skill.version})\n\n${sv.content}${footer}`,
1182
+ text: `## Skill: ${skill.name} (v${skill.version})${disabledWarning}\n\n${sv.content}${footer}`,
1179
1183
  }],
1180
1184
  details: {
1181
1185
  skillId: skill.id,
@@ -3,7 +3,7 @@
3
3
  "name": "MemOS Local Memory",
4
4
  "description": "Full-write local conversation memory with hybrid search (RRF + MMR + recency), task summarization, skill evolution, and team sharing (Hub-Client). Provides memory_search, memory_get, task_summary, skill_search, task_share, network_skill_pull, network_team_info, memory_viewer for layered retrieval and team collaboration.",
5
5
  "kind": "memory",
6
- "version": "1.0.6-beta.11",
6
+ "version": "1.0.8-beta.2",
7
7
  "skills": [
8
8
  "skill/memos-memory-guide"
9
9
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memtensor/memos-local-openclaw-plugin",
3
- "version": "1.0.8-beta",
3
+ "version": "1.0.8-beta.2",
4
4
  "description": "MemOS Local memory plugin for OpenClaw — full-write, hybrid-recall, progressive retrieval",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -110,6 +110,7 @@ export class SqliteStore {
110
110
  this.migrateOwnerFields();
111
111
  this.migrateSkillVisibility();
112
112
  this.migrateSkillEmbeddingsAndFts();
113
+ this.migrateTaskEmbeddingsAndFts();
113
114
  this.migrateFtsToTrigram();
114
115
  this.migrateHubTables();
115
116
  this.migrateHubFtsToTrigram();
@@ -290,6 +291,55 @@ export class SqliteStore {
290
291
  } catch { /* best-effort */ }
291
292
  }
292
293
 
294
+ private migrateTaskEmbeddingsAndFts(): void {
295
+ this.db.exec(`
296
+ CREATE TABLE IF NOT EXISTS task_embeddings (
297
+ task_id TEXT PRIMARY KEY REFERENCES tasks(id) ON DELETE CASCADE,
298
+ vector BLOB NOT NULL,
299
+ dimensions INTEGER NOT NULL,
300
+ updated_at INTEGER NOT NULL
301
+ );
302
+
303
+ CREATE VIRTUAL TABLE IF NOT EXISTS tasks_fts USING fts5(
304
+ summary,
305
+ topic,
306
+ content='tasks',
307
+ content_rowid='rowid',
308
+ tokenize='trigram'
309
+ );
310
+ `);
311
+
312
+ try {
313
+ this.db.exec(`
314
+ CREATE TRIGGER IF NOT EXISTS tasks_fts_ai AFTER INSERT ON tasks BEGIN
315
+ INSERT INTO tasks_fts(rowid, summary, topic)
316
+ VALUES (new.rowid, new.summary, COALESCE(new.topic, ''));
317
+ END;
318
+ CREATE TRIGGER IF NOT EXISTS tasks_fts_ad AFTER DELETE ON tasks BEGIN
319
+ INSERT INTO tasks_fts(tasks_fts, rowid, summary, topic)
320
+ VALUES ('delete', old.rowid, old.summary, COALESCE(old.topic, ''));
321
+ END;
322
+ CREATE TRIGGER IF NOT EXISTS tasks_fts_au AFTER UPDATE ON tasks BEGIN
323
+ INSERT INTO tasks_fts(tasks_fts, rowid, summary, topic)
324
+ VALUES ('delete', old.rowid, old.summary, COALESCE(old.topic, ''));
325
+ INSERT INTO tasks_fts(rowid, summary, topic)
326
+ VALUES (new.rowid, new.summary, COALESCE(new.topic, ''));
327
+ END;
328
+ `);
329
+ } catch {
330
+ // triggers may already exist
331
+ }
332
+
333
+ try {
334
+ const count = (this.db.prepare("SELECT COUNT(*) as c FROM tasks_fts").get() as { c: number }).c;
335
+ const taskCount = (this.db.prepare("SELECT COUNT(*) as c FROM tasks").get() as { c: number }).c;
336
+ if (count === 0 && taskCount > 0) {
337
+ this.db.exec("INSERT INTO tasks_fts(rowid, summary, topic) SELECT rowid, summary, COALESCE(topic, '') FROM tasks");
338
+ this.log.info(`Migrated: backfilled tasks_fts for ${taskCount} tasks`);
339
+ }
340
+ } catch { /* best-effort */ }
341
+ }
342
+
293
343
  private migrateFtsToTrigram(): void {
294
344
  // Check if chunks_fts still uses the old tokenizer (porter unicode61)
295
345
  try {
@@ -1477,6 +1527,21 @@ export class SqliteStore {
1477
1527
  return result.changes > 0;
1478
1528
  }
1479
1529
 
1530
+ disableSkill(skillId: string): boolean {
1531
+ const skill = this.getSkill(skillId);
1532
+ if (!skill || skill.status === "archived") return false;
1533
+ this.db.prepare("DELETE FROM skill_embeddings WHERE skill_id = ?").run(skillId);
1534
+ this.updateSkill(skillId, { status: "archived", installed: 0 });
1535
+ return true;
1536
+ }
1537
+
1538
+ enableSkill(skillId: string): boolean {
1539
+ const skill = this.getSkill(skillId);
1540
+ if (!skill || skill.status !== "archived") return false;
1541
+ this.updateSkill(skillId, { status: "active" });
1542
+ return true;
1543
+ }
1544
+
1480
1545
  // ─── Task CRUD ───
1481
1546
 
1482
1547
  insertTask(task: Task): void {
@@ -1558,10 +1623,11 @@ export class SqliteStore {
1558
1623
  return rows.map(rowToChunk);
1559
1624
  }
1560
1625
 
1561
- listTasks(opts: { status?: string; limit?: number; offset?: number; owner?: string } = {}): { tasks: Task[]; total: number } {
1626
+ listTasks(opts: { status?: string; limit?: number; offset?: number; owner?: string; session?: string } = {}): { tasks: Task[]; total: number } {
1562
1627
  const conditions: string[] = [];
1563
1628
  const params: unknown[] = [];
1564
1629
  if (opts.status) { conditions.push("status = ?"); params.push(opts.status); }
1630
+ if (opts.session) { conditions.push("session_key = ?"); params.push(opts.session); }
1565
1631
  if (opts.owner) {
1566
1632
  conditions.push("(owner = ? OR (owner = 'public' AND id IN (SELECT task_id FROM local_shared_tasks WHERE original_owner = ?)))");
1567
1633
  params.push(opts.owner, opts.owner);
@@ -1683,9 +1749,24 @@ export class SqliteStore {
1683
1749
  this.db.prepare(`UPDATE skills SET ${sets.join(", ")} WHERE id = ?`).run(...params);
1684
1750
  }
1685
1751
 
1686
- listSkills(opts: { status?: string } = {}): Skill[] {
1687
- const cond = opts.status ? "WHERE status = ?" : "";
1688
- const params = opts.status ? [opts.status] : [];
1752
+ listSkills(opts: { status?: string; session?: string; owner?: string } = {}): Skill[] {
1753
+ const conditions: string[] = [];
1754
+ const params: unknown[] = [];
1755
+ if (opts.status) { conditions.push("status = ?"); params.push(opts.status); }
1756
+ if (opts.owner) {
1757
+ conditions.push("(owner = ? OR owner = 'public')");
1758
+ params.push(opts.owner);
1759
+ }
1760
+ if (opts.session) {
1761
+ conditions.push(`EXISTS (
1762
+ SELECT 1
1763
+ FROM task_skills ts
1764
+ JOIN tasks t ON t.id = ts.task_id
1765
+ WHERE ts.skill_id = skills.id AND t.session_key = ?
1766
+ )`);
1767
+ params.push(opts.session);
1768
+ }
1769
+ const cond = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1689
1770
  const rows = this.db.prepare(`SELECT * FROM skills ${cond} ORDER BY updated_at DESC`).all(...params) as SkillRow[];
1690
1771
  return rows.map(rowToSkill);
1691
1772
  }
@@ -1775,6 +1856,61 @@ export class SqliteStore {
1775
1856
  }
1776
1857
  }
1777
1858
 
1859
+ // ─── Task Embeddings & Search ───
1860
+
1861
+ upsertTaskEmbedding(taskId: string, vector: number[]): void {
1862
+ const buf = Buffer.from(new Float32Array(vector).buffer);
1863
+ this.db.prepare(`
1864
+ INSERT OR REPLACE INTO task_embeddings (task_id, vector, dimensions, updated_at)
1865
+ VALUES (?, ?, ?, ?)
1866
+ `).run(taskId, buf, vector.length, Date.now());
1867
+ }
1868
+
1869
+ getTaskEmbeddings(owner?: string): Array<{ taskId: string; vector: number[] }> {
1870
+ let sql = `SELECT te.task_id, te.vector, te.dimensions
1871
+ FROM task_embeddings te
1872
+ JOIN tasks t ON t.id = te.task_id`;
1873
+ const params: any[] = [];
1874
+ if (owner) {
1875
+ sql += ` WHERE (t.owner = ? OR t.owner = 'public')`;
1876
+ params.push(owner);
1877
+ }
1878
+ const rows = this.db.prepare(sql).all(...params) as Array<{ task_id: string; vector: Buffer; dimensions: number }>;
1879
+ return rows.map((r) => ({
1880
+ taskId: r.task_id,
1881
+ vector: Array.from(new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions)),
1882
+ }));
1883
+ }
1884
+
1885
+ taskFtsSearch(query: string, limit: number, owner?: string): Array<{ taskId: string; score: number }> {
1886
+ const sanitized = sanitizeFtsQuery(query);
1887
+ if (!sanitized) return [];
1888
+ try {
1889
+ let sql = `
1890
+ SELECT t.id as task_id, rank
1891
+ FROM tasks_fts f
1892
+ JOIN tasks t ON t.rowid = f.rowid
1893
+ WHERE tasks_fts MATCH ?`;
1894
+ const params: any[] = [sanitized];
1895
+ if (owner) {
1896
+ sql += ` AND (t.owner = ? OR t.owner = 'public')`;
1897
+ params.push(owner);
1898
+ }
1899
+ sql += ` ORDER BY rank LIMIT ?`;
1900
+ params.push(limit);
1901
+ const rows = this.db.prepare(sql).all(...params) as Array<{ task_id: string; rank: number }>;
1902
+ if (rows.length === 0) return [];
1903
+ const maxAbsRank = Math.max(...rows.map((r) => Math.abs(r.rank)));
1904
+ return rows.map((r) => ({
1905
+ taskId: r.task_id,
1906
+ score: maxAbsRank > 0 ? Math.abs(r.rank) / maxAbsRank : 0,
1907
+ }));
1908
+ } catch {
1909
+ this.log.warn(`Task FTS query failed for: "${sanitized}", returning empty`);
1910
+ return [];
1911
+ }
1912
+ }
1913
+
1778
1914
  listPublicSkills(): Skill[] {
1779
1915
  const rows = this.db.prepare("SELECT * FROM skills WHERE visibility = 'public' AND status = 'active' ORDER BY updated_at DESC").all() as SkillRow[];
1780
1916
  return rows.map(rowToSkill);