@gmickel/gno 2.6.0 → 2.7.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 (89) hide show
  1. package/README.md +1 -1
  2. package/assets/skill/SKILL.md +5 -3
  3. package/assets/skill/cli-reference.md +9 -2
  4. package/assets/skill/mcp-reference.md +2 -1
  5. package/assets/spa-production.json.gz +0 -0
  6. package/browser-extension/artifacts/{gno-browser-clipper-v2.6.0.zip → gno-browser-clipper-v2.7.0.zip} +0 -0
  7. package/browser-extension/artifacts/gno-browser-clipper-v2.7.0.zip.sha256 +1 -0
  8. package/browser-extension/dist/manifest.json +1 -1
  9. package/package.json +1 -1
  10. package/spec/cli.md +109 -18
  11. package/spec/mcp.md +36 -2
  12. package/spec/output-schemas/ask.schema.json +1 -1
  13. package/spec/output-schemas/capture-receipt.schema.json +1 -1
  14. package/spec/output-schemas/doctor.schema.json +88 -0
  15. package/spec/output-schemas/error.schema.json +11 -2
  16. package/spec/output-schemas/get.schema.json +1 -1
  17. package/spec/output-schemas/mcp-capture-result.schema.json +1 -2
  18. package/spec/output-schemas/memory-remember.schema.json +2 -2
  19. package/spec/output-schemas/multi-get.schema.json +4 -1
  20. package/spec/output-schemas/peek.schema.json +2 -9
  21. package/spec/output-schemas/resident-status.schema.json +22 -0
  22. package/spec/output-schemas/search-result.schema.json +1 -1
  23. package/spec/output-schemas/search-results.schema.json +1 -1
  24. package/spec/output-schemas/status.schema.json +98 -0
  25. package/src/cli/commands/doctor.ts +54 -20
  26. package/src/cli/commands/embed.ts +41 -3
  27. package/src/cli/commands/query.ts +5 -0
  28. package/src/cli/commands/status.ts +63 -5
  29. package/src/cli/commands/vec.ts +54 -0
  30. package/src/cli/detach.ts +29 -1
  31. package/src/cli/errors.ts +13 -9
  32. package/src/cli/program.ts +53 -1
  33. package/src/core/capture-sync.ts +9 -2
  34. package/src/core/host-paths.ts +31 -0
  35. package/src/core/memory-remember.ts +4 -3
  36. package/src/core/shutdown-budget.ts +6 -0
  37. package/src/core/vector-partition-status.ts +52 -0
  38. package/src/embed/backlog.ts +124 -18
  39. package/src/embed/fingerprint.ts +6 -3
  40. package/src/embed/retry.ts +66 -27
  41. package/src/embed/variant-backlog.ts +15 -10
  42. package/src/embed/variant-retry.ts +31 -22
  43. package/src/index.ts +21 -2
  44. package/src/llm/native-worker/dispatcher.ts +2 -0
  45. package/src/llm/native-worker/embedding-identity.ts +42 -0
  46. package/src/llm/native-worker/protocol.ts +1 -0
  47. package/src/llm/types.ts +3 -0
  48. package/src/mcp/context.ts +9 -0
  49. package/src/mcp/resources/index.ts +6 -5
  50. package/src/mcp/tool-descriptions-core.ts +1 -1
  51. package/src/mcp/tools/capture.ts +1 -3
  52. package/src/mcp/tools/index.ts +11 -4
  53. package/src/mcp/tools/memory-remember.ts +1 -1
  54. package/src/mcp/tools/status.ts +4 -0
  55. package/src/pipeline/hybrid.ts +37 -7
  56. package/src/pipeline/vsearch.ts +14 -2
  57. package/src/serve/embed-scheduler.ts +133 -19
  58. package/src/serve/host-path-redaction.ts +79 -0
  59. package/src/serve/public/components/sessions/SessionSearch.tsx +2 -2
  60. package/src/serve/public/globals.built.css +1 -1
  61. package/src/serve/public/hooks/use-api.ts +17 -2
  62. package/src/serve/public/lib/request-intent.ts +8 -0
  63. package/src/serve/public/{components/sessions → lib}/snippet.tsx +2 -3
  64. package/src/serve/public/pages/Dashboard.tsx +12 -9
  65. package/src/serve/public/pages/DocView.tsx +10 -5
  66. package/src/serve/public/pages/DocumentEditor.tsx +91 -14
  67. package/src/serve/public/pages/Search.tsx +1 -41
  68. package/src/serve/resident-runtime.ts +26 -1
  69. package/src/serve/resident-status.ts +13 -1
  70. package/src/serve/server.ts +10 -9
  71. package/src/serve/status-model.ts +16 -0
  72. package/src/serve/status.ts +2 -0
  73. package/src/serve/watch-reconciliation-shared.ts +3 -0
  74. package/src/serve/watch-service-events.ts +3 -2
  75. package/src/serve/watch-service-run-flush.ts +35 -2
  76. package/src/serve/watch-service.ts +5 -0
  77. package/src/store/migrations/031-runtime-independent-vectors.ts +29 -0
  78. package/src/store/migrations/032-vector-runtime-callers.ts +17 -0
  79. package/src/store/migrations/index.ts +4 -0
  80. package/src/store/sqlite/adapter.ts +22 -1
  81. package/src/store/types.ts +11 -1
  82. package/src/store/vector/lazy.ts +46 -43
  83. package/src/store/vector/runtime-compat.ts +651 -0
  84. package/src/store/vector/sqlite-vec.ts +20 -2
  85. package/src/store/vector/status.ts +276 -35
  86. package/src/store/vector/types.ts +2 -0
  87. package/src/store/vector/variant-search.ts +71 -23
  88. package/src/store/vector/variants.ts +49 -14
  89. package/browser-extension/artifacts/gno-browser-clipper-v2.6.0.zip.sha256 +0 -1
