@cortexkit/opencode-magic-context 0.20.0 → 0.21.1

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 (80) hide show
  1. package/dist/config/index.d.ts +18 -0
  2. package/dist/config/index.d.ts.map +1 -1
  3. package/dist/features/magic-context/git-commits/indexer.d.ts +2 -3
  4. package/dist/features/magic-context/git-commits/indexer.d.ts.map +1 -1
  5. package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts +1 -0
  6. package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts.map +1 -1
  7. package/dist/features/magic-context/memory/embedding-backfill.d.ts +1 -0
  8. package/dist/features/magic-context/memory/embedding-backfill.d.ts.map +1 -1
  9. package/dist/features/magic-context/memory/embedding-local.d.ts +6 -0
  10. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  11. package/dist/features/magic-context/memory/embedding.d.ts +2 -0
  12. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  13. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +1 -0
  14. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
  15. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  16. package/dist/features/magic-context/project-embedding-registry.d.ts +44 -0
  17. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -0
  18. package/dist/features/magic-context/search.d.ts.map +1 -1
  19. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  20. package/dist/features/magic-context/storage-meta-persisted.d.ts +48 -0
  21. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  22. package/dist/features/magic-context/storage-meta-shared.d.ts +2 -0
  23. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  24. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  25. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  26. package/dist/features/magic-context/storage.d.ts +1 -1
  27. package/dist/features/magic-context/storage.d.ts.map +1 -1
  28. package/dist/features/magic-context/types.d.ts +2 -0
  29. package/dist/features/magic-context/types.d.ts.map +1 -1
  30. package/dist/hooks/magic-context/auto-search-runner.d.ts +6 -4
  31. package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
  32. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  33. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  34. package/dist/hooks/magic-context/compartment-runner-types.d.ts +1 -0
  35. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  36. package/dist/hooks/magic-context/event-handler.d.ts +3 -0
  37. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  39. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/note-nudger.d.ts +7 -1
  41. package/dist/hooks/magic-context/note-nudger.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  43. package/dist/hooks/magic-context/text-complete.d.ts.map +1 -1
  44. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +1 -0
  45. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  46. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +2 -3
  47. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/transform.d.ts +3 -3
  49. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +1343 -550
  52. package/dist/plugin/dream-timer.d.ts +5 -4
  53. package/dist/plugin/dream-timer.d.ts.map +1 -1
  54. package/dist/plugin/embedding-bootstrap-helpers.d.ts +35 -0
  55. package/dist/plugin/embedding-bootstrap-helpers.d.ts.map +1 -0
  56. package/dist/plugin/embedding-bootstrap.d.ts +3 -0
  57. package/dist/plugin/embedding-bootstrap.d.ts.map +1 -0
  58. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  59. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  60. package/dist/plugin/tool-registry.d.ts.map +1 -1
  61. package/dist/shared/models-dev-cache.d.ts +8 -7
  62. package/dist/shared/models-dev-cache.d.ts.map +1 -1
  63. package/dist/shared/rpc-types.d.ts +1 -0
  64. package/dist/shared/rpc-types.d.ts.map +1 -1
  65. package/dist/shared/subagent-runner.d.ts +2 -1
  66. package/dist/shared/subagent-runner.d.ts.map +1 -1
  67. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  68. package/dist/tools/ctx-memory/types.d.ts +3 -2
  69. package/dist/tools/ctx-memory/types.d.ts.map +1 -1
  70. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  71. package/dist/tools/ctx-search/types.d.ts +3 -2
  72. package/dist/tools/ctx-search/types.d.ts.map +1 -1
  73. package/dist/tui/data/context-db.d.ts.map +1 -1
  74. package/package.json +2 -2
  75. package/src/shared/models-dev-cache.test.ts +5 -10
  76. package/src/shared/models-dev-cache.ts +15 -45
  77. package/src/shared/rpc-types.ts +1 -0
  78. package/src/shared/subagent-runner.ts +2 -0
  79. package/src/tui/data/context-db.ts +1 -0
  80. package/src/tui/slots/sidebar-content.tsx +15 -1
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { createRequire } from "node:module";
1
2
  var __create = Object.create;
2
3
  var __getProtoOf = Object.getPrototypeOf;
3
4
  var __defProp = Object.defineProperty;
@@ -44,6 +45,7 @@ var __export = (target, all) => {
44
45
  };
45
46
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
46
47
  var __promiseAll = (args) => Promise.all(args);
48
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
47
49
 
48
50
  // src/agents/dreamer.ts
49
51
  var DREAMER_AGENT = "dreamer";
@@ -15017,6 +15019,46 @@ var init_magic_context = __esm(() => {
15017
15019
  });
15018
15020
  });
15019
15021
 
15022
+ // src/features/magic-context/memory/project-identity.ts
15023
+ import { execSync } from "node:child_process";
15024
+ import { createHash } from "node:crypto";
15025
+ import path from "node:path";
15026
+ function getRootCommitHash(directory) {
15027
+ try {
15028
+ const hash2 = execSync("git rev-list --max-parents=0 HEAD", {
15029
+ cwd: directory,
15030
+ encoding: "utf-8",
15031
+ stdio: ["pipe", "pipe", "pipe"],
15032
+ timeout: GIT_TIMEOUT_MS
15033
+ }).trim();
15034
+ const firstLine = hash2.split(`
15035
+ `)[0]?.trim();
15036
+ return firstLine && firstLine.length >= 7 ? firstLine : undefined;
15037
+ } catch {
15038
+ return;
15039
+ }
15040
+ }
15041
+ function directoryFallback(directory) {
15042
+ const canonical = path.resolve(directory);
15043
+ const hash2 = createHash("md5").update(canonical).digest("hex").slice(0, 12);
15044
+ return `dir:${hash2}`;
15045
+ }
15046
+ function resolveProjectIdentity(directory) {
15047
+ const resolved = path.resolve(directory);
15048
+ const cached2 = resolvedCache.get(resolved);
15049
+ if (cached2 !== undefined) {
15050
+ return cached2;
15051
+ }
15052
+ const rootHash = getRootCommitHash(resolved);
15053
+ const identity = rootHash ? `git:${rootHash}` : directoryFallback(resolved);
15054
+ resolvedCache.set(resolved, identity);
15055
+ return identity;
15056
+ }
15057
+ var GIT_TIMEOUT_MS = 5000, resolvedCache;
15058
+ var init_project_identity = __esm(() => {
15059
+ resolvedCache = new Map;
15060
+ });
15061
+
15020
15062
  // src/shared/harness.ts
15021
15063
  function getHarness() {
15022
15064
  return currentHarness;
@@ -15025,41 +15067,41 @@ var currentHarness = "opencode";
15025
15067
 
15026
15068
  // src/shared/data-path.ts
15027
15069
  import * as os from "node:os";
15028
- import * as path from "node:path";
15070
+ import * as path2 from "node:path";
15029
15071
  function getDataDir() {
15030
- return process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share");
15072
+ return process.env.XDG_DATA_HOME ?? path2.join(os.homedir(), ".local", "share");
15031
15073
  }
15032
15074
  function getMagicContextTempDir(harness = getHarness()) {
15033
- return path.join(os.tmpdir(), harness, "magic-context");
15075
+ return path2.join(os.tmpdir(), harness, "magic-context");
15034
15076
  }
15035
15077
  function getMagicContextLogPath(harness = getHarness()) {
15036
- return path.join(getMagicContextTempDir(harness), "magic-context.log");
15078
+ return path2.join(getMagicContextTempDir(harness), "magic-context.log");
15037
15079
  }
15038
15080
  function getProjectMagicContextDir(directory) {
15039
- return path.join(directory, ".opencode", "magic-context");
15081
+ return path2.join(directory, ".opencode", "magic-context");
15040
15082
  }
15041
15083
  function getProjectMagicContextHistorianDir(directory) {
15042
- return path.join(getProjectMagicContextDir(directory), "historian");
15084
+ return path2.join(getProjectMagicContextDir(directory), "historian");
15043
15085
  }
15044
15086
  function getOpenCodeStorageDir() {
15045
- return path.join(getDataDir(), "opencode", "storage");
15087
+ return path2.join(getDataDir(), "opencode", "storage");
15046
15088
  }
15047
15089
  function getMagicContextStorageDir() {
15048
- return path.join(getDataDir(), "cortexkit", "magic-context");
15090
+ return path2.join(getDataDir(), "cortexkit", "magic-context");
15049
15091
  }
15050
15092
  function getLegacyOpenCodeMagicContextStorageDir() {
15051
- return path.join(getOpenCodeStorageDir(), "plugin", "magic-context");
15093
+ return path2.join(getOpenCodeStorageDir(), "plugin", "magic-context");
15052
15094
  }
15053
15095
  function getCacheDir() {
15054
- return process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), ".cache");
15096
+ return process.env.XDG_CACHE_HOME ?? path2.join(os.homedir(), ".cache");
15055
15097
  }
15056
15098
  var init_data_path = () => {};
15057
15099
 
15058
15100
  // src/shared/logger.ts
15059
15101
  import * as fs from "node:fs";
