dsh-mnemon-source-memory-spaces 0.5.14 → 0.5.16

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/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { findMnemonCommand, isMnemonExecutable, resolveMnemonInvocation } from "./native-cli.js";
2
- import { CATEGORIES as CATEGORIES$1, EDGE_TYPES, INTENTS as INTENTS$1, MNEMON_EMBEDDING_PROTOCOLS, SOURCES as SOURCES$1 } from "./contracts.js";
2
+ import { CATEGORIES as CATEGORIES$1, EDGE_TYPES, INTENTS as INTENTS$1, MNEMON_EMBEDDING_PROTOCOLS, SOURCES as SOURCES$1, normalizeEntityKey } from "./contracts.js";
3
3
  import { a as MemoryProviderCatalog, i as EMPTY_MEMORY_PROVIDER_CATALOG, n as PrivateMemorySpaceProviderHost, r as MemoryProviderAdapterRegistry, t as createMemorySpaceProviderPlugin } from "./plugin-DlsNeZbQ.js";
4
4
  import { n as ProcessError, r as runProcess } from "./process-DIK9NnTg.js";
5
5
  import { r as defineMemorySpaceProvider } from "./definitions-DfmOqJjR.js";
@@ -348,6 +348,8 @@ var MemorySpaceRegistry = class MemorySpaceRegistry {
348
348
  let normalizedService = this.providerCatalog.normalizeService(providerId, service);
349
349
  const seen = /* @__PURE__ */ new Set();
350
350
  const existing = this.spaces.filter((body) => body.providerId === providerId);
351
+ const existingByExternalId = /* @__PURE__ */ new Map();
352
+ for (const body of existing) if (!existingByExternalId.has(body.externalId)) existingByExternalId.set(body.externalId, body);
351
353
  const reservedIds = new Set(this.spaces.filter((body) => body.providerId !== providerId).map((body) => body.id));
352
354
  const timestamp = this.now().toISOString();
353
355
  const projections = discovered.map((candidate) => {
@@ -359,7 +361,7 @@ var MemorySpaceRegistry = class MemorySpaceRegistry {
359
361
  ...normalizedService,
360
362
  ...connection
361
363
  });
362
- const previous = existing.find((body) => body.externalId === externalId);
364
+ const previous = existingByExternalId.get(externalId);
363
365
  let id = previous?.id ?? validateMemorySpaceId(`${providerId}-${createHash("sha256").update(externalId).digest("hex").slice(0, 24)}`);
364
366
  let suffix = 1;
365
367
  while (reservedIds.has(id)) {
@@ -756,9 +758,10 @@ var MemorySpaceRegistry = class MemorySpaceRegistry {
756
758
  const timestamp = this.now().toISOString();
757
759
  const legacyActive = this.runner.effectiveStore();
758
760
  let changed = false;
761
+ const knownIds = new Set(this.spaces.map((body) => body.id));
759
762
  for (const entry of readdirSync(this.directory, { withFileTypes: true })) {
760
763
  if (!entry.isDirectory() || !ID_PATTERN.test(entry.name) || !existsSync(join(this.directory, entry.name, "mnemon.db"))) continue;
761
- if (this.spaces.some((body) => body.id === entry.name)) continue;
764
+ if (knownIds.has(entry.name)) continue;
762
765
  this.spaces.push({
763
766
  id: entry.name,
764
767
  name: entry.name,
@@ -768,6 +771,7 @@ var MemorySpaceRegistry = class MemorySpaceRegistry {
768
771
  createdAt: timestamp,
769
772
  updatedAt: timestamp
770
773
  });
774
+ knownIds.add(entry.name);
771
775
  changed = true;
772
776
  }
773
777
  if (changed) this.save();
@@ -995,6 +999,88 @@ function lexicalRequiredMatchCount(tokens) {
995
999
  return Math.max(2, Math.ceil(tokens.length / 4));
996
1000
  }
997
1001
  //#endregion
1002
+ //#region src/entity-index.ts
1003
+ /**
1004
+ * The most memories one space's derived index asks list() for. Mnemon Native
1005
+ * lists a whole store; a Provider that stops earlier reports what it listed.
1006
+ */
1007
+ const ENTITY_INDEX_LIST_LIMIT = 1e5;
1008
+ /** The rail lists at most this many entities; the rest stay reachable by name. */
1009
+ const ENTITY_RAIL_LIMIT = 5e3;
1010
+ function buildSpaceEntityIndex(memories, memoryCount, complete) {
1011
+ const kept = [];
1012
+ const byKey = /* @__PURE__ */ new Map();
1013
+ const spellings = /* @__PURE__ */ new Map();
1014
+ for (const memory of memories) {
1015
+ const seen = /* @__PURE__ */ new Set();
1016
+ let position;
1017
+ for (const raw of memory.entities ?? []) {
1018
+ if (typeof raw !== "string") continue;
1019
+ const key = normalizeEntityKey(raw);
1020
+ if (key === "" || seen.has(key)) continue;
1021
+ seen.add(key);
1022
+ if (position === void 0) {
1023
+ position = kept.length;
1024
+ kept.push(memory);
1025
+ }
1026
+ const positions = byKey.get(key);
1027
+ if (positions === void 0) byKey.set(key, [position]);
1028
+ else positions.push(position);
1029
+ const name = raw.normalize("NFKC").trim();
1030
+ const names = spellings.get(key);
1031
+ if (names === void 0) spellings.set(key, /* @__PURE__ */ new Map([[name, 1]]));
1032
+ else names.set(name, (names.get(name) ?? 0) + 1);
1033
+ }
1034
+ }
1035
+ return {
1036
+ memories: kept,
1037
+ byKey,
1038
+ spellings,
1039
+ memoryCount,
1040
+ complete
1041
+ };
1042
+ }
1043
+ /** The spelling most memories use; the first one seen wins a tie. */
1044
+ function preferredSpelling(names) {
1045
+ let preferred = "";
1046
+ let most = 0;
1047
+ for (const [name, count] of names) if (count > most) {
1048
+ preferred = name;
1049
+ most = count;
1050
+ }
1051
+ return preferred;
1052
+ }
1053
+ /** Merge per-space indexes into one entity list, most frequent first. */
1054
+ function mergeEntityCounts(indexes) {
1055
+ const counts = /* @__PURE__ */ new Map();
1056
+ const spellings = /* @__PURE__ */ new Map();
1057
+ for (const index of indexes) for (const [key, positions] of index.byKey) {
1058
+ counts.set(key, (counts.get(key) ?? 0) + positions.length);
1059
+ let totals = spellings.get(key);
1060
+ if (totals === void 0) spellings.set(key, totals = /* @__PURE__ */ new Map());
1061
+ for (const [name, count] of index.spellings.get(key) ?? []) totals.set(name, (totals.get(name) ?? 0) + count);
1062
+ }
1063
+ const names = /* @__PURE__ */ new Map();
1064
+ for (const [key, totals] of spellings) names.set(key, preferredSpelling(totals));
1065
+ return {
1066
+ items: [...counts].map(([key, count]) => ({
1067
+ entity: names.get(key),
1068
+ count
1069
+ })).sort((left, right) => right.count - left.count || left.entity.localeCompare(right.entity)),
1070
+ names
1071
+ };
1072
+ }
1073
+ /** Importance first, then the newest; the order Mnemon Native lists a store in. */
1074
+ function compareEntityMemories(left, right) {
1075
+ return (right.importance ?? 0) - (left.importance ?? 0) || (right.createdAt ?? "").localeCompare(left.createdAt ?? "") || (left.memoryBodyName ?? "").localeCompare(right.memoryBodyName ?? "") || left.id.localeCompare(right.id);
1076
+ }
1077
+ /** Every memory across the indexes that carries the entity key. */
1078
+ function memoriesWithEntity(indexes, key) {
1079
+ const memories = [];
1080
+ for (const index of indexes) for (const position of index.byKey.get(key) ?? []) memories.push(index.memories[position]);
1081
+ return memories.sort(compareEntityMemories);
1082
+ }
1083
+ //#endregion
998
1084
  //#region src/recall-quality/policies.ts
999
1085
  function expandedLimit(context) {
1000
1086
  return Math.min(50, Math.max(context.requestedLimit, Math.ceil(context.requestedLimit * context.config.candidateMultiplier)));
@@ -1204,6 +1290,39 @@ function text(value) {
1204
1290
  function number(value) {
1205
1291
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1206
1292
  }
1293
+ /**
1294
+ * How long a selection reuses the Provider health its index was checked with.
1295
+ * The rail always checks again; a write through this Source drops both at once.
1296
+ */
1297
+ const ENTITY_SELECTION_STATUS_REUSE_MS = 1e4;
1298
+ /** How long an index that statistics cannot check is reused; a write through this Source still drops it. */
1299
+ const ENTITY_INDEX_UNCHECKED_REUSE_MS = 1e4;
1300
+ function entityName(entity) {
1301
+ const name = entity.trim();
1302
+ if (name.length > 200) throw new Error("entity is too long (max 200 characters)");
1303
+ return name;
1304
+ }
1305
+ function memoryKey(memoryBodyId, id) {
1306
+ return `${memoryBodyId}\u0000${id}`;
1307
+ }
1308
+ /**
1309
+ * What must stay the same for a space's entity index to stay valid: its
1310
+ * Provider statistics (Mnemon counts every write in its operation log) and the
1311
+ * Source's own record of the space. Without statistics it cannot be checked.
1312
+ */
1313
+ function entityIndexFingerprint(body, status) {
1314
+ const stats = status.stats;
1315
+ if (stats === void 0) return void 0;
1316
+ return JSON.stringify([
1317
+ body.provider.id,
1318
+ body.updatedAt,
1319
+ stats.totalInsights,
1320
+ stats.deletedInsights,
1321
+ stats.edgeCount,
1322
+ stats.oplogCount,
1323
+ stats.dbSizeBytes
1324
+ ]);
1325
+ }
1207
1326
  function readSource(body, mode, status, itemCount, options = {}) {
1208
1327
  return {
1209
1328
  memoryBodyId: body.id,
@@ -1373,6 +1492,12 @@ var MemorySpacesService = class {
1373
1492
  providers;
1374
1493
  recallQualityPolicy;
1375
1494
  spacesInFlight;
1495
+ /** One entity index per active entity space, valid while its fingerprint holds. */
1496
+ entityIndexCache = /* @__PURE__ */ new Map();
1497
+ /** settledAt stays undefined while the read runs, which every caller shares. */
1498
+ entityStatusCache = /* @__PURE__ */ new Map();
1499
+ /** The related read each page view is waiting for; a newer selection cancels the older one. */
1500
+ entityRelatedViews = /* @__PURE__ */ new Map();
1376
1501
  providersDisposed = false;
1377
1502
  /** The CLI version a full status last read, and the binary it read it from. */
1378
1503
  cliVersion;
@@ -1684,6 +1809,7 @@ var MemorySpacesService = class {
1684
1809
  }
1685
1810
  const provider = this.providerFor(body);
1686
1811
  provider.invalidateStatus?.(body.id);
1812
+ this.invalidateEntityIndex(body.id);
1687
1813
  const status = await provider.status(body, signal);
1688
1814
  return {
1689
1815
  ...body,
@@ -1692,7 +1818,8 @@ var MemorySpacesService = class {
1692
1818
  ...status
1693
1819
  };
1694
1820
  }
1695
- async search(request, signal) {
1821
+ /** exclude: memoryKey()s to leave out before the quality policy selects. */
1822
+ async search(request, signal, options = {}) {
1696
1823
  const query = required(request.query, "query", 2e3);
1697
1824
  const limit = memoryInputInteger(request.limit, this.config.defaultRecallLimit, 1, 50);
1698
1825
  const qualityContext = {
@@ -1751,7 +1878,8 @@ var MemorySpacesService = class {
1751
1878
  const hints = [];
1752
1879
  for (const [bodyOrder, { body, result }] of selectedBatches.entries()) {
1753
1880
  const scoreSemantics = this.providerFor(body).scoreSemantics;
1754
- candidates.push(...result.results.map((entry, index) => ({
1881
+ const entries = options.exclude === void 0 ? result.results : result.results.filter((entry) => !options.exclude.has(memoryKey(body.id, entry.id)));
1882
+ candidates.push(...entries.map((entry, index) => ({
1755
1883
  insight: this.annotate(entry, body),
1756
1884
  memoryBodyId: body.id,
1757
1885
  providerId: body.provider.id,
@@ -1797,22 +1925,36 @@ var MemorySpacesService = class {
1797
1925
  }
1798
1926
  const { hints, quality } = evaluation;
1799
1927
  const selected = recoveryPlan === void 0 ? quality.selected : prioritizeRecoveryEvidence(quality.selected, recoveryPlan);
1800
- const qualityStats = (memoryBodyId) => {
1801
- const evaluated = quality.evaluated.filter((candidate) => candidate.candidate.memoryBodyId === memoryBodyId);
1802
- const selected = quality.selected.filter((candidate) => candidate.candidate.memoryBodyId === memoryBodyId);
1803
- return {
1804
- policyId: quality.policyId,
1805
- ...quality.fallbackFrom === void 0 ? {} : { fallbackFrom: quality.fallbackFrom },
1806
- fetched: evaluated.length,
1807
- retained: evaluated.filter((candidate) => candidate.decision.action === "keep").length,
1808
- selected: selected.length,
1809
- droppedLowScore: evaluated.filter((candidate) => candidate.decision.action === "drop" && candidate.decision.reason === "low-score").length,
1810
- droppedNonPositiveScore: evaluated.filter((candidate) => candidate.decision.action === "drop" && candidate.decision.reason === "non-positive-score").length,
1811
- droppedInvalidScore: evaluated.filter((candidate) => candidate.decision.action === "drop" && candidate.decision.reason === "invalid-score").length,
1812
- unscored: evaluated.filter((candidate) => candidate.decision.reason === "unscored").length,
1813
- unscaled: evaluated.filter((candidate) => candidate.decision.reason === "unscaled-score").length
1814
- };
1815
- };
1928
+ const qualityStats = /* @__PURE__ */ new Map();
1929
+ for (const { body } of batches) qualityStats.set(body.id, {
1930
+ policyId: quality.policyId,
1931
+ ...quality.fallbackFrom === void 0 ? {} : { fallbackFrom: quality.fallbackFrom },
1932
+ fetched: 0,
1933
+ retained: 0,
1934
+ selected: 0,
1935
+ droppedLowScore: 0,
1936
+ droppedNonPositiveScore: 0,
1937
+ droppedInvalidScore: 0,
1938
+ unscored: 0,
1939
+ unscaled: 0
1940
+ });
1941
+ for (const { candidate, decision } of quality.evaluated) {
1942
+ const stats = qualityStats.get(candidate.memoryBodyId);
1943
+ if (stats === void 0) continue;
1944
+ stats.fetched += 1;
1945
+ if (decision.action === "keep") stats.retained += 1;
1946
+ if (decision.action === "drop") {
1947
+ if (decision.reason === "low-score") stats.droppedLowScore += 1;
1948
+ if (decision.reason === "non-positive-score") stats.droppedNonPositiveScore += 1;
1949
+ if (decision.reason === "invalid-score") stats.droppedInvalidScore += 1;
1950
+ }
1951
+ if (decision.reason === "unscored") stats.unscored += 1;
1952
+ if (decision.reason === "unscaled-score") stats.unscaled += 1;
1953
+ }
1954
+ for (const { candidate } of quality.selected) {
1955
+ const stats = qualityStats.get(candidate.memoryBodyId);
1956
+ if (stats !== void 0) stats.selected += 1;
1957
+ }
1816
1958
  return {
1817
1959
  query,
1818
1960
  mode,
@@ -1822,7 +1964,7 @@ var MemorySpacesService = class {
1822
1964
  ...decision.normalizedScore === void 0 ? {} : { normalizedScore: decision.normalizedScore }
1823
1965
  })),
1824
1966
  sources: batches.map((batch) => {
1825
- const stats = qualityStats(batch.body.id);
1967
+ const stats = qualityStats.get(batch.body.id);
1826
1968
  if (batch.source.status === "unavailable" || batch.source.status === "unsupported") return {
1827
1969
  ...batch.source,
1828
1970
  quality: stats
@@ -1995,41 +2137,208 @@ var MemorySpacesService = class {
1995
2137
  sources: batches.map((batch) => batch.source)
1996
2138
  };
1997
2139
  }
2140
+ /**
2141
+ * The entities of the active spaces, each counted once per memory that
2142
+ * carries it. With an entity, also the first page of those memories.
2143
+ */
1998
2144
  async entities(entity, limit, signal) {
1999
- const active = (await this.spaces(signal)).items.filter((body) => body.active);
2000
- const capable = active.filter((body) => body.provider.capabilities.entities);
2001
- const entityCounts = /* @__PURE__ */ new Map();
2002
- for (const body of capable) for (const item of body.stats?.topEntities ?? []) entityCounts.set(item.entity, (entityCounts.get(item.entity) ?? 0) + item.count);
2003
- const items = [...entityCounts].map(([name, count]) => ({
2004
- entity: name,
2005
- count
2006
- })).sort((left, right) => right.count - left.count);
2007
- const sources = active.map((body) => {
2008
- if (!body.provider.capabilities.entities) return readSource(body, "unsupported", "unsupported", 0, { hint: "This provider does not expose an entity index." });
2009
- if (!body.healthy) return readSource(body, "entities", "unavailable", 0, { hint: body.error ?? "Provider unavailable." });
2010
- const count = body.stats?.topEntities.length ?? 0;
2011
- return readSource(body, "entities", count === 0 ? "empty" : "ready", count);
2012
- });
2013
- const selected = entity?.trim() ?? "";
2014
- if (selected === "") return {
2015
- items,
2145
+ const selected = entity === void 0 ? "" : entityName(entity);
2146
+ const read = await this.readEntityIndexes(signal, 0);
2147
+ const { items } = mergeEntityCounts(read.indexes);
2148
+ const view = {
2149
+ items: items.slice(0, ENTITY_RAIL_LIMIT),
2016
2150
  insights: [],
2017
- sources
2151
+ sources: read.sources,
2152
+ total: items.length,
2153
+ complete: read.complete
2018
2154
  };
2019
- if (selected.length > 200) throw new Error("entity is too long (max 200 characters)");
2020
- const readableIds = capable.filter((body) => body.healthy).map((body) => body.id);
2155
+ if (selected === "") return view;
2156
+ const page = this.entityPage(read, selected, 0, memoryInputInteger(limit, 20, 1, 50));
2021
2157
  return {
2022
- items,
2023
- selected,
2024
- insights: readableIds.length === 0 ? [] : (await this.search({
2158
+ ...view,
2159
+ selected: page.entity,
2160
+ insights: page.items
2161
+ };
2162
+ }
2163
+ /** One page of the memories that carry an entity, by importance and then recency. */
2164
+ async entityMemories(entity, offset, limit, signal) {
2165
+ const selected = entityName(entity);
2166
+ if (selected === "") throw new Error("entity is required");
2167
+ const read = await this.readEntityIndexes(signal, ENTITY_SELECTION_STATUS_REUSE_MS);
2168
+ return this.entityPage(read, selected, memoryInputInteger(offset, 0, 0, 1e6), memoryInputInteger(limit, 50, 1, 200));
2169
+ }
2170
+ /**
2171
+ * What recall relates to an entity, without the memories that carry it.
2172
+ * Those are left out before the quality policy selects, so they cannot use up
2173
+ * its places and leave the related list empty.
2174
+ */
2175
+ async entityRelated(entity, limit, signal, view) {
2176
+ const selected = entityName(entity);
2177
+ if (selected === "") throw new Error("entity is required");
2178
+ const superseding = view === void 0 ? void 0 : new AbortController();
2179
+ if (view !== void 0) {
2180
+ this.entityRelatedViews.get(view)?.abort(/* @__PURE__ */ new Error("superseded by a newer entity selection"));
2181
+ this.entityRelatedViews.set(view, superseding);
2182
+ }
2183
+ const combined = superseding === void 0 ? signal : signal === void 0 ? superseding.signal : AbortSignal.any([signal, superseding.signal]);
2184
+ try {
2185
+ const read = await this.readEntityIndexes(combined, ENTITY_SELECTION_STATUS_REUSE_MS);
2186
+ const key = normalizeEntityKey(selected);
2187
+ const carrying = /* @__PURE__ */ new Set();
2188
+ for (const [bodyId, index] of read.byBody) for (const position of index.byKey.get(key) ?? []) carrying.add(memoryKey(bodyId, index.memories[position].id));
2189
+ const display = mergeEntityCounts(read.indexes).names.get(key) ?? selected;
2190
+ const readableIds = read.readable.map((body) => body.id);
2191
+ if (readableIds.length === 0) return {
2192
+ entity: display,
2193
+ items: [],
2194
+ sources: []
2195
+ };
2196
+ const result = await this.search({
2025
2197
  query: selected,
2026
2198
  intent: "ENTITY",
2027
2199
  limit: memoryInputInteger(limit, 20, 1, 50),
2028
2200
  memoryBodyIds: readableIds
2029
- }, signal)).results,
2030
- sources
2201
+ }, combined, { exclude: carrying });
2202
+ combined?.throwIfAborted();
2203
+ return {
2204
+ entity: display,
2205
+ items: result.results,
2206
+ sources: result.sources
2207
+ };
2208
+ } finally {
2209
+ if (view !== void 0 && this.entityRelatedViews.get(view) === superseding) this.entityRelatedViews.delete(view);
2210
+ }
2211
+ }
2212
+ entityPage(read, selected, offset, limit) {
2213
+ const key = normalizeEntityKey(selected);
2214
+ const memories = memoriesWithEntity(read.indexes, key);
2215
+ return {
2216
+ entity: mergeEntityCounts(read.indexes).names.get(key) ?? selected,
2217
+ total: memories.length,
2218
+ offset,
2219
+ items: memories.slice(offset, offset + limit),
2220
+ complete: read.complete,
2221
+ sources: read.sources
2031
2222
  };
2032
2223
  }
2224
+ /**
2225
+ * One entity index per active space that has an entity index, read
2226
+ * concurrently. A space keeps its index while its Provider statistics and
2227
+ * metadata stay the same; a write through this Source drops it.
2228
+ */
2229
+ async readEntityIndexes(signal, statusMaxAgeMs) {
2230
+ signal?.throwIfAborted();
2231
+ const active = this.memorySpaces.active();
2232
+ const capable = new Set(active.filter((body) => body.provider.capabilities.entities).map((body) => body.id));
2233
+ for (const id of this.entityIndexCache.keys()) if (!capable.has(id)) this.entityIndexCache.delete(id);
2234
+ const reads = await Promise.all(active.map(async (body) => {
2235
+ if (!body.provider.capabilities.entities) return {
2236
+ body,
2237
+ recall: false,
2238
+ source: readSource(body, "unsupported", "unsupported", 0, { hint: "This provider does not expose an entity index." })
2239
+ };
2240
+ const status = await this.entitySpaceStatus(body, statusMaxAgeMs);
2241
+ if (!status.healthy) return {
2242
+ body,
2243
+ recall: false,
2244
+ source: readSource(body, "entities", "unavailable", 0, { hint: status.error ?? "Provider unavailable." })
2245
+ };
2246
+ const recall = body.provider.capabilities.search;
2247
+ if (this.providerFor(body).entityIndex === void 0 && !body.provider.capabilities.browse) return {
2248
+ body,
2249
+ recall,
2250
+ source: readSource(body, "query-only", "query-required", 0, { hint: "This provider can only be queried; its memories appear among related memories." })
2251
+ };
2252
+ try {
2253
+ const index = await this.entityIndexFor(body, status);
2254
+ const options = {
2255
+ memoryCount: index.memoryCount,
2256
+ complete: index.complete,
2257
+ ...index.complete ? {} : { hint: "The Provider indexed only part of this space." }
2258
+ };
2259
+ return {
2260
+ body,
2261
+ index,
2262
+ recall,
2263
+ source: readSource(body, "entities", index.byKey.size === 0 ? "empty" : "ready", index.byKey.size, options)
2264
+ };
2265
+ } catch (error) {
2266
+ return {
2267
+ body,
2268
+ recall: false,
2269
+ source: readSource(body, "entities", "unavailable", 0, { hint: error instanceof Error ? error.message : String(error) })
2270
+ };
2271
+ }
2272
+ }));
2273
+ signal?.throwIfAborted();
2274
+ const byBody = /* @__PURE__ */ new Map();
2275
+ for (const read of reads) if (read.index !== void 0) byBody.set(read.body.id, read.index);
2276
+ const indexes = [...byBody.values()];
2277
+ return {
2278
+ byBody,
2279
+ indexes,
2280
+ readable: reads.filter((read) => read.recall).map((read) => read.body),
2281
+ sources: reads.map((read) => read.source),
2282
+ complete: indexes.every((index) => index.complete)
2283
+ };
2284
+ }
2285
+ /** Provider health for the entity reads: a read in flight is shared, a finished one reused for less than maxAgeMs. */
2286
+ entitySpaceStatus(body, maxAgeMs) {
2287
+ const recent = this.entityStatusCache.get(body.id);
2288
+ if (recent !== void 0 && (recent.settledAt === void 0 || Date.now() - recent.settledAt < maxAgeMs)) return recent.status;
2289
+ const entry = {
2290
+ settledAt: void 0,
2291
+ status: Promise.resolve(void 0)
2292
+ };
2293
+ entry.status = this.providerFor(body).status(body).catch((error) => ({
2294
+ healthy: false,
2295
+ error: error instanceof Error ? error.message : String(error)
2296
+ })).then((status) => {
2297
+ entry.settledAt = Date.now();
2298
+ return status;
2299
+ });
2300
+ this.entityStatusCache.set(body.id, entry);
2301
+ return entry.status;
2302
+ }
2303
+ entityIndexFor(body, status) {
2304
+ const fingerprint = entityIndexFingerprint(body, status);
2305
+ const cached = this.entityIndexCache.get(body.id);
2306
+ if (cached !== void 0 && cached.fingerprint === fingerprint) {
2307
+ if (fingerprint !== void 0 || cached.settledAt === void 0 || Date.now() - cached.settledAt < ENTITY_INDEX_UNCHECKED_REUSE_MS) return cached.index;
2308
+ }
2309
+ const entry = {
2310
+ fingerprint,
2311
+ settledAt: void 0,
2312
+ index: Promise.resolve(void 0)
2313
+ };
2314
+ entry.index = this.buildEntityIndex(body).then((index) => {
2315
+ entry.settledAt = Date.now();
2316
+ return index;
2317
+ }, (error) => {
2318
+ if (this.entityIndexCache.get(body.id) === entry) this.entityIndexCache.delete(body.id);
2319
+ throw error;
2320
+ });
2321
+ this.entityIndexCache.set(body.id, entry);
2322
+ return entry.index;
2323
+ }
2324
+ async buildEntityIndex(body) {
2325
+ const provider = this.providerFor(body);
2326
+ if (provider.entityIndex !== void 0) {
2327
+ const owned = await provider.entityIndex(body);
2328
+ return buildSpaceEntityIndex(owned.memories.map((memory) => this.entityMemory(memory, body)), owned.memories.length, owned.complete);
2329
+ }
2330
+ const listed = await provider.list(body, { limit: ENTITY_INDEX_LIST_LIMIT });
2331
+ return buildSpaceEntityIndex(listed.map((memory) => this.entityMemory(memory, body)), listed.length, listed.length < ENTITY_INDEX_LIST_LIMIT);
2332
+ }
2333
+ /** A listed memory, not a query result: no relevance fields. */
2334
+ entityMemory(memory, body) {
2335
+ const { score: _score, normalizedScore: _normalized, relevanceTier: _tier, federatedScore: _federated, confidence: _confidence, intent: _intent, matchedVia: _matchedVia, depth: _depth, edgeType: _edgeType, ...listed } = memory;
2336
+ return this.annotate(listed, body);
2337
+ }
2338
+ invalidateEntityIndex(memoryBodyId) {
2339
+ this.entityIndexCache.delete(memoryBodyId);
2340
+ this.entityStatusCache.delete(memoryBodyId);
2341
+ }
2033
2342
  async remember(request, signal) {
2034
2343
  this.assertWritable();
2035
2344
  const prepared = this.prepareRemember(request);
@@ -2044,13 +2353,21 @@ var MemorySpacesService = class {
2044
2353
  */
2045
2354
  async rememberMany(requests, signal) {
2046
2355
  this.assertWritable();
2047
- const prepared = requests.map((request) => this.prepareRemember(request));
2356
+ const destinations = /* @__PURE__ */ new Map();
2357
+ const prepared = requests.map((request) => this.prepareRemember(request, destinations));
2048
2358
  const results = new Array(prepared.length);
2049
2359
  const groups = /* @__PURE__ */ new Map();
2050
- for (const [index, entry] of prepared.entries()) groups.set(entry.body.id, [...groups.get(entry.body.id) ?? [], {
2051
- ...entry,
2052
- index
2053
- }]);
2360
+ for (const [index, entry] of prepared.entries()) {
2361
+ const group = groups.get(entry.body.id);
2362
+ if (group === void 0) groups.set(entry.body.id, [{
2363
+ ...entry,
2364
+ index
2365
+ }]);
2366
+ else group.push({
2367
+ ...entry,
2368
+ index
2369
+ });
2370
+ }
2054
2371
  for (const group of groups.values()) {
2055
2372
  const body = group[0].body;
2056
2373
  const provider = this.providerFor(body);
@@ -2066,8 +2383,9 @@ var MemorySpacesService = class {
2066
2383
  providerChanged ||= mutationResultCommitted(result);
2067
2384
  }
2068
2385
  }
2386
+ const batched = new Set(batch);
2069
2387
  for (const entry of group) {
2070
- if (batch.includes(entry)) continue;
2388
+ if (batched.has(entry)) continue;
2071
2389
  const result = await provider.remember(body, entry.request, signal);
2072
2390
  results[entry.index] = this.annotateResult(result, body);
2073
2391
  providerChanged ||= mutationResultCommitted(result);
@@ -2273,10 +2591,14 @@ var MemorySpacesService = class {
2273
2591
  return provider;
2274
2592
  }
2275
2593
  readSpaces(ids) {
2276
- const active = this.memorySpaces.active();
2277
- if (ids === void 0 || ids.length === 0) return active;
2278
- return [...new Set(ids.map((id) => id.trim()).filter((id) => id !== ""))].map((id) => {
2279
- const body = this.memorySpaces.get(id);
2594
+ if (ids === void 0 || ids.length === 0) return this.memorySpaces.active();
2595
+ const requested = [...new Set(ids.map((id) => id.trim()).filter((id) => id !== ""))];
2596
+ const available = /* @__PURE__ */ new Map();
2597
+ for (const body of this.memorySpaces.list()) if (!available.has(body.id)) available.set(body.id, body);
2598
+ return requested.map((id) => {
2599
+ const normalized = validateMemorySpaceId(id);
2600
+ const body = available.get(normalized);
2601
+ if (body === void 0) throw new Error(`unknown memory space: ${normalized}`);
2280
2602
  if (!body.active) throw new Error(`memory space is not active for reading: ${id}`);
2281
2603
  if (!this.isNativeSpace(body) && !this.memorySpaces.providerServiceEnabled(body.provider.id)) throw new Error(`${body.provider.label} is disabled on the dsh-mnemon page under Plugins`);
2282
2604
  return body;
@@ -2303,8 +2625,10 @@ var MemorySpacesService = class {
2303
2625
  if (active.length !== 1) throw new Error("memoryBodyId is required when the number of active memory spaces is not exactly one");
2304
2626
  return active[0];
2305
2627
  }
2306
- prepareRemember(request) {
2307
- const body = this.writeSpace(request.memoryBodyId);
2628
+ prepareRemember(request, destinations) {
2629
+ const key = request.memoryBodyId?.trim() ?? "";
2630
+ const body = destinations?.get(key) ?? this.writeSpace(request.memoryBodyId);
2631
+ destinations?.set(key, body);
2308
2632
  const content = required(request.content, "content", 8192);
2309
2633
  const importance = memoryInputInteger(request.importance, 3, 1, 5);
2310
2634
  const category = allowed(request.category, CATEGORIES$1, "category") ?? "general";
@@ -2345,6 +2669,7 @@ var MemorySpacesService = class {
2345
2669
  };
2346
2670
  }
2347
2671
  activateAfterWrite(body, providerChanged) {
2672
+ this.invalidateEntityIndex(body.id);
2348
2673
  if (!providerChanged) return;
2349
2674
  if (!body.active) this.memorySpaces.setActive(body.id, true);
2350
2675
  else this.memorySpaces.touch(body.id);
@@ -2741,6 +3066,8 @@ async function manageMemorySpaces(service, request) {
2741
3066
  ...memoryInputStringArray(input.memoryBodyIds, "memoryBodyIds", 1e4) === void 0 ? {} : { memoryBodyIds: memoryInputStringArray(input.memoryBodyIds, "memoryBodyIds", 1e4) }
2742
3067
  }, request.signal));
2743
3068
  case "entities": return managementResult(service, await service.entities(memoryInputText(input.entity, "entity", 500, false), input.limit === void 0 ? void 0 : memoryInputInteger(input.limit, 100, 1, 1e4), request.signal));
3069
+ case "entity-memories": return managementResult(service, await service.entityMemories(memoryInputText(input.entity, "entity", 500), input.offset === void 0 ? void 0 : memoryInputInteger(input.offset, 0, 0, 1e6), input.limit === void 0 ? void 0 : memoryInputInteger(input.limit, 50, 1, 200), request.signal));
3070
+ case "entity-related": return managementResult(service, await service.entityRelated(memoryInputText(input.entity, "entity", 500), input.limit === void 0 ? void 0 : memoryInputInteger(input.limit, 20, 1, 50), request.signal, memoryInputText(input.view, "view", 200, false)));
2744
3071
  case "search": return managementResult(service, await service.search({
2745
3072
  query: memoryInputText(input.query, "query", 2e3),
2746
3073
  ...memoryInputText(input.mode, "mode", 20, false) === void 0 ? {} : { mode: memoryInputText(input.mode, "mode", 20, false) },
@@ -1,5 +1,5 @@
1
1
  import { CATEGORIES, Category, EDGE_TYPES, EdgeType, INTENTS, Insight, Intent, JsonValue, MemoryBody, MemoryBodyProvider, MemoryBodyStats, MemoryGraphEdge, MemoryGraphNode, MemoryGraphSnapshot, MemoryListRequest, MemoryProviderCapabilities, MemoryProviderConfigField, MemoryProviderConfigOption, MemoryProviderConnection, MemoryProviderConnectionValue, MemoryProviderDescriptor, MemoryProviderIcon, MemoryProviderId, MemorySpace, MemorySpaceProvider, MemorySpaceStats, OpenVikingBodyConnection, OpenVikingSpaceConnection, RememberRequest, SOURCES, SearchRequest, Source } from "./contracts.js";
2
- import { C as ProviderSpaceStatus, S as ProviderSearchResult, _ as MemorySpaceNativeRunner, a as MemorySpaceProviderHost, b as ProviderMemorySpace, c as MemorySpaceProviderRuntimeContext, d as defineMemorySpaceProviderDefinition, f as MemoryProviderAdapter, g as MemorySpaceNativeRunOptions, h as MemorySpaceAuthority, i as MemorySpaceProviderEntry, l as MemorySpaceProviderScoreSemantics, m as MemoryProviderScoreSemantics, n as MemorySpaceProviderDefinition, o as MemorySpaceProviderManifest, p as MemoryProviderAdapterFactoryContext, r as MemorySpaceProviderDisposer, s as MemorySpaceProviderModule, t as MEMORY_SPACE_PROVIDER_API_VERSION, u as defineMemorySpaceProvider, v as NORMALIZED_RELEVANCE_SCORE, x as ProviderScoreSemantics, y as ProviderBodyStatus } from "./definitions-DwiEZSE_.js";
2
+ import { C as ProviderSearchResult, S as ProviderScoreSemantics, _ as MemorySpaceNativeRunner, a as MemorySpaceProviderHost, b as ProviderEntityIndex, c as MemorySpaceProviderRuntimeContext, d as defineMemorySpaceProviderDefinition, f as MemoryProviderAdapter, g as MemorySpaceNativeRunOptions, h as MemorySpaceAuthority, i as MemorySpaceProviderEntry, l as MemorySpaceProviderScoreSemantics, m as MemoryProviderScoreSemantics, n as MemorySpaceProviderDefinition, o as MemorySpaceProviderManifest, p as MemoryProviderAdapterFactoryContext, r as MemorySpaceProviderDisposer, s as MemorySpaceProviderModule, t as MEMORY_SPACE_PROVIDER_API_VERSION, u as defineMemorySpaceProvider, v as NORMALIZED_RELEVANCE_SCORE, w as ProviderSpaceStatus, x as ProviderMemorySpace, y as ProviderBodyStatus } from "./definitions-BcYzE092.js";
3
3
  //#region src/providers/http.d.ts
4
4
  interface HttpProviderOptions {
5
5
  label?: string;
@@ -60,4 +60,4 @@ declare const DEFAULT_MAX_OUTPUT_BYTES: number;
60
60
  /** Spawn without a shell, with bounded output and cooperative cancellation. */
61
61
  declare const runProcess: ProcessRunner;
62
62
  //#endregion
63
- export { CATEGORIES, type Category, DEFAULT_MAX_OUTPUT_BYTES, EDGE_TYPES, type EdgeType, HttpMemoryProvider, HttpProviderOptions, INTENTS, type Insight, type Intent, JsonRequestOptions, type JsonValue, MEMORY_SPACE_PROVIDER_API_VERSION, type MemoryBody, type MemoryBodyProvider, type MemoryBodyStats, type MemoryGraphEdge, type MemoryGraphNode, type MemoryGraphSnapshot, type MemoryListRequest, MemoryProviderAdapter, MemoryProviderAdapterFactoryContext, type MemoryProviderCapabilities, type MemoryProviderConfigField, type MemoryProviderConfigOption, type MemoryProviderConnection, type MemoryProviderConnectionValue, type MemoryProviderDescriptor, type MemoryProviderIcon, type MemoryProviderId, MemoryProviderScoreSemantics, type MemorySpace, MemorySpaceAuthority, MemorySpaceNativeRunOptions, MemorySpaceNativeRunner, type MemorySpaceProvider, type MemorySpaceProviderDefinition, type MemorySpaceProviderDisposer, type MemorySpaceProviderEntry, type MemorySpaceProviderHost, type MemorySpaceProviderManifest, type MemorySpaceProviderModule, type MemorySpaceProviderRuntimeContext, type MemorySpaceProviderScoreSemantics, type MemorySpaceStats, NORMALIZED_RELEVANCE_SCORE, type OpenVikingBodyConnection, type OpenVikingSpaceConnection, ProcessError, ProcessFailureReason, ProcessOptions, ProcessResult, ProcessRunner, ProviderBodyStatus, ProviderMemorySpace, ProviderScoreSemantics, ProviderSearchResult, ProviderSpaceStatus, type RememberRequest, SOURCES, type SearchRequest, type Source, defineMemorySpaceProvider, defineMemorySpaceProviderDefinition, firstArray, jsonArray, jsonNumber, jsonObject, jsonString, runProcess };
63
+ export { CATEGORIES, type Category, DEFAULT_MAX_OUTPUT_BYTES, EDGE_TYPES, type EdgeType, HttpMemoryProvider, HttpProviderOptions, INTENTS, type Insight, type Intent, JsonRequestOptions, type JsonValue, MEMORY_SPACE_PROVIDER_API_VERSION, type MemoryBody, type MemoryBodyProvider, type MemoryBodyStats, type MemoryGraphEdge, type MemoryGraphNode, type MemoryGraphSnapshot, type MemoryListRequest, MemoryProviderAdapter, MemoryProviderAdapterFactoryContext, type MemoryProviderCapabilities, type MemoryProviderConfigField, type MemoryProviderConfigOption, type MemoryProviderConnection, type MemoryProviderConnectionValue, type MemoryProviderDescriptor, type MemoryProviderIcon, type MemoryProviderId, MemoryProviderScoreSemantics, type MemorySpace, MemorySpaceAuthority, MemorySpaceNativeRunOptions, MemorySpaceNativeRunner, type MemorySpaceProvider, type MemorySpaceProviderDefinition, type MemorySpaceProviderDisposer, type MemorySpaceProviderEntry, type MemorySpaceProviderHost, type MemorySpaceProviderManifest, type MemorySpaceProviderModule, type MemorySpaceProviderRuntimeContext, type MemorySpaceProviderScoreSemantics, type MemorySpaceStats, NORMALIZED_RELEVANCE_SCORE, type OpenVikingBodyConnection, type OpenVikingSpaceConnection, ProcessError, ProcessFailureReason, ProcessOptions, ProcessResult, ProcessRunner, ProviderBodyStatus, ProviderEntityIndex, ProviderMemorySpace, ProviderScoreSemantics, ProviderSearchResult, ProviderSpaceStatus, type RememberRequest, SOURCES, type SearchRequest, type Source, defineMemorySpaceProvider, defineMemorySpaceProviderDefinition, firstArray, jsonArray, jsonNumber, jsonObject, jsonString, runProcess };
package/lib/testing.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { MemoryProviderConnection, MemoryProviderDescriptor, MemorySpace } from "./contracts.js";
2
- import { f as MemoryProviderAdapter, h as MemorySpaceAuthority, o as MemorySpaceProviderManifest, p as MemoryProviderAdapterFactoryContext, s as MemorySpaceProviderModule } from "./definitions-DwiEZSE_.js";
2
+ import { f as MemoryProviderAdapter, h as MemorySpaceAuthority, o as MemorySpaceProviderManifest, p as MemoryProviderAdapterFactoryContext, s as MemorySpaceProviderModule } from "./definitions-BcYzE092.js";
3
3
  //#region src/testing.d.ts
4
4
  interface MemorySpaceProviderTest {
5
5
  readonly registered: boolean;
@@ -1,4 +1,4 @@
1
- import type { CreateMemorySpaceRequest, EntityView, Insight, MemorySpace, MemorySpaceView, MemorySpaceMetadataMaintenanceResult, MemorySpaceCatalog, MemoryGraphSnapshot, MemoryListRequest, MemoryListView, MemoryReadSource, RememberRequest, SearchRequest, UpdateMemorySpaceRequest } from '../contracts.ts';
1
+ import type { CreateMemorySpaceRequest, EntityMemoriesView, EntityRelatedView, EntityView, Insight, MemorySpace, MemorySpaceView, MemorySpaceMetadataMaintenanceResult, MemorySpaceCatalog, MemoryGraphSnapshot, MemoryListRequest, MemoryListView, MemoryReadSource, RememberRequest, SearchRequest, UpdateMemorySpaceRequest } from '../contracts.ts';
2
2
  /**
3
3
  * Source-owned structural page API; the default bundle may supply agent-assisted callbacks.
4
4
  * body/bodies method and wire-field spellings remain compatible with existing
@@ -10,6 +10,10 @@ export interface MemorySpacesPageClient {
10
10
  graph(memoryBodyIds?: string[]): Promise<MemoryGraphSnapshot>;
11
11
  list(request?: MemoryListRequest): Promise<MemoryListView>;
12
12
  entities(entity?: string, limit?: number): Promise<EntityView>;
13
+ /** Optional for page clients written against an older Source; the Entities page then lists entities(entity) only. */
14
+ entityMemories?(entity: string, offset?: number, limit?: number): Promise<EntityMemoriesView>;
15
+ /** view: one id per page view; its next selection cancels the related read it is still waiting for. */
16
+ entityRelated?(entity: string, limit?: number, view?: string): Promise<EntityRelatedView>;
13
17
  search(request: SearchRequest): Promise<SearchResponse>;
14
18
  agentSearch(request: SearchRequest): Promise<AgentSearchResponse>;
15
19
  related(id: string, memoryBodyId?: string): Promise<Insight[]>;