@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.
- package/dist/access-admin-ops-surface.d.ts +2 -2
- package/dist/access-admin-ops-surface.js +3 -2
- package/dist/access-authorization-probe.d.ts +2 -2
- package/dist/access-authorization-probe.js +4 -3
- package/dist/access-boundary.d.ts +2 -2
- package/dist/access-boundary.js +3 -2
- package/dist/access-cli.js +16 -16
- package/dist/access-extraction-force-flush.d.ts +2 -2
- package/dist/access-extraction-force-flush.js +3 -2
- package/dist/access-http-lcm-compaction.d.ts +2 -2
- package/dist/access-http-lcm-compaction.js +1 -1
- package/dist/access-http-lifecycle-flush.d.ts +2 -2
- package/dist/access-http-lifecycle-flush.js +2 -2
- package/dist/access-http-offline-stream.d.ts +2 -2
- package/dist/access-http-query.js +3 -2
- package/dist/access-http.d.ts +8 -2
- package/dist/access-http.js +11 -10
- package/dist/access-identity-continuity-surface.d.ts +1 -1
- package/dist/access-identity-continuity-surface.js +3 -2
- package/dist/access-lcm-surface.d.ts +2 -2
- package/dist/access-lcm-surface.js +3 -2
- package/dist/access-mcp.d.ts +2 -2
- package/dist/access-mcp.js +7 -6
- package/dist/access-memory-search-fanout.d.ts +163 -2
- package/dist/access-memory-search-fanout.js +20 -3
- package/dist/access-namespace-preflight.js +3 -2
- package/dist/access-observe-write-surface.d.ts +2 -2
- package/dist/access-observe-write-surface.js +3 -2
- package/dist/access-offline-manifest.d.ts +1 -1
- package/dist/access-operations-batch.js +4 -3
- package/dist/access-operations.d.ts +6 -5
- package/dist/access-operations.js +6 -5
- package/dist/access-recall-concurrency.d.ts +2 -2
- package/dist/access-recall-concurrency.js +3 -2
- package/dist/access-recall-response.d.ts +2 -2
- package/dist/access-recall-response.js +3 -2
- package/dist/access-recall-surface.d.ts +2 -2
- package/dist/access-recall-surface.js +3 -2
- package/dist/access-schema.d.ts +84 -84
- package/dist/{access-service-loJadYd7.d.ts → access-service-DNBYHK33.d.ts} +2 -1
- package/dist/access-service-helpers.d.ts +2 -2
- package/dist/access-service.d.ts +2 -2
- package/dist/access-service.js +3 -2
- package/dist/access-surface-catalog.d.ts +2 -2
- package/dist/access-surface-catalog.js +1 -0
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +2 -2
- package/dist/bootstrap.d.ts +1 -1
- package/dist/{chunk-WHWDBTCJ.js → chunk-74HN5QUH.js} +2 -2
- package/dist/{chunk-2DSUT7DZ.js → chunk-FQU3HTQM.js} +40 -11
- package/dist/chunk-FQU3HTQM.js.map +1 -0
- package/dist/{chunk-5LRDM4R7.js → chunk-G2L5IZV4.js} +7 -7
- package/dist/{chunk-TQZ4DVBC.js → chunk-GZ3R6XOE.js} +5 -2
- package/dist/chunk-GZ3R6XOE.js.map +1 -0
- package/dist/{chunk-5546W5SI.js → chunk-HLKPRWF4.js} +2 -2
- package/dist/chunk-IXEBUYO4.js +201 -0
- package/dist/chunk-IXEBUYO4.js.map +1 -0
- package/dist/{chunk-RCU25Z66.js → chunk-IXH2U2QT.js} +169 -58
- package/dist/chunk-IXH2U2QT.js.map +1 -0
- package/dist/{chunk-HJ3SVHUW.js → chunk-IZQ5TDFT.js} +9 -4
- package/dist/chunk-IZQ5TDFT.js.map +1 -0
- package/dist/{chunk-RVPTS4U5.js → chunk-JCNJIXZN.js} +2 -2
- package/dist/{chunk-5R6S4DJZ.js → chunk-N5XULCU3.js} +2 -2
- package/dist/{chunk-6H744ZBH.js → chunk-QISYTKG4.js} +2 -2
- package/dist/{chunk-AUMWZSE7.js → chunk-RN53QBM4.js} +4 -4
- package/dist/{chunk-M32URQXV.js → chunk-SIP5DBDN.js} +275 -334
- package/dist/chunk-SIP5DBDN.js.map +1 -0
- package/dist/{chunk-44N6ESJ4.js → chunk-TIDEL77O.js} +2 -2
- package/dist/{chunk-XBMJ35IP.js → chunk-VFZS2HAA.js} +4 -3
- package/dist/chunk-VFZS2HAA.js.map +1 -0
- package/dist/{cli-D_RcqXfZ.d.ts → cli-BWXSPxB9.d.ts} +2 -2
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +15 -15
- package/dist/config.js +2 -2
- package/dist/explicit-capture.d.ts +1 -1
- package/dist/external-wiki-access.d.ts +2 -2
- package/dist/external-wiki-access.js +4 -3
- package/dist/external-wiki-mcp-tools.d.ts +2 -2
- package/dist/index.d.ts +680 -680
- package/dist/index.js +16 -16
- package/dist/mcp-memory-inspector-app.d.ts +2 -2
- package/dist/operator-toolkit.js +3 -3
- package/dist/{orchestrator-LPGegii3.d.ts → orchestrator-CRR-6sVB.d.ts} +6 -0
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +17 -17
- package/dist/resume-bundles.js +3 -3
- package/dist/schemas.d.ts +76 -76
- package/dist/shared-context/manager.d.ts +8 -8
- package/dist/transfer/types.d.ts +66 -66
- package/package.json +2 -2
- package/src/access-http-lcm-compaction.ts +3 -0
- package/src/access-http.test.ts +645 -0
- package/src/access-http.ts +55 -4
- package/src/access-mcp.ts +1 -0
- package/src/access-memory-search-fanout.ts +305 -2
- package/src/access-operations.ts +6 -0
- package/src/access-service-namespace.test.ts +146 -0
- package/src/access-service.ts +39 -40
- package/src/access-surface-catalog.ts +1 -0
- package/src/orchestration/generic-recall-paths.ts +116 -5
- package/dist/chunk-2DSUT7DZ.js.map +0 -1
- package/dist/chunk-7HXCF4RE.js +0 -72
- package/dist/chunk-7HXCF4RE.js.map +0 -1
- package/dist/chunk-HJ3SVHUW.js.map +0 -1
- package/dist/chunk-M32URQXV.js.map +0 -1
- package/dist/chunk-RCU25Z66.js.map +0 -1
- package/dist/chunk-TQZ4DVBC.js.map +0 -1
- package/dist/chunk-XBMJ35IP.js.map +0 -1
- /package/dist/{chunk-WHWDBTCJ.js.map → chunk-74HN5QUH.js.map} +0 -0
- /package/dist/{chunk-5LRDM4R7.js.map → chunk-G2L5IZV4.js.map} +0 -0
- /package/dist/{chunk-5546W5SI.js.map → chunk-HLKPRWF4.js.map} +0 -0
- /package/dist/{chunk-RVPTS4U5.js.map → chunk-JCNJIXZN.js.map} +0 -0
- /package/dist/{chunk-5R6S4DJZ.js.map → chunk-N5XULCU3.js.map} +0 -0
- /package/dist/{chunk-6H744ZBH.js.map → chunk-QISYTKG4.js.map} +0 -0
- /package/dist/{chunk-AUMWZSE7.js.map → chunk-RN53QBM4.js.map} +0 -0
- /package/dist/{chunk-44N6ESJ4.js.map → chunk-TIDEL77O.js.map} +0 -0
|
@@ -94,7 +94,7 @@ import {
|
|
|
94
94
|
buildResumeBundleFromState,
|
|
95
95
|
getResumeBundleStatus,
|
|
96
96
|
recordResumeBundle
|
|
97
|
-
} from "./chunk-
|
|
97
|
+
} from "./chunk-HLKPRWF4.js";
|
|
98
98
|
import {
|
|
99
99
|
getWorkProductLedgerStatus,
|
|
100
100
|
recordWorkProductLedgerEntry,
|
|
@@ -118,7 +118,7 @@ import {
|
|
|
118
118
|
runOperatorInventory,
|
|
119
119
|
runOperatorRepair,
|
|
120
120
|
runOperatorSetup
|
|
121
|
-
} from "./chunk-
|
|
121
|
+
} from "./chunk-N5XULCU3.js";
|
|
122
122
|
import {
|
|
123
123
|
analyzeSessionIntegrity,
|
|
124
124
|
applySessionRepair,
|
|
@@ -196,11 +196,11 @@ import {
|
|
|
196
196
|
} from "./chunk-UQ7RN5HK.js";
|
|
197
197
|
import {
|
|
198
198
|
parseConfig
|
|
199
|
-
} from "./chunk-
|
|
199
|
+
} from "./chunk-RN53QBM4.js";
|
|
200
200
|
import {
|
|
201
201
|
EngramAccessHttpServer,
|
|
202
202
|
MeetingsInputError
|
|
203
|
-
} from "./chunk-
|
|
203
|
+
} from "./chunk-FQU3HTQM.js";
|
|
204
204
|
import {
|
|
205
205
|
WearablesInputError
|
|
206
206
|
} from "./chunk-V7IZKJI3.js";
|
|
@@ -213,13 +213,13 @@ import {
|
|
|
213
213
|
createProductionChatLlmAdapter,
|
|
214
214
|
loadChatSession,
|
|
215
215
|
sessionBelongsToPrincipal
|
|
216
|
-
} from "./chunk-
|
|
216
|
+
} from "./chunk-VFZS2HAA.js";
|
|
217
217
|
import {
|
|
218
218
|
getAbstractionNodeStoreStatus
|
|
219
219
|
} from "./chunk-OADWQ5CR.js";
|
|
220
220
|
import {
|
|
221
221
|
EngramAccessService
|
|
222
|
-
} from "./chunk-
|
|
222
|
+
} from "./chunk-IXH2U2QT.js";
|
|
223
223
|
import {
|
|
224
224
|
WorkStorage
|
|
225
225
|
} from "./chunk-GDB4J2H3.js";
|
|
@@ -7985,4 +7985,4 @@ export {
|
|
|
7985
7985
|
listMemoryMarkdownFilePaths,
|
|
7986
7986
|
registerCli
|
|
7987
7987
|
};
|
|
7988
|
-
//# sourceMappingURL=chunk-
|
|
7988
|
+
//# sourceMappingURL=chunk-G2L5IZV4.js.map
|
|
@@ -4,7 +4,10 @@ function respondAccessCapabilitiesHttp(response) {
|
|
|
4
4
|
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
5
5
|
response.end(JSON.stringify({
|
|
6
6
|
lcmCompactionFlushBatch: true,
|
|
7
|
-
offlineSyncConvergenceComplete: true
|
|
7
|
+
offlineSyncConvergenceComplete: true,
|
|
8
|
+
// POST /engram|remnic/v1/memories/search — HTTP clients probe this before
|
|
9
|
+
// using the route so they can degrade against an older daemon.
|
|
10
|
+
memoriesSearch: true
|
|
8
11
|
}, null, 2));
|
|
9
12
|
}
|
|
10
13
|
async function runLcmCompactionFlushHttp({
|
|
@@ -130,4 +133,4 @@ export {
|
|
|
130
133
|
handleLcmCompactionFlushHttp,
|
|
131
134
|
runLcmCompactionRecordHttp
|
|
132
135
|
};
|
|
133
|
-
//# sourceMappingURL=chunk-
|
|
136
|
+
//# sourceMappingURL=chunk-GZ3R6XOE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/access-http-lcm-compaction.ts"],"sourcesContent":["import type { ServerResponse } from \"node:http\";\n\nimport type { LcmCompactionFlushRequest, LcmCompactionRecordRequest } from \"./access-schema.js\";\nimport type {\n EngramAccessLcmCompactionFlushResponse,\n EngramAccessLcmCompactionRecordResponse,\n EngramAccessService,\n} from \"./access-service.js\";\n\ntype LcmCompactionService = Pick<EngramAccessService, \"lcmCompactionFlush\" | \"lcmCompactionRecord\">;\ntype JsonResponder = (res: ServerResponse, status: number, payload: unknown) => void;\n\nexport interface LcmCompactionFlushHttpOptions {\n body: LcmCompactionFlushRequest;\n service: LcmCompactionService;\n response: ServerResponse;\n ensureWriteRateLimitAvailable: () => void;\n recordWriteRateLimitHit: () => void;\n resolveNamespace: (namespace?: string) => string | undefined;\n defaultNamespace?: string;\n resolveRequestPrincipal: () => string | undefined;\n respondJson: JsonResponder;\n}\n\nexport function respondAccessCapabilitiesHttp(response: ServerResponse): void {\n response.statusCode = 200;\n response.setHeader(\"content-type\", \"application/json; charset=utf-8\");\n response.end(JSON.stringify({\n lcmCompactionFlushBatch: true,\n offlineSyncConvergenceComplete: true,\n // POST /engram|remnic/v1/memories/search — HTTP clients probe this before\n // using the route so they can degrade against an older daemon.\n memoriesSearch: true,\n }, null, 2));\n}\n\nexport interface LcmCompactionFlushRunnerOptions {\n body: LcmCompactionFlushRequest;\n service: LcmCompactionService;\n ensureWriteRateLimitAvailable: () => void;\n recordWriteRateLimitHit: () => void;\n resolveNamespace: (namespace?: string) => string | undefined;\n defaultNamespace?: string;\n resolveRequestPrincipal: () => string | undefined;\n}\n\ntype LcmCompactionFlushBatchResult =\n | {\n namespace: string;\n status: \"fulfilled\";\n result: EngramAccessLcmCompactionFlushResponse;\n }\n | { namespace: string; status: \"rejected\" };\n\ntype LcmCompactionFlushHttpResult =\n | EngramAccessLcmCompactionFlushResponse\n | {\n enabled: boolean;\n flushed: boolean;\n sessionKey: string;\n namespaces: string[];\n results: LcmCompactionFlushBatchResult[];\n };\n\nexport async function runLcmCompactionFlushHttp({\n body,\n service,\n ensureWriteRateLimitAvailable,\n recordWriteRateLimitHit,\n resolveNamespace,\n defaultNamespace,\n resolveRequestPrincipal,\n}: LcmCompactionFlushRunnerOptions): Promise<LcmCompactionFlushHttpResult> {\n ensureWriteRateLimitAvailable();\n const requestedNamespaces = body.namespaces;\n if (requestedNamespaces === undefined) {\n const result = await service.lcmCompactionFlush({\n sessionKey: body.sessionKey,\n namespace: resolveNamespace(body.namespace),\n ...(body.cwd !== undefined ? { cwd: body.cwd } : {}),\n ...(body.projectTag !== undefined ? { projectTag: body.projectTag } : {}),\n authenticatedPrincipal: resolveRequestPrincipal(),\n });\n recordWriteRateLimitHit();\n return result;\n }\n const resolutionOutcomes = await Promise.allSettled(\n requestedNamespaces.map(async (requestedNamespace) => {\n const namespace = resolveNamespace(requestedNamespace);\n return {\n requestedNamespace,\n namespace,\n effectiveNamespace: namespace ?? defaultNamespace ?? \"\",\n };\n })\n );\n const uniqueResolvedNamespaces: Array<{\n requestedNamespace: string;\n namespace: string | undefined;\n effectiveNamespace: string;\n }> = [];\n const queuedEffectiveNamespaces = new Set<string>();\n for (const outcome of resolutionOutcomes) {\n if (outcome.status === \"fulfilled\" && !queuedEffectiveNamespaces.has(outcome.value.effectiveNamespace)) {\n queuedEffectiveNamespaces.add(outcome.value.effectiveNamespace);\n uniqueResolvedNamespaces.push(outcome.value);\n }\n }\n const serviceOutcomes = await Promise.allSettled(\n uniqueResolvedNamespaces.map(({ namespace }) =>\n service.lcmCompactionFlush({\n sessionKey: body.sessionKey,\n namespace,\n ...(body.cwd !== undefined ? { cwd: body.cwd } : {}),\n ...(body.projectTag !== undefined ? { projectTag: body.projectTag } : {}),\n authenticatedPrincipal: resolveRequestPrincipal(),\n })\n )\n );\n const serviceOutcomesByEffectiveNamespace = new Map<\n string,\n PromiseSettledResult<EngramAccessLcmCompactionFlushResponse>\n >();\n for (const [index, resolved] of uniqueResolvedNamespaces.entries()) {\n const serviceOutcome = serviceOutcomes[index];\n if (serviceOutcome !== undefined) {\n serviceOutcomesByEffectiveNamespace.set(resolved.effectiveNamespace, serviceOutcome);\n }\n }\n const results = resolutionOutcomes.map((outcome, index): LcmCompactionFlushBatchResult => {\n const requestedNamespace = requestedNamespaces[index] ?? \"\";\n if (outcome.status === \"rejected\") {\n return { status: \"rejected\", namespace: requestedNamespace };\n }\n const serviceOutcome = serviceOutcomesByEffectiveNamespace.get(outcome.value.effectiveNamespace);\n return serviceOutcome?.status === \"fulfilled\"\n ? { status: \"fulfilled\", namespace: requestedNamespace, result: serviceOutcome.value }\n : { status: \"rejected\", namespace: requestedNamespace };\n });\n recordWriteRateLimitHit();\n return {\n enabled: results.every((result) => result.status === \"fulfilled\" && result.result.enabled !== false),\n flushed: results.every((result) => result.status === \"fulfilled\" && result.result.flushed !== false),\n sessionKey: body.sessionKey,\n namespaces: requestedNamespaces,\n results,\n };\n}\nexport async function handleLcmCompactionFlushHttp({\n body,\n service,\n response,\n ensureWriteRateLimitAvailable,\n recordWriteRateLimitHit,\n resolveNamespace,\n defaultNamespace,\n resolveRequestPrincipal,\n respondJson,\n}: LcmCompactionFlushHttpOptions): Promise<void> {\n const result = await runLcmCompactionFlushHttp({\n body,\n service,\n ensureWriteRateLimitAvailable,\n recordWriteRateLimitHit,\n resolveNamespace,\n defaultNamespace,\n resolveRequestPrincipal,\n });\n respondJson(response, 200, result);\n}\n\nexport interface LcmCompactionRecordHttpOptions {\n body: LcmCompactionRecordRequest;\n service: LcmCompactionService;\n ensureWriteRateLimitAvailable: () => void;\n recordWriteRateLimitHit: () => void;\n resolveNamespace: (namespace?: string) => string | undefined;\n resolveRequestPrincipal: () => string | undefined;\n}\n\nexport async function runLcmCompactionRecordHttp({\n body,\n service,\n ensureWriteRateLimitAvailable,\n recordWriteRateLimitHit,\n resolveNamespace,\n resolveRequestPrincipal,\n}: LcmCompactionRecordHttpOptions): Promise<EngramAccessLcmCompactionRecordResponse> {\n ensureWriteRateLimitAvailable();\n const response = await service.lcmCompactionRecord({\n sessionKey: body.sessionKey,\n namespace: resolveNamespace(body.namespace),\n tokensBefore: body.tokensBefore,\n tokensAfter: body.tokensAfter,\n authenticatedPrincipal: resolveRequestPrincipal(),\n });\n recordWriteRateLimitHit();\n return response;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from "./chunk-LMDRGRJ2.js";
|
|
12
12
|
import {
|
|
13
13
|
parseConfig
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-RN53QBM4.js";
|
|
15
15
|
import {
|
|
16
16
|
resolveObjectiveStateStoreDir,
|
|
17
17
|
validateObjectiveStateSnapshot
|
|
@@ -268,4 +268,4 @@ export {
|
|
|
268
268
|
recordResumeBundle,
|
|
269
269
|
getResumeBundleStatus
|
|
270
270
|
};
|
|
271
|
-
//# sourceMappingURL=chunk-
|
|
271
|
+
//# sourceMappingURL=chunk-HLKPRWF4.js.map
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canReadNamespace,
|
|
3
|
+
defaultNamespaceForPrincipal
|
|
4
|
+
} from "./chunk-3GD2HFR6.js";
|
|
5
|
+
import {
|
|
6
|
+
EngramAccessInputError
|
|
7
|
+
} from "./chunk-2Z24E5W2.js";
|
|
8
|
+
import {
|
|
9
|
+
log
|
|
10
|
+
} from "./chunk-3WCASNQZ.js";
|
|
11
|
+
|
|
12
|
+
// src/access-memory-search-fanout.ts
|
|
13
|
+
async function resolveMemorySearchDefaultFallback(options) {
|
|
14
|
+
const { profilePlan, config, principal } = options;
|
|
15
|
+
const principalSelfIsDefault = defaultNamespaceForPrincipal(principal, config) === config.defaultNamespace;
|
|
16
|
+
const profileIntendsUserGlobal = profilePlan.profile.readOrder.includes("userGlobal");
|
|
17
|
+
const userGlobalLayerReadable = profilePlan.layers.some(
|
|
18
|
+
(layer) => layer.id === "userGlobal" && layer.readable && Boolean(layer.namespace)
|
|
19
|
+
);
|
|
20
|
+
const selfResolvedAwayFromDefault = profilePlan.baseNamespace !== config.defaultNamespace;
|
|
21
|
+
if (!(profileIntendsUserGlobal && userGlobalLayerReadable && principalSelfIsDefault && selfResolvedAwayFromDefault && canReadNamespace(principal, config.defaultNamespace, config))) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const defaultAtFlatRoot = await options.defaultAtFlatRootProvider();
|
|
25
|
+
return defaultAtFlatRoot ? config.defaultNamespace : null;
|
|
26
|
+
}
|
|
27
|
+
async function defaultNamespaceAtFlatRoot(getStorage, config) {
|
|
28
|
+
const storage = await getStorage(config.defaultNamespace);
|
|
29
|
+
return storage.dir === config.memoryDir;
|
|
30
|
+
}
|
|
31
|
+
function mergeMemorySearchDefaultFallback(profileNamespaces, fallback) {
|
|
32
|
+
if (!fallback || profileNamespaces.includes(fallback)) return profileNamespaces;
|
|
33
|
+
return [...profileNamespaces, fallback];
|
|
34
|
+
}
|
|
35
|
+
async function runMemorySearchFanout(options) {
|
|
36
|
+
const { query, namespaces, maxResults, principal, requestedNamespace, collection } = options;
|
|
37
|
+
if (namespaces.length === 0) {
|
|
38
|
+
log.warn("memory_search resolved zero readable namespaces", {
|
|
39
|
+
principal: principal ?? null,
|
|
40
|
+
requestedNamespace: requestedNamespace ?? null,
|
|
41
|
+
collection: collection ?? null
|
|
42
|
+
});
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
log.debug("memory_search fanout", {
|
|
46
|
+
principal: principal ?? null,
|
|
47
|
+
namespaces,
|
|
48
|
+
collection: collection ?? null
|
|
49
|
+
});
|
|
50
|
+
const degradations = [];
|
|
51
|
+
const results = await options.search({
|
|
52
|
+
query,
|
|
53
|
+
namespaces,
|
|
54
|
+
maxResults,
|
|
55
|
+
mode: options.mode ?? "search",
|
|
56
|
+
execution: {
|
|
57
|
+
onDegradation: (degradation) => degradations.push(degradation)
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
if (degradations.length > 0) {
|
|
61
|
+
log.warn("memory_search backend degradation", {
|
|
62
|
+
principal: principal ?? null,
|
|
63
|
+
degradations
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
var MEMORY_SEARCH_MODES = ["search", "hybrid", "bm25", "vector"];
|
|
69
|
+
function assertMemorySearchLimit(maxResults) {
|
|
70
|
+
if (maxResults === void 0) return;
|
|
71
|
+
if (typeof maxResults !== "number" || !Number.isInteger(maxResults) || maxResults < 0) {
|
|
72
|
+
throw new EngramAccessInputError(
|
|
73
|
+
`maxResults must be a non-negative integer (got ${JSON.stringify(maxResults)})`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function assertMemorySearchMode(mode) {
|
|
78
|
+
if (mode === void 0) return;
|
|
79
|
+
if (typeof mode !== "string" || !MEMORY_SEARCH_MODES.includes(mode)) {
|
|
80
|
+
throw new EngramAccessInputError(
|
|
81
|
+
`mode must be one of ${MEMORY_SEARCH_MODES.join(", ")} (got ${JSON.stringify(mode)})`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function assertFlatCorpusOptions(mode, collection) {
|
|
86
|
+
assertMemorySearchMode(mode);
|
|
87
|
+
if (mode && collection) {
|
|
88
|
+
throw new EngramAccessInputError(
|
|
89
|
+
`mode is not supported together with collection on a flat corpus (got collection: ${collection})`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function runFlatCorpusMemorySearch(options) {
|
|
94
|
+
const { query, maxResults, collection, mode } = options;
|
|
95
|
+
assertFlatCorpusOptions(options.mode, options.collection);
|
|
96
|
+
if (mode) {
|
|
97
|
+
return options.searchAcrossNamespaces({ query, maxResults, mode });
|
|
98
|
+
}
|
|
99
|
+
return collection === "global" ? options.searchGlobal(query, maxResults) : options.search(query, collection, maxResults);
|
|
100
|
+
}
|
|
101
|
+
var MEMORY_SEARCH_CANDIDATE_CAP = 25e3;
|
|
102
|
+
async function searchWithGenericExclusion(options) {
|
|
103
|
+
const { budget } = options;
|
|
104
|
+
if (budget <= 0) return [];
|
|
105
|
+
let results = [];
|
|
106
|
+
let limit = options.sendInitialLimit ? budget : void 0;
|
|
107
|
+
let target = budget;
|
|
108
|
+
let firstPage = true;
|
|
109
|
+
for (; ; ) {
|
|
110
|
+
const raw = await options.search(limit);
|
|
111
|
+
results = raw.filter((hit) => !options.isExcluded(hit.path));
|
|
112
|
+
if (firstPage) {
|
|
113
|
+
firstPage = false;
|
|
114
|
+
if (!options.sendInitialLimit) target = Math.min(budget, raw.length);
|
|
115
|
+
if (target <= 0) return [];
|
|
116
|
+
}
|
|
117
|
+
if (results.length >= target) break;
|
|
118
|
+
const served = limit ?? raw.length;
|
|
119
|
+
if (raw.length === 0 || raw.length < served) break;
|
|
120
|
+
const cap = Math.max(MEMORY_SEARCH_CANDIDATE_CAP, target * 2);
|
|
121
|
+
if (served >= cap) break;
|
|
122
|
+
limit = Math.min(served * 2, cap);
|
|
123
|
+
}
|
|
124
|
+
return results.slice(0, target);
|
|
125
|
+
}
|
|
126
|
+
async function runScopedMemorySearch(options) {
|
|
127
|
+
await options.authorizeScope();
|
|
128
|
+
const results = await searchWithGenericExclusion({
|
|
129
|
+
budget: options.budget,
|
|
130
|
+
sendInitialLimit: options.sendInitialLimit,
|
|
131
|
+
isExcluded: options.isExcluded,
|
|
132
|
+
search: (limit) => options.namespacesEnabled ? options.namespaced(limit) : options.flatCorpus(limit)
|
|
133
|
+
});
|
|
134
|
+
return results.map((hit) => ({
|
|
135
|
+
path: hit.path,
|
|
136
|
+
score: hit.score,
|
|
137
|
+
snippet: (hit.snippet ?? "").slice(0, 800)
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
async function memorySearchThroughScope(deps, request) {
|
|
141
|
+
const { query, namespace, maxResults, mode, principal } = request;
|
|
142
|
+
assertMemorySearchMode(mode);
|
|
143
|
+
assertMemorySearchLimit(maxResults);
|
|
144
|
+
const collection = request.collection?.trim();
|
|
145
|
+
if (request.collection !== void 0 && !collection) {
|
|
146
|
+
throw new EngramAccessInputError("collection must be a non-empty string");
|
|
147
|
+
}
|
|
148
|
+
let searchNamespaces = [];
|
|
149
|
+
const results = await runScopedMemorySearch({
|
|
150
|
+
query,
|
|
151
|
+
collection,
|
|
152
|
+
mode,
|
|
153
|
+
namespacesEnabled: deps.namespacesEnabled,
|
|
154
|
+
isExcluded: deps.isExcluded,
|
|
155
|
+
budget: maxResults ?? deps.defaultBudget,
|
|
156
|
+
sendInitialLimit: maxResults !== void 0,
|
|
157
|
+
authorizeScope: async () => {
|
|
158
|
+
if (!deps.namespacesEnabled) {
|
|
159
|
+
assertFlatCorpusOptions(mode, collection);
|
|
160
|
+
return deps.authorizeFlatCorpus(namespace, principal);
|
|
161
|
+
}
|
|
162
|
+
searchNamespaces = await deps.authorizeNamespaces(namespace, principal, collection);
|
|
163
|
+
},
|
|
164
|
+
flatCorpus: (limit) => runFlatCorpusMemorySearch({
|
|
165
|
+
query,
|
|
166
|
+
maxResults: limit,
|
|
167
|
+
collection,
|
|
168
|
+
mode,
|
|
169
|
+
searchAcrossNamespaces: (p) => deps.searchAcrossNamespaces(p),
|
|
170
|
+
searchGlobal: (q, globalLimit) => deps.searchGlobal(q, globalLimit),
|
|
171
|
+
search: (q, coll, searchLimit) => deps.search(q, coll, searchLimit)
|
|
172
|
+
}),
|
|
173
|
+
namespaced: (limit) => runMemorySearchFanout({
|
|
174
|
+
query,
|
|
175
|
+
maxResults: limit,
|
|
176
|
+
principal,
|
|
177
|
+
collection,
|
|
178
|
+
mode,
|
|
179
|
+
requestedNamespace: namespace,
|
|
180
|
+
namespaces: searchNamespaces,
|
|
181
|
+
search: (p) => deps.searchAcrossNamespaces(p)
|
|
182
|
+
})
|
|
183
|
+
});
|
|
184
|
+
return { query, results, count: results.length };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export {
|
|
188
|
+
resolveMemorySearchDefaultFallback,
|
|
189
|
+
defaultNamespaceAtFlatRoot,
|
|
190
|
+
mergeMemorySearchDefaultFallback,
|
|
191
|
+
runMemorySearchFanout,
|
|
192
|
+
MEMORY_SEARCH_MODES,
|
|
193
|
+
assertMemorySearchLimit,
|
|
194
|
+
assertMemorySearchMode,
|
|
195
|
+
assertFlatCorpusOptions,
|
|
196
|
+
runFlatCorpusMemorySearch,
|
|
197
|
+
searchWithGenericExclusion,
|
|
198
|
+
runScopedMemorySearch,
|
|
199
|
+
memorySearchThroughScope
|
|
200
|
+
};
|
|
201
|
+
//# sourceMappingURL=chunk-IXEBUYO4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/access-memory-search-fanout.ts"],"sourcesContent":["import { canReadNamespace, defaultNamespaceForPrincipal } from \"./namespaces/principal.js\";\nimport type { ResolvedScopeProfilePlan } from \"./namespaces/scope-profiles.js\";\nimport { EngramAccessInputError } from \"./access-errors.js\";\nimport { log } from \"./logger.js\";\nimport type { SearchDegradation, SearchExecutionOptions } from \"./search/port.js\";\nimport type { PluginConfig } from \"./types.js\";\n\n/**\n * Issue #2018: `memory_search` has no sessionKey, so — unlike recall — it\n * cannot resolve a coding overlay. With scope profiles active and no overlay,\n * `expandScopeProfileReadNamespaces` derives a per-principal self namespace.\n * On a legacy flat-root deployment the corpus still lives under the configured\n * DEFAULT namespace's base collection, which that derived set excludes, so\n * every query silently returned 0.\n *\n * Returns the configured default namespace to merge into the profile read set\n * when the base collection must stay reachable, or `null` otherwise. ALL of:\n *\n * - the default namespace is THIS principal's own self store — i.e. the\n * principal has no dedicated namespace policy, so\n * `defaultNamespaceForPrincipal` resolves to the configured default. On a\n * multi-tenant scope-profile deployment every principal has its own\n * policy; reaching into the default would read ANOTHER namespace, so the\n * fallback must not fire (#2056 r2 / #1501 privateOnly);\n * - the profile resolved `userGlobal` as a READABLE layer (not just listed\n * it in `readOrder`). `serverShared` maps to `sharedNamespace`, not the\n * default, and an unreadable resolved layer is a deliberate omission —\n * neither may trigger this fallback (#2056 r2);\n * - the profile's resolved self namespace is NOT already the default\n * (otherwise it is already in the set);\n * - the principal is authorized to read the default namespace.\n *\n * The flat-root signal is resolved lazily via {@link defaultAtFlatRootProvider}\n * — the provider is only invoked once the cheap gates (readOrder consent,\n * resolved-layer readability, principal-self, self-resolved-away, ACL) all\n * pass, so a project-only / serverShared-only / unreadable-userGlobal\n * profile never touches the default store (#2056 r6).\n */\nexport async function resolveMemorySearchDefaultFallback(options: {\n profilePlan: ResolvedScopeProfilePlan;\n config: PluginConfig;\n principal: string | undefined;\n /**\n * True only when the configured default namespace's storage root equals\n * `config.memoryDir` (the legacy flat-root layout). Invoked ONLY after the\n * cheap gates pass, so ineligible profiles never probe the default store.\n */\n defaultAtFlatRootProvider: () => Promise<boolean>;\n}): Promise<string | null> {\n const { profilePlan, config, principal } = options;\n // Defense-in-depth: principalSelfIsDefault is only ever true alongside a\n // flat-root default on legacy deployments (a hosted scope-profile principal\n // with its own policy fails this even when default is at the flat root). It\n // is NOT redundant — it keeps a policy-having principal isolated from the\n // flat default corpus.\n const principalSelfIsDefault =\n defaultNamespaceForPrincipal(principal, config) === config.defaultNamespace;\n // readOrder must explicitly intend userGlobal (resolveScopeProfilePlan\n // always materializes a userGlobal layer even when readOrder omits it, so\n // the layer's presence alone is not consent), AND that layer must have\n // resolved readable (a listed-but-unreadable layer is a deliberate\n // omission). serverShared maps to sharedNamespace, not the default.\n const profileIntendsUserGlobal =\n profilePlan.profile.readOrder.includes(\"userGlobal\");\n const userGlobalLayerReadable = profilePlan.layers.some(\n (layer) => layer.id === \"userGlobal\" && layer.readable && Boolean(layer.namespace),\n );\n const selfResolvedAwayFromDefault =\n profilePlan.baseNamespace !== config.defaultNamespace;\n if (\n !(\n profileIntendsUserGlobal &&\n userGlobalLayerReadable &&\n principalSelfIsDefault &&\n selfResolvedAwayFromDefault &&\n canReadNamespace(principal, config.defaultNamespace, config)\n )\n ) {\n // Cheap gates failed — never probe the default store.\n return null;\n }\n const defaultAtFlatRoot = await options.defaultAtFlatRootProvider();\n return defaultAtFlatRoot ? config.defaultNamespace : null;\n}\n\n/**\n * Issue #2018 r4: the explicit legacy-flat-root signal — the configured\n * default namespace's storage root equals `config.memoryDir`. Resolved from\n * storage (not inferred from config/policy shape) so a hosted scope-profile\n * deployment never reaches into the default corpus.\n */\nexport async function defaultNamespaceAtFlatRoot(\n getStorage: (namespace: string) => Promise<{ dir: string }>,\n config: { defaultNamespace: string; memoryDir: string },\n): Promise<boolean> {\n const storage = await getStorage(config.defaultNamespace);\n return storage.dir === config.memoryDir;\n}\n\n/**\n * Merge the {@link resolveMemorySearchDefaultFallback} result into a profile\n * read-namespace set without duplicates. Returns the original set unchanged\n * when no fallback applies.\n */\nexport function mergeMemorySearchDefaultFallback(\n profileNamespaces: string[],\n fallback: string | null,\n): string[] {\n if (!fallback || profileNamespaces.includes(fallback)) return profileNamespaces;\n return [...profileNamespaces, fallback];\n}\n\n\n/**\n * Run a memory_search namespace fanout with the observability required by\n * issue #2018: a silent empty fanout is surfaced as a warning (never silently\n * empty), and per-namespace backend degradations — which memory_search\n * previously swallowed — are collected and warned so a missing collection\n * looks different from a genuine no-matches.\n *\n * `search` is injected so this stays free of the orchestrator type.\n */\nexport async function runMemorySearchFanout<TResult>(options: {\n query: string;\n namespaces: string[];\n maxResults?: number;\n principal?: string;\n requestedNamespace?: string;\n collection?: string;\n mode?: \"search\" | \"hybrid\" | \"bm25\" | \"vector\";\n search(params: {\n query: string;\n namespaces: string[];\n maxResults?: number;\n mode: \"search\" | \"hybrid\" | \"bm25\" | \"vector\";\n execution: SearchExecutionOptions;\n }): Promise<TResult[]>;\n}): Promise<TResult[]> {\n const { query, namespaces, maxResults, principal, requestedNamespace, collection } = options;\n if (namespaces.length === 0) {\n log.warn(\"memory_search resolved zero readable namespaces\", {\n principal: principal ?? null,\n requestedNamespace: requestedNamespace ?? null,\n collection: collection ?? null,\n });\n return [];\n }\n log.debug(\"memory_search fanout\", {\n principal: principal ?? null,\n namespaces,\n collection: collection ?? null,\n });\n const degradations: SearchDegradation[] = [];\n const results = await options.search({\n query,\n namespaces,\n maxResults,\n mode: options.mode ?? \"search\",\n execution: {\n onDegradation: (degradation) => degradations.push(degradation),\n },\n });\n if (degradations.length > 0) {\n log.warn(\"memory_search backend degradation\", {\n principal: principal ?? null,\n degradations,\n });\n }\n return results;\n}\n\n/**\n * Resolve results for a FLAT corpus (namespaces disabled).\n *\n * An explicit ranking mode routes through the namespace-aware search even\n * here, because that is the only path honoring it; the legacy direct-QMD calls\n * stay the default so nothing else moves.\n */\n/**\n * Reject an option combination a flat corpus cannot honor.\n *\n * A `mode` routes through the namespace-aware backend, which cannot target a\n * specific collection, so pairing the two would silently search the default\n * collection and return unrelated results (AGENTS.md pattern 39).\n *\n * Exported as its own step because callers validate BEFORE any budget\n * short-circuit: changing only the requested result count must never make an\n * invalid request succeed.\n */\nexport const MEMORY_SEARCH_MODES = [\"search\", \"hybrid\", \"bm25\", \"vector\"] as const;\n\nexport type MemorySearchMode = (typeof MEMORY_SEARCH_MODES)[number];\n\n/**\n * Reject an unrecognized ranking mode at the service boundary.\n *\n * The HTTP and MCP schemas already validate their own input, but an\n * in-process caller reaches this service untyped. Both namespace backends\n * route an unknown mode through their DEFAULT ordinary-search branch, so a\n * typo would silently rank differently instead of reporting bad input\n * (AGENTS.md §39).\n */\n/**\n * Reject a budget that is not a finite non-negative integer.\n *\n * The HTTP and MCP schemas validate their own input, but an in-process caller\n * reaches this service untyped: a negative would hit the `budget <= 0`\n * short-circuit and return a successful EMPTY page, and a fraction or a\n * non-finite value would flow into the backend limit and top-up arithmetic.\n * Zero keeps its documented meaning — an empty result with no backend call.\n */\nexport function assertMemorySearchLimit(maxResults: unknown): void {\n if (maxResults === undefined) return;\n if (typeof maxResults !== \"number\" || !Number.isInteger(maxResults) || maxResults < 0) {\n throw new EngramAccessInputError(\n `maxResults must be a non-negative integer (got ${JSON.stringify(maxResults)})`,\n );\n }\n}\n\nexport function assertMemorySearchMode(mode: unknown): void {\n if (mode === undefined) return;\n if (typeof mode !== \"string\" || !(MEMORY_SEARCH_MODES as readonly string[]).includes(mode)) {\n throw new EngramAccessInputError(\n `mode must be one of ${MEMORY_SEARCH_MODES.join(\", \")} (got ${JSON.stringify(mode)})`,\n );\n }\n}\n\nexport function assertFlatCorpusOptions(\n mode: string | undefined,\n collection: string | undefined,\n): void {\n assertMemorySearchMode(mode);\n if (mode && collection) {\n throw new EngramAccessInputError(\n `mode is not supported together with collection on a flat corpus (got collection: ${collection})`,\n );\n }\n}\n\nexport async function runFlatCorpusMemorySearch<TResult>(options: {\n query: string;\n maxResults?: number;\n collection?: string;\n mode?: \"search\" | \"hybrid\" | \"bm25\" | \"vector\";\n searchAcrossNamespaces(params: {\n query: string;\n maxResults?: number;\n mode: \"search\" | \"hybrid\" | \"bm25\" | \"vector\";\n }): Promise<TResult[]>;\n searchGlobal(query: string, maxResults?: number): Promise<TResult[]>;\n search(query: string, collection: string | undefined, maxResults?: number): Promise<TResult[]>;\n}): Promise<TResult[]> {\n const { query, maxResults, collection, mode } = options;\n assertFlatCorpusOptions(options.mode, options.collection);\n if (mode) {\n // The mode-aware backend has no collection selector on a flat corpus, so\n // honoring the mode would silently search the default collection instead\n return options.searchAcrossNamespaces({ query, maxResults, mode });\n }\n return collection === \"global\"\n ? options.searchGlobal(query, maxResults)\n : options.search(query, collection, maxResults);\n}\n\n/**\n * How far one generic memory search may page before it stops.\n *\n * A backend-safety bound, NOT a stand-in for corpus exhaustion — which is why\n * it is ABSOLUTE rather than a multiple of the caller's budget. Scaling it to\n * the budget made \"the excluded paths happen to rank first\" indistinguishable\n * from \"there is nothing else\": a 1,000-row request whose first 4,000 hits are\n * artifacts stopped at 4,000 and returned an empty page while valid memories\n * sat at rank 4,001. Pages that come back FULL mean the corpus is not\n * exhausted, so the loop keeps going until a short page proves it is or this\n * cap protects the backend.\n */\nconst MEMORY_SEARCH_CANDIDATE_CAP = 25_000;\n\n\n/**\n * Run a ranked memory search and apply the generic-recall path exclusions\n * BEFORE the user-facing cap.\n *\n * Artifact isolation is a retrieval contract, not a ranking preference:\n * artifacts flow only through the dedicated verbatim path. Filtering after the\n * backend's own cap would let a handful of top-ranked artifacts shrink - or\n * empty - a page that has valid memories right behind them, so the search runs\n * with candidate headroom and tops up until the post-filter budget is met or\n * the corpus is exhausted.\n */\nexport async function searchWithGenericExclusion<TResult extends { path: string }>(options: {\n budget: number;\n /**\n * `false` omits `maxResults` on the FIRST request, so a caller that named no\n * budget keeps the backend's own page size on the wire; the resolved budget\n * is still what the filtered page is measured against.\n */\n sendInitialLimit: boolean;\n search(limit: number | undefined): Promise<TResult[]>;\n isExcluded(memoryPath: string): boolean;\n}): Promise<TResult[]> {\n const { budget } = options;\n if (budget <= 0) return [];\n let results: TResult[] = [];\n let limit: number | undefined = options.sendInitialLimit ? budget : undefined;\n // With no explicit limit the caller asked for the BACKEND's page, so that\n // page's size is the target — not the configured cap. Using the cap would\n // reissue the query whenever the backend's default page is smaller, and\n // return more rows than the same request used to.\n let target = budget;\n let firstPage = true;\n for (;;) {\n const raw = await options.search(limit);\n results = raw.filter((hit) => !options.isExcluded(hit.path));\n if (firstPage) {\n firstPage = false;\n if (!options.sendInitialLimit) target = Math.min(budget, raw.length);\n if (target <= 0) return [];\n }\n if (results.length >= target) break;\n // What the backend actually served this round: its own page size when we\n // named no limit.\n const served = limit ?? raw.length;\n // A short page means the corpus is exhausted - asking for more is wasted\n // work that returns the same rows.\n if (raw.length === 0 || raw.length < served) break;\n // The cap protects the backend from an unbounded walk, but it can never\n // sit at or below what the caller explicitly asked for: a request for N\n // rows needs room BEYOND N to replace the excluded hits among them, or a\n // large search returns a short page for a count the operation deliberately\n // supports.\n const cap = Math.max(MEMORY_SEARCH_CANDIDATE_CAP, target * 2);\n if (served >= cap) break;\n limit = Math.min(served * 2, cap);\n }\n return results.slice(0, target);\n}\n\n/**\n * The whole ranked memory-search path behind `POST /engram/v1/memories/search`\n * and the `memory_search` tool: pick the flat-corpus or namespace-aware\n * backend, then apply generic-recall exclusions before the caller's cap.\n */\nexport async function runScopedMemorySearch(options: {\n query: string;\n budget: number;\n /** `false` when the caller named no `maxResults`; see the helper below. */\n sendInitialLimit: boolean;\n /**\n * Authorize the scope. Runs BEFORE any budget decision so a zero budget - a\n * valid empty search - can never skip the namespace/principal gate and turn\n * an access error into a successful empty result.\n */\n authorizeScope(): Promise<void> | void;\n collection?: string;\n mode?: \"search\" | \"hybrid\" | \"bm25\" | \"vector\";\n namespacesEnabled: boolean;\n isExcluded(memoryPath: string): boolean;\n flatCorpus(\n limit: number | undefined,\n ): Promise<Array<{ path: string; score: number; snippet?: string }>>;\n namespaced(\n limit: number | undefined,\n ): Promise<Array<{ path: string; score: number; snippet?: string }>>;\n}): Promise<Array<{ path: string; score: number; snippet: string }>> {\n await options.authorizeScope();\n const results = await searchWithGenericExclusion({\n budget: options.budget,\n sendInitialLimit: options.sendInitialLimit,\n isExcluded: options.isExcluded,\n search: (limit) =>\n options.namespacesEnabled ? options.namespaced(limit) : options.flatCorpus(limit),\n });\n return results.map((hit) => ({\n path: hit.path,\n score: hit.score,\n snippet: (hit.snippet ?? \"\").slice(0, 800),\n }));\n}\n\n/** Everything the ranked memory-search surface needs from the service. */\nexport interface ScopedMemorySearchDeps {\n namespacesEnabled: boolean;\n defaultBudget: number;\n isExcluded(memoryPath: string): boolean;\n /** Flat-corpus authorization; throws when the namespace is unreadable. */\n authorizeFlatCorpus(namespace: string | undefined, principal: string | undefined): void;\n /** Namespace-aware authorization; throws, else returns the search fan-out. */\n authorizeNamespaces(\n namespace: string | undefined,\n principal: string | undefined,\n collection: string | undefined,\n ): Promise<string[]>;\n searchAcrossNamespaces(params: {\n query: string;\n namespaces?: string[];\n maxResults?: number;\n mode?: \"search\" | \"hybrid\" | \"bm25\" | \"vector\";\n }): Promise<Array<{ path: string; score: number; snippet?: string }>>;\n searchGlobal(\n query: string,\n maxResults?: number,\n ): Promise<Array<{ path: string; score: number; snippet?: string }>>;\n search(\n query: string,\n collection?: string,\n maxResults?: number,\n ): Promise<Array<{ path: string; score: number; snippet?: string }>>;\n}\n\n/** The whole `memory_search` surface: validate, authorize, search, shape. */\nexport async function memorySearchThroughScope(\n deps: ScopedMemorySearchDeps,\n request: {\n query: string;\n namespace?: string;\n maxResults?: number;\n collection?: string;\n mode?: \"search\" | \"hybrid\" | \"bm25\" | \"vector\";\n principal?: string;\n },\n): Promise<{\n query: string;\n results: Array<{ path: string; score: number; snippet: string }>;\n count: number;\n}> {\n const { query, namespace, maxResults, mode, principal } = request;\n // BOTH branches take the mode, and only the flat one reaches\n // `assertFlatCorpusOptions` — validate here so a namespaced request cannot\n // rank differently on a typo, and before any budget short-circuit.\n assertMemorySearchMode(mode);\n assertMemorySearchLimit(maxResults);\n const collection = request.collection?.trim();\n if (request.collection !== undefined && !collection) {\n throw new EngramAccessInputError(\"collection must be a non-empty string\");\n }\n let searchNamespaces: string[] = [];\n const results = await runScopedMemorySearch({\n query, collection, mode,\n namespacesEnabled: deps.namespacesEnabled,\n isExcluded: deps.isExcluded,\n budget: maxResults ?? deps.defaultBudget,\n sendInitialLimit: maxResults !== undefined,\n authorizeScope: async () => {\n if (!deps.namespacesEnabled) {\n // Validate the option combination here too: a zero budget must not be\n // a way to slip an invalid request past the check.\n assertFlatCorpusOptions(mode, collection);\n return deps.authorizeFlatCorpus(namespace, principal);\n }\n searchNamespaces = await deps.authorizeNamespaces(namespace, principal, collection);\n },\n flatCorpus: (limit) =>\n runFlatCorpusMemorySearch({\n query, maxResults: limit, collection, mode,\n searchAcrossNamespaces: (p) => deps.searchAcrossNamespaces(p),\n searchGlobal: (q, globalLimit) => deps.searchGlobal(q, globalLimit),\n search: (q, coll, searchLimit) => deps.search(q, coll, searchLimit),\n }),\n namespaced: (limit) =>\n runMemorySearchFanout({\n query, maxResults: limit, principal, collection, mode,\n requestedNamespace: namespace,\n namespaces: searchNamespaces,\n search: (p) => deps.searchAcrossNamespaces(p),\n }),\n });\n return { query, results, count: results.length };\n}\n"],"mappings":"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","names":[]}
|