@cortexkit/pi-magic-context 0.22.4 → 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.
package/dist/index.js CHANGED
@@ -8073,6 +8073,19 @@ function hasGitDir(canonical) {
8073
8073
  return false;
8074
8074
  }
8075
8075
  }
8076
+ function normalizeStoredProjectPath(rawOrStored) {
8077
+ if (rawOrStored.startsWith("git:") || rawOrStored.startsWith("dir:")) {
8078
+ return rawOrStored;
8079
+ }
8080
+ try {
8081
+ return resolveProjectIdentity(rawOrStored);
8082
+ } catch {
8083
+ return directoryFallback(rawOrStored);
8084
+ }
8085
+ }
8086
+ function storedPathBelongsToIdentity(storedProjectPath, projectIdentity) {
8087
+ return storedProjectPath === projectIdentity || normalizeStoredProjectPath(storedProjectPath) === projectIdentity;
8088
+ }
8076
8089
 
8077
8090
  // ../plugin/src/shared/logger.ts
8078
8091
  import * as fs from "node:fs";
@@ -142072,12 +142085,6 @@ function isCompartmentRow(row) {
142072
142085
  const candidate = row;
142073
142086
  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";
142074
142087
  }
142075
- function isSessionFactRow(row) {
142076
- if (row === null || typeof row !== "object")
142077
- return false;
142078
- const candidate = row;
142079
- 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";
142080
- }
142081
142088
  function insertCompartmentRows(db, sessionId, compartments, now) {
142082
142089
  const stmt = getInsertCompartmentStatement(db);
142083
142090
  for (const compartment of compartments) {
@@ -142106,16 +142113,6 @@ function toCompartment(row) {
142106
142113
  createdAt: row.created_at
142107
142114
  };
142108
142115
  }
142109
- function toSessionFact(row) {
142110
- return {
142111
- id: row.id,
142112
- sessionId: row.session_id,
142113
- category: row.category,
142114
- content: row.content,
142115
- createdAt: row.created_at,
142116
- updatedAt: row.updated_at
142117
- };
142118
- }
142119
142116
  function getCompartments(db, sessionId) {
142120
142117
  const rows = db.prepare("SELECT * FROM compartments WHERE session_id = ? ORDER BY sequence ASC").all(sessionId).filter(isCompartmentRow);
142121
142118
  return rows.map(toCompartment);
@@ -142136,10 +142133,6 @@ function appendCompartments(db, sessionId, compartments) {
142136
142133
  insertCompartmentRows(db, sessionId, compartments, now);
142137
142134
  })();
142138
142135
  }
