@joshuaswarren/openclaw-engram 8.3.71 → 8.3.73

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
@@ -3650,7 +3650,11 @@ function getGlobalQmdState() {
3650
3650
  lastGlobalUpdateFailAtMs: null,
3651
3651
  lastGlobalEmbedRunAtMs: null,
3652
3652
  lastGlobalEmbedFailAtMs: null,
3653
- lastCliWarnAtMs: null
3653
+ lastCliWarnAtMs: null,
3654
+ lastUpdateByCollectionMs: {},
3655
+ lastUpdateFailByCollectionMs: {},
3656
+ lastEmbedByCollectionMs: {},
3657
+ lastEmbedFailByCollectionMs: {}
3654
3658
  };
3655
3659
  }
3656
3660
  return g[QMD_GLOBAL_STATE_KEY];
@@ -4259,23 +4263,49 @@ ${stderr}`.trim();
4259
4263
  }
4260
4264
  }
4261
4265
  async update() {
4266
+ await this.runUpdateForCollection(this.collection, { perCollectionThrottle: false });
4267
+ }
4268
+ async updateCollection(collection) {
4269
+ await this.runUpdateForCollection(collection, { perCollectionThrottle: true });
4270
+ }
4271
+ async runUpdateForCollection(collection, options) {
4262
4272
  if (this.available === false) return;
4273
+ const name = collection.trim();
4274
+ if (!name) return;
4263
4275
  const globalState = getGlobalQmdState();
4264
- if (this.lastUpdateRunAtMs && Date.now() - this.lastUpdateRunAtMs < this.updateMinIntervalMs) {
4265
- log.debug("QMD update: suppressed due to min-interval gate");
4266
- return;
4267
- }
4268
- if (this.lastUpdateFailAtMs && Date.now() - this.lastUpdateFailAtMs < QMD_UPDATE_BACKOFF_MS) {
4269
- log.debug("QMD update: suppressed due to recent failures (backoff)");
4270
- return;
4271
- }
4272
- if (globalState.lastGlobalUpdateRunAtMs && Date.now() - globalState.lastGlobalUpdateRunAtMs < this.updateMinIntervalMs) {
4273
- log.debug("QMD update: suppressed by global min-interval gate");
4274
- return;
4275
- }
4276
- if (globalState.lastGlobalUpdateFailAtMs && Date.now() - globalState.lastGlobalUpdateFailAtMs < QMD_UPDATE_BACKOFF_MS) {
4277
- log.debug("QMD update: suppressed by global failure backoff");
4278
- return;
4276
+ const now = Date.now();
4277
+ if (options.perCollectionThrottle) {
4278
+ if (globalState.lastGlobalUpdateFailAtMs && now - globalState.lastGlobalUpdateFailAtMs < QMD_UPDATE_BACKOFF_MS) {
4279
+ log.debug("QMD update: suppressed by global failure backoff");
4280
+ return;
4281
+ }
4282
+ const lastCollectionRun = globalState.lastUpdateByCollectionMs[name];
4283
+ if (Number.isFinite(lastCollectionRun) && now - lastCollectionRun < this.updateMinIntervalMs) {
4284
+ log.debug(`QMD update: suppressed by per-collection min-interval gate (${name})`);
4285
+ return;
4286
+ }
4287
+ const lastCollectionFail = globalState.lastUpdateFailByCollectionMs[name];
4288
+ if (Number.isFinite(lastCollectionFail) && now - lastCollectionFail < QMD_UPDATE_BACKOFF_MS) {
4289
+ log.debug(`QMD update: suppressed by per-collection failure backoff (${name})`);
4290
+ return;
4291
+ }
4292
+ } else {
4293
+ if (this.lastUpdateRunAtMs && now - this.lastUpdateRunAtMs < this.updateMinIntervalMs) {
4294
+ log.debug("QMD update: suppressed due to min-interval gate");
4295
+ return;
4296
+ }
4297
+ if (this.lastUpdateFailAtMs && now - this.lastUpdateFailAtMs < QMD_UPDATE_BACKOFF_MS) {
4298
+ log.debug("QMD update: suppressed due to recent failures (backoff)");
4299
+ return;
4300
+ }
4301
+ if (globalState.lastGlobalUpdateRunAtMs && now - globalState.lastGlobalUpdateRunAtMs < this.updateMinIntervalMs) {
4302
+ log.debug("QMD update: suppressed by global min-interval gate");
4303
+ return;
4304
+ }
4305
+ if (globalState.lastGlobalUpdateFailAtMs && now - globalState.lastGlobalUpdateFailAtMs < QMD_UPDATE_BACKOFF_MS) {
4306
+ log.debug("QMD update: suppressed by global failure backoff");
4307
+ return;
4308
+ }
4279
4309
  }
4280
4310
  try {
4281
4311
  if (!globalState.warnedGlobalUpdateBehavior) {
@@ -4285,21 +4315,31 @@ ${stderr}`.trim();
4285
4315
  );
