@wolfx/pi-magic-context 0.22.3 → 0.23.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.
@@ -151,62 +151,6 @@ var init_logger = __esm(() => {
151
151
  }
152
152
  });
153
153
 
154
- // ../plugin/src/shared/sqlite.ts
155
- function buildNodeSqliteDatabaseClass(DatabaseSync) {
156
- const SAVEPOINT = "mc_tx_sp";
157
-
158
- class NodeSqliteDatabase extends DatabaseSync {
159
- constructor(filename, options) {
160
- const translated = { ...options };
161
- if (options && "readonly" in options) {
162
- translated.readOnly = options.readonly;
163
- delete translated.readonly;
164
- }
165
- super(typeof filename === "string" ? filename : ":memory:", translated);
166
- }
167
- transaction(fn) {
168
- const self = this;
169
- const wrapped = function(...args) {
170
- const nested = self.isTransaction === true;
171
- self.exec(nested ? `SAVEPOINT ${SAVEPOINT}` : "BEGIN");
172
- try {
173
- const result = fn.apply(this, args);
174
- self.exec(nested ? `RELEASE ${SAVEPOINT}` : "COMMIT");
175
- return result;
176
- } catch (error) {
177
- if (nested) {
178
- self.exec(`ROLLBACK TO ${SAVEPOINT}`);
179
- self.exec(`RELEASE ${SAVEPOINT}`);
180
- } else {
181
- self.exec("ROLLBACK");
182
- }
183
- throw error;
184
- }
185
- };
186
- return wrapped;
187
- }
188
- }
189
- return NodeSqliteDatabase;
190
- }
191
- var isBun, bunSpec, nodeSpec, sqliteModule, DatabaseImpl, Database;
192
- var init_sqlite = __esm(async () => {
193
- isBun = typeof process !== "undefined" && typeof process.versions?.bun === "string";
194
- bunSpec = "bun:" + "sqlite";
195
- nodeSpec = "node:" + "sqlite";
196
- sqliteModule = isBun ? await import(bunSpec) : await import(nodeSpec);
197
- DatabaseImpl = isBun ? sqliteModule.Database : buildNodeSqliteDatabaseClass(sqliteModule.DatabaseSync);
198
- Database = DatabaseImpl;
199
- });
200
-
201
- // ../plugin/src/shared/sqlite-helpers.ts
202
- function closeQuietly(db) {
203
- if (!db)
204
- return;
205
- try {
206
- db.close();
207
- } catch {}
208
- }
209
-
210
154
  // ../../node_modules/.bun/esprima@4.0.1/node_modules/esprima/dist/esprima.js
