@cortexkit/pi-magic-context 0.30.6 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -62,26 +62,26 @@ var currentHarness = "opencode", harnessLocked = false;
62
62
  // ../plugin/src/shared/data-path.ts
63
63
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
64
64
  import * as os from "node:os";
65
- import * as path2 from "node:path";
65
+ import * as path from "node:path";
66
66
  function getDataDir() {
67
- return process.env.XDG_DATA_HOME ?? path2.join(os.homedir(), ".local", "share");
67
+ return process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share");
68
68
  }
69
69
  function getMagicContextTempDir(harness = getHarness()) {
70
- return path2.join(os.tmpdir(), harness, "magic-context");
70
+ return path.join(os.tmpdir(), harness, "magic-context");
71
71
  }
72
72
  function getMagicContextLogPath(harness = getHarness()) {
73
73
  const envPath = process.env.MAGIC_CONTEXT_LOG_PATH?.trim();
74
74
  if (envPath)
75
75
  return envPath;
76
- return path2.join(getMagicContextTempDir(harness), "magic-context.log");
76
+ return path.join(getMagicContextTempDir(harness), "magic-context.log");
77
77
  }
78
78
  function getProjectMagicContextDir(directory) {
79
- return path2.join(directory, ".cortexkit", "magic-context");
79
+ return path.join(directory, ".cortexkit", "magic-context");
80
80
  }
