@remnic/core 9.46.1 → 9.46.2

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.
Files changed (116) hide show
  1. package/dist/access-admin-ops-surface.d.ts +2 -2
  2. package/dist/access-admin-ops-surface.js +3 -2
  3. package/dist/access-authorization-probe.d.ts +2 -2
  4. package/dist/access-authorization-probe.js +4 -3
  5. package/dist/access-boundary.d.ts +2 -2
  6. package/dist/access-boundary.js +3 -2
  7. package/dist/access-cli.js +16 -16
  8. package/dist/access-extraction-force-flush.d.ts +2 -2
  9. package/dist/access-extraction-force-flush.js +3 -2
  10. package/dist/access-http-lcm-compaction.d.ts +2 -2
  11. package/dist/access-http-lcm-compaction.js +1 -1
  12. package/dist/access-http-lifecycle-flush.d.ts +2 -2
  13. package/dist/access-http-lifecycle-flush.js +2 -2
  14. package/dist/access-http-offline-stream.d.ts +2 -2
  15. package/dist/access-http-query.js +3 -2
  16. package/dist/access-http.d.ts +8 -2
  17. package/dist/access-http.js +11 -10
  18. package/dist/access-identity-continuity-surface.d.ts +1 -1
  19. package/dist/access-identity-continuity-surface.js +3 -2
  20. package/dist/access-lcm-surface.d.ts +2 -2
  21. package/dist/access-lcm-surface.js +3 -2
  22. package/dist/access-mcp.d.ts +2 -2
  23. package/dist/access-mcp.js +7 -6
  24. package/dist/access-memory-search-fanout.d.ts +163 -2
  25. package/dist/access-memory-search-fanout.js +20 -3
  26. package/dist/access-namespace-preflight.js +3 -2
  27. package/dist/access-observe-write-surface.d.ts +2 -2
  28. package/dist/access-observe-write-surface.js +3 -2
  29. package/dist/access-offline-manifest.d.ts +1 -1
  30. package/dist/access-operations-batch.js +4 -3
  31. package/dist/access-operations.d.ts +6 -5
  32. package/dist/access-operations.js +6 -5
  33. package/dist/access-recall-concurrency.d.ts +2 -2
  34. package/dist/access-recall-concurrency.js +3 -2
  35. package/dist/access-recall-response.d.ts +2 -2
  36. package/dist/access-recall-response.js +3 -2
  37. package/dist/access-recall-surface.d.ts +2 -2
  38. package/dist/access-recall-surface.js +3 -2
  39. package/dist/access-schema.d.ts +84 -84
  40. package/dist/{access-service-loJadYd7.d.ts → access-service-DNBYHK33.d.ts} +2 -1
  41. package/dist/access-service-helpers.d.ts +2 -2
  42. package/dist/access-service.d.ts +2 -2
  43. package/dist/access-service.js +3 -2
  44. package/dist/access-surface-catalog.d.ts +2 -2
  45. package/dist/access-surface-catalog.js +1 -0
  46. package/dist/access-surface-catalog.js.map +1 -1
  47. package/dist/active-recall.js +2 -2
  48. package/dist/bootstrap.d.ts +1 -1
  49. package/dist/{chunk-WHWDBTCJ.js → chunk-74HN5QUH.js} +2 -2
  50. package/dist/{chunk-2DSUT7DZ.js → chunk-FQU3HTQM.js} +40 -11
  51. package/dist/chunk-FQU3HTQM.js.map +1 -0
  52. package/dist/{chunk-5LRDM4R7.js → chunk-G2L5IZV4.js} +7 -7
  53. package/dist/{chunk-TQZ4DVBC.js → chunk-GZ3R6XOE.js} +5 -2
  54. package/dist/chunk-GZ3R6XOE.js.map +1 -0
  55. package/dist/{chunk-5546W5SI.js → chunk-HLKPRWF4.js} +2 -2
  56. package/dist/chunk-IXEBUYO4.js +201 -0
  57. package/dist/chunk-IXEBUYO4.js.map +1 -0
  58. package/dist/{chunk-RCU25Z66.js → chunk-IXH2U2QT.js} +169 -58
  59. package/dist/chunk-IXH2U2QT.js.map +1 -0
  60. package/dist/{chunk-HJ3SVHUW.js → chunk-IZQ5TDFT.js} +9 -4
  61. package/dist/chunk-IZQ5TDFT.js.map +1 -0
  62. package/dist/{chunk-RVPTS4U5.js → chunk-JCNJIXZN.js} +2 -2
  63. package/dist/{chunk-5R6S4DJZ.js → chunk-N5XULCU3.js} +2 -2
  64. package/dist/{chunk-6H744ZBH.js → chunk-QISYTKG4.js} +2 -2
  65. package/dist/{chunk-AUMWZSE7.js → chunk-RN53QBM4.js} +4 -4
  66. package/dist/{chunk-M32URQXV.js → chunk-SIP5DBDN.js} +275 -334
  67. package/dist/chunk-SIP5DBDN.js.map +1 -0
  68. package/dist/{chunk-44N6ESJ4.js → chunk-TIDEL77O.js} +2 -2
  69. package/dist/{chunk-XBMJ35IP.js → chunk-VFZS2HAA.js} +4 -3
  70. package/dist/chunk-VFZS2HAA.js.map +1 -0
  71. package/dist/{cli-D_RcqXfZ.d.ts → cli-BWXSPxB9.d.ts} +2 -2
  72. package/dist/cli.d.ts +3 -3
  73. package/dist/cli.js +15 -15
  74. package/dist/config.js +2 -2
  75. package/dist/explicit-capture.d.ts +1 -1
  76. package/dist/external-wiki-access.d.ts +2 -2
  77. package/dist/external-wiki-access.js +4 -3
  78. package/dist/external-wiki-mcp-tools.d.ts +2 -2
  79. package/dist/index.d.ts +680 -680
  80. package/dist/index.js +16 -16
  81. package/dist/mcp-memory-inspector-app.d.ts +2 -2
  82. package/dist/operator-toolkit.js +3 -3
  83. package/dist/{orchestrator-LPGegii3.d.ts → orchestrator-CRR-6sVB.d.ts} +6 -0
  84. package/dist/orchestrator.d.ts +1 -1
  85. package/dist/orchestrator.js +17 -17
  86. package/dist/resume-bundles.js +3 -3
  87. package/dist/schemas.d.ts +76 -76
  88. package/dist/shared-context/manager.d.ts +8 -8
  89. package/dist/transfer/types.d.ts +66 -66
  90. package/package.json +2 -2
  91. package/src/access-http-lcm-compaction.ts +3 -0
  92. package/src/access-http.test.ts +645 -0
  93. package/src/access-http.ts +55 -4
  94. package/src/access-mcp.ts +1 -0
  95. package/src/access-memory-search-fanout.ts +305 -2
  96. package/src/access-operations.ts +6 -0
  97. package/src/access-service-namespace.test.ts +146 -0
  98. package/src/access-service.ts +39 -40
  99. package/src/access-surface-catalog.ts +1 -0
  100. package/src/orchestration/generic-recall-paths.ts +116 -5
  101. package/dist/chunk-2DSUT7DZ.js.map +0 -1
  102. package/dist/chunk-7HXCF4RE.js +0 -72
  103. package/dist/chunk-7HXCF4RE.js.map +0 -1
  104. package/dist/chunk-HJ3SVHUW.js.map +0 -1
  105. package/dist/chunk-M32URQXV.js.map +0 -1
  106. package/dist/chunk-RCU25Z66.js.map +0 -1
  107. package/dist/chunk-TQZ4DVBC.js.map +0 -1
  108. package/dist/chunk-XBMJ35IP.js.map +0 -1
  109. /package/dist/{chunk-WHWDBTCJ.js.map → chunk-74HN5QUH.js.map} +0 -0
  110. /package/dist/{chunk-5LRDM4R7.js.map → chunk-G2L5IZV4.js.map} +0 -0
  111. /package/dist/{chunk-5546W5SI.js.map → chunk-HLKPRWF4.js.map} +0 -0
  112. /package/dist/{chunk-RVPTS4U5.js.map → chunk-JCNJIXZN.js.map} +0 -0
  113. /package/dist/{chunk-5R6S4DJZ.js.map → chunk-N5XULCU3.js.map} +0 -0
  114. /package/dist/{chunk-6H744ZBH.js.map → chunk-QISYTKG4.js.map} +0 -0
  115. /package/dist/{chunk-AUMWZSE7.js.map → chunk-RN53QBM4.js.map} +0 -0
  116. /package/dist/{chunk-44N6ESJ4.js.map → chunk-TIDEL77O.js.map} +0 -0
