@cortexkit/pi-magic-context 0.30.7 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -62,26 +62,26 @@ var currentHarness = "opencode", harnessLocked = false;
62
62
  // ../plugin/src/shared/data-path.ts
63
63
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
64
64
  import * as os from "node:os";
65
- import * as path2 from "node:path";
65
+ import * as path from "node:path";
66
66
  function getDataDir() {
67
- return process.env.XDG_DATA_HOME ?? path2.join(os.homedir(), ".local", "share");
67
+ return process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share");
68
68
  }
69
69
  function getMagicContextTempDir(harness = getHarness()) {
70
- return path2.join(os.tmpdir(), harness, "magic-context");
70
+ return path.join(os.tmpdir(), harness, "magic-context");
71
71
  }
72
72
  function getMagicContextLogPath(harness = getHarness()) {
73
73
  const envPath = process.env.MAGIC_CONTEXT_LOG_PATH?.trim();
74
74
  if (envPath)
75
75
  return envPath;
76
- return path2.join(getMagicContextTempDir(harness), "magic-context.log");
76
+ return path.join(getMagicContextTempDir(harness), "magic-context.log");
77
77
  }
78
78
  function getProjectMagicContextDir(directory) {
79
- return path2.join(directory, ".cortexkit", "magic-context");
79
+ return path.join(directory, ".cortexkit", "magic-context");
80
80
  }
