@gmickel/gno 1.37.1 → 1.39.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/skill/README.md +2 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v1.37.1.zip → gno-browser-clipper-v1.39.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.39.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +243 -26
- package/spec/output-schemas/agents-mutation.schema.json +108 -0
- package/spec/output-schemas/agents-verify.schema.json +89 -0
- package/src/cli/commands/agents/block.ts +164 -0
- package/src/cli/commands/agents/commands.ts +413 -0
- package/src/cli/commands/agents/engine.ts +417 -0
- package/src/cli/commands/agents/harnesses.ts +298 -0
- package/src/cli/commands/agents/index.ts +35 -0
- package/src/cli/commands/cleanup.ts +8 -2
- package/src/cli/commands/collection/clear-embeddings.ts +6 -1
- package/src/cli/commands/completion/scripts.ts +5 -0
- 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 +449 -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/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.1.zip.sha256 +0 -1
package/src/embed/retry.ts
CHANGED
|
@@ -1,15 +1,36 @@
|
|
|
1
1
|
import type { EmbeddingPort } from "../llm/types";
|
|
2
|
+
import type { StoreResult } from "../store/types";
|
|
2
3
|
import type { BacklogItem, VectorIndexPort, VectorRow } from "../store/vector";
|
|
3
4
|
|
|
5
|
+
import { isSqliteLockContention } from "../core/file-lock";
|
|
4
6
|
import { formatDocForEmbedding } from "../pipeline/contextual";
|
|
5
7
|
import { embedTextsWithRecovery } from "./batch";
|
|
6
8
|
|
|
7
9
|
export const MAX_EMBED_CHUNK_ATTEMPTS = 2;
|
|
8
10
|
export const MAX_EMBED_FAILURE_SAMPLES = 5;
|
|
9
11
|
|
|
12
|
+
/** Total upsert attempts (initial + retries) when persistence hits SQLITE_BUSY/LOCKED. */
|
|
13
|
+
export const UPSERT_CONTENTION_MAX_ATTEMPTS = 5;
|
|
14
|
+
export const UPSERT_CONTENTION_BASE_DELAY_MS = 250;
|
|
15
|
+
export const UPSERT_CONTENTION_BACKOFF_FACTOR = 2;
|
|
16
|
+
export const UPSERT_CONTENTION_JITTER_RATIO = 0.25;
|
|
17
|
+
export const UPSERT_CONTENTION_MAX_DELAY_MS = 5_000;
|
|
18
|
+
|
|
19
|
+
export const UPSERT_CONTENTION_ERROR_SAMPLE =
|
|
20
|
+
"index is busy (SQLITE_BUSY) — another writer holds the database; rerun `gno embed`";
|
|
21
|
+
|
|
22
|
+
export const STORE_WRITE_FAILURE_SUGGESTION =
|
|
23
|
+
"Store write failed. Rerun `gno embed` once more; if it repeats, run `gno doctor` and `gno vec sync`.";
|
|
24
|
+
|
|
10
25
|
export interface EmbedStoreBatchResult {
|
|
11
26
|
embedded: number;
|
|
12
27
|
errors: number;
|
|
28
|
+
/**
|
|
29
|
+
* Chunks whose persistence failed after SQLITE_BUSY/SQLITE_LOCKED retries.
|
|
30
|
+
* Distinct from `errors` (embedding-provider / non-contention store failures).
|
|
31
|
+
* Default 0.
|
|
32
|
+
*/
|
|
33
|
+
contentionErrors: number;
|
|
13
34
|
retryItems: BacklogItem[];
|
|
14
35
|
errorSamples: string[];
|
|
15
36
|
suggestion?: string;
|
|
@@ -17,6 +38,10 @@ export interface EmbedStoreBatchResult {
|
|
|
17
38
|
batchError?: string;
|
|
18
39
|
}
|
|
19
40
|
|
|
41
|
+
// fn-127 integration: CLI consumers (src/cli/commands/embed.ts,
|
|
42
|
+
// src/cli/commands/index-cmd.ts) must surface contentionErrors in their
|
|
43
|
+
// summary and exit non-zero — the integrator wires that.
|
|
44
|
+
|
|
20
45
|
export function chunkRetryKey(item: Pick<BacklogItem, "mirrorHash" | "seq">) {
|
|
21
46
|
return `${item.mirrorHash}\0${item.seq}`;
|
|
22
47
|
}
|
|
@@ -52,12 +77,81 @@ export function formatLlmFailure(
|
|
|
52
77
|
: error.message;
|
|
53
78
|
}
|
|
54
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Classify a store-layer failure as SQLite lock contention.
|
|
82
|
+
* Matches SQLITE_BUSY/LOCKED on the error itself (stubs) or its `cause`
|
|
83
|
+
* (upsertVectors preserves the original SQLite error there).
|
|
84
|
+
*/
|
|
85
|
+
export function isUpsertLockContention(error: {
|
|
86
|
+
code?: unknown;
|
|
87
|
+
cause?: unknown;
|
|
88
|
+
}): boolean {
|
|
89
|
+
return isSqliteLockContention(error) || isSqliteLockContention(error.cause);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Delay before the next upsert retry after `failedAttemptIndex` (0-based)
|
|
94
|
+
* contention failures. Formula: base * factor^attempt ± jitter, capped.
|
|
95
|
+
*/
|
|
96
|
+
export function upsertContentionDelayMs(
|
|
97
|
+
failedAttemptIndex: number,
|
|
98
|
+
random: () => number = Math.random
|
|
99
|
+
): number {
|
|
100
|
+
const exponential =
|
|
101
|
+
UPSERT_CONTENTION_BASE_DELAY_MS *
|
|
102
|
+
UPSERT_CONTENTION_BACKOFF_FACTOR ** failedAttemptIndex;
|
|
103
|
+
const jitterMultiplier =
|
|
104
|
+
1 + (random() * 2 - 1) * UPSERT_CONTENTION_JITTER_RATIO;
|
|
105
|
+
return Math.min(
|
|
106
|
+
UPSERT_CONTENTION_MAX_DELAY_MS,
|
|
107
|
+
Math.max(0, exponential * jitterMultiplier)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function resolveContentionDelayMs(
|
|
112
|
+
failedAttemptIndex: number,
|
|
113
|
+
delays: number[] | undefined
|
|
114
|
+
): number {
|
|
115
|
+
if (delays) {
|
|
116
|
+
return delays[failedAttemptIndex] ?? 0;
|
|
117
|
+
}
|
|
118
|
+
return upsertContentionDelayMs(failedAttemptIndex);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Retry `upsertVectors` on SQLITE_BUSY/SQLITE_LOCKED with exponential backoff.
|
|
123
|
+
* `delays` is a test seam that replaces the computed schedule (missing slots = 0).
|
|
124
|
+
*/
|
|
125
|
+
export async function upsertVectorsWithContentionRetry(
|
|
126
|
+
vectorIndex: Pick<VectorIndexPort, "upsertVectors">,
|
|
127
|
+
vectors: VectorRow[],
|
|
128
|
+
delays?: number[]
|
|
129
|
+
): Promise<StoreResult<void>> {
|
|
130
|
+
let storeResult = await vectorIndex.upsertVectors(vectors);
|
|
131
|
+
let attempts = 1;
|
|
132
|
+
while (
|
|
133
|
+
!storeResult.ok &&
|
|
134
|
+
isUpsertLockContention(storeResult.error) &&
|
|
135
|
+
attempts < UPSERT_CONTENTION_MAX_ATTEMPTS
|
|
136
|
+
) {
|
|
137
|
+
const delayMs = resolveContentionDelayMs(attempts - 1, delays);
|
|
138
|
+
if (delayMs > 0) {
|
|
139
|
+
await Bun.sleep(delayMs);
|
|
140
|
+
}
|
|
141
|
+
storeResult = await vectorIndex.upsertVectors(vectors);
|
|
142
|
+
attempts += 1;
|
|
143
|
+
}
|
|
144
|
+
return storeResult;
|
|
145
|
+
}
|
|
146
|
+
|
|
55
147
|
export async function embedAndStoreBatch(params: {
|
|
56
148
|
embedPort: EmbeddingPort;
|
|
57
149
|
vectorIndex: VectorIndexPort;
|
|
58
150
|
items: BacklogItem[];
|
|
59
151
|
modelUri: string;
|
|
60
152
|
embedFingerprint: string;
|
|
153
|
+
/** Test seam: override contention-retry delays in milliseconds. */
|
|
154
|
+
delays?: number[];
|
|
61
155
|
}): Promise<EmbedStoreBatchResult> {
|
|
62
156
|
const { embedPort, vectorIndex, items, modelUri, embedFingerprint } = params;
|
|
63
157
|
const embedResult = await embedTextsWithRecovery(
|
|
@@ -72,6 +166,7 @@ export async function embedAndStoreBatch(params: {
|
|
|
72
166
|
return {
|
|
73
167
|
embedded: 0,
|
|
74
168
|
errors: embedResult.error.retryable ? 0 : items.length,
|
|
169
|
+
contentionErrors: 0,
|
|
75
170
|
retryItems: embedResult.error.retryable ? items : [],
|
|
76
171
|
errorSamples: [formattedError],
|
|
77
172
|
suggestion: embedResult.error.retryable
|
|
@@ -103,6 +198,7 @@ export async function embedAndStoreBatch(params: {
|
|
|
103
198
|
return {
|
|
104
199
|
embedded: 0,
|
|
105
200
|
errors: 0,
|
|
201
|
+
contentionErrors: 0,
|
|
106
202
|
retryItems,
|
|
107
203
|
errorSamples: embedResult.value.failureSamples,
|
|
108
204
|
suggestion: embedResult.value.retrySuggestion,
|
|
@@ -111,15 +207,31 @@ export async function embedAndStoreBatch(params: {
|
|
|
111
207
|
};
|
|
112
208
|
}
|
|
113
209
|
|
|
114
|
-
const storeResult = await
|
|
210
|
+
const storeResult = await upsertVectorsWithContentionRetry(
|
|
211
|
+
vectorIndex,
|
|
212
|
+
vectors,
|
|
213
|
+
params.delays
|
|
214
|
+
);
|
|
115
215
|
if (!storeResult.ok) {
|
|
216
|
+
if (isUpsertLockContention(storeResult.error)) {
|
|
217
|
+
return {
|
|
218
|
+
embedded: 0,
|
|
219
|
+
errors: 0,
|
|
220
|
+
contentionErrors: vectors.length,
|
|
221
|
+
retryItems,
|
|
222
|
+
errorSamples: [UPSERT_CONTENTION_ERROR_SAMPLE],
|
|
223
|
+
suggestion: UPSERT_CONTENTION_ERROR_SAMPLE,
|
|
224
|
+
batchFailed: embedResult.value.batchFailed,
|
|
225
|
+
batchError: embedResult.value.batchError,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
116
228
|
return {
|
|
117
229
|
embedded: 0,
|
|
118
230
|
errors: vectors.length,
|
|
231
|
+
contentionErrors: 0,
|
|
119
232
|
retryItems,
|
|
120
233
|
errorSamples: [storeResult.error.message],
|
|
121
|
-
suggestion:
|
|
122
|
-
"Store write failed. Rerun `gno embed` once more; if it repeats, run `gno doctor` and `gno vec sync`.",
|
|
234
|
+
suggestion: STORE_WRITE_FAILURE_SUGGESTION,
|
|
123
235
|
batchFailed: embedResult.value.batchFailed,
|
|
124
236
|
batchError: embedResult.value.batchError,
|
|
125
237
|
};
|
|
@@ -128,6 +240,7 @@ export async function embedAndStoreBatch(params: {
|
|
|
128
240
|
return {
|
|
129
241
|
embedded: vectors.length,
|
|
130
242
|
errors: 0,
|
|
243
|
+
contentionErrors: 0,
|
|
131
244
|
retryItems,
|
|
132
245
|
errorSamples: embedResult.value.failureSamples,
|
|
133
246
|
suggestion: embedResult.value.retrySuggestion,
|
package/src/sdk/client.ts
CHANGED
|
@@ -276,7 +276,9 @@ async function resolveClientState(
|
|
|
276
276
|
|
|
277
277
|
const store = new SqliteAdapter();
|
|
278
278
|
store.setConfigPath(configPath ?? "<inline-config>");
|
|
279
|
-
unwrapStore(
|
|
279
|
+
unwrapStore(
|
|
280
|
+
await store.open(dbPath, config.ftsTokenizer, config.busyTimeoutMs)
|
|
281
|
+
);
|
|
280
282
|
unwrapStore(await store.syncCollections(config.collections));
|
|
281
283
|
unwrapStore(await store.syncContexts(config.contexts ?? []));
|
|
282
284
|
|
package/src/sdk/embed.ts
CHANGED
|
@@ -117,9 +117,10 @@ async function forceEmbedAll(
|
|
|
117
117
|
vectorIndex: VectorIndexPort,
|
|
118
118
|
modelUri: string,
|
|
119
119
|
batchSize: number
|
|
120
|
-
): Promise<{ embedded: number; errors: number }> {
|
|
120
|
+
): Promise<{ embedded: number; errors: number; contentionErrors: number }> {
|
|
121
121
|
let embedded = 0;
|
|
122
122
|
let errors = 0;
|
|
123
|
+
let contentionErrors = 0;
|
|
123
124
|
let cursor: { mirrorHash: string; seq: number } | undefined;
|
|
124
125
|
const retryQueue = new Map<string, { item: BacklogItem; attempts: number }>();
|
|
125
126
|
const embedFingerprint = getEmbeddingFingerprint({
|
|
@@ -164,6 +165,7 @@ async function forceEmbedAll(
|
|
|
164
165
|
});
|
|
165
166
|
embedded += retryResult.embedded;
|
|
166
167
|
errors += retryResult.errors;
|
|
168
|
+
contentionErrors += retryResult.contentionErrors;
|
|
167
169
|
retryEmbedded += retryResult.embedded;
|
|
168
170
|
|
|
169
171
|
const retryByKey = new Set(
|
|
@@ -212,6 +214,7 @@ async function forceEmbedAll(
|
|
|
212
214
|
});
|
|
213
215
|
embedded += embedResult.embedded;
|
|
214
216
|
errors += embedResult.errors;
|
|
217
|
+
contentionErrors += embedResult.contentionErrors;
|
|
215
218
|
enqueueRetryItems(embedResult.retryItems, 1);
|
|
216
219
|
|
|
217
220
|
if (embedded > beforeEmbedded) {
|
|
@@ -232,7 +235,7 @@ async function forceEmbedAll(
|
|
|
232
235
|
}
|
|
233
236
|
}
|
|
234
237
|
|
|
235
|
-
return { embedded, errors };
|
|
238
|
+
return { embedded, errors, contentionErrors };
|
|
236
239
|
}
|
|
237
240
|
|
|
238
241
|
async function checkVecAvailable(db: Database): Promise<boolean> {
|
|
@@ -343,7 +346,7 @@ export async function runEmbed(
|
|
|
343
346
|
}
|
|
344
347
|
|
|
345
348
|
const startedAt = Date.now();
|
|
346
|
-
let result: { embedded: number; errors: number };
|
|
349
|
+
let result: { embedded: number; errors: number; contentionErrors: number };
|
|
347
350
|
if (force) {
|
|
348
351
|
result = await forceEmbedAll(
|
|
349
352
|
db,
|
|
@@ -369,12 +372,14 @@ export async function runEmbed(
|
|
|
369
372
|
result = {
|
|
370
373
|
embedded: processed.value.embedded,
|
|
371
374
|
errors: processed.value.errors,
|
|
375
|
+
contentionErrors: processed.value.contentionErrors ?? 0,
|
|
372
376
|
};
|
|
373
377
|
}
|
|
374
378
|
|
|
375
379
|
return {
|
|
376
380
|
embedded: result.embedded,
|
|
377
381
|
errors: result.errors,
|
|
382
|
+
contentionErrors: result.contentionErrors,
|
|
378
383
|
duration: (Date.now() - startedAt) / 1000,
|
|
379
384
|
model: modelUri,
|
|
380
385
|
searchAvailable: vectorIndex.searchAvailable,
|
package/src/sdk/types.ts
CHANGED
|
@@ -204,6 +204,8 @@ export interface GnoEmbedOptions {
|
|
|
204
204
|
export interface GnoEmbedResult {
|
|
205
205
|
embedded: number;
|
|
206
206
|
errors: number;
|
|
207
|
+
/** Persistence lock-contention failures; distinct from embedding-provider `errors`. */
|
|
208
|
+
contentionErrors?: number;
|
|
207
209
|
duration: number;
|
|
208
210
|
model: string;
|
|
209
211
|
searchAvailable: boolean;
|
|
@@ -130,6 +130,14 @@ export function createEmbedScheduler(deps: EmbedSchedulerDeps): EmbedScheduler {
|
|
|
130
130
|
console.error("[embed-scheduler] Embed failed:", result.error.message);
|
|
131
131
|
return { embedded: 0, errors: 0 };
|
|
132
132
|
}
|
|
133
|
+
if ((result.value.contentionErrors ?? 0) > 0) {
|
|
134
|
+
// Chunks deferred by SQLITE_BUSY stay in the backlog; retry the run
|
|
135
|
+
// instead of silently leaving embeddings a pass behind (fn-127 R6).
|
|
136
|
+
console.error(
|
|
137
|
+
`[embed-scheduler] ${result.value.contentionErrors} chunks deferred by index contention; rescheduling`
|
|
138
|
+
);
|
|
139
|
+
needsRerun = true;
|
|
140
|
+
}
|
|
133
141
|
if (result.value.embedded > 0) onEmbedded?.(result.value);
|
|
134
142
|
return result.value;
|
|
135
143
|
} finally {
|
|
@@ -212,7 +212,11 @@ export async function startResidentRuntime(
|
|
|
212
212
|
const paths = (deps.getConfigPaths ?? getConfigPaths)();
|
|
213
213
|
const actualConfigPath = resolve(options.configPath ?? paths.configFile);
|
|
214
214
|
store.setConfigPath(actualConfigPath);
|
|
215
|
-
const openResult = await store.open(
|
|
215
|
+
const openResult = await store.open(
|
|
216
|
+
dbPath,
|
|
217
|
+
initialConfig.ftsTokenizer,
|
|
218
|
+
initialConfig.busyTimeoutMs
|
|
219
|
+
);
|
|
216
220
|
if (!openResult.ok) {
|
|
217
221
|
await ownerLock.release();
|
|
218
222
|
return { success: false, error: openResult.error.message };
|
|
@@ -114,9 +114,12 @@ import { buildUri, deriveDocid, stripUriIndex } from "../../app/constants";
|
|
|
114
114
|
import {
|
|
115
115
|
type Collection,
|
|
116
116
|
type Context,
|
|
117
|
+
DEFAULT_BUSY_TIMEOUT_MS,
|
|
117
118
|
type EgressPolicy,
|
|
118
119
|
type EgressPolicySource,
|
|
119
120
|
type FtsTokenizer,
|
|
121
|
+
MAX_BUSY_TIMEOUT_MS,
|
|
122
|
+
MIN_BUSY_TIMEOUT_MS,
|
|
120
123
|
resolveConfiguredEgressPolicy,
|
|
121
124
|
} from "../../config/types";
|
|
122
125
|
import {
|
|
@@ -476,6 +479,19 @@ function isDatabaseLockedError(cause: unknown): boolean {
|
|
|
476
479
|
);
|
|
477
480
|
}
|
|
478
481
|
|
|
482
|
+
/** Resolve a caller-supplied busy_timeout, defaulting rather than using 0. */
|
|
483
|
+
function resolveBusyTimeoutMs(busyTimeoutMs?: number): number {
|
|
484
|
+
if (
|
|
485
|
+
busyTimeoutMs === undefined ||
|
|
486
|
+
!Number.isInteger(busyTimeoutMs) ||
|
|
487
|
+
busyTimeoutMs < MIN_BUSY_TIMEOUT_MS ||
|
|
488
|
+
busyTimeoutMs > MAX_BUSY_TIMEOUT_MS
|
|
489
|
+
) {
|
|
490
|
+
return DEFAULT_BUSY_TIMEOUT_MS;
|
|
491
|
+
}
|
|
492
|
+
return busyTimeoutMs;
|
|
493
|
+
}
|
|
494
|
+
|
|
479
495
|
export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
480
496
|
private db: Database | null = null;
|
|
481
497
|
private dbPath = "";
|
|
@@ -492,7 +508,8 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
492
508
|
|
|
493
509
|
async open(
|
|
494
510
|
dbPath: string,
|
|
495
|
-
ftsTokenizer: FtsTokenizer
|
|
511
|
+
ftsTokenizer: FtsTokenizer,
|
|
512
|
+
busyTimeoutMs: number = DEFAULT_BUSY_TIMEOUT_MS
|
|
496
513
|
): Promise<StoreResult<MigrationResult>> {
|
|
497
514
|
try {
|
|
498
515
|
this.db = new Database(dbPath, { create: true });
|
|
@@ -501,7 +518,9 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
501
518
|
|
|
502
519
|
// Enable pragmas for performance and safety
|
|
503
520
|
this.db.exec("PRAGMA foreign_keys = ON");
|
|
504
|
-
this.db.exec(
|
|
521
|
+
this.db.exec(
|
|
522
|
+
`PRAGMA busy_timeout = ${resolveBusyTimeoutMs(busyTimeoutMs)}`
|
|
523
|
+
);
|
|
505
524
|
|
|
506
525
|
// Keep WAL everywhere so readers can continue while a writer is active.
|
|
507
526
|
// CI still relaxes fsync/temp-store for speed, but MEMORY journal mode
|
|
@@ -558,12 +577,17 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
558
577
|
}
|
|
559
578
|
|
|
560
579
|
/** Open an existing index with SQLite enforced query-only semantics. */
|
|
561
|
-
openReadOnly(
|
|
580
|
+
openReadOnly(
|
|
581
|
+
dbPath: string,
|
|
582
|
+
busyTimeoutMs: number = DEFAULT_BUSY_TIMEOUT_MS
|
|
583
|
+
): StoreResult<void> {
|
|
562
584
|
try {
|
|
563
585
|
this.db = new Database(dbPath, { readonly: true, strict: true });
|
|
564
586
|
this.dbPath = dbPath;
|
|
565
587
|
this.db.exec("PRAGMA query_only = ON");
|
|
566
|
-
this.db.exec(
|
|
588
|
+
this.db.exec(
|
|
589
|
+
`PRAGMA busy_timeout = ${resolveBusyTimeoutMs(busyTimeoutMs)}`
|
|
590
|
+
);
|
|
567
591
|
this.contextGeneration += 1;
|
|
568
592
|
return ok(undefined);
|
|
569
593
|
} catch (cause) {
|
|
@@ -38,7 +38,11 @@ export async function openScopedIndexStore(options: {
|
|
|
38
38
|
|
|
39
39
|
const store = new SqliteAdapter();
|
|
40
40
|
store.setConfigPath(options.configPath ?? "<inline-config>");
|
|
41
|
-
const openResult = await store.open(
|
|
41
|
+
const openResult = await store.open(
|
|
42
|
+
dbPath,
|
|
43
|
+
options.config.ftsTokenizer,
|
|
44
|
+
options.config.busyTimeoutMs
|
|
45
|
+
);
|
|
42
46
|
if (!openResult.ok) {
|
|
43
47
|
throw new Error(openResult.error.message);
|
|
44
48
|
}
|
|
@@ -193,7 +193,8 @@ export async function createVectorIndexPort(
|
|
|
193
193
|
return Promise.resolve(
|
|
194
194
|
err(
|
|
195
195
|
"VECTOR_WRITE_FAILED",
|
|
196
|
-
`Vector write failed: ${e instanceof Error ? e.message : String(e)}
|
|
196
|
+
`Vector write failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
197
|
+
e
|
|
197
198
|
)
|
|
198
199
|
);
|
|
199
200
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
6954195a14c9cdff85a265043590873823a0b25b287866c036d83153a95b8b14 gno-browser-clipper-v1.37.1.zip
|