4286
4316
  }
4287
4317
  const startedAtMs = Date.now();
4288
- await runQmd(["update", "-c", this.collection], this.updateTimeoutMs, this.qmdPath);
4318
+ await runQmd(["update", "-c", name], this.updateTimeoutMs, this.qmdPath);
4289
4319
  const durationMs = Date.now() - startedAtMs;
4290
4320
  if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
4291
4321
  log.warn(`SLOW QMD update: durationMs=${durationMs}`);
4292
4322
  }
4293
- const now = Date.now();
4294
- this.lastUpdateRunAtMs = now;
4295
- globalState.lastGlobalUpdateRunAtMs = now;
4296
- log.debug("QMD update completed");
4323
+ const at = Date.now();
4324
+ if (options.perCollectionThrottle) {
4325
+ globalState.lastUpdateByCollectionMs[name] = at;
4326
+ globalState.lastGlobalUpdateRunAtMs = at;
4327
+ } else {
4328
+ this.lastUpdateRunAtMs = at;
4329
+ globalState.lastGlobalUpdateRunAtMs = at;
4330
+ }
4331
+ log.debug(`QMD update completed for collection=${name}`);
4297
4332
  } catch (err) {
4298
- const now = Date.now();
4299
- this.lastUpdateFailAtMs = now;
4300
- globalState.lastGlobalUpdateFailAtMs = now;
4333
+ const at = Date.now();
4334
+ if (options.perCollectionThrottle) {
4335
+ globalState.lastUpdateFailByCollectionMs[name] = at;
4336
+ globalState.lastGlobalUpdateFailAtMs = at;
4337
+ } else {
4338
+ this.lastUpdateFailAtMs = at;
4339
+ globalState.lastGlobalUpdateFailAtMs = at;
4340
+ }
4301
4341
  const msg = err instanceof Error ? err.message : String(err);
4302
- log.warn(`QMD update failed: ${msg}`);
4342
+ log.warn(`QMD update failed for collection ${name}: ${msg}`);
4303
4343
  }
4304
4344
  }
