@gmickel/gno 1.34.0 → 1.34.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/README.md CHANGED
@@ -117,7 +117,7 @@ gno daemon --detach # headless indexing + resident MCP gateway
117
117
 
118
118
  <!-- public-truth:current-version -->
119
119
 
120
- > Current release: **v1.34.0** — see [CHANGELOG.md](./CHANGELOG.md)
120
+ > Current release: **v1.34.1** — see [CHANGELOG.md](./CHANGELOG.md)
121
121
 
122
122
  <!-- /public-truth -->
123
123
 
@@ -0,0 +1 @@
1
+ ee0e3d4032a41a097c85fba552a9ab449906f258e14844ad5e3b5f99001716e0 gno-browser-clipper-v1.34.2.zip
@@ -21,5 +21,5 @@
21
21
  "content_security_policy": {
22
22
  "extension_pages": "script-src 'self'; object-src 'none'; connect-src http://127.0.0.1:*"
23
23
  },
24
- "version": "1.34.0"
24
+ "version": "1.34.2"
25
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmickel/gno",
3
- "version": "1.34.0",
3
+ "version": "1.34.2",
4
4
  "description": "Local semantic search for your documents. Index Markdown, PDF, and Office files with hybrid BM25 + vector search.",
5
5
  "keywords": [
6
6
  "embeddings",
package/spec/cli.md CHANGED
@@ -1922,6 +1922,8 @@ gno models pull [--all|--embed|--rerank|--gen] [--force]
1922
1922
  **Behavior:**
1923
1923
 
1924
1924
  - Skips models that are already cached (checksum match) unless `--force` is used
1925
+ - Skips HTTP(S) rerank endpoints as external services; they are called directly
1926
+ and are never downloaded or cached, including with `--force`
1925
1927
  - Default (no flags): pulls all models
1926
1928
 
1927
1929
  **Exit Codes:**
@@ -10,6 +10,7 @@ import type { DownloadProgress, ModelType } from "../../../llm/types";
10
10
  import { getModelsCachePath } from "../../../app/constants";
11
11
  import { loadConfig } from "../../../config";
12
12
  import { ModelCache } from "../../../llm/cache";
13
+ import { isHttpRerankUri } from "../../../llm/httpRerank";
13
14
  import { getActivePreset } from "../../../llm/registry";
14
15
 
15
16
  // ─────────────────────────────────────────────────────────────────────────────
@@ -46,6 +47,7 @@ export interface ModelPullResult {
46
47
  error?: string;
47
48
  path?: string;
48
49
  skipped?: boolean;
50
+ skipReason?: "cached" | "external";
49
51
  }
50
52
 
51
53
  export interface ModelsPullResult {
@@ -117,6 +119,20 @@ export async function modelsPull(
117
119
  const uri =
118
120
  type === "expand" ? (preset.expand ?? preset.gen) : preset[type];
119
121
 
122
+ // HTTP rerankers are services, not model artifacts. They are loaded
123
+ // directly by LlmAdapter and must never enter the local model cache path.
124
+ if (type === "rerank" && isHttpRerankUri(uri)) {
125
+ results.push({
126
+ type,
127
+ uri,
128
+ ok: true,
129
+ skipped: true,
130
+ skipReason: "external",
131
+ });
132
+ skipped += 1;
133
+ continue;
134
+ }
135
+
120
136
  // Check if already cached (skip unless --force)
121
137
  if (!options.force) {
122
138
  const isCached = await cache.isCached(uri);
@@ -128,6 +144,7 @@ export async function modelsPull(
128
144
  ok: true,
129
145
  path: path ?? undefined,
130
146
  skipped: true,
147
+ skipReason: "cached",
131
148
  });
132
149
  skipped += 1;
133
150
  continue;
@@ -176,13 +193,17 @@ export async function modelsPull(
176
193
  */
177
194
  export function formatModelsPull(result: ModelsPullResult): string {
178
195
  const lines: string[] = [];
196
+ let externalSkipped = 0;
179
197
  const label = (type: ModelType) =>
180
198
  type === "gen" ? "answer" : type === "expand" ? "expand" : type;
181
199
 
182
200
  for (const r of result.results) {
183
201
  if (r.ok) {
184
202
  if (r.skipped) {
185
- lines.push(`${label(r.type)}: skipped (already cached)`);
203
+ if (r.skipReason === "external") externalSkipped += 1;
204
+ const reason =
205
+ r.skipReason === "external" ? "external endpoint" : "already cached";
206
+ lines.push(`${label(r.type)}: skipped (${reason})`);
186
207
  } else {
187
208
  lines.push(`${label(r.type)}: downloaded`);
188
209
  }
@@ -196,10 +217,18 @@ export function formatModelsPull(result: ModelsPullResult): string {
196
217
  lines.push(`${result.failed} model(s) failed to download.`);
197
218
  } else if (result.skipped === result.results.length) {
198
219
  lines.push("");
199
- lines.push("All models already cached. Use --force to re-download.");
220
+ lines.push(
221
+ externalSkipped > 0
222
+ ? "No model downloads needed."
223
+ : "All models already cached. Use --force to re-download."
224
+ );
200
225
  } else {
201
226
  lines.push("");
202
- lines.push("All models downloaded successfully.");
227
+ lines.push(
228
+ externalSkipped > 0
229
+ ? "All downloadable models downloaded successfully."
230
+ : "All models downloaded successfully."
231
+ );
203
232
  }
204
233
 
205
234
  return lines.join("\n");
@@ -60,6 +60,29 @@ const DEFAULT_TEMPERATURE = 0;
60
60
  const DEFAULT_SEED = 42;
61
61
  const DEFAULT_MAX_TOKENS = 256;
62
62
 
63
+ // Context sizing: without an explicit contextSize, node-llama-cpp defaults to
64
+ // "auto", which grows the KV cache to fill available VRAM up to the model's
65
+ // trained context length (OOM risk on small GPUs — see issue #189). Instead,
66
+ // size the context to what the call actually needs: prompt tokens + output
67
+ // budget + margin for chat-template wrapping and special tokens.
68
+ const GEN_CONTEXT_MARGIN_TOKENS = 512;
69
+ const GEN_CONTEXT_MIN_TOKENS = 1024;
70
+
71
+ export const resolveGenContextSize = (input: {
72
+ promptTokenCount: number;
73
+ maxTokens: number;
74
+ trainContextSize?: number;
75
+ }): number => {
76
+ const needed = Math.max(
77
+ GEN_CONTEXT_MIN_TOKENS,
78
+ input.promptTokenCount + input.maxTokens + GEN_CONTEXT_MARGIN_TOKENS
79
+ );
80
+ if (input.trainContextSize && input.trainContextSize > 0) {
81
+ return Math.min(needed, input.trainContextSize);
82
+ }
83
+ return needed;
84
+ };
85
+
63
86
  // ─────────────────────────────────────────────────────────────────────────────
64
87
  // Implementation
65
88
  // ─────────────────────────────────────────────────────────────────────────────
@@ -97,9 +120,14 @@ export class NodeLlamaCppGeneration implements GenerationPort {
97
120
  await this.manager.getLlama()
98
121
  ).createGrammarForJsonSchema(params.jsonSchema as JsonGrammarSchema)
99
122
  : undefined;
100
- context = await llamaModel.createContext(
101
- params?.contextSize ? { contextSize: params.contextSize } : undefined
102
- );
123
+ const contextSize =
124
+ params?.contextSize ??
125
+ resolveGenContextSize({
126
+ promptTokenCount: llamaModel.tokenize(prompt).length,
127
+ maxTokens: params?.maxTokens ?? DEFAULT_MAX_TOKENS,
128
+ trainContextSize: llamaModel.trainContextSize,
129
+ });
130
+ context = await llamaModel.createContext({ contextSize });
103
131
  // Import LlamaChatSession dynamically
104
132
  const { LlamaChatSession } = await import("node-llama-cpp");
105
133
  const session = new LlamaChatSession({
@@ -128,11 +128,7 @@ import {
128
128
  classifyResolvedGraphEdge,
129
129
  mergeGraphEdgeAudit,
130
130
  } from "../../core/graph-edge-confidence";
131
- import {
132
- buildWikiBestMatchSubquery,
133
- buildWikiBestRankMatchCountSubquery,
134
- buildWikiBestRankSubquery,
135
- } from "../../core/graph-resolver";
131
+ import { buildWikiBestMatchSubquery } from "../../core/graph-resolver";
136
132
  import { buildContentPrefilterNeedles } from "../../core/link-relevance";
137
133
  import { normalizeWikiName, stripWikiMdExt } from "../../core/links";
138
134
  import {
@@ -179,6 +175,7 @@ import {
179
175
  getLatestFileRefactorReceiptByPlanDigest as getStoredLatestFileRefactorReceiptByPlanDigest,
180
176
  } from "./file-refactor-journal-store";
181
177
  import { loadFts5Snowball } from "./fts5-snowball";
178
+ import { resolveGraphLinkTargets } from "./graph-link-resolver";
182
179
  import { queryGraphNeighborsForSeeds } from "./graph-neighbors";
183
180
  import {
184
181
  appendExportManifest as appendStoredTraceExportManifest,
@@ -4757,9 +4754,13 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
4757
4754
  match_count: number | null;
4758
4755
  }
4759
4756
 
4760
- interface UnresolvedByTypeRow {
4757
+ interface GraphLinkResolutionRow {
4758
+ source_id: number;
4759
+ source_docid: string;
4760
+ source_collection: string;
4761
+ target_ref_norm: string;
4762
+ target_collection: string | null;
4761
4763
  link_type: "wiki" | "markdown";
4762
- unresolved: number;
4763
4764
  }
4764
4765
 
4765
4766
  interface NodeMetaRow {
@@ -4771,104 +4772,68 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
4771
4772
  rel_path: string;
4772
4773
  }
4773
4774
 
4774
- const edgeParams: string[] = [];
4775
- let edgeCollectionClause = "";
4775
+ const linkParams: string[] = [];
4776
+ let sourceCollectionClause = "";
4776
4777
  if (collection) {
4777
- edgeCollectionClause = "AND src.collection = ? AND tgt.collection = ?";
4778
- edgeParams.push(collection, collection);
4778
+ sourceCollectionClause = "AND src.collection = ?";
4779
+ linkParams.push(collection);
4779
4780
  }
4780
4781
 
4781
- const resolvedEdgeQuery = `
4782
+ const graphLinkRows = db
4783
+ .query<GraphLinkResolutionRow, string[]>(
4784
+ `
4782
4785
  SELECT
4783
4786
  src.id as source_id,
4784
4787
  src.docid as source_docid,
4785
- tgt.id as target_id,
4786
- tgt.docid as target_docid,
4787
- dl.link_type,
4788
- CASE dl.link_type
4789
- WHEN 'wiki' THEN (${buildWikiBestRankSubquery(
4790
- "COALESCE(dl.target_collection, src.collection)",
4791
- "dl.target_ref_norm"
4792
- )})
4793
- WHEN 'markdown' THEN 5
4794
- END as match_rank,
4795
- CASE dl.link_type
4796
- WHEN 'wiki' THEN (${buildWikiBestRankMatchCountSubquery(
4797
- "COALESCE(dl.target_collection, src.collection)",
4798
- "dl.target_ref_norm"
4799
- )})
4800
- WHEN 'markdown' THEN 1
4801
- END as match_count
4788
+ src.collection as source_collection,
4789
+ dl.target_ref_norm,
4790
+ dl.target_collection,
4791
+ dl.link_type
4802
4792
  FROM documents src
4803
4793
  JOIN doc_links dl ON dl.source_doc_id = src.id
4804
- JOIN documents tgt ON tgt.id = CASE dl.link_type
4805
- WHEN 'wiki' THEN (${buildWikiBestMatchSubquery(
4806
- "COALESCE(dl.target_collection, src.collection)",
4807
- "dl.target_ref_norm"
4808
- )})
4809
- WHEN 'markdown' THEN (
4810
- SELECT md.id FROM documents md
4811
- WHERE md.active = 1
4812
- AND md.collection = COALESCE(dl.target_collection, src.collection)
4813
- AND md.rel_path = dl.target_ref_norm
4814
- ORDER BY md.id LIMIT 1
4815
- )
4816
- END
4817
- WHERE src.active = 1 AND tgt.active = 1
4818
- ${edgeCollectionClause}
4819
- ORDER BY src.id ASC, tgt.id ASC, dl.link_type ASC
4820
- `;
4821
-
4822
- const resolvedEdgeRows = db
4823
- .query<ResolvedEdgeRow, string[]>(resolvedEdgeQuery)
4824
- .all(...edgeParams);
4825
-
4826
- const unresolvedParams: string[] = [];
4827
- let unresolvedCollectionClause = "";
4828
- if (collection) {
4829
- unresolvedCollectionClause = "AND src.collection = ?";
4830
- unresolvedParams.push(collection);
4831
- }
4832
- const unresolvedQuery = `
4833
- SELECT
4834
- link_type,
4835
- COUNT(*) as unresolved
4836
- FROM (
4837
- SELECT
4838
- dl.link_type,
4839
- CASE dl.link_type
4840
- WHEN 'wiki' THEN (
4841
- ${buildWikiBestMatchSubquery(
4842
- "COALESCE(dl.target_collection, src.collection)",
4843
- "dl.target_ref_norm"
4844
- )}
4845
- )
4846
- WHEN 'markdown' THEN (
4847
- SELECT t.id FROM documents t
4848
- WHERE t.active = 1
4849
- AND t.collection = COALESCE(dl.target_collection, src.collection)
4850
- AND t.rel_path = dl.target_ref_norm
4851
- ORDER BY t.id LIMIT 1
4852
- )
4853
- END as target_id
4854
- FROM documents src
4855
- JOIN doc_links dl ON dl.source_doc_id = src.id
4856
- WHERE src.active = 1
4857
- ${unresolvedCollectionClause}
4794
+ WHERE src.active = 1
4795
+ ${sourceCollectionClause}
4796
+ ORDER BY src.id ASC, dl.id ASC
4797
+ `
4858
4798
  )
4859
- WHERE target_id IS NULL
4860
- GROUP BY link_type
4861
- `;
4862
- const unresolvedRows = db
4863
- .query<UnresolvedByTypeRow, string[]>(unresolvedQuery)
4864
- .all(...unresolvedParams);
4799
+ .all(...linkParams);
4800
+ const resolutions = resolveGraphLinkTargets(
4801
+ db,
4802
+ graphLinkRows.map((row) => ({
4803
+ targetRefNorm: row.target_ref_norm,
4804
+ targetCollection: row.target_collection ?? row.source_collection,
4805
+ linkType: row.link_type,
4806
+ }))
4807
+ );
4808
+ const resolvedEdgeRows: ResolvedEdgeRow[] = [];
4865
4809
  const unresolvedByType: Record<"wiki" | "markdown", number> = {
4866
4810
  wiki: 0,
4867
4811
  markdown: 0,
4868
4812
  };
4869
- for (const row of unresolvedRows) {
4870
- unresolvedByType[row.link_type] = row.unresolved;
4813
+ for (const [index, row] of graphLinkRows.entries()) {
4814
+ const resolution = resolutions[index];
4815
+ if (!resolution) {
4816
+ unresolvedByType[row.link_type] += 1;
4817
+ continue;
4818
+ }
4819
+ const targetCollection = row.target_collection ?? row.source_collection;
4820
+ if (collection && targetCollection !== collection) continue;
4821
+ resolvedEdgeRows.push({
4822
+ source_id: row.source_id,
4823
+ source_docid: row.source_docid,
4824
+ target_id: resolution.targetId,
4825
+ target_docid: resolution.targetDocid,
4826
+ link_type: row.link_type,
4827
+ match_rank: resolution.matchRank,
4828
+ match_count: resolution.matchCount,
4829
+ });
4871
4830
  }
4831
+ resolvedEdgeRows.sort(
4832
+ (left, right) =>
4833
+ left.source_id - right.source_id ||
4834
+ left.target_id - right.target_id ||
4835
+ left.link_type.localeCompare(right.link_type)
4836
+ );
4872
4837
  const totalEdgesUnresolved =
4873
4838
  unresolvedByType.wiki + unresolvedByType.markdown;
4874
4839
 
@@ -1 +0,0 @@
1
- 0acc125d848721bdced540fe49412ef8c83df5d4ff3ba086076c46ed9d441998 gno-browser-clipper-v1.34.0.zip