@joshuaswarren/openclaw-engram 7.2.3 → 7.2.4

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
@@ -3921,8 +3921,8 @@ ${stderr}`.trim();
3921
3921
  }
3922
3922
  }
3923
3923
  async ensureCollection(memoryDir) {
3924
- if (this.available === false && !this.daemonAvailable) return false;
3925
- if (this.available === false) return true;
3924
+ if (this.available === false && !this.daemonAvailable) return "unknown";
3925
+ if (this.available === false) return "skipped";
3926
3926
  try {
3927
3927
  const { stdout } = await runQmd(
3928
3928
  ["collection", "list"],
@@ -3934,14 +3934,18 @@ ${stderr}`.trim();
3934
3934
  "m"
3935
3935
  );
3936
3936
  if (collectionRegex.test(stdout)) {
3937
- return true;
3937
+ return "present";
3938
3938
  }
3939
- } catch {
3939
+ } catch (err) {
3940
+ log.debug(
3941
+ `QMD collection check unavailable for "${this.collection}" (will not disable features): ${err instanceof Error ? err.message : String(err)}`
3942
+ );
3943
+ return "unknown";
3940
3944
  }
3941
3945
  log.info(
3942
3946
  `QMD collection "${this.collection}" not found. Add it to ~/.config/qmd/index.yml pointing at ${memoryDir}`
3943
3947
  );
3944
- return false;
3948
+ return "missing";
3945
3949
  }
3946
3950
  };
3947
3951
 
