@gmickel/gno 1.16.0 → 1.18.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.
Files changed (86) hide show
  1. package/README.md +34 -18
  2. package/assets/skill/SKILL.md +25 -6
  3. package/assets/skill/mcp-reference.md +21 -0
  4. package/package.json +2 -2
  5. package/src/app/context-agent-projection.ts +303 -0
  6. package/src/app/context-format.ts +249 -0
  7. package/src/app/context-runtime-contract.ts +325 -0
  8. package/src/app/context-runtime-input.ts +362 -0
  9. package/src/app/context-runtime-types.ts +65 -0
  10. package/src/app/context-runtime.ts +170 -0
  11. package/src/app/context-surface.ts +145 -0
  12. package/src/cli/commands/context-build.ts +149 -0
  13. package/src/cli/commands/context-verify.ts +90 -0
  14. package/src/cli/commands/daemon.ts +69 -2
  15. package/src/cli/commands/models/pull.ts +13 -3
  16. package/src/cli/commands/status.ts +2 -0
  17. package/src/cli/detach.ts +37 -20
  18. package/src/cli/options.ts +4 -0
  19. package/src/cli/program.ts +252 -27
  20. package/src/config/index.ts +3 -0
  21. package/src/config/types.ts +37 -0
  22. package/src/core/context-budget.ts +461 -0
  23. package/src/core/context-capsule-index-schema.ts +15 -0
  24. package/src/core/context-capsule-retrieval-schema.ts +81 -0
  25. package/src/core/context-capsule-schema.ts +473 -0
  26. package/src/core/context-capsule-validation.ts +416 -0
  27. package/src/core/context-capsule-verification.ts +218 -0
  28. package/src/core/context-capsule.ts +439 -0
  29. package/src/core/context-compiler.ts +513 -0
  30. package/src/core/context-evidence-metadata.ts +33 -0
  31. package/src/core/context-evidence.ts +495 -0
  32. package/src/core/context-facets.ts +163 -0
  33. package/src/core/context-guidance.ts +69 -0
  34. package/src/core/context-scope.ts +32 -0
  35. package/src/core/context-verifier-canonical.ts +90 -0
  36. package/src/core/context-verifier-input.ts +66 -0
  37. package/src/core/context-verifier.ts +447 -0
  38. package/src/core/job-manager.ts +19 -0
  39. package/src/core/mutation-generations.ts +33 -0
  40. package/src/core/sections.ts +63 -0
  41. package/src/llm/cache.ts +13 -3
  42. package/src/llm/nodeLlamaCpp/adapter.ts +10 -1
  43. package/src/llm/nodeLlamaCpp/lifecycle.ts +71 -0
  44. package/src/mcp/context.ts +161 -0
  45. package/src/mcp/http-security.ts +477 -0
  46. package/src/mcp/http-session.ts +272 -0
  47. package/src/mcp/http-transport.ts +370 -0
  48. package/src/mcp/resources/index.ts +141 -134
  49. package/src/mcp/server.ts +28 -82
  50. package/src/mcp/tools/add-collection.ts +3 -1
  51. package/src/mcp/tools/capture.ts +3 -0
  52. package/src/mcp/tools/clear-collection-embeddings.ts +2 -0
  53. package/src/mcp/tools/context.ts +230 -0
  54. package/src/mcp/tools/embed.ts +62 -52
  55. package/src/mcp/tools/index-cmd.ts +88 -74
  56. package/src/mcp/tools/index.ts +49 -2
  57. package/src/mcp/tools/remove-collection.ts +2 -0
  58. package/src/mcp/tools/status.ts +11 -0
  59. package/src/mcp/tools/sync.ts +16 -14
  60. package/src/mcp/tools/workspace-write.ts +7 -3
  61. package/src/pipeline/chunk-lookup.ts +33 -0
  62. package/src/pipeline/hybrid.ts +79 -57
  63. package/src/pipeline/types.ts +14 -0
  64. package/src/sdk/client.ts +68 -6
  65. package/src/sdk/index.ts +21 -0
  66. package/src/sdk/types.ts +24 -0
  67. package/src/serve/background-runtime.ts +12 -211
  68. package/src/serve/context-capsule.ts +136 -0
  69. package/src/serve/context.ts +10 -1
  70. package/src/serve/embed-scheduler.ts +74 -43
  71. package/src/serve/index.ts +9 -0
  72. package/src/serve/jobs.ts +78 -80
  73. package/src/serve/public/components/HealthCenter.tsx +74 -1
  74. package/src/serve/public/globals.built.css +1 -1
  75. package/src/serve/public/pages/Dashboard.tsx +1 -0
  76. package/src/serve/resident-admission.ts +159 -0
  77. package/src/serve/resident-background-work.ts +39 -0
  78. package/src/serve/resident-request.ts +55 -0
  79. package/src/serve/resident-runtime.ts +490 -0
  80. package/src/serve/resident-status.ts +96 -0
  81. package/src/serve/routes/api.ts +265 -167
  82. package/src/serve/routes/mcp.ts +69 -0
  83. package/src/serve/server.ts +212 -35
  84. package/src/serve/status-model.ts +51 -0
  85. package/src/serve/status.ts +5 -0
  86. package/src/store/sqlite/adapter.ts +64 -29
