@remnic/core 9.3.743 → 9.3.744

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.
@@ -512,19 +512,19 @@ import {
512
512
  } from "./chunk-PVGDJXVK.js";
513
513
 
514
514
  // src/orchestrator.ts
515
- import path6 from "path";
515
+ import path7 from "path";
516
516
  import os2 from "os";
517
- import { createHash as createHash2, randomBytes } from "crypto";
517
+ import { createHash as createHash3, randomBytes } from "crypto";
518
518
  import { existsSync, readFileSync } from "fs";
519
519
  import {
520
520
  lstat,
521
- mkdir as mkdir3,
521
+ mkdir as mkdir4,
522
522
  readdir as readdir2,
523
523
  readFile as readFile2,
524
524
  realpath,
525
525
  stat,
526
526
  unlink,
527
- writeFile as writeFile3
527
+ writeFile as writeFile4
528
528
  } from "fs/promises";
529
529
 
530
530
  // src/procedural/procedure-recall.ts
@@ -2196,7 +2196,7 @@ var RecallRerankCoordinator = class _RecallRerankCoordinator {
2196
2196
  nsMap = buildMemoryWorthCounterMap(memories);
2197
2197
  this.memoryWorthCounterCache.set(ns, { at: nowMs, counters: nsMap });
2198
2198
  }
