@cortexkit/magic-context 0.22.4 → 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.
Files changed (2) hide show
  1. package/dist/index.js +333 -38
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -145068,6 +145068,11 @@ function markSessionCompleted(db, sessionId, now) {
145068
145068
  SET status = 'completed', completed_at = ?, lease_expires_at = NULL, last_error = NULL
145069
145069
  WHERE session_id = ?`).run(now, sessionId);
145070
145070
  }
145071
+ function markSessionPendingRetry(db, sessionId) {
145072
+ db.prepare(`UPDATE tool_owner_backfill_state
145073
+ SET status = 'pending', completed_at = NULL, lease_expires_at = NULL, last_error = NULL
145074
+ WHERE session_id = ?`).run(sessionId);
145075
+ }
145071
145076
  function markSessionSkipped(db, sessionId, now, reason) {
145072
145077
  db.prepare(`INSERT INTO tool_owner_backfill_state(session_id, status, completed_at, last_error)
145073
145078
  VALUES (?, 'skipped', ?, ?)
@@ -145144,12 +145149,18 @@ function applyOwnersForSession(db, sessionId, ownersByCallId) {
145144
145149
  const updateRowStmt = db.prepare(`UPDATE tags
145145
145150
  SET tool_owner_message_id = ?
145146
145151
  WHERE id = ? AND tool_owner_message_id IS NULL`);
145152
+ const existingOwnerStmt = db.prepare(`SELECT 1 AS hit FROM tags
145153
+ WHERE session_id = ? AND message_id = ? AND type = 'tool'
145154
+ AND tool_owner_message_id = ?
145155
+ LIMIT 1`);
145147
145156
  let rowsUpdated = 0;
145148
145157
  db.transaction(() => {
145149
145158
  for (const [callId, ownerId] of ownersByCallId) {
145150
145159
  const orphan = findOrphanStmt.get(sessionId, callId);
145151
145160
  if (!orphan)
145152
145161
  continue;
145162
+ if (existingOwnerStmt.get(sessionId, callId, ownerId))
145163
+ continue;
145153
145164
  const result = updateRowStmt.run(ownerId, orphan.id);
145154
145165
  rowsUpdated += result.changes ?? 0;
145155
145166
  }
@@ -145178,6 +145189,8 @@ function backfillToolOwnersInChunks(db, result) {
145178
145189
  if (owners.size === 0) {
145179
145190
  markSessionSkipped(db, sessionId, Date.now(), "no_oc_matches");
145180
145191
  result.sessionsSkippedNoMatches += 1;
145192
+ } else if (rowsLeftNull > 0) {
145193
+ markSessionPendingRetry(db, sessionId);
145181
145194
  } else {
145182
145195
  markSessionCompleted(db, sessionId, Date.now());
145183
145196
  result.sessionsCompleted += 1;
@@ -145293,9 +145306,9 @@ function applySqliteTuningPragmas(db) {
145293
145306
  db.exec("PRAGMA analysis_limit=400");
145294
145307
  }
145295
145308
  function initializeDatabase(db) {
145309
+ db.exec("PRAGMA busy_timeout=5000");
145296
145310
  db.exec("PRAGMA foreign_keys=ON");
145297
145311
  db.exec("PRAGMA journal_mode=WAL");
145298
- db.exec("PRAGMA busy_timeout=5000");
145299
145312
  applySqliteTuningPragmas(db);
145300
145313
  db.exec(`
145301
145314
  CREATE TABLE IF NOT EXISTS tags (
@@ -145308,6 +145321,9 @@ function initializeDatabase(db) {
145308
145321
  tag_number INTEGER,
145309
145322
  harness TEXT NOT NULL DEFAULT 'opencode',
145310
145323
  entry_fingerprint TEXT,
145324
+ token_count INTEGER,
145325
+ input_token_count INTEGER,
145326
+ reasoning_token_count INTEGER,
145311
145327
  UNIQUE(session_id, tag_number)
145312
145328
  );
145313
145329
 
@@ -145597,6 +145613,11 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
145597
145613
  counter INTEGER DEFAULT 0,
145598
145614
  last_nudge_tokens INTEGER DEFAULT 0,
145599
145615
  last_nudge_band TEXT DEFAULT '',
145616
+ last_nudge_undropped INTEGER DEFAULT 0,
145617
+ last_nudge_level TEXT DEFAULT '',
145618
+ channel2_nudge_state TEXT DEFAULT '',
145619
+ channel2_nudge_claimed_at INTEGER DEFAULT 0,
145620
+ last_emergency_input_sample INTEGER DEFAULT 0,
145600
145621
  last_transform_error TEXT DEFAULT '',
145601
145622
  nudge_anchor_message_id TEXT DEFAULT '',
145602
145623
  nudge_anchor_text TEXT DEFAULT '',
@@ -145653,6 +145674,14 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
145653
145674
  cached_m0_project_docs_hash TEXT,
145654
145675
  cached_m1_bytes BLOB,
145655
145676
  last_observed_model_key TEXT,
145677
+ last_usage_context_limit INTEGER NOT NULL DEFAULT 0,
145678
+ prior_boundary_ordinal INTEGER NOT NULL DEFAULT 1,
145679
+ protected_tail_policy_version INTEGER NOT NULL DEFAULT 0,
145680
+ protected_tail_drain_window_started_at INTEGER NOT NULL DEFAULT 0,
145681
+ protected_tail_drain_tokens INTEGER NOT NULL DEFAULT 0,
145682
+ recovery_no_eligible_head_count INTEGER NOT NULL DEFAULT 0,
145683
+ force_emergency_bypass_window_start INTEGER NOT NULL DEFAULT 0,
145684
+ force_emergency_bypass_used INTEGER NOT NULL DEFAULT 0,
145656
145685
  cached_m0_materialized_at INTEGER,
145657
145686
  cached_m0_session_facts_version INTEGER,
145658
145687
  cached_m0_upgrade_state TEXT,
@@ -145763,6 +145792,11 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
145763
145792
  CREATE INDEX IF NOT EXISTS idx_message_history_index_updated_at ON message_history_index(updated_at);
145764
145793
  `);
145765
145794
  ensureColumn(db, "session_meta", "last_nudge_band", "TEXT DEFAULT ''");
145795
+ ensureColumn(db, "session_meta", "last_nudge_undropped", "INTEGER DEFAULT 0");
145796
+ ensureColumn(db, "session_meta", "last_nudge_level", "TEXT DEFAULT ''");
145797
+ ensureColumn(db, "session_meta", "channel2_nudge_state", "TEXT DEFAULT ''");
145798
+ ensureColumn(db, "session_meta", "channel2_nudge_claimed_at", "INTEGER DEFAULT 0");
145799
+ ensureColumn(db, "session_meta", "last_emergency_input_sample", "INTEGER DEFAULT 0");
145766
145800
  ensureColumn(db, "session_meta", "last_transform_error", "TEXT DEFAULT ''");
145767
145801
  ensureColumn(db, "session_meta", "nudge_anchor_message_id", "TEXT DEFAULT ''");
145768
145802
  ensureColumn(db, "session_meta", "nudge_anchor_text", "TEXT DEFAULT ''");
@@ -145789,6 +145823,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
145789
145823
  ensureColumn(db, "session_meta", "system_prompt_hash", "TEXT DEFAULT ''");
145790
145824
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
145791
145825
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
145826
+ ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
145792
145827
  ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
145793
145828
  ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
145794
145829
  ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
@@ -145807,6 +145842,9 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
145807
145842
  db.exec(`CREATE INDEX IF NOT EXISTS idx_tags_pi_adopt
145808
145843
  ON tags(session_id, entry_fingerprint)
145809
145844
  WHERE type='message' AND entry_fingerprint IS NOT NULL`);
145845
+ ensureColumn(db, "tags", "token_count", "INTEGER");
145846
+ ensureColumn(db, "tags", "input_token_count", "INTEGER");
145847
+ ensureColumn(db, "tags", "reasoning_token_count", "INTEGER");
145810
145848
  ensureColumn(db, "session_meta", "system_prompt_tokens", "INTEGER DEFAULT 0");
145811
145849
  ensureColumn(db, "session_meta", "compaction_marker_state", "TEXT DEFAULT ''");
145812
145850
  ensureColumn(db, "session_meta", "key_files", "TEXT DEFAULT ''");
@@ -145847,6 +145885,14 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
145847
145885
  ensureColumn(db, "session_meta", "cached_m0_project_docs_hash", "TEXT");
145848
145886
  ensureColumn(db, "session_meta", "cached_m1_bytes", "BLOB");
145849
145887
  ensureColumn(db, "session_meta", "last_observed_model_key", "TEXT");
145888
+ ensureColumn(db, "session_meta", "last_usage_context_limit", "INTEGER NOT NULL DEFAULT 0");
145889
+ ensureColumn(db, "session_meta", "prior_boundary_ordinal", "INTEGER NOT NULL DEFAULT 1");
145890
+ ensureColumn(db, "session_meta", "protected_tail_policy_version", "INTEGER NOT NULL DEFAULT 0");
145891
+ ensureColumn(db, "session_meta", "protected_tail_drain_window_started_at", "INTEGER NOT NULL DEFAULT 0");
145892
+ ensureColumn(db, "session_meta", "protected_tail_drain_tokens", "INTEGER NOT NULL DEFAULT 0");
145893
+ ensureColumn(db, "session_meta", "recovery_no_eligible_head_count", "INTEGER NOT NULL DEFAULT 0");
145894
+ ensureColumn(db, "session_meta", "force_emergency_bypass_window_start", "INTEGER NOT NULL DEFAULT 0");
145895
+ ensureColumn(db, "session_meta", "force_emergency_bypass_used", "INTEGER NOT NULL DEFAULT 0");
145850
145896
  ensureColumn(db, "session_meta", "cached_m0_materialized_at", "INTEGER");
145851
145897
  ensureColumn(db, "session_meta", "cached_m0_session_facts_version", "INTEGER");
145852
145898
  ensureColumn(db, "session_meta", "cached_m0_upgrade_state", "TEXT");
@@ -145916,6 +145962,12 @@ function healAllNullColumns(db) {
145916
145962
  healNullIntegerColumns(db);
145917
145963
  healMissingMemoryBlockIds(db);
145918
145964
  }
145965
+ function healWedgedChannel2Claims(db) {
145966
+ try {
145967
+ const staleBefore = Date.now() - CHANNEL2_CLAIM_TTL_MS;
145968
+ 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);
145969
+ } catch {}
145970
+ }
145919
145971
  function healMissingMemoryBlockIds(db) {
145920
145972
  try {
145921
145973
  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();
@@ -145925,6 +145977,7 @@ function healNullTextColumns(db) {
145925
145977
  const columns = [
145926
145978
  ["cache_ttl", ""],
145927
145979
  ["last_nudge_band", ""],
145980
+ ["last_nudge_level", ""],
145928
145981
  ["last_transform_error", ""],
145929
145982
  ["nudge_anchor_message_id", ""],
145930
145983
  ["nudge_anchor_text", ""],
@@ -145939,6 +145992,7 @@ function healNullTextColumns(db) {
145939
145992
  ["todo_synthetic_state_json", ""],
145940
145993
  ["system_prompt_hash", ""],
145941
145994
  ["stripped_placeholder_ids", ""],
145995
+ ["stale_reduce_stripped_ids", ""],
145942
145996
  ["memory_block_cache", ""],
145943
145997
  ["memory_block_ids", ""],
145944
145998
  ["compaction_marker_state", ""],
@@ -145964,7 +146018,17 @@ function healNullIntegerColumns(db) {
145964
146018
  ["observed_safe_input_tokens", 0],
145965
146019
  ["cache_alert_sent", 0],
145966
146020
  ["new_work_tokens", 0],
145967
- ["total_input_tokens", 0]
146021
+ ["total_input_tokens", 0],
146022
+ ["last_emergency_input_sample", 0],
146023
+ ["channel2_nudge_claimed_at", 0],
146024
+ ["last_usage_context_limit", 0],
146025
+ ["prior_boundary_ordinal", 1],
146026
+ ["protected_tail_policy_version", 0],
146027
+ ["protected_tail_drain_window_started_at", 0],
146028
+ ["protected_tail_drain_tokens", 0],
146029
+ ["recovery_no_eligible_head_count", 0],
146030
+ ["force_emergency_bypass_window_start", 0],
146031
+ ["force_emergency_bypass_used", 0]
145968
146032
  ];
145969
146033
  for (const [column, fallback] of columns) {
145970
146034
  try {
@@ -146003,6 +146067,7 @@ function openDatabase(dbPathOrOptions) {
146003
146067
  if (!persistenceByDatabase.has(existing)) {
146004
146068
  persistenceByDatabase.set(existing, true);
146005
146069
  }
146070
+ healWedgedChannel2Claims(existing);
146006
146071
  return existing;
146007
146072
  }
146008
146073
  try {
@@ -146028,6 +146093,7 @@ function openDatabase(dbPathOrOptions) {
146028
146093
  log2(`[magic-context] key-files orphan GC failed: ${getErrorMessage(error)}`);
146029
146094
  }
146030
146095
  }
146096
+ healWedgedChannel2Claims(db);
146031
146097
  if (!explicitDbPath) {
146032
146098
  try {
146033
146099
  runToolOwnerBackfill(db);
@@ -146063,7 +146129,7 @@ function closeDatabase() {
146063
146129
  }
146064
146130
  }
146065
146131
  }
146066
- var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 30, sqlitePragmaConfig;
146132
+ var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 32, sqlitePragmaConfig, CHANNEL2_CLAIM_TTL_MS = 120000;
146067
146133
  var init_storage_db = __esm(async () => {
146068
146134
  init_data_path();
146069
146135
  init_logger();
@@ -146908,15 +146974,60 @@ var init_migrations = __esm(async () => {
146908
146974
  cached_m0_model_key = NULL`).run();
146909
146975
  }
146910
146976
  }
146977
+ },
146978
+ {
146979
+ version: 31,
146980
+ 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)",
146981
+ up: (db) => {
146982
+ const hasSessionMeta = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_meta' LIMIT 1").get();
146983
+ if (!hasSessionMeta)
146984
+ return;
146985
+ ensureColumn(db, "session_meta", "last_nudge_undropped", "INTEGER DEFAULT 0");
146986
+ ensureColumn(db, "session_meta", "channel2_nudge_state", "TEXT DEFAULT ''");
146987
+ const columns = new Set(db.prepare("PRAGMA table_info(session_meta)").all().map((column) => column.name));
146988
+ if (columns.has("sticky_turn_reminder_text")) {
146989
+ db.prepare(`UPDATE session_meta SET
146990
+ sticky_turn_reminder_text = '',
146991
+ sticky_turn_reminder_message_id = '',
146992
+ nudge_anchor_message_id = '',
146993
+ nudge_anchor_text = ''`).run();
146994
+ }
146995
+ }
146996
+ },
146997
+ {
146998
+ version: 32,
146999
+ description: "Protected tail boundary state, usage resolver fields, recovery escape, and drain quota",
147000
+ up: (db) => {
147001
+ const hasSessionMeta = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_meta' LIMIT 1").get();
147002
+ if (!hasSessionMeta)
147003
+ return;
147004
+ ensureColumn(db, "session_meta", "prior_boundary_ordinal", "INTEGER NOT NULL DEFAULT 1");
147005
+ ensureColumn(db, "session_meta", "protected_tail_policy_version", "INTEGER NOT NULL DEFAULT 0");
147006
+ ensureColumn(db, "session_meta", "protected_tail_drain_window_started_at", "INTEGER NOT NULL DEFAULT 0");
147007
+ ensureColumn(db, "session_meta", "protected_tail_drain_tokens", "INTEGER NOT NULL DEFAULT 0");
147008
+ ensureColumn(db, "session_meta", "recovery_no_eligible_head_count", "INTEGER NOT NULL DEFAULT 0");
147009
+ ensureColumn(db, "session_meta", "force_emergency_bypass_window_start", "INTEGER NOT NULL DEFAULT 0");
147010
+ ensureColumn(db, "session_meta", "force_emergency_bypass_used", "INTEGER NOT NULL DEFAULT 0");
147011
+ ensureColumn(db, "session_meta", "last_usage_context_limit", "INTEGER NOT NULL DEFAULT 0");
147012
+ db.prepare("UPDATE session_meta SET prior_boundary_ordinal = 1 WHERE prior_boundary_ordinal IS NULL OR prior_boundary_ordinal < 1").run();
147013
+ db.prepare("UPDATE session_meta SET protected_tail_policy_version = 0 WHERE protected_tail_policy_version IS NULL").run();
147014
+ db.prepare("UPDATE session_meta SET protected_tail_drain_window_started_at = 0 WHERE protected_tail_drain_window_started_at IS NULL").run();
147015
+ db.prepare("UPDATE session_meta SET protected_tail_drain_tokens = 0 WHERE protected_tail_drain_tokens IS NULL").run();
147016
+ db.prepare("UPDATE session_meta SET recovery_no_eligible_head_count = 0 WHERE recovery_no_eligible_head_count IS NULL").run();
147017
+ db.prepare("UPDATE session_meta SET force_emergency_bypass_window_start = 0 WHERE force_emergency_bypass_window_start IS NULL").run();
147018
+ db.prepare("UPDATE session_meta SET force_emergency_bypass_used = 0 WHERE force_emergency_bypass_used IS NULL").run();
147019
+ db.prepare("UPDATE session_meta SET last_usage_context_limit = 0 WHERE last_usage_context_limit IS NULL").run();
147020
+ }
146911
147021
  }
146912
147022
  ];
146913
147023
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
146914
147024
  });
146915
147025
 
146916
147026
  // ../plugin/src/features/magic-context/project-docs-hash.ts
146917
- var docsCache;
147027
+ var MAX_PROJECT_DOC_BYTES, docsCache;
146918
147028
  var init_project_docs_hash = __esm(() => {
146919
147029
  init_compartment_storage();
147030
+ MAX_PROJECT_DOC_BYTES = 256 * 1024;
146920
147031
  docsCache = new Map;
146921
147032
  });
146922
147033
 
@@ -147109,7 +147220,7 @@ var init_storage_memory_mutation_log = __esm(() => {
147109
147220
  });
147110
147221
 
147111
147222
  // ../plugin/src/features/magic-context/storage-meta-persisted.ts
147112
- var AUTO_SEARCH_NO_HINT_REASONS;
147223
+ var AUTO_SEARCH_NO_HINT_REASONS, DRAIN_WINDOW_MS;
147113
147224
  var init_storage_meta_persisted = __esm(() => {
147114
147225
  init_logger();
147115
147226
  init_storage_meta_shared();
@@ -147121,6 +147232,7 @@ var init_storage_meta_persisted = __esm(() => {
147121
147232
  "stacked",
147122
147233
  "too-short"
147123
147234
  ]);
147235
+ DRAIN_WINDOW_MS = 10 * 60 * 1000;
147124
147236
  });
147125
147237
 
147126
147238
  // ../plugin/src/features/magic-context/resolve-subagent-fallback.ts
@@ -147167,7 +147279,7 @@ var init_storage_ops = __esm(() => {
147167
147279
  // ../plugin/src/features/magic-context/storage-source.ts
147168
147280
  var init_storage_source = () => {};
147169
147281
  // ../plugin/src/features/magic-context/storage-tags.ts
147170
- var insertTagStatements, updateTagStatusStatements, updateTagDropModeStatements, updateTagMessageIdStatements, getTagNumbersByMessageIdStatements, deleteTagsByMessageIdStatements, getMaxTagNumberBySessionStatements, getTagNumberByMessageIdStatements, updateTagByteSizeStatements, updateTagInputByteSizeStatements, getOwnerScopedToolTagNumbersStatements, getActiveTagsBySessionStatements, getMaxDroppedTagNumberStatements, getToolTagNumberByOwnerStatements, getNullOwnerToolTagStatements, adoptNullOwnerToolTagStatements, deleteToolTagsByOwnerStatements;
147282
+ var insertTagStatements, updateTagStatusStatements, updateTagDropModeStatements, updateTagMessageIdStatements, getTagNumbersByMessageIdStatements, deleteTagsByMessageIdStatements, getMaxTagNumberBySessionStatements, getTagNumberByMessageIdStatements, updateTagByteSizeStatements, updateTagInputByteSizeStatements, updateTagTokenCountStatements, updateTagInputTokenCountStatements, getOwnerScopedToolTagNumbersStatements, getActiveTagsBySessionStatements, getMaxDroppedTagNumberStatements, getToolTagNumberByOwnerStatements, getNullOwnerToolTagStatements, adoptNullOwnerToolTagStatements, deleteToolTagsByOwnerStatements;
147171
147283
  var init_storage_tags = __esm(() => {
147172
147284
  insertTagStatements = new WeakMap;
147173
147285
  updateTagStatusStatements = new WeakMap;
@@ -147179,6 +147291,8 @@ var init_storage_tags = __esm(() => {
147179
147291
  getTagNumberByMessageIdStatements = new WeakMap;
147180
147292
  updateTagByteSizeStatements = new WeakMap;
147181
147293
  updateTagInputByteSizeStatements = new WeakMap;
147294
+ updateTagTokenCountStatements = new WeakMap;
147295
+ updateTagInputTokenCountStatements = new WeakMap;
147182
147296
  getOwnerScopedToolTagNumbersStatements = new WeakMap;
147183
147297
  getActiveTagsBySessionStatements = new WeakMap;
147184
147298
  getMaxDroppedTagNumberStatements = new WeakMap;
@@ -147595,6 +147709,102 @@ function migrateLegacyExperimental(rawConfig, warnings) {
147595
147709
  return patched;
147596
147710
  }
147597
147711
 
147712
+ // ../plugin/src/config/project-security.ts
147713
+ function isPlainObject(value) {
147714
+ return typeof value === "object" && value !== null && !Array.isArray(value);
147715
+ }
147716
+ function stripUnsafeProjectConfigFields(projectRaw) {
147717
+ const warnings = [];
147718
+ if ("auto_update" in projectRaw) {
147719
+ delete projectRaw.auto_update;
147720
+ warnings.push("Ignoring auto_update from project config (security: this setting only honors user-level config).");
147721
+ }
147722
+ if ("sqlite" in projectRaw) {
147723
+ delete projectRaw.sqlite;
147724
+ 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).");
147725
+ }
147726
+ for (const agentKey of HIDDEN_AGENT_KEYS) {
147727
+ const block = projectRaw[agentKey];
147728
+ if (!isPlainObject(block))
147729
+ continue;
147730
+ const removed = [];
147731
+ for (const field of AGENT_ESCALATION_FIELDS) {
147732
+ if (field in block) {
147733
+ delete block[field];
147734
+ removed.push(field);
147735
+ }
147736
+ }
147737
+ if (removed.length > 0) {
147738
+ warnings.push(`Ignoring ${agentKey}.${removed.join("/")} from project config ` + "(security: a repository cannot reprogram or re-permission hidden agents).");
147739
+ }
147740
+ }
147741
+ return warnings;
147742
+ }
147743
+ function normalizeEndpoint(value) {
147744
+ if (typeof value !== "string")
147745
+ return;
147746
+ const trimmed = value.trim().replace(/\/+$/, "");
147747
+ return trimmed.length > 0 ? trimmed.toLowerCase() : undefined;
147748
+ }
147749
+ function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
147750
+ const projectEmbedding = projectRaw.embedding;
147751
+ if (!isPlainObject(projectEmbedding))
147752
+ return [];
147753
+ const redirectsEndpoint = "endpoint" in projectEmbedding;
147754
+ if (!redirectsEndpoint)
147755
+ return [];
147756
+ const userEmbedding = userRaw?.embedding;
147757
+ if (isPlainObject(userEmbedding)) {
147758
+ const projectEndpoint = normalizeEndpoint(projectEmbedding.endpoint);
147759
+ const userEndpoint = normalizeEndpoint(userEmbedding.endpoint);
147760
+ if (projectEndpoint !== undefined && projectEndpoint === userEndpoint) {
147761
+ return [];
147762
+ }
147763
+ }
147764
+ const providesOwnKey = typeof projectEmbedding.api_key === "string" && projectEmbedding.api_key.length > 0;
147765
+ if (providesOwnKey)
147766
+ return [];
147767
+ const mergedEmbedding = mergedRaw.embedding;
147768
+ if (!isPlainObject(mergedEmbedding))
147769
+ return [];
147770
+ if (!("api_key" in mergedEmbedding))
147771
+ return [];
147772
+ delete mergedEmbedding.api_key;
147773
+ return [
147774
+ "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)."
147775
+ ];
147776
+ }
147777
+ var HIDDEN_AGENT_KEYS, AGENT_ESCALATION_FIELDS;
147778
+ var init_project_security = __esm(() => {
147779
+ HIDDEN_AGENT_KEYS = ["historian", "dreamer", "sidekick"];
147780
+ AGENT_ESCALATION_FIELDS = ["prompt", "permission", "tools", "system_prompt"];
147781
+ });
147782
+
147783
+ // ../plugin/src/config/prune-config-leaf.ts
147784
+ function isPlainObject2(value) {
147785
+ return typeof value === "object" && value !== null && !Array.isArray(value);
147786
+ }
147787
+ function pruneNestedConfigLeaf(block, relativePath) {
147788
+ if (relativePath.length === 0)
147789
+ return null;
147790
+ const result = { ...block };
147791
+ let cursor = result;
147792
+ for (let i = 0;i < relativePath.length - 1; i++) {
147793
+ const seg = String(relativePath[i]);
147794
+ const child = cursor[seg];
147795
+ if (!isPlainObject2(child))
147796
+ return null;
147797
+ const clonedChild = { ...child };
147798
+ cursor[seg] = clonedChild;
147799
+ cursor = clonedChild;
147800
+ }
147801
+ const leaf = String(relativePath[relativePath.length - 1]);
147802
+ if (!(leaf in cursor))
147803
+ return null;
147804
+ delete cursor[leaf];
147805
+ return { block: result, removed: relativePath.map(String).join(".") };
147806
+ }
147807
+
147598
147808
  // ../../node_modules/.bun/zod@4.4.3/node_modules/zod/v4/core/core.js
147599
147809
  function $constructor(name2, initializer, params) {
147600
147810
  function init(inst, def) {
@@ -147705,7 +147915,7 @@ __export(exports_util, {
147705
147915
  jsonStringifyReplacer: () => jsonStringifyReplacer,
147706
147916
  joinValues: () => joinValues,
147707
147917
  issue: () => issue,
147708
- isPlainObject: () => isPlainObject,
147918
+ isPlainObject: () => isPlainObject3,
147709
147919
  isObject: () => isObject,
147710
147920
  hexToUint8Array: () => hexToUint8Array,
147711
147921
  getSizableOrigin: () => getSizableOrigin,
@@ -147870,7 +148080,7 @@ function slugify(input) {
147870
148080
  function isObject(data) {
147871
148081
  return typeof data === "object" && data !== null && !Array.isArray(data);
147872
148082
  }
147873
- function isPlainObject(o) {
148083
+ function isPlainObject3(o) {
147874
148084
  if (isObject(o) === false)
147875
148085
  return false;
147876
148086
  const ctor = o.constructor;
@@ -147887,7 +148097,7 @@ function isPlainObject(o) {
147887
148097
  return true;
147888
148098
  }
147889
148099
  function shallowClone(o) {
147890
- if (isPlainObject(o))
148100
+ if (isPlainObject3(o))
147891
148101
  return { ...o };
147892
148102
  if (Array.isArray(o))
147893
148103
  return [...o];
@@ -148027,7 +148237,7 @@ function omit(schema, mask) {
148027
148237
  return clone(schema, def);
148028
148238
  }
148029
148239
  function extend(schema, shape) {
148030
- if (!isPlainObject(shape)) {
148240
+ if (!isPlainObject3(shape)) {
148031
148241
  throw new Error("Invalid input to extend: expected a plain object");
148032
148242
  }
148033
148243
  const checks = schema._zod.def.checks;
@@ -148050,7 +148260,7 @@ function extend(schema, shape) {
148050
148260
  return clone(schema, def);
148051
148261
  }
148052
148262
  function safeExtend(schema, shape) {
148053
- if (!isPlainObject(shape)) {
148263
+ if (!isPlainObject3(shape)) {
148054
148264
  throw new Error("Invalid input to safeExtend: expected a plain object");
148055
148265
  }
148056
148266
  const def = mergeDefs(schema._zod.def, {
@@ -149533,7 +149743,7 @@ function mergeValues(a, b2) {
149533
149743
  if (a instanceof Date && b2 instanceof Date && +a === +b2) {
149534
149744
  return { valid: true, data: a };
149535
149745
  }
149536
- if (isPlainObject(a) && isPlainObject(b2)) {
149746
+ if (isPlainObject3(a) && isPlainObject3(b2)) {
149537
149747
  const bKeys = Object.keys(b2);
149538
149748
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
149539
149749
  const newObj = { ...a, ...b2 };
@@ -150790,7 +151000,7 @@ var init_schemas = __esm(() => {
150790
151000
  $ZodType.init(inst, def);
150791
151001
  inst._zod.parse = (payload, ctx) => {
150792
151002
  const input = payload.value;
150793
- if (!isPlainObject(input)) {
151003
+ if (!isPlainObject3(input)) {
150794
151004
  payload.issues.push({
150795
151005
  expected: "record",
150796
151006
  code: "invalid_type",
@@ -162254,7 +162464,7 @@ var init_agent_overrides = __esm(() => {
162254
162464
  });
162255
162465
 
162256
162466
  // ../plugin/src/config/schema/magic-context.ts
162257
- var DEFAULT_NUDGE_INTERVAL_TOKENS = 1e4, DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE = 65, 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.", DEFAULT_HISTORIAN_TIMEOUT_MS = 300000, DEFAULT_HISTORY_BUDGET_PERCENTAGE = 0.15, DEFAULT_LOCAL_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2", DREAMER_TASKS, DreamingTaskSchema, DEFAULT_DREAMER_TASKS, PiThinkingLevelSchema, DreamerConfigSchema, SidekickConfigSchema, HistorianConfigSchema, BaseEmbeddingConfigSchema, EmbeddingConfigSchema, MagicContextConfigSchema;
162467
+ var DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE = 65, 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.", DEFAULT_HISTORIAN_TIMEOUT_MS = 300000, DEFAULT_HISTORY_BUDGET_PERCENTAGE = 0.15, DEFAULT_LOCAL_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2", DREAMER_TASKS, DreamingTaskSchema, DEFAULT_DREAMER_TASKS, PiThinkingLevelSchema, DreamerConfigSchema, SidekickConfigSchema, HistorianConfigSchema, BaseEmbeddingConfigSchema, EmbeddingConfigSchema, MagicContextConfigSchema;
162258
162468
  var init_magic_context = __esm(() => {
162259
162469
  init_zod();
162260
162470
  init_agent_overrides();
@@ -162351,7 +162561,6 @@ var init_magic_context = __esm(() => {
162351
162561
  historian: HistorianConfigSchema.describe("Historian agent configuration (model, fallback_models, variant, temperature, maxTokens, permission, two_pass, etc.)"),
162352
162562
  dreamer: DreamerConfigSchema.optional().describe("Dreamer agent + scheduling configuration (model, fallback_models, disable, schedule, tasks, etc.)"),
162353
162563
  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" })'),
162354
- 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)"),
162355
162564
  execute_threshold_percentage: exports_external.union([
162356
162565
  exports_external.number().min(20).max(80, EXECUTE_THRESHOLD_CAP_MESSAGE),
162357
162566
  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))
@@ -162360,10 +162569,7 @@ var init_magic_context = __esm(() => {
162360
162569
  default: exports_external.number().min(5000).max(2000000).optional()
162361
162570
  }).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."),
162362
162571
  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)"),
162363
- 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)"),
162364
- drop_tool_structure: exports_external.boolean().default(true).describe("When true, dropped tool parts are fully removed instead of truncated in place (default: true)"),
162365
162572
  clear_reasoning_age: exports_external.number().min(10).default(50).describe("Clear reasoning/thinking blocks older than N tags (default: 50)"),
162366
- 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)"),
162367
162573
  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)"),
162368
162574
  historian_timeout_ms: exports_external.number().min(60000).default(DEFAULT_HISTORIAN_TIMEOUT_MS).describe("Timeout for each historian prompt call in milliseconds (default: 300000)"),
162369
162575
  commit_cluster_trigger: exports_external.object({
@@ -162427,6 +162633,21 @@ var init_magic_context = __esm(() => {
162427
162633
  import { existsSync as existsSync14, readFileSync as readFileSync8 } from "node:fs";
162428
162634
  import { homedir as homedir7 } from "node:os";
162429
162635
  import { dirname as dirname7, isAbsolute, resolve as resolve2 } from "node:path";
162636
+ function sensitiveFilePathReason(resolvedPath) {
162637
+ const home = homedir7();
162638
+ const sensitiveDirs = [
162639
+ { dir: resolve2(home, ".ssh"), label: "SSH keys" },
162640
+ { dir: resolve2(home, ".aws"), label: "AWS credentials" },
162641
+ { dir: resolve2(home, ".gnupg"), label: "GnuPG keyring" },
162642
+ { dir: resolve2(home, ".config", "gh"), label: "GitHub CLI auth" }
162643
+ ];
162644
+ for (const { dir, label } of sensitiveDirs) {
162645
+ if (resolvedPath === dir || resolvedPath.startsWith(`${dir}/`)) {
162646
+ return label;
162647
+ }
162648
+ }
162649
+ return null;
162650
+ }
162430
162651
  function substituteConfigVariables(input) {
162431
162652
  const warnings = [];
162432
162653
  let text2 = input.text;
@@ -162480,6 +162701,10 @@ function substituteConfigVariables(input) {
162480
162701
  } else if (!isAbsolute(filePath)) {
162481
162702
  filePath = resolve2(configDir, filePath);
162482
162703
  }
162704
+ const sensitiveReason = sensitiveFilePathReason(filePath);
162705
+ if (sensitiveReason) {
162706
+ warnings.push(`${token} resolves to a sensitive path (${sensitiveReason}: ${filePath}); ` + "inlining its contents into config — make sure this is intentional.");
162707
+ }
162483
162708
  if (!existsSync14(filePath)) {
162484
162709
  warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
162485
162710
  continue;
@@ -162550,9 +162775,6 @@ function deepMergeRawConfig(base, override) {
162550
162775
  }
162551
162776
  return result;
162552
162777
  }
162553
- function getProjectUserOnlyFields(config2) {
162554
- return "auto_update" in config2 ? ["auto_update"] : [];
162555
- }
162556
162778
  function redactConfigValue(value) {
162557
162779
  if (value === undefined)
162558
162780
  return "<missing>";
@@ -162591,12 +162813,16 @@ function parsePluginConfig(rawConfig, recoveredTopLevelKeys = []) {
162591
162813
  const warnings = [];
162592
162814
  const errorPaths = new Set;
162593
162815
  const customMessagesByKey = new Map;
162816
+ const issuePathsByKey = new Map;
162594
162817
  const GENERIC_ZOD_PREFIXES = ["Too big", "Too small", "Invalid input", "Invalid", "Expected"];
162595
162818
  for (const issue2 of parsed.error.issues) {
162596
162819
  const topKey = issue2.path[0];
162597
162820
  if (topKey !== undefined) {
162598
162821
  const key = String(topKey);
162599
162822
  errorPaths.add(key);
162823
+ const paths = issuePathsByKey.get(key) ?? [];
162824
+ paths.push([...issue2.path]);
162825
+ issuePathsByKey.set(key, paths);
162600
162826
  const msg = issue2.message;
162601
162827
  if (msg && !GENERIC_ZOD_PREFIXES.some((p2) => msg.startsWith(p2))) {
162602
162828
  if (!customMessagesByKey.has(key)) {
@@ -162612,12 +162838,33 @@ function parsePluginConfig(rawConfig, recoveredTopLevelKeys = []) {
162612
162838
  if (isAgentConfig) {
162613
162839
  delete patched[key];
162614
162840
  warnings.push(`"${key}": invalid agent configuration, ignoring. Check your magic-context.jsonc.`);
162615
- } else {
162616
- delete patched[key];
162617
- const defaultVal = defaults[key];
162618
- const reason = customMessagesByKey.get(key);
162619
- warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultVal)}.${reason ? ` ${reason}` : ""}`);
162841
+ continue;
162842
+ }
162843
+ const issuePaths = issuePathsByKey.get(key) ?? [];
162844
+ const rawValue = rawConfig[key];
162845
+ const allNested = issuePaths.length > 0 && issuePaths.every((p2) => p2.length >= 2) && typeof rawValue === "object" && rawValue !== null && !Array.isArray(rawValue);
162846
+ if (allNested) {
162847
+ let prunedBlock = {
162848
+ ...rawValue
162849
+ };
162850
+ const prunedLeaves = [];
162851
+ for (const p2 of issuePaths) {
162852
+ const relative = p2.slice(1);
162853
+ const result = pruneNestedConfigLeaf(prunedBlock, relative);
162854
+ if (result) {
162855
+ prunedBlock = result.block;
162856
+ prunedLeaves.push(result.removed);
162857
+ }
162858
+ }
162859
+ patched[key] = prunedBlock;
162860
+ const reason2 = customMessagesByKey.get(key);
162861
+ warnings.push(`"${key}": invalid nested field(s) ${prunedLeaves.map((l) => `"${l}"`).join(", ")}, using defaults for those.${reason2 ? ` ${reason2}` : ""}`);
162862
+ continue;
162620
162863
  }
162864
+ delete patched[key];
162865
+ const defaultVal = defaults[key];
162866
+ const reason = customMessagesByKey.get(key);
162867
+ warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultVal)}.${reason ? ` ${reason}` : ""}`);
162621
162868
  }
162622
162869
  const retryMigrated = migrateLegacyExperimental(patched, preMigrationWarnings);
162623
162870
  const retryParsed = MagicContextConfigSchema.safeParse(retryMigrated);
@@ -162653,14 +162900,13 @@ function loadPluginConfig(directory) {
162653
162900
  if (projectLoaded) {
162654
162901
  allWarnings.push(...projectLoaded.warnings.map((w3) => `[project config] ${w3}`));
162655
162902
  const projectRaw = { ...projectLoaded.config };
162656
- const strippedUserOnlyFields = getProjectUserOnlyFields(projectRaw);
162657
- if (strippedUserOnlyFields.length > 0) {
162658
- for (const key of strippedUserOnlyFields) {
162659
- delete projectRaw[key];
162660
- }
162661
- allWarnings.push(`[project config] Ignoring ${strippedUserOnlyFields.join(", ")} from project config (security: these settings only honor user-level config)`);
162903
+ for (const warning of stripUnsafeProjectConfigFields(projectRaw)) {
162904
+ allWarnings.push(`[project config] ${warning}`);
162662
162905
  }
162663
162906
  mergedRaw = deepMergeRawConfig(mergedRaw, projectRaw);
162907
+ for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userLoaded?.config)) {
162908
+ allWarnings.push(`[project config] ${warning}`);
162909
+ }
162664
162910
  }
162665
162911
  const config2 = parsePluginConfig(mergedRaw);
162666
162912
  if (config2.configWarnings?.length) {
@@ -162682,6 +162928,7 @@ function loadPluginConfig(directory) {
162682
162928
  var CONFIG_FILE_BASENAME = "magic-context";
162683
162929
  var init_config = __esm(() => {
162684
162930
  init_jsonc_parser();
162931
+ init_project_security();
162685
162932
  init_magic_context();
162686
162933
  init_variable();
162687
162934
  });
@@ -164337,6 +164584,14 @@ async function runDoctor(options = {}) {
164337
164584
  log.success("Removed deprecated compaction_markers (always-on since v0.21.4)");
164338
164585
  fixed++;
164339
164586
  }
164587
+ for (const deadKey of ["auto_drop_tool_age", "drop_tool_structure"]) {
164588
+ if (deadKey in mcConfig) {
164589
+ delete mcConfig[deadKey];
164590
+ mcChanged = true;
164591
+ log.success(`Removed deprecated ${deadKey} (replaced by tiered emergency drop)`);
164592
+ fixed++;
164593
+ }
164594
+ }
164340
164595
  const agentEnabledMigration = migrateLegacyAgentEnabledConfigForDoctor(mcConfig, log);
164341
164596
  if (agentEnabledMigration.changed) {
164342
164597
  mcChanged = true;
@@ -164775,7 +165030,8 @@ function loadConfigFile2(path5, scope) {
164775
165030
  const rawText = readFileSync15(path5, "utf-8");
164776
165031
  const substituted = substituteConfigVariables({
164777
165032
  text: rawText,
164778
- configPath: path5
165033
+ configPath: path5,
165034
+ isProjectConfig: scope === "project"
164779
165035
  });
164780
165036
  return {
164781
165037
  path: path5,
@@ -164817,14 +165073,14 @@ function redactConfigValue2(value) {
164817
165073
  }
164818
165074
  return typeof value;
164819
165075
  }
164820
- function isPlainObject2(value) {
165076
+ function isPlainObject4(value) {
164821
165077
  return typeof value === "object" && value !== null && !Array.isArray(value);
164822
165078
  }
164823
165079
  function mergeRawConfigs(base, override) {
164824
165080
  const merged = { ...base };
164825
165081
  for (const [key, overrideValue] of Object.entries(override)) {
164826
165082
  const baseValue = merged[key];
164827
- merged[key] = isPlainObject2(baseValue) && isPlainObject2(overrideValue) ? mergeRawConfigs(baseValue, overrideValue) : overrideValue;
165083
+ merged[key] = isPlainObject4(baseValue) && isPlainObject4(overrideValue) ? mergeRawConfigs(baseValue, overrideValue) : overrideValue;
164828
165084
  }
164829
165085
  return merged;
164830
165086
  }
@@ -164838,10 +165094,15 @@ function parsePiConfig(rawConfig, recoveredTopLevelKeys = []) {
164838
165094
  }
164839
165095
  const defaults = MagicContextConfigSchema.parse({});
164840
165096
  const errorPaths = new Set;
165097
+ const issuePathsByKey = new Map;
164841
165098
  for (const issue2 of parsed.error.issues) {
164842
165099
  const topKey = issue2.path[0];
164843
165100
  if (topKey !== undefined) {
164844
- errorPaths.add(String(topKey));
165101
+ const key = String(topKey);
165102
+ errorPaths.add(key);
165103
+ const paths = issuePathsByKey.get(key) ?? [];
165104
+ paths.push([...issue2.path]);
165105
+ issuePathsByKey.set(key, paths);
164845
165106
  }
164846
165107
  }
164847
165108
  const patched = { ...migrated };
@@ -164849,11 +165110,32 @@ function parsePiConfig(rawConfig, recoveredTopLevelKeys = []) {
164849
165110
  for (const key of errorPaths) {
164850
165111
  recoveredTopLevelKeys.push(key);
164851
165112
  const isAgentConfig = key === "historian" || key === "dreamer" || key === "sidekick";
164852
- delete patched[key];
164853
165113
  if (isAgentConfig) {
165114
+ delete patched[key];
164854
165115
  warnings.push(`"${key}": invalid agent configuration, ignoring. Check your magic-context.jsonc.`);
164855
165116
  continue;
164856
165117
  }
165118
+ const issuePaths = issuePathsByKey.get(key) ?? [];
165119
+ const rawValue = migrated[key];
165120
+ const allNested = issuePaths.length > 0 && issuePaths.every((p2) => p2.length >= 2) && typeof rawValue === "object" && rawValue !== null && !Array.isArray(rawValue);
165121
+ if (allNested) {
165122
+ let prunedBlock = {
165123
+ ...rawValue
165124
+ };
165125
+ const prunedLeaves = [];
165126
+ for (const p2 of issuePaths) {
165127
+ const relative = p2.slice(1);
165128
+ const result = pruneNestedConfigLeaf(prunedBlock, relative);
165129
+ if (result) {
165130
+ prunedBlock = result.block;
165131
+ prunedLeaves.push(result.removed);
165132
+ }
165133
+ }
165134
+ patched[key] = prunedBlock;
165135
+ warnings.push(`"${key}": invalid nested field(s) ${prunedLeaves.map((l) => `"${l}"`).join(", ")}, using defaults for those.`);
165136
+ continue;
165137
+ }
165138
+ delete patched[key];
164857
165139
  const defaultValue = defaults[key];
164858
165140
  warnings.push(`"${key}": invalid value (${redactConfigValue2(rawConfig[key])}), using default ${JSON.stringify(defaultValue)}.`);
164859
165141
  }
@@ -164886,10 +165168,22 @@ function loadPiConfig(opts = {}) {
164886
165168
  return 0;
164887
165169
  return a.scope === "user" ? -1 : 1;
164888
165170
  });
165171
+ const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
164889
165172
  for (const loaded of mergeFiles) {
164890
165173
  const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
164891
165174
  warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
164892
- rawConfig = mergeRawConfigs(rawConfig, loaded.config);
165175
+ if (loaded.scope === "project") {
165176
+ const projectRaw = { ...loaded.config };
165177
+ for (const warning of stripUnsafeProjectConfigFields(projectRaw)) {
165178
+ warnings.push(`${prefix} ${warning}`);
165179
+ }
165180
+ rawConfig = mergeRawConfigs(rawConfig, projectRaw);
165181
+ for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
165182
+ warnings.push(`${prefix} ${warning}`);
165183
+ }
165184
+ } else {
165185
+ rawConfig = mergeRawConfigs(rawConfig, loaded.config);
165186
+ }
164893
165187
  }
164894
165188
  const parsed = parsePiConfig(rawConfig);
164895
165189
  warnings.push(...parsed.warnings.map((warning) => `[merged config] ${warning}`));
@@ -164901,6 +165195,7 @@ function loadPiConfig(opts = {}) {
164901
165195
  }
164902
165196
  var import_comment_json10, CONFIG_FILE_NAME = "magic-context";
164903
165197
  var init_config2 = __esm(() => {
165198
+ init_project_security();
164904
165199
  init_magic_context();
164905
165200
  init_variable();
164906
165201
  import_comment_json10 = __toESM(require_src2(), 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/magic-context",
3
- "version": "0.22.4",
3
+ "version": "0.23.0",
4
4
  "description": "Unified CLI for Magic Context — setup, doctor, and migration across OpenCode and Pi",
5
5
  "keywords": [
6
6
  "opencode",