@cortexkit/magic-context 0.22.3 → 0.22.5

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 +279 -40
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -145656,6 +145656,9 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
145656
145656
  cached_m0_materialized_at INTEGER,
145657
145657
  cached_m0_session_facts_version INTEGER,
145658
145658
  cached_m0_upgrade_state TEXT,
145659
+ cached_m0_system_hash TEXT,
145660
+ cached_m0_tool_set_hash TEXT,
145661
+ cached_m0_model_key TEXT,
145659
145662
  cached_m0_last_baseline_end_message_id TEXT,
145660
145663
  upgrade_reminded_at INTEGER,
145661
145664
  pi_stable_id_scheme INTEGER
@@ -145786,6 +145789,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
145786
145789
  ensureColumn(db, "session_meta", "system_prompt_hash", "TEXT DEFAULT ''");
145787
145790
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
145788
145791
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
145792
+ ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
145789
145793
  ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
145790
145794
  ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
145791
145795
  ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
@@ -145847,6 +145851,9 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
145847
145851
  ensureColumn(db, "session_meta", "cached_m0_materialized_at", "INTEGER");
145848
145852
  ensureColumn(db, "session_meta", "cached_m0_session_facts_version", "INTEGER");
145849
145853
  ensureColumn(db, "session_meta", "cached_m0_upgrade_state", "TEXT");
145854
+ ensureColumn(db, "session_meta", "cached_m0_system_hash", "TEXT");
145855
+ ensureColumn(db, "session_meta", "cached_m0_tool_set_hash", "TEXT");
145856
+ ensureColumn(db, "session_meta", "cached_m0_model_key", "TEXT");
145850
145857
  ensureColumn(db, "session_meta", "cached_m0_last_baseline_end_message_id", "TEXT");
145851
145858
  ensureColumn(db, "session_meta", "upgrade_reminded_at", "INTEGER");