@@ -8,6 +8,7 @@ import type { IndexStatus } from "../../store/types";
8
8
  import type { ToolContext } from "../server";
9
9
 
10
10
  import { resolveModelUri } from "../../llm/registry";
11
+ import { createStandaloneResidentStatus } from "../../serve/resident-status";
11
12
  import { runTool, type ToolResult } from "./index";
12
13
 
13
14
  type StatusInput = Record<string, never>;
@@ -22,6 +23,14 @@ function formatStatus(status: IndexStatus): string {
22
23
  lines.push(`Config: ${status.configPath}`);
23
24
  lines.push(`Database: ${status.dbPath}`);
24
25
  lines.push(`Health: ${status.healthy ? "OK" : "DEGRADED"}`);
26
+ if ("resident" in status) {
27
+ const resident = status.resident as NonNullable<
28
+ ReturnType<NonNullable<ToolContext["getResidentStatus"]>>
29
+ >;
30
+ lines.push(
31
+ `Runtime: ${resident.mode}${resident.resident ? ` (${resident.transport.activeSessions} sessions)` : " (standalone)"}`
32
+ );
33
+ }
25
34
  lines.push("");
26
35
 
27
36
  if (status.collections.length === 0) {
@@ -78,6 +87,8 @@ export function handleStatus(
78
87
  return {
79
88
  ...result.value,
80
89
  configPath: ctx.actualConfigPath,
90
+ resident:
91
+ ctx.getResidentStatus?.() ?? createStandaloneResidentStatus("stdio"),
81
92
  };
82
93
  },
83
94
  formatStatus
@@ -11,6 +11,7 @@ import type { ToolContext } from "../server";
11
11
  import { MCP_ERRORS } from "../../core/errors";
12
12
  import { acquireWriteLock, type WriteLockHandle } from "../../core/file-lock";
13
13
  import { JobError } from "../../core/job-manager";
14
+ import { recordContentMutation } from "../../core/mutation-generations";
14
15
  import { normalizeCollectionName } from "../../core/validation";
15
16
  import { defaultSyncService, withContentTypeRules } from "../../ingestion";
16
17
  import { runTool, type ToolResult } from "./index";
@@ -113,20 +114,21 @@ export function handleSync(
113
114
  "sync",
114
115
  lock,
115
116
  async () => {
116
- if (collection) {
117
- const result = await defaultSyncService.syncCollection(
118
- collection,
119
- ctx.store,
120
- options
121
- );
122
- return wrapSingleSync(result);
123
- }
124
-
125
- return defaultSyncService.syncAll(
126
- ctx.collections,
127
- ctx.store,
128
- options
129
- );
117
+ const result = collection
118
+ ? wrapSingleSync(
119
+ await defaultSyncService.syncCollection(
120
+ collection,
121
+ ctx.store,
122
+ options
123
+ )
124
+ )
125
+ : await defaultSyncService.syncAll(
126
+ ctx.collections,
127
+ ctx.store,
128
+ options
129
+ );
130
+ recordContentMutation(result, ctx.markContentMutation);
131
+ return result;
130
132
  }
131
133
  );
132
134
 
@@ -27,6 +27,7 @@ import {
27
27
  planMoveRefactor,
28
28
  planRenameRefactor,
29
29
  } from "../../core/file-refactors";
30
+ import { recordContentMutation } from "../../core/mutation-generations";
30
31
  import { defaultSyncService, withContentTypeRules } from "../../ingestion";
31
32
  import { runTool, type ToolResult } from "./index";
32
33
 
@@ -210,11 +211,12 @@ export function handleRenameNote(
210
211
  const currentPath = join(collection.path, doc.relPath);
211
212
  const nextPath = join(collection.path, plan.nextRelPath);
212
213
  await renameFilePath(currentPath, nextPath);
213
- await defaultSyncService.syncCollection(
214
+ const syncResult = await defaultSyncService.syncCollection(
214
215
  collection,
215
216
  ctx.store,
216
217
  withContentTypeRules({ runUpdateCmd: false }, ctx.config)
217
218
  );
219
+ recordContentMutation(syncResult, ctx.markContentMutation);
218
220
  return {
219
221
  uri: plan.nextUri,
220
222
  relPath: plan.nextRelPath,
@@ -255,11 +257,12 @@ export function handleMoveNote(
255
257
  const nextPath = join(collection.path, plan.nextRelPath);
256
258
  await mkdir(dirname(nextPath), { recursive: true });
257
259
  await renameFilePath(currentPath, nextPath);
258
- await defaultSyncService.syncCollection(
260
+ const syncResult = await defaultSyncService.syncCollection(
259
261
  collection,
260
262
  ctx.store,
261
263
  withContentTypeRules({ runUpdateCmd: false }, ctx.config)
262
264
  );
265
+ recordContentMutation(syncResult, ctx.markContentMutation);
263
266
  return {
264
267
  uri: plan.nextUri,
265
268
  relPath: plan.nextRelPath,
@@ -308,11 +311,12 @@ export function handleDuplicateNote(
308
311
  const nextPath = join(collection.path, plan.nextRelPath);
309
312
  await mkdir(dirname(nextPath), { recursive: true });
310
313
  await copyFilePath(currentPath, nextPath);
311
- await defaultSyncService.syncCollection(
314
+ const syncResult = await defaultSyncService.syncCollection(
312
315
  collection,
313
316
  ctx.store,
314
317
  withContentTypeRules({ runUpdateCmd: false }, ctx.config)
315
318
  );
319
+ recordContentMutation(syncResult, ctx.markContentMutation);
316
320
  return {
317
321
  uri: plan.nextUri,
318
322
  relPath: plan.nextRelPath,
@@ -42,3 +42,36 @@ export function createChunkLookup(
42
42
  return bySeq.get(seq);
43
43
  };
44
44
  }
45
+
46
+ /**
47
+ * Prove that persisted chunk coordinates still address the exact stored
48
+ * canonical mirror bytes. Chunk text may begin or end mid-line; callers that
49
+ * need citation spans must expand it to full lines separately.
50
+ */
51
+ export function chunkMatchesCanonicalContent(
52
+ chunk: ChunkRow,
53
+ content: string
54
+ ): boolean {
55
+ if (
56
+ content.includes("\r") ||
57
+ chunk.pos < 0 ||
58
+ chunk.startLine < 1 ||
59
+ chunk.endLine < chunk.startLine ||
60
+ chunk.text.length === 0 ||
61
+ chunk.pos + chunk.text.length > content.length
62
+ ) {
63
+ return false;
64
+ }
65
+ if (content.slice(chunk.pos, chunk.pos + chunk.text.length) !== chunk.text) {
66
+ return false;
67
+ }
68
+ let startLine = 1;
69
+ let endLine = 1;
70
+ const endOffset = chunk.pos + chunk.text.length - 1;
71
+ for (let offset = 0; offset < endOffset; offset += 1) {
72
+ if (content[offset] !== "\n") continue;
73
+ if (offset < chunk.pos) startLine += 1;
74
+ endLine += 1;
75
+ }
76
+ return startLine === chunk.startLine && endLine === chunk.endLine;
77
+ }
@@ -54,7 +54,10 @@ import {
54
54
  resolveTemporalRange,
55
55
  shouldSortByRecency,
56
56
  } from "./temporal";
57
- import { DEFAULT_PIPELINE_CONFIG } from "./types";
57
+ import {
58
+ DEFAULT_PIPELINE_CONFIG,
59
+ SEARCH_RESULT_PLANNER_METADATA,
60
+ } from "./types";
58
61
 
59
62
  // ─────────────────────────────────────────────────────────────────────────────
60
63
  // Dependencies
@@ -719,7 +722,16 @@ export async function searchHybrid(
719
722
  }
720
723
 
721
724
  // Build lookup maps.
722
- const docByMirrorHash = new Map<string, (typeof docsResult.value)[number]>();
725
+ const docsByMirrorHash = new Map<
726
+ string,
727
+ (typeof docsResult.value)[number][]
728
+ >();
729
+ const addDocument = (doc: (typeof docsResult.value)[number]): void => {
730
+ if (!doc.mirrorHash) return;
731
+ const docs = docsByMirrorHash.get(doc.mirrorHash) ?? [];
732
+ docs.push(doc);
733
+ docsByMirrorHash.set(doc.mirrorHash, docs);
734
+ };
723
735
  const matchesMetadataFilters = (
724
736
  doc: (typeof docsResult.value)[number]
725
737
  ): boolean => {
@@ -761,7 +773,7 @@ export async function searchHybrid(
761
773
  candidateDocs.push(doc);
762
774
  } else {
763
775
  if (matchesMetadataFilters(doc)) {
764
- docByMirrorHash.set(doc.mirrorHash, doc);
776
+ addDocument(doc);
765
777
  }
766
778
  }
767
779
  }
@@ -789,12 +801,20 @@ export async function searchHybrid(
789
801
  }
790
802
 
791
803
  if (doc.mirrorHash && matchesMetadataFilters(doc)) {
792
- docByMirrorHash.set(doc.mirrorHash, doc);
804
+ addDocument(doc);
793
805
  }
794
806
  }
795
807
  }
796
808
  }
797
809
 
810
+ for (const docs of docsByMirrorHash.values()) {
811
+ docs.sort((left, right) => {
812
+ if (left.uri < right.uri) return -1;
813
+ if (left.uri > right.uri) return 1;
814
+ return left.docid < right.docid ? -1 : left.docid > right.docid ? 1 : 0;
815
+ });
816
+ }
817
+
798
818
  const collectionPaths = new Map<string, string>();
799
819
  if (collectionsResult.ok) {
800
820
  for (const c of collectionsResult.value) {
@@ -823,34 +843,19 @@ export async function searchHybrid(
823
843
  const seenDocids = new Set<string>();
824
844
 
825
845
  // Iterate until we have enough results (don't slice early - deduping may skip candidates)
826
- for (const candidate of filteredCandidates) {
846
+ for (const [candidateIndex, candidate] of filteredCandidates.entries()) {
827
847
  // Stop when we have enough results
828
848
  if (results.length >= assemblyLimit) {
829
849
  break;
830
850
  }
831
851
 
832
852
  // Find document from pre-fetched map
833
- const doc = docByMirrorHash.get(candidate.mirrorHash);
834
- if (!doc) {
853
+ const candidateDocs = docsByMirrorHash.get(candidate.mirrorHash) ?? [];
854
+ if (candidateDocs.length === 0) {
835
855
  continue;
836
856
  }
837
857
 
838
858
  const docChunks = chunksMap.get(candidate.mirrorHash) ?? [];
839
- const filterEval = evaluateDocumentChunkFilters(
840
- query,
841
- doc,
842
- docChunks,
843
- options
844
- );
845
- if (!filterEval.matches) {
846
- continue;
847
- }
848
-
849
- // For --full mode, de-dupe by docid (keep best scoring candidate per doc)
850
- if (options.full && seenDocids.has(doc.docid)) {
851
- continue;
852
- }
853
-
854
859
  // Get chunk via O(1) lookup
855
860
  // For doc-level FTS (seq=0), fall back to first available chunk if exact lookup fails
856
861
  let chunk = getChunk(candidate.mirrorHash, candidate.seq);
@@ -868,10 +873,6 @@ export async function searchHybrid(
868
873
  continue;
869
874
  }
870
875
 
871
- docidMap.set(`${candidate.mirrorHash}:${candidate.seq}`, doc.docid);
872
-
873
- const collectionPath = collectionPaths.get(doc.collection);
874
-
875
876
  // For --full mode, fetch full mirror content
876
877
  const snippetChunk =
877
878
  options.full || !options.intent?.trim()
@@ -907,37 +908,58 @@ export async function searchHybrid(
907
908
  // Fallback to chunk text if content unavailable
908
909
  }
909
910
 
910
- seenDocids.add(doc.docid);
911
-
912
- results.push({
913
- docid: doc.docid,
914
- score: candidate.blendedScore,
915
- uri: doc.uri,
916
- title: doc.title ?? undefined,
917
- contentType: doc.contentType ?? undefined,
918
- categories: doc.categories ?? undefined,
919
- line: snippetChunk.startLine,
920
- snippet,
921
- snippetLanguage: chunk.language ?? undefined,
922
- snippetRange,
923
- source: {
924
- relPath: doc.relPath,
925
- absPath: collectionPath
926
- ? `${collectionPath}/${doc.relPath}`
927
- : undefined,
928
- mime: doc.sourceMime,
929
- ext: doc.sourceExt,
930
- modifiedAt: doc.sourceMtime,
931
- documentDate: doc.frontmatterDate ?? undefined,
932
- sizeBytes: doc.sourceSize,
933
- sourceHash: doc.sourceHash,
934
- },
935
- conversion: {
936
- mirrorHash: candidate.mirrorHash,
937
- converterId: doc.converterId ?? undefined,
938
- converterVersion: doc.converterVersion ?? undefined,
939
- },
940
- });
911
+ for (const doc of candidateDocs) {
912
+ if (results.length >= assemblyLimit) break;
913
+ const filterEval = evaluateDocumentChunkFilters(
914
+ query,
915
+ doc,
916
+ docChunks,
917
+ options
918
+ );
919
+ if (!filterEval.matches || (options.full && seenDocids.has(doc.docid))) {
920
+ continue;
921
+ }
922
+ const docidKey = `${candidate.mirrorHash}:${candidate.seq}`;
923
+ if (!docidMap.has(docidKey)) docidMap.set(docidKey, doc.docid);
924
+ const collectionPath = collectionPaths.get(doc.collection);
925
+ seenDocids.add(doc.docid);
926
+ results.push({
927
+ docid: doc.docid,
928
+ score: candidate.blendedScore,
929
+ uri: doc.uri,
930
+ title: doc.title ?? undefined,
931
+ contentType: doc.contentType ?? undefined,
932
+ categories: doc.categories ?? undefined,
933
+ line: snippetChunk.startLine,
934
+ snippet,
935
+ snippetLanguage: chunk.language ?? undefined,
936
+ snippetRange,
937
+ source: {
938
+ relPath: doc.relPath,
939
+ absPath: collectionPath
940
+ ? `${collectionPath}/${doc.relPath}`
941
+ : undefined,
942
+ mime: doc.sourceMime,
943
+ ext: doc.sourceExt,
944
+ modifiedAt: doc.sourceMtime,
945
+ documentDate: doc.frontmatterDate ?? undefined,
946
+ sizeBytes: doc.sourceSize,
947
+ sourceHash: doc.sourceHash,
948
+ },
949
+ conversion: {
950
+ mirrorHash: candidate.mirrorHash,
951
+ converterId: doc.converterId ?? undefined,
952
+ converterVersion: doc.converterVersion ?? undefined,
953
+ },
954
+ [SEARCH_RESULT_PLANNER_METADATA]: {
955
+ retrievalRank: candidateIndex + 1,
956
+ mirrorHash: candidate.mirrorHash,
957
+ seq: snippetChunk.seq,
958
+ sources: [...candidate.sources].sort(),
959
+ graphExpanded: candidate.sources.includes("graph"),
960
+ },
961
+ });
962
+ }
941
963
  }
942
964
  timings.assemblyMs = performance.now() - assemblyStartedAt;
943
965
  timings.totalMs = performance.now() - runStartedAt;
@@ -37,6 +37,19 @@ export interface SnippetRange {
37
37
  endLine: number;
38
38
  }
39
39
 
40
+ /** Symbol-keyed planner metadata; omitted from JSON/API projections. */
41
+ export const SEARCH_RESULT_PLANNER_METADATA = Symbol(
42
+ "gno.searchResultPlannerMetadata"
43
+ );
44
+
45
+ export interface SearchResultPlannerMetadata {
46
+ retrievalRank: number;
47
+ mirrorHash: string;
48
+ seq: number;
49
+ sources: FusionSource[];
50
+ graphExpanded: boolean;
51
+ }
52
+
40
53
  /** Single search result matching output schema */
41
54
  export interface SearchResult {
42
55
  docid: string;
@@ -53,6 +66,7 @@ export interface SearchResult {
53
66
  context?: string;
54
67
  source: SearchResultSource;
55
68
  conversion?: SearchResultConversion;
69
+ [SEARCH_RESULT_PLANNER_METADATA]?: SearchResultPlannerMetadata;
56
70
  }
57
71
 
58
72
  /** Search mode enum */
package/src/sdk/client.ts CHANGED
@@ -22,6 +22,9 @@ import type {
22
22
  GnoCreateFolderResult,
23
23
  GnoCreateNoteOptions,
24
24
  GnoCreateNoteResult,
25
+ GnoContextInput,
26
+ GnoContextResult,
27
+ GnoContextVerificationResult,
25
28
  GnoClientInitOptions,
26
29
  GnoDuplicateNoteOptions,
27
30
  GnoEmbedOptions,
@@ -39,8 +42,21 @@ import type {
39
42
  GnoVectorSearchOptions,
40
43
  } from "./types";
41
44
 
42
- import { decorateUriForIndex, getIndexDbPath } from "../app/constants";
43
- import { INDEX_NAME_REQUIREMENTS, isValidIndexName } from "../app/index-name";
45
+ import {
46
+ decorateUriForIndex,
47
+ DEFAULT_INDEX_NAME,
48
+ getIndexDbPath,
49
+ } from "../app/constants";
50
+ import {
51
+ buildContextCapsule,
52
+ validateContextCapsuleBuildInput,
53
+ verifyContextCapsuleRuntime,
54
+ } from "../app/context-runtime";
55
+ import {
56
+ canonicalizeIndexName,
57
+ INDEX_NAME_REQUIREMENTS,
58
+ isValidIndexName,
59
+ } from "../app/index-name";
44
60
  import {
45
61
  ConfigSchema,
46
62
  loadConfig,
@@ -108,7 +124,7 @@ interface OpenedClientState {
108
124
  store: SqliteAdapter;
109
125
  llm: LlmAdapter;
110
126
  downloadPolicy: DownloadPolicy;
111
- indexName?: string;
127
+ indexName: string;
112
128
  }
113
129
 
114
130
  interface RuntimePorts {
@@ -161,7 +177,10 @@ async function resolveClientState(
161
177
  configSource = "file";
162
178
  }
163
179
 
164
- const dbPath = options.dbPath ?? getIndexDbPath(options.indexName);
180
+ const indexName = canonicalizeIndexName(
181
+ options.indexName ?? DEFAULT_INDEX_NAME
182
+ );
183
+ const dbPath = options.dbPath ?? getIndexDbPath(indexName);
165
184
  await mkdir(dirname(dbPath), { recursive: true });
166
185
 
167
186
  const store = new SqliteAdapter();
@@ -179,7 +198,7 @@ async function resolveClientState(
179
198
  llm: new LlmAdapter(config, options.cacheDir),
180
199
  downloadPolicy:
181
200
  options.downloadPolicy ?? resolveDownloadPolicy(process.env, {}),
182
- indexName: options.indexName,
201
+ indexName,
183
202
  };
184
203
  }
185
204
 
@@ -192,7 +211,7 @@ class GnoClientImpl implements GnoClient {
192
211
  private readonly store: SqliteAdapter;
193
212
  private readonly llm: LlmAdapter;
194
213
  private readonly downloadPolicy: DownloadPolicy;
195
- private readonly indexName?: string;
214
+ private readonly indexName: string;
196
215
  private closed = false;
197
216
 
198
217
  constructor(state: OpenedClientState) {
@@ -644,6 +663,49 @@ class GnoClientImpl implements GnoClient {
644
663
  }
645
664
  }
646
665
 
666
+ async context(input: GnoContextInput): Promise<GnoContextResult> {
667
+ this.assertOpen();
668
+ validateContextCapsuleBuildInput(
669
+ { ...input, indexName: this.indexName },
670
+ this.indexName,
671
+ this.config.collections.map((collection) => collection.name)
672
+ );
673
+ const collection =
674
+ input.collections?.length === 1 ? input.collections[0] : undefined;
675
+ const useModels = input.depthPolicy !== "fast";
676
+ const ports = await this.createRuntimePorts({
677
+ embed: useModels,
678
+ rerank: useModels,
679
+ collection,
680
+ });
681
+ try {
682
+ return await buildContextCapsule(
683
+ { ...input, indexName: this.indexName },
684
+ {
685
+ store: this.store,
686
+ config: this.config,
687
+ indexName: this.indexName,
688
+ vectorIndex: ports.vectorIndex,
689
+ embedPort: ports.embedPort,
690
+ rerankPort: ports.rerankPort,
691
+ }
692
+ );
693
+ } finally {
694
+ await this.disposeRuntimePorts(ports);
695
+ }
696
+ }
697
+
698
+ async verifyContext(
699
+ capsule: GnoContextResult
700
+ ): Promise<GnoContextVerificationResult> {
701
+ this.assertOpen();
702
+ return verifyContextCapsuleRuntime(capsule, {
703
+ store: this.store,
704
+ config: this.config,
705
+ indexName: this.indexName,
706
+ });
707
+ }
708
+
647
709
  async get(ref: string, options: GnoGetOptions = {}) {
648
710
  this.assertOpen();
649
711
  const resolution = resolveEffectiveIndex([ref], this.indexName);
package/src/sdk/index.ts CHANGED
@@ -31,6 +31,10 @@ export type {
31
31
  GnoAskOptions,
32
32
  GnoCaptureOptions,
33
33
  GnoCaptureResult,
34
+ GnoContextErrorCode,
35
+ GnoContextInput,
36
+ GnoContextResult,
37
+ GnoContextVerificationResult,
34
38
  GnoClient,
35
39
  GnoClientInitOptions,
36
40
  GnoEmbedOptions,
@@ -51,3 +55,20 @@ export type {
51
55
  GnoUpdateOptions,
52
56
  GnoVectorSearchOptions,
53
57
  } from "./types";
58
+ export {
59
+ ContextCapsuleContractError,
60
+ type ContextCapsuleErrorCode,
61
+ type ContextCapsuleV1,
62
+ } from "../core/context-capsule";
63
+ export {
64
+ ContextEvidenceError,
65
+ type ContextEvidenceErrorCode,
66
+ } from "../core/context-evidence";
67
+ export {
68
+ ContextVerifierError,
69
+ type ContextVerifierErrorCode,
70
+ } from "../core/context-verifier";
71
+ export {
72
+ ContextRuntimeError,
73
+ type ContextRuntimeErrorCode,
74
+ } from "../app/context-runtime";
package/src/sdk/types.ts CHANGED
@@ -4,8 +4,19 @@
4
4
  * @module src/sdk/types
5
5
  */
6
6
 
7
+ import type {
8
+ ContextCapsuleBuildInput,
9
+ ContextRuntimeErrorCode,
10
+ } from "../app/context-runtime";
7
11
  import type { Config } from "../config/types";
8
12
  import type { CaptureInput, CaptureReceipt } from "../core/capture";
13
+ import type {
14
+ ContextCapsuleErrorCode,
15
+ ContextCapsuleV1,
16
+ ContextCapsuleVerification,
17
+ } from "../core/context-capsule";
18
+ import type { ContextEvidenceErrorCode } from "../core/context-evidence";
19
+ import type { ContextVerifierErrorCode } from "../core/context-verifier";
9
20
  import type { NoteCollisionPolicy } from "../core/note-creation";
10
21
  import type { NotePresetId } from "../core/note-presets";
11
22
  import type { DocumentSection } from "../core/sections";
@@ -65,6 +76,15 @@ export type GnoVectorSearchOptions = SearchOptions & {
65
76
  model?: string;
66
77
  };
67
78
 
79
+ export type GnoContextInput = Omit<ContextCapsuleBuildInput, "indexName">;
80
+ export type GnoContextResult = ContextCapsuleV1;
81
+ export type GnoContextVerificationResult = ContextCapsuleVerification;
82
+ export type GnoContextErrorCode =
83
+ | ContextRuntimeErrorCode
84
+ | ContextCapsuleErrorCode
85
+ | ContextEvidenceErrorCode
86
+ | ContextVerifierErrorCode;
87
+
68
88
  export interface GnoGetOptions {
69
89
  from?: number;
70
90
  limit?: number;
@@ -185,6 +205,10 @@ export interface GnoClient {
185
205
  ): Promise<SearchResults>;
186
206
  query(query: string, options?: GnoQueryOptions): Promise<SearchResults>;
187
207
  ask(query: string, options?: GnoAskOptions): Promise<AskResult>;
208
+ context(input: GnoContextInput): Promise<GnoContextResult>;
209
+ verifyContext(
210
+ capsule: ContextCapsuleV1
211
+ ): Promise<GnoContextVerificationResult>;
188
212
  get(
189
213
  ref: string,
190
214
  options?: GnoGetOptions