@gmickel/gno 2.12.2 → 2.14.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 +36 -5
- package/assets/skill/cli-reference.md +62 -11
- package/assets/skill/examples.md +30 -4
- package/assets/skill/mcp-reference.md +16 -0
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.12.2.zip → gno-browser-clipper-v2.14.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.14.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +139 -16
- package/spec/mcp.md +111 -3
- package/spec/output-schemas/collection-list.schema.json +28 -0
- package/spec/output-schemas/convert.schema.json +97 -0
- package/spec/output-schemas/ls.schema.json +65 -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/ls.ts +20 -3
- package/src/cli/commands/profile-apply.ts +2 -1
- 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 +90 -10
- package/src/config/loader.ts +11 -1
- package/src/config/types.ts +90 -1
- package/src/core/audit-collection-rules.ts +331 -0
- package/src/core/audit-links.ts +3 -1
- package/src/core/audit-workspace.ts +69 -5
- package/src/core/collection-egress-policy-service.ts +4 -1
- package/src/core/collection-scope.ts +105 -0
- package/src/core/egress-enforcement.ts +20 -4
- package/src/core/egress-policy.ts +4 -0
- package/src/core/egress-provenance.ts +33 -11
- package/src/core/errors.ts +5 -0
- package/src/core/job-manager.ts +238 -19
- package/src/core/project-profile-apply-state.ts +6 -15
- package/src/core/project-profile-apply.ts +32 -1
- 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 +48 -5
- 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 +45 -12
- package/src/sdk/convert.ts +27 -0
- package/src/sdk/documents.ts +15 -2
- package/src/sdk/index.ts +2 -0
- package/src/sdk/types.ts +4 -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/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,
|
package/src/sdk/client.ts
CHANGED
|
@@ -110,6 +110,7 @@ import {
|
|
|
110
110
|
import { publishCapture } from "../core/capture-publish";
|
|
111
111
|
import { projectCollectionEgressPolicy } from "../core/collection-egress-policy-projection";
|
|
112
112
|
import { CollectionEgressPolicyService } from "../core/collection-egress-policy-service";
|
|
113
|
+
import { resolveCollectionScope } from "../core/collection-scope";
|
|
113
114
|
import { applyConfigChange } from "../core/config-mutation";
|
|
114
115
|
import { getDocumentCapabilities } from "../core/document-capabilities";
|
|
115
116
|
import { EgressAuditService } from "../core/egress-audit";
|
|
@@ -196,7 +197,7 @@ import {
|
|
|
196
197
|
} from "../llm/inference-scope";
|
|
197
198
|
import { LlmAdapter } from "../llm/nodeLlamaCpp/adapter";
|
|
198
199
|
import { resolveDownloadPolicy } from "../llm/policy";
|
|
199
|
-
import { resolveModelUri } from "../llm/registry";
|
|
200
|
+
import { modelCollectionForList, resolveModelUri } from "../llm/registry";
|
|
200
201
|
import {
|
|
201
202
|
answerTraceTerminalStatus,
|
|
202
203
|
generateGroundedAnswer,
|
|
@@ -476,6 +477,27 @@ class GnoClientImpl implements GnoClient {
|
|
|
476
477
|
}
|
|
477
478
|
}
|
|
478
479
|
|
|
480
|
+
/** Normalize and validate a search/vsearch/query `collections` allowlist. */
|
|
481
|
+
private withCollectionScope<
|
|
482
|
+
T extends { collection?: string; collections?: string[] },
|
|
483
|
+
>(options: T): T {
|
|
484
|
+
const scope = resolveCollectionScope(options, this.config.collections);
|
|
485
|
+
if (!scope.ok) throw sdkError("VALIDATION", scope.message);
|
|
486
|
+
return scope.collections === undefined
|
|
487
|
+
? options
|
|
488
|
+
: { ...options, collections: scope.collections };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
private modelCollection(
|
|
492
|
+
type: "embed" | "expand" | "rerank",
|
|
493
|
+
options: { collection?: string; collections?: string[] }
|
|
494
|
+
): string | undefined {
|
|
495
|
+
return (
|
|
496
|
+
options.collection ??
|
|
497
|
+
modelCollectionForList(this.config, type, options.collections)
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
479
501
|
private getCollections(collection?: string) {
|
|
480
502
|
if (!collection) {
|
|
481
503
|
return this.config.collections;
|
|
@@ -541,6 +563,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
541
563
|
answer?: boolean;
|
|
542
564
|
rerank?: boolean;
|
|
543
565
|
collection?: string;
|
|
566
|
+
collections?: string[];
|
|
544
567
|
requiredEmbed?: boolean;
|
|
545
568
|
requiredExpand?: boolean;
|
|
546
569
|
requiredAnswer?: boolean;
|
|
@@ -551,9 +574,9 @@ class GnoClientImpl implements GnoClient {
|
|
|
551
574
|
rerankModel?: string;
|
|
552
575
|
}): Promise<RuntimePorts> {
|
|
553
576
|
this.assertOpen();
|
|
554
|
-
const egressCollections =
|
|
555
|
-
|
|
556
|
-
: ("all" as const);
|
|
577
|
+
const egressCollections =
|
|
578
|
+
options.collections ??
|
|
579
|
+
(options.collection ? [options.collection] : ("all" as const));
|
|
557
580
|
|
|
558
581
|
let embedPort: EmbeddingPort | null = null;
|
|
559
582
|
let expandPort: GenerationPort | null = null;
|
|
@@ -567,7 +590,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
567
590
|
this.config,
|
|
568
591
|
"embed",
|
|
569
592
|
options.embedModel,
|
|
570
|
-
options
|
|
593
|
+
this.modelCollection("embed", options)
|
|
571
594
|
),
|
|
572
595
|
{
|
|
573
596
|
egressCollections,
|
|
@@ -612,7 +635,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
612
635
|
this.config,
|
|
613
636
|
"expand",
|
|
614
637
|
options.expandModel ?? options.genModel,
|
|
615
|
-
options
|
|
638
|
+
this.modelCollection("expand", options)
|
|
616
639
|
),
|
|
617
640
|
{
|
|
618
641
|
egressCollections,
|
|
@@ -665,7 +688,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
665
688
|
this.config,
|
|
666
689
|
"rerank",
|
|
667
690
|
options.rerankModel,
|
|
668
|
-
options
|
|
691
|
+
this.modelCollection("rerank", options)
|
|
669
692
|
),
|
|
670
693
|
{
|
|
671
694
|
egressCollections,
|
|
@@ -735,6 +758,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
735
758
|
options: GnoSearchOptions
|
|
736
759
|
): Promise<SearchResults> {
|
|
737
760
|
this.assertOpen();
|
|
761
|
+
options = this.withCollectionScope(options);
|
|
738
762
|
let traceSession: RetrievalTraceSession | null = null;
|
|
739
763
|
try {
|
|
740
764
|
const { projectHints, ...searchOptions } = options;
|
|
@@ -789,6 +813,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
789
813
|
options: GnoVectorSearchOptions
|
|
790
814
|
): Promise<SearchResults> {
|
|
791
815
|
this.assertOpen();
|
|
816
|
+
options = this.withCollectionScope(options);
|
|
792
817
|
|
|
793
818
|
let ports: RuntimePorts | null = null;
|
|
794
819
|
let traceSession: RetrievalTraceSession | null = null;
|
|
@@ -805,7 +830,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
805
830
|
this.config,
|
|
806
831
|
"embed",
|
|
807
832
|
options.model,
|
|
808
|
-
options
|
|
833
|
+
this.modelCollection("embed", options)
|
|
809
834
|
);
|
|
810
835
|
traceSession = unwrapStore(
|
|
811
836
|
await startRetrievalTraceRequest({
|
|
@@ -823,6 +848,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
823
848
|
requiredEmbed: true,
|
|
824
849
|
embedModel: options.model,
|
|
825
850
|
collection: options.collection,
|
|
851
|
+
collections: options.collections,
|
|
826
852
|
});
|
|
827
853
|
if (!ports.embedPort || !ports.vectorIndex) {
|
|
828
854
|
throw sdkError(
|
|
@@ -882,6 +908,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
882
908
|
options: GnoQueryOptions
|
|
883
909
|
): Promise<SearchResults> {
|
|
884
910
|
this.assertOpen();
|
|
911
|
+
options = this.withCollectionScope(options);
|
|
885
912
|
|
|
886
913
|
const normalizedInput = normalizeStructuredQueryInput(
|
|
887
914
|
query,
|
|
@@ -905,14 +932,14 @@ class GnoClientImpl implements GnoClient {
|
|
|
905
932
|
this.config,
|
|
906
933
|
"embed",
|
|
907
934
|
options.embedModel,
|
|
908
|
-
options
|
|
935
|
+
this.modelCollection("embed", options)
|
|
909
936
|
);
|
|
910
937
|
const expandUri = expandRequested
|
|
911
938
|
? resolveModelUri(
|
|
912
939
|
this.config,
|
|
913
940
|
"expand",
|
|
914
941
|
options.expandModel ?? options.genModel,
|
|
915
|
-
options
|
|
942
|
+
this.modelCollection("expand", options)
|
|
916
943
|
)
|
|
917
944
|
: undefined;
|
|
918
945
|
const rerankUri = rerankRequested
|
|
@@ -920,7 +947,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
920
947
|
this.config,
|
|
921
948
|
"rerank",
|
|
922
949
|
options.rerankModel,
|
|
923
|
-
options
|
|
950
|
+
this.modelCollection("rerank", options)
|
|
924
951
|
)
|
|
925
952
|
: undefined;
|
|
926
953
|
let ports: RuntimePorts | null = null;
|
|
@@ -956,6 +983,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
956
983
|
genModel: options.genModel,
|
|
957
984
|
rerankModel: options.rerankModel,
|
|
958
985
|
collection: options.collection,
|
|
986
|
+
collections: options.collections,
|
|
959
987
|
});
|
|
960
988
|
return attachRetrievalTraceMetadata(
|
|
961
989
|
this.decorateSearchResults(
|
|
@@ -1496,7 +1524,12 @@ class GnoClientImpl implements GnoClient {
|
|
|
1496
1524
|
|
|
1497
1525
|
async list(options: GnoListOptions = {}) {
|
|
1498
1526
|
this.assertOpen();
|
|
1499
|
-
const result = await listDocuments(
|
|
1527
|
+
const result = await listDocuments(
|
|
1528
|
+
this.store,
|
|
1529
|
+
options.filter === undefined
|
|
1530
|
+
? options
|
|
1531
|
+
: { ...options, filter: normalizeSdkMetadataFilter(options.filter) }
|
|
1532
|
+
);
|
|
1500
1533
|
return {
|
|
1501
1534
|
...result,
|
|
1502
1535
|
documents: result.documents.map((doc) => ({
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK conversion of one file to the Markdown GNO would index (no client,
|
|
3
|
+
* config or index needed).
|
|
4
|
+
*
|
|
5
|
+
* @module src/sdk/convert
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
type ConvertFileResult,
|
|
10
|
+
convertFile as convertOneFile,
|
|
11
|
+
} from "../ingestion/convert-file";
|
|
12
|
+
import { sdkError } from "./errors";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Convert one file to the Markdown GNO would index for it; nothing is
|
|
16
|
+
* persisted. Failures throw `GnoSdkError` with `details.reason` set to the
|
|
17
|
+
* same reason `gno convert` reports.
|
|
18
|
+
*/
|
|
19
|
+
export async function convertFile(path: string): Promise<ConvertFileResult> {
|
|
20
|
+
const result = await convertOneFile(path);
|
|
21
|
+
if (result.ok) return result.value;
|
|
22
|
+
const { reason, message, validation, details } = result.error;
|
|
23
|
+
let code: "NOT_FOUND" | "VALIDATION" | "RUNTIME" = "RUNTIME";
|
|
24
|
+
if (reason === "NOT_FOUND") code = "NOT_FOUND";
|
|
25
|
+
else if (validation) code = "VALIDATION";
|
|
26
|
+
throw sdkError(code, message, { details: { reason, ...details } });
|
|
27
|
+
}
|
package/src/sdk/documents.ts
CHANGED
|
@@ -30,6 +30,10 @@ import {
|
|
|
30
30
|
evidenceFromExactDocument,
|
|
31
31
|
RetrievalTraceSession,
|
|
32
32
|
} from "../core/retrieval-trace-session";
|
|
33
|
+
import {
|
|
34
|
+
filterDocumentsByMetadata,
|
|
35
|
+
metadataCoverageWarnings,
|
|
36
|
+
} from "../pipeline/filters";
|
|
33
37
|
import { supersedingUri } from "../pipeline/result-context";
|
|
34
38
|
import { sdkError } from "./errors";
|
|
35
39
|
|
|
@@ -388,11 +392,19 @@ export async function listDocuments(
|
|
|
388
392
|
});
|
|
389
393
|
}
|
|
390
394
|
|
|
391
|
-
const
|
|
395
|
+
const inScope = docsResult.value
|
|
392
396
|
.filter((d) => d.active)
|
|
393
397
|
.filter((d) =>
|
|
394
398
|
!scope?.startsWith("gno://") ? true : d.uri.startsWith(scope)
|
|
395
|
-
)
|
|
399
|
+
);
|
|
400
|
+
const filtered = options.filter
|
|
401
|
+
? filterDocumentsByMetadata(inScope, options.filter)
|
|
402
|
+
: undefined;
|
|
403
|
+
const warnings = filtered
|
|
404
|
+
? metadataCoverageWarnings(filtered, "listing")
|
|
405
|
+
: undefined;
|
|
406
|
+
|
|
407
|
+
const allActive = (filtered?.matches ?? inScope)
|
|
396
408
|
.map((d) => ({
|
|
397
409
|
docid: d.docid,
|
|
398
410
|
uri: d.uri,
|
|
@@ -415,6 +427,7 @@ export async function listDocuments(
|
|
|
415
427
|
total: allActive.length,
|
|
416
428
|
returned: paged.length,
|
|
417
429
|
offset,
|
|
430
|
+
...(warnings ? { warnings } : {}),
|
|
418
431
|
},
|
|
419
432
|
};
|
|
420
433
|
}
|
package/src/sdk/index.ts
CHANGED
|
@@ -27,6 +27,8 @@ export type { IndexStatus } from "../store/types";
|
|
|
27
27
|
|
|
28
28
|
export { GnoSdkError, sdkError } from "./errors";
|
|
29
29
|
export { createGnoClient } from "./client";
|
|
30
|
+
export { convertFile } from "./convert";
|
|
31
|
+
export type { ConvertFileResult } from "../ingestion/convert-file";
|
|
30
32
|
export type {
|
|
31
33
|
DocumentSection,
|
|
32
34
|
GnoAskOptions,
|
package/src/sdk/types.ts
CHANGED
|
@@ -89,6 +89,7 @@ import type {
|
|
|
89
89
|
SectionTargetResolveResult,
|
|
90
90
|
SectionTargetV1,
|
|
91
91
|
} from "../core/sections";
|
|
92
|
+
import type { MetadataPredicate } from "../core/typed-metadata";
|
|
92
93
|
import type { SyncResult } from "../ingestion";
|
|
93
94
|
import type { CollectionRootError } from "../ingestion/source-availability/root";
|
|
94
95
|
import type { DownloadPolicy } from "../llm/policy";
|
|
@@ -179,7 +180,7 @@ export type GnoQueryOptions = Omit<
|
|
|
179
180
|
GnoProjectHintOptions;
|
|
180
181
|
export type GnoAskOptions = Omit<
|
|
181
182
|
AskOptions,
|
|
182
|
-
"contentTypeRules" | "projectAffinity"
|
|
183
|
+
"collections" | "contentTypeRules" | "projectAffinity"
|
|
183
184
|
> &
|
|
184
185
|
GnoModelOverrides &
|
|
185
186
|
GnoProjectHintOptions;
|
|
@@ -223,6 +224,8 @@ export interface GnoListOptions {
|
|
|
223
224
|
scope?: string;
|
|
224
225
|
limit?: number;
|
|
225
226
|
offset?: number;
|
|
227
|
+
/** Typed metadata predicate, as for search `filter`. */
|
|
228
|
+
filter?: MetadataPredicate;
|
|
226
229
|
}
|
|
227
230
|
|
|
228
231
|
export interface GnoUpdateOptions {
|
package/src/serve/jobs.ts
CHANGED
|
@@ -16,11 +16,13 @@ export interface JobProgress {
|
|
|
16
16
|
export interface JobStatus {
|
|
17
17
|
id: string;
|
|
18
18
|
type: JobType;
|
|
19
|
-
status: "running" | "completed" | "failed";
|
|
19
|
+
status: "queued" | "running" | "completed" | "failed";
|
|
20
20
|
createdAt: number;
|
|
21
21
|
progress?: JobProgress;
|
|
22
22
|
result?: SyncResult;
|
|
23
23
|
error?: string;
|
|
24
|
+
/** Reindex jobs: the collection (null = all) and merged paths (null = whole). */
|
|
25
|
+
scope?: JobRecord["scope"];
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export interface StartJobSuccess {
|
|
@@ -49,6 +51,7 @@ const fromResidentRecord = (record: JobRecord): JobStatus => ({
|
|
|
49
51
|
progress: record.progress,
|
|
50
52
|
result: record.result,
|
|
51
53
|
error: record.error,
|
|
54
|
+
...(record.scope ? { scope: record.scope } : {}),
|
|
52
55
|
});
|
|
53
56
|
|
|
54
57
|
function cleanupExpiredJobs(now = Date.now()): void {
|
|
@@ -24,7 +24,7 @@ import { Badge } from "./ui/badge";
|
|
|
24
24
|
export interface JobStatus {
|
|
25
25
|
id: string;
|
|
26
26
|
type: "add" | "sync";
|
|
27
|
-
status: "running" | "completed" | "failed";
|
|
27
|
+
status: "queued" | "running" | "completed" | "failed";
|
|
28
28
|
createdAt: number;
|
|
29
29
|
result?: SyncResult;
|
|
30
30
|
error?: string;
|
|
@@ -138,7 +138,7 @@ export function IndexingProgress({
|
|
|
138
138
|
setStatus(data);
|
|
139
139
|
setFetchError(null);
|
|
140
140
|
|
|
141
|
-
if (data.status === "running") {
|
|
141
|
+
if (data.status === "queued" || data.status === "running") {
|
|
142
142
|
pollTimeout = setTimeout(() => {
|
|
143
143
|
void poll();
|
|
144
144
|
}, 1000);
|
|
@@ -187,6 +187,20 @@ export function IndexingProgress({
|
|
|
187
187
|
|
|
188
188
|
// Compact mode
|
|
189
189
|
if (compact) {
|
|
190
|
+
if (status?.status === "queued") {
|
|
191
|
+
return (
|
|
192
|
+
<div
|
|
193
|
+
aria-live="polite"
|
|
194
|
+
className={cn("flex items-center gap-2 text-sm", className)}
|
|
195
|
+
>
|
|
196
|
+
<Loader2Icon className="size-4 animate-spin text-muted-foreground" />
|
|
197
|
+
<span className="text-muted-foreground">
|
|
198
|
+
Queued... {formatElapsed(elapsed)}
|
|
199
|
+
</span>
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
190
204
|
if (status?.status === "running") {
|
|
191
205
|
return (
|
|
192
206
|
<div
|
|
@@ -237,6 +251,14 @@ export function IndexingProgress({
|
|
|
237
251
|
{/* Status header */}
|
|
238
252
|
<div className="flex items-center justify-between">
|
|
239
253
|
<div className="flex items-center gap-2">
|
|
254
|
+
{status?.status === "queued" && (
|
|
255
|
+
<>
|
|
256
|
+
<Loader2Icon className="size-5 animate-spin text-muted-foreground" />
|
|
257
|
+
<span className="font-medium">
|
|
258
|
+
Queued behind another indexing job
|
|
259
|
+
</span>
|
|
260
|
+
</>
|
|
261
|
+
)}
|
|
240
262
|
{status?.status === "running" && (
|
|
241
263
|
<>
|
|
242
264
|
<Loader2Icon className="size-5 animate-spin text-primary" />
|
|
@@ -264,7 +286,7 @@ export function IndexingProgress({
|
|
|
264
286
|
<div className="flex items-center gap-1.5 text-muted-foreground text-sm">
|
|
265
287
|
<TimerIcon className="size-4" />
|
|
266
288
|
<span>
|
|
267
|
-
{status?.status === "running"
|
|
289
|
+
{status?.status === "queued" || status?.status === "running"
|
|
268
290
|
? formatElapsed(elapsed)
|
|
269
291
|
: formatDuration(status?.result?.totalDurationMs ?? elapsed)}
|
|
270
292
|
</span>
|