@cortexkit/pi-magic-context 0.49.11 → 0.49.13

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 (50) hide show
  1. package/dist/auto-search-worker.js +4 -4
  2. package/dist/chunk-embedding-write-worker.js +87 -0
  3. package/dist/{compaction-marker-candidates-n4dphyz4.js → compaction-marker-candidates-k4tqg4jb.js} +2 -2
  4. package/dist/compaction-marker-candidates-worker.js +1 -1
  5. package/dist/embedding-backfill-7kj16fh9.js +21 -0
  6. package/dist/embedding-worker-process.js +1 -1
  7. package/dist/embedding-worker.js +5 -5
  8. package/dist/historian-calibration-extension.js +2 -2
  9. package/dist/{index-ta0ed2nc.js → index-1k729e4f.js} +15 -15
  10. package/dist/index-1mrvw9en.js +225 -0
  11. package/dist/{index-g13fb2sj.js → index-3cnarwf8.js} +4 -4
  12. package/dist/{index-729kkhmf.js → index-46q4e104.js} +1 -1
  13. package/dist/{index-m8aaepft.js → index-53xdqb3d.js} +7 -7
  14. package/dist/{index-szh001qb.js → index-6dz2ed1v.js} +2 -2
  15. package/dist/{index-gqxsds3q.js → index-6ex901g3.js} +187 -27
  16. package/dist/{index-gycf3sp7.js → index-7bqrfyny.js} +46 -2
  17. package/dist/{index-drah07tf.js → index-7qc73yk5.js} +64 -7
  18. package/dist/{index-s9kwv5pp.js → index-7wagp5jp.js} +5 -5
  19. package/dist/{index-8yjhda46.js → index-9jvktm7k.js} +3 -3
  20. package/dist/{index-3veejgp8.js → index-apgwdby5.js} +1 -1
  21. package/dist/{index-0vq6ebae.js → index-aypx5h57.js} +58 -64
  22. package/dist/{index-43zqh10z.js → index-d4dt19md.js} +1 -1
  23. package/dist/{index-fdxzpnbj.js → index-djxc8w8c.js} +1 -1
  24. package/dist/{index-x7426vy4.js → index-ed9d2ded.js} +18 -5
  25. package/dist/{index-kh3jt7t9.js → index-f18ec69t.js} +1 -1
  26. package/dist/{index-4pxydwvg.js → index-g4jf772p.js} +1 -1
  27. package/dist/{index-4vj37yw9.js → index-he9s4qsj.js} +16 -6
  28. package/dist/{index-ttxsmaj5.js → index-jrap43gc.js} +1 -1
  29. package/dist/{index-78c864c8.js → index-nwbhe6qv.js} +324 -254
  30. package/dist/{index-9azmm7cg.js → index-t27xk4r7.js} +10 -10
  31. package/dist/{index-s3c9dskk.js → index-vv1j5cak.js} +8 -8
  32. package/dist/{index-2df03yc3.js → index-wp9q91kn.js} +8 -8
  33. package/dist/{index-xmdkde4v.js → index-y29y2j2w.js} +12 -1
  34. package/dist/index.js +854 -369
  35. package/dist/{logger-qg51hmp7.js → logger-dygt96rn.js} +1 -1
  36. package/dist/{message-index-5zjmsg57.js → message-index-7w179p64.js} +13 -13
  37. package/dist/{message-index-bounded-chgr6dn3.js → message-index-bounded-80n96ehp.js} +13 -13
  38. package/dist/{message-time-backfill-q9s88zdv.js → message-time-backfill-r2ar1qvk.js} +2 -2
  39. package/dist/migration-worker.js +9 -9
  40. package/dist/{project-embedding-registry-dk5axctd.js → project-embedding-registry-52zwmtbn.js} +11 -10
  41. package/dist/raw-ordinal-warmup-worker.js +12 -7
  42. package/dist/{read-session-chunk-3kb7r5pq.js → read-session-chunk-83864b37.js} +8 -8
  43. package/dist/{read-session-raw-hm7ptnrh.js → read-session-raw-azkqjgzg.js} +6 -2
  44. package/dist/{safe-notification-target-62ty6rvp.js → safe-notification-target-2rkvkmw8.js} +2 -2
  45. package/dist/{search-8pczyejh.js → search-hffb8xht.js} +20 -19
  46. package/dist/{storage-tags-zh23ynsh.js → storage-tags-zmf30zqp.js} +6 -4
  47. package/dist/subagent-entry.js +23 -22
  48. package/package.json +2 -2
  49. package/dist/embedding-backfill-1tknh2jn.js +0 -20
  50. /package/dist/{index-69vas84q.js → index-e6xnpach.js} +0 -0
@@ -1,30 +1,28 @@
1
- import {
2
- getHarness2
3
- } from "./index-0h2mkab2.js";
4
- import {
5
- sanitizeDiagnosticText,
6
- log2
7
- } from "./index-gycf3sp7.js";
8
- import {
9
- isUserHomeDirectory,
10
- setBootQuietPeriodForTests
11
- } from "./index-gqxsds3q.js";
12
1
  import {
13
2
  inRootAsyncContext2,
3
+ getSqliteDatabasePath2,
14
4
  withSqliteBackgroundWriter2,
15
5
  withoutSqliteTransformPass2,
16
6
  isTransientSqliteError2
17
7
  } from "./index-asrxgdt4.js";
18
8
  import {
19
9
  estimateTokens
20
- } from "./index-4pxydwvg.js";
10
+ } from "./index-g4jf772p.js";
11
+ import {
12
+ getHarness2
13
+ } from "./index-0h2mkab2.js";
14
+ import {
15
+ sanitizeDiagnosticText,
16
+ log2
17
+ } from "./index-7bqrfyny.js";
21
18
  import {
22
19
  SYNAPSE_DEFAULT_MODEL,
23
20
  isSynapseEmbeddingTruncated,
24
21
  getSynapseBatchRequestKey,
25
22
  SynapseEmbeddingProvider
26
- } from "./index-szh001qb.js";
23
+ } from "./index-6dz2ed1v.js";
27
24
  import {
25
+ DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS,
28
26
  CHUNK_WINDOW_SAFETY_RATIO,
29
27
  normalizeCompartmentChunkMaxInputTokens,
30
28
  buildCanonicalChunkTextFromFts,
@@ -40,20 +38,31 @@ import {
40
38
  countSessionCompartmentEmbedCoverage,
41
39
  countSessionCompartmentEmbedCoveragePolite,
42
40
  countUnembeddedSessionCompartmentsPolite
43
- } from "./index-drah07tf.js";
41
+ } from "./index-7qc73yk5.js";
44
42
  import {
45
43
  DEFAULT_LOCAL_EMBEDDING_MODEL
46
- } from "./index-ta0ed2nc.js";
44
+ } from "./index-1k729e4f.js";
47
45
  import {
48
46
  embeddingModelsMatch,
49
47
  resolveEmbeddingTextPrefixes,
50
48
  getEmbeddingProviderIdentity,
51
49
  dominantEmbeddingFailure,
52
50
  LocalEmbeddingProvider
53
- } from "./index-8yjhda46.js";
51
+ } from "./index-9jvktm7k.js";
52
+ import {
53
+ setBootQuietPeriodForTests
54
+ } from "./index-6ex901g3.js";
54
55
  import {
55
56
  isEmbeddingHostBusy2
56
57
  } from "./index-9p40dgkm.js";
