akm-cli 0.9.15-beta.1 → 0.9.15-beta.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.
@@ -36375,7 +36375,7 @@ var require_libvips = __commonJS((exports, module) => {
36375
36375
  SPDX-License-Identifier: Apache-2.0
36376
36376
  */
36377
36377
  var { spawnSync: spawnSync6 } = __require("node:child_process");
36378
- var { createHash: createHash10 } = __require("node:crypto");
36378
+ var { createHash: createHash11 } = __require("node:crypto");
36379
36379
  var semverCoerce = require_coerce2();
36380
36380
  var semverGreaterThanOrEqualTo = require_gte2();
36381
36381
  var semverSatisfies = require_satisfies2();
@@ -36463,7 +36463,7 @@ var require_libvips = __commonJS((exports, module) => {
36463
36463
  }
36464
36464
  return false;
36465
36465
  };
36466
- var sha512 = (s) => createHash10("sha512").update(s).digest("hex");
36466
+ var sha512 = (s) => createHash11("sha512").update(s).digest("hex");
36467
36467
  var yarnLocator = () => {
36468
36468
  try {
36469
36469
  const identHash = sha512(`imgsharp-libvips-${buildPlatformArch()}`);
@@ -75917,11 +75917,14 @@ var EmbeddingConnectionConfigSchema = exports_external.object({
75917
75917
  apiKey: symbolicOrWarnApiKey("embedding.apiKey").optional(),
75918
75918
  dimension: positiveInt.max(4096).optional(),
75919
75919
  localModel: exports_external.string().min(1).optional(),
75920
+ maxInputTokens: positiveInt.optional(),
75920
75921
  maxTokens: positiveInt.optional(),
75921
75922
  batchSize: positiveInt.optional(),
75922
75923
  chunkSize: positiveInt.optional(),
75923
75924
  contextLength: positiveInt.optional(),
75924
- ollamaOptions: EmbeddingOllamaOptionsSchema.optional()
75925
+ ollamaOptions: EmbeddingOllamaOptionsSchema.optional(),
75926
+ timeoutMs: positiveInt.optional(),
75927
+ concurrency: positiveInt.max(16).optional()
75925
75928
  }).passthrough();
75926
75929
 
75927
75930
  // src/integrations/harnesses/ids.ts
@@ -76994,8 +76997,21 @@ function releaseLockRaw(lockPath) {
76994
76997
  function tryAcquireLockSync(lockPath, payload) {
76995
76998
  return withLockOperationMutex(lockPath, () => tryAcquireLockRaw(lockPath, payload));
76996
76999
  }
77000
+ function launcherPidFromEnv() {
77001
+ const raw = process.env.AKM_LAUNCHER_PID;
77002
+ if (!raw)
77003
+ return;
77004
+ const pid = Number.parseInt(raw, 10);
77005
+ return Number.isInteger(pid) && pid > 0 ? pid : undefined;
77006
+ }
76997
77007
  function createLockPayload(metadata = {}) {
76998
- return JSON.stringify({ ...metadata, pid: process.pid, lockId: randomUUID() });
77008
+ const launcherPid = launcherPidFromEnv();
77009
+ return JSON.stringify({
77010
+ ...metadata,
77011
+ pid: process.pid,
77012
+ ...launcherPid !== undefined ? { launcherPid } : {},
77013
+ lockId: randomUUID()
77014
+ });
76999
77015
  }
77000
77016
  function probeLock(lockPath, opts) {
77001
77017
  let snapshot;
@@ -77011,17 +77027,17 @@ function probeLock(lockPath, opts) {
77011
77027
  return { state: "absent" };
77012
77028
  const { rawContent, identity: identity3 } = snapshot;
77013
77029
  const ageMs = Date.now() - identity3.mtimeMs;
77014
- const holderPid = extractHolderPid(rawContent);
77030
+ const { holderPid, launcherPid } = extractLockIdentity(rawContent);
77015
77031
  if (holderPid === undefined) {
77016
77032
  return { state: "stale", reason: "invalid_pid", ageMs, rawContent, identity: identity3 };
77017
77033
  }
77018
77034
  if (!isProcessAlive(holderPid)) {
77019
- return { state: "stale", reason: "pid_dead", holderPid, ageMs, rawContent, identity: identity3 };
77035
+ return { state: "stale", reason: "pid_dead", holderPid, launcherPid, ageMs, rawContent, identity: identity3 };
77020
77036
  }
77021
77037
  if (opts?.staleAfterMs !== undefined && ageMs > opts.staleAfterMs) {
77022
- return { state: "stale", reason: "age_exceeded", holderPid, ageMs, rawContent, identity: identity3 };
77038
+ return { state: "stale", reason: "age_exceeded", holderPid, launcherPid, ageMs, rawContent, identity: identity3 };
77023
77039
  }
77024
- return { state: "held", holderPid, ageMs, rawContent, identity: identity3 };
77040
+ return { state: "held", holderPid, launcherPid, ageMs, rawContent, identity: identity3 };
77025
77041
  }
77026
77042
  function reclaimStaleLock(lockPath, probe, options) {
77027
77043
  if (probe.rawContent === undefined || probe.identity === undefined)
@@ -77089,21 +77105,25 @@ function releaseLock(ownership) {
77089
77105
  }
77090
77106
  });
77091
77107
  }
77092
- function extractHolderPid(content) {
77108
+ function extractLockIdentity(content) {
77093
77109
  const trimmed = content.trim();
77094
77110
  if (!trimmed)
77095
- return;
77111
+ return {};
77096
77112
  if (trimmed.startsWith("{")) {
77097
77113
  try {
77098
77114
  const parsed = JSON.parse(trimmed);
77099
77115
  const pid2 = typeof parsed.pid === "number" ? parsed.pid : Number.NaN;
77100
- return Number.isInteger(pid2) && pid2 > 0 ? pid2 : undefined;
77116
+ const rawLauncherPid = typeof parsed.launcherPid === "number" ? parsed.launcherPid : Number.NaN;
77117
+ return {
77118
+ holderPid: Number.isInteger(pid2) && pid2 > 0 ? pid2 : undefined,
77119
+ launcherPid: Number.isInteger(rawLauncherPid) && rawLauncherPid > 0 ? rawLauncherPid : undefined
77120
+ };
77101
77121
  } catch {
77102
- return;
77122
+ return {};
77103
77123
  }
77104
77124
  }
77105
77125
  const pid = Number.parseInt(trimmed, 10);
77106
- return Number.isInteger(pid) && pid > 0 ? pid : undefined;
77126
+ return { holderPid: Number.isInteger(pid) && pid > 0 ? pid : undefined };
77107
77127
  }
77108
77128
 
77109
77129
  // src/core/config/config-io.ts
@@ -95737,7 +95757,7 @@ function listTxnJournalsTolerant(predicate) {
95737
95757
  }
95738
95758
 
95739
95759
  // src/commands/proposal/repository.ts
95740
- import { createHash as createHash10, randomUUID as randomUUID6 } from "node:crypto";
95760
+ import { createHash as createHash11, randomUUID as randomUUID6 } from "node:crypto";
95741
95761
  import fs55 from "node:fs";
95742
95762
  init_dist();
95743
95763
  import path66 from "node:path";
@@ -95803,6 +95823,16 @@ import fs53 from "node:fs";
95803
95823
  import path64 from "node:path";
95804
95824
  init_errors();
95805
95825
  init_paths();
95826
+
95827
+ // src/core/run-lock.ts
95828
+ init_errors();
95829
+ function formatLockHolderPid(holder) {
95830
+ if (holder.pid === null)
95831
+ return "unknown";
95832
+ return holder.launcherPid !== null ? `${holder.pid} (launcher ${holder.launcherPid})` : String(holder.pid);
95833
+ }
95834
+
95835
+ // src/indexer/index-written-assets.ts
95806
95836
  init_warn();
95807
95837
 
95808
95838
  // src/storage/repositories/index-connection.ts
@@ -96057,6 +96087,15 @@ function isCanonicalIndexGeneration(db) {
96057
96087
  init_errors();
96058
96088
  init_warn();
96059
96089
 
96090
+ // src/storage/repositories/embedding-salvage-repository.ts
96091
+ import { createHash as createHash9 } from "node:crypto";
96092
+
96093
+ // src/storage/repositories/embeddings-repository.ts
96094
+ function blobToEmbedding(blob) {
96095
+ const f32 = new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / 4);
96096
+ return Array.from(f32);
96097
+ }
96098
+
96060
96099
  // src/storage/repositories/index-meta-repository.ts
96061
96100
  function getMeta(db, key) {
96062
96101
  const row = db.prepare("SELECT value FROM index_meta WHERE key = ?").get(key);
@@ -96069,6 +96108,64 @@ function deleteMeta(db, key) {
96069
96108
  db.prepare("DELETE FROM index_meta WHERE key = ?").run(key);
96070
96109
  }
96071
96110
 
96111
+ // src/storage/repositories/index-sql.ts
96112
+ var SQLITE_CHUNK_SIZE = 500;
96113
+
96114
+ // src/storage/repositories/embedding-salvage-repository.ts
96115
+ function hashEmbeddableText(searchText) {
96116
+ return createHash9("sha256").update(searchText, "utf8").digest("hex");
96117
+ }
96118
+ function purgeEmbeddingSalvage(db) {
96119
+ db.exec("DELETE FROM embedding_salvage");
96120
+ }
96121
+ function relabelEmbeddingSalvageFingerprint(db, fromFingerprint, toFingerprint) {
96122
+ db.prepare("UPDATE embedding_salvage SET fingerprint = ? WHERE fingerprint = ?").run(toFingerprint, fromFingerprint);
96123
+ }
96124
+ function reuseSalvagedEmbeddings(db, entries, fingerprint, writeReused) {
96125
+ if (entries.length === 0)
96126
+ return { reusedCount: 0, remaining: [] };
96127
+ const anySalvageForFingerprint = db.prepare("SELECT 1 FROM embedding_salvage WHERE fingerprint = ? LIMIT 1").get(fingerprint);
96128
+ if (!anySalvageForFingerprint)
96129
+ return { reusedCount: 0, remaining: [...entries] };
96130
+ const hashes = entries.map((entry) => hashEmbeddableText(entry.searchText));
96131
+ const salvageByHash = new Map;
96132
+ const uniqueHashes = [...new Set(hashes)];
96133
+ for (let offset = 0;offset < uniqueHashes.length; offset += SQLITE_CHUNK_SIZE) {
96134
+ const chunk = uniqueHashes.slice(offset, offset + SQLITE_CHUNK_SIZE);
96135
+ const placeholders = chunk.map(() => "?").join(",");
96136
+ const rows = db.prepare(`SELECT content_hash AS contentHash, embedding FROM embedding_salvage WHERE fingerprint = ? AND content_hash IN (${placeholders})`).all(fingerprint, ...chunk);
96137
+ for (const row of rows)
96138
+ salvageByHash.set(row.contentHash, row.embedding);
96139
+ }
96140
+ if (salvageByHash.size === 0)
96141
+ return { reusedCount: 0, remaining: [...entries] };
96142
+ let reusedCount = 0;
96143
+ const remaining = [];
96144
+ for (let offset = 0;offset < entries.length; offset += SQLITE_CHUNK_SIZE) {
96145
+ const end = Math.min(offset + SQLITE_CHUNK_SIZE, entries.length);
96146
+ const chunkMatches = [];
96147
+ for (let i = offset;i < end; i++) {
96148
+ const entry = entries[i];
96149
+ const blob = salvageByHash.get(hashes[i]);
96150
+ if (blob)
96151
+ chunkMatches.push({ entry, blob });
96152
+ else
96153
+ remaining.push(entry);
96154
+ }
96155
+ if (chunkMatches.length === 0)
96156
+ continue;
96157
+ db.transaction(() => {
96158
+ for (const { entry, blob } of chunkMatches) {
96159
+ if (writeReused(entry, blobToEmbedding(blob)))
96160
+ reusedCount++;
96161
+ else
96162
+ remaining.push(entry);
96163
+ }
96164
+ })();
96165
+ }
96166
+ return { reusedCount, remaining };
96167
+ }
96168
+
96072
96169
  // src/storage/repositories/index-vec-repository.ts
96073
96170
  import { createRequire as createRequire4 } from "node:module";
96074
96171
 
@@ -96110,9 +96207,6 @@ function cosineSimilarity(a, b) {
96110
96207
  return denom === 0 ? 0 : dot / denom;
96111
96208
  }
96112
96209
 
96113
- // src/storage/repositories/index-sql.ts
96114
- var SQLITE_CHUNK_SIZE = 500;
96115
-
96116
96210
  // src/storage/repositories/index-vec-repository.ts
96117
96211
  var vecStatus = new WeakMap;
96118
96212
  function loadVecExtension(db) {
@@ -96372,11 +96466,11 @@ init_warn();
96372
96466
 
96373
96467
  // src/core/asset/markdown-fragments.ts
96374
96468
  init_markdown();
96375
- import { createHash as createHash9 } from "node:crypto";
96469
+ import { createHash as createHash10 } from "node:crypto";
96376
96470
  var MARKDOWN_FRAGMENT_MAX_CHARS = 1600;
96377
96471
  var MARKDOWN_FRAGMENT_PREFIX = "akm-fragment-";
96378
96472
  function hash3(text) {
96379
- return createHash9("sha256").update(text).digest("hex");
96473
+ return createHash10("sha256").update(text).digest("hex");
96380
96474
  }
96381
96475
  function uniqueSlugs(body) {
96382
96476
  const out = new Map;
@@ -96657,6 +96751,7 @@ function getEmbeddableEntryCount(db) {
96657
96751
  }
96658
96752
 
96659
96753
  // src/indexer/materialize-embeddings.ts
96754
+ init_paths();
96660
96755
  init_warn();
96661
96756
 
96662
96757
  // src/llm/embedders/cache.ts
@@ -96896,6 +96991,39 @@ var DEFAULT_TOKEN_BUDGET = 8000;
96896
96991
  function estimateTokenCount(text) {
96897
96992
  return Math.round(text.length / 4);
96898
96993
  }
96994
+ var DEFAULT_MAX_INPUT_TOKENS = 512;
96995
+ function capEmbeddingText(text, maxTokens) {
96996
+ if (estimateTokenCount(text) <= maxTokens)
96997
+ return { text, truncated: false };
96998
+ const charBudget = Math.max(0, maxTokens * 4);
96999
+ let cut = Math.min(charBudget, text.length);
97000
+ if (cut > 0 && cut < text.length) {
97001
+ const code = text.charCodeAt(cut);
97002
+ if (code >= 56320 && code <= 57343)
97003
+ cut -= 1;
97004
+ }
97005
+ return { text: text.slice(0, cut), truncated: true };
97006
+ }
97007
+ var DEFAULT_EMBEDDING_TIMEOUT_MS = 120000;
97008
+ function resolveEmbeddingTimeoutMs(config) {
97009
+ return config.timeoutMs ?? DEFAULT_EMBEDDING_TIMEOUT_MS;
97010
+ }
97011
+ function scaleEmbeddingTimeoutMs(timeoutMs, requestTokens, tokenBudget) {
97012
+ const scaled = tokenBudget > 0 ? timeoutMs * (requestTokens / tokenBudget) : timeoutMs;
97013
+ return Math.min(Math.max(scaled, 30000), timeoutMs);
97014
+ }
97015
+ function isEmbeddingTimeoutError(err) {
97016
+ if (!(err instanceof Error))
97017
+ return false;
97018
+ if (err.name === "BodyReadTimeoutError")
97019
+ return true;
97020
+ return err.message.startsWith("Request timed out after ");
97021
+ }
97022
+ var embeddingTimeoutBackoffOverrideForTests;
97023
+ function embeddingTimeoutRetryBackoffMs(timeoutAttempt = 0) {
97024
+ const { baseMs, maxMs } = embeddingTimeoutBackoffOverrideForTests ?? { baseMs: 5000, maxMs: 60000 };
97025
+ return backoffDelay(timeoutAttempt, baseMs, maxMs);
97026
+ }
96899
97027
 
96900
97028
  class ContextExceededError extends Error {
96901
97029
  constructor(message) {
@@ -96903,13 +97031,15 @@ class ContextExceededError extends Error {
96903
97031
  this.name = "ContextExceededError";
96904
97032
  }
96905
97033
  }
96906
- var CONTEXT_EXCEEDED_PATTERN = /exceed_context_size_error|context size|context length|too many tokens/i;
97034
+ var CONTEXT_EXCEEDED_PATTERN = /exceed_context_size_error|context size|context length|too many tokens|input is too large to process|physical batch size|ubatch/i;
96907
97035
  function isContextExceededResponse(status, body) {
96908
97036
  if (status === 413)
96909
97037
  return true;
96910
97038
  return CONTEXT_EXCEEDED_PATTERN.test(body);
96911
97039
  }
96912
97040
  function resolveEmbeddingConcurrency(config) {
97041
+ if (typeof config.concurrency === "number")
97042
+ return config.concurrency;
96913
97043
  return defaultConcurrencyForEndpoint(config.endpoint);
96914
97044
  }
96915
97045
  function buildTokenBoundedBatches(texts, tokenBudget, maxCount) {
@@ -96965,20 +97095,21 @@ class RemoteEmbedder {
96965
97095
  if (ollamaOpts) {
96966
97096
  body.options = ollamaOpts;
96967
97097
  }
97098
+ const timeoutMs = resolveEmbeddingTimeoutMs(this.config);
96968
97099
  const response = await fetchWithTimeout(normalizeEmbeddingEndpoint(this.endpoint), {
96969
97100
  method: "POST",
96970
97101
  headers,
96971
97102
  body: JSON.stringify(body)
96972
- }, 30000, signal);
97103
+ }, timeoutMs, signal);
96973
97104
  if (!response.ok) {
96974
- const errBody = await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: 30000, signal }).catch((err) => {
97105
+ const errBody = await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: timeoutMs, signal }).catch((err) => {
96975
97106
  if (signal?.aborted)
96976
97107
  throw err;
96977
97108
  return "";
96978
97109
  });
96979
97110
  throw new Error(`Embedding request failed (${response.status}): ${this.safeErrorBody(errBody)}`);
96980
97111
  }
96981
- const json = JSON.parse(await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: 30000, signal }));
97112
+ const json = JSON.parse(await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: timeoutMs, signal }));
96982
97113
  if (!json.data?.[0]?.embedding) {
96983
97114
  throw new Error(`Unexpected embedding response format: missing data[0].embedding.${embeddingEndpointPathHint(this.endpoint)}`);
96984
97115
  }
@@ -96990,9 +97121,10 @@ class RemoteEmbedder {
96990
97121
  const results = new Array(texts.length).fill(undefined);
96991
97122
  const headers = this.buildHeaders();
96992
97123
  const ollamaOpts = resolveOllamaOptions(this.config);
96993
- const tokenBudget = this.config.maxTokens ?? this.config.contextLength ?? DEFAULT_TOKEN_BUDGET;
97124
+ const tokenBudget = this.config.maxTokens ?? DEFAULT_TOKEN_BUDGET;
96994
97125
  const maxCount = this.config.batchSize ?? DEFAULT_REMOTE_BATCH_SIZE;
96995
97126
  const textBatches = buildTokenBoundedBatches(texts, tokenBudget, maxCount);
97127
+ const configuredTimeoutMs = resolveEmbeddingTimeoutMs(this.config);
96996
97128
  const dispatchAbort = new AbortController;
96997
97129
  const stopDispatch = (reason) => {
96998
97130
  if (!dispatchAbort.signal.aborted)
@@ -97008,66 +97140,132 @@ class RemoteEmbedder {
97008
97140
  }
97009
97141
  }
97010
97142
  let firstOnBatchError;
97011
- const commitBatch = (indices, embeddings, model) => {
97143
+ const commitBatch = (indices, embeddings, model, outcome) => {
97012
97144
  if (!onBatch)
97013
97145
  return;
97014
97146
  if (firstOnBatchError !== undefined)
97015
97147
  return;
97016
97148
  try {
97017
- onBatch(indices, embeddings, model);
97149
+ onBatch(indices, embeddings, model, outcome);
97018
97150
  } catch (err) {
97019
97151
  firstOnBatchError = err;
97020
97152
  stopDispatch(err);
97021
97153
  }
97022
97154
  };
97023
- const requestAndCommit = async (indices) => {
97155
+ const requestAndCommit = async (indices, batchIndex, isTimeoutRetry = false, timeoutAttempt = 0) => {
97156
+ if (dispatchAbort.signal.aborted)
97157
+ return;
97024
97158
  const batch = indices.map((i) => texts[i]);
97159
+ const requestTokens = batch.reduce((sum, text) => sum + estimateTokenCount(text), 0);
97160
+ const requestTimeoutMs = scaleEmbeddingTimeoutMs(configuredTimeoutMs, requestTokens, tokenBudget);
97161
+ const requestStart = Date.now();
97025
97162
  let batchEmbeddings;
97026
97163
  let responseModel;
97164
+ let outcome;
97165
+ let failureReason;
97027
97166
  try {
97028
- const { vectors, model } = await this.requestBatch(batch, headers, ollamaOpts, signal);
97167
+ const { vectors, model } = await this.requestBatch(batch, headers, ollamaOpts, requestTimeoutMs, signal);
97029
97168
  for (let k2 = 0;k2 < indices.length; k2++) {
97030
97169
  results[indices[k2]] = vectors[k2];
97031
97170
  }
97032
97171
  batchEmbeddings = indices.map((i) => results[i]);
97033
97172
  responseModel = model;
97173
+ outcome = "stored";
97034
97174
  } catch (err) {
97035
97175
  if (signal?.aborted)
97036
97176
  throw err;
97037
97177
  if (err instanceof ContextExceededError && indices.length > 1) {
97038
97178
  const mid = Math.ceil(indices.length / 2);
97039
- await requestAndCommit(indices.slice(0, mid));
97040
- await requestAndCommit(indices.slice(mid));
97179
+ await requestAndCommit(indices.slice(0, mid), batchIndex, false, timeoutAttempt);
97180
+ await requestAndCommit(indices.slice(mid), batchIndex, false, timeoutAttempt);
97181
+ return;
97182
+ }
97183
+ const timedOut = isEmbeddingTimeoutError(err);
97184
+ if (timedOut && !isTimeoutRetry) {
97185
+ const backoffMs = embeddingTimeoutRetryBackoffMs(timeoutAttempt);
97186
+ warnVerbose(`[embed] batch of ${batch.length} document(s) timed out after ${requestTimeoutMs}ms; retrying once after a ${Math.round(backoffMs)}ms backoff`);
97187
+ commitBatch(indices, indices.map(() => {
97188
+ return;
97189
+ }), undefined, {
97190
+ batchIndex,
97191
+ batchCount: textBatches.length,
97192
+ docCount: indices.length,
97193
+ requestTokens,
97194
+ elapsedMs: backoffMs,
97195
+ outcome: "retrying",
97196
+ reason: "timed out"
97197
+ });
97198
+ await abortableDelay(backoffMs, signal, "embedding interrupted during retry backoff");
97199
+ if (!dispatchAbort.signal.aborted) {
97200
+ return requestAndCommit(indices, batchIndex, true, timeoutAttempt);
97201
+ }
97202
+ } else if (timedOut && indices.length > 1) {
97203
+ const mid = Math.ceil(indices.length / 2);
97204
+ await requestAndCommit(indices.slice(0, mid), batchIndex, false, timeoutAttempt + 1);
97205
+ await requestAndCommit(indices.slice(mid), batchIndex, false, timeoutAttempt + 1);
97041
97206
  return;
97042
97207
  }
97043
97208
  const message = err instanceof Error ? err.message : String(err);
97044
97209
  const reason = err instanceof ContextExceededError ? "context-window-exceeded" : "batch-request-failed";
97045
- warnVerbose(`[embed] batch of ${batch.length} document(s) failed and was skipped: ${message}`);
97046
- for (const idx of indices) {
97047
- onSkip?.({ index: idx, reason, message });
97048
- }
97210
+ const failureKind = reason === "batch-request-failed" ? timedOut ? "timeout" : "network-error" : undefined;
97211
+ let stopRequested = false;
97212
+ for (const [k2, idx] of indices.entries()) {
97213
+ if (onSkip?.({
97214
+ index: idx,
97215
+ reason,
97216
+ message,
97217
+ batchStart: k2 === 0,
97218
+ batchSize: indices.length,
97219
+ failureKind
97220
+ }) === false)
97221
+ stopRequested = true;
97222
+ }
97223
+ if (stopRequested)
97224
+ stopDispatch();
97049
97225
  batchEmbeddings = indices.map(() => {
97050
97226
  return;
97051
97227
  });
97052
- }
97053
- commitBatch(indices, batchEmbeddings, responseModel);
97228
+ outcome = "failed";
97229
+ failureReason = message;
97230
+ }
97231
+ commitBatch(indices, batchEmbeddings, responseModel, {
97232
+ batchIndex,
97233
+ batchCount: textBatches.length,
97234
+ docCount: indices.length,
97235
+ requestTokens,
97236
+ elapsedMs: Date.now() - requestStart,
97237
+ outcome,
97238
+ reason: failureReason
97239
+ });
97054
97240
  };
97055
- const runProviderBatch = async (textBatch) => {
97241
+ const runProviderBatch = async (textBatch, batchIndex) => {
97056
97242
  if (textBatch.oversized) {
97057
97243
  const idx = textBatch.indices[0];
97058
97244
  const estTokens = estimateTokenCount(texts[idx]);
97059
97245
  onSkip?.({
97060
97246
  index: idx,
97061
97247
  reason: "context-window-exceeded",
97062
- message: `Document estimated at ${estTokens} tokens exceeds the ${tokenBudget}-token embedding budget; skipped.`
97248
+ message: `Document estimated at ${estTokens} tokens exceeds the ${tokenBudget}-token embedding budget; skipped.`,
97249
+ batchStart: true,
97250
+ batchSize: 1
97251
+ });
97252
+ commitBatch([idx], [undefined], undefined, {
97253
+ batchIndex,
97254
+ batchCount: textBatches.length,
97255
+ docCount: 1,
97256
+ requestTokens: estTokens,
97257
+ elapsedMs: 0,
97258
+ outcome: "failed",
97259
+ reason: "oversized"
97063
97260
  });
97064
- commitBatch([idx], [undefined]);
97065
97261
  return;
97066
97262
  }
97067
- await requestAndCommit(textBatch.indices);
97263
+ await requestAndCommit(textBatch.indices, batchIndex);
97068
97264
  };
97069
97265
  const concurrency = resolveEmbeddingConcurrency(this.config);
97070
- await concurrentMap(textBatches, runProviderBatch, concurrency, { signal: dispatchAbort.signal });
97266
+ await concurrentMap(textBatches, (textBatch, i) => runProviderBatch(textBatch, i + 1), concurrency, {
97267
+ signal: dispatchAbort.signal
97268
+ });
97071
97269
  if (callerAbortListener)
97072
97270
  signal?.removeEventListener("abort", callerAbortListener);
97073
97271
  if (signal?.aborted) {
@@ -97078,7 +97276,7 @@ class RemoteEmbedder {
97078
97276
  }
97079
97277
  return results;
97080
97278
  }
97081
- async requestBatch(batch, headers, ollamaOpts, signal) {
97279
+ async requestBatch(batch, headers, ollamaOpts, timeoutMs, signal) {
97082
97280
  const body = {
97083
97281
  input: batch,
97084
97282
  model: this.model
@@ -97093,9 +97291,9 @@ class RemoteEmbedder {
97093
97291
  method: "POST",
97094
97292
  headers,
97095
97293
  body: JSON.stringify(body)
97096
- }, 30000, signal);
97294
+ }, timeoutMs, signal);
97097
97295
  if (!response.ok) {
97098
- const respBody = await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: 30000, signal }).catch((err) => {
97296
+ const respBody = await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: timeoutMs, signal }).catch((err) => {
97099
97297
  if (signal?.aborted)
97100
97298
  throw err;
97101
97299
  return "";
@@ -97106,7 +97304,7 @@ class RemoteEmbedder {
97106
97304
  }
97107
97305
  throw new Error(message);
97108
97306
  }
97109
- const json = JSON.parse(await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: 30000, signal }));
97307
+ const json = JSON.parse(await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: timeoutMs, signal }));
97110
97308
  if (!json.data || json.data.length !== batch.length) {
97111
97309
  throw new Error(`Unexpected embedding batch response: expected ${batch.length} embeddings, got ${json.data?.length ?? 0}.${embeddingEndpointPathHint(this.endpoint)}`);
97112
97310
  }
@@ -97175,6 +97373,15 @@ function resolveOllamaOptions(config) {
97175
97373
  function hasRemoteEndpoint(config) {
97176
97374
  return isHttpUrl(config.endpoint);
97177
97375
  }
97376
+ function describeEmbeddingCredential(apiKey) {
97377
+ if (!apiKey)
97378
+ return "none configured";
97379
+ if (SECRET_STORE_REFERENCE_PATTERN.test(apiKey))
97380
+ return `${apiKey} (store)`;
97381
+ if (ENV_REFERENCE_PATTERN.test(apiKey))
97382
+ return `${apiKey} (env)`;
97383
+ return "literal apiKey";
97384
+ }
97178
97385
 
97179
97386
  // src/llm/embedder.ts
97180
97387
  var embedderOverrides;
@@ -97225,9 +97432,12 @@ function deriveSemanticProviderFingerprint(embedding) {
97225
97432
  }
97226
97433
  return `local:${embedding?.localModel ?? DEFAULT_LOCAL_MODEL}`;
97227
97434
  }
97228
- var PROGRESS_INTERVAL = 500;
97435
+ function formatEmbeddingHeartbeat(storedCount, total, failedCount) {
97436
+ return `Still generating embeddings: ${storedCount}/${total} stored, ${failedCount} failed; waiting on embedding provider.`;
97437
+ }
97229
97438
  var CANARY_SAMPLE_SIZE = 8;
97230
97439
  var CANARY_SIMILARITY_THRESHOLD = 0.999;
97440
+ var CIRCUIT_BREAKER_THRESHOLD = 3;
97231
97441
  function decideEmbeddingCompatibility(pairs) {
97232
97442
  if (pairs.length === 0)
97233
97443
  return { outcome: "keep", medianSimilarity: undefined, verifiedSamples: 0 };
@@ -97315,11 +97525,24 @@ function throwIfAborted(signal) {
97315
97525
  }
97316
97526
  }
97317
97527
  async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds, opts) {
97528
+ if (db.inTransaction) {
97529
+ throw new Error("generateEmbeddingsForDb was called with an ambient transaction already open on `db`: per-batch commits " + "would become SAVEPOINTs inside it, losing the crash-durability contract per-batch commit exists for. " + "Run the embedding phase on a connection with no open transaction.");
97530
+ }
97318
97531
  throwIfAborted(signal);
97319
97532
  if (config.semanticSearchMode === "off") {
97533
+ purgeEmbeddingSalvage(db);
97320
97534
  onProgress({ phase: "embeddings", message: "Semantic search disabled; skipping embeddings." });
97321
97535
  return { success: false, message: "Semantic search is disabled." };
97322
97536
  }
97537
+ if (hasRemoteEndpoint(config.embedding ?? {})) {
97538
+ const endpoint = normalizeEmbeddingEndpoint(config.embedding?.endpoint ?? "");
97539
+ const credential = describeEmbeddingCredential(config.embedding?.apiKey);
97540
+ const configFileSuffix = isVerbose() ? `; config: ${getConfigPath()}` : "";
97541
+ onProgress({
97542
+ phase: "embeddings",
97543
+ message: `[embed] endpoint ${endpoint}, model ${config.embedding?.model ?? "unknown"}; credential: ${credential}${configFileSuffix}`
97544
+ });
97545
+ }
97323
97546
  const vecFastPathWasReady = isVecFastPathReady(db);
97324
97547
  const currentFingerprint = deriveSemanticProviderFingerprint(config.embedding);
97325
97548
  const storedFingerprint = getMeta(db, "embeddingFingerprint");
@@ -97328,6 +97551,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
97328
97551
  if (opts?.forceReembed) {
97329
97552
  db.transaction(() => {
97330
97553
  purgeEmbeddings(db, { dropVecTable: true });
97554
+ purgeEmbeddingSalvage(db);
97331
97555
  deleteMeta(db, "embeddingDim");
97332
97556
  setMeta(db, "embeddingFingerprint", currentFingerprint);
97333
97557
  deleteMeta(db, "embeddingIdentity");
@@ -97344,6 +97568,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
97344
97568
  if (decision.outcome === "rebuild") {
97345
97569
  db.transaction(() => {
97346
97570
  purgeEmbeddings(db, { dropVecTable: true });
97571
+ purgeEmbeddingSalvage(db);
97347
97572
  deleteMeta(db, "embeddingDim");
97348
97573
  setMeta(db, "embeddingFingerprint", currentFingerprint);
97349
97574
  if (decision.identity)
@@ -97355,6 +97580,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
97355
97580
  rebuildReason = decision.reason;
97356
97581
  } else {
97357
97582
  setMeta(db, "embeddingFingerprint", currentFingerprint);
97583
+ relabelEmbeddingSalvageFingerprint(db, storedFingerprint, currentFingerprint);
97358
97584
  if (decision.identity)
97359
97585
  setMeta(db, "embeddingIdentity", decision.identity);
97360
97586
  if (decision.verified) {
@@ -97365,28 +97591,66 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
97365
97591
  onProgress({ phase: "embeddings", message });
97366
97592
  }
97367
97593
  }
97594
+ } else {
97595
+ setMeta(db, "embeddingFingerprint", currentFingerprint);
97368
97596
  }
97369
97597
  try {
97370
97598
  throwIfAborted(signal);
97371
97599
  const allEntries = getAllEntriesForEmbedding(db, targetEntryIds);
97372
- if (allEntries.length === 0) {
97600
+ let vecFailedCount = 0;
97601
+ let vecUnavailableCount = 0;
97602
+ const { reusedCount, remaining: candidateEntries } = reuseSalvagedEmbeddings(db, allEntries, currentFingerprint, (entry, embedding) => {
97603
+ const result = upsertEmbedding(db, entry.id, embedding);
97604
+ if (result.vec === "failed")
97605
+ vecFailedCount++;
97606
+ if (result.vec === "unavailable")
97607
+ vecUnavailableCount++;
97608
+ return result.stored;
97609
+ });
97610
+ if (reusedCount > 0) {
97611
+ onProgress({
97612
+ phase: "embeddings",
97613
+ message: `Reused ${reusedCount} embedding${reusedCount === 1 ? "" : "s"} from the previous generation; embedding ${candidateEntries.length} new.`
97614
+ });
97615
+ }
97616
+ if (candidateEntries.length === 0) {
97373
97617
  onProgress({ phase: "embeddings", message: "Embeddings already up to date." });
97374
97618
  setMeta(db, "embeddingFingerprint", currentFingerprint);
97375
- return { success: true };
97619
+ if (reusedCount > 0) {
97620
+ const vecGenerationComplete = targetEntryIds === undefined ? isVecFastPathComplete(db) : vecFastPathWasReady;
97621
+ setVecFastPathReady(db, vecFailedCount === 0 && vecUnavailableCount === 0 && vecGenerationComplete);
97622
+ }
97623
+ purgeEmbeddingSalvage(db);
97624
+ return reusedCount > 0 ? { success: true, vecInsertFailures: vecFailedCount } : { success: true };
97625
+ }
97626
+ const maxInputTokens = config.embedding?.maxInputTokens ?? DEFAULT_MAX_INPUT_TOKENS;
97627
+ let truncatedCount = 0;
97628
+ const texts = [];
97629
+ const pendingEntries = [];
97630
+ for (const entry of candidateEntries) {
97631
+ const capped = capEmbeddingText(entry.searchText, maxInputTokens);
97632
+ if (capped.text.length === 0)
97633
+ continue;
97634
+ if (capped.truncated)
97635
+ truncatedCount++;
97636
+ pendingEntries.push(entry);
97637
+ texts.push(capped.text);
97638
+ }
97639
+ if (truncatedCount > 0) {
97640
+ const message = `[embed] ${truncatedCount} entr${truncatedCount === 1 ? "y" : "ies"} truncated to the ${maxInputTokens}-token embedding cap (embedding.maxInputTokens); rerun with a higher cap to embed the full text.`;
97641
+ onProgress({ phase: "embeddings", message });
97376
97642
  }
97377
97643
  if (rebuildReason) {
97378
- const message = `[embed] Re-embedding ${allEntries.length} entr${allEntries.length === 1 ? "y" : "ies"} because ${rebuildReason}`;
97379
- warn(message);
97644
+ const message = `[embed] Re-embedding ${pendingEntries.length} entr${pendingEntries.length === 1 ? "y" : "ies"} because ${rebuildReason}`;
97380
97645
  onProgress({ phase: "embeddings", message });
97381
97646
  }
97382
97647
  onProgress({
97383
97648
  phase: "embeddings",
97384
- message: `Generating embeddings for ${allEntries.length} entr${allEntries.length === 1 ? "y" : "ies"}.`
97649
+ message: `Generating embeddings for ${pendingEntries.length} entr${pendingEntries.length === 1 ? "y" : "ies"}.`
97385
97650
  });
97386
- const texts = allEntries.map((entry) => entry.searchText);
97387
97651
  if (isVerbose()) {
97388
97652
  if (hasRemoteEndpoint(config.embedding ?? {})) {
97389
- const tokenBudget = config.embedding?.maxTokens ?? config.embedding?.contextLength ?? DEFAULT_TOKEN_BUDGET;
97653
+ const tokenBudget = config.embedding?.maxTokens ?? DEFAULT_TOKEN_BUDGET;
97390
97654
  const maxCount = config.embedding?.batchSize ?? DEFAULT_REMOTE_BATCH_SIZE;
97391
97655
  const batches = buildTokenBoundedBatches(texts, tokenBudget, maxCount);
97392
97656
  const batchNumberByIndex = new Map;
@@ -97394,7 +97658,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
97394
97658
  for (const i of batch.indices)
97395
97659
  batchNumberByIndex.set(i, batchIdx + 1);
97396
97660
  });
97397
- for (const [i, entry] of allEntries.entries()) {
97661
+ for (const [i, entry] of pendingEntries.entries()) {
97398
97662
  const chars = entry.searchText.length;
97399
97663
  const tokens = estimateTokenCount(entry.searchText);
97400
97664
  const batch = batches[batchNumberByIndex.get(i) - 1];
@@ -97402,7 +97666,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
97402
97666
  warnVerbose(`[embed] ${entry.itemRef} (${chars} chars, est. ${tokens} tokens) → ${label}`);
97403
97667
  }
97404
97668
  } else {
97405
- for (const entry of allEntries) {
97669
+ for (const entry of pendingEntries) {
97406
97670
  warnVerbose(`[embed] ${entry.itemRef} (${entry.searchText.length} chars, est. ${estimateTokenCount(entry.searchText)} tokens)`);
97407
97671
  }
97408
97672
  }
@@ -97411,27 +97675,58 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
97411
97675
  let storedCount = 0;
97412
97676
  let skippedCount = 0;
97413
97677
  let embedFailedCount = 0;
97414
- let vecFailedCount = 0;
97415
- let vecUnavailableCount = 0;
97416
97678
  let storedTokens = 0;
97417
- let lastProgressBucket = 0;
97418
97679
  try {
97419
97680
  heartbeatTimer = setInterval(() => {
97420
97681
  onProgress({
97421
97682
  phase: "embeddings",
97422
- message: `Still generating embeddings: ${storedCount}/${allEntries.length} stored; waiting on embedding provider.`
97683
+ message: formatEmbeddingHeartbeat(storedCount, pendingEntries.length, embedFailedCount)
97423
97684
  });
97424
97685
  }, 15000);
97425
97686
  const skips = [];
97426
97687
  const embedStart = Date.now();
97688
+ let consecutiveSingleDocFailures = 0;
97689
+ let consecutiveNetworkErrorFailures = 0;
97690
+ let circuitBreakerReason;
97691
+ const onSkip = (skip) => {
97692
+ skips.push(skip);
97693
+ if (!skip.batchStart)
97694
+ return;
97695
+ if (skip.reason === "context-window-exceeded") {
97696
+ consecutiveSingleDocFailures = 0;
97697
+ consecutiveNetworkErrorFailures = 0;
97698
+ return;
97699
+ }
97700
+ consecutiveSingleDocFailures = skip.batchSize === 1 ? consecutiveSingleDocFailures + 1 : 0;
97701
+ consecutiveNetworkErrorFailures = skip.failureKind === "network-error" ? consecutiveNetworkErrorFailures + 1 : 0;
97702
+ if (consecutiveSingleDocFailures >= CIRCUIT_BREAKER_THRESHOLD || consecutiveNetworkErrorFailures >= CIRCUIT_BREAKER_THRESHOLD) {
97703
+ circuitBreakerReason = skip.message;
97704
+ return false;
97705
+ }
97706
+ return;
97707
+ };
97427
97708
  let observedModel;
97428
97709
  let observedVectorLen;
97429
- const onBatch = (indices, batchEmbeddings, model) => {
97710
+ const reportPerBatchLine = hasRemoteEndpoint(config.embedding ?? {});
97711
+ const onBatch = (indices, batchEmbeddings, model, outcome) => {
97712
+ if (outcome?.outcome === "retrying") {
97713
+ if (reportPerBatchLine) {
97714
+ onProgress({
97715
+ phase: "embeddings",
97716
+ message: `[embed] batch ${outcome.batchIndex}/${outcome.batchCount}: ${outcome.docCount} docs, ${outcome.requestTokens.toLocaleString()} tokens → retrying after ${(outcome.elapsedMs / 1000).toFixed(1)} s`
97717
+ });
97718
+ }
97719
+ return;
97720
+ }
97430
97721
  if (model)
97431
97722
  observedModel = model;
97723
+ if (batchEmbeddings.some((embedding) => embedding !== undefined)) {
97724
+ consecutiveSingleDocFailures = 0;
97725
+ consecutiveNetworkErrorFailures = 0;
97726
+ }
97432
97727
  db.transaction(() => {
97433
97728
  for (let k2 = 0;k2 < indices.length; k2++) {
97434
- const entry = allEntries[indices[k2]];
97729
+ const entry = pendingEntries[indices[k2]];
97435
97730
  if (!entry)
97436
97731
  continue;
97437
97732
  const embedding = batchEmbeddings[k2];
@@ -97454,29 +97749,25 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
97454
97749
  vecUnavailableCount++;
97455
97750
  }
97456
97751
  })();
97457
- const bucket = Math.floor(storedCount / PROGRESS_INTERVAL);
97458
- if (bucket > lastProgressBucket) {
97459
- lastProgressBucket = bucket;
97752
+ if (outcome && outcome.reason !== "oversized" && reportPerBatchLine) {
97753
+ const elapsedSeconds2 = (outcome.elapsedMs / 1000).toFixed(1);
97754
+ const outcomeLabel = outcome.outcome === "stored" ? `${outcome.docCount} stored (${elapsedSeconds2} s)` : `failed: ${outcome.reason}`;
97460
97755
  onProgress({
97461
97756
  phase: "embeddings",
97462
- message: `Embedded ${storedCount}/${allEntries.length} entries.`
97757
+ message: `[embed] batch ${outcome.batchIndex}/${outcome.batchCount}: ${outcome.docCount} docs, ${outcome.requestTokens.toLocaleString()} tokens → ${outcomeLabel}`
97463
97758
  });
97464
97759
  }
97760
+ onProgress({
97761
+ phase: "embeddings",
97762
+ message: `Embedded ${storedCount}/${pendingEntries.length} entries.`
97763
+ });
97465
97764
  };
97466
- await embedBatch(texts, config.embedding, signal, (skip) => skips.push(skip), onBatch);
97765
+ await embedBatch(texts, config.embedding, signal, onSkip, onBatch);
97467
97766
  throwIfAborted(signal);
97468
97767
  const elapsedSeconds = Math.max((Date.now() - embedStart) / 1000, 0.001);
97469
97768
  if (skippedCount > 0) {
97470
97769
  warn(`[embed] ${skippedCount} embedding${skippedCount === 1 ? "" : "s"} skipped (entry deleted between queue and write)`);
97471
97770
  }
97472
- if (embedFailedCount > 0) {
97473
- const detail = skips.slice(0, 20).map((skip) => ` - ${allEntries[skip.index]?.itemRef ?? skip.index} (${skip.reason}): ${skip.message}`).join(`
97474
- `);
97475
- const more = skips.length > 20 ? `
97476
- ...and ${skips.length - 20} more` : "";
97477
- warn(`[embed] ${embedFailedCount} embedding${embedFailedCount === 1 ? "" : "s"} could not be generated and ${embedFailedCount === 1 ? "was" : "were"} skipped:
97478
- ${detail}${more}`);
97479
- }
97480
97771
  const vecGenerationComplete = targetEntryIds === undefined ? isVecFastPathComplete(db) : vecFastPathWasReady;
97481
97772
  setVecFastPathReady(db, vecFailedCount === 0 && vecUnavailableCount === 0 && vecGenerationComplete);
97482
97773
  if (vecFailedCount > 0) {
@@ -97484,14 +97775,39 @@ ${detail}${more}`);
97484
97775
  }
97485
97776
  const entriesPerSec = storedCount / elapsedSeconds;
97486
97777
  const tokensPerSec = storedTokens / elapsedSeconds;
97778
+ const totalStored = storedCount + reusedCount;
97779
+ const oversizedSkips = skips.filter((skip) => skip.reason === "context-window-exceeded");
97780
+ const timedOutSkips = skips.filter((skip) => skip.reason === "batch-request-failed" && skip.failureKind === "timeout");
97781
+ const failedSkips = skips.filter((skip) => skip.reason === "batch-request-failed" && skip.failureKind !== "timeout");
97782
+ const throughputLine = reusedCount > 0 ? `Stored ${totalStored} embedding${totalStored === 1 ? "" : "s"} (${reusedCount} reused, ${storedCount} newly embedded) in ${elapsedSeconds.toFixed(1)}s (${entriesPerSec.toFixed(1)} entries/s, ~${Math.round(tokensPerSec)} tokens/s)` : `Stored ${storedCount} embedding${storedCount === 1 ? "" : "s"} in ${elapsedSeconds.toFixed(1)}s (${entriesPerSec.toFixed(1)} entries/s, ~${Math.round(tokensPerSec)} tokens/s)`;
97487
97783
  onProgress({
97488
97784
  phase: "embeddings",
97489
- message: `Stored ${storedCount} embedding${storedCount === 1 ? "" : "s"} in ${elapsedSeconds.toFixed(1)}s (${entriesPerSec.toFixed(1)} entries/s, ~${Math.round(tokensPerSec)} tokens/s).`
97785
+ message: `${throughputLine}; ${oversizedSkips.length} oversized skipped, ${timedOutSkips.length} timed out, ${failedSkips.length} failed.`
97490
97786
  });
97787
+ const printSkipList = (label, skipList) => {
97788
+ if (skipList.length === 0)
97789
+ return;
97790
+ const limit = isVerbose() ? skipList.length : 20;
97791
+ const listed = skipList.slice(0, limit).map((skip) => ` - ${pendingEntries[skip.index]?.itemRef ?? skip.index}: ${skip.message}`).join(`
97792
+ `);
97793
+ const more = skipList.length > limit ? `
97794
+ ...and ${skipList.length - limit} more` : "";
97795
+ onProgress({ phase: "embeddings", message: `[embed] ${label} skipped:
97796
+ ${listed}${more}` });
97797
+ };
97798
+ printSkipList("oversized documents", oversizedSkips);
97799
+ printSkipList("timed-out documents", timedOutSkips);
97800
+ printSkipList("failed documents", failedSkips);
97491
97801
  setMeta(db, "embeddingFingerprint", currentFingerprint);
97492
97802
  const observedIdentity = deriveObservedEmbeddingIdentity(config.embedding, observedModel, observedVectorLen);
97493
97803
  if (observedIdentity)
97494
97804
  setMeta(db, "embeddingIdentity", observedIdentity);
97805
+ if (circuitBreakerReason !== undefined) {
97806
+ const message = `embedding provider failed ${CIRCUIT_BREAKER_THRESHOLD} consecutive batches ` + `(last: ${circuitBreakerReason}); stopped after ${storedCount} embedding${storedCount === 1 ? "" : "s"} ` + "were stored — rerun akm index when the endpoint is healthy";
97807
+ warn(`[embed] ${message}`);
97808
+ onProgress({ phase: "embeddings", message });
97809
+ return { success: false, message, vecInsertFailures: vecFailedCount };
97810
+ }
97495
97811
  if (storedCount === 0 && embedFailedCount > 0) {
97496
97812
  const firstMessage = skips[0]?.message ?? "All embeddings failed.";
97497
97813
  return {
@@ -97499,6 +97815,7 @@ ${detail}${more}`);
97499
97815
  message: `All ${embedFailedCount} embedding batch(es) failed: ${firstMessage}`
97500
97816
  };
97501
97817
  }
97818
+ purgeEmbeddingSalvage(db);
97502
97819
  return { success: true, vecInsertFailures: vecFailedCount };
97503
97820
  } finally {
97504
97821
  if (heartbeatTimer)
@@ -97915,7 +98232,11 @@ async function indexWrittenAssets(stashDir, filePaths, options = {}) {
97915
98232
  return await (async () => {
97916
98233
  const rebuildProbe = probeLock(getIndexRebuildLockPath());
97917
98234
  if (rebuildProbe.state === "held") {
97918
- warn(`index rebuild in progress (pid ${rebuildProbe.holderPid}); the next index pass will index ${filePaths.join(", ")}`);
98235
+ const holderLabel = formatLockHolderPid({
98236
+ pid: rebuildProbe.holderPid,
98237
+ launcherPid: rebuildProbe.launcherPid ?? null
98238
+ });
98239
+ warn(`index rebuild in progress (pid ${holderLabel}); the next index pass will index ${filePaths.join(", ")}`);
97919
98240
  return true;
97920
98241
  }
97921
98242
  const dbPath = getDbPath();
@@ -98889,7 +99210,7 @@ var PROPOSAL_TXN_PHASES = [
98889
99210
  "committed"
98890
99211
  ];
98891
99212
  function proposalHash(content) {
98892
- return createHash10("sha256").update(content).digest("hex");
99213
+ return createHash11("sha256").update(content).digest("hex");
98893
99214
  }
98894
99215
  function proposalFileHash(filePath) {
98895
99216
  return proposalHash(fs55.readFileSync(filePath));