2199
- for (const [path7, c] of nsMap) counters.set(path7, c);
2199
+ for (const [path8, c] of nsMap) counters.set(path8, c);
2200
2200
  } catch (err) {
2201
2201
  log.debug("memory-worth: failed to read namespace, skipping", {
2202
2202
  namespace: ns,
@@ -2281,7 +2281,7 @@ var RecallRerankCoordinator = class _RecallRerankCoordinator {
2281
2281
  namespaces,
2282
2282
  {
2283
2283
  readNamespaceMemories: async (ns) => (await this.getStorage(ns)).readAllMemories(),
2284
- readMemoryFrontmatter: async (path7) => {
2284
+ readMemoryFrontmatter: async (path8) => {
2285
2285
  if (!fallbackReader) {
2286
2286
  for (const ns of namespaces) {
2287
2287
  try {
@@ -2292,7 +2292,7 @@ var RecallRerankCoordinator = class _RecallRerankCoordinator {
2292
2292
  }
2293
2293
  }
2294
2294
  if (!fallbackReader) return null;
2295
- const memory = await this.readQmdResultMemory(path7, fallbackReader, namespaces);
2295
+ const memory = await this.readQmdResultMemory(path8, fallbackReader, namespaces);
2296
2296
  return memory ? memory.frontmatter : null;
2297
2297
  }
2298
2298
  },
@@ -3016,6 +3016,295 @@ var ContradictionLinkingCoordinator = class {
3016
3016
  }
3017
3017
  };
3018
3018
 
3019
+ // src/orchestration/graph-recall-coordinator.ts
3020
+ import { createHash as createHash2 } from "crypto";
3021
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
3022
+ import path5 from "path";
3023
+ function mergeGraphExpandedResults(primary, expanded) {
3024
+ const mergedByPath = /* @__PURE__ */ new Map();
3025
+ for (const item of [...primary, ...expanded]) {
3026
+ const prev = mergedByPath.get(item.path);
3027
+ if (!prev) {
3028
+ mergedByPath.set(item.path, item);
3029
+ continue;
3030
+ }
3031
+ const better = item.score > prev.score ? item : prev;
3032
+ const snippet = prev.snippet || item.snippet;
3033
+ mergedByPath.set(item.path, { ...better, snippet });
3034
+ }
3035
+ return Array.from(mergedByPath.values());
3036
+ }
3037
+ function graphPathRelativeToStorage(storageDir, candidatePath) {
3038
+ const absolutePath = path5.isAbsolute(candidatePath) ? candidatePath : path5.resolve(storageDir, candidatePath);
3039
+ const rel = path5.relative(storageDir, absolutePath);
3040
+ if (!rel || rel === ".") return null;
3041
+ if (rel.startsWith("..")) return null;
3042
+ return rel.split(path5.sep).join("/");
3043
+ }
3044
+ function normalizeGraphActivationScore(score) {
3045
+ const bounded = Number.isFinite(score) && score > 0 ? score : 0;
3046
+ return bounded / (1 + bounded);
3047
+ }
3048
+ function blendGraphExpandedRecallScore(options) {
3049
+ const graphNorm = normalizeGraphActivationScore(options.graphActivationScore);
3050
+ const seedScore = Number.isFinite(options.seedRecallScore) ? Math.min(1, Math.max(0, options.seedRecallScore)) : 0;
3051
+ const weight = Math.min(1, Math.max(0, options.activationWeight));
3052
+ const rawMin = Math.min(1, Math.max(0, options.blendMin));
3053
+ const rawMax = Math.min(1, Math.max(0, options.blendMax));
3054
+ const minBound = Math.min(rawMin, rawMax);
3055
+ const maxBound = Math.max(rawMin, rawMax);
3056
+ const blended = graphNorm * weight + seedScore * (1 - weight);
3057
+ return Math.max(minBound, Math.min(maxBound, blended));
3058
+ }
3059
+ var GraphRecallCoordinator = class {
3060
+ getConfig;
3061
+ getStorage;
3062
+ storageFor;
3063
+ graphIndexFor;
3064
+ namespaceFromPath;
3065
+ resolveColdQmdResultForRecall;
3066
+ storageForAbsoluteQmdResultPath;
3067
+ readQmdResultMemory;
3068
+ constructor(options) {
3069
+ this.getConfig = options.getConfig;
3070
+ this.getStorage = options.getStorage;
3071
+ this.storageFor = options.storageFor;
3072
+ this.graphIndexFor = options.graphIndexFor;
3073
+ this.namespaceFromPath = options.namespaceFromPath;
3074
+ this.resolveColdQmdResultForRecall = options.resolveColdQmdResultForRecall;
3075
+ this.storageForAbsoluteQmdResultPath = options.storageForAbsoluteQmdResultPath;
3076
+ this.readQmdResultMemory = options.readQmdResultMemory;
3077
+ }
3078
+ async expandResultsViaGraph(options) {
3079
+ const config = this.getConfig();
3080
+ const deadlineExpired = () => typeof options.deadlineAtMs === "number" && Date.now() >= options.deadlineAtMs;
3081
+ const byNamespace = /* @__PURE__ */ new Map();
3082
+ const addResultForNamespace = (namespace, result) => {
3083
+ const existing = byNamespace.get(namespace);
3084
+ if (existing) {
3085
+ existing.push(result);
3086
+ } else {
3087
+ byNamespace.set(namespace, [result]);
3088
+ }
3089
+ };
3090
+ const resolvedAmbiguousSeeds = /* @__PURE__ */ new Map();
3091
+ const resolveAmbiguousSeedOwner = async (result, parts) => {
3092
+ const cached = resolvedAmbiguousSeeds.get(result.path);
3093
+ if (cached !== void 0) return cached;
3094
+ if (deadlineExpired()) {
3095
+ resolvedAmbiguousSeeds.set(result.path, null);
3096
+ return null;
3097
+ }
3098
+ let resolvedPath = result.path;
3099
+ let resolvedResult = result;
3100
+ if (parts) {
3101
+ const resolvedCold = await this.resolveColdQmdResultForRecall(
3102
+ result,
3103
+ this.getStorage(),
3104
+ options.recallNamespaces
3105
+ );
3106
+ if (!resolvedCold || deadlineExpired()) {
3107
+ resolvedAmbiguousSeeds.set(result.path, null);
3108
+ return null;
3109
+ }
3110
+ resolvedPath = resolvedCold.result.path;
3111
+ resolvedResult = resolvedCold.result;
3112
+ }
3113
+ if (!path5.isAbsolute(resolvedPath)) {
3114
+ resolvedAmbiguousSeeds.set(result.path, null);
3115
+ return null;
3116
+ }
3117
+ const ownerStorage = await this.storageForAbsoluteQmdResultPath(
3118
+ resolvedPath,
3119
+ this.getStorage(),
3120
+ options.recallNamespaces
3121
+ );
3122
+ const ownerNamespace = ownerStorage?.namespace ?? null;
3123
+ const resolved = ownerNamespace && options.recallNamespaces.includes(ownerNamespace) ? { namespace: ownerNamespace, result: resolvedResult } : null;
3124
+ resolvedAmbiguousSeeds.set(result.path, resolved);
3125
+ return resolved;
3126
+ };
3127
+ const coldCollection = config.qmdColdCollection ?? "openclaw-engram-cold";
3128
+ for (const result of options.memoryResults) {
3129
+ if (deadlineExpired()) break;
3130
+ const parts = qmdCollectionPathParts(result.path);
3131
+ if (parts?.collection === coldCollection) {
3132
+ const resolved = await resolveAmbiguousSeedOwner(result, parts);
3133
+ if (resolved) {
3134
+ addResultForNamespace(resolved.namespace, resolved.result);
3135
+ }
3136
+ continue;
3137
+ }
3138
+ if (path5.isAbsolute(result.path)) {
3139
+ const resolved = await resolveAmbiguousSeedOwner(result, null);
3140
+ if (resolved) {
3141
+ addResultForNamespace(resolved.namespace, resolved.result);
3142
+ }
3143
+ continue;
3144
+ }
3145
+ const ns = this.namespaceFromPath(result.path);
3146
+ if (!options.recallNamespaces.includes(ns)) continue;
3147
+ addResultForNamespace(ns, result);
3148
+ }
3149
+ const perNamespaceSeedCap = Math.max(3, options.recallResultLimit);
3150
+ const perNamespaceExpandedCap = Math.max(8, options.recallResultLimit * 2);
3151
+ const seedPaths = [];
3152
+ const seedResults = [];
3153
+ const expandedPaths = [];
3154
+ const expandedResults = [];
3155
+ for (const [namespace, nsResults] of byNamespace.entries()) {
3156
+ if (deadlineExpired()) break;
3157
+ const storage = await this.storageFor(namespace);
3158
+ const seedCandidates = nsResults.slice(0, perNamespaceSeedCap);
3159
+ seedResults.push(...seedCandidates);
3160
+ const seedRelativePaths = typeof options.deadlineAtMs === "number" ? await this.graphSeedPathsWithinDeadline(
3161
+ storage,
3162
+ seedCandidates,
3163
+ options.deadlineAtMs,
3164
+ [namespace]
3165
+ ) : (await Promise.all(
3166
+ seedCandidates.map(
3167
+ (result) => this.graphSeedPathRelativeToStorage(storage, result, [
3168
+ namespace
3169
+ ])
3170
+ )
3171
+ )).filter(
3172
+ (value) => typeof value === "string" && value.length > 0
3173
+ );
3174
+ if (deadlineExpired()) break;
3175
+ if (seedRelativePaths.length === 0) continue;
3176
+ const seedRecallScore = seedCandidates.reduce(
3177
+ (max, item) => Math.max(max, item.score),
3178
+ 0
3179
+ );
3180
+ seedPaths.push(
3181
+ ...seedRelativePaths.map((rel) => path5.join(storage.dir, rel))
3182
+ );
3183
+ const seedSet = new Set(seedRelativePaths);
3184
+ const expanded = await this.graphIndexFor(storage).spreadingActivation(
3185
+ seedRelativePaths,
3186
+ config.maxGraphTraversalSteps,
3187
+ {
3188
+ ...options.includeLowConfidence === true ? { includeLowConfidence: true } : {},
3189
+ ...typeof options.deadlineAtMs === "number" ? { deadlineAtMs: options.deadlineAtMs } : {}
3190
+ }
3191
+ );
3192
+ if (expanded.length === 0) continue;
3193
+ if (deadlineExpired()) break;
3194
+ for (const candidate of expanded.slice(0, perNamespaceExpandedCap)) {
3195
+ if (deadlineExpired()) break;
3196
+ if (seedSet.has(candidate.path)) continue;
3197
+ const memoryPath = path5.resolve(storage.dir, candidate.path);
3198
+ const memory = await storage.readMemoryByPath(memoryPath);
3199
+ if (deadlineExpired()) break;
3200
+ if (!memory) continue;
3201
+ if (/(?:^|[\\/])artifacts(?:[\\/]|$)/i.test(memory.path)) continue;
3202
+ if (memory.frontmatter.status && memory.frontmatter.status !== "active")
3203
+ continue;
3204
+ const snippet = memory.content.slice(0, 400);
3205
+ const score = blendGraphExpandedRecallScore({
3206
+ graphActivationScore: candidate.score,
3207
+ seedRecallScore,
3208
+ activationWeight: config.graphExpansionActivationWeight,
3209
+ blendMin: config.graphExpansionBlendMin,
3210
+ blendMax: config.graphExpansionBlendMax
3211
+ });
3212
+ expandedResults.push({
3213
+ docid: memory.frontmatter.id,
3214
+ path: memory.path,
3215
+ snippet,
3216
+ score
3217
+ });
3218
+ expandedPaths.push({
3219
+ path: memory.path,
3220
+ score,
3221
+ namespace,
3222
+ seed: path5.resolve(storage.dir, candidate.seed),
3223
+ hopDepth: candidate.hopDepth,
3224
+ decayedWeight: candidate.decayedWeight,
3225
+ graphType: candidate.graphType,
3226
+ // Issue #681 PR 3/3 — surface the per-edge confidence used for
3227
+ // PageRank weighting / floor pruning so downstream observability
3228
+ // (recall_xray, memory_graph_explain) can attribute ranking and
3229
+ // pruning decisions to specific edges.
3230
+ edgeConfidence: candidate.edgeConfidence
3231
+ });
3232
+ }
3233
+ }
3234
+ return {
3235
+ merged: mergeGraphExpandedResults(options.memoryResults, expandedResults),
3236
+ seedPaths,
3237
+ expandedPaths,
3238
+ seedResults
3239
+ };
3240
+ }
3241
+ async recordLastGraphRecallSnapshot(options) {
3242
+ try {
3243
+ const snapshotPath = path5.join(
3244
+ options.storage.dir,
3245
+ "state",
3246
+ "last_graph_recall.json"
3247
+ );
3248
+ await mkdir2(path5.dirname(snapshotPath), { recursive: true });
3249
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3250
+ const totalSeedCount = options.seedPaths.length;
3251
+ const totalExpandedCount = options.expandedPaths.length;
3252
+ const seeds = options.seedPaths.slice(0, 64);
3253
+ const expanded = clampGraphRecallExpandedEntries(
3254
+ options.expandedPaths,
3255
+ 64
3256
+ );
3257
+ const payload = {
3258
+ recordedAt: now,
3259
+ mode: options.recallMode,
3260
+ queryHash: createHash2("sha256").update(options.prompt).digest("hex"),
3261
+ queryLength: options.prompt.length,
3262
+ namespaces: options.recallNamespaces,
3263
+ seedCount: totalSeedCount,
3264
+ expandedCount: totalExpandedCount,
3265
+ seeds,
3266
+ expanded,
3267
+ status: options.status,
3268
+ reason: options.reason,
3269
+ shadowMode: options.shadowMode === true,
3270
+ queryIntent: options.queryIntent,
3271
+ seedResults: (options.seedResults ?? []).slice(0, 64),
3272
+ finalResults: (options.finalResults ?? []).slice(0, 64),
3273
+ shadowComparison: options.shadowComparison
3274
+ };
3275
+ await writeFile2(snapshotPath, JSON.stringify(payload, null, 2), "utf-8");
3276
+ } catch (err) {
3277
+ log.debug(`last graph recall write failed: ${err}`);
3278
+ }
3279
+ }
3280
+ async graphSeedPathRelativeToStorage(storage, result, recallNamespaces = []) {
3281
+ const parts = qmdCollectionPathParts(result.path);
3282
+ if (parts) {
3283
+ const memory = await this.readQmdResultMemory(
3284
+ result.path,
3285
+ storage,
3286
+ recallNamespaces
3287
+ );
3288
+ return memory ? graphPathRelativeToStorage(storage.dir, memory.path) : null;
3289
+ }
3290
+ return graphPathRelativeToStorage(storage.dir, result.path);
3291
+ }
3292
+ async graphSeedPathsWithinDeadline(storage, results, deadlineAtMs, recallNamespaces = []) {
3293
+ const resolved = [];
3294
+ for (const result of results) {
3295
+ if (Date.now() >= deadlineAtMs) break;
3296
+ const seedPath = await this.graphSeedPathRelativeToStorage(
3297
+ storage,
3298
+ result,
3299
+ recallNamespaces
3300
+ );
3301
+ if (Date.now() >= deadlineAtMs) break;
3302
+ if (seedPath) resolved.push(seedPath);
3303
+ }
3304
+ return resolved;
3305
+ }
3306
+ };
3307
+
3019
3308
  // src/maintenance/pattern-reinforcement.ts
3020
3309
  function patternReinforcementKey(content) {
3021
3310
  return content.trim().toLowerCase().replace(/\s+/g, " ").slice(0, 200);
@@ -3348,8 +3637,8 @@ function generateResolverDocument(taxonomy) {
3348
3637
  }
3349
3638
 
3350
3639
  // src/taxonomy/taxonomy-loader.ts
3351
- import { readFile, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
3352
- import path5 from "path";
3640
+ import { readFile, mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
3641
+ import path6 from "path";
3353
3642
  var TAXONOMY_DIR = ".taxonomy";
3354
3643
  var TAXONOMY_FILE = "taxonomy.json";
3355
3644
  var MAX_SLUG_LENGTH = 32;
@@ -3447,7 +3736,7 @@ function validateTaxonomy(taxonomy) {
3447
3736
  }
3448
3737
  }
3449
3738
  async function loadTaxonomy(memoryDir) {
3450
- const taxonomyPath = path5.join(memoryDir, TAXONOMY_DIR, TAXONOMY_FILE);
3739
+ const taxonomyPath = path6.join(memoryDir, TAXONOMY_DIR, TAXONOMY_FILE);
3451
3740
  let raw;
3452
3741
  try {
3453
3742
  raw = await readFile(taxonomyPath, "utf-8");
@@ -3497,16 +3786,16 @@ async function loadTaxonomy(memoryDir) {
3497
3786
  }
3498
3787
  async function saveTaxonomy(memoryDir, taxonomy) {
3499
3788
  validateTaxonomy(taxonomy);
3500
- const dir = path5.join(memoryDir, TAXONOMY_DIR);
3501
- await mkdir2(dir, { recursive: true });
3502
- const filePath = path5.join(dir, TAXONOMY_FILE);
3503
- await writeFile2(filePath, JSON.stringify(taxonomy, null, 2) + "\n", "utf-8");
3789
+ const dir = path6.join(memoryDir, TAXONOMY_DIR);
3790
+ await mkdir3(dir, { recursive: true });
3791
+ const filePath = path6.join(dir, TAXONOMY_FILE);
3792
+ await writeFile3(filePath, JSON.stringify(taxonomy, null, 2) + "\n", "utf-8");
3504
3793
  }
3505
3794
  function getTaxonomyDir(memoryDir) {
3506
- return path5.join(memoryDir, TAXONOMY_DIR);
3795
+ return path6.join(memoryDir, TAXONOMY_DIR);
3507
3796
  }
3508
3797
  function getTaxonomyFilePath(memoryDir) {
3509
- return path5.join(memoryDir, TAXONOMY_DIR, TAXONOMY_FILE);
3798
+ return path6.join(memoryDir, TAXONOMY_DIR, TAXONOMY_FILE);
3510
3799
  }
3511
3800
 
3512
3801
  // src/wearables/registry.ts
@@ -4516,11 +4805,11 @@ async function qmdStartupCollectionCheckWithTimeout(promise, controller, label)
4516
4805
  return await Promise.race([checkedPromise, timeoutPromise]);
4517
4806
  }
4518
4807
  function defaultWorkspaceDir() {
4519
- return path6.join(os2.homedir(), ".openclaw", "workspace");
4808
+ return path7.join(os2.homedir(), ".openclaw", "workspace");
4520
4809
  }
4521
4810
  function sanitizeSessionKeyForFilename(sessionKey) {
4522
4811
  const readable = sessionKey.replace(/[^a-zA-Z0-9._-]/g, "_");
4523
- const hash = createHash2("sha256").update(sessionKey).digest("hex").slice(0, 12);
4812
+ const hash = createHash3("sha256").update(sessionKey).digest("hex").slice(0, 12);
4524
4813
  return `${readable}-${hash}`;
4525
4814
  }
4526
4815
  function latestSourceValidAtFromTurns(turns) {
@@ -4778,42 +5067,6 @@ function computeQmdHybridFetchLimit(recallFetchLimit, artifactsEnabled, maxArtif
4778
5067
  const artifactHeadroom = Math.max(20, Math.max(0, maxArtifactRecall) * 8);
4779
5068
  return Math.min(400, cappedRecallLimit + artifactHeadroom);
4780
5069
  }
4781
- function mergeGraphExpandedResults(primary, expanded) {
4782
- const mergedByPath = /* @__PURE__ */ new Map();
4783
- for (const item of [...primary, ...expanded]) {
4784
- const prev = mergedByPath.get(item.path);
4785
- if (!prev) {
4786
- mergedByPath.set(item.path, item);
4787
- continue;
4788
- }
4789
- const better = item.score > prev.score ? item : prev;
4790
- const snippet = prev.snippet || item.snippet;
4791
- mergedByPath.set(item.path, { ...better, snippet });
4792
- }
4793
- return Array.from(mergedByPath.values());
4794
- }
4795
- function graphPathRelativeToStorage(storageDir, candidatePath) {
4796
- const absolutePath = path6.isAbsolute(candidatePath) ? candidatePath : path6.resolve(storageDir, candidatePath);
4797
- const rel = path6.relative(storageDir, absolutePath);
4798
- if (!rel || rel === ".") return null;
4799
- if (rel.startsWith("..")) return null;
4800
- return rel.split(path6.sep).join("/");
4801
- }
4802
- function normalizeGraphActivationScore(score) {
4803
- const bounded = Number.isFinite(score) && score > 0 ? score : 0;
4804
- return bounded / (1 + bounded);
4805
- }
4806
- function blendGraphExpandedRecallScore(options) {
4807
- const graphNorm = normalizeGraphActivationScore(options.graphActivationScore);
4808
- const seedScore = Number.isFinite(options.seedRecallScore) ? Math.min(1, Math.max(0, options.seedRecallScore)) : 0;
4809
- const weight = Math.min(1, Math.max(0, options.activationWeight));
4810
- const rawMin = Math.min(1, Math.max(0, options.blendMin));
4811
- const rawMax = Math.min(1, Math.max(0, options.blendMax));
4812
- const minBound = Math.min(rawMin, rawMax);
4813
- const maxBound = Math.max(rawMin, rawMax);
4814
- const blended = graphNorm * weight + seedScore * (1 - weight);
4815
- return Math.max(minBound, Math.min(maxBound, blended));
4816
- }
4817
5070
  function summarizeGraphShadowComparison(baseline, merged, topN) {
4818
5071
  const limit = Math.max(0, Math.floor(topN));
4819
5072
  const baselineTop = limit > 0 ? baseline.slice(0, limit) : [];
@@ -4942,7 +5195,7 @@ function buildMemoryPathById(allMemsForGraph, storageDir) {
4942
5195
  for (const mem of allMemsForGraph ?? []) {
4943
5196
  const id = mem.frontmatter.id;
4944
5197
  if (!id) continue;
4945
- pathById.set(id, path6.relative(storageDir, mem.path));
5198
+ pathById.set(id, path7.relative(storageDir, mem.path));
4946
5199
  }
4947
5200
  return pathById;
4948
5201
  }
@@ -4950,7 +5203,7 @@ function appendMemoryToGraphContext(options) {
4950
5203
  if (!Array.isArray(options.allMemsForGraph)) return;
4951
5204
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
4952
5205
  options.allMemsForGraph.push({
4953
- path: path6.join(options.storageDir, options.memoryRelPath),
5206
+ path: path7.join(options.storageDir, options.memoryRelPath),
4954
5207
  content: options.content,
4955
5208
  frontmatter: {
4956
5209
  id: options.memoryId,
@@ -4970,16 +5223,16 @@ function resolvePersistedMemoryRelativePath(options) {
4970
5223
  const persisted = options.pathById.get(options.memoryId);
4971
5224
  if (persisted) return persisted;
4972
5225
  if (options.category === "correction") {
4973
- return path6.join("corrections", `${options.memoryId}.md`);
5226
+ return path7.join("corrections", `${options.memoryId}.md`);
4974
5227
  }
4975
5228
  const subtree = categoryDirName(options.category);
4976
5229
  const idParts = options.memoryId.split("-");
4977
5230
  const maybeTimestamp = Number(idParts[1]);
4978
5231
  if (Number.isFinite(maybeTimestamp) && maybeTimestamp > 0) {
4979
5232
  const day = new Date(maybeTimestamp).toISOString().slice(0, 10);
4980
- return path6.join(subtree, day, `${options.memoryId}.md`);
5233
+ return path7.join(subtree, day, `${options.memoryId}.md`);
4981
5234
  }
4982
- return path6.join(subtree, `${options.memoryId}.md`);
5235
+ return path7.join(subtree, `${options.memoryId}.md`);
4983
5236
  }
4984
5237
  var Orchestrator = class _Orchestrator {
4985
5238
  storage;
@@ -5144,6 +5397,7 @@ var Orchestrator = class _Orchestrator {
5144
5397
  recallSectionCoordinator;
5145
5398
  qmdResultResolver;
5146
5399
  contradictionLinkingCoordinator;
5400
+ graphRecallCoordinator;
5147
5401
  heartbeatObserverChains = /* @__PURE__ */ new Map();
5148
5402
  recentExtractionFingerprints = /* @__PURE__ */ new Map();
5149
5403
  consolidationObservers = /* @__PURE__ */ new Set();
@@ -5436,7 +5690,7 @@ var Orchestrator = class _Orchestrator {
5436
5690
  const defaultNs = normalizeNamespaceIdentity(this.config.defaultNamespace);
5437
5691
  if (ns !== defaultNs && !isSafeRouteNamespace(ns)) return;
5438
5692
  if (!this.storageDirMatchesNamespaceHint(ns, storageDir)) return;
5439
- const resolvedStorageDir = path6.resolve(storageDir);
5693
+ const resolvedStorageDir = path7.resolve(storageDir);
5440
5694
  let hints = this.namespaceStorageDirHints.get(resolvedStorageDir);
5441
5695
  if (!hints) {
5442
5696
  hints = /* @__PURE__ */ new Set();
@@ -5447,21 +5701,21 @@ var Orchestrator = class _Orchestrator {
5447
5701
  storageDirMatchesNamespaceHint(namespace, storageDir) {
5448
5702
  const ns = normalizeNamespaceIdentity(namespace);
5449
5703
  if (!ns) return false;
5450
- const resolvedStorageDir = path6.resolve(storageDir);
5451
- const resolvedMemoryDir = path6.resolve(this.config.memoryDir);
5704
+ const resolvedStorageDir = path7.resolve(storageDir);
5705
+ const resolvedMemoryDir = path7.resolve(this.config.memoryDir);
5452
5706
  const defaultNs = normalizeNamespaceIdentity(this.config.defaultNamespace);
5453
5707
  if (resolvedStorageDir === resolvedMemoryDir) return ns === defaultNs;
5454
- const resolvedNamespacesDir = path6.join(resolvedMemoryDir, "namespaces");
5708
+ const resolvedNamespacesDir = path7.join(resolvedMemoryDir, "namespaces");
5455
5709
  if (!isPathInsideStorageRoot(resolvedNamespacesDir, resolvedStorageDir)) return false;
5456
- const rawRoot = path6.resolve(resolvedNamespacesDir, ns);
5457
- const tokenRoot = path6.resolve(resolvedNamespacesDir, namespaceIdentityToken(ns));
5710
+ const rawRoot = path7.resolve(resolvedNamespacesDir, ns);
5711
+ const tokenRoot = path7.resolve(resolvedNamespacesDir, namespaceIdentityToken(ns));
5458
5712
  return resolvedStorageDir === rawRoot || resolvedStorageDir === tokenRoot;
5459
5713
  }
5460
5714
  namespaceStorageDirHintOwnershipRank(record, resolvedStorageDir, configured) {
5461
- if (resolvedStorageDir === path6.resolve(this.config.memoryDir)) {
5715
+ if (resolvedStorageDir === path7.resolve(this.config.memoryDir)) {
5462
5716
  return record.namespace === normalizeNamespaceIdentity(this.config.defaultNamespace) ? 0 : 3;
5463
5717
  }
5464
- const leaf = path6.basename(resolvedStorageDir);
5718
+ const leaf = path7.basename(resolvedStorageDir);
5465
5719
  const tokenOwnsRoot = namespaceIdentityToken(record.namespace) === leaf;
5466
5720
  if (tokenOwnsRoot && configured.has(record.namespace)) return 0;
5467
5721
  if (record.namespace === leaf) return 1;
@@ -5489,7 +5743,7 @@ var Orchestrator = class _Orchestrator {
5489
5743
  loadNamespaceStorageDirHintsFromCatalog() {
5490
5744
  if (this.namespaceStorageDirHintsLoaded || !this.namespaceCatalog.enabled) return;
5491
5745
  this.namespaceStorageDirHintsLoaded = true;
5492
- const catalogPath = path6.join(this.config.memoryDir, "state", "namespaces.jsonl");
5746
+ const catalogPath = path7.join(this.config.memoryDir, "state", "namespaces.jsonl");
5493
5747
  if (!existsSync(catalogPath)) return;
5494
5748
  let body;
5495
5749
  try {
@@ -5526,7 +5780,7 @@ var Orchestrator = class _Orchestrator {
5526
5780
  if (!this.storageDirMatchesNamespaceHint(record.namespace, record.storageDir)) {
5527
5781
  continue;
5528
5782
  }
5529
- const resolvedStorageDir = path6.resolve(record.storageDir);
5783
+ const resolvedStorageDir = path7.resolve(record.storageDir);
5530
5784
  const current = preferredByStorageDir.get(resolvedStorageDir);
5531
5785
  preferredByStorageDir.set(
5532
5786
  resolvedStorageDir,
@@ -5790,7 +6044,7 @@ var Orchestrator = class _Orchestrator {
5790
6044
  this.config = config;
5791
6045
  this.profiler = new ProfilingCollector({
5792
6046
  enabled: resolvePipelineProcessingCapabilities(this.config).profiling,
5793
- storageDir: config.profilingStorageDir || path6.join(config.memoryDir, "profiling"),
6047
+ storageDir: config.profilingStorageDir || path7.join(config.memoryDir, "profiling"),
5794
6048
  maxTraces: config.profilingMaxTraces
5795
6049
  });
5796
6050
  this.namespaceCatalog = new NamespaceCatalog(config);
@@ -5860,7 +6114,7 @@ var Orchestrator = class _Orchestrator {
5860
6114
  this.compounding = resolveConsolidationCapabilities(config).compounding ? new CompoundingEngine(config, this.storage) : void 0;
5861
6115
  this.buffer = new SmartBuffer(config, this.storage);
5862
6116
  this.transcript = new TranscriptManager(config);
5863
- this.conversationIndexDir = path6.join(
6117
+ this.conversationIndexDir = path7.join(
5864
6118
  config.memoryDir,
5865
6119
  "conversation-index",
5866
6120
  "chunks"
@@ -5890,6 +6144,16 @@ var Orchestrator = class _Orchestrator {
5890
6144
  getExtraction: () => this.extraction
5891
6145
  });
5892
6146
  this.modelRegistry = new ModelRegistry(config.memoryDir);
6147
+ this.graphRecallCoordinator = new GraphRecallCoordinator({
6148
+ getConfig: () => this.config,
6149
+ getStorage: () => this.storage,
6150
+ storageFor: (namespace) => this.storageRouter.storageFor(namespace),
6151
+ graphIndexFor: (storage) => this.graphIndexFor(storage),
6152
+ namespaceFromPath: (p) => this.namespaceFromPath(p),
6153
+ resolveColdQmdResultForRecall: (result, fallbackStorage, recallNamespaces) => this.resolveColdQmdResultForRecall(result, fallbackStorage, recallNamespaces),
6154
+ storageForAbsoluteQmdResultPath: (resultPath, fallbackStorage, recallNamespaces) => this.storageForAbsoluteQmdResultPath(resultPath, fallbackStorage, recallNamespaces),
6155
+ readQmdResultMemory: (resultPath, fallbackStorage, recallNamespaces) => this.readQmdResultMemory(resultPath, fallbackStorage, recallNamespaces)
6156
+ });
5893
6157
  this.relevance = new RelevanceStore(config.memoryDir);
5894
6158
  this.negatives = new NegativeExampleStore(config.memoryDir);
5895
6159
  this.lastRecall = new LastRecallStore(config.memoryDir);
@@ -5986,7 +6250,7 @@ var Orchestrator = class _Orchestrator {
5986
6250
  saveContentHashIndexes: () => this.saveContentHashIndexes()
5987
6251
  });
5988
6252
  this.threading = new ThreadingManager(
5989
- path6.join(config.memoryDir, "threads"),
6253
+ path7.join(config.memoryDir, "threads"),
5990
6254
  config.threadingGapMinutes
5991
6255
  );
5992
6256
  const lifecycleCaps = resolveMemoryLifecycleCapabilities(config);
@@ -6066,7 +6330,7 @@ var Orchestrator = class _Orchestrator {
6066
6330
  utilityPromoteThresholdDelta: this.utilityRuntimeValues?.promoteThresholdDelta ?? 0,
6067
6331
  utilityDemoteThresholdDelta: this.utilityRuntimeValues?.demoteThresholdDelta ?? 0
6068
6332
  };
6069
- return createHash2("sha256").update(JSON.stringify(payload)).digest("hex").slice(0, 12);
6333
+ return createHash3("sha256").update(JSON.stringify(payload)).digest("hex").slice(0, 12);
6070
6334
  }
6071
6335
  effectiveLifecycleThresholds() {
6072
6336
  const archiveDecayThreshold = this.config.lifecycleArchiveDecayThreshold;
@@ -6309,7 +6573,7 @@ var Orchestrator = class _Orchestrator {
6309
6573
  const files = await readdir2(wsDir).catch(() => []);
6310
6574
  for (const f of files) {
6311
6575
  if (!f.startsWith(".compaction-reset-signal-")) continue;
6312
- const fp = path6.join(wsDir, f);
6576
+ const fp = path7.join(wsDir, f);
6313
6577
  const s = await stat(fp).catch(() => null);
6314
6578
  if (s && Date.now() - s.mtimeMs >= COMPACTION_SIGNAL_MAX_AGE_MS) {
6315
6579
  await unlink(fp).catch(() => {
@@ -6818,15 +7082,15 @@ ${doc.content}` : doc.content,
6818
7082
  this.lastFileHygieneRunAtMs = now;
6819
7083
  if (hygiene.rotateEnabled) {
6820
7084
  for (const rel of hygiene.rotatePaths) {
6821
- const abs = path6.isAbsolute(rel) ? rel : path6.join(this.config.workspaceDir, rel);
7085
+ const abs = path7.isAbsolute(rel) ? rel : path7.join(this.config.workspaceDir, rel);
6822
7086
  try {
6823
7087
  const raw = await readFile2(abs, "utf-8");
6824
7088
  if (raw.length > hygiene.rotateMaxBytes) {
6825
- const archiveDir = path6.join(
7089
+ const archiveDir = path7.join(
6826
7090
  this.config.workspaceDir,
6827
7091
  hygiene.archiveDir
6828
7092
  );
6829
- const base = path6.basename(abs);
7093
+ const base = path7.basename(abs);
6830
7094
  const prefix = base.toUpperCase().replace(/\.MD$/i, "").replace(/[^A-Z0-9]+/g, "-") || "FILE";
6831
7095
  const { newContent } = await rotateMarkdownFileToArchive({
6832
7096
  filePath: abs,
@@ -6834,7 +7098,7 @@ ${doc.content}` : doc.content,
6834
7098
  archivePrefix: prefix,
6835
7099
  keepTailChars: hygiene.rotateKeepTailChars
6836
7100
  });
6837
- await writeFile3(abs, newContent, "utf-8");
7101
+ await writeFile4(abs, newContent, "utf-8");
6838
7102
  }
6839
7103
  } catch {
6840
7104
  }
@@ -6851,8 +7115,8 @@ ${doc.content}` : doc.content,
6851
7115
  log.warn(w.message);
6852
7116
  }
6853
7117
  if (hygiene.warningsLogEnabled && warnings.length > 0) {
6854
- const fp = path6.join(this.config.memoryDir, hygiene.warningsLogPath);
6855
- await mkdir3(path6.dirname(fp), { recursive: true });
7118
+ const fp = path7.join(this.config.memoryDir, hygiene.warningsLogPath);
7119
+ await mkdir4(path7.dirname(fp), { recursive: true });
6856
7120
  const stamp = (/* @__PURE__ */ new Date()).toISOString();
6857
7121
  const block = `
6858
7122
 
@@ -6865,7 +7129,7 @@ ${doc.content}` : doc.content,
6865
7129
  } catch {
6866
7130
  existing = "# Engram File Hygiene Warnings\n";
6867
7131
  }
6868
- await writeFile3(fp, existing + block, "utf-8");
7132
+ await writeFile4(fp, existing + block, "utf-8");
6869
7133
  }
6870
7134
  }
6871
7135
  }
@@ -6991,7 +7255,7 @@ ${doc.content}` : doc.content,
6991
7255
  for (const categoryDir of RECALL_FALLBACK_DIRS) {
6992
7256
  if (memoryRootReal === null) break;
6993
7257
  for (const date of datesToScan) {
6994
- const dateDir = path6.join(storage.dir, categoryDir, date);
7258
+ const dateDir = path7.join(storage.dir, categoryDir, date);
6995
7259
  try {
6996
7260
  const dirStat = await lstat(dateDir);
6997
7261
  if (dirStat.isSymbolicLink() || !dirStat.isDirectory()) continue;
@@ -7000,7 +7264,7 @@ ${doc.content}` : doc.content,
7000
7264
  for (const entry of entries) {
7001
7265
  if (entry.isSymbolicLink()) continue;
7002
7266
  if (!entry.name.endsWith(".md")) continue;
7003
- const fullPath = path6.join(dateDir, entry.name);
7267
+ const fullPath = path7.join(dateDir, entry.name);
7004
7268
  try {
7005
7269
  assertPathInsideRoot(memoryRootReal, await realpath(fullPath), fullPath);
7006
7270
  const raw = await readFile2(fullPath, "utf-8");
@@ -7022,7 +7286,7 @@ ${doc.content}` : doc.content,
7022
7286
  facts.push({
7023
7287
  path: fullPath,
7024
7288
  frontmatter: {
7025
- id: fm.id || path6.basename(entry.name, ".md"),
7289
+ id: fm.id || path7.basename(entry.name, ".md"),
7026
7290
  category: fm.category || "fact",
7027
7291
  created,
7028
7292
  updated: fm.updated || created,
@@ -7045,13 +7309,13 @@ ${doc.content}` : doc.content,
7045
7309
  return a.frontmatter.created < b.frontmatter.created ? -1 : 1;
7046
7310
  });
7047
7311
  const hourlySummaries = [];
7048
- const hourlyBaseDir = path6.join(storage.dir, "summaries", "hourly");
7312
+ const hourlyBaseDir = path7.join(storage.dir, "summaries", "hourly");
7049
7313
  try {
7050
7314
  const sessionKeys = await readdir2(hourlyBaseDir, { withFileTypes: true });
7051
7315
  for (const sk of sessionKeys) {
7052
7316
  if (!sk.isDirectory()) continue;
7053
7317
  for (const date of datesToScan) {
7054
- const summaryFile = path6.join(hourlyBaseDir, sk.name, `${date}.md`);
7318
+ const summaryFile = path7.join(hourlyBaseDir, sk.name, `${date}.md`);
7055
7319
  try {
7056
7320
  const raw = await readFile2(summaryFile, "utf-8");
7057
7321
  const filtered = filterHourlySummaryMarkdownForLocalDay(
@@ -7155,7 +7419,7 @@ ${doc.content}` : doc.content,
7155
7419
  }
7156
7420
  async getLastGraphRecallSnapshot(namespace) {
7157
7421
  const storage = await this.getStorage(namespace);
7158
- const snapshotPath = path6.join(
7422
+ const snapshotPath = path7.join(
7159
7423
  storage.dir,
7160
7424
  "state",
7161
7425
  "last_graph_recall.json"
@@ -7194,7 +7458,7 @@ ${doc.content}` : doc.content,
7194
7458
  }
7195
7459
  async getLastIntentSnapshot(namespace) {
7196
7460
  const storage = await this.getStorage(namespace);
7197
- const snapshotPath = path6.join(storage.dir, "state", "last_intent.json");
7461
+ const snapshotPath = path7.join(storage.dir, "state", "last_intent.json");
7198
7462
  try {
7199
7463
  const raw = await readFile2(snapshotPath, "utf-8");
7200
7464
  const parsed = JSON.parse(raw);
@@ -7227,7 +7491,7 @@ ${doc.content}` : doc.content,
7227
7491
  }
7228
7492
  async getLastQmdRecallSnapshot(namespace) {
7229
7493
  const storage = await this.getStorage(namespace);
7230
- const snapshotPath = path6.join(
7494
+ const snapshotPath = path7.join(
7231
7495
  storage.dir,
7232
7496
  "state",
7233
7497
  "last_qmd_recall.json"
@@ -7929,7 +8193,7 @@ ${doc.content}` : doc.content,
7929
8193
  if (!options.onDebugSnapshot) return;
7930
8194
  await options.onDebugSnapshot({
7931
8195
  recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
7932
- queryHash: createHash2("sha256").update(prompt).digest("hex"),
8196
+ queryHash: createHash3("sha256").update(prompt).digest("hex"),
7933
8197
  queryLength: prompt.length,
7934
8198
  collection: options.collection,
7935
8199
  namespaces: options.recallNamespaces,
@@ -8077,216 +8341,26 @@ ${doc.content}` : doc.content,
8077
8341
  await emitDebugSnapshot(capped, fetchLimit);
8078
8342
  return capped;
8079
8343
  }
8344
+ // Issue #1526 (seam 14): graph-recall expansion moved to
8345
+ // GraphRecallCoordinator. Thin delegation keeps the private API stable
8346
+ // for callers (recallInternal, cold-fallback pipeline) + tests.
8080
8347
  async expandResultsViaGraph(options) {
8081
- const deadlineExpired = () => typeof options.deadlineAtMs === "number" && Date.now() >= options.deadlineAtMs;
8082
- const byNamespace = /* @__PURE__ */ new Map();
8083
- const addResultForNamespace = (namespace, result) => {
8084
- const existing = byNamespace.get(namespace);
8085
- if (existing) {
8086
- existing.push(result);
8087
- } else {
8088
- byNamespace.set(namespace, [result]);
8089
- }
8090
- };
8091
- const resolvedAmbiguousSeeds = /* @__PURE__ */ new Map();
8092
- const resolveAmbiguousSeedOwner = async (result, parts) => {
8093
- const cached = resolvedAmbiguousSeeds.get(result.path);
8094
- if (cached !== void 0) return cached;
8095
- if (deadlineExpired()) {
8096
- resolvedAmbiguousSeeds.set(result.path, null);
8097
- return null;
8098
- }
8099
- let resolvedPath = result.path;
8100
- let resolvedResult = result;
8101
- if (parts) {
8102
- const resolvedCold = await this.resolveColdQmdResultForRecall(
8103
- result,
8104
- this.storage,
8105
- options.recallNamespaces
8106
- );
8107
- if (!resolvedCold || deadlineExpired()) {
8108
- resolvedAmbiguousSeeds.set(result.path, null);
8109
- return null;
8110
- }
8111
- resolvedPath = resolvedCold.result.path;
8112
- resolvedResult = resolvedCold.result;
8113
- }
8114
- if (!path6.isAbsolute(resolvedPath)) {
8115
- resolvedAmbiguousSeeds.set(result.path, null);
8116
- return null;
8117
- }
8118
- const ownerStorage = await this.storageForAbsoluteQmdResultPath(
8119
- resolvedPath,
8120
- this.storage,
8121
- options.recallNamespaces
8122
- );
8123
- const ownerNamespace = ownerStorage?.namespace ?? null;
8124
- const resolved = ownerNamespace && options.recallNamespaces.includes(ownerNamespace) ? { namespace: ownerNamespace, result: resolvedResult } : null;
8125
- resolvedAmbiguousSeeds.set(result.path, resolved);
8126
- return resolved;
8127
- };
8128
- const coldCollection = this.config.qmdColdCollection ?? "openclaw-engram-cold";
8129
- for (const result of options.memoryResults) {
8130
- if (deadlineExpired()) break;
8131
- const parts = qmdCollectionPathParts(result.path);
8132
- if (parts?.collection === coldCollection) {
8133
- const resolved = await resolveAmbiguousSeedOwner(result, parts);
8134
- if (resolved) {
8135
- addResultForNamespace(resolved.namespace, resolved.result);
8136
- }
8137
- continue;
8138
- }
8139
- if (path6.isAbsolute(result.path)) {
8140
- const resolved = await resolveAmbiguousSeedOwner(result, null);
8141
- if (resolved) {
8142
- addResultForNamespace(resolved.namespace, resolved.result);
8143
- }
8144
- continue;
8145
- }
8146
- const ns = this.namespaceFromPath(result.path);
8147
- if (!options.recallNamespaces.includes(ns)) continue;
8148
- addResultForNamespace(ns, result);
8149
- }
8150
- const perNamespaceSeedCap = Math.max(3, options.recallResultLimit);
8151
- const perNamespaceExpandedCap = Math.max(8, options.recallResultLimit * 2);
8152
- const seedPaths = [];
8153
- const seedResults = [];
8154
- const expandedPaths = [];
8155
- const expandedResults = [];
8156
- for (const [namespace, nsResults] of byNamespace.entries()) {
8157
- if (deadlineExpired()) break;
8158
- const storage = await this.storageRouter.storageFor(namespace);
8159
- const seedCandidates = nsResults.slice(0, perNamespaceSeedCap);
8160
- seedResults.push(...seedCandidates);
8161
- const seedRelativePaths = typeof options.deadlineAtMs === "number" ? await this.graphSeedPathsWithinDeadline(
8162
- storage,
8163
- seedCandidates,
8164
- options.deadlineAtMs,
8165
- [namespace]
8166
- ) : (await Promise.all(
8167
- seedCandidates.map(
8168
- (result) => this.graphSeedPathRelativeToStorage(storage, result, [
8169
- namespace
8170
- ])
8171
- )
8172
- )).filter(
8173
- (value) => typeof value === "string" && value.length > 0
8174
- );
8175
- if (deadlineExpired()) break;
8176
- if (seedRelativePaths.length === 0) continue;
8177
- const seedRecallScore = seedCandidates.reduce(
8178
- (max, item) => Math.max(max, item.score),
8179
- 0
8180
- );
8181
- seedPaths.push(
8182
- ...seedRelativePaths.map((rel) => path6.join(storage.dir, rel))
8183
- );
8184
- const seedSet = new Set(seedRelativePaths);
8185
- const expanded = await this.graphIndexFor(storage).spreadingActivation(
8186
- seedRelativePaths,
8187
- this.config.maxGraphTraversalSteps,
8188
- {
8189
- ...options.includeLowConfidence === true ? { includeLowConfidence: true } : {},
8190
- ...typeof options.deadlineAtMs === "number" ? { deadlineAtMs: options.deadlineAtMs } : {}
8191
- }
8192
- );
8193
- if (expanded.length === 0) continue;
8194
- if (deadlineExpired()) break;
8195
- for (const candidate of expanded.slice(0, perNamespaceExpandedCap)) {
8196
- if (deadlineExpired()) break;
8197
- if (seedSet.has(candidate.path)) continue;
8198
- const memoryPath = path6.resolve(storage.dir, candidate.path);
8199
- const memory = await storage.readMemoryByPath(memoryPath);
8200
- if (deadlineExpired()) break;
8201
- if (!memory) continue;
8202
- if (isArtifactMemoryPath(memory.path)) continue;
8203
- if (memory.frontmatter.status && memory.frontmatter.status !== "active")
8204
- continue;
8205
- const snippet = memory.content.slice(0, 400);
8206
- const score = blendGraphExpandedRecallScore({
8207
- graphActivationScore: candidate.score,
8208
- seedRecallScore,
8209
- activationWeight: this.config.graphExpansionActivationWeight,
8210
- blendMin: this.config.graphExpansionBlendMin,
8211
- blendMax: this.config.graphExpansionBlendMax
8212
- });
8213
- expandedResults.push({
8214
- docid: memory.frontmatter.id,
8215
- path: memory.path,
8216
- snippet,
8217
- score
8218
- });
8219
- expandedPaths.push({
8220
- path: memory.path,
8221
- score,
8222
- namespace,
8223
- seed: path6.resolve(storage.dir, candidate.seed),
8224
- hopDepth: candidate.hopDepth,
8225
- decayedWeight: candidate.decayedWeight,
8226
- graphType: candidate.graphType,
8227
- // Issue #681 PR 3/3 — surface the per-edge confidence used for
8228
- // PageRank weighting / floor pruning so downstream observability
8229
- // (recall_xray, memory_graph_explain) can attribute ranking and
8230
- // pruning decisions to specific edges.
8231
- edgeConfidence: candidate.edgeConfidence
8232
- });
8233
- }
8234
- }
8235
- return {
8236
- merged: mergeGraphExpandedResults(options.memoryResults, expandedResults),
8237
- seedPaths,
8238
- expandedPaths,
8239
- seedResults
8240
- };
8348
+ return this.graphRecallCoordinator.expandResultsViaGraph(options);
8241
8349
  }
8350
+ // Issue #1526 (seam 14): graph-recall snapshot moved to
8351
+ // GraphRecallCoordinator. Thin delegation keeps the private API stable.
8242
8352
  async recordLastGraphRecallSnapshot(options) {
8243
- try {
8244
- const snapshotPath = path6.join(
8245
- options.storage.dir,
8246
- "state",
8247
- "last_graph_recall.json"
8248
- );
8249
- await mkdir3(path6.dirname(snapshotPath), { recursive: true });
8250
- const now = (/* @__PURE__ */ new Date()).toISOString();
8251
- const totalSeedCount = options.seedPaths.length;
8252
- const totalExpandedCount = options.expandedPaths.length;
8253
- const seeds = options.seedPaths.slice(0, 64);
8254
- const expanded = clampGraphRecallExpandedEntries(
8255
- options.expandedPaths,
8256
- 64
8257
- );
8258
- const payload = {
8259
- recordedAt: now,
8260
- mode: options.recallMode,
8261
- queryHash: createHash2("sha256").update(options.prompt).digest("hex"),
8262
- queryLength: options.prompt.length,
8263
- namespaces: options.recallNamespaces,
8264
- seedCount: totalSeedCount,
8265
- expandedCount: totalExpandedCount,
8266
- seeds,
8267
- expanded,
8268
- status: options.status,
8269
- reason: options.reason,
8270
- shadowMode: options.shadowMode === true,
8271
- queryIntent: options.queryIntent,
8272
- seedResults: (options.seedResults ?? []).slice(0, 64),
8273
- finalResults: (options.finalResults ?? []).slice(0, 64),
8274
- shadowComparison: options.shadowComparison
8275
- };
8276
- await writeFile3(snapshotPath, JSON.stringify(payload, null, 2), "utf-8");
8277
- } catch (err) {
8278
- log.debug(`last graph recall write failed: ${err}`);
8279
- }
8353
+ return this.graphRecallCoordinator.recordLastGraphRecallSnapshot(options);
8280
8354
  }
8281
8355
  async recordLastIntentSnapshot(options) {
8282
8356
  try {
8283
- const snapshotPath = path6.join(
8357
+ const snapshotPath = path7.join(
8284
8358
  options.storage.dir,
8285
8359
  "state",
8286
8360
  "last_intent.json"
8287
8361
  );
8288
- await mkdir3(path6.dirname(snapshotPath), { recursive: true });
8289
- await writeFile3(
8362
+ await mkdir4(path7.dirname(snapshotPath), { recursive: true });
8363
+ await writeFile4(
8290
8364
  snapshotPath,
8291
8365
  JSON.stringify(options.snapshot, null, 2),
8292
8366
  "utf-8"
@@ -8297,13 +8371,13 @@ ${doc.content}` : doc.content,
8297
8371
  }
8298
8372
  async recordLastQmdRecallSnapshot(options) {
8299
8373
  try {
8300
- const snapshotPath = path6.join(
8374
+ const snapshotPath = path7.join(
8301
8375
  options.storage.dir,
8302
8376
  "state",
8303
8377
  "last_qmd_recall.json"
8304
8378
  );
8305
- await mkdir3(path6.dirname(snapshotPath), { recursive: true });
8306
- await writeFile3(
8379
+ await mkdir4(path7.dirname(snapshotPath), { recursive: true });
8380
+ await writeFile4(
8307
8381
  snapshotPath,
8308
8382
  JSON.stringify(options.snapshot, null, 2),
8309
8383
  "utf-8"
@@ -8317,9 +8391,9 @@ ${doc.content}` : doc.content,
8317
8391
  const stateDir = await this.resolveStateDirForNamespace(
8318
8392
  options.namespace
8319
8393
  );
8320
- const snapshotPath = path6.join(stateDir, "last_intent.json");
8321
- await mkdir3(path6.dirname(snapshotPath), { recursive: true });
8322
- await writeFile3(
8394
+ const snapshotPath = path7.join(stateDir, "last_intent.json");
8395
+ await mkdir4(path7.dirname(snapshotPath), { recursive: true });
8396
+ await writeFile4(
8323
8397
  snapshotPath,
8324
8398
  JSON.stringify(options.snapshot, null, 2),
8325
8399
  "utf-8"
@@ -8330,12 +8404,12 @@ ${doc.content}` : doc.content,
8330
8404
  }
8331
8405
  async resolveStateDirForNamespace(namespace) {
8332
8406
  if (!resolveNamespaceCapabilities(this.config).namespaces) {
8333
- return path6.join(this.config.memoryDir, "state");
8407
+ return path7.join(this.config.memoryDir, "state");
8334
8408
  }
8335
8409
  if (namespace !== this.config.defaultNamespace) {
8336
- return path6.join(this.config.memoryDir, "namespaces", namespace, "state");
8410
+ return path7.join(this.config.memoryDir, "namespaces", namespace, "state");
8337
8411
  }
8338
- const candidate = path6.join(
8412
+ const candidate = path7.join(
8339
8413
  this.config.memoryDir,
8340
8414
  "namespaces",
8341
8415
  this.config.defaultNamespace
@@ -8343,11 +8417,11 @@ ${doc.content}` : doc.content,
8343
8417
  try {
8344
8418
  const candidateStat = await stat(candidate);
8345
8419
  if (candidateStat.isDirectory()) {
8346
- return path6.join(candidate, "state");
8420
+ return path7.join(candidate, "state");
8347
8421
  }
8348
8422
  } catch {
8349
8423
  }
8350
- return path6.join(this.config.memoryDir, "state");
8424
+ return path7.join(this.config.memoryDir, "state");
8351
8425
  }
8352
8426
  buildGraphRecallRankedResults(results, sourceLabelResolver, limit = 64) {
8353
8427
  return results.slice(0, limit).map((result) => ({
@@ -8478,8 +8552,8 @@ ${doc.content}` : doc.content,
8478
8552
  timings,
8479
8553
  logger: log
8480
8554
  });
8481
- const promptHash = createHash2("sha256").update(prompt).digest("hex");
8482
- const traceId = createHash2("sha256").update(`${sessionKey ?? "default"}:${recallStart}:${promptHash}`).digest("hex").slice(0, 16);
8555
+ const promptHash = createHash3("sha256").update(prompt).digest("hex");
8556
+ const traceId = createHash3("sha256").update(`${sessionKey ?? "default"}:${recallStart}:${promptHash}`).digest("hex").slice(0, 16);
8483
8557
  const sectionBuckets = /* @__PURE__ */ new Map();
8484
8558
  const queryPolicy = buildRecallQueryPolicy(prompt, sessionKey, {
8485
8559
  cronRecallPolicyEnabled: resolveRecallAuxiliaryCapabilities(this.config).cronRecallPolicy,
@@ -8488,7 +8562,7 @@ ${doc.content}` : doc.content,
8488
8562
  cronConversationRecallMode: this.config.cronConversationRecallMode
8489
8563
  });
8490
8564
  const retrievalQuery = queryPolicy.retrievalQuery || prompt;
8491
- const retrievalQueryHash = createHash2("sha256").update(retrievalQuery).digest("hex");
8565
+ const retrievalQueryHash = createHash3("sha256").update(retrievalQuery).digest("hex");
8492
8566
  const policyVersion = this.currentPolicyVersion();
8493
8567
  let impressionRecorded = false;
8494
8568
  let recallSource = "none";
@@ -8627,7 +8701,7 @@ ${doc.content}` : doc.content,
8627
8701
  const graphExpandedResultPaths = /* @__PURE__ */ new Set();
8628
8702
  const graphSourceLabelsForPath = (resultPath) => {
8629
8703
  const labels = [];
8630
- const normalizedPath = resultPath.split(path6.sep).join("/");
8704
+ const normalizedPath = resultPath.split(path7.sep).join("/");
8631
8705
  const isEntityPath = normalizedPath.startsWith("entities/") || normalizedPath.includes("/entities/");
8632
8706
  if (graphBaselinePaths.has(resultPath)) labels.push("baseline");
8633
8707
  if (graphExpandedResultPaths.has(resultPath))
@@ -8777,7 +8851,7 @@ ${doc.content}` : doc.content,
8777
8851
  profileStorageNamespaces.map((namespace) => this.storageRouter.storageFor(namespace))
8778
8852
  );
8779
8853
  const emptyProfileStorage = new Proxy(
8780
- { dir: path6.join(this.config.memoryDir, ".empty-scope-profile") },
8854
+ { dir: path7.join(this.config.memoryDir, ".empty-scope-profile") },
8781
8855
  {
8782
8856
  get(target, prop) {
8783
8857
  if (prop in target) return target[prop];
@@ -10253,11 +10327,11 @@ ${formatted}`;
10253
10327
  if (!resolveRecallAuxiliaryCapabilities(this.config).compactionReset) return null;
10254
10328
  const workspaceDir = compactionWorkspaceDir || this.config.workspaceDir || defaultWorkspaceDir();
10255
10329
  const safeSessionKey = sanitizeSessionKeyForFilename(effectiveSessionKey);
10256
- const signalPath = path6.join(
10330
+ const signalPath = path7.join(
10257
10331
  workspaceDir,
10258
10332
  `.compaction-reset-signal-${safeSessionKey}`
10259
10333
  );
10260
- const bootPath = path6.join(workspaceDir, "BOOT.md");
10334
+ const bootPath = path7.join(workspaceDir, "BOOT.md");
10261
10335
  try {
10262
10336
  const signalStat = await stat(signalPath).catch(() => null);
10263
10337
  if (!signalStat) return null;
@@ -12528,7 +12602,7 @@ _Context: ${topQuestion.context}_`
12528
12602
  const shouldUseStableBatchKey = turns.some(
12529
12603
  (turn) => turn.persistProcessedFingerprint === true || typeof turn.turnFingerprint === "string" && turn.turnFingerprint.length > 0
12530
12604
  );
12531
- const stableBatchFingerprint = shouldUseStableBatchKey ? createHash2("sha256").update(
12605
+ const stableBatchFingerprint = shouldUseStableBatchKey ? createHash3("sha256").update(
12532
12606
  turns.map(
12533
12607
  (turn) => [
12534
12608
  turn.role,
@@ -12778,7 +12852,7 @@ _Context: ${topQuestion.context}_`
12778
12852
  buildExtractionFingerprint(turns, bufferKey) {
12779
12853
  const normalized = this.normalizeExtractionFingerprintTurns(turns).join("\n");
12780
12854
  if (!normalized) return null;
12781
- return createHash2("sha256").update(`${bufferKey}
12855
+ return createHash3("sha256").update(`${bufferKey}
12782
12856
  ${normalized}`).digest("hex");
12783
12857
  }
12784
12858
  shouldQueueExtraction(turns, options = {}) {
@@ -14859,7 +14933,7 @@ ${normalized}`).digest("hex");
14859
14933
  const allMems = allMemsForGraph ?? [];
14860
14934
  for (const m of allMems) {
14861
14935
  if (m.frontmatter.entityRef === entityRef) {
14862
- const rel = path6.relative(storage.dir, m.path);
14936
+ const rel = path7.relative(storage.dir, m.path);
14863
14937
  if (rel !== memoryRelPath) entitySiblings.push(rel);
14864
14938
  }
14865
14939
  }
@@ -15183,7 +15257,7 @@ ${normalized}`).digest("hex");
15183
15257
  }
15184
15258
  if (resolveConsolidationCapabilities(this.config).semanticConsolidation) {
15185
15259
  try {
15186
- const stateFilePath = path6.join(
15260
+ const stateFilePath = path7.join(
15187
15261
  this.config.memoryDir,
15188
15262
  "state",
15189
15263
  "semantic-consolidation-last-run.json"
@@ -15231,9 +15305,9 @@ ${normalized}`).digest("hex");
15231
15305
  );
15232
15306
  }
15233
15307
  if (semResult.errors === 0 || semResult.memoriesArchived > 0) {
15234
- const stateDir = path6.join(this.config.memoryDir, "state");
15235
- await mkdir3(stateDir, { recursive: true });
15236
- await writeFile3(
15308
+ const stateDir = path7.join(this.config.memoryDir, "state");
15309
+ await mkdir4(stateDir, { recursive: true });
15310
+ await writeFile4(
15237
15311
  stateFilePath,
15238
15312
  JSON.stringify({ lastRunAt: (/* @__PURE__ */ new Date()).toISOString() }),
15239
15313
  "utf-8"
@@ -15627,12 +15701,12 @@ ${reflectionsContent.trim()}
15627
15701
  */
15628
15702
  semanticDedupScopeFor(targetStorage) {
15629
15703
  if (!resolveNamespaceCapabilities(this.config).namespaces) return {};
15630
- const memoryDir = path6.resolve(this.config.memoryDir);
15631
- const storageDir = path6.resolve(targetStorage.dir);
15704
+ const memoryDir = path7.resolve(this.config.memoryDir);
15705
+ const storageDir = path7.resolve(targetStorage.dir);
15632
15706
  if (storageDir === memoryDir) {
15633
15707
  return { pathExcludePrefixes: ["namespaces/"] };
15634
15708
  }
15635
- let rel = path6.relative(memoryDir, storageDir);
15709
+ let rel = path7.relative(memoryDir, storageDir);
15636
15710
  if (!rel || rel.startsWith("..")) {
15637
15711
  log.debug(
15638
15712
  `semantic dedup: target storage dir ${storageDir} is outside memoryDir ${memoryDir}; scoping lookup to absolute path prefix`
@@ -15651,7 +15725,7 @@ ${reflectionsContent.trim()}
15651
15725
  if (hits.length === 0) return [];
15652
15726
  const results = [];
15653
15727
  for (const hit of hits) {
15654
- const fullPath = path6.isAbsolute(hit.path) ? hit.path : path6.join(this.config.memoryDir, hit.path);
15728
+ const fullPath = path7.isAbsolute(hit.path) ? hit.path : path7.join(this.config.memoryDir, hit.path);
15655
15729
  const memory = await this.storage.readMemoryByPath(fullPath);
15656
15730
  if (!memory) continue;
15657
15731
  results.push({
@@ -15848,7 +15922,7 @@ ${reflectionsContent.trim()}
15848
15922
  const storage = await this.storageRouter.storageFor(namespace);
15849
15923
  const storageDir = typeof storage.dir === "string" && storage.dir ? storage.dir : null;
15850
15924
  if (!storageDir) continue;
15851
- const recallRoot = path6.resolve(storageDir);
15925
+ const recallRoot = path7.resolve(storageDir);
15852
15926
  if (seenRecallRoots.has(recallRoot)) continue;
15853
15927
  seenRecallRoots.add(recallRoot);
15854
15928
  recallRoots.push(recallRoot);
@@ -15872,8 +15946,8 @@ ${reflectionsContent.trim()}
15872
15946
  if (resolvedCold) scopedResults.push(resolvedCold.result);
15873
15947
  continue;
15874
15948
  }
15875
- if (path6.isAbsolute(result.path)) {
15876
- const resolvedPath = path6.resolve(result.path);
15949
+ if (path7.isAbsolute(result.path)) {
15950
+ const resolvedPath = path7.resolve(result.path);
15877
15951
  if (recallRoots.some(
15878
15952
  (recallRoot) => isPathInsideStorageRoot(recallRoot, resolvedPath)
15879
15953
  )) {
@@ -16574,32 +16648,6 @@ ${reflectionsContent.trim()}
16574
16648
  namespaceScope
16575
16649
  );
16576
16650
  }
16577
- async graphSeedPathRelativeToStorage(storage, result, recallNamespaces = []) {
16578
- const parts = qmdCollectionPathParts(result.path);
16579
- if (parts) {
16580
- const memory = await this.readQmdResultMemory(
16581
- result.path,
16582
- storage,
16583
- recallNamespaces
16584
- );
16585
- return memory ? graphPathRelativeToStorage(storage.dir, memory.path) : null;
16586
- }
16587
- return graphPathRelativeToStorage(storage.dir, result.path);
16588
- }
16589
- async graphSeedPathsWithinDeadline(storage, results, deadlineAtMs, recallNamespaces = []) {
16590
- const resolved = [];
16591
- for (const result of results) {
16592
- if (Date.now() >= deadlineAtMs) break;
16593
- const seedPath = await this.graphSeedPathRelativeToStorage(
16594
- storage,
16595
- result,
16596
- recallNamespaces
16597
- );
16598
- if (Date.now() >= deadlineAtMs) break;
16599
- if (seedPath) resolved.push(seedPath);
16600
- }
16601
- return resolved;
16602
- }
16603
16651
  namespaceFromPath(p) {
16604
16652
  if (!resolveNamespaceCapabilities(this.config).namespaces) return this.config.defaultNamespace;
16605
16653
  const parts = qmdCollectionPathParts(p);
@@ -16659,6 +16707,9 @@ export {
16659
16707
  buildProcedureRecallSection,
16660
16708
  hasIdentityRecoveryIntent,
16661
16709
  resolveEffectiveIdentityInjectionMode,
16710
+ mergeGraphExpandedResults,
16711
+ graphPathRelativeToStorage,
16712
+ blendGraphExpandedRecallScore,
16662
16713
  decideSemanticDedup,
16663
16714
  DEFAULT_TAXONOMY,
16664
16715
  generateResolverDocument,
@@ -16690,9 +16741,6 @@ export {
16690
16741
  resolveRecallModeDecisionAsync,
16691
16742
  computeArtifactCandidateFetchLimit,
16692
16743
  computeQmdHybridFetchLimit,
16693
- mergeGraphExpandedResults,
16694
- graphPathRelativeToStorage,
16695
- blendGraphExpandedRecallScore,
16696
16744
  summarizeGraphShadowComparison,
16697
16745
  mergeArtifactRecallCandidates,
16698
16746
  resolveRecentThreadMemoryPaths,
@@ -16701,4 +16749,4 @@ export {
16701
16749
  resolvePersistedMemoryRelativePath,
16702
16750
  Orchestrator
16703
16751
  };
16704
- //# sourceMappingURL=chunk-VO4VARZL.js.map
16752
+ //# sourceMappingURL=chunk-SODQQCAZ.js.map