@@ -14,6 +14,7 @@ import type { WatchQueueHost } from "./watch-service-events";
14
14
  import type { CollectionPending } from "./watch-service-state";
15
15
  import type { WatcherSnapshot, WatcherSnapshotFs } from "./watch-snapshot";
16
16
 
17
+ import { WATCHER_LEASE_RETRY_MS } from "./watch-reconciliation-shared";
17
18
  import {
18
19
  requeueAfterFailure,
19
20
  requeueGenerationReconcile,
@@ -65,6 +66,11 @@ export interface RunFlushContext {
65
66
  snapshotFs?: WatcherSnapshotFs;
66
67
  /** Test seam: lower snapshot entry ceiling for overflow→full proofs. */
67
68
  snapshotEntryCeiling?: number;
69
+ /**
70
+ * Shared writer lease for this flush; null when another writer holds it,
71
+ * which leaves the work queued for the retry timer instead of waiting.
72
+ */
73
+ acquireWriteLease?: () => Promise<(() => Promise<void>) | null>;
68
74
  }
69
75
 
70
76
  /**
@@ -86,19 +92,44 @@ export async function runOwnedCollectionFlush(
86
92
  return;
87
93
  }
88
94
 
95
+ ctx.syncing.add(collectionName);
96
+ let releaseLease: (() => Promise<void>) | null = null;
97
+ if (ctx.acquireWriteLease) {
98
+ releaseLease = await ctx.acquireWriteLease();
99
+ if (!releaseLease || ctx.disposed()) {
100
+ ctx.syncing.delete(collectionName);
101
+ await releaseLease?.();
102
+ if (!ctx.disposed()) {
103
+ requeueAfterFailure(
104
+ ctx.queueHost,
105
+ collectionName,
106
+ [],
107
+ [],
108
+ undefined,
109
+ WATCHER_LEASE_RETRY_MS
110
+ );
111
+ }
112
+ return;
113
+ }
114
+ }
115
+
89
116
  const collection = ctx
90
117
  .collections()
91
118
  .find((entry) => entry.name === collectionName);
92
119
  if (!collection) {
120
+ await releaseLease?.();
121
+ ctx.syncing.delete(collectionName);
93
122
  ctx.pendingByCollection.delete(collectionName);
94
123
  ctx.flushDeadlineAt.delete(collectionName);
95
124
  return;
96
125
  }
97
126
 
98
- const taken = takePending(pending);
127
+ // Events that arrived while the lease was being taken join this flush.
128
+ const taken = takePending(
129
+ ctx.pendingByCollection.get(collectionName) ?? pending
130
+ );
99
131
  ctx.pendingByCollection.set(collectionName, emptyPending());
100
132
  ctx.flushDeadlineAt.delete(collectionName);
101
- ctx.syncing.add(collectionName);
102
133
 
103
134
  const ownerGeneration = ctx.collectionGenerations.get(collectionName) ?? 0;
104
135
  const ownerRoot = normalize(collection.path);
@@ -219,6 +250,8 @@ export async function runOwnedCollectionFlush(
219
250
  throw outcome.error;
220
251
  }
221
252
  } finally {
253
+ // Release before any follow-up flush below tries to take the lease again.
254
+ await releaseLease?.();
222
255
  ctx.syncing.delete(collectionName);
223
256
  ctx.clearLifecycleTombstones(collectionName);
224
257
  ctx.pruneSuppression();
@@ -108,6 +108,8 @@ interface CollectionWatchServiceOptions {
108
108
  * Production leaves this unset (uses WATCHER_SNAPSHOT_ENTRY_CEILING).
109
109
  */
110
110
  snapshotEntryCeiling?: number;
111
+ /** Shared writer lease taken (no wait) around each flush's writes. */
112
+ acquireWriteLease?: () => Promise<(() => Promise<void>) | null>;
111
113
  }
