@joshuaswarren/openclaw-engram 8.3.0 → 8.3.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/dist/index.js CHANGED
@@ -339,6 +339,7 @@ function parseConfig(raw) {
339
339
  queryAwareIndexingMaxCandidates: typeof cfg.queryAwareIndexingMaxCandidates === "number" ? Math.max(0, cfg.queryAwareIndexingMaxCandidates) : 200,
340
340
  // v8.2: Multi-graph memory (PR 18)
341
341
  multiGraphMemoryEnabled: cfg.multiGraphMemoryEnabled === true,
342
+ graphRecallEnabled: cfg.graphRecallEnabled === true,
342
343
  entityGraphEnabled: cfg.entityGraphEnabled !== false,
343
344
  timeGraphEnabled: cfg.timeGraphEnabled !== false,
344
345
  causalGraphEnabled: cfg.causalGraphEnabled !== false,
@@ -9729,6 +9730,13 @@ function computeArtifactRecallLimit(recallMode, recallResultLimit, verbatimArtif
9729
9730
  }
9730
9731
  return base;
9731
9732
  }
9733
+ function resolveEffectiveRecallMode(options) {
9734
+ const plannedMode = options.plannerEnabled ? planRecallMode(options.prompt) : "full";
9735
+ if (plannedMode === "graph_mode" && (!options.graphRecallEnabled || !options.multiGraphMemoryEnabled)) {
9736
+ return "full";
9737
+ }
9738
+ return plannedMode;
9739
+ }
9732
9740
  function computeArtifactCandidateFetchLimit(targetCount) {
9733
9741
  const cappedTarget = Math.max(0, targetCount);
9734
9742
  if (cappedTarget === 0) return 0;
@@ -9742,6 +9750,32 @@ function computeQmdHybridFetchLimit(recallFetchLimit, artifactsEnabled, maxArtif
9742
9750
  const artifactHeadroom = Math.max(20, Math.max(0, maxArtifactRecall) * 8);
9743
9751
  return Math.min(400, cappedRecallLimit + artifactHeadroom);
9744
9752
  }
9753
+ function mergeGraphExpandedResults(primary, expanded) {
9754
+ const mergedByPath = /* @__PURE__ */ new Map();
9755
+ for (const item of [...primary, ...expanded]) {
9756
+ const prev = mergedByPath.get(item.path);
9757
+ if (!prev) {
9758
+ mergedByPath.set(item.path, item);
9759
+ continue;
9760
+ }
9761
+ const better = item.score > prev.score ? item : prev;
9762
+ const snippet = prev.snippet || item.snippet;
9763
+ mergedByPath.set(item.path, { ...better, snippet });
9764
+ }
9765
+ return Array.from(mergedByPath.values());
9766
+ }
9767
+ function graphPathRelativeToStorage(storageDir, candidatePath) {
9768
+ const absolutePath = path22.isAbsolute(candidatePath) ? candidatePath : path22.resolve(storageDir, candidatePath);
9769
+ const rel = path22.relative(storageDir, absolutePath);
9770
+ if (!rel || rel === ".") return null;
9771
+ if (rel.startsWith("..")) return null;
9772
+ return rel.split(path22.sep).join("/");
9773
+ }
9774
+ function graphActivationScoreToRecallScore(score) {
9775
+ const bounded = Number.isFinite(score) && score > 0 ? score : 0;
9776
+ const normalized = bounded / (1 + bounded);
9777
+ return Math.max(0.05, Math.min(0.95, normalized));
9778
+ }
9745
9779
  function mergeArtifactRecallCandidates(candidatesByNamespace, limit) {
9746
9780
  const cappedLimit = Math.max(0, limit);
9747
9781
  if (cappedLimit === 0) return [];
@@ -10378,11 +10412,93 @@ var Orchestrator = class _Orchestrator {
10378
10412
  }
10379
10413
  return bestFiltered;
10380
10414
  }
10415
+ async expandResultsViaGraph(options) {
10416
+ const byNamespace = /* @__PURE__ */ new Map();
10417
+ for (const result of options.memoryResults) {
10418
+ const ns = this.namespaceFromPath(result.path);
10419
+ if (!options.recallNamespaces.includes(ns)) continue;
10420
+ const existing = byNamespace.get(ns);
10421
+ if (existing) {
10422
+ existing.push(result);
10423
+ } else {
10424
+ byNamespace.set(ns, [result]);
10425
+ }
10426
+ }
10427
+ const perNamespaceSeedCap = Math.max(3, options.recallResultLimit);
10428
+ const perNamespaceExpandedCap = Math.max(8, options.recallResultLimit * 2);
10429
+ const seedPaths = [];
10430
+ const expandedPaths = [];
10431
+ const expandedResults = [];
10432
+ for (const [namespace, nsResults] of byNamespace.entries()) {
10433
+ const storage = await this.storageRouter.storageFor(namespace);
10434
+ const seedRelativePaths = nsResults.slice(0, perNamespaceSeedCap).map((result) => graphPathRelativeToStorage(storage.dir, result.path)).filter((value) => typeof value === "string" && value.length > 0);
10435
+ if (seedRelativePaths.length === 0) continue;
10436
+ seedPaths.push(...seedRelativePaths.map((rel) => path22.join(storage.dir, rel)));
10437
+ const seedSet = new Set(seedRelativePaths);
10438
+ const expanded = await this.graphIndexFor(storage).spreadingActivation(
10439
+ seedRelativePaths,
10440
+ this.config.maxGraphTraversalSteps
10441
+ );
10442
+ if (expanded.length === 0) continue;
10443
+ for (const candidate of expanded.slice(0, perNamespaceExpandedCap)) {
10444
+ if (seedSet.has(candidate.path)) continue;
10445
+ const memoryPath = path22.resolve(storage.dir, candidate.path);
10446
+ const memory = await storage.readMemoryByPath(memoryPath);
10447
+ if (!memory) continue;
10448
+ if (isArtifactMemoryPath(memory.path)) continue;
10449
+ if (memory.frontmatter.status && memory.frontmatter.status !== "active") continue;
10450
+ const snippet = memory.content.slice(0, 400);
10451
+ const score = graphActivationScoreToRecallScore(candidate.score);
10452
+ expandedResults.push({
10453
+ docid: memory.frontmatter.id,
10454
+ path: memory.path,
10455
+ snippet,
10456
+ score
10457
+ });
10458
+ expandedPaths.push({
10459
+ path: memory.path,
10460
+ score,
10461
+ namespace
10462
+ });
10463
+ }
10464
+ }
10465
+ return {
10466
+ merged: mergeGraphExpandedResults(options.memoryResults, expandedResults),
10467
+ seedPaths,
10468
+ expandedPaths
10469
+ };
10470
+ }
10471
+ async recordLastGraphRecallSnapshot(options) {
10472
+ try {
10473
+ const snapshotPath = path22.join(options.storage.dir, "state", "last_graph_recall.json");
10474
+ await mkdir16(path22.dirname(snapshotPath), { recursive: true });
10475
+ const now = (/* @__PURE__ */ new Date()).toISOString();
10476
+ const payload = {
10477
+ recordedAt: now,
10478
+ mode: options.recallMode,
10479
+ queryHash: createHash4("sha256").update(options.prompt).digest("hex"),
10480
+ queryLength: options.prompt.length,
10481
+ namespaces: options.recallNamespaces,
10482
+ seedCount: options.seedPaths.length,
10483
+ expandedCount: options.expandedPaths.length,
10484
+ seeds: options.seedPaths,
10485
+ expanded: options.expandedPaths
10486
+ };
10487
+ await writeFile15(snapshotPath, JSON.stringify(payload, null, 2), "utf-8");
10488
+ } catch (err) {
10489
+ log.debug(`last graph recall write failed: ${err}`);
10490
+ }
10491
+ }
10381
10492
  async recallInternal(prompt, sessionKey) {
10382
10493
  const recallStart = Date.now();
10383
10494
  const timings = {};
10384
10495
  const sections = [];
10385
- const recallMode = this.config.recallPlannerEnabled ? planRecallMode(prompt) : "full";
10496
+ const recallMode = resolveEffectiveRecallMode({
10497
+ plannerEnabled: this.config.recallPlannerEnabled,
10498
+ graphRecallEnabled: this.config.graphRecallEnabled,
10499
+ multiGraphMemoryEnabled: this.config.multiGraphMemoryEnabled,
10500
+ prompt
10501
+ });
10386
10502
  timings.recallPlan = recallMode;
10387
10503
  const recallResultLimit = recallMode === "no_recall" ? 0 : recallMode === "minimal" ? Math.max(0, Math.min(this.config.qmdMaxResults, this.config.recallPlannerMaxQmdResultsMinimal)) : this.config.qmdMaxResults;
10388
10504
  const recallHeadroom = this.config.verbatimArtifactsEnabled ? Math.max(12, this.config.verbatimArtifactsMaxRecall * 4) : 12;
@@ -10530,20 +10646,7 @@ ${tmtNode.summary}`);
10530
10646
  if (qmdResult) {
10531
10647
  const t0 = Date.now();
10532
10648
  const { memoryResultsLists, globalResults } = qmdResult;
10533
- const mergedByPath = /* @__PURE__ */ new Map();
10534
- for (const list of memoryResultsLists) {
10535
- for (const r of list) {
10536
- const prev = mergedByPath.get(r.path);
10537
- if (!prev) {
10538
- mergedByPath.set(r.path, r);
10539
- continue;
10540
- }
10541
- const better = r.score > prev.score ? r : prev;
10542
- const snippet = prev.snippet || r.snippet;
10543
- mergedByPath.set(r.path, { ...better, snippet });
10544
- }
10545
- }
10546
- const memoryResultsRaw = Array.from(mergedByPath.values());
10649
+ const memoryResultsRaw = mergeGraphExpandedResults(memoryResultsLists.flat(), []);
10547
10650
  let memoryResults = memoryResultsRaw;
10548
10651
  if (this.config.namespacesEnabled) {
10549
10652
  memoryResults = memoryResults.filter(
@@ -10551,6 +10654,26 @@ ${tmtNode.summary}`);
10551
10654
  );
10552
10655
  }
10553
10656
  memoryResults = memoryResults.filter((r) => !isArtifactMemoryPath(r.path));
10657
+ if (recallMode === "graph_mode") {
10658
+ const {
10659
+ merged,
10660
+ seedPaths,
10661
+ expandedPaths
10662
+ } = await this.expandResultsViaGraph({
10663
+ memoryResults,
10664
+ recallNamespaces,
10665
+ recallResultLimit
10666
+ });
10667
+ memoryResults = merged;
10668
+ await this.recordLastGraphRecallSnapshot({
10669
+ storage: profileStorage,
10670
+ prompt,
10671
+ recallMode,
10672
+ recallNamespaces,
10673
+ seedPaths,
10674
+ expandedPaths
10675
+ });
10676
+ }
10554
10677
  memoryResults = await this.boostSearchResults(memoryResults, recallNamespaces, prompt);
10555
10678
  if (this.config.rerankEnabled && this.config.rerankProvider === "local") {
10556
10679
  const ranked = await rerankLocalOrNoop({