@gmickel/gno 1.37.0 → 1.38.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/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v1.37.0.zip → gno-browser-clipper-v1.38.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.38.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +35 -15
- package/src/cli/commands/cleanup.ts +8 -2
- package/src/cli/commands/collection/clear-embeddings.ts +6 -1
- package/src/cli/commands/doctor-activation.ts +5 -1
- package/src/cli/commands/doctor.ts +72 -2
- package/src/cli/commands/embed.ts +227 -194
- package/src/cli/commands/index-cmd.ts +74 -50
- package/src/cli/commands/init.ts +5 -1
- package/src/cli/commands/profile-apply.ts +5 -1
- package/src/cli/commands/setup-activation.ts +2 -1
- package/src/cli/commands/setup.ts +2 -1
- package/src/cli/commands/shared.ts +5 -1
- package/src/cli/commands/status.ts +5 -1
- package/src/cli/commands/tags.ts +18 -3
- package/src/cli/commands/update.ts +34 -27
- package/src/cli/commands/vec.ts +13 -4
- package/src/cli/errors.ts +3 -2
- package/src/cli/program.ts +345 -194
- package/src/config/defaults.ts +2 -0
- package/src/config/index.ts +3 -0
- package/src/config/types.ts +32 -1
- package/src/core/file-lock.ts +16 -4
- package/src/core/write-lease.ts +354 -0
- package/src/embed/backlog.ts +9 -1
- package/src/embed/retry.ts +116 -3
- package/src/sdk/client.ts +3 -1
- package/src/sdk/embed.ts +8 -3
- package/src/sdk/types.ts +2 -0
- package/src/serve/embed-scheduler.ts +8 -0
- package/src/serve/resident-runtime.ts +5 -1
- package/src/serve/spa-production-build.ts +53 -7
- package/src/store/sqlite/adapter.ts +28 -4
- package/src/store/sqlite/scoped-index.ts +5 -1
- package/src/store/vector/sqlite-vec.ts +2 -1
- package/browser-extension/artifacts/gno-browser-clipper-v1.37.0.zip.sha256 +0 -1
|
@@ -17,6 +17,11 @@ import {
|
|
|
17
17
|
isInitialized,
|
|
18
18
|
loadConfig,
|
|
19
19
|
} from "../../config";
|
|
20
|
+
import {
|
|
21
|
+
type CliWriteLeaseOptions,
|
|
22
|
+
type WriteLeaseContention,
|
|
23
|
+
withCliWriteLease,
|
|
24
|
+
} from "../../core/write-lease";
|
|
20
25
|
import { getEmbeddingFingerprint } from "../../embed/fingerprint";
|
|
21
26
|
import {
|
|
22
27
|
addUniqueSamples,
|
|
@@ -47,7 +52,7 @@ import {
|
|
|
47
52
|
// Types
|
|
48
53
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
49
54
|
|
|
50
|
-
export interface EmbedOptions {
|
|
55
|
+
export interface EmbedOptions extends CliWriteLeaseOptions {
|
|
51
56
|
/** Override config path */
|
|
52
57
|
configPath?: string;
|
|
53
58
|
/** Index name */
|
|
@@ -77,6 +82,8 @@ export type EmbedResult =
|
|
|
77
82
|
success: true;
|
|
78
83
|
embedded: number;
|
|
79
84
|
errors: number;
|
|
85
|
+
/** Chunks deferred by SQLITE_BUSY/LOCKED after retries (fn-127 R6). */
|
|
86
|
+
contentionErrors: number;
|
|
80
87
|
duration: number;
|
|
81
88
|
model: string;
|
|
82
89
|
searchAvailable: boolean;
|
|
@@ -84,7 +91,7 @@ export type EmbedResult =
|
|
|
84
91
|
suggestion?: string;
|
|
85
92
|
syncError?: string;
|
|
86
93
|
}
|
|
87
|
-
| { success: false; error: string };
|
|
94
|
+
| { success: false; error: string; contention?: WriteLeaseContention };
|
|
88
95
|
|
|
89
96
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
90
97
|
// Helpers
|
|
@@ -137,6 +144,7 @@ type BatchResult =
|
|
|
137
144
|
ok: true;
|
|
138
145
|
embedded: number;
|
|
139
146
|
errors: number;
|
|
147
|
+
contentionErrors: number;
|
|
140
148
|
duration: number;
|
|
141
149
|
errorSamples: string[];
|
|
142
150
|
suggestion?: string;
|
|
@@ -152,6 +160,7 @@ async function processBatches(ctx: BatchContext): Promise<BatchResult> {
|
|
|
152
160
|
const startTime = Date.now();
|
|
153
161
|
let embedded = 0;
|
|
154
162
|
let errors = 0;
|
|
163
|
+
let contentionErrors = 0;
|
|
155
164
|
const errorSamples: string[] = [];
|
|
156
165
|
let suggestion: string | undefined;
|
|
157
166
|
let cursor: Cursor | undefined;
|
|
@@ -279,6 +288,7 @@ async function processBatches(ctx: BatchContext): Promise<BatchResult> {
|
|
|
279
288
|
suggestion ||= retryResult.suggestion;
|
|
280
289
|
embedded += retryResult.embedded;
|
|
281
290
|
errors += retryResult.errors;
|
|
291
|
+
contentionErrors += retryResult.contentionErrors;
|
|
282
292
|
retryEmbedded += retryResult.embedded;
|
|
283
293
|
|
|
284
294
|
const retryByKey = new Set(
|
|
@@ -331,6 +341,7 @@ async function processBatches(ctx: BatchContext): Promise<BatchResult> {
|
|
|
331
341
|
suggestion ||= batchStoreResult.suggestion;
|
|
332
342
|
embedded += batchStoreResult.embedded;
|
|
333
343
|
errors += batchStoreResult.errors;
|
|
344
|
+
contentionErrors += batchStoreResult.contentionErrors;
|
|
334
345
|
enqueueRetryItems(batchStoreResult.retryItems, 1);
|
|
335
346
|
|
|
336
347
|
if (embedded > beforeEmbedded) {
|
|
@@ -358,6 +369,7 @@ async function processBatches(ctx: BatchContext): Promise<BatchResult> {
|
|
|
358
369
|
ok: true,
|
|
359
370
|
embedded,
|
|
360
371
|
errors,
|
|
372
|
+
contentionErrors,
|
|
361
373
|
duration: (Date.now() - startTime) / 1000,
|
|
362
374
|
errorSamples,
|
|
363
375
|
suggestion,
|
|
@@ -407,7 +419,11 @@ async function initEmbedContext(
|
|
|
407
419
|
const paths = getConfigPaths();
|
|
408
420
|
store.setConfigPath(configPath ?? paths.configFile);
|
|
409
421
|
|
|
410
|
-
const openResult = await store.open(
|
|
422
|
+
const openResult = await store.open(
|
|
423
|
+
dbPath,
|
|
424
|
+
config.ftsTokenizer,
|
|
425
|
+
config.busyTimeoutMs
|
|
426
|
+
);
|
|
411
427
|
if (!openResult.ok) {
|
|
412
428
|
return { ok: false, error: openResult.error.message };
|
|
413
429
|
}
|
|
@@ -427,226 +443,232 @@ export async function embed(options: EmbedOptions = {}): Promise<EmbedResult> {
|
|
|
427
443
|
const force = options.force ?? false;
|
|
428
444
|
const dryRun = options.dryRun ?? false;
|
|
429
445
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const { config, modelUri, store } = initResult;
|
|
441
|
-
|
|
442
|
-
// Get raw DB for vector ops (SqliteAdapter always implements SqliteDbProvider)
|
|
443
|
-
const db = store.getRawDb();
|
|
444
|
-
let embedPort: EmbeddingPort | null = null;
|
|
445
|
-
let vectorIndex: VectorIndexPort | null = null;
|
|
446
|
-
|
|
447
|
-
try {
|
|
448
|
-
// Create stats port for backlog detection
|
|
449
|
-
const stats: VectorStatsPort = createVectorStatsPort(db);
|
|
450
|
-
|
|
451
|
-
let totalToEmbed = 0;
|
|
452
|
-
if (force) {
|
|
453
|
-
const forceCount = await getActiveChunkCount(db, options.collection);
|
|
454
|
-
if (!forceCount.ok) {
|
|
455
|
-
return { success: false, error: forceCount.error.message };
|
|
456
|
-
}
|
|
457
|
-
totalToEmbed = forceCount.value;
|
|
458
|
-
|
|
459
|
-
if (totalToEmbed === 0 || dryRun) {
|
|
460
|
-
const vecAvailable = await checkVecAvailable(db);
|
|
461
|
-
return {
|
|
462
|
-
success: true,
|
|
463
|
-
embedded: totalToEmbed,
|
|
464
|
-
errors: 0,
|
|
465
|
-
duration: 0,
|
|
466
|
-
model: modelUri,
|
|
467
|
-
searchAvailable: vecAvailable,
|
|
468
|
-
errorSamples: [],
|
|
469
|
-
};
|
|
470
|
-
}
|
|
446
|
+
return await withCliWriteLease(options, async () => {
|
|
447
|
+
// Initialize config and store
|
|
448
|
+
const initResult = await initEmbedContext(
|
|
449
|
+
options.configPath,
|
|
450
|
+
options.indexName,
|
|
451
|
+
options.collection,
|
|
452
|
+
options.model
|
|
453
|
+
);
|
|
454
|
+
if (!initResult.ok) {
|
|
455
|
+
return { success: false, error: initResult.error };
|
|
471
456
|
}
|
|
457
|
+
const { config, modelUri, store } = initResult;
|
|
458
|
+
|
|
459
|
+
// Get raw DB for vector ops (SqliteAdapter always implements SqliteDbProvider)
|
|
460
|
+
const db = store.getRawDb();
|
|
461
|
+
let embedPort: EmbeddingPort | null = null;
|
|
462
|
+
let vectorIndex: VectorIndexPort | null = null;
|
|
463
|
+
|
|
464
|
+
try {
|
|
465
|
+
// Create stats port for backlog detection
|
|
466
|
+
const stats: VectorStatsPort = createVectorStatsPort(db);
|
|
467
|
+
|
|
468
|
+
let totalToEmbed = 0;
|
|
469
|
+
if (force) {
|
|
470
|
+
const forceCount = await getActiveChunkCount(db, options.collection);
|
|
471
|
+
if (!forceCount.ok) {
|
|
472
|
+
return { success: false, error: forceCount.error.message };
|
|
473
|
+
}
|
|
474
|
+
totalToEmbed = forceCount.value;
|
|
475
|
+
|
|
476
|
+
if (totalToEmbed === 0 || dryRun) {
|
|
477
|
+
const vecAvailable = await checkVecAvailable(db);
|
|
478
|
+
return {
|
|
479
|
+
success: true,
|
|
480
|
+
embedded: totalToEmbed,
|
|
481
|
+
errors: 0,
|
|
482
|
+
contentionErrors: 0,
|
|
483
|
+
duration: 0,
|
|
484
|
+
model: modelUri,
|
|
485
|
+
searchAvailable: vecAvailable,
|
|
486
|
+
errorSamples: [],
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
}
|
|
472
490
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
491
|
+
// Create LLM adapter and embedding port with auto-download
|
|
492
|
+
let offline = options.offline;
|
|
493
|
+
if (offline === undefined) {
|
|
494
|
+
offline = getGlobals().offline;
|
|
495
|
+
}
|
|
496
|
+
const policy = resolveDownloadPolicy(process.env, {
|
|
497
|
+
offline,
|
|
498
|
+
});
|
|
481
499
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
500
|
+
// Create progress renderer for model download (throttled to avoid spam)
|
|
501
|
+
const showDownloadProgress = !options.json && process.stderr.isTTY;
|
|
502
|
+
const downloadProgress = showDownloadProgress
|
|
503
|
+
? createThrottledProgressRenderer(createProgressRenderer())
|
|
504
|
+
: undefined;
|
|
487
505
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
506
|
+
const llm = new LlmAdapter(config);
|
|
507
|
+
const recreateEmbedPort = async () => {
|
|
508
|
+
if (embedPort) {
|
|
509
|
+
await embedPort.dispose();
|
|
510
|
+
}
|
|
511
|
+
await llm.getManager().dispose(modelUri);
|
|
512
|
+
const recreated = await llm.createEmbeddingPort(modelUri, {
|
|
513
|
+
egressCollections: options.collection ? [options.collection] : "all",
|
|
514
|
+
policy,
|
|
515
|
+
onProgress: downloadProgress
|
|
516
|
+
? (progress) => downloadProgress("embed", progress)
|
|
517
|
+
: undefined,
|
|
518
|
+
});
|
|
519
|
+
if (!recreated.ok) {
|
|
520
|
+
return { ok: false as const, error: recreated.error.message };
|
|
521
|
+
}
|
|
522
|
+
const initResult = await recreated.value.init();
|
|
523
|
+
if (!initResult.ok) {
|
|
524
|
+
await recreated.value.dispose();
|
|
525
|
+
return { ok: false as const, error: initResult.error.message };
|
|
526
|
+
}
|
|
527
|
+
return { ok: true as const, value: recreated.value };
|
|
528
|
+
};
|
|
529
|
+
const embedResult = await llm.createEmbeddingPort(modelUri, {
|
|
495
530
|
egressCollections: options.collection ? [options.collection] : "all",
|
|
496
531
|
policy,
|
|
497
532
|
onProgress: downloadProgress
|
|
498
533
|
? (progress) => downloadProgress("embed", progress)
|
|
499
534
|
: undefined,
|
|
500
535
|
});
|
|
501
|
-
if (!
|
|
502
|
-
return {
|
|
503
|
-
}
|
|
504
|
-
const initResult = await recreated.value.init();
|
|
505
|
-
if (!initResult.ok) {
|
|
506
|
-
await recreated.value.dispose();
|
|
507
|
-
return { ok: false as const, error: initResult.error.message };
|
|
536
|
+
if (!embedResult.ok) {
|
|
537
|
+
return { success: false, error: embedResult.error.message };
|
|
508
538
|
}
|
|
509
|
-
|
|
510
|
-
};
|
|
511
|
-
const embedResult = await llm.createEmbeddingPort(modelUri, {
|
|
512
|
-
egressCollections: options.collection ? [options.collection] : "all",
|
|
513
|
-
policy,
|
|
514
|
-
onProgress: downloadProgress
|
|
515
|
-
? (progress) => downloadProgress("embed", progress)
|
|
516
|
-
: undefined,
|
|
517
|
-
});
|
|
518
|
-
if (!embedResult.ok) {
|
|
519
|
-
return { success: false, error: embedResult.error.message };
|
|
520
|
-
}
|
|
521
|
-
embedPort = embedResult.value;
|
|
539
|
+
embedPort = embedResult.value;
|
|
522
540
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
// Discover dimensions via probe embedding
|
|
529
|
-
const probeResult = await embedPort.embed("dimension probe");
|
|
530
|
-
if (!probeResult.ok) {
|
|
531
|
-
return { success: false, error: probeResult.error.message };
|
|
532
|
-
}
|
|
533
|
-
const dimensions = probeResult.value.length;
|
|
541
|
+
// Clear download progress line if shown
|
|
542
|
+
if (showDownloadProgress) {
|
|
543
|
+
process.stderr.write("\n");
|
|
544
|
+
}
|
|
534
545
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
return { success: false, error: vectorResult.error.message };
|
|
542
|
-
}
|
|
543
|
-
vectorIndex = vectorResult.value;
|
|
546
|
+
// Discover dimensions via probe embedding
|
|
547
|
+
const probeResult = await embedPort.embed("dimension probe");
|
|
548
|
+
if (!probeResult.ok) {
|
|
549
|
+
return { success: false, error: probeResult.error.message };
|
|
550
|
+
}
|
|
551
|
+
const dimensions = probeResult.value.length;
|
|
544
552
|
|
|
545
|
-
|
|
546
|
-
const
|
|
547
|
-
modelUri,
|
|
553
|
+
// Create vector index port
|
|
554
|
+
const vectorResult = await createVectorIndexPort(db, {
|
|
555
|
+
model: modelUri,
|
|
548
556
|
dimensions,
|
|
549
557
|
});
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
embedFingerprint,
|
|
553
|
-
{
|
|
554
|
-
collection: options.collection,
|
|
555
|
-
}
|
|
556
|
-
);
|
|
557
|
-
|
|
558
|
-
if (!backlogResult.ok) {
|
|
559
|
-
return { success: false, error: backlogResult.error.message };
|
|
558
|
+
if (!vectorResult.ok) {
|
|
559
|
+
return { success: false, error: vectorResult.error.message };
|
|
560
560
|
}
|
|
561
|
+
vectorIndex = vectorResult.value;
|
|
562
|
+
|
|
563
|
+
if (!force) {
|
|
564
|
+
const embedFingerprint = getEmbeddingFingerprint({
|
|
565
|
+
modelUri,
|
|
566
|
+
dimensions,
|
|
567
|
+
});
|
|
568
|
+
const backlogResult = await stats.countBacklog(
|
|
569
|
+
modelUri,
|
|
570
|
+
embedFingerprint,
|
|
571
|
+
{
|
|
572
|
+
collection: options.collection,
|
|
573
|
+
}
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
if (!backlogResult.ok) {
|
|
577
|
+
return { success: false, error: backlogResult.error.message };
|
|
578
|
+
}
|
|
561
579
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
580
|
+
totalToEmbed = backlogResult.value;
|
|
581
|
+
|
|
582
|
+
if (totalToEmbed === 0 || dryRun) {
|
|
583
|
+
return {
|
|
584
|
+
success: true,
|
|
585
|
+
embedded: totalToEmbed,
|
|
586
|
+
errors: 0,
|
|
587
|
+
contentionErrors: 0,
|
|
588
|
+
duration: 0,
|
|
589
|
+
model: modelUri,
|
|
590
|
+
searchAvailable: vectorIndex.searchAvailable,
|
|
591
|
+
errorSamples: [],
|
|
592
|
+
};
|
|
593
|
+
}
|
|
574
594
|
}
|
|
575
|
-
}
|
|
576
595
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
596
|
+
// Process batches
|
|
597
|
+
const result = await processBatches({
|
|
598
|
+
db,
|
|
599
|
+
stats,
|
|
600
|
+
embedPort,
|
|
601
|
+
vectorIndex,
|
|
602
|
+
modelUri,
|
|
603
|
+
collection: options.collection,
|
|
604
|
+
batchSize,
|
|
605
|
+
force,
|
|
606
|
+
showProgress: !options.json,
|
|
607
|
+
totalToEmbed,
|
|
608
|
+
verbose: options.verbose ?? false,
|
|
609
|
+
recreateEmbedPort,
|
|
610
|
+
});
|
|
592
611
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
612
|
+
if (!result.ok) {
|
|
613
|
+
return { success: false, error: result.error };
|
|
614
|
+
}
|
|
596
615
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
616
|
+
// Sync vec index if any vec0 writes failed (matches embedBacklog behavior)
|
|
617
|
+
if (vectorIndex.vecDirty) {
|
|
618
|
+
const syncResult = await vectorIndex.syncVecIndex();
|
|
619
|
+
if (syncResult.ok) {
|
|
620
|
+
const { added, removed } = syncResult.value;
|
|
621
|
+
if (added > 0 || removed > 0) {
|
|
622
|
+
if (!options.json) {
|
|
623
|
+
process.stdout.write(
|
|
624
|
+
`\n[vec] Synced index: +${added} -${removed}\n`
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
vectorIndex.vecDirty = false;
|
|
629
|
+
} else {
|
|
603
630
|
if (!options.json) {
|
|
604
631
|
process.stdout.write(
|
|
605
|
-
`\n[vec]
|
|
632
|
+
`\n[vec] Sync failed: ${syncResult.error.message}\n`
|
|
606
633
|
);
|
|
607
634
|
}
|
|
635
|
+
return {
|
|
636
|
+
success: true,
|
|
637
|
+
embedded: result.embedded,
|
|
638
|
+
errors: result.errors,
|
|
639
|
+
contentionErrors: result.contentionErrors,
|
|
640
|
+
duration: result.duration,
|
|
641
|
+
model: modelUri,
|
|
642
|
+
searchAvailable: vectorIndex.searchAvailable,
|
|
643
|
+
errorSamples: [
|
|
644
|
+
...result.errorSamples,
|
|
645
|
+
syncResult.error.message,
|
|
646
|
+
].slice(0, 5),
|
|
647
|
+
suggestion:
|
|
648
|
+
"Vector index sync failed after embedding. Rerun `gno embed` once more. If it repeats, run `gno vec sync`.",
|
|
649
|
+
syncError: syncResult.error.message,
|
|
650
|
+
};
|
|
608
651
|
}
|
|
609
|
-
vectorIndex.vecDirty = false;
|
|
610
|
-
} else {
|
|
611
|
-
if (!options.json) {
|
|
612
|
-
process.stdout.write(
|
|
613
|
-
`\n[vec] Sync failed: ${syncResult.error.message}\n`
|
|
614
|
-
);
|
|
615
|
-
}
|
|
616
|
-
return {
|
|
617
|
-
success: true,
|
|
618
|
-
embedded: result.embedded,
|
|
619
|
-
errors: result.errors,
|
|
620
|
-
duration: result.duration,
|
|
621
|
-
model: modelUri,
|
|
622
|
-
searchAvailable: vectorIndex.searchAvailable,
|
|
623
|
-
errorSamples: [
|
|
624
|
-
...result.errorSamples,
|
|
625
|
-
syncResult.error.message,
|
|
626
|
-
].slice(0, 5),
|
|
627
|
-
suggestion:
|
|
628
|
-
"Vector index sync failed after embedding. Rerun `gno embed` once more. If it repeats, run `gno vec sync`.",
|
|
629
|
-
syncError: syncResult.error.message,
|
|
630
|
-
};
|
|
631
652
|
}
|
|
632
|
-
}
|
|
633
653
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
654
|
+
return {
|
|
655
|
+
success: true,
|
|
656
|
+
embedded: result.embedded,
|
|
657
|
+
errors: result.errors,
|
|
658
|
+
contentionErrors: result.contentionErrors,
|
|
659
|
+
duration: result.duration,
|
|
660
|
+
model: modelUri,
|
|
661
|
+
searchAvailable: vectorIndex.searchAvailable,
|
|
662
|
+
errorSamples: result.errorSamples,
|
|
663
|
+
suggestion: result.suggestion,
|
|
664
|
+
};
|
|
665
|
+
} finally {
|
|
666
|
+
if (embedPort) {
|
|
667
|
+
await embedPort.dispose();
|
|
668
|
+
}
|
|
669
|
+
await store.close();
|
|
647
670
|
}
|
|
648
|
-
|
|
649
|
-
}
|
|
671
|
+
});
|
|
650
672
|
}
|
|
651
673
|
|
|
652
674
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -761,6 +783,7 @@ export function formatEmbed(
|
|
|
761
783
|
{
|
|
762
784
|
embedded: result.embedded,
|
|
763
785
|
errors: result.errors,
|
|
786
|
+
contentionErrors: result.contentionErrors,
|
|
764
787
|
duration: result.duration,
|
|
765
788
|
model: result.model,
|
|
766
789
|
searchAvailable: result.searchAvailable,
|
|
@@ -777,7 +800,11 @@ export function formatEmbed(
|
|
|
777
800
|
return `Dry run: would embed ${result.embedded.toLocaleString()} chunks with model ${result.model}`;
|
|
778
801
|
}
|
|
779
802
|
|
|
780
|
-
if (
|
|
803
|
+
if (
|
|
804
|
+
result.embedded === 0 &&
|
|
805
|
+
result.errors === 0 &&
|
|
806
|
+
result.contentionErrors === 0
|
|
807
|
+
) {
|
|
781
808
|
return "No chunks need embedding. All up to date.";
|
|
782
809
|
}
|
|
783
810
|
|
|
@@ -798,6 +825,12 @@ export function formatEmbed(
|
|
|
798
825
|
}
|
|
799
826
|
}
|
|
800
827
|
|
|
828
|
+
if (result.contentionErrors > 0) {
|
|
829
|
+
lines.push(
|
|
830
|
+
`${result.contentionErrors} chunks deferred by index contention (SQLITE_BUSY) — not embedding failures. Rerun \`gno embed\` when the other writer finishes.`
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
|
|
801
834
|
if (!result.searchAvailable) {
|
|
802
835
|
lines.push(
|
|
803
836
|
"Warning: sqlite-vec not available. Embeddings stored but KNN search disabled."
|