@remnic/core 9.3.748 → 9.3.749
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 +2 -2
- package/dist/access-cli.js +1 -1
- package/dist/access-http.d.ts +2 -2
- package/dist/access-mcp.d.ts +2 -2
- package/dist/access-operations.d.ts +2 -2
- package/dist/{access-service-CH1aqqb1.d.ts → access-service-5-EVpt3v.d.ts} +1 -1
- package/dist/access-service.d.ts +2 -2
- package/dist/access-surface-catalog.d.ts +2 -2
- package/dist/bootstrap.d.ts +1 -1
- package/dist/{chunk-ZWGXZ7ID.js → chunk-PNUOQYRJ.js} +560 -457
- package/dist/chunk-PNUOQYRJ.js.map +1 -0
- package/dist/{cli-CE5olss2.d.ts → cli-DuWWjaKT.d.ts} +2 -2
- package/dist/cli.d.ts +3 -3
- package/dist/explicit-capture.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/mcp-memory-inspector-app.d.ts +2 -2
- package/dist/{orchestrator-CIOqdNzF.d.ts → orchestrator-CZI6hO_R.d.ts} +7 -56
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +1 -1
- package/package.json +2 -2
- package/src/orchestration/persistence-index.ts +497 -0
- package/src/orchestrator.ts +69 -365
- package/dist/chunk-ZWGXZ7ID.js.map +0 -1
|
@@ -538,7 +538,7 @@ import {
|
|
|
538
538
|
} from "./chunk-PVGDJXVK.js";
|
|
539
539
|
|
|
540
540
|
// src/orchestrator.ts
|
|
541
|
-
import
|
|
541
|
+
import path13 from "path";
|
|
542
542
|
import os2 from "os";
|
|
543
543
|
import { createHash as createHash7 } from "crypto";
|
|
544
544
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -2168,7 +2168,7 @@ var RecallRerankCoordinator = class _RecallRerankCoordinator {
|
|
|
2168
2168
|
nsMap = buildMemoryWorthCounterMap(memories);
|
|
2169
2169
|
this.memoryWorthCounterCache.set(ns, { at: nowMs, counters: nsMap });
|
|
2170
2170
|
}
|
|
2171
|
-
for (const [
|
|
2171
|
+
for (const [path28, c] of nsMap) counters.set(path28, c);
|
|
2172
2172
|
} catch (err) {
|
|
2173
2173
|
log.debug("memory-worth: failed to read namespace, skipping", {
|
|
2174
2174
|
namespace: ns,
|
|
@@ -2253,7 +2253,7 @@ var RecallRerankCoordinator = class _RecallRerankCoordinator {
|
|
|
2253
2253
|
namespaces,
|
|
2254
2254
|
{
|
|
2255
2255
|
readNamespaceMemories: async (ns) => (await this.getStorage(ns)).readAllMemories(),
|
|
2256
|
-
readMemoryFrontmatter: async (
|
|
2256
|
+
readMemoryFrontmatter: async (path28) => {
|
|
2257
2257
|
if (!fallbackReader) {
|
|
2258
2258
|
for (const ns of namespaces) {
|
|
2259
2259
|
try {
|
|
@@ -2264,7 +2264,7 @@ var RecallRerankCoordinator = class _RecallRerankCoordinator {
|
|
|
2264
2264
|
}
|
|
2265
2265
|
}
|
|
2266
2266
|
if (!fallbackReader) return null;
|
|
2267
|
-
const memory = await this.readQmdResultMemory(
|
|
2267
|
+
const memory = await this.readQmdResultMemory(path28, fallbackReader, namespaces);
|
|
2268
2268
|
return memory ? memory.frontmatter : null;
|
|
2269
2269
|
}
|
|
2270
2270
|
},
|
|
@@ -12663,6 +12663,278 @@ var OrchestratorInitCoordinator = class {
|
|
|
12663
12663
|
}
|
|
12664
12664
|
};
|
|
12665
12665
|
|
|
12666
|
+
// src/orchestration/persistence-index.ts
|
|
12667
|
+
import path12 from "path";
|
|
12668
|
+
var PersistenceIndexCoordinator = class {
|
|
12669
|
+
constructor(deps) {
|
|
12670
|
+
this.deps = deps;
|
|
12671
|
+
}
|
|
12672
|
+
deps;
|
|
12673
|
+
async hasContentHashDedup(targetStorage, content) {
|
|
12674
|
+
const index = await this.deps.contentHashIndexForStorage(targetStorage);
|
|
12675
|
+
return index ? index.has(content) : false;
|
|
12676
|
+
}
|
|
12677
|
+
async addContentHashDedup(targetStorage, content) {
|
|
12678
|
+
const index = await this.deps.contentHashIndexForStorage(targetStorage);
|
|
12679
|
+
if (!index) return;
|
|
12680
|
+
index.add(content);
|
|
12681
|
+
}
|
|
12682
|
+
async removeContentHashForMemory(targetStorage, memory, context) {
|
|
12683
|
+
const index = await this.deps.contentHashIndexForStorage(targetStorage);
|
|
12684
|
+
if (!index) return;
|
|
12685
|
+
if (memory.frontmatter.contentHash) {
|
|
12686
|
+
index.removeByHash(memory.frontmatter.contentHash);
|
|
12687
|
+
return;
|
|
12688
|
+
}
|
|
12689
|
+
log.warn(
|
|
12690
|
+
`[${context}] removing hash for legacy memory ${memory.frontmatter.id ?? "(unknown)"} via content fallback - no contentHash in frontmatter`
|
|
12691
|
+
);
|
|
12692
|
+
index.remove(memory.content);
|
|
12693
|
+
}
|
|
12694
|
+
/**
|
|
12695
|
+
* Issue #1671 — backfill bi-temporal bounds onto an existing promoted/deduped
|
|
12696
|
+
* copy that was written BEFORE the source fact carried a resolved
|
|
12697
|
+
* `invalid_at`/`observedAt`/`eventTimeSource`.
|
|
12698
|
+
*
|
|
12699
|
+
* On re-extraction/backfill, a fact may now carry a resolved end bound (e.g.
|
|
12700
|
+
* "until June 2025") that the existing copy lacks because it was promoted
|
|
12701
|
+
* before bi-temporal wiring existed. Without this backfill, recall keeps
|
|
12702
|
+
* surfacing an expired fact even though the source copy now expires correctly.
|
|
12703
|
+
*
|
|
12704
|
+
* Finds the active fact in `targetStorage` matching `dedupContent`, then
|
|
12705
|
+
* patches the temporal frontmatter the existing copy is missing. Best-effort
|
|
12706
|
+
* / fail-open — any I/O error is logged and swallowed so the dedup
|
|
12707
|
+
* short-circuit is never blocked by a backfill failure.
|
|
12708
|
+
*
|
|
12709
|
+
* Matching: the stored `frontmatter.contentHash` is compared against
|
|
12710
|
+
* `ContentHashIndex.computeHash(dedupContent)` first (the exact hash the
|
|
12711
|
+
* content-hash index uses), then falls back to stripping citations and
|
|
12712
|
+
* comparing normalized bodies. This handles inline-attribution deployments
|
|
12713
|
+
* where the persisted body carries a citation marker the dedup key does not.
|
|
12714
|
+
*
|
|
12715
|
+
* I/O gate: only triggers when `invalidAt` is present (the end bound that
|
|
12716
|
+
* actually changes recall behavior by expiring the fact). `observedAt` and
|
|
12717
|
+
* `eventTimeSource` alone don'\''t expire a fact, so backfilling them without
|
|
12718
|
+
* an end bound would cause a full readAllMemories scan on every dedup hit
|
|
12719
|
+
* under biTemporal for no recall benefit.
|
|
12720
|
+
*
|
|
12721
|
+
* Only patches fields the existing copy LACKS — never overwrites a bound the
|
|
12722
|
+
* copy already carries.
|
|
12723
|
+
*/
|
|
12724
|
+
async backfillTemporalBoundsOnDedupHit(targetStorage, dedupContent, bounds, entityRef) {
|
|
12725
|
+
const hasExtractedStart = bounds.validFrom !== void 0 && bounds.eventTimeSource === "extracted";
|
|
12726
|
+
if (!bounds.invalidAt && !hasExtractedStart) return;
|
|
12727
|
+
try {
|
|
12728
|
+
const incomingHash = ContentHashIndex.computeHash(dedupContent);
|
|
12729
|
+
const normalizedIncoming = ContentHashIndex.normalizeContent(dedupContent);
|
|
12730
|
+
const incomingEntityNorm = entityRef ? normalizeSupersessionKey(entityRef) : void 0;
|
|
12731
|
+
const all = await targetStorage.readAllMemories();
|
|
12732
|
+
const existing = all.find((m) => {
|
|
12733
|
+
if (m.frontmatter.category !== "fact") return false;
|
|
12734
|
+
if ((m.frontmatter.status ?? "active") !== "active") return false;
|
|
12735
|
+
if (incomingEntityNorm && m.frontmatter.entityRef && normalizeSupersessionKey(m.frontmatter.entityRef) !== incomingEntityNorm) {
|
|
12736
|
+
return false;
|
|
12737
|
+
}
|
|
12738
|
+
if (m.frontmatter.contentHash) {
|
|
12739
|
+
return m.frontmatter.contentHash === incomingHash;
|
|
12740
|
+
}
|
|
12741
|
+
return ContentHashIndex.normalizeContent(
|
|
12742
|
+
stripCitationForTemplate(m.content ?? "", this.deps.config.inlineSourceAttributionFormat)
|
|
12743
|
+
) === normalizedIncoming;
|
|
12744
|
+
});
|
|
12745
|
+
if (!existing) return;
|
|
12746
|
+
const patch = {};
|
|
12747
|
+
const fm = existing.frontmatter;
|
|
12748
|
+
if (bounds.invalidAt && (!fm.invalid_at || fm.invalid_at.length === 0)) {
|
|
12749
|
+
patch.invalid_at = bounds.invalidAt;
|
|
12750
|
+
}
|
|
12751
|
+
if (bounds.observedAt && (!fm.observedAt || fm.observedAt.length === 0)) {
|
|
12752
|
+
patch.observedAt = bounds.observedAt;
|
|
12753
|
+
}
|
|
12754
|
+
if (bounds.eventTimeSource && (!fm.eventTimeSource || fm.eventTimeSource.length === 0)) {
|
|
12755
|
+
patch.eventTimeSource = bounds.eventTimeSource;
|
|
12756
|
+
}
|
|
12757
|
+
if (bounds.validFrom && bounds.eventTimeSource === "extracted" && fm.valid_at !== bounds.validFrom) {
|
|
12758
|
+
patch.valid_at = bounds.validFrom;
|
|
12759
|
+
if (fm.eventTimeSource !== "extracted") {
|
|
12760
|
+
patch.eventTimeSource = "extracted";
|
|
12761
|
+
}
|
|
12762
|
+
}
|
|
12763
|
+
if (Object.keys(patch).length === 0) return;
|
|
12764
|
+
const ok = await targetStorage.writeMemoryFrontmatter(existing, patch);
|
|
12765
|
+
if (ok) {
|
|
12766
|
+
log.debug(
|
|
12767
|
+
`bitemporal-backfill: patched ${Object.keys(patch).join(",")} onto existing fact ${fm.id ?? "(unknown)"} in ${targetStorage.dir}`
|
|
12768
|
+
);
|
|
12769
|
+
}
|
|
12770
|
+
} catch (err) {
|
|
12771
|
+
log.warn(
|
|
12772
|
+
`bitemporal-backfill: failed open for ${targetStorage.dir}: ${err}`
|
|
12773
|
+
);
|
|
12774
|
+
}
|
|
12775
|
+
}
|
|
12776
|
+
async saveContentHashIndexes() {
|
|
12777
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
12778
|
+
if (this.deps.contentHashIndex) indexes.add(this.deps.contentHashIndex);
|
|
12779
|
+
for (const index of this.deps.contentHashIndexesByStorageDir.values()) {
|
|
12780
|
+
indexes.add(index);
|
|
12781
|
+
}
|
|
12782
|
+
for (const index of indexes) {
|
|
12783
|
+
await index.save();
|
|
12784
|
+
}
|
|
12785
|
+
}
|
|
12786
|
+
async indexPersistedMemory(storage, memoryId) {
|
|
12787
|
+
if (!resolveMemoryLifecycleCapabilities(this.deps.config).embeddingFallback) return;
|
|
12788
|
+
if (!await this.deps.embeddingFallback.isAvailable()) return;
|
|
12789
|
+
const memory = await storage.getMemoryById(memoryId);
|
|
12790
|
+
if (!memory) return;
|
|
12791
|
+
await this.deps.embeddingFallback.indexFile(
|
|
12792
|
+
memoryId,
|
|
12793
|
+
memory.content,
|
|
12794
|
+
memory.path
|
|
12795
|
+
);
|
|
12796
|
+
}
|
|
12797
|
+
/**
|
|
12798
|
+
* Build a graph edge for a persisted memory (v8.2).
|
|
12799
|
+
* Shared helper used by both the chunked and non-chunked write paths to avoid duplication.
|
|
12800
|
+
* Fail-open: caller wraps in try/catch.
|
|
12801
|
+
*/
|
|
12802
|
+
async buildGraphEdge(storage, memoryRelPath, entityRef, memoryId, factContent, allMemsForGraph, memoryPathById, threadIdForEdge, threadEpisodeIdsForGraph, fallbackCausalPredecessor, graphCaps = resolveGraphConstructionCapabilities(this.deps.config)) {
|
|
12803
|
+
const entitySiblings = [];
|
|
12804
|
+
if (entityRef) {
|
|
12805
|
+
try {
|
|
12806
|
+
const allMems = allMemsForGraph ?? [];
|
|
12807
|
+
for (const m of allMems) {
|
|
12808
|
+
if (m.frontmatter.entityRef === entityRef) {
|
|
12809
|
+
const rel = path12.relative(storage.dir, m.path);
|
|
12810
|
+
if (rel !== memoryRelPath) entitySiblings.push(rel);
|
|
12811
|
+
}
|
|
12812
|
+
}
|
|
12813
|
+
} catch {
|
|
12814
|
+
}
|
|
12815
|
+
}
|
|
12816
|
+
const recentInThread = [];
|
|
12817
|
+
if (threadIdForEdge && threadEpisodeIdsForGraph?.length) {
|
|
12818
|
+
try {
|
|
12819
|
+
recentInThread.push(
|
|
12820
|
+
...resolveRecentThreadMemoryPaths({
|
|
12821
|
+
threadEpisodeIds: threadEpisodeIdsForGraph,
|
|
12822
|
+
currentMemoryId: memoryId,
|
|
12823
|
+
allMemsForGraph,
|
|
12824
|
+
pathById: memoryPathById,
|
|
12825
|
+
storageDir: storage.dir,
|
|
12826
|
+
maxRecent: 3
|
|
12827
|
+
})
|
|
12828
|
+
);
|
|
12829
|
+
} catch {
|
|
12830
|
+
}
|
|
12831
|
+
}
|
|
12832
|
+
if (recentInThread.length === 0 && graphCaps.graphWriteSessionAdjacency && fallbackCausalPredecessor && fallbackCausalPredecessor !== memoryRelPath) {
|
|
12833
|
+
recentInThread.push(fallbackCausalPredecessor);
|
|
12834
|
+
}
|
|
12835
|
+
const causalPredecessor = recentInThread[recentInThread.length - 1] ?? fallbackCausalPredecessor;
|
|
12836
|
+
await this.deps.graphIndexFor(storage).onMemoryWritten({
|
|
12837
|
+
memoryPath: memoryRelPath,
|
|
12838
|
+
entityRef,
|
|
12839
|
+
content: factContent,
|
|
12840
|
+
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12841
|
+
threadId: threadIdForEdge,
|
|
12842
|
+
recentInThread,
|
|
12843
|
+
entitySiblings,
|
|
12844
|
+
causalPredecessor,
|
|
12845
|
+
graphCapsOverride: {
|
|
12846
|
+
entityGraph: graphCaps.entityGraph,
|
|
12847
|
+
timeGraph: graphCaps.timeGraph,
|
|
12848
|
+
causalGraph: graphCaps.causalGraph,
|
|
12849
|
+
multiGraphMemory: graphCaps.multiGraphMemory
|
|
12850
|
+
}
|
|
12851
|
+
});
|
|
12852
|
+
}
|
|
12853
|
+
/**
|
|
12854
|
+
* Batch-update temporal and tag indexes after extraction (v8.1).
|
|
12855
|
+
* Reads each persisted memory's path + frontmatter and adds them to
|
|
12856
|
+
* state/index_time.json and state/index_tags.json.
|
|
12857
|
+
* Fail-open: any error is logged but does not abort extraction.
|
|
12858
|
+
*/
|
|
12859
|
+
async updateTemporalTagIndexes(storage, persistedIds) {
|
|
12860
|
+
const caps = resolveCapabilities(this.deps.config);
|
|
12861
|
+
if (!resolveIndexingCapabilities(this.deps.config).queryAwareIndexing && !caps.parallelRetrieval)
|
|
12862
|
+
return;
|
|
12863
|
+
const needsFullRebuild = !indexesExist(this.deps.config.memoryDir);
|
|
12864
|
+
if (!needsFullRebuild && persistedIds.length === 0) return;
|
|
12865
|
+
try {
|
|
12866
|
+
const allMemories = needsFullRebuild && resolveNamespaceCapabilities(this.deps.config).namespaces ? await this.deps.readAllMemoriesForNamespaces(
|
|
12867
|
+
Array.from(
|
|
12868
|
+
/* @__PURE__ */ new Set([
|
|
12869
|
+
this.deps.config.defaultNamespace,
|
|
12870
|
+
this.deps.config.sharedNamespace,
|
|
12871
|
+
...this.deps.config.namespacePolicies.map((p) => p.name)
|
|
12872
|
+
])
|
|
12873
|
+
)
|
|
12874
|
+
) : await storage.readAllMemories();
|
|
12875
|
+
const pool = needsFullRebuild ? allMemories.filter((m) => isActiveMemoryStatus(m.frontmatter.status)) : (() => {
|
|
12876
|
+
const idSet = new Set(persistedIds);
|
|
12877
|
+
return allMemories.filter((m) => idSet.has(m.frontmatter.id));
|
|
12878
|
+
})();
|
|
12879
|
+
const entries = [];
|
|
12880
|
+
for (const mem of pool) {
|
|
12881
|
+
if (mem.path && mem.frontmatter?.created) {
|
|
12882
|
+
entries.push({
|
|
12883
|
+
path: mem.path,
|
|
12884
|
+
createdAt: mem.frontmatter.created,
|
|
12885
|
+
tags: mem.frontmatter.tags ?? []
|
|
12886
|
+
});
|
|
12887
|
+
}
|
|
12888
|
+
}
|
|
12889
|
+
if (needsFullRebuild) {
|
|
12890
|
+
clearIndexes(this.deps.config.memoryDir);
|
|
12891
|
+
if (entries.length > 0) {
|
|
12892
|
+
indexMemoriesBatch(this.deps.config.memoryDir, entries);
|
|
12893
|
+
}
|
|
12894
|
+
log.info(
|
|
12895
|
+
`temporal-index: bootstrapped from ${entries.length} active memories`
|
|
12896
|
+
);
|
|
12897
|
+
} else if (entries.length > 0) {
|
|
12898
|
+
indexMemoriesBatch(this.deps.config.memoryDir, entries);
|
|
12899
|
+
}
|
|
12900
|
+
} catch (err) {
|
|
12901
|
+
log.debug(`temporal-index update failed (non-fatal): ${err}`);
|
|
12902
|
+
}
|
|
12903
|
+
}
|
|
12904
|
+
/**
|
|
12905
|
+
* Issue #373 — nearest-neighbor lookup for the write-time semantic dedup
|
|
12906
|
+
* guard. Returns the top-K embedding hits against the currently indexed
|
|
12907
|
+
* memories, or an empty array when the embedding backend is unavailable.
|
|
12908
|
+
* Intentionally does NOT throw; `decideSemanticDedup` treats both "empty"
|
|
12909
|
+
* and "error" outcomes as fail-open (keep the candidate).
|
|
12910
|
+
*
|
|
12911
|
+
* PR #399 P1 fix: when namespaces are enabled the lookup must be scoped
|
|
12912
|
+
* to the SAME namespace as the fact being written. Otherwise a
|
|
12913
|
+
* high-similarity memory from another namespace can suppress a write in
|
|
12914
|
+
* the target namespace — cross-tenant data loss. Callers pass the target
|
|
12915
|
+
* storage so we can translate its root directory into the correct index
|
|
12916
|
+
* path prefix (and, for the legacy default-namespace layout at
|
|
12917
|
+
* `memoryDir` root, an exclusion list for `namespaces/*`).
|
|
12918
|
+
*/
|
|
12919
|
+
async semanticDedupLookup(content, limit, targetStorage) {
|
|
12920
|
+
if (!resolveMemoryLifecycleCapabilities(this.deps.config).embeddingFallback) {
|
|
12921
|
+
throw new Error("semantic dedup: embedding backend not configured");
|
|
12922
|
+
}
|
|
12923
|
+
if (!await this.deps.embeddingFallback.isAvailable()) {
|
|
12924
|
+
log.debug("semantic dedup: embedding backend unavailable, skipping");
|
|
12925
|
+
throw new Error("semantic dedup: embedding backend unavailable");
|
|
12926
|
+
}
|
|
12927
|
+
const scope = this.deps.semanticDedupScopeFor(targetStorage);
|
|
12928
|
+
const hits = await this.deps.embeddingFallback.search(content, limit, { ...scope, throwOnTimeout: true });
|
|
12929
|
+
if (!Array.isArray(hits) || hits.length === 0) return [];
|
|
12930
|
+
return hits.map((hit) => ({
|
|
12931
|
+
id: hit.id,
|
|
12932
|
+
score: hit.score,
|
|
12933
|
+
path: hit.path
|
|
12934
|
+
}));
|
|
12935
|
+
}
|
|
12936
|
+
};
|
|
12937
|
+
|
|
12666
12938
|
// src/maintenance/pattern-reinforcement.ts
|
|
12667
12939
|
function patternReinforcementKey(content) {
|
|
12668
12940
|
return content.trim().toLowerCase().replace(/\s+/g, " ").slice(0, 200);
|
|
@@ -13533,7 +13805,7 @@ async function qmdStartupCollectionCheckWithTimeout(promise, controller, label)
|
|
|
13533
13805
|
return await Promise.race([checkedPromise, timeoutPromise]);
|
|
13534
13806
|
}
|
|
13535
13807
|
function defaultWorkspaceDir() {
|
|
13536
|
-
return
|
|
13808
|
+
return path13.join(os2.homedir(), ".openclaw", "workspace");
|
|
13537
13809
|
}
|
|
13538
13810
|
function sanitizeSessionKeyForFilename(sessionKey) {
|
|
13539
13811
|
const readable = sessionKey.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
@@ -13894,7 +14166,7 @@ function buildMemoryPathById(allMemsForGraph, storageDir) {
|
|
|
13894
14166
|
for (const mem of allMemsForGraph ?? []) {
|
|
13895
14167
|
const id = mem.frontmatter.id;
|
|
13896
14168
|
if (!id) continue;
|
|
13897
|
-
pathById.set(id,
|
|
14169
|
+
pathById.set(id, path13.relative(storageDir, mem.path));
|
|
13898
14170
|
}
|
|
13899
14171
|
return pathById;
|
|
13900
14172
|
}
|
|
@@ -13902,7 +14174,7 @@ function appendMemoryToGraphContext(options) {
|
|
|
13902
14174
|
if (!Array.isArray(options.allMemsForGraph)) return;
|
|
13903
14175
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
13904
14176
|
options.allMemsForGraph.push({
|
|
13905
|
-
path:
|
|
14177
|
+
path: path13.join(options.storageDir, options.memoryRelPath),
|
|
13906
14178
|
content: options.content,
|
|
13907
14179
|
frontmatter: {
|
|
13908
14180
|
id: options.memoryId,
|
|
@@ -13922,16 +14194,16 @@ function resolvePersistedMemoryRelativePath(options) {
|
|
|
13922
14194
|
const persisted = options.pathById.get(options.memoryId);
|
|
13923
14195
|
if (persisted) return persisted;
|
|
13924
14196
|
if (options.category === "correction") {
|
|
13925
|
-
return
|
|
14197
|
+
return path13.join("corrections", `${options.memoryId}.md`);
|
|
13926
14198
|
}
|
|
13927
14199
|
const subtree = categoryDirName(options.category);
|
|
13928
14200
|
const idParts = options.memoryId.split("-");
|
|
13929
14201
|
const maybeTimestamp = Number(idParts[1]);
|
|
13930
14202
|
if (Number.isFinite(maybeTimestamp) && maybeTimestamp > 0) {
|
|
13931
14203
|
const day = new Date(maybeTimestamp).toISOString().slice(0, 10);
|
|
13932
|
-
return
|
|
14204
|
+
return path13.join(subtree, day, `${options.memoryId}.md`);
|
|
13933
14205
|
}
|
|
13934
|
-
return
|
|
14206
|
+
return path13.join(subtree, `${options.memoryId}.md`);
|
|
13935
14207
|
}
|
|
13936
14208
|
var Orchestrator = class _Orchestrator {
|
|
13937
14209
|
storage;
|
|
@@ -14492,7 +14764,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
14492
14764
|
const defaultNs = normalizeNamespaceIdentity(this.config.defaultNamespace);
|
|
14493
14765
|
if (ns !== defaultNs && !isSafeRouteNamespace(ns)) return;
|
|
14494
14766
|
if (!this.storageDirMatchesNamespaceHint(ns, storageDir)) return;
|
|
14495
|
-
const resolvedStorageDir =
|
|
14767
|
+
const resolvedStorageDir = path13.resolve(storageDir);
|
|
14496
14768
|
let hints = this.namespaceStorageDirHints.get(resolvedStorageDir);
|
|
14497
14769
|
if (!hints) {
|
|
14498
14770
|
hints = /* @__PURE__ */ new Set();
|
|
@@ -14503,21 +14775,21 @@ var Orchestrator = class _Orchestrator {
|
|
|
14503
14775
|
storageDirMatchesNamespaceHint(namespace, storageDir) {
|
|
14504
14776
|
const ns = normalizeNamespaceIdentity(namespace);
|
|
14505
14777
|
if (!ns) return false;
|
|
14506
|
-
const resolvedStorageDir =
|
|
14507
|
-
const resolvedMemoryDir =
|
|
14778
|
+
const resolvedStorageDir = path13.resolve(storageDir);
|
|
14779
|
+
const resolvedMemoryDir = path13.resolve(this.config.memoryDir);
|
|
14508
14780
|
const defaultNs = normalizeNamespaceIdentity(this.config.defaultNamespace);
|
|
14509
14781
|
if (resolvedStorageDir === resolvedMemoryDir) return ns === defaultNs;
|
|
14510
|
-
const resolvedNamespacesDir =
|
|
14782
|
+
const resolvedNamespacesDir = path13.join(resolvedMemoryDir, "namespaces");
|
|
14511
14783
|
if (!isPathInsideStorageRoot(resolvedNamespacesDir, resolvedStorageDir)) return false;
|
|
14512
|
-
const rawRoot =
|
|
14513
|
-
const tokenRoot =
|
|
14784
|
+
const rawRoot = path13.resolve(resolvedNamespacesDir, ns);
|
|
14785
|
+
const tokenRoot = path13.resolve(resolvedNamespacesDir, namespaceIdentityToken(ns));
|
|
14514
14786
|
return resolvedStorageDir === rawRoot || resolvedStorageDir === tokenRoot;
|
|
14515
14787
|
}
|
|
14516
14788
|
namespaceStorageDirHintOwnershipRank(record, resolvedStorageDir, configured) {
|
|
14517
|
-
if (resolvedStorageDir ===
|
|
14789
|
+
if (resolvedStorageDir === path13.resolve(this.config.memoryDir)) {
|
|
14518
14790
|
return record.namespace === normalizeNamespaceIdentity(this.config.defaultNamespace) ? 0 : 3;
|
|
14519
14791
|
}
|
|
14520
|
-
const leaf =
|
|
14792
|
+
const leaf = path13.basename(resolvedStorageDir);
|
|
14521
14793
|
const tokenOwnsRoot = namespaceIdentityToken(record.namespace) === leaf;
|
|
14522
14794
|
if (tokenOwnsRoot && configured.has(record.namespace)) return 0;
|
|
14523
14795
|
if (record.namespace === leaf) return 1;
|
|
@@ -14545,7 +14817,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
14545
14817
|
loadNamespaceStorageDirHintsFromCatalog() {
|
|
14546
14818
|
if (this.namespaceStorageDirHintsLoaded || !this.namespaceCatalog.enabled) return;
|
|
14547
14819
|
this.namespaceStorageDirHintsLoaded = true;
|
|
14548
|
-
const catalogPath =
|
|
14820
|
+
const catalogPath = path13.join(this.config.memoryDir, "state", "namespaces.jsonl");
|
|
14549
14821
|
if (!existsSync(catalogPath)) return;
|
|
14550
14822
|
let body;
|
|
14551
14823
|
try {
|
|
@@ -14582,7 +14854,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
14582
14854
|
if (!this.storageDirMatchesNamespaceHint(record.namespace, record.storageDir)) {
|
|
14583
14855
|
continue;
|
|
14584
14856
|
}
|
|
14585
|
-
const resolvedStorageDir =
|
|
14857
|
+
const resolvedStorageDir = path13.resolve(record.storageDir);
|
|
14586
14858
|
const current = preferredByStorageDir.get(resolvedStorageDir);
|
|
14587
14859
|
preferredByStorageDir.set(
|
|
14588
14860
|
resolvedStorageDir,
|
|
@@ -14730,123 +15002,40 @@ var Orchestrator = class _Orchestrator {
|
|
|
14730
15002
|
return index;
|
|
14731
15003
|
}
|
|
14732
15004
|
async hasContentHashDedup(targetStorage, content) {
|
|
14733
|
-
|
|
14734
|
-
|
|
15005
|
+
return this.persistenceIndexCoordinator.hasContentHashDedup(
|
|
15006
|
+
targetStorage,
|
|
15007
|
+
content
|
|
15008
|
+
);
|
|
14735
15009
|
}
|
|
14736
15010
|
async addContentHashDedup(targetStorage, content) {
|
|
14737
|
-
|
|
14738
|
-
|
|
14739
|
-
|
|
15011
|
+
return this.persistenceIndexCoordinator.addContentHashDedup(
|
|
15012
|
+
targetStorage,
|
|
15013
|
+
content
|
|
15014
|
+
);
|
|
14740
15015
|
}
|
|
14741
15016
|
async removeContentHashForMemory(targetStorage, memory, context) {
|
|
14742
|
-
|
|
14743
|
-
|
|
14744
|
-
|
|
14745
|
-
|
|
14746
|
-
return;
|
|
14747
|
-
}
|
|
14748
|
-
log.warn(
|
|
14749
|
-
`[${context}] removing hash for legacy memory ${memory.frontmatter.id ?? "(unknown)"} via content fallback - no contentHash in frontmatter`
|
|
15017
|
+
return this.persistenceIndexCoordinator.removeContentHashForMemory(
|
|
15018
|
+
targetStorage,
|
|
15019
|
+
memory,
|
|
15020
|
+
context
|
|
14750
15021
|
);
|
|
14751
|
-
index.remove(memory.content);
|
|
14752
15022
|
}
|
|
14753
|
-
/**
|
|
14754
|
-
* Issue #1671 — backfill bi-temporal bounds onto an existing promoted/deduped
|
|
14755
|
-
* copy that was written BEFORE the source fact carried a resolved
|
|
14756
|
-
* `invalid_at`/`observedAt`/`eventTimeSource`.
|
|
14757
|
-
*
|
|
14758
|
-
* On re-extraction/backfill, a fact may now carry a resolved end bound (e.g.
|
|
14759
|
-
* "until June 2025") that the existing copy lacks because it was promoted
|
|
14760
|
-
* before bi-temporal wiring existed. Without this backfill, recall keeps
|
|
14761
|
-
* surfacing an expired fact even though the source copy now expires correctly.
|
|
14762
|
-
*
|
|
14763
|
-
* Finds the active fact in `targetStorage` matching `dedupContent`, then
|
|
14764
|
-
* patches the temporal frontmatter the existing copy is missing. Best-effort
|
|
14765
|
-
* / fail-open — any I/O error is logged and swallowed so the dedup
|
|
14766
|
-
* short-circuit is never blocked by a backfill failure.
|
|
14767
|
-
*
|
|
14768
|
-
* Matching: the stored `frontmatter.contentHash` is compared against
|
|
14769
|
-
* `ContentHashIndex.computeHash(dedupContent)` first (the exact hash the
|
|
14770
|
-
* content-hash index uses), then falls back to stripping citations and
|
|
14771
|
-
* comparing normalized bodies. This handles inline-attribution deployments
|
|
14772
|
-
* where the persisted body carries a citation marker the dedup key does not.
|
|
14773
|
-
*
|
|
14774
|
-
* I/O gate: only triggers when `invalidAt` is present (the end bound that
|
|
14775
|
-
* actually changes recall behavior by expiring the fact). `observedAt` and
|
|
14776
|
-
* `eventTimeSource` alone don'\''t expire a fact, so backfilling them without
|
|
14777
|
-
* an end bound would cause a full readAllMemories scan on every dedup hit
|
|
14778
|
-
* under biTemporal for no recall benefit.
|
|
14779
|
-
*
|
|
14780
|
-
* Only patches fields the existing copy LACKS — never overwrites a bound the
|
|
14781
|
-
* copy already carries.
|
|
14782
|
-
*/
|
|
14783
15023
|
async backfillTemporalBoundsOnDedupHit(targetStorage, dedupContent, bounds, entityRef) {
|
|
14784
|
-
|
|
14785
|
-
|
|
14786
|
-
|
|
14787
|
-
|
|
14788
|
-
|
|
14789
|
-
|
|
14790
|
-
const all = await targetStorage.readAllMemories();
|
|
14791
|
-
const existing = all.find((m) => {
|
|
14792
|
-
if (m.frontmatter.category !== "fact") return false;
|
|
14793
|
-
if ((m.frontmatter.status ?? "active") !== "active") return false;
|
|
14794
|
-
if (incomingEntityNorm && m.frontmatter.entityRef && normalizeSupersessionKey(m.frontmatter.entityRef) !== incomingEntityNorm) {
|
|
14795
|
-
return false;
|
|
14796
|
-
}
|
|
14797
|
-
if (m.frontmatter.contentHash) {
|
|
14798
|
-
return m.frontmatter.contentHash === incomingHash;
|
|
14799
|
-
}
|
|
14800
|
-
return ContentHashIndex.normalizeContent(
|
|
14801
|
-
stripCitationForTemplate(m.content ?? "", this.config.inlineSourceAttributionFormat)
|
|
14802
|
-
) === normalizedIncoming;
|
|
14803
|
-
});
|
|
14804
|
-
if (!existing) return;
|
|
14805
|
-
const patch = {};
|
|
14806
|
-
const fm = existing.frontmatter;
|
|
14807
|
-
if (bounds.invalidAt && (!fm.invalid_at || fm.invalid_at.length === 0)) {
|
|
14808
|
-
patch.invalid_at = bounds.invalidAt;
|
|
14809
|
-
}
|
|
14810
|
-
if (bounds.observedAt && (!fm.observedAt || fm.observedAt.length === 0)) {
|
|
14811
|
-
patch.observedAt = bounds.observedAt;
|
|
14812
|
-
}
|
|
14813
|
-
if (bounds.eventTimeSource && (!fm.eventTimeSource || fm.eventTimeSource.length === 0)) {
|
|
14814
|
-
patch.eventTimeSource = bounds.eventTimeSource;
|
|
14815
|
-
}
|
|
14816
|
-
if (bounds.validFrom && bounds.eventTimeSource === "extracted" && fm.valid_at !== bounds.validFrom) {
|
|
14817
|
-
patch.valid_at = bounds.validFrom;
|
|
14818
|
-
if (fm.eventTimeSource !== "extracted") {
|
|
14819
|
-
patch.eventTimeSource = "extracted";
|
|
14820
|
-
}
|
|
14821
|
-
}
|
|
14822
|
-
if (Object.keys(patch).length === 0) return;
|
|
14823
|
-
const ok = await targetStorage.writeMemoryFrontmatter(existing, patch);
|
|
14824
|
-
if (ok) {
|
|
14825
|
-
log.debug(
|
|
14826
|
-
`bitemporal-backfill: patched ${Object.keys(patch).join(",")} onto existing fact ${fm.id ?? "(unknown)"} in ${targetStorage.dir}`
|
|
14827
|
-
);
|
|
14828
|
-
}
|
|
14829
|
-
} catch (err) {
|
|
14830
|
-
log.warn(
|
|
14831
|
-
`bitemporal-backfill: failed open for ${targetStorage.dir}: ${err}`
|
|
14832
|
-
);
|
|
14833
|
-
}
|
|
15024
|
+
return this.persistenceIndexCoordinator.backfillTemporalBoundsOnDedupHit(
|
|
15025
|
+
targetStorage,
|
|
15026
|
+
dedupContent,
|
|
15027
|
+
bounds,
|
|
15028
|
+
entityRef
|
|
15029
|
+
);
|
|
14834
15030
|
}
|
|
14835
15031
|
async saveContentHashIndexes() {
|
|
14836
|
-
|
|
14837
|
-
if (this.contentHashIndex) indexes.add(this.contentHashIndex);
|
|
14838
|
-
for (const index of this.contentHashIndexesByStorageDir.values()) {
|
|
14839
|
-
indexes.add(index);
|
|
14840
|
-
}
|
|
14841
|
-
for (const index of indexes) {
|
|
14842
|
-
await index.save();
|
|
14843
|
-
}
|
|
15032
|
+
return this.persistenceIndexCoordinator.saveContentHashIndexes();
|
|
14844
15033
|
}
|
|
14845
15034
|
constructor(config) {
|
|
14846
15035
|
this.config = config;
|
|
14847
15036
|
this.profiler = new ProfilingCollector({
|
|
14848
15037
|
enabled: resolvePipelineProcessingCapabilities(this.config).profiling,
|
|
14849
|
-
storageDir: config.profilingStorageDir ||
|
|
15038
|
+
storageDir: config.profilingStorageDir || path13.join(config.memoryDir, "profiling"),
|
|
14850
15039
|
maxTraces: config.profilingMaxTraces
|
|
14851
15040
|
});
|
|
14852
15041
|
this.namespaceCatalog = new NamespaceCatalog(config);
|
|
@@ -14916,7 +15105,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
14916
15105
|
this.compounding = resolveConsolidationCapabilities(config).compounding ? new CompoundingEngine(config, this.storage) : void 0;
|
|
14917
15106
|
this.buffer = new SmartBuffer(config, this.storage);
|
|
14918
15107
|
this.transcript = new TranscriptManager(config);
|
|
14919
|
-
this.conversationIndexDir =
|
|
15108
|
+
this.conversationIndexDir = path13.join(
|
|
14920
15109
|
config.memoryDir,
|
|
14921
15110
|
"conversation-index",
|
|
14922
15111
|
"chunks"
|
|
@@ -15052,7 +15241,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
15052
15241
|
saveContentHashIndexes: () => this.saveContentHashIndexes()
|
|
15053
15242
|
});
|
|
15054
15243
|
this.threading = new ThreadingManager(
|
|
15055
|
-
|
|
15244
|
+
path13.join(config.memoryDir, "threads"),
|
|
15056
15245
|
config.threadingGapMinutes
|
|
15057
15246
|
);
|
|
15058
15247
|
const lifecycleCaps = resolveMemoryLifecycleCapabilities(config);
|
|
@@ -15467,15 +15656,15 @@ ${doc.content}` : doc.content,
|
|
|
15467
15656
|
this.lastFileHygieneRunAtMs = now;
|
|
15468
15657
|
if (hygiene.rotateEnabled) {
|
|
15469
15658
|
for (const rel of hygiene.rotatePaths) {
|
|
15470
|
-
const abs =
|
|
15659
|
+
const abs = path13.isAbsolute(rel) ? rel : path13.join(this.config.workspaceDir, rel);
|
|
15471
15660
|
try {
|
|
15472
15661
|
const raw = await readFile5(abs, "utf-8");
|
|
15473
15662
|
if (raw.length > hygiene.rotateMaxBytes) {
|
|
15474
|
-
const archiveDir =
|
|
15663
|
+
const archiveDir = path13.join(
|
|
15475
15664
|
this.config.workspaceDir,
|
|
15476
15665
|
hygiene.archiveDir
|
|
15477
15666
|
);
|
|
15478
|
-
const base =
|
|
15667
|
+
const base = path13.basename(abs);
|
|
15479
15668
|
const prefix = base.toUpperCase().replace(/\.MD$/i, "").replace(/[^A-Z0-9]+/g, "-") || "FILE";
|
|
15480
15669
|
const { newContent } = await rotateMarkdownFileToArchive({
|
|
15481
15670
|
filePath: abs,
|
|
@@ -15500,8 +15689,8 @@ ${doc.content}` : doc.content,
|
|
|
15500
15689
|
log.warn(w.message);
|
|
15501
15690
|
}
|
|
15502
15691
|
if (hygiene.warningsLogEnabled && warnings.length > 0) {
|
|
15503
|
-
const fp =
|
|
15504
|
-
await mkdir6(
|
|
15692
|
+
const fp = path13.join(this.config.memoryDir, hygiene.warningsLogPath);
|
|
15693
|
+
await mkdir6(path13.dirname(fp), { recursive: true });
|
|
15505
15694
|
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
15506
15695
|
const block = `
|
|
15507
15696
|
|
|
@@ -15640,7 +15829,7 @@ ${doc.content}` : doc.content,
|
|
|
15640
15829
|
for (const categoryDir of RECALL_FALLBACK_DIRS) {
|
|
15641
15830
|
if (memoryRootReal === null) break;
|
|
15642
15831
|
for (const date of datesToScan) {
|
|
15643
|
-
const dateDir =
|
|
15832
|
+
const dateDir = path13.join(storage.dir, categoryDir, date);
|
|
15644
15833
|
try {
|
|
15645
15834
|
const dirStat = await lstat(dateDir);
|
|
15646
15835
|
if (dirStat.isSymbolicLink() || !dirStat.isDirectory()) continue;
|
|
@@ -15649,7 +15838,7 @@ ${doc.content}` : doc.content,
|
|
|
15649
15838
|
for (const entry of entries) {
|
|
15650
15839
|
if (entry.isSymbolicLink()) continue;
|
|
15651
15840
|
if (!entry.name.endsWith(".md")) continue;
|
|
15652
|
-
const fullPath =
|
|
15841
|
+
const fullPath = path13.join(dateDir, entry.name);
|
|
15653
15842
|
try {
|
|
15654
15843
|
assertPathInsideRoot(memoryRootReal, await realpath(fullPath), fullPath);
|
|
15655
15844
|
const raw = await readFile5(fullPath, "utf-8");
|
|
@@ -15671,7 +15860,7 @@ ${doc.content}` : doc.content,
|
|
|
15671
15860
|
facts.push({
|
|
15672
15861
|
path: fullPath,
|
|
15673
15862
|
frontmatter: {
|
|
15674
|
-
id: fm.id ||
|
|
15863
|
+
id: fm.id || path13.basename(entry.name, ".md"),
|
|
15675
15864
|
category: fm.category || "fact",
|
|
15676
15865
|
created,
|
|
15677
15866
|
updated: fm.updated || created,
|
|
@@ -15694,13 +15883,13 @@ ${doc.content}` : doc.content,
|
|
|
15694
15883
|
return a.frontmatter.created < b.frontmatter.created ? -1 : 1;
|
|
15695
15884
|
});
|
|
15696
15885
|
const hourlySummaries = [];
|
|
15697
|
-
const hourlyBaseDir =
|
|
15886
|
+
const hourlyBaseDir = path13.join(storage.dir, "summaries", "hourly");
|
|
15698
15887
|
try {
|
|
15699
15888
|
const sessionKeys = await readdir3(hourlyBaseDir, { withFileTypes: true });
|
|
15700
15889
|
for (const sk of sessionKeys) {
|
|
15701
15890
|
if (!sk.isDirectory()) continue;
|
|
15702
15891
|
for (const date of datesToScan) {
|
|
15703
|
-
const summaryFile =
|
|
15892
|
+
const summaryFile = path13.join(hourlyBaseDir, sk.name, `${date}.md`);
|
|
15704
15893
|
try {
|
|
15705
15894
|
const raw = await readFile5(summaryFile, "utf-8");
|
|
15706
15895
|
const filtered = filterHourlySummaryMarkdownForLocalDay(
|
|
@@ -16175,12 +16364,12 @@ ${doc.content}` : doc.content,
|
|
|
16175
16364
|
}
|
|
16176
16365
|
async resolveStateDirForNamespace(namespace) {
|
|
16177
16366
|
if (!resolveNamespaceCapabilities(this.config).namespaces) {
|
|
16178
|
-
return
|
|
16367
|
+
return path13.join(this.config.memoryDir, "state");
|
|
16179
16368
|
}
|
|
16180
16369
|
if (namespace !== this.config.defaultNamespace) {
|
|
16181
|
-
return
|
|
16370
|
+
return path13.join(this.config.memoryDir, "namespaces", namespace, "state");
|
|
16182
16371
|
}
|
|
16183
|
-
const candidate =
|
|
16372
|
+
const candidate = path13.join(
|
|
16184
16373
|
this.config.memoryDir,
|
|
16185
16374
|
"namespaces",
|
|
16186
16375
|
this.config.defaultNamespace
|
|
@@ -16188,11 +16377,11 @@ ${doc.content}` : doc.content,
|
|
|
16188
16377
|
try {
|
|
16189
16378
|
const candidateStat = await stat3(candidate);
|
|
16190
16379
|
if (candidateStat.isDirectory()) {
|
|
16191
|
-
return
|
|
16380
|
+
return path13.join(candidate, "state");
|
|
16192
16381
|
}
|
|
16193
16382
|
} catch {
|
|
16194
16383
|
}
|
|
16195
|
-
return
|
|
16384
|
+
return path13.join(this.config.memoryDir, "state");
|
|
16196
16385
|
}
|
|
16197
16386
|
buildGraphRecallRankedResults(results, sourceLabelResolver, limit = 64) {
|
|
16198
16387
|
return results.slice(0, limit).map((result) => ({
|
|
@@ -16689,6 +16878,36 @@ ${doc.content}` : doc.content,
|
|
|
16689
16878
|
}
|
|
16690
16879
|
return this._orchestratorInitCoordinator;
|
|
16691
16880
|
}
|
|
16881
|
+
/**
|
|
16882
|
+
* Persistence-index coordinator (issue #1526 seam 23). Owns post-persist
|
|
16883
|
+
* bookkeeping (content-hash dedup, temporal indexes, graph edges,
|
|
16884
|
+
* semantic dedup lookup). Lazy + accessor-wired (late-binding rule).
|
|
16885
|
+
*/
|
|
16886
|
+
_persistenceIndexCoordinator;
|
|
16887
|
+
get persistenceIndexCoordinator() {
|
|
16888
|
+
if (!this._persistenceIndexCoordinator) {
|
|
16889
|
+
const self = this;
|
|
16890
|
+
this._persistenceIndexCoordinator = new PersistenceIndexCoordinator({
|
|
16891
|
+
get config() {
|
|
16892
|
+
return self.config;
|
|
16893
|
+
},
|
|
16894
|
+
get contentHashIndex() {
|
|
16895
|
+
return self.contentHashIndex;
|
|
16896
|
+
},
|
|
16897
|
+
contentHashIndexForStorage: (targetStorage) => self.contentHashIndexForStorage(targetStorage),
|
|
16898
|
+
get contentHashIndexesByStorageDir() {
|
|
16899
|
+
return self.contentHashIndexesByStorageDir;
|
|
16900
|
+
},
|
|
16901
|
+
get embeddingFallback() {
|
|
16902
|
+
return self.embeddingFallback;
|
|
16903
|
+
},
|
|
16904
|
+
graphIndexFor: (storage) => self.graphIndexFor(storage),
|
|
16905
|
+
readAllMemoriesForNamespaces: (namespaces) => self.readAllMemoriesForNamespaces(namespaces),
|
|
16906
|
+
semanticDedupScopeFor: (targetStorage) => self.semanticDedupScopeFor(targetStorage)
|
|
16907
|
+
});
|
|
16908
|
+
}
|
|
16909
|
+
return this._persistenceIndexCoordinator;
|
|
16910
|
+
}
|
|
16692
16911
|
async recallInternal(prompt, sessionKey, options = {}, caps = resolveCapabilities(this.config), graphCaps = resolveGraphConstructionCapabilities(this.config), lifecycleCaps = resolveMemoryLifecycleCapabilities(this.config)) {
|
|
16693
16912
|
return this.recallInternalCoordinator.recallInternal(
|
|
16694
16913
|
prompt,
|
|
@@ -16949,71 +17168,25 @@ ${doc.content}` : doc.content,
|
|
|
16949
17168
|
}
|
|
16950
17169
|
}
|
|
16951
17170
|
async indexPersistedMemory(storage, memoryId) {
|
|
16952
|
-
|
|
16953
|
-
|
|
16954
|
-
|
|
16955
|
-
if (!memory) return;
|
|
16956
|
-
await this.embeddingFallback.indexFile(
|
|
16957
|
-
memoryId,
|
|
16958
|
-
memory.content,
|
|
16959
|
-
memory.path
|
|
17171
|
+
return this.persistenceIndexCoordinator.indexPersistedMemory(
|
|
17172
|
+
storage,
|
|
17173
|
+
memoryId
|
|
16960
17174
|
);
|
|
16961
17175
|
}
|
|
16962
|
-
/**
|
|
16963
|
-
* Build a graph edge for a persisted memory (v8.2).
|
|
16964
|
-
* Shared helper used by both the chunked and non-chunked write paths to avoid duplication.
|
|
16965
|
-
* Fail-open: caller wraps in try/catch.
|
|
16966
|
-
*/
|
|
16967
17176
|
async buildGraphEdge(storage, memoryRelPath, entityRef, memoryId, factContent, allMemsForGraph, memoryPathById, threadIdForEdge, threadEpisodeIdsForGraph, fallbackCausalPredecessor, graphCaps = resolveGraphConstructionCapabilities(this.config)) {
|
|
16968
|
-
|
|
16969
|
-
|
|
16970
|
-
|
|
16971
|
-
const allMems = allMemsForGraph ?? [];
|
|
16972
|
-
for (const m of allMems) {
|
|
16973
|
-
if (m.frontmatter.entityRef === entityRef) {
|
|
16974
|
-
const rel = path12.relative(storage.dir, m.path);
|
|
16975
|
-
if (rel !== memoryRelPath) entitySiblings.push(rel);
|
|
16976
|
-
}
|
|
16977
|
-
}
|
|
16978
|
-
} catch {
|
|
16979
|
-
}
|
|
16980
|
-
}
|
|
16981
|
-
const recentInThread = [];
|
|
16982
|
-
if (threadIdForEdge && threadEpisodeIdsForGraph?.length) {
|
|
16983
|
-
try {
|
|
16984
|
-
recentInThread.push(
|
|
16985
|
-
...resolveRecentThreadMemoryPaths({
|
|
16986
|
-
threadEpisodeIds: threadEpisodeIdsForGraph,
|
|
16987
|
-
currentMemoryId: memoryId,
|
|
16988
|
-
allMemsForGraph,
|
|
16989
|
-
pathById: memoryPathById,
|
|
16990
|
-
storageDir: storage.dir,
|
|
16991
|
-
maxRecent: 3
|
|
16992
|
-
})
|
|
16993
|
-
);
|
|
16994
|
-
} catch {
|
|
16995
|
-
}
|
|
16996
|
-
}
|
|
16997
|
-
if (recentInThread.length === 0 && graphCaps.graphWriteSessionAdjacency && fallbackCausalPredecessor && fallbackCausalPredecessor !== memoryRelPath) {
|
|
16998
|
-
recentInThread.push(fallbackCausalPredecessor);
|
|
16999
|
-
}
|
|
17000
|
-
const causalPredecessor = recentInThread[recentInThread.length - 1] ?? fallbackCausalPredecessor;
|
|
17001
|
-
await this.graphIndexFor(storage).onMemoryWritten({
|
|
17002
|
-
memoryPath: memoryRelPath,
|
|
17177
|
+
return this.persistenceIndexCoordinator.buildGraphEdge(
|
|
17178
|
+
storage,
|
|
17179
|
+
memoryRelPath,
|
|
17003
17180
|
entityRef,
|
|
17004
|
-
|
|
17005
|
-
|
|
17006
|
-
|
|
17007
|
-
|
|
17008
|
-
|
|
17009
|
-
|
|
17010
|
-
|
|
17011
|
-
|
|
17012
|
-
|
|
17013
|
-
causalGraph: graphCaps.causalGraph,
|
|
17014
|
-
multiGraphMemory: graphCaps.multiGraphMemory
|
|
17015
|
-
}
|
|
17016
|
-
});
|
|
17181
|
+
memoryId,
|
|
17182
|
+
factContent,
|
|
17183
|
+
allMemsForGraph,
|
|
17184
|
+
memoryPathById,
|
|
17185
|
+
threadIdForEdge,
|
|
17186
|
+
threadEpisodeIdsForGraph,
|
|
17187
|
+
fallbackCausalPredecessor,
|
|
17188
|
+
graphCaps
|
|
17189
|
+
);
|
|
17017
17190
|
}
|
|
17018
17191
|
graphIndexFor(storage) {
|
|
17019
17192
|
const key = storage.dir;
|
|
@@ -17023,56 +17196,11 @@ ${doc.content}` : doc.content,
|
|
|
17023
17196
|
this.graphIndexes.set(key, created);
|
|
17024
17197
|
return created;
|
|
17025
17198
|
}
|
|
17026
|
-
/**
|
|
17027
|
-
* Batch-update temporal and tag indexes after extraction (v8.1).
|
|
17028
|
-
* Reads each persisted memory's path + frontmatter and adds them to
|
|
17029
|
-
* state/index_time.json and state/index_tags.json.
|
|
17030
|
-
* Fail-open: any error is logged but does not abort extraction.
|
|
17031
|
-
*/
|
|
17032
17199
|
async updateTemporalTagIndexes(storage, persistedIds) {
|
|
17033
|
-
|
|
17034
|
-
|
|
17035
|
-
|
|
17036
|
-
|
|
17037
|
-
if (!needsFullRebuild && persistedIds.length === 0) return;
|
|
17038
|
-
try {
|
|
17039
|
-
const allMemories = needsFullRebuild && resolveNamespaceCapabilities(this.config).namespaces ? await this.readAllMemoriesForNamespaces(
|
|
17040
|
-
Array.from(
|
|
17041
|
-
/* @__PURE__ */ new Set([
|
|
17042
|
-
this.config.defaultNamespace,
|
|
17043
|
-
this.config.sharedNamespace,
|
|
17044
|
-
...this.config.namespacePolicies.map((p) => p.name)
|
|
17045
|
-
])
|
|
17046
|
-
)
|
|
17047
|
-
) : await storage.readAllMemories();
|
|
17048
|
-
const pool = needsFullRebuild ? allMemories.filter((m) => isActiveMemoryStatus(m.frontmatter.status)) : (() => {
|
|
17049
|
-
const idSet = new Set(persistedIds);
|
|
17050
|
-
return allMemories.filter((m) => idSet.has(m.frontmatter.id));
|
|
17051
|
-
})();
|
|
17052
|
-
const entries = [];
|
|
17053
|
-
for (const mem of pool) {
|
|
17054
|
-
if (mem.path && mem.frontmatter?.created) {
|
|
17055
|
-
entries.push({
|
|
17056
|
-
path: mem.path,
|
|
17057
|
-
createdAt: mem.frontmatter.created,
|
|
17058
|
-
tags: mem.frontmatter.tags ?? []
|
|
17059
|
-
});
|
|
17060
|
-
}
|
|
17061
|
-
}
|
|
17062
|
-
if (needsFullRebuild) {
|
|
17063
|
-
clearIndexes(this.config.memoryDir);
|
|
17064
|
-
if (entries.length > 0) {
|
|
17065
|
-
indexMemoriesBatch(this.config.memoryDir, entries);
|
|
17066
|
-
}
|
|
17067
|
-
log.info(
|
|
17068
|
-
`temporal-index: bootstrapped from ${entries.length} active memories`
|
|
17069
|
-
);
|
|
17070
|
-
} else if (entries.length > 0) {
|
|
17071
|
-
indexMemoriesBatch(this.config.memoryDir, entries);
|
|
17072
|
-
}
|
|
17073
|
-
} catch (err) {
|
|
17074
|
-
log.debug(`temporal-index update failed (non-fatal): ${err}`);
|
|
17075
|
-
}
|
|
17200
|
+
return this.persistenceIndexCoordinator.updateTemporalTagIndexes(
|
|
17201
|
+
storage,
|
|
17202
|
+
persistedIds
|
|
17203
|
+
);
|
|
17076
17204
|
}
|
|
17077
17205
|
/** IDs of facts persisted in the last extraction */
|
|
17078
17206
|
lastPersistedIds = [];
|
|
@@ -17286,37 +17414,12 @@ ${reflectionsContent.trim()}
|
|
|
17286
17414
|
recallSource
|
|
17287
17415
|
);
|
|
17288
17416
|
}
|
|
17289
|
-
/**
|
|
17290
|
-
* Issue #373 — nearest-neighbor lookup for the write-time semantic dedup
|
|
17291
|
-
* guard. Returns the top-K embedding hits against the currently indexed
|
|
17292
|
-
* memories, or an empty array when the embedding backend is unavailable.
|
|
17293
|
-
* Intentionally does NOT throw; `decideSemanticDedup` treats both "empty"
|
|
17294
|
-
* and "error" outcomes as fail-open (keep the candidate).
|
|
17295
|
-
*
|
|
17296
|
-
* PR #399 P1 fix: when namespaces are enabled the lookup must be scoped
|
|
17297
|
-
* to the SAME namespace as the fact being written. Otherwise a
|
|
17298
|
-
* high-similarity memory from another namespace can suppress a write in
|
|
17299
|
-
* the target namespace — cross-tenant data loss. Callers pass the target
|
|
17300
|
-
* storage so we can translate its root directory into the correct index
|
|
17301
|
-
* path prefix (and, for the legacy default-namespace layout at
|
|
17302
|
-
* `memoryDir` root, an exclusion list for `namespaces/*`).
|
|
17303
|
-
*/
|
|
17304
17417
|
async semanticDedupLookup(content, limit, targetStorage) {
|
|
17305
|
-
|
|
17306
|
-
|
|
17307
|
-
|
|
17308
|
-
|
|
17309
|
-
|
|
17310
|
-
throw new Error("semantic dedup: embedding backend unavailable");
|
|
17311
|
-
}
|
|
17312
|
-
const scope = this.semanticDedupScopeFor(targetStorage);
|
|
17313
|
-
const hits = await this.embeddingFallback.search(content, limit, { ...scope, throwOnTimeout: true });
|
|
17314
|
-
if (!Array.isArray(hits) || hits.length === 0) return [];
|
|
17315
|
-
return hits.map((hit) => ({
|
|
17316
|
-
id: hit.id,
|
|
17317
|
-
score: hit.score,
|
|
17318
|
-
path: hit.path
|
|
17319
|
-
}));
|
|
17418
|
+
return this.persistenceIndexCoordinator.semanticDedupLookup(
|
|
17419
|
+
content,
|
|
17420
|
+
limit,
|
|
17421
|
+
targetStorage
|
|
17422
|
+
);
|
|
17320
17423
|
}
|
|
17321
17424
|
/**
|
|
17322
17425
|
* Resolve the namespace-scoped filter to pass into
|
|
@@ -17334,12 +17437,12 @@ ${reflectionsContent.trim()}
|
|
|
17334
17437
|
*/
|
|
17335
17438
|
semanticDedupScopeFor(targetStorage) {
|
|
17336
17439
|
if (!resolveNamespaceCapabilities(this.config).namespaces) return {};
|
|
17337
|
-
const memoryDir =
|
|
17338
|
-
const storageDir =
|
|
17440
|
+
const memoryDir = path13.resolve(this.config.memoryDir);
|
|
17441
|
+
const storageDir = path13.resolve(targetStorage.dir);
|
|
17339
17442
|
if (storageDir === memoryDir) {
|
|
17340
17443
|
return { pathExcludePrefixes: ["namespaces/"] };
|
|
17341
17444
|
}
|
|
17342
|
-
let rel =
|
|
17445
|
+
let rel = path13.relative(memoryDir, storageDir);
|
|
17343
17446
|
if (!rel || rel.startsWith("..")) {
|
|
17344
17447
|
log.debug(
|
|
17345
17448
|
`semantic dedup: target storage dir ${storageDir} is outside memoryDir ${memoryDir}; scoping lookup to absolute path prefix`
|
|
@@ -18084,9 +18187,9 @@ function readStructuralSymbol(entry) {
|
|
|
18084
18187
|
const rec = entry;
|
|
18085
18188
|
const symbol = typeof rec.symbol === "string" ? rec.symbol.trim() : "";
|
|
18086
18189
|
if (!symbol) return null;
|
|
18087
|
-
const
|
|
18190
|
+
const path28 = typeof rec.path === "string" && rec.path.length > 0 ? rec.path : void 0;
|
|
18088
18191
|
const kind = typeof rec.kind === "string" && rec.kind.length > 0 ? rec.kind : void 0;
|
|
18089
|
-
const item =
|
|
18192
|
+
const item = path28 !== void 0 && kind !== void 0 ? { symbol, path: path28, kind } : path28 !== void 0 ? { symbol, path: path28 } : kind !== void 0 ? { symbol, kind } : { symbol };
|
|
18090
18193
|
return item;
|
|
18091
18194
|
}
|
|
18092
18195
|
function classifySpawnError(err) {
|
|
@@ -18160,7 +18263,7 @@ var DEFAULT_GRACE_PERIOD_DAYS = 7;
|
|
|
18160
18263
|
|
|
18161
18264
|
// src/binary-lifecycle/backend.ts
|
|
18162
18265
|
import fsp from "fs/promises";
|
|
18163
|
-
import
|
|
18266
|
+
import path14 from "path";
|
|
18164
18267
|
var FilesystemBackend = class {
|
|
18165
18268
|
type = "filesystem";
|
|
18166
18269
|
basePath;
|
|
@@ -18168,19 +18271,19 @@ var FilesystemBackend = class {
|
|
|
18168
18271
|
if (!basePath || basePath.trim().length === 0) {
|
|
18169
18272
|
throw new Error("FilesystemBackend requires a non-empty basePath");
|
|
18170
18273
|
}
|
|
18171
|
-
this.basePath =
|
|
18274
|
+
this.basePath = path14.resolve(basePath);
|
|
18172
18275
|
}
|
|
18173
18276
|
resolveRemotePath(remotePath) {
|
|
18174
|
-
const resolved =
|
|
18175
|
-
const relative =
|
|
18176
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
18277
|
+
const resolved = path14.isAbsolute(remotePath) ? path14.resolve(remotePath) : path14.resolve(this.basePath, remotePath);
|
|
18278
|
+
const relative = path14.relative(this.basePath, resolved);
|
|
18279
|
+
if (relative === ".." || relative.startsWith(`..${path14.sep}`) || path14.isAbsolute(relative)) {
|
|
18177
18280
|
throw new Error(`FilesystemBackend remotePath escapes basePath: ${JSON.stringify(remotePath)}`);
|
|
18178
18281
|
}
|
|
18179
18282
|
return resolved;
|
|
18180
18283
|
}
|
|
18181
18284
|
isInsideBase(candidate, realBase) {
|
|
18182
|
-
const relative =
|
|
18183
|
-
return relative === "" || relative !== ".." && !relative.startsWith(`..${
|
|
18285
|
+
const relative = path14.relative(realBase, candidate);
|
|
18286
|
+
return relative === "" || relative !== ".." && !relative.startsWith(`..${path14.sep}`) && !path14.isAbsolute(relative);
|
|
18184
18287
|
}
|
|
18185
18288
|
async realBasePathIfExists() {
|
|
18186
18289
|
try {
|
|
@@ -18209,13 +18312,13 @@ var FilesystemBackend = class {
|
|
|
18209
18312
|
}
|
|
18210
18313
|
async ensureSafeParentDirectory(dest) {
|
|
18211
18314
|
const realBase = await this.ensureBaseDirectory();
|
|
18212
|
-
const destDir =
|
|
18213
|
-
const relativeDir =
|
|
18214
|
-
const segments = relativeDir === "" ? [] : relativeDir.split(
|
|
18315
|
+
const destDir = path14.dirname(dest);
|
|
18316
|
+
const relativeDir = path14.relative(this.basePath, destDir);
|
|
18317
|
+
const segments = relativeDir === "" ? [] : relativeDir.split(path14.sep);
|
|
18215
18318
|
let current = this.basePath;
|
|
18216
18319
|
for (const segment of segments) {
|
|
18217
18320
|
if (segment === "." || segment === "") continue;
|
|
18218
|
-
current =
|
|
18321
|
+
current = path14.join(current, segment);
|
|
18219
18322
|
try {
|
|
18220
18323
|
const stat5 = await fsp.lstat(current);
|
|
18221
18324
|
if (stat5.isSymbolicLink()) {
|
|
@@ -18243,13 +18346,13 @@ var FilesystemBackend = class {
|
|
|
18243
18346
|
if (realBase === null) {
|
|
18244
18347
|
return null;
|
|
18245
18348
|
}
|
|
18246
|
-
const destDir =
|
|
18247
|
-
const relativeDir =
|
|
18248
|
-
const segments = relativeDir === "" ? [] : relativeDir.split(
|
|
18349
|
+
const destDir = path14.dirname(dest);
|
|
18350
|
+
const relativeDir = path14.relative(this.basePath, destDir);
|
|
18351
|
+
const segments = relativeDir === "" ? [] : relativeDir.split(path14.sep);
|
|
18249
18352
|
let current = this.basePath;
|
|
18250
18353
|
for (const segment of segments) {
|
|
18251
18354
|
if (segment === "." || segment === "") continue;
|
|
18252
|
-
current =
|
|
18355
|
+
current = path14.join(current, segment);
|
|
18253
18356
|
let stat5;
|
|
18254
18357
|
try {
|
|
18255
18358
|
stat5 = await fsp.lstat(current);
|
|
@@ -18297,7 +18400,7 @@ var FilesystemBackend = class {
|
|
|
18297
18400
|
return dest;
|
|
18298
18401
|
}
|
|
18299
18402
|
async upload(localPath, remotePath) {
|
|
18300
|
-
if (
|
|
18403
|
+
if (path14.isAbsolute(remotePath)) {
|
|
18301
18404
|
throw new Error(`FilesystemBackend upload remotePath must be relative: ${JSON.stringify(remotePath)}`);
|
|
18302
18405
|
}
|
|
18303
18406
|
const dest = this.resolveRemotePath(remotePath);
|
|
@@ -18370,7 +18473,7 @@ function createBackend(cfg) {
|
|
|
18370
18473
|
|
|
18371
18474
|
// src/binary-lifecycle/scanner.ts
|
|
18372
18475
|
import fsp2 from "fs/promises";
|
|
18373
|
-
import
|
|
18476
|
+
import path15 from "path";
|
|
18374
18477
|
import crypto from "crypto";
|
|
18375
18478
|
function matchesPatterns(filename, patterns) {
|
|
18376
18479
|
const lower = filename.toLowerCase();
|
|
@@ -18395,8 +18498,8 @@ async function scanForBinaries(memoryDir, config, manifest) {
|
|
|
18395
18498
|
return;
|
|
18396
18499
|
}
|
|
18397
18500
|
for (const entry of entries) {
|
|
18398
|
-
const fullPath =
|
|
18399
|
-
const relativePath =
|
|
18501
|
+
const fullPath = path15.join(dir, entry.name);
|
|
18502
|
+
const relativePath = path15.relative(memoryDir, fullPath).split(path15.sep).join("/");
|
|
18400
18503
|
if (entry.isDirectory()) {
|
|
18401
18504
|
if (entry.name === ".binary-lifecycle") continue;
|
|
18402
18505
|
await walk(fullPath);
|
|
@@ -18436,15 +18539,15 @@ async function hashFile(filePath) {
|
|
|
18436
18539
|
|
|
18437
18540
|
// src/binary-lifecycle/manifest.ts
|
|
18438
18541
|
import fsp3 from "fs/promises";
|
|
18439
|
-
import
|
|
18542
|
+
import path16 from "path";
|
|
18440
18543
|
import crypto2 from "crypto";
|
|
18441
18544
|
var MANIFEST_DIR = ".binary-lifecycle";
|
|
18442
18545
|
var MANIFEST_FILE = "manifest.json";
|
|
18443
18546
|
function manifestDir(memoryDir) {
|
|
18444
|
-
return
|
|
18547
|
+
return path16.join(memoryDir, MANIFEST_DIR);
|
|
18445
18548
|
}
|
|
18446
18549
|
function manifestPath(memoryDir) {
|
|
18447
|
-
return
|
|
18550
|
+
return path16.join(memoryDir, MANIFEST_DIR, MANIFEST_FILE);
|
|
18448
18551
|
}
|
|
18449
18552
|
async function readManifest(memoryDir) {
|
|
18450
18553
|
const filePath = manifestPath(memoryDir);
|
|
@@ -18496,7 +18599,7 @@ function emptyManifest() {
|
|
|
18496
18599
|
|
|
18497
18600
|
// src/binary-lifecycle/pipeline.ts
|
|
18498
18601
|
import fsp4 from "fs/promises";
|
|
18499
|
-
import
|
|
18602
|
+
import path17 from "path";
|
|
18500
18603
|
import crypto3 from "crypto";
|
|
18501
18604
|
async function hashFile2(filePath) {
|
|
18502
18605
|
const content = await fsp4.readFile(filePath);
|
|
@@ -18519,13 +18622,13 @@ function escapeRegex(s) {
|
|
|
18519
18622
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18520
18623
|
}
|
|
18521
18624
|
function resolveManifestAssetPath(memoryDir, originalPath) {
|
|
18522
|
-
if (originalPath.length === 0 || originalPath.includes("\0") || originalPath.includes("\\") ||
|
|
18625
|
+
if (originalPath.length === 0 || originalPath.includes("\0") || originalPath.includes("\\") || path17.isAbsolute(originalPath) || path17.win32.isAbsolute(originalPath)) {
|
|
18523
18626
|
return null;
|
|
18524
18627
|
}
|
|
18525
|
-
const memoryRoot =
|
|
18526
|
-
const fullPath =
|
|
18527
|
-
const relative =
|
|
18528
|
-
if (relative === "" || relative === ".." || relative.startsWith(`..${
|
|
18628
|
+
const memoryRoot = path17.resolve(memoryDir);
|
|
18629
|
+
const fullPath = path17.resolve(memoryRoot, originalPath);
|
|
18630
|
+
const relative = path17.relative(memoryRoot, fullPath);
|
|
18631
|
+
if (relative === "" || relative === ".." || relative.startsWith(`..${path17.sep}`) || path17.isAbsolute(relative)) {
|
|
18529
18632
|
return null;
|
|
18530
18633
|
}
|
|
18531
18634
|
return fullPath;
|
|
@@ -18536,7 +18639,7 @@ function validateBinaryLifecycleConfig(config) {
|
|
|
18536
18639
|
}
|
|
18537
18640
|
}
|
|
18538
18641
|
function remotePathForAsset(backend, relPath) {
|
|
18539
|
-
const normalized = relPath.split(
|
|
18642
|
+
const normalized = relPath.split(path17.sep).join("/");
|
|
18540
18643
|
if (backend.type === "filesystem") {
|
|
18541
18644
|
return `.binary-lifecycle/mirrors/${normalized}`;
|
|
18542
18645
|
}
|
|
@@ -18549,11 +18652,11 @@ async function stageMirror(memoryDir, newPaths, backend, assets, log2, dryRun) {
|
|
|
18549
18652
|
let mirrored = 0;
|
|
18550
18653
|
const errors = [];
|
|
18551
18654
|
for (const relPath of newPaths) {
|
|
18552
|
-
const fullPath =
|
|
18655
|
+
const fullPath = path17.join(memoryDir, relPath);
|
|
18553
18656
|
try {
|
|
18554
18657
|
const stat5 = await fsp4.stat(fullPath);
|
|
18555
18658
|
const contentHash = await hashFile2(fullPath);
|
|
18556
|
-
const ext =
|
|
18659
|
+
const ext = path17.extname(relPath);
|
|
18557
18660
|
const mimeType = guessMimeType(ext);
|
|
18558
18661
|
const remotePath = remotePathForAsset(backend, relPath);
|
|
18559
18662
|
let backendLocation = remotePath;
|
|
@@ -18753,10 +18856,10 @@ async function countRemainingLocalReferences(memoryDir, asset, assetAbsolute, md
|
|
|
18753
18856
|
return { remaining, errors };
|
|
18754
18857
|
}
|
|
18755
18858
|
function markdownReferencePattern(asset, assetAbsolute, mdPath) {
|
|
18756
|
-
const mdDir =
|
|
18859
|
+
const mdDir = path17.dirname(mdPath);
|
|
18757
18860
|
const candidates = /* @__PURE__ */ new Set();
|
|
18758
18861
|
const addCandidate = (candidate) => {
|
|
18759
|
-
const normalized = candidate.split(
|
|
18862
|
+
const normalized = candidate.split(path17.sep).join("/");
|
|
18760
18863
|
if (normalized.length === 0) return;
|
|
18761
18864
|
candidates.add(normalized);
|
|
18762
18865
|
const isParentTraversal = normalized === ".." || normalized.startsWith("../");
|
|
@@ -18764,10 +18867,10 @@ function markdownReferencePattern(asset, assetAbsolute, mdPath) {
|
|
|
18764
18867
|
candidates.add(`./${normalized}`);
|
|
18765
18868
|
}
|
|
18766
18869
|
};
|
|
18767
|
-
addCandidate(
|
|
18768
|
-
const originalPath = asset.originalPath.split(
|
|
18769
|
-
const originalAsFileRelative =
|
|
18770
|
-
if (
|
|
18870
|
+
addCandidate(path17.relative(mdDir, assetAbsolute));
|
|
18871
|
+
const originalPath = asset.originalPath.split(path17.sep).join("/");
|
|
18872
|
+
const originalAsFileRelative = path17.resolve(mdDir, ...originalPath.split("/"));
|
|
18873
|
+
if (path17.resolve(originalAsFileRelative) === path17.resolve(assetAbsolute)) {
|
|
18771
18874
|
addCandidate(originalPath);
|
|
18772
18875
|
}
|
|
18773
18876
|
addCandidate(`/${originalPath}`);
|
|
@@ -18863,7 +18966,7 @@ async function findMarkdownFiles(dir) {
|
|
|
18863
18966
|
return;
|
|
18864
18967
|
}
|
|
18865
18968
|
for (const entry of entries) {
|
|
18866
|
-
const full =
|
|
18969
|
+
const full = path17.join(current, entry.name);
|
|
18867
18970
|
if (entry.isDirectory()) {
|
|
18868
18971
|
if (entry.name === ".binary-lifecycle") continue;
|
|
18869
18972
|
await walk(full);
|
|
@@ -18938,7 +19041,7 @@ async function runBinaryLifecyclePipeline(memoryDir, config, backend, log2, opts
|
|
|
18938
19041
|
|
|
18939
19042
|
// src/projection/index.ts
|
|
18940
19043
|
import fs from "fs";
|
|
18941
|
-
import
|
|
19044
|
+
import path18 from "path";
|
|
18942
19045
|
var VALID_PROJECTION_CATEGORIES = new Set(ALL_CATEGORY_KEYS);
|
|
18943
19046
|
async function generateContextTree(options) {
|
|
18944
19047
|
const startTime = Date.now();
|
|
@@ -18953,8 +19056,8 @@ async function generateContextTree(options) {
|
|
|
18953
19056
|
let nodesGenerated = 0;
|
|
18954
19057
|
let nodesSkipped = 0;
|
|
18955
19058
|
const categoryCounts = {};
|
|
18956
|
-
const resolvedMemoryDir =
|
|
18957
|
-
const resolvedOutputDir =
|
|
19059
|
+
const resolvedMemoryDir = path18.resolve(memoryDir);
|
|
19060
|
+
const resolvedOutputDir = path18.resolve(outputDir);
|
|
18958
19061
|
const requestedCategories = validateProjectionCategories(filterCategories);
|
|
18959
19062
|
const realMemoryDir = assertSafeMemoryRoot(resolvedMemoryDir);
|
|
18960
19063
|
assertNotSymlink(resolvedOutputDir, "context tree outputDir");
|
|
@@ -18992,7 +19095,7 @@ async function generateContextTree(options) {
|
|
|
18992
19095
|
}
|
|
18993
19096
|
}
|
|
18994
19097
|
if (includeEntities) {
|
|
18995
|
-
const entitiesDir =
|
|
19098
|
+
const entitiesDir = path18.join(memoryDir, "entities");
|
|
18996
19099
|
if (fs.existsSync(entitiesDir)) {
|
|
18997
19100
|
assertSafeInputRoot(realMemoryDir, entitiesDir, "entities root");
|
|
18998
19101
|
categoryCounts["entity"] = 0;
|
|
@@ -19004,7 +19107,7 @@ async function generateContextTree(options) {
|
|
|
19004
19107
|
continue;
|
|
19005
19108
|
}
|
|
19006
19109
|
const content = fs.readFileSync(filePath, "utf8");
|
|
19007
|
-
const fileName =
|
|
19110
|
+
const fileName = path18.basename(filePath, ".md");
|
|
19008
19111
|
const node = projectEntityNode(fileName, content);
|
|
19009
19112
|
const outputPath = resolveContainedOutputPath(realOutputDir, "entities", `${fileName}.md`);
|
|
19010
19113
|
writeProjectedContent(realOutputDir, outputPath, node.content);
|
|
@@ -19016,7 +19119,7 @@ async function generateContextTree(options) {
|
|
|
19016
19119
|
}
|
|
19017
19120
|
const shouldIncludeQuestions = includeQuestions && (requestedCategories === void 0 || requestedCategories.includes("question"));
|
|
19018
19121
|
if (shouldIncludeQuestions) {
|
|
19019
|
-
const questionsDir =
|
|
19122
|
+
const questionsDir = path18.join(memoryDir, "questions");
|
|
19020
19123
|
if (fs.existsSync(questionsDir)) {
|
|
19021
19124
|
assertSafeInputRoot(realMemoryDir, questionsDir, "questions root");
|
|
19022
19125
|
categoryCounts["question"] = 0;
|
|
@@ -19057,8 +19160,8 @@ async function generateContextTree(options) {
|
|
|
19057
19160
|
};
|
|
19058
19161
|
}
|
|
19059
19162
|
function isPathInside(root, candidate) {
|
|
19060
|
-
const relative =
|
|
19061
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
19163
|
+
const relative = path18.relative(root, candidate);
|
|
19164
|
+
return relative === "" || !relative.startsWith("..") && !path18.isAbsolute(relative);
|
|
19062
19165
|
}
|
|
19063
19166
|
function assertNotSymlink(targetPath, label) {
|
|
19064
19167
|
try {
|
|
@@ -19095,12 +19198,12 @@ function assertSafeInputRoot(realMemoryDir, targetPath, label) {
|
|
|
19095
19198
|
}
|
|
19096
19199
|
function assertSafeOutputTarget(realOutputDir, outputPath) {
|
|
19097
19200
|
let current = realOutputDir;
|
|
19098
|
-
const relative =
|
|
19099
|
-
if (relative.startsWith("..") ||
|
|
19201
|
+
const relative = path18.relative(realOutputDir, outputPath);
|
|
19202
|
+
if (relative.startsWith("..") || path18.isAbsolute(relative)) {
|
|
19100
19203
|
throw new Error(`context tree output path escapes outputDir: ${outputPath}`);
|
|
19101
19204
|
}
|
|
19102
|
-
for (const segment of relative.split(
|
|
19103
|
-
current =
|
|
19205
|
+
for (const segment of relative.split(path18.sep).filter(Boolean)) {
|
|
19206
|
+
current = path18.join(current, segment);
|
|
19104
19207
|
try {
|
|
19105
19208
|
const stat5 = fs.lstatSync(current);
|
|
19106
19209
|
if (stat5.isSymbolicLink()) {
|
|
@@ -19130,17 +19233,17 @@ function validateProjectionCategories(categories) {
|
|
|
19130
19233
|
return validated;
|
|
19131
19234
|
}
|
|
19132
19235
|
function resolveContainedOutputPath(outputRoot, ...segments) {
|
|
19133
|
-
const resolved =
|
|
19134
|
-
const relative =
|
|
19135
|
-
if (relative === "" || !relative.startsWith("..") && !
|
|
19236
|
+
const resolved = path18.resolve(outputRoot, ...segments);
|
|
19237
|
+
const relative = path18.relative(outputRoot, resolved);
|
|
19238
|
+
if (relative === "" || !relative.startsWith("..") && !path18.isAbsolute(relative)) {
|
|
19136
19239
|
return resolved;
|
|
19137
19240
|
}
|
|
19138
19241
|
throw new Error(`context tree output path escapes outputDir: ${segments.join("/")}`);
|
|
19139
19242
|
}
|
|
19140
19243
|
function writeProjectedContent(realOutputDir, outputPath, generatedContent) {
|
|
19141
19244
|
assertSafeOutputTarget(realOutputDir, outputPath);
|
|
19142
|
-
fs.mkdirSync(
|
|
19143
|
-
const realParent = fs.realpathSync(
|
|
19245
|
+
fs.mkdirSync(path18.dirname(outputPath), { recursive: true });
|
|
19246
|
+
const realParent = fs.realpathSync(path18.dirname(outputPath));
|
|
19144
19247
|
if (!isPathInside(realOutputDir, realParent)) {
|
|
19145
19248
|
throw new Error(`context tree output path escapes outputDir: ${outputPath}`);
|
|
19146
19249
|
}
|
|
@@ -19179,7 +19282,7 @@ function walkR(dir, realMemoryDir) {
|
|
|
19179
19282
|
const results = [];
|
|
19180
19283
|
function walk(directory) {
|
|
19181
19284
|
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
19182
|
-
const fullPath =
|
|
19285
|
+
const fullPath = path18.join(directory, entry.name);
|
|
19183
19286
|
if (entry.isSymbolicLink()) {
|
|
19184
19287
|
throw new Error(`context tree input path contains symlink: ${fullPath}`);
|
|
19185
19288
|
}
|
|
@@ -19232,13 +19335,13 @@ function extractBody(content) {
|
|
|
19232
19335
|
}
|
|
19233
19336
|
function projectNode(filePath, category, fm, rawContent) {
|
|
19234
19337
|
const body = extractBody(rawContent);
|
|
19235
|
-
const fileName =
|
|
19236
|
-
const dateDir =
|
|
19338
|
+
const fileName = path18.basename(filePath, ".md");
|
|
19339
|
+
const dateDir = path18.basename(path18.dirname(filePath));
|
|
19237
19340
|
let relPath;
|
|
19238
19341
|
if (/^\d{4}-\d{2}-\d{2}$/.test(dateDir)) {
|
|
19239
|
-
relPath =
|
|
19342
|
+
relPath = path18.join(category, dateDir, `${fileName}.md`);
|
|
19240
19343
|
} else {
|
|
19241
|
-
relPath =
|
|
19344
|
+
relPath = path18.join(category, `${fileName}.md`);
|
|
19242
19345
|
}
|
|
19243
19346
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
19244
19347
|
const md = `# ${fm.id}
|
|
@@ -19284,7 +19387,7 @@ function projectEntityNode(fileName, content) {
|
|
|
19284
19387
|
${content}
|
|
19285
19388
|
`;
|
|
19286
19389
|
return {
|
|
19287
|
-
path:
|
|
19390
|
+
path: path18.join("entities", `${fileName}.md`),
|
|
19288
19391
|
category: "entity",
|
|
19289
19392
|
title: fileName,
|
|
19290
19393
|
content: md,
|
|
@@ -19337,7 +19440,7 @@ function generateIndex(categoryCounts, outputDir) {
|
|
|
19337
19440
|
|
|
19338
19441
|
// src/onboarding/index.ts
|
|
19339
19442
|
import fs2 from "fs";
|
|
19340
|
-
import
|
|
19443
|
+
import path19 from "path";
|
|
19341
19444
|
var LANGUAGE_RULES = [
|
|
19342
19445
|
{
|
|
19343
19446
|
language: "TypeScript",
|
|
@@ -19439,7 +19542,7 @@ function onboard(options) {
|
|
|
19439
19542
|
maxDepth = 6,
|
|
19440
19543
|
excludeDirs = []
|
|
19441
19544
|
} = options;
|
|
19442
|
-
const directory =
|
|
19545
|
+
const directory = path19.resolve(options.directory ?? process.cwd());
|
|
19443
19546
|
let rootStat;
|
|
19444
19547
|
try {
|
|
19445
19548
|
rootStat = fs2.statSync(directory);
|
|
@@ -19481,7 +19584,7 @@ function walkDir(root, exclude, maxDepth) {
|
|
|
19481
19584
|
}
|
|
19482
19585
|
for (const entry of entries) {
|
|
19483
19586
|
if (exclude.has(entry.name)) continue;
|
|
19484
|
-
const fullPath =
|
|
19587
|
+
const fullPath = path19.join(dir, entry.name);
|
|
19485
19588
|
if (entry.isDirectory()) {
|
|
19486
19589
|
walk(fullPath, depth + 1);
|
|
19487
19590
|
} else if (entry.isFile()) {
|
|
@@ -19496,11 +19599,11 @@ function detectLanguages(files, root) {
|
|
|
19496
19599
|
const results = [];
|
|
19497
19600
|
const extCounts = /* @__PURE__ */ new Map();
|
|
19498
19601
|
for (const f of files) {
|
|
19499
|
-
const ext =
|
|
19602
|
+
const ext = path19.extname(f).toLowerCase();
|
|
19500
19603
|
if (ext) extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);
|
|
19501
19604
|
}
|
|
19502
19605
|
const rootFiles = new Set(
|
|
19503
|
-
files.filter((f) =>
|
|
19606
|
+
files.filter((f) => path19.dirname(f) === root).map((f) => path19.basename(f))
|
|
19504
19607
|
);
|
|
19505
19608
|
for (const rule of LANGUAGE_RULES) {
|
|
19506
19609
|
const evidence = [];
|
|
@@ -19544,7 +19647,7 @@ function detectLanguages(files, root) {
|
|
|
19544
19647
|
}
|
|
19545
19648
|
function detectShape(files, root) {
|
|
19546
19649
|
const rootFiles = new Set(
|
|
19547
|
-
files.filter((f) =>
|
|
19650
|
+
files.filter((f) => path19.dirname(f) === root).map((f) => path19.basename(f))
|
|
19548
19651
|
);
|
|
19549
19652
|
const rootDirs = /* @__PURE__ */ new Set();
|
|
19550
19653
|
try {
|
|
@@ -19555,7 +19658,7 @@ function detectShape(files, root) {
|
|
|
19555
19658
|
}
|
|
19556
19659
|
const evidence = [];
|
|
19557
19660
|
if (rootFiles.has("package.json")) {
|
|
19558
|
-
const pkg = readJsonSafe(
|
|
19661
|
+
const pkg = readJsonSafe(path19.join(root, "package.json"));
|
|
19559
19662
|
if (pkg?.workspaces) {
|
|
19560
19663
|
evidence.push("package.json has workspaces");
|
|
19561
19664
|
return { shape: "monorepo", evidence };
|
|
@@ -19569,13 +19672,13 @@ function detectShape(files, root) {
|
|
|
19569
19672
|
evidence.push("workspace manifest found");
|
|
19570
19673
|
return { shape: "workspace", evidence };
|
|
19571
19674
|
}
|
|
19572
|
-
const cargoToml = readTomlWorkspace(
|
|
19675
|
+
const cargoToml = readTomlWorkspace(path19.join(root, "Cargo.toml"));
|
|
19573
19676
|
if (cargoToml) {
|
|
19574
19677
|
evidence.push("Cargo.toml has workspace");
|
|
19575
19678
|
return { shape: "workspace", evidence };
|
|
19576
19679
|
}
|
|
19577
19680
|
if (rootFiles.has("package.json")) {
|
|
19578
|
-
const pkg = readJsonSafe(
|
|
19681
|
+
const pkg = readJsonSafe(path19.join(root, "package.json"));
|
|
19579
19682
|
if (pkg?.exports || pkg?.main) {
|
|
19580
19683
|
if (pkg?.bin) {
|
|
19581
19684
|
evidence.push("package.json has bin");
|
|
@@ -19609,8 +19712,8 @@ function discoverDocs(files, root) {
|
|
|
19609
19712
|
{ pattern: /^\.editorconfig$/i, kind: "config" }
|
|
19610
19713
|
];
|
|
19611
19714
|
for (const filePath of files) {
|
|
19612
|
-
const basename =
|
|
19613
|
-
const relPath =
|
|
19715
|
+
const basename = path19.basename(filePath).toLowerCase();
|
|
19716
|
+
const relPath = path19.relative(root, filePath);
|
|
19614
19717
|
let kind;
|
|
19615
19718
|
for (const { pattern, kind: k } of docPatterns) {
|
|
19616
19719
|
if (pattern.test(basename)) {
|
|
@@ -19622,7 +19725,7 @@ function discoverDocs(files, root) {
|
|
|
19622
19725
|
kind = "docs";
|
|
19623
19726
|
}
|
|
19624
19727
|
if (!kind && (basename.endsWith(".md") || basename.endsWith(".mdx"))) {
|
|
19625
|
-
if (
|
|
19728
|
+
if (path19.dirname(relPath) === "." || isUnderDocsDir(relPath)) {
|
|
19626
19729
|
kind = "docs";
|
|
19627
19730
|
}
|
|
19628
19731
|
}
|
|
@@ -19643,7 +19746,7 @@ function discoverDocs(files, root) {
|
|
|
19643
19746
|
return docs;
|
|
19644
19747
|
}
|
|
19645
19748
|
function isUnderDocsDir(relPath) {
|
|
19646
|
-
const parts = relPath.split(
|
|
19749
|
+
const parts = relPath.split(path19.sep);
|
|
19647
19750
|
return parts[0] === "docs" || parts[0] === "doc" || parts[0] === "documentation";
|
|
19648
19751
|
}
|
|
19649
19752
|
function buildPlan(languages, shape, docs, _root) {
|
|
@@ -19687,7 +19790,7 @@ function readTomlWorkspace(filePath) {
|
|
|
19687
19790
|
|
|
19688
19791
|
// src/curation/index.ts
|
|
19689
19792
|
import fs3 from "fs";
|
|
19690
|
-
import
|
|
19793
|
+
import path20 from "path";
|
|
19691
19794
|
import crypto4 from "crypto";
|
|
19692
19795
|
async function curate(options) {
|
|
19693
19796
|
const startTime = Date.now();
|
|
@@ -19778,12 +19881,12 @@ function resolveTargets(targetPath) {
|
|
|
19778
19881
|
const extensions = /* @__PURE__ */ new Set([".md", ".txt", ".mdx", ".rst"]);
|
|
19779
19882
|
function walk(dir) {
|
|
19780
19883
|
for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
|
|
19781
|
-
const fullPath =
|
|
19884
|
+
const fullPath = path20.join(dir, entry.name);
|
|
19782
19885
|
if (entry.isDirectory()) {
|
|
19783
19886
|
if (entry.name !== "node_modules" && entry.name !== ".git") {
|
|
19784
19887
|
walk(fullPath);
|
|
19785
19888
|
}
|
|
19786
|
-
} else if (extensions.has(
|
|
19889
|
+
} else if (extensions.has(path20.extname(entry.name).toLowerCase())) {
|
|
19787
19890
|
results.push(fullPath);
|
|
19788
19891
|
}
|
|
19789
19892
|
}
|
|
@@ -19792,12 +19895,12 @@ function resolveTargets(targetPath) {
|
|
|
19792
19895
|
return results;
|
|
19793
19896
|
}
|
|
19794
19897
|
function resolveProvenanceRoot(targetPath) {
|
|
19795
|
-
const resolvedTarget =
|
|
19898
|
+
const resolvedTarget = path20.resolve(targetPath);
|
|
19796
19899
|
const stat5 = fs3.statSync(resolvedTarget);
|
|
19797
|
-
return stat5.isFile() ?
|
|
19900
|
+
return stat5.isFile() ? path20.dirname(resolvedTarget) : resolvedTarget;
|
|
19798
19901
|
}
|
|
19799
19902
|
function extractStatements(content, filePath, projectRoot, source, sourceFileHash, categoryOverride, confidence, entityRef, tags) {
|
|
19800
|
-
const relativePath =
|
|
19903
|
+
const relativePath = path20.relative(projectRoot, path20.resolve(filePath)) || path20.basename(filePath);
|
|
19801
19904
|
const statements = [];
|
|
19802
19905
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19803
19906
|
const paragraphs = content.split(/\n{2,}/).map((p) => p.trim()).filter((p) => p.length > 20 && p.length < 2e3);
|
|
@@ -19891,7 +19994,7 @@ function loadExistingMemories(memoryDir) {
|
|
|
19891
19994
|
if (!fs3.existsSync(memoryDir)) return result;
|
|
19892
19995
|
const dirs = ALL_CATEGORY_DIRS;
|
|
19893
19996
|
for (const dir of dirs) {
|
|
19894
|
-
const fullDir =
|
|
19997
|
+
const fullDir = path20.join(memoryDir, dir);
|
|
19895
19998
|
if (!fs3.existsSync(fullDir)) continue;
|
|
19896
19999
|
walkFiles(fullDir, (filePath) => {
|
|
19897
20000
|
const content = readFileSafe(filePath);
|
|
@@ -19913,10 +20016,10 @@ function writeStatement(stmt, memoryDir) {
|
|
|
19913
20016
|
const now = /* @__PURE__ */ new Date();
|
|
19914
20017
|
const dateDir = now.toISOString().split("T")[0];
|
|
19915
20018
|
const categoryDir = getCategoryDir(memoryDir, stmt.category);
|
|
19916
|
-
const dir =
|
|
20019
|
+
const dir = path20.join(categoryDir, dateDir);
|
|
19917
20020
|
fs3.mkdirSync(dir, { recursive: true });
|
|
19918
20021
|
const fileName = `${stmt.category}-${Date.now()}-${stmt.id.slice(0, 8)}.md`;
|
|
19919
|
-
const filePath =
|
|
20022
|
+
const filePath = path20.join(dir, fileName);
|
|
19920
20023
|
const frontmatter = [
|
|
19921
20024
|
"---",
|
|
19922
20025
|
`id: ${stmt.id}`,
|
|
@@ -19983,7 +20086,7 @@ function extractBody2(content) {
|
|
|
19983
20086
|
}
|
|
19984
20087
|
function walkFiles(dir, callback) {
|
|
19985
20088
|
for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
|
|
19986
|
-
const fullPath =
|
|
20089
|
+
const fullPath = path20.join(dir, entry.name);
|
|
19987
20090
|
if (entry.isDirectory()) {
|
|
19988
20091
|
walkFiles(fullPath, callback);
|
|
19989
20092
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -19994,7 +20097,7 @@ function walkFiles(dir, callback) {
|
|
|
19994
20097
|
|
|
19995
20098
|
// src/dedup/index.ts
|
|
19996
20099
|
import fs4 from "fs";
|
|
19997
|
-
import
|
|
20100
|
+
import path21 from "path";
|
|
19998
20101
|
import crypto5 from "crypto";
|
|
19999
20102
|
var DEFAULT_DEDUP_THRESHOLD = 0.85;
|
|
20000
20103
|
var DEFAULT_MAX_LOAD = 1e4;
|
|
@@ -20136,7 +20239,7 @@ function loadMemories(memoryDir, categories, maxLoad = 1e4) {
|
|
|
20136
20239
|
const memoryRootReal = fs4.realpathSync(memoryDir);
|
|
20137
20240
|
for (const category of allCategories) {
|
|
20138
20241
|
if (result.length >= maxLoad) break;
|
|
20139
|
-
const dir =
|
|
20242
|
+
const dir = path21.join(memoryDir, category);
|
|
20140
20243
|
if (!fs4.existsSync(dir)) continue;
|
|
20141
20244
|
const categoryStat = fs4.lstatSync(dir);
|
|
20142
20245
|
if (categoryStat.isSymbolicLink()) {
|
|
@@ -20197,15 +20300,15 @@ function extractBody3(content) {
|
|
|
20197
20300
|
return match ? match[1].trim() : content.trim();
|
|
20198
20301
|
}
|
|
20199
20302
|
function assertPathInsideRoot2(rootReal, targetReal, sourcePath) {
|
|
20200
|
-
const rel =
|
|
20201
|
-
if (rel === "" || !rel.startsWith("..") && !
|
|
20303
|
+
const rel = path21.relative(rootReal, targetReal);
|
|
20304
|
+
if (rel === "" || !rel.startsWith("..") && !path21.isAbsolute(rel)) {
|
|
20202
20305
|
return;
|
|
20203
20306
|
}
|
|
20204
20307
|
throw new Error(`Refusing to scan memory path outside root: ${sourcePath}`);
|
|
20205
20308
|
}
|
|
20206
20309
|
function walkMdFiles(dir, memoryRootReal, categoryRootReal, callback) {
|
|
20207
20310
|
for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
|
|
20208
|
-
const fullPath =
|
|
20311
|
+
const fullPath = path21.join(dir, entry.name);
|
|
20209
20312
|
const entryStat = fs4.lstatSync(fullPath);
|
|
20210
20313
|
if (entryStat.isSymbolicLink()) {
|
|
20211
20314
|
throw new Error(`Refusing to scan symlinked memory path: ${fullPath}`);
|
|
@@ -20223,7 +20326,7 @@ function walkMdFiles(dir, memoryRootReal, categoryRootReal, callback) {
|
|
|
20223
20326
|
|
|
20224
20327
|
// src/review/index.ts
|
|
20225
20328
|
import fs5 from "fs";
|
|
20226
|
-
import
|
|
20329
|
+
import path22 from "path";
|
|
20227
20330
|
var DEFAULT_CONFIDENCE_THRESHOLD = 0.7;
|
|
20228
20331
|
function realMemoryRoot(memoryDir) {
|
|
20229
20332
|
try {
|
|
@@ -20235,8 +20338,8 @@ function realMemoryRoot(memoryDir) {
|
|
|
20235
20338
|
}
|
|
20236
20339
|
}
|
|
20237
20340
|
function isPathInside2(rootReal, candidateReal) {
|
|
20238
|
-
const relative =
|
|
20239
|
-
return relative === "" || !!relative && !relative.startsWith("..") && !
|
|
20341
|
+
const relative = path22.relative(rootReal, candidateReal);
|
|
20342
|
+
return relative === "" || !!relative && !relative.startsWith("..") && !path22.isAbsolute(relative);
|
|
20240
20343
|
}
|
|
20241
20344
|
function isSafeDirectory(rootReal, dir) {
|
|
20242
20345
|
try {
|
|
@@ -20279,7 +20382,7 @@ function listReviewItems(options) {
|
|
|
20279
20382
|
if (filterReason && item.reviewReason !== filterReason) return;
|
|
20280
20383
|
items.push(item);
|
|
20281
20384
|
};
|
|
20282
|
-
const suggestionsDir =
|
|
20385
|
+
const suggestionsDir = path22.join(memoryDir, "suggestions");
|
|
20283
20386
|
if (!isLimitReached() && fs5.existsSync(suggestionsDir) && isSafeDirectory(rootReal, suggestionsDir)) {
|
|
20284
20387
|
walkMd(rootReal, suggestionsDir, (filePath, content) => {
|
|
20285
20388
|
if (isLimitReached()) return true;
|
|
@@ -20300,7 +20403,7 @@ function listReviewItems(options) {
|
|
|
20300
20403
|
return isLimitReached();
|
|
20301
20404
|
});
|
|
20302
20405
|
}
|
|
20303
|
-
const reviewDir =
|
|
20406
|
+
const reviewDir = path22.join(memoryDir, "review");
|
|
20304
20407
|
if (!isLimitReached() && fs5.existsSync(reviewDir) && isSafeDirectory(rootReal, reviewDir)) {
|
|
20305
20408
|
walkMd(rootReal, reviewDir, (filePath, content) => {
|
|
20306
20409
|
if (isLimitReached()) return true;
|
|
@@ -20325,7 +20428,7 @@ function listReviewItems(options) {
|
|
|
20325
20428
|
const categories = ALL_CATEGORY_DIRS;
|
|
20326
20429
|
for (const category of categories) {
|
|
20327
20430
|
if (isLimitReached()) break;
|
|
20328
|
-
const dir =
|
|
20431
|
+
const dir = path22.join(memoryDir, category);
|
|
20329
20432
|
if (!fs5.existsSync(dir) || !isSafeDirectory(rootReal, dir)) continue;
|
|
20330
20433
|
walkMd(rootReal, dir, (filePath, content) => {
|
|
20331
20434
|
if (isLimitReached()) return true;
|
|
@@ -20410,8 +20513,8 @@ function approveItem(memoryDir, itemId, options) {
|
|
|
20410
20513
|
const targetDir = getCategoryDir(memoryDir, category);
|
|
20411
20514
|
const dateDir = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
20412
20515
|
ensureSafeDirectory(rootReal, targetDir);
|
|
20413
|
-
const outputPath =
|
|
20414
|
-
ensureSafeDirectory(rootReal,
|
|
20516
|
+
const outputPath = path22.join(targetDir, dateDir, path22.basename(found.filePath));
|
|
20517
|
+
ensureSafeDirectory(rootReal, path22.dirname(outputPath));
|
|
20415
20518
|
const promotedPath = writeFileWithoutClobber(outputPath, updatedContent, itemId);
|
|
20416
20519
|
fs5.unlinkSync(found.filePath);
|
|
20417
20520
|
return {
|
|
@@ -20469,13 +20572,13 @@ function findReviewFileById(memoryDir, id, options = {}) {
|
|
|
20469
20572
|
const rootReal = realMemoryRoot(memoryDir);
|
|
20470
20573
|
if (!rootReal) return null;
|
|
20471
20574
|
for (const loc of ["suggestions", "review"]) {
|
|
20472
|
-
const dir =
|
|
20575
|
+
const dir = path22.join(memoryDir, loc);
|
|
20473
20576
|
if (!fs5.existsSync(dir) || !isSafeDirectory(rootReal, dir)) continue;
|
|
20474
20577
|
const found = findFileById(rootReal, dir, id);
|
|
20475
20578
|
if (found) return { filePath: found, location: "queue" };
|
|
20476
20579
|
}
|
|
20477
20580
|
for (const category of ALL_CATEGORY_DIRS) {
|
|
20478
|
-
const dir =
|
|
20581
|
+
const dir = path22.join(memoryDir, category);
|
|
20479
20582
|
if (!fs5.existsSync(dir) || !isSafeDirectory(rootReal, dir)) continue;
|
|
20480
20583
|
const found = findFileById(rootReal, dir, id, (fm) => isLowConfidenceReviewCandidate(fm, options));
|
|
20481
20584
|
if (found) return { filePath: found, location: "category" };
|
|
@@ -20551,10 +20654,10 @@ function readFileSafe3(filePath) {
|
|
|
20551
20654
|
}
|
|
20552
20655
|
}
|
|
20553
20656
|
function writeFileWithoutClobber(basePath, content, discriminator) {
|
|
20554
|
-
const parsed =
|
|
20657
|
+
const parsed = path22.parse(basePath);
|
|
20555
20658
|
const safeDiscriminator = sanitizeFilePart(discriminator);
|
|
20556
20659
|
for (let attempt = 0; attempt < 1e3; attempt++) {
|
|
20557
|
-
const candidate = attempt === 0 ? basePath :
|
|
20660
|
+
const candidate = attempt === 0 ? basePath : path22.join(
|
|
20558
20661
|
parsed.dir,
|
|
20559
20662
|
`${parsed.name}-${safeDiscriminator}${attempt === 1 ? "" : `-${attempt}`}${parsed.ext || ".md"}`
|
|
20560
20663
|
);
|
|
@@ -20610,7 +20713,7 @@ function extractBody4(content) {
|
|
|
20610
20713
|
function walkMd(rootReal, dir, callback) {
|
|
20611
20714
|
if (!isSafeDirectory(rootReal, dir)) return false;
|
|
20612
20715
|
for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
|
|
20613
|
-
const fullPath =
|
|
20716
|
+
const fullPath = path22.join(dir, entry.name);
|
|
20614
20717
|
if (entry.isSymbolicLink()) continue;
|
|
20615
20718
|
if (entry.isDirectory()) {
|
|
20616
20719
|
if (walkMd(rootReal, fullPath, callback)) return true;
|
|
@@ -20625,7 +20728,7 @@ function walkMdPaths(rootReal, dir) {
|
|
|
20625
20728
|
const results = [];
|
|
20626
20729
|
if (!isSafeDirectory(rootReal, dir)) return results;
|
|
20627
20730
|
for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
|
|
20628
|
-
const fullPath =
|
|
20731
|
+
const fullPath = path22.join(dir, entry.name);
|
|
20629
20732
|
if (entry.isSymbolicLink()) continue;
|
|
20630
20733
|
if (entry.isDirectory()) {
|
|
20631
20734
|
results.push(...walkMdPaths(rootReal, fullPath));
|
|
@@ -20638,7 +20741,7 @@ function walkMdPaths(rootReal, dir) {
|
|
|
20638
20741
|
|
|
20639
20742
|
// src/sync/index.ts
|
|
20640
20743
|
import fs6 from "fs";
|
|
20641
|
-
import
|
|
20744
|
+
import path23 from "path";
|
|
20642
20745
|
import crypto6 from "crypto";
|
|
20643
20746
|
var DEFAULT_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".txt", ".mdx", ".rst"]);
|
|
20644
20747
|
var DEFAULT_EXCLUDE2 = /* @__PURE__ */ new Set([
|
|
@@ -20660,7 +20763,7 @@ function syncChanges(options) {
|
|
|
20660
20763
|
} = options;
|
|
20661
20764
|
const extSet = new Set(extensions);
|
|
20662
20765
|
const excludeSet = /* @__PURE__ */ new Set([...DEFAULT_EXCLUDE2, ...excludeDirs]);
|
|
20663
|
-
const stateFilePath = options.stateFile ??
|
|
20766
|
+
const stateFilePath = options.stateFile ?? path23.join(memoryDir, ".sync-state.json");
|
|
20664
20767
|
const prevState = loadState(stateFilePath);
|
|
20665
20768
|
const currentFiles = scanFiles(sourceDir, extSet, excludeSet);
|
|
20666
20769
|
const changes = computeDiff(currentFiles, prevState.fileHashes, sourceDir);
|
|
@@ -20676,7 +20779,7 @@ function syncChanges(options) {
|
|
|
20676
20779
|
for (const [relPath, hash] of Object.entries(currentFiles)) {
|
|
20677
20780
|
newState.fileHashes[relPath] = hash;
|
|
20678
20781
|
}
|
|
20679
|
-
fs6.mkdirSync(
|
|
20782
|
+
fs6.mkdirSync(path23.dirname(stateFilePath), { recursive: true });
|
|
20680
20783
|
fs6.writeFileSync(stateFilePath, JSON.stringify(newState, null, 2));
|
|
20681
20784
|
}
|
|
20682
20785
|
return {
|
|
@@ -20742,13 +20845,13 @@ function scanFiles(root, extensions, exclude) {
|
|
|
20742
20845
|
}
|
|
20743
20846
|
for (const entry of entries) {
|
|
20744
20847
|
if (exclude.has(entry.name)) continue;
|
|
20745
|
-
const fullPath =
|
|
20848
|
+
const fullPath = path23.join(dir, entry.name);
|
|
20746
20849
|
if (entry.isDirectory()) {
|
|
20747
20850
|
walk(fullPath);
|
|
20748
20851
|
} else if (entry.isFile()) {
|
|
20749
|
-
const ext =
|
|
20852
|
+
const ext = path23.extname(entry.name).toLowerCase();
|
|
20750
20853
|
if (!extensions.has(ext)) continue;
|
|
20751
|
-
const relPath =
|
|
20854
|
+
const relPath = path23.relative(root, fullPath);
|
|
20752
20855
|
try {
|
|
20753
20856
|
const content = fs6.readFileSync(fullPath, "utf8");
|
|
20754
20857
|
result[relPath] = hashContent3(content);
|
|
@@ -20763,7 +20866,7 @@ function scanFiles(root, extensions, exclude) {
|
|
|
20763
20866
|
function computeDiff(current, previous, sourceDir) {
|
|
20764
20867
|
const changes = [];
|
|
20765
20868
|
for (const [relPath, hash] of Object.entries(current)) {
|
|
20766
|
-
const fullPath =
|
|
20869
|
+
const fullPath = path23.join(sourceDir, relPath);
|
|
20767
20870
|
if (!(relPath in previous)) {
|
|
20768
20871
|
let size = 0;
|
|
20769
20872
|
try {
|
|
@@ -20796,7 +20899,7 @@ function computeDiff(current, previous, sourceDir) {
|
|
|
20796
20899
|
for (const relPath of Object.keys(previous)) {
|
|
20797
20900
|
if (!(relPath in current)) {
|
|
20798
20901
|
changes.push({
|
|
20799
|
-
filePath:
|
|
20902
|
+
filePath: path23.join(sourceDir, relPath),
|
|
20800
20903
|
relativePath: relPath,
|
|
20801
20904
|
type: "deleted",
|
|
20802
20905
|
currentHash: "",
|
|
@@ -20826,20 +20929,20 @@ function hashContent3(content) {
|
|
|
20826
20929
|
import { spawnSync } from "child_process";
|
|
20827
20930
|
import crypto7 from "crypto";
|
|
20828
20931
|
import fs7 from "fs";
|
|
20829
|
-
import
|
|
20932
|
+
import path24 from "path";
|
|
20830
20933
|
var MANIFEST_VERSION = 1;
|
|
20831
20934
|
var MANIFEST_LOCK_STALE_MS = 3e4;
|
|
20832
20935
|
var MANIFEST_LOCK_TIMEOUT_MS = MANIFEST_LOCK_STALE_MS + 1e4;
|
|
20833
20936
|
var MANIFEST_LOCK_SLEEP_MS = 20;
|
|
20834
20937
|
function normalizeSpaceMemoryDir(memoryDir) {
|
|
20835
|
-
return
|
|
20938
|
+
return path24.resolve(memoryDir);
|
|
20836
20939
|
}
|
|
20837
20940
|
function getSpacesDir(baseDir) {
|
|
20838
20941
|
const homeDir = baseDir ?? resolveHomeDir();
|
|
20839
|
-
return
|
|
20942
|
+
return path24.join(homeDir, ".config", "engram", "spaces");
|
|
20840
20943
|
}
|
|
20841
20944
|
function getManifestPath(baseDir) {
|
|
20842
|
-
return
|
|
20945
|
+
return path24.join(getSpacesDir(baseDir), "manifest.json");
|
|
20843
20946
|
}
|
|
20844
20947
|
function loadManifest(baseDir, memoryDirOverride) {
|
|
20845
20948
|
if (fs7.existsSync(getManifestPath(baseDir))) {
|
|
@@ -20886,9 +20989,9 @@ function readManifestUnlocked(baseDir, memoryDirOverride, options = {}) {
|
|
|
20886
20989
|
}
|
|
20887
20990
|
function saveManifestUnlocked(manifest, baseDir) {
|
|
20888
20991
|
const manifestPath2 = getManifestPath(baseDir);
|
|
20889
|
-
const manifestDir2 =
|
|
20992
|
+
const manifestDir2 = path24.dirname(manifestPath2);
|
|
20890
20993
|
fs7.mkdirSync(manifestDir2, { recursive: true });
|
|
20891
|
-
const tempPath =
|
|
20994
|
+
const tempPath = path24.join(manifestDir2, `.manifest.${process.pid}.${Date.now()}.${crypto7.randomUUID()}.tmp`);
|
|
20892
20995
|
try {
|
|
20893
20996
|
fs7.writeFileSync(tempPath, `${JSON.stringify(manifest, null, 2)}
|
|
20894
20997
|
`, { flag: "wx" });
|
|
@@ -20903,7 +21006,7 @@ function saveManifestUnlocked(manifest, baseDir) {
|
|
|
20903
21006
|
}
|
|
20904
21007
|
function withManifestLock(baseDir, operation) {
|
|
20905
21008
|
const lockDir = `${getManifestPath(baseDir)}.lock`;
|
|
20906
|
-
fs7.mkdirSync(
|
|
21009
|
+
fs7.mkdirSync(path24.dirname(lockDir), { recursive: true });
|
|
20907
21010
|
const lockOwner = acquireManifestLock(lockDir);
|
|
20908
21011
|
try {
|
|
20909
21012
|
return operation();
|
|
@@ -20929,7 +21032,7 @@ function acquireManifestLock(lockDir) {
|
|
|
20929
21032
|
try {
|
|
20930
21033
|
fs7.mkdirSync(lockDir, { recursive: false });
|
|
20931
21034
|
try {
|
|
20932
|
-
fs7.writeFileSync(
|
|
21035
|
+
fs7.writeFileSync(path24.join(lockDir, "owner"), `${owner}
|
|
20933
21036
|
`, { flag: "wx" });
|
|
20934
21037
|
} catch (error) {
|
|
20935
21038
|
fs7.rmSync(lockDir, { recursive: true, force: true });
|
|
@@ -20959,7 +21062,7 @@ function acquireManifestLock(lockDir) {
|
|
|
20959
21062
|
}
|
|
20960
21063
|
function releaseManifestLock(lockDir, owner) {
|
|
20961
21064
|
try {
|
|
20962
|
-
const ownerPath =
|
|
21065
|
+
const ownerPath = path24.join(lockDir, "owner");
|
|
20963
21066
|
if (fs7.readFileSync(ownerPath, "utf8").trim() === owner) {
|
|
20964
21067
|
fs7.rmSync(lockDir, { recursive: true, force: true });
|
|
20965
21068
|
}
|
|
@@ -20975,7 +21078,7 @@ function removeStaleManifestLock(lockDir) {
|
|
|
20975
21078
|
try {
|
|
20976
21079
|
fs7.mkdirSync(reclaimDir, { recursive: false });
|
|
20977
21080
|
try {
|
|
20978
|
-
fs7.writeFileSync(
|
|
21081
|
+
fs7.writeFileSync(path24.join(reclaimDir, "owner"), `${reclaimOwner}
|
|
20979
21082
|
`, { flag: "wx" });
|
|
20980
21083
|
} catch (error) {
|
|
20981
21084
|
fs7.rmSync(reclaimDir, { recursive: true, force: true });
|
|
@@ -21050,7 +21153,7 @@ function readManifestLockSnapshot(lockDir) {
|
|
|
21050
21153
|
throw error;
|
|
21051
21154
|
}
|
|
21052
21155
|
try {
|
|
21053
|
-
const owner = fs7.readFileSync(
|
|
21156
|
+
const owner = fs7.readFileSync(path24.join(lockDir, "owner"), "utf8").trim();
|
|
21054
21157
|
return { mtimeMs: stat5.mtimeMs, owner };
|
|
21055
21158
|
} catch (error) {
|
|
21056
21159
|
if (error.code === "ENOENT") {
|
|
@@ -21122,8 +21225,8 @@ function sleepSync(ms) {
|
|
|
21122
21225
|
}
|
|
21123
21226
|
function createPersonalSpace(baseDir, memoryDirOverride) {
|
|
21124
21227
|
const homeDir = baseDir ?? resolveHomeDir();
|
|
21125
|
-
const standalonePath =
|
|
21126
|
-
const openclawPath =
|
|
21228
|
+
const standalonePath = path24.join(homeDir, ".engram", "memory");
|
|
21229
|
+
const openclawPath = path24.join(homeDir, ".openclaw", "workspace", "memory", "local");
|
|
21127
21230
|
const memoryDir = memoryDirOverride ?? readEnvVar("REMNIC_MEMORY_DIR") ?? readEnvVar("ENGRAM_MEMORY_DIR") ?? (fs7.existsSync(standalonePath) ? standalonePath : fs7.existsSync(openclawPath) ? openclawPath : standalonePath);
|
|
21128
21231
|
const normalizedMemoryDir = normalizeSpaceMemoryDir(memoryDir);
|
|
21129
21232
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -21152,7 +21255,7 @@ function createSpace(options) {
|
|
|
21152
21255
|
const id = options.name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-");
|
|
21153
21256
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
21154
21257
|
const memoryDir = normalizeSpaceMemoryDir(
|
|
21155
|
-
options.memoryDir ??
|
|
21258
|
+
options.memoryDir ?? path24.join(getSpacesDir(options.baseDir), id, "memory")
|
|
21156
21259
|
);
|
|
21157
21260
|
const space = updateManifest(options.baseDir, (manifest) => {
|
|
21158
21261
|
if (manifest.spaces.some((s) => s.id === id)) {
|
|
@@ -21373,14 +21476,14 @@ function mergeSpaces(sourceSpaceId, targetSpaceId, options) {
|
|
|
21373
21476
|
};
|
|
21374
21477
|
}
|
|
21375
21478
|
function getAuditLog(baseDir) {
|
|
21376
|
-
const auditPath =
|
|
21479
|
+
const auditPath = path24.join(getSpacesDir(baseDir), "audit.jsonl");
|
|
21377
21480
|
if (!fs7.existsSync(auditPath)) return [];
|
|
21378
21481
|
const lines = fs7.readFileSync(auditPath, "utf8").trim().split("\n");
|
|
21379
21482
|
return lines.filter((l) => l.trim()).map((l) => JSON.parse(l));
|
|
21380
21483
|
}
|
|
21381
21484
|
function appendAudit(entry, baseDir) {
|
|
21382
|
-
const auditPath =
|
|
21383
|
-
fs7.mkdirSync(
|
|
21485
|
+
const auditPath = path24.join(getSpacesDir(baseDir), "audit.jsonl");
|
|
21486
|
+
fs7.mkdirSync(path24.dirname(auditPath), { recursive: true });
|
|
21384
21487
|
const full = {
|
|
21385
21488
|
id: crypto7.randomUUID(),
|
|
21386
21489
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -21412,8 +21515,8 @@ function copyMemories(sourceDir, targetDir, options) {
|
|
|
21412
21515
|
continue;
|
|
21413
21516
|
}
|
|
21414
21517
|
const content = fs7.readFileSync(sourcePath, "utf8");
|
|
21415
|
-
const relativePath =
|
|
21416
|
-
const targetPath =
|
|
21518
|
+
const relativePath = path24.relative(sourceRoot, sourceRealPath);
|
|
21519
|
+
const targetPath = path24.resolve(targetRoot, relativePath);
|
|
21417
21520
|
if (!isPathInsideRoot(targetPath, targetRoot)) {
|
|
21418
21521
|
skipped++;
|
|
21419
21522
|
continue;
|
|
@@ -21455,8 +21558,8 @@ function copyMemories(sourceDir, targetDir, options) {
|
|
|
21455
21558
|
skipped++;
|
|
21456
21559
|
continue;
|
|
21457
21560
|
}
|
|
21458
|
-
fs7.mkdirSync(
|
|
21459
|
-
const targetParentRealPath = safeRealpath(
|
|
21561
|
+
fs7.mkdirSync(path24.dirname(targetPath), { recursive: true });
|
|
21562
|
+
const targetParentRealPath = safeRealpath(path24.dirname(targetPath));
|
|
21460
21563
|
if (!targetParentRealPath || !isPathInsideRoot(targetParentRealPath, targetRoot)) {
|
|
21461
21564
|
skipped++;
|
|
21462
21565
|
continue;
|
|
@@ -21473,7 +21576,7 @@ function walkMd2(dir) {
|
|
|
21473
21576
|
const results = [];
|
|
21474
21577
|
function walk(d) {
|
|
21475
21578
|
for (const entry of fs7.readdirSync(d, { withFileTypes: true })) {
|
|
21476
|
-
const fullPath =
|
|
21579
|
+
const fullPath = path24.join(d, entry.name);
|
|
21477
21580
|
if (entry.isSymbolicLink()) {
|
|
21478
21581
|
continue;
|
|
21479
21582
|
}
|
|
@@ -21502,8 +21605,8 @@ function safeRealpath(filePath) {
|
|
|
21502
21605
|
}
|
|
21503
21606
|
}
|
|
21504
21607
|
function isPathInsideRoot(candidatePath, rootPath) {
|
|
21505
|
-
const relative =
|
|
21506
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
21608
|
+
const relative = path24.relative(rootPath, candidatePath);
|
|
21609
|
+
return relative === "" || !relative.startsWith("..") && !path24.isAbsolute(relative);
|
|
21507
21610
|
}
|
|
21508
21611
|
function parseSimpleFrontmatter(content) {
|
|
21509
21612
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
@@ -21624,7 +21727,7 @@ var REMNIC_RECALL_DECISION_RULES = `## When to Use Recall vs Direct Read
|
|
|
21624
21727
|
// src/memory-extension/codex-publisher.ts
|
|
21625
21728
|
import fs8 from "fs";
|
|
21626
21729
|
import os3 from "os";
|
|
21627
|
-
import
|
|
21730
|
+
import path25 from "path";
|
|
21628
21731
|
var REMNIC_EXTENSION_DIR_NAME = "remnic";
|
|
21629
21732
|
function resolveEnvHome(env) {
|
|
21630
21733
|
if (env === void 0) return resolveHomeDir();
|
|
@@ -21633,12 +21736,12 @@ function resolveEnvHome(env) {
|
|
|
21633
21736
|
function expandTildeWithHome(input, homeDir) {
|
|
21634
21737
|
if (input === "~") return homeDir;
|
|
21635
21738
|
if (input.startsWith("~/") || input.startsWith("~\\")) {
|
|
21636
|
-
return
|
|
21739
|
+
return path25.join(homeDir, input.slice(2));
|
|
21637
21740
|
}
|
|
21638
21741
|
return input;
|
|
21639
21742
|
}
|
|
21640
21743
|
function normalizeHostRoot(input, homeDir) {
|
|
21641
|
-
return
|
|
21744
|
+
return path25.resolve(expandTildeWithHome(input.trim(), homeDir));
|
|
21642
21745
|
}
|
|
21643
21746
|
var CodexMemoryExtensionPublisher = class {
|
|
21644
21747
|
hostId = "codex";
|
|
@@ -21652,12 +21755,12 @@ var CodexMemoryExtensionPublisher = class {
|
|
|
21652
21755
|
async resolveExtensionRoot(env) {
|
|
21653
21756
|
const homeDir = resolveEnvHome(env);
|
|
21654
21757
|
const codexHomeInput = env === void 0 ? readEnvVar("CODEX_HOME")?.trim() : env.CODEX_HOME?.trim();
|
|
21655
|
-
const codexHome = codexHomeInput ? normalizeHostRoot(codexHomeInput, homeDir) :
|
|
21656
|
-
return
|
|
21758
|
+
const codexHome = codexHomeInput ? normalizeHostRoot(codexHomeInput, homeDir) : path25.resolve(homeDir, ".codex");
|
|
21759
|
+
return path25.join(codexHome, "memories_extensions", REMNIC_EXTENSION_DIR_NAME);
|
|
21657
21760
|
}
|
|
21658
21761
|
async isHostAvailable() {
|
|
21659
21762
|
try {
|
|
21660
|
-
const home = readEnvVar("CODEX_HOME")?.trim() ||
|
|
21763
|
+
const home = readEnvVar("CODEX_HOME")?.trim() || path25.join(resolveHomeDir(), ".codex");
|
|
21661
21764
|
return fs8.existsSync(home);
|
|
21662
21765
|
} catch {
|
|
21663
21766
|
return false;
|
|
@@ -21708,7 +21811,7 @@ When running inside the Codex phase-2 consolidation sandbox:
|
|
|
21708
21811
|
}
|
|
21709
21812
|
async publish(ctx) {
|
|
21710
21813
|
const extensionRoot = await this.resolveExtensionRoot();
|
|
21711
|
-
const instructionsPath =
|
|
21814
|
+
const instructionsPath = path25.join(extensionRoot, "instructions.md");
|
|
21712
21815
|
const filesWritten = [];
|
|
21713
21816
|
const skipped = [];
|
|
21714
21817
|
ctx.log.info(`Publishing Codex memory extension to ${extensionRoot}`);
|
|
@@ -21828,7 +21931,7 @@ function publisherForConnector(connectorId) {
|
|
|
21828
21931
|
// src/session-summaries/index.ts
|
|
21829
21932
|
import { createHash as createHash8 } from "crypto";
|
|
21830
21933
|
import { lstat as lstat2, mkdir as mkdir7, readFile as readFile6, readdir as readdir4, rename, rm, stat as stat4, writeFile as writeFile7 } from "fs/promises";
|
|
21831
|
-
import
|
|
21934
|
+
import path26 from "path";
|
|
21832
21935
|
|
|
21833
21936
|
// src/session-summaries/adapters.ts
|
|
21834
21937
|
var VALID_ROLES = /* @__PURE__ */ new Set(["user", "assistant", "tool", "system", "other"]);
|
|
@@ -22333,7 +22436,7 @@ function shortHash(value, length = 16) {
|
|
|
22333
22436
|
async function listTranscriptFiles(root, maxFiles) {
|
|
22334
22437
|
const out = [];
|
|
22335
22438
|
let truncated = false;
|
|
22336
|
-
const entrySortKey = (entryName, isDirectory) => isDirectory ? `${entryName}${
|
|
22439
|
+
const entrySortKey = (entryName, isDirectory) => isDirectory ? `${entryName}${path26.sep}` : entryName;
|
|
22337
22440
|
async function visit(dir) {
|
|
22338
22441
|
if (truncated) return;
|
|
22339
22442
|
const entries = (await readdir4(dir, { withFileTypes: true })).sort(
|
|
@@ -22343,13 +22446,13 @@ async function listTranscriptFiles(root, maxFiles) {
|
|
|
22343
22446
|
if (truncated) return;
|
|
22344
22447
|
if (entry.name.startsWith(".")) continue;
|
|
22345
22448
|
if (entry.isSymbolicLink()) continue;
|
|
22346
|
-
const fullPath =
|
|
22449
|
+
const fullPath = path26.join(dir, entry.name);
|
|
22347
22450
|
if (entry.isDirectory()) {
|
|
22348
22451
|
await visit(fullPath);
|
|
22349
22452
|
continue;
|
|
22350
22453
|
}
|
|
22351
22454
|
if (!entry.isFile()) continue;
|
|
22352
|
-
const ext =
|
|
22455
|
+
const ext = path26.extname(entry.name).toLowerCase();
|
|
22353
22456
|
if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
|
|
22354
22457
|
out.push(fullPath);
|
|
22355
22458
|
if (out.length > maxFiles) {
|
|
@@ -22376,7 +22479,7 @@ function normalizeInputDir(inputDir) {
|
|
|
22376
22479
|
if (typeof inputDir !== "string" || inputDir.trim().length === 0) {
|
|
22377
22480
|
throw new Error("inputDir must be a non-empty string");
|
|
22378
22481
|
}
|
|
22379
|
-
return
|
|
22482
|
+
return path26.resolve(expandTildePath(inputDir.trim()));
|
|
22380
22483
|
}
|
|
22381
22484
|
function initRoleCounts() {
|
|
22382
22485
|
return {
|
|
@@ -22533,11 +22636,11 @@ async function collectLocalSessionSummaries(options) {
|
|
|
22533
22636
|
continue;
|
|
22534
22637
|
}
|
|
22535
22638
|
seenFileHashes.add(fileHash);
|
|
22536
|
-
const fileExtension =
|
|
22639
|
+
const fileExtension = path26.extname(filePath).toLowerCase();
|
|
22537
22640
|
const parsed = await adapter.parseFile(
|
|
22538
22641
|
{
|
|
22539
22642
|
content,
|
|
22540
|
-
fileName:
|
|
22643
|
+
fileName: path26.basename(filePath),
|
|
22541
22644
|
fileExtension,
|
|
22542
22645
|
fileRef: fileHash
|
|
22543
22646
|
},
|
|
@@ -22593,7 +22696,7 @@ async function collectLocalSessionSummaries(options) {
|
|
|
22593
22696
|
}
|
|
22594
22697
|
async function readRedactionConfig(pathLike) {
|
|
22595
22698
|
if (!pathLike) return void 0;
|
|
22596
|
-
const raw = await readFile6(
|
|
22699
|
+
const raw = await readFile6(path26.resolve(expandTildePath(pathLike)), "utf-8");
|
|
22597
22700
|
const parsed = JSON.parse(raw);
|
|
22598
22701
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
22599
22702
|
throw new Error("redaction config must be a JSON object");
|
|
@@ -22606,15 +22709,15 @@ function toJsonl(drafts) {
|
|
|
22606
22709
|
}
|
|
22607
22710
|
function defaultDraftOutputPath(memoryDir, generatedAt) {
|
|
22608
22711
|
const stamp = generatedAt.replace(/[:.]/g, "-");
|
|
22609
|
-
return
|
|
22610
|
-
|
|
22712
|
+
return path26.join(
|
|
22713
|
+
path26.resolve(expandTildePath(memoryDir)),
|
|
22611
22714
|
"state",
|
|
22612
22715
|
"session-summary-drafts",
|
|
22613
22716
|
`session-summaries-${stamp}.jsonl`
|
|
22614
22717
|
);
|
|
22615
22718
|
}
|
|
22616
22719
|
async function writeDrafts(filePath, drafts) {
|
|
22617
|
-
await mkdir7(
|
|
22720
|
+
await mkdir7(path26.dirname(filePath), { recursive: true });
|
|
22618
22721
|
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
22619
22722
|
try {
|
|
22620
22723
|
await writeFile7(tempPath, toJsonl(drafts), "utf-8");
|
|
@@ -22632,7 +22735,7 @@ async function runLocalSessionSummaryCliCommand(options) {
|
|
|
22632
22735
|
});
|
|
22633
22736
|
const wroteFiles = [];
|
|
22634
22737
|
if (options.output) {
|
|
22635
|
-
const outputPath =
|
|
22738
|
+
const outputPath = path26.resolve(expandTildePath(options.output));
|
|
22636
22739
|
await writeDrafts(outputPath, report.drafts);
|
|
22637
22740
|
wroteFiles.push(outputPath);
|
|
22638
22741
|
}
|
|
@@ -22667,19 +22770,19 @@ async function runLocalSessionSummaryCliCommand(options) {
|
|
|
22667
22770
|
|
|
22668
22771
|
// src/transfer/capsule-fork.ts
|
|
22669
22772
|
import { lstat as lstat3, mkdir as mkdir8, readFile as readFile7, realpath as realpath2, writeFile as writeFile8 } from "fs/promises";
|
|
22670
|
-
import
|
|
22773
|
+
import path27 from "path";
|
|
22671
22774
|
async function forkCapsule(opts) {
|
|
22672
22775
|
validateForkId(opts.forkId);
|
|
22673
|
-
const rootAbs =
|
|
22776
|
+
const rootAbs = path27.resolve(opts.targetRoot);
|
|
22674
22777
|
await assertIsDirectoryNotSymlink(rootAbs, "forkCapsule", "targetRoot");
|
|
22675
|
-
const forkDirAbs =
|
|
22778
|
+
const forkDirAbs = path27.join(rootAbs, "forks", opts.forkId);
|
|
22676
22779
|
const forkEntryExists = await pathEntryExists(forkDirAbs);
|
|
22677
22780
|
if (forkEntryExists) {
|
|
22678
22781
|
throw new Error(
|
|
22679
22782
|
`forkCapsule: fork path already exists \u2014 forkId "${opts.forkId}" is already in use at: ${forkDirAbs}`
|
|
22680
22783
|
);
|
|
22681
22784
|
}
|
|
22682
|
-
const archiveAbs =
|
|
22785
|
+
const archiveAbs = path27.resolve(opts.sourceArchive);
|
|
22683
22786
|
const importResult = await importCapsule({
|
|
22684
22787
|
archivePath: archiveAbs,
|
|
22685
22788
|
root: rootAbs,
|
|
@@ -22701,10 +22804,10 @@ async function forkCapsule(opts) {
|
|
|
22701
22804
|
importedRecords: importResult.imported.length,
|
|
22702
22805
|
skippedRecords: importResult.skipped.length
|
|
22703
22806
|
};
|
|
22704
|
-
const lineagePath =
|
|
22807
|
+
const lineagePath = path27.join(forkDirAbs, "lineage.json");
|
|
22705
22808
|
const rootReal = await realpath2(rootAbs);
|
|
22706
22809
|
await assertRealpathInsideRoot(rootReal, lineagePath, `forks/${opts.forkId}/lineage.json`, "forkCapsule");
|
|
22707
|
-
await mkdir8(
|
|
22810
|
+
await mkdir8(path27.dirname(lineagePath), { recursive: true });
|
|
22708
22811
|
await writeFile8(lineagePath, JSON.stringify(lineage, null, 2) + "\n", "utf-8");
|
|
22709
22812
|
return {
|
|
22710
22813
|
archivePath: archiveAbs,
|
|
@@ -22718,11 +22821,11 @@ async function readForkLineage(targetRoot, forkId) {
|
|
|
22718
22821
|
if (typeof forkId !== "string" || forkId.length === 0 || forkId.length > 64 || !CAPSULE_ID_PATTERN.test(forkId)) {
|
|
22719
22822
|
return null;
|
|
22720
22823
|
}
|
|
22721
|
-
const rootAbs =
|
|
22824
|
+
const rootAbs = path27.resolve(targetRoot);
|
|
22722
22825
|
const rootReal = await realpath2(rootAbs).catch(() => rootAbs);
|
|
22723
|
-
const lineagePath =
|
|
22724
|
-
const rel =
|
|
22725
|
-
if (rel.startsWith("..") ||
|
|
22826
|
+
const lineagePath = path27.join(rootReal, "forks", forkId, "lineage.json");
|
|
22827
|
+
const rel = path27.relative(rootReal, lineagePath);
|
|
22828
|
+
if (rel.startsWith("..") || path27.isAbsolute(rel)) {
|
|
22726
22829
|
return null;
|
|
22727
22830
|
}
|
|
22728
22831
|
if (!await isLineagePathContained(rootReal, lineagePath)) {
|
|
@@ -22763,19 +22866,19 @@ async function pathEntryExists(absPath) {
|
|
|
22763
22866
|
async function isLineagePathContained(rootReal, lineagePath) {
|
|
22764
22867
|
let existing = lineagePath;
|
|
22765
22868
|
const suffix = [];
|
|
22766
|
-
while (existing !==
|
|
22869
|
+
while (existing !== path27.dirname(existing)) {
|
|
22767
22870
|
const st = await lstat3(existing).catch(() => null);
|
|
22768
22871
|
if (st !== null) break;
|
|
22769
|
-
suffix.unshift(
|
|
22770
|
-
existing =
|
|
22872
|
+
suffix.unshift(path27.basename(existing));
|
|
22873
|
+
existing = path27.dirname(existing);
|
|
22771
22874
|
}
|
|
22772
22875
|
const existingReal = await realpath2(existing).catch(() => existing);
|
|
22773
|
-
const targetReal = suffix.length > 0 ?
|
|
22774
|
-
const rel =
|
|
22876
|
+
const targetReal = suffix.length > 0 ? path27.join(existingReal, ...suffix) : existingReal;
|
|
22877
|
+
const rel = path27.relative(rootReal, targetReal);
|
|
22775
22878
|
if (rel === "") return true;
|
|
22776
22879
|
if (rel === "..") return false;
|
|
22777
|
-
if (rel.startsWith(`..${
|
|
22778
|
-
if (
|
|
22880
|
+
if (rel.startsWith(`..${path27.sep}`)) return false;
|
|
22881
|
+
if (path27.isAbsolute(rel)) return false;
|
|
22779
22882
|
return true;
|
|
22780
22883
|
}
|
|
22781
22884
|
|
|
@@ -22911,4 +23014,4 @@ export {
|
|
|
22911
23014
|
resolvePersistedMemoryRelativePath,
|
|
22912
23015
|
Orchestrator
|
|
22913
23016
|
};
|
|
22914
|
-
//# sourceMappingURL=chunk-
|
|
23017
|
+
//# sourceMappingURL=chunk-PNUOQYRJ.js.map
|