@cortexkit/pi-magic-context 0.24.1 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8275,6 +8275,14 @@ function buildNodeSqliteDatabaseClass(DatabaseSync) {
8275
8275
  }
8276
8276
  super(typeof filename === "string" ? filename : ":memory:", translated);
8277
8277
  }
8278
+ prepare(sql) {
8279
+ const stmt = super.prepare(sql);
8280
+ for (const method of ["run", "get", "all"]) {
8281
+ const original = stmt[method].bind(stmt);
8282
+ stmt[method] = (...args) => args.length === 1 && Array.isArray(args[0]) ? original(...args[0]) : original(...args);
8283
+ }
8284
+ return stmt;
8285
+ }
8278
8286
  transaction(fn) {
8279
8287
  const self = this;
8280
8288
  const wrapped = function(...args) {
@@ -141059,6 +141067,8 @@ function readRawSessionTailFromDb(db, sessionId, baseOrdinal, anchorMessageId) {
141059
141067
  var encoder = new TextEncoder;
141060
141068
  var TAG_PREFIX_REGEX = /^(?:§\d+§\s*)+/;
141061
141069
  var MALFORMED_TAG_PREFIX_REGEX = /^(?:§\d+">§(?:\d+§)?\s*)+/;
141070
+ var DANGLING_TAG_GLOBAL_REGEX = /\u00a7\d+(?!\.\d)[^\s\u00a7\w.]?/g;
141071
+ var DANGLING_TAG_PREFIX_REGEX = /^(?:\u00a7\d+(?!\.\d)[^\s\u00a7\w.]?\s*)+/;
141062
141072
  var COMPLETE_TAG_PAIR_GLOBAL_REGEX = /\u00a7\d+\u00a7/g;
141063
141073
  var MALFORMED_TAG_GLOBAL_REGEX = /\u00a7\d+">(?:\u00a7(?:\d+\u00a7)?)?/g;
141064
141074
  var STRAY_SECTION_CHAR_REGEX = /\u00a7/g;
@@ -141071,6 +141081,9 @@ function stripCompleteTagPairsGlobally(value) {
141071
141081
  function stripMalformedTagNotationGlobally(value) {
141072
141082
  return value.replace(MALFORMED_TAG_GLOBAL_REGEX, "");
141073
141083
  }
141084
+ function stripDanglingTagNotationGlobally(value) {
141085
+ return value.replace(DANGLING_TAG_GLOBAL_REGEX, "");
141086
+ }
141074
141087
  function stripTagSectionCharacters(value) {
141075
141088
  return value.replace(STRAY_SECTION_CHAR_REGEX, "");
141076
141089
  }
@@ -141078,6 +141091,7 @@ function stripPersistedAssistantText(value) {
141078
141091
  let text = stripWellFormedLeadingTagPrefix(value);
141079
141092
  text = stripCompleteTagPairsGlobally(text);
141080
141093
  text = stripMalformedTagNotationGlobally(text);
141094
+ text = stripDanglingTagNotationGlobally(text);
141081
141095
  text = stripTagSectionCharacters(text);
141082
141096
  return text.trim();
141083
141097
  }
@@ -141090,6 +141104,7 @@ function stripTagPrefix(value) {
141090
141104
  const prev = stripped;
141091
141105
  stripped = stripped.replace(MALFORMED_TAG_PREFIX_REGEX, "");
141092
141106
  stripped = stripped.replace(TAG_PREFIX_REGEX, "");
141107
+ stripped = stripped.replace(DANGLING_TAG_PREFIX_REGEX, "");
141093
141108
  if (stripped === prev)
141094
141109
  break;
141095
141110
  }
@@ -141957,6 +141972,7 @@ var SESSION_META_SELECT_COLUMNS = [
141957
141972
  "conversation_tokens",
141958
141973
  "tool_call_tokens",
141959
141974
  "cleared_reasoning_through_tag",
141975
+ "tool_reclaim_watermark",
141960
141976
  "last_todo_state",
141961
141977
  "cached_m0_bytes",
141962
141978
  "cached_m1_bytes",
@@ -142005,6 +142021,7 @@ var META_COLUMNS = {
142005
142021
  conversationTokens: "conversation_tokens",
142006
142022
  toolCallTokens: "tool_call_tokens",
142007
142023
  clearedReasoningThroughTag: "cleared_reasoning_through_tag",
142024
+ toolReclaimWatermark: "tool_reclaim_watermark",
142008
142025
  lastTodoState: "last_todo_state",
142009
142026
  cachedM0Bytes: "cached_m0_bytes",
142010
142027
  cachedM1Bytes: "cached_m1_bytes",
@@ -142073,7 +142090,7 @@ function isSessionMetaRow(row) {
142073
142090
  if (row === null || typeof row !== "object")
142074
142091
  return false;
142075
142092
  const r = row;
142076
- return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.observed_safe_input_tokens) && isNumberOrNull(r.cache_alert_sent) && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state) && isBlobOrNull(r.cached_m0_bytes) && isBlobOrNull(r.cached_m1_bytes) && isNumberOrNull(r.cached_m0_project_memory_epoch) && isStringOrNull(r.cached_m0_workspace_fingerprint) && isNumberOrNull(r.cached_m0_project_user_profile_version) && isNumberOrNull(r.cached_m0_max_compartment_seq) && isNumberOrNull(r.cached_m0_max_memory_id) && isNumberOrNull(r.cached_m0_max_mutation_id) && isNumberOrNull(r.cached_m0_max_memory_mutation_id) && isStringOrNull(r.cached_m0_project_docs_hash) && isNumberOrNull(r.cached_m0_materialized_at) && isNumberOrNull(r.cached_m0_session_facts_version) && isStringOrNull(r.cached_m0_upgrade_state) && isStringOrNull(r.cached_m0_system_hash) && isStringOrNull(r.cached_m0_tool_set_hash) && isStringOrNull(r.cached_m0_model_key) && isStringOrNull(r.last_observed_model_key) && isNumberOrNull(r.last_usage_context_limit) && isNumberOrNull(r.prior_boundary_ordinal) && isNumberOrNull(r.protected_tail_policy_version) && isNumberOrNull(r.protected_tail_drain_window_started_at) && isNumberOrNull(r.protected_tail_drain_tokens) && isNumberOrNull(r.recovery_no_eligible_head_count) && isNumberOrNull(r.force_emergency_bypass_window_start) && isNumberOrNull(r.force_emergency_bypass_used) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme);
142093
+ return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.observed_safe_input_tokens) && isNumberOrNull(r.cache_alert_sent) && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state) && isBlobOrNull(r.cached_m0_bytes) && isBlobOrNull(r.cached_m1_bytes) && isNumberOrNull(r.cached_m0_project_memory_epoch) && isStringOrNull(r.cached_m0_workspace_fingerprint) && isNumberOrNull(r.cached_m0_project_user_profile_version) && isNumberOrNull(r.cached_m0_max_compartment_seq) && isNumberOrNull(r.cached_m0_max_memory_id) && isNumberOrNull(r.cached_m0_max_mutation_id) && isNumberOrNull(r.cached_m0_max_memory_mutation_id) && isStringOrNull(r.cached_m0_project_docs_hash) && isNumberOrNull(r.cached_m0_materialized_at) && isNumberOrNull(r.cached_m0_session_facts_version) && isStringOrNull(r.cached_m0_upgrade_state) && isStringOrNull(r.cached_m0_system_hash) && isStringOrNull(r.cached_m0_tool_set_hash) && isStringOrNull(r.cached_m0_model_key) && isStringOrNull(r.last_observed_model_key) && isNumberOrNull(r.last_usage_context_limit) && isNumberOrNull(r.prior_boundary_ordinal) && isNumberOrNull(r.protected_tail_policy_version) && isNumberOrNull(r.protected_tail_drain_window_started_at) && isNumberOrNull(r.protected_tail_drain_tokens) && isNumberOrNull(r.recovery_no_eligible_head_count) && isNumberOrNull(r.force_emergency_bypass_window_start) && isNumberOrNull(r.force_emergency_bypass_used) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme) && isNumberOrNull(r.tool_reclaim_watermark);
142077
142094
  }
142078
142095
  function getDefaultSessionMeta(sessionId) {
142079
142096
  return {
@@ -142096,6 +142113,7 @@ function getDefaultSessionMeta(sessionId) {
142096
142113
  conversationTokens: 0,
142097
142114
  toolCallTokens: 0,
142098
142115
  clearedReasoningThroughTag: 0,
142116
+ toolReclaimWatermark: 0,
142099
142117
  lastTodoState: "",
142100
142118
  cachedM0Bytes: null,
142101
142119
  cachedM1Bytes: null,
@@ -142159,6 +142177,7 @@ function toSessionMeta(row) {
142159
142177
  conversationTokens: numOrZero(row.conversation_tokens),
142160
142178
  toolCallTokens: numOrZero(row.tool_call_tokens),
142161
142179
  clearedReasoningThroughTag: numOrZero(row.cleared_reasoning_through_tag),
142180
+ toolReclaimWatermark: numOrZero(row.tool_reclaim_watermark),
142162
142181
  lastTodoState: lastTodoStateRaw,
142163
142182
  cachedM0Bytes: toBufferOrNull(row.cached_m0_bytes),
142164
142183
  cachedM1Bytes: toBufferOrNull(row.cached_m1_bytes),
@@ -143598,6 +143617,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
143598
143617
  ensureColumn(db, "session_meta", "historian_last_failure_at", "INTEGER DEFAULT NULL");
143599
143618
  ensureColumn(db, "session_meta", "system_prompt_hash", "TEXT DEFAULT ''");
143600
143619
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
143620
+ ensureColumn(db, "session_meta", "tool_reclaim_watermark", "INTEGER DEFAULT 0");
143601
143621
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
143602
143622
  ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
143603
143623
  ensureColumn(db, "session_meta", "processed_image_stripped_ids", "TEXT DEFAULT ''");
@@ -146217,6 +146237,7 @@ var SESSION_META_FALLBACK_SELECTS = {
146217
146237
  last_transform_error: "'' AS last_transform_error",
146218
146238
  system_prompt_hash: "'' AS system_prompt_hash",
146219
146239
  last_todo_state: "'' AS last_todo_state",
146240
+ tool_reclaim_watermark: "0 AS tool_reclaim_watermark",
146220
146241
  cached_m0_bytes: "NULL AS cached_m0_bytes",
146221
146242
  cached_m1_bytes: "NULL AS cached_m1_bytes",
146222
146243
  cached_m0_project_memory_epoch: "NULL AS cached_m0_project_memory_epoch",
@@ -146294,6 +146315,14 @@ function updateSessionMeta(db, sessionId, updates) {
146294
146315
  db.prepare(`UPDATE session_meta SET ${setClauses.join(", ")} WHERE session_id = ?`).run(...values, sessionId);
146295
146316
  })();
146296
146317
  }
146318
+ function advanceToolReclaimWatermark(db, sessionId, maxTagNumber) {
146319
+ if (maxTagNumber <= 0)
146320
+ return;
146321
+ db.transaction(() => {
146322
+ ensureSessionMetaRow(db, sessionId);
146323
+ db.prepare("UPDATE session_meta SET tool_reclaim_watermark = MAX(COALESCE(tool_reclaim_watermark, 0), ?) WHERE session_id = ?").run(maxTagNumber, sessionId);
146324
+ })();
146325
+ }
146297
146326
  // ../plugin/src/features/magic-context/storage-notes.ts
146298
146327
  var NOTE_TYPES = new Set(["session", "smart"]);
146299
146328
  var NOTE_STATUSES = new Set(["active", "pending", "ready", "dismissed"]);
@@ -146691,6 +146720,26 @@ function getActiveTagTokenAggregate(db, sessionId, protectedTags = 0) {
146691
146720
  nullCount: row?.null_count ?? 0
146692
146721
  };
146693
146722
  }
146723
+ function getOldestActiveUnprotectedToolTags(db, sessionId, protectedTags = 0, limit = 4) {
146724
+ if (limit <= 0)
146725
+ return [];
146726
+ const boundedLimit = Math.max(1, Math.min(10, Math.floor(limit)));
146727
+ const whereProtected = protectedTags > 0 ? `AND tag_number < (
146728
+ SELECT tag_number FROM tags
146729
+ WHERE session_id = ? AND status = 'active'
146730
+ ORDER BY tag_number DESC LIMIT 1 OFFSET ?
146731
+ )` : "";
146732
+ const params = protectedTags > 0 ? [sessionId, sessionId, protectedTags - 1, boundedLimit] : [sessionId, boundedLimit];
146733
+ const rows = db.prepare(`SELECT tag_number, tool_name
146734
+ FROM tags
146735
+ WHERE session_id = ? AND status = 'active' AND type = 'tool' ${whereProtected}
146736
+ ORDER BY tag_number ASC, id ASC
146737
+ LIMIT ?`).all(...params);
146738
+ return rows.filter((row) => typeof row.tag_number === "number").map((row) => ({
146739
+ tagNumber: row.tag_number,
146740
+ toolName: typeof row.tool_name === "string" ? row.tool_name : null
146741
+ }));
146742
+ }
146694
146743
  function getTriggerTagTokenUpperBound(db, sessionId) {
146695
146744
  const row = db.prepare(`SELECT
146696
146745
  COALESCE(SUM(COALESCE(token_count, 0) + COALESCE(input_token_count, 0) + COALESCE(reasoning_token_count, 0)), 0) AS bound,
@@ -147743,13 +147792,13 @@ async function maybeSendUpgradeReminder(deps, sessionId) {
147743
147792
  init_data_path();
147744
147793
  import * as fs2 from "node:fs";
147745
147794
  import * as path6 from "node:path";
147746
- var ANNOUNCEMENT_VERSION = "0.24.0";
147795
+ var ANNOUNCEMENT_VERSION = "0.25.0";
147747
147796
  var ANNOUNCEMENT_FEATURES = [
147748
- "Searchable session history: ctx_search can now find older discussion by meaning, not just keywords. New history is embedded automatically — to backfill an EXISTING session's older history, run /ctx-embed-history once (it works in the background).",
147749
- "Cross-project workspaces: group related repos and share project memories across them, with per-category control over what's shared. Set them up in the dashboard's Workspaces panel.",
147750
- "Pi: fixed sessions overflowing the model context while still showing moderate usage — Pi now sheds context before a tool-heavy turn overflows.",
147751
- "Fewer prompt-cache busts: doc edits, processed screenshots, and a rebuild-then-bust-again case no longer re-bill large prompt prefixes.",
147752
- "Setup wizard now lists your actual models with type-ahead instead of fixed recommendations, and explains the historian/dreamer roles (issue #144). Plus a GitHub Copilot tool-pairing fix (#135)."
147797
+ "Old tool output is now reclaimed automatically: once a file read / search / command output has gone a full execute cycle unused, it's dropped on the next one — no need to call ctx_reduce for stale results.",
147798
+ "Recover anything that was dropped: ctx_expand({ message: N }) returns a dropped message's full content (every tool call's input + output) from storage. ctx_expand({ start, end, verbose: true }) lists a range message-by-message to find it.",
147799
+ "Searchable history made reliable: /ctx-embed shows embedding coverage and runs a resilient backfill (retries transient failures, no longer bails on the first hiccup); the active session now auto-embeds in the background. ctx_reduce guidance also reframed as deferred + recoverable so models trim spent output earlier.",
147800
+ "Pi: fixed /ctx-dream (was failing with 'Unknown named parameter') and local-embedding load failures on Windows/Desktop (#151, #128).",
147801
+ "Runaway background agents on weak/local models are now capped and force-stopped (#154, #152). Plus several prompt-cache busts removed."
147753
147802
  ];
147754
147803
  var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU";
147755
147804
  var STATE_FILENAME = "last_announced_version";
@@ -148693,7 +148742,7 @@ function enqueueDream(db, projectIdentity, reason, force = false) {
148693
148742
  return db.transaction(() => {
148694
148743
  if (!hasActiveDreamLease(db)) {
148695
148744
  const staleThresholdMs = force ? 2 * 60 * 1000 : 120 * 60 * 1000;
148696
- db.prepare("DELETE FROM dream_queue WHERE project_path = ? AND started_at IS NOT NULL AND started_at < ?").run([projectIdentity, now - staleThresholdMs]);
148745
+ db.prepare("DELETE FROM dream_queue WHERE project_path = ? AND started_at IS NOT NULL AND started_at < ?").run(projectIdentity, now - staleThresholdMs);
148697
148746
  }
148698
148747
  const existing = db.prepare("SELECT id FROM dream_queue WHERE project_path = ?").get(projectIdentity);
148699
148748
  if (existing) {
@@ -148828,9 +148877,11 @@ async function promptWithTimeout(client, args, timeoutMs, signal) {
148828
148877
  });
148829
148878
  } catch (error) {
148830
148879
  if (signal?.aborted) {
148880
+ await abortChildRun(client, args.path.id);
148831
148881
  throw new Error("prompt aborted by external signal");
148832
148882
  }
148833
148883
  if (controller.signal.aborted) {
148884
+ await abortChildRun(client, args.path.id);
148834
148885
  throw new Error(`prompt timed out after ${timeoutMs}ms`);
148835
148886
  }
148836
148887
  throw error;
@@ -148839,6 +148890,13 @@ async function promptWithTimeout(client, args, timeoutMs, signal) {
148839
148890
  signal?.removeEventListener("abort", onExternalAbort);
148840
148891
  }
148841
148892
  }
148893
+ async function abortChildRun(client, sessionId) {
148894
+ try {
148895
+ await client.session.abort({ path: { id: sessionId } });
148896
+ } catch (error) {
148897
+ log(`[model-retry] child session abort failed for ${sessionId}: ${String(error)}`);
148898
+ }
148899
+ }
148842
148900
  function isNonRetryable(error, externalSignal) {
148843
148901
  if (externalSignal?.aborted)
148844
148902
  return true;
@@ -149695,6 +149753,20 @@ function getDistinctStoredModelIds(db, projectPath) {
149695
149753
  const rows = getDistinctStoredModelIdsStatement(db).all(projectPath);
149696
149754
  return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
149697
149755
  }
149756
+ function getMemoryEmbedCoverage(db, projectPath, modelId) {
149757
+ const row = db.prepare(`SELECT
149758
+ COUNT(*) AS total,
149759
+ SUM(CASE WHEN EXISTS (
149760
+ SELECT 1 FROM memory_embeddings e
149761
+ WHERE e.memory_id = m.id AND e.model_id = ?
149762
+ ) THEN 1 ELSE 0 END) AS embedded
149763
+ FROM memories m
149764
+ WHERE m.project_path = ? AND m.status = 'active'`).get(modelId, projectPath);
149765
+ return {
149766
+ total: typeof row?.total === "number" ? row.total : 0,
149767
+ embedded: typeof row?.embedded === "number" ? row.embedded : 0
149768
+ };
149769
+ }
149698
149770
 
149699
149771
  // ../plugin/src/features/magic-context/memory/embedding-cache.ts
149700
149772
  var DEFAULT_EMBEDDING_CACHE_TTL_MS = 60000;
@@ -166733,6 +166805,28 @@ function countUnembeddedSessionCompartments(db, projectPath, sessionId, modelId)
166733
166805
  )`).get(projectPath, sessionId, projectPath, modelId);
166734
166806
  return typeof row?.n === "number" ? row.n : 0;
166735
166807
  }
166808
+ function countSessionCompartmentEmbedCoverage(db, projectPath, sessionId, modelId) {
166809
+ const row = db.prepare(`SELECT
166810
+ COUNT(*) AS total,
166811
+ SUM(CASE WHEN EXISTS (
166812
+ SELECT 1 FROM compartment_chunk_embeddings e
166813
+ WHERE e.compartment_id = c.id
166814
+ AND e.project_path = ?
166815
+ AND e.model_id = ?
166816
+ ) THEN 1 ELSE 0 END) AS embedded
166817
+ FROM compartments c
166818
+ JOIN session_projects sp
166819
+ ON sp.session_id = c.session_id
166820
+ AND sp.harness = c.harness
166821
+ AND sp.project_path = ?
166822
+ WHERE c.session_id = ?
166823
+ AND c.start_message IS NOT NULL
166824
+ AND c.end_message IS NOT NULL`).get(projectPath, modelId, projectPath, sessionId);
166825
+ return {
166826
+ total: typeof row?.total === "number" ? row.total : 0,
166827
+ embedded: typeof row?.embedded === "number" ? row.embedded : 0
166828
+ };
166829
+ }
166736
166830
 
166737
166831
  // ../plugin/src/features/magic-context/memory/cosine-similarity.ts
166738
166832
  function cosineSimilarity(a, b) {
@@ -166875,6 +166969,19 @@ async function withQuietConsole(fn) {
166875
166969
  console.error = origError;
166876
166970
  }
166877
166971
  }
166972
+ var nativeRuntimeMissing = false;
166973
+ function isNativeRuntimeMissingError(error51) {
166974
+ const message = error51 instanceof Error ? error51.message : String(error51 ?? "");
166975
+ const lower = message.toLowerCase();
166976
+ const code = error51?.code;
166977
+ const name2 = error51?.name;
166978
+ if (code === "ERR_DLOPEN_FAILED" && lower.includes("onnxruntime")) {
166979
+ return true;
166980
+ }
166981
+ if (!lower.includes("onnxruntime-node"))
166982
+ return false;
166983
+ return code === "ERR_MODULE_NOT_FOUND" || name2 === "ResolveMessage" || lower.includes("cannot find package") || lower.includes("cannot find module") || lower.includes("err_module_not_found");
166984
+ }
166878
166985
  function isTransientLoadError(error51) {
166879
166986
  const message = error51 instanceof Error ? error51.message : String(error51 ?? "");
166880
166987
  if (!message)
@@ -166939,6 +167046,9 @@ class LocalEmbeddingProvider {
166939
167046
  if (this.pipeline) {
166940
167047
  return true;
166941
167048
  }
167049
+ if (nativeRuntimeMissing) {
167050
+ return false;
167051
+ }
166942
167052
  if (this.initPromise) {
166943
167053
  await this.initPromise;
166944
167054
  return this.pipeline !== null;
@@ -167005,7 +167115,12 @@ class LocalEmbeddingProvider {
167005
167115
  await releaseLock();
167006
167116
  }
167007
167117
  } catch (error51) {
167008
- log("[magic-context] embedding model failed to load:", error51);
167118
+ if (isNativeRuntimeMissingError(error51)) {
167119
+ nativeRuntimeMissing = true;
167120
+ log("[magic-context] local embedding runtime is not installed (onnxruntime-node missing from this install). Local embeddings are disabled. Fix: reinstall the plugin (run `npx @cortexkit/magic-context@latest doctor --force`), or configure an `openai-compatible`/`ollama` embedding endpoint instead. Existing memories are unaffected.");
167121
+ } else {
167122
+ log("[magic-context] embedding model failed to load:", error51);
167123
+ }
167009
167124
  this.pipeline = null;
167010
167125
  } finally {
167011
167126
  this.initPromise = null;
@@ -167519,6 +167634,118 @@ function getDistinctCommitEmbeddingModelIds(db, projectPath) {
167519
167634
  return new Set(rows.map((row) => typeof row.modelId === "string" ? row.modelId : null));
167520
167635
  }
167521
167636
 
167637
+ // ../plugin/src/features/magic-context/git-commits/storage-git-commits.ts
167638
+ init_logger();
167639
+ var insertStatements = new WeakMap;
167640
+ var existingShasStatements = new WeakMap;
167641
+ var projectCountStatements = new WeakMap;
167642
+ var evictStatements = new WeakMap;
167643
+ var evictOverflowStatements = new WeakMap;
167644
+ var latestCommitTimeStatements = new WeakMap;
167645
+ function getInsertStatement(db) {
167646
+ let stmt = insertStatements.get(db);
167647
+ if (!stmt) {
167648
+ stmt = db.prepare(`INSERT INTO git_commits (sha, project_path, short_sha, message, author, committed_at, indexed_at)
167649
+ VALUES (?, ?, ?, ?, ?, ?, ?)
167650
+ ON CONFLICT(sha) DO UPDATE SET
167651
+ project_path = excluded.project_path,
167652
+ short_sha = excluded.short_sha,
167653
+ message = excluded.message,
167654
+ author = excluded.author,
167655
+ committed_at = excluded.committed_at,
167656
+ indexed_at = excluded.indexed_at
167657
+ WHERE git_commits.message != excluded.message`);
167658
+ insertStatements.set(db, stmt);
167659
+ }
167660
+ return stmt;
167661
+ }
167662
+ function getExistingShasStatement(db) {
167663
+ let stmt = existingShasStatements.get(db);
167664
+ if (!stmt) {
167665
+ stmt = db.prepare("SELECT sha FROM git_commits WHERE project_path = ?");
167666
+ existingShasStatements.set(db, stmt);
167667
+ }
167668
+ return stmt;
167669
+ }
167670
+ function getProjectCountStatement(db) {
167671
+ let stmt = projectCountStatements.get(db);
167672
+ if (!stmt) {
167673
+ stmt = db.prepare("SELECT COUNT(*) AS count FROM git_commits WHERE project_path = ?");
167674
+ projectCountStatements.set(db, stmt);
167675
+ }
167676
+ return stmt;
167677
+ }
167678
+ function getLatestCommitTimeStatement(db) {
167679
+ let stmt = latestCommitTimeStatements.get(db);
167680
+ if (!stmt) {
167681
+ stmt = db.prepare("SELECT MAX(committed_at) AS latest FROM git_commits WHERE project_path = ?");
167682
+ latestCommitTimeStatements.set(db, stmt);
167683
+ }
167684
+ return stmt;
167685
+ }
167686
+ function getEvictOverflowStatement(db) {
167687
+ let stmt = evictOverflowStatements.get(db);
167688
+ if (!stmt) {
167689
+ stmt = db.prepare(`DELETE FROM git_commits
167690
+ WHERE rowid IN (
167691
+ SELECT rowid FROM git_commits
167692
+ WHERE project_path = ?
167693
+ ORDER BY committed_at DESC, sha DESC
167694
+ LIMIT -1 OFFSET ?
167695
+ )`);
167696
+ evictOverflowStatements.set(db, stmt);
167697
+ }
167698
+ return stmt;
167699
+ }
167700
+ function upsertCommits(db, projectPath, commits) {
167701
+ if (commits.length === 0)
167702
+ return { inserted: 0, updated: 0 };
167703
+ const existing = new Set;
167704
+ for (const row of getExistingShasStatement(db).all(projectPath)) {
167705
+ existing.add(row.sha);
167706
+ }
167707
+ let inserted = 0;
167708
+ let updated = 0;
167709
+ const now = Date.now();
167710
+ const insertStmt = getInsertStatement(db);
167711
+ db.transaction(() => {
167712
+ for (const commit of commits) {
167713
+ const result = insertStmt.run(commit.sha, projectPath, commit.shortSha, commit.message, commit.author, commit.committedAtMs, now);
167714
+ if (result.changes > 0) {
167715
+ if (existing.has(commit.sha)) {
167716
+ updated++;
167717
+ } else {
167718
+ inserted++;
167719
+ existing.add(commit.sha);
167720
+ }
167721
+ }
167722
+ }
167723
+ })();
167724
+ return { inserted, updated };
167725
+ }
167726
+ function getCommitCount(db, projectPath) {
167727
+ const row = getProjectCountStatement(db).get(projectPath);
167728
+ return row?.count ?? 0;
167729
+ }
167730
+ function getLatestIndexedCommitTimeMs(db, projectPath) {
167731
+ const row = getLatestCommitTimeStatement(db).get(projectPath);
167732
+ return row?.latest ?? null;
167733
+ }
167734
+ function enforceProjectCap(db, projectPath, maxCommits) {
167735
+ if (maxCommits <= 0)
167736
+ return 0;
167737
+ const count = getCommitCount(db, projectPath);
167738
+ if (count <= maxCommits)
167739
+ return 0;
167740
+ getEvictOverflowStatement(db).run(projectPath, maxCommits);
167741
+ const after = getCommitCount(db, projectPath);
167742
+ const evicted = Math.max(0, count - after);
167743
+ if (evicted > 0) {
167744
+ log(`[git-commits] evicted ${evicted} oldest commits for project ${projectPath} (cap=${maxCommits}, was=${count})`);
167745
+ }
167746
+ return evicted;
167747
+ }
167748
+
167522
167749
  // ../plugin/src/features/magic-context/git-commits/sweep-coordinator.ts
167523
167750
  var GIT_SWEEP_COOLDOWN_MS = 10 * 60 * 1000;
167524
167751
  var GIT_SWEEP_LEASE_TTL_MS = 5 * 60 * 1000;
@@ -167713,8 +167940,12 @@ function repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectPath) {
167713
167940
  var OFF_PROVIDER_IDENTITY = "embedding-provider:off";
167714
167941
  var SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
167715
167942
  var CHUNK_DRAIN_BATCH_SIZE = 8;
167716
- var MAX_WINDOWS_PER_EMBED_CALL = 16;
167943
+ var MAX_WINDOWS_PER_EMBED_CALL = 2;
167717
167944
  var SESSION_EMBED_LEASE_RENEWAL_MS = 60 * 1000;
167945
+ var EMBED_SLICE_RETRY_ATTEMPTS = 3;
167946
+ var EMBED_SLICE_RETRY_BASE_MS = 250;
167947
+ var EMBED_SLOW_FAILURE_NO_RETRY_MS = 1e4;
167948
+ var MAX_CONSECUTIVE_FAILED_BATCHES = 3;
167718
167949
  var projectRegistrations = new Map;
167719
167950
  var loadUnembeddedMemoriesStatements = new WeakMap;
167720
167951
  var globalRegistrationGeneration = 0;
@@ -167814,7 +168045,9 @@ function snapshotFor(registration) {
167814
168045
  enabled,
167815
168046
  gitCommitEnabled,
167816
168047
  modelId: registration.observationMode || !providerIsOn ? "off" : registration.modelId,
167817
- chunkModelId: registration.observationMode || !providerIsOn ? "off" : registration.chunkModelId
168048
+ chunkModelId: registration.observationMode || !providerIsOn ? "off" : registration.chunkModelId,
168049
+ model: registration.observationMode || !providerIsOn ? "off" : ("model" in registration.config) && registration.config.model.trim() ? registration.config.model.trim() : registration.modelId,
168050
+ provider: registration.observationMode || !providerIsOn ? "off" : registration.config.provider ?? "local"
167818
168051
  };
167819
168052
  }
167820
168053
  function disposeProvider(provider) {
@@ -168025,8 +168258,9 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
168025
168258
  }
168026
168259
  async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates, signal) {
168027
168260
  const noWork = [];
168261
+ const failed = [];
168028
168262
  if (candidates.length === 0)
168029
- return { embedded: 0, noWork };
168263
+ return { embedded: 0, noWork, failed };
168030
168264
  const maxInputTokens = getProjectEmbeddingMaxInputTokens(projectIdentity);
168031
168265
  const prepared = [];
168032
168266
  for (const candidate of candidates) {
@@ -168043,7 +168277,7 @@ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates
168043
168277
  prepared.push({ candidate, windows });
168044
168278
  }
168045
168279
  if (prepared.length === 0)
168046
- return { embedded: 0, noWork };
168280
+ return { embedded: 0, noWork, failed };
168047
168281
  let embedded = 0;
168048
168282
  let i = 0;
168049
168283
  while (i < prepared.length) {
@@ -168060,35 +168294,60 @@ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates
168060
168294
  const texts = [];
168061
168295
  for (const item of slice)
168062
168296
  texts.push(...item.windows.map((w) => w.text));
168063
- try {
168064
- const result = await embedBatchForProject(projectIdentity, texts, signal);
168065
- if (!result)
168066
- continue;
168297
+ const persistedIds = new Set;
168298
+ for (let attempt = 0;attempt < EMBED_SLICE_RETRY_ATTEMPTS; attempt++) {
168067
168299
  if (signal?.aborted)
168068
168300
  break;
168069
- let offset = 0;
168070
- for (const item of slice) {
168071
- const vectors = result.vectors.slice(offset, offset + item.windows.length);
168072
- offset += item.windows.length;
168073
- if (vectors.length !== item.windows.length || vectors.some((v) => !v)) {
168074
- continue;
168301
+ let result = null;
168302
+ const attemptStart = Date.now();
168303
+ try {
168304
+ result = await embedBatchForProject(projectIdentity, texts, signal);
168305
+ } catch (error51) {
168306
+ log("[magic-context] failed to proactively embed compartment chunks:", error51);
168307
+ }
168308
+ if (signal?.aborted)
168309
+ break;
168310
+ if (result) {
168311
+ let offset = 0;
168312
+ for (const item of slice) {
168313
+ const vectors = result.vectors.slice(offset, offset + item.windows.length);
168314
+ offset += item.windows.length;
168315
+ if (persistedIds.has(item.candidate.id))
168316
+ continue;
168317
+ if (vectors.length !== item.windows.length || vectors.some((v) => !v)) {
168318
+ continue;
168319
+ }
168320
+ const rows = item.windows.map((window, index) => ({
168321
+ compartmentId: item.candidate.id,
168322
+ sessionId: item.candidate.sessionId,
168323
+ projectPath: projectIdentity,
168324
+ window,
168325
+ modelId,
168326
+ vector: vectors[index]
168327
+ }));
168328
+ replaceCompartmentChunkEmbeddings(db, rows);
168329
+ persistedIds.add(item.candidate.id);
168075
168330
  }
168076
- const rows = item.windows.map((window, index) => ({
168077
- compartmentId: item.candidate.id,
168078
- sessionId: item.candidate.sessionId,
168079
- projectPath: projectIdentity,
168080
- window,
168081
- modelId,
168082
- vector: vectors[index]
168083
- }));
168084
- replaceCompartmentChunkEmbeddings(db, rows);
168085
- embedded += 1;
168086
168331
  }
168087
- } catch (error51) {
168088
- log("[magic-context] failed to proactively embed compartment chunks:", error51);
168332
+ if (persistedIds.size === slice.length)
168333
+ break;
168334
+ if (persistedIds.size > 0)
168335
+ break;
168336
+ if (Date.now() - attemptStart >= EMBED_SLOW_FAILURE_NO_RETRY_MS)
168337
+ break;
168338
+ if (attempt < EMBED_SLICE_RETRY_ATTEMPTS - 1) {
168339
+ await new Promise((resolve4) => setTimeout(resolve4, EMBED_SLICE_RETRY_BASE_MS * 2 ** attempt));
168340
+ }
168341
+ }
168342
+ embedded += persistedIds.size;
168343
+ if (!signal?.aborted) {
168344
+ for (const item of slice) {
168345
+ if (!persistedIds.has(item.candidate.id))
168346
+ failed.push(item.candidate.id);
168347
+ }
168089
168348
  }
168090
168349
  }
168091
- return { embedded, noWork };
168350
+ return { embedded, noWork, failed };
168092
168351
  }
168093
168352
  async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, options) {
168094
168353
  const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
@@ -168111,9 +168370,11 @@ async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, opt
168111
168370
  renewal.unref?.();
168112
168371
  const batchSize = Math.max(1, options?.batchSize ?? CHUNK_DRAIN_BATCH_SIZE);
168113
168372
  const skipIds = [];
168373
+ const failedIds = [];
168114
168374
  let embedded = 0;
168115
168375
  let aborted2 = false;
168116
- let providerStalled = false;
168376
+ let providerDown = false;
168377
+ let consecutiveFailedBatches = 0;
168117
168378
  try {
168118
168379
  options?.onProgress?.({ embedded, total });
168119
168380
  for (;; ) {
@@ -168121,15 +168382,26 @@ async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, opt
168121
168382
  aborted2 = true;
168122
168383
  break;
168123
168384
  }
168124
- const candidates = loadUnembeddedSessionChunkCandidates(db, projectIdentity, sessionId, snapshot.chunkModelId, batchSize, skipIds);
168385
+ const candidates = loadUnembeddedSessionChunkCandidates(db, projectIdentity, sessionId, snapshot.chunkModelId, batchSize, [...skipIds, ...failedIds]);
168125
168386
  if (candidates.length === 0)
168126
168387
  break;
168127
- const { embedded: n, noWork } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, options?.signal);
168388
+ const {
168389
+ embedded: n,
168390
+ noWork,
168391
+ failed
168392
+ } = await embedCandidateChunkBatch(db, projectIdentity, snapshot.chunkModelId, candidates, options?.signal);
168128
168393
  for (const id of noWork)
168129
168394
  skipIds.push(id);
168395
+ for (const id of failed)
168396
+ failedIds.push(id);
168130
168397
  if (n === 0 && noWork.length === 0) {
168131
- providerStalled = true;
168132
- break;
168398
+ consecutiveFailedBatches += 1;
168399
+ if (consecutiveFailedBatches >= MAX_CONSECUTIVE_FAILED_BATCHES) {
168400
+ providerDown = true;
168401
+ break;
168402
+ }
168403
+ } else {
168404
+ consecutiveFailedBatches = 0;
168133
168405
  }
168134
168406
  embedded += n;
168135
168407
  options?.onProgress?.({ embedded: Math.min(embedded, total), total });
@@ -168137,16 +168409,50 @@ async function embedSessionCompartmentChunks(db, projectIdentity, sessionId, opt
168137
168409
  }
168138
168410
  } finally {
168139
168411
  clearInterval(renewal);
168140
- releaseGitSweepLease(db, projectIdentity, holderId);
168412
+ try {
168413
+ releaseGitSweepLease(db, projectIdentity, holderId);
168414
+ } catch (error51) {
168415
+ log("[magic-context] embed drain: lease release failed (will TTL-expire):", error51);
168416
+ }
168141
168417
  }
168142
168418
  if (aborted2)
168143
- return { status: "aborted", embedded, total };
168144
- if (providerStalled) {
168419
+ return { status: "aborted", embedded, total, failed: failedIds.length };
168420
+ if (providerDown || failedIds.length > 0) {
168145
168421
  const remaining = Math.max(0, countUnembeddedSessionCompartments(db, projectIdentity, sessionId, snapshot.chunkModelId) - skipIds.length);
168146
- if (remaining > 0)
168147
- return { status: "stalled", embedded, total, remaining };
168422
+ if (remaining > 0) {
168423
+ return { status: "stalled", embedded, total, remaining, failed: failedIds.length };
168424
+ }
168148
168425
  }
168149
- return { status: "done", embedded, total };
168426
+ return { status: "done", embedded, total, failed: failedIds.length };
168427
+ }
168428
+ function getEmbeddingCoverageStatus(db, projectIdentity, sessionId) {
168429
+ const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
168430
+ if (!snapshot?.enabled || snapshot.chunkModelId === "off") {
168431
+ return {
168432
+ enabled: false,
168433
+ model: snapshot?.model ?? "off",
168434
+ provider: snapshot?.provider ?? "off",
168435
+ session: { embedded: 0, total: 0 },
168436
+ memories: { embedded: 0, total: 0 },
168437
+ commits: { embedded: 0, total: 0, gitEnabled: false }
168438
+ };
168439
+ }
168440
+ const session = countSessionCompartmentEmbedCoverage(db, projectIdentity, sessionId, snapshot.chunkModelId);
168441
+ const memories = getMemoryEmbedCoverage(db, projectIdentity, snapshot.modelId);
168442
+ const gitEnabled = snapshot.gitCommitEnabled;
168443
+ const commits = gitEnabled ? {
168444
+ embedded: countEmbeddedCommits(db, projectIdentity),
168445
+ total: getCommitCount(db, projectIdentity),
168446
+ gitEnabled: true
168447
+ } : { embedded: 0, total: 0, gitEnabled: false };
168448
+ return {
168449
+ enabled: true,
168450
+ model: snapshot.model,
168451
+ provider: snapshot.provider,
168452
+ session,
168453
+ memories,
168454
+ commits
168455
+ };
168150
168456
  }
168151
168457
 
168152
168458
  // ../plugin/src/features/magic-context/memory/embedding.ts
@@ -168195,118 +168501,6 @@ async function embedText(text, signal) {
168195
168501
  }
168196
168502
  var SWEEP_MAX_WALL_CLOCK_MS2 = 10 * 60 * 1000;
168197
168503
 
168198
- // ../plugin/src/features/magic-context/git-commits/storage-git-commits.ts
168199
- init_logger();
168200
- var insertStatements = new WeakMap;
168201
- var existingShasStatements = new WeakMap;
168202
- var projectCountStatements = new WeakMap;
168203
- var evictStatements = new WeakMap;
168204
- var evictOverflowStatements = new WeakMap;
168205
- var latestCommitTimeStatements = new WeakMap;
168206
- function getInsertStatement(db) {
168207
- let stmt = insertStatements.get(db);
168208
- if (!stmt) {
168209
- stmt = db.prepare(`INSERT INTO git_commits (sha, project_path, short_sha, message, author, committed_at, indexed_at)
168210
- VALUES (?, ?, ?, ?, ?, ?, ?)
168211
- ON CONFLICT(sha) DO UPDATE SET
168212
- project_path = excluded.project_path,
168213
- short_sha = excluded.short_sha,
168214
- message = excluded.message,
168215
- author = excluded.author,
168216
- committed_at = excluded.committed_at,
168217
- indexed_at = excluded.indexed_at
168218
- WHERE git_commits.message != excluded.message`);
168219
- insertStatements.set(db, stmt);
168220
- }
168221
- return stmt;
168222
- }
168223
- function getExistingShasStatement(db) {
168224
- let stmt = existingShasStatements.get(db);
168225
- if (!stmt) {
168226
- stmt = db.prepare("SELECT sha FROM git_commits WHERE project_path = ?");
168227
- existingShasStatements.set(db, stmt);
168228
- }
168229
- return stmt;
168230
- }
168231
- function getProjectCountStatement(db) {
168232
- let stmt = projectCountStatements.get(db);
168233
- if (!stmt) {
168234
- stmt = db.prepare("SELECT COUNT(*) AS count FROM git_commits WHERE project_path = ?");
168235
- projectCountStatements.set(db, stmt);
168236
- }
168237
- return stmt;
168238
- }
168239
- function getLatestCommitTimeStatement(db) {
168240
- let stmt = latestCommitTimeStatements.get(db);
168241
- if (!stmt) {
168242
- stmt = db.prepare("SELECT MAX(committed_at) AS latest FROM git_commits WHERE project_path = ?");
168243
- latestCommitTimeStatements.set(db, stmt);
168244
- }
168245
- return stmt;
168246
- }
168247
- function getEvictOverflowStatement(db) {
168248
- let stmt = evictOverflowStatements.get(db);
168249
- if (!stmt) {
168250
- stmt = db.prepare(`DELETE FROM git_commits
168251
- WHERE rowid IN (
168252
- SELECT rowid FROM git_commits
168253
- WHERE project_path = ?
168254
- ORDER BY committed_at DESC, sha DESC
168255
- LIMIT -1 OFFSET ?
168256
- )`);
168257
- evictOverflowStatements.set(db, stmt);
168258
- }
168259
- return stmt;
168260
- }
168261
- function upsertCommits(db, projectPath, commits) {
168262
- if (commits.length === 0)
168263
- return { inserted: 0, updated: 0 };
168264
- const existing = new Set;
168265
- for (const row of getExistingShasStatement(db).all(projectPath)) {
168266
- existing.add(row.sha);
168267
- }
168268
- let inserted = 0;
168269
- let updated = 0;
168270
- const now = Date.now();
168271
- const insertStmt = getInsertStatement(db);
168272
- db.transaction(() => {
168273
- for (const commit of commits) {
168274
- const result = insertStmt.run(commit.sha, projectPath, commit.shortSha, commit.message, commit.author, commit.committedAtMs, now);
168275
- if (result.changes > 0) {
168276
- if (existing.has(commit.sha)) {
168277
- updated++;
168278
- } else {
168279
- inserted++;
168280
- existing.add(commit.sha);
168281
- }
168282
- }
168283
- }
168284
- })();
168285
- return { inserted, updated };
168286
- }
168287
- function getCommitCount(db, projectPath) {
168288
- const row = getProjectCountStatement(db).get(projectPath);
168289
- return row?.count ?? 0;
168290
- }
168291
- function getLatestIndexedCommitTimeMs(db, projectPath) {
168292
- const row = getLatestCommitTimeStatement(db).get(projectPath);
168293
- return row?.latest ?? null;
168294
- }
168295
- function enforceProjectCap(db, projectPath, maxCommits) {
168296
- if (maxCommits <= 0)
168297
- return 0;
168298
- const count = getCommitCount(db, projectPath);
168299
- if (count <= maxCommits)
168300
- return 0;
168301
- getEvictOverflowStatement(db).run(projectPath, maxCommits);
168302
- const after = getCommitCount(db, projectPath);
168303
- const evicted = Math.max(0, count - after);
168304
- if (evicted > 0) {
168305
- log(`[git-commits] evicted ${evicted} oldest commits for project ${projectPath} (cap=${maxCommits}, was=${count})`);
168306
- }
168307
- return evicted;
168308
- }
168309
-
168310
168504
  // ../plugin/src/features/magic-context/git-commits/indexer.ts
168311
168505
  var MS_PER_DAY = 24 * 60 * 60 * 1000;
168312
168506
  var EMBED_BATCH_SIZE = 16;
@@ -169836,87 +170030,229 @@ function registerCtxDreamCommand(pi, deps) {
169836
170030
  });
169837
170031
  }
169838
170032
 
169839
- // src/commands/ctx-embed-history.ts
169840
- function registerCtxEmbedHistoryCommand(pi, deps) {
169841
- pi.registerCommand("ctx-embed-history", {
169842
- description: "Embed all of this session's history compartments for semantic search, in one pass",
169843
- handler: async (_args, ctx) => {
170033
+ // ../plugin/src/hooks/magic-context/embed-session-state.ts
170034
+ var embedPauseBySession = new Set;
170035
+ var embedRunStateBySession = new Map;
170036
+ var autoEmbedAttemptedBySession = new Set;
170037
+
170038
+ // ../plugin/src/hooks/magic-context/format-embed-status.ts
170039
+ function formatEmbedStatusText(coverage, drain) {
170040
+ if (!coverage.enabled) {
170041
+ return "Embedding is off (no provider configured).";
170042
+ }
170043
+ const lines = [];
170044
+ lines.push(`Embedding — model: ${coverage.model} (${coverage.provider})`);
170045
+ lines.push(`This session: ${coverage.session.embedded} / ${coverage.session.total} compartments embedded`);
170046
+ lines.push(`Project memories: ${coverage.memories.embedded} / ${coverage.memories.total} embedded`);
170047
+ if (coverage.commits.gitEnabled) {
170048
+ lines.push(`Git commits: ${coverage.commits.embedded} / ${coverage.commits.total}`);
170049
+ } else {
170050
+ lines.push("Git commits: 0 / 0 (git indexing off)");
170051
+ }
170052
+ let drainLine = "Drain: idle";
170053
+ switch (drain.status) {
170054
+ case "running": {
170055
+ const e = drain.embedded ?? coverage.session.embedded;
170056
+ const t = drain.total ?? coverage.session.total;
170057
+ const failedSuffix = drain.failed && drain.failed > 0 ? ` (${drain.failed} failed)` : "";
170058
+ drainLine = `Drain: running ${e}/${t}${failedSuffix}`;
170059
+ break;
170060
+ }
170061
+ case "paused": {
170062
+ const e = drain.embedded ?? coverage.session.embedded;
170063
+ const t = drain.total ?? coverage.session.total;
170064
+ drainLine = `Drain: paused ${e}/${t}`;
170065
+ break;
170066
+ }
170067
+ case "stopped":
170068
+ drainLine = "Drain: stopped (provider down)";
170069
+ break;
170070
+ default:
170071
+ drainLine = "Drain: idle";
170072
+ }
170073
+ lines.push(drainLine);
170074
+ return lines.join(`
170075
+ `);
170076
+ }
170077
+
170078
+ // src/commands/ctx-embed.ts
170079
+ function clearPiEmbedSessionState(sessionId) {
170080
+ embedPauseBySession.delete(sessionId);
170081
+ const ctrl = embedRunStateBySession.get(sessionId);
170082
+ if (ctrl) {
170083
+ ctrl.abort();
170084
+ embedRunStateBySession.delete(sessionId);
170085
+ }
170086
+ autoEmbedAttemptedBySession.delete(sessionId);
170087
+ }
170088
+ async function runEmbedDrain(db, projectIdentity, sessionId) {
170089
+ const activeCtrl = embedRunStateBySession.get(sessionId);
170090
+ if (activeCtrl && !activeCtrl.signal.aborted) {
170091
+ return {
170092
+ text: `## /ctx-embed
170093
+
170094
+ Embedding is already running for this session.`,
170095
+ level: "info"
170096
+ };
170097
+ }
170098
+ embedPauseBySession.delete(sessionId);
170099
+ const prior = embedRunStateBySession.get(sessionId);
170100
+ if (prior)
170101
+ prior.abort();
170102
+ const controller = new AbortController;
170103
+ embedRunStateBySession.set(sessionId, controller);
170104
+ let outcome;
170105
+ try {
170106
+ outcome = await embedSessionCompartmentChunks(db, projectIdentity, sessionId, {
170107
+ signal: controller.signal
170108
+ });
170109
+ } finally {
170110
+ if (embedRunStateBySession.get(sessionId) === controller) {
170111
+ embedRunStateBySession.delete(sessionId);
170112
+ }
170113
+ }
170114
+ switch (outcome.status) {
170115
+ case "nothing":
170116
+ return {
170117
+ text: `## /ctx-embed
170118
+
170119
+ All of this session's history is already embedded.`,
170120
+ level: "info"
170121
+ };
170122
+ case "disabled":
170123
+ return {
170124
+ text: `## /ctx-embed
170125
+
170126
+ No embedding provider is configured, so there is nothing to embed.`,
170127
+ level: "info"
170128
+ };
170129
+ case "busy":
170130
+ return {
170131
+ text: `## /ctx-embed
170132
+
170133
+ Embedding is already running for this project. Try again shortly.`,
170134
+ level: "info"
170135
+ };
170136
+ case "aborted": {
170137
+ const cov = getEmbeddingCoverageStatus(db, projectIdentity, sessionId);
170138
+ return {
170139
+ text: `## /ctx-embed
170140
+
170141
+ Paused at ${cov.session.embedded}/${cov.session.total} compartments embedded.`,
170142
+ level: "info"
170143
+ };
170144
+ }
170145
+ case "stalled":
170146
+ return {
170147
+ text: `## /ctx-embed
170148
+
170149
+ Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"}; ${outcome.remaining} could not be embedded (the provider returned no result). Run /ctx-embed start again to retry them.`,
170150
+ level: "info"
170151
+ };
170152
+ default:
170153
+ return {
170154
+ text: `## /ctx-embed
170155
+
170156
+ Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"} of history for semantic search.`,
170157
+ level: "success"
170158
+ };
170159
+ }
170160
+ }
170161
+ function registerCtxEmbedCommand(pi, deps) {
170162
+ pi.registerCommand("ctx-embed", {
170163
+ description: "Embedding status, or start/pause history compartment embedding (start | pause)",
170164
+ handler: async (args, ctx) => {
169844
170165
  const sessionId = resolveSessionId(ctx);
169845
170166
  if (!sessionId) {
169846
170167
  sendCtxStatusMessage(pi, {
169847
- title: "/ctx-embed-history",
169848
- text: `## /ctx-embed-history
170168
+ title: "/ctx-embed",
170169
+ text: `## /ctx-embed
169849
170170
 
169850
170171
  No active Pi session is available.`,
169851
170172
  level: "error"
169852
170173
  });
169853
170174
  return;
169854
170175
  }
170176
+ const project = deps.resolveProject?.(ctx) ?? {
170177
+ projectDir: deps.projectDir,
170178
+ projectIdentity: deps.projectIdentity
170179
+ };
170180
+ const sub = args.trim().toLowerCase();
170181
+ if (sub === "pause") {
170182
+ embedPauseBySession.add(sessionId);
170183
+ const ctrl = embedRunStateBySession.get(sessionId);
170184
+ if (ctrl)
170185
+ ctrl.abort();
170186
+ const cov = getEmbeddingCoverageStatus(deps.db, project.projectIdentity, sessionId);
170187
+ sendCtxStatusMessage(pi, {
170188
+ title: "/ctx-embed",
170189
+ text: `## /ctx-embed
170190
+
170191
+ Paused at ${cov.session.embedded}/${cov.session.total} compartments embedded.`,
170192
+ level: "info"
170193
+ });
170194
+ return;
170195
+ }
169855
170196
  if (deps.memoryEnabled === false) {
169856
170197
  sendCtxStatusMessage(pi, {
169857
- title: "/ctx-embed-history",
169858
- text: `## /ctx-embed-history
170198
+ title: "/ctx-embed",
170199
+ text: `## /ctx-embed
169859
170200
 
169860
170201
  Memory is disabled for this project, so there is no semantic embedding to backfill.`,
169861
170202
  level: "info"
169862
170203
  });
169863
170204
  return;
169864
170205
  }
169865
- const project = deps.resolveProject?.(ctx) ?? {
169866
- projectDir: deps.projectDir,
169867
- projectIdentity: deps.projectIdentity
169868
- };
169869
170206
  await ensureProjectRegisteredFromPiDirectory(project.projectDir, deps.db);
169870
- const outcome = await embedSessionCompartmentChunks(deps.db, project.projectIdentity, sessionId);
169871
- const { text, level } = (() => {
169872
- switch (outcome.status) {
169873
- case "nothing":
169874
- return {
169875
- text: `## /ctx-embed-history
169876
-
169877
- All of this session's history is already embedded.`,
169878
- level: "info"
169879
- };
169880
- case "disabled":
169881
- return {
169882
- text: `## /ctx-embed-history
169883
-
169884
- No embedding provider is configured, so there is nothing to embed.`,
169885
- level: "info"
169886
- };
169887
- case "busy":
169888
- return {
169889
- text: `## /ctx-embed-history
169890
-
169891
- Embedding is already running for this project — ${outcome.total} compartment${outcome.total === 1 ? "" : "s"} still pending. Try again shortly.`,
169892
- level: "info"
169893
- };
169894
- case "stalled":
169895
- return {
169896
- text: `## /ctx-embed-history
169897
-
169898
- Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"}; ${outcome.remaining} could not be embedded (the provider returned no result). Run /ctx-embed-history again to retry them.`,
169899
- level: "info"
169900
- };
169901
- default:
169902
- return {
169903
- text: `## /ctx-embed-history
170207
+ if (sub === "start") {
170208
+ const { text, level } = await runEmbedDrain(deps.db, project.projectIdentity, sessionId);
170209
+ sendCtxStatusMessage(pi, { title: "/ctx-embed", text, level });
170210
+ return;
170211
+ }
170212
+ if (sub !== "") {
170213
+ sendCtxStatusMessage(pi, {
170214
+ title: "/ctx-embed",
170215
+ text: "## /ctx-embed\n\nUsage: `/ctx-embed` (status), `/ctx-embed start`, or `/ctx-embed pause`.",
170216
+ level: "info"
170217
+ });
170218
+ return;
170219
+ }
170220
+ const coverage = getEmbeddingCoverageStatus(deps.db, project.projectIdentity, sessionId);
170221
+ const statusText = formatEmbedStatusText(coverage, { status: "idle" });
170222
+ sendCtxStatusMessage(pi, {
170223
+ title: "/ctx-embed",
170224
+ text: `## Embedding Status
169904
170225
 
169905
- Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"} of history for semantic search.`,
169906
- level: "success"
169907
- };
169908
- }
169909
- })();
169910
- sendCtxStatusMessage(pi, { title: "/ctx-embed-history", text, level }, {
169911
- sessionId,
169912
- projectIdentity: project.projectIdentity,
169913
- status: outcome.status,
169914
- embedded: outcome.embedded,
169915
- total: outcome.total
170226
+ ${statusText}`,
170227
+ level: "info"
169916
170228
  });
169917
170229
  }
169918
170230
  });
169919
170231
  }
170232
+ function maybeAutoEmbedPiSession(deps, sessionId, projectDir, projectIdentity, notify) {
170233
+ if (autoEmbedAttemptedBySession.has(sessionId))
170234
+ return;
170235
+ if (embedPauseBySession.has(sessionId))
170236
+ return;
170237
+ if (deps.memoryEnabled === false)
170238
+ return;
170239
+ autoEmbedAttemptedBySession.add(sessionId);
170240
+ (async () => {
170241
+ try {
170242
+ await new Promise((resolve5) => setTimeout(resolve5, 0));
170243
+ await ensureProjectRegisteredFromPiDirectory(projectDir, deps.db);
170244
+ const coverage = getEmbeddingCoverageStatus(deps.db, projectIdentity, sessionId);
170245
+ if (!coverage.enabled)
170246
+ return;
170247
+ const remaining = coverage.session.total - coverage.session.embedded;
170248
+ if (remaining <= 0)
170249
+ return;
170250
+ notify(`Embedding ${remaining} compartment${remaining === 1 ? "" : "s"} of history in the background…`);
170251
+ const { text } = await runEmbedDrain(deps.db, projectIdentity, sessionId);
170252
+ notify(text.replace(/^## \/ctx-embed\n\n/, ""));
170253
+ } catch {}
170254
+ })();
170255
+ }
169920
170256
 
169921
170257
  // ../plugin/src/hooks/magic-context/execute-flush.ts
169922
170258
  init_logger();
@@ -170331,76 +170667,12 @@ function computePiWorkMetrics(sessionEntries) {
170331
170667
  return { newWorkTokens, totalInputTokens };
170332
170668
  }
170333
170669
 
170334
- // ../plugin/src/hooks/magic-context/system-injection-stripper.ts
170335
- var SYSTEM_INJECTION_MARKERS = [
170336
- "<!-- OMO_INTERNAL_INITIATOR -->",
170337
- "[SYSTEM DIRECTIVE: MAGIC-CONTEXT",
170338
- "[SYSTEM DIRECTIVE: OH-MY-OPENCODE",
170339
- "[Category+Skill Reminder]",
170340
- "[EDIT ERROR - IMMEDIATE ACTION REQUIRED]",
170341
- "[task CALL FAILED - IMMEDIATE RETRY REQUIRED]",
170342
- "[EMERGENCY CONTEXT WINDOW WARNING]",
170343
- "Unstable background agent appears idle",
170344
- "**THE SUBAGENT JUST CLAIMED THIS TASK IS DONE."
170345
- ];
170346
- var SYSTEM_REMINDER_REGEX = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
170347
- var OMO_MARKER_REGEX = /<!-- OMO_INTERNAL_INITIATOR -->/g;
170348
- function stripSystemInjection(text) {
170349
- let hasInjection = false;
170350
- for (const marker of SYSTEM_INJECTION_MARKERS) {
170351
- if (text.includes(marker)) {
170352
- hasInjection = true;
170353
- break;
170354
- }
170355
- }
170356
- if (SYSTEM_REMINDER_REGEX.test(text))
170357
- hasInjection = true;
170358
- SYSTEM_REMINDER_REGEX.lastIndex = 0;
170359
- if (!hasInjection)
170360
- return null;
170361
- let cleaned = text;
170362
- cleaned = cleaned.replace(SYSTEM_REMINDER_REGEX, "");
170363
- cleaned = cleaned.replace(OMO_MARKER_REGEX, "");
170364
- cleaned = cleaned.replace(/\[SYSTEM DIRECTIVE: OH-MY-(?:OPENCODE|CLAUDE)[^\]]*\][\s\S]*?(?=\n\n(?!\s*[-*])|$)/g, "");
170365
- for (const marker of SYSTEM_INJECTION_MARKERS) {
170366
- if (marker.startsWith("<!-- ") || marker.startsWith("[SYSTEM DIRECTIVE"))
170367
- continue;
170368
- const idx = cleaned.indexOf(marker);
170369
- if (idx === -1)
170370
- continue;
170371
- const blockEnd = cleaned.indexOf(`
170372
-
170373
- `, idx + marker.length);
170374
- cleaned = blockEnd !== -1 ? cleaned.slice(0, idx) + cleaned.slice(blockEnd) : cleaned.slice(0, idx);
170375
- }
170376
- return cleaned.trim();
170377
- }
170378
-
170379
170670
  // ../plugin/src/hooks/magic-context/apply-operations.ts
170380
- var USER_DROP_PREVIEW_CHARS = 250;
170381
170671
  var RECENT_TOOL_SKELETON_WINDOW = 20;
170382
- function buildReplacementContent(tagId, target) {
170383
- const role = target.message?.info.role;
170384
- if (role !== "user") {
170385
- return `[dropped §${tagId}§]`;
170386
- }
170387
- const currentContent = target.getContent?.() ?? "";
170388
- const strippedInjection = stripSystemInjection(currentContent);
170389
- if (strippedInjection !== null && stripTagPrefix(strippedInjection).trim().length === 0) {
170390
- return `[dropped §${tagId}§]`;
170391
- }
170392
- const originalText = stripTagPrefix(currentContent);
170393
- if (originalText.length <= USER_DROP_PREVIEW_CHARS) {
170394
- return `[truncated §${tagId}§]
170395
- ${originalText}`;
170396
- }
170397
- const hardCut = originalText.slice(0, USER_DROP_PREVIEW_CHARS);
170398
- const softCutIndex = hardCut.search(/\s\S*$/);
170399
- const preview = softCutIndex > USER_DROP_PREVIEW_CHARS - 30 ? hardCut.slice(0, softCutIndex) : hardCut;
170400
- return `[truncated §${tagId}§]
170401
- ${preview}…`;
170402
- }
170403
- function applyPendingOperations(sessionId, db, targets, protectedTags = 0, preloadedTags, preloadedPendingOps) {
170672
+ function buildReplacementContent(tagId) {
170673
+ return `[dropped §${tagId}§]`;
170674
+ }
170675
+ function applyPendingOperations(sessionId, db, targets, protectedTags = 0, preloadedTags, preloadedPendingOps, syntheticPendingOps = []) {
170404
170676
  let didMutateMessage = false;
170405
170677
  db.transaction(() => {
170406
170678
  const tags = preloadedTags ?? getTagsBySession(db, sessionId);
@@ -170408,11 +170680,16 @@ function applyPendingOperations(sessionId, db, targets, protectedTags = 0, prelo
170408
170680
  const tagTypeById = new Map(tags.map((tag) => [tag.tagNumber, tag.type]));
170409
170681
  const protectedTagIds = protectedTags > 0 ? new Set(tags.filter((tag) => tag.status === "active").map((tag) => tag.tagNumber).sort((left, right) => right - left).slice(0, protectedTags)) : new Set;
170410
170682
  const pendingOps = preloadedPendingOps ?? getPendingOps(db, sessionId);
170683
+ const opsToApply = [
170684
+ ...pendingOps.map((op) => ({ op, synthetic: false })),
170685
+ ...syntheticPendingOps.map((op) => ({ op, synthetic: true }))
170686
+ ];
170411
170687
  const skeletonWindow = new Set(tags.filter((tag) => tag.type === "tool").map((tag) => tag.tagNumber).sort((left, right) => right - left).slice(0, RECENT_TOOL_SKELETON_WINDOW));
170412
- for (const pendingOp of pendingOps) {
170688
+ for (const { op: pendingOp, synthetic } of opsToApply) {
170413
170689
  const tagStatus = tagStatusById.get(pendingOp.tagId);
170414
170690
  if (tagStatus === "compacted" || tagStatus === "dropped") {
170415
- removePendingOp(db, sessionId, pendingOp.tagId);
170691
+ if (!synthetic)
170692
+ removePendingOp(db, sessionId, pendingOp.tagId);
170416
170693
  continue;
170417
170694
  }
170418
170695
  if (protectedTagIds.has(pendingOp.tagId)) {
@@ -170420,33 +170697,46 @@ function applyPendingOperations(sessionId, db, targets, protectedTags = 0, prelo
170420
170697
  }
170421
170698
  const target = targets.get(pendingOp.tagId);
170422
170699
  const isToolTag = tagTypeById.get(pendingOp.tagId) === "tool";
170700
+ if (synthetic) {
170701
+ if (!isToolTag || target?.canDrop?.() !== true)
170702
+ continue;
170703
+ }
170704
+ let shouldPersistDrop = false;
170423
170705
  if (isToolTag) {
170424
170706
  if (skeletonWindow.has(pendingOp.tagId)) {
170425
170707
  const truncResult = target?.truncate?.() ?? "absent";
170426
- if (truncResult === "incomplete") {
170708
+ if (truncResult === "incomplete" || synthetic && truncResult !== "truncated") {
170427
170709
  continue;
170428
170710
  }
170429
170711
  if (truncResult === "truncated") {
170430
170712
  didMutateMessage = true;
170431
170713
  }
170432
170714
  updateTagDropMode(db, sessionId, pendingOp.tagId, "truncated");
170715
+ shouldPersistDrop = true;
170433
170716
  } else {
170434
170717
  const dropResult = target?.drop?.() ?? "absent";
170435
- if (dropResult === "incomplete") {
170718
+ if (dropResult === "incomplete" || synthetic && dropResult !== "removed") {
170436
170719
  continue;
170437
170720
  }
170438
170721
  if (dropResult === "removed") {
170439
170722
  didMutateMessage = true;
170440
170723
  }
170441
170724
  updateTagDropMode(db, sessionId, pendingOp.tagId, "full");
170725
+ shouldPersistDrop = true;
170442
170726
  }
170443
170727
  } else if (target) {
170444
- const changed = target.setContent(buildReplacementContent(pendingOp.tagId, target));
170728
+ const changed = target.setContent(buildReplacementContent(pendingOp.tagId));
170445
170729
  if (changed)
170446
170730
  didMutateMessage = true;
170731
+ shouldPersistDrop = true;
170732
+ } else if (!synthetic) {
170733
+ shouldPersistDrop = true;
170447
170734
  }
170735
+ if (!shouldPersistDrop)
170736
+ continue;
170448
170737
  updateTagStatus(db, sessionId, pendingOp.tagId, "dropped");
170449
- removePendingOp(db, sessionId, pendingOp.tagId);
170738
+ if (!synthetic)
170739
+ removePendingOp(db, sessionId, pendingOp.tagId);
170450
170740
  }
170451
170741
  })();
170452
170742
  return didMutateMessage;
@@ -170470,7 +170760,7 @@ function applyFlushedStatuses(sessionId, db, targets, preloadedTags) {
170470
170760
  }
170471
170761
  }
170472
170762
  } else if (target) {
170473
- const changed = target.setContent(buildReplacementContent(tag.tagNumber, target));
170763
+ const changed = target.setContent(buildReplacementContent(tag.tagNumber));
170474
170764
  if (changed)
170475
170765
  didMutateMessage = true;
170476
170766
  }
@@ -170758,7 +171048,8 @@ function applyCavemanCleanup(sessionId, db, targets, tags, config2) {
170758
171048
  const result = {
170759
171049
  compressedToLite: 0,
170760
171050
  compressedToFull: 0,
170761
- compressedToUltra: 0
171051
+ compressedToUltra: 0,
171052
+ mutatedTextTags: 0
170762
171053
  };
170763
171054
  if (!config2.enabled)
170764
171055
  return result;
@@ -170799,7 +171090,9 @@ function applyCavemanCleanup(sessionId, db, targets, tags, config2) {
170799
171090
  const target = targets.get(tag.tagNumber);
170800
171091
  if (!target)
170801
171092
  continue;
170802
- target.setContent(compressed);
171093
+ const didMutate = target.setContent(compressed);
171094
+ if (didMutate)
171095
+ result.mutatedTextTags += 1;
170803
171096
  updateCavemanDepth(db, sessionId, tag.tagNumber, targetDepth);
170804
171097
  if (targetDepth === DEPTH_LITE)
170805
171098
  result.compressedToLite += 1;
@@ -171177,7 +171470,7 @@ function buildToolArcs(messages) {
171177
171470
  }
171178
171471
  return arcs.sort((a, b) => a.invOrdinal - b.invOrdinal || (a.resOrdinal ?? Number.MAX_SAFE_INTEGER) - (b.resOrdinal ?? Number.MAX_SAFE_INTEGER));
171179
171472
  }
171180
- function fenceBoundaryForToolArcs(candidate, arcs, lastCompartmentEndOrdinal) {
171473
+ function fenceBoundaryForToolArcs(candidate, arcs, lastCompartmentEndOrdinal, recentOpenArcCutoff) {
171181
171474
  let boundary = candidate;
171182
171475
  for (const arc of arcs) {
171183
171476
  if (arc.resOrdinal !== null) {
@@ -171186,6 +171479,8 @@ function fenceBoundaryForToolArcs(candidate, arcs, lastCompartmentEndOrdinal) {
171186
171479
  }
171187
171480
  continue;
171188
171481
  }
171482
+ if (arc.invOrdinal < recentOpenArcCutoff)
171483
+ continue;
171189
171484
  if (arc.invOrdinal >= lastCompartmentEndOrdinal + 1 && arc.invOrdinal < boundary) {
171190
171485
  return arc.invOrdinal;
171191
171486
  }
@@ -171423,7 +171718,7 @@ function semanticSnapBoundary(args) {
171423
171718
  return snapped;
171424
171719
  }
171425
171720
  function applyHeadCap(args) {
171426
- const { index, protectedTailStart, offset, arcs, capTokens } = args;
171721
+ const { index, protectedTailStart, offset, arcs, capTokens, recentOpenArcCutoff } = args;
171427
171722
  if (offset >= protectedTailStart)
171428
171723
  return { eligibleEndOrdinal: offset, oversizeAtomicUnit: false };
171429
171724
  let end = index.findHeadEndForCap(offset, protectedTailStart, capTokens);
@@ -171431,7 +171726,7 @@ function applyHeadCap(args) {
171431
171726
  for (const arc of arcs) {
171432
171727
  const resOrdinal = arc.resOrdinal;
171433
171728
  if (resOrdinal === null) {
171434
- if (arc.invOrdinal >= offset && arc.invOrdinal < end) {
171729
+ if (arc.invOrdinal >= recentOpenArcCutoff && arc.invOrdinal >= offset && arc.invOrdinal < end) {
171435
171730
  end = Math.min(end, arc.invOrdinal);
171436
171731
  }
171437
171732
  continue;
@@ -171498,7 +171793,14 @@ function resolveProtectedTailBoundary(ctx) {
171498
171793
  }
171499
171794
  if (ctx.mode === "manual-full-recomp") {
171500
171795
  const arcs2 = buildToolArcs(messages);
171501
- const firstOpenArc = arcs2.find((arc) => arc.resOrdinal === null && arc.invOrdinal >= offset);
171796
+ const recompTarget = deriveProtectedTailTokenTarget({
171797
+ contextLimit: ctx.contextLimit,
171798
+ executeThresholdPercentage: ctx.executeThresholdPercentage,
171799
+ usagePercentage: 0,
171800
+ triggerBudget: ctx.triggerBudget
171801
+ });
171802
+ const recentOpenArcCutoff2 = index.findSuffixStartForTokens(recompTarget.N);
171803
+ const firstOpenArc = arcs2.find((arc) => arc.resOrdinal === null && arc.invOrdinal >= offset && arc.invOrdinal >= recentOpenArcCutoff2);
171502
171804
  const protectedTailStart2 = firstOpenArc?.invOrdinal ?? rawMessageCount + 1;
171503
171805
  const rawRangeFingerprint2 = computeRawRangeFingerprint(messages, offset, protectedTailStart2);
171504
171806
  return {
@@ -171540,13 +171842,14 @@ function resolveProtectedTailBoundary(ctx) {
171540
171842
  const scaledN = ctx.emergencyTailScale ? Math.max(1, Math.floor(target.N * ctx.emergencyTailScale)) : target.N;
171541
171843
  const arcs = buildToolArcs(messages);
171542
171844
  let boundary = index.findSuffixStartForTokens(scaledN);
171845
+ const recentOpenArcCutoff = boundary;
171543
171846
  let boundaryReason = boundary === 1 ? "whole-session-smaller-than-tail" : "size-walk";
171544
171847
  const tokenAtBoundary = index.tokenForOrdinal(boundary);
171545
171848
  if (boundary <= rawMessageCount && tokenAtBoundary > Math.max(2 * scaledN, 64000) && boundary < rawMessageCount) {
171546
171849
  boundary += 1;
171547
171850
  boundaryReason = "huge-message-exception";
171548
171851
  }
171549
- boundary = fenceBoundaryForToolArcs(boundary, arcs, ctx.lastCompartmentEndOrdinal);
171852
+ boundary = fenceBoundaryForToolArcs(boundary, arcs, ctx.lastCompartmentEndOrdinal, recentOpenArcCutoff);
171550
171853
  const snapped = semanticSnapBoundary({
171551
171854
  messages,
171552
171855
  index,
@@ -171556,7 +171859,7 @@ function resolveProtectedTailBoundary(ctx) {
171556
171859
  });
171557
171860
  if (snapped !== boundary)
171558
171861
  boundaryReason = "semantic-snap";
171559
- boundary = fenceBoundaryForToolArcs(snapped, arcs, ctx.lastCompartmentEndOrdinal);
171862
+ boundary = fenceBoundaryForToolArcs(snapped, arcs, ctx.lastCompartmentEndOrdinal, recentOpenArcCutoff);
171560
171863
  let runtimeFloor = offset;
171561
171864
  if (ctx.migrationFloorActive)
171562
171865
  runtimeFloor = Math.max(runtimeFloor, ctx.priorBoundaryOrdinal);
@@ -171592,7 +171895,8 @@ function resolveProtectedTailBoundary(ctx) {
171592
171895
  offset,
171593
171896
  arcs,
171594
171897
  lastCompartmentEndOrdinal: ctx.lastCompartmentEndOrdinal,
171595
- capTokens: perRunCap
171898
+ capTokens: perRunCap,
171899
+ recentOpenArcCutoff
171596
171900
  });
171597
171901
  const rawRangeFingerprint = computeRawRangeFingerprint(messages, offset, head.eligibleEndOrdinal);
171598
171902
  return {
@@ -172066,6 +172370,13 @@ function computePressure(input) {
172066
172370
  function approxThousands(tokens) {
172067
172371
  return `${Math.round(tokens / 1000)}k`;
172068
172372
  }
172373
+ function formatOldestReclaimableHint(hint) {
172374
+ if (!hint || hint.length === 0)
172375
+ return "";
172376
+ const rendered = hint.slice(0, 4).map((tag) => `§${tag.tagNumber}§ ${tag.toolName ?? "tool"}`).join(" · ");
172377
+ return rendered.length > 0 ? `
172378
+ oldest reclaimable: ${rendered}.` : "";
172379
+ }
172069
172380
  var CHANNEL2_USABLE_FRACTION = 1 / 3;
172070
172381
  var CHANNEL2_MIN_RECLAIMABLE = 1e4;
172071
172382
  function shouldTriggerChannel2(input) {
@@ -172075,14 +172386,16 @@ function shouldTriggerChannel2(input) {
172075
172386
  return true;
172076
172387
  return input.reclaimableTokens >= input.usableTokens * CHANNEL2_USABLE_FRACTION;
172077
172388
  }
172078
- function buildChannel2Reminder(undroppedTokens) {
172389
+ function buildChannel2Reminder(undroppedTokens, hint) {
172079
172390
  const amount = approxThousands(undroppedTokens);
172391
+ const hintText = formatOldestReclaimableHint(hint);
172080
172392
  return `<system-reminder>
172081
- ` + `Routine context housekeeping is near: a large span of this session will be comparted soon, ` + `and ~${amount} tokens of tool output remain unreduced. Drop spent outputs with ctx_reduce ` + `first so the archived span is the part that matters.
172393
+ ` + `Routine context housekeeping is near: a large span of this session will be comparted soon, ` + `and ~${amount} tokens of tool output remain unreduced. Drop spent outputs with ctx_reduce ` + `first so the archived span is the part that matters.${hintText}
172082
172394
  ` + `</system-reminder>`;
172083
172395
  }
172084
- function buildChannel1Reminder(level, undroppedTokens) {
172396
+ function buildChannel1Reminder(level, undroppedTokens, hint) {
172085
172397
  const amount = approxThousands(undroppedTokens);
172398
+ const hintText = formatOldestReclaimableHint(hint);
172086
172399
  let body;
172087
172400
  switch (level) {
172088
172401
  case "gentle":
@@ -172098,7 +172411,7 @@ function buildChannel1Reminder(level, undroppedTokens) {
172098
172411
  return `
172099
172412
 
172100
172413
  <system-reminder>
172101
- ${body}
172414
+ ${body}${hintText}
172102
172415
  </system-reminder>`;
172103
172416
  }
172104
172417
 
@@ -172646,6 +172959,41 @@ function renderMemoryBlockV2(memories, wrapper = "project-memory", renderOptions
172646
172959
  `);
172647
172960
  }
172648
172961
 
172962
+ // ../plugin/src/hooks/magic-context/tool-reclaim.ts
172963
+ function buildSyntheticToolReclaimOps(input) {
172964
+ const watermark = Math.max(0, input.watermark);
172965
+ if (watermark <= 0)
172966
+ return [];
172967
+ const realPendingTagIds = new Set((input.pendingOps ?? []).map((op) => op.tagId));
172968
+ const tags = getActiveTagsBySession(input.db, input.sessionId);
172969
+ const synthetic = [];
172970
+ for (const tag of tags) {
172971
+ if (tag.type !== "tool")
172972
+ continue;
172973
+ if (tag.status !== "active")
172974
+ continue;
172975
+ if (tag.tagNumber > watermark)
172976
+ continue;
172977
+ if (realPendingTagIds.has(tag.tagNumber))
172978
+ continue;
172979
+ if (input.targets.get(tag.tagNumber)?.canDrop?.() !== true)
172980
+ continue;
172981
+ synthetic.push({
172982
+ id: 0,
172983
+ sessionId: input.sessionId,
172984
+ tagId: tag.tagNumber,
172985
+ operation: "drop",
172986
+ queuedAt: 0
172987
+ });
172988
+ }
172989
+ return synthetic;
172990
+ }
172991
+ function advanceToolReclaimWatermarkToCurrentMax(db, sessionId) {
172992
+ const maxTagNumber = getMaxTagNumberBySession(db, sessionId);
172993
+ advanceToolReclaimWatermark(db, sessionId, maxTagNumber);
172994
+ return maxTagNumber;
172995
+ }
172996
+
172649
172997
  // src/context-handler.ts
172650
172998
  init_logger();
172651
172999
 
@@ -173056,7 +173404,7 @@ function tagToolPart(args) {
173056
173404
  const tagged = prependTag(tagId, text);
173057
173405
  args.part.setText(tagged);
173058
173406
  }
173059
- args.targets.set(tagId, buildToolTarget(args.part, args.message));
173407
+ args.targets.set(tagId, buildToolTarget(args.part, args.message, tagId));
173060
173408
  }
173061
173409
  function setToolContentOrText(part, content) {
173062
173410
  try {
@@ -173096,7 +173444,7 @@ function buildAggregateTarget(tagId, occurrences) {
173096
173444
  return any2 ? "removed" : "absent";
173097
173445
  },
173098
173446
  truncate() {
173099
- const sentinel = "[truncated]";
173447
+ const sentinel = `[dropped §${tagId}§]`;
173100
173448
  let any2 = false;
173101
173449
  for (const occ of occurrences) {
173102
173450
  if (setToolContentOrText(occ.part, sentinel)) {
@@ -173128,7 +173476,7 @@ function buildTextTarget(part, message) {
173128
173476
  }
173129
173477
  };
173130
173478
  }
173131
- function buildToolTarget(part, message) {
173479
+ function buildToolTarget(part, message, tagId) {
173132
173480
  return {
173133
173481
  setContent(content) {
173134
173482
  return setToolContentOrText(part, content);
@@ -173137,11 +173485,11 @@ function buildToolTarget(part, message) {
173137
173485
  return part.getText() ?? null;
173138
173486
  },
173139
173487
  drop() {
173140
- const replaced = part.replaceWithSentinel(`[dropped §${part.id ?? "?"}§]`);
173488
+ const replaced = part.replaceWithSentinel(`[dropped §${tagId}§]`);
173141
173489
  return replaced ? "removed" : "absent";
173142
173490
  },
173143
173491
  truncate() {
173144
- const ok = setToolContentOrText(part, "[truncated]");
173492
+ const ok = setToolContentOrText(part, `[dropped §${tagId}§]`);
173145
173493
  return ok ? "truncated" : "absent";
173146
173494
  },
173147
173495
  message: {
@@ -174426,9 +174774,10 @@ function maybeChannel1ReminderForToolResult(args) {
174426
174774
  return null;
174427
174775
  return {
174428
174776
  type: "text",
174429
- text: buildChannel1Reminder(decision.level, decision.undroppedTokens)
174777
+ text: buildChannel1Reminder(decision.level, decision.undroppedTokens, state.oldestReclaimableToolTags)
174430
174778
  };
174431
174779
  }
174780
+ var CHANNEL2_NUDGE_CUSTOM_TYPE = "magic-context:ceiling-nudge";
174432
174781
  function maybeDeliverChannel2Pi(pi, db, sessionId, deliverAs = "followUp") {
174433
174782
  let state;
174434
174783
  try {
@@ -174456,9 +174805,12 @@ function maybeDeliverChannel2Pi(pi, db, sessionId, deliverAs = "followUp") {
174456
174805
  if (!casChannel2NudgeState(db, sessionId, "pending", "claimed"))
174457
174806
  return false;
174458
174807
  try {
174459
- pi.sendUserMessage(buildChannel2Reminder(undropped), {
174460
- deliverAs
174461
- });
174808
+ pi.sendMessage({
174809
+ customType: CHANNEL2_NUDGE_CUSTOM_TYPE,
174810
+ content: buildChannel2Reminder(undropped, baseline.oldestReclaimableToolTags),
174811
+ display: false,
174812
+ details: { kind: "channel-2-ceiling-nudge" }
174813
+ }, { deliverAs });
174462
174814
  } catch (error51) {
174463
174815
  try {
174464
174816
  casChannel2NudgeState(db, sessionId, "claimed", "pending");
@@ -174650,6 +175002,51 @@ function planEmergencyDrop(input) {
174650
175002
  };
174651
175003
  }
174652
175004
 
175005
+ // ../plugin/src/hooks/magic-context/system-injection-stripper.ts
175006
+ var SYSTEM_INJECTION_MARKERS = [
175007
+ "<!-- OMO_INTERNAL_INITIATOR -->",
175008
+ "[SYSTEM DIRECTIVE: MAGIC-CONTEXT",
175009
+ "[SYSTEM DIRECTIVE: OH-MY-OPENCODE",
175010
+ "[Category+Skill Reminder]",
175011
+ "[EDIT ERROR - IMMEDIATE ACTION REQUIRED]",
175012
+ "[task CALL FAILED - IMMEDIATE RETRY REQUIRED]",
175013
+ "[EMERGENCY CONTEXT WINDOW WARNING]",
175014
+ "Unstable background agent appears idle",
175015
+ "**THE SUBAGENT JUST CLAIMED THIS TASK IS DONE."
175016
+ ];
175017
+ var SYSTEM_REMINDER_REGEX = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
175018
+ var OMO_MARKER_REGEX = /<!-- OMO_INTERNAL_INITIATOR -->/g;
175019
+ function stripSystemInjection(text) {
175020
+ let hasInjection = false;
175021
+ for (const marker of SYSTEM_INJECTION_MARKERS) {
175022
+ if (text.includes(marker)) {
175023
+ hasInjection = true;
175024
+ break;
175025
+ }
175026
+ }
175027
+ if (SYSTEM_REMINDER_REGEX.test(text))
175028
+ hasInjection = true;
175029
+ SYSTEM_REMINDER_REGEX.lastIndex = 0;
175030
+ if (!hasInjection)
175031
+ return null;
175032
+ let cleaned = text;
175033
+ cleaned = cleaned.replace(SYSTEM_REMINDER_REGEX, "");
175034
+ cleaned = cleaned.replace(OMO_MARKER_REGEX, "");
175035
+ cleaned = cleaned.replace(/\[SYSTEM DIRECTIVE: OH-MY-(?:OPENCODE|CLAUDE)[^\]]*\][\s\S]*?(?=\n\n(?!\s*[-*])|$)/g, "");
175036
+ for (const marker of SYSTEM_INJECTION_MARKERS) {
175037
+ if (marker.startsWith("<!-- ") || marker.startsWith("[SYSTEM DIRECTIVE"))
175038
+ continue;
175039
+ const idx = cleaned.indexOf(marker);
175040
+ if (idx === -1)
175041
+ continue;
175042
+ const blockEnd = cleaned.indexOf(`
175043
+
175044
+ `, idx + marker.length);
175045
+ cleaned = blockEnd !== -1 ? cleaned.slice(0, idx) + cleaned.slice(blockEnd) : cleaned.slice(0, idx);
175046
+ }
175047
+ return cleaned.trim();
175048
+ }
175049
+
174653
175050
  // src/heuristic-cleanup-pi.ts
174654
175051
  init_logger();
174655
175052
  var DEDUP_SAFE_TOOLS = new Set([
@@ -174808,10 +175205,14 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config2, pr
174808
175205
  if (!matched)
174809
175206
  continue;
174810
175207
  const target = targets.get(tag.tagNumber);
174811
- target?.drop?.();
175208
+ const result = target?.drop?.() ?? "absent";
175209
+ if (result === "incomplete")
175210
+ continue;
174812
175211
  updateTagDropMode(db, sessionId, tag.tagNumber, "full");
174813
175212
  updateTagStatus(db, sessionId, tag.tagNumber, "dropped");
174814
- droppedStaleReduceCalls++;
175213
+ if (result === "removed" || result === "truncated") {
175214
+ droppedStaleReduceCalls++;
175215
+ }
174815
175216
  }
174816
175217
  })();
174817
175218
  }
@@ -174883,7 +175284,9 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config2, pr
174883
175284
  continue;
174884
175285
  updateTagDropMode(db, sessionId, tag.tagNumber, "full");
174885
175286
  updateTagStatus(db, sessionId, tag.tagNumber, "dropped");
174886
- deduplicatedTools++;
175287
+ if (result === "removed" || result === "truncated") {
175288
+ deduplicatedTools++;
175289
+ }
174887
175290
  }
174888
175291
  }
174889
175292
  })();
@@ -174892,6 +175295,7 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config2, pr
174892
175295
  sessionLog(sessionId, `heuristic cleanup: dropped ${droppedTools} tool tags, stale ctx_reduce=${droppedStaleReduceCalls}, deduplicated ${deduplicatedTools} tool calls, dropped ${droppedInjections} system injections`);
174893
175296
  }
174894
175297
  let compressedTextTags = 0;
175298
+ let mutatedTextTags = 0;
174895
175299
  if (config2.caveman?.enabled) {
174896
175300
  const cavemanResult = applyCavemanCleanup(sessionId, db, targets, tags, {
174897
175301
  enabled: true,
@@ -174899,13 +175303,15 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config2, pr
174899
175303
  protectedTags: config2.protectedTags
174900
175304
  });
174901
175305
  compressedTextTags = cavemanResult.compressedToLite + cavemanResult.compressedToFull + cavemanResult.compressedToUltra;
175306
+ mutatedTextTags = cavemanResult.mutatedTextTags;
174902
175307
  }
174903
175308
  return {
174904
175309
  droppedTools,
174905
175310
  deduplicatedTools,
174906
175311
  droppedInjections,
174907
175312
  droppedStaleReduceCalls,
174908
- compressedTextTags
175313
+ compressedTextTags,
175314
+ mutatedTextTags
174909
175315
  };
174910
175316
  }
174911
175317
  function buildMessageIdToMaxTagFromTargets(targets) {
@@ -180282,9 +180688,7 @@ Context is managed for you entirely automatically — there's nothing to prune a
180282
180688
  var CTX_NOTE_GUIDANCE = `Use \`ctx_note\` ONLY for genuinely future concerns — something to revisit much later, not work coming up in the next few turns (that's already in your active context) and not active multi-step work (use todos for that). Magic Context preserves your full context across both compaction and restarts, so an upcoming restart or "let's come back to this later" is never a reason to take a note — nothing is lost either way. Notes you do take survive compression and resurface at natural work boundaries (after commits, historian runs, todo completion).`;
180283
180689
  var TOOL_HISTORY_GUIDANCE = `Compressed history intentionally omits tool calls and their outputs — summaries like "I edited file X" are historian records, not patterns to replicate. In the live conversation, older tool calls and their results are cleaned up to save context — you may see your own past messages referencing actions without the corresponding tool call or result visible. This is normal context management. ALWAYS use real tool calls; never simulate, fabricate, or inline tool outputs in your text. If there is no tool result message, the action did not happen. NEVER simulate, hallucinate or claim tool calls, command output, search results, file edits, or diffs in plain text as if they actually occurred.`;
180284
180690
  var BASE_INTRO = (protectedTags) => `Messages and tool outputs are tagged with §N§ identifiers (e.g., §1§, §42§).
180285
- Use \`ctx_reduce\` to manage context size. It supports one operation:
180286
- - \`drop\`: Remove entirely (best for tool outputs you already acted on).
180287
- Syntax: "3-5", "1,2,9", or "1-5,8,12-15". Last ${protectedTags} tags are protected.
180691
+ Use \`ctx_reduce\` to mark spent tagged content as discardable and reclaim space. Marking is NOT an immediate delete — it queues the content, which stays fully visible until space is actually needed (as soon as the next turn if you're already under pressure, much later if not), so mark a tool output as soon as you're done with it rather than hoarding the call for the end of the turn. The last ${protectedTags} tags are protected (marking one just queues it until it ages out). Syntax: "3-5", "1,2,9", or "1-5,8,12-15".
180288
180692
  Do not announce or narrate \`ctx_reduce\` drops — just call the tool silently. Saying "I'll drop these outputs" wastes tokens the user does not care about.
180289
180693
  ${CTX_NOTE_GUIDANCE}
180290
180694
  Use \`ctx_memory\` for durable project knowledge: write what future sessions must know, update/archive/merge the memories you see in \`<project-memory>\` when they drift. Memories persist across sessions and every new session starts with them.
@@ -180303,7 +180707,7 @@ Use \`ctx_expand\` to recover the raw conversation behind a \`<compartment>\` su
180303
180707
  \`ctx_search\` returns ranked results from memories, git commits, and raw message history. Use message ordinals from results with \`ctx_expand\` to retrieve surrounding conversation context.
180304
180708
  ${TOOL_HISTORY_GUIDANCE}
180305
180709
  NEVER drop large ranges blindly (e.g., "1-50"). Review each tag before deciding.
180306
- NEVER drop user messages — they are short and will be summarized by compartmentalization automatically. Dropping them loses context the historian needs.
180710
+ Keep your user's instructions and intent — never drop a user message for its directive, even an old one. But a large block of pasted content inside a user message (logs, data dumps, long code, attachments) is fair to mark discardable once you've extracted what you need — it stays searchable via \`ctx_search\`.
180307
180711
  NEVER drop assistant text messages unless they are exceptionally large. Your conversation messages are lightweight; only large tool outputs are worth dropping.
180308
180712
  Before your turn finishes, consider using \`ctx_reduce\` to drop large tool outputs you no longer need.`;
180309
180713
  var BASE_INTRO_NO_REDUCE = () => `${CTX_NOTE_GUIDANCE}
@@ -181780,6 +182184,7 @@ function registerPiContextHandler(pi, baseOptions) {
181780
182184
  const executeThresholdTokensPi = Math.round((usageContextLimit ?? 0) * resolvedExecuteThresholdPct / 100);
181781
182185
  const usableTokensPi = Math.max(0, executeThresholdTokensPi - usageInputTokens + liveTailTokens);
181782
182186
  resetLastNudgeCycleIfTailShrank(options.db, sessionId, tailToolTokens);
182187
+ const oldestReclaimableToolTags = getOldestActiveUnprotectedToolTags(options.db, sessionId, options.protectedTags ?? 20);
181783
182188
  setPiChannel1Baseline(sessionId, {
181784
182189
  tailToolTokens,
181785
182190
  historyBudgetTokens: historyBudgetTokens ?? 0,
@@ -181788,7 +182193,8 @@ function registerPiContextHandler(pi, baseOptions) {
181788
182193
  lastInputTokens: usageInputTokens,
181789
182194
  turnToolTokens: 0,
181790
182195
  usableTokens: usableTokensPi,
181791
- reducedSinceRefresh: false
182196
+ reducedSinceRefresh: false,
182197
+ oldestReclaimableToolTags
181792
182198
  });
181793
182199
  if (usageContextLimit && usageContextLimit > 0 && resolvedExecuteThresholdPct > 0) {
181794
182200
  const channel2ShouldTrigger = shouldTriggerChannel2({
@@ -181816,6 +182222,7 @@ function registerPiContextHandler(pi, baseOptions) {
181816
182222
  logTransformTiming(sessionId, "postTransformPhase", tPostTransform);
181817
182223
  sessionLog(sessionId, `transform completed in ${(performance.now() - transformStartTime).toFixed(1)}ms (${outputMessages.length} messages, ${result.targetCount} targets, watermark: ${result.reasoningWatermark})`);
181818
182224
  clearLastTransformErrorIfSet(options.db, sessionId);
182225
+ options.maybeAutoEmbedSession?.(sessionId, projectDirectory, projectIdentity);
181819
182226
  return { messages: outputMessages };
181820
182227
  } catch (err) {
181821
182228
  const message = err instanceof Error ? err.message : String(err);
@@ -182162,6 +182569,8 @@ async function runPipeline(args) {
182162
182569
  let historyWasConsumedThisPass = false;
182163
182570
  let materializationSatisfiedThisPass = false;
182164
182571
  let pendingOpsAppliedThisPass = false;
182572
+ let pendingOpsDidMutate = false;
182573
+ let heuristicOrReasoningDidMutate = false;
182165
182574
  let suppressDeferredHistoryDrain = false;
182166
182575
  let casLost = false;
182167
182576
  const deferredHistoryWasPendingAtPassStart = deferredHistoryRefreshSessions.has(args.sessionId);
@@ -182247,7 +182656,7 @@ async function runPipeline(args) {
182247
182656
  sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOps.length}, context=${args.contextUsage.percentage.toFixed(1)}%`);
182248
182657
  try {
182249
182658
  const tApplyPending = performance.now();
182250
- applyPendingOperations(args.sessionId, args.db, targets, args.protectedTags, undefined, pendingOps);
182659
+ pendingOpsDidMutate = applyPendingOperations(args.sessionId, args.db, targets, args.protectedTags, undefined, pendingOps);
182251
182660
  logTransformTiming(args.sessionId, "applyPendingOperations", tApplyPending);
182252
182661
  executedWorkThisPass = true;
182253
182662
  materializationSatisfiedThisPass = true;
@@ -182331,6 +182740,9 @@ async function runPipeline(args) {
182331
182740
  } : undefined,
182332
182741
  caveman: args.heuristics.caveman
182333
182742
  }, activeTags, stableIdResolver);
182743
+ const heuristicMutationCount = heuristicsResult.droppedTools + heuristicsResult.deduplicatedTools + heuristicsResult.droppedInjections + heuristicsResult.droppedStaleReduceCalls + heuristicsResult.mutatedTextTags;
182744
+ if (heuristicMutationCount > 0)
182745
+ heuristicOrReasoningDidMutate = true;
182334
182746
  heuristicsExecuted = true;
182335
182747
  executedWorkThisPass = true;
182336
182748
  if (hasPendingMaterializeSignal) {
@@ -182339,7 +182751,7 @@ async function runPipeline(args) {
182339
182751
  if (currentTurnId !== null) {
182340
182752
  lastHeuristicsTurnIdBySession.set(args.sessionId, currentTurnId);
182341
182753
  }
182342
- logTransformTiming(args.sessionId, "applyHeuristicCleanup", tHeuristic, `droppedTools=${heuristicsResult.droppedTools} deduplicatedTools=${heuristicsResult.deduplicatedTools} droppedInjections=${heuristicsResult.droppedInjections} compressedTextTags=${heuristicsResult.compressedTextTags}`);
182754
+ logTransformTiming(args.sessionId, "applyHeuristicCleanup", tHeuristic, `droppedTools=${heuristicsResult.droppedTools} deduplicatedTools=${heuristicsResult.deduplicatedTools} droppedInjections=${heuristicsResult.droppedInjections} staleReduce=${heuristicsResult.droppedStaleReduceCalls} compressedTextTags=${heuristicsResult.compressedTextTags} mutatedTextTags=${heuristicsResult.mutatedTextTags}`);
182343
182755
  } catch (err) {
182344
182756
  sessionLog(args.sessionId, `heuristic cleanup failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
182345
182757
  }
@@ -182376,6 +182788,9 @@ async function runPipeline(args) {
182376
182788
  }
182377
182789
  logTransformTiming(args.sessionId, "clearOldReasoning", tClearReasoning);
182378
182790
  logTransformTiming(args.sessionId, "watermarkCleanup", tClearReasoning);
182791
+ if (clearOutcome.cleared > 0 || stripOutcome.stripped > 0) {
182792
+ heuristicOrReasoningDidMutate = true;
182793
+ }
182379
182794
  if (combinedWatermark > prevWatermark || clearOutcome.cleared > 0 || stripOutcome.stripped > 0) {
182380
182795
  executedWorkThisPass = true;
182381
182796
  }
@@ -182383,7 +182798,32 @@ async function runPipeline(args) {
182383
182798
  sessionLog(args.sessionId, `reasoning clearing failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
182384
182799
  }
182385
182800
  }
182801
+ const toolReclaimExecutePass = args.schedulerDecision === "execute";
182802
+ const alreadyMutatingThisPass = pendingOpsDidMutate || heuristicOrReasoningDidMutate;
182803
+ const emergencyDropEligible = args.forceMaterialization === true || args.contextUsage.percentage >= FORCE_MATERIALIZATION_PERCENTAGE;
182804
+ let autoReclaimTargetCount = 0;
182805
+ let autoReclaimDidMutate = false;
182806
+ if (toolReclaimExecutePass && alreadyMutatingThisPass && !emergencyDropEligible) {
182807
+ const reclaimMeta = getOrCreateSessionMeta(args.db, args.sessionId);
182808
+ const syntheticPendingOps = buildSyntheticToolReclaimOps({
182809
+ db: args.db,
182810
+ sessionId: args.sessionId,
182811
+ targets,
182812
+ watermark: reclaimMeta.toolReclaimWatermark ?? 0,
182813
+ pendingOps
182814
+ });
182815
+ autoReclaimTargetCount = syntheticPendingOps.length;
182816
+ if (syntheticPendingOps.length > 0) {
182817
+ autoReclaimDidMutate = applyPendingOperations(args.sessionId, args.db, targets, args.protectedTags, undefined, [], syntheticPendingOps);
182818
+ }
182819
+ }
182386
182820
  transcript.commit();
182821
+ if (toolReclaimExecutePass) {
182822
+ advanceToolReclaimWatermarkToCurrentMax(args.db, args.sessionId);
182823
+ }
182824
+ if (autoReclaimTargetCount > 0) {
182825
+ sessionLog(args.sessionId, `tool reclaim auto-drop: targets=${autoReclaimTargetCount} mutated=${autoReclaimDidMutate}`);
182826
+ }
182387
182827
  const postCommitStableIdByRef = new Map;
182388
182828
  const postCommitEntryIdByRef = new Map;
182389
182829
  for (let i = 0;i < args.messages.length; i++) {
@@ -182667,6 +183107,7 @@ function clearContextHandlerSession(sessionId) {
182667
183107
  rawMessageProviderUnregistersBySession.delete(sessionId);
182668
183108
  }
182669
183109
  clearSessionTracking(sessionId);
183110
+ clearPiEmbedSessionState(sessionId);
182670
183111
  }
182671
183112
 
182672
183113
  // src/commands/ctx-flush.ts
@@ -185169,7 +185610,7 @@ function formatThresholdPercent(value) {
185169
185610
  // package.json
185170
185611
  var package_default = {
185171
185612
  name: "@cortexkit/pi-magic-context",
185172
- version: "0.24.1",
185613
+ version: "0.25.0",
185173
185614
  type: "module",
185174
185615
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
185175
185616
  main: "dist/index.js",
@@ -185782,9 +186223,184 @@ Older parts of this session are summarized into <compartment> blocks inside <ses
185782
186223
 
185783
186224
  ctx_expand(start=120, end=245) ← the compartment's own start/end attributes
185784
186225
 
185785
- Returns the raw transcript as [N] U:/A: lines, capped at ~15K tokens; an oversized range returns the head and tells you where to continue. Also works with ordinals from ctx_search message results — expand a window around a hit (e.g. start=N-10, end=N+5). Ranges after the last compartment are your live tail — already visible in context, not expandable.`;
186226
+ Returns the raw transcript as [N] U:/A: lines, capped at ~15K tokens; an oversized range returns the head and tells you where to continue. Also works with ordinals from ctx_search message results — expand a window around a hit (e.g. start=N-10, end=N+5). Ranges after the last compartment are your live tail — already visible in context, not expandable.
186227
+
186228
+ Two recovery modes for finer detail:
186229
+ - ctx_expand(start=120, end=245, verbose=true) — lists each message SEPARATELY with its ordinal [N] and a per-part preview (each tool call shown with its output size). Use this to find the exact message or tool call you want, then recover it in full by ordinal.
186230
+ - ctx_expand(message=138) — returns the FULL untruncated content of the message at that ordinal: every text part, and every tool call's complete input + output, read from stored history. This is the cheap way to get back a tool output you dropped with ctx_reduce — the original is still in storage even though the wire shows [dropped §N§]. If the message was deleted from history (session prune/revert), it says so.`;
185786
186231
  var CTX_EXPAND_TOKEN_BUDGET = 15000;
185787
186232
 
186233
+ // ../plugin/src/tools/ctx-expand/render.ts
186234
+ function isRecord3(value) {
186235
+ return value !== null && typeof value === "object" && !Array.isArray(value);
186236
+ }
186237
+ function roleLabel(role) {
186238
+ if (role === "assistant")
186239
+ return "A (assistant)";
186240
+ if (role === "user")
186241
+ return "U (user)";
186242
+ return role;
186243
+ }
186244
+ function truncate2(value, max) {
186245
+ const t = value.trim();
186246
+ return t.length <= max ? t : `${t.slice(0, max)}…`;
186247
+ }
186248
+ function keyArg(input) {
186249
+ if (!input)
186250
+ return "";
186251
+ for (const k of ["filePath", "path", "pattern", "query", "symbol", "module", "action"]) {
186252
+ const v = input[k];
186253
+ if (typeof v === "string" && v.length > 0)
186254
+ return truncate2(v, 60);
186255
+ }
186256
+ if (typeof input.description === "string")
186257
+ return truncate2(input.description, 60);
186258
+ return "";
186259
+ }
186260
+ function asToolPart(part) {
186261
+ const type = typeof part.type === "string" ? part.type : "";
186262
+ if (type === "tool") {
186263
+ const state = isRecord3(part.state) ? part.state : null;
186264
+ const output = state && typeof state.output === "string" ? state.output : state && state.output != null ? JSON.stringify(state.output) : null;
186265
+ const metadata = state && isRecord3(state.metadata) ? state.metadata : null;
186266
+ const title = state && typeof state.title === "string" && state.title || metadata && typeof metadata.title === "string" && metadata.title || null;
186267
+ return {
186268
+ name: typeof part.tool === "string" ? part.tool : "tool",
186269
+ callId: typeof part.callID === "string" ? part.callID : "",
186270
+ title,
186271
+ input: state && isRecord3(state.input) ? state.input : null,
186272
+ output
186273
+ };
186274
+ }
186275
+ if (type === "tool_use") {
186276
+ return {
186277
+ name: typeof part.name === "string" ? part.name : "tool",
186278
+ callId: typeof part.id === "string" ? part.id : "",
186279
+ title: null,
186280
+ input: isRecord3(part.input) ? part.input : null,
186281
+ output: null
186282
+ };
186283
+ }
186284
+ if (type === "tool_result") {
186285
+ const content = part.content;
186286
+ const output = typeof content === "string" ? content : content != null ? JSON.stringify(content) : null;
186287
+ return {
186288
+ name: "tool_result",
186289
+ callId: typeof part.tool_use_id === "string" ? part.tool_use_id : "",
186290
+ title: null,
186291
+ input: null,
186292
+ output
186293
+ };
186294
+ }
186295
+ return null;
186296
+ }
186297
+ function textOf(part) {
186298
+ if (part.type === "text" && typeof part.text === "string")
186299
+ return part.text;
186300
+ return null;
186301
+ }
186302
+ function reasoningOf(part) {
186303
+ if ((part.type === "reasoning" || part.type === "thinking") && typeof part.text === "string") {
186304
+ return part.text;
186305
+ }
186306
+ return null;
186307
+ }
186308
+ function renderPartPreview(part) {
186309
+ if (!isRecord3(part))
186310
+ return null;
186311
+ const text = textOf(part);
186312
+ if (text !== null) {
186313
+ const t = truncate2(text, 200);
186314
+ return t.length > 0 ? ` • ${t}` : null;
186315
+ }
186316
+ const tool = asToolPart(part);
186317
+ if (tool) {
186318
+ const arg = keyArg(tool.input);
186319
+ const head = arg ? `${tool.name}(${arg})` : tool.name;
186320
+ return tool.output !== null ? ` • tool ${head} → output ~${estimateTokens(tool.output)} tok` : ` • tool ${head}`;
186321
+ }
186322
+ const reasoning = reasoningOf(part);
186323
+ if (reasoning !== null)
186324
+ return ` • [reasoning] ${truncate2(reasoning, 120)}`;
186325
+ const type = typeof part.type === "string" ? part.type : "part";
186326
+ if (type === "file")
186327
+ return " • [file]";
186328
+ if (type === "step-start" || type === "step-finish")
186329
+ return null;
186330
+ return ` • [${type}]`;
186331
+ }
186332
+ function renderPartFull(part) {
186333
+ if (!isRecord3(part))
186334
+ return null;
186335
+ const text = textOf(part);
186336
+ if (text !== null) {
186337
+ return text.trim().length > 0 ? ` [text]
186338
+ ${text}` : null;
186339
+ }
186340
+ const tool = asToolPart(part);
186341
+ if (tool) {
186342
+ const lines = [];
186343
+ const idSuffix = tool.callId ? ` #${tool.callId}` : "";
186344
+ lines.push(` [tool: ${tool.name}${idSuffix}]`);
186345
+ if (tool.title && tool.title.trim().length > 0) {
186346
+ lines.push(` description: ${tool.title.trim()}`);
186347
+ }
186348
+ if (tool.input)
186349
+ lines.push(` input: ${JSON.stringify(tool.input)}`);
186350
+ if (tool.output !== null)
186351
+ lines.push(` output:
186352
+ ${tool.output}`);
186353
+ return lines.join(`
186354
+ `);
186355
+ }
186356
+ const type = typeof part.type === "string" ? part.type : "part";
186357
+ if (type === "file") {
186358
+ const name2 = typeof part.filename === "string" && part.filename || typeof part.url === "string" && part.url || "";
186359
+ return ` [file]${name2 ? ` ${name2}` : ""}`;
186360
+ }
186361
+ return null;
186362
+ }
186363
+ function renderMessageByOrdinal(sessionId, ordinal) {
186364
+ const msg = readRawSessionMessages(sessionId).find((m) => m.ordinal === ordinal);
186365
+ if (!msg) {
186366
+ return `No message at ordinal ${ordinal} in this session's stored history — it was deleted ` + `(session prune/revert) or the ordinal is wrong, so it can't be recovered. ` + `Re-run the tool if you still need the data.`;
186367
+ }
186368
+ const rendered = msg.parts.map(renderPartFull).filter((l) => l !== null);
186369
+ const lines = [`[${msg.ordinal}] ${roleLabel(msg.role)} — full recovery:`, ""];
186370
+ if (rendered.length === 0) {
186371
+ lines.push(" (no recoverable content — message had only structural/reasoning parts)");
186372
+ } else {
186373
+ lines.push(...rendered);
186374
+ }
186375
+ return lines.join(`
186376
+ `);
186377
+ }
186378
+ function renderVerboseRange(sessionId, start, end, tokenBudget) {
186379
+ const messages = readRawSessionMessages(sessionId).filter((m) => m.ordinal >= start && m.ordinal <= end);
186380
+ const out = [];
186381
+ let usedTokens = 0;
186382
+ let lastOrdinal = start - 1;
186383
+ let truncated = false;
186384
+ for (const msg of messages) {
186385
+ const header = `[${msg.ordinal}] ${roleLabel(msg.role)}`;
186386
+ const partLines = msg.parts.map(renderPartPreview).filter((l) => l !== null);
186387
+ const block = partLines.length > 0 ? `${header}
186388
+ ${partLines.join(`
186389
+ `)}` : header;
186390
+ const blockTokens = estimateTokens(block);
186391
+ if (usedTokens + blockTokens > tokenBudget && out.length > 0) {
186392
+ truncated = true;
186393
+ break;
186394
+ }
186395
+ out.push(block);
186396
+ usedTokens += blockTokens;
186397
+ lastOrdinal = msg.ordinal;
186398
+ }
186399
+ return { text: out.join(`
186400
+
186401
+ `), lastOrdinal, truncated };
186402
+ }
186403
+
185788
186404
  // ../../node_modules/.bun/typebox@1.1.38/node_modules/typebox/build/system/memory/memory.mjs
185789
186405
  var exports_memory = {};
185790
186406
  __export(exports_memory, {
@@ -189850,12 +190466,18 @@ __export(exports_typebox, {
189850
190466
  });
189851
190467
  // src/tools/ctx-expand.ts
189852
190468
  var ParamsSchema = exports_typebox.Object({
189853
- start: exports_typebox.Number({
190469
+ start: exports_typebox.Optional(exports_typebox.Number({
189854
190470
  description: "Start message ordinal (from compartment start attribute)"
189855
- }),
189856
- end: exports_typebox.Number({
190471
+ })),
190472
+ end: exports_typebox.Optional(exports_typebox.Number({
189857
190473
  description: "End message ordinal (from compartment end attribute)"
189858
- })
190474
+ })),
190475
+ verbose: exports_typebox.Optional(exports_typebox.Boolean({
190476
+ description: "With start/end: list each message separately with its ordinal [N] and per-part preview, so you can recover one in full by ordinal."
190477
+ })),
190478
+ message: exports_typebox.Optional(exports_typebox.Number({
190479
+ description: "Full untruncated recovery of ONE message by its ordinal (text + every tool call's full input/output). Recovers a tool output you dropped with ctx_reduce."
190480
+ }))
189859
190481
  });
189860
190482
  function ok(text) {
189861
190483
  return { content: [{ type: "text", text }], details: undefined };
@@ -189874,22 +190496,41 @@ function createCtxExpandTool(deps) {
189874
190496
  description: CTX_EXPAND_DESCRIPTION,
189875
190497
  parameters: ParamsSchema,
189876
190498
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
189877
- if (typeof params.start !== "number" || typeof params.end !== "number" || params.start < 1 || params.end < params.start) {
189878
- return err("Error: start and end must be positive integers with start <= end.");
189879
- }
189880
190499
  const sessionId = ctx.sessionManager.getSessionId();
189881
190500
  if (!sessionId) {
189882
190501
  return err("Error: no active Pi session.");
189883
190502
  }
189884
- const lastCompartmentEnd = getLastCompartmentEndMessage(deps.db, sessionId);
189885
- if (lastCompartmentEnd >= 0 && params.start > lastCompartmentEnd) {
189886
- return ok(`Range ${params.start}-${params.end} is entirely within the live tail (after the last compacted message ${lastCompartmentEnd}); those messages are already visible in context.`);
189887
- }
189888
- const effectiveEnd = lastCompartmentEnd >= 0 ? Math.min(params.end, lastCompartmentEnd) : params.end;
189889
190503
  const unregister = setRawMessageProvider(sessionId, {
189890
190504
  readMessages: () => readPiSessionMessages(ctx)
189891
190505
  });
189892
190506
  try {
190507
+ if (typeof params.message === "number" && params.message >= 1) {
190508
+ return ok(renderMessageByOrdinal(sessionId, params.message));
190509
+ }
190510
+ if (typeof params.start !== "number" || typeof params.end !== "number" || params.start < 1 || params.end < params.start) {
190511
+ return err("Error: provide either message=<ordinal>, or start and end (positive integers, start <= end).");
190512
+ }
190513
+ const lastCompartmentEnd = getLastCompartmentEndMessage(deps.db, sessionId);
190514
+ if (lastCompartmentEnd >= 0 && params.start > lastCompartmentEnd) {
190515
+ return ok(`Range ${params.start}-${params.end} is entirely within the live tail (after the last compacted message ${lastCompartmentEnd}); those messages are already visible in context.`);
190516
+ }
190517
+ const effectiveEnd = lastCompartmentEnd >= 0 ? Math.min(params.end, lastCompartmentEnd) : params.end;
190518
+ if (params.verbose === true) {
190519
+ const v = renderVerboseRange(sessionId, params.start, effectiveEnd, CTX_EXPAND_TOKEN_BUDGET);
190520
+ if (!v.text) {
190521
+ return ok(`No messages found in range ${params.start}-${effectiveEnd}. The range may be outside this session's history.`);
190522
+ }
190523
+ const out = [
190524
+ `Messages ${params.start}-${v.lastOrdinal} (verbose). Recover any one in full with ctx_expand(message=<ordinal>):`,
190525
+ "",
190526
+ v.text
190527
+ ];
190528
+ if (v.truncated) {
190529
+ out.push("", `Truncated at message ${v.lastOrdinal} (budget: ~${CTX_EXPAND_TOKEN_BUDGET} tokens). Call again with start=${v.lastOrdinal + 1} end=${effectiveEnd} verbose=true for more.`);
190530
+ }
190531
+ return ok(out.join(`
190532
+ `));
190533
+ }
189893
190534
  const chunk = readSessionChunk(sessionId, CTX_EXPAND_TOKEN_BUDGET, params.start, effectiveEnd + 1);
189894
190535
  if (!chunk.text || chunk.messageCount === 0) {
189895
190536
  return ok(`No messages found in range ${params.start}-${params.end}. The range may be outside this session's history.`);
@@ -190608,15 +191249,16 @@ function parseInteger(str) {
190608
191249
  }
190609
191250
 
190610
191251
  // ../plugin/src/tools/ctx-reduce/constants.ts
190611
- var CTX_REDUCE_DESCRIPTION = `Reduce context by dropping tagged content you no longer need.
190612
- Use §N§ identifiers visible in conversation. The \`drop\` param accepts ranges: "3-5", "1,2,9", "1-5,8".
191252
+ var CTX_REDUCE_DESCRIPTION = `Mark spent tagged content as discardable to reclaim context space. This is NOT an immediate delete. Use §N§ identifiers visible in the conversation. The \`drop\` param accepts ranges: "3-5", "1,2,9", "1-5,8".
191253
+
191254
+ How it works:
191255
+ - Marking QUEUES content for release. It stays fully visible to you until context space is actually needed — which may be as soon as the next turn if you are already under pressure, or many turns later if not. So mark spent outputs as soon as you finish with them; don't hoard the call for the end of the turn.
191256
+ - The newest tags are protected: marking one just queues it until it ages out of the recent window, so marking recent output is harmless.
191257
+ - When content is finally released it becomes a short placeholder, and re-running the tool is the only way to get it back. So mark only what you are genuinely DONE with — the test is "have I extracted what I need from this?", not "is it safe / do I have time before it drops?".
190613
191258
 
190614
- CRITICAL RULES:
190615
- - NEVER blanket-drop large ranges (e.g., "1-50"). Always review what each tag contains first.
190616
- - Only drop tool outputs you have already processed and no longer need.
190617
- - Protected tags are accepted but deferred until they leave the last protected range.
190618
- - Keep recent context — only reduce OLD content that is no longer relevant to current work.
190619
- - Dropped content is gone forever.`;
191259
+ Mark discardable once processed: large outputs you've summarized, repeated or redundant dumps, data written to disk, status/log output that only confirmed an expected state.
191260
+ Keep: user messages, unresolved errors, raw evidence you haven't extracted yet, and outputs whose exact wording may matter later.
191261
+ Never blanket-mark large ranges (e.g. "1-50") — review what each tag holds first.`;
190620
191262
 
190621
191263
  // src/tools/ctx-reduce.ts
190622
191264
  var ParamsSchema4 = exports_typebox.Object({
@@ -191217,7 +191859,21 @@ async function src_default2(pi) {
191217
191859
  },
191218
191860
  historian: hist,
191219
191861
  autoSearch: auto,
191220
- resolveForProject: resolveContextOptionsForProject
191862
+ resolveForProject: resolveContextOptionsForProject,
191863
+ maybeAutoEmbedSession: (sessionId, dir, identity) => {
191864
+ maybeAutoEmbedPiSession({
191865
+ db: database,
191866
+ projectDir: dir,
191867
+ projectIdentity: identity,
191868
+ memoryEnabled: cfg.memory.enabled
191869
+ }, sessionId, dir, identity, (text) => {
191870
+ pi.sendMessage({
191871
+ customType: "ctx-status",
191872
+ content: text,
191873
+ display: true
191874
+ }, { triggerTurn: false });
191875
+ });
191876
+ }
191221
191877
  });
191222
191878
  function resolveContextOptionsForProject(dir) {
191223
191879
  const cached2 = contextOptionsByDir.get(dir);
@@ -191299,14 +191955,14 @@ async function src_default2(pi) {
191299
191955
  onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity)
191300
191956
  });
191301
191957
  info("registered /ctx-dream");
191302
- registerCtxEmbedHistoryCommand(pi, {
191958
+ registerCtxEmbedCommand(pi, {
191303
191959
  db,
191304
191960
  projectDir,
191305
191961
  projectIdentity,
191306
191962
  memoryEnabled: config2.memory.enabled,
191307
191963
  resolveProject: resolveCurrentProject
191308
191964
  });
191309
- info("registered /ctx-embed-history");
191965
+ info("registered /ctx-embed");
191310
191966
  const dreamerConfig = resolveDreamerFromConfig(config2);
191311
191967
  if (dreamerConfig) {
191312
191968
  registerPiDreamerProject({