4305
4345
  async embed() {
@@ -4334,6 +4374,39 @@ ${stderr}`.trim();
4334
4374
  log.warn(`QMD embed failed: ${msg}`);
4335
4375
  }
4336
4376
  }
4377
+ async embedCollection(collection) {
4378
+ if (this.available === false) return;
4379
+ const name = collection.trim();
4380
+ if (!name) return;
4381
+ const globalState = getGlobalQmdState();
4382
+ const now = Date.now();
4383
+ if (globalState.lastGlobalEmbedFailAtMs && now - globalState.lastGlobalEmbedFailAtMs < QMD_EMBED_BACKOFF_MS) {
4384
+ log.debug(`QMD embed: suppressed by global failure backoff (${name})`);
4385
+ return;
4386
+ }
4387
+ const lastCollectionRun = globalState.lastEmbedByCollectionMs[name];
4388
+ if (Number.isFinite(lastCollectionRun) && now - lastCollectionRun < this.updateMinIntervalMs) {
4389
+ log.debug(`QMD embed: suppressed by per-collection min-interval gate (${name})`);
4390
+ return;
4391
+ }
4392
+ const lastCollectionFail = globalState.lastEmbedFailByCollectionMs[name];
4393
+ if (Number.isFinite(lastCollectionFail) && now - lastCollectionFail < QMD_EMBED_BACKOFF_MS) {
4394
+ log.debug(`QMD embed: suppressed by per-collection failure backoff (${name})`);
4395
+ return;
4396
+ }
4397
+ try {
4398
+ await runQmd(["embed", "-c", name], 3e5, this.qmdPath);
4399
+ const at = Date.now();
4400
+ globalState.lastEmbedByCollectionMs[name] = at;
4401
+ globalState.lastGlobalEmbedRunAtMs = at;
4402
+ } catch (err) {
4403
+ const at = Date.now();
4404
+ globalState.lastEmbedFailByCollectionMs[name] = at;
4405
+ globalState.lastGlobalEmbedFailAtMs = at;
4406
+ const msg = err instanceof Error ? err.message : String(err);
4407
+ log.warn(`QMD embed failed for collection ${name}: ${msg}`);
4408
+ }
4409
+ }
4337
4410
  async ensureCollection(memoryDir) {
4338
4411
  if (this.available === false && !this.daemonAvailable) return "unknown";
4339
4412
  if (this.available === false) return "skipped";
@@ -4364,7 +4437,7 @@ ${stderr}`.trim();
4364
4437
  };
4365
4438
 
4366
4439
  // src/storage.ts
4367
- import { readdir, readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, unlink, appendFile } from "fs/promises";
4440
+ import { readdir, readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, unlink, rename, appendFile } from "fs/promises";
4368
4441
  import { appendFileSync, mkdirSync as mkdirSync2, statSync } from "fs";
4369
4442
  import { createHash } from "crypto";
4370
4443
  import path4 from "path";
@@ -5579,6 +5652,86 @@ ${sanitized.text}
5579
5652
  return null;
5580
5653
  }
5581
5654
  }
