@remnic/core 9.3.738 → 9.3.739
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/access-boundary.d.ts +6 -6
- package/dist/access-cli.js +1 -1
- package/dist/access-http.d.ts +6 -6
- package/dist/access-mcp.d.ts +6 -6
- package/dist/access-operations.d.ts +6 -6
- package/dist/{access-service-DwQ6BIQw.d.ts → access-service-Cbk-Kv_A.d.ts} +1 -1
- package/dist/access-service.d.ts +6 -6
- package/dist/access-surface-catalog.d.ts +6 -6
- package/dist/bootstrap.d.ts +5 -5
- package/dist/{chunk-EKQ3LWBM.js → chunk-IEWUPZ75.js} +583 -511
- package/dist/chunk-IEWUPZ75.js.map +1 -0
- package/dist/{cli-BlEIWDQw.d.ts → cli-BQW2xKdb.d.ts} +2 -2
- package/dist/cli.d.ts +7 -7
- package/dist/explicit-capture.d.ts +5 -5
- package/dist/index.d.ts +8 -8
- package/dist/index.js +1 -1
- package/dist/mcp-memory-inspector-app.d.ts +6 -6
- package/dist/{orchestrator-CJZ21ZiB.d.ts → orchestrator-BOAFN_Yr.d.ts} +186 -56
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +1 -1
- package/dist/schemas.d.ts +22 -22
- package/dist/transfer/types.d.ts +12 -12
- package/package.json +2 -2
- package/src/orchestration/conversation-index-coordinator.ts +344 -0
- package/src/orchestration/recall-rerank-coordinator.ts +425 -0
- package/src/orchestrator.ts +39 -552
- package/dist/chunk-EKQ3LWBM.js.map +0 -1
|
@@ -509,14 +509,14 @@ import {
|
|
|
509
509
|
} from "./chunk-PVGDJXVK.js";
|
|
510
510
|
|
|
511
511
|
// src/orchestrator.ts
|
|
512
|
-
import
|
|
512
|
+
import path5 from "path";
|
|
513
513
|
import os2 from "os";
|
|
514
514
|
import { createHash as createHash2, randomBytes } from "crypto";
|
|
515
515
|
import { existsSync, readFileSync } from "fs";
|
|
516
516
|
import {
|
|
517
517
|
lstat,
|
|
518
518
|
mkdir as mkdir3,
|
|
519
|
-
readdir,
|
|
519
|
+
readdir as readdir2,
|
|
520
520
|
readFile as readFile2,
|
|
521
521
|
realpath,
|
|
522
522
|
stat,
|
|
@@ -1942,6 +1942,452 @@ ${lines.join("\n\n")}`;
|
|
|
1942
1942
|
}
|
|
1943
1943
|
};
|
|
1944
1944
|
|
|
1945
|
+
// src/orchestration/conversation-index-coordinator.ts
|
|
1946
|
+
import { readdir } from "fs/promises";
|
|
1947
|
+
import path3 from "path";
|
|
1948
|
+
var ConversationIndexCoordinator = class {
|
|
1949
|
+
config;
|
|
1950
|
+
getTranscript;
|
|
1951
|
+
getBackend;
|
|
1952
|
+
indexDir;
|
|
1953
|
+
lastUpdateAtMs = /* @__PURE__ */ new Map();
|
|
1954
|
+
constructor(options) {
|
|
1955
|
+
this.config = options.config;
|
|
1956
|
+
this.getTranscript = options.getTranscript;
|
|
1957
|
+
this.getBackend = options.getBackend;
|
|
1958
|
+
this.indexDir = options.indexDir;
|
|
1959
|
+
}
|
|
1960
|
+
/** Semantic recall over past-conversation chunks (fail-open: empty on miss). */
|
|
1961
|
+
async search(retrievalQuery, topK) {
|
|
1962
|
+
const backend = this.getBackend();
|
|
1963
|
+
if (backend) {
|
|
1964
|
+
return backend.search(retrievalQuery, topK);
|
|
1965
|
+
}
|
|
1966
|
+
return [];
|
|
1967
|
+
}
|
|
1968
|
+
/** Render conversation-recall search hits as a budgeted markdown section. */
|
|
1969
|
+
formatRecallSection(results, maxChars) {
|
|
1970
|
+
if (!Array.isArray(results) || results.length === 0) return null;
|
|
1971
|
+
const lines = ["## Semantic Recall (Past Conversations)", ""];
|
|
1972
|
+
let used = 0;
|
|
1973
|
+
for (const r of results) {
|
|
1974
|
+
if (!r?.snippet) continue;
|
|
1975
|
+
const chunk = `### ${r.path}
|
|
1976
|
+
Score: ${r.score.toFixed(3)}
|
|
1977
|
+
|
|
1978
|
+
${r.snippet.trim()}
|
|
1979
|
+
`;
|
|
1980
|
+
if (used + chunk.length > maxChars) break;
|
|
1981
|
+
lines.push(chunk);
|
|
1982
|
+
used += chunk.length;
|
|
1983
|
+
}
|
|
1984
|
+
return used > 0 ? lines.join("\n") : null;
|
|
1985
|
+
}
|
|
1986
|
+
/** Recursively count `.md` chunk documents under a directory. */
|
|
1987
|
+
async countChunkDocs(dir) {
|
|
1988
|
+
try {
|
|
1989
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
1990
|
+
let total = 0;
|
|
1991
|
+
for (const entry of entries) {
|
|
1992
|
+
const fullPath = path3.join(dir, entry.name);
|
|
1993
|
+
if (entry.isDirectory()) {
|
|
1994
|
+
total += await this.countChunkDocs(fullPath);
|
|
1995
|
+
continue;
|
|
1996
|
+
}
|
|
1997
|
+
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
1998
|
+
total += 1;
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
return total;
|
|
2002
|
+
} catch {
|
|
2003
|
+
return 0;
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
/** Read recent transcript entries and chunk them for indexing. */
|
|
2007
|
+
async buildChunks(sessionKey, hours = 24) {
|
|
2008
|
+
const entries = await this.getTranscript().readRecent(hours, sessionKey);
|
|
2009
|
+
const effectiveSessionKey = sessionKey ?? "all-sessions";
|
|
2010
|
+
return chunkTranscriptEntries(effectiveSessionKey, entries, {
|
|
2011
|
+
maxChars: this.config.conversationRecallMaxChars * 2,
|
|
2012
|
+
maxTurns: Math.max(10, this.config.hourlySummariesMaxTurnsPerRun)
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
async getHealth() {
|
|
2016
|
+
const chunkDocCount = await this.countChunkDocs(this.indexDir);
|
|
2017
|
+
const lastUpdateAtMs = Math.max(0, ...this.lastUpdateAtMs.values());
|
|
2018
|
+
const lastUpdateAt = lastUpdateAtMs > 0 ? new Date(lastUpdateAtMs).toISOString() : null;
|
|
2019
|
+
if (!resolveIndexingCapabilities(this.config).conversationIndex) {
|
|
2020
|
+
return {
|
|
2021
|
+
enabled: false,
|
|
2022
|
+
backend: this.config.conversationIndexBackend,
|
|
2023
|
+
status: "disabled",
|
|
2024
|
+
chunkDocCount,
|
|
2025
|
+
lastUpdateAt
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
const backend = this.getBackend();
|
|
2029
|
+
const backendHealth = backend ? await backend.health() : {
|
|
2030
|
+
backend: this.config.conversationIndexBackend,
|
|
2031
|
+
status: "degraded"
|
|
2032
|
+
};
|
|
2033
|
+
return {
|
|
2034
|
+
enabled: true,
|
|
2035
|
+
chunkDocCount,
|
|
2036
|
+
lastUpdateAt,
|
|
2037
|
+
...backendHealth
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
async inspect() {
|
|
2041
|
+
const chunkDocCount = await this.countChunkDocs(this.indexDir);
|
|
2042
|
+
const lastUpdateAtMs = Math.max(0, ...this.lastUpdateAtMs.values());
|
|
2043
|
+
const lastUpdateAt = lastUpdateAtMs > 0 ? new Date(lastUpdateAtMs).toISOString() : null;
|
|
2044
|
+
if (!resolveIndexingCapabilities(this.config).conversationIndex) {
|
|
2045
|
+
return {
|
|
2046
|
+
enabled: false,
|
|
2047
|
+
backend: this.config.conversationIndexBackend,
|
|
2048
|
+
status: "disabled",
|
|
2049
|
+
available: false,
|
|
2050
|
+
indexPath: this.indexDir,
|
|
2051
|
+
supportsIncrementalUpdate: true,
|
|
2052
|
+
message: "Conversation index disabled by config",
|
|
2053
|
+
metadata: {
|
|
2054
|
+
chunkCount: chunkDocCount
|
|
2055
|
+
},
|
|
2056
|
+
chunkDocCount,
|
|
2057
|
+
lastUpdateAt
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
const backend = this.getBackend();
|
|
2061
|
+
const inspection = backend ? await backend.inspect() : {
|
|
2062
|
+
backend: this.config.conversationIndexBackend,
|
|
2063
|
+
status: "degraded",
|
|
2064
|
+
available: false,
|
|
2065
|
+
indexPath: this.indexDir,
|
|
2066
|
+
supportsIncrementalUpdate: true,
|
|
2067
|
+
message: "Conversation index backend unavailable",
|
|
2068
|
+
metadata: {
|
|
2069
|
+
chunkCount: chunkDocCount
|
|
2070
|
+
}
|
|
2071
|
+
};
|
|
2072
|
+
return {
|
|
2073
|
+
enabled: true,
|
|
2074
|
+
chunkDocCount,
|
|
2075
|
+
lastUpdateAt,
|
|
2076
|
+
...inspection
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
async update(sessionKey, hours = 24, opts) {
|
|
2080
|
+
if (!resolveIndexingCapabilities(this.config).conversationIndex) {
|
|
2081
|
+
return { chunks: 0, skipped: true, reason: "disabled", embedded: false };
|
|
2082
|
+
}
|
|
2083
|
+
const enforceMinInterval = opts?.enforceMinInterval !== false;
|
|
2084
|
+
if (enforceMinInterval) {
|
|
2085
|
+
const minIntervalMs = Math.max(
|
|
2086
|
+
0,
|
|
2087
|
+
this.config.conversationIndexMinUpdateIntervalMs
|
|
2088
|
+
);
|
|
2089
|
+
const now = Date.now();
|
|
2090
|
+
const last = this.lastUpdateAtMs.get(sessionKey) ?? 0;
|
|
2091
|
+
const elapsed = now - last;
|
|
2092
|
+
if (minIntervalMs > 0 && elapsed < minIntervalMs) {
|
|
2093
|
+
return {
|
|
2094
|
+
chunks: 0,
|
|
2095
|
+
skipped: true,
|
|
2096
|
+
reason: "min_interval",
|
|
2097
|
+
retryAfterMs: minIntervalMs - elapsed,
|
|
2098
|
+
embedded: false
|
|
2099
|
+
};
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
const chunks = await this.buildChunks(sessionKey, hours);
|
|
2103
|
+
await writeConversationChunks(this.indexDir, chunks);
|
|
2104
|
+
const retentionCutoffMs = Number.isFinite(this.config.conversationIndexRetentionDays) && this.config.conversationIndexRetentionDays > 0 ? Date.now() - this.config.conversationIndexRetentionDays * 24 * 60 * 60 * 1e3 : void 0;
|
|
2105
|
+
await cleanupConversationChunks(
|
|
2106
|
+
this.indexDir,
|
|
2107
|
+
this.config.conversationIndexRetentionDays
|
|
2108
|
+
);
|
|
2109
|
+
const shouldEmbed = opts?.embed ?? this.config.conversationIndexEmbedOnUpdate;
|
|
2110
|
+
let embedded = false;
|
|
2111
|
+
const backend = this.getBackend();
|
|
2112
|
+
if (backend) {
|
|
2113
|
+
const result = await backend.update(chunks, {
|
|
2114
|
+
embed: shouldEmbed,
|
|
2115
|
+
...retentionCutoffMs !== void 0 ? { retentionCutoffMs } : {}
|
|
2116
|
+
});
|
|
2117
|
+
embedded = result.embedded;
|
|
2118
|
+
}
|
|
2119
|
+
this.lastUpdateAtMs.set(sessionKey, Date.now());
|
|
2120
|
+
return { chunks: chunks.length, skipped: false, embedded };
|
|
2121
|
+
}
|
|
2122
|
+
async rebuild(sessionKey, hours = 24, opts) {
|
|
2123
|
+
if (!resolveIndexingCapabilities(this.config).conversationIndex) {
|
|
2124
|
+
return {
|
|
2125
|
+
chunks: 0,
|
|
2126
|
+
skipped: true,
|
|
2127
|
+
reason: "disabled",
|
|
2128
|
+
embedded: false,
|
|
2129
|
+
rebuilt: false
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
const chunks = await this.buildChunks(sessionKey, hours);
|
|
2133
|
+
await writeConversationChunks(this.indexDir, chunks);
|
|
2134
|
+
await cleanupConversationChunks(
|
|
2135
|
+
this.indexDir,
|
|
2136
|
+
this.config.conversationIndexRetentionDays
|
|
2137
|
+
);
|
|
2138
|
+
const shouldEmbed = opts?.embed ?? this.config.conversationIndexEmbedOnUpdate;
|
|
2139
|
+
let embedded = false;
|
|
2140
|
+
let rebuilt = false;
|
|
2141
|
+
const backend = this.getBackend();
|
|
2142
|
+
if (backend) {
|
|
2143
|
+
const result = await backend.rebuild(chunks, {
|
|
2144
|
+
embed: shouldEmbed
|
|
2145
|
+
});
|
|
2146
|
+
embedded = result.embedded;
|
|
2147
|
+
rebuilt = result.rebuilt;
|
|
2148
|
+
}
|
|
2149
|
+
const stamp = Date.now();
|
|
2150
|
+
if (sessionKey) {
|
|
2151
|
+
this.lastUpdateAtMs.set(sessionKey, stamp);
|
|
2152
|
+
} else {
|
|
2153
|
+
this.lastUpdateAtMs.set("__rebuild__", stamp);
|
|
2154
|
+
}
|
|
2155
|
+
return { chunks: chunks.length, skipped: false, embedded, rebuilt };
|
|
2156
|
+
}
|
|
2157
|
+
};
|
|
2158
|
+
|
|
2159
|
+
// src/orchestration/recall-rerank-coordinator.ts
|
|
2160
|
+
var RecallRerankCoordinator = class _RecallRerankCoordinator {
|
|
2161
|
+
getConfig;
|
|
2162
|
+
getStorage;
|
|
2163
|
+
readQmdResultMemory;
|
|
2164
|
+
memoryWorthCounterCache = /* @__PURE__ */ new Map();
|
|
2165
|
+
static MEMORY_WORTH_CACHE_TTL_MS = 3e4;
|
|
2166
|
+
trustSignalCache = /* @__PURE__ */ new Map();
|
|
2167
|
+
static TRUST_SIGNAL_CACHE_TTL_MS = 3e4;
|
|
2168
|
+
constructor(options) {
|
|
2169
|
+
this.getConfig = options.getConfig;
|
|
2170
|
+
this.getStorage = options.getStorage;
|
|
2171
|
+
this.readQmdResultMemory = options.readQmdResultMemory;
|
|
2172
|
+
}
|
|
2173
|
+
async applyMemoryWorthRerank(results, namespaces) {
|
|
2174
|
+
const counters = /* @__PURE__ */ new Map();
|
|
2175
|
+
const seenNamespaces = /* @__PURE__ */ new Set();
|
|
2176
|
+
const nowMs = Date.now();
|
|
2177
|
+
for (const [key, entry] of this.memoryWorthCounterCache) {
|
|
2178
|
+
if (nowMs - entry.at >= _RecallRerankCoordinator.MEMORY_WORTH_CACHE_TTL_MS) {
|
|
2179
|
+
this.memoryWorthCounterCache.delete(key);
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
for (const ns of namespaces) {
|
|
2183
|
+
if (seenNamespaces.has(ns)) continue;
|
|
2184
|
+
seenNamespaces.add(ns);
|
|
2185
|
+
try {
|
|
2186
|
+
const cached = this.memoryWorthCounterCache.get(ns);
|
|
2187
|
+
let nsMap;
|
|
2188
|
+
if (cached && nowMs - cached.at < _RecallRerankCoordinator.MEMORY_WORTH_CACHE_TTL_MS) {
|
|
2189
|
+
nsMap = cached.counters;
|
|
2190
|
+
} else {
|
|
2191
|
+
const storage = await this.getStorage(ns);
|
|
2192
|
+
const memories = await storage.readAllMemories();
|
|
2193
|
+
nsMap = buildMemoryWorthCounterMap(memories);
|
|
2194
|
+
this.memoryWorthCounterCache.set(ns, { at: nowMs, counters: nsMap });
|
|
2195
|
+
}
|
|
2196
|
+
for (const [path6, c] of nsMap) counters.set(path6, c);
|
|
2197
|
+
} catch (err) {
|
|
2198
|
+
log.debug("memory-worth: failed to read namespace, skipping", {
|
|
2199
|
+
namespace: ns,
|
|
2200
|
+
error: err.message
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
const missing = results.filter((r) => !counters.has(r.path));
|
|
2205
|
+
if (missing.length > 0) {
|
|
2206
|
+
let reader = null;
|
|
2207
|
+
for (const ns of namespaces) {
|
|
2208
|
+
try {
|
|
2209
|
+
reader = await this.getStorage(ns);
|
|
2210
|
+
break;
|
|
2211
|
+
} catch {
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
if (reader) {
|
|
2215
|
+
for (const r of missing) {
|
|
2216
|
+
try {
|
|
2217
|
+
const memory = await this.readQmdResultMemory(r.path, reader, namespaces);
|
|
2218
|
+
if (!memory) continue;
|
|
2219
|
+
const fm = memory.frontmatter;
|
|
2220
|
+
if (fm.mw_success === void 0 && fm.mw_fail === void 0) continue;
|
|
2221
|
+
counters.set(r.path, {
|
|
2222
|
+
mw_success: fm.mw_success,
|
|
2223
|
+
mw_fail: fm.mw_fail,
|
|
2224
|
+
lastAccessed: fm.lastAccessed
|
|
2225
|
+
});
|
|
2226
|
+
} catch (err) {
|
|
2227
|
+
log.debug("memory-worth: direct path lookup failed", {
|
|
2228
|
+
path: r.path,
|
|
2229
|
+
error: err.message
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
if (counters.size === 0) return results;
|
|
2236
|
+
const rankedInputs = results.map((r, i) => ({
|
|
2237
|
+
path: r.path,
|
|
2238
|
+
// Large positive rank score so multiplier math stays well-scaled and
|
|
2239
|
+
// we never hit zero; descending so earlier items rank higher.
|
|
2240
|
+
score: results.length - i
|
|
2241
|
+
}));
|
|
2242
|
+
const config = this.getConfig();
|
|
2243
|
+
const filtered = applyMemoryWorthFilter(rankedInputs, {
|
|
2244
|
+
counters,
|
|
2245
|
+
now: /* @__PURE__ */ new Date(),
|
|
2246
|
+
halfLifeMs: config.recallMemoryWorthHalfLifeMs > 0 ? config.recallMemoryWorthHalfLifeMs : void 0
|
|
2247
|
+
});
|
|
2248
|
+
const byPath = new Map(results.map((r) => [r.path, r]));
|
|
2249
|
+
const reordered = [];
|
|
2250
|
+
for (const item of filtered) {
|
|
2251
|
+
const original = byPath.get(item.path);
|
|
2252
|
+
if (original) reordered.push(original);
|
|
2253
|
+
}
|
|
2254
|
+
return reordered;
|
|
2255
|
+
}
|
|
2256
|
+
/**
|
|
2257
|
+
* Issue #1577 — unified TrustScore recall stage. Thin wiring over the pure
|
|
2258
|
+
* {@link applyTrustScoreStage} scorer + the {@link buildTrustSignalsForRerank}
|
|
2259
|
+
* signal builder. The stage subsumes the Memory Worth multiplier — the
|
|
2260
|
+
* orchestrator runs exactly one of the two (mutual exclusion, rule 39; the
|
|
2261
|
+
* double-multiplier test in trust-score-stage.test.ts pins it structurally).
|
|
2262
|
+
*
|
|
2263
|
+
* Returns the admitted results AND the per-path trust map (including
|
|
2264
|
+
* quarantined items) so the caller can: (a) render epistemic hedges, (b)
|
|
2265
|
+
* surface quarantined items in X-ray with a reason (rule 34), and (c) filter
|
|
2266
|
+
* quarantined paths from fallback recall branches. The trust map is a
|
|
2267
|
+
* per-recall local — never instance state — so concurrent recalls cannot
|
|
2268
|
+
* race on it (review: shared-trust-map concurrency).
|
|
2269
|
+
*/
|
|
2270
|
+
async applyTrustScoreRerank(results, namespaces) {
|
|
2271
|
+
if (results.length === 0) return { results, trustByPath: null };
|
|
2272
|
+
const config = this.getConfig();
|
|
2273
|
+
const now = /* @__PURE__ */ new Date();
|
|
2274
|
+
const halfLifeDays = config.recallMemoryWorthHalfLifeMs > 0 ? config.recallMemoryWorthHalfLifeMs / (24 * 60 * 60 * 1e3) : void 0;
|
|
2275
|
+
let fallbackReader = null;
|
|
2276
|
+
const signals = await buildTrustSignalsForRerank(
|
|
2277
|
+
results.map((r) => r.path),
|
|
2278
|
+
namespaces,
|
|
2279
|
+
{
|
|
2280
|
+
readNamespaceMemories: async (ns) => (await this.getStorage(ns)).readAllMemories(),
|
|
2281
|
+
readMemoryFrontmatter: async (path6) => {
|
|
2282
|
+
if (!fallbackReader) {
|
|
2283
|
+
for (const ns of namespaces) {
|
|
2284
|
+
try {
|
|
2285
|
+
fallbackReader = await this.getStorage(ns);
|
|
2286
|
+
break;
|
|
2287
|
+
} catch {
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
if (!fallbackReader) return null;
|
|
2292
|
+
const memory = await this.readQmdResultMemory(path6, fallbackReader, namespaces);
|
|
2293
|
+
return memory ? memory.frontmatter : null;
|
|
2294
|
+
}
|
|
2295
|
+
},
|
|
2296
|
+
{ cache: this.trustSignalCache, ttlMs: _RecallRerankCoordinator.TRUST_SIGNAL_CACHE_TTL_MS },
|
|
2297
|
+
now,
|
|
2298
|
+
{
|
|
2299
|
+
recencyHalfLifeDays: halfLifeDays,
|
|
2300
|
+
logDebug: (message, context) => log.debug(message, context)
|
|
2301
|
+
}
|
|
2302
|
+
);
|
|
2303
|
+
if (signals.size === 0) {
|
|
2304
|
+
return { results, trustByPath: null };
|
|
2305
|
+
}
|
|
2306
|
+
const rankedInputs = results.map((r, i) => ({ path: r.path, score: results.length - i }));
|
|
2307
|
+
const stage = applyTrustScoreStage(rankedInputs, {
|
|
2308
|
+
signals,
|
|
2309
|
+
weights: config.trustScoreWeights,
|
|
2310
|
+
minMultiplier: config.trustScoreMinMultiplier,
|
|
2311
|
+
maxMultiplier: config.trustScoreMaxMultiplier,
|
|
2312
|
+
quarantine: config.trustScoreQuarantine
|
|
2313
|
+
});
|
|
2314
|
+
const trustByPath = new Map(stage.all.map((item) => [item.path, item]));
|
|
2315
|
+
const byPath = new Map(results.map((r) => [r.path, r]));
|
|
2316
|
+
const admitted = stage.admitted.map((item) => byPath.get(item.path)).filter((r) => r !== void 0);
|
|
2317
|
+
return { results: admitted, trustByPath };
|
|
2318
|
+
}
|
|
2319
|
+
/**
|
|
2320
|
+
* Issue #1577 — apply the TrustScore stage (or, when trust is off, the Memory
|
|
2321
|
+
* Worth multiplier fallback) to ONE recall branch's results, returning the
|
|
2322
|
+
* scored results + the per-path trust map. Thin wiring over
|
|
2323
|
+
* {@link applyTrustScoreRerank} so every recall path — hot QMD, embedding
|
|
2324
|
+
* fallback, recent scan — applies the SAME multiplier gate (rule 41: a
|
|
2325
|
+
* feature gate must apply across ALL parallel recall paths). TrustScore
|
|
2326
|
+
* subsumes Memory Worth; exactly one runs (rule 39). Fail-open on lookup
|
|
2327
|
+
* errors so a storage hiccup never breaks a fallback path.
|
|
2328
|
+
*/
|
|
2329
|
+
async applyTrustScoreToBranch(results, namespaces, caps, label) {
|
|
2330
|
+
if (caps.recallTrustScore && results.length > 0) {
|
|
2331
|
+
try {
|
|
2332
|
+
return await this.applyTrustScoreRerank(results, namespaces);
|
|
2333
|
+
} catch (err) {
|
|
2334
|
+
log.debug(`trust-score stage (${label}) failed open`, {
|
|
2335
|
+
error: err.message
|
|
2336
|
+
});
|
|
2337
|
+
}
|
|
2338
|
+
} else if (caps.recallMemoryWorthFilter && results.length > 0) {
|
|
2339
|
+
try {
|
|
2340
|
+
const filtered = await this.applyMemoryWorthRerank(results, namespaces);
|
|
2341
|
+
return { results: filtered, trustByPath: null };
|
|
2342
|
+
} catch (err) {
|
|
2343
|
+
log.debug(`memory-worth filter (${label}) failed open`, {
|
|
2344
|
+
error: err.message
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
return { results, trustByPath: null };
|
|
2349
|
+
}
|
|
2350
|
+
diversifyAndLimitRecallResults(sectionId, results, limit, retrievalQuery, caps = resolveCapabilities(this.getConfig())) {
|
|
2351
|
+
const safeLimit = typeof limit === "number" && Number.isFinite(limit) ? Math.max(0, Math.floor(limit)) : 0;
|
|
2352
|
+
if (!Array.isArray(results) || results.length === 0) return [];
|
|
2353
|
+
if (safeLimit === 0) return [];
|
|
2354
|
+
const boosted = caps.recallReasoningTraceBoost && typeof retrievalQuery === "string" ? applyReasoningTraceBoost(results, {
|
|
2355
|
+
enabled: true,
|
|
2356
|
+
query: retrievalQuery
|
|
2357
|
+
}) : results;
|
|
2358
|
+
const diversified = this.applyMmrToQmdResults(sectionId, boosted, caps);
|
|
2359
|
+
return diversified.slice(0, safeLimit);
|
|
2360
|
+
}
|
|
2361
|
+
/**
|
|
2362
|
+
* Apply Maximal Marginal Relevance to a section's ordered candidate list.
|
|
2363
|
+
*
|
|
2364
|
+
* Operates per-section so one redundant cluster cannot dominate a section,
|
|
2365
|
+
* and so one section's MMR pass cannot starve other sections. Returns the
|
|
2366
|
+
* input unchanged when disabled, when there are fewer than 2 candidates, or
|
|
2367
|
+
* when no budget information is available.
|
|
2368
|
+
*/
|
|
2369
|
+
applyMmrToQmdResults(sectionId, results, caps = resolveCapabilities(this.getConfig())) {
|
|
2370
|
+
if (!caps.recallMmr) return results;
|
|
2371
|
+
if (!Array.isArray(results) || results.length < 2) return results;
|
|
2372
|
+
const config = this.getConfig();
|
|
2373
|
+
const configuredTopN = config.recallMmrTopN;
|
|
2374
|
+
const topN = typeof configuredTopN === "number" && Number.isFinite(configuredTopN) ? Math.max(0, Math.floor(configuredTopN)) : 40;
|
|
2375
|
+
if (topN === 0) return results;
|
|
2376
|
+
const lambda = config.recallMmrLambda ?? 0.7;
|
|
2377
|
+
const { reordered, diversity } = reorderRecallResultsWithMmr(results, {
|
|
2378
|
+
lambda,
|
|
2379
|
+
topN
|
|
2380
|
+
});
|
|
2381
|
+
try {
|
|
2382
|
+
log.info(
|
|
2383
|
+
`recall_mmr: section=${sectionId} kept=${diversity.kept}/${diversity.considered} headReorderCount=${diversity.headReorderCount} avgSimBefore=${diversity.avgPairwiseSimBefore.toFixed(3)} avgSimAfter=${diversity.avgPairwiseSimAfter.toFixed(3)} lambda=${lambda.toFixed(2)}`
|
|
2384
|
+
);
|
|
2385
|
+
} catch {
|
|
2386
|
+
}
|
|
2387
|
+
return reordered;
|
|
2388
|
+
}
|
|
2389
|
+
};
|
|
2390
|
+
|
|
1945
2391
|
// src/maintenance/pattern-reinforcement.ts
|
|
1946
2392
|
function patternReinforcementKey(content) {
|
|
1947
2393
|
return content.trim().toLowerCase().replace(/\s+/g, " ").slice(0, 200);
|
|
@@ -2275,7 +2721,7 @@ function generateResolverDocument(taxonomy) {
|
|
|
2275
2721
|
|
|
2276
2722
|
// src/taxonomy/taxonomy-loader.ts
|
|
2277
2723
|
import { readFile, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
2278
|
-
import
|
|
2724
|
+
import path4 from "path";
|
|
2279
2725
|
var TAXONOMY_DIR = ".taxonomy";
|
|
2280
2726
|
var TAXONOMY_FILE = "taxonomy.json";
|
|
2281
2727
|
var MAX_SLUG_LENGTH = 32;
|
|
@@ -2373,7 +2819,7 @@ function validateTaxonomy(taxonomy) {
|
|
|
2373
2819
|
}
|
|
2374
2820
|
}
|
|
2375
2821
|
async function loadTaxonomy(memoryDir) {
|
|
2376
|
-
const taxonomyPath =
|
|
2822
|
+
const taxonomyPath = path4.join(memoryDir, TAXONOMY_DIR, TAXONOMY_FILE);
|
|
2377
2823
|
let raw;
|
|
2378
2824
|
try {
|
|
2379
2825
|
raw = await readFile(taxonomyPath, "utf-8");
|
|
@@ -2423,16 +2869,16 @@ async function loadTaxonomy(memoryDir) {
|
|
|
2423
2869
|
}
|
|
2424
2870
|
async function saveTaxonomy(memoryDir, taxonomy) {
|
|
2425
2871
|
validateTaxonomy(taxonomy);
|
|
2426
|
-
const dir =
|
|
2872
|
+
const dir = path4.join(memoryDir, TAXONOMY_DIR);
|
|
2427
2873
|
await mkdir2(dir, { recursive: true });
|
|
2428
|
-
const filePath =
|
|
2874
|
+
const filePath = path4.join(dir, TAXONOMY_FILE);
|
|
2429
2875
|
await writeFile2(filePath, JSON.stringify(taxonomy, null, 2) + "\n", "utf-8");
|
|
2430
2876
|
}
|
|
2431
2877
|
function getTaxonomyDir(memoryDir) {
|
|
2432
|
-
return
|
|
2878
|
+
return path4.join(memoryDir, TAXONOMY_DIR);
|
|
2433
2879
|
}
|
|
2434
2880
|
function getTaxonomyFilePath(memoryDir) {
|
|
2435
|
-
return
|
|
2881
|
+
return path4.join(memoryDir, TAXONOMY_DIR, TAXONOMY_FILE);
|
|
2436
2882
|
}
|
|
2437
2883
|
|
|
2438
2884
|
// src/wearables/registry.ts
|
|
@@ -3333,7 +3779,7 @@ async function raceRecallAbort(promise, signal, message = "recall aborted") {
|
|
|
3333
3779
|
}
|
|
3334
3780
|
}
|
|
3335
3781
|
function qmdCollectionPathParts(resultPath) {
|
|
3336
|
-
if (!resultPath ||
|
|
3782
|
+
if (!resultPath || path5.isAbsolute(resultPath)) return null;
|
|
3337
3783
|
const normalized = resultPath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
3338
3784
|
const slashIndex = normalized.indexOf("/");
|
|
3339
3785
|
if (slashIndex <= 0 || slashIndex >= normalized.length - 1) return null;
|
|
@@ -3346,9 +3792,9 @@ function qmdCollectionPathParts(resultPath) {
|
|
|
3346
3792
|
}
|
|
3347
3793
|
function qmdResultPathCandidates(storageDir, resultPath) {
|
|
3348
3794
|
const candidates = /* @__PURE__ */ new Set();
|
|
3349
|
-
const storageRoot =
|
|
3795
|
+
const storageRoot = path5.resolve(storageDir);
|
|
3350
3796
|
const addCandidate = (candidate) => {
|
|
3351
|
-
const resolved =
|
|
3797
|
+
const resolved = path5.resolve(candidate);
|
|
3352
3798
|
if (isPathInsideStorageRoot(storageRoot, resolved)) {
|
|
3353
3799
|
candidates.add(resolved);
|
|
3354
3800
|
}
|
|
@@ -3356,12 +3802,12 @@ function qmdResultPathCandidates(storageDir, resultPath) {
|
|
|
3356
3802
|
const addRelativeCandidates = (relativePath) => {
|
|
3357
3803
|
const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
3358
3804
|
if (!normalized) return;
|
|
3359
|
-
addCandidate(
|
|
3805
|
+
addCandidate(path5.join(storageRoot, normalized));
|
|
3360
3806
|
if (/^\d{4}-\d{2}-\d{2}\//.test(normalized)) {
|
|
3361
|
-
addCandidate(
|
|
3807
|
+
addCandidate(path5.join(storageRoot, "facts", normalized));
|
|
3362
3808
|
}
|
|
3363
3809
|
};
|
|
3364
|
-
if (
|
|
3810
|
+
if (path5.isAbsolute(resultPath)) {
|
|
3365
3811
|
addCandidate(resultPath);
|
|
3366
3812
|
} else {
|
|
3367
3813
|
addRelativeCandidates(resultPath);
|
|
@@ -3478,7 +3924,7 @@ async function qmdStartupCollectionCheckWithTimeout(promise, controller, label)
|
|
|
3478
3924
|
return await Promise.race([checkedPromise, timeoutPromise]);
|
|
3479
3925
|
}
|
|
3480
3926
|
function defaultWorkspaceDir() {
|
|
3481
|
-
return
|
|
3927
|
+
return path5.join(os2.homedir(), ".openclaw", "workspace");
|
|
3482
3928
|
}
|
|
3483
3929
|
function sanitizeSessionKeyForFilename(sessionKey) {
|
|
3484
3930
|
const readable = sessionKey.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
@@ -3755,11 +4201,11 @@ function mergeGraphExpandedResults(primary, expanded) {
|
|
|
3755
4201
|
return Array.from(mergedByPath.values());
|
|
3756
4202
|
}
|
|
3757
4203
|
function graphPathRelativeToStorage(storageDir, candidatePath) {
|
|
3758
|
-
const absolutePath =
|
|
3759
|
-
const rel =
|
|
4204
|
+
const absolutePath = path5.isAbsolute(candidatePath) ? candidatePath : path5.resolve(storageDir, candidatePath);
|
|
4205
|
+
const rel = path5.relative(storageDir, absolutePath);
|
|
3760
4206
|
if (!rel || rel === ".") return null;
|
|
3761
4207
|
if (rel.startsWith("..")) return null;
|
|
3762
|
-
return rel.split(
|
|
4208
|
+
return rel.split(path5.sep).join("/");
|
|
3763
4209
|
}
|
|
3764
4210
|
function normalizeGraphActivationScore(score) {
|
|
3765
4211
|
const bounded = Number.isFinite(score) && score > 0 ? score : 0;
|
|
@@ -3904,7 +4350,7 @@ function buildMemoryPathById(allMemsForGraph, storageDir) {
|
|
|
3904
4350
|
for (const mem of allMemsForGraph ?? []) {
|
|
3905
4351
|
const id = mem.frontmatter.id;
|
|
3906
4352
|
if (!id) continue;
|
|
3907
|
-
pathById.set(id,
|
|
4353
|
+
pathById.set(id, path5.relative(storageDir, mem.path));
|
|
3908
4354
|
}
|
|
3909
4355
|
return pathById;
|
|
3910
4356
|
}
|
|
@@ -3912,7 +4358,7 @@ function appendMemoryToGraphContext(options) {
|
|
|
3912
4358
|
if (!Array.isArray(options.allMemsForGraph)) return;
|
|
3913
4359
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
3914
4360
|
options.allMemsForGraph.push({
|
|
3915
|
-
path:
|
|
4361
|
+
path: path5.join(options.storageDir, options.memoryRelPath),
|
|
3916
4362
|
content: options.content,
|
|
3917
4363
|
frontmatter: {
|
|
3918
4364
|
id: options.memoryId,
|
|
@@ -3932,16 +4378,16 @@ function resolvePersistedMemoryRelativePath(options) {
|
|
|
3932
4378
|
const persisted = options.pathById.get(options.memoryId);
|
|
3933
4379
|
if (persisted) return persisted;
|
|
3934
4380
|
if (options.category === "correction") {
|
|
3935
|
-
return
|
|
4381
|
+
return path5.join("corrections", `${options.memoryId}.md`);
|
|
3936
4382
|
}
|
|
3937
4383
|
const subtree = categoryDirName(options.category);
|
|
3938
4384
|
const idParts = options.memoryId.split("-");
|
|
3939
4385
|
const maybeTimestamp = Number(idParts[1]);
|
|
3940
4386
|
if (Number.isFinite(maybeTimestamp) && maybeTimestamp > 0) {
|
|
3941
4387
|
const day = new Date(maybeTimestamp).toISOString().slice(0, 10);
|
|
3942
|
-
return
|
|
4388
|
+
return path5.join(subtree, day, `${options.memoryId}.md`);
|
|
3943
4389
|
}
|
|
3944
|
-
return
|
|
4390
|
+
return path5.join(subtree, `${options.memoryId}.md`);
|
|
3945
4391
|
}
|
|
3946
4392
|
var Orchestrator = class _Orchestrator {
|
|
3947
4393
|
storage;
|
|
@@ -4048,25 +4494,6 @@ var Orchestrator = class _Orchestrator {
|
|
|
4048
4494
|
/** Lossless Context Management engine — proactive session archive + DAG summarization. */
|
|
4049
4495
|
lcmEngine = null;
|
|
4050
4496
|
rerankCache = new RerankCache();
|
|
4051
|
-
/**
|
|
4052
|
-
* Short-TTL cache for Memory Worth counter lookups so interactive recall
|
|
4053
|
-
* doesn't trigger a full `readAllMemories` scan per query. Keyed by
|
|
4054
|
-
* namespace; the filter unions across namespaces at query time. The TTL
|
|
4055
|
-
* is intentionally short (seconds, not minutes) because counters are
|
|
4056
|
-
* mutated by `recordMemoryOutcome` asynchronously and we'd rather serve
|
|
4057
|
-
* a 30-second-stale worth score than a stable-but-wrong one.
|
|
4058
|
-
*/
|
|
4059
|
-
memoryWorthCounterCache = /* @__PURE__ */ new Map();
|
|
4060
|
-
static MEMORY_WORTH_CACHE_TTL_MS = 3e4;
|
|
4061
|
-
/**
|
|
4062
|
-
* Issue #1577 — per-namespace TrustScore signal map cache. Same TTL/shape
|
|
4063
|
-
* discipline as {@link memoryWorthCounterCache}: seconds-scale, evicted on
|
|
4064
|
-
* every call, keyed by namespace. Holds the frontmatter-derived signals
|
|
4065
|
-
* (worth, provenance, faithfulness, corroboration, recency) so the trust
|
|
4066
|
-
* stage doesn't trigger a fresh `readAllMemories` scan per query.
|
|
4067
|
-
*/
|
|
4068
|
-
trustSignalCache = /* @__PURE__ */ new Map();
|
|
4069
|
-
static TRUST_SIGNAL_CACHE_TTL_MS = 3e4;
|
|
4070
4497
|
/**
|
|
4071
4498
|
* Per-session workspace selections keyed by sessionKey.
|
|
4072
4499
|
* Set by the before_agent_start hook so recall() uses the correct
|
|
@@ -4116,13 +4543,18 @@ var Orchestrator = class _Orchestrator {
|
|
|
4116
4543
|
* to RecallResultFormatter.
|
|
4117
4544
|
*/
|
|
4118
4545
|
recallResultFormatter;
|
|
4546
|
+
/**
|
|
4547
|
+
* Issue #1526: conversation-index subsystem moved to
|
|
4548
|
+
* ConversationIndexCoordinator.
|
|
4549
|
+
*/
|
|
4550
|
+
conversationIndexCoordinator;
|
|
4551
|
+
recallRerankCoordinator;
|
|
4119
4552
|
heartbeatObserverChains = /* @__PURE__ */ new Map();
|
|
4120
4553
|
recentExtractionFingerprints = /* @__PURE__ */ new Map();
|
|
4121
4554
|
consolidationObservers = /* @__PURE__ */ new Set();
|
|
4122
4555
|
wearablesServiceInstance = null;
|
|
4123
4556
|
wearablesAutoSyncHandle = null;
|
|
4124
4557
|
lastQmdReprobeAtMs = 0;
|
|
4125
|
-
conversationIndexLastUpdateAtMs = /* @__PURE__ */ new Map();
|
|
4126
4558
|
lastFileHygieneRunAtMs = 0;
|
|
4127
4559
|
// Pattern-reinforcement cadence gate (issue #687 PR 2/4). Tracks the
|
|
4128
4560
|
// last successful run so `runPatternReinforcement` can short-circuit
|
|
@@ -4409,7 +4841,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
4409
4841
|
const defaultNs = normalizeNamespaceIdentity(this.config.defaultNamespace);
|
|
4410
4842
|
if (ns !== defaultNs && !isSafeRouteNamespace(ns)) return;
|
|
4411
4843
|
if (!this.storageDirMatchesNamespaceHint(ns, storageDir)) return;
|
|
4412
|
-
const resolvedStorageDir =
|
|
4844
|
+
const resolvedStorageDir = path5.resolve(storageDir);
|
|
4413
4845
|
let hints = this.namespaceStorageDirHints.get(resolvedStorageDir);
|
|
4414
4846
|
if (!hints) {
|
|
4415
4847
|
hints = /* @__PURE__ */ new Set();
|
|
@@ -4420,21 +4852,21 @@ var Orchestrator = class _Orchestrator {
|
|
|
4420
4852
|
storageDirMatchesNamespaceHint(namespace, storageDir) {
|
|
4421
4853
|
const ns = normalizeNamespaceIdentity(namespace);
|
|
4422
4854
|
if (!ns) return false;
|
|
4423
|
-
const resolvedStorageDir =
|
|
4424
|
-
const resolvedMemoryDir =
|
|
4855
|
+
const resolvedStorageDir = path5.resolve(storageDir);
|
|
4856
|
+
const resolvedMemoryDir = path5.resolve(this.config.memoryDir);
|
|
4425
4857
|
const defaultNs = normalizeNamespaceIdentity(this.config.defaultNamespace);
|
|
4426
4858
|
if (resolvedStorageDir === resolvedMemoryDir) return ns === defaultNs;
|
|
4427
|
-
const resolvedNamespacesDir =
|
|
4859
|
+
const resolvedNamespacesDir = path5.join(resolvedMemoryDir, "namespaces");
|
|
4428
4860
|
if (!isPathInsideStorageRoot(resolvedNamespacesDir, resolvedStorageDir)) return false;
|
|
4429
|
-
const rawRoot =
|
|
4430
|
-
const tokenRoot =
|
|
4861
|
+
const rawRoot = path5.resolve(resolvedNamespacesDir, ns);
|
|
4862
|
+
const tokenRoot = path5.resolve(resolvedNamespacesDir, namespaceIdentityToken(ns));
|
|
4431
4863
|
return resolvedStorageDir === rawRoot || resolvedStorageDir === tokenRoot;
|
|
4432
4864
|
}
|
|
4433
4865
|
namespaceStorageDirHintOwnershipRank(record, resolvedStorageDir, configured) {
|
|
4434
|
-
if (resolvedStorageDir ===
|
|
4866
|
+
if (resolvedStorageDir === path5.resolve(this.config.memoryDir)) {
|
|
4435
4867
|
return record.namespace === normalizeNamespaceIdentity(this.config.defaultNamespace) ? 0 : 3;
|
|
4436
4868
|
}
|
|
4437
|
-
const leaf =
|
|
4869
|
+
const leaf = path5.basename(resolvedStorageDir);
|
|
4438
4870
|
const tokenOwnsRoot = namespaceIdentityToken(record.namespace) === leaf;
|
|
4439
4871
|
if (tokenOwnsRoot && configured.has(record.namespace)) return 0;
|
|
4440
4872
|
if (record.namespace === leaf) return 1;
|
|
@@ -4462,7 +4894,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
4462
4894
|
loadNamespaceStorageDirHintsFromCatalog() {
|
|
4463
4895
|
if (this.namespaceStorageDirHintsLoaded || !this.namespaceCatalog.enabled) return;
|
|
4464
4896
|
this.namespaceStorageDirHintsLoaded = true;
|
|
4465
|
-
const catalogPath =
|
|
4897
|
+
const catalogPath = path5.join(this.config.memoryDir, "state", "namespaces.jsonl");
|
|
4466
4898
|
if (!existsSync(catalogPath)) return;
|
|
4467
4899
|
let body;
|
|
4468
4900
|
try {
|
|
@@ -4499,7 +4931,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
4499
4931
|
if (!this.storageDirMatchesNamespaceHint(record.namespace, record.storageDir)) {
|
|
4500
4932
|
continue;
|
|
4501
4933
|
}
|
|
4502
|
-
const resolvedStorageDir =
|
|
4934
|
+
const resolvedStorageDir = path5.resolve(record.storageDir);
|
|
4503
4935
|
const current = preferredByStorageDir.get(resolvedStorageDir);
|
|
4504
4936
|
preferredByStorageDir.set(
|
|
4505
4937
|
resolvedStorageDir,
|
|
@@ -4763,7 +5195,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
4763
5195
|
this.config = config;
|
|
4764
5196
|
this.profiler = new ProfilingCollector({
|
|
4765
5197
|
enabled: config.profilingEnabled,
|
|
4766
|
-
storageDir: config.profilingStorageDir ||
|
|
5198
|
+
storageDir: config.profilingStorageDir || path5.join(config.memoryDir, "profiling"),
|
|
4767
5199
|
maxTraces: config.profilingMaxTraces
|
|
4768
5200
|
});
|
|
4769
5201
|
this.namespaceCatalog = new NamespaceCatalog(config);
|
|
@@ -4833,11 +5265,22 @@ var Orchestrator = class _Orchestrator {
|
|
|
4833
5265
|
this.compounding = resolveConsolidationCapabilities(config).compounding ? new CompoundingEngine(config, this.storage) : void 0;
|
|
4834
5266
|
this.buffer = new SmartBuffer(config, this.storage);
|
|
4835
5267
|
this.transcript = new TranscriptManager(config);
|
|
4836
|
-
this.conversationIndexDir =
|
|
5268
|
+
this.conversationIndexDir = path5.join(
|
|
4837
5269
|
config.memoryDir,
|
|
4838
5270
|
"conversation-index",
|
|
4839
5271
|
"chunks"
|
|
4840
5272
|
);
|
|
5273
|
+
this.conversationIndexCoordinator = new ConversationIndexCoordinator({
|
|
5274
|
+
config,
|
|
5275
|
+
getTranscript: () => this.transcript,
|
|
5276
|
+
getBackend: () => this.conversationIndexBackend,
|
|
5277
|
+
indexDir: this.conversationIndexDir
|
|
5278
|
+
});
|
|
5279
|
+
this.recallRerankCoordinator = new RecallRerankCoordinator({
|
|
5280
|
+
getConfig: () => this.config,
|
|
5281
|
+
getStorage: (namespace) => this.getStorage(namespace),
|
|
5282
|
+
readQmdResultMemory: (resultPath, fallbackStorage, recallNamespaces) => this.readQmdResultMemory(resultPath, fallbackStorage, recallNamespaces)
|
|
5283
|
+
});
|
|
4841
5284
|
this.modelRegistry = new ModelRegistry(config.memoryDir);
|
|
4842
5285
|
this.relevance = new RelevanceStore(config.memoryDir);
|
|
4843
5286
|
this.negatives = new NegativeExampleStore(config.memoryDir);
|
|
@@ -4928,7 +5371,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
4928
5371
|
saveContentHashIndexes: () => this.saveContentHashIndexes()
|
|
4929
5372
|
});
|
|
4930
5373
|
this.threading = new ThreadingManager(
|
|
4931
|
-
|
|
5374
|
+
path5.join(config.memoryDir, "threads"),
|
|
4932
5375
|
config.threadingGapMinutes
|
|
4933
5376
|
);
|
|
4934
5377
|
const lifecycleCaps = resolveMemoryLifecycleCapabilities(config);
|
|
@@ -5248,10 +5691,10 @@ var Orchestrator = class _Orchestrator {
|
|
|
5248
5691
|
if (resolveRecallAuxiliaryCapabilities(this.config).compactionReset) {
|
|
5249
5692
|
try {
|
|
5250
5693
|
const wsDir = this.config.workspaceDir || defaultWorkspaceDir();
|
|
5251
|
-
const files = await
|
|
5694
|
+
const files = await readdir2(wsDir).catch(() => []);
|
|
5252
5695
|
for (const f of files) {
|
|
5253
5696
|
if (!f.startsWith(".compaction-reset-signal-")) continue;
|
|
5254
|
-
const fp =
|
|
5697
|
+
const fp = path5.join(wsDir, f);
|
|
5255
5698
|
const s = await stat(fp).catch(() => null);
|
|
5256
5699
|
if (s && Date.now() - s.mtimeMs >= COMPACTION_SIGNAL_MAX_AGE_MS) {
|
|
5257
5700
|
await unlink(fp).catch(() => {
|
|
@@ -5760,15 +6203,15 @@ ${doc.content}` : doc.content,
|
|
|
5760
6203
|
this.lastFileHygieneRunAtMs = now;
|
|
5761
6204
|
if (hygiene.rotateEnabled) {
|
|
5762
6205
|
for (const rel of hygiene.rotatePaths) {
|
|
5763
|
-
const abs =
|
|
6206
|
+
const abs = path5.isAbsolute(rel) ? rel : path5.join(this.config.workspaceDir, rel);
|
|
5764
6207
|
try {
|
|
5765
6208
|
const raw = await readFile2(abs, "utf-8");
|
|
5766
6209
|
if (raw.length > hygiene.rotateMaxBytes) {
|
|
5767
|
-
const archiveDir =
|
|
6210
|
+
const archiveDir = path5.join(
|
|
5768
6211
|
this.config.workspaceDir,
|
|
5769
6212
|
hygiene.archiveDir
|
|
5770
6213
|
);
|
|
5771
|
-
const base =
|
|
6214
|
+
const base = path5.basename(abs);
|
|
5772
6215
|
const prefix = base.toUpperCase().replace(/\.MD$/i, "").replace(/[^A-Z0-9]+/g, "-") || "FILE";
|
|
5773
6216
|
const { newContent } = await rotateMarkdownFileToArchive({
|
|
5774
6217
|
filePath: abs,
|
|
@@ -5793,8 +6236,8 @@ ${doc.content}` : doc.content,
|
|
|
5793
6236
|
log.warn(w.message);
|
|
5794
6237
|
}
|
|
5795
6238
|
if (hygiene.warningsLogEnabled && warnings.length > 0) {
|
|
5796
|
-
const fp =
|
|
5797
|
-
await mkdir3(
|
|
6239
|
+
const fp = path5.join(this.config.memoryDir, hygiene.warningsLogPath);
|
|
6240
|
+
await mkdir3(path5.dirname(fp), { recursive: true });
|
|
5798
6241
|
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
5799
6242
|
const block = `
|
|
5800
6243
|
|
|
@@ -5933,16 +6376,16 @@ ${doc.content}` : doc.content,
|
|
|
5933
6376
|
for (const categoryDir of RECALL_FALLBACK_DIRS) {
|
|
5934
6377
|
if (memoryRootReal === null) break;
|
|
5935
6378
|
for (const date of datesToScan) {
|
|
5936
|
-
const dateDir =
|
|
6379
|
+
const dateDir = path5.join(storage.dir, categoryDir, date);
|
|
5937
6380
|
try {
|
|
5938
6381
|
const dirStat = await lstat(dateDir);
|
|
5939
6382
|
if (dirStat.isSymbolicLink() || !dirStat.isDirectory()) continue;
|
|
5940
6383
|
assertPathInsideRoot(memoryRootReal, await realpath(dateDir), dateDir);
|
|
5941
|
-
const entries = await
|
|
6384
|
+
const entries = await readdir2(dateDir, { withFileTypes: true });
|
|
5942
6385
|
for (const entry of entries) {
|
|
5943
6386
|
if (entry.isSymbolicLink()) continue;
|
|
5944
6387
|
if (!entry.name.endsWith(".md")) continue;
|
|
5945
|
-
const fullPath =
|
|
6388
|
+
const fullPath = path5.join(dateDir, entry.name);
|
|
5946
6389
|
try {
|
|
5947
6390
|
assertPathInsideRoot(memoryRootReal, await realpath(fullPath), fullPath);
|
|
5948
6391
|
const raw = await readFile2(fullPath, "utf-8");
|
|
@@ -5964,7 +6407,7 @@ ${doc.content}` : doc.content,
|
|
|
5964
6407
|
facts.push({
|
|
5965
6408
|
path: fullPath,
|
|
5966
6409
|
frontmatter: {
|
|
5967
|
-
id: fm.id ||
|
|
6410
|
+
id: fm.id || path5.basename(entry.name, ".md"),
|
|
5968
6411
|
category: fm.category || "fact",
|
|
5969
6412
|
created,
|
|
5970
6413
|
updated: fm.updated || created,
|
|
@@ -5987,13 +6430,13 @@ ${doc.content}` : doc.content,
|
|
|
5987
6430
|
return a.frontmatter.created < b.frontmatter.created ? -1 : 1;
|
|
5988
6431
|
});
|
|
5989
6432
|
const hourlySummaries = [];
|
|
5990
|
-
const hourlyBaseDir =
|
|
6433
|
+
const hourlyBaseDir = path5.join(storage.dir, "summaries", "hourly");
|
|
5991
6434
|
try {
|
|
5992
|
-
const sessionKeys = await
|
|
6435
|
+
const sessionKeys = await readdir2(hourlyBaseDir, { withFileTypes: true });
|
|
5993
6436
|
for (const sk of sessionKeys) {
|
|
5994
6437
|
if (!sk.isDirectory()) continue;
|
|
5995
6438
|
for (const date of datesToScan) {
|
|
5996
|
-
const summaryFile =
|
|
6439
|
+
const summaryFile = path5.join(hourlyBaseDir, sk.name, `${date}.md`);
|
|
5997
6440
|
try {
|
|
5998
6441
|
const raw = await readFile2(summaryFile, "utf-8");
|
|
5999
6442
|
const filtered = filterHourlySummaryMarkdownForLocalDay(
|
|
@@ -6097,7 +6540,7 @@ ${doc.content}` : doc.content,
|
|
|
6097
6540
|
}
|
|
6098
6541
|
async getLastGraphRecallSnapshot(namespace) {
|
|
6099
6542
|
const storage = await this.getStorage(namespace);
|
|
6100
|
-
const snapshotPath =
|
|
6543
|
+
const snapshotPath = path5.join(
|
|
6101
6544
|
storage.dir,
|
|
6102
6545
|
"state",
|
|
6103
6546
|
"last_graph_recall.json"
|
|
@@ -6136,7 +6579,7 @@ ${doc.content}` : doc.content,
|
|
|
6136
6579
|
}
|
|
6137
6580
|
async getLastIntentSnapshot(namespace) {
|
|
6138
6581
|
const storage = await this.getStorage(namespace);
|
|
6139
|
-
const snapshotPath =
|
|
6582
|
+
const snapshotPath = path5.join(storage.dir, "state", "last_intent.json");
|
|
6140
6583
|
try {
|
|
6141
6584
|
const raw = await readFile2(snapshotPath, "utf-8");
|
|
6142
6585
|
const parsed = JSON.parse(raw);
|
|
@@ -6169,7 +6612,7 @@ ${doc.content}` : doc.content,
|
|
|
6169
6612
|
}
|
|
6170
6613
|
async getLastQmdRecallSnapshot(namespace) {
|
|
6171
6614
|
const storage = await this.getStorage(namespace);
|
|
6172
|
-
const snapshotPath =
|
|
6615
|
+
const snapshotPath = path5.join(
|
|
6173
6616
|
storage.dir,
|
|
6174
6617
|
"state",
|
|
6175
6618
|
"last_qmd_recall.json"
|
|
@@ -6294,206 +6737,31 @@ ${doc.content}` : doc.content,
|
|
|
6294
6737
|
})
|
|
6295
6738
|
].join("\n");
|
|
6296
6739
|
}
|
|
6297
|
-
async searchConversationRecallResults(retrievalQuery, topK) {
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
return
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
`;
|
|
6314
|
-
if (used + chunk.length > maxChars) break;
|
|
6315
|
-
lines.push(chunk);
|
|
6316
|
-
used += chunk.length;
|
|
6317
|
-
}
|
|
6318
|
-
return used > 0 ? lines.join("\n") : null;
|
|
6319
|
-
}
|
|
6320
|
-
async countConversationChunkDocs(dir) {
|
|
6321
|
-
try {
|
|
6322
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
6323
|
-
let total = 0;
|
|
6324
|
-
for (const entry of entries) {
|
|
6325
|
-
const fullPath = path4.join(dir, entry.name);
|
|
6326
|
-
if (entry.isDirectory()) {
|
|
6327
|
-
total += await this.countConversationChunkDocs(fullPath);
|
|
6328
|
-
continue;
|
|
6329
|
-
}
|
|
6330
|
-
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
6331
|
-
total += 1;
|
|
6332
|
-
}
|
|
6333
|
-
}
|
|
6334
|
-
return total;
|
|
6335
|
-
} catch {
|
|
6336
|
-
return 0;
|
|
6337
|
-
}
|
|
6338
|
-
}
|
|
6339
|
-
async buildConversationIndexChunks(sessionKey, hours = 24) {
|
|
6340
|
-
const entries = await this.transcript.readRecent(hours, sessionKey);
|
|
6341
|
-
const effectiveSessionKey = sessionKey ?? "all-sessions";
|
|
6342
|
-
return chunkTranscriptEntries(effectiveSessionKey, entries, {
|
|
6343
|
-
maxChars: this.config.conversationRecallMaxChars * 2,
|
|
6344
|
-
maxTurns: Math.max(10, this.config.hourlySummariesMaxTurnsPerRun)
|
|
6345
|
-
});
|
|
6346
|
-
}
|
|
6347
|
-
async getConversationIndexHealth() {
|
|
6348
|
-
const chunkDocCount = await this.countConversationChunkDocs(
|
|
6349
|
-
this.conversationIndexDir
|
|
6350
|
-
);
|
|
6351
|
-
const lastUpdateAtMs = Math.max(
|
|
6352
|
-
0,
|
|
6353
|
-
...this.conversationIndexLastUpdateAtMs.values()
|
|
6354
|
-
);
|
|
6355
|
-
const lastUpdateAt = lastUpdateAtMs > 0 ? new Date(lastUpdateAtMs).toISOString() : null;
|
|
6356
|
-
if (!resolveIndexingCapabilities(this.config).conversationIndex) {
|
|
6357
|
-
return {
|
|
6358
|
-
enabled: false,
|
|
6359
|
-
backend: this.config.conversationIndexBackend,
|
|
6360
|
-
status: "disabled",
|
|
6361
|
-
chunkDocCount,
|
|
6362
|
-
lastUpdateAt
|
|
6363
|
-
};
|
|
6364
|
-
}
|
|
6365
|
-
const backendHealth = this.conversationIndexBackend ? await this.conversationIndexBackend.health() : {
|
|
6366
|
-
backend: this.config.conversationIndexBackend,
|
|
6367
|
-
status: "degraded"
|
|
6368
|
-
};
|
|
6369
|
-
return {
|
|
6370
|
-
enabled: true,
|
|
6371
|
-
chunkDocCount,
|
|
6372
|
-
lastUpdateAt,
|
|
6373
|
-
...backendHealth
|
|
6374
|
-
};
|
|
6375
|
-
}
|
|
6376
|
-
async inspectConversationIndex() {
|
|
6377
|
-
const chunkDocCount = await this.countConversationChunkDocs(
|
|
6378
|
-
this.conversationIndexDir
|
|
6379
|
-
);
|
|
6380
|
-
const lastUpdateAtMs = Math.max(
|
|
6381
|
-
0,
|
|
6382
|
-
...this.conversationIndexLastUpdateAtMs.values()
|
|
6383
|
-
);
|
|
6384
|
-
const lastUpdateAt = lastUpdateAtMs > 0 ? new Date(lastUpdateAtMs).toISOString() : null;
|
|
6385
|
-
if (!resolveIndexingCapabilities(this.config).conversationIndex) {
|
|
6386
|
-
return {
|
|
6387
|
-
enabled: false,
|
|
6388
|
-
backend: this.config.conversationIndexBackend,
|
|
6389
|
-
status: "disabled",
|
|
6390
|
-
available: false,
|
|
6391
|
-
indexPath: this.conversationIndexDir,
|
|
6392
|
-
supportsIncrementalUpdate: true,
|
|
6393
|
-
message: "Conversation index disabled by config",
|
|
6394
|
-
metadata: {
|
|
6395
|
-
chunkCount: chunkDocCount
|
|
6396
|
-
},
|
|
6397
|
-
chunkDocCount,
|
|
6398
|
-
lastUpdateAt
|
|
6399
|
-
};
|
|
6400
|
-
}
|
|
6401
|
-
const inspection = this.conversationIndexBackend ? await this.conversationIndexBackend.inspect() : {
|
|
6402
|
-
backend: this.config.conversationIndexBackend,
|
|
6403
|
-
status: "degraded",
|
|
6404
|
-
available: false,
|
|
6405
|
-
indexPath: this.conversationIndexDir,
|
|
6406
|
-
supportsIncrementalUpdate: true,
|
|
6407
|
-
message: "Conversation index backend unavailable",
|
|
6408
|
-
metadata: {
|
|
6409
|
-
chunkCount: chunkDocCount
|
|
6410
|
-
}
|
|
6411
|
-
};
|
|
6412
|
-
return {
|
|
6413
|
-
enabled: true,
|
|
6414
|
-
chunkDocCount,
|
|
6415
|
-
lastUpdateAt,
|
|
6416
|
-
...inspection
|
|
6417
|
-
};
|
|
6740
|
+
async searchConversationRecallResults(retrievalQuery, topK) {
|
|
6741
|
+
return this.conversationIndexCoordinator.search(retrievalQuery, topK);
|
|
6742
|
+
}
|
|
6743
|
+
formatConversationRecallSection(results, maxChars) {
|
|
6744
|
+
return this.conversationIndexCoordinator.formatRecallSection(
|
|
6745
|
+
results,
|
|
6746
|
+
maxChars
|
|
6747
|
+
);
|
|
6748
|
+
}
|
|
6749
|
+
// Issue #1526: countConversationChunkDocs / buildConversationIndexChunks moved
|
|
6750
|
+
// to ConversationIndexCoordinator (internal helpers, no orchestrator callers).
|
|
6751
|
+
async getConversationIndexHealth() {
|
|
6752
|
+
return this.conversationIndexCoordinator.getHealth();
|
|
6753
|
+
}
|
|
6754
|
+
async inspectConversationIndex() {
|
|
6755
|
+
return this.conversationIndexCoordinator.inspect();
|
|
6418
6756
|
}
|
|
6419
6757
|
async getRecoverySummary(sessionKey) {
|
|
6420
6758
|
return this.transcript.getRecoverySummary(sessionKey);
|
|
6421
6759
|
}
|
|
6422
6760
|
async updateConversationIndex(sessionKey, hours = 24, opts) {
|
|
6423
|
-
|
|
6424
|
-
return { chunks: 0, skipped: true, reason: "disabled", embedded: false };
|
|
6425
|
-
}
|
|
6426
|
-
const enforceMinInterval = opts?.enforceMinInterval !== false;
|
|
6427
|
-
if (enforceMinInterval) {
|
|
6428
|
-
const minIntervalMs = Math.max(
|
|
6429
|
-
0,
|
|
6430
|
-
this.config.conversationIndexMinUpdateIntervalMs
|
|
6431
|
-
);
|
|
6432
|
-
const now = Date.now();
|
|
6433
|
-
const last = this.conversationIndexLastUpdateAtMs.get(sessionKey) ?? 0;
|
|
6434
|
-
const elapsed = now - last;
|
|
6435
|
-
if (minIntervalMs > 0 && elapsed < minIntervalMs) {
|
|
6436
|
-
return {
|
|
6437
|
-
chunks: 0,
|
|
6438
|
-
skipped: true,
|
|
6439
|
-
reason: "min_interval",
|
|
6440
|
-
retryAfterMs: minIntervalMs - elapsed,
|
|
6441
|
-
embedded: false
|
|
6442
|
-
};
|
|
6443
|
-
}
|
|
6444
|
-
}
|
|
6445
|
-
const chunks = await this.buildConversationIndexChunks(sessionKey, hours);
|
|
6446
|
-
await writeConversationChunks(this.conversationIndexDir, chunks);
|
|
6447
|
-
const retentionCutoffMs = Number.isFinite(this.config.conversationIndexRetentionDays) && this.config.conversationIndexRetentionDays > 0 ? Date.now() - this.config.conversationIndexRetentionDays * 24 * 60 * 60 * 1e3 : void 0;
|
|
6448
|
-
await cleanupConversationChunks(
|
|
6449
|
-
this.conversationIndexDir,
|
|
6450
|
-
this.config.conversationIndexRetentionDays
|
|
6451
|
-
);
|
|
6452
|
-
const shouldEmbed = opts?.embed ?? this.config.conversationIndexEmbedOnUpdate;
|
|
6453
|
-
let embedded = false;
|
|
6454
|
-
if (this.conversationIndexBackend) {
|
|
6455
|
-
const result = await this.conversationIndexBackend.update(chunks, {
|
|
6456
|
-
embed: shouldEmbed,
|
|
6457
|
-
...retentionCutoffMs !== void 0 ? { retentionCutoffMs } : {}
|
|
6458
|
-
});
|
|
6459
|
-
embedded = result.embedded;
|
|
6460
|
-
}
|
|
6461
|
-
this.conversationIndexLastUpdateAtMs.set(sessionKey, Date.now());
|
|
6462
|
-
return { chunks: chunks.length, skipped: false, embedded };
|
|
6761
|
+
return this.conversationIndexCoordinator.update(sessionKey, hours, opts);
|
|
6463
6762
|
}
|
|
6464
6763
|
async rebuildConversationIndex(sessionKey, hours = 24, opts) {
|
|
6465
|
-
|
|
6466
|
-
return {
|
|
6467
|
-
chunks: 0,
|
|
6468
|
-
skipped: true,
|
|
6469
|
-
reason: "disabled",
|
|
6470
|
-
embedded: false,
|
|
6471
|
-
rebuilt: false
|
|
6472
|
-
};
|
|
6473
|
-
}
|
|
6474
|
-
const chunks = await this.buildConversationIndexChunks(sessionKey, hours);
|
|
6475
|
-
await writeConversationChunks(this.conversationIndexDir, chunks);
|
|
6476
|
-
await cleanupConversationChunks(
|
|
6477
|
-
this.conversationIndexDir,
|
|
6478
|
-
this.config.conversationIndexRetentionDays
|
|
6479
|
-
);
|
|
6480
|
-
const shouldEmbed = opts?.embed ?? this.config.conversationIndexEmbedOnUpdate;
|
|
6481
|
-
let embedded = false;
|
|
6482
|
-
let rebuilt = false;
|
|
6483
|
-
if (this.conversationIndexBackend) {
|
|
6484
|
-
const result = await this.conversationIndexBackend.rebuild(chunks, {
|
|
6485
|
-
embed: shouldEmbed
|
|
6486
|
-
});
|
|
6487
|
-
embedded = result.embedded;
|
|
6488
|
-
rebuilt = result.rebuilt;
|
|
6489
|
-
}
|
|
6490
|
-
const stamp = Date.now();
|
|
6491
|
-
if (sessionKey) {
|
|
6492
|
-
this.conversationIndexLastUpdateAtMs.set(sessionKey, stamp);
|
|
6493
|
-
} else {
|
|
6494
|
-
this.conversationIndexLastUpdateAtMs.set("__rebuild__", stamp);
|
|
6495
|
-
}
|
|
6496
|
-
return { chunks: chunks.length, skipped: false, embedded, rebuilt };
|
|
6764
|
+
return this.conversationIndexCoordinator.rebuild(sessionKey, hours, opts);
|
|
6497
6765
|
}
|
|
6498
6766
|
/**
|
|
6499
6767
|
* Validate local LLM model availability and context window compatibility.
|
|
@@ -7228,7 +7496,7 @@ ${r.snippet.trim()}
|
|
|
7228
7496
|
resolvedPath = resolvedCold.result.path;
|
|
7229
7497
|
resolvedResult = resolvedCold.result;
|
|
7230
7498
|
}
|
|
7231
|
-
if (!
|
|
7499
|
+
if (!path5.isAbsolute(resolvedPath)) {
|
|
7232
7500
|
resolvedAmbiguousSeeds.set(result.path, null);
|
|
7233
7501
|
return null;
|
|
7234
7502
|
}
|
|
@@ -7253,7 +7521,7 @@ ${r.snippet.trim()}
|
|
|
7253
7521
|
}
|
|
7254
7522
|
continue;
|
|
7255
7523
|
}
|
|
7256
|
-
if (
|
|
7524
|
+
if (path5.isAbsolute(result.path)) {
|
|
7257
7525
|
const resolved = await resolveAmbiguousSeedOwner(result, null);
|
|
7258
7526
|
if (resolved) {
|
|
7259
7527
|
addResultForNamespace(resolved.namespace, resolved.result);
|
|
@@ -7296,7 +7564,7 @@ ${r.snippet.trim()}
|
|
|
7296
7564
|
0
|
|
7297
7565
|
);
|
|
7298
7566
|
seedPaths.push(
|
|
7299
|
-
...seedRelativePaths.map((rel) =>
|
|
7567
|
+
...seedRelativePaths.map((rel) => path5.join(storage.dir, rel))
|
|
7300
7568
|
);
|
|
7301
7569
|
const seedSet = new Set(seedRelativePaths);
|
|
7302
7570
|
const expanded = await this.graphIndexFor(storage).spreadingActivation(
|
|
@@ -7312,7 +7580,7 @@ ${r.snippet.trim()}
|
|
|
7312
7580
|
for (const candidate of expanded.slice(0, perNamespaceExpandedCap)) {
|
|
7313
7581
|
if (deadlineExpired()) break;
|
|
7314
7582
|
if (seedSet.has(candidate.path)) continue;
|
|
7315
|
-
const memoryPath =
|
|
7583
|
+
const memoryPath = path5.resolve(storage.dir, candidate.path);
|
|
7316
7584
|
const memory = await storage.readMemoryByPath(memoryPath);
|
|
7317
7585
|
if (deadlineExpired()) break;
|
|
7318
7586
|
if (!memory) continue;
|
|
@@ -7337,7 +7605,7 @@ ${r.snippet.trim()}
|
|
|
7337
7605
|
path: memory.path,
|
|
7338
7606
|
score,
|
|
7339
7607
|
namespace,
|
|
7340
|
-
seed:
|
|
7608
|
+
seed: path5.resolve(storage.dir, candidate.seed),
|
|
7341
7609
|
hopDepth: candidate.hopDepth,
|
|
7342
7610
|
decayedWeight: candidate.decayedWeight,
|
|
7343
7611
|
graphType: candidate.graphType,
|
|
@@ -7358,12 +7626,12 @@ ${r.snippet.trim()}
|
|
|
7358
7626
|
}
|
|
7359
7627
|
async recordLastGraphRecallSnapshot(options) {
|
|
7360
7628
|
try {
|
|
7361
|
-
const snapshotPath =
|
|
7629
|
+
const snapshotPath = path5.join(
|
|
7362
7630
|
options.storage.dir,
|
|
7363
7631
|
"state",
|
|
7364
7632
|
"last_graph_recall.json"
|
|
7365
7633
|
);
|
|
7366
|
-
await mkdir3(
|
|
7634
|
+
await mkdir3(path5.dirname(snapshotPath), { recursive: true });
|
|
7367
7635
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7368
7636
|
const totalSeedCount = options.seedPaths.length;
|
|
7369
7637
|
const totalExpandedCount = options.expandedPaths.length;
|
|
@@ -7397,12 +7665,12 @@ ${r.snippet.trim()}
|
|
|
7397
7665
|
}
|
|
7398
7666
|
async recordLastIntentSnapshot(options) {
|
|
7399
7667
|
try {
|
|
7400
|
-
const snapshotPath =
|
|
7668
|
+
const snapshotPath = path5.join(
|
|
7401
7669
|
options.storage.dir,
|
|
7402
7670
|
"state",
|
|
7403
7671
|
"last_intent.json"
|
|
7404
7672
|
);
|
|
7405
|
-
await mkdir3(
|
|
7673
|
+
await mkdir3(path5.dirname(snapshotPath), { recursive: true });
|
|
7406
7674
|
await writeFile3(
|
|
7407
7675
|
snapshotPath,
|
|
7408
7676
|
JSON.stringify(options.snapshot, null, 2),
|
|
@@ -7414,12 +7682,12 @@ ${r.snippet.trim()}
|
|
|
7414
7682
|
}
|
|
7415
7683
|
async recordLastQmdRecallSnapshot(options) {
|
|
7416
7684
|
try {
|
|
7417
|
-
const snapshotPath =
|
|
7685
|
+
const snapshotPath = path5.join(
|
|
7418
7686
|
options.storage.dir,
|
|
7419
7687
|
"state",
|
|
7420
7688
|
"last_qmd_recall.json"
|
|
7421
7689
|
);
|
|
7422
|
-
await mkdir3(
|
|
7690
|
+
await mkdir3(path5.dirname(snapshotPath), { recursive: true });
|
|
7423
7691
|
await writeFile3(
|
|
7424
7692
|
snapshotPath,
|
|
7425
7693
|
JSON.stringify(options.snapshot, null, 2),
|
|
@@ -7434,8 +7702,8 @@ ${r.snippet.trim()}
|
|
|
7434
7702
|
const stateDir = await this.resolveStateDirForNamespace(
|
|
7435
7703
|
options.namespace
|
|
7436
7704
|
);
|
|
7437
|
-
const snapshotPath =
|
|
7438
|
-
await mkdir3(
|
|
7705
|
+
const snapshotPath = path5.join(stateDir, "last_intent.json");
|
|
7706
|
+
await mkdir3(path5.dirname(snapshotPath), { recursive: true });
|
|
7439
7707
|
await writeFile3(
|
|
7440
7708
|
snapshotPath,
|
|
7441
7709
|
JSON.stringify(options.snapshot, null, 2),
|
|
@@ -7447,12 +7715,12 @@ ${r.snippet.trim()}
|
|
|
7447
7715
|
}
|
|
7448
7716
|
async resolveStateDirForNamespace(namespace) {
|
|
7449
7717
|
if (!resolveNamespaceCapabilities(this.config).namespaces) {
|
|
7450
|
-
return
|
|
7718
|
+
return path5.join(this.config.memoryDir, "state");
|
|
7451
7719
|
}
|
|
7452
7720
|
if (namespace !== this.config.defaultNamespace) {
|
|
7453
|
-
return
|
|
7721
|
+
return path5.join(this.config.memoryDir, "namespaces", namespace, "state");
|
|
7454
7722
|
}
|
|
7455
|
-
const candidate =
|
|
7723
|
+
const candidate = path5.join(
|
|
7456
7724
|
this.config.memoryDir,
|
|
7457
7725
|
"namespaces",
|
|
7458
7726
|
this.config.defaultNamespace
|
|
@@ -7460,11 +7728,11 @@ ${r.snippet.trim()}
|
|
|
7460
7728
|
try {
|
|
7461
7729
|
const candidateStat = await stat(candidate);
|
|
7462
7730
|
if (candidateStat.isDirectory()) {
|
|
7463
|
-
return
|
|
7731
|
+
return path5.join(candidate, "state");
|
|
7464
7732
|
}
|
|
7465
7733
|
} catch {
|
|
7466
7734
|
}
|
|
7467
|
-
return
|
|
7735
|
+
return path5.join(this.config.memoryDir, "state");
|
|
7468
7736
|
}
|
|
7469
7737
|
buildGraphRecallRankedResults(results, sourceLabelResolver, limit = 64) {
|
|
7470
7738
|
return results.slice(0, limit).map((result) => ({
|
|
@@ -7866,7 +8134,7 @@ ${r.snippet.trim()}
|
|
|
7866
8134
|
const graphExpandedResultPaths = /* @__PURE__ */ new Set();
|
|
7867
8135
|
const graphSourceLabelsForPath = (resultPath) => {
|
|
7868
8136
|
const labels = [];
|
|
7869
|
-
const normalizedPath = resultPath.split(
|
|
8137
|
+
const normalizedPath = resultPath.split(path5.sep).join("/");
|
|
7870
8138
|
const isEntityPath = normalizedPath.startsWith("entities/") || normalizedPath.includes("/entities/");
|
|
7871
8139
|
if (graphBaselinePaths.has(resultPath)) labels.push("baseline");
|
|
7872
8140
|
if (graphExpandedResultPaths.has(resultPath))
|
|
@@ -8016,7 +8284,7 @@ ${r.snippet.trim()}
|
|
|
8016
8284
|
profileStorageNamespaces.map((namespace) => this.storageRouter.storageFor(namespace))
|
|
8017
8285
|
);
|
|
8018
8286
|
const emptyProfileStorage = new Proxy(
|
|
8019
|
-
{ dir:
|
|
8287
|
+
{ dir: path5.join(this.config.memoryDir, ".empty-scope-profile") },
|
|
8020
8288
|
{
|
|
8021
8289
|
get(target, prop) {
|
|
8022
8290
|
if (prop in target) return target[prop];
|
|
@@ -9492,11 +9760,11 @@ ${formatted}`;
|
|
|
9492
9760
|
if (!resolveRecallAuxiliaryCapabilities(this.config).compactionReset) return null;
|
|
9493
9761
|
const workspaceDir = compactionWorkspaceDir || this.config.workspaceDir || defaultWorkspaceDir();
|
|
9494
9762
|
const safeSessionKey = sanitizeSessionKeyForFilename(effectiveSessionKey);
|
|
9495
|
-
const signalPath =
|
|
9763
|
+
const signalPath = path5.join(
|
|
9496
9764
|
workspaceDir,
|
|
9497
9765
|
`.compaction-reset-signal-${safeSessionKey}`
|
|
9498
9766
|
);
|
|
9499
|
-
const bootPath =
|
|
9767
|
+
const bootPath = path5.join(workspaceDir, "BOOT.md");
|
|
9500
9768
|
try {
|
|
9501
9769
|
const signalStat = await stat(signalPath).catch(() => null);
|
|
9502
9770
|
if (!signalStat) return null;
|
|
@@ -14098,7 +14366,7 @@ ${normalized}`).digest("hex");
|
|
|
14098
14366
|
const allMems = allMemsForGraph ?? [];
|
|
14099
14367
|
for (const m of allMems) {
|
|
14100
14368
|
if (m.frontmatter.entityRef === entityRef) {
|
|
14101
|
-
const rel =
|
|
14369
|
+
const rel = path5.relative(storage.dir, m.path);
|
|
14102
14370
|
if (rel !== memoryRelPath) entitySiblings.push(rel);
|
|
14103
14371
|
}
|
|
14104
14372
|
}
|
|
@@ -14422,7 +14690,7 @@ ${normalized}`).digest("hex");
|
|
|
14422
14690
|
}
|
|
14423
14691
|
if (resolveConsolidationCapabilities(this.config).semanticConsolidation) {
|
|
14424
14692
|
try {
|
|
14425
|
-
const stateFilePath =
|
|
14693
|
+
const stateFilePath = path5.join(
|
|
14426
14694
|
this.config.memoryDir,
|
|
14427
14695
|
"state",
|
|
14428
14696
|
"semantic-consolidation-last-run.json"
|
|
@@ -14470,7 +14738,7 @@ ${normalized}`).digest("hex");
|
|
|
14470
14738
|
);
|
|
14471
14739
|
}
|
|
14472
14740
|
if (semResult.errors === 0 || semResult.memoriesArchived > 0) {
|
|
14473
|
-
const stateDir =
|
|
14741
|
+
const stateDir = path5.join(this.config.memoryDir, "state");
|
|
14474
14742
|
await mkdir3(stateDir, { recursive: true });
|
|
14475
14743
|
await writeFile3(
|
|
14476
14744
|
stateFilePath,
|
|
@@ -14791,7 +15059,7 @@ ${reflectionsContent.trim()}
|
|
|
14791
15059
|
const seenStorageDirs = /* @__PURE__ */ new Set();
|
|
14792
15060
|
const addStorage = (storage) => {
|
|
14793
15061
|
const storageDir = storageDirFor(storage);
|
|
14794
|
-
const storageKey = storageDir ?
|
|
15062
|
+
const storageKey = storageDir ? path5.resolve(storageDir) : `storage-without-dir-${storages.length}`;
|
|
14795
15063
|
if (seenStorageDirs.has(storageKey)) return;
|
|
14796
15064
|
seenStorageDirs.add(storageKey);
|
|
14797
15065
|
storages.push(storage);
|
|
@@ -14821,7 +15089,7 @@ ${reflectionsContent.trim()}
|
|
|
14821
15089
|
continue;
|
|
14822
15090
|
}
|
|
14823
15091
|
try {
|
|
14824
|
-
const coldRoot =
|
|
15092
|
+
const coldRoot = path5.join(storageDir, "cold");
|
|
14825
15093
|
for (const candidate of qmdResultPathCandidates(
|
|
14826
15094
|
coldRoot,
|
|
14827
15095
|
parts.relativePath
|
|
@@ -14862,7 +15130,7 @@ ${reflectionsContent.trim()}
|
|
|
14862
15130
|
return null;
|
|
14863
15131
|
}
|
|
14864
15132
|
}
|
|
14865
|
-
if (
|
|
15133
|
+
if (path5.isAbsolute(resultPath)) {
|
|
14866
15134
|
if (!fallbackStorageDir) {
|
|
14867
15135
|
return await fallbackStorage.readMemoryByPath(resultPath);
|
|
14868
15136
|
}
|
|
@@ -14901,7 +15169,7 @@ ${reflectionsContent.trim()}
|
|
|
14901
15169
|
);
|
|
14902
15170
|
if (!memory) return null;
|
|
14903
15171
|
let ownerNamespace = null;
|
|
14904
|
-
if (
|
|
15172
|
+
if (path5.isAbsolute(memory.path)) {
|
|
14905
15173
|
const ownerStorage = await this.storageForAbsoluteQmdResultPath(
|
|
14906
15174
|
memory.path,
|
|
14907
15175
|
fallbackStorage,
|
|
@@ -14925,16 +15193,16 @@ ${reflectionsContent.trim()}
|
|
|
14925
15193
|
};
|
|
14926
15194
|
}
|
|
14927
15195
|
async storageForAbsoluteQmdResultPath(resultPath, fallbackStorage, recallNamespaces = []) {
|
|
14928
|
-
const resolvedPath =
|
|
14929
|
-
const memoryRoot =
|
|
14930
|
-
const namespacesRoot =
|
|
15196
|
+
const resolvedPath = path5.resolve(resultPath);
|
|
15197
|
+
const memoryRoot = path5.resolve(this.config.memoryDir);
|
|
15198
|
+
const namespacesRoot = path5.join(memoryRoot, "namespaces");
|
|
14931
15199
|
const fallbackStorageDir = typeof fallbackStorage.dir === "string" && fallbackStorage.dir ? fallbackStorage.dir : null;
|
|
14932
15200
|
const matches = [];
|
|
14933
15201
|
const seenDirs = /* @__PURE__ */ new Set();
|
|
14934
15202
|
const maybeAddStorage = (storage, namespace) => {
|
|
14935
15203
|
const storageDir = typeof storage.dir === "string" && storage.dir ? storage.dir : null;
|
|
14936
15204
|
if (!storageDir) return;
|
|
14937
|
-
const candidateRoot =
|
|
15205
|
+
const candidateRoot = path5.resolve(storageDir);
|
|
14938
15206
|
if (seenDirs.has(candidateRoot)) return;
|
|
14939
15207
|
if (!isPathInsideStorageRoot(candidateRoot, resolvedPath)) return;
|
|
14940
15208
|
if (candidateRoot === memoryRoot && isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
|
|
@@ -14952,7 +15220,7 @@ ${reflectionsContent.trim()}
|
|
|
14952
15220
|
candidateNamespaces.add(ns);
|
|
14953
15221
|
}
|
|
14954
15222
|
if (isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
|
|
14955
|
-
const relativeToNamespaces =
|
|
15223
|
+
const relativeToNamespaces = path5.relative(namespacesRoot, resolvedPath);
|
|
14956
15224
|
const [namespaceSegment] = relativeToNamespaces.split(/[\\/]/);
|
|
14957
15225
|
if (namespaceSegment) {
|
|
14958
15226
|
candidateNamespaces.add(
|
|
@@ -14974,218 +15242,22 @@ ${reflectionsContent.trim()}
|
|
|
14974
15242
|
matches.sort((a, b) => b.dir.length - a.dir.length);
|
|
14975
15243
|
return matches[0] ?? null;
|
|
14976
15244
|
}
|
|
15245
|
+
// Issue #1526: recall-rerank methods moved to RecallRerankCoordinator.
|
|
15246
|
+
// Thin delegation keeps the private API stable for callers + tests.
|
|
14977
15247
|
async applyMemoryWorthRerank(results, namespaces) {
|
|
14978
|
-
|
|
14979
|
-
const seenNamespaces = /* @__PURE__ */ new Set();
|
|
14980
|
-
const nowMs = Date.now();
|
|
14981
|
-
for (const [key, entry] of this.memoryWorthCounterCache) {
|
|
14982
|
-
if (nowMs - entry.at >= _Orchestrator.MEMORY_WORTH_CACHE_TTL_MS) {
|
|
14983
|
-
this.memoryWorthCounterCache.delete(key);
|
|
14984
|
-
}
|
|
14985
|
-
}
|
|
14986
|
-
for (const ns of namespaces) {
|
|
14987
|
-
if (seenNamespaces.has(ns)) continue;
|
|
14988
|
-
seenNamespaces.add(ns);
|
|
14989
|
-
try {
|
|
14990
|
-
const cached = this.memoryWorthCounterCache.get(ns);
|
|
14991
|
-
let nsMap;
|
|
14992
|
-
if (cached && nowMs - cached.at < _Orchestrator.MEMORY_WORTH_CACHE_TTL_MS) {
|
|
14993
|
-
nsMap = cached.counters;
|
|
14994
|
-
} else {
|
|
14995
|
-
const storage = await this.getStorage(ns);
|
|
14996
|
-
const memories = await storage.readAllMemories();
|
|
14997
|
-
nsMap = buildMemoryWorthCounterMap(memories);
|
|
14998
|
-
this.memoryWorthCounterCache.set(ns, { at: nowMs, counters: nsMap });
|
|
14999
|
-
}
|
|
15000
|
-
for (const [path5, c] of nsMap) counters.set(path5, c);
|
|
15001
|
-
} catch (err) {
|
|
15002
|
-
log.debug("memory-worth: failed to read namespace, skipping", {
|
|
15003
|
-
namespace: ns,
|
|
15004
|
-
error: err.message
|
|
15005
|
-
});
|
|
15006
|
-
}
|
|
15007
|
-
}
|
|
15008
|
-
const missing = results.filter((r) => !counters.has(r.path));
|
|
15009
|
-
if (missing.length > 0) {
|
|
15010
|
-
let reader = null;
|
|
15011
|
-
for (const ns of namespaces) {
|
|
15012
|
-
try {
|
|
15013
|
-
reader = await this.getStorage(ns);
|
|
15014
|
-
break;
|
|
15015
|
-
} catch {
|
|
15016
|
-
}
|
|
15017
|
-
}
|
|
15018
|
-
if (reader) {
|
|
15019
|
-
for (const r of missing) {
|
|
15020
|
-
try {
|
|
15021
|
-
const memory = await this.readQmdResultMemory(r.path, reader, namespaces);
|
|
15022
|
-
if (!memory) continue;
|
|
15023
|
-
const fm = memory.frontmatter;
|
|
15024
|
-
if (fm.mw_success === void 0 && fm.mw_fail === void 0) continue;
|
|
15025
|
-
counters.set(r.path, {
|
|
15026
|
-
mw_success: fm.mw_success,
|
|
15027
|
-
mw_fail: fm.mw_fail,
|
|
15028
|
-
lastAccessed: fm.lastAccessed
|
|
15029
|
-
});
|
|
15030
|
-
} catch (err) {
|
|
15031
|
-
log.debug("memory-worth: direct path lookup failed", {
|
|
15032
|
-
path: r.path,
|
|
15033
|
-
error: err.message
|
|
15034
|
-
});
|
|
15035
|
-
}
|
|
15036
|
-
}
|
|
15037
|
-
}
|
|
15038
|
-
}
|
|
15039
|
-
if (counters.size === 0) return results;
|
|
15040
|
-
const rankedInputs = results.map((r, i) => ({
|
|
15041
|
-
path: r.path,
|
|
15042
|
-
// Large positive rank score so multiplier math stays well-scaled and
|
|
15043
|
-
// we never hit zero; descending so earlier items rank higher.
|
|
15044
|
-
score: results.length - i
|
|
15045
|
-
}));
|
|
15046
|
-
const filtered = applyMemoryWorthFilter(rankedInputs, {
|
|
15047
|
-
counters,
|
|
15048
|
-
now: /* @__PURE__ */ new Date(),
|
|
15049
|
-
halfLifeMs: this.config.recallMemoryWorthHalfLifeMs > 0 ? this.config.recallMemoryWorthHalfLifeMs : void 0
|
|
15050
|
-
});
|
|
15051
|
-
const byPath = new Map(results.map((r) => [r.path, r]));
|
|
15052
|
-
const reordered = [];
|
|
15053
|
-
for (const item of filtered) {
|
|
15054
|
-
const original = byPath.get(item.path);
|
|
15055
|
-
if (original) reordered.push(original);
|
|
15056
|
-
}
|
|
15057
|
-
return reordered;
|
|
15248
|
+
return this.recallRerankCoordinator.applyMemoryWorthRerank(results, namespaces);
|
|
15058
15249
|
}
|
|
15059
|
-
/**
|
|
15060
|
-
* Issue #1577 — unified TrustScore recall stage. Thin wiring over the pure
|
|
15061
|
-
* {@link applyTrustScoreStage} scorer + the {@link buildTrustSignalsForRerank}
|
|
15062
|
-
* signal builder. The stage subsumes the Memory Worth multiplier — the
|
|
15063
|
-
* orchestrator runs exactly one of the two (mutual exclusion, rule 39; the
|
|
15064
|
-
* double-multiplier test in trust-score-stage.test.ts pins it structurally).
|
|
15065
|
-
*
|
|
15066
|
-
* Returns the admitted results AND the per-path trust map (including
|
|
15067
|
-
* quarantined items) so the caller can: (a) render epistemic hedges, (b)
|
|
15068
|
-
* surface quarantined items in X-ray with a reason (rule 34), and (c) filter
|
|
15069
|
-
* quarantined paths from fallback recall branches. The trust map is a
|
|
15070
|
-
* per-recall local — never instance state — so concurrent recalls cannot
|
|
15071
|
-
* race on it (review: shared-trust-map concurrency).
|
|
15072
|
-
*/
|
|
15073
15250
|
async applyTrustScoreRerank(results, namespaces) {
|
|
15074
|
-
|
|
15075
|
-
const now = /* @__PURE__ */ new Date();
|
|
15076
|
-
const halfLifeDays = this.config.recallMemoryWorthHalfLifeMs > 0 ? this.config.recallMemoryWorthHalfLifeMs / (24 * 60 * 60 * 1e3) : void 0;
|
|
15077
|
-
let fallbackReader = null;
|
|
15078
|
-
const signals = await buildTrustSignalsForRerank(
|
|
15079
|
-
results.map((r) => r.path),
|
|
15080
|
-
namespaces,
|
|
15081
|
-
{
|
|
15082
|
-
readNamespaceMemories: async (ns) => (await this.getStorage(ns)).readAllMemories(),
|
|
15083
|
-
readMemoryFrontmatter: async (path5) => {
|
|
15084
|
-
if (!fallbackReader) {
|
|
15085
|
-
for (const ns of namespaces) {
|
|
15086
|
-
try {
|
|
15087
|
-
fallbackReader = await this.getStorage(ns);
|
|
15088
|
-
break;
|
|
15089
|
-
} catch {
|
|
15090
|
-
}
|
|
15091
|
-
}
|
|
15092
|
-
}
|
|
15093
|
-
if (!fallbackReader) return null;
|
|
15094
|
-
const memory = await this.readQmdResultMemory(path5, fallbackReader, namespaces);
|
|
15095
|
-
return memory ? memory.frontmatter : null;
|
|
15096
|
-
}
|
|
15097
|
-
},
|
|
15098
|
-
{ cache: this.trustSignalCache, ttlMs: _Orchestrator.TRUST_SIGNAL_CACHE_TTL_MS },
|
|
15099
|
-
now,
|
|
15100
|
-
{
|
|
15101
|
-
recencyHalfLifeDays: halfLifeDays,
|
|
15102
|
-
logDebug: (message, context) => log.debug(message, context)
|
|
15103
|
-
}
|
|
15104
|
-
);
|
|
15105
|
-
if (signals.size === 0) {
|
|
15106
|
-
return { results, trustByPath: null };
|
|
15107
|
-
}
|
|
15108
|
-
const rankedInputs = results.map((r, i) => ({ path: r.path, score: results.length - i }));
|
|
15109
|
-
const stage = applyTrustScoreStage(rankedInputs, {
|
|
15110
|
-
signals,
|
|
15111
|
-
weights: this.config.trustScoreWeights,
|
|
15112
|
-
minMultiplier: this.config.trustScoreMinMultiplier,
|
|
15113
|
-
maxMultiplier: this.config.trustScoreMaxMultiplier,
|
|
15114
|
-
quarantine: this.config.trustScoreQuarantine
|
|
15115
|
-
});
|
|
15116
|
-
const trustByPath = new Map(stage.all.map((item) => [item.path, item]));
|
|
15117
|
-
const byPath = new Map(results.map((r) => [r.path, r]));
|
|
15118
|
-
const admitted = stage.admitted.map((item) => byPath.get(item.path)).filter((r) => r !== void 0);
|
|
15119
|
-
return { results: admitted, trustByPath };
|
|
15251
|
+
return this.recallRerankCoordinator.applyTrustScoreRerank(results, namespaces);
|
|
15120
15252
|
}
|
|
15121
|
-
/**
|
|
15122
|
-
* Issue #1577 — apply the TrustScore stage (or, when trust is off, the Memory
|
|
15123
|
-
* Worth multiplier fallback) to ONE recall branch's results, returning the
|
|
15124
|
-
* scored results + the per-path trust map. Thin wiring over
|
|
15125
|
-
* {@link applyTrustScoreRerank} so every recall path — hot QMD, embedding
|
|
15126
|
-
* fallback, recent scan — applies the SAME multiplier gate (rule 41: a
|
|
15127
|
-
* feature gate must apply across ALL parallel recall paths). TrustScore
|
|
15128
|
-
* subsumes Memory Worth; exactly one runs (rule 39). Fail-open on lookup
|
|
15129
|
-
* errors so a storage hiccup never breaks a fallback path.
|
|
15130
|
-
*/
|
|
15131
15253
|
async applyTrustScoreToBranch(results, namespaces, caps, label) {
|
|
15132
|
-
|
|
15133
|
-
try {
|
|
15134
|
-
return await this.applyTrustScoreRerank(results, namespaces);
|
|
15135
|
-
} catch (err) {
|
|
15136
|
-
log.debug(`trust-score stage (${label}) failed open`, {
|
|
15137
|
-
error: err.message
|
|
15138
|
-
});
|
|
15139
|
-
}
|
|
15140
|
-
} else if (caps.recallMemoryWorthFilter && results.length > 0) {
|
|
15141
|
-
try {
|
|
15142
|
-
const filtered = await this.applyMemoryWorthRerank(results, namespaces);
|
|
15143
|
-
return { results: filtered, trustByPath: null };
|
|
15144
|
-
} catch (err) {
|
|
15145
|
-
log.debug(`memory-worth filter (${label}) failed open`, {
|
|
15146
|
-
error: err.message
|
|
15147
|
-
});
|
|
15148
|
-
}
|
|
15149
|
-
}
|
|
15150
|
-
return { results, trustByPath: null };
|
|
15254
|
+
return this.recallRerankCoordinator.applyTrustScoreToBranch(results, namespaces, caps, label);
|
|
15151
15255
|
}
|
|
15152
15256
|
diversifyAndLimitRecallResults(sectionId, results, limit, retrievalQuery, caps = resolveCapabilities(this.config)) {
|
|
15153
|
-
|
|
15154
|
-
if (!Array.isArray(results) || results.length === 0) return [];
|
|
15155
|
-
if (safeLimit === 0) return [];
|
|
15156
|
-
const boosted = caps.recallReasoningTraceBoost && typeof retrievalQuery === "string" ? applyReasoningTraceBoost(results, {
|
|
15157
|
-
enabled: true,
|
|
15158
|
-
query: retrievalQuery
|
|
15159
|
-
}) : results;
|
|
15160
|
-
const diversified = this.applyMmrToQmdResults(sectionId, boosted, caps);
|
|
15161
|
-
return diversified.slice(0, safeLimit);
|
|
15257
|
+
return this.recallRerankCoordinator.diversifyAndLimitRecallResults(sectionId, results, limit, retrievalQuery, caps);
|
|
15162
15258
|
}
|
|
15163
|
-
/**
|
|
15164
|
-
* Apply Maximal Marginal Relevance to a section's ordered candidate list.
|
|
15165
|
-
*
|
|
15166
|
-
* Operates per-section so one redundant cluster cannot dominate a section,
|
|
15167
|
-
* and so one section's MMR pass cannot starve other sections. Returns the
|
|
15168
|
-
* input unchanged when disabled, when there are fewer than 2 candidates, or
|
|
15169
|
-
* when no budget information is available.
|
|
15170
|
-
*/
|
|
15171
15259
|
applyMmrToQmdResults(sectionId, results, caps = resolveCapabilities(this.config)) {
|
|
15172
|
-
|
|
15173
|
-
if (!Array.isArray(results) || results.length < 2) return results;
|
|
15174
|
-
const configuredTopN = this.config.recallMmrTopN;
|
|
15175
|
-
const topN = typeof configuredTopN === "number" && Number.isFinite(configuredTopN) ? Math.max(0, Math.floor(configuredTopN)) : 40;
|
|
15176
|
-
if (topN === 0) return results;
|
|
15177
|
-
const lambda = this.config.recallMmrLambda ?? 0.7;
|
|
15178
|
-
const { reordered, diversity } = reorderRecallResultsWithMmr(results, {
|
|
15179
|
-
lambda,
|
|
15180
|
-
topN
|
|
15181
|
-
});
|
|
15182
|
-
try {
|
|
15183
|
-
log.info(
|
|
15184
|
-
`recall_mmr: section=${sectionId} kept=${diversity.kept}/${diversity.considered} headReorderCount=${diversity.headReorderCount} avgSimBefore=${diversity.avgPairwiseSimBefore.toFixed(3)} avgSimAfter=${diversity.avgPairwiseSimAfter.toFixed(3)} lambda=${lambda.toFixed(2)}`
|
|
15185
|
-
);
|
|
15186
|
-
} catch {
|
|
15187
|
-
}
|
|
15188
|
-
return reordered;
|
|
15260
|
+
return this.recallRerankCoordinator.applyMmrToQmdResults(sectionId, results, caps);
|
|
15189
15261
|
}
|
|
15190
15262
|
buildLastRecallBudgetSummary(options) {
|
|
15191
15263
|
return {
|
|
@@ -15261,12 +15333,12 @@ ${reflectionsContent.trim()}
|
|
|
15261
15333
|
*/
|
|
15262
15334
|
semanticDedupScopeFor(targetStorage) {
|
|
15263
15335
|
if (!resolveNamespaceCapabilities(this.config).namespaces) return {};
|
|
15264
|
-
const memoryDir =
|
|
15265
|
-
const storageDir =
|
|
15336
|
+
const memoryDir = path5.resolve(this.config.memoryDir);
|
|
15337
|
+
const storageDir = path5.resolve(targetStorage.dir);
|
|
15266
15338
|
if (storageDir === memoryDir) {
|
|
15267
15339
|
return { pathExcludePrefixes: ["namespaces/"] };
|
|
15268
15340
|
}
|
|
15269
|
-
let rel =
|
|
15341
|
+
let rel = path5.relative(memoryDir, storageDir);
|
|
15270
15342
|
if (!rel || rel.startsWith("..")) {
|
|
15271
15343
|
log.debug(
|
|
15272
15344
|
`semantic dedup: target storage dir ${storageDir} is outside memoryDir ${memoryDir}; scoping lookup to absolute path prefix`
|
|
@@ -15285,7 +15357,7 @@ ${reflectionsContent.trim()}
|
|
|
15285
15357
|
if (hits.length === 0) return [];
|
|
15286
15358
|
const results = [];
|
|
15287
15359
|
for (const hit of hits) {
|
|
15288
|
-
const fullPath =
|
|
15360
|
+
const fullPath = path5.isAbsolute(hit.path) ? hit.path : path5.join(this.config.memoryDir, hit.path);
|
|
15289
15361
|
const memory = await this.storage.readMemoryByPath(fullPath);
|
|
15290
15362
|
if (!memory) continue;
|
|
15291
15363
|
results.push({
|
|
@@ -15482,7 +15554,7 @@ ${reflectionsContent.trim()}
|
|
|
15482
15554
|
const storage = await this.storageRouter.storageFor(namespace);
|
|
15483
15555
|
const storageDir = typeof storage.dir === "string" && storage.dir ? storage.dir : null;
|
|
15484
15556
|
if (!storageDir) continue;
|
|
15485
|
-
const recallRoot =
|
|
15557
|
+
const recallRoot = path5.resolve(storageDir);
|
|
15486
15558
|
if (seenRecallRoots.has(recallRoot)) continue;
|
|
15487
15559
|
seenRecallRoots.add(recallRoot);
|
|
15488
15560
|
recallRoots.push(recallRoot);
|
|
@@ -15506,8 +15578,8 @@ ${reflectionsContent.trim()}
|
|
|
15506
15578
|
if (resolvedCold) scopedResults.push(resolvedCold.result);
|
|
15507
15579
|
continue;
|
|
15508
15580
|
}
|
|
15509
|
-
if (
|
|
15510
|
-
const resolvedPath =
|
|
15581
|
+
if (path5.isAbsolute(result.path)) {
|
|
15582
|
+
const resolvedPath = path5.resolve(result.path);
|
|
15511
15583
|
if (recallRoots.some(
|
|
15512
15584
|
(recallRoot) => isPathInsideStorageRoot(recallRoot, resolvedPath)
|
|
15513
15585
|
)) {
|
|
@@ -16441,4 +16513,4 @@ export {
|
|
|
16441
16513
|
resolvePersistedMemoryRelativePath,
|
|
16442
16514
|
Orchestrator
|
|
16443
16515
|
};
|
|
16444
|
-
//# sourceMappingURL=chunk-
|
|
16516
|
+
//# sourceMappingURL=chunk-IEWUPZ75.js.map
|