@@ -8220,6 +8224,9 @@ var Orchestrator = class _Orchestrator {
8220
8224
  lastQmdEmbedAtMs = 0;
8221
8225
  conversationIndexLastUpdateAtMs = /* @__PURE__ */ new Map();
8222
8226
  lastFileHygieneRunAtMs = 0;
8227
+ lastRecallFailureLogAtMs = 0;
8228
+ lastRecallFailureAtMs = 0;
8229
+ suppressedRecallFailures = 0;
8223
8230
  // Initialization gate: recall() awaits this before proceeding
8224
8231
  initPromise = null;
8225
8232
  resolveInit = null;
@@ -8309,7 +8316,17 @@ var Orchestrator = class _Orchestrator {
8309
8316
  if (available) {
8310
8317
  const mode = this.qmd.isDaemonMode() ? "daemon" : "subprocess";
8311
8318
  log.info(`QMD: available (mode: ${mode}) ${this.qmd.debugStatus()}`);
8312
- await this.qmd.ensureCollection(this.config.memoryDir);
8319
+ const collectionState = await this.qmd.ensureCollection(this.config.memoryDir);
8320
+ if (collectionState === "missing") {
8321
+ this.config.qmdEnabled = false;
8322
+ log.warn(
8323
+ "QMD collection missing for Engram memory store; disabling QMD retrieval for this runtime (fallback retrieval remains enabled)"
8324
+ );
8325
+ } else if (collectionState === "unknown") {
8326
+ log.warn("QMD collection check unavailable; keeping QMD retrieval enabled for fail-open behavior");
8327
+ } else if (collectionState === "skipped") {
8328
+ log.debug("QMD collection check skipped in daemon-only mode");
8329
+ }
8313
8330
  } else {
8314
8331
  log.warn(`QMD: not available ${this.qmd.debugStatus()}`);
8315
8332
  }
@@ -8318,9 +8335,21 @@ var Orchestrator = class _Orchestrator {
8318
8335
  const available = await this.conversationQmd.probe();
8319
8336
  if (available) {
8320
8337
  log.info(`Conversation index QMD: available ${this.conversationQmd.debugStatus()}`);
8321
- await this.conversationQmd.ensureCollection(
8338
+ const collectionState = await this.conversationQmd.ensureCollection(
8322
8339
  path18.join(this.config.memoryDir, "conversation-index")
8323
8340
  );
8341
+ if (collectionState === "missing") {
8342
+ this.config.conversationIndexEnabled = false;
8343
+ log.warn(
8344
+ "Conversation index collection missing; disabling conversation semantic recall for this runtime"
8345
+ );
8346
+ } else if (collectionState === "unknown") {
8347
+ log.warn(
8348
+ "Conversation index collection check unavailable; keeping conversation semantic recall enabled for fail-open behavior"
8349
+ );
8350
+ } else if (collectionState === "skipped") {
8351
+ log.debug("Conversation index collection check skipped in daemon-only mode");
8352
+ }
8324
8353
  } else {
8325
8354
  log.warn(`Conversation index QMD: not available ${this.conversationQmd.debugStatus()}`);
8326
8355
  }
@@ -8443,9 +8472,10 @@ var Orchestrator = class _Orchestrator {
8443
8472
  this.config.conversationIndexRetentionDays
8444
8473
  );
8445
8474
  const q = this.conversationQmd ?? this.qmd;
8475
+ const usingPrimaryQmdClient = q === this.qmd;
8446
8476
  const shouldEmbed = opts?.embed ?? this.config.conversationIndexEmbedOnUpdate;
8447
8477
  let embedded = false;
8448
- if (this.config.qmdEnabled && q.isAvailable()) {
8478
+ if ((!usingPrimaryQmdClient || this.config.qmdEnabled) && q.isAvailable()) {
8449
8479
  await q.update();
8450
8480
  if (shouldEmbed) {
8451
8481
  await q.embed();
@@ -8504,17 +8534,36 @@ var Orchestrator = class _Orchestrator {
8504
8534
  log.warn("recall: init gate timed out \u2014 proceeding without full init");
8505
8535
  }
8506
8536
  }
8507
- const RECALL_TIMEOUT_MS = 6e4;
8537
+ const RECALL_TIMEOUT_MS = 75e3;
8508
8538
  return Promise.race([
8509
8539
  this.recallInternal(prompt, sessionKey),
8510
8540
  new Promise(
8511
8541
  (_, reject) => setTimeout(() => reject(new Error("recall timeout")), RECALL_TIMEOUT_MS)
8512
8542
  )
8513
8543
  ]).catch((err) => {
8514
- log.warn(`recall timed out or failed: ${err}`);
8544
+ this.logRecallFailure(err);
8515
8545
  return "";
8516
8546
  });
8517
8547
  }
8548
+ logRecallFailure(err) {
8549
+ const now = Date.now();
8550
+ const errorMsg = err instanceof Error ? err.message : String(err);
8551
+ const LOG_WINDOW_MS = 6e4;
8552
+ const idleSinceLastFailureMs = now - this.lastRecallFailureAtMs;
8553
+ this.lastRecallFailureAtMs = now;
8554
+ if (idleSinceLastFailureMs >= LOG_WINDOW_MS) {
8555
+ this.suppressedRecallFailures = 0;
8556
+ }
8557
+ if (now - this.lastRecallFailureLogAtMs >= LOG_WINDOW_MS) {
8558
+ const suffix = this.suppressedRecallFailures > 0 ? ` (suppressed ${this.suppressedRecallFailures} similar failures in last minute)` : "";
8559
+ log.warn(`recall timed out or failed: ${errorMsg}${suffix}`);
8560
+ this.lastRecallFailureLogAtMs = now;
8561
+ this.suppressedRecallFailures = 0;
8562
+ return;
8563
+ }
8564
+ this.suppressedRecallFailures += 1;
8565
+ log.debug(`recall timed out or failed (suppressed): ${errorMsg}`);
8566
+ }
8518
8567
  async recallInternal(prompt, sessionKey) {
8519
8568
  const recallStart = Date.now();
8520
8569
  const timings = {};
@@ -8791,7 +8840,7 @@ ${formatted}`);
8791
8840
  }
8792
8841
  timings.summaries = `${Date.now() - summariesT0}ms`;
8793
8842
  const convT0 = Date.now();
8794
- if (this.config.conversationIndexEnabled && this.config.qmdEnabled && this.conversationQmd && this.conversationQmd.isAvailable()) {
8843
+ if (this.config.conversationIndexEnabled && this.conversationQmd && this.conversationQmd.isAvailable()) {
8795
8844
  const startedAtMs = Date.now();
8796
8845
  const timeoutMs = Math.max(200, this.config.conversationRecallTimeoutMs);
8797
8846
  const topK = Math.max(1, this.config.conversationRecallTopK);