5655
+ resolveTierRootDir(tier) {
5656
+ return tier === "cold" ? path4.join(this.baseDir, "cold") : this.baseDir;
5657
+ }
5658
+ resolveMemoryDateDir(memory) {
5659
+ const preferred = memory.frontmatter.created || memory.frontmatter.updated;
5660
+ const dateToken = (preferred ?? "").slice(0, 10);
5661
+ return /^\d{4}-\d{2}-\d{2}$/.test(dateToken) ? dateToken : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5662
+ }
5663
+ isArtifactMemory(memory) {
5664
+ if (memory.frontmatter.source === "artifact") return true;
5665
+ if (memory.frontmatter.artifactType !== void 0) return true;
5666
+ return /[\\/]artifacts[\\/]/.test(memory.path);
5667
+ }
5668
+ buildTierMemoryPath(memory, tier) {
5669
+ const root = this.resolveTierRootDir(tier);
5670
+ if (this.isArtifactMemory(memory)) {
5671
+ return path4.join(root, "artifacts", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
5672
+ }
5673
+ if (memory.frontmatter.category === "correction") {
5674
+ return path4.join(root, "corrections", `${memory.frontmatter.id}.md`);
5675
+ }
5676
+ return path4.join(root, "facts", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
5677
+ }
5678
+ async writeMemoryFileAtomic(targetPath, memory) {
5679
+ const fileContent = `${serializeFrontmatter(memory.frontmatter)}
5680
+
5681
+ ${memory.content}
5682
+ `;
5683
+ await mkdir2(path4.dirname(targetPath), { recursive: true });
5684
+ const tempPath = `${targetPath}.tmp-${process.pid}-${Date.now()}`;
5685
+ try {
5686
+ await writeFile2(tempPath, fileContent, "utf-8");
5687
+ await rename(tempPath, targetPath);
5688
+ } catch (err) {
5689
+ try {
5690
+ await unlink(tempPath);
5691
+ } catch {
5692
+ }
5693
+ throw err;
5694
+ }
5695
+ }
5696
+ async moveMemoryToPath(memory, targetPath) {
5697
+ await this.writeMemoryFileAtomic(targetPath, memory);
5698
+ const sourcePath = path4.resolve(memory.path);
5699
+ const destPath = path4.resolve(targetPath);
5700
+ if (sourcePath !== destPath) {
5701
+ try {
5702
+ await unlink(memory.path);
5703
+ } catch (err) {
5704
+ const message = err instanceof Error ? err.message : String(err);
5705
+ if (!message.includes("ENOENT")) {
5706
+ throw err;
5707
+ }
5708
+ }
5709
+ }
5710
+ }
5711
+ async migrateMemoryToTier(memory, targetTier) {
5712
+ const targetPath = this.buildTierMemoryPath(memory, targetTier);
5713
+ const sourcePath = path4.resolve(memory.path);
5714
+ const destPath = path4.resolve(targetPath);
5715
+ if (sourcePath === destPath) {
5716
+ return { changed: false, targetPath };
5717
+ }
5718
+ const existing = await this.readMemoryByPath(targetPath);
5719
+ if (existing?.frontmatter.id === memory.frontmatter.id) {
5720
+ try {
5721
+ await unlink(memory.path);
5722
+ } catch (err) {
5723
+ const message = err instanceof Error ? err.message : String(err);
5724
+ if (!message.includes("ENOENT")) {
5725
+ throw err;
5726
+ }
5727
+ }
5728
+ this.bumpMemoryStatusVersion();
5729
+ return { changed: false, targetPath };
5730
+ }
5731
+ await this.moveMemoryToPath(memory, targetPath);
5732
+ this.bumpMemoryStatusVersion();
5733
+ return { changed: true, targetPath };
5734
+ }
5582
5735
  get archiveDir() {
5583
5736
  return path4.join(this.baseDir, "archive");
5584
5737
  }
@@ -10614,16 +10767,22 @@ function resolveLifecycleState(frontmatter) {
10614
10767
  function computeHeat(memory, now, signals) {
10615
10768
  const frontmatter = memory.frontmatter;
10616
10769
  if (frontmatter.status === "archived") return 0;
10617
- const nowMs = now.getTime();
10618
- const confidence = confidenceTierWeight(frontmatter);
10619
- const access4 = accessWeight(frontmatter.accessCount);
10620
- const recency = recencyWeight(frontmatter, nowMs);
10621
- const importance = clamp01(frontmatter.importance?.score ?? 0.5);
10622
- const feedback = feedbackWeight(signals);
10623
- const disputedPenalty = frontmatter.verificationState === "disputed" ? 0.2 : 0;
10624
- const score = confidence * 0.25 + access4 * 0.3 + recency * 0.2 + importance * 0.15 + feedback * 0.1 - disputedPenalty;
10770
+ const inputs = computeLifecycleValueInputs(memory, now, signals);
10771
+ const score = inputs.confidence * 0.25 + inputs.access * 0.3 + inputs.recency * 0.2 + inputs.importance * 0.15 + inputs.feedback * 0.1 - inputs.disputedPenalty;
10625
10772
  return clamp01(score);
10626
10773
  }
10774
+ function computeLifecycleValueInputs(memory, now, signals) {
10775
+ const frontmatter = memory.frontmatter;
10776
+ const nowMs = now.getTime();
10777
+ return {
10778
+ confidence: confidenceTierWeight(frontmatter),
10779
+ access: accessWeight(frontmatter.accessCount),
10780
+ recency: recencyWeight(frontmatter, nowMs),
10781
+ importance: clamp01(frontmatter.importance?.score ?? 0.5),
10782
+ feedback: feedbackWeight(signals),
10783
+ disputedPenalty: frontmatter.verificationState === "disputed" ? 0.2 : 0
10784
+ };
10785
+ }
10627
10786
  function computeDecay(memory, now, signals) {
10628
10787
  const frontmatter = memory.frontmatter;
10629
10788
  if (frontmatter.status === "archived") return 1;