211
155
  var require_esprima = __commonJS((exports, module) => {
212
156
  (function webpackUniversalModuleDefinition(root, factory) {
@@ -7964,53 +7908,6 @@ var require_src2 = __commonJS((exports, module) => {
7964
7908
  };
7965
7909
  });
7966
7910
 
7967
- // ../plugin/src/hooks/magic-context/read-session-db.ts
7968
- import { existsSync as existsSync6 } from "node:fs";
7969
- import { join as join7 } from "node:path";
7970
- function getOpenCodeDbPath() {
7971
- return join7(getDataDir(), "opencode", "opencode.db");
7972
- }
7973
- function openCodeDbExists() {
7974
- return existsSync6(getOpenCodeDbPath());
7975
- }
7976
- function closeCachedReadOnlyDb() {
7977
- if (!cachedReadOnlyDb) {
7978
- return;
7979
- }
7980
- try {
7981
- closeQuietly(cachedReadOnlyDb.db);
7982
- } catch (error51) {
7983
- log("[magic-context] failed to close cached OpenCode read-only DB:", error51);
7984
- } finally {
7985
- cachedReadOnlyDb = null;
7986
- }
7987
- }
7988
- function getReadOnlySessionDb() {
7989
- const dbPath = getOpenCodeDbPath();
7990
- if (cachedReadOnlyDb?.path === dbPath) {
7991
- return cachedReadOnlyDb.db;
7992
- }
7993
- closeCachedReadOnlyDb();
7994
- const db = new Database(dbPath, { readonly: true });
7995
- cachedReadOnlyDb = { path: dbPath, db };
7996
- return db;
7997
- }
7998
- function withReadOnlySessionDb(fn) {
7999
- return fn(getReadOnlySessionDb());
8000
- }
8001
- function getRawSessionMessageCountFromDb(db, sessionId) {
8002
- const row = db.prepare(`SELECT COUNT(*) as count FROM message WHERE session_id = ?
8003
- AND NOT (COALESCE(json_extract(data, '$.summary'), 0) = 1
8004
- AND COALESCE(json_extract(data, '$.finish'), '') = 'stop')`).get(sessionId);
8005
- return typeof row?.count === "number" ? row.count : 0;
8006
- }
8007
- var cachedReadOnlyDb = null;
8008
- var init_read_session_db = __esm(async () => {
8009
- init_data_path();
8010
- init_logger();
8011
- await init_sqlite();
8012
- });
8013
-
8014
7911
  // ../plugin/src/features/magic-context/storage-db.ts
8015
7912
  init_data_path();
8016
7913
  import { copyFileSync, cpSync, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "node:fs";
@@ -8094,7 +7991,59 @@ function safeString(value) {
8094
7991
 
8095
7992
  // ../plugin/src/features/magic-context/storage-db.ts
8096
7993
  init_logger();
8097
- await init_sqlite();
7994
+
7995
+ // ../plugin/src/shared/sqlite.ts
7996
+ var isBun = typeof process !== "undefined" && typeof process.versions?.bun === "string";
7997
+ var bunSpec = "bun:" + "sqlite";
7998
+ var nodeSpec = "node:" + "sqlite";
7999
+ var sqliteModule = isBun ? await import(bunSpec) : await import(nodeSpec);
8000
+ var DatabaseImpl = isBun ? sqliteModule.Database : buildNodeSqliteDatabaseClass(sqliteModule.DatabaseSync);
8001
+ function buildNodeSqliteDatabaseClass(DatabaseSync) {
8002
+ const SAVEPOINT = "mc_tx_sp";
8003
+
8004
+ class NodeSqliteDatabase extends DatabaseSync {
8005
+ constructor(filename, options) {
8006
+ const translated = { ...options };
8007
+ if (options && "readonly" in options) {
8008
+ translated.readOnly = options.readonly;
8009
+ delete translated.readonly;
8010
+ }
8011
+ super(typeof filename === "string" ? filename : ":memory:", translated);
8012
+ }
8013
+ transaction(fn) {
8014
+ const self = this;
8015
+ const wrapped = function(...args) {
8016
+ const nested = self.isTransaction === true;
8017
+ self.exec(nested ? `SAVEPOINT ${SAVEPOINT}` : "BEGIN");
8018
+ try {
8019
+ const result = fn.apply(this, args);
8020
+ self.exec(nested ? `RELEASE ${SAVEPOINT}` : "COMMIT");
8021
+ return result;
8022
+ } catch (error) {
8023
+ if (nested) {
8024
+ self.exec(`ROLLBACK TO ${SAVEPOINT}`);
8025
+ self.exec(`RELEASE ${SAVEPOINT}`);
8026
+ } else {
8027
+ self.exec("ROLLBACK");
8028
+ }
8029
+ throw error;
8030
+ }
8031
+ };
8032
+ return wrapped;
8033
+ }
8034
+ }
8035
+ return NodeSqliteDatabase;
8036
+ }
8037
+ var Database = DatabaseImpl;
8038
+
8039
+ // ../plugin/src/shared/sqlite-helpers.ts
8040
+ function closeQuietly(db) {
8041
+ if (!db)
8042
+ return;
8043
+ try {
8044
+ db.close();
8045
+ } catch {}
8046
+ }
8098
8047
 
8099
8048
  // ../plugin/src/features/magic-context/key-files/project-key-files.ts
8100
8049
  init_logger();
@@ -8948,6 +8897,72 @@ var MIGRATIONS = [
8948
8897
  db.exec("ALTER TABLE notes ADD COLUMN anchor_ordinal INTEGER");
8949
8898
  }
8950
8899
  }
8900
+ },
8901
+ {
8902
+ version: 30,
8903
+ description: "HARD-bust m[0] markers: cached system/tool-set/model identity",
8904
+ up: (db) => {
8905
+ const hasSessionMeta = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_meta' LIMIT 1").get();
8906
+ if (!hasSessionMeta)
8907
+ return;
8908
+ ensureColumn(db, "session_meta", "cached_m0_system_hash", "TEXT");
8909
+ ensureColumn(db, "session_meta", "cached_m0_tool_set_hash", "TEXT");
8910
+ ensureColumn(db, "session_meta", "cached_m0_model_key", "TEXT");
8911
+ const columns = new Set(db.prepare("PRAGMA table_info(session_meta)").all().map((column) => column.name));
8912
+ if (columns.has("cached_m0_bytes")) {
8913
+ db.prepare(`UPDATE session_meta SET
8914
+ cached_m0_bytes = NULL,
8915
+ cached_m1_bytes = NULL,
8916
+ cached_m0_materialized_at = NULL,
8917
+ cached_m0_system_hash = NULL,
8918
+ cached_m0_tool_set_hash = NULL,
8919
+ cached_m0_model_key = NULL`).run();
8920
+ }
8921
+ }
8922
+ },
8923
+ {
8924
+ version: 31,
8925
+ description: "Nudge redesign: Channel 1 cadence (last_nudge_undropped) + Channel 2 ceiling lease " + "(channel2_nudge_state); zero legacy ctx_reduce-nudge sticky/anchor state (startup heal)",
8926
+ up: (db) => {
8927
+ const hasSessionMeta = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_meta' LIMIT 1").get();
8928
+ if (!hasSessionMeta)
8929
+ return;
8930
+ ensureColumn(db, "session_meta", "last_nudge_undropped", "INTEGER DEFAULT 0");
8931
+ ensureColumn(db, "session_meta", "channel2_nudge_state", "TEXT DEFAULT ''");
8932
+ const columns = new Set(db.prepare("PRAGMA table_info(session_meta)").all().map((column) => column.name));
8933
+ if (columns.has("sticky_turn_reminder_text")) {
8934
+ db.prepare(`UPDATE session_meta SET
8935
+ sticky_turn_reminder_text = '',
8936
+ sticky_turn_reminder_message_id = '',
8937
+ nudge_anchor_message_id = '',
8938
+ nudge_anchor_text = ''`).run();
8939
+ }
8940
+ }
8941
+ },
8942
+ {
8943
+ version: 32,
8944
+ description: "Protected tail boundary state, usage resolver fields, recovery escape, and drain quota",
8945
+ up: (db) => {
8946
+ const hasSessionMeta = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_meta' LIMIT 1").get();
8947
+ if (!hasSessionMeta)
8948
+ return;
8949
+ ensureColumn(db, "session_meta", "prior_boundary_ordinal", "INTEGER NOT NULL DEFAULT 1");
8950
+ ensureColumn(db, "session_meta", "protected_tail_policy_version", "INTEGER NOT NULL DEFAULT 0");
8951
+ ensureColumn(db, "session_meta", "protected_tail_drain_window_started_at", "INTEGER NOT NULL DEFAULT 0");
8952
+ ensureColumn(db, "session_meta", "protected_tail_drain_tokens", "INTEGER NOT NULL DEFAULT 0");
8953
+ ensureColumn(db, "session_meta", "recovery_no_eligible_head_count", "INTEGER NOT NULL DEFAULT 0");
8954
+ ensureColumn(db, "session_meta", "force_emergency_bypass_window_start", "INTEGER NOT NULL DEFAULT 0");
8955
+ ensureColumn(db, "session_meta", "force_emergency_bypass_used", "INTEGER NOT NULL DEFAULT 0");
8956
+ ensureColumn(db, "session_meta", "last_usage_context_limit", "INTEGER NOT NULL DEFAULT 0");
8957
+ db.prepare("UPDATE session_meta SET prior_boundary_ordinal = 1 WHERE prior_boundary_ordinal IS NULL OR prior_boundary_ordinal < 1").run();
8958
+ db.prepare("UPDATE session_meta SET protected_tail_policy_version = 0 WHERE protected_tail_policy_version IS NULL").run();
8959
+ db.prepare("UPDATE session_meta SET protected_tail_drain_window_started_at = 0 WHERE protected_tail_drain_window_started_at IS NULL").run();
8960
+ db.prepare("UPDATE session_meta SET protected_tail_drain_tokens = 0 WHERE protected_tail_drain_tokens IS NULL").run();
8961
+ db.prepare("UPDATE session_meta SET recovery_no_eligible_head_count = 0 WHERE recovery_no_eligible_head_count IS NULL").run();
8962
+ db.prepare("UPDATE session_meta SET force_emergency_bypass_window_start = 0 WHERE force_emergency_bypass_window_start IS NULL").run();
8963
+ db.prepare("UPDATE session_meta SET force_emergency_bypass_used = 0 WHERE force_emergency_bypass_used IS NULL").run();
8964
+ db.prepare("UPDATE session_meta SET last_usage_context_limit = 0 WHERE last_usage_context_limit IS NULL").run();
8965
+ }
8951
8966
  }
8952
8967
  ];
8953
8968
  var LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
@@ -141553,7 +141568,7 @@ var tokenizer = new src_default(exports_claude);
141553
141568
  function estimateTokens(text) {
141554
141569
  if (!text)
141555
141570
  return 0;
141556
- return tokenizer.count(text);
141571
+ return tokenizer.encode(text, "all").length;
141557
141572
  }
141558
141573
  function normalizeText(text) {
141559
141574
  return text.replace(/\s+/g, " ").trim();
@@ -141780,6 +141795,11 @@ function markSessionCompleted(db, sessionId, now) {
141780
141795
  SET status = 'completed', completed_at = ?, lease_expires_at = NULL, last_error = NULL
141781
141796
  WHERE session_id = ?`).run(now, sessionId);
141782
141797
  }
141798
+ function markSessionPendingRetry(db, sessionId) {
141799
+ db.prepare(`UPDATE tool_owner_backfill_state
141800
+ SET status = 'pending', completed_at = NULL, lease_expires_at = NULL, last_error = NULL
141801
+ WHERE session_id = ?`).run(sessionId);
141802
+ }
141783
141803
  function markSessionSkipped(db, sessionId, now, reason) {
141784
141804
  db.prepare(`INSERT INTO tool_owner_backfill_state(session_id, status, completed_at, last_error)
141785
141805
  VALUES (?, 'skipped', ?, ?)
@@ -141856,12 +141876,18 @@ function applyOwnersForSession(db, sessionId, ownersByCallId) {
141856
141876
  const updateRowStmt = db.prepare(`UPDATE tags
141857
141877
  SET tool_owner_message_id = ?
141858
141878
  WHERE id = ? AND tool_owner_message_id IS NULL`);
141879
+ const existingOwnerStmt = db.prepare(`SELECT 1 AS hit FROM tags
141880
+ WHERE session_id = ? AND message_id = ? AND type = 'tool'
141881
+ AND tool_owner_message_id = ?
141882
+ LIMIT 1`);
141859
141883
  let rowsUpdated = 0;
141860
141884
  db.transaction(() => {
141861
141885
  for (const [callId, ownerId] of ownersByCallId) {
141862
141886
  const orphan = findOrphanStmt.get(sessionId, callId);
141863
141887
  if (!orphan)
141864
141888
  continue;
141889
+ if (existingOwnerStmt.get(sessionId, callId, ownerId))
141890
+ continue;
141865
141891
  const result = updateRowStmt.run(ownerId, orphan.id);
141866
141892
  rowsUpdated += result.changes ?? 0;
141867
141893
  }
@@ -141890,6 +141916,8 @@ function backfillToolOwnersInChunks(db, result) {
141890
141916
  if (owners.size === 0) {
141891
141917
  markSessionSkipped(db, sessionId, Date.now(), "no_oc_matches");
141892
141918
  result.sessionsSkippedNoMatches += 1;
141919
+ } else if (rowsLeftNull > 0) {
141920
+ markSessionPendingRetry(db, sessionId);
141893
141921
  } else {
141894
141922
  markSessionCompleted(db, sessionId, Date.now());
141895
141923
  result.sessionsCompleted += 1;
@@ -141912,7 +141940,7 @@ var databases = new Map;
141912
141940
  var persistenceByDatabase = new WeakMap;
141913
141941
  var persistenceErrorByDatabase = new WeakMap;
141914
141942
  var lastSchemaFenceRejection = null;
141915
- var LATEST_SUPPORTED_VERSION = 29;
141943
+ var LATEST_SUPPORTED_VERSION = 32;
141916
141944
  function resolveDatabasePath(dbPathOverride) {
141917
141945
  if (dbPathOverride) {
141918
141946
  return { dbDir: dirname2(dbPathOverride), dbPath: dbPathOverride };
@@ -142014,9 +142042,9 @@ function runSqliteOptimize(db) {
142014
142042
  } catch {}
142015
142043
  }
142016
142044
  function initializeDatabase(db) {
142045
+ db.exec("PRAGMA busy_timeout=5000");
142017
142046
  db.exec("PRAGMA foreign_keys=ON");
142018
142047
  db.exec("PRAGMA journal_mode=WAL");
142019
- db.exec("PRAGMA busy_timeout=5000");
142020
142048
  applySqliteTuningPragmas(db);
142021
142049
  db.exec(`
142022
142050
  CREATE TABLE IF NOT EXISTS tags (
@@ -142029,6 +142057,9 @@ function initializeDatabase(db) {
142029
142057
  tag_number INTEGER,
142030
142058
  harness TEXT NOT NULL DEFAULT 'opencode',
142031
142059
  entry_fingerprint TEXT,
142060
+ token_count INTEGER,
142061
+ input_token_count INTEGER,
142062
+ reasoning_token_count INTEGER,
142032
142063
  UNIQUE(session_id, tag_number)
142033
142064
  );
142034
142065
 
@@ -142318,6 +142349,11 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142318
142349
  counter INTEGER DEFAULT 0,
142319
142350
  last_nudge_tokens INTEGER DEFAULT 0,
142320
142351
  last_nudge_band TEXT DEFAULT '',
142352
+ last_nudge_undropped INTEGER DEFAULT 0,
142353
+ last_nudge_level TEXT DEFAULT '',
142354
+ channel2_nudge_state TEXT DEFAULT '',
142355
+ channel2_nudge_claimed_at INTEGER DEFAULT 0,
142356
+ last_emergency_input_sample INTEGER DEFAULT 0,
142321
142357
  last_transform_error TEXT DEFAULT '',
142322
142358
  nudge_anchor_message_id TEXT DEFAULT '',
142323
142359
  nudge_anchor_text TEXT DEFAULT '',
@@ -142374,9 +142410,20 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142374
142410
  cached_m0_project_docs_hash TEXT,
142375
142411
  cached_m1_bytes BLOB,
142376
142412
  last_observed_model_key TEXT,
142413
+ last_usage_context_limit INTEGER NOT NULL DEFAULT 0,
142414
+ prior_boundary_ordinal INTEGER NOT NULL DEFAULT 1,
142415
+ protected_tail_policy_version INTEGER NOT NULL DEFAULT 0,
142416
+ protected_tail_drain_window_started_at INTEGER NOT NULL DEFAULT 0,
142417
+ protected_tail_drain_tokens INTEGER NOT NULL DEFAULT 0,
142418
+ recovery_no_eligible_head_count INTEGER NOT NULL DEFAULT 0,
142419
+ force_emergency_bypass_window_start INTEGER NOT NULL DEFAULT 0,
142420
+ force_emergency_bypass_used INTEGER NOT NULL DEFAULT 0,
142377
142421
  cached_m0_materialized_at INTEGER,
142378
142422
  cached_m0_session_facts_version INTEGER,
142379
142423
  cached_m0_upgrade_state TEXT,
142424
+ cached_m0_system_hash TEXT,
142425
+ cached_m0_tool_set_hash TEXT,
142426
+ cached_m0_model_key TEXT,
142380
142427
  cached_m0_last_baseline_end_message_id TEXT,
142381
142428
  upgrade_reminded_at INTEGER,
142382
142429
  pi_stable_id_scheme INTEGER
@@ -142481,6 +142528,11 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142481
142528
  CREATE INDEX IF NOT EXISTS idx_message_history_index_updated_at ON message_history_index(updated_at);
142482
142529
  `);
142483
142530
  ensureColumn(db, "session_meta", "last_nudge_band", "TEXT DEFAULT ''");
142531
+ ensureColumn(db, "session_meta", "last_nudge_undropped", "INTEGER DEFAULT 0");
142532
+ ensureColumn(db, "session_meta", "last_nudge_level", "TEXT DEFAULT ''");
142533
+ ensureColumn(db, "session_meta", "channel2_nudge_state", "TEXT DEFAULT ''");
142534
+ ensureColumn(db, "session_meta", "channel2_nudge_claimed_at", "INTEGER DEFAULT 0");
142535
+ ensureColumn(db, "session_meta", "last_emergency_input_sample", "INTEGER DEFAULT 0");
142484
142536
  ensureColumn(db, "session_meta", "last_transform_error", "TEXT DEFAULT ''");
142485
142537
  ensureColumn(db, "session_meta", "nudge_anchor_message_id", "TEXT DEFAULT ''");
142486
142538
  ensureColumn(db, "session_meta", "nudge_anchor_text", "TEXT DEFAULT ''");
@@ -142507,6 +142559,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142507
142559
  ensureColumn(db, "session_meta", "system_prompt_hash", "TEXT DEFAULT ''");
142508
142560
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
142509
142561
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
142562
+ ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
142510
142563
  ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
142511
142564
  ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
142512
142565
  ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
@@ -142525,6 +142578,9 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142525
142578
  db.exec(`CREATE INDEX IF NOT EXISTS idx_tags_pi_adopt
142526
142579
  ON tags(session_id, entry_fingerprint)
142527
142580
  WHERE type='message' AND entry_fingerprint IS NOT NULL`);
142581
+ ensureColumn(db, "tags", "token_count", "INTEGER");
142582
+ ensureColumn(db, "tags", "input_token_count", "INTEGER");
142583
+ ensureColumn(db, "tags", "reasoning_token_count", "INTEGER");
142528
142584
  ensureColumn(db, "session_meta", "system_prompt_tokens", "INTEGER DEFAULT 0");
142529
142585
  ensureColumn(db, "session_meta", "compaction_marker_state", "TEXT DEFAULT ''");
142530
142586
  ensureColumn(db, "session_meta", "key_files", "TEXT DEFAULT ''");
@@ -142565,9 +142621,20 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
142565
142621
  ensureColumn(db, "session_meta", "cached_m0_project_docs_hash", "TEXT");
142566
142622
  ensureColumn(db, "session_meta", "cached_m1_bytes", "BLOB");
142567
142623
  ensureColumn(db, "session_meta", "last_observed_model_key", "TEXT");
142624
+ ensureColumn(db, "session_meta", "last_usage_context_limit", "INTEGER NOT NULL DEFAULT 0");
142625
+ ensureColumn(db, "session_meta", "prior_boundary_ordinal", "INTEGER NOT NULL DEFAULT 1");
142626
+ ensureColumn(db, "session_meta", "protected_tail_policy_version", "INTEGER NOT NULL DEFAULT 0");
142627
+ ensureColumn(db, "session_meta", "protected_tail_drain_window_started_at", "INTEGER NOT NULL DEFAULT 0");
142628
+ ensureColumn(db, "session_meta", "protected_tail_drain_tokens", "INTEGER NOT NULL DEFAULT 0");
142629
+ ensureColumn(db, "session_meta", "recovery_no_eligible_head_count", "INTEGER NOT NULL DEFAULT 0");
142630
+ ensureColumn(db, "session_meta", "force_emergency_bypass_window_start", "INTEGER NOT NULL DEFAULT 0");
142631
+ ensureColumn(db, "session_meta", "force_emergency_bypass_used", "INTEGER NOT NULL DEFAULT 0");
142568
142632
  ensureColumn(db, "session_meta", "cached_m0_materialized_at", "INTEGER");
142569
142633
  ensureColumn(db, "session_meta", "cached_m0_session_facts_version", "INTEGER");
142570
142634
  ensureColumn(db, "session_meta", "cached_m0_upgrade_state", "TEXT");
142635
+ ensureColumn(db, "session_meta", "cached_m0_system_hash", "TEXT");
142636
+ ensureColumn(db, "session_meta", "cached_m0_tool_set_hash", "TEXT");
142637
+ ensureColumn(db, "session_meta", "cached_m0_model_key", "TEXT");
142571
142638
  ensureColumn(db, "session_meta", "cached_m0_last_baseline_end_message_id", "TEXT");
142572
142639
  ensureColumn(db, "session_meta", "upgrade_reminded_at", "INTEGER");
142573
142640
  db.exec(`
@@ -142631,6 +142698,13 @@ function healAllNullColumns(db) {
142631
142698
  healNullIntegerColumns(db);
142632
142699
  healMissingMemoryBlockIds(db);
142633
142700
  }
142701
+ var CHANNEL2_CLAIM_TTL_MS = 120000;
142702
+ function healWedgedChannel2Claims(db) {
142703
+ try {
142704
+ const staleBefore = Date.now() - CHANNEL2_CLAIM_TTL_MS;
142705
+ db.prepare("UPDATE session_meta SET channel2_nudge_state = 'pending', channel2_nudge_claimed_at = 0 WHERE channel2_nudge_state = 'claimed' AND (channel2_nudge_claimed_at IS NULL OR channel2_nudge_claimed_at = 0 OR channel2_nudge_claimed_at <= ?)").run(staleBefore);
142706
+ } catch {}
142707
+ }
142634
142708
  function healMissingMemoryBlockIds(db) {
142635
142709
  try {
142636
142710
  db.prepare("UPDATE session_meta SET memory_block_cache = '' WHERE memory_block_cache != '' AND (memory_block_ids IS NULL OR memory_block_ids = '') AND memory_block_count > 0").run();
@@ -142640,6 +142714,7 @@ function healNullTextColumns(db) {
142640
142714
  const columns = [
142641
142715
  ["cache_ttl", ""],
142642
142716
  ["last_nudge_band", ""],
142717
+ ["last_nudge_level", ""],
142643
142718
  ["last_transform_error", ""],
142644
142719
  ["nudge_anchor_message_id", ""],
142645
142720
  ["nudge_anchor_text", ""],
@@ -142654,6 +142729,7 @@ function healNullTextColumns(db) {
142654
142729
  ["todo_synthetic_state_json", ""],
142655
142730
  ["system_prompt_hash", ""],
142656
142731
  ["stripped_placeholder_ids", ""],
142732
+ ["stale_reduce_stripped_ids", ""],
142657
142733
  ["memory_block_cache", ""],
142658
142734
  ["memory_block_ids", ""],
142659
142735
  ["compaction_marker_state", ""],
@@ -142679,7 +142755,17 @@ function healNullIntegerColumns(db) {
142679
142755
  ["observed_safe_input_tokens", 0],
142680
142756
  ["cache_alert_sent", 0],
142681
142757
  ["new_work_tokens", 0],
142682
- ["total_input_tokens", 0]
142758
+ ["total_input_tokens", 0],
142759
+ ["last_emergency_input_sample", 0],
142760
+ ["channel2_nudge_claimed_at", 0],
142761
+ ["last_usage_context_limit", 0],
142762
+ ["prior_boundary_ordinal", 1],
142763
+ ["protected_tail_policy_version", 0],
142764
+ ["protected_tail_drain_window_started_at", 0],
142765
+ ["protected_tail_drain_tokens", 0],
142766
+ ["recovery_no_eligible_head_count", 0],
142767
+ ["force_emergency_bypass_window_start", 0],
142768
+ ["force_emergency_bypass_used", 0]
142683
142769
  ];
142684
142770
  for (const [column, fallback] of columns) {
142685
142771
  try {
@@ -142718,6 +142804,7 @@ function openDatabase(dbPathOrOptions) {
142718
142804
  if (!persistenceByDatabase.has(existing)) {
142719
142805
  persistenceByDatabase.set(existing, true);
142720
142806
  }
142807
+ healWedgedChannel2Claims(existing);
142721
142808
  return existing;
142722
142809
  }
142723
142810
  try {
@@ -142743,6 +142830,7 @@ function openDatabase(dbPathOrOptions) {
142743
142830
  log(`[magic-context] key-files orphan GC failed: ${getErrorMessage(error)}`);
142744
142831
  }
142745
142832
  }
142833
+ healWedgedChannel2Claims(db);
142746
142834
  if (!explicitDbPath) {
142747
142835
  try {
142748
142836
  runToolOwnerBackfill(db);
@@ -142766,6 +142854,31 @@ function openDatabase(dbPathOrOptions) {
142766
142854
  // src/subagent-entry.ts
142767
142855
  init_logger();
142768
142856
 
142857
+ // ../plugin/src/config/prune-config-leaf.ts
142858
+ function isPlainObject(value) {
142859
+ return typeof value === "object" && value !== null && !Array.isArray(value);
142860
+ }
142861
+ function pruneNestedConfigLeaf(block, relativePath) {
142862
+ if (relativePath.length === 0)
142863
+ return null;
142864
+ const result = { ...block };
142865
+ let cursor = result;
142866
+ for (let i = 0;i < relativePath.length - 1; i++) {
142867
+ const seg = String(relativePath[i]);
142868
+ const child = cursor[seg];
142869
+ if (!isPlainObject(child))
142870
+ return null;
142871
+ const clonedChild = { ...child };
142872
+ cursor[seg] = clonedChild;
142873
+ cursor = clonedChild;
142874
+ }
142875
+ const leaf = String(relativePath[relativePath.length - 1]);
142876
+ if (!(leaf in cursor))
142877
+ return null;
142878
+ delete cursor[leaf];
142879
+ return { block: result, removed: relativePath.map(String).join(".") };
142880
+ }
142881
+
142769
142882
  // src/config/index.ts
142770
142883
  import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
142771
142884
  import { homedir as homedir3 } from "node:os";
@@ -142911,6 +143024,74 @@ function migrateLegacyExperimental(rawConfig, warnings) {
142911
143024
  return patched;
142912
143025
  }
142913
143026
 
143027
+ // ../plugin/src/config/project-security.ts
143028
+ var HIDDEN_AGENT_KEYS = ["historian", "dreamer", "sidekick"];
143029
+ var AGENT_ESCALATION_FIELDS = ["prompt", "permission", "tools", "system_prompt"];
143030
+ function isPlainObject2(value) {
143031
+ return typeof value === "object" && value !== null && !Array.isArray(value);
143032
+ }
143033
+ function stripUnsafeProjectConfigFields(projectRaw) {
143034
+ const warnings = [];
143035
+ if ("auto_update" in projectRaw) {
143036
+ delete projectRaw.auto_update;
143037
+ warnings.push("Ignoring auto_update from project config (security: this setting only honors user-level config).");
143038
+ }
143039
+ if ("sqlite" in projectRaw) {
143040
+ delete projectRaw.sqlite;
143041
+ warnings.push("Ignoring sqlite.* from project config (security: SQLite cache/mmap PRAGMAs apply to the " + "process-global shared database handle; only user-level config may set them).");
143042
+ }
143043
+ for (const agentKey of HIDDEN_AGENT_KEYS) {
143044
+ const block = projectRaw[agentKey];
143045
+ if (!isPlainObject2(block))
143046
+ continue;
143047
+ const removed = [];
143048
+ for (const field of AGENT_ESCALATION_FIELDS) {
143049
+ if (field in block) {
143050
+ delete block[field];
143051
+ removed.push(field);
143052
+ }
143053
+ }
143054
+ if (removed.length > 0) {
143055
+ warnings.push(`Ignoring ${agentKey}.${removed.join("/")} from project config ` + "(security: a repository cannot reprogram or re-permission hidden agents).");
143056
+ }
143057
+ }
143058
+ return warnings;
143059
+ }
143060
+ function normalizeEndpoint(value) {
143061
+ if (typeof value !== "string")
143062
+ return;
143063
+ const trimmed = value.trim().replace(/\/+$/, "");
143064
+ return trimmed.length > 0 ? trimmed.toLowerCase() : undefined;
143065
+ }
143066
+ function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
143067
+ const projectEmbedding = projectRaw.embedding;
143068
+ if (!isPlainObject2(projectEmbedding))
143069
+ return [];
143070
+ const redirectsEndpoint = "endpoint" in projectEmbedding;
143071
+ if (!redirectsEndpoint)
143072
+ return [];
143073
+ const userEmbedding = userRaw?.embedding;
143074
+ if (isPlainObject2(userEmbedding)) {
143075
+ const projectEndpoint = normalizeEndpoint(projectEmbedding.endpoint);
143076
+ const userEndpoint = normalizeEndpoint(userEmbedding.endpoint);
143077
+ if (projectEndpoint !== undefined && projectEndpoint === userEndpoint) {
143078
+ return [];
143079
+ }
143080
+ }
143081
+ const providesOwnKey = typeof projectEmbedding.api_key === "string" && projectEmbedding.api_key.length > 0;
143082
+ if (providesOwnKey)
143083
+ return [];
143084
+ const mergedEmbedding = mergedRaw.embedding;
143085
+ if (!isPlainObject2(mergedEmbedding))
143086
+ return [];
143087
+ if (!("api_key" in mergedEmbedding))
143088
+ return [];
143089
+ delete mergedEmbedding.api_key;
143090
+ return [
143091
+ "Dropped inherited user embedding api_key because project config redirected " + "embedding.endpoint without supplying its own key (security: prevents key " + "exfiltration to a repository-chosen endpoint)."
143092
+ ];
143093
+ }
143094
+
142914
143095
  // ../../node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
142915
143096
  var exports_external = {};
142916
143097
  __export(exports_external, {
@@ -143542,7 +143723,7 @@ __export(exports_util, {
143542
143723
  jsonStringifyReplacer: () => jsonStringifyReplacer,
143543
143724
  joinValues: () => joinValues,
143544
143725
  issue: () => issue,
143545
- isPlainObject: () => isPlainObject,
143726
+ isPlainObject: () => isPlainObject3,
143546
143727
  isObject: () => isObject,
143547
143728
  hexToUint8Array: () => hexToUint8Array,
143548
143729
  getSizableOrigin: () => getSizableOrigin,
@@ -143724,7 +143905,7 @@ var allowsEval = /* @__PURE__ */ cached(() => {
143724
143905
  return false;
143725
143906
  }
143726
143907
  });
143727
- function isPlainObject(o) {
143908
+ function isPlainObject3(o) {
143728
143909
  if (isObject(o) === false)
143729
143910
  return false;
143730
143911
  const ctor = o.constructor;
@@ -143741,7 +143922,7 @@ function isPlainObject(o) {
143741
143922
  return true;
143742
143923
  }
143743
143924
  function shallowClone(o) {
143744
- if (isPlainObject(o))
143925
+ if (isPlainObject3(o))
143745
143926
  return { ...o };
143746
143927
  if (Array.isArray(o))
143747
143928
  return [...o];
@@ -143945,7 +144126,7 @@ function omit(schema, mask) {
143945
144126
  return clone(schema, def);
143946
144127
  }
143947
144128
  function extend(schema, shape) {
143948
- if (!isPlainObject(shape)) {
144129
+ if (!isPlainObject3(shape)) {
143949
144130
  throw new Error("Invalid input to extend: expected a plain object");
143950
144131
  }
143951
144132
  const checks = schema._zod.def.checks;
@@ -143968,7 +144149,7 @@ function extend(schema, shape) {
143968
144149
  return clone(schema, def);
143969
144150
  }
143970
144151
  function safeExtend(schema, shape) {
143971
- if (!isPlainObject(shape)) {
144152
+ if (!isPlainObject3(shape)) {
143972
144153
  throw new Error("Invalid input to safeExtend: expected a plain object");
143973
144154
  }
143974
144155
  const def = mergeDefs(schema._zod.def, {
@@ -146311,7 +146492,7 @@ function mergeValues(a, b) {
146311
146492
  if (a instanceof Date && b instanceof Date && +a === +b) {
146312
146493
  return { valid: true, data: a };
146313
146494
  }
146314
- if (isPlainObject(a) && isPlainObject(b)) {
146495
+ if (isPlainObject3(a) && isPlainObject3(b)) {
146315
146496
  const bKeys = Object.keys(b);
146316
146497
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
146317
146498
  const newObj = { ...a, ...b };
@@ -146497,7 +146678,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
146497
146678
  $ZodType.init(inst, def);
146498
146679
  inst._zod.parse = (payload, ctx) => {
146499
146680
  const input = payload.value;
146500
- if (!isPlainObject(input)) {
146681
+ if (!isPlainObject3(input)) {
146501
146682
  payload.issues.push({
146502
146683
  expected: "record",
146503
146684
  code: "invalid_type",
@@ -157217,7 +157398,6 @@ var AgentOverrideConfigSchema = exports_external.object({
157217
157398
  });
157218
157399
 
157219
157400
  // ../plugin/src/config/schema/magic-context.ts
157220
- var DEFAULT_NUDGE_INTERVAL_TOKENS = 1e4;
157221
157401
  var DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE = 65;
157222
157402
  var EXECUTE_THRESHOLD_CAP_MESSAGE = "execute_threshold is capped at 80% for cache safety: a single large agent step can overflow the context window before Magic Context can compact between turns, forcing OpenCode's native compaction (hard to recover from). 80% also leaves headroom below the 85%/95% emergency bands. Use a value between 20 and 80.";
157223
157403
  var DEFAULT_HISTORIAN_TIMEOUT_MS = 300000;
@@ -157315,7 +157495,6 @@ var MagicContextConfigSchema = exports_external.object({
157315
157495
  historian: HistorianConfigSchema.describe("Historian agent configuration (model, fallback_models, variant, temperature, maxTokens, permission, two_pass, etc.)"),
157316
157496
  dreamer: DreamerConfigSchema.optional().describe("Dreamer agent + scheduling configuration (model, fallback_models, disable, schedule, tasks, etc.)"),
157317
157497
  cache_ttl: exports_external.union([exports_external.string(), exports_external.object({ default: exports_external.string() }).catchall(exports_external.string())]).default("5m").describe('Cache TTL: string (e.g. "5m") or per-model object ({ default: "5m", "model-id": "10m" })'),
157318
- nudge_interval_tokens: exports_external.number().min(1000).default(DEFAULT_NUDGE_INTERVAL_TOKENS).describe("Minimum token growth between low-priority rolling nudges (default: DEFAULT_NUDGE_INTERVAL_TOKENS)"),
157319
157498
  execute_threshold_percentage: exports_external.union([
157320
157499
  exports_external.number().min(20).max(80, EXECUTE_THRESHOLD_CAP_MESSAGE),
157321
157500
  exports_external.object({ default: exports_external.number().min(20).max(80, EXECUTE_THRESHOLD_CAP_MESSAGE) }).catchall(exports_external.number().min(20).max(80, EXECUTE_THRESHOLD_CAP_MESSAGE))
@@ -157324,10 +157503,7 @@ var MagicContextConfigSchema = exports_external.object({
157324
157503
  default: exports_external.number().min(5000).max(2000000).optional()
157325
157504
  }).catchall(exports_external.number().min(5000).max(2000000)).optional().describe("Absolute token thresholds per model. When matched, overrides execute_threshold_percentage for that model. Accepts `default` for all models or per-model keys. Values above 80% × context_limit are clamped with a warning log. Min 5_000, max 2_000_000."),
157326
157505
  protected_tags: exports_external.number().min(1).max(100).optional().describe("Number of recent tags to protect from dropping (min: 1, max: 100, default: 20)"),
157327
- auto_drop_tool_age: exports_external.number().min(10).default(100).describe("Auto-drop tool outputs older than N tags during queue execution (default: 100)"),
157328
- drop_tool_structure: exports_external.boolean().default(true).describe("When true, dropped tool parts are fully removed instead of truncated in place (default: true)"),
157329
157506
  clear_reasoning_age: exports_external.number().min(10).default(50).describe("Clear reasoning/thinking blocks older than N tags (default: 50)"),
157330
- iteration_nudge_threshold: exports_external.number().min(5).default(15).describe("Number of consecutive assistant messages without user input to trigger iteration nudge (default: 15)"),
157331
157507
  history_budget_percentage: exports_external.number().min(0.05).max(0.5).default(DEFAULT_HISTORY_BUDGET_PERCENTAGE).describe("Fraction of usable context (context_limit × execute_threshold) reserved for the session history block (default: 0.15)"),
157332
157508
  historian_timeout_ms: exports_external.number().min(60000).default(DEFAULT_HISTORIAN_TIMEOUT_MS).describe("Timeout for each historian prompt call in milliseconds (default: 300000)"),
157333
157509
  commit_cluster_trigger: exports_external.object({
@@ -157350,6 +157526,7 @@ var MagicContextConfigSchema = exports_external.object({
157350
157526
  model: DEFAULT_LOCAL_EMBEDDING_MODEL
157351
157527
  }).describe("Embedding provider configuration"),
157352
157528
  temporal_awareness: exports_external.boolean().default(true).describe('Inject wall-clock gap markers (<!-- +Xm -->) between user messages where > 5 min elapsed since the previous message, and add start/end date attributes on compartments. Gives the agent a sense of session pacing and "how long ago" across multi-day sessions. Graduated from experimental.temporal_awareness; default: true (set false to opt out).'),
157529
+ keep_subagents: exports_external.boolean().default(false).describe("Debug: keep the child sessions Magic Context spawns for its own subagents (historian, dreamer, sidekick, memory-migration) instead of deleting them on success. Useful for short-term inspection/data collection — their full transcript (prompt, tool calls, token usage, output) stays in the host session store. Kept sessions accumulate until manually cleared; leave false for normal use. Requires a restart to take effect."),
157353
157530
  caveman_text_compression: exports_external.object({
157354
157531
  enabled: exports_external.boolean().default(false).describe("Apply deterministic caveman-style text compression to old conversation text. Only active when ctx_reduce_enabled=false. Compresses user/assistant text in oldest-first tiers: ultra (oldest 20%), full, lite, untouched (newest 40%)."),
157355
157532
  min_chars: exports_external.number().min(100).max(1e4).default(500).describe("Text parts shorter than this (characters) stay untouched. Min 100, max 10000. Default: 500.")
@@ -157453,6 +157630,21 @@ function stripJsonComments(content) {
157453
157630
  // ../plugin/src/config/variable.ts
157454
157631
  var ENV_PATTERN = /\{env:([^}]+)\}/g;
157455
157632
  var FILE_PATTERN = /\{file:([^}]+)\}/g;
157633
+ function sensitiveFilePathReason(resolvedPath) {
157634
+ const home = homedir2();
157635
+ const sensitiveDirs = [
157636
+ { dir: resolve2(home, ".ssh"), label: "SSH keys" },
157637
+ { dir: resolve2(home, ".aws"), label: "AWS credentials" },
157638
+ { dir: resolve2(home, ".gnupg"), label: "GnuPG keyring" },
157639
+ { dir: resolve2(home, ".config", "gh"), label: "GitHub CLI auth" }
157640
+ ];
157641
+ for (const { dir, label } of sensitiveDirs) {
157642
+ if (resolvedPath === dir || resolvedPath.startsWith(`${dir}/`)) {
157643
+ return label;
157644
+ }
157645
+ }
157646
+ return null;
157647
+ }
157456
157648
  function substituteConfigVariables(input) {
157457
157649
  const warnings = [];
157458
157650
  let text = input.text;
@@ -157506,6 +157698,10 @@ function substituteConfigVariables(input) {
157506
157698
  } else if (!isAbsolute(filePath)) {
157507
157699
  filePath = resolve2(configDir, filePath);
157508
157700
  }
157701
+ const sensitiveReason = sensitiveFilePathReason(filePath);
157702
+ if (sensitiveReason) {
157703
+ warnings.push(`${token} resolves to a sensitive path (${sensitiveReason}: ${filePath}); ` + "inlining its contents into config — make sure this is intentional.");
157704
+ }
157509
157705
  if (!existsSync4(filePath)) {
157510
157706
  warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
157511
157707
  continue;
@@ -157544,7 +157740,8 @@ function loadConfigFile(path3, scope) {
157544
157740
  const rawText = readFileSync3(path3, "utf-8");
157545
157741
  const substituted = substituteConfigVariables({
157546
157742
  text: rawText,
157547
- configPath: path3
157743
+ configPath: path3,
157744
+ isProjectConfig: scope === "project"
157548
157745
  });
157549
157746
  return {
157550
157747
  path: path3,
@@ -157586,14 +157783,14 @@ function redactConfigValue(value) {
157586
157783
  }
157587
157784
  return typeof value;
157588
157785
  }
157589
- function isPlainObject2(value) {
157786
+ function isPlainObject4(value) {
157590
157787
  return typeof value === "object" && value !== null && !Array.isArray(value);
157591
157788
  }
157592
157789
  function mergeRawConfigs(base, override) {
157593
157790
  const merged = { ...base };
157594
157791
  for (const [key, overrideValue] of Object.entries(override)) {
157595
157792
  const baseValue = merged[key];
157596
- merged[key] = isPlainObject2(baseValue) && isPlainObject2(overrideValue) ? mergeRawConfigs(baseValue, overrideValue) : overrideValue;
157793
+ merged[key] = isPlainObject4(baseValue) && isPlainObject4(overrideValue) ? mergeRawConfigs(baseValue, overrideValue) : overrideValue;
157597
157794
  }
157598
157795
  return merged;
157599
157796
  }
@@ -157607,10 +157804,15 @@ function parsePiConfig(rawConfig, recoveredTopLevelKeys = []) {
157607
157804
  }
157608
157805
  const defaults = MagicContextConfigSchema.parse({});
157609
157806
  const errorPaths = new Set;
157807
+ const issuePathsByKey = new Map;
157610
157808
  for (const issue2 of parsed.error.issues) {
157611
157809
  const topKey = issue2.path[0];
157612
157810
  if (topKey !== undefined) {
157613
- errorPaths.add(String(topKey));
157811
+ const key = String(topKey);
157812
+ errorPaths.add(key);
157813
+ const paths = issuePathsByKey.get(key) ?? [];
157814
+ paths.push([...issue2.path]);
157815
+ issuePathsByKey.set(key, paths);
157614
157816
  }
157615
157817
  }
157616
157818
  const patched = { ...migrated };
@@ -157618,11 +157820,32 @@ function parsePiConfig(rawConfig, recoveredTopLevelKeys = []) {
157618
157820
  for (const key of errorPaths) {
157619
157821
  recoveredTopLevelKeys.push(key);
157620
157822
  const isAgentConfig = key === "historian" || key === "dreamer" || key === "sidekick";
157621
- delete patched[key];
157622
157823
  if (isAgentConfig) {
157824
+ delete patched[key];
157623
157825
  warnings.push(`"${key}": invalid agent configuration, ignoring. Check your magic-context.jsonc.`);
157624
157826
  continue;
157625
157827
  }
157828
+ const issuePaths = issuePathsByKey.get(key) ?? [];
157829
+ const rawValue = migrated[key];
157830
+ const allNested = issuePaths.length > 0 && issuePaths.every((p) => p.length >= 2) && typeof rawValue === "object" && rawValue !== null && !Array.isArray(rawValue);
157831
+ if (allNested) {
157832
+ let prunedBlock = {
157833
+ ...rawValue
157834
+ };
157835
+ const prunedLeaves = [];
157836
+ for (const p of issuePaths) {
157837
+ const relative = p.slice(1);
157838
+ const result = pruneNestedConfigLeaf(prunedBlock, relative);
157839
+ if (result) {
157840
+ prunedBlock = result.block;
157841
+ prunedLeaves.push(result.removed);
157842
+ }
157843
+ }
157844
+ patched[key] = prunedBlock;
157845
+ warnings.push(`"${key}": invalid nested field(s) ${prunedLeaves.map((l) => `"${l}"`).join(", ")}, using defaults for those.`);
157846
+ continue;
157847
+ }
157848
+ delete patched[key];
157626
157849
  const defaultValue = defaults[key];
157627
157850
  warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultValue)}.`);
157628
157851
  }
@@ -157655,10 +157878,22 @@ function loadPiConfig(opts = {}) {
157655
157878
  return 0;
157656
157879
  return a.scope === "user" ? -1 : 1;
157657
157880
  });
157881
+ const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
157658
157882
  for (const loaded of mergeFiles) {
157659
157883
  const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
157660
157884
  warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
157661
- rawConfig = mergeRawConfigs(rawConfig, loaded.config);
157885
+ if (loaded.scope === "project") {
157886
+ const projectRaw = { ...loaded.config };
157887
+ for (const warning of stripUnsafeProjectConfigFields(projectRaw)) {
157888
+ warnings.push(`${prefix} ${warning}`);
157889
+ }
157890
+ rawConfig = mergeRawConfigs(rawConfig, projectRaw);
157891
+ for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
157892
+ warnings.push(`${prefix} ${warning}`);
157893
+ }
157894
+ } else {
157895
+ rawConfig = mergeRawConfigs(rawConfig, loaded.config);
157896
+ }
157662
157897
  }
157663
157898
  const parsed = parsePiConfig(rawConfig);
157664
157899
  warnings.push(...parsed.warnings.map((warning) => `[merged config] ${warning}`));
@@ -157733,10 +157968,22 @@ function loadPiConfigDetailed(opts = {}) {
157733
157968
  return 0;
157734
157969
  return a.scope === "user" ? -1 : 1;
157735
157970
  });
157971
+ const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
157736
157972
  for (const loaded of mergeFiles) {
157737
157973
  const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
157738
157974
  warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
157739
- rawConfig = mergeRawConfigs(rawConfig, loaded.config);
157975
+ if (loaded.scope === "project") {
157976
+ const projectRaw = { ...loaded.config };
157977
+ for (const warning of stripUnsafeProjectConfigFields(projectRaw)) {
157978
+ warnings.push(`${prefix} ${warning}`);
157979
+ }
157980
+ rawConfig = mergeRawConfigs(rawConfig, projectRaw);
157981
+ for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
157982
+ warnings.push(`${prefix} ${warning}`);
157983
+ }
157984
+ } else {
157985
+ rawConfig = mergeRawConfigs(rawConfig, loaded.config);
157986
+ }
157740
157987
  }
157741
157988
  const recoveredTopLevelKeys = [];
157742
157989
  const parsed = parsePiConfig(rawConfig, recoveredTopLevelKeys);
@@ -157794,7 +158041,7 @@ function computeNormalizedHash(content) {
157794
158041
  }
157795
158042
 
157796
158043
  // ../plugin/src/features/magic-context/memory/embedding-identity.ts
157797
- function normalizeEndpoint(endpoint) {
158044
+ function normalizeEndpoint2(endpoint) {
157798
158045
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
157799
158046
  }
157800
158047
  function getEmbeddingProviderIdentity(config2) {
@@ -157804,7 +158051,7 @@ function getEmbeddingProviderIdentity(config2) {
157804
158051
  const identityInput = config2.provider === "openai-compatible" ? {
157805
158052
  provider: "openai-compatible",
157806
158053
  model: config2.model.trim(),
157807
- endpoint: normalizeEndpoint(config2.endpoint),
158054
+ endpoint: normalizeEndpoint2(config2.endpoint),
157808
158055
  apiKeyPresent: Boolean(config2.api_key?.trim()),
157809
158056
  inputType: config2.input_type?.trim() || ""
157810
158057
  } : {
@@ -158159,7 +158406,62 @@ class LocalEmbeddingProvider {
158159
158406
 
158160
158407
  // ../plugin/src/features/magic-context/memory/embedding-openai.ts
158161
158408
  init_logger();
158162
- function normalizeEndpoint2(endpoint) {
158409
+
158410
+ // ../plugin/src/features/magic-context/memory/embedding-ssrf.ts
158411
+ var METADATA_HOSTNAMES = new Set(["metadata.google.internal", "metadata.goog"]);
158412
+ var IPV6_METADATA_HOSTS = new Set(["fd00:ec2::254"]);
158413
+ function isLinkLocalIpv4(host) {
158414
+ return /^169\.254\.\d{1,3}\.\d{1,3}$/.test(host);
158415
+ }
158416
+ function ipv4FromMappedIpv6(host) {
158417
+ const m = /^::ffff:(.+)$/.exec(host);
158418
+ if (!m)
158419
+ return null;
158420
+ const tail = m[1];
158421
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(tail))
158422
+ return tail;
158423
+ const hex3 = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(tail);
158424
+ if (hex3) {
158425
+ const hi = Number.parseInt(hex3[1], 16);
158426
+ const lo = Number.parseInt(hex3[2], 16);
158427
+ if (Number.isNaN(hi) || Number.isNaN(lo))
158428
+ return null;
158429
+ return `${hi >> 8 & 255}.${hi & 255}.${lo >> 8 & 255}.${lo & 255}`;
158430
+ }
158431
+ return null;
158432
+ }
158433
+ function blockedEmbeddingEndpointReason(endpoint) {
158434
+ const trimmed = endpoint.trim();
158435
+ if (trimmed.length === 0)
158436
+ return null;
158437
+ let url2;
158438
+ try {
158439
+ url2 = new URL(trimmed);
158440
+ } catch {
158441
+ return `embedding endpoint is not a valid URL: ${trimmed}`;
158442
+ }
158443
+ const host = url2.hostname.toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
158444
+ if (METADATA_HOSTNAMES.has(host)) {
158445
+ return `embedding endpoint host ${host} is a cloud metadata service (blocked)`;
158446
+ }
158447
+ if (IPV6_METADATA_HOSTS.has(host)) {
158448
+ return `embedding endpoint host ${host} is the AWS IPv6 metadata service (blocked)`;
158449
+ }
158450
+ if (isLinkLocalIpv4(host)) {
158451
+ return `embedding endpoint host ${host} is link-local / cloud metadata (blocked)`;
158452
+ }
158453
+ const mappedV4 = ipv4FromMappedIpv6(host);
158454
+ if (mappedV4 && isLinkLocalIpv4(mappedV4)) {
158455
+ return `embedding endpoint host ${host} (IPv4-mapped ${mappedV4}) is link-local / cloud metadata (blocked)`;
158456
+ }
158457
+ if (host.startsWith("fe80:")) {
158458
+ return `embedding endpoint host ${host} is link-local / cloud metadata (blocked)`;
158459
+ }
158460
+ return null;
158461
+ }
158462
+
158463
+ // ../plugin/src/features/magic-context/memory/embedding-openai.ts
158464
+ function normalizeEndpoint3(endpoint) {
158163
158465
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
158164
158466
  }
158165
158467
  var FAILURE_THRESHOLD = 3;
@@ -158180,7 +158482,7 @@ class OpenAICompatibleEmbeddingProvider {
158180
158482
  openLogged = false;
158181
158483
  halfOpenProbeInFlight = false;
158182
158484
  constructor(options) {
158183
- this.endpoint = normalizeEndpoint2(options.endpoint);
158485
+ this.endpoint = normalizeEndpoint3(options.endpoint);
158184
158486
  this.model = options.model?.trim() ?? "";
158185
158487
  this.apiKey = options.apiKey?.trim() ?? "";
158186
158488
  this.inputType = options.inputType?.trim() ?? "";
@@ -158200,6 +158502,12 @@ class OpenAICompatibleEmbeddingProvider {
158200
158502
  this.initialized = false;
158201
158503
  return false;
158202
158504
  }
158505
+ const blockedReason = blockedEmbeddingEndpointReason(this.endpoint);
158506
+ if (blockedReason) {
158507
+ log(`[magic-context] embedding endpoint blocked: ${blockedReason}`);
158508
+ this.initialized = false;
158509
+ return false;
158510
+ }
158203
158511
  this.initialized = true;
158204
158512
  return true;
158205
158513
  }
@@ -158245,6 +158553,7 @@ class OpenAICompatibleEmbeddingProvider {
158245
158553
  ...this.inputType ? { input_type: this.inputType } : {},
158246
158554
  ...this.truncate ? { truncate: this.truncate } : {}
158247
158555
  }),
158556
+ redirect: "error",
158248
158557
  signal: internalController.signal
158249
158558
  });
158250
158559
  if (!response.ok) {
@@ -158725,11 +159034,15 @@ function resolveEmbeddingConfig(config2) {
158725
159034
  }
158726
159035
  if (config2.provider === "openai-compatible") {
158727
159036
  const apiKey = config2.api_key?.trim();
159037
+ const inputType = config2.input_type?.trim();
159038
+ const truncate = config2.truncate?.trim();
158728
159039
  return {
158729
159040
  provider: "openai-compatible",
158730
159041
  model: config2.model.trim(),
158731
159042
  endpoint: config2.endpoint.trim(),
158732
- ...apiKey ? { api_key: apiKey } : {}
159043
+ ...apiKey ? { api_key: apiKey } : {},
159044
+ ...inputType ? { input_type: inputType } : {},
159045
+ ...truncate ? { truncate } : {}
158733
159046
  };
158734
159047
  }
158735
159048
  return { provider: "off" };
@@ -158745,7 +159058,9 @@ function createProvider(config2) {
158745
159058
  return new OpenAICompatibleEmbeddingProvider({
158746
159059
  endpoint: config2.endpoint,
158747
159060
  model: config2.model,
158748
- apiKey: config2.api_key
159061
+ apiKey: config2.api_key,
159062
+ inputType: config2.input_type,
159063
+ truncate: config2.truncate
158749
159064
  });
158750
159065
  }
158751
159066
  return new LocalEmbeddingProvider(config2.model);
@@ -159184,6 +159499,19 @@ function hasGitDir(canonical) {
159184
159499
  return false;
159185
159500
  }
159186
159501
  }
159502
+ function normalizeStoredProjectPath(rawOrStored) {
159503
+ if (rawOrStored.startsWith("git:") || rawOrStored.startsWith("dir:")) {
159504
+ return rawOrStored;
159505
+ }
159506
+ try {
159507
+ return resolveProjectIdentity(rawOrStored);
159508
+ } catch {
159509
+ return directoryFallback(rawOrStored);
159510
+ }
159511
+ }
159512
+ function storedPathBelongsToIdentity(storedProjectPath, projectIdentity) {
159513
+ return storedProjectPath === projectIdentity || normalizeStoredProjectPath(storedProjectPath) === projectIdentity;
159514
+ }
159187
159515
 
159188
159516
  // ../plugin/src/plugin/embedding-bootstrap-helpers.ts
159189
159517
  import { createHash as createHash5 } from "node:crypto";
@@ -159192,10 +159520,20 @@ var EMBEDDING_AFFECTING_KEYS = new Set([
159192
159520
  "embedding.api_key",
159193
159521
  "embedding.endpoint",
159194
159522
  "embedding.model",
159195
- "embedding.provider"
159523
+ "embedding.provider",
159524
+ "embedding.input_type",
159525
+ "embedding.truncate"
159196
159526
  ]);
159197
159527
  var EMBEDDING_AFFECTING_TOP_LEVEL_KEYS = new Set(["embedding", "memory", "experimental"]);
159198
- var EMBEDDING_WARNING_TERMS = ["api_key", "endpoint", "model", "provider", "embedding"];
159528
+ var EMBEDDING_WARNING_TERMS = [
159529
+ "api_key",
159530
+ "endpoint",
159531
+ "model",
159532
+ "provider",
159533
+ "embedding",
159534
+ "input_type",
159535
+ "truncate"
159536
+ ];
159199
159537
  var loggedFailureSignatures = new Map;
159200
159538
  function sha256Prefix2(value, length = 16) {
159201
159539
  return createHash5("sha256").update(value).digest("hex").slice(0, length);
@@ -159368,7 +159706,18 @@ var SESSION_META_SELECT_COLUMNS = [
159368
159706
  "cached_m0_materialized_at",
159369
159707
  "cached_m0_session_facts_version",
159370
159708
  "cached_m0_upgrade_state",
159709
+ "cached_m0_system_hash",
159710
+ "cached_m0_tool_set_hash",
159711
+ "cached_m0_model_key",
159371
159712
  "last_observed_model_key",
159713
+ "last_usage_context_limit",
159714
+ "prior_boundary_ordinal",
159715
+ "protected_tail_policy_version",
159716
+ "protected_tail_drain_window_started_at",
159717
+ "protected_tail_drain_tokens",
159718
+ "recovery_no_eligible_head_count",
159719
+ "force_emergency_bypass_window_start",
159720
+ "force_emergency_bypass_used",
159372
159721
  "upgrade_reminded_at",
159373
159722
  "pi_stable_id_scheme"
159374
159723
  ];
@@ -159404,7 +159753,18 @@ var META_COLUMNS = {
159404
159753
  cachedM0MaterializedAt: "cached_m0_materialized_at",
159405
159754
  cachedM0SessionFactsVersion: "cached_m0_session_facts_version",
159406
159755
  cachedM0UpgradeState: "cached_m0_upgrade_state",
159756
+ cachedM0SystemHash: "cached_m0_system_hash",
159757
+ cachedM0ToolSetHash: "cached_m0_tool_set_hash",
159758
+ cachedM0ModelKey: "cached_m0_model_key",
159407
159759
  lastObservedModelKey: "last_observed_model_key",
159760
+ lastUsageContextLimit: "last_usage_context_limit",
159761
+ priorBoundaryOrdinal: "prior_boundary_ordinal",
159762
+ protectedTailPolicyVersion: "protected_tail_policy_version",
159763
+ protectedTailDrainWindowStartedAt: "protected_tail_drain_window_started_at",
159764
+ protectedTailDrainTokens: "protected_tail_drain_tokens",
159765
+ recoveryNoEligibleHeadCount: "recovery_no_eligible_head_count",
159766
+ forceEmergencyBypassWindowStart: "force_emergency_bypass_window_start",
159767
+ forceEmergencyBypassUsed: "force_emergency_bypass_used",
159408
159768
  upgradeRemindedAt: "upgrade_reminded_at",
159409
159769
  piStableIdScheme: "pi_stable_id_scheme"
159410
159770
  };
@@ -159446,7 +159806,7 @@ function isSessionMetaRow(row) {
159446
159806
  if (row === null || typeof row !== "object")
159447
159807
  return false;
159448
159808
  const r = row;
159449
- 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) && 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.last_observed_model_key) && isNumberOrNull(r.upgrade_reminded_at) && isNumberOrNull(r.pi_stable_id_scheme);
159809
+ 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) && 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);
159450
159810
  }
159451
159811
  function getDefaultSessionMeta(sessionId) {
159452
159812
  return {
@@ -159482,7 +159842,18 @@ function getDefaultSessionMeta(sessionId) {
159482
159842
  cachedM0MaterializedAt: null,
159483
159843
  cachedM0SessionFactsVersion: null,
159484
159844
  cachedM0UpgradeState: null,
159845
+ cachedM0SystemHash: null,
159846
+ cachedM0ToolSetHash: null,
159847
+ cachedM0ModelKey: null,
159485
159848
  lastObservedModelKey: null,
159849
+ lastUsageContextLimit: 0,
159850
+ priorBoundaryOrdinal: 1,
159851
+ protectedTailPolicyVersion: 0,
159852
+ protectedTailDrainWindowStartedAt: 0,
159853
+ protectedTailDrainTokens: 0,
159854
+ recoveryNoEligibleHeadCount: 0,
159855
+ forceEmergencyBypassWindowStart: 0,
159856
+ forceEmergencyBypassUsed: 0,
159486
159857
  upgradeRemindedAt: null,
159487
159858
  piStableIdScheme: null
159488
159859
  };
@@ -159533,7 +159904,18 @@ function toSessionMeta(row) {
159533
159904
  cachedM0MaterializedAt: numOrNull(row.cached_m0_materialized_at),
159534
159905
  cachedM0SessionFactsVersion: numOrNull(row.cached_m0_session_facts_version),
159535
159906
  cachedM0UpgradeState: stringOrNull(row.cached_m0_upgrade_state),
159907
+ cachedM0SystemHash: stringOrNull(row.cached_m0_system_hash),
159908
+ cachedM0ToolSetHash: stringOrNull(row.cached_m0_tool_set_hash),
159909
+ cachedM0ModelKey: stringOrNull(row.cached_m0_model_key),
159536
159910
  lastObservedModelKey: stringOrNull(row.last_observed_model_key),
159911
+ lastUsageContextLimit: numOrZero(row.last_usage_context_limit),
159912
+ priorBoundaryOrdinal: Math.max(1, numOrZero(row.prior_boundary_ordinal) || 1),
159913
+ protectedTailPolicyVersion: numOrZero(row.protected_tail_policy_version),
159914
+ protectedTailDrainWindowStartedAt: numOrZero(row.protected_tail_drain_window_started_at),
159915
+ protectedTailDrainTokens: numOrZero(row.protected_tail_drain_tokens),
159916
+ recoveryNoEligibleHeadCount: numOrZero(row.recovery_no_eligible_head_count),
159917
+ forceEmergencyBypassWindowStart: numOrZero(row.force_emergency_bypass_window_start),
159918
+ forceEmergencyBypassUsed: numOrZero(row.force_emergency_bypass_used),
159537
159919
  upgradeRemindedAt: numOrNull(row.upgrade_reminded_at),
159538
159920
  piStableIdScheme: numOrNull(row.pi_stable_id_scheme)
159539
159921
  };
@@ -159552,8 +159934,11 @@ function persistCachedM0(db, sessionId, payload) {
159552
159934
  cached_m0_project_docs_hash = ?,
159553
159935
  cached_m0_materialized_at = ?,
159554
159936
  cached_m0_session_facts_version = ?,
159555
- cached_m0_upgrade_state = ?
159556
- WHERE session_id = ?`).run(Buffer2.from(payload.m0Bytes), payload.projectMemoryEpoch, payload.projectUserProfileVersion, payload.maxCompartmentSeq, payload.maxMemoryId, payload.maxMutationId, payload.maxMemoryMutationId ?? null, payload.m1Bytes ? Buffer2.from(payload.m1Bytes) : null, payload.projectDocsHash, payload.materializedAt, payload.sessionFactsVersion, payload.upgradeState, sessionId);
159937
+ cached_m0_upgrade_state = ?,
159938
+ cached_m0_system_hash = ?,
159939
+ cached_m0_tool_set_hash = ?,
159940
+ cached_m0_model_key = ?
159941
+ WHERE session_id = ?`).run(Buffer2.from(payload.m0Bytes), payload.projectMemoryEpoch, payload.projectUserProfileVersion, payload.maxCompartmentSeq, payload.maxMemoryId, payload.maxMutationId, payload.maxMemoryMutationId ?? null, payload.m1Bytes ? Buffer2.from(payload.m1Bytes) : null, payload.projectDocsHash, payload.materializedAt, payload.sessionFactsVersion, payload.upgradeState, payload.systemHash ?? "", payload.toolSetHash ?? "", payload.modelKey ?? "", sessionId);
159557
159942
  }
159558
159943
  function clearCachedM0M1(db, sessionId) {
159559
159944
  ensureSessionMetaRow(db, sessionId);
@@ -159571,6 +159956,9 @@ function clearCachedM0M1(db, sessionId) {
159571
159956
  ["cached_m0_materialized_at", null],
159572
159957
  ["cached_m0_session_facts_version", null],
159573
159958
  ["cached_m0_upgrade_state", null],
159959
+ ["cached_m0_system_hash", null],
159960
+ ["cached_m0_tool_set_hash", null],
159961
+ ["cached_m0_model_key", null],
159574
159962
  ["cached_m0_last_baseline_end_message_id", null],
159575
159963
  ["memory_block_cache", ""],
159576
159964
  ["memory_block_count", 0],
@@ -159612,12 +160000,6 @@ function isCompartmentRow(row) {
159612
160000
  const candidate = row;
159613
160001
  return typeof candidate.id === "number" && typeof candidate.session_id === "string" && typeof candidate.sequence === "number" && typeof candidate.start_message === "number" && typeof candidate.end_message === "number" && typeof candidate.start_message_id === "string" && typeof candidate.end_message_id === "string" && typeof candidate.title === "string" && typeof candidate.content === "string" && isStringOrNullish(candidate.p1) && isStringOrNullish(candidate.p2) && isStringOrNullish(candidate.p3) && isStringOrNullish(candidate.p4) && isNumberOrNullish(candidate.importance) && isStringOrNullish(candidate.episode_type) && isNumberOrNullish(candidate.legacy) && typeof candidate.created_at === "number";
159614
160002
  }
159615
- function isSessionFactRow(row) {
159616
- if (row === null || typeof row !== "object")
159617
- return false;
159618
- const candidate = row;
159619
- return typeof candidate.id === "number" && typeof candidate.session_id === "string" && typeof candidate.category === "string" && typeof candidate.content === "string" && typeof candidate.created_at === "number" && typeof candidate.updated_at === "number";
159620
- }
159621
160003
  function insertCompartmentRows(db, sessionId, compartments, now) {
159622
160004
  const stmt = getInsertCompartmentStatement(db);
159623
160005
  for (const compartment of compartments) {
@@ -159646,16 +160028,6 @@ function toCompartment(row) {
159646
160028
  createdAt: row.created_at
159647
160029
  };
159648
160030
  }
159649
- function toSessionFact(row) {
159650
- return {
159651
- id: row.id,
159652
- sessionId: row.session_id,
159653
- category: row.category,
159654
- content: row.content,
159655
- createdAt: row.created_at,
159656
- updatedAt: row.updated_at
159657
- };
159658
- }
159659
160031
  function getCompartments(db, sessionId) {
159660
160032
  const rows = db.prepare("SELECT * FROM compartments WHERE session_id = ? ORDER BY sequence ASC").all(sessionId).filter(isCompartmentRow);
159661
160033
  return rows.map(toCompartment);
@@ -159664,6 +160036,11 @@ function getLastCompartmentEndMessage(db, sessionId) {
159664
160036
  const row = db.prepare("SELECT MAX(end_message) as max_end FROM compartments WHERE session_id = ?").get(sessionId);
159665
160037
  return row?.max_end ?? -1;
159666
160038
  }
160039
+ function getLastCompartmentEndMessageId(db, sessionId) {
160040
+ const row = db.prepare("SELECT end_message_id FROM compartments WHERE session_id = ? ORDER BY sequence DESC LIMIT 1").get(sessionId);
160041
+ const id = row?.end_message_id;
160042
+ return id && id.length > 0 ? id : null;
160043
+ }
159667
160044
  function getCompartmentsByEndMessageId(db, sessionId, endMessageId) {
159668
160045
  const rows = db.prepare("SELECT * FROM compartments WHERE session_id = ? AND end_message_id = ? ORDER BY sequence ASC").all(sessionId, endMessageId).filter(isCompartmentRow);
159669
160046
  return rows.map(toCompartment);
@@ -159676,10 +160053,6 @@ function appendCompartments(db, sessionId, compartments) {
159676
160053
  insertCompartmentRows(db, sessionId, compartments, now);
159677
160054
  })();
159678
160055
  }
159679
- function getSessionFacts(db, sessionId) {
159680
- const rows = db.prepare("SELECT * FROM session_facts WHERE session_id = ? ORDER BY category ASC, id ASC").all(sessionId).filter(isSessionFactRow);
159681
- return rows.map(toSessionFact);
159682
- }
159683
160056
  function saveRecompStagingPass(db, sessionId, passNumber, compartments, facts) {
159684
160057
  const now = Date.now();
159685
160058
  db.transaction(() => {
@@ -159723,14 +160096,6 @@ function getRecompStaging(db, sessionId) {
159723
160096
  lastEndMessage: lastEnd
159724
160097
  };
159725
160098
  }
159726
- function invalidateAllMemoryBlockCaches(db) {
159727
- try {
159728
- const rows = db.prepare("SELECT session_id FROM session_meta").all();
159729
- for (const row of rows) {
159730
- clearCachedM0M1(db, row.session_id);
159731
- }
159732
- } catch {}
159733
- }
159734
160099
  function clearRecompStaging(db, sessionId) {
159735
160100
  db.transaction(() => {
159736
160101
  db.prepare("DELETE FROM recomp_compartments WHERE session_id = ?").run(sessionId);
@@ -159777,8 +160142,49 @@ function escapeXmlContent(s) {
159777
160142
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
159778
160143
  }
159779
160144
 
159780
- // ../plugin/src/hooks/magic-context/read-session-chunk.ts
159781
- await init_read_session_db();
160145
+ // ../plugin/src/hooks/magic-context/read-session-db.ts
160146
+ init_data_path();
160147
+ init_logger();
160148
+ import { existsSync as existsSync6 } from "node:fs";
160149
+ import { join as join7 } from "node:path";
160150
+ function getOpenCodeDbPath() {
160151
+ return join7(getDataDir(), "opencode", "opencode.db");
160152
+ }
160153
+ function openCodeDbExists() {
160154
+ return existsSync6(getOpenCodeDbPath());
160155
+ }
160156
+ var cachedReadOnlyDb = null;
160157
+ function closeCachedReadOnlyDb() {
160158
+ if (!cachedReadOnlyDb) {
160159
+ return;
160160
+ }
160161
+ try {
160162
+ closeQuietly(cachedReadOnlyDb.db);
160163
+ } catch (error51) {
160164
+ log("[magic-context] failed to close cached OpenCode read-only DB:", error51);
160165
+ } finally {
160166
+ cachedReadOnlyDb = null;
160167
+ }
160168
+ }
160169
+ function getReadOnlySessionDb() {
160170
+ const dbPath = getOpenCodeDbPath();
160171
+ if (cachedReadOnlyDb?.path === dbPath) {
160172
+ return cachedReadOnlyDb.db;
160173
+ }
160174
+ closeCachedReadOnlyDb();
160175
+ const db = new Database(dbPath, { readonly: true });
160176
+ cachedReadOnlyDb = { path: dbPath, db };
160177
+ return db;
160178
+ }
160179
+ function withReadOnlySessionDb(fn) {
160180
+ return fn(getReadOnlySessionDb());
160181
+ }
160182
+ function getRawSessionMessageCountFromDb(db, sessionId) {
160183
+ const row = db.prepare(`SELECT COUNT(*) as count FROM message WHERE session_id = ?
160184
+ AND NOT (COALESCE(json_extract(data, '$.summary'), 0) = 1
160185
+ AND COALESCE(json_extract(data, '$.finish'), '') = 'stop')`).get(sessionId);
160186
+ return typeof row?.count === "number" ? row.count : 0;
160187
+ }
159782
160188
 
159783
160189
  // ../plugin/src/hooks/magic-context/read-session-raw.ts
159784
160190
  function isRawMessageRow(row) {
@@ -159811,13 +160217,27 @@ function parseJsonUnknown(value) {
159811
160217
  return null;
159812
160218
  }
159813
160219
  }
160220
+ function attachRawPartVersion(value, timeUpdated) {
160221
+ if (value === null || typeof value !== "object" || Array.isArray(value))
160222
+ return value;
160223
+ if (typeof timeUpdated !== "number")
160224
+ return value;
160225
+ try {
160226
+ Object.defineProperty(value, "__magicContextPartUpdatedAt", {
160227
+ value: timeUpdated,
160228
+ enumerable: false,
160229
+ configurable: true
160230
+ });
160231
+ } catch {}
160232
+ return value;
160233
+ }
159814
160234
  function readRawSessionMessagesFromDb(db, sessionId) {
159815
- const messageRows = db.prepare("SELECT id, data FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawMessageRow);
159816
- const partRows = db.prepare("SELECT message_id, data FROM part WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawPartRow);
160235
+ const messageRows = db.prepare("SELECT id, data, time_updated FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawMessageRow);
160236
+ const partRows = db.prepare("SELECT message_id, data, time_updated FROM part WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawPartRow);
159817
160237
  const partsByMessageId = new Map;
159818
160238
  for (const part of partRows) {
159819
160239
  const list = partsByMessageId.get(part.message_id) ?? [];
159820
- list.push(parseJsonUnknown(part.data));
160240
+ list.push(attachRawPartVersion(parseJsonUnknown(part.data), part.time_updated));
159821
160241
  partsByMessageId.set(part.message_id, list);
159822
160242
  }
159823
160243
  const filtered = messageRows.filter((row) => {
@@ -159833,10 +160253,63 @@ function readRawSessionMessagesFromDb(db, sessionId) {
159833
160253
  ordinal: index + 1,
159834
160254
  id: row.id,
159835
160255
  role,
159836
- parts: partsByMessageId.get(row.id) ?? []
160256
+ parts: partsByMessageId.get(row.id) ?? [],
160257
+ version: row.time_updated ?? null
159837
160258
  };
159838
160259
  });
159839
160260
  }
160261
+ function isAnchorRow(row) {
160262
+ return row !== null && typeof row === "object" && typeof row.time_created === "number" && typeof row.id === "string";
160263
+ }
160264
+ function readRawSessionTailFromDb(db, sessionId, baseOrdinal, anchorMessageId) {
160265
+ const anchorRow = db.prepare("SELECT time_created, id, data FROM message WHERE id = ? AND session_id = ?").get(anchorMessageId, sessionId);
160266
+ if (!isAnchorRow(anchorRow))
160267
+ return null;
160268
+ const anchorInfo = parseJsonRecord(anchorRow.data ?? "");
160269
+ if (anchorInfo?.summary === true && anchorInfo?.finish === "stop")
160270
+ return null;
160271
+ const messageRows = db.prepare(`SELECT id, data, time_updated FROM message
160272
+ WHERE session_id = ?
160273
+ AND (time_created > ? OR (time_created = ? AND id >= ?))
160274
+ ORDER BY time_created ASC, id ASC`).all(sessionId, anchorRow.time_created, anchorRow.time_created, anchorRow.id).filter(isRawMessageRow);
160275
+ const filtered = messageRows.filter((row) => {
160276
+ const info = parseJsonRecord(row.data);
160277
+ return !(info?.summary === true && info?.finish === "stop");
160278
+ });
160279
+ const ids = filtered.map((row) => row.id);
160280
+ const partsByMessageId = new Map;
160281
+ if (ids.length > 0) {
160282
+ const CHUNK = 800;
160283
+ for (let i = 0;i < ids.length; i += CHUNK) {
160284
+ const slice = ids.slice(i, i + CHUNK);
160285
+ const placeholders = slice.map(() => "?").join(",");
160286
+ const partRows = db.prepare(`SELECT message_id, data, time_updated FROM part WHERE session_id = ? AND message_id IN (${placeholders}) ORDER BY time_created ASC, id ASC`).all(sessionId, ...slice).filter(isRawPartRow);
160287
+ for (const part of partRows) {
160288
+ const list = partsByMessageId.get(part.message_id) ?? [];
160289
+ list.push(attachRawPartVersion(parseJsonUnknown(part.data), part.time_updated));
160290
+ partsByMessageId.set(part.message_id, list);
160291
+ }
160292
+ }
160293
+ }
160294
+ const messages = [];
160295
+ let ord = baseOrdinal;
160296
+ for (const row of filtered) {
160297
+ const info = parseJsonRecord(row.data);
160298
+ if (!info) {
160299
+ ord += 1;
160300
+ continue;
160301
+ }
160302
+ messages.push({
160303
+ ordinal: ord,
160304
+ id: row.id,
160305
+ role: typeof info.role === "string" ? info.role : "unknown",
160306
+ parts: partsByMessageId.get(row.id) ?? [],
160307
+ version: row.time_updated ?? null
160308
+ });
160309
+ ord += 1;
160310
+ }
160311
+ return { messages, absoluteMessageCount: Math.max(0, ord - 1) };
160312
+ }
159840
160313
 
159841
160314
  // ../plugin/src/hooks/magic-context/tag-content-primitives.ts
159842
160315
  var encoder = new TextEncoder;
@@ -159981,6 +160454,7 @@ class ToolMutationBatch {
159981
160454
 
159982
160455
  // ../plugin/src/hooks/magic-context/read-session-chunk.ts
159983
160456
  var activeRawMessageCache = null;
160457
+ var activeAbsoluteCountCache = null;
159984
160458
  var sessionProviders = new Map;
159985
160459
  function setRawMessageProvider(sessionId, provider2) {
159986
160460
  sessionProviders.set(sessionId, provider2);
@@ -160012,12 +160486,14 @@ function withRawSessionMessageCache(fn) {
160012
160486
  const outerCache = activeRawMessageCache;
160013
160487
  if (!outerCache) {
160014
160488
  activeRawMessageCache = new Map;
160489
+ activeAbsoluteCountCache = new Map;
160015
160490
  }
160016
160491
  try {
160017
160492
  return fn();
160018
160493
  } finally {
160019
160494
  if (!outerCache) {
160020
160495
  activeRawMessageCache = null;
160496
+ activeAbsoluteCountCache = null;
160021
160497
  }
160022
160498
  }
160023
160499
  }
@@ -160033,6 +160509,40 @@ function readRawSessionMessages(sessionId) {
160033
160509
  }
160034
160510
  return readRawSessionMessagesFromSource(sessionId);
160035
160511
  }
160512
+ function primeTailRawMessageCache(args) {
160513
+ const { sessionId, lastCompartmentEnd, anchorMessageId } = args;
160514
+ if (!activeRawMessageCache)
160515
+ return false;
160516
+ if (activeRawMessageCache.has(sessionId))
160517
+ return false;
160518
+ if (sessionProviders.has(sessionId))
160519
+ return false;
160520
+ if (!openCodeDbExists())
160521
+ return false;
160522
+ if (lastCompartmentEnd < 1 || !anchorMessageId)
160523
+ return false;
160524
+ const result = withReadOnlySessionDb((db) => readRawSessionTailFromDb(db, sessionId, lastCompartmentEnd, anchorMessageId));
160525
+ if (!result)
160526
+ return false;
160527
+ activeRawMessageCache.set(sessionId, result.messages);
160528
+ activeAbsoluteCountCache?.set(sessionId, result.absoluteMessageCount);
160529
+ return true;
160530
+ }
160531
+ function getCachedAbsoluteMessageCount(sessionId) {
160532
+ return activeAbsoluteCountCache?.get(sessionId) ?? null;
160533
+ }
160534
+ function primeInMemoryTailRawMessageCache(args) {
160535
+ const { sessionId, messages, absoluteMessageCount } = args;
160536
+ if (!activeRawMessageCache)
160537
+ return false;
160538
+ if (activeRawMessageCache.has(sessionId))
160539
+ return false;
160540
+ if (sessionProviders.has(sessionId))
160541
+ return false;
160542
+ activeRawMessageCache.set(sessionId, messages);
160543
+ activeAbsoluteCountCache?.set(sessionId, absoluteMessageCount);
160544
+ return true;
160545
+ }
160036
160546
  function readRawSessionMessagesFromSource(sessionId) {
160037
160547
  const provider2 = sessionProviders.get(sessionId);
160038
160548
  if (provider2)
@@ -160097,7 +160607,7 @@ function getRawSessionTagKeysThrough(sessionId, upToMessageIndex) {
160097
160607
  return { messageFileKeys, toolObservations };
160098
160608
  }
160099
160609
  var PROTECTED_TAIL_USER_TURNS = 5;
160100
- function getProtectedTailStartOrdinal(sessionId) {
160610
+ function getLegacyProtectedTailStartOrdinal(sessionId) {
160101
160611
  const messages = readRawSessionMessages(sessionId);
160102
160612
  const userOrdinals = messages.filter((m) => m.role === "user" && hasMeaningfulUserText(m.parts)).map((m) => m.ordinal);
160103
160613
  if (userOrdinals.length < PROTECTED_TAIL_USER_TURNS) {
@@ -160107,6 +160617,7 @@ function getProtectedTailStartOrdinal(sessionId) {
160107
160617
  }
160108
160618
  function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal) {
160109
160619
  const messages = readRawSessionMessages(sessionId);
160620
+ const totalMessageCount = getCachedAbsoluteMessageCount(sessionId) ?? messages.length;
160110
160621
  const startOrdinal = Math.max(1, offset);
160111
160622
  const lines = [];
160112
160623
  const lineMeta = [];
@@ -160234,7 +160745,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
160234
160745
  endMessageId: lastMessageId,
160235
160746
  messageCount: messagesProcessed,
160236
160747
  tokenEstimate: totalTokens,
160237
- hasMore: lastOrdinal < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, messages.length) : messages.length),
160748
+ hasMore: lastOrdinal < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, totalMessageCount) : totalMessageCount),
160238
160749
  text: lines.join(`
160239
160750
  `),
160240
160751
  lines: lineMeta,
@@ -160244,7 +160755,13 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
160244
160755
  }
160245
160756
 
160246
160757
  // ../plugin/src/tools/ctx-expand/constants.ts
160247
- var CTX_EXPAND_DESCRIPTION = "Decompress a compartment range to see the original conversation transcript. " + 'Use start/end from <compartment start="N" end="M"> attributes. ' + "Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.";
160758
+ var CTX_EXPAND_DESCRIPTION = `Recover the original conversation from your compacted history.
160759
+
160760
+ Older parts of this session are summarized into <compartment> blocks inside <session-history> — e.g. <compartment start="120" end="245" title="Fixed tagger collision">. Each one replaces the raw messages in that ordinal range with a summary. When the summary isn't enough — you need exact wording, a specific value, an error message, or the reasoning behind a decision — expand the range:
160761
+
160762
+ ctx_expand(start=120, end=245) ← the compartment's own start/end attributes
160763
+
160764
+ 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.`;
160248
160765
  var CTX_EXPAND_TOKEN_BUDGET = 15000;
160249
160766
 
160250
160767
  // ../../node_modules/.bun/typebox@1.1.38/node_modules/typebox/build/system/memory/memory.mjs
@@ -164413,11 +164930,31 @@ function findLastModelKeyFromBranch(entries) {
164413
164930
  }
164414
164931
  return;
164415
164932
  }
164933
+ function rawEntryVersion(entry) {
164934
+ const record4 = entry;
164935
+ const updated = record4.updatedAt ?? record4.updated_at ?? record4.timestamp;
164936
+ return typeof updated === "string" || typeof updated === "number" ? updated : entry.id;
164937
+ }
164938
+ function attachPiPartVersion(parts, version2) {
164939
+ return parts.map((part) => {
164940
+ if (part === null || typeof part !== "object" || Array.isArray(part))
164941
+ return part;
164942
+ try {
164943
+ Object.defineProperty(part, "__magicContextPartUpdatedAt", {
164944
+ value: version2,
164945
+ enumerable: false,
164946
+ configurable: true
164947
+ });
164948
+ } catch {}
164949
+ return part;
164950
+ });
164951
+ }
164416
164952
  function convertEntriesToRawMessages(entries) {
164417
164953
  const result = [];
164418
164954
  let nextOrdinal = 1;
164419
164955
  let pendingToolParts = [];
164420
164956
  let pendingFirstRealId = "";
164957
+ let pendingFirstRealVersion = "";
164421
164958
  for (const entry of entries) {
164422
164959
  if (!isMessageEntry(entry)) {
164423
164960
  continue;
@@ -164425,24 +164962,29 @@ function convertEntriesToRawMessages(entries) {
164425
164962
  const msg = entry.message;
164426
164963
  const role = msg.role;
164427
164964
  if (role === "toolResult") {
164428
- pendingToolParts.push(...synthesizeToolResultParts(msg));
164965
+ const version2 = rawEntryVersion(entry);
164966
+ pendingToolParts.push(...attachPiPartVersion(synthesizeToolResultParts(msg), version2));
164429
164967
  if (pendingFirstRealId === "") {
164430
164968
  pendingFirstRealId = entry.id;
164969
+ pendingFirstRealVersion = version2;
164431
164970
  }
164432
164971
  continue;
164433
164972
  }
164434
164973
  if (role === "user") {
164974
+ const version2 = rawEntryVersion(entry);
164435
164975
  const parts = [
164436
164976
  ...pendingToolParts,
164437
- ...synthesizeUserParts(msg)
164977
+ ...attachPiPartVersion(synthesizeUserParts(msg), version2)
164438
164978
  ];
164439
164979
  pendingToolParts = [];
164440
164980
  pendingFirstRealId = "";
164981
+ pendingFirstRealVersion = "";
164441
164982
  result.push({
164442
164983
  ordinal: nextOrdinal++,
164443
164984
  id: entry.id,
164444
164985
  role: "user",
164445
- parts
164986
+ parts,
164987
+ version: version2
164446
164988
  });
164447
164989
  continue;
164448
164990
  }
@@ -164452,16 +164994,20 @@ function convertEntriesToRawMessages(entries) {
164452
164994
  ordinal: nextOrdinal++,
164453
164995
  id: `${SYNTH_USER_ID_PREFIX}${pendingFirstRealId}`,
164454
164996
  role: "user",
164455
- parts: pendingToolParts
164997
+ parts: pendingToolParts,
164998
+ version: pendingFirstRealVersion
164456
164999
  });
164457
165000
  pendingToolParts = [];
164458
165001
  pendingFirstRealId = "";
165002
+ pendingFirstRealVersion = "";
164459
165003
  }
165004
+ const version2 = rawEntryVersion(entry);
164460
165005
  result.push({
164461
165006
  ordinal: nextOrdinal++,
164462
165007
  id: entry.id,
164463
165008
  role: "assistant",
164464
- parts: synthesizeAssistantParts(msg)
165009
+ parts: attachPiPartVersion(synthesizeAssistantParts(msg), version2),
165010
+ version: version2
164465
165011
  });
164466
165012
  continue;
164467
165013
  }
@@ -164469,7 +165015,8 @@ function convertEntriesToRawMessages(entries) {
164469
165015
  ordinal: nextOrdinal++,
164470
165016
  id: entry.id,
164471
165017
  role: typeof role === "string" ? role : "unknown",
164472
- parts: []
165018
+ parts: [],
165019
+ version: rawEntryVersion(entry)
164473
165020
  });
164474
165021
  }
164475
165022
  if (pendingToolParts.length > 0) {
@@ -164477,7 +165024,8 @@ function convertEntriesToRawMessages(entries) {
164477
165024
  ordinal: nextOrdinal,
164478
165025
  id: `${SYNTH_USER_ID_PREFIX}${pendingFirstRealId}`,
164479
165026
  role: "user",
164480
- parts: pendingToolParts
165027
+ parts: pendingToolParts,
165028
+ version: pendingFirstRealVersion
164481
165029
  });
164482
165030
  }
164483
165031
  return result;
@@ -164634,6 +165182,13 @@ function createCtxExpandTool(deps) {
164634
165182
  }
164635
165183
 
164636
165184
  // ../plugin/src/features/magic-context/memory/constants.ts
165185
+ var V2_MEMORY_CATEGORIES = [
165186
+ "PROJECT_RULES",
165187
+ "ARCHITECTURE",
165188
+ "CONSTRAINTS",
165189
+ "CONFIG_VALUES",
165190
+ "NAMING"
165191
+ ];
164637
165192
  var PROMOTABLE_CATEGORIES = [
164638
165193
  "PROJECT_RULES",
164639
165194
  "ARCHITECTURE",
@@ -165385,7 +165940,7 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
165385
165940
  }
165386
165941
  // ../plugin/src/features/magic-context/project-docs-hash.ts
165387
165942
  import { createHash as createHash6 } from "node:crypto";
165388
- import { readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
165943
+ import { lstatSync, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
165389
165944
  import path4 from "node:path";
165390
165945
  var PROJECT_DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
165391
165946
  var PROJECT_DOCS_DELIMITER = `
@@ -165393,6 +165948,7 @@ var PROJECT_DOCS_DELIMITER = `
165393
165948
  ---
165394
165949
 
165395
165950
  `;
165951
+ var MAX_PROJECT_DOC_BYTES = 256 * 1024;
165396
165952
  var docsCache = new Map;
165397
165953
  function canonicalizeDocContent(raw) {
165398
165954
  return raw.replace(/^\uFEFF/, "").replace(/\r\n/g, `
@@ -165402,9 +165958,10 @@ function canonicalizeDocContent(raw) {
165402
165958
  }
165403
165959
  function fingerprintFile(filePath) {
165404
165960
  try {
165405
- const stat2 = statSync2(filePath);
165961
+ const stat2 = lstatSync(filePath);
165962
+ const isReadableDoc = stat2.isFile() && stat2.size <= MAX_PROJECT_DOC_BYTES;
165406
165963
  return {
165407
- exists: stat2.isFile(),
165964
+ exists: isReadableDoc,
165408
165965
  mtimeMs: stat2.mtimeMs,
165409
165966
  size: stat2.size
165410
165967
  };
@@ -165447,6 +166004,16 @@ function readCanonicalPieces(projectDirectory, files) {
165447
166004
  if (!fingerprint?.exists) {
165448
166005
  continue;
165449
166006
  }
166007
+ let safeToRead = false;
166008
+ try {
166009
+ const st = lstatSync(filePath);
166010
+ safeToRead = st.isFile() && st.size <= MAX_PROJECT_DOC_BYTES;
166011
+ } catch {
166012
+ safeToRead = false;
166013
+ }
166014
+ if (!safeToRead) {
166015
+ continue;
166016
+ }
165450
166017
  const canonicalContent = canonicalizeDocContent(readFileSync4(filePath, "utf8"));
165451
166018
  hashPieces.push(`file:${filename}
165452
166019
  ${canonicalContent}`);
@@ -165623,12 +166190,6 @@ var AUTO_SEARCH_NO_HINT_REASONS = new Set([
165623
166190
  "stacked",
165624
166191
  "too-short"
165625
166192
  ]);
165626
- function isPersistedStickyTurnReminderRow(row) {
165627
- if (row === null || typeof row !== "object")
165628
- return false;
165629
- const r = row;
165630
- return typeof r.sticky_turn_reminder_text === "string" && typeof r.sticky_turn_reminder_message_id === "string";
165631
- }
165632
166193
  function isPersistedNoteNudgeRow(row) {
165633
166194
  if (row === null || typeof row !== "object")
165634
166195
  return false;
@@ -165694,6 +166255,139 @@ function getDefaultHistorianFailureState() {
165694
166255
  lastFailureAt: null
165695
166256
  };
165696
166257
  }
166258
+ var DEFAULT_PROTECTED_TAIL_META = {
166259
+ priorBoundaryOrdinal: 1,
166260
+ protectedTailPolicyVersion: 0,
166261
+ protectedTailDrainWindowStartedAt: 0,
166262
+ protectedTailDrainTokens: 0,
166263
+ recoveryNoEligibleHeadCount: 0,
166264
+ forceEmergencyBypassWindowStart: 0,
166265
+ forceEmergencyBypassUsed: 0
166266
+ };
166267
+ function toProtectedTailMeta(row) {
166268
+ if (row === null || typeof row !== "object")
166269
+ return { ...DEFAULT_PROTECTED_TAIL_META };
166270
+ const r = row;
166271
+ const numberOr = (value, fallback) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
166272
+ return {
166273
+ priorBoundaryOrdinal: Math.max(1, numberOr(r.prior_boundary_ordinal, 1)),
166274
+ protectedTailPolicyVersion: numberOr(r.protected_tail_policy_version, 0),
166275
+ protectedTailDrainWindowStartedAt: numberOr(r.protected_tail_drain_window_started_at, 0),
166276
+ protectedTailDrainTokens: numberOr(r.protected_tail_drain_tokens, 0),
166277
+ recoveryNoEligibleHeadCount: numberOr(r.recovery_no_eligible_head_count, 0),
166278
+ forceEmergencyBypassWindowStart: numberOr(r.force_emergency_bypass_window_start, 0),
166279
+ forceEmergencyBypassUsed: numberOr(r.force_emergency_bypass_used, 0)
166280
+ };
166281
+ }
166282
+ function loadProtectedTailMeta(db, sessionId) {
166283
+ ensureSessionMetaRow(db, sessionId);
166284
+ const row = db.prepare(`SELECT prior_boundary_ordinal, protected_tail_policy_version,
166285
+ protected_tail_drain_window_started_at, protected_tail_drain_tokens,
166286
+ recovery_no_eligible_head_count, force_emergency_bypass_window_start,
166287
+ force_emergency_bypass_used
166288
+ FROM session_meta WHERE session_id = ?`).get(sessionId);
166289
+ return toProtectedTailMeta(row);
166290
+ }
166291
+ function markProtectedTailPolicyV3Seeded(db, sessionId, priorBoundaryOrdinal) {
166292
+ let seeded = false;
166293
+ db.transaction(() => {
166294
+ ensureSessionMetaRow(db, sessionId);
166295
+ const existing = loadProtectedTailMeta(db, sessionId);
166296
+ if (existing.protectedTailPolicyVersion < 3) {
166297
+ db.prepare(`UPDATE session_meta
166298
+ SET prior_boundary_ordinal = ?, protected_tail_policy_version = 3
166299
+ WHERE session_id = ? AND protected_tail_policy_version < 3`).run(Math.max(1, Math.floor(priorBoundaryOrdinal)), sessionId);
166300
+ seeded = true;
166301
+ }
166302
+ })();
166303
+ return { ...loadProtectedTailMeta(db, sessionId), seeded };
166304
+ }
166305
+ function recordProtectedTailPublicationFloor(db, sessionId, floorOrdinal) {
166306
+ db.transaction(() => {
166307
+ ensureSessionMetaRow(db, sessionId);
166308
+ db.prepare(`UPDATE session_meta
166309
+ SET prior_boundary_ordinal = MAX(COALESCE(prior_boundary_ordinal, 1), ?),
166310
+ recovery_no_eligible_head_count = 0
166311
+ WHERE session_id = ?`).run(Math.max(1, Math.floor(floorOrdinal)), sessionId);
166312
+ })();
166313
+ }
166314
+ function recordProtectedTailNoEligibleHead(db, sessionId) {
166315
+ db.transaction(() => {
166316
+ ensureSessionMetaRow(db, sessionId);
166317
+ db.prepare(`UPDATE session_meta
166318
+ SET recovery_no_eligible_head_count = COALESCE(recovery_no_eligible_head_count, 0) + 1
166319
+ WHERE session_id = ?`).run(sessionId);
166320
+ })();
166321
+ return loadProtectedTailMeta(db, sessionId).recoveryNoEligibleHeadCount;
166322
+ }
166323
+ var DRAIN_WINDOW_MS = 10 * 60 * 1000;
166324
+ function protectedTailWindowBudget(usagePercentage, usable, perRunCap) {
166325
+ if (usagePercentage >= 95)
166326
+ return Math.min(1e6, Math.max(4 * perRunCap, Math.round(0.5 * usable)));
166327
+ if (usagePercentage >= 80)
166328
+ return Math.min(750000, Math.max(3 * perRunCap, Math.round(0.35 * usable)));
166329
+ return Math.min(500000, Math.max(perRunCap, Math.round(0.2 * usable)));
166330
+ }
166331
+ function reserveProtectedTailDrainTokens(args) {
166332
+ const now = args.now ?? Date.now();
166333
+ const requested = Math.max(0, Math.floor(args.trueRawTokens));
166334
+ if (requested === 0) {
166335
+ return { ok: true, reservedTokens: 0, overQuotaBypass: false, reservation: null };
166336
+ }
166337
+ let result = {
166338
+ ok: false,
166339
+ reservedTokens: 0,
166340
+ overQuotaBypass: false,
166341
+ reservation: null,
166342
+ skippedReason: "quota exhausted"
166343
+ };
166344
+ args.db.transaction(() => {
166345
+ ensureSessionMetaRow(args.db, args.sessionId);
166346
+ let meta3 = loadProtectedTailMeta(args.db, args.sessionId);
166347
+ if (now - meta3.protectedTailDrainWindowStartedAt > DRAIN_WINDOW_MS) {
166348
+ args.db.prepare(`UPDATE session_meta
166349
+ SET protected_tail_drain_window_started_at = ?, protected_tail_drain_tokens = 0,
166350
+ force_emergency_bypass_window_start = ?, force_emergency_bypass_used = 0
166351
+ WHERE session_id = ?`).run(now, now, args.sessionId);
166352
+ meta3 = loadProtectedTailMeta(args.db, args.sessionId);
166353
+ }
166354
+ const budget = protectedTailWindowBudget(args.usagePercentage, args.usable, args.perRunCap);
166355
+ const remaining = Math.max(0, budget - meta3.protectedTailDrainTokens);
166356
+ let reserved = Math.min(requested, args.perRunCap, remaining);
166357
+ let bypass = false;
166358
+ const bypassWindowExpired = now - meta3.forceEmergencyBypassWindowStart > DRAIN_WINDOW_MS;
166359
+ const bypassUsed = bypassWindowExpired ? 0 : meta3.forceEmergencyBypassUsed;
166360
+ if (reserved <= 0 && args.usagePercentage >= 95 && bypassUsed === 0) {
166361
+ reserved = Math.min(requested, args.perRunCap);
166362
+ bypass = true;
166363
+ }
166364
+ if (reserved <= 0)
166365
+ return;
166366
+ args.db.prepare(`UPDATE session_meta
166367
+ SET protected_tail_drain_window_started_at = CASE WHEN protected_tail_drain_window_started_at = 0 THEN ? ELSE protected_tail_drain_window_started_at END,
166368
+ protected_tail_drain_tokens = COALESCE(protected_tail_drain_tokens, 0) + ?,
166369
+ force_emergency_bypass_window_start = CASE WHEN ? THEN ? ELSE force_emergency_bypass_window_start END,
166370
+ force_emergency_bypass_used = CASE WHEN ? THEN 1 ELSE force_emergency_bypass_used END
166371
+ WHERE session_id = ?`).run(now, reserved, bypass ? 1 : 0, now, bypass ? 1 : 0, args.sessionId);
166372
+ result = {
166373
+ ok: true,
166374
+ reservedTokens: reserved,
166375
+ overQuotaBypass: bypass,
166376
+ reservation: { sessionId: args.sessionId, runId: args.runId, tokens: reserved }
166377
+ };
166378
+ })();
166379
+ return result;
166380
+ }
166381
+ function rollbackProtectedTailDrainReservation(db, reservation) {
166382
+ if (!reservation || reservation.tokens <= 0)
166383
+ return;
166384
+ db.transaction(() => {
166385
+ ensureSessionMetaRow(db, reservation.sessionId);
166386
+ db.prepare(`UPDATE session_meta
166387
+ SET protected_tail_drain_tokens = MAX(0, COALESCE(protected_tail_drain_tokens, 0) - ?)
166388
+ WHERE session_id = ?`).run(reservation.tokens, reservation.sessionId);
166389
+ })();
166390
+ }
165697
166391
  function setPersistedReasoningWatermark(db, sessionId, tagNumber) {
165698
166392
  ensureSessionMetaRow(db, sessionId);
165699
166393
  db.prepare("UPDATE session_meta SET cleared_reasoning_through_tag = ? WHERE session_id = ?").run(tagNumber, sessionId);
@@ -165701,27 +166395,95 @@ function setPersistedReasoningWatermark(db, sessionId, tagNumber) {
165701
166395
  function clearPersistedReasoningWatermark(db, sessionId) {
165702
166396
  setPersistedReasoningWatermark(db, sessionId, 0);
165703
166397
  }
165704
- function getPersistedStickyTurnReminder(db, sessionId) {
165705
- const result = db.prepare("SELECT sticky_turn_reminder_text, sticky_turn_reminder_message_id FROM session_meta WHERE session_id = ?").get(sessionId);
165706
- if (!isPersistedStickyTurnReminderRow(result)) {
165707
- return null;
165708
- }
165709
- if (result.sticky_turn_reminder_text.length === 0) {
165710
- return null;
165711
- }
165712
- return {
165713
- text: result.sticky_turn_reminder_text,
165714
- messageId: result.sticky_turn_reminder_message_id.length > 0 ? result.sticky_turn_reminder_message_id : null
165715
- };
166398
+ function isEmergencyInputSampleRow(row) {
166399
+ return typeof row === "object" && row !== null && typeof row.last_emergency_input_sample === "number";
166400
+ }
166401
+ function getEmergencyInputSample(db, sessionId) {
166402
+ const result = db.prepare("SELECT last_emergency_input_sample FROM session_meta WHERE session_id = ?").get(sessionId);
166403
+ return isEmergencyInputSampleRow(result) ? result.last_emergency_input_sample : 0;
166404
+ }
166405
+ function setEmergencyDropSample(db, sessionId, inputSample) {
166406
+ db.transaction(() => {
166407
+ ensureSessionMetaRow(db, sessionId);
166408
+ db.prepare("UPDATE session_meta SET last_emergency_input_sample = ? WHERE session_id = ?").run(Math.max(0, Math.round(inputSample)), sessionId);
166409
+ })();
165716
166410
  }
165717
- function setPersistedStickyTurnReminder(db, sessionId, text, messageId = "") {
166411
+ function clearEmergencyDropSample(db, sessionId) {
165718
166412
  db.transaction(() => {
165719
166413
  ensureSessionMetaRow(db, sessionId);
165720
- db.prepare("UPDATE session_meta SET sticky_turn_reminder_text = ?, sticky_turn_reminder_message_id = ? WHERE session_id = ?").run(text, messageId, sessionId);
166414
+ db.prepare("UPDATE session_meta SET last_emergency_input_sample = 0 WHERE session_id = ?").run(sessionId);
165721
166415
  })();
165722
166416
  }
165723
- function clearPersistedStickyTurnReminder(db, sessionId) {
165724
- db.prepare("UPDATE session_meta SET sticky_turn_reminder_text = '', sticky_turn_reminder_message_id = '' WHERE session_id = ?").run(sessionId);
166417
+ function isLastNudgeUndroppedRow(row) {
166418
+ return typeof row === "object" && row !== null && typeof row.last_nudge_undropped === "number";
166419
+ }
166420
+ function isLastNudgeLevelRow(row) {
166421
+ return typeof row === "object" && row !== null && typeof row.last_nudge_level === "string";
166422
+ }
166423
+ function normalizeLastNudgeLevel(value) {
166424
+ return value === "gentle" || value === "firm" || value === "urgent" ? value : "";
166425
+ }
166426
+ function getLastNudgeUndropped(db, sessionId) {
166427
+ const result = db.prepare("SELECT last_nudge_undropped FROM session_meta WHERE session_id = ?").get(sessionId);
166428
+ return isLastNudgeUndroppedRow(result) ? result.last_nudge_undropped : 0;
166429
+ }
166430
+ function setLastNudgeUndropped(db, sessionId, value) {
166431
+ db.transaction(() => {
166432
+ ensureSessionMetaRow(db, sessionId);
166433
+ db.prepare("UPDATE session_meta SET last_nudge_undropped = ? WHERE session_id = ?").run(Math.max(0, Math.round(value)), sessionId);
166434
+ })();
166435
+ }
166436
+ function getLastNudgeLevel(db, sessionId) {
166437
+ const result = db.prepare("SELECT last_nudge_level FROM session_meta WHERE session_id = ?").get(sessionId);
166438
+ return isLastNudgeLevelRow(result) ? normalizeLastNudgeLevel(result.last_nudge_level) : "";
166439
+ }
166440
+ function setLastNudgeLevel(db, sessionId, value) {
166441
+ db.transaction(() => {
166442
+ ensureSessionMetaRow(db, sessionId);
166443
+ db.prepare("UPDATE session_meta SET last_nudge_level = ? WHERE session_id = ?").run(normalizeLastNudgeLevel(value), sessionId);
166444
+ })();
166445
+ }
166446
+ function resetLastNudgeCycle(db, sessionId) {
166447
+ db.transaction(() => {
166448
+ ensureSessionMetaRow(db, sessionId);
166449
+ db.prepare("UPDATE session_meta SET last_nudge_undropped = 0, last_nudge_level = '' WHERE session_id = ?").run(sessionId);
166450
+ })();
166451
+ }
166452
+ function resetLastNudgeCycleIfTailShrank(db, sessionId, measuredUndropped) {
166453
+ let changed = false;
166454
+ db.transaction(() => {
166455
+ ensureSessionMetaRow(db, sessionId);
166456
+ const result = db.prepare("UPDATE session_meta SET last_nudge_undropped = 0, last_nudge_level = '' WHERE session_id = ? AND last_nudge_undropped > ?").run(sessionId, Math.max(0, Math.round(measuredUndropped)));
166457
+ changed = (result.changes ?? 0) > 0;
166458
+ })();
166459
+ return changed;
166460
+ }
166461
+ function isChannel2StateRow(row) {
166462
+ return typeof row === "object" && row !== null && typeof row.channel2_nudge_state === "string";
166463
+ }
166464
+ function getChannel2NudgeState(db, sessionId) {
166465
+ const result = db.prepare("SELECT channel2_nudge_state FROM session_meta WHERE session_id = ?").get(sessionId);
166466
+ if (!isChannel2StateRow(result))
166467
+ return "";
166468
+ const raw = result.channel2_nudge_state;
166469
+ return raw === "pending" || raw === "claimed" || raw === "delivered" ? raw : "";
166470
+ }
166471
+ function setChannel2NudgeState(db, sessionId, state) {
166472
+ db.transaction(() => {
166473
+ ensureSessionMetaRow(db, sessionId);
166474
+ const claimedAt = state === "claimed" ? Date.now() : 0;
166475
+ db.prepare("UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at = ? WHERE session_id = ?").run(state, claimedAt, sessionId);
166476
+ })();
166477
+ }
166478
+ function casChannel2NudgeState(db, sessionId, from, to) {
166479
+ let changed = false;
166480
+ db.transaction(() => {
166481
+ ensureSessionMetaRow(db, sessionId);
166482
+ const claimedAt = to === "claimed" ? Date.now() : 0;
166483
+ const result = db.prepare("UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at = ? WHERE session_id = ? AND channel2_nudge_state = ?").run(to, claimedAt, sessionId, from);
166484
+ changed = (result.changes ?? 0) > 0;
166485
+ })();
166486
+ return changed;
165725
166487
  }
165726
166488
  function getPersistedNoteNudge(db, sessionId) {
165727
166489
  const result = db.prepare("SELECT note_nudge_trigger_pending, note_nudge_trigger_message_id, note_nudge_sticky_text, note_nudge_sticky_message_id FROM session_meta WHERE session_id = ?").get(sessionId);
@@ -165768,7 +166530,8 @@ function casUpdateJsonArrayColumn(db, sessionId, column, validator, mutate, opti
165768
166530
  }
165769
166531
  for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
165770
166532
  const row = db.prepare(`SELECT ${column} FROM session_meta WHERE session_id = ?`).get(sessionId);
165771
- const currentBlob = row?.[column] ?? "[]";
166533
+ const rawCurrent = row?.[column] ?? null;
166534
+ const currentBlob = rawCurrent ?? "[]";
165772
166535
  const current = parseJsonArray(currentBlob, validator);
165773
166536
  const next = mutate(current);
165774
166537
  if (next === null)
@@ -165776,7 +166539,7 @@ function casUpdateJsonArrayColumn(db, sessionId, column, validator, mutate, opti
165776
166539
  const nextBlob = stableStringify(next);
165777
166540
  if (nextBlob === currentBlob)
165778
166541
  return true;
165779
- const result = db.prepare(`UPDATE session_meta SET ${column} = ? WHERE session_id = ? AND ${column} = ?`).run(nextBlob, sessionId, currentBlob);
166542
+ const result = db.prepare(`UPDATE session_meta SET ${column} = ? WHERE session_id = ? AND ${column} IS ?`).run(nextBlob, sessionId, rawCurrent);
165780
166543
  if (result.changes > 0)
165781
166544
  return true;
165782
166545
  }
@@ -165905,14 +166668,16 @@ function getHistorianFailureState(db, sessionId) {
165905
166668
  };
165906
166669
  }
165907
166670
  function incrementHistorianFailure(db, sessionId, error51) {
166671
+ let nextCount = 1;
165908
166672
  db.transaction(() => {
165909
166673
  ensureSessionMetaRow(db, sessionId);
165910
166674
  const current = getHistorianFailureState(db, sessionId);
165911
- const nextCount = current.failureCount + 1;
166675
+ nextCount = current.failureCount + 1;
165912
166676
  db.prepare("UPDATE session_meta SET historian_failure_count = ?, historian_last_error = ?, historian_last_failure_at = ? WHERE session_id = ?").run(nextCount, error51, Date.now(), sessionId);
165913
166677
  const reason = error51.replace(/\s+/g, " ").trim().slice(0, 300);
165914
166678
  sessionLog(sessionId, `historian failure recorded: count=${nextCount} reason="${reason}"`);
165915
166679
  })();
166680
+ return nextCount;
165916
166681
  }
165917
166682
  function clearHistorianFailureState(db, sessionId) {
165918
166683
  db.transaction(() => {
@@ -165942,7 +166707,11 @@ function recordOverflowDetected(db, sessionId, reportedLimit) {
165942
166707
  function clearEmergencyRecovery(db, sessionId) {
165943
166708
  db.transaction(() => {
165944
166709
  ensureSessionMetaRow(db, sessionId);
165945
- db.prepare("UPDATE session_meta SET needs_emergency_recovery = 0 WHERE session_id = ?").run(sessionId);
166710
+ try {
166711
+ db.prepare("UPDATE session_meta SET needs_emergency_recovery = 0, recovery_no_eligible_head_count = 0 WHERE session_id = ?").run(sessionId);
166712
+ } catch {
166713
+ db.prepare("UPDATE session_meta SET needs_emergency_recovery = 0 WHERE session_id = ?").run(sessionId);
166714
+ }
165946
166715
  })();
165947
166716
  }
165948
166717
  function clearDetectedContextLimit(db, sessionId) {
@@ -165981,10 +166750,39 @@ function getStrippedPlaceholderIds(db, sessionId) {
165981
166750
  } catch {}
165982
166751
  return new Set;
165983
166752
  }
165984
- function setStrippedPlaceholderIds(db, sessionId, ids) {
166753
+ function applyStrippedPlaceholderDelta(db, sessionId, delta) {
166754
+ const add = delta.add ? [...delta.add] : [];
166755
+ const remove = delta.remove ? [...delta.remove] : [];
166756
+ if (add.length === 0 && remove.length === 0)
166757
+ return true;
165985
166758
  ensureSessionMetaRow(db, sessionId);
165986
- const json2 = ids.size > 0 ? JSON.stringify([...ids]) : "";
165987
- db.prepare("UPDATE session_meta SET stripped_placeholder_ids = ? WHERE session_id = ?").run(json2, sessionId);
166759
+ for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
166760
+ const row = db.prepare("SELECT stripped_placeholder_ids FROM session_meta WHERE session_id = ?").get(sessionId);
166761
+ const rawStored = row ? row.stripped_placeholder_ids ?? null : null;
166762
+ const current = new Set(parseStrippedBlob(rawStored));
166763
+ for (const id of add)
166764
+ current.add(id);
166765
+ for (const id of remove)
166766
+ current.delete(id);
166767
+ const nextBlob = current.size > 0 ? JSON.stringify([...current]) : "";
166768
+ if (nextBlob === (rawStored ?? ""))
166769
+ return true;
166770
+ const result = db.prepare("UPDATE session_meta SET stripped_placeholder_ids = ? WHERE session_id = ? AND stripped_placeholder_ids IS ?").run(nextBlob, sessionId, rawStored);
166771
+ if (result.changes > 0)
166772
+ return true;
166773
+ }
166774
+ sessionLog(sessionId, `stripped_placeholder_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
166775
+ return false;
166776
+ }
166777
+ function parseStrippedBlob(raw) {
166778
+ if (!raw || raw.length === 0)
166779
+ return [];
166780
+ try {
166781
+ const parsed = JSON.parse(raw);
166782
+ if (Array.isArray(parsed))
166783
+ return parsed.filter((v) => typeof v === "string");
166784
+ } catch {}
166785
+ return [];
165988
166786
  }
165989
166787
  function isPendingCompactionMarker(value) {
165990
166788
  return typeof value === "object" && value !== null && typeof value.ordinal === "number" && typeof value.endMessageId === "string" && typeof value.publishedAt === "number";
@@ -166077,7 +166875,6 @@ import { Buffer as Buffer3 } from "node:buffer";
166077
166875
 
166078
166876
  // ../plugin/src/features/magic-context/resolve-subagent-fallback.ts
166079
166877
  init_logger();
166080
- await init_read_session_db();
166081
166878
  function resolveIsSubagentFromOpenCodeDb(sessionId) {
166082
166879
  try {
166083
166880
  return withReadOnlySessionDb((openCodeDb) => {
@@ -166111,6 +166908,9 @@ var SESSION_META_FALLBACK_SELECTS = {
166111
166908
  cached_m0_materialized_at: "NULL AS cached_m0_materialized_at",
166112
166909
  cached_m0_session_facts_version: "NULL AS cached_m0_session_facts_version",
166113
166910
  cached_m0_upgrade_state: "NULL AS cached_m0_upgrade_state",
166911
+ cached_m0_system_hash: "NULL AS cached_m0_system_hash",
166912
+ cached_m0_tool_set_hash: "NULL AS cached_m0_tool_set_hash",
166913
+ cached_m0_model_key: "NULL AS cached_m0_model_key",
166114
166914
  last_observed_model_key: "NULL AS last_observed_model_key",
166115
166915
  upgrade_reminded_at: "NULL AS upgrade_reminded_at"
166116
166916
  };
@@ -166293,7 +167093,8 @@ function updateNote(db, noteId, updates, scope) {
166293
167093
  sets.push("session_id = ?");
166294
167094
  params.push(updates.sessionId);
166295
167095
  }
166296
- if (updates.status !== undefined) {
167096
+ const smartConditionChanged = existing.type === "smart" && updates.surfaceCondition !== undefined && updates.surfaceCondition !== existing.surfaceCondition;
167097
+ if (updates.status !== undefined && !smartConditionChanged) {
166297
167098
  sets.push("status = ?");
166298
167099
  params.push(updates.status);
166299
167100
  }
@@ -166306,17 +167107,21 @@ function updateNote(db, noteId, updates, scope) {
166306
167107
  sets.push("surface_condition = ?");
166307
167108
  params.push(updates.surfaceCondition);
166308
167109
  }
166309
- if (updates.lastCheckedAt !== undefined) {
166310
- sets.push("last_checked_at = ?");
166311
- params.push(updates.lastCheckedAt);
166312
- }
166313
- if (updates.readyAt !== undefined) {
166314
- sets.push("ready_at = ?");
166315
- params.push(updates.readyAt);
166316
- }
166317
- if (updates.readyReason !== undefined) {
166318
- sets.push("ready_reason = ?");
166319
- params.push(updates.readyReason);
167110
+ if (smartConditionChanged) {
167111
+ sets.push("status = 'pending'", "last_checked_at = NULL", "ready_at = NULL", "ready_reason = NULL");
167112
+ } else {
167113
+ if (updates.lastCheckedAt !== undefined) {
167114
+ sets.push("last_checked_at = ?");
167115
+ params.push(updates.lastCheckedAt);
167116
+ }
167117
+ if (updates.readyAt !== undefined) {
167118
+ sets.push("ready_at = ?");
167119
+ params.push(updates.readyAt);
167120
+ }
167121
+ if (updates.readyReason !== undefined) {
167122
+ sets.push("ready_reason = ?");
167123
+ params.push(updates.readyReason);
167124
+ }
166320
167125
  }
166321
167126
  }
166322
167127
  if (sets.length === 1) {
@@ -166506,7 +167311,7 @@ var getTagNumberByMessageIdStatements = new WeakMap;
166506
167311
  function getInsertTagStatement(db) {
166507
167312
  let stmt = insertTagStatements.get(db);
166508
167313
  if (!stmt) {
166509
- stmt = db.prepare("INSERT INTO tags (session_id, message_id, type, byte_size, reasoning_byte_size, tag_number, tool_name, input_byte_size, harness, tool_owner_message_id, entry_fingerprint) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
167314
+ stmt = db.prepare("INSERT INTO tags (session_id, message_id, type, byte_size, reasoning_byte_size, tag_number, tool_name, input_byte_size, harness, tool_owner_message_id, entry_fingerprint, token_count, input_token_count, reasoning_token_count) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
166510
167315
  insertTagStatements.set(db, stmt);
166511
167316
  }
166512
167317
  return stmt;
@@ -166548,9 +167353,95 @@ function getUpdateTagInputByteSizeStatement(db) {
166548
167353
  function updateTagByteSize(db, sessionId, tagNumber, newByteSize) {
166549
167354
  getUpdateTagByteSizeStatement(db).run(newByteSize, sessionId, tagNumber);
166550
167355
  }
167356
+ var CONTENT_ID_SUFFIX = /:(?:p|file)\d+$/;
167357
+ function ownerMessageIdForTagRow(row) {
167358
+ if (row.type === "tool") {
167359
+ return row.tool_owner_message_id ?? row.message_id;
167360
+ }
167361
+ return row.message_id.replace(CONTENT_ID_SUFFIX, "");
167362
+ }
167363
+ function getActiveTagTokenAggregate(db, sessionId) {
167364
+ const row = db.prepare(`SELECT
167365
+ COALESCE(SUM(CASE WHEN type != 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0)
167366
+ + COALESCE(SUM(COALESCE(reasoning_token_count, 0)), 0) AS conversation,
167367
+ COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) + COALESCE(input_token_count, 0) ELSE 0 END), 0) AS tool_call,
167368
+ COALESCE(SUM(CASE WHEN type = 'tool' THEN COALESCE(token_count, 0) ELSE 0 END), 0) AS tool_output,
167369
+ COALESCE(SUM(CASE WHEN token_count IS NULL THEN 1 ELSE 0 END), 0) AS null_count
167370
+ FROM tags
167371
+ WHERE session_id = ? AND status = 'active'`).get(sessionId);
167372
+ return {
167373
+ conversation: row?.conversation ?? 0,
167374
+ toolCall: row?.tool_call ?? 0,
167375
+ toolOutput: row?.tool_output ?? 0,
167376
+ nullCount: row?.null_count ?? 0
167377
+ };
167378
+ }
167379
+ function getTriggerTagTokenUpperBound(db, sessionId) {
167380
+ const row = db.prepare(`SELECT
167381
+ COALESCE(SUM(COALESCE(token_count, 0) + COALESCE(input_token_count, 0) + COALESCE(reasoning_token_count, 0)), 0) AS bound,
167382
+ COALESCE(SUM(CASE WHEN token_count IS NULL THEN 1 ELSE 0 END), 0) AS null_count
167383
+ FROM tags
167384
+ WHERE session_id = ? AND status IN ('active', 'dropped')`).get(sessionId);
167385
+ return { bound: row?.bound ?? 0, nullCount: row?.null_count ?? 0 };
167386
+ }
166551
167387
  function updateTagInputByteSize(db, sessionId, tagNumber, newInputByteSize) {
166552
167388
  getUpdateTagInputByteSizeStatement(db).run(newInputByteSize, sessionId, tagNumber);
166553
167389
  }
167390
+ var updateTagTokenCountStatements = new WeakMap;
167391
+ var updateTagInputTokenCountStatements = new WeakMap;
167392
+ function getUpdateTagTokenCountStatement(db) {
167393
+ let stmt = updateTagTokenCountStatements.get(db);
167394
+ if (!stmt) {
167395
+ stmt = db.prepare("UPDATE tags SET token_count = ? WHERE session_id = ? AND tag_number = ?");
167396
+ updateTagTokenCountStatements.set(db, stmt);
167397
+ }
167398
+ return stmt;
167399
+ }
167400
+ function getUpdateTagInputTokenCountStatement(db) {
167401
+ let stmt = updateTagInputTokenCountStatements.get(db);
167402
+ if (!stmt) {
167403
+ stmt = db.prepare("UPDATE tags SET input_token_count = ? WHERE session_id = ? AND tag_number = ?");
167404
+ updateTagInputTokenCountStatements.set(db, stmt);
167405
+ }
167406
+ return stmt;
167407
+ }
167408
+ function updateTagTokenCount(db, sessionId, tagNumber, newTokenCount) {
167409
+ getUpdateTagTokenCountStatement(db).run(newTokenCount, sessionId, tagNumber);
167410
+ }
167411
+ function getAllStatusTagTokenTotalsFlat(db, sessionId) {
167412
+ const rows = db.prepare(`SELECT type, message_id, tool_owner_message_id, token_count, input_token_count, reasoning_token_count
167413
+ FROM tags
167414
+ WHERE session_id = ?`).all(sessionId);
167415
+ const totals = new Map;
167416
+ const nullMessageIds = new Set;
167417
+ for (const row of rows) {
167418
+ if (row.type === "tool" && row.tool_owner_message_id === null)
167419
+ continue;
167420
+ const owner = ownerMessageIdForTagRow(row);
167421
+ if (row.token_count === null) {
167422
+ nullMessageIds.add(owner);
167423
+ totals.delete(owner);
167424
+ continue;
167425
+ }
167426
+ if (nullMessageIds.has(owner))
167427
+ continue;
167428
+ const weight = (row.token_count ?? 0) + (row.input_token_count ?? 0) + (row.reasoning_token_count ?? 0);
167429
+ totals.set(owner, (totals.get(owner) ?? 0) + weight);
167430
+ }
167431
+ return { totals, nullMessageIds };
167432
+ }
167433
+ function updateTagInputTokenCount(db, sessionId, tagNumber, newInputTokenCount) {
167434
+ getUpdateTagInputTokenCountStatement(db).run(newInputTokenCount, sessionId, tagNumber);
167435
+ }
167436
+ function tagTokenCountIsNull(db, sessionId, tagNumber) {
167437
+ const row = db.prepare("SELECT token_count FROM tags WHERE session_id = ? AND tag_number = ?").get(sessionId, tagNumber);
167438
+ return row !== undefined && row.token_count === null;
167439
+ }
167440
+ function backfillTagTokenCounts(db, sessionId, tagNumber, counts) {
167441
+ db.prepare(`UPDATE tags
167442
+ SET token_count = ?, input_token_count = ?, reasoning_token_count = ?
167443
+ WHERE session_id = ? AND tag_number = ? AND token_count IS NULL`).run(counts.tokenCount ?? null, counts.inputTokenCount ?? null, counts.reasoningTokenCount ?? null, sessionId, tagNumber);
167444
+ }
166554
167445
  function getMaxTagNumberBySessionStatement(db) {
166555
167446
  let stmt = getMaxTagNumberBySessionStatements.get(db);
166556
167447
  if (!stmt) {
@@ -166603,8 +167494,8 @@ function isMaxTagNumberRow(row) {
166603
167494
  const r = row;
166604
167495
  return typeof r.max_tag_number === "number";
166605
167496
  }
166606
- function insertTag(db, sessionId, messageId, type, byteSize2, tagNumber, reasoningByteSize = 0, toolName = null, inputByteSize = 0, toolOwnerMessageId = null, entryFingerprint = null) {
166607
- getInsertTagStatement(db).run(sessionId, messageId, type, byteSize2, reasoningByteSize, tagNumber, toolName, inputByteSize, getHarness(), toolOwnerMessageId, entryFingerprint);
167497
+ function insertTag(db, sessionId, messageId, type, byteSize2, tagNumber, reasoningByteSize = 0, toolName = null, inputByteSize = 0, toolOwnerMessageId = null, entryFingerprint = null, tokenCounts = null) {
167498
+ getInsertTagStatement(db).run(sessionId, messageId, type, byteSize2, reasoningByteSize, tagNumber, toolName, inputByteSize, getHarness(), toolOwnerMessageId, entryFingerprint, tokenCounts?.tokenCount ?? null, tokenCounts?.inputTokenCount ?? null, tokenCounts?.reasoningTokenCount ?? null);
166608
167499
  return tagNumber;
166609
167500
  }
166610
167501
  function updateTagStatus(db, sessionId, tagId, status) {
@@ -166672,13 +167563,6 @@ function getTagsByNumbers(db, sessionId, tagNumbers) {
166672
167563
  const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND tag_number IN (${placeholders}) ORDER BY tag_number ASC, id ASC`).all(sessionId, ...tagNumbers).filter(isTagRow);
166673
167564
  return rows.map(toTagEntry);
166674
167565
  }
166675
- function getTopNBySize(db, sessionId, n) {
166676
- if (n <= 0) {
166677
- return [];
166678
- }
166679
- const rows = db.prepare(`SELECT ${TAG_SELECT_COLUMNS} FROM tags WHERE session_id = ? AND status = 'active' ORDER BY byte_size DESC, tag_number ASC LIMIT ?`).all(sessionId, n).filter(isTagRow);
166680
- return rows.map(toTagEntry);
166681
- }
166682
167566
  var getToolTagNumberByOwnerStatements = new WeakMap;
166683
167567
  var getNullOwnerToolTagStatements = new WeakMap;
166684
167568
  var adoptNullOwnerToolTagStatements = new WeakMap;
@@ -166746,43 +167630,41 @@ var ERROR_CLASSES = new Set([
166746
167630
  ]);
166747
167631
  // src/tools/ctx-memory.ts
166748
167632
  init_logger();
167633
+
167634
+ // ../plugin/src/tools/ctx-memory/constants.ts
167635
+ var CTX_MEMORY_DESCRIPTION = `Durable project knowledge shared across every session on this project.
167636
+
167637
+ Your active memories are already visible in <project-memory> (each with its id), and every future session starts with them — write one when you learn something future sessions must know: a project rule, an architectural fact, a hard-won constraint, a config value, or a naming convention. Keep each memory one standalone fact, phrased to make sense without this session's context.
167638
+
167639
+ Actions:
167640
+ - write: save a new memory (content + category).
167641
+ - update: rewrite one memory whose fact changed (ids: [one], content).
167642
+ - archive: retire wrong or obsolete memories (ids: [one or more], optional reason).
167643
+ - merge: collapse duplicates into one memory (ids: [two or more], content).
167644
+
167645
+ Example: ctx_memory(action="write", category="CONSTRAINTS", content="Pi stores sessions as JSONL under ~/.pi/agent/sessions/, not SQLite")`;
167646
+
167647
+ // src/tools/ctx-memory.ts
166749
167648
  var DEFAULT_LIST_LIMIT = 10;
166750
- var VALID_CATEGORIES = new Set(CATEGORY_PRIORITY);
166751
- function isMemoryCategory2(value) {
166752
- return VALID_CATEGORIES.has(value);
166753
- }
166754
- var ALL_ACTIONS = [
166755
- "write",
166756
- "delete",
166757
- "list",
166758
- "update",
166759
- "merge",
166760
- "archive"
166761
- ];
166762
- var DREAMER_ONLY_ACTIONS = new Set([
166763
- "list",
166764
- "update",
166765
- "merge",
166766
- "archive"
166767
- ]);
167649
+ var ALL_ACTIONS = ["write", "archive", "update", "merge", "list"];
167650
+ var DREAMER_ONLY_ACTIONS = new Set(["list"]);
166768
167651
  var ParamsSchema2 = exports_typebox.Object({
166769
- action: exports_typebox.Union(ALL_ACTIONS.map((a) => exports_typebox.Literal(a)), { description: "Action to perform on memories" }),
167652
+ action: exports_typebox.Union(ALL_ACTIONS.map((a) => exports_typebox.Literal(a)), { description: "What to do: write, update, archive, or merge" }),
166770
167653
  content: exports_typebox.Optional(exports_typebox.String({
166771
- description: "Memory content (required for write, update, merge)"
166772
- })),
166773
- category: exports_typebox.Optional(exports_typebox.String({
166774
- description: "Memory category (required for write, optional filter for list, optional override for merge). One of: " + CATEGORY_PRIORITY.join(", ")
167654
+ description: "The memory text — one standalone fact (required for write, update, merge)"
166775
167655
  })),
166776
- id: exports_typebox.Optional(exports_typebox.Number({
166777
- description: "Memory ID (required for delete, update, archive)"
167656
+ category: exports_typebox.Optional(exports_typebox.Union(V2_MEMORY_CATEGORIES.map((c) => exports_typebox.Literal(c)), {
167657
+ description: "What kind of fact this is (required for write; optional merge override)"
166778
167658
  })),
166779
167659
  ids: exports_typebox.Optional(exports_typebox.Array(exports_typebox.Number(), {
166780
- description: "Memory IDs to merge (required for merge)"
167660
+ description: "Target memory id(s) from <project-memory>: update takes exactly one, archive one or more, merge two or more"
166781
167661
  })),
166782
167662
  limit: exports_typebox.Optional(exports_typebox.Number({
166783
- description: "Maximum results to return for list (default: 10)"
167663
+ description: "Max results for list (default: 10)"
166784
167664
  })),
166785
- reason: exports_typebox.Optional(exports_typebox.String({ description: "Archive reason (optional for archive)" }))
167665
+ reason: exports_typebox.Optional(exports_typebox.String({
167666
+ description: "Why the memory is being archived (optional, recommended)"
167667
+ }))
166786
167668
  });
166787
167669
  function ok2(text) {
166788
167670
  return { content: [{ type: "text", text }], details: undefined };
@@ -166829,6 +167711,12 @@ function formatMemoryList(memories) {
166829
167711
  ].join(`
166830
167712
  `);
166831
167713
  }
167714
+ function isPrimaryMutableMemory(memory2) {
167715
+ return (memory2.status === "active" || memory2.status === "permanent") && memory2.supersededByMemoryId === null;
167716
+ }
167717
+ function inactiveMemoryError(id, action2) {
167718
+ return `Error: Memory with ID ${id} is archived or superseded; restore it before ${action2}.`;
167719
+ }
166832
167720
  function queueEmbedding(args) {
166833
167721
  const snapshot = getProjectEmbeddingSnapshot(args.projectIdentity);
166834
167722
  if (!snapshot?.enabled)
@@ -166849,7 +167737,8 @@ function queueEmbedding(args) {
166849
167737
  }
166850
167738
  function createCtxMemoryTool(deps) {
166851
167739
  const dreamerAllowed = deps.allowDreamerActions === true;
166852
- const description = dreamerAllowed ? "Manage cross-session project memories. Memories persist across sessions and are " + "shared with OpenCode sessions on the same project. " + "Supported actions: write, delete, list, update, merge, archive." : "Manage cross-session project memories. Memories persist across sessions and are " + "shared with OpenCode sessions on the same project. " + "Supported actions: write, delete.";
167740
+ const description = dreamerAllowed ? `${CTX_MEMORY_DESCRIPTION}
167741
+ - list: enumerate stored memories (maintenance sessions).` : CTX_MEMORY_DESCRIPTION;
166853
167742
  return {
166854
167743
  name: "ctx_memory",
166855
167744
  label: "Magic Context: Memory",
@@ -166870,13 +167759,10 @@ function createCtxMemoryTool(deps) {
166870
167759
  const content = params.content?.trim();
166871
167760
  if (!content)
166872
167761
  return err2("Error: 'content' is required when action is 'write'.");
166873
- const rawCategory = params.category?.trim();
167762
+ const rawCategory = params.category;
166874
167763
  if (!rawCategory) {
166875
167764
  return err2("Error: 'category' is required when action is 'write'.");
166876
167765
  }
166877
- if (!isMemoryCategory2(rawCategory)) {
166878
- return err2(`Error: Unknown memory category '${rawCategory}'. Valid: ${CATEGORY_PRIORITY.join(", ")}`);
166879
- }
166880
167766
  const existing = getMemoryByHash(deps.db, projectIdentity, rawCategory, computeNormalizedHash(content));
166881
167767
  if (existing) {
166882
167768
  updateMemorySeenCount(deps.db, existing.id);
@@ -166890,45 +167776,31 @@ function createCtxMemoryTool(deps) {
166890
167776
  sourceType: dreamerAllowed ? "dreamer" : "agent"
166891
167777
  });
166892
167778
  queueEmbedding({ deps, projectIdentity, memoryId: memory2.id, content });
166893
- invalidateAllMemoryBlockCaches(deps.db);
166894
167779
  return ok2(`Saved memory [ID: ${memory2.id}] in ${rawCategory}.`);
166895
167780
  }
166896
- if (params.action === "delete") {
166897
- if (typeof params.id !== "number" || !Number.isInteger(params.id)) {
166898
- return err2("Error: 'id' is required when action is 'delete'.");
166899
- }
166900
- const memory2 = getMemoryById(deps.db, params.id);
166901
- if (!memory2 || memory2.projectPath !== projectIdentity) {
166902
- return err2(`Error: Memory with ID ${params.id} was not found.`);
166903
- }
166904
- deps.db.transaction(() => {
166905
- archiveMemory(deps.db, params.id);
166906
- queueMemoryMutation(deps.db, {
166907
- projectPath: projectIdentity,
166908
- mutationType: "delete",
166909
- targetMemoryId: params.id
166910
- });
166911
- })();
166912
- return ok2(`Archived memory [ID: ${params.id}].`);
166913
- }
166914
167781
  if (params.action === "list") {
166915
167782
  const limit = normalizeLimit(params.limit);
166916
167783
  const filtered = getMemoriesByProject(deps.db, projectIdentity);
166917
- const category = params.category?.trim();
167784
+ const category = params.category;
166918
167785
  const filtered2 = category ? filtered.filter((m) => m.category === category) : filtered;
166919
167786
  return ok2(formatMemoryList(filtered2.slice(0, limit)));
166920
167787
  }
166921
167788
  if (params.action === "update") {
166922
- if (typeof params.id !== "number" || !Number.isInteger(params.id)) {
166923
- return err2("Error: 'id' is required when action is 'update'.");
167789
+ const updateIds = params.ids;
167790
+ if (!updateIds || updateIds.length !== 1 || !updateIds.every(Number.isInteger)) {
167791
+ return err2("Error: 'ids' must contain exactly one integer memory ID when action is 'update'.");
166924
167792
  }
167793
+ const updateId = updateIds[0];
166925
167794
  const content = params.content?.trim();
166926
167795
  if (!content) {
166927
167796
  return err2("Error: 'content' is required when action is 'update'.");
166928
167797
  }
166929
- const memory2 = getMemoryById(deps.db, params.id);
166930
- if (!memory2 || memory2.projectPath !== projectIdentity) {
166931
- return err2(`Error: Memory with ID ${params.id} was not found.`);
167798
+ const memory2 = getMemoryById(deps.db, updateId);
167799
+ if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
167800
+ return err2(`Error: Memory with ID ${updateId} was not found.`);
167801
+ }
167802
+ if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
167803
+ return err2(inactiveMemoryError(updateId, "updating"));
166932
167804
  }
166933
167805
  const normalizedHash = computeNormalizedHash(content);
166934
167806
  const duplicate = getMemoryByHash(deps.db, projectIdentity, memory2.category, normalizedHash);
@@ -166949,9 +167821,12 @@ function createCtxMemoryTool(deps) {
166949
167821
  return ok2(`Updated memory [ID: ${memory2.id}] in ${memory2.category}.`);
166950
167822
  }
166951
167823
  if (params.action === "merge") {
166952
- const ids = params.ids?.filter((id) => Number.isInteger(id));
166953
- if (!ids || ids.length < 2) {
166954
- return err2("Error: 'ids' must include at least two memory IDs when action is 'merge'.");
167824
+ const ids = params.ids;
167825
+ if (!ids || ids.length < 2 || !ids.every(Number.isInteger)) {
167826
+ return err2("Error: 'ids' must include at least two integer memory IDs when action is 'merge'.");
167827
+ }
167828
+ if (new Set(ids).size !== ids.length) {
167829
+ return err2("Error: 'ids' must include at least two distinct memory IDs when action is 'merge'.");
166955
167830
  }
166956
167831
  const content = params.content?.trim();
166957
167832
  if (!content) {
@@ -166961,14 +167836,17 @@ function createCtxMemoryTool(deps) {
166961
167836
  if (sourceMemories.length !== ids.length) {
166962
167837
  return err2("Error: One or more source memories were not found.");
166963
167838
  }
166964
- if (sourceMemories.some((memory2) => memory2.projectPath !== projectIdentity)) {
166965
- return err2("Error: All memories to merge must belong to the current project.");
166966
- }
166967
- const requestedCategory = params.category?.trim();
166968
- if (requestedCategory && !isMemoryCategory2(requestedCategory)) {
166969
- return err2(`Error: Unknown memory category '${requestedCategory}'. Valid: ${CATEGORY_PRIORITY.join(", ")}`);
167839
+ if (!dreamerAllowed) {
167840
+ const foreign = sourceMemories.find((memory2) => !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity));
167841
+ if (foreign) {
167842
+ return err2(`Error: Memory with ID ${foreign.id} was not found.`);
167843
+ }
167844
+ const inactive = sourceMemories.find((memory2) => !isPrimaryMutableMemory(memory2));
167845
+ if (inactive) {
167846
+ return err2(inactiveMemoryError(inactive.id, "merging"));
167847
+ }
166970
167848
  }
166971
- const requestedCategoryTyped = requestedCategory && isMemoryCategory2(requestedCategory) ? requestedCategory : undefined;
167849
+ const requestedCategoryTyped = params.category;
166972
167850
  const fallbackCategory = sourceMemories[0]?.category;
166973
167851
  const category = requestedCategoryTyped ?? fallbackCategory;
166974
167852
  if (!category) {
@@ -167022,7 +167900,7 @@ function createCtxMemoryTool(deps) {
167022
167900
  }
167023
167901
  supersededMemory(deps.db, memory2.id, canonicalMemory.id);
167024
167902
  queueMemoryMutation(deps.db, {
167025
- projectPath: memory2.projectPath,
167903
+ projectPath: normalizeStoredProjectPath(memory2.projectPath),
167026
167904
  mutationType: "superseded",
167027
167905
  targetMemoryId: memory2.id,
167028
167906
  supersededById: canonicalMemory.id
@@ -167030,7 +167908,7 @@ function createCtxMemoryTool(deps) {
167030
167908
  }
167031
167909
  if (canonicalExisting && canonicalContentChanged) {
167032
167910
  queueMemoryMutation(deps.db, {
167033
- projectPath: canonicalMemory.projectPath,
167911
+ projectPath: normalizeStoredProjectPath(canonicalMemory.projectPath),
167034
167912
  mutationType: "update",
167035
167913
  targetMemoryId: canonicalMemory.id,
167036
167914
  category,
@@ -167048,29 +167926,57 @@ function createCtxMemoryTool(deps) {
167048
167926
  return ok2(`Merged memories [${ids.join(", ")}] into canonical memory [ID: ${canonicalMemory.id}] in ${category}; superseded [${supersededIds.join(", ")}].`);
167049
167927
  }
167050
167928
  if (params.action === "archive") {
167051
- if (typeof params.id !== "number" || !Number.isInteger(params.id)) {
167052
- return err2("Error: 'id' is required when action is 'archive'.");
167929
+ const archiveIds = params.ids;
167930
+ if (!archiveIds || archiveIds.length === 0 || !archiveIds.every(Number.isInteger)) {
167931
+ return err2("Error: 'ids' must contain at least one integer memory ID when action is 'archive'.");
167053
167932
  }
167054
- const memory2 = getMemoryById(deps.db, params.id);
167055
- if (!memory2 || memory2.projectPath !== projectIdentity) {
167056
- return err2(`Error: Memory with ID ${params.id} was not found.`);
167933
+ for (const memoryId of archiveIds) {
167934
+ const memory2 = getMemoryById(deps.db, memoryId);
167935
+ if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
167936
+ return err2(`Error: Memory with ID ${memoryId} was not found.`);
167937
+ }
167938
+ if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
167939
+ return err2(inactiveMemoryError(memoryId, "archiving"));
167940
+ }
167057
167941
  }
167058
167942
  deps.db.transaction(() => {
167059
- archiveMemory(deps.db, params.id, params.reason);
167060
- queueMemoryMutation(deps.db, {
167061
- projectPath: projectIdentity,
167062
- mutationType: "archive",
167063
- targetMemoryId: params.id
167064
- });
167943
+ for (const memoryId of archiveIds) {
167944
+ archiveMemory(deps.db, memoryId, params.reason);
167945
+ queueMemoryMutation(deps.db, {
167946
+ projectPath: projectIdentity,
167947
+ mutationType: "archive",
167948
+ targetMemoryId: memoryId
167949
+ });
167950
+ }
167065
167951
  })();
167066
167952
  const reasonSuffix = params.reason ? ` (${params.reason})` : "";
167067
- return ok2(`Archived memory [ID: ${params.id}]${reasonSuffix}.`);
167953
+ const idList = archiveIds.join(", ");
167954
+ const plural = archiveIds.length > 1 ? "memories" : "memory";
167955
+ return ok2(`Archived ${plural} [ID: ${idList}]${reasonSuffix}.`);
167068
167956
  }
167069
167957
  return err2("Error: Unknown action.");
167070
167958
  }
167071
167959
  };
167072
167960
  }
167073
167961
 
167962
+ // ../plugin/src/tools/ctx-note/constants.ts
167963
+ var CTX_NOTE_DESCRIPTION = `Working notes for this session's future — reminders, follow-ups, and things to revisit later.
167964
+
167965
+ Use a note when something matters LATER but not in the next few steps: "revisit the retry logic after the release", "user wants the dashboard polish batched", "flaky test to investigate when touching CI". Don't use notes for active multi-step work (use todos) or for durable project knowledge that should outlive this session (use ctx_memory). Notes resurface automatically at natural work boundaries and whenever you read them.
167966
+
167967
+ Actions:
167968
+ - write: save a note (content). Add surface_condition to make it a smart note (below).
167969
+ - read: list notes, newest first. Default: latest active session notes + ready smart notes; page older ones with limit/offset, or inspect other states with filter.
167970
+ - update / dismiss: change or retire a note by note_id.
167971
+
167972
+ Smart notes: pass surface_condition and the note stays hidden until a background checker confirms the condition — using ONLY externally verifiable signals (GitHub state via gh, files on disk, git history, web pages). It cannot see this conversation, so the condition must be checkable from outside:
167973
+ ✓ "When PR #42 in cortexkit/magic-context is merged"
167974
+ ✓ "When the latest release tag is >= v0.22.0"
167975
+ ✓ "When packages/plugin/src/foo.ts contains a function named bar"
167976
+ ✗ "When the user mentions X" / "when we revisit Y" / "after we finish this refactor" — no external signal; write a regular note instead.
167977
+
167978
+ Example: ctx_note(action="write", content="Re-run the perf benchmark once the boundary rework ships", surface_condition="When the latest release tag is >= v0.23.0")`;
167979
+
167074
167980
  // src/tools/ctx-note.ts
167075
167981
  var FILTER_VALUES = [
167076
167982
  "active",
@@ -167090,13 +167996,19 @@ var ParamsSchema3 = exports_typebox.Object({
167090
167996
  })),
167091
167997
  content: exports_typebox.Optional(exports_typebox.String({ description: "Note text to store when action is 'write'." })),
167092
167998
  surface_condition: exports_typebox.Optional(exports_typebox.String({
167093
- description: "Open-ended condition for smart notes. When provided, creates a project-scoped smart note that the dreamer evaluates nightly. The note surfaces when the condition is met."
167999
+ description: "Externally verifiable condition for smart notes. A background checker verifies it using ONLY outside signals (GitHub state via gh, files on disk, git history, web) it cannot see this conversation. Use for PR/issue state, release tags, file contents, workflow runs. NOT for 'when the user mentions X' / 'when we revisit Y' — write a regular note instead."
167094
168000
  })),
167095
168001
  note_id: exports_typebox.Optional(exports_typebox.Number({
167096
168002
  description: "Note ID (required for 'dismiss' and 'update' actions)."
167097
168003
  })),
167098
168004
  filter: exports_typebox.Optional(exports_typebox.Union(FILTER_VALUES.map((value) => exports_typebox.Literal(value)), {
167099
168005
  description: "Optional read filter. Defaults to active session notes + ready smart notes. Use 'all' to inspect every status or 'pending' to inspect unsurfaced smart notes."
168006
+ })),
168007
+ limit: exports_typebox.Optional(exports_typebox.Number({
168008
+ description: "Max notes per section for read, newest first (default: 25)"
168009
+ })),
168010
+ offset: exports_typebox.Optional(exports_typebox.Number({
168011
+ description: "Skip this many newest notes for read — page older ones (default: 0)"
167100
168012
  }))
167101
168013
  });
167102
168014
  function ok3(text) {
@@ -167133,15 +168045,20 @@ function formatNoteLine(note) {
167133
168045
  var DISMISS_FOOTER = `
167134
168046
 
167135
168047
  To dismiss a stale note: ctx_note(action="dismiss", note_id=N)`;
168048
+ var DEFAULT_READ_LIMIT = 25;
168049
+ function paginateNewestFirst(notes, limit, offset) {
168050
+ const total = notes.length;
168051
+ const newestFirst = [...notes].reverse();
168052
+ const page = newestFirst.slice(offset, offset + limit);
168053
+ const remaining = total - offset - page.length;
168054
+ const footer = remaining > 0 ? `Showing ${page.length} of ${total} (newest first) — ${remaining} older: ctx_note(action="read", offset=${offset + page.length})` : null;
168055
+ return { page, total, footer };
168056
+ }
167136
168057
  function createCtxNoteTool(deps) {
167137
168058
  return {
167138
168059
  name: "ctx_note",
167139
168060
  label: "Magic Context: Notes",
167140
- description: `Save or inspect durable session notes that persist for this session.
167141
- ` + `Use this for short goals, constraints, decisions, or reminders worth carrying forward.
167142
-
167143
- ` + `Actions:
167144
- ` + "- `write`: Append one note. Optionally provide `surface_condition` to create a smart note.\n" + "- `read`: Show current notes. Defaults to active session notes + ready smart notes; use `filter` to inspect all, pending, ready, active, or dismissed notes.\n" + "- `dismiss`: Dismiss a note by `note_id`.\n" + "- `update`: Update a note by `note_id`.\n\n" + "**Smart Notes**: When `surface_condition` is provided with `write`, the note becomes a project-scoped smart note. " + "The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met. " + 'Example: `ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")`',
168061
+ description: CTX_NOTE_DESCRIPTION,
167145
168062
  parameters: ParamsSchema3,
167146
168063
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
167147
168064
  const sessionId = ctx.sessionManager.getSessionId();
@@ -167223,11 +168140,15 @@ function createCtxNoteTool(deps) {
167223
168140
  - ${parts.join(`
167224
168141
  - `)}`);
167225
168142
  }
168143
+ const limit = typeof params.limit === "number" && params.limit > 0 ? Math.floor(params.limit) : DEFAULT_READ_LIMIT;
168144
+ const offset = typeof params.offset === "number" && params.offset > 0 ? Math.floor(params.offset) : 0;
167226
168145
  const sections = readNotes({
167227
168146
  db: deps.db,
167228
168147
  sessionId,
167229
168148
  cwd: ctx.cwd,
167230
- filter: params.filter
168149
+ filter: params.filter,
168150
+ limit,
168151
+ offset
167231
168152
  });
167232
168153
  try {
167233
168154
  setNoteLastReadAt(deps.db, sessionId);
@@ -167235,7 +168156,7 @@ function createCtxNoteTool(deps) {
167235
168156
  if (sections.length === 0) {
167236
168157
  return ok3(`## Notes
167237
168158
 
167238
- No notes for the current filter.`);
168159
+ No session notes or smart notes.`);
167239
168160
  }
167240
168161
  const body = sections.join(`
167241
168162
 
@@ -167262,10 +168183,14 @@ function readNotes(args) {
167262
168183
  }) : [];
167263
168184
  const sections2 = [];
167264
168185
  if (sessionNotes2.length > 0) {
168186
+ const { page, footer } = paginateNewestFirst(sessionNotes2, args.limit, args.offset);
168187
+ const lines = page.map(formatNoteLine).join(`
168188
+ `);
167265
168189
  sections2.push(`## Session Notes
167266
168190
 
167267
- ${sessionNotes2.map(formatNoteLine).join(`
167268
- `)}`);
168191
+ ${lines}${footer ? `
168192
+
168193
+ ${footer}` : ""}`);
167269
168194
  }
167270
168195
  if (readySmartNotes.length > 0) {
167271
168196
  sections2.push(`## \uD83D\uDD14 Ready Smart Notes
@@ -167296,17 +168221,25 @@ ${readySmartNotes.map(formatNoteLine).join(`
167296
168221
  }) : [];
167297
168222
  const sections = [];
167298
168223
  if (sessionNotes.length > 0) {
168224
+ const { page, footer } = paginateNewestFirst(sessionNotes, args.limit, args.offset);
168225
+ const lines = page.map(formatNoteLine).join(`
168226
+ `);
167299
168227
  sections.push(`## Session Notes
167300
168228
 
167301
- ${sessionNotes.map(formatNoteLine).join(`
167302
- `)}`);
168229
+ ${lines}${footer ? `
168230
+
168231
+ ${footer}` : ""}`);
167303
168232
  }
167304
168233
  if (smartNotes.length > 0) {
168234
+ const { page, footer } = paginateNewestFirst(smartNotes, args.limit, args.offset);
168235
+ const lines = page.map(formatNoteLine).join(`
168236
+
168237
+ `);
167305
168238
  sections.push(`## Smart Notes
167306
168239
 
167307
- ${smartNotes.map(formatNoteLine).join(`
168240
+ ${lines}${footer ? `
167308
168241
 
167309
- `)}`);
168242
+ ${footer}` : ""}`);
167310
168243
  }
167311
168244
  return sections;
167312
168245
  }
@@ -167314,7 +168247,7 @@ ${smartNotes.map(formatNoteLine).join(`
167314
168247
  // ../plugin/src/features/magic-context/range-parser.ts
167315
168248
  function parseRangeString(input) {
167316
168249
  const maxRangeElements = 1000;
167317
- const trimmed = input.trim();
168250
+ const trimmed = input.replace(/§/g, "").trim();
167318
168251
  if (trimmed === "") {
167319
168252
  throw new Error("Range string must not be empty");
167320
168253
  }
@@ -168504,6 +169437,9 @@ function escapeXmlAttr2(s) {
168504
169437
  function escapeXmlContent2(s) {
168505
169438
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
168506
169439
  }
169440
+ function isTieredRow(c) {
169441
+ return typeof c.p1 === "string" && c.p1.length > 0;
169442
+ }
168507
169443
  function tierBody(c, tier2) {
168508
169444
  const tiers = [c.p1, c.p2, c.p3, c.p4];
168509
169445
  const requested = tiers[tier2 - 1];
@@ -168533,12 +169469,13 @@ function renderOneCompartment(c, tier2) {
168533
169469
  const baseAttrs = `start="${c.startMessage}" end="${c.endMessage}" title="${escapeXmlAttr2(c.title)}"`;
168534
169470
  if (tier2 >= 5)
168535
169471
  return "";
168536
- if (c.legacy === 1) {
168537
- if (tier2 >= 4)
169472
+ if (c.legacy === 1 || !isTieredRow(c)) {
169473
+ const flat = (c.content ?? "").trim();
169474
+ if (tier2 >= 4 || flat.length === 0)
168538
169475
  return `<compartment ${baseAttrs} />`;
168539
169476
  return [
168540
169477
  `<compartment ${baseAttrs}>`,
168541
- escapeXmlContent2(legacyBodyForTier(c.content, tier2)),
169478
+ escapeXmlContent2(legacyBodyForTier(flat, tier2)),
168542
169479
  "</compartment>"
168543
169480
  ].join(`
168544
169481
  `);
@@ -168615,26 +169552,58 @@ import { join as join9, sep as sep2 } from "node:path";
168615
169552
  var import_comment_json2 = __toESM(require_src2(), 1);
168616
169553
  import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
168617
169554
  import { homedir as homedir4 } from "node:os";
168618
- import { join as join8 } from "node:path";
169555
+ import { isAbsolute as isAbsolute2, join as join8, resolve as resolve3 } from "node:path";
169556
+ import { fileURLToPath } from "node:url";
168619
169557
  var overrideAvailability = null;
168620
169558
  function parseConfig(path5) {
168621
169559
  if (!existsSync7(path5))
168622
169560
  return null;
168623
169561
  return import_comment_json2.parse(readFileSync5(path5, "utf-8"));
168624
169562
  }
168625
- function entryMatchesAft(entry) {
169563
+ var AFT_NAME_NEEDLES = ["@cortexkit/aft", "aft-opencode", "aft-pi"];
169564
+ function stringMentionsAft(value) {
169565
+ return AFT_NAME_NEEDLES.some((needle) => value.includes(needle));
169566
+ }
169567
+ function resolveLocalEntryPackageName(value, configDir) {
169568
+ let dir = null;
169569
+ if (value.startsWith("file://")) {
169570
+ try {
169571
+ dir = fileURLToPath(value);
169572
+ } catch {
169573
+ return null;
169574
+ }
169575
+ } else if (value.startsWith("~/")) {
169576
+ dir = join8(homedir4(), value.slice(2));
169577
+ } else if (value.startsWith("/") || value.startsWith("./") || value.startsWith("../")) {
169578
+ dir = isAbsolute2(value) ? value : resolve3(configDir, value);
169579
+ } else {
169580
+ return null;
169581
+ }
169582
+ try {
169583
+ const pkg = JSON.parse(readFileSync5(join8(dir, "package.json"), "utf-8"));
169584
+ return typeof pkg.name === "string" ? pkg.name : null;
169585
+ } catch {
169586
+ return null;
169587
+ }
169588
+ }
169589
+ function entryMatchesAft(entry, configDir) {
168626
169590
  const value = Array.isArray(entry) ? entry[0] : entry;
168627
- return typeof value === "string" && (value.includes("@cortexkit/aft") || value.includes("aft-opencode") || value.includes("aft-pi"));
169591
+ if (typeof value !== "string")
169592
+ return false;
169593
+ if (stringMentionsAft(value))
169594
+ return true;
169595
+ const name2 = resolveLocalEntryPackageName(value, configDir);
169596
+ return name2 != null && stringMentionsAft(name2);
168628
169597
  }
168629
- function hasAftInArray(value) {
168630
- return Array.isArray(value) && value.some(entryMatchesAft);
169598
+ function hasAftInArray(value, configDir) {
169599
+ return Array.isArray(value) && value.some((entry) => entryMatchesAft(entry, configDir));
168631
169600
  }
168632
- function hasAftAtKeys(value, keys2) {
169601
+ function hasAftAtKeys(value, keys2, configDir) {
168633
169602
  if (!value || typeof value !== "object")
168634
169603
  return false;
168635
169604
  const record4 = value;
168636
169605
  for (const key of keys2) {
168637
- if (hasAftInArray(record4[key]))
169606
+ if (hasAftInArray(record4[key], configDir))
168638
169607
  return true;
168639
169608
  }
168640
169609
  return false;
@@ -168651,7 +169620,8 @@ function getAftAvailability() {
168651
169620
  for (const path5 of opencodePaths) {
168652
169621
  try {
168653
169622
  const config2 = parseConfig(path5);
168654
- if (hasAftAtKeys(config2, ["plugin", "plugins", "mcp", "mcp_servers"])) {
169623
+ const configDir = join8(path5, "..");
169624
+ if (hasAftAtKeys(config2, ["plugin", "plugins", "mcp", "mcp_servers"], configDir)) {
168655
169625
  opencode = true;
168656
169626
  break;
168657
169627
  }
@@ -168661,12 +169631,13 @@ function getAftAvailability() {
168661
169631
  for (const path5 of piPaths) {
168662
169632
  try {
168663
169633
  const config2 = parseConfig(path5);
168664
- if (hasAftAtKeys(config2, ["packages", "extensions"])) {
169634
+ const configDir = join8(path5, "..");
169635
+ if (hasAftAtKeys(config2, ["packages", "extensions"], configDir)) {
168665
169636
  pi = true;
168666
169637
  break;
168667
169638
  }
168668
169639
  const agent = config2?.agent;
168669
- if (hasAftAtKeys(agent, ["packages", "extensions"])) {
169640
+ if (hasAftAtKeys(agent, ["packages", "extensions"], configDir)) {
168670
169641
  pi = true;
168671
169642
  break;
168672
169643
  }
@@ -168783,9 +169754,6 @@ ${blocks.join(`
168783
169754
  return rendered;
168784
169755
  }
168785
169756
 
168786
- // ../plugin/src/hooks/magic-context/inject-compartments.ts
168787
- await init_read_session_db();
168788
-
168789
169757
  // ../plugin/src/hooks/magic-context/temporal-awareness.ts
168790
169758
  var TEMPORAL_AWARENESS_THRESHOLD_SECONDS = 300;
168791
169759
  var SECONDS_PER_MINUTE = 60;
@@ -168942,6 +169910,21 @@ function renderMemoryBlockV2(memories, wrapper = "project-memory") {
168942
169910
  `);
168943
169911
  }
168944
169912
 
169913
+ // ../plugin/src/tools/ctx-search/constants.ts
169914
+ var CTX_SEARCH_DESCRIPTION = `Your long-term recall for this project — search everything that ever happened here, not just what's currently visible.
169915
+
169916
+ Reach for it when something feels familiar but isn't in view: "did we solve this before?", "what did we decide about X?", "when did this break?", "where does Y live?". Results only contain things you CANNOT currently see — memories already shown in <project-memory> and the live conversation tail are filtered out.
169917
+
169918
+ Sources (omit for a broad search across all):
169919
+ - memory: curated cross-session project knowledge — rules, constraints, conventions.
169920
+ - message: the raw conversation behind your compacted history. Hits include message ordinals — expand the surrounding exchange with ctx_expand(start=N-10, end=N+5).
169921
+ - git_commit: this repository's commit history.
169922
+
169923
+ Picking sources:
169924
+ - "when did this change / was this working before" → ["git_commit", "message"]
169925
+ - "did we discuss this earlier" → ["message"]
169926
+ - "what's our convention / rule for X" → ["memory"]`;
169927
+
168945
169928
  // src/tools/ctx-search.ts
168946
169929
  var DEFAULT_LIMIT = 10;
168947
169930
  var ParamsSchema5 = exports_typebox.Object({
@@ -169025,18 +170008,7 @@ function createCtxSearchTool(deps) {
169025
170008
  return {
169026
170009
  name: "ctx_search",
169027
170010
  label: "Magic Context: Search",
169028
- description: `Search across project memories, indexed git commits, and raw conversation history.
169029
-
169030
- ` + `Sources:
169031
- ` + `- memory: curated cross-session knowledge for this project
169032
- ` + `- message: raw user/assistant messages from older compartmentalized history
169033
- ` + `- git_commit: HEAD git commits (when git commit indexing is enabled)
169034
-
169035
- ` + "Narrow via the `sources` param when the question maps to a specific channel:\n" + `- "was this working before / when did this break" → ["git_commit", "message"]
169036
- ` + `- "when did we change this" → ["git_commit"]
169037
- ` + `- "what is our naming convention" → ["memory"]
169038
- ` + `- "did we discuss this earlier" → ["message"]
169039
- ` + "Omit sources for a broad search across all enabled channels.",
170011
+ description: CTX_SEARCH_DESCRIPTION,
169040
170012
  parameters: ParamsSchema5,
169041
170013
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
169042
170014
  const query = params.query?.trim();
@@ -169055,6 +170027,7 @@ function createCtxSearchTool(deps) {
169055
170027
  const embeddingEnabled = snapshot ? snapshot.enabled || snapshot.gitCommitEnabled : deps.embeddingEnabled;
169056
170028
  const gitCommitsEnabled = snapshot?.gitCommitEnabled ?? deps.gitCommitsEnabled ?? false;
169057
170029
  const lastCompartmentEnd = getLastCompartmentEndMessage(deps.db, sessionId);
170030
+ const messageOrdinalCutoff = lastCompartmentEnd >= 0 ? lastCompartmentEnd : 0;
169058
170031
  const visibleMemoryIds = getVisibleMemoryIds(deps.db, sessionId);
169059
170032
  const results = await unifiedSearch(deps.db, sessionId, projectIdentity, query, {
169060
170033
  limit: normalizeLimit3(params.limit),
@@ -169065,7 +170038,7 @@ function createCtxSearchTool(deps) {
169065
170038
  return result?.vector ?? null;
169066
170039
  },
169067
170040
  isEmbeddingRuntimeEnabled: () => embeddingEnabled === true,
169068
- maxMessageOrdinal: lastCompartmentEnd >= 0 ? lastCompartmentEnd : undefined,
170041
+ maxMessageOrdinal: messageOrdinalCutoff,
169069
170042
  gitCommitsEnabled,
169070
170043
  sources: params.sources,
169071
170044
  visibleMemoryIds,
@@ -169152,13 +170125,15 @@ function registerMagicContextTools(pi, opts) {
169152
170125
  embeddingEnabled: opts.embeddingEnabled,
169153
170126
  gitCommitsEnabled: opts.gitCommitsEnabled
169154
170127
  }));
169155
- pi.registerTool(createCtxMemoryTool({
169156
- db: opts.db,
169157
- ensureProjectRegistered: opts.ensureProjectRegistered,
169158
- memoryEnabled: opts.memoryEnabled,
169159
- embeddingEnabled: opts.embeddingEnabled,
169160
- allowDreamerActions: opts.allowDreamerActions ?? false
169161
- }));
170128
+ if (opts.memoryToolEnabled !== false) {
170129
+ pi.registerTool(createCtxMemoryTool({
170130
+ db: opts.db,
170131
+ ensureProjectRegistered: opts.ensureProjectRegistered,
170132
+ memoryEnabled: opts.memoryEnabled,
170133
+ embeddingEnabled: opts.embeddingEnabled,
170134
+ allowDreamerActions: opts.allowDreamerActions ?? false
170135
+ }));
170136
+ }
169162
170137
  if (!opts.sessionScopedToolsDisabled) {
169163
170138
  pi.registerTool(createCtxNoteTool({
169164
170139
  db: opts.db,
@@ -169181,7 +170156,7 @@ var openedDb;
169181
170156
  function magicContextSubagentExtension(pi) {
169182
170157
  setHarness("pi");
169183
170158
  pi.registerFlag(SUBAGENT_DREAMER_ACTIONS_FLAG, {
169184
- description: "Enable dreamer-only ctx_memory actions (update, merge, archive).",
170159
+ description: "Register ctx_memory with dreamer actions for Magic Context subagents.",
169185
170160
  type: "boolean",
169186
170161
  default: false
169187
170162
  });
@@ -169199,10 +170174,11 @@ function magicContextSubagentExtension(pi) {
169199
170174
  registerMagicContextTools(pi, {
169200
170175
  db,
169201
170176
  ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
170177
+ memoryToolEnabled: dreamerActionsEnabled,
169202
170178
  allowDreamerActions: dreamerActionsEnabled,
169203
170179
  sessionScopedToolsDisabled: true
169204
170180
  });
169205
- log(`[pi-subagent] registered tools: ctx_search, ctx_memory` + ` (ctx_note/ctx_expand omitted: --no-session child;` + ` memory=${cfg.memory.enabled}, embedding=${cfg.embedding.provider !== "off"},` + ` git_commits=${cfg.memory.git_commit_indexing.enabled}, dreamer_actions=${dreamerActionsEnabled})`);
170181
+ log(`[pi-subagent] registered tools: ctx_search${dreamerActionsEnabled ? ", ctx_memory" : ""}` + ` (ctx_note/ctx_expand omitted: --no-session child;` + ` memory=${cfg.memory.enabled}, embedding=${cfg.embedding.provider !== "off"},` + ` git_commits=${cfg.memory.git_commit_indexing.enabled}, dreamer_actions=${dreamerActionsEnabled})`);
169206
170182
  } catch (err5) {
169207
170183
  const message = err5 instanceof Error ? err5.message : String(err5);
169208
170184
  log(`[pi-subagent] startup failed: ${message}`);