@cortexkit/opencode-magic-context 0.13.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +13 -7
  2. package/dist/cli/doctor.d.ts.map +1 -1
  3. package/dist/cli.js +59 -3
  4. package/dist/config/index.d.ts.map +1 -1
  5. package/dist/config/schema/magic-context.d.ts +61 -22
  6. package/dist/config/schema/magic-context.d.ts.map +1 -1
  7. package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
  8. package/dist/features/magic-context/git-commits/git-log-reader.d.ts +57 -0
  9. package/dist/features/magic-context/git-commits/git-log-reader.d.ts.map +1 -0
  10. package/dist/features/magic-context/git-commits/index.d.ts +7 -0
  11. package/dist/features/magic-context/git-commits/index.d.ts.map +1 -0
  12. package/dist/features/magic-context/git-commits/indexer.d.ts +44 -0
  13. package/dist/features/magic-context/git-commits/indexer.d.ts.map +1 -0
  14. package/dist/features/magic-context/git-commits/search-git-commits.d.ts +32 -0
  15. package/dist/features/magic-context/git-commits/search-git-commits.d.ts.map +1 -0
  16. package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts +18 -0
  17. package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts.map +1 -0
  18. package/dist/features/magic-context/git-commits/storage-git-commits.d.ts +47 -0
  19. package/dist/features/magic-context/git-commits/storage-git-commits.d.ts.map +1 -0
  20. package/dist/features/magic-context/memory/embedding-local.d.ts +2 -2
  21. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  22. package/dist/features/magic-context/memory/embedding-openai.d.ts +25 -2
  23. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  24. package/dist/features/magic-context/memory/embedding-provider.d.ts +8 -2
  25. package/dist/features/magic-context/memory/embedding-provider.d.ts.map +1 -1
  26. package/dist/features/magic-context/memory/embedding.d.ts +2 -2
  27. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  28. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  29. package/dist/features/magic-context/overflow-detection.d.ts +51 -0
  30. package/dist/features/magic-context/overflow-detection.d.ts.map +1 -0
  31. package/dist/features/magic-context/search.d.ts +40 -9
  32. package/dist/features/magic-context/search.d.ts.map +1 -1
  33. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  34. package/dist/features/magic-context/storage-meta-persisted.d.ts +40 -0
  35. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  36. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  37. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  38. package/dist/features/magic-context/storage.d.ts +1 -1
  39. package/dist/features/magic-context/storage.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/auto-search-hint.d.ts +34 -0
  41. package/dist/hooks/magic-context/auto-search-hint.d.ts.map +1 -0
  42. package/dist/hooks/magic-context/auto-search-runner.d.ts +51 -0
  43. package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -0
  44. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  45. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  46. package/dist/hooks/magic-context/event-payloads.d.ts +15 -1
  47. package/dist/hooks/magic-context/event-payloads.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/event-resolvers.d.ts +20 -1
  49. package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/hook-handlers.d.ts +5 -0
  51. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  52. package/dist/hooks/magic-context/hook.d.ts +14 -6
  53. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  54. package/dist/hooks/magic-context/inject-compartments.d.ts +12 -0
  55. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  56. package/dist/hooks/magic-context/note-nudger.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/reasoning-capability.d.ts +23 -0
  58. package/dist/hooks/magic-context/reasoning-capability.d.ts.map +1 -0
  59. package/dist/hooks/magic-context/strip-content.d.ts +3 -3
  60. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  61. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +22 -1
  62. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/transform.d.ts +12 -0
  64. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +2058 -569
  67. package/dist/plugin/dream-timer.d.ts +24 -7
  68. package/dist/plugin/dream-timer.d.ts.map +1 -1
  69. package/dist/plugin/hooks/create-session-hooks.d.ts +4 -0
  70. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  71. package/dist/plugin/tool-registry.d.ts.map +1 -1
  72. package/dist/shared/models-dev-cache.d.ts +20 -6
  73. package/dist/shared/models-dev-cache.d.ts.map +1 -1
  74. package/dist/tools/ctx-note/tools.d.ts.map +1 -1
  75. package/dist/tools/ctx-search/constants.d.ts +1 -1
  76. package/dist/tools/ctx-search/constants.d.ts.map +1 -1
  77. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  78. package/dist/tools/ctx-search/types.d.ts +8 -0
  79. package/dist/tools/ctx-search/types.d.ts.map +1 -1
  80. package/package.json +1 -1
  81. package/src/shared/models-dev-cache.test.ts +57 -6
  82. package/src/shared/models-dev-cache.ts +119 -38
package/dist/index.js CHANGED
@@ -14101,7 +14101,16 @@ var init_magic_context = __esm(() => {
14101
14101
  max_runtime_minutes: exports_external.number().min(10).default(120),
14102
14102
  tasks: exports_external.array(DreamingTaskSchema).default(DEFAULT_DREAMER_TASKS),
14103
14103
  task_timeout_minutes: exports_external.number().min(5).default(20),
14104
- inject_docs: exports_external.boolean().default(true)
14104
+ inject_docs: exports_external.boolean().default(true),
14105
+ user_memories: exports_external.object({
14106
+ enabled: exports_external.boolean().default(true),
14107
+ promotion_threshold: exports_external.number().min(2).max(20).default(3)
14108
+ }).default({ enabled: true, promotion_threshold: 3 }),
14109
+ pin_key_files: exports_external.object({
14110
+ enabled: exports_external.boolean().default(false),
14111
+ token_budget: exports_external.number().min(2000).max(30000).default(1e4),
14112
+ min_reads: exports_external.number().min(2).max(20).default(4)
14113
+ }).default({ enabled: false, token_budget: 1e4, min_reads: 4 })
14105
14114
  }));