145852
145859
  db.exec(`
@@ -145933,6 +145940,7 @@ function healNullTextColumns(db) {
145933
145940
  ["todo_synthetic_state_json", ""],
145934
145941
  ["system_prompt_hash", ""],
145935
145942
  ["stripped_placeholder_ids", ""],
145943
+ ["stale_reduce_stripped_ids", ""],
145936
145944
  ["memory_block_cache", ""],
145937
145945
  ["memory_block_ids", ""],
145938
145946
  ["compaction_marker_state", ""],
@@ -146057,7 +146065,7 @@ function closeDatabase() {
146057
146065
  }
146058
146066
  }
146059
146067
  }
146060
- var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 29, sqlitePragmaConfig;
146068
+ var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 30, sqlitePragmaConfig;
146061
146069
  var init_storage_db = __esm(async () => {
146062
146070
  init_data_path();
146063
146071
  init_logger();
@@ -146880,15 +146888,38 @@ var init_migrations = __esm(async () => {
146880
146888
  db.exec("ALTER TABLE notes ADD COLUMN anchor_ordinal INTEGER");
146881
146889
  }
146882
146890
  }
146891
+ },
146892
+ {
146893
+ version: 30,
146894
+ description: "HARD-bust m[0] markers: cached system/tool-set/model identity",
146895
+ up: (db) => {
146896
+ const hasSessionMeta = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_meta' LIMIT 1").get();
146897
+ if (!hasSessionMeta)
146898
+ return;
146899
+ ensureColumn(db, "session_meta", "cached_m0_system_hash", "TEXT");
146900
+ ensureColumn(db, "session_meta", "cached_m0_tool_set_hash", "TEXT");
146901
+ ensureColumn(db, "session_meta", "cached_m0_model_key", "TEXT");
146902
+ const columns = new Set(db.prepare("PRAGMA table_info(session_meta)").all().map((column) => column.name));
146903
+ if (columns.has("cached_m0_bytes")) {
146904
+ db.prepare(`UPDATE session_meta SET
146905
+ cached_m0_bytes = NULL,
146906
+ cached_m1_bytes = NULL,
146907
+ cached_m0_materialized_at = NULL,
146908
+ cached_m0_system_hash = NULL,
146909
+ cached_m0_tool_set_hash = NULL,
146910
+ cached_m0_model_key = NULL`).run();
146911
+ }
146912
+ }
146883
146913
  }
146884
146914
  ];
146885
146915
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
146886
146916
  });
146887
146917
 
146888
146918
  // ../plugin/src/features/magic-context/project-docs-hash.ts
146889
- var docsCache;
146919
+ var MAX_PROJECT_DOC_BYTES, docsCache;
146890
146920
  var init_project_docs_hash = __esm(() => {
146891
146921
  init_compartment_storage();
146922
+ MAX_PROJECT_DOC_BYTES = 256 * 1024;
146892
146923
  docsCache = new Map;
146893
146924
  });
146894
146925
 
@@ -147567,6 +147598,98 @@ function migrateLegacyExperimental(rawConfig, warnings) {
147567
147598
  return patched;
147568
147599
  }
147569
147600
 
147601
+ // ../plugin/src/config/project-security.ts
147602
+ function isPlainObject(value) {
147603
+ return typeof value === "object" && value !== null && !Array.isArray(value);
147604
+ }
147605
+ function stripUnsafeProjectConfigFields(projectRaw) {
147606
+ const warnings = [];
147607
+ if ("auto_update" in projectRaw) {
147608
+ delete projectRaw.auto_update;
147609
+ warnings.push("Ignoring auto_update from project config (security: this setting only honors user-level config).");
147610
+ }
147611
+ for (const agentKey of HIDDEN_AGENT_KEYS) {
147612
+ const block = projectRaw[agentKey];
147613
+ if (!isPlainObject(block))
147614
+ continue;
147615
+ const removed = [];
147616
+ for (const field of AGENT_ESCALATION_FIELDS) {
147617
+ if (field in block) {
147618
+ delete block[field];
147619
+ removed.push(field);
147620
+ }
147621
+ }
147622
+ if (removed.length > 0) {
147623
+ warnings.push(`Ignoring ${agentKey}.${removed.join("/")} from project config ` + "(security: a repository cannot reprogram or re-permission hidden agents).");
147624
+ }
147625
+ }
147626
+ return warnings;
147627
+ }
147628
+ function normalizeEndpoint(value) {
147629
+ if (typeof value !== "string")
147630
+ return;
147631
+ const trimmed = value.trim().replace(/\/+$/, "");
147632
+ return trimmed.length > 0 ? trimmed.toLowerCase() : undefined;
147633
+ }
147634
+ function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
147635
+ const projectEmbedding = projectRaw.embedding;
147636
+ if (!isPlainObject(projectEmbedding))
147637
+ return [];
147638
+ const redirectsEndpoint = "endpoint" in projectEmbedding;
147639
+ if (!redirectsEndpoint)
147640
+ return [];
147641
+ const userEmbedding = userRaw?.embedding;
147642
+ if (isPlainObject(userEmbedding)) {
147643
+ const projectEndpoint = normalizeEndpoint(projectEmbedding.endpoint);
147644
+ const userEndpoint = normalizeEndpoint(userEmbedding.endpoint);
147645
+ if (projectEndpoint !== undefined && projectEndpoint === userEndpoint) {
147646
+ return [];
147647
+ }
147648
+ }
147649
+ const providesOwnKey = typeof projectEmbedding.api_key === "string" && projectEmbedding.api_key.length > 0;
147650
+ if (providesOwnKey)
147651
+ return [];
147652
+ const mergedEmbedding = mergedRaw.embedding;
147653
+ if (!isPlainObject(mergedEmbedding))
147654
+ return [];
147655
+ if (!("api_key" in mergedEmbedding))
147656
+ return [];
147657
+ delete mergedEmbedding.api_key;
147658
+ return [
147659
+ "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)."
147660
+ ];
147661
+ }
147662
+ var HIDDEN_AGENT_KEYS, AGENT_ESCALATION_FIELDS;
147663
+ var init_project_security = __esm(() => {
147664
+ HIDDEN_AGENT_KEYS = ["historian", "dreamer", "sidekick"];
147665
+ AGENT_ESCALATION_FIELDS = ["prompt", "permission", "tools", "system_prompt"];
147666
+ });
147667
+
147668
+ // ../plugin/src/config/prune-config-leaf.ts
147669
+ function isPlainObject2(value) {
147670
+ return typeof value === "object" && value !== null && !Array.isArray(value);
147671
+ }
147672
+ function pruneNestedConfigLeaf(block, relativePath) {
147673
+ if (relativePath.length === 0)
147674
+ return null;
147675
+ const result = { ...block };
147676
+ let cursor = result;
147677
+ for (let i = 0;i < relativePath.length - 1; i++) {
147678
+ const seg = String(relativePath[i]);
147679
+ const child = cursor[seg];
147680
+ if (!isPlainObject2(child))
147681
+ return null;
147682
+ const clonedChild = { ...child };
147683
+ cursor[seg] = clonedChild;
147684
+ cursor = clonedChild;
147685
+ }
147686
+ const leaf = String(relativePath[relativePath.length - 1]);
147687
+ if (!(leaf in cursor))
147688
+ return null;
147689
+ delete cursor[leaf];
147690
+ return { block: result, removed: relativePath.map(String).join(".") };
147691
+ }
147692
+
147570
147693
  // ../../node_modules/.bun/zod@4.4.3/node_modules/zod/v4/core/core.js
147571
147694
  function $constructor(name2, initializer, params) {
147572
147695
  function init(inst, def) {
@@ -147677,7 +147800,7 @@ __export(exports_util, {
147677
147800
  jsonStringifyReplacer: () => jsonStringifyReplacer,
147678
147801
  joinValues: () => joinValues,
147679
147802
  issue: () => issue,
147680
- isPlainObject: () => isPlainObject,
147803
+ isPlainObject: () => isPlainObject3,
147681
147804
  isObject: () => isObject,
147682
147805
  hexToUint8Array: () => hexToUint8Array,
147683
147806
  getSizableOrigin: () => getSizableOrigin,
@@ -147842,7 +147965,7 @@ function slugify(input) {
147842
147965
  function isObject(data) {
147843
147966
  return typeof data === "object" && data !== null && !Array.isArray(data);
147844
147967
  }
147845
- function isPlainObject(o) {
147968
+ function isPlainObject3(o) {
147846
147969
  if (isObject(o) === false)
147847
147970
  return false;
147848
147971
  const ctor = o.constructor;
@@ -147859,7 +147982,7 @@ function isPlainObject(o) {
147859
147982
  return true;
147860
147983
  }
147861
147984
  function shallowClone(o) {
147862
- if (isPlainObject(o))
147985
+ if (isPlainObject3(o))
147863
147986
  return { ...o };
147864
147987
  if (Array.isArray(o))
147865
147988
  return [...o];
@@ -147999,7 +148122,7 @@ function omit(schema, mask) {
147999
148122
  return clone(schema, def);
148000
148123
  }
148001
148124
  function extend(schema, shape) {
148002
- if (!isPlainObject(shape)) {
148125
+ if (!isPlainObject3(shape)) {
148003
148126
  throw new Error("Invalid input to extend: expected a plain object");
148004
148127
  }
148005
148128
  const checks = schema._zod.def.checks;
@@ -148022,7 +148145,7 @@ function extend(schema, shape) {
148022
148145
  return clone(schema, def);
148023
148146
  }
148024
148147
  function safeExtend(schema, shape) {
148025
- if (!isPlainObject(shape)) {
148148
+ if (!isPlainObject3(shape)) {
148026
148149
  throw new Error("Invalid input to safeExtend: expected a plain object");
148027
148150
  }
148028
148151
  const def = mergeDefs(schema._zod.def, {
@@ -149505,7 +149628,7 @@ function mergeValues(a, b2) {
149505
149628
  if (a instanceof Date && b2 instanceof Date && +a === +b2) {
149506
149629
  return { valid: true, data: a };
149507
149630
  }
149508
- if (isPlainObject(a) && isPlainObject(b2)) {
149631
+ if (isPlainObject3(a) && isPlainObject3(b2)) {
149509
149632
  const bKeys = Object.keys(b2);
149510
149633
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
149511
149634
  const newObj = { ...a, ...b2 };
@@ -150762,7 +150885,7 @@ var init_schemas = __esm(() => {
150762
150885
  $ZodType.init(inst, def);
150763
150886
  inst._zod.parse = (payload, ctx) => {
150764
150887
  const input = payload.value;
150765
- if (!isPlainObject(input)) {
150888
+ if (!isPlainObject3(input)) {
150766
150889
  payload.issues.push({
150767
150890
  expected: "record",
150768
150891
  code: "invalid_type",
@@ -162358,6 +162481,7 @@ var init_magic_context = __esm(() => {
162358
162481
  model: DEFAULT_LOCAL_EMBEDDING_MODEL
162359
162482
  }).describe("Embedding provider configuration"),
162360
162483
  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).'),
162484
+ 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."),
162361
162485
  caveman_text_compression: exports_external.object({
162362
162486
  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%)."),
162363
162487
  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.")
@@ -162398,6 +162522,21 @@ var init_magic_context = __esm(() => {
162398
162522
  import { existsSync as existsSync14, readFileSync as readFileSync8 } from "node:fs";
162399
162523
  import { homedir as homedir7 } from "node:os";
162400
162524
  import { dirname as dirname7, isAbsolute, resolve as resolve2 } from "node:path";
162525
+ function sensitiveFilePathReason(resolvedPath) {
162526
+ const home = homedir7();
162527
+ const sensitiveDirs = [
162528
+ { dir: resolve2(home, ".ssh"), label: "SSH keys" },
162529
+ { dir: resolve2(home, ".aws"), label: "AWS credentials" },
162530
+ { dir: resolve2(home, ".gnupg"), label: "GnuPG keyring" },
162531
+ { dir: resolve2(home, ".config", "gh"), label: "GitHub CLI auth" }
162532
+ ];
162533
+ for (const { dir, label } of sensitiveDirs) {
162534
+ if (resolvedPath === dir || resolvedPath.startsWith(`${dir}/`)) {
162535
+ return label;
162536
+ }
162537
+ }
162538
+ return null;
162539
+ }
162401
162540
  function substituteConfigVariables(input) {
162402
162541
  const warnings = [];
162403
162542
  let text2 = input.text;
@@ -162451,6 +162590,10 @@ function substituteConfigVariables(input) {
162451
162590
  } else if (!isAbsolute(filePath)) {
162452
162591
  filePath = resolve2(configDir, filePath);
162453
162592
  }
162593
+ const sensitiveReason = sensitiveFilePathReason(filePath);
162594
+ if (sensitiveReason) {
162595
+ warnings.push(`${token} resolves to a sensitive path (${sensitiveReason}: ${filePath}); ` + "inlining its contents into config — make sure this is intentional.");
162596
+ }
162454
162597
  if (!existsSync14(filePath)) {
162455
162598
  warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
162456
162599
  continue;
@@ -162521,9 +162664,6 @@ function deepMergeRawConfig(base, override) {
162521
162664
  }
162522
162665
  return result;
162523
162666
  }
162524
- function getProjectUserOnlyFields(config2) {
162525
- return "auto_update" in config2 ? ["auto_update"] : [];
162526
- }
162527
162667
  function redactConfigValue(value) {
162528
162668
  if (value === undefined)
162529
162669
  return "<missing>";
@@ -162562,12 +162702,16 @@ function parsePluginConfig(rawConfig, recoveredTopLevelKeys = []) {
162562
162702
  const warnings = [];
162563
162703
  const errorPaths = new Set;
162564
162704
  const customMessagesByKey = new Map;
162705
+ const issuePathsByKey = new Map;
162565
162706
  const GENERIC_ZOD_PREFIXES = ["Too big", "Too small", "Invalid input", "Invalid", "Expected"];
162566
162707
  for (const issue2 of parsed.error.issues) {
162567
162708
  const topKey = issue2.path[0];
162568
162709
  if (topKey !== undefined) {
162569
162710
  const key = String(topKey);
162570
162711
  errorPaths.add(key);
162712
+ const paths = issuePathsByKey.get(key) ?? [];
162713
+ paths.push([...issue2.path]);
162714
+ issuePathsByKey.set(key, paths);
162571
162715
  const msg = issue2.message;
162572
162716
  if (msg && !GENERIC_ZOD_PREFIXES.some((p2) => msg.startsWith(p2))) {
162573
162717
  if (!customMessagesByKey.has(key)) {
@@ -162583,12 +162727,33 @@ function parsePluginConfig(rawConfig, recoveredTopLevelKeys = []) {
162583
162727
  if (isAgentConfig) {
162584
162728
  delete patched[key];
162585
162729
  warnings.push(`"${key}": invalid agent configuration, ignoring. Check your magic-context.jsonc.`);
162586
- } else {
162587
- delete patched[key];
162588
- const defaultVal = defaults[key];
162589
- const reason = customMessagesByKey.get(key);
162590
- warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultVal)}.${reason ? ` ${reason}` : ""}`);
162730
+ continue;
162591
162731
  }