142139
- function getSessionFacts(db, sessionId) {
142140
- const rows = db.prepare("SELECT * FROM session_facts WHERE session_id = ? ORDER BY category ASC, id ASC").all(sessionId).filter(isSessionFactRow);
142141
- return rows.map(toSessionFact);
142142
- }
142143
142136
  function saveRecompStagingPass(db, sessionId, passNumber, compartments, facts) {
142144
142137
  const now = Date.now();
142145
142138
  db.transaction(() => {
@@ -142183,14 +142176,6 @@ function getRecompStaging(db, sessionId) {
142183
142176
  lastEndMessage: lastEnd
142184
142177
  };
142185
142178
  }
142186
- function invalidateAllMemoryBlockCaches(db) {
142187
- try {
142188
- const rows = db.prepare("SELECT session_id FROM session_meta").all();
142189
- for (const row of rows) {
142190
- clearCachedM0M1(db, row.session_id);
142191
- }
142192
- } catch {}
142193
- }
142194
142179
  function clearRecompStaging(db, sessionId) {
142195
142180
  db.transaction(() => {
142196
142181
  db.prepare("DELETE FROM recomp_compartments WHERE session_id = ?").run(sessionId);
@@ -143331,6 +143316,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
143331
143316
  ensureColumn(db, "session_meta", "system_prompt_hash", "TEXT DEFAULT ''");
143332
143317
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
143333
143318
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
143319
+ ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
143334
143320
  ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
143335
143321
  ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
143336
143322
  ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
@@ -143481,6 +143467,7 @@ function healNullTextColumns(db) {
143481
143467
  ["todo_synthetic_state_json", ""],
143482
143468
  ["system_prompt_hash", ""],
143483
143469
  ["stripped_placeholder_ids", ""],
143470
+ ["stale_reduce_stripped_ids", ""],
143484
143471
  ["memory_block_cache", ""],
143485
143472
  ["memory_block_ids", ""],
143486
143473
  ["compaction_marker_state", ""],
@@ -144416,7 +144403,7 @@ function runMigrations(db) {
144416
144403
  }
144417
144404
  // ../plugin/src/features/magic-context/project-docs-hash.ts
144418
144405
  import { createHash as createHash3 } from "node:crypto";
144419
- import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
144406
+ import { lstatSync, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
144420
144407
  import path4 from "node:path";
144421
144408
  var PROJECT_DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
144422
144409
  var PROJECT_DOCS_DELIMITER = `
@@ -144424,6 +144411,7 @@ var PROJECT_DOCS_DELIMITER = `
144424
144411
  ---
144425
144412
 
144426
144413
  `;
144414
+ var MAX_PROJECT_DOC_BYTES = 256 * 1024;
144427
144415
  var docsCache = new Map;
144428
144416
  function canonicalizeDocContent(raw) {
144429
144417
  return raw.replace(/^\uFEFF/, "").replace(/\r\n/g, `
@@ -144433,9 +144421,10 @@ function canonicalizeDocContent(raw) {
144433
144421
  }
144434
144422
  function fingerprintFile(filePath) {
144435
144423
  try {
144436
- const stat = statSync2(filePath);
144424
+ const stat = lstatSync(filePath);
144425
+ const isReadableDoc = stat.isFile() && stat.size <= MAX_PROJECT_DOC_BYTES;
144437
144426
  return {
144438
- exists: stat.isFile(),
144427
+ exists: isReadableDoc,
144439
144428
  mtimeMs: stat.mtimeMs,
144440
144429
  size: stat.size
144441
144430
  };
@@ -144478,6 +144467,16 @@ function readCanonicalPieces(projectDirectory, files) {
144478
144467
  if (!fingerprint?.exists) {
144479
144468
  continue;
144480
144469
  }
144470
+ let safeToRead = false;
144471
+ try {
144472
+ const st = lstatSync(filePath);
144473
+ safeToRead = st.isFile() && st.size <= MAX_PROJECT_DOC_BYTES;
144474
+ } catch {
144475
+ safeToRead = false;
144476
+ }
144477
+ if (!safeToRead) {
144478
+ continue;
144479
+ }
144481
144480
  const canonicalContent = canonicalizeDocContent(readFileSync2(filePath, "utf8"));
144482
144481
  hashPieces.push(`file:${filename}
144483
144482
  ${canonicalContent}`);
@@ -144798,7 +144797,8 @@ function casUpdateJsonArrayColumn(db, sessionId, column, validator, mutate, opti
144798
144797
  }
144799
144798
  for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
144800
144799
  const row = db.prepare(`SELECT ${column} FROM session_meta WHERE session_id = ?`).get(sessionId);
144801
- const currentBlob = row?.[column] ?? "[]";
144800
+ const rawCurrent = row?.[column] ?? null;
144801
+ const currentBlob = rawCurrent ?? "[]";
144802
144802
  const current = parseJsonArray(currentBlob, validator);
144803
144803
  const next = mutate(current);
144804
144804
  if (next === null)
@@ -144806,7 +144806,7 @@ function casUpdateJsonArrayColumn(db, sessionId, column, validator, mutate, opti
144806
144806
  const nextBlob = stableStringify(next);
144807
144807
  if (nextBlob === currentBlob)
144808
144808
  return true;
144809
- const result = db.prepare(`UPDATE session_meta SET ${column} = ? WHERE session_id = ? AND ${column} = ?`).run(nextBlob, sessionId, currentBlob);
144809
+ const result = db.prepare(`UPDATE session_meta SET ${column} = ? WHERE session_id = ? AND ${column} IS ?`).run(nextBlob, sessionId, rawCurrent);
144810
144810
  if (result.changes > 0)
144811
144811
  return true;
144812
144812
  }
@@ -144935,14 +144935,16 @@ function getHistorianFailureState(db, sessionId) {
144935
144935
  };
144936
144936
  }
144937
144937
  function incrementHistorianFailure(db, sessionId, error) {
144938
+ let nextCount = 1;
144938
144939
  db.transaction(() => {
144939
144940
  ensureSessionMetaRow(db, sessionId);
144940
144941
  const current = getHistorianFailureState(db, sessionId);
144941
- const nextCount = current.failureCount + 1;
144942
+ nextCount = current.failureCount + 1;
144942
144943
  db.prepare("UPDATE session_meta SET historian_failure_count = ?, historian_last_error = ?, historian_last_failure_at = ? WHERE session_id = ?").run(nextCount, error, Date.now(), sessionId);
144943
144944
  const reason = error.replace(/\s+/g, " ").trim().slice(0, 300);
144944
144945
  sessionLog(sessionId, `historian failure recorded: count=${nextCount} reason="${reason}"`);
144945
144946
  })();
144947
+ return nextCount;
144946
144948
  }
144947
144949
  function clearHistorianFailureState(db, sessionId) {
144948
144950
  db.transaction(() => {
@@ -145011,10 +145013,39 @@ function getStrippedPlaceholderIds(db, sessionId) {
145011
145013
  } catch {}
145012
145014
  return new Set;
145013
145015
  }
145014
- function setStrippedPlaceholderIds(db, sessionId, ids) {
145016
+ function applyStrippedPlaceholderDelta(db, sessionId, delta) {
145017
+ const add = delta.add ? [...delta.add] : [];
145018
+ const remove = delta.remove ? [...delta.remove] : [];
145019
+ if (add.length === 0 && remove.length === 0)
145020
+ return true;
145015
145021
  ensureSessionMetaRow(db, sessionId);
145016
- const json = ids.size > 0 ? JSON.stringify([...ids]) : "";
145017
- db.prepare("UPDATE session_meta SET stripped_placeholder_ids = ? WHERE session_id = ?").run(json, sessionId);
145022
+ for (let attempt = 0;attempt < CAS_RETRY_LIMIT; attempt += 1) {
145023
+ const row = db.prepare("SELECT stripped_placeholder_ids FROM session_meta WHERE session_id = ?").get(sessionId);
145024
+ const rawStored = row ? row.stripped_placeholder_ids ?? null : null;
145025
+ const current = new Set(parseStrippedBlob(rawStored));
145026
+ for (const id of add)
145027
+ current.add(id);
145028
+ for (const id of remove)
145029
+ current.delete(id);
145030
+ const nextBlob = current.size > 0 ? JSON.stringify([...current]) : "";
145031
+ if (nextBlob === (rawStored ?? ""))
145032
+ return true;
145033
+ const result = db.prepare("UPDATE session_meta SET stripped_placeholder_ids = ? WHERE session_id = ? AND stripped_placeholder_ids IS ?").run(nextBlob, sessionId, rawStored);
145034
+ if (result.changes > 0)
145035
+ return true;
145036
+ }
145037
+ sessionLog(sessionId, `stripped_placeholder_ids CAS: ${CAS_RETRY_LIMIT} retries exhausted`);
145038
+ return false;
145039
+ }
145040
+ function parseStrippedBlob(raw) {
145041
+ if (!raw || raw.length === 0)
145042
+ return [];
145043
+ try {
145044
+ const parsed = JSON.parse(raw);
145045
+ if (Array.isArray(parsed))
145046
+ return parsed.filter((v) => typeof v === "string");
145047
+ } catch {}
145048
+ return [];
145018
145049
  }
145019
145050
  function isPendingCompactionMarker(value) {
145020
145051
  return typeof value === "object" && value !== null && typeof value.ordinal === "number" && typeof value.endMessageId === "string" && typeof value.publishedAt === "number";
@@ -149732,7 +149763,7 @@ async function runDream(args) {
149732
149763
  }
149733
149764
  }
149734
149765
  const deadline = startedAt + args.maxRuntimeMinutes * 60 * 1000;
149735
- const lastDreamAt = getDreamState(args.db, `last_dream_at:${args.projectIdentity}`) ?? getDreamState(args.db, "last_dream_at");
149766
+ const lastDreamAt = getDreamState(args.db, `last_dream_at:${args.projectIdentity}`);
149736
149767
  log(`[dreamer] last dream at: ${lastDreamAt ?? "never"} (project=${args.projectIdentity})`);
149737
149768
  let lastErrorSignature = null;
149738
149769
  let consecutiveSameErrorFailures = 0;
@@ -150114,7 +150145,6 @@ async function runDream(args) {
150114
150145
  const hasSuccessfulTask = result.tasks.some((t) => !t.error && !POST_TASK_NAMES.has(t.name));
150115
150146
  if (hasSuccessfulTask && !lostLease) {
150116
150147
  setDreamState(args.db, `last_dream_at:${args.projectIdentity}`, String(result.finishedAt));
150117
- setDreamState(args.db, "last_dream_at", String(result.finishedAt));
150118
150148
  }
150119
150149
  const totalDuration = ((result.finishedAt - startedAt) / 1000).toFixed(1);
150120
150150
  const succeeded = result.tasks.filter((t) => !t.error).length;
@@ -150390,8 +150420,7 @@ function findProjectsNeedingDream(db) {
150390
150420
  const now = new Date;
150391
150421
  for (const row of projectRows) {
150392
150422
  const lastDreamAtStr = getDreamState(db, `last_dream_at:${row.project_path}`);
150393
- const fallbackStr = !lastDreamAtStr ? getDreamState(db, "last_dream_at") : null;
150394
- const lastDreamAt = Number(lastDreamAtStr ?? fallbackStr ?? "0") || 0;
150423
+ const lastDreamAt = Number(lastDreamAtStr ?? "0") || 0;
150395
150424
  if (lastDreamAt > 0 && isDreamFromCurrentWindow(lastDreamAt, now)) {
150396
150425
  continue;
150397
150426
  }
@@ -165363,6 +165392,59 @@ class LocalEmbeddingProvider {
165363
165392
  }
165364
165393
  }
165365
165394
 
165395
+ // ../plugin/src/features/magic-context/memory/embedding-ssrf.ts
165396
+ var METADATA_HOSTNAMES = new Set(["metadata.google.internal", "metadata.goog"]);
165397
+ var IPV6_METADATA_HOSTS = new Set(["fd00:ec2::254"]);
165398
+ function isLinkLocalIpv4(host) {
165399
+ return /^169\.254\.\d{1,3}\.\d{1,3}$/.test(host);
165400
+ }
165401
+ function ipv4FromMappedIpv6(host) {
165402
+ const m = /^::ffff:(.+)$/.exec(host);
165403
+ if (!m)
165404
+ return null;
165405
+ const tail = m[1];
165406
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(tail))
165407
+ return tail;
165408
+ const hex3 = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(tail);
165409
+ if (hex3) {
165410
+ const hi = Number.parseInt(hex3[1], 16);
165411
+ const lo = Number.parseInt(hex3[2], 16);
165412
+ if (Number.isNaN(hi) || Number.isNaN(lo))
165413
+ return null;
165414
+ return `${hi >> 8 & 255}.${hi & 255}.${lo >> 8 & 255}.${lo & 255}`;
165415
+ }
165416
+ return null;
165417
+ }
165418
+ function blockedEmbeddingEndpointReason(endpoint) {
165419
+ const trimmed = endpoint.trim();
165420
+ if (trimmed.length === 0)
165421
+ return null;
165422
+ let url2;
165423
+ try {
165424
+ url2 = new URL(trimmed);
165425
+ } catch {
165426
+ return `embedding endpoint is not a valid URL: ${trimmed}`;
165427
+ }
165428
+ const host = url2.hostname.toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
165429
+ if (METADATA_HOSTNAMES.has(host)) {
165430
+ return `embedding endpoint host ${host} is a cloud metadata service (blocked)`;
165431
+ }
165432
+ if (IPV6_METADATA_HOSTS.has(host)) {
165433
+ return `embedding endpoint host ${host} is the AWS IPv6 metadata service (blocked)`;
165434
+ }
165435
+ if (isLinkLocalIpv4(host)) {
165436
+ return `embedding endpoint host ${host} is link-local / cloud metadata (blocked)`;
165437
+ }
165438
+ const mappedV4 = ipv4FromMappedIpv6(host);
165439
+ if (mappedV4 && isLinkLocalIpv4(mappedV4)) {
165440
+ return `embedding endpoint host ${host} (IPv4-mapped ${mappedV4}) is link-local / cloud metadata (blocked)`;
165441
+ }
165442
+ if (host.startsWith("fe80:")) {
165443
+ return `embedding endpoint host ${host} is link-local / cloud metadata (blocked)`;
165444
+ }
165445
+ return null;
165446
+ }
165447
+
165366
165448
  // ../plugin/src/features/magic-context/memory/embedding-openai.ts
165367
165449
  function normalizeEndpoint2(endpoint) {
165368
165450
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
@@ -165405,6 +165487,12 @@ class OpenAICompatibleEmbeddingProvider {
165405
165487
  this.initialized = false;
165406
165488
  return false;
165407
165489
  }
165490
+ const blockedReason = blockedEmbeddingEndpointReason(this.endpoint);
165491
+ if (blockedReason) {
165492
+ log(`[magic-context] embedding endpoint blocked: ${blockedReason}`);
165493
+ this.initialized = false;
165494
+ return false;
165495
+ }
165408
165496
  this.initialized = true;
165409
165497
  return true;
165410
165498
  }
@@ -165450,6 +165538,7 @@ class OpenAICompatibleEmbeddingProvider {
165450
165538
  ...this.inputType ? { input_type: this.inputType } : {},
165451
165539
  ...this.truncate ? { truncate: this.truncate } : {}
165452
165540
  }),
165541
+ redirect: "error",
165453
165542
  signal: internalController.signal
165454
165543
  });
165455
165544
  if (!response.ok) {
@@ -165816,11 +165905,15 @@ function resolveEmbeddingConfig(config2) {
165816
165905
  }
165817
165906
  if (config2.provider === "openai-compatible") {
165818
165907
  const apiKey = config2.api_key?.trim();
165908
+ const inputType = config2.input_type?.trim();
165909
+ const truncate = config2.truncate?.trim();
165819
165910
  return {
165820
165911
  provider: "openai-compatible",
165821
165912
  model: config2.model.trim(),
165822
165913
  endpoint: config2.endpoint.trim(),
165823
- ...apiKey ? { api_key: apiKey } : {}
165914
+ ...apiKey ? { api_key: apiKey } : {},
165915
+ ...inputType ? { input_type: inputType } : {},
165916
+ ...truncate ? { truncate } : {}
165824
165917
  };
165825
165918
  }
165826
165919
  return { provider: "off" };
@@ -165836,7 +165929,9 @@ function createProvider(config2) {
165836
165929
  return new OpenAICompatibleEmbeddingProvider({
165837
165930
  endpoint: config2.endpoint,
165838
165931
  model: config2.model,
165839
- apiKey: config2.api_key
165932
+ apiKey: config2.api_key,
165933
+ inputType: config2.input_type,
165934
+ truncate: config2.truncate
165840
165935
  });
165841
165936
  }
165842
165937
  return new LocalEmbeddingProvider(config2.model);
@@ -166648,10 +166743,20 @@ var EMBEDDING_AFFECTING_KEYS = new Set([
166648
166743
  "embedding.api_key",
166649
166744
  "embedding.endpoint",
166650
166745
  "embedding.model",
166651
- "embedding.provider"
166746
+ "embedding.provider",
166747
+ "embedding.input_type",
166748
+ "embedding.truncate"
166652
166749
  ]);
166653
166750
  var EMBEDDING_AFFECTING_TOP_LEVEL_KEYS = new Set(["embedding", "memory", "experimental"]);
166654
- var EMBEDDING_WARNING_TERMS = ["api_key", "endpoint", "model", "provider", "embedding"];
166751
+ var EMBEDDING_WARNING_TERMS = [
166752
+ "api_key",
166753
+ "endpoint",
166754
+ "model",
166755
+ "provider",
166756
+ "embedding",
166757
+ "input_type",
166758
+ "truncate"
166759
+ ];
166655
166760
  var loggedFailureSignatures = new Map;
166656
166761
  function sha256Prefix2(value, length = 16) {
166657
166762
  return createHash8("sha256").update(value).digest("hex").slice(0, length);
@@ -166717,6 +166822,31 @@ function handleUntrustedLoad(db, projectIdentity, directory, detailed) {
166717
166822
  return true;
166718
166823
  }
166719
166824
 
166825
+ // ../plugin/src/config/prune-config-leaf.ts
166826
+ function isPlainObject2(value) {
166827
+ return typeof value === "object" && value !== null && !Array.isArray(value);
166828
+ }
166829
+ function pruneNestedConfigLeaf(block, relativePath) {
166830
+ if (relativePath.length === 0)
166831
+ return null;
166832
+ const result = { ...block };
166833
+ let cursor = result;
166834
+ for (let i = 0;i < relativePath.length - 1; i++) {
166835
+ const seg = String(relativePath[i]);
166836
+ const child = cursor[seg];
166837
+ if (!isPlainObject2(child))
166838
+ return null;
166839
+ const clonedChild = { ...child };
166840
+ cursor[seg] = clonedChild;
166841
+ cursor = clonedChild;
166842
+ }
166843
+ const leaf = String(relativePath[relativePath.length - 1]);
166844
+ if (!(leaf in cursor))
166845
+ return null;
166846
+ delete cursor[leaf];
166847
+ return { block: result, removed: relativePath.map(String).join(".") };
166848
+ }
166849
+
166720
166850
  // src/config/index.ts
166721
166851
  import { existsSync as existsSync10, readFileSync as readFileSync8 } from "node:fs";
166722
166852
  import { homedir as homedir4 } from "node:os";
@@ -166808,6 +166938,70 @@ function migrateLegacyExperimental(rawConfig, warnings) {
166808
166938
  return patched;
166809
166939
  }
166810
166940
 
166941
+ // ../plugin/src/config/project-security.ts
166942
+ var HIDDEN_AGENT_KEYS = ["historian", "dreamer", "sidekick"];
166943
+ var AGENT_ESCALATION_FIELDS = ["prompt", "permission", "tools", "system_prompt"];
166944
+ function isPlainObject3(value) {
166945
+ return typeof value === "object" && value !== null && !Array.isArray(value);
166946
+ }
166947
+ function stripUnsafeProjectConfigFields(projectRaw) {
166948
+ const warnings = [];
166949
+ if ("auto_update" in projectRaw) {
166950
+ delete projectRaw.auto_update;
166951
+ warnings.push("Ignoring auto_update from project config (security: this setting only honors user-level config).");
166952
+ }
166953
+ for (const agentKey of HIDDEN_AGENT_KEYS) {
166954
+ const block = projectRaw[agentKey];
166955
+ if (!isPlainObject3(block))
166956
+ continue;
166957
+ const removed = [];
166958
+ for (const field of AGENT_ESCALATION_FIELDS) {
166959
+ if (field in block) {
166960
+ delete block[field];
166961
+ removed.push(field);
166962
+ }
166963
+ }
166964
+ if (removed.length > 0) {
166965
+ warnings.push(`Ignoring ${agentKey}.${removed.join("/")} from project config ` + "(security: a repository cannot reprogram or re-permission hidden agents).");
166966
+ }
166967
+ }
166968
+ return warnings;
166969
+ }
166970
+ function normalizeEndpoint3(value) {
166971
+ if (typeof value !== "string")
166972
+ return;
166973
+ const trimmed = value.trim().replace(/\/+$/, "");
166974
+ return trimmed.length > 0 ? trimmed.toLowerCase() : undefined;
166975
+ }
166976
+ function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
166977
+ const projectEmbedding = projectRaw.embedding;
166978
+ if (!isPlainObject3(projectEmbedding))
166979
+ return [];
166980
+ const redirectsEndpoint = "endpoint" in projectEmbedding;
166981
+ if (!redirectsEndpoint)
166982
+ return [];
166983
+ const userEmbedding = userRaw?.embedding;
166984
+ if (isPlainObject3(userEmbedding)) {
166985
+ const projectEndpoint = normalizeEndpoint3(projectEmbedding.endpoint);
166986
+ const userEndpoint = normalizeEndpoint3(userEmbedding.endpoint);
166987
+ if (projectEndpoint !== undefined && projectEndpoint === userEndpoint) {
166988
+ return [];
166989
+ }
166990
+ }
166991
+ const providesOwnKey = typeof projectEmbedding.api_key === "string" && projectEmbedding.api_key.length > 0;
166992
+ if (providesOwnKey)
166993
+ return [];
166994
+ const mergedEmbedding = mergedRaw.embedding;
166995
+ if (!isPlainObject3(mergedEmbedding))
166996
+ return [];
166997
+ if (!("api_key" in mergedEmbedding))
166998
+ return [];
166999
+ delete mergedEmbedding.api_key;
167000
+ return [
167001
+ "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)."
167002
+ ];
167003
+ }
167004
+
166811
167005
  // ../plugin/src/config/variable.ts
166812
167006
  import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
166813
167007
  import { homedir as homedir3 } from "node:os";
@@ -166872,6 +167066,21 @@ function stripJsonComments(content) {
166872
167066
  // ../plugin/src/config/variable.ts
166873
167067
  var ENV_PATTERN = /\{env:([^}]+)\}/g;
166874
167068
  var FILE_PATTERN = /\{file:([^}]+)\}/g;
167069
+ function sensitiveFilePathReason(resolvedPath) {
167070
+ const home = homedir3();
167071
+ const sensitiveDirs = [
167072
+ { dir: resolve4(home, ".ssh"), label: "SSH keys" },
167073
+ { dir: resolve4(home, ".aws"), label: "AWS credentials" },
167074
+ { dir: resolve4(home, ".gnupg"), label: "GnuPG keyring" },
167075
+ { dir: resolve4(home, ".config", "gh"), label: "GitHub CLI auth" }
167076
+ ];
167077
+ for (const { dir, label } of sensitiveDirs) {
167078
+ if (resolvedPath === dir || resolvedPath.startsWith(`${dir}/`)) {
167079
+ return label;
167080
+ }
167081
+ }
167082
+ return null;
167083
+ }
166875
167084
  function substituteConfigVariables(input) {
166876
167085
  const warnings = [];
166877
167086
  let text = input.text;
@@ -166925,6 +167134,10 @@ function substituteConfigVariables(input) {
166925
167134
  } else if (!isAbsolute3(filePath)) {
166926
167135
  filePath = resolve4(configDir, filePath);
166927
167136
  }
167137
+ const sensitiveReason = sensitiveFilePathReason(filePath);
167138
+ if (sensitiveReason) {
167139
+ warnings.push(`${token} resolves to a sensitive path (${sensitiveReason}: ${filePath}); ` + "inlining its contents into config — make sure this is intentional.");
167140
+ }
166928
167141
  if (!existsSync9(filePath)) {
166929
167142
  warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
166930
167143
  continue;
@@ -166963,7 +167176,8 @@ function loadConfigFile(path7, scope) {
166963
167176
  const rawText = readFileSync8(path7, "utf-8");
166964
167177
  const substituted = substituteConfigVariables({
166965
167178
  text: rawText,
166966
- configPath: path7
167179
+ configPath: path7,
167180
+ isProjectConfig: scope === "project"
166967
167181
  });
166968
167182
  return {
166969
167183
  path: path7,
@@ -167005,14 +167219,14 @@ function redactConfigValue(value) {
167005
167219
  }
167006
167220
  return typeof value;
167007
167221
  }
167008
- function isPlainObject2(value) {
167222
+ function isPlainObject4(value) {
167009
167223
  return typeof value === "object" && value !== null && !Array.isArray(value);
167010
167224
  }
167011
167225
  function mergeRawConfigs(base, override) {
167012
167226
  const merged = { ...base };
167013
167227
  for (const [key, overrideValue] of Object.entries(override)) {
167014
167228
  const baseValue = merged[key];
167015
- merged[key] = isPlainObject2(baseValue) && isPlainObject2(overrideValue) ? mergeRawConfigs(baseValue, overrideValue) : overrideValue;
167229
+ merged[key] = isPlainObject4(baseValue) && isPlainObject4(overrideValue) ? mergeRawConfigs(baseValue, overrideValue) : overrideValue;
167016
167230
  }
167017
167231
  return merged;
167018
167232
  }
@@ -167026,10 +167240,15 @@ function parsePiConfig(rawConfig, recoveredTopLevelKeys = []) {
167026
167240
  }
167027
167241
  const defaults = MagicContextConfigSchema.parse({});
167028
167242
  const errorPaths = new Set;
167243
+ const issuePathsByKey = new Map;
167029
167244
  for (const issue2 of parsed.error.issues) {
167030
167245
  const topKey = issue2.path[0];
167031
167246
  if (topKey !== undefined) {
167032
- errorPaths.add(String(topKey));
167247
+ const key = String(topKey);
167248
+ errorPaths.add(key);
167249
+ const paths = issuePathsByKey.get(key) ?? [];
167250
+ paths.push([...issue2.path]);
167251
+ issuePathsByKey.set(key, paths);
167033
167252
  }
167034
167253
  }
167035
167254
  const patched = { ...migrated };
@@ -167037,11 +167256,32 @@ function parsePiConfig(rawConfig, recoveredTopLevelKeys = []) {
167037
167256
  for (const key of errorPaths) {
167038
167257
  recoveredTopLevelKeys.push(key);
167039
167258
  const isAgentConfig = key === "historian" || key === "dreamer" || key === "sidekick";
167040
- delete patched[key];
167041
167259
  if (isAgentConfig) {
167260
+ delete patched[key];
167042
167261
  warnings.push(`"${key}": invalid agent configuration, ignoring. Check your magic-context.jsonc.`);
167043
167262
  continue;
167044
167263
  }
167264
+ const issuePaths = issuePathsByKey.get(key) ?? [];
167265
+ const rawValue = migrated[key];
167266
+ const allNested = issuePaths.length > 0 && issuePaths.every((p) => p.length >= 2) && typeof rawValue === "object" && rawValue !== null && !Array.isArray(rawValue);
167267
+ if (allNested) {
167268
+ let prunedBlock = {
167269
+ ...rawValue
167270
+ };
167271
+ const prunedLeaves = [];
167272
+ for (const p of issuePaths) {
167273
+ const relative3 = p.slice(1);
167274
+ const result = pruneNestedConfigLeaf(prunedBlock, relative3);
167275
+ if (result) {
167276
+ prunedBlock = result.block;
167277
+ prunedLeaves.push(result.removed);
167278
+ }
167279
+ }
167280
+ patched[key] = prunedBlock;
167281
+ warnings.push(`"${key}": invalid nested field(s) ${prunedLeaves.map((l) => `"${l}"`).join(", ")}, using defaults for those.`);
167282
+ continue;
167283
+ }
167284
+ delete patched[key];
167045
167285
  const defaultValue = defaults[key];
167046
167286
  warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultValue)}.`);
167047
167287
  }
@@ -167074,10 +167314,22 @@ function loadPiConfig(opts = {}) {
167074
167314
  return 0;
167075
167315
  return a.scope === "user" ? -1 : 1;
167076
167316
  });
167317
+ const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
167077
167318
  for (const loaded of mergeFiles) {
167078
167319
  const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
167079
167320
  warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
167080
- rawConfig = mergeRawConfigs(rawConfig, loaded.config);
167321
+ if (loaded.scope === "project") {
167322
+ const projectRaw = { ...loaded.config };
167323
+ for (const warning of stripUnsafeProjectConfigFields(projectRaw)) {
167324
+ warnings.push(`${prefix} ${warning}`);
167325
+ }
167326
+ rawConfig = mergeRawConfigs(rawConfig, projectRaw);
167327
+ for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
167328
+ warnings.push(`${prefix} ${warning}`);
167329
+ }
167330
+ } else {
167331
+ rawConfig = mergeRawConfigs(rawConfig, loaded.config);
167332
+ }
167081
167333
  }
167082
167334
  const parsed = parsePiConfig(rawConfig);
167083
167335
  warnings.push(...parsed.warnings.map((warning) => `[merged config] ${warning}`));
@@ -167152,10 +167404,22 @@ function loadPiConfigDetailed(opts = {}) {
167152
167404
  return 0;
167153
167405
  return a.scope === "user" ? -1 : 1;
167154
167406
  });
167407
+ const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
167155
167408
  for (const loaded of mergeFiles) {
167156
167409
  const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
167157
167410
  warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
167158
- rawConfig = mergeRawConfigs(rawConfig, loaded.config);
167411
+ if (loaded.scope === "project") {
167412
+ const projectRaw = { ...loaded.config };
167413
+ for (const warning of stripUnsafeProjectConfigFields(projectRaw)) {
167414
+ warnings.push(`${prefix} ${warning}`);
167415
+ }
167416
+ rawConfig = mergeRawConfigs(rawConfig, projectRaw);
167417
+ for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
167418
+ warnings.push(`${prefix} ${warning}`);
167419
+ }
167420
+ } else {
167421
+ rawConfig = mergeRawConfigs(rawConfig, loaded.config);
167422
+ }
167159
167423
  }
167160
167424
  const recoveredTopLevelKeys = [];
167161
167425
  const parsed = parsePiConfig(rawConfig, recoveredTopLevelKeys);
@@ -167210,7 +167474,11 @@ function registerPiDreamerProject(opts) {
167210
167474
  }
167211
167475
  const existing = registeredProjects2.get(opts.projectIdentity);
167212
167476
  if (existing) {
167213
- return;
167477
+ if (existing.projectDir === opts.projectDir) {
167478
+ return;
167479
+ }
167480
+ existing.cleanup();
167481
+ registeredProjects2.delete(opts.projectIdentity);
167214
167482
  }
167215
167483
  registerDreamProjectDirectory(opts.projectIdentity, opts.projectDir);
167216
167484
  const client = createPiDreamerClient(opts);
@@ -167256,7 +167524,8 @@ function registerPiDreamerProject(opts) {
167256
167524
  cancelled = true;
167257
167525
  cleanup?.();
167258
167526
  },
167259
- runOnce
167527
+ runOnce,
167528
+ projectDir: opts.projectDir
167260
167529
  });
167261
167530
  }
167262
167531
  async function runPiDreamForProject(projectIdentity) {
@@ -167283,7 +167552,7 @@ async function awaitInFlightDreamers() {
167283
167552
  function createPiDreamerClient(opts) {
167284
167553
  const runner2 = piSubagentRunnerFactory();
167285
167554
  const model = opts.config.model;
167286
- const fallbackModels = normalizeFallbackModels(opts.config.fallback_models);
167555
+ const fallbackModels = resolveFallbackChain(DREAMER_AGENT, opts.config.fallback_models);
167287
167556
  const session = {
167288
167557
  create: async (args) => {
167289
167558
  const sessionId = `magic-context-pi-dream-${++sessionCounter}`;
@@ -167343,15 +167612,6 @@ function createPiDreamerClient(opts) {
167343
167612
  };
167344
167613
  return { session };
167345
167614
  }
167346
- function normalizeFallbackModels(value) {
167347
- if (Array.isArray(value)) {
167348
- return value;
167349
- }
167350
- if (typeof value === "string" && value.trim().length > 0) {
167351
- return [value];
167352
- }
167353
- return;
167354
- }
167355
167615
  function readDirectory(args) {
167356
167616
  const query = args.query;
167357
167617
  if (typeof query !== "object" || query === null) {
@@ -168682,6 +168942,9 @@ function escapeXmlAttr2(s) {
168682
168942
  function escapeXmlContent2(s) {
168683
168943
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
168684
168944
  }
168945
+ function isTieredRow(c) {
168946
+ return typeof c.p1 === "string" && c.p1.length > 0;
168947
+ }
168685
168948
  function tierBody(c, tier2) {
168686
168949
  const tiers = [c.p1, c.p2, c.p3, c.p4];
168687
168950
  const requested = tiers[tier2 - 1];
@@ -168711,12 +168974,13 @@ function renderOneCompartment(c, tier2) {
168711
168974
  const baseAttrs = `start="${c.startMessage}" end="${c.endMessage}" title="${escapeXmlAttr2(c.title)}"`;
168712
168975
  if (tier2 >= 5)
168713
168976
  return "";
168714
- if (c.legacy === 1) {
168715
- if (tier2 >= 4)
168977
+ if (c.legacy === 1 || !isTieredRow(c)) {
168978
+ const flat = (c.content ?? "").trim();
168979
+ if (tier2 >= 4 || flat.length === 0)
168716
168980
  return `<compartment ${baseAttrs} />`;
168717
168981
  return [
168718
168982
  `<compartment ${baseAttrs}>`,
168719
- escapeXmlContent2(legacyBodyForTier(c.content, tier2)),
168983
+ escapeXmlContent2(legacyBodyForTier(flat, tier2)),
168720
168984
  "</compartment>"
168721
168985
  ].join(`
168722
168986
  `);
@@ -171139,6 +171403,9 @@ function safeGetActiveUserMemoriesPi(db) {
171139
171403
  throw error51;
171140
171404
  }
171141
171405
  }
171406
+ function memoryProjectPath(state) {
171407
+ return state.memoryEnabled === false ? undefined : state.projectIdentity;
171408
+ }
171142
171409
  var EMPTY_PI_HARD_SIGNALS = {
171143
171410
  systemHash: "",
171144
171411
  toolSetHash: "",
@@ -171206,17 +171473,15 @@ function readCurrentMarkers(db, state, projectDocsHash) {
171206
171473
  return readCurrentMarkersFromCompartments(db, state, getCompartments(db, state.sessionId), projectDocsHash);
171207
171474
  }
171208
171475
  function readCurrentMarkersFromCompartments(db, state, compartments, projectDocsHash) {
171209
- const memories = getMemoriesByProject(db, state.projectIdentity, [
171210
- "active",
171211
- "permanent"
171212
- ]);
171213
- const projectState = getProjectState(db, state.projectIdentity);
171476
+ const memPath = memoryProjectPath(state);
171477
+ const memories = memPath ? getMemoriesByProject(db, memPath, ["active", "permanent"]) : [];
171478
+ const projectState = memPath ? getProjectState(db, memPath) : undefined;
171214
171479
  const globalState = getProjectState(db, GLOBAL_USER_PROFILE_PROJECT_PATH);
171215
171480
  return {
171216
171481
  maxCompartmentSeq: compartments.length > 0 ? compartments.reduce((max, compartment) => compartment.sequence > max ? compartment.sequence : max, EMPTY_MAX_COMPARTMENT_SEQ) : EMPTY_MAX_COMPARTMENT_SEQ,
171217
171482
  maxMemoryId: memories.length > 0 ? memories.reduce((max, memory) => memory.id > max ? memory.id : max, 0) : 0,
171218
171483
  maxMutationId: getMaxM0MutationId(db, state.sessionId) ?? 0,
171219
- maxMemoryMutationId: getMaxMemoryMutationId(db, state.projectIdentity) ?? 0,
171484
+ maxMemoryMutationId: memPath ? getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
171220
171485
  projectMemoryEpoch: projectState?.projectMemoryEpoch ?? 0,
171221
171486
  projectUserProfileVersion: globalState?.projectUserProfileVersion ?? 0,
171222
171487
  projectDocsHash: projectDocsHash ?? readProjectDocsCanonical(state.projectDirectory).canonicalHash,
@@ -171280,7 +171545,8 @@ ${memories.map((memory) => `- ${escapeXmlContent(memory.content)}`).join(`
171280
171545
  </${wrapper}>`;
171281
171546
  }
171282
171547
  function renderM0Pi(state, db, projectDocs = readProjectDocsCanonical(state.projectDirectory).renderedBlock, decayPressureMultiplier = 1, memoriesOverride, compartmentsOverride, userProfileOverride) {
171283
- const allMemories = memoriesOverride ?? getMemoriesByProject(db, state.projectIdentity, ["active", "permanent"]);
171548
+ const memPath = memoryProjectPath(state);
171549
+ const allMemories = memoriesOverride ?? (memPath ? getMemoriesByProject(db, memPath, ["active", "permanent"]) : []);
171284
171550
  const memories = allMemories.length > 0 ? trimMemoriesToBudgetV2(state.sessionId, allMemories, state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS).renderOrder : allMemories;
171285
171551
  const memoryBlock = memories.length > 0 ? renderMemoryBlockV2(memories) : undefined;
171286
171552
  const baseHistoryBudget = state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
@@ -171330,17 +171596,18 @@ class PiMaterializeContentionError extends Error {
171330
171596
  }
171331
171597
  }
171332
171598
  function readFrozenM0InputsPi(state, db, docs = readProjectDocsCanonical(state.projectDirectory), memoryCutoff) {
171599
+ const memPath = memoryProjectPath(state);
171333
171600
  const read = db.transaction(() => {
171334
171601
  const compartments = getCompartments(db, state.sessionId);
171335
- const memories = getMemoriesByProject(db, state.projectIdentity, ["active", "permanent"], memoryCutoff);
171602
+ const memories = memPath ? getMemoriesByProject(db, memPath, ["active", "permanent"], memoryCutoff) : [];
171336
171603
  const userProfile = safeGetActiveUserMemoriesPi(db);
171337
- const projectState = getProjectState(db, state.projectIdentity);
171604
+ const projectState = memPath ? getProjectState(db, memPath) : undefined;
171338
171605
  const globalState = getProjectState(db, GLOBAL_USER_PROFILE_PROJECT_PATH);
171339
171606
  const markers = {
171340
171607
  maxCompartmentSeq: compartments.reduce((max, compartment) => compartment.sequence > max ? compartment.sequence : max, EMPTY_MAX_COMPARTMENT_SEQ),
171341
171608
  maxMemoryId: memories.reduce((max, memory) => memory.id > max ? memory.id : max, 0),
171342
171609
  maxMutationId: getMaxM0MutationId(db, state.sessionId) ?? 0,
171343
- maxMemoryMutationId: getMaxMemoryMutationId(db, state.projectIdentity) ?? 0,
171610
+ maxMemoryMutationId: memPath ? getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
171344
171611
  projectMemoryEpoch: projectState?.projectMemoryEpoch ?? 0,
171345
171612
  projectUserProfileVersion: globalState?.projectUserProfileVersion ?? 0,
171346
171613
  projectDocsHash: docs.canonicalHash,
@@ -171511,20 +171778,21 @@ ${lines.join(`
171511
171778
  count: mutations.length
171512
171779
  };
171513
171780
  }
171514
- function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, preRenderedKeyFilesBlock) {
171781
+ function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, preRenderedKeyFilesBlock, compartmentsOverride) {
171515
171782
  const sections = [];
171516
171783
  const keyFiles = renderedKeyFilesBlockPi(state, db, preRenderedKeyFilesBlock);
171517
171784
  if (keyFiles)
171518
171785
  sections.push(keyFiles);
171519
- const memoryUpdates = renderMemoryUpdatesBlockPi({
171786
+ const memPath = memoryProjectPath(state);
171787
+ const memoryUpdates = memPath ? renderMemoryUpdatesBlockPi({
171520
171788
  db,
171521
- projectPath: state.projectIdentity,
171789
+ projectPath: memPath,
171522
171790
  afterId: markers.maxMemoryMutationId,
171523
171791
  renderedMemoryIds
171524
- });
171792
+ }) : { block: undefined, count: 0 };
171525
171793
  if (memoryUpdates.block)
171526
171794
  sections.push(memoryUpdates.block);
171527
- const newCompartments = getCompartments(db, state.sessionId).filter((compartment) => compartment.sequence > markers.maxCompartmentSeq);
171795
+ const newCompartments = (compartmentsOverride ?? getCompartments(db, state.sessionId)).filter((compartment) => compartment.sequence > markers.maxCompartmentSeq);
171528
171796
  if (newCompartments.length > 0) {
171529
171797
  const body = newCompartments.map((compartment) => renderCompartmentAtTier(compartment, 1)).join(`
171530
171798
 
@@ -171533,7 +171801,7 @@ function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, preRender
171533
171801
  ${body}
171534
171802
  </new-compartments>`);
171535
171803
  }
171536
- const newMemories = getMemoriesByProject(db, state.projectIdentity, ["active", "permanent"], markers.materializedAt).filter((memory) => memory.id > markers.maxMemoryId);
171804
+ const newMemories = memPath ? getMemoriesByProject(db, memPath, ["active", "permanent"], markers.materializedAt).filter((memory) => memory.id > markers.maxMemoryId) : [];
171537
171805
  if (newMemories.length > 0) {
171538
171806
  const memoryBudget = state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
171539
171807
  const trimmedNewMemories = trimMemoriesToBudgetV2(state.sessionId, newMemories, Math.max(1, Math.floor(memoryBudget * 0.25))).renderOrder;
@@ -171703,7 +171971,7 @@ function softRefreshCachedM1Pi(args) {
171703
171971
  if (!markers) {
171704
171972
  throw new PiMaterializeContentionError(`invalid cached m[0] markers for ${args.state.sessionId}`);
171705
171973
  }
171706
- const rendered = renderM1PiWithMetadata(args.state, args.db, markers, parseMemoryBlockIds(row.memory_block_ids), preRenderedKeyFilesBlock);
171974
+ const rendered = renderM1PiWithMetadata(args.state, args.db, markers, parseMemoryBlockIds(row.memory_block_ids), preRenderedKeyFilesBlock, args.compartmentsForNormalization);
171707
171975
  const m1Bytes = Buffer.from(rendered.text, "utf8");
171708
171976
  const latestCompartment = args.compartmentsForNormalization.at(-1);
171709
171977
  const advancedBoundary = latestCompartment?.endMessageId && latestCompartment.endMessageId.length > 0 ? latestCompartment.endMessageId : markers.lastBaselineEndMessageId;
@@ -171831,11 +172099,15 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
171831
172099
  m1 = replayed.m1;
171832
172100
  markers = replayed.markers;
171833
172101
  }
171834
- const M0_DRIFT_RATIO_FLOOR = 2000;
172102
+ const M0_DRIFT_RATIO_FLOOR_TOKENS = 500;
172103
+ const M1_DRIFT_RATIO = 0.15;
171835
172104
  const M1_ABSOLUTE_CAP_RATIO = 0.2;
171836
172105
  const m1AbsoluteBudget = (state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS) * M1_ABSOLUTE_CAP_RATIO;
171837
- const m1OverAbsoluteCap = m1 !== PI_M1_PLACEHOLDER && estimateTokens(m1) > m1AbsoluteBudget;
171838
- if (!materialized && !contentionExhausted && m1Recomputed && recomputeM1ThisPass && (memoryUpdateCount > 40 || m1OverAbsoluteCap || m1 !== PI_M1_PLACEHOLDER && m0.length >= M0_DRIFT_RATIO_FLOOR && m1.length > m0.length * 0.15)) {
172106
+ const m1HasContent = m1 !== PI_M1_PLACEHOLDER;
172107
+ const m1Tokens = m1HasContent ? estimateTokens(m1) : 0;
172108
+ const m0Tokens = estimateTokens(m0);
172109
+ const m1OverAbsoluteCap = m1HasContent && m1Tokens > m1AbsoluteBudget;
172110
+ if (!materialized && !contentionExhausted && m1Recomputed && recomputeM1ThisPass && (memoryUpdateCount > 40 || m1OverAbsoluteCap || m1HasContent && m0Tokens >= M0_DRIFT_RATIO_FLOOR_TOKENS && m1Tokens > m0Tokens * M1_DRIFT_RATIO)) {
171839
172111
  decision = { value: true, reason: "drift" };
171840
172112
  try {
171841
172113
  const result = materializeM0PiWithRetry(state, db);
@@ -171856,10 +172128,10 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
171856
172128
  injected: true,
171857
172129
  compartmentCount: getCompartments(db, state.sessionId).length,
171858
172130
  factCount: 0,
171859
- memoryCount: getMemoriesByProject(db, state.projectIdentity, [
172131
+ memoryCount: memoryProjectPath(state) ? getMemoriesByProject(db, memoryProjectPath(state), [
171860
172132
  "active",
171861
172133
  "permanent"
171862
- ]).length,
172134
+ ]).length : 0,
171863
172135
  skippedVisibleMessages,
171864
172136
  m0Materialized: materialized,
171865
172137
  m0Reason: decision.reason,
@@ -173155,6 +173427,27 @@ function validateHistorianOutput(text, _sessionId, chunk, _priorCompartments, se
173155
173427
  events: parsed.events.length > 0 ? parsed.events : undefined
173156
173428
  };
173157
173429
  }
173430
+ var HISTORIAN_PERSISTENT_FAILURE_THRESHOLD = 3;
173431
+ function buildHistorianFailureNotice(failureCount, lastError) {
173432
+ if (failureCount >= HISTORIAN_PERSISTENT_FAILURE_THRESHOLD) {
173433
+ return [
173434
+ "## Magic Context — history comparting needs attention",
173435
+ "",
173436
+ `Magic Context has been unable to compart this session's history ${failureCount} times in a row. This usually means the configured historian model is misconfigured or unreachable (Magic Context already retried every fallback model automatically).`,
173437
+ "",
173438
+ `Last error: ${lastError}`,
173439
+ "",
173440
+ "Check your historian model in magic-context.jsonc, then restart. Your conversation keeps working normally in the meantime — this only affects how older history is summarized."
173441
+ ].join(`
173442
+ `);
173443
+ }
173444
+ return [
173445
+ "## Magic Context",
173446
+ "",
173447
+ "Hit a transient issue comparting history this turn — Magic Context will retry automatically on the next turn. Nothing is lost and your conversation continues normally. You'll only be alerted again if this keeps happening."
173448
+ ].join(`
173449
+ `);
173450
+ }
173158
173451
  function buildHistorianRepairPrompt(originalPrompt, previousOutput, validationError) {
173159
173452
  return [
173160
173453
  originalPrompt,
@@ -174993,7 +175286,7 @@ ${body}
174993
175286
  function renderSessionRefCompartment(c) {
174994
175287
  const importance = c.importance ?? 50;
174995
175288
  const attrs = `start="${c.startMessage}" end="${c.endMessage}" title="${escapeXmlAttr(c.title)}"` + (c.episodeType ? ` episode_type="${escapeXmlAttr(c.episodeType)}"` : "") + ` importance="${importance}"`;
174996
- if (c.p1 != null) {
175289
+ if (typeof c.p1 === "string" && c.p1.length > 0) {
174997
175290
  const p4 = c.p4 && c.p4.length > 0 ? `<p4>
174998
175291
  ${escapeXmlContent(c.p4)}
174999
175292
  </p4>` : "<p4/>";
@@ -175094,8 +175387,10 @@ async function runPiHistorian(deps) {
175094
175387
  const existingValidationError = validateStoredCompartments(priorCompartments);
175095
175388
  if (existingValidationError) {
175096
175389
  sessionLog(sessionId, `historian failure: source=existing-validation reason="${existingValidationError}"`);
175097
- incrementHistorianFailure(db, sessionId, existingValidationError);
175098
- await notify(`Historian skipped: existing stored compartments are invalid: ${existingValidationError}`);
175390
+ {
175391
+ const failCount = incrementHistorianFailure(db, sessionId, existingValidationError);
175392
+ await notify(buildHistorianFailureNotice(failCount, existingValidationError));
175393
+ }
175099
175394
  return;
175100
175395
  }
175101
175396
  const offset = priorCompartments.length > 0 ? priorCompartments[priorCompartments.length - 1].endMessage + 1 : 1;
@@ -175114,8 +175409,10 @@ async function runPiHistorian(deps) {
175114
175409
  const chunkCoverageError = validateChunkCoverage(chunk);
175115
175410
  if (chunkCoverageError) {
175116
175411
  sessionLog(sessionId, `historian failure: source=chunk-coverage reason="${chunkCoverageError}" chunkRange=${chunk.startIndex}-${chunk.endIndex}`);
175117
- incrementHistorianFailure(db, sessionId, chunkCoverageError);
175118
- await notify(`Historian skipped: raw chunk could not be safely chunked: ${chunkCoverageError}`);
175412
+ {
175413
+ const failCount = incrementHistorianFailure(db, sessionId, chunkCoverageError);
175414
+ await notify(buildHistorianFailureNotice(failCount, chunkCoverageError));
175415
+ }
175119
175416
  return;
175120
175417
  }
175121
175418
  const projectPath = resolveProjectIdentity(directory);
@@ -175244,8 +175541,10 @@ ${chunk.text}`,
175244
175541
  if (validatedPass.kind !== "ok") {
175245
175542
  const errorMsg = validatedPass.kind === "validation-failed" ? validatedPass.error : validatedPass.kind === "spawn-failed" ? `subagent run failed (${validatedPass.reason}): ${validatedPass.error}` : "historian returned no usable text";
175246
175543
  sessionLog(sessionId, `historian failure: ${errorMsg}`);
175247
- incrementHistorianFailure(db, sessionId, errorMsg);
175248
- await notify(`Historian failed: ${errorMsg}`);
175544
+ {
175545
+ const failCount = incrementHistorianFailure(db, sessionId, errorMsg);
175546
+ await notify(buildHistorianFailureNotice(failCount, errorMsg));
175547
+ }
175249
175548
  return;
175250
175549
  }
175251
175550
  if (twoPass && validatedPass.kind === "ok") {
@@ -175290,8 +175589,10 @@ ${chunk.text}`,
175290
175589
  if (lastNewEnd + 1 <= offset) {
175291
175590
  const errorMsg = `historian returned compartments that did not advance past raw message ${offset - 1}`;
175292
175591
  sessionLog(sessionId, `historian failure: source=no-progress newCompartmentCount=${newCompartments.length} lastNewEnd=${lastNewEnd} priorEnd=${offset - 1}`);
175293
- incrementHistorianFailure(db, sessionId, errorMsg);
175294
- await notify(`Historian failed: ${errorMsg}`);
175592
+ {
175593
+ const failCount = incrementHistorianFailure(db, sessionId, errorMsg);
175594
+ await notify(buildHistorianFailureNotice(failCount, errorMsg));
175595
+ }
175295
175596
  return;
175296
175597
  }
175297
175598
  const markerSummary = buildPiCompactionSummary(newCompartments);
@@ -175405,8 +175706,10 @@ ${chunk.text}`,
175405
175706
  const desc = describeError(error51);
175406
175707
  telemetry.failureReason = `exception: ${desc.brief}`;
175407
175708
  sessionLog(sessionId, `historian failure: source=exception ${desc.brief}${desc.stackHead ? ` stackHead="${desc.stackHead}"` : ""}`);
175408
- incrementHistorianFailure(db, sessionId, desc.brief);
175409
- await notify(`Historian failed unexpectedly: ${desc.brief}`);
175709
+ {
175710
+ const failCount = incrementHistorianFailure(db, sessionId, desc.brief);
175711
+ await notify(buildHistorianFailureNotice(failCount, desc.brief));
175712
+ }
175410
175713
  } finally {
175411
175714
  updateSessionMeta(db, sessionId, { compartmentInProgress: false });
175412
175715
  try {
@@ -175870,6 +176173,7 @@ function stripPiDroppedPlaceholderMessages(args) {
175870
176173
  };
175871
176174
  const canPrune = (isCacheBusting || args.forceDiscovery === true) && !!stableIdByRef;
175872
176175
  const presentIds = canPrune ? new Set : null;
176176
+ const discoveredIds = [];
175873
176177
  if (isCacheBusting || args.forceDiscovery) {
175874
176178
  for (let i = 0;i < messages.length; i++) {
175875
176179
  const id = idOf(messages[i], i);
@@ -175880,6 +176184,7 @@ function stripPiDroppedPlaceholderMessages(args) {
175880
176184
  continue;
175881
176185
  if (!idsToStrip.has(id)) {
175882
176186
  idsToStrip.add(id);
176187
+ discoveredIds.push(id);
175883
176188
  discovered++;
175884
176189
  }
175885
176190
  }
@@ -175894,17 +176199,20 @@ function stripPiDroppedPlaceholderMessages(args) {
175894
176199
  }
175895
176200
  let pruned = 0;
175896
176201
  if (presentIds) {
175897
- const finalIds = new Set;
175898
- for (const id of idsToStrip) {
175899
- if (presentIds.has(id))
175900
- finalIds.add(id);
175901
- }
175902
- pruned = idsToStrip.size - finalIds.size;
175903
- if (discovered > 0 || pruned > 0) {
175904
- setStrippedPlaceholderIds(db, sessionId, finalIds);
176202
+ const removedIds = [];
176203
+ for (const id of persistedIds) {
176204
+ if (!presentIds.has(id))
176205
+ removedIds.push(id);
176206
+ }
176207
+ pruned = removedIds.length;
176208
+ if (discoveredIds.length > 0 || removedIds.length > 0) {
176209
+ applyStrippedPlaceholderDelta(db, sessionId, {
176210
+ add: discoveredIds,
176211
+ remove: removedIds
176212
+ });
175905
176213
  }
175906
- } else if (discovered > 0) {
175907
- setStrippedPlaceholderIds(db, sessionId, idsToStrip);
176214
+ } else if (discoveredIds.length > 0) {
176215
+ applyStrippedPlaceholderDelta(db, sessionId, { add: discoveredIds });
175908
176216
  }
175909
176217
  if (removed > 0 || discovered > 0 || pruned > 0) {
175910
176218
  sessionLog(sessionId, `placeholder strip: removed=${removed} discovered=${discovered} pruned=${pruned}`);
@@ -175929,6 +176237,7 @@ var PARTNER_FRAME_CLOSER_REDUCE = `
175929
176237
  Reduction prompts are routine housekeeping to keep the session fast and cheap — act on them as light maintenance, never as scarcity warnings. Keep individual operations efficient, but never let context size change *what* work you take on or *how thoroughly* you do it.`;
175930
176238
  var PARTNER_FRAME_CLOSER_NO_REDUCE = `
175931
176239
  Context is managed for you entirely automatically — there's nothing to prune and no warnings to act on. Stay reasonably concise per operation, and never let context size change *what* work you take on or *how thoroughly* you do it.`;
176240
+ var CTX_NOTE_GUIDANCE = `Use \`ctx_note\` ONLY for genuinely future concerns — something to revisit much later, not work coming up in the next few turns (that's already in your active context) and not active multi-step work (use todos for that). Magic Context preserves your full context across both compaction and restarts, so an upcoming restart or "let's come back to this later" is never a reason to take a note — nothing is lost either way. Notes you do take survive compression and resurface at natural work boundaries (after commits, historian runs, todo completion).`;
175932
176241
  function getToolHistoryGuidance(dropToolStructure) {
175933
176242
  if (dropToolStructure) {
175934
176243
  return `Compressed history intentionally omits tool calls and their outputs — summaries like "I edited file X" are historian records, not patterns to replicate. In the live conversation, older tool calls and their results are cleaned up to save context — you may see your own past messages referencing actions without the corresponding tool call or result visible. This is normal context management. ALWAYS use real tool calls; never simulate, fabricate, or inline tool outputs in your text. If there is no tool result message, the action did not happen. NEVER simulate, hallucinate or claim tool calls, command output, search results, file edits, or diffs in plain text as if they actually occurred.`;
@@ -175939,7 +176248,8 @@ var BASE_INTRO = (protectedTags, dropToolStructure) => `Messages and tool output
175939
176248
  Use \`ctx_reduce\` to manage context size. It supports one operation:
175940
176249
  - \`drop\`: Remove entirely (best for tool outputs you already acted on).
175941
176250
  Syntax: "3-5", "1,2,9", or "1-5,8,12-15". Last ${protectedTags} tags are protected.
175942
- Use \`ctx_note\` for deferred intentions — things to tackle later, not right now. NOT for task tracking (use todos). Notes survive context compression and you'll be reminded at natural work boundaries (after commits, historian runs, todo completion).
176251
+ Do not announce or narrate \`ctx_reduce\` drops — just call the tool silently. Saying "I'll drop these outputs" wastes tokens the user does not care about.
176252
+ ${CTX_NOTE_GUIDANCE}
175943
176253
  Use \`ctx_memory\` to manage cross-session project memories. Write new memories or delete stale ones. Memories persist across sessions and are automatically injected into new sessions.
175944
176254
  **Save to memory proactively**: If you spent multiple turns finding something (a file path, a DB location, a config pattern, a workaround), save it with \`ctx_memory\` so future sessions don't repeat the search. Examples:
175945
176255
  - Found a project's source code path after searching → \`ctx_memory(action="write", category="ENVIRONMENT", content="OpenCode source is at ~/Work/OSS/opencode")\`
@@ -175959,7 +176269,7 @@ NEVER drop large ranges blindly (e.g., "1-50"). Review each tag before deciding.
175959
176269
  NEVER drop user messages — they are short and will be summarized by compartmentalization automatically. Dropping them loses context the historian needs.
175960
176270
  NEVER drop assistant text messages unless they are exceptionally large. Your conversation messages are lightweight; only large tool outputs are worth dropping.
175961
176271
  Before your turn finishes, consider using \`ctx_reduce\` to drop large tool outputs you no longer need.`;
175962
- var BASE_INTRO_NO_REDUCE = (dropToolStructure) => `Use \`ctx_note\` for deferred intentions — things to tackle later, not right now. NOT for task tracking (use todos). Notes survive context compression and you'll be reminded at natural work boundaries (after commits, historian runs, todo completion).
176272
+ var BASE_INTRO_NO_REDUCE = (dropToolStructure) => `${CTX_NOTE_GUIDANCE}
175963
176273
  Use \`ctx_memory\` to manage cross-session project memories. Write new memories or delete stale ones. Memories persist across sessions and are automatically injected into new sessions.
175964
176274
  **Save to memory proactively**: If you spent multiple turns finding something (a file path, a DB location, a config pattern, a workaround), save it with \`ctx_memory\` so future sessions don't repeat the search. Examples:
175965
176275
  - Found a project's source code path after searching → \`ctx_memory(action="write", category="ENVIRONMENT", content="OpenCode source is at ~/Work/OSS/opencode")\`
@@ -176749,7 +177059,9 @@ function consumeDeferredMaterialization(sessionId) {
176749
177059
  deferredMaterializationSessions.delete(sessionId);
176750
177060
  return wasSet;
176751
177061
  }
177062
+ var MAX_TRACKED_SESSIONS = 100;
176752
177063
  function trackSessionForProject(projectIdentity, sessionId) {
177064
+ activeContextHandlerSessions.delete(sessionId);
176753
177065
  activeContextHandlerSessions.add(sessionId);
176754
177066
  let sessions = sessionsByProject.get(projectIdentity);
176755
177067
  if (!sessions) {
@@ -176757,6 +177069,12 @@ function trackSessionForProject(projectIdentity, sessionId) {
176757
177069
  sessionsByProject.set(projectIdentity, sessions);
176758
177070
  }
176759
177071
  sessions.add(sessionId);
177072
+ while (activeContextHandlerSessions.size > MAX_TRACKED_SESSIONS) {
177073
+ const oldest = activeContextHandlerSessions.values().next().value;
177074
+ if (oldest === undefined || oldest === sessionId)
177075
+ break;
177076
+ clearContextHandlerSession(oldest);
177077
+ }
176760
177078
  }
176761
177079
  function isContextHandlerSessionActive(sessionId) {
176762
177080
  return activeContextHandlerSessions.has(sessionId);
@@ -177140,24 +177458,26 @@ function registerPiContextHandler(pi, options) {
177140
177458
  }
177141
177459
  let usagePercentage = 0;
177142
177460
  let usageInputTokens = 0;
177461
+ let usedPersistedUsage = false;
177143
177462
  if (sessionMetaForUsage.lastContextPercentage > 0 && sessionMetaForUsage.lastInputTokens > 0) {
177144
177463
  usagePercentage = sessionMetaForUsage.lastContextPercentage;
177145
177464
  usageInputTokens = sessionMetaForUsage.lastInputTokens;
177465
+ usedPersistedUsage = true;
177146
177466
  } else {
177147
177467
  usagePercentage = typeof piUsage?.percent === "number" ? piUsage.percent : 0;
177148
177468
  usageInputTokens = typeof piUsage?.tokens === "number" ? piUsage.tokens : 0;
177149
177469
  }
177150
- let usageContextLimit = typeof piUsage?.contextWindow === "number" && piUsage.contextWindow > 0 ? piUsage.contextWindow : undefined;
177470
+ let usageContextLimit = isSaneLimit(piUsage?.contextWindow) ? piUsage.contextWindow : undefined;
177151
177471
  const tEmergencyRecovery = performance.now();
177472
+ let needsEmergencyBump = false;
177473
+ let emergencyRecoveryArmed = false;
177152
177474
  try {
177153
177475
  const overflowState = getOverflowState(options.db, sessionId);
177154
177476
  if (overflowState.detectedContextLimit > 0) {
177155
177477
  usageContextLimit = Math.min(usageContextLimit ?? overflowState.detectedContextLimit, overflowState.detectedContextLimit);
177156
177478
  }
177157
- if (overflowState.needsEmergencyRecovery && usagePercentage < 95) {
177158
- sessionLog(sessionId, `transform: overflow recovery flag set — bumping percentage from ${usagePercentage.toFixed(1)}% to 95% (detectedLimit=${overflowState.detectedContextLimit || "unknown"})`);
177159
- usagePercentage = 95;
177160
- }
177479
+ emergencyRecoveryArmed = overflowState.needsEmergencyRecovery;
177480
+ needsEmergencyBump = overflowState.needsEmergencyRecovery && usagePercentage < 95;
177161
177481
  } catch (err) {
177162
177482
  sessionLog(sessionId, `transform: overflow state read failed: ${err instanceof Error ? err.message : String(err)}`);
177163
177483
  }
@@ -177169,6 +177489,13 @@ function registerPiContextHandler(pi, options) {
177169
177489
  usageContextLimit = modelWindow;
177170
177490
  }
177171
177491
  }
177492
+ if (!usedPersistedUsage && isSaneLimit(usageContextLimit) && usageInputTokens > 0) {
177493
+ usagePercentage = usageInputTokens / usageContextLimit * 100;
177494
+ }
177495
+ if (needsEmergencyBump) {
177496
+ sessionLog(sessionId, `transform: overflow recovery flag set — bumping percentage to 95% (detectedLimit=${usageContextLimit ?? "unknown"})`);
177497
+ usagePercentage = 95;
177498
+ }
177172
177499
  let schedulerDecision;
177173
177500
  const tScheduler = performance.now();
177174
177501
  try {
@@ -177218,10 +177545,6 @@ function registerPiContextHandler(pi, options) {
177218
177545
  setDeferredExecutePendingIfAbsent(options.db, sessionId, flagPayload);
177219
177546
  }
177220
177547
  schedulerDecision = midTurnAdjustedSchedulerDecision;
177221
- const deferredExecutePending = peekDeferredExecutePending(options.db, sessionId);
177222
- if (deferredExecutePending !== null && schedulerDecision === "defer" && !midTurn) {
177223
- schedulerDecision = "execute";
177224
- }
177225
177548
  sessionLog(sessionId, `[boundary-exec] base=${schedulerDecisionEarly} bypass=${bypassReason} midTurn=${midTurn} effective=${midTurnAdjustedSchedulerDecision} sideEffect=${sideEffect}`);
177226
177549
  const forceMaterialization = usagePercentage >= FORCE_MATERIALIZATION_PERCENTAGE;
177227
177550
  const isEmergency = usagePercentage >= EMERGENCY_BLOCK_PERCENTAGE;
@@ -177239,6 +177562,14 @@ function registerPiContextHandler(pi, options) {
177239
177562
  sessionLog(sessionId, "EMERGENCY: historian wait completed (or timed out)");
177240
177563
  } catch {}
177241
177564
  }
177565
+ if (emergencyRecoveryArmed && !inFlightHistorian.has(sessionId) && !hasEligiblePiCompartmentHistory(options.db, sessionId)) {
177566
+ try {
177567
+ clearEmergencyRecovery(options.db, sessionId);
177568
+ sessionLog(sessionId, "EMERGENCY: disarming recovery — no eligible pre-tail history to compact (would otherwise loop at 95%)");
177569
+ } catch (err) {
177570
+ sessionLog(sessionId, `EMERGENCY: clearEmergencyRecovery failed: ${err instanceof Error ? err.message : String(err)}`);
177571
+ }
177572
+ }
177242
177573
  }
177243
177574
  const isCacheBusting = historyRefreshSessions.has(sessionId);
177244
177575
  sessionLog(sessionId, `transform: usage=${usagePercentage.toFixed(1)}% (${usageInputTokens} tokens, limit=${usageContextLimit ?? "?"}) decision=${schedulerDecision}${forceMaterialization ? " force=true" : ""}${isEmergency ? " EMERGENCY=true" : ""}${isCacheBusting ? " busting=true" : ""}`);
@@ -177256,6 +177587,7 @@ function registerPiContextHandler(pi, options) {
177256
177587
  heuristics: options.heuristics,
177257
177588
  injection: options.injection ? {
177258
177589
  ...options.injection,
177590
+ memoryEnabled: options.injection.memoryEnabled,
177259
177591
  historyBudgetTokens: resolveHistoryBudgetTokensForPi({
177260
177592
  historyBudgetPercentage: options.historian?.historyBudgetPercentage,
177261
177593
  usagePercentage,
@@ -177573,7 +177905,16 @@ function maybeFireHistorian(args) {
177573
177905
  let usageContextLimit;
177574
177906
  try {
177575
177907
  const piUsage = ctx.getContextUsage?.();
177576
- usageContextLimit = typeof piUsage?.contextWindow === "number" && piUsage.contextWindow > 0 ? piUsage.contextWindow : undefined;
177908
+ usageContextLimit = isSaneLimit(piUsage?.contextWindow) ? piUsage.contextWindow : undefined;
177909
+ if (usageContextLimit === undefined && isSaneLimit(ctx.model?.contextWindow)) {
177910
+ usageContextLimit = ctx.model.contextWindow;
177911
+ }
177912
+ try {
177913
+ const overflowState = getOverflowState(db, sessionId);
177914
+ if (overflowState.detectedContextLimit > 0) {
177915
+ usageContextLimit = Math.min(usageContextLimit ?? overflowState.detectedContextLimit, overflowState.detectedContextLimit);
177916
+ }
177917
+ } catch {}
177577
177918
  const sessionMetaForUsage = getOrCreateSessionMeta(db, sessionId);
177578
177919
  if (sessionMetaForUsage.lastContextPercentage > 0 && sessionMetaForUsage.lastInputTokens > 0) {
177579
177920
  usage = {
@@ -177586,8 +177927,9 @@ function maybeFireHistorian(args) {
177586
177927
  sessionLog(sessionId, `historian trigger eval: no usage info yet (tokens=${piUsage?.tokens ?? "<no piUsage>"}, percent=${piUsage?.percent ?? "<no piUsage>"}, contextWindow=${piUsage?.contextWindow ?? "<no piUsage>"})`);
177587
177928
  return;
177588
177929
  }
177930
+ const fallbackPercentage = isSaneLimit(usageContextLimit) && piUsage.tokens > 0 ? piUsage.tokens / usageContextLimit * 100 : piUsage.percent;
177589
177931
  usage = {
177590
- percentage: piUsage.percent,
177932
+ percentage: fallbackPercentage,
177591
177933
  inputTokens: piUsage.tokens
177592
177934
  };
177593
177935
  sessionLog(sessionId, `historian trigger eval: usage=${usage.percentage.toFixed(1)}% (${usage.inputTokens} tokens) [piUsage fallback], checking trigger...`);
@@ -177663,6 +178005,7 @@ async function runPipeline(args) {
177663
178005
  let executedWorkThisPass = false;
177664
178006
  let historyWasConsumedThisPass = false;
177665
178007
  let materializationSatisfiedThisPass = false;
178008
+ let pendingOpsAppliedThisPass = false;
177666
178009
  let suppressDeferredHistoryDrain = false;
177667
178010
  let casLost = false;
177668
178011
  const deferredHistoryWasPendingAtPassStart = deferredHistoryRefreshSessions.has(args.sessionId);
@@ -177686,7 +178029,9 @@ async function runPipeline(args) {
177686
178029
  return findLatestUserMessageIdPi(args.messages, ids)?.messageId ?? null;
177687
178030
  })();
177688
178031
  const alreadyRanHeuristicsThisTurn = currentTurnId !== null && lastHeuristicsTurnIdBySession.get(args.sessionId) === currentTurnId;
177689
- const shouldRunHeuristics = args.heuristics !== undefined && (args.forceMaterialization === true || hasPendingMaterialization(args.sessionId) || deferredMaterializationSessions.has(args.sessionId) || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
178032
+ const canConsumeDeferredLate = args.schedulerDecision === "execute" || args.forceMaterialization === true || args.contextUsage.percentage >= FORCE_MATERIALIZATION_PERCENTAGE;
178033
+ const deferredMaterializeEligible = canConsumeDeferredLate && deferredMaterializationSessions.has(args.sessionId);
178034
+ const shouldRunHeuristics = args.heuristics !== undefined && (args.forceMaterialization === true || hasPendingMaterialization(args.sessionId) || deferredMaterializeEligible || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
177690
178035
  const entryFingerprintByMessageId = buildEntryFingerprintMap(args.messages, stableIdResolver);
177691
178036
  adoptPiFallbackTags(args.db, args.sessionId, args.tagger, entryFingerprintByMessageId);
177692
178037
  const tTag = performance.now();
@@ -177712,15 +178057,13 @@ async function runPipeline(args) {
177712
178057
  const hasPendingMaterializeSignal = hasPendingMaterialization(args.sessionId);
177713
178058
  const deferredMaterializationWasPending = deferredMaterializationSessions.has(args.sessionId);
177714
178059
  const deferredHistoryRefreshWasPending = deferredHistoryWasPendingAtPassStart;
177715
- const deferredExecuteWasPending = peekDeferredExecutePending(args.db, args.sessionId) !== null;
177716
178060
  const pendingOps = getPendingOps(args.db, args.sessionId);
177717
- const baseShouldApplyPendingOps = args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal || deferredExecuteWasPending;
177718
- const canConsumeDeferredLate = baseShouldApplyPendingOps || shouldRunHeuristics;
178061
+ const baseShouldApplyPendingOps = args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal;
177719
178062
  const deferredMaterialize = canConsumeDeferredLate && deferredMaterializationWasPending;
177720
178063
  const deferredHistoryRefresh = canConsumeDeferredLate && deferredHistoryRefreshWasPending;
177721
178064
  const shouldApplyPendingOps = baseShouldApplyPendingOps || deferredMaterialize;
177722
178065
  if (shouldApplyPendingOps) {
177723
- const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : deferredExecuteWasPending ? "deferred_boundary_execute" : deferredMaterialize ? "deferred_publication" : args.forceMaterialization ? "force_materialization" : `scheduler_execute (scheduler=${args.schedulerDecision})`;
178066
+ const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : deferredMaterialize ? "deferred_publication" : args.forceMaterialization ? "force_materialization" : `scheduler_execute (scheduler=${args.schedulerDecision})`;
177724
178067
  sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOps.length}, context=${args.contextUsage.percentage.toFixed(1)}%`);
177725
178068
  try {
177726
178069
  const tApplyPending = performance.now();
@@ -177728,14 +178071,12 @@ async function runPipeline(args) {
177728
178071
  logTransformTiming(args.sessionId, "applyPendingOperations", tApplyPending);
177729
178072
  executedWorkThisPass = true;
177730
178073
  materializationSatisfiedThisPass = true;
178074
+ pendingOpsAppliedThisPass = true;
177731
178075
  if (hasPendingMaterializeSignal) {
177732
178076
  if (args.heuristics === undefined) {
177733
178077
  consumePendingMaterialization(args.sessionId);
177734
178078
  }
177735
178079
  }
177736
- if (deferredMaterialize) {
177737
- consumeDeferredMaterialization(args.sessionId);
177738
- }
177739
178080
  } catch (err) {
177740
178081
  sessionLog(args.sessionId, `pending operations failed: ${err instanceof Error ? err.message : String(err)}`);
177741
178082
  throw err;
@@ -177822,6 +178163,12 @@ async function runPipeline(args) {
177822
178163
  sessionLog(args.sessionId, `heuristic cleanup failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
177823
178164
  }
177824
178165
  }
178166
+ if (deferredMaterialize && pendingOpsAppliedThisPass) {
178167
+ const fullPassSucceeded = shouldRunHeuristics ? heuristicsExecuted : true;
178168
+ if (fullPassSucceeded) {
178169
+ consumeDeferredMaterialization(args.sessionId);
178170
+ }
178171
+ }
177825
178172
  if (args.reasoningClearing && shouldRunHeuristics) {
177826
178173
  try {
177827
178174
  const tClearReasoning = performance.now();
@@ -177890,6 +178237,7 @@ async function runPipeline(args) {
177890
178237
  sessionId: args.sessionId,
177891
178238
  projectIdentity: args.projectIdentity,
177892
178239
  projectDirectory: args.projectDirectory,
178240
+ memoryEnabled: args.injection.memoryEnabled,
177893
178241
  injectionBudgetTokens: args.injection.injectionBudgetTokens,
177894
178242
  historyBudgetTokens: args.injection.historyBudgetTokens,
177895
178243
  keyFilesEnabled: args.injection.keyFilesEnabled,
@@ -178417,15 +178765,15 @@ function updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEn
178417
178765
  const existing = getPersistedCompactionMarkerState(db, sessionId);
178418
178766
  if (existing) {
178419
178767
  if (existing.boundaryOrdinal === lastCompartmentEnd) {
178420
- return;
178768
+ return true;
178421
178769
  }
178422
- try {
178423
- removeCompactionMarker(existing);
178424
- setPersistedCompactionMarkerState(db, sessionId, null);
178425
- sessionLog(sessionId, `compaction-marker: removed old boundary at ordinal ${existing.boundaryOrdinal}, moving to ${lastCompartmentEnd}`);
178426
- } catch (error51) {
178427
- sessionLog(sessionId, `compaction-marker: failed to remove old boundary at ordinal ${existing.boundaryOrdinal}, proceeding with new injection:`, error51);
178770
+ const removed = removeCompactionMarker(existing);
178771
+ if (!removed) {
178772
+ sessionLog(sessionId, `compaction-marker: failed to remove old boundary at ordinal ${existing.boundaryOrdinal}; preserving persisted state for retry (not injecting new marker this pass)`);
178773
+ return false;
178428
178774
  }
178775
+ setPersistedCompactionMarkerState(db, sessionId, null);
178776
+ sessionLog(sessionId, `compaction-marker: removed old boundary at ordinal ${existing.boundaryOrdinal}, moving to ${lastCompartmentEnd}`);
178429
178777
  }
178430
178778
  const result = injectCompactionMarker({
178431
178779
  sessionId,
@@ -178439,7 +178787,9 @@ function updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEn
178439
178787
  boundaryOrdinal: lastCompartmentEnd
178440
178788
  });
178441
178789
  sessionLog(sessionId, `compaction-marker: injected at ordinal ${lastCompartmentEnd}, boundary user msg ${result.boundaryMessageId}`);
178790
+ return true;
178442
178791
  }
178792
+ return false;
178443
178793
  }
178444
178794
 
178445
178795
  // ../plugin/src/hooks/magic-context/compartment-runner-historian.ts
@@ -178991,7 +179341,6 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
178991
179341
  if (deps.preserveInjectionCacheUntilConsumed !== true) {
178992
179342
  clearInjectionCache(sessionId);
178993
179343
  }
178994
- deps.onCompartmentStatePublished?.(sessionId);
178995
179344
  promoted2.facts;
178996
179345
  if (deps.memoryEnabled !== false) {
178997
179346
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
@@ -179004,11 +179353,14 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
179004
179353
  if (lastCompartmentEnd2 > 0) {
179005
179354
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd2);
179006
179355
  }
179356
+ deps.onCompartmentStatePublished?.(sessionId);
179007
179357
  if (lastCompartmentEnd2 > 0) {
179008
- updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd2, deps.directory);
179009
- const stalePending = getPendingCompactionMarkerState(db, sessionId);
179010
- if (stalePending) {
179011
- clearPendingCompactionMarkerStateIf(db, sessionId, stalePending);
179358
+ const markerUpdated = updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd2, deps.directory);
179359
+ if (markerUpdated) {
179360
+ const stalePending = getPendingCompactionMarkerState(db, sessionId);
179361
+ if (stalePending) {
179362
+ clearPendingCompactionMarkerStateIf(db, sessionId, stalePending);
179363
+ }
179012
179364
  }
179013
179365
  }
179014
179366
  return [
@@ -179197,13 +179549,13 @@ Another process acquired the compartment-state lease before recomp could publish
179197
179549
  if (deps.preserveInjectionCacheUntilConsumed !== true) {
179198
179550
  clearInjectionCache(sessionId);
179199
179551
  }
179200
- deps.onCompartmentStatePublished?.(sessionId);
179201
179552
  const finalCompartments = promoted?.compartments ?? candidateCompartments;
179202
179553
  const finalFacts = promoted?.facts ?? candidateFacts;
179203
179554
  const lastCompartmentEnd = finalCompartments[finalCompartments.length - 1]?.endMessage ?? 0;
179204
179555
  if (lastCompartmentEnd > 0) {
179205
179556
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd);
179206
179557
  }
179558
+ deps.onCompartmentStatePublished?.(sessionId);
179207
179559
  if (deps.memoryEnabled !== false) {
179208
179560
  const projectIdentity = resolveProjectIdentity(sessionDirectory);
179209
179561
  await deps.ensureProjectRegistered?.(sessionDirectory, db);
@@ -179212,10 +179564,12 @@ Another process acquired the compartment-state lease before recomp could publish
179212
179564
  embedAndStoreCompartments(db, sessionId, projectIdentity, toEmbed);
179213
179565
  }
179214
179566
  if (lastCompartmentEnd > 0) {
179215
- updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, deps.directory);
179216
- const stalePending = getPendingCompactionMarkerState(db, sessionId);
179217
- if (stalePending) {
179218
- clearPendingCompactionMarkerStateIf(db, sessionId, stalePending);
179567
+ const markerUpdated = updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, deps.directory);
179568
+ if (markerUpdated) {
179569
+ const stalePending = getPendingCompactionMarkerState(db, sessionId);
179570
+ if (stalePending) {
179571
+ clearPendingCompactionMarkerStateIf(db, sessionId, stalePending);
179572
+ }
179219
179573
  }
179220
179574
  }
179221
179575
  return [
@@ -179349,7 +179703,7 @@ Could not acquire the compartment-state lease for this session.`;
179349
179703
  log(`[magic-context] partial recomp merged validation failed: ${mergedError}`);
179350
179704
  return null;
179351
179705
  }
179352
- saveRecompStagingPass(db, sessionId, passCount + 1, merged, currentFacts);
179706
+ saveRecompStagingPass(db, sessionId, passCount + 1, merged, stagedFacts);
179353
179707
  const promoted = promoteRecompStagingWithM0Mutation(db, sessionId, leaseHolderId);
179354
179708
  if (!promoted) {
179355
179709
  log("[magic-context] partial recomp promote returned null");
@@ -179369,10 +179723,12 @@ Could not acquire the compartment-state lease for this session.`;
179369
179723
  }
179370
179724
  const lastEnd = merged[merged.length - 1]?.endMessage ?? snapEnd;
179371
179725
  if (lastEnd > 0) {
179372
- updateCompactionMarkerAfterPublication(db, sessionId, lastEnd, deps.directory);
179373
- const stalePending = getPendingCompactionMarkerState(db, sessionId);
179374
- if (stalePending) {
179375
- clearPendingCompactionMarkerStateIf(db, sessionId, stalePending);
179726
+ const markerUpdated = updateCompactionMarkerAfterPublication(db, sessionId, lastEnd, deps.directory);
179727
+ if (markerUpdated) {
179728
+ const stalePending = getPendingCompactionMarkerState(db, sessionId);
179729
+ if (stalePending) {
179730
+ clearPendingCompactionMarkerStateIf(db, sessionId, stalePending);
179731
+ }
179376
179732
  }
179377
179733
  }
179378
179734
  return { compartmentCount: merged.length, lastEndMessage: lastEnd };
@@ -179414,10 +179770,7 @@ Snapped range ${snapStart}-${snapEnd} would cross into the protected tail (start
179414
179770
  ].join(`
179415
179771
  `);
179416
179772
  }
179417
- const currentFacts = getSessionFacts(db, sessionId).map((f) => ({
179418
- category: f.category,
179419
- content: f.content
179420
- }));
179773
+ const stagedFacts = [];
179421
179774
  const parentSessionResponse = await client.session.get({ path: { id: sessionId } }).catch(() => null);
179422
179775
  const parentSession = normalizeSDKResponse(parentSessionResponse, null, { preferResponseOnMissingData: true });
179423
179776
  const sessionDirectory = parentSession?.directory ?? directory;
@@ -179434,7 +179787,7 @@ Snapped range ${snapStart}-${snapEnd} would cross into the protected tail (start
179434
179787
  candidateCompartments = priorCompartments.map((c, idx) => compartmentToInput(c, idx));
179435
179788
  passCount = 0;
179436
179789
  offset = snapStart;
179437
- saveRecompStagingPass(db, sessionId, 0, candidateCompartments, currentFacts);
179790
+ saveRecompStagingPass(db, sessionId, 0, candidateCompartments, stagedFacts);
179438
179791
  setRecompPartialRange(db, sessionId, { start: snapStart, end: snapEnd });
179439
179792
  }
179440
179793
  let currentTokenBudget = historianChunkTokens;
@@ -179527,7 +179880,7 @@ Original state preserved (staging kept for retry).`;
179527
179880
  passCount += 1;
179528
179881
  currentTokenBudget = historianChunkTokens;
179529
179882
  passAttempt = 1;
179530
- saveRecompStagingPass(db, sessionId, passCount, candidateCompartments, currentFacts);
179883
+ saveRecompStagingPass(db, sessionId, passCount, candidateCompartments, stagedFacts);
179531
179884
  const nextOffset = (validatedPass.compartments?.[validatedPass.compartments.length - 1]?.endMessage ?? chunk.endIndex) + 1;
179532
179885
  if (nextOffset <= offset) {
179533
179886
  return `## Magic Recomp — Failed
@@ -179548,7 +179901,6 @@ Partial recomp completed historian passes but the final compartment set failed v
179548
179901
  ...resumed ? ["Resumed from previous interrupted partial run."] : [],
179549
179902
  `Rebuilt compartments covering messages ${snapStart}-${snapEnd} using ${passCount} historian pass${passCount === 1 ? "" : "es"}.`,
179550
179903
  `Preserved ${priorCompartments.length} prior compartment(s) and ${tailCompartments.length} tail compartment(s) unchanged.`,
179551
- `Facts unchanged (${currentFacts.length} entr${currentFacts.length === 1 ? "y" : "ies"}).`,
179552
179904
  `Total compartments: ${finalResult.compartmentCount}.`
179553
179905
  ].join(`
179554
179906
  `);
@@ -180646,7 +180998,7 @@ function formatThresholdPercent(value) {
180646
180998
  // package.json
180647
180999
  var package_default = {
180648
181000
  name: "@cortexkit/pi-magic-context",
180649
- version: "0.22.4",
181001
+ version: "0.22.5",
180650
181002
  type: "module",
180651
181003
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
180652
181004
  main: "dist/index.js",
@@ -185528,7 +185880,6 @@ function createCtxMemoryTool(deps) {
185528
185880
  sourceType: dreamerAllowed ? "dreamer" : "agent"
185529
185881
  });
185530
185882
  queueEmbedding({ deps, projectIdentity, memoryId: memory2.id, content });
185531
- invalidateAllMemoryBlockCaches(deps.db);
185532
185883
  return ok2(`Saved memory [ID: ${memory2.id}] in ${rawCategory}.`);
185533
185884
  }
185534
185885
  if (params.action === "delete") {
@@ -185536,7 +185887,7 @@ function createCtxMemoryTool(deps) {
185536
185887
  return err2("Error: 'id' is required when action is 'delete'.");
185537
185888
  }
185538
185889
  const memory2 = getMemoryById(deps.db, params.id);
185539
- if (!memory2 || memory2.projectPath !== projectIdentity) {
185890
+ if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
185540
185891
  return err2(`Error: Memory with ID ${params.id} was not found.`);
185541
185892
  }
185542
185893
  deps.db.transaction(() => {
@@ -185565,7 +185916,7 @@ function createCtxMemoryTool(deps) {
185565
185916
  return err2("Error: 'content' is required when action is 'update'.");
185566
185917
  }
185567
185918
  const memory2 = getMemoryById(deps.db, params.id);
185568
- if (!memory2 || memory2.projectPath !== projectIdentity) {
185919
+ if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
185569
185920
  return err2(`Error: Memory with ID ${params.id} was not found.`);
185570
185921
  }
185571
185922
  const normalizedHash = computeNormalizedHash(content);
@@ -185599,9 +185950,6 @@ function createCtxMemoryTool(deps) {
185599
185950
  if (sourceMemories.length !== ids.length) {
185600
185951
  return err2("Error: One or more source memories were not found.");
185601
185952
  }
185602
- if (sourceMemories.some((memory2) => memory2.projectPath !== projectIdentity)) {
185603
- return err2("Error: All memories to merge must belong to the current project.");
185604
- }
185605
185953
  const requestedCategory = params.category?.trim();
185606
185954
  if (requestedCategory && !isMemoryCategory2(requestedCategory)) {
185607
185955
  return err2(`Error: Unknown memory category '${requestedCategory}'. Valid: ${CATEGORY_PRIORITY.join(", ")}`);
@@ -185660,7 +186008,7 @@ function createCtxMemoryTool(deps) {
185660
186008
  }
185661
186009
  supersededMemory(deps.db, memory2.id, canonicalMemory.id);
185662
186010
  queueMemoryMutation(deps.db, {
185663
- projectPath: memory2.projectPath,
186011
+ projectPath: normalizeStoredProjectPath(memory2.projectPath),
185664
186012
  mutationType: "superseded",
185665
186013
  targetMemoryId: memory2.id,
185666
186014
  supersededById: canonicalMemory.id
@@ -185668,7 +186016,7 @@ function createCtxMemoryTool(deps) {
185668
186016
  }
185669
186017
  if (canonicalExisting && canonicalContentChanged) {
185670
186018
  queueMemoryMutation(deps.db, {
185671
- projectPath: canonicalMemory.projectPath,
186019
+ projectPath: normalizeStoredProjectPath(canonicalMemory.projectPath),
185672
186020
  mutationType: "update",
185673
186021
  targetMemoryId: canonicalMemory.id,
185674
186022
  category,
@@ -185690,7 +186038,7 @@ function createCtxMemoryTool(deps) {
185690
186038
  return err2("Error: 'id' is required when action is 'archive'.");
185691
186039
  }
185692
186040
  const memory2 = getMemoryById(deps.db, params.id);
185693
- if (!memory2 || memory2.projectPath !== projectIdentity) {
186041
+ if (!memory2 || !storedPathBelongsToIdentity(memory2.projectPath, projectIdentity)) {
185694
186042
  return err2(`Error: Memory with ID ${params.id} was not found.`);
185695
186043
  }
185696
186044
  deps.db.transaction(() => {
@@ -185952,7 +186300,7 @@ ${smartNotes.map(formatNoteLine).join(`
185952
186300
  // ../plugin/src/features/magic-context/range-parser.ts
185953
186301
  function parseRangeString(input) {
185954
186302
  const maxRangeElements = 1000;
185955
- const trimmed = input.trim();
186303
+ const trimmed = input.replace(/§/g, "").trim();
185956
186304
  if (trimmed === "") {
185957
186305
  throw new Error("Range string must not be empty");
185958
186306
  }
@@ -186213,6 +186561,7 @@ function createCtxSearchTool(deps) {
186213
186561
  const embeddingEnabled = snapshot ? snapshot.enabled || snapshot.gitCommitEnabled : deps.embeddingEnabled;
186214
186562
  const gitCommitsEnabled = snapshot?.gitCommitEnabled ?? deps.gitCommitsEnabled ?? false;
186215
186563
  const lastCompartmentEnd = getLastCompartmentEndMessage(deps.db, sessionId);
186564
+ const messageOrdinalCutoff = lastCompartmentEnd >= 0 ? lastCompartmentEnd : 0;
186216
186565
  const visibleMemoryIds = getVisibleMemoryIds(deps.db, sessionId);
186217
186566
  const results = await unifiedSearch(deps.db, sessionId, projectIdentity, query, {
186218
186567
  limit: normalizeLimit3(params.limit),
@@ -186223,7 +186572,7 @@ function createCtxSearchTool(deps) {
186223
186572
  return result?.vector ?? null;
186224
186573
  },
186225
186574
  isEmbeddingRuntimeEnabled: () => embeddingEnabled === true,
186226
- maxMessageOrdinal: lastCompartmentEnd >= 0 ? lastCompartmentEnd : undefined,
186575
+ maxMessageOrdinal: messageOrdinalCutoff,
186227
186576
  gitCommitsEnabled,
186228
186577
  sources: params.sources,
186229
186578
  visibleMemoryIds,
@@ -186592,6 +186941,7 @@ async function src_default2(pi) {
186592
186941
  clearReasoningAge: config2.clear_reasoning_age
186593
186942
  },
186594
186943
  injection: {
186944
+ memoryEnabled: config2.memory.enabled,
186595
186945
  injectionBudgetTokens: config2.memory.injection_budget_tokens,
186596
186946
  temporalAwareness: config2.temporal_awareness === true,
186597
186947
  keyFilesEnabled: config2.dreamer?.pin_key_files?.enabled ?? false,
@@ -186703,6 +187053,33 @@ async function src_default2(pi) {
186703
187053
  try {
186704
187054
  const currentProject = resolveCurrentProject(ctx);
186705
187055
  seenDreamerProjectIdentities.add(currentProject.projectIdentity);
187056
+ const switchedProject = currentProject.projectDir !== projectDir;
187057
+ const effectiveConfig = switchedProject ? loadPiConfig({ cwd: currentProject.projectDir }).config : config2;
187058
+ const effectiveDreamerConfig = switchedProject ? resolveDreamerFromConfig(effectiveConfig) : dreamerConfig;
187059
+ if (effectiveDreamerConfig) {
187060
+ try {
187061
+ registerPiDreamerProject({
187062
+ db,
187063
+ projectDir: currentProject.projectDir,
187064
+ projectIdentity: currentProject.projectIdentity,
187065
+ config: effectiveDreamerConfig,
187066
+ embeddingConfig: effectiveConfig.embedding,
187067
+ memoryEnabled: effectiveConfig.memory.enabled,
187068
+ gitCommitIndexing: effectiveConfig.memory.git_commit_indexing,
187069
+ onAdjunctsRefreshNeeded: signalPiSystemPromptRefreshForProject
187070
+ });
187071
+ } catch (err5) {
187072
+ warn("before_agent_start: registerPiDreamerProject threw:", err5);
187073
+ }
187074
+ } else if (switchedProject) {
187075
+ try {
187076
+ unregisterPiDreamerProject({
187077
+ projectIdentity: currentProject.projectIdentity
187078
+ });
187079
+ } catch (err5) {
187080
+ warn("before_agent_start: unregisterPiDreamerProject threw:", err5);
187081
+ }
187082
+ }
186706
187083
  const sm = ctx.sessionManager;
186707
187084
  let sessionId;
186708
187085
  if (sm !== undefined) {