81
81
  function ensureCortexKitArtifactGitignore(directory) {
82
82
  try {
83
- const cortexKitDir = path2.join(directory, ".cortexkit");
84
- const gitignorePath = path2.join(cortexKitDir, ".gitignore");
83
+ const cortexKitDir = path.join(directory, ".cortexkit");
84
+ const gitignorePath = path.join(cortexKitDir, ".gitignore");
85
85
  let existing = "";
86
86
  if (existsSync2(gitignorePath)) {
87
87
  existing = readFileSync2(gitignorePath, "utf8");
@@ -101,25 +101,25 @@ ${GITIGNORE_GUARD_CLOSE}
101
101
  } catch {}
102
102
  }
103
103
  function getProjectMagicContextHistorianDir(directory) {
104
- return path2.join(getProjectMagicContextDir(directory), "historian");
104
+ return path.join(getProjectMagicContextDir(directory), "historian");
105
105
  }
106
106
  function getOpenCodeStorageDir() {
107
- return path2.join(getDataDir(), "opencode", "storage");
107
+ return path.join(getDataDir(), "opencode", "storage");
108
108
  }
109
109
  function getMagicContextStorageDir() {
110
- return path2.join(getDataDir(), "cortexkit", "magic-context");
110
+ return path.join(getDataDir(), "cortexkit", "magic-context");
111
111
  }
112
112
  function getLegacyOpenCodeMagicContextStorageDir() {
113
- return path2.join(getOpenCodeStorageDir(), "plugin", "magic-context");
113
+ return path.join(getOpenCodeStorageDir(), "plugin", "magic-context");
114
114
  }
115
115
  var GITIGNORE_GUARD_OPEN = "# >>> cortexkit:magic-context", GITIGNORE_GUARD_CLOSE = "# <<< cortexkit:magic-context";
116
116
  var init_data_path = () => {};
117
117
 
118
118
  // ../plugin/src/shared/logger.ts
119
119
  import * as fs from "node:fs";
120
- import * as path3 from "node:path";
120
+ import * as path2 from "node:path";
121
121
  function ensureDir(filePath) {
122
- const dir = path3.dirname(filePath);
122
+ const dir = path2.dirname(filePath);
123
123
  if (dir === lastEnsuredDir)
124
124
  return;
125
125
  try {
@@ -15707,13 +15707,23 @@ function summarizeDreamSchedule(dreamer) {
15707
15707
  }
15708
15708
 
15709
15709
  // ../plugin/src/features/magic-context/memory/project-identity.ts
15710
+ init_logger();
15710
15711
  import { execFileSync } from "node:child_process";
15711
15712
  import { createHash } from "node:crypto";
15712
- import { statSync as statSync2 } from "node:fs";
15713
- import path from "node:path";
15713
+ import { existsSync as existsSync3, realpathSync, statSync as statSync2 } from "node:fs";
15714
+ import path3 from "node:path";
15714
15715
  var GIT_TIMEOUT_MS = 5000;
15716
+ var TRANSIENT_FAILURE_COOLDOWN_MS = 5 * 60 * 1000;
15715
15717
  var identityCache = new Map;
15718
+ var lastKnownGitIdentityCache = new Map;
15716
15719
  var directoryFallbackCache = new Map;
15720
+ var transientFailureCooldown = new Map;
15721
+ var dubiousOwnershipFallbackDirectories = new Set;
15722
+ var dubiousOwnershipLoggedDirectories = new Set;
15723
+ var dubiousOwnershipWarnedDirectories = new Set;
15724
+ var transientGitIdentityReuseLoggedDirectories = new Set;
15725
+ var execFileSyncForIdentity = execFileSync;
15726
+ var nowMs = () => Date.now();
15717
15727
 
15718
15728
  class ProjectIdentityError extends Error {
15719
15729
  errorClass;
@@ -15765,7 +15775,7 @@ function getErrorStderr(error) {
15765
15775
  return "";
15766
15776
  }
15767
15777
  function directoryFallback(directory) {
15768
- const canonical = path.resolve(directory);
15778
+ const canonical = path3.resolve(directory);
15769
15779
  const hash = createHash("md5").update(canonical, "utf8").digest("hex").slice(0, 12);
15770
15780
  return `dir:${hash}`;
15771
15781
  }
@@ -15803,21 +15813,27 @@ function classifyGitError(error, rawDirectory) {
15803
15813
  return new ProjectIdentityError("permission_denied", rawDirectory, "Permission denied while spawning git", asError(error));
15804
15814
  }
15805
15815
  const stderr = getErrorStderr(error).toLowerCase();
15816
+ if (stderr.includes("detected dubious ownership")) {
15817
+ return new ProjectIdentityError("dubious_ownership", rawDirectory, "git refused to read the repository because it detected dubious ownership", asError(error));
15818
+ }
15806
15819
  if (stderr.includes("not a git repository") || stderr.includes("does not have any commits yet") || stderr.includes("ambiguous argument 'head'") || stderr.includes("unknown revision or path")) {
15807
15820
  return new ProjectIdentityError("not_git_repo", rawDirectory, "Directory has no git root commit; caller may use directory fallback", asError(error));
15808
15821
  }
15809
15822
  return new ProjectIdentityError("unknown", rawDirectory, "git rev-list failed while resolving project identity", asError(error));
15810
15823
  }
15811
15824
  function resolveProjectIdentityStrict(directory) {
15812
- const canonical = path.resolve(directory);
15825
+ const canonical = path3.resolve(directory);
15813
15826
  const cached = identityCache.get(canonical);
15814
15827
  if (cached !== undefined) {
15815
15828
  return cached;
15816
15829
  }
15817
15830
  assertDirectoryUsable(canonical, directory);
15831
+ if (!hasGitDir(canonical)) {
15832
+ throw new ProjectIdentityError("not_git_repo", directory, "Directory has no git metadata; caller may use directory fallback");
15833
+ }
15818
15834
  let output;
15819
15835
  try {
15820
- output = execFileSync("git", ["rev-list", "--max-parents=0", "HEAD"], {
15836
+ output = execFileSyncForIdentity("git", ["rev-list", "--max-parents=0", "HEAD"], {
15821
15837
  cwd: canonical,
15822
15838
  encoding: "utf8",
15823
15839
  env: { ...process.env, LC_ALL: "C", LANG: "C" },
@@ -15835,13 +15851,76 @@ function resolveProjectIdentityStrict(directory) {
15835
15851
  }
15836
15852
  const identity = `git:${rootCommit}`;
15837
15853
  identityCache.set(canonical, identity);
15854
+ lastKnownGitIdentityCache.set(canonical, identity);
15855
+ transientFailureCooldown.delete(canonical);
15856
+ dubiousOwnershipFallbackDirectories.delete(canonical);
15857
+ transientGitIdentityReuseLoggedDirectories.delete(canonical);
15838
15858
  return identity;
15839
15859
  }
15840
15860
  function shouldUseDirectoryFallback(error) {
15841
- return error.errorClass === "not_git_repo" || error.errorClass === "unknown" && error.message.startsWith("Unable to access project directory:");
15861
+ return error.errorClass !== "permission_denied";
15862
+ }
15863
+ function getActiveCooldown(canonical) {
15864
+ const until = transientFailureCooldown.get(canonical);
15865
+ if (until === undefined)
15866
+ return;
15867
+ if (nowMs() < until)
15868
+ return until;
15869
+ transientFailureCooldown.delete(canonical);
15870
+ return;
15871
+ }
15872
+ function lastKnownGitIdentity(canonical) {
15873
+ return lastKnownGitIdentityCache.get(canonical) ?? identityCache.get(canonical);
15874
+ }
15875
+ function nearestLastKnownGitIdentity(canonical) {
15876
+ const visited = new Set;
15877
+ const walk = (start) => {
15878
+ let current = start;
15879
+ while (!visited.has(current)) {
15880
+ visited.add(current);
15881
+ const cached = lastKnownGitIdentity(current);
15882
+ if (cached !== undefined)
15883
+ return { identity: cached, source: current };
15884
+ const parent = path3.dirname(current);
15885
+ if (parent === current)
15886
+ break;
15887
+ current = parent;
15888
+ }
15889
+ return;
15890
+ };
15891
+ const exactOrAncestor = walk(canonical);
15892
+ if (exactOrAncestor)
15893
+ return exactOrAncestor;
15894
+ try {
15895
+ const realCanonical = realpathSync.native(canonical);
15896
+ if (realCanonical !== canonical)
15897
+ return walk(realCanonical);
15898
+ } catch {}
15899
+ return;
15900
+ }
15901
+ function reuseLastKnownGitIdentity(canonical) {
15902
+ const cached = nearestLastKnownGitIdentity(canonical);
15903
+ if (cached === undefined)
15904
+ return;
15905
+ if (!transientGitIdentityReuseLoggedDirectories.has(canonical)) {
15906
+ transientGitIdentityReuseLoggedDirectories.add(canonical);
15907
+ const sourceNote = cached.source === canonical ? "" : ` from ancestor ${cached.source}`;
15908
+ log(`[magic-context] git identity resolution is temporarily unavailable for ${canonical}; reusing the last successful project identity${sourceNote} to avoid splitting project-scoped memory`);
15909
+ }
15910
+ return cached.identity;
15911
+ }
15912
+ function formatDubiousOwnershipWarning(canonical) {
15913
+ return `Magic Context: git refused to read ${canonical} (dubious ownership — the repo is owned by a different user). Using a directory-based project identity for now, which keeps memory separate from this repo's normal identity. Fix: git config --global --add safe.directory ${canonical}`;
15914
+ }
15915
+ function recordDubiousOwnershipFallback(canonical) {
15916
+ dubiousOwnershipFallbackDirectories.add(canonical);
15917
+ if (dubiousOwnershipLoggedDirectories.has(canonical))
15918
+ return;
15919
+ dubiousOwnershipLoggedDirectories.add(canonical);
15920
+ log(`[magic-context] ${formatDubiousOwnershipWarning(canonical)}`);
15842
15921
  }
15843
15922
  function resolveProjectIdentity(directory) {
15844
- const canonical = path.resolve(directory);
15923
+ const canonical = path3.resolve(directory);
15845
15924
  const cachedFallback = directoryFallbackCache.get(canonical);
15846
15925
  if (cachedFallback !== undefined) {
15847
15926
  if (!hasGitDir(canonical)) {
@@ -15849,27 +15928,74 @@ function resolveProjectIdentity(directory) {
15849
15928
  }
15850
15929
  directoryFallbackCache.delete(canonical);
15851
15930
  }
15931
+ if (getActiveCooldown(canonical) !== undefined) {
15932
+ if (hasGitDir(canonical)) {
15933
+ const cachedGitIdentity = reuseLastKnownGitIdentity(canonical);
15934
+ if (cachedGitIdentity !== undefined) {
15935
+ return cachedGitIdentity;
15936
+ }
15937
+ }
15938
+ return directoryFallback(canonical);
15939
+ }
15852
15940
  try {
15853
15941
  return resolveProjectIdentityStrict(directory);
15854
15942
  } catch (error) {
15855
15943
  if (error instanceof ProjectIdentityError && shouldUseDirectoryFallback(error)) {
15856
15944
  const fallback = directoryFallback(canonical);
15857
- if (!hasGitDir(canonical)) {
15945
+ const hasGitMetadata = hasGitDir(canonical);
15946
+ if (!hasGitMetadata) {
15858
15947
  directoryFallbackCache.set(canonical, fallback);
15948
+ transientFailureCooldown.delete(canonical);
15949
+ } else {
15950
+ transientFailureCooldown.set(canonical, nowMs() + TRANSIENT_FAILURE_COOLDOWN_MS);
15951
+ const cachedGitIdentity = reuseLastKnownGitIdentity(canonical);
15952
+ if (cachedGitIdentity !== undefined) {
15953
+ return cachedGitIdentity;
15954
+ }
15955
+ }
15956
+ if (error.errorClass === "dubious_ownership") {
15957
+ recordDubiousOwnershipFallback(canonical);
15859
15958
  }
15860
15959
  return fallback;
15861
15960
  }
15862
15961
  throw error;
15863
15962
  }
15864
15963
  }
15865
- function hasGitDir(canonical) {
15964
+ function resolveProjectIdentityOrFallback(directory) {
15866
15965
  try {
15867
- statSync2(path.join(canonical, ".git"));
15966
+ return resolveProjectIdentity(directory);
15967
+ } catch (error) {
15968
+ const canonical = path3.resolve(directory);
15969
+ const fallback = directoryFallback(canonical);
15970
+ const message = error instanceof Error ? error.message : String(error);
15971
+ log(`[magic-context] project identity resolution failed for ${canonical}; using directory fallback ${fallback}: ${message}`);
15972
+ return fallback;
15973
+ }
15974
+ }
15975
+ function hasGitDir(canonical) {
15976
+ if (hasGitDirInAncestorChain(canonical)) {
15868
15977
  return true;
15978
+ }
15979
+ try {
15980
+ const realCanonical = realpathSync.native(canonical);
15981
+ return realCanonical !== canonical && hasGitDirInAncestorChain(realCanonical);
15869
15982
  } catch {
15870
15983
  return false;
15871
15984
  }
15872
15985
  }
15986
+ function hasGitDirInAncestorChain(startDirectory) {
15987
+ let current = startDirectory;
15988
+ while (true) {
15989
+ if (existsSync3(path3.join(current, ".git"))) {
15990
+ return true;
15991
+ }
15992
+ const parent = path3.dirname(current);
15993
+ if (parent === current) {
15994
+ return false;
15995
+ }
15996
+ current = parent;
15997
+ }
15998
+ }
15873
15999
  function normalizeStoredProjectPath(rawOrStored) {
15874
16000
  if (rawOrStored.startsWith("git:") || rawOrStored.startsWith("dir:")) {
15875
16001
  return rawOrStored;
@@ -15902,7 +16028,7 @@ function removeSystemReminders(text) {
15902
16028
  // ../plugin/src/hooks/magic-context/read-session-db.ts
15903
16029
  init_data_path();
15904
16030
  init_logger();
15905
- import { existsSync as existsSync3 } from "node:fs";
16031
+ import { existsSync as existsSync4 } from "node:fs";
15906
16032
  import { join as join3 } from "node:path";
15907
16033
 
15908
16034
  // ../plugin/src/shared/sqlite.ts
@@ -15971,7 +16097,7 @@ function getOpenCodeDbPath() {
15971
16097
  return join3(getDataDir(), "opencode", "opencode.db");
15972
16098
  }
15973
16099
  function openCodeDbExists() {
15974
- return existsSync3(getOpenCodeDbPath());
16100
+ return existsSync4(getOpenCodeDbPath());
15975
16101
  }
15976
16102
  var cachedReadOnlyDb = null;
15977
16103
  function closeCachedReadOnlyDb() {
@@ -149634,6 +149760,263 @@ function parseReportedLimit(message) {
149634
149760
  }
149635
149761
  return;
149636
149762
  }
149763
+
149764
+ // ../plugin/src/features/magic-context/session-project-backfill.ts
149765
+ init_logger();
149766
+ import { existsSync as existsSync5 } from "node:fs";
149767
+
149768
+ // ../plugin/src/features/magic-context/session-project-storage.ts
149769
+ var upsertSessionProjectStatements = new WeakMap;
149770
+ var repairSessionChunkProjectStatements = new WeakMap;
149771
+ var repairProjectChunkProjectStatements = new WeakMap;
149772
+ function getUpsertSessionProjectStatement(db) {
149773
+ let stmt = upsertSessionProjectStatements.get(db);
149774
+ if (!stmt) {
149775
+ stmt = db.prepare(`INSERT INTO session_projects (session_id, harness, project_path, updated_at)
149776
+ VALUES (?, ?, ?, ?)
149777
+ ON CONFLICT(session_id, harness) DO UPDATE SET
149778
+ project_path = excluded.project_path,
149779
+ updated_at = excluded.updated_at
149780
+ WHERE session_projects.project_path <> excluded.project_path`);
149781
+ upsertSessionProjectStatements.set(db, stmt);
149782
+ }
149783
+ return stmt;
149784
+ }
149785
+ function getRepairSessionChunkProjectStatement(db) {
149786
+ let stmt = repairSessionChunkProjectStatements.get(db);
149787
+ if (!stmt) {
149788
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
149789
+ SET project_path = ?
149790
+ WHERE session_id = ?
149791
+ AND harness = ?
149792
+ AND project_path <> ?`);
149793
+ repairSessionChunkProjectStatements.set(db, stmt);
149794
+ }
149795
+ return stmt;
149796
+ }
149797
+ function getRepairProjectChunkProjectStatement(db) {
149798
+ let stmt = repairProjectChunkProjectStatements.get(db);
149799
+ if (!stmt) {
149800
+ stmt = db.prepare(`UPDATE compartment_chunk_embeddings
149801
+ SET project_path = (
149802
+ SELECT sp.project_path
149803
+ FROM session_projects sp
149804
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
149805
+ AND sp.harness = compartment_chunk_embeddings.harness
149806
+ LIMIT 1
149807
+ )
149808
+ WHERE EXISTS (
149809
+ SELECT 1
149810
+ FROM session_projects sp
149811
+ WHERE sp.session_id = compartment_chunk_embeddings.session_id
149812
+ AND sp.harness = compartment_chunk_embeddings.harness
149813
+ AND sp.project_path <> compartment_chunk_embeddings.project_path
149814
+ AND (
149815
+ sp.project_path = ?
149816
+ OR compartment_chunk_embeddings.project_path = ?
149817
+ )
149818
+ )`);
149819
+ repairProjectChunkProjectStatements.set(db, stmt);
149820
+ }
149821
+ return stmt;
149822
+ }
149823
+ function recordSessionProjectIdentity(db, sessionId, projectPath) {
149824
+ if (!sessionId || !projectPath)
149825
+ return;
149826
+ const harness = getHarness();
149827
+ const now = Date.now();
149828
+ db.transaction(() => {
149829
+ getUpsertSessionProjectStatement(db).run(sessionId, harness, projectPath, now);
149830
+ getRepairSessionChunkProjectStatement(db).run(projectPath, sessionId, harness, projectPath);
149831
+ })();
149832
+ }
149833
+ function repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectPath) {
149834
+ if (!projectPath)
149835
+ return 0;
149836
+ return getRepairProjectChunkProjectStatement(db).run(projectPath, projectPath).changes;
149837
+ }
149838
+
149839
+ // ../plugin/src/features/magic-context/session-project-backfill.ts
149840
+ var LEASE_TTL_MS = 10 * 60 * 1000;
149841
+ var SESSION_QUERY_CHUNK_SIZE = 250;
149842
+ var YIELD_EVERY_NEW_DIRECTORIES = 10;
149843
+ function ensureBackfillStateTable(db) {
149844
+ db.exec(`
149845
+ CREATE TABLE IF NOT EXISTS session_project_backfill_state (
149846
+ harness TEXT PRIMARY KEY,
149847
+ status TEXT NOT NULL CHECK (status IN ('running', 'completed')),
149848
+ started_at INTEGER,
149849
+ lease_expires_at INTEGER,
149850
+ completed_at INTEGER
149851
+ );
149852
+ `);
149853
+ }
149854
+ function withImmediateTransaction(db, fn) {
149855
+ db.exec("BEGIN IMMEDIATE");
149856
+ try {
149857
+ const result = fn();
149858
+ db.exec("COMMIT");
149859
+ return result;
149860
+ } catch (error) {
149861
+ try {
149862
+ db.exec("ROLLBACK");
149863
+ } catch {}
149864
+ throw error;
149865
+ }
149866
+ }
149867
+ function defaultYieldFn() {
149868
+ return new Promise((resolve) => setTimeout(resolve, 0));
149869
+ }
149870
+ function acquireBackfillLease(db, harness, now) {
149871
+ ensureBackfillStateTable(db);
149872
+ return withImmediateTransaction(db, () => {
149873
+ const current = db.prepare(`SELECT harness, status, started_at, lease_expires_at, completed_at
149874
+ FROM session_project_backfill_state
149875
+ WHERE harness = ?`).get(harness);
149876
+ if (current?.status === "completed") {
149877
+ return "already_completed";
149878
+ }
149879
+ if (current?.status === "running" && current.lease_expires_at !== null && current.lease_expires_at > now) {
149880
+ return "blocked_by_lease";
149881
+ }
149882
+ db.prepare(`INSERT INTO session_project_backfill_state(
149883
+ harness,
149884
+ status,
149885
+ started_at,
149886
+ lease_expires_at,
149887
+ completed_at
149888
+ )
149889
+ VALUES (?, 'running', ?, ?, NULL)
149890
+ ON CONFLICT(harness) DO UPDATE SET
149891
+ status = 'running',
149892
+ started_at = excluded.started_at,
149893
+ lease_expires_at = excluded.lease_expires_at,
149894
+ completed_at = NULL`).run(harness, now, now + LEASE_TTL_MS);
149895
+ return "completed";
149896
+ });
149897
+ }
149898
+ function renewBackfillLease(db, harness, now) {
149899
+ ensureBackfillStateTable(db);
149900
+ db.prepare(`UPDATE session_project_backfill_state
149901
+ SET lease_expires_at = ?
149902
+ WHERE harness = ? AND status = 'running'`).run(now + LEASE_TTL_MS, harness);
149903
+ }
149904
+ function markBackfillCompleted(db, harness, now) {
149905
+ ensureBackfillStateTable(db);
149906
+ db.prepare(`INSERT INTO session_project_backfill_state(
149907
+ harness,
149908
+ status,
149909
+ started_at,
149910
+ lease_expires_at,
149911
+ completed_at
149912
+ )
149913
+ VALUES (?, 'completed', ?, NULL, ?)
149914
+ ON CONFLICT(harness) DO UPDATE SET
149915
+ status = 'completed',
149916
+ lease_expires_at = NULL,
149917
+ completed_at = excluded.completed_at`).run(harness, now, now);
149918
+ }
149919
+ function dedupeSessions(sessions) {
149920
+ const sessionsById = new Map;
149921
+ for (const session of sessions) {
149922
+ if (!session.sessionId)
149923
+ continue;
149924
+ const existing = sessionsById.get(session.sessionId);
149925
+ if (!existing) {
149926
+ sessionsById.set(session.sessionId, {
149927
+ sessionId: session.sessionId,
149928
+ directory: session.directory
149929
+ });
149930
+ continue;
149931
+ }
149932
+ if (!existing.directory && session.directory) {
149933
+ sessionsById.set(session.sessionId, {
149934
+ sessionId: session.sessionId,
149935
+ directory: session.directory
149936
+ });
149937
+ }
149938
+ }
149939
+ return [...sessionsById.values()];
149940
+ }
149941
+ function getMappedSessionIds(db, harness, sessions) {
149942
+ const mapped = new Set;
149943
+ for (let index = 0;index < sessions.length; index += SESSION_QUERY_CHUNK_SIZE) {
149944
+ const chunk = sessions.slice(index, index + SESSION_QUERY_CHUNK_SIZE);
149945
+ if (chunk.length === 0)
149946
+ continue;
149947
+ const placeholders = chunk.map(() => "?").join(", ");
149948
+ const rows = db.prepare(`SELECT session_id
149949
+ FROM session_projects
149950
+ WHERE harness = ?
149951
+ AND session_id IN (${placeholders})`).all(harness, ...chunk.map((session) => session.sessionId));
149952
+ for (const row of rows) {
149953
+ mapped.add(row.session_id);
149954
+ }
149955
+ }
149956
+ return mapped;
149957
+ }
149958
+ async function runSessionProjectBackfill(db, sessions, options = {}) {
149959
+ const startedAt = performance.now();
149960
+ const harness = getHarness();
149961
+ const now = options.now ?? Date.now;
149962
+ const resolveIdentity = options.resolveIdentity ?? resolveProjectIdentity;
149963
+ const yieldFn = options.yieldFn ?? defaultYieldFn;
149964
+ const dedupedSessions = dedupeSessions(sessions);
149965
+ const result = {
149966
+ status: "completed",
149967
+ totalSessions: dedupedSessions.length,
149968
+ alreadyMappedSessions: 0,
149969
+ unmappedSessions: 0,
149970
+ backfilledSessions: 0,
149971
+ skippedDeadDirectories: 0,
149972
+ skippedEmptyDirectories: 0,
149973
+ durationMs: 0
149974
+ };
149975
+ const leaseStatus = acquireBackfillLease(db, harness, now());
149976
+ if (leaseStatus !== "completed") {
149977
+ result.status = leaseStatus;
149978
+ result.durationMs = performance.now() - startedAt;
149979
+ return result;
149980
+ }
149981
+ const mappedSessionIds = getMappedSessionIds(db, harness, dedupedSessions);
149982
+ result.alreadyMappedSessions = mappedSessionIds.size;
149983
+ const existenceCache = new Map;
149984
+ const identityCache2 = new Map;
149985
+ let newDirectoryResolutions = 0;
149986
+ for (const session of dedupedSessions) {
149987
+ if (mappedSessionIds.has(session.sessionId)) {
149988
+ continue;
149989
+ }
149990
+ result.unmappedSessions += 1;
149991
+ if (!session.directory) {
149992
+ result.skippedEmptyDirectories += 1;
149993
+ continue;
149994
+ }
149995
+ const stillExists = existenceCache.get(session.directory) ?? existsSync5(session.directory);
149996
+ existenceCache.set(session.directory, stillExists);
149997
+ if (!stillExists) {
149998
+ result.skippedDeadDirectories += 1;
149999
+ continue;
150000
+ }
150001
+ let identity = identityCache2.get(session.directory);
150002
+ if (!identity) {
150003
+ identity = resolveIdentity(session.directory);
150004
+ identityCache2.set(session.directory, identity);
150005
+ newDirectoryResolutions += 1;
150006
+ if (newDirectoryResolutions % YIELD_EVERY_NEW_DIRECTORIES === 0) {
150007
+ renewBackfillLease(db, harness, now());
150008
+ await yieldFn();
150009
+ renewBackfillLease(db, harness, now());
150010
+ }
150011
+ }
150012
+ recordSessionProjectIdentity(db, session.sessionId, identity);
150013
+ result.backfilledSessions += 1;
150014
+ }
150015
+ markBackfillCompleted(db, harness, now());
150016
+ result.durationMs = performance.now() - startedAt;
150017
+ log(`[session-projects] backfilled ${result.backfilledSessions} of ${result.unmappedSessions} unmapped sessions (skipped ${result.skippedDeadDirectories} dead dirs) in ${Math.round(result.durationMs)}ms`);
150018
+ return result;
150019
+ }
149637
150020
  // ../plugin/src/features/magic-context/compartment-lease.ts
149638
150021
  var COMPARTMENT_LEASE_TTL_MS = 5 * 60 * 1000;
149639
150022
  var COMPARTMENT_LEASE_RENEWAL_MS = 60 * 1000;
@@ -151859,6 +152242,15 @@ var MIGRATIONS = [
151859
152242
  );
151860
152243
  `);
151861
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
+ }
151862
152254
  }
151863
152255
  ];
151864
152256
  var LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
@@ -152047,7 +152439,7 @@ function readProjectDocsCanonical(projectDirectory) {
152047
152439
  return { renderedBlock, canonicalHash };
152048
152440
  }
152049
152441
  // ../plugin/src/features/magic-context/storage-db.ts
152050
- import { chmodSync, copyFileSync, cpSync, existsSync as existsSync5, mkdirSync as mkdirSync4, mkdtempSync } from "node:fs";
152442
+ import { chmodSync, copyFileSync, cpSync, existsSync as existsSync7, mkdirSync as mkdirSync4, mkdtempSync } from "node:fs";
152051
152443
  init_data_path();
152052
152444
  import { dirname as dirname3, join as join5 } from "node:path";
152053
152445
 
@@ -152186,14 +152578,14 @@ function loadToolDefinitionMeasurements(db) {
152186
152578
  // ../plugin/src/features/magic-context/tool-owner-backfill.ts
152187
152579
  init_data_path();
152188
152580
  init_logger();
152189
- import { existsSync as existsSync4 } from "node:fs";
152581
+ import { existsSync as existsSync6 } from "node:fs";
152190
152582
  import { join as join4 } from "node:path";
152191
152583
  var LEASE_DURATION_MS = 5 * 60 * 1000;
152192
152584
  var LEASE_RENEWAL_MS = 60 * 1000;
152193
152585
  function resolveOpencodeDbPath() {
152194
152586
  return join4(getDataDir(), "opencode", "opencode.db");
152195
152587
  }
152196
- function ensureBackfillStateTable(db) {
152588
+ function ensureBackfillStateTable2(db) {
152197
152589
  db.exec(`
152198
152590
  CREATE TABLE IF NOT EXISTS tool_owner_backfill_state (
152199
152591
  session_id TEXT PRIMARY KEY,
@@ -152209,7 +152601,7 @@ function ensureBackfillStateTable(db) {
152209
152601
  }
152210
152602
  function runToolOwnerBackfill(db) {
152211
152603
  const startedAt = performance.now();
152212
- ensureBackfillStateTable(db);
152604
+ ensureBackfillStateTable2(db);
152213
152605
  const result = {
152214
152606
  sessionsProcessed: 0,
152215
152607
  sessionsSkippedNoOcDb: 0,
@@ -152226,7 +152618,7 @@ function runToolOwnerBackfill(db) {
152226
152618
  return result;
152227
152619
  }
152228
152620
  const opencodeDbPath = resolveOpencodeDbPath();
152229
- if (!existsSync4(opencodeDbPath)) {
152621
+ if (!existsSync6(opencodeDbPath)) {
152230
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.`);
152231
152623
  markAllUnbackfilledSessionsSkipped(db);
152232
152624
  result.sessionsSkippedNoOcDb = countSessionsByStatus(db, "skipped");
@@ -152249,7 +152641,7 @@ function runToolOwnerBackfill(db) {
152249
152641
  return result;
152250
152642
  }
152251
152643
  function isToolOwnerBackfillNeeded(db) {
152252
- ensureBackfillStateTable(db);
152644
+ ensureBackfillStateTable2(db);
152253
152645
  const row = db.prepare(`SELECT 1 AS hit
152254
152646
  FROM tags
152255
152647
  WHERE type = 'tool' AND tool_owner_message_id IS NULL
@@ -152448,7 +152840,7 @@ var persistenceByDatabase = new WeakMap;
152448
152840
  var persistenceErrorByDatabase = new WeakMap;
152449
152841
  var pathByDatabase = new WeakMap;
152450
152842
  var lastSchemaFenceRejection = null;
152451
- var LATEST_SUPPORTED_VERSION = 49;
152843
+ var LATEST_SUPPORTED_VERSION = 50;
152452
152844
  var PERMISSIONS_ENFORCEABLE = process.platform !== "win32";
152453
152845
  function ensureSecureStorageDir(dir) {
152454
152846
  mkdirSync4(dir, { recursive: true, mode: 448 });
@@ -152465,7 +152857,7 @@ function restrictDatabaseFilePermissions(dbPath) {
152465
152857
  return;
152466
152858
  for (const suffix of ["", "-wal", "-shm"]) {
152467
152859
  const file = `${dbPath}${suffix}`;
152468
- if (!existsSync5(file))
152860
+ if (!existsSync7(file))
152469
152861
  continue;
152470
152862
  try {
152471
152863
  chmodSync(file, 384);
@@ -152491,11 +152883,11 @@ function getDatabasePath(db) {
152491
152883
  return pathByDatabase.get(db) ?? null;
152492
152884
  }
152493
152885
  function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
152494
- if (existsSync5(targetDbPath))
152886
+ if (existsSync7(targetDbPath))
152495
152887
  return;
152496
152888
  const legacyDir = getLegacyOpenCodeMagicContextStorageDir();
152497
152889
  const legacyDbPath = join5(legacyDir, "context.db");
152498
- if (!existsSync5(legacyDbPath))
152890
+ if (!existsSync7(legacyDbPath))
152499
152891
  return;
152500
152892
  log(`[magic-context] migrating legacy plugin storage: ${legacyDir} -> ${targetDbDir} (legacy left in place as backup)`);
152501
152893
  ensureSecureStorageDir(targetDbDir);
@@ -152512,7 +152904,7 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
152512
152904
  for (const suffix of ["", "-wal", "-shm"]) {
152513
152905
  const src = `${legacyDbPath}${suffix}`;
152514
152906
  const dst = join5(targetDbDir, `context.db${suffix}`);
152515
- if (existsSync5(src)) {
152907
+ if (existsSync7(src)) {
152516
152908
  try {
152517
152909
  copyFileSync(src, dst);
152518
152910
  } catch (error) {
@@ -152522,7 +152914,7 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
152522
152914
  }
152523
152915
  const legacyModelsDir = join5(legacyDir, "models");
152524
152916
  const targetModelsDir = join5(targetDbDir, "models");
152525
- if (existsSync5(legacyModelsDir) && !existsSync5(targetModelsDir)) {
152917
+ if (existsSync7(legacyModelsDir) && !existsSync7(targetModelsDir)) {
152526
152918
  try {
152527
152919
  cpSync(legacyModelsDir, targetModelsDir, { recursive: true });
152528
152920
  } catch (error) {
@@ -153126,6 +153518,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
153126
153518
  force_emergency_bypass_used INTEGER NOT NULL DEFAULT 0,
153127
153519
  emergency_drain_active INTEGER NOT NULL DEFAULT 0,
153128
153520
  historian_drain_failure_at INTEGER NOT NULL DEFAULT 0,
153521
+ wrapup_in_progress_state TEXT,
153129
153522
  cached_m0_materialized_at INTEGER,
153130
153523
  cached_m0_session_facts_version INTEGER,
153131
153524
  cached_m0_upgrade_state TEXT,
@@ -153414,6 +153807,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
153414
153807
  ensureColumn(db, "session_meta", "force_emergency_bypass_used", "INTEGER NOT NULL DEFAULT 0");
153415
153808
  ensureColumn(db, "session_meta", "emergency_drain_active", "INTEGER NOT NULL DEFAULT 0");
153416
153809
  ensureColumn(db, "session_meta", "historian_drain_failure_at", "INTEGER NOT NULL DEFAULT 0");
153810
+ ensureColumn(db, "session_meta", "wrapup_in_progress_state", "TEXT");
153417
153811
  ensureColumn(db, "session_meta", "cached_m0_materialized_at", "INTEGER");
153418
153812
  ensureColumn(db, "session_meta", "cached_m0_session_facts_version", "INTEGER");
153419
153813
  ensureColumn(db, "session_meta", "cached_m0_upgrade_state", "TEXT");
@@ -153886,6 +154280,148 @@ function recordProtectedTailNoEligibleHead(db, sessionId) {
153886
154280
  return loadProtectedTailMeta(db, sessionId).recoveryNoEligibleHeadCount;
153887
154281
  }
153888
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
+ }
153889
154425
  function protectedTailWindowBudget(usagePercentage, usable, perRunCap) {
153890
154426
  if (usagePercentage >= 95)
153891
154427
  return Math.min(1e6, Math.max(4 * perRunCap, Math.round(0.5 * usable)));
@@ -155742,7 +156278,7 @@ var ERROR_CLASSES = new Set([
155742
156278
  // ../plugin/src/features/magic-context/v22-deferred-backfill.ts
155743
156279
  init_logger();
155744
156280
  import { createHash as createHash4 } from "node:crypto";
155745
- import { realpathSync } from "node:fs";
156281
+ import { realpathSync as realpathSync2 } from "node:fs";
155746
156282
  import path5 from "node:path";
155747
156283
 
155748
156284
  // ../plugin/src/features/magic-context/memory/relocate-memory.ts
@@ -155776,7 +156312,10 @@ function countFailures(db) {
155776
156312
  }
155777
156313
  function classifyBackfillError(error) {
155778
156314
  if (error instanceof ProjectIdentityError) {
155779
- return { errorClass: error.errorClass, errorMessage: error.message };
156315
+ return {
156316
+ errorClass: error.errorClass === "dubious_ownership" ? "unknown" : error.errorClass,
156317
+ errorMessage: error.message
156318
+ };
155780
156319
  }
155781
156320
  if (error instanceof Error) {
155782
156321
  return { errorClass: "unknown", errorMessage: error.message };
@@ -155796,7 +156335,7 @@ function resolveBackfillIdentity(rawProjectPath) {
155796
156335
  function computeLegacyRustDirIdentity(rawProjectPath) {
155797
156336
  let canonical;
155798
156337
  try {
155799
- canonical = realpathSync(rawProjectPath);
156338
+ canonical = realpathSync2(rawProjectPath);
155800
156339
  } catch {
155801
156340
  canonical = path5.isAbsolute(rawProjectPath) ? rawProjectPath : path5.join(process.cwd(), rawProjectPath);
155802
156341
  }
@@ -156487,11 +157026,12 @@ function compareSemverCore(a, b) {
156487
157026
 
156488
157027
  // ../plugin/src/shared/announcement.ts
156489
157028
  init_data_path();
156490
- var ANNOUNCEMENT_VERSION = "0.30.4";
157029
+ var ANNOUNCEMENT_VERSION = "0.31.0";
156491
157030
  var ANNOUNCEMENT_FEATURES = [
156492
- "Pi: fixed historian and dreamer failing when your model comes from a provider extension (Antigravity, Anthropic OAuth, etc.). Subagents now load your extensions.",
156493
- "Pi: fixed the retrospective and refresh-primers dreamer tasks reading sessions through the wrong API.",
156494
- "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)."
156495
157035
  ];
156496
157036
  var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU";
156497
157037
  var STATE_FILENAME = "last_announced_version";
@@ -156667,7 +157207,7 @@ init_logger();
156667
157207
 
156668
157208
  // src/subagent-runner.ts
156669
157209
  import * as childProcess from "node:child_process";
156670
- import { existsSync as existsSync7, 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";
156671
157211
  import { createRequire as createRequire2 } from "node:module";
156672
157212
  import { tmpdir as tmpdir2 } from "node:os";
156673
157213
  import { basename as basename2, dirname as dirname4, join as join8, resolve as resolvePath } from "node:path";
@@ -156798,7 +157338,7 @@ function resolveBundledPiCli() {
156798
157338
  const require_ = createRequire2(import.meta.url);
156799
157339
  const pkgJson = require_.resolve("@earendil-works/pi-coding-agent/package.json");
156800
157340
  const cliPath = join8(dirname4(pkgJson), "dist/cli.js");
156801
- if (existsSync7(cliPath))
157341
+ if (existsSync9(cliPath))
156802
157342
  return cliPath;
156803
157343
  return null;
156804
157344
  } catch {
@@ -156809,7 +157349,7 @@ function resolvePiInvocation() {
156809
157349
  const execPath = process.execPath;
156810
157350
  const currentScript = process.argv[1];
156811
157351
  const isBunVirtualScript = currentScript?.startsWith("/$bunfs/root/") ?? false;
156812
- if (currentScript && !isBunVirtualScript && existsSync7(currentScript)) {
157352
+ if (currentScript && !isBunVirtualScript && existsSync9(currentScript)) {
156813
157353
  return { command: execPath, prefixArgs: [currentScript] };
156814
157354
  }
156815
157355
  const execName = basename2(execPath).toLowerCase();
@@ -156827,7 +157367,7 @@ function resolveSubagentEntryPath() {
156827
157367
  try {
156828
157368
  const here = dirname4(fileURLToPath(import.meta.url));
156829
157369
  const candidate = resolvePath(here, "subagent-entry.js");
156830
- if (existsSync7(candidate))
157370
+ if (existsSync9(candidate))
156831
157371
  return candidate;
156832
157372
  return;
156833
157373
  } catch {
@@ -157496,12 +158036,13 @@ ${sidekickText}
157496
158036
  init_logger();
157497
158037
 
157498
158038
  // ../plugin/src/features/magic-context/dreamer/open-opencode-db.ts
157499
- import { existsSync as existsSync8 } from "node:fs";
158039
+ import { existsSync as existsSync11 } from "node:fs";
157500
158040
  init_logger();
157501
158041
 
157502
158042
  // ../plugin/src/features/magic-context/compaction-marker.ts
157503
158043
  init_data_path();
157504
158044
  init_logger();
158045
+ import { existsSync as existsSync10 } from "node:fs";
157505
158046
  import { join as join9 } from "node:path";
157506
158047
  var BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
157507
158048
  function randomBase62(length) {
@@ -157568,6 +158109,9 @@ function getWritableOpenCodeDb() {
157568
158109
  closeQuietly(cachedWriteDb.db);
157569
158110
  } catch {}
157570
158111
  }
158112
+ if (!existsSync10(dbPath)) {
158113
+ throw new Error(`OpenCode database not found at ${dbPath} (is OpenCode installed?)`);
158114
+ }
157571
158115
  const db = new Database(dbPath);
157572
158116
  db.exec("PRAGMA busy_timeout=5000");
157573
158117
  db.exec("PRAGMA journal_mode=WAL");
@@ -157686,8 +158230,8 @@ function removeCompactionMarker(state) {
157686
158230
  // ../plugin/src/features/magic-context/dreamer/open-opencode-db.ts
157687
158231
  function openOpenCodeDb() {
157688
158232
  const dbPath = getOpenCodeDbPath2();
157689
- if (!existsSync8(dbPath)) {
157690
- log(`[key-files] OpenCode DB not found at ${dbPath} — skipping`);
158233
+ if (!existsSync11(dbPath)) {
158234
+ log(`[dreamer] OpenCode DB not found at ${dbPath} — skipping OpenCode history scan`);
157691
158235
  return null;
157692
158236
  }
157693
158237
  try {
@@ -157695,14 +158239,14 @@ function openOpenCodeDb() {
157695
158239
  db.exec("PRAGMA busy_timeout = 5000");
157696
158240
  return db;
157697
158241
  } catch (error) {
157698
- log(`[key-files] failed to open OpenCode DB at ${dbPath}: ${getErrorMessage(error)}`);
158242
+ log(`[dreamer] failed to open OpenCode DB at ${dbPath}: ${getErrorMessage(error)}`);
157699
158243
  return null;
157700
158244
  }
157701
158245
  }
157702
158246
 
157703
158247
  // ../plugin/src/features/magic-context/dreamer/task-executor.ts
157704
158248
  import { createHash as createHash10 } from "node:crypto";
157705
- import { existsSync as existsSync12 } from "node:fs";
158249
+ import { existsSync as existsSync15 } from "node:fs";
157706
158250
 
157707
158251
  // ../plugin/src/agents/dreamer.ts
157708
158252
  var DREAMER_AGENT = "dreamer";
@@ -172654,7 +173198,6 @@ var MagicContextConfigSchema = exports_external.object({
172654
173198
  enabled: exports_external.boolean().default(true).describe("Enable magic context (default: true)"),
172655
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."),
172656
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."),
172657
- 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)"),
172658
173201
  historian: HistorianConfigSchema.describe("Historian agent configuration (model, fallback_models, variant, temperature, maxTokens, permission, two_pass, etc.)"),
172659
173202
  dreamer: DreamerConfigSchema.optional().describe("Dreamer agent + scheduling configuration (model, fallback_models, disable, schedule, tasks, etc.)"),
172660
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" })'),
@@ -172693,9 +173236,9 @@ var MagicContextConfigSchema = exports_external.object({
172693
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."),
172694
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."),
172695
173238
  caveman_text_compression: exports_external.object({
172696
- 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%)."),
172697
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.")
172698
- }).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)."),
172699
173242
  memory: exports_external.object({
172700
173243
  enabled: exports_external.boolean().default(true).describe("Enable cross-session memory (default: true)"),
172701
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)"),
@@ -173502,7 +174045,8 @@ function isNativeRuntimeMissingError(error51) {
173502
174045
  if (code === "ERR_DLOPEN_FAILED" && lower.includes("onnxruntime")) {
173503
174046
  return true;
173504
174047
  }
173505
- if (!lower.includes("onnxruntime-node"))
174048
+ const mentionsNativeRuntime = lower.includes("onnxruntime-node") || lower.includes("onnxruntime_binding");
174049
+ if (!mentionsNativeRuntime)
173506
174050
  return false;
173507
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");
173508
174052
  }
@@ -173647,7 +174191,7 @@ class LocalEmbeddingProvider {
173647
174191
  } catch (error51) {
173648
174192
  if (isNativeRuntimeMissingError(error51)) {
173649
174193
  nativeRuntimeMissing = true;
173650
- 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.");
173651
174195
  } else {
173652
174196
  log("[magic-context] embedding model failed to load:", error51);
173653
174197
  }
@@ -174572,77 +175116,6 @@ function invalidateMemory(projectPath, memoryId) {
174572
175116
  }
174573
175117
  }
174574
175118
 
174575
- // ../plugin/src/features/magic-context/session-project-storage.ts
174576
- var upsertSessionProjectStatements = new WeakMap;
174577
- var repairSessionChunkProjectStatements = new WeakMap;
174578
- var repairProjectChunkProjectStatements = new WeakMap;
174579
- function getUpsertSessionProjectStatement(db) {
174580
- let stmt = upsertSessionProjectStatements.get(db);
174581
- if (!stmt) {
174582
- stmt = db.prepare(`INSERT INTO session_projects (session_id, harness, project_path, updated_at)
174583
- VALUES (?, ?, ?, ?)
174584
- ON CONFLICT(session_id, harness) DO UPDATE SET
174585
- project_path = excluded.project_path,
174586
- updated_at = excluded.updated_at
174587
- WHERE session_projects.project_path <> excluded.project_path`);
174588
- upsertSessionProjectStatements.set(db, stmt);
174589
- }
174590
- return stmt;
174591
- }
174592
- function getRepairSessionChunkProjectStatement(db) {
174593
- let stmt = repairSessionChunkProjectStatements.get(db);
174594
- if (!stmt) {
174595
- stmt = db.prepare(`UPDATE compartment_chunk_embeddings
174596
- SET project_path = ?
174597
- WHERE session_id = ?
174598
- AND harness = ?
174599
- AND project_path <> ?`);
174600
- repairSessionChunkProjectStatements.set(db, stmt);
174601
- }
174602
- return stmt;
174603
- }
174604
- function getRepairProjectChunkProjectStatement(db) {
174605
- let stmt = repairProjectChunkProjectStatements.get(db);
174606
- if (!stmt) {
174607
- stmt = db.prepare(`UPDATE compartment_chunk_embeddings
174608
- SET project_path = (
174609
- SELECT sp.project_path
174610
- FROM session_projects sp
174611
- WHERE sp.session_id = compartment_chunk_embeddings.session_id
174612
- AND sp.harness = compartment_chunk_embeddings.harness
174613
- LIMIT 1
174614
- )
174615
- WHERE EXISTS (
174616
- SELECT 1
174617
- FROM session_projects sp
174618
- WHERE sp.session_id = compartment_chunk_embeddings.session_id
174619
- AND sp.harness = compartment_chunk_embeddings.harness
174620
- AND sp.project_path <> compartment_chunk_embeddings.project_path
174621
- AND (
174622
- sp.project_path = ?
174623
- OR compartment_chunk_embeddings.project_path = ?
174624
- )
174625
- )`);
174626
- repairProjectChunkProjectStatements.set(db, stmt);
174627
- }
174628
- return stmt;
174629
- }
174630
- function recordSessionProjectIdentity(db, sessionId, projectPath) {
174631
- if (!sessionId || !projectPath)
174632
- return;
174633
- const harness = getHarness();
174634
- const now = Date.now();
174635
- db.transaction(() => {
174636
- getUpsertSessionProjectStatement(db).run(sessionId, harness, projectPath, now);
174637
- getRepairSessionChunkProjectStatement(db).run(projectPath, sessionId, harness, projectPath);
174638
- })();
174639
- }
174640
- function repairMisScopedCompartmentChunkEmbeddingsForProject(db, projectPath) {
174641
- if (!projectPath)
174642
- return 0;
174643
- return getRepairProjectChunkProjectStatement(db).run(projectPath, projectPath).changes;
174644
- }
174645
-
174646
175119
  // ../plugin/src/features/magic-context/project-embedding-registry.ts
174647
175120
  var OFF_PROVIDER_IDENTITY = "embedding-provider:off";
174648
175121
  var SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
@@ -176295,7 +176768,7 @@ function getMemoryVerifications(db, memoryIds) {
176295
176768
  }
176296
176769
  // ../plugin/src/features/magic-context/memory/verification-paths.ts
176297
176770
  import { execFile } from "node:child_process";
176298
- import { existsSync as existsSync9, realpathSync as realpathSync2, statSync as statSync4 } from "node:fs";
176771
+ import { existsSync as existsSync12, realpathSync as realpathSync3, statSync as statSync4 } from "node:fs";
176299
176772
  import path7 from "node:path";
176300
176773
  import { promisify } from "node:util";
176301
176774
  var execFileAsync = promisify(execFile);
@@ -176322,7 +176795,7 @@ function isWithin(root, candidate) {
176322
176795
  }
176323
176796
  function safeRealpath(value) {
176324
176797
  try {
176325
- return realpathSync2.native(value);
176798
+ return realpathSync3.native(value);
176326
176799
  } catch {
176327
176800
  return null;
176328
176801
  }
@@ -176400,7 +176873,7 @@ function verificationFileExists(baseRoot, filePath) {
176400
176873
  return false;
176401
176874
  const root = path7.resolve(baseRoot);
176402
176875
  const candidate = path7.resolve(root, filePath);
176403
- return isWithin(root, candidate) && existsSync9(candidate);
176876
+ return isWithin(root, candidate) && existsSync12(candidate);
176404
176877
  }
176405
176878
  async function normalizeVerificationFiles(args) {
176406
176879
  const cwd = path7.resolve(args.cwd);
@@ -176433,7 +176906,7 @@ async function normalizeVerificationFiles(args) {
176433
176906
  warnings.push(`Skipped verification path "${value}" because it is the repo/project root.`);
176434
176907
  continue;
176435
176908
  }
176436
- if (existsSync9(candidate)) {
176909
+ if (existsSync12(candidate)) {
176437
176910
  try {
176438
176911
  if (statSync4(candidate).isDirectory()) {
176439
176912
  warnings.push(`Skipped verification path "${value}" because it is a directory.`);
@@ -176464,7 +176937,7 @@ async function normalizeVerificationFiles(args) {
176464
176937
  }
176465
176938
  normalized.push(tracked);
176466
176939
  } else {
176467
- if (!existsSync9(candidate)) {
176940
+ if (!existsSync12(candidate)) {
176468
176941
  warnings.push(`Skipped verification path "${value}" because it does not exist.`);
176469
176942
  continue;
176470
176943
  }
@@ -179107,7 +179580,7 @@ Output exactly JSON: {"met": false}`;
179107
179580
 
179108
179581
  // ../plugin/src/features/magic-context/dreamer/maintain-docs-protected-enforcement.ts
179109
179582
  init_logger();
179110
- import { existsSync as existsSync10, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
179583
+ import { existsSync as existsSync13, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
179111
179584
  import { join as join11 } from "node:path";
179112
179585
 
179113
179586
  // ../plugin/src/features/magic-context/dreamer/protected-regions.ts
@@ -179211,7 +179684,7 @@ function snapshotMaintainDocsFiles(docsDir) {
179211
179684
  for (const name2 of MAINTAIN_DOCS_SNAPSHOT_FILES) {
179212
179685
  const path9 = join11(docsDir, name2);
179213
179686
  try {
179214
- if (existsSync10(path9)) {
179687
+ if (existsSync13(path9)) {
179215
179688
  snapshot.set(name2, readFileSync6(path9, "utf8"));
179216
179689
  }
179217
179690
  } catch {}
@@ -179239,7 +179712,7 @@ function enforceMaintainDocsProtectedRegions(args) {
179239
179712
  init_logger();
179240
179713
 
179241
179714
  // ../plugin/src/features/magic-context/dreamer/map-memories-prompt.ts
179242
- import { existsSync as existsSync11, statSync as statSync5 } from "node:fs";
179715
+ import { existsSync as existsSync14, statSync as statSync5 } from "node:fs";
179243
179716
  import path9 from "node:path";
179244
179717
  var MAP_MEMORIES_SYSTEM_PROMPT = `You are a memory mapper for the magic-context system. You map project memories to the repository files that back them.
179245
179718
 
@@ -179277,7 +179750,7 @@ function extractMemoryCandidatePaths(content, repoDir) {
179277
179750
  if (!abs.startsWith(`${root}/`))
179278
179751
  continue;
179279
179752
  try {
179280
- if (existsSync11(abs) && statSync5(abs).isFile())
179753
+ if (existsSync14(abs) && statSync5(abs).isFile())
179281
179754
  found.add(rel);
179282
179755
  } catch {}
179283
179756
  if (found.size >= MAX_SEED_PATHS_PER_MEMORY)
@@ -181418,8 +181891,8 @@ async function runAgenticTask(config2, ctx, helpers) {
181418
181891
  const lastRunAt = getTaskScheduleState(db, projectIdentity, config2.task)?.lastRunAt ?? null;
181419
181892
  const maintainDocsSnapshot = task === "maintain-docs" ? snapshotMaintainDocsFiles(docsDir) : undefined;
181420
181893
  const existingDocs = task === "maintain-docs" ? {
181421
- architecture: existsSync12(`${docsDir}/ARCHITECTURE.md`),
181422
- structure: existsSync12(`${docsDir}/STRUCTURE.md`)
181894
+ architecture: existsSync15(`${docsDir}/ARCHITECTURE.md`),
181895
+ structure: existsSync15(`${docsDir}/STRUCTURE.md`)
181423
181896
  } : undefined;
181424
181897
  const userMemories = task === "curate" ? getActiveUserMemories(db).map((um) => ({ id: um.id, content: um.content })) : undefined;
181425
181898
  let curateMemories;
@@ -182634,7 +183107,7 @@ function pruneNestedConfigLeaf(block, relativePath) {
182634
183107
  }
182635
183108
 
182636
183109
  // src/config/index.ts
182637
- import { existsSync as existsSync14, readFileSync as readFileSync8 } from "node:fs";
183110
+ import { existsSync as existsSync17, readFileSync as readFileSync8 } from "node:fs";
182638
183111
 
182639
183112
  // ../plugin/src/config/migrate-dreamer-v2.ts
182640
183113
  var OLD_VERIFY_TASK = "verify";
@@ -183016,7 +183489,7 @@ function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
183016
183489
  }
183017
183490
 
183018
183491
  // ../plugin/src/config/variable.ts
183019
- import { existsSync as existsSync13, readFileSync as readFileSync7 } from "node:fs";
183492
+ import { existsSync as existsSync16, readFileSync as readFileSync7 } from "node:fs";
183020
183493
  import { homedir as homedir4 } from "node:os";
183021
183494
  import { dirname as dirname6, isAbsolute as isAbsolute2, resolve } from "node:path";
183022
183495
 
@@ -183151,7 +183624,7 @@ function substituteConfigVariables(input) {
183151
183624
  if (sensitiveReason) {
183152
183625
  warnings.push(`${token} resolves to a sensitive path (${sensitiveReason}: ${filePath}); ` + "inlining its contents into config — make sure this is intentional.");
183153
183626
  }
183154
- if (!existsSync13(filePath)) {
183627
+ if (!existsSync16(filePath)) {
183155
183628
  warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
183156
183629
  continue;
183157
183630
  }
@@ -183180,10 +183653,10 @@ function getUserConfigPaths() {
183180
183653
  return [`${basePath}.jsonc`, `${basePath}.json`];
183181
183654
  }
183182
183655
  function resolveFirstExisting(paths) {
183183
- return paths.find((path11) => existsSync14(path11));
183656
+ return paths.find((path11) => existsSync17(path11));
183184
183657
  }
183185
183658
  function resolvePiLegacyFallback(sources) {
183186
- return sources.find((source) => existsSync14(source.path)) ?? null;
183659
+ return sources.find((source) => existsSync17(source.path)) ?? null;
183187
183660
  }
183188
183661
  function loadConfigFile(path11, scope) {
183189
183662
  try {
@@ -183320,7 +183793,7 @@ function loadPiConfig(opts = {}) {
183320
183793
  if (loaded)
183321
183794
  loadedFiles.push(loaded);
183322
183795
  }
183323
- const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync14(source.path));
183796
+ const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync17(source.path));
183324
183797
  const userPath = resolveFirstExisting(getUserConfigPaths());
183325
183798
  const userLegacyFallback = userPath ? null : resolvePiLegacyFallback(harnessLegacy.user);
183326
183799
  const userReadPath = userPath ?? userLegacyFallback?.path;
@@ -183329,7 +183802,7 @@ function loadPiConfig(opts = {}) {
183329
183802
  if (loaded)
183330
183803
  loadedFiles.push(loaded);
183331
183804
  }
183332
- const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync14(source.path));
183805
+ const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync17(source.path));
183333
183806
  if (userLegacyFallback) {
183334
183807
  warnings.push(`[user config] reading legacy config from ${userLegacyFallback.path} until migration completes; run \`npx @cortexkit/magic-context doctor\` to consolidate into the shared CortexKit location.`);
183335
183808
  } else if (legacyUserUnmigrated) {
@@ -183430,7 +183903,7 @@ function loadPiConfigDetailed(opts = {}) {
183430
183903
  if (loaded)
183431
183904
  loadedFiles.push(loaded);
183432
183905
  }
183433
- const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync14(source.path));
183906
+ const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync17(source.path));
183434
183907
  const userPath = resolveFirstExisting(getUserConfigPaths());
183435
183908
  const userLegacyFallback = userPath ? null : resolvePiLegacyFallback(harnessLegacy.user);
183436
183909
  const userReadPath = userPath ?? userLegacyFallback?.path;
@@ -183439,7 +183912,7 @@ function loadPiConfigDetailed(opts = {}) {
183439
183912
  if (loaded)
183440
183913
  loadedFiles.push(loaded);
183441
183914
  }
183442
- const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync14(source.path));
183915
+ const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync17(source.path));
183443
183916
  if (userLegacyFallback) {
183444
183917
  warnings.push(`[user config] reading legacy config from ${userLegacyFallback.path} until migration completes; run \`npx @cortexkit/magic-context doctor\` to consolidate into the shared CortexKit location.`);
183445
183918
  } else if (legacyUserUnmigrated) {
@@ -184910,7 +185383,8 @@ var canonicalReasons = new Set([
184910
185383
  "first_render",
184911
185384
  "pressure_refold",
184912
185385
  "upgrade_state",
184913
- "cached_m1_missing"
185386
+ "cached_m1_missing",
185387
+ "project_change"
184914
185388
  ]);
184915
185389
  var piReasonAliases = {
184916
185390
  project_memory_change: "project_memory_epoch",
@@ -186189,6 +186663,43 @@ function semanticSnapBoundary(args) {
186189
186663
  }
186190
186664
  return snapped;
186191
186665
  }
186666
+ function snapWrapupBoundaryToUser(args) {
186667
+ const { messages, index, candidate, offset, triggerBudget } = args;
186668
+ if (candidate <= offset)
186669
+ return candidate;
186670
+ const snapTokenLimit = Math.min(Math.max(triggerBudget, 2000), 48000);
186671
+ for (let ordinal = candidate;ordinal >= offset; ordinal -= 1) {
186672
+ const message = messages.find((m) => m.ordinal === ordinal);
186673
+ if (!message)
186674
+ continue;
186675
+ if (message.role !== "user" || !hasMeaningfulUserText(message.parts))
186676
+ continue;
186677
+ const extraTokens = index.rangeTokens(ordinal, candidate);
186678
+ if (extraTokens <= snapTokenLimit)
186679
+ return ordinal;
186680
+ return candidate;
186681
+ }
186682
+ return candidate;
186683
+ }
186684
+ function fenceWrapupBoundaryForToolArcs(args) {
186685
+ let boundary = args.candidate;
186686
+ const maxPasses = args.arcs.length + 1;
186687
+ for (let pass = 0;pass < maxPasses; pass += 1) {
186688
+ let next = boundary;
186689
+ for (const arc of args.arcs) {
186690
+ if (arc.resOrdinal === null) {
186691
+ continue;
186692
+ }
186693
+ if (arc.invOrdinal >= args.lastCompartmentEndOrdinal + 1 && arc.invOrdinal < next && next <= arc.resOrdinal) {
186694
+ next = arc.invOrdinal;
186695
+ }
186696
+ }
186697
+ if (next === boundary)
186698
+ return boundary;
186699
+ boundary = next;
186700
+ }
186701
+ return boundary;
186702
+ }
186192
186703
  function applyHeadCap(args) {
186193
186704
  const { index, protectedTailStart, offset, arcs, capTokens, recentOpenArcCutoff } = args;
186194
186705
  if (offset >= protectedTailStart)
@@ -186444,6 +186955,124 @@ function resolveBoundaryContext(args) {
186444
186955
  function resolveOpenCodeProtectedTailBoundary(args) {
186445
186956
  return resolveProtectedTailBoundary(resolveBoundaryContext(args));
186446
186957
  }
186958
+ function resolveWrapupProtectedTailBoundary(args) {
186959
+ const ctx = resolveBoundaryContext({ ...args, mode: "manual-wrapup" });
186960
+ const createdAt = ctx.createdAt ?? Date.now();
186961
+ const messages = readRawSessionMessages(ctx.sessionId);
186962
+ const absoluteMessageCount = getCachedAbsoluteMessageCount(ctx.sessionId) ?? undefined;
186963
+ const index = buildTrueRawTokenIndex(ctx.sessionId, messages, {
186964
+ providerShapeVersion: ctx.providerShapeVersion,
186965
+ cacheNamespace: ctx.cacheNamespace,
186966
+ absoluteMessageCount,
186967
+ storedTotalForMessage: ctx.storedTokenTotals ? (m) => {
186968
+ const value = ctx.storedTokenTotals?.get(m.id);
186969
+ return value === undefined ? null : value;
186970
+ } : undefined
186971
+ });
186972
+ const rawMessageCount = index.rawMessageCount;
186973
+ const offset = Math.max(1, ctx.lastCompartmentEndOrdinal + 1);
186974
+ const anchorRawMessageCount = Math.max(0, Math.min(rawMessageCount, Math.floor(args.anchorRawMessageCount ?? rawMessageCount)));
186975
+ const usagePercentage = clampPercentage(ctx.usage?.percentage ?? 0);
186976
+ const usageInputTokens = Math.max(0, Math.round(ctx.usage?.inputTokens ?? 0));
186977
+ const rawMessagesAboveLastCompartment = Math.max(0, anchorRawMessageCount - offset + 1);
186978
+ const keep = Math.max(1, Math.floor(args.messagesToKeep));
186979
+ let targetProtectedTailStart = offset;
186980
+ let boundaryReason = "manual-wrapup-empty";
186981
+ if (rawMessageCount === 0 || rawMessagesAboveLastCompartment <= keep) {
186982
+ targetProtectedTailStart = offset;
186983
+ boundaryReason = rawMessageCount === 0 ? "manual-wrapup-empty" : "manual-wrapup-within-keep";
186984
+ } else {
186985
+ targetProtectedTailStart = anchorRawMessageCount - keep + 1;
186986
+ boundaryReason = "manual-wrapup-keep-watermark";
186987
+ const arcs = buildToolArcs(messages);
186988
+ const fenced = fenceWrapupBoundaryForToolArcs({
186989
+ candidate: targetProtectedTailStart,
186990
+ arcs,
186991
+ lastCompartmentEndOrdinal: ctx.lastCompartmentEndOrdinal
186992
+ });
186993
+ if (fenced !== targetProtectedTailStart)
186994
+ boundaryReason = "manual-wrapup-tool-arc";
186995
+ targetProtectedTailStart = fenced;
186996
+ const snapped = snapWrapupBoundaryToUser({
186997
+ messages,
186998
+ index,
186999
+ candidate: targetProtectedTailStart,
187000
+ offset,
187001
+ triggerBudget: ctx.triggerBudget
187002
+ });
187003
+ if (snapped !== targetProtectedTailStart)
187004
+ boundaryReason = "manual-wrapup-user-snap";
187005
+ targetProtectedTailStart = snapped;
187006
+ const refenced = fenceWrapupBoundaryForToolArcs({
187007
+ candidate: targetProtectedTailStart,
187008
+ arcs,
187009
+ lastCompartmentEndOrdinal: ctx.lastCompartmentEndOrdinal
187010
+ });
187011
+ if (refenced !== targetProtectedTailStart)
187012
+ boundaryReason = "manual-wrapup-tool-arc";
187013
+ targetProtectedTailStart = refenced;
187014
+ }
187015
+ targetProtectedTailStart = clampOrdinal(targetProtectedTailStart, rawMessageCount);
187016
+ const target = deriveProtectedTailTokenTarget({
187017
+ contextLimit: ctx.contextLimit,
187018
+ executeThresholdPercentage: ctx.executeThresholdPercentage,
187019
+ usagePercentage,
187020
+ triggerBudget: ctx.triggerBudget
187021
+ });
187022
+ const perRunCap = selectPerRunCap({
187023
+ usagePercentage,
187024
+ N: target.N,
187025
+ contextLimit: ctx.contextLimit,
187026
+ executeThresholdPercentage: ctx.executeThresholdPercentage
187027
+ });
187028
+ const head = applyHeadCap({
187029
+ index,
187030
+ protectedTailStart: targetProtectedTailStart,
187031
+ offset,
187032
+ arcs: buildToolArcs(messages),
187033
+ lastCompartmentEndOrdinal: ctx.lastCompartmentEndOrdinal,
187034
+ capTokens: perRunCap,
187035
+ recentOpenArcCutoff: targetProtectedTailStart
187036
+ });
187037
+ const eligibleEndOrdinal = Math.min(head.eligibleEndOrdinal, targetProtectedTailStart);
187038
+ const rawRangeFingerprint = computeRawRangeFingerprint(messages, offset, eligibleEndOrdinal);
187039
+ const snapshot = {
187040
+ sessionId: ctx.sessionId,
187041
+ mode: "manual-wrapup",
187042
+ offset,
187043
+ offsetMessageId: boundaryMessageId(index, offset),
187044
+ protectedTailStart: targetProtectedTailStart,
187045
+ protectedTailStartMessageId: boundaryMessageId(index, targetProtectedTailStart),
187046
+ eligibleEndOrdinal,
187047
+ eligibleEndMessageId: boundaryMessageId(index, eligibleEndOrdinal - 1),
187048
+ rawMessageCountAtTrigger: rawMessageCount,
187049
+ rawLastMessageIdAtTrigger: boundaryMessageId(index, rawMessageCount),
187050
+ N: keep,
187051
+ usagePercentage,
187052
+ usageInputTokens,
187053
+ usageSource: ctx.usageSource,
187054
+ contextLimit: ctx.contextLimit,
187055
+ executeThresholdPercentage: ctx.executeThresholdPercentage,
187056
+ triggerBudget: ctx.triggerBudget,
187057
+ priorBoundaryOrdinal: ctx.priorBoundaryOrdinal,
187058
+ migrationFloorActive: ctx.migrationFloorActive,
187059
+ emergencyTailScale: ctx.emergencyTailScale,
187060
+ providerShapeVersion: ctx.providerShapeVersion,
187061
+ cacheNamespace: ctx.cacheNamespace,
187062
+ createdAt,
187063
+ rawRangeFingerprint,
187064
+ trueRawEligibleTokens: index.rangeTokens(offset, targetProtectedTailStart),
187065
+ oversizeAtomicUnit: head.oversizeAtomicUnit,
187066
+ boundaryReason
187067
+ };
187068
+ return {
187069
+ snapshot,
187070
+ rawMessagesAboveLastCompartment,
187071
+ anchorRawMessageCount,
187072
+ targetProtectedTailStart,
187073
+ targetEligibleEndOrdinal: targetProtectedTailStart
187074
+ };
187075
+ }
186447
187076
  function getRawHistoryEligibility(db, sessionId) {
186448
187077
  const lastCompartmentEnd = getLastCompartmentEndMessage(db, sessionId);
186449
187078
  const offset = Math.max(1, lastCompartmentEnd + 1);
@@ -188489,6 +189118,147 @@ function searchMessages(args) {
188489
189118
  role: entry.row.role
188490
189119
  }));
188491
189120
  }
189121
+ var NOTE_SEARCHABLE_STATUSES = ["active", "pending", "ready", "dismissed"];
189122
+ function noteSearchText(note) {
189123
+ const reason = note.readyReason?.trim();
189124
+ return reason ? `${note.content}
189125
+ Reason: ${reason}` : note.content;
189126
+ }
189127
+ function tokenizeKeywordNeedle(text) {
189128
+ const matches = text.toLowerCase().match(/[a-z0-9/._:-]+/g) ?? [];
189129
+ const seen = new Set;
189130
+ const tokens = [];
189131
+ for (const match of matches) {
189132
+ if (match.length <= 1 || !/[a-z0-9]/.test(match) || seen.has(match)) {
189133
+ continue;
189134
+ }
189135
+ seen.add(match);
189136
+ tokens.push(match);
189137
+ }
189138
+ return tokens;
189139
+ }
189140
+ function rankNotesForNeedle(notes, needle) {
189141
+ const normalizedNeedle = needle.trim().toLowerCase();
189142
+ if (normalizedNeedle.length === 0) {
189143
+ return [];
189144
+ }
189145
+ const needleTokens = tokenizeKeywordNeedle(normalizedNeedle);
189146
+ const ranked = [];
189147
+ for (const note of notes) {
189148
+ const text = noteSearchText(note);
189149
+ const normalizedText = text.toLowerCase();
189150
+ const noteTokens = new Set(tokenizeKeywordNeedle(normalizedText));
189151
+ const exact = normalizedText.includes(normalizedNeedle);
189152
+ const matchedTokens = needleTokens.filter((token) => noteTokens.has(token)).length;
189153
+ if (!exact && matchedTokens === 0) {
189154
+ continue;
189155
+ }
189156
+ const coverage = needleTokens.length > 0 ? matchedTokens / needleTokens.length : 0;
189157
+ const score = (exact ? 2 : 0) + coverage + (needleTokens.length > 1 && matchedTokens === needleTokens.length ? 0.5 : 0);
189158
+ ranked.push({ note, score, text });
189159
+ }
189160
+ return ranked.sort((left, right) => {
189161
+ if (right.score !== left.score) {
189162
+ return right.score - left.score;
189163
+ }
189164
+ if (right.note.createdAt !== left.note.createdAt) {
189165
+ return right.note.createdAt - left.note.createdAt;
189166
+ }
189167
+ return left.note.id - right.note.id;
189168
+ });
189169
+ }
189170
+ function searchNotes(args) {
189171
+ if (args.limit <= 0) {
189172
+ return [];
189173
+ }
189174
+ const notes = [
189175
+ ...getNotes(args.db, {
189176
+ sessionId: args.sessionId,
189177
+ type: "session",
189178
+ status: NOTE_SEARCHABLE_STATUSES
189179
+ }),
189180
+ ...getNotes(args.db, {
189181
+ projectPath: args.projectPath,
189182
+ type: "smart",
189183
+ status: NOTE_SEARCHABLE_STATUSES
189184
+ })
189185
+ ];
189186
+ if (notes.length === 0) {
189187
+ return [];
189188
+ }
189189
+ const baseList = rankNotesForNeedle(notes, args.query);
189190
+ const probes = args.probes ?? [];
189191
+ if (probes.length === 0) {
189192
+ const ranked2 = baseList.slice(0, args.limit);
189193
+ return ranked2.map((entry, rank) => ({
189194
+ source: "note",
189195
+ content: previewText(entry.text),
189196
+ score: linearDecayScore(rank, ranked2.length),
189197
+ noteId: entry.note.id,
189198
+ status: entry.note.status,
189199
+ createdAt: entry.note.createdAt,
189200
+ anchorOrdinal: entry.note.anchorOrdinal,
189201
+ sourceSessionId: entry.note.sessionId
189202
+ }));
189203
+ }
189204
+ const queryLists = [];
189205
+ if (baseList.length > 0) {
189206
+ queryLists.push({ rows: baseList, weight: 1 });
189207
+ }
189208
+ const probeWeights = new Map;
189209
+ for (const probe of probes) {
189210
+ const rows = rankNotesForNeedle(notes, probe);
189211
+ if (rows.length === 0) {
189212
+ continue;
189213
+ }
189214
+ const weight = probeDiscriminationWeight(rows.length, notes.length);
189215
+ probeWeights.set(probe, weight);
189216
+ queryLists.push({ rows, weight });
189217
+ }
189218
+ const fused = new Map;
189219
+ for (const list of queryLists) {
189220
+ list.rows.forEach((row, rank) => {
189221
+ const rrf = list.weight / (RRF_K + rank);
189222
+ const existing = fused.get(row.note.id);
189223
+ if (existing) {
189224
+ existing.score += rrf;
189225
+ } else {
189226
+ fused.set(row.note.id, { entry: row, score: rrf });
189227
+ }
189228
+ });
189229
+ }
189230
+ for (const match of fused.values()) {
189231
+ let best = 0;
189232
+ for (const probe of probes) {
189233
+ const weight = probeWeights.get(probe) ?? 0;
189234
+ if (weight > best && containsProbeVerbatim(match.entry.text, [probe])) {
189235
+ best = weight;
189236
+ }
189237
+ }
189238
+ if (best > 0) {
189239
+ match.score += best * VERBATIM_RANK_BONUS;
189240
+ }
189241
+ }
189242
+ const ranked = [...fused.values()].sort((left, right) => {
189243
+ if (right.score !== left.score) {
189244
+ return right.score - left.score;
189245
+ }
189246
+ if (right.entry.note.createdAt !== left.entry.note.createdAt) {
189247
+ return right.entry.note.createdAt - left.entry.note.createdAt;
189248
+ }
189249
+ return left.entry.note.id - right.entry.note.id;
189250
+ }).slice(0, args.limit);
189251
+ return ranked.map((entry, rank) => ({
189252
+ source: "note",
189253
+ content: previewText(entry.entry.text),
189254
+ score: linearDecayScore(rank, ranked.length),
189255
+ noteId: entry.entry.note.id,
189256
+ status: entry.entry.note.status,
189257
+ createdAt: entry.entry.note.createdAt,
189258
+ anchorOrdinal: entry.entry.note.anchorOrdinal,
189259
+ sourceSessionId: entry.entry.note.sessionId
189260
+ }));
189261
+ }
188492
189262
  function searchCompartmentChunks(args) {
188493
189263
  if (!args.queryEmbedding || args.limit <= 0 || !args.modelId || args.modelId === "off")
188494
189264
  return [];
@@ -188574,6 +189344,8 @@ function getSourceBoost(result) {
188574
189344
  return GIT_COMMIT_SOURCE_BOOST;
188575
189345
  case "primer":
188576
189346
  return PRIMER_SOURCE_BOOST;
189347
+ case "note":
189348
+ return 1;
188577
189349
  }
188578
189350
  }
188579
189351
  function compareUnifiedResults(left, right) {
@@ -188597,6 +189369,9 @@ function compareUnifiedResults(left, right) {
188597
189369
  if (left.source === "primer" && right.source === "primer") {
188598
189370
  return right.support - left.support || left.primerId - right.primerId;
188599
189371
  }
189372
+ if (left.source === "note" && right.source === "note") {
189373
+ return right.createdAt - left.createdAt || left.noteId - right.noteId;
189374
+ }
188600
189375
  return 0;
188601
189376
  }
188602
189377
  function toGitCommitResult(hit) {
@@ -188664,11 +189439,11 @@ function searchPrimers(args) {
188664
189439
  }
188665
189440
  function resolveSources(sources) {
188666
189441
  if (sources === undefined) {
188667
- return new Set(["memory", "message", "git_commit", "primer"]);
189442
+ return new Set(["memory", "message", "git_commit", "primer", "note"]);
188668
189443
  }
188669
189444
  const set2 = new Set;
188670
189445
  for (const source of sources) {
188671
- if (source === "memory" || source === "message" || source === "git_commit" || source === "primer") {
189446
+ if (source === "memory" || source === "message" || source === "git_commit" || source === "primer" || source === "note") {
188672
189447
  set2.add(source);
188673
189448
  }
188674
189449
  }
@@ -188691,6 +189466,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
188691
189466
  const runMessages = activeSources.has("message");
188692
189467
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
188693
189468
  const runPrimers = activeSources.has("primer") && memoryFeatureEnabled;
189469
+ const runNotes = activeSources.has("note");
188694
189470
  const runCompartmentChunks = runMessages && memoryFeatureEnabled && embeddingEnabled;
188695
189471
  const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks || runPrimers) && embeddingEnabled && isEmbeddingRuntimeEnabled();
188696
189472
  const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options.signal).catch((error51) => {
@@ -188726,7 +189502,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
188726
189502
  compartments: compartmentResults,
188727
189503
  limit: tierLimit
188728
189504
  });
188729
- const [memoryResults, gitCommitResults, primerResults] = await Promise.all([
189505
+ const [memoryResults, gitCommitResults, primerResults, noteResults] = await Promise.all([
188730
189506
  runMemory ? searchMemories({
188731
189507
  db,
188732
189508
  projectPath,
@@ -188753,9 +189529,23 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
188753
189529
  limit: tierLimit,
188754
189530
  queryEmbedding,
188755
189531
  queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null
189532
+ })) : Promise.resolve([]),
189533
+ runNotes ? Promise.resolve(searchNotes({
189534
+ db,
189535
+ sessionId,
189536
+ projectPath,
189537
+ query: trimmedQuery,
189538
+ limit: tierLimit,
189539
+ probes: messageProbes
188756
189540
  })) : Promise.resolve([])
188757
189541
  ]);
188758
- const results = [...memoryResults, ...primerResults, ...messageLikeResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
189542
+ const results = [
189543
+ ...memoryResults,
189544
+ ...primerResults,
189545
+ ...messageLikeResults,
189546
+ ...gitCommitResults,
189547
+ ...noteResults
189548
+ ].sort(compareUnifiedResults).slice(0, limit);
188759
189549
  const countRetrievals = options.countRetrievals ?? true;
188760
189550
  if (countRetrievals) {
188761
189551
  const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
@@ -188824,6 +189614,10 @@ function renderFragment(result, charCap) {
188824
189614
  const compressed = cavemanCompress(result.content, "ultra");
188825
189615
  return truncate(compressed, charCap);
188826
189616
  }
189617
+ case "note": {
189618
+ const compressed = cavemanCompress(result.content, "ultra");
189619
+ return truncate(compressed, charCap);
189620
+ }
188827
189621
  }
188828
189622
  }
188829
189623
  function buildAutoSearchHint(results, options = {}) {
@@ -189045,13 +189839,11 @@ async function runAutoSearchHintForPi(args) {
189045
189839
  };
189046
189840
  results = await unifiedSearchWithTimeout(db, sessionId, options.projectPath, rawPrompt, searchOptions, AUTO_SEARCH_TIMEOUT_MS);
189047
189841
  } catch (error51) {
189048
- log(`[auto-search] unified search failed for session ${sessionId}: ${error51 instanceof Error ? error51.message : String(error51)}`);
189049
- writeNoHintAndReconcile("error");
189842
+ log(`[auto-search] unified search failed for session ${sessionId} (will retry next pass): ${error51 instanceof Error ? error51.message : String(error51)}`);
189050
189843
  return messages;
189051
189844
  }
189052
189845
  if (results === null) {
189053
- sessionLog(sessionId, `auto-search: timed out after ${AUTO_SEARCH_TIMEOUT_MS}ms, skipping hint for this turn`);
189054
- writeNoHintAndReconcile("timeout");
189846
+ sessionLog(sessionId, `auto-search: timed out after ${AUTO_SEARCH_TIMEOUT_MS}ms, skipping hint for this turn (will retry)`);
189055
189847
  return messages;
189056
189848
  }
189057
189849
  if (results.length === 0) {
@@ -190626,6 +191418,15 @@ function findCompartmentBoundaryForSnapshot(markers) {
190626
191418
  return null;
190627
191419
  return markers.lastBaselineEndMessageId;
190628
191420
  }
191421
+ function resolveRenderedCompartmentBoundary(compartments, boundaryId) {
191422
+ if (!boundaryId)
191423
+ return { endMessageId: null, ordinal: null };
191424
+ const boundary = compartments.find((compartment) => compartment.endMessageId === boundaryId);
191425
+ return {
191426
+ endMessageId: boundaryId,
191427
+ ordinal: typeof boundary?.endMessage === "number" ? boundary.endMessage : null
191428
+ };
191429
+ }
190629
191430
  function prependM0M1Messages(piMessages, m0, m1) {
190630
191431
  const firstTimestamp = piMessages[0]?.timestamp;
190631
191432
  const baseTimestamp = typeof firstTimestamp === "number" ? firstTimestamp : Date.now();
@@ -190755,6 +191556,7 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
190755
191556
  }
190756
191557
  }
190757
191558
  const boundaryId = findCompartmentBoundaryForSnapshot(markers);
191559
+ const renderedBoundary = resolveRenderedCompartmentBoundary(currentCompartments, boundaryId);
190758
191560
  const skippedVisibleMessages = boundaryId ? trimPiMessagesToBoundary(piMessages, entryIds, boundaryId) : 0;
190759
191561
  prependM0M1Messages(piMessages, m0, m1);
190760
191562
  sessionLog(state.sessionId, `injected m[0]/m[1] into Pi messages (${m0.length} + ${m1.length} bytes, materialized=${materialized}${decision.reason ? ` reason=${decision.reason}` : ""})`);
@@ -190771,6 +191573,8 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
190771
191573
  m0Reason: decision.reason,
190772
191574
  m0Bytes: m0.length,
190773
191575
  m1Bytes: m1.length,
191576
+ contentionExhausted,
191577
+ renderedBoundary,
190774
191578
  syntheticLeadingCount: 2
190775
191579
  };
190776
191580
  }
@@ -194042,7 +194846,9 @@ async function runPiHistorian(deps) {
194042
194846
  compartmentLeaseHolderId,
194043
194847
  readBranchEntries,
194044
194848
  notifyIssue,
194045
- ensureProjectRegistered = ensureProjectRegisteredFromPiDirectory
194849
+ ensureProjectRegistered = ensureProjectRegisteredFromPiDirectory,
194850
+ forceDrainQuota,
194851
+ forceKeepLastCompartment
194046
194852
  } = deps;
194047
194853
  const notify = async (message) => {
194048
194854
  if (shouldSuppressHistorianAlert(sessionId)) {
@@ -194117,7 +194923,8 @@ async function runPiHistorian(deps) {
194117
194923
  if (protectedTailStart <= offset || eligibleEndOrdinal <= offset) {
194118
194924
  sessionLog(sessionId, `historian no-op: protectedTailStart=${protectedTailStart} eligibleEnd=${eligibleEndOrdinal} <= offset=${offset} — nothing to compact`);
194119
194925
  if (boundarySnapshot.usagePercentage < 80) {
194120
- clearEmergencyRecovery(db, sessionId);
194926
+ if (!isWrapupInProgress(db, sessionId))
194927
+ clearEmergencyRecovery(db, sessionId);
194121
194928
  } else {
194122
194929
  recordHighPressureNoEligibleHead(db, boundarySnapshot);
194123
194930
  }
@@ -194126,7 +194933,7 @@ async function runPiHistorian(deps) {
194126
194933
  }
194127
194934
  const perRunCap = selectPerRunCap(boundarySnapshot);
194128
194935
  const usable = Math.max(1, Math.round(boundarySnapshot.contextLimit * boundarySnapshot.executeThresholdPercentage / 100));
194129
- const reserve = reserveProtectedTailDrainTokens({
194936
+ const reserve = forceDrainQuota ? { ok: true, reservation: null } : reserveProtectedTailDrainTokens({
194130
194937
  db,
194131
194938
  sessionId,
194132
194939
  runId: crypto2.randomUUID(),
@@ -194144,10 +194951,12 @@ async function runPiHistorian(deps) {
194144
194951
  }
194145
194952
  drainReservation = reserve.reservation;
194146
194953
  const chunk = readSessionChunk(sessionId, historianChunkTokens, offset, eligibleEndOrdinal);
194954
+ const forceKeepLastCompartmentForChunk = forceKeepLastCompartment === true && !chunk.hasMore;
194147
194955
  if (!chunk.text || chunk.messageCount === 0) {
194148
194956
  sessionLog(sessionId, `historian no-op: chunk empty after filtering (messageCount=${chunk.messageCount}, textLen=${chunk.text?.length ?? 0}) range=${offset}-${protectedTailStart - 1}`);
194149
194957
  if (boundarySnapshot.usagePercentage < 80) {
194150
- clearEmergencyRecovery(db, sessionId);
194958
+ if (!isWrapupInProgress(db, sessionId))
194959
+ clearEmergencyRecovery(db, sessionId);
194151
194960
  } else {
194152
194961
  recordHighPressureNoEligibleHead(db, boundarySnapshot);
194153
194962
  }
@@ -194337,7 +195146,7 @@ ${chunkText}`,
194337
195146
  const inEmergency = getOverflowState(db, sessionId).needsEmergencyRecovery;
194338
195147
  const emittedCompartments = validatedPass.compartments;
194339
195148
  let newCompartments = emittedCompartments;
194340
- if (!inEmergency && emittedCompartments.length >= 2) {
195149
+ if (!inEmergency && !forceKeepLastCompartmentForChunk && emittedCompartments.length >= 2) {
194341
195150
  const lastEmitted = emittedCompartments[emittedCompartments.length - 1];
194342
195151
  const lookaheadMargin = chunk.endIndex - lastEmitted.endMessage;
194343
195152
  if (lookaheadMargin <= BOUNDARY_HEALING_SLACK) {
@@ -194370,9 +195179,19 @@ ${chunkText}`,
194370
195179
  }
194371
195180
  }
194372
195181
  const discardedLast = newCompartments.length < emittedCompartments.length;
195182
+ const weakLookaheadFinalCompartment = forceKeepLastCompartmentForChunk;
195183
+ const skipUnanchoredPromotion = discardedLast || weakLookaheadFinalCompartment;
194373
195184
  const embeddingActive = memoryEnabled !== false;
194374
195185
  const promotionActive = embeddingActive && autoPromote !== false;
194375
- const publishableEvents = (validatedPass.events ?? []).filter((e) => e.atCompartment == null || e.atCompartment <= newCompartments.length);
195186
+ const publishableEvents = (validatedPass.events ?? []).filter((e) => {
195187
+ if (typeof e.atCompartment !== "number")
195188
+ return !weakLookaheadFinalCompartment;
195189
+ if (e.atCompartment > newCompartments.length)
195190
+ return false;
195191
+ if (weakLookaheadFinalCompartment && e.atCompartment >= emittedCompartments.length)
195192
+ return false;
195193
+ return true;
195194
+ });
194376
195195
  let promotedFactRefs = [];
194377
195196
  let persistedIds = [];
194378
195197
  if (!compartmentLeaseHolderId) {
@@ -194391,7 +195210,7 @@ ${chunkText}`,
194391
195210
  }
194392
195211
  appendCompartments(db, sessionId, newCompartments);
194393
195212
  persistedIds = getCompartments(db, sessionId).slice(-newCompartments.length).map((c) => c.id);
194394
- if (promotionActive && !discardedLast) {
195213
+ if (promotionActive && !skipUnanchoredPromotion) {
194395
195214
  promotedFactRefs = promoteSessionFactsDurable(db, sessionId, projectPath, validatedPass.facts ?? []);
194396
195215
  }
194397
195216
  if (publishableEvents.length > 0) {
@@ -194406,7 +195225,8 @@ ${chunkText}`,
194406
195225
  clearHistorianFailureState(db, sessionId);
194407
195226
  clearHistorianDrainFailure(db, sessionId);
194408
195227
  recordProtectedTailPublicationFloor(db, sessionId, lastNewEnd + 1);
194409
- clearEmergencyRecovery(db, sessionId);
195228
+ if (!isWrapupInProgress(db, sessionId))
195229
+ clearEmergencyRecovery(db, sessionId);
194410
195230
  if (firstKeptEntryId && lastNewEndMessageId) {
194411
195231
  setPendingPiCompactionMarkerState(db, sessionId, {
194412
195232
  firstKeptEntryId,
@@ -194430,7 +195250,7 @@ ${chunkText}`,
194430
195250
  completedSuccessfully = true;
194431
195251
  sessionLog(sessionId, `historian: published ${newCompartments.length} compartment(s), ${validatedPass.facts?.length ?? 0} fact(s) covering messages ${chunk.startIndex}-${lastNewEnd}`);
194432
195252
  onNoteTrigger(db, sessionId, "historian_complete");
194433
- if (userMemoriesEnabled === true && !discardedLast && validatedPass.userObservations?.length) {
195253
+ if (userMemoriesEnabled === true && !skipUnanchoredPromotion && validatedPass.userObservations?.length) {
194434
195254
  try {
194435
195255
  insertUserMemoryCandidates(db, validatedPass.userObservations.map((obs) => ({
194436
195256
  content: obs,
@@ -194443,7 +195263,7 @@ ${chunkText}`,
194443
195263
  sessionLog(sessionId, "failed to store user memory candidates:", error51);
194444
195264
  }
194445
195265
  }
194446
- if (!discardedLast && validatedPass.primerCandidates?.length && projectPath) {
195266
+ if (!skipUnanchoredPromotion && validatedPass.primerCandidates?.length && projectPath) {
194447
195267
  try {
194448
195268
  const firstNew = newCompartments[0];
194449
195269
  const lastNew = newCompartments[newCompartments.length - 1];
@@ -195140,7 +195960,7 @@ Drop silently — do not narrate it. NEVER drop large ranges blindly (e.g., "1-5
195140
195960
  Older tool calls may show \`[dropped §N§]\` sentinels; that is normal context management, not a pattern to copy. ALWAYS make fresh real tool calls when you need data again; never fabricate or inline tool output.`;
195141
195961
  var CAVEMAN_COMPRESSION_WARNING = `
195142
195962
  **BEWARE**: History compression is on; older user AND assistant text — including your own earlier responses — has been deterministically rewritten in a terse caveman style (dropped articles, missing auxiliaries, \`//\` instead of connectives like \`because\`). This is automatic context compression that runs after the fact, not your actual prior wording or the user's. **DO NOT mimic this style in new turns.** Write fresh responses in normal prose. If you notice your output drifting into caveman cadence, that drift is in-context-learning bleeding from the compressed history — consciously revert to full sentences.`;
195143
- function buildMagicContextSection(_agent, protectedTags, ctxReduceEnabled = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false, language, memoryEnabled = true) {
195963
+ function buildMagicContextSection(_agent, protectedTags, ctxReduceCallable = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false, language, memoryEnabled = true) {
195144
195964
  if (subagentMode) {
195145
195965
  return `## Magic Context
195146
195966
 
@@ -195151,12 +195971,12 @@ When \`surface_condition\` is provided with \`write\`, the note becomes a projec
195151
195971
  The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met.
195152
195972
  Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")\`` : "";
195153
195973
  const temporalGuidance = temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : "";
195154
- const cavemanWarning = cavemanTextCompressionEnabled && !ctxReduceEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
195974
+ const cavemanWarning = cavemanTextCompressionEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
195155
195975
  const languageDirective = buildPrimaryLanguageDirective(language);
195156
195976
  const languageGuidance = languageDirective ? `
195157
195977
 
195158
195978
  ${languageDirective}` : "";
195159
- if (!ctxReduceEnabled) {
195979
+ if (!ctxReduceCallable) {
195160
195980
  return `## Magic Context
195161
195981
 
195162
195982
  ${LONG_TERM_PARTNER_FRAME}
@@ -195169,7 +195989,7 @@ ${BASE_INTRO_NO_REDUCE(memoryEnabled)}${smartNoteGuidance}${temporalGuidance}${c
195169
195989
  ${LONG_TERM_PARTNER_FRAME}
195170
195990
  ${PARTNER_FRAME_CLOSER_REDUCE}
195171
195991
 
195172
- ${BASE_INTRO(protectedTags, memoryEnabled)}${smartNoteGuidance}${temporalGuidance}
195992
+ ${BASE_INTRO(protectedTags, memoryEnabled)}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}
195173
195993
  ${GENERIC_SECTION}
195174
195994
 
195175
195995
  Prefer many small targeted operations over one large blanket operation, and keep the working set tidy as routine maintenance.${languageGuidance}`;
@@ -195184,7 +196004,7 @@ function buildMagicContextBlock(opts) {
195184
196004
  const includeGuidance = (opts.includeGuidance ?? true) && !existing.includes(MAGIC_CONTEXT_MARKER);
195185
196005
  if (!includeGuidance)
195186
196006
  return null;
195187
- return buildMagicContextSection(null, opts.protectedTags ?? 20, opts.ctxReduceEnabled ?? true, opts.dreamerEnabled ?? false, opts.temporalAwarenessEnabled ?? false, opts.cavemanTextCompressionEnabled ?? false, false, opts.language, opts.memoryEnabled !== false);
196007
+ return buildMagicContextSection(null, opts.protectedTags ?? 20, opts.ctxReduceCallable ?? true, opts.dreamerEnabled ?? false, opts.temporalAwarenessEnabled ?? false, opts.cavemanTextCompressionEnabled ?? false, false, opts.language, opts.memoryEnabled !== false);
195188
196008
  }
195189
196009
  var DATE_PATTERN = /Today's date: .+/;
195190
196010
  function processSystemPromptForCache(args) {
@@ -195877,6 +196697,7 @@ function applyForwardPressureFloor(trailingPercentage, trailingInputTokens, piUs
195877
196697
  inputTokens: Math.max(trailingInputTokens, forwardTokens)
195878
196698
  } : { percentage: trailingPercentage, inputTokens: trailingInputTokens };
195879
196699
  }
196700
+ var injectM0M1PiForRun = injectM0M1Pi;
195880
196701
  var DEFAULT_CLEAR_REASONING_AGE = 50;
195881
196702
  var PI_STABLE_ID_SCHEME = 1;
195882
196703
  var lastEmergencyNotificationAtMs = new Map;
@@ -196576,7 +197397,6 @@ function registerPiContextHandler(pi, baseOptions) {
196576
197397
  projectIdentity,
196577
197398
  projectDirectory,
196578
197399
  messages: event.messages,
196579
- ctxReduceEnabled: options.ctxReduceEnabled,
196580
197400
  smartDrops: options.smartDrops === true,
196581
197401
  protectedTags: options.protectedTags ?? 20,
196582
197402
  heuristics: options.heuristics,
@@ -196609,7 +197429,8 @@ function registerPiContextHandler(pi, baseOptions) {
196609
197429
  },
196610
197430
  temporalAwareness: options.injection?.temporalAwareness === true,
196611
197431
  appendCompaction: resolvePiAppendCompaction(ctx),
196612
- readBranchEntries: resolvePiReadBranchEntries(ctx)
197432
+ readBranchEntries: resolvePiReadBranchEntries(ctx),
197433
+ isSubagent: sessionMeta.isSubagent
196613
197434
  });
196614
197435
  const piDecisionSnapshotNewestAssistant = branchEntries === null ? undefined : findNewestPiAssistantEntryId(branchEntries);
196615
197436
  if (result.bustedThisPass && piDecisionSnapshotNewestAssistant !== undefined) {
@@ -196691,7 +197512,7 @@ function registerPiContextHandler(pi, baseOptions) {
196691
197512
  }
196692
197513
  try {
196693
197514
  const sessionMetaForCh1 = getOrCreateSessionMeta(options.db, sessionId);
196694
- if (options.ctxReduceEnabled && !sessionMetaForCh1.isSubagent) {
197515
+ if (!sessionMetaForCh1.isSubagent) {
196695
197516
  const resolvedExecuteThresholdPct = resolveExecuteThreshold(schedulerConfig.executeThresholdPercentage ?? 65, liveModelBySession.get(sessionId), 65, {
196696
197517
  tokensConfig: schedulerConfig.executeThresholdTokens,
196697
197518
  contextLimit: usageContextLimit ?? 0
@@ -196819,8 +197640,12 @@ function resolveHistoryBudgetTokensForPi(args) {
196819
197640
  }
196820
197641
  function startPiCompartmentLeaseRenewal(db, sessionId, holderId) {
196821
197642
  return setInterval(() => {
196822
- if (!renewCompartmentLease(db, sessionId, holderId)) {
196823
- sessionLog(sessionId, "compartment lease renewal failed; publish will be skipped if holder is stale");
197643
+ try {
197644
+ if (!renewCompartmentLease(db, sessionId, holderId)) {
197645
+ sessionLog(sessionId, "compartment lease renewal failed; publish will be skipped if holder is stale");
197646
+ }
197647
+ } catch (err) {
197648
+ sessionLog(sessionId, `compartment lease renewal threw; publish will be skipped if holder is stale (${err instanceof Error ? err.message : String(err)})`);
196824
197649
  }
196825
197650
  }, COMPARTMENT_LEASE_RENEWAL_MS);
196826
197651
  }
@@ -196880,6 +197705,11 @@ function spawnPiHistorianRun(args) {
196880
197705
  sessionLog(sessionId, "historian skipped: compartment lease held by another process");
196881
197706
  return;
196882
197707
  }
197708
+ if (isWrapupInProgress(db, sessionId)) {
197709
+ sessionLog(sessionId, "historian skipped: /ctx-wrapup became active");
197710
+ releaseCompartmentLease(db, sessionId, holderId);
197711
+ return;
197712
+ }
196883
197713
  const renewal = startPiCompartmentLeaseRenewal(db, sessionId, holderId);
196884
197714
  try {
196885
197715
  await runPiHistorian({
@@ -196958,6 +197788,10 @@ function maybeFireHistorian(args) {
196958
197788
  sessionLog(sessionId, "historian trigger eval: in-flight, skipping");
196959
197789
  return;
196960
197790
  }
197791
+ if (isWrapupInProgress(db, sessionId)) {
197792
+ sessionLog(sessionId, "historian trigger eval: /ctx-wrapup active, skipping");
197793
+ return;
197794
+ }
196961
197795
  let usage;
196962
197796
  let usageContextLimit;
196963
197797
  try {
@@ -197100,6 +197934,14 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
197100
197934
  unregister();
197101
197935
  }
197102
197936
  }
197937
+ function pendingPiMarkerCoveredByRenderedBoundary(pending, injection) {
197938
+ if (!injection || injection.contentionExhausted)
197939
+ return false;
197940
+ const boundary = injection.renderedBoundary;
197941
+ if (pending.endMessageId === boundary.endMessageId)
197942
+ return true;
197943
+ return boundary.ordinal !== null && pending.ordinal <= boundary.ordinal;
197944
+ }
197103
197945
  async function runPipeline(args) {
197104
197946
  let executedWorkThisPass = false;
197105
197947
  let historyWasConsumedThisPass = false;
@@ -197113,6 +197955,7 @@ async function runPipeline(args) {
197113
197955
  let emergency = false;
197114
197956
  let autoReclaimDidMutateThisPass = false;
197115
197957
  let suppressDeferredHistoryDrain = false;
197958
+ let deferredMaterializationConsumedThisPass = false;
197116
197959
  let casLost = false;
197117
197960
  const deferredHistoryWasPendingAtPassStart = deferredHistoryRefreshSessions.has(args.sessionId);
197118
197961
  if (args.temporalAwareness) {
@@ -197135,6 +197978,8 @@ async function runPipeline(args) {
197135
197978
  return findLatestUserMessageIdPi(args.messages, ids)?.messageId ?? null;
197136
197979
  })();
197137
197980
  const alreadyRanHeuristicsThisTurn = currentTurnId !== null && lastHeuristicsTurnIdBySession.get(args.sessionId) === currentTurnId;
197981
+ const sessionMetaForAvailability = getOrCreateSessionMeta(args.db, args.sessionId);
197982
+ const ctxReduceCallable = !sessionMetaForAvailability.isSubagent;
197138
197983
  const canConsumeDeferredLate = args.schedulerDecision === "execute" || args.forceMaterialization === true || args.contextUsage.percentage >= FORCE_MATERIALIZATION_PERCENTAGE;
197139
197984
  const deferredMaterializeEligible = canConsumeDeferredLate && deferredMaterializationSessions.has(args.sessionId);
197140
197985
  const piHardSignals = args.injection ? (() => {
@@ -197175,7 +198020,7 @@ async function runPipeline(args) {
197175
198020
  }
197176
198021
  const tTag = performance.now();
197177
198022
  const { targets } = tagTranscript(args.sessionId, transcript, args.tagger, args.db, {
197178
- skipPrefixInjection: !args.ctxReduceEnabled,
198023
+ skipPrefixInjection: !ctxReduceCallable,
197179
198024
  entryFingerprintByMessageId
197180
198025
  });
197181
198026
  logTransformTiming(args.sessionId, "tagMessages", tTag);
@@ -197261,14 +198106,16 @@ async function runPipeline(args) {
197261
198106
  sessionLog(args.sessionId, `reasoning replay failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
197262
198107
  }
197263
198108
  }
197264
- try {
197265
- const tags = getTagsByNumbers(args.db, args.sessionId, targetTagNumbers);
197266
- const replayed = replayCavemanCompression(args.sessionId, args.db, targets, tags);
197267
- if (replayed > 0) {
197268
- sessionLog(args.sessionId, `caveman replay: ${replayed} tags re-compressed from source`);
198109
+ if (args.heuristics?.caveman?.enabled && !args.isSubagent) {
198110
+ try {
198111
+ const tags = getTagsByNumbers(args.db, args.sessionId, targetTagNumbers);
198112
+ const replayed = replayCavemanCompression(args.sessionId, args.db, targets, tags);
198113
+ if (replayed > 0) {
198114
+ sessionLog(args.sessionId, `caveman replay: ${replayed} tags re-compressed from source`);
198115
+ }
198116
+ } catch (err) {
198117
+ sessionLog(args.sessionId, `caveman replay failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
197269
198118
  }
197270
- } catch (err) {
197271
- sessionLog(args.sessionId, `caveman replay failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
197272
198119
  }
197273
198120
  let heuristicsExecuted = false;
197274
198121
  let heuristicsResult = null;
@@ -197291,7 +198138,7 @@ async function runPipeline(args) {
197291
198138
  currentTotalInputTokens: args.contextUsage.inputTokens,
197292
198139
  ceilingTokens: args.emergencyCeilingTokens
197293
198140
  } : undefined,
197294
- caveman: args.heuristics.caveman
198141
+ caveman: args.isSubagent ? undefined : args.heuristics.caveman
197295
198142
  }, activeTags, stableIdResolver);
197296
198143
  const heuristicMutationCount = heuristicsResult.droppedTools + heuristicsResult.deduplicatedTools + heuristicsResult.droppedInjections + heuristicsResult.droppedStaleReduceCalls + heuristicsResult.mutatedTextTags;
197297
198144
  droppedCount += heuristicsResult.droppedTools + heuristicsResult.deduplicatedTools + heuristicsResult.droppedInjections + heuristicsResult.droppedStaleReduceCalls + heuristicsResult.mutatedTextTags;
@@ -197314,7 +198161,7 @@ async function runPipeline(args) {
197314
198161
  if (deferredMaterialize && pendingOpsAppliedThisPass) {
197315
198162
  const fullPassSucceeded = shouldRunHeuristics ? heuristicsExecuted : true;
197316
198163
  if (fullPassSucceeded) {
197317
- consumeDeferredMaterialization(args.sessionId);
198164
+ deferredMaterializationConsumedThisPass = consumeDeferredMaterialization(args.sessionId);
197318
198165
  }
197319
198166
  }
197320
198167
  if (args.reasoningClearing && shouldRunHeuristics) {
@@ -197431,7 +198278,7 @@ async function runPipeline(args) {
197431
198278
  if (args.injection) {
197432
198279
  try {
197433
198280
  const tInjection = performance.now();
197434
- injectionResult = injectM0M1Pi({
198281
+ injectionResult = injectM0M1PiForRun({
197435
198282
  sessionId: args.sessionId,
197436
198283
  projectIdentity: args.projectIdentity,
197437
198284
  projectDirectory: args.projectDirectory,
@@ -197463,11 +198310,23 @@ async function runPipeline(args) {
197463
198310
  forceDiscovery: args.stableIdSchemeCutover === true
197464
198311
  });
197465
198312
  const deferredHistoryDrainEligible = historyWasConsumedThisPass && materializationSatisfiedThisPass && (deferredHistoryWasPendingAtPassStart || hasPendingMaterializeSignal) && !suppressDeferredHistoryDrain && !casLost;
198313
+ let preserveDeferredMaterializationForMarkerDrain = false;
197466
198314
  if (deferredHistoryDrainEligible) {
197467
198315
  try {
197468
198316
  const pending = getPendingPiCompactionMarkerState(args.db, args.sessionId);
197469
198317
  if (!pending) {
197470
- consumeDeferredHistoryRefresh(args.sessionId);
198318
+ if (injectionResult?.contentionExhausted === true) {
198319
+ suppressDeferredHistoryDrain = true;
198320
+ preserveDeferredMaterializationForMarkerDrain = true;
198321
+ sessionLog(args.sessionId, "Pi deferred-history drain skipped: m[0]/m[1] used a contention fallback; preserving deferred signals");
198322
+ } else {
198323
+ consumeDeferredHistoryRefresh(args.sessionId);
198324
+ }
198325
+ } else if (!pendingPiMarkerCoveredByRenderedBoundary(pending, injectionResult)) {
198326
+ suppressDeferredHistoryDrain = true;
198327
+ preserveDeferredMaterializationForMarkerDrain = true;
198328
+ const boundary = injectionResult?.renderedBoundary;
198329
+ sessionLog(args.sessionId, `Pi compaction-marker drain skipped: pending ordinal ${pending.ordinal} is newer than rendered boundary ${boundary?.ordinal ?? "<none>"} endMessageId=${boundary?.endMessageId ?? "<none>"}; preserving deferred signals`);
197471
198330
  } else if (!args.appendCompaction || !args.readBranchEntries) {
197472
198331
  suppressDeferredHistoryDrain = true;
197473
198332
  sessionLog(args.sessionId, "Pi compaction-marker drain skipped: sessionManager appendCompaction/getBranch unavailable; preserving deferred-history signal");
@@ -197490,6 +198349,9 @@ async function runPipeline(args) {
197490
198349
  sessionLog(args.sessionId, `Pi compaction-marker drain failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
197491
198350
  }
197492
198351
  }
198352
+ if (preserveDeferredMaterializationForMarkerDrain && deferredMaterializationConsumedThisPass) {
198353
+ signalPiDeferredMaterialization(args.sessionId);
198354
+ }
197493
198355
  if (executedWorkThisPass) {
197494
198356
  try {
197495
198357
  const currentFlag = peekDeferredExecutePending(args.db, args.sessionId);
@@ -197795,6 +198657,9 @@ function existingMarkerAlreadyCoversTarget(sessionId, existing, targetOrdinal, t
197795
198657
  return true;
197796
198658
  }
197797
198659
  function updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, directory) {
198660
+ if (getHarness() !== "opencode") {
198661
+ return true;
198662
+ }
197798
198663
  const targetEndMessageId = getCompartmentEndMessageIdForOrdinal(db, sessionId, lastCompartmentEnd);
197799
198664
  if (!targetEndMessageId) {
197800
198665
  sessionLog(sessionId, `compaction-marker: no compartment endMessageId for ordinal ${lastCompartmentEnd}; preserving existing marker`);
@@ -199042,13 +199907,23 @@ function withPublishedCallback(deps) {
199042
199907
  }
199043
199908
  function startLeaseRenewal(deps, holderId) {
199044
199909
  return setInterval(() => {
199045
- if (!renewCompartmentLease(deps.db, deps.sessionId, holderId)) {
199046
- sessionLog(deps.sessionId, "compartment lease renewal failed; publish will be skipped if holder is stale");
199910
+ try {
199911
+ if (!renewCompartmentLease(deps.db, deps.sessionId, holderId)) {
199912
+ sessionLog(deps.sessionId, "compartment lease renewal failed; publish will be skipped if holder is stale");
199913
+ }
199914
+ } catch (err) {
199915
+ sessionLog(deps.sessionId, `compartment lease renewal threw; publish will be skipped if holder is stale (${err instanceof Error ? err.message : String(err)})`);
199047
199916
  }
199048
199917
  }, COMPARTMENT_LEASE_RENEWAL_MS);
199049
199918
  }
199050
199919
  async function executeContextRecompWithResult(deps, options = {}) {
199051
199920
  const { sessionId } = deps;
199921
+ if (isWrapupInProgress(deps.db, sessionId)) {
199922
+ return {
199923
+ message: "## Magic Recomp — Skipped\n\n/ctx-wrapup is already compacting this session. Wait for it to finish, then try `/ctx-recomp` again.",
199924
+ published: false
199925
+ };
199926
+ }
199052
199927
  if (activeRuns.has(sessionId)) {
199053
199928
  return {
199054
199929
  message: "## Magic Recomp — Skipped\n\nHistorian is already running for this session. Wait for it to finish, then try `/ctx-recomp` again.",
@@ -199072,7 +199947,7 @@ async function executeContextRecompWithResult(deps, options = {}) {
199072
199947
  }).catch((err) => {
199073
199948
  sessionLog(sessionId, "compartment agent: recomp unhandled rejection:", err);
199074
199949
  });
199075
- activeRuns.set(sessionId, { promise: wrappedPromise, published: false });
199950
+ activeRuns.set(sessionId, { promise: wrappedPromise, published: false, kind: "recomp" });
199076
199951
  try {
199077
199952
  const message = await promise2;
199078
199953
  const published = activeRuns.get(sessionId)?.published === true;
@@ -199404,6 +200279,14 @@ ${parsed.message}`,
199404
200279
  });
199405
200280
  return;
199406
200281
  }
200282
+ if (isWrapupInProgress(deps.db, sessionId)) {
200283
+ sendCtxStatusMessage(pi, {
200284
+ title: "/ctx-recomp",
200285
+ text: "## Magic Recomp\n\n/ctx-wrapup is already compacting this session. Wait for it to finish, then try `/ctx-recomp` again.",
200286
+ level: "warning"
200287
+ });
200288
+ return;
200289
+ }
199407
200290
  if (isPiRecompInFlight(sessionId)) {
199408
200291
  sendCtxStatusMessage(pi, {
199409
200292
  title: "/ctx-recomp",
@@ -199795,6 +200678,16 @@ No active Pi session is available.`,
199795
200678
  });
199796
200679
  return;
199797
200680
  }
200681
+ if (isWrapupInProgress(deps.db, sessionId)) {
200682
+ sendCtxStatusMessage(pi, {
200683
+ title: "/ctx-session-upgrade",
200684
+ text: `## Session Upgrade
200685
+
200686
+ /ctx-wrapup is already compacting this session. Wait for it to finish, then try again.`,
200687
+ level: "warning"
200688
+ });
200689
+ return;
200690
+ }
199798
200691
  if (isPiRecompInFlight(sessionId)) {
199799
200692
  sendCtxStatusMessage(pi, {
199800
200693
  title: "/ctx-session-upgrade",
@@ -200126,7 +201019,7 @@ function formatThresholdPercent(value) {
200126
201019
  // package.json
200127
201020
  var package_default = {
200128
201021
  name: "@cortexkit/pi-magic-context",
200129
- version: "0.30.6",
201022
+ version: "0.31.0",
200130
201023
  type: "module",
200131
201024
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
200132
201025
  main: "dist/index.js",
@@ -200392,7 +201285,6 @@ ${safeStringify(tool.parameters)}`);
200392
201285
  inputTokens,
200393
201286
  systemPromptTokens,
200394
201287
  compartmentCount: compartments.length,
200395
- factCount: 0,
200396
201288
  memoryCount: safeRead(() => getMemoryCount(deps.db, deps.projectIdentity), 0),
200397
201289
  memoryBlockCount,
200398
201290
  sessionNoteCount: safeRead(() => getNotes(deps.db, {
@@ -200668,6 +201560,365 @@ function readHistorianState(db, sessionId, meta3) {
200668
201560
  };
200669
201561
  }
200670
201562
 
201563
+ // src/commands/ctx-wrapup.ts
201564
+ import * as crypto4 from "node:crypto";
201565
+ var DEFAULT_MESSAGES_TO_KEEP = 20;
201566
+ var LEASE_WAIT_MS = 1000;
201567
+ function parseWrapupArgs(raw) {
201568
+ const trimmed = raw.trim();
201569
+ if (trimmed === "")
201570
+ return { ok: true, messagesToKeep: DEFAULT_MESSAGES_TO_KEEP };
201571
+ if (!/^\d+$/.test(trimmed)) {
201572
+ return {
201573
+ ok: false,
201574
+ message: "Usage: `/ctx-wrapup [messages_to_keep]` where messages_to_keep is a positive integer."
201575
+ };
201576
+ }
201577
+ const messagesToKeep = Number.parseInt(trimmed, 10);
201578
+ if (!Number.isSafeInteger(messagesToKeep) || messagesToKeep <= 0) {
201579
+ return {
201580
+ ok: false,
201581
+ message: "messages_to_keep must be a positive integer."
201582
+ };
201583
+ }
201584
+ return { ok: true, messagesToKeep };
201585
+ }
201586
+ function registerCtxWrapupCommand(pi, deps) {
201587
+ pi.registerCommand("ctx-wrapup", {
201588
+ description: "Compact older Magic Context history while keeping the newest messages raw",
201589
+ handler: async (args, ctx) => {
201590
+ const sessionId = resolveSessionId(ctx);
201591
+ if (!sessionId) {
201592
+ sendCtxStatusMessage(pi, {
201593
+ title: "/ctx-wrapup",
201594
+ text: `## Magic Wrapup
201595
+
201596
+ No active Pi session is available.`,
201597
+ level: "error"
201598
+ });
201599
+ return;
201600
+ }
201601
+ const sessionMeta = getOrCreateSessionMeta(deps.db, sessionId);
201602
+ if (sessionMeta.isSubagent) {
201603
+ sendCtxStatusMessage(pi, {
201604
+ title: "/ctx-wrapup",
201605
+ text: `## Magic Wrapup — Skipped
201606
+
201607
+ /ctx-wrapup is only available in primary sessions.`,
201608
+ level: "warning"
201609
+ });
201610
+ return;
201611
+ }
201612
+ if (!deps.historianModel) {
201613
+ sendCtxStatusMessage(pi, {
201614
+ title: "/ctx-wrapup",
201615
+ text: "## Magic Wrapup\n\n/ctx-wrapup is unavailable because `historian.model` is not configured.",
201616
+ level: "error"
201617
+ });
201618
+ return;
201619
+ }
201620
+ const parsed = parseWrapupArgs(args);
201621
+ if (!parsed.ok) {
201622
+ sendCtxStatusMessage(pi, {
201623
+ title: "/ctx-wrapup",
201624
+ text: `## Magic Wrapup — Invalid Arguments
201625
+
201626
+ ${parsed.message}`,
201627
+ level: "error"
201628
+ });
201629
+ return;
201630
+ }
201631
+ const result = await runPiWrapup(pi, deps, ctx, sessionId, parsed.messagesToKeep);
201632
+ sendCtxStatusMessage(pi, {
201633
+ title: "/ctx-wrapup",
201634
+ text: result,
201635
+ level: result.includes("Failed") || result.includes("Partial") ? "error" : "success"
201636
+ });
201637
+ }
201638
+ });
201639
+ }
201640
+ async function runPiWrapup(pi, deps, ctx, sessionId, messagesToKeep) {
201641
+ if (getOrCreateSessionMeta(deps.db, sessionId).isSubagent) {
201642
+ return `## Magic Wrapup — Skipped
201643
+
201644
+ /ctx-wrapup is only available in primary sessions.`;
201645
+ }
201646
+ if (isPiRecompInFlight(sessionId)) {
201647
+ return "## Magic Wrapup — Skipped\n\nA recomp or upgrade is already running for this session. Wait for it to finish, then try `/ctx-wrapup` again.";
201648
+ }
201649
+ const provider2 = { readMessages: () => readPiSessionMessages(ctx) };
201650
+ const unregister = setRawMessageProvider(sessionId, provider2);
201651
+ let holderId = "";
201652
+ try {
201653
+ const contextLimit = resolvePiContextLimit(ctx);
201654
+ const modelKey = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
201655
+ const executeThresholdPercentage = resolveExecuteThreshold(deps.executeThresholdPercentage ?? 65, modelKey, 65, {
201656
+ tokensConfig: deps.executeThresholdTokens,
201657
+ contextLimit,
201658
+ sessionId
201659
+ });
201660
+ const initialPlan = resolveWrapupProtectedTailBoundary({
201661
+ db: deps.db,
201662
+ sessionId,
201663
+ mode: "manual-wrapup",
201664
+ contextLimit,
201665
+ executeThresholdPercentage,
201666
+ usage: { percentage: 0, inputTokens: 0 },
201667
+ usageSource: "manual-none",
201668
+ providerShapeVersion: "pi-folded-v1",
201669
+ cacheNamespace: `pi:${sessionId}`,
201670
+ messagesToKeep
201671
+ });
201672
+ if (initialPlan.rawMessagesAboveLastCompartment <= messagesToKeep) {
201673
+ return `## Magic Wrapup
201674
+
201675
+ Nothing to wrap up — only ${initialPlan.rawMessagesAboveLastCompartment} messages above the last compartment.`;
201676
+ }
201677
+ if (!hasRunnableCompartmentWindow(initialPlan.snapshot)) {
201678
+ return `## Magic Wrapup — Partial
201679
+
201680
+ No runnable wrapup boundary is available yet; wrapped up 0 messages into 0 compartments. Run /ctx-wrapup again to continue.`;
201681
+ }
201682
+ holderId = crypto4.randomUUID();
201683
+ const acquired = acquireWrapupInProgress(deps.db, sessionId, {
201684
+ holderId,
201685
+ messagesToKeep,
201686
+ anchorRawMessageCount: initialPlan.anchorRawMessageCount,
201687
+ targetEligibleEndOrdinal: initialPlan.targetEligibleEndOrdinal,
201688
+ lastCompartmentEnd: getLastCompartmentEndMessage(deps.db, sessionId),
201689
+ chunkIndex: 0,
201690
+ expectedChunks: estimateChunks(initialPlan.snapshot.trueRawEligibleTokens, deps.historianChunkTokens)
201691
+ });
201692
+ if (!acquired.ok) {
201693
+ return formatExistingWrapup(acquired.state ?? getWrapupInProgressState(deps.db, sessionId));
201694
+ }
201695
+ let ownershipLost = false;
201696
+ const ownershipLostReason = "another process took over this session's wrapup";
201697
+ const markOwnershipLost = () => {
201698
+ if (ownershipLost)
201699
+ return;
201700
+ ownershipLost = true;
201701
+ };
201702
+ const renewWrapupMarker = (updates) => {
201703
+ const updated = updateWrapupInProgress(deps.db, sessionId, holderId, updates);
201704
+ if (!updated) {
201705
+ markOwnershipLost();
201706
+ return false;
201707
+ }
201708
+ return true;
201709
+ };
201710
+ const renewal = setInterval(() => {
201711
+ try {
201712
+ renewWrapupMarker({
201713
+ lastCompartmentEnd: getLastCompartmentEndMessage(deps.db, sessionId)
201714
+ });
201715
+ } catch (err) {
201716
+ console.warn(`[magic-context][pi] /ctx-wrapup marker renewal failed for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
201717
+ }
201718
+ }, 60000);
201719
+ try {
201720
+ sendCtxStatusMessage(pi, {
201721
+ title: "/ctx-wrapup",
201722
+ text: `## Magic Wrapup
201723
+
201724
+ Eligible history is about ${initialPlan.snapshot.trueRawEligibleTokens.toLocaleString()} tokens across approximately ${estimateChunks(initialPlan.snapshot.trueRawEligibleTokens, deps.historianChunkTokens)} historian chunk(s).`,
201725
+ level: "info"
201726
+ });
201727
+ const startEnd = getLastCompartmentEndMessage(deps.db, sessionId);
201728
+ const startCompartmentCount = getCompartments(deps.db, sessionId).length;
201729
+ let chunkIndex = 0;
201730
+ let lastEnd = startEnd;
201731
+ let targetEligibleEndOrdinal = initialPlan.targetEligibleEndOrdinal;
201732
+ let failure = null;
201733
+ for (;; ) {
201734
+ if (ownershipLost) {
201735
+ failure = `${ownershipLostReason}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.`;
201736
+ break;
201737
+ }
201738
+ if (!renewWrapupMarker({
201739
+ chunkIndex,
201740
+ lastCompartmentEnd: getLastCompartmentEndMessage(deps.db, sessionId)
201741
+ })) {
201742
+ failure = `${ownershipLostReason}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.`;
201743
+ break;
201744
+ }
201745
+ const plan = resolveWrapupProtectedTailBoundary({
201746
+ db: deps.db,
201747
+ sessionId,
201748
+ mode: "manual-wrapup",
201749
+ contextLimit,
201750
+ executeThresholdPercentage,
201751
+ usage: { percentage: 0, inputTokens: 0 },
201752
+ usageSource: "manual-none",
201753
+ providerShapeVersion: "pi-folded-v1",
201754
+ cacheNamespace: `pi:${sessionId}`,
201755
+ messagesToKeep,
201756
+ anchorRawMessageCount: initialPlan.anchorRawMessageCount
201757
+ });
201758
+ targetEligibleEndOrdinal = plan.targetEligibleEndOrdinal;
201759
+ lastEnd = getLastCompartmentEndMessage(deps.db, sessionId);
201760
+ if (lastEnd + 1 >= targetEligibleEndOrdinal)
201761
+ break;
201762
+ if (!hasRunnableCompartmentWindow(plan.snapshot)) {
201763
+ failure = `No runnable wrapup boundary is available yet; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.`;
201764
+ break;
201765
+ }
201766
+ chunkIndex += 1;
201767
+ if (!renewWrapupMarker({
201768
+ chunkIndex,
201769
+ lastCompartmentEnd: getLastCompartmentEndMessage(deps.db, sessionId),
201770
+ targetEligibleEndOrdinal: plan.targetEligibleEndOrdinal,
201771
+ expectedChunks: Math.max(chunkIndex, estimateChunks(plan.snapshot.trueRawEligibleTokens, deps.historianChunkTokens))
201772
+ })) {
201773
+ failure = `${ownershipLostReason}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.`;
201774
+ break;
201775
+ }
201776
+ sendCtxStatusMessage(pi, {
201777
+ title: "/ctx-wrapup",
201778
+ text: `## Magic Wrapup
201779
+
201780
+ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.eligibleEndOrdinal - 1} (~${plan.snapshot.trueRawEligibleTokens.toLocaleString()} eligible tokens remain).`,
201781
+ level: "info"
201782
+ });
201783
+ const leaseHolder = await acquireCompartmentLeaseEventually(deps.db, sessionId, renewWrapupMarker);
201784
+ if (!leaseHolder) {
201785
+ failure = ownershipLost ? `${ownershipLostReason}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.` : "Another Magic Context rebuild started while wrapup was waiting. Run /ctx-wrapup again to continue.";
201786
+ break;
201787
+ }
201788
+ const leaseRenewal = setInterval(() => {
201789
+ try {
201790
+ renewCompartmentLease(deps.db, sessionId, leaseHolder);
201791
+ } catch (err) {
201792
+ console.warn(`[magic-context][pi] /ctx-wrapup compartment lease renewal failed for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
201793
+ }
201794
+ }, COMPARTMENT_LEASE_RENEWAL_MS);
201795
+ try {
201796
+ const runHistorian = deps.runPiHistorianForWrapup ?? runPiHistorian;
201797
+ await runHistorian({
201798
+ db: deps.db,
201799
+ sessionId,
201800
+ directory: ctx.cwd,
201801
+ provider: provider2,
201802
+ runner: deps.runner,
201803
+ historianModel: deps.historianModel,
201804
+ fallbackModels: deps.historianFallbacks,
201805
+ historianChunkTokens: deps.historianChunkTokens,
201806
+ boundarySnapshot: plan.snapshot,
201807
+ refreshBoundarySnapshot: () => resolveWrapupProtectedTailBoundary({
201808
+ db: deps.db,
201809
+ sessionId,
201810
+ mode: "manual-wrapup",
201811
+ contextLimit,
201812
+ executeThresholdPercentage,
201813
+ usage: { percentage: 0, inputTokens: 0 },
201814
+ usageSource: "manual-none",
201815
+ providerShapeVersion: "pi-folded-v1",
201816
+ cacheNamespace: `pi:${sessionId}`,
201817
+ messagesToKeep,
201818
+ anchorRawMessageCount: initialPlan.anchorRawMessageCount
201819
+ }).snapshot,
201820
+ currentContextLimit: contextLimit,
201821
+ historianTimeoutMs: deps.historianTimeoutMs,
201822
+ thinkingLevel: deps.historianThinkingLevel,
201823
+ memoryEnabled: deps.memoryEnabled,
201824
+ autoPromote: deps.autoPromote,
201825
+ userMemoriesEnabled: deps.userMemoriesEnabled,
201826
+ language: deps.language,
201827
+ compartmentLeaseHolderId: leaseHolder,
201828
+ readBranchEntries: () => readBranchEntries(ctx),
201829
+ notifyIssue: (text) => sendCtxStatusMessage(pi, {
201830
+ title: "/ctx-wrapup",
201831
+ text,
201832
+ level: "warning"
201833
+ }),
201834
+ ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
201835
+ forceDrainQuota: true,
201836
+ forceKeepLastCompartment: true,
201837
+ onPublished: () => {
201838
+ updateStatusLine(ctx, { db: deps.db, projectIdentity: ctx.cwd });
201839
+ signalPiDeferredHistoryRefresh(sessionId);
201840
+ signalPiDeferredMaterialization(sessionId);
201841
+ }
201842
+ });
201843
+ } finally {
201844
+ clearInterval(leaseRenewal);
201845
+ releaseCompartmentLease(deps.db, sessionId, leaseHolder);
201846
+ }
201847
+ const afterEnd = getLastCompartmentEndMessage(deps.db, sessionId);
201848
+ if (afterEnd <= lastEnd) {
201849
+ failure = `No forward progress after chunk ${chunkIndex}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.`;
201850
+ break;
201851
+ }
201852
+ lastEnd = afterEnd;
201853
+ }
201854
+ const finalEnd = getLastCompartmentEndMessage(deps.db, sessionId);
201855
+ if (!failure && finalEnd + 1 < targetEligibleEndOrdinal) {
201856
+ failure = `Wrapped up through message ${finalEnd}, but no runnable wrapup boundary remained before target message ${targetEligibleEndOrdinal}. Run /ctx-wrapup again to continue.`;
201857
+ }
201858
+ const finalCompartmentCount = getCompartments(deps.db, sessionId).length;
201859
+ const messagesWrapped = Math.max(0, finalEnd - startEnd);
201860
+ const compartmentsCreated = Math.max(0, finalCompartmentCount - startCompartmentCount);
201861
+ if (failure) {
201862
+ return `## Magic Wrapup — Partial
201863
+
201864
+ Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments; ${failure}`;
201865
+ }
201866
+ try {
201867
+ clearEmergencyRecovery(deps.db, sessionId);
201868
+ } catch {}
201869
+ return `## Magic Wrapup
201870
+
201871
+ Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments. The compacted history is queued and materializes on your next message.`;
201872
+ } finally {
201873
+ clearInterval(renewal);
201874
+ releaseWrapupInProgress(deps.db, sessionId, holderId);
201875
+ }
201876
+ } finally {
201877
+ unregister();
201878
+ }
201879
+ }
201880
+ function resolvePiContextLimit(ctx) {
201881
+ const usage = ctx.getContextUsage?.();
201882
+ if (typeof usage?.contextWindow === "number" && usage.contextWindow > 0) {
201883
+ return usage.contextWindow;
201884
+ }
201885
+ if (typeof ctx.model?.contextWindow === "number" && ctx.model.contextWindow > 0) {
201886
+ return ctx.model.contextWindow;
201887
+ }
201888
+ return 128000;
201889
+ }
201890
+ async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker) {
201891
+ for (;; ) {
201892
+ const holderId = crypto4.randomUUID();
201893
+ const lease = acquireCompartmentLease(db, sessionId, holderId);
201894
+ if (lease)
201895
+ return holderId;
201896
+ if (!renewWrapupMarker({}))
201897
+ return null;
201898
+ await new Promise((resolve3) => setTimeout(resolve3, LEASE_WAIT_MS));
201899
+ }
201900
+ }
201901
+ function estimateChunks(tokens, chunkTokens) {
201902
+ return Math.max(1, Math.ceil(Math.max(0, tokens) / Math.max(1, chunkTokens)));
201903
+ }
201904
+ function formatExistingWrapup(state) {
201905
+ if (!state) {
201906
+ return `## Magic Wrapup — Skipped
201907
+
201908
+ Another /ctx-wrapup is already compacting this session. Wait for it to finish, then try again.`;
201909
+ }
201910
+ return `## Magic Wrapup — Skipped
201911
+
201912
+ Another /ctx-wrapup is already compacting this session (chunk ${state.chunkIndex}/${Math.max(state.chunkIndex, state.expectedChunks)}, wrapped through message ${state.lastCompartmentEnd}). Wait for it to finish, then try again.`;
201913
+ }
201914
+ function readBranchEntries(ctx) {
201915
+ const getBranch = ctx.sessionManager.getBranch;
201916
+ if (typeof getBranch !== "function")
201917
+ return [];
201918
+ const branch = getBranch.call(ctx.sessionManager);
201919
+ return Array.isArray(branch?.entries) ? branch.entries : [];
201920
+ }
201921
+
200671
201922
  // src/pi-pressure.ts
200672
201923
  function extractAssistantUsage(message) {
200673
201924
  if (!message || typeof message !== "object")
@@ -206059,14 +207310,17 @@ Sources (omit for a broad search across all):
206059
207310
  - memory: curated cross-session project knowledge — rules, constraints, conventions.
206060
207311
  - message: the raw conversation behind your compacted history. Hits include message ordinals — expand the surrounding exchange with ctx_expand(start=N-10, end=N+5).
206061
207312
  - git_commit: this repository's commit history.
207313
+ - note: parked decisions, follow-ups, and dismissed notes with their recorded text.
206062
207314
 
206063
207315
  Picking sources:
206064
207316
  - "when did this change / was this working before" → ["git_commit", "message"]
206065
207317
  - "did we discuss this earlier" → ["message"]
207318
+ - "did we decide something about this / leave a follow-up" → ["note"]
206066
207319
  - "what's our convention / rule for X" → ["memory"]`;
206067
207320
 
206068
207321
  // src/tools/ctx-search.ts
206069
207322
  var DEFAULT_LIMIT = 10;
207323
+ var NOTE_EXPAND_HINT = "Use ctx_expand(start=N-10, end=N) around any note @msg anchor above to read the surrounding conversation context.";
206070
207324
  var ParamsSchema5 = exports_typebox.Object({
206071
207325
  query: exports_typebox.String({
206072
207326
  description: "Search query. Matches against memory content, Primers, git commit messages, and raw user/assistant message text."
@@ -206078,9 +207332,10 @@ var ParamsSchema5 = exports_typebox.Object({
206078
207332
  exports_typebox.Literal("memory"),
206079
207333
  exports_typebox.Literal("message"),
206080
207334
  exports_typebox.Literal("git_commit"),
206081
- exports_typebox.Literal("primer")
207335
+ exports_typebox.Literal("primer"),
207336
+ exports_typebox.Literal("note")
206082
207337
  ]), {
206083
- description: 'Optional. Restrict to specific sources. Examples: ["primer"] for standing project explanations, ["git_commit"] for "when did we change X", ["memory"] for naming conventions, ["message"] for "did we discuss this earlier", ["git_commit","message"] for regression hunts. Omit for a broad search across all enabled sources.'
207338
+ description: 'Optional. Restrict to specific sources. Examples: ["primer"] for standing project explanations, ["git_commit"] for "when did we change X", ["memory"] for naming conventions, ["message"] for "did we discuss this earlier", ["note"] for parked decisions or follow-ups, ["git_commit","message"] for regression hunts. Omit for a broad search across all enabled sources.'
206084
207339
  }))
206085
207340
  });
206086
207341
  function normalizeLimit3(limit) {
@@ -206107,7 +207362,7 @@ function formatAge2(committedAtMs) {
206107
207362
  const years = Math.floor(days / 365);
206108
207363
  return years === 1 ? "1y ago" : `${years}y ago`;
206109
207364
  }
206110
- function formatResult(result, index) {
207365
+ function formatResult(result, index, currentSessionId) {
206111
207366
  if (result.source === "memory") {
206112
207367
  const source = result.sourceName ? ` source=${result.sourceName}` : "";
206113
207368
  return [
@@ -206128,6 +207383,14 @@ function formatResult(result, index) {
206128
207383
  `[${index}] [primer] score=${result.score.toFixed(2)} id=${result.primerId} support=${result.support} match=${result.matchType}`,
206129
207384
  result.content
206130
207385
  ].join(`
207386
+ `);
207387
+ }
207388
+ if (result.source === "note") {
207389
+ const anchor = result.anchorOrdinal !== null && result.sourceSessionId === currentSessionId ? ` @msg ${result.anchorOrdinal}` : "";
207390
+ return [
207391
+ `[${index}] [note] score=${result.score.toFixed(2)} id=#${result.noteId} status=${result.status} ${formatAge2(result.createdAt)}${anchor}`,
207392
+ result.content
207393
+ ].join(`
206131
207394
  `);
206132
207395
  }
206133
207396
  if (result.source === "compartment") {
@@ -206145,14 +207408,17 @@ function formatResult(result, index) {
206145
207408
  ].join(`
206146
207409
  `);
206147
207410
  }
206148
- function formatSearchResults(query, results) {
207411
+ function formatSearchResults(query, results, currentSessionId) {
206149
207412
  if (results.length === 0) {
206150
- return `No results found for "${query}" across memories, primers, git commits, or message history.`;
207413
+ return `No results found for "${query}" across notes, memories, primers, git commits, or message history.`;
206151
207414
  }
206152
- const bodyParts = results.map((result, index) => formatResult(result, index + 1));
207415
+ const bodyParts = results.map((result, index) => formatResult(result, index + 1, currentSessionId));
206153
207416
  if (results.some((result) => result.source === "message" || result.source === "compartment")) {
206154
207417
  bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
206155
207418
  }
207419
+ if (results.some((result) => result.source === "note" && result.anchorOrdinal !== null && result.sourceSessionId === currentSessionId)) {
207420
+ bodyParts.push(NOTE_EXPAND_HINT);
207421
+ }
206156
207422
  const body = bodyParts.join(`
206157
207423
 
206158
207424
  `);
@@ -206201,7 +207467,12 @@ function createCtxSearchTool(deps) {
206201
207467
  explicitSearch: true
206202
207468
  });
206203
207469
  return {
206204
- content: [{ type: "text", text: formatSearchResults(query, results) }],
207470
+ content: [
207471
+ {
207472
+ type: "text",
207473
+ text: formatSearchResults(query, results, sessionId)
207474
+ }
207475
+ ],
206205
207476
  details: undefined
206206
207477
  };
206207
207478
  }
@@ -206298,7 +207569,7 @@ function registerMagicContextTools(pi, opts) {
206298
207569
  pi.registerTool(createCtxExpandTool({ db: opts.db }));
206299
207570
  }
206300
207571
  pi.registerTool(createTodowriteTool());
206301
- if (opts.ctxReduceEnabled === true) {
207572
+ if (!opts.sessionScopedToolsDisabled) {
206302
207573
  pi.registerTool(createCtxReduceTool({
206303
207574
  db: opts.db,
206304
207575
  protectedTags: opts.protectedTags ?? 20
@@ -206310,7 +207581,7 @@ function registerMagicContextTools(pi, opts) {
206310
207581
  var PREFIX = "[magic-context][pi]";
206311
207582
  function resolveCurrentProject(ctx) {
206312
207583
  const projectDir = ctx.cwd;
206313
- const projectIdentity = resolveProjectIdentity(projectDir);
207584
+ const projectIdentity = resolveProjectIdentityOrFallback(projectDir);
206314
207585
  return { projectDir, projectIdentity };
206315
207586
  }
206316
207587
  function persistPiMessageEndModelMeta(args) {
@@ -206497,14 +207768,28 @@ async function src_default2(pi) {
206497
207768
  runDeferredV22Backfill(db).catch((err5) => {
206498
207769
  warn(`[v22-backfill] background runner failed: ${err5}`);
206499
207770
  });
207771
+ setTimeout(() => {
207772
+ (async () => {
207773
+ try {
207774
+ const api2 = await loadDefaultPiSessionApi();
207775
+ const sessions = await api2.listSessions();
207776
+ await runSessionProjectBackfill(database, sessions.map((session) => ({
207777
+ sessionId: typeof session?.id === "string" ? session.id : "",
207778
+ directory: typeof session?.cwd === "string" ? session.cwd : ""
207779
+ })));
207780
+ } catch (err5) {
207781
+ warn(`[session-projects] background runner failed: ${err5}`);
207782
+ }
207783
+ })();
207784
+ }, 0);
206500
207785
  const projectDir = process.cwd();
206501
- const projectIdentity = resolveProjectIdentity(projectDir);
207786
+ const projectIdentity = resolveProjectIdentityOrFallback(projectDir);
206502
207787
  const seenDreamerProjectIdentities = new Set([projectIdentity]);
206503
207788
  try {
206504
207789
  const pendingPiMarkerSessions = getSessionsWithPendingPiMarker(db);
206505
207790
  for (const sid of pendingPiMarkerSessions) {
206506
207791
  signalPiDeferredHistoryRefresh(sid);
206507
- signalPiPendingMaterialization(sid);
207792
+ signalPiDeferredMaterialization(sid);
206508
207793
  }
206509
207794
  if (pendingPiMarkerSessions.length > 0) {
206510
207795
  log(`${PREFIX} rehydrated ${pendingPiMarkerSessions.length} Pi deferred compaction marker session(s)`);
@@ -206542,11 +207827,10 @@ async function src_default2(pi) {
206542
207827
  ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
206543
207828
  allowDreamerActions: false,
206544
207829
  memoryToolEnabled: true,
206545
- ctxReduceEnabled: config2.ctx_reduce_enabled === true,
206546
207830
  protectedTags: config2.protected_tags ?? 20,
206547
207831
  dreamerEnabled: isDreamerRunnable(config2)
206548
207832
  });
206549
- info(`registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand${config2.ctx_reduce_enabled === true ? ", ctx_reduce" : ""}`);
207833
+ info("registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand, ctx_reduce");
206550
207834
  const historianConfig = resolveHistorianFromConfig(config2);
206551
207835
  if (historianConfig) {
206552
207836
  historianConfig.onStatusChange = (ctx) => {
@@ -206560,11 +207844,10 @@ async function src_default2(pi) {
206560
207844
  const contextOptionsByDir = new Map;
206561
207845
  const buildContextOptions = (cfg, hist, auto) => ({
206562
207846
  db: database,
206563
- ctxReduceEnabled: cfg.ctx_reduce_enabled,
206564
207847
  smartDrops: cfg.smart_drops === true,
206565
207848
  protectedTags: cfg.protected_tags ?? 20,
206566
207849
  heuristics: {
206567
- caveman: cfg.ctx_reduce_enabled === false && cfg.caveman_text_compression ? {
207850
+ caveman: cfg.caveman_text_compression ? {
206568
207851
  enabled: cfg.caveman_text_compression.enabled,
206569
207852
  minChars: cfg.caveman_text_compression.min_chars
206570
207853
  } : undefined,
@@ -206621,7 +207904,7 @@ async function src_default2(pi) {
206621
207904
  const bootContextOptions = buildContextOptions(config2, historianConfig, autoSearchConfig);
206622
207905
  contextOptionsByDir.set(projectDir, bootContextOptions);
206623
207906
  registerPiContextHandler(pi, bootContextOptions);
206624
- info(historianConfig ? `registered historian trigger (model=${historianConfig.model}, executeThreshold=${historianConfig.executeThresholdPercentage ?? 65}%)` : "registered historian trigger: DISABLED (set historian.model in magic-context.jsonc)");
207907
+ info(historianConfig ? `registered historian trigger (model=${historianConfig.model}, executeThreshold=${formatExecuteThresholdForLog(historianConfig.executeThresholdPercentage)})` : "registered historian trigger: DISABLED (set historian.model in magic-context.jsonc)");
206625
207908
  info(autoSearchConfig.enabled ? `registered auto-search hint (threshold=${autoSearchConfig.scoreThreshold}, minChars=${autoSearchConfig.minPromptChars})` : "registered auto-search hint: DISABLED (memory.auto_search.enabled=false)");
206626
207909
  const sidekickConfig = resolveSidekickFromConfig(config2);
206627
207910
  registerCtxAugCommand(pi, sidekickConfig);
@@ -206659,6 +207942,22 @@ async function src_default2(pi) {
206659
207942
  autoPromote: config2.memory.auto_promote
206660
207943
  });
206661
207944
  info("registered /ctx-recomp");
207945
+ registerCtxWrapupCommand(pi, {
207946
+ db,
207947
+ runner: new PiSubagentRunner,
207948
+ historianModel: historianConfig?.model,
207949
+ historianChunkTokens: deriveHistorianChunkTokens(resolveHistorianContextLimit(historianConfig?.model)),
207950
+ historianFallbacks: historianConfig?.fallbackModels,
207951
+ historianTimeoutMs: config2.historian_timeout_ms,
207952
+ historianThinkingLevel: historianConfig?.thinkingLevel,
207953
+ language: config2.language,
207954
+ memoryEnabled: config2.memory.enabled,
207955
+ autoPromote: config2.memory.auto_promote,
207956
+ userMemoriesEnabled: userMemoryCollectionEnabled(config2.dreamer),
207957
+ executeThresholdPercentage: config2.execute_threshold_percentage,
207958
+ executeThresholdTokens: config2.execute_threshold_tokens
207959
+ });
207960
+ info("registered /ctx-wrapup");
206662
207961
  registerCtxSessionUpgradeCommand(pi, {
206663
207962
  db,
206664
207963
  runner: new PiSubagentRunner,
@@ -206808,10 +208107,10 @@ async function src_default2(pi) {
206808
208107
  memoryEnabled: effectiveConfig.memory.enabled,
206809
208108
  includeGuidance: true,
206810
208109
  protectedTags: effectiveConfig.protected_tags,
206811
- ctxReduceEnabled: effectiveConfig.ctx_reduce_enabled,
208110
+ ctxReduceCallable: true,
206812
208111
  dreamerEnabled: effectiveDreamerRunnable,
206813
208112
  temporalAwarenessEnabled: effectiveConfig.temporal_awareness ?? false,
206814
- cavemanTextCompressionEnabled: effectiveConfig.ctx_reduce_enabled === false && effectiveConfig.caveman_text_compression?.enabled === true,
208113
+ cavemanTextCompressionEnabled: effectiveConfig.caveman_text_compression?.enabled === true,
206815
208114
  language: effectiveConfig.language,
206816
208115
  userMemoriesEnabled: userMemoryCollectionEnabled(effectiveConfig.dreamer),
206817
208116
  isCacheBusting,
@@ -207078,6 +208377,15 @@ ${block}` : event.systemPrompt;
207078
208377
  } catch {}
207079
208378
  });
207080
208379
  }
208380
+ function formatExecuteThresholdForLog(value) {
208381
+ if (value === undefined)
208382
+ return "65%";
208383
+ if (typeof value === "number")
208384
+ return `${value}%`;
208385
+ const overrides = Object.entries(value).filter(([key]) => key !== "default").map(([key, pct]) => `${key}=${pct}%`);
208386
+ const base = `${value.default}%`;
208387
+ return overrides.length > 0 ? `${base} (${overrides.join(", ")})` : base;
208388
+ }
207081
208389
  export {
207082
208390
  resolveSidekickFromConfig,
207083
208391
  resolveHistorianFromConfig,