112
114
 
113
115
  export class CollectionWatchService {
@@ -146,6 +148,7 @@ export class CollectionWatchService {
146
148
  | undefined;
147
149
  readonly #snapshotFs: WatcherSnapshotFs | undefined;
148
150
  readonly #snapshotEntryCeiling: number | undefined;
151
+ readonly #acquireWriteLease: CollectionWatchServiceOptions["acquireWriteLease"];
149
152
  #nextCollectionGeneration = 0;
150
153
  #disposed = false;
151
154
  #lastEventAt: string | null = null;
@@ -169,6 +172,7 @@ export class CollectionWatchService {
169
172
  this.#buildSnapshot = options.buildSnapshot;
170
173
  this.#snapshotFs = options.snapshotFs;
171
174
  this.#snapshotEntryCeiling = options.snapshotEntryCeiling;
175
+ this.#acquireWriteLease = options.acquireWriteLease;
172
176
  }
173
177
 
174
178
  start(): void {
@@ -394,6 +398,7 @@ export class CollectionWatchService {
394
398
  },
395
399
  snapshotFs: this.#snapshotFs,
396
400
  snapshotEntryCeiling: this.#snapshotEntryCeiling,
401
+ acquireWriteLease: this.#acquireWriteLease,
397
402
  });
398
403
  }
399
404
 
@@ -0,0 +1,29 @@
1
+ /** Runtime details become provenance; existing partitions await measured re-keying. */
2
+ import type { Migration } from "./runner";
3
+
4
+ export const migration: Migration = {
5
+ version: 31,
6
+ name: "runtime_independent_vectors",
7
+ up(db): void {
8
+ db.exec(`
9
+ ALTER TABLE vector_partitions ADD COLUMN provenance TEXT;
10
+ ALTER TABLE vector_partitions ADD COLUMN legacy INTEGER NOT NULL DEFAULT 0;
11
+ -- Vector-defining key shared by a primary and its confirmed forks.
12
+ ALTER TABLE vector_partitions ADD COLUMN base_fingerprint TEXT;
13
+ -- Runtime fingerprint of a confirmed separate partition; NULL = primary.
14
+ ALTER TABLE vector_partitions ADD COLUMN fork TEXT;
15
+ UPDATE vector_partitions SET legacy = 1;
16
+ CREATE TABLE vector_runtime_verdicts (
17
+ partition_id TEXT NOT NULL,
18
+ runtime TEXT NOT NULL,
19
+ label TEXT NOT NULL,
20
+ verdict TEXT NOT NULL CHECK (verdict IN ('compatible', 'incompatible')),
21
+ min_cosine REAL NOT NULL,
22
+ samples INTEGER NOT NULL CHECK (samples >= 0),
23
+ sample_ms REAL NOT NULL,
24
+ PRIMARY KEY (partition_id, runtime)
25
+ );
26
+
27
+ `);
28
+ },
29
+ };
@@ -0,0 +1,17 @@
1
+ /** Identity each caller (process runtime + env) last resolved, for status. */
2
+ import type { Migration } from "./runner";
3
+
4
+ export const migration: Migration = {
5
+ version: 32,
6
+ name: "vector_runtime_callers",
7
+ up(db): void {
8
+ db.exec(`
9
+ CREATE TABLE IF NOT EXISTS vector_runtime_callers (
10
+ caller TEXT PRIMARY KEY,
11
+ runtime TEXT NOT NULL,
12
+ label TEXT NOT NULL,
13
+ identity TEXT NOT NULL
14
+ );
15
+ `);
16
+ },
17
+ };
@@ -44,6 +44,8 @@ import { migration as m027 } from "./027-memory-scopes";
44
44
  import { migration as m028 } from "./028-vector-variants";
