dsh-mnemon-source-memory-spaces 0.5.15 → 0.5.17

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";
@@ -45,8 +45,11 @@ function modelStatus(status) {
45
45
  const unhealthy = active.filter((body) => !body.healthy);
46
46
  const relevantProviders = status.providerServices?.filter((provider) => provider.enabled || provider.configured || provider.memoryBodyCount > 0 || provider.status === "unhealthy") ?? [];
47
47
  const providers = relevantProviders.slice(0, 16);
48
+ const providerReady = status.commandFound === true || (status.providerServices ?? []).some((provider) => provider.enabled && provider.configured);
49
+ const notice = status.memoryBodies.length > 0 ? void 0 : providerReady ? "No Memory Space exists yet, so durable memory has nowhere to go and a full MEMORY.md archives to a local file. Create and activate a Memory Space to keep durable memory." : "No Memory Space exists, and none can be created until a memory Provider is ready, so durable memory has nowhere to go and a full MEMORY.md archives to a local file. Install the Mnemon CLI for Mnemon Native, or connect another Provider on the dsh-mnemon page under Plugins, then create and activate a Memory Space.";
48
50
  return {
49
51
  healthy: status.healthy && unhealthy.length === 0,
52
+ ...notice === void 0 ? {} : { notice },
50
53
  ...status.error === void 0 ? {} : { error: boundedToolText(status.error, 1e3) },
51
54
  ...status.version === void 0 ? {} : { version: boundedToolText(status.version, 120) },
52
55
  commandFound: status.commandFound,
@@ -999,6 +1002,88 @@ function lexicalRequiredMatchCount(tokens) {
999
1002
  return Math.max(2, Math.ceil(tokens.length / 4));
1000
1003
  }
1001
1004
  //#endregion
1005
+ //#region src/entity-index.ts
1006
+ /**
1007
+ * The most memories one space's derived index asks list() for. Mnemon Native
1008
+ * lists a whole store; a Provider that stops earlier reports what it listed.
1009
+ */
1010
+ const ENTITY_INDEX_LIST_LIMIT = 1e5;
1011
+ /** The rail lists at most this many entities; the rest stay reachable by name. */
1012
+ const ENTITY_RAIL_LIMIT = 5e3;
1013
+ function buildSpaceEntityIndex(memories, memoryCount, complete) {
1014
+ const kept = [];
1015
+ const byKey = /* @__PURE__ */ new Map();
1016
+ const spellings = /* @__PURE__ */ new Map();
1017
+ for (const memory of memories) {
1018
+ const seen = /* @__PURE__ */ new Set();
1019
+ let position;
1020
+ for (const raw of memory.entities ?? []) {
1021
+ if (typeof raw !== "string") continue;
1022
+ const key = normalizeEntityKey(raw);
1023
+ if (key === "" || seen.has(key)) continue;
1024
+ seen.add(key);
1025
+ if (position === void 0) {
1026
+ position = kept.length;
1027
+ kept.push(memory);
1028
+ }
1029
+ const positions = byKey.get(key);
1030
+ if (positions === void 0) byKey.set(key, [position]);
1031
+ else positions.push(position);
1032
+ const name = raw.normalize("NFKC").trim();
1033
+ const names = spellings.get(key);
1034
+ if (names === void 0) spellings.set(key, /* @__PURE__ */ new Map([[name, 1]]));
1035
+ else names.set(name, (names.get(name) ?? 0) + 1);
1036
+ }
1037
+ }
1038
+ return {
1039
+ memories: kept,
1040
+ byKey,
1041
+ spellings,
1042
+ memoryCount,
1043
+ complete
1044
+ };
1045
+ }
1046
+ /** The spelling most memories use; the first one seen wins a tie. */
1047
+ function preferredSpelling(names) {
1048
+ let preferred = "";
1049
+ let most = 0;
1050
+ for (const [name, count] of names) if (count > most) {
1051
+ preferred = name;
1052
+ most = count;
1053
+ }
1054
+ return preferred;
1055
+ }
1056
+ /** Merge per-space indexes into one entity list, most frequent first. */
1057
+ function mergeEntityCounts(indexes) {
1058
+ const counts = /* @__PURE__ */ new Map();
1059
+ const spellings = /* @__PURE__ */ new Map();
1060
+ for (const index of indexes) for (const [key, positions] of index.byKey) {
1061
+ counts.set(key, (counts.get(key) ?? 0) + positions.length);
1062
+ let totals = spellings.get(key);
1063
+ if (totals === void 0) spellings.set(key, totals = /* @__PURE__ */ new Map());
1064
+ for (const [name, count] of index.spellings.get(key) ?? []) totals.set(name, (totals.get(name) ?? 0) + count);
1065
+ }
1066
+ const names = /* @__PURE__ */ new Map();
1067
+ for (const [key, totals] of spellings) names.set(key, preferredSpelling(totals));
1068
+ return {
1069
+ items: [...counts].map(([key, count]) => ({
1070
+ entity: names.get(key),
1071
+ count
1072
+ })).sort((left, right) => right.count - left.count || left.entity.localeCompare(right.entity)),
1073
+ names
1074
+ };
1075
+ }
1076
+ /** Importance first, then the newest; the order Mnemon Native lists a store in. */
1077
+ function compareEntityMemories(left, right) {
1078
+ return (right.importance ?? 0) - (left.importance ?? 0) || (right.createdAt ?? "").localeCompare(left.createdAt ?? "") || (left.memoryBodyName ?? "").localeCompare(right.memoryBodyName ?? "") || left.id.localeCompare(right.id);
1079
+ }
1080
+ /** Every memory across the indexes that carries the entity key. */
1081
+ function memoriesWithEntity(indexes, key) {
1082
+ const memories = [];
1083
+ for (const index of indexes) for (const position of index.byKey.get(key) ?? []) memories.push(index.memories[position]);
1084
+ return memories.sort(compareEntityMemories);
1085
+ }
1086
+ //#endregion
1002
1087
  //#region src/recall-quality/policies.ts
1003
1088
  function expandedLimit(context) {
1004
1089
  return Math.min(50, Math.max(context.requestedLimit, Math.ceil(context.requestedLimit * context.config.candidateMultiplier)));
@@ -1208,6 +1293,39 @@ function text(value) {
1208
1293
  function number(value) {
1209
1294
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1210
1295
  }
1296
+ /**
1297
+ * How long a selection reuses the Provider health its index was checked with.
1298
+ * The rail always checks again; a write through this Source drops both at once.
1299
+ */
1300
+ const ENTITY_SELECTION_STATUS_REUSE_MS = 1e4;
1301
+ /** How long an index that statistics cannot check is reused; a write through this Source still drops it. */
1302
+ const ENTITY_INDEX_UNCHECKED_REUSE_MS = 1e4;
1303
+ function entityName(entity) {
1304
+ const name = entity.trim();
1305
+ if (name.length > 200) throw new Error("entity is too long (max 200 characters)");
1306
+ return name;
1307
+ }
1308
+ function memoryKey(memoryBodyId, id) {
1309
+ return `${memoryBodyId}\u0000${id}`;
1310
+ }
1311
+ /**
1312
+ * What must stay the same for a space's entity index to stay valid: its
1313
+ * Provider statistics (Mnemon counts every write in its operation log) and the
1314
+ * Source's own record of the space. Without statistics it cannot be checked.
1315
+ */
1316
+ function entityIndexFingerprint(body, status) {
1317
+ const stats = status.stats;
1318
+ if (stats === void 0) return void 0;
1319
+ return JSON.stringify([
1320
+ body.provider.id,
1321
+ body.updatedAt,
1322
+ stats.totalInsights,
1323
+ stats.deletedInsights,
1324
+ stats.edgeCount,
1325
+ stats.oplogCount,
1326
+ stats.dbSizeBytes
1327
+ ]);
1328
+ }
1211
1329
  function readSource(body, mode, status, itemCount, options = {}) {
1212
1330
  return {
1213
1331
  memoryBodyId: body.id,
@@ -1377,6 +1495,12 @@ var MemorySpacesService = class {
1377
1495
  providers;
1378
1496
  recallQualityPolicy;
1379
1497
  spacesInFlight;
1498
+ /** One entity index per active entity space, valid while its fingerprint holds. */
1499
+ entityIndexCache = /* @__PURE__ */ new Map();
1500
+ /** settledAt stays undefined while the read runs, which every caller shares. */
1501
+ entityStatusCache = /* @__PURE__ */ new Map();
1502
+ /** The related read each page view is waiting for; a newer selection cancels the older one. */
1503
+ entityRelatedViews = /* @__PURE__ */ new Map();
1380
1504
  providersDisposed = false;
1381
1505
  /** The CLI version a full status last read, and the binary it read it from. */
1382
1506
  cliVersion;
@@ -1512,7 +1636,8 @@ var MemorySpacesService = class {
1512
1636
  const all = this.memorySpaces.list();
1513
1637
  const serviceItems = this.memorySpaces.providerServices().items;
1514
1638
  const enabled = new Set(serviceItems.filter((service) => service.enabled).map((service) => service.providerId));
1515
- const active = all.filter((body) => body.active && (this.isNativeSpace(body) || enabled.has(body.provider.id))).sort((left, right) => left.id.localeCompare(right.id));
1639
+ const commandFound = this.runner.commandFound;
1640
+ const active = all.filter((body) => body.active && (this.isNativeSpace(body) ? commandFound : enabled.has(body.provider.id))).sort((left, right) => left.id.localeCompare(right.id));
1516
1641
  const spaces = [...all].sort((left, right) => left.id.localeCompare(right.id)).map((body) => ({
1517
1642
  id: body.id,
1518
1643
  name: body.name,
@@ -1527,13 +1652,16 @@ var MemorySpacesService = class {
1527
1652
  enabled: service.enabled,
1528
1653
  configured: service.configured
1529
1654
  })).sort((left, right) => left.providerId.localeCompare(right.providerId));
1655
+ const revision = createHash("sha256").update(JSON.stringify({
1656
+ bodies: spaces,
1657
+ services,
1658
+ ...commandFound ? {} : { nativeUnavailable: true }
1659
+ })).digest("hex");
1530
1660
  return {
1531
1661
  all,
1532
1662
  active,
1533
- revision: createHash("sha256").update(JSON.stringify({
1534
- bodies: spaces,
1535
- services
1536
- })).digest("hex")
1663
+ providerReady: this.memorySpaces.defaultProviderId() !== void 0,
1664
+ revision
1537
1665
  };
1538
1666
  }
1539
1667
  /** Return a usable system snapshot without waiting for any Provider I/O. */
@@ -1688,6 +1816,7 @@ var MemorySpacesService = class {
1688
1816
  }
1689
1817
  const provider = this.providerFor(body);
1690
1818
  provider.invalidateStatus?.(body.id);
1819
+ this.invalidateEntityIndex(body.id);
1691
1820
  const status = await provider.status(body, signal);
1692
1821
  return {
1693
1822
  ...body,
@@ -1696,7 +1825,8 @@ var MemorySpacesService = class {
1696
1825
  ...status
1697
1826
  };
1698
1827
  }
1699
- async search(request, signal) {
1828
+ /** exclude: memoryKey()s to leave out before the quality policy selects. */
1829
+ async search(request, signal, options = {}) {
1700
1830
  const query = required(request.query, "query", 2e3);
1701
1831
  const limit = memoryInputInteger(request.limit, this.config.defaultRecallLimit, 1, 50);
1702
1832
  const qualityContext = {
@@ -1719,7 +1849,8 @@ var MemorySpacesService = class {
1719
1849
  limit: preparedPolicy.candidateLimit,
1720
1850
  ...category === void 0 ? {} : { category },
1721
1851
  ...source === void 0 ? {} : { source },
1722
- ...intent === void 0 ? {} : { intent }
1852
+ ...intent === void 0 ? {} : { intent },
1853
+ ...request.inspect === true ? { inspect: true } : {}
1723
1854
  };
1724
1855
  let batches = await Promise.all(spaces.map(async (body) => {
1725
1856
  if (!body.provider.capabilities.search) return {
@@ -1755,7 +1886,8 @@ var MemorySpacesService = class {
1755
1886
  const hints = [];
1756
1887
  for (const [bodyOrder, { body, result }] of selectedBatches.entries()) {
1757
1888
  const scoreSemantics = this.providerFor(body).scoreSemantics;
1758
- candidates.push(...result.results.map((entry, index) => ({
1889
+ const entries = options.exclude === void 0 ? result.results : result.results.filter((entry) => !options.exclude.has(memoryKey(body.id, entry.id)));
1890
+ candidates.push(...entries.map((entry, index) => ({
1759
1891
  insight: this.annotate(entry, body),
1760
1892
  memoryBodyId: body.id,
1761
1893
  providerId: body.provider.id,
@@ -1782,7 +1914,8 @@ var MemorySpacesService = class {
1782
1914
  const recovered = await this.providerFor(batch.body).search(batch.body, {
1783
1915
  query: recoveryPlan.query,
1784
1916
  mode: "keyword",
1785
- limit: Math.min(limit, preparedPolicy.candidateLimit)
1917
+ limit: Math.min(limit, preparedPolicy.candidateLimit),
1918
+ ...request.inspect === true ? { inspect: true } : {}
1786
1919
  }, signal);
1787
1920
  const admitted = recovered.results.some((insight) => recoveryMatchCount(insight.content, recoveryPlan) >= recoveryPlan.requiredMatches);
1788
1921
  const results = mergeRecoveryResults(batch.result.results, recovered.results, recoveryPlan, preparedPolicy.candidateLimit);
@@ -2013,41 +2146,209 @@ var MemorySpacesService = class {
2013
2146
  sources: batches.map((batch) => batch.source)
2014
2147
  };
2015
2148
  }
2149
+ /**
2150
+ * The entities of the active spaces, each counted once per memory that
2151
+ * carries it. With an entity, also the first page of those memories.
2152
+ */
2016
2153
  async entities(entity, limit, signal) {
2017
- const active = (await this.spaces(signal)).items.filter((body) => body.active);
2018
- const capable = active.filter((body) => body.provider.capabilities.entities);
2019
- const entityCounts = /* @__PURE__ */ new Map();
2020
- for (const body of capable) for (const item of body.stats?.topEntities ?? []) entityCounts.set(item.entity, (entityCounts.get(item.entity) ?? 0) + item.count);
2021
- const items = [...entityCounts].map(([name, count]) => ({
2022
- entity: name,
2023
- count
2024
- })).sort((left, right) => right.count - left.count);
2025
- const sources = active.map((body) => {
2026
- if (!body.provider.capabilities.entities) return readSource(body, "unsupported", "unsupported", 0, { hint: "This provider does not expose an entity index." });
2027
- if (!body.healthy) return readSource(body, "entities", "unavailable", 0, { hint: body.error ?? "Provider unavailable." });
2028
- const count = body.stats?.topEntities.length ?? 0;
2029
- return readSource(body, "entities", count === 0 ? "empty" : "ready", count);
2030
- });
2031
- const selected = entity?.trim() ?? "";
2032
- if (selected === "") return {
2033
- items,
2154
+ const selected = entity === void 0 ? "" : entityName(entity);
2155
+ const read = await this.readEntityIndexes(signal, 0);
2156
+ const { items } = mergeEntityCounts(read.indexes);
2157
+ const view = {
2158
+ items: items.slice(0, ENTITY_RAIL_LIMIT),
2034
2159
  insights: [],
2035
- sources
2160
+ sources: read.sources,
2161
+ total: items.length,
2162
+ complete: read.complete
2036
2163
  };
2037
- if (selected.length > 200) throw new Error("entity is too long (max 200 characters)");
2038
- const readableIds = capable.filter((body) => body.healthy).map((body) => body.id);
2164
+ if (selected === "") return view;
2165
+ const page = this.entityPage(read, selected, 0, memoryInputInteger(limit, 20, 1, 50));
2039
2166
  return {
2040
- items,
2041
- selected,
2042
- insights: readableIds.length === 0 ? [] : (await this.search({
2167
+ ...view,
2168
+ selected: page.entity,
2169
+ insights: page.items
2170
+ };
2171
+ }
2172
+ /** One page of the memories that carry an entity, by importance and then recency. */
2173
+ async entityMemories(entity, offset, limit, signal) {
2174
+ const selected = entityName(entity);
2175
+ if (selected === "") throw new Error("entity is required");
2176
+ const read = await this.readEntityIndexes(signal, ENTITY_SELECTION_STATUS_REUSE_MS);
2177
+ return this.entityPage(read, selected, memoryInputInteger(offset, 0, 0, 1e6), memoryInputInteger(limit, 50, 1, 200));
2178
+ }
2179
+ /**
2180
+ * What recall relates to an entity, without the memories that carry it.
2181
+ * Those are left out before the quality policy selects, so they cannot use up
2182
+ * its places and leave the related list empty.
2183
+ */
2184
+ async entityRelated(entity, limit, signal, view) {
2185
+ const selected = entityName(entity);
2186
+ if (selected === "") throw new Error("entity is required");
2187
+ const superseding = view === void 0 ? void 0 : new AbortController();
2188
+ if (view !== void 0) {
2189
+ this.entityRelatedViews.get(view)?.abort(/* @__PURE__ */ new Error("superseded by a newer entity selection"));
2190
+ this.entityRelatedViews.set(view, superseding);
2191
+ }
2192
+ const combined = superseding === void 0 ? signal : signal === void 0 ? superseding.signal : AbortSignal.any([signal, superseding.signal]);
2193
+ try {
2194
+ const read = await this.readEntityIndexes(combined, ENTITY_SELECTION_STATUS_REUSE_MS);
2195
+ const key = normalizeEntityKey(selected);
2196
+ const carrying = /* @__PURE__ */ new Set();
2197
+ for (const [bodyId, index] of read.byBody) for (const position of index.byKey.get(key) ?? []) carrying.add(memoryKey(bodyId, index.memories[position].id));
2198
+ const display = mergeEntityCounts(read.indexes).names.get(key) ?? selected;
2199
+ const readableIds = read.readable.map((body) => body.id);
2200
+ if (readableIds.length === 0) return {
2201
+ entity: display,
2202
+ items: [],
2203
+ sources: []
2204
+ };
2205
+ const result = await this.search({
2043
2206
  query: selected,
2044
2207
  intent: "ENTITY",
2045
2208
  limit: memoryInputInteger(limit, 20, 1, 50),
2046
- memoryBodyIds: readableIds
2047
- }, signal)).results,
2048
- sources
2209
+ memoryBodyIds: readableIds,
2210
+ inspect: true
2211
+ }, combined, { exclude: carrying });
2212
+ combined?.throwIfAborted();
2213
+ return {
2214
+ entity: display,
2215
+ items: result.results,
2216
+ sources: result.sources
2217
+ };
2218
+ } finally {
2219
+ if (view !== void 0 && this.entityRelatedViews.get(view) === superseding) this.entityRelatedViews.delete(view);
2220
+ }
2221
+ }
2222
+ entityPage(read, selected, offset, limit) {
2223
+ const key = normalizeEntityKey(selected);
2224
+ const memories = memoriesWithEntity(read.indexes, key);
2225
+ return {
2226
+ entity: mergeEntityCounts(read.indexes).names.get(key) ?? selected,
2227
+ total: memories.length,
2228
+ offset,
2229
+ items: memories.slice(offset, offset + limit),
2230
+ complete: read.complete,
2231
+ sources: read.sources
2049
2232
  };
2050
2233
  }
2234
+ /**
2235
+ * One entity index per active space that has an entity index, read
2236
+ * concurrently. A space keeps its index while its Provider statistics and
2237
+ * metadata stay the same; a write through this Source drops it.
2238
+ */
2239
+ async readEntityIndexes(signal, statusMaxAgeMs) {
2240
+ signal?.throwIfAborted();
2241
+ const active = this.memorySpaces.active();
2242
+ const capable = new Set(active.filter((body) => body.provider.capabilities.entities).map((body) => body.id));
2243
+ for (const id of this.entityIndexCache.keys()) if (!capable.has(id)) this.entityIndexCache.delete(id);
2244
+ const reads = await Promise.all(active.map(async (body) => {
2245
+ if (!body.provider.capabilities.entities) return {
2246
+ body,
2247
+ recall: false,
2248
+ source: readSource(body, "unsupported", "unsupported", 0, { hint: "This provider does not expose an entity index." })
2249
+ };
2250
+ const status = await this.entitySpaceStatus(body, statusMaxAgeMs);
2251
+ if (!status.healthy) return {
2252
+ body,
2253
+ recall: false,
2254
+ source: readSource(body, "entities", "unavailable", 0, { hint: status.error ?? "Provider unavailable." })
2255
+ };
2256
+ const recall = body.provider.capabilities.search;
2257
+ if (this.providerFor(body).entityIndex === void 0 && !body.provider.capabilities.browse) return {
2258
+ body,
2259
+ recall,
2260
+ source: readSource(body, "query-only", "query-required", 0, { hint: "This provider can only be queried; its memories appear among related memories." })
2261
+ };
2262
+ try {
2263
+ const index = await this.entityIndexFor(body, status);
2264
+ const options = {
2265
+ memoryCount: index.memoryCount,
2266
+ complete: index.complete,
2267
+ ...index.complete ? {} : { hint: "The Provider indexed only part of this space." }
2268
+ };
2269
+ return {
2270
+ body,
2271
+ index,
2272
+ recall,
2273
+ source: readSource(body, "entities", index.byKey.size === 0 ? "empty" : "ready", index.byKey.size, options)
2274
+ };
2275
+ } catch (error) {
2276
+ return {
2277
+ body,
2278
+ recall: false,
2279
+ source: readSource(body, "entities", "unavailable", 0, { hint: error instanceof Error ? error.message : String(error) })
2280
+ };
2281
+ }
2282
+ }));
2283
+ signal?.throwIfAborted();
2284
+ const byBody = /* @__PURE__ */ new Map();
2285
+ for (const read of reads) if (read.index !== void 0) byBody.set(read.body.id, read.index);
2286
+ const indexes = [...byBody.values()];
2287
+ return {
2288
+ byBody,
2289
+ indexes,
2290
+ readable: reads.filter((read) => read.recall).map((read) => read.body),
2291
+ sources: reads.map((read) => read.source),
2292
+ complete: indexes.every((index) => index.complete)
2293
+ };
2294
+ }
2295
+ /** Provider health for the entity reads: a read in flight is shared, a finished one reused for less than maxAgeMs. */
2296
+ entitySpaceStatus(body, maxAgeMs) {
2297
+ const recent = this.entityStatusCache.get(body.id);
2298
+ if (recent !== void 0 && (recent.settledAt === void 0 || Date.now() - recent.settledAt < maxAgeMs)) return recent.status;
2299
+ const entry = {
2300
+ settledAt: void 0,
2301
+ status: Promise.resolve(void 0)
2302
+ };
2303
+ entry.status = this.providerFor(body).status(body).catch((error) => ({
2304
+ healthy: false,
2305
+ error: error instanceof Error ? error.message : String(error)
2306
+ })).then((status) => {
2307
+ entry.settledAt = Date.now();
2308
+ return status;
2309
+ });
2310
+ this.entityStatusCache.set(body.id, entry);
2311
+ return entry.status;
2312
+ }
2313
+ entityIndexFor(body, status) {
2314
+ const fingerprint = entityIndexFingerprint(body, status);
2315
+ const cached = this.entityIndexCache.get(body.id);
2316
+ if (cached !== void 0 && cached.fingerprint === fingerprint) {
2317
+ if (fingerprint !== void 0 || cached.settledAt === void 0 || Date.now() - cached.settledAt < ENTITY_INDEX_UNCHECKED_REUSE_MS) return cached.index;
2318
+ }
2319
+ const entry = {
2320
+ fingerprint,
2321
+ settledAt: void 0,
2322
+ index: Promise.resolve(void 0)
2323
+ };
2324
+ entry.index = this.buildEntityIndex(body).then((index) => {
2325
+ entry.settledAt = Date.now();
2326
+ return index;
2327
+ }, (error) => {
2328
+ if (this.entityIndexCache.get(body.id) === entry) this.entityIndexCache.delete(body.id);
2329
+ throw error;
2330
+ });
2331
+ this.entityIndexCache.set(body.id, entry);
2332
+ return entry.index;
2333
+ }
2334
+ async buildEntityIndex(body) {
2335
+ const provider = this.providerFor(body);
2336
+ if (provider.entityIndex !== void 0) {
2337
+ const owned = await provider.entityIndex(body);
2338
+ return buildSpaceEntityIndex(owned.memories.map((memory) => this.entityMemory(memory, body)), owned.memories.length, owned.complete);
2339
+ }
2340
+ const listed = await provider.list(body, { limit: ENTITY_INDEX_LIST_LIMIT });
2341
+ return buildSpaceEntityIndex(listed.map((memory) => this.entityMemory(memory, body)), listed.length, listed.length < ENTITY_INDEX_LIST_LIMIT);
2342
+ }
2343
+ /** A listed memory, not a query result: no relevance fields. */
2344
+ entityMemory(memory, body) {
2345
+ const { score: _score, normalizedScore: _normalized, relevanceTier: _tier, federatedScore: _federated, confidence: _confidence, intent: _intent, matchedVia: _matchedVia, depth: _depth, edgeType: _edgeType, ...listed } = memory;
2346
+ return this.annotate(listed, body);
2347
+ }
2348
+ invalidateEntityIndex(memoryBodyId) {
2349
+ this.entityIndexCache.delete(memoryBodyId);
2350
+ this.entityStatusCache.delete(memoryBodyId);
2351
+ }
2051
2352
  async remember(request, signal) {
2052
2353
  this.assertWritable();
2053
2354
  const prepared = this.prepareRemember(request);
@@ -2103,6 +2404,24 @@ var MemorySpacesService = class {
2103
2404
  }
2104
2405
  return results;
2105
2406
  }
2407
+ /**
2408
+ * The memories held under an exact id among the given spaces that can be
2409
+ * read now, asked of the Providers that can look ids up.
2410
+ */
2411
+ async locate(id, memoryBodyIds, signal) {
2412
+ const exact = required(id, "id", 2e3);
2413
+ const readable = new Map(this.memorySpaces.active().filter((body) => this.isNativeSpace(body) || this.memorySpaces.providerServiceEnabled(body.provider.id)).map((body) => [body.id, body]));
2414
+ const found = [];
2415
+ for (const memoryBodyId of new Set(memoryBodyIds)) {
2416
+ const body = readable.get(memoryBodyId);
2417
+ if (body === void 0) continue;
2418
+ const provider = this.providerFor(body);
2419
+ if (provider.get === void 0) continue;
2420
+ const insight = await provider.get(body, exact, signal);
2421
+ if (insight !== void 0 && insight.id === exact) found.push(this.annotate(insight, body));
2422
+ }
2423
+ return found;
2424
+ }
2106
2425
  async related(id, depth = 2, edge, signal, memoryBodyId) {
2107
2426
  const body = this.readSpace(memoryBodyId);
2108
2427
  const selectedEdge = allowed(edge, EDGE_TYPES, "edge");
@@ -2378,6 +2697,7 @@ var MemorySpacesService = class {
2378
2697
  };
2379
2698
  }
2380
2699
  activateAfterWrite(body, providerChanged) {
2700
+ this.invalidateEntityIndex(body.id);
2381
2701
  if (!providerChanged) return;
2382
2702
  if (!body.active) this.memorySpaces.setActive(body.id, true);
2383
2703
  else this.memorySpaces.touch(body.id);
@@ -2408,7 +2728,9 @@ const EMBEDDING_ENVIRONMENT_KEYS = /* @__PURE__ */ new Set([
2408
2728
  function processFailureMessage(error, args) {
2409
2729
  const detail = error instanceof Error ? error.message : String(error);
2410
2730
  const reason = error instanceof ProcessError ? error.reason : "launch";
2411
- if (reason === "output-limit") return `mnemon ${args.find((arg) => !arg.startsWith("-")) ?? "command"} stopped: ${detail.replace(/^mnemon /u, "")}`;
2731
+ const command = args.find((arg) => !arg.startsWith("-"));
2732
+ if (reason === "output-limit") return `mnemon ${command ?? "command"} stopped: ${detail.replace(/^mnemon /u, "")}`;
2733
+ if (reason === "timeout" && command !== void 0) return detail.replace(/^mnemon /u, `mnemon ${command} `);
2412
2734
  if (reason !== "launch") return detail;
2413
2735
  return `${detail}. ${process.platform === "win32" ? "Install the official Mnemon Windows release, ensure mnemon.exe is on PATH or under %LOCALAPPDATA%\\Programs\\mnemon, or set MNEMON_CLI_PATH or mnemon.cliPath to its absolute path." : "Install Mnemon and ensure \"mnemon\" is on PATH, or set MNEMON_CLI_PATH or mnemon.cliPath."}`;
2414
2736
  }
@@ -2774,6 +3096,8 @@ async function manageMemorySpaces(service, request) {
2774
3096
  ...memoryInputStringArray(input.memoryBodyIds, "memoryBodyIds", 1e4) === void 0 ? {} : { memoryBodyIds: memoryInputStringArray(input.memoryBodyIds, "memoryBodyIds", 1e4) }
2775
3097
  }, request.signal));
2776
3098
  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));
3099
+ 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));
3100
+ 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)));
2777
3101
  case "search": return managementResult(service, await service.search({
2778
3102
  query: memoryInputText(input.query, "query", 2e3),
2779
3103
  ...memoryInputText(input.mode, "mode", 20, false) === void 0 ? {} : { mode: memoryInputText(input.mode, "mode", 20, false) },
@@ -2781,7 +3105,8 @@ async function manageMemorySpaces(service, request) {
2781
3105
  ...memoryInputText(input.category, "category", 30, false) === void 0 ? {} : { category: memoryInputText(input.category, "category", 30, false) },
2782
3106
  ...memoryInputText(input.source, "source", 30, false) === void 0 ? {} : { source: memoryInputText(input.source, "source", 30, false) },
2783
3107
  ...memoryInputText(input.intent, "intent", 30, false) === void 0 ? {} : { intent: memoryInputText(input.intent, "intent", 30, false) },
2784
- ...memoryInputStringArray(input.memoryBodyIds, "memoryBodyIds", 1e4) === void 0 ? {} : { memoryBodyIds: memoryInputStringArray(input.memoryBodyIds, "memoryBodyIds", 1e4) }
3108
+ ...memoryInputStringArray(input.memoryBodyIds, "memoryBodyIds", 1e4) === void 0 ? {} : { memoryBodyIds: memoryInputStringArray(input.memoryBodyIds, "memoryBodyIds", 1e4) },
3109
+ ...input.inspect === true ? { inspect: true } : {}
2785
3110
  }, request.signal));
2786
3111
  case "related": return managementResult(service, await service.related(memoryInputText(input.id, "id", 2e3), memoryInputInteger(input.depth, 2, 1, 5), memoryInputText(input.edge, "edge", 30, false), request.signal, memoryInputText(input.memoryBodyId, "memoryBodyId", 300, false)));
2787
3112
  case "body-reconnect": return managementResult(service, await service.reconnectSpace(memoryInputText(input.memoryBodyId, "memoryBodyId", 300), request.signal));
@@ -3059,6 +3384,34 @@ function createMemorySpacesSource(providerSnapshot, config = {}) {
3059
3384
  admittedByView.set(viewId, current);
3060
3385
  while (admittedByView.size > 128) admittedByView.delete(admittedByView.keys().next().value);
3061
3386
  };
3387
+ /**
3388
+ * The Memory Space an exact id belongs to, for an action or traversal that
3389
+ * names a memory by id. Evidence this View returned decides first, as
3390
+ * before. Otherwise the Providers that can look an id up are asked among
3391
+ * the spaces in `scope`, and a found memory is admitted to this View. An
3392
+ * Agent can so forget what it just wrote, and a delegated worker can act on
3393
+ * an id from its parent's turn (#337). A Provider that cannot look ids up
3394
+ * keeps to evidence: its own call may reach beyond the space named.
3395
+ */
3396
+ const ownerOf = async (viewId, id, requestedSpace, scope, signal) => {
3397
+ const entries = admittedByView.get(viewId);
3398
+ if (requestedSpace !== void 0) {
3399
+ if (entries?.has(requestedSpace + "/" + id) === true) return requestedSpace;
3400
+ } else {
3401
+ const owners = [...entries?.entries() ?? []].filter(([reference]) => reference.endsWith("/" + id)).map(([, owner]) => owner);
3402
+ if (owners.length > 0) return owners.length === 1 ? owners[0] : void 0;
3403
+ }
3404
+ const candidates = requestedSpace === void 0 ? scope : scope.includes(requestedSpace) ? [requestedSpace] : [];
3405
+ if (candidates.length === 0) return void 0;
3406
+ const found = await service.locate(id, candidates, signal);
3407
+ const owners = [...new Set(found.flatMap((item) => item.memoryBodyId === void 0 ? [] : [item.memoryBodyId]))];
3408
+ if (owners.length !== 1) return void 0;
3409
+ admit(viewId, [{
3410
+ id,
3411
+ memoryBodyId: owners[0]
3412
+ }]);
3413
+ return owners[0];
3414
+ };
3062
3415
  const evidence = (request, items, unavailable) => {
3063
3416
  const catalog = request.route.sourceRouteId === "inspect";
3064
3417
  let remaining = request.route.maxCharacters ?? 16e3;
@@ -3116,15 +3469,15 @@ function createMemorySpacesSource(providerSnapshot, config = {}) {
3116
3469
  };
3117
3470
  return {
3118
3471
  facts(request) {
3119
- const { active, revision } = sourceState(request.scope);
3472
+ const { active, providerReady, revision } = sourceState(request.scope);
3120
3473
  const routeIds = [
3121
3474
  "inspect",
3122
3475
  ...active.some((body) => body.provider.capabilities.search) ? ["recall"] : [],
3123
3476
  ...active.some((body) => body.provider.capabilities.related) ? ["related"] : []
3124
3477
  ];
3125
3478
  const actionIds = service.config.writeEnabled ? [
3126
- "manage-spaces",
3127
- ...canRemember ? ["remember"] : [],
3479
+ ...providerReady ? ["manage-spaces"] : [],
3480
+ ...canRemember && providerReady ? ["remember"] : [],
3128
3481
  ...active.some((body) => body.provider.capabilities.link) ? ["link"] : [],
3129
3482
  ...active.some((body) => body.provider.capabilities.forget) ? ["forget"] : []
3130
3483
  ] : [];
@@ -3145,7 +3498,8 @@ function createMemorySpacesSource(providerSnapshot, config = {}) {
3145
3498
  actionIds,
3146
3499
  hints: {
3147
3500
  activeCount: active.length,
3148
- providerCount: new Set(active.map((body) => body.provider.id)).size
3501
+ providerCount: new Set(active.map((body) => body.provider.id)).size,
3502
+ archivableCount: active.filter((body) => body.provider.capabilities.remember && body.provider.capabilities.forget && body.provider.capabilities.writeMode === "exact").length
3149
3503
  }
3150
3504
  };
3151
3505
  },
@@ -3229,10 +3583,8 @@ function createMemorySpacesSource(providerSnapshot, config = {}) {
3229
3583
  if (request.route.sourceRouteId === "related") {
3230
3584
  const id = memoryInputText(input.id, "id", 2e3);
3231
3585
  const requestedSpace = memoryInputText(input.memoryBodyId, "memoryBodyId", 300, false);
3232
- const admitted = admittedByView.get(request.view.id);
3233
- const owners = [...admitted?.entries() ?? []].filter(([reference]) => reference.endsWith("/" + id)).map(([, bodyId]) => bodyId);
3234
- const owner = requestedSpace === void 0 ? owners.length === 1 ? owners[0] : void 0 : admitted?.get(requestedSpace + "/" + id);
3235
- if (owner === void 0) throw new Error("related-memory traversal requires evidence already admitted by this View");
3586
+ const owner = await ownerOf(request.view.id, id, requestedSpace, allowedBodies, request.signal);
3587
+ if (owner === void 0) throw new Error("related-memory traversal requires evidence already admitted by this View, or an id found in a Memory Space it can read");
3236
3588
  if (!allowedBodies.includes(owner)) throw new Error("related-memory owner is outside this View ReadGrant");
3237
3589
  const edge = memoryInputText(input.edge, "edge", 30, false);
3238
3590
  if (edge !== void 0 && !EDGES.has(edge)) throw new Error(`unsupported edge: ${edge}`);
@@ -3249,13 +3601,7 @@ function createMemorySpacesSource(providerSnapshot, config = {}) {
3249
3601
  const allowedBodies = grantIds(grant);
3250
3602
  const created = createdByView.get(request.view.id) ?? /* @__PURE__ */ new Set();
3251
3603
  const writableBodies = writeBodies(request.view.id, grant);
3252
- const admittedOwner = (id) => {
3253
- const requestedSpace = memoryInputText(input.memoryBodyId, "memoryBodyId", 300, false);
3254
- const entries = admittedByView.get(request.view.id);
3255
- if (requestedSpace !== void 0) return entries?.get(requestedSpace + "/" + id);
3256
- const owners = [...entries?.entries() ?? []].filter(([reference]) => reference.endsWith("/" + id)).map(([, owner]) => owner);
3257
- return owners.length === 1 ? owners[0] : void 0;
3258
- };
3604
+ const admittedOwner = (id) => ownerOf(request.view.id, id, memoryInputText(input.memoryBodyId, "memoryBodyId", 300, false), allowedBodies, request.signal);
3259
3605
  let result;
3260
3606
  let bodyId;
3261
3607
  if (request.offer.sourceActionId === "manage-spaces") {
@@ -3322,9 +3668,9 @@ function createMemorySpacesSource(providerSnapshot, config = {}) {
3322
3668
  } else if (request.offer.sourceActionId === "link") {
3323
3669
  const sourceId = memoryInputText(input.sourceId, "sourceId", 2e3);
3324
3670
  const targetId = memoryInputText(input.targetId, "targetId", 2e3);
3325
- const sourceSpace = admittedOwner(sourceId);
3326
- const targetSpace = admittedOwner(targetId);
3327
- if (sourceSpace === void 0 || targetSpace === void 0 || sourceSpace !== targetSpace || !allowedBodies.includes(sourceSpace)) throw new Error("link requires two evidence items admitted by this View from the same Memory Space");
3671
+ const sourceSpace = await admittedOwner(sourceId);
3672
+ const targetSpace = await admittedOwner(targetId);
3673
+ if (sourceSpace === void 0 || targetSpace === void 0 || sourceSpace !== targetSpace || !allowedBodies.includes(sourceSpace)) throw new Error("link requires two evidence items admitted by this View from the same Memory Space, or two ids found in one Memory Space it can read");
3328
3674
  bodyId = sourceSpace;
3329
3675
  const edge = memoryInputText(input.type, "type", 30, false);
3330
3676
  if (edge !== void 0 && !EDGES.has(edge)) throw new Error(`unsupported edge: ${edge}`);
@@ -3332,8 +3678,8 @@ function createMemorySpacesSource(providerSnapshot, config = {}) {
3332
3678
  result = await service.link(sourceId, targetId, edge, weight, memoryInputText(input.reason, "reason", 1e3, false), request.signal, bodyId);
3333
3679
  } else if (request.offer.sourceActionId === "forget") {
3334
3680
  const id = memoryInputText(input.id, "id", 2e3);
3335
- bodyId = admittedOwner(id);
3336
- if (bodyId === void 0 || !allowedBodies.includes(bodyId)) throw new Error("forget requires evidence already admitted by this View");
3681
+ bodyId = await admittedOwner(id);
3682
+ if (bodyId === void 0 || !allowedBodies.includes(bodyId)) throw new Error("forget requires evidence already admitted by this View, or an id found in one Memory Space it can read; name the space (memoryBodyId) when several hold it");
3337
3683
  result = await service.forget(id, request.signal, bodyId);
3338
3684
  } else throw new Error(`unsupported Memory Spaces action: ${request.offer.sourceActionId}`);
3339
3685
  return createMemoryMutationReceipt(request.view.id, request.offer.id, context.sourceInstanceKey, service.memoryRevision(), {