14106
14115
  SidekickConfigSchema = AgentOverrideConfigSchema.extend({
14107
14116
  enabled: exports_external.boolean().default(false),
@@ -14196,20 +14205,21 @@ var init_magic_context = __esm(() => {
14196
14205
  model: DEFAULT_LOCAL_EMBEDDING_MODEL
14197
14206
  }),
14198
14207
  experimental: exports_external.object({
14199
- user_memories: exports_external.object({
14208
+ temporal_awareness: exports_external.boolean().default(false),
14209
+ git_commit_indexing: exports_external.object({
14200
14210
  enabled: exports_external.boolean().default(false),
14201
- promotion_threshold: exports_external.number().min(2).max(20).default(3)
14202
- }).default({ enabled: false, promotion_threshold: 3 }),
14203
- pin_key_files: exports_external.object({
14211
+ since_days: exports_external.number().min(7).max(3650).default(365),
14212
+ max_commits: exports_external.number().min(100).max(20000).default(2000)
14213
+ }).default({ enabled: false, since_days: 365, max_commits: 2000 }),
14214
+ auto_search: exports_external.object({
14204
14215
  enabled: exports_external.boolean().default(false),
14205
- token_budget: exports_external.number().min(2000).max(30000).default(1e4),
14206
- min_reads: exports_external.number().min(2).max(20).default(4)
14207
- }).default({ enabled: false, token_budget: 1e4, min_reads: 4 }),
14208
- temporal_awareness: exports_external.boolean().default(false)
14216
+ score_threshold: exports_external.number().min(0.3).max(0.95).default(0.55),
14217
+ min_prompt_chars: exports_external.number().min(5).max(500).default(20)
14218
+ }).default({ enabled: false, score_threshold: 0.55, min_prompt_chars: 20 })
14209
14219
  }).default({
14210
- user_memories: { enabled: false, promotion_threshold: 3 },
14211
- pin_key_files: { enabled: false, token_budget: 1e4, min_reads: 4 },
14212
- temporal_awareness: false
14220
+ temporal_awareness: false,
14221
+ git_commit_indexing: { enabled: false, since_days: 365, max_commits: 2000 },
14222
+ auto_search: { enabled: false, score_threshold: 0.55, min_prompt_chars: 20 }
14213
14223
  }),
14214
14224
  memory: exports_external.object({
14215
14225
  enabled: exports_external.boolean().default(true),
@@ -14603,7 +14613,7 @@ function replaceSessionFacts(db, sessionId, facts) {
14603
14613
  db.transaction(() => {
14604
14614
  db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
14605
14615
  insertFactRows(db, sessionId, facts, now);
14606
- db.prepare("UPDATE session_meta SET memory_block_cache = '' WHERE session_id = ?").run(sessionId);
14616
+ db.prepare("UPDATE session_meta SET memory_block_cache = '', memory_block_ids = '' WHERE session_id = ?").run(sessionId);
14607
14617
  })();
14608
14618
  }
14609
14619
  function getSessionFacts(db, sessionId) {
@@ -14617,7 +14627,7 @@ function replaceAllCompartmentState(db, sessionId, compartments, facts) {
14617
14627
  db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
14618
14628
  insertCompartmentRows(db, sessionId, compartments, now);
14619
14629
  insertFactRows(db, sessionId, facts, now);
14620
- db.prepare("UPDATE session_meta SET memory_block_cache = '' WHERE session_id = ?").run(sessionId);
14630
+ db.prepare("UPDATE session_meta SET memory_block_cache = '', memory_block_ids = '' WHERE session_id = ?").run(sessionId);
14621
14631
  })();
14622
14632
  }
14623
14633
  function buildCompartmentBlock(compartments, facts, memoryBlock, dateRanges) {
@@ -14705,7 +14715,7 @@ function promoteRecompStaging(db, sessionId) {
14705
14715
  }
14706
14716
  function invalidateAllMemoryBlockCaches(db) {
14707
14717
  try {
14708
- db.prepare("UPDATE session_meta SET memory_block_cache = '' WHERE memory_block_cache != ''").run();
14718
+ db.prepare("UPDATE session_meta SET memory_block_cache = '', memory_block_ids = '' WHERE memory_block_cache != '' OR memory_block_ids != ''").run();
14709
14719
  } catch {}
14710
14720
  }
14711
14721
  function clearRecompStaging(db, sessionId) {
@@ -16612,7 +16622,9 @@ class LocalEmbeddingProvider {
16612
16622
  await this.initPromise;
16613
16623
  return this.pipeline !== null;
16614
16624
  }
16615
- async embed(text) {
16625
+ async embed(text, signal) {
16626
+ if (signal?.aborted)
16627
+ return null;
16616
16628
  if (!await this.initialize()) {
16617
16629
  return null;
16618
16630
  }
@@ -16631,10 +16643,13 @@ class LocalEmbeddingProvider {
16631
16643
  return null;
16632
16644
  }
16633
16645
  }
16634
- async embedBatch(texts) {
16646
+ async embedBatch(texts, signal) {
16635
16647
  if (texts.length === 0) {
16636
16648
  return [];
16637
16649
  }
16650
+ if (signal?.aborted) {
16651
+ return Array.from({ length: texts.length }, () => null);
16652
+ }
16638
16653
  if (!await this.initialize()) {
16639
16654
  return Array.from({ length: texts.length }, () => null);
16640
16655
  }
@@ -16695,6 +16710,10 @@ class OpenAICompatibleEmbeddingProvider {
16695
16710
  model;
16696
16711
  apiKey;
16697
16712
  initialized = false;
16713
+ failureTimes = [];
16714
+ circuitOpenUntil = 0;
16715
+ openLogged = false;
16716
+ halfOpenProbeInFlight = false;
16698
16717
  constructor(options) {
16699
16718
  this.endpoint = normalizeEndpoint(options.endpoint);
16700
16719
  this.model = options.model?.trim() ?? "";
@@ -16712,18 +16731,36 @@ class OpenAICompatibleEmbeddingProvider {
16712
16731
  this.initialized = true;
16713
16732
  return true;
16714
16733
  }
16715
- async embed(text) {
16716
- const [embedding] = await this.embedBatch([text]);
16734
+ async embed(text, signal) {
16735
+ const [embedding] = await this.embedBatch([text], signal);
16717
16736
  return embedding ?? null;
16718
16737
  }
16719
- async embedBatch(texts) {
16738
+ async embedBatch(texts, signal) {
16720
16739
  if (texts.length === 0) {
16721
16740
  return [];
16722
16741
  }
16723
16742
  if (!await this.initialize()) {
16724
16743
  return Array.from({ length: texts.length }, () => null);
16725
16744
  }
16745
+ if (signal?.aborted) {
16746
+ return Array.from({ length: texts.length }, () => null);
16747
+ }
16748
+ let isProbe = false;
16749
+ let internalController;
16750
+ let timeoutHandle;
16751
+ let onOuterAbort;
16726
16752
  try {
16753
+ const claim = this.claimProbeOrShortCircuit();
16754
+ if (claim === "short_circuit") {
16755
+ return Array.from({ length: texts.length }, () => null);
16756
+ }
16757
+ isProbe = claim === "probe";
16758
+ internalController = new AbortController;
16759
+ timeoutHandle = setTimeout(() => internalController?.abort(), FETCH_TIMEOUT_MS);
16760
+ onOuterAbort = () => internalController?.abort();
16761
+ if (signal) {
16762
+ signal.addEventListener("abort", onOuterAbort, { once: true });
16763
+ }
16727
16764
  const response = await fetch(`${this.endpoint}/embeddings`, {
16728
16765
  method: "POST",
16729
16766
  headers: {
@@ -16733,21 +16770,48 @@ class OpenAICompatibleEmbeddingProvider {
16733
16770
  body: JSON.stringify({
16734
16771
  model: this.model,
16735
16772
  input: texts
16736
- })
16773
+ }),
16774
+ signal: internalController.signal
16737
16775
  });
16738
16776
  if (!response.ok) {
16739
16777
  log(`[magic-context] openai-compatible embedding request failed: ${response.status} ${response.statusText}`);
16778
+ this.recordFailure(isProbe);
16740
16779
  return Array.from({ length: texts.length }, () => null);
16741
16780
  }
16742
16781
  const body = await response.json();
16743
16782
  const items = Array.isArray(body.data) ? body.data : [];
16744
- return Array.from({ length: texts.length }, (_, index) => {
16783
+ const results = Array.from({ length: texts.length }, (_, index) => {
16745
16784
  const embedding = items[index]?.embedding;
16746
16785
  return Array.isArray(embedding) ? Float32Array.from(embedding) : null;
16747
16786
  });
16787
+ if (results.every((r) => r === null)) {
16788
+ this.recordFailure(isProbe);
16789
+ } else {
16790
+ this.recordSuccess();
16791
+ }
16792
+ return results;
16748
16793
  } catch (error48) {
16749
- log("[magic-context] openai-compatible embedding request failed:", error48);
16794
+ const isAbort = error48 instanceof Error && (error48.name === "AbortError" || error48.message.includes("aborted"));
16795
+ if (isAbort) {
16796
+ if (signal?.aborted) {} else {
16797
+ log(`[magic-context] openai-compatible embedding request timed out after ${FETCH_TIMEOUT_MS}ms`);
16798
+ this.recordFailure(isProbe);
16799
+ }
16800
+ } else {
16801
+ log("[magic-context] openai-compatible embedding request failed:", error48);
16802
+ this.recordFailure(isProbe);
16803
+ }
16750
16804
  return Array.from({ length: texts.length }, () => null);
16805
+ } finally {
16806
+ if (timeoutHandle !== undefined) {
16807
+ clearTimeout(timeoutHandle);
16808
+ }
16809
+ if (signal && onOuterAbort) {
16810
+ signal.removeEventListener("abort", onOuterAbort);
16811
+ }
16812
+ if (isProbe) {
16813
+ this.halfOpenProbeInFlight = false;
16814
+ }
16751
16815
  }
16752
16816
  }
16753
16817
  async dispose() {
@@ -16756,9 +16820,73 @@ class OpenAICompatibleEmbeddingProvider {
16756
16820
  isLoaded() {
16757
16821
  return this.initialized;
16758
16822
  }
16823
+ claimProbeOrShortCircuit() {
16824
+ if (this.circuitOpenUntil === 0) {
16825
+ return "allow";
16826
+ }
16827
+ if (Date.now() < this.circuitOpenUntil) {
16828
+ return "short_circuit";
16829
+ }
16830
+ if (this.halfOpenProbeInFlight) {
16831
+ return "short_circuit";
16832
+ }
16833
+ this.halfOpenProbeInFlight = true;
16834
+ log("[magic-context] openai-compatible embedding: circuit half-open, probing endpoint");
16835
+ return "probe";
16836
+ }
16837
+ recordFailure(isProbe) {
16838
+ if (isProbe) {
16839
+ this.circuitOpenUntil = Date.now() + OPEN_DURATION_MS;
16840
+ if (!this.openLogged) {
16841
+ log(`[magic-context] openai-compatible embedding: probe failed, re-opening circuit for ${OPEN_DURATION_MS / 60000}min`);
16842
+ this.openLogged = true;
16843
+ }
16844
+ this.failureTimes = [];
16845
+ return;
16846
+ }
16847
+ const now = Date.now();
16848
+ const cutoff = now - FAILURE_WINDOW_MS;
16849
+ this.failureTimes = this.failureTimes.filter((t) => t > cutoff);
16850
+ this.failureTimes.push(now);
16851
+ if (this.failureTimes.length >= FAILURE_THRESHOLD) {
16852
+ this.circuitOpenUntil = now + OPEN_DURATION_MS;
16853
+ if (!this.openLogged) {
16854
+ log(`[magic-context] openai-compatible embedding: opening circuit for ${OPEN_DURATION_MS / 60000}min after ${this.failureTimes.length} failures in ${FAILURE_WINDOW_MS / 1000}s`);
16855
+ this.openLogged = true;
16856
+ }
16857
+ this.failureTimes = [];
16858
+ }
16859
+ }
16860
+ recordSuccess() {
16861
+ if (this.failureTimes.length > 0 || this.circuitOpenUntil > 0 || this.openLogged) {
16862
+ log("[magic-context] openai-compatible embedding: endpoint recovered, circuit closed");
16863
+ }
16864
+ this.failureTimes = [];
16865
+ this.circuitOpenUntil = 0;
16866
+ this.openLogged = false;
16867
+ }
16868
+ _getCircuitState() {
16869
+ if (this.circuitOpenUntil === 0)
16870
+ return "closed";
16871
+ if (Date.now() < this.circuitOpenUntil) {
16872
+ return this.halfOpenProbeInFlight ? "half_open" : "open";
16873
+ }
16874
+ return "half_open";
16875
+ }
16876
+ _getFailureCount() {
16877
+ return this.failureTimes.length;
16878
+ }
16879
+ _resetCircuit() {
16880
+ this.failureTimes = [];
16881
+ this.circuitOpenUntil = 0;
16882
+ this.openLogged = false;
16883
+ this.halfOpenProbeInFlight = false;
16884
+ }
16759
16885
  }
16886
+ var FAILURE_THRESHOLD = 3, FAILURE_WINDOW_MS = 60000, OPEN_DURATION_MS, FETCH_TIMEOUT_MS = 30000;
16760
16887
  var init_embedding_openai = __esm(() => {
16761
16888
  init_logger();
16889
+ OPEN_DURATION_MS = 5 * 60000;
16762
16890
  });
16763
16891
 
16764
16892
  // src/features/magic-context/memory/embedding.ts
@@ -16847,7 +16975,7 @@ function initializeEmbedding(config2) {
16847
16975
  function isEmbeddingEnabled() {
16848
16976
  return embeddingConfig.provider !== "off";
16849
16977
  }
16850
- async function embedText(text) {
16978
+ async function embedText(text, signal) {
16851
16979
  const currentProvider = getOrCreateProvider();
16852
16980
  if (!currentProvider) {
16853
16981
  return null;
@@ -16855,9 +16983,9 @@ async function embedText(text) {
16855
16983
  if (!await currentProvider.initialize()) {
16856
16984
  return null;
16857
16985
  }
16858
- return currentProvider.embed(text);
16986
+ return currentProvider.embed(text, signal);
16859
16987
  }
16860
- async function embedBatch(texts) {
16988
+ async function embedBatch(texts, signal) {
16861
16989
  if (texts.length === 0) {
16862
16990
  return [];
16863
16991
  }
@@ -16868,7 +16996,7 @@ async function embedBatch(texts) {
16868
16996
  if (!await currentProvider.initialize()) {
16869
16997
  return Array.from({ length: texts.length }, () => null);
16870
16998
  }
16871
- return currentProvider.embedBatch(texts);
16999
+ return currentProvider.embedBatch(texts, signal);
16872
17000
  }
16873
17001
  async function embedAllUnembeddedMemories(db, config2, batchSize = 10) {
16874
17002
  if (sweepInProgress) {
@@ -16971,6 +17099,79 @@ var init_embedding = __esm(() => {
16971
17099
  SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
16972
17100
  });
16973
17101
 
17102
+ // src/features/magic-context/memory/storage-memory-fts.ts
17103
+ function getSearchStatement(db) {
17104
+ let stmt = searchStatements.get(db);
17105
+ if (!stmt) {
17106
+ const selectColumns = Object.entries(COLUMN_MAP).map(([property, column]) => `memories.${column} AS ${property}`).join(", ");
17107
+ stmt = db.prepare(`SELECT ${selectColumns} FROM memories_fts INNER JOIN memories ON memories.id = memories_fts.rowid WHERE memories.project_path = ? AND memories.status IN ('active', 'permanent') AND (memories.expires_at IS NULL OR memories.expires_at > ?) AND memories_fts MATCH ? ORDER BY bm25(memories_fts), memories.updated_at DESC, memories.id ASC LIMIT ?`);
17108
+ searchStatements.set(db, stmt);
17109
+ }
17110
+ return stmt;
17111
+ }
17112
+ function sanitizeFtsQuery(query) {
17113
+ const tokens = query.split(/\s+/).filter((token) => token.length > 0);
17114
+ if (tokens.length === 0)
17115
+ return "";
17116
+ return tokens.map((token) => `"${token.replace(/"/g, '""')}"`).join(" ");
17117
+ }
17118
+ function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT) {
17119
+ const trimmedQuery = query.trim();
17120
+ if (trimmedQuery.length === 0 || limit <= 0) {
17121
+ return [];
17122
+ }
17123
+ const sanitized = sanitizeFtsQuery(trimmedQuery);
17124
+ if (sanitized.length === 0) {
17125
+ return [];
17126
+ }
17127
+ const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
17128
+ return rows.map(toMemory);
17129
+ }
17130
+ var DEFAULT_SEARCH_LIMIT = 10, searchStatements;
17131
+ var init_storage_memory_fts = __esm(() => {
17132
+ init_storage_memory();
17133
+ searchStatements = new WeakMap;
17134
+ });
17135
+
17136
+ // src/features/magic-context/memory/project-identity.ts
17137
+ import { execSync } from "child_process";
17138
+ import path3 from "path";
17139
+ function getRootCommitHash(directory) {
17140
+ try {
17141
+ const hash2 = execSync("git rev-list --max-parents=0 HEAD", {
17142
+ cwd: directory,
17143
+ encoding: "utf-8",
17144
+ stdio: ["pipe", "pipe", "pipe"],
17145
+ timeout: GIT_TIMEOUT_MS2
17146
+ }).trim();
17147
+ const firstLine = hash2.split(`
17148
+ `)[0]?.trim();
17149
+ return firstLine && firstLine.length >= 7 ? firstLine : undefined;
17150
+ } catch {
17151
+ return;
17152
+ }
17153
+ }
17154
+ function directoryFallback(directory) {
17155
+ const canonical = path3.resolve(directory);
17156
+ const hash2 = Bun.hash(canonical).toString(16).slice(0, 12);
17157
+ return `dir:${hash2}`;
17158
+ }
17159
+ function resolveProjectIdentity(directory) {
17160
+ const resolved = path3.resolve(directory);
17161
+ const cached2 = resolvedCache.get(resolved);
17162
+ if (cached2 !== undefined) {
17163
+ return cached2;
17164
+ }
17165
+ const rootHash = getRootCommitHash(resolved);
17166
+ const identity = rootHash ? `git:${rootHash}` : directoryFallback(resolved);
17167
+ resolvedCache.set(resolved, identity);
17168
+ return identity;
17169
+ }
17170
+ var GIT_TIMEOUT_MS2 = 5000, resolvedCache;
17171
+ var init_project_identity = __esm(() => {
17172
+ resolvedCache = new Map;
17173
+ });
17174
+
16974
17175
  // src/features/magic-context/compression-depth-storage.ts
16975
17176
  function getIncrementDepthStatement(db) {
16976
17177
  let stmt = incrementDepthStatements.get(db);
@@ -150227,6 +150428,62 @@ var init_migrations = __esm(() => {
150227
150428
  CREATE INDEX IF NOT EXISTS idx_um_status ON user_memories(status);
150228
150429
  `);
150229
150430
  }
150431
+ },
150432
+ {
150433
+ version: 4,
150434
+ description: "Add git_commits + git_commit_embeddings + git_commits_fts tables",
150435
+ up: (db) => {
150436
+ db.exec(`
150437
+ CREATE TABLE IF NOT EXISTS git_commits (
150438
+ sha TEXT PRIMARY KEY,
150439
+ project_path TEXT NOT NULL,
150440
+ short_sha TEXT NOT NULL,
150441
+ message TEXT NOT NULL,
150442
+ author TEXT,
150443
+ committed_at INTEGER NOT NULL,
150444
+ indexed_at INTEGER NOT NULL
150445
+ );
150446
+ CREATE INDEX IF NOT EXISTS idx_git_commits_project_time
150447
+ ON git_commits(project_path, committed_at DESC);
150448
+
150449
+ CREATE TABLE IF NOT EXISTS git_commit_embeddings (
150450
+ sha TEXT PRIMARY KEY,
150451
+ embedding BLOB NOT NULL,
150452
+ model_id TEXT NOT NULL,
150453
+ created_at INTEGER NOT NULL,
150454
+ FOREIGN KEY(sha) REFERENCES git_commits(sha) ON DELETE CASCADE
150455
+ );
150456
+
150457
+ CREATE VIRTUAL TABLE IF NOT EXISTS git_commits_fts USING fts5(
150458
+ sha UNINDEXED,
150459
+ project_path UNINDEXED,
150460
+ message,
150461
+ tokenize = 'porter unicode61'
150462
+ );
150463
+
150464
+ -- Mirror writes into FTS. We intentionally rebuild FTS rows on
150465
+ -- every INSERT OR REPLACE so amended commits or re-indexed
150466
+ -- messages update cleanly.
150467
+ CREATE TRIGGER IF NOT EXISTS git_commits_fts_insert
150468
+ AFTER INSERT ON git_commits BEGIN
150469
+ DELETE FROM git_commits_fts WHERE sha = NEW.sha;
150470
+ INSERT INTO git_commits_fts(sha, project_path, message)
150471
+ VALUES (NEW.sha, NEW.project_path, NEW.message);
150472
+ END;
150473
+
150474
+ CREATE TRIGGER IF NOT EXISTS git_commits_fts_delete
150475
+ AFTER DELETE ON git_commits BEGIN
150476
+ DELETE FROM git_commits_fts WHERE sha = OLD.sha;
150477
+ END;
150478
+
150479
+ CREATE TRIGGER IF NOT EXISTS git_commits_fts_update
150480
+ AFTER UPDATE OF message, project_path ON git_commits BEGIN
150481
+ DELETE FROM git_commits_fts WHERE sha = OLD.sha;
150482
+ INSERT INTO git_commits_fts(sha, project_path, message)
150483
+ VALUES (NEW.sha, NEW.project_path, NEW.message);
150484
+ END;
150485
+ `);
150486
+ }
150230
150487
  }
150231
150488
  ];
150232
150489
  });
@@ -150449,7 +150706,8 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
150449
150706
  historian_last_failure_at INTEGER DEFAULT NULL,
150450
150707
  system_prompt_hash TEXT DEFAULT '',
150451
150708
  memory_block_cache TEXT DEFAULT '',
150452
- memory_block_count INTEGER DEFAULT 0
150709
+ memory_block_count INTEGER DEFAULT 0,
150710
+ memory_block_ids TEXT DEFAULT ''
150453
150711
  );
150454
150712
 
150455
150713
  CREATE INDEX IF NOT EXISTS idx_tags_session_tag_number ON tags(session_id, tag_number);
@@ -150500,6 +150758,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
150500
150758
  ensureColumn(db, "session_meta", "note_nudge_trigger_message_id", "TEXT DEFAULT ''");
150501
150759
  ensureColumn(db, "session_meta", "note_nudge_sticky_text", "TEXT DEFAULT ''");
150502
150760
  ensureColumn(db, "session_meta", "note_nudge_sticky_message_id", "TEXT DEFAULT ''");
150761
+ ensureColumn(db, "session_meta", "note_last_read_at", "INTEGER DEFAULT 0");
150503
150762
  ensureColumn(db, "session_meta", "times_execute_threshold_reached", "INTEGER DEFAULT 0");
150504
150763
  ensureColumn(db, "session_meta", "compartment_in_progress", "INTEGER DEFAULT 0");
150505
150764
  ensureColumn(db, "session_meta", "historian_failure_count", "INTEGER DEFAULT 0");
@@ -150513,6 +150772,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
150513
150772
  ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
150514
150773
  ensureColumn(db, "session_meta", "memory_block_cache", "TEXT DEFAULT ''");
150515
150774
  ensureColumn(db, "session_meta", "memory_block_count", "INTEGER DEFAULT 0");
150775
+ ensureColumn(db, "session_meta", "memory_block_ids", "TEXT DEFAULT ''");
150516
150776
  ensureColumn(db, "dream_queue", "retry_count", "INTEGER DEFAULT 0");
150517
150777
  ensureColumn(db, "tags", "reasoning_byte_size", "INTEGER DEFAULT 0");
150518
150778
  ensureColumn(db, "tags", "drop_mode", "TEXT DEFAULT 'full'");
@@ -150525,8 +150785,16 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
150525
150785
  ensureColumn(db, "session_meta", "tool_call_tokens", "INTEGER DEFAULT 0");
150526
150786
  ensureColumn(db, "session_meta", "recomp_partial_range_start", "INTEGER DEFAULT 0");
150527
150787
  ensureColumn(db, "session_meta", "recomp_partial_range_end", "INTEGER DEFAULT 0");
150788
+ ensureColumn(db, "session_meta", "detected_context_limit", "INTEGER DEFAULT 0");
150789
+ ensureColumn(db, "session_meta", "needs_emergency_recovery", "INTEGER DEFAULT 0");
150528
150790
  healNullTextColumns(db);
150529
150791
  healNullIntegerColumns(db);
150792
+ healMissingMemoryBlockIds(db);
150793
+ }
150794
+ function healMissingMemoryBlockIds(db) {
150795
+ try {
150796
+ db.prepare("UPDATE session_meta SET memory_block_cache = '' WHERE memory_block_cache != '' AND (memory_block_ids IS NULL OR memory_block_ids = '') AND memory_block_count > 0").run();
150797
+ } catch {}
150530
150798
  }
150531
150799
  function healNullTextColumns(db) {
150532
150800
  const columns = [
@@ -150543,6 +150811,7 @@ function healNullTextColumns(db) {
150543
150811
  ["system_prompt_hash", ""],
150544
150812
  ["stripped_placeholder_ids", ""],
150545
150813
  ["memory_block_cache", ""],
150814
+ ["memory_block_ids", ""],
150546
150815
  ["compaction_marker_state", ""],
150547
150816
  ["key_files", ""]
150548
150817
  ];
@@ -150802,6 +151071,9 @@ function setPersistedReasoningWatermark(db, sessionId, tagNumber) {
150802
151071
  ensureSessionMetaRow(db, sessionId);
150803
151072
  db.prepare("UPDATE session_meta SET cleared_reasoning_through_tag = ? WHERE session_id = ?").run(tagNumber, sessionId);
150804
151073
  }
151074
+ function clearPersistedReasoningWatermark(db, sessionId) {
151075
+ setPersistedReasoningWatermark(db, sessionId, 0);
151076
+ }
150805
151077
  function getPersistedNudgePlacement(db, sessionId) {
150806
151078
  const result = db.prepare("SELECT nudge_anchor_message_id, nudge_anchor_text FROM session_meta WHERE session_id = ?").get(sessionId);
150807
151079
  if (!isPersistedNudgePlacementRow(result)) {
@@ -150879,6 +151151,23 @@ function setPersistedDeliveredNoteNudge(db, sessionId, text, messageId = "") {
150879
151151
  function clearPersistedNoteNudge(db, sessionId) {
150880
151152
  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);
150881
151153
  }
151154
+ function getNoteLastReadAt(db, sessionId) {
151155
+ try {
151156
+ const result = db.prepare("SELECT note_last_read_at FROM session_meta WHERE session_id = ?").get(sessionId);
151157
+ if (!result || typeof result !== "object")
151158
+ return 0;
151159
+ const value = result.note_last_read_at;
151160
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
151161
+ } catch {
151162
+ return 0;
151163
+ }
151164
+ }
151165
+ function setNoteLastReadAt(db, sessionId, at = Date.now()) {
151166
+ db.transaction(() => {
151167
+ ensureSessionMetaRow(db, sessionId);
151168
+ db.prepare("UPDATE session_meta SET note_last_read_at = ? WHERE session_id = ?").run(at, sessionId);
151169
+ })();
151170
+ }
150882
151171
  function getHistorianFailureState(db, sessionId) {
150883
151172
  const result = db.prepare("SELECT historian_failure_count, historian_last_error, historian_last_failure_at FROM session_meta WHERE session_id = ?").get(sessionId);
150884
151173
  if (!isPersistedHistorianFailureRow(result)) {
@@ -150903,6 +151192,37 @@ function clearHistorianFailureState(db, sessionId) {
150903
151192
  db.prepare("UPDATE session_meta SET historian_failure_count = 0, historian_last_error = NULL, historian_last_failure_at = NULL WHERE session_id = ?").run(sessionId);
150904
151193
  })();
150905
151194
  }
151195
+ function getOverflowState(db, sessionId) {
151196
+ const result = db.prepare("SELECT detected_context_limit, needs_emergency_recovery FROM session_meta WHERE session_id = ?").get(sessionId);
151197
+ if (!result) {
151198
+ return { detectedContextLimit: 0, needsEmergencyRecovery: false };
151199
+ }
151200
+ const limit = typeof result.detected_context_limit === "number" && result.detected_context_limit > 0 ? result.detected_context_limit : 0;
151201
+ const needs = typeof result.needs_emergency_recovery === "number" && result.needs_emergency_recovery > 0;
151202
+ return { detectedContextLimit: limit, needsEmergencyRecovery: needs };
151203
+ }
151204
+ function recordOverflowDetected(db, sessionId, reportedLimit) {
151205
+ db.transaction(() => {
151206
+ ensureSessionMetaRow(db, sessionId);
151207
+ if (typeof reportedLimit === "number" && reportedLimit > 0) {
151208
+ db.prepare("UPDATE session_meta SET detected_context_limit = ?, needs_emergency_recovery = 1 WHERE session_id = ?").run(reportedLimit, sessionId);
151209
+ } else {
151210
+ db.prepare("UPDATE session_meta SET needs_emergency_recovery = 1 WHERE session_id = ?").run(sessionId);
151211
+ }
151212
+ })();
151213
+ }
151214
+ function clearEmergencyRecovery(db, sessionId) {
151215
+ db.transaction(() => {
151216
+ ensureSessionMetaRow(db, sessionId);
151217
+ db.prepare("UPDATE session_meta SET needs_emergency_recovery = 0 WHERE session_id = ?").run(sessionId);
151218
+ })();
151219
+ }
151220
+ function clearDetectedContextLimit(db, sessionId) {
151221
+ db.transaction(() => {
151222
+ ensureSessionMetaRow(db, sessionId);
151223
+ db.prepare("UPDATE session_meta SET detected_context_limit = 0 WHERE session_id = ?").run(sessionId);
151224
+ })();
151225
+ }
150906
151226
  function getPersistedCompactionMarkerState(db, sessionId) {
150907
151227
  const row = db.prepare("SELECT compaction_marker_state FROM session_meta WHERE session_id = ?").get(sessionId);
150908
151228
  const raw = row?.compaction_marker_state;
@@ -151308,8 +151628,33 @@ function resolveLimit(limit) {
151308
151628
  return limit.context;
151309
151629
  return;
151310
151630
  }
151311
- function loadModelsDevLimitsFromFile() {
151312
- const limits = new Map;
151631
+ function resolveInterleavedField(interleaved) {
151632
+ if (interleaved && typeof interleaved === "object" && typeof interleaved.field === "string" && interleaved.field.length > 0) {
151633
+ return interleaved.field;
151634
+ }
151635
+ return;
151636
+ }
151637
+ function setCachedModelMetadata(cache, key, model) {
151638
+ const limit = resolveLimit(model?.limit);
151639
+ const interleavedField = resolveInterleavedField(model?.capabilities?.interleaved) ?? resolveInterleavedField(model?.interleaved);
151640
+ if (limit === undefined && interleavedField === undefined) {
151641
+ return;
151642
+ }
151643
+ const value = {};
151644
+ if (limit !== undefined)
151645
+ value.limit = limit;
151646
+ if (interleavedField !== undefined)
151647
+ value.interleavedField = interleavedField;
151648
+ cache.set(key, value);
151649
+ const modes = model?.experimental?.modes;
151650
+ if (modes && typeof modes === "object") {
151651
+ for (const mode of Object.keys(modes)) {
151652
+ cache.set(`${key}-${mode}`, value);
151653
+ }
151654
+ }
151655
+ }
151656
+ function loadModelsDevMetadataFromFile() {
151657
+ const metadata = new Map;
151313
151658
  const modelsJsonPath = getModelsJsonPath();
151314
151659
  let fileFound = false;
151315
151660
  try {
@@ -151321,16 +151666,7 @@ function loadModelsDevLimitsFromFile() {
151321
151666
  if (!provider2?.models || typeof provider2.models !== "object")
151322
151667
  continue;
151323
151668
  for (const [modelId, model] of Object.entries(provider2.models)) {
151324
- const effective = resolveLimit(model?.limit);
151325
- if (typeof effective === "number" && effective > 0) {
151326
- limits.set(`${providerId}/${modelId}`, effective);
151327
- const modes = model?.experimental?.modes;
151328
- if (modes && typeof modes === "object") {
151329
- for (const mode of Object.keys(modes)) {
151330
- limits.set(`${providerId}/${modelId}-${mode}`, effective);
151331
- }
151332
- }
151333
- }
151669
+ setCachedModelMetadata(metadata, `${providerId}/${modelId}`, model);
151334
151670
  }
151335
151671
  }
151336
151672
  }
@@ -151348,10 +151684,7 @@ function loadModelsDevLimitsFromFile() {
151348
151684
  if (!provider2?.models || typeof provider2.models !== "object")
151349
151685
  continue;
151350
151686
  for (const [modelId, model] of Object.entries(provider2.models)) {
151351
- const effective = resolveLimit(model?.limit);
151352
- if (typeof effective === "number" && effective > 0) {
151353
- limits.set(`${providerId}/${modelId}`, effective);
151354
- }
151687
+ setCachedModelMetadata(metadata, `${providerId}/${modelId}`, model);
151355
151688
  }
151356
151689
  }
151357
151690
  }
@@ -151359,8 +151692,8 @@ function loadModelsDevLimitsFromFile() {
151359
151692
  } catch (error48) {
151360
151693
  sessionLog("global", "models-dev-cache: failed to read opencode config for custom models:", error48 instanceof Error ? error48.message : String(error48));
151361
151694
  }
151362
- sessionLog("global", `models-dev-cache: file-layer loaded ${limits.size} model limits (modelsJsonPath=${modelsJsonPath}, found=${fileFound})`);
151363
- return limits;
151695
+ sessionLog("global", `models-dev-cache: file-layer loaded ${metadata.size} model metadata entries (modelsJsonPath=${modelsJsonPath}, found=${fileFound})`);
151696
+ return metadata;
151364
151697
  }
151365
151698
  async function refreshModelLimitsFromApi(client) {
151366
151699
  try {
@@ -151377,17 +151710,14 @@ async function refreshModelLimitsFromApi(client) {
151377
151710
  if (!p?.id || !p.models || typeof p.models !== "object")
151378
151711
  continue;
151379
151712
  for (const [modelId, model] of Object.entries(p.models)) {
151380
- const effective = resolveLimit(model?.limit);
151381
- if (typeof effective === "number" && effective > 0) {
151382
- map2.set(`${p.id}/${modelId}`, effective);
151383
- }
151713
+ setCachedModelMetadata(map2, `${p.id}/${modelId}`, model);
151384
151714
  }
151385
151715
  }
151386
151716
  const previousSize = apiCache?.size ?? null;
151387
151717
  apiCache = map2;
151388
151718
  apiLoadedAt = Date.now();
151389
151719
  if (previousSize === null || previousSize !== map2.size) {
151390
- sessionLog("global", `models-dev-cache: API layer loaded ${map2.size} model limits${previousSize !== null ? ` (was ${previousSize})` : ""}`);
151720
+ sessionLog("global", `models-dev-cache: API layer loaded ${map2.size} model metadata entries${previousSize !== null ? ` (was ${previousSize})` : ""}`);
151391
151721
  }
151392
151722
  } catch (error48) {
151393
151723
  sessionLog("global", "models-dev-cache: API refresh failed:", error48 instanceof Error ? error48.message : String(error48));
@@ -151396,16 +151726,32 @@ async function refreshModelLimitsFromApi(client) {
151396
151726
  function getModelsDevContextLimit(providerID, modelID) {
151397
151727
  const key = `${providerID}/${modelID}`;
151398
151728
  if (apiCache) {
151399
- const fromApi = apiCache.get(key);
151729
+ const fromApi = apiCache.get(key)?.limit;
151400
151730
  if (typeof fromApi === "number")
151401
151731
  return fromApi;
151402
151732
  }
151403
151733
  const now = Date.now();
151404
151734
  if (!fileCache || now - fileLastAttempt > RELOAD_INTERVAL_MS) {
151405
151735
  fileLastAttempt = now;
151406
- fileCache = loadModelsDevLimitsFromFile();
151736
+ fileCache = loadModelsDevMetadataFromFile();
151407
151737
  }
151408
- return fileCache.get(key);
151738
+ return fileCache.get(key)?.limit;
151739
+ }
151740
+ function getModelsDevInterleavedField(providerID, modelID) {
151741
+ const key = `${providerID}/${modelID}`;
151742
+ if (apiCache) {
151743
+ const fromApi = apiCache.get(key)?.interleavedField;
151744
+ if (typeof fromApi === "string" && fromApi.length > 0) {
151745
+ return fromApi;
151746
+ }
151747
+ }
151748
+ const now = Date.now();
151749
+ if (!fileCache || now - fileLastAttempt > RELOAD_INTERVAL_MS) {
151750
+ fileLastAttempt = now;
151751
+ fileCache = loadModelsDevMetadataFromFile();
151752
+ }
151753
+ const fromFile = fileCache.get(key)?.interleavedField;
151754
+ return typeof fromFile === "string" && fromFile.length > 0 ? fromFile : undefined;
151409
151755
  }
151410
151756
  var RELOAD_INTERVAL_MS, apiCache = null, apiLoadedAt = 0, fileCache = null, fileLastAttempt = 0;
151411
151757
  var init_models_dev_cache = __esm(() => {
@@ -151413,45 +151759,6 @@ var init_models_dev_cache = __esm(() => {
151413
151759
  RELOAD_INTERVAL_MS = 5 * 60 * 1000;
151414
151760
  });
151415
151761
 
151416
- // src/features/magic-context/memory/project-identity.ts
151417
- import { execSync } from "child_process";
151418
- import path3 from "path";
151419
- function getRootCommitHash(directory) {
151420
- try {
151421
- const hash2 = execSync("git rev-list --max-parents=0 HEAD", {
151422
- cwd: directory,
151423
- encoding: "utf-8",
151424
- stdio: ["pipe", "pipe", "pipe"],
151425
- timeout: GIT_TIMEOUT_MS
151426
- }).trim();
151427
- const firstLine = hash2.split(`
151428
- `)[0]?.trim();
151429
- return firstLine && firstLine.length >= 7 ? firstLine : undefined;
151430
- } catch {
151431
- return;
151432
- }
151433
- }
151434
- function directoryFallback(directory) {
151435
- const canonical = path3.resolve(directory);
151436
- const hash2 = Bun.hash(canonical).toString(16).slice(0, 12);
151437
- return `dir:${hash2}`;
151438
- }
151439
- function resolveProjectIdentity(directory) {
151440
- const resolved = path3.resolve(directory);
151441
- const cached2 = resolvedCache.get(resolved);
151442
- if (cached2 !== undefined) {
151443
- return cached2;
151444
- }
151445
- const rootHash = getRootCommitHash(resolved);
151446
- const identity = rootHash ? `git:${rootHash}` : directoryFallback(resolved);
151447
- resolvedCache.set(resolved, identity);
151448
- return identity;
151449
- }
151450
- var GIT_TIMEOUT_MS = 5000, resolvedCache;
151451
- var init_project_identity = __esm(() => {
151452
- resolvedCache = new Map;
151453
- });
151454
-
151455
151762
  // src/shared/rpc-notifications.ts
151456
151763
  var exports_rpc_notifications = {};
151457
151764
  __export(exports_rpc_notifications, {
@@ -152141,6 +152448,25 @@ var init_temporal_awareness = __esm(() => {
152141
152448
  function clearInjectionCache(sessionId) {
152142
152449
  injectionCache.delete(sessionId);
152143
152450
  }
152451
+ function getVisibleMemoryIds(db, sessionId) {
152452
+ try {
152453
+ const row = db.prepare("SELECT memory_block_ids FROM session_meta WHERE session_id = ?").get(sessionId);
152454
+ if (!row?.memory_block_ids)
152455
+ return null;
152456
+ const parsed = JSON.parse(row.memory_block_ids);
152457
+ if (!Array.isArray(parsed))
152458
+ return null;
152459
+ const ids = new Set;
152460
+ for (const value of parsed) {
152461
+ if (typeof value === "number" && Number.isFinite(value)) {
152462
+ ids.add(value);
152463
+ }
152464
+ }
152465
+ return ids.size > 0 ? ids : null;
152466
+ } catch {
152467
+ return null;
152468
+ }
152469
+ }
152144
152470
  function renderMemoryBlock(memories) {
152145
152471
  const byCategory = new Map;
152146
152472
  for (const m of memories) {
@@ -152236,8 +152562,9 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
152236
152562
  }
152237
152563
  memoryCount = memories.length;
152238
152564
  memoryBlock = renderMemoryBlock(memories) ?? undefined;
152565
+ const renderedIds = memories.map((m) => m.id);
152239
152566
  try {
152240
- db.prepare("UPDATE session_meta SET memory_block_cache = ?, memory_block_count = ? WHERE session_id = ?").run(memoryBlock ?? "", memoryCount, sessionId);
152567
+ db.prepare("UPDATE session_meta SET memory_block_cache = ?, memory_block_count = ?, memory_block_ids = ? WHERE session_id = ?").run(memoryBlock ?? "", memoryCount, JSON.stringify(renderedIds), sessionId);
152241
152568
  } catch (error48) {
152242
152569
  const code = error48?.code;
152243
152570
  if (code === "SQLITE_BUSY") {
@@ -153084,23 +153411,42 @@ function peekNoteNudgeText(db, sessionId, currentUserMessageId, projectIdentity)
153084
153411
  return null;
153085
153412
  }
153086
153413
  const notes = getSessionNotes(db, sessionId);
153087
- const readySmartCount = projectIdentity ? getReadySmartNotes(db, projectIdentity).length : 0;
153088
- const totalCount = notes.length + readySmartCount;
153414
+ const readySmartNotes = projectIdentity ? getReadySmartNotes(db, projectIdentity) : [];
153415
+ const totalCount = notes.length + readySmartNotes.length;
153089
153416
  if (totalCount === 0) {
153090
153417
  sessionLog(sessionId, "note-nudge: triggerPending but no notes found, skipping");
153091
153418
  clearPersistedNoteNudge(db, sessionId);
153092
153419
  return null;
153093
153420
  }
153421
+ const lastReadAt = getNoteLastReadAt(db, sessionId);
153422
+ if (lastReadAt > 0) {
153423
+ const mostRecentNoteActivity = maxNoteActivityTime([...notes, ...readySmartNotes]);
153424
+ if (mostRecentNoteActivity > 0 && lastReadAt > mostRecentNoteActivity) {
153425
+ sessionLog(sessionId, `note-nudge: suppressing \u2014 agent already ran ctx_note(read) at ${new Date(lastReadAt).toISOString()}, no new notes since ${new Date(mostRecentNoteActivity).toISOString()}`);
153426
+ clearPersistedNoteNudge(db, sessionId);
153427
+ return null;
153428
+ }
153429
+ }
153094
153430
  const parts = [];
153095
153431
  if (notes.length > 0) {
153096
153432
  parts.push(`${notes.length} deferred note${notes.length === 1 ? "" : "s"}`);
153097
153433
  }
153098
- if (readySmartCount > 0) {
153099
- parts.push(`${readySmartCount} ready smart note${readySmartCount === 1 ? "" : "s"}`);
153434
+ if (readySmartNotes.length > 0) {
153435
+ parts.push(`${readySmartNotes.length} ready smart note${readySmartNotes.length === 1 ? "" : "s"}`);
153100
153436
  }
153101
153437
  sessionLog(sessionId, `note-nudge: delivering nudge for ${parts.join(" and ")}`);
153102
153438
  return `You have ${parts.join(" and ")}. Review with ctx_note read \u2014 some may be actionable now.`;
153103
153439
  }
153440
+ function maxNoteActivityTime(notes) {
153441
+ let max = 0;
153442
+ for (const note of notes) {
153443
+ if (note.updatedAt > max)
153444
+ max = note.updatedAt;
153445
+ if (note.readyAt !== null && note.readyAt > max)
153446
+ max = note.readyAt;
153447
+ }
153448
+ return max;
153449
+ }
153104
153450
  function markNoteNudgeDelivered(db, sessionId, text, messageId) {
153105
153451
  setPersistedDeliveredNoteNudge(db, sessionId, messageId ? text : "", messageId ?? "");
153106
153452
  recordNoteNudgeDeliveryTime(sessionId);
@@ -153216,40 +153562,6 @@ var init_promotion = __esm(() => {
153216
153562
  init_storage_memory();
153217
153563
  init_storage_memory_embeddings();
153218
153564
  });
153219
-
153220
- // src/features/magic-context/memory/storage-memory-fts.ts
153221
- function getSearchStatement(db) {
153222
- let stmt = searchStatements.get(db);
153223
- if (!stmt) {
153224
- const selectColumns = Object.entries(COLUMN_MAP).map(([property, column]) => `memories.${column} AS ${property}`).join(", ");
153225
- stmt = db.prepare(`SELECT ${selectColumns} FROM memories_fts INNER JOIN memories ON memories.id = memories_fts.rowid WHERE memories.project_path = ? AND memories.status IN ('active', 'permanent') AND (memories.expires_at IS NULL OR memories.expires_at > ?) AND memories_fts MATCH ? ORDER BY bm25(memories_fts), memories.updated_at DESC, memories.id ASC LIMIT ?`);
153226
- searchStatements.set(db, stmt);
153227
- }
153228
- return stmt;
153229
- }
153230
- function sanitizeFtsQuery(query) {
153231
- const tokens = query.split(/\s+/).filter((token) => token.length > 0);
153232
- if (tokens.length === 0)
153233
- return "";
153234
- return tokens.map((token) => `"${token.replace(/"/g, '""')}"`).join(" ");
153235
- }
153236
- function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT) {
153237
- const trimmedQuery = query.trim();
153238
- if (trimmedQuery.length === 0 || limit <= 0) {
153239
- return [];
153240
- }
153241
- const sanitized = sanitizeFtsQuery(trimmedQuery);
153242
- if (sanitized.length === 0) {
153243
- return [];
153244
- }
153245
- const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
153246
- return rows.map(toMemory);
153247
- }
153248
- var DEFAULT_SEARCH_LIMIT = 10, searchStatements;
153249
- var init_storage_memory_fts = __esm(() => {
153250
- init_storage_memory();
153251
- searchStatements = new WeakMap;
153252
- });
153253
153565
  // src/features/magic-context/memory/index.ts
153254
153566
  var init_memory = __esm(() => {
153255
153567
  init_project_identity();
@@ -154024,6 +154336,7 @@ No new compartments or facts were written. Check the historian model/output and
154024
154336
  appendCompartments(db, sessionId, newCompartments);
154025
154337
  replaceSessionFacts(db, sessionId, validatedPass.facts ?? []);
154026
154338
  clearHistorianFailureState(db, sessionId);
154339
+ clearEmergencyRecovery(db, sessionId);
154027
154340
  })();
154028
154341
  clearInjectionCache(sessionId);
154029
154342
  if (deps.directory) {
@@ -162379,15 +162692,75 @@ function redactConfigValue(value) {
162379
162692
  }
162380
162693
  return typeof value;
162381
162694
  }
162695
+ function migrateLegacyExperimental(rawConfig, warnings) {
162696
+ const experimental = rawConfig.experimental;
162697
+ if (typeof experimental !== "object" || experimental === null) {
162698
+ return rawConfig;
162699
+ }
162700
+ const exp = experimental;
162701
+ const hasUM = "user_memories" in exp;
162702
+ const hasPKF = "pin_key_files" in exp;
162703
+ if (!hasUM && !hasPKF) {
162704
+ return rawConfig;
162705
+ }
162706
+ const patched = { ...rawConfig };
162707
+ const dreamer = typeof patched.dreamer === "object" && patched.dreamer !== null ? { ...patched.dreamer } : {};
162708
+ const newExperimental = { ...exp };
162709
+ const coerceToObject = (value) => {
162710
+ if (typeof value === "boolean") {
162711
+ return { enabled: value };
162712
+ }
162713
+ if (typeof value === "object" && value !== null) {
162714
+ return { ...value };
162715
+ }
162716
+ return;
162717
+ };
162718
+ if (hasUM) {
162719
+ const oldUM = coerceToObject(exp.user_memories);
162720
+ if (oldUM !== undefined) {
162721
+ if (dreamer.user_memories === undefined) {
162722
+ dreamer.user_memories = oldUM;
162723
+ warnings.push('Migrated "experimental.user_memories" \u2192 "dreamer.user_memories" in-memory (run `doctor` to persist).');
162724
+ } else if (typeof dreamer.user_memories === "object" && dreamer.user_memories !== null) {
162725
+ dreamer.user_memories = {
162726
+ ...oldUM,
162727
+ ...dreamer.user_memories
162728
+ };
162729
+ }
162730
+ }
162731
+ delete newExperimental.user_memories;
162732
+ }
162733
+ if (hasPKF) {
162734
+ const oldPKF = coerceToObject(exp.pin_key_files);
162735
+ if (oldPKF !== undefined) {
162736
+ if (dreamer.pin_key_files === undefined) {
162737
+ dreamer.pin_key_files = oldPKF;
162738
+ warnings.push('Migrated "experimental.pin_key_files" \u2192 "dreamer.pin_key_files" in-memory (run `doctor` to persist).');
162739
+ } else if (typeof dreamer.pin_key_files === "object" && dreamer.pin_key_files !== null) {
162740
+ dreamer.pin_key_files = {
162741
+ ...oldPKF,
162742
+ ...dreamer.pin_key_files
162743
+ };
162744
+ }
162745
+ }
162746
+ delete newExperimental.pin_key_files;
162747
+ }
162748
+ patched.experimental = newExperimental;
162749
+ patched.dreamer = dreamer;
162750
+ return patched;
162751
+ }
162382
162752
  function parsePluginConfig(rawConfig) {
162383
- const parsed = MagicContextConfigSchema.safeParse(rawConfig);
162753
+ const preMigrationWarnings = [];
162754
+ const migrated = migrateLegacyExperimental(rawConfig, preMigrationWarnings);
162755
+ const parsed = MagicContextConfigSchema.safeParse(migrated);
162384
162756
  const disabledHooks = Array.isArray(rawConfig.disabled_hooks) ? rawConfig.disabled_hooks.filter((value) => typeof value === "string") : undefined;
162385
162757
  const command = typeof rawConfig.command === "object" && rawConfig.command !== null ? rawConfig.command : undefined;
162386
162758
  if (parsed.success) {
162387
162759
  return {
162388
162760
  ...parsed.data,
162389
162761
  disabled_hooks: disabledHooks,
162390
- command
162762
+ command,
162763
+ ...preMigrationWarnings.length > 0 ? { configWarnings: preMigrationWarnings } : {}
162391
162764
  };
162392
162765
  }
162393
162766
  const defaults = MagicContextConfigSchema.parse({});
@@ -162411,17 +162784,23 @@ function parsePluginConfig(rawConfig) {
162411
162784
  warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultVal)}.`);
162412
162785
  }
162413
162786
  }
162414
- const retryParsed = MagicContextConfigSchema.safeParse(patched);
162787
+ const retryMigrated = migrateLegacyExperimental(patched, preMigrationWarnings);
162788
+ const retryParsed = MagicContextConfigSchema.safeParse(retryMigrated);
162415
162789
  if (retryParsed.success) {
162416
162790
  return {
162417
162791
  ...retryParsed.data,
162418
162792
  disabled_hooks: disabledHooks,
162419
162793
  command,
162420
- configWarnings: warnings
162794
+ configWarnings: [...preMigrationWarnings, ...warnings]
162421
162795
  };
162422
162796
  }
162423
162797
  warnings.push("Config recovery failed, using all defaults.");
162424
- return { ...defaults, disabled_hooks: disabledHooks, command, configWarnings: warnings };
162798
+ return {
162799
+ ...defaults,
162800
+ disabled_hooks: disabledHooks,
162801
+ command,
162802
+ configWarnings: [...preMigrationWarnings, ...warnings]
162803
+ };
162425
162804
  }
162426
162805
  function loadPluginConfig(directory) {
162427
162806
  const userDetected = detectConfigFile(getUserConfigBasePath());
@@ -164199,36 +164578,562 @@ function checkScheduleAndEnqueue(db, schedule) {
164199
164578
  }
164200
164579
  return enqueued;
164201
164580
  }
164581
+ // src/features/magic-context/git-commits/git-log-reader.ts
164582
+ init_logger();
164583
+ import { execFile } from "child_process";
164584
+ import { promisify } from "util";
164585
+ var execFileAsync = promisify(execFile);
164586
+ var GIT_TIMEOUT_MS = 1e4;
164587
+ var DEFAULT_MAX_COMMITS = 5000;
164588
+ var RECORD_SEPARATOR = "\x1E";
164589
+ var FIELD_SEPARATOR = "\x1F";
164590
+ async function readGitCommits(directory, options = {}) {
164591
+ const args = [
164592
+ "log",
164593
+ options.branch ?? "HEAD",
164594
+ "--no-merges",
164595
+ `--max-count=${options.maxCommits ?? DEFAULT_MAX_COMMITS}`,
164596
+ `--format=%H${FIELD_SEPARATOR}%s${FIELD_SEPARATOR}%ae${FIELD_SEPARATOR}%ct${FIELD_SEPARATOR}%b${RECORD_SEPARATOR}`
164597
+ ];
164598
+ if (options.sinceMs !== undefined && options.sinceMs > 0) {
164599
+ const iso = new Date(options.sinceMs).toISOString();
164600
+ args.push(`--since=${iso}`);
164601
+ }
164602
+ let stdout;
164603
+ try {
164604
+ const result = await execFileAsync("git", args, {
164605
+ cwd: directory,
164606
+ timeout: GIT_TIMEOUT_MS,
164607
+ maxBuffer: 32 * 1024 * 1024,
164608
+ encoding: "utf8"
164609
+ });
164610
+ stdout = result.stdout;
164611
+ } catch (error48) {
164612
+ const message = error48 instanceof Error ? error48.message : String(error48);
164613
+ log(`[git-commits] readGitCommits failed at cwd=${directory}: ${message.slice(0, 500)}`);
164614
+ return [];
164615
+ }
164616
+ if (stdout.trim().length === 0) {
164617
+ log(`[git-commits] readGitCommits returned empty stdout at cwd=${directory} (sinceMs=${options.sinceMs ?? "none"} args=${args.slice(0, 4).join(" ")})`);
164618
+ }
164619
+ return parseGitLogOutput(stdout);
164620
+ }
164621
+ function parseGitLogOutput(stdout) {
164622
+ const commits = [];
164623
+ const records = stdout.split(RECORD_SEPARATOR);
164624
+ for (const rawRecord of records) {
164625
+ const record2 = rawRecord.replace(/^\s+/, "");
164626
+ if (!record2)
164627
+ continue;
164628
+ const fields = [];
164629
+ let remaining = record2;
164630
+ for (let i = 0;i < 4; i++) {
164631
+ const idx = remaining.indexOf(FIELD_SEPARATOR);
164632
+ if (idx < 0)
164633
+ break;
164634
+ fields.push(remaining.slice(0, idx));
164635
+ remaining = remaining.slice(idx + FIELD_SEPARATOR.length);
164636
+ }
164637
+ fields.push(remaining);
164638
+ if (fields.length < 5)
164639
+ continue;
164640
+ const sha = fields[0].trim();
164641
+ const subject = fields[1].trim();
164642
+ const author = fields[2].trim();
164643
+ const timeSec = Number.parseInt(fields[3].trim(), 10);
164644
+ const body = fields[4].trim();
164645
+ if (sha.length !== 40 || !Number.isFinite(timeSec) || timeSec <= 0) {
164646
+ continue;
164647
+ }
164648
+ const message = body.length > 0 ? `${subject}
164649
+
164650
+ ${body}` : subject;
164651
+ commits.push({
164652
+ sha,
164653
+ shortSha: sha.slice(0, 7),
164654
+ message,
164655
+ author: author.length > 0 ? author : null,
164656
+ committedAtMs: timeSec * 1000
164657
+ });
164658
+ }
164659
+ return commits;
164660
+ }
164661
+ // src/features/magic-context/git-commits/indexer.ts
164662
+ init_logger();
164663
+ init_embedding();
164664
+
164665
+ // src/features/magic-context/git-commits/storage-git-commit-embeddings.ts
164666
+ var saveStatements = new WeakMap;
164667
+ var loadProjectStatements = new WeakMap;
164668
+ var loadUnembeddedStatements = new WeakMap;
164669
+ var countEmbeddedStatements = new WeakMap;
164670
+ var clearProjectStatements = new WeakMap;
164671
+ function getSaveStatement(db) {
164672
+ let stmt = saveStatements.get(db);
164673
+ if (!stmt) {
164674
+ stmt = db.prepare(`INSERT INTO git_commit_embeddings (sha, embedding, model_id, created_at)
164675
+ VALUES (?, ?, ?, ?)
164676
+ ON CONFLICT(sha) DO UPDATE SET
164677
+ embedding = excluded.embedding,
164678
+ model_id = excluded.model_id,
164679
+ created_at = excluded.created_at`);
164680
+ saveStatements.set(db, stmt);
164681
+ }
164682
+ return stmt;
164683
+ }
164684
+ function getLoadProjectStatement(db) {
164685
+ let stmt = loadProjectStatements.get(db);
164686
+ if (!stmt) {
164687
+ stmt = db.prepare(`SELECT e.sha AS sha, e.embedding AS embedding, e.model_id AS model_id
164688
+ FROM git_commit_embeddings e
164689
+ JOIN git_commits c ON c.sha = e.sha
164690
+ WHERE c.project_path = ?`);
164691
+ loadProjectStatements.set(db, stmt);
164692
+ }
164693
+ return stmt;
164694
+ }
164695
+ function getLoadUnembeddedStatement(db) {
164696
+ let stmt = loadUnembeddedStatements.get(db);
164697
+ if (!stmt) {
164698
+ stmt = db.prepare(`SELECT c.sha AS sha, c.message AS message
164699
+ FROM git_commits c
164700
+ LEFT JOIN git_commit_embeddings e ON c.sha = e.sha
164701
+ WHERE c.project_path = ? AND e.sha IS NULL
164702
+ ORDER BY c.committed_at DESC
164703
+ LIMIT ?`);
164704
+ loadUnembeddedStatements.set(db, stmt);
164705
+ }
164706
+ return stmt;
164707
+ }
164708
+ function getCountEmbeddedStatement(db) {
164709
+ let stmt = countEmbeddedStatements.get(db);
164710
+ if (!stmt) {
164711
+ stmt = db.prepare(`SELECT COUNT(*) AS count FROM git_commit_embeddings e
164712
+ JOIN git_commits c ON c.sha = e.sha WHERE c.project_path = ?`);
164713
+ countEmbeddedStatements.set(db, stmt);
164714
+ }
164715
+ return stmt;
164716
+ }
164717
+ function saveCommitEmbedding(db, sha, embedding, modelId) {
164718
+ const bytes = new Uint8Array(embedding.buffer, embedding.byteOffset, embedding.byteLength);
164719
+ getSaveStatement(db).run(sha, bytes, modelId, Date.now());
164720
+ }
164721
+ function loadProjectCommitEmbeddings(db, projectPath) {
164722
+ const rows = getLoadProjectStatement(db).all(projectPath);
164723
+ const map2 = new Map;
164724
+ for (const row of rows) {
164725
+ const buffer2 = row.embedding.buffer.slice(row.embedding.byteOffset, row.embedding.byteOffset + row.embedding.byteLength);
164726
+ map2.set(row.sha, new Float32Array(buffer2));
164727
+ }
164728
+ return map2;
164729
+ }
164730
+ function loadUnembeddedCommits(db, projectPath, limit) {
164731
+ return getLoadUnembeddedStatement(db).all(projectPath, limit);
164732
+ }
164733
+ function countEmbeddedCommits(db, projectPath) {
164734
+ const row = getCountEmbeddedStatement(db).get(projectPath);
164735
+ return row?.count ?? 0;
164736
+ }
164737
+
164738
+ // src/features/magic-context/git-commits/storage-git-commits.ts
164739
+ init_logger();
164740
+ var insertStatements = new WeakMap;
164741
+ var existingShasStatements = new WeakMap;
164742
+ var projectCountStatements = new WeakMap;
164743
+ var evictStatements = new WeakMap;
164744
+ var latestCommitTimeStatements = new WeakMap;
164745
+ function getInsertStatement(db) {
164746
+ let stmt = insertStatements.get(db);
164747
+ if (!stmt) {
164748
+ stmt = db.prepare(`INSERT INTO git_commits (sha, project_path, short_sha, message, author, committed_at, indexed_at)
164749
+ VALUES (?, ?, ?, ?, ?, ?, ?)
164750
+ ON CONFLICT(sha) DO UPDATE SET
164751
+ project_path = excluded.project_path,
164752
+ short_sha = excluded.short_sha,
164753
+ message = excluded.message,
164754
+ author = excluded.author,
164755
+ committed_at = excluded.committed_at,
164756
+ indexed_at = excluded.indexed_at
164757
+ WHERE git_commits.message != excluded.message`);
164758
+ insertStatements.set(db, stmt);
164759
+ }
164760
+ return stmt;
164761
+ }
164762
+ function getExistingShasStatement(db) {
164763
+ let stmt = existingShasStatements.get(db);
164764
+ if (!stmt) {
164765
+ stmt = db.prepare("SELECT sha FROM git_commits WHERE project_path = ?");
164766
+ existingShasStatements.set(db, stmt);
164767
+ }
164768
+ return stmt;
164769
+ }
164770
+ function getProjectCountStatement(db) {
164771
+ let stmt = projectCountStatements.get(db);
164772
+ if (!stmt) {
164773
+ stmt = db.prepare("SELECT COUNT(*) AS count FROM git_commits WHERE project_path = ?");
164774
+ projectCountStatements.set(db, stmt);
164775
+ }
164776
+ return stmt;
164777
+ }
164778
+ function getLatestCommitTimeStatement(db) {
164779
+ let stmt = latestCommitTimeStatements.get(db);
164780
+ if (!stmt) {
164781
+ stmt = db.prepare("SELECT MAX(committed_at) AS latest FROM git_commits WHERE project_path = ?");
164782
+ latestCommitTimeStatements.set(db, stmt);
164783
+ }
164784
+ return stmt;
164785
+ }
164786
+ function getEvictStatement(db) {
164787
+ let stmt = evictStatements.get(db);
164788
+ if (!stmt) {
164789
+ stmt = db.prepare(`DELETE FROM git_commits
164790
+ WHERE sha IN (
164791
+ SELECT sha FROM git_commits
164792
+ WHERE project_path = ?
164793
+ ORDER BY committed_at ASC
164794
+ LIMIT ?
164795
+ )`);
164796
+ evictStatements.set(db, stmt);
164797
+ }
164798
+ return stmt;
164799
+ }
164800
+ function upsertCommits(db, projectPath, commits) {
164801
+ if (commits.length === 0)
164802
+ return { inserted: 0, updated: 0 };
164803
+ const existing = new Set;
164804
+ for (const row of getExistingShasStatement(db).all(projectPath)) {
164805
+ existing.add(row.sha);
164806
+ }
164807
+ let inserted = 0;
164808
+ let updated = 0;
164809
+ const now = Date.now();
164810
+ const insertStmt = getInsertStatement(db);
164811
+ db.transaction(() => {
164812
+ for (const commit of commits) {
164813
+ const result = insertStmt.run(commit.sha, projectPath, commit.shortSha, commit.message, commit.author, commit.committedAtMs, now);
164814
+ if (result.changes > 0) {
164815
+ if (existing.has(commit.sha)) {
164816
+ updated++;
164817
+ } else {
164818
+ inserted++;
164819
+ existing.add(commit.sha);
164820
+ }
164821
+ }
164822
+ }
164823
+ })();
164824
+ return { inserted, updated };
164825
+ }
164826
+ function getCommitCount(db, projectPath) {
164827
+ const row = getProjectCountStatement(db).get(projectPath);
164828
+ return row?.count ?? 0;
164829
+ }
164830
+ function getLatestIndexedCommitTimeMs(db, projectPath) {
164831
+ const row = getLatestCommitTimeStatement(db).get(projectPath);
164832
+ return row?.latest ?? null;
164833
+ }
164834
+ function evictOldestCommits(db, projectPath, excess) {
164835
+ if (excess <= 0)
164836
+ return 0;
164837
+ const before = getCommitCount(db, projectPath);
164838
+ getEvictStatement(db).run(projectPath, excess);
164839
+ const after = getCommitCount(db, projectPath);
164840
+ return Math.max(0, before - after);
164841
+ }
164842
+ function enforceProjectCap(db, projectPath, maxCommits) {
164843
+ if (maxCommits <= 0)
164844
+ return 0;
164845
+ const count = getCommitCount(db, projectPath);
164846
+ if (count <= maxCommits)
164847
+ return 0;
164848
+ const excess = count - maxCommits;
164849
+ const evicted = evictOldestCommits(db, projectPath, excess);
164850
+ if (evicted > 0) {
164851
+ log(`[git-commits] evicted ${evicted} oldest commits for project ${projectPath} (cap=${maxCommits}, was=${count})`);
164852
+ }
164853
+ return evicted;
164854
+ }
164855
+
164856
+ // src/features/magic-context/git-commits/indexer.ts
164857
+ var MS_PER_DAY = 24 * 60 * 60 * 1000;
164858
+ var EMBED_BATCH_SIZE = 16;
164859
+ var EMBED_MAX_PER_SWEEP = 500;
164860
+ var EMBED_SWEEP_MAX_WALL_CLOCK_MS = 5 * 60 * 1000;
164861
+ var indexInProgress = new Set;
164862
+ var embedInProgress = new Set;
164863
+ async function indexCommitsForProject(db, projectPath, directory, embeddingConfig2, options) {
164864
+ const result = {
164865
+ scanned: 0,
164866
+ inserted: 0,
164867
+ updated: 0,
164868
+ evicted: 0,
164869
+ embedded: 0
164870
+ };
164871
+ if (indexInProgress.has(projectPath)) {
164872
+ log(`[git-commits] index already in progress for ${projectPath}, skipping`);
164873
+ return result;
164874
+ }
164875
+ indexInProgress.add(projectPath);
164876
+ try {
164877
+ const latestIndexed = getLatestIndexedCommitTimeMs(db, projectPath);
164878
+ const sinceMs = latestIndexed !== null ? Math.max(latestIndexed - 60000, Date.now() - options.sinceDays * MS_PER_DAY) : Date.now() - options.sinceDays * MS_PER_DAY;
164879
+ const commits = await readGitCommits(directory, {
164880
+ sinceMs,
164881
+ maxCommits: options.maxCommits
164882
+ });
164883
+ result.scanned = commits.length;
164884
+ if (commits.length === 0) {
164885
+ result.evicted = enforceProjectCap(db, projectPath, options.maxCommits);
164886
+ log(`[git-commits] no new commits for ${projectPath} (sinceMs=${sinceMs} latestIndexed=${latestIndexed ?? "none"} evicted=${result.evicted})`);
164887
+ return result;
164888
+ }
164889
+ log(`[git-commits] read ${commits.length} commits for ${projectPath} (sinceMs=${sinceMs} latestIndexed=${latestIndexed ?? "none"})`);
164890
+ const upsert = upsertCommits(db, projectPath, commits);
164891
+ result.inserted = upsert.inserted;
164892
+ result.updated = upsert.updated;
164893
+ result.evicted = enforceProjectCap(db, projectPath, options.maxCommits);
164894
+ if (options.skipEmbed || !isEmbeddingEnabled()) {
164895
+ 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()})`);
164896
+ return result;
164897
+ }
164898
+ result.embedded = await embedUnembeddedCommits(db, projectPath, embeddingConfig2);
164899
+ log(`[git-commits] indexed ${projectPath}: scanned=${result.scanned} inserted=${result.inserted} updated=${result.updated} evicted=${result.evicted} embedded=${result.embedded}`);
164900
+ return result;
164901
+ } finally {
164902
+ indexInProgress.delete(projectPath);
164903
+ }
164904
+ }
164905
+ async function embedUnembeddedCommits(db, projectPath, _config) {
164906
+ if (embedInProgress.has(projectPath)) {
164907
+ return 0;
164908
+ }
164909
+ if (!isEmbeddingEnabled()) {
164910
+ return 0;
164911
+ }
164912
+ embedInProgress.add(projectPath);
164913
+ const startedAt = Date.now();
164914
+ const deadline = startedAt + EMBED_SWEEP_MAX_WALL_CLOCK_MS;
164915
+ let total = 0;
164916
+ try {
164917
+ while (Date.now() < deadline && total < EMBED_MAX_PER_SWEEP) {
164918
+ const rows = loadUnembeddedCommits(db, projectPath, EMBED_BATCH_SIZE);
164919
+ if (rows.length === 0)
164920
+ break;
164921
+ let embeddedThisBatch = 0;
164922
+ try {
164923
+ const embeddings = await embedBatch(rows.map((row) => row.message));
164924
+ const modelId = getEmbeddingModelId();
164925
+ if (modelId === "off")
164926
+ break;
164927
+ db.transaction(() => {
164928
+ for (const [index, row] of rows.entries()) {
164929
+ const embedding = embeddings[index];
164930
+ if (!embedding)
164931
+ continue;
164932
+ saveCommitEmbedding(db, row.sha, embedding, modelId);
164933
+ embeddedThisBatch += 1;
164934
+ }
164935
+ })();
164936
+ } catch (error48) {
164937
+ log(`[git-commits] embed batch failed for ${projectPath}: ${error48 instanceof Error ? error48.message : String(error48)}`);
164938
+ break;
164939
+ }
164940
+ if (embeddedThisBatch === 0)
164941
+ break;
164942
+ total += embeddedThisBatch;
164943
+ if (embeddedThisBatch < rows.length)
164944
+ break;
164945
+ }
164946
+ if (total > 0) {
164947
+ const totalEmbedded = countEmbeddedCommits(db, projectPath);
164948
+ log(`[git-commits] embedded ${total} commits for ${projectPath} (total embedded: ${totalEmbedded})`);
164949
+ }
164950
+ return total;
164951
+ } finally {
164952
+ embedInProgress.delete(projectPath);
164953
+ }
164954
+ }
164955
+ // src/features/magic-context/git-commits/search-git-commits.ts
164956
+ init_logger();
164957
+ init_storage_memory_fts();
164958
+ var ftsStatements = new WeakMap;
164959
+ var ftsPlainStatements = new WeakMap;
164960
+ var getBySHAStatements = new WeakMap;
164961
+ function rowToCommit(row) {
164962
+ return {
164963
+ sha: row.sha,
164964
+ shortSha: row.short_sha,
164965
+ projectPath: row.project_path,
164966
+ message: row.message,
164967
+ author: row.author,
164968
+ committedAtMs: row.committed_at,
164969
+ indexedAtMs: row.indexed_at
164970
+ };
164971
+ }
164972
+ function getFtsStatement(db) {
164973
+ let stmt = ftsStatements.get(db);
164974
+ if (!stmt) {
164975
+ stmt = db.prepare(`SELECT c.sha AS sha, c.project_path AS project_path, c.short_sha AS short_sha,
164976
+ c.message AS message, c.author AS author,
164977
+ c.committed_at AS committed_at, c.indexed_at AS indexed_at
164978
+ FROM git_commits_fts
164979
+ INNER JOIN git_commits c ON c.sha = git_commits_fts.sha
164980
+ WHERE c.project_path = ? AND git_commits_fts MATCH ?
164981
+ ORDER BY bm25(git_commits_fts) LIMIT ?`);
164982
+ ftsStatements.set(db, stmt);
164983
+ }
164984
+ return stmt;
164985
+ }
164986
+ function getLikeFallbackStatement(db) {
164987
+ let stmt = ftsPlainStatements.get(db);
164988
+ if (!stmt) {
164989
+ stmt = db.prepare(`SELECT sha, project_path, short_sha, message, author, committed_at, indexed_at
164990
+ FROM git_commits
164991
+ WHERE project_path = ? AND lower(message) LIKE '%' || lower(?) || '%'
164992
+ ORDER BY committed_at DESC LIMIT ?`);
164993
+ ftsPlainStatements.set(db, stmt);
164994
+ }
164995
+ return stmt;
164996
+ }
164997
+ function getBySHAStatement(db) {
164998
+ let stmt = getBySHAStatements.get(db);
164999
+ if (!stmt) {
165000
+ stmt = db.prepare(`SELECT sha, project_path, short_sha, message, author, committed_at, indexed_at
165001
+ FROM git_commits WHERE sha = ?`);
165002
+ getBySHAStatements.set(db, stmt);
165003
+ }
165004
+ return stmt;
165005
+ }
165006
+ function clamp01(value) {
165007
+ if (!Number.isFinite(value))
165008
+ return 0;
165009
+ return Math.min(1, Math.max(0, value));
165010
+ }
165011
+ function searchGitCommitsSync(db, projectPath, query, options) {
165012
+ const trimmed = query.trim();
165013
+ if (trimmed.length === 0 || options.limit <= 0)
165014
+ return [];
165015
+ const semanticWeight = options.semanticWeight ?? 0.7;
165016
+ const ftsWeight = options.ftsWeight ?? 0.3;
165017
+ const singleSourcePenalty = options.singleSourcePenalty ?? 0.8;
165018
+ const fetchLimit = Math.max(options.limit * 3, 30);
165019
+ const ftsCandidates = [];
165020
+ const sanitized = sanitizeFtsQuery(trimmed);
165021
+ if (sanitized.length > 0) {
165022
+ try {
165023
+ for (const row of getFtsStatement(db).all(projectPath, sanitized, fetchLimit)) {
165024
+ ftsCandidates.push(rowToCommit(row));
165025
+ }
165026
+ } catch (error48) {
165027
+ log(`[git-commits] FTS query failed for "${trimmed}": ${error48 instanceof Error ? error48.message : String(error48)}`);
165028
+ }
165029
+ }
165030
+ if (ftsCandidates.length === 0) {
165031
+ for (const row of getLikeFallbackStatement(db).all(projectPath, trimmed, fetchLimit)) {
165032
+ ftsCandidates.push(rowToCommit(row));
165033
+ }
165034
+ }
165035
+ const ftsScores = new Map;
165036
+ ftsCandidates.forEach((commit, rank) => {
165037
+ ftsScores.set(commit.sha, 1 / (rank + 1));
165038
+ });
165039
+ const semanticScores = new Map;
165040
+ if (options.queryEmbedding) {
165041
+ const embeddings = loadProjectCommitEmbeddings(db, projectPath);
165042
+ for (const [sha, embedding] of embeddings.entries()) {
165043
+ const similarity = clamp01(cosineSimilarity(options.queryEmbedding, embedding));
165044
+ if (similarity > 0) {
165045
+ semanticScores.set(sha, similarity);
165046
+ }
165047
+ }
165048
+ }
165049
+ const bySha = new Map;
165050
+ for (const commit of ftsCandidates)
165051
+ bySha.set(commit.sha, commit);
165052
+ const getCommitStmt = getBySHAStatement(db);
165053
+ for (const sha of semanticScores.keys()) {
165054
+ if (bySha.has(sha))
165055
+ continue;
165056
+ const row = getCommitStmt.get(sha);
165057
+ if (row && row.project_path === projectPath) {
165058
+ bySha.set(sha, rowToCommit(row));
165059
+ }
165060
+ }
165061
+ const results = [];
165062
+ for (const [sha, commit] of bySha.entries()) {
165063
+ const sem = semanticScores.get(sha);
165064
+ const fts = ftsScores.get(sha);
165065
+ let score = 0;
165066
+ let matchType = "fts";
165067
+ if (sem !== undefined && fts !== undefined) {
165068
+ score = semanticWeight * sem + ftsWeight * fts;
165069
+ matchType = "hybrid";
165070
+ } else if (sem !== undefined) {
165071
+ score = sem * singleSourcePenalty;
165072
+ matchType = "semantic";
165073
+ } else if (fts !== undefined) {
165074
+ score = fts * singleSourcePenalty;
165075
+ matchType = "fts";
165076
+ }
165077
+ if (score <= 0)
165078
+ continue;
165079
+ results.push({ commit, score, matchType });
165080
+ }
165081
+ results.sort((left, right) => {
165082
+ if (right.score !== left.score)
165083
+ return right.score - left.score;
165084
+ return right.commit.committedAtMs - left.commit.committedAtMs;
165085
+ });
165086
+ return results.slice(0, options.limit);
165087
+ }
164202
165088
  // src/plugin/dream-timer.ts
164203
165089
  init_embedding();
165090
+ init_project_identity();
164204
165091
  init_storage();
164205
165092
  init_logger();
164206
165093
  var DREAM_TIMER_INTERVAL_MS = 15 * 60 * 1000;
164207
165094
  var activeTimer = null;
164208
- var activeCleanup = null;
165095
+ var registeredProjects = new Map;
164209
165096
  function startDreamScheduleTimer(args) {
164210
- if (activeTimer) {
164211
- log("[dreamer] schedule timer already running, skipping duplicate start");
164212
- return activeCleanup ?? undefined;
164213
- }
164214
- const {
164215
- client,
164216
- dreamerConfig,
164217
- embeddingConfig: embeddingConfig2,
164218
- memoryEnabled,
164219
- experimentalUserMemories,
164220
- experimentalPinKeyFiles
164221
- } = args;
164222
- const dreamingEnabled = Boolean(dreamerConfig?.enabled && dreamerConfig.schedule?.trim());
164223
- const embeddingSweepEnabled = memoryEnabled && embeddingConfig2.provider !== "off";
164224
- if (!dreamingEnabled && !embeddingSweepEnabled) {
165097
+ const dreamingEnabled = Boolean(args.dreamerConfig?.enabled && args.dreamerConfig.schedule?.trim());
165098
+ const embeddingSweepEnabled = args.memoryEnabled && args.embeddingConfig.provider !== "off";
165099
+ const commitIndexingEnabled = args.gitCommitIndexing?.enabled === true;
165100
+ if (!dreamingEnabled && !embeddingSweepEnabled && !commitIndexingEnabled) {
164225
165101
  return;
164226
165102
  }
164227
- const timer = setInterval(() => {
164228
- log("[dreamer] timer tick \u2014 checking schedule and embeddings");
164229
- try {
164230
- if (embeddingSweepEnabled) {
164231
- embedAllUnembeddedMemories(openDatabase(), embeddingConfig2).then((embeddedCount) => {
165103
+ const isNewRegistration = !registeredProjects.has(args.directory);
165104
+ registeredProjects.set(args.directory, args);
165105
+ if (isNewRegistration) {
165106
+ log(`[dreamer] registered project ${args.directory} (dreaming=${dreamingEnabled} embeddings=${embeddingSweepEnabled} commits=${commitIndexingEnabled}; total=${registeredProjects.size})`);
165107
+ }
165108
+ if (!activeTimer) {
165109
+ log(`[dreamer] started independent schedule timer (every ${DREAM_TIMER_INTERVAL_MS / 60000}m)`);
165110
+ runTick("startup");
165111
+ const timer = setInterval(() => runTick("interval"), DREAM_TIMER_INTERVAL_MS);
165112
+ if (typeof timer === "object" && "unref" in timer) {
165113
+ timer.unref();
165114
+ }
165115
+ activeTimer = timer;
165116
+ } else if (isNewRegistration) {
165117
+ sweepProject(args, "startup");
165118
+ }
165119
+ return () => {
165120
+ registeredProjects.delete(args.directory);
165121
+ log(`[dreamer] unregistered project ${args.directory} (remaining=${registeredProjects.size})`);
165122
+ if (registeredProjects.size === 0 && activeTimer) {
165123
+ clearInterval(activeTimer);
165124
+ activeTimer = null;
165125
+ log("[dreamer] stopped dream schedule timer (no projects left)");
165126
+ }
165127
+ };
165128
+ }
165129
+ function runTick(origin) {
165130
+ log(`[dreamer] timer tick (${origin}) \u2014 projects=${registeredProjects.size}`);
165131
+ try {
165132
+ const anyEmbeddingEnabled = Array.from(registeredProjects.values()).some((r) => r.memoryEnabled && r.embeddingConfig.provider !== "off");
165133
+ if (anyEmbeddingEnabled) {
165134
+ const first = registeredProjects.values().next().value;
165135
+ if (first) {
165136
+ embedAllUnembeddedMemories(openDatabase(), first.embeddingConfig).then((embeddedCount) => {
164232
165137
  if (embeddedCount > 0) {
164233
165138
  log(`[magic-context] proactively embedded ${embeddedCount} ${embeddedCount === 1 ? "memory" : "memories"} across all projects`);
164234
165139
  }
@@ -164236,41 +165141,65 @@ function startDreamScheduleTimer(args) {
164236
165141
  log("[magic-context] periodic memory embedding sweep failed:", error48);
164237
165142
  });
164238
165143
  }
164239
- if (!dreamingEnabled || !dreamerConfig?.schedule?.trim()) {
164240
- log("[dreamer] timer tick \u2014 dreaming disabled, skipping schedule check");
164241
- return;
164242
- }
164243
- const db = openDatabase();
164244
- log(`[dreamer] timer tick \u2014 checking schedule window "${dreamerConfig.schedule}"`);
164245
- checkScheduleAndEnqueue(db, dreamerConfig.schedule);
164246
- processDreamQueue({
164247
- db,
164248
- client,
164249
- tasks: dreamerConfig.tasks,
164250
- taskTimeoutMinutes: dreamerConfig.task_timeout_minutes,
164251
- maxRuntimeMinutes: dreamerConfig.max_runtime_minutes,
164252
- experimentalUserMemories,
164253
- experimentalPinKeyFiles
164254
- }).catch((error48) => {
164255
- log("[dreamer] timer-triggered queue processing failed:", error48);
164256
- });
164257
- } catch (error48) {
164258
- log("[magic-context] timer-triggered maintenance check failed:", error48);
164259
165144
  }
164260
- }, DREAM_TIMER_INTERVAL_MS);
164261
- if (typeof timer === "object" && "unref" in timer) {
164262
- timer.unref();
165145
+ for (const reg of registeredProjects.values()) {
165146
+ sweepProject(reg, origin);
165147
+ }
165148
+ } catch (error48) {
165149
+ log("[magic-context] timer-triggered maintenance check failed:", error48);
165150
+ }
165151
+ }
165152
+ async function sweepProject(reg, origin) {
165153
+ const dreamingEnabled = Boolean(reg.dreamerConfig?.enabled && reg.dreamerConfig.schedule?.trim());
165154
+ const commitIndexingEnabled = reg.gitCommitIndexing?.enabled === true;
165155
+ if (commitIndexingEnabled && reg.gitCommitIndexing) {
165156
+ await sweepGitCommits({
165157
+ directory: reg.directory,
165158
+ gitCommitIndexing: reg.gitCommitIndexing,
165159
+ embeddingConfig: reg.embeddingConfig
165160
+ });
165161
+ }
165162
+ if (!dreamingEnabled || !reg.dreamerConfig?.schedule?.trim()) {
165163
+ return;
165164
+ }
165165
+ try {
165166
+ const db = openDatabase();
165167
+ log(`[dreamer] timer tick (${origin}) ${reg.directory} \u2014 checking schedule window "${reg.dreamerConfig.schedule}"`);
165168
+ checkScheduleAndEnqueue(db, reg.dreamerConfig.schedule);
165169
+ await processDreamQueue({
165170
+ db,
165171
+ client: reg.client,
165172
+ tasks: reg.dreamerConfig.tasks,
165173
+ taskTimeoutMinutes: reg.dreamerConfig.task_timeout_minutes,
165174
+ maxRuntimeMinutes: reg.dreamerConfig.max_runtime_minutes,
165175
+ experimentalUserMemories: reg.experimentalUserMemories,
165176
+ experimentalPinKeyFiles: reg.experimentalPinKeyFiles
165177
+ });
165178
+ } catch (error48) {
165179
+ log(`[dreamer] timer-triggered queue processing failed for ${reg.directory}:`, error48);
165180
+ }
165181
+ }
165182
+ async function sweepGitCommits(args) {
165183
+ const { directory, gitCommitIndexing, embeddingConfig: embeddingConfig2 } = args;
165184
+ const startedAt = Date.now();
165185
+ log(`[git-commits] sweep starting for ${directory} (sinceDays=${gitCommitIndexing.since_days} maxCommits=${gitCommitIndexing.max_commits} embedding=${embeddingConfig2.provider})`);
165186
+ try {
165187
+ const db = openDatabase();
165188
+ const projectPath = resolveProjectIdentity(directory);
165189
+ const result = await indexCommitsForProject(db, projectPath, directory, embeddingConfig2, {
165190
+ sinceDays: gitCommitIndexing.since_days,
165191
+ maxCommits: gitCommitIndexing.max_commits
165192
+ });
165193
+ let drainedEmbeddings = 0;
165194
+ if (embeddingConfig2.provider !== "off" && result.embedded > 0) {
165195
+ drainedEmbeddings = await embedUnembeddedCommits(db, projectPath, embeddingConfig2);
165196
+ }
165197
+ const elapsedMs = Date.now() - startedAt;
165198
+ 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}`);
165199
+ } catch (error48) {
165200
+ const elapsedMs = Date.now() - startedAt;
165201
+ log(`[git-commits] sweep failed for ${directory} after ${elapsedMs}ms: ${error48 instanceof Error ? error48.message : String(error48)}`);
164263
165202
  }
164264
- const cleanup = () => {
164265
- clearInterval(timer);
164266
- activeTimer = null;
164267
- activeCleanup = null;
164268
- log("[dreamer] stopped dream schedule timer");
164269
- };
164270
- activeTimer = timer;
164271
- activeCleanup = cleanup;
164272
- log(`[dreamer] started independent schedule timer (every ${DREAM_TIMER_INTERVAL_MS / 60000}m)`);
164273
- return cleanup;
164274
165203
  }
164275
165204
 
164276
165205
  // src/plugin/event.ts
@@ -164297,21 +165226,23 @@ function createCompactionHandler() {
164297
165226
  };
164298
165227
  }
164299
165228
  // src/hooks/magic-context/event-resolvers.ts
165229
+ init_storage_meta_persisted();
164300
165230
  init_logger();
164301
165231
  init_models_dev_cache();
164302
165232
  var DEFAULT_CONTEXT_LIMIT = 128000;
164303
165233
  var MAX_EXECUTE_THRESHOLD = 80;
164304
- function resolveContextLimit(providerID, modelID) {
164305
- if (!providerID) {
164306
- return DEFAULT_CONTEXT_LIMIT;
164307
- }
164308
- if (modelID) {
164309
- const modelsDevLimit = getModelsDevContextLimit(providerID, modelID);
164310
- if (modelsDevLimit !== undefined) {
164311
- return modelsDevLimit;
164312
- }
165234
+ function resolveContextLimit(providerID, modelID, ctx) {
165235
+ const fromModelsDev = providerID && modelID ? getModelsDevContextLimit(providerID, modelID) : undefined;
165236
+ const baseline = fromModelsDev ?? DEFAULT_CONTEXT_LIMIT;
165237
+ if (ctx?.db && ctx.sessionID) {
165238
+ try {
165239
+ const overflow = getOverflowState(ctx.db, ctx.sessionID);
165240
+ if (overflow.detectedContextLimit > 0 && overflow.detectedContextLimit < baseline) {
165241
+ return overflow.detectedContextLimit;
165242
+ }
165243
+ } catch {}
164313
165244
  }
164314
- return DEFAULT_CONTEXT_LIMIT;
165245
+ return baseline;
164315
165246
  }
164316
165247
  function resolveCacheTtl(cacheTtl, modelKey) {
164317
165248
  if (typeof cacheTtl === "string") {
@@ -165251,6 +166182,112 @@ ${snap.error}`;
165251
166182
  // src/hooks/magic-context/hook.ts
165252
166183
  init_derive_budgets();
165253
166184
 
166185
+ // src/features/magic-context/overflow-detection.ts
166186
+ var OVERFLOW_PATTERNS = [
166187
+ /prompt is too long/i,
166188
+ /input is too long for requested model/i,
166189
+ /exceeds the context window/i,
166190
+ /input token count.*exceeds the maximum/i,
166191
+ /maximum prompt length is \d+/i,
166192
+ /reduce the length of the messages/i,
166193
+ /maximum context length is \d+ tokens/i,
166194
+ /exceeds the limit of \d+/i,
166195
+ /exceeds the available context size/i,
166196
+ /greater than the context length/i,
166197
+ /context window exceeds limit/i,
166198
+ /exceeded model token limit/i,
166199
+ /context[_ ]length[_ ]exceeded/i,
166200
+ /request entity too large/i,
166201
+ /context length is only \d+ tokens/i,
166202
+ /input length.*exceeds.*context length/i,
166203
+ /prompt too long; exceeded (?:max )?context length/i,
166204
+ /too large for model with \d+ maximum context length/i,
166205
+ /model_context_window_exceeded/i,
166206
+ /context size has been exceeded/i
166207
+ ];
166208
+ var LIMIT_EXTRACTION_PATTERNS = [
166209
+ /maximum prompt length is (\d+)/i,
166210
+ /maximum context length is (\d+) tokens?/i,
166211
+ /context length is only (\d+) tokens?/i,
166212
+ /exceeds the limit of (\d+)/i,
166213
+ /too large for model with (\d+) maximum context length/i,
166214
+ /context size.*(\d+) tokens?/i,
166215
+ /exceeds? the context length of (\d+)/i,
166216
+ /max(?:imum)?.*context.*?(\d+)/i
166217
+ ];
166218
+ var MIN_PLAUSIBLE_LIMIT = 1024;
166219
+ var MAX_PLAUSIBLE_LIMIT = 1e7;
166220
+ function extractErrorMessage(error48) {
166221
+ if (!error48)
166222
+ return "";
166223
+ if (typeof error48 === "string")
166224
+ return error48;
166225
+ if (typeof error48 === "object") {
166226
+ const obj = error48;
166227
+ const nested = obj.error;
166228
+ if (nested && typeof nested.message === "string" && nested.message.length > 0) {
166229
+ return nested.message;
166230
+ }
166231
+ }
166232
+ if (error48 instanceof Error)
166233
+ return error48.message;
166234
+ if (typeof error48 === "object") {
166235
+ const obj = error48;
166236
+ if (typeof obj.message === "string")
166237
+ return obj.message;
166238
+ if (typeof obj.responseBody === "string")
166239
+ return obj.responseBody;
166240
+ try {
166241
+ return JSON.stringify(error48);
166242
+ } catch {
166243
+ return String(error48);
166244
+ }
166245
+ }
166246
+ return String(error48);
166247
+ }
166248
+ function detectOverflow(error48) {
166249
+ const message = extractErrorMessage(error48);
166250
+ if (!message) {
166251
+ return { isOverflow: false };
166252
+ }
166253
+ const hasStatus413 = /\b413\b/.test(message) && /(entity|payload|context|prompt)/i.test(message);
166254
+ let matched;
166255
+ for (const pattern of OVERFLOW_PATTERNS) {
166256
+ if (pattern.test(message)) {
166257
+ matched = pattern;
166258
+ break;
166259
+ }
166260
+ }
166261
+ if (!matched && !hasStatus413) {
166262
+ return { isOverflow: false };
166263
+ }
166264
+ const reportedLimit = parseReportedLimit(message);
166265
+ return {
166266
+ isOverflow: true,
166267
+ reportedLimit,
166268
+ matchedPattern: matched?.source
166269
+ };
166270
+ }
166271
+ function parseReportedLimit(message) {
166272
+ if (!message)
166273
+ return;
166274
+ for (const pattern of LIMIT_EXTRACTION_PATTERNS) {
166275
+ const match = message.match(pattern);
166276
+ if (!match)
166277
+ continue;
166278
+ const raw = match[1];
166279
+ if (!raw)
166280
+ continue;
166281
+ const value = Number.parseInt(raw, 10);
166282
+ if (!Number.isFinite(value))
166283
+ continue;
166284
+ if (value < MIN_PLAUSIBLE_LIMIT || value > MAX_PLAUSIBLE_LIMIT)
166285
+ continue;
166286
+ return value;
166287
+ }
166288
+ return;
166289
+ }
166290
+
165254
166291
  // src/hooks/magic-context/event-handler.ts
165255
166292
  init_storage();
165256
166293
  init_storage_meta_persisted();
@@ -165309,9 +166346,18 @@ function getMessageUpdatedAssistantInfo(properties) {
165309
166346
  read: typeof cache?.read === "number" ? cache.read : undefined,
165310
166347
  write: typeof cache?.write === "number" ? cache.write : undefined
165311
166348
  }
165312
- }
166349
+ },
166350
+ error: info.error !== undefined ? info.error : undefined
165313
166351
  };
165314
166352
  }
166353
+ function getSessionErrorInfo(properties) {
166354
+ if (!isRecord(properties))
166355
+ return null;
166356
+ const sessionID = properties.sessionID;
166357
+ if (typeof sessionID !== "string" || sessionID.length === 0)
166358
+ return null;
166359
+ return { sessionID, error: properties.error };
166360
+ }
165315
166361
  function getMessageRemovedInfo(properties) {
165316
166362
  if (!isRecord(properties)) {
165317
166363
  return null;
@@ -165490,6 +166536,18 @@ init_inject_compartments();
165490
166536
  init_note_nudger();
165491
166537
  init_read_session_chunk();
165492
166538
  init_read_session_formatting();
166539
+
166540
+ // src/hooks/magic-context/reasoning-capability.ts
166541
+ init_models_dev_cache();
166542
+ function modelRequiresInterleavedReasoning(model) {
166543
+ if (!model?.providerID || !model?.modelID) {
166544
+ return false;
166545
+ }
166546
+ const field = getModelsDevInterleavedField(model.providerID, model.modelID);
166547
+ return typeof field === "string" && field.length > 0;
166548
+ }
166549
+
166550
+ // src/hooks/magic-context/transform.ts
165493
166551
  init_send_session_notification();
165494
166552
 
165495
166553
  // src/hooks/magic-context/strip-content.ts
@@ -165614,7 +166672,9 @@ function stripDroppedPlaceholderMessages(messages) {
165614
166672
  }
165615
166673
  return stripped;
165616
166674
  }
165617
- function replayClearedReasoning(messages, reasoningByMessage, messageTagNumbers, persistedWatermark) {
166675
+ function replayClearedReasoning(messages, reasoningByMessage, messageTagNumbers, persistedWatermark, skipTypedReasoningCleanup = false) {
166676
+ if (skipTypedReasoningCleanup)
166677
+ return 0;
165618
166678
  if (persistedWatermark <= 0)
165619
166679
  return 0;
165620
166680
  let cleared = 0;
@@ -165660,7 +166720,9 @@ function replayStrippedInlineThinking(messages, messageTagNumbers, persistedWate
165660
166720
  }
165661
166721
  return stripped;
165662
166722
  }
165663
- function clearOldReasoning(messages, reasoningByMessage, messageTagNumbers, clearReasoningAge) {
166723
+ function clearOldReasoning(messages, reasoningByMessage, messageTagNumbers, clearReasoningAge, skipTypedReasoningCleanup = false) {
166724
+ if (skipTypedReasoningCleanup)
166725
+ return 0;
165664
166726
  const maxTag = findMaxTag(messageTagNumbers);
165665
166727
  if (maxTag === 0)
165666
166728
  return 0;
@@ -165695,7 +166757,9 @@ function findMaxTag(messageTagNumbers) {
165695
166757
  return max;
165696
166758
  }
165697
166759
  var CLEARED_REASONING_TYPES = new Set(["thinking", "reasoning"]);
165698
- function stripClearedReasoning(messages) {
166760
+ function stripClearedReasoning(messages, skipTypedReasoningCleanup = false) {
166761
+ if (skipTypedReasoningCleanup)
166762
+ return 0;
165699
166763
  let stripped = 0;
165700
166764
  for (const message of messages) {
165701
166765
  if (message.info.role !== "assistant")
@@ -166924,6 +167988,571 @@ function applyContextNudge(messages, nudge, nudgePlacements, sessionId) {
166924
167988
  }
166925
167989
  }
166926
167990
 
167991
+ // src/features/magic-context/search.ts
167992
+ init_read_session_chunk();
167993
+ init_logger();
167994
+ init_memory();
167995
+ init_embedding();
167996
+ init_storage_memory_fts();
167997
+ init_message_index();
167998
+ var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
167999
+ var FTS_SEMANTIC_CANDIDATE_LIMIT = 50;
168000
+ var SEMANTIC_WEIGHT = 0.7;
168001
+ var FTS_WEIGHT = 0.3;
168002
+ var SINGLE_SOURCE_PENALTY = 0.8;
168003
+ var RESULT_PREVIEW_LIMIT = 220;
168004
+ var MEMORY_SOURCE_BOOST = 1.3;
168005
+ var MESSAGE_SOURCE_BOOST = 1.15;
168006
+ var GIT_COMMIT_SOURCE_BOOST = 1.2;
168007
+ var messageSearchStatements = new WeakMap;
168008
+ function normalizeLimit(limit) {
168009
+ if (typeof limit !== "number" || !Number.isFinite(limit)) {
168010
+ return DEFAULT_UNIFIED_SEARCH_LIMIT;
168011
+ }
168012
+ return Math.max(1, Math.floor(limit));
168013
+ }
168014
+ function normalizeCosineScore(score) {
168015
+ if (!Number.isFinite(score)) {
168016
+ return 0;
168017
+ }
168018
+ return Math.min(1, Math.max(0, score));
168019
+ }
168020
+ function previewText(text) {
168021
+ const normalized = text.replace(/\s+/g, " ").trim();
168022
+ if (normalized.length <= RESULT_PREVIEW_LIMIT) {
168023
+ return normalized;
168024
+ }
168025
+ return `${normalized.slice(0, RESULT_PREVIEW_LIMIT - 1).trimEnd()}\u2026`;
168026
+ }
168027
+ function getMessageSearchStatement(db) {
168028
+ let stmt = messageSearchStatements.get(db);
168029
+ if (!stmt) {
168030
+ stmt = db.prepare("SELECT message_ordinal AS messageOrdinal, message_id AS messageId, role, content FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ? ORDER BY bm25(message_history_fts), CAST(message_ordinal AS INTEGER) ASC LIMIT ?");
168031
+ messageSearchStatements.set(db, stmt);
168032
+ }
168033
+ return stmt;
168034
+ }
168035
+ function getMessageOrdinal(value) {
168036
+ if (typeof value === "number" && Number.isFinite(value)) {
168037
+ return value;
168038
+ }
168039
+ if (typeof value === "string" && value.trim().length > 0) {
168040
+ const parsed = Number.parseInt(value, 10);
168041
+ return Number.isFinite(parsed) ? parsed : null;
168042
+ }
168043
+ return null;
168044
+ }
168045
+ async function getSemanticScores(args) {
168046
+ const semanticScores = new Map;
168047
+ if (!args.embeddingEnabled || !args.isEmbeddingRuntimeEnabled() || args.memories.length === 0) {
168048
+ return semanticScores;
168049
+ }
168050
+ const queryEmbedding = await args.embedQuery(args.query, args.signal);
168051
+ if (!queryEmbedding) {
168052
+ return semanticScores;
168053
+ }
168054
+ const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
168055
+ const embeddings = await ensureMemoryEmbeddings({
168056
+ db: args.db,
168057
+ memories: args.memories,
168058
+ existingEmbeddings: cachedEmbeddings
168059
+ });
168060
+ for (const memory of args.memories) {
168061
+ const memoryEmbedding = embeddings.get(memory.id);
168062
+ if (!memoryEmbedding) {
168063
+ continue;
168064
+ }
168065
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(queryEmbedding, memoryEmbedding)));
168066
+ }
168067
+ return semanticScores;
168068
+ }
168069
+ function getFtsMatches(args) {
168070
+ try {
168071
+ return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
168072
+ } catch (error48) {
168073
+ log(`[search] FTS query failed for "${args.query}": ${error48 instanceof Error ? error48.message : String(error48)}`);
168074
+ return [];
168075
+ }
168076
+ }
168077
+ function getFtsScores(matches) {
168078
+ return new Map(matches.map((memory, rank) => [memory.id, 1 / (rank + 1)]));
168079
+ }
168080
+ function selectSemanticCandidates(args) {
168081
+ if (args.ftsMatches.length === 0) {
168082
+ return args.memories;
168083
+ }
168084
+ const candidateIds = new Set(args.ftsMatches.map((memory) => memory.id));
168085
+ const cachedEmbeddings = peekProjectEmbeddings(args.projectPath);
168086
+ if (cachedEmbeddings) {
168087
+ for (const memoryId of cachedEmbeddings.keys()) {
168088
+ candidateIds.add(memoryId);
168089
+ }
168090
+ }
168091
+ return args.memories.filter((memory) => candidateIds.has(memory.id));
168092
+ }
168093
+ function mergeMemoryResults(args) {
168094
+ const memoryById = new Map(args.memories.map((memory) => [memory.id, memory]));
168095
+ const candidateIds = new Set([...args.semanticScores.keys(), ...args.ftsScores.keys()]);
168096
+ const results = [];
168097
+ for (const id of candidateIds) {
168098
+ if (args.visibleMemoryIds?.has(id)) {
168099
+ continue;
168100
+ }
168101
+ const memory = memoryById.get(id);
168102
+ if (!memory) {
168103
+ continue;
168104
+ }
168105
+ const semanticScore = args.semanticScores.get(id);
168106
+ const ftsScore = args.ftsScores.get(id);
168107
+ let score = 0;
168108
+ let matchType = "fts";
168109
+ if (semanticScore !== undefined && ftsScore !== undefined) {
168110
+ score = SEMANTIC_WEIGHT * semanticScore + FTS_WEIGHT * ftsScore;
168111
+ matchType = "hybrid";
168112
+ } else if (semanticScore !== undefined) {
168113
+ score = semanticScore * SINGLE_SOURCE_PENALTY;
168114
+ matchType = "semantic";
168115
+ } else if (ftsScore !== undefined) {
168116
+ score = ftsScore * SINGLE_SOURCE_PENALTY;
168117
+ matchType = "fts";
168118
+ }
168119
+ if (score <= 0) {
168120
+ continue;
168121
+ }
168122
+ results.push({
168123
+ source: "memory",
168124
+ content: previewText(memory.content),
168125
+ score,
168126
+ memoryId: memory.id,
168127
+ category: memory.category,
168128
+ matchType
168129
+ });
168130
+ }
168131
+ return results.sort((left, right) => {
168132
+ if (right.score !== left.score) {
168133
+ return right.score - left.score;
168134
+ }
168135
+ return left.memoryId - right.memoryId;
168136
+ }).slice(0, args.limit);
168137
+ }
168138
+ async function searchMemories(args) {
168139
+ if (!args.memoryEnabled) {
168140
+ return [];
168141
+ }
168142
+ const memories = getMemoriesByProject(args.db, args.projectPath);
168143
+ if (memories.length === 0) {
168144
+ return [];
168145
+ }
168146
+ const ftsMatches = getFtsMatches({
168147
+ db: args.db,
168148
+ projectPath: args.projectPath,
168149
+ query: args.query,
168150
+ limit: FTS_SEMANTIC_CANDIDATE_LIMIT
168151
+ });
168152
+ const ftsScores = getFtsScores(ftsMatches);
168153
+ const semanticCandidates = selectSemanticCandidates({
168154
+ memories,
168155
+ projectPath: args.projectPath,
168156
+ ftsMatches
168157
+ });
168158
+ const semanticScores = await getSemanticScores({
168159
+ db: args.db,
168160
+ projectPath: args.projectPath,
168161
+ query: args.query,
168162
+ memories: semanticCandidates,
168163
+ embeddingEnabled: args.embeddingEnabled,
168164
+ embedQuery: args.embedQuery,
168165
+ isEmbeddingRuntimeEnabled: args.isEmbeddingRuntimeEnabled,
168166
+ signal: args.signal
168167
+ });
168168
+ return mergeMemoryResults({
168169
+ memories,
168170
+ semanticScores,
168171
+ ftsScores,
168172
+ limit: args.limit,
168173
+ visibleMemoryIds: args.visibleMemoryIds
168174
+ });
168175
+ }
168176
+ function linearDecayScore(rank, total) {
168177
+ if (total <= 0)
168178
+ return 0;
168179
+ return Math.max(0, 1 - rank / total);
168180
+ }
168181
+ function searchMessages(args) {
168182
+ ensureMessagesIndexed(args.db, args.sessionId, args.readMessages);
168183
+ const sanitizedQuery = sanitizeFtsQuery(args.query.trim());
168184
+ if (sanitizedQuery.length === 0) {
168185
+ return [];
168186
+ }
168187
+ const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
168188
+ const rows = getMessageSearchStatement(args.db).all(args.sessionId, sanitizedQuery, fetchLimit).map((row) => row);
168189
+ const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
168190
+ const filtered = rows.map((row) => {
168191
+ const messageOrdinal = getMessageOrdinal(row.messageOrdinal);
168192
+ if (messageOrdinal === null || typeof row.messageId !== "string" || typeof row.role !== "string" || typeof row.content !== "string") {
168193
+ return null;
168194
+ }
168195
+ if (cutoff !== null && messageOrdinal > cutoff) {
168196
+ return null;
168197
+ }
168198
+ return {
168199
+ messageOrdinal,
168200
+ messageId: row.messageId,
168201
+ role: row.role,
168202
+ content: row.content
168203
+ };
168204
+ }).filter((result) => result !== null).slice(0, args.limit);
168205
+ return filtered.map((row, rank) => ({
168206
+ source: "message",
168207
+ content: previewText(row.content),
168208
+ score: linearDecayScore(rank, filtered.length),
168209
+ messageOrdinal: row.messageOrdinal,
168210
+ messageId: row.messageId,
168211
+ role: row.role
168212
+ }));
168213
+ }
168214
+ function getSourceBoost(result) {
168215
+ switch (result.source) {
168216
+ case "memory":
168217
+ return MEMORY_SOURCE_BOOST;
168218
+ case "message":
168219
+ return MESSAGE_SOURCE_BOOST;
168220
+ case "git_commit":
168221
+ return GIT_COMMIT_SOURCE_BOOST;
168222
+ }
168223
+ }
168224
+ function compareUnifiedResults(left, right) {
168225
+ const leftEffective = left.score * getSourceBoost(left);
168226
+ const rightEffective = right.score * getSourceBoost(right);
168227
+ if (rightEffective !== leftEffective) {
168228
+ return rightEffective - leftEffective;
168229
+ }
168230
+ if (left.source === "memory" && right.source === "memory") {
168231
+ return left.memoryId - right.memoryId;
168232
+ }
168233
+ if (left.source === "message" && right.source === "message") {
168234
+ return left.messageOrdinal - right.messageOrdinal;
168235
+ }
168236
+ if (left.source === "git_commit" && right.source === "git_commit") {
168237
+ return right.committedAtMs - left.committedAtMs;
168238
+ }
168239
+ return 0;
168240
+ }
168241
+ function toGitCommitResult(hit) {
168242
+ return {
168243
+ source: "git_commit",
168244
+ content: previewText(hit.commit.message),
168245
+ score: hit.score,
168246
+ sha: hit.commit.sha,
168247
+ shortSha: hit.commit.shortSha,
168248
+ author: hit.commit.author,
168249
+ committedAtMs: hit.commit.committedAtMs,
168250
+ matchType: hit.matchType
168251
+ };
168252
+ }
168253
+ async function searchGitCommitsAsync(args) {
168254
+ if (args.limit <= 0)
168255
+ return [];
168256
+ let queryEmbedding = null;
168257
+ if (args.embeddingEnabled && args.isEmbeddingRuntimeEnabled()) {
168258
+ try {
168259
+ queryEmbedding = await args.embedQuery(args.query, args.signal);
168260
+ } catch (error48) {
168261
+ log(`[search] git commit query embedding failed: ${error48 instanceof Error ? error48.message : String(error48)}`);
168262
+ }
168263
+ }
168264
+ const hits = searchGitCommitsSync(args.db, args.projectPath, args.query, {
168265
+ limit: args.limit,
168266
+ queryEmbedding
168267
+ });
168268
+ return hits.map(toGitCommitResult);
168269
+ }
168270
+ function resolveSources(sources) {
168271
+ if (sources === undefined) {
168272
+ return new Set(["memory", "message", "git_commit"]);
168273
+ }
168274
+ const set2 = new Set;
168275
+ for (const source of sources) {
168276
+ if (source === "memory" || source === "message" || source === "git_commit") {
168277
+ set2.add(source);
168278
+ }
168279
+ }
168280
+ return set2;
168281
+ }
168282
+ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
168283
+ const trimmedQuery = query.trim();
168284
+ if (trimmedQuery.length === 0) {
168285
+ return [];
168286
+ }
168287
+ const limit = normalizeLimit(options.limit);
168288
+ const tierLimit = Math.max(limit * 3, DEFAULT_UNIFIED_SEARCH_LIMIT);
168289
+ const embeddingEnabled = options.embeddingEnabled ?? true;
168290
+ const embedQuery = options.embedQuery ?? embedText;
168291
+ const isEmbeddingRuntimeEnabled = options.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled;
168292
+ const gitCommitsEnabled = options.gitCommitsEnabled ?? false;
168293
+ const activeSources = resolveSources(options.sources);
168294
+ const runMemory = activeSources.has("memory") && (options.memoryEnabled ?? true);
168295
+ const runMessages = activeSources.has("message");
168296
+ const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
168297
+ const [memoryResults, messageResults, gitCommitResults] = await Promise.all([
168298
+ runMemory ? searchMemories({
168299
+ db,
168300
+ projectPath,
168301
+ query: trimmedQuery,
168302
+ limit: tierLimit,
168303
+ memoryEnabled: true,
168304
+ embeddingEnabled,
168305
+ embedQuery,
168306
+ isEmbeddingRuntimeEnabled,
168307
+ visibleMemoryIds: options.visibleMemoryIds,
168308
+ signal: options.signal
168309
+ }) : Promise.resolve([]),
168310
+ runMessages ? Promise.resolve(searchMessages({
168311
+ db,
168312
+ sessionId,
168313
+ query: trimmedQuery,
168314
+ limit: tierLimit,
168315
+ readMessages: options.readMessages ?? readRawSessionMessages,
168316
+ maxOrdinal: options.maxMessageOrdinal
168317
+ })) : Promise.resolve([]),
168318
+ runGitCommits ? searchGitCommitsAsync({
168319
+ db,
168320
+ projectPath,
168321
+ query: trimmedQuery,
168322
+ limit: tierLimit,
168323
+ embeddingEnabled,
168324
+ embedQuery,
168325
+ isEmbeddingRuntimeEnabled,
168326
+ signal: options.signal
168327
+ }) : Promise.resolve([])
168328
+ ]);
168329
+ const results = [...memoryResults, ...messageResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
168330
+ const countRetrievals = options.countRetrievals ?? true;
168331
+ if (countRetrievals) {
168332
+ const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
168333
+ if (memoryIds.length > 0) {
168334
+ db.transaction(() => {
168335
+ for (const memoryId of memoryIds) {
168336
+ updateMemoryRetrievalCount(db, memoryId);
168337
+ }
168338
+ })();
168339
+ }
168340
+ }
168341
+ return results;
168342
+ }
168343
+
168344
+ // src/hooks/magic-context/auto-search-runner.ts
168345
+ init_logger();
168346
+
168347
+ // src/hooks/magic-context/auto-search-hint.ts
168348
+ init_caveman();
168349
+ var MAX_FRAGMENTS = 3;
168350
+ var FRAGMENT_CHAR_CAP = 80;
168351
+ var MAX_HINT_CHARS = 800;
168352
+ var MS_PER_DAY2 = 24 * 60 * 60 * 1000;
168353
+ function truncate(text, limit) {
168354
+ const normalized = text.replace(/\s+/g, " ").trim();
168355
+ if (normalized.length <= limit)
168356
+ return normalized;
168357
+ return `${normalized.slice(0, Math.max(0, limit - 1)).trimEnd()}\u2026`;
168358
+ }
168359
+ function formatAge(committedAtMs) {
168360
+ const delta = Date.now() - committedAtMs;
168361
+ if (delta < 0)
168362
+ return "future";
168363
+ const days = Math.floor(delta / MS_PER_DAY2);
168364
+ if (days <= 0)
168365
+ return "today";
168366
+ if (days === 1)
168367
+ return "1d ago";
168368
+ if (days < 30)
168369
+ return `${days}d ago`;
168370
+ const months = Math.floor(days / 30);
168371
+ if (months === 1)
168372
+ return "1mo ago";
168373
+ if (months < 12)
168374
+ return `${months}mo ago`;
168375
+ const years = Math.floor(days / 365);
168376
+ return years === 1 ? "1y ago" : `${years}y ago`;
168377
+ }
168378
+ function renderFragment(result, charCap) {
168379
+ switch (result.source) {
168380
+ case "memory": {
168381
+ const compressed = cavemanCompress(result.content, "ultra");
168382
+ return truncate(compressed, charCap);
168383
+ }
168384
+ case "git_commit": {
168385
+ const subject = result.content.split(/\r?\n/)[0] ?? result.content;
168386
+ const body = truncate(subject, Math.max(10, charCap - 20));
168387
+ return `commit ${result.shortSha} ${formatAge(result.committedAtMs)}: ${body}`;
168388
+ }
168389
+ case "message": {
168390
+ const compressed = cavemanCompress(result.content, "ultra");
168391
+ return truncate(compressed, charCap);
168392
+ }
168393
+ }
168394
+ }
168395
+ function buildAutoSearchHint(results, options = {}) {
168396
+ const maxFragments = Math.max(1, options.maxFragments ?? MAX_FRAGMENTS);
168397
+ const fragmentCharCap = Math.max(20, options.fragmentCharCap ?? FRAGMENT_CHAR_CAP);
168398
+ const picks = results.slice(0, maxFragments);
168399
+ const lines = [];
168400
+ for (const result of picks) {
168401
+ const fragment = renderFragment(result, fragmentCharCap);
168402
+ if (fragment.length === 0)
168403
+ continue;
168404
+ lines.push(`- ${fragment}`);
168405
+ }
168406
+ if (lines.length === 0)
168407
+ return null;
168408
+ const header = lines.length === 1 ? "Your memory may contain 1 related fragment:" : `Your memory may contain ${lines.length} related fragments:`;
168409
+ const footer = "Run ctx_search to retrieve full context if relevant.";
168410
+ const body = [header, ...lines, footer].join(`
168411
+ `);
168412
+ const wrapped = `<ctx-search-hint>
168413
+ ${body}
168414
+ </ctx-search-hint>`;
168415
+ if (wrapped.length > MAX_HINT_CHARS) {
168416
+ const overflow = wrapped.length - MAX_HINT_CHARS;
168417
+ const trimmedBody = body.slice(0, Math.max(0, body.length - overflow - 1)).trimEnd();
168418
+ return `<ctx-search-hint>
168419
+ ${trimmedBody}\u2026
168420
+ </ctx-search-hint>`;
168421
+ }
168422
+ return wrapped;
168423
+ }
168424
+
168425
+ // src/hooks/magic-context/auto-search-runner.ts
168426
+ var autoSearchByTurn = new Map;
168427
+ var AUTO_SEARCH_TIMEOUT_MS = 3000;
168428
+ async function unifiedSearchWithTimeout(db, sessionId, projectPath, prompt, options, timeoutMs) {
168429
+ const controller = new AbortController;
168430
+ let timer;
168431
+ const timeoutPromise = new Promise((resolve3) => {
168432
+ timer = setTimeout(() => {
168433
+ controller.abort();
168434
+ resolve3(null);
168435
+ }, timeoutMs);
168436
+ });
168437
+ try {
168438
+ return await Promise.race([
168439
+ unifiedSearch(db, sessionId, projectPath, prompt, {
168440
+ ...options,
168441
+ signal: controller.signal,
168442
+ countRetrievals: false
168443
+ }),
168444
+ timeoutPromise
168445
+ ]);
168446
+ } finally {
168447
+ if (timer !== undefined)
168448
+ clearTimeout(timer);
168449
+ }
168450
+ }
168451
+ function collectUserPromptParts(message) {
168452
+ let collected = "";
168453
+ for (const part of message.parts) {
168454
+ const p = part;
168455
+ if (p.type === "text" && typeof p.text === "string") {
168456
+ collected += (collected.length > 0 ? `
168457
+ ` : "") + p.text;
168458
+ }
168459
+ }
168460
+ return collected;
168461
+ }
168462
+ function hasStackedAugmentation(rawText) {
168463
+ return rawText.includes("<sidekick-augmentation>") || rawText.includes("<ctx-search-hint>") || rawText.includes("<ctx-search-auto>");
168464
+ }
168465
+ function extractUserPromptText(message) {
168466
+ return collectUserPromptParts(message).replace(/\u00A7\d+\u00A7\s*/g, "").replace(/<!--\s*\+[\d\s.hmdw]+\s*-->/g, "").replace(/<!--\s*OMO_INTERNAL_INITIATOR[\s\S]*?-->/g, "").replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").replace(/<ctx-search-hint>[\s\S]*?<\/ctx-search-hint>/g, "").replace(/<ctx-search-auto>[\s\S]*?<\/ctx-search-auto>/g, "").replace(/<instruction[^>]*>[\s\S]*?<\/instruction>/g, "").replace(/<sidekick-augmentation>[\s\S]*?<\/sidekick-augmentation>/g, "").replace(/[ \t]+\n/g, `
168467
+ `).replace(/\n{3,}/g, `
168468
+
168469
+ `).trim();
168470
+ }
168471
+ function findLatestMeaningfulUserMessage(messages) {
168472
+ for (let i = messages.length - 1;i >= 0; i -= 1) {
168473
+ const msg = messages[i];
168474
+ if (msg.info.role !== "user")
168475
+ continue;
168476
+ if (typeof msg.info.id !== "string")
168477
+ continue;
168478
+ for (const part of msg.parts) {
168479
+ const p = part;
168480
+ if (p.type === "text" && typeof p.text === "string" && p.text.trim().length > 0) {
168481
+ return msg;
168482
+ }
168483
+ }
168484
+ }
168485
+ return null;
168486
+ }
168487
+ async function runAutoSearchHint(args) {
168488
+ const { sessionId, db, messages, options } = args;
168489
+ if (!options.enabled)
168490
+ return;
168491
+ const userMsg = findLatestMeaningfulUserMessage(messages);
168492
+ if (!userMsg || typeof userMsg.info.id !== "string")
168493
+ return;
168494
+ const userMsgId = userMsg.info.id;
168495
+ const cached2 = autoSearchByTurn.get(sessionId);
168496
+ if (cached2 && cached2.messageId === userMsgId) {
168497
+ appendReminderToUserMessageById(messages, userMsgId, cached2.hint);
168498
+ return;
168499
+ }
168500
+ const rawPartsText = collectUserPromptParts(userMsg);
168501
+ if (hasStackedAugmentation(rawPartsText)) {
168502
+ sessionLog(sessionId, "auto-search: skipping \u2014 user message already carries augmentation/hint");
168503
+ autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
168504
+ return;
168505
+ }
168506
+ const rawPrompt = extractUserPromptText(userMsg);
168507
+ if (rawPrompt.length < options.minPromptChars) {
168508
+ autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
168509
+ return;
168510
+ }
168511
+ let results;
168512
+ try {
168513
+ const searchOptions = {
168514
+ limit: 10,
168515
+ memoryEnabled: options.memoryEnabled,
168516
+ embeddingEnabled: options.embeddingEnabled,
168517
+ gitCommitsEnabled: options.gitCommitsEnabled,
168518
+ visibleMemoryIds: options.visibleMemoryIds ?? null
168519
+ };
168520
+ results = await unifiedSearchWithTimeout(db, sessionId, options.projectPath, rawPrompt, searchOptions, AUTO_SEARCH_TIMEOUT_MS);
168521
+ } catch (error48) {
168522
+ log(`[auto-search] unified search failed for session ${sessionId}: ${error48 instanceof Error ? error48.message : String(error48)}`);
168523
+ autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
168524
+ return;
168525
+ }
168526
+ if (results === null) {
168527
+ sessionLog(sessionId, `auto-search: timed out after ${AUTO_SEARCH_TIMEOUT_MS}ms, skipping hint for this turn`);
168528
+ autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
168529
+ return;
168530
+ }
168531
+ if (results.length === 0) {
168532
+ autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
168533
+ return;
168534
+ }
168535
+ if (results[0].score < options.scoreThreshold) {
168536
+ sessionLog(sessionId, `auto-search: top score ${results[0].score.toFixed(3)} below threshold ${options.scoreThreshold}`);
168537
+ autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
168538
+ return;
168539
+ }
168540
+ const hintText = buildAutoSearchHint(results);
168541
+ if (!hintText) {
168542
+ autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
168543
+ return;
168544
+ }
168545
+ const payload = `
168546
+
168547
+ ${hintText}`;
168548
+ autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: payload });
168549
+ appendReminderToUserMessageById(messages, userMsgId, payload);
168550
+ sessionLog(sessionId, `auto-search: attached hint to ${userMsgId} (${results.length} fragments, top score ${results[0].score.toFixed(3)})`);
168551
+ }
168552
+ function clearAutoSearchForSession(sessionId) {
168553
+ autoSearchByTurn.delete(sessionId);
168554
+ }
168555
+
166927
168556
  // src/hooks/magic-context/transform-postprocess-phase.ts
166928
168557
  init_compartment_runner();
166929
168558
 
@@ -167151,7 +168780,7 @@ function logTransformTiming(sessionId, stage, startMs, extra) {
167151
168780
  }
167152
168781
 
167153
168782
  // src/hooks/magic-context/transform-postprocess-phase.ts
167154
- function runPostTransformPhase(args) {
168783
+ async function runPostTransformPhase(args) {
167155
168784
  let didMutateFromPendingOperations = false;
167156
168785
  const isExplicitFlush = args.flushedSessions.has(args.sessionId);
167157
168786
  const alreadyRanThisTurn = args.currentTurnId !== null && args.lastHeuristicsTurnId.get(args.sessionId) === args.currentTurnId;
@@ -167211,8 +168840,8 @@ function runPostTransformPhase(args) {
167211
168840
  logTransformTiming(args.sessionId, "watermarkCleanup", t6);
167212
168841
  }
167213
168842
  const t7 = performance.now();
167214
- const clearedReasoning = clearOldReasoning(args.messages, args.reasoningByMessage, args.messageTagNumbers, args.clearReasoningAge);
167215
- stripClearedReasoning(args.messages);
168843
+ const clearedReasoning = clearOldReasoning(args.messages, args.reasoningByMessage, args.messageTagNumbers, args.clearReasoningAge, args.skipTypedReasoningCleanup);
168844
+ stripClearedReasoning(args.messages, args.skipTypedReasoningCleanup);
167216
168845
  const strippedInline = stripInlineThinking(args.messages, args.messageTagNumbers, args.clearReasoningAge);
167217
168846
  if (clearedReasoning > 0 || strippedInline > 0) {
167218
168847
  let maxTag = 0;
@@ -167226,6 +168855,7 @@ function runPostTransformPhase(args) {
167226
168855
  updateSessionMeta(args.db, args.sessionId, {
167227
168856
  clearedReasoningThroughTag: newWatermark
167228
168857
  });
168858
+ args.sessionMeta.clearedReasoningThroughTag = newWatermark;
167229
168859
  sessionLog(args.sessionId, `reasoning cleanup: cleared=${clearedReasoning} inlineStripped=${strippedInline} watermark=${currentWatermark}\u2192${newWatermark}`);
167230
168860
  } else {
167231
168861
  sessionLog(args.sessionId, `reasoning cleanup: cleared=${clearedReasoning} inlineStripped=${strippedInline} watermark=${currentWatermark} (unchanged)`);
@@ -167392,6 +169022,28 @@ function runPostTransformPhase(args) {
167392
169022
  const anchoredMessageId = appendReminderToLatestUserMessage(args.messages, noteInstruction);
167393
169023
  markNoteNudgeDelivered(args.db, args.sessionId, noteInstruction, anchoredMessageId);
167394
169024
  }
169025
+ if (args.fullFeatureMode && args.autoSearch?.enabled && args.projectPath) {
169026
+ const visibleMemoryIds = getVisibleMemoryIds(args.db, args.sessionId) ?? undefined;
169027
+ try {
169028
+ await runAutoSearchHint({
169029
+ sessionId: args.sessionId,
169030
+ db: args.db,
169031
+ messages: args.messages,
169032
+ options: {
169033
+ enabled: true,
169034
+ scoreThreshold: args.autoSearch.scoreThreshold,
169035
+ minPromptChars: args.autoSearch.minPromptChars,
169036
+ projectPath: args.projectPath,
169037
+ memoryEnabled: args.autoSearch.memoryEnabled,
169038
+ embeddingEnabled: args.autoSearch.embeddingEnabled,
169039
+ gitCommitsEnabled: args.autoSearch.gitCommitsEnabled,
169040
+ visibleMemoryIds
169041
+ }
169042
+ });
169043
+ } catch (error48) {
169044
+ sessionLog(args.sessionId, "auto-search runner failed:", error48);
169045
+ }
169046
+ }
167395
169047
  }
167396
169048
 
167397
169049
  // src/hooks/magic-context/nudge-placement-store.ts
@@ -167503,10 +169155,20 @@ function createTransform(deps) {
167503
169155
  deps.liveModelBySession.set(sessionId, lastAssistantModel);
167504
169156
  updateSessionMeta(db, sessionId, {
167505
169157
  lastContextPercentage: 0,
167506
- lastInputTokens: 0
169158
+ lastInputTokens: 0,
169159
+ clearedReasoningThroughTag: 0
167507
169160
  });
167508
169161
  clearHistorianFailureState(db, sessionId);
169162
+ clearPersistedReasoningWatermark(db, sessionId);
169163
+ clearDetectedContextLimit(db, sessionId);
169164
+ clearEmergencyRecovery(db, sessionId);
167509
169165
  deps.contextUsageMap.delete(sessionId);
169166
+ sessionMeta = {
169167
+ ...sessionMeta,
169168
+ lastContextPercentage: 0,
169169
+ lastInputTokens: 0,
169170
+ clearedReasoningThroughTag: 0
169171
+ };
167510
169172
  }
167511
169173
  }
167512
169174
  }
@@ -167528,7 +169190,21 @@ function createTransform(deps) {
167528
169190
  sessionMeta = { ...sessionMeta, lastContextPercentage: 0, lastInputTokens: 0 };
167529
169191
  }
167530
169192
  }
167531
- const contextUsageEarly = loadContextUsage(deps.contextUsageMap, db, sessionId);
169193
+ let contextUsageEarly = loadContextUsage(deps.contextUsageMap, db, sessionId);
169194
+ if (fullFeatureMode) {
169195
+ try {
169196
+ const overflowState = getOverflowState(db, sessionId);
169197
+ if (overflowState.needsEmergencyRecovery && contextUsageEarly.percentage < 95) {
169198
+ sessionLog(sessionId, `transform: bumping percentage to 95% due to overflow recovery flag (was ${contextUsageEarly.percentage.toFixed(1)}%, detectedLimit=${overflowState.detectedContextLimit || "unknown"})`);
169199
+ contextUsageEarly = {
169200
+ ...contextUsageEarly,
169201
+ percentage: 95
169202
+ };
169203
+ }
169204
+ } catch (error48) {
169205
+ sessionLog(sessionId, "transform: overflow recovery state read failed:", getErrorMessage(error48));
169206
+ }
169207
+ }
167532
169208
  const historyBudgetTokens = resolveHistoryBudgetTokens(deps.historyBudgetPercentage, contextUsageEarly, deps.executeThresholdPercentage, deps.getModelKey?.(sessionId), deps.executeThresholdTokens);
167533
169209
  const schedulerDecisionEarly = resolveSchedulerDecision(deps.scheduler, sessionMeta, contextUsageEarly, sessionId, deps.getModelKey?.(sessionId));
167534
169210
  const isCacheBusting = deps.flushedSessions.has(sessionId);
@@ -167657,10 +169333,12 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
167657
169333
  const t3 = performance.now();
167658
169334
  const strippedStructuralNoise = stripStructuralNoise(messages);
167659
169335
  logTransformTiming(sessionId, "stripStructuralNoise", t3, `strippedParts=${strippedStructuralNoise}`);
169336
+ const currentSessionModel = deps.liveModelBySession?.get(sessionId) ?? findLastAssistantModel(messages) ?? undefined;
169337
+ const skipTypedReasoningCleanup = modelRequiresInterleavedReasoning(currentSessionModel);
167660
169338
  const persistedReasoningWatermark = sessionMeta?.clearedReasoningThroughTag ?? 0;
167661
169339
  if (persistedReasoningWatermark > 0) {
167662
169340
  const tReplay = performance.now();
167663
- const replayed = replayClearedReasoning(messages, reasoningByMessage, messageTagNumbers, persistedReasoningWatermark);
169341
+ const replayed = replayClearedReasoning(messages, reasoningByMessage, messageTagNumbers, persistedReasoningWatermark, skipTypedReasoningCleanup);
167664
169342
  const replayedInline = replayStrippedInlineThinking(messages, messageTagNumbers, persistedReasoningWatermark);
167665
169343
  if (replayed > 0 || replayedInline > 0) {
167666
169344
  sessionLog(sessionId, `reasoning replay: cleared=${replayed} inlineStripped=${replayedInline} (watermark=${persistedReasoningWatermark})`);
@@ -167668,10 +169346,10 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
167668
169346
  logTransformTiming(sessionId, "replayReasoningClearing", tReplay);
167669
169347
  }
167670
169348
  const t4 = performance.now();
167671
- const strippedClearedReasoning = stripClearedReasoning(messages);
169349
+ const strippedClearedReasoning = stripClearedReasoning(messages, skipTypedReasoningCleanup);
167672
169350
  logTransformTiming(sessionId, "stripClearedReasoning", t4, `strippedParts=${strippedClearedReasoning}`);
167673
169351
  const tMergeStrip = performance.now();
167674
- const strippedMergedReasoning = stripReasoningFromMergedAssistants(messages);
169352
+ const strippedMergedReasoning = skipTypedReasoningCleanup ? 0 : stripReasoningFromMergedAssistants(messages);
167675
169353
  if (strippedMergedReasoning > 0) {
167676
169354
  sessionLog(sessionId, `stripped ${strippedMergedReasoning} reasoning parts from merged assistants (anthropic groupIntoBlocks workaround)`);
167677
169355
  }
@@ -167721,7 +169399,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
167721
169399
  sessionMeta = { ...sessionMeta, compartmentInProgress };
167722
169400
  logTransformTiming(sessionId, "compartmentPhase", tCompartmentPhase);
167723
169401
  const tPostProcess = performance.now();
167724
- runPostTransformPhase({
169402
+ await runPostTransformPhase({
167725
169403
  sessionId,
167726
169404
  db,
167727
169405
  messages,
@@ -167751,7 +169429,9 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
167751
169429
  watermark,
167752
169430
  forceMaterializationPercentage: FORCE_MATERIALIZE_PERCENTAGE,
167753
169431
  hasRecentReduceCall,
167754
- projectPath: deps.projectPath
169432
+ skipTypedReasoningCleanup,
169433
+ projectPath: deps.projectPath,
169434
+ autoSearch: deps.autoSearch
167755
169435
  });
167756
169436
  logTransformTiming(sessionId, "postTransformPhase", tPostProcess);
167757
169437
  const msgTokens = getMessageTokensCache(sessionId);
@@ -167971,6 +169651,25 @@ function createEventHandler2(deps) {
167971
169651
  }
167972
169652
  return;
167973
169653
  }
169654
+ if (input.event.type === "session.error") {
169655
+ const errInfo = getSessionErrorInfo(input.event.properties);
169656
+ if (!errInfo) {
169657
+ return;
169658
+ }
169659
+ try {
169660
+ const detection = detectOverflow(errInfo.error);
169661
+ if (!detection.isOverflow) {
169662
+ return;
169663
+ }
169664
+ const existing = getOverflowState(deps.db, errInfo.sessionID);
169665
+ recordOverflowDetected(deps.db, errInfo.sessionID, detection.reportedLimit);
169666
+ sessionLog(errInfo.sessionID, `overflow detected via session.error: reportedLimit=${detection.reportedLimit ?? "unknown"} pattern=${detection.matchedPattern ?? "n/a"} (previousRecovery=${existing.needsEmergencyRecovery})`);
169667
+ deps.onSessionCacheInvalidated?.(errInfo.sessionID);
169668
+ } catch (error48) {
169669
+ sessionLog(errInfo.sessionID, "event session.error handling failed:", error48);
169670
+ }
169671
+ return;
169672
+ }
167974
169673
  if (input.event.type === "message.updated") {
167975
169674
  const info = getMessageUpdatedAssistantInfo(input.event.properties);
167976
169675
  if (!info) {
@@ -167987,6 +169686,18 @@ function createEventHandler2(deps) {
167987
169686
  } else {
167988
169687
  clearMessageTokensCache(info.sessionID);
167989
169688
  }
169689
+ if (info.error !== undefined && info.error !== null) {
169690
+ const detection = detectOverflow(info.error);
169691
+ if (detection.isOverflow) {
169692
+ try {
169693
+ recordOverflowDetected(deps.db, info.sessionID, detection.reportedLimit);
169694
+ sessionLog(info.sessionID, `overflow detected via message.updated: reportedLimit=${detection.reportedLimit ?? "unknown"} pattern=${detection.matchedPattern ?? "n/a"}`);
169695
+ deps.onSessionCacheInvalidated?.(info.sessionID);
169696
+ } catch (error48) {
169697
+ sessionLog(info.sessionID, "event message.updated overflow persistence failed:", error48);
169698
+ }
169699
+ }
169700
+ }
167990
169701
  const now = Date.now();
167991
169702
  const usageTokens = [
167992
169703
  info.tokens?.input,
@@ -168012,7 +169723,10 @@ function createEventHandler2(deps) {
168012
169723
  }
168013
169724
  if (hasUsageTokens) {
168014
169725
  const totalInputTokens = (info.tokens?.input ?? 0) + (info.tokens?.cache?.read ?? 0) + (info.tokens?.cache?.write ?? 0);
168015
- const contextLimit = resolveContextLimit(info.providerID, info.modelID);
169726
+ const contextLimit = resolveContextLimit(info.providerID, info.modelID, {
169727
+ db: deps.db,
169728
+ sessionID: info.sessionID
169729
+ });
168016
169730
  const percentage = contextLimit > 0 ? totalInputTokens / contextLimit * 100 : 0;
168017
169731
  sessionLog(info.sessionID, `event message.updated: totalInputTokens=${totalInputTokens} contextLimit=${contextLimit} percentage=${percentage.toFixed(1)}%`);
168018
169732
  deps.contextUsageMap.set(info.sessionID, {
@@ -168349,6 +170063,12 @@ function createChatMessageHook(args) {
168349
170063
  const sessionId = input.sessionID;
168350
170064
  if (!sessionId)
168351
170065
  return;
170066
+ if (input.model?.providerID && input.model.modelID) {
170067
+ args.liveModelBySession.set(sessionId, {
170068
+ providerID: input.model.providerID,
170069
+ modelID: input.model.modelID
170070
+ });
170071
+ }
168352
170072
  if (args.ctxReduceEnabled !== false) {
168353
170073
  const sessionMeta = getOrCreateSessionMeta(args.db, sessionId);
168354
170074
  const turnUsage = args.toolUsageSinceUserTurn.get(sessionId);
@@ -168382,8 +170102,12 @@ function createEventHook(args) {
168382
170102
  modelID: assistantInfo.modelID
168383
170103
  });
168384
170104
  if (previous && (previous.providerID !== assistantInfo.providerID || previous.modelID !== assistantInfo.modelID)) {
168385
- sessionLog(assistantInfo.sessionID, `model changed (${previous.providerID}/${previous.modelID} -> ${assistantInfo.providerID}/${assistantInfo.modelID}), clearing historian failure state`);
170105
+ sessionLog(assistantInfo.sessionID, `model changed (${previous.providerID}/${previous.modelID} -> ${assistantInfo.providerID}/${assistantInfo.modelID}), clearing historian failure state and reasoning watermark`);
168386
170106
  clearHistorianFailureState(args.db, assistantInfo.sessionID);
170107
+ clearPersistedReasoningWatermark(args.db, assistantInfo.sessionID);
170108
+ updateSessionMeta(args.db, assistantInfo.sessionID, {
170109
+ clearedReasoningThroughTag: 0
170110
+ });
168387
170111
  }
168388
170112
  }
168389
170113
  }
@@ -168402,6 +170126,7 @@ function createEventHook(args) {
168402
170126
  args.lastHeuristicsTurnId.delete(sessionId);
168403
170127
  args.commitSeenLastPass?.delete(sessionId);
168404
170128
  clearNoteNudgeState(args.db, sessionId);
170129
+ clearAutoSearchForSession(sessionId);
168405
170130
  }
168406
170131
  if (input.event.type === "message.removed") {
168407
170132
  return;
@@ -169012,13 +170737,21 @@ function createMagicContextHook(deps) {
169012
170737
  },
169013
170738
  projectPath,
169014
170739
  experimentalCompactionMarkers: deps.config.compaction_markers,
169015
- experimentalUserMemories: deps.config.experimental?.user_memories?.enabled,
170740
+ experimentalUserMemories: deps.config.dreamer?.user_memories?.enabled,
169016
170741
  experimentalTemporalAwareness: deps.config.experimental?.temporal_awareness === true,
169017
170742
  historianTwoPass: deps.config.historian?.two_pass === true,
169018
170743
  compressorMinCompartmentRatio: deps.config.compressor?.enabled === false ? undefined : deps.config.compressor?.min_compartment_ratio,
169019
170744
  compressorMaxMergeDepth: deps.config.compressor?.enabled === false ? undefined : deps.config.compressor?.max_merge_depth,
169020
170745
  compressorCooldownMs: deps.config.compressor?.enabled === false ? undefined : deps.config.compressor?.cooldown_ms,
169021
- liveModelBySession
170746
+ liveModelBySession,
170747
+ autoSearch: deps.config.experimental?.auto_search?.enabled ? {
170748
+ enabled: true,
170749
+ scoreThreshold: deps.config.experimental.auto_search.score_threshold,
170750
+ minPromptChars: deps.config.experimental.auto_search.min_prompt_chars,
170751
+ memoryEnabled: deps.config.memory?.enabled !== false,
170752
+ embeddingEnabled: deps.config.embedding?.provider !== "off",
170753
+ gitCommitsEnabled: deps.config.experimental?.git_commit_indexing?.enabled === true
170754
+ } : undefined
169022
170755
  });
169023
170756
  const eventHandler = createEventHandler2({
169024
170757
  contextUsageMap,
@@ -169054,14 +170787,14 @@ function createMagicContextHook(deps) {
169054
170787
  tasks: dreaming.tasks,
169055
170788
  taskTimeoutMinutes: dreaming.task_timeout_minutes,
169056
170789
  maxRuntimeMinutes: dreaming.max_runtime_minutes,
169057
- experimentalUserMemories: deps.config.experimental?.user_memories?.enabled ? {
170790
+ experimentalUserMemories: deps.config.dreamer?.user_memories?.enabled ? {
169058
170791
  enabled: true,
169059
- promotionThreshold: deps.config.experimental.user_memories?.promotion_threshold
170792
+ promotionThreshold: deps.config.dreamer.user_memories.promotion_threshold
169060
170793
  } : undefined,
169061
- experimentalPinKeyFiles: deps.config.experimental?.pin_key_files?.enabled ? {
170794
+ experimentalPinKeyFiles: deps.config.dreamer?.pin_key_files?.enabled ? {
169062
170795
  enabled: true,
169063
- token_budget: deps.config.experimental.pin_key_files?.token_budget,
169064
- min_reads: deps.config.experimental.pin_key_files?.min_reads
170796
+ token_budget: deps.config.dreamer.pin_key_files.token_budget,
170797
+ min_reads: deps.config.dreamer.pin_key_files.min_reads
169065
170798
  } : undefined
169066
170799
  }).catch((error48) => {
169067
170800
  log("[dreamer] scheduled queue processing failed:", error48);
@@ -169117,14 +170850,14 @@ function createMagicContextHook(deps) {
169117
170850
  projectPath,
169118
170851
  client: deps.client,
169119
170852
  directory: deps.directory,
169120
- experimentalUserMemories: deps.config.experimental?.user_memories?.enabled ? {
170853
+ experimentalUserMemories: deps.config.dreamer?.user_memories?.enabled ? {
169121
170854
  enabled: true,
169122
- promotionThreshold: deps.config.experimental.user_memories?.promotion_threshold
170855
+ promotionThreshold: deps.config.dreamer.user_memories.promotion_threshold
169123
170856
  } : undefined,
169124
- experimentalPinKeyFiles: deps.config.experimental?.pin_key_files?.enabled ? {
170857
+ experimentalPinKeyFiles: deps.config.dreamer?.pin_key_files?.enabled ? {
169125
170858
  enabled: true,
169126
- token_budget: deps.config.experimental.pin_key_files?.token_budget,
169127
- min_reads: deps.config.experimental.pin_key_files?.min_reads
170859
+ token_budget: deps.config.dreamer.pin_key_files.token_budget,
170860
+ min_reads: deps.config.dreamer.pin_key_files.min_reads
169128
170861
  } : undefined
169129
170862
  } : undefined
169130
170863
  });
@@ -169139,9 +170872,9 @@ function createMagicContextHook(deps) {
169139
170872
  directory: deps.directory,
169140
170873
  flushedSessions,
169141
170874
  lastHeuristicsTurnId,
169142
- experimentalUserMemories: deps.config.experimental?.user_memories?.enabled,
169143
- experimentalPinKeyFiles: deps.config.experimental?.pin_key_files?.enabled ?? false,
169144
- experimentalPinKeyFilesTokenBudget: deps.config.experimental?.pin_key_files?.token_budget,
170875
+ experimentalUserMemories: deps.config.dreamer?.user_memories?.enabled,
170876
+ experimentalPinKeyFiles: deps.config.dreamer?.pin_key_files?.enabled ?? false,
170877
+ experimentalPinKeyFilesTokenBudget: deps.config.dreamer?.pin_key_files?.token_budget,
169145
170878
  experimentalTemporalAwareness: deps.config.experimental?.temporal_awareness === true
169146
170879
  });
169147
170880
  const eventHook = createEventHook({
@@ -169169,6 +170902,7 @@ function createMagicContextHook(deps) {
169169
170902
  db,
169170
170903
  toolUsageSinceUserTurn,
169171
170904
  recentReduceBySession,
170905
+ liveModelBySession,
169172
170906
  variantBySession,
169173
170907
  agentBySession,
169174
170908
  flushedSessions,
@@ -169226,6 +170960,8 @@ function createSessionHooks(args) {
169226
170960
  memory: pluginConfig.memory,
169227
170961
  sidekick: pluginConfig.sidekick,
169228
170962
  dreamer: pluginConfig.dreamer,
170963
+ commit_cluster_trigger: pluginConfig.commit_cluster_trigger,
170964
+ compaction_markers: pluginConfig.compaction_markers,
169229
170965
  compressor: pluginConfig.compressor,
169230
170966
  experimental: pluginConfig.experimental
169231
170967
  }
@@ -169720,7 +171456,7 @@ var MEMORY_CATEGORIES = new Set(CATEGORY_PRIORITY);
169720
171456
  function isMemoryCategory2(value) {
169721
171457
  return MEMORY_CATEGORIES.has(value);
169722
171458
  }
169723
- function normalizeLimit(limit) {
171459
+ function normalizeLimit2(limit) {
169724
171460
  if (typeof limit !== "number" || !Number.isFinite(limit)) {
169725
171461
  return DEFAULT_SEARCH_LIMIT2;
169726
171462
  }
@@ -169895,7 +171631,7 @@ function createCtxMemoryTool(deps) {
169895
171631
  return `Archived memory [ID: ${args.id}].`;
169896
171632
  }
169897
171633
  if (args.action === "list") {
169898
- const limit = normalizeLimit(args.limit);
171634
+ const limit = normalizeLimit2(args.limit);
169899
171635
  const category = normalizeCategory(args.category);
169900
171636
  const memories = filterByCategory(getMemoriesByProject(deps.db, deps.projectPath), category).slice(0, limit);
169901
171637
  return formatMemoryList(memories);
@@ -170184,6 +171920,9 @@ ${parts.join(`
170184
171920
  projectIdentity: deps.projectIdentity,
170185
171921
  sessionId
170186
171922
  });
171923
+ try {
171924
+ setNoteLastReadAt(deps.db, sessionId);
171925
+ } catch {}
170187
171926
  if (sections.length === 0) {
170188
171927
  return `## Notes
170189
171928
 
@@ -170342,336 +172081,73 @@ function createCtxReduceTools(deps) {
170342
172081
  }
170343
172082
  // src/tools/ctx-search/constants.ts
170344
172083
  var CTX_SEARCH_TOOL_NAME = "ctx_search";
170345
- var CTX_SEARCH_DESCRIPTION = "Search across project memories, session facts, and conversation history. Returns ranked results from all sources. Use message ordinals with ctx_expand to retrieve full conversation context around a result.";
172084
+ var CTX_SEARCH_DESCRIPTION = [
172085
+ "Search across project memories, indexed git commits, and raw conversation history.",
172086
+ "",
172087
+ "Sources:",
172088
+ "- memory: curated cross-session knowledge (highest-signal; already-visible ones are filtered out)",
172089
+ "- message: raw user/assistant messages from older compartmentalized history",
172090
+ "- git_commit: HEAD git commits (enabled when experimental.git_commit_indexing is on)",
172091
+ "",
172092
+ "Narrow via the `sources` param when the question maps to a specific channel:",
172093
+ '- "was this working before / when did this break" \u2192 ["git_commit", "message"]',
172094
+ '- "when did we change this" \u2192 ["git_commit"]',
172095
+ '- "what is our naming convention" \u2192 ["memory"]',
172096
+ '- "did we discuss this earlier" \u2192 ["message"]',
172097
+ "Omit sources for a broad search across all enabled channels.",
172098
+ "",
172099
+ "Memories already rendered in <session-history> are hard-filtered from results \u2014 the agent already sees them in context.",
172100
+ "Session facts are NOT a search source; they're always visible in <session-history>.",
172101
+ "Message results include ordinals the agent can pass to ctx_expand for full context."
172102
+ ].join(`
172103
+ `);
170346
172104
  var DEFAULT_CTX_SEARCH_LIMIT = 10;
170347
172105
  // src/tools/ctx-search/tools.ts
170348
172106
  init_compartment_storage();
170349
172107
  import { tool as tool5 } from "@opencode-ai/plugin";
170350
-
170351
- // src/features/magic-context/search.ts
170352
- init_read_session_chunk();
170353
- init_logger();
170354
- init_memory();
170355
- init_embedding();
170356
- init_storage_memory_fts();
170357
- init_message_index();
170358
- init_storage();
170359
- var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
170360
- var FTS_SEMANTIC_CANDIDATE_LIMIT = 50;
170361
- var SEMANTIC_WEIGHT = 0.7;
170362
- var FTS_WEIGHT = 0.3;
170363
- var SINGLE_SOURCE_PENALTY = 0.8;
170364
- var RESULT_PREVIEW_LIMIT = 220;
170365
- var MEMORY_SOURCE_BOOST = 1.3;
170366
- var FACT_SOURCE_BOOST = 1.15;
170367
- var MESSAGE_SOURCE_BOOST = 1;
170368
- var messageSearchStatements = new WeakMap;
170369
- function normalizeLimit2(limit) {
170370
- if (typeof limit !== "number" || !Number.isFinite(limit)) {
170371
- return DEFAULT_UNIFIED_SEARCH_LIMIT;
170372
- }
170373
- return Math.max(1, Math.floor(limit));
170374
- }
170375
- function normalizeCosineScore(score) {
170376
- if (!Number.isFinite(score)) {
170377
- return 0;
170378
- }
170379
- return Math.min(1, Math.max(0, score));
170380
- }
170381
- function previewText(text) {
170382
- const normalized = text.replace(/\s+/g, " ").trim();
170383
- if (normalized.length <= RESULT_PREVIEW_LIMIT) {
170384
- return normalized;
170385
- }
170386
- return `${normalized.slice(0, RESULT_PREVIEW_LIMIT - 1).trimEnd()}\u2026`;
170387
- }
170388
- function tokenizeQuery(query) {
170389
- return Array.from(new Set(query.toLowerCase().split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0)));
170390
- }
170391
- function scoreTextMatch(content, query, extraText = "") {
170392
- const tokens = tokenizeQuery(query);
170393
- if (tokens.length === 0) {
170394
- return 0;
170395
- }
170396
- const haystack = `${content} ${extraText}`.toLowerCase();
170397
- const queryLower = query.trim().toLowerCase();
170398
- let matchedTokens = 0;
170399
- for (const token of tokens) {
170400
- if (haystack.includes(token)) {
170401
- matchedTokens++;
170402
- }
170403
- }
170404
- if (matchedTokens === 0) {
170405
- return 0;
170406
- }
170407
- let score = matchedTokens / tokens.length;
170408
- if (queryLower.length > 0 && haystack.includes(queryLower)) {
170409
- score += 0.35;
170410
- }
170411
- return Math.min(1, score);
170412
- }
170413
- function getMessageSearchStatement(db) {
170414
- let stmt = messageSearchStatements.get(db);
170415
- if (!stmt) {
170416
- stmt = db.prepare("SELECT message_ordinal AS messageOrdinal, message_id AS messageId, role, content FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ? ORDER BY bm25(message_history_fts), CAST(message_ordinal AS INTEGER) ASC LIMIT ?");
170417
- messageSearchStatements.set(db, stmt);
170418
- }
170419
- return stmt;
170420
- }
170421
- function getMessageOrdinal(value) {
170422
- if (typeof value === "number" && Number.isFinite(value)) {
170423
- return value;
170424
- }
170425
- if (typeof value === "string" && value.trim().length > 0) {
170426
- const parsed = Number.parseInt(value, 10);
170427
- return Number.isFinite(parsed) ? parsed : null;
170428
- }
170429
- return null;
170430
- }
170431
- async function getSemanticScores(args) {
170432
- const semanticScores = new Map;
170433
- if (!args.embeddingEnabled || !args.isEmbeddingRuntimeEnabled() || args.memories.length === 0) {
170434
- return semanticScores;
170435
- }
170436
- const queryEmbedding = await args.embedQuery(args.query);
170437
- if (!queryEmbedding) {
170438
- return semanticScores;
170439
- }
170440
- const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
170441
- const embeddings = await ensureMemoryEmbeddings({
170442
- db: args.db,
170443
- memories: args.memories,
170444
- existingEmbeddings: cachedEmbeddings
170445
- });
170446
- for (const memory of args.memories) {
170447
- const memoryEmbedding = embeddings.get(memory.id);
170448
- if (!memoryEmbedding) {
170449
- continue;
170450
- }
170451
- semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(queryEmbedding, memoryEmbedding)));
170452
- }
170453
- return semanticScores;
170454
- }
170455
- function getFtsMatches(args) {
170456
- try {
170457
- return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
170458
- } catch (error48) {
170459
- log(`[search] FTS query failed for "${args.query}": ${error48 instanceof Error ? error48.message : String(error48)}`);
170460
- return [];
170461
- }
170462
- }
170463
- function getFtsScores(matches) {
170464
- return new Map(matches.map((memory, rank) => [memory.id, 1 / (rank + 1)]));
170465
- }
170466
- function selectSemanticCandidates(args) {
170467
- if (args.ftsMatches.length === 0) {
170468
- return args.memories;
170469
- }
170470
- const candidateIds = new Set(args.ftsMatches.map((memory) => memory.id));
170471
- const cachedEmbeddings = peekProjectEmbeddings(args.projectPath);
170472
- if (cachedEmbeddings) {
170473
- for (const memoryId of cachedEmbeddings.keys()) {
170474
- candidateIds.add(memoryId);
170475
- }
170476
- }
170477
- return args.memories.filter((memory) => candidateIds.has(memory.id));
170478
- }
170479
- function mergeMemoryResults(args) {
170480
- const memoryById = new Map(args.memories.map((memory) => [memory.id, memory]));
170481
- const candidateIds = new Set([...args.semanticScores.keys(), ...args.ftsScores.keys()]);
170482
- const results = [];
170483
- for (const id of candidateIds) {
170484
- const memory = memoryById.get(id);
170485
- if (!memory) {
170486
- continue;
170487
- }
170488
- const semanticScore = args.semanticScores.get(id);
170489
- const ftsScore = args.ftsScores.get(id);
170490
- let score = 0;
170491
- let matchType = "fts";
170492
- if (semanticScore !== undefined && ftsScore !== undefined) {
170493
- score = SEMANTIC_WEIGHT * semanticScore + FTS_WEIGHT * ftsScore;
170494
- matchType = "hybrid";
170495
- } else if (semanticScore !== undefined) {
170496
- score = semanticScore * SINGLE_SOURCE_PENALTY;
170497
- matchType = "semantic";
170498
- } else if (ftsScore !== undefined) {
170499
- score = ftsScore * SINGLE_SOURCE_PENALTY;
170500
- matchType = "fts";
170501
- }
170502
- if (score <= 0) {
170503
- continue;
170504
- }
170505
- results.push({
170506
- source: "memory",
170507
- content: previewText(memory.content),
170508
- score,
170509
- memoryId: memory.id,
170510
- category: memory.category,
170511
- matchType
170512
- });
170513
- }
170514
- return results.sort((left, right) => {
170515
- if (right.score !== left.score) {
170516
- return right.score - left.score;
170517
- }
170518
- return left.memoryId - right.memoryId;
170519
- }).slice(0, args.limit);
170520
- }
170521
- async function searchMemories(args) {
170522
- if (!args.memoryEnabled) {
170523
- return [];
170524
- }
170525
- const memories = getMemoriesByProject(args.db, args.projectPath);
170526
- if (memories.length === 0) {
170527
- return [];
170528
- }
170529
- const ftsMatches = getFtsMatches({
170530
- db: args.db,
170531
- projectPath: args.projectPath,
170532
- query: args.query,
170533
- limit: FTS_SEMANTIC_CANDIDATE_LIMIT
170534
- });
170535
- const ftsScores = getFtsScores(ftsMatches);
170536
- const semanticCandidates = selectSemanticCandidates({
170537
- memories,
170538
- projectPath: args.projectPath,
170539
- ftsMatches
170540
- });
170541
- const semanticScores = await getSemanticScores({
170542
- db: args.db,
170543
- projectPath: args.projectPath,
170544
- query: args.query,
170545
- memories: semanticCandidates,
170546
- embeddingEnabled: args.embeddingEnabled,
170547
- embedQuery: args.embedQuery,
170548
- isEmbeddingRuntimeEnabled: args.isEmbeddingRuntimeEnabled
170549
- });
170550
- return mergeMemoryResults({
170551
- memories,
170552
- semanticScores,
170553
- ftsScores,
170554
- limit: args.limit
170555
- });
170556
- }
170557
- function searchFacts(args) {
170558
- return getSessionFacts(args.db, args.sessionId).map((fact) => ({
170559
- fact,
170560
- score: scoreTextMatch(fact.content, args.query, fact.category)
170561
- })).filter((candidate) => candidate.score > 0).sort((left, right) => {
170562
- if (right.score !== left.score) {
170563
- return right.score - left.score;
170564
- }
170565
- return left.fact.id - right.fact.id;
170566
- }).slice(0, args.limit).map(({ fact, score }) => ({
170567
- source: "fact",
170568
- content: previewText(fact.content),
170569
- score,
170570
- factId: fact.id,
170571
- factCategory: fact.category
170572
- }));
170573
- }
170574
- function searchMessages(args) {
170575
- ensureMessagesIndexed(args.db, args.sessionId, args.readMessages);
170576
- const sanitizedQuery = sanitizeFtsQuery(args.query.trim());
170577
- if (sanitizedQuery.length === 0) {
170578
- return [];
170579
- }
170580
- const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
170581
- const rows = getMessageSearchStatement(args.db).all(args.sessionId, sanitizedQuery, fetchLimit).map((row) => row);
170582
- const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
170583
- return rows.map((row, rank) => {
170584
- const messageOrdinal = getMessageOrdinal(row.messageOrdinal);
170585
- if (messageOrdinal === null || typeof row.messageId !== "string" || typeof row.role !== "string" || typeof row.content !== "string") {
170586
- return null;
170587
- }
170588
- if (cutoff !== null && messageOrdinal > cutoff) {
170589
- return null;
170590
- }
170591
- return {
170592
- source: "message",
170593
- content: previewText(row.content),
170594
- score: 1 / (rank + 1),
170595
- messageOrdinal,
170596
- messageId: row.messageId,
170597
- role: row.role
170598
- };
170599
- }).filter((result) => result !== null).slice(0, args.limit);
170600
- }
170601
- function getSourceBoost(result) {
170602
- switch (result.source) {
170603
- case "memory":
170604
- return MEMORY_SOURCE_BOOST;
170605
- case "fact":
170606
- return FACT_SOURCE_BOOST;
170607
- case "message":
170608
- return MESSAGE_SOURCE_BOOST;
170609
- }
170610
- }
170611
- function compareUnifiedResults(left, right) {
170612
- const leftEffective = left.score * getSourceBoost(left);
170613
- const rightEffective = right.score * getSourceBoost(right);
170614
- if (rightEffective !== leftEffective) {
170615
- return rightEffective - leftEffective;
170616
- }
170617
- if (left.source === "memory" && right.source === "memory") {
170618
- return left.memoryId - right.memoryId;
170619
- }
170620
- if (left.source === "fact" && right.source === "fact") {
170621
- return left.factId - right.factId;
170622
- }
170623
- if (left.source === "message" && right.source === "message") {
170624
- return left.messageOrdinal - right.messageOrdinal;
170625
- }
170626
- return 0;
170627
- }
170628
- async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
170629
- const trimmedQuery = query.trim();
170630
- if (trimmedQuery.length === 0) {
170631
- return [];
170632
- }
170633
- const limit = normalizeLimit2(options.limit);
170634
- const tierLimit = Math.max(limit * 3, DEFAULT_UNIFIED_SEARCH_LIMIT);
170635
- const [memoryResults, factResults, messageResults] = await Promise.all([
170636
- searchMemories({
170637
- db,
170638
- projectPath,
170639
- query: trimmedQuery,
170640
- limit: tierLimit,
170641
- memoryEnabled: options.memoryEnabled ?? true,
170642
- embeddingEnabled: options.embeddingEnabled ?? true,
170643
- embedQuery: options.embedQuery ?? embedText,
170644
- isEmbeddingRuntimeEnabled: options.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled
170645
- }),
170646
- Promise.resolve(searchFacts({ db, sessionId, query: trimmedQuery, limit: tierLimit })),
170647
- Promise.resolve(searchMessages({
170648
- db,
170649
- sessionId,
170650
- query: trimmedQuery,
170651
- limit: tierLimit,
170652
- readMessages: options.readMessages ?? readRawSessionMessages,
170653
- maxOrdinal: options.maxMessageOrdinal
170654
- }))
170655
- ]);
170656
- const results = [...memoryResults, ...factResults, ...messageResults].sort(compareUnifiedResults).slice(0, limit);
170657
- const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
170658
- if (memoryIds.length > 0) {
170659
- db.transaction(() => {
170660
- for (const memoryId of memoryIds) {
170661
- updateMemoryRetrievalCount(db, memoryId);
170662
- }
170663
- })();
170664
- }
170665
- return results;
170666
- }
170667
-
170668
- // src/tools/ctx-search/tools.ts
172108
+ init_inject_compartments();
172109
+ var VALID_SOURCES = new Set(["memory", "message", "git_commit"]);
170669
172110
  function normalizeLimit3(limit) {
170670
172111
  if (typeof limit !== "number" || !Number.isFinite(limit)) {
170671
172112
  return DEFAULT_CTX_SEARCH_LIMIT;
170672
172113
  }
170673
172114
  return Math.max(1, Math.floor(limit));
170674
172115
  }
172116
+ function normalizeSources(sources) {
172117
+ if (!sources || sources.length === 0)
172118
+ return;
172119
+ const result = [];
172120
+ const seen = new Set;
172121
+ for (const source of sources) {
172122
+ if (VALID_SOURCES.has(source)) {
172123
+ const typed = source;
172124
+ if (!seen.has(typed)) {
172125
+ seen.add(typed);
172126
+ result.push(typed);
172127
+ }
172128
+ }
172129
+ }
172130
+ return result.length > 0 ? result : undefined;
172131
+ }
172132
+ function formatAge2(committedAtMs) {
172133
+ const ageMs = Date.now() - committedAtMs;
172134
+ if (ageMs < 0)
172135
+ return "future";
172136
+ const days = Math.floor(ageMs / (24 * 60 * 60 * 1000));
172137
+ if (days <= 0)
172138
+ return "today";
172139
+ if (days === 1)
172140
+ return "1d ago";
172141
+ if (days < 30)
172142
+ return `${days}d ago`;
172143
+ const months = Math.floor(days / 30);
172144
+ if (months === 1)
172145
+ return "1mo ago";
172146
+ if (months < 12)
172147
+ return `${months}mo ago`;
172148
+ const years = Math.floor(days / 365);
172149
+ return years === 1 ? "1y ago" : `${years}y ago`;
172150
+ }
170675
172151
  function formatResult(result, index) {
170676
172152
  if (result.source === "memory") {
170677
172153
  return [
@@ -170680,9 +172156,9 @@ function formatResult(result, index) {
170680
172156
  ].join(`
170681
172157
  `);
170682
172158
  }
170683
- if (result.source === "fact") {
172159
+ if (result.source === "git_commit") {
170684
172160
  return [
170685
- `[${index}] [fact] score=${result.score.toFixed(2)} category=${result.factCategory} id=${result.factId}`,
172161
+ `[${index}] [git_commit] score=${result.score.toFixed(2)} sha=${result.shortSha} ${formatAge2(result.committedAtMs)} match=${result.matchType}`,
170686
172162
  result.content
170687
172163
  ].join(`
170688
172164
  `);
@@ -170698,7 +172174,7 @@ function formatResult(result, index) {
170698
172174
  }
170699
172175
  function formatSearchResults(query, results) {
170700
172176
  if (results.length === 0) {
170701
- return `No results found for "${query}" across memories, session facts, or message history.`;
172177
+ return `No results found for "${query}" across memories, git commits, or message history.`;
170702
172178
  }
170703
172179
  const body = results.map((result, index) => formatResult(result, index + 1)).join(`
170704
172180
 
@@ -170711,8 +172187,9 @@ function createCtxSearchTool(deps) {
170711
172187
  return tool5({
170712
172188
  description: CTX_SEARCH_DESCRIPTION,
170713
172189
  args: {
170714
- query: tool5.schema.string().describe("Search query across memories, facts, and conversation history."),
170715
- limit: tool5.schema.number().optional().describe("Maximum results to return (default: 10)")
172190
+ query: tool5.schema.string().describe("Search query. Matches against memory content, git commit messages, and raw user/assistant message text."),
172191
+ limit: tool5.schema.number().optional().describe("Maximum results to return (default: 10)"),
172192
+ sources: tool5.schema.array(tool5.schema.enum(["memory", "message", "git_commit"])).optional().describe('Optional. Restrict to specific sources. Examples: ["git_commit"] for "when did we change X", ["memory"] for naming conventions, ["message"] for "did we discuss this earlier", ["git_commit","message"] for regression hunts. Omit for a broad search across all enabled sources.')
170716
172193
  },
170717
172194
  async execute(args, toolContext) {
170718
172195
  const query = args.query?.trim();
@@ -170720,12 +172197,16 @@ function createCtxSearchTool(deps) {
170720
172197
  return "Error: 'query' is required.";
170721
172198
  }
170722
172199
  const lastCompartmentEnd = getLastCompartmentEndMessage(deps.db, toolContext.sessionID);
172200
+ const visibleMemoryIds = getVisibleMemoryIds(deps.db, toolContext.sessionID);
170723
172201
  const results = await unifiedSearch(deps.db, toolContext.sessionID, deps.projectPath, query, {
170724
172202
  limit: normalizeLimit3(args.limit),
170725
172203
  memoryEnabled: deps.memoryEnabled,
170726
172204
  embeddingEnabled: deps.embeddingEnabled,
170727
172205
  readMessages: deps.readMessages,
170728
- maxMessageOrdinal: lastCompartmentEnd >= 0 ? lastCompartmentEnd : undefined
172206
+ maxMessageOrdinal: lastCompartmentEnd >= 0 ? lastCompartmentEnd : undefined,
172207
+ gitCommitsEnabled: deps.gitCommitsEnabled ?? false,
172208
+ sources: normalizeSources(args.sources),
172209
+ visibleMemoryIds
170729
172210
  });
170730
172211
  return formatSearchResults(query, results);
170731
172212
  }
@@ -170807,7 +172288,8 @@ function createToolRegistry(args) {
170807
172288
  db,
170808
172289
  projectPath,
170809
172290
  memoryEnabled,
170810
- embeddingEnabled: embeddingConfig2.provider !== "off"
172291
+ embeddingEnabled: embeddingConfig2.provider !== "off",
172292
+ gitCommitsEnabled: pluginConfig.experimental?.git_commit_indexing?.enabled === true
170811
172293
  }),
170812
172294
  ...memoryEnabled ? {
170813
172295
  ...createCtxMemoryTools({
@@ -170972,8 +172454,10 @@ var plugin = async (ctx) => {
170972
172454
  setTimeout(async () => {
170973
172455
  try {
170974
172456
  const { sendIgnoredMessage: sendIgnoredMessage2 } = await Promise.resolve().then(() => (init_send_session_notification(), exports_send_session_notification));
170975
- const sessions = await Promise.resolve(ctx.client.session?.list?.()).catch(() => null);
170976
- const sessionId = sessions?.data?.[0]?.id ?? sessions?.[0]?.id;
172457
+ const clientWithSessions = ctx.client;
172458
+ const sessions = await Promise.resolve(clientWithSessions.session?.list?.()).catch(() => null);
172459
+ const sessionList = Array.isArray(sessions) ? sessions : sessions?.data;
172460
+ const sessionId = sessionList?.[0]?.id;
170977
172461
  if (sessionId) {
170978
172462
  await sendIgnoredMessage2(ctx.client, sessionId, warningText, {});
170979
172463
  }
@@ -171007,14 +172491,19 @@ var plugin = async (ctx) => {
171007
172491
  dreamerConfig: pluginConfig.dreamer,
171008
172492
  embeddingConfig: pluginConfig.embedding,
171009
172493
  memoryEnabled: pluginConfig.memory?.enabled === true,
171010
- experimentalUserMemories: pluginConfig.experimental?.user_memories?.enabled ? {
172494
+ experimentalUserMemories: pluginConfig.dreamer?.user_memories?.enabled ? {
172495
+ enabled: true,
172496
+ promotionThreshold: pluginConfig.dreamer.user_memories.promotion_threshold
172497
+ } : undefined,
172498
+ experimentalPinKeyFiles: pluginConfig.dreamer?.pin_key_files?.enabled ? {
171011
172499
  enabled: true,
171012
- promotionThreshold: pluginConfig.experimental.user_memories?.promotion_threshold
172500
+ token_budget: pluginConfig.dreamer.pin_key_files.token_budget,
172501
+ min_reads: pluginConfig.dreamer.pin_key_files.min_reads
171013
172502
  } : undefined,
171014
- experimentalPinKeyFiles: pluginConfig.experimental?.pin_key_files?.enabled ? {
172503
+ gitCommitIndexing: pluginConfig.experimental?.git_commit_indexing?.enabled ? {
171015
172504
  enabled: true,
171016
- token_budget: pluginConfig.experimental.pin_key_files?.token_budget,
171017
- min_reads: pluginConfig.experimental.pin_key_files?.min_reads
172505
+ since_days: pluginConfig.experimental.git_commit_indexing.since_days,
172506
+ max_commits: pluginConfig.experimental.git_commit_indexing.max_commits
171018
172507
  } : undefined
171019
172508
  });
171020
172509
  const storageDir = `${getOpenCodeStorageDir()}/plugin/magic-context`;
@@ -171114,7 +172603,7 @@ var plugin = async (ctx) => {
171114
172603
  config2.agent = {
171115
172604
  ...config2.agent ?? {},
171116
172605
  [DREAMER_AGENT]: buildHiddenAgentConfig(DREAMER_AGENT, DREAMER_SYSTEM_PROMPT, dreamerAgentOverrides),
171117
- [HISTORIAN_AGENT]: buildHiddenAgentConfig(HISTORIAN_AGENT, pluginConfig.experimental?.user_memories?.enabled ? COMPARTMENT_AGENT_SYSTEM_PROMPT + USER_OBSERVATIONS_APPENDIX : COMPARTMENT_AGENT_SYSTEM_PROMPT, historianAgentOverrides),
172606
+ [HISTORIAN_AGENT]: buildHiddenAgentConfig(HISTORIAN_AGENT, pluginConfig.dreamer?.user_memories?.enabled ? COMPARTMENT_AGENT_SYSTEM_PROMPT + USER_OBSERVATIONS_APPENDIX : COMPARTMENT_AGENT_SYSTEM_PROMPT, historianAgentOverrides),
171118
172607
  [HISTORIAN_EDITOR_AGENT]: buildHiddenAgentConfig(HISTORIAN_EDITOR_AGENT, HISTORIAN_EDITOR_SYSTEM_PROMPT, historianAgentOverrides),
171119
172608
  [SIDEKICK_AGENT]: buildHiddenAgentConfig(SIDEKICK_AGENT, SIDEKICK_SYSTEM_PROMPT, sidekickAgentOverrides)
171120
172609
  };