45
45
  import { migration as m029 } from "./029-graph-reference-state";
46
46
  import { migration as m030 } from "./030-typed-metadata";
47
+ import { migration as m031 } from "./031-runtime-independent-vectors";
48
+ import { migration as m032 } from "./032-vector-runtime-callers";
47
49
 
48
50
  /** All migrations in order */
49
51
  export const migrations = [
@@ -77,4 +79,6 @@ export const migrations = [
77
79
  m028,
78
80
  m029,
79
81
  m030,
82
+ m031,
83
+ m032,
80
84
  ];
@@ -158,7 +158,11 @@ import { getSchemaVersion, migrations, runMigrations } from "../migrations";
158
158
  import { err, ok } from "../types";
159
159
  import { getStoredEmbeddingFingerprint } from "../vector/freshness";
160
160
  import { modelTableName } from "../vector/sqlite-vec";
161
- import { getVariantStatus } from "../vector/status";
161
+ import {
162
+ getVariantStatus,
163
+ listVectorPartitions,
164
+ vectorRuntimeStatus,
165
+ } from "../vector/status";
162
166
  import {
163
167
  deleteSavedCapsuleRegistration as deleteStoredSavedCapsuleRegistration,
164
168
  getSavedCapsuleRegistration as getStoredSavedCapsuleRegistration,
@@ -711,6 +715,11 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
711
715
  }
712
716
  }
713
717
 
718
+ /** Set this connection's SQLite busy_timeout (resident event-loop bound). */
719
+ setBusyTimeout(ms: number): void {
720
+ this.db?.exec(`PRAGMA busy_timeout = ${Math.max(0, Math.floor(ms))}`);
721
+ }
722
+
714
723
  /** Cap subsequent SQLite lock waits to the resident settlement deadline. */
715
724
  beginShutdown(deadline: number): void {
716
725
  this.shutdownDeadline = deadline;
@@ -5893,6 +5902,10 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
5893
5902
  (embedModel ? getStoredEmbeddingFingerprint(db, embedModel) : null);
5894
5903
 
5895
5904
  const variantStatus = getVariantStatus(db, options);
5905
+ const vectorPartitions = listVectorPartitions(
5906
+ db,
5907
+ embedModel ?? undefined
5908
+ );
5896
5909
 
5897
5910
  // Get version
5898
5911
  const versionRow = db
@@ -6077,6 +6090,14 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
6077
6090
  ? (variantStatus.embeddedByCollection.get(s.name) ?? 0)
6078
6091
  : s.embedded_count,
6079
6092
  })),
6093
+ ...(vectorPartitions.length
6094
+ ? {
6095
+ vectorPartitions,
6096
+ ...(embedModel
6097
+ ? { vectorRuntime: vectorRuntimeStatus(db, embedModel) }
6098
+ : {}),
6099
+ }
6100
+ : {}),
6080
6101
  totalDocuments: totalsRow?.total ?? 0,
6081
6102
  activeDocuments: totalsRow?.active ?? 0,
6082
6103
  totalChunks: chunkCount,
@@ -26,6 +26,10 @@ import type {
26
26
  ChunkingStatus,
27
27
  PendingChunkingMirror,
28
28
  } from "./chunking";
29
+ import type {
30
+ VectorPartitionStatus,
31
+ VectorRuntimeStatus,
32
+ } from "./vector/status";
29
33
 
30
34
  // ─────────────────────────────────────────────────────────────────────────────
31
35
  // Error Types
@@ -61,7 +65,9 @@ export type StoreErrorCode =
61
65
  | "VEC_SEARCH_UNAVAILABLE"
62
66
  | "VEC_SEARCH_FAILED"
63
67
  | "VEC_REBUILD_FAILED"
64
- | "VEC_SYNC_FAILED";
68
+ | "VEC_SYNC_FAILED"
69
+ /** Embedding would build a separate vector partition without confirmation. */
70
+ | "VECTOR_PARTITION_FORK";
65
71
 
66
72
  /** Store error with structured details */