162732
+ const issuePaths = issuePathsByKey.get(key) ?? [];
162733
+ const rawValue = rawConfig[key];
162734
+ const allNested = issuePaths.length > 0 && issuePaths.every((p2) => p2.length >= 2) && typeof rawValue === "object" && rawValue !== null && !Array.isArray(rawValue);
162735
+ if (allNested) {
162736
+ let prunedBlock = {
162737
+ ...rawValue
162738
+ };
162739
+ const prunedLeaves = [];
162740
+ for (const p2 of issuePaths) {
162741
+ const relative = p2.slice(1);
162742
+ const result = pruneNestedConfigLeaf(prunedBlock, relative);
162743
+ if (result) {
162744
+ prunedBlock = result.block;
162745
+ prunedLeaves.push(result.removed);
162746
+ }
162747
+ }
162748
+ patched[key] = prunedBlock;
162749
+ const reason2 = customMessagesByKey.get(key);
162750
+ warnings.push(`"${key}": invalid nested field(s) ${prunedLeaves.map((l) => `"${l}"`).join(", ")}, using defaults for those.${reason2 ? ` ${reason2}` : ""}`);
162751
+ continue;
162752
+ }
162753
+ delete patched[key];
162754
+ const defaultVal = defaults[key];
162755
+ const reason = customMessagesByKey.get(key);
162756
+ warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultVal)}.${reason ? ` ${reason}` : ""}`);
162592
162757
  }
162593
162758
  const retryMigrated = migrateLegacyExperimental(patched, preMigrationWarnings);
162594
162759
  const retryParsed = MagicContextConfigSchema.safeParse(retryMigrated);
@@ -162624,14 +162789,13 @@ function loadPluginConfig(directory) {
162624
162789
  if (projectLoaded) {
162625
162790
  allWarnings.push(...projectLoaded.warnings.map((w3) => `[project config] ${w3}`));
162626
162791
  const projectRaw = { ...projectLoaded.config };
162627
- const strippedUserOnlyFields = getProjectUserOnlyFields(projectRaw);
162628
- if (strippedUserOnlyFields.length > 0) {
162629
- for (const key of strippedUserOnlyFields) {
162630
- delete projectRaw[key];
162631
- }
162632
- allWarnings.push(`[project config] Ignoring ${strippedUserOnlyFields.join(", ")} from project config (security: these settings only honor user-level config)`);
162792
+ for (const warning of stripUnsafeProjectConfigFields(projectRaw)) {
162793
+ allWarnings.push(`[project config] ${warning}`);
162633
162794
  }
162634
162795
  mergedRaw = deepMergeRawConfig(mergedRaw, projectRaw);
162796
+ for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userLoaded?.config)) {
162797
+ allWarnings.push(`[project config] ${warning}`);
162798
+ }
162635
162799
  }
162636
162800
  const config2 = parsePluginConfig(mergedRaw);
162637
162801
  if (config2.configWarnings?.length) {
@@ -162653,6 +162817,7 @@ function loadPluginConfig(directory) {
162653
162817
  var CONFIG_FILE_BASENAME = "magic-context";
162654
162818
  var init_config = __esm(() => {
162655
162819
  init_jsonc_parser();
162820
+ init_project_security();
162656
162821
  init_magic_context();
162657
162822
  init_variable();
162658
162823
  });
@@ -162660,25 +162825,56 @@ var init_config = __esm(() => {
162660
162825
  // ../plugin/src/features/magic-context/key-files/aft-availability.ts
162661
162826
  import { existsSync as existsSync16, readFileSync as readFileSync10 } from "node:fs";
162662
162827
  import { homedir as homedir9 } from "node:os";
162663
- import { join as join13 } from "node:path";
162828
+ import { isAbsolute as isAbsolute2, join as join13, resolve as resolve3 } from "node:path";
162829
+ import { fileURLToPath } from "node:url";
162664
162830
  function parseConfig(path5) {
162665
162831
  if (!existsSync16(path5))
162666
162832
  return null;
162667
162833
  return import_comment_json6.parse(readFileSync10(path5, "utf-8"));
162668
162834
  }
162669
- function entryMatchesAft(entry) {
162835
+ function stringMentionsAft(value) {
162836
+ return AFT_NAME_NEEDLES.some((needle) => value.includes(needle));
162837
+ }
162838
+ function resolveLocalEntryPackageName(value, configDir) {
162839
+ let dir = null;
162840
+ if (value.startsWith("file://")) {
162841
+ try {
162842
+ dir = fileURLToPath(value);
162843
+ } catch {
162844
+ return null;
162845
+ }
162846
+ } else if (value.startsWith("~/")) {
162847
+ dir = join13(homedir9(), value.slice(2));
162848
+ } else if (value.startsWith("/") || value.startsWith("./") || value.startsWith("../")) {
162849
+ dir = isAbsolute2(value) ? value : resolve3(configDir, value);
162850
+ } else {
162851
+ return null;
162852
+ }
162853
+ try {
162854
+ const pkg = JSON.parse(readFileSync10(join13(dir, "package.json"), "utf-8"));
162855
+ return typeof pkg.name === "string" ? pkg.name : null;
162856
+ } catch {
162857
+ return null;
162858
+ }
162859
+ }
162860
+ function entryMatchesAft(entry, configDir) {
162670
162861
  const value = Array.isArray(entry) ? entry[0] : entry;
162671
- return typeof value === "string" && (value.includes("@cortexkit/aft") || value.includes("aft-opencode") || value.includes("aft-pi"));
162862
+ if (typeof value !== "string")
162863
+ return false;
162864
+ if (stringMentionsAft(value))
162865
+ return true;
162866
+ const name2 = resolveLocalEntryPackageName(value, configDir);
162867
+ return name2 != null && stringMentionsAft(name2);
162672
162868
  }
162673
- function hasAftInArray(value) {
162674
- return Array.isArray(value) && value.some(entryMatchesAft);
162869
+ function hasAftInArray(value, configDir) {
162870
+ return Array.isArray(value) && value.some((entry) => entryMatchesAft(entry, configDir));
162675
162871
  }
162676
- function hasAftAtKeys(value, keys) {
162872
+ function hasAftAtKeys(value, keys, configDir) {
162677
162873
  if (!value || typeof value !== "object")
162678
162874
  return false;
162679
162875
  const record2 = value;
162680
162876
  for (const key of keys) {
162681
- if (hasAftInArray(record2[key]))
162877
+ if (hasAftInArray(record2[key], configDir))
162682
162878
  return true;
162683
162879
  }
162684
162880
  return false;
@@ -162695,7 +162891,8 @@ function getAftAvailability() {
162695
162891
  for (const path5 of opencodePaths) {
162696
162892
  try {
162697
162893
  const config2 = parseConfig(path5);
162698
- if (hasAftAtKeys(config2, ["plugin", "plugins", "mcp", "mcp_servers"])) {
162894
+ const configDir = join13(path5, "..");
162895
+ if (hasAftAtKeys(config2, ["plugin", "plugins", "mcp", "mcp_servers"], configDir)) {
162699
162896
  opencode = true;
162700
162897
  break;
162701
162898
  }
@@ -162705,12 +162902,13 @@ function getAftAvailability() {
162705
162902
  for (const path5 of piPaths) {
162706
162903
  try {
162707
162904
  const config2 = parseConfig(path5);
162708
- if (hasAftAtKeys(config2, ["packages", "extensions"])) {
162905
+ const configDir = join13(path5, "..");
162906
+ if (hasAftAtKeys(config2, ["packages", "extensions"], configDir)) {
162709
162907
  pi = true;
162710
162908
  break;
162711
162909
  }
162712
162910
  const agent = config2?.agent;
162713
- if (hasAftAtKeys(agent, ["packages", "extensions"])) {
162911
+ if (hasAftAtKeys(agent, ["packages", "extensions"], configDir)) {
162714
162912
  pi = true;
162715
162913
  break;
162716
162914
  }
@@ -162724,9 +162922,10 @@ function getAftAvailability() {
162724
162922
  checkedPaths
162725
162923
  };
162726
162924
  }
162727
- var import_comment_json6, overrideAvailability = null;
162925
+ var import_comment_json6, overrideAvailability = null, AFT_NAME_NEEDLES;
162728
162926
  var init_aft_availability = __esm(() => {
162729
162927
  import_comment_json6 = __toESM(require_src2(), 1);
162928
+ AFT_NAME_NEEDLES = ["@cortexkit/aft", "aft-opencode", "aft-pi"];
162730
162929
  });
162731
162930
 
162732
162931
  // ../plugin/src/features/magic-context/memory/embedding-probe.ts
@@ -164712,7 +164911,8 @@ function loadConfigFile2(path5, scope) {
164712
164911
  const rawText = readFileSync15(path5, "utf-8");
164713
164912
  const substituted = substituteConfigVariables({
164714
164913
  text: rawText,
164715
- configPath: path5
164914
+ configPath: path5,
164915
+ isProjectConfig: scope === "project"
164716
164916
  });
164717
164917
  return {
164718
164918
  path: path5,
@@ -164754,14 +164954,14 @@ function redactConfigValue2(value) {
164754
164954
  }
164755
164955
  return typeof value;
164756
164956
  }
164757
- function isPlainObject2(value) {
164957
+ function isPlainObject4(value) {
164758
164958
  return typeof value === "object" && value !== null && !Array.isArray(value);
164759
164959
  }
164760
164960
  function mergeRawConfigs(base, override) {
164761
164961
  const merged = { ...base };
164762
164962
  for (const [key, overrideValue] of Object.entries(override)) {
164763
164963
  const baseValue = merged[key];
164764
- merged[key] = isPlainObject2(baseValue) && isPlainObject2(overrideValue) ? mergeRawConfigs(baseValue, overrideValue) : overrideValue;
164964
+ merged[key] = isPlainObject4(baseValue) && isPlainObject4(overrideValue) ? mergeRawConfigs(baseValue, overrideValue) : overrideValue;
164765
164965
  }
164766
164966
  return merged;
164767
164967
  }
@@ -164775,10 +164975,15 @@ function parsePiConfig(rawConfig, recoveredTopLevelKeys = []) {
164775
164975
  }
164776
164976
  const defaults = MagicContextConfigSchema.parse({});
164777
164977
  const errorPaths = new Set;
164978
+ const issuePathsByKey = new Map;
164778
164979
  for (const issue2 of parsed.error.issues) {
164779
164980
  const topKey = issue2.path[0];
164780
164981
  if (topKey !== undefined) {
164781
- errorPaths.add(String(topKey));
164982
+ const key = String(topKey);
164983
+ errorPaths.add(key);
164984
+ const paths = issuePathsByKey.get(key) ?? [];
164985
+ paths.push([...issue2.path]);
164986
+ issuePathsByKey.set(key, paths);
164782
164987
  }
164783
164988
  }
164784
164989
  const patched = { ...migrated };
@@ -164786,11 +164991,32 @@ function parsePiConfig(rawConfig, recoveredTopLevelKeys = []) {
164786
164991
  for (const key of errorPaths) {
164787
164992
  recoveredTopLevelKeys.push(key);
164788
164993
  const isAgentConfig = key === "historian" || key === "dreamer" || key === "sidekick";
164789
- delete patched[key];
164790
164994
  if (isAgentConfig) {
164995
+ delete patched[key];
164791
164996
  warnings.push(`"${key}": invalid agent configuration, ignoring. Check your magic-context.jsonc.`);
164792
164997
  continue;
164793
164998
  }
164999
+ const issuePaths = issuePathsByKey.get(key) ?? [];
165000
+ const rawValue = migrated[key];
165001
+ const allNested = issuePaths.length > 0 && issuePaths.every((p2) => p2.length >= 2) && typeof rawValue === "object" && rawValue !== null && !Array.isArray(rawValue);
165002
+ if (allNested) {
165003
+ let prunedBlock = {
165004
+ ...rawValue
165005
+ };
165006
+ const prunedLeaves = [];
165007
+ for (const p2 of issuePaths) {
165008
+ const relative = p2.slice(1);
165009
+ const result = pruneNestedConfigLeaf(prunedBlock, relative);
165010
+ if (result) {
165011
+ prunedBlock = result.block;
165012
+ prunedLeaves.push(result.removed);
165013
+ }
165014
+ }
165015
+ patched[key] = prunedBlock;
165016
+ warnings.push(`"${key}": invalid nested field(s) ${prunedLeaves.map((l) => `"${l}"`).join(", ")}, using defaults for those.`);
165017
+ continue;
165018
+ }
165019
+ delete patched[key];
164794
165020
  const defaultValue = defaults[key];
164795
165021
  warnings.push(`"${key}": invalid value (${redactConfigValue2(rawConfig[key])}), using default ${JSON.stringify(defaultValue)}.`);
164796
165022
  }
@@ -164823,10 +165049,22 @@ function loadPiConfig(opts = {}) {
164823
165049
  return 0;
164824
165050
  return a.scope === "user" ? -1 : 1;
164825
165051
  });
165052
+ const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
164826
165053
  for (const loaded of mergeFiles) {
164827
165054
  const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
164828
165055
  warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
164829
- rawConfig = mergeRawConfigs(rawConfig, loaded.config);
165056
+ if (loaded.scope === "project") {
165057
+ const projectRaw = { ...loaded.config };
165058
+ for (const warning of stripUnsafeProjectConfigFields(projectRaw)) {
165059
+ warnings.push(`${prefix} ${warning}`);
165060
+ }
165061
+ rawConfig = mergeRawConfigs(rawConfig, projectRaw);
165062
+ for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
165063
+ warnings.push(`${prefix} ${warning}`);
165064
+ }
165065
+ } else {
165066
+ rawConfig = mergeRawConfigs(rawConfig, loaded.config);
165067
+ }
164830
165068
  }
164831
165069
  const parsed = parsePiConfig(rawConfig);
164832
165070
  warnings.push(...parsed.warnings.map((warning) => `[merged config] ${warning}`));
@@ -164838,6 +165076,7 @@ function loadPiConfig(opts = {}) {
164838
165076
  }
164839
165077
  var import_comment_json10, CONFIG_FILE_NAME = "magic-context";
164840
165078
  var init_config2 = __esm(() => {
165079
+ init_project_security();
164841
165080
  init_magic_context();
164842
165081
  init_variable();
164843
165082
  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.3",
3
+ "version": "0.22.5",
4
4
  "description": "Unified CLI for Magic Context — setup, doctor, and migration across OpenCode and Pi",
5
5
  "keywords": [
6
6
  "opencode",