@@ -107,8 +107,9 @@ import {
107
107
  defaultNamespaceAtFlatRoot,
108
108
  mergeMemorySearchDefaultFallback,
109
109
  resolveMemorySearchDefaultFallback,
110
- runMemorySearchFanout,
110
+ memorySearchThroughScope,
111
111
  } from "./access-memory-search-fanout.js";
112
+ import { isSearchExcludedPath } from "./orchestration/generic-recall-paths.js";
112
113
  import {
113
114
  buildQualityScore,
114
115
  buildProposedActions,
@@ -4711,47 +4712,45 @@ export class EngramAccessService {
4711
4712
  namespace?: string;
4712
4713
  maxResults?: number;
4713
4714
  collection?: string;
4715
+ mode?: "search" | "hybrid" | "bm25" | "vector";
4714
4716
  principal?: string;
4715
4717
  }): Promise<{ query: string; results: Array<{ path: string; score: number; snippet: string }>; count: number }> {
4716
- const { query, namespace, maxResults, principal } = request;
4717
- const collection = request.collection?.trim();
4718
- if (request.collection !== undefined && !collection) {
4719
- throw new EngramAccessInputError("collection must be a non-empty string");
4720
- }
4721
-
4722
- let results: Array<{ path: string; score: number; snippet?: string }>;
4723
- if (!resolveNamespaceCapabilities(this.orchestrator.config).namespaces) {
4724
- this.resolveReadableNamespace(namespace, principal);
4725
- results = collection === "global"
4726
- ? await this.orchestrator.qmd.searchGlobal(query, maxResults)
4727
- : await this.orchestrator.qmd.search(query, collection, maxResults);
4728
- } else {
4729
- const readableNamespaces = await this.resolveReadableNamespacesForSearch(namespace, principal);
4730
- const namespaces = this.resolveMemorySearchNamespacesForCollection(
4731
- collection,
4732
- readableNamespaces,
4733
- namespace?.trim() ? undefined : principal,
4734
- );
4735
- results = await runMemorySearchFanout({
4736
- query,
4737
- namespaces,
4738
- maxResults,
4739
- principal,
4740
- requestedNamespace: namespace,
4741
- collection,
4742
- search: (params) => this.orchestrator.searchAcrossNamespaces(params),
4743
- });
4744
- }
4745
-
4746
- return {
4747
- query,
4748
- results: results.map((r) => ({
4749
- path: r.path,
4750
- score: r.score,
4751
- snippet: (r.snippet ?? "").slice(0, 800),
4752
- })),
4753
- count: results.length,
4754
- };
4718
+ const { qmd, config } = this.orchestrator;
4719
+ return memorySearchThroughScope(
4720
+ {
4721
+ namespacesEnabled: resolveNamespaceCapabilities(config).namespaces,
4722
+ // `0` is preserved, never coerced: a zero limit is a runtime
4723
+ // compatibility guarantee (AGENTS.md guardrail 4), and it means the
4724
+ // same thing here as an explicit `maxResults: 0` from the caller - an
4725
+ // empty result with no backend call. Anything non-numeric falls back
4726
+ // to the documented default.
4727
+ defaultBudget: typeof config.qmdMaxResults === "number" ? config.qmdMaxResults : 10,
4728
+ // Search-specific: artifacts and the other dedicated surfaces stay
4729
+ // out, but ARCHIVED memories remain findable - explicit search is one
4730
+ // of the surfaces the lifecycle reserves them for.
4731
+ isExcluded: (memoryPath) =>
4732
+ isSearchExcludedPath(
4733
+ memoryPath,
4734
+ // The caller's own collection disambiguates a prefix that is also
4735
+ // a memory category name (`collection: "facts"`).
4736
+ { ...config, requestedCollection: request.collection?.trim() || undefined },
4737
+ "qmd",
4738
+ ),
4739
+ authorizeFlatCorpus: (namespace, principal) => {
4740
+ this.resolveReadableNamespace(namespace, principal);
4741
+ },
4742
+ authorizeNamespaces: async (namespace, principal, collection) =>
4743
+ this.resolveMemorySearchNamespacesForCollection(
4744
+ collection,
4745
+ await this.resolveReadableNamespacesForSearch(namespace, principal),
4746
+ namespace?.trim() ? undefined : principal,
4747
+ ),
4748
+ searchAcrossNamespaces: (params) => this.orchestrator.searchAcrossNamespaces(params),
4749
+ searchGlobal: (query, maxResults) => qmd.searchGlobal(query, maxResults),
4750
+ search: (query, collection, maxResults) => qmd.search(query, collection, maxResults),
4751
+ },
4752
+ request,
4753
+ );
4755
4754
  }
