@unblocklabs/unblock-memory 0.3.20 → 0.3.22

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.
@@ -412,34 +412,34 @@ export class QmdMemoryManager {
412
412
  ])),
413
413
  },
414
414
  });
415
- enableSecureDelete(store);
416
- ensureMemoryAnalysisSchema(store.internal.db);
417
- markStaleForAnalysisCollectionChange(store.internal.db, this.#analysisCollectionNames(), this.#skillCollectionNames().length > 0);
418
- const configuredCollections = new Set(this.#qmdSources().map((source) => source.collection));
419
- const staleCollections = (await store.getStatus()).collections
420
- .map((collection) => collection.name)
421
- .filter((collection) => !configuredCollections.has(collection));
422
- const appearsInAnalysis = store.internal.db.prepare(`
423
- SELECT 1
424
- FROM memory_analysis_memberships membership
425
- JOIN documents document ON document.hash = membership.hash
426
- WHERE membership.run_id = (
427
- SELECT id FROM memory_analysis_runs
428
- WHERE completed_at IS NOT NULL
429
- ORDER BY completed_at DESC, created_at DESC, id DESC
430
- LIMIT 1
431
- ) AND document.collection = ?
432
- LIMIT 1
433
- `);
434
- const prunedAnalysisInput = staleCollections.some((collection) => appearsInAnalysis.get(collection));
435
- const prunedDocuments = await pruneStaleCollections(store, configuredCollections);
436
- if (prunedDocuments > 0 && prunedAnalysisInput)
437
- markMemoryAnalysisStale(store.internal.db);
438
415
  try {
416
+ enableSecureDelete(store);
417
+ ensureMemoryAnalysisSchema(store.internal.db);
418
+ markStaleForAnalysisCollectionChange(store.internal.db, this.#analysisCollectionNames(), this.#skillCollectionNames().length > 0);
419
+ const configuredCollections = new Set(this.#qmdSources().map((source) => source.collection));
420
+ const staleCollections = (await store.getStatus()).collections
421
+ .map((collection) => collection.name)
422
+ .filter((collection) => !configuredCollections.has(collection));
423
+ const appearsInAnalysis = store.internal.db.prepare(`
424
+ SELECT 1
425
+ FROM memory_analysis_memberships membership
426
+ JOIN documents document ON document.hash = membership.hash
427
+ WHERE membership.run_id = (
428
+ SELECT id FROM memory_analysis_runs
429
+ WHERE completed_at IS NOT NULL
430
+ ORDER BY completed_at DESC, created_at DESC, id DESC
431
+ LIMIT 1
432
+ ) AND document.collection = ?
433
+ LIMIT 1
434
+ `);
435
+ const prunedAnalysisInput = staleCollections.some((collection) => appearsInAnalysis.get(collection));
436
+ const prunedDocuments = await pruneStaleCollections(store, configuredCollections);
437
+ if (prunedDocuments > 0 && prunedAnalysisInput)
438
+ markMemoryAnalysisStale(store.internal.db);
439
439
  await ensureSemanticChunking(store);
440
440
  }
441
441
  catch (error) {
442
- await store.close();
442
+ await store.close().catch(() => undefined);
443
443
  throw error;
444
444
  }
445
445
  this.#cleanupRemovedDocuments = (changedDocuments) => {
@@ -22,6 +22,21 @@ function requireRegularFile(path) {
22
22
  if (file && !file.isFile())
23
23
  throw new Error("Memory database must be a regular file, not a symlink");
24
24
  }
25
+ function enableWal(db) {
26
+ // Switching journal modes can return SQLITE_BUSY without invoking busy_timeout.
27
+ const deadline = Date.now() + 5000;
28
+ for (;;) {
29
+ try {
30
+ db.exec("PRAGMA journal_mode=WAL");
31
+ return;
32
+ }
33
+ catch (error) {
34
+ if (!(error instanceof Error) || !("errcode" in error) || error.errcode !== 5 || Date.now() >= deadline)
35
+ throw error;
36
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, Math.min(25, Math.max(0, deadline - Date.now())));
37
+ }
38
+ }
39
+ }
25
40
  /** Separate domain stores share settings, not a monolithic data-access API. */
26
41
  export function openMemoryDatabase(path) {
27
42
  mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
@@ -35,7 +50,9 @@ export function openMemoryDatabase(path) {
35
50
  }
36
51
  const db = new DatabaseSync(path);
37
52
  try {
38
- db.exec(`PRAGMA busy_timeout=5000; PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;
53
+ db.exec("PRAGMA busy_timeout=5000");
54
+ enableWal(db);
55
+ db.exec(`PRAGMA foreign_keys=ON;
39
56
  PRAGMA trusted_schema=OFF;
40
57
  CREATE TABLE IF NOT EXISTS memory_schema (component TEXT PRIMARY KEY, version INTEGER NOT NULL) STRICT;`);
41
58
  // Explicit standalone store paths remain useful to tests and offline tools.
@@ -247,7 +247,14 @@ export class QmdMemoryRuntime {
247
247
  analysisExecutable: this.#analysisExecutable,
248
248
  sessions,
249
249
  });
250
- await manager.start();
250
+ try {
251
+ await manager.start();
252
+ }
253
+ catch (error) {
254
+ // The manager is not in the runtime's resolved cache yet, so we own cleanup.
255
+ await manager.close().catch(() => undefined);
256
+ throw error;
257
+ }
251
258
  return manager;
252
259
  }
253
260
  #sessionConfig(cfg, agentId) {
@@ -5,6 +5,8 @@ type SlackDirectoryEntry = {
5
5
  name?: string;
6
6
  handle?: string;
7
7
  avatarUrl?: string;
8
+ isBot?: boolean;
9
+ isDeactivated?: boolean;
8
10
  };
9
11
  export type SlackDirectoryReader = {
10
12
  listUsers(params: {
@@ -26,6 +26,8 @@ function slackEntry(value) {
26
26
  avatarUrl: text(profile?.image_512, 2_000) ??
27
27
  text(profile?.image_192, 2_000) ??
28
28
  text(profile?.image_72, 2_000),
29
+ isBot: typeof member?.is_bot === "boolean" ? member.is_bot : undefined,
30
+ isDeactivated: typeof member?.deleted === "boolean" ? member.deleted : undefined,
29
31
  };
30
32
  }
31
33
  export function createOpenClawSlackDirectory(params) {
@@ -100,7 +102,9 @@ export async function syncSlackDirectory(params) {
100
102
  const changed = existing !== undefined &&
101
103
  ((entry.name !== undefined && entry.name !== existing.displayName) ||
102
104
  (entry.handle !== undefined && entry.handle !== existing.handle) ||
103
- (entry.avatarUrl !== undefined && entry.avatarUrl !== existing.avatarUrl));
105
+ (entry.avatarUrl !== undefined && entry.avatarUrl !== existing.avatarUrl) ||
106
+ (entry.isBot !== undefined && entry.isBot !== existing.isBot) ||
107
+ (entry.isDeactivated !== undefined && entry.isDeactivated !== existing.isDeactivated));
104
108
  const result = params.store.upsertIdentity({
105
109
  provider: "slack",
106
110
  accountScope: params.accountId,
@@ -108,6 +112,8 @@ export async function syncSlackDirectory(params) {
108
112
  displayName: entry.name,
109
113
  handle: entry.handle,
110
114
  avatarUrl: entry.avatarUrl,
115
+ isBot: entry.isBot,
116
+ isDeactivated: entry.isDeactivated,
111
117
  syncedAt,
112
118
  });
113
119
  if (result.created)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "unblock-memory",
3
3
  "name": "Unblock Memory",
4
- "version": "0.3.20",
4
+ "version": "0.3.22",
5
5
  "description": "Indexes, retrieves, and analyzes configured workspace memory with existing QMD vectors.",
6
6
  "kind": "memory",
7
7
  "activation": { "onStartup": true },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unblocklabs/unblock-memory",
3
- "version": "0.3.20",
3
+ "version": "0.3.22",
4
4
  "description": "Workspace-native memory for OpenClaw, powered by QMD",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -35,7 +35,7 @@
35
35
  "preflight": "npm run knip && npm run build && npm run typecheck && npm test && npm run plugin:inspect && npm run plugin:inspect:runtime && npm pack --dry-run"
36
36
  },
37
37
  "dependencies": {
38
- "@unblocklabs/qmd": "https://github.com/unblocklabs-ai/qmd/releases/download/v2.10.0/unblocklabs-qmd-2.10.0.tgz",
38
+ "@unblocklabs/qmd": "https://github.com/unblocklabs-ai/qmd/releases/download/v2.10.1/unblocklabs-qmd-2.10.1.tgz",
39
39
  "chokidar": "5.0.0",
40
40
  "picomatch": "^4.0.5",
41
41
  "typebox": "1.3.6"