15060
- import * as path2 from "node:path";
15102
+ import * as path3 from "node:path";
15061
15103
  function ensureDir(filePath) {
15062
- const dir = path2.dirname(filePath);
15104
+ const dir = path3.dirname(filePath);
15063
15105
  if (dir === lastEnsuredDir)
15064
15106
  return;
15065
15107
  try {
@@ -148209,13 +148251,13 @@ function stableStringify(value, seen = new WeakSet) {
148209
148251
  }
148210
148252
 
148211
148253
  // src/features/magic-context/tool-definition-tokens.ts
148212
- import { createHash } from "node:crypto";
148254
+ import { createHash as createHash2 } from "node:crypto";
148213
148255
  function keyFor(providerID, modelID, agentName) {
148214
148256
  const agent = agentName && agentName.length > 0 ? agentName : "default";
148215
148257
  return `${providerID}/${modelID}/${agent}`;
148216
148258
  }
148217
148259
  function fingerprintFor(description, parameters) {
148218
- return createHash("sha256").update(description).update("\x00").update(stableStringify(parameters)).digest("hex");
148260
+ return createHash2("sha256").update(description).update("\x00").update(stableStringify(parameters)).digest("hex");
148219
148261
  }
148220
148262
  function setDatabase(db) {
148221
148263
  persistenceDb = db;
@@ -157333,8 +157375,8 @@ async function parseTarGzip(data, opts = {}) {
157333
157375
  const decompressedData = await new Response(stream).arrayBuffer();
157334
157376
  return parseTar(decompressedData, opts);
157335
157377
  }
157336
- function _sanitizePath(path3) {
157337
- let normalized = path3.replace(/\\/g, "/");
157378
+ function _sanitizePath(path4) {
157379
+ let normalized = path4.replace(/\\/g, "/");
157338
157380
  normalized = normalized.replace(/^[a-zA-Z]:\//, "");
157339
157381
  normalized = normalized.replace(/^\/+/, "");
157340
157382
  const hasLeadingDotSlash = normalized.startsWith("./");
@@ -157351,7 +157393,7 @@ function _sanitizePath(path3) {
157351
157393
  if (hasLeadingDotSlash && !result.startsWith("./")) {
157352
157394
  result = "./" + result;
157353
157395
  }
157354
- if (path3.endsWith("/") && !result.endsWith("/")) {
157396
+ if (path4.endsWith("/") && !result.endsWith("/")) {
157355
157397
  result += "/";
157356
157398
  }
157357
157399
  return result;
@@ -157415,8 +157457,8 @@ __export(exports_native_binding, {
157415
157457
  resolveBetterSqliteNativeBinding: () => resolveBetterSqliteNativeBinding
157416
157458
  });
157417
157459
  import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "node:fs";
157418
- import { createRequire } from "node:module";
157419
- import * as path3 from "node:path";
157460
+ import { createRequire as createRequire2 } from "node:module";
157461
+ import * as path4 from "node:path";
157420
157462
  function logInfo(message) {
157421
157463
  log(`${PREFIX} ${message}`);
157422
157464
  }
@@ -157446,9 +157488,9 @@ function probeAbi(binaryPath) {
157446
157488
  function resolveBetterSqlite3OnDisk(requireFn) {
157447
157489
  try {
157448
157490
  const pkgJsonPath = requireFn.resolve("better-sqlite3/package.json");
157449
- const pkgDir = path3.dirname(pkgJsonPath);
157491
+ const pkgDir = path4.dirname(pkgJsonPath);
157450
157492
  const pkgJson = requireFn(pkgJsonPath);
157451
- const binaryPath = path3.join(pkgDir, "build", "Release", "better_sqlite3.node");
157493
+ const binaryPath = path4.join(pkgDir, "build", "Release", "better_sqlite3.node");
157452
157494
  return { binaryPath, pkgVersion: pkgJson.version };
157453
157495
  } catch (err) {
157454
157496
  logWarn(`could not resolve better-sqlite3 in node_modules: ${err instanceof Error ? err.message : String(err)}`);
@@ -157456,7 +157498,7 @@ function resolveBetterSqlite3OnDisk(requireFn) {
157456
157498
  }
157457
157499
  }
157458
157500
  function getCachedBinaryPath(pkgVersion, abi) {
157459
- return path3.join(getCacheDir(), "cortexkit", "native-bindings", "better-sqlite3", `v${pkgVersion}`, `electron-v${abi}-${process.platform}-${process.arch}`, "better_sqlite3.node");
157501
+ return path4.join(getCacheDir(), "cortexkit", "native-bindings", "better-sqlite3", `v${pkgVersion}`, `electron-v${abi}-${process.platform}-${process.arch}`, "better_sqlite3.node");
157460
157502
  }
157461
157503
  async function downloadElectronPrebuild(pkgVersion, abi) {
157462
157504
  const filename = `better-sqlite3-v${pkgVersion}-electron-v${abi}-${process.platform}-${process.arch}.tar.gz`;
@@ -157489,7 +157531,7 @@ async function resolveBetterSqliteNativeBinding() {
157489
157531
  const promise2 = (async () => {
157490
157532
  const expected = process.versions.modules;
157491
157533
  logInfo(`Electron detected (v${process.versions.electron}, NODE_MODULE_VERSION ${expected}); verifying better-sqlite3 binding`);
157492
- const requireFn = createRequire(import.meta.url);
157534
+ const requireFn = createRequire2(import.meta.url);
157493
157535
  const resolved = resolveBetterSqlite3OnDisk(requireFn);
157494
157536
  if (!resolved) {
157495
157537
  return null;
@@ -157514,7 +157556,7 @@ async function resolveBetterSqliteNativeBinding() {
157514
157556
  }
157515
157557
  logWarn(`cached binary at ${cachedPath} has wrong ABI (${cachedProbe.actual ?? "unknown"} != ${expected}); refetching`);
157516
157558
  }
157517
- mkdirSync3(path3.dirname(cachedPath), { recursive: true });
157559
+ mkdirSync3(path4.dirname(cachedPath), { recursive: true });
157518
157560
  const nodeFileBytes = await downloadElectronPrebuild(pkgVersion, expected);
157519
157561
  writeFileSync4(cachedPath, nodeFileBytes);
157520
157562
  logInfo(`cached Electron prebuild at ${cachedPath} (${(nodeFileBytes.length / 1024).toFixed(1)} KB)`);
@@ -157574,11 +157616,11 @@ function closeQuietly(db) {
157574
157616
  }
157575
157617
 
157576
157618
  // src/features/magic-context/key-files/project-key-files.ts
157577
- import { createHash as createHash2 } from "node:crypto";
157619
+ import { createHash as createHash3 } from "node:crypto";
157578
157620
  import { existsSync as existsSync10, readFileSync as readFileSync9, realpathSync } from "node:fs";
157579
157621
  import { join as join12, resolve as resolve4, sep } from "node:path";
157580
157622
  function sha256(input) {
157581
- return createHash2("sha256").update(input).digest("hex");
157623
+ return createHash3("sha256").update(input).digest("hex");
157582
157624
  }
157583
157625
  function resolveProjectPath(directory) {
157584
157626
  if (!directory?.trim())
@@ -157721,14 +157763,6 @@ function getLoadAllEmbeddingsStatement(db) {
157721
157763
  }
157722
157764
  return stmt;
157723
157765
  }
157724
- function getStoredModelIdStatement(db) {
157725
- let stmt = getStoredModelIdStatements.get(db);
157726
- if (!stmt) {
157727
- stmt = db.prepare("SELECT memory_embeddings.model_id AS modelId FROM memory_embeddings INNER JOIN memories ON memories.id = memory_embeddings.memory_id WHERE memories.project_path = ? AND memory_embeddings.model_id IS NOT NULL LIMIT 1");
157728
- getStoredModelIdStatements.set(db, stmt);
157729
- }
157730
- return stmt;
157731
- }
157732
157766
  function getClearAllEmbeddingsStatement(db) {
157733
157767
  let stmt = clearAllEmbeddingsStatements.get(db);
157734
157768
  if (!stmt) {
@@ -157737,6 +157771,14 @@ function getClearAllEmbeddingsStatement(db) {
157737
157771
  }
157738
157772
  return stmt;
157739
157773
  }
157774
+ function getDistinctStoredModelIdsStatement(db) {
157775
+ let stmt = getDistinctStoredModelIdsStatements.get(db);
157776
+ if (!stmt) {
157777
+ stmt = db.prepare("SELECT DISTINCT memory_embeddings.model_id AS modelId FROM memory_embeddings INNER JOIN memories ON memories.id = memory_embeddings.memory_id WHERE memories.project_path = ?");
157778
+ getDistinctStoredModelIdsStatements.set(db, stmt);
157779
+ }
157780
+ return stmt;
157781
+ }
157740
157782
  function saveEmbedding(db, memoryId, embedding, modelId) {
157741
157783
  const blob = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
157742
157784
  getSaveEmbeddingStatement(db).run(memoryId, blob, modelId);
@@ -157749,20 +157791,21 @@ function loadAllEmbeddings(db, projectPath) {
157749
157791
  }
157750
157792
  return embeddings;
157751
157793
  }
157752
- function getStoredModelId(db, projectPath) {
157753
- const row = getStoredModelIdStatement(db).get(projectPath);
157754
- return typeof row?.modelId === "string" ? row.modelId : null;
157755
- }
157756
157794
  function clearEmbeddingsForProject(db, projectPath) {
157757
157795
  getClearAllEmbeddingsStatement(db).run(projectPath);
157758
157796
  }
157759
- var saveEmbeddingStatements, loadAllEmbeddingsStatements, deleteEmbeddingStatements, getStoredModelIdStatements, clearAllEmbeddingsStatements;
157797
+ function getDistinctStoredModelIds(db, projectPath) {
157798
+ const rows = getDistinctStoredModelIdsStatement(db).all(projectPath);
157799
+ return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
157800
+ }
157801
+ var saveEmbeddingStatements, loadAllEmbeddingsStatements, deleteEmbeddingStatements, getStoredModelIdStatements, clearAllEmbeddingsStatements, getDistinctStoredModelIdsStatements;
157760
157802
  var init_storage_memory_embeddings = __esm(() => {
157761
157803
  saveEmbeddingStatements = new WeakMap;
157762
157804
  loadAllEmbeddingsStatements = new WeakMap;
157763
157805
  deleteEmbeddingStatements = new WeakMap;
157764
157806
  getStoredModelIdStatements = new WeakMap;
157765
157807
  clearAllEmbeddingsStatements = new WeakMap;
157808
+ getDistinctStoredModelIdsStatements = new WeakMap;
157766
157809
  });
157767
157810
 
157768
157811
  // src/features/magic-context/memory/embedding-cache.ts
@@ -157807,13 +157850,13 @@ var init_embedding_cache = __esm(() => {
157807
157850
  });
157808
157851
 
157809
157852
  // src/features/magic-context/memory/normalize-hash.ts
157810
- import { createHash as createHash3 } from "node:crypto";
157853
+ import { createHash as createHash4 } from "node:crypto";
157811
157854
  function normalizeMemoryContent(content) {
157812
157855
  return content.toLowerCase().replace(/\s+/g, " ").trim();
157813
157856
  }
157814
157857
  function computeNormalizedHash(content) {
157815
157858
  const normalized = normalizeMemoryContent(content);
157816
- return createHash3("md5").update(normalized).digest("hex");
157859
+ return createHash4("md5").update(normalized).digest("hex");
157817
157860
  }
157818
157861
  var init_normalize_hash = () => {};
157819
157862
 
@@ -158444,7 +158487,8 @@ var init_embedding_identity = __esm(() => {
158444
158487
  // src/features/magic-context/memory/embedding-local.ts
158445
158488
  import { mkdirSync as mkdirSync4 } from "node:fs";
158446
158489
  import { open, stat, unlink, writeFile } from "node:fs/promises";
158447
- import { join as join15 } from "node:path";
158490
+ import { dirname as dirname7, join as join15 } from "node:path";
158491
+ import { pathToFileURL } from "node:url";
158448
158492
  async function acquireModelLoadLock(lockPath) {
158449
158493
  const waitStart = Date.now();
158450
158494
  while (true) {
@@ -158494,6 +158538,33 @@ function startLockHeartbeat(lockPath) {
158494
158538
  timer.unref?.();
158495
158539
  return () => clearInterval(timer);
158496
158540
  }
158541
+ async function injectWasmOrtForElectron() {
158542
+ if (typeof process === "undefined" || !process.versions?.electron) {
158543
+ return false;
158544
+ }
158545
+ try {
158546
+ const ortWebSpec = `onnxruntime-${"web"}`;
158547
+ const ortWeb = await import(ortWebSpec);
158548
+ try {
158549
+ const { createRequire: createRequireFn } = await import("node:module");
158550
+ const requireFn = createRequireFn(import.meta.url);
158551
+ const pkgPath = requireFn.resolve("onnxruntime-web/package.json");
158552
+ const distDir = join15(dirname7(pkgPath), "dist");
158553
+ const wasmPathsPrefix = `${pathToFileURL(distDir).href}/`;
158554
+ if (ortWeb.env?.wasm) {
158555
+ ortWeb.env.wasm.wasmPaths = wasmPathsPrefix;
158556
+ }
158557
+ } catch (pathError) {
158558
+ log("[magic-context] could not resolve local onnxruntime-web/dist, falling back to default WASM paths:", pathError instanceof Error ? pathError.message : String(pathError));
158559
+ }
158560
+ globalThis[Symbol.for("onnxruntime")] = ortWeb;
158561
+ log("[magic-context] Electron detected — using onnxruntime-web (WASM) for embeddings (bypasses onnxruntime-node native load)");
158562
+ return true;
158563
+ } catch (error51) {
158564
+ log("[magic-context] failed to inject onnxruntime-web for Electron — letting transformers fall back to native:", error51 instanceof Error ? error51.message : String(error51));
158565
+ return false;
158566
+ }
158567
+ }
158497
158568
  async function withQuietConsole(fn) {
158498
158569
  const origWarn = console.warn;
158499
158570
  const origError = console.error;
@@ -158557,11 +158628,18 @@ class LocalEmbeddingProvider {
158557
158628
  model;
158558
158629
  pipeline = null;
158559
158630
  initPromise = null;
158631
+ inFlight = 0;
158632
+ disposing = false;
158633
+ disposePromise = null;
158634
+ inFlightWaiters = [];
158560
158635
  constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL) {
158561
158636
  this.model = model;
158562
158637
  this.modelId = getEmbeddingProviderIdentity({ provider: "local", model });
158563
158638
  }
158564
158639
  async initialize() {
158640
+ if (this.disposing) {
158641
+ return false;
158642
+ }
158565
158643
  if (this.pipeline) {
158566
158644
  return true;
158567
158645
  }
@@ -158571,7 +158649,11 @@ class LocalEmbeddingProvider {
158571
158649
  }
158572
158650
  this.initPromise = (async () => {
158573
158651
  try {
158574
- const transformersSpec = `@huggingface/${"transformers"}`;
158652
+ if (this.disposing) {
158653
+ return;
158654
+ }
158655
+ await injectWasmOrtForElectron();
158656
+ const transformersSpec = "@huggingface/transformers";
158575
158657
  const transformersModule = await import(transformersSpec);
158576
158658
  const env = transformersModule.env;
158577
158659
  const LogLevel = transformersModule.LogLevel;
@@ -158594,9 +158676,15 @@ class LocalEmbeddingProvider {
158594
158676
  let lastError;
158595
158677
  for (let attempt = 1;attempt <= MAX_ATTEMPTS; attempt++) {
158596
158678
  try {
158597
- this.pipeline = await withQuietConsole(() => createPipeline("feature-extraction", this.model, {
158679
+ const pipeline = await withQuietConsole(() => createPipeline("feature-extraction", this.model, {
158598
158680
  dtype: "fp32"
158599
158681
  }));
158682
+ if (this.disposing) {
158683
+ await pipeline.dispose?.();
158684
+ this.pipeline = null;
158685
+ } else {
158686
+ this.pipeline = pipeline;
158687
+ }
158600
158688
  lastError = undefined;
158601
158689
  break;
158602
158690
  } catch (error51) {
@@ -158611,6 +158699,8 @@ class LocalEmbeddingProvider {
158611
158699
  }
158612
158700
  if (this.pipeline) {
158613
158701
  log(`[magic-context] embedding model loaded: ${this.model}`);
158702
+ } else if (this.disposing) {
158703
+ return;
158614
158704
  } else {
158615
158705
  throw lastError ?? new Error("unknown embedding load failure");
158616
158706
  }
@@ -158628,13 +158718,33 @@ class LocalEmbeddingProvider {
158628
158718
  await this.initPromise;
158629
158719
  return this.pipeline !== null;
158630
158720
  }
158721
+ waitForInFlightToDrain() {
158722
+ if (this.inFlight === 0) {
158723
+ return Promise.resolve();
158724
+ }
158725
+ return new Promise((resolve6) => {
158726
+ this.inFlightWaiters.push(resolve6);
158727
+ });
158728
+ }
158729
+ finishInFlight() {
158730
+ this.inFlight = Math.max(0, this.inFlight - 1);
158731
+ if (this.inFlight !== 0)
158732
+ return;
158733
+ const waiters = this.inFlightWaiters.splice(0);
158734
+ for (const waiter of waiters) {
158735
+ waiter();
158736
+ }
158737
+ }
158631
158738
  async embed(text, signal) {
158632
158739
  if (signal?.aborted)
158633
158740
  return null;
158634
- if (!await this.initialize()) {
158741
+ if (this.disposing)
158635
158742
  return null;
158636
- }
158743
+ this.inFlight += 1;
158637
158744
  try {
158745
+ if (!await this.initialize()) {
158746
+ return null;
158747
+ }
158638
158748
  const pipeline = this.pipeline;
158639
158749
  if (!pipeline) {
158640
158750
  return null;
@@ -158647,6 +158757,8 @@ class LocalEmbeddingProvider {
158647
158757
  } catch (error51) {
158648
158758
  log("[magic-context] embedding failed:", error51);
158649
158759
  return null;
158760
+ } finally {
158761
+ this.finishInFlight();
158650
158762
  }
158651
158763
  }
158652
158764
  async embedBatch(texts, signal) {
@@ -158656,10 +158768,14 @@ class LocalEmbeddingProvider {
158656
158768
  if (signal?.aborted) {
158657
158769
  return Array.from({ length: texts.length }, () => null);
158658
158770
  }
158659
- if (!await this.initialize()) {
158771
+ if (this.disposing) {
158660
158772
  return Array.from({ length: texts.length }, () => null);
158661
158773
  }
158774
+ this.inFlight += 1;
158662
158775
  try {
158776
+ if (!await this.initialize()) {
158777
+ return Array.from({ length: texts.length }, () => null);
158778
+ }
158663
158779
  const pipeline = this.pipeline;
158664
158780
  if (!pipeline) {
158665
158781
  return Array.from({ length: texts.length }, () => null);
@@ -158672,25 +158788,33 @@ class LocalEmbeddingProvider {
158672
158788
  } catch (error51) {
158673
158789
  log("[magic-context] embedding batch failed:", error51);
158674
158790
  return Array.from({ length: texts.length }, () => null);
158791
+ } finally {
158792
+ this.finishInFlight();
158675
158793
  }
158676
158794
  }
158677
158795
  async dispose() {
158678
- if (this.initPromise) {
158679
- await this.initPromise;
158680
- }
158681
- if (!this.pipeline) {
158682
- this.pipeline = null;
158683
- this.initPromise = null;
158684
- return;
158796
+ if (this.disposePromise) {
158797
+ return this.disposePromise;
158685
158798
  }
158686
- try {
158687
- await this.pipeline.dispose?.();
158688
- } catch (error51) {
158689
- log("[magic-context] embedding model dispose failed:", error51);
158690
- } finally {
158799
+ this.disposing = true;
158800
+ this.disposePromise = (async () => {
158801
+ if (this.initPromise) {
158802
+ await this.initPromise;
158803
+ }
158804
+ await this.waitForInFlightToDrain();
158805
+ const pipelineToDispose = this.pipeline;
158691
158806
  this.pipeline = null;
158692
158807
  this.initPromise = null;
158693
- }
158808
+ if (!pipelineToDispose) {
158809
+ return;
158810
+ }
158811
+ try {
158812
+ await pipelineToDispose.dispose?.();
158813
+ } catch (error51) {
158814
+ log("[magic-context] embedding model dispose failed:", error51);
158815
+ }
158816
+ })();
158817
+ return this.disposePromise;
158694
158818
  }
158695
158819
  isLoaded() {
158696
158820
  return this.pipeline !== null;
@@ -158916,22 +159040,112 @@ var init_embedding_openai = __esm(() => {
158916
159040
  OPEN_DURATION_MS = 5 * 60000;
158917
159041
  });
158918
159042
 
158919
- // src/features/magic-context/memory/embedding.ts
158920
- function isUnembeddedMemoryRow(row) {
158921
- if (row === null || typeof row !== "object") {
158922
- return false;
159043
+ // src/features/magic-context/git-commits/storage-git-commit-embeddings.ts
159044
+ function getSaveStatement(db) {
159045
+ let stmt = saveStatements.get(db);
159046
+ if (!stmt) {
159047
+ stmt = db.prepare(`INSERT INTO git_commit_embeddings (sha, embedding, model_id, created_at)
159048
+ VALUES (?, ?, ?, ?)
159049
+ ON CONFLICT(sha) DO UPDATE SET
159050
+ embedding = excluded.embedding,
159051
+ model_id = excluded.model_id,
159052
+ created_at = excluded.created_at`);
159053
+ saveStatements.set(db, stmt);
158923
159054
  }
158924
- const candidate = row;
158925
- return typeof candidate.id === "number" && typeof candidate.content === "string";
159055
+ return stmt;
158926
159056
  }
158927
- function getLoadUnembeddedMemoriesStatement(db) {
158928
- let stmt = loadUnembeddedMemoriesStatements.get(db);
159057
+ function getLoadProjectStatement(db) {
159058
+ let stmt = loadProjectStatements.get(db);
158929
159059
  if (!stmt) {
158930
- stmt = db.prepare("SELECT m.id AS id, m.content AS content FROM memories m LEFT JOIN memory_embeddings me ON m.id = me.memory_id WHERE m.project_path = ? AND m.status = 'active' AND me.memory_id IS NULL LIMIT ?");
158931
- loadUnembeddedMemoriesStatements.set(db, stmt);
159060
+ stmt = db.prepare(`SELECT e.sha AS sha, e.embedding AS embedding, e.model_id AS model_id
159061
+ FROM git_commit_embeddings e
159062
+ JOIN git_commits c ON c.sha = e.sha
159063
+ WHERE c.project_path = ?`);
159064
+ loadProjectStatements.set(db, stmt);
159065
+ }
159066
+ return stmt;
159067
+ }
159068
+ function getLoadUnembeddedStatement(db) {
159069
+ let stmt = loadUnembeddedStatements.get(db);
159070
+ if (!stmt) {
159071
+ stmt = db.prepare(`SELECT c.sha AS sha, c.message AS message
159072
+ FROM git_commits c
159073
+ LEFT JOIN git_commit_embeddings e ON c.sha = e.sha
159074
+ WHERE c.project_path = ? AND e.sha IS NULL
159075
+ ORDER BY c.committed_at DESC
159076
+ LIMIT ?`);
159077
+ loadUnembeddedStatements.set(db, stmt);
159078
+ }
159079
+ return stmt;
159080
+ }
159081
+ function getCountEmbeddedStatement(db) {
159082
+ let stmt = countEmbeddedStatements.get(db);
159083
+ if (!stmt) {
159084
+ stmt = db.prepare(`SELECT COUNT(*) AS count FROM git_commit_embeddings e
159085
+ JOIN git_commits c ON c.sha = e.sha WHERE c.project_path = ?`);
159086
+ countEmbeddedStatements.set(db, stmt);
159087
+ }
159088
+ return stmt;
159089
+ }
159090
+ function getClearProjectStatement(db) {
159091
+ let stmt = clearProjectStatements.get(db);
159092
+ if (!stmt) {
159093
+ stmt = db.prepare(`DELETE FROM git_commit_embeddings
159094
+ WHERE sha IN (SELECT sha FROM git_commits WHERE project_path = ?)`);
159095
+ clearProjectStatements.set(db, stmt);
158932
159096
  }
158933
159097
  return stmt;
158934
159098
  }
159099
+ function getDistinctModelIdStatement(db) {
159100
+ let stmt = distinctModelIdStatements.get(db);
159101
+ if (!stmt) {
159102
+ stmt = db.prepare(`SELECT DISTINCT e.model_id AS modelId
159103
+ FROM git_commit_embeddings e
159104
+ JOIN git_commits c ON c.sha = e.sha
159105
+ WHERE c.project_path = ?`);
159106
+ distinctModelIdStatements.set(db, stmt);
159107
+ }
159108
+ return stmt;
159109
+ }
159110
+ function saveCommitEmbedding(db, sha, embedding, modelId) {
159111
+ const bytes = new Uint8Array(embedding.buffer, embedding.byteOffset, embedding.byteLength);
159112
+ getSaveStatement(db).run(sha, bytes, modelId, Date.now());
159113
+ }
159114
+ function loadProjectCommitEmbeddings(db, projectPath) {
159115
+ const rows = getLoadProjectStatement(db).all(projectPath);
159116
+ const map2 = new Map;
159117
+ for (const row of rows) {
159118
+ const buffer2 = row.embedding.buffer.slice(row.embedding.byteOffset, row.embedding.byteOffset + row.embedding.byteLength);
159119
+ map2.set(row.sha, new Float32Array(buffer2));
159120
+ }
159121
+ return map2;
159122
+ }
159123
+ function loadUnembeddedCommits(db, projectPath, limit) {
159124
+ return getLoadUnembeddedStatement(db).all(projectPath, limit);
159125
+ }
159126
+ function countEmbeddedCommits(db, projectPath) {
159127
+ const row = getCountEmbeddedStatement(db).get(projectPath);
159128
+ return row?.count ?? 0;
159129
+ }
159130
+ function clearProjectCommitEmbeddings(db, projectPath) {
159131
+ return getClearProjectStatement(db).run(projectPath).changes;
159132
+ }
159133
+ function getDistinctCommitEmbeddingModelIds(db, projectPath) {
159134
+ const rows = getDistinctModelIdStatement(db).all(projectPath);
159135
+ return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
159136
+ }
159137
+ var saveStatements, loadProjectStatements, loadUnembeddedStatements, countEmbeddedStatements, clearProjectStatements, distinctModelIdStatements;
159138
+ var init_storage_git_commit_embeddings = __esm(() => {
159139
+ saveStatements = new WeakMap;
159140
+ loadProjectStatements = new WeakMap;
159141
+ loadUnembeddedStatements = new WeakMap;
159142
+ countEmbeddedStatements = new WeakMap;
159143
+ clearProjectStatements = new WeakMap;
159144
+ distinctModelIdStatements = new WeakMap;
159145
+ });
159146
+
159147
+ // src/features/magic-context/project-embedding-registry.ts
159148
+ import { createHash as createHash5 } from "node:crypto";
158935
159149
  function resolveEmbeddingConfig(config2) {
158936
159150
  if (!config2 || config2.provider === "local") {
158937
159151
  return {
@@ -158950,10 +159164,10 @@ function resolveEmbeddingConfig(config2) {
158950
159164
  }
158951
159165
  return { provider: "off" };
158952
159166
  }
158953
- function resolveProviderIdentity(config2) {
158954
- return getEmbeddingProviderIdentity(config2);
158955
- }
158956
159167
  function createProvider(config2) {
159168
+ if (testProviderFactory) {
159169
+ return testProviderFactory(config2);
159170
+ }
158957
159171
  if (config2.provider === "off") {
158958
159172
  return null;
158959
159173
  }
@@ -158966,129 +159180,221 @@ function createProvider(config2) {
158966
159180
  }
158967
159181
  return new LocalEmbeddingProvider(config2.model);
158968
159182
  }
158969
- function getOrCreateProvider() {
158970
- if (provider) {
158971
- return provider;
159183
+ function stableStringify2(value) {
159184
+ if (Array.isArray(value)) {
159185
+ return `[${value.map((entry) => stableStringify2(entry)).join(",")}]`;
158972
159186
  }
158973
- provider = createProvider(embeddingConfig);
158974
- return provider;
159187
+ if (value && typeof value === "object") {
159188
+ const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
159189
+ return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify2(entry)}`).join(",")}}`;
159190
+ }
159191
+ return JSON.stringify(value);
159192
+ }
159193
+ function sha256Prefix(value, length = 16) {
159194
+ return createHash5("sha256").update(value).digest("hex").slice(0, length);
158975
159195
  }
158976
- function initializeEmbedding(config2) {
158977
- const nextConfig = resolveEmbeddingConfig(config2);
158978
- const nextProviderIdentity = resolveProviderIdentity(nextConfig);
158979
- const previousProvider = provider;
158980
- const previousProviderIdentity = previousProvider?.modelId ?? resolveProviderIdentity(embeddingConfig);
158981
- if (previousProviderIdentity === nextProviderIdentity) {
158982
- embeddingConfig = nextConfig;
159196
+ function getRuntimeFingerprint(config2) {
159197
+ if (config2.provider === "off") {
159198
+ return OFF_PROVIDER_IDENTITY;
159199
+ }
159200
+ return `${getEmbeddingProviderIdentity(config2)}:${sha256Prefix(stableStringify2(config2))}`;
159201
+ }
159202
+ function sameFeatures(a, b) {
159203
+ return a.memoryEnabled === b.memoryEnabled && a.gitCommitEnabled === b.gitCommitEnabled;
159204
+ }
159205
+ function snapshotFor(registration) {
159206
+ const providerIsOn = registration.providerIdentity !== OFF_PROVIDER_IDENTITY;
159207
+ const enabled = !registration.observationMode && providerIsOn && registration.features.memoryEnabled;
159208
+ const gitCommitEnabled = !registration.observationMode && providerIsOn && registration.features.gitCommitEnabled;
159209
+ return {
159210
+ projectIdentity: registration.projectIdentity,
159211
+ sourceDirectory: registration.sourceDirectory,
159212
+ providerIdentity: registration.providerIdentity,
159213
+ runtimeFingerprint: registration.runtimeFingerprint,
159214
+ generation: registration.generation,
159215
+ features: { ...registration.features },
159216
+ enabled,
159217
+ gitCommitEnabled,
159218
+ modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId
159219
+ };
159220
+ }
159221
+ function disposeProvider(provider) {
159222
+ if (!provider)
158983
159223
  return;
159224
+ provider.dispose().catch((error51) => {
159225
+ log("[magic-context] embedding provider dispose failed:", error51);
159226
+ });
159227
+ }
159228
+ function anyStoredModelIdIsStale(storedIds, currentId) {
159229
+ if (storedIds.size === 0)
159230
+ return false;
159231
+ for (const id of storedIds) {
159232
+ if (id === null || id !== currentId) {
159233
+ return true;
159234
+ }
158984
159235
  }
158985
- embeddingConfig = nextConfig;
158986
- provider = null;
158987
- if (previousProvider) {
158988
- previousProvider.dispose().catch((error51) => {
158989
- log("[magic-context] embedding provider dispose failed:", error51);
158990
- });
159236
+ return false;
159237
+ }
159238
+ function maybeWipeStaleEmbeddings(db, projectIdentity, currentProviderIdentity, features) {
159239
+ if (currentProviderIdentity === OFF_PROVIDER_IDENTITY) {
159240
+ return false;
158991
159241
  }
159242
+ let wiped = false;
159243
+ db.transaction(() => {
159244
+ if (features.memoryEnabled) {
159245
+ const memoryIds = getDistinctStoredModelIds(db, projectIdentity);
159246
+ if (anyStoredModelIdIsStale(memoryIds, currentProviderIdentity)) {
159247
+ clearEmbeddingsForProject(db, projectIdentity);
159248
+ invalidateProject(projectIdentity);
159249
+ wiped = true;
159250
+ }
159251
+ }
159252
+ if (features.gitCommitEnabled) {
159253
+ const commitIds = getDistinctCommitEmbeddingModelIds(db, projectIdentity);
159254
+ if (anyStoredModelIdIsStale(commitIds, currentProviderIdentity)) {
159255
+ clearProjectCommitEmbeddings(db, projectIdentity);
159256
+ wiped = true;
159257
+ }
159258
+ }
159259
+ })();
159260
+ return wiped;
158992
159261
  }
158993
- function isEmbeddingEnabled() {
158994
- return embeddingConfig.provider !== "off";
159262
+ function registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, config2, features, sourceDirectory) {
159263
+ const resolvedConfig = resolveEmbeddingConfig(config2);
159264
+ const providerIdentity = getEmbeddingProviderIdentity(resolvedConfig);
159265
+ const runtimeFingerprint = getRuntimeFingerprint(resolvedConfig);
159266
+ const prior = projectRegistrations.get(projectIdentity);
159267
+ const canReuseProvider = prior !== undefined && !prior.observationMode && prior.runtimeFingerprint === runtimeFingerprint && prior.providerIdentity === providerIdentity;
159268
+ const wiped = maybeWipeStaleEmbeddings(db, projectIdentity, providerIdentity, features);
159269
+ const generationChanged = prior === undefined || prior.observationMode || prior.runtimeFingerprint !== runtimeFingerprint || !sameFeatures(prior.features, features) || wiped;
159270
+ const generation = generationChanged ? ++globalRegistrationGeneration : prior.generation;
159271
+ const registration = {
159272
+ projectIdentity,
159273
+ sourceDirectory,
159274
+ config: resolvedConfig,
159275
+ providerIdentity,
159276
+ runtimeFingerprint,
159277
+ provider: canReuseProvider ? prior.provider : null,
159278
+ generation,
159279
+ features: { ...features },
159280
+ modelId: providerIdentity === OFF_PROVIDER_IDENTITY ? "off" : providerIdentity,
159281
+ observationMode: false
159282
+ };
159283
+ projectRegistrations.set(projectIdentity, registration);
159284
+ if (!canReuseProvider) {
159285
+ disposeProvider(prior?.provider ?? null);
159286
+ }
159287
+ return snapshotFor(registration);
159288
+ }
159289
+ function registerProjectInObservationMode(db, projectIdentity, sourceDirectory, failedConfig, failureSummary) {
159290
+ const prior = projectRegistrations.get(projectIdentity);
159291
+ const runtimeFingerprint = `observation:${sha256Prefix(failureSummary)}`;
159292
+ const generation = prior?.runtimeFingerprint === runtimeFingerprint && prior.observationMode ? prior.generation : ++globalRegistrationGeneration;
159293
+ const registration = {
159294
+ projectIdentity,
159295
+ sourceDirectory,
159296
+ config: resolveEmbeddingConfig(failedConfig),
159297
+ providerIdentity: OFF_PROVIDER_IDENTITY,
159298
+ runtimeFingerprint,
159299
+ provider: null,
159300
+ generation,
159301
+ features: { memoryEnabled: false, gitCommitEnabled: false },
159302
+ modelId: "off",
159303
+ observationMode: true
159304
+ };
159305
+ projectRegistrations.set(projectIdentity, registration);
159306
+ disposeProvider(prior?.provider ?? null);
159307
+ return snapshotFor(registration);
158995
159308
  }
158996
- async function embedText(text, signal) {
158997
- const currentProvider = getOrCreateProvider();
158998
- if (!currentProvider) {
159309
+ function getProjectEmbeddingSnapshot(projectIdentity) {
159310
+ const registration = projectRegistrations.get(projectIdentity);
159311
+ return registration ? snapshotFor(registration) : null;
159312
+ }
159313
+ function getOrCreateProjectProvider(registration) {
159314
+ if (registration.providerIdentity === OFF_PROVIDER_IDENTITY || registration.observationMode) {
158999
159315
  return null;
159000
159316
  }
159001
- if (!await currentProvider.initialize()) {
159317
+ if (registration.provider) {
159318
+ return registration.provider;
159319
+ }
159320
+ const provider = createProvider(registration.config);
159321
+ registration.provider = provider;
159322
+ return provider;
159323
+ }
159324
+ async function embedTextForProject(projectIdentity, text, signal) {
159325
+ const registration = projectRegistrations.get(projectIdentity);
159326
+ if (!registration)
159327
+ return null;
159328
+ const generation = registration.generation;
159329
+ const modelId = registration.modelId;
159330
+ const provider = getOrCreateProjectProvider(registration);
159331
+ if (!provider)
159332
+ return null;
159333
+ const vector = await provider.embed(text, signal);
159334
+ if (!vector)
159335
+ return null;
159336
+ const current = projectRegistrations.get(projectIdentity);
159337
+ if (!current || current.generation !== generation || current.runtimeFingerprint !== registration.runtimeFingerprint) {
159002
159338
  return null;
159003
159339
  }
159004
- return currentProvider.embed(text, signal);
159340
+ return { vector, modelId, generation };
159005
159341
  }
159006
- async function embedBatch(texts, signal) {
159342
+ async function embedBatchForProject(projectIdentity, texts, signal) {
159007
159343
  if (texts.length === 0) {
159008
- return [];
159009
- }
159010
- const currentProvider = getOrCreateProvider();
159011
- if (!currentProvider) {
159012
- return Array.from({ length: texts.length }, () => null);
159344
+ const registration2 = projectRegistrations.get(projectIdentity);
159345
+ if (!registration2 || registration2.observationMode)
159346
+ return null;
159347
+ return { vectors: [], modelId: registration2.modelId, generation: registration2.generation };
159013
159348
  }
159014
- if (!await currentProvider.initialize()) {
159015
- return Array.from({ length: texts.length }, () => null);
159349
+ const registration = projectRegistrations.get(projectIdentity);
159350
+ if (!registration)
159351
+ return null;
159352
+ const generation = registration.generation;
159353
+ const modelId = registration.modelId;
159354
+ const runtimeFingerprint = registration.runtimeFingerprint;
159355
+ const provider = getOrCreateProjectProvider(registration);
159356
+ if (!provider)
159357
+ return null;
159358
+ const vectors = await provider.embedBatch(texts, signal);
159359
+ const current = projectRegistrations.get(projectIdentity);
159360
+ if (!current || current.generation !== generation || current.runtimeFingerprint !== runtimeFingerprint) {
159361
+ return null;
159016
159362
  }
159017
- return currentProvider.embedBatch(texts, signal);
159363
+ return { vectors, modelId, generation };
159018
159364
  }
159019
- async function embedAllUnembeddedMemories(db, config2, batchSize = 10) {
159020
- if (sweepInProgress) {
159021
- log("[magic-context] embedding sweep already in progress, skipping this tick");
159022
- return 0;
159023
- }
159024
- sweepInProgress = true;
159025
- const startedAt = Date.now();
159026
- const deadline = startedAt + SWEEP_MAX_WALL_CLOCK_MS;
159027
- try {
159028
- const resolvedConfig = resolveEmbeddingConfig(config2);
159029
- if (resolvedConfig.provider === "off")
159030
- return 0;
159031
- const projects = db.prepare(`SELECT m.project_path, MAX(m.updated_at) AS latest
159032
- FROM memories m
159033
- WHERE m.status IN ('active', 'permanent')
159034
- AND m.id NOT IN (SELECT memory_id FROM memory_embeddings)
159035
- GROUP BY m.project_path
159036
- ORDER BY latest DESC
159037
- LIMIT 20`).all();
159038
- let total = 0;
159039
- let consecutiveEmpty = 0;
159040
- outer:
159041
- for (const project of projects) {
159042
- while (Date.now() < deadline) {
159043
- const count = await embedUnembeddedMemoriesForProject(db, project.project_path, config2, batchSize);
159044
- if (count === 0) {
159045
- consecutiveEmpty += 1;
159046
- if (consecutiveEmpty >= SWEEP_MAX_CONSECUTIVE_EMPTY) {
159047
- log(`[magic-context] embedding sweep: ${SWEEP_MAX_CONSECUTIVE_EMPTY} consecutive empty batches, stopping (total=${total})`);
159048
- break outer;
159049
- }
159050
- break;
159051
- }
159052
- consecutiveEmpty = 0;
159053
- total += count;
159054
- if (count < batchSize)
159055
- break;
159056
- }
159057
- if (Date.now() >= deadline) {
159058
- log(`[magic-context] embedding sweep: wall-clock deadline reached after ${((Date.now() - startedAt) / 1000).toFixed(1)}s (total=${total})`);
159059
- break;
159060
- }
159061
- }
159062
- return total;
159063
- } finally {
159064
- sweepInProgress = false;
159365
+ function isUnembeddedMemoryRow(row) {
159366
+ if (row === null || typeof row !== "object")
159367
+ return false;
159368
+ const candidate = row;
159369
+ return typeof candidate.id === "number" && typeof candidate.content === "string";
159370
+ }
159371
+ function getLoadUnembeddedMemoriesStatement(db) {
159372
+ let stmt = loadUnembeddedMemoriesStatements.get(db);
159373
+ if (!stmt) {
159374
+ stmt = db.prepare("SELECT m.id AS id, m.content AS content FROM memories m LEFT JOIN memory_embeddings me ON m.id = me.memory_id WHERE m.project_path = ? AND m.status = 'active' AND me.memory_id IS NULL LIMIT ?");
159375
+ loadUnembeddedMemoriesStatements.set(db, stmt);
159065
159376
  }
159377
+ return stmt;
159066
159378
  }
159067
- async function embedUnembeddedMemoriesForProject(db, projectPath, config2, batchSize = 10) {
159068
- const normalizedBatchSize = Math.max(1, Math.floor(batchSize));
159069
- const resolvedConfig = resolveEmbeddingConfig(config2);
159070
- if (resolvedConfig.provider === "off") {
159379
+ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize = 10) {
159380
+ const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
159381
+ if (!snapshot?.enabled)
159071
159382
  return 0;
159072
- }
159073
- initializeEmbedding(resolvedConfig);
159074
- const memories = getLoadUnembeddedMemoriesStatement(db).all(projectPath, normalizedBatchSize).filter(isUnembeddedMemoryRow);
159075
- if (memories.length === 0) {
159383
+ const normalizedBatchSize = Math.max(1, Math.floor(batchSize));
159384
+ const memories = getLoadUnembeddedMemoriesStatement(db).all(projectIdentity, normalizedBatchSize).filter(isUnembeddedMemoryRow);
159385
+ if (memories.length === 0)
159076
159386
  return 0;
159077
- }
159078
159387
  try {
159079
- const embeddings = await embedBatch(memories.map((memory) => memory.content));
159080
- const modelId = getEmbeddingModelId();
159081
- if (modelId === "off") {
159388
+ const result = await embedBatchForProject(projectIdentity, memories.map((memory) => memory.content));
159389
+ if (!result)
159082
159390
  return 0;
159083
- }
159084
159391
  let embeddedCount = 0;
159085
159392
  db.transaction(() => {
159086
159393
  for (const [index, memory] of memories.entries()) {
159087
- const embedding = embeddings[index];
159088
- if (!embedding) {
159394
+ const embedding = result.vectors[index];
159395
+ if (!embedding)
159089
159396
  continue;
159090
- }
159091
- saveEmbedding(db, memory.id, embedding, modelId);
159397
+ saveEmbedding(db, memory.id, embedding, result.modelId);
159092
159398
  embeddedCount += 1;
159093
159399
  }
159094
159400
  })();
@@ -159098,10 +159404,56 @@ async function embedUnembeddedMemoriesForProject(db, projectPath, config2, batch
159098
159404
  return 0;
159099
159405
  }
159100
159406
  }
159101
- function getEmbeddingModelId() {
159102
- return getOrCreateProvider()?.modelId ?? "off";
159407
+ var OFF_PROVIDER_IDENTITY = "embedding-provider:off", SWEEP_MAX_WALL_CLOCK_MS, projectRegistrations, loadUnembeddedMemoriesStatements, globalRegistrationGeneration = 0, testProviderFactory = null;
159408
+ var init_project_embedding_registry = __esm(() => {
159409
+ init_magic_context();
159410
+ init_logger();
159411
+ init_storage_git_commit_embeddings();
159412
+ init_embedding_cache();
159413
+ init_embedding_identity();
159414
+ init_embedding_local();
159415
+ init_embedding_openai();
159416
+ init_storage_memory_embeddings();
159417
+ SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
159418
+ projectRegistrations = new Map;
159419
+ loadUnembeddedMemoriesStatements = new WeakMap;
159420
+ });
159421
+
159422
+ // src/features/magic-context/memory/embedding.ts
159423
+ function createProvider2(config2) {
159424
+ if (config2.provider === "off") {
159425
+ return null;
159426
+ }
159427
+ if (config2.provider === "openai-compatible") {
159428
+ return new OpenAICompatibleEmbeddingProvider({
159429
+ endpoint: config2.endpoint,
159430
+ model: config2.model,
159431
+ apiKey: config2.api_key
159432
+ });
159433
+ }
159434
+ return new LocalEmbeddingProvider(config2.model);
159435
+ }
159436
+ function getOrCreateProvider() {
159437
+ if (provider) {
159438
+ return provider;
159439
+ }
159440
+ provider = createProvider2(embeddingConfig);
159441
+ return provider;
159442
+ }
159443
+ function isEmbeddingEnabled() {
159444
+ return embeddingConfig.provider !== "off";
159445
+ }
159446
+ async function embedText(text, signal) {
159447
+ const currentProvider = getOrCreateProvider();
159448
+ if (!currentProvider) {
159449
+ return null;
159450
+ }
159451
+ if (!await currentProvider.initialize()) {
159452
+ return null;
159453
+ }
159454
+ return currentProvider.embed(text, signal);
159103
159455
  }
159104
- var DEFAULT_EMBEDDING_CONFIG, embeddingConfig, provider = null, loadUnembeddedMemoriesStatements, SWEEP_MAX_WALL_CLOCK_MS, SWEEP_MAX_CONSECUTIVE_EMPTY = 3, sweepInProgress = false;
159456
+ var DEFAULT_EMBEDDING_CONFIG, embeddingConfig, provider = null, loadUnembeddedMemoriesStatements2, SWEEP_MAX_WALL_CLOCK_MS2;
159105
159457
  var init_embedding = __esm(() => {
159106
159458
  init_magic_context();
159107
159459
  init_logger();
@@ -159109,13 +159461,14 @@ var init_embedding = __esm(() => {
159109
159461
  init_embedding_local();
159110
159462
  init_embedding_openai();
159111
159463
  init_storage_memory_embeddings();
159464
+ init_project_embedding_registry();
159112
159465
  DEFAULT_EMBEDDING_CONFIG = {
159113
159466
  provider: "local",
159114
159467
  model: DEFAULT_LOCAL_EMBEDDING_MODEL
159115
159468
  };
159116
159469
  embeddingConfig = DEFAULT_EMBEDDING_CONFIG;
159117
- loadUnembeddedMemoriesStatements = new WeakMap;
159118
- SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
159470
+ loadUnembeddedMemoriesStatements2 = new WeakMap;
159471
+ SWEEP_MAX_WALL_CLOCK_MS2 = 10 * 60 * 1000;
159119
159472
  });
159120
159473
 
159121
159474
  // src/features/magic-context/memory/storage-memory-fts.ts
@@ -159152,46 +159505,6 @@ var init_storage_memory_fts = __esm(() => {
159152
159505
  searchStatements = new WeakMap;
159153
159506
  });
159154
159507
 
159155
- // src/features/magic-context/memory/project-identity.ts
159156
- import { execSync } from "node:child_process";
159157
- import { createHash as createHash4 } from "node:crypto";
159158
- import path4 from "node:path";
159159
- function getRootCommitHash(directory) {
159160
- try {
159161
- const hash2 = execSync("git rev-list --max-parents=0 HEAD", {
159162
- cwd: directory,
159163
- encoding: "utf-8",
159164
- stdio: ["pipe", "pipe", "pipe"],
159165
- timeout: GIT_TIMEOUT_MS2
159166
- }).trim();
159167
- const firstLine = hash2.split(`
159168
- `)[0]?.trim();
159169
- return firstLine && firstLine.length >= 7 ? firstLine : undefined;
159170
- } catch {
159171
- return;
159172
- }
159173
- }
159174
- function directoryFallback(directory) {
159175
- const canonical = path4.resolve(directory);
159176
- const hash2 = createHash4("md5").update(canonical).digest("hex").slice(0, 12);
159177
- return `dir:${hash2}`;
159178
- }
159179
- function resolveProjectIdentity(directory) {
159180
- const resolved = path4.resolve(directory);
159181
- const cached2 = resolvedCache.get(resolved);
159182
- if (cached2 !== undefined) {
159183
- return cached2;
159184
- }
159185
- const rootHash = getRootCommitHash(resolved);
159186
- const identity = rootHash ? `git:${rootHash}` : directoryFallback(resolved);
159187
- resolvedCache.set(resolved, identity);
159188
- return identity;
159189
- }
159190
- var GIT_TIMEOUT_MS2 = 5000, resolvedCache;
159191
- var init_project_identity = __esm(() => {
159192
- resolvedCache = new Map;
159193
- });
159194
-
159195
159508
  // src/features/magic-context/compression-depth-storage.ts
159196
159509
  function getIncrementDepthStatement(db) {
159197
159510
  let stmt = incrementDepthStatements.get(db);
@@ -160550,6 +160863,52 @@ var init_migrations = __esm(async () => {
160550
160863
  db.exec("ALTER TABLE session_meta ADD COLUMN deferred_execute_state TEXT");
160551
160864
  }
160552
160865
  }
160866
+ },
160867
+ {
160868
+ version: 16,
160869
+ description: "Add context-limit cache regression sentinels",
160870
+ up: (db) => {
160871
+ const cols = db.prepare("PRAGMA table_info(session_meta)").all();
160872
+ if (!cols.some((c) => c.name === "observed_safe_input_tokens")) {
160873
+ db.exec("ALTER TABLE session_meta ADD COLUMN observed_safe_input_tokens INTEGER NOT NULL DEFAULT 0");
160874
+ }
160875
+ if (!cols.some((c) => c.name === "cache_alert_sent")) {
160876
+ db.exec("ALTER TABLE session_meta ADD COLUMN cache_alert_sent INTEGER NOT NULL DEFAULT 0");
160877
+ }
160878
+ }
160879
+ },
160880
+ {
160881
+ version: 17,
160882
+ description: "Multi-anchor JSON storage for note-nudge and auto-search-hint persistence",
160883
+ up: (db) => {
160884
+ const cols = db.prepare("PRAGMA table_info(session_meta)").all();
160885
+ if (!cols.some((c) => c.name === "note_nudge_anchors")) {
160886
+ db.exec("ALTER TABLE session_meta ADD COLUMN note_nudge_anchors TEXT NOT NULL DEFAULT '[]'");
160887
+ }
160888
+ if (!cols.some((c) => c.name === "auto_search_hint_decisions")) {
160889
+ db.exec("ALTER TABLE session_meta ADD COLUMN auto_search_hint_decisions TEXT NOT NULL DEFAULT '[]'");
160890
+ }
160891
+ db.exec(`
160892
+ UPDATE session_meta
160893
+ SET note_nudge_anchors = json_array(
160894
+ json_object(
160895
+ 'messageId', note_nudge_sticky_message_id,
160896
+ 'text', note_nudge_sticky_text
160897
+ )
160898
+ )
160899
+ WHERE COALESCE(note_nudge_sticky_text, '') != ''
160900
+ AND COALESCE(note_nudge_sticky_message_id, '') != ''
160901
+ AND (note_nudge_anchors IS NULL OR note_nudge_anchors = '[]')
160902
+ `);
160903
+ db.exec(`
160904
+ UPDATE session_meta SET note_nudge_anchors = '[]'
160905
+ WHERE note_nudge_anchors IS NULL
160906
+ `);
160907
+ db.exec(`
160908
+ UPDATE session_meta SET auto_search_hint_decisions = '[]'
160909
+ WHERE auto_search_hint_decisions IS NULL
160910
+ `);
160911
+ }
160553
160912
  }
160554
160913
  ];
160555
160914
  });
@@ -161055,6 +161414,8 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
161055
161414
  note_nudge_trigger_message_id TEXT DEFAULT '',
161056
161415
  note_nudge_sticky_text TEXT DEFAULT '',
161057
161416
  note_nudge_sticky_message_id TEXT DEFAULT '',
161417
+ note_nudge_anchors TEXT NOT NULL DEFAULT '[]',
161418
+ auto_search_hint_decisions TEXT NOT NULL DEFAULT '[]',
161058
161419
  last_todo_state TEXT DEFAULT '',
161059
161420
  todo_synthetic_call_id TEXT DEFAULT '',
161060
161421
  todo_synthetic_anchor_message_id TEXT DEFAULT '',
@@ -161062,6 +161423,8 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
161062
161423
  is_subagent INTEGER DEFAULT 0,
161063
161424
  last_context_percentage REAL DEFAULT 0,
161064
161425
  last_input_tokens INTEGER DEFAULT 0,
161426
+ observed_safe_input_tokens INTEGER NOT NULL DEFAULT 0,
161427
+ cache_alert_sent INTEGER NOT NULL DEFAULT 0,
161065
161428
  times_execute_threshold_reached INTEGER DEFAULT 0,
161066
161429
  compartment_in_progress INTEGER DEFAULT 0,
161067
161430
  historian_failure_count INTEGER DEFAULT 0,
@@ -161133,12 +161496,16 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
161133
161496
  ensureColumn(db, "session_meta", "note_nudge_trigger_message_id", "TEXT DEFAULT ''");
161134
161497
  ensureColumn(db, "session_meta", "note_nudge_sticky_text", "TEXT DEFAULT ''");
161135
161498
  ensureColumn(db, "session_meta", "note_nudge_sticky_message_id", "TEXT DEFAULT ''");
161499
+ ensureColumn(db, "session_meta", "note_nudge_anchors", "TEXT NOT NULL DEFAULT '[]'");
161500
+ ensureColumn(db, "session_meta", "auto_search_hint_decisions", "TEXT NOT NULL DEFAULT '[]'");
161136
161501
  ensureColumn(db, "session_meta", "last_todo_state", "TEXT DEFAULT ''");
161137
161502
  ensureColumn(db, "session_meta", "todo_synthetic_call_id", "TEXT DEFAULT ''");
161138
161503
  ensureColumn(db, "session_meta", "todo_synthetic_anchor_message_id", "TEXT DEFAULT ''");
161139
161504
  ensureColumn(db, "session_meta", "todo_synthetic_state_json", "TEXT DEFAULT ''");
161140
161505
  ensureColumn(db, "session_meta", "note_last_read_at", "INTEGER DEFAULT 0");
161141
161506
  ensureColumn(db, "session_meta", "times_execute_threshold_reached", "INTEGER DEFAULT 0");
161507
+ ensureColumn(db, "session_meta", "observed_safe_input_tokens", "INTEGER NOT NULL DEFAULT 0");
161508
+ ensureColumn(db, "session_meta", "cache_alert_sent", "INTEGER NOT NULL DEFAULT 0");
161142
161509
  ensureColumn(db, "session_meta", "compartment_in_progress", "INTEGER DEFAULT 0");
161143
161510
  ensureColumn(db, "session_meta", "historian_failure_count", "INTEGER DEFAULT 0");
161144
161511
  ensureColumn(db, "session_meta", "historian_last_error", "TEXT DEFAULT NULL");
@@ -161230,7 +161597,9 @@ function healNullIntegerColumns(db) {
161230
161597
  ["system_prompt_tokens", 0],
161231
161598
  ["conversation_tokens", 0],
161232
161599
  ["tool_call_tokens", 0],
161233
- ["note_nudge_trigger_pending", 0]
161600
+ ["note_nudge_trigger_pending", 0],
161601
+ ["observed_safe_input_tokens", 0],
161602
+ ["cache_alert_sent", 0]
161234
161603
  ];
161235
161604
  for (const [column, fallback] of columns) {
161236
161605
  try {
@@ -161239,7 +161608,7 @@ function healNullIntegerColumns(db) {
161239
161608
  }
161240
161609
  }
161241
161610
  function ensureColumn(db, table, column, definition) {
161242
- if (!/^[a-z_]+$/.test(table) || !/^[a-z_]+$/.test(column) || !/^[A-Z0-9_'(),\s]+$/i.test(definition)) {
161611
+ if (!/^[a-z_]+$/.test(table) || !/^[a-z_]+$/.test(column) || !/^[A-Z0-9_'(),[\]\s]+$/i.test(definition)) {
161243
161612
  throw new Error(`Unsafe schema identifier: ${table}.${column} ${definition}`);
161244
161613
  }
161245
161614
  const rows = db.prepare(`PRAGMA table_info(${table})`).all();
@@ -161326,7 +161695,7 @@ function isSessionMetaRow(row) {
161326
161695
  if (row === null || typeof row !== "object")
161327
161696
  return false;
161328
161697
  const r = row;
161329
- return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state);
161698
+ return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.observed_safe_input_tokens) && isNumberOrNull(r.cache_alert_sent) && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state);
161330
161699
  }
161331
161700
  function getDefaultSessionMeta(sessionId) {
161332
161701
  return {
@@ -161340,6 +161709,8 @@ function getDefaultSessionMeta(sessionId) {
161340
161709
  isSubagent: false,
161341
161710
  lastContextPercentage: 0,
161342
161711
  lastInputTokens: 0,
161712
+ observedSafeInputTokens: 0,
161713
+ cacheAlertSent: false,
161343
161714
  timesExecuteThresholdReached: 0,
161344
161715
  compartmentInProgress: false,
161345
161716
  systemPromptHash: "",
@@ -161352,7 +161723,7 @@ function getDefaultSessionMeta(sessionId) {
161352
161723
  }
161353
161724
  function ensureSessionMetaRow(db, sessionId) {
161354
161725
  const defaults = getDefaultSessionMeta(sessionId);
161355
- db.prepare("INSERT OR IGNORE INTO session_meta (session_id, harness, last_response_time, cache_ttl, counter, last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent, last_context_percentage, last_input_tokens, times_execute_threshold_reached, compartment_in_progress, system_prompt_hash, cleared_reasoning_through_tag) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(sessionId, getHarness(), defaults.lastResponseTime, defaults.cacheTtl, defaults.counter, defaults.lastNudgeTokens, defaults.lastNudgeBand ?? "", defaults.lastTransformError ?? "", defaults.isSubagent ? 1 : 0, defaults.lastContextPercentage, defaults.lastInputTokens, defaults.timesExecuteThresholdReached, defaults.compartmentInProgress ? 1 : 0, defaults.systemPromptHash ?? "", defaults.clearedReasoningThroughTag);
161726
+ db.prepare("INSERT OR IGNORE INTO session_meta (session_id, harness, last_response_time, cache_ttl, counter, last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent, last_context_percentage, last_input_tokens, observed_safe_input_tokens, cache_alert_sent, times_execute_threshold_reached, compartment_in_progress, system_prompt_hash, cleared_reasoning_through_tag) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(sessionId, getHarness(), defaults.lastResponseTime, defaults.cacheTtl, defaults.counter, defaults.lastNudgeTokens, defaults.lastNudgeBand ?? "", defaults.lastTransformError ?? "", defaults.isSubagent ? 1 : 0, defaults.lastContextPercentage, defaults.lastInputTokens, defaults.observedSafeInputTokens, defaults.cacheAlertSent ? 1 : 0, defaults.timesExecuteThresholdReached, defaults.compartmentInProgress ? 1 : 0, defaults.systemPromptHash ?? "", defaults.clearedReasoningThroughTag);
161356
161727
  }
161357
161728
  function toSessionMeta(row) {
161358
161729
  const nudgeBandRaw = typeof row.last_nudge_band === "string" ? row.last_nudge_band : "";
@@ -161372,6 +161743,8 @@ function toSessionMeta(row) {
161372
161743
  isSubagent: row.is_subagent === 1,
161373
161744
  lastContextPercentage: row.last_context_percentage,
161374
161745
  lastInputTokens: row.last_input_tokens,
161746
+ observedSafeInputTokens: numOrZero(row.observed_safe_input_tokens),
161747
+ cacheAlertSent: numOrZero(row.cache_alert_sent) === 1,
161375
161748
  timesExecuteThresholdReached: numOrZero(row.times_execute_threshold_reached),
161376
161749
  compartmentInProgress: row.compartment_in_progress === 1,
161377
161750
  systemPromptHash: String(systemPromptHashRaw),
@@ -161394,6 +161767,8 @@ var init_storage_meta_shared = __esm(() => {
161394
161767
  isSubagent: "is_subagent",
161395
161768
  lastContextPercentage: "last_context_percentage",
161396
161769
  lastInputTokens: "last_input_tokens",
161770
+ observedSafeInputTokens: "observed_safe_input_tokens",
161771
+ cacheAlertSent: "cache_alert_sent",
161397
161772
  timesExecuteThresholdReached: "times_execute_threshold_reached",
161398
161773
  compartmentInProgress: "compartment_in_progress",
161399
161774
  systemPromptHash: "system_prompt_hash",
@@ -161403,7 +161778,7 @@ var init_storage_meta_shared = __esm(() => {
161403
161778
  clearedReasoningThroughTag: "cleared_reasoning_through_tag",
161404
161779
  lastTodoState: "last_todo_state"
161405
161780
  };
161406
- BOOLEAN_META_KEYS = new Set(["isSubagent", "compartmentInProgress"]);
161781
+ BOOLEAN_META_KEYS = new Set(["isSubagent", "compartmentInProgress", "cacheAlertSent"]);
161407
161782
  });
161408
161783
 
161409
161784
  // src/features/magic-context/storage-meta-persisted.ts
@@ -161437,6 +161812,38 @@ function isPersistedNoteNudgeRow(row) {
161437
161812
  const r = row;
161438
161813
  return typeof r.note_nudge_trigger_pending === "number" && typeof r.note_nudge_trigger_message_id === "string" && typeof r.note_nudge_sticky_text === "string" && typeof r.note_nudge_sticky_message_id === "string";
161439
161814
  }
161815
+ function isValidNoteNudgeAnchor(value) {
161816
+ if (value === null || typeof value !== "object")
161817
+ return false;
161818
+ const row = value;
161819
+ return typeof row.messageId === "string" && row.messageId.length > 0 && typeof row.text === "string" && row.text.length > 0;
161820
+ }
161821
+ function isValidAutoSearchHintDecision(value) {
161822
+ if (value === null || typeof value !== "object")
161823
+ return false;
161824
+ const row = value;
161825
+ if (typeof row.messageId !== "string" || row.messageId.length === 0)
161826
+ return false;
161827
+ if (row.decision === "hint") {
161828
+ return typeof row.text === "string" && row.text.length > 0;
161829
+ }
161830
+ if (row.decision === "no-hint") {
161831
+ return typeof row.reason === "string" && AUTO_SEARCH_NO_HINT_REASONS.has(row.reason);
161832
+ }
161833
+ return false;
161834
+ }
161835
+ function parseJsonArray(json2, validator) {
161836
+ if (!json2)
161837
+ return [];
161838
+ try {
161839
+ const parsed = JSON.parse(json2);
161840
+ if (!Array.isArray(parsed))
161841
+ return [];
161842
+ return parsed.filter(validator);
161843
+ } catch {
161844
+ return [];
161845
+ }
161846
+ }
161440
161847
  function isPersistedTodoSyntheticAnchorRow(row) {
161441
161848
  if (row === null || typeof row !== "object")
161442
161849
  return false;
@@ -161547,7 +161954,7 @@ function getPersistedNoteNudge(db, sessionId) {
161547
161954
  function setPersistedNoteNudgeTrigger(db, sessionId, triggerMessageId = "") {
161548
161955
  db.transaction(() => {
161549
161956
  ensureSessionMetaRow(db, sessionId);
161550
- db.prepare("UPDATE session_meta SET note_nudge_trigger_pending = 1, note_nudge_trigger_message_id = ?, note_nudge_sticky_text = '', note_nudge_sticky_message_id = '' WHERE session_id = ?").run(triggerMessageId, sessionId);
161957
+ db.prepare("UPDATE session_meta SET note_nudge_trigger_pending = 1, note_nudge_trigger_message_id = ? WHERE session_id = ?").run(triggerMessageId, sessionId);
161551
161958
  })();
161552
161959
  }
161553
161960
  function setPersistedNoteNudgeTriggerMessageId(db, sessionId, triggerMessageId) {
@@ -161556,14 +161963,126 @@ function setPersistedNoteNudgeTriggerMessageId(db, sessionId, triggerMessageId)
161556
161963
  db.prepare("UPDATE session_meta SET note_nudge_trigger_message_id = ? WHERE session_id = ?").run(triggerMessageId, sessionId);
161557
161964
  })();
161558
161965
  }
161559
- function setPersistedDeliveredNoteNudge(db, sessionId, text, messageId = "") {
161560
- db.transaction(() => {
161966
+ function getNoteNudgeAnchors(db, sessionId) {
161967
+ const row = db.prepare("SELECT note_nudge_anchors FROM session_meta WHERE session_id = ?").get(sessionId);
161968
+ return parseJsonArray(row?.note_nudge_anchors, isValidNoteNudgeAnchor);
161969
+ }
161970
+ function getAutoSearchHintDecisions(db, sessionId) {
161971
+ const row = db.prepare("SELECT auto_search_hint_decisions FROM session_meta WHERE session_id = ?").get(sessionId);
161972
+ return parseJsonArray(row?.auto_search_hint_decisions, isValidAutoSearchHintDecision);
161973
+ }
161974
+ function casUpdateJsonArrayColumn(db, sessionId, column, validator, mutate, options) {
161975
+ if (options?.ensureRow === false) {
161976
+ const exists = db.prepare("SELECT 1 FROM session_meta WHERE session_id = ?").get(sessionId);
161977
+ if (!exists)
161978
+ return true;
161979
+ } else {
161561
161980
  ensureSessionMetaRow(db, sessionId);
161562
- db.prepare("UPDATE session_meta SET note_nudge_trigger_pending = 0, note_nudge_trigger_message_id = '', note_nudge_sticky_text = ?, note_nudge_sticky_message_id = ? WHERE session_id = ?").run(text, messageId, sessionId);
161563
- })();
161981
+ }
161982
+ for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
161983
+ const row = db.prepare(`SELECT ${column} FROM session_meta WHERE session_id = ?`).get(sessionId);
161984
+ const currentBlob = row?.[column] ?? "[]";
161985
+ const current = parseJsonArray(currentBlob, validator);
161986
+ const next = mutate(current);
161987
+ if (next === null)
161988
+ return true;
161989
+ const nextBlob = stableStringify(next);
161990
+ if (nextBlob === currentBlob)
161991
+ return true;
161992
+ const result = db.prepare(`UPDATE session_meta SET ${column} = ? WHERE session_id = ? AND ${column} = ?`).run(nextBlob, sessionId, currentBlob);
161993
+ if (result.changes > 0)
161994
+ return true;
161995
+ }
161996
+ sessionLog(sessionId, `${column} CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
161997
+ return false;
161564
161998
  }
161565
- function clearPersistedNoteNudge(db, sessionId) {
161566
- db.prepare("UPDATE session_meta SET note_nudge_trigger_pending = 0, note_nudge_trigger_message_id = '', note_nudge_sticky_text = '', note_nudge_sticky_message_id = '' WHERE session_id = ?").run(sessionId);
161999
+ function deliverNoteNudgeAtomic(db, sessionId, messageId, text) {
162000
+ let plan = null;
162001
+ const casOk = casUpdateJsonArrayColumn(db, sessionId, "note_nudge_anchors", isValidNoteNudgeAnchor, (current) => {
162002
+ if (current.some((anchor) => anchor.messageId === messageId && anchor.text === text)) {
162003
+ plan = { kind: "already-present" };
162004
+ return null;
162005
+ }
162006
+ if (current.some((anchor) => anchor.messageId === messageId)) {
162007
+ plan = { kind: "conflict" };
162008
+ sessionLog(sessionId, "note-nudge: messageId conflict, refusing append");
162009
+ return null;
162010
+ }
162011
+ plan = { kind: "appended" };
162012
+ return [...current, { messageId, text }];
162013
+ });
162014
+ if (!casOk) {
162015
+ sessionLog(sessionId, `note-nudge: CAS exhausted for ${messageId}; skipping wire append`);
162016
+ return { ok: false, kind: "cas-exhausted" };
162017
+ }
162018
+ const committedPlan = plan;
162019
+ if (!committedPlan) {
162020
+ sessionLog(sessionId, "note-nudge: CAS reported success with no plan staged; treating as failure");
162021
+ return { ok: false, kind: "cas-exhausted" };
162022
+ }
162023
+ if (committedPlan.kind === "conflict") {
162024
+ return { ok: false, kind: "conflict" };
162025
+ }
162026
+ db.prepare("UPDATE session_meta SET note_nudge_trigger_pending = 0, note_nudge_trigger_message_id = '' WHERE session_id = ?").run(sessionId);
162027
+ return { ok: true, kind: committedPlan.kind };
162028
+ }
162029
+ function appendAutoSearchHintDecision(db, sessionId, entry) {
162030
+ if (!entry.messageId)
162031
+ return { ok: false, kind: "cas-exhausted" };
162032
+ let staged = null;
162033
+ const casOk = casUpdateJsonArrayColumn(db, sessionId, "auto_search_hint_decisions", isValidAutoSearchHintDecision, (current) => {
162034
+ const existing = current.find((decision) => decision.messageId === entry.messageId);
162035
+ if (existing) {
162036
+ staged = { kind: "already-present", decision: existing };
162037
+ return null;
162038
+ }
162039
+ staged = { kind: "appended", decision: entry };
162040
+ return [...current, entry];
162041
+ });
162042
+ if (!casOk)
162043
+ return { ok: false, kind: "cas-exhausted" };
162044
+ const committed = staged;
162045
+ if (!committed) {
162046
+ sessionLog(sessionId, "auto-search: CAS reported success with no staged outcome");
162047
+ return { ok: false, kind: "cas-exhausted" };
162048
+ }
162049
+ return { ok: true, kind: committed.kind, decision: committed.decision };
162050
+ }
162051
+ function pruneNoteNudgeAnchors(db, sessionId, visibleMessageIds) {
162052
+ let pruned = 0;
162053
+ casUpdateJsonArrayColumn(db, sessionId, "note_nudge_anchors", isValidNoteNudgeAnchor, (current) => {
162054
+ const next = current.filter((anchor) => visibleMessageIds.has(anchor.messageId));
162055
+ pruned = current.length - next.length;
162056
+ return pruned > 0 ? next : null;
162057
+ });
162058
+ return pruned;
162059
+ }
162060
+ function pruneAutoSearchHintDecisions(db, sessionId, visibleMessageIds) {
162061
+ let pruned = 0;
162062
+ casUpdateJsonArrayColumn(db, sessionId, "auto_search_hint_decisions", isValidAutoSearchHintDecision, (current) => {
162063
+ const next = current.filter((decision) => visibleMessageIds.has(decision.messageId));
162064
+ pruned = current.length - next.length;
162065
+ return pruned > 0 ? next : null;
162066
+ });
162067
+ return pruned;
162068
+ }
162069
+ function removeNoteNudgeAnchorByMessageId(db, sessionId, messageId) {
162070
+ let removed = false;
162071
+ const ok = casUpdateJsonArrayColumn(db, sessionId, "note_nudge_anchors", isValidNoteNudgeAnchor, (current) => {
162072
+ const next = current.filter((anchor) => anchor.messageId !== messageId);
162073
+ removed = next.length !== current.length;
162074
+ return removed ? next : null;
162075
+ }, { ensureRow: false });
162076
+ return ok && removed;
162077
+ }
162078
+ function removeAutoSearchHintDecisionByMessageId(db, sessionId, messageId) {
162079
+ let removed = false;
162080
+ const ok = casUpdateJsonArrayColumn(db, sessionId, "auto_search_hint_decisions", isValidAutoSearchHintDecision, (current) => {
162081
+ const next = current.filter((decision) => decision.messageId !== messageId);
162082
+ removed = next.length !== current.length;
162083
+ return removed ? next : null;
162084
+ }, { ensureRow: false });
162085
+ return ok && removed;
161567
162086
  }
161568
162087
  function getPersistedTodoSyntheticAnchor(db, sessionId) {
161569
162088
  const result = db.prepare("SELECT todo_synthetic_call_id, todo_synthetic_anchor_message_id, todo_synthetic_state_json FROM session_meta WHERE session_id = ?").get(sessionId);
@@ -161645,9 +162164,9 @@ function recordOverflowDetected(db, sessionId, reportedLimit) {
161645
162164
  db.transaction(() => {
161646
162165
  ensureSessionMetaRow(db, sessionId);
161647
162166
  if (typeof reportedLimit === "number" && reportedLimit > 0) {
161648
- db.prepare("UPDATE session_meta SET detected_context_limit = ?, needs_emergency_recovery = 1 WHERE session_id = ?").run(reportedLimit, sessionId);
162167
+ db.prepare("UPDATE session_meta SET detected_context_limit = ?, needs_emergency_recovery = 1, observed_safe_input_tokens = 0, cache_alert_sent = 0 WHERE session_id = ?").run(reportedLimit, sessionId);
161649
162168
  } else {
161650
- db.prepare("UPDATE session_meta SET needs_emergency_recovery = 1 WHERE session_id = ?").run(sessionId);
162169
+ db.prepare("UPDATE session_meta SET needs_emergency_recovery = 1, observed_safe_input_tokens = 0, cache_alert_sent = 0 WHERE session_id = ?").run(sessionId);
161651
162170
  }
161652
162171
  })();
161653
162172
  }
@@ -161656,7 +162175,7 @@ function recordDetectedContextLimit(db, sessionId, reportedLimit) {
161656
162175
  return;
161657
162176
  db.transaction(() => {
161658
162177
  ensureSessionMetaRow(db, sessionId);
161659
- db.prepare("UPDATE session_meta SET detected_context_limit = ? WHERE session_id = ?").run(reportedLimit, sessionId);
162178
+ db.prepare("UPDATE session_meta SET detected_context_limit = ?, observed_safe_input_tokens = 0, cache_alert_sent = 0 WHERE session_id = ?").run(reportedLimit, sessionId);
161660
162179
  })();
161661
162180
  }
161662
162181
  function clearEmergencyRecovery(db, sessionId) {
@@ -161771,9 +162290,18 @@ function getSessionsWithPendingMarker(db) {
161771
162290
  AND pending_compaction_marker_state != ''`).all();
161772
162291
  return rows.map((r) => r.session_id);
161773
162292
  }
162293
+ var CAS_RETRY_LIMIT = 5, AUTO_SEARCH_NO_HINT_REASONS;
161774
162294
  var init_storage_meta_persisted = __esm(() => {
161775
162295
  init_logger();
161776
162296
  init_storage_meta_shared();
162297
+ AUTO_SEARCH_NO_HINT_REASONS = new Set([
162298
+ "below-threshold",
162299
+ "timeout",
162300
+ "empty",
162301
+ "error",
162302
+ "stacked",
162303
+ "too-short"
162304
+ ]);
161777
162305
  });
161778
162306
 
161779
162307
  // src/features/magic-context/resolve-subagent-fallback.ts
@@ -161797,7 +162325,7 @@ var init_resolve_subagent_fallback = __esm(async () => {
161797
162325
 
161798
162326
  // src/features/magic-context/storage-meta-session.ts
161799
162327
  function getOrCreateSessionMeta(db, sessionId) {
161800
- const result = db.prepare("SELECT session_id, last_response_time, cache_ttl, counter, last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent, last_context_percentage, last_input_tokens, times_execute_threshold_reached, compartment_in_progress, system_prompt_hash, system_prompt_tokens, conversation_tokens, tool_call_tokens, cleared_reasoning_through_tag, last_todo_state FROM session_meta WHERE session_id = ?").get(sessionId);
162328
+ const result = db.prepare("SELECT session_id, last_response_time, cache_ttl, counter, last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent, last_context_percentage, last_input_tokens, observed_safe_input_tokens, cache_alert_sent, times_execute_threshold_reached, compartment_in_progress, system_prompt_hash, system_prompt_tokens, conversation_tokens, tool_call_tokens, cleared_reasoning_through_tag, last_todo_state FROM session_meta WHERE session_id = ?").get(sessionId);
161801
162329
  if (isSessionMetaRow(result)) {
161802
162330
  return toSessionMeta(result);
161803
162331
  }
@@ -162298,12 +162826,12 @@ var init_storage = __esm(async () => {
162298
162826
  });
162299
162827
 
162300
162828
  // src/shared/models-dev-cache.ts
162301
- import { createHash as createHash5 } from "node:crypto";
162829
+ import { createHash as createHash7 } from "node:crypto";
162302
162830
  import { existsSync as existsSync14, readFileSync as readFileSync11 } from "node:fs";
162303
162831
  import { homedir as homedir9, platform as platform3 } from "node:os";
162304
162832
  import { join as join19 } from "node:path";
162305
162833
  function hashFast(input) {
162306
- return createHash5("sha1").update(input).digest("hex");
162834
+ return createHash7("sha1").update(input).digest("hex");
162307
162835
  }
162308
162836
  function getModelsJsonPath() {
162309
162837
  const explicit = process.env.OPENCODE_MODELS_PATH?.trim();
@@ -162410,17 +162938,9 @@ async function refreshModelLimitsFromApi(client) {
162410
162938
  apiCache = map2;
162411
162939
  apiLoadedAt = Date.now();
162412
162940
  if (previousSize === null) {
162413
- recentlySeenApiSizes.add(map2.size);
162414
162941
  sessionLog("global", `models-dev-cache: API layer loaded ${map2.size} model metadata entries`);
162415
162942
  } else if (previousSize !== map2.size) {
162416
- const sizeAlreadySeen = recentlySeenApiSizes.has(map2.size);
162417
- recentlySeenApiSizes.add(map2.size);
162418
- if (!sizeAlreadySeen) {
162419
- sessionLog("global", `models-dev-cache: API layer loaded ${map2.size} model metadata entries (was ${previousSize})`);
162420
- } else if (!oscillationLogged) {
162421
- oscillationLogged = true;
162422
- sessionLog("global", `models-dev-cache: API count oscillating between ${[...recentlySeenApiSizes].sort((a, b) => a - b).join(" ↔ ")} — likely upstream provider plugin returning slightly different model sets between calls (e.g. github-copilot's /models endpoint toggling model_picker_enabled). Suppressing further size-change logs.`);
162423
- }
162943
+ sessionLog("global", `models-dev-cache: API layer loaded ${map2.size} model metadata entries (was ${previousSize})`);
162424
162944
  }
162425
162945
  } catch (error51) {
162426
162946
  sessionLog("global", "models-dev-cache: API refresh failed:", error51 instanceof Error ? error51.message : String(error51));
@@ -162440,13 +162960,12 @@ function getModelsDevContextLimit(providerID, modelID) {
162440
162960
  }
162441
162961
  return fileCache.get(key)?.limit;
162442
162962
  }
162443
- var RELOAD_INTERVAL_MS, apiCache = null, apiLoadedAt = 0, recentlySeenApiSizes, oscillationLogged = false, fileCache = null, fileLastAttempt = 0;
162963
+ var RELOAD_INTERVAL_MS, apiCache = null, apiLoadedAt = 0, fileCache = null, fileLastAttempt = 0;
162444
162964
  var init_models_dev_cache = __esm(() => {
162445
162965
  init_data_path();
162446
162966
  init_jsonc_parser();
162447
162967
  init_logger();
162448
162968
  RELOAD_INTERVAL_MS = 5 * 60 * 1000;
162449
- recentlySeenApiSizes = new Set;
162450
162969
  });
162451
162970
 
162452
162971
  // src/shared/rpc-notifications.ts
@@ -164340,7 +164859,7 @@ function peekNoteNudgeText(db, sessionId, currentUserMessageId, projectIdentity,
164340
164859
  const deliveredAt = getPersistedNoteNudgeDeliveredAt(db, sessionId);
164341
164860
  if (deliveredAt > 0 && Date.now() - deliveredAt < NOTE_NUDGE_COOLDOWN_MS) {
164342
164861
  sessionLog(sessionId, `note-nudge: suppressing — last delivered ${Math.round((Date.now() - deliveredAt) / 1000)}s ago (cooldown ${NOTE_NUDGE_COOLDOWN_MS / 60000}m)`);
164343
- clearPersistedNoteNudge(db, sessionId);
164862
+ clearNoteNudgeTriggerOnly(db, sessionId);
164344
164863
  return null;
164345
164864
  }
164346
164865
  const notes = getSessionNotes(db, sessionId);
@@ -164348,7 +164867,7 @@ function peekNoteNudgeText(db, sessionId, currentUserMessageId, projectIdentity,
164348
164867
  const totalCount = notes.length + readySmartNotes.length;
164349
164868
  if (totalCount === 0) {
164350
164869
  sessionLog(sessionId, "note-nudge: triggerPending but no notes found, skipping");
164351
- clearPersistedNoteNudge(db, sessionId);
164870
+ clearNoteNudgeTriggerOnly(db, sessionId);
164352
164871
  return null;
164353
164872
  }
164354
164873
  const lastReadAt = getNoteLastReadAt(db, sessionId);
@@ -164356,7 +164875,7 @@ function peekNoteNudgeText(db, sessionId, currentUserMessageId, projectIdentity,
164356
164875
  const mostRecentNoteActivity = maxNoteActivityTime([...notes, ...readySmartNotes]);
164357
164876
  if (mostRecentNoteActivity > 0 && lastReadAt > mostRecentNoteActivity) {
164358
164877
  sessionLog(sessionId, `note-nudge: suppressing — agent ran ctx_note(read) at ${new Date(lastReadAt).toISOString()} and the read is still visible; no new notes since ${new Date(mostRecentNoteActivity).toISOString()}`);
164359
- clearPersistedNoteNudge(db, sessionId);
164878
+ clearNoteNudgeTriggerOnly(db, sessionId);
164360
164879
  return null;
164361
164880
  }
164362
164881
  }
@@ -164381,22 +164900,28 @@ function maxNoteActivityTime(notes) {
164381
164900
  return max;
164382
164901
  }
164383
164902
  function markNoteNudgeDelivered(db, sessionId, text, messageId) {
164384
- setPersistedDeliveredNoteNudge(db, sessionId, messageId ? text : "", messageId ?? "");
164385
- recordNoteNudgeDeliveryTime(sessionId);
164386
- sessionLog(sessionId, messageId ? `note-nudge: marked delivered, sticky anchor=${messageId}` : "note-nudge: marked delivered without anchor");
164903
+ if (!messageId) {
164904
+ clearNoteNudgeTriggerAndCooldown(db, sessionId);
164905
+ sessionLog(sessionId, "note-nudge: marked delivered without anchor");
164906
+ return { ok: true, kind: "already-present" };
164907
+ }
164908
+ const outcome = deliverNoteNudgeAtomic(db, sessionId, messageId, text);
164909
+ if (outcome.ok) {
164910
+ recordNoteNudgeDeliveryTime(sessionId);
164911
+ }
164912
+ sessionLog(sessionId, outcome.ok ? `note-nudge: marked delivered, sticky anchor=${messageId} (${outcome.kind})` : `note-nudge: delivery not persisted for anchor=${messageId} (${outcome.kind})`);
164913
+ return outcome;
164387
164914
  }
164388
- function getStickyNoteNudge(db, sessionId) {
164389
- const state = getPersistedNoteNudge(db, sessionId);
164390
- if (!state.stickyText || !state.stickyMessageId)
164391
- return null;
164392
- return { text: state.stickyText, messageId: state.stickyMessageId };
164915
+ function clearNoteNudgeTriggerAndCooldown(db, sessionId) {
164916
+ db.prepare("UPDATE session_meta SET note_nudge_trigger_pending = 0, note_nudge_trigger_message_id = '' WHERE session_id = ?").run(sessionId);
164917
+ lastDeliveredAt.delete(sessionId);
164393
164918
  }
164394
- function clearNoteNudgeState(db, sessionId, options) {
164395
- if (options?.persist !== false) {
164396
- clearPersistedNoteNudge(db, sessionId);
164397
- }
164919
+ function resetNoteNudgeCooldownOnly(sessionId) {
164398
164920
  lastDeliveredAt.delete(sessionId);
164399
164921
  }
164922
+ function clearNoteNudgeTriggerOnly(db, sessionId) {
164923
+ db.prepare("UPDATE session_meta SET note_nudge_trigger_pending = 0, note_nudge_trigger_message_id = '' WHERE session_id = ?").run(sessionId);
164924
+ }
164400
164925
  var NOTE_NUDGE_COOLDOWN_MS, lastDeliveredAt;
164401
164926
  var init_note_nudger = __esm(() => {
164402
164927
  init_storage_meta_persisted();
@@ -164668,7 +165193,8 @@ var init_historian_state_file = __esm(() => {
164668
165193
 
164669
165194
  // src/features/magic-context/memory/embedding-backfill.ts
164670
165195
  async function ensureMemoryEmbeddings(args) {
164671
- if (!isEmbeddingEnabled()) {
165196
+ const snapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
165197
+ if (!snapshot?.enabled) {
164672
165198
  return args.existingEmbeddings;
164673
165199
  }
164674
165200
  const missingMemories = args.memories.filter((memory) => !args.existingEmbeddings.has(memory.id));
@@ -164676,19 +165202,25 @@ async function ensureMemoryEmbeddings(args) {
164676
165202
  return args.existingEmbeddings;
164677
165203
  }
164678
165204
  try {
164679
- const embeddings = await embedBatch(missingMemories.map((memory) => memory.content));
164680
- const modelId = getEmbeddingModelId();
165205
+ const result = await embedBatchForProject(args.projectIdentity, missingMemories.map((memory) => memory.content));
165206
+ if (!result) {
165207
+ return args.existingEmbeddings;
165208
+ }
164681
165209
  const staged = new Map;
164682
165210
  args.db.transaction(() => {
164683
165211
  for (const [index, memory] of missingMemories.entries()) {
164684
- const embedding = embeddings[index];
165212
+ const embedding = result.vectors[index];
164685
165213
  if (!embedding) {
164686
165214
  continue;
164687
165215
  }
164688
- saveEmbedding(args.db, memory.id, embedding, modelId);
165216
+ saveEmbedding(args.db, memory.id, embedding, result.modelId);
164689
165217
  staged.set(memory.id, embedding);
164690
165218
  }
164691
165219
  })();
165220
+ const currentSnapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
165221
+ if (!currentSnapshot || currentSnapshot.generation !== result.generation) {
165222
+ return args.existingEmbeddings;
165223
+ }
164692
165224
  for (const [id, embedding] of staged) {
164693
165225
  args.existingEmbeddings.set(id, embedding);
164694
165226
  }
@@ -164732,17 +165264,17 @@ function promoteSessionFactsToMemory(db, sessionId, projectPath, facts) {
164732
165264
  expiresAt: resolveExpiresAt(fact.category)
164733
165265
  };
164734
165266
  const memory = insertMemory(db, memoryInput);
164735
- embedAndStoreMemory(db, sessionId, memory.id, memory.content);
165267
+ embedAndStoreMemory(db, sessionId, projectPath, memory.id, memory.content);
164736
165268
  } catch (error51) {
164737
165269
  sessionLog(sessionId, `memory promotion failed for fact "${fact.content.slice(0, 60)}":`, error51);
164738
165270
  }
164739
165271
  }
164740
165272
  }
164741
- async function embedAndStoreMemory(db, sessionId, memoryId, content) {
165273
+ async function embedAndStoreMemory(db, sessionId, projectPath, memoryId, content) {
164742
165274
  try {
164743
- const embedding = await embedText(content);
164744
- if (embedding) {
164745
- saveEmbedding(db, memoryId, embedding, getEmbeddingModelId());
165275
+ const result = await embedTextForProject(projectPath, content);
165276
+ if (result) {
165277
+ saveEmbedding(db, memoryId, result.vector, result.modelId);
164746
165278
  }
164747
165279
  } catch (error51) {
164748
165280
  sessionLog(sessionId, `memory embedding failed for memory ${memoryId}:`, error51);
@@ -165344,6 +165876,7 @@ No new compartments or facts were written. Check the historian model/output and
165344
165876
  }
165345
165877
  deps.onCompartmentStatePublished?.(sessionId);
165346
165878
  if (deps.directory && deps.memoryEnabled !== false && deps.autoPromote !== false) {
165879
+ await deps.ensureProjectRegistered?.(deps.directory, db);
165347
165880
  promoteSessionFactsToMemory(db, sessionId, resolveProjectIdentity(deps.directory), validatedPass.facts ?? []);
165348
165881
  }
165349
165882
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd);
@@ -165446,7 +165979,33 @@ async function executeContextRecompInternal(deps) {
165446
165979
  let currentStateFilePath;
165447
165980
  updateSessionMeta(db, sessionId, { compartmentInProgress: true });
165448
165981
  try {
165449
- let promoteAndFinalize = function(reason) {
165982
+ const protectedTailStart = getProtectedTailStartOrdinal(sessionId);
165983
+ if (protectedTailStart <= 1) {
165984
+ return `## Magic Recomp
165985
+
165986
+ No eligible raw history exists before the protected tail, so nothing was rebuilt.`;
165987
+ }
165988
+ const rawMessageCount = getRawSessionMessageCount(sessionId);
165989
+ const parentSessionResponse = await client.session.get({ path: { id: sessionId } }).catch(() => null);
165990
+ const parentSession = normalizeSDKResponse(parentSessionResponse, null, { preferResponseOnMissingData: true });
165991
+ const sessionDirectory = parentSession?.directory ?? directory;
165992
+ const projectPath = directory ? resolveProjectIdentity(directory) : undefined;
165993
+ const memories = projectPath ? getMemoriesByProject(db, projectPath, ["active", "permanent"]) : [];
165994
+ const memoryBlock = renderMemoryBlock(memories) ?? undefined;
165995
+ const existingStaging = getRecompStaging(db, sessionId);
165996
+ let candidateCompartments = existingStaging?.compartments ?? [];
165997
+ let candidateFacts = existingStaging?.facts ?? [];
165998
+ let offset = existingStaging ? existingStaging.lastEndMessage + 1 : 1;
165999
+ let passCount = existingStaging?.passCount ?? 0;
166000
+ let currentTokenBudget = historianChunkTokens;
166001
+ let passAttempt = 1;
166002
+ const resumed = existingStaging !== null;
166003
+ if (resumed) {
166004
+ await sendIgnoredMessage(client, sessionId, `## Magic Recomp — Resumed
166005
+
166006
+ Found ${existingStaging.compartments.length} staged compartment(s) from ${existingStaging.passCount} previous pass(es), covering messages 1-${existingStaging.lastEndMessage}. Resuming from message ${offset}.`, notifParams());
166007
+ }
166008
+ async function promoteAndFinalize(reason) {
165450
166009
  if (passCount === 0 || candidateCompartments.length === 0)
165451
166010
  return null;
165452
166011
  const mergedError = validateStoredCompartments(candidateCompartments);
@@ -165462,6 +166021,7 @@ async function executeContextRecompInternal(deps) {
165462
166021
  }
165463
166022
  deps.onCompartmentStatePublished?.(sessionId);
165464
166023
  if (deps.directory && deps.memoryEnabled !== false && deps.autoPromote !== false) {
166024
+ await deps.ensureProjectRegistered?.(deps.directory, db);
165465
166025
  promoteSessionFactsToMemory(db, sessionId, resolveProjectIdentity(deps.directory), promoted2.facts);
165466
166026
  }
165467
166027
  const lastCompartmentEnd2 = promoted2.compartments[promoted2.compartments.length - 1]?.endMessage ?? 0;
@@ -165481,37 +166041,11 @@ async function executeContextRecompInternal(deps) {
165481
166041
  `Remaining messages ${lastCompartmentEnd2 + 1}-${protectedTailStart - 1} were not rebuilt (${reason}).`
165482
166042
  ].join(`
165483
166043
  `);
165484
- };
165485
- const protectedTailStart = getProtectedTailStartOrdinal(sessionId);
165486
- if (protectedTailStart <= 1) {
165487
- return `## Magic Recomp
165488
-
165489
- No eligible raw history exists before the protected tail, so nothing was rebuilt.`;
165490
- }
165491
- const rawMessageCount = getRawSessionMessageCount(sessionId);
165492
- const parentSessionResponse = await client.session.get({ path: { id: sessionId } }).catch(() => null);
165493
- const parentSession = normalizeSDKResponse(parentSessionResponse, null, { preferResponseOnMissingData: true });
165494
- const sessionDirectory = parentSession?.directory ?? directory;
165495
- const projectPath = directory ? resolveProjectIdentity(directory) : undefined;
165496
- const memories = projectPath ? getMemoriesByProject(db, projectPath, ["active", "permanent"]) : [];
165497
- const memoryBlock = renderMemoryBlock(memories) ?? undefined;
165498
- const existingStaging = getRecompStaging(db, sessionId);
165499
- let candidateCompartments = existingStaging?.compartments ?? [];
165500
- let candidateFacts = existingStaging?.facts ?? [];
165501
- let offset = existingStaging ? existingStaging.lastEndMessage + 1 : 1;
165502
- let passCount = existingStaging?.passCount ?? 0;
165503
- let currentTokenBudget = historianChunkTokens;
165504
- let passAttempt = 1;
165505
- const resumed = existingStaging !== null;
165506
- if (resumed) {
165507
- await sendIgnoredMessage(client, sessionId, `## Magic Recomp — Resumed
165508
-
165509
- Found ${existingStaging.compartments.length} staged compartment(s) from ${existingStaging.passCount} previous pass(es), covering messages 1-${existingStaging.lastEndMessage}. Resuming from message ${offset}.`, notifParams());
165510
166044
  }
165511
166045
  while (offset < protectedTailStart) {
165512
166046
  const chunk = readSessionChunk(sessionId, currentTokenBudget, offset, protectedTailStart);
165513
166047
  if (!chunk.text || chunk.messageCount === 0 || chunk.endIndex < offset) {
165514
- const promoted2 = promoteAndFinalize(`remaining messages ${offset}-${protectedTailStart - 1} were too few or all noise to form a historian chunk`);
166048
+ const promoted2 = await promoteAndFinalize(`remaining messages ${offset}-${protectedTailStart - 1} were too few or all noise to form a historian chunk`);
165515
166049
  if (promoted2) {
165516
166050
  return `## Magic Recomp — Complete
165517
166051
 
@@ -165523,7 +166057,7 @@ Recomp stopped because raw history ${offset}-${protectedTailStart - 1} could not
165523
166057
  }
165524
166058
  const chunkCoverageError = validateChunkCoverage(chunk);
165525
166059
  if (chunkCoverageError) {
165526
- const partial2 = promoteAndFinalize(`chunk could not be represented safely: ${chunkCoverageError}`);
166060
+ const partial2 = await promoteAndFinalize(`chunk could not be represented safely: ${chunkCoverageError}`);
165527
166061
  if (partial2) {
165528
166062
  return `## Magic Recomp — Partial
165529
166063
 
@@ -165584,7 +166118,7 @@ Validator result: ${validatedPass.error}`, notifParams());
165584
166118
  continue;
165585
166119
  }
165586
166120
  }
165587
- const partial2 = promoteAndFinalize(`historian failed to validate messages ${chunk.startIndex}-${chunk.endIndex}: ${validatedPass.error}`);
166121
+ const partial2 = await promoteAndFinalize(`historian failed to validate messages ${chunk.startIndex}-${chunk.endIndex}: ${validatedPass.error}`);
165588
166122
  if (partial2) {
165589
166123
  return `## Magic Recomp — Partial
165590
166124
 
@@ -165607,7 +166141,7 @@ Nothing was written.`;
165607
166141
  saveRecompStagingPass(db, sessionId, passCount, candidateCompartments, candidateFacts);
165608
166142
  const nextOffset = (validatedPass.compartments?.[validatedPass.compartments.length - 1]?.endMessage ?? chunk.endIndex) + 1;
165609
166143
  if (nextOffset <= offset) {
165610
- const partial2 = promoteAndFinalize(`historian made no forward progress after messages ${chunk.startIndex}-${chunk.endIndex}`);
166144
+ const partial2 = await promoteAndFinalize(`historian made no forward progress after messages ${chunk.startIndex}-${chunk.endIndex}`);
165611
166145
  if (partial2) {
165612
166146
  return `## Magic Recomp — Partial
165613
166147
 
@@ -165806,7 +166340,7 @@ __export(exports_tui_config, {
165806
166340
  ensureTuiPluginEntry: () => ensureTuiPluginEntry
165807
166341
  });
165808
166342
  import { existsSync as existsSync16, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
165809
- import { dirname as dirname8, join as join27 } from "node:path";
166343
+ import { dirname as dirname9, join as join27 } from "node:path";
165810
166344
  function isMagicContextEntry(entry) {
165811
166345
  if (!entry)
165812
166346
  return false;
@@ -165852,7 +166386,7 @@ function ensureTuiPluginEntry() {
165852
166386
  plugins.push(PLUGIN_ENTRY);
165853
166387
  }
165854
166388
  config2.plugin = plugins;
165855
- mkdirSync9(dirname8(configPath), { recursive: true });
166389
+ mkdirSync9(dirname9(configPath), { recursive: true });
165856
166390
  writeFileSync8(configPath, `${import_comment_json4.stringify(config2, null, 2)}
165857
166391
  `);
165858
166392
  log(`[magic-context] updated TUI plugin entry in ${configPath}`);
@@ -165963,6 +166497,42 @@ function loadConfigFile(configPath) {
165963
166497
  return null;
165964
166498
  }
165965
166499
  }
166500
+ function loadConfigFileDetailed(configPath, source) {
166501
+ if (!existsSync3(configPath)) {
166502
+ return null;
166503
+ }
166504
+ let rawText;
166505
+ try {
166506
+ rawText = readFileSync3(configPath, "utf-8");
166507
+ } catch (error51) {
166508
+ return {
166509
+ config: {},
166510
+ warnings: [
166511
+ `${configPath}: failed to read config: ${error51 instanceof Error ? error51.message : String(error51)}`
166512
+ ],
166513
+ outcome: "project-file-io-error",
166514
+ source
166515
+ };
166516
+ }
166517
+ try {
166518
+ const substituted = substituteConfigVariables({ text: rawText, configPath });
166519
+ return {
166520
+ config: parseJsonc(substituted.text),
166521
+ warnings: substituted.warnings.map((w) => `${configPath}: ${w}`),
166522
+ outcome: substituted.warnings.length > 0 ? "substitution-failure" : "ok",
166523
+ source
166524
+ };
166525
+ } catch (error51) {
166526
+ return {
166527
+ config: {},
166528
+ warnings: [
166529
+ `${configPath}: failed to load config: ${error51 instanceof Error ? error51.message : String(error51)}`
166530
+ ],
166531
+ outcome: "project-file-parse-error",
166532
+ source
166533
+ };
166534
+ }
166535
+ }
165966
166536
  function deepMergeRawConfig(base, override) {
165967
166537
  const result = { ...base };
165968
166538
  for (const key of Object.keys(override)) {
@@ -166057,7 +166627,7 @@ function migrateLegacyExperimental(rawConfig, warnings) {
166057
166627
  patched.dreamer = dreamer;
166058
166628
  return patched;
166059
166629
  }
166060
- function parsePluginConfig(rawConfig) {
166630
+ function parsePluginConfig(rawConfig, recoveredTopLevelKeys = []) {
166061
166631
  const preMigrationWarnings = [];
166062
166632
  const migrated = migrateLegacyExperimental(rawConfig, preMigrationWarnings);
166063
166633
  const parsed = MagicContextConfigSchema.safeParse(migrated);
@@ -166082,6 +166652,7 @@ function parsePluginConfig(rawConfig) {
166082
166652
  }
166083
166653
  const patched = { ...rawConfig };
166084
166654
  for (const key of errorPaths) {
166655
+ recoveredTopLevelKeys.push(key);
166085
166656
  const isAgentConfig = key === "historian" || key === "dreamer" || key === "sidekick";
166086
166657
  if (isAgentConfig) {
166087
166658
  delete patched[key];
@@ -166152,6 +166723,102 @@ function loadPluginConfig(directory) {
166152
166723
  }
166153
166724
  return config2;
166154
166725
  }
166726
+ function collectEmptyStringPaths(value, prefix = "") {
166727
+ if (typeof value === "string") {
166728
+ return value === "" && prefix ? [prefix] : [];
166729
+ }
166730
+ if (Array.isArray(value) || value === null || typeof value !== "object") {
166731
+ return [];
166732
+ }
166733
+ const paths = [];
166734
+ for (const [key, child] of Object.entries(value)) {
166735
+ const nextPrefix = prefix ? `${prefix}.${key}` : key;
166736
+ paths.push(...collectEmptyStringPaths(child, nextPrefix));
166737
+ }
166738
+ return paths;
166739
+ }
166740
+ function bindSubstitutionFailures(loaded) {
166741
+ if (!loaded || loaded.warnings.length === 0 || loaded.outcome !== "substitution-failure") {
166742
+ return [];
166743
+ }
166744
+ const emptyPaths = collectEmptyStringPaths(loaded.config);
166745
+ return loaded.warnings.map((message) => {
166746
+ const matchedPath = emptyPaths.find((path) => {
166747
+ const tail = path.split(".").at(-1) ?? path;
166748
+ return message.includes(path) || message.toLowerCase().includes(tail.toLowerCase());
166749
+ });
166750
+ return { keyPath: matchedPath ?? "<unknown>", source: loaded.source, message };
166751
+ });
166752
+ }
166753
+ function combinedOutcome(args) {
166754
+ const sourceOutcomes = Object.values(args.sources);
166755
+ if (sourceOutcomes.includes("project-file-parse-error"))
166756
+ return "project-file-parse-error";
166757
+ if (sourceOutcomes.includes("project-file-io-error"))
166758
+ return "project-file-io-error";
166759
+ if (args.recoveredTopLevelKeys.length > 0)
166760
+ return "schema-recovery";
166761
+ if (args.substitutionFailures.length > 0)
166762
+ return "substitution-failure";
166763
+ return "ok";
166764
+ }
166765
+ function loadPluginConfigDetailed(directory) {
166766
+ const userDetected = detectConfigFile(getUserConfigBasePath());
166767
+ const rootDetected = detectConfigFile(join(directory, CONFIG_FILE_BASENAME));
166768
+ const dotOpenCodeDetected = detectConfigFile(getProjectConfigBasePath(directory));
166769
+ const projectDetected = rootDetected.format !== "none" ? rootDetected : dotOpenCodeDetected;
166770
+ const userLoaded = userDetected.format === "none" ? null : loadConfigFileDetailed(userDetected.path, "user");
166771
+ const projectLoaded = projectDetected.format === "none" ? null : loadConfigFileDetailed(projectDetected.path, "project");
166772
+ const allWarnings = [];
166773
+ let mergedRaw = {};
166774
+ if (userLoaded) {
166775
+ allWarnings.push(...userLoaded.warnings.map((w) => `[user config] ${w}`));
166776
+ mergedRaw = deepMergeRawConfig(mergedRaw, userLoaded.config);
166777
+ }
166778
+ if (projectLoaded) {
166779
+ allWarnings.push(...projectLoaded.warnings.map((w) => `[project config] ${w}`));
166780
+ const projectRaw = { ...projectLoaded.config };
166781
+ const strippedUserOnlyFields = getProjectUserOnlyFields(projectRaw);
166782
+ if (strippedUserOnlyFields.length > 0) {
166783
+ for (const key of strippedUserOnlyFields) {
166784
+ delete projectRaw[key];
166785
+ }
166786
+ allWarnings.push(`[project config] Ignoring ${strippedUserOnlyFields.join(", ")} from project config (security: these settings only honor user-level config)`);
166787
+ }
166788
+ mergedRaw = deepMergeRawConfig(mergedRaw, projectRaw);
166789
+ }
166790
+ const recoveredTopLevelKeys = [];
166791
+ const config2 = parsePluginConfig(mergedRaw, recoveredTopLevelKeys);
166792
+ if (config2.configWarnings?.length) {
166793
+ allWarnings.push(...config2.configWarnings.map((w) => {
166794
+ if (userLoaded && projectLoaded)
166795
+ return `[config] ${w}`;
166796
+ if (userLoaded)
166797
+ return `[user config] ${w}`;
166798
+ return `[project config] ${w}`;
166799
+ }));
166800
+ }
166801
+ if (allWarnings.length > 0) {
166802
+ config2.configWarnings = allWarnings;
166803
+ } else if ("configWarnings" in config2) {
166804
+ config2.configWarnings = undefined;
166805
+ }
166806
+ const substitutionFailures = [
166807
+ ...bindSubstitutionFailures(userLoaded),
166808
+ ...bindSubstitutionFailures(projectLoaded)
166809
+ ];
166810
+ const sources = {
166811
+ userConfig: userLoaded?.outcome ?? "ok",
166812
+ projectConfig: projectLoaded?.outcome ?? "ok"
166813
+ };
166814
+ return {
166815
+ config: config2,
166816
+ loadOutcome: combinedOutcome({ sources, substitutionFailures, recoveredTopLevelKeys }),
166817
+ sources,
166818
+ substitutionFailures,
166819
+ recoveredTopLevelKeys
166820
+ };
166821
+ }
166155
166822
 
166156
166823
  // src/features/builtin-commands/commands.ts
166157
166824
  function getMagicContextBuiltinCommands() {
@@ -166545,6 +167212,9 @@ function buildDreamTaskPrompt(task, args) {
166545
167212
  }
166546
167213
  }
166547
167214
 
167215
+ // src/index.ts
167216
+ init_project_identity();
167217
+
166548
167218
  // src/features/magic-context/sidekick/agent.ts
166549
167219
  init_shared();
166550
167220
  init_assistant_message_extractor();
@@ -167425,10 +168095,10 @@ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "node:f
167425
168095
  import { homedir as homedir8 } from "node:os";
167426
168096
  import { join as join11 } from "node:path";
167427
168097
  var overrideAvailability = null;
167428
- function parseConfig(path4) {
167429
- if (!existsSync9(path4))
168098
+ function parseConfig(path5) {
168099
+ if (!existsSync9(path5))
167430
168100
  return null;
167431
- return import_comment_json3.parse(readFileSync8(path4, "utf-8"));
168101
+ return import_comment_json3.parse(readFileSync8(path5, "utf-8"));
167432
168102
  }
167433
168103
  function entryMatchesAft(entry) {
167434
168104
  const value = Array.isArray(entry) ? entry[0] : entry;
@@ -167456,9 +168126,9 @@ function getAftAvailability() {
167456
168126
  const piPaths = [join11(home, ".pi", "agent", "settings.json")];
167457
168127
  const checkedPaths = [...opencodePaths, ...piPaths];
167458
168128
  let opencode = false;
167459
- for (const path4 of opencodePaths) {
168129
+ for (const path5 of opencodePaths) {
167460
168130
  try {
167461
- const config2 = parseConfig(path4);
168131
+ const config2 = parseConfig(path5);
167462
168132
  if (hasAftAtKeys(config2, ["plugin", "plugins", "mcp", "mcp_servers"])) {
167463
168133
  opencode = true;
167464
168134
  break;
@@ -167466,9 +168136,9 @@ function getAftAvailability() {
167466
168136
  } catch {}
167467
168137
  }
167468
168138
  let pi = false;
167469
- for (const path4 of piPaths) {
168139
+ for (const path5 of piPaths) {
167470
168140
  try {
167471
- const config2 = parseConfig(path4);
168141
+ const config2 = parseConfig(path5);
167472
168142
  if (hasAftAtKeys(config2, ["packages", "extensions"])) {
167473
168143
  pi = true;
167474
168144
  break;
@@ -168866,7 +169536,7 @@ init_logger();
168866
169536
  import { execFile } from "node:child_process";
168867
169537
  import { promisify } from "node:util";
168868
169538
  var execFileAsync = promisify(execFile);
168869
- var GIT_TIMEOUT_MS = 1e4;
169539
+ var GIT_TIMEOUT_MS2 = 1e4;
168870
169540
  var DEFAULT_MAX_COMMITS = 5000;
168871
169541
  var RECORD_SEPARATOR = "\x1E";
168872
169542
  var FIELD_SEPARATOR = "\x1F";
@@ -168886,7 +169556,7 @@ async function readGitCommits(directory, options = {}) {
168886
169556
  try {
168887
169557
  const result = await execFileAsync("git", args, {
168888
169558
  cwd: directory,
168889
- timeout: GIT_TIMEOUT_MS,
169559
+ timeout: GIT_TIMEOUT_MS2,
168890
169560
  maxBuffer: 32 * 1024 * 1024,
168891
169561
  encoding: "utf8"
168892
169562
  });
@@ -168944,79 +169614,7 @@ ${body}` : subject;
168944
169614
  // src/features/magic-context/git-commits/indexer.ts
168945
169615
  init_logger();
168946
169616
  init_embedding();
168947
-
168948
- // src/features/magic-context/git-commits/storage-git-commit-embeddings.ts
168949
- var saveStatements = new WeakMap;
168950
- var loadProjectStatements = new WeakMap;
168951
- var loadUnembeddedStatements = new WeakMap;
168952
- var countEmbeddedStatements = new WeakMap;
168953
- var clearProjectStatements = new WeakMap;
168954
- function getSaveStatement(db) {
168955
- let stmt = saveStatements.get(db);
168956
- if (!stmt) {
168957
- stmt = db.prepare(`INSERT INTO git_commit_embeddings (sha, embedding, model_id, created_at)
168958
- VALUES (?, ?, ?, ?)
168959
- ON CONFLICT(sha) DO UPDATE SET
168960
- embedding = excluded.embedding,
168961
- model_id = excluded.model_id,
168962
- created_at = excluded.created_at`);
168963
- saveStatements.set(db, stmt);
168964
- }
168965
- return stmt;
168966
- }
168967
- function getLoadProjectStatement(db) {
168968
- let stmt = loadProjectStatements.get(db);
168969
- if (!stmt) {
168970
- stmt = db.prepare(`SELECT e.sha AS sha, e.embedding AS embedding, e.model_id AS model_id
168971
- FROM git_commit_embeddings e
168972
- JOIN git_commits c ON c.sha = e.sha
168973
- WHERE c.project_path = ?`);
168974
- loadProjectStatements.set(db, stmt);
168975
- }
168976
- return stmt;
168977
- }
168978
- function getLoadUnembeddedStatement(db) {
168979
- let stmt = loadUnembeddedStatements.get(db);
168980
- if (!stmt) {
168981
- stmt = db.prepare(`SELECT c.sha AS sha, c.message AS message
168982
- FROM git_commits c
168983
- LEFT JOIN git_commit_embeddings e ON c.sha = e.sha
168984
- WHERE c.project_path = ? AND e.sha IS NULL
168985
- ORDER BY c.committed_at DESC
168986
- LIMIT ?`);
168987
- loadUnembeddedStatements.set(db, stmt);
168988
- }
168989
- return stmt;
168990
- }
168991
- function getCountEmbeddedStatement(db) {
168992
- let stmt = countEmbeddedStatements.get(db);
168993
- if (!stmt) {
168994
- stmt = db.prepare(`SELECT COUNT(*) AS count FROM git_commit_embeddings e
168995
- JOIN git_commits c ON c.sha = e.sha WHERE c.project_path = ?`);
168996
- countEmbeddedStatements.set(db, stmt);
168997
- }
168998
- return stmt;
168999
- }
169000
- function saveCommitEmbedding(db, sha, embedding, modelId) {
169001
- const bytes = new Uint8Array(embedding.buffer, embedding.byteOffset, embedding.byteLength);
169002
- getSaveStatement(db).run(sha, bytes, modelId, Date.now());
169003
- }
169004
- function loadProjectCommitEmbeddings(db, projectPath) {
169005
- const rows = getLoadProjectStatement(db).all(projectPath);
169006
- const map2 = new Map;
169007
- for (const row of rows) {
169008
- const buffer2 = row.embedding.buffer.slice(row.embedding.byteOffset, row.embedding.byteOffset + row.embedding.byteLength);
169009
- map2.set(row.sha, new Float32Array(buffer2));
169010
- }
169011
- return map2;
169012
- }
169013
- function loadUnembeddedCommits(db, projectPath, limit) {
169014
- return getLoadUnembeddedStatement(db).all(projectPath, limit);
169015
- }
169016
- function countEmbeddedCommits(db, projectPath) {
169017
- const row = getCountEmbeddedStatement(db).get(projectPath);
169018
- return row?.count ?? 0;
169019
- }
169617
+ init_storage_git_commit_embeddings();
169020
169618
 
169021
169619
  // src/features/magic-context/git-commits/storage-git-commits.ts
169022
169620
  init_logger();
@@ -169143,7 +169741,7 @@ var EMBED_MAX_PER_SWEEP = 500;
169143
169741
  var EMBED_SWEEP_MAX_WALL_CLOCK_MS = 5 * 60 * 1000;
169144
169742
  var indexInProgress = new Set;
169145
169743
  var embedInProgress = new Set;
169146
- async function indexCommitsForProject(db, projectPath, directory, embeddingConfig2, options) {
169744
+ async function indexCommitsForProject(db, projectPath, directory, options) {
169147
169745
  const result = {
169148
169746
  scanned: 0,
169149
169747
  inserted: 0,
@@ -169174,22 +169772,24 @@ async function indexCommitsForProject(db, projectPath, directory, embeddingConfi
169174
169772
  result.inserted = upsert.inserted;
169175
169773
  result.updated = upsert.updated;
169176
169774
  result.evicted = enforceProjectCap(db, projectPath, options.maxCommits);
169177
- if (options.skipEmbed || !isEmbeddingEnabled()) {
169178
- log(`[git-commits] indexed ${projectPath}: scanned=${result.scanned} inserted=${result.inserted} updated=${result.updated} evicted=${result.evicted} embedded=0 (embedding skipped: skipEmbed=${options.skipEmbed === true} embeddingEnabled=${isEmbeddingEnabled()})`);
169775
+ const snapshot = getProjectEmbeddingSnapshot(projectPath);
169776
+ if (options.skipEmbed || !snapshot?.gitCommitEnabled) {
169777
+ log(`[git-commits] indexed ${projectPath}: scanned=${result.scanned} inserted=${result.inserted} updated=${result.updated} evicted=${result.evicted} embedded=0 (embedding skipped: skipEmbed=${options.skipEmbed === true} gitCommitEnabled=${snapshot?.gitCommitEnabled === true})`);
169179
169778
  return result;
169180
169779
  }
169181
- result.embedded = await embedUnembeddedCommits(db, projectPath, embeddingConfig2);
169780
+ result.embedded = await embedUnembeddedCommits(db, projectPath);
169182
169781
  log(`[git-commits] indexed ${projectPath}: scanned=${result.scanned} inserted=${result.inserted} updated=${result.updated} evicted=${result.evicted} embedded=${result.embedded}`);
169183
169782
  return result;
169184
169783
  } finally {
169185
169784
  indexInProgress.delete(projectPath);
169186
169785
  }
169187
169786
  }
169188
- async function embedUnembeddedCommits(db, projectPath, _config) {
169787
+ async function embedUnembeddedCommits(db, projectPath) {
169189
169788
  if (embedInProgress.has(projectPath)) {
169190
169789
  return 0;
169191
169790
  }
169192
- if (!isEmbeddingEnabled()) {
169791
+ const snapshot = getProjectEmbeddingSnapshot(projectPath);
169792
+ if (!snapshot?.gitCommitEnabled) {
169193
169793
  return 0;
169194
169794
  }
169195
169795
  embedInProgress.add(projectPath);
@@ -169203,16 +169803,15 @@ async function embedUnembeddedCommits(db, projectPath, _config) {
169203
169803
  break;
169204
169804
  let embeddedThisBatch = 0;
169205
169805
  try {
169206
- const embeddings = await embedBatch(rows.map((row) => row.message));
169207
- const modelId = getEmbeddingModelId();
169208
- if (modelId === "off")
169806
+ const result = await embedBatchForProject(projectPath, rows.map((row) => row.message));
169807
+ if (!result)
169209
169808
  break;
169210
169809
  db.transaction(() => {
169211
169810
  for (const [index, row] of rows.entries()) {
169212
- const embedding = embeddings[index];
169811
+ const embedding = result.vectors[index];
169213
169812
  if (!embedding)
169214
169813
  continue;
169215
- saveCommitEmbedding(db, row.sha, embedding, modelId);
169814
+ saveCommitEmbedding(db, row.sha, embedding, result.modelId);
169216
169815
  embeddedThisBatch += 1;
169217
169816
  }
169218
169817
  })();
@@ -169238,6 +169837,7 @@ async function embedUnembeddedCommits(db, projectPath, _config) {
169238
169837
  // src/features/magic-context/git-commits/search-git-commits.ts
169239
169838
  init_logger();
169240
169839
  init_storage_memory_fts();
169840
+ init_storage_git_commit_embeddings();
169241
169841
  var ftsStatements = new WeakMap;
169242
169842
  var ftsPlainStatements = new WeakMap;
169243
169843
  var getBySHAStatements = new WeakMap;
@@ -169368,19 +169968,25 @@ function searchGitCommitsSync(db, projectPath, query, options) {
169368
169968
  });
169369
169969
  return results.slice(0, options.limit);
169370
169970
  }
169971
+
169972
+ // src/features/magic-context/git-commits/index.ts
169973
+ init_storage_git_commit_embeddings();
169974
+
169371
169975
  // src/plugin/dream-timer.ts
169372
169976
  init_embedding();
169373
- init_project_identity();
169374
169977
  init_logger();
169375
169978
  init_resolve_fallbacks();
169376
169979
  await init_storage();
169377
169980
  var DREAM_TIMER_INTERVAL_MS = 15 * 60 * 1000;
169378
169981
  var activeTimer = null;
169379
169982
  var registeredProjects = new Map;
169380
- function startDreamScheduleTimer(args) {
169983
+ async function startDreamScheduleTimer(args) {
169984
+ const db = openDatabase();
169985
+ await args.ensureRegistered(args.directory, db);
169986
+ const snapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
169381
169987
  const dreamingEnabled = Boolean(args.dreamerConfig?.enabled && args.dreamerConfig.schedule?.trim());
169382
- const embeddingSweepEnabled = args.memoryEnabled && args.embeddingConfig.provider !== "off";
169383
- const commitIndexingEnabled = args.gitCommitIndexing?.enabled === true;
169988
+ const embeddingSweepEnabled = snapshot?.enabled ?? false;
169989
+ const commitIndexingEnabled = snapshot?.gitCommitEnabled ?? false;
169384
169990
  if (!dreamingEnabled && !embeddingSweepEnabled && !commitIndexingEnabled) {
169385
169991
  return;
169386
169992
  }
@@ -169412,45 +170018,43 @@ function startDreamScheduleTimer(args) {
169412
170018
  }
169413
170019
  function runTick(origin) {
169414
170020
  log(`[dreamer] timer tick (${origin}) — projects=${registeredProjects.size}`);
169415
- try {
169416
- const anyEmbeddingEnabled = Array.from(registeredProjects.values()).some((r) => r.memoryEnabled && r.embeddingConfig.provider !== "off");
169417
- if (anyEmbeddingEnabled) {
169418
- const first = registeredProjects.values().next().value;
169419
- if (first) {
169420
- embedAllUnembeddedMemories(openDatabase(), first.embeddingConfig).then((embeddedCount) => {
170021
+ (async () => {
170022
+ try {
170023
+ const db = openDatabase();
170024
+ for (const reg of registeredProjects.values()) {
170025
+ await reg.ensureRegistered(reg.directory, db);
170026
+ const memorySnapshot = getProjectEmbeddingSnapshot(reg.projectIdentity);
170027
+ if (memorySnapshot?.enabled) {
170028
+ const embeddedCount = await embedUnembeddedMemoriesForProject(db, reg.projectIdentity);
169421
170029
  if (embeddedCount > 0) {
169422
- log(`[magic-context] proactively embedded ${embeddedCount} ${embeddedCount === 1 ? "memory" : "memories"} across all projects`);
170030
+ log(`[magic-context] proactively embedded ${embeddedCount} ${embeddedCount === 1 ? "memory" : "memories"} for project ${reg.projectIdentity}`);
169423
170031
  }
169424
- }).catch((error51) => {
169425
- log("[magic-context] periodic memory embedding sweep failed:", error51);
169426
- });
170032
+ }
170033
+ await reg.ensureRegistered(reg.directory, db);
170034
+ const gitSnapshot = getProjectEmbeddingSnapshot(reg.projectIdentity);
170035
+ await sweepProject(reg, origin, db, gitSnapshot?.gitCommitEnabled === true);
169427
170036
  }
170037
+ } catch (error51) {
170038
+ log("[magic-context] timer-triggered maintenance check failed:", error51);
169428
170039
  }
169429
- for (const reg of registeredProjects.values()) {
169430
- sweepProject(reg, origin);
169431
- }
169432
- } catch (error51) {
169433
- log("[magic-context] timer-triggered maintenance check failed:", error51);
169434
- }
170040
+ })();
169435
170041
  }
169436
- async function sweepProject(reg, origin) {
170042
+ async function sweepProject(reg, origin, db = openDatabase(), gitCommitEnabled = getProjectEmbeddingSnapshot(reg.projectIdentity)?.gitCommitEnabled === true) {
169437
170043
  const dreamingEnabled = Boolean(reg.dreamerConfig?.enabled && reg.dreamerConfig.schedule?.trim());
169438
- const commitIndexingEnabled = reg.gitCommitIndexing?.enabled === true;
169439
- if (commitIndexingEnabled && reg.gitCommitIndexing) {
170044
+ if (gitCommitEnabled && reg.gitCommitIndexing) {
169440
170045
  await sweepGitCommits({
169441
170046
  directory: reg.directory,
169442
170047
  gitCommitIndexing: reg.gitCommitIndexing,
169443
- embeddingConfig: reg.embeddingConfig
170048
+ projectIdentity: reg.projectIdentity,
170049
+ db
169444
170050
  });
169445
170051
  }
169446
170052
  if (!dreamingEnabled || !reg.dreamerConfig?.schedule?.trim()) {
169447
170053
  return;
169448
170054
  }
169449
170055
  try {
169450
- const db = openDatabase();
169451
170056
  log(`[dreamer] timer tick (${origin}) ${reg.directory} — checking schedule window "${reg.dreamerConfig.schedule}"`);
169452
- const registrationIdentity = resolveProjectIdentity(reg.directory);
169453
- checkScheduleAndEnqueue(db, reg.dreamerConfig.schedule, registrationIdentity);
170057
+ checkScheduleAndEnqueue(db, reg.dreamerConfig.schedule, reg.projectIdentity);
169454
170058
  await processDreamQueue({
169455
170059
  db,
169456
170060
  client: reg.client,
@@ -169459,7 +170063,7 @@ async function sweepProject(reg, origin) {
169459
170063
  maxRuntimeMinutes: reg.dreamerConfig.max_runtime_minutes,
169460
170064
  experimentalUserMemories: reg.experimentalUserMemories,
169461
170065
  experimentalPinKeyFiles: reg.experimentalPinKeyFiles,
169462
- projectIdentity: registrationIdentity,
170066
+ projectIdentity: reg.projectIdentity,
169463
170067
  fallbackModels: resolveFallbackChain(DREAMER_AGENT, reg.dreamerConfig.fallback_models)
169464
170068
  });
169465
170069
  } catch (error51) {
@@ -169467,28 +170071,124 @@ async function sweepProject(reg, origin) {
169467
170071
  }
169468
170072
  }
169469
170073
  async function sweepGitCommits(args) {
169470
- const { directory, gitCommitIndexing, embeddingConfig: embeddingConfig2 } = args;
170074
+ const { directory, projectIdentity, db, gitCommitIndexing } = args;
169471
170075
  const startedAt = Date.now();
169472
- log(`[git-commits] sweep starting for ${directory} (sinceDays=${gitCommitIndexing.since_days} maxCommits=${gitCommitIndexing.max_commits} embedding=${embeddingConfig2.provider})`);
170076
+ log(`[git-commits] sweep starting for ${directory} (sinceDays=${gitCommitIndexing.since_days} maxCommits=${gitCommitIndexing.max_commits})`);
169473
170077
  try {
169474
- const db = openDatabase();
169475
- const projectPath = resolveProjectIdentity(directory);
169476
- const result = await indexCommitsForProject(db, projectPath, directory, embeddingConfig2, {
170078
+ const result = await indexCommitsForProject(db, projectIdentity, directory, {
169477
170079
  sinceDays: gitCommitIndexing.since_days,
169478
170080
  maxCommits: gitCommitIndexing.max_commits
169479
170081
  });
169480
170082
  let drainedEmbeddings = 0;
169481
- if (embeddingConfig2.provider !== "off" && result.embedded > 0) {
169482
- drainedEmbeddings = await embedUnembeddedCommits(db, projectPath, embeddingConfig2);
170083
+ if (result.embedded > 0) {
170084
+ drainedEmbeddings = await embedUnembeddedCommits(db, projectIdentity);
169483
170085
  }
169484
170086
  const elapsedMs = Date.now() - startedAt;
169485
- log(`[git-commits] sweep finished for ${projectPath} in ${elapsedMs}ms: scanned=${result.scanned} inserted=${result.inserted} updated=${result.updated} evicted=${result.evicted} embedded=${result.embedded} drained=${drainedEmbeddings}`);
170087
+ log(`[git-commits] sweep finished for ${projectIdentity} in ${elapsedMs}ms: scanned=${result.scanned} inserted=${result.inserted} updated=${result.updated} evicted=${result.evicted} embedded=${result.embedded} drained=${drainedEmbeddings}`);
169486
170088
  } catch (error51) {
169487
170089
  const elapsedMs = Date.now() - startedAt;
169488
170090
  log(`[git-commits] sweep failed for ${directory} after ${elapsedMs}ms: ${error51 instanceof Error ? error51.message : String(error51)}`);
169489
170091
  }
169490
170092
  }
169491
170093
 
170094
+ // src/plugin/embedding-bootstrap.ts
170095
+ init_embedding();
170096
+ init_embedding_cache();
170097
+ init_project_identity();
170098
+
170099
+ // src/plugin/embedding-bootstrap-helpers.ts
170100
+ init_embedding();
170101
+ init_logger();
170102
+ import { createHash as createHash6 } from "node:crypto";
170103
+ var EMBEDDING_AFFECTING_KEYS = new Set([
170104
+ "embedding.api_key",
170105
+ "embedding.endpoint",
170106
+ "embedding.model",
170107
+ "embedding.provider"
170108
+ ]);
170109
+ var EMBEDDING_AFFECTING_TOP_LEVEL_KEYS = new Set(["embedding", "memory", "experimental"]);
170110
+ var EMBEDDING_WARNING_TERMS = ["api_key", "endpoint", "model", "provider", "embedding"];
170111
+ var loggedFailureSignatures = new Map;
170112
+ function sha256Prefix2(value, length = 16) {
170113
+ return createHash6("sha256").update(value).digest("hex").slice(0, length);
170114
+ }
170115
+ function warningLooksEmbeddingRelated(message) {
170116
+ const lower = message.toLowerCase();
170117
+ return EMBEDDING_WARNING_TERMS.some((term) => lower.includes(term));
170118
+ }
170119
+ function isConfigLoadUntrusted(detailed) {
170120
+ if (detailed.sources.userConfig === "project-file-parse-error" || detailed.sources.userConfig === "project-file-io-error" || detailed.sources.projectConfig === "project-file-parse-error" || detailed.sources.projectConfig === "project-file-io-error") {
170121
+ return true;
170122
+ }
170123
+ for (const failure of detailed.substitutionFailures) {
170124
+ if (EMBEDDING_AFFECTING_KEYS.has(failure.keyPath)) {
170125
+ return true;
170126
+ }
170127
+ if (failure.keyPath === "<unknown>" && warningLooksEmbeddingRelated(failure.message)) {
170128
+ return true;
170129
+ }
170130
+ }
170131
+ for (const recoveredKey of detailed.recoveredTopLevelKeys) {
170132
+ if (EMBEDDING_AFFECTING_TOP_LEVEL_KEYS.has(recoveredKey)) {
170133
+ return true;
170134
+ }
170135
+ }
170136
+ return false;
170137
+ }
170138
+ function describeFailure(detailed) {
170139
+ const parts = [];
170140
+ for (const [source, outcome] of Object.entries(detailed.sources)) {
170141
+ if (outcome !== "ok") {
170142
+ parts.push(`${source}=${outcome}`);
170143
+ }
170144
+ }
170145
+ if (detailed.substitutionFailures.length > 0) {
170146
+ parts.push(`substitution=${detailed.substitutionFailures.map((failure) => `${failure.source}:${failure.keyPath}`).join(",")}`);
170147
+ }
170148
+ if (detailed.recoveredTopLevelKeys.length > 0) {
170149
+ parts.push(`recovered=${detailed.recoveredTopLevelKeys.join(",")}`);
170150
+ }
170151
+ return parts.length > 0 ? parts.join("; ") : detailed.loadOutcome;
170152
+ }
170153
+ function logConfigFailureOnce(projectIdentity, detailed) {
170154
+ const signature = sha256Prefix2(JSON.stringify({
170155
+ outcomes: detailed.sources,
170156
+ substitutions: detailed.substitutionFailures.map((failure) => `${failure.source}:${failure.keyPath}:${failure.message}`).sort(),
170157
+ recoveredTopLevelKeys: [...detailed.recoveredTopLevelKeys].sort()
170158
+ }));
170159
+ const existing = loggedFailureSignatures.get(projectIdentity) ?? new Set;
170160
+ if (existing.has(signature))
170161
+ return;
170162
+ existing.add(signature);
170163
+ loggedFailureSignatures.set(projectIdentity, existing);
170164
+ log(`[mc][embedding] config load untrusted, preserving last-known-good for ${projectIdentity} — ${describeFailure(detailed)}`);
170165
+ }
170166
+ function handleUntrustedLoad(db, projectIdentity, directory, detailed) {
170167
+ const prior = getProjectEmbeddingSnapshot(projectIdentity);
170168
+ if (prior && !prior.runtimeFingerprint.startsWith("observation:")) {
170169
+ logConfigFailureOnce(projectIdentity, detailed);
170170
+ return true;
170171
+ }
170172
+ registerProjectInObservationMode(db, projectIdentity, directory, detailed.config.embedding, describeFailure(detailed));
170173
+ return true;
170174
+ }
170175
+
170176
+ // src/plugin/embedding-bootstrap.ts
170177
+ async function ensureProjectRegisteredFromOpenCodeDirectory(directory, db) {
170178
+ const projectIdentity = resolveProjectIdentity(directory);
170179
+ invalidateProject(projectIdentity);
170180
+ const detailed = loadPluginConfigDetailed(directory);
170181
+ if (isConfigLoadUntrusted(detailed)) {
170182
+ handleUntrustedLoad(db, projectIdentity, directory, detailed);
170183
+ return;
170184
+ }
170185
+ const features = {
170186
+ memoryEnabled: detailed.config.memory.enabled,
170187
+ gitCommitEnabled: detailed.config.experimental.git_commit_indexing.enabled
170188
+ };
170189
+ registerProjectEmbeddingAndMaybeWipe(db, projectIdentity, detailed.config.embedding, features, directory);
170190
+ }
170191
+
169492
170192
  // src/plugin/event.ts
169493
170193
  function createEventHandler(args) {
169494
170194
  return async (input) => {
@@ -169987,9 +170687,9 @@ function createTagger() {
169987
170687
  // src/hooks/magic-context/hook.ts
169988
170688
  init_magic_context();
169989
170689
  init_project_identity();
170690
+ await init_storage();
169990
170691
  init_logger();
169991
170692
  init_resolve_fallbacks();
169992
- await init_storage();
169993
170693
 
169994
170694
  // src/hooks/magic-context/command-handler.ts
169995
170695
  init_shared();
@@ -170770,6 +171470,7 @@ function clearSessionTracking(sessionId) {
170770
171470
  init_overflow_detection();
170771
171471
  init_storage_meta_persisted();
170772
171472
  init_logger();
171473
+ init_models_dev_cache();
170773
171474
  await __promiseAll([
170774
171475
  init_storage(),
170775
171476
  init_compaction_marker_manager()
@@ -170872,6 +171573,7 @@ function getMessageRemovedInfo(properties) {
170872
171573
 
170873
171574
  // src/hooks/magic-context/event-handler.ts
170874
171575
  init_note_nudger();
171576
+ init_send_session_notification();
170875
171577
  await init_read_session_chunk();
170876
171578
 
170877
171579
  // src/hooks/magic-context/transform.ts
@@ -171728,6 +172430,7 @@ async function runCompartmentPhase(args) {
171728
172430
  fallbackModels: args.fallbackModels,
171729
172431
  directory: args.compartmentDirectory,
171730
172432
  fallbackModelId: args.fallbackModelId,
172433
+ ensureProjectRegistered: args.ensureProjectRegistered,
171731
172434
  getNotificationParams: args.getNotificationParams,
171732
172435
  experimentalCompactionMarkers: args.experimentalCompactionMarkers,
171733
172436
  experimentalUserMemories: args.experimentalUserMemories,
@@ -171757,6 +172460,7 @@ async function runCompartmentPhase(args) {
171757
172460
  fallbackModels: args.fallbackModels,
171758
172461
  directory: args.compartmentDirectory,
171759
172462
  fallbackModelId: args.fallbackModelId,
172463
+ ensureProjectRegistered: args.ensureProjectRegistered,
171760
172464
  getNotificationParams: args.getNotificationParams,
171761
172465
  experimentalCompactionMarkers: args.experimentalCompactionMarkers,
171762
172466
  experimentalUserMemories: args.experimentalUserMemories,
@@ -172627,6 +173331,9 @@ function applyContextNudge(messages, nudge, nudgePlacements, sessionId) {
172627
173331
  }
172628
173332
  }
172629
173333
 
173334
+ // src/hooks/magic-context/auto-search-runner.ts
173335
+ init_embedding();
173336
+
172630
173337
  // src/features/magic-context/search.ts
172631
173338
  init_logger();
172632
173339
  init_memory();
@@ -172687,6 +173394,7 @@ async function getSemanticScores(args) {
172687
173394
  const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
172688
173395
  const embeddings = await ensureMemoryEmbeddings({
172689
173396
  db: args.db,
173397
+ projectIdentity: args.projectPath,
172690
173398
  memories: args.memories,
172691
173399
  existingEmbeddings: cachedEmbeddings
172692
173400
  });
@@ -172962,6 +173670,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
172962
173670
  }
172963
173671
 
172964
173672
  // src/hooks/magic-context/auto-search-runner.ts
173673
+ init_storage_meta_persisted();
172965
173674
  init_logger();
172966
173675
 
172967
173676
  // src/hooks/magic-context/auto-search-hint.ts
@@ -173043,7 +173752,6 @@ ${trimmedBody}…
173043
173752
  }
173044
173753
 
173045
173754
  // src/hooks/magic-context/auto-search-runner.ts
173046
- var autoSearchByTurn = new Map;
173047
173755
  var AUTO_SEARCH_TIMEOUT_MS = 3000;
173048
173756
  async function unifiedSearchWithTimeout(db, sessionId, projectPath, prompt, options, timeoutMs) {
173049
173757
  const controller = new AbortController;
@@ -173135,66 +173843,101 @@ async function runAutoSearchHint(args) {
173135
173843
  if (!userMsg || typeof userMsg.info.id !== "string")
173136
173844
  return;
173137
173845
  const userMsgId = userMsg.info.id;
173138
- const cached2 = autoSearchByTurn.get(sessionId);
173139
- if (cached2 && cached2.messageId === userMsgId) {
173140
- appendReminderToUserMessageById(messages, userMsgId, cached2.hint);
173846
+ const existing = getAutoSearchHintDecisions(db, sessionId);
173847
+ const existingForMessage = existing.find((decision) => decision.messageId === userMsgId);
173848
+ if (existingForMessage) {
173849
+ if (existingForMessage.decision === "hint") {
173850
+ appendReminderToUserMessageById(messages, userMsgId, existingForMessage.text);
173851
+ }
173141
173852
  return;
173142
173853
  }
173854
+ const writeNoHintAndReconcile = (reason) => {
173855
+ const outcome2 = appendAutoSearchHintDecision(db, sessionId, {
173856
+ messageId: userMsgId,
173857
+ decision: "no-hint",
173858
+ reason
173859
+ });
173860
+ if (!outcome2.ok)
173861
+ return;
173862
+ if (outcome2.kind === "already-present" && outcome2.decision.decision === "hint") {
173863
+ appendReminderToUserMessageById(messages, userMsgId, outcome2.decision.text);
173864
+ }
173865
+ };
173143
173866
  const rawPartsText = collectUserPromptParts(userMsg);
173144
173867
  if (hasStackedAugmentation(rawPartsText)) {
173145
173868
  sessionLog(sessionId, "auto-search: skipping — user message already carries augmentation/hint");
173146
- autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
173869
+ writeNoHintAndReconcile("stacked");
173147
173870
  return;
173148
173871
  }
173149
173872
  const rawPrompt = extractUserPromptText(userMsg);
173150
173873
  if (rawPrompt.length < options.minPromptChars) {
173151
- autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
173874
+ writeNoHintAndReconcile("too-short");
173152
173875
  return;
173153
173876
  }
173154
173877
  let results;
173155
173878
  try {
173879
+ if (options.directory) {
173880
+ await options.ensureProjectRegistered?.(options.directory, db);
173881
+ }
173882
+ const embeddingSnapshot = getProjectEmbeddingSnapshot(options.projectPath);
173883
+ const memoryEnabled = embeddingSnapshot?.features.memoryEnabled ?? options.memoryEnabled;
173884
+ const embeddingEnabled = embeddingSnapshot ? embeddingSnapshot.enabled || embeddingSnapshot.gitCommitEnabled : options.embeddingEnabled;
173885
+ const gitCommitsEnabled = embeddingSnapshot?.gitCommitEnabled ?? options.gitCommitsEnabled ?? false;
173156
173886
  const searchOptions = {
173157
173887
  limit: 10,
173158
- memoryEnabled: options.memoryEnabled,
173159
- embeddingEnabled: options.embeddingEnabled,
173160
- gitCommitsEnabled: options.gitCommitsEnabled,
173888
+ memoryEnabled,
173889
+ embeddingEnabled,
173890
+ gitCommitsEnabled,
173891
+ embedQuery: async (text, signal) => {
173892
+ const result = await embedTextForProject(options.projectPath, text, signal);
173893
+ return result?.vector ?? null;
173894
+ },
173895
+ isEmbeddingRuntimeEnabled: () => embeddingEnabled === true,
173161
173896
  visibleMemoryIds: options.visibleMemoryIds ?? null
173162
173897
  };
173163
173898
  results = await unifiedSearchWithTimeout(db, sessionId, options.projectPath, rawPrompt, searchOptions, AUTO_SEARCH_TIMEOUT_MS);
173164
173899
  } catch (error51) {
173165
173900
  log(`[auto-search] unified search failed for session ${sessionId}: ${error51 instanceof Error ? error51.message : String(error51)}`);
173166
- autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
173901
+ writeNoHintAndReconcile("error");
173167
173902
  return;
173168
173903
  }
173169
173904
  if (results === null) {
173170
173905
  sessionLog(sessionId, `auto-search: timed out after ${AUTO_SEARCH_TIMEOUT_MS}ms, skipping hint for this turn`);
173171
- autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
173906
+ writeNoHintAndReconcile("timeout");
173172
173907
  return;
173173
173908
  }
173174
173909
  if (results.length === 0) {
173175
- autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
173910
+ writeNoHintAndReconcile("empty");
173176
173911
  return;
173177
173912
  }
173178
173913
  if (results[0].score < options.scoreThreshold) {
173179
173914
  sessionLog(sessionId, `auto-search: top score ${results[0].score.toFixed(3)} below threshold ${options.scoreThreshold}`);
173180
- autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
173915
+ writeNoHintAndReconcile("below-threshold");
173181
173916
  return;
173182
173917
  }
173183
173918
  const hintText = buildAutoSearchHint(results);
173184
173919
  if (!hintText) {
173185
- autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
173920
+ writeNoHintAndReconcile("empty");
173186
173921
  return;
173187
173922
  }
173188
173923
  const payload = `
173189
173924
 
173190
173925
  ${hintText}`;
173191
- autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: payload });
173192
- appendReminderToUserMessageById(messages, userMsgId, payload);
173926
+ const outcome = appendAutoSearchHintDecision(db, sessionId, {
173927
+ messageId: userMsgId,
173928
+ decision: "hint",
173929
+ text: payload
173930
+ });
173931
+ if (!outcome.ok) {
173932
+ sessionLog(sessionId, `auto-search: CAS exhausted for ${userMsgId}; skipping wire append`);
173933
+ return;
173934
+ }
173935
+ if (outcome.decision.decision === "hint") {
173936
+ appendReminderToUserMessageById(messages, userMsgId, outcome.decision.text);
173937
+ }
173193
173938
  sessionLog(sessionId, `auto-search: attached hint to ${userMsgId} (${results.length} fragments, top score ${results[0].score.toFixed(3)})`);
173194
173939
  }
173195
- function clearAutoSearchForSession(sessionId) {
173196
- autoSearchByTurn.delete(sessionId);
173197
- }
173940
+ function clearAutoSearchForSession(_sessionId) {}
173198
173941
 
173199
173942
  // src/hooks/magic-context/transform-postprocess-phase.ts
173200
173943
  await __promiseAll([
@@ -173474,7 +174217,7 @@ function isVisibleNoteReadPart(part) {
173474
174217
  }
173475
174218
 
173476
174219
  // src/hooks/magic-context/todo-view.ts
173477
- import { createHash as createHash6 } from "node:crypto";
174220
+ import { createHash as createHash8 } from "node:crypto";
173478
174221
  var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
173479
174222
  var TITLE_DONE_STATUSES = new Set(["completed"]);
173480
174223
  var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
@@ -173519,7 +174262,7 @@ function buildSyntheticTodoPart(stateJson) {
173519
174262
  };
173520
174263
  }
173521
174264
  function computeSyntheticCallId(stateJson) {
173522
- const hash2 = createHash6("sha256").update(stateJson).digest("hex").slice(0, 16);
174265
+ const hash2 = createHash8("sha256").update(stateJson).digest("hex").slice(0, 16);
173523
174266
  return `${SYNTHETIC_CALL_ID_PREFIX}${hash2}`;
173524
174267
  }
173525
174268
  function parseTodoState(stateJson) {
@@ -173793,15 +174536,13 @@ async function runPostTransformPhase(args) {
173793
174536
  } else {
173794
174537
  args.nudgePlacements.clear(args.sessionId);
173795
174538
  }
173796
- const stickyNoteNudge = args.fullFeatureMode ? getStickyNoteNudge(args.db, args.sessionId) : null;
173797
- if (stickyNoteNudge) {
173798
- const reinjected = appendReminderToUserMessageById(args.messages, stickyNoteNudge.messageId, stickyNoteNudge.text);
173799
- if (!reinjected) {
173800
- if (isCacheBustingPass) {
173801
- clearNoteNudgeState(args.db, args.sessionId);
173802
- sessionLog(args.sessionId, `sticky note nudge cleared — anchor ${stickyNoteNudge.messageId} gone (compacted/deleted)`);
173803
- } else {
173804
- sessionLog(args.sessionId, `preserving sticky note nudge anchor to avoid cache bust: messageId=${stickyNoteNudge.messageId}`);
174539
+ if (args.fullFeatureMode) {
174540
+ for (const anchor of getNoteNudgeAnchors(args.db, args.sessionId)) {
174541
+ appendReminderToUserMessageById(args.messages, anchor.messageId, anchor.text);
174542
+ }
174543
+ for (const decision of getAutoSearchHintDecisions(args.db, args.sessionId)) {
174544
+ if (decision.decision === "hint") {
174545
+ appendReminderToUserMessageById(args.messages, decision.messageId, decision.text);
173805
174546
  }
173806
174547
  }
173807
174548
  }
@@ -173811,8 +174552,13 @@ async function runPostTransformPhase(args) {
173811
174552
  const noteInstruction = `
173812
174553
 
173813
174554
  <instruction name="deferred_notes">${deferredNoteText}</instruction>`;
173814
- const anchoredMessageId = appendReminderToLatestUserMessage(args.messages, noteInstruction);
173815
- markNoteNudgeDelivered(args.db, args.sessionId, noteInstruction, anchoredMessageId);
174555
+ const anchoredMessageId = findLastUserMessageId(args.messages);
174556
+ const outcome = markNoteNudgeDelivered(args.db, args.sessionId, noteInstruction, anchoredMessageId);
174557
+ if (anchoredMessageId && outcome.ok) {
174558
+ appendReminderToUserMessageById(args.messages, anchoredMessageId, noteInstruction);
174559
+ } else if (anchoredMessageId && !outcome.ok) {
174560
+ sessionLog(args.sessionId, `note-nudge delivery skipped wire append: ${outcome.kind}`);
174561
+ }
173816
174562
  }
173817
174563
  if (args.fullFeatureMode) {
173818
174564
  const persistedAnchor = getPersistedTodoSyntheticAnchor(args.db, args.sessionId);
@@ -173903,10 +174649,9 @@ async function runPostTransformPhase(args) {
173903
174649
  enabled: true,
173904
174650
  scoreThreshold: args.autoSearch.scoreThreshold,
173905
174651
  minPromptChars: args.autoSearch.minPromptChars,
174652
+ directory: args.autoSearch.directory ?? args.sessionDirectory,
173906
174653
  projectPath: args.projectPath,
173907
- memoryEnabled: args.autoSearch.memoryEnabled,
173908
- embeddingEnabled: args.autoSearch.embeddingEnabled,
173909
- gitCommitsEnabled: args.autoSearch.gitCommitsEnabled,
174654
+ ensureProjectRegistered: args.autoSearch.ensureProjectRegistered,
173910
174655
  visibleMemoryIds
173911
174656
  }
173912
174657
  });
@@ -173914,6 +174659,19 @@ async function runPostTransformPhase(args) {
173914
174659
  sessionLog(args.sessionId, "auto-search runner failed:", error51);
173915
174660
  }
173916
174661
  }
174662
+ if (args.fullFeatureMode && isCacheBustingPass) {
174663
+ const visibleIds = new Set;
174664
+ for (const message of args.messages) {
174665
+ if (typeof message.info?.id === "string") {
174666
+ visibleIds.add(message.info.id);
174667
+ }
174668
+ }
174669
+ const prunedAnchors = pruneNoteNudgeAnchors(args.db, args.sessionId, visibleIds);
174670
+ const prunedDecisions = pruneAutoSearchHintDecisions(args.db, args.sessionId, visibleIds);
174671
+ if (prunedAnchors > 0 || prunedDecisions > 0) {
174672
+ sessionLog(args.sessionId, `sticky-injection GC: pruned ${prunedAnchors} note-nudge anchor(s), ${prunedDecisions} auto-search decision(s)`);
174673
+ }
174674
+ }
173917
174675
  return { explicitMaterializedSuccessfully, deferredMaterializedSuccessfully };
173918
174676
  }
173919
174677
 
@@ -174047,6 +174805,8 @@ function createTransform(deps) {
174047
174805
  updateSessionMeta(db, sessionId, {
174048
174806
  lastContextPercentage: 0,
174049
174807
  lastInputTokens: 0,
174808
+ observedSafeInputTokens: 0,
174809
+ cacheAlertSent: false,
174050
174810
  clearedReasoningThroughTag: 0
174051
174811
  });
174052
174812
  clearHistorianFailureState(db, sessionId);
@@ -174058,7 +174818,9 @@ function createTransform(deps) {
174058
174818
  ...sessionMeta,
174059
174819
  lastContextPercentage: 0,
174060
174820
  lastInputTokens: 0,
174061
- clearedReasoningThroughTag: 0
174821
+ clearedReasoningThroughTag: 0,
174822
+ observedSafeInputTokens: 0,
174823
+ cacheAlertSent: false
174062
174824
  };
174063
174825
  }
174064
174826
  }
@@ -174169,6 +174931,7 @@ function createTransform(deps) {
174169
174931
  compressorMaxMergeDepth: deps.compressorMaxMergeDepth,
174170
174932
  memoryEnabled: deps.memoryConfig?.enabled,
174171
174933
  autoPromote: deps.memoryConfig?.autoPromote,
174934
+ ensureProjectRegistered: deps.ensureProjectRegistered,
174172
174935
  preserveInjectionCacheUntilConsumed: true,
174173
174936
  onCompartmentStatePublished: (sid) => {
174174
174937
  deferredHistoryRefreshSessions.add(sid);
@@ -174353,6 +175116,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
174353
175116
  compressorCooldownMs: deps.compressorCooldownMs,
174354
175117
  memoryEnabled: deps.memoryConfig?.enabled,
174355
175118
  autoPromote: deps.memoryConfig?.autoPromote,
175119
+ ensureProjectRegistered: deps.ensureProjectRegistered,
174356
175120
  onCompartmentStatePublished: (sid) => {
174357
175121
  deferredHistoryRefreshSessions.add(sid);
174358
175122
  deferredMaterializationSessions.add(sid);
@@ -174567,6 +175331,9 @@ function truncateHistorianEmergencyError(error51) {
174567
175331
 
174568
175332
  // src/hooks/magic-context/event-handler.ts
174569
175333
  var CONTEXT_USAGE_TTL_MS = 60 * 60 * 1000;
175334
+ function formatTokens(value) {
175335
+ return value.toLocaleString();
175336
+ }
174570
175337
  function evictExpiredUsageEntries(contextUsageMap) {
174571
175338
  const now = Date.now();
174572
175339
  for (const [sessionId, entry] of contextUsageMap) {
@@ -174587,12 +175354,16 @@ function cleanupRemovedMessageState(deps, sessionId, messageId) {
174587
175354
  clearPersistedNudgePlacement(deps.db, sessionId);
174588
175355
  }
174589
175356
  sessionLog(sessionId, clearedNudgePlacement ? `event message.removed: cleared nudge anchor for ${messageId}` : `event message.removed: nudge anchor unchanged for ${messageId}`);
175357
+ const removedNoteNudgeAnchor = removeNoteNudgeAnchorByMessageId(deps.db, sessionId, messageId);
175358
+ const removedAutoSearchDecision = removeAutoSearchHintDecisionByMessageId(deps.db, sessionId, messageId);
174590
175359
  const persistedNoteNudge = getPersistedNoteNudge(deps.db, sessionId);
174591
- const clearedNoteNudge = persistedNoteNudge.triggerMessageId === messageId || persistedNoteNudge.stickyMessageId === messageId;
174592
- if (clearedNoteNudge) {
174593
- clearPersistedNoteNudge(deps.db, sessionId);
175360
+ const clearedNoteNudgeTrigger = persistedNoteNudge.triggerMessageId === messageId;
175361
+ if (clearedNoteNudgeTrigger) {
175362
+ clearNoteNudgeTriggerOnly(deps.db, sessionId);
174594
175363
  }
174595
- sessionLog(sessionId, clearedNoteNudge ? `event message.removed: cleared note nudge state for ${messageId}` : `event message.removed: note nudge state unchanged for ${messageId}`);
175364
+ const clearedNoteNudge = removedNoteNudgeAnchor || clearedNoteNudgeTrigger;
175365
+ sessionLog(sessionId, clearedNoteNudge ? `event message.removed: pruned note nudge state for ${messageId}` : `event message.removed: note nudge state unchanged for ${messageId}`);
175366
+ sessionLog(sessionId, removedAutoSearchDecision ? `event message.removed: pruned auto-search decision for ${messageId}` : `event message.removed: auto-search decision unchanged for ${messageId}`);
174596
175367
  const persistedStickyTurnReminder = getPersistedStickyTurnReminder(deps.db, sessionId);
174597
175368
  const clearedStickyTurnReminder = persistedStickyTurnReminder?.messageId === messageId;
174598
175369
  if (clearedStickyTurnReminder) {
@@ -174678,9 +175449,11 @@ function createEventHandler2(deps) {
174678
175449
  } else {
174679
175450
  clearMessageTokensCache(info.sessionID);
174680
175451
  }
175452
+ let messageHadOverflowError = false;
174681
175453
  if (info.error !== undefined && info.error !== null) {
174682
175454
  const detection = detectOverflow(info.error);
174683
175455
  if (detection.isOverflow) {
175456
+ messageHadOverflowError = true;
174684
175457
  try {
174685
175458
  const metaForOverflow = getOrCreateSessionMeta(deps.db, info.sessionID);
174686
175459
  if (metaForOverflow.isSubagent) {
@@ -174723,12 +175496,33 @@ function createEventHandler2(deps) {
174723
175496
  }
174724
175497
  if (hasUsageTokens) {
174725
175498
  const totalInputTokens = (info.tokens?.input ?? 0) + (info.tokens?.cache?.read ?? 0) + (info.tokens?.cache?.write ?? 0);
174726
- const contextLimit = resolveContextLimit(info.providerID, info.modelID, {
175499
+ let contextLimit = resolveContextLimit(info.providerID, info.modelID, {
174727
175500
  db: deps.db,
174728
175501
  sessionID: info.sessionID
174729
175502
  });
174730
- const percentage = contextLimit > 0 ? totalInputTokens / contextLimit * 100 : 0;
175503
+ let percentage = contextLimit > 0 ? totalInputTokens / contextLimit * 100 : 0;
174731
175504
  sessionLog(info.sessionID, `event message.updated: totalInputTokens=${totalInputTokens} contextLimit=${contextLimit} percentage=${percentage.toFixed(1)}%`);
175505
+ const sessionMeta = getOrCreateSessionMeta(deps.db, info.sessionID);
175506
+ const observedSafeInputTokens = sessionMeta.observedSafeInputTokens ?? 0;
175507
+ if (percentage > 100 && observedSafeInputTokens > 0 && totalInputTokens <= observedSafeInputTokens * 2) {
175508
+ const oldLimit = contextLimit;
175509
+ if (deps.client) {
175510
+ await refreshModelLimitsFromApi(deps.client);
175511
+ contextLimit = resolveContextLimit(info.providerID, info.modelID, {
175512
+ db: deps.db,
175513
+ sessionID: info.sessionID
175514
+ });
175515
+ if (contextLimit >= totalInputTokens) {
175516
+ percentage = totalInputTokens / contextLimit * 100;
175517
+ sessionLog(info.sessionID, `models-dev-cache: regression recovered for ${info.providerID}/${info.modelID} via refresh (was=${oldLimit}, now=${contextLimit})`);
175518
+ }
175519
+ }
175520
+ if (contextLimit < totalInputTokens && !sessionMeta.cacheAlertSent) {
175521
+ updates.cacheAlertSent = true;
175522
+ const safeTokens = Math.max(observedSafeInputTokens, totalInputTokens);
175523
+ await sendIgnoredMessage(deps.client, info.sessionID, `⚠️ Magic Context: OpenCode reports a context limit of ${formatTokens(contextLimit)} tokens for ${info.providerID}/${info.modelID} but you've successfully sent ${formatTokens(safeTokens)} tokens in this session — the cached limit looks wrong. Restart OpenCode if you suspect this is incorrect.`, deps.getNotificationParams?.(info.sessionID) ?? {});
175524
+ }
175525
+ }
174732
175526
  deps.contextUsageMap.set(info.sessionID, {
174733
175527
  usage: {
174734
175528
  percentage,
@@ -174739,12 +175533,14 @@ function createEventHandler2(deps) {
174739
175533
  });
174740
175534
  updates.lastContextPercentage = percentage;
174741
175535
  updates.lastInputTokens = totalInputTokens;
175536
+ if (!messageHadOverflowError) {
175537
+ updates.observedSafeInputTokens = Math.max(observedSafeInputTokens, totalInputTokens);
175538
+ }
174742
175539
  const historianFailureState = getHistorianFailureState(deps.db, info.sessionID);
174743
175540
  if (historianFailureState.failureCount > 0 && percentage < 90) {
174744
175541
  clearHistorianFailureState(deps.db, info.sessionID);
174745
175542
  sessionLog(info.sessionID, `event message.updated: cleared historian failure state at ${percentage.toFixed(1)}%`);
174746
175543
  }
174747
- const sessionMeta = getOrCreateSessionMeta(deps.db, info.sessionID);
174748
175544
  const previousPercentage = sessionMeta.lastContextPercentage;
174749
175545
  if (!sessionMeta.isSubagent) {
174750
175546
  const effectiveExecuteThreshold = resolveExecuteThreshold(deps.config.execute_threshold_percentage ?? 65, modelKey, 65, {
@@ -174789,10 +175585,6 @@ function createEventHandler2(deps) {
174789
175585
  deps.nudgePlacements.clear(info.sessionID, { persist: false });
174790
175586
  sessionLog(info.sessionID, "event message.removed: cleared in-memory nudge placement cache");
174791
175587
  }
174792
- if (cleanup.clearedNoteNudge) {
174793
- clearNoteNudgeState(deps.db, info.sessionID, { persist: false });
174794
- sessionLog(info.sessionID, "event message.removed: cleared in-memory note nudge state");
174795
- }
174796
175588
  const markerState = getPersistedCompactionMarkerState(deps.db, info.sessionID);
174797
175589
  if (markerState && (markerState.boundaryMessageId === info.messageID || markerState.summaryMessageId === info.messageID)) {
174798
175590
  removeCompactionMarkerForSession(deps.db, info.sessionID);
@@ -175017,10 +175809,11 @@ function estimateProjectedPercentage(db, sessionId, contextUsage, preloadedTags)
175017
175809
  await init_read_session_db();
175018
175810
 
175019
175811
  // src/hooks/magic-context/text-complete.ts
175020
- var TAG_PREFIX_REGEX2 = /^(\u00a7\d+\u00a7\s*)+/;
175812
+ var LEADING_TAG_PREFIX_REGEX = /^(\u00a7\d+\u00a7\s*)+/;
175813
+ var SECTION_CHAR_REGEX = /\u00a7/g;
175021
175814
  function createTextCompleteHandler() {
175022
175815
  return async (_input, output) => {
175023
- output.text = output.text.replace(TAG_PREFIX_REGEX2, "");
175816
+ output.text = output.text.replace(LEADING_TAG_PREFIX_REGEX, "").replace(SECTION_CHAR_REGEX, "");
175024
175817
  };
175025
175818
  }
175026
175819
 
@@ -175155,7 +175948,9 @@ function createEventHook(args) {
175155
175948
  clearHistorianFailureState(args.db, assistantInfo.sessionID);
175156
175949
  clearPersistedReasoningWatermark(args.db, assistantInfo.sessionID);
175157
175950
  updateSessionMeta(args.db, assistantInfo.sessionID, {
175158
- clearedReasoningThroughTag: 0
175951
+ clearedReasoningThroughTag: 0,
175952
+ observedSafeInputTokens: 0,
175953
+ cacheAlertSent: false
175159
175954
  });
175160
175955
  }
175161
175956
  }
@@ -175181,7 +175976,7 @@ function createEventHook(args) {
175181
175976
  args.deferredMaterializationSessions.delete(sessionId);
175182
175977
  args.lastHeuristicsTurnId.delete(sessionId);
175183
175978
  args.commitSeenLastPass?.delete(sessionId);
175184
- clearNoteNudgeState(args.db, sessionId);
175979
+ resetNoteNudgeCooldownOnly(sessionId);
175185
175980
  clearAutoSearchForSession(sessionId);
175186
175981
  clearSidebarSnapshotCache(sessionId);
175187
175982
  clearSessionTracking(sessionId);
@@ -175229,7 +176024,7 @@ function createToolExecuteAfterHook(args) {
175229
176024
  }
175230
176025
  }
175231
176026
  if (typedInput.tool === "ctx_note") {
175232
- clearNoteNudgeState(args.db, typedInput.sessionID);
176027
+ clearNoteNudgeTriggerAndCooldown(args.db, typedInput.sessionID);
175233
176028
  }
175234
176029
  args.toolUsageSinceUserTurn.set(typedInput.sessionID, turnUsage + 1);
175235
176030
  };
@@ -175239,7 +176034,7 @@ function createToolExecuteAfterHook(args) {
175239
176034
  init_send_session_notification();
175240
176035
 
175241
176036
  // src/hooks/magic-context/system-prompt-hash.ts
175242
- import { createHash as createHash7 } from "node:crypto";
176037
+ import { createHash as createHash9 } from "node:crypto";
175243
176038
  import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
175244
176039
  import { join as join25 } from "node:path";
175245
176040
 
@@ -175331,6 +176126,7 @@ Prefer many small targeted operations over one large blanket operation. Compress
175331
176126
  }
175332
176127
 
175333
176128
  // src/hooks/magic-context/system-prompt-hash.ts
176129
+ init_compartment_storage();
175334
176130
  init_logger();
175335
176131
  await init_storage();
175336
176132
 
@@ -175487,7 +176283,7 @@ function readProjectDocs(directory) {
175487
176283
  const content = readFileSync13(filePath, "utf-8").trim();
175488
176284
  if (content.length > 0) {
175489
176285
  sections.push(`<${filename}>
175490
- ${content}
176286
+ ${escapeXmlContent(content)}
175491
176287
  </${filename}>`);
175492
176288
  }
175493
176289
  }
@@ -175564,7 +176360,7 @@ function createSystemPromptHashHandler(deps) {
175564
176360
  if (!hasCachedProfile || isCacheBusting) {
175565
176361
  const memories = getActiveUserMemories(deps.db);
175566
176362
  if (memories.length > 0) {
175567
- const items = memories.map((m) => `- ${m.content}`).join(`
176363
+ const items = memories.map((m) => `- ${escapeXmlContent(m.content)}`).join(`
175568
176364
  `);
175569
176365
  cachedUserProfileBySession.set(sessionId, `${USER_PROFILE_MARKER}
175570
176366
  ${items}
@@ -175620,7 +176416,7 @@ ${items}
175620
176416
  `);
175621
176417
  if (systemContent.length === 0)
175622
176418
  return;
175623
- const currentHash = createHash7("md5").update(systemContent).digest("hex");
176419
+ const currentHash = createHash9("md5").update(systemContent).digest("hex");
175624
176420
  if (!sessionMetaEarly) {
175625
176421
  return;
175626
176422
  }
@@ -175772,6 +176568,7 @@ function createMagicContextHook(deps) {
175772
176568
  injectionBudgetTokens: deps.config.memory.injection_budget_tokens,
175773
176569
  autoPromote: deps.config.memory.auto_promote ?? true
175774
176570
  } : undefined,
176571
+ ensureProjectRegistered: ensureProjectRegisteredFromOpenCodeDirectory,
175775
176572
  getHistorianChunkTokens,
175776
176573
  historyBudgetPercentage: deps.config.history_budget_percentage,
175777
176574
  executeThresholdPercentage: deps.config.execute_threshold_percentage,
@@ -175801,9 +176598,8 @@ function createMagicContextHook(deps) {
175801
176598
  enabled: true,
175802
176599
  scoreThreshold: deps.config.experimental.auto_search.score_threshold,
175803
176600
  minPromptChars: deps.config.experimental.auto_search.min_prompt_chars,
175804
- memoryEnabled: deps.config.memory?.enabled !== false,
175805
- embeddingEnabled: deps.config.embedding?.provider !== "off",
175806
- gitCommitsEnabled: deps.config.experimental?.git_commit_indexing?.enabled === true
176601
+ directory: deps.directory,
176602
+ ensureProjectRegistered: ensureProjectRegisteredFromOpenCodeDirectory
175807
176603
  } : undefined,
175808
176604
  cavemanTextCompression: ctxReduceEnabled === false && deps.config.experimental?.caveman_text_compression?.enabled === true ? {
175809
176605
  enabled: true,
@@ -175816,6 +176612,8 @@ function createMagicContextHook(deps) {
175816
176612
  config: deps.config,
175817
176613
  tagger: deps.tagger,
175818
176614
  db,
176615
+ client: deps.client,
176616
+ getNotificationParams: (sessionId) => getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession),
175819
176617
  nudgePlacements,
175820
176618
  onSessionCacheInvalidated: (sessionId) => {
175821
176619
  clearInjectionCache(sessionId);
@@ -175901,6 +176699,7 @@ function createMagicContextHook(deps) {
175901
176699
  historianTwoPass: deps.config.historian?.two_pass === true,
175902
176700
  memoryEnabled: deps.config.memory?.enabled,
175903
176701
  autoPromote: deps.config.memory?.auto_promote ?? true,
176702
+ ensureProjectRegistered: ensureProjectRegisteredFromOpenCodeDirectory,
175904
176703
  onCompartmentStatePublished: (sid) => {
175905
176704
  historyRefreshSessions.add(sid);
175906
176705
  pendingMaterializationSessions.add(sid);
@@ -176333,6 +177132,7 @@ function buildSidebarSnapshot(db, sessionId, directory, liveSessionState, inject
176333
177132
  sessionId,
176334
177133
  usagePercentage: 0,
176335
177134
  inputTokens: 0,
177135
+ contextLimit: 0,
176336
177136
  systemPromptTokens: 0,
176337
177137
  compartmentCount: 0,
176338
177138
  factCount: 0,
@@ -176469,6 +177269,7 @@ ${c.content}
176469
177269
  measuredToolDefTokens = getMeasuredToolDefinitionTokens(model.providerID, model.modelID, agent) ?? 0;
176470
177270
  }
176471
177271
  }
177272
+ const contextLimit = activeProviderID && activeModelID ? resolveContextLimit(activeProviderID, activeModelID, { db, sessionID: sessionId }) : 0;
176472
177273
  const calibration = resolveModelCalibration(activeProviderID, activeModelID);
176473
177274
  const calibrated = calibrateBuckets({
176474
177275
  inputTokens,
@@ -176485,6 +177286,7 @@ ${c.content}
176485
177286
  sessionId,
176486
177287
  usagePercentage,
176487
177288
  inputTokens,
177289
+ contextLimit,
176488
177290
  systemPromptTokens: calibrated.systemTokens,
176489
177291
  compartmentCount,
176490
177292
  factCount,
@@ -176562,7 +177364,7 @@ function buildStatusDetail(db, sessionId, directory, modelKey, config2, liveSess
176562
177364
  const ops = db.prepare("SELECT tag_id, operation FROM pending_ops WHERE session_id = ?").all(sessionId);
176563
177365
  detail.pendingOps = ops.map((o) => ({ tagId: o.tag_id, operation: o.operation }));
176564
177366
  } catch {}
176565
- const contextLimitForTokens = base.usagePercentage > 0 ? Math.round(base.inputTokens / (base.usagePercentage / 100)) : 0;
177367
+ const contextLimitForTokens = base.contextLimit > 0 ? base.contextLimit : base.usagePercentage > 0 ? Math.round(base.inputTokens / (base.usagePercentage / 100)) : 0;
176566
177368
  if (config2) {
176567
177369
  const pctCfg = config2.execute_threshold_percentage;
176568
177370
  const tokensCfg = config2.execute_threshold_tokens;
@@ -176588,7 +177390,9 @@ function buildStatusDetail(db, sessionId, directory, modelKey, config2, liveSess
176588
177390
  detail.historyBudgetPercentage = config2.history_budget_percentage;
176589
177391
  }
176590
177392
  }
176591
- if (base.usagePercentage > 0) {
177393
+ if (base.contextLimit > 0) {
177394
+ detail.contextLimit = base.contextLimit;
177395
+ } else if (base.usagePercentage > 0) {
176592
177396
  detail.contextLimit = Math.round(base.inputTokens / (base.usagePercentage / 100));
176593
177397
  }
176594
177398
  detail.cacheTtlMs = parseTtlString(detail.cacheTtl);
@@ -176688,9 +177492,6 @@ function registerRpcHandlers(rpcServer, args) {
176688
177492
  }
176689
177493
 
176690
177494
  // src/plugin/tool-registry.ts
176691
- init_magic_context();
176692
- init_memory();
176693
- init_embedding();
176694
177495
  init_project_identity();
176695
177496
  await init_storage();
176696
177497
 
@@ -176838,21 +177639,17 @@ function filterByCategory(memories, category) {
176838
177639
  return memories.filter((memory) => memory.category === category);
176839
177640
  }
176840
177641
  function queueMemoryEmbedding(args) {
176841
- if (!args.deps.embeddingEnabled) {
177642
+ const snapshot = getProjectEmbeddingSnapshot(args.projectPath);
177643
+ if (!snapshot?.enabled) {
176842
177644
  return;
176843
177645
  }
176844
177646
  (async () => {
176845
- const embedding2 = await embedText(args.content);
176846
- if (!embedding2) {
177647
+ const result = await embedTextForProject(args.projectPath, args.content);
177648
+ if (!result) {
176847
177649
  sessionLog(args.sessionId, `memory embedding skipped for memory ${args.memoryId}: provider unavailable or embedding generation failed.`);
176848
177650
  return;
176849
177651
  }
176850
- const modelId = getEmbeddingModelId();
176851
- if (modelId === "off") {
176852
- sessionLog(args.sessionId, `memory embedding skipped for memory ${args.memoryId}: embedding provider is off.`);
176853
- return;
176854
- }
176855
- saveEmbedding(args.deps.db, args.memoryId, embedding2, modelId);
177652
+ saveEmbedding(args.deps.db, args.memoryId, result.vector, result.modelId);
176856
177653
  sessionLog(args.sessionId, `proactively embedded memory ${args.memoryId}.`);
176857
177654
  })().catch((error51) => {
176858
177655
  sessionLog(args.sessionId, `memory embedding failed for memory ${args.memoryId}:`, error51);
@@ -176888,13 +177685,15 @@ function createCtxMemoryTool(deps) {
176888
177685
  reason: tool2.schema.string().optional().describe("Archive reason (optional for archive)")
176889
177686
  },
176890
177687
  async execute(args, toolContext) {
176891
- if (!deps.memoryEnabled) {
176892
- return getDisabledMessage();
176893
- }
176894
177688
  if (toolContext.agent !== DREAMER_AGENT && !allowedActions.includes(args.action)) {
176895
177689
  return `Error: Action '${args.action}' is not allowed in this context.`;
176896
177690
  }
176897
177691
  const projectPath = deps.resolveProjectPath(toolContext.directory);
177692
+ await deps.ensureProjectRegistered?.(toolContext.directory, deps.db);
177693
+ const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
177694
+ if (embeddingSnapshot ? !embeddingSnapshot.features.memoryEnabled : deps.memoryEnabled === false) {
177695
+ return getDisabledMessage();
177696
+ }
176898
177697
  if (args.action === "write") {
176899
177698
  const content = args.content?.trim();
176900
177699
  if (!content) {
@@ -176923,6 +177722,7 @@ function createCtxMemoryTool(deps) {
176923
177722
  queueMemoryEmbedding({
176924
177723
  deps,
176925
177724
  sessionId: toolContext.sessionID,
177725
+ projectPath,
176926
177726
  memoryId: memory.id,
176927
177727
  content
176928
177728
  });
@@ -176968,6 +177768,7 @@ function createCtxMemoryTool(deps) {
176968
177768
  queueMemoryEmbedding({
176969
177769
  deps,
176970
177770
  sessionId: toolContext.sessionID,
177771
+ projectPath,
176971
177772
  memoryId: memory.id,
176972
177773
  content
176973
177774
  });
@@ -177041,6 +177842,7 @@ function createCtxMemoryTool(deps) {
177041
177842
  queueMemoryEmbedding({
177042
177843
  deps,
177043
177844
  sessionId: toolContext.sessionID,
177845
+ projectPath,
177044
177846
  memoryId: canonicalMemory.id,
177045
177847
  content
177046
177848
  });
@@ -177416,6 +178218,7 @@ var CTX_SEARCH_DESCRIPTION = [
177416
178218
  var DEFAULT_CTX_SEARCH_LIMIT = 10;
177417
178219
  // src/tools/ctx-search/tools.ts
177418
178220
  init_compartment_storage();
178221
+ init_embedding();
177419
178222
  import { tool as tool5 } from "@opencode-ai/plugin";
177420
178223
  await init_inject_compartments();
177421
178224
  var VALID_SOURCES = new Set(["memory", "message", "git_commit"]);
@@ -177514,13 +178317,23 @@ function createCtxSearchTool(deps) {
177514
178317
  const lastCompartmentEnd = getLastCompartmentEndMessage(deps.db, toolContext.sessionID);
177515
178318
  const visibleMemoryIds = getVisibleMemoryIds(deps.db, toolContext.sessionID);
177516
178319
  const projectPath = deps.resolveProjectPath(toolContext.directory);
178320
+ await deps.ensureProjectRegistered?.(toolContext.directory, deps.db);
178321
+ const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
178322
+ const memoryEnabled = embeddingSnapshot?.features.memoryEnabled ?? deps.memoryEnabled;
178323
+ const embeddingEnabled = embeddingSnapshot ? embeddingSnapshot.enabled || embeddingSnapshot.gitCommitEnabled : deps.embeddingEnabled;
178324
+ const gitCommitsEnabled = embeddingSnapshot?.gitCommitEnabled ?? deps.gitCommitsEnabled ?? false;
177517
178325
  const results = await unifiedSearch(deps.db, toolContext.sessionID, projectPath, query, {
177518
178326
  limit: normalizeLimit3(args.limit),
177519
- memoryEnabled: deps.memoryEnabled,
177520
- embeddingEnabled: deps.embeddingEnabled,
178327
+ memoryEnabled,
178328
+ embeddingEnabled,
178329
+ embedQuery: async (text, signal) => {
178330
+ const result = await embedTextForProject(projectPath, text, signal);
178331
+ return result?.vector ?? null;
178332
+ },
178333
+ isEmbeddingRuntimeEnabled: () => embeddingEnabled === true,
177521
178334
  readMessages: deps.readMessages,
177522
178335
  maxMessageOrdinal: lastCompartmentEnd >= 0 ? lastCompartmentEnd : undefined,
177523
- gitCommitsEnabled: deps.gitCommitsEnabled ?? false,
178336
+ gitCommitsEnabled,
177524
178337
  sources: normalizeSources(args.sources),
177525
178338
  visibleMemoryIds
177526
178339
  });
@@ -177563,10 +178376,6 @@ function normalizeToolArgSchemas(toolDefinition) {
177563
178376
  // src/plugin/tool-registry.ts
177564
178377
  function createToolRegistry(args) {
177565
178378
  const { ctx, pluginConfig } = args;
177566
- const embeddingConfig2 = pluginConfig.embedding ?? {
177567
- provider: "local",
177568
- model: DEFAULT_LOCAL_EMBEDDING_MODEL
177569
- };
177570
178379
  if (pluginConfig.enabled !== true) {
177571
178380
  return {};
177572
178381
  }
@@ -177583,18 +178392,7 @@ function createToolRegistry(args) {
177583
178392
  console.warn(`[magic-context] persistent storage unavailable; disabling magic-context tools${reason ? `: ${reason}` : ""}`);
177584
178393
  return {};
177585
178394
  }
177586
- const memoryEnabled = pluginConfig.memory?.enabled === true;
177587
- initializeEmbedding(embeddingConfig2);
177588
- const launchProjectPath = resolveProjectIdentity(ctx.directory);
177589
- if (memoryEnabled) {
177590
- const currentModelId = getEmbeddingModelId();
177591
- const storedModelId = getStoredModelId(db, launchProjectPath);
177592
- const hasEmbeddings = db.prepare("SELECT 1 FROM memory_embeddings me JOIN memories m ON me.memory_id = m.id WHERE m.project_path = ? LIMIT 1").get(launchProjectPath) !== null;
177593
- if (hasEmbeddings && storedModelId !== currentModelId) {
177594
- clearEmbeddingsForProject(db, launchProjectPath);
177595
- console.warn(`[magic-context] embedding model changed from ${storedModelId} to ${currentModelId}; cleared embeddings for project ${launchProjectPath}`);
177596
- }
177597
- }
178395
+ ensureProjectRegisteredFromOpenCodeDirectory(ctx.directory, db);
177598
178396
  const resolveProjectPath2 = (directory) => resolveProjectIdentity(directory);
177599
178397
  const ctxReduceEnabled = pluginConfig.ctx_reduce_enabled !== false;
177600
178398
  const allTools = {
@@ -177611,19 +178409,14 @@ function createToolRegistry(args) {
177611
178409
  ...createCtxSearchTools({
177612
178410
  db,
177613
178411
  resolveProjectPath: resolveProjectPath2,
177614
- memoryEnabled,
177615
- embeddingEnabled: embeddingConfig2.provider !== "off",
177616
- gitCommitsEnabled: pluginConfig.experimental?.git_commit_indexing?.enabled === true
178412
+ ensureProjectRegistered: ensureProjectRegisteredFromOpenCodeDirectory
177617
178413
  }),
177618
- ...memoryEnabled ? {
177619
- ...createCtxMemoryTools({
177620
- db,
177621
- resolveProjectPath: resolveProjectPath2,
177622
- memoryEnabled: true,
177623
- embeddingEnabled: embeddingConfig2.provider !== "off",
177624
- allowedActions: ["write", "delete"]
177625
- })
177626
- } : {}
178414
+ ...createCtxMemoryTools({
178415
+ db,
178416
+ resolveProjectPath: resolveProjectPath2,
178417
+ ensureProjectRegistered: ensureProjectRegisteredFromOpenCodeDirectory,
178418
+ allowedActions: ["write", "delete"]
178419
+ })
177627
178420
  };
177628
178421
  for (const toolDefinition of Object.values(allTools)) {
177629
178422
  normalizeToolArgSchemas(toolDefinition);
@@ -177649,14 +178442,14 @@ import {
177649
178442
  writeFileSync as writeFileSync7
177650
178443
  } from "node:fs";
177651
178444
  import { createServer } from "node:http";
177652
- import { dirname as dirname7 } from "node:path";
178445
+ import { dirname as dirname8 } from "node:path";
177653
178446
 
177654
178447
  // src/shared/rpc-utils.ts
177655
- import { createHash as createHash8 } from "node:crypto";
178448
+ import { createHash as createHash10 } from "node:crypto";
177656
178449
  import { join as join26 } from "node:path";
177657
178450
  function projectHash(directory) {
177658
178451
  const normalized = directory.replace(/\/+$/, "");
177659
- return createHash8("sha256").update(normalized).digest("hex").slice(0, 16);
178452
+ return createHash10("sha256").update(normalized).digest("hex").slice(0, 16);
177660
178453
  }
177661
178454
  function rpcPortDir(storageDir, directory) {
177662
178455
  return join26(storageDir, "rpc", projectHash(directory));
@@ -177736,7 +178529,7 @@ class MagicContextRpcServer {
177736
178529
  this.server = server;
177737
178530
  try {
177738
178531
  this.warnIfOtherLiveInstance();
177739
- const dir = dirname7(this.portFilePath);
178532
+ const dir = dirname8(this.portFilePath);
177740
178533
  mkdirSync8(dir, { recursive: true });
177741
178534
  const tmpPath = `${this.portFilePath}.tmp`;
177742
178535
  writeFileSync7(tmpPath, JSON.stringify({
@@ -177884,8 +178677,9 @@ var plugin = async (ctx) => {
177884
178677
  });
177885
178678
  const storageDir = getMagicContextStorageDir();
177886
178679
  if (pluginConfig.enabled) {
177887
- startDreamScheduleTimer({
178680
+ const timerRegistration = {
177888
178681
  directory: ctx.directory,
178682
+ projectIdentity: resolveProjectIdentity(ctx.directory),
177889
178683
  client: ctx.client,
177890
178684
  dreamerConfig: pluginConfig.dreamer,
177891
178685
  embeddingConfig: pluginConfig.embedding,
@@ -177903,8 +178697,10 @@ var plugin = async (ctx) => {
177903
178697
  enabled: true,
177904
178698
  since_days: pluginConfig.experimental.git_commit_indexing.since_days,
177905
178699
  max_commits: pluginConfig.experimental.git_commit_indexing.max_commits
177906
- } : undefined
177907
- });
178700
+ } : undefined,
178701
+ ensureRegistered: ensureProjectRegisteredFromOpenCodeDirectory
178702
+ };
178703
+ await startDreamScheduleTimer(timerRegistration);
177908
178704
  const rpcServer = new MagicContextRpcServer(storageDir, ctx.directory);
177909
178705
  registerRpcHandlers(rpcServer, {
177910
178706
  directory: ctx.directory,
@@ -177916,9 +178712,6 @@ var plugin = async (ctx) => {
177916
178712
  log(`[magic-context] RPC server failed to start: ${err}`);
177917
178713
  });
177918
178714
  refreshModelLimitsFromApi(ctx.client);
177919
- setInterval(() => {
177920
- refreshModelLimitsFromApi(ctx.client);
177921
- }, 60 * 60 * 1000);
177922
178715
  }
177923
178716
  if (conflictResult?.hasConflict) {
177924
178717
  sendConflictWarning(ctx.client, ctx.directory, conflictResult);