@gmickel/gno 2.12.2 → 2.13.0
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/README.md +1 -1
- package/assets/skill/SKILL.md +1 -1
- package/assets/skill/cli-reference.md +23 -10
- package/assets/skill/mcp-reference.md +2 -0
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.12.2.zip → gno-browser-clipper-v2.13.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.13.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +66 -5
- package/spec/mcp.md +96 -1
- package/spec/output-schemas/convert.schema.json +97 -0
- package/spec/output-schemas/mcp-job-status.schema.json +17 -1
- package/spec/output-schemas/mcp-sync-result.schema.json +9 -2
- package/spec/output-schemas/search-results.schema.json +7 -0
- package/src/cli/commands/index-cmd.ts +50 -12
- package/src/cli/commands/query.ts +21 -8
- package/src/cli/commands/search.ts +11 -0
- package/src/cli/commands/vsearch.ts +17 -4
- package/src/cli/options.ts +19 -0
- package/src/cli/program.ts +79 -10
- package/src/core/collection-scope.ts +105 -0
- package/src/core/errors.ts +5 -0
- package/src/core/job-manager.ts +238 -19
- package/src/core/reindex-paths.ts +167 -0
- package/src/core/retrieval-trace-request.ts +1 -0
- package/src/ingestion/convert-file.ts +181 -0
- package/src/llm/registry.ts +20 -0
- package/src/mcp/http-egress.ts +21 -2
- package/src/mcp/tools/collection-scope.ts +25 -0
- package/src/mcp/tools/index-cmd.ts +66 -48
- package/src/mcp/tools/index.ts +74 -10
- package/src/mcp/tools/job-status.ts +7 -0
- package/src/mcp/tools/query.ts +28 -7
- package/src/mcp/tools/search.ts +4 -0
- package/src/mcp/tools/sync.ts +68 -60
- package/src/mcp/tools/vsearch.ts +9 -3
- package/src/pipeline/filters.ts +18 -3
- package/src/pipeline/graph-retrieval.ts +1 -0
- package/src/pipeline/hybrid.ts +15 -2
- package/src/pipeline/hydration.ts +7 -2
- package/src/pipeline/owner-fusion.ts +1 -0
- package/src/pipeline/search.ts +2 -0
- package/src/pipeline/types.ts +7 -0
- package/src/pipeline/vsearch.ts +10 -5
- package/src/sdk/client.ts +39 -11
- package/src/sdk/convert.ts +27 -0
- package/src/sdk/index.ts +2 -0
- package/src/sdk/types.ts +1 -1
- package/src/serve/jobs.ts +4 -1
- package/src/serve/public/components/IndexingProgress.tsx +25 -3
- package/src/serve/routes/api.ts +164 -24
- package/src/store/sqlite/adapter.ts +97 -27
- package/src/store/sqlite/eligibility.ts +4 -0
- package/src/store/types.ts +7 -0
- package/src/store/vector/sqlite-vec.ts +67 -15
- package/src/store/vector/variant-search.ts +41 -4
- package/browser-extension/artifacts/gno-browser-clipper-v2.12.2.zip.sha256 +0 -1
package/src/mcp/tools/query.ts
CHANGED
|
@@ -44,17 +44,23 @@ import {
|
|
|
44
44
|
} from "../../core/typed-metadata";
|
|
45
45
|
import { LlmAdapter } from "../../llm/nodeLlamaCpp/adapter";
|
|
46
46
|
import { resolveDownloadPolicy } from "../../llm/policy";
|
|
47
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
getActivePreset,
|
|
49
|
+
modelCollectionForList,
|
|
50
|
+
resolveModelUri,
|
|
51
|
+
} from "../../llm/registry";
|
|
48
52
|
import { diagnoseQueryTarget } from "../../pipeline/diagnose";
|
|
49
53
|
import { type HybridSearchDeps, searchHybrid } from "../../pipeline/hybrid";
|
|
50
54
|
import { createLazyVectorIndex } from "../../store/vector/lazy";
|
|
51
55
|
import { appendRetrievalWarnings } from "../retrieval-warnings";
|
|
56
|
+
import { requestCollections } from "./collection-scope";
|
|
52
57
|
import { normalizeTagFilters, runTool, type ToolResult } from "./index";
|
|
53
58
|
|
|
54
59
|
interface QueryInput {
|
|
55
60
|
query: string;
|
|
56
61
|
projectHints?: string[];
|
|
57
62
|
collection?: string;
|
|
63
|
+
collections?: string[];
|
|
58
64
|
limit?: number;
|
|
59
65
|
minScore?: number;
|
|
60
66
|
lang?: string;
|
|
@@ -198,6 +204,7 @@ export function handleQuery(
|
|
|
198
204
|
throw new Error(`Collection not found: ${args.collection}`);
|
|
199
205
|
}
|
|
200
206
|
}
|
|
207
|
+
const collections = requestCollections(args, ctx);
|
|
201
208
|
|
|
202
209
|
const normalizedInput = normalizeStructuredQueryInput(
|
|
203
210
|
args.query,
|
|
@@ -219,11 +226,14 @@ export function handleQuery(
|
|
|
219
226
|
let rerankPort: RerankPort | null = null;
|
|
220
227
|
let vectorIndex: VectorIndexPort | null = null;
|
|
221
228
|
let traceSession: RetrievalTraceSession | undefined;
|
|
229
|
+
const modelCollection = (type: "embed" | "expand" | "rerank") =>
|
|
230
|
+
args.collection ??
|
|
231
|
+
modelCollectionForList(ctx.config, type, collections);
|
|
222
232
|
const embedUri = resolveModelUri(
|
|
223
233
|
ctx.config,
|
|
224
234
|
"embed",
|
|
225
235
|
undefined,
|
|
226
|
-
|
|
236
|
+
modelCollection("embed")
|
|
227
237
|
);
|
|
228
238
|
const hasStructuredModes = Boolean(queryModes?.length);
|
|
229
239
|
const depthPolicy = resolveDepthPolicy({
|
|
@@ -242,15 +252,26 @@ export function handleQuery(
|
|
|
242
252
|
);
|
|
243
253
|
const expandUri =
|
|
244
254
|
!noExpand && !hasStructuredModes
|
|
245
|
-
? resolveModelUri(
|
|
255
|
+
? resolveModelUri(
|
|
256
|
+
ctx.config,
|
|
257
|
+
"expand",
|
|
258
|
+
undefined,
|
|
259
|
+
modelCollection("expand")
|
|
260
|
+
)
|
|
246
261
|
: undefined;
|
|
247
262
|
const rerankUri = !noRerank
|
|
248
|
-
? resolveModelUri(
|
|
263
|
+
? resolveModelUri(
|
|
264
|
+
ctx.config,
|
|
265
|
+
"rerank",
|
|
266
|
+
undefined,
|
|
267
|
+
modelCollection("rerank")
|
|
268
|
+
)
|
|
249
269
|
: undefined;
|
|
250
270
|
const options = {
|
|
251
271
|
limit: args.limit ?? 5,
|
|
252
272
|
minScore: args.minScore,
|
|
253
273
|
collection: args.collection,
|
|
274
|
+
collections,
|
|
254
275
|
queryLanguageHint: args.lang,
|
|
255
276
|
intent: args.intent,
|
|
256
277
|
candidateLimit: depthPolicy.candidateLimit,
|
|
@@ -289,9 +310,9 @@ export function handleQuery(
|
|
|
289
310
|
if (!traceStart.ok) throw new Error(traceStart.error.message);
|
|
290
311
|
traceSession = traceStart.value ?? undefined;
|
|
291
312
|
const llm = ctx.getModelAdapter?.() ?? new LlmAdapter(ctx.config);
|
|
292
|
-
const egressCollections =
|
|
293
|
-
|
|
294
|
-
: ("all" as const);
|
|
313
|
+
const egressCollections =
|
|
314
|
+
collections ??
|
|
315
|
+
(args.collection ? [args.collection] : ("all" as const));
|
|
295
316
|
|
|
296
317
|
// Resolve download policy from env (MCP has no CLI flags)
|
|
297
318
|
const policy = resolveDownloadPolicy(process.env, {});
|
package/src/mcp/tools/search.ts
CHANGED
|
@@ -25,12 +25,14 @@ import {
|
|
|
25
25
|
} from "../../core/typed-metadata";
|
|
26
26
|
import { searchBm25 } from "../../pipeline/search";
|
|
27
27
|
import { appendRetrievalWarnings } from "../retrieval-warnings";
|
|
28
|
+
import { requestCollections } from "./collection-scope";
|
|
28
29
|
import { normalizeTagFilters, runTool, type ToolResult } from "./index";
|
|
29
30
|
|
|
30
31
|
interface SearchInput {
|
|
31
32
|
query: string;
|
|
32
33
|
projectHints?: string[];
|
|
33
34
|
collection?: string;
|
|
35
|
+
collections?: string[];
|
|
34
36
|
limit?: number;
|
|
35
37
|
minScore?: number;
|
|
36
38
|
lang?: string;
|
|
@@ -123,6 +125,7 @@ export function handleSearch(
|
|
|
123
125
|
throw new Error(`Collection not found: ${args.collection}`);
|
|
124
126
|
}
|
|
125
127
|
}
|
|
128
|
+
const collections = requestCollections(args, ctx);
|
|
126
129
|
|
|
127
130
|
const projectAffinity = await resolveRemoteProjectAffinity(
|
|
128
131
|
ctx.config,
|
|
@@ -132,6 +135,7 @@ export function handleSearch(
|
|
|
132
135
|
limit: args.limit ?? 5,
|
|
133
136
|
minScore: args.minScore,
|
|
134
137
|
collection: args.collection,
|
|
138
|
+
collections,
|
|
135
139
|
lang: args.lang,
|
|
136
140
|
intent: args.intent,
|
|
137
141
|
exclude: args.exclude,
|
package/src/mcp/tools/sync.ts
CHANGED
|
@@ -9,9 +9,9 @@ import type { CollectionSyncResult, SyncResult } from "../../ingestion";
|
|
|
9
9
|
import type { ToolContext } from "../server";
|
|
10
10
|
|
|
11
11
|
import { MCP_ERRORS } from "../../core/errors";
|
|
12
|
-
import { acquireWriteLock, type WriteLockHandle } from "../../core/file-lock";
|
|
13
12
|
import { JobError } from "../../core/job-manager";
|
|
14
13
|
import { recordContentMutation } from "../../core/mutation-generations";
|
|
14
|
+
import { resolveReindexScope } from "../../core/reindex-paths";
|
|
15
15
|
import { normalizeCollectionName } from "../../core/validation";
|
|
16
16
|
import { defaultSyncService, withContentTypeRules } from "../../ingestion";
|
|
17
17
|
import { enforceSyncCommandEgress } from "../sync-egress";
|
|
@@ -21,12 +21,14 @@ interface SyncInput {
|
|
|
21
21
|
collection?: string;
|
|
22
22
|
gitPull?: boolean;
|
|
23
23
|
runUpdateCmd?: boolean;
|
|
24
|
+
paths?: string[];
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
interface SyncResultOutput {
|
|
27
28
|
jobId: string;
|
|
28
29
|
collections: string[];
|
|
29
|
-
status: "started";
|
|
30
|
+
status: "started" | "queued";
|
|
31
|
+
paths?: string[];
|
|
30
32
|
options: {
|
|
31
33
|
gitPull: boolean;
|
|
32
34
|
runUpdateCmd: boolean;
|
|
@@ -38,6 +40,7 @@ function formatSyncResult(result: SyncResultOutput): string {
|
|
|
38
40
|
lines.push(`Job: ${result.jobId}`);
|
|
39
41
|
lines.push(`Status: ${result.status}`);
|
|
40
42
|
lines.push(`Collections: ${result.collections.join(", ") || "(none)"}`);
|
|
43
|
+
if (result.paths) lines.push(`Paths: ${result.paths.join(", ")}`);
|
|
41
44
|
lines.push(`Git pull: ${result.options.gitPull ? "yes" : "no"}`);
|
|
42
45
|
lines.push(`Run update cmd: ${result.options.runUpdateCmd ? "yes" : "no"}`);
|
|
43
46
|
return lines.join("\n");
|
|
@@ -77,73 +80,82 @@ export function handleSync(
|
|
|
77
80
|
throw new Error("Write tools disabled. Start MCP with --enable-write.");
|
|
78
81
|
}
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
const requestedCollection = args.collection?.trim();
|
|
84
|
+
const collection = requestedCollection
|
|
85
|
+
? resolveCollection(requestedCollection, ctx.collections)
|
|
86
|
+
: null;
|
|
82
87
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
throw new Error(
|
|
87
|
-
`${MCP_ERRORS.LOCKED.code}: ${MCP_ERRORS.LOCKED.message}`
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const requestedCollection = args.collection?.trim();
|
|
92
|
-
const collection = requestedCollection
|
|
93
|
-
? resolveCollection(requestedCollection, ctx.collections)
|
|
94
|
-
: null;
|
|
95
|
-
|
|
96
|
-
if (requestedCollection && !collection) {
|
|
97
|
-
throw new Error(
|
|
98
|
-
`${MCP_ERRORS.NOT_FOUND.code}: Collection not found: ${requestedCollection}`
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const collections = collection
|
|
103
|
-
? [collection.name]
|
|
104
|
-
: ctx.collections.map((entry) => entry.name);
|
|
105
|
-
|
|
106
|
-
const options = withContentTypeRules(
|
|
107
|
-
{
|
|
108
|
-
gitPull: args.gitPull ?? false,
|
|
109
|
-
runUpdateCmd: args.runUpdateCmd ?? false,
|
|
110
|
-
},
|
|
111
|
-
ctx.config
|
|
88
|
+
if (requestedCollection && !collection) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`${MCP_ERRORS.NOT_FOUND.code}: Collection not found: ${requestedCollection}`
|
|
112
91
|
);
|
|
113
|
-
|
|
114
|
-
collectionNames: collections,
|
|
115
|
-
gitPull: options.gitPull,
|
|
116
|
-
runUpdateCmd: options.runUpdateCmd,
|
|
117
|
-
});
|
|
92
|
+
}
|
|
118
93
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
94
|
+
const collections = collection
|
|
95
|
+
? [collection.name]
|
|
96
|
+
: ctx.collections.map((entry) => entry.name);
|
|
97
|
+
|
|
98
|
+
const options = withContentTypeRules(
|
|
99
|
+
{
|
|
100
|
+
gitPull: args.gitPull ?? false,
|
|
101
|
+
runUpdateCmd: args.runUpdateCmd ?? false,
|
|
102
|
+
},
|
|
103
|
+
ctx.config
|
|
104
|
+
);
|
|
105
|
+
const paths = await resolveReindexScope(collection, args.paths, options);
|
|
106
|
+
enforceSyncCommandEgress(ctx, {
|
|
107
|
+
collectionNames: collections,
|
|
108
|
+
gitPull: options.gitPull,
|
|
109
|
+
runUpdateCmd: options.runUpdateCmd,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Captured now: a queued pass runs after this call's context is gone.
|
|
113
|
+
const config = ctx.config;
|
|
114
|
+
const allCollections = ctx.collections;
|
|
115
|
+
try {
|
|
116
|
+
const accepted = await ctx.jobManager.submitReindex({
|
|
117
|
+
type: "sync",
|
|
118
|
+
collection: collection?.name ?? null,
|
|
119
|
+
paths,
|
|
120
|
+
options: {
|
|
121
|
+
gitPull: options.gitPull ?? false,
|
|
122
|
+
runUpdateCmd: options.runUpdateCmd ?? false,
|
|
123
|
+
},
|
|
124
|
+
run: async (scope) => {
|
|
125
|
+
const passOptions = withContentTypeRules(
|
|
126
|
+
{ ...scope.options },
|
|
127
|
+
config
|
|
128
|
+
);
|
|
123
129
|
const result = collection
|
|
124
130
|
? wrapSingleSync(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
131
|
+
scope.paths
|
|
132
|
+
? await defaultSyncService.syncPaths(
|
|
133
|
+
collection,
|
|
134
|
+
ctx.store,
|
|
135
|
+
scope.paths,
|
|
136
|
+
passOptions
|
|
137
|
+
)
|
|
138
|
+
: await defaultSyncService.syncCollection(
|
|
139
|
+
collection,
|
|
140
|
+
ctx.store,
|
|
141
|
+
passOptions
|
|
142
|
+
)
|
|
130
143
|
)
|
|
131
144
|
: await defaultSyncService.syncAll(
|
|
132
|
-
|
|
145
|
+
allCollections,
|
|
133
146
|
ctx.store,
|
|
134
|
-
|
|
147
|
+
passOptions
|
|
135
148
|
);
|
|
136
149
|
recordContentMutation(result, ctx.markContentMutation);
|
|
137
|
-
return result;
|
|
138
|
-
}
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
handedOff = true;
|
|
150
|
+
return { kind: "sync" as const, value: result };
|
|
151
|
+
},
|
|
152
|
+
});
|
|
142
153
|
|
|
143
154
|
const result: SyncResultOutput = {
|
|
144
|
-
jobId,
|
|
155
|
+
jobId: accepted.jobId,
|
|
145
156
|
collections,
|
|
146
|
-
status: "started",
|
|
157
|
+
status: accepted.status === "running" ? "started" : "queued",
|
|
158
|
+
...(paths ? { paths } : {}),
|
|
147
159
|
options: {
|
|
148
160
|
gitPull: options.gitPull ?? false,
|
|
149
161
|
runUpdateCmd: options.runUpdateCmd ?? false,
|
|
@@ -156,10 +168,6 @@ export function handleSync(
|
|
|
156
168
|
throw new Error(`${error.code}: ${error.message}`);
|
|
157
169
|
}
|
|
158
170
|
throw error;
|
|
159
|
-
} finally {
|
|
160
|
-
if (lock && !handedOff) {
|
|
161
|
-
await lock.release();
|
|
162
|
-
}
|
|
163
171
|
}
|
|
164
172
|
},
|
|
165
173
|
formatSyncResult
|
package/src/mcp/tools/vsearch.ts
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
} from "../../core/typed-metadata";
|
|
29
29
|
import { LlmAdapter } from "../../llm/nodeLlamaCpp/adapter";
|
|
30
30
|
import { resolveDownloadPolicy } from "../../llm/policy";
|
|
31
|
-
import { resolveModelUri } from "../../llm/registry";
|
|
31
|
+
import { modelCollectionForList, resolveModelUri } from "../../llm/registry";
|
|
32
32
|
import { formatQueryForEmbedding } from "../../pipeline/contextual";
|
|
33
33
|
import {
|
|
34
34
|
searchVectorWithEmbedding,
|
|
@@ -36,12 +36,14 @@ import {
|
|
|
36
36
|
} from "../../pipeline/vsearch";
|
|
37
37
|
import { createVectorIndexPort } from "../../store/vector";
|
|
38
38
|
import { appendRetrievalWarnings } from "../retrieval-warnings";
|
|
39
|
+
import { requestCollections } from "./collection-scope";
|
|
39
40
|
import { normalizeTagFilters, runTool, type ToolResult } from "./index";
|
|
40
41
|
|
|
41
42
|
interface VsearchInput {
|
|
42
43
|
query: string;
|
|
43
44
|
projectHints?: string[];
|
|
44
45
|
collection?: string;
|
|
46
|
+
collections?: string[];
|
|
45
47
|
limit?: number;
|
|
46
48
|
minScore?: number;
|
|
47
49
|
lang?: string;
|
|
@@ -137,13 +139,15 @@ export function handleVsearch(
|
|
|
137
139
|
throw new Error(`Collection not found: ${args.collection}`);
|
|
138
140
|
}
|
|
139
141
|
}
|
|
142
|
+
const collections = requestCollections(args, ctx);
|
|
140
143
|
|
|
141
144
|
// Get model from active preset
|
|
142
145
|
const modelUri = resolveModelUri(
|
|
143
146
|
ctx.config,
|
|
144
147
|
"embed",
|
|
145
148
|
undefined,
|
|
146
|
-
args.collection
|
|
149
|
+
args.collection ??
|
|
150
|
+
modelCollectionForList(ctx.config, "embed", collections)
|
|
147
151
|
);
|
|
148
152
|
const projectAffinity = await resolveRemoteProjectAffinity(
|
|
149
153
|
ctx.config,
|
|
@@ -153,6 +157,7 @@ export function handleVsearch(
|
|
|
153
157
|
limit: args.limit ?? 5,
|
|
154
158
|
minScore: args.minScore,
|
|
155
159
|
collection: args.collection,
|
|
160
|
+
collections,
|
|
156
161
|
intent: args.intent,
|
|
157
162
|
exclude: args.exclude,
|
|
158
163
|
since: args.since,
|
|
@@ -192,7 +197,8 @@ export function handleVsearch(
|
|
|
192
197
|
// Create LLM adapter for embeddings
|
|
193
198
|
const llm = ctx.getModelAdapter?.() ?? new LlmAdapter(ctx.config);
|
|
194
199
|
const embedResult = await llm.createEmbeddingPort(modelUri, {
|
|
195
|
-
egressCollections:
|
|
200
|
+
egressCollections:
|
|
201
|
+
collections ?? (args.collection ? [args.collection] : "all"),
|
|
196
202
|
policy,
|
|
197
203
|
onProgress: (progress) => downloadProgress("embed", progress),
|
|
198
204
|
});
|
package/src/pipeline/filters.ts
CHANGED
|
@@ -25,13 +25,27 @@ export interface QueryFilterEvaluation {
|
|
|
25
25
|
* not replace complete ownership lineage or change ranking/deduplication.
|
|
26
26
|
* Managed-memory scopes/supersession must already be enforced by the store.
|
|
27
27
|
*/
|
|
28
|
+
/** True when `collection` falls outside the request's collection scope. */
|
|
29
|
+
function outsideCollectionScope(
|
|
30
|
+
scope: Pick<HybridSearchOptions, "collection" | "collections">,
|
|
31
|
+
collection: string
|
|
32
|
+
): boolean {
|
|
33
|
+
if (scope.collection && collection !== scope.collection) return true;
|
|
34
|
+
return (
|
|
35
|
+
scope.collections !== undefined && !scope.collections.includes(collection)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
28
39
|
export async function evaluateRetrievalEligibility(
|
|
29
40
|
store: StorePort,
|
|
30
41
|
query: string,
|
|
31
42
|
doc: DocumentRow | undefined,
|
|
32
43
|
chunks: ChunkRow[] | undefined,
|
|
33
44
|
options: HybridSearchOptions,
|
|
34
|
-
callerScope?: Pick<
|
|
45
|
+
callerScope?: Pick<
|
|
46
|
+
HybridSearchOptions,
|
|
47
|
+
"collection" | "collections" | "retrievalScope"
|
|
48
|
+
>
|
|
35
49
|
): Promise<QueryFilterEvaluation & { chunks: ChunkRow[] }> {
|
|
36
50
|
if (!doc || !chunks || !doc.mirrorHash) {
|
|
37
51
|
return { matches: false, reasons: ["metadata"], chunks: [] };
|
|
@@ -40,7 +54,7 @@ export async function evaluateRetrievalEligibility(
|
|
|
40
54
|
if (!doc.active) reasons.push("inactive");
|
|
41
55
|
const sourcePath = doc.recordSourcePath ?? doc.relPath;
|
|
42
56
|
for (const scope of [callerScope, options]) {
|
|
43
|
-
if (scope
|
|
57
|
+
if (scope && outsideCollectionScope(scope, doc.collection)) {
|
|
44
58
|
reasons.push("collection");
|
|
45
59
|
}
|
|
46
60
|
if (scope?.retrievalScope) {
|
|
@@ -98,7 +112,7 @@ export function evaluateDocumentChunkFilters(
|
|
|
98
112
|
options.until
|
|
99
113
|
);
|
|
100
114
|
|
|
101
|
-
if (options
|
|
115
|
+
if (outsideCollectionScope(options, doc.collection)) {
|
|
102
116
|
reasons.push("collection");
|
|
103
117
|
}
|
|
104
118
|
if (!isWithinTemporalRange(doc.sourceMtime, temporalRange)) {
|
|
@@ -219,6 +233,7 @@ export async function typedMetadataWarnings(
|
|
|
219
233
|
const range = resolveTemporalRange(query, options.since, options.until);
|
|
220
234
|
const result = await store.getTypedMetadataCoverage({
|
|
221
235
|
collection: options.collection,
|
|
236
|
+
collections: options.collections,
|
|
222
237
|
relPathPrefix: options.retrievalScope?.relPathPrefix,
|
|
223
238
|
allowedMirrorHashes: options.retrievalScope?.allowedMirrorHashes,
|
|
224
239
|
chunkLanguage: options.lang,
|
package/src/pipeline/hybrid.ts
CHANGED
|
@@ -150,6 +150,7 @@ async function checkBm25Strength(
|
|
|
150
150
|
query: string,
|
|
151
151
|
options?: {
|
|
152
152
|
collection?: string;
|
|
153
|
+
collections?: string[];
|
|
153
154
|
lang?: string;
|
|
154
155
|
tagsAll?: string[];
|
|
155
156
|
tagsAny?: string[];
|
|
@@ -168,6 +169,7 @@ async function checkBm25Strength(
|
|
|
168
169
|
const result = await store.searchFts(query, {
|
|
169
170
|
limit: 5,
|
|
170
171
|
collection: options?.collection,
|
|
172
|
+
collections: options?.collections,
|
|
171
173
|
relPathPrefix: options?.relPathPrefix,
|
|
172
174
|
language: options?.lang,
|
|
173
175
|
chunkLanguage: options?.lang,
|
|
@@ -225,6 +227,7 @@ async function searchFtsChunks(
|
|
|
225
227
|
options: {
|
|
226
228
|
limit: number;
|
|
227
229
|
collection?: string;
|
|
230
|
+
collections?: string[];
|
|
228
231
|
lang?: string;
|
|
229
232
|
tagsAll?: string[];
|
|
230
233
|
tagsAny?: string[];
|
|
@@ -243,6 +246,7 @@ async function searchFtsChunks(
|
|
|
243
246
|
const result = await store.searchFts(query, {
|
|
244
247
|
limit: options.limit,
|
|
245
248
|
collection: options.collection,
|
|
249
|
+
collections: options.collections,
|
|
246
250
|
relPathPrefix: options.relPathPrefix,
|
|
247
251
|
language: options.lang,
|
|
248
252
|
chunkLanguage: options.lang,
|
|
@@ -500,6 +504,7 @@ async function searchHybridWithHydration(
|
|
|
500
504
|
? false
|
|
501
505
|
: await checkBm25Strength(store, query, {
|
|
502
506
|
collection: options.collection,
|
|
507
|
+
collections: options.collections,
|
|
503
508
|
lang: options.lang,
|
|
504
509
|
memoryScopesAny: options.memoryFilter?.scopes,
|
|
505
510
|
excludeSuperseded: options.memoryFilter?.excludeSuperseded,
|
|
@@ -550,6 +555,7 @@ async function searchHybridWithHydration(
|
|
|
550
555
|
|
|
551
556
|
const vectorEligibility: VectorSearchOptions["eligibility"] = {
|
|
552
557
|
collection: options.collection,
|
|
558
|
+
collections: options.collections,
|
|
553
559
|
memoryScopesAny: options.memoryFilter?.scopes,
|
|
554
560
|
excludeSuperseded: options.memoryFilter?.excludeSuperseded,
|
|
555
561
|
relPathPrefix: options.retrievalScope?.relPathPrefix,
|
|
@@ -575,6 +581,7 @@ async function searchHybridWithHydration(
|
|
|
575
581
|
exclude: options.exclude,
|
|
576
582
|
allowedMirrorHashes: options.retrievalScope?.allowedMirrorHashes,
|
|
577
583
|
collection: options.collection,
|
|
584
|
+
collections: options.collections,
|
|
578
585
|
lang: options.lang,
|
|
579
586
|
tagsAll: options.tagsAll,
|
|
580
587
|
tagsAny: options.tagsAny,
|
|
@@ -615,6 +622,7 @@ async function searchHybridWithHydration(
|
|
|
615
622
|
exclude: options.exclude,
|
|
616
623
|
allowedMirrorHashes: options.retrievalScope?.allowedMirrorHashes,
|
|
617
624
|
collection: options.collection,
|
|
625
|
+
collections: options.collections,
|
|
618
626
|
lang: options.lang,
|
|
619
627
|
tagsAll: options.tagsAll,
|
|
620
628
|
tagsAny: options.tagsAny,
|
|
@@ -806,7 +814,7 @@ async function searchHybridWithHydration(
|
|
|
806
814
|
fusedCandidates,
|
|
807
815
|
{
|
|
808
816
|
collection: options.collection,
|
|
809
|
-
collections: options.graphCollections,
|
|
817
|
+
collections: options.graphCollections ?? options.collections,
|
|
810
818
|
includeSimilar: vectorAvailable,
|
|
811
819
|
eligibility: vectorEligibility,
|
|
812
820
|
limit,
|
|
@@ -879,6 +887,7 @@ async function searchHybridWithHydration(
|
|
|
879
887
|
[...new Set(fusedCandidates.map((candidate) => candidate.mirrorHash))],
|
|
880
888
|
{
|
|
881
889
|
collection: options.collection,
|
|
890
|
+
collections: options.collections,
|
|
882
891
|
activeOnly: true,
|
|
883
892
|
}
|
|
884
893
|
);
|
|
@@ -991,6 +1000,7 @@ async function searchHybridWithHydration(
|
|
|
991
1000
|
[...neededHashes],
|
|
992
1001
|
{
|
|
993
1002
|
collection: options.collection,
|
|
1003
|
+
collections: options.collections,
|
|
994
1004
|
activeOnly: true,
|
|
995
1005
|
}
|
|
996
1006
|
);
|
|
@@ -1374,7 +1384,9 @@ async function searchHybridWithHydration(
|
|
|
1374
1384
|
{
|
|
1375
1385
|
ownershipHashes: [...neededHashes],
|
|
1376
1386
|
ownershipDocuments:
|
|
1377
|
-
options.collection === undefined
|
|
1387
|
+
options.collection === undefined && options.collections === undefined
|
|
1388
|
+
? documents
|
|
1389
|
+
: undefined,
|
|
1378
1390
|
collections: collectionsResult.ok ? collectionsResult.value : undefined,
|
|
1379
1391
|
}
|
|
1380
1392
|
);
|
|
@@ -1394,6 +1406,7 @@ async function searchHybridWithHydration(
|
|
|
1394
1406
|
intent: options.intent,
|
|
1395
1407
|
exclude: options.exclude,
|
|
1396
1408
|
collection: options.collection,
|
|
1409
|
+
collections: options.collections,
|
|
1397
1410
|
lang: options.lang,
|
|
1398
1411
|
since: temporalRange.since,
|
|
1399
1412
|
until: temporalRange.until,
|
|
@@ -142,13 +142,18 @@ export class RequestHydration {
|
|
|
142
142
|
const store = this.store;
|
|
143
143
|
if (!store) return err("QUERY_FAILED", "Hydration request released");
|
|
144
144
|
// Capture caller-owned options before any asynchronous read. Collection
|
|
145
|
-
// and activity are part of the lookup identity; titles never key by
|
|
146
|
-
|
|
145
|
+
// scope and activity are part of the lookup identity; titles never key by
|
|
146
|
+
// hash.
|
|
147
|
+
const scope = {
|
|
148
|
+
...options,
|
|
149
|
+
collections: options?.collections ? [...options.collections] : undefined,
|
|
150
|
+
};
|
|
147
151
|
// Cache the exact lookup, retaining the adapter's ordering (including SQL
|
|
148
152
|
// batch boundaries). Regrouping by content hash would reorder documents.
|
|
149
153
|
const requested = [...hashes];
|
|
150
154
|
const identity = JSON.stringify([
|
|
151
155
|
scope.collection ?? null,
|
|
156
|
+
scope.collections ?? null,
|
|
152
157
|
scope.activeOnly ?? null,
|
|
153
158
|
requested,
|
|
154
159
|
]);
|
|
@@ -30,6 +30,7 @@ export async function resolveFusionOwners(
|
|
|
30
30
|
];
|
|
31
31
|
const documents = await hydration.getDocumentsByMirrorHashes(hashes, {
|
|
32
32
|
collection: options.collection,
|
|
33
|
+
collections: options.collections,
|
|
33
34
|
activeOnly: true,
|
|
34
35
|
});
|
|
35
36
|
const chunks = await hydration.getChunksBatch(hashes);
|
package/src/pipeline/search.ts
CHANGED
|
@@ -341,6 +341,7 @@ export async function searchBm25(
|
|
|
341
341
|
const ftsResult = await store.searchFts(query, {
|
|
342
342
|
limit: retrievalLimit,
|
|
343
343
|
collection: options.collection,
|
|
344
|
+
collections: options.collections,
|
|
344
345
|
allowedMirrorHashes: options.retrievalScope?.allowedMirrorHashes,
|
|
345
346
|
exclude: options.exclude,
|
|
346
347
|
relPathPrefix: options.retrievalScope?.relPathPrefix,
|
|
@@ -621,6 +622,7 @@ export async function searchBm25(
|
|
|
621
622
|
intent: options.intent,
|
|
622
623
|
exclude: options.exclude,
|
|
623
624
|
collection: options.collection,
|
|
625
|
+
collections: options.collections,
|
|
624
626
|
lang: options.lang,
|
|
625
627
|
since: temporalRange.since,
|
|
626
628
|
until: temporalRange.until,
|
package/src/pipeline/types.ts
CHANGED
|
@@ -114,6 +114,8 @@ export interface SearchMeta {
|
|
|
114
114
|
totalResults: number;
|
|
115
115
|
intent?: string;
|
|
116
116
|
collection?: string;
|
|
117
|
+
/** Normalized collection allowlist; present only when the request set one. */
|
|
118
|
+
collections?: string[];
|
|
117
119
|
lang?: string;
|
|
118
120
|
/** Detected/overridden query language for prompt selection (typically BCP-47; may be user-provided via --lang) */
|
|
119
121
|
queryLanguage?: string;
|
|
@@ -193,6 +195,11 @@ export interface SearchOptions extends InferenceOptions {
|
|
|
193
195
|
minScore?: number;
|
|
194
196
|
/** Filter by collection */
|
|
195
197
|
collection?: string;
|
|
198
|
+
/**
|
|
199
|
+
* Filter to documents in ANY of these collections, before candidate limits.
|
|
200
|
+
* Never combined with `collection`; surfaces reject that pairing.
|
|
201
|
+
*/
|
|
202
|
+
collections?: string[];
|
|
196
203
|
/**
|
|
197
204
|
* Internal graph allowlist for a request partitioned into one retrieval per
|
|
198
205
|
* collection (Context Capsules, replay): graph neighbours may come from any
|
package/src/pipeline/vsearch.ts
CHANGED
|
@@ -176,6 +176,7 @@ async function searchVectorWithEmbeddingOwned(
|
|
|
176
176
|
excludeMetadata: true,
|
|
177
177
|
semanticMetadata: true,
|
|
178
178
|
collection: options.collection,
|
|
179
|
+
collections: options.collections,
|
|
179
180
|
memoryScopesAny: options.memoryFilter?.scopes,
|
|
180
181
|
excludeSuperseded: options.memoryFilter?.excludeSuperseded,
|
|
181
182
|
relPathPrefix: options.retrievalScope?.relPathPrefix,
|
|
@@ -215,6 +216,7 @@ async function searchVectorWithEmbeddingOwned(
|
|
|
215
216
|
const { documents: docsByMirrorHash, ownershipDocuments } =
|
|
216
217
|
await buildDocumentMap(store, {
|
|
217
218
|
collection: options.collection,
|
|
219
|
+
collections: options.collections,
|
|
218
220
|
relPathPrefix: options.retrievalScope?.relPathPrefix,
|
|
219
221
|
tagsAll: options.tagsAll,
|
|
220
222
|
tagsAny: options.tagsAny,
|
|
@@ -547,6 +549,7 @@ async function searchVectorWithEmbeddingOwned(
|
|
|
547
549
|
intent: options.intent,
|
|
548
550
|
exclude: options.exclude,
|
|
549
551
|
collection: options.collection,
|
|
552
|
+
collections: options.collections,
|
|
550
553
|
lang: options.lang,
|
|
551
554
|
since: temporalRange.since,
|
|
552
555
|
until: temporalRange.until,
|
|
@@ -666,6 +669,7 @@ interface DocumentInfo {
|
|
|
666
669
|
|
|
667
670
|
interface DocumentMapOptions {
|
|
668
671
|
collection?: string;
|
|
672
|
+
collections?: string[];
|
|
669
673
|
relPathPrefix?: string;
|
|
670
674
|
tagsAll?: string[];
|
|
671
675
|
tagsAny?: string[];
|
|
@@ -718,11 +722,12 @@ async function buildDocumentMap(
|
|
|
718
722
|
const ownershipDocuments = options.mirrorHashes
|
|
719
723
|
? docs.value.filter((d) => d.mirrorHash)
|
|
720
724
|
: docs.value.filter((d) => d.mirrorHash && d.active);
|
|
721
|
-
const activeDocs =
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
725
|
+
const activeDocs = ownershipDocuments.filter(
|
|
726
|
+
(document) =>
|
|
727
|
+
(!options.collection || document.collection === options.collection) &&
|
|
728
|
+
(options.collections === undefined ||
|
|
729
|
+
options.collections.includes(document.collection))
|
|
730
|
+
);
|
|
726
731
|
const temporalRange: TemporalRange = {
|
|
727
732
|
since: options.since,
|
|
728
733
|
until: options.until,
|