58
+ import {
59
+ findMisScopedCompartmentChunkEmbeddingIdsForProject,
60
+ recordSessionProjectIdentity,
61
+ repairMisScopedCompartmentChunkEmbeddingsForProject,
62
+ acquireGitSweepLease,
63
+ renewGitSweepLease,
64
+ releaseGitSweepLease
65
+ } from "./index-1mrvw9en.js";
57
66
  import {
58
67
  saveEmbeddingIfHashMatches,
59
68
  getMemoryEmbedCoverage,
@@ -61,84 +70,34 @@ import {
61
70
  } from "./index-yhzhdp3m.js";
62
71
  import {
63
72
  logSlowWriteTransaction
64
- } from "./index-43zqh10z.js";
73
+ } from "./index-d4dt19md.js";
65
74
 
66
- // ../plugin/src/features/magic-context/session-project-storage.ts
67
- var SESSION_CHUNK_REPAIR_BATCH_SIZE = 100;
68
- var upsertSessionProjectStatements = new WeakMap;
69
- var repairSessionChunkProjectStatements = new WeakMap;
70
- var misScopedProjectChunkStatements = new WeakMap;
71
- var MIS_SCOPED_PROJECT_CHUNK_IDS_SQL = `
72
- SELECT e.id FROM compartment_chunk_embeddings e
73
- JOIN session_projects sp ON sp.session_id = e.session_id AND sp.harness = e.harness
74
- WHERE e.project_path = ? AND sp.project_path <> e.project_path
75
- UNION ALL
76
- SELECT e.id FROM session_projects sp
77
- JOIN compartment_chunk_embeddings e ON e.session_id = sp.session_id
78
- WHERE sp.project_path = ? AND e.harness = sp.harness AND e.project_path <> sp.project_path`;
79
- function getUpsertSessionProjectStatement(db) {
80
- let stmt = upsertSessionProjectStatements.get(db);
81
- if (!stmt) {
82
- stmt = db.prepare(`INSERT INTO session_projects (session_id, harness, project_path, updated_at)
83
- VALUES (?, ?, ?, ?)
84
- ON CONFLICT(session_id, harness) DO UPDATE SET
85
- project_path = excluded.project_path,
86
- updated_at = excluded.updated_at
87
- WHERE session_projects.project_path <> excluded.project_path`);
88
- upsertSessionProjectStatements.set(db, stmt);
89
- }
90
- return stmt;
91
- }
92
- function getRepairSessionChunkProjectStatement(db) {
93
- let stmt = repairSessionChunkProjectStatements.get(db);
94
- if (!stmt) {
95
- stmt = db.prepare(`UPDATE compartment_chunk_embeddings
96
- SET project_path = ?
97
- WHERE id IN (
98
- SELECT id
99
- FROM compartment_chunk_embeddings
100
- WHERE session_id = ?
101
- AND harness = ?
102
- AND project_path <> ?
103
- LIMIT ?
104
- )`);
105
- repairSessionChunkProjectStatements.set(db, stmt);
75
+ // ../plugin/src/hooks/magic-context/embed-session-state.ts
76
+ var embedPauseBySession = new Set;
77
+ var embedRunStateBySession = new Map;
78
+ var activeEmbeddingSessions = new Map;
79
+ var autoEmbedAttemptedBySession = new Set;
80
+ var autoEmbedIdentityBySession = new Map;
81
+ var autoEmbedRetryAfterBySession = new Map;
82
+ function invalidateAutoEmbedSession(sessionId) {
83
+ autoEmbedAttemptedBySession.delete(sessionId);
84
+ autoEmbedIdentityBySession.delete(sessionId);
85
+ autoEmbedRetryAfterBySession.delete(sessionId);
86
+ }
87
+ function getEmbedDrainUiStatus(sessionId, progress) {
88
+ if (embedPauseBySession.has(sessionId)) {
89
+ return { status: "paused" };
90
+ }
91
+ const activeRun = embedRunStateBySession.get(sessionId);
92
+ if (activeRun && !activeRun.signal.aborted || progress?.kind === "embed" && progress.phase === "recomp") {
93
+ return { status: "running" };
94
+ }
95
+ if (progress?.kind === "embed" && (progress.phase === "failed" || progress.phase === "skipped") && progress.message) {
96
+ if (/provider/i.test(progress.message)) {
97
+ return { status: "stopped", detail: progress.message };
98
+ }
106
99
  }
107
- return stmt;
108
- }
109
- function findMisScopedCompartmentChunkEmbeddingIdsForProject(db, projectPath) {
110
- return db.prepare(`SELECT id FROM (${MIS_SCOPED_PROJECT_CHUNK_IDS_SQL}) LIMIT 25`).all(projectPath, projectPath).map(({ id }) => id);
111
- }
112
- function recordSessionProjectIdentity(db, sessionId, projectPath) {
113
- if (!sessionId || !projectPath)
114
- return;
115
- if (!projectPath.startsWith("git:") && !projectPath.startsWith("dir:") && isUserHomeDirectory(projectPath))
116
- return;
117
- const harness = getHarness2();
118
- const now = Date.now();
119
- let previousProjectPath;
120
- db.transaction(() => {
121
- const existing = db.prepare("SELECT project_path FROM session_projects WHERE session_id = ? AND harness = ?").get(sessionId, harness);
122
- previousProjectPath = existing?.project_path;
123
- getUpsertSessionProjectStatement(db).run(sessionId, harness, projectPath, now);
124
- getRepairSessionChunkProjectStatement(db).run(projectPath, sessionId, harness, projectPath, SESSION_CHUNK_REPAIR_BATCH_SIZE);
125
- }).immediate();
126
- return previousProjectPath === projectPath ? undefined : { previousProjectPath, projectPath };
127
- }
128
- function repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectPath, ids = findMisScopedCompartmentChunkEmbeddingIdsForProject(db, projectPath)) {
129
- if (!projectPath || ids.length === 0)
130
- return 0;
131
- return db.prepare(`UPDATE compartment_chunk_embeddings
132
- SET project_path = (SELECT sp.project_path FROM session_projects sp
133
- WHERE sp.session_id = compartment_chunk_embeddings.session_id
134
- AND sp.harness = compartment_chunk_embeddings.harness)
135
- WHERE id IN (${ids.map(() => "?").join(",")}) AND EXISTS (
136
- SELECT 1 FROM session_projects sp
137
- WHERE sp.session_id = compartment_chunk_embeddings.session_id
138
- AND sp.harness = compartment_chunk_embeddings.harness
139
- AND sp.project_path <> compartment_chunk_embeddings.project_path
140
- AND (sp.project_path = ? OR compartment_chunk_embeddings.project_path = ?)
141
- )`).run(...ids, projectPath, projectPath).changes;
100
+ return { status: "idle" };
142
101
  }
143
102
 
144
103
  // ../plugin/src/shared/background-batch-drain.ts
@@ -219,6 +178,9 @@ function pruneSynapseBatchLedgerForProject(db, projectIdentity, ttlMs = SYNAPSE_
219
178
  return db.prepare(`DELETE FROM synapse_batch_ledger WHERE ${predicate}`).run(projectIdentity, shadow, cutoff).changes;
220
179
  }
221
180
 
181
+ // ../plugin/src/features/magic-context/memory/embedding-openai.ts
182
+ import { createHash as createHash2 } from "node:crypto";
183
+
222
184
  // ../plugin/src/features/magic-context/memory/embedding-ssrf.ts
223
185
  var METADATA_HOSTNAMES = new Set(["metadata.google.internal", "metadata.goog"]);
224
186
  var IPV6_METADATA_HOSTS = new Set(["fd00:ec2::254"]);
@@ -318,6 +280,55 @@ var FAILURE_THRESHOLD = 3;
318
280
  var FAILURE_WINDOW_MS = 60000;
319
281
  var OPEN_DURATION_MS = 5 * 60000;
320
282
  var FETCH_TIMEOUT_MS = 30000;
283
+ var RETRY_AFTER_CAP_MS = 60000;
284
+ var RATE_LIMIT_BACKOFF_BASE_MS = 1000;
285
+ var RATE_LIMIT_BACKOFF_CAP_MS = 30000;
286
+ var RATE_LIMIT_BACKOFF_JITTER = 0.25;
287
+ var rateLimitStates = new Map;
288
+ function rateLimitStateKey(endpoint, apiKey) {
289
+ const keyHash = apiKey ? createHash2("sha256").update(apiKey).digest("hex").slice(0, 16) : "";
290
+ return `${endpoint}\x00${keyHash}`;
291
+ }
292
+ function parseRetryAfterMs(value, nowMs) {
293
+ const trimmed = value?.trim() ?? "";
294
+ if (!trimmed)
295
+ return null;
296
+ let delayMs;
297
+ if (/^\d+(?:\.\d+)?$/.test(trimmed)) {
298
+ delayMs = Number(trimmed) * 1000;
299
+ } else {
300
+ const at = Date.parse(trimmed);
301
+ if (!Number.isFinite(at))
302
+ return null;
303
+ delayMs = at - nowMs;
304
+ }
305
+ if (!(delayMs > 0))
306
+ return null;
307
+ return Math.min(Math.ceil(delayMs), RETRY_AFTER_CAP_MS);
308
+ }
309
+ function rateLimitBackoffMs(consecutive, random = Math.random) {
310
+ const exponent = Math.min(Math.max(0, Math.floor(consecutive)), 30);
311
+ const nominal = Math.min(RATE_LIMIT_BACKOFF_CAP_MS, RATE_LIMIT_BACKOFF_BASE_MS * 2 ** exponent);
312
+ const jitter = Math.min(1, Math.max(0, random())) * RATE_LIMIT_BACKOFF_JITTER;
313
+ return Math.min(RATE_LIMIT_BACKOFF_CAP_MS, Math.round(nominal * (1 + jitter)));
314
+ }
315
+ function sleepUnlessAborted(ms, signal) {
316
+ if (signal?.aborted)
317
+ return Promise.resolve(false);
318
+ return new Promise((resolve) => {
319
+ const onAbort = () => {
320
+ clearTimeout(timer);
321
+ resolve(false);
322
+ };
323
+ const timer = setTimeout(() => {
324
+ signal?.removeEventListener("abort", onAbort);
325
+ resolve(true);
326
+ }, ms);
327
+ signal?.addEventListener("abort", onAbort, { once: true });
328
+ });
329
+ }
330
+ var RATE_LIMITED = Symbol("rate-limited");
331
+ var maxInputTokensWarnings = new Set;
321
332
 
322
333
  class OpenAICompatibleEmbeddingProvider {
323
334
  modelId;
@@ -338,6 +349,7 @@ class OpenAICompatibleEmbeddingProvider {
338
349
  lastFailureReason = null;
339
350
  halfOpenProbeInFlight = false;
340
351
  queryTruncationLogged = false;
352
+ rateLimitKey;
341
353
  constructor(options) {
342
354
  this.endpoint = normalizeEndpoint(options.endpoint);
343
355
  this.model = options.model?.trim() ?? "";
@@ -348,7 +360,16 @@ class OpenAICompatibleEmbeddingProvider {
348
360
  this.queryPrefix = prefixes.queryPrefix;
349
361
  this.documentPrefix = prefixes.documentPrefix;
350
362
  this.truncate = options.truncate?.trim() ?? "";
351
- this.maxInputTokens = typeof options.maxInputTokens === "number" && Number.isFinite(options.maxInputTokens) ? Math.max(1, Math.floor(options.maxInputTokens)) : 512;
363
+ this.rateLimitKey = rateLimitStateKey(this.endpoint, this.apiKey);
364
+ const maxInputTokensSet = typeof options.maxInputTokens === "number" && Number.isFinite(options.maxInputTokens);
365
+ this.maxInputTokens = maxInputTokensSet ? Math.max(1, Math.floor(options.maxInputTokens)) : DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS;
366
+ if (!maxInputTokensSet && this.endpoint && this.model) {
367
+ const warningKey = `${this.endpoint}\x00${this.model}`;
368
+ if (!maxInputTokensWarnings.has(warningKey)) {
369
+ maxInputTokensWarnings.add(warningKey);
370
+ log2(`[magic-context] embedding.max_input_tokens is not set for openai-compatible model "${sanitizeDiagnosticText(this.model)}"; chunk embeddings use the ${DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS}-token default, which is sized for the bundled local model and splits history into small pieces. Set embedding.max_input_tokens to the model's input limit (changing it re-embeds stored chunks once).`);
371
+ }
372
+ }
352
373
  this.modelId = getEmbeddingProviderIdentity({
353
374
  provider: "openai-compatible",
354
375
  endpoint: this.endpoint,
@@ -406,6 +427,23 @@ class OpenAICompatibleEmbeddingProvider {
406
427
  if (signal?.aborted) {
407
428
  return Array.from({ length: texts.length }, () => null);
408
429
  }
430
+ const isQuery = purpose === "query";
431
+ for (;; ) {
432
+ if (Date.now() < this.rateLimit.until) {
433
+ if (isQuery)
434
+ return Array.from({ length: texts.length }, () => null);
435
+ if (!await this.waitForRateLimitCooldown(signal)) {
436
+ return Array.from({ length: texts.length }, () => null);
437
+ }
438
+ }
439
+ const outcome = await this.requestOnce(texts, requestTexts, signal, purpose);
440
+ if (outcome !== RATE_LIMITED)
441
+ return outcome;
442
+ if (isQuery)
443
+ return Array.from({ length: texts.length }, () => null);
444
+ }
445
+ }
446
+ async requestOnce(texts, requestTexts, signal, purpose) {
409
447
  let isProbe = false;
410
448
  let internalController;
411
449
  let timeoutHandle;
@@ -438,9 +476,14 @@ class OpenAICompatibleEmbeddingProvider {
438
476
  redirect: "error",
439
477
  signal: internalController.signal
440
478
  });
479
+ if (response.status === 429) {
480
+ await response.text().catch(() => "");
481
+ this.noteRateLimit(response.headers.get("retry-after"));
482
+ return RATE_LIMITED;
483
+ }
441
484
  if (!response.ok) {
442
485
  const excerpt = await response.text().catch(() => "");
443
- const failure = this.failure("http_error", `HTTP ${response.status} from endpoint${this.bodyExcerpt(excerpt)}`, response.status >= 500 || response.status === 408 || response.status === 429);
486
+ const failure = this.failure("http_error", `HTTP ${response.status} from endpoint${this.bodyExcerpt(excerpt)}`, response.status >= 500 || response.status === 408);
444
487
  log2(`[magic-context] openai-compatible embedding request failed: ${failure.reason}`);
445
488
  this.recordFailure(isProbe, failure);
446
489
  return Array.from({ length: texts.length }, () => null);
@@ -539,6 +582,36 @@ class OpenAICompatibleEmbeddingProvider {
539
582
  isLoaded() {
540
583
  return this.initialized;
541
584
  }
585
+ get rateLimit() {
586
+ let state = rateLimitStates.get(this.rateLimitKey);
587
+ if (!state) {
588
+ state = { until: 0, consecutive: 0 };
589
+ rateLimitStates.set(this.rateLimitKey, state);
590
+ }
591
+ return state;
592
+ }
593
+ async waitForRateLimitCooldown(signal) {
594
+ for (;; ) {
595
+ const remaining = this.rateLimit.until - Date.now();
596
+ if (remaining <= 0)
597
+ return true;
598
+ if (!await sleepUnlessAborted(remaining, signal))
599
+ return false;
600
+ }
601
+ }
602
+ noteRateLimit(retryAfter) {
603
+ const state = this.rateLimit;
604
+ const now = Date.now();
605
+ const headerDelayMs = parseRetryAfterMs(retryAfter, now);
606
+ const delayMs = headerDelayMs ?? rateLimitBackoffMs(state.consecutive);
607
+ state.consecutive += 1;
608
+ const alreadyCoolingDown = now < state.until;
609
+ state.until = Math.max(state.until, now + delayMs);
610
+ const seconds = Math.ceil(delayMs / 1000);
611
+ if (!alreadyCoolingDown) {
612
+ log2(`[magic-context] openai-compatible embedding rate limited (HTTP 429): pausing requests for ${seconds}s (${headerDelayMs !== null ? "Retry-After" : "backoff"}); background embedding resumes after the pause and searches run lexical-only until then`);
613
+ }
614
+ }
542
615
  claimProbeOrShortCircuit() {
543
616
  if (this.circuitOpenUntil === 0) {
544
617
  return "allow";
@@ -593,6 +666,7 @@ class OpenAICompatibleEmbeddingProvider {
593
666
  this.circuitOpenUntil = 0;
594
667
  this.openLogged = false;
595
668
  this.lastFailureReason = null;
669
+ this.rateLimit.consecutive = 0;
596
670
  }
597
671
  getLastFailureReason() {
598
672
  return this.lastFailureReason;
@@ -608,6 +682,9 @@ class OpenAICompatibleEmbeddingProvider {
608
682
  _getFailureCount() {
609
683
  return this.failureTimes.length;
610
684
  }
685
+ _getRateLimitedUntil() {
686
+ return this.rateLimit.until;
687
+ }
611
688
  _resetCircuit() {
612
689
  this.failureTimes = [];
613
690
  this.circuitOpenUntil = 0;
@@ -618,7 +695,85 @@ class OpenAICompatibleEmbeddingProvider {
618
695
  }
619
696
 
620
697
  // ../plugin/src/features/magic-context/project-embedding-registry.ts
621
- import { createHash as createHash2, randomUUID } from "node:crypto";
698
+ import { createHash as createHash3, randomUUID } from "node:crypto";
699
+
700
+ // ../plugin/src/features/magic-context/chunk-embedding-writer.ts
701
+ import { Worker } from "node:worker_threads";
702
+ function createChunkEmbeddingWriter(db) {
703
+ const path = getSqliteDatabasePath2(db);
704
+ if (!path)
705
+ return null;
706
+ if (path.startsWith("file:"))
707
+ throw new Error("chunk embedding writer requires a filesystem path");
708
+ const entry = new URL(import.meta.url.endsWith(".ts") ? "./chunk-embedding-write-worker.ts" : "./chunk-embedding-write-worker.js", import.meta.url);
709
+ const timeout = db.prepare("PRAGMA busy_timeout").get().timeout;
710
+ const worker = inRootAsyncContext2(() => new Worker(entry, { workerData: { path, harness: getHarness2(), timeout } }));
711
+ let pending;
712
+ let failure;
713
+ let deadline;
714
+ worker.on("message", (reply) => {
715
+ clearTimeout(deadline);
716
+ const request = pending;
717
+ pending = undefined;
718
+ if (reply.error)
719
+ request?.reject(new Error(reply.error));
720
+ else
721
+ request?.resolve(reply.value);
722
+ });
723
+ const fail = (error) => {
724
+ clearTimeout(deadline);
725
+ failure = error;
726
+ pending?.reject(error);
727
+ pending = undefined;
728
+ };
729
+ worker.on("error", fail);
730
+ worker.on("exit", (code) => fail(new Error(`chunk embedding writer exited (${code})`)));
731
+ worker.unref();
732
+ let queue = Promise.resolve();
733
+ const request = (message) => {
734
+ const result = queue.then(() => {
735
+ if (failure)
736
+ throw failure;
737
+ return new Promise((resolve, reject) => {
738
+ pending = { resolve, reject };
739
+ deadline = inRootAsyncContext2(() => setTimeout(() => {
740
+ fail(new Error("chunk embedding database operation timed out; coverage will be rechecked"));
741
+ worker.terminate();
742
+ }, 30000));
743
+ deadline.unref();
744
+ worker.postMessage(message);
745
+ });
746
+ });
747
+ queue = result.catch(() => {});
748
+ return result;
749
+ };
750
+ return {
751
+ async replace(rows, sourceKey) {
752
+ if (rows.some((row) => isSynapseEmbeddingTruncated(row.vector)))
753
+ return;
754
+ await request({ kind: "replace", rows, sourceKey });
755
+ },
756
+ project: (project, session) => request({ kind: "project", project, session }),
757
+ count: (project, session, model, tokens) => request({ kind: "count", project, session, model, tokens }),
758
+ candidates: (project, session, model, limit, excluded, tokens) => request({
759
+ kind: "candidates",
760
+ project,
761
+ session,
762
+ model,
763
+ limit,
764
+ excluded,
765
+ tokens
766
+ }),
767
+ backoff: (candidate, project, model) => request({ kind: "backoff", candidate, project, model }),
768
+ acquire: (project, holder) => request({ kind: "acquire", project, holder }),
769
+ renew: (project, holder) => request({ kind: "renew", project, holder }),
770
+ release: (project, holder) => request({ kind: "release", project, holder }),
771
+ async close() {
772
+ clearTimeout(deadline);
773
+ await worker.terminate();
774
+ }
775
+ };
776
+ }
622
777
 
623
778
  // ../plugin/src/features/magic-context/git-commits/storage-git-commit-embeddings.ts
624
779
  var saveStatements = new WeakMap;
@@ -802,142 +957,6 @@ function enforceProjectCap(db, projectPath, maxCommits) {
802
957
  return evicted;
803
958
  }
804
959
 
805
- // ../plugin/src/features/magic-context/git-commits/sweep-coordinator.ts
806
- var GIT_SWEEP_COOLDOWN_MS = 10 * 60 * 1000;
807
- var GIT_SWEEP_LEASE_TTL_MS = 5 * 60 * 1000;
808
- var GIT_SWEEP_NON_INDEXABLE_REPROBE_MS = 24 * 60 * 60 * 1000;
809
- var GIT_SWEEP_LEASE_RENEWAL_MS = 60 * 1000;
810
- function runImmediate(db, body) {
811
- db.exec("BEGIN IMMEDIATE");
812
- const transactionStartedAt = performance.now();
813
- let committed = false;
814
- try {
815
- const result = body();
816
- db.exec("COMMIT");
817
- committed = true;
818
- logSlowWriteTransaction("git_sweep_lease", transactionStartedAt);
819
- return result;
820
- } finally {
821
- if (!committed) {
822
- try {
823
- db.exec("ROLLBACK");
824
- } catch {}
825
- }
826
- }
827
- }
828
- function rowToState(row) {
829
- return {
830
- projectPath: row.project_path,
831
- leaseHolder: row.lease_holder,
832
- leaseExpiresAt: row.lease_expires_at,
833
- lastSweptAt: row.last_swept_at
834
- };
835
- }
836
- function getGitSweepCoordinatorState(db, projectPath) {
837
- const row = db.prepare(`SELECT project_path, lease_holder, lease_expires_at, last_swept_at
838
- FROM git_sweep_coordinator
839
- WHERE project_path = ?`).get(projectPath);
840
- return row ? rowToState(row) : null;
841
- }
842
- function acquireGitSweepLease(db, projectPath, holderId, options = {}) {
843
- const cooldownMs = options.cooldownMs ?? GIT_SWEEP_COOLDOWN_MS;
844
- const leaseTtlMs = options.leaseTtlMs ?? GIT_SWEEP_LEASE_TTL_MS;
845
- return runImmediate(db, () => {
846
- const now = Date.now();
847
- const row = getGitSweepCoordinatorState(db, projectPath);
848
- if (row?.leaseHolder && row.leaseExpiresAt !== null && row.leaseExpiresAt > now) {
849
- return {
850
- acquired: false,
851
- projectPath,
852
- reason: "lease_active",
853
- leaseHolder: row.leaseHolder,
854
- leaseExpiresAt: row.leaseExpiresAt,
855
- lastSweptAt: row.lastSweptAt,
856
- nextAllowedAt: null
857
- };
858
- }
859
- if (!options.ignoreCooldown && row?.lastSweptAt !== null && row?.lastSweptAt !== undefined) {
860
- const nextAllowedAt = row.lastSweptAt + cooldownMs;
861
- if (nextAllowedAt > now) {
862
- return {
863
- acquired: false,
864
- projectPath,
865
- reason: "cooldown_active",
866
- leaseHolder: row.leaseHolder,
867
- leaseExpiresAt: row.leaseExpiresAt,
868
- lastSweptAt: row.lastSweptAt,
869
- nextAllowedAt
870
- };
871
- }
872
- }
873
- const leaseExpiresAt = now + leaseTtlMs;
874
- db.prepare(`INSERT INTO git_sweep_coordinator (
875
- project_path,
876
- lease_holder,
877
- lease_expires_at,
878
- last_swept_at
879
- ) VALUES (?, ?, ?, NULL)
880
- ON CONFLICT(project_path) DO UPDATE SET
881
- lease_holder = excluded.lease_holder,
882
- lease_expires_at = excluded.lease_expires_at`).run(projectPath, holderId, leaseExpiresAt);
883
- return {
884
- acquired: true,
885
- projectPath,
886
- holderId,
887
- acquiredAt: now,
888
- leaseExpiresAt
889
- };
890
- });
891
- }
892
- function renewGitSweepLease(db, projectPath, holderId, leaseTtlMs = GIT_SWEEP_LEASE_TTL_MS) {
893
- return runImmediate(db, () => {
894
- const now = Date.now();
895
- const leaseExpiresAt = now + leaseTtlMs;
896
- const result = db.prepare(`UPDATE git_sweep_coordinator
897
- SET lease_expires_at = ?
898
- WHERE project_path = ?
899
- AND lease_holder = ?
900
- AND lease_expires_at > ?`).run(leaseExpiresAt, projectPath, holderId, now);
901
- return result.changes === 1;
902
- });
903
- }
904
- function markGitSweepSuccessAndRelease(db, projectPath, holderId) {
905
- return runImmediate(db, () => {
906
- const now = Date.now();
907
- const result = db.prepare(`UPDATE git_sweep_coordinator
908
- SET lease_holder = NULL,
909
- lease_expires_at = NULL,
910
- last_swept_at = ?
911
- WHERE project_path = ?
912
- AND lease_holder = ?
913
- AND lease_expires_at > ?`).run(now, projectPath, holderId, now);
914
- return result.changes === 1;
915
- });
916
- }
917
- function parkGitSweepNonIndexable(db, projectPath, holderId, reprobeMs = GIT_SWEEP_NON_INDEXABLE_REPROBE_MS) {
918
- return runImmediate(db, () => {
919
- const now = Date.now();
920
- const sweptAt = now + reprobeMs - GIT_SWEEP_COOLDOWN_MS;
921
- const result = db.prepare(`UPDATE git_sweep_coordinator
922
- SET lease_holder = NULL,
923
- lease_expires_at = NULL,
924
- last_swept_at = ?
925
- WHERE project_path = ?
926
- AND lease_holder = ?
927
- AND lease_expires_at > ?`).run(sweptAt, projectPath, holderId, now);
928
- return result.changes === 1;
929
- });
930
- }
931
- function releaseGitSweepLease(db, projectPath, holderId) {
932
- runImmediate(db, () => {
933
- db.prepare(`UPDATE git_sweep_coordinator
934
- SET lease_holder = NULL,
935
- lease_expires_at = NULL
936
- WHERE project_path = ?
937
- AND lease_holder = ?`).run(projectPath, holderId);
938
- });
939
- }
940
-
941
960
  // ../plugin/src/features/magic-context/shadow-backfill-state.ts
942
961
  var SHADOW_BACKFILL_PROVENANCE_KEY = "_magicContextShadowBackfill";
943
962
  function parseJsonRecord(value) {
@@ -1301,10 +1320,10 @@ function stableStringify(value) {
1301
1320
  return JSON.stringify(value);
1302
1321
  }
1303
1322
  function sha256Prefix(value, length = 16) {
1304
- return createHash2("sha256").update(value).digest("hex").slice(0, length);
1323
+ return createHash3("sha256").update(value).digest("hex").slice(0, length);
1305
1324
  }
1306
1325
  function contentSha2562(value) {
1307
- return createHash2("sha256").update(value).digest("hex");
1326
+ return createHash3("sha256").update(value).digest("hex");
1308
1327
  }
1309
1328
  function getRuntimeFingerprint(config) {
1310
1329
  if (config.provider === "off") {
@@ -2628,22 +2647,31 @@ async function drainCommitBacklogForProject2(db, projectIdentity, deadline) {
2628
2647
  }
2629
2648
  return total;
2630
2649
  }
2631
- async function embedCompartmentChunkBatch(db, projectIdentity, batchSize) {
2650
+ async function embedCompartmentChunkBatch(db, projectIdentity, batchSize, signal) {
2632
2651
  const snapshot = getProjectEmbeddingSnapshot2(projectIdentity);
2633
2652
  if (!snapshot?.historyEnabled || snapshot.chunkModelId === "off")
2634
2653
  return 0;
2635
2654
  repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectIdentity);
2636
- const candidates = await loadUnembeddedCompartmentChunkCandidatesPolite(db, projectIdentity, snapshot.chunkModelId, batchSize, getProjectEmbeddingMaxInputTokens2(projectIdentity), true);
2655
+ const priorModels = db.prepare(`SELECT COUNT(*) AS total,
2656
+ COALESCE(MAX(model_id = ?), 0) AS current
2657
+ FROM compartment_chunk_embeddings WHERE compartment_id = ?`);
2658
+ const candidates = await loadUnembeddedCompartmentChunkCandidatesPolite(db, projectIdentity, snapshot.chunkModelId, batchSize, getProjectEmbeddingMaxInputTokens2(projectIdentity), true, (candidate) => {
2659
+ const activity = activeEmbeddingSessions.get(candidate.sessionId);
2660
+ if (activity?.project === projectIdentity && activity.model === snapshot.chunkModelId && !embedPauseBySession.has(candidate.sessionId))
2661
+ return true;
2662
+ const prior = priorModels.get(snapshot.chunkModelId, candidate.id);
2663
+ return prior.total === 0 || prior.current !== 0;
2664
+ });
2637
2665
  if (candidates.length === 0)
2638
2666
  return 0;
2639
- const { embedded } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates);
2667
+ const { embedded } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, signal);
2640
2668
  return embedded;
2641
2669
  }
2642
2670
  function getLastEmbeddingFailureForProject(projectIdentity) {
2643
2671
  const registration = projectRegistrations.get(projectIdentity);
2644
2672
  return registration?.provider?.getLastFailureReason?.() ?? null;
2645
2673
  }
2646
- async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates, signal) {
2674
+ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates, signal, writer) {
2647
2675
  const noWork = [];
2648
2676
  const failed = [];
2649
2677
  const failureReasons = [];
@@ -2658,7 +2686,10 @@ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates
2658
2686
  continue;
2659
2687
  const canonicalText = mappedText || buildCompartmentSummaryFallbackText(db, candidate.id);
2660
2688
  if (canonicalText.length === 0) {
2661
- recordChunkEmbedBackoff(db, candidate, projectIdentity, modelId);
2689
+ if (writer)
2690
+ await writer.backoff(candidate, projectIdentity, modelId);
2691
+ else
2692
+ recordChunkEmbedBackoff(db, candidate, projectIdentity, modelId);
2662
2693
  noWork.push(candidate.id);
2663
2694
  continue;
2664
2695
  }
@@ -2715,6 +2746,8 @@ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates
2715
2746
  if (result) {
2716
2747
  let writeSliceStarted = performance.now();
2717
2748
  for (const item of slice) {
2749
+ if (signal?.aborted || getProjectChunkEmbeddingModelId2(projectIdentity) !== modelId)
2750
+ break;
2718
2751
  if (persistedIds.has(item.candidate.id))
2719
2752
  continue;
2720
2753
  const vectors = item.windows.map((window) => result.vectors.get(`chunk:${item.candidate.id}:${window.windowIndex}`));
@@ -2729,7 +2762,10 @@ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates
2729
2762
  modelId,
2730
2763
  vector: vectors[index]
2731
2764
  }));
2732
- replaceCompartmentChunkEmbeddings(db, rows, item.windowSourceKey);
2765
+ if (writer)
2766
+ await writer.replace(rows, item.windowSourceKey);
2767
+ else
2768
+ replaceCompartmentChunkEmbeddings(db, rows, item.windowSourceKey);
2733
2769
  persistedIds.add(item.candidate.id);
2734
2770
  enqueueShadowEmbeddingItems2(projectIdentity, "chunk", [
2735
2771
  String(item.candidate.id)
@@ -2754,7 +2790,10 @@ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates
2754
2790
  if (!signal?.aborted) {
2755
2791
  for (const item of slice) {
2756
2792
  if (!persistedIds.has(item.candidate.id)) {
2757
- recordChunkEmbedBackoff(db, item.candidate, projectIdentity, modelId);
2793
+ if (writer)
2794
+ await writer.backoff(item.candidate, projectIdentity, modelId);
2795
+ else
2796
+ recordChunkEmbedBackoff(db, item.candidate, projectIdentity, modelId);
2758
2797
  failed.push(item.candidate.id);
2759
2798
  }
2760
2799
  }
@@ -2773,6 +2812,9 @@ async function drainCompartmentChunkBacklogForProject(db, projectIdentity, deadl
2773
2812
  }
2774
2813
  let total = 0;
2775
2814
  let leaseLost = false;
2815
+ const drainAbort = new AbortController;
2816
+ const deadlineTimer = setTimeout(() => drainAbort.abort(), Math.max(0, deadline - Date.now()));
2817
+ deadlineTimer.unref?.();
2776
2818
  const renewal = inRootAsyncContext2(() => setInterval(() => {
2777
2819
  try {
2778
2820
  if (!renewGitSweepLease(db, projectIdentity, holderId))
@@ -2781,8 +2823,8 @@ async function drainCompartmentChunkBacklogForProject(db, projectIdentity, deadl
2781
2823
  }, SESSION_EMBED_LEASE_RENEWAL_MS));
2782
2824
  renewal.unref?.();
2783
2825
  try {
2784
- while (!leaseLost && !isEmbeddingHostBusy2() && Date.now() < deadline && total < CHUNK_DRAIN_MAX_PER_SWEEP) {
2785
- const embedded = await embedCompartmentChunkBatch(db, projectIdentity, CHUNK_DRAIN_BATCH_SIZE);
2826
+ while (!leaseLost && !drainAbort.signal.aborted && !isEmbeddingHostBusy2() && Date.now() < deadline && total < CHUNK_DRAIN_MAX_PER_SWEEP) {
2827
+ const embedded = await embedCompartmentChunkBatch(db, projectIdentity, CHUNK_DRAIN_BATCH_SIZE, drainAbort.signal);
2786
2828
  if (!renewGitSweepLease(db, projectIdentity, holderId))
2787
2829
  leaseLost = true;
2788
2830
  if (leaseLost || embedded === 0)
@@ -2792,6 +2834,7 @@ async function drainCompartmentChunkBacklogForProject(db, projectIdentity, deadl
2792
2834
  break;
2793
2835
  }
2794
2836
  } finally {
2837
+ clearTimeout(deadlineTimer);
2795
2838
  clearInterval(renewal);
2796
2839
  releaseGitSweepLease(db, projectIdentity, holderId);
2797
2840
  }
@@ -2805,11 +2848,28 @@ async function embedSessionCompartmentChunks2(db, projectIdentity, sessionId, op
2805
2848
  if (!snapshot?.historyEnabled || snapshot.chunkModelId === "off") {
2806
2849
  return { status: "disabled", embedded: 0, total: 0 };
2807
2850
  }
2808
- recordSessionProjectIdentity(db, sessionId, projectIdentity);
2851
+ if (options?.automatic && !options.databaseWorker) {
2852
+ const worker = createChunkEmbeddingWriter(db);
2853
+ if (worker) {
2854
+ try {
2855
+ return await embedSessionCompartmentChunks2(db, projectIdentity, sessionId, {
2856
+ ...options,
2857
+ databaseWorker: worker
2858
+ });
2859
+ } finally {
2860
+ await worker.close();
2861
+ }
2862
+ }
2863
+ }
2864
+ const writer = options?.databaseWorker ?? null;
2865
+ if (writer)
2866
+ await writer.project(projectIdentity, sessionId);
2867
+ else
2868
+ recordSessionProjectIdentity(db, sessionId, projectIdentity);
2809
2869
  const maxInputTokens = getProjectEmbeddingMaxInputTokens2(projectIdentity);
2810
- const total = await countUnembeddedSessionCompartmentsPolite(db, projectIdentity, sessionId, snapshot.chunkModelId, maxInputTokens);
2870
+ const total = writer ? await writer.count(projectIdentity, sessionId, snapshot.chunkModelId, maxInputTokens) : await countUnembeddedSessionCompartmentsPolite(db, projectIdentity, sessionId, snapshot.chunkModelId, maxInputTokens);
2811
2871
  const holderId = `session-embed-${randomUUID()}`;
2812
- const lease = acquireGitSweepLease(db, projectIdentity, holderId, { ignoreCooldown: true });
2872
+ const lease = writer ? await writer.acquire(projectIdentity, holderId) : acquireGitSweepLease(db, projectIdentity, holderId, { ignoreCooldown: true });
2813
2873
  if (!lease.acquired)
2814
2874
  return { status: "busy", embedded: 0, total };
2815
2875
  let leaseLost = false;
@@ -2819,9 +2879,9 @@ async function embedSessionCompartmentChunks2(db, projectIdentity, sessionId, op
2819
2879
  drainAbort.abort();
2820
2880
  else
2821
2881
  options?.signal?.addEventListener("abort", forwardCallerAbort, { once: true });
2822
- const renewal = inRootAsyncContext2(() => setInterval(() => {
2882
+ const renewal = inRootAsyncContext2(() => setInterval(async () => {
2823
2883
  try {
2824
- if (!renewGitSweepLease(db, projectIdentity, holderId)) {
2884
+ if (!(writer ? await writer.renew(projectIdentity, holderId) : renewGitSweepLease(db, projectIdentity, holderId))) {
2825
2885
  leaseLost = true;
2826
2886
  drainAbort.abort();
2827
2887
  }
@@ -2839,11 +2899,18 @@ async function embedSessionCompartmentChunks2(db, projectIdentity, sessionId, op
2839
2899
  try {
2840
2900
  options?.onProgress?.({ embedded, total });
2841
2901
  for (;; ) {
2842
- if (leaseLost || drainAbort.signal.aborted || isEmbeddingHostBusy2()) {
2902
+ if (leaseLost || drainAbort.signal.aborted || !options?.automatic && isEmbeddingHostBusy2() || getProjectEmbeddingSnapshot2(projectIdentity)?.generation !== snapshot.generation) {
2843
2903
  aborted = true;
2844
2904
  break;
2845
2905
  }
2846
- const candidates = await loadUnembeddedSessionChunkCandidatesPolite(db, projectIdentity, sessionId, snapshot.chunkModelId, batchSize, [...skipIds, ...failedIds], maxInputTokens, true);
2906
+ if (options?.automatic && isEmbeddingHostBusy2()) {
2907
+ await new Promise((resolve) => setTimeout(resolve, 100));
2908
+ if (drainAbort.signal.aborted) {
2909
+ aborted = true;
2910
+ break;
2911
+ }
2912
+ }
2913
+ const candidates = writer ? await writer.candidates(projectIdentity, sessionId, snapshot.chunkModelId, batchSize, [...skipIds, ...failedIds], maxInputTokens) : await loadUnembeddedSessionChunkCandidatesPolite(db, projectIdentity, sessionId, snapshot.chunkModelId, batchSize, [...skipIds, ...failedIds], maxInputTokens, true);
2847
2914
  if (candidates.length === 0)
2848
2915
  break;
2849
2916
  const {
@@ -2851,8 +2918,8 @@ async function embedSessionCompartmentChunks2(db, projectIdentity, sessionId, op
2851
2918
  noWork,
2852
2919
  failed,
2853
2920
  failureReasons: batchFailureReasons
2854
- } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, drainAbort.signal);
2855
- if (leaseLost || !renewGitSweepLease(db, projectIdentity, holderId)) {
2921
+ } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, drainAbort.signal, writer);
2922
+ if (leaseLost || !(writer ? await writer.renew(projectIdentity, holderId) : renewGitSweepLease(db, projectIdentity, holderId))) {
2856
2923
  leaseLost = true;
2857
2924
  drainAbort.abort();
2858
2925
  aborted = true;
@@ -2880,7 +2947,10 @@ async function embedSessionCompartmentChunks2(db, projectIdentity, sessionId, op
2880
2947
  clearInterval(renewal);
2881
2948
  options?.signal?.removeEventListener("abort", forwardCallerAbort);
2882
2949
  try {
2883
- releaseGitSweepLease(db, projectIdentity, holderId);
2950
+ if (writer)
2951
+ await writer.release(projectIdentity, holderId);
2952
+ else
2953
+ releaseGitSweepLease(db, projectIdentity, holderId);
2884
2954
  } catch (error) {
2885
2955
  log2("[magic-context] embed drain: lease release failed (will TTL-expire):", error);
2886
2956
  }
@@ -2890,7 +2960,7 @@ async function embedSessionCompartmentChunks2(db, projectIdentity, sessionId, op
2890
2960
  if (aborted)
2891
2961
  return { status: "aborted", embedded, total, failed: failedIds.length };
2892
2962
  if (providerDown || failedIds.length > 0) {
2893
- const remaining = Math.max(failedIds.length, await countUnembeddedSessionCompartmentsPolite(db, projectIdentity, sessionId, snapshot.chunkModelId, maxInputTokens) - skipIds.length);
2963
+ const remaining = Math.max(failedIds.length, (writer ? await writer.count(projectIdentity, sessionId, snapshot.chunkModelId, maxInputTokens) : await countUnembeddedSessionCompartmentsPolite(db, projectIdentity, sessionId, snapshot.chunkModelId, maxInputTokens)) - skipIds.length);
2894
2964
  if (remaining > 0) {
2895
2965
  return {
2896
2966
  status: "stalled",
@@ -3025,4 +3095,4 @@ function _resetProjectEmbeddingRegistryForTests2() {
3025
3095
  setBootQuietPeriodForTests(null);
3026
3096
  }
3027
3097
 
3028
- export { recordSessionProjectIdentity, drainBackgroundBatches, recordEmbeddingMeasurement, OpenAICompatibleEmbeddingProvider, saveCommitEmbedding, loadProjectCommitEmbeddings, loadUnembeddedCommits, countEmbeddedCommits, upsertCommits, getLatestIndexedCommitSha, enforceProjectCap, GIT_SWEEP_LEASE_RENEWAL_MS, acquireGitSweepLease, renewGitSweepLease, markGitSweepSuccessAndRelease, parkGitSweepNonIndexable, releaseGitSweepLease, describeShadowBackfillWriteRefusal2, listShadowBackfillStalls2, getShadowBackfillStopReason2, markProjectLoadUntrusted2, contentSha2562, drainProjectEmbeddingIdentityMaintenance2, drainStaleEmbeddingIdentitiesForProject2, registerProjectEmbedding2, registerProjectShadowEmbedding2, getShadowEmbeddingMeasurementCohort2, getPrimaryEmbeddingMeasurementCohort2, embedShadowTextForProject2, enqueueShadowEmbeddingItems2, getShadowBackfillRemaining2, flushShadowEmbeddingBacklog2, registerProjectInObservationMode2, unregisterProjectShadowEmbedding2, unregisterProjectEmbedding2, getProjectEmbeddingSnapshot2, installProjectEmbeddingSearchBridge2, getProjectChunkEmbeddingModelId2, getProjectEmbeddingMaxInputTokens2, embedTextForProject2, embedBatchForProject2, embedItemsForProject2, embedUnembeddedMemoriesForProject2, drainCommitBacklogForProject2, embedUnembeddedCompartmentChunksForProject2, embedSessionCompartmentChunks2, getEmbeddingCoverageStatus2, getAutoEmbeddingSessionCoverage2, sweepAllRegisteredProjects2, _setTestProviderFactoryForProject2, _resetProjectEmbeddingRegistryForTests2 };
3098
+ export { embedPauseBySession, embedRunStateBySession, activeEmbeddingSessions, autoEmbedAttemptedBySession, autoEmbedIdentityBySession, autoEmbedRetryAfterBySession, invalidateAutoEmbedSession, getEmbedDrainUiStatus, drainBackgroundBatches, recordEmbeddingMeasurement, OpenAICompatibleEmbeddingProvider, saveCommitEmbedding, loadProjectCommitEmbeddings, loadUnembeddedCommits, countEmbeddedCommits, upsertCommits, getLatestIndexedCommitSha, enforceProjectCap, describeShadowBackfillWriteRefusal2, listShadowBackfillStalls2, getShadowBackfillStopReason2, markProjectLoadUntrusted2, contentSha2562, drainProjectEmbeddingIdentityMaintenance2, drainStaleEmbeddingIdentitiesForProject2, registerProjectEmbedding2, registerProjectShadowEmbedding2, getShadowEmbeddingMeasurementCohort2, getPrimaryEmbeddingMeasurementCohort2, embedShadowTextForProject2, enqueueShadowEmbeddingItems2, getShadowBackfillRemaining2, flushShadowEmbeddingBacklog2, registerProjectInObservationMode2, unregisterProjectShadowEmbedding2, unregisterProjectEmbedding2, getProjectEmbeddingSnapshot2, installProjectEmbeddingSearchBridge2, getProjectChunkEmbeddingModelId2, getProjectEmbeddingMaxInputTokens2, embedTextForProject2, embedBatchForProject2, embedItemsForProject2, embedUnembeddedMemoriesForProject2, drainCommitBacklogForProject2, embedUnembeddedCompartmentChunksForProject2, embedSessionCompartmentChunks2, getEmbeddingCoverageStatus2, getAutoEmbeddingSessionCoverage2, sweepAllRegisteredProjects2, _setTestProviderFactoryForProject2, _resetProjectEmbeddingRegistryForTests2 };