4756
4755
 
4757
4756
  async memoryProfile(namespace?: string, principal?: string): Promise<Record<string, unknown>> {
@@ -177,6 +177,7 @@ export const HTTP_ROUTES: readonly HttpRouteEntry[] = [
177
177
  { method: "GET", pathname: "/engram/v1/adapters", operation: "adapters_status" },
178
178
  { method: "POST", pathname: "/engram/v1/recall", operation: "recall" },
179
179
  { method: "POST", pathname: "/engram/v1/external-wikis/search", operation: "external_wiki_search" },
180
+ { method: "POST", pathname: "/engram/v1/memories/search", operation: "memory_search" },
180
181
  { method: "POST", pathname: "/engram/v1/coding-context", operation: "set_coding_context" },
181
182
  { method: "POST", pathname: "/engram/v1/capsules/export", operation: "capsule_export" },
182
183
  { method: "POST", pathname: "/engram/v1/capsules/import", operation: "capsule_import" },
@@ -13,6 +13,12 @@ export interface GenericRecallPathPolicy {
13
13
  readonly memoryDir?: string;
14
14
  readonly qmdCollection?: string;
15
15
  readonly qmdColdCollection?: string;
16
+ /**
17
+ * The collection THIS request named, when it named one. A caller may select
18
+ * a custom collection whose name is also a memory category, which the
19
+ * configured names alone cannot disambiguate.
20
+ */
21
+ readonly requestedCollection?: string;
16
22
  }
17
23
 
18
24
  type GenericRecallPathSource = "filesystem" | "qmd";
@@ -94,17 +100,122 @@ function isExternalWikiQmdPath(
94
100
  return isExternalWikiCollectionName(collection);
95
101
  }
96
102
 
103
+ /**
104
+ * Paths an EXPLICIT ranked search must not return.
105
+ *
106
+ * Narrower than {@link isGenericRecallExcludedPath} on purpose. Recall
107
+ * injection also drops archived memories because they are cold by definition,
108
+ * but archive is explicitly reserved for "explicit read or search surfaces"
109
+ * (docs/architecture/memory-lifecycle.md) — hiding it from `memory_search`
110
+ * would remove the only way to find it. Everything else stays excluded: those
111
+ * paths flow through their own dedicated surfaces, never a generic search.
112
+ */
113
+ export function isSearchExcludedPath(
114
+ filePath: string,
115
+ policy: GenericRecallPathPolicy = {},
116
+ source: GenericRecallPathSource = "filesystem",
117
+ ): boolean {
118
+ if (isExternalWikiQmdPath(filePath, source)) return true;
119
+ // A QMD transport hands back collection-qualified paths
120
+ // (`qmd://<collection>/activity/<date>.md`, or `<collection>/activity/…`),
121
+ // and the collection is not always one this policy knows: a caller may name
122
+ // a custom one, and global search spans every collection. The activity
123
+ // predicate is ROOT-AWARE — it matches only the top-level
124
+ // `activity/<date>.md` so a nested ordinary memory stays recallable — so an
125
+ // un-stripped prefix reads as a nested path and the digest would be served.
126
+ //
127
+ // Both spellings are therefore tested. Widening is safe in this direction
128
+ // and only here: a leading segment is dropped ONLY to re-test the dedicated
129
+ // surface shapes, never to decide anything else, and a segment that names a
130
+ // memory CATEGORY (`facts/proj/activity/…`) is never treated as a
131
+ // collection, so ordinary nested memories stay searchable.
132
+ for (const candidate of collectionSpellings(filePath, policy, source)) {
133
+ if (
134
+ isArtifactMemoryPath(candidate) ||
135
+ isNamespacedActivityDigestPath(candidate, policy.memoryDir) ||
136
+ isMeetingRecordPath(candidate)
137
+ ) {
138
+ return true;
139
+ }
140
+ }
141
+ return false;
142
+ }
143
+
144
+ /**
145
+ * The activity check, applied against the NAMESPACE root a hit came from.
146
+ *
147
+ * `isActivityDigestPath` is root-aware — the digest must sit directly under
148
+ * the root it is given — but a namespaced search rewrites hits to absolute
149
+ * paths beneath `…/namespaces/<ns>/`, and only the global `memoryDir` is
150
+ * available here. Measured against that root, a real digest at
151
+ * `<memoryDir>/namespaces/team/activity/<date>.md` reads as nested and is
152
+ * served. Re-measuring against the namespace's own root closes that.
153
+ */
154
+ function isNamespacedActivityDigestPath(candidate: string, memoryDir?: string): boolean {
155
+ if (isActivityDigestPath(candidate, memoryDir)) return true;
156
+ if (memoryDir === undefined || memoryDir.length === 0) return false;
157
+ const relative = path.relative(memoryDir, path.resolve(memoryDir, candidate));
158
+ const namespaced = /^namespaces[\\/]([^\\/]+)[\\/](.*)$/.exec(relative);
159
+ if (namespaced === null) return false;
160
+ return isActivityDigestPath(namespaced[2] ?? "", memoryDir);
161
+ }
162
+
163
+ /** A path as given, plus its form with a leading COLLECTION segment removed. */
164
+ function collectionSpellings(
165
+ filePath: string,
166
+ policy: GenericRecallPathPolicy,
167
+ source: GenericRecallPathSource,
168
+ ): string[] {
169
+ if (path.isAbsolute(filePath)) return [filePath];
170
+ // A `qmd://<collection>/<path>` URI states its collection UNAMBIGUOUSLY in
171
+ // the authority, so the leading segment of its normalized form is never a
172
+ // memory category — strip it outright rather than guessing.
173
+ if (source === "qmd" && filePath.startsWith("qmd://")) {
174
+ const normalized = path.posix.normalize(normalizeQmdUriPath(filePath, source).replace(/\\/g, "/"));
175
+ const slashIndex = normalized.indexOf("/");
176
+ return slashIndex <= 0 || slashIndex >= normalized.length - 1
177
+ ? [normalized]
178
+ : [normalized, normalized.slice(slashIndex + 1)];
179
+ }
180
+ const normalized = path.posix.normalize(
181
+ normalizeQmdUriPath(filePath, source).replace(/\\/g, "/"),
182
+ );
183
+ const stripped = stripQmdCollectionPrefix(filePath, policy, source);
184
+ const spellings = [stripped];
185
+ // If the AUTHORITATIVE strip already fired — the prefix matched a configured
186
+ // collection — stop. Removing a second segment would take a real directory
187
+ // with it: `memories/projects/activity/<date>.md` would become
188
+ // `activity/<date>.md` and an ordinary nested memory would read as a
189
+ // top-level digest, vanishing from search AND recall.
190
+ if (stripped !== normalized) return spellings;
191
+ const slashIndex = stripped.indexOf("/");
192
+ if (slashIndex <= 0 || slashIndex >= stripped.length - 1) return spellings;
193
+ const prefix = stripped.slice(0, slashIndex);
194
+ // The CALLER's own collection is authoritative: a request for
195
+ // `collection: "facts"` means that segment is the collection, whatever it is
196
+ // also the name of. Otherwise a category-shaped prefix stays part of the
197
+ // path, so an ordinary `facts/proj/activity/<date>.md` memory is searchable.
198
+ const requested = policy.requestedCollection;
199
+ if (
200
+ prefix !== requested &&
201
+ (Object.hasOwn(CATEGORY_MEMORY_ROOTS, prefix) || Object.hasOwn(RESERVED_ARCHIVE_ROOTS, prefix))
202
+ ) {
203
+ return spellings;
204
+ }
205
+ spellings.push(stripped.slice(slashIndex + 1));
206
+ return spellings;
207
+ }
208
+
97
209
  export function isGenericRecallExcludedPath(
98
210
  filePath: string,
99
211
  policy: GenericRecallPathPolicy = {},
100
212
  source: GenericRecallPathSource = "filesystem",
101
213
  ): boolean {
102
214
  return (
103
- isExternalWikiQmdPath(filePath, source) ||
104
- isArtifactMemoryPath(filePath) ||
105
- isActivityDigestPath(filePath, policy.memoryDir) ||
106
- isTopLevelArchivePath(filePath, policy, source) ||
107
- isMeetingRecordPath(filePath)
215
+ isSearchExcludedPath(filePath, policy, source) ||
216
+ // Recall-only: archived memories are cold, but an explicit search is one
217
+ // of the surfaces they remain reachable through.
218
+ isTopLevelArchivePath(filePath, policy, source)
108
219
  );
109
220
  }
110
221