81
81
  function ensureCortexKitArtifactGitignore(directory) {
82
82
  try {
83
- const cortexKitDir = path2.join(directory, ".cortexkit");
84
- const gitignorePath = path2.join(cortexKitDir, ".gitignore");
83
+ const cortexKitDir = path.join(directory, ".cortexkit");
84
+ const gitignorePath = path.join(cortexKitDir, ".gitignore");
85
85
  let existing = "";
86
86
  if (existsSync2(gitignorePath)) {
87
87
  existing = readFileSync2(gitignorePath, "utf8");
@@ -101,25 +101,25 @@ ${GITIGNORE_GUARD_CLOSE}
101
101
  } catch {}
102
102
  }
103
103
  function getProjectMagicContextHistorianDir(directory) {
104
- return path2.join(getProjectMagicContextDir(directory), "historian");
104
+ return path.join(getProjectMagicContextDir(directory), "historian");
105
105
  }
106
106
  function getOpenCodeStorageDir() {
107
- return path2.join(getDataDir(), "opencode", "storage");
107
+ return path.join(getDataDir(), "opencode", "storage");
108
108
  }
109
109
  function getMagicContextStorageDir() {
110
- return path2.join(getDataDir(), "cortexkit", "magic-context");
110
+ return path.join(getDataDir(), "cortexkit", "magic-context");
111
111
  }
112
112
  function getLegacyOpenCodeMagicContextStorageDir() {
113
- return path2.join(getOpenCodeStorageDir(), "plugin", "magic-context");
113
+ return path.join(getOpenCodeStorageDir(), "plugin", "magic-context");
114
114
  }
115
115
  var GITIGNORE_GUARD_OPEN = "# >>> cortexkit:magic-context", GITIGNORE_GUARD_CLOSE = "# <<< cortexkit:magic-context";
116
116
  var init_data_path = () => {};
117
117
 
118
118
  // ../plugin/src/shared/logger.ts
119
119
  import * as fs from "node:fs";
120
- import * as path3 from "node:path";
120
+ import * as path2 from "node:path";
121
121
  function ensureDir(filePath) {
122
- const dir = path3.dirname(filePath);
122
+ const dir = path2.dirname(filePath);
123
123
  if (dir === lastEnsuredDir)
124
124
  return;
125
125
  try {
@@ -15707,13 +15707,23 @@ function summarizeDreamSchedule(dreamer) {
15707
15707
  }
15708
15708
 
15709
15709
  // ../plugin/src/features/magic-context/memory/project-identity.ts
15710
+ init_logger();
15710
15711
  import { execFileSync } from "node:child_process";
15711
15712
  import { createHash } from "node:crypto";
15712
- import { statSync as statSync2 } from "node:fs";
15713
- import path from "node:path";
15713
+ import { existsSync as existsSync3, realpathSync, statSync as statSync2 } from "node:fs";
15714
+ import path3 from "node:path";
15714
15715
  var GIT_TIMEOUT_MS = 5000;
15716
+ var TRANSIENT_FAILURE_COOLDOWN_MS = 5 * 60 * 1000;
15715
15717
  var identityCache = new Map;
15718
+ var lastKnownGitIdentityCache = new Map;
15716
15719
  var directoryFallbackCache = new Map;
15720
+ var transientFailureCooldown = new Map;
15721
+ var dubiousOwnershipFallbackDirectories = new Set;
15722
+ var dubiousOwnershipLoggedDirectories = new Set;
15723
+ var dubiousOwnershipWarnedDirectories = new Set;
15724
+ var transientGitIdentityReuseLoggedDirectories = new Set;
15725
+ var execFileSyncForIdentity = execFileSync;
15726
+ var nowMs = () => Date.now();
15717
15727
 
15718
15728
  class ProjectIdentityError extends Error {
15719
15729
  errorClass;
@@ -15765,7 +15775,7 @@ function getErrorStderr(error) {
15765
15775
  return "";
15766
15776
  }
15767
15777
  function directoryFallback(directory) {
15768
- const canonical = path.resolve(directory);
15778
+ const canonical = path3.resolve(directory);
15769
15779
  const hash = createHash("md5").update(canonical, "utf8").digest("hex").slice(0, 12);
15770
15780
  return `dir:${hash}`;
15771
15781
  }
@@ -15803,21 +15813,27 @@ function classifyGitError(error, rawDirectory) {
15803
15813
  return new ProjectIdentityError("permission_denied", rawDirectory, "Permission denied while spawning git", asError(error));
15804
15814
  }
15805
15815
  const stderr = getErrorStderr(error).toLowerCase();
15816
+ if (stderr.includes("detected dubious ownership")) {
15817
+ return new ProjectIdentityError("dubious_ownership", rawDirectory, "git refused to read the repository because it detected dubious ownership", asError(error));
15818
+ }
15806
15819
  if (stderr.includes("not a git repository") || stderr.includes("does not have any commits yet") || stderr.includes("ambiguous argument 'head'") || stderr.includes("unknown revision or path")) {
15807
15820
  return new ProjectIdentityError("not_git_repo", rawDirectory, "Directory has no git root commit; caller may use directory fallback", asError(error));
15808
15821
  }
15809
15822
  return new ProjectIdentityError("unknown", rawDirectory, "git rev-list failed while resolving project identity", asError(error));
15810
15823
  }
15811
15824
  function resolveProjectIdentityStrict(directory) {
15812
- const canonical = path.resolve(directory);
15825
+ const canonical = path3.resolve(directory);
15813
15826
  const cached = identityCache.get(canonical);
15814
15827
  if (cached !== undefined) {
15815
15828
  return cached;
15816
15829
  }
15817
15830
  assertDirectoryUsable(canonical, directory);
15831
+ if (!hasGitDir(canonical)) {
15832
+ throw new ProjectIdentityError("not_git_repo", directory, "Directory has no git metadata; caller may use directory fallback");
15833
+ }
15818
15834
  let output;
15819
15835
  try {
15820
- output = execFileSync("git", ["rev-list", "--max-parents=0", "HEAD"], {
15836
+ output = execFileSyncForIdentity("git", ["rev-list", "--max-parents=0", "HEAD"], {
15821
15837
  cwd: canonical,
15822
15838
  encoding: "utf8",
15823
15839
  env: { ...process.env, LC_ALL: "C", LANG: "C" },
@@ -15835,13 +15851,76 @@ function resolveProjectIdentityStrict(directory) {
15835
15851
  }
15836
15852
  const identity = `git:${rootCommit}`;
15837
15853
  identityCache.set(canonical, identity);
15854
+ lastKnownGitIdentityCache.set(canonical, identity);
15855
+ transientFailureCooldown.delete(canonical);
15856
+ dubiousOwnershipFallbackDirectories.delete(canonical);
15857
+ transientGitIdentityReuseLoggedDirectories.delete(canonical);
15838
15858
  return identity;
15839
15859
  }
15840
15860
  function shouldUseDirectoryFallback(error) {
15841
- return error.errorClass === "not_git_repo" || error.errorClass === "unknown" && error.message.startsWith("Unable to access project directory:");
15861
+ return error.errorClass !== "permission_denied";
15862
+ }
15863
+ function getActiveCooldown(canonical) {
15864
+ const until = transientFailureCooldown.get(canonical);
15865
+ if (until === undefined)
15866
+ return;
15867
+ if (nowMs() < until)
15868
+ return until;
15869
+ transientFailureCooldown.delete(canonical);
15870
+ return;
15871
+ }
15872
+ function lastKnownGitIdentity(canonical) {
15873
+ return lastKnownGitIdentityCache.get(canonical) ?? identityCache.get(canonical);
15874
+ }
15875
+ function nearestLastKnownGitIdentity(canonical) {
15876
+ const visited = new Set;
15877
+ const walk = (start) => {
15878
+ let current = start;
15879
+ while (!visited.has(current)) {
15880
+ visited.add(current);
15881
+ const cached = lastKnownGitIdentity(current);
15882
+ if (cached !== undefined)
15883
+ return { identity: cached, source: current };
15884
+ const parent = path3.dirname(current);
15885
+ if (parent === current)
15886
+ break;
15887
+ current = parent;
15888
+ }
15889
+ return;
15890
+ };
15891
+ const exactOrAncestor = walk(canonical);
15892
+ if (exactOrAncestor)
15893
+ return exactOrAncestor;
15894
+ try {
15895
+ const realCanonical = realpathSync.native(canonical);
15896
+ if (realCanonical !== canonical)
15897
+ return walk(realCanonical);
15898
+ } catch {}
15899
+ return;
15900
+ }
15901
+ function reuseLastKnownGitIdentity(canonical) {
15902
+ const cached = nearestLastKnownGitIdentity(canonical);
15903
+ if (cached === undefined)
15904
+ return;
15905
+ if (!transientGitIdentityReuseLoggedDirectories.has(canonical)) {
15906
+ transientGitIdentityReuseLoggedDirectories.add(canonical);
15907
+ const sourceNote = cached.source === canonical ? "" : ` from ancestor ${cached.source}`;
15908
+ log(`[magic-context] git identity resolution is temporarily unavailable for ${canonical}; reusing the last successful project identity${sourceNote} to avoid splitting project-scoped memory`);
15909
+ }
15910
+ return cached.identity;
15911
+ }
15912
+ function formatDubiousOwnershipWarning(canonical) {
15913
+ return `Magic Context: git refused to read ${canonical} (dubious ownership — the repo is owned by a different user). Using a directory-based project identity for now, which keeps memory separate from this repo's normal identity. Fix: git config --global --add safe.directory ${canonical}`;
15914
+ }
15915
+ function recordDubiousOwnershipFallback(canonical) {
15916
+ dubiousOwnershipFallbackDirectories.add(canonical);
15917
+ if (dubiousOwnershipLoggedDirectories.has(canonical))
15918
+ return;
15919
+ dubiousOwnershipLoggedDirectories.add(canonical);
15920
+ log(`[magic-context] ${formatDubiousOwnershipWarning(canonical)}`);
15842
15921
  }
15843
15922
  function resolveProjectIdentity(directory) {
15844
- const canonical = path.resolve(directory);
15923
+ const canonical = path3.resolve(directory);
15845
15924
  const cachedFallback = directoryFallbackCache.get(canonical);
15846
15925
  if (cachedFallback !== undefined) {
15847
15926
  if (!hasGitDir(canonical)) {
@@ -15849,27 +15928,74 @@ function resolveProjectIdentity(directory) {
15849
15928
  }
15850
15929
  directoryFallbackCache.delete(canonical);
15851
15930
  }
15931
+ if (getActiveCooldown(canonical) !== undefined) {
15932
+ if (hasGitDir(canonical)) {
15933
+ const cachedGitIdentity = reuseLastKnownGitIdentity(canonical);
15934
+ if (cachedGitIdentity !== undefined) {
15935
+ return cachedGitIdentity;
15936
+ }
15937
+ }
15938
+ return directoryFallback(canonical);
15939
+ }
15852
15940
  try {
15853
15941
  return resolveProjectIdentityStrict(directory);
15854
15942
  } catch (error) {
15855
15943
  if (error instanceof ProjectIdentityError && shouldUseDirectoryFallback(error)) {
15856
15944
  const fallback = directoryFallback(canonical);
15857
- if (!hasGitDir(canonical)) {
15945
+ const hasGitMetadata = hasGitDir(canonical);
15946
+ if (!hasGitMetadata) {
15858
15947
  directoryFallbackCache.set(canonical, fallback);
15948
+ transientFailureCooldown.delete(canonical);
15949
+ } else {
15950
+ transientFailureCooldown.set(canonical, nowMs() + TRANSIENT_FAILURE_COOLDOWN_MS);
15951
+ const cachedGitIdentity = reuseLastKnownGitIdentity(canonical);
15952
+ if (cachedGitIdentity !== undefined) {
15953
+ return cachedGitIdentity;
15954
+ }
15955
+ }
15956
+ if (error.errorClass === "dubious_ownership") {
15957
+ recordDubiousOwnershipFallback(canonical);
15859
15958
  }
15860
15959
  return fallback;
15861
15960
  }
15862
15961
  throw error;
15863
15962
  }
15864
15963
  }
15865
- function hasGitDir(canonical) {
15964
+ function resolveProjectIdentityOrFallback(directory) {
15866
15965
  try {
15867
- statSync2(path.join(canonical, ".git"));
15966
+ return resolveProjectIdentity(directory);
15967
+ } catch (error) {
15968
+ const canonical = path3.resolve(directory);
15969
+ const fallback = directoryFallback(canonical);
15970
+ const message = error instanceof Error ? error.message : String(error);
15971
+ log(`[magic-context] project identity resolution failed for ${canonical}; using directory fallback ${fallback}: ${message}`);
15972
+ return fallback;
15973
+ }
15974
+ }
15975
+ function hasGitDir(canonical) {
15976
+ if (hasGitDirInAncestorChain(canonical)) {
15868
15977
  return true;
15978
+ }
15979
+ try {
15980
+ const realCanonical = realpathSync.native(canonical);
15981
+ return realCanonical !== canonical && hasGitDirInAncestorChain(realCanonical);
15869
15982
  } catch {
15870
15983
  return false;
15871
15984
  }
15872
15985
  }
15986
+ function hasGitDirInAncestorChain(startDirectory) {
15987
+ let current = startDirectory;
15988
+ while (true) {
15989
+ if (existsSync3(path3.join(current, ".git"))) {
15990
+ return true;
15991
+ }
15992
+ const parent = path3.dirname(current);
15993
+ if (parent === current) {
15994
+ return false;
15995
+ }
15996
+ current = parent;
15997
+ }
15998
+ }
15873
15999
  function normalizeStoredProjectPath(rawOrStored) {
15874
16000
  if (rawOrStored.startsWith("git:") || rawOrStored.startsWith("dir:")) {
15875
16001
  return rawOrStored;
@@ -15902,7 +16028,7 @@ function removeSystemReminders(text) {
15902
16028
  // ../plugin/src/hooks/magic-context/read-session-db.ts
15903
16029
  init_data_path();
15904
16030
  init_logger();
15905
- import { existsSync as existsSync3 } from "node:fs";
16031
+ import { existsSync as existsSync4 } from "node:fs";
15906
16032
  import { join as join3 } from "node:path";
15907
16033
 
15908
16034
  // ../plugin/src/shared/sqlite.ts
@@ -15971,7 +16097,7 @@ function getOpenCodeDbPath() {
15971
16097
  return join3(getDataDir(), "opencode", "opencode.db");
15972
16098
  }
15973
16099
  function openCodeDbExists() {
15974
- return existsSync3(getOpenCodeDbPath());
16100
+ return existsSync4(getOpenCodeDbPath());
15975
16101
  }
15976
16102
  var cachedReadOnlyDb = null;
15977
16103
  function closeCachedReadOnlyDb() {
@@ -149637,7 +149763,7 @@ function parseReportedLimit(message) {
149637
149763
 
149638
149764
  // ../plugin/src/features/magic-context/session-project-backfill.ts
149639
149765
  init_logger();
149640
- import { existsSync as existsSync4 } from "node:fs";
149766
+ import { existsSync as existsSync5 } from "node:fs";
149641
149767
 
149642
149768
  // ../plugin/src/features/magic-context/session-project-storage.ts
149643
149769
  var upsertSessionProjectStatements = new WeakMap;
@@ -149866,7 +149992,7 @@ async function runSessionProjectBackfill(db, sessions, options = {}) {
149866
149992
  result.skippedEmptyDirectories += 1;
149867
149993
  continue;
149868
149994
  }
149869
- const stillExists = existenceCache.get(session.directory) ?? existsSync4(session.directory);
149995
+ const stillExists = existenceCache.get(session.directory) ?? existsSync5(session.directory);
149870
149996
  existenceCache.set(session.directory, stillExists);
149871
149997
  if (!stillExists) {
149872
149998
  result.skippedDeadDirectories += 1;
@@ -152116,6 +152242,15 @@ var MIGRATIONS = [
152116
152242
  );
152117
152243
  `);
152118
152244
  }
152245
+ },
152246
+ {
152247
+ version: 50,
152248
+ description: "add durable ctx-wrapup session marker",
152249
+ up(db) {
152250
+ if (tableExists2(db, "session_meta")) {
152251
+ ensureColumn(db, "session_meta", "wrapup_in_progress_state", "TEXT");
152252
+ }
152253
+ }
152119
152254
  }
152120
152255
  ];
152121
152256
  var LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
@@ -152304,7 +152439,7 @@ function readProjectDocsCanonical(projectDirectory) {
152304
152439
  return { renderedBlock, canonicalHash };
152305
152440
  }
152306
152441
  // ../plugin/src/features/magic-context/storage-db.ts
152307
- import { chmodSync, copyFileSync, cpSync, existsSync as existsSync6, mkdirSync as mkdirSync4, mkdtempSync } from "node:fs";
152442
+ import { chmodSync, copyFileSync, cpSync, existsSync as existsSync7, mkdirSync as mkdirSync4, mkdtempSync } from "node:fs";
152308
152443
  init_data_path();
152309
152444
  import { dirname as dirname3, join as join5 } from "node:path";
152310
152445
 
@@ -152443,7 +152578,7 @@ function loadToolDefinitionMeasurements(db) {
152443
152578
  // ../plugin/src/features/magic-context/tool-owner-backfill.ts
152444
152579
  init_data_path();
152445
152580
  init_logger();
152446
- import { existsSync as existsSync5 } from "node:fs";
152581
+ import { existsSync as existsSync6 } from "node:fs";
152447
152582
  import { join as join4 } from "node:path";
152448
152583
  var LEASE_DURATION_MS = 5 * 60 * 1000;
152449
152584
  var LEASE_RENEWAL_MS = 60 * 1000;
@@ -152483,7 +152618,7 @@ function runToolOwnerBackfill(db) {
152483
152618
  return result;
152484
152619
  }
152485
152620
  const opencodeDbPath = resolveOpencodeDbPath();
152486
- if (!existsSync5(opencodeDbPath)) {
152621
+ if (!existsSync6(opencodeDbPath)) {
152487
152622
  log(`[backfill] OpenCode DB not found at ${opencodeDbPath} — marking all unbackfilled sessions as skipped. Lazy adoption (defense-in-depth) handles legacy rows at runtime.`);
152488
152623
  markAllUnbackfilledSessionsSkipped(db);
152489
152624
  result.sessionsSkippedNoOcDb = countSessionsByStatus(db, "skipped");
@@ -152705,7 +152840,7 @@ var persistenceByDatabase = new WeakMap;
152705
152840
  var persistenceErrorByDatabase = new WeakMap;
152706
152841
  var pathByDatabase = new WeakMap;
152707
152842
  var lastSchemaFenceRejection = null;
152708
- var LATEST_SUPPORTED_VERSION = 49;
152843
+ var LATEST_SUPPORTED_VERSION = 50;
152709
152844
  var PERMISSIONS_ENFORCEABLE = process.platform !== "win32";
152710
152845
  function ensureSecureStorageDir(dir) {
152711
152846
  mkdirSync4(dir, { recursive: true, mode: 448 });
@@ -152722,7 +152857,7 @@ function restrictDatabaseFilePermissions(dbPath) {
152722
152857
  return;
152723
152858
  for (const suffix of ["", "-wal", "-shm"]) {
152724
152859
  const file = `${dbPath}${suffix}`;
152725
- if (!existsSync6(file))
152860
+ if (!existsSync7(file))
152726
152861
  continue;
152727
152862
  try {
152728
152863
  chmodSync(file, 384);
@@ -152748,11 +152883,11 @@ function getDatabasePath(db) {
152748
152883
  return pathByDatabase.get(db) ?? null;
152749
152884
  }
152750
152885
  function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
152751
- if (existsSync6(targetDbPath))
152886
+ if (existsSync7(targetDbPath))
152752
152887
  return;
152753
152888
  const legacyDir = getLegacyOpenCodeMagicContextStorageDir();
152754
152889
  const legacyDbPath = join5(legacyDir, "context.db");
152755
- if (!existsSync6(legacyDbPath))
152890
+ if (!existsSync7(legacyDbPath))
152756
152891
  return;
152757
152892
  log(`[magic-context] migrating legacy plugin storage: ${legacyDir} -> ${targetDbDir} (legacy left in place as backup)`);
152758
152893
  ensureSecureStorageDir(targetDbDir);
@@ -152769,7 +152904,7 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
152769
152904
  for (const suffix of ["", "-wal", "-shm"]) {
152770
152905
  const src = `${legacyDbPath}${suffix}`;
152771
152906
  const dst = join5(targetDbDir, `context.db${suffix}`);
152772
- if (existsSync6(src)) {
152907
+ if (existsSync7(src)) {
152773
152908
  try {
152774
152909
  copyFileSync(src, dst);
152775
152910
  } catch (error) {
@@ -152779,7 +152914,7 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
152779
152914
  }
152780
152915
  const legacyModelsDir = join5(legacyDir, "models");
152781
152916
  const targetModelsDir = join5(targetDbDir, "models");
152782
- if (existsSync6(legacyModelsDir) && !existsSync6(targetModelsDir)) {
152917
+ if (existsSync7(legacyModelsDir) && !existsSync7(targetModelsDir)) {
152783
152918
  try {
152784
152919
  cpSync(legacyModelsDir, targetModelsDir, { recursive: true });
152785
152920
  } catch (error) {
@@ -153383,6 +153518,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
153383
153518
  force_emergency_bypass_used INTEGER NOT NULL DEFAULT 0,
153384
153519
  emergency_drain_active INTEGER NOT NULL DEFAULT 0,
153385
153520
  historian_drain_failure_at INTEGER NOT NULL DEFAULT 0,
153521
+ wrapup_in_progress_state TEXT,
153386
153522
  cached_m0_materialized_at INTEGER,
153387
153523
  cached_m0_session_facts_version INTEGER,
153388
153524
  cached_m0_upgrade_state TEXT,
@@ -153671,6 +153807,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
153671
153807
  ensureColumn(db, "session_meta", "force_emergency_bypass_used", "INTEGER NOT NULL DEFAULT 0");
153672
153808
  ensureColumn(db, "session_meta", "emergency_drain_active", "INTEGER NOT NULL DEFAULT 0");
153673
153809
  ensureColumn(db, "session_meta", "historian_drain_failure_at", "INTEGER NOT NULL DEFAULT 0");
153810
+ ensureColumn(db, "session_meta", "wrapup_in_progress_state", "TEXT");
153674
153811
  ensureColumn(db, "session_meta", "cached_m0_materialized_at", "INTEGER");
153675
153812
  ensureColumn(db, "session_meta", "cached_m0_session_facts_version", "INTEGER");
153676
153813
  ensureColumn(db, "session_meta", "cached_m0_upgrade_state", "TEXT");
@@ -154143,6 +154280,148 @@ function recordProtectedTailNoEligibleHead(db, sessionId) {
154143
154280
  return loadProtectedTailMeta(db, sessionId).recoveryNoEligibleHeadCount;
154144
154281
  }
154145
154282
  var DRAIN_WINDOW_MS = 10 * 60 * 1000;
154283
+ var WRAPUP_IN_PROGRESS_TTL_MS = 5 * 60 * 1000;
154284
+ function parseWrapupState(value) {
154285
+ if (typeof value !== "string" || value.trim().length === 0)
154286
+ return null;
154287
+ try {
154288
+ const parsed = JSON.parse(value);
154289
+ if (!parsed || typeof parsed !== "object")
154290
+ return null;
154291
+ if (typeof parsed.holderId !== "string" || parsed.holderId.length === 0)
154292
+ return null;
154293
+ const numberFields = [
154294
+ "acquiredAt",
154295
+ "expiresAt",
154296
+ "messagesToKeep",
154297
+ "anchorRawMessageCount",
154298
+ "targetEligibleEndOrdinal",
154299
+ "lastCompartmentEnd",
154300
+ "chunkIndex",
154301
+ "expectedChunks",
154302
+ "updatedAt"
154303
+ ];
154304
+ for (const field of numberFields) {
154305
+ if (typeof parsed[field] !== "number" || !Number.isFinite(parsed[field]))
154306
+ return null;
154307
+ }
154308
+ return parsed;
154309
+ } catch {
154310
+ return null;
154311
+ }
154312
+ }
154313
+ function readRawWrapupState(db, sessionId) {
154314
+ const row = db.prepare("SELECT wrapup_in_progress_state FROM session_meta WHERE session_id = ?").get(sessionId);
154315
+ return parseWrapupState(row?.wrapup_in_progress_state);
154316
+ }
154317
+ function getWrapupInProgressState(db, sessionId, now = Date.now()) {
154318
+ const state = readRawWrapupState(db, sessionId);
154319
+ if (!state)
154320
+ return null;
154321
+ if (state.expiresAt > now)
154322
+ return state;
154323
+ try {
154324
+ db.exec("BEGIN IMMEDIATE");
154325
+ } catch {
154326
+ return null;
154327
+ }
154328
+ let finished = false;
154329
+ try {
154330
+ const current = readRawWrapupState(db, sessionId);
154331
+ if (current && current.expiresAt <= now) {
154332
+ db.prepare("UPDATE session_meta SET wrapup_in_progress_state = NULL WHERE session_id = ?").run(sessionId);
154333
+ }
154334
+ db.exec("COMMIT");
154335
+ finished = true;
154336
+ } finally {
154337
+ if (!finished) {
154338
+ try {
154339
+ db.exec("ROLLBACK");
154340
+ } catch {}
154341
+ }
154342
+ }
154343
+ return null;
154344
+ }
154345
+ function isWrapupInProgress(db, sessionId, now = Date.now()) {
154346
+ return getWrapupInProgressState(db, sessionId, now) !== null;
154347
+ }
154348
+ function acquireWrapupInProgress(db, sessionId, state, now = Date.now()) {
154349
+ const acquiredAt = now;
154350
+ const next = {
154351
+ ...state,
154352
+ acquiredAt,
154353
+ expiresAt: acquiredAt + WRAPUP_IN_PROGRESS_TTL_MS,
154354
+ updatedAt: acquiredAt
154355
+ };
154356
+ db.exec("BEGIN IMMEDIATE");
154357
+ let finished = false;
154358
+ try {
154359
+ ensureSessionMetaRow(db, sessionId);
154360
+ const current = readRawWrapupState(db, sessionId);
154361
+ if (current && current.expiresAt > now && current.holderId !== state.holderId) {
154362
+ db.exec("COMMIT");
154363
+ finished = true;
154364
+ return { ok: false, state: current };
154365
+ }
154366
+ db.prepare("UPDATE session_meta SET wrapup_in_progress_state = ? WHERE session_id = ?").run(stableStringify(next), sessionId);
154367
+ db.exec("COMMIT");
154368
+ finished = true;
154369
+ return { ok: true, state: next };
154370
+ } finally {
154371
+ if (!finished) {
154372
+ try {
154373
+ db.exec("ROLLBACK");
154374
+ } catch {}
154375
+ }
154376
+ }
154377
+ }
154378
+ function updateWrapupInProgress(db, sessionId, holderId, updates, now = Date.now()) {
154379
+ db.exec("BEGIN IMMEDIATE");
154380
+ let finished = false;
154381
+ try {
154382
+ const current = readRawWrapupState(db, sessionId);
154383
+ if (!current || current.holderId !== holderId || current.expiresAt <= now) {
154384
+ db.exec("ROLLBACK");
154385
+ finished = true;
154386
+ return null;
154387
+ }
154388
+ const next = {
154389
+ ...current,
154390
+ ...updates,
154391
+ holderId,
154392
+ expiresAt: now + WRAPUP_IN_PROGRESS_TTL_MS,
154393
+ updatedAt: now
154394
+ };
154395
+ db.prepare("UPDATE session_meta SET wrapup_in_progress_state = ? WHERE session_id = ?").run(stableStringify(next), sessionId);
154396
+ db.exec("COMMIT");
154397
+ finished = true;
154398
+ return next;
154399
+ } finally {
154400
+ if (!finished) {
154401
+ try {
154402
+ db.exec("ROLLBACK");
154403
+ } catch {}
154404
+ }
154405
+ }
154406
+ }
154407
+ function releaseWrapupInProgress(db, sessionId, holderId) {
154408
+ db.exec("BEGIN IMMEDIATE");
154409
+ let finished = false;
154410
+ try {
154411
+ const current = readRawWrapupState(db, sessionId);
154412
+ if (current?.holderId === holderId) {
154413
+ db.prepare("UPDATE session_meta SET wrapup_in_progress_state = NULL WHERE session_id = ?").run(sessionId);
154414
+ }
154415
+ db.exec("COMMIT");
154416
+ finished = true;
154417
+ } finally {
154418
+ if (!finished) {
154419
+ try {
154420
+ db.exec("ROLLBACK");
154421
+ } catch {}
154422
+ }
154423
+ }
154424
+ }
154146
154425
  function protectedTailWindowBudget(usagePercentage, usable, perRunCap) {
154147
154426
  if (usagePercentage >= 95)
154148
154427
  return Math.min(1e6, Math.max(4 * perRunCap, Math.round(0.5 * usable)));
@@ -155999,7 +156278,7 @@ var ERROR_CLASSES = new Set([
155999
156278
  // ../plugin/src/features/magic-context/v22-deferred-backfill.ts
156000
156279
  init_logger();
156001
156280
  import { createHash as createHash4 } from "node:crypto";
156002
- import { realpathSync } from "node:fs";
156281
+ import { realpathSync as realpathSync2 } from "node:fs";
156003
156282
  import path5 from "node:path";
156004
156283
 
156005
156284
  // ../plugin/src/features/magic-context/memory/relocate-memory.ts
@@ -156033,7 +156312,10 @@ function countFailures(db) {
156033
156312
  }
156034
156313
  function classifyBackfillError(error) {
156035
156314
  if (error instanceof ProjectIdentityError) {
156036
- return { errorClass: error.errorClass, errorMessage: error.message };
156315
+ return {
156316
+ errorClass: error.errorClass === "dubious_ownership" ? "unknown" : error.errorClass,
156317
+ errorMessage: error.message
156318
+ };
156037
156319
  }
156038
156320
  if (error instanceof Error) {
156039
156321
  return { errorClass: "unknown", errorMessage: error.message };
@@ -156053,7 +156335,7 @@ function resolveBackfillIdentity(rawProjectPath) {
156053
156335
  function computeLegacyRustDirIdentity(rawProjectPath) {
156054
156336
  let canonical;
156055
156337
  try {
156056
- canonical = realpathSync(rawProjectPath);
156338
+ canonical = realpathSync2(rawProjectPath);
156057
156339
  } catch {
156058
156340
  canonical = path5.isAbsolute(rawProjectPath) ? rawProjectPath : path5.join(process.cwd(), rawProjectPath);
156059
156341
  }
@@ -156744,11 +157026,12 @@ function compareSemverCore(a, b) {
156744
157026
 
156745
157027
  // ../plugin/src/shared/announcement.ts
156746
157028
  init_data_path();
156747
- var ANNOUNCEMENT_VERSION = "0.30.4";
157029
+ var ANNOUNCEMENT_VERSION = "0.31.0";
156748
157030
  var ANNOUNCEMENT_FEATURES = [
156749
- "Pi: fixed historian and dreamer failing when your model comes from a provider extension (Antigravity, Anthropic OAuth, etc.). Subagents now load your extensions.",
156750
- "Pi: fixed the retrospective and refresh-primers dreamer tasks reading sessions through the wrong API.",
156751
- "OpenCode: the plugin no longer re-adds its TUI sidebar entry on startup, so removing it from tui.jsonc now sticks."
157031
+ "New /ctx-wrapup command: compact older history on demand, keeping the newest N messages raw. Run it before switching to a smaller-context model.",
157032
+ "ctx_search now also searches your session notes and smart notes.",
157033
+ "Project identity now survives transient git failures (slow disks, dubious ownership) without splitting your project memory.",
157034
+ "Removed the ctx_reduce_enabled setting; agent-controlled reduction is always on. Caveman text compression is now independent (caveman_text_compression.enabled)."
156752
157035
  ];
156753
157036
  var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU";
156754
157037
  var STATE_FILENAME = "last_announced_version";
@@ -156924,7 +157207,7 @@ init_logger();
156924
157207
 
156925
157208
  // src/subagent-runner.ts
156926
157209
  import * as childProcess from "node:child_process";
156927
- import { existsSync as existsSync8, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
157210
+ import { existsSync as existsSync9, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
156928
157211
  import { createRequire as createRequire2 } from "node:module";
156929
157212
  import { tmpdir as tmpdir2 } from "node:os";
156930
157213
  import { basename as basename2, dirname as dirname4, join as join8, resolve as resolvePath } from "node:path";
@@ -157055,7 +157338,7 @@ function resolveBundledPiCli() {
157055
157338
  const require_ = createRequire2(import.meta.url);
157056
157339
  const pkgJson = require_.resolve("@earendil-works/pi-coding-agent/package.json");
157057
157340
  const cliPath = join8(dirname4(pkgJson), "dist/cli.js");
157058
- if (existsSync8(cliPath))
157341
+ if (existsSync9(cliPath))
157059
157342
  return cliPath;
157060
157343
  return null;
157061
157344
  } catch {
@@ -157066,7 +157349,7 @@ function resolvePiInvocation() {
157066
157349
  const execPath = process.execPath;
157067
157350
  const currentScript = process.argv[1];
157068
157351
  const isBunVirtualScript = currentScript?.startsWith("/$bunfs/root/") ?? false;
157069
- if (currentScript && !isBunVirtualScript && existsSync8(currentScript)) {
157352
+ if (currentScript && !isBunVirtualScript && existsSync9(currentScript)) {
157070
157353
  return { command: execPath, prefixArgs: [currentScript] };
157071
157354
  }
157072
157355
  const execName = basename2(execPath).toLowerCase();
@@ -157084,7 +157367,7 @@ function resolveSubagentEntryPath() {
157084
157367
  try {
157085
157368
  const here = dirname4(fileURLToPath(import.meta.url));
157086
157369
  const candidate = resolvePath(here, "subagent-entry.js");
157087
- if (existsSync8(candidate))
157370
+ if (existsSync9(candidate))
157088
157371
  return candidate;
157089
157372
  return;
157090
157373
  } catch {
@@ -157753,13 +158036,13 @@ ${sidekickText}
157753
158036
  init_logger();
157754
158037
 
157755
158038
  // ../plugin/src/features/magic-context/dreamer/open-opencode-db.ts
157756
- import { existsSync as existsSync10 } from "node:fs";
158039
+ import { existsSync as existsSync11 } from "node:fs";
157757
158040
  init_logger();
157758
158041
 
157759
158042
  // ../plugin/src/features/magic-context/compaction-marker.ts
157760
158043
  init_data_path();
157761
158044
  init_logger();
157762
- import { existsSync as existsSync9 } from "node:fs";
158045
+ import { existsSync as existsSync10 } from "node:fs";
157763
158046
  import { join as join9 } from "node:path";
157764
158047
  var BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
157765
158048
  function randomBase62(length) {
@@ -157826,7 +158109,7 @@ function getWritableOpenCodeDb() {
157826
158109
  closeQuietly(cachedWriteDb.db);
157827
158110
  } catch {}
157828
158111
  }
157829
- if (!existsSync9(dbPath)) {
158112
+ if (!existsSync10(dbPath)) {
157830
158113
  throw new Error(`OpenCode database not found at ${dbPath} (is OpenCode installed?)`);
157831
158114
  }
157832
158115
  const db = new Database(dbPath);
@@ -157947,7 +158230,7 @@ function removeCompactionMarker(state) {
157947
158230
  // ../plugin/src/features/magic-context/dreamer/open-opencode-db.ts
157948
158231
  function openOpenCodeDb() {
157949
158232
  const dbPath = getOpenCodeDbPath2();
157950
- if (!existsSync10(dbPath)) {
158233
+ if (!existsSync11(dbPath)) {
157951
158234
  log(`[dreamer] OpenCode DB not found at ${dbPath} — skipping OpenCode history scan`);
157952
158235
  return null;
157953
158236
  }
@@ -157963,7 +158246,7 @@ function openOpenCodeDb() {
157963
158246
 
157964
158247
  // ../plugin/src/features/magic-context/dreamer/task-executor.ts
157965
158248
  import { createHash as createHash10 } from "node:crypto";
157966
- import { existsSync as existsSync14 } from "node:fs";
158249
+ import { existsSync as existsSync15 } from "node:fs";
157967
158250
 
157968
158251
  // ../plugin/src/agents/dreamer.ts
157969
158252
  var DREAMER_AGENT = "dreamer";
@@ -172915,7 +173198,6 @@ var MagicContextConfigSchema = exports_external.object({
172915
173198
  enabled: exports_external.boolean().default(true).describe("Enable magic context (default: true)"),
172916
173199
  auto_update: exports_external.boolean().optional().describe("Enable automatic npm self-update checks for the OpenCode plugin. Security: USER-only in config loader, so hostile project configs cannot suppress updates."),
172917
173200
  language: exports_external.string().trim().toLowerCase().refine((s) => isValidLanguageCode(s), 'language must be a 2-letter ISO 639-1 code (e.g. "tr", "es", "de")').optional().describe("Output language for Magic Context's generated content and guidance, as a " + '2-letter ISO 639-1 code (e.g. "tr", "es", "de", "ja", "pt"). When set, the ' + "historian, dreamer, sidekick, and the agent-guidance block instruct the model to " + "write its PROSE in this language while keeping all structural tokens (XML tags, " + "the five memory category names, code identifiers, file paths) in English. " + "USER-LEVEL ONLY (ignored in project config for security). Unset = today's " + "behavior (model mirrors the conversation; English scaffolding). Changing it " + "triggers one cache re-materialization; existing compartments/memories keep their " + "original language until naturally rewritten."),
172918
- ctx_reduce_enabled: exports_external.boolean().default(true).describe("When false, ctx_reduce tool is hidden, all nudges disabled, and prompt guidance about ctx_reduce stripped. Heuristic cleanup, compartments, memory, and other features still work. (default: true)"),
172919
173201
  historian: HistorianConfigSchema.describe("Historian agent configuration (model, fallback_models, variant, temperature, maxTokens, permission, two_pass, etc.)"),
172920
173202
  dreamer: DreamerConfigSchema.optional().describe("Dreamer agent + scheduling configuration (model, fallback_models, disable, schedule, tasks, etc.)"),
172921
173203
  cache_ttl: exports_external.union([exports_external.string(), exports_external.object({ default: exports_external.string() }).catchall(exports_external.string())]).default("5m").describe('Cache TTL: string (e.g. "5m") or per-model object ({ default: "5m", "model-id": "10m" })'),
@@ -172954,9 +173236,9 @@ var MagicContextConfigSchema = exports_external.object({
172954
173236
  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."),
172955
173237
  smart_drops: exports_external.boolean().default(false).describe("Content-aware reclaim of provably-superseded tool output, layered on the existing execute-pass auto-drop. When on: superseded todowrite (keep newest 1), spent ctx_reduce (keep newest 5), and zero-value meta (bash_status, bash_kill, ctx_note read/dismiss) outputs are dropped; older edits to a file are compressed to a filePath-preserving marker while the newest edit per file stays full. Only acts on passes already busting the cache, so it never originates a cache bust. Honors the protected-tag reserve. Experimental: opt-in, default off until cache stability is proven; when off the wire is byte-identical to the positional-only reclaim. Requires a restart."),
172956
173238
  caveman_text_compression: exports_external.object({
172957
- 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%)."),
173239
+ enabled: exports_external.boolean().default(false).describe("Apply deterministic caveman-style text compression to old conversation text. Active for primary sessions when enabled; never for subagents. Compresses user/assistant text in oldest-first tiers: ultra (oldest 20%), full, lite, untouched (newest 40%)."),
172958
173240
  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.")
172959
- }).default({ enabled: false, min_chars: 500 }).describe("Age-tier caveman compression for long user/assistant text parts. Only active when ctx_reduce_enabled is false. Oldest 20% of eligible tags (outside protected tail) go to ultra, next 20% to full, next 20% to lite, newest 40% untouched. Graduated from experimental.caveman_text_compression; opt-in, default off (lossy)."),
173241
+ }).default({ enabled: false, min_chars: 500 }).describe("Age-tier caveman compression for long user/assistant text parts. Active for primary sessions when enabled; never for subagents. Oldest 20% of eligible tags (outside protected tail) go to ultra, next 20% to full, next 20% to lite, newest 40% untouched. Graduated from experimental.caveman_text_compression; opt-in, default off (lossy)."),
172960
173242
  memory: exports_external.object({
172961
173243
  enabled: exports_external.boolean().default(true).describe("Enable cross-session memory (default: true)"),
172962
173244
  injection_budget_tokens: exports_external.number().min(500).max(20000).default(4000).describe("Token budget for memory injection on session start (min: 500, max: 20000, default: 4000)"),
@@ -173763,7 +174045,8 @@ function isNativeRuntimeMissingError(error51) {
173763
174045
  if (code === "ERR_DLOPEN_FAILED" && lower.includes("onnxruntime")) {
173764
174046
  return true;
173765
174047
  }
173766
- if (!lower.includes("onnxruntime-node"))
174048
+ const mentionsNativeRuntime = lower.includes("onnxruntime-node") || lower.includes("onnxruntime_binding");
174049
+ if (!mentionsNativeRuntime)
173767
174050
  return false;
173768
174051
  return code === "ERR_MODULE_NOT_FOUND" || name2 === "ResolveMessage" || lower.includes("cannot find package") || lower.includes("cannot find module") || lower.includes("err_module_not_found");
173769
174052
  }
@@ -173908,7 +174191,7 @@ class LocalEmbeddingProvider {
173908
174191
  } catch (error51) {
173909
174192
  if (isNativeRuntimeMissingError(error51)) {
173910
174193
  nativeRuntimeMissing = true;
173911
- log("[magic-context] local embedding runtime is not installed (onnxruntime-node missing from this install). Local embeddings are disabled. Fix: reinstall the plugin (run `npx @cortexkit/magic-context@latest doctor --force`), or configure an `openai-compatible`/`ollama` embedding endpoint instead. Existing memories are unaffected.");
174194
+ log("[magic-context] local embeddings are disabled because the onnxruntime-node native binding is missing or failed to load. Run `npx @cortexkit/magic-context@latest doctor` for repair guidance (use `doctor --force` to reinstall cached plugin packages), or configure an `openai-compatible` embedding HTTP endpoint. Existing memories are unaffected.");
173912
174195
  } else {
173913
174196
  log("[magic-context] embedding model failed to load:", error51);
173914
174197
  }
@@ -176485,7 +176768,7 @@ function getMemoryVerifications(db, memoryIds) {
176485
176768
  }
176486
176769
  // ../plugin/src/features/magic-context/memory/verification-paths.ts
176487
176770
  import { execFile } from "node:child_process";
176488
- import { existsSync as existsSync11, realpathSync as realpathSync2, statSync as statSync4 } from "node:fs";
176771
+ import { existsSync as existsSync12, realpathSync as realpathSync3, statSync as statSync4 } from "node:fs";
176489
176772
  import path7 from "node:path";
176490
176773
  import { promisify } from "node:util";
176491
176774
  var execFileAsync = promisify(execFile);
@@ -176512,7 +176795,7 @@ function isWithin(root, candidate) {
176512
176795
  }
176513
176796
  function safeRealpath(value) {
176514
176797
  try {
176515
- return realpathSync2.native(value);
176798
+ return realpathSync3.native(value);
176516
176799
  } catch {
176517
176800
  return null;
176518
176801
  }
@@ -176590,7 +176873,7 @@ function verificationFileExists(baseRoot, filePath) {
176590
176873
  return false;
176591
176874
  const root = path7.resolve(baseRoot);
176592
176875
  const candidate = path7.resolve(root, filePath);
176593
- return isWithin(root, candidate) && existsSync11(candidate);
176876
+ return isWithin(root, candidate) && existsSync12(candidate);
176594
176877
  }
176595
176878
  async function normalizeVerificationFiles(args) {
176596
176879
  const cwd = path7.resolve(args.cwd);
@@ -176623,7 +176906,7 @@ async function normalizeVerificationFiles(args) {
176623
176906
  warnings.push(`Skipped verification path "${value}" because it is the repo/project root.`);
176624
176907
  continue;
176625
176908
  }
176626
- if (existsSync11(candidate)) {
176909
+ if (existsSync12(candidate)) {
176627
176910
  try {
176628
176911
  if (statSync4(candidate).isDirectory()) {
176629
176912
  warnings.push(`Skipped verification path "${value}" because it is a directory.`);
@@ -176654,7 +176937,7 @@ async function normalizeVerificationFiles(args) {
176654
176937
  }
176655
176938
  normalized.push(tracked);
176656
176939
  } else {
176657
- if (!existsSync11(candidate)) {
176940
+ if (!existsSync12(candidate)) {
176658
176941
  warnings.push(`Skipped verification path "${value}" because it does not exist.`);
176659
176942
  continue;
176660
176943
  }
@@ -179297,7 +179580,7 @@ Output exactly JSON: {"met": false}`;
179297
179580
 
179298
179581
  // ../plugin/src/features/magic-context/dreamer/maintain-docs-protected-enforcement.ts
179299
179582
  init_logger();
179300
- import { existsSync as existsSync12, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
179583
+ import { existsSync as existsSync13, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
179301
179584
  import { join as join11 } from "node:path";
179302
179585
 
179303
179586
  // ../plugin/src/features/magic-context/dreamer/protected-regions.ts
@@ -179401,7 +179684,7 @@ function snapshotMaintainDocsFiles(docsDir) {
179401
179684
  for (const name2 of MAINTAIN_DOCS_SNAPSHOT_FILES) {
179402
179685
  const path9 = join11(docsDir, name2);
179403
179686
  try {
179404
- if (existsSync12(path9)) {
179687
+ if (existsSync13(path9)) {
179405
179688
  snapshot.set(name2, readFileSync6(path9, "utf8"));
179406
179689
  }
179407
179690
  } catch {}
@@ -179429,7 +179712,7 @@ function enforceMaintainDocsProtectedRegions(args) {
179429
179712
  init_logger();
179430
179713
 
179431
179714
  // ../plugin/src/features/magic-context/dreamer/map-memories-prompt.ts
179432
- import { existsSync as existsSync13, statSync as statSync5 } from "node:fs";
179715
+ import { existsSync as existsSync14, statSync as statSync5 } from "node:fs";
179433
179716
  import path9 from "node:path";
179434
179717
  var MAP_MEMORIES_SYSTEM_PROMPT = `You are a memory mapper for the magic-context system. You map project memories to the repository files that back them.
179435
179718
 
@@ -179467,7 +179750,7 @@ function extractMemoryCandidatePaths(content, repoDir) {
179467
179750
  if (!abs.startsWith(`${root}/`))
179468
179751
  continue;
179469
179752
  try {
179470
- if (existsSync13(abs) && statSync5(abs).isFile())
179753
+ if (existsSync14(abs) && statSync5(abs).isFile())
179471
179754
  found.add(rel);
179472
179755
  } catch {}
179473
179756
  if (found.size >= MAX_SEED_PATHS_PER_MEMORY)
@@ -181608,8 +181891,8 @@ async function runAgenticTask(config2, ctx, helpers) {
181608
181891
  const lastRunAt = getTaskScheduleState(db, projectIdentity, config2.task)?.lastRunAt ?? null;
181609
181892
  const maintainDocsSnapshot = task === "maintain-docs" ? snapshotMaintainDocsFiles(docsDir) : undefined;
181610
181893
  const existingDocs = task === "maintain-docs" ? {
181611
- architecture: existsSync14(`${docsDir}/ARCHITECTURE.md`),
181612
- structure: existsSync14(`${docsDir}/STRUCTURE.md`)
181894
+ architecture: existsSync15(`${docsDir}/ARCHITECTURE.md`),
181895
+ structure: existsSync15(`${docsDir}/STRUCTURE.md`)
181613
181896
  } : undefined;
181614
181897
  const userMemories = task === "curate" ? getActiveUserMemories(db).map((um) => ({ id: um.id, content: um.content })) : undefined;
181615
181898
  let curateMemories;
@@ -182824,7 +183107,7 @@ function pruneNestedConfigLeaf(block, relativePath) {
182824
183107
  }
182825
183108
 
182826
183109
  // src/config/index.ts
182827
- import { existsSync as existsSync16, readFileSync as readFileSync8 } from "node:fs";
183110
+ import { existsSync as existsSync17, readFileSync as readFileSync8 } from "node:fs";
182828
183111
 
182829
183112
  // ../plugin/src/config/migrate-dreamer-v2.ts
182830
183113
  var OLD_VERIFY_TASK = "verify";
@@ -183206,7 +183489,7 @@ function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
183206
183489
  }
183207
183490
 
183208
183491
  // ../plugin/src/config/variable.ts
183209
- import { existsSync as existsSync15, readFileSync as readFileSync7 } from "node:fs";
183492
+ import { existsSync as existsSync16, readFileSync as readFileSync7 } from "node:fs";
183210
183493
  import { homedir as homedir4 } from "node:os";
183211
183494
  import { dirname as dirname6, isAbsolute as isAbsolute2, resolve } from "node:path";
183212
183495
 
@@ -183341,7 +183624,7 @@ function substituteConfigVariables(input) {
183341
183624
  if (sensitiveReason) {
183342
183625
  warnings.push(`${token} resolves to a sensitive path (${sensitiveReason}: ${filePath}); ` + "inlining its contents into config — make sure this is intentional.");
183343
183626
  }
183344
- if (!existsSync15(filePath)) {
183627
+ if (!existsSync16(filePath)) {
183345
183628
  warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
183346
183629
  continue;
183347
183630
  }
@@ -183370,10 +183653,10 @@ function getUserConfigPaths() {
183370
183653
  return [`${basePath}.jsonc`, `${basePath}.json`];
183371
183654
  }
183372
183655
  function resolveFirstExisting(paths) {
183373
- return paths.find((path11) => existsSync16(path11));
183656
+ return paths.find((path11) => existsSync17(path11));
183374
183657
  }
183375
183658
  function resolvePiLegacyFallback(sources) {
183376
- return sources.find((source) => existsSync16(source.path)) ?? null;
183659
+ return sources.find((source) => existsSync17(source.path)) ?? null;
183377
183660
  }
183378
183661
  function loadConfigFile(path11, scope) {
183379
183662
  try {
@@ -183510,7 +183793,7 @@ function loadPiConfig(opts = {}) {
183510
183793
  if (loaded)
183511
183794
  loadedFiles.push(loaded);
183512
183795
  }
183513
- const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync16(source.path));
183796
+ const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync17(source.path));
183514
183797
  const userPath = resolveFirstExisting(getUserConfigPaths());
183515
183798
  const userLegacyFallback = userPath ? null : resolvePiLegacyFallback(harnessLegacy.user);
183516
183799
  const userReadPath = userPath ?? userLegacyFallback?.path;
@@ -183519,7 +183802,7 @@ function loadPiConfig(opts = {}) {
183519
183802
  if (loaded)
183520
183803
  loadedFiles.push(loaded);
183521
183804
  }
183522
- const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync16(source.path));
183805
+ const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync17(source.path));
183523
183806
  if (userLegacyFallback) {
183524
183807
  warnings.push(`[user config] reading legacy config from ${userLegacyFallback.path} until migration completes; run \`npx @cortexkit/magic-context doctor\` to consolidate into the shared CortexKit location.`);
183525
183808
  } else if (legacyUserUnmigrated) {
@@ -183620,7 +183903,7 @@ function loadPiConfigDetailed(opts = {}) {
183620
183903
  if (loaded)
183621
183904
  loadedFiles.push(loaded);
183622
183905
  }
183623
- const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync16(source.path));
183906
+ const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync17(source.path));
183624
183907
  const userPath = resolveFirstExisting(getUserConfigPaths());
183625
183908
  const userLegacyFallback = userPath ? null : resolvePiLegacyFallback(harnessLegacy.user);
183626
183909
  const userReadPath = userPath ?? userLegacyFallback?.path;
@@ -183629,7 +183912,7 @@ function loadPiConfigDetailed(opts = {}) {
183629
183912
  if (loaded)
183630
183913
  loadedFiles.push(loaded);
183631
183914
  }
183632
- const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync16(source.path));
183915
+ const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync17(source.path));
183633
183916
  if (userLegacyFallback) {
183634
183917
  warnings.push(`[user config] reading legacy config from ${userLegacyFallback.path} until migration completes; run \`npx @cortexkit/magic-context doctor\` to consolidate into the shared CortexKit location.`);
183635
183918
  } else if (legacyUserUnmigrated) {
@@ -185100,7 +185383,8 @@ var canonicalReasons = new Set([
185100
185383
  "first_render",
185101
185384
  "pressure_refold",
185102
185385
  "upgrade_state",
185103
- "cached_m1_missing"
185386
+ "cached_m1_missing",
185387
+ "project_change"
185104
185388
  ]);
185105
185389
  var piReasonAliases = {
185106
185390
  project_memory_change: "project_memory_epoch",
@@ -186379,6 +186663,43 @@ function semanticSnapBoundary(args) {
186379
186663
  }
186380
186664
  return snapped;
186381
186665
  }
186666
+ function snapWrapupBoundaryToUser(args) {
186667
+ const { messages, index, candidate, offset, triggerBudget } = args;
186668
+ if (candidate <= offset)
186669
+ return candidate;
186670
+ const snapTokenLimit = Math.min(Math.max(triggerBudget, 2000), 48000);
186671
+ for (let ordinal = candidate;ordinal >= offset; ordinal -= 1) {
186672
+ const message = messages.find((m) => m.ordinal === ordinal);
186673
+ if (!message)
186674
+ continue;
186675
+ if (message.role !== "user" || !hasMeaningfulUserText(message.parts))
186676
+ continue;
186677
+ const extraTokens = index.rangeTokens(ordinal, candidate);
186678
+ if (extraTokens <= snapTokenLimit)
186679
+ return ordinal;
186680
+ return candidate;
186681
+ }
186682
+ return candidate;
186683
+ }
186684
+ function fenceWrapupBoundaryForToolArcs(args) {
186685
+ let boundary = args.candidate;
186686
+ const maxPasses = args.arcs.length + 1;
186687
+ for (let pass = 0;pass < maxPasses; pass += 1) {
186688
+ let next = boundary;
186689
+ for (const arc of args.arcs) {
186690
+ if (arc.resOrdinal === null) {
186691
+ continue;
186692
+ }
186693
+ if (arc.invOrdinal >= args.lastCompartmentEndOrdinal + 1 && arc.invOrdinal < next && next <= arc.resOrdinal) {
186694
+ next = arc.invOrdinal;
186695
+ }
186696
+ }
186697
+ if (next === boundary)
186698
+ return boundary;
186699
+ boundary = next;
186700
+ }
186701
+ return boundary;
186702
+ }
186382
186703
  function applyHeadCap(args) {
186383
186704
  const { index, protectedTailStart, offset, arcs, capTokens, recentOpenArcCutoff } = args;
186384
186705
  if (offset >= protectedTailStart)
@@ -186634,6 +186955,124 @@ function resolveBoundaryContext(args) {
186634
186955
  function resolveOpenCodeProtectedTailBoundary(args) {
186635
186956
  return resolveProtectedTailBoundary(resolveBoundaryContext(args));
186636
186957
  }
186958
+ function resolveWrapupProtectedTailBoundary(args) {
186959
+ const ctx = resolveBoundaryContext({ ...args, mode: "manual-wrapup" });
186960
+ const createdAt = ctx.createdAt ?? Date.now();
186961
+ const messages = readRawSessionMessages(ctx.sessionId);
186962
+ const absoluteMessageCount = getCachedAbsoluteMessageCount(ctx.sessionId) ?? undefined;
186963
+ const index = buildTrueRawTokenIndex(ctx.sessionId, messages, {
186964
+ providerShapeVersion: ctx.providerShapeVersion,
186965
+ cacheNamespace: ctx.cacheNamespace,
186966
+ absoluteMessageCount,
186967
+ storedTotalForMessage: ctx.storedTokenTotals ? (m) => {
186968
+ const value = ctx.storedTokenTotals?.get(m.id);
186969
+ return value === undefined ? null : value;
186970
+ } : undefined
186971
+ });
186972
+ const rawMessageCount = index.rawMessageCount;
186973
+ const offset = Math.max(1, ctx.lastCompartmentEndOrdinal + 1);
186974
+ const anchorRawMessageCount = Math.max(0, Math.min(rawMessageCount, Math.floor(args.anchorRawMessageCount ?? rawMessageCount)));
186975
+ const usagePercentage = clampPercentage(ctx.usage?.percentage ?? 0);
186976
+ const usageInputTokens = Math.max(0, Math.round(ctx.usage?.inputTokens ?? 0));
186977
+ const rawMessagesAboveLastCompartment = Math.max(0, anchorRawMessageCount - offset + 1);
186978
+ const keep = Math.max(1, Math.floor(args.messagesToKeep));
186979
+ let targetProtectedTailStart = offset;
186980
+ let boundaryReason = "manual-wrapup-empty";
186981
+ if (rawMessageCount === 0 || rawMessagesAboveLastCompartment <= keep) {
186982
+ targetProtectedTailStart = offset;
186983
+ boundaryReason = rawMessageCount === 0 ? "manual-wrapup-empty" : "manual-wrapup-within-keep";
186984
+ } else {
186985
+ targetProtectedTailStart = anchorRawMessageCount - keep + 1;
186986
+ boundaryReason = "manual-wrapup-keep-watermark";
186987
+ const arcs = buildToolArcs(messages);
186988
+ const fenced = fenceWrapupBoundaryForToolArcs({
186989
+ candidate: targetProtectedTailStart,
186990
+ arcs,
186991
+ lastCompartmentEndOrdinal: ctx.lastCompartmentEndOrdinal
186992
+ });
186993
+ if (fenced !== targetProtectedTailStart)
186994
+ boundaryReason = "manual-wrapup-tool-arc";
186995
+ targetProtectedTailStart = fenced;
186996
+ const snapped = snapWrapupBoundaryToUser({
186997
+ messages,
186998
+ index,
186999
+ candidate: targetProtectedTailStart,
187000
+ offset,
187001
+ triggerBudget: ctx.triggerBudget
187002
+ });
187003
+ if (snapped !== targetProtectedTailStart)
187004
+ boundaryReason = "manual-wrapup-user-snap";
187005
+ targetProtectedTailStart = snapped;
187006
+ const refenced = fenceWrapupBoundaryForToolArcs({
187007
+ candidate: targetProtectedTailStart,
187008
+ arcs,
187009
+ lastCompartmentEndOrdinal: ctx.lastCompartmentEndOrdinal
187010
+ });
187011
+ if (refenced !== targetProtectedTailStart)
187012
+ boundaryReason = "manual-wrapup-tool-arc";
187013
+ targetProtectedTailStart = refenced;
187014
+ }
187015
+ targetProtectedTailStart = clampOrdinal(targetProtectedTailStart, rawMessageCount);
187016
+ const target = deriveProtectedTailTokenTarget({
187017
+ contextLimit: ctx.contextLimit,
187018
+ executeThresholdPercentage: ctx.executeThresholdPercentage,
187019
+ usagePercentage,
187020
+ triggerBudget: ctx.triggerBudget
187021
+ });
187022
+ const perRunCap = selectPerRunCap({
187023
+ usagePercentage,
187024
+ N: target.N,
187025
+ contextLimit: ctx.contextLimit,
187026
+ executeThresholdPercentage: ctx.executeThresholdPercentage
187027
+ });
187028
+ const head = applyHeadCap({
187029
+ index,
187030
+ protectedTailStart: targetProtectedTailStart,
187031
+ offset,
187032
+ arcs: buildToolArcs(messages),
187033
+ lastCompartmentEndOrdinal: ctx.lastCompartmentEndOrdinal,
187034
+ capTokens: perRunCap,
187035
+ recentOpenArcCutoff: targetProtectedTailStart
187036
+ });
187037
+ const eligibleEndOrdinal = Math.min(head.eligibleEndOrdinal, targetProtectedTailStart);
187038
+ const rawRangeFingerprint = computeRawRangeFingerprint(messages, offset, eligibleEndOrdinal);
187039
+ const snapshot = {
187040
+ sessionId: ctx.sessionId,
187041
+ mode: "manual-wrapup",
187042
+ offset,
187043
+ offsetMessageId: boundaryMessageId(index, offset),
187044
+ protectedTailStart: targetProtectedTailStart,
187045
+ protectedTailStartMessageId: boundaryMessageId(index, targetProtectedTailStart),
187046
+ eligibleEndOrdinal,
187047
+ eligibleEndMessageId: boundaryMessageId(index, eligibleEndOrdinal - 1),
187048
+ rawMessageCountAtTrigger: rawMessageCount,
187049
+ rawLastMessageIdAtTrigger: boundaryMessageId(index, rawMessageCount),
187050
+ N: keep,
187051
+ usagePercentage,
187052
+ usageInputTokens,
187053
+ usageSource: ctx.usageSource,
187054
+ contextLimit: ctx.contextLimit,
187055
+ executeThresholdPercentage: ctx.executeThresholdPercentage,
187056
+ triggerBudget: ctx.triggerBudget,
187057
+ priorBoundaryOrdinal: ctx.priorBoundaryOrdinal,
187058
+ migrationFloorActive: ctx.migrationFloorActive,
187059
+ emergencyTailScale: ctx.emergencyTailScale,
187060
+ providerShapeVersion: ctx.providerShapeVersion,
187061
+ cacheNamespace: ctx.cacheNamespace,
187062
+ createdAt,
187063
+ rawRangeFingerprint,
187064
+ trueRawEligibleTokens: index.rangeTokens(offset, targetProtectedTailStart),
187065
+ oversizeAtomicUnit: head.oversizeAtomicUnit,
187066
+ boundaryReason
187067
+ };
187068
+ return {
187069
+ snapshot,
187070
+ rawMessagesAboveLastCompartment,
187071
+ anchorRawMessageCount,
187072
+ targetProtectedTailStart,
187073
+ targetEligibleEndOrdinal: targetProtectedTailStart
187074
+ };
187075
+ }
186637
187076
  function getRawHistoryEligibility(db, sessionId) {
186638
187077
  const lastCompartmentEnd = getLastCompartmentEndMessage(db, sessionId);
186639
187078
  const offset = Math.max(1, lastCompartmentEnd + 1);
@@ -188679,6 +189118,147 @@ function searchMessages(args) {
188679
189118
  role: entry.row.role
188680
189119
  }));
188681
189120
  }
189121
+ var NOTE_SEARCHABLE_STATUSES = ["active", "pending", "ready", "dismissed"];
189122
+ function noteSearchText(note) {
189123
+ const reason = note.readyReason?.trim();
189124
+ return reason ? `${note.content}
189125
+ Reason: ${reason}` : note.content;
189126
+ }
189127
+ function tokenizeKeywordNeedle(text) {
189128
+ const matches = text.toLowerCase().match(/[a-z0-9/._:-]+/g) ?? [];
189129
+ const seen = new Set;
189130
+ const tokens = [];
189131
+ for (const match of matches) {
189132
+ if (match.length <= 1 || !/[a-z0-9]/.test(match) || seen.has(match)) {
189133
+ continue;
189134
+ }
189135
+ seen.add(match);
189136
+ tokens.push(match);
189137
+ }
189138
+ return tokens;
189139
+ }
189140
+ function rankNotesForNeedle(notes, needle) {
189141
+ const normalizedNeedle = needle.trim().toLowerCase();
189142
+ if (normalizedNeedle.length === 0) {
189143
+ return [];
189144
+ }
189145
+ const needleTokens = tokenizeKeywordNeedle(normalizedNeedle);
189146
+ const ranked = [];
189147
+ for (const note of notes) {
189148
+ const text = noteSearchText(note);
189149
+ const normalizedText = text.toLowerCase();
189150
+ const noteTokens = new Set(tokenizeKeywordNeedle(normalizedText));
189151
+ const exact = normalizedText.includes(normalizedNeedle);
189152
+ const matchedTokens = needleTokens.filter((token) => noteTokens.has(token)).length;
189153
+ if (!exact && matchedTokens === 0) {
189154
+ continue;
189155
+ }
189156
+ const coverage = needleTokens.length > 0 ? matchedTokens / needleTokens.length : 0;
189157
+ const score = (exact ? 2 : 0) + coverage + (needleTokens.length > 1 && matchedTokens === needleTokens.length ? 0.5 : 0);
189158
+ ranked.push({ note, score, text });
189159
+ }
189160
+ return ranked.sort((left, right) => {
189161
+ if (right.score !== left.score) {
189162
+ return right.score - left.score;
189163
+ }
189164
+ if (right.note.createdAt !== left.note.createdAt) {
189165
+ return right.note.createdAt - left.note.createdAt;
189166
+ }
189167
+ return left.note.id - right.note.id;
189168
+ });
189169
+ }
189170
+ function searchNotes(args) {
189171
+ if (args.limit <= 0) {
189172
+ return [];
189173
+ }
189174
+ const notes = [
189175
+ ...getNotes(args.db, {
189176
+ sessionId: args.sessionId,
189177
+ type: "session",
189178
+ status: NOTE_SEARCHABLE_STATUSES
189179
+ }),
189180
+ ...getNotes(args.db, {
189181
+ projectPath: args.projectPath,
189182
+ type: "smart",
189183
+ status: NOTE_SEARCHABLE_STATUSES
189184
+ })
189185
+ ];
189186
+ if (notes.length === 0) {
189187
+ return [];
189188
+ }
189189
+ const baseList = rankNotesForNeedle(notes, args.query);
189190
+ const probes = args.probes ?? [];
189191
+ if (probes.length === 0) {
189192
+ const ranked2 = baseList.slice(0, args.limit);
189193
+ return ranked2.map((entry, rank) => ({
189194
+ source: "note",
189195
+ content: previewText(entry.text),
189196
+ score: linearDecayScore(rank, ranked2.length),
189197
+ noteId: entry.note.id,
189198
+ status: entry.note.status,
189199
+ createdAt: entry.note.createdAt,
189200
+ anchorOrdinal: entry.note.anchorOrdinal,
189201
+ sourceSessionId: entry.note.sessionId
189202
+ }));
189203
+ }
189204
+ const queryLists = [];
189205
+ if (baseList.length > 0) {
189206
+ queryLists.push({ rows: baseList, weight: 1 });
189207
+ }
189208
+ const probeWeights = new Map;
189209
+ for (const probe of probes) {
189210
+ const rows = rankNotesForNeedle(notes, probe);
189211
+ if (rows.length === 0) {
189212
+ continue;
189213
+ }
189214
+ const weight = probeDiscriminationWeight(rows.length, notes.length);
189215
+ probeWeights.set(probe, weight);
189216
+ queryLists.push({ rows, weight });
189217
+ }
189218
+ const fused = new Map;
189219
+ for (const list of queryLists) {
189220
+ list.rows.forEach((row, rank) => {
189221
+ const rrf = list.weight / (RRF_K + rank);
189222
+ const existing = fused.get(row.note.id);
189223
+ if (existing) {
189224
+ existing.score += rrf;
189225
+ } else {
189226
+ fused.set(row.note.id, { entry: row, score: rrf });
189227
+ }
189228
+ });
189229
+ }
189230
+ for (const match of fused.values()) {
189231
+ let best = 0;
189232
+ for (const probe of probes) {
189233
+ const weight = probeWeights.get(probe) ?? 0;
189234
+ if (weight > best && containsProbeVerbatim(match.entry.text, [probe])) {
189235
+ best = weight;
189236
+ }
189237
+ }
189238
+ if (best > 0) {
189239
+ match.score += best * VERBATIM_RANK_BONUS;
189240
+ }
189241
+ }
189242
+ const ranked = [...fused.values()].sort((left, right) => {
189243
+ if (right.score !== left.score) {
189244
+ return right.score - left.score;
189245
+ }
189246
+ if (right.entry.note.createdAt !== left.entry.note.createdAt) {
189247
+ return right.entry.note.createdAt - left.entry.note.createdAt;
189248
+ }
189249
+ return left.entry.note.id - right.entry.note.id;
189250
+ }).slice(0, args.limit);
189251
+ return ranked.map((entry, rank) => ({
189252
+ source: "note",
189253
+ content: previewText(entry.entry.text),
189254
+ score: linearDecayScore(rank, ranked.length),
189255
+ noteId: entry.entry.note.id,
189256
+ status: entry.entry.note.status,
189257
+ createdAt: entry.entry.note.createdAt,
189258
+ anchorOrdinal: entry.entry.note.anchorOrdinal,
189259
+ sourceSessionId: entry.entry.note.sessionId
189260
+ }));
189261
+ }
188682
189262
  function searchCompartmentChunks(args) {
188683
189263
  if (!args.queryEmbedding || args.limit <= 0 || !args.modelId || args.modelId === "off")
188684
189264
  return [];
@@ -188764,6 +189344,8 @@ function getSourceBoost(result) {
188764
189344
  return GIT_COMMIT_SOURCE_BOOST;
188765
189345
  case "primer":
188766
189346
  return PRIMER_SOURCE_BOOST;
189347
+ case "note":
189348
+ return 1;
188767
189349
  }
188768
189350
  }
188769
189351
  function compareUnifiedResults(left, right) {
@@ -188787,6 +189369,9 @@ function compareUnifiedResults(left, right) {
188787
189369
  if (left.source === "primer" && right.source === "primer") {
188788
189370
  return right.support - left.support || left.primerId - right.primerId;
188789
189371
  }
189372
+ if (left.source === "note" && right.source === "note") {
189373
+ return right.createdAt - left.createdAt || left.noteId - right.noteId;
189374
+ }
188790
189375
  return 0;
188791
189376
  }
188792
189377
  function toGitCommitResult(hit) {
@@ -188854,11 +189439,11 @@ function searchPrimers(args) {
188854
189439
  }
188855
189440
  function resolveSources(sources) {
188856
189441
  if (sources === undefined) {
188857
- return new Set(["memory", "message", "git_commit", "primer"]);
189442
+ return new Set(["memory", "message", "git_commit", "primer", "note"]);
188858
189443
  }
188859
189444
  const set2 = new Set;
188860
189445
  for (const source of sources) {
188861
- if (source === "memory" || source === "message" || source === "git_commit" || source === "primer") {
189446
+ if (source === "memory" || source === "message" || source === "git_commit" || source === "primer" || source === "note") {
188862
189447
  set2.add(source);
188863
189448
  }
188864
189449
  }
@@ -188881,6 +189466,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
188881
189466
  const runMessages = activeSources.has("message");
188882
189467
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
188883
189468
  const runPrimers = activeSources.has("primer") && memoryFeatureEnabled;
189469
+ const runNotes = activeSources.has("note");
188884
189470
  const runCompartmentChunks = runMessages && memoryFeatureEnabled && embeddingEnabled;
188885
189471
  const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks || runPrimers) && embeddingEnabled && isEmbeddingRuntimeEnabled();
188886
189472
  const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options.signal).catch((error51) => {
@@ -188916,7 +189502,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
188916
189502
  compartments: compartmentResults,
188917
189503
  limit: tierLimit
188918
189504
  });
188919
- const [memoryResults, gitCommitResults, primerResults] = await Promise.all([
189505
+ const [memoryResults, gitCommitResults, primerResults, noteResults] = await Promise.all([
188920
189506
  runMemory ? searchMemories({
188921
189507
  db,
188922
189508
  projectPath,
@@ -188943,9 +189529,23 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
188943
189529
  limit: tierLimit,
188944
189530
  queryEmbedding,
188945
189531
  queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null
189532
+ })) : Promise.resolve([]),
189533
+ runNotes ? Promise.resolve(searchNotes({
189534
+ db,
189535
+ sessionId,
189536
+ projectPath,
189537
+ query: trimmedQuery,
189538
+ limit: tierLimit,
189539
+ probes: messageProbes
188946
189540
  })) : Promise.resolve([])
188947
189541
  ]);
188948
- const results = [...memoryResults, ...primerResults, ...messageLikeResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
189542
+ const results = [
189543
+ ...memoryResults,
189544
+ ...primerResults,
189545
+ ...messageLikeResults,
189546
+ ...gitCommitResults,
189547
+ ...noteResults
189548
+ ].sort(compareUnifiedResults).slice(0, limit);
188949
189549
  const countRetrievals = options.countRetrievals ?? true;
188950
189550
  if (countRetrievals) {
188951
189551
  const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
@@ -189014,6 +189614,10 @@ function renderFragment(result, charCap) {
189014
189614
  const compressed = cavemanCompress(result.content, "ultra");
189015
189615
  return truncate(compressed, charCap);
189016
189616
  }
189617
+ case "note": {
189618
+ const compressed = cavemanCompress(result.content, "ultra");
189619
+ return truncate(compressed, charCap);
189620
+ }
189017
189621
  }
189018
189622
  }
189019
189623
  function buildAutoSearchHint(results, options = {}) {
@@ -189235,13 +189839,11 @@ async function runAutoSearchHintForPi(args) {
189235
189839
  };
189236
189840
  results = await unifiedSearchWithTimeout(db, sessionId, options.projectPath, rawPrompt, searchOptions, AUTO_SEARCH_TIMEOUT_MS);
189237
189841
  } catch (error51) {
189238
- log(`[auto-search] unified search failed for session ${sessionId}: ${error51 instanceof Error ? error51.message : String(error51)}`);
189239
- writeNoHintAndReconcile("error");
189842
+ log(`[auto-search] unified search failed for session ${sessionId} (will retry next pass): ${error51 instanceof Error ? error51.message : String(error51)}`);
189240
189843
  return messages;
189241
189844
  }
189242
189845
  if (results === null) {
189243
- sessionLog(sessionId, `auto-search: timed out after ${AUTO_SEARCH_TIMEOUT_MS}ms, skipping hint for this turn`);
189244
- writeNoHintAndReconcile("timeout");
189846
+ sessionLog(sessionId, `auto-search: timed out after ${AUTO_SEARCH_TIMEOUT_MS}ms, skipping hint for this turn (will retry)`);
189245
189847
  return messages;
189246
189848
  }
189247
189849
  if (results.length === 0) {
@@ -190816,6 +191418,15 @@ function findCompartmentBoundaryForSnapshot(markers) {
190816
191418
  return null;
190817
191419
  return markers.lastBaselineEndMessageId;
190818
191420
  }
191421
+ function resolveRenderedCompartmentBoundary(compartments, boundaryId) {
191422
+ if (!boundaryId)
191423
+ return { endMessageId: null, ordinal: null };
191424
+ const boundary = compartments.find((compartment) => compartment.endMessageId === boundaryId);
191425
+ return {
191426
+ endMessageId: boundaryId,
191427
+ ordinal: typeof boundary?.endMessage === "number" ? boundary.endMessage : null
191428
+ };
191429
+ }
190819
191430
  function prependM0M1Messages(piMessages, m0, m1) {
190820
191431
  const firstTimestamp = piMessages[0]?.timestamp;
190821
191432
  const baseTimestamp = typeof firstTimestamp === "number" ? firstTimestamp : Date.now();
@@ -190945,6 +191556,7 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
190945
191556
  }
190946
191557
  }
190947
191558
  const boundaryId = findCompartmentBoundaryForSnapshot(markers);
191559
+ const renderedBoundary = resolveRenderedCompartmentBoundary(currentCompartments, boundaryId);
190948
191560
  const skippedVisibleMessages = boundaryId ? trimPiMessagesToBoundary(piMessages, entryIds, boundaryId) : 0;
190949
191561
  prependM0M1Messages(piMessages, m0, m1);
190950
191562
  sessionLog(state.sessionId, `injected m[0]/m[1] into Pi messages (${m0.length} + ${m1.length} bytes, materialized=${materialized}${decision.reason ? ` reason=${decision.reason}` : ""})`);
@@ -190961,6 +191573,8 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
190961
191573
  m0Reason: decision.reason,
190962
191574
  m0Bytes: m0.length,
190963
191575
  m1Bytes: m1.length,
191576
+ contentionExhausted,
191577
+ renderedBoundary,
190964
191578
  syntheticLeadingCount: 2
190965
191579
  };
190966
191580
  }
@@ -194232,7 +194846,9 @@ async function runPiHistorian(deps) {
194232
194846
  compartmentLeaseHolderId,
194233
194847
  readBranchEntries,
194234
194848
  notifyIssue,
194235
- ensureProjectRegistered = ensureProjectRegisteredFromPiDirectory
194849
+ ensureProjectRegistered = ensureProjectRegisteredFromPiDirectory,
194850
+ forceDrainQuota,
194851
+ forceKeepLastCompartment
194236
194852
  } = deps;
194237
194853
  const notify = async (message) => {
194238
194854
  if (shouldSuppressHistorianAlert(sessionId)) {
@@ -194307,7 +194923,8 @@ async function runPiHistorian(deps) {
194307
194923
  if (protectedTailStart <= offset || eligibleEndOrdinal <= offset) {
194308
194924
  sessionLog(sessionId, `historian no-op: protectedTailStart=${protectedTailStart} eligibleEnd=${eligibleEndOrdinal} <= offset=${offset} — nothing to compact`);
194309
194925
  if (boundarySnapshot.usagePercentage < 80) {
194310
- clearEmergencyRecovery(db, sessionId);
194926
+ if (!isWrapupInProgress(db, sessionId))
194927
+ clearEmergencyRecovery(db, sessionId);
194311
194928
  } else {
194312
194929
  recordHighPressureNoEligibleHead(db, boundarySnapshot);
194313
194930
  }
@@ -194316,7 +194933,7 @@ async function runPiHistorian(deps) {
194316
194933
  }
194317
194934
  const perRunCap = selectPerRunCap(boundarySnapshot);
194318
194935
  const usable = Math.max(1, Math.round(boundarySnapshot.contextLimit * boundarySnapshot.executeThresholdPercentage / 100));
194319
- const reserve = reserveProtectedTailDrainTokens({
194936
+ const reserve = forceDrainQuota ? { ok: true, reservation: null } : reserveProtectedTailDrainTokens({
194320
194937
  db,
194321
194938
  sessionId,
194322
194939
  runId: crypto2.randomUUID(),
@@ -194334,10 +194951,12 @@ async function runPiHistorian(deps) {
194334
194951
  }
194335
194952
  drainReservation = reserve.reservation;
194336
194953
  const chunk = readSessionChunk(sessionId, historianChunkTokens, offset, eligibleEndOrdinal);
194954
+ const forceKeepLastCompartmentForChunk = forceKeepLastCompartment === true && !chunk.hasMore;
194337
194955
  if (!chunk.text || chunk.messageCount === 0) {
194338
194956
  sessionLog(sessionId, `historian no-op: chunk empty after filtering (messageCount=${chunk.messageCount}, textLen=${chunk.text?.length ?? 0}) range=${offset}-${protectedTailStart - 1}`);
194339
194957
  if (boundarySnapshot.usagePercentage < 80) {
194340
- clearEmergencyRecovery(db, sessionId);
194958
+ if (!isWrapupInProgress(db, sessionId))
194959
+ clearEmergencyRecovery(db, sessionId);
194341
194960
  } else {
194342
194961
  recordHighPressureNoEligibleHead(db, boundarySnapshot);
194343
194962
  }
@@ -194527,7 +195146,7 @@ ${chunkText}`,
194527
195146
  const inEmergency = getOverflowState(db, sessionId).needsEmergencyRecovery;
194528
195147
  const emittedCompartments = validatedPass.compartments;
194529
195148
  let newCompartments = emittedCompartments;
194530
- if (!inEmergency && emittedCompartments.length >= 2) {
195149
+ if (!inEmergency && !forceKeepLastCompartmentForChunk && emittedCompartments.length >= 2) {
194531
195150
  const lastEmitted = emittedCompartments[emittedCompartments.length - 1];
194532
195151
  const lookaheadMargin = chunk.endIndex - lastEmitted.endMessage;
194533
195152
  if (lookaheadMargin <= BOUNDARY_HEALING_SLACK) {
@@ -194560,9 +195179,19 @@ ${chunkText}`,
194560
195179
  }
194561
195180
  }
194562
195181
  const discardedLast = newCompartments.length < emittedCompartments.length;
195182
+ const weakLookaheadFinalCompartment = forceKeepLastCompartmentForChunk;
195183
+ const skipUnanchoredPromotion = discardedLast || weakLookaheadFinalCompartment;
194563
195184
  const embeddingActive = memoryEnabled !== false;
194564
195185
  const promotionActive = embeddingActive && autoPromote !== false;
194565
- const publishableEvents = (validatedPass.events ?? []).filter((e) => e.atCompartment == null || e.atCompartment <= newCompartments.length);
195186
+ const publishableEvents = (validatedPass.events ?? []).filter((e) => {
195187
+ if (typeof e.atCompartment !== "number")
195188
+ return !weakLookaheadFinalCompartment;
195189
+ if (e.atCompartment > newCompartments.length)
195190
+ return false;
195191
+ if (weakLookaheadFinalCompartment && e.atCompartment >= emittedCompartments.length)
195192
+ return false;
195193
+ return true;
195194
+ });
194566
195195
  let promotedFactRefs = [];
194567
195196
  let persistedIds = [];
194568
195197
  if (!compartmentLeaseHolderId) {
@@ -194581,7 +195210,7 @@ ${chunkText}`,
194581
195210
  }
194582
195211
  appendCompartments(db, sessionId, newCompartments);
194583
195212
  persistedIds = getCompartments(db, sessionId).slice(-newCompartments.length).map((c) => c.id);
194584
- if (promotionActive && !discardedLast) {
195213
+ if (promotionActive && !skipUnanchoredPromotion) {
194585
195214
  promotedFactRefs = promoteSessionFactsDurable(db, sessionId, projectPath, validatedPass.facts ?? []);
194586
195215
  }
194587
195216
  if (publishableEvents.length > 0) {
@@ -194596,7 +195225,8 @@ ${chunkText}`,
194596
195225
  clearHistorianFailureState(db, sessionId);
194597
195226
  clearHistorianDrainFailure(db, sessionId);
194598
195227
  recordProtectedTailPublicationFloor(db, sessionId, lastNewEnd + 1);
194599
- clearEmergencyRecovery(db, sessionId);
195228
+ if (!isWrapupInProgress(db, sessionId))
195229
+ clearEmergencyRecovery(db, sessionId);
194600
195230
  if (firstKeptEntryId && lastNewEndMessageId) {
194601
195231
  setPendingPiCompactionMarkerState(db, sessionId, {
194602
195232
  firstKeptEntryId,
@@ -194620,7 +195250,7 @@ ${chunkText}`,
194620
195250
  completedSuccessfully = true;
194621
195251
  sessionLog(sessionId, `historian: published ${newCompartments.length} compartment(s), ${validatedPass.facts?.length ?? 0} fact(s) covering messages ${chunk.startIndex}-${lastNewEnd}`);
194622
195252
  onNoteTrigger(db, sessionId, "historian_complete");
194623
- if (userMemoriesEnabled === true && !discardedLast && validatedPass.userObservations?.length) {
195253
+ if (userMemoriesEnabled === true && !skipUnanchoredPromotion && validatedPass.userObservations?.length) {
194624
195254
  try {
194625
195255
  insertUserMemoryCandidates(db, validatedPass.userObservations.map((obs) => ({
194626
195256
  content: obs,
@@ -194633,7 +195263,7 @@ ${chunkText}`,
194633
195263
  sessionLog(sessionId, "failed to store user memory candidates:", error51);
194634
195264
  }
194635
195265
  }
194636
- if (!discardedLast && validatedPass.primerCandidates?.length && projectPath) {
195266
+ if (!skipUnanchoredPromotion && validatedPass.primerCandidates?.length && projectPath) {
194637
195267
  try {
194638
195268
  const firstNew = newCompartments[0];
194639
195269
  const lastNew = newCompartments[newCompartments.length - 1];
@@ -195330,7 +195960,7 @@ Drop silently — do not narrate it. NEVER drop large ranges blindly (e.g., "1-5
195330
195960
  Older tool calls may show \`[dropped §N§]\` sentinels; that is normal context management, not a pattern to copy. ALWAYS make fresh real tool calls when you need data again; never fabricate or inline tool output.`;
195331
195961
  var CAVEMAN_COMPRESSION_WARNING = `
195332
195962
  **BEWARE**: History compression is on; older user AND assistant text — including your own earlier responses — has been deterministically rewritten in a terse caveman style (dropped articles, missing auxiliaries, \`//\` instead of connectives like \`because\`). This is automatic context compression that runs after the fact, not your actual prior wording or the user's. **DO NOT mimic this style in new turns.** Write fresh responses in normal prose. If you notice your output drifting into caveman cadence, that drift is in-context-learning bleeding from the compressed history — consciously revert to full sentences.`;
195333
- function buildMagicContextSection(_agent, protectedTags, ctxReduceEnabled = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false, language, memoryEnabled = true) {
195963
+ function buildMagicContextSection(_agent, protectedTags, ctxReduceCallable = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false, language, memoryEnabled = true) {
195334
195964
  if (subagentMode) {
195335
195965
  return `## Magic Context
195336
195966
 
@@ -195341,12 +195971,12 @@ When \`surface_condition\` is provided with \`write\`, the note becomes a projec
195341
195971
  The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met.
195342
195972
  Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")\`` : "";
195343
195973
  const temporalGuidance = temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : "";
195344
- const cavemanWarning = cavemanTextCompressionEnabled && !ctxReduceEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
195974
+ const cavemanWarning = cavemanTextCompressionEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
195345
195975
  const languageDirective = buildPrimaryLanguageDirective(language);
195346
195976
  const languageGuidance = languageDirective ? `
195347
195977
 
195348
195978
  ${languageDirective}` : "";
195349
- if (!ctxReduceEnabled) {
195979
+ if (!ctxReduceCallable) {
195350
195980
  return `## Magic Context
195351
195981
 
195352
195982
  ${LONG_TERM_PARTNER_FRAME}
@@ -195359,7 +195989,7 @@ ${BASE_INTRO_NO_REDUCE(memoryEnabled)}${smartNoteGuidance}${temporalGuidance}${c
195359
195989
  ${LONG_TERM_PARTNER_FRAME}
195360
195990
  ${PARTNER_FRAME_CLOSER_REDUCE}
195361
195991
 
195362
- ${BASE_INTRO(protectedTags, memoryEnabled)}${smartNoteGuidance}${temporalGuidance}
195992
+ ${BASE_INTRO(protectedTags, memoryEnabled)}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}
195363
195993
  ${GENERIC_SECTION}
195364
195994
 
195365
195995
  Prefer many small targeted operations over one large blanket operation, and keep the working set tidy as routine maintenance.${languageGuidance}`;
@@ -195374,7 +196004,7 @@ function buildMagicContextBlock(opts) {
195374
196004
  const includeGuidance = (opts.includeGuidance ?? true) && !existing.includes(MAGIC_CONTEXT_MARKER);
195375
196005
  if (!includeGuidance)
195376
196006
  return null;
195377
- return buildMagicContextSection(null, opts.protectedTags ?? 20, opts.ctxReduceEnabled ?? true, opts.dreamerEnabled ?? false, opts.temporalAwarenessEnabled ?? false, opts.cavemanTextCompressionEnabled ?? false, false, opts.language, opts.memoryEnabled !== false);
196007
+ return buildMagicContextSection(null, opts.protectedTags ?? 20, opts.ctxReduceCallable ?? true, opts.dreamerEnabled ?? false, opts.temporalAwarenessEnabled ?? false, opts.cavemanTextCompressionEnabled ?? false, false, opts.language, opts.memoryEnabled !== false);
195378
196008
  }
195379
196009
  var DATE_PATTERN = /Today's date: .+/;
195380
196010
  function processSystemPromptForCache(args) {
@@ -196067,6 +196697,7 @@ function applyForwardPressureFloor(trailingPercentage, trailingInputTokens, piUs
196067
196697
  inputTokens: Math.max(trailingInputTokens, forwardTokens)
196068
196698
  } : { percentage: trailingPercentage, inputTokens: trailingInputTokens };
196069
196699
  }
196700
+ var injectM0M1PiForRun = injectM0M1Pi;
196070
196701
  var DEFAULT_CLEAR_REASONING_AGE = 50;
196071
196702
  var PI_STABLE_ID_SCHEME = 1;
196072
196703
  var lastEmergencyNotificationAtMs = new Map;
@@ -196766,7 +197397,6 @@ function registerPiContextHandler(pi, baseOptions) {
196766
197397
  projectIdentity,
196767
197398
  projectDirectory,
196768
197399
  messages: event.messages,
196769
- ctxReduceEnabled: options.ctxReduceEnabled,
196770
197400
  smartDrops: options.smartDrops === true,
196771
197401
  protectedTags: options.protectedTags ?? 20,
196772
197402
  heuristics: options.heuristics,
@@ -196799,7 +197429,8 @@ function registerPiContextHandler(pi, baseOptions) {
196799
197429
  },
196800
197430
  temporalAwareness: options.injection?.temporalAwareness === true,
196801
197431
  appendCompaction: resolvePiAppendCompaction(ctx),
196802
- readBranchEntries: resolvePiReadBranchEntries(ctx)
197432
+ readBranchEntries: resolvePiReadBranchEntries(ctx),
197433
+ isSubagent: sessionMeta.isSubagent
196803
197434
  });
196804
197435
  const piDecisionSnapshotNewestAssistant = branchEntries === null ? undefined : findNewestPiAssistantEntryId(branchEntries);
196805
197436
  if (result.bustedThisPass && piDecisionSnapshotNewestAssistant !== undefined) {
@@ -196881,7 +197512,7 @@ function registerPiContextHandler(pi, baseOptions) {
196881
197512
  }
196882
197513
  try {
196883
197514
  const sessionMetaForCh1 = getOrCreateSessionMeta(options.db, sessionId);
196884
- if (options.ctxReduceEnabled && !sessionMetaForCh1.isSubagent) {
197515
+ if (!sessionMetaForCh1.isSubagent) {
196885
197516
  const resolvedExecuteThresholdPct = resolveExecuteThreshold(schedulerConfig.executeThresholdPercentage ?? 65, liveModelBySession.get(sessionId), 65, {
196886
197517
  tokensConfig: schedulerConfig.executeThresholdTokens,
196887
197518
  contextLimit: usageContextLimit ?? 0
@@ -197009,8 +197640,12 @@ function resolveHistoryBudgetTokensForPi(args) {
197009
197640
  }
197010
197641
  function startPiCompartmentLeaseRenewal(db, sessionId, holderId) {
197011
197642
  return setInterval(() => {
197012
- if (!renewCompartmentLease(db, sessionId, holderId)) {
197013
- sessionLog(sessionId, "compartment lease renewal failed; publish will be skipped if holder is stale");
197643
+ try {
197644
+ if (!renewCompartmentLease(db, sessionId, holderId)) {
197645
+ sessionLog(sessionId, "compartment lease renewal failed; publish will be skipped if holder is stale");
197646
+ }
197647
+ } catch (err) {
197648
+ sessionLog(sessionId, `compartment lease renewal threw; publish will be skipped if holder is stale (${err instanceof Error ? err.message : String(err)})`);
197014
197649
  }
197015
197650
  }, COMPARTMENT_LEASE_RENEWAL_MS);
197016
197651
  }
@@ -197070,6 +197705,11 @@ function spawnPiHistorianRun(args) {
197070
197705
  sessionLog(sessionId, "historian skipped: compartment lease held by another process");
197071
197706
  return;
197072
197707
  }
197708
+ if (isWrapupInProgress(db, sessionId)) {
197709
+ sessionLog(sessionId, "historian skipped: /ctx-wrapup became active");
197710
+ releaseCompartmentLease(db, sessionId, holderId);
197711
+ return;
197712
+ }
197073
197713
  const renewal = startPiCompartmentLeaseRenewal(db, sessionId, holderId);
197074
197714
  try {
197075
197715
  await runPiHistorian({
@@ -197148,6 +197788,10 @@ function maybeFireHistorian(args) {
197148
197788
  sessionLog(sessionId, "historian trigger eval: in-flight, skipping");
197149
197789
  return;
197150
197790
  }
197791
+ if (isWrapupInProgress(db, sessionId)) {
197792
+ sessionLog(sessionId, "historian trigger eval: /ctx-wrapup active, skipping");
197793
+ return;
197794
+ }
197151
197795
  let usage;
197152
197796
  let usageContextLimit;
197153
197797
  try {
@@ -197290,6 +197934,14 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
197290
197934
  unregister();
197291
197935
  }
197292
197936
  }
197937
+ function pendingPiMarkerCoveredByRenderedBoundary(pending, injection) {
197938
+ if (!injection || injection.contentionExhausted)
197939
+ return false;
197940
+ const boundary = injection.renderedBoundary;
197941
+ if (pending.endMessageId === boundary.endMessageId)
197942
+ return true;
197943
+ return boundary.ordinal !== null && pending.ordinal <= boundary.ordinal;
197944
+ }
197293
197945
  async function runPipeline(args) {
197294
197946
  let executedWorkThisPass = false;
197295
197947
  let historyWasConsumedThisPass = false;
@@ -197303,6 +197955,7 @@ async function runPipeline(args) {
197303
197955
  let emergency = false;
197304
197956
  let autoReclaimDidMutateThisPass = false;
197305
197957
  let suppressDeferredHistoryDrain = false;
197958
+ let deferredMaterializationConsumedThisPass = false;
197306
197959
  let casLost = false;
197307
197960
  const deferredHistoryWasPendingAtPassStart = deferredHistoryRefreshSessions.has(args.sessionId);
197308
197961
  if (args.temporalAwareness) {
@@ -197325,6 +197978,8 @@ async function runPipeline(args) {
197325
197978
  return findLatestUserMessageIdPi(args.messages, ids)?.messageId ?? null;
197326
197979
  })();
197327
197980
  const alreadyRanHeuristicsThisTurn = currentTurnId !== null && lastHeuristicsTurnIdBySession.get(args.sessionId) === currentTurnId;
197981
+ const sessionMetaForAvailability = getOrCreateSessionMeta(args.db, args.sessionId);
197982
+ const ctxReduceCallable = !sessionMetaForAvailability.isSubagent;
197328
197983
  const canConsumeDeferredLate = args.schedulerDecision === "execute" || args.forceMaterialization === true || args.contextUsage.percentage >= FORCE_MATERIALIZATION_PERCENTAGE;
197329
197984
  const deferredMaterializeEligible = canConsumeDeferredLate && deferredMaterializationSessions.has(args.sessionId);
197330
197985
  const piHardSignals = args.injection ? (() => {
@@ -197365,7 +198020,7 @@ async function runPipeline(args) {
197365
198020
  }
197366
198021
  const tTag = performance.now();
197367
198022
  const { targets } = tagTranscript(args.sessionId, transcript, args.tagger, args.db, {
197368
- skipPrefixInjection: !args.ctxReduceEnabled,
198023
+ skipPrefixInjection: !ctxReduceCallable,
197369
198024
  entryFingerprintByMessageId
197370
198025
  });
197371
198026
  logTransformTiming(args.sessionId, "tagMessages", tTag);
@@ -197451,14 +198106,16 @@ async function runPipeline(args) {
197451
198106
  sessionLog(args.sessionId, `reasoning replay failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
197452
198107
  }
197453
198108
  }
197454
- try {
197455
- const tags = getTagsByNumbers(args.db, args.sessionId, targetTagNumbers);
197456
- const replayed = replayCavemanCompression(args.sessionId, args.db, targets, tags);
197457
- if (replayed > 0) {
197458
- sessionLog(args.sessionId, `caveman replay: ${replayed} tags re-compressed from source`);
198109
+ if (args.heuristics?.caveman?.enabled && !args.isSubagent) {
198110
+ try {
198111
+ const tags = getTagsByNumbers(args.db, args.sessionId, targetTagNumbers);
198112
+ const replayed = replayCavemanCompression(args.sessionId, args.db, targets, tags);
198113
+ if (replayed > 0) {
198114
+ sessionLog(args.sessionId, `caveman replay: ${replayed} tags re-compressed from source`);
198115
+ }
198116
+ } catch (err) {
198117
+ sessionLog(args.sessionId, `caveman replay failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
197459
198118
  }
197460
- } catch (err) {
197461
- sessionLog(args.sessionId, `caveman replay failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
197462
198119
  }
197463
198120
  let heuristicsExecuted = false;
197464
198121
  let heuristicsResult = null;
@@ -197481,7 +198138,7 @@ async function runPipeline(args) {
197481
198138
  currentTotalInputTokens: args.contextUsage.inputTokens,
197482
198139
  ceilingTokens: args.emergencyCeilingTokens
197483
198140
  } : undefined,
197484
- caveman: args.heuristics.caveman
198141
+ caveman: args.isSubagent ? undefined : args.heuristics.caveman
197485
198142
  }, activeTags, stableIdResolver);
197486
198143
  const heuristicMutationCount = heuristicsResult.droppedTools + heuristicsResult.deduplicatedTools + heuristicsResult.droppedInjections + heuristicsResult.droppedStaleReduceCalls + heuristicsResult.mutatedTextTags;
197487
198144
  droppedCount += heuristicsResult.droppedTools + heuristicsResult.deduplicatedTools + heuristicsResult.droppedInjections + heuristicsResult.droppedStaleReduceCalls + heuristicsResult.mutatedTextTags;
@@ -197504,7 +198161,7 @@ async function runPipeline(args) {
197504
198161
  if (deferredMaterialize && pendingOpsAppliedThisPass) {
197505
198162
  const fullPassSucceeded = shouldRunHeuristics ? heuristicsExecuted : true;
197506
198163
  if (fullPassSucceeded) {
197507
- consumeDeferredMaterialization(args.sessionId);
198164
+ deferredMaterializationConsumedThisPass = consumeDeferredMaterialization(args.sessionId);
197508
198165
  }
197509
198166
  }
197510
198167
  if (args.reasoningClearing && shouldRunHeuristics) {
@@ -197621,7 +198278,7 @@ async function runPipeline(args) {
197621
198278
  if (args.injection) {
197622
198279
  try {
197623
198280
  const tInjection = performance.now();
197624
- injectionResult = injectM0M1Pi({
198281
+ injectionResult = injectM0M1PiForRun({
197625
198282
  sessionId: args.sessionId,
197626
198283
  projectIdentity: args.projectIdentity,
197627
198284
  projectDirectory: args.projectDirectory,
@@ -197653,11 +198310,23 @@ async function runPipeline(args) {
197653
198310
  forceDiscovery: args.stableIdSchemeCutover === true
197654
198311
  });
197655
198312
  const deferredHistoryDrainEligible = historyWasConsumedThisPass && materializationSatisfiedThisPass && (deferredHistoryWasPendingAtPassStart || hasPendingMaterializeSignal) && !suppressDeferredHistoryDrain && !casLost;
198313
+ let preserveDeferredMaterializationForMarkerDrain = false;
197656
198314
  if (deferredHistoryDrainEligible) {
197657
198315
  try {
197658
198316
  const pending = getPendingPiCompactionMarkerState(args.db, args.sessionId);
197659
198317
  if (!pending) {
197660
- consumeDeferredHistoryRefresh(args.sessionId);
198318
+ if (injectionResult?.contentionExhausted === true) {
198319
+ suppressDeferredHistoryDrain = true;
198320
+ preserveDeferredMaterializationForMarkerDrain = true;
198321
+ sessionLog(args.sessionId, "Pi deferred-history drain skipped: m[0]/m[1] used a contention fallback; preserving deferred signals");
198322
+ } else {
198323
+ consumeDeferredHistoryRefresh(args.sessionId);
198324
+ }
198325
+ } else if (!pendingPiMarkerCoveredByRenderedBoundary(pending, injectionResult)) {
198326
+ suppressDeferredHistoryDrain = true;
198327
+ preserveDeferredMaterializationForMarkerDrain = true;
198328
+ const boundary = injectionResult?.renderedBoundary;
198329
+ sessionLog(args.sessionId, `Pi compaction-marker drain skipped: pending ordinal ${pending.ordinal} is newer than rendered boundary ${boundary?.ordinal ?? "<none>"} endMessageId=${boundary?.endMessageId ?? "<none>"}; preserving deferred signals`);
197661
198330
  } else if (!args.appendCompaction || !args.readBranchEntries) {
197662
198331
  suppressDeferredHistoryDrain = true;
197663
198332
  sessionLog(args.sessionId, "Pi compaction-marker drain skipped: sessionManager appendCompaction/getBranch unavailable; preserving deferred-history signal");
@@ -197680,6 +198349,9 @@ async function runPipeline(args) {
197680
198349
  sessionLog(args.sessionId, `Pi compaction-marker drain failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
197681
198350
  }
197682
198351
  }
198352
+ if (preserveDeferredMaterializationForMarkerDrain && deferredMaterializationConsumedThisPass) {
198353
+ signalPiDeferredMaterialization(args.sessionId);
198354
+ }
197683
198355
  if (executedWorkThisPass) {
197684
198356
  try {
197685
198357
  const currentFlag = peekDeferredExecutePending(args.db, args.sessionId);
@@ -199235,13 +199907,23 @@ function withPublishedCallback(deps) {
199235
199907
  }
199236
199908
  function startLeaseRenewal(deps, holderId) {
199237
199909
  return setInterval(() => {
199238
- if (!renewCompartmentLease(deps.db, deps.sessionId, holderId)) {
199239
- sessionLog(deps.sessionId, "compartment lease renewal failed; publish will be skipped if holder is stale");
199910
+ try {
199911
+ if (!renewCompartmentLease(deps.db, deps.sessionId, holderId)) {
199912
+ sessionLog(deps.sessionId, "compartment lease renewal failed; publish will be skipped if holder is stale");
199913
+ }
199914
+ } catch (err) {
199915
+ sessionLog(deps.sessionId, `compartment lease renewal threw; publish will be skipped if holder is stale (${err instanceof Error ? err.message : String(err)})`);
199240
199916
  }
199241
199917
  }, COMPARTMENT_LEASE_RENEWAL_MS);
199242
199918
  }
199243
199919
  async function executeContextRecompWithResult(deps, options = {}) {
199244
199920
  const { sessionId } = deps;
199921
+ if (isWrapupInProgress(deps.db, sessionId)) {
199922
+ return {
199923
+ message: "## Magic Recomp — Skipped\n\n/ctx-wrapup is already compacting this session. Wait for it to finish, then try `/ctx-recomp` again.",
199924
+ published: false
199925
+ };
199926
+ }
199245
199927
  if (activeRuns.has(sessionId)) {
199246
199928
  return {
199247
199929
  message: "## Magic Recomp — Skipped\n\nHistorian is already running for this session. Wait for it to finish, then try `/ctx-recomp` again.",
@@ -199265,7 +199947,7 @@ async function executeContextRecompWithResult(deps, options = {}) {
199265
199947
  }).catch((err) => {
199266
199948
  sessionLog(sessionId, "compartment agent: recomp unhandled rejection:", err);
199267
199949
  });
199268
- activeRuns.set(sessionId, { promise: wrappedPromise, published: false });
199950
+ activeRuns.set(sessionId, { promise: wrappedPromise, published: false, kind: "recomp" });
199269
199951
  try {
199270
199952
  const message = await promise2;
199271
199953
  const published = activeRuns.get(sessionId)?.published === true;
@@ -199597,6 +200279,14 @@ ${parsed.message}`,
199597
200279
  });
199598
200280
  return;
199599
200281
  }
200282
+ if (isWrapupInProgress(deps.db, sessionId)) {
200283
+ sendCtxStatusMessage(pi, {
200284
+ title: "/ctx-recomp",
200285
+ text: "## Magic Recomp\n\n/ctx-wrapup is already compacting this session. Wait for it to finish, then try `/ctx-recomp` again.",
200286
+ level: "warning"
200287
+ });
200288
+ return;
200289
+ }
199600
200290
  if (isPiRecompInFlight(sessionId)) {
199601
200291
  sendCtxStatusMessage(pi, {
199602
200292
  title: "/ctx-recomp",
@@ -199988,6 +200678,16 @@ No active Pi session is available.`,
199988
200678
  });
199989
200679
  return;
199990
200680
  }
200681
+ if (isWrapupInProgress(deps.db, sessionId)) {
200682
+ sendCtxStatusMessage(pi, {
200683
+ title: "/ctx-session-upgrade",
200684
+ text: `## Session Upgrade
200685
+
200686
+ /ctx-wrapup is already compacting this session. Wait for it to finish, then try again.`,
200687
+ level: "warning"
200688
+ });
200689
+ return;
200690
+ }
199991
200691
  if (isPiRecompInFlight(sessionId)) {
199992
200692
  sendCtxStatusMessage(pi, {
199993
200693
  title: "/ctx-session-upgrade",
@@ -200319,7 +201019,7 @@ function formatThresholdPercent(value) {
200319
201019
  // package.json
200320
201020
  var package_default = {
200321
201021
  name: "@cortexkit/pi-magic-context",
200322
- version: "0.30.7",
201022
+ version: "0.31.0",
200323
201023
  type: "module",
200324
201024
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
200325
201025
  main: "dist/index.js",
@@ -200585,7 +201285,6 @@ ${safeStringify(tool.parameters)}`);
200585
201285
  inputTokens,
200586
201286
  systemPromptTokens,
200587
201287
  compartmentCount: compartments.length,
200588
- factCount: 0,
200589
201288
  memoryCount: safeRead(() => getMemoryCount(deps.db, deps.projectIdentity), 0),
200590
201289
  memoryBlockCount,
200591
201290
  sessionNoteCount: safeRead(() => getNotes(deps.db, {
@@ -200861,6 +201560,365 @@ function readHistorianState(db, sessionId, meta3) {
200861
201560
  };
200862
201561
  }
200863
201562
 
201563
+ // src/commands/ctx-wrapup.ts
201564
+ import * as crypto4 from "node:crypto";
201565
+ var DEFAULT_MESSAGES_TO_KEEP = 20;
201566
+ var LEASE_WAIT_MS = 1000;
201567
+ function parseWrapupArgs(raw) {
201568
+ const trimmed = raw.trim();
201569
+ if (trimmed === "")
201570
+ return { ok: true, messagesToKeep: DEFAULT_MESSAGES_TO_KEEP };
201571
+ if (!/^\d+$/.test(trimmed)) {
201572
+ return {
201573
+ ok: false,
201574
+ message: "Usage: `/ctx-wrapup [messages_to_keep]` where messages_to_keep is a positive integer."
201575
+ };
201576
+ }
201577
+ const messagesToKeep = Number.parseInt(trimmed, 10);
201578
+ if (!Number.isSafeInteger(messagesToKeep) || messagesToKeep <= 0) {
201579
+ return {
201580
+ ok: false,
201581
+ message: "messages_to_keep must be a positive integer."
201582
+ };
201583
+ }
201584
+ return { ok: true, messagesToKeep };
201585
+ }
201586
+ function registerCtxWrapupCommand(pi, deps) {
201587
+ pi.registerCommand("ctx-wrapup", {
201588
+ description: "Compact older Magic Context history while keeping the newest messages raw",
201589
+ handler: async (args, ctx) => {
201590
+ const sessionId = resolveSessionId(ctx);
201591
+ if (!sessionId) {
201592
+ sendCtxStatusMessage(pi, {
201593
+ title: "/ctx-wrapup",
201594
+ text: `## Magic Wrapup
201595
+
201596
+ No active Pi session is available.`,
201597
+ level: "error"
201598
+ });
201599
+ return;
201600
+ }
201601
+ const sessionMeta = getOrCreateSessionMeta(deps.db, sessionId);
201602
+ if (sessionMeta.isSubagent) {
201603
+ sendCtxStatusMessage(pi, {
201604
+ title: "/ctx-wrapup",
201605
+ text: `## Magic Wrapup — Skipped
201606
+
201607
+ /ctx-wrapup is only available in primary sessions.`,
201608
+ level: "warning"
201609
+ });
201610
+ return;
201611
+ }
201612
+ if (!deps.historianModel) {
201613
+ sendCtxStatusMessage(pi, {
201614
+ title: "/ctx-wrapup",
201615
+ text: "## Magic Wrapup\n\n/ctx-wrapup is unavailable because `historian.model` is not configured.",
201616
+ level: "error"
201617
+ });
201618
+ return;
201619
+ }
201620
+ const parsed = parseWrapupArgs(args);
201621
+ if (!parsed.ok) {
201622
+ sendCtxStatusMessage(pi, {
201623
+ title: "/ctx-wrapup",
201624
+ text: `## Magic Wrapup — Invalid Arguments
201625
+
201626
+ ${parsed.message}`,
201627
+ level: "error"
201628
+ });
201629
+ return;
201630
+ }
201631
+ const result = await runPiWrapup(pi, deps, ctx, sessionId, parsed.messagesToKeep);
201632
+ sendCtxStatusMessage(pi, {
201633
+ title: "/ctx-wrapup",
201634
+ text: result,
201635
+ level: result.includes("Failed") || result.includes("Partial") ? "error" : "success"
201636
+ });
201637
+ }
201638
+ });
201639
+ }
201640
+ async function runPiWrapup(pi, deps, ctx, sessionId, messagesToKeep) {
201641
+ if (getOrCreateSessionMeta(deps.db, sessionId).isSubagent) {
201642
+ return `## Magic Wrapup — Skipped
201643
+
201644
+ /ctx-wrapup is only available in primary sessions.`;
201645
+ }
201646
+ if (isPiRecompInFlight(sessionId)) {
201647
+ return "## Magic Wrapup — Skipped\n\nA recomp or upgrade is already running for this session. Wait for it to finish, then try `/ctx-wrapup` again.";
201648
+ }
201649
+ const provider2 = { readMessages: () => readPiSessionMessages(ctx) };
201650
+ const unregister = setRawMessageProvider(sessionId, provider2);
201651
+ let holderId = "";
201652
+ try {
201653
+ const contextLimit = resolvePiContextLimit(ctx);
201654
+ const modelKey = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
201655
+ const executeThresholdPercentage = resolveExecuteThreshold(deps.executeThresholdPercentage ?? 65, modelKey, 65, {
201656
+ tokensConfig: deps.executeThresholdTokens,
201657
+ contextLimit,
201658
+ sessionId
201659
+ });
201660
+ const initialPlan = resolveWrapupProtectedTailBoundary({
201661
+ db: deps.db,
201662
+ sessionId,
201663
+ mode: "manual-wrapup",
201664
+ contextLimit,
201665
+ executeThresholdPercentage,
201666
+ usage: { percentage: 0, inputTokens: 0 },
201667
+ usageSource: "manual-none",
201668
+ providerShapeVersion: "pi-folded-v1",
201669
+ cacheNamespace: `pi:${sessionId}`,
201670
+ messagesToKeep
201671
+ });
201672
+ if (initialPlan.rawMessagesAboveLastCompartment <= messagesToKeep) {
201673
+ return `## Magic Wrapup
201674
+
201675
+ Nothing to wrap up — only ${initialPlan.rawMessagesAboveLastCompartment} messages above the last compartment.`;
201676
+ }
201677
+ if (!hasRunnableCompartmentWindow(initialPlan.snapshot)) {
201678
+ return `## Magic Wrapup — Partial
201679
+
201680
+ No runnable wrapup boundary is available yet; wrapped up 0 messages into 0 compartments. Run /ctx-wrapup again to continue.`;
201681
+ }
201682
+ holderId = crypto4.randomUUID();
201683
+ const acquired = acquireWrapupInProgress(deps.db, sessionId, {
201684
+ holderId,
201685
+ messagesToKeep,
201686
+ anchorRawMessageCount: initialPlan.anchorRawMessageCount,
201687
+ targetEligibleEndOrdinal: initialPlan.targetEligibleEndOrdinal,
201688
+ lastCompartmentEnd: getLastCompartmentEndMessage(deps.db, sessionId),
201689
+ chunkIndex: 0,
201690
+ expectedChunks: estimateChunks(initialPlan.snapshot.trueRawEligibleTokens, deps.historianChunkTokens)
201691
+ });
201692
+ if (!acquired.ok) {
201693
+ return formatExistingWrapup(acquired.state ?? getWrapupInProgressState(deps.db, sessionId));
201694
+ }
201695
+ let ownershipLost = false;
201696
+ const ownershipLostReason = "another process took over this session's wrapup";
201697
+ const markOwnershipLost = () => {
201698
+ if (ownershipLost)
201699
+ return;
201700
+ ownershipLost = true;
201701
+ };
201702
+ const renewWrapupMarker = (updates) => {
201703
+ const updated = updateWrapupInProgress(deps.db, sessionId, holderId, updates);
201704
+ if (!updated) {
201705
+ markOwnershipLost();
201706
+ return false;
201707
+ }
201708
+ return true;
201709
+ };
201710
+ const renewal = setInterval(() => {
201711
+ try {
201712
+ renewWrapupMarker({
201713
+ lastCompartmentEnd: getLastCompartmentEndMessage(deps.db, sessionId)
201714
+ });
201715
+ } catch (err) {
201716
+ console.warn(`[magic-context][pi] /ctx-wrapup marker renewal failed for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
201717
+ }
201718
+ }, 60000);
201719
+ try {
201720
+ sendCtxStatusMessage(pi, {
201721
+ title: "/ctx-wrapup",
201722
+ text: `## Magic Wrapup
201723
+
201724
+ Eligible history is about ${initialPlan.snapshot.trueRawEligibleTokens.toLocaleString()} tokens across approximately ${estimateChunks(initialPlan.snapshot.trueRawEligibleTokens, deps.historianChunkTokens)} historian chunk(s).`,
201725
+ level: "info"
201726
+ });
201727
+ const startEnd = getLastCompartmentEndMessage(deps.db, sessionId);
201728
+ const startCompartmentCount = getCompartments(deps.db, sessionId).length;
201729
+ let chunkIndex = 0;
201730
+ let lastEnd = startEnd;
201731
+ let targetEligibleEndOrdinal = initialPlan.targetEligibleEndOrdinal;
201732
+ let failure = null;
201733
+ for (;; ) {
201734
+ if (ownershipLost) {
201735
+ failure = `${ownershipLostReason}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.`;
201736
+ break;
201737
+ }
201738
+ if (!renewWrapupMarker({
201739
+ chunkIndex,
201740
+ lastCompartmentEnd: getLastCompartmentEndMessage(deps.db, sessionId)
201741
+ })) {
201742
+ failure = `${ownershipLostReason}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.`;
201743
+ break;
201744
+ }
201745
+ const plan = resolveWrapupProtectedTailBoundary({
201746
+ db: deps.db,
201747
+ sessionId,
201748
+ mode: "manual-wrapup",
201749
+ contextLimit,
201750
+ executeThresholdPercentage,
201751
+ usage: { percentage: 0, inputTokens: 0 },
201752
+ usageSource: "manual-none",
201753
+ providerShapeVersion: "pi-folded-v1",
201754
+ cacheNamespace: `pi:${sessionId}`,
201755
+ messagesToKeep,
201756
+ anchorRawMessageCount: initialPlan.anchorRawMessageCount
201757
+ });
201758
+ targetEligibleEndOrdinal = plan.targetEligibleEndOrdinal;
201759
+ lastEnd = getLastCompartmentEndMessage(deps.db, sessionId);
201760
+ if (lastEnd + 1 >= targetEligibleEndOrdinal)
201761
+ break;
201762
+ if (!hasRunnableCompartmentWindow(plan.snapshot)) {
201763
+ failure = `No runnable wrapup boundary is available yet; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.`;
201764
+ break;
201765
+ }
201766
+ chunkIndex += 1;
201767
+ if (!renewWrapupMarker({
201768
+ chunkIndex,
201769
+ lastCompartmentEnd: getLastCompartmentEndMessage(deps.db, sessionId),
201770
+ targetEligibleEndOrdinal: plan.targetEligibleEndOrdinal,
201771
+ expectedChunks: Math.max(chunkIndex, estimateChunks(plan.snapshot.trueRawEligibleTokens, deps.historianChunkTokens))
201772
+ })) {
201773
+ failure = `${ownershipLostReason}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.`;
201774
+ break;
201775
+ }
201776
+ sendCtxStatusMessage(pi, {
201777
+ title: "/ctx-wrapup",
201778
+ text: `## Magic Wrapup
201779
+
201780
+ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.eligibleEndOrdinal - 1} (~${plan.snapshot.trueRawEligibleTokens.toLocaleString()} eligible tokens remain).`,
201781
+ level: "info"
201782
+ });
201783
+ const leaseHolder = await acquireCompartmentLeaseEventually(deps.db, sessionId, renewWrapupMarker);
201784
+ if (!leaseHolder) {
201785
+ failure = ownershipLost ? `${ownershipLostReason}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.` : "Another Magic Context rebuild started while wrapup was waiting. Run /ctx-wrapup again to continue.";
201786
+ break;
201787
+ }
201788
+ const leaseRenewal = setInterval(() => {
201789
+ try {
201790
+ renewCompartmentLease(deps.db, sessionId, leaseHolder);
201791
+ } catch (err) {
201792
+ console.warn(`[magic-context][pi] /ctx-wrapup compartment lease renewal failed for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
201793
+ }
201794
+ }, COMPARTMENT_LEASE_RENEWAL_MS);
201795
+ try {
201796
+ const runHistorian = deps.runPiHistorianForWrapup ?? runPiHistorian;
201797
+ await runHistorian({
201798
+ db: deps.db,
201799
+ sessionId,
201800
+ directory: ctx.cwd,
201801
+ provider: provider2,
201802
+ runner: deps.runner,
201803
+ historianModel: deps.historianModel,
201804
+ fallbackModels: deps.historianFallbacks,
201805
+ historianChunkTokens: deps.historianChunkTokens,
201806
+ boundarySnapshot: plan.snapshot,
201807
+ refreshBoundarySnapshot: () => resolveWrapupProtectedTailBoundary({
201808
+ db: deps.db,
201809
+ sessionId,
201810
+ mode: "manual-wrapup",
201811
+ contextLimit,
201812
+ executeThresholdPercentage,
201813
+ usage: { percentage: 0, inputTokens: 0 },
201814
+ usageSource: "manual-none",
201815
+ providerShapeVersion: "pi-folded-v1",
201816
+ cacheNamespace: `pi:${sessionId}`,
201817
+ messagesToKeep,
201818
+ anchorRawMessageCount: initialPlan.anchorRawMessageCount
201819
+ }).snapshot,
201820
+ currentContextLimit: contextLimit,
201821
+ historianTimeoutMs: deps.historianTimeoutMs,
201822
+ thinkingLevel: deps.historianThinkingLevel,
201823
+ memoryEnabled: deps.memoryEnabled,
201824
+ autoPromote: deps.autoPromote,
201825
+ userMemoriesEnabled: deps.userMemoriesEnabled,
201826
+ language: deps.language,
201827
+ compartmentLeaseHolderId: leaseHolder,
201828
+ readBranchEntries: () => readBranchEntries(ctx),
201829
+ notifyIssue: (text) => sendCtxStatusMessage(pi, {
201830
+ title: "/ctx-wrapup",
201831
+ text,
201832
+ level: "warning"
201833
+ }),
201834
+ ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
201835
+ forceDrainQuota: true,
201836
+ forceKeepLastCompartment: true,
201837
+ onPublished: () => {
201838
+ updateStatusLine(ctx, { db: deps.db, projectIdentity: ctx.cwd });
201839
+ signalPiDeferredHistoryRefresh(sessionId);
201840
+ signalPiDeferredMaterialization(sessionId);
201841
+ }
201842
+ });
201843
+ } finally {
201844
+ clearInterval(leaseRenewal);
201845
+ releaseCompartmentLease(deps.db, sessionId, leaseHolder);
201846
+ }
201847
+ const afterEnd = getLastCompartmentEndMessage(deps.db, sessionId);
201848
+ if (afterEnd <= lastEnd) {
201849
+ failure = `No forward progress after chunk ${chunkIndex}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.`;
201850
+ break;
201851
+ }
201852
+ lastEnd = afterEnd;
201853
+ }
201854
+ const finalEnd = getLastCompartmentEndMessage(deps.db, sessionId);
201855
+ if (!failure && finalEnd + 1 < targetEligibleEndOrdinal) {
201856
+ failure = `Wrapped up through message ${finalEnd}, but no runnable wrapup boundary remained before target message ${targetEligibleEndOrdinal}. Run /ctx-wrapup again to continue.`;
201857
+ }
201858
+ const finalCompartmentCount = getCompartments(deps.db, sessionId).length;
201859
+ const messagesWrapped = Math.max(0, finalEnd - startEnd);
201860
+ const compartmentsCreated = Math.max(0, finalCompartmentCount - startCompartmentCount);
201861
+ if (failure) {
201862
+ return `## Magic Wrapup — Partial
201863
+
201864
+ Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments; ${failure}`;
201865
+ }
201866
+ try {
201867
+ clearEmergencyRecovery(deps.db, sessionId);
201868
+ } catch {}
201869
+ return `## Magic Wrapup
201870
+
201871
+ Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments. The compacted history is queued and materializes on your next message.`;
201872
+ } finally {
201873
+ clearInterval(renewal);
201874
+ releaseWrapupInProgress(deps.db, sessionId, holderId);
201875
+ }
201876
+ } finally {
201877
+ unregister();
201878
+ }
201879
+ }
201880
+ function resolvePiContextLimit(ctx) {
201881
+ const usage = ctx.getContextUsage?.();
201882
+ if (typeof usage?.contextWindow === "number" && usage.contextWindow > 0) {
201883
+ return usage.contextWindow;
201884
+ }
201885
+ if (typeof ctx.model?.contextWindow === "number" && ctx.model.contextWindow > 0) {
201886
+ return ctx.model.contextWindow;
201887
+ }
201888
+ return 128000;
201889
+ }
201890
+ async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker) {
201891
+ for (;; ) {
201892
+ const holderId = crypto4.randomUUID();
201893
+ const lease = acquireCompartmentLease(db, sessionId, holderId);
201894
+ if (lease)
201895
+ return holderId;
201896
+ if (!renewWrapupMarker({}))
201897
+ return null;
201898
+ await new Promise((resolve3) => setTimeout(resolve3, LEASE_WAIT_MS));
201899
+ }
201900
+ }
201901
+ function estimateChunks(tokens, chunkTokens) {
201902
+ return Math.max(1, Math.ceil(Math.max(0, tokens) / Math.max(1, chunkTokens)));
201903
+ }
201904
+ function formatExistingWrapup(state) {
201905
+ if (!state) {
201906
+ return `## Magic Wrapup — Skipped
201907
+
201908
+ Another /ctx-wrapup is already compacting this session. Wait for it to finish, then try again.`;
201909
+ }
201910
+ return `## Magic Wrapup — Skipped
201911
+
201912
+ Another /ctx-wrapup is already compacting this session (chunk ${state.chunkIndex}/${Math.max(state.chunkIndex, state.expectedChunks)}, wrapped through message ${state.lastCompartmentEnd}). Wait for it to finish, then try again.`;
201913
+ }
201914
+ function readBranchEntries(ctx) {
201915
+ const getBranch = ctx.sessionManager.getBranch;
201916
+ if (typeof getBranch !== "function")
201917
+ return [];
201918
+ const branch = getBranch.call(ctx.sessionManager);
201919
+ return Array.isArray(branch?.entries) ? branch.entries : [];
201920
+ }
201921
+
200864
201922
  // src/pi-pressure.ts
200865
201923
  function extractAssistantUsage(message) {
200866
201924
  if (!message || typeof message !== "object")
@@ -206252,14 +207310,17 @@ Sources (omit for a broad search across all):
206252
207310
  - memory: curated cross-session project knowledge — rules, constraints, conventions.
206253
207311
  - message: the raw conversation behind your compacted history. Hits include message ordinals — expand the surrounding exchange with ctx_expand(start=N-10, end=N+5).
206254
207312
  - git_commit: this repository's commit history.
207313
+ - note: parked decisions, follow-ups, and dismissed notes with their recorded text.
206255
207314
 
206256
207315
  Picking sources:
206257
207316
  - "when did this change / was this working before" → ["git_commit", "message"]
206258
207317
  - "did we discuss this earlier" → ["message"]
207318
+ - "did we decide something about this / leave a follow-up" → ["note"]
206259
207319
  - "what's our convention / rule for X" → ["memory"]`;
206260
207320
 
206261
207321
  // src/tools/ctx-search.ts
206262
207322
  var DEFAULT_LIMIT = 10;
207323
+ var NOTE_EXPAND_HINT = "Use ctx_expand(start=N-10, end=N) around any note @msg anchor above to read the surrounding conversation context.";
206263
207324
  var ParamsSchema5 = exports_typebox.Object({
206264
207325
  query: exports_typebox.String({
206265
207326
  description: "Search query. Matches against memory content, Primers, git commit messages, and raw user/assistant message text."
@@ -206271,9 +207332,10 @@ var ParamsSchema5 = exports_typebox.Object({
206271
207332
  exports_typebox.Literal("memory"),
206272
207333
  exports_typebox.Literal("message"),
206273
207334
  exports_typebox.Literal("git_commit"),
206274
- exports_typebox.Literal("primer")
207335
+ exports_typebox.Literal("primer"),
207336
+ exports_typebox.Literal("note")
206275
207337
  ]), {
206276
- description: 'Optional. Restrict to specific sources. Examples: ["primer"] for standing project explanations, ["git_commit"] for "when did we change X", ["memory"] for naming conventions, ["message"] for "did we discuss this earlier", ["git_commit","message"] for regression hunts. Omit for a broad search across all enabled sources.'
207338
+ description: 'Optional. Restrict to specific sources. Examples: ["primer"] for standing project explanations, ["git_commit"] for "when did we change X", ["memory"] for naming conventions, ["message"] for "did we discuss this earlier", ["note"] for parked decisions or follow-ups, ["git_commit","message"] for regression hunts. Omit for a broad search across all enabled sources.'
206277
207339
  }))
206278
207340
  });
206279
207341
  function normalizeLimit3(limit) {
@@ -206300,7 +207362,7 @@ function formatAge2(committedAtMs) {
206300
207362
  const years = Math.floor(days / 365);
206301
207363
  return years === 1 ? "1y ago" : `${years}y ago`;
206302
207364
  }
206303
- function formatResult(result, index) {
207365
+ function formatResult(result, index, currentSessionId) {
206304
207366
  if (result.source === "memory") {
206305
207367
  const source = result.sourceName ? ` source=${result.sourceName}` : "";
206306
207368
  return [
@@ -206321,6 +207383,14 @@ function formatResult(result, index) {
206321
207383
  `[${index}] [primer] score=${result.score.toFixed(2)} id=${result.primerId} support=${result.support} match=${result.matchType}`,
206322
207384
  result.content
206323
207385
  ].join(`
207386
+ `);
207387
+ }
207388
+ if (result.source === "note") {
207389
+ const anchor = result.anchorOrdinal !== null && result.sourceSessionId === currentSessionId ? ` @msg ${result.anchorOrdinal}` : "";
207390
+ return [
207391
+ `[${index}] [note] score=${result.score.toFixed(2)} id=#${result.noteId} status=${result.status} ${formatAge2(result.createdAt)}${anchor}`,
207392
+ result.content
207393
+ ].join(`
206324
207394
  `);
206325
207395
  }
206326
207396
  if (result.source === "compartment") {
@@ -206338,14 +207408,17 @@ function formatResult(result, index) {
206338
207408
  ].join(`
206339
207409
  `);
206340
207410
  }
206341
- function formatSearchResults(query, results) {
207411
+ function formatSearchResults(query, results, currentSessionId) {
206342
207412
  if (results.length === 0) {
206343
- return `No results found for "${query}" across memories, primers, git commits, or message history.`;
207413
+ return `No results found for "${query}" across notes, memories, primers, git commits, or message history.`;
206344
207414
  }
206345
- const bodyParts = results.map((result, index) => formatResult(result, index + 1));
207415
+ const bodyParts = results.map((result, index) => formatResult(result, index + 1, currentSessionId));
206346
207416
  if (results.some((result) => result.source === "message" || result.source === "compartment")) {
206347
207417
  bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
206348
207418
  }
207419
+ if (results.some((result) => result.source === "note" && result.anchorOrdinal !== null && result.sourceSessionId === currentSessionId)) {
207420
+ bodyParts.push(NOTE_EXPAND_HINT);
207421
+ }
206349
207422
  const body = bodyParts.join(`
206350
207423
 
206351
207424
  `);
@@ -206394,7 +207467,12 @@ function createCtxSearchTool(deps) {
206394
207467
  explicitSearch: true
206395
207468
  });
206396
207469
  return {
206397
- content: [{ type: "text", text: formatSearchResults(query, results) }],
207470
+ content: [
207471
+ {
207472
+ type: "text",
207473
+ text: formatSearchResults(query, results, sessionId)
207474
+ }
207475
+ ],
206398
207476
  details: undefined
206399
207477
  };
206400
207478
  }
@@ -206491,7 +207569,7 @@ function registerMagicContextTools(pi, opts) {
206491
207569
  pi.registerTool(createCtxExpandTool({ db: opts.db }));
206492
207570
  }
206493
207571
  pi.registerTool(createTodowriteTool());
206494
- if (opts.ctxReduceEnabled === true) {
207572
+ if (!opts.sessionScopedToolsDisabled) {
206495
207573
  pi.registerTool(createCtxReduceTool({
206496
207574
  db: opts.db,
206497
207575
  protectedTags: opts.protectedTags ?? 20
@@ -206503,7 +207581,7 @@ function registerMagicContextTools(pi, opts) {
206503
207581
  var PREFIX = "[magic-context][pi]";
206504
207582
  function resolveCurrentProject(ctx) {
206505
207583
  const projectDir = ctx.cwd;
206506
- const projectIdentity = resolveProjectIdentity(projectDir);
207584
+ const projectIdentity = resolveProjectIdentityOrFallback(projectDir);
206507
207585
  return { projectDir, projectIdentity };
206508
207586
  }
206509
207587
  function persistPiMessageEndModelMeta(args) {
@@ -206705,13 +207783,13 @@ async function src_default2(pi) {
206705
207783
  })();
206706
207784
  }, 0);
206707
207785
  const projectDir = process.cwd();
206708
- const projectIdentity = resolveProjectIdentity(projectDir);
207786
+ const projectIdentity = resolveProjectIdentityOrFallback(projectDir);
206709
207787
  const seenDreamerProjectIdentities = new Set([projectIdentity]);
206710
207788
  try {
206711
207789
  const pendingPiMarkerSessions = getSessionsWithPendingPiMarker(db);
206712
207790
  for (const sid of pendingPiMarkerSessions) {
206713
207791
  signalPiDeferredHistoryRefresh(sid);
206714
- signalPiPendingMaterialization(sid);
207792
+ signalPiDeferredMaterialization(sid);
206715
207793
  }
206716
207794
  if (pendingPiMarkerSessions.length > 0) {
206717
207795
  log(`${PREFIX} rehydrated ${pendingPiMarkerSessions.length} Pi deferred compaction marker session(s)`);
@@ -206749,11 +207827,10 @@ async function src_default2(pi) {
206749
207827
  ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
206750
207828
  allowDreamerActions: false,
206751
207829
  memoryToolEnabled: true,
206752
- ctxReduceEnabled: config2.ctx_reduce_enabled === true,
206753
207830
  protectedTags: config2.protected_tags ?? 20,
206754
207831
  dreamerEnabled: isDreamerRunnable(config2)
206755
207832
  });
206756
- info(`registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand${config2.ctx_reduce_enabled === true ? ", ctx_reduce" : ""}`);
207833
+ info("registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand, ctx_reduce");
206757
207834
  const historianConfig = resolveHistorianFromConfig(config2);
206758
207835
  if (historianConfig) {
206759
207836
  historianConfig.onStatusChange = (ctx) => {
@@ -206767,11 +207844,10 @@ async function src_default2(pi) {
206767
207844
  const contextOptionsByDir = new Map;
206768
207845
  const buildContextOptions = (cfg, hist, auto) => ({
206769
207846
  db: database,
206770
- ctxReduceEnabled: cfg.ctx_reduce_enabled,
206771
207847
  smartDrops: cfg.smart_drops === true,
206772
207848
  protectedTags: cfg.protected_tags ?? 20,
206773
207849
  heuristics: {
206774
- caveman: cfg.ctx_reduce_enabled === false && cfg.caveman_text_compression ? {
207850
+ caveman: cfg.caveman_text_compression ? {
206775
207851
  enabled: cfg.caveman_text_compression.enabled,
206776
207852
  minChars: cfg.caveman_text_compression.min_chars
206777
207853
  } : undefined,
@@ -206866,6 +207942,22 @@ async function src_default2(pi) {
206866
207942
  autoPromote: config2.memory.auto_promote
206867
207943
  });
206868
207944
  info("registered /ctx-recomp");
207945
+ registerCtxWrapupCommand(pi, {
207946
+ db,
207947
+ runner: new PiSubagentRunner,
207948
+ historianModel: historianConfig?.model,
207949
+ historianChunkTokens: deriveHistorianChunkTokens(resolveHistorianContextLimit(historianConfig?.model)),
207950
+ historianFallbacks: historianConfig?.fallbackModels,
207951
+ historianTimeoutMs: config2.historian_timeout_ms,
207952
+ historianThinkingLevel: historianConfig?.thinkingLevel,
207953
+ language: config2.language,
207954
+ memoryEnabled: config2.memory.enabled,
207955
+ autoPromote: config2.memory.auto_promote,
207956
+ userMemoriesEnabled: userMemoryCollectionEnabled(config2.dreamer),
207957
+ executeThresholdPercentage: config2.execute_threshold_percentage,
207958
+ executeThresholdTokens: config2.execute_threshold_tokens
207959
+ });
207960
+ info("registered /ctx-wrapup");
206869
207961
  registerCtxSessionUpgradeCommand(pi, {
206870
207962
  db,
206871
207963
  runner: new PiSubagentRunner,
@@ -207015,10 +208107,10 @@ async function src_default2(pi) {
207015
208107
  memoryEnabled: effectiveConfig.memory.enabled,
207016
208108
  includeGuidance: true,
207017
208109
  protectedTags: effectiveConfig.protected_tags,
207018
- ctxReduceEnabled: effectiveConfig.ctx_reduce_enabled,
208110
+ ctxReduceCallable: true,
207019
208111
  dreamerEnabled: effectiveDreamerRunnable,
207020
208112
  temporalAwarenessEnabled: effectiveConfig.temporal_awareness ?? false,
207021
- cavemanTextCompressionEnabled: effectiveConfig.ctx_reduce_enabled === false && effectiveConfig.caveman_text_compression?.enabled === true,
208113
+ cavemanTextCompressionEnabled: effectiveConfig.caveman_text_compression?.enabled === true,
207022
208114
  language: effectiveConfig.language,
207023
208115
  userMemoriesEnabled: userMemoryCollectionEnabled(effectiveConfig.dreamer),
207024
208116
  isCacheBusting,