@cortexkit/pi-magic-context 0.30.7 → 0.31.1

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
  }
@@ -178093,6 +178376,9 @@ function requestValidatedAddress(validation, candidate, options) {
178093
178376
  }
178094
178377
  chunks.push(buf);
178095
178378
  });
178379
+ response.on("error", (error51) => {
178380
+ reject(toNetworkError(error51, "response failed"));
178381
+ });
178096
178382
  response.on("end", () => {
178097
178383
  const status = response.statusCode ?? 0;
178098
178384
  if (status >= 500) {
@@ -178822,14 +179108,21 @@ function toSmartNote(note) {
178822
179108
  };
178823
179109
  }
178824
179110
  function commitSmartNoteState(db, args) {
179111
+ db.exec("BEGIN IMMEDIATE");
178825
179112
  let leaseLost = false;
178826
- db.transaction(() => {
179113
+ try {
178827
179114
  if (args.leaseHeld && !args.leaseHeld()) {
178828
179115
  leaseLost = true;
178829
- return;
179116
+ } else {
179117
+ args.write();
178830
179118
  }
178831
- args.write();
178832
- })();
179119
+ db.exec("COMMIT");
179120
+ } catch (error51) {
179121
+ try {
179122
+ db.exec("ROLLBACK");
179123
+ } catch {}
179124
+ throw error51;
179125
+ }
178833
179126
  if (leaseLost) {
178834
179127
  throw new Error(`Dream lease lost during smart-note ${args.phase} commit`);
178835
179128
  }
@@ -179297,7 +179590,7 @@ Output exactly JSON: {"met": false}`;
179297
179590
 
179298
179591
  // ../plugin/src/features/magic-context/dreamer/maintain-docs-protected-enforcement.ts
179299
179592
  init_logger();
179300
- import { existsSync as existsSync12, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
179593
+ import { existsSync as existsSync13, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
179301
179594
  import { join as join11 } from "node:path";
179302
179595
 
179303
179596
  // ../plugin/src/features/magic-context/dreamer/protected-regions.ts
@@ -179401,7 +179694,7 @@ function snapshotMaintainDocsFiles(docsDir) {
179401
179694
  for (const name2 of MAINTAIN_DOCS_SNAPSHOT_FILES) {
179402
179695
  const path9 = join11(docsDir, name2);
179403
179696
  try {
179404
- if (existsSync12(path9)) {
179697
+ if (existsSync13(path9)) {
179405
179698
  snapshot.set(name2, readFileSync6(path9, "utf8"));
179406
179699
  }
179407
179700
  } catch {}
@@ -179429,7 +179722,7 @@ function enforceMaintainDocsProtectedRegions(args) {
179429
179722
  init_logger();
179430
179723
 
179431
179724
  // ../plugin/src/features/magic-context/dreamer/map-memories-prompt.ts
179432
- import { existsSync as existsSync13, statSync as statSync5 } from "node:fs";
179725
+ import { existsSync as existsSync14, statSync as statSync5 } from "node:fs";
179433
179726
  import path9 from "node:path";
179434
179727
  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
179728
 
@@ -179467,7 +179760,7 @@ function extractMemoryCandidatePaths(content, repoDir) {
179467
179760
  if (!abs.startsWith(`${root}/`))
179468
179761
  continue;
179469
179762
  try {
179470
- if (existsSync13(abs) && statSync5(abs).isFile())
179763
+ if (existsSync14(abs) && statSync5(abs).isFile())
179471
179764
  found.add(rel);
179472
179765
  } catch {}
179473
179766
  if (found.size >= MAX_SEED_PATHS_PER_MEMORY)
@@ -181608,8 +181901,8 @@ async function runAgenticTask(config2, ctx, helpers) {
181608
181901
  const lastRunAt = getTaskScheduleState(db, projectIdentity, config2.task)?.lastRunAt ?? null;
181609
181902
  const maintainDocsSnapshot = task === "maintain-docs" ? snapshotMaintainDocsFiles(docsDir) : undefined;
181610
181903
  const existingDocs = task === "maintain-docs" ? {
181611
- architecture: existsSync14(`${docsDir}/ARCHITECTURE.md`),
181612
- structure: existsSync14(`${docsDir}/STRUCTURE.md`)
181904
+ architecture: existsSync15(`${docsDir}/ARCHITECTURE.md`),
181905
+ structure: existsSync15(`${docsDir}/STRUCTURE.md`)
181613
181906
  } : undefined;
181614
181907
  const userMemories = task === "curate" ? getActiveUserMemories(db).map((um) => ({ id: um.id, content: um.content })) : undefined;
181615
181908
  let curateMemories;
@@ -182824,7 +183117,7 @@ function pruneNestedConfigLeaf(block, relativePath) {
182824
183117
  }
182825
183118
 
182826
183119
  // src/config/index.ts
182827
- import { existsSync as existsSync16, readFileSync as readFileSync8 } from "node:fs";
183120
+ import { existsSync as existsSync17, readFileSync as readFileSync8 } from "node:fs";
182828
183121
 
182829
183122
  // ../plugin/src/config/migrate-dreamer-v2.ts
182830
183123
  var OLD_VERIFY_TASK = "verify";
@@ -183206,7 +183499,7 @@ function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
183206
183499
  }
183207
183500
 
183208
183501
  // ../plugin/src/config/variable.ts
183209
- import { existsSync as existsSync15, readFileSync as readFileSync7 } from "node:fs";
183502
+ import { existsSync as existsSync16, readFileSync as readFileSync7 } from "node:fs";
183210
183503
  import { homedir as homedir4 } from "node:os";
183211
183504
  import { dirname as dirname6, isAbsolute as isAbsolute2, resolve } from "node:path";
183212
183505
 
@@ -183341,7 +183634,7 @@ function substituteConfigVariables(input) {
183341
183634
  if (sensitiveReason) {
183342
183635
  warnings.push(`${token} resolves to a sensitive path (${sensitiveReason}: ${filePath}); ` + "inlining its contents into config — make sure this is intentional.");
183343
183636
  }
183344
- if (!existsSync15(filePath)) {
183637
+ if (!existsSync16(filePath)) {
183345
183638
  warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
183346
183639
  continue;
183347
183640
  }
@@ -183370,10 +183663,10 @@ function getUserConfigPaths() {
183370
183663
  return [`${basePath}.jsonc`, `${basePath}.json`];
183371
183664
  }
183372
183665
  function resolveFirstExisting(paths) {
183373
- return paths.find((path11) => existsSync16(path11));
183666
+ return paths.find((path11) => existsSync17(path11));
183374
183667
  }
183375
183668
  function resolvePiLegacyFallback(sources) {
183376
- return sources.find((source) => existsSync16(source.path)) ?? null;
183669
+ return sources.find((source) => existsSync17(source.path)) ?? null;
183377
183670
  }
183378
183671
  function loadConfigFile(path11, scope) {
183379
183672
  try {
@@ -183510,7 +183803,7 @@ function loadPiConfig(opts = {}) {
183510
183803
  if (loaded)
183511
183804
  loadedFiles.push(loaded);
183512
183805
  }
183513
- const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync16(source.path));
183806
+ const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync17(source.path));
183514
183807
  const userPath = resolveFirstExisting(getUserConfigPaths());
183515
183808
  const userLegacyFallback = userPath ? null : resolvePiLegacyFallback(harnessLegacy.user);
183516
183809
  const userReadPath = userPath ?? userLegacyFallback?.path;
@@ -183519,7 +183812,7 @@ function loadPiConfig(opts = {}) {
183519
183812
  if (loaded)
183520
183813
  loadedFiles.push(loaded);
183521
183814
  }
183522
- const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync16(source.path));
183815
+ const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync17(source.path));
183523
183816
  if (userLegacyFallback) {
183524
183817
  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
183818
  } else if (legacyUserUnmigrated) {
@@ -183620,7 +183913,7 @@ function loadPiConfigDetailed(opts = {}) {
183620
183913
  if (loaded)
183621
183914
  loadedFiles.push(loaded);
183622
183915
  }
183623
- const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync16(source.path));
183916
+ const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync17(source.path));
183624
183917
  const userPath = resolveFirstExisting(getUserConfigPaths());
183625
183918
  const userLegacyFallback = userPath ? null : resolvePiLegacyFallback(harnessLegacy.user);
183626
183919
  const userReadPath = userPath ?? userLegacyFallback?.path;
@@ -183629,7 +183922,7 @@ function loadPiConfigDetailed(opts = {}) {
183629
183922
  if (loaded)
183630
183923
  loadedFiles.push(loaded);
183631
183924
  }
183632
- const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync16(source.path));
183925
+ const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync17(source.path));
183633
183926
  if (userLegacyFallback) {
183634
183927
  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
183928
  } else if (legacyUserUnmigrated) {
@@ -185100,7 +185393,8 @@ var canonicalReasons = new Set([
185100
185393
  "first_render",
185101
185394
  "pressure_refold",
185102
185395
  "upgrade_state",
185103
- "cached_m1_missing"
185396
+ "cached_m1_missing",
185397
+ "project_change"
185104
185398
  ]);
185105
185399
  var piReasonAliases = {
185106
185400
  project_memory_change: "project_memory_epoch",
@@ -186379,6 +186673,43 @@ function semanticSnapBoundary(args) {
186379
186673
  }
186380
186674
  return snapped;
186381
186675
  }
186676
+ function snapWrapupBoundaryToUser(args) {
186677
+ const { messages, index, candidate, offset, triggerBudget } = args;
186678
+ if (candidate <= offset)
186679
+ return candidate;
186680
+ const snapTokenLimit = Math.min(Math.max(triggerBudget, 2000), 48000);
186681
+ for (let ordinal = candidate;ordinal >= offset; ordinal -= 1) {
186682
+ const message = messages.find((m) => m.ordinal === ordinal);
186683
+ if (!message)
186684
+ continue;
186685
+ if (message.role !== "user" || !hasMeaningfulUserText(message.parts))
186686
+ continue;
186687
+ const extraTokens = index.rangeTokens(ordinal, candidate);
186688
+ if (extraTokens <= snapTokenLimit)
186689
+ return ordinal;
186690
+ return candidate;
186691
+ }
186692
+ return candidate;
186693
+ }
186694
+ function fenceWrapupBoundaryForToolArcs(args) {
186695
+ let boundary = args.candidate;
186696
+ const maxPasses = args.arcs.length + 1;
186697
+ for (let pass = 0;pass < maxPasses; pass += 1) {
186698
+ let next = boundary;
186699
+ for (const arc of args.arcs) {
186700
+ if (arc.resOrdinal === null) {
186701
+ continue;
186702
+ }
186703
+ if (arc.invOrdinal >= args.lastCompartmentEndOrdinal + 1 && arc.invOrdinal < next && next <= arc.resOrdinal) {
186704
+ next = arc.invOrdinal;
186705
+ }
186706
+ }
186707
+ if (next === boundary)
186708
+ return boundary;
186709
+ boundary = next;
186710
+ }
186711
+ return boundary;
186712
+ }
186382
186713
  function applyHeadCap(args) {
186383
186714
  const { index, protectedTailStart, offset, arcs, capTokens, recentOpenArcCutoff } = args;
186384
186715
  if (offset >= protectedTailStart)
@@ -186634,6 +186965,124 @@ function resolveBoundaryContext(args) {
186634
186965
  function resolveOpenCodeProtectedTailBoundary(args) {
186635
186966
  return resolveProtectedTailBoundary(resolveBoundaryContext(args));
186636
186967
  }
186968
+ function resolveWrapupProtectedTailBoundary(args) {
186969
+ const ctx = resolveBoundaryContext({ ...args, mode: "manual-wrapup" });
186970
+ const createdAt = ctx.createdAt ?? Date.now();
186971
+ const messages = readRawSessionMessages(ctx.sessionId);
186972
+ const absoluteMessageCount = getCachedAbsoluteMessageCount(ctx.sessionId) ?? undefined;
186973
+ const index = buildTrueRawTokenIndex(ctx.sessionId, messages, {
186974
+ providerShapeVersion: ctx.providerShapeVersion,
186975
+ cacheNamespace: ctx.cacheNamespace,
186976
+ absoluteMessageCount,
186977
+ storedTotalForMessage: ctx.storedTokenTotals ? (m) => {
186978
+ const value = ctx.storedTokenTotals?.get(m.id);
186979
+ return value === undefined ? null : value;
186980
+ } : undefined
186981
+ });
186982
+ const rawMessageCount = index.rawMessageCount;
186983
+ const offset = Math.max(1, ctx.lastCompartmentEndOrdinal + 1);
186984
+ const anchorRawMessageCount = Math.max(0, Math.min(rawMessageCount, Math.floor(args.anchorRawMessageCount ?? rawMessageCount)));
186985
+ const usagePercentage = clampPercentage(ctx.usage?.percentage ?? 0);
186986
+ const usageInputTokens = Math.max(0, Math.round(ctx.usage?.inputTokens ?? 0));
186987
+ const rawMessagesAboveLastCompartment = Math.max(0, anchorRawMessageCount - offset + 1);
186988
+ const keep = Math.max(1, Math.floor(args.messagesToKeep));
186989
+ let targetProtectedTailStart = offset;
186990
+ let boundaryReason = "manual-wrapup-empty";
186991
+ if (rawMessageCount === 0 || rawMessagesAboveLastCompartment <= keep) {
186992
+ targetProtectedTailStart = offset;
186993
+ boundaryReason = rawMessageCount === 0 ? "manual-wrapup-empty" : "manual-wrapup-within-keep";
186994
+ } else {
186995
+ targetProtectedTailStart = anchorRawMessageCount - keep + 1;
186996
+ boundaryReason = "manual-wrapup-keep-watermark";
186997
+ const arcs = buildToolArcs(messages);
186998
+ const fenced = fenceWrapupBoundaryForToolArcs({
186999
+ candidate: targetProtectedTailStart,
187000
+ arcs,
187001
+ lastCompartmentEndOrdinal: ctx.lastCompartmentEndOrdinal
187002
+ });
187003
+ if (fenced !== targetProtectedTailStart)
187004
+ boundaryReason = "manual-wrapup-tool-arc";
187005
+ targetProtectedTailStart = fenced;
187006
+ const snapped = snapWrapupBoundaryToUser({
187007
+ messages,
187008
+ index,
187009
+ candidate: targetProtectedTailStart,
187010
+ offset,
187011
+ triggerBudget: ctx.triggerBudget
187012
+ });
187013
+ if (snapped !== targetProtectedTailStart)
187014
+ boundaryReason = "manual-wrapup-user-snap";
187015
+ targetProtectedTailStart = snapped;
187016
+ const refenced = fenceWrapupBoundaryForToolArcs({
187017
+ candidate: targetProtectedTailStart,
187018
+ arcs,
187019
+ lastCompartmentEndOrdinal: ctx.lastCompartmentEndOrdinal
187020
+ });
187021
+ if (refenced !== targetProtectedTailStart)
187022
+ boundaryReason = "manual-wrapup-tool-arc";
187023
+ targetProtectedTailStart = refenced;
187024
+ }
187025
+ targetProtectedTailStart = clampOrdinal(targetProtectedTailStart, rawMessageCount);
187026
+ const target = deriveProtectedTailTokenTarget({
187027
+ contextLimit: ctx.contextLimit,
187028
+ executeThresholdPercentage: ctx.executeThresholdPercentage,
187029
+ usagePercentage,
187030
+ triggerBudget: ctx.triggerBudget
187031
+ });
187032
+ const perRunCap = selectPerRunCap({
187033
+ usagePercentage,
187034
+ N: target.N,
187035
+ contextLimit: ctx.contextLimit,
187036
+ executeThresholdPercentage: ctx.executeThresholdPercentage
187037
+ });
187038
+ const head = applyHeadCap({
187039
+ index,
187040
+ protectedTailStart: targetProtectedTailStart,
187041
+ offset,
187042
+ arcs: buildToolArcs(messages),
187043
+ lastCompartmentEndOrdinal: ctx.lastCompartmentEndOrdinal,
187044
+ capTokens: perRunCap,
187045
+ recentOpenArcCutoff: targetProtectedTailStart
187046
+ });
187047
+ const eligibleEndOrdinal = Math.min(head.eligibleEndOrdinal, targetProtectedTailStart);
187048
+ const rawRangeFingerprint = computeRawRangeFingerprint(messages, offset, eligibleEndOrdinal);
187049
+ const snapshot = {
187050
+ sessionId: ctx.sessionId,
187051
+ mode: "manual-wrapup",
187052
+ offset,
187053
+ offsetMessageId: boundaryMessageId(index, offset),
187054
+ protectedTailStart: targetProtectedTailStart,
187055
+ protectedTailStartMessageId: boundaryMessageId(index, targetProtectedTailStart),
187056
+ eligibleEndOrdinal,
187057
+ eligibleEndMessageId: boundaryMessageId(index, eligibleEndOrdinal - 1),
187058
+ rawMessageCountAtTrigger: rawMessageCount,
187059
+ rawLastMessageIdAtTrigger: boundaryMessageId(index, rawMessageCount),
187060
+ N: keep,
187061
+ usagePercentage,
187062
+ usageInputTokens,
187063
+ usageSource: ctx.usageSource,
187064
+ contextLimit: ctx.contextLimit,
187065
+ executeThresholdPercentage: ctx.executeThresholdPercentage,
187066
+ triggerBudget: ctx.triggerBudget,
187067
+ priorBoundaryOrdinal: ctx.priorBoundaryOrdinal,
187068
+ migrationFloorActive: ctx.migrationFloorActive,
187069
+ emergencyTailScale: ctx.emergencyTailScale,
187070
+ providerShapeVersion: ctx.providerShapeVersion,
187071
+ cacheNamespace: ctx.cacheNamespace,
187072
+ createdAt,
187073
+ rawRangeFingerprint,
187074
+ trueRawEligibleTokens: index.rangeTokens(offset, targetProtectedTailStart),
187075
+ oversizeAtomicUnit: head.oversizeAtomicUnit,
187076
+ boundaryReason
187077
+ };
187078
+ return {
187079
+ snapshot,
187080
+ rawMessagesAboveLastCompartment,
187081
+ anchorRawMessageCount,
187082
+ targetProtectedTailStart,
187083
+ targetEligibleEndOrdinal: targetProtectedTailStart
187084
+ };
187085
+ }
186637
187086
  function getRawHistoryEligibility(db, sessionId) {
186638
187087
  const lastCompartmentEnd = getLastCompartmentEndMessage(db, sessionId);
186639
187088
  const offset = Math.max(1, lastCompartmentEnd + 1);
@@ -188679,6 +189128,147 @@ function searchMessages(args) {
188679
189128
  role: entry.row.role
188680
189129
  }));
188681
189130
  }
189131
+ var NOTE_SEARCHABLE_STATUSES = ["active", "pending", "ready", "dismissed"];
189132
+ function noteSearchText(note) {
189133
+ const reason = note.readyReason?.trim();
189134
+ return reason ? `${note.content}
189135
+ Reason: ${reason}` : note.content;
189136
+ }
189137
+ function tokenizeKeywordNeedle(text) {
189138
+ const matches = text.toLowerCase().match(/[a-z0-9/._:-]+/g) ?? [];
189139
+ const seen = new Set;
189140
+ const tokens = [];
189141
+ for (const match of matches) {
189142
+ if (match.length <= 1 || !/[a-z0-9]/.test(match) || seen.has(match)) {
189143
+ continue;
189144
+ }
189145
+ seen.add(match);
189146
+ tokens.push(match);
189147
+ }
189148
+ return tokens;
189149
+ }
189150
+ function rankNotesForNeedle(notes, needle) {
189151
+ const normalizedNeedle = needle.trim().toLowerCase();
189152
+ if (normalizedNeedle.length === 0) {
189153
+ return [];
189154
+ }
189155
+ const needleTokens = tokenizeKeywordNeedle(normalizedNeedle);
189156
+ const ranked = [];
189157
+ for (const note of notes) {
189158
+ const text = noteSearchText(note);
189159
+ const normalizedText = text.toLowerCase();
189160
+ const noteTokens = new Set(tokenizeKeywordNeedle(normalizedText));
189161
+ const exact = normalizedText.includes(normalizedNeedle);
189162
+ const matchedTokens = needleTokens.filter((token) => noteTokens.has(token)).length;
189163
+ if (!exact && matchedTokens === 0) {
189164
+ continue;
189165
+ }
189166
+ const coverage = needleTokens.length > 0 ? matchedTokens / needleTokens.length : 0;
189167
+ const score = (exact ? 2 : 0) + coverage + (needleTokens.length > 1 && matchedTokens === needleTokens.length ? 0.5 : 0);
189168
+ ranked.push({ note, score, text });
189169
+ }
189170
+ return ranked.sort((left, right) => {
189171
+ if (right.score !== left.score) {
189172
+ return right.score - left.score;
189173
+ }
189174
+ if (right.note.createdAt !== left.note.createdAt) {
189175
+ return right.note.createdAt - left.note.createdAt;
189176
+ }
189177
+ return left.note.id - right.note.id;
189178
+ });
189179
+ }
189180
+ function searchNotes(args) {
189181
+ if (args.limit <= 0) {
189182
+ return [];
189183
+ }
189184
+ const notes = [
189185
+ ...getNotes(args.db, {
189186
+ sessionId: args.sessionId,
189187
+ type: "session",
189188
+ status: NOTE_SEARCHABLE_STATUSES
189189
+ }),
189190
+ ...getNotes(args.db, {
189191
+ projectPath: args.projectPath,
189192
+ type: "smart",
189193
+ status: NOTE_SEARCHABLE_STATUSES
189194
+ })
189195
+ ];
189196
+ if (notes.length === 0) {
189197
+ return [];
189198
+ }
189199
+ const baseList = rankNotesForNeedle(notes, args.query);
189200
+ const probes = args.probes ?? [];
189201
+ if (probes.length === 0) {
189202
+ const ranked2 = baseList.slice(0, args.limit);
189203
+ return ranked2.map((entry, rank) => ({
189204
+ source: "note",
189205
+ content: previewText(entry.text),
189206
+ score: linearDecayScore(rank, ranked2.length),
189207
+ noteId: entry.note.id,
189208
+ status: entry.note.status,
189209
+ createdAt: entry.note.createdAt,
189210
+ anchorOrdinal: entry.note.anchorOrdinal,
189211
+ sourceSessionId: entry.note.sessionId
189212
+ }));
189213
+ }
189214
+ const queryLists = [];
189215
+ if (baseList.length > 0) {
189216
+ queryLists.push({ rows: baseList, weight: 1 });
189217
+ }
189218
+ const probeWeights = new Map;
189219
+ for (const probe of probes) {
189220
+ const rows = rankNotesForNeedle(notes, probe);
189221
+ if (rows.length === 0) {
189222
+ continue;
189223
+ }
189224
+ const weight = probeDiscriminationWeight(rows.length, notes.length);
189225
+ probeWeights.set(probe, weight);
189226
+ queryLists.push({ rows, weight });
189227
+ }
189228
+ const fused = new Map;
189229
+ for (const list of queryLists) {
189230
+ list.rows.forEach((row, rank) => {
189231
+ const rrf = list.weight / (RRF_K + rank);
189232
+ const existing = fused.get(row.note.id);
189233
+ if (existing) {
189234
+ existing.score += rrf;
189235
+ } else {
189236
+ fused.set(row.note.id, { entry: row, score: rrf });
189237
+ }
189238
+ });
189239
+ }
189240
+ for (const match of fused.values()) {
189241
+ let best = 0;
189242
+ for (const probe of probes) {
189243
+ const weight = probeWeights.get(probe) ?? 0;
189244
+ if (weight > best && containsProbeVerbatim(match.entry.text, [probe])) {
189245
+ best = weight;
189246
+ }
189247
+ }
189248
+ if (best > 0) {
189249
+ match.score += best * VERBATIM_RANK_BONUS;
189250
+ }
189251
+ }
189252
+ const ranked = [...fused.values()].sort((left, right) => {
189253
+ if (right.score !== left.score) {
189254
+ return right.score - left.score;
189255
+ }
189256
+ if (right.entry.note.createdAt !== left.entry.note.createdAt) {
189257
+ return right.entry.note.createdAt - left.entry.note.createdAt;
189258
+ }
189259
+ return left.entry.note.id - right.entry.note.id;
189260
+ }).slice(0, args.limit);
189261
+ return ranked.map((entry, rank) => ({
189262
+ source: "note",
189263
+ content: previewText(entry.entry.text),
189264
+ score: linearDecayScore(rank, ranked.length),
189265
+ noteId: entry.entry.note.id,
189266
+ status: entry.entry.note.status,
189267
+ createdAt: entry.entry.note.createdAt,
189268
+ anchorOrdinal: entry.entry.note.anchorOrdinal,
189269
+ sourceSessionId: entry.entry.note.sessionId
189270
+ }));
189271
+ }
188682
189272
  function searchCompartmentChunks(args) {
188683
189273
  if (!args.queryEmbedding || args.limit <= 0 || !args.modelId || args.modelId === "off")
188684
189274
  return [];
@@ -188764,6 +189354,8 @@ function getSourceBoost(result) {
188764
189354
  return GIT_COMMIT_SOURCE_BOOST;
188765
189355
  case "primer":
188766
189356
  return PRIMER_SOURCE_BOOST;
189357
+ case "note":
189358
+ return 1;
188767
189359
  }
188768
189360
  }
188769
189361
  function compareUnifiedResults(left, right) {
@@ -188787,6 +189379,9 @@ function compareUnifiedResults(left, right) {
188787
189379
  if (left.source === "primer" && right.source === "primer") {
188788
189380
  return right.support - left.support || left.primerId - right.primerId;
188789
189381
  }
189382
+ if (left.source === "note" && right.source === "note") {
189383
+ return right.createdAt - left.createdAt || left.noteId - right.noteId;
189384
+ }
188790
189385
  return 0;
188791
189386
  }
188792
189387
  function toGitCommitResult(hit) {
@@ -188854,11 +189449,11 @@ function searchPrimers(args) {
188854
189449
  }
188855
189450
  function resolveSources(sources) {
188856
189451
  if (sources === undefined) {
188857
- return new Set(["memory", "message", "git_commit", "primer"]);
189452
+ return new Set(["memory", "message", "git_commit", "primer", "note"]);
188858
189453
  }
188859
189454
  const set2 = new Set;
188860
189455
  for (const source of sources) {
188861
- if (source === "memory" || source === "message" || source === "git_commit" || source === "primer") {
189456
+ if (source === "memory" || source === "message" || source === "git_commit" || source === "primer" || source === "note") {
188862
189457
  set2.add(source);
188863
189458
  }
188864
189459
  }
@@ -188881,6 +189476,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
188881
189476
  const runMessages = activeSources.has("message");
188882
189477
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
188883
189478
  const runPrimers = activeSources.has("primer") && memoryFeatureEnabled;
189479
+ const runNotes = activeSources.has("note");
188884
189480
  const runCompartmentChunks = runMessages && memoryFeatureEnabled && embeddingEnabled;
188885
189481
  const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks || runPrimers) && embeddingEnabled && isEmbeddingRuntimeEnabled();
188886
189482
  const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options.signal).catch((error51) => {
@@ -188916,7 +189512,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
188916
189512
  compartments: compartmentResults,
188917
189513
  limit: tierLimit
188918
189514
  });
188919
- const [memoryResults, gitCommitResults, primerResults] = await Promise.all([
189515
+ const [memoryResults, gitCommitResults, primerResults, noteResults] = await Promise.all([
188920
189516
  runMemory ? searchMemories({
188921
189517
  db,
188922
189518
  projectPath,
@@ -188943,9 +189539,23 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
188943
189539
  limit: tierLimit,
188944
189540
  queryEmbedding,
188945
189541
  queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null
189542
+ })) : Promise.resolve([]),
189543
+ runNotes ? Promise.resolve(searchNotes({
189544
+ db,
189545
+ sessionId,
189546
+ projectPath,
189547
+ query: trimmedQuery,
189548
+ limit: tierLimit,
189549
+ probes: messageProbes
188946
189550
  })) : Promise.resolve([])
188947
189551
  ]);
188948
- const results = [...memoryResults, ...primerResults, ...messageLikeResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
189552
+ const results = [
189553
+ ...memoryResults,
189554
+ ...primerResults,
189555
+ ...messageLikeResults,
189556
+ ...gitCommitResults,
189557
+ ...noteResults
189558
+ ].sort(compareUnifiedResults).slice(0, limit);
188949
189559
  const countRetrievals = options.countRetrievals ?? true;
188950
189560
  if (countRetrievals) {
188951
189561
  const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
@@ -189014,6 +189624,10 @@ function renderFragment(result, charCap) {
189014
189624
  const compressed = cavemanCompress(result.content, "ultra");
189015
189625
  return truncate(compressed, charCap);
189016
189626
  }
189627
+ case "note": {
189628
+ const compressed = cavemanCompress(result.content, "ultra");
189629
+ return truncate(compressed, charCap);
189630
+ }
189017
189631
  }
189018
189632
  }
189019
189633
  function buildAutoSearchHint(results, options = {}) {
@@ -189235,13 +189849,11 @@ async function runAutoSearchHintForPi(args) {
189235
189849
  };
189236
189850
  results = await unifiedSearchWithTimeout(db, sessionId, options.projectPath, rawPrompt, searchOptions, AUTO_SEARCH_TIMEOUT_MS);
189237
189851
  } catch (error51) {
189238
- log(`[auto-search] unified search failed for session ${sessionId}: ${error51 instanceof Error ? error51.message : String(error51)}`);
189239
- writeNoHintAndReconcile("error");
189852
+ log(`[auto-search] unified search failed for session ${sessionId} (will retry next pass): ${error51 instanceof Error ? error51.message : String(error51)}`);
189240
189853
  return messages;
189241
189854
  }
189242
189855
  if (results === null) {
189243
- sessionLog(sessionId, `auto-search: timed out after ${AUTO_SEARCH_TIMEOUT_MS}ms, skipping hint for this turn`);
189244
- writeNoHintAndReconcile("timeout");
189856
+ sessionLog(sessionId, `auto-search: timed out after ${AUTO_SEARCH_TIMEOUT_MS}ms, skipping hint for this turn (will retry)`);
189245
189857
  return messages;
189246
189858
  }
189247
189859
  if (results.length === 0) {
@@ -190816,6 +191428,15 @@ function findCompartmentBoundaryForSnapshot(markers) {
190816
191428
  return null;
190817
191429
  return markers.lastBaselineEndMessageId;
190818
191430
  }
191431
+ function resolveRenderedCompartmentBoundary(compartments, boundaryId) {
191432
+ if (!boundaryId)
191433
+ return { endMessageId: null, ordinal: null };
191434
+ const boundary = compartments.find((compartment) => compartment.endMessageId === boundaryId);
191435
+ return {
191436
+ endMessageId: boundaryId,
191437
+ ordinal: typeof boundary?.endMessage === "number" ? boundary.endMessage : null
191438
+ };
191439
+ }
190819
191440
  function prependM0M1Messages(piMessages, m0, m1) {
190820
191441
  const firstTimestamp = piMessages[0]?.timestamp;
190821
191442
  const baseTimestamp = typeof firstTimestamp === "number" ? firstTimestamp : Date.now();
@@ -190945,6 +191566,7 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
190945
191566
  }
190946
191567
  }
190947
191568
  const boundaryId = findCompartmentBoundaryForSnapshot(markers);
191569
+ const renderedBoundary = resolveRenderedCompartmentBoundary(currentCompartments, boundaryId);
190948
191570
  const skippedVisibleMessages = boundaryId ? trimPiMessagesToBoundary(piMessages, entryIds, boundaryId) : 0;
190949
191571
  prependM0M1Messages(piMessages, m0, m1);
190950
191572
  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 +191583,8 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
190961
191583
  m0Reason: decision.reason,
190962
191584
  m0Bytes: m0.length,
190963
191585
  m1Bytes: m1.length,
191586
+ contentionExhausted,
191587
+ renderedBoundary,
190964
191588
  syntheticLeadingCount: 2
190965
191589
  };
190966
191590
  }
@@ -194232,7 +194856,9 @@ async function runPiHistorian(deps) {
194232
194856
  compartmentLeaseHolderId,
194233
194857
  readBranchEntries,
194234
194858
  notifyIssue,
194235
- ensureProjectRegistered = ensureProjectRegisteredFromPiDirectory
194859
+ ensureProjectRegistered = ensureProjectRegisteredFromPiDirectory,
194860
+ forceDrainQuota,
194861
+ forceKeepLastCompartment
194236
194862
  } = deps;
194237
194863
  const notify = async (message) => {
194238
194864
  if (shouldSuppressHistorianAlert(sessionId)) {
@@ -194307,7 +194933,8 @@ async function runPiHistorian(deps) {
194307
194933
  if (protectedTailStart <= offset || eligibleEndOrdinal <= offset) {
194308
194934
  sessionLog(sessionId, `historian no-op: protectedTailStart=${protectedTailStart} eligibleEnd=${eligibleEndOrdinal} <= offset=${offset} — nothing to compact`);
194309
194935
  if (boundarySnapshot.usagePercentage < 80) {
194310
- clearEmergencyRecovery(db, sessionId);
194936
+ if (!isWrapupInProgress(db, sessionId))
194937
+ clearEmergencyRecovery(db, sessionId);
194311
194938
  } else {
194312
194939
  recordHighPressureNoEligibleHead(db, boundarySnapshot);
194313
194940
  }
@@ -194316,7 +194943,7 @@ async function runPiHistorian(deps) {
194316
194943
  }
194317
194944
  const perRunCap = selectPerRunCap(boundarySnapshot);
194318
194945
  const usable = Math.max(1, Math.round(boundarySnapshot.contextLimit * boundarySnapshot.executeThresholdPercentage / 100));
194319
- const reserve = reserveProtectedTailDrainTokens({
194946
+ const reserve = forceDrainQuota ? { ok: true, reservation: null } : reserveProtectedTailDrainTokens({
194320
194947
  db,
194321
194948
  sessionId,
194322
194949
  runId: crypto2.randomUUID(),
@@ -194334,10 +194961,12 @@ async function runPiHistorian(deps) {
194334
194961
  }
194335
194962
  drainReservation = reserve.reservation;
194336
194963
  const chunk = readSessionChunk(sessionId, historianChunkTokens, offset, eligibleEndOrdinal);
194964
+ const forceKeepLastCompartmentForChunk = forceKeepLastCompartment === true && !chunk.hasMore;
194337
194965
  if (!chunk.text || chunk.messageCount === 0) {
194338
194966
  sessionLog(sessionId, `historian no-op: chunk empty after filtering (messageCount=${chunk.messageCount}, textLen=${chunk.text?.length ?? 0}) range=${offset}-${protectedTailStart - 1}`);
194339
194967
  if (boundarySnapshot.usagePercentage < 80) {
194340
- clearEmergencyRecovery(db, sessionId);
194968
+ if (!isWrapupInProgress(db, sessionId))
194969
+ clearEmergencyRecovery(db, sessionId);
194341
194970
  } else {
194342
194971
  recordHighPressureNoEligibleHead(db, boundarySnapshot);
194343
194972
  }
@@ -194527,7 +195156,7 @@ ${chunkText}`,
194527
195156
  const inEmergency = getOverflowState(db, sessionId).needsEmergencyRecovery;
194528
195157
  const emittedCompartments = validatedPass.compartments;
194529
195158
  let newCompartments = emittedCompartments;
194530
- if (!inEmergency && emittedCompartments.length >= 2) {
195159
+ if (!inEmergency && !forceKeepLastCompartmentForChunk && emittedCompartments.length >= 2) {
194531
195160
  const lastEmitted = emittedCompartments[emittedCompartments.length - 1];
194532
195161
  const lookaheadMargin = chunk.endIndex - lastEmitted.endMessage;
194533
195162
  if (lookaheadMargin <= BOUNDARY_HEALING_SLACK) {
@@ -194560,9 +195189,19 @@ ${chunkText}`,
194560
195189
  }
194561
195190
  }
194562
195191
  const discardedLast = newCompartments.length < emittedCompartments.length;
195192
+ const weakLookaheadFinalCompartment = forceKeepLastCompartmentForChunk;
195193
+ const skipUnanchoredPromotion = discardedLast || weakLookaheadFinalCompartment;
194563
195194
  const embeddingActive = memoryEnabled !== false;
194564
195195
  const promotionActive = embeddingActive && autoPromote !== false;
194565
- const publishableEvents = (validatedPass.events ?? []).filter((e) => e.atCompartment == null || e.atCompartment <= newCompartments.length);
195196
+ const publishableEvents = (validatedPass.events ?? []).filter((e) => {
195197
+ if (typeof e.atCompartment !== "number")
195198
+ return !weakLookaheadFinalCompartment;
195199
+ if (e.atCompartment > newCompartments.length)
195200
+ return false;
195201
+ if (weakLookaheadFinalCompartment && e.atCompartment >= emittedCompartments.length)
195202
+ return false;
195203
+ return true;
195204
+ });
194566
195205
  let promotedFactRefs = [];
194567
195206
  let persistedIds = [];
194568
195207
  if (!compartmentLeaseHolderId) {
@@ -194581,7 +195220,7 @@ ${chunkText}`,
194581
195220
  }
194582
195221
  appendCompartments(db, sessionId, newCompartments);
194583
195222
  persistedIds = getCompartments(db, sessionId).slice(-newCompartments.length).map((c) => c.id);
194584
- if (promotionActive && !discardedLast) {
195223
+ if (promotionActive && !skipUnanchoredPromotion) {
194585
195224
  promotedFactRefs = promoteSessionFactsDurable(db, sessionId, projectPath, validatedPass.facts ?? []);
194586
195225
  }
194587
195226
  if (publishableEvents.length > 0) {
@@ -194596,7 +195235,8 @@ ${chunkText}`,
194596
195235
  clearHistorianFailureState(db, sessionId);
194597
195236
  clearHistorianDrainFailure(db, sessionId);
194598
195237
  recordProtectedTailPublicationFloor(db, sessionId, lastNewEnd + 1);
194599
- clearEmergencyRecovery(db, sessionId);
195238
+ if (!isWrapupInProgress(db, sessionId))
195239
+ clearEmergencyRecovery(db, sessionId);
194600
195240
  if (firstKeptEntryId && lastNewEndMessageId) {
194601
195241
  setPendingPiCompactionMarkerState(db, sessionId, {
194602
195242
  firstKeptEntryId,
@@ -194620,7 +195260,7 @@ ${chunkText}`,
194620
195260
  completedSuccessfully = true;
194621
195261
  sessionLog(sessionId, `historian: published ${newCompartments.length} compartment(s), ${validatedPass.facts?.length ?? 0} fact(s) covering messages ${chunk.startIndex}-${lastNewEnd}`);
194622
195262
  onNoteTrigger(db, sessionId, "historian_complete");
194623
- if (userMemoriesEnabled === true && !discardedLast && validatedPass.userObservations?.length) {
195263
+ if (userMemoriesEnabled === true && !skipUnanchoredPromotion && validatedPass.userObservations?.length) {
194624
195264
  try {
194625
195265
  insertUserMemoryCandidates(db, validatedPass.userObservations.map((obs) => ({
194626
195266
  content: obs,
@@ -194633,7 +195273,7 @@ ${chunkText}`,
194633
195273
  sessionLog(sessionId, "failed to store user memory candidates:", error51);
194634
195274
  }
194635
195275
  }
194636
- if (!discardedLast && validatedPass.primerCandidates?.length && projectPath) {
195276
+ if (!skipUnanchoredPromotion && validatedPass.primerCandidates?.length && projectPath) {
194637
195277
  try {
194638
195278
  const firstNew = newCompartments[0];
194639
195279
  const lastNew = newCompartments[newCompartments.length - 1];
@@ -195330,7 +195970,7 @@ Drop silently — do not narrate it. NEVER drop large ranges blindly (e.g., "1-5
195330
195970
  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
195971
  var CAVEMAN_COMPRESSION_WARNING = `
195332
195972
  **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) {
195973
+ function buildMagicContextSection(_agent, protectedTags, ctxReduceCallable = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false, language, memoryEnabled = true) {
195334
195974
  if (subagentMode) {
195335
195975
  return `## Magic Context
195336
195976
 
@@ -195341,12 +195981,12 @@ When \`surface_condition\` is provided with \`write\`, the note becomes a projec
195341
195981
  The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met.
195342
195982
  Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")\`` : "";
195343
195983
  const temporalGuidance = temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : "";
195344
- const cavemanWarning = cavemanTextCompressionEnabled && !ctxReduceEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
195984
+ const cavemanWarning = cavemanTextCompressionEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
195345
195985
  const languageDirective = buildPrimaryLanguageDirective(language);
195346
195986
  const languageGuidance = languageDirective ? `
195347
195987
 
195348
195988
  ${languageDirective}` : "";
195349
- if (!ctxReduceEnabled) {
195989
+ if (!ctxReduceCallable) {
195350
195990
  return `## Magic Context
195351
195991
 
195352
195992
  ${LONG_TERM_PARTNER_FRAME}
@@ -195359,7 +195999,7 @@ ${BASE_INTRO_NO_REDUCE(memoryEnabled)}${smartNoteGuidance}${temporalGuidance}${c
195359
195999
  ${LONG_TERM_PARTNER_FRAME}
195360
196000
  ${PARTNER_FRAME_CLOSER_REDUCE}
195361
196001
 
195362
- ${BASE_INTRO(protectedTags, memoryEnabled)}${smartNoteGuidance}${temporalGuidance}
196002
+ ${BASE_INTRO(protectedTags, memoryEnabled)}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}
195363
196003
  ${GENERIC_SECTION}
195364
196004
 
195365
196005
  Prefer many small targeted operations over one large blanket operation, and keep the working set tidy as routine maintenance.${languageGuidance}`;
@@ -195374,7 +196014,7 @@ function buildMagicContextBlock(opts) {
195374
196014
  const includeGuidance = (opts.includeGuidance ?? true) && !existing.includes(MAGIC_CONTEXT_MARKER);
195375
196015
  if (!includeGuidance)
195376
196016
  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);
196017
+ 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
196018
  }
195379
196019
  var DATE_PATTERN = /Today's date: .+/;
195380
196020
  function processSystemPromptForCache(args) {
@@ -196067,6 +196707,7 @@ function applyForwardPressureFloor(trailingPercentage, trailingInputTokens, piUs
196067
196707
  inputTokens: Math.max(trailingInputTokens, forwardTokens)
196068
196708
  } : { percentage: trailingPercentage, inputTokens: trailingInputTokens };
196069
196709
  }
196710
+ var injectM0M1PiForRun = injectM0M1Pi;
196070
196711
  var DEFAULT_CLEAR_REASONING_AGE = 50;
196071
196712
  var PI_STABLE_ID_SCHEME = 1;
196072
196713
  var lastEmergencyNotificationAtMs = new Map;
@@ -196766,7 +197407,6 @@ function registerPiContextHandler(pi, baseOptions) {
196766
197407
  projectIdentity,
196767
197408
  projectDirectory,
196768
197409
  messages: event.messages,
196769
- ctxReduceEnabled: options.ctxReduceEnabled,
196770
197410
  smartDrops: options.smartDrops === true,
196771
197411
  protectedTags: options.protectedTags ?? 20,
196772
197412
  heuristics: options.heuristics,
@@ -196799,7 +197439,8 @@ function registerPiContextHandler(pi, baseOptions) {
196799
197439
  },
196800
197440
  temporalAwareness: options.injection?.temporalAwareness === true,
196801
197441
  appendCompaction: resolvePiAppendCompaction(ctx),
196802
- readBranchEntries: resolvePiReadBranchEntries(ctx)
197442
+ readBranchEntries: resolvePiReadBranchEntries(ctx),
197443
+ isSubagent: sessionMeta.isSubagent
196803
197444
  });
196804
197445
  const piDecisionSnapshotNewestAssistant = branchEntries === null ? undefined : findNewestPiAssistantEntryId(branchEntries);
196805
197446
  if (result.bustedThisPass && piDecisionSnapshotNewestAssistant !== undefined) {
@@ -196881,7 +197522,7 @@ function registerPiContextHandler(pi, baseOptions) {
196881
197522
  }
196882
197523
  try {
196883
197524
  const sessionMetaForCh1 = getOrCreateSessionMeta(options.db, sessionId);
196884
- if (options.ctxReduceEnabled && !sessionMetaForCh1.isSubagent) {
197525
+ if (!sessionMetaForCh1.isSubagent) {
196885
197526
  const resolvedExecuteThresholdPct = resolveExecuteThreshold(schedulerConfig.executeThresholdPercentage ?? 65, liveModelBySession.get(sessionId), 65, {
196886
197527
  tokensConfig: schedulerConfig.executeThresholdTokens,
196887
197528
  contextLimit: usageContextLimit ?? 0
@@ -197009,8 +197650,12 @@ function resolveHistoryBudgetTokensForPi(args) {
197009
197650
  }
197010
197651
  function startPiCompartmentLeaseRenewal(db, sessionId, holderId) {
197011
197652
  return setInterval(() => {
197012
- if (!renewCompartmentLease(db, sessionId, holderId)) {
197013
- sessionLog(sessionId, "compartment lease renewal failed; publish will be skipped if holder is stale");
197653
+ try {
197654
+ if (!renewCompartmentLease(db, sessionId, holderId)) {
197655
+ sessionLog(sessionId, "compartment lease renewal failed; publish will be skipped if holder is stale");
197656
+ }
197657
+ } catch (err) {
197658
+ sessionLog(sessionId, `compartment lease renewal threw; publish will be skipped if holder is stale (${err instanceof Error ? err.message : String(err)})`);
197014
197659
  }
197015
197660
  }, COMPARTMENT_LEASE_RENEWAL_MS);
197016
197661
  }
@@ -197070,6 +197715,11 @@ function spawnPiHistorianRun(args) {
197070
197715
  sessionLog(sessionId, "historian skipped: compartment lease held by another process");
197071
197716
  return;
197072
197717
  }
197718
+ if (isWrapupInProgress(db, sessionId)) {
197719
+ sessionLog(sessionId, "historian skipped: /ctx-wrapup became active");
197720
+ releaseCompartmentLease(db, sessionId, holderId);
197721
+ return;
197722
+ }
197073
197723
  const renewal = startPiCompartmentLeaseRenewal(db, sessionId, holderId);
197074
197724
  try {
197075
197725
  await runPiHistorian({
@@ -197148,6 +197798,10 @@ function maybeFireHistorian(args) {
197148
197798
  sessionLog(sessionId, "historian trigger eval: in-flight, skipping");
197149
197799
  return;
197150
197800
  }
197801
+ if (isWrapupInProgress(db, sessionId)) {
197802
+ sessionLog(sessionId, "historian trigger eval: /ctx-wrapup active, skipping");
197803
+ return;
197804
+ }
197151
197805
  let usage;
197152
197806
  let usageContextLimit;
197153
197807
  try {
@@ -197290,6 +197944,14 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
197290
197944
  unregister();
197291
197945
  }
197292
197946
  }
197947
+ function pendingPiMarkerCoveredByRenderedBoundary(pending, injection) {
197948
+ if (!injection || injection.contentionExhausted)
197949
+ return false;
197950
+ const boundary = injection.renderedBoundary;
197951
+ if (pending.endMessageId === boundary.endMessageId)
197952
+ return true;
197953
+ return boundary.ordinal !== null && pending.ordinal <= boundary.ordinal;
197954
+ }
197293
197955
  async function runPipeline(args) {
197294
197956
  let executedWorkThisPass = false;
197295
197957
  let historyWasConsumedThisPass = false;
@@ -197303,6 +197965,7 @@ async function runPipeline(args) {
197303
197965
  let emergency = false;
197304
197966
  let autoReclaimDidMutateThisPass = false;
197305
197967
  let suppressDeferredHistoryDrain = false;
197968
+ let deferredMaterializationConsumedThisPass = false;
197306
197969
  let casLost = false;
197307
197970
  const deferredHistoryWasPendingAtPassStart = deferredHistoryRefreshSessions.has(args.sessionId);
197308
197971
  if (args.temporalAwareness) {
@@ -197325,6 +197988,8 @@ async function runPipeline(args) {
197325
197988
  return findLatestUserMessageIdPi(args.messages, ids)?.messageId ?? null;
197326
197989
  })();
197327
197990
  const alreadyRanHeuristicsThisTurn = currentTurnId !== null && lastHeuristicsTurnIdBySession.get(args.sessionId) === currentTurnId;
197991
+ const sessionMetaForAvailability = getOrCreateSessionMeta(args.db, args.sessionId);
197992
+ const ctxReduceCallable = !sessionMetaForAvailability.isSubagent;
197328
197993
  const canConsumeDeferredLate = args.schedulerDecision === "execute" || args.forceMaterialization === true || args.contextUsage.percentage >= FORCE_MATERIALIZATION_PERCENTAGE;
197329
197994
  const deferredMaterializeEligible = canConsumeDeferredLate && deferredMaterializationSessions.has(args.sessionId);
197330
197995
  const piHardSignals = args.injection ? (() => {
@@ -197365,7 +198030,7 @@ async function runPipeline(args) {
197365
198030
  }
197366
198031
  const tTag = performance.now();
197367
198032
  const { targets } = tagTranscript(args.sessionId, transcript, args.tagger, args.db, {
197368
- skipPrefixInjection: !args.ctxReduceEnabled,
198033
+ skipPrefixInjection: !ctxReduceCallable,
197369
198034
  entryFingerprintByMessageId
197370
198035
  });
197371
198036
  logTransformTiming(args.sessionId, "tagMessages", tTag);
@@ -197451,14 +198116,16 @@ async function runPipeline(args) {
197451
198116
  sessionLog(args.sessionId, `reasoning replay failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
197452
198117
  }
197453
198118
  }
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`);
198119
+ if (args.heuristics?.caveman?.enabled && !args.isSubagent) {
198120
+ try {
198121
+ const tags = getTagsByNumbers(args.db, args.sessionId, targetTagNumbers);
198122
+ const replayed = replayCavemanCompression(args.sessionId, args.db, targets, tags);
198123
+ if (replayed > 0) {
198124
+ sessionLog(args.sessionId, `caveman replay: ${replayed} tags re-compressed from source`);
198125
+ }
198126
+ } catch (err) {
198127
+ sessionLog(args.sessionId, `caveman replay failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
197459
198128
  }
197460
- } catch (err) {
197461
- sessionLog(args.sessionId, `caveman replay failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
197462
198129
  }
197463
198130
  let heuristicsExecuted = false;
197464
198131
  let heuristicsResult = null;
@@ -197481,7 +198148,7 @@ async function runPipeline(args) {
197481
198148
  currentTotalInputTokens: args.contextUsage.inputTokens,
197482
198149
  ceilingTokens: args.emergencyCeilingTokens
197483
198150
  } : undefined,
197484
- caveman: args.heuristics.caveman
198151
+ caveman: args.isSubagent ? undefined : args.heuristics.caveman
197485
198152
  }, activeTags, stableIdResolver);
197486
198153
  const heuristicMutationCount = heuristicsResult.droppedTools + heuristicsResult.deduplicatedTools + heuristicsResult.droppedInjections + heuristicsResult.droppedStaleReduceCalls + heuristicsResult.mutatedTextTags;
197487
198154
  droppedCount += heuristicsResult.droppedTools + heuristicsResult.deduplicatedTools + heuristicsResult.droppedInjections + heuristicsResult.droppedStaleReduceCalls + heuristicsResult.mutatedTextTags;
@@ -197504,7 +198171,7 @@ async function runPipeline(args) {
197504
198171
  if (deferredMaterialize && pendingOpsAppliedThisPass) {
197505
198172
  const fullPassSucceeded = shouldRunHeuristics ? heuristicsExecuted : true;
197506
198173
  if (fullPassSucceeded) {
197507
- consumeDeferredMaterialization(args.sessionId);
198174
+ deferredMaterializationConsumedThisPass = consumeDeferredMaterialization(args.sessionId);
197508
198175
  }
197509
198176
  }
197510
198177
  if (args.reasoningClearing && shouldRunHeuristics) {
@@ -197621,7 +198288,7 @@ async function runPipeline(args) {
197621
198288
  if (args.injection) {
197622
198289
  try {
197623
198290
  const tInjection = performance.now();
197624
- injectionResult = injectM0M1Pi({
198291
+ injectionResult = injectM0M1PiForRun({
197625
198292
  sessionId: args.sessionId,
197626
198293
  projectIdentity: args.projectIdentity,
197627
198294
  projectDirectory: args.projectDirectory,
@@ -197653,11 +198320,23 @@ async function runPipeline(args) {
197653
198320
  forceDiscovery: args.stableIdSchemeCutover === true
197654
198321
  });
197655
198322
  const deferredHistoryDrainEligible = historyWasConsumedThisPass && materializationSatisfiedThisPass && (deferredHistoryWasPendingAtPassStart || hasPendingMaterializeSignal) && !suppressDeferredHistoryDrain && !casLost;
198323
+ let preserveDeferredMaterializationForMarkerDrain = false;
197656
198324
  if (deferredHistoryDrainEligible) {
197657
198325
  try {
197658
198326
  const pending = getPendingPiCompactionMarkerState(args.db, args.sessionId);
197659
198327
  if (!pending) {
197660
- consumeDeferredHistoryRefresh(args.sessionId);
198328
+ if (injectionResult?.contentionExhausted === true) {
198329
+ suppressDeferredHistoryDrain = true;
198330
+ preserveDeferredMaterializationForMarkerDrain = true;
198331
+ sessionLog(args.sessionId, "Pi deferred-history drain skipped: m[0]/m[1] used a contention fallback; preserving deferred signals");
198332
+ } else {
198333
+ consumeDeferredHistoryRefresh(args.sessionId);
198334
+ }
198335
+ } else if (!pendingPiMarkerCoveredByRenderedBoundary(pending, injectionResult)) {
198336
+ suppressDeferredHistoryDrain = true;
198337
+ preserveDeferredMaterializationForMarkerDrain = true;
198338
+ const boundary = injectionResult?.renderedBoundary;
198339
+ 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
198340
  } else if (!args.appendCompaction || !args.readBranchEntries) {
197662
198341
  suppressDeferredHistoryDrain = true;
197663
198342
  sessionLog(args.sessionId, "Pi compaction-marker drain skipped: sessionManager appendCompaction/getBranch unavailable; preserving deferred-history signal");
@@ -197680,6 +198359,9 @@ async function runPipeline(args) {
197680
198359
  sessionLog(args.sessionId, `Pi compaction-marker drain failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
197681
198360
  }
197682
198361
  }
198362
+ if (preserveDeferredMaterializationForMarkerDrain && deferredMaterializationConsumedThisPass) {
198363
+ signalPiDeferredMaterialization(args.sessionId);
198364
+ }
197683
198365
  if (executedWorkThisPass) {
197684
198366
  try {
197685
198367
  const currentFlag = peekDeferredExecutePending(args.db, args.sessionId);
@@ -199235,13 +199917,23 @@ function withPublishedCallback(deps) {
199235
199917
  }
199236
199918
  function startLeaseRenewal(deps, holderId) {
199237
199919
  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");
199920
+ try {
199921
+ if (!renewCompartmentLease(deps.db, deps.sessionId, holderId)) {
199922
+ sessionLog(deps.sessionId, "compartment lease renewal failed; publish will be skipped if holder is stale");
199923
+ }
199924
+ } catch (err) {
199925
+ sessionLog(deps.sessionId, `compartment lease renewal threw; publish will be skipped if holder is stale (${err instanceof Error ? err.message : String(err)})`);
199240
199926
  }
199241
199927
  }, COMPARTMENT_LEASE_RENEWAL_MS);
199242
199928
  }
199243
199929
  async function executeContextRecompWithResult(deps, options = {}) {
199244
199930
  const { sessionId } = deps;
199931
+ if (isWrapupInProgress(deps.db, sessionId)) {
199932
+ return {
199933
+ message: "## Magic Recomp — Skipped\n\n/ctx-wrapup is already compacting this session. Wait for it to finish, then try `/ctx-recomp` again.",
199934
+ published: false
199935
+ };
199936
+ }
199245
199937
  if (activeRuns.has(sessionId)) {
199246
199938
  return {
199247
199939
  message: "## Magic Recomp — Skipped\n\nHistorian is already running for this session. Wait for it to finish, then try `/ctx-recomp` again.",
@@ -199265,7 +199957,7 @@ async function executeContextRecompWithResult(deps, options = {}) {
199265
199957
  }).catch((err) => {
199266
199958
  sessionLog(sessionId, "compartment agent: recomp unhandled rejection:", err);
199267
199959
  });
199268
- activeRuns.set(sessionId, { promise: wrappedPromise, published: false });
199960
+ activeRuns.set(sessionId, { promise: wrappedPromise, published: false, kind: "recomp" });
199269
199961
  try {
199270
199962
  const message = await promise2;
199271
199963
  const published = activeRuns.get(sessionId)?.published === true;
@@ -199597,6 +200289,14 @@ ${parsed.message}`,
199597
200289
  });
199598
200290
  return;
199599
200291
  }
200292
+ if (isWrapupInProgress(deps.db, sessionId)) {
200293
+ sendCtxStatusMessage(pi, {
200294
+ title: "/ctx-recomp",
200295
+ text: "## Magic Recomp\n\n/ctx-wrapup is already compacting this session. Wait for it to finish, then try `/ctx-recomp` again.",
200296
+ level: "warning"
200297
+ });
200298
+ return;
200299
+ }
199600
200300
  if (isPiRecompInFlight(sessionId)) {
199601
200301
  sendCtxStatusMessage(pi, {
199602
200302
  title: "/ctx-recomp",
@@ -199988,6 +200688,16 @@ No active Pi session is available.`,
199988
200688
  });
199989
200689
  return;
199990
200690
  }
200691
+ if (isWrapupInProgress(deps.db, sessionId)) {
200692
+ sendCtxStatusMessage(pi, {
200693
+ title: "/ctx-session-upgrade",
200694
+ text: `## Session Upgrade
200695
+
200696
+ /ctx-wrapup is already compacting this session. Wait for it to finish, then try again.`,
200697
+ level: "warning"
200698
+ });
200699
+ return;
200700
+ }
199991
200701
  if (isPiRecompInFlight(sessionId)) {
199992
200702
  sendCtxStatusMessage(pi, {
199993
200703
  title: "/ctx-session-upgrade",
@@ -200319,7 +201029,7 @@ function formatThresholdPercent(value) {
200319
201029
  // package.json
200320
201030
  var package_default = {
200321
201031
  name: "@cortexkit/pi-magic-context",
200322
- version: "0.30.7",
201032
+ version: "0.31.1",
200323
201033
  type: "module",
200324
201034
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
200325
201035
  main: "dist/index.js",
@@ -200585,7 +201295,6 @@ ${safeStringify(tool.parameters)}`);
200585
201295
  inputTokens,
200586
201296
  systemPromptTokens,
200587
201297
  compartmentCount: compartments.length,
200588
- factCount: 0,
200589
201298
  memoryCount: safeRead(() => getMemoryCount(deps.db, deps.projectIdentity), 0),
200590
201299
  memoryBlockCount,
200591
201300
  sessionNoteCount: safeRead(() => getNotes(deps.db, {
@@ -200861,6 +201570,365 @@ function readHistorianState(db, sessionId, meta3) {
200861
201570
  };
200862
201571
  }
200863
201572
 
201573
+ // src/commands/ctx-wrapup.ts
201574
+ import * as crypto4 from "node:crypto";
201575
+ var DEFAULT_MESSAGES_TO_KEEP = 20;
201576
+ var LEASE_WAIT_MS = 1000;
201577
+ function parseWrapupArgs(raw) {
201578
+ const trimmed = raw.trim();
201579
+ if (trimmed === "")
201580
+ return { ok: true, messagesToKeep: DEFAULT_MESSAGES_TO_KEEP };
201581
+ if (!/^\d+$/.test(trimmed)) {
201582
+ return {
201583
+ ok: false,
201584
+ message: "Usage: `/ctx-wrapup [messages_to_keep]` where messages_to_keep is a positive integer."
201585
+ };
201586
+ }
201587
+ const messagesToKeep = Number.parseInt(trimmed, 10);
201588
+ if (!Number.isSafeInteger(messagesToKeep) || messagesToKeep <= 0) {
201589
+ return {
201590
+ ok: false,
201591
+ message: "messages_to_keep must be a positive integer."
201592
+ };
201593
+ }
201594
+ return { ok: true, messagesToKeep };
201595
+ }
201596
+ function registerCtxWrapupCommand(pi, deps) {
201597
+ pi.registerCommand("ctx-wrapup", {
201598
+ description: "Compact older Magic Context history while keeping the newest messages raw",
201599
+ handler: async (args, ctx) => {
201600
+ const sessionId = resolveSessionId(ctx);
201601
+ if (!sessionId) {
201602
+ sendCtxStatusMessage(pi, {
201603
+ title: "/ctx-wrapup",
201604
+ text: `## Magic Wrapup
201605
+
201606
+ No active Pi session is available.`,
201607
+ level: "error"
201608
+ });
201609
+ return;
201610
+ }
201611
+ const sessionMeta = getOrCreateSessionMeta(deps.db, sessionId);
201612
+ if (sessionMeta.isSubagent) {
201613
+ sendCtxStatusMessage(pi, {
201614
+ title: "/ctx-wrapup",
201615
+ text: `## Magic Wrapup — Skipped
201616
+
201617
+ /ctx-wrapup is only available in primary sessions.`,
201618
+ level: "warning"
201619
+ });
201620
+ return;
201621
+ }
201622
+ if (!deps.historianModel) {
201623
+ sendCtxStatusMessage(pi, {
201624
+ title: "/ctx-wrapup",
201625
+ text: "## Magic Wrapup\n\n/ctx-wrapup is unavailable because `historian.model` is not configured.",
201626
+ level: "error"
201627
+ });
201628
+ return;
201629
+ }
201630
+ const parsed = parseWrapupArgs(args);
201631
+ if (!parsed.ok) {
201632
+ sendCtxStatusMessage(pi, {
201633
+ title: "/ctx-wrapup",
201634
+ text: `## Magic Wrapup — Invalid Arguments
201635
+
201636
+ ${parsed.message}`,
201637
+ level: "error"
201638
+ });
201639
+ return;
201640
+ }
201641
+ const result = await runPiWrapup(pi, deps, ctx, sessionId, parsed.messagesToKeep);
201642
+ sendCtxStatusMessage(pi, {
201643
+ title: "/ctx-wrapup",
201644
+ text: result,
201645
+ level: result.includes("Failed") || result.includes("Partial") ? "error" : "success"
201646
+ });
201647
+ }
201648
+ });
201649
+ }
201650
+ async function runPiWrapup(pi, deps, ctx, sessionId, messagesToKeep) {
201651
+ if (getOrCreateSessionMeta(deps.db, sessionId).isSubagent) {
201652
+ return `## Magic Wrapup — Skipped
201653
+
201654
+ /ctx-wrapup is only available in primary sessions.`;
201655
+ }
201656
+ if (isPiRecompInFlight(sessionId)) {
201657
+ 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.";
201658
+ }
201659
+ const provider2 = { readMessages: () => readPiSessionMessages(ctx) };
201660
+ const unregister = setRawMessageProvider(sessionId, provider2);
201661
+ let holderId = "";
201662
+ try {
201663
+ const contextLimit = resolvePiContextLimit(ctx);
201664
+ const modelKey = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
201665
+ const executeThresholdPercentage = resolveExecuteThreshold(deps.executeThresholdPercentage ?? 65, modelKey, 65, {
201666
+ tokensConfig: deps.executeThresholdTokens,
201667
+ contextLimit,
201668
+ sessionId
201669
+ });
201670
+ const initialPlan = resolveWrapupProtectedTailBoundary({
201671
+ db: deps.db,
201672
+ sessionId,
201673
+ mode: "manual-wrapup",
201674
+ contextLimit,
201675
+ executeThresholdPercentage,
201676
+ usage: { percentage: 0, inputTokens: 0 },
201677
+ usageSource: "manual-none",
201678
+ providerShapeVersion: "pi-folded-v1",
201679
+ cacheNamespace: `pi:${sessionId}`,
201680
+ messagesToKeep
201681
+ });
201682
+ if (initialPlan.rawMessagesAboveLastCompartment <= messagesToKeep) {
201683
+ return `## Magic Wrapup
201684
+
201685
+ Nothing to wrap up — only ${initialPlan.rawMessagesAboveLastCompartment} messages above the last compartment.`;
201686
+ }
201687
+ if (!hasRunnableCompartmentWindow(initialPlan.snapshot)) {
201688
+ return `## Magic Wrapup — Partial
201689
+
201690
+ No runnable wrapup boundary is available yet; wrapped up 0 messages into 0 compartments. Run /ctx-wrapup again to continue.`;
201691
+ }
201692
+ holderId = crypto4.randomUUID();
201693
+ const acquired = acquireWrapupInProgress(deps.db, sessionId, {
201694
+ holderId,
201695
+ messagesToKeep,
201696
+ anchorRawMessageCount: initialPlan.anchorRawMessageCount,
201697
+ targetEligibleEndOrdinal: initialPlan.targetEligibleEndOrdinal,
201698
+ lastCompartmentEnd: getLastCompartmentEndMessage(deps.db, sessionId),
201699
+ chunkIndex: 0,
201700
+ expectedChunks: estimateChunks(initialPlan.snapshot.trueRawEligibleTokens, deps.historianChunkTokens)
201701
+ });
201702
+ if (!acquired.ok) {
201703
+ return formatExistingWrapup(acquired.state ?? getWrapupInProgressState(deps.db, sessionId));
201704
+ }
201705
+ let ownershipLost = false;
201706
+ const ownershipLostReason = "another process took over this session's wrapup";
201707
+ const markOwnershipLost = () => {
201708
+ if (ownershipLost)
201709
+ return;
201710
+ ownershipLost = true;
201711
+ };
201712
+ const renewWrapupMarker = (updates) => {
201713
+ const updated = updateWrapupInProgress(deps.db, sessionId, holderId, updates);
201714
+ if (!updated) {
201715
+ markOwnershipLost();
201716
+ return false;
201717
+ }
201718
+ return true;
201719
+ };
201720
+ const renewal = setInterval(() => {
201721
+ try {
201722
+ renewWrapupMarker({
201723
+ lastCompartmentEnd: getLastCompartmentEndMessage(deps.db, sessionId)
201724
+ });
201725
+ } catch (err) {
201726
+ console.warn(`[magic-context][pi] /ctx-wrapup marker renewal failed for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
201727
+ }
201728
+ }, 60000);
201729
+ try {
201730
+ sendCtxStatusMessage(pi, {
201731
+ title: "/ctx-wrapup",
201732
+ text: `## Magic Wrapup
201733
+
201734
+ Eligible history is about ${initialPlan.snapshot.trueRawEligibleTokens.toLocaleString()} tokens across approximately ${estimateChunks(initialPlan.snapshot.trueRawEligibleTokens, deps.historianChunkTokens)} historian chunk(s).`,
201735
+ level: "info"
201736
+ });
201737
+ const startEnd = getLastCompartmentEndMessage(deps.db, sessionId);
201738
+ const startCompartmentCount = getCompartments(deps.db, sessionId).length;
201739
+ let chunkIndex = 0;
201740
+ let lastEnd = startEnd;
201741
+ let targetEligibleEndOrdinal = initialPlan.targetEligibleEndOrdinal;
201742
+ let failure = null;
201743
+ for (;; ) {
201744
+ if (ownershipLost) {
201745
+ failure = `${ownershipLostReason}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.`;
201746
+ break;
201747
+ }
201748
+ if (!renewWrapupMarker({
201749
+ chunkIndex,
201750
+ lastCompartmentEnd: getLastCompartmentEndMessage(deps.db, sessionId)
201751
+ })) {
201752
+ failure = `${ownershipLostReason}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.`;
201753
+ break;
201754
+ }
201755
+ const plan = resolveWrapupProtectedTailBoundary({
201756
+ db: deps.db,
201757
+ sessionId,
201758
+ mode: "manual-wrapup",
201759
+ contextLimit,
201760
+ executeThresholdPercentage,
201761
+ usage: { percentage: 0, inputTokens: 0 },
201762
+ usageSource: "manual-none",
201763
+ providerShapeVersion: "pi-folded-v1",
201764
+ cacheNamespace: `pi:${sessionId}`,
201765
+ messagesToKeep,
201766
+ anchorRawMessageCount: initialPlan.anchorRawMessageCount
201767
+ });
201768
+ targetEligibleEndOrdinal = plan.targetEligibleEndOrdinal;
201769
+ lastEnd = getLastCompartmentEndMessage(deps.db, sessionId);
201770
+ if (lastEnd + 1 >= targetEligibleEndOrdinal)
201771
+ break;
201772
+ if (!hasRunnableCompartmentWindow(plan.snapshot)) {
201773
+ failure = `No runnable wrapup boundary is available yet; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.`;
201774
+ break;
201775
+ }
201776
+ chunkIndex += 1;
201777
+ if (!renewWrapupMarker({
201778
+ chunkIndex,
201779
+ lastCompartmentEnd: getLastCompartmentEndMessage(deps.db, sessionId),
201780
+ targetEligibleEndOrdinal: plan.targetEligibleEndOrdinal,
201781
+ expectedChunks: Math.max(chunkIndex, estimateChunks(plan.snapshot.trueRawEligibleTokens, deps.historianChunkTokens))
201782
+ })) {
201783
+ failure = `${ownershipLostReason}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.`;
201784
+ break;
201785
+ }
201786
+ sendCtxStatusMessage(pi, {
201787
+ title: "/ctx-wrapup",
201788
+ text: `## Magic Wrapup
201789
+
201790
+ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.eligibleEndOrdinal - 1} (~${plan.snapshot.trueRawEligibleTokens.toLocaleString()} eligible tokens remain).`,
201791
+ level: "info"
201792
+ });
201793
+ const leaseHolder = await acquireCompartmentLeaseEventually(deps.db, sessionId, renewWrapupMarker);
201794
+ if (!leaseHolder) {
201795
+ 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.";
201796
+ break;
201797
+ }
201798
+ const leaseRenewal = setInterval(() => {
201799
+ try {
201800
+ renewCompartmentLease(deps.db, sessionId, leaseHolder);
201801
+ } catch (err) {
201802
+ console.warn(`[magic-context][pi] /ctx-wrapup compartment lease renewal failed for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
201803
+ }
201804
+ }, COMPARTMENT_LEASE_RENEWAL_MS);
201805
+ try {
201806
+ const runHistorian = deps.runPiHistorianForWrapup ?? runPiHistorian;
201807
+ await runHistorian({
201808
+ db: deps.db,
201809
+ sessionId,
201810
+ directory: ctx.cwd,
201811
+ provider: provider2,
201812
+ runner: deps.runner,
201813
+ historianModel: deps.historianModel,
201814
+ fallbackModels: deps.historianFallbacks,
201815
+ historianChunkTokens: deps.historianChunkTokens,
201816
+ boundarySnapshot: plan.snapshot,
201817
+ refreshBoundarySnapshot: () => resolveWrapupProtectedTailBoundary({
201818
+ db: deps.db,
201819
+ sessionId,
201820
+ mode: "manual-wrapup",
201821
+ contextLimit,
201822
+ executeThresholdPercentage,
201823
+ usage: { percentage: 0, inputTokens: 0 },
201824
+ usageSource: "manual-none",
201825
+ providerShapeVersion: "pi-folded-v1",
201826
+ cacheNamespace: `pi:${sessionId}`,
201827
+ messagesToKeep,
201828
+ anchorRawMessageCount: initialPlan.anchorRawMessageCount
201829
+ }).snapshot,
201830
+ currentContextLimit: contextLimit,
201831
+ historianTimeoutMs: deps.historianTimeoutMs,
201832
+ thinkingLevel: deps.historianThinkingLevel,
201833
+ memoryEnabled: deps.memoryEnabled,
201834
+ autoPromote: deps.autoPromote,
201835
+ userMemoriesEnabled: deps.userMemoriesEnabled,
201836
+ language: deps.language,
201837
+ compartmentLeaseHolderId: leaseHolder,
201838
+ readBranchEntries: () => readBranchEntries(ctx),
201839
+ notifyIssue: (text) => sendCtxStatusMessage(pi, {
201840
+ title: "/ctx-wrapup",
201841
+ text,
201842
+ level: "warning"
201843
+ }),
201844
+ ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
201845
+ forceDrainQuota: true,
201846
+ forceKeepLastCompartment: true,
201847
+ onPublished: () => {
201848
+ updateStatusLine(ctx, { db: deps.db, projectIdentity: ctx.cwd });
201849
+ signalPiDeferredHistoryRefresh(sessionId);
201850
+ signalPiDeferredMaterialization(sessionId);
201851
+ }
201852
+ });
201853
+ } finally {
201854
+ clearInterval(leaseRenewal);
201855
+ releaseCompartmentLease(deps.db, sessionId, leaseHolder);
201856
+ }
201857
+ const afterEnd = getLastCompartmentEndMessage(deps.db, sessionId);
201858
+ if (afterEnd <= lastEnd) {
201859
+ failure = `No forward progress after chunk ${chunkIndex}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.`;
201860
+ break;
201861
+ }
201862
+ lastEnd = afterEnd;
201863
+ }
201864
+ const finalEnd = getLastCompartmentEndMessage(deps.db, sessionId);
201865
+ if (!failure && finalEnd + 1 < targetEligibleEndOrdinal) {
201866
+ failure = `Wrapped up through message ${finalEnd}, but no runnable wrapup boundary remained before target message ${targetEligibleEndOrdinal}. Run /ctx-wrapup again to continue.`;
201867
+ }
201868
+ const finalCompartmentCount = getCompartments(deps.db, sessionId).length;
201869
+ const messagesWrapped = Math.max(0, finalEnd - startEnd);
201870
+ const compartmentsCreated = Math.max(0, finalCompartmentCount - startCompartmentCount);
201871
+ if (failure) {
201872
+ return `## Magic Wrapup — Partial
201873
+
201874
+ Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments; ${failure}`;
201875
+ }
201876
+ try {
201877
+ clearEmergencyRecovery(deps.db, sessionId);
201878
+ } catch {}
201879
+ return `## Magic Wrapup
201880
+
201881
+ Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments. The compacted history is queued and materializes on your next message.`;
201882
+ } finally {
201883
+ clearInterval(renewal);
201884
+ releaseWrapupInProgress(deps.db, sessionId, holderId);
201885
+ }
201886
+ } finally {
201887
+ unregister();
201888
+ }
201889
+ }
201890
+ function resolvePiContextLimit(ctx) {
201891
+ const usage = ctx.getContextUsage?.();
201892
+ if (typeof usage?.contextWindow === "number" && usage.contextWindow > 0) {
201893
+ return usage.contextWindow;
201894
+ }
201895
+ if (typeof ctx.model?.contextWindow === "number" && ctx.model.contextWindow > 0) {
201896
+ return ctx.model.contextWindow;
201897
+ }
201898
+ return 128000;
201899
+ }
201900
+ async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker) {
201901
+ for (;; ) {
201902
+ const holderId = crypto4.randomUUID();
201903
+ const lease = acquireCompartmentLease(db, sessionId, holderId);
201904
+ if (lease)
201905
+ return holderId;
201906
+ if (!renewWrapupMarker({}))
201907
+ return null;
201908
+ await new Promise((resolve3) => setTimeout(resolve3, LEASE_WAIT_MS));
201909
+ }
201910
+ }
201911
+ function estimateChunks(tokens, chunkTokens) {
201912
+ return Math.max(1, Math.ceil(Math.max(0, tokens) / Math.max(1, chunkTokens)));
201913
+ }
201914
+ function formatExistingWrapup(state) {
201915
+ if (!state) {
201916
+ return `## Magic Wrapup — Skipped
201917
+
201918
+ Another /ctx-wrapup is already compacting this session. Wait for it to finish, then try again.`;
201919
+ }
201920
+ return `## Magic Wrapup — Skipped
201921
+
201922
+ 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.`;
201923
+ }
201924
+ function readBranchEntries(ctx) {
201925
+ const getBranch = ctx.sessionManager.getBranch;
201926
+ if (typeof getBranch !== "function")
201927
+ return [];
201928
+ const branch = getBranch.call(ctx.sessionManager);
201929
+ return Array.isArray(branch?.entries) ? branch.entries : [];
201930
+ }
201931
+
200864
201932
  // src/pi-pressure.ts
200865
201933
  function extractAssistantUsage(message) {
200866
201934
  if (!message || typeof message !== "object")
@@ -206252,14 +207320,17 @@ Sources (omit for a broad search across all):
206252
207320
  - memory: curated cross-session project knowledge — rules, constraints, conventions.
206253
207321
  - 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
207322
  - git_commit: this repository's commit history.
207323
+ - note: parked decisions, follow-ups, and dismissed notes with their recorded text.
206255
207324
 
206256
207325
  Picking sources:
206257
207326
  - "when did this change / was this working before" → ["git_commit", "message"]
206258
207327
  - "did we discuss this earlier" → ["message"]
207328
+ - "did we decide something about this / leave a follow-up" → ["note"]
206259
207329
  - "what's our convention / rule for X" → ["memory"]`;
206260
207330
 
206261
207331
  // src/tools/ctx-search.ts
206262
207332
  var DEFAULT_LIMIT = 10;
207333
+ 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
207334
  var ParamsSchema5 = exports_typebox.Object({
206264
207335
  query: exports_typebox.String({
206265
207336
  description: "Search query. Matches against memory content, Primers, git commit messages, and raw user/assistant message text."
@@ -206271,9 +207342,10 @@ var ParamsSchema5 = exports_typebox.Object({
206271
207342
  exports_typebox.Literal("memory"),
206272
207343
  exports_typebox.Literal("message"),
206273
207344
  exports_typebox.Literal("git_commit"),
206274
- exports_typebox.Literal("primer")
207345
+ exports_typebox.Literal("primer"),
207346
+ exports_typebox.Literal("note")
206275
207347
  ]), {
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.'
207348
+ 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
207349
  }))
206278
207350
  });
206279
207351
  function normalizeLimit3(limit) {
@@ -206300,7 +207372,7 @@ function formatAge2(committedAtMs) {
206300
207372
  const years = Math.floor(days / 365);
206301
207373
  return years === 1 ? "1y ago" : `${years}y ago`;
206302
207374
  }
206303
- function formatResult(result, index) {
207375
+ function formatResult(result, index, currentSessionId) {
206304
207376
  if (result.source === "memory") {
206305
207377
  const source = result.sourceName ? ` source=${result.sourceName}` : "";
206306
207378
  return [
@@ -206321,6 +207393,14 @@ function formatResult(result, index) {
206321
207393
  `[${index}] [primer] score=${result.score.toFixed(2)} id=${result.primerId} support=${result.support} match=${result.matchType}`,
206322
207394
  result.content
206323
207395
  ].join(`
207396
+ `);
207397
+ }
207398
+ if (result.source === "note") {
207399
+ const anchor = result.anchorOrdinal !== null && result.sourceSessionId === currentSessionId ? ` @msg ${result.anchorOrdinal}` : "";
207400
+ return [
207401
+ `[${index}] [note] score=${result.score.toFixed(2)} id=#${result.noteId} status=${result.status} ${formatAge2(result.createdAt)}${anchor}`,
207402
+ result.content
207403
+ ].join(`
206324
207404
  `);
206325
207405
  }
206326
207406
  if (result.source === "compartment") {
@@ -206338,14 +207418,17 @@ function formatResult(result, index) {
206338
207418
  ].join(`
206339
207419
  `);
206340
207420
  }
206341
- function formatSearchResults(query, results) {
207421
+ function formatSearchResults(query, results, currentSessionId) {
206342
207422
  if (results.length === 0) {
206343
- return `No results found for "${query}" across memories, primers, git commits, or message history.`;
207423
+ return `No results found for "${query}" across notes, memories, primers, git commits, or message history.`;
206344
207424
  }
206345
- const bodyParts = results.map((result, index) => formatResult(result, index + 1));
207425
+ const bodyParts = results.map((result, index) => formatResult(result, index + 1, currentSessionId));
206346
207426
  if (results.some((result) => result.source === "message" || result.source === "compartment")) {
206347
207427
  bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
206348
207428
  }
207429
+ if (results.some((result) => result.source === "note" && result.anchorOrdinal !== null && result.sourceSessionId === currentSessionId)) {
207430
+ bodyParts.push(NOTE_EXPAND_HINT);
207431
+ }
206349
207432
  const body = bodyParts.join(`
206350
207433
 
206351
207434
  `);
@@ -206394,7 +207477,12 @@ function createCtxSearchTool(deps) {
206394
207477
  explicitSearch: true
206395
207478
  });
206396
207479
  return {
206397
- content: [{ type: "text", text: formatSearchResults(query, results) }],
207480
+ content: [
207481
+ {
207482
+ type: "text",
207483
+ text: formatSearchResults(query, results, sessionId)
207484
+ }
207485
+ ],
206398
207486
  details: undefined
206399
207487
  };
206400
207488
  }
@@ -206491,7 +207579,7 @@ function registerMagicContextTools(pi, opts) {
206491
207579
  pi.registerTool(createCtxExpandTool({ db: opts.db }));
206492
207580
  }
206493
207581
  pi.registerTool(createTodowriteTool());
206494
- if (opts.ctxReduceEnabled === true) {
207582
+ if (!opts.sessionScopedToolsDisabled) {
206495
207583
  pi.registerTool(createCtxReduceTool({
206496
207584
  db: opts.db,
206497
207585
  protectedTags: opts.protectedTags ?? 20
@@ -206503,7 +207591,7 @@ function registerMagicContextTools(pi, opts) {
206503
207591
  var PREFIX = "[magic-context][pi]";
206504
207592
  function resolveCurrentProject(ctx) {
206505
207593
  const projectDir = ctx.cwd;
206506
- const projectIdentity = resolveProjectIdentity(projectDir);
207594
+ const projectIdentity = resolveProjectIdentityOrFallback(projectDir);
206507
207595
  return { projectDir, projectIdentity };
206508
207596
  }
206509
207597
  function persistPiMessageEndModelMeta(args) {
@@ -206705,13 +207793,13 @@ async function src_default2(pi) {
206705
207793
  })();
206706
207794
  }, 0);
206707
207795
  const projectDir = process.cwd();
206708
- const projectIdentity = resolveProjectIdentity(projectDir);
207796
+ const projectIdentity = resolveProjectIdentityOrFallback(projectDir);
206709
207797
  const seenDreamerProjectIdentities = new Set([projectIdentity]);
206710
207798
  try {
206711
207799
  const pendingPiMarkerSessions = getSessionsWithPendingPiMarker(db);
206712
207800
  for (const sid of pendingPiMarkerSessions) {
206713
207801
  signalPiDeferredHistoryRefresh(sid);
206714
- signalPiPendingMaterialization(sid);
207802
+ signalPiDeferredMaterialization(sid);
206715
207803
  }
206716
207804
  if (pendingPiMarkerSessions.length > 0) {
206717
207805
  log(`${PREFIX} rehydrated ${pendingPiMarkerSessions.length} Pi deferred compaction marker session(s)`);
@@ -206749,11 +207837,10 @@ async function src_default2(pi) {
206749
207837
  ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
206750
207838
  allowDreamerActions: false,
206751
207839
  memoryToolEnabled: true,
206752
- ctxReduceEnabled: config2.ctx_reduce_enabled === true,
206753
207840
  protectedTags: config2.protected_tags ?? 20,
206754
207841
  dreamerEnabled: isDreamerRunnable(config2)
206755
207842
  });
206756
- info(`registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand${config2.ctx_reduce_enabled === true ? ", ctx_reduce" : ""}`);
207843
+ info("registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand, ctx_reduce");
206757
207844
  const historianConfig = resolveHistorianFromConfig(config2);
206758
207845
  if (historianConfig) {
206759
207846
  historianConfig.onStatusChange = (ctx) => {
@@ -206767,11 +207854,10 @@ async function src_default2(pi) {
206767
207854
  const contextOptionsByDir = new Map;
206768
207855
  const buildContextOptions = (cfg, hist, auto) => ({
206769
207856
  db: database,
206770
- ctxReduceEnabled: cfg.ctx_reduce_enabled,
206771
207857
  smartDrops: cfg.smart_drops === true,
206772
207858
  protectedTags: cfg.protected_tags ?? 20,
206773
207859
  heuristics: {
206774
- caveman: cfg.ctx_reduce_enabled === false && cfg.caveman_text_compression ? {
207860
+ caveman: cfg.caveman_text_compression ? {
206775
207861
  enabled: cfg.caveman_text_compression.enabled,
206776
207862
  minChars: cfg.caveman_text_compression.min_chars
206777
207863
  } : undefined,
@@ -206866,6 +207952,22 @@ async function src_default2(pi) {
206866
207952
  autoPromote: config2.memory.auto_promote
206867
207953
  });
206868
207954
  info("registered /ctx-recomp");
207955
+ registerCtxWrapupCommand(pi, {
207956
+ db,
207957
+ runner: new PiSubagentRunner,
207958
+ historianModel: historianConfig?.model,
207959
+ historianChunkTokens: deriveHistorianChunkTokens(resolveHistorianContextLimit(historianConfig?.model)),
207960
+ historianFallbacks: historianConfig?.fallbackModels,
207961
+ historianTimeoutMs: config2.historian_timeout_ms,
207962
+ historianThinkingLevel: historianConfig?.thinkingLevel,
207963
+ language: config2.language,
207964
+ memoryEnabled: config2.memory.enabled,
207965
+ autoPromote: config2.memory.auto_promote,
207966
+ userMemoriesEnabled: userMemoryCollectionEnabled(config2.dreamer),
207967
+ executeThresholdPercentage: config2.execute_threshold_percentage,
207968
+ executeThresholdTokens: config2.execute_threshold_tokens
207969
+ });
207970
+ info("registered /ctx-wrapup");
206869
207971
  registerCtxSessionUpgradeCommand(pi, {
206870
207972
  db,
206871
207973
  runner: new PiSubagentRunner,
@@ -207015,10 +208117,10 @@ async function src_default2(pi) {
207015
208117
  memoryEnabled: effectiveConfig.memory.enabled,
207016
208118
  includeGuidance: true,
207017
208119
  protectedTags: effectiveConfig.protected_tags,
207018
- ctxReduceEnabled: effectiveConfig.ctx_reduce_enabled,
208120
+ ctxReduceCallable: true,
207019
208121
  dreamerEnabled: effectiveDreamerRunnable,
207020
208122
  temporalAwarenessEnabled: effectiveConfig.temporal_awareness ?? false,
207021
- cavemanTextCompressionEnabled: effectiveConfig.ctx_reduce_enabled === false && effectiveConfig.caveman_text_compression?.enabled === true,
208123
+ cavemanTextCompressionEnabled: effectiveConfig.caveman_text_compression?.enabled === true,
207022
208124
  language: effectiveConfig.language,
207023
208125
  userMemoriesEnabled: userMemoryCollectionEnabled(effectiveConfig.dreamer),
207024
208126
  isCacheBusting,