67
73
  export interface StoreError {
@@ -820,6 +826,10 @@ export interface IndexStatus {
820
826
  totalChunks: number;
821
827
  /** Chunks without embeddings */
822
828
  embeddingBacklog: number;
829
+ /** Vector partitions of the status model; counts use the `retrieval` one. */
830
+ vectorPartitions?: VectorPartitionStatus[];
831
+ /** This process's runtime, resolved by retrieval's own selection rule. */
832
+ vectorRuntime?: VectorRuntimeStatus;
823
833
  /** Configuration and applied cached layouts; separate from source freshness. */
824
834
  chunking?: ChunkingStatus;
825
835
  /** Recent ingest errors (last 24h) */
@@ -5,7 +5,7 @@ import type { EmbeddingPort } from "../../llm/types";
5
5
  import type { VectorIndexPort } from "./types";
6
6
 
7
7
  import { getStoredEmbeddingDimensions } from "./freshness";
8
- import { createVectorIndexPort } from "./sqlite-vec";
8
+ import { bindVectorIndexDatabase, createVectorIndexPort } from "./sqlite-vec";
9
9
 
10
10
  export async function createLazyVectorIndex(
11
11
  db: Database,
@@ -35,47 +35,50 @@ export async function createLazyVectorIndex(
35
35
  });
36
36
  return loading;
37
37
  };
38
- return {
39
- model,
40
- get dimensions() {
41
- return current?.dimensions ?? embedPort.dimensions();
38
+ return bindVectorIndexDatabase(
39
+ {
40
+ model,
41
+ get dimensions() {
42
+ return current?.dimensions ?? embedPort.dimensions();
43
+ },
44
+ get searchAvailable() {
45
+ return current?.searchAvailable ?? true;
46
+ },
47
+ get loadError() {
48
+ return current?.loadError;
49
+ },
50
+ get guidance() {
51
+ return current?.guidance;
52
+ },
53
+ get vecDirty() {
54
+ return current?.vecDirty ?? dirty;
55
+ },
56
+ set vecDirty(value) {
57
+ dirty = value;
58
+ if (current) current.vecDirty = value;
59
+ },
60
+ async upsertVectors(rows) {
61
+ return (await get()).upsertVectors(rows);
62
+ },
63
+ async upsertVectorsChecked(rows, checkpoint) {
64
+ const port = await get();
65
+ if (!port.upsertVectorsChecked)
66
+ throw new Error("Atomic vector checkpoint unavailable");
67
+ return port.upsertVectorsChecked(rows, checkpoint);
68
+ },
69
+ async deleteVectorsForMirror(hash) {
70
+ return (await get()).deleteVectorsForMirror(hash);
71
+ },
72
+ async searchNearest(embedding, k, options) {
73
+ return (await get()).searchNearest(embedding, k, options);
74
+ },
75
+ async rebuildVecIndex() {
76
+ return (await get()).rebuildVecIndex();
77
+ },
78
+ async syncVecIndex() {
79
+ return (await get()).syncVecIndex();
80
+ },
42
81
  },
43
- get searchAvailable() {
44
- return current?.searchAvailable ?? true;
45
- },
46
- get loadError() {
47
- return current?.loadError;
48
- },
49
- get guidance() {
50
- return current?.guidance;
51
- },
52
- get vecDirty() {
53
- return current?.vecDirty ?? dirty;
54
- },
55
- set vecDirty(value) {
56
- dirty = value;
57
- if (current) current.vecDirty = value;
58
- },
59
- async upsertVectors(rows) {
60
- return (await get()).upsertVectors(rows);
61
- },
62
- async upsertVectorsChecked(rows, checkpoint) {
63
- const port = await get();
64
- if (!port.upsertVectorsChecked)
65
- throw new Error("Atomic vector checkpoint unavailable");
66
- return port.upsertVectorsChecked(rows, checkpoint);
67
- },
68
- async deleteVectorsForMirror(hash) {
69
- return (await get()).deleteVectorsForMirror(hash);
70
- },
71
- async searchNearest(embedding, k, options) {
72
- return (await get()).searchNearest(embedding, k, options);
73
- },
74
- async rebuildVecIndex() {
75
- return (await get()).rebuildVecIndex();
76
- },
77
- async syncVecIndex() {
78
- return (await get()).syncVecIndex();
79
- },
80
- };
82
+ db
83
+ );
81
84
  }